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