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