busybox/miscutils/fbsplash.c
James Byrne 6937487be7 libbb: reduce the overhead of single parameter bb_error_msg() calls
Back in 2007, commit 0c97c9d437 ("'simple' error message functions by
Loic Grenie") introduced bb_simple_perror_msg() to allow for a lower
overhead call to bb_perror_msg() when only a string was being printed
with no parameters. This saves space for some CPU architectures because
it avoids the overhead of a call to a variadic function. However there
has never been a simple version of bb_error_msg(), and since 2007 many
new calls to bb_perror_msg() have been added that only take a single
parameter and so could have been using bb_simple_perror_message().

This changeset introduces 'simple' versions of bb_info_msg(),
bb_error_msg(), bb_error_msg_and_die(), bb_herror_msg() and
bb_herror_msg_and_die(), and replaces all calls that only take a
single parameter, or use something like ("%s", arg), with calls to the
corresponding 'simple' version.

Since it is likely that single parameter calls to the variadic functions
may be accidentally reintroduced in the future a new debugging config
option WARN_SIMPLE_MSG has been introduced. This uses some macro magic
which will cause any such calls to generate a warning, but this is
turned off by default to avoid use of the unpleasant macros in normal
circumstances.

This is a large changeset due to the number of calls that have been
replaced. The only files that contain changes other than simple
substitution of function calls are libbb.h, libbb/herror_msg.c,
libbb/verror_msg.c and libbb/xfuncs_printf.c. In miscutils/devfsd.c,
networking/udhcp/common.h and util-linux/mdev.c additonal macros have
been added for logging so that single parameter and multiple parameter
logging variants exist.

The amount of space saved varies considerably by architecture, and was
found to be as follows (for 'defconfig' using GCC 7.4):

Arm:     -92 bytes
MIPS:    -52 bytes
PPC:   -1836 bytes
x86_64: -938 bytes

Note that for the MIPS architecture only an exception had to be made
disabling the 'simple' calls for 'udhcp' (in networking/udhcp/common.h)
because it made these files larger on MIPS.

Signed-off-by: James Byrne <james.byrne@origamienergy.com>
Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
2019-07-02 11:35:03 +02:00

571 lines
15 KiB
C

/* vi: set sw=4 ts=4: */
/*
* Copyright (C) 2008 Michele Sanges <michele.sanges@gmail.com>
*
* Licensed under GPLv2 or later, see file LICENSE in this source tree.
*
* Usage:
* - use kernel option 'vga=xxx' or otherwise enable framebuffer device.
* - put somewhere fbsplash.cfg file and an image in .ppm format.
* - run applet: $ setsid fbsplash [params] &
* -c: hide cursor
* -d /dev/fbN: framebuffer device (if not /dev/fb0)
* -s path_to_image_file (can be "-" for stdin)
* -i path_to_cfg_file
* -f path_to_fifo (can be "-" for stdin)
* - if you want to run it only in presence of a kernel parameter
* (for example fbsplash=on), use:
* grep -q "fbsplash=on" </proc/cmdline && setsid fbsplash [params]
* - commands for fifo:
* "NN" (ASCII decimal number) - percentage to show on progress bar.
* "exit" (or just close fifo) - well you guessed it.
*/
//config:config FBSPLASH
//config: bool "fbsplash (26 kb)"
//config: default y
//config: select PLATFORM_LINUX
//config: help
//config: Shows splash image and progress bar on framebuffer device.
//config: Can be used during boot phase of an embedded device.
//config: Usage:
//config: - use kernel option 'vga=xxx' or otherwise enable fb device.
//config: - put somewhere fbsplash.cfg file and an image in .ppm format.
//config: - $ setsid fbsplash [params] &
//config: -c: hide cursor
//config: -d /dev/fbN: framebuffer device (if not /dev/fb0)
//config: -s path_to_image_file (can be "-" for stdin)
//config: -i path_to_cfg_file (can be "-" for stdin)
//config: -f path_to_fifo (can be "-" for stdin)
//config: - if you want to run it only in presence of kernel parameter:
//config: grep -q "fbsplash=on" </proc/cmdline && setsid fbsplash [params] &
//config: - commands for fifo:
//config: "NN" (ASCII decimal number) - percentage to show on progress bar
//config: "exit" - well you guessed it
//applet:IF_FBSPLASH(APPLET(fbsplash, BB_DIR_SBIN, BB_SUID_DROP))
//kbuild:lib-$(CONFIG_FBSPLASH) += fbsplash.o
//usage:#define fbsplash_trivial_usage
//usage: "-s IMGFILE [-c] [-d DEV] [-i INIFILE] [-f CMD]"
//usage:#define fbsplash_full_usage "\n\n"
//usage: " -s Image"
//usage: "\n -c Hide cursor"
//usage: "\n -d Framebuffer device (default /dev/fb0)"
//usage: "\n -i Config file (var=value):"
//usage: "\n BAR_LEFT,BAR_TOP,BAR_WIDTH,BAR_HEIGHT"
//usage: "\n BAR_R,BAR_G,BAR_B,IMG_LEFT,IMG_TOP"
//usage: "\n -f Control pipe (else exit after drawing image)"
//usage: "\n commands: 'NN' (% for progress bar) or 'exit'"
#include "libbb.h"
#include "common_bufsiz.h"
#include <linux/fb.h>
/* If you want logging messages on /tmp/fbsplash.log... */
#define DEBUG 0
#define ESC "\033"
struct globals {
#if DEBUG
bool bdebug_messages; // enable/disable logging
FILE *logfile_fd; // log file
#endif
unsigned char *addr; // pointer to framebuffer memory
unsigned ns[9]; // n-parameters
const char *image_filename;
struct fb_var_screeninfo scr_var;
struct fb_fix_screeninfo scr_fix;
unsigned bytes_per_pixel;
// cached (8 - scr_var.COLOR.length):
unsigned red_shift;
unsigned green_shift;
unsigned blue_shift;
};
#define G (*ptr_to_globals)
#define INIT_G() do { \
SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
} while (0)
#define nbar_width ns[0] // progress bar width
#define nbar_height ns[1] // progress bar height
#define nbar_posx ns[2] // progress bar horizontal position
#define nbar_posy ns[3] // progress bar vertical position
#define nbar_colr ns[4] // progress bar color red component
#define nbar_colg ns[5] // progress bar color green component
#define nbar_colb ns[6] // progress bar color blue component
#define img_posx ns[7] // image horizontal position
#define img_posy ns[8] // image vertical position
#if DEBUG
#define DEBUG_MESSAGE(strMessage, args...) \
if (G.bdebug_messages) { \
fprintf(G.logfile_fd, "[%s][%s] - %s\n", \
__FILE__, __FUNCTION__, strMessage); \
}
#else
#define DEBUG_MESSAGE(...) ((void)0)
#endif
/**
* Configure palette for RGB:332
*/
static void fb_setpal(int fd)
{
struct fb_cmap cmap;
/* fb colors are 16 bit */
unsigned short red[256], green[256], blue[256];
unsigned i;
/* RGB:332 */
for (i = 0; i < 256; i++) {
/* Color is encoded in pixel value as rrrgggbb.
* 3-bit color is mapped to 16-bit one as:
* 000 -> 00000000 00000000
* 001 -> 00100100 10010010
* ...
* 011 -> 01101101 10110110
* 100 -> 10010010 01001001
* ...
* 111 -> 11111111 11111111
*/
red[i] = (( i >> 5 ) * 0x9249) >> 2; // rrr * 00 10010010 01001001 >> 2
green[i] = (((i >> 2) & 0x7) * 0x9249) >> 2; // ggg * 00 10010010 01001001 >> 2
/* 2-bit color is easier: */
blue[i] = ( i & 0x3) * 0x5555; // bb * 01010101 01010101
}
cmap.start = 0;
cmap.len = 256;
cmap.red = red;
cmap.green = green;
cmap.blue = blue;
cmap.transp = 0;
xioctl(fd, FBIOPUTCMAP, &cmap);
}
/**
* Open and initialize the framebuffer device
* \param *strfb_device pointer to framebuffer device
*/
static void fb_open(const char *strfb_device)
{
int fbfd = xopen(strfb_device, O_RDWR);
// framebuffer properties
xioctl(fbfd, FBIOGET_VSCREENINFO, &G.scr_var);
xioctl(fbfd, FBIOGET_FSCREENINFO, &G.scr_fix);
switch (G.scr_var.bits_per_pixel) {
case 8:
fb_setpal(fbfd);
break;
case 16:
case 24:
case 32:
break;
default:
bb_error_msg_and_die("unsupported %u bpp", (int)G.scr_var.bits_per_pixel);
break;
}
G.red_shift = 8 - G.scr_var.red.length;
G.green_shift = 8 - G.scr_var.green.length;
G.blue_shift = 8 - G.scr_var.blue.length;
G.bytes_per_pixel = (G.scr_var.bits_per_pixel + 7) >> 3;
// map the device in memory
G.addr = mmap(NULL,
(G.scr_var.yres_virtual ?: G.scr_var.yres) * G.scr_fix.line_length,
PROT_WRITE, MAP_SHARED, fbfd, 0);
if (G.addr == MAP_FAILED)
bb_simple_perror_msg_and_die("mmap");
// point to the start of the visible screen
G.addr += G.scr_var.yoffset * G.scr_fix.line_length + G.scr_var.xoffset * G.bytes_per_pixel;
close(fbfd);
}
/**
* Return pixel value of the passed RGB color.
* This is performance critical fn.
*/
static unsigned fb_pixel_value(unsigned r, unsigned g, unsigned b)
{
/* We assume that the r,g,b values are <= 255 */
if (G.bytes_per_pixel == 1) {
r = r & 0xe0; // 3-bit red
g = (g >> 3) & 0x1c; // 3-bit green
b = b >> 6; // 2-bit blue
return r + g + b;
}
if (G.bytes_per_pixel == 2) {
// ARM PL110 on Integrator/CP has RGBA5551 bit arrangement.
// We want to support bit locations like that.
//
// First shift out unused bits
r = r >> G.red_shift;
g = g >> G.green_shift;
b = b >> G.blue_shift;
// Then shift the remaining bits to their offset
return (r << G.scr_var.red.offset) +
(g << G.scr_var.green.offset) +
(b << G.scr_var.blue.offset);
}
// RGB 888
return b + (g << 8) + (r << 16);
}
/**
* Draw pixel on framebuffer
*/
static void fb_write_pixel(unsigned char *addr, unsigned pixel)
{
switch (G.bytes_per_pixel) {
case 1:
*addr = pixel;
break;
case 2:
*(uint16_t *)addr = pixel;
break;
case 4:
*(uint32_t *)addr = pixel;
break;
default: // 24 bits per pixel
addr[0] = pixel;
addr[1] = pixel >> 8;
addr[2] = pixel >> 16;
}
}
/**
* Draw hollow rectangle on framebuffer
*/
static void fb_drawrectangle(void)
{
int cnt;
unsigned thispix;
unsigned char *ptr1, *ptr2;
unsigned char nred = G.nbar_colr/2;
unsigned char ngreen = G.nbar_colg/2;
unsigned char nblue = G.nbar_colb/2;
thispix = fb_pixel_value(nred, ngreen, nblue);
// horizontal lines
ptr1 = G.addr + G.nbar_posy * G.scr_fix.line_length + G.nbar_posx * G.bytes_per_pixel;
ptr2 = G.addr + (G.nbar_posy + G.nbar_height - 1) * G.scr_fix.line_length + G.nbar_posx * G.bytes_per_pixel;
cnt = G.nbar_width - 1;
do {
fb_write_pixel(ptr1, thispix);
fb_write_pixel(ptr2, thispix);
ptr1 += G.bytes_per_pixel;
ptr2 += G.bytes_per_pixel;
} while (--cnt >= 0);
// vertical lines
ptr1 = G.addr + G.nbar_posy * G.scr_fix.line_length + G.nbar_posx * G.bytes_per_pixel;
ptr2 = G.addr + G.nbar_posy * G.scr_fix.line_length + (G.nbar_posx + G.nbar_width - 1) * G.bytes_per_pixel;
cnt = G.nbar_height - 1;
do {
fb_write_pixel(ptr1, thispix);
fb_write_pixel(ptr2, thispix);
ptr1 += G.scr_fix.line_length;
ptr2 += G.scr_fix.line_length;
} while (--cnt >= 0);
}
/**
* Draw filled rectangle on framebuffer
* \param nx1pos,ny1pos upper left position
* \param nx2pos,ny2pos down right position
* \param nred,ngreen,nblue rgb color
*/
static void fb_drawfullrectangle(int nx1pos, int ny1pos, int nx2pos, int ny2pos,
unsigned char nred, unsigned char ngreen, unsigned char nblue)
{
int cnt1, cnt2, nypos;
unsigned thispix;
unsigned char *ptr;
thispix = fb_pixel_value(nred, ngreen, nblue);
cnt1 = ny2pos - ny1pos;
nypos = ny1pos;
do {
ptr = G.addr + nypos * G.scr_fix.line_length + nx1pos * G.bytes_per_pixel;
cnt2 = nx2pos - nx1pos;
do {
fb_write_pixel(ptr, thispix);
ptr += G.bytes_per_pixel;
} while (--cnt2 >= 0);
nypos++;
} while (--cnt1 >= 0);
}
/**
* Draw a progress bar on framebuffer
* \param percent percentage of loading
*/
static void fb_drawprogressbar(unsigned percent)
{
int left_x, top_y, pos_x;
unsigned width, height;
// outer box
left_x = G.nbar_posx;
top_y = G.nbar_posy;
width = G.nbar_width - 1;
height = G.nbar_height - 1;
if ((int)(height | width) < 0)
return;
// NB: "width" of 1 actually makes rect with width of 2!
fb_drawrectangle();
// inner "empty" rectangle
left_x++;
top_y++;
width -= 2;
height -= 2;
if ((int)(height | width) < 0)
return;
pos_x = left_x;
if (percent > 0) {
int i, y;
// actual progress bar
pos_x += (unsigned)(width * percent) / 100;
y = top_y;
i = height;
if (height == 0)
height++; // divide by 0 is bad
while (i >= 0) {
// draw one-line thick "rectangle"
// top line will have gray lvl 200, bottom one 100
unsigned gray_level = 100 + (unsigned)i*100 / height;
fb_drawfullrectangle(
left_x, y, pos_x, y,
gray_level, gray_level, gray_level);
y++;
i--;
}
}
fb_drawfullrectangle(
pos_x, top_y,
left_x + width, top_y + height,
G.nbar_colr, G.nbar_colg, G.nbar_colb);
}
/**
* Draw image from PPM file
*/
static void fb_drawimage(void)
{
FILE *theme_file;
char *read_ptr;
unsigned char *pixline;
unsigned i, j, width, height, line_size;
if (LONE_DASH(G.image_filename)) {
theme_file = stdin;
} else {
int fd = open_zipped(G.image_filename, /*fail_if_not_compressed:*/ 0);
if (fd < 0)
bb_simple_perror_msg_and_die(G.image_filename);
theme_file = xfdopen_for_read(fd);
}
/* Parse ppm header:
* - Magic: two characters "P6".
* - Whitespace (blanks, TABs, CRs, LFs).
* - A width, formatted as ASCII characters in decimal.
* - Whitespace.
* - A height, ASCII decimal.
* - Whitespace.
* - The maximum color value, ASCII decimal, in 0..65535
* - Newline or other single whitespace character.
* (we support newline only)
* - A raster of Width * Height pixels in triplets of rgb
* in pure binary by 1 or 2 bytes. (we support only 1 byte)
*/
#define concat_buf bb_common_bufsiz1
setup_common_bufsiz();
read_ptr = concat_buf;
while (1) {
int w, h, max_color_val;
int rem = concat_buf + COMMON_BUFSIZE - read_ptr;
if (rem < 2
|| fgets(read_ptr, rem, theme_file) == NULL
) {
bb_error_msg_and_die("bad PPM file '%s'", G.image_filename);
}
read_ptr = strchrnul(read_ptr, '#');
*read_ptr = '\0'; /* ignore #comments */
if (sscanf(concat_buf, "P6 %u %u %u", &w, &h, &max_color_val) == 3
&& max_color_val <= 255
) {
width = w; /* w is on stack, width may be in register */
height = h;
break;
}
}
line_size = width*3;
pixline = xmalloc(line_size);
if ((width + G.img_posx) > G.scr_var.xres)
width = G.scr_var.xres - G.img_posx;
if ((height + G.img_posy) > G.scr_var.yres)
height = G.scr_var.yres - G.img_posy;
for (j = 0; j < height; j++) {
unsigned char *pixel;
unsigned char *src;
if (fread(pixline, 1, line_size, theme_file) != line_size)
bb_error_msg_and_die("bad PPM file '%s'", G.image_filename);
pixel = pixline;
src = G.addr + (G.img_posy + j) * G.scr_fix.line_length + G.img_posx * G.bytes_per_pixel;
for (i = 0; i < width; i++) {
unsigned thispix = fb_pixel_value(pixel[0], pixel[1], pixel[2]);
fb_write_pixel(src, thispix);
src += G.bytes_per_pixel;
pixel += 3;
}
}
free(pixline);
fclose(theme_file);
}
/**
* Parse configuration file
* \param *cfg_filename name of the configuration file
*/
static void init(const char *cfg_filename)
{
static const char param_names[] ALIGN1 =
"BAR_WIDTH\0" "BAR_HEIGHT\0"
"BAR_LEFT\0" "BAR_TOP\0"
"BAR_R\0" "BAR_G\0" "BAR_B\0"
"IMG_LEFT\0" "IMG_TOP\0"
#if DEBUG
"DEBUG\0"
#endif
;
char *token[2];
parser_t *parser = config_open2(cfg_filename, xfopen_stdin);
while (config_read(parser, token, 2, 2, "#=",
(PARSE_NORMAL | PARSE_MIN_DIE) & ~(PARSE_TRIM | PARSE_COLLAPSE))) {
unsigned val = xatoi_positive(token[1]);
int i = index_in_strings(param_names, token[0]);
if (i < 0)
bb_error_msg_and_die("syntax error: %s", token[0]);
if (i >= 0 && i < 9)
G.ns[i] = val;
#if DEBUG
if (i == 9) {
G.bdebug_messages = val;
if (G.bdebug_messages)
G.logfile_fd = xfopen_for_write("/tmp/fbsplash.log");
}
#endif
}
config_close(parser);
}
int fbsplash_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
int fbsplash_main(int argc UNUSED_PARAM, char **argv)
{
const char *fb_device, *cfg_filename, *fifo_filename;
FILE *fp = fp; // for compiler
char *num_buf;
unsigned num;
bool bCursorOff;
INIT_G();
// parse command line options
fb_device = "/dev/fb0";
cfg_filename = NULL;
fifo_filename = NULL;
bCursorOff = 1 & getopt32(argv, "cs:d:i:f:",
&G.image_filename, &fb_device, &cfg_filename, &fifo_filename);
// parse configuration file
if (cfg_filename)
init(cfg_filename);
// We must have -s IMG
if (!G.image_filename)
bb_show_usage();
fb_open(fb_device);
if (fifo_filename && bCursorOff) {
// hide cursor (BEFORE any fb ops)
full_write(STDOUT_FILENO, ESC"[?25l", 6);
}
fb_drawimage();
if (!fifo_filename)
return EXIT_SUCCESS;
fp = xfopen_stdin(fifo_filename);
if (fp != stdin) {
// For named pipes, we want to support this:
// mkfifo cmd_pipe
// fbsplash -f cmd_pipe .... &
// ...
// echo 33 >cmd_pipe
// ...
// echo 66 >cmd_pipe
// This means that we don't want fbsplash to get EOF
// when last writer closes input end.
// The simplest way is to open fifo for writing too
// and become an additional writer :)
open(fifo_filename, O_WRONLY); // errors are ignored
}
fb_drawprogressbar(0);
// Block on read, waiting for some input.
// Use of <stdio.h> style I/O allows to correctly
// handle a case when we have many buffered lines
// already in the pipe
while ((num_buf = xmalloc_fgetline(fp)) != NULL) {
if (is_prefixed_with(num_buf, "exit")) {
DEBUG_MESSAGE("exit");
break;
}
num = atoi(num_buf);
if (isdigit(num_buf[0]) && (num <= 100)) {
#if DEBUG
DEBUG_MESSAGE(itoa(num));
#endif
fb_drawprogressbar(num);
}
free(num_buf);
}
if (bCursorOff) // restore cursor
full_write(STDOUT_FILENO, ESC"[?25h", 6);
return EXIT_SUCCESS;
}