blob: 2f0b91a353d1ab41480cc537b4e277eccac78328 [file] [log] [blame]
Heinrich Schuchardtfdbe3d52024-01-03 09:07:21 +01001// SPDX-License-Identifier: GPL-2.0-or-later
2/*
3 * Copyright 2023, Heinrich Schuchardt <heinrich.schuchardt@canonical.com>
4 *
5 * smbiosdump.efi saves the SMBIOS table as file.
6 *
7 * Specifying 'nocolor' as load option data suppresses colored output and
8 * clearing of the screen.
9 */
10
11#include <efi_api.h>
12#include <part.h>
13#include <smbios.h>
14#include <string.h>
15
16#define BUFFER_SIZE 64
17
18static struct efi_simple_text_output_protocol *cerr;
19static struct efi_simple_text_output_protocol *cout;
20static struct efi_simple_text_input_protocol *cin;
21static struct efi_boot_services *bs;
22static efi_handle_t handle;
23static struct efi_system_table *systable;
24static const efi_guid_t smbios_guid = SMBIOS_TABLE_GUID;
25static const efi_guid_t smbios3_guid = SMBIOS3_TABLE_GUID;
26static const efi_guid_t loaded_image_guid = EFI_LOADED_IMAGE_PROTOCOL_GUID;
27static const efi_guid_t guid_simple_file_system_protocol =
28 EFI_SIMPLE_FILE_SYSTEM_PROTOCOL_GUID;
29static const efi_guid_t efi_system_partition_guid = PARTITION_SYSTEM_GUID;
30static bool nocolor;
31
32/**
33 * color() - set foreground color
34 *
35 * @color: foreground color
36 */
37static void color(u8 color)
38{
39 if (!nocolor)
40 cout->set_attribute(cout, color | EFI_BACKGROUND_BLACK);
41}
42
43/**
44 * print() - print string
45 *
46 * @string: text
47 */
48static void print(u16 *string)
49{
50 cout->output_string(cout, string);
51}
52
53/**
54 * cls() - clear screen
55 */
56static void cls(void)
57{
58 if (nocolor)
59 print(u"\r\n");
60 else
61 cout->clear_screen(cout);
62}
63
64/**
65 * error() - print error string
66 *
67 * @string: error text
68 */
69static void error(u16 *string)
70{
71 color(EFI_LIGHTRED);
72 print(string);
73 color(EFI_LIGHTBLUE);
74}
75
76/**
77 * efi_input_yn() - get answer to yes/no question
78 *
79 * Return:
80 * y or Y
81 * EFI_SUCCESS
82 * n or N
83 * EFI_ACCESS_DENIED
84 * ESC
85 * EFI_ABORTED
86 */
87static efi_status_t efi_input_yn(void)
88{
89 struct efi_input_key key = {0};
90 efi_uintn_t index;
91 efi_status_t ret;
92
93 /* Drain the console input */
94 ret = cin->reset(cin, true);
95 for (;;) {
96 ret = bs->wait_for_event(1, &cin->wait_for_key, &index);
97 if (ret != EFI_SUCCESS)
98 continue;
99 ret = cin->read_key_stroke(cin, &key);
100 if (ret != EFI_SUCCESS)
101 continue;
102 switch (key.scan_code) {
103 case 0x17: /* Escape */
104 return EFI_ABORTED;
105 default:
106 break;
107 }
108 /* Convert to lower case */
109 switch (key.unicode_char | 0x20) {
110 case 'y':
111 return EFI_SUCCESS;
112 case 'n':
113 return EFI_ACCESS_DENIED;
114 default:
115 break;
116 }
117 }
118}
119
120/**
121 * efi_input() - read string from console
122 *
123 * @buffer: input buffer
124 * @buffer_size: buffer size
125 * Return: status code
126 */
127static efi_status_t efi_input(u16 *buffer, efi_uintn_t buffer_size)
128{
129 struct efi_input_key key = {0};
130 efi_uintn_t index;
131 efi_uintn_t pos = 0;
132 u16 outbuf[2] = u" ";
133 efi_status_t ret;
134
135 /* Drain the console input */
136 ret = cin->reset(cin, true);
137 *buffer = 0;
138 for (;;) {
139 ret = bs->wait_for_event(1, &cin->wait_for_key, &index);
140 if (ret != EFI_SUCCESS)
141 continue;
142 ret = cin->read_key_stroke(cin, &key);
143 if (ret != EFI_SUCCESS)
144 continue;
145 switch (key.scan_code) {
146 case 0x17: /* Escape */
147 print(u"\r\nAborted\r\n");
148 return EFI_ABORTED;
149 default:
150 break;
151 }
152 switch (key.unicode_char) {
153 case 0x08: /* Backspace */
154 if (pos) {
155 buffer[pos--] = 0;
156 print(u"\b \b");
157 }
158 break;
159 case 0x0a: /* Linefeed */
160 case 0x0d: /* Carriage return */
161 print(u"\r\n");
162 return EFI_SUCCESS;
163 default:
164 break;
165 }
166 /* Ignore surrogate codes */
167 if (key.unicode_char >= 0xD800 && key.unicode_char <= 0xDBFF)
168 continue;
169 if (key.unicode_char >= 0x20 &&
170 pos < buffer_size - 1) {
171 *outbuf = key.unicode_char;
172 buffer[pos++] = key.unicode_char;
173 buffer[pos] = 0;
174 print(outbuf);
175 }
176 }
177}
178
179/**
180 * skip_whitespace() - skip over leading whitespace
181 *
182 * @pos: UTF-16 string
183 * Return: pointer to first non-whitespace
184 */
185static u16 *skip_whitespace(u16 *pos)
186{
187 for (; *pos && *pos <= 0x20; ++pos)
188 ;
189 return pos;
190}
191
192/**
193 * starts_with() - check if @string starts with @keyword
194 *
195 * @string: string to search for keyword
196 * @keyword: keyword to be searched
197 * Return: true fi @string starts with the keyword
198 */
199static bool starts_with(u16 *string, u16 *keyword)
200{
201 if (!string || !keyword)
202 return NULL;
203
204 for (; *keyword; ++string, ++keyword) {
205 if (*string != *keyword)
206 return false;
207 }
208 return true;
209}
210
211/**
212 * open_file_system() - open simple file system protocol
213 *
214 * file_system: interface of the simple file system protocol
215 * Return: status code
216 */
217static efi_status_t
218open_file_system(struct efi_simple_file_system_protocol **file_system)
219{
220 struct efi_loaded_image *loaded_image;
221 efi_status_t ret;
222 efi_handle_t *handle_buffer = NULL;
223 efi_uintn_t count;
224
225 ret = bs->open_protocol(handle, &loaded_image_guid,
226 (void **)&loaded_image, NULL, NULL,
227 EFI_OPEN_PROTOCOL_GET_PROTOCOL);
228 if (ret != EFI_SUCCESS) {
229 error(u"Loaded image protocol not found\r\n");
230 return ret;
231 }
232
233 /* Open the simple file system protocol on the same partition */
234 ret = bs->open_protocol(loaded_image->device_handle,
235 &guid_simple_file_system_protocol,
236 (void **)file_system, NULL, NULL,
237 EFI_OPEN_PROTOCOL_GET_PROTOCOL);
238 if (ret == EFI_SUCCESS)
239 return ret;
240
241 /* Open the simple file system protocol on the UEFI system partition */
242 ret = bs->locate_handle_buffer(BY_PROTOCOL, &efi_system_partition_guid,
243 NULL, &count, &handle_buffer);
244 if (ret == EFI_SUCCESS && handle_buffer)
245 ret = bs->open_protocol(handle_buffer[0],
246 &guid_simple_file_system_protocol,
247 (void **)file_system, NULL, NULL,
248 EFI_OPEN_PROTOCOL_GET_PROTOCOL);
249 if (ret != EFI_SUCCESS)
250 error(u"Failed to open simple file system protocol\r\n");
251 if (handle)
252 bs->free_pool(handle_buffer);
253
254 return ret;
255}
256
257/**
258 * do_help() - print help
259 */
260static void do_help(void)
261{
262 error(u"check - check SMBIOS table\r\n");
263 error(u"save <file> - save SMBIOS table to file\r\n");
264 error(u"exit - exit the shell\r\n");
265}
266
267/**
268 * get_config_table() - get configuration table
269 *
270 * @guid: GUID of the configuration table
271 * Return: pointer to configuration table or NULL
272 */
273static void *get_config_table(const efi_guid_t *guid)
274{
275 size_t i;
276
277 for (i = 0; i < systable->nr_tables; ++i) {
278 if (!memcmp(guid, &systable->tables[i].guid, 16))
279 return systable->tables[i].table;
280 }
281
282 return NULL;
283}
284
285/**
286 * checksum() - calculate checksum
287 *
288 * @buf: buffer to checksum
289 * @len: length of buffer
290 * Return: checksum
291 */
292u8 checksum(void *buf, int len)
293{
294 u8 ret = 0;
295
296 for (u8 *ptr = buf; len; --len, ++ptr)
297 ret -= *ptr;
298
299 return ret;
300}
301
302/**
303 * do_check() - check SMBIOS table
304 *
305 * Return: status code
306 */
307efi_status_t do_check(void)
308{
309 struct smbios3_entry *smbios3_anchor;
310 void *table, *table_end;
311 u32 len;
312
313 smbios3_anchor = get_config_table(&smbios3_guid);
314 if (smbios3_anchor) {
315 int r;
316
317 r = memcmp(smbios3_anchor->anchor, "_SM3_", 5);
318 if (r) {
319 error(u"Invalid anchor string\n");
320 return EFI_LOAD_ERROR;
321 }
322 print(u"Found SMBIOS 3 entry point\n");
323 if (smbios3_anchor->length != 0x18) {
324 error(u"Invalid anchor length\n");
325 return EFI_LOAD_ERROR;
326 }
327 if (checksum(smbios3_anchor, smbios3_anchor->length)) {
328 error(u"Invalid anchor checksum\n");
329 return EFI_LOAD_ERROR;
330 }
331 table = (void *)(uintptr_t)smbios3_anchor->struct_table_address;
Heinrich Schuchardt68e948a2024-01-31 23:49:34 +0100332 len = smbios3_anchor->table_maximum_size;
Heinrich Schuchardtfdbe3d52024-01-03 09:07:21 +0100333 } else {
334 struct smbios_entry *smbios_anchor;
335 int r;
336
337 smbios_anchor = get_config_table(&smbios_guid);
338 if (!smbios_anchor) {
339 error(u"No SMBIOS table\n");
340 return EFI_NOT_FOUND;
341 }
342 r = memcmp(smbios_anchor->anchor, "_SM_", 4);
343 if (r) {
344 error(u"Invalid anchor string\n");
345 return EFI_LOAD_ERROR;
346 }
347 print(u"Found SMBIOS 2.1 entry point\n");
348 if (smbios_anchor->length != 0x1f) {
349 error(u"Invalid anchor length\n");
350 return EFI_LOAD_ERROR;
351 }
352 if (checksum(smbios_anchor, smbios_anchor->length)) {
353 error(u"Invalid anchor checksum\n");
354 return EFI_LOAD_ERROR;
355 }
356 r = memcmp(smbios_anchor->intermediate_anchor, "_DMI_", 5);
357 if (r) {
358 error(u"Invalid intermediate anchor string\n");
359 return EFI_LOAD_ERROR;
360 }
361 if (checksum(&smbios_anchor->intermediate_anchor, 0xf)) {
362 error(u"Invalid intermediate anchor checksum\n");
363 return EFI_LOAD_ERROR;
364 }
365 table = (void *)(uintptr_t)smbios_anchor->struct_table_address;
366 len = smbios_anchor->struct_table_length;
367 }
368
369 table_end = (void *)((u8 *)table + len);
370 for (struct smbios_header *pos = table; ;) {
371 u8 *str = (u8 *)pos + pos->length;
372
373 if (!*str)
374 ++str;
375 while (*str) {
376 for (; *str; ++str) {
377 if ((void *)str >= table_end) {
378 error(u"Structure table length exceeded\n");
379 return EFI_LOAD_ERROR;
380 }
381 }
382 ++str;
383 }
384 ++str;
385 if ((void *)str > table_end) {
386 error(u"Structure table length exceeded\n");
387 return EFI_LOAD_ERROR;
388 }
389 if (pos->type == 0x7f) /* End of table */
390 break;
391 pos = (struct smbios_header *)str;
392 }
393
394 return EFI_SUCCESS;
395}
396
397/**
398 * save_file() - save file to EFI system partition
399 *
400 * @filename: file name
401 * @buf: buffer to write
402 * @size: size of the buffer
403 */
404efi_status_t save_file(u16 *filename, void *buf, efi_uintn_t size)
405{
406 efi_uintn_t ret;
407 struct efi_simple_file_system_protocol *file_system;
408 struct efi_file_handle *root, *file;
409
410 ret = open_file_system(&file_system);
411 if (ret != EFI_SUCCESS)
412 return ret;
413
414 /* Open volume */
415 ret = file_system->open_volume(file_system, &root);
416 if (ret != EFI_SUCCESS) {
417 error(u"Failed to open volume\r\n");
418 return ret;
419 }
420 /* Check if file already exists */
421 ret = root->open(root, &file, filename, EFI_FILE_MODE_READ, 0);
422 if (ret == EFI_SUCCESS) {
423 file->close(file);
424 print(u"Overwrite existing file (y/n)? ");
425 ret = efi_input_yn();
426 print(u"\r\n");
427 if (ret != EFI_SUCCESS) {
428 root->close(root);
429 error(u"Aborted by user\r\n");
430 bs->free_pool(buf);
431 return ret;
432 }
433 }
434
435 /* Create file */
436 ret = root->open(root, &file, filename,
437 EFI_FILE_MODE_READ | EFI_FILE_MODE_WRITE |
438 EFI_FILE_MODE_CREATE, EFI_FILE_ARCHIVE);
439 if (ret == EFI_SUCCESS) {
440 /* Write file */
441 ret = file->write(file, &size, buf);
442 if (ret != EFI_SUCCESS)
443 error(u"Failed to write file\r\n");
444 file->close(file);
445 } else {
446 error(u"Failed to open file\r\n");
447 }
448 root->close(root);
449
450 return ret;
451}
452
453/**
454 * do_save() - save SMBIOS table
455 *
456 * @filename: file name
457 * Return: status code
458 */
459static efi_status_t do_save(u16 *filename)
460{
461 struct smbios3_entry *smbios3_anchor;
462 u8 *buf;
463 efi_uintn_t size;
464 efi_uintn_t ret;
465
466 ret = do_check();
467 if (ret != EFI_SUCCESS)
468 return ret;
469
470 smbios3_anchor = get_config_table(&smbios3_guid);
471 if (smbios3_anchor) {
Heinrich Schuchardt68e948a2024-01-31 23:49:34 +0100472 size = 0x20 + smbios3_anchor->table_maximum_size;
Heinrich Schuchardtfdbe3d52024-01-03 09:07:21 +0100473 ret = bs->allocate_pool(EFI_LOADER_DATA, size, (void **)&buf);
474 if (ret != EFI_SUCCESS) {
475 error(u"Out of memory\n");
476 return ret;
477 }
478
479 memset(buf, 0, size);
480 memcpy(buf, smbios3_anchor, smbios3_anchor->length);
481 memcpy(buf + 0x20,
482 (void *)(uintptr_t)smbios3_anchor->struct_table_address,
Heinrich Schuchardt68e948a2024-01-31 23:49:34 +0100483 smbios3_anchor->table_maximum_size);
Heinrich Schuchardtfdbe3d52024-01-03 09:07:21 +0100484
485 smbios3_anchor = (struct smbios3_entry *)buf;
486 smbios3_anchor->struct_table_address = 0x20;
487 smbios3_anchor->checksum +=
488 checksum(smbios3_anchor, smbios3_anchor->length);
489 } else {
490 struct smbios_entry *smbios_anchor;
491
492 smbios_anchor = get_config_table(&smbios_guid);
493 if (!smbios_anchor) {
494 /* Should not be reached after successful do_check() */
495 error(u"No SMBIOS table\n");
496 return EFI_NOT_FOUND;
497 }
498
499 size = 0x20 + smbios_anchor->struct_table_length;
500
501 ret = bs->allocate_pool(EFI_LOADER_DATA, size, (void **)&buf);
502 if (ret != EFI_SUCCESS) {
503 error(u"Out of memory\n");
504 return ret;
505 }
506
507 memset(buf, 0, size);
508 memcpy(buf, smbios_anchor, smbios_anchor->length);
509 memcpy(buf + 0x20,
510 (void *)(uintptr_t)smbios_anchor->struct_table_address,
511 smbios_anchor->struct_table_length);
512
513 smbios_anchor = (struct smbios_entry *)buf;
514 smbios_anchor->struct_table_address = 0x20;
515 smbios_anchor->intermediate_checksum +=
516 checksum(&smbios_anchor->intermediate_anchor, 0xf);
517 smbios_anchor->checksum +=
518 checksum(smbios_anchor, smbios_anchor->length);
519 }
520
521 filename = skip_whitespace(filename);
522
523 ret = save_file(filename, buf, size);
524
525 if (ret == EFI_SUCCESS) {
526 print(filename);
527 print(u" written\r\n");
528 }
529
530 bs->free_pool(buf);
531
532 return ret;
533}
534
535/**
536 * get_load_options() - get load options
537 *
538 * Return: load options or NULL
539 */
540static u16 *get_load_options(void)
541{
542 efi_status_t ret;
543 struct efi_loaded_image *loaded_image;
544
545 ret = bs->open_protocol(handle, &loaded_image_guid,
546 (void **)&loaded_image, NULL, NULL,
547 EFI_OPEN_PROTOCOL_GET_PROTOCOL);
548 if (ret != EFI_SUCCESS) {
549 error(u"Loaded image protocol not found\r\n");
550 return NULL;
551 }
552
553 if (!loaded_image->load_options_size || !loaded_image->load_options)
554 return NULL;
555
556 return loaded_image->load_options;
557}
558
559/**
560 * command_loop - process user commands
561 */
562static void command_loop(void)
563{
564 for (;;) {
565 u16 command[BUFFER_SIZE];
566 u16 *pos;
567 efi_uintn_t ret;
568
569 print(u"=> ");
570 ret = efi_input(command, sizeof(command));
571 if (ret == EFI_ABORTED)
572 break;
573 pos = skip_whitespace(command);
574 if (starts_with(pos, u"exit")) {
575 break;
576 } else if (starts_with(pos, u"check")) {
577 ret = do_check();
578 if (ret == EFI_SUCCESS)
579 print(u"OK\n");
580 } else if (starts_with(pos, u"save ")) {
581 do_save(pos + 5);
582 } else {
583 do_help();
584 }
585 }
586}
587
588/**
589 * efi_main() - entry point of the EFI application.
590 *
591 * @handle: handle of the loaded image
592 * @systab: system table
593 * Return: status code
594 */
595efi_status_t EFIAPI efi_main(efi_handle_t image_handle,
596 struct efi_system_table *systab)
597{
598 u16 *load_options;
599
600 handle = image_handle;
601 systable = systab;
602 cerr = systable->std_err;
603 cout = systable->con_out;
604 cin = systable->con_in;
605 bs = systable->boottime;
606 load_options = get_load_options();
607
608 if (starts_with(load_options, u"nocolor"))
609 nocolor = true;
610
611 color(EFI_WHITE);
612 cls();
613 print(u"SMBIOS Dump\r\n===========\r\n\r\n");
614 color(EFI_LIGHTBLUE);
615
616 command_loop();
617
618 color(EFI_LIGHTGRAY);
619 cls();
620
621 return EFI_SUCCESS;
622}