xref: /aosp_15_r20/external/coreboot/util/cbfstool/ifittool.c (revision b9411a12aaaa7e1e6a6fb7c5e057f44ee179a49c)
1 /* cbfstool, CLI utility for creating rmodules */
2 /* SPDX-License-Identifier: GPL-2.0-only */
3 
4 #include <stdio.h>
5 #include <stdlib.h>
6 #include <string.h>
7 #include <unistd.h>
8 #include <getopt.h>
9 
10 #include "common.h"
11 #include "cbfs_image.h"
12 #include "partitioned_file.h"
13 #include "fit.h"
14 
15 /* Global variables */
16 partitioned_file_t *image_file;
17 
18 static const char *optstring  = "H:j:f:r:d:t:n:s:cAaDvhF?";
19 static struct option long_options[] = {
20 	{"file",            required_argument, 0, 'f' },
21 	{"region",          required_argument, 0, 'r' },
22 	{"add-cbfs-entry",  no_argument,       0, 'a' },
23 	{"add-region",      no_argument,       0, 'A' },
24 	{"del-entry",       required_argument, 0, 'd' },
25 	{"clear-table",     no_argument,       0, 'c' },
26 	{"set-fit-pointer", no_argument,       0, 'F' },
27 	{"fit-type",        required_argument, 0, 't' },
28 	{"cbfs-filename",   required_argument, 0, 'n' },
29 	{"max-table-size",  required_argument, 0, 's' },
30 	{"topswap-size",    required_argument, 0, 'j' },
31 	{"dump",            no_argument,       0, 'D' },
32 	{"verbose",         no_argument,       0, 'v' },
33 	{"help",            no_argument,       0, 'h' },
34 	{"header-offset",   required_argument, 0, 'H' },
35 	{NULL,              0,                 0,  0  }
36 };
37 
usage(const char * name)38 static void usage(const char *name)
39 {
40 	printf(
41 		"ifittool: utility for modifying Intel Firmware Interface Table\n\n"
42 		"USAGE: %s [-h] [-H] [-v] [-D] [-c] <-f|--file name> <-s|--max-table-size size> <-r|--region fmap region> OPERATION\n"
43 		"\tOPERATION:\n"
44 		"\t\t-a|--add-entry        :   Add a CBFS file as new entry to FIT\n"
45 		"\t\t-A|--add-region       :   Add region as new entry to FIT (for microcodes)\n"
46 		"\t\t-d|--del-entry number :   Delete existing <number> entry\n"
47 		"\t\t-F|--set-fit-pointer  :   Set the FIT pointer to a CBFS file\n"
48 		"\t\t-t|--fit-type         :   Type of new entry\n"
49 		"\t\t-n|--name             :   The CBFS filename or region to add to table\n"
50 		"\tOPTIONAL ARGUMENTS:\n"
51 		"\t\t-h|--help             :   Display this text\n"
52 		"\t\t-H|--header-offset    :   Do not search for header, use this offset\n"
53 		"\t\t-v|--verbose          :   Be verbose (-v=INFO -vv=DEBUG output)\n"
54 		"\t\t-D|--dump             :   Dump FIT table (at end of operation)\n"
55 		"\t\t-c|--clear-table      :   Remove all existing entries (do not update)\n"
56 		"\t\t-j|--topswap-size     :   Use second FIT table if non zero\n"
57 		"\tREQUIRED ARGUMENTS:\n"
58 		"\t\t-f|--file name        :   The file containing the CBFS\n"
59 		"\t\t-s|--max-table-size   :   The number of possible FIT entries in table\n"
60 		"\t\t-r|--region           :   The FMAP region to operate on\n"
61 	, name);
62 }
63 
is_valid_topswap(size_t topswap_size)64 static int is_valid_topswap(size_t topswap_size)
65 {
66 	switch (topswap_size) {
67 	case (64 * KiB):
68 	case (128 * KiB):
69 	case (256 * KiB):
70 	case (512 * KiB):
71 	case (1 * MiB):
72 		break;
73 	default:
74 		ERROR("Invalid topswap_size %zd\n", topswap_size);
75 		ERROR("topswap can be 64K|128K|256K|512K|1M\n");
76 		return 0;
77 	}
78 	return 1;
79 }
80 
81 /*
82  * Converts between offsets from the start of the specified image region and
83  * "top-aligned" offsets from the top of the entire boot media. See comment
84  * below for convert_to_from_top_aligned() about forming addresses.
85  */
convert_to_from_absolute_top_aligned(const struct buffer * region,unsigned int offset)86 static unsigned int convert_to_from_absolute_top_aligned(
87 		const struct buffer *region, unsigned int offset)
88 {
89 	assert(region);
90 
91 	size_t image_size = partitioned_file_total_size(image_file);
92 
93 	return image_size - region->offset - offset;
94 }
95 
96 /*
97  * Converts between offsets from the start of the specified image region and
98  * "top-aligned" offsets from the top of the image region. Works in either
99  * direction: pass in one type of offset and receive the other type.
100  * N.B. A top-aligned offset is always a positive number, and should not be
101  * confused with a top-aligned *address*, which is its arithmetic inverse. */
convert_to_from_top_aligned(const struct buffer * region,unsigned int offset)102 static unsigned int convert_to_from_top_aligned(const struct buffer *region,
103 						unsigned int offset)
104 {
105 	assert(region);
106 
107 	/* Cover the situation where a negative base address is given by the
108 	 * user. Callers of this function negate it, so it'll be a positive
109 	 * number smaller than the region.
110 	 */
111 	if ((offset > 0) && (offset < region->size))
112 		return region->size - offset;
113 
114 	return convert_to_from_absolute_top_aligned(region, offset);
115 }
116 
117 /*
118  * Get a pointer from an offset. This function assumes the ROM is located
119  * in the host address space at [4G - romsize -> 4G). It also assume all
120  * pointers have values within this address range.
121  */
offset_to_ptr(fit_offset_converter_t helper,const struct buffer * region,int offset)122 static inline uint32_t offset_to_ptr(fit_offset_converter_t helper,
123 				     const struct buffer *region, int offset)
124 {
125 	return -helper(region, offset);
126 }
127 
128 enum fit_operation {
129 	NO_OP = 0,
130 	ADD_CBFS_OP,
131 	ADD_REGI_OP,
132 	DEL_OP,
133 	SET_FIT_PTR_OP
134 };
135 
main(int argc,char * argv[])136 int main(int argc, char *argv[])
137 {
138 	int c;
139 	const char *input_file = NULL;
140 	const char *name = NULL;
141 	const char *region_name = NULL;
142 	enum fit_operation op = NO_OP;
143 	bool dump = false, clear_table = false;
144 	size_t max_table_size = 0;
145 	size_t table_entry = 0;
146 	uint32_t addr = 0;
147 	size_t topswap_size = 0;
148 	enum fit_type fit_type = 0;
149 	uint32_t headeroffset = HEADER_OFFSET_UNKNOWN;
150 
151 	verbose = 0;
152 
153 	if (argc < 4) {
154 		usage(argv[0]);
155 		return 1;
156 	}
157 
158 	while (1) {
159 		int optindex = 0;
160 		char *suffix = NULL;
161 
162 		c = getopt_long(argc, argv, optstring, long_options, &optindex);
163 
164 		if (c == -1)
165 			break;
166 
167 		switch (c) {
168 		case 'h':
169 			usage(argv[0]);
170 			return 1;
171 		case 'a':
172 			if (op != NO_OP) {
173 				ERROR("specified multiple actions at once\n");
174 				usage(argv[0]);
175 				return 1;
176 			}
177 			op = ADD_CBFS_OP;
178 			break;
179 		case 'A':
180 			if (op != NO_OP) {
181 				ERROR("specified multiple actions at once\n");
182 				usage(argv[0]);
183 				return 1;
184 			}
185 			op = ADD_REGI_OP;
186 			break;
187 		case 'c':
188 			clear_table = true;
189 			break;
190 		case 'd':
191 			if (op != NO_OP) {
192 				ERROR("specified multiple actions at once\n");
193 				usage(argv[0]);
194 				return 1;
195 			}
196 			op = DEL_OP;
197 			table_entry = atoi(optarg);
198 			break;
199 		case 'D':
200 			dump = true;
201 			break;
202 		case 'f':
203 			input_file = optarg;
204 			break;
205 		case 'F':
206 			if (op != NO_OP) {
207 				ERROR("specified multiple actions at once\n");
208 				usage(argv[0]);
209 				return 1;
210 			}
211 			op = SET_FIT_PTR_OP;
212 			break;
213 		case 'H':
214 			headeroffset = strtoul(optarg, &suffix, 0);
215 			if (!*optarg || (suffix && *suffix)) {
216 				ERROR("Invalid header offset '%s'.\n", optarg);
217 				return 1;
218 			}
219 			break;
220 		case 'j':
221 			topswap_size = strtol(optarg, NULL, 0);
222 			if (!is_valid_topswap(topswap_size))
223 				return 1;
224 			break;
225 		case 'n':
226 			name = optarg;
227 			break;
228 		case 'r':
229 			region_name = optarg;
230 			break;
231 		case 's':
232 			max_table_size = atoi(optarg);
233 			break;
234 		case 't':
235 			fit_type = atoi(optarg);
236 			break;
237 		case 'v':
238 			verbose++;
239 			break;
240 		default:
241 			break;
242 		}
243 	}
244 
245 	if (input_file == NULL) {
246 		ERROR("No input file given\n");
247 		usage(argv[0]);
248 		return 1;
249 	}
250 
251 	if (op == ADD_CBFS_OP || op == ADD_REGI_OP) {
252 		if (fit_type == 0) {
253 			ERROR("Adding FIT entry, but no type given\n");
254 			usage(argv[0]);
255 			return 1;
256 		} else if (name == NULL) {
257 			ERROR("Adding FIT entry, but no name set\n");
258 			usage(argv[0]);
259 			return 1;
260 		} else if (max_table_size == 0) {
261 			ERROR("Maximum table size not given\n");
262 			usage(argv[0]);
263 			return 1;
264 		}
265 	}
266 
267 	if (op == SET_FIT_PTR_OP) {
268 		if (name == NULL) {
269 			ERROR("Adding FIT entry, but no name set\n");
270 			usage(argv[0]);
271 			return 1;
272 		}
273 	}
274 
275 	if (!region_name) {
276 		ERROR("Region not given\n");
277 		usage(argv[0]);
278 		return 1;
279 	}
280 
281 	image_file = partitioned_file_reopen(input_file,
282 					     op != NO_OP || clear_table);
283 
284 	struct buffer image_region;
285 
286 	if (!partitioned_file_read_region(&image_region, image_file,
287 					  region_name)) {
288 		partitioned_file_close(image_file);
289 		ERROR("The image will be left unmodified.\n");
290 		return 1;
291 	}
292 
293 	struct buffer bootblock;
294 	// The bootblock is part of the CBFS on x86
295 	buffer_clone(&bootblock, &image_region);
296 
297 	struct cbfs_image image;
298 	if (cbfs_image_from_buffer(&image, &image_region, headeroffset)) {
299 		partitioned_file_close(image_file);
300 		return 1;
301 	}
302 
303 	struct fit_table *fit = NULL;
304 	if (op != SET_FIT_PTR_OP) {
305 		fit = fit_get_table(&bootblock, convert_to_from_top_aligned, topswap_size);
306 		if (!fit) {
307 			partitioned_file_close(image_file);
308 			ERROR("FIT not found.\n");
309 			return 1;
310 		}
311 		if (clear_table) {
312 			if (fit_clear_table(fit)) {
313 				partitioned_file_close(image_file);
314 				ERROR("Failed to clear table.\n");
315 				return 1;
316 			}
317 		}
318 	}
319 
320 	switch (op) {
321 	case ADD_REGI_OP:
322 	{
323 		struct buffer region;
324 
325 		if (partitioned_file_read_region(&region, image_file, name)) {
326 			addr = -convert_to_from_top_aligned(&region, 0);
327 		} else {
328 			partitioned_file_close(image_file);
329 			return 1;
330 		}
331 
332 		if (fit_add_entry(fit, addr, 0, fit_type, max_table_size)) {
333 			partitioned_file_close(image_file);
334 			ERROR("Adding type %u FIT entry\n", fit_type);
335 			return 1;
336 		}
337 		break;
338 	}
339 	case ADD_CBFS_OP:
340 	{
341 		if (fit_type == FIT_TYPE_MICROCODE) {
342 			if (fit_add_microcode_file(fit, &image, name,
343 						   convert_to_from_top_aligned,
344 						   max_table_size)) {
345 				return 1;
346 			}
347 		} else {
348 			uint32_t offset, len;
349 			struct cbfs_file *cbfs_file;
350 
351 			cbfs_file = cbfs_get_entry(&image, name);
352 			if (!cbfs_file) {
353 				partitioned_file_close(image_file);
354 				ERROR("%s not found in CBFS.\n", name);
355 				return 1;
356 			}
357 
358 			len = be32toh(cbfs_file->len);
359 			offset = offset_to_ptr(convert_to_from_top_aligned,
360 					&image.buffer,
361 					cbfs_get_entry_addr(&image, cbfs_file) +
362 					be32toh(cbfs_file->offset));
363 
364 
365 			if (fit_add_entry(fit, offset, len, fit_type,
366 					  max_table_size)) {
367 				partitioned_file_close(image_file);
368 				ERROR("Adding type %u FIT entry\n", fit_type);
369 				return 1;
370 			}
371 		}
372 		break;
373 	}
374 	case SET_FIT_PTR_OP:
375 	{
376 		uint32_t fit_address;
377 		struct cbfs_file *cbfs_file = cbfs_get_entry(&image, name);
378 		if (!cbfs_file) {
379 			partitioned_file_close(image_file);
380 			ERROR("%s not found in CBFS.\n", name);
381 			return 1;
382 		}
383 
384 		fit_address = offset_to_ptr(convert_to_from_top_aligned, &image.buffer,
385 				       cbfs_get_entry_addr(&image, cbfs_file)
386 					       + be32toh(cbfs_file->offset));
387 
388 
389 		if (set_fit_pointer(&bootblock, fit_address, convert_to_from_top_aligned,
390 				    topswap_size)) {
391 			partitioned_file_close(image_file);
392 			ERROR("%s is not a FIT table\n", name);
393 			return 1;
394 		}
395 		fit = fit_get_table(&bootblock, convert_to_from_top_aligned, topswap_size);
396 
397 		if (clear_table) {
398 			if (fit_clear_table(fit)) {
399 				partitioned_file_close(image_file);
400 				ERROR("Failed to clear table.\n");
401 				return 1;
402 			}
403 		}
404 
405 		break;
406 	}
407 	case DEL_OP:
408 	{
409 		if (fit_delete_entry(fit, table_entry)) {
410 			partitioned_file_close(image_file);
411 			ERROR("Deleting FIT entry %zu failed\n", table_entry);
412 			return 1;
413 		}
414 		break;
415 	}
416 	case NO_OP:
417 	default:
418 		break;
419 	}
420 
421 	if (op != NO_OP || clear_table) {
422 		if (!partitioned_file_write_region(image_file, &bootblock)) {
423 			ERROR("Failed to write changes to disk.\n");
424 			partitioned_file_close(image_file);
425 			return 1;
426 		}
427 	}
428 
429 	if (dump) {
430 		if (fit_dump(fit)) {
431 			partitioned_file_close(image_file);
432 			return 1;
433 		}
434 	}
435 
436 	partitioned_file_close(image_file);
437 
438 	return 0;
439 }
440