15193a33dSArd Biesheuvel // SPDX-License-Identifier: GPL-2.0
25193a33dSArd Biesheuvel /*
35193a33dSArd Biesheuvel  * Helper functions used by the EFI stub on multiple
45193a33dSArd Biesheuvel  * architectures. This should be #included by the EFI stub
55193a33dSArd Biesheuvel  * implementation files.
65193a33dSArd Biesheuvel  *
75193a33dSArd Biesheuvel  * Copyright 2011 Intel Corporation; author Matt Fleming
85193a33dSArd Biesheuvel  */
95193a33dSArd Biesheuvel 
105193a33dSArd Biesheuvel #include <linux/efi.h>
115193a33dSArd Biesheuvel #include <asm/efi.h>
125193a33dSArd Biesheuvel 
135193a33dSArd Biesheuvel #include "efistub.h"
145193a33dSArd Biesheuvel 
159302c1bbSArd Biesheuvel #define MAX_FILENAME_SIZE	256
169302c1bbSArd Biesheuvel 
175193a33dSArd Biesheuvel /*
185193a33dSArd Biesheuvel  * Some firmware implementations have problems reading files in one go.
195193a33dSArd Biesheuvel  * A read chunk size of 1MB seems to work for most platforms.
205193a33dSArd Biesheuvel  *
215193a33dSArd Biesheuvel  * Unfortunately, reading files in chunks triggers *other* bugs on some
225193a33dSArd Biesheuvel  * platforms, so we provide a way to disable this workaround, which can
235193a33dSArd Biesheuvel  * be done by passing "efi=nochunk" on the EFI boot stub command line.
245193a33dSArd Biesheuvel  *
255193a33dSArd Biesheuvel  * If you experience issues with initrd images being corrupt it's worth
265193a33dSArd Biesheuvel  * trying efi=nochunk, but chunking is enabled by default on x86 because
275193a33dSArd Biesheuvel  * there are far more machines that require the workaround than those that
285193a33dSArd Biesheuvel  * break with it enabled.
295193a33dSArd Biesheuvel  */
305193a33dSArd Biesheuvel #define EFI_READ_CHUNK_SIZE	SZ_1M
315193a33dSArd Biesheuvel 
329302c1bbSArd Biesheuvel static efi_status_t efi_open_file(efi_file_protocol_t *volume,
339302c1bbSArd Biesheuvel 				  efi_char16_t *filename_16,
349302c1bbSArd Biesheuvel 				  efi_file_protocol_t **handle,
359302c1bbSArd Biesheuvel 				  unsigned long *file_size)
365193a33dSArd Biesheuvel {
379302c1bbSArd Biesheuvel 	struct {
389302c1bbSArd Biesheuvel 		efi_file_info_t info;
399302c1bbSArd Biesheuvel 		efi_char16_t	filename[MAX_FILENAME_SIZE];
409302c1bbSArd Biesheuvel 	} finfo;
415193a33dSArd Biesheuvel 	efi_guid_t info_guid = EFI_FILE_INFO_ID;
429302c1bbSArd Biesheuvel 	efi_file_protocol_t *fh;
435193a33dSArd Biesheuvel 	unsigned long info_sz;
449302c1bbSArd Biesheuvel 	efi_status_t status;
455193a33dSArd Biesheuvel 
469302c1bbSArd Biesheuvel 	status = volume->open(volume, &fh, filename_16, EFI_FILE_MODE_READ, 0);
475193a33dSArd Biesheuvel 	if (status != EFI_SUCCESS) {
489302c1bbSArd Biesheuvel 		pr_efi_err("Failed to open file: ");
495193a33dSArd Biesheuvel 		efi_char16_printk(filename_16);
505193a33dSArd Biesheuvel 		efi_printk("\n");
515193a33dSArd Biesheuvel 		return status;
525193a33dSArd Biesheuvel 	}
535193a33dSArd Biesheuvel 
549302c1bbSArd Biesheuvel 	info_sz = sizeof(finfo);
559302c1bbSArd Biesheuvel 	status = fh->get_info(fh, &info_guid, &info_sz, &finfo);
565193a33dSArd Biesheuvel 	if (status != EFI_SUCCESS) {
579302c1bbSArd Biesheuvel 		pr_efi_err("Failed to get file info\n");
589302c1bbSArd Biesheuvel 		fh->close(fh);
595193a33dSArd Biesheuvel 		return status;
605193a33dSArd Biesheuvel 	}
615193a33dSArd Biesheuvel 
629302c1bbSArd Biesheuvel 	*handle = fh;
639302c1bbSArd Biesheuvel 	*file_size = finfo.info.file_size;
649302c1bbSArd Biesheuvel 	return EFI_SUCCESS;
655193a33dSArd Biesheuvel }
665193a33dSArd Biesheuvel 
675193a33dSArd Biesheuvel static efi_status_t efi_open_volume(efi_loaded_image_t *image,
689302c1bbSArd Biesheuvel 				    efi_file_protocol_t **fh)
695193a33dSArd Biesheuvel {
705193a33dSArd Biesheuvel 	efi_guid_t fs_proto = EFI_FILE_SYSTEM_GUID;
719302c1bbSArd Biesheuvel 	efi_simple_file_system_protocol_t *io;
725193a33dSArd Biesheuvel 	efi_status_t status;
735193a33dSArd Biesheuvel 
749302c1bbSArd Biesheuvel 	status = efi_bs_call(handle_protocol, image->device_handle, &fs_proto,
759302c1bbSArd Biesheuvel 			     (void **)&io);
765193a33dSArd Biesheuvel 	if (status != EFI_SUCCESS) {
779302c1bbSArd Biesheuvel 		pr_efi_err("Failed to handle fs_proto\n");
785193a33dSArd Biesheuvel 		return status;
795193a33dSArd Biesheuvel 	}
805193a33dSArd Biesheuvel 
819302c1bbSArd Biesheuvel 	status = io->open_volume(io, fh);
825193a33dSArd Biesheuvel 	if (status != EFI_SUCCESS)
839302c1bbSArd Biesheuvel 		pr_efi_err("Failed to open volume\n");
845193a33dSArd Biesheuvel 
855193a33dSArd Biesheuvel 	return status;
865193a33dSArd Biesheuvel }
875193a33dSArd Biesheuvel 
889302c1bbSArd Biesheuvel static int find_file_option(const efi_char16_t *cmdline, int cmdline_len,
899302c1bbSArd Biesheuvel 			    const efi_char16_t *prefix, int prefix_size,
909302c1bbSArd Biesheuvel 			    efi_char16_t *result, int result_len)
919302c1bbSArd Biesheuvel {
929302c1bbSArd Biesheuvel 	int prefix_len = prefix_size / 2;
939302c1bbSArd Biesheuvel 	bool found = false;
949302c1bbSArd Biesheuvel 	int i;
959302c1bbSArd Biesheuvel 
969302c1bbSArd Biesheuvel 	for (i = prefix_len; i < cmdline_len; i++) {
979302c1bbSArd Biesheuvel 		if (!memcmp(&cmdline[i - prefix_len], prefix, prefix_size)) {
989302c1bbSArd Biesheuvel 			found = true;
999302c1bbSArd Biesheuvel 			break;
1009302c1bbSArd Biesheuvel 		}
1019302c1bbSArd Biesheuvel 	}
1029302c1bbSArd Biesheuvel 
1039302c1bbSArd Biesheuvel 	if (!found)
1049302c1bbSArd Biesheuvel 		return 0;
1059302c1bbSArd Biesheuvel 
1069302c1bbSArd Biesheuvel 	while (--result_len > 0 && i < cmdline_len) {
1079302c1bbSArd Biesheuvel 		if (cmdline[i] == L'\0' ||
1089302c1bbSArd Biesheuvel 		    cmdline[i] == L'\n' ||
1099302c1bbSArd Biesheuvel 		    cmdline[i] == L' ')
1109302c1bbSArd Biesheuvel 			break;
1119302c1bbSArd Biesheuvel 		*result++ = cmdline[i++];
1129302c1bbSArd Biesheuvel 	}
1139302c1bbSArd Biesheuvel 	*result = L'\0';
1149302c1bbSArd Biesheuvel 	return i;
1159302c1bbSArd Biesheuvel }
1169302c1bbSArd Biesheuvel 
1175193a33dSArd Biesheuvel /*
1185193a33dSArd Biesheuvel  * Check the cmdline for a LILO-style file= arguments.
1195193a33dSArd Biesheuvel  *
1205193a33dSArd Biesheuvel  * We only support loading a file from the same filesystem as
1215193a33dSArd Biesheuvel  * the kernel image.
1225193a33dSArd Biesheuvel  */
1239302c1bbSArd Biesheuvel static efi_status_t handle_cmdline_files(efi_loaded_image_t *image,
1249302c1bbSArd Biesheuvel 					 const efi_char16_t *optstr,
1259302c1bbSArd Biesheuvel 					 int optstr_size,
12631f5e546SArd Biesheuvel 					 unsigned long soft_limit,
12731f5e546SArd Biesheuvel 					 unsigned long hard_limit,
1285193a33dSArd Biesheuvel 					 unsigned long *load_addr,
1295193a33dSArd Biesheuvel 					 unsigned long *load_size)
1305193a33dSArd Biesheuvel {
1319302c1bbSArd Biesheuvel 	const efi_char16_t *cmdline = image->load_options;
1329302c1bbSArd Biesheuvel 	int cmdline_len = image->load_options_size / 2;
1335193a33dSArd Biesheuvel 	unsigned long efi_chunk_size = ULONG_MAX;
1349302c1bbSArd Biesheuvel 	efi_file_protocol_t *volume = NULL;
1359302c1bbSArd Biesheuvel 	efi_file_protocol_t *file;
1369302c1bbSArd Biesheuvel 	unsigned long alloc_addr;
1379302c1bbSArd Biesheuvel 	unsigned long alloc_size;
1385193a33dSArd Biesheuvel 	efi_status_t status;
1399302c1bbSArd Biesheuvel 	int offset;
1405193a33dSArd Biesheuvel 
1415193a33dSArd Biesheuvel 	if (!load_addr || !load_size)
1425193a33dSArd Biesheuvel 		return EFI_INVALID_PARAMETER;
1435193a33dSArd Biesheuvel 
1449302c1bbSArd Biesheuvel 	if (IS_ENABLED(CONFIG_X86) && !nochunk())
1459302c1bbSArd Biesheuvel 		efi_chunk_size = EFI_READ_CHUNK_SIZE;
1465193a33dSArd Biesheuvel 
1479302c1bbSArd Biesheuvel 	alloc_addr = alloc_size = 0;
1489302c1bbSArd Biesheuvel 	do {
1499302c1bbSArd Biesheuvel 		efi_char16_t filename[MAX_FILENAME_SIZE];
1509302c1bbSArd Biesheuvel 		unsigned long size;
1519302c1bbSArd Biesheuvel 		void *addr;
1525193a33dSArd Biesheuvel 
1539302c1bbSArd Biesheuvel 		offset = find_file_option(cmdline, cmdline_len,
1549302c1bbSArd Biesheuvel 					  optstr, optstr_size,
1559302c1bbSArd Biesheuvel 					  filename, ARRAY_SIZE(filename));
1569302c1bbSArd Biesheuvel 
1579302c1bbSArd Biesheuvel 		if (!offset)
1585193a33dSArd Biesheuvel 			break;
1595193a33dSArd Biesheuvel 
1609302c1bbSArd Biesheuvel 		cmdline += offset;
1619302c1bbSArd Biesheuvel 		cmdline_len -= offset;
1625193a33dSArd Biesheuvel 
1639302c1bbSArd Biesheuvel 		if (!volume) {
1649302c1bbSArd Biesheuvel 			status = efi_open_volume(image, &volume);
1655193a33dSArd Biesheuvel 			if (status != EFI_SUCCESS)
1669302c1bbSArd Biesheuvel 				return status;
1675193a33dSArd Biesheuvel 		}
1685193a33dSArd Biesheuvel 
1699302c1bbSArd Biesheuvel 		status = efi_open_file(volume, filename, &file, &size);
1705193a33dSArd Biesheuvel 		if (status != EFI_SUCCESS)
1719302c1bbSArd Biesheuvel 			goto err_close_volume;
1725193a33dSArd Biesheuvel 
1735193a33dSArd Biesheuvel 		/*
1749302c1bbSArd Biesheuvel 		 * Check whether the existing allocation can contain the next
1759302c1bbSArd Biesheuvel 		 * file. This condition will also trigger naturally during the
1769302c1bbSArd Biesheuvel 		 * first (and typically only) iteration of the loop, given that
1779302c1bbSArd Biesheuvel 		 * alloc_size == 0 in that case.
1785193a33dSArd Biesheuvel 		 */
1799302c1bbSArd Biesheuvel 		if (round_up(alloc_size + size, EFI_ALLOC_ALIGN) >
1809302c1bbSArd Biesheuvel 		    round_up(alloc_size, EFI_ALLOC_ALIGN)) {
1819302c1bbSArd Biesheuvel 			unsigned long old_addr = alloc_addr;
1829302c1bbSArd Biesheuvel 
18331f5e546SArd Biesheuvel 			status = EFI_OUT_OF_RESOURCES;
18431f5e546SArd Biesheuvel 			if (soft_limit < hard_limit)
18531f5e546SArd Biesheuvel 				status = efi_allocate_pages(alloc_size + size,
18631f5e546SArd Biesheuvel 							    &alloc_addr,
18731f5e546SArd Biesheuvel 							    soft_limit);
18831f5e546SArd Biesheuvel 			if (status == EFI_OUT_OF_RESOURCES)
18931f5e546SArd Biesheuvel 				status = efi_allocate_pages(alloc_size + size,
19031f5e546SArd Biesheuvel 							    &alloc_addr,
19131f5e546SArd Biesheuvel 							    hard_limit);
1925193a33dSArd Biesheuvel 			if (status != EFI_SUCCESS) {
1939302c1bbSArd Biesheuvel 				pr_efi_err("Failed to reallocate memory for files\n");
1949302c1bbSArd Biesheuvel 				goto err_close_file;
1955193a33dSArd Biesheuvel 			}
1965193a33dSArd Biesheuvel 
1979302c1bbSArd Biesheuvel 			if (old_addr != 0) {
1989302c1bbSArd Biesheuvel 				/*
1999302c1bbSArd Biesheuvel 				 * This is not the first time we've gone
2009302c1bbSArd Biesheuvel 				 * around this loop, and so we are loading
2019302c1bbSArd Biesheuvel 				 * multiple files that need to be concatenated
2029302c1bbSArd Biesheuvel 				 * and returned in a single buffer.
2039302c1bbSArd Biesheuvel 				 */
2049302c1bbSArd Biesheuvel 				memcpy((void *)alloc_addr, (void *)old_addr, alloc_size);
2059302c1bbSArd Biesheuvel 				efi_free(alloc_size, old_addr);
2069302c1bbSArd Biesheuvel 			}
2075193a33dSArd Biesheuvel 		}
2085193a33dSArd Biesheuvel 
2099302c1bbSArd Biesheuvel 		addr = (void *)alloc_addr + alloc_size;
2109302c1bbSArd Biesheuvel 		alloc_size += size;
2115193a33dSArd Biesheuvel 
2125193a33dSArd Biesheuvel 		while (size) {
2139302c1bbSArd Biesheuvel 			unsigned long chunksize = min(size, efi_chunk_size);
2145193a33dSArd Biesheuvel 
2159302c1bbSArd Biesheuvel 			status = file->read(file, &chunksize, addr);
2165193a33dSArd Biesheuvel 			if (status != EFI_SUCCESS) {
2175193a33dSArd Biesheuvel 				pr_efi_err("Failed to read file\n");
2189302c1bbSArd Biesheuvel 				goto err_close_file;
2195193a33dSArd Biesheuvel 			}
2205193a33dSArd Biesheuvel 			addr += chunksize;
2215193a33dSArd Biesheuvel 			size -= chunksize;
2225193a33dSArd Biesheuvel 		}
2239302c1bbSArd Biesheuvel 		file->close(file);
2249302c1bbSArd Biesheuvel 	} while (offset > 0);
2255193a33dSArd Biesheuvel 
2269302c1bbSArd Biesheuvel 	*load_addr = alloc_addr;
2279302c1bbSArd Biesheuvel 	*load_size = alloc_size;
2289302c1bbSArd Biesheuvel 
2299302c1bbSArd Biesheuvel 	if (volume)
2309302c1bbSArd Biesheuvel 		volume->close(volume);
2319302c1bbSArd Biesheuvel 	return EFI_SUCCESS;
2329302c1bbSArd Biesheuvel 
2339302c1bbSArd Biesheuvel err_close_file:
2349302c1bbSArd Biesheuvel 	file->close(file);
2359302c1bbSArd Biesheuvel 
2369302c1bbSArd Biesheuvel err_close_volume:
2379302c1bbSArd Biesheuvel 	volume->close(volume);
2389302c1bbSArd Biesheuvel 	efi_free(alloc_size, alloc_addr);
2399302c1bbSArd Biesheuvel 	return status;
2405193a33dSArd Biesheuvel }
2415193a33dSArd Biesheuvel 
2429302c1bbSArd Biesheuvel efi_status_t efi_load_dtb(efi_loaded_image_t *image,
2439302c1bbSArd Biesheuvel 			  unsigned long *load_addr,
2449302c1bbSArd Biesheuvel 			  unsigned long *load_size)
2459302c1bbSArd Biesheuvel {
2469302c1bbSArd Biesheuvel 	return handle_cmdline_files(image, L"dtb=", sizeof(L"dtb=") - 2,
24731f5e546SArd Biesheuvel 				    ULONG_MAX, ULONG_MAX, load_addr, load_size);
2485193a33dSArd Biesheuvel }
2495193a33dSArd Biesheuvel 
2509302c1bbSArd Biesheuvel efi_status_t efi_load_initrd(efi_loaded_image_t *image,
2519302c1bbSArd Biesheuvel 			     unsigned long *load_addr,
2529302c1bbSArd Biesheuvel 			     unsigned long *load_size,
25331f5e546SArd Biesheuvel 			     unsigned long soft_limit,
25431f5e546SArd Biesheuvel 			     unsigned long hard_limit)
2559302c1bbSArd Biesheuvel {
2569302c1bbSArd Biesheuvel 	return handle_cmdline_files(image, L"initrd=", sizeof(L"initrd=") - 2,
25731f5e546SArd Biesheuvel 				    soft_limit, hard_limit, load_addr, load_size);
2585193a33dSArd Biesheuvel }
259