]>
Commit | Line | Data |
---|---|---|
f3b70e50 AT |
1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* | |
3 | * Kexec image loader | |
4 | ||
5 | * Copyright (C) 2018 Linaro Limited | |
6 | * Author: AKASHI Takahiro <takahiro.akashi@linaro.org> | |
7 | */ | |
8 | ||
9 | #define pr_fmt(fmt) "kexec_file(Image): " fmt | |
10 | ||
11 | #include <linux/err.h> | |
12 | #include <linux/errno.h> | |
13 | #include <linux/kernel.h> | |
14 | #include <linux/kexec.h> | |
732b7b93 | 15 | #include <linux/pe.h> |
f3b70e50 | 16 | #include <linux/string.h> |
732b7b93 | 17 | #include <linux/verification.h> |
f3b70e50 AT |
18 | #include <asm/byteorder.h> |
19 | #include <asm/cpufeature.h> | |
20 | #include <asm/image.h> | |
21 | #include <asm/memory.h> | |
22 | ||
23 | static int image_probe(const char *kernel_buf, unsigned long kernel_len) | |
24 | { | |
732b7b93 AT |
25 | const struct arm64_image_header *h = |
26 | (const struct arm64_image_header *)(kernel_buf); | |
f3b70e50 | 27 | |
732b7b93 AT |
28 | if (!h || (kernel_len < sizeof(*h))) |
29 | return -EINVAL; | |
f3b70e50 | 30 | |
732b7b93 | 31 | if (memcmp(&h->magic, ARM64_IMAGE_MAGIC, sizeof(h->magic))) |
f3b70e50 AT |
32 | return -EINVAL; |
33 | ||
34 | return 0; | |
35 | } | |
36 | ||
37 | static void *image_load(struct kimage *image, | |
38 | char *kernel, unsigned long kernel_len, | |
39 | char *initrd, unsigned long initrd_len, | |
40 | char *cmdline, unsigned long cmdline_len) | |
41 | { | |
42 | struct arm64_image_header *h; | |
43 | u64 flags, value; | |
44 | bool be_image, be_kernel; | |
45 | struct kexec_buf kbuf; | |
108aa503 | 46 | unsigned long text_offset, kernel_segment_number; |
f3b70e50 AT |
47 | struct kexec_segment *kernel_segment; |
48 | int ret; | |
49 | ||
50 | /* | |
51 | * We require a kernel with an unambiguous Image header. Per | |
b693d0b3 | 52 | * Documentation/arm64/booting.rst, this is the case when image_size |
f3b70e50 AT |
53 | * is non-zero (practically speaking, since v3.17). |
54 | */ | |
55 | h = (struct arm64_image_header *)kernel; | |
56 | if (!h->image_size) | |
57 | return ERR_PTR(-EINVAL); | |
58 | ||
59 | /* Check cpu features */ | |
60 | flags = le64_to_cpu(h->flags); | |
61 | be_image = arm64_image_flag_field(flags, ARM64_IMAGE_FLAG_BE); | |
62 | be_kernel = IS_ENABLED(CONFIG_CPU_BIG_ENDIAN); | |
63 | if ((be_image != be_kernel) && !system_supports_mixed_endian()) | |
64 | return ERR_PTR(-EINVAL); | |
65 | ||
66 | value = arm64_image_flag_field(flags, ARM64_IMAGE_FLAG_PAGE_SIZE); | |
67 | if (((value == ARM64_IMAGE_FLAG_PAGE_SIZE_4K) && | |
68 | !system_supports_4kb_granule()) || | |
69 | ((value == ARM64_IMAGE_FLAG_PAGE_SIZE_64K) && | |
70 | !system_supports_64kb_granule()) || | |
71 | ((value == ARM64_IMAGE_FLAG_PAGE_SIZE_16K) && | |
72 | !system_supports_16kb_granule())) | |
73 | return ERR_PTR(-EINVAL); | |
74 | ||
75 | /* Load the kernel */ | |
76 | kbuf.image = image; | |
77 | kbuf.buf_min = 0; | |
78 | kbuf.buf_max = ULONG_MAX; | |
79 | kbuf.top_down = false; | |
80 | ||
81 | kbuf.buffer = kernel; | |
82 | kbuf.bufsz = kernel_len; | |
c19d050f | 83 | kbuf.mem = KEXEC_BUF_MEM_UNKNOWN; |
f3b70e50 AT |
84 | kbuf.memsz = le64_to_cpu(h->image_size); |
85 | text_offset = le64_to_cpu(h->text_offset); | |
86 | kbuf.buf_align = MIN_KIMG_ALIGN; | |
87 | ||
88 | /* Adjust kernel segment with TEXT_OFFSET */ | |
89 | kbuf.memsz += text_offset; | |
90 | ||
108aa503 BG |
91 | kernel_segment_number = image->nr_segments; |
92 | ||
93 | /* | |
94 | * The location of the kernel segment may make it impossible to satisfy | |
95 | * the other segment requirements, so we try repeatedly to find a | |
96 | * location that will work. | |
97 | */ | |
98 | while ((ret = kexec_add_buffer(&kbuf)) == 0) { | |
99 | /* Try to load additional data */ | |
100 | kernel_segment = &image->segment[kernel_segment_number]; | |
101 | ret = load_other_segments(image, kernel_segment->mem, | |
102 | kernel_segment->memsz, initrd, | |
103 | initrd_len, cmdline); | |
104 | if (!ret) | |
105 | break; | |
106 | ||
107 | /* | |
108 | * We couldn't find space for the other segments; erase the | |
109 | * kernel segment and try the next available hole. | |
110 | */ | |
111 | image->nr_segments -= 1; | |
112 | kbuf.buf_min = kernel_segment->mem + kernel_segment->memsz; | |
113 | kbuf.mem = KEXEC_BUF_MEM_UNKNOWN; | |
114 | } | |
115 | ||
116 | if (ret) { | |
117 | pr_err("Could not find any suitable kernel location!"); | |
f3b70e50 | 118 | return ERR_PTR(ret); |
108aa503 | 119 | } |
f3b70e50 | 120 | |
108aa503 | 121 | kernel_segment = &image->segment[kernel_segment_number]; |
f3b70e50 AT |
122 | kernel_segment->mem += text_offset; |
123 | kernel_segment->memsz -= text_offset; | |
124 | image->start = kernel_segment->mem; | |
125 | ||
126 | pr_debug("Loaded kernel at 0x%lx bufsz=0x%lx memsz=0x%lx\n", | |
127 | kernel_segment->mem, kbuf.bufsz, | |
128 | kernel_segment->memsz); | |
129 | ||
108aa503 | 130 | return 0; |
f3b70e50 AT |
131 | } |
132 | ||
732b7b93 AT |
133 | #ifdef CONFIG_KEXEC_IMAGE_VERIFY_SIG |
134 | static int image_verify_sig(const char *kernel, unsigned long kernel_len) | |
135 | { | |
136 | return verify_pefile_signature(kernel, kernel_len, NULL, | |
137 | VERIFYING_KEXEC_PE_SIGNATURE); | |
138 | } | |
139 | #endif | |
140 | ||
f3b70e50 AT |
141 | const struct kexec_file_ops kexec_image_ops = { |
142 | .probe = image_probe, | |
143 | .load = image_load, | |
732b7b93 AT |
144 | #ifdef CONFIG_KEXEC_IMAGE_VERIFY_SIG |
145 | .verify_sig = image_verify_sig, | |
146 | #endif | |
f3b70e50 | 147 | }; |