Patrick Rudolph | a892cde | 2018-04-19 14:39:07 +0200 | [diff] [blame] | 1 | /* |
Martin Roth | 96c075a | 2019-07-28 19:50:52 -0600 | [diff] [blame] | 2 | * This file is part of the coreboot project. |
Patrick Rudolph | a892cde | 2018-04-19 14:39:07 +0200 | [diff] [blame] | 3 | * |
| 4 | * This program is free software; you can redistribute it and/or |
| 5 | * modify it under the terms of the GNU General Public License as |
| 6 | * published by the Free Software Foundation; either version 2 of |
| 7 | * the License, or (at your option) any later version. |
| 8 | * |
| 9 | * This program is distributed in the hope that it will be useful, |
| 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 12 | * GNU General Public License for more details. |
| 13 | */ |
| 14 | |
| 15 | #include <console/console.h> |
| 16 | #include <bootmem.h> |
Patrick Rudolph | a892cde | 2018-04-19 14:39:07 +0200 | [diff] [blame] | 17 | #include <program_loading.h> |
| 18 | #include <string.h> |
| 19 | #include <commonlib/compression.h> |
| 20 | #include <commonlib/cbfs_serialized.h> |
Elyes HAOUAS | e2d152c | 2019-06-21 07:06:50 +0200 | [diff] [blame] | 21 | #include <commonlib/helpers.h> |
Patrick Rudolph | a892cde | 2018-04-19 14:39:07 +0200 | [diff] [blame] | 22 | #include <lib.h> |
| 23 | #include <fit.h> |
| 24 | #include <endian.h> |
| 25 | |
| 26 | #define MAX_KERNEL_SIZE (64*MiB) |
| 27 | |
| 28 | struct arm64_kernel_header { |
| 29 | u32 code0; |
| 30 | u32 code1; |
| 31 | u64 text_offset; |
| 32 | u64 image_size; |
| 33 | u64 flags; |
| 34 | u64 res2; |
| 35 | u64 res3; |
| 36 | u64 res4; |
| 37 | u32 magic; |
| 38 | #define KERNEL_HEADER_MAGIC 0x644d5241 |
| 39 | u32 res5; |
| 40 | }; |
| 41 | |
| 42 | static struct { |
| 43 | union { |
| 44 | struct arm64_kernel_header header; |
| 45 | u8 raw[sizeof(struct arm64_kernel_header) + 0x100]; |
| 46 | }; |
| 47 | #define SCRATCH_CANARY_VALUE 0xdeadbeef |
| 48 | u32 canary; |
| 49 | } scratch; |
| 50 | |
| 51 | /* Returns true if decompressing was successful and it looks like a kernel. */ |
| 52 | static bool decompress_kernel_header(const struct fit_image_node *node) |
| 53 | { |
| 54 | /* Partially decompress to get text_offset. Can't check for errors. */ |
| 55 | scratch.canary = SCRATCH_CANARY_VALUE; |
| 56 | switch (node->compression) { |
| 57 | case CBFS_COMPRESS_NONE: |
| 58 | memcpy(scratch.raw, node->data, sizeof(scratch.raw)); |
| 59 | break; |
| 60 | case CBFS_COMPRESS_LZMA: |
| 61 | ulzman(node->data, node->size, |
| 62 | scratch.raw, sizeof(scratch.raw)); |
| 63 | break; |
| 64 | case CBFS_COMPRESS_LZ4: |
| 65 | ulz4fn(node->data, node->size, |
| 66 | scratch.raw, sizeof(scratch.raw)); |
| 67 | break; |
| 68 | default: |
| 69 | printk(BIOS_ERR, "ERROR: Unsupported compression algorithm!\n"); |
| 70 | return false; |
| 71 | } |
| 72 | |
| 73 | /* Should never happen, but if it does we'll want to know. */ |
| 74 | if (scratch.canary != SCRATCH_CANARY_VALUE) |
| 75 | die("ERROR: Partial decompression ran over scratchbuf!\n"); |
| 76 | |
| 77 | if (scratch.header.magic != KERNEL_HEADER_MAGIC) { |
| 78 | printk(BIOS_ERR, |
| 79 | "ERROR: Invalid kernel magic: %#.8x\n != %#.8x\n", |
| 80 | scratch.header.magic, KERNEL_HEADER_MAGIC); |
| 81 | return false; |
| 82 | } |
| 83 | |
| 84 | /** |
| 85 | * Prior to v3.17, the endianness of text_offset was not specified. In |
| 86 | * these cases image_size is zero and text_offset is 0x80000 in the |
| 87 | * endianness of the kernel. Where image_size is non-zero image_size is |
| 88 | * little-endian and must be respected. Where image_size is zero, |
| 89 | * text_offset can be assumed to be 0x80000. |
| 90 | */ |
| 91 | if (!scratch.header.image_size) |
| 92 | scratch.header.text_offset = cpu_to_le64(0x80000); |
| 93 | |
| 94 | return true; |
| 95 | } |
| 96 | |
| 97 | static size_t get_kernel_size(const struct fit_image_node *node) |
| 98 | { |
| 99 | if (scratch.header.image_size) |
| 100 | return le64_to_cpu(scratch.header.image_size); |
| 101 | |
| 102 | /** |
| 103 | * When image_size is zero, a bootloader should attempt to keep as much |
| 104 | * memory as possible free for use by the kernel immediately after the |
| 105 | * end of the kernel image. The amount of space required will vary |
| 106 | * depending on selected features, and is effectively unbound. |
| 107 | */ |
| 108 | |
| 109 | printk(BIOS_WARNING, "FIT: image_size not set in kernel header.\n" |
| 110 | "Leaving additional %u MiB of free space after kernel.\n", |
| 111 | MAX_KERNEL_SIZE >> 20); |
| 112 | |
| 113 | return node->size + MAX_KERNEL_SIZE; |
| 114 | } |
| 115 | |
| 116 | static bool fit_place_kernel(const struct range_entry *r, void *arg) |
| 117 | { |
| 118 | struct region *region = arg; |
| 119 | resource_t start; |
| 120 | |
| 121 | if (range_entry_tag(r) != BM_MEM_RAM) |
| 122 | return true; |
| 123 | |
| 124 | /** |
| 125 | * The Image must be placed text_offset bytes from a 2MB aligned base |
| 126 | * address anywhere in usable system RAM and called there. The region |
| 127 | * between the 2 MB aligned base address and the start of the image has |
| 128 | * no special significance to the kernel, and may be used for other |
| 129 | * purposes. |
| 130 | * |
| 131 | * If the reserved memory (BL31 for example) is smaller than text_offset |
| 132 | * we can use the 2 MiB base address, otherwise use the next 2 MiB page. |
| 133 | * It's not mandatory, but wastes less memory below the kernel. |
| 134 | */ |
| 135 | start = ALIGN_DOWN(range_entry_base(r), 2 * MiB) + |
| 136 | le64_to_cpu(scratch.header.text_offset); |
| 137 | |
| 138 | if (start < range_entry_base(r)) |
| 139 | start += 2 * MiB; |
| 140 | /** |
| 141 | * At least image_size bytes from the start of the image must be free |
| 142 | * for use by the kernel. |
| 143 | */ |
| 144 | if (start + region->size < range_entry_end(r)) { |
| 145 | region->offset = (size_t)start; |
| 146 | return false; |
| 147 | } |
| 148 | |
| 149 | return true; |
| 150 | } |
| 151 | |
| 152 | /** |
| 153 | * Place the region in free memory range. |
| 154 | * |
| 155 | * The caller has to set region->offset to the minimum allowed address. |
| 156 | * The region->offset is usually 0 on kernel >v4.6 and kernel_base + kernel_size |
| 157 | * on kernel <v4.6. |
| 158 | */ |
| 159 | static bool fit_place_mem(const struct range_entry *r, void *arg) |
| 160 | { |
| 161 | struct region *region = arg; |
| 162 | resource_t start; |
| 163 | |
| 164 | if (range_entry_tag(r) != BM_MEM_RAM) |
| 165 | return true; |
| 166 | |
| 167 | /* Linux 4.15 doesn't like 4KiB alignment. Align to 1 MiB for now. */ |
| 168 | start = ALIGN_UP(MAX(region->offset, range_entry_base(r)), 1 * MiB); |
| 169 | |
| 170 | if (start + region->size < range_entry_end(r)) { |
| 171 | region->offset = (size_t)start; |
| 172 | return false; |
| 173 | } |
| 174 | |
| 175 | return true; |
| 176 | } |
| 177 | |
| 178 | bool fit_payload_arch(struct prog *payload, struct fit_config_node *config, |
| 179 | struct region *kernel, |
| 180 | struct region *fdt, |
| 181 | struct region *initrd) |
| 182 | { |
| 183 | bool place_anywhere; |
| 184 | void *arg = NULL; |
| 185 | |
Julius Werner | b379f19 | 2019-05-13 16:34:16 -0700 | [diff] [blame] | 186 | if (!decompress_kernel_header(config->kernel)) { |
Patrick Rudolph | a892cde | 2018-04-19 14:39:07 +0200 | [diff] [blame] | 187 | printk(BIOS_CRIT, "CRIT: Payload doesn't look like an ARM64" |
| 188 | " kernel Image.\n"); |
| 189 | return false; |
| 190 | } |
| 191 | |
| 192 | /* Update kernel size from image header, if possible */ |
Julius Werner | b379f19 | 2019-05-13 16:34:16 -0700 | [diff] [blame] | 193 | kernel->size = get_kernel_size(config->kernel); |
Patrick Rudolph | a892cde | 2018-04-19 14:39:07 +0200 | [diff] [blame] | 194 | printk(BIOS_DEBUG, "FIT: Using kernel size of 0x%zx bytes\n", |
| 195 | kernel->size); |
| 196 | |
| 197 | /** |
| 198 | * The code assumes that bootmem_walk provides a sorted list of memory |
| 199 | * regions, starting from the lowest address. |
| 200 | * The order of the calls here doesn't matter, as the placement is |
| 201 | * enforced in the called functions. |
| 202 | * For details check code on top. |
| 203 | */ |
| 204 | |
| 205 | if (!bootmem_walk(fit_place_kernel, kernel)) |
| 206 | return false; |
| 207 | |
| 208 | /* Mark as reserved for future allocations. */ |
| 209 | bootmem_add_range(kernel->offset, kernel->size, BM_MEM_PAYLOAD); |
| 210 | |
| 211 | /** |
| 212 | * NOTE: versions prior to v4.6 cannot make use of memory below the |
| 213 | * physical offset of the Image so it is recommended that the Image be |
| 214 | * placed as close as possible to the start of system RAM. |
| 215 | * |
| 216 | * For kernel <v4.6 the INITRD and FDT can't be placed below the kernel. |
| 217 | * In that case set region offset to an address on top of kernel. |
| 218 | */ |
| 219 | place_anywhere = !!(le64_to_cpu(scratch.header.flags) & (1 << 3)); |
| 220 | printk(BIOS_DEBUG, "FIT: Placing FDT and INITRD %s\n", |
| 221 | place_anywhere ? "anywhere" : "on top of kernel"); |
| 222 | |
| 223 | /* Place INITRD */ |
| 224 | if (config->ramdisk) { |
| 225 | if (place_anywhere) |
| 226 | initrd->offset = 0; |
| 227 | else |
| 228 | initrd->offset = kernel->offset + kernel->size; |
| 229 | |
| 230 | if (!bootmem_walk(fit_place_mem, initrd)) |
| 231 | return false; |
| 232 | /* Mark as reserved for future allocations. */ |
| 233 | bootmem_add_range(initrd->offset, initrd->size, BM_MEM_PAYLOAD); |
| 234 | } |
| 235 | |
| 236 | /* Place FDT */ |
| 237 | if (place_anywhere) |
| 238 | fdt->offset = 0; |
| 239 | else |
| 240 | fdt->offset = kernel->offset + kernel->size; |
| 241 | |
| 242 | if (!bootmem_walk(fit_place_mem, fdt)) |
| 243 | return false; |
| 244 | /* Mark as reserved for future allocations. */ |
| 245 | bootmem_add_range(fdt->offset, fdt->size, BM_MEM_PAYLOAD); |
| 246 | |
| 247 | /* Kernel expects FDT as argument */ |
| 248 | arg = (void *)fdt->offset; |
| 249 | |
| 250 | prog_set_entry(payload, (void *)kernel->offset, arg); |
| 251 | |
| 252 | bootmem_dump_ranges(); |
| 253 | |
| 254 | return true; |
| 255 | } |