blob: 325951b49860dac473c9a1aeb9e518523a7bff55 [file] [log] [blame]
Heinrich Schuchardtc4392d92021-01-17 07:30:26 +01001// SPDX-License-Identifier: GPL-2.0+
2/*
3 * Copyright 2020, Heinrich Schuchardt <xypron.glpk@gmx.de>
4 *
5 * initrddump.efi saves the initial RAM disk provided via the
6 * EFI_LOAD_FILE2_PROTOCOL.
7 */
8
9#include <common.h>
10#include <efi_api.h>
11#include <efi_load_initrd.h>
12
13#define BUFFER_SIZE 64
14#define ESC 0x17
15
16#define efi_size_in_pages(size) (((size) + EFI_PAGE_MASK) >> EFI_PAGE_SHIFT)
17
18static struct efi_system_table *systable;
19static struct efi_boot_services *bs;
20static struct efi_simple_text_output_protocol *cerr;
21static struct efi_simple_text_output_protocol *cout;
22static struct efi_simple_text_input_protocol *cin;
23static const efi_guid_t loaded_image_guid = EFI_LOADED_IMAGE_PROTOCOL_GUID;
24static const efi_guid_t guid_simple_file_system_protocol =
25 EFI_SIMPLE_FILE_SYSTEM_PROTOCOL_GUID;
26static const efi_guid_t load_file2_guid = EFI_LOAD_FILE2_PROTOCOL_GUID;
27static efi_handle_t handle;
28
29/*
30 * Device path defined by Linux to identify the handle providing the
31 * EFI_LOAD_FILE2_PROTOCOL used for loading the initial ramdisk.
32 */
33static const struct efi_initrd_dp initrd_dp = {
34 .vendor = {
35 {
36 DEVICE_PATH_TYPE_MEDIA_DEVICE,
37 DEVICE_PATH_SUB_TYPE_VENDOR_PATH,
38 sizeof(initrd_dp.vendor),
39 },
40 EFI_INITRD_MEDIA_GUID,
41 },
42 .end = {
43 DEVICE_PATH_TYPE_END,
44 DEVICE_PATH_SUB_TYPE_END,
45 sizeof(initrd_dp.end),
46 }
47};
48
49/**
50 * print() - print string
51 *
52 * @string: text
53 */
54static void print(u16 *string)
55{
56 cout->output_string(cout, string);
57}
58
59/**
60 * error() - print error string
61 *
62 * @string: error text
63 */
64static void error(u16 *string)
65{
66 cout->set_attribute(cout, EFI_LIGHTRED | EFI_BACKGROUND_BLACK);
67 print(string);
68 cout->set_attribute(cout, EFI_LIGHTBLUE | EFI_BACKGROUND_BLACK);
69}
70
71/*
72 * printx() - print hexadecimal number
73 *
74 * @val: value to print;
75 * @prec: minimum number of digits to print
76 */
77static void printx(u64 val, u32 prec)
78{
79 int i;
80 u16 c;
81 u16 buf[16];
82 u16 *pos = buf;
83
84 for (i = 2 * sizeof(val) - 1; i >= 0; --i) {
85 c = (val >> (4 * i)) & 0x0f;
86 if (c || pos != buf || !i || i < prec) {
87 c += '0';
88 if (c > '9')
89 c += 'a' - '9' - 1;
90 *pos++ = c;
91 }
92 }
93 *pos = 0;
94 print(buf);
95}
96
97/**
98 * efi_input_yn() - get answer to yes/no question
99 *
100 * Return:
101 * y or Y
102 * EFI_SUCCESS
103 * n or N
104 * EFI_ACCESS_DENIED
105 * ESC
106 * EFI_ABORTED
107 */
108static efi_status_t efi_input_yn(void)
109{
110 struct efi_input_key key = {0};
111 efi_uintn_t index;
112 efi_status_t ret;
113
114 /* Drain the console input */
115 ret = cin->reset(cin, true);
116 for (;;) {
117 ret = bs->wait_for_event(1, &cin->wait_for_key, &index);
118 if (ret != EFI_SUCCESS)
119 continue;
120 ret = cin->read_key_stroke(cin, &key);
121 if (ret != EFI_SUCCESS)
122 continue;
123 switch (key.scan_code) {
124 case 0x17: /* Escape */
125 return EFI_ABORTED;
126 default:
127 break;
128 }
129 /* Convert to lower case */
130 switch (key.unicode_char | 0x20) {
131 case 'y':
132 return EFI_SUCCESS;
133 case 'n':
134 return EFI_ACCESS_DENIED;
135 default:
136 break;
137 }
138 }
139}
140
141/**
142 * efi_input() - read string from console
143 *
144 * @buffer: input buffer
145 * @buffer_size: buffer size
146 * Return: status code
147 */
148static efi_status_t efi_input(u16 *buffer, efi_uintn_t buffer_size)
149{
150 struct efi_input_key key = {0};
151 efi_uintn_t index;
152 efi_uintn_t pos = 0;
153 u16 outbuf[2] = L" ";
154 efi_status_t ret;
155
156 /* Drain the console input */
157 ret = cin->reset(cin, true);
158 *buffer = 0;
159 for (;;) {
160 ret = bs->wait_for_event(1, &cin->wait_for_key, &index);
161 if (ret != EFI_SUCCESS)
162 continue;
163 ret = cin->read_key_stroke(cin, &key);
164 if (ret != EFI_SUCCESS)
165 continue;
166 switch (key.scan_code) {
167 case 0x17: /* Escape */
168 print(L"\r\nAborted\r\n");
169 return EFI_ABORTED;
170 default:
171 break;
172 }
173 switch (key.unicode_char) {
174 case 0x08: /* Backspace */
175 if (pos) {
176 buffer[pos--] = 0;
177 print(L"\b \b");
178 }
179 break;
180 case 0x0a: /* Linefeed */
181 case 0x0d: /* Carriage return */
182 print(L"\r\n");
183 return EFI_SUCCESS;
184 default:
185 break;
186 }
187 /* Ignore surrogate codes */
188 if (key.unicode_char >= 0xD800 && key.unicode_char <= 0xDBFF)
189 continue;
190 if (key.unicode_char >= 0x20 &&
191 pos < buffer_size - 1) {
192 *outbuf = key.unicode_char;
193 buffer[pos++] = key.unicode_char;
194 buffer[pos] = 0;
195 print(outbuf);
196 }
197 }
198}
199
200/**
201 * skip_whitespace() - skip over leading whitespace
202 *
203 * @pos: UTF-16 string
204 * Return: pointer to first non-whitespace
205 */
206static u16 *skip_whitespace(u16 *pos)
207{
208 for (; *pos && *pos <= 0x20; ++pos)
209 ;
210 return pos;
211}
212
213/**
214 * starts_with() - check if @string starts with @keyword
215 *
216 * @string: string to search for keyword
217 * @keyword: keyword to be searched
218 * Return: true fi @string starts with the keyword
219 */
220static bool starts_with(u16 *string, u16 *keyword)
221{
222 for (; *keyword; ++string, ++keyword) {
223 if (*string != *keyword)
224 return false;
225 }
226 return true;
227}
228
229/**
230 * do_help() - print help
231 */
232static void do_help(void)
233{
234 error(L"load - show length and CRC32 of initial RAM disk\r\n");
235 error(L"save <initrd> - save initial RAM disk to file\r\n");
236 error(L"exit - exit the shell\r\n");
237}
238
239/**
240 * get_initrd() - read initial RAM disk via EFI_LOAD_FILE2_PROTOCOL
241 *
242 * @initrd: on return buffer with initial RAM disk
243 * @initrd_size: size of initial RAM disk
244 * Return: status code
245 */
246static efi_status_t get_initrd(void **initrd, efi_uintn_t *initrd_size)
247{
248 struct efi_device_path *dp = (struct efi_device_path *)&initrd_dp;
249 struct efi_load_file_protocol *load_file2_prot;
250 u64 buffer;
251 efi_handle_t handle;
252 efi_status_t ret;
253
254 *initrd = NULL;
255 *initrd_size = 0;
256 ret = bs->locate_device_path(&load_file2_guid, &dp, &handle);
257 if (ret != EFI_SUCCESS) {
258 error(L"Load File2 protocol not found\r\n");
259 return ret;
260 }
261 ret = bs->handle_protocol(handle, &load_file2_guid,
262 (void **)&load_file2_prot);
263 ret = load_file2_prot->load_file(load_file2_prot, dp, false,
264 initrd_size, NULL);
265 if (ret != EFI_BUFFER_TOO_SMALL) {
266 error(L"Load File2 protocol does not provide file length\r\n");
267 return EFI_LOAD_ERROR;
268 }
269 ret = bs->allocate_pages(EFI_ALLOCATE_ANY_PAGES, EFI_LOADER_DATA,
270 efi_size_in_pages(*initrd_size), &buffer);
271 if (ret != EFI_SUCCESS) {
272 error(L"Out of memory\r\n");
273 return ret;
274 }
Heinrich Schuchardt22f038f2021-03-14 10:12:01 +0100275 *initrd = (void *)(uintptr_t)buffer;
Heinrich Schuchardtc4392d92021-01-17 07:30:26 +0100276 ret = load_file2_prot->load_file(load_file2_prot, dp, false,
277 initrd_size, *initrd);
278 if (ret != EFI_SUCCESS) {
279 error(L"Load File2 protocol failed to provide file\r\n");
280 bs->free_pages(buffer, efi_size_in_pages(*initrd_size));
281 return EFI_LOAD_ERROR;
282 }
283 return ret;
284}
285
286/**
287 * do_load() - load initial RAM disk and display CRC32 and length
288 *
289 * @filename: file name
290 * Return: status code
291 */
292static efi_status_t do_load(void)
293{
294 void *initrd;
295 efi_uintn_t initrd_size;
296 u32 crc32;
297 efi_uintn_t ret;
298
299 ret = get_initrd(&initrd, &initrd_size);
300 if (ret != EFI_SUCCESS)
301 return ret;
302 print(L"length: 0x");
303 printx(initrd_size, 1);
304 print(L"\r\n");
305
306 ret = bs->calculate_crc32(initrd, initrd_size, &crc32);
307 if (ret != EFI_SUCCESS) {
308 error(L"Calculating CRC32 failed\r\n");
309 return EFI_LOAD_ERROR;
310 }
311 print(L"crc32: 0x");
312 printx(crc32, 8);
313 print(L"\r\n");
314
315 return EFI_SUCCESS;
316}
317
318/**
319 * do_save() - save initial RAM disk
320 *
321 * @filename: file name
322 * Return: status code
323 */
324static efi_status_t do_save(u16 *filename)
325{
326 struct efi_loaded_image *loaded_image;
327 struct efi_simple_file_system_protocol *file_system;
328 struct efi_file_handle *root, *file;
329 void *initrd;
330 efi_uintn_t initrd_size;
331 efi_uintn_t ret;
332
333 ret = get_initrd(&initrd, &initrd_size);
334 if (ret != EFI_SUCCESS)
335 return ret;
336
337 filename = skip_whitespace(filename);
338
339 ret = bs->open_protocol(handle, &loaded_image_guid,
340 (void **)&loaded_image, NULL, NULL,
341 EFI_OPEN_PROTOCOL_GET_PROTOCOL);
342 if (ret != EFI_SUCCESS) {
343 error(L"Loaded image protocol not found\r\n");
344 goto out;
345 }
346
347 /* Open the simple file system protocol */
348 ret = bs->open_protocol(loaded_image->device_handle,
349 &guid_simple_file_system_protocol,
350 (void **)&file_system, NULL, NULL,
351 EFI_OPEN_PROTOCOL_GET_PROTOCOL);
352 if (ret != EFI_SUCCESS) {
353 error(L"Failed to open simple file system protocol\r\n");
354 goto out;
355 }
356
357 /* Open volume */
358 ret = file_system->open_volume(file_system, &root);
359 if (ret != EFI_SUCCESS) {
360 error(L"Failed to open volume\r\n");
361 goto out;
362 }
363 /* Check if file already exists */
364 ret = root->open(root, &file, filename, EFI_FILE_MODE_READ, 0);
365 if (ret == EFI_SUCCESS) {
366 file->close(file);
367 print(L"Overwrite existing file (y/n)? ");
368 ret = efi_input_yn();
369 print(L"\r\n");
370 if (ret != EFI_SUCCESS) {
371 root->close(root);
372 error(L"Aborted by user\r\n");
373 goto out;
374 }
375 }
376
377 /* Create file */
378 ret = root->open(root, &file, filename,
379 EFI_FILE_MODE_READ | EFI_FILE_MODE_WRITE |
380 EFI_FILE_MODE_CREATE, EFI_FILE_ARCHIVE);
381 if (ret == EFI_SUCCESS) {
382 /* Write file */
383 ret = file->write(file, &initrd_size, initrd);
384 if (ret != EFI_SUCCESS) {
385 error(L"Failed to write file\r\n");
386 } else {
387 print(filename);
388 print(L" written\r\n");
389 }
390 file->close(file);
391 } else {
392 error(L"Failed to open file\r\n");
393 }
394 root->close(root);
395
396out:
397 if (initrd)
398 bs->free_pages((uintptr_t)initrd,
399 efi_size_in_pages(initrd_size));
400 return ret;
401}
402
403/**
404 * efi_main() - entry point of the EFI application.
405 *
406 * @handle: handle of the loaded image
407 * @systab: system table
408 * @return: status code
409 */
410efi_status_t EFIAPI efi_main(efi_handle_t image_handle,
411 struct efi_system_table *systab)
412{
413 handle = image_handle;
414 systable = systab;
415 cerr = systable->std_err;
416 cout = systable->con_out;
417 cin = systable->con_in;
418 bs = systable->boottime;
419
420 cout->set_attribute(cout, EFI_LIGHTBLUE | EFI_BACKGROUND_BLACK);
421 cout->clear_screen(cout);
422 cout->set_attribute(cout, EFI_WHITE | EFI_BACKGROUND_BLACK);
423 print(L"INITRD Dump\r\n========\r\n\r\n");
424 cout->set_attribute(cout, EFI_LIGHTBLUE | EFI_BACKGROUND_BLACK);
425
426 for (;;) {
427 u16 command[BUFFER_SIZE];
428 u16 *pos;
429 efi_uintn_t ret;
430
431 print(L"=> ");
432 ret = efi_input(command, sizeof(command));
433 if (ret == EFI_ABORTED)
434 break;
435 pos = skip_whitespace(command);
436 if (starts_with(pos, L"exit"))
437 break;
438 else if (starts_with(pos, L"load"))
439 do_load();
440 else if (starts_with(pos, L"save "))
441 do_save(pos + 5);
442 else
443 do_help();
444 }
445
446 cout->set_attribute(cout, EFI_LIGHTGRAY | EFI_BACKGROUND_BLACK);
447 cout->clear_screen(cout);
448 return EFI_SUCCESS;
449}