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