xref: /freebsd/stand/efi/loader/memdisk.c (revision a259b98fa211ed87bfee58c575de4e2de94ee0fa)
1 /*
2  * Copyright (c) 2026 Netflix, Inc. Written by Warner Losh
3  *
4  * SPDX-License-Identifier: BSD-2-Clause
5  *
6  * Derived from memdisk_uefi.c
7  * Copyright 2025 Richard Russo
8  * SPDX-License-Identifier: BSD-2-Clause-Patent
9  */
10 
11 #include "loader_efi.h"
12 #include <bootstrap.h>
13 #include <dev_net.h>
14 #include <efilib.h>
15 #include <Protocol/RamDisk.h>
16 #include "decompress.h"
17 #include <ipxe_download.h>
18 #include <sys/_param.h>
19 
20 #define ULL(x) ((unsigned long long)(x))
21 #define DOWNLOAD_BUFSIZE	(64 * 1024)
22 
23 static EFI_GUID ipxeGuid = IPXE_DOWNLOAD_PROTOCOL_GUID;
24 static EFI_GUID ramdiskGuid = EFI_RAM_DISK_PROTOCOL_GUID;
25 static EFI_GUID virtual_disk_guid = EFI_VIRTUAL_DISK_GUID;
26 static EFI_GUID virtual_cd_guid = EFI_VIRTUAL_CD_GUID;
27 
28 static IPXE_DOWNLOAD_PROTOCOL *ipxe_download;
29 static EFI_RAM_DISK_PROTOCOL *ram_disk;
30 
31 static bool
32 download_cancel_requested(void)
33 {
34 	int c;
35 
36 	if (!ischar())
37 		return (false);
38 	c = getchar();
39 	return (c == '\033');
40 }
41 
42 struct dl_state;
43 typedef struct dl_state dl_state;
44 
45 static struct dl_state
46 {
47 	bool in_progress;
48 	size_t size;
49 	EFI_STATUS status;
50 	decomp_state *dctx;
51 	bool complete;
52 } dl;
53 
54 static void
55 download_cleanup(dl_state *ctx)
56 {
57 	if (ctx->dctx)
58 		decomp_fini(ctx->dctx, true);
59 	ctx->in_progress = false;
60 }
61 
62 static EFI_STATUS
63 download_chunk(dl_state *ctx, void *buffer, size_t length, size_t offset)
64 {
65 	decomp_state *dctx = ctx->dctx;
66 	enum step_return sr;
67 
68 	if (offset == 0 && length == 0) {
69 		printf("Starting the download\n");
70 		return (EFI_SUCCESS);
71 	}
72 
73 	/*
74 	 * Make a note of the size when we're hinted about it.
75 	 */
76 	if (length == 0) {
77 		printf("We know we will download %llu bytes\n", ULL(offset));
78 		ctx->size = offset;
79 		ctx->status = EFI_SUCCESS;
80 		return (EFI_SUCCESS);
81 	}
82 
83 	/*
84 	 * Peek into the first chunk to see the format of the data.
85 	 */
86 	if (offset == 0) {
87 		dctx = decomp_init(buffer, length, ctx->size);
88 		if (dctx == NULL) {
89 			ctx->in_progress = false;
90 			ctx->status = EFI_VOLUME_CORRUPTED;
91 			return (ctx->status);
92 		}
93 		ctx->dctx = dctx;
94 	}
95 
96 	sr = decomp_step(dctx, buffer, length, offset);
97 	if (sr == err) {
98 		printf("Error on download\n");
99 		return (EFI_VOLUME_CORRUPTED);
100 	}
101 	ctx->complete = (sr == done);
102 
103 	unsigned long long sofar = offset + length;
104 #define MB  1000000
105 	if (sofar / MB != offset / MB) {
106 		if (ctx->size)
107 			printf("%dMB / %dMB (%d%%)\r",
108 			    (int)(sofar / MB),
109 			    (int)(ctx->size / MB),
110 			    (int)(100 * sofar / ctx->size));
111 		else
112 			printf("%dMB\r", (int)(sofar / MB));
113 	}
114 	return (EFI_SUCCESS);
115 }
116 
117 static EFI_STATUS EFIAPI
118 download_data(IN VOID *Context, IN VOID *Buffer, IN UINTN BufferLength,
119     IN UINTN FileOffset)
120 {
121 
122 	return (download_chunk(Context, Buffer, BufferLength, FileOffset));
123 }
124 
125 static void EFIAPI
126 download_finish(IN VOID *Context, IN EFI_STATUS Status)
127 {
128 	dl_state *ctx = Context;
129 
130 	ctx->in_progress = false;
131 	ctx->status = Status;
132 	if (ctx->dctx != NULL && !EFI_ERROR(Status))
133 		decomp_fini(ctx->dctx, false);
134 }
135 
136 static int
137 fallback_to_md(EFI_PHYSICAL_ADDRESS pa, size_t len)
138 {
139 	int unit;
140 
141 	unit = md_register((void *)(uintptr_t)pa, len, MD_FLAG_KERNEL);
142 	if (unit < 0) {
143 		printf("Could not register downloaded image as an md: %s\n",
144 		    strerror(errno));
145 		return (errno);
146 	}
147 	setenv("uefi_ignore_boot_mgr", "true", 1);
148 	return (0);
149 }
150 
151 int
152 download_md_image(const char *url)
153 {
154 	struct stat sb;
155 	dl_state ctx;
156 	uint8_t *buf;
157 	size_t offset;
158 	ssize_t nread;
159 	int error, fd;
160 
161 	fd = open(url, O_RDONLY);
162 	if (fd < 0)
163 		return (errno);
164 	if (fstat(fd, &sb) != 0) {
165 		error = errno;
166 		goto out_close;
167 	}
168 	if (sb.st_size <= 0 || (uintmax_t)sb.st_size > SIZE_MAX) {
169 		error = EINVAL;
170 		goto out_close;
171 	}
172 
173 	buf = malloc(DOWNLOAD_BUFSIZE);
174 	if (buf == NULL) {
175 		error = ENOMEM;
176 		goto out_close;
177 	}
178 	memset(&ctx, 0, sizeof(ctx));
179 	ctx.size = sb.st_size;
180 	offset = 0;
181 	printf("Press Esc to cancel.\n");
182 	while ((nread = read(fd, buf, DOWNLOAD_BUFSIZE)) > 0) {
183 		if (download_cancel_requested()) {
184 			printf("\nDownload cancelled.\n");
185 			error = ECANCELED;
186 			goto out_decomp;
187 		}
188 		if (EFI_ERROR(download_chunk(&ctx, buf, nread, offset))) {
189 			error = EIO;
190 			goto out_decomp;
191 		}
192 		offset += nread;
193 	}
194 	if (nread < 0) {
195 		error = errno;
196 		goto out_decomp;
197 	}
198 	if (offset != ctx.size || !ctx.complete) {
199 		error = EIO;
200 		goto out_decomp;
201 	}
202 
203 	decomp_fini(ctx.dctx, false);
204 	error = fallback_to_md(decomp_buffer(ctx.dctx),
205 	    decomp_buffer_length(ctx.dctx));
206 	goto out_free;
207 
208 out_decomp:
209 	if (ctx.dctx != NULL)
210 		decomp_fini(ctx.dctx, true);
211 out_free:
212 	free(buf);
213 out_close:
214 	close(fd);
215 	return (error);
216 }
217 
218 void
219 maybe_download_initmd(void)
220 {
221 	struct devdesc dev;
222 	const char *url;
223 	int error;
224 
225 	if (efi_find_handle(&efinet_dev, 0) == NULL)
226 		return;
227 
228 	memset(&dev, 0, sizeof(dev));
229 	dev.d_dev = &efinet_dev;
230 	dev.d_unit = 0;
231 	error = net_configure(&dev);
232 	if (error != 0) {
233 		printf("Could not configure net0 for initmd: %s\n",
234 		    strerror(error));
235 		return;
236 	}
237 
238 	url = getenv("dhcp.initmd");
239 	if (url == NULL || *url == '\0')
240 		return;
241 
242 	printf("Downloading initmd from %s\n", url);
243 	error = download_md_image(url);
244 	if (error != 0 && error != ECANCELED)
245 		printf("Could not download initmd: %s\n", strerror(error));
246 }
247 
248 static void
249 do_download_ramdisk(CHAR8 *url, bool is_disk)
250 {
251 	EFI_STATUS Status;
252 	EFI_GUID disk_type = is_disk ? virtual_disk_guid : virtual_cd_guid;
253 	EFI_DEVICE_PATH_PROTOCOL *ram_disk_path;
254 	IPXE_DOWNLOAD_FILE token;
255 	dl_state *ctx = &dl;
256 	int error;
257 
258 	printf("Downloading %s as a %s\n", url, is_disk ? "disk" : "cd");
259 	printf("Press Esc to cancel.\n");
260 	memset(ctx, 0, sizeof(*ctx));
261 	ctx->in_progress = true;
262 	Status = ipxe_download->Start(ipxe_download, url, download_data, download_finish,
263 	    &dl, &token);
264 	if (EFI_ERROR(Status)) {
265 		printf("Couldn't start download %u\n", (unsigned)Status);
266 		download_cleanup(ctx);
267 		return;
268 	}
269 	while (ctx->in_progress) {
270 		ipxe_download->Poll(ipxe_download);
271 		if (!ctx->in_progress)
272 			break;
273 		if (download_cancel_requested()) {
274 			printf("\nCancelling download...\n");
275 			Status = ipxe_download->Abort(ipxe_download, token,
276 			    EFI_ABORTED);
277 			if (EFI_ERROR(Status)) {
278 				printf("Could not cancel download %u\n",
279 				    (unsigned)Status);
280 			}
281 		}
282 	}
283 	if (ctx->status == EFI_ABORTED) {
284 		printf("Download cancelled.\n");
285 		download_cleanup(ctx);
286 		return;
287 	}
288 	if (EFI_ERROR(ctx->status)) {
289 		printf("Download had error %u\n", (unsigned)ctx->status);
290 		download_cleanup(ctx);
291 		return;
292 	}
293 	if (ctx->size == 0) {
294 		printf("Nothing downloaded\n");
295 		download_cleanup(ctx);
296 		return;
297 	}
298 
299 	printf("\nDownloaded %llu bytes, actual size %llu -- registering ramdisk\n",
300 	    ULL(ctx->size), ULL(decomp_buffer_length(ctx->dctx)));
301 
302 	/* ram_disk will be NULL if this fails */
303 	BS->LocateProtocol(&ramdiskGuid, NULL, (void**)&ram_disk);
304 
305 	/*
306 	 * If there's no ram_disk protocol installed in this firmware, do the
307 	 * next best thing by saving a pointer and using that later.
308 	 */
309 	if (ram_disk == NULL) {
310 		printf("No RamDisk protocol, falling back to md image\n");
311 		error = fallback_to_md(decomp_buffer(ctx->dctx), decomp_buffer_length(ctx->dctx));
312 		if (error) {
313 			printf("Failed to register as an MD device\n");
314 			download_cleanup(ctx);
315 		}
316 		return;
317 	}
318 
319 	/*
320 	 * Register the RamDisk with UEFI. This registers it so the rest of the
321 	 * boot loader can see it as a block device.
322 	 */
323 	Status = ram_disk->Register(decomp_buffer(ctx->dctx), decomp_buffer_length(ctx->dctx),
324 	    &disk_type, NULL, &ram_disk_path);
325 	if (EFI_ERROR(Status)) {
326 		printf("failed to register ram disk %u, falling back to md image\n", (unsigned)Status);
327 		error = fallback_to_md(decomp_buffer(ctx->dctx), decomp_buffer_length(ctx->dctx));
328 		if (error) {
329 			printf("Failed to register as an MD device\n");
330 			download_cleanup(ctx);
331 		}
332 		return;
333 	}
334 
335 	CHAR16 *text = efi_devpath_name(ram_disk_path);
336 	if (text != NULL) {
337 		CHAR8 uefi_path[1024];
338 		printf("Installed RAM disk as %S\n", text);
339 
340 		cpy16to8(text, uefi_path, sizeof(uefi_path));
341 		setenv("uefi_ignore_boot_mgr", "true", 1);
342 		setenv("uefi_rootdev", uefi_path, 1);
343 		efi_free_devpath_name(text);
344 	} else {
345 		printf("Installed RAM disk to unknown device type\n");
346 	}
347 }
348 
349 /*
350  * Scan the command line for memdisk=url or memcd=url. Do nothing if that's not
351  * present, otherwise try to download that image. Returns true when we've tried
352  * to download an image, whether successful or not.
353  *
354  * Open Question: Do we want some way to chain boot into the /boot/loader.efi or
355  * \efi\boot\bootXXXXX.efi inside the ram disk we load? If so, how do we keep
356  * from infinite chainbooting? Also, I don't understand the load it but don't save
357  * it option...
358  */
359 bool
360 maybe_download_ramdisk(int argc, CHAR16 **argv)
361 {
362 	char var[256];
363 	EFI_STATUS status;
364 
365 	status = BS->LocateProtocol(&ipxeGuid, NULL, (void **)&ipxe_download);
366 	if (EFI_ERROR(status)) {
367 		ipxe_download = NULL;
368 		return (false);
369 	}
370 
371 	for (int i = 0; i < argc; i++) {
372 		cpy16to8(argv[i], var, sizeof(var));
373 		if (strncmp(var, "memdisk=", 8) == 0) {
374 			do_download_ramdisk(var + 8, true);
375 			return (true);
376 		}
377 		if (strncmp(var, "memcd=", 6) == 0) {
378 			do_download_ramdisk(var + 6, false);
379 			return (true);
380 		}
381 	}
382 	return (false);
383 }
384