blob: 002df44fcc262723c35d92a1a2065ba961d29468 [file] [log] [blame]
Patrick Rudolpha892cde2018-04-19 14:39:07 +02001/*
Martin Roth96c075a2019-07-28 19:50:52 -06002 * This file is part of the coreboot project.
Patrick Rudolpha892cde2018-04-19 14:39:07 +02003 *
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 Rudolpha892cde2018-04-19 14:39:07 +020017#include <program_loading.h>
18#include <string.h>
19#include <commonlib/compression.h>
20#include <commonlib/cbfs_serialized.h>
Elyes HAOUASe2d152c2019-06-21 07:06:50 +020021#include <commonlib/helpers.h>
Patrick Rudolpha892cde2018-04-19 14:39:07 +020022#include <lib.h>
23#include <fit.h>
24#include <endian.h>
25
26#define MAX_KERNEL_SIZE (64*MiB)
27
28struct 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
42static 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. */
52static 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
97static 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
116static 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 */
159static 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
178bool 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 Wernerb379f192019-05-13 16:34:16 -0700186 if (!decompress_kernel_header(config->kernel)) {
Patrick Rudolpha892cde2018-04-19 14:39:07 +0200187 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 Wernerb379f192019-05-13 16:34:16 -0700193 kernel->size = get_kernel_size(config->kernel);
Patrick Rudolpha892cde2018-04-19 14:39:07 +0200194 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}