blob: 750235cbbd7e6f8afad843573b3a67f0288cae08 [file] [log] [blame]
Bill Richardsond55085d2011-02-04 15:01:37 -08001// Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4//
5// Utility for manipulating firmware screen block (BMPBLOCK) in GBB.
6//
7
8#include "bmpblk_utility.h"
9
10#include <assert.h>
11#include <errno.h>
12#include <getopt.h>
13#include <stdarg.h>
14#include <stdio.h>
15#include <stdlib.h>
16#include <string.h>
17#include <yaml.h>
18
Bill Richardsonfc05bb82011-02-08 15:03:36 -080019/* BMP header, used to validate image requirements
20 * See http://en.wikipedia.org/wiki/BMP_file_format
21 */
22typedef struct {
23 uint8_t CharB; // must be 'B'
24 uint8_t CharM; // must be 'M'
25 uint32_t Size;
26 uint16_t Reserved[2];
27 uint32_t ImageOffset;
28 uint32_t HeaderSize;
29 uint32_t PixelWidth;
30 uint32_t PixelHeight;
31 uint16_t Planes; // Must be 1 for x86
32 uint16_t BitPerPixel; // 1, 4, 8, or 24 for x86
33 uint32_t CompressionType; // must be 0 for x86
34 uint32_t ImageSize;
35 uint32_t XPixelsPerMeter;
36 uint32_t YPixelsPerMeter;
37 uint32_t NumberOfColors;
38 uint32_t ImportantColors;
39} __attribute__((packed)) BMP_IMAGE_HEADER;
40
Bill Richardsond55085d2011-02-04 15:01:37 -080041
42static void error(const char *format, ...) {
43 va_list ap;
44 va_start(ap, format);
45 fprintf(stderr, "ERROR: ");
46 vfprintf(stderr, format, ap);
47 va_end(ap);
48 exit(1);
49}
50
51///////////////////////////////////////////////////////////////////////
52// BmpBlock Utility implementation
53
54namespace vboot_reference {
55
56BmpBlockUtil::BmpBlockUtil() {
57 initialize();
58}
59
60BmpBlockUtil::~BmpBlockUtil() {
61}
62
63void BmpBlockUtil::initialize() {
64 config_.config_filename.clear();
65 memset(&config_.header, '\0', BMPBLOCK_SIGNATURE_SIZE);
66 config_.images_map.clear();
67 config_.screens_map.clear();
68 config_.localizations.clear();
69 bmpblock_.clear();
70}
71
72void BmpBlockUtil::load_from_config(const char *filename) {
73 load_yaml_config(filename);
74 fill_bmpblock_header();
75 load_all_image_files();
76 fill_all_image_infos();
77}
78
79void BmpBlockUtil::load_yaml_config(const char *filename) {
80 yaml_parser_t parser;
81
82 config_.config_filename = filename;
83 config_.images_map.clear();
84 config_.screens_map.clear();
85 config_.localizations.clear();
86
87 FILE *fp = fopen(filename, "rb");
88 if (!fp) {
89 perror(filename);
90 exit(errno);
91 }
92
93 yaml_parser_initialize(&parser);
94 yaml_parser_set_input_file(&parser, fp);
95 parse_config(&parser);
96 yaml_parser_delete(&parser);
97 fclose(fp);
98}
99
100void BmpBlockUtil::expect_event(yaml_parser_t *parser,
101 const yaml_event_type_e type) {
102 yaml_event_t event;
103 yaml_parser_parse(parser, &event);
104 if (event.type != type) {
105 error("Syntax error.\n");
106 }
107 yaml_event_delete(&event);
108}
109
110void BmpBlockUtil::parse_config(yaml_parser_t *parser) {
111 expect_event(parser, YAML_STREAM_START_EVENT);
112 expect_event(parser, YAML_DOCUMENT_START_EVENT);
113 parse_first_layer(parser);
114 expect_event(parser, YAML_DOCUMENT_END_EVENT);
115 expect_event(parser, YAML_STREAM_END_EVENT);
116}
117
118void BmpBlockUtil::parse_first_layer(yaml_parser_t *parser) {
119 yaml_event_t event;
120 string keyword;
121 expect_event(parser, YAML_MAPPING_START_EVENT);
122 for (;;) {
123 yaml_parser_parse(parser, &event);
124 switch (event.type) {
125 case YAML_SCALAR_EVENT:
126 keyword = (char*)event.data.scalar.value;
127 if (keyword == "bmpblock") {
128 parse_bmpblock(parser);
129 } else if (keyword == "images") {
130 parse_images(parser);
131 } else if (keyword == "screens") {
132 parse_screens(parser);
133 } else if (keyword == "localizations") {
134 parse_localizations(parser);
135 }
136 break;
137 case YAML_MAPPING_END_EVENT:
138 yaml_event_delete(&event);
139 return;
140 default:
141 error("Syntax error in parsing config file.\n");
142 }
143 yaml_event_delete(&event);
144 }
145}
146
147void BmpBlockUtil::parse_bmpblock(yaml_parser_t *parser) {
148 yaml_event_t event;
149 yaml_parser_parse(parser, &event);
150 if (event.type != YAML_SCALAR_EVENT) {
151 error("Syntax error in parsing bmpblock.\n");
152 }
Bill Richardsonfc05bb82011-02-08 15:03:36 -0800153 char wantversion[20];
154 sprintf(wantversion, "%d.%d",
155 BMPBLOCK_MAJOR_VERSION,
156 BMPBLOCK_MINOR_VERSION);
157 string gotversion = (char*)event.data.scalar.value;
158 if (gotversion != wantversion) {
159 error("Invalid version specified in config file\n");
160 }
Bill Richardsond55085d2011-02-04 15:01:37 -0800161 yaml_event_delete(&event);
162}
163
164void BmpBlockUtil::parse_images(yaml_parser_t *parser) {
165 yaml_event_t event;
166 string image_name, image_filename;
167 expect_event(parser, YAML_MAPPING_START_EVENT);
168 for (;;) {
169 yaml_parser_parse(parser, &event);
170 switch (event.type) {
171 case YAML_SCALAR_EVENT:
172 image_name = (char*)event.data.scalar.value;
173 yaml_event_delete(&event);
174 yaml_parser_parse(parser, &event);
175 if (event.type != YAML_SCALAR_EVENT) {
176 error("Syntax error in parsing images.\n");
177 }
178 image_filename = (char*)event.data.scalar.value;
179 config_.images_map[image_name] = ImageConfig();
180 config_.images_map[image_name].filename = image_filename;
181 break;
182 case YAML_MAPPING_END_EVENT:
183 yaml_event_delete(&event);
184 return;
185 default:
186 error("Syntax error in parsing images.\n");
187 }
188 yaml_event_delete(&event);
189 }
190}
191
192void BmpBlockUtil::parse_layout(yaml_parser_t *parser, ScreenConfig &screen) {
193 yaml_event_t event;
194 string screen_name;
195 int depth = 0, index1 = 0, index2 = 0;
196 expect_event(parser, YAML_SEQUENCE_START_EVENT);
197 for (;;) {
198 yaml_parser_parse(parser, &event);
199 switch (event.type) {
200 case YAML_SEQUENCE_START_EVENT:
201 depth++;
202 break;
203 case YAML_SCALAR_EVENT:
204 switch (index2) {
205 case 0:
206 screen.data.images[index1].x = atoi((char*)event.data.scalar.value);
207 break;
208 case 1:
209 screen.data.images[index1].y = atoi((char*)event.data.scalar.value);
210 break;
211 case 2:
212 screen.image_names[index1] = (char*)event.data.scalar.value;
213 break;
214 default:
215 error("Syntax error in parsing layout.\n");
216 }
217 index2++;
218 break;
219 case YAML_SEQUENCE_END_EVENT:
220 if (depth == 1) {
221 index1++;
222 index2 = 0;
223 } else if (depth == 0) {
224 yaml_event_delete(&event);
225 return;
226 }
227 depth--;
228 break;
229 default:
230 error("Syntax error in paring layout.\n");
231 }
232 yaml_event_delete(&event);
233 }
234}
235
236void BmpBlockUtil::parse_screens(yaml_parser_t *parser) {
237 yaml_event_t event;
238 string screen_name;
239 expect_event(parser, YAML_MAPPING_START_EVENT);
240 for (;;) {
241 yaml_parser_parse(parser, &event);
242 switch (event.type) {
243 case YAML_SCALAR_EVENT:
244 screen_name = (char*)event.data.scalar.value;
245 config_.screens_map[screen_name] = ScreenConfig();
246 parse_layout(parser, config_.screens_map[screen_name]);
247 break;
248 case YAML_MAPPING_END_EVENT:
249 yaml_event_delete(&event);
250 return;
251 default:
252 error("Syntax error in parsing screens.\n");
253 }
254 yaml_event_delete(&event);
255 }
256}
257
258void BmpBlockUtil::parse_localizations(yaml_parser_t *parser) {
259 yaml_event_t event;
260 int depth = 0, index = 0;
261 expect_event(parser, YAML_SEQUENCE_START_EVENT);
262 for (;;) {
263 yaml_parser_parse(parser, &event);
264 switch (event.type) {
265 case YAML_SEQUENCE_START_EVENT:
266 config_.localizations.push_back(vector<string>());
267 depth++;
268 break;
269 case YAML_SCALAR_EVENT:
270 config_.localizations[index].push_back((char*)event.data.scalar.value);
271 break;
272 case YAML_SEQUENCE_END_EVENT:
273 if (depth == 1) {
274 index++;
275 } else if (depth == 0) {
276 yaml_event_delete(&event);
277 return;
278 }
279 depth--;
280 break;
281 default:
282 error("Syntax error in parsing localizations.\n");
283 }
284 yaml_event_delete(&event);
285 }
286}
287
288void BmpBlockUtil::load_all_image_files() {
289 for (StrImageConfigMap::iterator it = config_.images_map.begin();
290 it != config_.images_map.end();
291 ++it) {
292 const string &content = read_image_file(it->second.filename.c_str());
293 it->second.raw_content = content;
294 it->second.data.original_size = content.size();
295 /* Use no compression as default */
296 it->second.data.compression = COMPRESS_NONE;
297 it->second.compressed_content = content;
298 it->second.data.compressed_size = content.size();
299 }
300}
301
302const string BmpBlockUtil::read_image_file(const char *filename) {
303 string content;
304 vector<char> buffer;
305
306 FILE *fp = fopen(filename, "rb");
307 if (!fp) {
308 perror(filename);
309 exit(errno);
310 }
311
312 if (fseek(fp, 0, SEEK_END) == 0) {
313 buffer.resize(ftell(fp));
314 rewind(fp);
315 }
316
317 if (!buffer.empty()) {
318 if(fread(&buffer[0], buffer.size(), 1, fp) != 1) {
319 perror(filename);
320 buffer.clear();
321 } else {
322 content.assign(buffer.begin(), buffer.end());
323 }
324 }
325
326 fclose(fp);
327 return content;
328}
329
330ImageFormat BmpBlockUtil::get_image_format(const string content) {
Bill Richardsonfc05bb82011-02-08 15:03:36 -0800331 if (content.size() < sizeof(BMP_IMAGE_HEADER))
Bill Richardsond55085d2011-02-04 15:01:37 -0800332 return FORMAT_INVALID;
Bill Richardsonfc05bb82011-02-08 15:03:36 -0800333 const BMP_IMAGE_HEADER *hdr = (const BMP_IMAGE_HEADER *)content.c_str();
334
335 if (hdr->CharB != 'B' || hdr->CharM != 'M' ||
336 hdr->Planes != 1 ||
337 hdr->CompressionType != 0 ||
338 (hdr->BitPerPixel != 1 && hdr->BitPerPixel != 4 &&
339 hdr->BitPerPixel != 8 && hdr->BitPerPixel != 24))
340 return FORMAT_INVALID;
341
342 return FORMAT_BMP;
Bill Richardsond55085d2011-02-04 15:01:37 -0800343}
344
345uint32_t BmpBlockUtil::get_bmp_image_width(const string content) {
Bill Richardsonfc05bb82011-02-08 15:03:36 -0800346 const BMP_IMAGE_HEADER *hdr = (const BMP_IMAGE_HEADER *)content.c_str();
347 return hdr->PixelWidth;
Bill Richardsond55085d2011-02-04 15:01:37 -0800348}
349
350uint32_t BmpBlockUtil::get_bmp_image_height(const string content) {
Bill Richardsonfc05bb82011-02-08 15:03:36 -0800351 const BMP_IMAGE_HEADER *hdr = (const BMP_IMAGE_HEADER *)content.c_str();
352 return hdr->PixelHeight;
Bill Richardsond55085d2011-02-04 15:01:37 -0800353}
354
355void BmpBlockUtil::fill_all_image_infos() {
Bill Richardsonfc05bb82011-02-08 15:03:36 -0800356 int errcnt = 0;
Bill Richardsond55085d2011-02-04 15:01:37 -0800357 for (StrImageConfigMap::iterator it = config_.images_map.begin();
358 it != config_.images_map.end();
359 ++it) {
360 it->second.data.format = (uint32_t)get_image_format(it->second.raw_content);
361 switch (it->second.data.format) {
362 case FORMAT_BMP:
363 it->second.data.width = get_bmp_image_width(it->second.raw_content);
364 it->second.data.height = get_bmp_image_height(it->second.raw_content);
365 break;
366 default:
Bill Richardsonfc05bb82011-02-08 15:03:36 -0800367 fprintf(stderr, "Unsupported image format in %s\n",
368 it->second.filename.c_str());
369 errcnt++;
Bill Richardsond55085d2011-02-04 15:01:37 -0800370 }
371 }
Bill Richardsonfc05bb82011-02-08 15:03:36 -0800372 if (errcnt)
373 error("Unable to continue due to errors.\n");
Bill Richardsond55085d2011-02-04 15:01:37 -0800374}
375
376void BmpBlockUtil::compress_all_images(const Compression compress) {
377 switch (compress) {
378 case COMPRESS_NONE:
379 for (StrImageConfigMap::iterator it = config_.images_map.begin();
380 it != config_.images_map.end();
381 ++it) {
382 it->second.data.compression = compress;
383 it->second.compressed_content = it->second.raw_content;
384 it->second.data.compressed_size = it->second.compressed_content.size();
385 }
386 break;
387 default:
Bill Richardson856e0722011-02-07 15:39:45 -0800388 error("Unsupported data compression.\n");
Bill Richardsond55085d2011-02-04 15:01:37 -0800389 }
390}
391
392void BmpBlockUtil::fill_bmpblock_header() {
393 memset(&config_.header, '\0', sizeof(config_.header));
394 memcpy(&config_.header.signature, BMPBLOCK_SIGNATURE,
395 BMPBLOCK_SIGNATURE_SIZE);
Bill Richardsonfc05bb82011-02-08 15:03:36 -0800396 config_.header.major_version = BMPBLOCK_MAJOR_VERSION;
397 config_.header.minor_version = BMPBLOCK_MINOR_VERSION;
Bill Richardsond55085d2011-02-04 15:01:37 -0800398 config_.header.number_of_localizations = config_.localizations.size();
399 config_.header.number_of_screenlayouts = config_.localizations[0].size();
400 for (unsigned int i = 1; i < config_.localizations.size(); ++i) {
401 assert(config_.header.number_of_screenlayouts ==
402 config_.localizations[i].size());
403 }
404 config_.header.number_of_imageinfos = config_.images_map.size();
405}
406
407void BmpBlockUtil::pack_bmpblock() {
408 bmpblock_.clear();
409
410 /* Compute the ImageInfo offsets from start of BMPBLOCK. */
411 uint32_t current_offset = sizeof(BmpBlockHeader) +
412 sizeof(ScreenLayout) * config_.images_map.size();
413 for (StrImageConfigMap::iterator it = config_.images_map.begin();
414 it != config_.images_map.end();
415 ++it) {
416 it->second.offset = current_offset;
417 current_offset += sizeof(ImageInfo) + it->second.data.compressed_size;
418 /* Make it 4-byte aligned. */
419 if ((current_offset & 3) > 0) {
420 current_offset = (current_offset & ~3) + 4;
421 }
422 }
423 bmpblock_.resize(current_offset);
424
425 /* Fill BmpBlockHeader struct. */
426 string::iterator current_filled = bmpblock_.begin();
427 std::copy(reinterpret_cast<char*>(&config_.header),
428 reinterpret_cast<char*>(&config_.header + 1),
429 current_filled);
430 current_filled += sizeof(config_.header);
431
432 /* Fill all ScreenLayout structs. */
433 for (unsigned int i = 0; i < config_.localizations.size(); ++i) {
434 for (unsigned int j = 0; j < config_.localizations[i].size(); ++j) {
435 ScreenConfig &screen = config_.screens_map[config_.localizations[i][j]];
436 for (unsigned int k = 0;
437 k < MAX_IMAGE_IN_LAYOUT && !screen.image_names[k].empty();
438 ++k) {
439 screen.data.images[k].image_info_offset =
440 config_.images_map[screen.image_names[k]].offset;
441 }
442 std::copy(reinterpret_cast<char*>(&screen.data),
443 reinterpret_cast<char*>(&screen.data + 1),
444 current_filled);
445 current_filled += sizeof(screen.data);
446 }
447 }
448
449 /* Fill all ImageInfo structs and image contents. */
450 for (StrImageConfigMap::iterator it = config_.images_map.begin();
451 it != config_.images_map.end();
452 ++it) {
453 current_filled = bmpblock_.begin() + it->second.offset;
454 std::copy(reinterpret_cast<char*>(&it->second.data),
455 reinterpret_cast<char*>(&it->second.data + 1),
456 current_filled);
457 current_filled += sizeof(it->second.data);
458 std::copy(it->second.compressed_content.begin(),
459 it->second.compressed_content.end(),
460 current_filled);
461 }
462}
463
464void BmpBlockUtil::write_to_bmpblock(const char *filename) {
465 assert(!bmpblock_.empty());
466
467 FILE *fp = fopen(filename, "wb");
468 if (!fp) {
469 perror(filename);
470 exit(errno);
471 }
472
473 int r = fwrite(bmpblock_.c_str(), bmpblock_.size(), 1, fp);
474 fclose(fp);
475 if (r != 1) {
476 perror(filename);
477 exit(errno);
478 }
479}
480
481} // namespace vboot_reference
482
483#ifdef WITH_UTIL_MAIN
484
485///////////////////////////////////////////////////////////////////////
486// Command line utilities
487
488using vboot_reference::BmpBlockUtil;
489
490// utility function: provide usage of this utility and exit.
491static void usagehelp_exit(const char *prog_name) {
492 printf(
493 "Utility to manage firmware screen block (BMPBLOCK)\n"
494 "Usage: %s -c|-l|-x [options] BMPBLOCK_FILE\n"
495 "\n"
496 "Main Operation Mode:\n"
497 " -c, --create Create a new BMPBLOCK file. Should specify --config.\n"
498 " -l, --list List the contents of a BMPBLOCK file.\n"
499 " -x, --extract Extract embedded images and config file from a BMPBLOCK\n"
500 " file.\n"
501 "\n"
502 "Other Options:\n"
503 " -C, --config=CONFIG_FILE Config file describing screen layouts and\n"
504 " embedded images. (default: bmpblk.cfg)\n"
505 "\n"
506 "Example:\n"
507 " %s --create --config=screens.cfg bmpblk.bin\n"
508 , prog_name, prog_name);
509 exit(1);
510}
511
512///////////////////////////////////////////////////////////////////////
513// main
514
515int main(int argc, char *argv[]) {
516 const char *prog_name = argv[0];
517 BmpBlockUtil util;
518
519 struct BmpBlockUtilOptions {
520 bool create_mode, list_mode, extract_mode;
521 string config_fn, bmpblock_fn;
522 } options;
523
524 int longindex, opt;
525 static struct option longopts[] = {
526 {"create", 0, NULL, 'c'},
527 {"list", 0, NULL, 'l'},
528 {"extract", 0, NULL, 'x'},
529 {"config", 1, NULL, 'C'},
530 { NULL, 0, NULL, 0 },
531 };
532
533 while ((opt = getopt_long(argc, argv, "clxC:", longopts, &longindex)) >= 0) {
534 switch (opt) {
535 case 'c':
536 options.create_mode = true;
537 break;
538 case 'l':
539 options.list_mode = true;
540 break;
541 case 'x':
542 options.extract_mode = true;
543 break;
544 case 'C':
545 options.config_fn = optarg;
546 break;
547 default:
548 case '?':
549 usagehelp_exit(prog_name);
550 break;
551 }
552 }
553 argc -= optind;
554 argv += optind;
555
556 if (argc == 1) {
557 options.bmpblock_fn = argv[0];
558 } else {
559 usagehelp_exit(prog_name);
560 }
561
562 if (options.create_mode) {
563 util.load_from_config(options.config_fn.c_str());
564 util.pack_bmpblock();
565 util.write_to_bmpblock(options.bmpblock_fn.c_str());
566 printf("The BMPBLOCK is sucessfully created in: %s.\n",
567 options.bmpblock_fn.c_str());
568 }
569
570 if (options.list_mode) {
571 /* TODO(waihong): Implement the list mode. */
572 error("List mode hasn't been implemented yet.\n");
573 }
574
575 if (options.extract_mode) {
576 /* TODO(waihong): Implement the extract mode. */
577 error("Extract mode hasn't been implemented yet.\n");
578 }
579
580 return 0;
581}
582
583#endif // WITH_UTIL_MAIN