1 /*
2 * File: elftosb.cpp
3 *
4 * Copyright (c) Freescale Semiconductor, Inc. All rights reserved.
5 * See included license file for license details.
6 */
7
8 #include "stdafx.h"
9 #include <iostream>
10 #include <fstream>
11 #include <sstream>
12 #include <stdlib.h>
13 #include <stdexcept>
14 #include "ConversionController.h"
15 #include "options.h"
16 #include "Version.h"
17 #include "EncoreBootImage.h"
18 #include "smart_ptr.h"
19 #include "Logging.h"
20 #include "EncoreBootImageGenerator.h"
21 #include "SearchPath.h"
22 #include "format_string.h"
23
24 //! An array of strings.
25 typedef std::vector<std::string> string_vector_t;
26
27 //! The tool's name.
28 const char k_toolName[] = "elftosb";
29
30 //! Current version number for the tool.
31 const char k_version[] = "2.6.1";
32
33 //! Copyright string.
34 const char k_copyright[] = "Copyright (c) 2004-2010 Freescale Semiconductor, Inc.\nAll rights reserved.";
35
36 static const char * k_optionsDefinition[] = {
37 "?|help",
38 "v|version",
39 "f:chip-family <family>",
40 "c:command <file>",
41 "o:output <file>",
42 "P:product <version>",
43 "C:component <version>",
44 "k:key <file>",
45 "z|zero-key",
46 "D:define <const>",
47 "O:option <option>",
48 "d|debug",
49 "q|quiet",
50 "V|verbose",
51 "p:search-path <path>",
52 NULL
53 };
54
55 //! Help string.
56 const char k_usageText[] = "\nOptions:\n\
57 -?/--help Show this help\n\
58 -v/--version Display tool version\n\
59 -f/--chip-family <family> Select the chip family (default is 37xx)\n\
60 -c/--command <file> Use this command file\n\
61 -o/--output <file> Write output to this file\n\
62 -p/--search-path <path> Add a search path used to find input files\n\
63 -P/--product <version Set product version\n\
64 -C/--component <version> Set component version\n\
65 -k/--key <file> Add OTP key, enable encryption\n\
66 -z/--zero-key Add default key of all zeroes\n\
67 -D/--define <const>=<int> Define or override a constant value\n\
68 -O/--option <name>=<value> Set or override a processing option\n\
69 -d/--debug Enable debug output\n\
70 -q/--quiet Output only warnings and errors\n\
71 -V/--verbose Print extra detailed log information\n\n";
72
73 // prototypes
74 int main(int argc, char* argv[], char* envp[]);
75
76 /*!
77 * \brief Class that encapsulates the elftosb tool.
78 *
79 * A single global logger instance is created during object construction. It is
80 * never freed because we need it up to the last possible minute, when an
81 * exception could be thrown.
82 */
83 class elftosbTool
84 {
85 protected:
86 //! Supported chip families.
87 enum chip_family_t
88 {
89 k37xxFamily, //!< 37xx series.
90 kMX28Family, //!< Catskills series.
91 };
92
93 /*!
94 * \brief A structure describing an entry in the table of chip family names.
95 */
96 struct FamilyNameTableEntry
97 {
98 const char * const name;
99 chip_family_t family;
100 };
101
102 //! \brief Table that maps from family name strings to chip family constants.
103 static const FamilyNameTableEntry kFamilyNameTable[];
104
105 int m_argc; //!< Number of command line arguments.
106 char ** m_argv; //!< String value for each command line argument.
107 StdoutLogger * m_logger; //!< Singleton logger instance.
108 string_vector_t m_keyFilePaths; //!< Paths to OTP key files.
109 string_vector_t m_positionalArgs; //!< Arguments coming after explicit options.
110 bool m_isVerbose; //!< Whether the verbose flag was turned on.
111 bool m_useDefaultKey; //!< Include a default (zero) crypto key.
112 const char * m_commandFilePath; //!< Path to the elftosb command file.
113 const char * m_outputFilePath; //!< Path to the output .sb file.
114 const char * m_searchPath; //!< Optional search path for input files.
115 elftosb::version_t m_productVersion; //!< Product version specified on command line.
116 elftosb::version_t m_componentVersion; //!< Component version specified on command line.
117 bool m_productVersionSpecified; //!< True if the product version was specified on the command line.
118 bool m_componentVersionSpecified; //!< True if the component version was specified on the command line.
119 chip_family_t m_family; //!< Chip family that the output file is formatted for.
120 elftosb::ConversionController m_controller; //!< Our conversion controller instance.
121
122 public:
123 /*!
124 * Constructor.
125 *
126 * Creates the singleton logger instance.
127 */
elftosbTool(int argc,char * argv[])128 elftosbTool(int argc, char * argv[])
129 : m_argc(argc),
130 m_argv(argv),
131 m_logger(0),
132 m_keyFilePaths(),
133 m_positionalArgs(),
134 m_isVerbose(false),
135 m_useDefaultKey(false),
136 m_commandFilePath(NULL),
137 m_outputFilePath(NULL),
138 m_searchPath(NULL),
139 m_productVersion(),
140 m_componentVersion(),
141 m_productVersionSpecified(false),
142 m_componentVersionSpecified(false),
143 m_family(k37xxFamily),
144 m_controller()
145 {
146 // create logger instance
147 m_logger = new StdoutLogger();
148 m_logger->setFilterLevel(Logger::INFO);
149 Log::setLogger(m_logger);
150 }
151
152 /*!
153 * Destructor.
154 */
~elftosbTool()155 ~elftosbTool()
156 {
157 }
158
159 /*!
160 * \brief Searches the family name table.
161 *
162 * \retval true The \a name was found in the table, and \a family is valid.
163 * \retval false No matching family name was found. The \a family argument is not modified.
164 */
lookupFamilyName(const char * name,chip_family_t * family)165 bool lookupFamilyName(const char * name, chip_family_t * family)
166 {
167 // Create a local read-write copy of the argument string.
168 std::string familyName(name);
169
170 // Convert the argument string to lower case for case-insensitive comparison.
171 for (int n=0; n < familyName.length(); n++)
172 {
173 familyName[n] = tolower(familyName[n]);
174 }
175
176 // Exit the loop if we hit the NULL terminator entry.
177 const FamilyNameTableEntry * entry = &kFamilyNameTable[0];
178 for (; entry->name; entry++)
179 {
180 // Compare lowercased name with the table entry.
181 if (familyName == entry->name)
182 {
183 *family = entry->family;
184 return true;
185 }
186 }
187
188 // Failed to find a matching name.
189 return false;
190 }
191
192 /*!
193 * Reads the command line options passed into the constructor.
194 *
195 * This method can return a return code to its caller, which will cause the
196 * tool to exit immediately with that return code value. Normally, though, it
197 * will return -1 to signal that the tool should continue to execute and
198 * all options were processed successfully.
199 *
200 * The Options class is used to parse command line options. See
201 * #k_optionsDefinition for the list of options and #k_usageText for the
202 * descriptive help for each option.
203 *
204 * \retval -1 The options were processed successfully. Let the tool run normally.
205 * \return A zero or positive result is a return code value that should be
206 * returned from the tool as it exits immediately.
207 */
processOptions()208 int processOptions()
209 {
210 Options options(*m_argv, k_optionsDefinition);
211 OptArgvIter iter(--m_argc, ++m_argv);
212
213 // process command line options
214 int optchar;
215 const char * optarg;
216 while (optchar = options(iter, optarg))
217 {
218 switch (optchar)
219 {
220 case '?':
221 printUsage(options);
222 return 0;
223
224 case 'v':
225 printf("%s %s\n%s\n", k_toolName, k_version, k_copyright);
226 return 0;
227
228 case 'f':
229 if (!lookupFamilyName(optarg, &m_family))
230 {
231 Log::log(Logger::ERROR, "error: unknown chip family '%s'\n", optarg);
232 printUsage(options);
233 return 0;
234 }
235 break;
236
237 case 'c':
238 m_commandFilePath = optarg;
239 break;
240
241 case 'o':
242 m_outputFilePath = optarg;
243 break;
244
245 case 'P':
246 m_productVersion.set(optarg);
247 m_productVersionSpecified = true;
248 break;
249
250 case 'C':
251 m_componentVersion.set(optarg);
252 m_componentVersionSpecified = true;
253 break;
254
255 case 'k':
256 m_keyFilePaths.push_back(optarg);
257 break;
258
259 case 'z':
260 m_useDefaultKey = true;
261 break;
262
263 case 'D':
264 overrideVariable(optarg);
265 break;
266
267 case 'O':
268 overrideOption(optarg);
269 break;
270
271 case 'd':
272 Log::getLogger()->setFilterLevel(Logger::DEBUG);
273 break;
274
275 case 'q':
276 Log::getLogger()->setFilterLevel(Logger::WARNING);
277 break;
278
279 case 'V':
280 m_isVerbose = true;
281 break;
282
283 case 'p':
284 {
285 std::string newSearchPath(optarg);
286 PathSearcher::getGlobalSearcher().addSearchPath(newSearchPath);
287 break;
288 }
289
290 default:
291 Log::log(Logger::ERROR, "error: unrecognized option\n\n");
292 printUsage(options);
293 return 0;
294 }
295 }
296
297 // handle positional args
298 if (iter.index() < m_argc)
299 {
300 Log::SetOutputLevel leveler(Logger::DEBUG);
301 Log::log("positional args:\n");
302 int i;
303 for (i = iter.index(); i < m_argc; ++i)
304 {
305 Log::log("%d: %s\n", i - iter.index(), m_argv[i]);
306 m_positionalArgs.push_back(m_argv[i]);
307 }
308 }
309
310 // all is well
311 return -1;
312 }
313
314 /*!
315 * Prints help for the tool.
316 */
printUsage(Options & options)317 void printUsage(Options & options)
318 {
319 options.usage(std::cout, "files...");
320 printf("%s", k_usageText);
321 }
322
323 /*!
324 * \brief Core of the tool.
325 *
326 * Calls processOptions() to handle command line options before performing the
327 * real work the tool does.
328 */
run()329 int run()
330 {
331 try
332 {
333 // read command line options
334 int result;
335 if ((result = processOptions()) != -1)
336 {
337 return result;
338 }
339
340 // set verbose logging
341 setVerboseLogging();
342
343 // check argument values
344 checkArguments();
345
346 // set up the controller
347 m_controller.setCommandFilePath(m_commandFilePath);
348
349 // add external paths to controller
350 string_vector_t::iterator it = m_positionalArgs.begin();
351 for (; it != m_positionalArgs.end(); ++it)
352 {
353 m_controller.addExternalFilePath(*it);
354 }
355
356 // run conversion
357 convert();
358 }
359 catch (std::exception & e)
360 {
361 Log::log(Logger::ERROR, "error: %s\n", e.what());
362 return 1;
363 }
364 catch (...)
365 {
366 Log::log(Logger::ERROR, "error: unexpected exception\n");
367 return 1;
368 }
369
370 return 0;
371 }
372
373 /*!
374 * \brief Validate arguments that can be checked.
375 * \exception std::runtime_error Thrown if an argument value fails to pass validation.
376 */
checkArguments()377 void checkArguments()
378 {
379 if (m_commandFilePath == NULL)
380 {
381 throw std::runtime_error("no command file was specified");
382 }
383 if (m_outputFilePath == NULL)
384 {
385 throw std::runtime_error("no output file was specified");
386 }
387 }
388
389 /*!
390 * \brief Turns on verbose logging.
391 */
setVerboseLogging()392 void setVerboseLogging()
393 {
394 if (m_isVerbose)
395 {
396 // verbose only affects the INFO and DEBUG filter levels
397 // if the user has selected quiet mode, it overrides verbose
398 switch (Log::getLogger()->getFilterLevel())
399 {
400 case Logger::INFO:
401 Log::getLogger()->setFilterLevel(Logger::INFO2);
402 break;
403 case Logger::DEBUG:
404 Log::getLogger()->setFilterLevel(Logger::DEBUG2);
405 break;
406 }
407 }
408 }
409
410 /*!
411 * \brief Returns the integer value for a string.
412 *
413 * Metric multiplier prefixes are supported.
414 */
parseIntValue(const char * value)415 uint32_t parseIntValue(const char * value)
416 {
417 // Accept 'true'/'yes' and 'false'/'no' as integer values.
418 if ((strcmp(value, "true") == 0) || (strcmp(value, "yes") == 0))
419 {
420 return 1;
421 }
422 else if ((strcmp(value, "false") == 0) || (strcmp(value, "no") == 0))
423 {
424 return 0;
425 }
426
427 uint32_t intValue = strtoul(value, NULL, 0);
428 unsigned multiplier;
429 switch (value[strlen(value) - 1])
430 {
431 case 'G':
432 multiplier = 1024 * 1024 * 1024;
433 break;
434 case 'M':
435 multiplier = 1024 * 1024;
436 break;
437 case 'K':
438 multiplier = 1024;
439 break;
440 default:
441 multiplier = 1;
442 }
443 intValue *= multiplier;
444 return intValue;
445 }
446
447 /*!
448 * \brief Parses the -D option to override a constant value.
449 */
overrideVariable(const char * optarg)450 void overrideVariable(const char * optarg)
451 {
452 // split optarg into two strings
453 std::string constName(optarg);
454 int i;
455 for (i=0; i < strlen(optarg); ++i)
456 {
457 if (optarg[i] == '=')
458 {
459 constName.resize(i++);
460 break;
461 }
462 }
463
464 uint32_t constValue = parseIntValue(&optarg[i]);
465
466 elftosb::EvalContext & context = m_controller.getEvalContext();
467 context.setVariable(constName, constValue);
468 context.lockVariable(constName);
469 }
470
471 /*!
472 * \brief
473 */
overrideOption(const char * optarg)474 void overrideOption(const char * optarg)
475 {
476 // split optarg into two strings
477 std::string optionName(optarg);
478 int i;
479 for (i=0; i < strlen(optarg); ++i)
480 {
481 if (optarg[i] == '=')
482 {
483 optionName.resize(i++);
484 break;
485 }
486 }
487
488 // handle quotes for option value
489 const char * valuePtr = &optarg[i];
490 bool isString = false;
491 int len;
492 if (valuePtr[0] == '"')
493 {
494 // remember that the value is a string and get rid of the opening quote
495 isString = true;
496 valuePtr++;
497
498 // remove trailing quote if present
499 len = strlen(valuePtr);
500 if (valuePtr[len] == '"')
501 {
502 len--;
503 }
504 }
505
506 elftosb::Value * value;
507 if (isString)
508 {
509 std::string stringValue(valuePtr);
510 stringValue.resize(len); // remove trailing quote
511 value = new elftosb::StringValue(stringValue);
512 }
513 else
514 {
515 value = new elftosb::IntegerValue(parseIntValue(valuePtr));
516 }
517
518 // Set and lock the option in the controller
519 m_controller.setOption(optionName, value);
520 m_controller.lockOption(optionName);
521 }
522
523 /*!
524 * \brief Do the conversion.
525 * \exception std::runtime_error This exception is thrown if the conversion controller does
526 * not produce a boot image, or if the output file cannot be opened. Other errors
527 * internal to the conversion controller may also produce this exception.
528 */
convert()529 void convert()
530 {
531 // create a generator for the chosen chip family
532 smart_ptr<elftosb::BootImageGenerator> generator;
533 switch (m_family)
534 {
535 case k37xxFamily:
536 generator = new elftosb::EncoreBootImageGenerator;
537 elftosb::g_enableHABSupport = false;
538 break;
539
540 case kMX28Family:
541 generator = new elftosb::EncoreBootImageGenerator;
542 elftosb::g_enableHABSupport = true;
543 break;
544 }
545
546 // process input and get a boot image
547 m_controller.run();
548 smart_ptr<elftosb::BootImage> image = m_controller.generateOutput(generator);
549 if (!image)
550 {
551 throw std::runtime_error("failed to produce output!");
552 }
553
554 // set version numbers if they were provided on the command line
555 if (m_productVersionSpecified)
556 {
557 image->setProductVersion(m_productVersion);
558 }
559 if (m_componentVersionSpecified)
560 {
561 image->setComponentVersion(m_componentVersion);
562 }
563
564 // special handling for each family
565 switch (m_family)
566 {
567 case k37xxFamily:
568 case kMX28Family:
569 {
570 // add OTP keys
571 elftosb::EncoreBootImage * encoreImage = dynamic_cast<elftosb::EncoreBootImage*>(image.get());
572 if (encoreImage)
573 {
574 // add keys
575 addCryptoKeys(encoreImage);
576
577 // print debug image
578 encoreImage->debugPrint();
579 }
580 break;
581 }
582 }
583
584 // write output
585 std::ofstream outputStream(m_outputFilePath, std::ios_base::binary | std::ios_base::out | std::ios_base::trunc);
586 if (outputStream.is_open())
587 {
588 image->writeToStream(outputStream);
589 }
590 else
591 {
592 throw std::runtime_error(format_string("could not open output file %s", m_outputFilePath));
593 }
594 }
595
596 /*!
597 * \brief
598 */
addCryptoKeys(elftosb::EncoreBootImage * encoreImage)599 void addCryptoKeys(elftosb::EncoreBootImage * encoreImage)
600 {
601 string_vector_t::iterator it = m_keyFilePaths.begin();
602 for (; it != m_keyFilePaths.end(); ++it)
603 {
604 std::string & keyPath = *it;
605
606 std::string actualPath;
607 bool found = PathSearcher::getGlobalSearcher().search(keyPath, PathSearcher::kFindFile, true, actualPath);
608 if (!found)
609 {
610 throw std::runtime_error(format_string("unable to find key file %s\n", keyPath.c_str()));
611 }
612
613 std::ifstream keyStream(actualPath.c_str(), std::ios_base::in);
614 if (!keyStream.is_open())
615 {
616 throw std::runtime_error(format_string("unable to read key file %s\n", keyPath.c_str()));
617 }
618 keyStream.seekg(0);
619
620 try
621 {
622 // read as many keys as possible from the stream
623 while (true)
624 {
625 AESKey<128> key(keyStream);
626 encoreImage->addKey(key);
627
628 // dump key bytes
629 dumpKey(key);
630 }
631 }
632 catch (...)
633 {
634 // ignore the exception -- there are just no more keys in the stream
635 }
636 }
637
638 // add the default key of all zero bytes if requested
639 if (m_useDefaultKey)
640 {
641 AESKey<128> defaultKey;
642 encoreImage->addKey(defaultKey);
643 }
644 }
645
646 /*!
647 * \brief Write the value of each byte of the \a key to the log.
648 */
dumpKey(const AESKey<128> & key)649 void dumpKey(const AESKey<128> & key)
650 {
651 // dump key bytes
652 Log::log(Logger::DEBUG, "key bytes: ");
653 AESKey<128>::key_t the_key;
654 key.getKey(&the_key);
655 int q;
656 for (q=0; q<16; q++)
657 {
658 Log::log(Logger::DEBUG, "%02x ", the_key[q]);
659 }
660 Log::log(Logger::DEBUG, "\n");
661 }
662
663 };
664
665 const elftosbTool::FamilyNameTableEntry elftosbTool::kFamilyNameTable[] =
666 {
667 { "37xx", k37xxFamily },
668 { "377x", k37xxFamily },
669 { "378x", k37xxFamily },
670 { "mx23", k37xxFamily },
671 { "imx23", k37xxFamily },
672 { "i.mx23", k37xxFamily },
673 { "mx28", kMX28Family },
674 { "imx28", kMX28Family },
675 { "i.mx28", kMX28Family },
676
677 // Null terminator entry.
678 { NULL, k37xxFamily }
679 };
680
681 /*!
682 * Main application entry point. Creates an sbtool instance and lets it take over.
683 */
main(int argc,char * argv[],char * envp[])684 int main(int argc, char* argv[], char* envp[])
685 {
686 try
687 {
688 return elftosbTool(argc, argv).run();
689 }
690 catch (...)
691 {
692 Log::log(Logger::ERROR, "error: unexpected exception\n");
693 return 1;
694 }
695
696 return 0;
697 }
698
699
700
701