diff --git a/stand/kboot/Makefile b/stand/kboot/Makefile index 518e945a596e..c8bd313d1d1d 100644 --- a/stand/kboot/Makefile +++ b/stand/kboot/Makefile @@ -1,54 +1,55 @@ # $FreeBSD$ LOADER_DISK_SUPPORT?= yes LOADER_CD9660_SUPPORT?= yes LOADER_MSDOS_SUPPORT?= no LOADER_EXT2FS_SUPPORT?= yes LOADER_UFS_SUPPORT?= yes LOADER_NET_SUPPORT?= yes LOADER_NFS_SUPPORT?= yes LOADER_TFTP_SUPPORT?= no LOADER_GZIP_SUPPORT?= yes LOADER_BZIP2_SUPPORT?= no .include PROG= loader.kboot NEWVERSWHAT= "kboot loader" ${MACHINE_ARCH} INSTALLFLAGS= -b # Architecture-specific loader code SRCS= \ conf.c \ crt1.c \ gfx_fb_stub.c \ host_syscalls.c \ hostcons.c \ hostdisk.c \ + hostfs.c \ init.c \ kbootfdt.c \ main.c \ termios.c \ vers.c \ CFLAGS.gfx_fb_stub.c += -I${SRCTOP}/contrib/pnglite -I${SRCTOP}/sys/teken .include "${BOOTSRC}/fdt.mk" # Note: Since we're producing a userland binary, we key off of MACHINE_ARCH # instead of the more normal MACHINE since the changes between different flavors # of MACHINE_ARCH are large enough in Linux that it's easier that way. .PATH: ${.CURDIR}/arch/${MACHINE_ARCH} .include "${.CURDIR}/arch/${MACHINE_ARCH}/Makefile.inc" # Always add MI sources .include "${BOOTSRC}/loader.mk" .PATH: ${SYSDIR}/libkern CFLAGS+= -I${.CURDIR} -I${.CURDIR}/arch/${MACHINE_ARCH} CFLAGS+= -Wall DPADD= ${LDR_INTERP} ${LIBOFW} ${LIBFDT} ${LIBSA} LDADD= ${LDR_INTERP} ${LIBOFW} ${LIBFDT} ${LIBSA} .include diff --git a/stand/kboot/conf.c b/stand/kboot/conf.c index b840d008a347..26788342c4b9 100644 --- a/stand/kboot/conf.c +++ b/stand/kboot/conf.c @@ -1,104 +1,109 @@ /*- * Copyright (C) 1999 Michael Smith * 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 __FBSDID("$FreeBSD$"); #include #include "bootstrap.h" #if defined(LOADER_NET_SUPPORT) #include "dev_net.h" #endif extern struct devsw hostdisk; +extern struct devsw host_dev; /* * We could use linker sets for some or all of these, but * then we would have to control what ended up linked into * the bootstrap. So it's easier to conditionalise things * here. * * XXX rename these arrays to be consistent and less namespace-hostile */ /* Exported for libsa */ struct devsw *devsw[] = { #if defined(LOADER_DISK_SUPPORT) || defined(LOADER_CD9660_SUPPORT) &hostdisk, #endif #if defined(LOADER_NET_SUPPORT) &netdev, #endif + &host_dev, NULL }; +extern struct fs_ops hostfs_fsops; + struct fs_ops *file_system[] = { #if defined(LOADER_UFS_SUPPORT) &ufs_fsops, #endif #if defined(LOADER_CD9660_SUPPORT) &cd9660_fsops, #endif #if defined(LOADER_EXT2FS_SUPPORT) &ext2fs_fsops, #endif #if defined(LOADER_NFS_SUPPORT) &nfs_fsops, #endif #if defined(LOADER_TFTP_SUPPORT) &tftp_fsops, #endif #if defined(LOADER_GZIP_SUPPORT) &gzipfs_fsops, #endif #if defined(LOADER_BZIP2_SUPPORT) &bzipfs_fsops, #endif &dosfs_fsops, + &hostfs_fsops, NULL }; extern struct netif_driver kbootnet; struct netif_driver *netif_drivers[] = { #if 0 /* XXX */ #if defined(LOADER_NET_SUPPORT) &kbootnet, #endif #endif NULL, }; /* * Consoles */ extern struct console hostconsole; struct console *consoles[] = { &hostconsole, NULL }; diff --git a/stand/kboot/hostfs.c b/stand/kboot/hostfs.c new file mode 100644 index 000000000000..08f532999abe --- /dev/null +++ b/stand/kboot/hostfs.c @@ -0,0 +1,280 @@ +/*- + * Copyright (c) 2022 Netflix, Inc + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include "stand.h" +#include "host_syscall.h" +#include "kboot.h" + +#define HOST_PATH_MAX 1025 + +extern struct devsw host_dev; + +const char *hostfs_root = "/"; + +enum FTYPE { + regular, + dir, +}; + +typedef struct _hostfs_file { + enum FTYPE hf_type; + int hf_fd; + /* The following are only used for FTYPE == dir */ + char hf_dents[2048]; + char *hf_curdent; + int hf_dentlen; /* Valid part of hf_dents */ +} hostfs_file; + +static hostfs_file * +hostfs_alloc(void) +{ + hostfs_file *hf; + + hf = malloc(sizeof(*hf)); + if (hf != NULL) + memset(hf, 0, sizeof(*hf)); + return (hf); +} + +static void +hostfs_free(hostfs_file *hf) +{ + free(hf); +} + +static int +hostfs_open(const char *fn, struct open_file *f) +{ + hostfs_file *hf; + struct host_kstat ksb; + char path[HOST_PATH_MAX]; + + if (f->f_dev != &host_dev) { + return (EINVAL); + } + + /* + * Normally, we root everything at hostfs_root. However, there are two + * exceptions that make it easier to write code. First is /sys and /proc + * are special Linux filesystems, so we pass those paths + * through. Second, if the path starts with //, then we strip off the + * first / and pass it through (in a weird way, this is actually in + * POSIX: hosts are allowed to do specail things with paths that start + * with two //, but one or three or more are required to be treated as + * one). + */ + if (strncmp("/sys/", fn, 5) == 0 || strncmp("/proc/", fn, 6) == 0) + strlcpy(path, fn, sizeof(path)); + else if (fn[0] == '/' && fn[1] == '/' && fn[2] != '/') + strlcpy(path, fn + 1, sizeof(path)); + else + snprintf(path, sizeof(path), "%s/%s", hostfs_root, fn); + hf = hostfs_alloc(); + hf->hf_fd = host_open(path, HOST_O_RDONLY, 0); + if (hf->hf_fd < 0) { + hostfs_free(hf); + return (EINVAL); + } + + if (host_fstat(hf->hf_fd, &ksb) < 0) { + hostfs_free(hf); + return (EINVAL); + } + if (S_ISDIR(hf->hf_fd)) { + hf->hf_type = dir; + } else { + hf->hf_type = regular; + } + f->f_fsdata = hf; + return (0); +} + +static int +hostfs_close(struct open_file *f) +{ + hostfs_file *hf = f->f_fsdata; + + host_close(hf->hf_fd); + hostfs_free(hf); + f->f_fsdata = NULL; + + return (0); +} + +static int +hostfs_read(struct open_file *f, void *start, size_t size, size_t *resid) +{ + hostfs_file *hf = f->f_fsdata; + ssize_t sz; + + sz = host_read(hf->hf_fd, start, size); + if (sz < 0) { + return (EINVAL); + } + *resid = size - sz; + + return (0); +} + +static off_t +hostfs_seek(struct open_file *f, off_t offset, int whence) +{ + hostfs_file *hf = f->f_fsdata; + uint32_t offl, offh; + int err; + uint64_t res; + + /* + * Assumes Linux host with 'reduced' system call wrappers. Also assume + * host and libstand have same whence encoding (safe since it all comes + * from V7 later ISO-C). Also assumes we have to support powerpc still, + * it's interface is weird for legacy reasons.... + */ + offl = offset & 0xffffffff; + offh = (offset >> 32) & 0xffffffff; + err = host_llseek(hf->hf_fd, offh, offl, &res, whence); + if (err < 0) + return (err); + return (res); +} + +static int +hostfs_stat(struct open_file *f, struct stat *sb) +{ + struct host_kstat ksb; + hostfs_file *hf = f->f_fsdata; + + if (host_fstat(hf->hf_fd, &ksb) < 0) + return (EINVAL); + /* + * Translate Linux stat info to lib stand's notion (which uses FreeBSD's + * stat structure, missing fields are zero and commented below). + */ + memset(sb, 0, sizeof(*sb)); + sb->st_dev = ksb.st_dev; + sb->st_ino = ksb.st_ino; + sb->st_nlink = ksb.st_nlink; + sb->st_mode = ksb.st_mode; + sb->st_uid = ksb.st_uid; + sb->st_gid = ksb.st_gid; + sb->st_rdev = ksb.st_rdev; + /* No st_?time_ext on i386 */ + sb->st_atim.tv_sec = ksb.st_atime_sec; + sb->st_atim.tv_nsec = ksb.st_atime_nsec; + sb->st_mtim.tv_sec = ksb.st_mtime_sec; + sb->st_mtim.tv_nsec = ksb.st_mtime_nsec; + sb->st_ctim.tv_sec = ksb.st_ctime_sec; + sb->st_ctim.tv_nsec = ksb.st_ctime_nsec; + /* No st_birthtim */ + sb->st_size = ksb.st_size; + sb->st_blocks = ksb.st_blocks; + sb->st_blksize = ksb.st_blksize; + /* no st_flags */ + /* no st_get */ + + return (0); +} + +static int +hostfs_readdir(struct open_file *f, struct dirent *d) +{ + hostfs_file *hf = f->f_fsdata; + int dentlen; + struct host_dirent64 *dent; + + if (hf->hf_curdent == NULL) { + dentlen = host_getdents64(hf->hf_fd, hf->hf_dents, sizeof(hf->hf_dents)); + if (dentlen <= 0) + return (EINVAL); + hf->hf_dentlen = dentlen; + hf->hf_curdent = hf->hf_dents; + } + dent = (struct host_dirent64 *)hf->hf_curdent; + d->d_fileno = dent->d_ino; + d->d_type = dent->d_type; /* HOST_DT_XXXX == DX_XXXX for all values */ + strlcpy(d->d_name, dent->d_name, sizeof(d->d_name)); /* d_name is NUL terminated */ + d->d_namlen = strlen(d->d_name); + hf->hf_curdent += dent->d_reclen; + if (hf->hf_curdent >= hf->hf_dents + hf->hf_dentlen) { + hf->hf_curdent = NULL; + hf->hf_dentlen = 0; + } + + return (0); +} + +struct fs_ops hostfs_fsops = { + .fs_name = "host", + .fo_open = hostfs_open, + .fo_close = hostfs_close, + .fo_read = hostfs_read, + .fo_write = null_write, + .fo_seek = hostfs_seek, + .fo_stat = hostfs_stat, + .fo_readdir = hostfs_readdir +}; + +/* + * Generic "null" host device. This goes hand and hand with the host fs object + * + * XXXX This and userboot for bhyve both use exactly the same code, modulo some + * formatting nits. Make them common. We mostly use it to 'gate' the open of the + * filesystem to only this device. + */ + +static int +host_dev_init(void) +{ + return (0); +} + +static int +host_dev_print(int verbose) +{ + char line[80]; + + printf("%s devices:", host_dev.dv_name); + if (pager_output("\n") != 0) + return (1); + + snprintf(line, sizeof(line), " host%d: Host filesystem\n", 0); + return (pager_output(line)); +} + +/* + * 'Open' the host device. + */ +static int +host_dev_open(struct open_file *f, ...) +{ + return (0); +} + +static int +host_dev_close(struct open_file *f) +{ + return (0); +} + +static int +host_dev_strategy(void *devdata, int rw, daddr_t dblk, size_t size, + char *buf, size_t *rsize) +{ + return (ENOSYS); +} + +struct devsw host_dev = { + .dv_name = "host", + .dv_type = DEVT_NET, + .dv_init = host_dev_init, + .dv_strategy = host_dev_strategy, + .dv_open = host_dev_open, + .dv_close = host_dev_close, + .dv_ioctl = noioctl, + .dv_print = host_dev_print, + .dv_cleanup = NULL +}; diff --git a/stand/kboot/kboot.h b/stand/kboot/kboot.h index 81bd18faa893..5441c90eaecc 100644 --- a/stand/kboot/kboot.h +++ b/stand/kboot/kboot.h @@ -1,20 +1,22 @@ /*- * Copyright (c) 2022, Netflix, Inc. * * SPDX-License-Identifier: BSD-2-Clause */ #ifndef KBOOT_H #define KBOOT_H #define DEVT_HOSTDISK 1234 void do_init(void); +extern const char *hostfs_root; + /* Per-platform fdt fixup */ void fdt_arch_fixups(void *fdtp); uint64_t kboot_get_phys_load_segment(void); uint8_t kboot_get_kernel_machine_bits(void); #endif /* KBOOT_H */ diff --git a/stand/kboot/main.c b/stand/kboot/main.c index 10dd6b05194b..23211ce9df4c 100644 --- a/stand/kboot/main.c +++ b/stand/kboot/main.c @@ -1,339 +1,341 @@ /*- * Copyright (C) 2010-2014 Nathan Whitehorn * 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 TOOLS GMBH 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 __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include "host_syscall.h" #include "kboot.h" struct arch_switch archsw; extern void *_end; int kboot_getdev(void **vdev, const char *devspec, const char **path); ssize_t kboot_copyin(const void *src, vm_offset_t dest, const size_t len); ssize_t kboot_copyout(vm_offset_t src, void *dest, const size_t len); ssize_t kboot_readin(readin_handle_t fd, vm_offset_t dest, const size_t len); int kboot_autoload(void); uint64_t kboot_loadaddr(u_int type, void *data, uint64_t addr); static void kboot_kseg_get(int *nseg, void **ptr); extern int command_fdt_internal(int argc, char *argv[]); int kboot_getdev(void **vdev, const char *devspec, const char **path) { int i, rv; const char *devpath, *filepath; struct devsw *dv; struct devdesc *desc; if (devspec == NULL) { rv = kboot_getdev(vdev, getenv("currdev"), NULL); if (rv == 0 && path != NULL) *path = devspec; return (rv); } if (strchr(devspec, ':') != NULL) { devpath = devspec; filepath = strchr(devspec, ':') + 1; } else { devpath = getenv("currdev"); filepath = devspec; } for (i = 0; (dv = devsw[i]) != NULL; i++) { if (strncmp(dv->dv_name, devpath, strlen(dv->dv_name)) == 0) goto found; } return (ENOENT); found: if (path != NULL && filepath != NULL) *path = filepath; else if (path != NULL) *path = strchr(devspec, ':') + 1; if (vdev != NULL) { desc = malloc(sizeof(*desc)); desc->d_dev = dv; desc->d_unit = 0; desc->d_opendata = strdup(devpath); *vdev = desc; } return (0); } int main(int argc, const char **argv) { void *heapbase; const size_t heapsize = 15*1024*1024; const char *bootdev; /* Give us a sane world if we're running as init */ do_init(); /* * Setup the heap 15MB should be plenty */ heapbase = host_getmem(heapsize); setheap(heapbase, heapbase + heapsize); /* * Set up console. */ cons_probe(); /* Choose bootdev if provided */ if (argc > 1) bootdev = argv[1]; else bootdev = ""; + if (argc > 2) + hostfs_root = argv[2]; - printf("Boot device: %s\n", bootdev); + printf("Boot device: %s with hostfs_root %s\n", bootdev, hostfs_root); archsw.arch_getdev = kboot_getdev; archsw.arch_copyin = kboot_copyin; archsw.arch_copyout = kboot_copyout; archsw.arch_readin = kboot_readin; archsw.arch_autoload = kboot_autoload; archsw.arch_loadaddr = kboot_loadaddr; archsw.arch_kexec_kseg_get = kboot_kseg_get; printf("\n%s", bootprog_info); setenv("currdev", bootdev, 1); setenv("loaddev", bootdev, 1); setenv("LINES", "24", 1); setenv("usefdt", "1", 1); interact(); /* doesn't return */ return (0); } void exit(int code) { host_exit(code); __unreachable(); } void delay(int usecs) { struct host_timeval tvi, tv; uint64_t ti, t; host_gettimeofday(&tvi, NULL); ti = tvi.tv_sec*1000000 + tvi.tv_usec; do { host_gettimeofday(&tv, NULL); t = tv.tv_sec*1000000 + tv.tv_usec; } while (t < ti + usecs); } time_t getsecs(void) { struct host_timeval tv; host_gettimeofday(&tv, NULL); return (tv.tv_sec); } time_t time(time_t *tloc) { time_t rv; rv = getsecs(); if (tloc != NULL) *tloc = rv; return (rv); } struct host_kexec_segment loaded_segments[HOST_KEXEC_SEGMENT_MAX]; int nkexec_segments = 0; static ssize_t get_phys_buffer(vm_offset_t dest, const size_t len, void **buf) { int i = 0; const size_t segsize = 8*1024*1024; if (nkexec_segments == HOST_KEXEC_SEGMENT_MAX) panic("Tried to load too many kexec segments"); for (i = 0; i < nkexec_segments; i++) { if (dest >= (vm_offset_t)loaded_segments[i].mem && dest < (vm_offset_t)loaded_segments[i].mem + loaded_segments[i].memsz) goto out; } loaded_segments[nkexec_segments].buf = host_getmem(segsize); loaded_segments[nkexec_segments].bufsz = segsize; loaded_segments[nkexec_segments].mem = (void *)rounddown2(dest,segsize); loaded_segments[nkexec_segments].memsz = segsize; i = nkexec_segments; nkexec_segments++; out: *buf = loaded_segments[i].buf + (dest - (vm_offset_t)loaded_segments[i].mem); return (min(len,loaded_segments[i].bufsz - (dest - (vm_offset_t)loaded_segments[i].mem))); } ssize_t kboot_copyin(const void *src, vm_offset_t dest, const size_t len) { ssize_t segsize, remainder; void *destbuf; remainder = len; do { segsize = get_phys_buffer(dest, remainder, &destbuf); bcopy(src, destbuf, segsize); remainder -= segsize; src += segsize; dest += segsize; } while (remainder > 0); return (len); } ssize_t kboot_copyout(vm_offset_t src, void *dest, const size_t len) { ssize_t segsize, remainder; void *srcbuf; remainder = len; do { segsize = get_phys_buffer(src, remainder, &srcbuf); bcopy(srcbuf, dest, segsize); remainder -= segsize; src += segsize; dest += segsize; } while (remainder > 0); return (len); } ssize_t kboot_readin(readin_handle_t fd, vm_offset_t dest, const size_t len) { void *buf; size_t resid, chunk, get; ssize_t got; vm_offset_t p; p = dest; chunk = min(PAGE_SIZE, len); buf = malloc(chunk); if (buf == NULL) { printf("kboot_readin: buf malloc failed\n"); return (0); } for (resid = len; resid > 0; resid -= got, p += got) { get = min(chunk, resid); got = VECTX_READ(fd, buf, get); if (got <= 0) { if (got < 0) printf("kboot_readin: read failed\n"); break; } kboot_copyin(buf, p, got); } free (buf); return (len - resid); } int kboot_autoload(void) { return (0); } uint64_t kboot_loadaddr(u_int type, void *data, uint64_t addr) { if (type == LOAD_ELF) addr = roundup(addr, PAGE_SIZE); else addr += kboot_get_phys_load_segment(); return (addr); } static void kboot_kseg_get(int *nseg, void **ptr) { #if 0 int a; for (a = 0; a < nkexec_segments; a++) { printf("kseg_get: %jx %jx %jx %jx\n", (uintmax_t)loaded_segments[a].buf, (uintmax_t)loaded_segments[a].bufsz, (uintmax_t)loaded_segments[a].mem, (uintmax_t)loaded_segments[a].memsz); } #endif *nseg = nkexec_segments; *ptr = &loaded_segments[0]; } /* * Since proper fdt command handling function is defined in fdt_loader_cmd.c, * and declaring it as extern is in contradiction with COMMAND_SET() macro * (which uses static pointer), we're defining wrapper function, which * calls the proper fdt handling routine. */ static int command_fdt(int argc, char *argv[]) { return (command_fdt_internal(argc, argv)); } COMMAND_SET(fdt, "fdt", "flattened device tree handling", command_fdt);