blob: 1739a3e3cda52cbe845456d5348f8decc6c96c7f
1 | /* vi: set sw=4 ts=4: */ |
2 | /* |
3 | * Copyright (C) 2008 Michele Sanges <michele.sanges@gmail.com> |
4 | * |
5 | * Licensed under GPLv2 or later, see file LICENSE in this source tree. |
6 | * |
7 | * Usage: |
8 | * - use kernel option 'vga=xxx' or otherwise enable framebuffer device. |
9 | * - put somewhere fbsplash.cfg file and an image in .ppm format. |
10 | * - run applet: $ setsid fbsplash [params] & |
11 | * -c: hide cursor |
12 | * -d /dev/fbN: framebuffer device (if not /dev/fb0) |
13 | * -s path_to_image_file (can be "-" for stdin) |
14 | * -i path_to_cfg_file |
15 | * -f path_to_fifo (can be "-" for stdin) |
16 | * - if you want to run it only in presence of a kernel parameter |
17 | * (for example fbsplash=on), use: |
18 | * grep -q "fbsplash=on" </proc/cmdline && setsid fbsplash [params] |
19 | * - commands for fifo: |
20 | * "NN" (ASCII decimal number) - percentage to show on progress bar. |
21 | * "exit" (or just close fifo) - well you guessed it. |
22 | */ |
23 | //config:config FBSPLASH |
24 | //config: bool "fbsplash" |
25 | //config: default y |
26 | //config: select PLATFORM_LINUX |
27 | //config: help |
28 | //config: Shows splash image and progress bar on framebuffer device. |
29 | //config: Can be used during boot phase of an embedded device. ~2kb. |
30 | //config: Usage: |
31 | //config: - use kernel option 'vga=xxx' or otherwise enable fb device. |
32 | //config: - put somewhere fbsplash.cfg file and an image in .ppm format. |
33 | //config: - $ setsid fbsplash [params] & |
34 | //config: -c: hide cursor |
35 | //config: -d /dev/fbN: framebuffer device (if not /dev/fb0) |
36 | //config: -s path_to_image_file (can be "-" for stdin) |
37 | //config: -i path_to_cfg_file (can be "-" for stdin) |
38 | //config: -f path_to_fifo (can be "-" for stdin) |
39 | //config: - if you want to run it only in presence of kernel parameter: |
40 | //config: grep -q "fbsplash=on" </proc/cmdline && setsid fbsplash [params] & |
41 | //config: - commands for fifo: |
42 | //config: "NN" (ASCII decimal number) - percentage to show on progress bar |
43 | //config: "exit" - well you guessed it |
44 | |
45 | //applet:IF_FBSPLASH(APPLET(fbsplash, BB_DIR_SBIN, BB_SUID_DROP)) |
46 | |
47 | //kbuild:lib-$(CONFIG_FBSPLASH) += fbsplash.o |
48 | |
49 | //usage:#define fbsplash_trivial_usage |
50 | //usage: "-s IMGFILE [-c] [-d DEV] [-i INIFILE] [-f CMD]" |
51 | //usage:#define fbsplash_full_usage "\n\n" |
52 | //usage: " -s Image" |
53 | //usage: "\n -c Hide cursor" |
54 | //usage: "\n -d Framebuffer device (default /dev/fb0)" |
55 | //usage: "\n -i Config file (var=value):" |
56 | //usage: "\n BAR_LEFT,BAR_TOP,BAR_WIDTH,BAR_HEIGHT" |
57 | //usage: "\n BAR_R,BAR_G,BAR_B" |
58 | //usage: "\n -f Control pipe (else exit after drawing image)" |
59 | //usage: "\n commands: 'NN' (% for progress bar) or 'exit'" |
60 | |
61 | #include "libbb.h" |
62 | #include "common_bufsiz.h" |
63 | #include <linux/fb.h> |
64 | |
65 | /* If you want logging messages on /tmp/fbsplash.log... */ |
66 | #define DEBUG 0 |
67 | |
68 | struct globals { |
69 | #if DEBUG |
70 | bool bdebug_messages; // enable/disable logging |
71 | FILE *logfile_fd; // log file |
72 | #endif |
73 | unsigned char *addr; // pointer to framebuffer memory |
74 | unsigned ns[7]; // n-parameters |
75 | const char *image_filename; |
76 | struct fb_var_screeninfo scr_var; |
77 | struct fb_fix_screeninfo scr_fix; |
78 | unsigned bytes_per_pixel; |
79 | // cached (8 - scr_var.COLOR.length): |
80 | unsigned red_shift; |
81 | unsigned green_shift; |
82 | unsigned blue_shift; |
83 | }; |
84 | #define G (*ptr_to_globals) |
85 | #define INIT_G() do { \ |
86 | SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \ |
87 | } while (0) |
88 | |
89 | #define nbar_width ns[0] // progress bar width |
90 | #define nbar_height ns[1] // progress bar height |
91 | #define nbar_posx ns[2] // progress bar horizontal position |
92 | #define nbar_posy ns[3] // progress bar vertical position |
93 | #define nbar_colr ns[4] // progress bar color red component |
94 | #define nbar_colg ns[5] // progress bar color green component |
95 | #define nbar_colb ns[6] // progress bar color blue component |
96 | |
97 | #if DEBUG |
98 | #define DEBUG_MESSAGE(strMessage, args...) \ |
99 | if (G.bdebug_messages) { \ |
100 | fprintf(G.logfile_fd, "[%s][%s] - %s\n", \ |
101 | __FILE__, __FUNCTION__, strMessage); \ |
102 | } |
103 | #else |
104 | #define DEBUG_MESSAGE(...) ((void)0) |
105 | #endif |
106 | |
107 | /** |
108 | * Configure palette for RGB:332 |
109 | */ |
110 | static void fb_setpal(int fd) |
111 | { |
112 | struct fb_cmap cmap; |
113 | /* fb colors are 16 bit */ |
114 | unsigned short red[256], green[256], blue[256]; |
115 | unsigned i; |
116 | |
117 | /* RGB:332 */ |
118 | for (i = 0; i < 256; i++) { |
119 | /* Color is encoded in pixel value as rrrgggbb. |
120 | * 3-bit color is mapped to 16-bit one as: |
121 | * 000 -> 00000000 00000000 |
122 | * 001 -> 00100100 10010010 |
123 | * ... |
124 | * 011 -> 01101101 10110110 |
125 | * 100 -> 10010010 01001001 |
126 | * ... |
127 | * 111 -> 11111111 11111111 |
128 | */ |
129 | red[i] = (( i >> 5 ) * 0x9249) >> 2; // rrr * 00 10010010 01001001 >> 2 |
130 | green[i] = (((i >> 2) & 0x7) * 0x9249) >> 2; // ggg * 00 10010010 01001001 >> 2 |
131 | /* 2-bit color is easier: */ |
132 | blue[i] = ( i & 0x3) * 0x5555; // bb * 01010101 01010101 |
133 | } |
134 | |
135 | cmap.start = 0; |
136 | cmap.len = 256; |
137 | cmap.red = red; |
138 | cmap.green = green; |
139 | cmap.blue = blue; |
140 | cmap.transp = 0; |
141 | |
142 | xioctl(fd, FBIOPUTCMAP, &cmap); |
143 | } |
144 | |
145 | /** |
146 | * Open and initialize the framebuffer device |
147 | * \param *strfb_device pointer to framebuffer device |
148 | */ |
149 | static void fb_open(const char *strfb_device) |
150 | { |
151 | int fbfd = xopen(strfb_device, O_RDWR); |
152 | |
153 | // framebuffer properties |
154 | xioctl(fbfd, FBIOGET_VSCREENINFO, &G.scr_var); |
155 | xioctl(fbfd, FBIOGET_FSCREENINFO, &G.scr_fix); |
156 | |
157 | switch (G.scr_var.bits_per_pixel) { |
158 | case 8: |
159 | fb_setpal(fbfd); |
160 | break; |
161 | |
162 | case 16: |
163 | case 24: |
164 | case 32: |
165 | break; |
166 | |
167 | default: |
168 | bb_error_msg_and_die("unsupported %u bpp", (int)G.scr_var.bits_per_pixel); |
169 | break; |
170 | } |
171 | |
172 | G.red_shift = 8 - G.scr_var.red.length; |
173 | G.green_shift = 8 - G.scr_var.green.length; |
174 | G.blue_shift = 8 - G.scr_var.blue.length; |
175 | G.bytes_per_pixel = (G.scr_var.bits_per_pixel + 7) >> 3; |
176 | |
177 | // map the device in memory |
178 | G.addr = mmap(NULL, |
179 | (G.scr_var.yres_virtual ?: G.scr_var.yres) * G.scr_fix.line_length, |
180 | PROT_WRITE, MAP_SHARED, fbfd, 0); |
181 | if (G.addr == MAP_FAILED) |
182 | bb_perror_msg_and_die("mmap"); |
183 | |
184 | // point to the start of the visible screen |
185 | G.addr += G.scr_var.yoffset * G.scr_fix.line_length + G.scr_var.xoffset * G.bytes_per_pixel; |
186 | close(fbfd); |
187 | } |
188 | |
189 | |
190 | /** |
191 | * Return pixel value of the passed RGB color. |
192 | * This is performance critical fn. |
193 | */ |
194 | static unsigned fb_pixel_value(unsigned r, unsigned g, unsigned b) |
195 | { |
196 | /* We assume that the r,g,b values are <= 255 */ |
197 | |
198 | if (G.bytes_per_pixel == 1) { |
199 | r = r & 0xe0; // 3-bit red |
200 | g = (g >> 3) & 0x1c; // 3-bit green |
201 | b = b >> 6; // 2-bit blue |
202 | return r + g + b; |
203 | } |
204 | if (G.bytes_per_pixel == 2) { |
205 | // ARM PL110 on Integrator/CP has RGBA5551 bit arrangement. |
206 | // We want to support bit locations like that. |
207 | // |
208 | // First shift out unused bits |
209 | r = r >> G.red_shift; |
210 | g = g >> G.green_shift; |
211 | b = b >> G.blue_shift; |
212 | // Then shift the remaining bits to their offset |
213 | return (r << G.scr_var.red.offset) + |
214 | (g << G.scr_var.green.offset) + |
215 | (b << G.scr_var.blue.offset); |
216 | } |
217 | // RGB 888 |
218 | return b + (g << 8) + (r << 16); |
219 | } |
220 | |
221 | /** |
222 | * Draw pixel on framebuffer |
223 | */ |
224 | static void fb_write_pixel(unsigned char *addr, unsigned pixel) |
225 | { |
226 | switch (G.bytes_per_pixel) { |
227 | case 1: |
228 | *addr = pixel; |
229 | break; |
230 | case 2: |
231 | *(uint16_t *)addr = pixel; |
232 | break; |
233 | case 4: |
234 | *(uint32_t *)addr = pixel; |
235 | break; |
236 | default: // 24 bits per pixel |
237 | addr[0] = pixel; |
238 | addr[1] = pixel >> 8; |
239 | addr[2] = pixel >> 16; |
240 | } |
241 | } |
242 | |
243 | |
244 | /** |
245 | * Draw hollow rectangle on framebuffer |
246 | */ |
247 | static void fb_drawrectangle(void) |
248 | { |
249 | int cnt; |
250 | unsigned thispix; |
251 | unsigned char *ptr1, *ptr2; |
252 | unsigned char nred = G.nbar_colr/2; |
253 | unsigned char ngreen = G.nbar_colg/2; |
254 | unsigned char nblue = G.nbar_colb/2; |
255 | |
256 | thispix = fb_pixel_value(nred, ngreen, nblue); |
257 | |
258 | // horizontal lines |
259 | ptr1 = G.addr + G.nbar_posy * G.scr_fix.line_length + G.nbar_posx * G.bytes_per_pixel; |
260 | ptr2 = G.addr + (G.nbar_posy + G.nbar_height - 1) * G.scr_fix.line_length + G.nbar_posx * G.bytes_per_pixel; |
261 | cnt = G.nbar_width - 1; |
262 | do { |
263 | fb_write_pixel(ptr1, thispix); |
264 | fb_write_pixel(ptr2, thispix); |
265 | ptr1 += G.bytes_per_pixel; |
266 | ptr2 += G.bytes_per_pixel; |
267 | } while (--cnt >= 0); |
268 | |
269 | // vertical lines |
270 | ptr1 = G.addr + G.nbar_posy * G.scr_fix.line_length + G.nbar_posx * G.bytes_per_pixel; |
271 | ptr2 = G.addr + G.nbar_posy * G.scr_fix.line_length + (G.nbar_posx + G.nbar_width - 1) * G.bytes_per_pixel; |
272 | cnt = G.nbar_height - 1; |
273 | do { |
274 | fb_write_pixel(ptr1, thispix); |
275 | fb_write_pixel(ptr2, thispix); |
276 | ptr1 += G.scr_fix.line_length; |
277 | ptr2 += G.scr_fix.line_length; |
278 | } while (--cnt >= 0); |
279 | } |
280 | |
281 | |
282 | /** |
283 | * Draw filled rectangle on framebuffer |
284 | * \param nx1pos,ny1pos upper left position |
285 | * \param nx2pos,ny2pos down right position |
286 | * \param nred,ngreen,nblue rgb color |
287 | */ |
288 | static void fb_drawfullrectangle(int nx1pos, int ny1pos, int nx2pos, int ny2pos, |
289 | unsigned char nred, unsigned char ngreen, unsigned char nblue) |
290 | { |
291 | int cnt1, cnt2, nypos; |
292 | unsigned thispix; |
293 | unsigned char *ptr; |
294 | |
295 | thispix = fb_pixel_value(nred, ngreen, nblue); |
296 | |
297 | cnt1 = ny2pos - ny1pos; |
298 | nypos = ny1pos; |
299 | do { |
300 | ptr = G.addr + nypos * G.scr_fix.line_length + nx1pos * G.bytes_per_pixel; |
301 | cnt2 = nx2pos - nx1pos; |
302 | do { |
303 | fb_write_pixel(ptr, thispix); |
304 | ptr += G.bytes_per_pixel; |
305 | } while (--cnt2 >= 0); |
306 | |
307 | nypos++; |
308 | } while (--cnt1 >= 0); |
309 | } |
310 | |
311 | |
312 | /** |
313 | * Draw a progress bar on framebuffer |
314 | * \param percent percentage of loading |
315 | */ |
316 | static void fb_drawprogressbar(unsigned percent) |
317 | { |
318 | int left_x, top_y, pos_x; |
319 | unsigned width, height; |
320 | |
321 | // outer box |
322 | left_x = G.nbar_posx; |
323 | top_y = G.nbar_posy; |
324 | width = G.nbar_width - 1; |
325 | height = G.nbar_height - 1; |
326 | if ((int)(height | width) < 0) |
327 | return; |
328 | // NB: "width" of 1 actually makes rect with width of 2! |
329 | fb_drawrectangle(); |
330 | |
331 | // inner "empty" rectangle |
332 | left_x++; |
333 | top_y++; |
334 | width -= 2; |
335 | height -= 2; |
336 | if ((int)(height | width) < 0) |
337 | return; |
338 | |
339 | pos_x = left_x; |
340 | if (percent > 0) { |
341 | int i, y; |
342 | |
343 | // actual progress bar |
344 | pos_x += (unsigned)(width * percent) / 100; |
345 | |
346 | y = top_y; |
347 | i = height; |
348 | if (height == 0) |
349 | height++; // divide by 0 is bad |
350 | while (i >= 0) { |
351 | // draw one-line thick "rectangle" |
352 | // top line will have gray lvl 200, bottom one 100 |
353 | unsigned gray_level = 100 + (unsigned)i*100 / height; |
354 | fb_drawfullrectangle( |
355 | left_x, y, pos_x, y, |
356 | gray_level, gray_level, gray_level); |
357 | y++; |
358 | i--; |
359 | } |
360 | } |
361 | |
362 | fb_drawfullrectangle( |
363 | pos_x, top_y, |
364 | left_x + width, top_y + height, |
365 | G.nbar_colr, G.nbar_colg, G.nbar_colb); |
366 | } |
367 | |
368 | |
369 | /** |
370 | * Draw image from PPM file |
371 | */ |
372 | static void fb_drawimage(void) |
373 | { |
374 | FILE *theme_file; |
375 | char *read_ptr; |
376 | unsigned char *pixline; |
377 | unsigned i, j, width, height, line_size; |
378 | |
379 | if (LONE_DASH(G.image_filename)) { |
380 | theme_file = stdin; |
381 | } else { |
382 | int fd = open_zipped(G.image_filename, /*fail_if_not_compressed:*/ 0); |
383 | if (fd < 0) |
384 | bb_simple_perror_msg_and_die(G.image_filename); |
385 | theme_file = xfdopen_for_read(fd); |
386 | } |
387 | |
388 | /* Parse ppm header: |
389 | * - Magic: two characters "P6". |
390 | * - Whitespace (blanks, TABs, CRs, LFs). |
391 | * - A width, formatted as ASCII characters in decimal. |
392 | * - Whitespace. |
393 | * - A height, ASCII decimal. |
394 | * - Whitespace. |
395 | * - The maximum color value, ASCII decimal, in 0..65535 |
396 | * - Newline or other single whitespace character. |
397 | * (we support newline only) |
398 | * - A raster of Width * Height pixels in triplets of rgb |
399 | * in pure binary by 1 or 2 bytes. (we support only 1 byte) |
400 | */ |
401 | #define concat_buf bb_common_bufsiz1 |
402 | setup_common_bufsiz(); |
403 | |
404 | read_ptr = concat_buf; |
405 | while (1) { |
406 | int w, h, max_color_val; |
407 | int rem = concat_buf + COMMON_BUFSIZE - read_ptr; |
408 | if (rem < 2 |
409 | || fgets(read_ptr, rem, theme_file) == NULL |
410 | ) { |
411 | bb_error_msg_and_die("bad PPM file '%s'", G.image_filename); |
412 | } |
413 | read_ptr = strchrnul(read_ptr, '#'); |
414 | *read_ptr = '\0'; /* ignore #comments */ |
415 | if (sscanf(concat_buf, "P6 %u %u %u", &w, &h, &max_color_val) == 3 |
416 | && max_color_val <= 255 |
417 | ) { |
418 | width = w; /* w is on stack, width may be in register */ |
419 | height = h; |
420 | break; |
421 | } |
422 | } |
423 | |
424 | line_size = width*3; |
425 | pixline = xmalloc(line_size); |
426 | |
427 | if (width > G.scr_var.xres) |
428 | width = G.scr_var.xres; |
429 | if (height > G.scr_var.yres) |
430 | height = G.scr_var.yres; |
431 | for (j = 0; j < height; j++) { |
432 | unsigned char *pixel; |
433 | unsigned char *src; |
434 | |
435 | if (fread(pixline, 1, line_size, theme_file) != line_size) |
436 | bb_error_msg_and_die("bad PPM file '%s'", G.image_filename); |
437 | pixel = pixline; |
438 | src = G.addr + j * G.scr_fix.line_length; |
439 | for (i = 0; i < width; i++) { |
440 | unsigned thispix = fb_pixel_value(pixel[0], pixel[1], pixel[2]); |
441 | fb_write_pixel(src, thispix); |
442 | src += G.bytes_per_pixel; |
443 | pixel += 3; |
444 | } |
445 | } |
446 | free(pixline); |
447 | fclose(theme_file); |
448 | } |
449 | |
450 | |
451 | /** |
452 | * Parse configuration file |
453 | * \param *cfg_filename name of the configuration file |
454 | */ |
455 | static void init(const char *cfg_filename) |
456 | { |
457 | static const char param_names[] ALIGN1 = |
458 | "BAR_WIDTH\0" "BAR_HEIGHT\0" |
459 | "BAR_LEFT\0" "BAR_TOP\0" |
460 | "BAR_R\0" "BAR_G\0" "BAR_B\0" |
461 | #if DEBUG |
462 | "DEBUG\0" |
463 | #endif |
464 | ; |
465 | char *token[2]; |
466 | parser_t *parser = config_open2(cfg_filename, xfopen_stdin); |
467 | while (config_read(parser, token, 2, 2, "#=", |
468 | (PARSE_NORMAL | PARSE_MIN_DIE) & ~(PARSE_TRIM | PARSE_COLLAPSE))) { |
469 | unsigned val = xatoi_positive(token[1]); |
470 | int i = index_in_strings(param_names, token[0]); |
471 | if (i < 0) |
472 | bb_error_msg_and_die("syntax error: %s", token[0]); |
473 | if (i >= 0 && i < 7) |
474 | G.ns[i] = val; |
475 | #if DEBUG |
476 | if (i == 7) { |
477 | G.bdebug_messages = val; |
478 | if (G.bdebug_messages) |
479 | G.logfile_fd = xfopen_for_write("/tmp/fbsplash.log"); |
480 | } |
481 | #endif |
482 | } |
483 | config_close(parser); |
484 | } |
485 | |
486 | |
487 | int fbsplash_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; |
488 | int fbsplash_main(int argc UNUSED_PARAM, char **argv) |
489 | { |
490 | const char *fb_device, *cfg_filename, *fifo_filename; |
491 | FILE *fp = NULL; |
492 | char *num_buf; |
493 | unsigned num; |
494 | bool bCursorOff; |
495 | |
496 | INIT_G(); |
497 | |
498 | // parse command line options |
499 | fb_device = "/dev/graphics/fb0"; |
500 | cfg_filename = NULL; |
501 | fifo_filename = NULL; |
502 | bCursorOff = 1 & getopt32(argv, "cs:d:i:f:", |
503 | &G.image_filename, &fb_device, &cfg_filename, &fifo_filename); |
504 | |
505 | // parse configuration file |
506 | if (cfg_filename) |
507 | init(cfg_filename); |
508 | |
509 | // We must have -s IMG |
510 | if (!G.image_filename) |
511 | bb_show_usage(); |
512 | |
513 | fb_open(fb_device); |
514 | |
515 | if (fifo_filename && bCursorOff) { |
516 | // hide cursor (BEFORE any fb ops) |
517 | full_write(STDOUT_FILENO, "\033[?25l", 6); |
518 | } |
519 | |
520 | fb_drawimage(); |
521 | |
522 | if (!fifo_filename) |
523 | return EXIT_SUCCESS; |
524 | |
525 | fp = xfopen_stdin(fifo_filename); |
526 | if (fp != stdin) { |
527 | // For named pipes, we want to support this: |
528 | // mkfifo cmd_pipe |
529 | // fbsplash -f cmd_pipe .... & |
530 | // ... |
531 | // echo 33 >cmd_pipe |
532 | // ... |
533 | // echo 66 >cmd_pipe |
534 | // This means that we don't want fbsplash to get EOF |
535 | // when last writer closes input end. |
536 | // The simplest way is to open fifo for writing too |
537 | // and become an additional writer :) |
538 | open(fifo_filename, O_WRONLY); // errors are ignored |
539 | } |
540 | |
541 | fb_drawprogressbar(0); |
542 | // Block on read, waiting for some input. |
543 | // Use of <stdio.h> style I/O allows to correctly |
544 | // handle a case when we have many buffered lines |
545 | // already in the pipe |
546 | while ((num_buf = xmalloc_fgetline(fp)) != NULL) { |
547 | if (is_prefixed_with(num_buf, "exit")) { |
548 | DEBUG_MESSAGE("exit"); |
549 | break; |
550 | } |
551 | num = atoi(num_buf); |
552 | if (isdigit(num_buf[0]) && (num <= 100)) { |
553 | #if DEBUG |
554 | DEBUG_MESSAGE(itoa(num)); |
555 | #endif |
556 | fb_drawprogressbar(num); |
557 | } |
558 | free(num_buf); |
559 | } |
560 | |
561 | if (bCursorOff) // restore cursor |
562 | full_write(STDOUT_FILENO, "\033[?25h", 6); |
563 | |
564 | return EXIT_SUCCESS; |
565 | } |
566 |