xref: /openbmc/linux/drivers/firmware/efi/libstub/file.c (revision 9302c1bb8e475829330146423626c3d32e8cb012)
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 
15*9302c1bbSArd Biesheuvel #define MAX_FILENAME_SIZE	256
16*9302c1bbSArd 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 
32*9302c1bbSArd Biesheuvel static efi_status_t efi_open_file(efi_file_protocol_t *volume,
33*9302c1bbSArd Biesheuvel 				  efi_char16_t *filename_16,
34*9302c1bbSArd Biesheuvel 				  efi_file_protocol_t **handle,
35*9302c1bbSArd Biesheuvel 				  unsigned long *file_size)
365193a33dSArd Biesheuvel {
37*9302c1bbSArd Biesheuvel 	struct {
38*9302c1bbSArd Biesheuvel 		efi_file_info_t info;
39*9302c1bbSArd Biesheuvel 		efi_char16_t	filename[MAX_FILENAME_SIZE];
40*9302c1bbSArd Biesheuvel 	} finfo;
415193a33dSArd Biesheuvel 	efi_guid_t info_guid = EFI_FILE_INFO_ID;
42*9302c1bbSArd Biesheuvel 	efi_file_protocol_t *fh;
435193a33dSArd Biesheuvel 	unsigned long info_sz;
44*9302c1bbSArd Biesheuvel 	efi_status_t status;
455193a33dSArd Biesheuvel 
46*9302c1bbSArd Biesheuvel 	status = volume->open(volume, &fh, filename_16, EFI_FILE_MODE_READ, 0);
475193a33dSArd Biesheuvel 	if (status != EFI_SUCCESS) {
48*9302c1bbSArd 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 
54*9302c1bbSArd Biesheuvel 	info_sz = sizeof(finfo);
55*9302c1bbSArd Biesheuvel 	status = fh->get_info(fh, &info_guid, &info_sz, &finfo);
565193a33dSArd Biesheuvel 	if (status != EFI_SUCCESS) {
57*9302c1bbSArd Biesheuvel 		pr_efi_err("Failed to get file info\n");
58*9302c1bbSArd Biesheuvel 		fh->close(fh);
595193a33dSArd Biesheuvel 		return status;
605193a33dSArd Biesheuvel 	}
615193a33dSArd Biesheuvel 
62*9302c1bbSArd Biesheuvel 	*handle = fh;
63*9302c1bbSArd Biesheuvel 	*file_size = finfo.info.file_size;
64*9302c1bbSArd Biesheuvel 	return EFI_SUCCESS;
655193a33dSArd Biesheuvel }
665193a33dSArd Biesheuvel 
675193a33dSArd Biesheuvel static efi_status_t efi_open_volume(efi_loaded_image_t *image,
68*9302c1bbSArd Biesheuvel 				    efi_file_protocol_t **fh)
695193a33dSArd Biesheuvel {
705193a33dSArd Biesheuvel 	efi_guid_t fs_proto = EFI_FILE_SYSTEM_GUID;
71*9302c1bbSArd Biesheuvel 	efi_simple_file_system_protocol_t *io;
725193a33dSArd Biesheuvel 	efi_status_t status;
735193a33dSArd Biesheuvel 
74*9302c1bbSArd Biesheuvel 	status = efi_bs_call(handle_protocol, image->device_handle, &fs_proto,
75*9302c1bbSArd Biesheuvel 			     (void **)&io);
765193a33dSArd Biesheuvel 	if (status != EFI_SUCCESS) {
77*9302c1bbSArd Biesheuvel 		pr_efi_err("Failed to handle fs_proto\n");
785193a33dSArd Biesheuvel 		return status;
795193a33dSArd Biesheuvel 	}
805193a33dSArd Biesheuvel 
81*9302c1bbSArd Biesheuvel 	status = io->open_volume(io, fh);
825193a33dSArd Biesheuvel 	if (status != EFI_SUCCESS)
83*9302c1bbSArd Biesheuvel 		pr_efi_err("Failed to open volume\n");
845193a33dSArd Biesheuvel 
855193a33dSArd Biesheuvel 	return status;
865193a33dSArd Biesheuvel }
875193a33dSArd Biesheuvel 
88*9302c1bbSArd Biesheuvel static int find_file_option(const efi_char16_t *cmdline, int cmdline_len,
89*9302c1bbSArd Biesheuvel 			    const efi_char16_t *prefix, int prefix_size,
90*9302c1bbSArd Biesheuvel 			    efi_char16_t *result, int result_len)
91*9302c1bbSArd Biesheuvel {
92*9302c1bbSArd Biesheuvel 	int prefix_len = prefix_size / 2;
93*9302c1bbSArd Biesheuvel 	bool found = false;
94*9302c1bbSArd Biesheuvel 	int i;
95*9302c1bbSArd Biesheuvel 
96*9302c1bbSArd Biesheuvel 	for (i = prefix_len; i < cmdline_len; i++) {
97*9302c1bbSArd Biesheuvel 		if (!memcmp(&cmdline[i - prefix_len], prefix, prefix_size)) {
98*9302c1bbSArd Biesheuvel 			found = true;
99*9302c1bbSArd Biesheuvel 			break;
100*9302c1bbSArd Biesheuvel 		}
101*9302c1bbSArd Biesheuvel 	}
102*9302c1bbSArd Biesheuvel 
103*9302c1bbSArd Biesheuvel 	if (!found)
104*9302c1bbSArd Biesheuvel 		return 0;
105*9302c1bbSArd Biesheuvel 
106*9302c1bbSArd Biesheuvel 	while (--result_len > 0 && i < cmdline_len) {
107*9302c1bbSArd Biesheuvel 		if (cmdline[i] == L'\0' ||
108*9302c1bbSArd Biesheuvel 		    cmdline[i] == L'\n' ||
109*9302c1bbSArd Biesheuvel 		    cmdline[i] == L' ')
110*9302c1bbSArd Biesheuvel 			break;
111*9302c1bbSArd Biesheuvel 		*result++ = cmdline[i++];
112*9302c1bbSArd Biesheuvel 	}
113*9302c1bbSArd Biesheuvel 	*result = L'\0';
114*9302c1bbSArd Biesheuvel 	return i;
115*9302c1bbSArd Biesheuvel }
116*9302c1bbSArd 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  */
123*9302c1bbSArd Biesheuvel static efi_status_t handle_cmdline_files(efi_loaded_image_t *image,
124*9302c1bbSArd Biesheuvel 					 const efi_char16_t *optstr,
125*9302c1bbSArd Biesheuvel 					 int optstr_size,
1265193a33dSArd Biesheuvel 					 unsigned long max_addr,
1275193a33dSArd Biesheuvel 					 unsigned long *load_addr,
1285193a33dSArd Biesheuvel 					 unsigned long *load_size)
1295193a33dSArd Biesheuvel {
130*9302c1bbSArd Biesheuvel 	const efi_char16_t *cmdline = image->load_options;
131*9302c1bbSArd Biesheuvel 	int cmdline_len = image->load_options_size / 2;
1325193a33dSArd Biesheuvel 	unsigned long efi_chunk_size = ULONG_MAX;
133*9302c1bbSArd Biesheuvel 	efi_file_protocol_t *volume = NULL;
134*9302c1bbSArd Biesheuvel 	efi_file_protocol_t *file;
135*9302c1bbSArd Biesheuvel 	unsigned long alloc_addr;
136*9302c1bbSArd Biesheuvel 	unsigned long alloc_size;
1375193a33dSArd Biesheuvel 	efi_status_t status;
138*9302c1bbSArd Biesheuvel 	int offset;
1395193a33dSArd Biesheuvel 
1405193a33dSArd Biesheuvel 	if (!load_addr || !load_size)
1415193a33dSArd Biesheuvel 		return EFI_INVALID_PARAMETER;
1425193a33dSArd Biesheuvel 
143*9302c1bbSArd Biesheuvel 	if (IS_ENABLED(CONFIG_X86) && !nochunk())
144*9302c1bbSArd Biesheuvel 		efi_chunk_size = EFI_READ_CHUNK_SIZE;
1455193a33dSArd Biesheuvel 
146*9302c1bbSArd Biesheuvel 	alloc_addr = alloc_size = 0;
147*9302c1bbSArd Biesheuvel 	do {
148*9302c1bbSArd Biesheuvel 		efi_char16_t filename[MAX_FILENAME_SIZE];
149*9302c1bbSArd Biesheuvel 		unsigned long size;
150*9302c1bbSArd Biesheuvel 		void *addr;
1515193a33dSArd Biesheuvel 
152*9302c1bbSArd Biesheuvel 		offset = find_file_option(cmdline, cmdline_len,
153*9302c1bbSArd Biesheuvel 					  optstr, optstr_size,
154*9302c1bbSArd Biesheuvel 					  filename, ARRAY_SIZE(filename));
155*9302c1bbSArd Biesheuvel 
156*9302c1bbSArd Biesheuvel 		if (!offset)
1575193a33dSArd Biesheuvel 			break;
1585193a33dSArd Biesheuvel 
159*9302c1bbSArd Biesheuvel 		cmdline += offset;
160*9302c1bbSArd Biesheuvel 		cmdline_len -= offset;
1615193a33dSArd Biesheuvel 
162*9302c1bbSArd Biesheuvel 		if (!volume) {
163*9302c1bbSArd Biesheuvel 			status = efi_open_volume(image, &volume);
1645193a33dSArd Biesheuvel 			if (status != EFI_SUCCESS)
165*9302c1bbSArd Biesheuvel 				return status;
1665193a33dSArd Biesheuvel 		}
1675193a33dSArd Biesheuvel 
168*9302c1bbSArd Biesheuvel 		status = efi_open_file(volume, filename, &file, &size);
1695193a33dSArd Biesheuvel 		if (status != EFI_SUCCESS)
170*9302c1bbSArd Biesheuvel 			goto err_close_volume;
1715193a33dSArd Biesheuvel 
1725193a33dSArd Biesheuvel 		/*
173*9302c1bbSArd Biesheuvel 		 * Check whether the existing allocation can contain the next
174*9302c1bbSArd Biesheuvel 		 * file. This condition will also trigger naturally during the
175*9302c1bbSArd Biesheuvel 		 * first (and typically only) iteration of the loop, given that
176*9302c1bbSArd Biesheuvel 		 * alloc_size == 0 in that case.
1775193a33dSArd Biesheuvel 		 */
178*9302c1bbSArd Biesheuvel 		if (round_up(alloc_size + size, EFI_ALLOC_ALIGN) >
179*9302c1bbSArd Biesheuvel 		    round_up(alloc_size, EFI_ALLOC_ALIGN)) {
180*9302c1bbSArd Biesheuvel 			unsigned long old_addr = alloc_addr;
181*9302c1bbSArd Biesheuvel 
182*9302c1bbSArd Biesheuvel 			status = efi_allocate_pages(alloc_size + size, &alloc_addr,
183*9302c1bbSArd Biesheuvel 						    max_addr);
1845193a33dSArd Biesheuvel 			if (status != EFI_SUCCESS) {
185*9302c1bbSArd Biesheuvel 				pr_efi_err("Failed to reallocate memory for files\n");
186*9302c1bbSArd Biesheuvel 				goto err_close_file;
1875193a33dSArd Biesheuvel 			}
1885193a33dSArd Biesheuvel 
189*9302c1bbSArd Biesheuvel 			if (old_addr != 0) {
190*9302c1bbSArd Biesheuvel 				/*
191*9302c1bbSArd Biesheuvel 				 * This is not the first time we've gone
192*9302c1bbSArd Biesheuvel 				 * around this loop, and so we are loading
193*9302c1bbSArd Biesheuvel 				 * multiple files that need to be concatenated
194*9302c1bbSArd Biesheuvel 				 * and returned in a single buffer.
195*9302c1bbSArd Biesheuvel 				 */
196*9302c1bbSArd Biesheuvel 				memcpy((void *)alloc_addr, (void *)old_addr, alloc_size);
197*9302c1bbSArd Biesheuvel 				efi_free(alloc_size, old_addr);
198*9302c1bbSArd Biesheuvel 			}
1995193a33dSArd Biesheuvel 		}
2005193a33dSArd Biesheuvel 
201*9302c1bbSArd Biesheuvel 		addr = (void *)alloc_addr + alloc_size;
202*9302c1bbSArd Biesheuvel 		alloc_size += size;
2035193a33dSArd Biesheuvel 
2045193a33dSArd Biesheuvel 		while (size) {
205*9302c1bbSArd Biesheuvel 			unsigned long chunksize = min(size, efi_chunk_size);
2065193a33dSArd Biesheuvel 
207*9302c1bbSArd Biesheuvel 			status = file->read(file, &chunksize, addr);
2085193a33dSArd Biesheuvel 			if (status != EFI_SUCCESS) {
2095193a33dSArd Biesheuvel 				pr_efi_err("Failed to read file\n");
210*9302c1bbSArd Biesheuvel 				goto err_close_file;
2115193a33dSArd Biesheuvel 			}
2125193a33dSArd Biesheuvel 			addr += chunksize;
2135193a33dSArd Biesheuvel 			size -= chunksize;
2145193a33dSArd Biesheuvel 		}
215*9302c1bbSArd Biesheuvel 		file->close(file);
216*9302c1bbSArd Biesheuvel 	} while (offset > 0);
2175193a33dSArd Biesheuvel 
218*9302c1bbSArd Biesheuvel 	*load_addr = alloc_addr;
219*9302c1bbSArd Biesheuvel 	*load_size = alloc_size;
220*9302c1bbSArd Biesheuvel 
221*9302c1bbSArd Biesheuvel 	if (volume)
222*9302c1bbSArd Biesheuvel 		volume->close(volume);
223*9302c1bbSArd Biesheuvel 	return EFI_SUCCESS;
224*9302c1bbSArd Biesheuvel 
225*9302c1bbSArd Biesheuvel err_close_file:
226*9302c1bbSArd Biesheuvel 	file->close(file);
227*9302c1bbSArd Biesheuvel 
228*9302c1bbSArd Biesheuvel err_close_volume:
229*9302c1bbSArd Biesheuvel 	volume->close(volume);
230*9302c1bbSArd Biesheuvel 	efi_free(alloc_size, alloc_addr);
231*9302c1bbSArd Biesheuvel 	return status;
2325193a33dSArd Biesheuvel }
2335193a33dSArd Biesheuvel 
234*9302c1bbSArd Biesheuvel efi_status_t efi_load_dtb(efi_loaded_image_t *image,
235*9302c1bbSArd Biesheuvel 			  unsigned long *load_addr,
236*9302c1bbSArd Biesheuvel 			  unsigned long *load_size)
237*9302c1bbSArd Biesheuvel {
238*9302c1bbSArd Biesheuvel 	return handle_cmdline_files(image, L"dtb=", sizeof(L"dtb=") - 2,
239*9302c1bbSArd Biesheuvel 				    ULONG_MAX, load_addr, load_size);
2405193a33dSArd Biesheuvel }
2415193a33dSArd Biesheuvel 
242*9302c1bbSArd Biesheuvel efi_status_t efi_load_initrd(efi_loaded_image_t *image,
243*9302c1bbSArd Biesheuvel 			     unsigned long *load_addr,
244*9302c1bbSArd Biesheuvel 			     unsigned long *load_size,
245*9302c1bbSArd Biesheuvel 			     unsigned long max_addr)
246*9302c1bbSArd Biesheuvel {
247*9302c1bbSArd Biesheuvel 	return handle_cmdline_files(image, L"initrd=", sizeof(L"initrd=") - 2,
248*9302c1bbSArd Biesheuvel 				    max_addr, load_addr, load_size);
2495193a33dSArd Biesheuvel }
250