Olivier Deprez | 157378f | 2022-04-04 15:47:50 +0200 | [diff] [blame^] | 1 | // SPDX-License-Identifier: GPL-2.0 |
| 2 | /* |
| 3 | * Helper functions used by the EFI stub on multiple |
| 4 | * architectures. This should be #included by the EFI stub |
| 5 | * implementation files. |
| 6 | * |
| 7 | * Copyright 2011 Intel Corporation; author Matt Fleming |
| 8 | */ |
| 9 | |
| 10 | #include <linux/efi.h> |
| 11 | #include <asm/efi.h> |
| 12 | |
| 13 | #include "efistub.h" |
| 14 | |
| 15 | #define MAX_FILENAME_SIZE 256 |
| 16 | |
| 17 | /* |
| 18 | * Some firmware implementations have problems reading files in one go. |
| 19 | * A read chunk size of 1MB seems to work for most platforms. |
| 20 | * |
| 21 | * Unfortunately, reading files in chunks triggers *other* bugs on some |
| 22 | * platforms, so we provide a way to disable this workaround, which can |
| 23 | * be done by passing "efi=nochunk" on the EFI boot stub command line. |
| 24 | * |
| 25 | * If you experience issues with initrd images being corrupt it's worth |
| 26 | * trying efi=nochunk, but chunking is enabled by default on x86 because |
| 27 | * there are far more machines that require the workaround than those that |
| 28 | * break with it enabled. |
| 29 | */ |
| 30 | #define EFI_READ_CHUNK_SIZE SZ_1M |
| 31 | |
| 32 | struct finfo { |
| 33 | efi_file_info_t info; |
| 34 | efi_char16_t filename[MAX_FILENAME_SIZE]; |
| 35 | }; |
| 36 | |
| 37 | static efi_status_t efi_open_file(efi_file_protocol_t *volume, |
| 38 | struct finfo *fi, |
| 39 | efi_file_protocol_t **handle, |
| 40 | unsigned long *file_size) |
| 41 | { |
| 42 | efi_guid_t info_guid = EFI_FILE_INFO_ID; |
| 43 | efi_file_protocol_t *fh; |
| 44 | unsigned long info_sz; |
| 45 | efi_status_t status; |
| 46 | |
| 47 | status = volume->open(volume, &fh, fi->filename, EFI_FILE_MODE_READ, 0); |
| 48 | if (status != EFI_SUCCESS) { |
| 49 | efi_err("Failed to open file: %ls\n", fi->filename); |
| 50 | return status; |
| 51 | } |
| 52 | |
| 53 | info_sz = sizeof(struct finfo); |
| 54 | status = fh->get_info(fh, &info_guid, &info_sz, fi); |
| 55 | if (status != EFI_SUCCESS) { |
| 56 | efi_err("Failed to get file info\n"); |
| 57 | fh->close(fh); |
| 58 | return status; |
| 59 | } |
| 60 | |
| 61 | *handle = fh; |
| 62 | *file_size = fi->info.file_size; |
| 63 | return EFI_SUCCESS; |
| 64 | } |
| 65 | |
| 66 | static efi_status_t efi_open_volume(efi_loaded_image_t *image, |
| 67 | efi_file_protocol_t **fh) |
| 68 | { |
| 69 | efi_guid_t fs_proto = EFI_FILE_SYSTEM_GUID; |
| 70 | efi_simple_file_system_protocol_t *io; |
| 71 | efi_status_t status; |
| 72 | |
| 73 | status = efi_bs_call(handle_protocol, image->device_handle, &fs_proto, |
| 74 | (void **)&io); |
| 75 | if (status != EFI_SUCCESS) { |
| 76 | efi_err("Failed to handle fs_proto\n"); |
| 77 | return status; |
| 78 | } |
| 79 | |
| 80 | status = io->open_volume(io, fh); |
| 81 | if (status != EFI_SUCCESS) |
| 82 | efi_err("Failed to open volume\n"); |
| 83 | |
| 84 | return status; |
| 85 | } |
| 86 | |
| 87 | static int find_file_option(const efi_char16_t *cmdline, int cmdline_len, |
| 88 | const efi_char16_t *prefix, int prefix_size, |
| 89 | efi_char16_t *result, int result_len) |
| 90 | { |
| 91 | int prefix_len = prefix_size / 2; |
| 92 | bool found = false; |
| 93 | int i; |
| 94 | |
| 95 | for (i = prefix_len; i < cmdline_len; i++) { |
| 96 | if (!memcmp(&cmdline[i - prefix_len], prefix, prefix_size)) { |
| 97 | found = true; |
| 98 | break; |
| 99 | } |
| 100 | } |
| 101 | |
| 102 | if (!found) |
| 103 | return 0; |
| 104 | |
| 105 | /* Skip any leading slashes */ |
| 106 | while (i < cmdline_len && (cmdline[i] == L'/' || cmdline[i] == L'\\')) |
| 107 | i++; |
| 108 | |
| 109 | while (--result_len > 0 && i < cmdline_len) { |
| 110 | efi_char16_t c = cmdline[i++]; |
| 111 | |
| 112 | if (c == L'\0' || c == L'\n' || c == L' ') |
| 113 | break; |
| 114 | else if (c == L'/') |
| 115 | /* Replace UNIX dir separators with EFI standard ones */ |
| 116 | *result++ = L'\\'; |
| 117 | else |
| 118 | *result++ = c; |
| 119 | } |
| 120 | *result = L'\0'; |
| 121 | return i; |
| 122 | } |
| 123 | |
| 124 | /* |
| 125 | * Check the cmdline for a LILO-style file= arguments. |
| 126 | * |
| 127 | * We only support loading a file from the same filesystem as |
| 128 | * the kernel image. |
| 129 | */ |
| 130 | efi_status_t handle_cmdline_files(efi_loaded_image_t *image, |
| 131 | const efi_char16_t *optstr, |
| 132 | int optstr_size, |
| 133 | unsigned long soft_limit, |
| 134 | unsigned long hard_limit, |
| 135 | unsigned long *load_addr, |
| 136 | unsigned long *load_size) |
| 137 | { |
| 138 | const efi_char16_t *cmdline = image->load_options; |
| 139 | int cmdline_len = image->load_options_size; |
| 140 | unsigned long efi_chunk_size = ULONG_MAX; |
| 141 | efi_file_protocol_t *volume = NULL; |
| 142 | efi_file_protocol_t *file; |
| 143 | unsigned long alloc_addr; |
| 144 | unsigned long alloc_size; |
| 145 | efi_status_t status; |
| 146 | int offset; |
| 147 | |
| 148 | if (!load_addr || !load_size) |
| 149 | return EFI_INVALID_PARAMETER; |
| 150 | |
| 151 | efi_apply_loadoptions_quirk((const void **)&cmdline, &cmdline_len); |
| 152 | cmdline_len /= sizeof(*cmdline); |
| 153 | |
| 154 | if (IS_ENABLED(CONFIG_X86) && !efi_nochunk) |
| 155 | efi_chunk_size = EFI_READ_CHUNK_SIZE; |
| 156 | |
| 157 | alloc_addr = alloc_size = 0; |
| 158 | do { |
| 159 | struct finfo fi; |
| 160 | unsigned long size; |
| 161 | void *addr; |
| 162 | |
| 163 | offset = find_file_option(cmdline, cmdline_len, |
| 164 | optstr, optstr_size, |
| 165 | fi.filename, ARRAY_SIZE(fi.filename)); |
| 166 | |
| 167 | if (!offset) |
| 168 | break; |
| 169 | |
| 170 | cmdline += offset; |
| 171 | cmdline_len -= offset; |
| 172 | |
| 173 | if (!volume) { |
| 174 | status = efi_open_volume(image, &volume); |
| 175 | if (status != EFI_SUCCESS) |
| 176 | return status; |
| 177 | } |
| 178 | |
| 179 | status = efi_open_file(volume, &fi, &file, &size); |
| 180 | if (status != EFI_SUCCESS) |
| 181 | goto err_close_volume; |
| 182 | |
| 183 | /* |
| 184 | * Check whether the existing allocation can contain the next |
| 185 | * file. This condition will also trigger naturally during the |
| 186 | * first (and typically only) iteration of the loop, given that |
| 187 | * alloc_size == 0 in that case. |
| 188 | */ |
| 189 | if (round_up(alloc_size + size, EFI_ALLOC_ALIGN) > |
| 190 | round_up(alloc_size, EFI_ALLOC_ALIGN)) { |
| 191 | unsigned long old_addr = alloc_addr; |
| 192 | |
| 193 | status = EFI_OUT_OF_RESOURCES; |
| 194 | if (soft_limit < hard_limit) |
| 195 | status = efi_allocate_pages(alloc_size + size, |
| 196 | &alloc_addr, |
| 197 | soft_limit); |
| 198 | if (status == EFI_OUT_OF_RESOURCES) |
| 199 | status = efi_allocate_pages(alloc_size + size, |
| 200 | &alloc_addr, |
| 201 | hard_limit); |
| 202 | if (status != EFI_SUCCESS) { |
| 203 | efi_err("Failed to allocate memory for files\n"); |
| 204 | goto err_close_file; |
| 205 | } |
| 206 | |
| 207 | if (old_addr != 0) { |
| 208 | /* |
| 209 | * This is not the first time we've gone |
| 210 | * around this loop, and so we are loading |
| 211 | * multiple files that need to be concatenated |
| 212 | * and returned in a single buffer. |
| 213 | */ |
| 214 | memcpy((void *)alloc_addr, (void *)old_addr, alloc_size); |
| 215 | efi_free(alloc_size, old_addr); |
| 216 | } |
| 217 | } |
| 218 | |
| 219 | addr = (void *)alloc_addr + alloc_size; |
| 220 | alloc_size += size; |
| 221 | |
| 222 | while (size) { |
| 223 | unsigned long chunksize = min(size, efi_chunk_size); |
| 224 | |
| 225 | status = file->read(file, &chunksize, addr); |
| 226 | if (status != EFI_SUCCESS) { |
| 227 | efi_err("Failed to read file\n"); |
| 228 | goto err_close_file; |
| 229 | } |
| 230 | addr += chunksize; |
| 231 | size -= chunksize; |
| 232 | } |
| 233 | file->close(file); |
| 234 | } while (offset > 0); |
| 235 | |
| 236 | *load_addr = alloc_addr; |
| 237 | *load_size = alloc_size; |
| 238 | |
| 239 | if (volume) |
| 240 | volume->close(volume); |
| 241 | return EFI_SUCCESS; |
| 242 | |
| 243 | err_close_file: |
| 244 | file->close(file); |
| 245 | |
| 246 | err_close_volume: |
| 247 | volume->close(volume); |
| 248 | efi_free(alloc_size, alloc_addr); |
| 249 | return status; |
| 250 | } |