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