1230557Sjimharris// SPDX-License-Identifier: GPL-2.0+
2230557Sjimharris/*
3230557Sjimharris * Copyright 2020, Heinrich Schuchardt <xypron.glpk@gmx.de>
4230557Sjimharris *
5230557Sjimharris * dtbdump.efi saves the device tree provided as a configuration table
6230557Sjimharris * to a file.
7230557Sjimharris */
8230557Sjimharris
9230557Sjimharris#include <efi_api.h>
10230557Sjimharris#include <efi_dt_fixup.h>
11230557Sjimharris#include <part.h>
12230557Sjimharris#include <linux/libfdt.h>
13230557Sjimharris
14230557Sjimharris#define BUFFER_SIZE 64
15230557Sjimharris#define ESC 0x17
16230557Sjimharris
17230557Sjimharris#define efi_size_in_pages(size) ((size + EFI_PAGE_MASK) >> EFI_PAGE_SHIFT)
18230557Sjimharris
19230557Sjimharrisstatic struct efi_simple_text_output_protocol *cerr;
20230557Sjimharrisstatic struct efi_simple_text_output_protocol *cout;
21230557Sjimharrisstatic struct efi_simple_text_input_protocol *cin;
22230557Sjimharrisstatic struct efi_boot_services *bs;
23230557Sjimharrisstatic const efi_guid_t fdt_guid = EFI_FDT_GUID;
24230557Sjimharrisstatic const efi_guid_t loaded_image_guid = EFI_LOADED_IMAGE_PROTOCOL_GUID;
25230557Sjimharrisstatic const efi_guid_t guid_simple_file_system_protocol =
26230557Sjimharris					EFI_SIMPLE_FILE_SYSTEM_PROTOCOL_GUID;
27230557Sjimharrisstatic efi_handle_t handle;
28230557Sjimharrisstatic struct efi_system_table *systable;
29230557Sjimharrisstatic const efi_guid_t efi_dt_fixup_protocol_guid = EFI_DT_FIXUP_PROTOCOL_GUID;
30230557Sjimharrisstatic const efi_guid_t efi_file_info_guid = EFI_FILE_INFO_GUID;
31230557Sjimharrisstatic const efi_guid_t efi_system_partition_guid = PARTITION_SYSTEM_GUID;
32230557Sjimharris
33230557Sjimharris/**
34230557Sjimharris * print() - print string
35230557Sjimharris *
36230557Sjimharris * @string:	text
37230557Sjimharris */
38230557Sjimharrisstatic void print(u16 *string)
39230557Sjimharris{
40230557Sjimharris	cout->output_string(cout, string);
41230557Sjimharris}
42230557Sjimharris
43230557Sjimharris/**
44230557Sjimharris * error() - print error string
45230557Sjimharris *
46230557Sjimharris * @string:	error text
47230557Sjimharris */
48230557Sjimharrisstatic void error(u16 *string)
49230557Sjimharris{
50230557Sjimharris	cout->set_attribute(cout, EFI_LIGHTRED | EFI_BACKGROUND_BLACK);
51230557Sjimharris	print(string);
52230557Sjimharris	cout->set_attribute(cout, EFI_LIGHTBLUE | EFI_BACKGROUND_BLACK);
53230557Sjimharris}
54230557Sjimharris
55230557Sjimharris/**
56230557Sjimharris * efi_input_yn() - get answer to yes/no question
57230557Sjimharris *
58230557Sjimharris * Return:
59230557Sjimharris * y or Y
60230557Sjimharris *     EFI_SUCCESS
61230557Sjimharris * n or N
62230557Sjimharris *     EFI_ACCESS_DENIED
63230557Sjimharris * ESC
64230557Sjimharris *     EFI_ABORTED
65230557Sjimharris */
66230557Sjimharrisstatic efi_status_t efi_input_yn(void)
67230557Sjimharris{
68230557Sjimharris	struct efi_input_key key = {0};
69230557Sjimharris	efi_uintn_t index;
70230557Sjimharris	efi_status_t ret;
71230557Sjimharris
72230557Sjimharris	/* Drain the console input */
73230557Sjimharris	ret = cin->reset(cin, true);
74230557Sjimharris	for (;;) {
75230557Sjimharris		ret = bs->wait_for_event(1, &cin->wait_for_key, &index);
76230557Sjimharris		if (ret != EFI_SUCCESS)
77230557Sjimharris			continue;
78230557Sjimharris		ret = cin->read_key_stroke(cin, &key);
79230557Sjimharris		if (ret != EFI_SUCCESS)
80230557Sjimharris			continue;
81230557Sjimharris		switch (key.scan_code) {
82230557Sjimharris		case 0x17: /* Escape */
83230557Sjimharris			return EFI_ABORTED;
84230557Sjimharris		default:
85230557Sjimharris			break;
86230557Sjimharris		}
87230557Sjimharris		/* Convert to lower case */
88230557Sjimharris		switch (key.unicode_char | 0x20) {
89230557Sjimharris		case 'y':
90230557Sjimharris			return EFI_SUCCESS;
91230557Sjimharris		case 'n':
92230557Sjimharris			return EFI_ACCESS_DENIED;
93230557Sjimharris		default:
94230557Sjimharris			break;
95230557Sjimharris		}
96230557Sjimharris	}
97230557Sjimharris}
98230557Sjimharris
99230557Sjimharris/**
100230557Sjimharris * efi_input() - read string from console
101230557Sjimharris *
102230557Sjimharris * @buffer:		input buffer
103230557Sjimharris * @buffer_size:	buffer size
104230557Sjimharris * Return:		status code
105230557Sjimharris */
106230557Sjimharrisstatic efi_status_t efi_input(u16 *buffer, efi_uintn_t buffer_size)
107230557Sjimharris{
108230557Sjimharris	struct efi_input_key key = {0};
109230557Sjimharris	efi_uintn_t index;
110230557Sjimharris	efi_uintn_t pos = 0;
111230557Sjimharris	u16 outbuf[2] = u" ";
112230557Sjimharris	efi_status_t ret;
113230557Sjimharris
114230557Sjimharris	/* Drain the console input */
115230557Sjimharris	ret = cin->reset(cin, true);
116230557Sjimharris	*buffer = 0;
117230557Sjimharris	for (;;) {
118230557Sjimharris		ret = bs->wait_for_event(1, &cin->wait_for_key, &index);
119230557Sjimharris		if (ret != EFI_SUCCESS)
120230557Sjimharris			continue;
121230557Sjimharris		ret = cin->read_key_stroke(cin, &key);
122230557Sjimharris		if (ret != EFI_SUCCESS)
123230557Sjimharris			continue;
124230557Sjimharris		switch (key.scan_code) {
125230557Sjimharris		case 0x17: /* Escape */
126230557Sjimharris			print(u"\r\nAborted\r\n");
127230557Sjimharris			return EFI_ABORTED;
128230557Sjimharris		default:
129230557Sjimharris			break;
130230557Sjimharris		}
131230557Sjimharris		switch (key.unicode_char) {
132230557Sjimharris		case 0x08: /* Backspace */
133230557Sjimharris			if (pos) {
134230557Sjimharris				buffer[pos--] = 0;
135230557Sjimharris				print(u"\b \b");
136230557Sjimharris			}
137230557Sjimharris			break;
138230557Sjimharris		case 0x0a: /* Linefeed */
139230557Sjimharris		case 0x0d: /* Carriage return */
140230557Sjimharris			print(u"\r\n");
141230557Sjimharris			return EFI_SUCCESS;
142230557Sjimharris		default:
143230557Sjimharris			break;
144230557Sjimharris		}
145230557Sjimharris		/* Ignore surrogate codes */
146230557Sjimharris		if (key.unicode_char >= 0xD800 && key.unicode_char <= 0xDBFF)
147			continue;
148		if (key.unicode_char >= 0x20 &&
149		    pos < buffer_size - 1) {
150			*outbuf = key.unicode_char;
151			buffer[pos++] = key.unicode_char;
152			buffer[pos] = 0;
153			print(outbuf);
154		}
155	}
156}
157
158/*
159 * Convert FDT value to host endianness.
160 *
161 * @val		FDT value
162 * Return:	converted value
163 */
164static u32 f2h(fdt32_t val)
165{
166	char *buf = (char *)&val;
167	char i;
168
169	/* Swap the bytes */
170	i = buf[0]; buf[0] = buf[3]; buf[3] = i;
171	i = buf[1]; buf[1] = buf[2]; buf[2] = i;
172	return *(u32 *)buf;
173}
174
175/**
176 * get_dtb() - get device tree
177 *
178 * @systable:	system table
179 * Return:	device tree or NULL
180 */
181void *get_dtb(struct efi_system_table *systable)
182{
183	void *dtb = NULL;
184	efi_uintn_t i;
185
186	for (i = 0; i < systable->nr_tables; ++i) {
187		if (!memcmp(&systable->tables[i].guid, &fdt_guid,
188			    sizeof(efi_guid_t))) {
189			dtb = systable->tables[i].table;
190			break;
191		}
192	}
193	return dtb;
194}
195
196/**
197 * skip_whitespace() - skip over leading whitespace
198 *
199 * @pos:	UTF-16 string
200 * Return:	pointer to first non-whitespace
201 */
202u16 *skip_whitespace(u16 *pos)
203{
204	for (; *pos && *pos <= 0x20; ++pos)
205		;
206	return pos;
207}
208
209/**
210 * starts_with() - check if @string starts with @keyword
211 *
212 * @string:	string to search for keyword
213 * @keyword:	keyword to be searched
214 * Return:	true fi @string starts with the keyword
215 */
216bool starts_with(u16 *string, u16 *keyword)
217{
218	for (; *keyword; ++string, ++keyword) {
219		if (*string != *keyword)
220			return false;
221	}
222	return true;
223}
224
225/**
226 * do_help() - print help
227 */
228void do_help(void)
229{
230	error(u"load <dtb> - load device-tree from file\r\n");
231	error(u"save <dtb> - save device-tree to file\r\n");
232	error(u"exit       - exit the shell\r\n");
233}
234
235/**
236 * open_file_system() - open simple file system protocol
237 *
238 * file_system:	interface of the simple file system protocol
239 * Return:	status code
240 */
241static efi_status_t
242open_file_system(struct efi_simple_file_system_protocol **file_system)
243{
244	struct efi_loaded_image *loaded_image;
245	efi_status_t ret;
246	efi_handle_t *handle_buffer = NULL;
247	efi_uintn_t count;
248
249	ret = bs->open_protocol(handle, &loaded_image_guid,
250				(void **)&loaded_image, NULL, NULL,
251				EFI_OPEN_PROTOCOL_GET_PROTOCOL);
252	if (ret != EFI_SUCCESS) {
253		error(u"Loaded image protocol not found\r\n");
254		return ret;
255	}
256
257	/* Open the simple file system protocol on the same partition */
258	ret = bs->open_protocol(loaded_image->device_handle,
259				&guid_simple_file_system_protocol,
260				(void **)file_system, NULL, NULL,
261				EFI_OPEN_PROTOCOL_GET_PROTOCOL);
262	if (ret == EFI_SUCCESS)
263		return ret;
264
265	/* Open the simple file system protocol on the UEFI system partition */
266	ret = bs->locate_handle_buffer(BY_PROTOCOL, &efi_system_partition_guid,
267				       NULL, &count, &handle_buffer);
268	if (ret == EFI_SUCCESS && handle_buffer)
269		ret = bs->open_protocol(handle_buffer[0],
270					&guid_simple_file_system_protocol,
271					(void **)file_system, NULL, NULL,
272					EFI_OPEN_PROTOCOL_GET_PROTOCOL);
273	if (ret != EFI_SUCCESS)
274		error(u"Failed to open simple file system protocol\r\n");
275	if (handle)
276		bs->free_pool(handle_buffer);
277
278	return ret;
279}
280
281/**
282 * do_load() - load and install device-tree
283 *
284 * @filename:	file name
285 * Return:	status code
286 */
287efi_status_t do_load(u16 *filename)
288{
289	struct efi_dt_fixup_protocol *dt_fixup_prot;
290	struct efi_simple_file_system_protocol *file_system;
291	struct efi_file_handle *root = NULL, *file = NULL;
292	u64 addr = 0;
293	struct efi_file_info *info;
294	struct fdt_header *dtb;
295	efi_uintn_t buffer_size;
296	efi_uintn_t pages;
297	efi_status_t ret, ret2;
298
299	ret = bs->locate_protocol(&efi_dt_fixup_protocol_guid, NULL,
300				  (void **)&dt_fixup_prot);
301	if (ret != EFI_SUCCESS) {
302		error(u"Device-tree fix-up protocol not found\r\n");
303		return ret;
304	}
305
306	filename = skip_whitespace(filename);
307
308	ret = open_file_system(&file_system);
309	if (ret != EFI_SUCCESS)
310		goto out;
311
312	/* Open volume */
313	ret = file_system->open_volume(file_system, &root);
314	if (ret != EFI_SUCCESS) {
315		error(u"Failed to open volume\r\n");
316		goto out;
317	}
318
319	/* Open file */
320	ret = root->open(root, &file, filename, EFI_FILE_MODE_READ, 0);
321	if (ret != EFI_SUCCESS) {
322		error(u"File not found\r\n");
323		goto out;
324	}
325	/* Get file size */
326	buffer_size = 0;
327	ret = file->getinfo(file, &efi_file_info_guid, &buffer_size, NULL);
328	if (ret != EFI_BUFFER_TOO_SMALL) {
329		error(u"Can't get file info size\r\n");
330		goto out;
331	}
332	ret = bs->allocate_pool(EFI_LOADER_DATA, buffer_size, (void **)&info);
333	if (ret != EFI_SUCCESS) {
334		error(u"Out of memory\r\n");
335		goto out;
336	}
337	ret = file->getinfo(file, &efi_file_info_guid, &buffer_size, info);
338	if (ret != EFI_SUCCESS) {
339		error(u"Can't get file info\r\n");
340		goto out;
341	}
342	buffer_size = info->file_size;
343	pages = efi_size_in_pages(buffer_size);
344	ret = bs->free_pool(info);
345	if (ret != EFI_SUCCESS)
346		error(u"Can't free memory pool\r\n");
347	/* Read file */
348	ret = bs->allocate_pages(EFI_ALLOCATE_ANY_PAGES,
349				 EFI_ACPI_RECLAIM_MEMORY,
350				 pages, &addr);
351	if (ret != EFI_SUCCESS) {
352		error(u"Out of memory\r\n");
353		goto out;
354	}
355	dtb = (struct fdt_header *)(uintptr_t)addr;
356	ret = file->read(file, &buffer_size, dtb);
357	if (ret != EFI_SUCCESS) {
358		error(u"Can't read file\r\n");
359		goto out;
360	}
361	/* Fixup file, expecting EFI_BUFFER_TOO_SMALL */
362	ret = dt_fixup_prot->fixup(dt_fixup_prot, dtb, &buffer_size,
363				   EFI_DT_APPLY_FIXUPS | EFI_DT_RESERVE_MEMORY |
364				   EFI_DT_INSTALL_TABLE);
365	if (ret == EFI_BUFFER_TOO_SMALL) {
366		/* Read file into larger buffer */
367		ret = bs->free_pages(addr, pages);
368		if (ret != EFI_SUCCESS)
369			error(u"Can't free memory pages\r\n");
370		pages = efi_size_in_pages(buffer_size);
371		ret = bs->allocate_pages(EFI_ALLOCATE_ANY_PAGES,
372					 EFI_ACPI_RECLAIM_MEMORY,
373					 pages, &addr);
374		if (ret != EFI_SUCCESS) {
375			error(u"Out of memory\r\n");
376			goto out;
377		}
378		dtb = (struct fdt_header *)(uintptr_t)addr;
379		ret = file->setpos(file, 0);
380		if (ret != EFI_SUCCESS) {
381			error(u"Can't position file\r\n");
382			goto out;
383		}
384		ret = file->read(file, &buffer_size, dtb);
385		if (ret != EFI_SUCCESS) {
386			error(u"Can't read file\r\n");
387			goto out;
388		}
389		buffer_size = pages << EFI_PAGE_SHIFT;
390		ret = dt_fixup_prot->fixup(
391				dt_fixup_prot, dtb, &buffer_size,
392				EFI_DT_APPLY_FIXUPS | EFI_DT_RESERVE_MEMORY |
393				EFI_DT_INSTALL_TABLE);
394	}
395	if (ret == EFI_SUCCESS)
396		print(u"device-tree installed\r\n");
397	else
398		error(u"Device-tree fix-up failed\r\n");
399out:
400	if (addr) {
401		ret2 = bs->free_pages(addr, pages);
402		if (ret2 != EFI_SUCCESS)
403			error(u"Can't free memory pages\r\n");
404	}
405	if (file) {
406		ret2 = file->close(file);
407		if (ret2 != EFI_SUCCESS)
408			error(u"Can't close file\r\n");
409	}
410	if (root) {
411		ret2 = root->close(root);
412		if (ret2 != EFI_SUCCESS)
413			error(u"Can't close volume\r\n");
414	}
415	return ret;
416}
417
418/**
419 * do_save() - save current device-tree
420 *
421 * @filename:	file name
422 * Return:	status code
423 */
424efi_status_t do_save(u16 *filename)
425{
426	struct efi_simple_file_system_protocol *file_system;
427	efi_uintn_t dtb_size;
428	struct efi_file_handle *root, *file;
429	struct fdt_header *dtb;
430	efi_uintn_t ret;
431
432	dtb = get_dtb(systable);
433	if (!dtb) {
434		error(u"DTB not found\r\n");
435		return EFI_NOT_FOUND;
436	}
437	if (f2h(dtb->magic) != FDT_MAGIC) {
438		error(u"Wrong device tree magic\r\n");
439		return EFI_NOT_FOUND;
440	}
441	dtb_size = f2h(dtb->totalsize);
442
443	filename = skip_whitespace(filename);
444
445	ret = open_file_system(&file_system);
446	if (ret != EFI_SUCCESS)
447		return ret;
448
449	/* Open volume */
450	ret = file_system->open_volume(file_system, &root);
451	if (ret != EFI_SUCCESS) {
452		error(u"Failed to open volume\r\n");
453		return ret;
454	}
455	/* Check if file already exists */
456	ret = root->open(root, &file, filename, EFI_FILE_MODE_READ, 0);
457	if (ret == EFI_SUCCESS) {
458		file->close(file);
459		print(u"Overwrite existing file (y/n)? ");
460		ret = efi_input_yn();
461		print(u"\r\n");
462		if (ret != EFI_SUCCESS) {
463			root->close(root);
464			error(u"Aborted by user\r\n");
465			return ret;
466		}
467	}
468
469	/* Create file */
470	ret = root->open(root, &file, filename,
471			 EFI_FILE_MODE_READ | EFI_FILE_MODE_WRITE |
472			 EFI_FILE_MODE_CREATE, EFI_FILE_ARCHIVE);
473	if (ret == EFI_SUCCESS) {
474		/* Write file */
475		ret = file->write(file, &dtb_size, dtb);
476		if (ret != EFI_SUCCESS)
477			error(u"Failed to write file\r\n");
478		file->close(file);
479	} else {
480		error(u"Failed to open file\r\n");
481	}
482	root->close(root);
483
484	if (ret == EFI_SUCCESS) {
485		print(filename);
486		print(u" written\r\n");
487	}
488
489	return ret;
490}
491
492/**
493 * efi_main() - entry point of the EFI application.
494 *
495 * @handle:	handle of the loaded image
496 * @systab:	system table
497 * Return:	status code
498 */
499efi_status_t EFIAPI efi_main(efi_handle_t image_handle,
500			     struct efi_system_table *systab)
501{
502	handle = image_handle;
503	systable = systab;
504	cerr = systable->std_err;
505	cout = systable->con_out;
506	cin = systable->con_in;
507	bs = systable->boottime;
508
509	cout->set_attribute(cout, EFI_LIGHTBLUE | EFI_BACKGROUND_BLACK);
510	cout->clear_screen(cout);
511	cout->set_attribute(cout, EFI_WHITE | EFI_BACKGROUND_BLACK);
512	print(u"DTB Dump\r\n========\r\n\r\n");
513	cout->set_attribute(cout, EFI_LIGHTBLUE | EFI_BACKGROUND_BLACK);
514
515	for (;;) {
516		u16 command[BUFFER_SIZE];
517		u16 *pos;
518		efi_uintn_t ret;
519
520		print(u"=> ");
521		ret = efi_input(command, sizeof(command));
522		if (ret == EFI_ABORTED)
523			break;
524		pos = skip_whitespace(command);
525		if (starts_with(pos, u"exit"))
526			break;
527		else if (starts_with(pos, u"load "))
528			do_load(pos + 5);
529		else if (starts_with(pos, u"save "))
530			do_save(pos + 5);
531		else
532			do_help();
533	}
534
535	cout->set_attribute(cout, EFI_LIGHTGRAY | EFI_BACKGROUND_BLACK);
536	cout->clear_screen(cout);
537	return EFI_SUCCESS;
538}
539