blob: d0d9431804b0a0255c0f9e62c5072d0a9e9172db [file] [log] [blame]
Patrick Georgiea063cb2020-05-08 19:28:13 +02001/* cbfstool, CLI utility for creating rmodules */
Patrick Georgi7333a112020-05-08 20:48:04 +02002/* SPDX-License-Identifier: GPL-2.0-only */
Philipp Deppenwiese5ada0022018-11-20 13:54:49 +01003
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 */
16partitioned_file_t *image_file;
17
Arthur Heymanse9e4e542021-02-17 17:34:44 +010018static const char *optstring = "H:j:f:r:d:t:n:s:cAaDvhF?";
Philipp Deppenwiese5ada0022018-11-20 13:54:49 +010019static struct option long_options[] = {
Arthur Heymanse9e4e542021-02-17 17:34:44 +010020 {"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 }
Philipp Deppenwiese5ada0022018-11-20 13:54:49 +010036};
37
38static 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"
Arthur Heymanse9e4e542021-02-17 17:34:44 +010047 "\t\t-F|--set-fit-pointer : Set the FIT pointer to a CBFS file\n"
Philipp Deppenwiese5ada0022018-11-20 13:54:49 +010048 "\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"
Maximilian Brune60c6a5a2023-03-06 02:40:58 +010053 "\t\t-v|--verbose : Be verbose (-v=INFO -vv=DEBUG output)\n"
Philipp Deppenwiese5ada0022018-11-20 13:54:49 +010054 "\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
64static 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 */
86static 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. */
102static 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 */
122static 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
128enum fit_operation {
129 NO_OP = 0,
130 ADD_CBFS_OP,
131 ADD_REGI_OP,
Arthur Heymanse9e4e542021-02-17 17:34:44 +0100132 DEL_OP,
133 SET_FIT_PTR_OP
Philipp Deppenwiese5ada0022018-11-20 13:54:49 +0100134};
135
136int 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;
Jakub Czapigaaa415632022-08-01 16:01:28 +0200149 uint32_t headeroffset = HEADER_OFFSET_UNKNOWN;
Philipp Deppenwiese5ada0022018-11-20 13:54:49 +0100150
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;
Philipp Deppenwiese5ada0022018-11-20 13:54:49 +0100187 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;
Arthur Heymanse9e4e542021-02-17 17:34:44 +0100205 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;
Philipp Deppenwiese5ada0022018-11-20 13:54:49 +0100213 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':
Pandya, Varshit B36cc6642019-07-01 16:27:01 +0530221 topswap_size = strtol(optarg, NULL, 0);
Philipp Deppenwiese5ada0022018-11-20 13:54:49 +0100222 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 }
Philipp Deppenwiese5ada0022018-11-20 13:54:49 +0100266
Arthur Heymanse9e4e542021-02-17 17:34:44 +0100267 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
Philipp Deppenwiese5ada0022018-11-20 13:54:49 +0100275 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
Arthur Heymanse9e4e542021-02-17 17:34:44 +0100303 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 }
Arthur Heymans8b82c6b92022-03-23 19:58:44 +0100311 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 }
Philipp Deppenwiese5ada0022018-11-20 13:54:49 +0100317 }
318 }
319
320 switch (op) {
321 case ADD_REGI_OP:
322 {
323 struct buffer region;
Philipp Deppenwiese5ada0022018-11-20 13:54:49 +0100324
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
Alex James02001a382021-12-19 16:41:59 -0600358 len = be32toh(cbfs_file->len);
Philipp Deppenwiese5ada0022018-11-20 13:54:49 +0100359 offset = offset_to_ptr(convert_to_from_top_aligned,
360 &image.buffer,
361 cbfs_get_entry_addr(&image, cbfs_file) +
Alex James02001a382021-12-19 16:41:59 -0600362 be32toh(cbfs_file->offset));
Philipp Deppenwiese5ada0022018-11-20 13:54:49 +0100363
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 }
Arthur Heymanse9e4e542021-02-17 17:34:44 +0100374 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)
Alex James02001a382021-12-19 16:41:59 -0600386 + be32toh(cbfs_file->offset));
Arthur Heymanse9e4e542021-02-17 17:34:44 +0100387
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);
Arthur Heymans8b82c6b92022-03-23 19:58:44 +0100396
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
Arthur Heymanse9e4e542021-02-17 17:34:44 +0100405 break;
406 }
Philipp Deppenwiese5ada0022018-11-20 13:54:49 +0100407 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}