xref: /aosp_15_r20/external/coreboot/util/cbfstool/cbfs-payload-linux.c (revision b9411a12aaaa7e1e6a6fb7c5e057f44ee179a49c)
1 /* SPDX-License-Identifier: GPL-2.0-only */
2 
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 
11 /* trampoline */
12 extern unsigned char trampoline[];
13 extern unsigned int trampoline_len;
14 
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 
27 struct 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. */
37 	enum cbfs_compression algo;
38 	comp_func_ptr compress;
39 	struct buffer output;
40 	size_t offset;
41 	struct cbfs_payload_segment *out_seg;
42 };
43 
bzp_init(struct bzpayload * bzp,enum cbfs_compression algo)44 static int bzp_init(struct bzpayload *bzp, enum cbfs_compression algo)
45 {
46 	memset(bzp, 0, sizeof(*bzp));
47 
48 	/*
49 	 * Need at least the terminating entry segment.
50 	 */
51 	bzp->num_segments = 1;
52 
53 	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 
bzp_add_initrd(struct bzpayload * bzp,const char * fname)63 static 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 
bzp_add_segment(struct bzpayload * bzp,struct buffer * b,void * data,size_t size)78 static 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 
bzp_add_trampoline(struct bzpayload * bzp)85 static int bzp_add_trampoline(struct bzpayload *bzp)
86 {
87 	bzp_add_segment(bzp, &bzp->trampoline, trampoline,
88 			trampoline_len);
89 	return 0;
90 }
91 
bzp_add_cmdline(struct bzpayload * bzp,char * cmdline)92 static 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 
bzp_add_params(struct bzpayload * bzp,struct linux_params * params)102 static 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 
bzp_add_kernel(struct bzpayload * bzp,const struct buffer * in,size_t setup_size)109 static 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 
bzp_init_output(struct bzpayload * bzp,const char * name)120 static 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 
bzp_output_segment(struct bzpayload * bzp,struct buffer * b,uint32_t type,uint64_t load_addr)141 static 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 
174 /* 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  */
parse_bzImage_to_payload(const struct buffer * input,struct buffer * output,const char * initrd_name,char * cmdline,enum cbfs_compression algo)187 int parse_bzImage_to_payload(const struct buffer *input,
188 			     struct buffer *output, const char *initrd_name,
189 			     char *cmdline, enum cbfs_compression algo)
190 {
191 	struct bzpayload bzp;
192 	unsigned int initrd_base = 64*1024*1024;
193 	struct linux_header *hdr = (struct linux_header *)input->data;
194 	unsigned int setup_size = 4 * 512;
195 
196 	if (bzp_init(&bzp, algo) != 0)
197 		return -1;
198 
199 	if (bzp_add_trampoline(&bzp) != 0)
200 		return -1;
201 
202 	if (bzp_add_initrd(&bzp, initrd_name) != 0)
203 		return -1;
204 
205 	if (bzp_add_cmdline(&bzp, cmdline) != 0)
206 		return -1;
207 
208 	if (hdr->setup_sects != 0) {
209 		setup_size = (hdr->setup_sects + 1) * 512;
210 	} else {
211 		WARN("hdr->setup_sects is 0, which could cause boot problems.\n");
212 	}
213 
214 	/* Setup parameter block. Imitate FILO. */
215 	struct linux_params params;
216 
217 	memset(&params, 0, sizeof(struct linux_params));
218 
219 	params.mount_root_rdonly = hdr->root_flags;
220 	params.orig_root_dev = hdr->root_dev;
221 	params.init_size = hdr->init_size;
222 
223 	/* 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;
242 	if ((hdr->protocol_version < 0x200) || !(hdr->loadflags & 1)) {
243 		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. */
256 			kernel_base = ALIGN_UP(16*1024*1024, params.kernel_alignment);
257 			if (hdr->init_size == 0) {
258 				ERROR("init_size 0 for relocatable kernel\n");
259 				return -1;
260 			}
261 		}
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 
268 	/* To make decisions based on the protocol version,
269 	   copy that as well. */
270 	params.param_block_version = hdr->protocol_version;
271 
272 	if (bzp_add_kernel(&bzp, input, setup_size) != 0)
273 		return -1;
274 
275 	if (buffer_size(&bzp.initrd) != 0) {
276 		/* 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 		 */
281 		initrd_base = kernel_base + buffer_size(&bzp.kernel);
282 		initrd_base = ALIGN_UP(initrd_base, 64*1024*1024);
283 
284 		params.initrd_start = initrd_base;
285 		params.initrd_size = buffer_size(&bzp.initrd);
286 	}
287 
288 	if (bzp_add_params(&bzp, &params) != 0)
289 		return -1;
290 
291 	if (bzp_init_output(&bzp, input->name) != 0)
292 		return -1;
293 
294 	/* parameter block */
295 	bzp_output_segment(&bzp, &bzp.parameters,
296 	                   PAYLOAD_SEGMENT_DATA, LINUX_PARAM_LOC);
297 
298 	/* code block */
299 	bzp_output_segment(&bzp, &bzp.kernel,
300 	                   PAYLOAD_SEGMENT_CODE, kernel_base);
301 
302 	/* trampoline */
303 	bzp_output_segment(&bzp, &bzp.trampoline,
304 	                   PAYLOAD_SEGMENT_CODE, TRAMPOLINE_ENTRY_LOC);
305 
306 	/* cmdline */
307 	bzp_output_segment(&bzp, &bzp.cmdline,
308 	                   PAYLOAD_SEGMENT_DATA, COMMAND_LINE_LOC);
309 
310 	/* initrd */
311 	bzp_output_segment(&bzp, &bzp.initrd,
312 	                   PAYLOAD_SEGMENT_DATA, initrd_base);
313 
314 	/* Terminating entry segment. */
315 	bzp_output_segment(&bzp, NULL, PAYLOAD_SEGMENT_ENTRY, TRAMPOLINE_ENTRY_LOC);
316 
317 	/* 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);
321 
322 	/* Serialize the segments with the correct encoding. */
323 	xdr_segs(output, bzp.segs, bzp.num_segments);
324 	return 0;
325 }
326