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 
155193a33dSArd Biesheuvel /*
165193a33dSArd Biesheuvel  * Some firmware implementations have problems reading files in one go.
175193a33dSArd Biesheuvel  * A read chunk size of 1MB seems to work for most platforms.
185193a33dSArd Biesheuvel  *
195193a33dSArd Biesheuvel  * Unfortunately, reading files in chunks triggers *other* bugs on some
205193a33dSArd Biesheuvel  * platforms, so we provide a way to disable this workaround, which can
215193a33dSArd Biesheuvel  * be done by passing "efi=nochunk" on the EFI boot stub command line.
225193a33dSArd Biesheuvel  *
235193a33dSArd Biesheuvel  * If you experience issues with initrd images being corrupt it's worth
245193a33dSArd Biesheuvel  * trying efi=nochunk, but chunking is enabled by default on x86 because
255193a33dSArd Biesheuvel  * there are far more machines that require the workaround than those that
265193a33dSArd Biesheuvel  * break with it enabled.
275193a33dSArd Biesheuvel  */
285193a33dSArd Biesheuvel #define EFI_READ_CHUNK_SIZE	SZ_1M
295193a33dSArd Biesheuvel 
305193a33dSArd Biesheuvel struct file_info {
315193a33dSArd Biesheuvel 	efi_file_protocol_t *handle;
325193a33dSArd Biesheuvel 	u64 size;
335193a33dSArd Biesheuvel };
345193a33dSArd Biesheuvel 
355193a33dSArd Biesheuvel static efi_status_t efi_file_size(void *__fh, efi_char16_t *filename_16,
365193a33dSArd Biesheuvel 				  void **handle, u64 *file_sz)
375193a33dSArd Biesheuvel {
385193a33dSArd Biesheuvel 	efi_file_protocol_t *h, *fh = __fh;
395193a33dSArd Biesheuvel 	efi_file_info_t *info;
405193a33dSArd Biesheuvel 	efi_status_t status;
415193a33dSArd Biesheuvel 	efi_guid_t info_guid = EFI_FILE_INFO_ID;
425193a33dSArd Biesheuvel 	unsigned long info_sz;
435193a33dSArd Biesheuvel 
445193a33dSArd Biesheuvel 	status = fh->open(fh, &h, filename_16, EFI_FILE_MODE_READ, 0);
455193a33dSArd Biesheuvel 	if (status != EFI_SUCCESS) {
465193a33dSArd Biesheuvel 		efi_printk("Failed to open file: ");
475193a33dSArd Biesheuvel 		efi_char16_printk(filename_16);
485193a33dSArd Biesheuvel 		efi_printk("\n");
495193a33dSArd Biesheuvel 		return status;
505193a33dSArd Biesheuvel 	}
515193a33dSArd Biesheuvel 
525193a33dSArd Biesheuvel 	*handle = h;
535193a33dSArd Biesheuvel 
545193a33dSArd Biesheuvel 	info_sz = 0;
555193a33dSArd Biesheuvel 	status = h->get_info(h, &info_guid, &info_sz, NULL);
565193a33dSArd Biesheuvel 	if (status != EFI_BUFFER_TOO_SMALL) {
575193a33dSArd Biesheuvel 		efi_printk("Failed to get file info size\n");
585193a33dSArd Biesheuvel 		return status;
595193a33dSArd Biesheuvel 	}
605193a33dSArd Biesheuvel 
615193a33dSArd Biesheuvel grow:
625193a33dSArd Biesheuvel 	status = efi_bs_call(allocate_pool, EFI_LOADER_DATA, info_sz,
635193a33dSArd Biesheuvel 			     (void **)&info);
645193a33dSArd Biesheuvel 	if (status != EFI_SUCCESS) {
655193a33dSArd Biesheuvel 		efi_printk("Failed to alloc mem for file info\n");
665193a33dSArd Biesheuvel 		return status;
675193a33dSArd Biesheuvel 	}
685193a33dSArd Biesheuvel 
695193a33dSArd Biesheuvel 	status = h->get_info(h, &info_guid, &info_sz, info);
705193a33dSArd Biesheuvel 	if (status == EFI_BUFFER_TOO_SMALL) {
715193a33dSArd Biesheuvel 		efi_bs_call(free_pool, info);
725193a33dSArd Biesheuvel 		goto grow;
735193a33dSArd Biesheuvel 	}
745193a33dSArd Biesheuvel 
755193a33dSArd Biesheuvel 	*file_sz = info->file_size;
765193a33dSArd Biesheuvel 	efi_bs_call(free_pool, info);
775193a33dSArd Biesheuvel 
785193a33dSArd Biesheuvel 	if (status != EFI_SUCCESS)
795193a33dSArd Biesheuvel 		efi_printk("Failed to get initrd info\n");
805193a33dSArd Biesheuvel 
815193a33dSArd Biesheuvel 	return status;
825193a33dSArd Biesheuvel }
835193a33dSArd Biesheuvel 
845193a33dSArd Biesheuvel static efi_status_t efi_file_read(efi_file_protocol_t *handle,
855193a33dSArd Biesheuvel 				  unsigned long *size, void *addr)
865193a33dSArd Biesheuvel {
875193a33dSArd Biesheuvel 	return handle->read(handle, size, addr);
885193a33dSArd Biesheuvel }
895193a33dSArd Biesheuvel 
905193a33dSArd Biesheuvel static efi_status_t efi_file_close(efi_file_protocol_t *handle)
915193a33dSArd Biesheuvel {
925193a33dSArd Biesheuvel 	return handle->close(handle);
935193a33dSArd Biesheuvel }
945193a33dSArd Biesheuvel 
955193a33dSArd Biesheuvel static efi_status_t efi_open_volume(efi_loaded_image_t *image,
965193a33dSArd Biesheuvel 				    efi_file_protocol_t **__fh)
975193a33dSArd Biesheuvel {
985193a33dSArd Biesheuvel 	efi_simple_file_system_protocol_t *io;
995193a33dSArd Biesheuvel 	efi_file_protocol_t *fh;
1005193a33dSArd Biesheuvel 	efi_guid_t fs_proto = EFI_FILE_SYSTEM_GUID;
1015193a33dSArd Biesheuvel 	efi_status_t status;
1025193a33dSArd Biesheuvel 	efi_handle_t handle = image->device_handle;
1035193a33dSArd Biesheuvel 
1045193a33dSArd Biesheuvel 	status = efi_bs_call(handle_protocol, handle, &fs_proto, (void **)&io);
1055193a33dSArd Biesheuvel 	if (status != EFI_SUCCESS) {
1065193a33dSArd Biesheuvel 		efi_printk("Failed to handle fs_proto\n");
1075193a33dSArd Biesheuvel 		return status;
1085193a33dSArd Biesheuvel 	}
1095193a33dSArd Biesheuvel 
1105193a33dSArd Biesheuvel 	status = io->open_volume(io, &fh);
1115193a33dSArd Biesheuvel 	if (status != EFI_SUCCESS)
1125193a33dSArd Biesheuvel 		efi_printk("Failed to open volume\n");
1135193a33dSArd Biesheuvel 	else
1145193a33dSArd Biesheuvel 		*__fh = fh;
1155193a33dSArd Biesheuvel 
1165193a33dSArd Biesheuvel 	return status;
1175193a33dSArd Biesheuvel }
1185193a33dSArd Biesheuvel 
1195193a33dSArd Biesheuvel /*
1205193a33dSArd Biesheuvel  * Check the cmdline for a LILO-style file= arguments.
1215193a33dSArd Biesheuvel  *
1225193a33dSArd Biesheuvel  * We only support loading a file from the same filesystem as
1235193a33dSArd Biesheuvel  * the kernel image.
1245193a33dSArd Biesheuvel  */
1255193a33dSArd Biesheuvel efi_status_t handle_cmdline_files(efi_loaded_image_t *image,
1265193a33dSArd Biesheuvel 				  char *cmd_line, char *option_string,
1275193a33dSArd Biesheuvel 				  unsigned long max_addr,
1285193a33dSArd Biesheuvel 				  unsigned long *load_addr,
1295193a33dSArd Biesheuvel 				  unsigned long *load_size)
1305193a33dSArd Biesheuvel {
1315193a33dSArd Biesheuvel 	unsigned long efi_chunk_size = ULONG_MAX;
1325193a33dSArd Biesheuvel 	struct file_info *files;
1335193a33dSArd Biesheuvel 	unsigned long file_addr;
1345193a33dSArd Biesheuvel 	u64 file_size_total;
1355193a33dSArd Biesheuvel 	efi_file_protocol_t *fh = NULL;
1365193a33dSArd Biesheuvel 	efi_status_t status;
1375193a33dSArd Biesheuvel 	int nr_files;
1385193a33dSArd Biesheuvel 	char *str;
1395193a33dSArd Biesheuvel 	int i, j, k;
1405193a33dSArd Biesheuvel 
1415193a33dSArd Biesheuvel 	if (IS_ENABLED(CONFIG_X86) && !nochunk())
1425193a33dSArd Biesheuvel 		efi_chunk_size = EFI_READ_CHUNK_SIZE;
1435193a33dSArd Biesheuvel 
1445193a33dSArd Biesheuvel 	file_addr = 0;
1455193a33dSArd Biesheuvel 	file_size_total = 0;
1465193a33dSArd Biesheuvel 
1475193a33dSArd Biesheuvel 	str = cmd_line;
1485193a33dSArd Biesheuvel 
1495193a33dSArd Biesheuvel 	j = 0;			/* See close_handles */
1505193a33dSArd Biesheuvel 
1515193a33dSArd Biesheuvel 	if (!load_addr || !load_size)
1525193a33dSArd Biesheuvel 		return EFI_INVALID_PARAMETER;
1535193a33dSArd Biesheuvel 
1545193a33dSArd Biesheuvel 	*load_addr = 0;
1555193a33dSArd Biesheuvel 	*load_size = 0;
1565193a33dSArd Biesheuvel 
1575193a33dSArd Biesheuvel 	if (!str || !*str)
1585193a33dSArd Biesheuvel 		return EFI_SUCCESS;
1595193a33dSArd Biesheuvel 
1605193a33dSArd Biesheuvel 	for (nr_files = 0; *str; nr_files++) {
1615193a33dSArd Biesheuvel 		str = strstr(str, option_string);
1625193a33dSArd Biesheuvel 		if (!str)
1635193a33dSArd Biesheuvel 			break;
1645193a33dSArd Biesheuvel 
1655193a33dSArd Biesheuvel 		str += strlen(option_string);
1665193a33dSArd Biesheuvel 
1675193a33dSArd Biesheuvel 		/* Skip any leading slashes */
1685193a33dSArd Biesheuvel 		while (*str == '/' || *str == '\\')
1695193a33dSArd Biesheuvel 			str++;
1705193a33dSArd Biesheuvel 
1715193a33dSArd Biesheuvel 		while (*str && *str != ' ' && *str != '\n')
1725193a33dSArd Biesheuvel 			str++;
1735193a33dSArd Biesheuvel 	}
1745193a33dSArd Biesheuvel 
1755193a33dSArd Biesheuvel 	if (!nr_files)
1765193a33dSArd Biesheuvel 		return EFI_SUCCESS;
1775193a33dSArd Biesheuvel 
1785193a33dSArd Biesheuvel 	status = efi_bs_call(allocate_pool, EFI_LOADER_DATA,
1795193a33dSArd Biesheuvel 			     nr_files * sizeof(*files), (void **)&files);
1805193a33dSArd Biesheuvel 	if (status != EFI_SUCCESS) {
1815193a33dSArd Biesheuvel 		pr_efi_err("Failed to alloc mem for file handle list\n");
1825193a33dSArd Biesheuvel 		goto fail;
1835193a33dSArd Biesheuvel 	}
1845193a33dSArd Biesheuvel 
1855193a33dSArd Biesheuvel 	str = cmd_line;
1865193a33dSArd Biesheuvel 	for (i = 0; i < nr_files; i++) {
1875193a33dSArd Biesheuvel 		struct file_info *file;
1885193a33dSArd Biesheuvel 		efi_char16_t filename_16[256];
1895193a33dSArd Biesheuvel 		efi_char16_t *p;
1905193a33dSArd Biesheuvel 
1915193a33dSArd Biesheuvel 		str = strstr(str, option_string);
1925193a33dSArd Biesheuvel 		if (!str)
1935193a33dSArd Biesheuvel 			break;
1945193a33dSArd Biesheuvel 
1955193a33dSArd Biesheuvel 		str += strlen(option_string);
1965193a33dSArd Biesheuvel 
1975193a33dSArd Biesheuvel 		file = &files[i];
1985193a33dSArd Biesheuvel 		p = filename_16;
1995193a33dSArd Biesheuvel 
2005193a33dSArd Biesheuvel 		/* Skip any leading slashes */
2015193a33dSArd Biesheuvel 		while (*str == '/' || *str == '\\')
2025193a33dSArd Biesheuvel 			str++;
2035193a33dSArd Biesheuvel 
2045193a33dSArd Biesheuvel 		while (*str && *str != ' ' && *str != '\n') {
2055193a33dSArd Biesheuvel 			if ((u8 *)p >= (u8 *)filename_16 + sizeof(filename_16))
2065193a33dSArd Biesheuvel 				break;
2075193a33dSArd Biesheuvel 
2085193a33dSArd Biesheuvel 			if (*str == '/') {
2095193a33dSArd Biesheuvel 				*p++ = '\\';
2105193a33dSArd Biesheuvel 				str++;
2115193a33dSArd Biesheuvel 			} else {
2125193a33dSArd Biesheuvel 				*p++ = *str++;
2135193a33dSArd Biesheuvel 			}
2145193a33dSArd Biesheuvel 		}
2155193a33dSArd Biesheuvel 
2165193a33dSArd Biesheuvel 		*p = '\0';
2175193a33dSArd Biesheuvel 
2185193a33dSArd Biesheuvel 		/* Only open the volume once. */
2195193a33dSArd Biesheuvel 		if (!i) {
2205193a33dSArd Biesheuvel 			status = efi_open_volume(image, &fh);
2215193a33dSArd Biesheuvel 			if (status != EFI_SUCCESS)
2225193a33dSArd Biesheuvel 				goto free_files;
2235193a33dSArd Biesheuvel 		}
2245193a33dSArd Biesheuvel 
2255193a33dSArd Biesheuvel 		status = efi_file_size(fh, filename_16, (void **)&file->handle,
2265193a33dSArd Biesheuvel 				       &file->size);
2275193a33dSArd Biesheuvel 		if (status != EFI_SUCCESS)
2285193a33dSArd Biesheuvel 			goto close_handles;
2295193a33dSArd Biesheuvel 
2305193a33dSArd Biesheuvel 		file_size_total += file->size;
2315193a33dSArd Biesheuvel 	}
2325193a33dSArd Biesheuvel 
2335193a33dSArd Biesheuvel 	if (file_size_total) {
2345193a33dSArd Biesheuvel 		unsigned long addr;
2355193a33dSArd Biesheuvel 
2365193a33dSArd Biesheuvel 		/*
2375193a33dSArd Biesheuvel 		 * Multiple files need to be at consecutive addresses in memory,
2385193a33dSArd Biesheuvel 		 * so allocate enough memory for all the files.  This is used
2395193a33dSArd Biesheuvel 		 * for loading multiple files.
2405193a33dSArd Biesheuvel 		 */
2415193a33dSArd Biesheuvel 		status = efi_allocate_pages(file_size_total, &file_addr, max_addr);
2425193a33dSArd Biesheuvel 		if (status != EFI_SUCCESS) {
2435193a33dSArd Biesheuvel 			pr_efi_err("Failed to alloc highmem for files\n");
2445193a33dSArd Biesheuvel 			goto close_handles;
2455193a33dSArd Biesheuvel 		}
2465193a33dSArd Biesheuvel 
2475193a33dSArd Biesheuvel 		/* We've run out of free low memory. */
2485193a33dSArd Biesheuvel 		if (file_addr > max_addr) {
2495193a33dSArd Biesheuvel 			pr_efi_err("We've run out of free low memory\n");
2505193a33dSArd Biesheuvel 			status = EFI_INVALID_PARAMETER;
2515193a33dSArd Biesheuvel 			goto free_file_total;
2525193a33dSArd Biesheuvel 		}
2535193a33dSArd Biesheuvel 
2545193a33dSArd Biesheuvel 		addr = file_addr;
2555193a33dSArd Biesheuvel 		for (j = 0; j < nr_files; j++) {
2565193a33dSArd Biesheuvel 			unsigned long size;
2575193a33dSArd Biesheuvel 
2585193a33dSArd Biesheuvel 			size = files[j].size;
2595193a33dSArd Biesheuvel 			while (size) {
2605193a33dSArd Biesheuvel 				unsigned long chunksize;
2615193a33dSArd Biesheuvel 
2625193a33dSArd Biesheuvel 				if (size > efi_chunk_size)
2635193a33dSArd Biesheuvel 					chunksize = efi_chunk_size;
2645193a33dSArd Biesheuvel 				else
2655193a33dSArd Biesheuvel 					chunksize = size;
2665193a33dSArd Biesheuvel 
2675193a33dSArd Biesheuvel 				status = efi_file_read(files[j].handle,
2685193a33dSArd Biesheuvel 						       &chunksize,
2695193a33dSArd Biesheuvel 						       (void *)addr);
2705193a33dSArd Biesheuvel 				if (status != EFI_SUCCESS) {
2715193a33dSArd Biesheuvel 					pr_efi_err("Failed to read file\n");
2725193a33dSArd Biesheuvel 					goto free_file_total;
2735193a33dSArd Biesheuvel 				}
2745193a33dSArd Biesheuvel 				addr += chunksize;
2755193a33dSArd Biesheuvel 				size -= chunksize;
2765193a33dSArd Biesheuvel 			}
2775193a33dSArd Biesheuvel 
2785193a33dSArd Biesheuvel 			efi_file_close(files[j].handle);
2795193a33dSArd Biesheuvel 		}
2805193a33dSArd Biesheuvel 
2815193a33dSArd Biesheuvel 	}
2825193a33dSArd Biesheuvel 
2835193a33dSArd Biesheuvel 	efi_bs_call(free_pool, files);
2845193a33dSArd Biesheuvel 
2855193a33dSArd Biesheuvel 	*load_addr = file_addr;
2865193a33dSArd Biesheuvel 	*load_size = file_size_total;
2875193a33dSArd Biesheuvel 
2885193a33dSArd Biesheuvel 	return status;
2895193a33dSArd Biesheuvel 
2905193a33dSArd Biesheuvel free_file_total:
2915193a33dSArd Biesheuvel 	efi_free(file_size_total, file_addr);
2925193a33dSArd Biesheuvel 
2935193a33dSArd Biesheuvel close_handles:
2945193a33dSArd Biesheuvel 	for (k = j; k < i; k++)
2955193a33dSArd Biesheuvel 		efi_file_close(files[k].handle);
2965193a33dSArd Biesheuvel free_files:
2975193a33dSArd Biesheuvel 	efi_bs_call(free_pool, files);
2985193a33dSArd Biesheuvel fail:
2995193a33dSArd Biesheuvel 	*load_addr = 0;
3005193a33dSArd Biesheuvel 	*load_size = 0;
3015193a33dSArd Biesheuvel 
3025193a33dSArd Biesheuvel 	return status;
3035193a33dSArd Biesheuvel }
304