Date: Fri, 26 Jun 2026 14:59:23 +0000 From: Warner Losh <imp@FreeBSD.org> To: src-committers@FreeBSD.org, dev-commits-src-all@FreeBSD.org, dev-commits-src-main@FreeBSD.org Subject: git: afee781523e4 - main - loader.efi: Recognize new memdisk=<url> and memcd=<url> options Message-ID: <6a3e93cb.1f9c6.15fdc385@gitrepo.freebsd.org>
index | next in thread | raw e-mail
The branch main has been updated by imp: URL: https://cgit.FreeBSD.org/src/commit/?id=afee781523e45198c7be0a19281bcae2c4ab66db commit afee781523e45198c7be0a19281bcae2c4ab66db Author: Warner Losh <imp@FreeBSD.org> AuthorDate: 2026-06-26 14:47:36 +0000 Commit: Warner Losh <imp@FreeBSD.org> CommitDate: 2026-06-26 14:54:37 +0000 loader.efi: Recognize new memdisk=<url> and memcd=<url> options Support ipxe downloading of a memory disk (either presented to the OS as a harddisk or a cd). This requires an ipxe server since it uses the ipxe download protocol to grab the disk. If there is a disk, we add it to the environment as a disk, and then the rest of the bootloader just sees it and boots from it. I've cribbed code from https://github.com/russor/memdisk_uefi and adapted it to work in the context of the FreeBSD bootloader. The ipxe_download.h file was created from the documentation of the interface. So a .ipxe file with the line chain http://10.2.0.1/loader.efi memdisk=${cwduri}FreeBSD-15.1-RELEASE-amd64-bootonly.iso would use the FreeBSD boot loader to boot the FreeBSD 15.1 release. md(9) has a bug at the moment that prevents it from probing the partitions on it. Also, we'll automatically decompress gzip, bzip2 and zstd files automatically. Sponsored by: Netflix Differential Revision: https://reviews.freebsd.org/D57677 --- stand/efi/include/ipxe_download.h | 33 +++ stand/efi/libefi/efipart.c | 8 +- stand/efi/loader/Makefile | 4 + stand/efi/loader/decompress.c | 421 ++++++++++++++++++++++++++++++++++++++ stand/efi/loader/decompress.h | 19 ++ stand/efi/loader/loader_efi.h | 2 + stand/efi/loader/main.c | 13 ++ stand/efi/loader/memdisk.c | 203 ++++++++++++++++++ 8 files changed, 700 insertions(+), 3 deletions(-) diff --git a/stand/efi/include/ipxe_download.h b/stand/efi/include/ipxe_download.h new file mode 100644 index 000000000000..dcf616da7745 --- /dev/null +++ b/stand/efi/include/ipxe_download.h @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2026 Netfilix, Inc. Written by Warner Losh + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +/* + * IPXE protocol to download files + * + * Written from https://dox.ipxe.org/efi__download_8h.html using the names + * contained there for compatibility. See that for the full docs. Provides the + * same interface as <ipxe/efi/efi_download.h> from the ipxe distribution. + */ +#pragma once + +typedef struct _IPXE_DOWNLOAD_PROTOCOL IPXE_DOWNLOAD_PROTOCOL; +typedef void *IPXE_DOWNLOAD_FILE; +typedef EFI_STATUS(EFIAPI *IPXE_DOWNLOAD_DATA_CALLBACK)(IN VOID *, IN VOID *, IN UINTN, IN UINTN); +typedef void(EFIAPI *IPXE_DOWNLOAD_FINISH_CALLBACK)(IN VOID *, IN EFI_STATUS); +typedef EFI_STATUS(EFIAPI *IPXE_DOWNLOAD_START)(IN IPXE_DOWNLOAD_PROTOCOL *, + IN CHAR8 *, IN IPXE_DOWNLOAD_DATA_CALLBACK, IN IPXE_DOWNLOAD_FINISH_CALLBACK, + IN VOID *, OUT IPXE_DOWNLOAD_FILE *); +typedef EFI_STATUS(EFIAPI *IPXE_DOWNLOAD_ABORT)(IN IPXE_DOWNLOAD_PROTOCOL *, + IN IPXE_DOWNLOAD_FILE, IN EFI_STATUS); +typedef EFI_STATUS(EFIAPI *IPXE_DOWNLOAD_POLL) (IN IPXE_DOWNLOAD_PROTOCOL *); + +struct _IPXE_DOWNLOAD_PROTOCOL { + IPXE_DOWNLOAD_START Start; + IPXE_DOWNLOAD_ABORT Abort; + IPXE_DOWNLOAD_POLL Poll; +}; +#define IPXE_DOWNLOAD_PROTOCOL_GUID \ + { 0x3eaeaebd, 0xdecf, 0x493b, { 0x9b, 0xd1, 0xcd, 0xb2, 0xde, 0xca, 0xe7, 0x19 } } diff --git a/stand/efi/libefi/efipart.c b/stand/efi/libefi/efipart.c index e02993064d26..b4842e1b52aa 100644 --- a/stand/efi/libefi/efipart.c +++ b/stand/efi/libefi/efipart.c @@ -1255,11 +1255,11 @@ error: * Predicate: is this a VirtualDisk as far as UEFI is concerned? */ static bool -testmd(pdinfo_t *hd, pdinfo_t *data __unused) +testmd(pdinfo_t *md) { EFI_DEVICE_PATH *node; - node = efi_devpath_last_node(hd->pd_devpath); + node = efi_devpath_last_node(md->pd_devpath); if (node == NULL) return (false); if (DevicePathType(node) == MEDIA_DEVICE_PATH && @@ -1281,7 +1281,9 @@ efiblk_memdisk_preload(void) char key[32], value[32]; int i; - while ((md = efipart_get_pd(&pdinfo, testmd, NULL)) != NULL) { + STAILQ_FOREACH(md, &pdinfo, pd_link) { + if (!testmd(md)) + continue; ram = (MEDIA_RAM_DISK_DEVICE_PATH *)efi_devpath_last_node(md->pd_devpath); i = ram->Instance; start = ((uint64_t)ram->StartingAddr[1] << 32) | ram->StartingAddr[0]; diff --git a/stand/efi/loader/Makefile b/stand/efi/loader/Makefile index 44d7347ee7dd..164b16ff2a59 100644 --- a/stand/efi/loader/Makefile +++ b/stand/efi/loader/Makefile @@ -22,9 +22,11 @@ SRCS= autoload.c \ bootinfo.c \ conf.c \ copy.c \ + decompress.c \ efi_main.c \ framebuffer.c \ main.c \ + memdisk.c \ self_reloc.c \ vers.c \ gfx_fb.c \ @@ -50,6 +52,7 @@ CFLAGS.framebuffer.c += -I$(SRCTOP)/sys/teken CFLAGS.framebuffer.c += -I${SRCTOP}/contrib/pnglite CFLAGS.main.c += -I$(SRCTOP)/sys/teken CFLAGS.main.c += -I${SRCTOP}/contrib/pnglite +CFLAGS.memdisk.c += -I${.OBJDIR} CFLAGS.gfx_fb.c += -I$(SRCTOP)/sys/teken CFLAGS.gfx_fb.c += -I${SRCTOP}/sys/cddl/contrib/opensolaris/common/lz4 CFLAGS.gfx_fb.c += -I${SRCTOP}/contrib/pnglite @@ -61,6 +64,7 @@ CFLAGS.gfx_fb.c += ${ZLIB_CFLAGS} # of a short. There's no good cast to use here so just ignore the # warnings for now. CWARNFLAGS.main.c+= -Wno-format +CWARNFLAGS.memdisk.c+= -Wno-format .PATH: ${.CURDIR}/../loader .PATH: ${.CURDIR}/../loader/arch/${__arch} diff --git a/stand/efi/loader/decompress.c b/stand/efi/loader/decompress.c new file mode 100644 index 000000000000..1f44d1000f11 --- /dev/null +++ b/stand/efi/loader/decompress.c @@ -0,0 +1,421 @@ +/* + * Copyright (c) 2026 Netflix, Inc. Written by Warner Losh + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include "stand.h" +#include "loader_efi.h" +#include <efilib.h> +#include "decompress.h" + +#include <zlib.h> +#include <bzlib.h> +#ifdef LOADER_ZFS_SUPPORT /* ZSTD only available with ZFS */ +#include <zstd.h> +#endif +#include <sys/_param.h> + +#define ULL(x) ((unsigned long long)(x)) + +static EFI_MEMORY_TYPE mem_type = EfiReservedMemoryType; + +struct decomp_state +{ + enum compression type; + size_t size; /* Best guess at final size */ + size_t alloc_size; /* Current size of `buf` */ + size_t pages; /* Alloc_size in pages */ + uint8_t *buf_cur; /* Current output buffer */ + uint8_t *buf_end; /* Current end of allocated buffer */ + EFI_PHYSICAL_ADDRESS buf; /* Decompression buffer */ + union { + z_stream zstrm; + bz_stream bzstrm; +#ifdef LOADER_ZFS_SUPPORT + ZSTD_DStream *zstdstrm; +#endif + }; + EFI_STATUS (*init)(decomp_state *dctx, uint8_t *first_buf, size_t buflen, + size_t size_hint); + enum step_return (*step)(decomp_state *dctx, uint8_t *buf, size_t len, size_t offset); + void (*fini)(decomp_state *dctx, bool flush); +}; + +static enum compression +what_compressed(uint8_t *buf, size_t len) +{ + /* + * Failsafe + */ + if (len < 32) + return (none); + if (memcmp(buf, "\x1f\x8b", 2) == 0) { + printf("GZIP\n"); + return (zlib); + } + if (memcmp(buf, "BZh", 3) == 0) { + printf("BZIP2\n"); + return (bzip2); + } + if (memcmp(buf, "\x28\xb5\x2f\xfd", 4) == 0) { + printf("zstd\n"); + return (zstd); + } + if (memcmp(buf, "\xfd""7zXZ\x00", 6) == 0) { + printf("xz -- unsupproted\n"); + } else { + printf("Not compressed\n"); + } + return (none); +} + +static EFI_STATUS +alloc_buffer(decomp_state *dctx, size_t size) +{ + dctx->alloc_size = roundup2(size, EFI_PAGE_SIZE); + dctx->pages = dctx->alloc_size / EFI_PAGE_SIZE; + EFI_STATUS status = BS->AllocatePages(AllocateAnyPages, mem_type, dctx->pages, &dctx->buf); + if (EFI_ERROR(status)) { + printf("Failed to allocate memory for %llu bytes\n", ULL(dctx->alloc_size)); + return (status); + } + BS->SetMem((void *)dctx->buf, dctx->alloc_size, 0); + dctx->buf_cur = (uint8_t *)dctx->buf; + dctx->buf_end = (uint8_t *)dctx->buf + dctx->alloc_size; + return (EFI_SUCCESS); +} + +static EFI_STATUS +grow_buffer(decomp_state *dctx) +{ + /* + * a 1.5 exp growth trades a few more copies for a little less waste. + */ + size_t newsz = roundup2(dctx->alloc_size * 3 / 2, EFI_PAGE_SIZE); + size_t newpages = newsz / EFI_PAGE_SIZE; + EFI_PHYSICAL_ADDRESS newbuf; + EFI_STATUS status = BS->AllocatePages(AllocateAnyPages, mem_type, newpages, &newbuf); + if (EFI_ERROR(status)) { + printf("Failed to allocate memory for %llu bytes\n", ULL(newsz)); + return (status); + } + memcpy((void *)newbuf, (void *)dctx->buf, dctx->alloc_size); + BS->FreePages(dctx->buf, dctx->pages); + dctx->buf = newbuf; + dctx->pages = newpages; + dctx->buf_cur = (uint8_t *)dctx->buf + dctx->alloc_size; + dctx->buf_end = (uint8_t *)dctx->buf + newsz; + BS->SetMem(dctx->buf_cur, newsz - dctx->alloc_size, 0); + dctx->alloc_size = newsz; + return (EFI_SUCCESS); +} + +static void +free_buffer(decomp_state *dctx) +{ + if (dctx->buf) + BS->FreePages(dctx->buf, dctx->pages); + dctx->buf = 0; +} + +/* + * zlib supprot + */ +static EFI_STATUS +zlib_init(decomp_state *dctx, uint8_t *first_buf, size_t buflen, size_t size_hint) +{ + z_stream *strm = &dctx->zstrm; + + /* + * Assume 4x compression, but start at 64MB + */ + dctx->size = max(size_hint * 4, M(64)); + EFI_STATUS status = alloc_buffer(dctx, dctx->size); + if (EFI_ERROR(status)) + return (status); + memset(strm, 0, sizeof(*strm)); + strm->next_in = first_buf; + strm->avail_in = buflen; + return (inflateInit2(strm, 15 + 16) == Z_OK ? EFI_SUCCESS : EFI_VOLUME_CORRUPTED); +} + +static enum step_return +zlib_step(decomp_state *dctx, uint8_t *buf, size_t len, size_t offset) +{ + z_stream *strm = &dctx->zstrm; + size_t outlen = dctx->buf_end - dctx->buf_cur; + + strm->next_in = buf; + strm->avail_in = len; + strm->next_out = dctx->buf_cur; + strm->avail_out = outlen; + + int ret = inflate(strm, Z_NO_FLUSH); + dctx->buf_cur += outlen - strm->avail_out; + + if (ret == Z_STREAM_END) + return (done); + if (ret != Z_OK) + return (err); + if (dctx->buf_cur < dctx->buf_end) /* Have output space */ + return (ok); + + /* + * We're out of space, grow the buffer and try again if there's buffer + * space. We try again recursively since we know that will usually go + * only 1 deep. + */ + if (EFI_ERROR(grow_buffer(dctx))) + return (err); + if (strm->avail_in == 0) + return (ok); + size_t consumed = len - strm->avail_in; + return (zlib_step(dctx, buf + consumed, strm->avail_in, offset + consumed)); +} + +static void +zlib_fini(decomp_state *dctx, bool flush) +{ + inflateEnd(&dctx->zstrm); + if (!flush) + return; + free_buffer(dctx); +} + +/* + * Bzip2 supprot + */ +static EFI_STATUS +bzip2_init(decomp_state *dctx, uint8_t *first_buf, size_t buflen, size_t size_hint) +{ + bz_stream *strm = &dctx->bzstrm; + + /* + * Assume 4x compression, but start at 64MB + */ + dctx->size = max(size_hint * 4, M(64)); + EFI_STATUS status = alloc_buffer(dctx, dctx->size); + if (EFI_ERROR(status)) + return (status); + memset(strm, 0, sizeof(*strm)); + strm->next_in = first_buf; + strm->avail_in = buflen; + return (BZ2_bzDecompressInit(strm, 0, 0) == BZ_OK ? EFI_SUCCESS : EFI_VOLUME_CORRUPTED); +} + +static enum step_return +bzip2_step(decomp_state *dctx, uint8_t *buf, size_t len, size_t offset) +{ + bz_stream *strm = &dctx->bzstrm; + size_t outlen = dctx->buf_end - dctx->buf_cur; + + strm->next_in = buf; + strm->avail_in = len; + strm->next_out = dctx->buf_cur; + strm->avail_out = outlen; + + int ret = BZ2_bzDecompress(strm); + dctx->buf_cur += outlen - strm->avail_out; + + if (ret == BZ_STREAM_END) + return (done); + if (ret != Z_OK) + return (err); + if (dctx->buf_cur < dctx->buf_end) /* Have output space */ + return (ok); + + /* + * We're out of space, grow the buffer and try again if there's buffer + * space. We try again recursively since we know that will usually go + * only 1 deep. + */ + if (EFI_ERROR(grow_buffer(dctx))) + return (err); + if (strm->avail_in == 0) + return (ok); + size_t consumed = len - strm->avail_in; + return (bzip2_step(dctx, buf + consumed, strm->avail_in, offset + consumed)); +} + +static void +bzip2_fini(decomp_state *dctx, bool flush) +{ + BZ2_bzDecompressEnd(&dctx->bzstrm); + if (!flush) + return; + free_buffer(dctx); +} + +/* + * ZSTD supprot + */ +#ifdef LOADER_ZFS_SUPPORT +static EFI_STATUS +zstd_init(decomp_state *dctx, uint8_t *first_buf, size_t buflen, size_t size_hint) +{ + unsigned long long size = ZSTD_getFrameContentSize(first_buf, buflen); + if (size == ZSTD_CONTENTSIZE_ERROR) + return (EFI_VOLUME_CORRUPTED); + if (size == ZSTD_CONTENTSIZE_UNKNOWN) + dctx->size = max(size_hint * 4, M(64)); /* Guess 4x compression or 64M */ + else + dctx->size = size; /* We know the size */ + EFI_STATUS status = alloc_buffer(dctx, dctx->size); + if (EFI_ERROR(status)) + return (status); + + dctx->zstdstrm = ZSTD_createDStream(); + if (dctx->zstdstrm == NULL) + return (EFI_OUT_OF_RESOURCES); + if (ZSTD_isError(ZSTD_initDStream(dctx->zstdstrm))) { + ZSTD_freeDStream(dctx->zstdstrm); + dctx->zstdstrm = NULL; + return (EFI_OUT_OF_RESOURCES); + } + return (EFI_SUCCESS); +} + +static enum step_return +zstd_step(decomp_state *dctx, uint8_t *buf, size_t len, size_t offset) +{ + size_t outlen = dctx->buf_end - dctx->buf_cur; + ZSTD_inBuffer inbuf = { buf, len, 0 }; + ZSTD_outBuffer outbuf = { dctx->buf_cur, outlen, 0 }; + size_t ret; + + ret = ZSTD_decompressStream(dctx->zstdstrm, &outbuf, &inbuf); + dctx->buf_cur += outbuf.pos; + + if (ZSTD_isError(ret)) + return (err); + if (ret == 0) + return (done); + if (dctx->buf_cur < dctx->buf_end) /* Have output space */ + return (ok); + + /* + * We're out of space, grow the buffer and try again if there's buffer + * space. We try again recursively since we know that will usually go + * only 1 deep. + */ + if (EFI_ERROR(grow_buffer(dctx))) + return (err); + if (inbuf.size == inbuf.pos) + return (ok); + return (zstd_step(dctx, buf + inbuf.pos, inbuf.size - inbuf.pos, offset + inbuf.pos)); +} + +static void +zstd_fini(decomp_state *dctx, bool flush) +{ + ZSTD_freeDStream(dctx->zstdstrm); + if (!flush) + return; + free_buffer(dctx); +} +#endif + +/* + * No / Unknown decompression fallback + */ +static EFI_STATUS +null_init(decomp_state *dctx, uint8_t *first_buf, size_t buflen, size_t size_hint) +{ + dctx->size = size_hint; + return (alloc_buffer(dctx, size_hint)); +} + +static enum step_return +null_step(decomp_state *dctx, uint8_t *buf, size_t len, size_t offset) +{ + size_t end = offset + len; + + if (end > dctx->size) { + printf("Too much data recieved!"); + return (err); + } + + CHAR8 *src = buf; + CHAR8 *dst = (void*)(dctx->buf + offset); + BS->CopyMem(dst, src, len); + return (end == dctx->size ? done : ok); +} + +static void +null_fini(decomp_state *dctx, bool flush) +{ + if (!flush) + return; + free_buffer(dctx); +} + +decomp_state * +decomp_init(uint8_t *buf, size_t buflen, size_t size_hint) +{ + decomp_state *dctx; + + dctx = malloc(sizeof(*dctx)); + memset(dctx, 0, sizeof(*dctx)); + dctx->type = what_compressed(buf, buflen); + switch (dctx->type) { + case zlib: + dctx->init = zlib_init; + dctx->step = zlib_step; + dctx->fini = zlib_fini; + break; + case bzip2: + dctx->init = bzip2_init; + dctx->step = bzip2_step; + dctx->fini = bzip2_fini; + break; +#ifdef LOADER_ZFS_SUPPORT + case zstd: + dctx->init = zstd_init; + dctx->step = zstd_step; + dctx->fini = zstd_fini; + break; +#endif + case none: + dctx->init = null_init; + dctx->step = null_step; + dctx->fini = null_fini; + break; + default: + return (NULL); + } + + if (EFI_ERROR(dctx->init(dctx, buf, buflen, size_hint))) { + free(dctx); + dctx = NULL; + } + return (dctx); +} + +enum step_return +decomp_step(decomp_state *dctx, uint8_t *buf, size_t len, size_t offset) +{ + return (dctx->step(dctx, buf, len, offset)); +} + +void +decomp_fini(decomp_state *dctx, bool flush) +{ + return (dctx->fini(dctx, flush)); +} + +EFI_PHYSICAL_ADDRESS +decomp_buffer(decomp_state *dctx) +{ + if (dctx == NULL) + return (0); + return (dctx->buf); +} + +size_t +decomp_buffer_length(decomp_state *dctx) +{ + if (dctx == NULL) + return (0); + return ((void *)dctx->buf_cur - (void *)dctx->buf); +} diff --git a/stand/efi/loader/decompress.h b/stand/efi/loader/decompress.h new file mode 100644 index 000000000000..43c48078f822 --- /dev/null +++ b/stand/efi/loader/decompress.h @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2026 Netflix, Inc. Written by Warner Losh + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +enum compression { none, zlib, bzip2, zstd }; +enum step_return { ok, done, err }; + +typedef struct decomp_state decomp_state; + +decomp_state *decomp_init(uint8_t *buf, size_t buflen, size_t size_hint); +enum step_return decomp_step(decomp_state *dctx, uint8_t *buf, size_t len, size_t offset); +void decomp_fini(decomp_state *dctx, bool flush); +EFI_PHYSICAL_ADDRESS decomp_buffer(decomp_state *dctx); +size_t decomp_buffer_length(decomp_state *dctx); + diff --git a/stand/efi/loader/loader_efi.h b/stand/efi/loader/loader_efi.h index be991eeb9573..c53edb639ad5 100644 --- a/stand/efi/loader/loader_efi.h +++ b/stand/efi/loader/loader_efi.h @@ -59,6 +59,8 @@ void * efi_translate(vm_offset_t ptr); void efi_copy_finish(void); void efi_copy_finish_nop(void); +void maybe_download_ramdisk(int argc, CHAR16 **argv); + #if defined(__amd64__) || defined(__i386__) /* Need this to setup page tables */ extern EFI_PHYSICAL_ADDRESS staging; diff --git a/stand/efi/loader/main.c b/stand/efi/loader/main.c index 050e802181b1..cc7a9c4ac814 100644 --- a/stand/efi/loader/main.c +++ b/stand/efi/loader/main.c @@ -1319,6 +1319,14 @@ main(int argc, CHAR16 *argv[]) */ bcache_init(32768, 512); + /* + * Scan the command line args for memdisk=<url> and download that image + * to install as a ramdisk. This needs to be done before we scan the + * handles because it installs a handle and creates the right ACPI + * tables for the kernel to find it. + */ + maybe_download_ramdisk(argc, argv); + /* * Scan the BLOCK IO MEDIA handles then * march through the device switch probing for things. @@ -1329,6 +1337,11 @@ main(int argc, CHAR16 *argv[]) "failures\n", i); } + /* + * Scan all the VirtualDisks, passing them along to the FreeBSD kernel. + */ + efiblk_memdisk_preload(); + devinit(); /* diff --git a/stand/efi/loader/memdisk.c b/stand/efi/loader/memdisk.c new file mode 100644 index 000000000000..8d34d3ceae42 --- /dev/null +++ b/stand/efi/loader/memdisk.c @@ -0,0 +1,203 @@ +/* + * Copyright (c) 2026 Netflix, Inc. Written by Warner Losh + * + * SPDX-License-Identifier: BSD-2-Clause + * + * Derived from memdisk_uefi.c + * Copyright 2025 Richard Russo + * SPDX-License-Identifier: BSD-2-Clause-Patent + */ + +#include "loader_efi.h" +#include <efilib.h> +#include <Protocol/RamDisk.h> +#include "decompress.h" +#include <ipxe_download.h> +#include <sys/_param.h> + +#define ULL(x) ((unsigned long long)(x)) + +static EFI_GUID ipxeGuid = IPXE_DOWNLOAD_PROTOCOL_GUID; +static EFI_GUID ramdiskGuid = EFI_RAM_DISK_PROTOCOL_GUID; +static EFI_GUID virtual_disk_guid = EFI_VIRTUAL_DISK_GUID; +static EFI_GUID virtual_cd_guid = EFI_VIRTUAL_CD_GUID; + +static IPXE_DOWNLOAD_PROTOCOL *ipxe_download; +static EFI_RAM_DISK_PROTOCOL *ram_disk; + +struct dl_state; +typedef struct dl_state dl_state; + +static struct dl_state +{ + bool in_progress; + size_t size; + EFI_STATUS status; + decomp_state *dctx; +} dl; + +static void +download_cleanup(dl_state *ctx) +{ + if (ctx->dctx) + decomp_fini(ctx->dctx, true); + ctx->in_progress = false; +} + +static EFI_STATUS EFIAPI +download_data(IN VOID *Context, IN VOID *Buffer, IN UINTN BufferLength, IN UINTN FileOffset) +{ + dl_state *ctx = Context; + decomp_state *dctx = ctx->dctx; + + /* + * Make a note of the size when we're hinted about it. But once + * we start the download, ignore the hints. + */ + if (ctx->size == 0 && BufferLength == 0) { + printf("We know we will download %llu bytes\n", ULL(FileOffset)); + ctx->size = FileOffset; + ctx->status = EFI_SUCCESS; + return (EFI_SUCCESS); + } + + /* + * Peek into the first chunk to see the format of the data. + */ + if (FileOffset == 0) { + dctx = decomp_init((uint8_t *)Buffer, (size_t)BufferLength, ctx->size); + if (dctx == NULL) { + ctx->in_progress = false; + ctx->status = EFI_VOLUME_CORRUPTED; + return (ctx->status); + } + ctx->dctx = dctx; + } + + enum step_return sr = decomp_step(dctx, Buffer, BufferLength, FileOffset); + if (sr == err) { + printf("Error on download\n"); + decomp_fini(dctx, true); + return (EFI_VOLUME_CORRUPTED); + } + + unsigned long long sofar = FileOffset + BufferLength; +#define MB 1000000 + if (sofar / MB != FileOffset / MB) { + if (ctx->size) + printf("%dMB / %dMB (%d%%)\r", + (int)(sofar / MB), + (int)(ctx->size / MB), + (int)(100 * sofar / ctx->size)); + else + printf("%dMB\r", (int)(sofar / MB)); + } + return (EFI_SUCCESS); +} + +static void EFIAPI +download_finish(IN VOID *Context, IN EFI_STATUS Status) +{ + dl_state *ctx = Context; + + ctx->in_progress = false; + ctx->status = Status; + if (ctx->dctx) + decomp_fini(ctx->dctx, EFI_ERROR(Status)); +} + +static void +do_download_ramdisk(CHAR8 *url, bool is_disk) +{ + EFI_STATUS Status; + EFI_GUID disk_type = is_disk ? virtual_disk_guid : virtual_cd_guid; + EFI_DEVICE_PATH_PROTOCOL *ram_disk_path; + IPXE_DOWNLOAD_FILE token; + dl_state *ctx = &dl; + + Status = BS->LocateProtocol(&ipxeGuid, NULL, (void**)&ipxe_download); + if (EFI_ERROR(Status)) + return; /* most uses won't have this, don't whine */ + Status = BS->LocateProtocol(&ramdiskGuid, NULL, (void**)&ram_disk); + if (EFI_ERROR(Status)) + return; /* XXX whine about it? */ + + printf("Downloading %s as a %s\n", url, is_disk ? "disk" : "cd"); + ctx->in_progress = true; + Status = ipxe_download->Start(ipxe_download, url, download_data, download_finish, + &dl, &token); + if (EFI_ERROR(Status)) { + printf("Couldn't start download %u\n", (unsigned)Status); + download_cleanup(ctx); + return; + } + while (ctx->in_progress) { + ipxe_download->Poll(ipxe_download); + } + if (EFI_ERROR(ctx->status)) { + printf("Download had error %u\n", (unsigned)ctx->status); + download_cleanup(ctx); + return; + } + if (ctx->size == 0) { + printf("Nothing downloaded\n"); + download_cleanup(ctx); + return; + } + + printf("\nDownloaded %llu bytes, actual size %llu -- registering ramdisk\n", + ULL(ctx->size), ULL(decomp_buffer_length(ctx->dctx))); + + /* + * Register the RamDisk with UEFI. This registers it so the rest of the + * boot loader can see it as a block device. + */ + Status = ram_disk->Register(decomp_buffer(ctx->dctx), decomp_buffer_length(ctx->dctx), + &disk_type, NULL, &ram_disk_path); + if (EFI_ERROR(Status)) { + printf("failed to register ram disk %u\n", (unsigned)Status); + download_cleanup(ctx); + return; + } + + CHAR16 *text = efi_devpath_name(ram_disk_path); + if (text != NULL) { + CHAR8 uefi_path[1024]; + printf("Installed RAM disk as %S\n", text); + + cpy16to8(text, uefi_path, sizeof(uefi_path)); + setenv("uefi_ignore_boot_mgr", "true", 1); + setenv("uefi_rootdev", uefi_path, 1); + efi_free_devpath_name(text); + } else { + printf("Installed RAM disk to unknown device type\n"); + } +} + +/* + * Scan the command line for memdisk=url or memcd=url. Do nothing if that's not + * present, otherwise try to download that image. + * + * Open Question: Do we want some way to chain boot into the /boot/loader.efi or + * \efi\boot\bootXXXXX.efi inside the ram disk we load? If so, how do we keep + * from infinite chainbooting? Also, I don't understand the load it but don't save + * it option... + */ +void +maybe_download_ramdisk(int argc, CHAR16 **argv) +{ + char var[256]; + + for (int i = 0; i < argc; i++) { + cpy16to8(argv[i], var, sizeof(var)); + if (strncmp(var, "memdisk=", 8) == 0) { + do_download_ramdisk(var + 8, true); + return; + } + if (strncmp(var, "memcd=", 6) == 0) { + do_download_ramdisk(var + 6, false); + return; + } + } + return; +}home | help
Want to link to this message? Use this
URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?6a3e93cb.1f9c6.15fdc385>
