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