mirror of
https://github.com/freebsd/freebsd-src.git
synced 2026-10-28 19:34:43 +01:00
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
This commit is contained in:
1 parent
59219fc76a
commit
afee781523
8 files changed
+700
-3
No files matched your search
@@ -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 } }
|
||||
@@ -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];
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
/*
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
Reference in new issue
Block a user