xref: /freebsd/stand/efi/loader/memdisk.c (revision afee781523e45198c7be0a19281bcae2c4ab66db)
1*afee7815SWarner Losh /*
2*afee7815SWarner Losh  * Copyright (c) 2026 Netflix, Inc. Written by Warner Losh
3*afee7815SWarner Losh  *
4*afee7815SWarner Losh  * SPDX-License-Identifier: BSD-2-Clause
5*afee7815SWarner Losh  *
6*afee7815SWarner Losh  * Derived from memdisk_uefi.c
7*afee7815SWarner Losh  * Copyright 2025 Richard Russo
8*afee7815SWarner Losh  * SPDX-License-Identifier: BSD-2-Clause-Patent
9*afee7815SWarner Losh  */
10*afee7815SWarner Losh 
11*afee7815SWarner Losh #include "loader_efi.h"
12*afee7815SWarner Losh #include <efilib.h>
13*afee7815SWarner Losh #include <Protocol/RamDisk.h>
14*afee7815SWarner Losh #include "decompress.h"
15*afee7815SWarner Losh #include <ipxe_download.h>
16*afee7815SWarner Losh #include <sys/_param.h>
17*afee7815SWarner Losh 
18*afee7815SWarner Losh #define ULL(x) ((unsigned long long)(x))
19*afee7815SWarner Losh 
20*afee7815SWarner Losh static EFI_GUID ipxeGuid = IPXE_DOWNLOAD_PROTOCOL_GUID;
21*afee7815SWarner Losh static EFI_GUID ramdiskGuid = EFI_RAM_DISK_PROTOCOL_GUID;
22*afee7815SWarner Losh static EFI_GUID virtual_disk_guid = EFI_VIRTUAL_DISK_GUID;
23*afee7815SWarner Losh static EFI_GUID virtual_cd_guid = EFI_VIRTUAL_CD_GUID;
24*afee7815SWarner Losh 
25*afee7815SWarner Losh static IPXE_DOWNLOAD_PROTOCOL *ipxe_download;
26*afee7815SWarner Losh static EFI_RAM_DISK_PROTOCOL *ram_disk;
27*afee7815SWarner Losh 
28*afee7815SWarner Losh struct dl_state;
29*afee7815SWarner Losh typedef struct dl_state dl_state;
30*afee7815SWarner Losh 
31*afee7815SWarner Losh static struct dl_state
32*afee7815SWarner Losh {
33*afee7815SWarner Losh 	bool in_progress;
34*afee7815SWarner Losh 	size_t size;
35*afee7815SWarner Losh 	EFI_STATUS status;
36*afee7815SWarner Losh 	decomp_state *dctx;
37*afee7815SWarner Losh } dl;
38*afee7815SWarner Losh 
39*afee7815SWarner Losh static void
download_cleanup(dl_state * ctx)40*afee7815SWarner Losh download_cleanup(dl_state *ctx)
41*afee7815SWarner Losh {
42*afee7815SWarner Losh 	if (ctx->dctx)
43*afee7815SWarner Losh 		decomp_fini(ctx->dctx, true);
44*afee7815SWarner Losh 	ctx->in_progress = false;
45*afee7815SWarner Losh }
46*afee7815SWarner Losh 
47*afee7815SWarner Losh static EFI_STATUS EFIAPI
download_data(IN VOID * Context,IN VOID * Buffer,IN UINTN BufferLength,IN UINTN FileOffset)48*afee7815SWarner Losh download_data(IN VOID *Context, IN VOID *Buffer, IN UINTN BufferLength, IN UINTN FileOffset)
49*afee7815SWarner Losh {
50*afee7815SWarner Losh 	dl_state *ctx = Context;
51*afee7815SWarner Losh 	decomp_state *dctx = ctx->dctx;
52*afee7815SWarner Losh 
53*afee7815SWarner Losh 	/*
54*afee7815SWarner Losh 	 * Make a note of the size when we're hinted about it. But once
55*afee7815SWarner Losh 	 * we start the download, ignore the hints.
56*afee7815SWarner Losh 	 */
57*afee7815SWarner Losh 	if (ctx->size == 0 && BufferLength == 0) {
58*afee7815SWarner Losh 		printf("We know we will download %llu bytes\n", ULL(FileOffset));
59*afee7815SWarner Losh 		ctx->size = FileOffset;
60*afee7815SWarner Losh 		ctx->status = EFI_SUCCESS;
61*afee7815SWarner Losh 		return (EFI_SUCCESS);
62*afee7815SWarner Losh 	}
63*afee7815SWarner Losh 
64*afee7815SWarner Losh 	/*
65*afee7815SWarner Losh 	 * Peek into the first chunk to see the format of the data.
66*afee7815SWarner Losh 	 */
67*afee7815SWarner Losh 	if (FileOffset == 0) {
68*afee7815SWarner Losh 		dctx = decomp_init((uint8_t *)Buffer, (size_t)BufferLength, ctx->size);
69*afee7815SWarner Losh 		if (dctx == NULL) {
70*afee7815SWarner Losh 			ctx->in_progress = false;
71*afee7815SWarner Losh 			ctx->status = EFI_VOLUME_CORRUPTED;
72*afee7815SWarner Losh 			return (ctx->status);
73*afee7815SWarner Losh 		}
74*afee7815SWarner Losh 		ctx->dctx = dctx;
75*afee7815SWarner Losh 	}
76*afee7815SWarner Losh 
77*afee7815SWarner Losh 	enum step_return sr = decomp_step(dctx, Buffer, BufferLength, FileOffset);
78*afee7815SWarner Losh 	if (sr == err) {
79*afee7815SWarner Losh 		printf("Error on download\n");
80*afee7815SWarner Losh 		decomp_fini(dctx, true);
81*afee7815SWarner Losh 		return (EFI_VOLUME_CORRUPTED);
82*afee7815SWarner Losh 	}
83*afee7815SWarner Losh 
84*afee7815SWarner Losh 	unsigned long long sofar = FileOffset + BufferLength;
85*afee7815SWarner Losh #define MB  1000000
86*afee7815SWarner Losh 	if (sofar / MB != FileOffset / MB) {
87*afee7815SWarner Losh 		if (ctx->size)
88*afee7815SWarner Losh 			printf("%dMB / %dMB (%d%%)\r",
89*afee7815SWarner Losh 			    (int)(sofar / MB),
90*afee7815SWarner Losh 			    (int)(ctx->size / MB),
91*afee7815SWarner Losh 			    (int)(100 * sofar / ctx->size));
92*afee7815SWarner Losh 		else
93*afee7815SWarner Losh 			printf("%dMB\r", (int)(sofar / MB));
94*afee7815SWarner Losh 	}
95*afee7815SWarner Losh 	return (EFI_SUCCESS);
96*afee7815SWarner Losh }
97*afee7815SWarner Losh 
98*afee7815SWarner Losh static void EFIAPI
download_finish(IN VOID * Context,IN EFI_STATUS Status)99*afee7815SWarner Losh download_finish(IN VOID *Context, IN EFI_STATUS Status)
100*afee7815SWarner Losh {
101*afee7815SWarner Losh 	dl_state *ctx = Context;
102*afee7815SWarner Losh 
103*afee7815SWarner Losh 	ctx->in_progress = false;
104*afee7815SWarner Losh 	ctx->status = Status;
105*afee7815SWarner Losh 	if (ctx->dctx)
106*afee7815SWarner Losh 		decomp_fini(ctx->dctx, EFI_ERROR(Status));
107*afee7815SWarner Losh }
108*afee7815SWarner Losh 
109*afee7815SWarner Losh static void
do_download_ramdisk(CHAR8 * url,bool is_disk)110*afee7815SWarner Losh do_download_ramdisk(CHAR8 *url, bool is_disk)
111*afee7815SWarner Losh {
112*afee7815SWarner Losh 	EFI_STATUS Status;
113*afee7815SWarner Losh 	EFI_GUID disk_type = is_disk ? virtual_disk_guid : virtual_cd_guid;
114*afee7815SWarner Losh 	EFI_DEVICE_PATH_PROTOCOL *ram_disk_path;
115*afee7815SWarner Losh 	IPXE_DOWNLOAD_FILE token;
116*afee7815SWarner Losh 	dl_state *ctx = &dl;
117*afee7815SWarner Losh 
118*afee7815SWarner Losh 	Status = BS->LocateProtocol(&ipxeGuid, NULL, (void**)&ipxe_download);
119*afee7815SWarner Losh 	if (EFI_ERROR(Status))
120*afee7815SWarner Losh 		return; /* most uses won't have this, don't whine */
121*afee7815SWarner Losh 	Status = BS->LocateProtocol(&ramdiskGuid, NULL, (void**)&ram_disk);
122*afee7815SWarner Losh 	if (EFI_ERROR(Status))
123*afee7815SWarner Losh 		return; /* XXX whine about it? */
124*afee7815SWarner Losh 
125*afee7815SWarner Losh 	printf("Downloading %s as a %s\n", url, is_disk ? "disk" : "cd");
126*afee7815SWarner Losh 	ctx->in_progress = true;
127*afee7815SWarner Losh 	Status = ipxe_download->Start(ipxe_download, url, download_data, download_finish,
128*afee7815SWarner Losh 	    &dl, &token);
129*afee7815SWarner Losh 	if (EFI_ERROR(Status)) {
130*afee7815SWarner Losh 		printf("Couldn't start download %u\n", (unsigned)Status);
131*afee7815SWarner Losh 		download_cleanup(ctx);
132*afee7815SWarner Losh 		return;
133*afee7815SWarner Losh 	}
134*afee7815SWarner Losh 	while (ctx->in_progress) {
135*afee7815SWarner Losh 		ipxe_download->Poll(ipxe_download);
136*afee7815SWarner Losh 	}
137*afee7815SWarner Losh 	if (EFI_ERROR(ctx->status)) {
138*afee7815SWarner Losh 		printf("Download had error %u\n", (unsigned)ctx->status);
139*afee7815SWarner Losh 		download_cleanup(ctx);
140*afee7815SWarner Losh 		return;
141*afee7815SWarner Losh 	}
142*afee7815SWarner Losh 	if (ctx->size == 0) {
143*afee7815SWarner Losh 		printf("Nothing downloaded\n");
144*afee7815SWarner Losh 		download_cleanup(ctx);
145*afee7815SWarner Losh 		return;
146*afee7815SWarner Losh 	}
147*afee7815SWarner Losh 
148*afee7815SWarner Losh 	printf("\nDownloaded %llu bytes, actual size %llu -- registering ramdisk\n",
149*afee7815SWarner Losh 	    ULL(ctx->size), ULL(decomp_buffer_length(ctx->dctx)));
150*afee7815SWarner Losh 
151*afee7815SWarner Losh 	/*
152*afee7815SWarner Losh 	 * Register the RamDisk with UEFI. This registers it so the rest of the
153*afee7815SWarner Losh 	 * boot loader can see it as a block device.
154*afee7815SWarner Losh 	 */
155*afee7815SWarner Losh 	Status = ram_disk->Register(decomp_buffer(ctx->dctx), decomp_buffer_length(ctx->dctx),
156*afee7815SWarner Losh 	    &disk_type, NULL, &ram_disk_path);
157*afee7815SWarner Losh 	if (EFI_ERROR(Status)) {
158*afee7815SWarner Losh 		printf("failed to register ram disk %u\n", (unsigned)Status);
159*afee7815SWarner Losh 		download_cleanup(ctx);
160*afee7815SWarner Losh 		return;
161*afee7815SWarner Losh 	}
162*afee7815SWarner Losh 
163*afee7815SWarner Losh 	CHAR16 *text = efi_devpath_name(ram_disk_path);
164*afee7815SWarner Losh 	if (text != NULL) {
165*afee7815SWarner Losh 		CHAR8 uefi_path[1024];
166*afee7815SWarner Losh 		printf("Installed RAM disk as %S\n", text);
167*afee7815SWarner Losh 
168*afee7815SWarner Losh 		cpy16to8(text, uefi_path, sizeof(uefi_path));
169*afee7815SWarner Losh 		setenv("uefi_ignore_boot_mgr", "true", 1);
170*afee7815SWarner Losh 		setenv("uefi_rootdev", uefi_path, 1);
171*afee7815SWarner Losh 		efi_free_devpath_name(text);
172*afee7815SWarner Losh 	} else {
173*afee7815SWarner Losh 		printf("Installed RAM disk to unknown device type\n");
174*afee7815SWarner Losh 	}
175*afee7815SWarner Losh }
176*afee7815SWarner Losh 
177*afee7815SWarner Losh /*
178*afee7815SWarner Losh  * Scan the command line for memdisk=url or memcd=url. Do nothing if that's not
179*afee7815SWarner Losh  * present, otherwise try to download that image.
180*afee7815SWarner Losh  *
181*afee7815SWarner Losh  * Open Question: Do we want some way to chain boot into the /boot/loader.efi or
182*afee7815SWarner Losh  * \efi\boot\bootXXXXX.efi inside the ram disk we load? If so, how do we keep
183*afee7815SWarner Losh  * from infinite chainbooting? Also, I don't understand the load it but don't save
184*afee7815SWarner Losh  * it option...
185*afee7815SWarner Losh  */
186*afee7815SWarner Losh void
maybe_download_ramdisk(int argc,CHAR16 ** argv)187*afee7815SWarner Losh maybe_download_ramdisk(int argc, CHAR16 **argv)
188*afee7815SWarner Losh {
189*afee7815SWarner Losh 	char var[256];
190*afee7815SWarner Losh 
191*afee7815SWarner Losh 	for (int i = 0; i < argc; i++) {
192*afee7815SWarner Losh 		cpy16to8(argv[i], var, sizeof(var));
193*afee7815SWarner Losh 		if (strncmp(var, "memdisk=", 8) == 0) {
194*afee7815SWarner Losh 			do_download_ramdisk(var + 8, true);
195*afee7815SWarner Losh 			return;
196*afee7815SWarner Losh 		}
197*afee7815SWarner Losh 		if (strncmp(var, "memcd=", 6) == 0) {
198*afee7815SWarner Losh 			do_download_ramdisk(var + 6, false);
199*afee7815SWarner Losh 			return;
200*afee7815SWarner Losh 		}
201*afee7815SWarner Losh 	}
202*afee7815SWarner Losh 	return;
203*afee7815SWarner Losh }
204