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