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 
32464fb126SArd Biesheuvel struct finfo {
33464fb126SArd Biesheuvel 	efi_file_info_t info;
34464fb126SArd Biesheuvel 	efi_char16_t	filename[MAX_FILENAME_SIZE];
35464fb126SArd Biesheuvel };
36464fb126SArd Biesheuvel 
379302c1bbSArd Biesheuvel static efi_status_t efi_open_file(efi_file_protocol_t *volume,
38464fb126SArd Biesheuvel 				  struct finfo *fi,
399302c1bbSArd Biesheuvel 				  efi_file_protocol_t **handle,
409302c1bbSArd Biesheuvel 				  unsigned long *file_size)
415193a33dSArd Biesheuvel {
425193a33dSArd Biesheuvel 	efi_guid_t info_guid = EFI_FILE_INFO_ID;
439302c1bbSArd Biesheuvel 	efi_file_protocol_t *fh;
445193a33dSArd Biesheuvel 	unsigned long info_sz;
459302c1bbSArd Biesheuvel 	efi_status_t status;
465193a33dSArd Biesheuvel 
47464fb126SArd Biesheuvel 	status = volume->open(volume, &fh, fi->filename, EFI_FILE_MODE_READ, 0);
485193a33dSArd Biesheuvel 	if (status != EFI_SUCCESS) {
49793473c2SArvind Sankar 		efi_err("Failed to open file: ");
50464fb126SArd Biesheuvel 		efi_char16_printk(fi->filename);
515193a33dSArd Biesheuvel 		efi_printk("\n");
525193a33dSArd Biesheuvel 		return status;
535193a33dSArd Biesheuvel 	}
545193a33dSArd Biesheuvel 
55464fb126SArd Biesheuvel 	info_sz = sizeof(struct finfo);
56464fb126SArd Biesheuvel 	status = fh->get_info(fh, &info_guid, &info_sz, fi);
575193a33dSArd Biesheuvel 	if (status != EFI_SUCCESS) {
58793473c2SArvind Sankar 		efi_err("Failed to get file info\n");
599302c1bbSArd Biesheuvel 		fh->close(fh);
605193a33dSArd Biesheuvel 		return status;
615193a33dSArd Biesheuvel 	}
625193a33dSArd Biesheuvel 
639302c1bbSArd Biesheuvel 	*handle = fh;
64464fb126SArd Biesheuvel 	*file_size = fi->info.file_size;
659302c1bbSArd Biesheuvel 	return EFI_SUCCESS;
665193a33dSArd Biesheuvel }
675193a33dSArd Biesheuvel 
685193a33dSArd Biesheuvel static efi_status_t efi_open_volume(efi_loaded_image_t *image,
699302c1bbSArd Biesheuvel 				    efi_file_protocol_t **fh)
705193a33dSArd Biesheuvel {
715193a33dSArd Biesheuvel 	efi_guid_t fs_proto = EFI_FILE_SYSTEM_GUID;
729302c1bbSArd Biesheuvel 	efi_simple_file_system_protocol_t *io;
735193a33dSArd Biesheuvel 	efi_status_t status;
745193a33dSArd Biesheuvel 
759302c1bbSArd Biesheuvel 	status = efi_bs_call(handle_protocol, image->device_handle, &fs_proto,
769302c1bbSArd Biesheuvel 			     (void **)&io);
775193a33dSArd Biesheuvel 	if (status != EFI_SUCCESS) {
78793473c2SArvind Sankar 		efi_err("Failed to handle fs_proto\n");
795193a33dSArd Biesheuvel 		return status;
805193a33dSArd Biesheuvel 	}
815193a33dSArd Biesheuvel 
829302c1bbSArd Biesheuvel 	status = io->open_volume(io, fh);
835193a33dSArd Biesheuvel 	if (status != EFI_SUCCESS)
84793473c2SArvind Sankar 		efi_err("Failed to open volume\n");
855193a33dSArd Biesheuvel 
865193a33dSArd Biesheuvel 	return status;
875193a33dSArd Biesheuvel }
885193a33dSArd Biesheuvel 
899302c1bbSArd Biesheuvel static int find_file_option(const efi_char16_t *cmdline, int cmdline_len,
909302c1bbSArd Biesheuvel 			    const efi_char16_t *prefix, int prefix_size,
919302c1bbSArd Biesheuvel 			    efi_char16_t *result, int result_len)
929302c1bbSArd Biesheuvel {
939302c1bbSArd Biesheuvel 	int prefix_len = prefix_size / 2;
949302c1bbSArd Biesheuvel 	bool found = false;
959302c1bbSArd Biesheuvel 	int i;
969302c1bbSArd Biesheuvel 
979302c1bbSArd Biesheuvel 	for (i = prefix_len; i < cmdline_len; i++) {
989302c1bbSArd Biesheuvel 		if (!memcmp(&cmdline[i - prefix_len], prefix, prefix_size)) {
999302c1bbSArd Biesheuvel 			found = true;
1009302c1bbSArd Biesheuvel 			break;
1019302c1bbSArd Biesheuvel 		}
1029302c1bbSArd Biesheuvel 	}
1039302c1bbSArd Biesheuvel 
1049302c1bbSArd Biesheuvel 	if (!found)
1059302c1bbSArd Biesheuvel 		return 0;
1069302c1bbSArd Biesheuvel 
1079302c1bbSArd Biesheuvel 	while (--result_len > 0 && i < cmdline_len) {
1089302c1bbSArd Biesheuvel 		if (cmdline[i] == L'\0' ||
1099302c1bbSArd Biesheuvel 		    cmdline[i] == L'\n' ||
1109302c1bbSArd Biesheuvel 		    cmdline[i] == L' ')
1119302c1bbSArd Biesheuvel 			break;
1129302c1bbSArd Biesheuvel 		*result++ = cmdline[i++];
1139302c1bbSArd Biesheuvel 	}
1149302c1bbSArd Biesheuvel 	*result = L'\0';
1159302c1bbSArd Biesheuvel 	return i;
1169302c1bbSArd Biesheuvel }
1179302c1bbSArd Biesheuvel 
1185193a33dSArd Biesheuvel /*
1195193a33dSArd Biesheuvel  * Check the cmdline for a LILO-style file= arguments.
1205193a33dSArd Biesheuvel  *
1215193a33dSArd Biesheuvel  * We only support loading a file from the same filesystem as
1225193a33dSArd Biesheuvel  * the kernel image.
1235193a33dSArd Biesheuvel  */
124cf6b8366SArd Biesheuvel efi_status_t handle_cmdline_files(efi_loaded_image_t *image,
1259302c1bbSArd Biesheuvel 				  const efi_char16_t *optstr,
1269302c1bbSArd Biesheuvel 				  int optstr_size,
12731f5e546SArd Biesheuvel 				  unsigned long soft_limit,
12831f5e546SArd Biesheuvel 				  unsigned long hard_limit,
1295193a33dSArd Biesheuvel 				  unsigned long *load_addr,
1305193a33dSArd Biesheuvel 				  unsigned long *load_size)
1315193a33dSArd Biesheuvel {
1329302c1bbSArd Biesheuvel 	const efi_char16_t *cmdline = image->load_options;
1339302c1bbSArd Biesheuvel 	int cmdline_len = image->load_options_size / 2;
1345193a33dSArd Biesheuvel 	unsigned long efi_chunk_size = ULONG_MAX;
1359302c1bbSArd Biesheuvel 	efi_file_protocol_t *volume = NULL;
1369302c1bbSArd Biesheuvel 	efi_file_protocol_t *file;
1379302c1bbSArd Biesheuvel 	unsigned long alloc_addr;
1389302c1bbSArd Biesheuvel 	unsigned long alloc_size;
1395193a33dSArd Biesheuvel 	efi_status_t status;
1409302c1bbSArd Biesheuvel 	int offset;
1415193a33dSArd Biesheuvel 
1425193a33dSArd Biesheuvel 	if (!load_addr || !load_size)
1435193a33dSArd Biesheuvel 		return EFI_INVALID_PARAMETER;
1445193a33dSArd Biesheuvel 
145980771f6SArd Biesheuvel 	if (IS_ENABLED(CONFIG_X86) && !efi_nochunk)
1469302c1bbSArd Biesheuvel 		efi_chunk_size = EFI_READ_CHUNK_SIZE;
1475193a33dSArd Biesheuvel 
1489302c1bbSArd Biesheuvel 	alloc_addr = alloc_size = 0;
1499302c1bbSArd Biesheuvel 	do {
150464fb126SArd Biesheuvel 		struct finfo fi;
1519302c1bbSArd Biesheuvel 		unsigned long size;
1529302c1bbSArd Biesheuvel 		void *addr;
1535193a33dSArd Biesheuvel 
1549302c1bbSArd Biesheuvel 		offset = find_file_option(cmdline, cmdline_len,
1559302c1bbSArd Biesheuvel 					  optstr, optstr_size,
156464fb126SArd Biesheuvel 					  fi.filename, ARRAY_SIZE(fi.filename));
1579302c1bbSArd Biesheuvel 
1589302c1bbSArd Biesheuvel 		if (!offset)
1595193a33dSArd Biesheuvel 			break;
1605193a33dSArd Biesheuvel 
1619302c1bbSArd Biesheuvel 		cmdline += offset;
1629302c1bbSArd Biesheuvel 		cmdline_len -= offset;
1635193a33dSArd Biesheuvel 
1649302c1bbSArd Biesheuvel 		if (!volume) {
1659302c1bbSArd Biesheuvel 			status = efi_open_volume(image, &volume);
1665193a33dSArd Biesheuvel 			if (status != EFI_SUCCESS)
1679302c1bbSArd Biesheuvel 				return status;
1685193a33dSArd Biesheuvel 		}
1695193a33dSArd Biesheuvel 
170464fb126SArd Biesheuvel 		status = efi_open_file(volume, &fi, &file, &size);
1715193a33dSArd Biesheuvel 		if (status != EFI_SUCCESS)
1729302c1bbSArd Biesheuvel 			goto err_close_volume;
1735193a33dSArd Biesheuvel 
1745193a33dSArd Biesheuvel 		/*
1759302c1bbSArd Biesheuvel 		 * Check whether the existing allocation can contain the next
1769302c1bbSArd Biesheuvel 		 * file. This condition will also trigger naturally during the
1779302c1bbSArd Biesheuvel 		 * first (and typically only) iteration of the loop, given that
1789302c1bbSArd Biesheuvel 		 * alloc_size == 0 in that case.
1795193a33dSArd Biesheuvel 		 */
1809302c1bbSArd Biesheuvel 		if (round_up(alloc_size + size, EFI_ALLOC_ALIGN) >
1819302c1bbSArd Biesheuvel 		    round_up(alloc_size, EFI_ALLOC_ALIGN)) {
1829302c1bbSArd Biesheuvel 			unsigned long old_addr = alloc_addr;
1839302c1bbSArd Biesheuvel 
18431f5e546SArd Biesheuvel 			status = EFI_OUT_OF_RESOURCES;
18531f5e546SArd Biesheuvel 			if (soft_limit < hard_limit)
18631f5e546SArd Biesheuvel 				status = efi_allocate_pages(alloc_size + size,
18731f5e546SArd Biesheuvel 							    &alloc_addr,
18831f5e546SArd Biesheuvel 							    soft_limit);
18931f5e546SArd Biesheuvel 			if (status == EFI_OUT_OF_RESOURCES)
19031f5e546SArd Biesheuvel 				status = efi_allocate_pages(alloc_size + size,
19131f5e546SArd Biesheuvel 							    &alloc_addr,
19231f5e546SArd Biesheuvel 							    hard_limit);
1935193a33dSArd Biesheuvel 			if (status != EFI_SUCCESS) {
194793473c2SArvind Sankar 				efi_err("Failed to allocate memory for files\n");
1959302c1bbSArd Biesheuvel 				goto err_close_file;
1965193a33dSArd Biesheuvel 			}
1975193a33dSArd Biesheuvel 
1989302c1bbSArd Biesheuvel 			if (old_addr != 0) {
1999302c1bbSArd Biesheuvel 				/*
2009302c1bbSArd Biesheuvel 				 * This is not the first time we've gone
2019302c1bbSArd Biesheuvel 				 * around this loop, and so we are loading
2029302c1bbSArd Biesheuvel 				 * multiple files that need to be concatenated
2039302c1bbSArd Biesheuvel 				 * and returned in a single buffer.
2049302c1bbSArd Biesheuvel 				 */
2059302c1bbSArd Biesheuvel 				memcpy((void *)alloc_addr, (void *)old_addr, alloc_size);
2069302c1bbSArd Biesheuvel 				efi_free(alloc_size, old_addr);
2079302c1bbSArd Biesheuvel 			}
2085193a33dSArd Biesheuvel 		}
2095193a33dSArd Biesheuvel 
2109302c1bbSArd Biesheuvel 		addr = (void *)alloc_addr + alloc_size;
2119302c1bbSArd Biesheuvel 		alloc_size += size;
2125193a33dSArd Biesheuvel 
2135193a33dSArd Biesheuvel 		while (size) {
2149302c1bbSArd Biesheuvel 			unsigned long chunksize = min(size, efi_chunk_size);
2155193a33dSArd Biesheuvel 
2169302c1bbSArd Biesheuvel 			status = file->read(file, &chunksize, addr);
2175193a33dSArd Biesheuvel 			if (status != EFI_SUCCESS) {
218793473c2SArvind Sankar 				efi_err("Failed to read file\n");
2199302c1bbSArd Biesheuvel 				goto err_close_file;
2205193a33dSArd Biesheuvel 			}
2215193a33dSArd Biesheuvel 			addr += chunksize;
2225193a33dSArd Biesheuvel 			size -= chunksize;
2235193a33dSArd Biesheuvel 		}
2249302c1bbSArd Biesheuvel 		file->close(file);
2259302c1bbSArd Biesheuvel 	} while (offset > 0);
2265193a33dSArd Biesheuvel 
2279302c1bbSArd Biesheuvel 	*load_addr = alloc_addr;
2289302c1bbSArd Biesheuvel 	*load_size = alloc_size;
2299302c1bbSArd Biesheuvel 
2309302c1bbSArd Biesheuvel 	if (volume)
2319302c1bbSArd Biesheuvel 		volume->close(volume);
2329302c1bbSArd Biesheuvel 	return EFI_SUCCESS;
2339302c1bbSArd Biesheuvel 
2349302c1bbSArd Biesheuvel err_close_file:
2359302c1bbSArd Biesheuvel 	file->close(file);
2369302c1bbSArd Biesheuvel 
2379302c1bbSArd Biesheuvel err_close_volume:
2389302c1bbSArd Biesheuvel 	volume->close(volume);
2399302c1bbSArd Biesheuvel 	efi_free(alloc_size, alloc_addr);
2409302c1bbSArd Biesheuvel 	return status;
2415193a33dSArd Biesheuvel }
242