/ util / cbfstool / ifittool.c
ifittool.c
  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  
 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  
 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   */
 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. */
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   */
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  
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  }