diff options
author | Dan McDonald <danmcd@joyent.com> | 2022-02-17 11:18:01 -0500 |
---|---|---|
committer | Dan McDonald <danmcd@joyent.com> | 2022-02-17 11:18:01 -0500 |
commit | 6b5cbdc877c63887c7f97b6d8f95fc833c906550 (patch) | |
tree | d63a0c474d67f2af1ad63d22affdbcf587bcc290 /usr/src/boot/common | |
parent | 226e0abadbf2adba4a444e9c65cadbff340be638 (diff) | |
parent | 22028508fd28d36ff74dc02c5774a8ba1f0db045 (diff) | |
download | illumos-joyent-6b5cbdc877c63887c7f97b6d8f95fc833c906550.tar.gz |
[illumos-gate merge]
commit 22028508fd28d36ff74dc02c5774a8ba1f0db045
14480 loader: restructure loader source tree
commit 94afd1448ba04525848cf1165d8deec88a124035
14479 SMB testoplock broken after 13515 (fix check_rtime)
commit 4d723c3fe851d72cc2dc241f5a1777f9e3e85b87
14479 SMB testoplock broken after 13515
commit 0a34963c38fe21eee84ebab010996317731a5171
14475 Recursive death in libfakekernel assfail after 12396
commit 6ce41887bdf3c5c43465fd94dffe1b4acd302afe
14499 i86xpv/unix: variable 'val' is uninitialized
commit dfc4fe31363cc213fe0423dc162bc08298c796cd
14473 ps: only build 64-bit ps
Conflicts:
manifest
usr/src/boot/efi/loader/Makefile.com
usr/src/boot/forth/Makefile
usr/src/boot/i386/loader/Makefile
Diffstat (limited to 'usr/src/boot/common')
59 files changed, 21103 insertions, 0 deletions
diff --git a/usr/src/boot/common/Makefile.inc b/usr/src/boot/common/Makefile.inc new file mode 100644 index 0000000000..bbf81332d2 --- /dev/null +++ b/usr/src/boot/common/Makefile.inc @@ -0,0 +1,70 @@ +# $FreeBSD$ + +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 + +.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 +.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 +.elif ${MACHINE_CPUARCH} == "sparc64" +SRCS+= load_elf64.c reloc_elf64.c +.elif ${MACHINE_ARCH} == "mips64" || ${MACHINE_ARCH} == "mips64el" +SRCS+= load_elf64.c reloc_elf64.c +.elif ${MACHINE_ARCH} == "mips" || ${MACHINE_ARCH} == "mipsel" +SRCS+= load_elf32.c reloc_elf32.c +.endif + +.if defined(LOADER_NET_SUPPORT) +SRCS+= dev_net.c +.endif + +.if !defined(LOADER_NO_DISK_SUPPORT) +SRCS+= disk.c part.c +CFLAGS+= -DLOADER_DISK_SUPPORT +.if !defined(LOADER_NO_GPT_SUPPORT) +SRCS+= crc32.c +CFLAGS+= -DLOADER_GPT_SUPPORT +.endif +.if !defined(LOADER_NO_MBR_SUPPORT) +CFLAGS+= -DLOADER_MBR_SUPPORT +.endif +.endif + +.if defined(HAVE_BCACHE) +SRCS+= bcache.c +.endif + +.if defined(MD_IMAGE_SIZE) +CFLAGS+= -DMD_IMAGE_SIZE=${MD_IMAGE_SIZE} +SRCS+= md.c +.endif + +# Machine-independant ISA PnP +.if defined(HAVE_ISABUS) +SRCS+= isapnp.c +.endif +.if defined(HAVE_PNP) +SRCS+= pnp.c +.endif + +# Forth interpreter +.if defined(BOOT_FORTH) +SRCS+= interp_forth.c +.endif + +.if defined(BOOT_PROMPT_123) +CFLAGS+= -DBOOT_PROMPT_123 +.endif + +.if defined(LOADER_INSTALL_SUPPORT) +SRCS+= install.c +CFLAGS+=-I${.CURDIR}/../../../../lib/libstand +.endif diff --git a/usr/src/boot/common/bcache.c b/usr/src/boot/common/bcache.c new file mode 100644 index 0000000000..5838f02fef --- /dev/null +++ b/usr/src/boot/common/bcache.c @@ -0,0 +1,494 @@ +/* + * Copyright (c) 1998 Michael Smith <msmith@freebsd.org> + * Copyright 2015 Toomas Soome <tsoome@me.com> + * 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> +#include <sys/param.h> + +/* + * Simple hashed block cache + */ + +#include <sys/stdint.h> + +#include <stand.h> +#include <string.h> +#include <strings.h> + +#include "bootstrap.h" + +/* #define BCACHE_DEBUG */ + +#ifdef BCACHE_DEBUG +#define DPRINTF(fmt, args...) printf("%s: " fmt "\n", __func__, ## args) +#else +#define DPRINTF(fmt, args...) ((void)0) +#endif + +struct bcachectl +{ + daddr_t bc_blkno; + int bc_count; +}; + +/* + * bcache per device node. cache is allocated on device first open and freed + * on last close, to save memory. The issue there is the size; biosdisk + * supports up to 31 (0x1f) devices. Classic setup would use single disk + * to boot from, but this has changed with zfs. + */ +struct bcache { + struct bcachectl *bcache_ctl; + caddr_t bcache_data; + size_t bcache_nblks; + size_t ra; +}; + +static uint_t bcache_total_nblks; /* set by bcache_init */ +static uint_t bcache_blksize; /* set by bcache_init */ +static uint_t bcache_numdev; /* set by bcache_add_dev */ +/* statistics */ +static uint_t bcache_units; /* number of devices with cache */ +static uint_t bcache_unit_nblks; /* nblocks per unit */ +static uint_t bcache_hits; +static uint_t bcache_misses; +static uint_t bcache_ops; +static uint_t bcache_bypasses; +static uint_t bcache_bcount; +static uint_t bcache_rablks; + +#define BHASH(bc, blkno) ((blkno) & ((bc)->bcache_nblks - 1)) +#define BCACHE_LOOKUP(bc, blkno) \ + ((bc)->bcache_ctl[BHASH((bc), (blkno))].bc_blkno != (blkno)) +#define BCACHE_READAHEAD 256 +#define BCACHE_MINREADAHEAD 32 + +static void bcache_invalidate(struct bcache *bc, daddr_t blkno); +static void bcache_insert(struct bcache *bc, daddr_t blkno); +static void bcache_free_instance(struct bcache *bc); + +/* + * Initialise the cache for (nblks) of (bsize). + */ +void +bcache_init(size_t nblks, size_t bsize) +{ + /* set up control data */ + bcache_total_nblks = nblks; + bcache_blksize = bsize; +} + +/* + * add number of devices to bcache. we have to divide cache space + * between the devices, so bcache_add_dev() can be used to set up the + * number. The issue is, we need to get the number before actual allocations. + * bcache_add_dev() is supposed to be called from device init() call, so the + * assumption is, devsw dv_init is called for plain devices first, and + * for zfs, last. + */ +void +bcache_add_dev(int devices) +{ + bcache_numdev += devices; +} + +void * +bcache_allocate(void) +{ + uint_t i; + struct bcache *bc = malloc(sizeof (struct bcache)); + int disks = bcache_numdev; + + if (disks == 0) + disks = 1; /* safe guard */ + + if (bc == NULL) { + errno = ENOMEM; + return (bc); + } + + /* + * the bcache block count must be power of 2 for hash function + */ + i = fls(disks) - 1; /* highbit - 1 */ + if (disks > (1 << i)) /* next power of 2 */ + i++; + + bc->bcache_nblks = bcache_total_nblks >> i; + bcache_unit_nblks = bc->bcache_nblks; + bc->bcache_data = malloc(bc->bcache_nblks * bcache_blksize); + if (bc->bcache_data == NULL) { + /* dont error out yet. fall back to 32 blocks and try again */ + bc->bcache_nblks = 32; + bc->bcache_data = malloc(bc->bcache_nblks * bcache_blksize + + sizeof (uint32_t)); + } + + bc->bcache_ctl = malloc(bc->bcache_nblks * sizeof (struct bcachectl)); + + if ((bc->bcache_data == NULL) || (bc->bcache_ctl == NULL)) { + bcache_free_instance(bc); + errno = ENOMEM; + return (NULL); + } + + /* Flush the cache */ + for (i = 0; i < bc->bcache_nblks; i++) { + bc->bcache_ctl[i].bc_count = -1; + bc->bcache_ctl[i].bc_blkno = -1; + } + bcache_units++; + bc->ra = BCACHE_READAHEAD; /* optimistic read ahead */ + return (bc); +} + +void +bcache_free(void *cache) +{ + struct bcache *bc = cache; + + if (bc == NULL) + return; + + bcache_free_instance(bc); + bcache_units--; +} + +/* + * Handle a write request; write directly to the disk, and populate the + * cache with the new values. + */ +static int +write_strategy(void *devdata, int rw, daddr_t blk, size_t size, + char *buf, size_t *rsize) +{ + struct bcache_devdata *dd = (struct bcache_devdata *)devdata; + struct bcache *bc = dd->dv_cache; + daddr_t i, nblk; + + nblk = size / bcache_blksize; + + /* Invalidate the blocks being written */ + for (i = 0; i < nblk; i++) { + bcache_invalidate(bc, blk + i); + } + + /* Write the blocks */ + return (dd->dv_strategy(dd->dv_devdata, rw, blk, size, buf, rsize)); +} + +/* + * Handle a read request; fill in parts of the request that can + * be satisfied by the cache, use the supplied strategy routine to do + * device I/O and then use the I/O results to populate the cache. + */ +static int +read_strategy(void *devdata, int rw, daddr_t blk, size_t size, + char *buf, size_t *rsize) +{ + struct bcache_devdata *dd = devdata; + struct bcache *bc = dd->dv_cache; + size_t i, nblk, p_size, r_size, complete, ra; + int result; + daddr_t p_blk; + caddr_t p_buf; + + if (bc == NULL) { + errno = ENODEV; + return (-1); + } + + if (rsize != NULL) + *rsize = 0; + + nblk = size / bcache_blksize; + if (nblk == 0 && size != 0) + nblk++; + result = 0; + complete = 1; + + /* Satisfy any cache hits up front, break on first miss */ + for (i = 0; i < nblk; i++) { + if (BCACHE_LOOKUP(bc, (daddr_t)(blk + i))) { + bcache_misses += (nblk - i); + complete = 0; + if (nblk - i > BCACHE_MINREADAHEAD && + bc->ra > BCACHE_MINREADAHEAD) + bc->ra >>= 1; /* reduce read ahead */ + break; + } else { + bcache_hits++; + } + } + + if (complete) { /* whole set was in cache, return it */ + if (bc->ra < BCACHE_READAHEAD) + bc->ra <<= 1; /* increase read ahead */ + bcopy(bc->bcache_data + (bcache_blksize * BHASH(bc, blk)), + buf, size); + goto done; + } + + /* + * Fill in any misses. From check we have i pointing to first missing + * block, read in all remaining blocks + readahead. + * We have space at least for nblk - i before bcache wraps. + */ + p_blk = blk + i; + p_buf = bc->bcache_data + (bcache_blksize * BHASH(bc, p_blk)); + r_size = bc->bcache_nblks - BHASH(bc, p_blk); /* remaining blocks */ + + p_size = MIN(r_size, nblk - i); /* read at least those blocks */ + + /* + * The read ahead size setup. + * While the read ahead can save us IO, it also can complicate things: + * 1. We do not want to read ahead by wrapping around the + * bcache end - this would complicate the cache management. + * 2. We are using bc->ra as dynamic hint for read ahead size, + * detected cache hits will increase the read-ahead block count, + * and misses will decrease, see the code above. + * 3. The bcache is sized by 512B blocks, however, the underlying device + * may have a larger sector size, and we should perform the IO by + * taking into account these larger sector sizes. We could solve + * this by passing the sector size to bcache_allocate(), or by + * using ioctl(), but in this version we are using the constant, + * 16 blocks, and are rounding read ahead block count down to + * multiple of 16. Using the constant has two reasons, we are not + * entirely sure if the BIOS disk interface is providing the + * correct value for sector size. And secondly, this way we get + * the most conservative setup for the ra. + * + * The selection of multiple of 16 blocks (8KB) is quite arbitrary, + * however, we want to cover CDs (2K) and 4K disks. + * bcache_allocate() will always fall back to a minimum of 32 blocks. + * Our choice of 16 read ahead blocks will always fit inside the bcache. + */ + + if ((rw & F_NORA) == F_NORA) + ra = 0; + else + ra = bc->bcache_nblks - BHASH(bc, p_blk + p_size); + + if (ra != 0 && ra != bc->bcache_nblks) { /* do we have RA space? */ + ra = MIN(bc->ra, ra - 1); + ra = rounddown(ra, 16); /* multiple of 16 blocks */ + p_size += ra; + } + + /* invalidate bcache */ + for (i = 0; i < p_size; i++) { + bcache_invalidate(bc, p_blk + i); + } + + r_size = 0; + /* + * with read-ahead, it may happen we are attempting to read past + * disk end, as bcache has no information about disk size. + * in such case we should get partial read if some blocks can be + * read or error, if no blocks can be read. + * in either case we should return the data in bcache and only + * return error if there is no data. + */ + rw &= F_MASK; + result = dd->dv_strategy(dd->dv_devdata, rw, p_blk, + p_size * bcache_blksize, p_buf, &r_size); + + r_size /= bcache_blksize; + for (i = 0; i < r_size; i++) + bcache_insert(bc, p_blk + i); + + /* update ra statistics */ + if (r_size != 0) { + if (r_size < p_size) + bcache_rablks += (p_size - r_size); + else + bcache_rablks += ra; + } + + /* check how much data can we copy */ + for (i = 0; i < nblk; i++) { + if (BCACHE_LOOKUP(bc, (daddr_t)(blk + i))) + break; + } + + if (size > i * bcache_blksize) + size = i * bcache_blksize; + + if (size != 0) { + bcopy(bc->bcache_data + (bcache_blksize * BHASH(bc, blk)), + buf, size); + result = 0; + } + +done: + if ((result == 0) && (rsize != NULL)) + *rsize = size; + return (result); +} + +/* + * Requests larger than 1/2 cache size will be bypassed and go + * directly to the disk. XXX tune this. + */ +int +bcache_strategy(void *devdata, int rw, daddr_t blk, size_t size, + char *buf, size_t *rsize) +{ + struct bcache_devdata *dd = (struct bcache_devdata *)devdata; + struct bcache *bc = dd->dv_cache; + uint_t bcache_nblks = 0; + int nblk, cblk, ret; + size_t csize, isize, total; + + bcache_ops++; + + if (bc != NULL) + bcache_nblks = bc->bcache_nblks; + + /* bypass large requests, or when the cache is inactive */ + if (bc == NULL || + ((size * 2 / bcache_blksize) > bcache_nblks)) { + DPRINTF("bypass %zu from %jd", size / bcache_blksize, + (intmax_t)blk); + bcache_bypasses++; + rw &= F_MASK; + return (dd->dv_strategy(dd->dv_devdata, rw, blk, size, buf, + rsize)); + } + + switch (rw & F_MASK) { + case F_READ: + nblk = size / bcache_blksize; + if (size != 0 && nblk == 0) + nblk++; /* read at least one block */ + + ret = 0; + total = 0; + while (size) { + /* # of blocks left */ + cblk = bcache_nblks - BHASH(bc, blk); + cblk = MIN(cblk, nblk); + + if (size <= bcache_blksize) + csize = size; + else + csize = cblk * bcache_blksize; + + ret = read_strategy(devdata, rw, blk, csize, + buf + total, &isize); + + /* + * we may have error from read ahead, if we have read + * some data return partial read. + */ + if (ret != 0 || isize == 0) { + if (total != 0) + ret = 0; + break; + } + blk += isize / bcache_blksize; + total += isize; + size -= isize; + nblk = size / bcache_blksize; + } + + if (rsize) + *rsize = total; + + return (ret); + case F_WRITE: + return (write_strategy(devdata, F_WRITE, blk, size, buf, + rsize)); + } + return (-1); +} + +/* + * Free allocated bcache instance + */ +static void +bcache_free_instance(struct bcache *bc) +{ + if (bc != NULL) { + free(bc->bcache_ctl); + free(bc->bcache_data); + free(bc); + } +} + +/* + * Insert a block into the cache. + */ +static void +bcache_insert(struct bcache *bc, daddr_t blkno) +{ + uint_t cand; + + cand = BHASH(bc, blkno); + + DPRINTF("insert blk %jd -> %u # %d", (intmax_t)blkno, cand, + bcache_bcount); + bc->bcache_ctl[cand].bc_blkno = blkno; + bc->bcache_ctl[cand].bc_count = bcache_bcount++; +} + +/* + * Invalidate a block from the cache. + */ +static void +bcache_invalidate(struct bcache *bc, daddr_t blkno) +{ + uint_t i; + + i = BHASH(bc, blkno); + if (bc->bcache_ctl[i].bc_blkno == blkno) { + bc->bcache_ctl[i].bc_count = -1; + bc->bcache_ctl[i].bc_blkno = -1; + DPRINTF("invalidate blk %jd", (intmax_t)blkno); + } +} + +COMMAND_SET(bcachestat, "bcachestat", "get disk block cache stats", + command_bcache); + +static int +command_bcache(int argc, char *argv[] __unused) +{ + if (argc != 1) { + command_errmsg = "wrong number of arguments"; + return (CMD_ERROR); + } + + printf("\ncache blocks: %u\n", bcache_total_nblks); + printf("cache blocksz: %u\n", bcache_blksize); + printf("cache readahead: %u\n", bcache_rablks); + printf("unit cache blocks: %u\n", bcache_unit_nblks); + printf("cached units: %u\n", bcache_units); + printf("%u ops %u bypasses %u hits %u misses\n", bcache_ops, + bcache_bypasses, bcache_hits, bcache_misses); + return (CMD_OK); +} diff --git a/usr/src/boot/common/boot.c b/usr/src/boot/common/boot.c new file mode 100644 index 0000000000..db5aae9b92 --- /dev/null +++ b/usr/src/boot/common/boot.c @@ -0,0 +1,417 @@ +/* + * 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> + +/* + * Loading modules, booting the system + */ + +#include <stand.h> +#include <string.h> + +#include "bootstrap.h" + +static char *getbootfile(int try); +static int loadakernel(int try, int argc, char *argv[]); + +/* + * List of kernel names to try (may be overwritten by boot.config) + * XXX should move from here? + */ +static const char *default_bootfiles = "kernel"; + +static int autoboot_tried; + +/* + * The user wants us to boot. + */ +COMMAND_SET(boot, "boot", "boot a file or loaded kernel", command_boot); + +static int +command_boot(int argc, char *argv[]) +{ + struct preloaded_file *fp; + + /* + * See if the user has specified an explicit kernel to boot. + */ + if ((argc > 1) && (argv[1][0] == '/')) { + + /* XXX maybe we should discard everything and start again? */ + if (file_findfile(NULL, NULL) != NULL) { + snprintf(command_errbuf, sizeof (command_errbuf), + "can't boot '%s', kernel module already loaded", + argv[1]); + return (CMD_ERROR); + } + + /* find/load the kernel module */ + if (mod_loadkld(argv[1], argc - 2, argv + 2) != 0) + return (CMD_ERROR); + /* we have consumed all arguments */ + argc = 1; + } + + /* + * See if there is a kernel module already loaded + */ + if (file_findfile(NULL, NULL) == NULL) + if (loadakernel(0, argc - 1, argv + 1)) { + /* we have consumed all arguments */ + argc = 1; + } + + /* + * Loaded anything yet? + */ + if ((fp = file_findfile(NULL, NULL)) == NULL) { + command_errmsg = "no bootable kernel"; + return (CMD_ERROR); + } + + /* + * If we were given arguments, discard any previous. + * XXX should we merge arguments? Hard to DWIM. + */ + if (argc > 1) { + free(fp->f_args); + fp->f_args = unargv(argc - 1, argv + 1); + } + + /* Hook for platform-specific autoloading of modules */ + if (archsw.arch_autoload() != 0) + return (CMD_ERROR); + + /* Call the exec handler from the loader matching the kernel */ + file_formats[fp->f_loader]->l_exec(fp); + return (CMD_ERROR); +} + + +/* + * Autoboot after a delay + */ + +COMMAND_SET(autoboot, "autoboot", "boot automatically after a delay", + command_autoboot); + +static int +command_autoboot(int argc, char *argv[]) +{ + int howlong; + char *cp, *prompt; + + prompt = NULL; + howlong = -1; + switch (argc) { + case 3: + prompt = argv[2]; + /* FALLTHROUGH */ + case 2: + howlong = strtol(argv[1], &cp, 0); + if (*cp != 0) { + snprintf(command_errbuf, sizeof (command_errbuf), + "bad delay '%s'", argv[1]); + return (CMD_ERROR); + } + /* FALLTHROUGH */ + case 1: + return (autoboot(howlong, prompt)); + } + + command_errmsg = "too many arguments"; + return (CMD_ERROR); +} + +/* + * Called before we go interactive. If we think we can autoboot, and + * we haven't tried already, try now. + */ +void +autoboot_maybe(void) +{ + char *cp; + + /* compatibility with sparc prom, check for autoboot? */ + cp = getenv("autoboot?"); + if (cp != NULL && strcasecmp(cp, "true") != 0) + return; + cp = getenv("autoboot_delay"); + if ((autoboot_tried == 0) && ((cp == NULL) || strcasecmp(cp, "NO"))) + autoboot(-1, NULL); /* try to boot automatically */ +} + +int +autoboot(int timeout, char *prompt) +{ + time_t when, otime, ntime; + int c, yes; + char *argv[2], *cp, *ep; + char *kernelname; + struct preloaded_file *fp; + + autoboot_tried = 1; + + if (timeout == -1) { + timeout = 10; + /* try to get a delay from the environment */ + if ((cp = getenv("autoboot_delay"))) { + timeout = strtol(cp, &ep, 0); + if (cp == ep) + timeout = 10; /* Unparseable? Set default! */ + } + } + + fp = file_findfile(NULL, NULL); + if (fp == NULL) { + /* no preloaded files, run command start to load all */ + bf_run("start"); + fp = file_findfile(NULL, NULL); + if (fp == NULL) { /* still nothing? can't boot */ + command_errmsg = "no valid kernel found"; + return (CMD_ERROR); + } + } + + kernelname = fp->f_name; + + if (timeout >= 0) { + otime = time(NULL); + when = otime + timeout; /* when to boot */ + + yes = 0; + + printf("%s\n", (prompt == NULL) ? + "Hit [Enter] to boot immediately, or any other key " + "for command prompt." : prompt); + + for (;;) { + if (ischar()) { + c = getchar(); + if ((c == '\r') || (c == '\n')) + yes = 1; + break; + } + ntime = time(NULL); + if (ntime >= when) { + yes = 1; + break; + } + + if (ntime != otime) { + printf("\rBooting [%s] in %d second%s... ", + kernelname, (int)(when - ntime), + (when - ntime) == 1? "":"s"); + otime = ntime; + } + } + } else { + yes = 1; + } + + if (yes) + printf("\rBooting [%s]... ", kernelname); + putchar('\n'); + if (yes) { + argv[0] = "boot"; + argv[1] = NULL; + return (command_boot(1, argv)); + } + return (CMD_OK); +} + +/* + * Scrounge for the name of the (try)'th file we will try to boot. + */ +static char * +getbootfile(int try) +{ + static char *name = NULL; + const char *spec, *ep; + size_t len; + + /* we use dynamic storage */ + free(name); + name = NULL; + + /* + * Try $bootfile, then try our builtin default + */ + if ((spec = getenv("bootfile")) == NULL) + spec = default_bootfiles; + + while ((try > 0) && (spec != NULL)) { + spec = strchr(spec, ';'); + if (spec) + spec++; /* skip over the leading ';' */ + try--; + } + if (spec != NULL) { + if ((ep = strchr(spec, ';')) != NULL) { + len = ep - spec; + } else { + len = strlen(spec); + } + name = malloc(len + 1); + strncpy(name, spec, len); + name[len] = 0; + } + if (name && name[0] == 0) { + free(name); + name = NULL; + } + return (name); +} + +/* + * Try to find the /etc/fstab file on the filesystem (rootdev), + * which should be be the root filesystem, and parse it to find + * out what the kernel ought to think the root filesystem is. + * + * If we're successful, set vfs.root.mountfrom to <vfstype>:<path> + * so that the kernel can tell both which VFS and which node to use + * to mount the device. If this variable's already set, don't + * overwrite it. + */ +int +getrootmount(char *rootdev) +{ + char lbuf[128], *cp, *ep, *dev, *fstyp, *options; + int fd, error; + + if (getenv("vfs.root.mountfrom") != NULL) + return (0); + + error = 1; + sprintf(lbuf, "%s/etc/fstab", rootdev); + if ((fd = open(lbuf, O_RDONLY)) < 0) + goto notfound; + + /* + * loop reading lines from /etc/fstab + * What was that about sscanf again? + */ + fstyp = NULL; + dev = NULL; + while (fgetstr(lbuf, sizeof (lbuf), fd) >= 0) { + if ((lbuf[0] == 0) || (lbuf[0] == '#')) + continue; + + /* skip device name */ + for (cp = lbuf; (*cp != 0) && !isspace(*cp); cp++) + ; + if (*cp == 0) /* misformatted */ + continue; + /* delimit and save */ + *cp++ = 0; + free(dev); + dev = strdup(lbuf); + + /* skip whitespace up to mountpoint */ + while ((*cp != 0) && isspace(*cp)) + cp++; + /* must have /<space> to be root */ + if ((*cp == 0) || (*cp != '/') || !isspace(*(cp + 1))) + continue; + /* skip whitespace up to fstype */ + cp += 2; + while ((*cp != 0) && isspace(*cp)) + cp++; + if (*cp == 0) /* misformatted */ + continue; + /* skip text to end of fstype and delimit */ + ep = cp; + while ((*cp != 0) && !isspace(*cp)) + cp++; + *cp = 0; + free(fstyp); + fstyp = strdup(ep); + + /* skip whitespace up to mount options */ + cp += 1; + while ((*cp != 0) && isspace(*cp)) + cp++; + if (*cp == 0) /* misformatted */ + continue; + /* skip text to end of mount options and delimit */ + ep = cp; + while ((*cp != 0) && !isspace(*cp)) + cp++; + *cp = 0; + options = strdup(ep); + /* + * Build the <fstype>:<device> and save it in + * vfs.root.mountfrom + */ + sprintf(lbuf, "%s:%s", fstyp, dev); + setenv("vfs.root.mountfrom", lbuf, 0); + + /* + * Don't override vfs.root.mountfrom.options if it is + * already set + */ + if (getenv("vfs.root.mountfrom.options") == NULL) { + /* save mount options */ + setenv("vfs.root.mountfrom.options", options, 0); + } + free(options); + error = 0; + break; + } + close(fd); + free(dev); + free(fstyp); + +notfound: + if (error) { + const char *currdev; + + currdev = getenv("currdev"); + if (currdev != NULL && strncmp("zfs:", currdev, 4) == 0) { + cp = strdup(currdev); + cp[strlen(cp) - 1] = '\0'; + setenv("vfs.root.mountfrom", cp, 0); + error = 0; + free(cp); + } + } + + return (error); +} + +static int +loadakernel(int try, int argc, char *argv[]) +{ + char *cp; + + for (try = 0; (cp = getbootfile(try)) != NULL; try++) + if (mod_loadkld(cp, argc - 1, argv + 1) != 0) + printf("can't load '%s'\n", cp); + else + return (1); + return (0); +} diff --git a/usr/src/boot/common/bootstrap.h b/usr/src/boot/common/bootstrap.h new file mode 100644 index 0000000000..689905ae68 --- /dev/null +++ b/usr/src/boot/common/bootstrap.h @@ -0,0 +1,418 @@ +/* + * 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. + */ + +#ifndef _BOOTSTRAP_H_ +#define _BOOTSTRAP_H_ + +#include <sys/types.h> +#include <sys/queue.h> +#include <sys/linker_set.h> +#include <stdbool.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(const char *rc); +int include(const char *filename); + +/* interp_backslash.c */ +char *backslash(char *str); + +/* interp_parse.c */ +int parse(int *argc, char ***argv, char *str); + +/* interp_forth.c */ +void bf_init(char *rc); +int bf_run(char *line); + +/* boot.c */ +int autoboot(int timeout, char *prompt); +void autoboot_maybe(void); +int getrootmount(char *rootdev); + +/* misc.c */ +char *unargv(int argc, char *argv[]); +void hexdump(caddr_t region, size_t len); +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(int fd, vm_offset_t dest, size_t len, off_t off); +void *alloc_pread(int 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 *devdata, int rw, daddr_t blk, + size_t size, char *buf, size_t *rsize); + 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 */ +#define C_MODERAW (1<<5) /* raw mode */ + + /* set c_flags to match hardware */ + void (*c_probe)(struct console *); + /* reinit XXX may need more args */ + int (*c_init)(struct console *, int); + /* emit c */ + void (*c_out)(struct console *, int); + /* wait for and return input */ + int (*c_in)(struct console *); + /* return nonzero if input is waiting */ + int (*c_ready)(struct console *); + int (*c_ioctl)(struct console *, int, void *); + /* Print device info */ + void (*c_devinfo)(struct console *); + void *c_private; /* private data */ +}; +extern struct console *consoles[]; +void cons_probe(void); +void cons_mode(int); +void autoload_font(bool); + +/* + * Plug-and-play enumerator/configurator interface. + */ +struct pnphandler +{ + const char *pp_name; /* handler/bus name */ + /* enumerate PnP devices, add to chain */ + void (*pp_enumerate)(void); +}; + +struct pnpident +{ + /* ASCII identifier, actual format varies with bus/handler */ + char *id_ident; + STAILQ_ENTRY(pnpident) id_link; +}; + +struct pnpinfo +{ + /* ASCII description, optional */ + char *pi_desc; + /* optional revision (or -1) if not supported */ + int pi_revision; + /* module/args nominated to handle device */ + char *pi_module; + /* module arguments */ + int pi_argc; + char **pi_argv; + /* handler which detected this device */ + struct pnphandler *pi_handler; + /* list of identifiers */ + STAILQ_HEAD(, pnpident) pi_ident; + 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[]; + +/* + * 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; + /* data are immediately appended */ + char md_data[1]; +}; + +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 */ + /* verbose file type, eg 'ELF kernel', 'pnptable', etc. */ + char *f_type; + char *f_args; /* arguments for the file */ + /* metadata that will be placed in the module directory */ + struct file_metadata *f_metadata; + /* index of the loader that read the file */ + int f_loader; + 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 argc, + char **argv, 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 build_environment_module(void); +void build_font_module(void); +vm_offset_t bi_copyenv(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 *, Elf_Size); + +int elf64_loadfile(char *, uint64_t, struct preloaded_file **); +int elf32_loadfile(char *, uint64_t, struct preloaded_file **); +int elf64_obj_loadfile(char *, uint64_t, struct preloaded_file **); +int elf32_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 elf64_loadfile_raw(char *, uint64_t, struct preloaded_file **, int); +int elf32_loadfile_raw(char *, uint64_t, struct preloaded_file **, int); +int elf64_load_modmetadata(struct preloaded_file *, uint64_t); +int elf32_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 *src, vm_offset_t dest, + const size_t len); + /* + * Copy to local address space from module address space, + * similar to bcopy() + */ + ssize_t (*arch_copyout)(const vm_offset_t src, void *dest, + const size_t len); + /* Read from file to module address space, same semantics as read() */ + ssize_t (*arch_readin)(const int fd, vm_offset_t dest, + const size_t len); + /* 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. + */ + vm_offset_t (*arch_loadaddr)(uint_t type, void *data, vm_offset_t addr); +#define LOAD_ELF 1 /* data points to the ELF header. */ +#define LOAD_RAW 2 /* data points to the module file name. */ +#define LOAD_KERN 3 /* data points to the kernel file name. */ +#define LOAD_MEM 4 /* data points to int for buffer size. */ + /* + * Interface to release the load address. + */ + void (*arch_free_loadaddr)(vm_offset_t addr, size_t pages); + + /* + * 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); +}; +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 /* Allow lint to override */ +#define CTASSERT(x) _CTASSERT(x, __LINE__) +#define _CTASSERT(x, y) __CTASSERT(x, y) +#define __CTASSERT(x, y) typedef char __assert ## y[(x) ? 1 : -1] +#endif + +#endif /* !_BOOTSTRAP_H_ */ diff --git a/usr/src/boot/common/commands.c b/usr/src/boot/common/commands.c new file mode 100644 index 0000000000..04a2bf591f --- /dev/null +++ b/usr/src/boot/common/commands.c @@ -0,0 +1,533 @@ +/* + * 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> + +#include <stand.h> +#include <string.h> + +#include "bootstrap.h" + +const char *command_errmsg; +/* XXX should have procedural interface for setting, size limit? */ +char command_errbuf[COMMAND_ERRBUFSZ]; + +static int page_file(char *filename); + +/* BEGIN CSTYLED */ +/* + * Help is read from a formatted text file. + * + * Entries in the file are formatted as + +# Ttopic [Ssubtopic] Ddescription +help +text +here +# + + * + * Note that for code simplicity's sake, the above format must be followed + * exactly. + * + * Subtopic entries must immediately follow the topic (this is used to + * produce the listing of subtopics). + * + * If no argument(s) are supplied by the user, the help for 'help' is displayed. + */ +/* END CSTYLED */ +COMMAND_SET(help, "help", "detailed help", command_help); + +static int +help_getnext(int fd, char **topic, char **subtopic, char **desc) +{ + char line[81], *cp, *ep; + + /* Make sure we provide sane values. */ + *topic = *subtopic = *desc = NULL; + for (;;) { + if (fgetstr(line, 80, fd) < 0) + return (0); + + if (strlen(line) < 3 || line[0] != '#' || line[1] != ' ') + continue; + + *topic = *subtopic = *desc = NULL; + cp = line + 2; + while (cp != NULL && *cp != 0) { + ep = strchr(cp, ' '); + if (*cp == 'T' && *topic == NULL) { + if (ep != NULL) + *ep++ = 0; + *topic = strdup(cp + 1); + } else if (*cp == 'S' && *subtopic == NULL) { + if (ep != NULL) + *ep++ = 0; + *subtopic = strdup(cp + 1); + } else if (*cp == 'D') { + *desc = strdup(cp + 1); + ep = NULL; + } + cp = ep; + } + if (*topic == NULL) { + free(*subtopic); + free(*desc); + *subtopic = *desc = NULL; + continue; + } + return (1); + } +} + +static int +help_emitsummary(char *topic, char *subtopic, char *desc) +{ + int i; + + pager_output(" "); + pager_output(topic); + i = strlen(topic); + if (subtopic != NULL) { + pager_output(" "); + pager_output(subtopic); + i += strlen(subtopic) + 1; + } + if (desc != NULL) { + do { + pager_output(" "); + } while (i++ < 30); + pager_output(desc); + } + return (pager_output("\n")); +} + + +static int +command_help(int argc, char *argv[]) +{ + char buf[81]; /* XXX buffer size? */ + int hfd, matched, doindex; + char *topic, *subtopic, *t, *s, *d; + + /* page the help text from our load path */ + snprintf(buf, sizeof (buf), "%s/boot/loader.help", getenv("loaddev")); + if ((hfd = open(buf, O_RDONLY)) < 0) { + printf("Verbose help not available, " + "use '?' to list commands\n"); + return (CMD_OK); + } + + /* pick up request from arguments */ + topic = subtopic = NULL; + switch (argc) { + case 3: + subtopic = strdup(argv[2]); + /* FALLTHROUGH */ + case 2: + topic = strdup(argv[1]); + break; + case 1: + topic = strdup("help"); + break; + default: + command_errmsg = "usage is 'help <topic> [<subtopic>]"; + close(hfd); + return (CMD_ERROR); + } + + /* magic "index" keyword */ + doindex = strcmp(topic, "index") == 0? 1 : 0; + matched = doindex; + + /* Scan the helpfile looking for help matching the request */ + pager_open(); + while (help_getnext(hfd, &t, &s, &d)) { + + if (doindex) { /* dink around formatting */ + if (help_emitsummary(t, s, d)) + break; + + } else if (strcmp(topic, t)) { + /* topic mismatch */ + if (matched) { + /* nothing more on this topic, stop scanning */ + break; + } + } else { + /* topic matched */ + matched = 1; + if ((subtopic == NULL && s == NULL) || + (subtopic != NULL && s != NULL && + strcmp(subtopic, s) == 0)) { + /* exact match, print text */ + while (fgetstr(buf, 80, hfd) >= 0 && + buf[0] != '#') { + if (pager_output(buf)) + break; + if (pager_output("\n")) + break; + } + } else if (subtopic == NULL && s != NULL) { + /* topic match, list subtopics */ + if (help_emitsummary(t, s, d)) + break; + } + } + free(t); + free(s); + free(d); + t = s = d = NULL; + } + free(t); + free(s); + free(d); + pager_close(); + close(hfd); + if (!matched) { + snprintf(command_errbuf, sizeof (command_errbuf), + "no help available for '%s'", topic); + free(topic); + free(subtopic); + return (CMD_ERROR); + } + free(topic); + free(subtopic); + return (CMD_OK); +} + +COMMAND_SET(commandlist, "?", "list commands", command_commandlist); + +static int +command_commandlist(int argc __unused, char *argv[] __unused) +{ + struct bootblk_command **cmdp; + int res; + char name[20]; + + res = 0; + pager_open(); + res = pager_output("Available commands:\n"); + SET_FOREACH(cmdp, Xcommand_set) { + if (res) + break; + if ((*cmdp)->c_name != NULL && (*cmdp)->c_desc != NULL) { + snprintf(name, sizeof (name)," %-15s ", + (*cmdp)->c_name); + pager_output(name); + pager_output((*cmdp)->c_desc); + res = pager_output("\n"); + } + } + pager_close(); + return (CMD_OK); +} + +/* + * XXX set/show should become set/echo if we have variable + * substitution happening. + */ + +COMMAND_SET(show, "show", "show variable(s)", command_show); + +static int +command_show(int argc, char *argv[]) +{ + struct env_var *ev; + char *cp; + + if (argc < 2) { + /* + * With no arguments, print everything. + */ + pager_open(); + for (ev = environ; ev != NULL; ev = ev->ev_next) { + pager_output(ev->ev_name); + cp = getenv(ev->ev_name); + if (cp != NULL) { + pager_output("="); + pager_output(cp); + } + if (pager_output("\n")) + break; + } + pager_close(); + } else { + if ((cp = getenv(argv[1])) != NULL) { + printf("%s\n", cp); + } else { + snprintf(command_errbuf, sizeof (command_errbuf), + "variable '%s' not found", argv[1]); + return (CMD_ERROR); + } + } + return (CMD_OK); +} + +COMMAND_SET(set, "set", "set a variable", command_set); + +static int +command_set(int argc, char *argv[]) +{ + int err; + + if (argc != 2) { + command_errmsg = "wrong number of arguments"; + return (CMD_ERROR); + } else { + if ((err = putenv(argv[1])) != 0) { + command_errmsg = strerror(err); + return (CMD_ERROR); + } + } + return (CMD_OK); +} + +COMMAND_SET(setprop, "setprop", "set a variable", command_setprop); + +static int +command_setprop(int argc, char *argv[]) +{ + int err; + + if (argc != 3) { + command_errmsg = "wrong number of arguments"; + return (CMD_ERROR); + } else { + if ((err = setenv(argv[1], argv[2], 1)) != 0) { + command_errmsg = strerror(err); + return (CMD_ERROR); + } + } + return (CMD_OK); +} + +COMMAND_SET(unset, "unset", "unset a variable", command_unset); + +static int +command_unset(int argc, char *argv[]) +{ + int err; + + if (argc != 2) { + command_errmsg = "wrong number of arguments"; + return (CMD_ERROR); + } else { + if ((err = unsetenv(argv[1])) != 0) { + command_errmsg = strerror(err); + return (CMD_ERROR); + } + } + return (CMD_OK); +} + +COMMAND_SET(echo, "echo", "echo arguments", command_echo); + +static int +command_echo(int argc, char *argv[]) +{ + char *s; + int nl, ch; + + nl = 0; + optind = 1; + optreset = 1; + while ((ch = getopt(argc, argv, "n")) != -1) { + switch (ch) { + case 'n': + nl = 1; + break; + case '?': + default: + /* getopt has already reported an error */ + return (CMD_OK); + } + } + argv += (optind); + argc -= (optind); + + s = unargv(argc, argv); + if (s != NULL) { + printf("%s", s); + free(s); + } + if (!nl) + printf("\n"); + return (CMD_OK); +} + +/* + * A passable emulation of the sh(1) command of the same name. + */ + +COMMAND_SET(read, "read", "read input from the terminal", command_read); + +static int +command_read(int argc, char *argv[]) +{ + char *prompt; + int timeout; + time_t when; + char *cp; + char *name; + char buf[256]; /* XXX size? */ + int c; + + timeout = -1; + prompt = NULL; + optind = 1; + optreset = 1; + while ((c = getopt(argc, argv, "p:t:")) != -1) { + switch (c) { + case 'p': + prompt = optarg; + break; + case 't': + timeout = strtol(optarg, &cp, 0); + if (cp == optarg) { + snprintf(command_errbuf, + sizeof (command_errbuf), + "bad timeout '%s'", optarg); + return (CMD_ERROR); + } + break; + default: + return (CMD_OK); + } + } + + argv += (optind); + argc -= (optind); + name = (argc > 0) ? argv[0]: NULL; + + if (prompt != NULL) + printf("%s", prompt); + if (timeout >= 0) { + when = time(NULL) + timeout; + while (!ischar()) + if (time(NULL) >= when) + return (CMD_OK); /* is timeout an error? */ + } + + ngets(buf, sizeof (buf)); + + if (name != NULL) + setenv(name, buf, 1); + return (CMD_OK); +} + +/* + * File pager + */ +COMMAND_SET(more, "more", "show contents of a file", command_more); + +static int +command_more(int argc, char *argv[]) +{ + int i; + int res; + char line[80]; + + res = 0; + pager_open(); + for (i = 1; (i < argc) && (res == 0); i++) { + snprintf(line, sizeof (line), "*** FILE %s BEGIN ***\n", + argv[i]); + if (pager_output(line)) + break; + res = page_file(argv[i]); + if (!res) { + snprintf(line, sizeof (line), "*** FILE %s END ***\n", + argv[i]); + res = pager_output(line); + } + } + pager_close(); + + if (res == 0) + return (CMD_OK); + else + return (CMD_ERROR); +} + +static int +page_file(char *filename) +{ + int result; + + result = pager_file(filename); + + if (result == -1) { + snprintf(command_errbuf, sizeof (command_errbuf), + "error showing %s", filename); + } + + return (result); +} + +/* + * List all disk-like devices + */ +COMMAND_SET(lsdev, "lsdev", "list all devices", command_lsdev); + +static int +command_lsdev(int argc, char *argv[]) +{ + int verbose, ch, i; + char line[80]; + + 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); + } + } + argv += (optind); + argc -= (optind); + + pager_open(); + for (i = 0; devsw[i] != NULL; i++) { + if (devsw[i]->dv_print != NULL) { + if (devsw[i]->dv_print(verbose)) + break; + } else { + snprintf(line, sizeof (line), "%s: (unknown)\n", + devsw[i]->dv_name); + if (pager_output(line)) + break; + } + } + pager_close(); + return (CMD_OK); +} diff --git a/usr/src/boot/common/console.c b/usr/src/boot/common/console.c new file mode 100644 index 0000000000..a847e19fb9 --- /dev/null +++ b/usr/src/boot/common/console.c @@ -0,0 +1,384 @@ +/* + * 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. + */ +/* + * Copyright (c) 2019, Joyent, Inc. + */ +#include <sys/cdefs.h> + +#include <stand.h> +#include <string.h> + +#include "bootstrap.h" +/* + * Core console support + */ + +static int cons_set(struct env_var *ev, int flags, const void *value); +static int cons_find(const char *name); +static int cons_check(const char *string); +static int cons_change(const char *string); +static int twiddle_set(struct env_var *ev, int flags, const void *value); + +/* + * Detect possible console(s) to use. If preferred console(s) have been + * specified, mark them as active. Else, mark the first probed console + * as active. Also create the console variable. + */ +void +cons_probe(void) +{ + int cons; + int active; + char *prefconsole; + + /* We want a callback to install the new value when this var changes. */ + env_setenv("twiddle_divisor", EV_VOLATILE, "1", twiddle_set, + env_nounset); + + /* Do all console probes */ + for (cons = 0; consoles[cons] != NULL; cons++) { + consoles[cons]->c_flags = 0; + consoles[cons]->c_probe(consoles[cons]); + } + /* Now find the first working one */ + active = -1; + for (cons = 0; consoles[cons] != NULL && active == -1; cons++) { + consoles[cons]->c_flags = 0; + consoles[cons]->c_probe(consoles[cons]); + if (consoles[cons]->c_flags == (C_PRESENTIN | C_PRESENTOUT)) + active = cons; + } + /* Force a console even if all probes failed */ + if (active == -1) + active = 0; + + /* Check to see if a console preference has already been registered */ + prefconsole = getenv("console"); + if (prefconsole != NULL) + prefconsole = strdup(prefconsole); + if (prefconsole != NULL) { + unsetenv("console"); /* we want to replace this */ + cons_change(prefconsole); + } else { + consoles[active]->c_flags |= C_ACTIVEIN | C_ACTIVEOUT; + consoles[active]->c_init(consoles[active], 0); + prefconsole = strdup(consoles[active]->c_name); + } + + printf("Consoles: "); + for (cons = 0; consoles[cons] != NULL; cons++) + if (consoles[cons]->c_flags & (C_ACTIVEIN | C_ACTIVEOUT)) + printf("%s ", consoles[cons]->c_desc); + printf("\n"); + + if (prefconsole != NULL) { + env_setenv("console", EV_VOLATILE, prefconsole, cons_set, + env_nounset); + free(prefconsole); + } +} + +void +cons_mode(int raw) +{ + int cons; + + for (cons = 0; consoles[cons] != NULL; cons++) { + if (raw == 0) + consoles[cons]->c_flags &= ~C_MODERAW; + else + consoles[cons]->c_flags |= C_MODERAW; + } +} + +int +getchar(void) +{ + int cons; + int flags = C_PRESENTIN | C_ACTIVEIN; + int rv; + + /* + * Loop forever polling all active consoles. Somewhat strangely, + * this code expects all ->c_in() implementations to effectively do an + * ischar() check first, returning -1 if there's not a char ready. + */ + for (;;) { + for (cons = 0; consoles[cons] != NULL; cons++) { + if ((consoles[cons]->c_flags & flags) == flags && + ((rv = consoles[cons]->c_in(consoles[cons])) != -1)) + return (rv); + } + delay(30 * 1000); /* delay 30ms */ + } +} + +int +ischar(void) +{ + int cons; + + for (cons = 0; consoles[cons] != NULL; cons++) + if ((consoles[cons]->c_flags & (C_PRESENTIN | C_ACTIVEIN)) == + (C_PRESENTIN | C_ACTIVEIN) && + (consoles[cons]->c_ready(consoles[cons]) != 0)) + return (1); + return (0); +} + +void +putchar(int c) +{ + int cons; + + /* Expand newlines if not in raw mode */ + for (cons = 0; consoles[cons] != NULL; cons++) + if ((consoles[cons]->c_flags & (C_PRESENTOUT | C_ACTIVEOUT)) == + (C_PRESENTOUT | C_ACTIVEOUT)) { + if (c == '\n' && + (consoles[cons]->c_flags & C_MODERAW) == 0) + consoles[cons]->c_out(consoles[cons], '\r'); + consoles[cons]->c_out(consoles[cons], c); + } +} + +/* + * Find the console with the specified name. + */ +static int +cons_find(const char *name) +{ + int cons; + + for (cons = 0; consoles[cons] != NULL; cons++) + if (strcmp(consoles[cons]->c_name, name) == 0) + return (cons); + return (-1); +} + +/* + * Select one or more consoles. + */ +static int +cons_set(struct env_var *ev, int flags, const void *value) +{ + int ret, cons; + char *list, *tmp; + + if ((value == NULL) || (cons_check(value) == 0)) { + /* + * Return CMD_OK instead of CMD_ERROR to prevent forth syntax + * error, which would prevent it processing any further + * loader.conf entries. + */ + return (CMD_OK); + } + + ret = cons_change(value); + if (ret != CMD_OK) + return (ret); + + /* + * build list of active consoles. + */ + list = NULL; + for (cons = 0; consoles[cons] != NULL; cons++) { + if ((consoles[cons]->c_flags & (C_ACTIVEIN | C_ACTIVEOUT)) == + (C_ACTIVEIN | C_ACTIVEOUT)) { + if (list == NULL) { + list = strdup(consoles[cons]->c_name); + } else { + if (asprintf(&tmp, "%s,%s", list, + consoles[cons]->c_name) > 0) { + free(list); + list = tmp; + } + } + } + } + + /* + * set console variable. + */ + if (list != NULL) { + (void) env_setenv(ev->ev_name, flags | EV_NOHOOK, list, + NULL, NULL); + } else { + (void) env_setenv(ev->ev_name, flags | EV_NOHOOK, value, + NULL, NULL); + } + free(list); + return (CMD_OK); +} + +/* + * Check that at least one the consoles listed in *string is valid + */ +static int +cons_check(const char *string) +{ + int cons, found, failed; + char *curpos, *dup, *next; + + dup = next = strdup(string); + found = failed = 0; + while (next != NULL) { + curpos = strsep(&next, " ,"); + if (*curpos != '\0') { + cons = cons_find(curpos); + if (cons == -1) { + printf("console %s is invalid!\n", curpos); + failed++; + } else { + if ((consoles[cons]->c_flags & + (C_PRESENTIN | C_PRESENTOUT)) != + (C_PRESENTIN | C_PRESENTOUT)) { + failed++; + } else + found++; + } + } + } + + free(dup); + + if (found == 0) + printf("no valid consoles!\n"); + + if (found == 0 || failed != 0) { + printf("Available consoles:\n"); + for (cons = 0; consoles[cons] != NULL; cons++) { + printf(" %s", consoles[cons]->c_name); + if (consoles[cons]->c_devinfo != NULL) + consoles[cons]->c_devinfo(consoles[cons]); + printf("\n"); + } + } + + return (found); +} + + +/* + * Activate all the valid consoles listed in *string and disable all others. + */ +static int +cons_change(const char *string) +{ + int cons, active; + char *curpos, *dup, *next; + + /* Disable all consoles */ + for (cons = 0; consoles[cons] != NULL; cons++) { + consoles[cons]->c_flags &= ~(C_ACTIVEIN | C_ACTIVEOUT); + } + + /* Enable selected consoles */ + dup = next = strdup(string); + active = 0; + while (next != NULL) { + curpos = strsep(&next, " ,"); + if (*curpos == '\0') + continue; + cons = cons_find(curpos); + if (cons >= 0) { + consoles[cons]->c_flags |= C_ACTIVEIN | C_ACTIVEOUT; + consoles[cons]->c_init(consoles[cons], 0); + if ((consoles[cons]->c_flags & + (C_ACTIVEIN | C_ACTIVEOUT)) == + (C_ACTIVEIN | C_ACTIVEOUT)) { + active++; + continue; + } + } + } + + free(dup); + + if (active == 0) { + /* + * All requested consoles failed to initialise, try to recover. + */ + for (cons = 0; consoles[cons] != NULL; cons++) { + consoles[cons]->c_flags |= C_ACTIVEIN | C_ACTIVEOUT; + consoles[cons]->c_init(consoles[cons], 0); + if ((consoles[cons]->c_flags & + (C_ACTIVEIN | C_ACTIVEOUT)) == + (C_ACTIVEIN | C_ACTIVEOUT)) + active++; + } + + if (active == 0) + return (CMD_ERROR); /* Recovery failed. */ + } + + return (CMD_OK); +} + +/* + * Change the twiddle divisor. + * + * The user can set the twiddle_divisor variable to directly control how fast + * the progress twiddle spins, useful for folks with slow serial consoles. The + * code to monitor changes to the variable and propagate them to the twiddle + * routines has to live somewhere. Twiddling is console-related so it's here. + */ +static int +twiddle_set(struct env_var *ev, int flags, const void *value) +{ + ulong_t tdiv; + char *eptr; + + tdiv = strtoul(value, &eptr, 0); + if (*(const char *)value == 0 || *eptr != 0) { + printf("invalid twiddle_divisor '%s'\n", (const char *)value); + return (CMD_ERROR); + } + twiddle_divisor((uint_t)tdiv); + env_setenv(ev->ev_name, flags | EV_NOHOOK, value, NULL, NULL); + + return (CMD_OK); +} + +COMMAND_SET(console, "console", "console info", command_console); + +static int +command_console(int argc, char *argv[]) +{ + if (argc > 1) + printf("%s: list info about available consoles\n", argv[0]); + + printf("Current console: %s\n", getenv("console")); + printf("Available consoles:\n"); + for (int cons = 0; consoles[cons] != NULL; cons++) { + printf(" %s", consoles[cons]->c_name); + if (consoles[cons]->c_devinfo != NULL) + consoles[cons]->c_devinfo(consoles[cons]); + printf("\n"); + } + + return (CMD_OK); +} diff --git a/usr/src/boot/common/dev_net.c b/usr/src/boot/common/dev_net.c new file mode 100644 index 0000000000..a71127c641 --- /dev/null +++ b/usr/src/boot/common/dev_net.c @@ -0,0 +1,449 @@ +/* + * Copyright (c) 1997 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Gordon W. Ross. + * + * 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. + */ + +/* + * Copyright 2018 OmniOS Community Edition (OmniOSce) Association. + */ + +#include <sys/cdefs.h> + +/* + * This module implements a "raw device" interface suitable for + * use by the stand-alone I/O library NFS code. This interface + * does not support any "block" access, and exists only for the + * purpose of initializing the network interface, getting boot + * parameters, and performing the NFS mount. + * + * At open time, this does: + * + * find interface - netif_open() + * RARP for IP address - rarp_getipaddress() + * RPC/bootparams - callrpc(d, RPC_BOOTPARAMS, ...) + * RPC/mountd - nfs_mount(sock, ip, path) + * + * the root file handle from mountd is saved in a global + * for use by the NFS open code (NFS/lookup). + */ + +#include <machine/stdarg.h> +#include <sys/param.h> +#include <sys/socket.h> +#include <net/if.h> +#include <netinet/in.h> +#include <netinet/in_systm.h> + +#include <stand.h> +#include <stddef.h> +#include <string.h> +#include <net.h> +#include <netif.h> +#include <bootp.h> +#include <bootparam.h> + +#include "dev_net.h" +#include "bootstrap.h" + +#ifdef NETIF_DEBUG +int debug = 0; +#endif + +static char *netdev_name; +static int netdev_sock = -1; +static int netdev_opens; + +static int net_init(void); +static int net_open(struct open_file *, ...); +static int net_close(struct open_file *); +static void net_cleanup(void); +static int net_strategy(void *, int, daddr_t, size_t, char *, size_t *); +static int net_print(int); + +static int net_getparams(int sock); + +struct devsw netdev = { + "net", + DEVT_NET, + net_init, + net_strategy, + net_open, + net_close, + noioctl, + net_print, + net_cleanup +}; + +static struct uri_scheme { + const char *scheme; + int proto; +} uri_schemes[] = { + { "tftp:/", NET_TFTP }, + { "nfs:/", NET_NFS }, +}; + +static int +net_init(void) +{ + + return (0); +} + +/* + * Called by devopen after it sets f->f_dev to our devsw entry. + * This opens the low-level device and sets dev->d_opendata. + * This is declared with variable arguments... + */ +static int +net_open(struct open_file *f, ...) +{ + struct iodesc *d; + va_list args; + struct devdesc *dev; + const char *devname; /* Device part of file name (or NULL). */ + int error = 0; + + va_start(args, f); + dev = va_arg(args, struct devdesc *); + va_end(args); + + devname = dev->d_dev->dv_name; + /* Before opening another interface, close the previous one first. */ + if (netdev_sock >= 0 && strcmp(devname, netdev_name) != 0) + net_cleanup(); + + /* On first open, do netif open, mount, etc. */ + if (netdev_opens == 0) { + /* Find network interface. */ + if (netdev_sock < 0) { + netdev_sock = netif_open(dev); + if (netdev_sock < 0) { + printf("%s: netif_open() failed\n", __func__); + return (ENXIO); + } + netdev_name = strdup(devname); +#ifdef NETIF_DEBUG + if (debug) + printf("%s: netif_open() succeeded\n", + __func__); +#endif + } + /* + * If network params were not set by netif_open(), try to get + * them via bootp, rarp, etc. + */ + if (rootip.s_addr == 0) { + /* Get root IP address, and path, etc. */ + error = net_getparams(netdev_sock); + if (error) { + /* getparams makes its own noise */ + free(netdev_name); + netif_close(netdev_sock); + netdev_sock = -1; + return (error); + } + } + /* + * Set the variables required by the kernel's nfs_diskless + * mechanism. This is the minimum set of variables required to + * mount a root filesystem without needing to obtain additional + * info from bootp or other sources. + */ + d = socktodesc(netdev_sock); + setenv("boot.netif.hwaddr", ether_sprintf(d->myea), 1); + setenv("boot.netif.ip", inet_ntoa(myip), 1); + setenv("boot.netif.netmask", intoa(netmask), 1); + setenv("boot.netif.gateway", inet_ntoa(gateip), 1); + setenv("boot.netif.server", inet_ntoa(rootip), 1); + if (netproto == NET_TFTP) { + setenv("boot.tftproot.server", inet_ntoa(rootip), 1); + setenv("boot.tftproot.path", rootpath, 1); + } else { + setenv("boot.nfsroot.server", inet_ntoa(rootip), 1); + setenv("boot.nfsroot.path", rootpath, 1); + } + if (intf_mtu != 0) { + char mtu[16]; + snprintf(mtu, sizeof (mtu), "%u", intf_mtu); + setenv("boot.netif.mtu", mtu, 1); + } + } + netdev_opens++; + dev->d_opendata = &netdev_sock; + return (error); +} + +static int +net_close(struct open_file *f) +{ + struct devdesc *dev; + +#ifdef NETIF_DEBUG + if (debug) + printf("%s: opens=%d\n", __func__, netdev_opens); +#endif + + dev = f->f_devdata; + dev->d_opendata = NULL; + + return (0); +} + +static void +net_cleanup(void) +{ + + if (netdev_sock >= 0) { +#ifdef NETIF_DEBUG + if (debug) + printf("%s: calling netif_close()\n", __func__); +#endif + rootip.s_addr = 0; + free(netdev_name); + netif_close(netdev_sock); + netdev_sock = -1; + } +} + +static int +net_strategy(void *devdata __unused, int rw __unused, daddr_t blk __unused, + size_t size __unused, char *buf __unused, size_t *rsize __unused) +{ + + return (EIO); +} + +/* + * Get info for NFS boot: our IP address, our hostname, + * server IP address, and our root path on the server. + * There are two ways to do this: The old, Sun way, + * and the more modern, BOOTP/DHCP way. (RFC951, RFC1048) + */ + +extern n_long ip_convertaddr(char *p); + +static int +net_getparams(int sock) +{ + char buf[MAXHOSTNAMELEN]; + n_long rootaddr, smask; + + /* + * Try to get boot info using BOOTP/DHCP. If we succeed, then + * the server IP address, gateway, and root path will all + * be initialized. If any remain uninitialized, we will + * use RARP and RPC/bootparam (the Sun way) to get them. + */ + bootp(sock); + if (myip.s_addr != 0) + goto exit; +#ifdef NETIF_DEBUG + if (debug) + printf("%s: BOOTP failed, trying RARP/RPC...\n", __func__); +#endif + + /* + * Use RARP to get our IP address. This also sets our + * netmask to the "natural" default for our address. + */ + if (rarp_getipaddress(sock)) { + printf("%s: RARP failed\n", __func__); + return (EIO); + } + printf("%s: client addr: %s\n", __func__, inet_ntoa(myip)); + + /* Get our hostname, server IP address, gateway. */ + if (bp_whoami(sock)) { + printf("%s: bootparam/whoami RPC failed\n", __func__); + return (EIO); + } +#ifdef NETIF_DEBUG + if (debug) + printf("%s: client name: %s\n", __func__, hostname); +#endif + + /* + * Ignore the gateway from whoami (unreliable). + * Use the "gateway" parameter instead. + */ + smask = 0; + gateip.s_addr = 0; + if (bp_getfile(sock, "gateway", &gateip, buf) == 0) { + /* Got it! Parse the netmask. */ + smask = ip_convertaddr(buf); + } + if (smask) { + netmask = smask; +#ifdef NETIF_DEBUG + if (debug) + printf("%s: subnet mask: %s\n", __func__, + intoa(netmask)); +#endif + } +#ifdef NETIF_DEBUG + if (gateip.s_addr && debug) + printf("%s: net gateway: %s\n", __func__, inet_ntoa(gateip)); +#endif + + /* Get the root server and pathname. */ + if (bp_getfile(sock, "root", &rootip, rootpath)) { + printf("%s: bootparam/getfile RPC failed\n", __func__); + return (EIO); + } +exit: + if ((rootaddr = net_parse_rootpath()) != INADDR_NONE) + rootip.s_addr = rootaddr; + +#ifdef NETIF_DEBUG + if (debug) { + printf("%s: server addr: %s\n", __func__, + inet_ntoa(rootip)); + printf("%s: server path: %s\n", __func__, rootpath); + } +#endif + + return (0); +} + +static int +net_print(int verbose) +{ + struct netif_driver *drv; + int i, d, cnt; + int ret = 0; + + if (netif_drivers[0] == NULL) + return (ret); + + printf("%s devices:", netdev.dv_name); + if ((ret = pager_output("\n")) != 0) + return (ret); + + cnt = 0; + for (d = 0; netif_drivers[d]; d++) { + drv = netif_drivers[d]; + for (i = 0; i < drv->netif_nifs; i++) { + printf("\t%s%d:", netdev.dv_name, cnt++); + if (verbose) { + printf(" (%s%d)", drv->netif_bname, + drv->netif_ifs[i].dif_unit); + } + if ((ret = pager_output("\n")) != 0) + return (ret); + } + } + return (ret); +} + +/* + * Parses the rootpath if present + * + * The rootpath format can be in the form + * <scheme>://IPv4/path + * <scheme>:/path + * + * For compatibility with previous behaviour it also accepts as an NFS scheme + * IPv4:/path + * /path + * + * If an IPv4 address has been specified, it will be stripped out and passed + * out as the return value of this function in network byte order. + * + * If no rootpath is present then we will default to TFTP. + * + * If no global default scheme has been specified and no scheme has been + * specified, we will assume that this is an NFS URL. + * + * The pathname will be stored in the global variable rootpath. + */ +uint32_t +net_parse_rootpath(void) +{ + n_long addr = htonl(INADDR_NONE); + size_t i; + char ip[FNAME_SIZE]; + char *ptr, *val; + + netproto = NET_NONE; + + for (i = 0; i < nitems(uri_schemes); i++) { + if (strncmp(rootpath, uri_schemes[i].scheme, + strlen(uri_schemes[i].scheme)) != 0) + continue; + + netproto = uri_schemes[i].proto; + break; + } + ptr = rootpath; + /* Fallback for compatibility mode */ + if (netproto == NET_NONE) { + if (strcmp(rootpath, "/") == 0) { + netproto = NET_TFTP; + } else { + netproto = NET_NFS; + (void) strsep(&ptr, ":"); + if (ptr != NULL) { + addr = inet_addr(rootpath); + bcopy(ptr, rootpath, strlen(ptr) + 1); + } + } + } else { + ptr += strlen(uri_schemes[i].scheme); + if (*ptr == '/') { + /* + * We are in the form <scheme>://, we do expect an ip. + */ + ptr++; + /* + * XXX when http will be there we will need to check for + * a port, but right now we do not need it yet. + * Also will need rework for IPv6. + */ + val = strchr(ptr, '/'); + if (val == NULL) { + /* If no pathname component, default to / */ + strlcat(rootpath, "/", sizeof (rootpath)); + val = strchr(ptr, '/'); + } + if (val != NULL) { + snprintf(ip, sizeof (ip), "%.*s", + (int)((uintptr_t)val - (uintptr_t)ptr), + ptr); + addr = inet_addr(ip); + if (addr == htonl(INADDR_NONE)) { + printf("Bad IP address: %s\n", ip); + } + bcopy(val, rootpath, strlen(val) + 1); + } + } else { + ptr--; + bcopy(ptr, rootpath, strlen(ptr) + 1); + } + } + + return (addr); +} diff --git a/usr/src/boot/common/dev_net.h b/usr/src/boot/common/dev_net.h new file mode 100644 index 0000000000..995b67241d --- /dev/null +++ b/usr/src/boot/common/dev_net.h @@ -0,0 +1,36 @@ +/*- + * Copyright (c) 1998 Doug Rabson <dfr@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 _BOOT_DEV_NET_H_ +#define _BOOT_DEV_NET_H_ + +extern struct devsw netdev; + +uint32_t net_parse_rootpath(void); + +#endif diff --git a/usr/src/boot/common/devopen.c b/usr/src/boot/common/devopen.c new file mode 100644 index 0000000000..c4aa21c5c6 --- /dev/null +++ b/usr/src/boot/common/devopen.c @@ -0,0 +1,63 @@ +/* + * 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> + +#include <stand.h> +#include <string.h> + +#include "bootstrap.h" + +int +devopen(struct open_file *f, const char *fname, const char **file) +{ + struct devdesc *dev; + int result; + + result = archsw.arch_getdev((void **)&dev, fname, file); + if (result) + return (result); + + /* point to device-specific data so that device open can use it */ + f->f_dev = dev->d_dev; + f->f_devdata = dev; + result = dev->d_dev->dv_open(f, dev); + if (result != 0) { + f->f_devdata = NULL; + f->f_dev = NULL; + free(dev); + } + + return (result); +} + +int +devclose(struct open_file *f) +{ + + free(f->f_devdata); + return (0); +} diff --git a/usr/src/boot/common/disk.c b/usr/src/boot/common/disk.c new file mode 100644 index 0000000000..08912cc4e1 --- /dev/null +++ b/usr/src/boot/common/disk.c @@ -0,0 +1,482 @@ +/* + * Copyright (c) 1998 Michael Smith <msmith@freebsd.org> + * Copyright (c) 2012 Andrey V. Elsukov <ae@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> +#include <sys/disk.h> +#include <sys/queue.h> +#include <stand.h> +#include <stdarg.h> +#include <inttypes.h> +#include <bootstrap.h> +#include <part.h> + +#include "disk.h" + +#ifdef DISK_DEBUG +#define DPRINTF(fmt, args...) printf("%s: " fmt "\n", __func__, ## args) +#else +#define DPRINTF(fmt, args...) ((void)0) +#endif + +struct open_disk { + struct ptable *table; + uint64_t mediasize; + uint64_t entrysize; + uint_t sectorsize; +}; + +struct print_args { + struct disk_devdesc *dev; + const char *prefix; + int verbose; +}; + +/* Convert size to a human-readable number. */ +static char * +display_size(uint64_t size, uint_t sectorsize) +{ + static char buf[80]; + char unit; + + size = size * sectorsize / 1024; + unit = 'K'; + if (size >= 10485760000LL) { + size /= 1073741824; + unit = 'T'; + } else if (size >= 10240000) { + size /= 1048576; + unit = 'G'; + } else if (size >= 10000) { + size /= 1024; + unit = 'M'; + } + snprintf(buf, sizeof (buf), "%4" PRIu64 "%cB", size, unit); + return (buf); +} + +static int +ptblread(void *d, void *buf, size_t blocks, uint64_t offset) +{ + struct disk_devdesc *dev; + struct open_disk *od; + + dev = (struct disk_devdesc *)d; + od = (struct open_disk *)dev->dd.d_opendata; + + /* + * The strategy function assumes the offset is in units of 512 byte + * sectors. For larger sector sizes, we need to adjust the offset to + * match the actual sector size. + */ + offset *= (od->sectorsize / 512); + /* + * As the GPT backup partition is located at the end of the disk, + * to avoid reading past disk end, flag bcache not to use RA. + */ + return (dev->dd.d_dev->dv_strategy(dev, F_READ | F_NORA, offset, + blocks * od->sectorsize, (char *)buf, NULL)); +} + +static int +ptable_print(void *arg, const char *pname, const struct ptable_entry *part) +{ + struct disk_devdesc dev; + struct print_args *pa, bsd; + struct open_disk *od; + struct ptable *table; + char line[80]; + int res; + uint_t sectsize; + uint64_t partsize; + + pa = (struct print_args *)arg; + od = (struct open_disk *)pa->dev->dd.d_opendata; + sectsize = od->sectorsize; + partsize = part->end - part->start + 1; + snprintf(line, sizeof (line), " %s%s: %s", pa->prefix, pname, + parttype2str(part->type)); + if (pager_output(line)) + return (1); + + if (pa->verbose) { + /* Emit extra tab when the line is shorter than 3 tab stops */ + if (strlen(line) < 24) + (void) pager_output("\t"); + + snprintf(line, sizeof (line), "\t%s", + display_size(partsize, sectsize)); + if (pager_output(line)) + return (1); + } + if (pager_output("\n")) + return (1); + res = 0; + if (part->type == PART_FREEBSD || part->type == PART_SOLARIS2) { + /* Open slice with BSD or VTOC label */ + dev.dd.d_dev = pa->dev->dd.d_dev; + dev.dd.d_unit = pa->dev->dd.d_unit; + dev.d_slice = part->index; + dev.d_partition = D_PARTNONE; + if (disk_open(&dev, partsize, sectsize) == 0) { + table = ptable_open(&dev, partsize, sectsize, ptblread); + if (table != NULL) { + snprintf(line, sizeof (line), " %s%s", + pa->prefix, pname); + bsd.dev = &dev; + bsd.prefix = line; + bsd.verbose = pa->verbose; + res = ptable_iterate(table, &bsd, ptable_print); + ptable_close(table); + } + disk_close(&dev); + } + } + + return (res); +} + +int +disk_print(struct disk_devdesc *dev, char *prefix, int verbose) +{ + struct open_disk *od; + struct print_args pa; + + /* Disk should be opened */ + od = (struct open_disk *)dev->dd.d_opendata; + pa.dev = dev; + pa.prefix = prefix; + pa.verbose = verbose; + return (ptable_iterate(od->table, &pa, ptable_print)); +} + +int +disk_read(struct disk_devdesc *dev, void *buf, uint64_t offset, uint_t blocks) +{ + struct open_disk *od; + int ret; + + od = (struct open_disk *)dev->dd.d_opendata; + ret = dev->dd.d_dev->dv_strategy(dev, F_READ, dev->d_offset + offset, + blocks * od->sectorsize, buf, NULL); + + return (ret); +} + +int +disk_write(struct disk_devdesc *dev, void *buf, uint64_t offset, uint_t blocks) +{ + struct open_disk *od; + int ret; + + od = (struct open_disk *)dev->dd.d_opendata; + ret = dev->dd.d_dev->dv_strategy(dev, F_WRITE, dev->d_offset + offset, + blocks * od->sectorsize, buf, NULL); + + return (ret); +} + +int +disk_ioctl(struct disk_devdesc *dev, unsigned long cmd, void *data) +{ + struct open_disk *od = dev->dd.d_opendata; + + if (od == NULL) + return (ENOTTY); + + switch (cmd) { + case DIOCGSECTORSIZE: + *(uint_t *)data = od->sectorsize; + break; + case DIOCGMEDIASIZE: + if (dev->d_offset == 0) + *(uint64_t *)data = od->mediasize; + else + *(uint64_t *)data = od->entrysize * od->sectorsize; + break; + default: + return (ENOTTY); + } + + return (0); +} + +int +disk_open(struct disk_devdesc *dev, uint64_t mediasize, uint_t sectorsize) +{ + struct disk_devdesc partdev; + struct open_disk *od; + struct ptable *table; + struct ptable_entry part; + int rc, slice, partition; + + if (sectorsize == 0) { + DPRINTF("unknown sector size"); + return (ENXIO); + } + rc = 0; + od = (struct open_disk *)malloc(sizeof (struct open_disk)); + if (od == NULL) { + DPRINTF("no memory"); + return (ENOMEM); + } + dev->dd.d_opendata = od; + od->entrysize = 0; + od->mediasize = mediasize; + od->sectorsize = sectorsize; + /* + * While we are reading disk metadata, make sure we do it relative + * to the start of the disk + */ + memcpy(&partdev, dev, sizeof(partdev)); + partdev.d_offset = 0; + partdev.d_slice = D_SLICENONE; + partdev.d_partition = D_PARTNONE; + + dev->d_offset = 0; + table = NULL; + slice = dev->d_slice; + partition = dev->d_partition; + + DPRINTF("%s unit %d, slice %d, partition %d => %p", disk_fmtdev(dev), + dev->dd.d_unit, dev->d_slice, dev->d_partition, od); + + /* Determine disk layout. */ + od->table = ptable_open(&partdev, mediasize / sectorsize, sectorsize, + ptblread); + if (od->table == NULL) { + DPRINTF("Can't read partition table"); + rc = ENXIO; + goto out; + } + + if (ptable_getsize(od->table, &mediasize) != 0) { + rc = ENXIO; + goto out; + } + od->mediasize = mediasize; + + if ((ptable_gettype(od->table) == PTABLE_BSD || + ptable_gettype(od->table) == PTABLE_VTOC) && + partition >= 0) { + /* It doesn't matter what value has d_slice */ + rc = ptable_getpart(od->table, &part, partition); + if (rc == 0) { + dev->d_offset = part.start; + od->entrysize = part.end - part.start + 1; + } + } else if (ptable_gettype(od->table) == PTABLE_ISO9660) { + dev->d_offset = 0; + od->entrysize = mediasize; + } else if (slice >= 0) { + /* Try to get information about partition */ + if (slice == 0) + rc = ptable_getbestpart(od->table, &part); + else + rc = ptable_getpart(od->table, &part, slice); + if (rc != 0) /* Partition doesn't exist */ + goto out; + dev->d_offset = part.start; + od->entrysize = part.end - part.start + 1; + slice = part.index; + if (ptable_gettype(od->table) == PTABLE_GPT) { + partition = D_PARTISGPT; + goto out; /* Nothing more to do */ + } else if (partition == D_PARTISGPT) { + /* + * When we try to open GPT partition, but partition + * table isn't GPT, reset partition value to + * D_PARTWILD and try to autodetect appropriate value. + */ + partition = D_PARTWILD; + } + + /* + * If partition is D_PARTNONE, then disk_open() was called + * to open raw MBR slice. + */ + if (partition == D_PARTNONE) + goto out; + + /* + * If partition is D_PARTWILD and we are looking at a + * BSD/VTOC slice, then try to read label, otherwise return + * the whole MBR slice. + */ + if (partition == D_PARTWILD) { + switch (part.type) { + case PART_FREEBSD: + case PART_SOLARIS2: + break; + default: + goto out; + } + } + /* Try to read label */ + table = ptable_open(dev, part.end - part.start + 1, + od->sectorsize, ptblread); + if (table == NULL) { + DPRINTF("Can't read BSD/VTOC label"); + rc = ENXIO; + goto out; + } + /* + * If slice contains BSD/VTOC label and partition < 0, then + * assume the 'a' partition. Otherwise just return the + * whole MBR slice, because it can contain ZFS. + */ + if (partition < 0) { + if (ptable_gettype(table) != PTABLE_BSD && + ptable_gettype(table) != PTABLE_VTOC) + goto out; + partition = 0; + } + rc = ptable_getpart(table, &part, partition); + if (rc != 0) + goto out; + dev->d_offset += part.start; + od->entrysize = part.end - part.start + 1; + } +out: + if (table != NULL) + ptable_close(table); + + if (rc != 0) { + if (od->table != NULL) + ptable_close(od->table); + free(od); + DPRINTF("%s could not open", disk_fmtdev(dev)); + } else { + /* Save the slice and partition number to the dev */ + dev->d_slice = slice; + dev->d_partition = partition; + DPRINTF("%s offset %" PRIu64 " => %p", disk_fmtdev(dev), + dev->d_offset, od); + } + return (rc); +} + +int +disk_close(struct disk_devdesc *dev) +{ + struct open_disk *od; + + od = (struct open_disk *)dev->dd.d_opendata; + DPRINTF("%s closed => %p", disk_fmtdev(dev), od); + ptable_close(od->table); + free(od); + return (0); +} + +char * +disk_fmtdev(struct disk_devdesc *dev) +{ + static char buf[128]; + char *cp; + + cp = buf + sprintf(buf, "%s%d", dev->dd.d_dev->dv_name, dev->dd.d_unit); + if (dev->d_slice > D_SLICENONE) { +#ifdef LOADER_GPT_SUPPORT + if (dev->d_partition == D_PARTISGPT) { + sprintf(cp, "p%d:", dev->d_slice); + return (buf); + } else +#endif +#ifdef LOADER_MBR_SUPPORT + cp += sprintf(cp, "s%d", dev->d_slice); +#endif + } + if (dev->d_partition > D_PARTNONE) + cp += sprintf(cp, "%c", dev->d_partition + 'a'); + strcat(cp, ":"); + return (buf); +} + +int +disk_parsedev(struct disk_devdesc *dev, const char *devspec, const char **path) +{ + int unit, slice, partition; + const char *np; + char *cp; + + np = devspec; + unit = -1; + /* + * If there is path/file info after the device info, then any missing + * slice or partition info should be considered a request to search for + * an appropriate partition. Otherwise we want to open the raw device + * itself and not try to fill in missing info by searching. + */ + if ((cp = strchr(np, ':')) != NULL && cp[1] != '\0') { + slice = D_SLICEWILD; + partition = D_PARTWILD; + } else { + slice = D_SLICENONE; + partition = D_PARTNONE; + } + + if (*np != '\0' && *np != ':') { + unit = strtol(np, &cp, 10); + if (cp == np) + return (EUNIT); +#ifdef LOADER_GPT_SUPPORT + if (*cp == 'p') { + np = cp + 1; + slice = strtol(np, &cp, 10); + if (np == cp) + return (ESLICE); + /* we don't support nested partitions on GPT */ + if (*cp != '\0' && *cp != ':') + return (EINVAL); + partition = D_PARTISGPT; + } else +#endif +#ifdef LOADER_MBR_SUPPORT + if (*cp == 's') { + np = cp + 1; + slice = strtol(np, &cp, 10); + if (np == cp) + return (ESLICE); + } +#endif + if (*cp != '\0' && *cp != ':') { + partition = *cp - 'a'; + if (partition < 0) + return (EPART); + cp++; + } + } else + return (EINVAL); + + if (*cp != '\0' && *cp != ':') + return (EINVAL); + dev->dd.d_unit = unit; + dev->d_slice = slice; + dev->d_partition = partition; + if (path != NULL) + *path = (*cp == '\0') ? cp: cp + 1; + return (0); +} diff --git a/usr/src/boot/common/disk.h b/usr/src/boot/common/disk.h new file mode 100644 index 0000000000..81d002314f --- /dev/null +++ b/usr/src/boot/common/disk.h @@ -0,0 +1,124 @@ +/* + * 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. + */ + +/* + * Device descriptor for partitioned disks. To use, set the + * d_slice and d_partition variables as follows: + * + * Whole disk access: + * + * d_slice = D_SLICENONE + * d_partition = <doesn't matter> + * + * Whole MBR slice: + * + * d_slice = MBR slice number (typically 1..4) + * d_partition = D_PARTNONE + * + * VTOC disklabel partition within an MBR slice: + * + * d_slice = MBR slice number (typically 1..4) + * d_partition = disklabel partition (typically 0..19 or D_PARTWILD) + * + * BSD disklabel partition within an MBR slice: + * + * d_slice = MBR slice number (typically 1..4) + * d_partition = disklabel partition (typically 0..19 or D_PARTWILD) + * + * BSD disklabel partition on the true dedicated disk: + * + * d_slice = D_SLICENONE + * d_partition = disklabel partition (typically 0..19 or D_PARTWILD) + * + * GPT partition: + * + * d_slice = GPT partition number (typically 1..N) + * d_partition = D_PARTISGPT + * + * For MBR, setting d_partition to D_PARTWILD will automatically use the first + * partition within the slice. + * + * For both MBR and GPT, to automatically find the 'best' slice and partition, + * set d_slice to D_SLICEWILD. This uses the partition type to decide which + * partition to use according to the following list of preferences: + * + * Solaris2 (active) + * Solaris2 (inactive) + * Linux (active) + * Linux (inactive) + * DOS/Windows (active) + * DOS/Windows (inactive) + * + * Active MBR slices (marked as bootable) are preferred over inactive. GPT + * doesn't have the concept of active/inactive partitions. In both MBR and GPT, + * if there are multiple slices/partitions of a given type, the first one + * is chosen. + * + * The low-level disk device will typically call disk_open() from its open + * method to interpret the disk partition tables according to the rules above. + * This will initialize d_offset to the block offset of the start of the + * selected partition - this offset should be added to the offset passed to + * the device's strategy method. + */ + +#ifndef _DISK_H +#define _DISK_H + +#define D_SLICENONE -1 +#define D_SLICEWILD 0 +#define D_PARTNONE -1 +#define D_PARTWILD -2 +#define D_PARTISGPT 255 + +struct disk_devdesc { + struct devdesc dd; /* Must be first. */ + int d_slice; + int d_partition; + uint64_t d_offset; +}; + +enum disk_ioctl { + IOCTL_GET_BLOCKS, + IOCTL_GET_BLOCK_SIZE +}; + +/* + * Parse disk metadata and initialise dev->d_offset. + */ +extern int disk_open(struct disk_devdesc *, uint64_t, u_int); +extern int disk_close(struct disk_devdesc *); +extern int disk_ioctl(struct disk_devdesc *, u_long, void *); +extern int disk_read(struct disk_devdesc *, void *, uint64_t, u_int); +extern int disk_write(struct disk_devdesc *, void *, uint64_t, u_int); + +/* + * Print information about slices on a disk. + */ +extern int disk_print(struct disk_devdesc *, char *, int); +extern char* disk_fmtdev(struct disk_devdesc *); +extern int disk_parsedev(struct disk_devdesc *, const char *, const char **); + +#endif /* _DISK_H */ diff --git a/usr/src/boot/common/gfx_fb.c b/usr/src/boot/common/gfx_fb.c new file mode 100644 index 0000000000..6cb328acbb --- /dev/null +++ b/usr/src/boot/common/gfx_fb.c @@ -0,0 +1,2530 @@ +/* + * This file and its contents are supplied under the terms of the + * Common Development and Distribution License ("CDDL"), version 1.0. + * You may only use this file in accordance with the terms of version + * 1.0 of the CDDL. + * + * A full copy of the text of the CDDL should have accompanied this + * source. A copy of the CDDL is also available via the Internet at + * http://www.illumos.org/license/CDDL. + */ + +/* + * Copyright 2016 Toomas Soome <tsoome@me.com> + * Copyright 2019 OmniOS Community Edition (OmniOSce) Association. + * Copyright 2020 RackTop Systems, Inc. + */ + +/* + * The workhorse here is gfxfb_blt(). It is implemented to mimic UEFI + * GOP Blt, and allows us to fill the rectangle on screen, copy + * rectangle from video to buffer and buffer to video and video to video. + * Such implementation does allow us to have almost identical implementation + * for both BIOS VBE and UEFI. + * + * ALL pixel data is assumed to be 32-bit BGRA (byte order Blue, Green, Red, + * Alpha) format, this allows us to only handle RGB data and not to worry + * about mixing RGB with indexed colors. + * Data exchange between memory buffer and video will translate BGRA + * and native format as following: + * + * 32-bit to/from 32-bit is trivial case. + * 32-bit to/from 24-bit is also simple - we just drop the alpha channel. + * 32-bit to/from 16-bit is more complicated, because we nee to handle + * data loss from 32-bit to 16-bit. While reading/writing from/to video, we + * need to apply masks of 16-bit color components. This will preserve + * colors for terminal text. For 32-bit truecolor PMG images, we need to + * translate 32-bit colors to 15/16 bit colors and this means data loss. + * There are different algorithms how to perform such color space reduction, + * we are currently using bitwise right shift to reduce color space and so far + * this technique seems to be sufficient (see also gfx_fb_putimage(), the + * end of for loop). + * 32-bit to/from 8-bit is the most troublesome because 8-bit colors are + * indexed. From video, we do get color indexes, and we do translate + * color index values to RGB. To write to video, we again need to translate + * RGB to color index. Additionally, we need to translate between VGA and + * Sun colors. + * + * Our internal color data is represented using BGRA format. But the hardware + * used indexed colors for 8-bit colors (0-255) and for this mode we do + * need to perform translation to/from BGRA and index values. + * + * - paletteentry RGB <-> index - + * BGRA BUFFER <----/ \ - VIDEO + * \ / + * - RGB (16/24/32) - + * + * To perform index to RGB translation, we use palette table generated + * from when we set up 8-bit mode video. We cannot read palette data from + * the hardware, because not all hardware supports reading it. + * + * BGRA to index is implemented in rgb_to_color_index() by searching + * palette array for closest match of RBG values. + * + * Note: In 8-bit mode, We do store first 16 colors to palette registers + * in VGA color order, this serves two purposes; firstly, + * if palette update is not supported, we still have correct 16 colors. + * Secondly, the kernel does get correct 16 colors when some other boot + * loader is used. However, the palette map for 8-bit colors is using + * Sun color ordering - this does allow us to skip translation + * from VGA colors to Sun colors, while we are reading RGB data. + */ + +#include <sys/cdefs.h> +#include <sys/param.h> +#include <stand.h> +#if defined(EFI) +#include <efi.h> +#include <efilib.h> +#else +#include <btxv86.h> +#include <vbe.h> +#endif +#include <sys/tem_impl.h> +#include <sys/consplat.h> +#include <sys/visual_io.h> +#include <sys/multiboot2.h> +#include <sys/font.h> +#include <sys/rgb.h> +#include <sys/endian.h> +#include <gfx_fb.h> +#include <pnglite.h> +#include <bootstrap.h> +#include <lz4.h> + +/* VGA text mode does use bold font. */ +#if !defined(VGA_8X16_FONT) +#define VGA_8X16_FONT "/boot/fonts/8x16b.fnt" +#endif +#if !defined(DEFAULT_8X16_FONT) +#define DEFAULT_8X16_FONT "/boot/fonts/8x16.fnt" +#endif + +/* + * Global framebuffer struct, to be updated with mode changes. + */ +multiboot_tag_framebuffer_t gfx_fb; + +/* To support setenv, keep track of inverses and colors. */ +static int gfx_inverse = 0; +static int gfx_inverse_screen = 0; +static uint8_t gfx_fg = DEFAULT_ANSI_FOREGROUND; +static uint8_t gfx_bg = DEFAULT_ANSI_BACKGROUND; +#if defined(EFI) +EFI_GRAPHICS_OUTPUT_BLT_PIXEL *shadow_fb; +static EFI_GRAPHICS_OUTPUT_BLT_PIXEL *GlyphBuffer; +#else +struct paletteentry *shadow_fb; +static struct paletteentry *GlyphBuffer; +#endif +static size_t GlyphBufferSize; + +int gfx_fb_cons_clear(struct vis_consclear *); +void gfx_fb_cons_copy(struct vis_conscopy *); +void gfx_fb_cons_display(struct vis_consdisplay *); + +static bool insert_font(char *, FONT_FLAGS); + +/* + * Set default operations to use bitmap based implementation. + * In case of UEFI, if GOP is available, we will switch to GOP based + * implementation. + * + * Also note, for UEFI we do attempt to boost the execution by setting + * Task Priority Level (TPL) to TPL_NOTIFY, which is highest priority + * usable in application. + */ + +/* + * Translate platform specific FB address. + */ +static uint8_t * +gfx_get_fb_address(void) +{ + return ((uint8_t *)ptov(gfx_fb.framebuffer_common.framebuffer_addr)); +} + +/* + * Generic platform callbacks for tem. + */ +void +plat_tem_get_prom_font_size(int *charheight, int *windowtop) +{ + *charheight = 0; + *windowtop = 0; +} + +void +plat_tem_get_colors(uint8_t *fg, uint8_t *bg) +{ + *fg = gfx_fg; + *bg = gfx_bg; +} + +void +plat_tem_get_inverses(int *inverse, int *inverse_screen) +{ + *inverse = gfx_inverse; + *inverse_screen = gfx_inverse_screen; +} + +/* + * 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); +} + +uint32_t +gfx_fb_color_map(uint8_t index) +{ + return (rgb_color_map(&rgb_info, index, 0xff)); +} + +static bool +color_name_to_ansi(const char *name, int *val) +{ + if (strcasecmp(name, "black") == 0) { + *val = ANSI_COLOR_BLACK; + return (true); + } + if (strcasecmp(name, "red") == 0) { + *val = ANSI_COLOR_RED; + return (true); + } + if (strcasecmp(name, "green") == 0) { + *val = ANSI_COLOR_GREEN; + return (true); + } + if (strcasecmp(name, "yellow") == 0) { + *val = ANSI_COLOR_YELLOW; + return (true); + } + if (strcasecmp(name, "blue") == 0) { + *val = ANSI_COLOR_BLUE; + return (true); + } + if (strcasecmp(name, "magenta") == 0) { + *val = ANSI_COLOR_MAGENTA; + return (true); + } + if (strcasecmp(name, "cyan") == 0) { + *val = ANSI_COLOR_CYAN; + return (true); + } + if (strcasecmp(name, "white") == 0) { + *val = ANSI_COLOR_WHITE; + return (true); + } + return (false); +} + +/* Callback to check and set colors */ +static int +gfx_set_colors(struct env_var *ev, int flags, const void *value) +{ + int val = 0, limit; + char buf[2]; + const void *evalue; + + if (value == NULL) + return (CMD_OK); + + limit = 255; + + if (color_name_to_ansi(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-255].\n"); + return (CMD_OK); + } + evalue = value; + } + + /* invalid value? */ + if ((val < 0 || val > limit)) { + printf("Allowed values are either ansi color name or " + "number from range [0-255].\n"); + return (CMD_OK); + } + + if (strcmp(ev->ev_name, "tem.fg_color") == 0) { + /* is it already set? */ + if (gfx_fg == val) + return (CMD_OK); + gfx_fg = val; + } + if (strcmp(ev->ev_name, "tem.bg_color") == 0) { + /* is it already set? */ + if (gfx_bg == val) + return (CMD_OK); + gfx_bg = val; + } + env_setenv(ev->ev_name, flags | EV_NOHOOK, evalue, NULL, NULL); + plat_cons_update_mode(-1); + return (CMD_OK); +} + +/* Callback to check and set inverses */ +static int +gfx_set_inverses(struct env_var *ev, int flags, const void *value) +{ + int t, f; + + if (value == NULL) + return (CMD_OK); + + t = strcmp(value, "true"); + f = strcmp(value, "false"); + + /* invalid value? */ + if (t != 0 && f != 0) + return (CMD_OK); + + if (strcmp(ev->ev_name, "tem.inverse") == 0) { + /* is it already set? */ + if (gfx_inverse == (t == 0)) + return (CMD_OK); + gfx_inverse = (t == 0); + } + if (strcmp(ev->ev_name, "tem.inverse-screen") == 0) { + /* is it already set? */ + if (gfx_inverse_screen == (t == 0)) + return (CMD_OK); + gfx_inverse_screen = (t == 0); + } + env_setenv(ev->ev_name, flags | EV_NOHOOK, value, NULL, NULL); + plat_cons_update_mode(-1); + return (CMD_OK); +} + +/* + * Initialize gfx framework. + */ +void +gfx_framework_init(void) +{ + int rc, limit; + char *env, buf[2]; + + if (gfx_fb.framebuffer_common.framebuffer_bpp < 24) + limit = 7; + else + limit = 255; + + /* set up tem inverse controls */ + env = getenv("tem.inverse"); + if (env != NULL) { + if (strcmp(env, "true") == 0) + gfx_inverse = 1; + unsetenv("tem.inverse"); + } + + env = getenv("tem.inverse-screen"); + if (env != NULL) { + if (strcmp(env, "true") == 0) + gfx_inverse_screen = 1; + unsetenv("tem.inverse-screen"); + } + + if (gfx_inverse) + env = "true"; + else + env = "false"; + + env_setenv("tem.inverse", EV_VOLATILE, env, gfx_set_inverses, + env_nounset); + + if (gfx_inverse_screen) + env = "true"; + else + env = "false"; + + env_setenv("tem.inverse-screen", EV_VOLATILE, env, gfx_set_inverses, + env_nounset); + + /* set up tem color controls */ + env = getenv("tem.fg_color"); + if (env != NULL) { + rc = (int)strtol(env, NULL, 0); + if ((rc >= 0 && rc <= limit) && (rc <= 7 || rc >= 16)) + gfx_fg = rc; + unsetenv("tem.fg_color"); + } + + env = getenv("tem.bg_color"); + if (env != NULL) { + rc = (int)strtol(env, NULL, 0); + if ((rc >= 0 && rc <= limit) && (rc <= 7 || rc >= 16)) + gfx_bg = rc; + unsetenv("tem.bg_color"); + } + + snprintf(buf, sizeof (buf), "%d", gfx_fg); + env_setenv("tem.fg_color", EV_VOLATILE, buf, gfx_set_colors, + env_nounset); + snprintf(buf, sizeof (buf), "%d", gfx_bg); + env_setenv("tem.bg_color", EV_VOLATILE, buf, gfx_set_colors, + env_nounset); + + /* + * Setup font list to have builtin font. + */ + (void) insert_font(NULL, FONT_BUILTIN); +} + +/* + * Get indexed color from RGB. This function is used to write data to video + * memory when the adapter is set to use indexed colors. + * Since UEFI does only support 32-bit colors, we do not implement it for + * UEFI because there is no need for it and we do not have palette array + * for UEFI. + */ +static uint8_t +rgb_to_color_index(uint8_t r, uint8_t g, uint8_t b) +{ +#if !defined(EFI) + uint32_t color, best, dist, k; + int diff; + + color = 0; + best = 255 * 255 * 255; + for (k = 0; k < NCMAP; k++) { + diff = r - pe8[k].Red; + dist = diff * diff; + diff = g - pe8[k].Green; + dist += diff * diff; + diff = b - pe8[k].Blue; + dist += diff * diff; + + /* Exact match, exit the loop */ + if (dist == 0) + break; + + if (dist < best) { + color = k; + best = dist; + } + } + if (k == NCMAP) + k = color; + return (k); +#else + (void) r; + (void) g; + (void) b; + return (0); +#endif +} + +static void +gfx_mem_wr1(uint8_t *base, size_t size, uint32_t o, uint8_t v) +{ + + if (o >= size) + return; + *(uint8_t *)(base + o) = v; +} + +static void +gfx_mem_wr2(uint8_t *base, size_t size, uint32_t o, uint16_t v) +{ + + if (o >= size) + return; + *(uint16_t *)(base + o) = v; +} + +static void +gfx_mem_wr4(uint8_t *base, size_t size, uint32_t o, uint32_t v) +{ + + if (o >= size) + return; + *(uint32_t *)(base + o) = v; +} + +static int +gfxfb_blt_fill(void *BltBuffer, + uint32_t DestinationX, uint32_t DestinationY, + uint32_t Width, uint32_t Height) +{ +#if defined(EFI) + EFI_GRAPHICS_OUTPUT_BLT_PIXEL *p; +#else + struct paletteentry *p; +#endif + uint32_t data, bpp, pitch, y, x; + size_t size; + off_t off; + uint8_t *destination; + + if (BltBuffer == NULL) + return (EINVAL); + + if (DestinationY + Height > + gfx_fb.framebuffer_common.framebuffer_height) + return (EINVAL); + + if (DestinationX + Width > gfx_fb.framebuffer_common.framebuffer_width) + return (EINVAL); + + if (Width == 0 || Height == 0) + return (EINVAL); + + p = BltBuffer; + if (gfx_fb.framebuffer_common.framebuffer_bpp == 8) { + data = rgb_to_color_index(p->Red, p->Green, p->Blue); + } else { + data = (p->Red & + ((1 << gfx_fb.u.fb2.framebuffer_red_mask_size) - 1)) << + gfx_fb.u.fb2.framebuffer_red_field_position; + data |= (p->Green & + ((1 << gfx_fb.u.fb2.framebuffer_green_mask_size) - 1)) << + gfx_fb.u.fb2.framebuffer_green_field_position; + data |= (p->Blue & + ((1 << gfx_fb.u.fb2.framebuffer_blue_mask_size) - 1)) << + gfx_fb.u.fb2.framebuffer_blue_field_position; + } + + bpp = roundup2(gfx_fb.framebuffer_common.framebuffer_bpp, 8) >> 3; + pitch = gfx_fb.framebuffer_common.framebuffer_pitch; + destination = gfx_get_fb_address(); + size = gfx_fb.framebuffer_common.framebuffer_height * pitch; + + 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) ? + solaris_color_to_pc_color[data] : data); + break; + case 2: + gfx_mem_wr2(destination, size, off, data); + break; + case 3: + gfx_mem_wr1(destination, size, off, + (data >> 16) & 0xff); + gfx_mem_wr1(destination, size, off + 1, + (data >> 8) & 0xff); + gfx_mem_wr1(destination, size, off + 2, + data & 0xff); + break; + case 4: + gfx_mem_wr4(destination, size, off, data); + break; + default: + return (EINVAL); + } + off += bpp; + } + } + + return (0); +} + +static int +gfxfb_blt_video_to_buffer(void *BltBuffer, uint32_t SourceX, uint32_t SourceY, + uint32_t DestinationX, uint32_t DestinationY, + uint32_t Width, uint32_t Height, uint32_t Delta) +{ +#if defined(EFI) + EFI_GRAPHICS_OUTPUT_BLT_PIXEL *p; +#else + struct paletteentry *p; +#endif + uint32_t x, sy, dy; + uint32_t bpp, pitch, copybytes; + off_t off; + uint8_t *source, *destination, *sb; + uint8_t rm, rp, gm, gp, bm, bp; + bool bgra; + + if (BltBuffer == NULL) + return (EINVAL); + + if (SourceY + Height > + gfx_fb.framebuffer_common.framebuffer_height) + return (EINVAL); + + if (SourceX + Width > gfx_fb.framebuffer_common.framebuffer_width) + return (EINVAL); + + if (Width == 0 || Height == 0) + return (EINVAL); + + if (Delta == 0) + Delta = Width * sizeof (*p); + + bpp = roundup2(gfx_fb.framebuffer_common.framebuffer_bpp, 8) >> 3; + pitch = gfx_fb.framebuffer_common.framebuffer_pitch; + + copybytes = Width * bpp; + + rm = (1 << gfx_fb.u.fb2.framebuffer_red_mask_size) - 1; + rp = gfx_fb.u.fb2.framebuffer_red_field_position; + gm = (1 << gfx_fb.u.fb2.framebuffer_green_mask_size) - 1; + gp = gfx_fb.u.fb2.framebuffer_green_field_position; + bm = (1 << gfx_fb.u.fb2.framebuffer_blue_mask_size) - 1; + bp = gfx_fb.u.fb2.framebuffer_blue_field_position; + /* If FB pixel format is BGRA, we can use direct copy. */ + bgra = bpp == 4 && + gfx_fb.u.fb2.framebuffer_red_mask_size == 8 && + gfx_fb.u.fb2.framebuffer_red_field_position == 16 && + gfx_fb.u.fb2.framebuffer_green_mask_size == 8 && + gfx_fb.u.fb2.framebuffer_green_field_position == 8 && + gfx_fb.u.fb2.framebuffer_blue_mask_size == 8 && + gfx_fb.u.fb2.framebuffer_blue_field_position == 0; + + for (sy = SourceY, dy = DestinationY; dy < Height + DestinationY; + sy++, dy++) { + off = sy * pitch + SourceX * bpp; + source = gfx_get_fb_address() + off; + destination = (uint8_t *)BltBuffer + dy * Delta + + DestinationX * sizeof (*p); + + if (bgra) { + bcopy(source, destination, copybytes); + } else { + for (x = 0; x < Width; x++) { + uint32_t c = 0; + + p = (void *)(destination + x * sizeof (*p)); + sb = source + x * bpp; + switch (bpp) { + case 1: + c = *sb; + break; + case 2: + c = *(uint16_t *)sb; + break; + case 3: + c = sb[0] << 16 | sb[1] << 8 | sb[2]; + break; + case 4: + c = *(uint32_t *)sb; + break; + default: + return (EINVAL); + } + + if (bpp == 1) { + *(uint32_t *)p = gfx_fb_color_map( + (c < NCOLORS) ? + pc_color_to_solaris_color[c] : c); + } else { + p->Red = (c >> rp) & rm; + p->Green = (c >> gp) & gm; + p->Blue = (c >> bp) & bm; + p->Reserved = 0; + } + } + } + } + + return (0); +} + +static int +gfxfb_blt_buffer_to_video(void *BltBuffer, uint32_t SourceX, uint32_t SourceY, + uint32_t DestinationX, uint32_t DestinationY, + uint32_t Width, uint32_t Height, uint32_t Delta) +{ +#if defined(EFI) + EFI_GRAPHICS_OUTPUT_BLT_PIXEL *p; +#else + struct paletteentry *p; +#endif + uint32_t x, sy, dy; + uint32_t bpp, pitch, copybytes; + off_t off; + uint8_t *source, *destination; + uint8_t rm, rp, gm, gp, bm, bp; + bool bgra; + + if (BltBuffer == NULL) + return (EINVAL); + + if (DestinationY + Height > + gfx_fb.framebuffer_common.framebuffer_height) + return (EINVAL); + + if (DestinationX + Width > gfx_fb.framebuffer_common.framebuffer_width) + return (EINVAL); + + if (Width == 0 || Height == 0) + return (EINVAL); + + if (Delta == 0) + Delta = Width * sizeof (*p); + + bpp = roundup2(gfx_fb.framebuffer_common.framebuffer_bpp, 8) >> 3; + pitch = gfx_fb.framebuffer_common.framebuffer_pitch; + + copybytes = Width * bpp; + + rm = (1 << gfx_fb.u.fb2.framebuffer_red_mask_size) - 1; + rp = gfx_fb.u.fb2.framebuffer_red_field_position; + gm = (1 << gfx_fb.u.fb2.framebuffer_green_mask_size) - 1; + gp = gfx_fb.u.fb2.framebuffer_green_field_position; + bm = (1 << gfx_fb.u.fb2.framebuffer_blue_mask_size) - 1; + bp = gfx_fb.u.fb2.framebuffer_blue_field_position; + /* If FB pixel format is BGRA, we can use direct copy. */ + bgra = bpp == 4 && + gfx_fb.u.fb2.framebuffer_red_mask_size == 8 && + gfx_fb.u.fb2.framebuffer_red_field_position == 16 && + gfx_fb.u.fb2.framebuffer_green_mask_size == 8 && + gfx_fb.u.fb2.framebuffer_green_field_position == 8 && + gfx_fb.u.fb2.framebuffer_blue_mask_size == 8 && + gfx_fb.u.fb2.framebuffer_blue_field_position == 0; + + for (sy = SourceY, dy = DestinationY; sy < Height + SourceY; + sy++, dy++) { + off = dy * pitch + DestinationX * bpp; + destination = gfx_get_fb_address() + off; + + if (bgra) { + source = (uint8_t *)BltBuffer + sy * Delta + + SourceX * sizeof (*p); + bcopy(source, destination, copybytes); + } else { + for (x = 0; x < Width; x++) { + uint32_t c; + + p = (void *)((uint8_t *)BltBuffer + + sy * Delta + + (SourceX + x) * sizeof (*p)); + if (bpp == 1) { + c = rgb_to_color_index(p->Red, + p->Green, p->Blue); + } else { + c = (p->Red & rm) << rp | + (p->Green & gm) << gp | + (p->Blue & bm) << bp; + } + off = x * bpp; + switch (bpp) { + case 1: + gfx_mem_wr1(destination, copybytes, + off, (c < NCOLORS) ? + solaris_color_to_pc_color[c] : c); + break; + case 2: + gfx_mem_wr2(destination, copybytes, + off, c); + break; + case 3: + gfx_mem_wr1(destination, copybytes, + off, (c >> 16) & 0xff); + gfx_mem_wr1(destination, copybytes, + off + 1, (c >> 8) & 0xff); + gfx_mem_wr1(destination, copybytes, + off + 2, c & 0xff); + break; + case 4: + gfx_mem_wr4(destination, copybytes, + off, c); + break; + default: + return (EINVAL); + } + } + } + } + + return (0); +} + +static int +gfxfb_blt_video_to_video(uint32_t SourceX, uint32_t SourceY, + uint32_t DestinationX, uint32_t DestinationY, + uint32_t Width, uint32_t Height) +{ + uint32_t bpp, copybytes; + int pitch; + uint8_t *source, *destination; + off_t off; + + if (SourceY + Height > + gfx_fb.framebuffer_common.framebuffer_height) + return (EINVAL); + + if (SourceX + Width > gfx_fb.framebuffer_common.framebuffer_width) + return (EINVAL); + + if (DestinationY + Height > + gfx_fb.framebuffer_common.framebuffer_height) + return (EINVAL); + + if (DestinationX + Width > gfx_fb.framebuffer_common.framebuffer_width) + return (EINVAL); + + if (Width == 0 || Height == 0) + return (EINVAL); + + bpp = roundup2(gfx_fb.framebuffer_common.framebuffer_bpp, 8) >> 3; + pitch = gfx_fb.framebuffer_common.framebuffer_pitch; + + 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; + + /* + * To handle overlapping areas, set up reverse copy here. + */ + 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); +} + +static void +gfxfb_shadow_fill(uint32_t *BltBuffer, + uint32_t DestinationX, uint32_t DestinationY, + uint32_t Width, uint32_t Height) +{ + uint32_t fbX, fbY; + + if (shadow_fb == NULL) + return; + + fbX = gfx_fb.framebuffer_common.framebuffer_width; + fbY = gfx_fb.framebuffer_common.framebuffer_height; + + if (BltBuffer == NULL) + return; + + if (DestinationX + Width > fbX) + Width = fbX - DestinationX; + + if (DestinationY + Height > fbY) + Height = fbY - DestinationY; + + uint32_t y2 = Height + DestinationY; + for (uint32_t y1 = DestinationY; y1 < y2; y1++) { + uint32_t off = y1 * fbX + DestinationX; + + for (uint32_t x = 0; x < Width; x++) { + *(uint32_t *)&shadow_fb[off + x] = *BltBuffer; + } + } +} + +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_TPL tpl; + extern EFI_GRAPHICS_OUTPUT *gop; + + /* + * We assume Blt() does work, if not, we will need to build + * exception list case by case. + * Once boot services are off, we can not use GOP Blt(). + */ + if (gop != NULL && has_boot_services) { + tpl = BS->RaiseTPL(TPL_NOTIFY); + switch (BltOperation) { + case GfxFbBltVideoFill: + gfxfb_shadow_fill(BltBuffer, DestinationX, + DestinationY, Width, Height); + 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; + } + + BS->RestoreTPL(tpl); + return (rv); + } +#endif + + switch (BltOperation) { + case GfxFbBltVideoFill: + gfxfb_shadow_fill(BltBuffer, DestinationX, DestinationY, + Width, Height); + 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); +} + +/* + * visual io callbacks. + */ +int +gfx_fb_cons_clear(struct vis_consclear *ca) +{ + int rv; + uint32_t width, height; + + width = gfx_fb.framebuffer_common.framebuffer_width; + height = gfx_fb.framebuffer_common.framebuffer_height; + + rv = gfxfb_blt(&ca->bg_color, GfxFbBltVideoFill, 0, 0, + 0, 0, width, height, 0); + + return (rv); +} + +void +gfx_fb_cons_copy(struct vis_conscopy *ma) +{ +#if defined(EFI) + EFI_GRAPHICS_OUTPUT_BLT_PIXEL *source, *destination; +#else + struct paletteentry *source, *destination; +#endif + uint32_t width, height, bytes; + uint32_t sx, sy, dx, dy; + uint32_t pitch; + int step; + + width = ma->e_col - ma->s_col + 1; + height = ma->e_row - ma->s_row + 1; + + sx = ma->s_col; + sy = ma->s_row; + dx = ma->t_col; + dy = ma->t_row; + + if (sx + width > gfx_fb.framebuffer_common.framebuffer_width) + width = gfx_fb.framebuffer_common.framebuffer_width - sx; + + if (sy + height > gfx_fb.framebuffer_common.framebuffer_height) + height = gfx_fb.framebuffer_common.framebuffer_height - sy; + + if (dx + width > gfx_fb.framebuffer_common.framebuffer_width) + width = gfx_fb.framebuffer_common.framebuffer_width - dx; + + if (dy + height > gfx_fb.framebuffer_common.framebuffer_height) + height = gfx_fb.framebuffer_common.framebuffer_height - dy; + + if (width == 0 || height == 0) + return; + + /* + * With no shadow fb, use video to video copy. + */ + if (shadow_fb == NULL) { + (void) gfxfb_blt(NULL, GfxFbBltVideoToVideo, + sx, sy, dx, dy, width, height, 0); + return; + } + + /* + * With shadow fb, we need to copy data on both shadow and video, + * to preserve the consistency. We only read data from shadow fb. + */ + + step = 1; + pitch = gfx_fb.framebuffer_common.framebuffer_width; + bytes = width * sizeof (*shadow_fb); + + /* + * To handle overlapping areas, set up reverse copy here. + */ + if (dy * pitch + dx > sy * pitch + sx) { + sy += height; + dy += height; + step = -step; + } + + while (height-- > 0) { + source = &shadow_fb[sy * pitch + sx]; + destination = &shadow_fb[dy * pitch + dx]; + + bcopy(source, destination, bytes); + (void) gfxfb_blt(destination, GfxFbBltBufferToVideo, + 0, 0, dx, dy, width, 1, 0); + + sy += step; + dy += step; + } +} + +/* + * 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; + uint8_t max_alpha; + + /* 15/16 bit depths have alpha channel size less than 8 */ + max_alpha = (1 << (rgb_info.red.size + rgb_info.green.size + + rgb_info.blue.size) / 3) - 1; + + /* trivial corner cases */ + if (alpha == 0) + return (bg); + if (alpha >= max_alpha) + return (fg); + blend = (alpha * fg + (max_alpha - alpha) * bg); + /* Division by max_alpha */ + h = blend >> 8; + l = blend & max_alpha; + if (h + l >= max_alpha) + h++; + return (h); +} + +/* Copy memory to framebuffer or to memory. */ +static void +bitmap_cpy(void *dst, void *src, size_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; + + 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(struct vis_consdisplay *da) +{ +#if defined(EFI) + EFI_GRAPHICS_OUTPUT_BLT_PIXEL *BltBuffer, *data; +#else + struct paletteentry *BltBuffer, *data; +#endif + uint32_t size; + + /* make sure we will not write past FB */ + if ((uint32_t)da->col >= gfx_fb.framebuffer_common.framebuffer_width || + (uint32_t)da->row >= gfx_fb.framebuffer_common.framebuffer_height || + (uint32_t)da->col + da->width > + gfx_fb.framebuffer_common.framebuffer_width || + (uint32_t)da->row + da->height > + gfx_fb.framebuffer_common.framebuffer_height) + return; + + /* + * If we do have shadow fb, we will use shadow to render data, + * and copy shadow to video. + */ + if (shadow_fb != NULL) { + uint32_t pitch = gfx_fb.framebuffer_common.framebuffer_width; + uint32_t dx, dy, width, height; + + dx = da->col; + dy = da->row; + height = da->height; + width = da->width; + + data = (void *)da->data; + /* Copy rectangle line by line. */ + for (uint32_t y = 0; y < height; y++) { + BltBuffer = shadow_fb + dy * pitch + dx; + bitmap_cpy(BltBuffer, &data[y * width], width); + (void) gfxfb_blt(BltBuffer, GfxFbBltBufferToVideo, + 0, 0, dx, dy, width, 1, 0); + dy++; + } + return; + } + + /* + * Common data to display is glyph, use preallocated + * glyph buffer. + */ + if (tems.ts_pix_data_size != GlyphBufferSize) + (void) allocate_glyphbuffer(da->width, da->height); + + size = sizeof (*BltBuffer) * da->width * da->height; + if (size == GlyphBufferSize) { + BltBuffer = GlyphBuffer; + } else { + BltBuffer = malloc(size); + } + if (BltBuffer == NULL) + return; + + if (gfxfb_blt(BltBuffer, GfxFbBltVideoToBltBuffer, + da->col, da->row, 0, 0, da->width, da->height, 0) == 0) { + bitmap_cpy(BltBuffer, da->data, da->width * da->height); + (void) gfxfb_blt(BltBuffer, GfxFbBltBufferToVideo, + 0, 0, da->col, da->row, da->width, da->height, 0); + } + + if (BltBuffer != GlyphBuffer) + free(BltBuffer); +} + +static void +gfx_fb_cursor_impl(void *buf, uint32_t stride, uint32_t fg, uint32_t bg, + struct vis_conscursor *ca) +{ +#if defined(EFI) + EFI_GRAPHICS_OUTPUT_BLT_PIXEL *p; +#else + struct paletteentry *p; +#endif + union pixel { +#if defined(EFI) + EFI_GRAPHICS_OUTPUT_BLT_PIXEL p; +#else + struct paletteentry p; +#endif + uint32_t p32; + } *row; + + p = buf; + + /* + * Build inverse image of the glyph. + * Since xor has self-inverse property, drawing cursor + * second time on the same spot, will restore the original content. + */ + for (screen_size_t i = 0; i < ca->height; i++) { + row = (union pixel *)(p + i * stride); + for (screen_size_t j = 0; j < ca->width; j++) { + row[j].p32 = (row[j].p32 ^ fg) ^ bg; + } + } +} + +void +gfx_fb_display_cursor(struct vis_conscursor *ca) +{ + union pixel { +#if defined(EFI) + EFI_GRAPHICS_OUTPUT_BLT_PIXEL p; +#else + struct paletteentry p; +#endif + uint32_t p32; + } fg, bg; + + bcopy(&ca->fg_color, &fg.p32, sizeof (fg.p32)); + bcopy(&ca->bg_color, &bg.p32, sizeof (bg.p32)); + + if (shadow_fb == NULL && + allocate_glyphbuffer(ca->width, ca->height) != NULL) { + if (gfxfb_blt(GlyphBuffer, GfxFbBltVideoToBltBuffer, + ca->col, ca->row, 0, 0, ca->width, ca->height, 0) == 0) + gfx_fb_cursor_impl(GlyphBuffer, ca->width, + fg.p32, bg.p32, ca); + + (void) gfxfb_blt(GlyphBuffer, GfxFbBltBufferToVideo, 0, 0, + ca->col, ca->row, ca->width, ca->height, 0); + return; + } + + uint32_t pitch = gfx_fb.framebuffer_common.framebuffer_width; + uint32_t dx, dy, width, height; + + dx = ca->col; + dy = ca->row; + width = ca->width; + height = ca->height; + + gfx_fb_cursor_impl(shadow_fb + dy * pitch + dx, pitch, + fg.p32, bg.p32, ca); + /* Copy rectangle line by line. */ + for (uint32_t y = 0; y < height; y++) { + (void) gfxfb_blt(shadow_fb + dy * pitch + dx, + GfxFbBltBufferToVideo, 0, 0, dx, dy, width, 1, 0); + dy++; + } +} + +/* + * 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) +{ + text_color_t fg, bg; + + if (plat_stdout_is_framebuffer() == 0) + return; + + tem_get_colors((tem_vt_state_t)tems.ts_active, &fg, &bg); + + if (x >= gfx_fb.framebuffer_common.framebuffer_width || + y >= gfx_fb.framebuffer_common.framebuffer_height) + return; + + gfxfb_blt(&fg.n, GfxFbBltVideoFill, 0, 0, x, y, 1, 1, 0); +} + +/* + * draw rectangle in framebuffer using gfx coordinates. + */ +void +gfx_fb_drawrect(uint32_t x1, uint32_t y1, uint32_t x2, uint32_t y2, + uint32_t fill) +{ + text_color_t fg, bg; + + if (plat_stdout_is_framebuffer() == 0) + return; + + tem_get_colors((tem_vt_state_t)tems.ts_active, &fg, &bg); + + if (fill != 0) { + gfxfb_blt(&fg.n, GfxFbBltVideoFill, + 0, 0, x1, y1, x2 - x1, y2 - y1, 0); + } else { + gfxfb_blt(&fg.n, GfxFbBltVideoFill, + 0, 0, x1, y1, x2 - x1, 1, 0); + gfxfb_blt(&fg.n, GfxFbBltVideoFill, + 0, 0, x1, y2, x2 - x1, 1, 0); + gfxfb_blt(&fg.n, GfxFbBltVideoFill, + 0, 0, x1, y1, 1, y2 - y1, 0); + gfxfb_blt(&fg.n, GfxFbBltVideoFill, + 0, 0, x2, y1, 1, y2 - y1, 0); + } +} + +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 (plat_stdout_is_framebuffer() == 0) + 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 (plat_stdout_is_framebuffer() == 0) + 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; + + if (plat_stdout_is_framebuffer() == 0) + return; + + vf_width = tems.ts_font.vf_width; + vf_height = tems.ts_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 tem */ + tem_image_display(tems.ts_active, uy1, ux1, uy2 + 1, ux2 + 1); + + /* + * Draw horizontal lines width points thick, shifted from outer edge. + */ + x1 = (ux1 + 1) * vf_width + tems.ts_p_offset.x; + y1 = uy1 * vf_height + tems.ts_p_offset.y + yshift; + x2 = ux2 * vf_width + tems.ts_p_offset.x; + gfx_fb_drawrect(x1, y1, x2, y1 + width, 1); + y2 = uy2 * vf_height + tems.ts_p_offset.y; + 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 + tems.ts_p_offset.x + xshift; + y1 = uy1 * vf_height + tems.ts_p_offset.y; + y1 += vf_height; + y2 = uy2 * vf_height + tems.ts_p_offset.y; + gfx_fb_drawrect(x1, y1, x1 + width, y2, 1); + x1 = ux2 * vf_width + tems.ts_p_offset.x; + x1 += vf_width - xshift - width; + gfx_fb_drawrect(x1, y1, x1 + width, y2, 1); + + /* Draw upper left corner. */ + x1 = ux1 * vf_width + tems.ts_p_offset.x + xshift; + y1 = uy1 * vf_height + tems.ts_p_offset.y; + y1 += vf_height; + + x2 = ux1 * vf_width + tems.ts_p_offset.x; + x2 += vf_width; + y2 = uy1 * vf_height + tems.ts_p_offset.y + 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 + tems.ts_p_offset.x; + x1 += vf_width; + y1 = uy2 * vf_height + tems.ts_p_offset.y; + y1 += vf_height - yshift; + x2 = ux1 * vf_width + tems.ts_p_offset.x + xshift; + y2 = uy2 * vf_height + tems.ts_p_offset.y; + 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 + tems.ts_p_offset.x; + y1 = uy1 * vf_height + tems.ts_p_offset.y + yshift; + x2 = ux2 * vf_width + tems.ts_p_offset.x; + x2 += vf_width - xshift - width; + y2 = uy1 * vf_height + tems.ts_p_offset.y; + 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 + tems.ts_p_offset.x; + y1 = uy2 * vf_height + tems.ts_p_offset.y; + y1 += vf_height - yshift; + x2 = ux2 * vf_width + tems.ts_p_offset.x; + x2 += vf_width - xshift - width; + y2 = uy2 * vf_height + tems.ts_p_offset.y; + 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 + struct vis_consdisplay da; + uint32_t i, j, x, y, fheight, fwidth; + uint8_t r, g, b, a; + bool scale = false; + bool trace = false; + + trace = (flags & FL_PUTIMAGE_DEBUG) != 0; + + if (plat_stdout_is_framebuffer() == 0) { + 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_fb.framebuffer_common.framebuffer_width || + uy1 > gfx_fb.framebuffer_common.framebuffer_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_fb.framebuffer_common.framebuffer_width || + uy2 > gfx_fb.framebuffer_common.framebuffer_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_fb.framebuffer_common.framebuffer_width - + tems.ts_p_offset.x; + 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_fb.framebuffer_common.framebuffer_height - + tems.ts_p_offset.y; + 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); + + da.col = ux1; + da.row = uy1; + da.width = fwidth; + da.height = fheight; + + /* + * mark area used in tem + */ + if (!(flags & FL_PUTIMAGE_NOSCROLL)) { + tem_image_display(tems.ts_active, + da.row / tems.ts_font.vf_height, + da.col / tems.ts_font.vf_width, + (da.row + da.height) / tems.ts_font.vf_height, + (da.col + da.width) / tems.ts_font.vf_width); + } + + if ((flags & FL_PUTIMAGE_BORDER)) + gfx_fb_drawrect(ux1, uy1, ux2, uy2, 0); + + da.data = malloc(fwidth * fheight * sizeof (*p)); + p = (void *)da.data; + if (da.data == NULL) { + if (trace) + printf("Out of memory.\n"); + return (1); + } + + /* + * Build image for our framebuffer. + */ + + /* Helper to calculate the pixel index from the source png */ +#define GETPIXEL(xx, yy) (((yy) * png->width + (xx)) * png->bpp) + + /* + * For each of the x and y directions, calculate the number of pixels + * in the source image that correspond to a single pixel in the target. + * Use fixed-point arithmetic with 16-bits for each of the integer and + * fractional parts. + */ + const uint32_t wcstep = ((png->width - 1) << 16) / (fwidth - 1); + const uint32_t hcstep = ((png->height - 1) << 16) / (fheight - 1); + + uint32_t hc = 0; + for (y = 0; y < fheight; y++) { + uint32_t hc2 = (hc >> 9) & 0x7f; + uint32_t hc1 = 0x80 - hc2; + + uint32_t offset_y = hc >> 16; + uint32_t offset_y1 = offset_y + 1; + + uint32_t wc = 0; + for (x = 0; x < fwidth; x++) { + uint32_t wc2 = (wc >> 9) & 0x7f; + uint32_t wc1 = 0x80 - wc2; + + uint32_t offset_x = wc >> 16; + uint32_t offset_x1 = offset_x + 1; + + /* Target pixel index */ + j = y * fwidth + x; + + 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 >> + (8 - gfx_fb.u.fb2.framebuffer_red_mask_size); + p[j].Green = g >> + (8 - gfx_fb.u.fb2.framebuffer_green_mask_size); + p[j].Blue = b >> + (8 - gfx_fb.u.fb2.framebuffer_blue_mask_size); + p[j].Reserved = a; + + wc += wcstep; + } + hc += hcstep; + } + + gfx_fb_cons_display(&da); + free(da.data); + return (0); +} + +/* Return w^2 + h^2 or 0, if the dimensions are unknown */ +static unsigned +edid_diagonal_squared(void) +{ + unsigned w, h; + + if (edid_info == NULL) + return (0); + + w = edid_info->display.max_horizontal_image_size; + h = edid_info->display.max_vertical_image_size; + + /* If either one is 0, we have aspect ratio, not size */ + if (w == 0 || h == 0) + return (0); + + /* + * some monitors encode the aspect ratio instead of the physical size. + */ + if ((w == 16 && h == 9) || (w == 16 && h == 10) || + (w == 4 && h == 3) || (w == 5 && h == 4)) + return (0); + + /* + * translate cm to inch, note we scale by 100 here. + */ + w = w * 100 / 254; + h = h * 100 / 254; + + /* Return w^2 + h^2 */ + return (w * w + h * h); +} + +/* + * calculate pixels per inch. + */ +static unsigned +gfx_get_ppi(void) +{ + unsigned dp, di; + + di = edid_diagonal_squared(); + if (di == 0) + return (0); + + dp = gfx_fb.framebuffer_common.framebuffer_width * + gfx_fb.framebuffer_common.framebuffer_width + + gfx_fb.framebuffer_common.framebuffer_height * + gfx_fb.framebuffer_common.framebuffer_height; + + return (isqrt(dp / di)); +} + +/* + * Calculate font size from density independent pixels (dp): + * ((16dp * ppi) / 160) * display_factor. + * Here we are using fixed constants: 1dp == 160 ppi and + * display_factor 2. + * + * We are rounding font size up and are searching for font which is + * not smaller than calculated size value. + */ +bitmap_data_t * +gfx_get_font(void) +{ + unsigned ppi, size; + bitmap_data_t *font = NULL; + struct fontlist *fl, *next; + + /* Text mode is not supported here. */ + if (gfx_fb.framebuffer_common.framebuffer_type == + MULTIBOOT_FRAMEBUFFER_TYPE_EGA_TEXT) + return (NULL); + + ppi = gfx_get_ppi(); + if (ppi == 0) + return (NULL); + + /* + * We will search for 16dp font. + * We are using scale up by 10 for roundup. + */ + size = (16 * ppi * 10) / 160; + /* Apply display factor 2. */ + size = roundup(size * 2, 10) / 10; + + STAILQ_FOREACH(fl, &fonts, font_next) { + next = STAILQ_NEXT(fl, font_next); + /* + * If this is last font or, if next font is smaller, + * we have our font. Make sure, it actually is loaded. + */ + if (next == NULL || next->font_data->height < size) { + font = fl->font_data; + if (font->font == NULL || + fl->font_flags == FONT_RELOAD) { + if (fl->font_load != NULL && + fl->font_name != NULL) + font = fl->font_load(fl->font_name); + } + break; + } + } + + return (font); +} + +static int +load_mapping(int fd, struct font *fp, int n) +{ + size_t i, size; + ssize_t rv; + struct font_map *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].font_src = be32toh(mp[i].font_src); + mp[i].font_dst = be16toh(mp[i].font_dst); + mp[i].font_len = be16toh(mp[i].font_len); + } + return (0); +} + +static int +builtin_mapping(struct font *fp, int n) +{ + size_t size; + struct font_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.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 bitmap_data_t * +load_font(char *path) +{ + int fd, i; + uint32_t glyphs; + struct font_header fh; + struct fontlist *fl; + bitmap_data_t *bp; + struct 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->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 font))) == NULL) + return (NULL); + + fp->vf_width = DEFAULT_FONT_DATA.width; + fp->vf_height = DEFAULT_FONT_DATA.height; + + fp->vf_bytes = malloc(DEFAULT_FONT_DATA.uncompressed_size); + if (fp->vf_bytes == NULL) { + free(fp); + return (NULL); + } + + bp->uncompressed_size = DEFAULT_FONT_DATA.uncompressed_size; + bp->compressed_size = DEFAULT_FONT_DATA.compressed_size; + + if (lz4_decompress(DEFAULT_FONT_DATA.compressed_data, + fp->vf_bytes, + DEFAULT_FONT_DATA.compressed_size, + DEFAULT_FONT_DATA.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.font->vf_map_count[i]; + if (builtin_mapping(fp, i) != 0) + goto free_done; + } + + bp->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 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->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 tem is notified. + * We do need to keep fl->font_data for glyph dimensions. + */ + STAILQ_FOREACH(fl, &fonts, font_next) { + if (fl->font_data->font == NULL) + continue; + + for (i = 0; i < VFNT_MAPS; i++) + free(fl->font_data->font->vf_map[i]); + free(fl->font_data->font->vf_bytes); + free(fl->font_data->font); + fl->font_data->font = NULL; + } + + bp->font = fp; + bp->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 buf[PATH_MAX]; + int fd, len; + + 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) { + np = malloc(sizeof (*np)); + if (np == NULL) { + close(fd); + return (nl); /* return what we have */ + } + np->n_name = strdup(buf); + if (np->n_name == NULL) { + 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.width; + fh.fh_height = DEFAULT_FONT_DATA.height; + + (void) asprintf(&font_name, "%dx%d", + DEFAULT_FONT_DATA.width, DEFAULT_FONT_DATA.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->width && + fh.fh_height == entry->font_data->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->width = fh.fh_width; + fp->font_data->height = fh.fh_height; + + if (STAILQ_EMPTY(&fonts)) { + STAILQ_INSERT_HEAD(&fonts, fp, font_next); + return (true); + } + + previous = NULL; + size = fp->font_data->width * fp->font_data->height; + + STAILQ_FOREACH(entry, &fonts, font_next) { + /* Should fp be inserted before the entry? */ + if (size > entry->font_data->width * entry->font_data->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->width * next->font_data->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->width == x && fl->font_data->height == y) + break; + } + if (fl != NULL) { + /* Reset any FONT_MANUAL flag. */ + reset_font_flags(); + + /* Mark this font manually loaded */ + fl->font_flags = FONT_MANUAL; + /* Trigger tem update. */ + tems.update_font = true; + plat_cons_update_mode(-1); + return (CMD_OK); + } + + printf("Available fonts:\n"); + STAILQ_FOREACH(fl, &fonts, font_next) { + printf(" %dx%d\n", fl->font_data->width, + fl->font_data->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); + tems.update_font = true; +} + +void +autoload_font(bool bios) +{ + struct name_list *nl; + struct name_entry *np; + + nl = read_list("/boot/fonts/fonts.dir"); + if (nl == NULL) + return; + + while (!SLIST_EMPTY(nl)) { + np = SLIST_FIRST(nl); + SLIST_REMOVE_HEAD(nl, n_entry); + if (insert_font(np->n_name, FONT_AUTO) == false) + printf("failed to add font: %s\n", np->n_name); + free(np->n_name); + free(np); + } + + unsetenv("screen-font"); + env_setenv("screen-font", EV_VOLATILE, NULL, font_set, env_nounset); + + /* + * If vga text mode was requested, load vga.font (8x16 bold) font. + */ + if (bios) { + bios_text_font(true); + } + + /* Trigger tem update. */ + tems.update_font = true; + plat_cons_update_mode(-1); +} + +COMMAND_SET(load_font, "loadfont", "load console font from file", command_font); + +static int +command_font(int argc, char *argv[]) +{ + int i, c, rc = CMD_OK; + struct fontlist *fl; + 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->width, + fl->font_data->height, + fl->font_data->font == NULL? "" : " loaded"); + } + return (CMD_OK); + } + + 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); + } + + tems.update_font = true; + plat_cons_update_mode(-1); + 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->font != NULL) { + /* Note the tem is releasing font bytes */ + for (i = 0; i < VFNT_MAPS; i++) + free(fl->font_data->font->vf_map[i]); + free(fl->font_data->font); + fl->font_data->font = NULL; + fl->font_data->uncompressed_size = 0; + fl->font_flags = FONT_AUTO; + } + } + tems.update_font = true; + plat_cons_update_mode(-1); + } + 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/usr/src/boot/common/gfx_fb.h b/usr/src/boot/common/gfx_fb.h new file mode 100644 index 0000000000..8d20dcf162 --- /dev/null +++ b/usr/src/boot/common/gfx_fb.h @@ -0,0 +1,176 @@ +/* + * This file and its contents are supplied under the terms of the + * Common Development and Distribution License ("CDDL"), version 1.0. + * You may only use this file in accordance with the terms of version + * 1.0 of the CDDL. + * + * A full copy of the text of the CDDL should have accompanied this + * source. A copy of the CDDL is also available via the Internet at + * http://www.illumos.org/license/CDDL. + */ + +/* + * Copyright 2017 Toomas Soome <tsoome@me.com> + * Copyright 2020 RackTop Systems, Inc. + */ + +#ifndef _GFX_FB_H +#define _GFX_FB_H + +#include <stdbool.h> +#include <sys/visual_io.h> +#include <sys/multiboot2.h> +#include <sys/queue.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; + +extern struct vesa_edid_info *edid_info; + +#define STD_TIMINGS 8 +#define DET_TIMINGS 4 + +#define HSIZE(x) (((x & 0xff) + 31) * 8) +#define RATIO(x) ((x & 0xC000) >> 14) +#define RATIO1_1 0 +/* EDID Ver. 1.3 redefined this */ +#define RATIO16_10 RATIO1_1 +#define RATIO4_3 1 +#define RATIO5_4 2 +#define RATIO16_9 3 + +/* + * Number of pixels and lines is 12-bit int, valid values 0-4095. + */ +#define EDID_MAX_PIXELS 4095 +#define EDID_MAX_LINES 4095 + +#define GET_EDID_INFO_WIDTH(edid_info, timings_num) \ + ((edid_info)->detailed_timings[(timings_num)].horizontal_active_lo | \ + (((uint_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 | \ + (((uint_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; + +extern multiboot_tag_framebuffer_t gfx_fb; + +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); + +void bios_text_font(bool); +bool gfx_get_edid_resolution(struct vesa_edid_info *, edid_res_list_t *); +void gfx_framework_init(void); +uint32_t gfx_fb_color_map(uint8_t); +int gfx_fb_cons_clear(struct vis_consclear *); +void gfx_fb_cons_copy(struct vis_conscopy *); +void gfx_fb_cons_display(struct vis_consdisplay *); +void gfx_fb_display_cursor(struct vis_conscursor *); +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); +void plat_cons_update_mode(int); + +#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 *); +#ifdef __cplusplus +} +#endif + +#endif /* _GFX_FB_H */ diff --git a/usr/src/boot/common/gpt.c b/usr/src/boot/common/gpt.c new file mode 100644 index 0000000000..e63a5419f1 --- /dev/null +++ b/usr/src/boot/common/gpt.c @@ -0,0 +1,384 @@ +/* + * Copyright (c) 2010 Pawel Jakub Dawidek <pjd@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 AUTHORS 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 AUTHORS 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> + +#include <sys/param.h> +#include <sys/gpt.h> + +#ifndef LITTLE_ENDIAN +#error gpt.c works only for little endian architectures +#endif + +#include "zlib.h" +#include "drv.h" +#include "util.h" +#include "gpt.h" + +#define MAXTBLENTS 128 + +static struct gpt_hdr hdr_primary, hdr_backup, *gpthdr; +static uint64_t hdr_primary_lba, hdr_backup_lba; +static struct gpt_ent table_primary[MAXTBLENTS], table_backup[MAXTBLENTS]; +static struct gpt_ent *gpttable; +static int curent, bootonce; + +/* + * Buffer below 64kB passed on gptread(), which can hold at least + * one sector of data (512 bytes). + */ +static char *secbuf; + +static void +gptupdate(const char *which, struct dsk *dskp, struct gpt_hdr *hdr, + struct gpt_ent *table) +{ + int entries_per_sec, firstent; + daddr_t slba; + + /* + * We need to update the following for both primary and backup GPT: + * 1. Sector on disk that contains current partition. + * 2. Partition table checksum. + * 3. Header checksum. + * 4. Header on disk. + */ + + entries_per_sec = DEV_BSIZE / hdr->hdr_entsz; + slba = curent / entries_per_sec; + firstent = slba * entries_per_sec; + bcopy(&table[firstent], secbuf, DEV_BSIZE); + slba += hdr->hdr_lba_table; + if (drvwrite(dskp, secbuf, slba, 1)) { + printf("%s: unable to update %s GPT partition table\n", + BOOTPROG, which); + return; + } + hdr->hdr_crc_table = crc32(0, Z_NULL, 0); + hdr->hdr_crc_table = crc32(hdr->hdr_crc_table, table, + hdr->hdr_entries * hdr->hdr_entsz); + hdr->hdr_crc_self = crc32(0, Z_NULL, 0); + hdr->hdr_crc_self = crc32(hdr->hdr_crc_self, hdr, hdr->hdr_size); + bzero(secbuf, DEV_BSIZE); + bcopy(hdr, secbuf, hdr->hdr_size); + if (drvwrite(dskp, secbuf, hdr->hdr_lba_self, 1)) { + printf("%s: unable to update %s GPT header\n", BOOTPROG, which); + return; + } +} + +int +gptfind(const uuid_t *uuid, struct dsk *dskp, int part) +{ + struct gpt_ent *ent; + int firsttry; + + if (part >= 0) { + if (part == 0 || part > gpthdr->hdr_entries) { + printf("%s: invalid partition index\n", BOOTPROG); + return (-1); + } + ent = &gpttable[part - 1]; + if (bcmp(&ent->ent_type, uuid, sizeof (uuid_t)) != 0) { + printf("%s: specified partition is not UFS\n", + BOOTPROG); + return (-1); + } + curent = part - 1; + goto found; + } + + firsttry = (curent == -1); + curent++; + if (curent >= gpthdr->hdr_entries) { + curent = gpthdr->hdr_entries; + return (-1); + } + if (bootonce) { + /* + * First look for partition with both GPT_ENT_ATTR_BOOTME and + * GPT_ENT_ATTR_BOOTONCE flags. + */ + for (; curent < gpthdr->hdr_entries; curent++) { + ent = &gpttable[curent]; + if (bcmp(&ent->ent_type, uuid, sizeof (uuid_t)) != 0) + continue; + if (!(ent->ent_attr & GPT_ENT_ATTR_BOOTME)) + continue; + if (!(ent->ent_attr & GPT_ENT_ATTR_BOOTONCE)) + continue; + /* Ok, found one. */ + goto found; + } + bootonce = 0; + curent = 0; + } + for (; curent < gpthdr->hdr_entries; curent++) { + ent = &gpttable[curent]; + if (bcmp(&ent->ent_type, uuid, sizeof (uuid_t)) != 0) + continue; + if (!(ent->ent_attr & GPT_ENT_ATTR_BOOTME)) + continue; + if (ent->ent_attr & GPT_ENT_ATTR_BOOTONCE) + continue; + /* Ok, found one. */ + goto found; + } + if (firsttry) { + /* + * No partition with BOOTME flag was found, try to boot from + * first UFS partition. + */ + for (curent = 0; curent < gpthdr->hdr_entries; curent++) { + ent = &gpttable[curent]; + if (bcmp(&ent->ent_type, uuid, sizeof (uuid_t)) != 0) + continue; + /* Ok, found one. */ + goto found; + } + } + return (-1); +found: + dskp->part = curent + 1; + ent = &gpttable[curent]; + dskp->start = ent->ent_lba_start; + if (ent->ent_attr & GPT_ENT_ATTR_BOOTONCE) { + /* + * Clear BOOTME, but leave BOOTONCE set before trying to + * boot from this partition. + */ + if (hdr_primary_lba > 0) { + table_primary[curent].ent_attr &= ~GPT_ENT_ATTR_BOOTME; + gptupdate("primary", dskp, &hdr_primary, table_primary); + } + if (hdr_backup_lba > 0) { + table_backup[curent].ent_attr &= ~GPT_ENT_ATTR_BOOTME; + gptupdate("backup", dskp, &hdr_backup, table_backup); + } + } + return (0); +} + +static int +gptread_hdr(const char *which, struct dsk *dskp, struct gpt_hdr *hdr, + uint64_t hdrlba) +{ + uint32_t crc; + + if (drvread(dskp, secbuf, hdrlba, 1)) { + printf("%s: unable to read %s GPT header\n", BOOTPROG, which); + return (-1); + } + bcopy(secbuf, hdr, sizeof (*hdr)); + if (bcmp(hdr->hdr_sig, GPT_HDR_SIG, sizeof (hdr->hdr_sig)) != 0 || + hdr->hdr_lba_self != hdrlba || hdr->hdr_revision < 0x00010000 || + hdr->hdr_entsz < sizeof (struct gpt_ent) || + hdr->hdr_entries > MAXTBLENTS || DEV_BSIZE % hdr->hdr_entsz != 0) { + printf("%s: invalid %s GPT header\n", BOOTPROG, which); + return (-1); + } + crc = hdr->hdr_crc_self; + hdr->hdr_crc_self = crc32(0, Z_NULL, 0); + if (crc32(hdr->hdr_crc_self, hdr, hdr->hdr_size) != crc) { + printf("%s: %s GPT header checksum mismatch\n", BOOTPROG, + which); + return (-1); + } + hdr->hdr_crc_self = crc; + return (0); +} + +void +gptbootfailed(struct dsk *dskp) +{ + + if (!(gpttable[curent].ent_attr & GPT_ENT_ATTR_BOOTONCE)) + return; + + if (hdr_primary_lba > 0) { + table_primary[curent].ent_attr &= ~GPT_ENT_ATTR_BOOTONCE; + table_primary[curent].ent_attr |= GPT_ENT_ATTR_BOOTFAILED; + gptupdate("primary", dskp, &hdr_primary, table_primary); + } + if (hdr_backup_lba > 0) { + table_backup[curent].ent_attr &= ~GPT_ENT_ATTR_BOOTONCE; + table_backup[curent].ent_attr |= GPT_ENT_ATTR_BOOTFAILED; + gptupdate("backup", dskp, &hdr_backup, table_backup); + } +} + +static void +gptbootconv(const char *which, struct dsk *dskp, struct gpt_hdr *hdr, + struct gpt_ent *table) +{ + struct gpt_ent *ent; + daddr_t slba; + int table_updated, sector_updated; + int entries_per_sec, nent, part; + + table_updated = 0; + entries_per_sec = DEV_BSIZE / hdr->hdr_entsz; + for (nent = 0, slba = hdr->hdr_lba_table; + slba < hdr->hdr_lba_table + hdr->hdr_entries / entries_per_sec; + slba++, nent += entries_per_sec) { + sector_updated = 0; + for (part = 0; part < entries_per_sec; part++) { + ent = &table[nent + part]; + if ((ent->ent_attr & (GPT_ENT_ATTR_BOOTME | + GPT_ENT_ATTR_BOOTONCE | + GPT_ENT_ATTR_BOOTFAILED)) != + GPT_ENT_ATTR_BOOTONCE) { + continue; + } + ent->ent_attr &= ~GPT_ENT_ATTR_BOOTONCE; + ent->ent_attr |= GPT_ENT_ATTR_BOOTFAILED; + table_updated = 1; + sector_updated = 1; + } + if (!sector_updated) + continue; + bcopy(&table[nent], secbuf, DEV_BSIZE); + if (drvwrite(dskp, secbuf, slba, 1)) { + printf("%s: unable to update %s GPT partition table\n", + BOOTPROG, which); + } + } + if (!table_updated) + return; + hdr->hdr_crc_table = crc32(0, Z_NULL, 0); + hdr->hdr_crc_table = crc32(hdr->hdr_crc_table, table, + hdr->hdr_entries * hdr->hdr_entsz); + hdr->hdr_crc_self = crc32(0, Z_NULL, 0); + hdr->hdr_crc_self = crc32(hdr->hdr_crc_self, hdr, hdr->hdr_size); + bzero(secbuf, DEV_BSIZE); + bcopy(hdr, secbuf, hdr->hdr_size); + if (drvwrite(dskp, secbuf, hdr->hdr_lba_self, 1)) + printf("%s: unable to update %s GPT header\n", BOOTPROG, which); +} + +static int +gptread_table(const char *which, const uuid_t *uuid, struct dsk *dskp, + struct gpt_hdr *hdr, struct gpt_ent *table) +{ + struct gpt_ent *ent; + int entries_per_sec; + int part, nent; + daddr_t slba; + + if (hdr->hdr_entries == 0) + return (0); + + entries_per_sec = DEV_BSIZE / hdr->hdr_entsz; + slba = hdr->hdr_lba_table; + nent = 0; + for (;;) { + if (drvread(dskp, secbuf, slba, 1)) { + printf("%s: unable to read %s GPT partition table\n", + BOOTPROG, which); + return (-1); + } + ent = (struct gpt_ent *)secbuf; + for (part = 0; part < entries_per_sec; part++, ent++) { + bcopy(ent, &table[nent], sizeof (table[nent])); + if (++nent >= hdr->hdr_entries) + break; + } + if (nent >= hdr->hdr_entries) + break; + slba++; + } + if (crc32(0, table, nent * hdr->hdr_entsz) != hdr->hdr_crc_table) { + printf("%s: %s GPT table checksum mismatch\n", BOOTPROG, which); + return (-1); + } + return (0); +} + +int +gptread(const uuid_t *uuid, struct dsk *dskp, char *buf) +{ + uint64_t altlba; + + /* + * Read and verify both GPT headers: primary and backup. + */ + + secbuf = buf; + hdr_primary_lba = hdr_backup_lba = 0; + curent = -1; + bootonce = 1; + dskp->start = 0; + + if (gptread_hdr("primary", dskp, &hdr_primary, 1) == 0 && + gptread_table("primary", uuid, dskp, &hdr_primary, + table_primary) == 0) { + hdr_primary_lba = hdr_primary.hdr_lba_self; + gpthdr = &hdr_primary; + gpttable = table_primary; + } + + if (hdr_primary_lba > 0) { + /* + * If primary header is valid, we can get backup + * header location from there. + */ + altlba = hdr_primary.hdr_lba_alt; + } else { + altlba = drvsize(dskp); + if (altlba > 0) + altlba--; + } + if (altlba == 0) + printf("%s: unable to locate backup GPT header\n", BOOTPROG); + else if (gptread_hdr("backup", dskp, &hdr_backup, altlba) == 0 && + gptread_table("backup", uuid, dskp, &hdr_backup, + table_backup) == 0) { + hdr_backup_lba = hdr_backup.hdr_lba_self; + if (hdr_primary_lba == 0) { + gpthdr = &hdr_backup; + gpttable = table_backup; + printf("%s: using backup GPT\n", BOOTPROG); + } + } + + /* + * Convert all BOOTONCE without BOOTME flags into BOOTFAILED. + * BOOTONCE without BOOTME means that we tried to boot from it, + * but failed after leaving gptboot and machine was rebooted. + * We don't want to leave partitions marked as BOOTONCE only, + * because when we boot successfully start-up scripts should + * find at most one partition with only BOOTONCE flag and this + * will mean that we booted from that partition. + */ + if (hdr_primary_lba != 0) + gptbootconv("primary", dskp, &hdr_primary, table_primary); + if (hdr_backup_lba != 0) + gptbootconv("backup", dskp, &hdr_backup, table_backup); + + if (hdr_primary_lba == 0 && hdr_backup_lba == 0) + return (-1); + return (0); +} diff --git a/usr/src/boot/common/gpt.h b/usr/src/boot/common/gpt.h new file mode 100644 index 0000000000..afbc3a7abc --- /dev/null +++ b/usr/src/boot/common/gpt.h @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2010 Pawel Jakub Dawidek <pjd@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 AUTHORS 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 AUTHORS 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. + */ + +#ifndef _GPT_H_ +#define _GPT_H_ + +#include <uuid.h> +#include <drv.h> + +int gptread(const uuid_t *uuid, struct dsk *dskp, char *buf); +int gptfind(const uuid_t *uuid, struct dsk *dskp, int part); +void gptbootfailed(struct dsk *dskp); + +#endif /* !_GPT_H_ */ diff --git a/usr/src/boot/common/help.common b/usr/src/boot/common/help.common new file mode 100644 index 0000000000..dca3b09ab9 --- /dev/null +++ b/usr/src/boot/common/help.common @@ -0,0 +1,371 @@ +################################################################################ +# Thelp DDisplay command help + + help [topic [subtopic]] + help index + + The help command displays help on commands and their usage. + + In command help, a term enclosed with <...> indicates a value as + described by the term. A term enclosed with [...] is optional, + and may not be required by all forms of the command. + + Some commands may not be available. Use the '?' command to list + most available commands. + +################################################################################ +# T? DList available commands + + ? + + Lists all available commands. + +################################################################################ +# Tautoboot DBoot after a delay + + autoboot [<delay> [<prompt>]] + + Displays <prompt> or a default prompt, and counts down <delay> seconds + before attempting to boot. If <delay> is not specified, the default + value is 10. + +################################################################################ +# Tbeadm DList or switch Boot Environment + + beadm activate beName [<device>] + beadm list [<device>] + + beadm activate unloads the currently loaded configuration and modules, + sets currdev to <device> and loads configuration from new device. + Use lsdev to get available device names. + +################################################################################ +# Tboot DBoot immediately + + boot [<kernelname>] [-<arg> ...] + + Boot the system. If arguments are specified, they are added to the + arguments for the kernel. If <kernelname> is specified, and a kernel + has not already been loaded, it will be booted instead of the default + kernel. + +################################################################################ +# Tbcachestat DGet disk block cache stats + + bcachestat + + Displays statistics about disk cache usage. For debugging only. + +################################################################################ +# Tconsole DOutput information about console devices + + console + + Display the currently active console device(s) and show + information about available console devices. + +################################################################################ +# Tchain DChain load disk block + + chain disk: + + chain will read stage1 (MBR or VBR) boot block from specified device + to address 0000:7C00 and attempts to run it. Use lsdev to get available + device names. Disk name must end with colon. + +################################################################################ +# Techo DEcho arguments + + echo [-n] [<message>] + + Emits <message>, with no trailing newline if -n is specified. This is + most useful in conjunction with scripts and the '@' line prefix. + + Variables are substituted by prefixing them with $, eg. + + echo Current device is $currdev + + will print the current device. + +################################################################################ +# Tframebuffer DManage framebuffer setup + + framebuffer on | off | get | list [depth] | set <display or mode number> + + Switch framebuffer mode on or off, get current mode, list available + modes or set mode by using either display resolution or framebuffer + mode number. If the system does not provide display resolution via + EDID, the default resolution will be set to 800x600. If depth is not + specified, the best depth is used. + +################################################################################ +# Tload DLoad a kernel or module + + load [-t <type>] <filename> [arguments] + + Loads the module contained in <filename> into memory. If no other + modules are loaded, <filename> must be a kernel or the command will + fail. + + If -t is specified, the module is loaded as raw data of <type>, for + later use by the kernel or other modules. <type> may be any string. + + Optional arguments will be set as module arguments. + +################################################################################ +# Tls DList files + + ls [-l] [<path>] + + Displays a listing of files in the directory <path>, or the root + directory of the current device if <path> is not specified. + + The -l argument displays file sizes as well; the process of obtaining + file sizes on some media may be very slow. + +################################################################################ +# Tlsdev DList devices + + lsdev [-v] + + List all of the devices from which it may be possible to load modules. + If -v is specified, print more details. + +################################################################################ +# Tlsmod DList modules + + lsmod [-v] + + List loaded modules. If [-v] is specified, print more details. + +################################################################################ +# Tmap-vdisk DMap virtual disk + + map-vdisk filename + + Map file as virtual disk. + +################################################################################ +# Tmore DPage files + + more <filename> [<filename> ...] + + Show contents of text files. When displaying the contents of more, + than one file, if the user elects to quit displaying a file, the + remaining files will not be shown. + +################################################################################ +# Tpnpscan DScan for PnP devices + + pnpscan [-v] + + Scan for Plug-and-Play devices. This command is normally automatically + run as part of the boot process, in order to dynamically load modules + required for system operation. + + If the -v argument is specified, details on the devices found will + be printed. + +################################################################################ +# Tset DSet a variable + + set <variable name> + set <variable name>=<value> + + The set command is used to set variables. + +################################################################################ +# Tsetprop DSet a variable + + setprop <variable name> <value> + + The setprop command is used to set variables. + +################################################################################ +# Tset Sautoboot_delay DSet the default autoboot delay + + set autoboot_delay=<value> + + Sets the default delay for the autoboot command to <value> seconds. + Set value to -1 if you don't want to allow user to interrupt autoboot + process and escape to the loader prompt. + +################################################################################ +# Tset Sbootfile DSet the default boot file set + + set bootfile=<filename>[;<filename>...] + + Sets the default set of kernel boot filename(s). It may be overridden + by setting the bootfile variable to a semicolon-separated list of + filenames, each of which will be searched for in the module_path + directories. The default bootfile set is "unix". + +################################################################################ +# Tset Sboot_ask DPrompt for configuration information + + set boot_ask + + Instructs the kernel to prompt the user for the configuration + information when the kernel is booted. + +################################################################################ +# Tset Sboot_drop_into_kmdb DDrop into the kernel debugger (kmdb) + + set boot_drop_into_kmdb + + Instructs the kernel to start in the kmdb debugger, rather than + proceeding to initialize when booted. Can only be used when boot_kmdb + is set. + +################################################################################ +# Tset Sboot_kmdb DStart the kernel debugger (kmdb) + + set boot_kmdb + + Instructs the kernel to start the kmdb debugger and then continue + with normal boot. + +################################################################################ +# Tset Sboot_reconfigure DInitaiate reconfiguration boot + + set boot_reconfigure + + The system will probe all attached hardware devices and configure + the logical namespace in /dev. + +################################################################################ +# Tset Sboot_multicons DUse multiple consoles + + set boot_multicons + + Enables multiple console support in the kernel early on boot. + In a running system, console configuration can be manipulated + by the conscontrol(8) utility. + +################################################################################ +# Tset Sboot_single DBoot into the single user mode + + set boot_single + + Boots only to init level 's'. + +################################################################################ +# Tset Sboot_verbose DBoot with verbose messages enabled + + set boot_verbose + + Without this setting, the messages are only logged in the system log. + +################################################################################ +# Tset Sconsole DSet the current console + + set console[=<value>[,<value>]] + + Sets the current console. If <value> is omitted, a list of valid + consoles will be displayed. + +################################################################################ +# Tset Scurrdev DSet the current device + + set currdev=<device> + + Selects the default device. See lsdev for available devices. + +################################################################################ +# Tset Smodule_path DSet the module search path + + set module_path=<path>[;<path>...] + + Sets the list of directories which will be searched in for modules + named in a load command or implicitly required by a dependency. The + default module_path is "/boot/modules" with the kernel directory + prepended. + +################################################################################ +# Tset Sprompt DSet the command prompt + + set prompt=<value> + + The command prompt is displayed when the loader is waiting for input. + Variable substitution is performed on the prompt. The default + prompt can be set with: + + set prompt=\${interpret} + +################################################################################ +# Tset Sscreen-font DSet the framebuffer font + + Without the value, will list the currently available list + of the fonts. + +################################################################################ +# Tset Srootdev DSet the root filesystem + + set rootdev=<path> + + By default the value of $currdev is used to set the root filesystem + when the kernel is booted. This can be overridden by setting + $rootdev explicitly. + +################################################################################ +# Tshow DShow the values of variables + + show [<variable>] + + Displays the value of <variable>, or all variables if not specified. + +################################################################################ +# Tsifting DSearch for words containing a substring + + sifting <substring> + + Displays words in the search order list containing the provided + <substring>. + +################################################################################ +# Tinclude DRead commands from a script file + + include <filename> [<filename> ...] + + The entire contents of <filename> are read into memory before executing + commands, so it is safe to source a file from removable media. + +################################################################################ +# Tread DRead input from the terminal + + read [-t <value>] [-p <prompt>] [<variable name>] + + The read command reads a line of input from the terminal. If the + -t argument is specified, it will return nothing if no input has been + received after <value> seconds. (Any keypress will cancel the + timeout). + + If -p is specified, <prompt> is printed before reading input. No + newline is emitted after the prompt. + + If a variable name is supplied, the variable is set to the value read, + less any terminating newline. + +################################################################################ +# Tunload DRemove all modules from memory + + unload + + This command removes any kernel and all loaded modules from memory. + +################################################################################ +# Tunmap-vdisk DUnmap virtual disk + + unmap-vdisk diskname + + Delete virtual disk mapping. + +################################################################################ +# Tunset DUnset a variable + + unset <variable name> + + If allowed, the named variable's value is discarded and the variable + is removed. + +################################################################################ diff --git a/usr/src/boot/common/install.c b/usr/src/boot/common/install.c new file mode 100644 index 0000000000..36e7c6484d --- /dev/null +++ b/usr/src/boot/common/install.c @@ -0,0 +1,339 @@ +/*- + * Copyright (c) 2008-2014, Juniper Networks, 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 ``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 <sys/param.h> +#include <sys/socket.h> +#include <net/if.h> +#include <netinet/in.h> +#include <netinet/in_systm.h> + +#include <stand.h> +#include <net.h> +#include <string.h> + +#include "bootstrap.h" + +extern struct in_addr rootip; +extern struct in_addr servip; + +extern int pkgfs_init(const char *, struct fs_ops *); +extern void pkgfs_cleanup(void); + +COMMAND_SET(install, "install", "install software package", command_install); + +static char *inst_kernel; +static char **inst_modules; +static char *inst_rootfs; + +static int +setpath(char **what, char *val) +{ + char *path; + size_t len; + int rel; + + len = strlen(val) + 1; + rel = (val[0] != '/') ? 1 : 0; + path = malloc(len + rel); + if (path == NULL) + return (ENOMEM); + path[0] = '/'; + strcpy(path + rel, val); + + *what = path; + return (0); +} + +static int +setmultipath(char ***what, char *val) +{ + char *s, *v; + int count, error, idx; + + count = 0; + v = val; + do { + count++; + s = strchr(v, ','); + v = (s == NULL) ? NULL : s + 1; + } while (v != NULL); + + *what = calloc(count + 1, sizeof(char *)); + if (*what == NULL) + return (ENOMEM); + + for (idx = 0; idx < count; idx++) { + s = strchr(val, ','); + if (s != NULL) + *s++ = '\0'; + error = setpath(*what + idx, val); + if (error) + return (error); + val = s; + } + + return (0); +} + +static int +read_metatags(int fd) +{ + char buf[1024]; + char *p, *tag, *val; + ssize_t fsize; + int error; + + fsize = read(fd, buf, sizeof(buf)); + if (fsize == -1) + return (errno); + + /* + * Assume that if we read a whole buffer worth of data, we + * haven't read the entire file. In other words, the buffer + * size must always be larger than the file size. That way + * we can append a '\0' and use standard string operations. + * Return an error if this is not possible. + */ + if (fsize == sizeof(buf)) + return (ENOMEM); + + buf[fsize] = '\0'; + error = 0; + tag = buf; + while (!error && *tag != '\0') { + val = strchr(tag, '='); + if (val == NULL) { + error = EINVAL; + break; + } + *val++ = '\0'; + p = strchr(val, '\n'); + if (p == NULL) { + error = EINVAL; + break; + } + *p++ = '\0'; + + if (strcmp(tag, "KERNEL") == 0) + error = setpath(&inst_kernel, val); + else if (strcmp(tag, "MODULES") == 0) + error = setmultipath(&inst_modules, val); + else if (strcmp(tag, "ROOTFS") == 0) + error = setpath(&inst_rootfs, val); + + tag = p; + } + + return (error); +} + +static void +cleanup(void) +{ + u_int i; + + if (inst_kernel != NULL) { + free(inst_kernel); + inst_kernel = NULL; + } + if (inst_modules != NULL) { + i = 0; + while (inst_modules[i] != NULL) + free(inst_modules[i++]); + free(inst_modules); + inst_modules = NULL; + } + if (inst_rootfs != NULL) { + free(inst_rootfs); + inst_rootfs = NULL; + } + pkgfs_cleanup(); +} + +/* + * usage: install URL + * where: URL = (tftp|file)://[host]/<package> + */ +static int +install(char *pkgname) +{ + static char buf[256]; + struct fs_ops *proto; + struct preloaded_file *fp; + char *s, *currdev; + const char *devname; + int error, fd, i, local; + + s = strstr(pkgname, "://"); + if (s == NULL) + goto invalid_url; + + i = s - pkgname; + if (i == 4 && !strncasecmp(pkgname, "tftp", i)) { + devname = "net0"; + proto = &tftp_fsops; + local = 0; + } else if (i == 4 && !strncasecmp(pkgname, "file", i)) { + currdev = getenv("currdev"); + if (currdev != NULL && strcmp(currdev, "pxe0:") == 0) { + devname = "pxe0"; + proto = NULL; + } else { + devname = "disk1"; + proto = &dosfs_fsops; + } + local = 1; + } else + goto invalid_url; + + s += 3; + if (*s == '\0') + goto invalid_url; + + if (*s != '/' ) { + if (local) + goto invalid_url; + + pkgname = strchr(s, '/'); + if (pkgname == NULL) + goto invalid_url; + + *pkgname = '\0'; + servip.s_addr = inet_addr(s); + if (servip.s_addr == htonl(INADDR_NONE)) + goto invalid_url; + + setenv("serverip", inet_ntoa(servip), 1); + + *pkgname = '/'; + } else + pkgname = s; + + if (strlen(devname) + strlen(pkgname) + 2 > sizeof(buf)) { + command_errmsg = "package name too long"; + return (CMD_ERROR); + } + sprintf(buf, "%s:%s", devname, pkgname); + setenv("install_package", buf, 1); + + error = pkgfs_init(buf, proto); + if (error) { + command_errmsg = "cannot open package"; + goto fail; + } + + /* + * Point of no return: unload anything that may have been + * loaded and prune the environment from harmful variables. + */ + unload(); + unsetenv("vfs.root.mountfrom"); + + /* + * read the metatags file. + */ + fd = open("/metatags", O_RDONLY); + if (fd != -1) { + error = read_metatags(fd); + close(fd); + if (error) { + command_errmsg = "cannot load metatags"; + goto fail; + } + } + + s = (inst_kernel == NULL) ? "/kernel" : inst_kernel; + error = mod_loadkld(s, 0, NULL); + if (error) { + command_errmsg = "cannot load kernel from package"; + goto fail; + } + + i = 0; + while (inst_modules != NULL && inst_modules[i] != NULL) { + error = mod_loadkld(inst_modules[i], 0, NULL); + if (error) { + command_errmsg = "cannot load module(s) from package"; + goto fail; + } + i++; + } + + s = (inst_rootfs == NULL) ? "/install.iso" : inst_rootfs; + if (file_loadraw(s, "mfs_root") == NULL) { + error = errno; + command_errmsg = "cannot load root file system"; + goto fail; + } + + cleanup(); + + fp = file_findfile(NULL, NULL); + if (fp != NULL) + file_formats[fp->f_loader]->l_exec(fp); + error = CMD_ERROR; + command_errmsg = "unable to start installation"; + + fail: + sprintf(buf, "%s (error %d)", command_errmsg, error); + cleanup(); + unload(); + exclusive_file_system = NULL; + command_errmsg = buf; /* buf is static. */ + return (CMD_ERROR); + + invalid_url: + command_errmsg = "invalid URL"; + return (CMD_ERROR); +} + +static int +command_install(int argc, char *argv[]) +{ + int argidx; + + unsetenv("install_format"); + + argidx = 1; + while (1) { + if (argc == argidx) { + command_errmsg = + "usage: install [--format] <URL>"; + return (CMD_ERROR); + } + if (!strcmp(argv[argidx], "--format")) { + setenv("install_format", "yes", 1); + argidx++; + continue; + } + break; + } + + return (install(argv[argidx])); +} diff --git a/usr/src/boot/common/interp.c b/usr/src/boot/common/interp.c new file mode 100644 index 0000000000..f69d0460e5 --- /dev/null +++ b/usr/src/boot/common/interp.c @@ -0,0 +1,321 @@ +/* + * Copyright (c) 1998 Michael Smith <msmith@freebsd.org> + * Copyright 2019 OmniOS Community Edition (OmniOSce) Association. + * 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> + +/* + * Simple commandline interpreter, toplevel and misc. + * + * XXX may be obsoleted by BootFORTH or some other, better, interpreter. + */ + +#include <stand.h> +#include <string.h> +#include "bootstrap.h" + +#ifdef BOOT_FORTH +#include "ficl.h" +#define RETURN(x) ficlStackPushInteger(ficlVmGetDataStack(bf_vm),!x); return(x) + +extern ficlVm *bf_vm; +#else +#define RETURN(x) return(x) +#endif + +#include "linenoise/linenoise.h" + +#define MAXARGS 20 /* maximum number of arguments allowed */ + +static char *prompt(void); + +#ifndef BOOT_FORTH +static int perform(int argc, char *argv[]); + +/* + * Perform the command + */ +int +perform(int argc, char *argv[]) +{ + int result; + struct bootblk_command **cmdp; + bootblk_cmd_t *cmd; + + if (argc < 1) + return(CMD_OK); + + /* set return defaults; a successful command will override these */ + command_errmsg = command_errbuf; + strcpy(command_errbuf, "no error message"); + cmd = NULL; + result = CMD_ERROR; + + /* search the command set for the command */ + SET_FOREACH(cmdp, Xcommand_set) { + if (((*cmdp)->c_name != NULL) && !strcmp(argv[0], (*cmdp)->c_name)) + cmd = (*cmdp)->c_fn; + } + if (cmd != NULL) { + result = (cmd)(argc, argv); + } else { + command_errmsg = "unknown command"; + } + RETURN(result); +} +#endif /* ! BOOT_FORTH */ + +/* + * Interactive mode + */ +void +interact(const char *rc) +{ + char *input = NULL; + + bf_init((rc) ? "" : NULL); + + if (rc == NULL) { + /* Read our default configuration. */ + include("/boot/loader.rc"); + } else if (*rc != '\0') + include(rc); + + printf("\n"); + + /* + * Before interacting, we might want to autoboot. + */ + autoboot_maybe(); + + /* + * Not autobooting, go manual + */ + printf("\nType '?' for a list of commands, 'help' for more detailed help.\n"); + if (getenv("prompt") == NULL) + setenv("prompt", "${interpret}", 1); + if (getenv("interpret") == NULL) + setenv("interpret", "ok", 1); + + while ((input = linenoise(prompt())) != NULL) { + bf_vm->sourceId.i = 0; + bf_run(input); + linenoiseHistoryAdd(input); + free(input); + } +} + +/* + * Read commands from a file, then execute them. + * + * We store the commands in memory and close the source file so that the media + * holding it can safely go away while we are executing. + * + * Commands may be prefixed with '@' (so they aren't displayed) or '-' (so + * that the script won't stop if they fail). + */ +COMMAND_SET(include, "include", "read commands from a file", command_include); + +static int +command_include(int argc, char *argv[]) +{ + int i; + int res; + char **argvbuf; + + /* + * Since argv is static, we need to save it here. + */ + argvbuf = (char**) calloc((u_int)argc, sizeof(char*)); + for (i = 0; i < argc; i++) + argvbuf[i] = strdup(argv[i]); + + res=CMD_OK; + for (i = 1; (i < argc) && (res == CMD_OK); i++) + res = include(argvbuf[i]); + + for (i = 0; i < argc; i++) + free(argvbuf[i]); + free(argvbuf); + + return(res); +} + +COMMAND_SET(sifting, "sifting", "find words", command_sifting); + +static int +command_sifting(int argc, char *argv[]) +{ + if (argc != 2) { + command_errmsg = "wrong number of arguments"; + return (CMD_ERROR); + } + ficlPrimitiveSiftingImpl(bf_vm, argv[1]); + return (CMD_OK); +} + +/* + * Header prepended to each line. The text immediately follows the header. + * We try to make this short in order to save memory -- the loader has + * limited memory available, and some of the forth files are very long. + */ +struct includeline +{ + struct includeline *next; + int line; + char text[0]; +}; + +/* + * The PXE TFTP service allows opening exactly one connection at the time, + * so we need to read included file into memory, then process line by line + * as it may contain embedded include commands. + */ +int +include(const char *filename) +{ + struct includeline *script, *se, *sp; + int res = CMD_OK; + int prevsrcid, fd, line; + char *cp, input[256]; /* big enough? */ + + if (((fd = open(filename, O_RDONLY)) == -1)) { + snprintf(command_errbuf, sizeof (command_errbuf), "can't open '%s': %s", + filename, strerror(errno)); + return(CMD_ERROR); + } + /* + * Read the script into memory. + */ + script = se = NULL; + line = 0; + + while (fgetstr(input, sizeof(input), fd) >= 0) { + line++; + cp = input; + /* Allocate script line structure and copy line, flags */ + if (*cp == '\0') + continue; /* ignore empty line, save memory */ + if (cp[0] == '\\' && cp[1] == ' ') + continue; /* ignore comment */ + + sp = malloc(sizeof(struct includeline) + strlen(cp) + 1); + /* On malloc failure (it happens!), free as much as possible and exit */ + if (sp == NULL) { + while (script != NULL) { + se = script; + script = script->next; + free(se); + } + snprintf(command_errbuf, sizeof (command_errbuf), + "file '%s' line %d: memory allocation failure - aborting", + filename, line); + close(fd); + return (CMD_ERROR); + } + strcpy(sp->text, cp); + sp->line = line; + sp->next = NULL; + + if (script == NULL) { + script = sp; + } else { + se->next = sp; + } + se = sp; + } + close(fd); + + /* + * Execute the script + */ + + prevsrcid = bf_vm->sourceId.i; + bf_vm->sourceId.i = fd+1; /* 0 is user input device */ + + res = CMD_OK; + + for (sp = script; sp != NULL; sp = sp->next) { + res = bf_run(sp->text); + if (res != FICL_VM_STATUS_OUT_OF_TEXT) { + snprintf(command_errbuf, sizeof (command_errbuf), + "Error while including %s, in the line %d:\n%s", + filename, sp->line, sp->text); + res = CMD_ERROR; + break; + } else + res = CMD_OK; + } + + bf_vm->sourceId.i = -1; + (void) bf_run(""); + bf_vm->sourceId.i = prevsrcid; + + while(script != NULL) { + se = script; + script = script->next; + free(se); + } + + return(res); +} + +/* + * Emit the current prompt; use the same syntax as the parser + * for embedding environment variables. + */ +static char * +prompt(void) +{ + static char promptbuf[20]; /* probably too large, but well... */ + char *pr, *p, *cp, *ev; + int n = 0; + + if ((cp = getenv("prompt")) == NULL) + cp = (char *)(uintptr_t)">"; + pr = p = strdup(cp); + + while (*p != 0) { + if ((*p == '$') && (*(p+1) == '{')) { + for (cp = p + 2; (*cp != 0) && (*cp != '}'); cp++) + ; + *cp = 0; + ev = getenv(p + 2); + + if (ev != NULL) + n = sprintf(promptbuf+n, "%s", ev); + p = cp + 1; + continue; + } + promptbuf[n++] = *p; + p++; + } + if (promptbuf[n - 1] != ' ') + promptbuf[n++] = ' '; + promptbuf[n] = '\0'; + free(pr); + return (promptbuf); +} diff --git a/usr/src/boot/common/interp_backslash.c b/usr/src/boot/common/interp_backslash.c new file mode 100644 index 0000000000..c8d59fa070 --- /dev/null +++ b/usr/src/boot/common/interp_backslash.c @@ -0,0 +1,171 @@ +/* + * 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. + * + * Jordan K. Hubbard + * 29 August 1998 + * + * Routine for doing backslash elimination. + */ + +#include <sys/cdefs.h> + +#include <stand.h> +#include <string.h> +#include "bootstrap.h" + +#define DIGIT(x) \ + (isdigit(x) ? (x) - '0' : islower(x) ? (x) + 10 - 'a' : (x) + 10 - 'A') + +/* + * backslash: Return malloc'd copy of str with all standard "backslash + * processing" done on it. Original can be free'd if desired. + */ +char * +backslash(char *str) +{ + /* + * Remove backslashes from the strings. Turn \040 etc. into a single + * character (we allow eight bit values). Currently NUL is not + * allowed. + * + * Turn "\n" and "\t" into '\n' and '\t' characters. Etc. + * + */ + char *new_str; + int seenbs = 0; + int i = 0; + + if ((new_str = strdup(str)) == NULL) + return (NULL); + + while (*str) { + if (seenbs) { + seenbs = 0; + switch (*str) { + case '\\': + new_str[i++] = '\\'; + str++; + break; + + /* preserve backslashed quotes, dollar signs */ + case '\'': + case '"': + case '$': + new_str[i++] = '\\'; + new_str[i++] = *str++; + break; + + case 'b': + new_str[i++] = '\b'; + str++; + break; + + case 'f': + new_str[i++] = '\f'; + str++; + break; + + case 'r': + new_str[i++] = '\r'; + str++; + break; + + case 'n': + new_str[i++] = '\n'; + str++; + break; + + case 's': + new_str[i++] = ' '; + str++; + break; + + case 't': + new_str[i++] = '\t'; + str++; + break; + + case 'v': + new_str[i++] = '\13'; + str++; + break; + + case 'z': + str++; + break; + + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': { + char val; + + /* Three digit octal constant? */ + if (*str >= '0' && *str <= '3' && + *(str + 1) >= '0' && *(str + 1) <= '7' && + *(str + 2) >= '0' && *(str + 2) <= '7') { + + val = (DIGIT(*str) << 6) + + (DIGIT(*(str + 1)) << 3) + + DIGIT(*(str + 2)); + + /* + * Allow null value if user really + * wants to shoot at feet, but beware! + */ + new_str[i++] = val; + str += 3; + break; + } + + /* + * One or two digit hex constant? + * If two are there they will both be taken. + * Use \z to split them up if this is not + * wanted. + */ + if (*str == '0' && + (*(str + 1) == 'x' || *(str + 1) == 'X') && + isxdigit(*(str + 2))) { + val = DIGIT(*(str + 2)); + if (isxdigit(*(str + 3))) { + val = (val << 4) + + DIGIT(*(str + 3)); + str += 4; + } else + str += 3; + /* Yep, allow null value here too */ + new_str[i++] = val; + break; + } + } + break; + + default: + new_str[i++] = *str++; + break; + } + } else { + if (*str == '\\') { + seenbs = 1; + str++; + } else + new_str[i++] = *str++; + } + } + + if (seenbs) { + /* + * The final character was a '\'. + * Put it in as a single backslash. + */ + new_str[i++] = '\\'; + } + new_str[i] = '\0'; + return (new_str); +} diff --git a/usr/src/boot/common/interp_forth.c b/usr/src/boot/common/interp_forth.c new file mode 100644 index 0000000000..1f3a92bcb4 --- /dev/null +++ b/usr/src/boot/common/interp_forth.c @@ -0,0 +1,374 @@ +/* + * 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> + +#include <sys/param.h> /* to pick up __FreeBSD_version */ +#include <string.h> +#include <stand.h> +#include "bootstrap.h" +#include "ficl.h" + +extern unsigned bootprog_rev; + +/* #define BFORTH_DEBUG */ + +#ifdef BFORTH_DEBUG +#define DPRINTF(fmt, args...) printf("%s: " fmt "\n", __func__, ## args) +#else +#define DPRINTF(fmt, args...) ((void)0) +#endif + +/* + * Eventually, all builtin commands throw codes must be defined + * elsewhere, possibly bootstrap.h. For now, just this code, used + * just in this file, it is getting defined. + */ +#define BF_PARSE 100 + +/* + * FreeBSD loader default dictionary cells + */ +#ifndef BF_DICTSIZE +#define BF_DICTSIZE 30000 +#endif + +/* + * BootForth Interface to Ficl Forth interpreter. + */ + +ficlSystemInformation *fsi; +ficlSystem *bf_sys; +ficlVm *bf_vm; + +/* + * Shim for taking commands from BF and passing them out to 'standard' + * argv/argc command functions. + */ +static void +bf_command(ficlVm *vm) +{ + char *name, *line, *tail, *cp; + size_t len; + struct bootblk_command **cmdp; + bootblk_cmd_t *cmd; + int nstrings, i; + int argc, result; + char **argv; + + /* Get the name of the current word */ + name = vm->runningWord->name; + + /* Find our command structure */ + cmd = NULL; + SET_FOREACH(cmdp, Xcommand_set) { + if (((*cmdp)->c_name != NULL) && + strcmp(name, (*cmdp)->c_name) == 0) + cmd = (*cmdp)->c_fn; + } + if (cmd == NULL) + panic("callout for unknown command '%s'", name); + + /* Check whether we have been compiled or are being interpreted */ + if (ficlStackPopInteger(ficlVmGetDataStack(vm))) { + /* + * Get parameters from stack, in the format: + * an un ... a2 u2 a1 u1 n -- + * Where n is the number of strings, a/u are pairs of + * address/size for strings, and they will be concatenated + * in LIFO order. + */ + nstrings = ficlStackPopInteger(ficlVmGetDataStack(vm)); + for (i = 0, len = 0; i < nstrings; i++) { + ficlStack *stack = ficlVmGetDataStack(vm); + len += ficlStackFetch(stack, i * 2).i + 1; + } + line = malloc(strlen(name) + len + 1); + strcpy(line, name); + + if (nstrings) + for (i = 0; i < nstrings; i++) { + ficlStack *stack = ficlVmGetDataStack(vm); + + len = ficlStackPopInteger(stack); + cp = ficlStackPopPointer(stack); + strcat(line, " "); + strncat(line, cp, len); + } + } else { + /* Get remainder of invocation */ + tail = ficlVmGetInBuf(vm); + + len = 0; + cp = tail; + for (; cp != vm->tib.end && *cp != 0 && *cp != '\n'; cp++) + len++; + + line = malloc(strlen(name) + len + 2); + strcpy(line, name); + if (len > 0) { + strcat(line, " "); + strncat(line, tail, len); + ficlVmUpdateTib(vm, tail + len); + } + } + DPRINTF("cmd '%s'", line); + + command_errmsg = command_errbuf; + command_errbuf[0] = 0; + if (!parse(&argc, &argv, line)) { + result = (cmd)(argc, argv); + free(argv); + } else { + result = BF_PARSE; + } + + switch (result) { + case CMD_CRIT: + printf("%s\n", command_errmsg); + command_errmsg = NULL; + break; + case CMD_FATAL: + panic("%s", command_errmsg); + } + + free(line); + /* + * If there was error during nested ficlExec(), we may no longer have + * valid environment to return. Throw all exceptions from here. + */ + if (result != CMD_OK) + ficlVmThrow(vm, result); + + /* This is going to be thrown!!! */ + ficlStackPushInteger(ficlVmGetDataStack(vm), result); +} + +/* + * Replace a word definition (a builtin command) with another + * one that: + * + * - Throw error results instead of returning them on the stack + * - Pass a flag indicating whether the word was compiled or is + * being interpreted. + * + * There is one major problem with builtins that cannot be overcome + * in anyway, except by outlawing it. We want builtins to behave + * differently depending on whether they have been compiled or they + * are being interpreted. Notice that this is *not* the interpreter's + * current state. For example: + * + * : example ls ; immediate + * : problem example ; \ "ls" gets executed while compiling + * example \ "ls" gets executed while interpreting + * + * Notice that, though the current state is different in the two + * invocations of "example", in both cases "ls" has been + * *compiled in*, which is what we really want. + * + * The problem arises when you tick the builtin. For example: + * + * : example-1 ['] ls postpone literal ; immediate + * : example-2 example-1 execute ; immediate + * : problem example-2 ; + * example-2 + * + * We have no way, when we get EXECUTEd, of knowing what our behavior + * should be. Thus, our only alternative is to "outlaw" this. See RFI + * 0007, and ANS Forth Standard's appendix D, item 6.7 for a related + * problem, concerning compile semantics. + * + * The problem is compounded by the fact that "' builtin CATCH" is valid + * and desirable. The only solution is to create an intermediary word. + * For example: + * + * : my-ls ls ; + * : example ['] my-ls catch ; + * + * So, with the below implementation, here is a summary of the behavior + * of builtins: + * + * ls -l \ "interpret" behavior, ie, + * \ takes parameters from TIB + * : ex-1 s" -l" 1 ls ; \ "compile" behavior, ie, + * \ takes parameters from the stack + * : ex-2 ['] ls catch ; immediate \ undefined behavior + * : ex-3 ['] ls catch ; \ undefined behavior + * ex-2 ex-3 \ "interpret" behavior, + * \ catch works + * : ex-4 ex-2 ; \ "compile" behavior, + * \ catch does not work + * : ex-5 ex-3 ; immediate \ same as ex-2 + * : ex-6 ex-3 ; \ same as ex-3 + * : ex-7 ['] ex-1 catch ; \ "compile" behavior, + * \ catch works + * : ex-8 postpone ls ; immediate \ same as ex-2 + * : ex-9 postpone ls ; \ same as ex-3 + * + * As the definition below is particularly tricky, and it's side effects + * must be well understood by those playing with it, I'll be heavy on + * the comments. + * + * (if you edit this definition, pay attention to trailing spaces after + * each word -- I warned you! :-) ) + */ +#define BUILTIN_CONSTRUCTOR \ +": builtin: " \ + ">in @ " /* save the tib index pointer */ \ + "' " /* get next word's xt */ \ + "swap >in ! " /* point again to next word */ \ + "create " /* create a new definition of the next word */ \ + ", " /* save previous definition's xt */ \ + "immediate " /* make the new definition an immediate word */ \ + \ + "does> " /* Now, the *new* definition will: */ \ + "state @ if " /* if in compiling state: */ \ + "1 postpone literal " /* pass 1 flag to indicate compile */ \ + "@ compile, " /* compile in previous definition */ \ + "postpone throw " /* throw stack-returned result */ \ + "else " /* if in interpreting state: */ \ + "0 swap " /* pass 0 flag to indicate interpret */ \ + "@ execute " /* call previous definition */ \ + "throw " /* throw stack-returned result */ \ + "then ; " + +/* + * Initialise the Forth interpreter, create all our commands as words. + */ +void +bf_init(char *rc) +{ + struct bootblk_command **cmdp; + char create_buf[41]; /* 31 characters-long builtins */ + int fd, rv; + ficlDictionary *dict; + ficlDictionary *env; + + fsi = malloc(sizeof (ficlSystemInformation)); + ficlSystemInformationInitialize(fsi); + fsi->dictionarySize = BF_DICTSIZE; + + bf_sys = ficlSystemCreate(fsi); + bf_vm = ficlSystemCreateVm(bf_sys); + + /* Put all private definitions in a "builtins" vocabulary */ + rv = ficlVmEvaluate(bf_vm, + "vocabulary builtins also builtins definitions"); + if (rv != FICL_VM_STATUS_OUT_OF_TEXT) { + panic("error interpreting forth: %d", rv); + } + + /* Builtin constructor word */ + rv = ficlVmEvaluate(bf_vm, BUILTIN_CONSTRUCTOR); + if (rv != FICL_VM_STATUS_OUT_OF_TEXT) { + panic("error interpreting forth: %d", rv); + } + + /* make all commands appear as Forth words */ + dict = ficlSystemGetDictionary(bf_sys); + SET_FOREACH(cmdp, Xcommand_set) { + ficlDictionaryAppendPrimitive(dict, (char *)(*cmdp)->c_name, + bf_command, FICL_WORD_DEFAULT); + rv = ficlVmEvaluate(bf_vm, "forth definitions builtins"); + if (rv != FICL_VM_STATUS_OUT_OF_TEXT) { + panic("error interpreting forth: %d", rv); + } + sprintf(create_buf, "builtin: %s", (*cmdp)->c_name); + rv = ficlVmEvaluate(bf_vm, create_buf); + if (rv != FICL_VM_STATUS_OUT_OF_TEXT) { + panic("error interpreting forth: %d", rv); + } + rv = ficlVmEvaluate(bf_vm, "builtins definitions"); + if (rv != FICL_VM_STATUS_OUT_OF_TEXT) { + panic("error interpreting forth: %d", rv); + } + } + rv = ficlVmEvaluate(bf_vm, "only forth definitions"); + if (rv != FICL_VM_STATUS_OUT_OF_TEXT) { + panic("error interpreting forth: %d", rv); + } + + /* + * Export some version numbers so that code can detect the loader/host + * version + */ + env = ficlSystemGetEnvironment(bf_sys); + ficlDictionarySetConstant(env, "loader_version", bootprog_rev); + + /* try to load and run init file if present */ + if (rc == NULL) + rc = "/boot/forth/boot.4th"; + if (*rc != '\0') { + fd = open(rc, O_RDONLY); + if (fd != -1) { + (void) ficlExecFD(bf_vm, fd); + close(fd); + } + } +} + +/* + * Feed a line of user input to the Forth interpreter + */ +int +bf_run(char *line) +{ + int result; + ficlString s; + + FICL_STRING_SET_FROM_CSTRING(s, line); + result = ficlVmExecuteString(bf_vm, s); + + DPRINTF("ficlExec '%s' = %d", line, result); + switch (result) { + case FICL_VM_STATUS_OUT_OF_TEXT: + case FICL_VM_STATUS_ABORTQ: + case FICL_VM_STATUS_QUIT: + case FICL_VM_STATUS_ERROR_EXIT: + break; + case FICL_VM_STATUS_USER_EXIT: + printf("No where to leave to!\n"); + break; + case FICL_VM_STATUS_ABORT: + printf("Aborted!\n"); + break; + case BF_PARSE: + printf("Parse error!\n"); + break; + default: + if (command_errmsg != NULL) { + printf("%s\n", command_errmsg); + command_errmsg = NULL; + } + } + + /* bye is same as reboot and will behave depending on platform */ + if (result == FICL_VM_STATUS_USER_EXIT) + bf_run("reboot"); + setenv("interpret", bf_vm->state ? "" : "ok", 1); + + return (result); +} diff --git a/usr/src/boot/common/interp_parse.c b/usr/src/boot/common/interp_parse.c new file mode 100644 index 0000000000..11cf11c057 --- /dev/null +++ b/usr/src/boot/common/interp_parse.c @@ -0,0 +1,222 @@ +/* + * 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. + * + * Jordan K. Hubbard + * 29 August 1998 + * + * The meat of the simple parser. + */ + +#include <sys/cdefs.h> + +#include <stand.h> +#include <string.h> +#include "bootstrap.h" + +static void clean(void); +static int insert(int *argcp, char *buf); +static char *variable_lookup(char *name); + +#define PARSE_BUFSIZE 1024 /* maximum size of one element */ +#define MAXARGS 20 /* maximum number of elements */ +static char *args[MAXARGS]; + +/* + * parse: accept a string of input and "parse" it for backslash + * substitutions and environment variable expansions (${var}), + * returning an argc/argv style vector of whitespace separated + * arguments. Returns 0 on success, 1 on failure (ok, ok, so I + * wimped-out on the error codes! :). + * + * Note that the argv array returned must be freed by the caller, but + * we own the space allocated for arguments and will free that on next + * invocation. This allows argv consumers to modify the array if + * required. + * + * NB: environment variables that expand to more than one whitespace + * separated token will be returned as a single argv[] element, not + * split in turn. Expanded text is also immune to further backslash + * elimination or expansion since this is a one-pass, non-recursive + * parser. You didn't specify more than this so if you want more, ask + * me. - jkh + */ + +#define PARSE_FAIL(expr) \ + if (expr) { \ + printf("fail at line %d\n", __LINE__); \ + clean(); \ + free(copy); \ + free(buf); \ + return (1); \ + } + +/* Accept the usual delimiters for a variable, returning counterpart */ +static char +isdelim(int ch) +{ + if (ch == '{') + return ('}'); + else if (ch == '(') + return (')'); + return ('\0'); +} + +static int +isquote(int ch) +{ + return (ch == '\''); +} + +static int +isdquote(int ch) +{ + return (ch == '"'); +} + +int +parse(int *argc, char ***argv, char *str) +{ + int ac; + char *val, *p, *q, *copy = NULL; + size_t i = 0; + char token, tmp, quote, dquote, *buf; + enum { STR, VAR, WHITE } state; + + ac = *argc = 0; + dquote = quote = 0; + if (!str || (p = copy = backslash(str)) == NULL) + return (1); + + /* Initialize vector and state */ + clean(); + state = STR; + buf = malloc(PARSE_BUFSIZE); + token = 0; + + /* And awaaaaaaaaay we go! */ + while (*p) { + switch (state) { + case STR: + if ((*p == '\\') && p[1]) { + p++; + PARSE_FAIL(i == (PARSE_BUFSIZE - 1)); + buf[i++] = *p++; + } else if (isquote(*p)) { + quote = quote ? 0 : *p; + if (dquote) { /* keep quote */ + PARSE_FAIL(i == (PARSE_BUFSIZE - 1)); + buf[i++] = *p++; + } else { + ++p; + } + } else if (isdquote(*p)) { + dquote = dquote ? 0 : *p; + if (quote) { /* keep dquote */ + PARSE_FAIL(i == (PARSE_BUFSIZE - 1)); + buf[i++] = *p++; + } else { + ++p; + } + } else if (isspace(*p) && !quote && !dquote) { + state = WHITE; + if (i) { + buf[i] = '\0'; + PARSE_FAIL(insert(&ac, buf)); + i = 0; + } + ++p; + } else if (*p == '$' && !quote) { + token = isdelim(*(p + 1)); + if (token) + p += 2; + else + ++p; + state = VAR; + } else { + PARSE_FAIL(i == (PARSE_BUFSIZE - 1)); + buf[i++] = *p++; + } + break; + + case WHITE: + if (isspace(*p)) + ++p; + else + state = STR; + break; + + case VAR: + if (token) { + PARSE_FAIL((q = strchr(p, token)) == NULL); + } else { + q = p; + while (*q && !isspace(*q)) + ++q; + } + tmp = *q; + *q = '\0'; + if ((val = variable_lookup(p)) != NULL) { + size_t len = strlen(val); + + strncpy(buf + i, val, PARSE_BUFSIZE - (i + 1)); + i += min(len, PARSE_BUFSIZE - 1); + } + *q = tmp; /* restore value */ + p = q + (token ? 1 : 0); + state = STR; + break; + } + } + /* missing terminating ' or " */ + PARSE_FAIL(quote || dquote); + /* If at end of token, add it */ + if (i && state == STR) { + buf[i] = '\0'; + PARSE_FAIL(insert(&ac, buf)); + } + args[ac] = NULL; + *argc = ac; + *argv = malloc((sizeof (char *) * ac + 1)); + bcopy(args, *argv, sizeof (char *) * ac + 1); + free(buf); + free(copy); + return (0); +} + +#define MAXARGS 20 + +/* Clean vector space */ +static void +clean(void) +{ + int i; + + for (i = 0; i < MAXARGS; i++) { + free(args[i]); + args[i] = NULL; + } +} + +static int +insert(int *argcp, char *buf) +{ + if (*argcp >= MAXARGS) + return (1); + args[(*argcp)++] = strdup(buf); + return (0); +} + +static char * +variable_lookup(char *name) +{ + + /* XXX search "special variable" space first? */ + return (getenv(name)); +} diff --git a/usr/src/boot/common/isapnp.c b/usr/src/boot/common/isapnp.c new file mode 100644 index 0000000000..438687bc45 --- /dev/null +++ b/usr/src/boot/common/isapnp.c @@ -0,0 +1,321 @@ +/* + * Copyright (c) 1998, Michael Smith + * Copyright (c) 1996, Sujal M. Patel + * 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> + +/* + * Machine-independant ISA PnP enumerator implementing a subset of the + * ISA PnP specification. + */ +#include <stand.h> +#include <string.h> +#include <bootstrap.h> +#include <isapnp.h> + +#define inb(x) (archsw.arch_isainb((x))) +#define outb(x, y) (archsw.arch_isaoutb((x), (y))) + +static void isapnp_write(int d, int r); +static void isapnp_send_Initiation_LFSR(void); +static int isapnp_get_serial(uint8_t *p); +static int isapnp_isolation_protocol(void); +static void isapnp_enumerate(void); + +/* PnP read data port */ +int isapnp_readport = 0; + +#define _PNP_ID_LEN 9 + +struct pnphandler isapnphandler = +{ + "ISA bus", + isapnp_enumerate +}; + +static void +isapnp_write(int d, int r) +{ + outb(_PNP_ADDRESS, d); + outb(_PNP_WRITE_DATA, r); +} + +/* + * Send Initiation LFSR as described in "Plug and Play ISA Specification", + * Intel May 94. + */ +static void +isapnp_send_Initiation_LFSR(void) +{ + int cur, i; + + /* Reset the LSFR */ + outb(_PNP_ADDRESS, 0); + outb(_PNP_ADDRESS, 0); /* yes, we do need it twice! */ + + cur = 0x6a; + outb(_PNP_ADDRESS, cur); + + for (i = 1; i < 32; i++) { + cur = (cur >> 1) | (((cur ^ (cur >> 1)) << 7) & 0xff); + outb(_PNP_ADDRESS, cur); + } +} + +/* + * Get the device's serial number. Returns 1 if the serial is valid. + */ +static int +isapnp_get_serial(uint8_t *data) +{ + int i, bit, valid = 0, sum = 0x6a; + + bzero(data, _PNP_ID_LEN); + outb(_PNP_ADDRESS, SERIAL_ISOLATION); + for (i = 0; i < 72; i++) { + bit = inb(isapnp_readport) == 0x55; + delay(250); /* Delay 250 usec */ + + /* Can't Short Circuit the next evaluation, so 'and' is last */ + bit = (inb(isapnp_readport) == 0xaa) && bit; + delay(250); /* Delay 250 usec */ + + valid = valid || bit; + + if (i < 64) { + sum = (sum >> 1) | + (((sum ^ (sum >> 1) ^ bit) << 7) & 0xff); + } + + data[i / 8] = (data[i / 8] >> 1) | (bit ? 0x80 : 0); + } + + valid = valid && (data[8] == sum); + + return (valid); +} + +/* + * Fills the buffer with resource info from the device. + * Returns nonzero if the device fails to report + */ +static int +isapnp_get_resource_info(uint8_t *buffer, int len) +{ + int i, j; + uchar_t temp; + + for (i = 0; i < len; i++) { + outb(_PNP_ADDRESS, STATUS); + for (j = 0; j < 100; j++) { + if ((inb(isapnp_readport)) & 0x1) + break; + delay(1); + } + if (j == 100) { + printf("PnP device failed to report resource data\n"); + return (1); + } + outb(_PNP_ADDRESS, RESOURCE_DATA); + temp = inb(isapnp_readport); + if (buffer != NULL) + buffer[i] = temp; + } + return (0); +} + +/* + * Scan Resource Data for useful information. + * + * We scan the resource data for compatible device IDs and + * identifier strings; we only take the first identifier string + * and assume it's for the card as a whole. + * + * Returns 0 if the scan completed OK, nonzero on error. + */ +static int +isapnp_scan_resdata(struct pnpinfo *pi) +{ + uchar_t tag, resinfo[8]; + uint_t limit; + size_t large_len; + uchar_t *str; + + limit = 1000; + while ((limit-- > 0) && !isapnp_get_resource_info(&tag, 1)) { + if (PNP_RES_TYPE(tag) == 0) { + /* Small resource */ + switch (PNP_SRES_NUM(tag)) { + case COMP_DEVICE_ID: + /* Got a compatible device id resource */ + if (isapnp_get_resource_info(resinfo, + PNP_SRES_LEN(tag))) + return (1); + pnp_addident(pi, pnp_eisaformat(resinfo)); + return (0); + + case END_TAG: + return (0); + + default: + /* Skip this resource */ + if (isapnp_get_resource_info(NULL, + PNP_SRES_LEN(tag))) + return (1); + break; + } + } else { + /* Large resource */ + if (isapnp_get_resource_info(resinfo, 2)) + return (1); + + large_len = resinfo[1]; + large_len = (large_len << 8) + resinfo[0]; + + switch (PNP_LRES_NUM(tag)) { + case ID_STRING_ANSI: + str = malloc(large_len + 1); + if (isapnp_get_resource_info(str, + (ssize_t)large_len)) { + free(str); + return (1); + } + str[large_len] = 0; + if (pi->pi_desc == NULL) { + pi->pi_desc = (char *)str; + } else { + free(str); + } + break; + + default: + /* Large resource, skip it */ + if (isapnp_get_resource_info(NULL, + (ssize_t)large_len)) + return (1); + } + } + } + return (1); +} + +/* + * Run the isolation protocol. Upon exiting, all cards are aware that + * they should use isapnp_readport as the READ_DATA port. + */ +static int +isapnp_isolation_protocol(void) +{ + int csn; + struct pnpinfo *pi; + uint8_t cardid[_PNP_ID_LEN]; + int ndevs; + + isapnp_send_Initiation_LFSR(); + ndevs = 0; + + isapnp_write(CONFIG_CONTROL, 0x04); /* Reset CSN for All Cards */ + + for (csn = 1; ; csn++) { + /* Wake up cards without a CSN (ie. all of them) */ + isapnp_write(WAKE, 0); + isapnp_write(SET_RD_DATA, (isapnp_readport >> 2)); + outb(_PNP_ADDRESS, SERIAL_ISOLATION); + delay(1000); /* Delay 1 msec */ + + if (isapnp_get_serial(cardid)) { + isapnp_write(SET_CSN, csn); + pi = pnp_allocinfo(); + ndevs++; + pnp_addident(pi, pnp_eisaformat(cardid)); + /* + * scan the card obtaining all the identifiers it holds + */ + if (isapnp_scan_resdata(pi)) { + /* error getting data, ignore */ + pnp_freeinfo(pi); + } else { + pnp_addinfo(pi); + } + } else { + break; + } + } + /* Move all cards to wait-for-key state */ + while (--csn > 0) { + isapnp_send_Initiation_LFSR(); + isapnp_write(WAKE, csn); + isapnp_write(CONFIG_CONTROL, 0x02); + delay(1000); /* XXX is it really necessary ? */ + csn--; + } + return (ndevs); +} + +/* + * Locate ISA-PnP devices and populate the supplied list. + */ +static void +isapnp_enumerate(void) +{ + int pnp_rd_port; + + /* Check for I/O port access */ + if ((archsw.arch_isainb == NULL) || (archsw.arch_isaoutb == NULL)) + return; + + /* + * Validate a possibly-suggested read port value. If the autoscan failed + * last time, this will return us to autoscan mode again. + */ + if ((isapnp_readport > 0) && + (((isapnp_readport < 0x203) || + (isapnp_readport > 0x3ff) || + (isapnp_readport & 0x3) != 0x3))) { + /* invalid, go look for ourselves */ + isapnp_readport = 0; + } + + if (isapnp_readport < 0) { + /* someone is telling us there is no ISA in the system */ + return; + } else if (isapnp_readport > 0) { + /* + * Someone has told us where the port is/should be, + * or we found one last time. + */ + isapnp_isolation_protocol(); + } else { + /* No clues, look for it ourselves */ + for (pnp_rd_port = 0x80; pnp_rd_port < 0xff; + pnp_rd_port += 0x10) { + /* Look for something, quit when we find it */ + isapnp_readport = (pnp_rd_port << 2) | 0x3; + if (isapnp_isolation_protocol() > 0) + break; + } + } +} diff --git a/usr/src/boot/common/isapnp.h b/usr/src/boot/common/isapnp.h new file mode 100644 index 0000000000..595cbf0fee --- /dev/null +++ b/usr/src/boot/common/isapnp.h @@ -0,0 +1,306 @@ +/* + * Copyright (c) 1996, Sujal M. Patel + * 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. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Sujal M. Patel + * 4. Neither the name of the author nor the names of any co-contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * 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. + */ + +#ifndef _I386_ISA_PNP_H_ +#define _I386_ISA_PNP_H_ + +/* Maximum Number of PnP Devices. 8 should be plenty */ +#define MAX_PNP_CARDS 8 +/* + * the following is the maximum number of PnP Logical devices that + * userconfig can handle. + */ +#define MAX_PNP_LDN 20 + +/* Static ports to access PnP state machine */ +#ifndef _KERNEL +/* pnp.h is included from pnpinfo.c. */ +#define _PNP_ADDRESS 0x279 +#define _PNP_WRITE_DATA 0xa79 +#endif + +/* PnP Registers. Write to ADDRESS and then use WRITE/READ_DATA */ +#define SET_RD_DATA 0x00 + /*** + Writing to this location modifies the address of the port used for + reading from the Plug and Play ISA cards. Bits[7:0] become I/O + read port address bits[9:2]. Reads from this register are ignored. + ***/ + +#define SERIAL_ISOLATION 0x01 + /*** + A read to this register causes a Plug and Play cards in the Isolation + state to compare one bit of the boards ID. + This register is read only. + ***/ + +#define CONFIG_CONTROL 0x02 + /*** + Bit[2] Reset CSN to 0 + Bit[1] Return to the Wait for Key state + Bit[0] Reset all logical devices and restore configuration + registers to their power-up values. + + A write to bit[0] of this register performs a reset function on + all logical devices. This resets the contents of configuration + registers to their default state. All card's logical devices + enter their default state and the CSN is preserved. + + A write to bit[1] of this register causes all cards to enter the + Wait for Key state but all CSNs are preserved and logical devices + are not affected. + + A write to bit[2] of this register causes all cards to reset their + CSN to zero . + + This register is write-only. The values are not sticky, that is, + hardware will automatically clear them and there is no need for + software to clear the bits. + ***/ + +#define WAKE 0x03 + /*** + A write to this port will cause all cards that have a CSN that + matches the write data[7:0] to go from the Sleep state to the either + the Isolation state if the write data for this command is zero or + the Config state if the write data is not zero. Additionally, the + pointer to the byte-serial device is reset. This register is + writeonly. + ***/ + +#define RESOURCE_DATA 0x04 + /*** + A read from this address reads the next byte of resource information. + The Status register must be polled until bit[0] is set before this + register may be read. This register is read only. + ***/ + +#define STATUS 0x05 + /*** + Bit[0] when set indicates it is okay to read the next data byte + from the Resource Data register. This register is readonly. + ***/ + +#define SET_CSN 0x06 + /*** + A write to this port sets a card's CSN. The CSN is a value uniquely + assigned to each ISA card after the serial identification process + so that each card may be individually selected during a Wake[CSN] + command. This register is read/write. + ***/ + +#define SET_LDN 0x07 + /*** + Selects the current logical device. All reads and writes of memory, + I/O, interrupt and DMA configuration information access the registers + of the logical device written here. In addition, the I/O Range + Check and Activate commands operate only on the selected logical + device. This register is read/write. If a card has only 1 logical + device, this location should be a read-only value of 0x00. + ***/ + +/*** addresses 0x08 - 0x1F Card Level Reserved for future use ***/ +/*** addresses 0x20 - 0x2F Card Level, Vendor Defined ***/ + +#define ACTIVATE 0x30 + /*** + For each logical device there is one activate register that controls + whether or not the logical device is active on the ISA bus. Bit[0], + if set, activates the logical device. Bits[7:1] are reserved and + must return 0 on reads. This is a read/write register. Before a + logical device is activated, I/O range check must be disabled. + ***/ + +#define IO_RANGE_CHECK 0x31 + /*** + This register is used to perform a conflict check on the I/O port + range programmed for use by a logical device. + + Bit[7:2] Reserved and must return 0 on reads + Bit[1] Enable I/O Range check, if set then I/O Range Check + is enabled. I/O range check is only valid when the logical + device is inactive. + + Bit[0], if set, forces the logical device to respond to I/O reads + of the logical device's assigned I/O range with a 0x55 when I/O + range check is in operation. If clear, the logical device drives + 0xAA. This register is read/write. + ***/ + +/*** addr 0x32 - 0x37 Logical Device Control Reserved for future use ***/ +/*** addr 0x38 - 0x3F Logical Device Control Vendor Define ***/ + +#define MEM_CONFIG 0x40 + /*** + Four memory resource registers per range, four ranges. + Fill with 0 if no ranges are enabled. + + Offset 0: RW Memory base address bits[23:16] + Offset 1: RW Memory base address bits[15:8] + Offset 2: Memory control + Bit[1] specifies 8/16-bit control. This bit is set to indicate + 16-bit memory, and cleared to indicate 8-bit memory. + Bit[0], if cleared, indicates the next field can be used as a range + length for decode (implies range length and base alignment of memory + descriptor are equal). + Bit[0], if set, indicates the next field is the upper limit for + the address. - - Bit[0] is read-only. + Offset 3: RW upper limit or range len, bits[23:16] + Offset 4: RW upper limit or range len, bits[15:8] + Offset 5-Offset 7: filler, unused. + ***/ + +#define IO_CONFIG_BASE 0x60 + /*** + Eight ranges, two bytes per range. + Offset 0: I/O port base address bits[15:8] + Offset 1: I/O port base address bits[7:0] + ***/ + +#define IRQ_CONFIG 0x70 + /*** + Two entries, two bytes per entry. + Offset 0: RW interrupt level (1..15, 0=unused). + Offset 1: Bit[1]: level(1:hi, 0:low), + Bit[0]: type (1:level, 0:edge) + byte 1 can be readonly if 1 type of int is used. + ***/ + +#define DRQ_CONFIG 0x74 + /*** + Two entries, one byte per entry. Bits[2:0] select + which DMA channel is in use for DMA 0. Zero selects DMA channel + 0, seven selects DMA channel 7. DMA channel 4, the cascade channel + is used to indicate no DMA channel is active. + ***/ + +/*** 32-bit memory accesses are at 0x76 ***/ + +/* Macros to parse Resource IDs */ +#define PNP_RES_TYPE(a) (a >> 7) +#define PNP_SRES_NUM(a) (a >> 3) +#define PNP_SRES_LEN(a) (a & 0x07) +#define PNP_LRES_NUM(a) (a & 0x7f) + +/* Small Resource Item names */ +#define PNP_VERSION 0x1 +#define LOG_DEVICE_ID 0x2 +#define COMP_DEVICE_ID 0x3 +#define IRQ_FORMAT 0x4 +#define DMA_FORMAT 0x5 +#define START_DEPEND_FUNC 0x6 +#define END_DEPEND_FUNC 0x7 +#define IO_PORT_DESC 0x8 +#define FIXED_IO_PORT_DESC 0x9 +#define SM_RES_RESERVED 0xa-0xd +#define SM_VENDOR_DEFINED 0xe +#define END_TAG 0xf + +/* Large Resource Item names */ +#define MEMORY_RANGE_DESC 0x1 +#define ID_STRING_ANSI 0x2 +#define ID_STRING_UNICODE 0x3 +#define LG_VENDOR_DEFINED 0x4 +#define _32BIT_MEM_RANGE_DESC 0x5 +#define _32BIT_FIXED_LOC_DESC 0x6 +#define LG_RES_RESERVED 0x7-0x7f + +/* + * pnp_cinfo contains Configuration Information. They are used + * to communicate to the device driver the actual configuration + * of the device, and also by the userconfig menu to let the + * operating system override any configuration set by the bios. + * + */ +struct pnp_cinfo { + u_int vendor_id; /* board id */ + u_int serial; /* Board's Serial Number */ + u_long flags; /* OS-reserved flags */ + u_char csn; /* assigned Card Select Number */ + u_char ldn; /* Logical Device Number */ + u_char enable; /* pnp enable */ + u_char override; /* override bios parms (in userconfig) */ + u_char irq[2]; /* IRQ Number */ + u_char irq_type[2]; /* IRQ Type */ + u_char drq[2]; + u_short port[8]; /* The Base Address of the Port */ + struct { + u_long base; /* Memory Base Address */ + int control; /* Memory Control Register */ + u_long range; /* Memory Range *OR* Upper Limit */ + } mem[4]; +}; + +#ifdef _KERNEL + +struct pnp_device { + char *pd_name; + char * (*pd_probe ) (u_long csn, u_long vendor_id); + void (*pd_attach ) (u_long csn, u_long vend_id, char * name, + struct isa_device *dev); + u_long *pd_count; + u_int *imask ; +}; + +struct _pnp_id { + u_long vendor_id; + u_long serial; + u_char checksum; +} ; + +struct pnp_dlist_node { + struct pnp_device *pnp; + struct isa_device dev; + struct pnp_dlist_node *next; +}; + +typedef struct _pnp_id pnp_id; +extern struct pnp_dlist_node *pnp_device_list; +extern pnp_id pnp_devices[MAX_PNP_CARDS]; +extern struct pnp_cinfo pnp_ldn_overrides[MAX_PNP_LDN]; +extern int pnp_overrides_valid; + +/* + * these two functions are for use in drivers + */ +int read_pnp_parms(struct pnp_cinfo *d, int ldn); +int write_pnp_parms(struct pnp_cinfo *d, int ldn); +int enable_pnp_card(void); + +/* + * used by autoconfigure to actually probe and attach drivers + */ +void pnp_configure(void); + +#endif /* _KERNEL */ + +#endif /* !_I386_ISA_PNP_H_ */ diff --git a/usr/src/boot/common/linenoise/LICENSE b/usr/src/boot/common/linenoise/LICENSE new file mode 100755 index 0000000000..18e814865a --- /dev/null +++ b/usr/src/boot/common/linenoise/LICENSE @@ -0,0 +1,25 @@ +Copyright (c) 2010-2014, Salvatore Sanfilippo <antirez at gmail dot com> +Copyright (c) 2010-2013, Pieter Noordhuis <pcnoordhuis at gmail dot com> + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + +* 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 COPYRIGHT HOLDERS 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 COPYRIGHT OWNER 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. diff --git a/usr/src/boot/common/linenoise/LICENSE.descrip b/usr/src/boot/common/linenoise/LICENSE.descrip new file mode 100644 index 0000000000..2191e517d7 --- /dev/null +++ b/usr/src/boot/common/linenoise/LICENSE.descrip @@ -0,0 +1 @@ +linenoise diff --git a/usr/src/boot/common/linenoise/Makefile b/usr/src/boot/common/linenoise/Makefile new file mode 100755 index 0000000000..a285410678 --- /dev/null +++ b/usr/src/boot/common/linenoise/Makefile @@ -0,0 +1,7 @@ +linenoise_example: linenoise.h linenoise.c + +linenoise_example: linenoise.c example.c + $(CC) -Wall -W -Os -g -o linenoise_example linenoise.c example.c + +clean: + rm -f linenoise_example diff --git a/usr/src/boot/common/linenoise/README.markdown b/usr/src/boot/common/linenoise/README.markdown new file mode 100755 index 0000000000..c845673cd4 --- /dev/null +++ b/usr/src/boot/common/linenoise/README.markdown @@ -0,0 +1,52 @@ +# Linenoise + +A minimal, zero-config, BSD licensed, readline replacement used in Redis, +MongoDB, and Android. + +* Single and multi line editing mode with the usual key bindings implemented. +* History handling. +* Completion. +* About 1,100 lines of BSD license source code. +* Only uses a subset of VT100 escapes (ANSI.SYS compatible). + +## Can a line editing library be 20k lines of code? + +Line editing with some support for history is a really important feature for command line utilities. Instead of retyping almost the same stuff again and again it's just much better to hit the up arrow and edit on syntax errors, or in order to try a slightly different command. But apparently code dealing with terminals is some sort of Black Magic: readline is 30k lines of code, libedit 20k. Is it reasonable to link small utilities to huge libraries just to get a minimal support for line editing? + +So what usually happens is either: + + * Large programs with configure scripts disabling line editing if readline is not present in the system, or not supporting it at all since readline is GPL licensed and libedit (the BSD clone) is not as known and available as readline is (Real world example of this problem: Tclsh). + * Smaller programs not using a configure script not supporting line editing at all (A problem we had with Redis-cli for instance). + +The result is a pollution of binaries without line editing support. + +So I spent more or less two hours doing a reality check resulting in this little library: is it *really* needed for a line editing library to be 20k lines of code? Apparently not, it is possibe to get a very small, zero configuration, trivial to embed library, that solves the problem. Smaller programs will just include this, supporing line editing out of the box. Larger programs may use this little library or just checking with configure if readline/libedit is available and resorting to linenoise if not. + +## Terminals, in 2010. + +Apparently almost every terminal you can happen to use today has some kind of support for basic VT100 escape sequences. So I tried to write a lib using just very basic VT100 features. The resulting library appears to work everywhere I tried to use it, and now can work even on ANSI.SYS compatible terminals, since no +VT220 specific sequences are used anymore. + +The library is currently about 1100 lines of code. In order to use it in your project just look at the *example.c* file in the source distribution, it is trivial. Linenoise is BSD code, so you can use both in free software and commercial software. + +## Tested with... + + * Linux text only console ($TERM = linux) + * Linux KDE terminal application ($TERM = xterm) + * Linux xterm ($TERM = xterm) + * Linux Buildroot ($TERM = vt100) + * Mac OS X iTerm ($TERM = xterm) + * Mac OS X default Terminal.app ($TERM = xterm) + * OpenBSD 4.5 through an OSX Terminal.app ($TERM = screen) + * IBM AIX 6.1 + * FreeBSD xterm ($TERM = xterm) + * ANSI.SYS + +Please test it everywhere you can and report back! + +## Let's push this forward! + +Patches should be provided in the respect of linenoise sensibility for small +easy to understand code. + +Send feedbacks to antirez at gmail diff --git a/usr/src/boot/common/linenoise/example.c b/usr/src/boot/common/linenoise/example.c new file mode 100755 index 0000000000..a2f0936ede --- /dev/null +++ b/usr/src/boot/common/linenoise/example.c @@ -0,0 +1,64 @@ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include "linenoise.h" + + +void completion(const char *buf, linenoiseCompletions *lc) { + if (buf[0] == 'h') { + linenoiseAddCompletion(lc,"hello"); + linenoiseAddCompletion(lc,"hello there"); + } +} + +int main(int argc, char **argv) { + char *line; + char *prgname = argv[0]; + + /* Parse options, with --multiline we enable multi line editing. */ + while(argc > 1) { + argc--; + argv++; + if (!strcmp(*argv,"--multiline")) { + linenoiseSetMultiLine(1); + printf("Multi-line mode enabled.\n"); + } else if (!strcmp(*argv,"--keycodes")) { + linenoisePrintKeyCodes(); + exit(0); + } else { + fprintf(stderr, "Usage: %s [--multiline] [--keycodes]\n", prgname); + exit(1); + } + } + + /* Set the completion callback. This will be called every time the + * user uses the <tab> key. */ + linenoiseSetCompletionCallback(completion); + + /* Load history from file. The history file is just a plain text file + * where entries are separated by newlines. */ + linenoiseHistoryLoad("history.txt"); /* Load the history at startup */ + + /* Now this is the main loop of the typical linenoise-based application. + * The call to linenoise() will block as long as the user types something + * and presses enter. + * + * The typed string is returned as a malloc() allocated string by + * linenoise, so the user needs to free() it. */ + while((line = linenoise("hello> ")) != NULL) { + /* Do something with the string. */ + if (line[0] != '\0' && line[0] != '/') { + printf("echo: '%s'\n", line); + linenoiseHistoryAdd(line); /* Add to the history. */ + linenoiseHistorySave("history.txt"); /* Save the history on disk. */ + } else if (!strncmp(line,"/historylen",11)) { + /* The "/historylen" command will change the history len. */ + int len = atoi(line+11); + linenoiseHistorySetMaxLen(len); + } else if (line[0] == '/') { + printf("Unreconized command: %s\n", line); + } + free(line); + } + return 0; +} diff --git a/usr/src/boot/common/linenoise/linenoise.c b/usr/src/boot/common/linenoise/linenoise.c new file mode 100755 index 0000000000..e3a72151a4 --- /dev/null +++ b/usr/src/boot/common/linenoise/linenoise.c @@ -0,0 +1,855 @@ +/* linenoise.c -- VERSION 1.0 + * + * Guerrilla line editing library against the idea that a line editing lib + * needs to be 20,000 lines of C code. + * + * You can find the latest source code at: + * + * http://github.com/antirez/linenoise + * + * Does a number of crazy assumptions that happen to be true in 99.9999% of + * the 2010 UNIX computers around. + * + * ------------------------------------------------------------------------ + * + * Copyright (c) 2010-2014, Salvatore Sanfilippo <antirez at gmail dot com> + * Copyright (c) 2010-2013, Pieter Noordhuis <pcnoordhuis at gmail dot com> + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * 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 COPYRIGHT HOLDERS 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 COPYRIGHT + * HOLDER 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. + * + * ------------------------------------------------------------------------ + * + * References: + * - http://invisible-island.net/xterm/ctlseqs/ctlseqs.html + * - http://www.3waylabs.com/nw/WWW/products/wizcon/vt220.html + * + * Todo list: + * - Filter bogus Ctrl+<char> combinations. + * - Win32 support + * + * Bloat: + * - History search like Ctrl+r in readline? + * + * List of escape sequences used by this program, we do everything just + * with three sequences. In order to be so cheap we may have some + * flickering effect with some slow terminal, but the lesser sequences + * the more compatible. + * + * EL (Erase Line) + * Sequence: ESC [ n K + * Effect: if n is 0 or missing, clear from cursor to end of line + * Effect: if n is 1, clear from beginning of line to cursor + * Effect: if n is 2, clear entire line + * + * CUF (CUrsor Forward) + * Sequence: ESC [ n C + * Effect: moves cursor forward n chars + * + * CUB (CUrsor Backward) + * Sequence: ESC [ n D + * Effect: moves cursor backward n chars + * + * The following is used to get the terminal width if getting + * the width with the TIOCGWINSZ ioctl fails + * + * DSR (Device Status Report) + * Sequence: ESC [ 6 n + * Effect: reports the current cusor position as ESC [ n ; m R + * where n is the row and m is the column + * + * When multi line mode is enabled, we also use an additional escape + * sequence. However multi line editing is disabled by default. + * + * CUU (Cursor Up) + * Sequence: ESC [ n A + * Effect: moves cursor up of n chars. + * + * CUD (Cursor Down) + * Sequence: ESC [ n B + * Effect: moves cursor down of n chars. + * + * When linenoiseClearScreen() is called, two additional escape sequences + * are used in order to clear the screen and position the cursor at home + * position. + * + * CUP (Cursor position) + * Sequence: ESC [ H + * Effect: moves the cursor to upper left corner + * + * ED (Erase display) + * Sequence: ESC [ 2 J + * Effect: clear the whole screen + * + */ + +#include <stand.h> +#include "linenoise.h" +#include "bootstrap.h" + +#define LINENOISE_DEFAULT_HISTORY_MAX_LEN 100 +#define LINENOISE_MAX_LINE 256 +static linenoiseCompletionCallback *completionCallback = NULL; + +static int mlmode = 1; /* Multi line mode. Default is single line. */ +static int history_max_len = LINENOISE_DEFAULT_HISTORY_MAX_LEN; +static int history_len = 0; +static char **history = NULL; + +/* The linenoiseState structure represents the state during line editing. + * We pass this state to functions implementing specific editing + * functionalities. */ +struct linenoiseState { + char *buf; /* Edited line buffer. */ + size_t buflen; /* Edited line buffer size. */ + const char *prompt; /* Prompt to display. */ + size_t plen; /* Prompt length. */ + size_t pos; /* Current cursor position. */ + size_t oldpos; /* Previous refresh cursor position. */ + size_t len; /* Current edited line length. */ + size_t cols; /* Number of columns in terminal. */ + size_t maxrows; /* Maximum num of rows used so far (multiline mode) */ + int history_index; /* The history index we are currently editing. */ +}; + +enum KEY_ACTION{ + KEY_NULL = 0, /* NULL */ + CTRL_A = 1, /* Ctrl+a */ + CTRL_B = 2, /* Ctrl-b */ + CTRL_C = 3, /* Ctrl-c */ + CTRL_D = 4, /* Ctrl-d */ + CTRL_E = 5, /* Ctrl-e */ + CTRL_F = 6, /* Ctrl-f */ + CTRL_H = 8, /* Ctrl-h */ + TAB = 9, /* Tab */ + CTRL_K = 11, /* Ctrl+k */ + CTRL_L = 12, /* Ctrl+l */ + ENTER = 13, /* Enter */ + CTRL_N = 14, /* Ctrl-n */ + CTRL_P = 16, /* Ctrl-p */ + CTRL_T = 20, /* Ctrl-t */ + CTRL_U = 21, /* Ctrl+u */ + CTRL_W = 23, /* Ctrl+w */ + ESC = 27, /* Escape */ + BACKSPACE = 127 /* Backspace */ +}; + +static void refreshLine(struct linenoiseState *l); + +/* ======================= Low level terminal handling ====================== */ + +static int +put_bytes(const char *s, int len) +{ + int i; + if (s == NULL) + return -1; + + for (i = 0; i < len; i++) + putchar(s[i]); + return (i); +} + +/* Set if to use or not the multi line mode. */ +void linenoiseSetMultiLine(int ml) { + mlmode = ml; +} + +/* Clear the screen. Used to handle ctrl+l */ +void linenoiseClearScreen(void) { + if (put_bytes("\x1b[H\x1b[J", 6) <= 0) { + /* nothing to do, just to avoid warning. */ + } +} + +static int +getColumns(void) +{ + char *columns = getenv("screen-#cols"); + if (columns == NULL) + return (80); + return (strtol(columns, NULL, 0)); +} + +/* Beep, used for completion when there is nothing to complete or when all + * the choices were already shown. */ +static void linenoiseBeep(void) { + put_bytes("\x7", 1); +} + +/* ============================== Completion ================================ */ + +/* Free a list of completion option populated by linenoiseAddCompletion(). */ +static void freeCompletions(linenoiseCompletions *lc) { + size_t i; + for (i = 0; i < lc->len; i++) + free(lc->cvec[i]); + if (lc->cvec != NULL) + free(lc->cvec); +} + +/* This is an helper function for linenoiseEdit() and is called when the + * user types the <tab> key in order to complete the string currently in the + * input. + * + * The state of the editing is encapsulated into the pointed linenoiseState + * structure as described in the structure definition. */ +static int completeLine(struct linenoiseState *ls) { + linenoiseCompletions lc = { 0, NULL }; + int nwritten; + char c = 0; + + completionCallback(ls->buf,&lc); + if (lc.len == 0) { + linenoiseBeep(); + } else { + size_t stop = 0, i = 0; + + while(!stop) { + /* Show completion or original buffer */ + if (i < lc.len) { + struct linenoiseState saved = *ls; + + ls->len = ls->pos = strlen(lc.cvec[i]); + ls->buf = lc.cvec[i]; + refreshLine(ls); + ls->len = saved.len; + ls->pos = saved.pos; + ls->buf = saved.buf; + } else { + refreshLine(ls); + } + + c = getchar(); + if (c <= 0) { + freeCompletions(&lc); + return -1; + } + + switch(c) { + case 9: /* tab */ + i = (i+1) % (lc.len+1); + if (i == lc.len) linenoiseBeep(); + break; + case 27: /* escape */ + /* Re-show original buffer */ + if (i < lc.len) refreshLine(ls); + stop = 1; + break; + default: + /* Update buffer and return */ + if (i < lc.len) { + nwritten = snprintf(ls->buf,ls->buflen,"%s",lc.cvec[i]); + ls->len = ls->pos = nwritten; + } + stop = 1; + break; + } + } + } + + freeCompletions(&lc); + return c; /* Return last read character */ +} + +/* Register a callback function to be called for tab-completion. */ +void linenoiseSetCompletionCallback(linenoiseCompletionCallback *fn) { + completionCallback = fn; +} + +/* This function is used by the callback function registered by the user + * in order to add completion options given the input string when the + * user typed <tab>. See the example.c source code for a very easy to + * understand example. */ +void linenoiseAddCompletion(linenoiseCompletions *lc, const char *str) { + size_t len = strlen(str); + char *copy, **cvec; + + copy = malloc(len+1); + if (copy == NULL) return; + memcpy(copy,str,len+1); + cvec = realloc(lc->cvec,sizeof(char*)*(lc->len+1)); + if (cvec == NULL) { + free(copy); + return; + } + lc->cvec = cvec; + lc->cvec[lc->len++] = copy; +} + +/* =========================== Line editing ================================= */ + +/* We define a very simple "append buffer" structure, that is an heap + * allocated string where we can append to. This is useful in order to + * write all the escape sequences in a buffer and flush them to the standard + * output in a single call, to avoid flickering effects. */ +struct abuf { + char *b; + int len; +}; + +static void abInit(struct abuf *ab) { + ab->b = NULL; + ab->len = 0; +} + +static void abAppend(struct abuf *ab, const char *s, int len) { + char *new = malloc(ab->len+len); + + if (new == NULL) return; + memcpy(new, ab->b, ab->len); + memcpy(new+ab->len,s,len); + free(ab->b); + ab->b = new; + ab->len += len; +} + +static void abFree(struct abuf *ab) { + free(ab->b); +} + +/* Single line low level line refresh. + * + * Rewrite the currently edited line accordingly to the buffer content, + * cursor position, and number of columns of the terminal. */ +static void refreshSingleLine(struct linenoiseState *l) { + char seq[64]; + size_t plen = strlen(l->prompt); + char *buf = l->buf; + size_t len = l->len; + size_t pos = l->pos; + struct abuf ab; + + while((plen+pos) >= l->cols) { + buf++; + len--; + pos--; + } + while (plen+len > l->cols) { + len--; + } + + abInit(&ab); + /* Cursor to left edge */ + snprintf(seq,64,"\r"); + abAppend(&ab,seq,strlen(seq)); + /* Write the prompt and the current buffer content */ + abAppend(&ab,l->prompt,strlen(l->prompt)); + abAppend(&ab,buf,len); + /* Erase to right */ + snprintf(seq,64,"\x1b[K"); + abAppend(&ab,seq,strlen(seq)); + /* Move cursor to original position. */ + snprintf(seq,64,"\r\x1b[%dC", (int)(pos+plen)); + abAppend(&ab,seq,strlen(seq)); + put_bytes(ab.b, ab.len); + + abFree(&ab); +} + +/* Multi line low level line refresh. + * + * Rewrite the currently edited line accordingly to the buffer content, + * cursor position, and number of columns of the terminal. */ +static void refreshMultiLine(struct linenoiseState *l) { + char seq[64]; + int plen = strlen(l->prompt); + int rows = (plen+l->len+l->cols)/l->cols; /* rows used by current buf. */ + int rpos = (plen+l->oldpos+l->cols)/l->cols; /* cursor relative row. */ + int rpos2; /* rpos after refresh. */ + int col; /* colum position, zero-based. */ + int old_rows = l->maxrows; + int j; + struct abuf ab; + + /* Update maxrows if needed. */ + if (rows > (int)l->maxrows) l->maxrows = rows; + + /* First step: clear all the lines used before. To do so start by + * going to the last row. */ + abInit(&ab); + if (old_rows-rpos > 0) { + snprintf(seq,64,"\x1b[%dB", old_rows-rpos); + abAppend(&ab,seq,strlen(seq)); + } + + /* Now for every row clear it, go up. */ + for (j = 0; j < old_rows-1; j++) { + snprintf(seq,64,"\r\x1b[0K\x1b[1A"); + abAppend(&ab,seq,strlen(seq)); + } + + /* Clean the top line. */ + snprintf(seq,64,"\r\x1b[0K"); + abAppend(&ab,seq,strlen(seq)); + + /* Write the prompt and the current buffer content */ + abAppend(&ab,l->prompt,strlen(l->prompt)); + abAppend(&ab,l->buf,l->len); + + /* If we are at the very end of the screen with our prompt, we need to + * emit a newline and move the prompt to the first column. */ + if (l->pos && + l->pos == l->len && + (l->pos+plen) % l->cols == 0) + { + abAppend(&ab,"\n",1); + snprintf(seq,64,"\r"); + abAppend(&ab,seq,strlen(seq)); + rows++; + if (rows > (int)l->maxrows) l->maxrows = rows; + } + + /* Move cursor to right position. */ + rpos2 = (plen+l->pos+l->cols)/l->cols; /* current cursor relative row. */ + + /* Go up till we reach the expected positon. */ + if (rows-rpos2 > 0) { + snprintf(seq,64,"\x1b[%dA", rows-rpos2); + abAppend(&ab,seq,strlen(seq)); + } + + /* Set column. */ + col = (plen+(int)l->pos) % (int)l->cols; + if (col) + snprintf(seq,64,"\r\x1b[%dC", col); + else + snprintf(seq,64,"\r"); + abAppend(&ab,seq,strlen(seq)); + + l->oldpos = l->pos; + + put_bytes(ab.b, ab.len); + abFree(&ab); +} + +/* Calls the two low level functions refreshSingleLine() or + * refreshMultiLine() according to the selected mode. */ +static void refreshLine(struct linenoiseState *l) { + if (mlmode) + refreshMultiLine(l); + else + refreshSingleLine(l); +} + +/* Insert the character 'c' at cursor current position. + * + * On error writing to the terminal -1 is returned, otherwise 0. */ +static int +linenoiseEditInsert(struct linenoiseState *l, char c) { + if (l->len < l->buflen) { + if (l->len == l->pos) { + l->buf[l->pos] = c; + l->pos++; + l->len++; + l->buf[l->len] = '\0'; + if ((!mlmode && l->plen+l->len < l->cols) /* || mlmode */) { + /* Avoid a full update of the line in the + * trivial case. */ + putchar(c); + } else { + refreshLine(l); + } + } else { + memmove(l->buf+l->pos+1,l->buf+l->pos,l->len-l->pos); + l->buf[l->pos] = c; + l->len++; + l->pos++; + l->buf[l->len] = '\0'; + refreshLine(l); + } + } + return 0; +} + +/* Move cursor on the left. */ +static void +linenoiseEditMoveLeft(struct linenoiseState *l) { + if (l->pos > 0) { + l->pos--; + refreshLine(l); + } +} + +/* Move cursor on the right. */ +static void +linenoiseEditMoveRight(struct linenoiseState *l) { + if (l->pos != l->len) { + l->pos++; + refreshLine(l); + } +} + +/* Move cursor to the start of the line. */ +static void +linenoiseEditMoveHome(struct linenoiseState *l) { + if (l->pos != 0) { + l->pos = 0; + refreshLine(l); + } +} + +/* Move cursor to the end of the line. */ +static void +linenoiseEditMoveEnd(struct linenoiseState *l) { + if (l->pos != l->len) { + l->pos = l->len; + refreshLine(l); + } +} + +/* Substitute the currently edited line with the next or previous history + * entry as specified by 'dir'. */ +#define LINENOISE_HISTORY_NEXT 0 +#define LINENOISE_HISTORY_PREV 1 +static void +linenoiseEditHistoryNext(struct linenoiseState *l, int dir) { + if (history_len > 1) { + /* Update the current history entry before to + * overwrite it with the next one. */ + free(history[history_len - 1 - l->history_index]); + history[history_len - 1 - l->history_index] = strdup(l->buf); + /* Show the new entry */ + l->history_index += (dir == LINENOISE_HISTORY_PREV) ? 1 : -1; + if (l->history_index < 0) { + l->history_index = 0; + return; + } else if (l->history_index >= history_len) { + l->history_index = history_len-1; + return; + } + strncpy(l->buf,history[history_len - 1 - l->history_index],l->buflen); + l->buf[l->buflen-1] = '\0'; + l->len = l->pos = strlen(l->buf); + refreshLine(l); + } +} + +/* Delete the character at the right of the cursor without altering the cursor + * position. Basically this is what happens with the "Delete" keyboard key. */ +static void +linenoiseEditDelete(struct linenoiseState *l) { + if (l->len > 0 && l->pos < l->len) { + memmove(l->buf+l->pos,l->buf+l->pos+1,l->len-l->pos-1); + l->len--; + l->buf[l->len] = '\0'; + refreshLine(l); + } +} + +/* Backspace implementation. */ +static void +linenoiseEditBackspace(struct linenoiseState *l) { + if (l->pos > 0 && l->len > 0) { + memmove(l->buf+l->pos-1,l->buf+l->pos,l->len-l->pos); + l->pos--; + l->len--; + l->buf[l->len] = '\0'; + refreshLine(l); + } +} + +/* Delete the previosu word, maintaining the cursor at the start of the + * current word. */ +static void +linenoiseEditDeletePrevWord(struct linenoiseState *l) { + size_t old_pos = l->pos; + size_t diff; + + while (l->pos > 0 && l->buf[l->pos-1] == ' ') + l->pos--; + while (l->pos > 0 && l->buf[l->pos-1] != ' ') + l->pos--; + diff = old_pos - l->pos; + memmove(l->buf+l->pos,l->buf+old_pos,l->len-old_pos+1); + l->len -= diff; + refreshLine(l); +} + +/* This function is the core of the line editing capability of linenoise. + * It expects 'fd' to be already in "raw mode" so that every key pressed + * will be returned ASAP to read(). + * + * The resulting string is put into 'buf' when the user type enter, or + * when ctrl+d is typed. + * + * The function returns the length of the current buffer. */ +static int linenoiseEdit(char *buf, size_t buflen, const char *prompt) +{ + struct linenoiseState l; + + /* Populate the linenoise state that we pass to functions implementing + * specific editing functionalities. */ + l.buf = buf; + l.buflen = buflen; + l.prompt = prompt; + l.plen = strlen(prompt); + l.oldpos = l.pos = 0; + l.len = 0; + l.cols = getColumns(); + l.maxrows = 0; + l.history_index = 0; + + /* Buffer starts empty. */ + l.buf[0] = '\0'; + l.buflen--; /* Make sure there is always space for the nulterm */ + + /* The latest history entry is always our current buffer, that + * initially is just an empty string. */ + linenoiseHistoryAdd(""); + + printf ("%s", prompt); + while(1) { + char c; + char seq[3]; + + c = getchar(); + if (c == -1) + continue; + + /* Only autocomplete when the callback is set. It returns < 0 when + * there was an error reading from fd. Otherwise it will return the + * character that should be handled next. */ + if (c == 9 && completionCallback != NULL) { + c = completeLine(&l); + /* Return on errors */ + if (c < 0) return l.len; + /* Read next character when 0 */ + if (c == 0) continue; + } + + switch(c) { + case ENTER: /* enter */ + history_len--; + free(history[history_len]); + if (mlmode) linenoiseEditMoveEnd(&l); + return (int)l.len; + case CTRL_C: /* ctrl-c */ + buf[0] = '\0'; + l.pos = l.len = 0; + refreshLine(&l); + break; + case BACKSPACE: /* backspace */ + case 8: /* ctrl-h */ + linenoiseEditBackspace(&l); + break; + case CTRL_D: /* ctrl-d, remove char at right of cursor. */ + if (l.len > 0) { + linenoiseEditDelete(&l); + } + break; + case CTRL_T: /* ctrl-t, swaps current character with previous. */ + if (l.pos > 0 && l.pos < l.len) { + int aux = buf[l.pos-1]; + buf[l.pos-1] = buf[l.pos]; + buf[l.pos] = aux; + if (l.pos != l.len-1) l.pos++; + refreshLine(&l); + } + break; + case CTRL_B: /* ctrl-b */ + linenoiseEditMoveLeft(&l); + break; + case CTRL_F: /* ctrl-f */ + linenoiseEditMoveRight(&l); + break; + case CTRL_P: /* ctrl-p */ + linenoiseEditHistoryNext(&l, LINENOISE_HISTORY_PREV); + break; + case CTRL_N: /* ctrl-n */ + linenoiseEditHistoryNext(&l, LINENOISE_HISTORY_NEXT); + break; + case ESC: /* escape sequence */ + /* Read the next two bytes representing the escape sequence. + * Use two calls to handle slow terminals returning the two + * chars at different times. */ + seq[0] = getchar(); + seq[1] = getchar(); + + /* ESC [ sequences. */ + if (seq[0] == '[') { + if (seq[1] >= '0' && seq[1] <= '9') { + /* Extended escape, read additional byte. */ + seq[2] = getchar(); + if (seq[2] == '~') { + switch(seq[1]) { + case '3': /* Delete key. */ + linenoiseEditDelete(&l); + break; + } + } + } else { + switch(seq[1]) { + case 'A': /* Up */ + linenoiseEditHistoryNext(&l, LINENOISE_HISTORY_PREV); + break; + case 'B': /* Down */ + linenoiseEditHistoryNext(&l, LINENOISE_HISTORY_NEXT); + break; + case 'C': /* Right */ + linenoiseEditMoveRight(&l); + break; + case 'D': /* Left */ + linenoiseEditMoveLeft(&l); + break; + case 'H': /* Home */ + linenoiseEditMoveHome(&l); + break; + case 'F': /* End*/ + linenoiseEditMoveEnd(&l); + break; + } + } + } + + /* ESC O sequences. */ + else if (seq[0] == 'O') { + switch(seq[1]) { + case 'H': /* Home */ + linenoiseEditMoveHome(&l); + break; + case 'F': /* End*/ + linenoiseEditMoveEnd(&l); + break; + } + } + break; + default: + if (linenoiseEditInsert(&l,c)) return -1; + break; + case CTRL_U: /* Ctrl+u, delete the whole line. */ + buf[0] = '\0'; + l.pos = l.len = 0; + refreshLine(&l); + break; + case CTRL_K: /* Ctrl+k, delete from current to end of line. */ + buf[l.pos] = '\0'; + l.len = l.pos; + refreshLine(&l); + break; + case CTRL_A: /* Ctrl+a, go to the start of the line */ + linenoiseEditMoveHome(&l); + break; + case CTRL_E: /* ctrl+e, go to the end of the line */ + linenoiseEditMoveEnd(&l); + break; + case CTRL_L: /* ctrl+l, clear screen */ + linenoiseClearScreen(); + refreshLine(&l); + break; + case CTRL_W: /* ctrl+w, delete previous word */ + linenoiseEditDeletePrevWord(&l); + break; + } + } + return l.len; +} + +/* The high level function that is the main API of the linenoise library. + * This function checks if the terminal has basic capabilities, just checking + * for a blacklist of stupid terminals, and later either calls the line + * editing function or uses dummy fgets() so that you will be able to type + * something even in the most desperate of the conditions. */ +char *linenoise(const char *prompt) { + char buf[LINENOISE_MAX_LINE]; + int count; + + cons_mode(C_MODERAW); + count = linenoiseEdit(buf,LINENOISE_MAX_LINE,prompt); + cons_mode(0); + printf("\n"); + if (count == -1) return NULL; + return strdup(buf); +} + +/* ================================ History ================================= */ + +/* This is the API call to add a new entry in the linenoise history. + * It uses a fixed array of char pointers that are shifted (memmoved) + * when the history max length is reached in order to remove the older + * entry and make room for the new one, so it is not exactly suitable for huge + * histories, but will work well for a few hundred of entries. + * + * Using a circular buffer is smarter, but a bit more complex to handle. */ +int linenoiseHistoryAdd(const char *line) { + char *linecopy; + + if (history_max_len == 0) return 0; + + /* Initialization on first call. */ + if (history == NULL) { + history = malloc(sizeof(char*)*history_max_len); + if (history == NULL) return 0; + memset(history,0,(sizeof(char*)*history_max_len)); + } + + /* Don't add duplicated lines. */ + if (history_len && !strcmp(history[history_len-1], line)) return 0; + + /* Add an heap allocated copy of the line in the history. + * If we reached the max length, remove the older line. */ + linecopy = strdup(line); + if (!linecopy) return 0; + if (history_len == history_max_len) { + free(history[0]); + memmove(history,history+1,sizeof(char*)*(history_max_len-1)); + history_len--; + } + history[history_len] = linecopy; + history_len++; + return 1; +} + +/* Set the maximum length for the history. This function can be called even + * if there is already some history, the function will make sure to retain + * just the latest 'len' elements if the new history length value is smaller + * than the amount of items already inside the history. */ +int linenoiseHistorySetMaxLen(int len) { + char **new; + + if (len < 1) return 0; + if (history) { + int tocopy = history_len; + + new = malloc(sizeof(char*)*len); + if (new == NULL) return 0; + + /* If we can't copy everything, free the elements we'll not use. */ + if (len < tocopy) { + int j; + + for (j = 0; j < tocopy-len; j++) free(history[j]); + tocopy = len; + } + memset(new,0,sizeof(char*)*len); + memcpy(new,history+(history_len-tocopy), sizeof(char*)*tocopy); + free(history); + history = new; + } + history_max_len = len; + if (history_len > history_max_len) + history_len = history_max_len; + return 1; +} diff --git a/usr/src/boot/common/linenoise/linenoise.h b/usr/src/boot/common/linenoise/linenoise.h new file mode 100755 index 0000000000..fbb01cfaad --- /dev/null +++ b/usr/src/boot/common/linenoise/linenoise.h @@ -0,0 +1,68 @@ +/* linenoise.h -- VERSION 1.0 + * + * Guerrilla line editing library against the idea that a line editing lib + * needs to be 20,000 lines of C code. + * + * See linenoise.c for more information. + * + * ------------------------------------------------------------------------ + * + * Copyright (c) 2010-2014, Salvatore Sanfilippo <antirez at gmail dot com> + * Copyright (c) 2010-2013, Pieter Noordhuis <pcnoordhuis at gmail dot com> + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * 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 COPYRIGHT HOLDERS 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 COPYRIGHT + * HOLDER 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. + */ + +#ifndef __LINENOISE_H +#define __LINENOISE_H + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct linenoiseCompletions { + size_t len; + char **cvec; +} linenoiseCompletions; + +typedef void(linenoiseCompletionCallback)(const char *, linenoiseCompletions *); +void linenoiseSetCompletionCallback(linenoiseCompletionCallback *); +void linenoiseAddCompletion(linenoiseCompletions *, const char *); + +char *linenoise(const char *prompt); +int linenoiseHistoryAdd(const char *line); +int linenoiseHistorySetMaxLen(int len); +int linenoiseHistorySave(const char *filename); +int linenoiseHistoryLoad(const char *filename); +void linenoiseClearScreen(void); +void linenoiseSetMultiLine(int ml); +void linenoisePrintKeyCodes(void); + +#ifdef __cplusplus +} +#endif + +#endif /* __LINENOISE_H */ diff --git a/usr/src/boot/common/load_elf.c b/usr/src/boot/common/load_elf.c new file mode 100644 index 0000000000..b7fc4bea09 --- /dev/null +++ b/usr/src/boot/common/load_elf.c @@ -0,0 +1,1094 @@ +/*- + * Copyright (c) 1998 Michael Smith <msmith@freebsd.org> + * Copyright (c) 1998 Peter Wemm <peter@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 <sys/exec.h> +#include <sys/linker.h> +#include <sys/module.h> +#include <sys/stdint.h> +#include <string.h> +#include <machine/elf.h> +#include <stand.h> +#define FREEBSD_ELF +#include <link.h> + +#include "bootstrap.h" + +#define COPYOUT(s,d,l) archsw.arch_copyout((vm_offset_t)(s), d, l) + +#if defined(__i386__) && __ELF_WORD_SIZE == 64 +#undef ELF_TARG_CLASS +#undef ELF_TARG_MACH +#define ELF_TARG_CLASS ELFCLASS64 +#define ELF_TARG_MACH EM_X86_64 +#endif + +typedef struct elf_file { + Elf_Phdr *ph; + Elf_Ehdr *ehdr; + Elf_Sym *symtab; + Elf_Hashelt *hashtab; + Elf_Hashelt nbuckets; + Elf_Hashelt nchains; + Elf_Hashelt *buckets; + Elf_Hashelt *chains; + Elf_Rel *rel; + size_t relsz; + Elf_Rela *rela; + size_t relasz; + char *strtab; + size_t strsz; + int fd; + caddr_t firstpage; + size_t firstlen; + int kernel; + u_int64_t off; +} *elf_file_t; + +static int __elfN(loadimage)(struct preloaded_file *mp, elf_file_t ef, u_int64_t loadaddr); +static int __elfN(lookup_symbol)(struct preloaded_file *mp, elf_file_t ef, const char* name, Elf_Sym* sym); +static int __elfN(reloc_ptr)(struct preloaded_file *mp, elf_file_t ef, + Elf_Addr p, void *val, size_t len); +static int __elfN(parse_modmetadata)(struct preloaded_file *mp, elf_file_t ef, + Elf_Addr p_start, Elf_Addr p_end); +static symaddr_fn __elfN(symaddr); +static char *fake_modname(const char *name); + +const char *__elfN(kerneltype) = "elf kernel"; +const char *__elfN(moduletype) = "elf module"; + +u_int64_t __elfN(relocation_offset) = 0; + +static int +__elfN(load_elf_header)(char *filename, elf_file_t ef) +{ + ssize_t bytes_read; + Elf_Ehdr *ehdr; + int err; + + /* + * Open the image, read and validate the ELF header + */ + if (filename == NULL) /* can't handle nameless */ + return (EFTYPE); + if ((ef->fd = open(filename, O_RDONLY)) == -1) + return (errno); + ef->firstpage = malloc(PAGE_SIZE); + if (ef->firstpage == NULL) { + close(ef->fd); + return (ENOMEM); + } + bytes_read = read(ef->fd, ef->firstpage, PAGE_SIZE); + ef->firstlen = (size_t)bytes_read; + if (bytes_read < 0 || ef->firstlen <= sizeof(Elf_Ehdr)) { + err = EFTYPE; /* could be EIO, but may be small file */ + goto error; + } + ehdr = ef->ehdr = (Elf_Ehdr *)ef->firstpage; + + /* Is it ELF? */ + if (!IS_ELF(*ehdr)) { + err = EFTYPE; + goto error; + } + if (ehdr->e_ident[EI_CLASS] != ELF_TARG_CLASS || /* Layout ? */ + ehdr->e_ident[EI_DATA] != ELF_TARG_DATA || + ehdr->e_ident[EI_VERSION] != EV_CURRENT || /* Version ? */ + ehdr->e_version != EV_CURRENT || + ehdr->e_machine != ELF_TARG_MACH) { /* Machine ? */ + err = EFTYPE; + goto error; + } + + return (0); + +error: + if (ef->firstpage != NULL) { + free(ef->firstpage); + ef->firstpage = NULL; + } + if (ef->fd != -1) { + close(ef->fd); + ef->fd = -1; + } + return (err); +} + +/* + * Attempt to load the file (file) as an ELF module. It will be stored at + * (dest), and a pointer to a module structure describing the loaded object + * will be saved in (result). + */ +int +__elfN(loadfile)(char *filename, u_int64_t dest, struct preloaded_file **result) +{ + return (__elfN(loadfile_raw)(filename, dest, result, 0)); +} + +int +__elfN(loadfile_raw)(char *filename, u_int64_t dest, + struct preloaded_file **result, int multiboot) +{ + struct preloaded_file *fp, *kfp; + struct elf_file ef; + Elf_Ehdr *ehdr; + int err; + + fp = NULL; + bzero(&ef, sizeof(struct elf_file)); + ef.fd = -1; + + err = __elfN(load_elf_header)(filename, &ef); + if (err != 0) + return (err); + + ehdr = ef.ehdr; + + /* + * Check to see what sort of module we are. + */ + kfp = file_findfile(NULL, __elfN(kerneltype)); +#ifdef __powerpc__ + /* + * Kernels can be ET_DYN, so just assume the first loaded object is the + * kernel. This assumption will be checked later. + */ + if (kfp == NULL) + ef.kernel = 1; +#endif + if (ef.kernel || ehdr->e_type == ET_EXEC) { + /* Looks like a kernel */ + if (kfp != NULL) { + printf("elf" __XSTRING(__ELF_WORD_SIZE) "_loadfile: kernel already loaded\n"); + err = EPERM; + goto oerr; + } + /* + * Calculate destination address based on kernel entrypoint. + * + * For ARM, the destination address is independent of any values in the + * elf header (an ARM kernel can be loaded at any 2MB boundary), so we + * leave dest set to the value calculated by archsw.arch_loadaddr() and + * passed in to this function. + */ +#ifndef __arm__ + if (ehdr->e_type == ET_EXEC) + dest = (ehdr->e_entry & ~PAGE_MASK); +#endif + if ((ehdr->e_entry & ~PAGE_MASK) == 0) { + printf("elf" __XSTRING(__ELF_WORD_SIZE) "_loadfile: not a kernel (maybe static binary?)\n"); + err = EPERM; + goto oerr; + } + ef.kernel = 1; + + } else if (ehdr->e_type == ET_DYN) { + /* Looks like a kld module */ + if (multiboot != 0) { + printf("elf" __XSTRING(__ELF_WORD_SIZE) "_loadfile: can't load module as multiboot\n"); + err = EPERM; + goto oerr; + } + if (kfp == NULL) { + printf("elf" __XSTRING(__ELF_WORD_SIZE) "_loadfile: can't load module before kernel\n"); + err = EPERM; + goto oerr; + } + if (strcmp(__elfN(kerneltype), kfp->f_type)) { + printf("elf" __XSTRING(__ELF_WORD_SIZE) "_loadfile: can't load module with kernel type '%s'\n", kfp->f_type); + err = EPERM; + goto oerr; + } + /* Looks OK, got ahead */ + ef.kernel = 0; + + } else { + err = EFTYPE; + goto oerr; + } + + if (archsw.arch_loadaddr != NULL) + dest = archsw.arch_loadaddr(LOAD_ELF, ehdr, dest); + else + dest = roundup(dest, PAGE_SIZE); + + /* + * Ok, we think we should handle this. + */ + fp = file_alloc(); + if (fp == NULL) { + printf("elf" __XSTRING(__ELF_WORD_SIZE) "_loadfile: cannot allocate module info\n"); + err = EPERM; + goto out; + } + if (ef.kernel == 1 && multiboot == 0) + setenv("kernelname", filename, 1); + fp->f_name = strdup(filename); + if (multiboot == 0) { + fp->f_type = strdup(ef.kernel ? + __elfN(kerneltype) : __elfN(moduletype)); + } else { + if (multiboot == 1) + fp->f_type = strdup("elf multiboot kernel"); + else + fp->f_type = strdup("elf multiboot2 kernel"); + } + +#ifdef ELF_VERBOSE + if (ef.kernel) + printf("%s entry at 0x%jx\n", filename, (uintmax_t)ehdr->e_entry); +#else + printf("%s ", filename); +#endif + + fp->f_size = __elfN(loadimage)(fp, &ef, dest); + if (fp->f_size == 0 || fp->f_addr == 0) + goto ioerr; + + /* save exec header as metadata */ + file_addmetadata(fp, MODINFOMD_ELFHDR, sizeof(*ehdr), ehdr); + + /* Load OK, return module pointer */ + *result = (struct preloaded_file *)fp; + err = 0; + goto out; + + ioerr: + err = EIO; + oerr: + file_discard(fp); + out: + if (ef.firstpage) + free(ef.firstpage); + if (ef.fd != -1) + close(ef.fd); + return(err); +} + +/* + * With the file (fd) open on the image, and (ehdr) containing + * the Elf header, load the image at (off) + */ +static int +__elfN(loadimage)(struct preloaded_file *fp, elf_file_t ef, u_int64_t off) +{ + int i; + u_int j; + Elf_Ehdr *ehdr; + Elf_Phdr *phdr, *php; + Elf_Shdr *shdr; + char *shstr; + int ret; + vm_offset_t firstaddr; + vm_offset_t lastaddr; + size_t chunk; + ssize_t result; + Elf_Addr ssym, esym; + Elf_Dyn *dp; + Elf_Addr adp; + Elf_Addr ctors; + int ndp; + int symstrindex; + int symtabindex; + Elf_Size size; + u_int fpcopy; + Elf_Sym sym; + Elf_Addr p_start, p_end; + + dp = NULL; + shdr = NULL; + ret = 0; + firstaddr = lastaddr = 0; + ehdr = ef->ehdr; + if (ehdr->e_type == ET_EXEC) { +#if defined(__i386__) || defined(__amd64__) +#if __ELF_WORD_SIZE == 64 + off = - (off & 0xffffffffff000000ull);/* x86_64 relocates after locore */ +#else + off = - (off & 0xff000000u); /* i386 relocates after locore */ +#endif +#elif defined(__powerpc__) + /* + * On the purely virtual memory machines like e500, the kernel is + * linked against its final VA range, which is most often not + * available at the loader stage, but only after kernel initializes + * and completes its VM settings. In such cases we cannot use p_vaddr + * field directly to load ELF segments, but put them at some + * 'load-time' locations. + */ + if (off & 0xf0000000u) { + off = -(off & 0xf0000000u); + /* + * XXX the physical load address should not be hardcoded. Note + * that the Book-E kernel assumes that it's loaded at a 16MB + * boundary for now... + */ + off += 0x01000000; + ehdr->e_entry += off; +#ifdef ELF_VERBOSE + printf("Converted entry 0x%08x\n", ehdr->e_entry); +#endif + } else + off = 0; +#elif defined(__arm__) && !defined(EFI) + /* + * The elf headers in arm kernels specify virtual addresses in all + * header fields, even the ones that should be physical addresses. + * We assume the entry point is in the first page, and masking the page + * offset will leave us with the virtual address the kernel was linked + * at. We subtract that from the load offset, making 'off' into the + * value which, when added to a virtual address in an elf header, + * translates it to a physical address. We do the va->pa conversion on + * the entry point address in the header now, so that later we can + * launch the kernel by just jumping to that address. + * + * When booting from UEFI the copyin and copyout functions handle + * adjusting the location relative to the first virtual address. + * Because of this there is no need to adjust the offset or entry + * point address as these will both be handled by the efi code. + */ + off -= ehdr->e_entry & ~PAGE_MASK; + ehdr->e_entry += off; +#ifdef ELF_VERBOSE + printf("ehdr->e_entry 0x%08x, va<->pa off %llx\n", ehdr->e_entry, off); +#endif +#else + off = 0; /* other archs use direct mapped kernels */ +#endif + } + ef->off = off; + + if (ehdr->e_ident[EI_OSABI] == ELFOSABI_SOLARIS) { + /* use entry address from header */ + fp->f_addr = ehdr->e_entry; + } + + if (ef->kernel) + __elfN(relocation_offset) = off; + + if ((ehdr->e_phoff + ehdr->e_phnum * sizeof(*phdr)) > ef->firstlen) { + printf("elf" __XSTRING(__ELF_WORD_SIZE) "_loadimage: program header not within first page\n"); + goto out; + } + phdr = (Elf_Phdr *)(ef->firstpage + ehdr->e_phoff); + + for (i = 0; i < ehdr->e_phnum; i++) { + /* We want to load PT_LOAD segments only.. */ + if (phdr[i].p_type != PT_LOAD) + continue; + +#ifdef ELF_VERBOSE + if (ehdr->e_ident[EI_OSABI] == ELFOSABI_SOLARIS) { + printf("Segment: 0x%lx@0x%lx -> 0x%lx-0x%lx", + (long)phdr[i].p_filesz, (long)phdr[i].p_offset, + (long)(phdr[i].p_paddr + off), + (long)(phdr[i].p_paddr + off + phdr[i].p_memsz - 1)); + } else { + printf("Segment: 0x%lx@0x%lx -> 0x%lx-0x%lx", + (long)phdr[i].p_filesz, (long)phdr[i].p_offset, + (long)(phdr[i].p_vaddr + off), + (long)(phdr[i].p_vaddr + off + phdr[i].p_memsz - 1)); + } +#else + if ((phdr[i].p_flags & PF_W) == 0) { + printf("text=0x%lx ", (long)phdr[i].p_filesz); + } else { + printf("data=0x%lx", (long)phdr[i].p_filesz); + if (phdr[i].p_filesz < phdr[i].p_memsz) + printf("+0x%lx", (long)(phdr[i].p_memsz -phdr[i].p_filesz)); + printf(" "); + } +#endif + fpcopy = 0; + if (ef->firstlen > phdr[i].p_offset) { + fpcopy = ef->firstlen - phdr[i].p_offset; + if (ehdr->e_ident[EI_OSABI] == ELFOSABI_SOLARIS) { + archsw.arch_copyin(ef->firstpage + phdr[i].p_offset, + phdr[i].p_paddr + off, fpcopy); + } else { + archsw.arch_copyin(ef->firstpage + phdr[i].p_offset, + phdr[i].p_vaddr + off, fpcopy); + } + } + if (phdr[i].p_filesz > fpcopy) { + if (ehdr->e_ident[EI_OSABI] == ELFOSABI_SOLARIS) { + if (kern_pread(ef->fd, phdr[i].p_paddr + off + fpcopy, + phdr[i].p_filesz - fpcopy, + phdr[i].p_offset + fpcopy) != 0) { + printf("\nelf" __XSTRING(__ELF_WORD_SIZE) + "_loadimage: read failed\n"); + goto out; + } + } else { + if (kern_pread(ef->fd, phdr[i].p_vaddr + off + fpcopy, + phdr[i].p_filesz - fpcopy, + phdr[i].p_offset + fpcopy) != 0) { + printf("\nelf" __XSTRING(__ELF_WORD_SIZE) + "_loadimage: read failed\n"); + goto out; + } + } + } + /* clear space from oversized segments; eg: bss */ + if (phdr[i].p_filesz < phdr[i].p_memsz) { +#ifdef ELF_VERBOSE + if (ehdr->e_ident[EI_OSABI] == ELFOSABI_SOLARIS) { + printf(" (bss: 0x%lx-0x%lx)", + (long)(phdr[i].p_paddr + off + phdr[i].p_filesz), + (long)(phdr[i].p_paddr + off + phdr[i].p_memsz - 1)); + } else { + printf(" (bss: 0x%lx-0x%lx)", + (long)(phdr[i].p_vaddr + off + phdr[i].p_filesz), + (long)(phdr[i].p_vaddr + off + phdr[i].p_memsz - 1)); + } +#endif + + if (ehdr->e_ident[EI_OSABI] == ELFOSABI_SOLARIS) { + kern_bzero(phdr[i].p_paddr + off + phdr[i].p_filesz, + phdr[i].p_memsz - phdr[i].p_filesz); + } else { + kern_bzero(phdr[i].p_vaddr + off + phdr[i].p_filesz, + phdr[i].p_memsz - phdr[i].p_filesz); + } + } +#ifdef ELF_VERBOSE + printf("\n"); +#endif + + if (archsw.arch_loadseg != NULL) + archsw.arch_loadseg(ehdr, phdr + i, off); + + if (ehdr->e_ident[EI_OSABI] == ELFOSABI_SOLARIS) { + if (firstaddr == 0 || firstaddr > (phdr[i].p_paddr + off)) + firstaddr = phdr[i].p_paddr + off; + if (lastaddr == 0 || + lastaddr < (phdr[i].p_paddr + off + phdr[i].p_memsz)) + lastaddr = phdr[i].p_paddr + off + phdr[i].p_memsz; + } else { + if (firstaddr == 0 || firstaddr > (phdr[i].p_vaddr + off)) + firstaddr = phdr[i].p_vaddr + off; + if (lastaddr == 0 || + lastaddr < (phdr[i].p_vaddr + off + phdr[i].p_memsz)) + lastaddr = phdr[i].p_vaddr + off + phdr[i].p_memsz; + } + } + lastaddr = roundup(lastaddr, sizeof(long)); + + /* + * Get the section headers. We need this for finding the .ctors + * section as well as for loading any symbols. Both may be hard + * to do if reading from a .gz file as it involves seeking. I + * think the rule is going to have to be that you must strip a + * file to remove symbols before gzipping it. + */ + chunk = ehdr->e_shnum * ehdr->e_shentsize; + if (chunk == 0 || ehdr->e_shoff == 0) + goto nosyms; + shdr = alloc_pread(ef->fd, ehdr->e_shoff, chunk); + if (shdr == NULL) { + printf("\nelf" __XSTRING(__ELF_WORD_SIZE) + "_loadimage: failed to read section headers"); + goto nosyms; + } + file_addmetadata(fp, MODINFOMD_SHDR, chunk, shdr); + + /* + * Read the section string table and look for the .ctors section. + * We need to tell the kernel where it is so that it can call the + * ctors. + */ + chunk = shdr[ehdr->e_shstrndx].sh_size; + if (chunk) { + shstr = alloc_pread(ef->fd, shdr[ehdr->e_shstrndx].sh_offset, chunk); + if (shstr) { + for (i = 0; i < ehdr->e_shnum; i++) { + if (strcmp(shstr + shdr[i].sh_name, ".ctors") != 0) + continue; + ctors = shdr[i].sh_addr; + file_addmetadata(fp, MODINFOMD_CTORS_ADDR, sizeof(ctors), + &ctors); + size = shdr[i].sh_size; + file_addmetadata(fp, MODINFOMD_CTORS_SIZE, sizeof(size), + &size); + break; + } + free(shstr); + } + } + + /* + * Now load any symbols. + */ + symtabindex = -1; + symstrindex = -1; + for (i = 0; i < ehdr->e_shnum; i++) { + if (shdr[i].sh_type != SHT_SYMTAB) + continue; + for (j = 0; j < ehdr->e_phnum; j++) { + if (phdr[j].p_type != PT_LOAD) + continue; + if (shdr[i].sh_offset >= phdr[j].p_offset && + (shdr[i].sh_offset + shdr[i].sh_size <= + phdr[j].p_offset + phdr[j].p_filesz)) { + shdr[i].sh_offset = 0; + shdr[i].sh_size = 0; + break; + } + } + if (shdr[i].sh_offset == 0 || shdr[i].sh_size == 0) + continue; /* alread loaded in a PT_LOAD above */ + /* Save it for loading below */ + symtabindex = i; + symstrindex = shdr[i].sh_link; + } + if (symtabindex < 0 || symstrindex < 0) + goto nosyms; + + /* Ok, committed to a load. */ +#ifndef ELF_VERBOSE + printf("syms=["); +#endif + ssym = lastaddr; + for (i = symtabindex; i >= 0; i = symstrindex) { +#ifdef ELF_VERBOSE + char *secname; + + switch(shdr[i].sh_type) { + case SHT_SYMTAB: /* Symbol table */ + secname = "symtab"; + break; + case SHT_STRTAB: /* String table */ + secname = "strtab"; + break; + default: + secname = "WHOA!!"; + break; + } +#endif + + size = shdr[i].sh_size; + archsw.arch_copyin(&size, lastaddr, sizeof(size)); + lastaddr += sizeof(size); + +#ifdef ELF_VERBOSE + printf("\n%s: 0x%jx@0x%jx -> 0x%jx-0x%jx", secname, + (uintmax_t)shdr[i].sh_size, (uintmax_t)shdr[i].sh_offset, + (uintmax_t)lastaddr, (uintmax_t)(lastaddr + shdr[i].sh_size)); +#else + if (i == symstrindex) + printf("+"); + printf("0x%lx+0x%lx", (long)sizeof(size), (long)size); +#endif + + if (lseek(ef->fd, (off_t)shdr[i].sh_offset, SEEK_SET) == -1) { + printf("\nelf" __XSTRING(__ELF_WORD_SIZE) "_loadimage: could not seek for symbols - skipped!"); + lastaddr = ssym; + ssym = 0; + goto nosyms; + } + result = archsw.arch_readin(ef->fd, lastaddr, shdr[i].sh_size); + if (result < 0 || (size_t)result != shdr[i].sh_size) { + printf("\nelf" __XSTRING(__ELF_WORD_SIZE) "_loadimage: could not read symbols - skipped! (%ju != %ju)", (uintmax_t)result, + (uintmax_t)shdr[i].sh_size); + lastaddr = ssym; + ssym = 0; + goto nosyms; + } + /* Reset offsets relative to ssym */ + lastaddr += shdr[i].sh_size; + lastaddr = roundup(lastaddr, sizeof(size)); + if (i == symtabindex) + symtabindex = -1; + else if (i == symstrindex) + symstrindex = -1; + } + esym = lastaddr; +#ifndef ELF_VERBOSE + printf("]"); +#endif + + file_addmetadata(fp, MODINFOMD_SSYM, sizeof(ssym), &ssym); + file_addmetadata(fp, MODINFOMD_ESYM, sizeof(esym), &esym); + +nosyms: + printf("\n"); + + ret = lastaddr - firstaddr; + if (ehdr->e_ident[EI_OSABI] != ELFOSABI_SOLARIS) + fp->f_addr = firstaddr; + + php = NULL; + for (i = 0; i < ehdr->e_phnum; i++) { + if (phdr[i].p_type == PT_DYNAMIC) { + php = phdr + i; + adp = php->p_vaddr; + file_addmetadata(fp, MODINFOMD_DYNAMIC, sizeof(adp), &adp); + break; + } + } + + if (php == NULL) /* this is bad, we cannot get to symbols or _DYNAMIC */ + goto out; + + ndp = php->p_filesz / sizeof(Elf_Dyn); + if (ndp == 0) + goto out; + dp = malloc(php->p_filesz); + if (dp == NULL) + goto out; + if (ehdr->e_ident[EI_OSABI] == ELFOSABI_SOLARIS) + archsw.arch_copyout(php->p_paddr + off, dp, php->p_filesz); + else + archsw.arch_copyout(php->p_vaddr + off, dp, php->p_filesz); + + ef->strsz = 0; + for (i = 0; i < ndp; i++) { + if (dp[i].d_tag == 0) + break; + switch (dp[i].d_tag) { + case DT_HASH: + ef->hashtab = (Elf_Hashelt*)(uintptr_t)(dp[i].d_un.d_ptr + off); + break; + case DT_STRTAB: + ef->strtab = (char *)(uintptr_t)(dp[i].d_un.d_ptr + off); + break; + case DT_STRSZ: + ef->strsz = dp[i].d_un.d_val; + break; + case DT_SYMTAB: + ef->symtab = (Elf_Sym*)(uintptr_t)(dp[i].d_un.d_ptr + off); + break; + case DT_REL: + ef->rel = (Elf_Rel *)(uintptr_t)(dp[i].d_un.d_ptr + off); + break; + case DT_RELSZ: + ef->relsz = dp[i].d_un.d_val; + break; + case DT_RELA: + ef->rela = (Elf_Rela *)(uintptr_t)(dp[i].d_un.d_ptr + off); + break; + case DT_RELASZ: + ef->relasz = dp[i].d_un.d_val; + break; + default: + break; + } + } + if (ef->hashtab == NULL || ef->symtab == NULL || + ef->strtab == NULL || ef->strsz == 0) + goto out; + COPYOUT(ef->hashtab, &ef->nbuckets, sizeof(ef->nbuckets)); + COPYOUT(ef->hashtab + 1, &ef->nchains, sizeof(ef->nchains)); + ef->buckets = ef->hashtab + 2; + ef->chains = ef->buckets + ef->nbuckets; + + if (__elfN(lookup_symbol)(fp, ef, "__start_set_modmetadata_set", &sym) != 0) + return 0; + p_start = sym.st_value + ef->off; + if (__elfN(lookup_symbol)(fp, ef, "__stop_set_modmetadata_set", &sym) != 0) + return ENOENT; + p_end = sym.st_value + ef->off; + + if (__elfN(parse_modmetadata)(fp, ef, p_start, p_end) == 0) + goto out; + + if (ef->kernel) /* kernel must not depend on anything */ + goto out; + +out: + if (dp) + free(dp); + if (shdr) + free(shdr); + return ret; +} + +static char invalid_name[] = "bad"; + +char * +fake_modname(const char *name) +{ + const char *sp, *ep; + char *fp; + size_t len; + + sp = strrchr(name, '/'); + if (sp) + sp++; + else + sp = name; + ep = strrchr(name, '.'); + if (ep) { + if (ep == name) { + sp = invalid_name; + ep = invalid_name + sizeof(invalid_name) - 1; + } + } else + ep = name + strlen(name); + len = ep - sp; + fp = malloc(len + 1); + if (fp == NULL) + return NULL; + memcpy(fp, sp, len); + fp[len] = '\0'; + return fp; +} + +#if (defined(__i386__) || defined(__powerpc__)) && __ELF_WORD_SIZE == 64 +struct mod_metadata64 { + int md_version; /* structure version MDTV_* */ + int md_type; /* type of entry MDT_* */ + u_int64_t md_data; /* specific data */ + u_int64_t md_cval; /* common string label */ +}; +#endif +#if defined(__amd64__) && __ELF_WORD_SIZE == 32 +struct mod_metadata32 { + int md_version; /* structure version MDTV_* */ + int md_type; /* type of entry MDT_* */ + u_int32_t md_data; /* specific data */ + u_int32_t md_cval; /* common string label */ +}; +#endif + +int +__elfN(load_modmetadata)(struct preloaded_file *fp, u_int64_t dest) +{ + struct elf_file ef; + int err, i, j; + Elf_Shdr *sh_meta, *shdr = NULL; + Elf_Shdr *sh_data[2]; + char *shstrtab = NULL; + size_t size; + Elf_Addr p_start, p_end; + + bzero(&ef, sizeof(struct elf_file)); + ef.fd = -1; + + err = __elfN(load_elf_header)(fp->f_name, &ef); + if (err != 0) + goto out; + + if (ef.kernel == 1 || ef.ehdr->e_type == ET_EXEC) { + ef.kernel = 1; + } else if (ef.ehdr->e_type != ET_DYN) { + err = EFTYPE; + goto out; + } + + size = ef.ehdr->e_shnum * ef.ehdr->e_shentsize; + shdr = alloc_pread(ef.fd, ef.ehdr->e_shoff, size); + if (shdr == NULL) { + err = ENOMEM; + goto out; + } + + /* Load shstrtab. */ + shstrtab = alloc_pread(ef.fd, shdr[ef.ehdr->e_shstrndx].sh_offset, + shdr[ef.ehdr->e_shstrndx].sh_size); + if (shstrtab == NULL) { + printf("\nelf" __XSTRING(__ELF_WORD_SIZE) + "load_modmetadata: unable to load shstrtab\n"); + err = EFTYPE; + goto out; + } + + /* Find set_modmetadata_set and data sections. */ + sh_data[0] = sh_data[1] = sh_meta = NULL; + for (i = 0, j = 0; i < ef.ehdr->e_shnum; i++) { + if (strcmp(&shstrtab[shdr[i].sh_name], + "set_modmetadata_set") == 0) { + sh_meta = &shdr[i]; + } + if ((strcmp(&shstrtab[shdr[i].sh_name], ".data") == 0) || + (strcmp(&shstrtab[shdr[i].sh_name], ".rodata") == 0)) { + sh_data[j++] = &shdr[i]; + } + } + if (sh_meta == NULL || sh_data[0] == NULL || sh_data[1] == NULL) { + printf("\nelf" __XSTRING(__ELF_WORD_SIZE) + "load_modmetadata: unable to find set_modmetadata_set or data sections\n"); + err = EFTYPE; + goto out; + } + + /* Load set_modmetadata_set into memory */ + err = kern_pread(ef.fd, dest, sh_meta->sh_size, sh_meta->sh_offset); + if (err != 0) { + printf("\nelf" __XSTRING(__ELF_WORD_SIZE) + "load_modmetadata: unable to load set_modmetadata_set: %d\n", err); + goto out; + } + p_start = dest; + p_end = dest + sh_meta->sh_size; + dest += sh_meta->sh_size; + + /* Load data sections into memory. */ + err = kern_pread(ef.fd, dest, sh_data[0]->sh_size, + sh_data[0]->sh_offset); + if (err != 0) { + printf("\nelf" __XSTRING(__ELF_WORD_SIZE) + "load_modmetadata: unable to load data: %d\n", err); + goto out; + } + + /* + * We have to increment the dest, so that the offset is the same into + * both the .rodata and .data sections. + */ + ef.off = -(sh_data[0]->sh_addr - dest); + dest += (sh_data[1]->sh_addr - sh_data[0]->sh_addr); + + err = kern_pread(ef.fd, dest, sh_data[1]->sh_size, + sh_data[1]->sh_offset); + if (err != 0) { + printf("\nelf" __XSTRING(__ELF_WORD_SIZE) + "load_modmetadata: unable to load data: %d\n", err); + goto out; + } + + err = __elfN(parse_modmetadata)(fp, &ef, p_start, p_end); + if (err != 0) { + printf("\nelf" __XSTRING(__ELF_WORD_SIZE) + "load_modmetadata: unable to parse metadata: %d\n", err); + goto out; + } + +out: + if (shstrtab != NULL) + free(shstrtab); + if (shdr != NULL) + free(shdr); + if (ef.firstpage != NULL) + free(ef.firstpage); + if (ef.fd != -1) + close(ef.fd); + return (err); +} + +int +__elfN(parse_modmetadata)(struct preloaded_file *fp, elf_file_t ef, + Elf_Addr p_start, Elf_Addr p_end) +{ + struct mod_metadata md; +#if (defined(__i386__) || defined(__powerpc__)) && __ELF_WORD_SIZE == 64 + struct mod_metadata64 md64; +#elif defined(__amd64__) && __ELF_WORD_SIZE == 32 + struct mod_metadata32 md32; +#endif + struct mod_depend *mdepend; + struct mod_version mver; + char *s; + int error, modcnt, minfolen; + Elf_Addr v, p; + + modcnt = 0; + p = p_start; + while (p < p_end) { + COPYOUT(p, &v, sizeof(v)); + error = __elfN(reloc_ptr)(fp, ef, p, &v, sizeof(v)); + if (error == EOPNOTSUPP) + v += ef->off; + else if (error != 0) + return (error); +#if (defined(__i386__) || defined(__powerpc__)) && __ELF_WORD_SIZE == 64 + COPYOUT(v, &md64, sizeof(md64)); + error = __elfN(reloc_ptr)(fp, ef, v, &md64, sizeof(md64)); + if (error == EOPNOTSUPP) { + md64.md_cval += ef->off; + md64.md_data += ef->off; + } else if (error != 0) + return (error); + md.md_version = md64.md_version; + md.md_type = md64.md_type; + md.md_cval = (const char *)(uintptr_t)md64.md_cval; + md.md_data = (void *)(uintptr_t)md64.md_data; +#elif defined(__amd64__) && __ELF_WORD_SIZE == 32 + COPYOUT(v, &md32, sizeof(md32)); + error = __elfN(reloc_ptr)(fp, ef, v, &md32, sizeof(md32)); + if (error == EOPNOTSUPP) { + md32.md_cval += ef->off; + md32.md_data += ef->off; + } else if (error != 0) + return (error); + md.md_version = md32.md_version; + md.md_type = md32.md_type; + md.md_cval = (const char *)(uintptr_t)md32.md_cval; + md.md_data = (void *)(uintptr_t)md32.md_data; +#else + COPYOUT(v, &md, sizeof(md)); + error = __elfN(reloc_ptr)(fp, ef, v, &md, sizeof(md)); + if (error == EOPNOTSUPP) { + md.md_cval += ef->off; + md.md_data = (void *)((uintptr_t)md.md_data + (uintptr_t)ef->off); + } else if (error != 0) + return (error); +#endif + p += sizeof(Elf_Addr); + switch(md.md_type) { + case MDT_DEPEND: + if (ef->kernel) /* kernel must not depend on anything */ + break; + s = strdupout((vm_offset_t)md.md_cval); + minfolen = sizeof(*mdepend) + strlen(s) + 1; + mdepend = malloc(minfolen); + if (mdepend == NULL) + return ENOMEM; + COPYOUT((vm_offset_t)md.md_data, mdepend, sizeof(*mdepend)); + strcpy((char*)(mdepend + 1), s); + free(s); + file_addmetadata(fp, MODINFOMD_DEPLIST, minfolen, mdepend); + free(mdepend); + break; + case MDT_VERSION: + s = strdupout((vm_offset_t)md.md_cval); + COPYOUT((vm_offset_t)md.md_data, &mver, sizeof(mver)); + file_addmodule(fp, s, mver.mv_version, NULL); + free(s); + modcnt++; + break; + } + } + if (modcnt == 0) { + s = fake_modname(fp->f_name); + file_addmodule(fp, s, 1, NULL); + free(s); + } + return 0; +} + +static unsigned long +elf_hash(const char *name) +{ + const unsigned char *p = (const unsigned char *) name; + unsigned long h = 0; + unsigned long g; + + while (*p != '\0') { + h = (h << 4) + *p++; + if ((g = h & 0xf0000000) != 0) + h ^= g >> 24; + h &= ~g; + } + return h; +} + +static const char __elfN(bad_symtable)[] = "elf" __XSTRING(__ELF_WORD_SIZE) "_lookup_symbol: corrupt symbol table\n"; +int +__elfN(lookup_symbol)(struct preloaded_file *fp __unused, elf_file_t ef, + const char* name, Elf_Sym *symp) +{ + Elf_Hashelt symnum; + Elf_Sym sym; + char *strp; + unsigned long hash; + + hash = elf_hash(name); + COPYOUT(&ef->buckets[hash % ef->nbuckets], &symnum, sizeof(symnum)); + + while (symnum != STN_UNDEF) { + if (symnum >= ef->nchains) { + printf(__elfN(bad_symtable)); + return ENOENT; + } + + COPYOUT(ef->symtab + symnum, &sym, sizeof(sym)); + if (sym.st_name == 0) { + printf(__elfN(bad_symtable)); + return ENOENT; + } + + strp = strdupout((vm_offset_t)(ef->strtab + sym.st_name)); + if (strcmp(name, strp) == 0) { + free(strp); + if (sym.st_shndx != SHN_UNDEF || + (sym.st_value != 0 && + ELF_ST_TYPE(sym.st_info) == STT_FUNC)) { + *symp = sym; + return 0; + } + return ENOENT; + } + free(strp); + COPYOUT(&ef->chains[symnum], &symnum, sizeof(symnum)); + } + return ENOENT; +} + +/* + * Apply any intra-module relocations to the value. p is the load address + * of the value and val/len is the value to be modified. This does NOT modify + * the image in-place, because this is done by kern_linker later on. + * + * Returns EOPNOTSUPP if no relocation method is supplied. + */ +static int +__elfN(reloc_ptr)(struct preloaded_file *mp, elf_file_t ef, + Elf_Addr p, void *val, size_t len) +{ + size_t n; + Elf_Rela a; + Elf_Rel r; + int error; + + (void)mp; + /* + * The kernel is already relocated, but we still want to apply + * offset adjustments. + */ + if (ef->kernel) + return (EOPNOTSUPP); + + for (n = 0; n < ef->relsz / sizeof(r); n++) { + COPYOUT(ef->rel + n, &r, sizeof(r)); + + error = __elfN(reloc)(ef, __elfN(symaddr), &r, ELF_RELOC_REL, + ef->off, p, val, len); + if (error != 0) + return (error); + } + for (n = 0; n < ef->relasz / sizeof(a); n++) { + COPYOUT(ef->rela + n, &a, sizeof(a)); + + error = __elfN(reloc)(ef, __elfN(symaddr), &a, ELF_RELOC_RELA, + ef->off, p, val, len); + if (error != 0) + return (error); + } + + return (0); +} + +static Elf_Addr +__elfN(symaddr)(struct elf_file *ef __unused, Elf_Size symidx __unused) +{ + /* Symbol lookup by index not required here. */ + return (0); +} diff --git a/usr/src/boot/common/load_elf32.c b/usr/src/boot/common/load_elf32.c new file mode 100644 index 0000000000..0c9f460d48 --- /dev/null +++ b/usr/src/boot/common/load_elf32.c @@ -0,0 +1,7 @@ +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#define __ELF_WORD_SIZE 32 +#define _MACHINE_ELF_WANT_32BIT + +#include "load_elf.c" diff --git a/usr/src/boot/common/load_elf32_obj.c b/usr/src/boot/common/load_elf32_obj.c new file mode 100644 index 0000000000..94b0896188 --- /dev/null +++ b/usr/src/boot/common/load_elf32_obj.c @@ -0,0 +1,7 @@ +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#define __ELF_WORD_SIZE 32 +#define _MACHINE_ELF_WANT_32BIT + +#include "load_elf_obj.c" diff --git a/usr/src/boot/common/load_elf64.c b/usr/src/boot/common/load_elf64.c new file mode 100644 index 0000000000..c29e8e3596 --- /dev/null +++ b/usr/src/boot/common/load_elf64.c @@ -0,0 +1,6 @@ +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#define __ELF_WORD_SIZE 64 + +#include "load_elf.c" diff --git a/usr/src/boot/common/load_elf64_obj.c b/usr/src/boot/common/load_elf64_obj.c new file mode 100644 index 0000000000..3c9371ba01 --- /dev/null +++ b/usr/src/boot/common/load_elf64_obj.c @@ -0,0 +1,6 @@ +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#define __ELF_WORD_SIZE 64 + +#include "load_elf_obj.c" diff --git a/usr/src/boot/common/load_elf_obj.c b/usr/src/boot/common/load_elf_obj.c new file mode 100644 index 0000000000..f32388e170 --- /dev/null +++ b/usr/src/boot/common/load_elf_obj.c @@ -0,0 +1,535 @@ +/*- + * Copyright (c) 2004 Ian Dowse <iedowse@freebsd.org> + * Copyright (c) 1998 Michael Smith <msmith@freebsd.org> + * Copyright (c) 1998 Peter Wemm <peter@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 <sys/exec.h> +#include <sys/linker.h> +#include <sys/module.h> +#include <inttypes.h> +#include <string.h> +#include <machine/elf.h> +#include <stand.h> +#define FREEBSD_ELF +#include <link.h> + +#include "bootstrap.h" + +#define COPYOUT(s,d,l) archsw.arch_copyout((vm_offset_t)(s), d, l) + +#if defined(__i386__) && __ELF_WORD_SIZE == 64 +#undef ELF_TARG_CLASS +#undef ELF_TARG_MACH +#define ELF_TARG_CLASS ELFCLASS64 +#define ELF_TARG_MACH EM_X86_64 +#endif + +typedef struct elf_file { + Elf_Ehdr hdr; + Elf_Shdr *e_shdr; + + int symtabindex; /* Index of symbol table */ + int shstrindex; /* Index of section name string table */ + + int fd; + vm_offset_t off; +} *elf_file_t; + +static int __elfN(obj_loadimage)(struct preloaded_file *mp, elf_file_t ef, + u_int64_t loadaddr); +static int __elfN(obj_lookup_set)(struct preloaded_file *mp, elf_file_t ef, + const char *name, Elf_Addr *startp, Elf_Addr *stopp, int *countp); +static int __elfN(obj_reloc_ptr)(struct preloaded_file *mp, elf_file_t ef, + Elf_Addr p, void *val, size_t len); +static int __elfN(obj_parse_modmetadata)(struct preloaded_file *mp, + elf_file_t ef); +static Elf_Addr __elfN(obj_symaddr)(struct elf_file *ef, Elf_Size symidx); + +const char *__elfN(obj_kerneltype) = "elf kernel"; +const char *__elfN(obj_moduletype) = "elf obj module"; + +/* + * Attempt to load the file (file) as an ELF module. It will be stored at + * (dest), and a pointer to a module structure describing the loaded object + * will be saved in (result). + */ +int +__elfN(obj_loadfile)(char *filename, u_int64_t dest, + struct preloaded_file **result) +{ + struct preloaded_file *fp, *kfp; + struct elf_file ef; + Elf_Ehdr *hdr; + int err; + ssize_t bytes_read; + + fp = NULL; + bzero(&ef, sizeof(struct elf_file)); + + /* + * Open the image, read and validate the ELF header + */ + if (filename == NULL) /* can't handle nameless */ + return(EFTYPE); + if ((ef.fd = open(filename, O_RDONLY)) == -1) + return(errno); + + hdr = &ef.hdr; + bytes_read = read(ef.fd, hdr, sizeof(*hdr)); + if (bytes_read != sizeof(*hdr)) { + err = EFTYPE; /* could be EIO, but may be small file */ + goto oerr; + } + + /* Is it ELF? */ + if (!IS_ELF(*hdr)) { + err = EFTYPE; + goto oerr; + } + if (hdr->e_ident[EI_CLASS] != ELF_TARG_CLASS || /* Layout ? */ + hdr->e_ident[EI_DATA] != ELF_TARG_DATA || + hdr->e_ident[EI_VERSION] != EV_CURRENT || /* Version ? */ + hdr->e_version != EV_CURRENT || + hdr->e_machine != ELF_TARG_MACH || /* Machine ? */ + hdr->e_type != ET_REL) { + err = EFTYPE; + goto oerr; + } + + if (hdr->e_shnum * hdr->e_shentsize == 0 || hdr->e_shoff == 0 || + hdr->e_shentsize != sizeof(Elf_Shdr)) { + err = EFTYPE; + goto oerr; + } + + kfp = file_findfile(NULL, __elfN(obj_kerneltype)); + if (kfp == NULL) { + printf("elf" __XSTRING(__ELF_WORD_SIZE) + "_obj_loadfile: can't load module before kernel\n"); + err = EPERM; + goto oerr; + } + + if (archsw.arch_loadaddr != NULL) + dest = archsw.arch_loadaddr(LOAD_ELF, hdr, dest); + else + dest = roundup(dest, PAGE_SIZE); + + /* + * Ok, we think we should handle this. + */ + fp = file_alloc(); + if (fp == NULL) { + printf("elf" __XSTRING(__ELF_WORD_SIZE) + "_obj_loadfile: cannot allocate module info\n"); + err = EPERM; + goto out; + } + fp->f_name = strdup(filename); + fp->f_type = strdup(__elfN(obj_moduletype)); + + printf("%s ", filename); + + fp->f_size = __elfN(obj_loadimage)(fp, &ef, dest); + if (fp->f_size == 0 || fp->f_addr == 0) + goto ioerr; + + /* save exec header as metadata */ + file_addmetadata(fp, MODINFOMD_ELFHDR, sizeof(*hdr), hdr); + + /* Load OK, return module pointer */ + *result = (struct preloaded_file *)fp; + err = 0; + goto out; + +ioerr: + err = EIO; +oerr: + file_discard(fp); +out: + close(ef.fd); + if (ef.e_shdr != NULL) + free(ef.e_shdr); + + return(err); +} + +/* + * With the file (fd) open on the image, and (ehdr) containing + * the Elf header, load the image at (off) + */ +static int +__elfN(obj_loadimage)(struct preloaded_file *fp, elf_file_t ef, u_int64_t off) +{ + Elf_Ehdr *hdr; + Elf_Shdr *shdr, *cshdr, *lshdr; + vm_offset_t firstaddr, lastaddr; + int i, nsym, res, ret, shdrbytes, symstrindex; + + ret = 0; + firstaddr = lastaddr = (vm_offset_t)off; + hdr = &ef->hdr; + ef->off = (vm_offset_t)off; + + /* Read in the section headers. */ + shdrbytes = hdr->e_shnum * hdr->e_shentsize; + shdr = alloc_pread(ef->fd, (off_t)hdr->e_shoff, shdrbytes); + if (shdr == NULL) { + printf("\nelf" __XSTRING(__ELF_WORD_SIZE) + "_obj_loadimage: read section headers failed\n"); + goto out; + } + ef->e_shdr = shdr; + + /* + * Decide where to load everything, but don't read it yet. + * We store the load address as a non-zero sh_addr value. + * Start with the code/data and bss. + */ + for (i = 0; i < hdr->e_shnum; i++) + shdr[i].sh_addr = 0; + for (i = 0; i < hdr->e_shnum; i++) { + if (shdr[i].sh_size == 0) + continue; + switch (shdr[i].sh_type) { + case SHT_PROGBITS: + case SHT_NOBITS: + lastaddr = roundup(lastaddr, shdr[i].sh_addralign); + shdr[i].sh_addr = (Elf_Addr)lastaddr; + lastaddr += shdr[i].sh_size; + break; + } + } + + /* Symbols. */ + nsym = 0; + for (i = 0; i < hdr->e_shnum; i++) { + switch (shdr[i].sh_type) { + case SHT_SYMTAB: + nsym++; + ef->symtabindex = i; + shdr[i].sh_addr = (Elf_Addr)lastaddr; + lastaddr += shdr[i].sh_size; + break; + } + } + if (nsym != 1) { + printf("\nelf" __XSTRING(__ELF_WORD_SIZE) + "_obj_loadimage: file has no valid symbol table\n"); + goto out; + } + lastaddr = roundup(lastaddr, shdr[ef->symtabindex].sh_addralign); + shdr[ef->symtabindex].sh_addr = (Elf_Addr)lastaddr; + lastaddr += shdr[ef->symtabindex].sh_size; + + symstrindex = shdr[ef->symtabindex].sh_link; + if (symstrindex < 0 || symstrindex >= hdr->e_shnum || + shdr[symstrindex].sh_type != SHT_STRTAB) { + printf("\nelf" __XSTRING(__ELF_WORD_SIZE) + "_obj_loadimage: file has invalid symbol strings\n"); + goto out; + } + lastaddr = roundup(lastaddr, shdr[symstrindex].sh_addralign); + shdr[symstrindex].sh_addr = (Elf_Addr)lastaddr; + lastaddr += shdr[symstrindex].sh_size; + + /* Section names. */ + if (hdr->e_shstrndx == 0 || hdr->e_shstrndx >= hdr->e_shnum || + shdr[hdr->e_shstrndx].sh_type != SHT_STRTAB) { + printf("\nelf" __XSTRING(__ELF_WORD_SIZE) + "_obj_loadimage: file has no section names\n"); + goto out; + } + ef->shstrindex = hdr->e_shstrndx; + lastaddr = roundup(lastaddr, shdr[ef->shstrindex].sh_addralign); + shdr[ef->shstrindex].sh_addr = (Elf_Addr)lastaddr; + lastaddr += shdr[ef->shstrindex].sh_size; + + /* Relocation tables. */ + for (i = 0; i < hdr->e_shnum; i++) { + switch (shdr[i].sh_type) { + case SHT_REL: + case SHT_RELA: + lastaddr = roundup(lastaddr, shdr[i].sh_addralign); + shdr[i].sh_addr = (Elf_Addr)lastaddr; + lastaddr += shdr[i].sh_size; + break; + } + } + + /* Clear the whole area, including bss regions. */ + kern_bzero(firstaddr, lastaddr - firstaddr); + + /* Figure section with the lowest file offset we haven't loaded yet. */ + for (cshdr = NULL; /* none */; /* none */) + { + /* + * Find next section to load. The complexity of this loop is + * O(n^2), but with the number of sections being typically + * small, we do not care. + */ + lshdr = cshdr; + + for (i = 0; i < hdr->e_shnum; i++) { + if (shdr[i].sh_addr == 0 || + shdr[i].sh_type == SHT_NOBITS) + continue; + /* Skip sections that were loaded already. */ + if (lshdr != NULL && + lshdr->sh_offset >= shdr[i].sh_offset) + continue; + /* Find section with smallest offset. */ + if (cshdr == lshdr || + cshdr->sh_offset > shdr[i].sh_offset) + cshdr = &shdr[i]; + } + + if (cshdr == lshdr) + break; + + if (kern_pread(ef->fd, (vm_offset_t)cshdr->sh_addr, + cshdr->sh_size, (off_t)cshdr->sh_offset) != 0) { + printf("\nelf" __XSTRING(__ELF_WORD_SIZE) + "_obj_loadimage: read failed\n"); + goto out; + } + } + + file_addmetadata(fp, MODINFOMD_SHDR, shdrbytes, shdr); + + res = __elfN(obj_parse_modmetadata)(fp, ef); + if (res != 0) + goto out; + + ret = lastaddr - firstaddr; + fp->f_addr = firstaddr; + + printf("size 0x%lx at 0x%lx", (u_long)ret, (u_long)firstaddr); + +out: + printf("\n"); + return ret; +} + +#if defined(__i386__) && __ELF_WORD_SIZE == 64 +struct mod_metadata64 { + int md_version; /* structure version MDTV_* */ + int md_type; /* type of entry MDT_* */ + u_int64_t md_data; /* specific data */ + u_int64_t md_cval; /* common string label */ +}; +#endif + +int +__elfN(obj_parse_modmetadata)(struct preloaded_file *fp, elf_file_t ef) +{ + struct mod_metadata md; +#if defined(__i386__) && __ELF_WORD_SIZE == 64 + struct mod_metadata64 md64; +#endif + struct mod_depend *mdepend; + struct mod_version mver; + char *s; + int error, modcnt, minfolen; + Elf_Addr v, p, p_stop; + + if (__elfN(obj_lookup_set)(fp, ef, "modmetadata_set", &p, &p_stop, + &modcnt) != 0) + return 0; + + modcnt = 0; + while (p < p_stop) { + COPYOUT(p, &v, sizeof(v)); + error = __elfN(obj_reloc_ptr)(fp, ef, p, &v, sizeof(v)); + if (error != 0) + return (error); +#if defined(__i386__) && __ELF_WORD_SIZE == 64 + COPYOUT(v, &md64, sizeof(md64)); + error = __elfN(obj_reloc_ptr)(fp, ef, v, &md64, sizeof(md64)); + if (error != 0) + return (error); + md.md_version = md64.md_version; + md.md_type = md64.md_type; + md.md_cval = (const char *)(uintptr_t)md64.md_cval; + md.md_data = (void *)(uintptr_t)md64.md_data; +#else + COPYOUT(v, &md, sizeof(md)); + error = __elfN(obj_reloc_ptr)(fp, ef, v, &md, sizeof(md)); + if (error != 0) + return (error); +#endif + p += sizeof(Elf_Addr); + switch(md.md_type) { + case MDT_DEPEND: + s = strdupout((vm_offset_t)md.md_cval); + minfolen = sizeof(*mdepend) + strlen(s) + 1; + mdepend = malloc(minfolen); + if (mdepend == NULL) + return ENOMEM; + COPYOUT((vm_offset_t)md.md_data, mdepend, + sizeof(*mdepend)); + strcpy((char*)(mdepend + 1), s); + free(s); + file_addmetadata(fp, MODINFOMD_DEPLIST, minfolen, + mdepend); + free(mdepend); + break; + case MDT_VERSION: + s = strdupout((vm_offset_t)md.md_cval); + COPYOUT((vm_offset_t)md.md_data, &mver, sizeof(mver)); + file_addmodule(fp, s, mver.mv_version, NULL); + free(s); + modcnt++; + break; + case MDT_MODULE: + case MDT_PNP_INFO: + break; + default: + printf("unknown type %d\n", md.md_type); + break; + } + } + return 0; +} + +static int +__elfN(obj_lookup_set)(struct preloaded_file *fp __unused, elf_file_t ef, + const char* name, Elf_Addr *startp, Elf_Addr *stopp, int *countp) +{ + Elf_Ehdr *hdr; + Elf_Shdr *shdr; + char *p; + vm_offset_t shstrtab; + int i; + + hdr = &ef->hdr; + shdr = ef->e_shdr; + shstrtab = shdr[ef->shstrindex].sh_addr; + + for (i = 0; i < hdr->e_shnum; i++) { + if (shdr[i].sh_type != SHT_PROGBITS) + continue; + if (shdr[i].sh_name == 0) + continue; + p = strdupout(shstrtab + shdr[i].sh_name); + if (strncmp(p, "set_", 4) == 0 && strcmp(p + 4, name) == 0) { + *startp = shdr[i].sh_addr; + *stopp = shdr[i].sh_addr + shdr[i].sh_size; + *countp = (*stopp - *startp) / sizeof(Elf_Addr); + free(p); + return (0); + } + free(p); + } + + return (ESRCH); +} + +/* + * Apply any intra-module relocations to the value. p is the load address + * of the value and val/len is the value to be modified. This does NOT modify + * the image in-place, because this is done by kern_linker later on. + */ +static int +__elfN(obj_reloc_ptr)(struct preloaded_file *mp, elf_file_t ef, Elf_Addr p, + void *val, size_t len) +{ + Elf_Ehdr *hdr; + Elf_Shdr *shdr; + Elf_Addr off = p; + Elf_Addr base; + Elf_Rela a, *abase; + Elf_Rel r, *rbase; + int error, i, j, nrel, nrela; + + (void)mp; + hdr = &ef->hdr; + shdr = ef->e_shdr; + + for (i = 0; i < hdr->e_shnum; i++) { + if (shdr[i].sh_type != SHT_RELA && shdr[i].sh_type != SHT_REL) + continue; + base = shdr[shdr[i].sh_info].sh_addr; + if (base == 0 || shdr[i].sh_addr == 0) + continue; + if (off < base || off + len > base + + shdr[shdr[i].sh_info].sh_size) + continue; + + switch (shdr[i].sh_type) { + case SHT_RELA: + abase = (Elf_Rela *)(intptr_t)shdr[i].sh_addr; + + nrela = shdr[i].sh_size / sizeof(Elf_Rela); + for (j = 0; j < nrela; j++) { + COPYOUT(abase + j, &a, sizeof(a)); + + error = __elfN(reloc)(ef, __elfN(obj_symaddr), + &a, ELF_RELOC_RELA, base, off, val, len); + if (error != 0) + return (error); + } + break; + case SHT_REL: + rbase = (Elf_Rel *)(intptr_t)shdr[i].sh_addr; + + nrel = shdr[i].sh_size / sizeof(Elf_Rel); + for (j = 0; j < nrel; j++) { + COPYOUT(rbase + j, &r, sizeof(r)); + + error = __elfN(reloc)(ef, __elfN(obj_symaddr), + &r, ELF_RELOC_REL, base, off, val, len); + if (error != 0) + return (error); + } + break; + } + } + return (0); +} + +/* Look up the address of a specified symbol. */ +static Elf_Addr +__elfN(obj_symaddr)(struct elf_file *ef, Elf_Size symidx) +{ + Elf_Sym sym; + Elf_Addr base; + + if (symidx >= ef->e_shdr[ef->symtabindex].sh_size / sizeof(Elf_Sym)) + return (0); + COPYOUT(ef->e_shdr[ef->symtabindex].sh_addr + symidx * sizeof(Elf_Sym), + &sym, sizeof(sym)); + if (sym.st_shndx == SHN_UNDEF || sym.st_shndx >= ef->hdr.e_shnum) + return (0); + base = ef->e_shdr[sym.st_shndx].sh_addr; + if (base == 0) + return (0); + return (base + sym.st_value); +} diff --git a/usr/src/boot/common/ls.c b/usr/src/boot/common/ls.c new file mode 100644 index 0000000000..1ca760c7ab --- /dev/null +++ b/usr/src/boot/common/ls.c @@ -0,0 +1,212 @@ +/* + * $NetBSD: ls.c,v 1.3 1997/06/13 13:48:47 drochner Exp $ + */ + +/* + * Copyright (c) 1993 + * The Regents of the University of California. All rights reserved. + * Copyright (c) 1996 + * Matthias Drochner. 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. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS 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> + +#include <sys/param.h> +#include <ufs/ufs/dinode.h> +#include <ufs/ufs/dir.h> + +#include <stand.h> +#include <string.h> + +#include "bootstrap.h" + +static char typestr[] = "?fc?d?b? ?l?s?w"; + +static int ls_getdir(char **pathp); + +COMMAND_SET(ls, "ls", "list files", command_ls); + +static int +command_ls(int argc, char *argv[]) +{ + int fd; + struct stat sb; + struct dirent *d; + char *buf, *path; + char lbuf[128]; /* one line */ + int result, ch; + int verbose; + + result = CMD_OK; + fd = -1; + verbose = 0; + optind = 1; + optreset = 1; + while ((ch = getopt(argc, argv, "l")) != -1) { + switch (ch) { + case 'l': + verbose = 1; + break; + case '?': + default: + /* getopt has already reported an error */ + return (CMD_OK); + } + } + argv += (optind - 1); + argc -= (optind - 1); + + if (argc < 2) { + path = ""; + } else { + path = argv[1]; + } + + if (stat(path, &sb) == 0 && !S_ISDIR(sb.st_mode)) { + if (verbose) { + printf(" %c %8d %s\n", + typestr[sb.st_mode >> 12], + (int)sb.st_size, path); + } else { + printf(" %c %s\n", + typestr[sb.st_mode >> 12], path); + } + return (CMD_OK); + } + + fd = ls_getdir(&path); + if (fd == -1) { + result = CMD_ERROR; + goto out; + } + pager_open(); + pager_output(path); + pager_output("\n"); + + while ((d = readdirfd(fd)) != NULL) { + if (strcmp(d->d_name, ".") && strcmp(d->d_name, "..")) { + if (d->d_type == 0 || verbose) { + /* stat the file, if possible */ + sb.st_size = 0; + sb.st_mode = 0; + buf = malloc(strlen(path) + strlen(d->d_name) + 2); + if (buf != NULL) { + sprintf(buf, "%s/%s", path, d->d_name); + /* ignore return, could be symlink, etc. */ + if (stat(buf, &sb)) { + sb.st_size = 0; + sb.st_mode = 0; + } + free(buf); + } + } + if (verbose) { + snprintf(lbuf, sizeof (lbuf), " %c %8d %s\n", + typestr[d->d_type? d->d_type:sb.st_mode >> 12], + (int)sb.st_size, d->d_name); + } else { + snprintf(lbuf, sizeof (lbuf), " %c %s\n", + typestr[d->d_type? d->d_type:sb.st_mode >> 12], d->d_name); + } + if (pager_output(lbuf)) + goto out; + } + } + out: + pager_close(); + if (fd != -1) + close(fd); + free(path); /* ls_getdir() did allocate path */ + return (result); +} + +/* + * Given (path) containing a vaguely reasonable path specification, return an fd + * on the directory, and an allocated copy of the path to the directory. + */ +static int +ls_getdir(char **pathp) +{ + struct stat sb; + int fd; + const char *cp; + char *path; + + fd = -1; + + /* one extra byte for a possible trailing slash required */ + path = malloc(strlen(*pathp) + 2); + if (path == NULL) { + snprintf(command_errbuf, sizeof (command_errbuf), + "out of memory"); + goto out; + } + + strcpy(path, *pathp); + + /* Make sure the path is respectable to begin with */ + if (archsw.arch_getdev(NULL, path, &cp)) { + snprintf(command_errbuf, sizeof (command_errbuf), + "bad path '%s'", path); + goto out; + } + + /* If there's no path on the device, assume '/' */ + if (*cp == 0) + strcat(path, "/"); + + fd = open(path, O_RDONLY); + if (fd < 0) { + snprintf(command_errbuf, sizeof (command_errbuf), + "open '%s' failed: %s", path, strerror(errno)); + goto out; + } + if (fstat(fd, &sb) < 0) { + snprintf(command_errbuf, sizeof (command_errbuf), + "stat failed: %s", strerror(errno)); + goto out; + } + if (!S_ISDIR(sb.st_mode)) { + snprintf(command_errbuf, sizeof (command_errbuf), + "%s: %s", path, strerror(ENOTDIR)); + goto out; + } + + *pathp = path; + return (fd); + + out: + free(path); + *pathp = NULL; + if (fd != -1) + close(fd); + return (-1); +} diff --git a/usr/src/boot/common/mb_header.S b/usr/src/boot/common/mb_header.S new file mode 100644 index 0000000000..411d126025 --- /dev/null +++ b/usr/src/boot/common/mb_header.S @@ -0,0 +1,43 @@ +/* + * This file and its contents are supplied under the terms of the + * Common Development and Distribution License ("CDDL"), version 1.0. + * You may only use this file in accordance with the terms of version + * 1.0 of the CDDL. + * + * A full copy of the text of the CDDL should have accompanied this + * source. A copy of the CDDL is also available via the Internet at + * http://www.illumos.org/license/CDDL. + */ + +/* + * Copyright 2018 Toomas Soome <tsoome@me.com> + */ + + .file "mb_header.S" + +/* + * Provide fake multiboot header to support versioning and partition + * start. The fake MB header is used by versioning code located in + * usr/src/cmd/boot/common. Since the BIOS bootblock is stored on raw disk, + * this fake header is used to store the location of the version info block. + * Additionally we use it to store partition start_sector, so we can identify + * our root file system partition. Note we are using LBA64 here. + */ + +#define ASM_FILE +#include <sys/multiboot.h> + + .globl mb_header, start_sector + .text + + .align 4 +mb_header: + .long MULTIBOOT_HEADER_MAGIC + .long MULTIBOOT_AOUT_KLUDGE + .long -(MULTIBOOT_HEADER_MAGIC + MULTIBOOT_AOUT_KLUDGE) + .long 0 /* header_addr */ + .long 0 /* load_addr */ + .long 0 /* load_end_addr */ +start_sector: .long 0 /* partition LBA */ + .long 0 + diff --git a/usr/src/boot/common/md.c b/usr/src/boot/common/md.c new file mode 100644 index 0000000000..175833d748 --- /dev/null +++ b/usr/src/boot/common/md.c @@ -0,0 +1,156 @@ +/*- + * Copyright (c) 2009 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. + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <stand.h> +#include <sys/param.h> +#include <sys/endian.h> +#include <sys/queue.h> +#include <machine/stdarg.h> + +#include "bootstrap.h" + +#define MD_BLOCK_SIZE 512 + +#ifndef MD_IMAGE_SIZE +#error Must be compiled with MD_IMAGE_SIZE defined +#endif +#if (MD_IMAGE_SIZE == 0 || MD_IMAGE_SIZE % MD_BLOCK_SIZE) +#error Image size must be a multiple of 512. +#endif + +/* + * Preloaded image gets put here. + * Applications that patch the object with the image can determine + * the size looking at the start and end markers (strings), + * so we want them contiguous. + */ +static struct { + u_char start[MD_IMAGE_SIZE]; + u_char end[128]; +} md_image = { + .start = "MFS Filesystem goes here", + .end = "MFS Filesystem had better STOP here", +}; + +/* devsw I/F */ +static int md_init(void); +static int md_strategy(void *, int, daddr_t, size_t, char *, size_t *); +static int md_open(struct open_file *, ...); +static int md_close(struct open_file *); +static int md_print(int); + +struct devsw md_dev = { + "md", + DEVT_DISK, + md_init, + md_strategy, + md_open, + md_close, + noioctl, + md_print +}; + +static int +md_init(void) +{ + + return (0); +} + +static int +md_strategy(void *devdata, int rw, daddr_t blk, size_t size, char *buf, + size_t *rsize) +{ + struct devdesc *dev = (struct devdesc *)devdata; + size_t ofs; + + if (dev->d_unit != 0) + return (ENXIO); + + if (blk < 0 || blk >= (MD_IMAGE_SIZE / MD_BLOCK_SIZE)) + return (EIO); + + if (size % MD_BLOCK_SIZE) + return (EIO); + + ofs = blk * MD_BLOCK_SIZE; + if ((ofs + size) > MD_IMAGE_SIZE) + size = MD_IMAGE_SIZE - ofs; + + if (rsize != 0) + *rsize = size; + + switch (rw & F_MASK) { + case F_READ: + bcopy(md_image.start + ofs, buf, size); + return (0); + case F_WRITE: + bcopy(buf, md_image.start + ofs, size); + return (0); + } + + return (ENODEV); +} + +static int +md_open(struct open_file *f, ...) +{ + va_list ap; + struct devdesc *dev; + + va_start(ap, f); + dev = va_arg(ap, struct devdesc *); + va_end(ap); + + if (dev->d_unit != 0) + return (ENXIO); + + return (0); +} + +static int +md_close(struct open_file *f) +{ + struct devdesc *dev; + + dev = (struct devdesc *)(f->f_devdata); + return ((dev->d_unit != 0) ? ENXIO : 0); +} + +static int +md_print(int verbose) +{ + + printf("%s devices:", md_dev.dv_name); + if (pager_output("\n") != 0) + return (1); + + printf("MD (%u bytes)", MD_IMAGE_SIZE); + return (pager_output("\n")); +} diff --git a/usr/src/boot/common/merge_help.awk b/usr/src/boot/common/merge_help.awk new file mode 100644 index 0000000000..1070f73f1f --- /dev/null +++ b/usr/src/boot/common/merge_help.awk @@ -0,0 +1,104 @@ +#!/usr/bin/awk -f +# +# $FreeBSD$ +# +# Merge two boot loader help files for FreeBSD 3.0 +# Joe Abley <jabley@patho.gen.nz> + +BEGIN \ +{ + state = 0; + first = -1; + ind = 0; +} + +# beginning of first command +/^###/ && (state == 0) \ +{ + state = 1; + next; +} + +# entry header +/^# T[[:graph:]]+ (S[[:graph:]]+ )*D[[:graph:]][[:print:]]*$/ && (state == 1) \ +{ + match($0, " T[[:graph:]]+"); + T = substr($0, RSTART + 2, RLENGTH - 2); + match($0, " S[[:graph:]]+"); + SSTART = RSTART + S = (RLENGTH == -1) ? "" : substr($0, RSTART + 2, RLENGTH - 2); + match($0, " D[[:graph:]][[:print:]]*$"); + D = substr($0, RSTART + 2); + if (SSTART > RSTART) + S = ""; + + # find a suitable place to store this one... + ind++; + if (ind == 1) + { + first = ind; + help[ind, "T"] = T; + help[ind, "S"] = S; + help[ind, "link"] = -1; + } else { + i = first; j = -1; + while (help[i, "T"] help[i, "S"] < T S) + { + j = i; + i = help[i, "link"]; + if (i == -1) break; + } + + if (i == -1) + { + help[j, "link"] = ind; + help[ind, "link"] = -1; + } else { + help[ind, "link"] = i; + if (j == -1) + first = ind; + else + help[j, "link"] = ind; + } + } + help[ind, "T"] = T; + help[ind, "S"] = S; + help[ind, "D"] = D; + + # set our state + state = 2; + help[ind, "text"] = 0; + next; +} + +# end of last command, beginning of next one +/^###/ && (state == 2) \ +{ + state = 1; +} + +(state == 2) \ +{ + sub("[[:blank:]]+$", ""); + if (help[ind, "text"] == 0 && $0 ~ /^[[:blank:]]*$/) next; + help[ind, "text", help[ind, "text"]] = $0; + help[ind, "text"]++; + next; +} + +# show them what we have (it's already sorted in help[]) +END \ +{ + node = first; + while (node != -1) + { + printf "################################################################################\n"; + printf "# T%s ", help[node, "T"]; + if (help[node, "S"] != "") printf "S%s ", help[node, "S"]; + printf "D%s\n\n", help[node, "D"]; + for (i = 0; i < help[node, "text"]; i++) + printf "%s\n", help[node, "text", i]; + node = help[node, "link"]; + } + printf "################################################################################\n"; +} diff --git a/usr/src/boot/common/misc.c b/usr/src/boot/common/misc.c new file mode 100644 index 0000000000..ef21ad4db2 --- /dev/null +++ b/usr/src/boot/common/misc.c @@ -0,0 +1,268 @@ +/*- + * 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> +#include <string.h> +#include <stand.h> +#include <bootstrap.h> +#ifndef BOOT2 +#include <machine/cpufunc.h> +#include "ficl.h" +#endif + +/* + * Concatenate the (argc) elements of (argv) into a single string, and return + * a copy of same. + */ +char * +unargv(int argc, char *argv[]) +{ + size_t hlong; + int i; + char *cp; + + for (i = 0, hlong = 0; i < argc; i++) + hlong += strlen(argv[i]) + 2; + + if(hlong == 0) + return(NULL); + + cp = malloc(hlong); + cp[0] = 0; + for (i = 0; i < argc; i++) { + strcat(cp, argv[i]); + if (i < (argc - 1)) + strcat(cp, " "); + } + + return(cp); +} + +/* + * Get the length of a string in kernel space + */ +size_t +strlenout(vm_offset_t src) +{ + char c; + size_t len; + + for (len = 0; ; len++) { + archsw.arch_copyout(src++, &c, 1); + if (c == 0) + break; + } + return(len); +} + +/* + * Make a duplicate copy of a string in kernel space + */ +char * +strdupout(vm_offset_t str) +{ + char *result, *cp; + + result = malloc(strlenout(str) + 1); + for (cp = result; ;cp++) { + archsw.arch_copyout(str++, cp, 1); + if (*cp == 0) + break; + } + return(result); +} + +/* Zero a region in kernel space. */ +void +kern_bzero(vm_offset_t dest, size_t len) +{ + char buf[256]; + size_t chunk, resid; + + bzero(buf, sizeof(buf)); + resid = len; + while (resid > 0) { + chunk = min(sizeof(buf), resid); + archsw.arch_copyin(buf, dest, chunk); + resid -= chunk; + dest += chunk; + } +} + +/* + * Read the specified part of a file to kernel space. Unlike regular + * pread, the file pointer is advanced to the end of the read data, + * and it just returns 0 if successful. + */ +int +kern_pread(int fd, vm_offset_t dest, size_t len, off_t off) +{ + + if (lseek(fd, off, SEEK_SET) == -1) { +#ifdef DEBUG + printf("\nlseek failed\n"); +#endif + return (-1); + } + if ((size_t)archsw.arch_readin(fd, dest, len) != len) { +#ifdef DEBUG + printf("\nreadin failed\n"); +#endif + return (-1); + } + return (0); +} + +/* + * Read the specified part of a file to a malloced buffer. The file + * pointer is advanced to the end of the read data. + */ +void * +alloc_pread(int fd, off_t off, size_t len) +{ + void *buf; + + buf = malloc(len); + if (buf == NULL) { +#ifdef DEBUG + printf("\nmalloc(%d) failed\n", (int)len); +#endif + return (NULL); + } + if (lseek(fd, off, SEEK_SET) == -1) { +#ifdef DEBUG + printf("\nlseek failed\n"); +#endif + free(buf); + return (NULL); + } + if ((size_t)read(fd, buf, len) != len) { +#ifdef DEBUG + printf("\nread failed\n"); +#endif + free(buf); + return (NULL); + } + return (buf); +} + +/* + * Display a region in traditional hexdump format. + */ +void +hexdump(caddr_t region, size_t len) +{ + caddr_t line; + int x, c; + char lbuf[80]; +#define emit(fmt, args...) {sprintf(lbuf, fmt , ## args); pager_output(lbuf);} + + pager_open(); + for (line = region; line < (region + len); line += 16) { + emit("%08lx ", (long) line); + + for (x = 0; x < 16; x++) { + if ((line + x) < (region + len)) { + emit("%02x ", *(u_int8_t *)(line + x)); + } else { + emit("-- "); + } + if (x == 7) + emit(" "); + } + emit(" |"); + for (x = 0; x < 16; x++) { + if ((line + x) < (region + len)) { + c = *(u_int8_t *)(line + x); + if ((c < ' ') || (c > '~')) /* !isprint(c) */ + c = '.'; + emit("%c", c); + } else { + emit(" "); + } + } + emit("|\n"); + } + pager_close(); +} + +void +dev_cleanup(void) +{ + int i; + + /* Call cleanup routines */ + for (i = 0; devsw[i] != NULL; ++i) + if (devsw[i]->dv_cleanup != NULL) + (devsw[i]->dv_cleanup)(); +} + +#ifndef BOOT2 +/* + * outb ( port# c -- ) + * Store a byte to I/O port number port# + */ +static void +ficlOutb(ficlVm *pVM) +{ + uint8_t c; + uint32_t port; + + port = ficlStackPopUnsigned(ficlVmGetDataStack(pVM)); + c = ficlStackPopInteger(ficlVmGetDataStack(pVM)); + outb(port, c); +} + +/* + * inb ( port# -- c ) + * Fetch a byte from I/O port number port# + */ +static void +ficlInb(ficlVm *pVM) +{ + uint8_t c; + uint32_t port; + + port = ficlStackPopUnsigned(ficlVmGetDataStack(pVM)); + c = inb(port); + ficlStackPushInteger(ficlVmGetDataStack(pVM), c); +} + +static void +ficlCompileCpufunc(ficlSystem *pSys) +{ + ficlDictionary *dp = ficlSystemGetDictionary(pSys); + + FICL_SYSTEM_ASSERT(pSys, dp); + + (void) ficlDictionarySetPrimitive(dp, "outb", ficlOutb, + FICL_WORD_DEFAULT); + (void) ficlDictionarySetPrimitive(dp, "inb", ficlInb, + FICL_WORD_DEFAULT); +} + +FICL_COMPILE_SET(ficlCompileCpufunc); +#endif diff --git a/usr/src/boot/common/module.c b/usr/src/boot/common/module.c new file mode 100644 index 0000000000..481c07eb58 --- /dev/null +++ b/usr/src/boot/common/module.c @@ -0,0 +1,1360 @@ +/* + * 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> + +/* + * 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/tem_impl.h> +#include <sys/font.h> +#include <sys/sha1.h> +#include <libcrypto.h> + +#include "bootstrap.h" + +#if defined(EFI) +#define PTOV(pa) ((void *)pa) +#else +#include "../i386/btx/lib/btxv86.h" +#endif + +#define MDIR_REMOVED 0x0001 +#define MDIR_NOHINTS 0x0002 + +struct moduledir { + char *d_path; /* path of modules directory */ + uchar_t *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 *, vm_offset_t, struct preloaded_file **); +static int file_load_dependencies(struct preloaded_file *); +static char *file_search(const char *, const char **); +static struct kernel_module *file_findmodule(struct preloaded_file *, char *, + struct mod_depend *); +static int file_havepath(const char *); +static char *mod_searchmodule(char *, struct mod_depend *); +static void file_insert_tail(struct preloaded_file *); +static void file_remove(struct preloaded_file *); +struct file_metadata *metadata_next(struct file_metadata *, int); +static void moduledir_readhints(struct moduledir *); +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 = "/platform/i86pc"; +#endif + +static STAILQ_HEAD(, moduledir) moduledir_list = + STAILQ_HEAD_INITIALIZER(moduledir_list); + +struct preloaded_file *preloaded_files = NULL; + +static const 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[]) +{ + char *typestr; + int dofile, dokld, ch, error; + + dokld = dofile = 0; + optind = 1; + optreset = 1; + typestr = NULL; + if (argc == 1) { + command_errmsg = "no filename specified"; + return (CMD_CRIT); + } + while ((ch = getopt(argc, argv, "kt:")) != -1) { + switch (ch) { + case 'k': + dokld = 1; + 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); + + printf("Loading %s...\n", argv[1]); + /* + * Request to load a raw file? + */ + if (dofile) { + struct preloaded_file *fp; + + if ((typestr == NULL) || (*typestr == 0)) { + command_errmsg = "invalid load type"; + return (CMD_CRIT); + } + + if (file_findfile(argv[1], typestr) != NULL) { + snprintf(command_errbuf, sizeof (command_errbuf), + "warning: file '%s' already loaded", argv[1]); + return (CMD_WARN); + } + + fp = file_loadraw(argv[1], typestr, argc - 2, argv + 2, 1); + if (fp != 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); +} + +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 __unused, char *argv[] __unused) +{ + 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, hash, ret = 0; + + verbose = 0; + hash = 0; + optind = 1; + optreset = 1; + while ((ch = getopt(argc, argv, "vs")) != -1) { + switch (ch) { + case 'v': + verbose = 1; + break; + case 's': + hash = 1; + break; + case '?': + default: + /* getopt has already reported an error */ + return (CMD_OK); + } + } + + pager_open(); + for (fp = preloaded_files; fp; fp = fp->f_next) { + sprintf(lbuf, " %p: ", (void *) fp->f_addr); + pager_output(lbuf); + pager_output(fp->f_name); + sprintf(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 (strcmp(fp->f_type, "hash") == 0) { + pager_output(" contents: "); + strlcpy(lbuf, PTOV(fp->f_addr), sizeof (lbuf)); + if (pager_output(lbuf)) + break; + } + } + + if (hash == 1) { + void *ptr = PTOV(fp->f_addr); + + pager_output(" hash: "); + sha1(ptr, fp->f_size, (uint8_t *)lbuf); + for (int i = 0; i < SHA1_DIGEST_LENGTH; i++) + printf("%02x", (int)(lbuf[i] & 0xff)); + if (pager_output("\n")) + break; + } + + if (fp->f_modules) { + pager_output(" modules: "); + for (mp = fp->f_modules; mp; mp = mp->m_next) { + sprintf(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) { + sprintf(lbuf, " 0x%04x, 0x%lx\n", + md->md_type, (long)md->md_size); + if ((ret = pager_output(lbuf))) + break; + } + } + if (ret != 0) + break; + } + pager_close(); + 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 (preloaded_files == NULL) + last_file_format = 0; + + 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) { + /* remember the loader */ + fp->f_loader = last_file_format = i; + *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); +} + +/* + * Calculate the size of the environment module. + * The environment is list of name=value C strings, ending with a '\0' byte. + */ +static size_t +env_get_size(void) +{ + size_t size = 0; + struct env_var *ep; + + /* Traverse the environment. */ + for (ep = environ; ep != NULL; ep = ep->ev_next) { + size += strlen(ep->ev_name); + size++; /* "=" */ + if (ep->ev_value != NULL) + size += strlen(ep->ev_value); + size++; /* nul byte */ + } + size++; /* nul byte */ + return (size); +} + +static void +module_hash(struct preloaded_file *fp, void *addr, size_t size) +{ + uint8_t hash[SHA1_DIGEST_LENGTH]; + char ascii[2 * SHA1_DIGEST_LENGTH + 1]; + int i; + + sha1(addr, size, hash); + for (i = 0; i < SHA1_DIGEST_LENGTH; i++) { + snprintf(ascii + 2 * i, sizeof (ascii) - 2 * i, "%02x", + hash[i] & 0xff); + } + /* Out of memory here is not fatal issue. */ + asprintf(&fp->f_args, "hash=%s", ascii); +} + +/* + * Create virtual module for environment variables. + * This module should be created as late as possible before executing + * the OS kernel, or we may miss some environment variable updates. + */ +void +build_environment_module(void) +{ + struct preloaded_file *fp; + size_t size; + char *name = "environment"; + vm_offset_t laddr; + + /* We can't load first */ + if ((file_findfile(NULL, NULL)) == NULL) { + printf("Can not load environment module: %s\n", + "the kernel is not loaded"); + return; + } + + tem_save_state(); /* Ask tem to save it's state in env. */ + size = env_get_size(); + + fp = file_alloc(); + if (fp != NULL) { + fp->f_name = strdup(name); + fp->f_type = strdup(name); + } + + if (fp == NULL || fp->f_name == NULL || fp->f_type == NULL) { + printf("Can not load environment module: %s\n", + "out of memory"); + file_discard(fp); + return; + } + + + if (archsw.arch_loadaddr != NULL) + loadaddr = archsw.arch_loadaddr(LOAD_MEM, &size, loadaddr); + + if (loadaddr == 0) { + printf("Can not load environment module: %s\n", + "out of memory"); + file_discard(fp); + return; + } + + laddr = bi_copyenv(loadaddr); + /* Looks OK so far; populate control structure */ + module_hash(fp, PTOV(loadaddr), laddr - loadaddr); + fp->f_loader = -1; + fp->f_addr = loadaddr; + fp->f_size = laddr - loadaddr; + + /* recognise space consumption */ + loadaddr = laddr; + + file_insert_tail(fp); +} + +void +build_font_module(void) +{ + bitmap_data_t *bd; + struct font *fd; + struct preloaded_file *fp; + size_t size; + uint32_t checksum; + int i; + char *name = "console-font"; + vm_offset_t laddr; + struct font_info fi; + struct fontlist *fl; + + if (STAILQ_EMPTY(&fonts)) + return; + + /* 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; + } + + /* helper pointers */ + bd = NULL; + STAILQ_FOREACH(fl, &fonts, font_next) { + if (tems.ts_font.vf_width == fl->font_data->width && + tems.ts_font.vf_height == fl->font_data->height) { + /* + * Kernel does have better built in font. + */ + if (fl->font_flags == FONT_BUILTIN) + return; + + bd = fl->font_data; + break; + } + } + if (bd == NULL) + return; + fd = bd->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->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 font_map); + size += roundup2(size, 8); + } + size += bd->uncompressed_size; + + fi.fi_checksum = -checksum; + + fp = file_alloc(); + if (fp != NULL) { + fp->f_name = strdup(name); + fp->f_type = strdup(name); + } + + if (fp == NULL || fp->f_name == NULL || fp->f_type == NULL) { + printf("Can not load font module: %s\n", + "out of memory"); + file_discard(fp); + return; + } + + if (archsw.arch_loadaddr != NULL) + loadaddr = archsw.arch_loadaddr(LOAD_MEM, &size, loadaddr); + + if (loadaddr == 0) { + printf("Can not load font module: %s\n", + "out of memory"); + file_discard(fp); + return; + } + + laddr = loadaddr; + laddr += archsw.arch_copyin(&fi, laddr, sizeof (struct font_info)); + laddr = roundup2(laddr, 8); + + /* Copy maps. */ + for (i = 0; i < VFNT_MAPS; i++) { + if (fd->vf_map_count[i] != 0) { + laddr += archsw.arch_copyin(fd->vf_map[i], laddr, + fd->vf_map_count[i] * sizeof (struct font_map)); + laddr = roundup2(laddr, 8); + } + } + + /* Copy the bitmap. */ + laddr += archsw.arch_copyin(fd->vf_bytes, laddr, fi.fi_bitmap_size); + + /* Looks OK so far; populate control structure */ + module_hash(fp, PTOV(loadaddr), laddr - loadaddr); + fp->f_loader = -1; + fp->f_addr = loadaddr; + fp->f_size = laddr - loadaddr; + + /* recognise space consumption */ + loadaddr = laddr; + + file_insert_tail(fp); +} + +/* + * 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 argc, char **argv, int insert) +{ + struct preloaded_file *fp; + char *name; + int fd, got; + vm_offset_t laddr; + struct stat st; + + /* 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); + } + if (fstat(fd, &st) < 0) { + close(fd); + snprintf(command_errbuf, sizeof (command_errbuf), + "stat error '%s': %s", name, strerror(errno)); + free(name); + return (NULL); + } + + if (archsw.arch_loadaddr != NULL) + loadaddr = archsw.arch_loadaddr(LOAD_RAW, name, loadaddr); + if (loadaddr == 0) { + close(fd); + snprintf(command_errbuf, sizeof (command_errbuf), + "no memory to load %s", name); + free(name); + return (NULL); + } + + laddr = loadaddr; + for (;;) { + /* read in 4k chunks; size is not really important */ + got = archsw.arch_readin(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); + if (archsw.arch_free_loadaddr != NULL && + st.st_size != 0) { + archsw.arch_free_loadaddr(loadaddr, + (uint64_t) + (roundup2(st.st_size, PAGE_SIZE) >> 12)); + } + return (NULL); + } + laddr += got; + } + + /* Looks OK so far; create & populate control structure */ + fp = file_alloc(); + if (fp == NULL) { + if (archsw.arch_free_loadaddr != NULL && st.st_size != 0) + archsw.arch_free_loadaddr(loadaddr, + (uint64_t)(roundup2(st.st_size, PAGE_SIZE) >> 12)); + snprintf(command_errbuf, sizeof (command_errbuf), + "no memory to load %s", name); + free(name); + close(fd); + return (NULL); + } + + fp->f_name = name; + fp->f_args = unargv(argc, argv); + fp->f_type = strdup(type); + fp->f_metadata = NULL; + fp->f_loader = -1; + fp->f_addr = loadaddr; + fp->f_size = laddr - loadaddr; + + if (fp->f_type == NULL || + (argc != 0 && fp->f_args == NULL)) { + close(fd); + snprintf(command_errbuf, sizeof (command_errbuf), + "no memory to load %s", name); + file_discard(fp); + 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 != NULL) { + free(mp->m_args); + mp->m_args = unargv(argc, argv); + 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 != NULL) { + 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) == 0) && + ((type == NULL) || strcmp(type, fp->f_type) == 0)) + 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 != NULL) + 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); +} + +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 const 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, + const char **extlist) +{ + struct stat st; + char *result, *cp; + const char **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, const 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 != NULL) + break; + } + return (result); +} + +#define INT_ALIGN(base, ptr) ptr = \ + (base) + (((ptr) - (base) + sizeof (int) - 1) & ~(sizeof (int) - 1)) + +static char * +mod_search_hints(struct moduledir *mdp, const char *modname, + struct mod_depend *verinfo) +{ + uchar_t *cp, *recptr, *bufend, *best; + char *result; + int *intp, bestver, blen, clen, ival, modnamelen, reclen; + bool found; + + moduledir_readhints(mdp); + modnamelen = strlen(modname); + found = false; + 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 = (uchar_t *)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 = true; + 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, (char *)cp, clen, NULL); + else if (best) + result = file_lookup(mdp->d_path, (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 == 0 && result == NULL) { + result = file_lookup(mdp->d_path, modname, modnamelen, + kld_ext_list); + } + return (result); +} + +/* + * 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 != NULL) + 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 != NULL) + 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; + + if (archsw.arch_free_loadaddr != NULL && fp->f_addr && + fp->f_size != 0) { + archsw.arch_free_loadaddr(fp->f_addr, + (uint64_t)(roundup2(fp->f_size, PAGE_SIZE) >> 12)); + } + + md = fp->f_metadata; + while (md != NULL) { + md1 = md; + md = md->md_next; + free(md1); + } + mp = fp->f_modules; + while (mp != NULL) { + 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; +} + +/* + * 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); + } + } +} diff --git a/usr/src/boot/common/multiboot2.c b/usr/src/boot/common/multiboot2.c new file mode 100644 index 0000000000..55af7d0456 --- /dev/null +++ b/usr/src/boot/common/multiboot2.c @@ -0,0 +1,1341 @@ +/* + * This file and its contents are supplied under the terms of the + * Common Development and Distribution License ("CDDL"), version 1.0. + * You may only use this file in accordance with the terms of version + * 1.0 of the CDDL. + * + * A full copy of the text of the CDDL should have accompanied this + * source. A copy of the CDDL is also available via the Internet at + * http://www.illumos.org/license/CDDL. + */ + +/* + * Copyright 2017 Toomas Soome <tsoome@me.com> + * Copyright 2019, Joyent, Inc. + */ + +/* + * This module adds support for loading and booting illumos multiboot2 + * kernel. This code is only built to support the illumos kernel, it does + * not support xen. + */ + +#include <sys/cdefs.h> +#include <sys/stddef.h> + +#include <sys/param.h> +#include <sys/exec.h> +#include <sys/linker.h> +#include <sys/module.h> +#include <sys/stdint.h> +#include <sys/multiboot2.h> +#include <stand.h> +#include <stdbool.h> +#include <machine/elf.h> +#include "libzfs.h" + +#include "bootstrap.h" +#include <sys/consplat.h> + +#include <machine/metadata.h> +#include <machine/pc/bios.h> + +#define SUPPORT_DHCP +#include <bootp.h> + +#if !defined(EFI) +#include "../i386/btx/lib/btxv86.h" +#include "libi386.h" +#include "vbe.h" + +#else +#include <efi.h> +#include <efilib.h> +#include "loader_efi.h" + +static void (*trampoline)(uint32_t, struct relocator *, uint64_t); +static UINTN efi_map_size; /* size of efi memory map */ +#endif + +#include "platform/acfreebsd.h" +#include "acconfig.h" +#define ACPI_SYSTEM_XFACE +#include "actypes.h" +#include "actbl.h" + +extern ACPI_TABLE_RSDP *rsdp; + +/* MB data heap pointer. */ +static vm_offset_t last_addr; + +static int multiboot2_loadfile(char *, uint64_t, struct preloaded_file **); +static int multiboot2_exec(struct preloaded_file *); + +struct file_format multiboot2 = { multiboot2_loadfile, multiboot2_exec }; +static bool keep_bs = false; +static bool have_framebuffer = false; +static vm_offset_t load_addr; +static vm_offset_t entry_addr; +bool has_boot_services = true; + +/* + * Validate tags in info request. This function is provided just to + * recognize the current tag list and only serves as a limited + * safe guard against possibly corrupt information. + */ +static bool +is_info_request_valid(multiboot_header_tag_information_request_t *rtag) +{ + int i; + + /* + * If the tag is optional and we do not support it, we do not + * have to do anything special, so we skip optional tags. + */ + if (rtag->mbh_flags & MULTIBOOT_HEADER_TAG_OPTIONAL) + return (true); + + for (i = 0; i < (rtag->mbh_size - sizeof (*rtag)) / + sizeof (rtag->mbh_requests[0]); i++) + switch (rtag->mbh_requests[i]) { + case MULTIBOOT_TAG_TYPE_END: + case MULTIBOOT_TAG_TYPE_CMDLINE: + case MULTIBOOT_TAG_TYPE_BOOT_LOADER_NAME: + case MULTIBOOT_TAG_TYPE_MODULE: + case MULTIBOOT_TAG_TYPE_BASIC_MEMINFO: + case MULTIBOOT_TAG_TYPE_BOOTDEV: + case MULTIBOOT_TAG_TYPE_MMAP: + case MULTIBOOT_TAG_TYPE_FRAMEBUFFER: + case MULTIBOOT_TAG_TYPE_VBE: + case MULTIBOOT_TAG_TYPE_ELF_SECTIONS: + case MULTIBOOT_TAG_TYPE_APM: + case MULTIBOOT_TAG_TYPE_EFI32: + case MULTIBOOT_TAG_TYPE_EFI64: + case MULTIBOOT_TAG_TYPE_ACPI_OLD: + case MULTIBOOT_TAG_TYPE_ACPI_NEW: + case MULTIBOOT_TAG_TYPE_NETWORK: + case MULTIBOOT_TAG_TYPE_EFI_MMAP: + case MULTIBOOT_TAG_TYPE_EFI_BS: + case MULTIBOOT_TAG_TYPE_EFI32_IH: + case MULTIBOOT_TAG_TYPE_EFI64_IH: + case MULTIBOOT_TAG_TYPE_LOAD_BASE_ADDR: + break; + default: + printf("unsupported information tag: 0x%x\n", + rtag->mbh_requests[i]); + return (false); + } + return (true); +} + +static int +multiboot2_loadfile(char *filename, uint64_t dest, + struct preloaded_file **result) +{ + int fd, error; + uint32_t i; + struct stat st; + caddr_t header_search; + multiboot2_header_t *header; + multiboot_header_tag_t *tag; + multiboot_header_tag_address_t *addr_tag = NULL; + multiboot_header_tag_entry_address_t *entry_tag = NULL; + struct preloaded_file *fp; + + /* This allows to check other file formats from file_formats array. */ + error = EFTYPE; + if (filename == NULL) + return (error); + + /* is kernel already loaded? */ + fp = file_findfile(NULL, NULL); + if (fp != NULL) + return (error); + + if ((fd = open(filename, O_RDONLY)) == -1) + return (errno); + + /* + * Read MULTIBOOT_SEARCH size in order to search for the + * multiboot magic header. + */ + header_search = malloc(MULTIBOOT_SEARCH); + if (header_search == NULL) { + close(fd); + return (ENOMEM); + } + + if (read(fd, header_search, MULTIBOOT_SEARCH) != MULTIBOOT_SEARCH) + goto out; + + header = NULL; + for (i = 0; i <= (MULTIBOOT_SEARCH - sizeof (multiboot2_header_t)); + i += MULTIBOOT_HEADER_ALIGN) { + header = (multiboot2_header_t *)(header_search + i); + + /* Do we have match on magic? */ + if (header->mb2_magic != MULTIBOOT2_HEADER_MAGIC) { + header = NULL; + continue; + } + /* + * Validate checksum, the sum of magic + architecture + + * header_length + checksum must equal 0. + */ + if (header->mb2_magic + header->mb2_architecture + + header->mb2_header_length + header->mb2_checksum != 0) { + header = NULL; + continue; + } + /* + * Finally, the entire header must fit within MULTIBOOT_SEARCH. + */ + if (i + header->mb2_header_length > MULTIBOOT_SEARCH) { + header = NULL; + continue; + } + break; + } + + if (header == NULL) + goto out; + + have_framebuffer = false; + for (tag = header->mb2_tags; tag->mbh_type != MULTIBOOT_TAG_TYPE_END; + tag = (multiboot_header_tag_t *)((uintptr_t)tag + + roundup2(tag->mbh_size, MULTIBOOT_TAG_ALIGN))) { + switch (tag->mbh_type) { + case MULTIBOOT_HEADER_TAG_INFORMATION_REQUEST: + if (is_info_request_valid((void*)tag) == false) + goto out; + break; + case MULTIBOOT_HEADER_TAG_ADDRESS: + addr_tag = (multiboot_header_tag_address_t *)tag; + break; + case MULTIBOOT_HEADER_TAG_ENTRY_ADDRESS: + entry_tag = + (multiboot_header_tag_entry_address_t *)tag; + break; + case MULTIBOOT_HEADER_TAG_CONSOLE_FLAGS: + break; + case MULTIBOOT_HEADER_TAG_FRAMEBUFFER: + have_framebuffer = true; + break; + case MULTIBOOT_HEADER_TAG_MODULE_ALIGN: + /* we always align modules */ + break; + case MULTIBOOT_HEADER_TAG_EFI_BS: + keep_bs = true; + break; + default: + if (!(tag->mbh_flags & MULTIBOOT_HEADER_TAG_OPTIONAL)) { + printf("unsupported tag: 0x%x\n", + tag->mbh_type); + goto out; + } + } + } + + /* + * We must have addr_tag and entry_tag to load a 64-bit kernel. + * If these tags are missing, we either have a 32-bit kernel, or + * this is not our kernel at all. + */ + if (addr_tag != NULL && entry_tag != NULL) { + fp = file_alloc(); + if (fp == NULL) { + error = ENOMEM; + goto out; + } + if (lseek(fd, 0, SEEK_SET) == -1) { + printf("lseek failed\n"); + error = EIO; + file_discard(fp); + goto out; + } + if (fstat(fd, &st) < 0) { + printf("fstat failed\n"); + error = EIO; + file_discard(fp); + goto out; + } + + load_addr = addr_tag->mbh_load_addr; + entry_addr = entry_tag->mbh_entry_addr; + fp->f_addr = archsw.arch_loadaddr(LOAD_KERN, filename, + addr_tag->mbh_load_addr); + if (fp->f_addr == 0) { + error = ENOMEM; + file_discard(fp); + goto out; + } + fp->f_size = archsw.arch_readin(fd, fp->f_addr, st.st_size); + + if (fp->f_size != st.st_size) { + printf("error reading %s: %s\n", filename, + strerror(errno)); + file_discard(fp); + error = EIO; + goto out; + } + + fp->f_name = strdup(filename); + fp->f_type = strdup("aout multiboot2 kernel"); + if (fp->f_name == NULL || fp->f_type == NULL) { + error = ENOMEM; + file_discard(fp); + goto out; + } + + fp->f_metadata = NULL; + error = 0; + } else { +#if defined(EFI) + /* 32-bit kernel is not yet supported for EFI */ + printf("32-bit kernel is not supported by UEFI loader\n"); + error = ENOTSUP; + goto out; +#endif + /* elf32_loadfile_raw will fill the attributes in fp. */ + error = elf32_loadfile_raw(filename, dest, &fp, 2); + if (error != 0) { + printf("elf32_loadfile_raw failed: %d unable to " + "load multiboot2 kernel\n", error); + goto out; + } + entry_addr = fp->f_addr; + /* + * We want the load_addr to have some legal value, + * so we set it same as the entry_addr. + * The distinction is important with UEFI, but not + * with BIOS version, because BIOS version does not use + * staging area. + */ + load_addr = fp->f_addr; + } + + setenv("kernelname", fp->f_name, 1); +#if defined(EFI) + efi_addsmapdata(fp); +#else + bios_addsmapdata(fp); +#endif + *result = fp; +out: + free(header_search); + close(fd); + return (error); +} + +/* + * Search the command line for named property. + * + * Return codes: + * 0 The name is found, we return the data in value and len. + * ENOENT The name is not found. + * EINVAL The provided command line is badly formed. + */ +static int +find_property_value(const char *cmd, const char *name, const char **value, + size_t *len) +{ + const char *namep, *valuep; + size_t name_len, value_len; + int quoted; + + *value = NULL; + *len = 0; + + if (cmd == NULL) + return (ENOENT); + + while (*cmd != '\0') { + if (cmd[0] != '-' || cmd[1] != 'B') { + cmd++; + continue; + } + cmd += 2; /* Skip -B */ + while (cmd[0] == ' ' || cmd[0] == '\t') + cmd++; /* Skip whitespaces. */ + while (*cmd != '\0' && cmd[0] != ' ' && cmd[0] != '\t') { + namep = cmd; + valuep = strchr(cmd, '='); + if (valuep == NULL) + break; + name_len = valuep - namep; + valuep++; + value_len = 0; + quoted = 0; + for (; ; ++value_len) { + if (valuep[value_len] == '\0') + break; + + /* Is this value quoted? */ + if (value_len == 0 && + (valuep[0] == '\'' || valuep[0] == '"')) { + quoted = valuep[0]; + ++value_len; + } + + /* + * In the quote accept any character, + * but look for ending quote. + */ + if (quoted != 0) { + if (valuep[value_len] == quoted) + quoted = 0; + continue; + } + + /* A comma or white space ends the value. */ + if (valuep[value_len] == ',' || + valuep[value_len] == ' ' || + valuep[value_len] == '\t') + break; + } + if (quoted != 0) { + printf("Missing closing '%c' in \"%s\"\n", + quoted, valuep); + return (EINVAL); + } + if (value_len != 0) { + if (strncmp(namep, name, name_len) == 0) { + *value = valuep; + *len = value_len; + return (0); + } + } + cmd = valuep + value_len; + while (*cmd == ',') + cmd++; + } + } + return (ENOENT); +} + +/* + * If command line has " -B ", insert property after "-B ", otherwise + * append to command line. + */ +static char * +insert_cmdline(const char *head, const char *prop) +{ + const char *prop_opt = " -B "; + char *cmdline, *tail; + int len = 0; + + tail = strstr(head, prop_opt); + if (tail != NULL) { + ptrdiff_t diff; + tail += strlen(prop_opt); + diff = tail - head; + if (diff >= INT_MAX) + return (NULL); + len = (int)diff; + } + + if (tail == NULL) + asprintf(&cmdline, "%s%s%s", head, prop_opt, prop); + else + asprintf(&cmdline, "%.*s%s,%s", len, head, prop, tail); + + return (cmdline); +} + +/* + * Since we have no way to pass the environment to the mb1 kernel other than + * through arguments, we need to take care of console setup. + * + * If the console is in mirror mode, set the kernel console from $os_console. + * If it's unset, use first item from $console. + * If $console is "ttyX", also pass $ttyX-mode, since it may have been set by + * the user. + * + * In case of memory allocation errors, just return the original command line + * so we have a chance of booting. + * + * On success, cl will be freed and a new, allocated command line string is + * returned. + * + * For the mb2 kernel, we only set command line console if os_console is set. + * We can not overwrite console in the environment, as it can disrupt the + * loader console messages, and we do not want to deal with the os_console + * in the kernel. + */ +static char * +update_cmdline(char *cl, bool mb2) +{ + char *os_console = getenv("os_console"); + char *ttymode = NULL; + char mode[10]; + char *tmp; + const char *prop; + size_t plen; + int rv; + + if (mb2 == true && os_console == NULL) + return (cl); + + if (os_console == NULL) { + tmp = strdup(getenv("console")); + os_console = strsep(&tmp, ", "); + } else { + os_console = strdup(os_console); + } + + if (os_console == NULL) + return (cl); + + if (mb2 == false && strncmp(os_console, "tty", 3) == 0) { + snprintf(mode, sizeof (mode), "%s-mode", os_console); + /* + * The ttyX-mode variable is set by our serial console + * driver for ttya-ttyd. However, since the os_console + * values are not verified, it is possible we get bogus + * name and no mode variable. If so, we do not set console + * property and let the kernel use defaults. + */ + if ((ttymode = getenv(mode)) == NULL) + return (cl); + } + + rv = find_property_value(cl, "console", &prop, &plen); + if (rv != 0 && rv != ENOENT) { + free(os_console); + return (cl); + } + + /* If console is set and this is MB2 boot, we are done. */ + if (rv == 0 && mb2 == true) { + free(os_console); + return (cl); + } + + /* If console is set, do we need to set tty mode? */ + if (rv == 0) { + const char *ttyp = NULL; + size_t ttylen; + + free(os_console); + os_console = NULL; + *mode = '\0'; + if (strncmp(prop, "tty", 3) == 0 && plen == 4) { + strncpy(mode, prop, plen); + mode[plen] = '\0'; + strncat(mode, "-mode", 5); + find_property_value(cl, mode, &ttyp, &ttylen); + } + + if (*mode != '\0' && ttyp == NULL) + ttymode = getenv(mode); + else + return (cl); + } + + /* Build updated command line. */ + if (os_console != NULL) { + char *propstr; + + asprintf(&propstr, "console=%s", os_console); + free(os_console); + if (propstr == NULL) { + return (cl); + } + + tmp = insert_cmdline(cl, propstr); + free(propstr); + if (tmp == NULL) + return (cl); + + free(cl); + cl = tmp; + } + if (ttymode != NULL) { + char *propstr; + + asprintf(&propstr, "%s=\"%s\"", mode, ttymode); + if (propstr == NULL) + return (cl); + + tmp = insert_cmdline(cl, propstr); + free(propstr); + if (tmp == NULL) + return (cl); + free(cl); + cl = tmp; + } + + return (cl); +} + +/* + * Build the kernel command line. Shared function between MB1 and MB2. + * + * In both cases, if fstype is set and is not zfs, we do not set up + * zfs-bootfs property. But we set kernel file name and options. + * + * For the MB1, we only can pass properties on command line, so + * we will set console, ttyX-mode (for serial console) and zfs-bootfs. + * + * For the MB2, we can pass properties in environment, but if os_console + * is set in environment, we need to add console property on the kernel + * command line. + * + * The console properties are managed in update_cmdline(). + */ +int +mb_kernel_cmdline(struct preloaded_file *fp, struct devdesc *rootdev, + char **line) +{ + const char *fs = getenv("fstype"); + char *cmdline; + size_t len; + bool zfs_root = false; + bool mb2; + int rv; + + /* + * 64-bit kernel has aout header, 32-bit kernel is elf, and the + * type strings are different. Lets just search for "multiboot2". + */ + if (strstr(fp->f_type, "multiboot2") == NULL) + mb2 = false; + else + mb2 = true; + + if (rootdev->d_dev->dv_type == DEVT_ZFS) + zfs_root = true; + + /* If we have fstype set in env, reset zfs_root if needed. */ + if (fs != NULL && strcmp(fs, "zfs") != 0) + zfs_root = false; + + /* + * If we have fstype set on the command line, + * reset zfs_root if needed. + */ + rv = find_property_value(fp->f_args, "fstype", &fs, &len); + if (rv != 0 && rv != ENOENT) + return (rv); + + if (fs != NULL && strncmp(fs, "zfs", len) != 0) + zfs_root = false; + + /* zfs_bootfs() will set the environment, it must be called. */ + if (zfs_root == true) + fs = zfs_bootfs(rootdev); + + if (fp->f_args == NULL) + cmdline = strdup(fp->f_name); + else + asprintf(&cmdline, "%s %s", fp->f_name, fp->f_args); + + if (cmdline == NULL) + return (ENOMEM); + + /* Append zfs-bootfs for MB1 command line. */ + if (mb2 == false && zfs_root == true) { + char *tmp; + + tmp = insert_cmdline(cmdline, fs); + free(cmdline); + if (tmp == NULL) + return (ENOMEM); + cmdline = tmp; + } + + *line = update_cmdline(cmdline, mb2); + return (0); +} + +/* + * Returns allocated virtual address from MB info area. + */ +static vm_offset_t +mb_malloc(size_t n) +{ + vm_offset_t ptr = last_addr; + last_addr = roundup(last_addr + n, MULTIBOOT_TAG_ALIGN); + return (ptr); +} + +/* + * Calculate size for module tag list. + */ +static size_t +module_size(struct preloaded_file *fp) +{ + size_t len, size; + struct preloaded_file *mfp; + + size = 0; + for (mfp = fp->f_next; mfp != NULL; mfp = mfp->f_next) { + len = strlen(mfp->f_name) + 1; + len += strlen(mfp->f_type) + 5 + 1; /* 5 is for "type=" */ + if (mfp->f_args != NULL) + len += strlen(mfp->f_args) + 1; + size += sizeof (multiboot_tag_module_t) + len; + size = roundup(size, MULTIBOOT_TAG_ALIGN); + } + return (size); +} + +#if defined(EFI) +/* + * Calculate size for UEFI memory map tag. + */ +#define EFI_EXTRA_PAGES 3 + +static int +efimemmap_size(void) +{ + UINTN size, cur_size, desc_size; + EFI_MEMORY_DESCRIPTOR *mmap; + EFI_STATUS ret; + + size = EFI_PAGE_SIZE; /* Start with 4k. */ + while (1) { + cur_size = size; + mmap = malloc(cur_size); + if (mmap == NULL) + return (0); + ret = BS->GetMemoryMap(&cur_size, mmap, NULL, &desc_size, NULL); + free(mmap); + if (ret == EFI_SUCCESS) + break; + if (ret == EFI_BUFFER_TOO_SMALL) { + if (size < cur_size) + size = cur_size; + size += (EFI_PAGE_SIZE); + } else + return (0); + } + + /* EFI MMAP will grow when we allocate MBI, set some buffer. */ + size += (EFI_EXTRA_PAGES << EFI_PAGE_SHIFT); + size = roundup2(size, EFI_PAGE_SIZE); + efi_map_size = size; /* Record the calculated size. */ + return (sizeof (multiboot_tag_efi_mmap_t) + size); +} +#endif + +/* + * Calculate size for bios smap tag. + */ +static size_t +biossmap_size(struct preloaded_file *fp) +{ + int num; + struct file_metadata *md; + + md = file_findmetadata(fp, MODINFOMD_SMAP); + if (md == NULL) + return (0); + + num = md->md_size / sizeof (struct bios_smap); /* number of entries */ + return (sizeof (multiboot_tag_mmap_t) + + num * sizeof (multiboot_mmap_entry_t)); +} + +static size_t +mbi_size(struct preloaded_file *fp, char *cmdline) +{ + size_t size; +#if !defined(EFI) + extern multiboot_tag_framebuffer_t gfx_fb; +#endif + + size = sizeof (uint32_t) * 2; /* first 2 fields from MBI header */ + size += sizeof (multiboot_tag_string_t) + strlen(cmdline) + 1; + size = roundup2(size, MULTIBOOT_TAG_ALIGN); + size += sizeof (multiboot_tag_string_t) + strlen(bootprog_info) + 1; + size = roundup2(size, MULTIBOOT_TAG_ALIGN); +#if !defined(EFI) + size += sizeof (multiboot_tag_basic_meminfo_t); + size = roundup2(size, MULTIBOOT_TAG_ALIGN); +#endif + size += module_size(fp); + size = roundup2(size, MULTIBOOT_TAG_ALIGN); +#if defined(EFI) + size += sizeof (multiboot_tag_efi64_t); + size = roundup2(size, MULTIBOOT_TAG_ALIGN); + size += efimemmap_size(); + size = roundup2(size, MULTIBOOT_TAG_ALIGN); + + if (have_framebuffer == true) { + size += sizeof (multiboot_tag_framebuffer_t); + size = roundup2(size, MULTIBOOT_TAG_ALIGN); + } +#endif + + size += biossmap_size(fp); + size = roundup2(size, MULTIBOOT_TAG_ALIGN); + +#if !defined(EFI) + if (gfx_fb.framebuffer_common.framebuffer_type == + MULTIBOOT_FRAMEBUFFER_TYPE_INDEXED) { + size += sizeof (struct multiboot_tag_framebuffer_common); + size += CMAP_SIZE * sizeof (multiboot_color_t); + } else { + size += sizeof (multiboot_tag_framebuffer_t); + } + size = roundup2(size, MULTIBOOT_TAG_ALIGN); + + size += sizeof (multiboot_tag_vbe_t); + size = roundup2(size, MULTIBOOT_TAG_ALIGN); +#endif + + if (bootp_response != NULL) { + size += sizeof (multiboot_tag_network_t) + bootp_response_size; + size = roundup2(size, MULTIBOOT_TAG_ALIGN); + } + + if (rsdp != NULL) { + if (rsdp->Revision == 0) { + size += sizeof (multiboot_tag_old_acpi_t) + + sizeof (ACPI_RSDP_COMMON); + } else { + size += sizeof (multiboot_tag_new_acpi_t) + + rsdp->Length; + } + size = roundup2(size, MULTIBOOT_TAG_ALIGN); + } + size += sizeof (multiboot_tag_t); + + return (size); +} + +#if defined(EFI) +static bool +overlaps(uintptr_t start1, size_t size1, uintptr_t start2, size_t size2) +{ + if (start1 < start2 + size2 && + start1 + size1 >= start2) { + printf("overlaps: %zx-%zx, %zx-%zx\n", + start1, start1 + size1, start2, start2 + size2); + return (true); + } + + return (false); +} +#endif + +static int +multiboot2_exec(struct preloaded_file *fp) +{ + multiboot2_info_header_t *mbi = NULL; + struct preloaded_file *mfp; + char *cmdline = NULL; + struct devdesc *rootdev; + struct file_metadata *md; + int i, error, num; + int rootfs = 0; + size_t size; + struct bios_smap *smap; +#if defined(EFI) + multiboot_tag_module_t *module, *mp; + struct relocator *relocator = NULL; + EFI_MEMORY_DESCRIPTOR *map; + UINTN map_size, desc_size; + struct chunk_head *head; + struct chunk *chunk; + vm_offset_t tmp; + + efi_getdev((void **)(&rootdev), NULL, NULL); + + /* + * We need 5 pages for relocation. We'll allocate from the heap: while + * it's possible that our heap got placed low down enough to be in the + * way of where we're going to relocate our kernel, it's hopefully not + * likely. + */ + if ((relocator = malloc(EFI_PAGE_SIZE * 5)) == NULL) { + printf("relocator malloc failed!\n"); + error = ENOMEM; + goto error; + } + + if (overlaps((uintptr_t)relocator, EFI_PAGE_SIZE * 5, + load_addr, fp->f_size)) { + printf("relocator pages overlap the kernel!\n"); + error = EINVAL; + goto error; + } + +#else + i386_getdev((void **)(&rootdev), NULL, NULL); + + if (have_framebuffer == false) { + /* make sure we have text mode */ + bios_set_text_mode(VGA_TEXT_MODE); + } +#endif + + error = EINVAL; + if (rootdev == NULL) { + printf("can't determine root device\n"); + goto error; + } + + /* + * Set the image command line. + */ + if (fp->f_args == NULL) { + cmdline = getenv("boot-args"); + if (cmdline != NULL) { + fp->f_args = strdup(cmdline); + if (fp->f_args == NULL) { + error = ENOMEM; + goto error; + } + } + } + + error = mb_kernel_cmdline(fp, rootdev, &cmdline); + if (error != 0) + goto error; + + /* mb_kernel_cmdline() updates the environment. */ + build_environment_module(); + + /* Pass the loaded console font for kernel. */ + build_font_module(); + + size = mbi_size(fp, cmdline); /* Get the size for MBI. */ + + /* Set up the base for mb_malloc. */ + i = 0; + for (mfp = fp; mfp->f_next != NULL; mfp = mfp->f_next) + i++; + +#if defined(EFI) + /* We need space for kernel + MBI + # modules */ + num = (EFI_PAGE_SIZE - offsetof(struct relocator, rel_chunklist)) / + sizeof (struct chunk); + if (i + 2 >= num) { + printf("Too many modules, do not have space for relocator.\n"); + error = ENOMEM; + goto error; + } + + last_addr = efi_loadaddr(LOAD_MEM, &size, mfp->f_addr + mfp->f_size); + mbi = (multiboot2_info_header_t *)last_addr; + if (mbi == NULL) { + error = ENOMEM; + goto error; + } + last_addr = (vm_offset_t)mbi->mbi_tags; +#else + /* Start info block from the new page. */ + last_addr = i386_loadaddr(LOAD_MEM, &size, mfp->f_addr + mfp->f_size); + + /* Do we have space for multiboot info? */ + if (last_addr + size >= memtop_copyin) { + error = ENOMEM; + goto error; + } + + mbi = (multiboot2_info_header_t *)PTOV(last_addr); + last_addr = (vm_offset_t)mbi->mbi_tags; +#endif /* EFI */ + + { + multiboot_tag_string_t *tag; + i = sizeof (multiboot_tag_string_t) + strlen(cmdline) + 1; + tag = (multiboot_tag_string_t *)mb_malloc(i); + + tag->mb_type = MULTIBOOT_TAG_TYPE_CMDLINE; + tag->mb_size = i; + memcpy(tag->mb_string, cmdline, strlen(cmdline) + 1); + free(cmdline); + cmdline = NULL; + } + + { + multiboot_tag_string_t *tag; + i = sizeof (multiboot_tag_string_t) + strlen(bootprog_info) + 1; + tag = (multiboot_tag_string_t *)mb_malloc(i); + + tag->mb_type = MULTIBOOT_TAG_TYPE_BOOT_LOADER_NAME; + tag->mb_size = i; + memcpy(tag->mb_string, bootprog_info, + strlen(bootprog_info) + 1); + } + +#if !defined(EFI) + /* Only set in case of BIOS. */ + { + multiboot_tag_basic_meminfo_t *tag; + tag = (multiboot_tag_basic_meminfo_t *) + mb_malloc(sizeof (*tag)); + + tag->mb_type = MULTIBOOT_TAG_TYPE_BASIC_MEMINFO; + tag->mb_size = sizeof (*tag); + tag->mb_mem_lower = bios_basemem / 1024; + tag->mb_mem_upper = bios_extmem / 1024; + } +#endif + + num = 0; + for (mfp = fp->f_next; mfp != NULL; mfp = mfp->f_next) { + num++; + if (mfp->f_type != NULL && strcmp(mfp->f_type, "rootfs") == 0) + rootfs++; + } + + if (num == 0 || rootfs == 0) { + /* We need at least one module - rootfs. */ + printf("No rootfs module provided, aborting\n"); + error = EINVAL; + goto error; + } + + /* + * Set the stage for physical memory layout: + * - We have kernel at load_addr. + * - Modules are aligned to page boundary. + * - MBI is aligned to page boundary. + * - Set the tmp to point to physical address of the first module. + * - tmp != mfp->f_addr only in case of EFI. + */ +#if defined(EFI) + tmp = roundup2(load_addr + fp->f_size + 1, MULTIBOOT_MOD_ALIGN); + module = (multiboot_tag_module_t *)last_addr; +#endif + + for (mfp = fp->f_next; mfp != NULL; mfp = mfp->f_next) { + multiboot_tag_module_t *tag; + + num = strlen(mfp->f_name) + 1; + num += strlen(mfp->f_type) + 5 + 1; + if (mfp->f_args != NULL) { + num += strlen(mfp->f_args) + 1; + } + cmdline = malloc(num); + if (cmdline == NULL) { + error = ENOMEM; + goto error; + } + + if (mfp->f_args != NULL) + snprintf(cmdline, num, "%s type=%s %s", + mfp->f_name, mfp->f_type, mfp->f_args); + else + snprintf(cmdline, num, "%s type=%s", + mfp->f_name, mfp->f_type); + + tag = (multiboot_tag_module_t *)mb_malloc(sizeof (*tag) + num); + + tag->mb_type = MULTIBOOT_TAG_TYPE_MODULE; + tag->mb_size = sizeof (*tag) + num; +#if defined(EFI) + /* + * We can assign module addresses only after BS have been + * switched off. + */ + tag->mb_mod_start = 0; + tag->mb_mod_end = mfp->f_size; +#else + tag->mb_mod_start = mfp->f_addr; + tag->mb_mod_end = mfp->f_addr + mfp->f_size; +#endif + memcpy(tag->mb_cmdline, cmdline, num); + free(cmdline); + cmdline = NULL; + } + + md = file_findmetadata(fp, MODINFOMD_SMAP); + if (md == NULL) { + printf("no memory smap\n"); + error = EINVAL; + goto error; + } + + smap = (struct bios_smap *)md->md_data; + num = md->md_size / sizeof (struct bios_smap); /* number of entries */ + + { + multiboot_tag_mmap_t *tag; + multiboot_mmap_entry_t *mmap_entry; + + tag = (multiboot_tag_mmap_t *) + mb_malloc(sizeof (*tag) + + num * sizeof (multiboot_mmap_entry_t)); + + tag->mb_type = MULTIBOOT_TAG_TYPE_MMAP; + tag->mb_size = sizeof (*tag) + + num * sizeof (multiboot_mmap_entry_t); + tag->mb_entry_size = sizeof (multiboot_mmap_entry_t); + tag->mb_entry_version = 0; + mmap_entry = (multiboot_mmap_entry_t *)tag->mb_entries; + + for (i = 0; i < num; i++) { + mmap_entry[i].mmap_addr = smap[i].base; + mmap_entry[i].mmap_len = smap[i].length; + mmap_entry[i].mmap_type = smap[i].type; + mmap_entry[i].mmap_reserved = 0; + } + } + + if (bootp_response != NULL) { + multiboot_tag_network_t *tag; + tag = (multiboot_tag_network_t *) + mb_malloc(sizeof (*tag) + bootp_response_size); + + tag->mb_type = MULTIBOOT_TAG_TYPE_NETWORK; + tag->mb_size = sizeof (*tag) + bootp_response_size; + memcpy(tag->mb_dhcpack, bootp_response, bootp_response_size); + } + +#if !defined(EFI) + multiboot_tag_vbe_t *tag; + extern multiboot_tag_vbe_t vbestate; + + if (VBE_VALID_MODE(vbestate.vbe_mode)) { + tag = (multiboot_tag_vbe_t *)mb_malloc(sizeof (*tag)); + memcpy(tag, &vbestate, sizeof (*tag)); + tag->mb_type = MULTIBOOT_TAG_TYPE_VBE; + tag->mb_size = sizeof (*tag); + } +#endif + + if (rsdp != NULL) { + multiboot_tag_new_acpi_t *ntag; + multiboot_tag_old_acpi_t *otag; + uint32_t tsize; + + if (rsdp->Revision == 0) { + tsize = sizeof (*otag) + sizeof (ACPI_RSDP_COMMON); + otag = (multiboot_tag_old_acpi_t *)mb_malloc(tsize); + otag->mb_type = MULTIBOOT_TAG_TYPE_ACPI_OLD; + otag->mb_size = tsize; + memcpy(otag->mb_rsdp, rsdp, sizeof (ACPI_RSDP_COMMON)); + } else { + tsize = sizeof (*ntag) + rsdp->Length; + ntag = (multiboot_tag_new_acpi_t *)mb_malloc(tsize); + ntag->mb_type = MULTIBOOT_TAG_TYPE_ACPI_NEW; + ntag->mb_size = tsize; + memcpy(ntag->mb_rsdp, rsdp, rsdp->Length); + } + } + +#if defined(EFI) +#ifdef __LP64__ + { + multiboot_tag_efi64_t *tag; + tag = (multiboot_tag_efi64_t *) + mb_malloc(sizeof (*tag)); + + tag->mb_type = MULTIBOOT_TAG_TYPE_EFI64; + tag->mb_size = sizeof (*tag); + tag->mb_pointer = (uint64_t)(uintptr_t)ST; + } +#else + { + multiboot_tag_efi32_t *tag; + tag = (multiboot_tag_efi32_t *) + mb_malloc(sizeof (*tag)); + + tag->mb_type = MULTIBOOT_TAG_TYPE_EFI32; + tag->mb_size = sizeof (*tag); + tag->mb_pointer = (uint32_t)ST; + } +#endif /* __LP64__ */ +#endif /* EFI */ + + if (have_framebuffer == true) { + multiboot_tag_framebuffer_t *tag; + extern multiboot_tag_framebuffer_t gfx_fb; +#if defined(EFI) + + tag = (multiboot_tag_framebuffer_t *)mb_malloc(sizeof (*tag)); + memcpy(tag, &gfx_fb, sizeof (*tag)); + tag->framebuffer_common.mb_type = + MULTIBOOT_TAG_TYPE_FRAMEBUFFER; + tag->framebuffer_common.mb_size = sizeof (*tag); +#else + extern multiboot_color_t *cmap; + uint32_t size; + + if (gfx_fb.framebuffer_common.framebuffer_type == + MULTIBOOT_FRAMEBUFFER_TYPE_INDEXED) { + uint16_t nc; + nc = gfx_fb.u.fb1.framebuffer_palette_num_colors; + size = sizeof (struct multiboot_tag_framebuffer_common) + + sizeof (nc) + + nc * sizeof (multiboot_color_t); + } else { + size = sizeof (gfx_fb); + } + + tag = (multiboot_tag_framebuffer_t *)mb_malloc(size); + memcpy(tag, &gfx_fb, sizeof (*tag)); + + tag->framebuffer_common.mb_type = + MULTIBOOT_TAG_TYPE_FRAMEBUFFER; + tag->framebuffer_common.mb_size = size; + + if (gfx_fb.framebuffer_common.framebuffer_type == + MULTIBOOT_FRAMEBUFFER_TYPE_INDEXED) { + gfx_fb.u.fb1.framebuffer_palette_num_colors = CMAP_SIZE; + + memcpy(tag->u.fb1.framebuffer_palette, cmap, + sizeof (multiboot_color_t) * CMAP_SIZE); + } +#endif /* EFI */ + } + +#if defined(EFI) + /* Leave EFI memmap last as we will also switch off the BS. */ + { + multiboot_tag_efi_mmap_t *tag; + UINTN key; + EFI_STATUS status; + + tag = (multiboot_tag_efi_mmap_t *) + mb_malloc(sizeof (*tag)); + + map_size = 0; + status = BS->GetMemoryMap(&map_size, + (EFI_MEMORY_DESCRIPTOR *)tag->mb_efi_mmap, &key, + &desc_size, &tag->mb_descr_vers); + if (status != EFI_BUFFER_TOO_SMALL) { + error = EINVAL; + goto error; + } + map_size = roundup2(map_size, EFI_PAGE_SIZE); + + i = 2; /* Attempts to ExitBootServices() */ + while (map_size <= efi_map_size && i > 0) { + status = BS->GetMemoryMap(&map_size, + (EFI_MEMORY_DESCRIPTOR *)tag->mb_efi_mmap, &key, + &desc_size, &tag->mb_descr_vers); + if (status == EFI_BUFFER_TOO_SMALL) { + /* Still too small? */ + map_size += EFI_PAGE_SIZE; + continue; + } + if (EFI_ERROR(status)) { + error = EINVAL; + goto error; + } + + if (keep_bs != 0) + break; + + status = BS->ExitBootServices(IH, key); + if (status == EFI_SUCCESS) { + has_boot_services = false; + break; + } + i--; + } + if (status != EFI_SUCCESS) { + error = EINVAL; + goto error; + } + + tag->mb_type = MULTIBOOT_TAG_TYPE_EFI_MMAP; + tag->mb_size = sizeof (*tag) + map_size; + tag->mb_descr_size = (uint32_t)desc_size; + + map = (EFI_MEMORY_DESCRIPTOR *)tag->mb_efi_mmap; + + last_addr += map_size; + last_addr = roundup2(last_addr, MULTIBOOT_TAG_ALIGN); + } +#endif /* EFI */ + + /* + * MB tag list end marker. + */ + { + multiboot_tag_t *tag = (multiboot_tag_t *) + mb_malloc(sizeof (*tag)); + tag->mb_type = MULTIBOOT_TAG_TYPE_END; + tag->mb_size = sizeof (*tag); + } + + mbi->mbi_total_size = last_addr - (vm_offset_t)mbi; + mbi->mbi_reserved = 0; + +#if defined(EFI) + /* + * At this point we have load_addr pointing to kernel load + * address, module list in MBI having physical addresses, + * module list in fp having logical addresses and tmp pointing to + * physical address for MBI. + * Now we must move all pieces to place and start the kernel. + */ + head = &relocator->rel_chunk_head; + STAILQ_INIT(head); + + i = 0; + chunk = &relocator->rel_chunklist[i++]; + chunk->chunk_vaddr = fp->f_addr; + chunk->chunk_paddr = load_addr; + chunk->chunk_size = fp->f_size; + + STAILQ_INSERT_TAIL(head, chunk, chunk_next); + + mp = module; + for (mfp = fp->f_next; mfp != NULL; mfp = mfp->f_next) { + chunk = &relocator->rel_chunklist[i++]; + chunk->chunk_vaddr = mfp->f_addr; + + /* + * fix the mb_mod_start and mb_mod_end. + */ + mp->mb_mod_start = efi_physaddr(module, tmp, map, + map_size / desc_size, desc_size, mfp->f_addr, + mp->mb_mod_end); + if (mp->mb_mod_start == 0) + panic("Could not find memory for module"); + + mp->mb_mod_end += mp->mb_mod_start; + chunk->chunk_paddr = mp->mb_mod_start; + chunk->chunk_size = mfp->f_size; + STAILQ_INSERT_TAIL(head, chunk, chunk_next); + + mp = (multiboot_tag_module_t *) + roundup2((uintptr_t)mp + mp->mb_size, + MULTIBOOT_TAG_ALIGN); + } + chunk = &relocator->rel_chunklist[i++]; + chunk->chunk_vaddr = (EFI_VIRTUAL_ADDRESS)(uintptr_t)mbi; + chunk->chunk_paddr = efi_physaddr(module, tmp, map, + map_size / desc_size, desc_size, (uintptr_t)mbi, + mbi->mbi_total_size); + chunk->chunk_size = mbi->mbi_total_size; + STAILQ_INSERT_TAIL(head, chunk, chunk_next); + + trampoline = (void *)(uintptr_t)relocator + EFI_PAGE_SIZE; + memmove(trampoline, multiboot_tramp, EFI_PAGE_SIZE); + + relocator->rel_copy = (uintptr_t)trampoline + EFI_PAGE_SIZE; + memmove((void *)relocator->rel_copy, efi_copy_finish, EFI_PAGE_SIZE); + + relocator->rel_memmove = (uintptr_t)relocator->rel_copy + EFI_PAGE_SIZE; + memmove((void *)relocator->rel_memmove, memmove, EFI_PAGE_SIZE); + relocator->rel_stack = relocator->rel_memmove + EFI_PAGE_SIZE - 8; + + trampoline(MULTIBOOT2_BOOTLOADER_MAGIC, relocator, entry_addr); +#else + dev_cleanup(); + __exec((void *)VTOP(multiboot_tramp), MULTIBOOT2_BOOTLOADER_MAGIC, + (void *)entry_addr, (void *)VTOP(mbi)); +#endif /* EFI */ + panic("exec returned"); + +error: + free(cmdline); + +#if defined(EFI) + free(relocator); + + if (mbi != NULL) + efi_free_loadaddr((vm_offset_t)mbi, EFI_SIZE_TO_PAGES(size)); +#endif + + return (error); +} diff --git a/usr/src/boot/common/newvers.sh b/usr/src/boot/common/newvers.sh new file mode 100755 index 0000000000..0cb1b16b76 --- /dev/null +++ b/usr/src/boot/common/newvers.sh @@ -0,0 +1,42 @@ +#!/bin/sh - +# +# $NetBSD: newvers.sh,v 1.1 1997/07/26 01:50:38 thorpej Exp $ +# +# Copyright (c) 1984, 1986, 1990, 1993 +# The Regents of the University of California. 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. +# 4. Neither the name of the University nor the names of its contributors +# may be used to endorse or promote products derived from this software +# without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS 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. +# +# @(#)newvers.sh 8.1 (Berkeley) 4/20/94 + +tempfile=$(mktemp tmp.XXXXXX) || exit +trap "rm -f $tempfile" EXIT INT TERM + +LC_ALL=C; export LC_ALL +r="$1" + +echo "char bootprog_info[] = \"illumos/${3} ${2}, Revision ${r}\\\\n\";" > $tempfile +echo "unsigned bootprog_rev = ${r%%.*}${r##*.};" >> $tempfile +mv $tempfile vers.c diff --git a/usr/src/boot/common/nvstore.c b/usr/src/boot/common/nvstore.c new file mode 100644 index 0000000000..b3e6cdbeaa --- /dev/null +++ b/usr/src/boot/common/nvstore.c @@ -0,0 +1,309 @@ +/* + * Copyright 2020 Toomas Soome <tsoome@me.com> + * + * 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. + */ + +/* + * Big Theory Statement. + * + * nvstore is abstraction layer to implement data read/write to different + * types of non-volatile storage. + * + * User interfaces: + * Provide mapping via environment: setenv/unsetenv/putenv. Access via + * environment functions/commands is available once nvstore has + * attached the backend and stored textual data is mapped to environment. + * + * Provide command "nvstore" to create new data instances. + * + * API: TBD. + * nvstore_init(): attach new backend and create the environment mapping. + * nvstore_fini: detach backend and unmap the related environment. + * + * The disk based storage, such as UFS file or ZFS bootenv label area, is + * only accessible after root file system is set. Root file system change + * will switch the back end storage. + */ + +#include <sys/cdefs.h> + +#include <stdbool.h> +#include <sys/queue.h> +#include <bootstrap.h> +#include "stand.h" + +typedef struct nvstore { + char *nvs_name; + void *nvs_data; + nvs_callbacks_t *nvs_cb; + STAILQ_ENTRY(nvstore) nvs_next; +} nvstore_t; + +typedef STAILQ_HEAD(store_list, nvstore) nvstore_list_t; + +nvstore_list_t stores = STAILQ_HEAD_INITIALIZER(stores); + +void * +nvstore_get_store(const char *name) +{ + nvstore_t *st; + + st = NULL; + + STAILQ_FOREACH(st, &stores, nvs_next) { + if (strcmp(name, st->nvs_name) == 0) + break; + } + + return (st); +} + +int +nvstore_init(const char *name, nvs_callbacks_t *cb, void *data) +{ + nvstore_t *st; + + st = nvstore_get_store(name); + if (st != NULL) + return (EEXIST); + + if ((st = malloc(sizeof (*st))) == NULL) + return (ENOMEM); + + if ((st->nvs_name = strdup(name)) == NULL) { + free(st); + return (ENOMEM); + } + + st->nvs_data = data; + st->nvs_cb = cb; + + STAILQ_INSERT_TAIL(&stores, st, nvs_next); + return (0); +} + +int +nvstore_fini(const char *name) +{ + nvstore_t *st; + + st = nvstore_get_store(name); + if (st == NULL) + return (ENOENT); + + STAILQ_REMOVE(&stores, st, nvstore, nvs_next); + + free(st->nvs_name); + free(st->nvs_data); + free(st); + return (0); +} + +int +nvstore_print(void *ptr) +{ + nvstore_t *st = ptr; + + return (st->nvs_cb->nvs_iterate(st->nvs_data, st->nvs_cb->nvs_print)); +} + +int +nvstore_get_var(void *ptr, const char *name, void **data) +{ + nvstore_t *st = ptr; + + return (st->nvs_cb->nvs_getter(st->nvs_data, name, data)); +} + +int +nvstore_set_var(void *ptr, int type, const char *name, + void *data, size_t size) +{ + nvstore_t *st = ptr; + + return (st->nvs_cb->nvs_setter(st->nvs_data, type, name, data, size)); +} + +int +nvstore_set_var_from_string(void *ptr, const char *type, const char *name, + const char *data) +{ + nvstore_t *st = ptr; + + return (st->nvs_cb->nvs_setter_str(st->nvs_data, type, name, data)); +} + +int +nvstore_unset_var(void *ptr, const char *name) +{ + nvstore_t *st = ptr; + + return (st->nvs_cb->nvs_unset(st->nvs_data, name)); +} + +COMMAND_SET(nvstore, "nvstore", "manage non-volatile data", command_nvstore); + +static void +nvstore_usage(const char *me) +{ + printf("Usage:\t%s -l\n", me); + printf("\t%s store -l\n", me); + printf("\t%s store [-t type] key value\n", me); + printf("\t%s store -g key\n", me); + printf("\t%s store -d key\n", me); +} + +/* + * Usage: nvstore -l # list stores + * nvstore store -l # list data in store + * nvstore store [-t type] key value + * nvstore store -g key # get value + * nvstore store -d key # delete key + */ +static int +command_nvstore(int argc, char *argv[]) +{ + int c; + bool list, get, delete; + nvstore_t *st; + char *me, *name, *type; + + me = argv[0]; + optind = 1; + optreset = 1; + + list = false; + 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 == 0) { + if (list) { + if (STAILQ_EMPTY(&stores)) { + printf("No configured nvstores\n"); + return (CMD_OK); + } + printf("List of configured nvstores:\n"); + STAILQ_FOREACH(st, &stores, nvs_next) { + printf("\t%s\n", st->nvs_name); + } + return (CMD_OK); + } + nvstore_usage(me); + return (CMD_ERROR); + } + + if (argc == 0 || (argc != 0 && list)) { + nvstore_usage(me); + return (CMD_ERROR); + } + + st = nvstore_get_store(argv[0]); + if (st == NULL) { + nvstore_usage(me); + return (CMD_ERROR); + } + + optind = 1; + optreset = 1; + name = NULL; + type = NULL; + get = delete = false; + + while ((c = getopt(argc, argv, "d:g:lt:")) != -1) { + switch (c) { + case 'd': + if (list || get) { + nvstore_usage(me); + return (CMD_ERROR); + } + name = optarg; + delete = true; + break; + case 'g': + if (delete || list) { + nvstore_usage(me); + return (CMD_ERROR); + } + name = optarg; + get = true; + break; + case 'l': + if (delete || get) { + nvstore_usage(me); + return (CMD_ERROR); + } + list = true; + break; + case 't': + type = optarg; + break; + case '?': + default: + return (CMD_ERROR); + } + } + + argc -= optind; + argv += optind; + + if (list) { + (void) nvstore_print(st); + return (CMD_OK); + } + + if (delete && name != NULL) { + (void) nvstore_unset_var(st, name); + return (CMD_OK); + } + + if (get && name != NULL) { + char *ptr = NULL; + + if (nvstore_get_var(st, name, (void **)&ptr) == 0) + printf("%s = %s\n", name, ptr); + return (CMD_OK); + } + + if (argc == 2) { + c = nvstore_set_var_from_string(st, type, argv[0], argv[1]); + if (c != 0) { + printf("error: %s\n", strerror(c)); + return (CMD_ERROR); + } + return (CMD_OK); + } + + nvstore_usage(me); + return (CMD_OK); +} diff --git a/usr/src/boot/common/part.c b/usr/src/boot/common/part.c new file mode 100644 index 0000000000..084eb38f80 --- /dev/null +++ b/usr/src/boot/common/part.c @@ -0,0 +1,1066 @@ +/* + * Copyright (c) 2012 Andrey V. Elsukov <ae@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 AUTHORS 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 AUTHORS 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> + +#include <stand.h> +#include <stddef.h> +#include <sys/param.h> +#include <sys/diskmbr.h> +#include <sys/disklabel.h> +#include <sys/endian.h> +#include <sys/gpt.h> +#include <sys/queue.h> +#include <sys/vtoc.h> + +#include <fs/cd9660/iso.h> + +#include <zlib.h> +#include <part.h> +#include <uuid.h> + +#ifdef PART_DEBUG +#define DPRINTF(fmt, args...) printf("%s: " fmt "\n", __func__, ## args) +#else +#define DPRINTF(fmt, args...) ((void)0) +#endif + +#ifdef LOADER_GPT_SUPPORT +#define MAXTBLSZ 64 +static const uuid_t gpt_uuid_unused = GPT_ENT_TYPE_UNUSED; +static const uuid_t gpt_uuid_ms_basic_data = GPT_ENT_TYPE_MS_BASIC_DATA; +static const uuid_t gpt_uuid_freebsd_ufs = GPT_ENT_TYPE_FREEBSD_UFS; +static const uuid_t gpt_uuid_efi = GPT_ENT_TYPE_EFI; +static const uuid_t gpt_uuid_freebsd = GPT_ENT_TYPE_FREEBSD; +static const uuid_t gpt_uuid_freebsd_boot = GPT_ENT_TYPE_FREEBSD_BOOT; +static const uuid_t gpt_uuid_freebsd_swap = GPT_ENT_TYPE_FREEBSD_SWAP; +static const uuid_t gpt_uuid_freebsd_zfs = GPT_ENT_TYPE_FREEBSD_ZFS; +static const uuid_t gpt_uuid_freebsd_vinum = GPT_ENT_TYPE_FREEBSD_VINUM; +static const uuid_t gpt_uuid_illumos_boot = GPT_ENT_TYPE_ILLUMOS_BOOT; +static const uuid_t gpt_uuid_illumos_ufs = GPT_ENT_TYPE_ILLUMOS_UFS; +static const uuid_t gpt_uuid_illumos_zfs = GPT_ENT_TYPE_ILLUMOS_ZFS; +static const uuid_t gpt_uuid_reserved = GPT_ENT_TYPE_RESERVED; +static const uuid_t gpt_uuid_apple_apfs = GPT_ENT_TYPE_APPLE_APFS; +#endif + +struct pentry { + struct ptable_entry part; + uint64_t flags; + union { + uint8_t bsd; + uint8_t mbr; + uuid_t gpt; + uint16_t vtoc8; + uint16_t vtoc; + } type; + STAILQ_ENTRY(pentry) entry; +}; + +struct ptable { + enum ptable_type type; + uint16_t sectorsize; + uint64_t sectors; + + STAILQ_HEAD(, pentry) entries; +}; + +static struct parttypes { + enum partition_type type; + const char *desc; +} ptypes[] = { + { PART_UNKNOWN, "Unknown" }, + { PART_EFI, "EFI" }, + { PART_FREEBSD, "FreeBSD" }, + { PART_FREEBSD_BOOT, "FreeBSD boot" }, + { PART_FREEBSD_UFS, "FreeBSD UFS" }, + { PART_FREEBSD_ZFS, "FreeBSD ZFS" }, + { PART_FREEBSD_SWAP, "FreeBSD swap" }, + { PART_FREEBSD_VINUM, "FreeBSD vinum" }, + { PART_LINUX, "Linux" }, + { PART_LINUX_SWAP, "Linux swap" }, + { PART_DOS, "DOS/Windows" }, + { PART_ISO9660, "ISO9660" }, + { PART_SOLARIS2, "Solaris 2" }, + { PART_ILLUMOS_UFS, "illumos UFS" }, + { PART_ILLUMOS_ZFS, "illumos ZFS" }, + { PART_RESERVED, "Reserved" }, + { PART_VTOC_BOOT, "boot" }, + { PART_VTOC_ROOT, "root" }, + { PART_VTOC_SWAP, "swap" }, + { PART_VTOC_USR, "usr" }, + { PART_VTOC_STAND, "stand" }, + { PART_VTOC_VAR, "var" }, + { PART_VTOC_HOME, "home" }, + { PART_APFS, "APFS" } +}; + +const char * +parttype2str(enum partition_type type) +{ + size_t i; + + for (i = 0; i < nitems(ptypes); i++) + if (ptypes[i].type == type) + return (ptypes[i].desc); + return (ptypes[0].desc); +} + +#ifdef LOADER_GPT_SUPPORT +static void +uuid_letoh(uuid_t *uuid) +{ + + uuid->time_low = le32toh(uuid->time_low); + uuid->time_mid = le16toh(uuid->time_mid); + uuid->time_hi_and_version = le16toh(uuid->time_hi_and_version); +} + +static enum partition_type +gpt_parttype(uuid_t type) +{ + + if (uuid_equal(&type, &gpt_uuid_efi, NULL)) + return (PART_EFI); + else if (uuid_equal(&type, &gpt_uuid_ms_basic_data, NULL)) + return (PART_DOS); + else if (uuid_equal(&type, &gpt_uuid_freebsd_boot, NULL)) + return (PART_FREEBSD_BOOT); + else if (uuid_equal(&type, &gpt_uuid_freebsd_ufs, NULL)) + return (PART_FREEBSD_UFS); + else if (uuid_equal(&type, &gpt_uuid_freebsd_zfs, NULL)) + return (PART_FREEBSD_ZFS); + else if (uuid_equal(&type, &gpt_uuid_freebsd_swap, NULL)) + return (PART_FREEBSD_SWAP); + else if (uuid_equal(&type, &gpt_uuid_freebsd_vinum, NULL)) + return (PART_FREEBSD_VINUM); + else if (uuid_equal(&type, &gpt_uuid_freebsd, NULL)) + return (PART_FREEBSD); + else if (uuid_equal(&type, &gpt_uuid_illumos_boot, NULL)) + return (PART_VTOC_BOOT); + else if (uuid_equal(&type, &gpt_uuid_illumos_ufs, NULL)) + return (PART_ILLUMOS_UFS); + else if (uuid_equal(&type, &gpt_uuid_illumos_zfs, NULL)) + return (PART_ILLUMOS_ZFS); + else if (uuid_equal(&type, &gpt_uuid_reserved, NULL)) + return (PART_RESERVED); + else if (uuid_equal(&type, &gpt_uuid_apple_apfs, NULL)) + return (PART_APFS); + return (PART_UNKNOWN); +} + +static struct gpt_hdr * +gpt_checkhdr(struct gpt_hdr *hdr, uint64_t lba_self, + uint64_t lba_last __attribute((unused)), uint16_t sectorsize) +{ + uint32_t sz, crc; + + if (memcmp(hdr->hdr_sig, GPT_HDR_SIG, sizeof (hdr->hdr_sig)) != 0) { + DPRINTF("no GPT signature"); + return (NULL); + } + sz = le32toh(hdr->hdr_size); + if (sz < 92 || sz > sectorsize) { + DPRINTF("invalid GPT header size: %u", sz); + return (NULL); + } + crc = le32toh(hdr->hdr_crc_self); + hdr->hdr_crc_self = crc32(0, Z_NULL, 0); + if (crc32(hdr->hdr_crc_self, (const Bytef *)hdr, sz) != crc) { + DPRINTF("GPT header's CRC doesn't match"); + return (NULL); + } + hdr->hdr_crc_self = crc; + hdr->hdr_revision = le32toh(hdr->hdr_revision); + if (hdr->hdr_revision < GPT_HDR_REVISION) { + DPRINTF("unsupported GPT revision %u", hdr->hdr_revision); + return (NULL); + } + hdr->hdr_lba_self = le64toh(hdr->hdr_lba_self); + if (hdr->hdr_lba_self != lba_self) { + DPRINTF("self LBA doesn't match"); + return (NULL); + } + hdr->hdr_lba_alt = le64toh(hdr->hdr_lba_alt); + if (hdr->hdr_lba_alt == hdr->hdr_lba_self) { + DPRINTF("invalid alternate LBA"); + return (NULL); + } + hdr->hdr_entries = le32toh(hdr->hdr_entries); + hdr->hdr_entsz = le32toh(hdr->hdr_entsz); + if (hdr->hdr_entries == 0 || + hdr->hdr_entsz < sizeof (struct gpt_ent) || + sectorsize % hdr->hdr_entsz != 0) { + DPRINTF("invalid entry size or number of entries"); + return (NULL); + } + hdr->hdr_lba_start = le64toh(hdr->hdr_lba_start); + hdr->hdr_lba_end = le64toh(hdr->hdr_lba_end); + hdr->hdr_lba_table = le64toh(hdr->hdr_lba_table); + hdr->hdr_crc_table = le32toh(hdr->hdr_crc_table); + uuid_letoh(&hdr->hdr_uuid); + return (hdr); +} + +static int +gpt_checktbl(const struct gpt_hdr *hdr, uint8_t *tbl, size_t size, + uint64_t lba_last __attribute((unused))) +{ + struct gpt_ent *ent; + uint32_t i, cnt; + + cnt = size / hdr->hdr_entsz; + if (hdr->hdr_entries <= cnt) { + cnt = hdr->hdr_entries; + /* Check CRC only when buffer size is enough for table. */ + if (hdr->hdr_crc_table != + crc32(0, tbl, hdr->hdr_entries * hdr->hdr_entsz)) { + DPRINTF("GPT table's CRC doesn't match"); + return (-1); + } + } + for (i = 0; i < cnt; i++) { + ent = (struct gpt_ent *)(tbl + i * hdr->hdr_entsz); + uuid_letoh(&ent->ent_type); + if (uuid_equal(&ent->ent_type, &gpt_uuid_unused, NULL)) + continue; + ent->ent_lba_start = le64toh(ent->ent_lba_start); + ent->ent_lba_end = le64toh(ent->ent_lba_end); + } + return (0); +} + +static struct ptable * +ptable_gptread(struct ptable *table, void *dev, diskread_t dread) +{ + struct pentry *entry; + struct gpt_hdr *phdr, hdr; + struct gpt_ent *ent; + uint8_t *buf, *tbl; + uint64_t offset; + int pri, sec; + size_t size, i; + + buf = malloc(table->sectorsize); + if (buf == NULL) + return (NULL); + tbl = malloc(table->sectorsize * MAXTBLSZ); + if (tbl == NULL) { + free(buf); + return (NULL); + } + /* Read the primary GPT header. */ + if (dread(dev, buf, 1, 1) != 0) { + ptable_close(table); + table = NULL; + goto out; + } + pri = sec = 0; + /* Check the primary GPT header. */ + phdr = gpt_checkhdr((struct gpt_hdr *)buf, 1, table->sectors - 1, + table->sectorsize); + if (phdr != NULL) { + /* Read the primary GPT table. */ + size = MIN(MAXTBLSZ, (phdr->hdr_entries * phdr->hdr_entsz + + table->sectorsize - 1) / table->sectorsize); + if (dread(dev, tbl, size, phdr->hdr_lba_table) == 0 && + gpt_checktbl(phdr, tbl, size * table->sectorsize, + table->sectors - 1) == 0) { + memcpy(&hdr, phdr, sizeof (hdr)); + pri = 1; + } + } + offset = pri ? hdr.hdr_lba_alt: table->sectors - 1; + /* Read the backup GPT header. */ + if (dread(dev, buf, 1, offset) != 0) + phdr = NULL; + else + phdr = gpt_checkhdr((struct gpt_hdr *)buf, offset, + table->sectors - 1, table->sectorsize); + if (phdr != NULL) { + /* + * Compare primary and backup headers. + * If they are equal, then we do not need to read backup + * table. If they are different, then prefer backup header + * and try to read backup table. + */ + if (pri == 0 || + uuid_equal(&hdr.hdr_uuid, &phdr->hdr_uuid, NULL) == 0 || + hdr.hdr_revision != phdr->hdr_revision || + hdr.hdr_size != phdr->hdr_size || + hdr.hdr_lba_start != phdr->hdr_lba_start || + hdr.hdr_lba_end != phdr->hdr_lba_end || + hdr.hdr_entries != phdr->hdr_entries || + hdr.hdr_entsz != phdr->hdr_entsz || + hdr.hdr_crc_table != phdr->hdr_crc_table) { + /* Read the backup GPT table. */ + size = MIN(MAXTBLSZ, (phdr->hdr_entries * + phdr->hdr_entsz + table->sectorsize - 1) / + table->sectorsize); + if (dread(dev, tbl, size, phdr->hdr_lba_table) == 0 && + gpt_checktbl(phdr, tbl, size * table->sectorsize, + table->sectors - 1) == 0) { + memcpy(&hdr, phdr, sizeof (hdr)); + sec = 1; + } + } + } + if (pri == 0 && sec == 0) { + /* Both primary and backup tables are invalid. */ + table->type = PTABLE_NONE; + goto out; + } + DPRINTF("GPT detected"); + size = MIN(hdr.hdr_entries * hdr.hdr_entsz, + MAXTBLSZ * table->sectorsize); + + /* + * If the disk's sector count is smaller than the sector count recorded + * in the disk's GPT table header, set the table->sectors to the value + * recorded in GPT tables. This is done to work around buggy firmware + * that returns truncated disk sizes. + * + * Note, this is still not a foolproof way to get disk's size. For + * example, an image file can be truncated when copied to smaller media. + */ + table->sectors = hdr.hdr_lba_alt + 1; + + for (i = 0; i < size / hdr.hdr_entsz; i++) { + ent = (struct gpt_ent *)(tbl + i * hdr.hdr_entsz); + if (uuid_equal(&ent->ent_type, &gpt_uuid_unused, NULL)) + continue; + + /* Simple sanity checks. */ + if (ent->ent_lba_start < hdr.hdr_lba_start || + ent->ent_lba_end > hdr.hdr_lba_end || + ent->ent_lba_start > ent->ent_lba_end) + continue; + + entry = malloc(sizeof (*entry)); + if (entry == NULL) + break; + entry->part.start = ent->ent_lba_start; + entry->part.end = ent->ent_lba_end; + entry->part.index = i + 1; + entry->part.type = gpt_parttype(ent->ent_type); + entry->flags = le64toh(ent->ent_attr); + memcpy(&entry->type.gpt, &ent->ent_type, sizeof (uuid_t)); + STAILQ_INSERT_TAIL(&table->entries, entry, entry); + DPRINTF("new GPT partition added"); + } +out: + free(buf); + free(tbl); + return (table); +} +#endif /* LOADER_GPT_SUPPORT */ + +#ifdef LOADER_MBR_SUPPORT +/* We do not need to support too many EBR partitions in the loader */ +#define MAXEBRENTRIES 8 +static enum partition_type +mbr_parttype(uint8_t type) +{ + + switch (type) { + case DOSPTYP_386BSD: + return (PART_FREEBSD); + case DOSPTYP_LINSWP: + return (PART_LINUX_SWAP); + case DOSPTYP_LINUX: + return (PART_LINUX); + case DOSPTYP_SUNIXOS2: + return (PART_SOLARIS2); + case 0x01: + case 0x04: + case 0x06: + case 0x07: + case 0x0b: + case 0x0c: + case 0x0e: + return (PART_DOS); + } + return (PART_UNKNOWN); +} + +static struct ptable * +ptable_ebrread(struct ptable *table, void *dev, diskread_t dread) +{ + struct dos_partition *dp; + struct pentry *e1, *entry; + uint32_t start, end, offset; + uint8_t *buf; + int i, idx; + + STAILQ_FOREACH(e1, &table->entries, entry) { + if (e1->type.mbr == DOSPTYP_EXT || + e1->type.mbr == DOSPTYP_EXTLBA) + break; + } + if (e1 == NULL) + return (table); + idx = 5; + offset = e1->part.start; + buf = malloc(table->sectorsize); + if (buf == NULL) + return (table); + DPRINTF("EBR detected"); + for (i = 0; i < MAXEBRENTRIES; i++) { +#if 0 /* Some BIOSes return an incorrect number of sectors */ + if (offset >= table->sectors) + break; +#endif + if (dread(dev, buf, 1, offset) != 0) + break; + dp = (struct dos_partition *)(buf + DOSPARTOFF); + if (dp[0].dp_typ == 0) + break; + start = le32toh(dp[0].dp_start); + if (dp[0].dp_typ == DOSPTYP_EXT && + dp[1].dp_typ == 0) { + offset = e1->part.start + start; + continue; + } + end = le32toh(dp[0].dp_size); + entry = malloc(sizeof (*entry)); + if (entry == NULL) + break; + entry->part.start = offset + start; + entry->part.end = entry->part.start + end - 1; + entry->part.index = idx++; + entry->part.type = mbr_parttype(dp[0].dp_typ); + entry->flags = dp[0].dp_flag; + entry->type.mbr = dp[0].dp_typ; + STAILQ_INSERT_TAIL(&table->entries, entry, entry); + DPRINTF("new EBR partition added"); + if (dp[1].dp_typ == 0) + break; + offset = e1->part.start + le32toh(dp[1].dp_start); + } + free(buf); + return (table); +} +#endif /* LOADER_MBR_SUPPORT */ + +static enum partition_type +bsd_parttype(uint8_t type) +{ + + switch (type) { + case FS_SWAP: + return (PART_FREEBSD_SWAP); + case FS_BSDFFS: + return (PART_FREEBSD_UFS); + case FS_VINUM: + return (PART_FREEBSD_VINUM); + case FS_ZFS: + return (PART_FREEBSD_ZFS); + } + return (PART_UNKNOWN); +} + +static struct ptable * +ptable_bsdread(struct ptable *table, void *dev, diskread_t dread) +{ + struct disklabel *dl; + struct partition *part; + struct pentry *entry; + uint8_t *buf; + uint32_t raw_offset; + int i; + + if (table->sectorsize < sizeof (struct disklabel)) { + DPRINTF("Too small sectorsize"); + return (table); + } + buf = malloc(table->sectorsize); + if (buf == NULL) + return (table); + if (dread(dev, buf, 1, 1) != 0) { + DPRINTF("read failed"); + ptable_close(table); + table = NULL; + goto out; + } + dl = (struct disklabel *)buf; + if (le32toh(dl->d_magic) != DISKMAGIC && + le32toh(dl->d_magic2) != DISKMAGIC) + goto out; + if (le32toh(dl->d_secsize) != table->sectorsize) { + DPRINTF("unsupported sector size"); + goto out; + } + dl->d_npartitions = le16toh(dl->d_npartitions); + if (dl->d_npartitions > 20 || dl->d_npartitions < 8) { + DPRINTF("invalid number of partitions"); + goto out; + } + DPRINTF("BSD detected"); + part = &dl->d_partitions[0]; + raw_offset = le32toh(part[RAW_PART].p_offset); + for (i = 0; i < dl->d_npartitions; i++, part++) { + if (i == RAW_PART) + continue; + if (part->p_size == 0) + continue; + entry = malloc(sizeof (*entry)); + if (entry == NULL) + break; + entry->part.start = le32toh(part->p_offset) - raw_offset; + entry->part.end = entry->part.start + + le32toh(part->p_size) - 1; + entry->part.type = bsd_parttype(part->p_fstype); + entry->part.index = i; /* starts from zero */ + entry->type.bsd = part->p_fstype; + STAILQ_INSERT_TAIL(&table->entries, entry, entry); + DPRINTF("new BSD partition added"); + } + table->type = PTABLE_BSD; +out: + free(buf); + return (table); +} + +#ifdef LOADER_VTOC8_SUPPORT +static enum partition_type +vtoc8_parttype(uint16_t type) +{ + + switch (type) { + case VTOC_TAG_FREEBSD_SWAP: + return (PART_FREEBSD_SWAP); + case VTOC_TAG_FREEBSD_UFS: + return (PART_FREEBSD_UFS); + case VTOC_TAG_FREEBSD_VINUM: + return (PART_FREEBSD_VINUM); + case VTOC_TAG_FREEBSD_ZFS: + return (PART_FREEBSD_ZFS); + }; + return (PART_UNKNOWN); +} + +static struct ptable * +ptable_vtoc8read(struct ptable *table, void *dev, diskread_t dread) +{ + struct pentry *entry; + struct vtoc8 *dl; + uint8_t *buf; + uint16_t sum, heads, sectors; + int i; + + if (table->sectorsize != sizeof (struct vtoc8)) + return (table); + buf = malloc(table->sectorsize); + if (buf == NULL) + return (table); + if (dread(dev, buf, 1, 0) != 0) { + DPRINTF("read failed"); + ptable_close(table); + table = NULL; + goto out; + } + dl = (struct vtoc8 *)buf; + /* Check the sum */ + for (i = sum = 0; i < sizeof (struct vtoc8); i += sizeof (sum)) + sum ^= be16dec(buf + i); + if (sum != 0) { + DPRINTF("incorrect checksum"); + goto out; + } + if (be16toh(dl->nparts) != VTOC8_NPARTS) { + DPRINTF("invalid number of entries"); + goto out; + } + sectors = be16toh(dl->nsecs); + heads = be16toh(dl->nheads); + if (sectors * heads == 0) { + DPRINTF("invalid geometry"); + goto out; + } + DPRINTF("VTOC8 detected"); + for (i = 0; i < VTOC8_NPARTS; i++) { + dl->part[i].tag = be16toh(dl->part[i].tag); + if (i == VTOC_RAW_PART || + dl->part[i].tag == VTOC_TAG_UNASSIGNED) + continue; + entry = malloc(sizeof (*entry)); + if (entry == NULL) + break; + entry->part.start = be32toh(dl->map[i].cyl) * heads * sectors; + entry->part.end = be32toh(dl->map[i].nblks) + + entry->part.start - 1; + entry->part.type = vtoc8_parttype(dl->part[i].tag); + entry->part.index = i; /* starts from zero */ + entry->type.vtoc8 = dl->part[i].tag; + STAILQ_INSERT_TAIL(&table->entries, entry, entry); + DPRINTF("new VTOC8 partition added"); + } + table->type = PTABLE_VTOC8; +out: + free(buf); + return (table); + +} +#endif /* LOADER_VTOC8_SUPPORT */ + +static enum partition_type +vtoc_parttype(uint16_t type) +{ + switch (type) { + case VTOC_TAG_BOOT: + return (PART_VTOC_BOOT); + case VTOC_TAG_ROOT: + return (PART_VTOC_ROOT); + case VTOC_TAG_SWAP: + return (PART_VTOC_SWAP); + case VTOC_TAG_USR: + return (PART_VTOC_USR); + case VTOC_TAG_BACKUP: + return (PART_VTOC_BACKUP); + case VTOC_TAG_STAND: + return (PART_VTOC_STAND); + case VTOC_TAG_VAR: + return (PART_VTOC_VAR); + case VTOC_TAG_HOME: + return (PART_VTOC_HOME); + }; + return (PART_UNKNOWN); +} + +static struct ptable * +ptable_dklabelread(struct ptable *table, void *dev, diskread_t dread) +{ + struct pentry *entry; + struct dk_label *dl; + struct dk_vtoc *dv; + uint8_t *buf; + int i; + + if (table->sectorsize < sizeof (struct dk_label)) { + DPRINTF("Too small sectorsize"); + return (table); + } + buf = malloc(table->sectorsize); + if (buf == NULL) + return (table); + if (dread(dev, buf, 1, DK_LABEL_LOC) != 0) { + DPRINTF("read failed"); + ptable_close(table); + table = NULL; + goto out; + } + dl = (struct dk_label *)buf; + dv = (struct dk_vtoc *)&dl->dkl_vtoc; + + if (dl->dkl_magic != VTOC_MAGIC) { + DPRINTF("dk_label magic error"); + goto out; + } + if (dv->v_sanity != VTOC_SANITY) { + DPRINTF("this vtoc is not sane"); + goto out; + } + if (dv->v_nparts != NDKMAP) { + DPRINTF("invalid number of entries"); + goto out; + } + DPRINTF("VTOC detected"); + for (i = 0; i < NDKMAP; i++) { + if (i == VTOC_RAW_PART || /* skip slice 2 and empty */ + dv->v_part[i].p_size == 0) + continue; + entry = malloc(sizeof (*entry)); + if (entry == NULL) + break; + entry->part.start = dv->v_part[i].p_start; + entry->part.end = dv->v_part[i].p_size + + entry->part.start - 1; + entry->part.type = vtoc_parttype(dv->v_part[i].p_tag); + entry->part.index = i; /* starts from zero */ + entry->type.vtoc = dv->v_part[i].p_tag; + STAILQ_INSERT_TAIL(&table->entries, entry, entry); + DPRINTF("new VTOC partition added"); + } + table->type = PTABLE_VTOC; +out: + free(buf); + return (table); +} + +#define cdb2devb(bno) ((bno) * ISO_DEFAULT_BLOCK_SIZE / table->sectorsize) + +static struct ptable * +ptable_iso9660read(struct ptable *table, void *dev, diskread_t dread) +{ + uint8_t *buf; + struct iso_primary_descriptor *vd; + struct pentry *entry; + + buf = malloc(table->sectorsize); + if (buf == NULL) + return (table); + + if (dread(dev, buf, 1, cdb2devb(16)) != 0) { + DPRINTF("read failed"); + ptable_close(table); + table = NULL; + goto out; + } + vd = (struct iso_primary_descriptor *)buf; + if (bcmp(vd->id, ISO_STANDARD_ID, sizeof (vd->id)) != 0) + goto out; + + entry = malloc(sizeof (*entry)); + if (entry == NULL) + goto out; + entry->part.start = 0; + entry->part.end = table->sectors; + entry->part.type = PART_ISO9660; + entry->part.index = 0; + STAILQ_INSERT_TAIL(&table->entries, entry, entry); + + table->type = PTABLE_ISO9660; + +out: + free(buf); + return (table); +} + +struct ptable * +ptable_open(void *dev, uint64_t sectors, uint16_t sectorsize, diskread_t *dread) +{ + struct dos_partition *dp; + struct ptable *table; + uint8_t *buf; + int i; +#ifdef LOADER_MBR_SUPPORT + struct pentry *entry; + uint32_t start, end; + int has_ext; +#endif + table = NULL; + dp = NULL; + buf = malloc(sectorsize); + if (buf == NULL) + return (NULL); + /* First, read the MBR. */ + if (dread(dev, buf, 1, DOSBBSECTOR) != 0) { + DPRINTF("read failed"); + goto out; + } + + table = malloc(sizeof (*table)); + if (table == NULL) + goto out; + table->sectors = sectors; + table->sectorsize = sectorsize; + table->type = PTABLE_NONE; + STAILQ_INIT(&table->entries); + + if (ptable_iso9660read(table, dev, dread) == NULL) { + /* Read error. */ + table = NULL; + goto out; + } else if (table->type == PTABLE_ISO9660) + goto out; + + if (ptable_dklabelread(table, dev, dread) == NULL) { /* Read error. */ + table = NULL; + goto out; + } else if (table->type == PTABLE_VTOC) + goto out; + +#ifdef LOADER_VTOC8_SUPPORT + if (be16dec(buf + offsetof(struct vtoc8, magic)) == VTOC_MAGIC) { + if (ptable_vtoc8read(table, dev, dread) == NULL) { + /* Read error. */ + table = NULL; + goto out; + } else if (table->type == PTABLE_VTOC8) + goto out; + } +#endif + /* Check the BSD label. */ + if (ptable_bsdread(table, dev, dread) == NULL) { /* Read error. */ + table = NULL; + goto out; + } else if (table->type == PTABLE_BSD) + goto out; + +#if defined(LOADER_GPT_SUPPORT) || defined(LOADER_MBR_SUPPORT) + /* Check the MBR magic. */ + if (buf[DOSMAGICOFFSET] != 0x55 || + buf[DOSMAGICOFFSET + 1] != 0xaa) { + DPRINTF("magic sequence not found"); +#if defined(LOADER_GPT_SUPPORT) + /* There is no PMBR, check that we have backup GPT */ + table->type = PTABLE_GPT; + table = ptable_gptread(table, dev, dread); +#endif + goto out; + } + /* Check that we have PMBR. Also do some validation. */ + dp = malloc(NDOSPART * sizeof (struct dos_partition)); + if (dp == NULL) + goto out; + bcopy(buf + DOSPARTOFF, dp, NDOSPART * sizeof (struct dos_partition)); + + /* + * macOS can create PMBR partition in a hybrid MBR; that is, an MBR + * partition which has a DOSTYP_PMBR entry defined to start at sector 1. + * After the DOSTYP_PMBR, there may be other paritions. A UEFI + * compliant PMBR has no other partitions. + */ + for (i = 0; i < NDOSPART; i++) { + if (dp[i].dp_flag != 0 && dp[i].dp_flag != 0x80) { + DPRINTF("invalid partition flag %x", dp[i].dp_flag); + goto out; + } +#ifdef LOADER_GPT_SUPPORT + if (dp[i].dp_typ == DOSPTYP_PMBR && dp[i].dp_start == 1) { + table->type = PTABLE_GPT; + DPRINTF("PMBR detected"); + } +#endif + } +#ifdef LOADER_GPT_SUPPORT + if (table->type == PTABLE_GPT) { + table = ptable_gptread(table, dev, dread); + goto out; + } +#endif +#ifdef LOADER_MBR_SUPPORT + /* Read MBR. */ + DPRINTF("MBR detected"); + table->type = PTABLE_MBR; + for (i = has_ext = 0; i < NDOSPART; i++) { + if (dp[i].dp_typ == 0) + continue; + start = le32dec(&(dp[i].dp_start)); + end = le32dec(&(dp[i].dp_size)); + if (start == 0 || end == 0) + continue; +#if 0 /* Some BIOSes return an incorrect number of sectors */ + if (start + end - 1 >= sectors) + continue; /* XXX: ignore */ +#endif + if (dp[i].dp_typ == DOSPTYP_EXT || + dp[i].dp_typ == DOSPTYP_EXTLBA) + has_ext = 1; + entry = malloc(sizeof (*entry)); + if (entry == NULL) + break; + entry->part.start = start; + entry->part.end = start + end - 1; + entry->part.index = i + 1; + entry->part.type = mbr_parttype(dp[i].dp_typ); + entry->flags = dp[i].dp_flag; + entry->type.mbr = dp[i].dp_typ; + STAILQ_INSERT_TAIL(&table->entries, entry, entry); + DPRINTF("new MBR partition added"); + } + if (has_ext) { + table = ptable_ebrread(table, dev, dread); + /* FALLTHROUGH */ + } +#endif /* LOADER_MBR_SUPPORT */ +#endif /* LOADER_MBR_SUPPORT || LOADER_GPT_SUPPORT */ +out: + free(dp); + free(buf); + return (table); +} + +void +ptable_close(struct ptable *table) +{ + struct pentry *entry; + + if (table == NULL) + return; + + while (!STAILQ_EMPTY(&table->entries)) { + entry = STAILQ_FIRST(&table->entries); + STAILQ_REMOVE_HEAD(&table->entries, entry); + free(entry); + } + free(table); +} + +enum ptable_type +ptable_gettype(const struct ptable *table) +{ + + return (table->type); +} + +int +ptable_getsize(const struct ptable *table, uint64_t *sizep) +{ + uint64_t tmp = table->sectors * table->sectorsize; + + if (tmp < table->sectors) + return (EOVERFLOW); + + if (sizep != NULL) + *sizep = tmp; + return (0); +} + +int +ptable_getpart(const struct ptable *table, struct ptable_entry *part, int idx) +{ + struct pentry *entry; + + if (part == NULL || table == NULL) + return (EINVAL); + + STAILQ_FOREACH(entry, &table->entries, entry) { + if (entry->part.index != idx) + continue; + memcpy(part, &entry->part, sizeof (*part)); + return (0); + } + return (ENOENT); +} + +/* + * Search for a slice with the following preferences: + * + * 1: Active illumos slice + * 2: Non-active illumos slice + * 3: Active Linux slice + * 4: non-active Linux slice + * 5: Active FAT/FAT32 slice + * 6: non-active FAT/FAT32 slice + */ +#define PREF_RAWDISK 0 +#define PREF_ILLUMOS_ACT 1 +#define PREF_ILLUMOS 2 +#define PREF_LINUX_ACT 3 +#define PREF_LINUX 4 +#define PREF_DOS_ACT 5 +#define PREF_DOS 6 +#define PREF_NONE 7 +int +ptable_getbestpart(const struct ptable *table, struct ptable_entry *part) +{ + struct pentry *entry, *best; + int pref, preflevel; + + if (part == NULL || table == NULL) + return (EINVAL); + + best = NULL; + preflevel = pref = PREF_NONE; + STAILQ_FOREACH(entry, &table->entries, entry) { +#ifdef LOADER_MBR_SUPPORT + if (table->type == PTABLE_MBR) { + switch (entry->type.mbr) { + case DOSPTYP_SUNIXOS2: + pref = entry->flags & 0x80 ? PREF_ILLUMOS_ACT: + PREF_ILLUMOS; + break; + case DOSPTYP_LINUX: + pref = entry->flags & 0x80 ? PREF_LINUX_ACT: + PREF_LINUX; + break; + case 0x01: /* DOS/Windows */ + case 0x04: + case 0x06: + case 0x0c: + case 0x0e: + case DOSPTYP_FAT32: + pref = entry->flags & 0x80 ? PREF_DOS_ACT: + PREF_DOS; + break; + default: + pref = PREF_NONE; + } + } +#endif /* LOADER_MBR_SUPPORT */ +#ifdef LOADER_GPT_SUPPORT + if (table->type == PTABLE_GPT) { + if (entry->part.type == PART_DOS) + pref = PREF_DOS; + else if (entry->part.type == PART_ILLUMOS_ZFS) + pref = PREF_ILLUMOS; + else + pref = PREF_NONE; + } +#endif /* LOADER_GPT_SUPPORT */ + if (pref < preflevel) { + preflevel = pref; + best = entry; + } + } + if (best != NULL) { + memcpy(part, &best->part, sizeof (*part)); + return (0); + } + return (ENOENT); +} + +/* + * iterate will stop if iterator will return non 0. + */ +int +ptable_iterate(const struct ptable *table, void *arg, ptable_iterate_t *iter) +{ + struct pentry *entry; + char name[32]; + int ret = 0; + + name[0] = '\0'; + STAILQ_FOREACH(entry, &table->entries, entry) { +#ifdef LOADER_MBR_SUPPORT + if (table->type == PTABLE_MBR) + sprintf(name, "s%d", entry->part.index); + else +#endif +#ifdef LOADER_GPT_SUPPORT + if (table->type == PTABLE_GPT) + sprintf(name, "p%d", entry->part.index); + else +#endif +#ifdef LOADER_VTOC8_SUPPORT + if (table->type == PTABLE_VTOC8) + sprintf(name, "%c", (uint8_t)'a' + + entry->part.index); + else +#endif + if (table->type == PTABLE_VTOC) + sprintf(name, "%c", (uint8_t)'a' + + entry->part.index); + else + if (table->type == PTABLE_BSD) + sprintf(name, "%c", (uint8_t)'a' + + entry->part.index); + ret = iter(arg, name, &entry->part); + if (ret != 0) + return (ret); + } + return (ret); +} diff --git a/usr/src/boot/common/part.h b/usr/src/boot/common/part.h new file mode 100644 index 0000000000..bdeddcbd5c --- /dev/null +++ b/usr/src/boot/common/part.h @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2012 Andrey V. Elsukov <ae@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 AUTHORS 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 AUTHORS 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. + */ + +#ifndef _PART_H_ +#define _PART_H_ + +struct ptable; + +enum ptable_type { + PTABLE_NONE, + PTABLE_BSD, + PTABLE_MBR, + PTABLE_GPT, + PTABLE_VTOC8, + PTABLE_VTOC, + PTABLE_ISO9660 +}; + +enum partition_type { + PART_UNKNOWN, + PART_EFI, + PART_FREEBSD, + PART_FREEBSD_BOOT, + PART_FREEBSD_UFS, + PART_FREEBSD_ZFS, + PART_FREEBSD_SWAP, + PART_FREEBSD_VINUM, + PART_LINUX, + PART_LINUX_SWAP, + PART_DOS, + PART_ISO9660, + PART_SOLARIS2, + PART_ILLUMOS_UFS, + PART_ILLUMOS_ZFS, + PART_RESERVED, + PART_VTOC_BOOT, + PART_VTOC_ROOT, + PART_VTOC_SWAP, + PART_VTOC_USR, + PART_VTOC_BACKUP, + PART_VTOC_STAND, + PART_VTOC_VAR, + PART_VTOC_HOME, + PART_APFS +}; + +struct ptable_entry { + uint64_t start; + uint64_t end; + int index; + enum partition_type type; +}; + +/* The offset and size are in sectors */ +typedef int (diskread_t)(void *arg, void *buf, size_t blocks, uint64_t offset); +typedef int (ptable_iterate_t)(void *arg, const char *partname, + const struct ptable_entry *part); + +struct ptable *ptable_open(void *dev, uint64_t sectors, uint16_t sectorsize, + diskread_t *dread); +void ptable_close(struct ptable *table); +enum ptable_type ptable_gettype(const struct ptable *table); +int ptable_getsize(const struct ptable *table, uint64_t *sizep); + +int ptable_getpart(const struct ptable *table, struct ptable_entry *part, + int index); +int ptable_getbestpart(const struct ptable *table, struct ptable_entry *part); + +int ptable_iterate(const struct ptable *table, void *arg, + ptable_iterate_t *iter); +const char *parttype2str(enum partition_type type); + +#endif /* !_PART_H_ */ diff --git a/usr/src/boot/common/paths.h b/usr/src/boot/common/paths.h new file mode 100644 index 0000000000..3934ef4909 --- /dev/null +++ b/usr/src/boot/common/paths.h @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2016 M. Warner Losh <imp@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 AUTHORS 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 AUTHORS 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. + */ + +#ifndef _PATHS_H_ +#define _PATHS_H_ + +#define PATH_DOTCONFIG "/boot.config" +#define PATH_CONFIG "/boot/config" +#define PATH_LOADER "/boot/loader" +#define PATH_LOADER_EFI "/boot/" LOADER_EFI +#define PATH_KERNEL "/boot/kernel/kernel" + +#endif /* _PATHS_H_ */ diff --git a/usr/src/boot/common/pnp.c b/usr/src/boot/common/pnp.c new file mode 100644 index 0000000000..14b0b965f0 --- /dev/null +++ b/usr/src/boot/common/pnp.c @@ -0,0 +1,225 @@ +/* + * mjs copyright + * + */ + +#include <sys/cdefs.h> + +/* + * "Plug and Play" functionality. + * + * We use the PnP enumerators to obtain identifiers for installed hardware, + * and the contents of a database to determine modules to be loaded to support + * such hardware. + */ + +#include <stand.h> +#include <string.h> +#include <bootstrap.h> +#include "ficl.h" + +static struct pnpinfo_stql pnp_devices; +static int pnp_devices_initted = 0; + +static void pnp_discard(void); + +/* + * Perform complete enumeration sweep + */ + +COMMAND_SET(pnpscan, "pnpscan", "scan for PnP devices", pnp_scan); + +static int +pnp_scan(int argc, char *argv[]) +{ + struct pnpinfo *pi; + int hdlr; + int verbose; + int ch; + + if (pnp_devices_initted == 0) { + STAILQ_INIT(&pnp_devices); + pnp_devices_initted = 1; + } + + 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); + } + } + + /* forget anything we think we knew */ + pnp_discard(); + + /* iterate over all of the handlers */ + for (hdlr = 0; pnphandlers[hdlr] != NULL; hdlr++) { + if (verbose) + printf("Probing %s...\n", pnphandlers[hdlr]->pp_name); + pnphandlers[hdlr]->pp_enumerate(); + } + if (verbose) { + pager_open(); + pager_output("PNP scan summary:\n"); + STAILQ_FOREACH(pi, &pnp_devices, pi_link) { + pager_output(STAILQ_FIRST(&pi->pi_ident)->id_ident); /* first ident should be canonical */ + if (pi->pi_desc != NULL) { + pager_output(" : "); + pager_output(pi->pi_desc); + } + pager_output("\n"); + } + pager_close(); + } + return(CMD_OK); +} + +/* + * Throw away anything we think we know about PnP devices. + */ +static void +pnp_discard(void) +{ + struct pnpinfo *pi; + + while (STAILQ_FIRST(&pnp_devices) != NULL) { + pi = STAILQ_FIRST(&pnp_devices); + STAILQ_REMOVE_HEAD(&pnp_devices, pi_link); + pnp_freeinfo(pi); + } +} + +/* + * Add a unique identifier to (pi) + */ +void +pnp_addident(struct pnpinfo *pi, char *ident) +{ + struct pnpident *id; + + STAILQ_FOREACH(id, &pi->pi_ident, id_link) + if (!strcmp(id->id_ident, ident)) + return; /* already have this one */ + + id = malloc(sizeof(struct pnpident)); + id->id_ident = strdup(ident); + STAILQ_INSERT_TAIL(&pi->pi_ident, id, id_link); +} + +/* + * Allocate a new pnpinfo struct + */ +struct pnpinfo * +pnp_allocinfo(void) +{ + struct pnpinfo *pi; + + pi = malloc(sizeof(struct pnpinfo)); + bzero(pi, sizeof(struct pnpinfo)); + STAILQ_INIT(&pi->pi_ident); + return(pi); +} + +/* + * Release storage held by a pnpinfo struct + */ +void +pnp_freeinfo(struct pnpinfo *pi) +{ + struct pnpident *id; + + while (!STAILQ_EMPTY(&pi->pi_ident)) { + id = STAILQ_FIRST(&pi->pi_ident); + STAILQ_REMOVE_HEAD(&pi->pi_ident, id_link); + free(id->id_ident); + free(id); + } + if (pi->pi_desc) + free(pi->pi_desc); + if (pi->pi_module) + free(pi->pi_module); + if (pi->pi_argv) + free(pi->pi_argv); + free(pi); +} + +/* + * Add a new pnpinfo struct to the list. + */ +void +pnp_addinfo(struct pnpinfo *pi) +{ + STAILQ_INSERT_TAIL(&pnp_devices, pi, pi_link); +} + + +/* + * Format an EISA id as a string in standard ISA PnP format, AAAIIRR + * where 'AAA' is the EISA vendor ID, II is the product ID and RR the revision ID. + */ +char * +pnp_eisaformat(u_int8_t *data) +{ + static char idbuf[8]; + const char hextoascii[] = "0123456789abcdef"; + + idbuf[0] = '@' + ((data[0] & 0x7c) >> 2); + idbuf[1] = '@' + (((data[0] & 0x3) << 3) + ((data[1] & 0xe0) >> 5)); + idbuf[2] = '@' + (data[1] & 0x1f); + idbuf[3] = hextoascii[(data[2] >> 4)]; + idbuf[4] = hextoascii[(data[2] & 0xf)]; + idbuf[5] = hextoascii[(data[3] >> 4)]; + idbuf[6] = hextoascii[(data[3] & 0xf)]; + idbuf[7] = 0; + return(idbuf); +} + +void +ficlPnpdevices(ficlVm *pVM) +{ + static int pnp_devices_initted = 0; + + FICL_STACK_CHECK(ficlVmGetDataStack(pVM), 0, 1); + + if (!pnp_devices_initted) { + STAILQ_INIT(&pnp_devices); + pnp_devices_initted = 1; + } + + ficlStackPushPointer(ficlVmGetDataStack(pVM), &pnp_devices); +} + +void +ficlPnphandlers(ficlVm *pVM) +{ + FICL_STACK_CHECK(ficlVmGetDataStack(pVM), 0, 1); + + ficlStackPushPointer(ficlVmGetDataStack(pVM), pnphandlers); +} + +/* + * Glue function to add the appropriate forth words to access pnp BIOS + * functionality. + */ +static void +ficlCompilePnp(ficlSystem *pSys) +{ + ficlDictionary *dp = ficlSystemGetDictionary(pSys); + + FICL_SYSTEM_ASSERT(pSys, dp); + + ficlDictionarySetPrimitive(dp, "pnpdevices", ficlPnpdevices, + FICL_WORD_DEFAULT); + ficlDictionarySetPrimitive(dp, "pnphandlers", ficlPnphandlers, + FICL_WORD_DEFAULT); +} + +FICL_COMPILE_SET(ficlCompilePnp); diff --git a/usr/src/boot/common/rbx.h b/usr/src/boot/common/rbx.h new file mode 100644 index 0000000000..5fdb9075b2 --- /dev/null +++ b/usr/src/boot/common/rbx.h @@ -0,0 +1,60 @@ +/* + * Copyright (c) 1998 Robert Nordier + * All rights reserved. + * + * Redistribution and use in source and binary forms are freely + * permitted provided that the above copyright notice and this + * paragraph and the following disclaimer are duplicated in all + * such forms. + * + * This software is provided "AS IS" and without any express or + * implied warranties, including, without limitation, the implied + * warranties of merchantability and fitness for a particular + * purpose. + */ + +#ifndef _RBX_H_ +#define _RBX_H_ + +#define RBX_ASKNAME 0x0 /* -a */ +#define RBX_SINGLE 0x1 /* -s */ +/* 0x2 is reserved for log2(RB_NOSYNC). */ +/* 0x3 is reserved for log2(RB_HALT). */ +/* 0x4 is reserved for log2(RB_INITNAME). */ +#define RBX_DFLTROOT 0x5 /* -r */ +#define RBX_KDB 0x6 /* -d */ +/* 0x7 is reserved for log2(RB_RDONLY). */ +/* 0x8 is reserved for log2(RB_DUMP). */ +/* 0x9 is reserved for log2(RB_MINIROOT). */ +#define RBX_CONFIG 0xa /* -c */ +#define RBX_VERBOSE 0xb /* -v */ +#define RBX_SERIAL 0xc /* -h */ +#define RBX_CDROM 0xd /* -C */ +/* 0xe is reserved for log2(RB_POWEROFF). */ +#define RBX_GDB 0xf /* -g */ +#define RBX_MUTE 0x10 /* -m */ +/* 0x11 is reserved for log2(RB_SELFTEST). */ +/* 0x12 is reserved for boot programs. */ +#define RBX_TEXT_MODE 0x13 /* -t */ +#define RBX_PAUSE 0x14 /* -p */ +#define RBX_QUIET 0x15 /* -q */ +#define RBX_NOINTR 0x1c /* -n */ +/* 0x1d is reserved for log2(RB_MULTIPLE) and is just misnamed here. */ +#define RBX_DUAL 0x1d /* -D */ +/* 0x1f is reserved for log2(RB_BOOTINFO). */ + +/* pass: -a, -s, -r, -d, -c, -v, -h, -C, -g, -m, -p, -D, -t */ +#define RBX_MASK (OPT_SET(RBX_ASKNAME) | OPT_SET(RBX_SINGLE) | \ + OPT_SET(RBX_DFLTROOT) | OPT_SET(RBX_KDB ) | \ + OPT_SET(RBX_CONFIG) | OPT_SET(RBX_VERBOSE) | \ + OPT_SET(RBX_SERIAL) | OPT_SET(RBX_CDROM) | \ + OPT_SET(RBX_GDB ) | OPT_SET(RBX_MUTE) | \ + OPT_SET(RBX_PAUSE) | OPT_SET(RBX_DUAL) | \ + OPT_SET(RBX_TEXT_MODE)) + +#define OPT_SET(opt) (1 << (opt)) +#define OPT_CHECK(opt) ((opts) & OPT_SET(opt)) + +extern uint32_t opts; + +#endif /* !_RBX_H_ */ diff --git a/usr/src/boot/common/reloc_elf.c b/usr/src/boot/common/reloc_elf.c new file mode 100644 index 0000000000..188d259069 --- /dev/null +++ b/usr/src/boot/common/reloc_elf.c @@ -0,0 +1,231 @@ +/*- + * Copyright (c) 2003 Jake Burkholder. + * Copyright 1996-1998 John D. Polstra. + * Copyright (c) 1998 Michael Smith <msmith@freebsd.org> + * Copyright (c) 1998 Peter Wemm <peter@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/types.h> +#include <machine/elf.h> + +#include <stand.h> + +#define FREEBSD_ELF +#include <link.h> + +#include "bootstrap.h" + +#define COPYOUT(s,d,l) archsw.arch_copyout((vm_offset_t)(s), d, l) + +/* + * Apply a single intra-module relocation to the data. `relbase' is the + * target relocation base for the section (i.e. it corresponds to where + * r_offset == 0). `dataaddr' is the relocated address corresponding to + * the start of the data, and `len' is the number of bytes. + */ +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) +{ +#ifdef __sparc__ + Elf_Size w; + const Elf_Rela *a; + + switch (reltype) { + case ELF_RELOC_RELA: + a = reldata; + if (relbase + a->r_offset >= dataaddr && + relbase + a->r_offset < dataaddr + len) { + switch (ELF_R_TYPE(a->r_info)) { + case R_SPARC_RELATIVE: + w = relbase + a->r_addend; + bcopy(&w, (u_char *)data + (relbase + + a->r_offset - dataaddr), sizeof(w)); + break; + default: + printf("\nunhandled relocation type %u\n", + (u_int)ELF_R_TYPE(a->r_info)); + return (EFTYPE); + } + } + break; + } + + return (0); +#elif (defined(__i386__) || defined(__amd64__)) && __ELF_WORD_SIZE == 64 + Elf64_Addr *where, val; + Elf_Addr addend, addr; + Elf_Size rtype, symidx; + const Elf_Rel *rel; + const Elf_Rela *rela; + + switch (reltype) { + case ELF_RELOC_REL: + rel = (const Elf_Rel *)reldata; + where = (Elf_Addr *)((char *)data + relbase + rel->r_offset - + dataaddr); + addend = 0; + rtype = ELF_R_TYPE(rel->r_info); + symidx = ELF_R_SYM(rel->r_info); + addend = 0; + break; + case ELF_RELOC_RELA: + rela = (const Elf_Rela *)reldata; + where = (Elf_Addr *)((char *)data + relbase + rela->r_offset - + dataaddr); + addend = rela->r_addend; + rtype = ELF_R_TYPE(rela->r_info); + symidx = ELF_R_SYM(rela->r_info); + break; + default: + return (EINVAL); + } + + if ((char *)where < (char *)data || (char *)where >= (char *)data + len) + return (0); + + if (reltype == ELF_RELOC_REL) + addend = *where; + +/* XXX, definitions not available on i386. */ +#define R_X86_64_64 1 +#define R_X86_64_RELATIVE 8 + + switch (rtype) { + case R_X86_64_64: /* S + A */ + addr = symaddr(ef, symidx); + if (addr == 0) + return (ESRCH); + val = addr + addend; + *where = val; + break; + case R_X86_64_RELATIVE: + addr = (Elf_Addr)addend + relbase; + val = addr; + *where = val; + break; + default: + printf("\nunhandled relocation type %u\n", (u_int)rtype); + return (EFTYPE); + } + + return (0); +#elif defined(__i386__) && __ELF_WORD_SIZE == 32 + Elf_Addr addend, addr, *where, val; + Elf_Size rtype, symidx; + const Elf_Rel *rel; + const Elf_Rela *rela; + + switch (reltype) { + case ELF_RELOC_REL: + rel = (const Elf_Rel *)reldata; + where = (Elf_Addr *)((char *)data + relbase + rel->r_offset - + dataaddr); + addend = 0; + rtype = ELF_R_TYPE(rel->r_info); + symidx = ELF_R_SYM(rel->r_info); + addend = 0; + break; + case ELF_RELOC_RELA: + rela = (const Elf_Rela *)reldata; + where = (Elf_Addr *)((char *)data + relbase + rela->r_offset - + dataaddr); + addend = rela->r_addend; + rtype = ELF_R_TYPE(rela->r_info); + symidx = ELF_R_SYM(rela->r_info); + break; + default: + return (EINVAL); + } + + if ((char *)where < (char *)data || (char *)where >= (char *)data + len) + return (0); + + if (reltype == ELF_RELOC_REL) + addend = *where; + +/* XXX, definitions not available on amd64. */ +#define R_386_32 1 /* Add symbol value. */ +#define R_386_GLOB_DAT 6 /* Set GOT entry to data address. */ +#define R_386_RELATIVE 8 /* Add load address of shared object. */ + + switch (rtype) { + case R_386_RELATIVE: + addr = addend + relbase; + *where = addr; + break; + case R_386_32: /* S + A */ + addr = symaddr(ef, symidx); + if (addr == 0) + return (ESRCH); + val = addr + addend; + *where = val; + break; + default: + printf("\nunhandled relocation type %u\n", (u_int)rtype); + return (EFTYPE); + } + + return (0); +#elif defined(__powerpc__) + Elf_Size w; + const Elf_Rela *rela; + + switch (reltype) { + case ELF_RELOC_RELA: + rela = reldata; + if (relbase + rela->r_offset >= dataaddr && + relbase + rela->r_offset < dataaddr + len) { + switch (ELF_R_TYPE(rela->r_info)) { + case R_PPC_RELATIVE: + w = relbase + rela->r_addend; + bcopy(&w, (u_char *)data + (relbase + + rela->r_offset - dataaddr), sizeof(w)); + break; + default: + printf("\nunhandled relocation type %u\n", + (u_int)ELF_R_TYPE(rela->r_info)); + return (EFTYPE); + } + } + break; + } + + return (0); +#else + (void)ef; + (void)symaddr; + (void)reldata; + (void)reltype; + (void)relbase; + (void)dataaddr; + (void)data; + (void)len; + return (EOPNOTSUPP); +#endif +} diff --git a/usr/src/boot/common/reloc_elf32.c b/usr/src/boot/common/reloc_elf32.c new file mode 100644 index 0000000000..03d9d73bab --- /dev/null +++ b/usr/src/boot/common/reloc_elf32.c @@ -0,0 +1,6 @@ +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#define __ELF_WORD_SIZE 32 + +#include "reloc_elf.c" diff --git a/usr/src/boot/common/reloc_elf64.c b/usr/src/boot/common/reloc_elf64.c new file mode 100644 index 0000000000..c8dcf2a36b --- /dev/null +++ b/usr/src/boot/common/reloc_elf64.c @@ -0,0 +1,6 @@ +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#define __ELF_WORD_SIZE 64 + +#include "reloc_elf.c" diff --git a/usr/src/boot/common/self_reloc.c b/usr/src/boot/common/self_reloc.c new file mode 100644 index 0000000000..f82006078f --- /dev/null +++ b/usr/src/boot/common/self_reloc.c @@ -0,0 +1,123 @@ +/*- + * Copyright (c) 2008-2010 Rui Paulo <rpaulo@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> + +#include <sys/types.h> +#include <elf.h> +#include <bootstrap.h> + +#if defined(__aarch64__) || defined(__amd64__) +#define ElfW_Rel Elf64_Rela +#define ElfW_Dyn Elf64_Dyn +#define ELFW_R_TYPE ELF64_R_TYPE +#define ELF_RELA +#elif defined(__arm__) || defined(__i386__) +#define ElfW_Rel Elf32_Rel +#define ElfW_Dyn Elf32_Dyn +#define ELFW_R_TYPE ELF32_R_TYPE +#else +#error architecture not supported +#endif +#if defined(__aarch64__) +#define RELOC_TYPE_NONE R_AARCH64_NONE +#define RELOC_TYPE_RELATIVE R_AARCH64_RELATIVE +#elif defined(__amd64__) +#define RELOC_TYPE_NONE R_X86_64_NONE +#define RELOC_TYPE_RELATIVE R_X86_64_RELATIVE +#elif defined(__arm__) +#define RELOC_TYPE_NONE R_ARM_NONE +#define RELOC_TYPE_RELATIVE R_ARM_RELATIVE +#elif defined(__i386__) +#define RELOC_TYPE_NONE R_386_NONE +#define RELOC_TYPE_RELATIVE R_386_RELATIVE +#endif + +void self_reloc(Elf_Addr baseaddr, ElfW_Dyn *dynamic); + +/* + * A simple elf relocator. + */ +void +self_reloc(Elf_Addr baseaddr, ElfW_Dyn *dynamic) +{ + Elf_Word relsz, relent; + Elf_Addr *newaddr; + ElfW_Rel *rel = 0; + ElfW_Dyn *dynp; + + /* + * Find the relocation address, its size and the relocation entry. + */ + relsz = 0; + relent = 0; + for (dynp = dynamic; dynp->d_tag != DT_NULL; dynp++) { + switch (dynp->d_tag) { + case DT_REL: + case DT_RELA: + rel = (ElfW_Rel *)(dynp->d_un.d_ptr + baseaddr); + break; + case DT_RELSZ: + case DT_RELASZ: + relsz = dynp->d_un.d_val; + break; + case DT_RELENT: + case DT_RELAENT: + relent = dynp->d_un.d_val; + break; + default: + break; + } + } + + /* + * Perform the actual relocation. We rely on the object having been + * linked at 0, so that the difference between the load and link + * address is the same as the load address. + */ + for (; relsz > 0; relsz -= relent) { + switch (ELFW_R_TYPE(rel->r_info)) { + case RELOC_TYPE_NONE: + /* No relocation needs be performed. */ + break; + + case RELOC_TYPE_RELATIVE: + newaddr = (Elf_Addr *)(rel->r_offset + baseaddr); +#ifdef ELF_RELA + /* Addend relative to the base address. */ + *newaddr = baseaddr + rel->r_addend; +#else + /* Address relative to the base address. */ + *newaddr += baseaddr; +#endif + break; + default: + /* XXX: do we need other relocations ? */ + break; + } + rel = (ElfW_Rel *)(void *)((caddr_t) rel + relent); + } +} diff --git a/usr/src/boot/common/tem.c b/usr/src/boot/common/tem.c new file mode 100644 index 0000000000..2798f2883d --- /dev/null +++ b/usr/src/boot/common/tem.c @@ -0,0 +1,2952 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2008 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + * Copyright 2016 Joyent, Inc. + * Copyright 2021 Toomas Soome <tsoome@me.com> + */ + +/* + * ANSI terminal emulator module; parse ANSI X3.64 escape sequences and + * the like. + * + * How Virtual Terminal Emulator Works: + * + * Every virtual terminal is associated with a tem_vt_state structure + * and maintains a virtual screen buffer in tvs_screen_buf, which contains + * all the characters which should be shown on the physical screen when + * the terminal is activated. + * + * Data written to a virtual terminal is composed of characters which + * should be displayed on the screen when this virtual terminal is + * activated, fg/bg colors of these characters, and other control + * information (escape sequence, etc). + * + * When data is passed to a virtual terminal it first is parsed for + * control information by tem_parse(). Subsequently the character + * and color data are written to tvs_screen_buf. + * They are saved in buffer in order to refresh the screen when this + * terminal is activated. If the terminal is currently active, the data + * (characters and colors) are also written to the physical screen by + * invoking a callback function, tem_text_callbacks() or tem_pix_callbacks(). + * + * When rendering data to the framebuffer, if the framebuffer is in + * VIS_PIXEL mode, the character data will first be converted to pixel + * data using tem_pix_bit2pix(), and then the pixels get displayed + * on the physical screen. We only store the character and color data in + * tem_vt_state since the bit2pix conversion only happens when actually + * rendering to the physical framebuffer. + * + * Color support: + * Text mode can only support standard system colors, 4-bit [0-15] indexed. + * On framebuffer devices, we can aditionally use [16-255] or truecolor. + * Additional colors can be used via CSI 38 and CSI 48 sequences. + * CSI 38/48;5 is using indexed colors [0-255], CSI 38/48;2 does + * specify color by RGB triple. + * + * While sending glyphs to display, we need to process glyph attributes: + * TEM_ATTR_BOLD will cause BOLD font to be used (or BRIGHT color if we + * we use indexed color [0-7]). + * We ignore TEM_ATTR_BRIGHT_FG/TEM_ATTR_BRIGHT_BG with RGB colors. + * TEM_ATTR_REVERSE and TEM_ATTR_SCREEN_REVERSE will cause fg and bg to be + * swapped. + */ + +#include <stand.h> +#include <sys/ascii.h> +#include <sys/errno.h> +#include <sys/tem_impl.h> +#ifdef _HAVE_TEM_FIRMWARE +#include <sys/promif.h> +#endif /* _HAVE_TEM_FIRMWARE */ +#include <sys/consplat.h> +#include <sys/kd.h> +#include <stdbool.h> + +/* Terminal emulator internal helper functions */ +static void tems_setup_terminal(struct vis_devinit *, size_t, size_t); +static void tems_modechange_callback(struct vis_modechg_arg *, + struct vis_devinit *); + +static void tems_reset_colormap(void); + +static void tem_free_buf(struct tem_vt_state *); +static void tem_internal_init(struct tem_vt_state *, boolean_t, boolean_t); +static void tems_get_initial_color(tem_color_t *pcolor); + +static void tem_control(struct tem_vt_state *, uint8_t); +static void tem_setparam(struct tem_vt_state *, int, int); +static void tem_selgraph(struct tem_vt_state *); +static void tem_chkparam(struct tem_vt_state *, uint8_t); +static void tem_getparams(struct tem_vt_state *, uint8_t); +static void tem_outch(struct tem_vt_state *, tem_char_t); +static void tem_parse(struct tem_vt_state *, tem_char_t); + +static void tem_new_line(struct tem_vt_state *); +static void tem_cr(struct tem_vt_state *); +static void tem_lf(struct tem_vt_state *); +static void tem_send_data(struct tem_vt_state *); +static void tem_cls(struct tem_vt_state *); +static void tem_tab(struct tem_vt_state *); +static void tem_back_tab(struct tem_vt_state *); +static void tem_clear_tabs(struct tem_vt_state *, int); +static void tem_set_tab(struct tem_vt_state *); +static void tem_mv_cursor(struct tem_vt_state *, int, int); +static void tem_shift(struct tem_vt_state *, int, int); +static void tem_scroll(struct tem_vt_state *, int, int, int, int); +static void tem_clear_chars(struct tem_vt_state *tem, + int count, screen_pos_t row, screen_pos_t col); +static void tem_copy_area(struct tem_vt_state *tem, + screen_pos_t s_col, screen_pos_t s_row, + screen_pos_t e_col, screen_pos_t e_row, + screen_pos_t t_col, screen_pos_t t_row); +static void tem_bell(struct tem_vt_state *tem); +static void tem_pix_clear_prom_output(struct tem_vt_state *tem); + +static void tem_virtual_cls(struct tem_vt_state *, size_t, screen_pos_t, + screen_pos_t); +static void tem_virtual_display(struct tem_vt_state *, term_char_t *, + size_t, screen_pos_t, screen_pos_t); +static void tem_align_cursor(struct tem_vt_state *tem); + +static void tem_check_first_time(struct tem_vt_state *tem); +static void tem_reset_display(struct tem_vt_state *, boolean_t, boolean_t); +static void tem_terminal_emulate(struct tem_vt_state *, uint8_t *, int); +static void tem_text_cursor(struct tem_vt_state *, short); +static void tem_text_cls(struct tem_vt_state *, + int count, screen_pos_t row, screen_pos_t col); +static void tem_pix_display(struct tem_vt_state *, term_char_t *, + int, screen_pos_t, screen_pos_t); +static void tem_pix_copy(struct tem_vt_state *, + screen_pos_t, screen_pos_t, + screen_pos_t, screen_pos_t, + screen_pos_t, screen_pos_t); +static void tem_pix_cursor(struct tem_vt_state *, short); +static void tem_get_attr(struct tem_vt_state *, text_color_t *, + text_color_t *, text_attr_t *, uint8_t); +static void tem_get_color(struct tem_vt_state *, + text_color_t *, text_color_t *, term_char_t *); +static void tem_set_color(text_color_t *, color_t *); +static void tem_pix_align(struct tem_vt_state *); +static void tem_text_display(struct tem_vt_state *, term_char_t *, int, + screen_pos_t, screen_pos_t); +static void tem_text_copy(struct tem_vt_state *, + screen_pos_t, screen_pos_t, screen_pos_t, screen_pos_t, + screen_pos_t, screen_pos_t); +static void tem_pix_bit2pix(struct tem_vt_state *, term_char_t *); +static void tem_pix_cls_range(struct tem_vt_state *, screen_pos_t, int, + int, screen_pos_t, int, int, boolean_t); +static void tem_pix_cls(struct tem_vt_state *, int, + screen_pos_t, screen_pos_t); + +static void bit_to_pix32(struct tem_vt_state *tem, tem_char_t c, + text_color_t fg_color, text_color_t bg_color); + +/* + * Globals + */ +tem_state_t tems; /* common term info */ + +tem_callbacks_t tem_text_callbacks = { + .tsc_display = &tem_text_display, + .tsc_copy = &tem_text_copy, + .tsc_cursor = &tem_text_cursor, + .tsc_bit2pix = NULL, + .tsc_cls = &tem_text_cls +}; +tem_callbacks_t tem_pix_callbacks = { + .tsc_display = &tem_pix_display, + .tsc_copy = &tem_pix_copy, + .tsc_cursor = &tem_pix_cursor, + .tsc_bit2pix = &tem_pix_bit2pix, + .tsc_cls = &tem_pix_cls +}; + +#define tem_callback_display (*tems.ts_callbacks->tsc_display) +#define tem_callback_copy (*tems.ts_callbacks->tsc_copy) +#define tem_callback_cursor (*tems.ts_callbacks->tsc_cursor) +#define tem_callback_cls (*tems.ts_callbacks->tsc_cls) +#define tem_callback_bit2pix (*tems.ts_callbacks->tsc_bit2pix) + +static void +tem_add(struct tem_vt_state *tem) +{ + list_insert_head(&tems.ts_list, tem); +} + +/* + * This is the main entry point to the module. It handles output requests + * during normal system operation, when (e.g.) mutexes are available. + */ +void +tem_write(tem_vt_state_t tem_arg, uint8_t *buf, ssize_t len) +{ + struct tem_vt_state *tem = (struct tem_vt_state *)tem_arg; + + if (tems.ts_initialized == 0 || tem->tvs_initialized == 0) { + return; + } + + tem_check_first_time(tem); + tem_terminal_emulate(tem, buf, len); +} + +static void +tem_internal_init(struct tem_vt_state *ptem, + boolean_t init_color, boolean_t clear_screen) +{ + size_t size, width, height; + + if (tems.ts_display_mode == VIS_PIXEL) { + ptem->tvs_pix_data_size = tems.ts_pix_data_size; + ptem->tvs_pix_data = malloc(ptem->tvs_pix_data_size); + } + + width = tems.ts_c_dimension.width; + height = tems.ts_c_dimension.height; + + size = width * sizeof (tem_char_t); + ptem->tvs_outbuf = malloc(size); + if (ptem->tvs_outbuf == NULL) + panic("out of memory in tem_internal_init()\n"); + + ptem->tvs_maxtab = width / 8; + ptem->tvs_tabs = calloc(ptem->tvs_maxtab, sizeof (*ptem->tvs_tabs)); + if (ptem->tvs_tabs == NULL) + panic("out of memory in tem_internal_init()\n"); + + tem_reset_display(ptem, clear_screen, init_color); + + ptem->tvs_utf8_left = 0; + ptem->tvs_utf8_partial = 0; + + ptem->tvs_initialized = true; + + /* + * Out of memory is not fatal there, without the screen history, + * we can not optimize the screen copy. + */ + size = width * height * sizeof (term_char_t); + ptem->tvs_screen_buf = malloc(size); + tem_virtual_cls(ptem, width * height, 0, 0); +} + +int +tem_initialized(tem_vt_state_t tem_arg) +{ + struct tem_vt_state *ptem = (struct tem_vt_state *)tem_arg; + + return (ptem->tvs_initialized); +} + +tem_vt_state_t +tem_init(void) +{ + struct tem_vt_state *ptem; + + ptem = calloc(1, sizeof (struct tem_vt_state)); + if (ptem == NULL) + return ((tem_vt_state_t)ptem); + + ptem->tvs_isactive = false; + ptem->tvs_fbmode = KD_TEXT; + + /* + * A tem is regarded as initialized only after tem_internal_init(), + * will be set at the end of tem_internal_init(). + */ + ptem->tvs_initialized = 0; + + if (!tems.ts_initialized) { + /* + * Only happens during early console configuration. + */ + tem_add(ptem); + return ((tem_vt_state_t)ptem); + } + + tem_internal_init(ptem, B_TRUE, B_FALSE); + tem_add(ptem); + + return ((tem_vt_state_t)ptem); +} + +/* + * re-init the tem after video mode has changed and tems_info has + * been re-inited. + */ +static void +tem_reinit(struct tem_vt_state *tem, boolean_t reset_display) +{ + tem_free_buf(tem); /* only free virtual buffers */ + + /* reserve color */ + tem_internal_init(tem, B_FALSE, reset_display); +} + +static void +tem_free_buf(struct tem_vt_state *tem) +{ + free(tem->tvs_outbuf); + tem->tvs_outbuf = NULL; + + free(tem->tvs_pix_data); + tem->tvs_pix_data = NULL; + + free(tem->tvs_screen_buf); + tem->tvs_screen_buf = NULL; + + free(tem->tvs_tabs); + tem->tvs_tabs = NULL; +} + +static int +tems_failed(boolean_t finish_ioctl) +{ + if (finish_ioctl && tems.ts_hdl != NULL) + (void) tems.ts_hdl->c_ioctl(tems.ts_hdl, VIS_DEVFINI, NULL); + + tems.ts_hdl = NULL; + return (ENXIO); +} + +/* + * Only called once during boot + */ +int +tem_info_init(struct console *cp) +{ + int ret; + struct vis_devinit temargs; + size_t height = 0; + size_t width = 0; + struct tem_vt_state *p; + + if (tems.ts_initialized) { + return (0); + } + + list_create(&tems.ts_list, sizeof (struct tem_vt_state), + __offsetof(struct tem_vt_state, tvs_list_node)); + tems.ts_active = NULL; + + tems.ts_hdl = cp; + bzero(&temargs, sizeof (temargs)); + temargs.modechg_cb = (vis_modechg_cb_t)tems_modechange_callback; + temargs.modechg_arg = NULL; + + /* + * Initialize the console and get the device parameters + */ + if (cp->c_ioctl(cp, VIS_DEVINIT, &temargs) != 0) { + printf("terminal emulator: Compatible fb not found\n"); + ret = tems_failed(B_FALSE); + return (ret); + } + + /* Make sure the fb driver and terminal emulator versions match */ + if (temargs.version != VIS_CONS_REV) { + printf( + "terminal emulator: VIS_CONS_REV %d (see sys/visual_io.h) " + "of console fb driver not supported\n", temargs.version); + ret = tems_failed(B_TRUE); + return (ret); + } + + /* other sanity checks */ + if (!((temargs.depth == 4) || (temargs.depth == 8) || + (temargs.depth == 15) || (temargs.depth == 16) || + (temargs.depth == 24) || (temargs.depth == 32))) { + printf("terminal emulator: unsupported depth\n"); + ret = tems_failed(B_TRUE); + return (ret); + } + + if ((temargs.mode != VIS_TEXT) && (temargs.mode != VIS_PIXEL)) { + printf("terminal emulator: unsupported mode\n"); + ret = tems_failed(B_TRUE); + return (ret); + } + + plat_tem_get_prom_size(&height, &width); + + /* + * Initialize the common terminal emulator info + */ + tems_setup_terminal(&temargs, height, width); + + tems_reset_colormap(); + tems_get_initial_color(&tems.ts_init_color); + + tems.ts_initialized = 1; /* initialization flag */ + + for (p = list_head(&tems.ts_list); p != NULL; + p = list_next(&tems.ts_list, p)) { + tem_internal_init(p, B_TRUE, B_FALSE); + if (temargs.mode == VIS_PIXEL) + tem_pix_align(p); + } + + return (0); +} + +#define TEMS_DEPTH_DIFF 0x01 +#define TEMS_DIMENSION_DIFF 0x02 + +static uint8_t +tems_check_videomode(struct vis_devinit *tp) +{ + uint8_t result = 0; + + if (tems.ts_pdepth != tp->depth) + result |= TEMS_DEPTH_DIFF; + + if (tp->mode == VIS_TEXT) { + if (tems.ts_c_dimension.width != tp->width || + tems.ts_c_dimension.height != tp->height) + result |= TEMS_DIMENSION_DIFF; + } else { + if (tems.ts_p_dimension.width != tp->width || + tems.ts_p_dimension.height != tp->height) + result |= TEMS_DIMENSION_DIFF; + } + if (tems.update_font == true) + result |= TEMS_DIMENSION_DIFF; + + return (result); +} + +static int +env_screen_nounset(struct env_var *ev __unused) +{ + if (tems.ts_p_dimension.width == 0 && + tems.ts_p_dimension.height == 0) + return (0); + return (EPERM); +} + +static void +tems_setup_font(screen_size_t height, screen_size_t width) +{ + bitmap_data_t *font_data; + + /* + * set_font() will select an 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(&tems.ts_c_dimension.height, + &tems.ts_c_dimension.width, height, width); + + if (font_data == NULL) + panic("out of memory"); + + /* + * To use loaded font, we assign the loaded font data to tems.ts_font. + * In case of next load, the previously loaded data is freed + * when loading the new font. + */ + for (int i = 0; i < VFNT_MAPS; i++) { + tems.ts_font.vf_map[i] = + font_data->font->vf_map[i]; + tems.ts_font.vf_map_count[i] = + font_data->font->vf_map_count[i]; + } + + tems.ts_font.vf_bytes = font_data->font->vf_bytes; + tems.ts_font.vf_width = font_data->font->vf_width; + tems.ts_font.vf_height = font_data->font->vf_height; +} + +static void +tems_setup_terminal(struct vis_devinit *tp, size_t height, size_t width) +{ + char env[8]; + + tems.ts_pdepth = tp->depth; + tems.ts_linebytes = tp->linebytes; + tems.ts_display_mode = tp->mode; + tems.ts_color_map = tp->color_map; + + switch (tp->mode) { + case VIS_TEXT: + /* Set fake pixel dimensions to assist set_font() */ + tems.ts_p_dimension.width = 0; + tems.ts_p_dimension.height = 0; + tems.ts_c_dimension.width = tp->width; + tems.ts_c_dimension.height = tp->height; + tems.ts_callbacks = &tem_text_callbacks; + + tems_setup_font(16 * tp->height + BORDER_PIXELS, + 8 * tp->width + BORDER_PIXELS); + + /* ensure the following are not set for text mode */ + unsetenv("screen-height"); + unsetenv("screen-width"); + break; + + case VIS_PIXEL: + /* + * First check to see if the user has specified a screen size. + * If so, use those values. Else use 34x80 as the default. + */ + if (width == 0) { + width = TEM_DEFAULT_COLS; + height = TEM_DEFAULT_ROWS; + } + tems.ts_c_dimension.height = (screen_size_t)height; + tems.ts_c_dimension.width = (screen_size_t)width; + tems.ts_p_dimension.height = tp->height; + tems.ts_p_dimension.width = tp->width; + tems.ts_callbacks = &tem_pix_callbacks; + + tems_setup_font(tp->height, tp->width); + + snprintf(env, sizeof (env), "%d", tems.ts_p_dimension.height); + env_setenv("screen-height", EV_VOLATILE | EV_NOHOOK, env, + env_noset, env_screen_nounset); + snprintf(env, sizeof (env), "%d", tems.ts_p_dimension.width); + env_setenv("screen-width", EV_VOLATILE | EV_NOHOOK, env, + env_noset, env_screen_nounset); + + tems.ts_p_offset.y = (tems.ts_p_dimension.height - + (tems.ts_c_dimension.height * tems.ts_font.vf_height)) / 2; + tems.ts_p_offset.x = (tems.ts_p_dimension.width - + (tems.ts_c_dimension.width * tems.ts_font.vf_width)) / 2; + tems.ts_pix_data_size = + tems.ts_font.vf_width * tems.ts_font.vf_height; + tems.ts_pix_data_size *= 4; + tems.ts_pdepth = tp->depth; + + break; + } + + tems.update_font = false; + + snprintf(env, sizeof (env), "%d", tems.ts_c_dimension.height); + env_setenv("screen-#rows", EV_VOLATILE | EV_NOHOOK, env, + env_noset, env_nounset); + snprintf(env, sizeof (env), "%d", tems.ts_c_dimension.width); + env_setenv("screen-#cols", EV_VOLATILE | EV_NOHOOK, env, + env_noset, env_nounset); + + snprintf(env, sizeof (env), "%dx%d", tems.ts_font.vf_width, + tems.ts_font.vf_height); + env_setenv("screen-font", EV_VOLATILE | EV_NOHOOK, env, NULL, + NULL); +} + +/* + * This is a callback function that we register with the frame + * buffer driver layered underneath. It gets invoked from + * the underlying frame buffer driver to reconfigure the terminal + * emulator to a new screen size and depth in conjunction with + * framebuffer videomode changes. + * Here we keep the foreground/background color and attributes, + * which may be different with the initial settings, so that + * the color won't change while the framebuffer videomode changes. + * And we also reset the kernel terminal emulator and clear the + * whole screen. + */ +/* ARGSUSED */ +void +tems_modechange_callback(struct vis_modechg_arg *arg __unused, + struct vis_devinit *devinit) +{ + uint8_t diff; + struct tem_vt_state *p; + tem_modechg_cb_t cb; + tem_modechg_cb_arg_t cb_arg; + size_t height = 0; + size_t width = 0; + int state; + + diff = tems_check_videomode(devinit); + if (diff == 0) { + /* + * This is color related change, reset color and redraw the + * screen. Only need to reinit the active tem. + */ + struct tem_vt_state *active = tems.ts_active; + tems_get_initial_color(&tems.ts_init_color); + active->tvs_fg_color = tems.ts_init_color.fg_color; + active->tvs_bg_color = tems.ts_init_color.bg_color; + active->tvs_flags = tems.ts_init_color.a_flags; + tem_reinit(active, B_TRUE); + return; + } + + diff = diff & TEMS_DIMENSION_DIFF; + + if (diff == 0) { + /* + * Only need to reinit the active tem. + */ + struct tem_vt_state *active = tems.ts_active; + tems.ts_pdepth = devinit->depth; + /* color depth did change, reset colors */ + tems_reset_colormap(); + tems_get_initial_color(&tems.ts_init_color); + tem_reinit(active, B_TRUE); + + return; + } + + plat_tem_get_prom_size(&height, &width); + + state = tems.ts_initialized; + tems.ts_initialized = 0; /* stop all output */ + tems_setup_terminal(devinit, height, width); + + tems_reset_colormap(); + tems_get_initial_color(&tems.ts_init_color); + tems.ts_initialized = state; /* restore state */ + + for (p = list_head(&tems.ts_list); p != NULL; + p = list_next(&tems.ts_list, p)) { + tem_reinit(p, p->tvs_isactive); + } + + + if (tems.ts_modechg_cb == NULL) { + return; + } + + cb = tems.ts_modechg_cb; + cb_arg = tems.ts_modechg_arg; + + cb(cb_arg); +} + +/* + * This function is used to clear entire screen via the underlying framebuffer + * driver. + */ +int +tems_cls(struct vis_consclear *pda) +{ + if (tems.ts_hdl == NULL) + return (1); + return (tems.ts_hdl->c_ioctl(tems.ts_hdl, VIS_CONSCLEAR, pda)); +} + +/* + * This function is used to display a rectangular blit of data + * of a given size and location via the underlying framebuffer driver. + * The blit can be as small as a pixel or as large as the screen. + */ +void +tems_display(struct vis_consdisplay *pda) +{ + if (tems.ts_hdl != NULL) + (void) tems.ts_hdl->c_ioctl(tems.ts_hdl, VIS_CONSDISPLAY, pda); +} + +/* + * This function is used to invoke a block copy operation in the + * underlying framebuffer driver. Rectangle copies are how scrolling + * is implemented, as well as horizontal text shifting escape seqs. + * such as from vi when deleting characters and words. + */ +void +tems_copy(struct vis_conscopy *pma) +{ + if (tems.ts_hdl != NULL) + (void) tems.ts_hdl->c_ioctl(tems.ts_hdl, VIS_CONSCOPY, pma); +} + +/* + * This function is used to show or hide a rectangluar monochrom + * pixel inverting, text block cursor via the underlying framebuffer. + */ +void +tems_cursor(struct vis_conscursor *pca) +{ + if (tems.ts_hdl != NULL) + (void) tems.ts_hdl->c_ioctl(tems.ts_hdl, VIS_CONSCURSOR, pca); +} + +static void +tem_kdsetmode(int mode) +{ + if (tems.ts_hdl != NULL) { + (void) tems.ts_hdl->c_ioctl(tems.ts_hdl, KDSETMODE, + (void *)(intptr_t)mode); + } +} + +static void +tems_reset_colormap(void) +{ + struct vis_cmap cm; + + switch (tems.ts_pdepth) { + case 8: + cm.index = 0; + cm.count = 16; + /* 8-bits (1/3 of TrueColor 24) */ + cm.red = (uint8_t *)cmap4_to_24.red; + /* 8-bits (1/3 of TrueColor 24) */ + cm.blue = (uint8_t *)cmap4_to_24.blue; + /* 8-bits (1/3 of TrueColor 24) */ + cm.green = (uint8_t *)cmap4_to_24.green; + if (tems.ts_hdl != NULL) + (void) tems.ts_hdl->c_ioctl(tems.ts_hdl, + VIS_PUTCMAP, &cm); + break; + } +} + +void +tem_get_size(uint16_t *r, uint16_t *c, uint16_t *x, uint16_t *y) +{ + *r = (uint16_t)tems.ts_c_dimension.height; + *c = (uint16_t)tems.ts_c_dimension.width; + *x = (uint16_t)tems.ts_p_dimension.width; + *y = (uint16_t)tems.ts_p_dimension.height; +} + +/* + * Loader extension. Store important data in environment. Intended to be used + * just before booting the OS to make the data available in kernel + * environment module. + */ +void +tem_save_state(void) +{ + struct tem_vt_state *active = tems.ts_active; + char buf[80]; + + /* + * We already have in environment: + * tem.inverse, tem.inverse_screen + * tem.fg_color, tem.bg_color. + * So we only need to add the position of the cursor. + */ + + if (active != NULL) { + snprintf(buf, sizeof (buf), "%d", active->tvs_c_cursor.col); + setenv("tem.cursor.col", buf, 1); + snprintf(buf, sizeof (buf), "%d", active->tvs_c_cursor.row); + setenv("tem.cursor.row", buf, 1); + } +} + +void +tem_register_modechg_cb(tem_modechg_cb_t func, tem_modechg_cb_arg_t arg) +{ + tems.ts_modechg_cb = func; + tems.ts_modechg_arg = arg; +} + +/* + * This function is to scroll up the OBP output, which has + * different screen height and width with our kernel console. + */ +static void +tem_prom_scroll_up(struct tem_vt_state *tem, int nrows) +{ + struct vis_conscopy ma; + int ncols, width; + + /* copy */ + ma.s_row = nrows * tems.ts_font.vf_height; + ma.e_row = tems.ts_p_dimension.height - 1; + ma.t_row = 0; + + ma.s_col = 0; + ma.e_col = tems.ts_p_dimension.width - 1; + ma.t_col = 0; + + tems_copy(&ma); + + /* clear */ + width = tems.ts_font.vf_width; + ncols = (tems.ts_p_dimension.width + (width - 1)) / width; + + tem_pix_cls_range(tem, 0, nrows, tems.ts_p_offset.y, + 0, ncols, 0, B_TRUE); +} + +/* + * This function is to compute the starting row of the console, according to + * PROM cursor's position. Here we have to take different fonts into account. + */ +static int +tem_adjust_row(struct tem_vt_state *tem, int prom_row) +{ + int tem_row; + int tem_y; + int prom_charheight = 0; + int prom_window_top = 0; + int scroll_up_lines; + + plat_tem_get_prom_font_size(&prom_charheight, &prom_window_top); + if (prom_charheight == 0) + prom_charheight = tems.ts_font.vf_height; + + tem_y = (prom_row + 1) * prom_charheight + prom_window_top - + tems.ts_p_offset.y; + tem_row = (tem_y + tems.ts_font.vf_height - 1) / + tems.ts_font.vf_height - 1; + + if (tem_row < 0) { + tem_row = 0; + } else if (tem_row >= (tems.ts_c_dimension.height - 1)) { + /* + * Scroll up the prom outputs if the PROM cursor's position is + * below our tem's lower boundary. + */ + scroll_up_lines = tem_row - + (tems.ts_c_dimension.height - 1); + tem_prom_scroll_up(tem, scroll_up_lines); + tem_row = tems.ts_c_dimension.height - 1; + } + + return (tem_row); +} + +static void +tem_pix_align(struct tem_vt_state *tem) +{ + uint32_t row = 0; + uint32_t col = 0; + + if (plat_stdout_is_framebuffer()) { + plat_tem_hide_prom_cursor(); + + /* + * We are getting the current cursor position in pixel + * mode so that we don't over-write the console output + * during boot. + */ + plat_tem_get_prom_pos(&row, &col); + + /* + * Adjust the row if necessary when the font of our + * kernel console tem is different with that of prom + * tem. + */ + row = tem_adjust_row(tem, row); + + /* first line of our kernel console output */ + tem->tvs_first_line = row + 1; + + /* re-set and align cursor position */ + tem->tvs_s_cursor.row = tem->tvs_c_cursor.row = + (screen_pos_t)row; + tem->tvs_s_cursor.col = tem->tvs_c_cursor.col = 0; + } else { + tem_reset_display(tem, B_TRUE, B_TRUE); + } +} + +static void +tems_get_inverses(boolean_t *p_inverse, boolean_t *p_inverse_screen) +{ + int i_inverse = 0; + int i_inverse_screen = 0; + + plat_tem_get_inverses(&i_inverse, &i_inverse_screen); + + *p_inverse = (i_inverse == 0) ? B_FALSE : B_TRUE; + *p_inverse_screen = (i_inverse_screen == 0) ? B_FALSE : B_TRUE; +} + +/* + * Get the foreground/background color and attributes from environment. + */ +static void +tems_get_initial_color(tem_color_t *pcolor) +{ + boolean_t inverse, inverse_screen; + unsigned short flags = 0; + uint8_t fg, bg; + + fg = DEFAULT_ANSI_FOREGROUND; + bg = DEFAULT_ANSI_BACKGROUND; + plat_tem_get_colors(&fg, &bg); + pcolor->fg_color.n = fg; + pcolor->bg_color.n = bg; + + tems_get_inverses(&inverse, &inverse_screen); + if (inverse) + flags |= TEM_ATTR_REVERSE; + if (inverse_screen) + flags |= TEM_ATTR_SCREEN_REVERSE; + + if (flags != 0) { + /* + * The reverse attribute is set. + * In case of black on white we want bright white for BG. + */ + if (pcolor->fg_color.n == ANSI_COLOR_WHITE) + flags |= TEM_ATTR_BRIGHT_BG; + + /* + * For white on black, unset the bright attribute we + * had set to have bright white background. + */ + if (pcolor->fg_color.n == ANSI_COLOR_BLACK) + flags &= ~TEM_ATTR_BRIGHT_BG; + } else { + /* + * In case of black on white we want bright white for BG. + */ + if (pcolor->bg_color.n == ANSI_COLOR_WHITE) + flags |= TEM_ATTR_BRIGHT_BG; + } + + pcolor->a_flags = flags; +} + +void +tem_activate(tem_vt_state_t tem_arg, boolean_t unblank) +{ + struct tem_vt_state *tem = (struct tem_vt_state *)tem_arg; + + tems.ts_active = tem; + tem->tvs_isactive = true; + + tem_kdsetmode(tem->tvs_fbmode); + + if (unblank) + tem_cls(tem); +} + +static void +tem_check_first_time(struct tem_vt_state *tem) +{ + static int first_time = 1; + + /* + * Realign the console cursor. We did this in tem_init(). + * However, drivers in the console stream may emit additional + * messages before we are ready. This causes text overwrite + * on the screen. This is a workaround. + */ + if (!first_time) + return; + + first_time = 0; + if (tems.ts_display_mode == VIS_TEXT) + tem_text_cursor(tem, VIS_GET_CURSOR); + else + tem_pix_cursor(tem, VIS_GET_CURSOR); + tem_align_cursor(tem); +} + +/* Process partial UTF-8 sequence. */ +static void +tem_input_partial(struct tem_vt_state *tem) +{ + unsigned i; + tem_char_t c; + + if (tem->tvs_utf8_left == 0) + return; + + for (i = 0; i < sizeof (tem->tvs_utf8_partial); i++) { + c = (tem->tvs_utf8_partial >> (24 - (i << 3))) & 0xff; + if (c != 0) { + tem_parse(tem, c); + } + } + tem->tvs_utf8_left = 0; + tem->tvs_utf8_partial = 0; +} + +/* + * Handle UTF-8 sequences. + */ +static void +tem_input_byte(struct tem_vt_state *tem, uint8_t c) +{ + /* + * Check for UTF-8 code points. In case of error fall back to + * 8-bit code. As we only have 8859-1 fonts for console, this will set + * the limits on what chars we actually can display, therefore we + * have to return to this code once we have solved the font issue. + */ + if ((c & 0x80) == 0x00) { + /* One-byte sequence. */ + tem_input_partial(tem); + tem_parse(tem, c); + return; + } + if ((c & 0xe0) == 0xc0) { + /* Two-byte sequence. */ + tem_input_partial(tem); + tem->tvs_utf8_left = 1; + tem->tvs_utf8_partial = c; + return; + } + if ((c & 0xf0) == 0xe0) { + /* Three-byte sequence. */ + tem_input_partial(tem); + tem->tvs_utf8_left = 2; + tem->tvs_utf8_partial = c; + return; + } + if ((c & 0xf8) == 0xf0) { + /* Four-byte sequence. */ + tem_input_partial(tem); + tem->tvs_utf8_left = 3; + tem->tvs_utf8_partial = c; + return; + } + if ((c & 0xc0) == 0x80) { + /* Invalid state? */ + if (tem->tvs_utf8_left == 0) { + tem_parse(tem, c); + return; + } + tem->tvs_utf8_left--; + tem->tvs_utf8_partial = (tem->tvs_utf8_partial << 8) | c; + if (tem->tvs_utf8_left == 0) { + tem_char_t v, u; + uint8_t b; + + /* + * Transform the sequence of 2 to 4 bytes to + * unicode number. + */ + v = 0; + u = tem->tvs_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); + } + + tem_parse(tem, v); + tem->tvs_utf8_partial = 0; + } + return; + } + /* Anything left is illegal in UTF-8 sequence. */ + tem_input_partial(tem); + tem_parse(tem, c); +} + +/* + * This is the main entry point into the terminal emulator. + * + * For each data message coming downstream, ANSI assumes that it is composed + * of ASCII characters, which are treated as a byte-stream input to the + * parsing state machine. All data is parsed immediately -- there is + * no enqueing. + */ +static void +tem_terminal_emulate(struct tem_vt_state *tem, uint8_t *buf, int len) +{ + if (tem->tvs_isactive) + tem_callback_cursor(tem, VIS_HIDE_CURSOR); + + for (; len > 0; len--, buf++) + tem_input_byte(tem, *buf); + + /* + * Send the data we just got to the framebuffer. + */ + tem_send_data(tem); + + if (tem->tvs_isactive) + tem_callback_cursor(tem, VIS_DISPLAY_CURSOR); +} + +/* + * send the appropriate control message or set state based on the + * value of the control character ch + */ + +static void +tem_control(struct tem_vt_state *tem, uint8_t ch) +{ + tem->tvs_state = A_STATE_START; + switch (ch) { + case A_BEL: + tem_bell(tem); + break; + + case A_BS: + tem_mv_cursor(tem, + tem->tvs_c_cursor.row, + tem->tvs_c_cursor.col - 1); + break; + + case A_HT: + tem_tab(tem); + break; + + case A_NL: + /* + * tem_send_data(tem, credp, called_from); + * tem_new_line(tem, credp, called_from); + * break; + */ + + case A_VT: + tem_send_data(tem); + tem_lf(tem); + break; + + case A_FF: + tem_send_data(tem); + tem_cls(tem); + break; + + case A_CR: + tem_send_data(tem); + tem_cr(tem); + break; + + case A_ESC: + tem->tvs_state = A_STATE_ESC; + break; + + case A_CSI: + tem->tvs_curparam = 0; + tem->tvs_paramval = 0; + tem->tvs_gotparam = B_FALSE; + /* clear the parameters */ + for (int i = 0; i < TEM_MAXPARAMS; i++) + tem->tvs_params[i] = -1; + tem->tvs_state = A_STATE_CSI; + break; + + case A_GS: + tem_back_tab(tem); + break; + + default: + break; + } +} + + +/* + * if parameters [0..count - 1] are not set, set them to the value + * of newparam. + */ + +static void +tem_setparam(struct tem_vt_state *tem, int count, int newparam) +{ + int i; + + for (i = 0; i < count; i++) { + if (tem->tvs_params[i] == -1) + tem->tvs_params[i] = newparam; + } +} + +/* + * For colors 0-15 the tem is using color code translation + * from sun colors to vga (dim_xlate and brt_xlate tables, see tem_get_color). + * Colors 16-255 are used without translation. + */ +static void +tem_select_color(struct tem_vt_state *tem, int color, bool fg) +{ + if (color < 0 || color > 255) + return; + + /* VGA text mode only does support 16 colors. */ + if (tems.ts_display_mode == VIS_TEXT && color > 15) + return; + + /* Switch to use indexed colors. */ + if (fg == true) { + tem->tvs_flags &= ~TEM_ATTR_RGB_FG; + tem->tvs_fg_color.n = color; + } else { + tem->tvs_flags &= ~TEM_ATTR_RGB_BG; + tem->tvs_bg_color.n = color; + } + + /* + * For colors 0-7, make sure the BRIGHT attribute is not set. + */ + if (color < 8) { + if (fg == true) + tem->tvs_flags &= ~TEM_ATTR_BRIGHT_FG; + else + tem->tvs_flags &= ~TEM_ATTR_BRIGHT_BG; + return; + } + + /* + * For colors 8-15, we use color codes 0-7 and set BRIGHT attribute. + */ + if (color < 16) { + if (fg == true) { + tem->tvs_fg_color.n -= 8; + tem->tvs_flags |= TEM_ATTR_BRIGHT_FG; + } else { + tem->tvs_bg_color.n -= 8; + tem->tvs_flags |= TEM_ATTR_BRIGHT_BG; + } + } +} + +/* + * select graphics mode based on the param vals stored in a_params + */ +static void +tem_selgraph(struct tem_vt_state *tem) +{ + int curparam; + int count = 0; + int param; + int r, g, b; + + tem->tvs_state = A_STATE_START; + + curparam = tem->tvs_curparam; + do { + param = tem->tvs_params[count]; + + switch (param) { + case -1: + case 0: + /* reset to initial normal settings */ + tem->tvs_fg_color = tems.ts_init_color.fg_color; + tem->tvs_bg_color = tems.ts_init_color.bg_color; + tem->tvs_flags = tems.ts_init_color.a_flags; + break; + + case 1: /* Bold Intense */ + tem->tvs_flags |= TEM_ATTR_BOLD; + break; + + case 2: /* Faint Intense */ + tem->tvs_flags &= ~TEM_ATTR_BOLD; + break; + + case 4: /* Underline */ + tem->tvs_flags |= TEM_ATTR_UNDERLINE; + break; + + case 5: /* Blink */ + tem->tvs_flags |= TEM_ATTR_BLINK; + break; + + case 7: /* Reverse video */ + if (tem->tvs_flags & TEM_ATTR_SCREEN_REVERSE) { + tem->tvs_flags &= ~TEM_ATTR_REVERSE; + } else { + tem->tvs_flags |= TEM_ATTR_REVERSE; + } + break; + + case 22: /* Remove Bold */ + tem->tvs_flags &= ~TEM_ATTR_BOLD; + break; + + case 24: /* Remove Underline */ + tem->tvs_flags &= ~TEM_ATTR_UNDERLINE; + break; + + case 25: /* Remove Blink */ + tem->tvs_flags &= ~TEM_ATTR_BLINK; + break; + + case 27: /* Remove Reverse */ + if (tem->tvs_flags & TEM_ATTR_SCREEN_REVERSE) { + tem->tvs_flags |= TEM_ATTR_REVERSE; + } else { + tem->tvs_flags &= ~TEM_ATTR_REVERSE; + } + break; + + case 30: /* black (grey) foreground */ + case 31: /* red (light red) foreground */ + case 32: /* green (light green) foreground */ + case 33: /* brown (yellow) foreground */ + case 34: /* blue (light blue) foreground */ + case 35: /* magenta (light magenta) foreground */ + case 36: /* cyan (light cyan) foreground */ + case 37: /* white (bright white) foreground */ + tem->tvs_fg_color.n = param - 30; + tem->tvs_flags &= ~TEM_ATTR_BRIGHT_FG; + tem->tvs_flags &= ~TEM_ATTR_RGB_FG; + break; + + case 38: + /* + * We should have 3 parameters for 256 colors and + * 5 parameters for 24-bit colors. + */ + if (curparam < 3) { + curparam = 0; + break; + } + + /* + * 256 and truecolor needs depth > 8, but + * we still need to process the sequence. + */ + count++; + curparam--; + param = tem->tvs_params[count]; + switch (param) { + case 2: /* RGB colors */ + if (curparam < 4) { + curparam = 0; + break; + } + r = tem->tvs_params[++count]; + g = tem->tvs_params[++count]; + b = tem->tvs_params[++count]; + curparam -= 3; + if (r < 0 || r > 255 || g < 0 || g > 255 || + b < 0 || b > 255) + break; + + if (tems.ts_display_mode == VIS_PIXEL && + tems.ts_pdepth > 8) { + tem->tvs_flags |= TEM_ATTR_RGB_FG; + tem->tvs_flags &= ~TEM_ATTR_BRIGHT_FG; + tem->tvs_fg_color.rgb.a = + tem->tvs_alpha; + tem->tvs_fg_color.rgb.r = r; + tem->tvs_fg_color.rgb.g = g; + tem->tvs_fg_color.rgb.b = b; + } + break; + case 5: /* 256 colors */ + count++; + curparam--; + tem_select_color(tem, tem->tvs_params[count], + true); + break; + default: + curparam = 0; + break; + } + break; + + case 39: + /* + * Reset the foreground colour and brightness. + */ + tem->tvs_fg_color = tems.ts_init_color.fg_color; + tem->tvs_flags &= ~TEM_ATTR_RGB_FG; + if (tems.ts_init_color.a_flags & TEM_ATTR_BRIGHT_FG) + tem->tvs_flags |= TEM_ATTR_BRIGHT_FG; + else + tem->tvs_flags &= ~TEM_ATTR_BRIGHT_FG; + break; + + case 40: /* black (grey) background */ + case 41: /* red (light red) background */ + case 42: /* green (light green) background */ + case 43: /* brown (yellow) background */ + case 44: /* blue (light blue) background */ + case 45: /* magenta (light magenta) background */ + case 46: /* cyan (light cyan) background */ + case 47: /* white (bright white) background */ + tem->tvs_bg_color.n = param - 40; + tem->tvs_flags &= ~TEM_ATTR_RGB_BG; + tem->tvs_flags &= ~TEM_ATTR_BRIGHT_BG; + break; + + case 48: + /* + * We should have 3 parameters for 256 colors and + * 5 parameters for 24-bit colors. + */ + /* We should have at least 3 parameters */ + if (curparam < 3) { + curparam = 0; + break; + } + + /* + * 256 and truecolor needs depth > 8, but + * we still need to process the sequence. + */ + count++; + curparam--; + param = tem->tvs_params[count]; + switch (param) { + case 2: /* RGB colors */ + if (curparam < 4) { + curparam = 0; + break; + } + r = tem->tvs_params[++count]; + g = tem->tvs_params[++count]; + b = tem->tvs_params[++count]; + curparam -= 3; + if (r < 0 || r > 255 || g < 0 || g > 255 || + b < 0 || b > 255) + break; + + if (tems.ts_display_mode == VIS_PIXEL && + tems.ts_pdepth > 8) { + tem->tvs_flags |= TEM_ATTR_RGB_BG; + tem->tvs_flags &= ~TEM_ATTR_BRIGHT_BG; + tem->tvs_bg_color.rgb.a = + tem->tvs_alpha; + tem->tvs_bg_color.rgb.r = r; + tem->tvs_bg_color.rgb.g = g; + tem->tvs_bg_color.rgb.b = b; + } + break; + case 5: /* 256 colors */ + count++; + curparam--; + tem_select_color(tem, tem->tvs_params[count], + false); + break; + default: + curparam = 0; + break; + } + break; + + case 49: + /* + * Reset the background colour and brightness. + */ + tem->tvs_bg_color = tems.ts_init_color.bg_color; + tem->tvs_flags &= ~TEM_ATTR_RGB_BG; + if (tems.ts_init_color.a_flags & TEM_ATTR_BRIGHT_BG) + tem->tvs_flags |= TEM_ATTR_BRIGHT_BG; + else + tem->tvs_flags &= ~TEM_ATTR_BRIGHT_BG; + break; + + case 90: /* black (grey) foreground */ + case 91: /* red (light red) foreground */ + case 92: /* green (light green) foreground */ + case 93: /* brown (yellow) foreground */ + case 94: /* blue (light blue) foreground */ + case 95: /* magenta (light magenta) foreground */ + case 96: /* cyan (light cyan) foreground */ + case 97: /* white (bright white) foreground */ + tem->tvs_fg_color.n = param - 90; + tem->tvs_flags |= TEM_ATTR_BRIGHT_FG; + tem->tvs_flags &= ~TEM_ATTR_RGB_FG; + break; + + case 100: /* black (grey) background */ + case 101: /* red (light red) background */ + case 102: /* green (light green) background */ + case 103: /* brown (yellow) background */ + case 104: /* blue (light blue) background */ + case 105: /* magenta (light magenta) background */ + case 106: /* cyan (light cyan) background */ + case 107: /* white (bright white) background */ + tem->tvs_bg_color.n = param - 100; + tem->tvs_flags |= TEM_ATTR_BRIGHT_BG; + tem->tvs_flags &= ~TEM_ATTR_RGB_BG; + break; + + default: + break; + } + count++; + curparam--; + + } while (curparam > 0); +} + +/* + * perform the appropriate action for the escape sequence + * + * General rule: This code does not validate the arguments passed. + * It assumes that the next lower level will do so. + */ +static void +tem_chkparam(struct tem_vt_state *tem, uint8_t ch) +{ + int i; + int row; + int col; + + row = tem->tvs_c_cursor.row; + col = tem->tvs_c_cursor.col; + + switch (ch) { + + case 'm': /* select terminal graphics mode */ + tem_send_data(tem); + tem_selgraph(tem); + break; + + case '@': /* insert char */ + tem_setparam(tem, 1, 1); + tem_shift(tem, tem->tvs_params[0], TEM_SHIFT_RIGHT); + break; + + case 'A': /* cursor up */ + tem_setparam(tem, 1, 1); + tem_mv_cursor(tem, row - tem->tvs_params[0], col); + break; + + case 'd': /* VPA - vertical position absolute */ + tem_setparam(tem, 1, 1); + tem_mv_cursor(tem, tem->tvs_params[0] - 1, col); + break; + + case 'e': /* VPR - vertical position relative */ + case 'B': /* cursor down */ + tem_setparam(tem, 1, 1); + tem_mv_cursor(tem, row + tem->tvs_params[0], col); + break; + + case 'a': /* HPR - horizontal position relative */ + case 'C': /* cursor right */ + tem_setparam(tem, 1, 1); + tem_mv_cursor(tem, row, col + tem->tvs_params[0]); + break; + + case '`': /* HPA - horizontal position absolute */ + tem_setparam(tem, 1, 1); + tem_mv_cursor(tem, row, tem->tvs_params[0] - 1); + break; + + case 'D': /* cursor left */ + tem_setparam(tem, 1, 1); + tem_mv_cursor(tem, row, col - tem->tvs_params[0]); + break; + + case 'E': /* CNL cursor next line */ + tem_setparam(tem, 1, 1); + tem_mv_cursor(tem, row + tem->tvs_params[0], 0); + break; + + case 'F': /* CPL cursor previous line */ + tem_setparam(tem, 1, 1); + tem_mv_cursor(tem, row - tem->tvs_params[0], 0); + break; + + case 'G': /* cursor horizontal position */ + tem_setparam(tem, 1, 1); + tem_mv_cursor(tem, row, tem->tvs_params[0] - 1); + break; + + case 'g': /* clear tabs */ + tem_setparam(tem, 1, 0); + tem_clear_tabs(tem, tem->tvs_params[0]); + break; + + case 'f': /* HVP Horizontal and Vertical Position */ + case 'H': /* CUP position cursor */ + tem_setparam(tem, 2, 1); + tem_mv_cursor(tem, + tem->tvs_params[0] - 1, tem->tvs_params[1] - 1); + break; + + case 'I': /* CHT - Cursor Horizontal Tab */ + /* Not implemented */ + break; + + case 'J': /* ED - Erase in Display */ + tem_send_data(tem); + tem_setparam(tem, 1, 0); + switch (tem->tvs_params[0]) { + case 0: + /* erase cursor to end of screen */ + /* FIRST erase cursor to end of line */ + tem_clear_chars(tem, + tems.ts_c_dimension.width - + tem->tvs_c_cursor.col, + tem->tvs_c_cursor.row, + tem->tvs_c_cursor.col); + + /* THEN erase lines below the cursor */ + for (row = tem->tvs_c_cursor.row + 1; + row < tems.ts_c_dimension.height; + row++) { + tem_clear_chars(tem, + tems.ts_c_dimension.width, row, 0); + } + break; + + case 1: + /* erase beginning of screen to cursor */ + /* FIRST erase lines above the cursor */ + for (row = 0; + row < tem->tvs_c_cursor.row; + row++) { + tem_clear_chars(tem, + tems.ts_c_dimension.width, row, 0); + } + /* THEN erase beginning of line to cursor */ + tem_clear_chars(tem, + tem->tvs_c_cursor.col + 1, + tem->tvs_c_cursor.row, 0); + break; + + case 2: + /* erase whole screen */ + for (row = 0; + row < tems.ts_c_dimension.height; + row++) { + tem_clear_chars(tem, + tems.ts_c_dimension.width, row, 0); + } + break; + } + break; + + case 'K': /* EL - Erase in Line */ + tem_send_data(tem); + tem_setparam(tem, 1, 0); + switch (tem->tvs_params[0]) { + case 0: + /* erase cursor to end of line */ + tem_clear_chars(tem, + (tems.ts_c_dimension.width - + tem->tvs_c_cursor.col), + tem->tvs_c_cursor.row, + tem->tvs_c_cursor.col); + break; + + case 1: + /* erase beginning of line to cursor */ + tem_clear_chars(tem, + tem->tvs_c_cursor.col + 1, + tem->tvs_c_cursor.row, 0); + break; + + case 2: + /* erase whole line */ + tem_clear_chars(tem, + tems.ts_c_dimension.width, + tem->tvs_c_cursor.row, 0); + break; + } + break; + + case 'L': /* insert line */ + tem_send_data(tem); + tem_setparam(tem, 1, 1); + tem_scroll(tem, + tem->tvs_c_cursor.row, + tems.ts_c_dimension.height - 1, + tem->tvs_params[0], TEM_SCROLL_DOWN); + break; + + case 'M': /* delete line */ + tem_send_data(tem); + tem_setparam(tem, 1, 1); + tem_scroll(tem, + tem->tvs_c_cursor.row, + tems.ts_c_dimension.height - 1, + tem->tvs_params[0], TEM_SCROLL_UP); + break; + + case 'P': /* DCH - delete char */ + tem_setparam(tem, 1, 1); + tem_shift(tem, tem->tvs_params[0], TEM_SHIFT_LEFT); + break; + + case 'S': /* scroll up */ + tem_send_data(tem); + tem_setparam(tem, 1, 1); + tem_scroll(tem, 0, + tems.ts_c_dimension.height - 1, + tem->tvs_params[0], TEM_SCROLL_UP); + break; + + case 'T': /* scroll down */ + tem_send_data(tem); + tem_setparam(tem, 1, 1); + tem_scroll(tem, 0, + tems.ts_c_dimension.height - 1, + tem->tvs_params[0], TEM_SCROLL_DOWN); + break; + + case 'X': /* erase char */ + tem_setparam(tem, 1, 1); + tem_clear_chars(tem, + tem->tvs_params[0], + tem->tvs_c_cursor.row, + tem->tvs_c_cursor.col); + break; + + case 'Z': /* cursor backward tabulation */ + tem_setparam(tem, 1, 1); + + /* + * Rule exception - We do sanity checking here. + * + * Restrict the count to a sane value to keep from + * looping for a long time. There can't be more than one + * tab stop per column, so use that as a limit. + */ + if (tem->tvs_params[0] > tems.ts_c_dimension.width) + tem->tvs_params[0] = tems.ts_c_dimension.width; + + for (i = 0; i < tem->tvs_params[0]; i++) + tem_back_tab(tem); + break; + } + tem->tvs_state = A_STATE_START; +} + + +/* + * Gather the parameters of an ANSI escape sequence + */ +static void +tem_getparams(struct tem_vt_state *tem, uint8_t ch) +{ + if (isdigit(ch)) { + tem->tvs_paramval = ((tem->tvs_paramval * 10) + (ch - '0')); + tem->tvs_gotparam = B_TRUE; /* Remember got parameter */ + return; /* Return immediately */ + } else if (tem->tvs_state == A_STATE_CSI_EQUAL || + tem->tvs_state == A_STATE_CSI_QMARK) { + tem->tvs_state = A_STATE_START; + } else { + if (tem->tvs_curparam < TEM_MAXPARAMS) { + if (tem->tvs_gotparam) { + /* get the parameter value */ + tem->tvs_params[tem->tvs_curparam] = + tem->tvs_paramval; + } + tem->tvs_curparam++; + } + + if (ch == ';') { + /* Restart parameter search */ + tem->tvs_gotparam = B_FALSE; + tem->tvs_paramval = 0; /* No parame value yet */ + } else { + /* Handle escape sequence */ + tem_chkparam(tem, ch); + } + } +} + +/* + * Add character to internal buffer. + * When its full, send it to the next layer. + */ +static void +tem_outch(struct tem_vt_state *tem, tem_char_t ch) +{ + text_color_t fg; + text_color_t bg; + text_attr_t attr; + + /* buffer up the character until later */ + tem_get_attr(tem, &fg, &bg, &attr, TEM_ATTR_REVERSE); + tem->tvs_outbuf[tem->tvs_outindex].tc_char = ch | TEM_ATTR(attr); + tem->tvs_outbuf[tem->tvs_outindex].tc_fg_color = fg; + tem->tvs_outbuf[tem->tvs_outindex].tc_bg_color = bg; + tem->tvs_outindex++; + tem->tvs_c_cursor.col++; + if (tem->tvs_c_cursor.col >= tems.ts_c_dimension.width) { + tem_send_data(tem); + tem_new_line(tem); + } +} + +static void +tem_new_line(struct tem_vt_state *tem) +{ + tem_cr(tem); + tem_lf(tem); +} + +static void +tem_cr(struct tem_vt_state *tem) +{ + tem->tvs_c_cursor.col = 0; + tem_align_cursor(tem); +} + +static void +tem_lf(struct tem_vt_state *tem) +{ + int row; + + /* + * Sanity checking notes: + * . a_nscroll was validated when it was set. + * . Regardless of that, tem_scroll and tem_mv_cursor + * will prevent anything bad from happening. + */ + row = tem->tvs_c_cursor.row + 1; + + if (row >= tems.ts_c_dimension.height) { + if (tem->tvs_nscroll != 0) { + tem_scroll(tem, 0, + tems.ts_c_dimension.height - 1, + tem->tvs_nscroll, TEM_SCROLL_UP); + row = tems.ts_c_dimension.height - + tem->tvs_nscroll; + } else { /* no scroll */ + /* + * implement Esc[#r when # is zero. This means no + * scroll but just return cursor to top of screen, + * do not clear screen. + */ + row = 0; + } + } + + tem_mv_cursor(tem, row, tem->tvs_c_cursor.col); + + if (tem->tvs_nscroll == 0) { + /* erase rest of cursor line */ + tem_clear_chars(tem, + tems.ts_c_dimension.width - + tem->tvs_c_cursor.col, + tem->tvs_c_cursor.row, + tem->tvs_c_cursor.col); + + } + + tem_align_cursor(tem); +} + +static void +tem_send_data(struct tem_vt_state *tem) +{ + if (tem->tvs_outindex == 0) { + tem_align_cursor(tem); + return; + } + + tem_virtual_display(tem, tem->tvs_outbuf, tem->tvs_outindex, + tem->tvs_s_cursor.row, tem->tvs_s_cursor.col); + + if (tem->tvs_isactive) { + /* + * Call the primitive to render this data. + */ + tem_callback_display(tem, + tem->tvs_outbuf, tem->tvs_outindex, + tem->tvs_s_cursor.row, tem->tvs_s_cursor.col); + } + + tem->tvs_outindex = 0; + + tem_align_cursor(tem); +} + + +/* + * We have just done something to the current output point. Reset the start + * point for the buffered data in a_outbuf. There shouldn't be any data + * buffered yet. + */ +static void +tem_align_cursor(struct tem_vt_state *tem) +{ + tem->tvs_s_cursor.row = tem->tvs_c_cursor.row; + tem->tvs_s_cursor.col = tem->tvs_c_cursor.col; +} + +/* + * State machine parser based on the current state and character input + * major terminations are to control character or normal character + */ + +static void +tem_parse(struct tem_vt_state *tem, tem_char_t ch) +{ + int i; + + if (tem->tvs_state == A_STATE_START) { /* Normal state? */ + if (ch == A_CSI || ch == A_ESC || ch < ' ') { + /* Control */ + tem_control(tem, ch); + } else { + /* Display */ + tem_outch(tem, ch); + } + return; + } + + /* In <ESC> sequence */ + if (tem->tvs_state != A_STATE_ESC) { /* Need to get parameters? */ + if (tem->tvs_state != A_STATE_CSI) { + tem_getparams(tem, ch); + return; + } + + switch (ch) { + case '?': + tem->tvs_state = A_STATE_CSI_QMARK; + return; + case '=': + tem->tvs_state = A_STATE_CSI_EQUAL; + return; + case 's': + /* + * As defined below, this sequence + * saves the cursor. However, Sun + * defines ESC[s as reset. We resolved + * the conflict by selecting reset as it + * is exported in the termcap file for + * sun-mon, while the "save cursor" + * definition does not exist anywhere in + * /etc/termcap. + * However, having no coherent + * definition of reset, we have not + * implemented it. + */ + + /* + * Original code + * tem->tvs_r_cursor.row = tem->tvs_c_cursor.row; + * tem->tvs_r_cursor.col = tem->tvs_c_cursor.col; + * tem->tvs_state = A_STATE_START; + */ + + tem->tvs_state = A_STATE_START; + return; + case 'u': + tem_mv_cursor(tem, tem->tvs_r_cursor.row, + tem->tvs_r_cursor.col); + tem->tvs_state = A_STATE_START; + return; + case 'p': /* sunbow */ + tem_send_data(tem); + /* + * Don't set anything if we are + * already as we want to be. + */ + if (tem->tvs_flags & TEM_ATTR_SCREEN_REVERSE) { + tem->tvs_flags &= ~TEM_ATTR_SCREEN_REVERSE; + /* + * If we have switched the characters to be the + * inverse from the screen, then switch them as + * well to keep them the inverse of the screen. + */ + if (tem->tvs_flags & TEM_ATTR_REVERSE) + tem->tvs_flags &= ~TEM_ATTR_REVERSE; + else + tem->tvs_flags |= TEM_ATTR_REVERSE; + } + tem_cls(tem); + tem->tvs_state = A_STATE_START; + return; + case 'q': /* sunwob */ + tem_send_data(tem); + /* + * Don't set anything if we are + * already where as we want to be. + */ + if (!(tem->tvs_flags & TEM_ATTR_SCREEN_REVERSE)) { + tem->tvs_flags |= TEM_ATTR_SCREEN_REVERSE; + /* + * If we have switched the characters to be the + * inverse from the screen, then switch them as + * well to keep them the inverse of the screen. + */ + if (!(tem->tvs_flags & TEM_ATTR_REVERSE)) + tem->tvs_flags |= TEM_ATTR_REVERSE; + else + tem->tvs_flags &= ~TEM_ATTR_REVERSE; + } + + tem_cls(tem); + tem->tvs_state = A_STATE_START; + return; + case 'r': /* sunscrl */ + /* + * Rule exception: check for validity here. + */ + tem->tvs_nscroll = tem->tvs_paramval; + if (tem->tvs_nscroll > tems.ts_c_dimension.height) + tem->tvs_nscroll = tems.ts_c_dimension.height; + if (tem->tvs_nscroll < 0) + tem->tvs_nscroll = 1; + tem->tvs_state = A_STATE_START; + return; + default: + tem_getparams(tem, ch); + return; + } + } + + /* Previous char was <ESC> */ + if (ch == '[') { + tem->tvs_curparam = 0; + tem->tvs_paramval = 0; + tem->tvs_gotparam = B_FALSE; + /* clear the parameters */ + for (i = 0; i < TEM_MAXPARAMS; i++) + tem->tvs_params[i] = -1; + tem->tvs_state = A_STATE_CSI; + } else if (ch == 'Q') { /* <ESC>Q ? */ + tem->tvs_state = A_STATE_START; + } else if (ch == 'C') { /* <ESC>C ? */ + tem->tvs_state = A_STATE_START; + } else { + tem->tvs_state = A_STATE_START; + if (ch == 'c') { + /* ESC c resets display */ + tem_reset_display(tem, B_TRUE, B_TRUE); + } else if (ch == 'H') { + /* ESC H sets a tab */ + tem_set_tab(tem); + } else if (ch == '7') { + /* ESC 7 Save Cursor position */ + tem->tvs_r_cursor.row = tem->tvs_c_cursor.row; + tem->tvs_r_cursor.col = tem->tvs_c_cursor.col; + } else if (ch == '8') { + /* ESC 8 Restore Cursor position */ + tem_mv_cursor(tem, tem->tvs_r_cursor.row, + tem->tvs_r_cursor.col); + /* check for control chars */ + } else if (ch < ' ') { + tem_control(tem, ch); + } else { + tem_outch(tem, ch); + } + } +} + +/* ARGSUSED */ +static void +tem_bell(struct tem_vt_state *tem __unused) +{ + /* (void) beep(BEEP_CONSOLE); */ +} + + +static void +tem_scroll(struct tem_vt_state *tem, int start, int end, int count, + int direction) +{ + int row; + int lines_affected; + + lines_affected = end - start + 1; + if (count > lines_affected) + count = lines_affected; + if (count <= 0) + return; + + switch (direction) { + case TEM_SCROLL_UP: + if (count < lines_affected) { + tem_copy_area(tem, 0, start + count, + tems.ts_c_dimension.width - 1, end, 0, start); + } + for (row = (end - count) + 1; row <= end; row++) { + tem_clear_chars(tem, tems.ts_c_dimension.width, row, 0); + } + break; + + case TEM_SCROLL_DOWN: + if (count < lines_affected) { + tem_copy_area(tem, 0, start, + tems.ts_c_dimension.width - 1, + end - count, 0, start + count); + } + for (row = start; row < start + count; row++) { + tem_clear_chars(tem, tems.ts_c_dimension.width, row, 0); + } + break; + } +} + +static int +tem_copy_width(term_char_t *src, term_char_t *dst, int cols) +{ + int width = cols - 1; + + while (width >= 0) { + /* We do not have image bits to compare, stop there. */ + if (TEM_CHAR_ATTR(src[width].tc_char) == TEM_ATTR_IMAGE || + TEM_CHAR_ATTR(dst[width].tc_char) == TEM_ATTR_IMAGE) + break; + + /* + * Find difference on line, compare char with its attributes + * and colors. + */ + if (src[width].tc_char != dst[width].tc_char || + src[width].tc_fg_color.n != dst[width].tc_fg_color.n || + src[width].tc_bg_color.n != dst[width].tc_bg_color.n) { + break; + } + width--; + } + return (width + 1); +} + +static void +tem_copy_area(struct tem_vt_state *tem, + screen_pos_t s_col, screen_pos_t s_row, + screen_pos_t e_col, screen_pos_t e_row, + screen_pos_t t_col, screen_pos_t t_row) +{ + size_t soffset, toffset; + term_char_t *src, *dst; + int rows; + int cols; + + if (s_col < 0 || s_row < 0 || + e_col < 0 || e_row < 0 || + t_col < 0 || t_row < 0 || + s_col >= tems.ts_c_dimension.width || + e_col >= tems.ts_c_dimension.width || + t_col >= tems.ts_c_dimension.width || + s_row >= tems.ts_c_dimension.height || + e_row >= tems.ts_c_dimension.height || + t_row >= tems.ts_c_dimension.height) + return; + + if (s_row > e_row || s_col > e_col) + return; + + rows = e_row - s_row + 1; + cols = e_col - s_col + 1; + if (t_row + rows > tems.ts_c_dimension.height || + t_col + cols > tems.ts_c_dimension.width) + return; + + if (tem->tvs_screen_buf == NULL) { + if (tem->tvs_isactive) { + tem_callback_copy(tem, s_col, s_row, + e_col, e_row, t_col, t_row); + } + return; + } + + soffset = s_col + s_row * tems.ts_c_dimension.width; + toffset = t_col + t_row * tems.ts_c_dimension.width; + src = tem->tvs_screen_buf + soffset; + dst = tem->tvs_screen_buf + toffset; + + /* + * Copy line by line. We determine the length by comparing the + * screen content from cached text in tvs_screen_buf. + */ + if (toffset <= soffset) { + for (int i = 0; i < rows; i++) { + int increment = i * tems.ts_c_dimension.width; + int width; + + width = tem_copy_width(src + increment, + dst + increment, cols); + memmove(dst + increment, src + increment, + width * sizeof (term_char_t)); + if (tem->tvs_isactive) { + tem_callback_copy(tem, s_col, s_row + i, + e_col - cols + width, s_row + i, + t_col, t_row + i); + } + } + } else { + for (int i = rows - 1; i >= 0; i--) { + int increment = i * tems.ts_c_dimension.width; + int width; + + width = tem_copy_width(src + increment, + dst + increment, cols); + memmove(dst + increment, src + increment, + width * sizeof (term_char_t)); + if (tem->tvs_isactive) { + tem_callback_copy(tem, s_col, s_row + i, + e_col - cols + width, s_row + i, + t_col, t_row + i); + } + } + } +} + +static void +tem_clear_chars(struct tem_vt_state *tem, int count, screen_pos_t row, + screen_pos_t col) +{ + if (row < 0 || row >= tems.ts_c_dimension.height || + col < 0 || col >= tems.ts_c_dimension.width || + count < 0) + return; + + /* + * Note that very large values of "count" could cause col+count + * to overflow, so we check "count" independently. + */ + if (count > tems.ts_c_dimension.width || + col + count > tems.ts_c_dimension.width) + count = tems.ts_c_dimension.width - col; + + tem_virtual_cls(tem, count, row, col); + + if (!tem->tvs_isactive) + return; + + tem_callback_cls(tem, count, row, col); +} + +static void +tem_text_display(struct tem_vt_state *tem __unused, term_char_t *string, + int count, screen_pos_t row, screen_pos_t col) +{ + struct vis_consdisplay da; + int i; + tem_char_t c; + text_color_t bg, fg; + + if (count == 0) + return; + + da.data = (unsigned char *)&c; + da.width = 1; + da.row = row; + da.col = col; + + for (i = 0; i < count; i++) { + tem_get_color(tem, &fg, &bg, &string[i]); + tem_set_color(&fg, &da.fg_color); + tem_set_color(&bg, &da.bg_color); + c = TEM_CHAR(string[i].tc_char); + tems_display(&da); + da.col++; + } +} + +/* + * This function is used to mark a rectangular image area so the scrolling + * will know we need to copy the data from there. + */ +void +tem_image_display(struct tem_vt_state *tem, screen_pos_t s_row, + screen_pos_t s_col, screen_pos_t e_row, screen_pos_t e_col) +{ + screen_pos_t i, j; + term_char_t c; + + c.tc_char = TEM_ATTR(TEM_ATTR_IMAGE); + + for (i = s_row; i <= e_row; i++) { + for (j = s_col; j <= e_col; j++) { + tem_virtual_display(tem, &c, 1, i, j); + } + } +} + +/*ARGSUSED*/ +static void +tem_text_copy(struct tem_vt_state *tem __unused, + screen_pos_t s_col, screen_pos_t s_row, + screen_pos_t e_col, screen_pos_t e_row, + screen_pos_t t_col, screen_pos_t t_row) +{ + struct vis_conscopy da; + + da.s_row = s_row; + da.s_col = s_col; + da.e_row = e_row; + da.e_col = e_col; + da.t_row = t_row; + da.t_col = t_col; + tems_copy(&da); +} + +static void +tem_text_cls(struct tem_vt_state *tem, + int count, screen_pos_t row, screen_pos_t col) +{ + text_attr_t attr; + term_char_t c; + int i; + + tem_get_attr(tem, &c.tc_fg_color, &c.tc_bg_color, &attr, + TEM_ATTR_SCREEN_REVERSE); + c.tc_char = TEM_ATTR(attr & ~TEM_ATTR_UNDERLINE) | ' '; + + if (count > tems.ts_c_dimension.width || + col + count > tems.ts_c_dimension.width) + count = tems.ts_c_dimension.width - col; + + for (i = 0; i < count; i++) + tem_text_display(tem, &c, 1, row, col++); + +} + +static void +tem_pix_display(struct tem_vt_state *tem, + term_char_t *string, int count, + screen_pos_t row, screen_pos_t col) +{ + struct vis_consdisplay da; + int i; + + da.data = (uint8_t *)tem->tvs_pix_data; + da.width = tems.ts_font.vf_width; + da.height = tems.ts_font.vf_height; + da.row = (row * da.height) + tems.ts_p_offset.y; + da.col = (col * da.width) + tems.ts_p_offset.x; + + for (i = 0; i < count; i++) { + tem_callback_bit2pix(tem, &string[i]); + tems_display(&da); + da.col += da.width; + } +} + +static void +tem_pix_copy(struct tem_vt_state *tem, + screen_pos_t s_col, screen_pos_t s_row, + screen_pos_t e_col, screen_pos_t e_row, + screen_pos_t t_col, screen_pos_t t_row) +{ + struct vis_conscopy ma; + static boolean_t need_clear = B_TRUE; + + if (need_clear && tem->tvs_first_line > 0) { + /* + * Clear OBP output above our kernel console term + * when our kernel console term begins to scroll up, + * we hope it is user friendly. + * (Also see comments on tem_pix_clear_prom_output) + * + * This is only one time call. + */ + tem_pix_clear_prom_output(tem); + } + need_clear = B_FALSE; + + ma.s_row = s_row * tems.ts_font.vf_height + tems.ts_p_offset.y; + ma.e_row = (e_row + 1) * tems.ts_font.vf_height + + tems.ts_p_offset.y - 1; + ma.t_row = t_row * tems.ts_font.vf_height + tems.ts_p_offset.y; + + /* + * Check if we're in process of clearing OBP's columns area, + * which only happens when term scrolls up a whole line. + */ + if (tem->tvs_first_line > 0 && t_row < s_row && t_col == 0 && + e_col == tems.ts_c_dimension.width - 1) { + /* + * We need to clear OBP's columns area outside our kernel + * console term. So that we set ma.e_col to entire row here. + */ + ma.s_col = s_col * tems.ts_font.vf_width; + ma.e_col = tems.ts_p_dimension.width - 1; + + ma.t_col = t_col * tems.ts_font.vf_width; + } else { + ma.s_col = s_col * tems.ts_font.vf_width + tems.ts_p_offset.x; + ma.e_col = (e_col + 1) * tems.ts_font.vf_width + + tems.ts_p_offset.x - 1; + ma.t_col = t_col * tems.ts_font.vf_width + tems.ts_p_offset.x; + } + + tems_copy(&ma); + + if (tem->tvs_first_line > 0 && t_row < s_row) { + /* We have scrolled up (s_row - t_row) rows. */ + tem->tvs_first_line -= (s_row - t_row); + if (tem->tvs_first_line <= 0) { + /* All OBP rows have been cleared. */ + tem->tvs_first_line = 0; + } + } +} + +static void +tem_pix_bit2pix(struct tem_vt_state *tem, term_char_t *c) +{ + text_color_t fg, bg; + + tem_get_color(tem, &fg, &bg, c); + bit_to_pix32(tem, c->tc_char, fg, bg); +} + + +/* + * This function only clears count of columns in one row + */ +static void +tem_pix_cls(struct tem_vt_state *tem, int count, + screen_pos_t row, screen_pos_t col) +{ + tem_pix_cls_range(tem, row, 1, tems.ts_p_offset.y, + col, count, tems.ts_p_offset.x, B_FALSE); +} + +/* + * This function clears OBP output above our kernel console term area + * because OBP's term may have a bigger terminal window than that of + * our kernel console term. So we need to clear OBP output garbage outside + * of our kernel console term at a proper time, which is when the first + * row output of our kernel console term scrolls at the first screen line. + * + * _________________________________ + * | _____________________ | ---> OBP's bigger term window + * | | | | + * |___| | | + * | | | | | + * | | | | | + * |_|_|___________________|_______| + * | | | ---> first line + * | |___________________|---> our kernel console term window + * | + * |---> columns area to be cleared + * + * This function only takes care of the output above our kernel console term, + * and tem_prom_scroll_up takes care of columns area outside of our kernel + * console term. + */ +static void +tem_pix_clear_prom_output(struct tem_vt_state *tem) +{ + int nrows, ncols, width, height, offset; + + width = tems.ts_font.vf_width; + height = tems.ts_font.vf_height; + offset = tems.ts_p_offset.y % height; + + nrows = tems.ts_p_offset.y / height; + ncols = (tems.ts_p_dimension.width + (width - 1)) / width; + + if (nrows > 0) + tem_pix_cls_range(tem, 0, nrows, offset, 0, ncols, 0, + B_FALSE); +} + +/* + * Clear the whole screen and reset the cursor to start point. + */ +static void +tem_cls(struct tem_vt_state *tem) +{ + struct vis_consclear cl; + text_color_t fg_color; + text_color_t bg_color; + text_attr_t attr; + term_char_t c; + int row; + + for (row = 0; row < tems.ts_c_dimension.height; row++) { + tem_virtual_cls(tem, tems.ts_c_dimension.width, row, 0); + } + + if (!tem->tvs_isactive) + return; + + tem_get_attr(tem, &c.tc_fg_color, &c.tc_bg_color, &attr, + TEM_ATTR_SCREEN_REVERSE); + c.tc_char = TEM_ATTR(attr); + + tem_get_color(tem, &fg_color, &bg_color, &c); + tem_set_color(&bg_color, &cl.bg_color); + (void) tems_cls(&cl); + + tem->tvs_c_cursor.row = 0; + tem->tvs_c_cursor.col = 0; + tem_align_cursor(tem); +} + +static void +tem_back_tab(struct tem_vt_state *tem) +{ + int i; + screen_pos_t tabstop; + + tabstop = 0; + + for (i = tem->tvs_ntabs - 1; i >= 0; i--) { + if (tem->tvs_tabs[i] < tem->tvs_c_cursor.col) { + tabstop = tem->tvs_tabs[i]; + break; + } + } + + tem_mv_cursor(tem, tem->tvs_c_cursor.row, tabstop); +} + +static void +tem_tab(struct tem_vt_state *tem) +{ + size_t i; + screen_pos_t tabstop; + + tabstop = tems.ts_c_dimension.width - 1; + + for (i = 0; i < tem->tvs_ntabs; i++) { + if (tem->tvs_tabs[i] > tem->tvs_c_cursor.col) { + tabstop = tem->tvs_tabs[i]; + break; + } + } + + tem_mv_cursor(tem, tem->tvs_c_cursor.row, tabstop); +} + +static void +tem_set_tab(struct tem_vt_state *tem) +{ + size_t i, j; + + if (tem->tvs_ntabs == tem->tvs_maxtab) + return; + if (tem->tvs_ntabs == 0 || + tem->tvs_tabs[tem->tvs_ntabs] < tem->tvs_c_cursor.col) { + tem->tvs_tabs[tem->tvs_ntabs++] = tem->tvs_c_cursor.col; + return; + } + for (i = 0; i < tem->tvs_ntabs; i++) { + if (tem->tvs_tabs[i] == tem->tvs_c_cursor.col) + return; + if (tem->tvs_tabs[i] > tem->tvs_c_cursor.col) { + for (j = tem->tvs_ntabs - 1; j >= i; j--) + tem->tvs_tabs[j+ 1] = tem->tvs_tabs[j]; + tem->tvs_tabs[i] = tem->tvs_c_cursor.col; + tem->tvs_ntabs++; + return; + } + } +} + +static void +tem_clear_tabs(struct tem_vt_state *tem, int action) +{ + size_t i, j; + + switch (action) { + case 3: /* clear all tabs */ + tem->tvs_ntabs = 0; + break; + case 0: /* clr tab at cursor */ + + for (i = 0; i < tem->tvs_ntabs; i++) { + if (tem->tvs_tabs[i] == tem->tvs_c_cursor.col) { + tem->tvs_ntabs--; + for (j = i; j < tem->tvs_ntabs; j++) + tem->tvs_tabs[j] = tem->tvs_tabs[j + 1]; + return; + } + } + break; + } +} + +static void +tem_mv_cursor(struct tem_vt_state *tem, int row, int col) +{ + /* + * Sanity check and bounds enforcement. Out of bounds requests are + * clipped to the screen boundaries. This seems to be what SPARC + * does. + */ + if (row < 0) + row = 0; + if (row >= tems.ts_c_dimension.height) + row = tems.ts_c_dimension.height - 1; + if (col < 0) + col = 0; + if (col >= tems.ts_c_dimension.width) + col = tems.ts_c_dimension.width - 1; + + tem_send_data(tem); + tem->tvs_c_cursor.row = (screen_pos_t)row; + tem->tvs_c_cursor.col = (screen_pos_t)col; + tem_align_cursor(tem); +} + +/* ARGSUSED */ +static void +tem_reset_emulator(struct tem_vt_state *tem, boolean_t init_color) +{ + int j; + + tem->tvs_c_cursor.row = 0; + tem->tvs_c_cursor.col = 0; + tem->tvs_r_cursor.row = 0; + tem->tvs_r_cursor.col = 0; + tem->tvs_s_cursor.row = 0; + tem->tvs_s_cursor.col = 0; + tem->tvs_outindex = 0; + tem->tvs_state = A_STATE_START; + tem->tvs_gotparam = B_FALSE; + tem->tvs_curparam = 0; + tem->tvs_paramval = 0; + tem->tvs_nscroll = 1; + + if (init_color) { + /* use initial settings */ + tem->tvs_alpha = 0xff; + tem->tvs_fg_color = tems.ts_init_color.fg_color; + tem->tvs_bg_color = tems.ts_init_color.bg_color; + tem->tvs_flags = tems.ts_init_color.a_flags; + } + + /* + * set up the initial tab stops + */ + tem->tvs_ntabs = 0; + for (j = 8; j < tems.ts_c_dimension.width; j += 8) + tem->tvs_tabs[tem->tvs_ntabs++] = (screen_pos_t)j; + + for (j = 0; j < TEM_MAXPARAMS; j++) + tem->tvs_params[j] = 0; +} + +static void +tem_reset_display(struct tem_vt_state *tem, + boolean_t clear_txt, boolean_t init_color) +{ + tem_reset_emulator(tem, init_color); + + if (clear_txt) { + if (tem->tvs_isactive) + tem_callback_cursor(tem, VIS_HIDE_CURSOR); + + tem_cls(tem); + + if (tem->tvs_isactive) + tem_callback_cursor(tem, VIS_DISPLAY_CURSOR); + } +} + +static void +tem_shift(struct tem_vt_state *tem, int count, int direction) +{ + int rest_of_line; + + rest_of_line = tems.ts_c_dimension.width - tem->tvs_c_cursor.col; + if (count > rest_of_line) + count = rest_of_line; + + if (count <= 0) + return; + + switch (direction) { + case TEM_SHIFT_LEFT: + if (count < rest_of_line) { + tem_copy_area(tem, + tem->tvs_c_cursor.col + count, + tem->tvs_c_cursor.row, + tems.ts_c_dimension.width - 1, + tem->tvs_c_cursor.row, + tem->tvs_c_cursor.col, + tem->tvs_c_cursor.row); + } + + tem_clear_chars(tem, count, tem->tvs_c_cursor.row, + (tems.ts_c_dimension.width - count)); + break; + case TEM_SHIFT_RIGHT: + if (count < rest_of_line) { + tem_copy_area(tem, + tem->tvs_c_cursor.col, + tem->tvs_c_cursor.row, + tems.ts_c_dimension.width - count - 1, + tem->tvs_c_cursor.row, + tem->tvs_c_cursor.col + count, + tem->tvs_c_cursor.row); + } + + tem_clear_chars(tem, count, tem->tvs_c_cursor.row, + tem->tvs_c_cursor.col); + break; + } +} + +static void +tem_text_cursor(struct tem_vt_state *tem, short action) +{ + struct vis_conscursor ca; + + ca.row = tem->tvs_c_cursor.row; + ca.col = tem->tvs_c_cursor.col; + ca.action = action; + + tems_cursor(&ca); + + if (action == VIS_GET_CURSOR) { + tem->tvs_c_cursor.row = ca.row; + tem->tvs_c_cursor.col = ca.col; + } +} + +static void +tem_pix_cursor(struct tem_vt_state *tem, short action) +{ + struct vis_conscursor ca; + text_color_t fg, bg; + term_char_t c; + text_attr_t attr; + + ca.row = tem->tvs_c_cursor.row * tems.ts_font.vf_height + + tems.ts_p_offset.y; + ca.col = tem->tvs_c_cursor.col * tems.ts_font.vf_width + + tems.ts_p_offset.x; + ca.width = tems.ts_font.vf_width; + ca.height = tems.ts_font.vf_height; + + tem_get_attr(tem, &c.tc_fg_color, &c.tc_bg_color, &attr, + TEM_ATTR_REVERSE); + c.tc_char = TEM_ATTR(attr); + + tem_get_color(tem, &fg, &bg, &c); + tem_set_color(&fg, &ca.fg_color); + tem_set_color(&bg, &ca.bg_color); + + ca.action = action; + + tems_cursor(&ca); + + if (action == VIS_GET_CURSOR) { + tem->tvs_c_cursor.row = 0; + tem->tvs_c_cursor.col = 0; + + if (ca.row != 0) { + tem->tvs_c_cursor.row = (ca.row - tems.ts_p_offset.y) / + tems.ts_font.vf_height; + } + if (ca.col != 0) { + tem->tvs_c_cursor.col = (ca.col - tems.ts_p_offset.x) / + tems.ts_font.vf_width; + } + } +} + +static void +bit_to_pix32(struct tem_vt_state *tem, + tem_char_t c, text_color_t fg, text_color_t bg) +{ + uint32_t *dest; + + dest = (uint32_t *)tem->tvs_pix_data; + font_bit_to_pix32(&tems.ts_font, dest, c, fg.n, bg.n); +} + +/* + * flag: TEM_ATTR_SCREEN_REVERSE or TEM_ATTR_REVERSE + */ +static void +tem_get_attr(struct tem_vt_state *tem, text_color_t *fg, + text_color_t *bg, text_attr_t *attr, uint8_t flag) +{ + if (tem->tvs_flags & flag) { + *fg = tem->tvs_bg_color; + *bg = tem->tvs_fg_color; + } else { + *fg = tem->tvs_fg_color; + *bg = tem->tvs_bg_color; + } + + if (attr != NULL) + *attr = tem->tvs_flags; +} + +static void +tem_get_color(struct tem_vt_state *tem, text_color_t *fg, text_color_t *bg, + term_char_t *c) +{ + bool bold_font; + + *fg = c->tc_fg_color; + *bg = c->tc_bg_color; + + bold_font = tems.ts_font.vf_map_count[VFNT_MAP_BOLD] != 0; + + /* + * If we have both normal and bold font components, + * we use bold font for TEM_ATTR_BOLD. + * The bright color is traditionally used with TEM_ATTR_BOLD, + * in case there is no bold font. + */ + if (!TEM_ATTR_ISSET(c->tc_char, TEM_ATTR_RGB_FG) && + c->tc_fg_color.n < XLATE_NCOLORS) { + if (TEM_ATTR_ISSET(c->tc_char, TEM_ATTR_BRIGHT_FG) || + (TEM_ATTR_ISSET(c->tc_char, TEM_ATTR_BOLD) && !bold_font)) + fg->n = brt_xlate[c->tc_fg_color.n]; + else + fg->n = dim_xlate[c->tc_fg_color.n]; + } + + if (!TEM_ATTR_ISSET(c->tc_char, TEM_ATTR_RGB_BG) && + c->tc_bg_color.n < XLATE_NCOLORS) { + if (TEM_ATTR_ISSET(c->tc_char, TEM_ATTR_BRIGHT_BG)) + bg->n = brt_xlate[c->tc_bg_color.n]; + else + bg->n = dim_xlate[c->tc_bg_color.n]; + } + + if (tems.ts_display_mode == VIS_TEXT) + return; + + /* + * Translate fg and bg to RGB colors. + */ + if (TEM_ATTR_ISSET(c->tc_char, TEM_ATTR_RGB_FG)) { + fg->n = rgb_to_color(&rgb_info, + fg->rgb.a, fg->rgb.r, fg->rgb.g, fg->rgb.b); + } else { + fg->n = rgb_color_map(&rgb_info, fg->n, tem->tvs_alpha); + } + + if (TEM_ATTR_ISSET(c->tc_char, TEM_ATTR_RGB_BG)) { + bg->n = rgb_to_color(&rgb_info, + bg->rgb.a, bg->rgb.r, bg->rgb.g, bg->rgb.b); + } else { + bg->n = rgb_color_map(&rgb_info, bg->n, tem->tvs_alpha); + } +} + +static void +tem_set_color(text_color_t *t, color_t *c) +{ + switch (tems.ts_pdepth) { + case 4: + c->four = t->n & 0xFF; + break; + default: + /* gfx module is expecting all pixel data in 32-bit colors */ + *(uint32_t *)c = t->n; + break; + } +} + +void +tem_get_colors(tem_vt_state_t tem_arg, text_color_t *fg, text_color_t *bg) +{ + struct tem_vt_state *tem = (struct tem_vt_state *)tem_arg; + text_attr_t attr; + term_char_t c; + + tem_get_attr(tem, &c.tc_fg_color, &c.tc_bg_color, &attr, + TEM_ATTR_REVERSE); + c.tc_char = TEM_ATTR(attr); + tem_get_color(tem, fg, bg, &c); +} + +/* + * Clear a rectangle of screen for pixel mode. + * + * arguments: + * row: start row# + * nrows: the number of rows to clear + * offset_y: the offset of height in pixels to begin clear + * col: start col# + * ncols: the number of cols to clear + * offset_x: the offset of width in pixels to begin clear + * scroll_up: whether this function is called during sroll up, + * which is called only once. + */ +static void +tem_pix_cls_range(struct tem_vt_state *tem, + screen_pos_t row, int nrows, int offset_y, + screen_pos_t col, int ncols, int offset_x, + boolean_t sroll_up) +{ + struct vis_consdisplay da; + int i, j; + int row_add = 0; + term_char_t c; + text_attr_t attr; + + if (sroll_up) + row_add = tems.ts_c_dimension.height - 1; + + da.width = tems.ts_font.vf_width; + da.height = tems.ts_font.vf_height; + + tem_get_attr(tem, &c.tc_fg_color, &c.tc_bg_color, &attr, + TEM_ATTR_SCREEN_REVERSE); + /* Make sure we will not draw underlines */ + c.tc_char = TEM_ATTR(attr & ~TEM_ATTR_UNDERLINE) | ' '; + + tem_callback_bit2pix(tem, &c); + da.data = (uint8_t *)tem->tvs_pix_data; + + for (i = 0; i < nrows; i++, row++) { + da.row = (row + row_add) * da.height + offset_y; + da.col = col * da.width + offset_x; + for (j = 0; j < ncols; j++) { + tems_display(&da); + da.col += da.width; + } + } +} + +/* + * virtual screen operations + */ +static void +tem_virtual_display(struct tem_vt_state *tem, term_char_t *string, + size_t count, screen_pos_t row, screen_pos_t col) +{ + size_t i, width; + term_char_t *addr; + + if (tem->tvs_screen_buf == NULL) + return; + + if (row < 0 || row >= tems.ts_c_dimension.height || + col < 0 || col >= tems.ts_c_dimension.width || + col + count > (size_t)tems.ts_c_dimension.width) + return; + + width = tems.ts_c_dimension.width; + addr = tem->tvs_screen_buf + (row * width + col); + for (i = 0; i < count; i++) { + *addr++ = string[i]; + } +} + +static void +tem_virtual_cls(struct tem_vt_state *tem, size_t count, + screen_pos_t row, screen_pos_t col) +{ + term_char_t c; + text_attr_t attr; + + tem_get_attr(tem, &c.tc_fg_color, &c.tc_bg_color, &attr, + TEM_ATTR_SCREEN_REVERSE); + /* Make sure we will not draw underlines */ + c.tc_char = TEM_ATTR(attr & ~TEM_ATTR_UNDERLINE) | ' '; + + while (count > 0) { + tem_virtual_display(tem, &c, 1, row, col); + col++; + count--; + } +} diff --git a/usr/src/boot/common/util.c b/usr/src/boot/common/util.c new file mode 100644 index 0000000000..d73d992105 --- /dev/null +++ b/usr/src/boot/common/util.c @@ -0,0 +1,182 @@ +/*- + * Copyright (c) 1998 Robert Nordier + * Copyright (c) 2010 Pawel Jakub Dawidek <pjd@FreeBSD.org> + * All rights reserved. + * + * Redistribution and use in source and binary forms are freely + * permitted provided that the above copyright notice and this + * paragraph and the following disclaimer are duplicated in all + * such forms. + * + * This software is provided "AS IS" and without any express or + * implied warranties, including, without limitation, the implied + * warranties of merchantability and fitness for a particular + * purpose. + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <sys/param.h> + +#include <stdarg.h> + +#include "cons.h" +#include "util.h" + +void +memcpy(void *dst, const void *src, int len) +{ + const char *s = src; + char *d = dst; + + while (len--) + *d++ = *s++; +} + +void +memset(void *b, int c, size_t len) +{ + char *bp = b; + + while (len--) + *bp++ = (unsigned char)c; +} + +int +memcmp(const void *b1, const void *b2, size_t len) +{ + const unsigned char *p1, *p2; + + for (p1 = b1, p2 = b2; len > 0; len--, p1++, p2++) { + if (*p1 != *p2) + return ((*p1) - (*p2)); + } + return (0); +} + +int +strcmp(const char *s1, const char *s2) +{ + + for (; *s1 == *s2 && *s1 != '\0'; s1++, s2++) + ; + return ((unsigned char)*s1 - (unsigned char)*s2); +} + +int +strncmp(const char *s1, const char *s2, size_t len) +{ + + for (; len > 0 && *s1 == *s2 && *s1 != '\0'; len--, s1++, s2++) + ; + return (len == 0 ? 0 : (unsigned char)*s1 - (unsigned char)*s2); +} + +void +strcpy(char *dst, const char *src) +{ + + while (*src != '\0') + *dst++ = *src++; + *dst = '\0'; +} + +void +strcat(char *dst, const char *src) +{ + + while (*dst != '\0') + dst++; + while (*src != '\0') + *dst++ = *src++; + *dst = '\0'; +} + +char * +strchr(const char *s, char ch) +{ + + for (; *s != '\0'; s++) { + if (*s == ch) + return ((char *)(uintptr_t)(const void *)s); + } + return (NULL); +} + +size_t +strlen(const char *s) +{ + size_t len = 0; + + while (*s++ != '\0') + len++; + return (len); +} + +int +printf(const char *fmt, ...) +{ + va_list ap; + const char *hex = "0123456789abcdef"; + char buf[32], *s; + uint16_t *S; + unsigned long long u; + int c, l; + + va_start(ap, fmt); + while ((c = *fmt++) != '\0') { + if (c != '%') { + putchar(c); + continue; + } + l = 0; +nextfmt: + c = *fmt++; + switch (c) { + case 'l': + l++; + goto nextfmt; + case 'c': + putchar(va_arg(ap, int)); + break; + case 's': + for (s = va_arg(ap, char *); *s != '\0'; s++) + putchar(*s); + break; + case 'S': /* Assume console can cope with wide chars */ + for (S = va_arg(ap, uint16_t *); *S != 0; S++) + putchar(*S); + break; + case 'd': /* A lie, always prints unsigned */ + case 'u': + case 'x': + switch (l) { + case 2: + u = va_arg(ap, unsigned long long); + break; + case 1: + u = va_arg(ap, unsigned long); + break; + default: + u = va_arg(ap, unsigned int); + break; + } + s = buf; + if (c == 'd' || c == 'u') { + do + *s++ = '0' + (u % 10U); + while (u /= 10); + } else { + do + *s++ = hex[u & 0xfu]; + while (u >>= 4); + } + while (--s >= buf) + putchar(*s); + break; + } + } + va_end(ap); + return (0); +} diff --git a/usr/src/boot/common/util.h b/usr/src/boot/common/util.h new file mode 100644 index 0000000000..88a99f19f0 --- /dev/null +++ b/usr/src/boot/common/util.h @@ -0,0 +1,53 @@ +/*- + * Copyright (c) 2010 Pawel Jakub Dawidek <pjd@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 AUTHORS 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 AUTHORS 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 _UTIL_H_ +#define _UTIL_H_ + +#include <sys/types.h> + +#include <stdarg.h> + +void memcpy(void *dst, const void *src, int len); +void memset(void *b, int c, size_t len); +int memcmp(const void *b1, const void *b2, size_t len); + +#define bcopy(src, dst, len) memcpy((dst), (src), (len)) +#define bzero(buf, size) memset((buf), 0, (size)) +#define bcmp(b1, b2, len) (memcmp((b1), (b2), (len)) != 0) + +int strcmp(const char *s1, const char *s2); +int strncmp(const char *s1, const char *s2, size_t len); +void strcpy(char *dst, const char *src); +void strcat(char *dst, const char *src); +char *strchr(const char *s, char ch); +size_t strlen(const char *s); + +int printf(const char *fmt, ...); + +#endif /* !_UTIL_H_ */ diff --git a/usr/src/boot/common/vdisk.c b/usr/src/boot/common/vdisk.c new file mode 100644 index 0000000000..bb5b2eb6d1 --- /dev/null +++ b/usr/src/boot/common/vdisk.c @@ -0,0 +1,416 @@ +/* + * Copyright 2019 Toomas Soome <tsoome@me.com> + * + * 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> + +#include <stand.h> +#include <stdarg.h> +#include <inttypes.h> +#include <bootstrap.h> +#include <sys/disk.h> +#include <sys/errno.h> +#include <sys/queue.h> +#include <sys/param.h> +#include <disk.h> + +static int vdisk_init(void); +static int vdisk_strategy(void *, int, daddr_t, size_t, char *, size_t *); +static int vdisk_open(struct open_file *, ...); +static int vdisk_close(struct open_file *); +static int vdisk_ioctl(struct open_file *, ulong_t, void *); +static int vdisk_print(int); + +struct devsw vdisk_dev = { + .dv_name = "vdisk", + .dv_type = DEVT_DISK, + .dv_init = vdisk_init, + .dv_strategy = vdisk_strategy, + .dv_open = vdisk_open, + .dv_close = vdisk_close, + .dv_ioctl = vdisk_ioctl, + .dv_print = vdisk_print, + .dv_cleanup = NULL +}; + +typedef STAILQ_HEAD(vdisk_info_list, vdisk_info) vdisk_info_list_t; + +typedef struct vdisk_info +{ + STAILQ_ENTRY(vdisk_info) vdisk_link; /* link in device list */ + char *vdisk_path; + int vdisk_unit; + int vdisk_fd; + uint64_t vdisk_size; /* size in bytes */ + uint32_t vdisk_sectorsz; + uint32_t vdisk_open; /* reference counter */ +} vdisk_info_t; + +static vdisk_info_list_t vdisk_list; /* list of mapped vdisks. */ + +static vdisk_info_t * +vdisk_get_info(struct devdesc *dev) +{ + vdisk_info_t *vd; + + STAILQ_FOREACH(vd, &vdisk_list, vdisk_link) { + if (vd->vdisk_unit == dev->d_unit) + return (vd); + } + return (vd); +} + +COMMAND_SET(map_vdisk, "map-vdisk", "map file as virtual disk", command_mapvd); + +static int +command_mapvd(int argc, char *argv[]) +{ + vdisk_info_t *vd, *p; + struct stat sb; + + if (argc != 2) { + printf("usage: %s filename\n", argv[0]); + return (CMD_ERROR); + } + + STAILQ_FOREACH(vd, &vdisk_list, vdisk_link) { + if (strcmp(vd->vdisk_path, argv[1]) == 0) { + printf("%s: file %s is already mapped as %s%d\n", + argv[0], argv[1], vdisk_dev.dv_name, + vd->vdisk_unit); + return (CMD_ERROR); + } + } + + if (stat(argv[1], &sb) < 0) { + /* + * ENOSYS is really ENOENT because we did try to walk + * through devsw list to try to open this file. + */ + if (errno == ENOSYS) + errno = ENOENT; + + printf("%s: stat failed: %s\n", argv[0], strerror(errno)); + return (CMD_ERROR); + } + + /* + * Avoid mapping small files. + */ + if (sb.st_size < 1024 * 1024) { + printf("%s: file %s is too small.\n", argv[0], argv[1]); + return (CMD_ERROR); + } + + vd = calloc(1, sizeof (*vd)); + if (vd == NULL) { + printf("%s: out of memory\n", argv[0]); + return (CMD_ERROR); + } + vd->vdisk_path = strdup(argv[1]); + if (vd->vdisk_path == NULL) { + free(vd); + printf("%s: out of memory\n", argv[0]); + return (CMD_ERROR); + } + vd->vdisk_fd = open(vd->vdisk_path, O_RDONLY); + if (vd->vdisk_fd < 0) { + printf("%s: open failed: %s\n", argv[0], strerror(errno)); + free(vd->vdisk_path); + free(vd); + return (CMD_ERROR); + } + + vd->vdisk_size = sb.st_size; + vd->vdisk_sectorsz = DEV_BSIZE; + STAILQ_FOREACH(p, &vdisk_list, vdisk_link) { + vdisk_info_t *n; + if (p->vdisk_unit == vd->vdisk_unit) { + vd->vdisk_unit++; + continue; + } + n = STAILQ_NEXT(p, vdisk_link); + if (p->vdisk_unit < vd->vdisk_unit) { + if (n == NULL) { + /* p is last elem */ + STAILQ_INSERT_TAIL(&vdisk_list, vd, vdisk_link); + break; + } + if (n->vdisk_unit > vd->vdisk_unit) { + /* p < vd < n */ + STAILQ_INSERT_AFTER(&vdisk_list, p, vd, + vdisk_link); + break; + } + /* else n < vd or n == vd */ + vd->vdisk_unit++; + continue; + } + /* p > vd only if p is the first element */ + STAILQ_INSERT_HEAD(&vdisk_list, vd, vdisk_link); + break; + } + + /* if the list was empty or contiguous */ + if (p == NULL) + STAILQ_INSERT_TAIL(&vdisk_list, vd, vdisk_link); + + printf("%s: file %s is mapped as %s%d\n", argv[0], vd->vdisk_path, + vdisk_dev.dv_name, vd->vdisk_unit); + return (CMD_OK); +} + +COMMAND_SET(unmap_vdisk, "unmap-vdisk", "unmap virtual disk", command_unmapvd); + +/* + * unmap-vdisk vdiskX + */ +static int +command_unmapvd(int argc, char *argv[]) +{ + size_t len; + vdisk_info_t *vd; + long unit; + char *end; + + if (argc != 2) { + printf("usage: %s %sN\n", argv[0], vdisk_dev.dv_name); + return (CMD_ERROR); + } + + len = strlen(vdisk_dev.dv_name); + if (strncmp(vdisk_dev.dv_name, argv[1], len) != 0) { + printf("%s: unknown device %s\n", argv[0], argv[1]); + return (CMD_ERROR); + } + errno = 0; + unit = strtol(argv[1] + len, &end, 10); + if (errno != 0 || (*end != '\0' && strcmp(end, ":") != 0)) { + printf("%s: unknown device %s\n", argv[0], argv[1]); + return (CMD_ERROR); + } + + STAILQ_FOREACH(vd, &vdisk_list, vdisk_link) { + if (vd->vdisk_unit == unit) + break; + } + + if (vd == NULL) { + printf("%s: unknown device %s\n", argv[0], argv[1]); + return (CMD_ERROR); + } + + if (vd->vdisk_open != 0) { + printf("%s: %s is in use, unable to unmap.\n", + argv[0], argv[1]); + return (CMD_ERROR); + } + + STAILQ_REMOVE(&vdisk_list, vd, vdisk_info, vdisk_link); + (void) close(vd->vdisk_fd); + printf("%s (%s) unmapped\n", argv[1], vd->vdisk_path); + free(vd->vdisk_path); + free(vd); + + return (CMD_OK); +} + +static int +vdisk_init(void) +{ + STAILQ_INIT(&vdisk_list); + return (0); +} + +static int +vdisk_strategy(void *devdata, int rw, daddr_t blk, size_t size, + char *buf, size_t *rsize) +{ + struct disk_devdesc *dev; + vdisk_info_t *vd; + ssize_t rv; + + dev = devdata; + if (dev == NULL) + return (EINVAL); + vd = vdisk_get_info((struct devdesc *)dev); + if (vd == NULL) + return (EINVAL); + + if (size == 0 || (size % 512) != 0) + return (EIO); + + if (dev->dd.d_dev->dv_type == DEVT_DISK) { + daddr_t offset; + + offset = dev->d_offset * vd->vdisk_sectorsz; + offset /= 512; + blk += offset; + } + if (lseek(vd->vdisk_fd, blk << 9, SEEK_SET) == -1) + return (EIO); + + errno = 0; + switch (rw & F_MASK) { + case F_READ: + rv = read(vd->vdisk_fd, buf, size); + break; + case F_WRITE: + rv = write(vd->vdisk_fd, buf, size); + break; + default: + return (ENOSYS); + } + + if (errno == 0 && rsize != NULL) { + *rsize = rv; + } + return (errno); +} + +static int +vdisk_open(struct open_file *f, ...) +{ + va_list args; + struct disk_devdesc *dev; + vdisk_info_t *vd; + int rc = 0; + + va_start(args, f); + dev = va_arg(args, struct disk_devdesc *); + va_end(args); + if (dev == NULL) + return (EINVAL); + vd = vdisk_get_info((struct devdesc *)dev); + if (vd == NULL) + return (EINVAL); + + if (dev->dd.d_dev->dv_type == DEVT_DISK) { + rc = disk_open(dev, vd->vdisk_size, vd->vdisk_sectorsz); + } + if (rc == 0) + vd->vdisk_open++; + return (rc); +} + +static int +vdisk_close(struct open_file *f) +{ + struct disk_devdesc *dev; + vdisk_info_t *vd; + + dev = (struct disk_devdesc *)(f->f_devdata); + if (dev == NULL) + return (EINVAL); + vd = vdisk_get_info((struct devdesc *)dev); + if (vd == NULL) + return (EINVAL); + + vd->vdisk_open--; + if (dev->dd.d_dev->dv_type == DEVT_DISK) + return (disk_close(dev)); + return (0); +} + +static int +vdisk_ioctl(struct open_file *f, ulong_t cmd, void *data) +{ + struct disk_devdesc *dev; + vdisk_info_t *vd; + int rc; + + dev = (struct disk_devdesc *)(f->f_devdata); + if (dev == NULL) + return (EINVAL); + vd = vdisk_get_info((struct devdesc *)dev); + if (vd == NULL) + return (EINVAL); + + if (dev->dd.d_dev->dv_type == DEVT_DISK) { + rc = disk_ioctl(dev, cmd, data); + if (rc != ENOTTY) + return (rc); + } + + switch (cmd) { + case DIOCGSECTORSIZE: + *(uint_t *)data = vd->vdisk_sectorsz; + break; + case DIOCGMEDIASIZE: + *(uint64_t *)data = vd->vdisk_size; + break; + default: + return (ENOTTY); + } + return (0); +} + +static int +vdisk_print(int verbose) +{ + int ret = 0; + vdisk_info_t *vd; + char line[80]; + + if (STAILQ_EMPTY(&vdisk_list)) + return (ret); + + printf("%s devices:", vdisk_dev.dv_name); + if ((ret = pager_output("\n")) != 0) + return (ret); + + STAILQ_FOREACH(vd, &vdisk_list, vdisk_link) { + struct disk_devdesc vd_dev; + + if (verbose) { + printf(" %s", vd->vdisk_path); + if ((ret = pager_output("\n")) != 0) + break; + } + snprintf(line, sizeof (line), + " %s%d", vdisk_dev.dv_name, vd->vdisk_unit); + printf("%s: %" PRIu64 " X %u blocks", line, + vd->vdisk_size / vd->vdisk_sectorsz, + vd->vdisk_sectorsz); + if ((ret = pager_output("\n")) != 0) + break; + + vd_dev.dd.d_dev = &vdisk_dev; + vd_dev.dd.d_unit = vd->vdisk_unit; + vd_dev.d_slice = -1; + vd_dev.d_partition = -1; + + ret = disk_open(&vd_dev, vd->vdisk_size, vd->vdisk_sectorsz); + if (ret == 0) { + ret = disk_print(&vd_dev, line, verbose); + disk_close(&vd_dev); + if (ret != 0) + break; + } else { + ret = 0; + } + } + + return (ret); +} diff --git a/usr/src/boot/common/zfs_cmd.c b/usr/src/boot/common/zfs_cmd.c new file mode 100644 index 0000000000..fd8edd10c3 --- /dev/null +++ b/usr/src/boot/common/zfs_cmd.c @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2018 Warner Losh <imp@freebd.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> + +#include <stand.h> +#include <stddef.h> +#include <sys/disk.h> +#include <sys/reboot.h> + +#include "bootstrap.h" + +#include "libzfs.h" + +COMMAND_SET(lszfs, "lszfs", "list child datasets of a zfs dataset", + command_lszfs); + +static int +command_lszfs(int argc, char *argv[]) +{ + int err; + + if (argc != 2) { + command_errmsg = "a single dataset must be supplied"; + return (CMD_ERROR); + } + + err = zfs_list(argv[1]); + if (err != 0) { + command_errmsg = strerror(err); + return (CMD_ERROR); + } + return (CMD_OK); +} + +uint64_t +ldi_get_size(void *priv) +{ + int fd = (uintptr_t) priv; + uint64_t size; + + ioctl(fd, DIOCGMEDIASIZE, &size); + return (size); +} |