1 /* SPDX-License-Identifier: GPL-2.0-or-later */
3 #include <commonlib/bsd/compression.h>
4 #include <console/console.h>
6 #include <program_loading.h>
12 #define MAX_KERNEL_SIZE (64*MiB)
14 struct arm64_kernel_header
{
24 #define KERNEL_HEADER_MAGIC 0x644d5241
30 struct arm64_kernel_header header
;
31 u8 raw
[sizeof(struct arm64_kernel_header
) + 0x100];
33 #define SCRATCH_CANARY_VALUE 0xdeadbeef
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
)
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
));
46 case CBFS_COMPRESS_LZMA
:
47 ulzman(node
->data
, node
->size
,
48 scratch
.raw
, sizeof(scratch
.raw
));
50 case CBFS_COMPRESS_LZ4
:
51 ulz4fn(node
->data
, node
->size
,
52 scratch
.raw
, sizeof(scratch
.raw
));
55 printk(BIOS_ERR
, "Unsupported compression algorithm!\n");
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");
63 if (scratch
.header
.magic
!= KERNEL_HEADER_MAGIC
) {
64 printk(BIOS_ERR
, "Invalid kernel magic: %#.8x\n != %#.8x\n",
65 scratch
.header
.magic
, KERNEL_HEADER_MAGIC
);
70 * Prior to v3.17, the endianness of text_offset was not specified. In
71 * these cases image_size is zero and text_offset is 0x80000 in the
72 * endianness of the kernel. Where image_size is non-zero image_size is
73 * little-endian and must be respected. Where image_size is zero,
74 * text_offset can be assumed to be 0x80000.
76 if (!scratch
.header
.image_size
)
77 scratch
.header
.text_offset
= cpu_to_le64(0x80000);
82 static size_t get_kernel_size(const struct fit_image_node
*node
)
84 if (scratch
.header
.image_size
)
85 return le64_to_cpu(scratch
.header
.image_size
);
88 * When image_size is zero, a bootloader should attempt to keep as much
89 * memory as possible free for use by the kernel immediately after the
90 * end of the kernel image. The amount of space required will vary
91 * depending on selected features, and is effectively unbound.
94 printk(BIOS_WARNING
, "FIT: image_size not set in kernel header.\n"
95 "Leaving additional %u MiB of free space after kernel.\n",
96 MAX_KERNEL_SIZE
>> 20);
98 return node
->size
+ MAX_KERNEL_SIZE
;
101 static bool fit_place_kernel(const struct range_entry
*r
, void *arg
)
103 struct region
*region
= arg
;
106 if (range_entry_tag(r
) != BM_MEM_RAM
)
110 * The Image must be placed text_offset bytes from a 2MB aligned base
111 * address anywhere in usable system RAM and called there. The region
112 * between the 2 MB aligned base address and the start of the image has
113 * no special significance to the kernel, and may be used for other
116 * If the reserved memory (BL31 for example) is smaller than text_offset
117 * we can use the 2 MiB base address, otherwise use the next 2 MiB page.
118 * It's not mandatory, but wastes less memory below the kernel.
120 start
= ALIGN_DOWN(range_entry_base(r
), 2 * MiB
) +
121 le64_to_cpu(scratch
.header
.text_offset
);
123 if (start
< range_entry_base(r
))
126 * At least image_size bytes from the start of the image must be free
127 * for use by the kernel.
129 if (start
+ region
->size
< range_entry_end(r
)) {
130 region
->offset
= (size_t)start
;
138 * Place the region in free memory range.
140 * The caller has to set region->offset to the minimum allowed address.
141 * The region->offset is usually 0 on kernel >v4.6 and kernel_base + kernel_size
144 static bool fit_place_mem(const struct range_entry
*r
, void *arg
)
146 struct region
*region
= arg
;
149 if (range_entry_tag(r
) != BM_MEM_RAM
)
152 /* Linux 4.15 doesn't like 4KiB alignment. Align to 1 MiB for now. */
153 start
= ALIGN_UP(MAX(region
->offset
, range_entry_base(r
)), 1 * MiB
);
155 if (start
+ region
->size
< range_entry_end(r
)) {
156 region
->offset
= (size_t)start
;
163 bool fit_payload_arch(struct prog
*payload
, struct fit_config_node
*config
,
164 struct region
*kernel
,
166 struct region
*initrd
)
171 if (!decompress_kernel_header(config
->kernel
)) {
172 printk(BIOS_CRIT
, "Payload doesn't look like an ARM64"
177 /* Update kernel size from image header, if possible */
178 kernel
->size
= get_kernel_size(config
->kernel
);
179 printk(BIOS_DEBUG
, "FIT: Using kernel size of 0x%zx bytes\n",
183 * The code assumes that bootmem_walk provides a sorted list of memory
184 * regions, starting from the lowest address.
185 * The order of the calls here doesn't matter, as the placement is
186 * enforced in the called functions.
187 * For details check code on top.
190 if (!bootmem_walk(fit_place_kernel
, kernel
))
193 /* Mark as reserved for future allocations. */
194 bootmem_add_range(kernel
->offset
, kernel
->size
, BM_MEM_PAYLOAD
);
197 * NOTE: versions prior to v4.6 cannot make use of memory below the
198 * physical offset of the Image so it is recommended that the Image be
199 * placed as close as possible to the start of system RAM.
201 * For kernel <v4.6 the INITRD and FDT can't be placed below the kernel.
202 * In that case set region offset to an address on top of kernel.
204 place_anywhere
= !!(le64_to_cpu(scratch
.header
.flags
) & (1 << 3));
205 printk(BIOS_DEBUG
, "FIT: Placing FDT and INITRD %s\n",
206 place_anywhere
? "anywhere" : "on top of kernel");
209 if (config
->ramdisk
) {
213 initrd
->offset
= kernel
->offset
+ kernel
->size
;
215 if (!bootmem_walk(fit_place_mem
, initrd
))
217 /* Mark as reserved for future allocations. */
218 bootmem_add_range(initrd
->offset
, initrd
->size
, BM_MEM_PAYLOAD
);
225 fdt
->offset
= kernel
->offset
+ kernel
->size
;
227 if (!bootmem_walk(fit_place_mem
, fdt
))
229 /* Mark as reserved for future allocations. */
230 bootmem_add_range(fdt
->offset
, fdt
->size
, BM_MEM_PAYLOAD
);
232 /* Kernel expects FDT as argument */
233 arg
= (void *)fdt
->offset
;
235 prog_set_entry(payload
, (void *)kernel
->offset
, arg
);
237 bootmem_dump_ranges();