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