blob: 53d455cfee53c0ca98e7381159e7fee2c125b54f [file] [log] [blame]
Patrick Georgi7333a112020-05-08 20:48:04 +02001/* SPDX-License-Identifier: GPL-2.0-only */
Patrick Georgide36d332013-08-27 20:22:21 +02002
3#include <stdio.h>
4#include <stdlib.h>
5#include <string.h>
6
7#include "common.h"
8#include "cbfs.h"
9#include "linux.h"
10
Aaron Durbin4f3bb802014-03-26 22:57:55 -050011/* trampoline */
Stefan Reinauer5dda4df2015-12-01 17:58:08 -080012extern unsigned char trampoline[];
13extern unsigned int trampoline_len;
Aaron Durbin4f3bb802014-03-26 22:57:55 -050014
15/*
16 * Current max number of segments include:
17 *
18 * 1. parameters
19 * 2. kernel
20 * 3. trampoline
21 * 4. optional cmdline
22 * 5. optional initrd
23 * 6. terminating entry segment
24 */
25#define MAX_NUM_SEGMENTS 6
26
27struct bzpayload {
28 /* Input variables. */
29 int num_segments;
30 struct cbfs_payload_segment segs[MAX_NUM_SEGMENTS];
31 struct buffer parameters;
32 struct buffer kernel;
33 struct buffer trampoline;
34 struct buffer cmdline;
35 struct buffer initrd;
36 /* Output variables. */
Julius Wernerd4775652020-03-13 16:43:34 -070037 enum cbfs_compression algo;
Aaron Durbin4f3bb802014-03-26 22:57:55 -050038 comp_func_ptr compress;
39 struct buffer output;
40 size_t offset;
41 struct cbfs_payload_segment *out_seg;
42};
43
Julius Wernerd4775652020-03-13 16:43:34 -070044static int bzp_init(struct bzpayload *bzp, enum cbfs_compression algo)
Aaron Durbin4f3bb802014-03-26 22:57:55 -050045{
46 memset(bzp, 0, sizeof(*bzp));
47
48 /*
49 * Need at least the terminating entry segment.
50 */
51 bzp->num_segments = 1;
52
Aaron Durbin4f3bb802014-03-26 22:57:55 -050053 bzp->algo = algo;
54 bzp->compress = compression_function(algo);
55 if (bzp->compress == NULL) {
56 ERROR("Invalid compression algorithm specified.\n");
57 return -1;
58 }
59
60 return 0;
61}
62
63static int bzp_add_initrd(struct bzpayload *bzp, const char *fname)
64{
65 if (fname == NULL)
66 return 0;
67
68 if (buffer_from_file(&bzp->initrd, fname)) {
69 ERROR("could not open initrd.\n");
70 return -1;
71 }
72
73 bzp->num_segments++;
74
75 return 0;
76}
77
78static void bzp_add_segment(struct bzpayload *bzp, struct buffer *b, void *data,
79 size_t size)
80{
81 buffer_init(b, NULL, data, size);
82 bzp->num_segments++;
83}
84
Aaron Durbinb2757572014-05-08 11:54:25 -050085static int bzp_add_trampoline(struct bzpayload *bzp)
86{
Stefan Reinauer5dda4df2015-12-01 17:58:08 -080087 bzp_add_segment(bzp, &bzp->trampoline, trampoline,
Stefan Reinauer16c7e0f2015-12-01 17:58:58 -080088 trampoline_len);
Aaron Durbinb2757572014-05-08 11:54:25 -050089 return 0;
90}
Aaron Durbin4f3bb802014-03-26 22:57:55 -050091
92static int bzp_add_cmdline(struct bzpayload *bzp, char *cmdline)
93{
94 if (cmdline == NULL)
95 return 0;
96
97 bzp_add_segment(bzp, &bzp->cmdline, cmdline, strlen(cmdline) + 1);
98
99 return 0;
100}
101
102static int bzp_add_params(struct bzpayload *bzp, struct linux_params *params)
103{
104 bzp_add_segment(bzp, &bzp->parameters, params, sizeof(*params));
105
106 return 0;
107}
108
109static int bzp_add_kernel(struct bzpayload *bzp, const struct buffer *in,
110 size_t setup_size)
111{
112 char *input = buffer_get(in);
113 size_t kern_sz = buffer_size(in) - setup_size;
114
115 bzp_add_segment(bzp, &bzp->kernel, &input[setup_size], kern_sz);
116
117 return 0;
118}
119
120static int bzp_init_output(struct bzpayload *bzp, const char *name)
121{
122 size_t sz = 0;
123
124 sz += buffer_size(&bzp->parameters);
125 sz += buffer_size(&bzp->kernel);
126 sz += buffer_size(&bzp->trampoline);
127 sz += buffer_size(&bzp->cmdline);
128 sz += buffer_size(&bzp->initrd);
129
130 bzp->offset = bzp->num_segments * sizeof(struct cbfs_payload_segment);
131 sz += bzp->offset;
132
133 if (buffer_create(&bzp->output, sz, name) != 0)
134 return -1;
135
136 bzp->out_seg = &bzp->segs[0];
137
138 return 0;
139}
140
141static void bzp_output_segment(struct bzpayload *bzp, struct buffer *b,
142 uint32_t type, uint64_t load_addr)
143{
144 struct buffer out;
145 struct cbfs_payload_segment *seg;
146 int len = 0;
147
148 /* Don't process empty buffers. */
149 if (b != NULL && buffer_size(b) == 0)
150 return;
151
152 seg = bzp->out_seg;
153 seg->type = type;
154 seg->load_addr = load_addr;
155 bzp->out_seg++;
156
157 /* No buffer associated with segment. */
158 if (b == NULL)
159 return;
160
161 /* Use a temp buffer for easier management. */
162 buffer_splice(&out, &bzp->output, bzp->offset, buffer_size(b));
163
164 seg->mem_len = buffer_size(b);
165 seg->offset = bzp->offset;
166 bzp->compress(buffer_get(b), buffer_size(b), buffer_get(&out), &len);
167 seg->compression = bzp->algo;
168 seg->len = len;
169
170 /* Update output offset. */
171 bzp->offset += len;
172}
173
Patrick Georgide36d332013-08-27 20:22:21 +0200174/* TODO:
175 * handle special arguments
176 * mem= argument - only affects loading decisions (kernel + initrd), not e820 -> build time
177 * vga= argument (FILO ignores this)
178 * add support for more parameters to trampoline:
179 * alt_mem_k, ext_mem_k (not strictly necessary since e820 takes precedence)
180 * framebuffer/console values
181 *
182 * larger work:
183 * is compress() safe to use in a size constrained buffer? ie. do(es) the
184 * compression algorithm(s) stop once the compression result reaches input
185 * size (ie. incompressible data)?
186 */
187int parse_bzImage_to_payload(const struct buffer *input,
188 struct buffer *output, const char *initrd_name,
Julius Wernerd4775652020-03-13 16:43:34 -0700189 char *cmdline, enum cbfs_compression algo)
Patrick Georgide36d332013-08-27 20:22:21 +0200190{
Aaron Durbin4f3bb802014-03-26 22:57:55 -0500191 struct bzpayload bzp;
Patrick Georgide36d332013-08-27 20:22:21 +0200192 unsigned int initrd_base = 64*1024*1024;
Patrick Georgide36d332013-08-27 20:22:21 +0200193 struct linux_header *hdr = (struct linux_header *)input->data;
194 unsigned int setup_size = 4 * 512;
Aaron Durbin4f3bb802014-03-26 22:57:55 -0500195
196 if (bzp_init(&bzp, algo) != 0)
197 return -1;
198
Aaron Durbinb2757572014-05-08 11:54:25 -0500199 if (bzp_add_trampoline(&bzp) != 0)
200 return -1;
201
Aaron Durbin4f3bb802014-03-26 22:57:55 -0500202 if (bzp_add_initrd(&bzp, initrd_name) != 0)
203 return -1;
204
205 if (bzp_add_cmdline(&bzp, cmdline) != 0)
206 return -1;
207
Patrick Georgide36d332013-08-27 20:22:21 +0200208 if (hdr->setup_sects != 0) {
209 setup_size = (hdr->setup_sects + 1) * 512;
Ronald G. Minnichfa74e472016-09-28 10:43:12 -0700210 } else {
211 WARN("hdr->setup_sects is 0, which could cause boot problems.\n");
Patrick Georgide36d332013-08-27 20:22:21 +0200212 }
213
214 /* Setup parameter block. Imitate FILO. */
215 struct linux_params params;
Werner Zehbbf50892016-03-02 17:45:53 +0100216
217 memset(&params, 0, sizeof(struct linux_params));
Ronald G. Minnichfa74e472016-09-28 10:43:12 -0700218
Patrick Georgide36d332013-08-27 20:22:21 +0200219 params.mount_root_rdonly = hdr->root_flags;
220 params.orig_root_dev = hdr->root_dev;
Ronald G. Minnichfa74e472016-09-28 10:43:12 -0700221 params.init_size = hdr->init_size;
222
Patrick Georgide36d332013-08-27 20:22:21 +0200223 /* Sensible video defaults. Might be overridden on runtime by coreboot tables. */
224 params.orig_video_mode = 3;
225 params.orig_video_cols = 80;
226 params.orig_video_lines = 25;
227 params.orig_video_isVGA = 1;
228 params.orig_video_points = 16;
229
230 params.loader_type = 0xff; /* Unregistered Linux loader */
231
232 if (cmdline != NULL) {
233 if (hdr->protocol_version < 0x202) {
234 params.cl_magic = CL_MAGIC_VALUE;
235 params.cl_offset = COMMAND_LINE_LOC - LINUX_PARAM_LOC;
236 } else {
237 params.cmd_line_ptr = COMMAND_LINE_LOC;
238 }
239 }
240
241 unsigned long kernel_base = 0x100000;
Manoj Guptacf846192016-11-14 11:19:30 -0800242 if ((hdr->protocol_version < 0x200) || !(hdr->loadflags & 1)) {
Patrick Georgide36d332013-08-27 20:22:21 +0200243 kernel_base = 0x1000; /* zImage kernel */
244 }
245 /* kernel prefers an address, so listen */
246 if ((hdr->protocol_version >= 0x20a) && (!(hdr->pref_address >> 32))) {
247 kernel_base = hdr->pref_address;
248 }
249 if (hdr->protocol_version >= 0x205) {
250 params.relocatable_kernel = hdr->relocatable_kernel;
251 params.kernel_alignment = hdr->kernel_alignment;
252 if (hdr->relocatable_kernel != 0) {
253 /* 16 MB should be way outside coreboot's playground,
254 * so if possible (relocatable kernel) use that to
255 * avoid a trampoline copy. */
Elyes Haouas898176a2022-10-14 10:01:05 +0200256 kernel_base = ALIGN_UP(16*1024*1024, params.kernel_alignment);
Ronald G. Minnichfa74e472016-09-28 10:43:12 -0700257 if (hdr->init_size == 0) {
258 ERROR("init_size 0 for relocatable kernel\n");
259 return -1;
260 }
Patrick Georgide36d332013-08-27 20:22:21 +0200261 }
262 }
263
264 /* We have a trampoline and use that, but it can simply use
265 * this information for its jump to real Linux. */
266 params.kernel_start = kernel_base;
267
Nico Huber6e133fa2023-07-14 23:15:17 +0200268 /* To make decisions based on the protocol version,
269 copy that as well. */
270 params.param_block_version = hdr->protocol_version;
271
Aaron Durbin4f3bb802014-03-26 22:57:55 -0500272 if (bzp_add_kernel(&bzp, input, setup_size) != 0)
273 return -1;
Patrick Georgide36d332013-08-27 20:22:21 +0200274
Aaron Durbin4f3bb802014-03-26 22:57:55 -0500275 if (buffer_size(&bzp.initrd) != 0) {
Patrick Georgide36d332013-08-27 20:22:21 +0200276 /* TODO: this is a bit of a hack. Linux recommends to store
277 * initrd near to end-of-mem, but that's hard to do on build
278 * time. It definitely fails to read the image if it's too
279 * close to the kernel, so give it some room.
280 */
Aaron Durbin4f3bb802014-03-26 22:57:55 -0500281 initrd_base = kernel_base + buffer_size(&bzp.kernel);
Elyes Haouas898176a2022-10-14 10:01:05 +0200282 initrd_base = ALIGN_UP(initrd_base, 64*1024*1024);
Patrick Georgide36d332013-08-27 20:22:21 +0200283
284 params.initrd_start = initrd_base;
Aaron Durbin4f3bb802014-03-26 22:57:55 -0500285 params.initrd_size = buffer_size(&bzp.initrd);
Patrick Georgide36d332013-08-27 20:22:21 +0200286 }
287
Aaron Durbin4f3bb802014-03-26 22:57:55 -0500288 if (bzp_add_params(&bzp, &params) != 0)
Patrick Georgide36d332013-08-27 20:22:21 +0200289 return -1;
Patrick Georgide36d332013-08-27 20:22:21 +0200290
Aaron Durbin4f3bb802014-03-26 22:57:55 -0500291 if (bzp_init_output(&bzp, input->name) != 0)
292 return -1;
Patrick Georgide36d332013-08-27 20:22:21 +0200293
294 /* parameter block */
Aaron Durbin4f3bb802014-03-26 22:57:55 -0500295 bzp_output_segment(&bzp, &bzp.parameters,
296 PAYLOAD_SEGMENT_DATA, LINUX_PARAM_LOC);
Patrick Georgide36d332013-08-27 20:22:21 +0200297
298 /* code block */
Aaron Durbin4f3bb802014-03-26 22:57:55 -0500299 bzp_output_segment(&bzp, &bzp.kernel,
300 PAYLOAD_SEGMENT_CODE, kernel_base);
Patrick Georgide36d332013-08-27 20:22:21 +0200301
302 /* trampoline */
Aaron Durbin4f3bb802014-03-26 22:57:55 -0500303 bzp_output_segment(&bzp, &bzp.trampoline,
Curt Brune3c12cb02014-08-29 10:43:36 -0700304 PAYLOAD_SEGMENT_CODE, TRAMPOLINE_ENTRY_LOC);
Patrick Georgide36d332013-08-27 20:22:21 +0200305
Aaron Durbin4f3bb802014-03-26 22:57:55 -0500306 /* cmdline */
307 bzp_output_segment(&bzp, &bzp.cmdline,
308 PAYLOAD_SEGMENT_DATA, COMMAND_LINE_LOC);
Patrick Georgide36d332013-08-27 20:22:21 +0200309
Aaron Durbin4f3bb802014-03-26 22:57:55 -0500310 /* initrd */
311 bzp_output_segment(&bzp, &bzp.initrd,
312 PAYLOAD_SEGMENT_DATA, initrd_base);
Patrick Georgide36d332013-08-27 20:22:21 +0200313
Aaron Durbin4f3bb802014-03-26 22:57:55 -0500314 /* Terminating entry segment. */
Curt Brune3c12cb02014-08-29 10:43:36 -0700315 bzp_output_segment(&bzp, NULL, PAYLOAD_SEGMENT_ENTRY, TRAMPOLINE_ENTRY_LOC);
Patrick Georgide36d332013-08-27 20:22:21 +0200316
Aaron Durbin4f3bb802014-03-26 22:57:55 -0500317 /* Set size of buffer taking into account potential compression. */
318 buffer_set_size(&bzp.output, bzp.offset);
319 /* Make passed-in output buffer be valid. */
320 buffer_clone(output, &bzp.output);
Patrick Georgide36d332013-08-27 20:22:21 +0200321
Aaron Durbin4f3bb802014-03-26 22:57:55 -0500322 /* Serialize the segments with the correct encoding. */
323 xdr_segs(output, bzp.segs, bzp.num_segments);
Patrick Georgide36d332013-08-27 20:22:21 +0200324 return 0;
325}