Removed boot splash
This commit is contained in:
		
							parent
							
								
									237bd58a1c
								
							
						
					
					
						commit
						2b3450cc38
					
				| @ -1,150 +0,0 @@ | ||||
| --- b/drivers/video/fbdev/core/bitblit.c
 | ||||
| +++ a/drivers/video/fbdev/core/bitblit.c
 | ||||
| @@ -234,7 +234,7 @@
 | ||||
|  } | ||||
|   | ||||
|  static void bit_cursor(struct vc_data *vc, struct fb_info *info, int mode, | ||||
| +		       int softback_lines, int fg, int bg)
 | ||||
| -		       int fg, int bg)
 | ||||
|  { | ||||
|  	struct fb_cursor cursor; | ||||
|  	struct fbcon_ops *ops = info->fbcon_par; | ||||
| @@ -247,6 +247,15 @@
 | ||||
|   | ||||
|  	cursor.set = 0; | ||||
|   | ||||
| +	if (softback_lines) {
 | ||||
| +		if (y + softback_lines >= vc->vc_rows) {
 | ||||
| +			mode = CM_ERASE;
 | ||||
| +			ops->cursor_flash = 0;
 | ||||
| +			return;
 | ||||
| +		} else
 | ||||
| +			y += softback_lines;
 | ||||
| +	}
 | ||||
| +
 | ||||
|   	c = scr_readw((u16 *) vc->vc_pos); | ||||
|  	attribute = get_attribute(info, c); | ||||
|  	src = vc->vc_font.data + ((c & charmask) * (w * vc->vc_font.height)); | ||||
| --- b/drivers/video/fbdev/core/fbcon.c
 | ||||
| +++ a/drivers/video/fbdev/core/fbcon.c
 | ||||
| @@ -394,7 +394,7 @@
 | ||||
|  	c = scr_readw((u16 *) vc->vc_pos); | ||||
|  	mode = (!ops->cursor_flash || ops->cursor_state.enable) ? | ||||
|  		CM_ERASE : CM_DRAW; | ||||
| +	ops->cursor(vc, info, mode, 0, get_color(vc, info, c, 1),
 | ||||
| -	ops->cursor(vc, info, mode, get_color(vc, info, c, 1),
 | ||||
|  		    get_color(vc, info, c, 0)); | ||||
|  	console_unlock(); | ||||
|  } | ||||
| @@ -1345,7 +1345,7 @@
 | ||||
|   | ||||
|  	ops->cursor_flash = (mode == CM_ERASE) ? 0 : 1; | ||||
|   | ||||
| +	ops->cursor(vc, info, mode, 0, get_color(vc, info, c, 1),
 | ||||
| -	ops->cursor(vc, info, mode, get_color(vc, info, c, 1),
 | ||||
|  		    get_color(vc, info, c, 0)); | ||||
|  } | ||||
|   | ||||
| --- b/drivers/video/fbdev/core/fbcon.h
 | ||||
| +++ a/drivers/video/fbdev/core/fbcon.h
 | ||||
| @@ -62,7 +62,7 @@
 | ||||
|  	void (*clear_margins)(struct vc_data *vc, struct fb_info *info, | ||||
|  			      int color, int bottom_only); | ||||
|  	void (*cursor)(struct vc_data *vc, struct fb_info *info, int mode, | ||||
| +		       int softback_lines, int fg, int bg);
 | ||||
| -		       int fg, int bg);
 | ||||
|  	int  (*update_start)(struct fb_info *info); | ||||
|  	int  (*rotate_font)(struct fb_info *info, struct vc_data *vc); | ||||
|  	struct fb_var_screeninfo var;  /* copy of the current fb_var_screeninfo */ | ||||
| --- b/drivers/video/fbdev/core/fbcon_ccw.c
 | ||||
| +++ a/drivers/video/fbdev/core/fbcon_ccw.c
 | ||||
| @@ -219,7 +219,7 @@
 | ||||
|  } | ||||
|   | ||||
|  static void ccw_cursor(struct vc_data *vc, struct fb_info *info, int mode, | ||||
| +		       int softback_lines, int fg, int bg)
 | ||||
| -		       int fg, int bg)
 | ||||
|  { | ||||
|  	struct fb_cursor cursor; | ||||
|  	struct fbcon_ops *ops = info->fbcon_par; | ||||
| @@ -236,6 +236,15 @@
 | ||||
|   | ||||
|  	cursor.set = 0; | ||||
|   | ||||
| +	if (softback_lines) {
 | ||||
| +		if (y + softback_lines >= vc->vc_rows) {
 | ||||
| +			mode = CM_ERASE;
 | ||||
| +			ops->cursor_flash = 0;
 | ||||
| +			return;
 | ||||
| +		} else
 | ||||
| +			y += softback_lines;
 | ||||
| +	}
 | ||||
| +
 | ||||
|   	c = scr_readw((u16 *) vc->vc_pos); | ||||
|  	attribute = get_attribute(info, c); | ||||
|  	src = ops->fontbuffer + ((c & charmask) * (w * vc->vc_font.width)); | ||||
| --- b/drivers/video/fbdev/core/fbcon_cw.c
 | ||||
| +++ a/drivers/video/fbdev/core/fbcon_cw.c
 | ||||
| @@ -202,7 +202,7 @@
 | ||||
|  } | ||||
|   | ||||
|  static void cw_cursor(struct vc_data *vc, struct fb_info *info, int mode, | ||||
| +		      int softback_lines, int fg, int bg)
 | ||||
| -		      int fg, int bg)
 | ||||
|  { | ||||
|  	struct fb_cursor cursor; | ||||
|  	struct fbcon_ops *ops = info->fbcon_par; | ||||
| @@ -219,6 +219,15 @@
 | ||||
|   | ||||
|  	cursor.set = 0; | ||||
|   | ||||
| +	if (softback_lines) {
 | ||||
| +		if (y + softback_lines >= vc->vc_rows) {
 | ||||
| +			mode = CM_ERASE;
 | ||||
| +			ops->cursor_flash = 0;
 | ||||
| +			return;
 | ||||
| +		} else
 | ||||
| +			y += softback_lines;
 | ||||
| +	}
 | ||||
| +
 | ||||
|   	c = scr_readw((u16 *) vc->vc_pos); | ||||
|  	attribute = get_attribute(info, c); | ||||
|  	src = ops->fontbuffer + ((c & charmask) * (w * vc->vc_font.width)); | ||||
| --- b/drivers/video/fbdev/core/fbcon_ud.c
 | ||||
| +++ a/drivers/video/fbdev/core/fbcon_ud.c
 | ||||
| @@ -249,7 +249,7 @@
 | ||||
|  } | ||||
|   | ||||
|  static void ud_cursor(struct vc_data *vc, struct fb_info *info, int mode, | ||||
| +		      int softback_lines, int fg, int bg)
 | ||||
| -		      int fg, int bg)
 | ||||
|  { | ||||
|  	struct fb_cursor cursor; | ||||
|  	struct fbcon_ops *ops = info->fbcon_par; | ||||
| @@ -267,6 +267,15 @@
 | ||||
|   | ||||
|  	cursor.set = 0; | ||||
|   | ||||
| +	if (softback_lines) {
 | ||||
| +		if (y + softback_lines >= vc->vc_rows) {
 | ||||
| +			mode = CM_ERASE;
 | ||||
| +			ops->cursor_flash = 0;
 | ||||
| +			return;
 | ||||
| +		} else
 | ||||
| +			y += softback_lines;
 | ||||
| +	}
 | ||||
| +
 | ||||
|   	c = scr_readw((u16 *) vc->vc_pos); | ||||
|  	attribute = get_attribute(info, c); | ||||
|  	src = ops->fontbuffer + ((c & charmask) * (w * vc->vc_font.height)); | ||||
| --- b/drivers/video/fbdev/core/tileblit.c
 | ||||
| +++ a/drivers/video/fbdev/core/tileblit.c
 | ||||
| @@ -80,7 +80,7 @@
 | ||||
|  } | ||||
|   | ||||
|  static void tile_cursor(struct vc_data *vc, struct fb_info *info, int mode, | ||||
| +			int softback_lines, int fg, int bg)
 | ||||
| -			int fg, int bg)
 | ||||
|  { | ||||
|  	struct fb_tilecursor cursor; | ||||
|  	int use_sw = (vc->vc_cursor_type & 0x10); | ||||
| @ -1,31 +0,0 @@ | ||||
| --- b/drivers/video/fbdev/core/fbcon.c
 | ||||
| +++ a/drivers/video/fbdev/core/fbcon.c
 | ||||
| @@ -163,6 +163,8 @@
 | ||||
|   | ||||
|  #define advance_row(p, delta) (unsigned short *)((unsigned long)(p) + (delta) * vc->vc_size_row) | ||||
|   | ||||
| +static int fbcon_set_origin(struct vc_data *);
 | ||||
| +
 | ||||
|  static int fbcon_cursor_noblink; | ||||
|   | ||||
|  #define divides(a, b)	((!(a) || (b)%(a)) ? 0 : 1) | ||||
| @@ -2633,6 +2635,11 @@
 | ||||
|  	} | ||||
|  } | ||||
|   | ||||
| +static int fbcon_set_origin(struct vc_data *vc)
 | ||||
| +{
 | ||||
| +	return 0;
 | ||||
| +}
 | ||||
| +
 | ||||
|  void fbcon_suspended(struct fb_info *info) | ||||
|  { | ||||
|  	struct vc_data *vc = NULL; | ||||
| @@ -3103,6 +3110,7 @@
 | ||||
|  	.con_font_default	= fbcon_set_def_font, | ||||
|  	.con_font_copy 		= fbcon_copy_font, | ||||
|  	.con_set_palette 	= fbcon_set_palette, | ||||
| +	.con_set_origin 	= fbcon_set_origin,
 | ||||
|  	.con_invert_region 	= fbcon_invert_region, | ||||
|  	.con_screen_pos 	= fbcon_screen_pos, | ||||
|  	.con_getxy 		= fbcon_getxy, | ||||
| @ -1,500 +0,0 @@ | ||||
| --- b/drivers/video/fbdev/core/fbcon.c
 | ||||
| +++ a/drivers/video/fbdev/core/fbcon.c
 | ||||
| @@ -124,6 +124,12 @@ static int logo_lines;
 | ||||
|  /* logo_shown is an index to vc_cons when >= 0; otherwise follows FBCON_LOGO | ||||
|     enums.  */ | ||||
|  static int logo_shown = FBCON_LOGO_CANSHOW; | ||||
| +/* Software scrollback */
 | ||||
| +static int fbcon_softback_size = 32768;
 | ||||
| +static unsigned long softback_buf, softback_curr;
 | ||||
| +static unsigned long softback_in;
 | ||||
| +static unsigned long softback_top, softback_end;
 | ||||
| +static int softback_lines;
 | ||||
|  /* console mappings */ | ||||
|  static unsigned int first_fb_vc; | ||||
|  static unsigned int last_fb_vc = MAX_NR_CONSOLES - 1; | ||||
| @@ -163,6 +169,8 @@ static int margin_color;
 | ||||
|   | ||||
|  static const struct consw fb_con; | ||||
|   | ||||
| +#define CM_SOFTBACK	(8)
 | ||||
| +
 | ||||
|  #define advance_row(p, delta) (unsigned short *)((unsigned long)(p) + (delta) * vc->vc_size_row) | ||||
|   | ||||
|  static int fbcon_set_origin(struct vc_data *); | ||||
| @@ -347,6 +355,18 @@ static int get_color(struct vc_data *vc,
 | ||||
|  	return color; | ||||
|  } | ||||
|   | ||||
| +static void fbcon_update_softback(struct vc_data *vc)
 | ||||
| +{
 | ||||
| +	int l = fbcon_softback_size / vc->vc_size_row;
 | ||||
| +
 | ||||
| +	if (l > 5)
 | ||||
| +		softback_end = softback_buf + l * vc->vc_size_row;
 | ||||
| +	else
 | ||||
| +		/* Smaller scrollback makes no sense, and 0 would screw
 | ||||
| +		   the operation totally */
 | ||||
| +		softback_top = 0;
 | ||||
| +}
 | ||||
| +
 | ||||
|  static void fb_flashcursor(struct work_struct *work) | ||||
|  { | ||||
|  	struct fbcon_ops *ops = container_of(work, struct fbcon_ops, cursor_work.work); | ||||
| @@ -379,7 +399,7 @@ static void fb_flashcursor(struct work_s
 | ||||
|  	c = scr_readw((u16 *) vc->vc_pos); | ||||
|  	mode = (!ops->cursor_flash || ops->cursor_state.enable) ? | ||||
|  		CM_ERASE : CM_DRAW; | ||||
| -	ops->cursor(vc, info, mode, 0, get_color(vc, info, c, 1),
 | ||||
| +	ops->cursor(vc, info, mode, softback_lines, get_color(vc, info, c, 1),
 | ||||
|  		    get_color(vc, info, c, 0)); | ||||
|  	console_unlock(); | ||||
|   | ||||
| @@ -419,7 +439,13 @@ static int __init fb_console_setup(char
 | ||||
|  		} | ||||
|  		 | ||||
|  		if (!strncmp(options, "scrollback:", 11)) { | ||||
| -			pr_warn("Ignoring scrollback size option\n");
 | ||||
| +			options += 11;
 | ||||
| +			if (*options) {
 | ||||
| +				fbcon_softback_size = simple_strtoul(options, &options, 0);
 | ||||
| +				if (*options == 'k' || *options == 'K') {
 | ||||
| +					fbcon_softback_size *= 1024;
 | ||||
| +				}
 | ||||
| +			}
 | ||||
|  			continue; | ||||
|  		} | ||||
|  		 | ||||
| @@ -959,6 +985,31 @@ static const char *fbcon_startup(void)
 | ||||
|   | ||||
|  	set_blitting_type(vc, info); | ||||
|   | ||||
| +	if (info->fix.type != FB_TYPE_TEXT) {
 | ||||
| +		if (fbcon_softback_size) {
 | ||||
| +			if (!softback_buf) {
 | ||||
| +				softback_buf =
 | ||||
| +				    (unsigned long)
 | ||||
| +				    kvmalloc(fbcon_softback_size,
 | ||||
| +					    GFP_KERNEL);
 | ||||
| +				if (!softback_buf) {
 | ||||
| +					fbcon_softback_size = 0;
 | ||||
| +					softback_top = 0;
 | ||||
| +				}
 | ||||
| +			}
 | ||||
| +		} else {
 | ||||
| +			if (softback_buf) {
 | ||||
| +				kvfree((void *) softback_buf);
 | ||||
| +				softback_buf = 0;
 | ||||
| +				softback_top = 0;
 | ||||
| +			}
 | ||||
| +		}
 | ||||
| +		if (softback_buf)
 | ||||
| +			softback_in = softback_top = softback_curr =
 | ||||
| +			    softback_buf;
 | ||||
| +		softback_lines = 0;
 | ||||
| +	}
 | ||||
| +
 | ||||
|  	/* Setup default font */ | ||||
|  	if (!p->fontdata && !vc->vc_font.data) { | ||||
|  		if (!fontname[0] || !(font = find_font(fontname))) | ||||
| @@ -1129,6 +1180,9 @@ static void fbcon_init(struct vc_data *v
 | ||||
|  	if (logo) | ||||
|  		fbcon_prepare_logo(vc, info, cols, rows, new_cols, new_rows); | ||||
|   | ||||
| +	if (vc == svc && softback_buf)
 | ||||
| +		fbcon_update_softback(vc);
 | ||||
| +
 | ||||
|  	if (ops->rotate_font && ops->rotate_font(info, vc)) { | ||||
|  		ops->rotate = FB_ROTATE_UR; | ||||
|  		set_blitting_type(vc, info); | ||||
| @@ -1152,6 +1206,9 @@ static void fbcon_release_all(void)
 | ||||
|  	struct fb_info *info; | ||||
|  	int i, j, mapped; | ||||
|   | ||||
| +	kvfree((void *)softback_buf);
 | ||||
| +	softback_buf = 0UL;
 | ||||
| +
 | ||||
|  	fbcon_for_each_registered_fb(i) { | ||||
|  		mapped = 0; | ||||
|  		info = fbcon_registered_fb[i]; | ||||
| @@ -1312,6 +1369,7 @@ static void fbcon_cursor(struct vc_data
 | ||||
|  { | ||||
|  	struct fb_info *info = fbcon_info_from_console(vc->vc_num); | ||||
|  	struct fbcon_ops *ops = info->fbcon_par; | ||||
| +	int y;
 | ||||
|   	int c = scr_readw((u16 *) vc->vc_pos); | ||||
|   | ||||
|  	ops->cur_blink_jiffies = msecs_to_jiffies(vc->vc_cur_blink_ms); | ||||
| @@ -1325,11 +1383,19 @@ static void fbcon_cursor(struct vc_data
 | ||||
|  		fbcon_add_cursor_work(info); | ||||
|   | ||||
|  	ops->cursor_flash = (mode == CM_ERASE) ? 0 : 1; | ||||
| +	if (mode & CM_SOFTBACK) {
 | ||||
| +		mode &= ~CM_SOFTBACK;
 | ||||
| +		y = softback_lines;
 | ||||
| +	} else {
 | ||||
| +		if (softback_lines)
 | ||||
| +			fbcon_set_origin(vc);
 | ||||
| +		y = 0;
 | ||||
| +	}
 | ||||
|   | ||||
|  	if (!ops->cursor) | ||||
|  		return; | ||||
|   | ||||
| -	ops->cursor(vc, info, mode, 0, get_color(vc, info, c, 1),
 | ||||
| +	ops->cursor(vc, info, mode, y, get_color(vc, info, c, 1),
 | ||||
|  		    get_color(vc, info, c, 0)); | ||||
|  } | ||||
|   | ||||
| @@ -1399,6 +1465,8 @@ static void fbcon_set_disp(struct fb_inf
 | ||||
|   | ||||
|  	if (con_is_visible(vc)) { | ||||
|  		update_screen(vc); | ||||
| +		if (softback_buf)
 | ||||
| +			fbcon_update_softback(vc);
 | ||||
|  	} | ||||
|  } | ||||
|   | ||||
| @@ -1536,6 +1604,99 @@ static __inline__ void ypan_down_redraw(
 | ||||
|  	scrollback_current = 0; | ||||
|  } | ||||
|   | ||||
| +static void fbcon_redraw_softback(struct vc_data *vc, struct fbcon_display *p,
 | ||||
| +				  long delta)
 | ||||
| +{
 | ||||
| +	int count = vc->vc_rows;
 | ||||
| +	unsigned short *d, *s;
 | ||||
| +	unsigned long n;
 | ||||
| +	int line = 0;
 | ||||
| +
 | ||||
| +	d = (u16 *) softback_curr;
 | ||||
| +	if (d == (u16 *) softback_in)
 | ||||
| +		d = (u16 *) vc->vc_origin;
 | ||||
| +	n = softback_curr + delta * vc->vc_size_row;
 | ||||
| +	softback_lines -= delta;
 | ||||
| +	if (delta < 0) {
 | ||||
| +		if (softback_curr < softback_top && n < softback_buf) {
 | ||||
| +			n += softback_end - softback_buf;
 | ||||
| +			if (n < softback_top) {
 | ||||
| +				softback_lines -=
 | ||||
| +				    (softback_top - n) / vc->vc_size_row;
 | ||||
| +				n = softback_top;
 | ||||
| +			}
 | ||||
| +		} else if (softback_curr >= softback_top
 | ||||
| +			   && n < softback_top) {
 | ||||
| +			softback_lines -=
 | ||||
| +			    (softback_top - n) / vc->vc_size_row;
 | ||||
| +			n = softback_top;
 | ||||
| +		}
 | ||||
| +	} else {
 | ||||
| +		if (softback_curr > softback_in && n >= softback_end) {
 | ||||
| +			n += softback_buf - softback_end;
 | ||||
| +			if (n > softback_in) {
 | ||||
| +				n = softback_in;
 | ||||
| +				softback_lines = 0;
 | ||||
| +			}
 | ||||
| +		} else if (softback_curr <= softback_in && n > softback_in) {
 | ||||
| +			n = softback_in;
 | ||||
| +			softback_lines = 0;
 | ||||
| +		}
 | ||||
| +	}
 | ||||
| +	if (n == softback_curr)
 | ||||
| +		return;
 | ||||
| +	softback_curr = n;
 | ||||
| +	s = (u16 *) softback_curr;
 | ||||
| +	if (s == (u16 *) softback_in)
 | ||||
| +		s = (u16 *) vc->vc_origin;
 | ||||
| +	while (count--) {
 | ||||
| +		unsigned short *start;
 | ||||
| +		unsigned short *le;
 | ||||
| +		unsigned short c;
 | ||||
| +		int x = 0;
 | ||||
| +		unsigned short attr = 1;
 | ||||
| +
 | ||||
| +		start = s;
 | ||||
| +		le = advance_row(s, 1);
 | ||||
| +		do {
 | ||||
| +			c = scr_readw(s);
 | ||||
| +			if (attr != (c & 0xff00)) {
 | ||||
| +				attr = c & 0xff00;
 | ||||
| +				if (s > start) {
 | ||||
| +					fbcon_putcs(vc, start, s - start,
 | ||||
| +						    line, x);
 | ||||
| +					x += s - start;
 | ||||
| +					start = s;
 | ||||
| +				}
 | ||||
| +			}
 | ||||
| +			if (c == scr_readw(d)) {
 | ||||
| +				if (s > start) {
 | ||||
| +					fbcon_putcs(vc, start, s - start,
 | ||||
| +						    line, x);
 | ||||
| +					x += s - start + 1;
 | ||||
| +					start = s + 1;
 | ||||
| +				} else {
 | ||||
| +					x++;
 | ||||
| +					start++;
 | ||||
| +				}
 | ||||
| +			}
 | ||||
| +			s++;
 | ||||
| +			d++;
 | ||||
| +		} while (s < le);
 | ||||
| +		if (s > start)
 | ||||
| +			fbcon_putcs(vc, start, s - start, line, x);
 | ||||
| +		line++;
 | ||||
| +		if (d == (u16 *) softback_end)
 | ||||
| +			d = (u16 *) softback_buf;
 | ||||
| +		if (d == (u16 *) softback_in)
 | ||||
| +			d = (u16 *) vc->vc_origin;
 | ||||
| +		if (s == (u16 *) softback_end)
 | ||||
| +			s = (u16 *) softback_buf;
 | ||||
| +		if (s == (u16 *) softback_in)
 | ||||
| +			s = (u16 *) vc->vc_origin;
 | ||||
| +	}
 | ||||
| +}
 | ||||
| +
 | ||||
|  static void fbcon_redraw_move(struct vc_data *vc, struct fbcon_display *p, | ||||
|  			      int line, int count, int dy) | ||||
|  { | ||||
| @@ -1740,6 +1901,31 @@ static void fbcon_bmove(struct vc_data *
 | ||||
|  			p->vrows - p->yscroll); | ||||
|  } | ||||
|   | ||||
| +static inline void fbcon_softback_note(struct vc_data *vc, int t,
 | ||||
| +				       int count)
 | ||||
| +{
 | ||||
| +	unsigned short *p;
 | ||||
| +
 | ||||
| +	if (vc->vc_num != fg_console)
 | ||||
| +		return;
 | ||||
| +	p = (unsigned short *) (vc->vc_origin + t * vc->vc_size_row);
 | ||||
| +
 | ||||
| +	while (count) {
 | ||||
| +		scr_memcpyw((u16 *) softback_in, p, vc->vc_size_row);
 | ||||
| +		count--;
 | ||||
| +		p = advance_row(p, 1);
 | ||||
| +		softback_in += vc->vc_size_row;
 | ||||
| +		if (softback_in == softback_end)
 | ||||
| +			softback_in = softback_buf;
 | ||||
| +		if (softback_in == softback_top) {
 | ||||
| +			softback_top += vc->vc_size_row;
 | ||||
| +			if (softback_top == softback_end)
 | ||||
| +				softback_top = softback_buf;
 | ||||
| +		}
 | ||||
| +	}
 | ||||
| +	softback_curr = softback_in;
 | ||||
| +}
 | ||||
| +
 | ||||
|  static bool fbcon_scroll(struct vc_data *vc, unsigned int t, unsigned int b, | ||||
|  		enum con_scroll dir, unsigned int count) | ||||
|  { | ||||
| @@ -1762,6 +1948,8 @@ static bool fbcon_scroll(struct vc_data
 | ||||
|  	case SM_UP: | ||||
|  		if (count > vc->vc_rows)	/* Maximum realistic size */ | ||||
|  			count = vc->vc_rows; | ||||
| +		if (softback_top)
 | ||||
| +			fbcon_softback_note(vc, t, count);
 | ||||
|  		switch (fb_scrollmode(p)) { | ||||
|  		case SCROLL_MOVE: | ||||
|  			fbcon_redraw_blit(vc, info, p, t, b - t - count, | ||||
| @@ -2076,6 +2264,14 @@ static int fbcon_switch(struct vc_data *
 | ||||
|  	info = fbcon_info_from_console(vc->vc_num); | ||||
|  	ops = info->fbcon_par; | ||||
|   | ||||
| +	if (softback_top) {
 | ||||
| +		if (softback_lines)
 | ||||
| +			fbcon_set_origin(vc);
 | ||||
| +		softback_top = softback_curr = softback_in = softback_buf;
 | ||||
| +		softback_lines = 0;
 | ||||
| +		fbcon_update_softback(vc);
 | ||||
| +	}
 | ||||
| +
 | ||||
|  	if (logo_shown >= 0) { | ||||
|  		struct vc_data *conp2 = vc_cons[logo_shown].d; | ||||
|   | ||||
| @@ -2406,6 +2602,9 @@ static int fbcon_do_set_font(struct vc_d
 | ||||
|  	int resize; | ||||
|  	char *old_data = NULL; | ||||
|   | ||||
| +	if (con_is_visible(vc) && softback_lines)
 | ||||
| +		fbcon_set_origin(vc);
 | ||||
| +
 | ||||
|  	resize = (w != vc->vc_font.width) || (h != vc->vc_font.height); | ||||
|  	if (p->userfont) | ||||
|  		old_data = vc->vc_font.data; | ||||
| @@ -2428,6 +2627,8 @@ static int fbcon_do_set_font(struct vc_d
 | ||||
|  		cols /= w; | ||||
|  		rows /= h; | ||||
|  		vc_resize(vc, cols, rows); | ||||
| +		if (con_is_visible(vc) && softback_buf)
 | ||||
| +			fbcon_update_softback(vc);
 | ||||
|  	} else if (con_is_visible(vc) | ||||
|  		   && vc->vc_mode == KD_TEXT) { | ||||
|  		fbcon_clear_margins(vc, 0); | ||||
| @@ -2582,7 +2783,19 @@ static void fbcon_set_palette(struct vc_
 | ||||
|   | ||||
|  static u16 *fbcon_screen_pos(const struct vc_data *vc, int offset) | ||||
|  { | ||||
| -	return (u16 *) (vc->vc_origin + offset);
 | ||||
| +	unsigned long p;
 | ||||
| +	int line;
 | ||||
| +	
 | ||||
| +	if (vc->vc_num != fg_console || !softback_lines)
 | ||||
| +		return (u16 *) (vc->vc_origin + offset);
 | ||||
| +	line = offset / vc->vc_size_row;
 | ||||
| +	if (line >= softback_lines)
 | ||||
| +		return (u16 *) (vc->vc_origin + offset -
 | ||||
| +				softback_lines * vc->vc_size_row);
 | ||||
| +	p = softback_curr + offset;
 | ||||
| +	if (p >= softback_end)
 | ||||
| +		p += softback_buf - softback_end;
 | ||||
| +	return (u16 *) p;
 | ||||
|  } | ||||
|   | ||||
|  static unsigned long fbcon_getxy(struct vc_data *vc, unsigned long pos, | ||||
| @@ -2596,7 +2809,22 @@ static unsigned long fbcon_getxy(struct
 | ||||
|   | ||||
|  		x = offset % vc->vc_cols; | ||||
|  		y = offset / vc->vc_cols; | ||||
| +		if (vc->vc_num == fg_console)
 | ||||
| +			y += softback_lines;
 | ||||
| +		ret = pos + (vc->vc_cols - x) * 2;
 | ||||
| +	} else if (vc->vc_num == fg_console && softback_lines) {
 | ||||
| +		unsigned long offset = pos - softback_curr;
 | ||||
| +
 | ||||
| +		if (pos < softback_curr)
 | ||||
| +			offset += softback_end - softback_buf;
 | ||||
| +		offset /= 2;
 | ||||
| +		x = offset % vc->vc_cols;
 | ||||
| +		y = offset / vc->vc_cols;
 | ||||
|  		ret = pos + (vc->vc_cols - x) * 2; | ||||
| +		if (ret == softback_end)
 | ||||
| +			ret = softback_buf;
 | ||||
| +		if (ret == softback_in)
 | ||||
| +			ret = vc->vc_origin;
 | ||||
|  	} else { | ||||
|  		/* Should not happen */ | ||||
|  		x = y = 0; | ||||
| @@ -2624,11 +2852,106 @@ static void fbcon_invert_region(struct v
 | ||||
|  			a = ((a) & 0x88ff) | (((a) & 0x7000) >> 4) | | ||||
|  			    (((a) & 0x0700) << 4); | ||||
|  		scr_writew(a, p++); | ||||
| +		if (p == (u16 *) softback_end)
 | ||||
| +			p = (u16 *) softback_buf;
 | ||||
| +		if (p == (u16 *) softback_in)
 | ||||
| +			p = (u16 *) vc->vc_origin;
 | ||||
| +	}
 | ||||
| +}
 | ||||
| +
 | ||||
| +static void fbcon_scrolldelta(struct vc_data *vc, int lines)
 | ||||
| +{
 | ||||
| +	struct fb_info *info = registered_fb[con2fb_map[fg_console]];
 | ||||
| +	struct fbcon_ops *ops = info->fbcon_par;
 | ||||
| +	struct fbcon_display *disp = &fb_display[fg_console];
 | ||||
| +	int offset, limit, scrollback_old;
 | ||||
| +
 | ||||
| +	if (softback_top) {
 | ||||
| +		if (vc->vc_num != fg_console)
 | ||||
| +			return;
 | ||||
| +		if (vc->vc_mode != KD_TEXT || !lines)
 | ||||
| +			return;
 | ||||
| +		if (logo_shown >= 0) {
 | ||||
| +			struct vc_data *conp2 = vc_cons[logo_shown].d;
 | ||||
| +
 | ||||
| +			if (conp2->vc_top == logo_lines
 | ||||
| +			    && conp2->vc_bottom == conp2->vc_rows)
 | ||||
| +				conp2->vc_top = 0;
 | ||||
| +			if (logo_shown == vc->vc_num) {
 | ||||
| +				unsigned long p, q;
 | ||||
| +				int i;
 | ||||
| +
 | ||||
| +				p = softback_in;
 | ||||
| +				q = vc->vc_origin +
 | ||||
| +				    logo_lines * vc->vc_size_row;
 | ||||
| +				for (i = 0; i < logo_lines; i++) {
 | ||||
| +					if (p == softback_top)
 | ||||
| +						break;
 | ||||
| +					if (p == softback_buf)
 | ||||
| +						p = softback_end;
 | ||||
| +					p -= vc->vc_size_row;
 | ||||
| +					q -= vc->vc_size_row;
 | ||||
| +					scr_memcpyw((u16 *) q, (u16 *) p,
 | ||||
| +						    vc->vc_size_row);
 | ||||
| +				}
 | ||||
| +				softback_in = softback_curr = p;
 | ||||
| +				update_region(vc, vc->vc_origin,
 | ||||
| +					      logo_lines * vc->vc_cols);
 | ||||
| +			}
 | ||||
| +			logo_shown = FBCON_LOGO_CANSHOW;
 | ||||
| +		}
 | ||||
| +		fbcon_cursor(vc, CM_ERASE | CM_SOFTBACK);
 | ||||
| +		fbcon_redraw_softback(vc, disp, lines);
 | ||||
| +		fbcon_cursor(vc, CM_DRAW | CM_SOFTBACK);
 | ||||
| +		return;
 | ||||
|  	} | ||||
| +
 | ||||
| +	if (!scrollback_phys_max)
 | ||||
| +		return;
 | ||||
| +
 | ||||
| +	scrollback_old = scrollback_current;
 | ||||
| +	scrollback_current -= lines;
 | ||||
| +	if (scrollback_current < 0)
 | ||||
| +		scrollback_current = 0;
 | ||||
| +	else if (scrollback_current > scrollback_max)
 | ||||
| +		scrollback_current = scrollback_max;
 | ||||
| +	if (scrollback_current == scrollback_old)
 | ||||
| +		return;
 | ||||
| +
 | ||||
| +	if (fbcon_is_inactive(vc, info))
 | ||||
| +		return;
 | ||||
| +
 | ||||
| +	fbcon_cursor(vc, CM_ERASE);
 | ||||
| +
 | ||||
| +	offset = disp->yscroll - scrollback_current;
 | ||||
| +	limit = disp->vrows;
 | ||||
| +	switch (disp->scrollmode) {
 | ||||
| +	case SCROLL_WRAP_MOVE:
 | ||||
| +		info->var.vmode |= FB_VMODE_YWRAP;
 | ||||
| +		break;
 | ||||
| +	case SCROLL_PAN_MOVE:
 | ||||
| +	case SCROLL_PAN_REDRAW:
 | ||||
| +		limit -= vc->vc_rows;
 | ||||
| +		info->var.vmode &= ~FB_VMODE_YWRAP;
 | ||||
| +		break;
 | ||||
| +	}
 | ||||
| +	if (offset < 0)
 | ||||
| +		offset += limit;
 | ||||
| +	else if (offset >= limit)
 | ||||
| +		offset -= limit;
 | ||||
| +
 | ||||
| +	ops->var.xoffset = 0;
 | ||||
| +	ops->var.yoffset = offset * vc->vc_font.height;
 | ||||
| +	ops->update_start(info);
 | ||||
| +
 | ||||
| +	if (!scrollback_current)
 | ||||
| +		fbcon_cursor(vc, CM_DRAW);
 | ||||
|  } | ||||
|   | ||||
|  static int fbcon_set_origin(struct vc_data *vc) | ||||
|  { | ||||
| +	if (softback_lines)
 | ||||
| +		fbcon_scrolldelta(vc, softback_lines);
 | ||||
|  	return 0; | ||||
|  } | ||||
|   | ||||
| @@ -2692,6 +3015,8 @@ static void fbcon_modechanged(struct fb_
 | ||||
|   | ||||
|  		fbcon_set_palette(vc, color_table); | ||||
|  		update_screen(vc); | ||||
| +		if (softback_buf)
 | ||||
| +			fbcon_update_softback(vc);
 | ||||
|  	} | ||||
|  } | ||||
|   | ||||
| @@ -3154,6 +3479,7 @@ static const struct consw fb_con = {
 | ||||
|  	.con_font_get 		= fbcon_get_font, | ||||
|  	.con_font_default	= fbcon_set_def_font, | ||||
|  	.con_set_palette 	= fbcon_set_palette, | ||||
| +	.con_scrolldelta 	= fbcon_scrolldelta,
 | ||||
|  	.con_set_origin 	= fbcon_set_origin, | ||||
|  	.con_invert_region 	= fbcon_invert_region, | ||||
|  	.con_screen_pos 	= fbcon_screen_pos, | ||||
| @ -1,746 +0,0 @@ | ||||
| diff --git a/MAINTAINERS b/MAINTAINERS
 | ||||
| index a74227ad082e..b5633b56391e 100644
 | ||||
| --- a/MAINTAINERS
 | ||||
| +++ b/MAINTAINERS
 | ||||
| @@ -3615,6 +3615,14 @@
 | ||||
|  F:     Documentation/devicetree/bindings/iio/accel/bosch,bma400.yaml | ||||
|  F:     drivers/iio/accel/bma400* | ||||
|   | ||||
| +BOOTSPLASH
 | ||||
| +M:     Max Staudt <mstaudt@suse.de>
 | ||||
| +L:     linux-fbdev@vger.kernel.org
 | ||||
| +S:     Maintained
 | ||||
| +F:     drivers/video/fbdev/core/bootsplash*.*
 | ||||
| +F:     drivers/video/fbdev/core/dummycon.c
 | ||||
| +F:     include/linux/bootsplash.h
 | ||||
| +
 | ||||
|  BPF [GENERAL] (Safe Dynamic Programs and Tools) | ||||
|  M:     Alexei Starovoitov <ast@kernel.org> | ||||
|  M:     Daniel Borkmann <daniel@iogearbox.net> | ||||
| diff --git a/drivers/video/console/Kconfig b/drivers/video/console/Kconfig
 | ||||
| index 7f1f1fbcef9e..f3ff976266fe 100644
 | ||||
| --- a/drivers/video/console/Kconfig
 | ||||
| +++ b/drivers/video/console/Kconfig
 | ||||
| @@ -151,6 +151,30 @@ config FRAMEBUFFER_CONSOLE_ROTATION
 | ||||
|           such that other users of the framebuffer will remain normally | ||||
|           oriented. | ||||
|   | ||||
| +config BOOTSPLASH
 | ||||
| +	bool "Bootup splash screen"
 | ||||
| +	depends on FRAMEBUFFER_CONSOLE
 | ||||
| +	help
 | ||||
| +	  This option enables the Linux bootsplash screen.
 | ||||
| +
 | ||||
| +	  The bootsplash is a full-screen logo or animation indicating a
 | ||||
| +	  booting system. It replaces the classic scrolling text with a
 | ||||
| +	  graphical alternative, similar to other systems.
 | ||||
| +
 | ||||
| +	  Since this is technically implemented as a hook on top of fbcon,
 | ||||
| +	  it can only work if the FRAMEBUFFER_CONSOLE is enabled and a
 | ||||
| +	  framebuffer driver is active. Thus, to get a text-free boot,
 | ||||
| +	  the system needs to boot with vesafb, efifb, or similar.
 | ||||
| +
 | ||||
| +	  Once built into the kernel, the bootsplash needs to be enabled
 | ||||
| +	  with bootsplash.enabled=1 and a splash file needs to be supplied.
 | ||||
| +
 | ||||
| +	  Further documentation can be found in:
 | ||||
| +	    Documentation/fb/bootsplash.txt
 | ||||
| +
 | ||||
| +	  If unsure, say N.
 | ||||
| +	  This is typically used by distributors and system integrators.
 | ||||
| +
 | ||||
|  config STI_CONSOLE | ||||
|          bool "STI text console" | ||||
|          depends on PARISC | ||||
| diff --git a/drivers/video/fbdev/core/Makefile b/drivers/video/fbdev/core/Makefile
 | ||||
| index 73493bbd7a15..66895321928e 100644
 | ||||
| --- a/drivers/video/fbdev/core/Makefile
 | ||||
| +++ b/drivers/video/fbdev/core/Makefile
 | ||||
| @@ -29,3 +29,6 @@ obj-$(CONFIG_FB_SYS_IMAGEBLIT) += sysimgblt.o
 | ||||
|  obj-$(CONFIG_FB_SYS_FOPS)      += fb_sys_fops.o | ||||
|  obj-$(CONFIG_FB_SVGALIB)       += svgalib.o | ||||
|  obj-$(CONFIG_FB_DDC)           += fb_ddc.o | ||||
| +
 | ||||
| +obj-$(CONFIG_BOOTSPLASH)       += bootsplash.o bootsplash_render.o \
 | ||||
| +                                  dummyblit.o
 | ||||
| diff --git a/drivers/video/fbdev/core/bootsplash.c b/drivers/video/fbdev/core/bootsplash.c
 | ||||
| new file mode 100644 | ||||
| index 000000000000..e449755af268
 | ||||
| --- /dev/null
 | ||||
| +++ b/drivers/video/fbdev/core/bootsplash.c
 | ||||
| @@ -0,0 +1,294 @@
 | ||||
| +/*
 | ||||
| + * Kernel based bootsplash.
 | ||||
| + *
 | ||||
| + * (Main file: Glue code, workers, timer, PM, kernel and userland API)
 | ||||
| + *
 | ||||
| + * Authors:
 | ||||
| + * Max Staudt <mstaudt@suse.de>
 | ||||
| + *
 | ||||
| + * SPDX-License-Identifier: GPL-2.0
 | ||||
| + */
 | ||||
| +
 | ||||
| +#define pr_fmt(fmt) "bootsplash: " fmt
 | ||||
| +
 | ||||
| +
 | ||||
| +#include <linux/atomic.h>
 | ||||
| +#include <linux/bootsplash.h>
 | ||||
| +#include <linux/console.h>
 | ||||
| +#include <linux/device.h> /* dev_warn() */
 | ||||
| +#include <linux/fb.h>
 | ||||
| +#include <linux/fs.h>
 | ||||
| +#include <linux/kernel.h>
 | ||||
| +#include <linux/jiffies.h>
 | ||||
| +#include <linux/module.h>
 | ||||
| +#include <linux/mutex.h>
 | ||||
| +#include <linux/platform_device.h>
 | ||||
| +#include <linux/printk.h>
 | ||||
| +#include <linux/selection.h> /* console_blanked */
 | ||||
| +#include <linux/stringify.h>
 | ||||
| +#include <linux/types.h>
 | ||||
| +#include <linux/vmalloc.h>
 | ||||
| +#include <linux/vt_kern.h>
 | ||||
| +#include <linux/workqueue.h>
 | ||||
| +
 | ||||
| +#include "bootsplash_internal.h"
 | ||||
| +
 | ||||
| +
 | ||||
| +/*
 | ||||
| + * We only have one splash screen, so let's keep a single
 | ||||
| + * instance of the internal state.
 | ||||
| + */
 | ||||
| +static struct splash_priv splash_state;
 | ||||
| +
 | ||||
| +
 | ||||
| +static void splash_callback_redraw_vc(struct work_struct *ignored)
 | ||||
| +{
 | ||||
| +	if (console_blanked)
 | ||||
| +		return;
 | ||||
| +
 | ||||
| +	console_lock();
 | ||||
| +	if (vc_cons[fg_console].d)
 | ||||
| +		update_screen(vc_cons[fg_console].d);
 | ||||
| +	console_unlock();
 | ||||
| +}
 | ||||
| +
 | ||||
| +
 | ||||
| +static bool is_fb_compatible(const struct fb_info *info)
 | ||||
| +{
 | ||||
| +	if (!(info->flags & FBINFO_BE_MATH)
 | ||||
| +	    != !fb_be_math((struct fb_info *)info)) {
 | ||||
| +		dev_warn(info->device,
 | ||||
| +			 "Can't draw on foreign endianness framebuffer.\n");
 | ||||
| +
 | ||||
| +		return false;
 | ||||
| +	}
 | ||||
| +
 | ||||
| +	if (info->flags & FBINFO_MISC_TILEBLITTING) {
 | ||||
| +		dev_warn(info->device,
 | ||||
| +			 "Can't draw splash on tiling framebuffer.\n");
 | ||||
| +
 | ||||
| +		return false;
 | ||||
| +	}
 | ||||
| +
 | ||||
| +	if (info->fix.type != FB_TYPE_PACKED_PIXELS
 | ||||
| +	    || (info->fix.visual != FB_VISUAL_TRUECOLOR
 | ||||
| +		&& info->fix.visual != FB_VISUAL_DIRECTCOLOR)) {
 | ||||
| +		dev_warn(info->device,
 | ||||
| +			 "Can't draw splash on non-packed or non-truecolor framebuffer.\n");
 | ||||
| +
 | ||||
| +		dev_warn(info->device,
 | ||||
| +			 "  type: %u   visual: %u\n",
 | ||||
| +			 info->fix.type, info->fix.visual);
 | ||||
| +
 | ||||
| +		return false;
 | ||||
| +	}
 | ||||
| +
 | ||||
| +	if (info->var.bits_per_pixel != 16
 | ||||
| +	    && info->var.bits_per_pixel != 24
 | ||||
| +	    && info->var.bits_per_pixel != 32) {
 | ||||
| +		dev_warn(info->device,
 | ||||
| +			 "We only support drawing on framebuffers with 16, 24, or 32 bpp, not %d.\n",
 | ||||
| +			 info->var.bits_per_pixel);
 | ||||
| +
 | ||||
| +		return false;
 | ||||
| +	}
 | ||||
| +
 | ||||
| +	return true;
 | ||||
| +}
 | ||||
| +
 | ||||
| +
 | ||||
| +/*
 | ||||
| + * Called by fbcon_switch() when an instance is activated or refreshed.
 | ||||
| + */
 | ||||
| +void bootsplash_render_full(struct fb_info *info)
 | ||||
| +{
 | ||||
| +	if (!is_fb_compatible(info))
 | ||||
| +		return;
 | ||||
| +
 | ||||
| +	bootsplash_do_render_background(info);
 | ||||
| +}
 | ||||
| +
 | ||||
| +
 | ||||
| +/*
 | ||||
| + * External status enquiry and on/off switch
 | ||||
| + */
 | ||||
| +bool bootsplash_would_render_now(void)
 | ||||
| +{
 | ||||
| +	return !oops_in_progress
 | ||||
| +		&& !console_blanked
 | ||||
| +		&& bootsplash_is_enabled();
 | ||||
| +}
 | ||||
| +
 | ||||
| +bool bootsplash_is_enabled(void)
 | ||||
| +{
 | ||||
| +	bool was_enabled;
 | ||||
| +
 | ||||
| +	/* Make sure we have the newest state */
 | ||||
| +	smp_rmb();
 | ||||
| +
 | ||||
| +	was_enabled = test_bit(0, &splash_state.enabled);
 | ||||
| +
 | ||||
| +	return was_enabled;
 | ||||
| +}
 | ||||
| +
 | ||||
| +void bootsplash_disable(void)
 | ||||
| +{
 | ||||
| +	int was_enabled;
 | ||||
| +
 | ||||
| +	was_enabled = test_and_clear_bit(0, &splash_state.enabled);
 | ||||
| +
 | ||||
| +	if (was_enabled) {
 | ||||
| +		if (oops_in_progress) {
 | ||||
| +			/* Redraw screen now so we can see a panic */
 | ||||
| +			if (vc_cons[fg_console].d)
 | ||||
| +				update_screen(vc_cons[fg_console].d);
 | ||||
| +		} else {
 | ||||
| +			/* No urgency, redraw at next opportunity */
 | ||||
| +			schedule_work(&splash_state.work_redraw_vc);
 | ||||
| +		}
 | ||||
| +	}
 | ||||
| +}
 | ||||
| +
 | ||||
| +void bootsplash_enable(void)
 | ||||
| +{
 | ||||
| +	bool was_enabled;
 | ||||
| +
 | ||||
| +	if (oops_in_progress)
 | ||||
| +		return;
 | ||||
| +
 | ||||
| +	was_enabled = test_and_set_bit(0, &splash_state.enabled);
 | ||||
| +
 | ||||
| +	if (!was_enabled)
 | ||||
| +		schedule_work(&splash_state.work_redraw_vc);
 | ||||
| +}
 | ||||
| +
 | ||||
| +
 | ||||
| +/*
 | ||||
| + * Userland API via platform device in sysfs
 | ||||
| + */
 | ||||
| +static ssize_t splash_show_enabled(struct device *dev,
 | ||||
| +				   struct device_attribute *attr, char *buf)
 | ||||
| +{
 | ||||
| +	return sprintf(buf, "%d\n", bootsplash_is_enabled());
 | ||||
| +}
 | ||||
| +
 | ||||
| +static ssize_t splash_store_enabled(struct device *device,
 | ||||
| +				    struct device_attribute *attr,
 | ||||
| +				    const char *buf, size_t count)
 | ||||
| +{
 | ||||
| +	bool enable;
 | ||||
| +	int err;
 | ||||
| +
 | ||||
| +	if (!buf || !count)
 | ||||
| +		return -EFAULT;
 | ||||
| +
 | ||||
| +	err = kstrtobool(buf, &enable);
 | ||||
| +	if (err)
 | ||||
| +		return err;
 | ||||
| +
 | ||||
| +	if (enable)
 | ||||
| +		bootsplash_enable();
 | ||||
| +	else
 | ||||
| +		bootsplash_disable();
 | ||||
| +
 | ||||
| +	return count;
 | ||||
| +}
 | ||||
| +
 | ||||
| +static DEVICE_ATTR(enabled, 0644, splash_show_enabled, splash_store_enabled);
 | ||||
| +
 | ||||
| +
 | ||||
| +static struct attribute *splash_dev_attrs[] = {
 | ||||
| +	&dev_attr_enabled.attr,
 | ||||
| +	NULL
 | ||||
| +};
 | ||||
| +
 | ||||
| +ATTRIBUTE_GROUPS(splash_dev);
 | ||||
| +
 | ||||
| +
 | ||||
| +
 | ||||
| +
 | ||||
| +/*
 | ||||
| + * Power management fixup via platform device
 | ||||
| + *
 | ||||
| + * When the system is woken from sleep or restored after hibernating, we
 | ||||
| + * cannot expect the screen contents to still be present in video RAM.
 | ||||
| + * Thus, we have to redraw the splash if we're currently active.
 | ||||
| + */
 | ||||
| +static int splash_resume(struct device *device)
 | ||||
| +{
 | ||||
| +	if (bootsplash_would_render_now())
 | ||||
| +		schedule_work(&splash_state.work_redraw_vc);
 | ||||
| +
 | ||||
| +	return 0;
 | ||||
| +}
 | ||||
| +
 | ||||
| +static int splash_suspend(struct device *device)
 | ||||
| +{
 | ||||
| +	cancel_work_sync(&splash_state.work_redraw_vc);
 | ||||
| +
 | ||||
| +	return 0;
 | ||||
| +}
 | ||||
| +
 | ||||
| +
 | ||||
| +static const struct dev_pm_ops splash_pm_ops = {
 | ||||
| +	.thaw = splash_resume,
 | ||||
| +	.restore = splash_resume,
 | ||||
| +	.resume = splash_resume,
 | ||||
| +	.suspend = splash_suspend,
 | ||||
| +	.freeze = splash_suspend,
 | ||||
| +};
 | ||||
| +
 | ||||
| +static struct platform_driver splash_driver = {
 | ||||
| +	.driver = {
 | ||||
| +		.name = "bootsplash",
 | ||||
| +		.pm = &splash_pm_ops,
 | ||||
| +	},
 | ||||
| +};
 | ||||
| +
 | ||||
| +
 | ||||
| +/*
 | ||||
| + * Main init
 | ||||
| + */
 | ||||
| +void bootsplash_init(void)
 | ||||
| +{
 | ||||
| +	int ret;
 | ||||
| +
 | ||||
| +	/* Initialized already? */
 | ||||
| +	if (splash_state.splash_device)
 | ||||
| +		return;
 | ||||
| +
 | ||||
| +
 | ||||
| +	/* Register platform device to export user API */
 | ||||
| +	ret = platform_driver_register(&splash_driver);
 | ||||
| +	if (ret) {
 | ||||
| +		pr_err("platform_driver_register() failed: %d\n", ret);
 | ||||
| +		goto err;
 | ||||
| +	}
 | ||||
| +
 | ||||
| +	splash_state.splash_device
 | ||||
| +		= platform_device_alloc("bootsplash", 0);
 | ||||
| +
 | ||||
| +	if (!splash_state.splash_device)
 | ||||
| +		goto err_driver;
 | ||||
| +
 | ||||
| +	splash_state.splash_device->dev.groups = splash_dev_groups;
 | ||||
| +
 | ||||
| +	ret = platform_device_add(splash_state.splash_device);
 | ||||
| +	if (ret) {
 | ||||
| +		pr_err("platform_device_add() failed: %d\n", ret);
 | ||||
| +		goto err_device;
 | ||||
| +	}
 | ||||
| +
 | ||||
| +
 | ||||
| +	INIT_WORK(&splash_state.work_redraw_vc, splash_callback_redraw_vc);
 | ||||
| +
 | ||||
| +	return;
 | ||||
| +
 | ||||
| +err_device:
 | ||||
| +	platform_device_put(splash_state.splash_device);
 | ||||
| +	splash_state.splash_device = NULL;
 | ||||
| +err_driver:
 | ||||
| +	platform_driver_unregister(&splash_driver);
 | ||||
| +err:
 | ||||
| +	pr_err("Failed to initialize.\n");
 | ||||
| +}
 | ||||
| diff --git a/drivers/video/fbdev/core/bootsplash_internal.h b/drivers/video/fbdev/core/bootsplash_internal.h
 | ||||
| new file mode 100644 | ||||
| index 000000000000..b11da5cb90bf
 | ||||
| --- /dev/null
 | ||||
| +++ b/drivers/video/fbdev/core/bootsplash_internal.h
 | ||||
| @@ -0,0 +1,55 @@
 | ||||
| +/*
 | ||||
| + * Kernel based bootsplash.
 | ||||
| + *
 | ||||
| + * (Internal data structures used at runtime)
 | ||||
| + *
 | ||||
| + * Authors:
 | ||||
| + * Max Staudt <mstaudt@suse.de>
 | ||||
| + *
 | ||||
| + * SPDX-License-Identifier: GPL-2.0
 | ||||
| + */
 | ||||
| +
 | ||||
| +#ifndef __BOOTSPLASH_INTERNAL_H
 | ||||
| +#define __BOOTSPLASH_INTERNAL_H
 | ||||
| +
 | ||||
| +
 | ||||
| +#include <linux/types.h>
 | ||||
| +#include <linux/fb.h>
 | ||||
| +#include <linux/kernel.h>
 | ||||
| +#include <linux/mutex.h>
 | ||||
| +#include <linux/spinlock.h>
 | ||||
| +
 | ||||
| +
 | ||||
| +/*
 | ||||
| + * Runtime types
 | ||||
| + */
 | ||||
| +struct splash_priv {
 | ||||
| +	/*
 | ||||
| +	 * Enabled/disabled state, to be used with atomic bit operations.
 | ||||
| +	 *   Bit 0: 0 = Splash hidden
 | ||||
| +	 *          1 = Splash shown
 | ||||
| +	 *
 | ||||
| +	 * Note: fbcon.c uses this twice, by calling
 | ||||
| +	 *       bootsplash_would_render_now() in set_blitting_type() and
 | ||||
| +	 *       in fbcon_switch().
 | ||||
| +	 *       This is racy, but eventually consistent: Turning the
 | ||||
| +	 *       splash on/off will cause a redraw, which calls
 | ||||
| +	 *       fbcon_switch(), which calls set_blitting_type().
 | ||||
| +	 *       So the last on/off toggle will make things consistent.
 | ||||
| +	 */
 | ||||
| +	unsigned long enabled;
 | ||||
| +
 | ||||
| +	/* Our gateway to userland via sysfs */
 | ||||
| +	struct platform_device *splash_device;
 | ||||
| +
 | ||||
| +	struct work_struct work_redraw_vc;
 | ||||
| +};
 | ||||
| +
 | ||||
| +
 | ||||
| +
 | ||||
| +/*
 | ||||
| + * Rendering functions
 | ||||
| + */
 | ||||
| +void bootsplash_do_render_background(struct fb_info *info);
 | ||||
| +
 | ||||
| +#endif
 | ||||
| diff --git a/drivers/video/fbdev/core/bootsplash_render.c b/drivers/video/fbdev/core/bootsplash_render.c
 | ||||
| new file mode 100644 | ||||
| index 000000000000..4d7e0117f653
 | ||||
| --- /dev/null
 | ||||
| +++ b/drivers/video/fbdev/core/bootsplash_render.c
 | ||||
| @@ -0,0 +1,93 @@
 | ||||
| +/*
 | ||||
| + * Kernel based bootsplash.
 | ||||
| + *
 | ||||
| + * (Rendering functions)
 | ||||
| + *
 | ||||
| + * Authors:
 | ||||
| + * Max Staudt <mstaudt@suse.de>
 | ||||
| + *
 | ||||
| + * SPDX-License-Identifier: GPL-2.0
 | ||||
| + */
 | ||||
| +
 | ||||
| +#define pr_fmt(fmt) "bootsplash: " fmt
 | ||||
| +
 | ||||
| +
 | ||||
| +#include <linux/bootsplash.h>
 | ||||
| +#include <linux/fb.h>
 | ||||
| +#include <linux/kernel.h>
 | ||||
| +#include <linux/printk.h>
 | ||||
| +#include <linux/types.h>
 | ||||
| +
 | ||||
| +#include "bootsplash_internal.h"
 | ||||
| +
 | ||||
| +
 | ||||
| +
 | ||||
| +
 | ||||
| +/*
 | ||||
| + * Rendering: Internal drawing routines
 | ||||
| + */
 | ||||
| +
 | ||||
| +
 | ||||
| +/*
 | ||||
| + * Pack pixel into target format and do Big/Little Endian handling.
 | ||||
| + * This would be a good place to handle endianness conversion if necessary.
 | ||||
| + */
 | ||||
| +static inline u32 pack_pixel(const struct fb_var_screeninfo *dst_var,
 | ||||
| +			     u8 red, u8 green, u8 blue)
 | ||||
| +{
 | ||||
| +	u32 dstpix;
 | ||||
| +
 | ||||
| +	/* Quantize pixel */
 | ||||
| +	red = red >> (8 - dst_var->red.length);
 | ||||
| +	green = green >> (8 - dst_var->green.length);
 | ||||
| +	blue = blue >> (8 - dst_var->blue.length);
 | ||||
| +
 | ||||
| +	/* Pack pixel */
 | ||||
| +	dstpix = red << (dst_var->red.offset)
 | ||||
| +		| green << (dst_var->green.offset)
 | ||||
| +		| blue << (dst_var->blue.offset);
 | ||||
| +
 | ||||
| +	/*
 | ||||
| +	 * Move packed pixel to the beginning of the memory cell,
 | ||||
| +	 * so we can memcpy() it out easily
 | ||||
| +	 */
 | ||||
| +#ifdef __BIG_ENDIAN
 | ||||
| +	switch (dst_var->bits_per_pixel) {
 | ||||
| +	case 16:
 | ||||
| +		dstpix <<= 16;
 | ||||
| +		break;
 | ||||
| +	case 24:
 | ||||
| +		dstpix <<= 8;
 | ||||
| +		break;
 | ||||
| +	case 32:
 | ||||
| +		break;
 | ||||
| +	}
 | ||||
| +#else
 | ||||
| +	/* This is intrinsically unnecessary on Little Endian */
 | ||||
| +#endif
 | ||||
| +
 | ||||
| +	return dstpix;
 | ||||
| +}
 | ||||
| +
 | ||||
| +
 | ||||
| +void bootsplash_do_render_background(struct fb_info *info)
 | ||||
| +{
 | ||||
| +	unsigned int x, y;
 | ||||
| +	u32 dstpix;
 | ||||
| +	u32 dst_octpp = info->var.bits_per_pixel / 8;
 | ||||
| +
 | ||||
| +	dstpix = pack_pixel(&info->var,
 | ||||
| +			    0,
 | ||||
| +			    0,
 | ||||
| +			    0);
 | ||||
| +
 | ||||
| +	for (y = 0; y < info->var.yres_virtual; y++) {
 | ||||
| +		u8 *dstline = info->screen_buffer + (y * info->fix.line_length);
 | ||||
| +
 | ||||
| +		for (x = 0; x < info->var.xres_virtual; x++) {
 | ||||
| +			memcpy(dstline, &dstpix, dst_octpp);
 | ||||
| +
 | ||||
| +			dstline += dst_octpp;
 | ||||
| +		}
 | ||||
| +	}
 | ||||
| +}
 | ||||
| diff --git a/drivers/video/fbdev/core/dummyblit.c b/drivers/video/fbdev/core/dummyblit.c
 | ||||
| new file mode 100644 | ||||
| index 000000000000..8c22ff92ce24
 | ||||
| --- /dev/null
 | ||||
| +++ b/drivers/video/fbdev/core/dummyblit.c
 | ||||
| @@ -0,0 +1,89 @@
 | ||||
| +/*
 | ||||
| + *  linux/drivers/video/fbdev/core/dummyblit.c -- Dummy Blitting Operation
 | ||||
| + *
 | ||||
| + *  Authors:
 | ||||
| + *  Max Staudt <mstaudt@suse.de>
 | ||||
| + *
 | ||||
| + *  These functions are used in place of blitblit/tileblit to suppress
 | ||||
| + *  fbcon's text output while a splash is shown.
 | ||||
| + *
 | ||||
| + *  Only suppressing actual rendering keeps the text buffer in the VC layer
 | ||||
| + *  intact and makes it easy to switch back from the bootsplash to a full
 | ||||
| + *  text console with a simple redraw (with the original functions in place).
 | ||||
| + *
 | ||||
| + *  Based on linux/drivers/video/fbdev/core/bitblit.c
 | ||||
| + *       and linux/drivers/video/fbdev/core/tileblit.c
 | ||||
| + *
 | ||||
| + * SPDX-License-Identifier: GPL-2.0
 | ||||
| + */
 | ||||
| +
 | ||||
| +#include <linux/module.h>
 | ||||
| +#include <linux/fb.h>
 | ||||
| +#include <linux/vt_kern.h>
 | ||||
| +#include <linux/console.h>
 | ||||
| +#include <asm/types.h>
 | ||||
| +#include "fbcon.h"
 | ||||
| +
 | ||||
| +static void dummy_bmove(struct vc_data *vc, struct fb_info *info, int sy,
 | ||||
| +			int sx, int dy, int dx, int height, int width)
 | ||||
| +{
 | ||||
| +	;
 | ||||
| +}
 | ||||
| +
 | ||||
| +static void dummy_clear(struct vc_data *vc, struct fb_info *info, int sy,
 | ||||
| +			int sx, int height, int width)
 | ||||
| +{
 | ||||
| +	;
 | ||||
| +}
 | ||||
| +
 | ||||
| +static void dummy_putcs(struct vc_data *vc, struct fb_info *info,
 | ||||
| +			const unsigned short *s, int count, int yy, int xx,
 | ||||
| +			int fg, int bg)
 | ||||
| +{
 | ||||
| +	;
 | ||||
| +}
 | ||||
| +
 | ||||
| +static void dummy_clear_margins(struct vc_data *vc, struct fb_info *info,
 | ||||
| +				int color, int bottom_only)
 | ||||
| +{
 | ||||
| +	;
 | ||||
| +}
 | ||||
| +
 | ||||
| +static void dummy_cursor(struct vc_data *vc, struct fb_info *info, int mode,
 | ||||
| +			int softback_lines, int fg, int bg)
 | ||||
| +{
 | ||||
| +	;
 | ||||
| +}
 | ||||
| +
 | ||||
| +static int dummy_update_start(struct fb_info *info)
 | ||||
| +{
 | ||||
| +	/*
 | ||||
| +	 * Copied from bitblit.c and tileblit.c
 | ||||
| +	 *
 | ||||
| +	 * As of Linux 4.12, nobody seems to care about our return value.
 | ||||
| +	 */
 | ||||
| +	struct fbcon_ops *ops = info->fbcon_par;
 | ||||
| +	int err;
 | ||||
| +
 | ||||
| +	err = fb_pan_display(info, &ops->var);
 | ||||
| +	ops->var.xoffset = info->var.xoffset;
 | ||||
| +	ops->var.yoffset = info->var.yoffset;
 | ||||
| +	ops->var.vmode = info->var.vmode;
 | ||||
| +	return err;
 | ||||
| +}
 | ||||
| +
 | ||||
| +void fbcon_set_dummyops(struct fbcon_ops *ops)
 | ||||
| +{
 | ||||
| +	ops->bmove = dummy_bmove;
 | ||||
| +	ops->clear = dummy_clear;
 | ||||
| +	ops->putcs = dummy_putcs;
 | ||||
| +	ops->clear_margins = dummy_clear_margins;
 | ||||
| +	ops->cursor = dummy_cursor;
 | ||||
| +	ops->update_start = dummy_update_start;
 | ||||
| +	ops->rotate_font = NULL;
 | ||||
| +}
 | ||||
| +EXPORT_SYMBOL_GPL(fbcon_set_dummyops);
 | ||||
| +
 | ||||
| +MODULE_AUTHOR("Max Staudt <mstaudt@suse.de>");
 | ||||
| +MODULE_DESCRIPTION("Dummy Blitting Operation");
 | ||||
| +MODULE_LICENSE("GPL");
 | ||||
| diff --git a/drivers/video/fbdev/core/fbcon.c b/drivers/video/fbdev/core/fbcon.c
 | ||||
| index 04612f938bab..9a39a6fcfe98 100644
 | ||||
| --- a/drivers/video/fbdev/core/fbcon.c
 | ||||
| +++ b/drivers/video/fbdev/core/fbcon.c
 | ||||
| @@ -80,6 +80,7 @@
 | ||||
|  #include <asm/irq.h> | ||||
|   | ||||
|  #include "fbcon.h" | ||||
| +#include <linux/bootsplash.h>
 | ||||
|   | ||||
|  #ifdef FBCONDEBUG | ||||
|  #  define DPRINTK(fmt, args...) printk(KERN_DEBUG "%s: " fmt, __func__ , ## args) | ||||
| @@ -542,6 +543,8 @@ static int do_fbcon_takeover(int show_logo)
 | ||||
|  	for (i = first_fb_vc; i <= last_fb_vc; i++) | ||||
|  		con2fb_map[i] = info_idx; | ||||
|   | ||||
| +	bootsplash_init();
 | ||||
| +
 | ||||
|  	err = do_take_over_console(&fb_con, first_fb_vc, last_fb_vc, | ||||
|  				fbcon_is_default); | ||||
|   | ||||
| @@ -661,6 +664,9 @@ static void set_blitting_type(struct vc_data *vc, struct fb_info *info)
 | ||||
|  	else { | ||||
|  		fbcon_set_rotation(info); | ||||
|  		fbcon_set_bitops(ops); | ||||
| +
 | ||||
| +		if (bootsplash_would_render_now())
 | ||||
| +			fbcon_set_dummyops(ops);
 | ||||
|  	} | ||||
|  } | ||||
|   | ||||
| @@ -683,6 +689,19 @@ static void set_blitting_type(struct vc_data *vc, struct fb_info *info)
 | ||||
|  	ops->p = &fb_display[vc->vc_num]; | ||||
|  	fbcon_set_rotation(info); | ||||
|  	fbcon_set_bitops(ops); | ||||
| +
 | ||||
| +	/*
 | ||||
| +	 * Note:
 | ||||
| +	 * This is *eventually correct*.
 | ||||
| +	 * Setting the fbcon operations and drawing the splash happen at
 | ||||
| +	 * different points in time. If the splash is enabled/disabled
 | ||||
| +	 * in between, then bootsplash_{en,dis}able will schedule a
 | ||||
| +	 * redraw, which will again render the splash (or not) and set
 | ||||
| +	 * the correct fbcon ops.
 | ||||
| +	 * The last run will then be the right one.
 | ||||
| +	 */
 | ||||
| +	if (bootsplash_would_render_now())
 | ||||
| +		fbcon_set_dummyops(ops);
 | ||||
|  } | ||||
|   | ||||
|  static int fbcon_invalid_charcount(struct fb_info *info, unsigned charcount) | ||||
| @@ -2184,6 +2203,9 @@ static int fbcon_switch(struct vc_data *vc)
 | ||||
|  	info = registered_fb[con2fb_map[vc->vc_num]]; | ||||
|  	ops = info->fbcon_par; | ||||
|   | ||||
| +	if (bootsplash_would_render_now())
 | ||||
| +		bootsplash_render_full(info);
 | ||||
| +
 | ||||
|  	if (softback_top) { | ||||
|  		if (softback_lines) | ||||
|  			fbcon_set_origin(vc); | ||||
| diff --git a/drivers/video/fbdev/core/fbcon.h b/drivers/video/fbdev/core/fbcon.h
 | ||||
| index 18f3ac144237..45f94347fe5e 100644
 | ||||
| --- a/drivers/video/fbdev/core/fbcon.h
 | ||||
| +++ b/drivers/video/fbdev/core/fbcon.h
 | ||||
| @@ -214,6 +214,11 @@ static inline int attr_col_ec(int shift, struct vc_data *vc,
 | ||||
|  #define SCROLL_REDRAW	   0x004 | ||||
|  #define SCROLL_PAN_REDRAW  0x005 | ||||
|   | ||||
| +#ifdef CONFIG_BOOTSPLASH
 | ||||
| +extern void fbcon_set_dummyops(struct fbcon_ops *ops);
 | ||||
| +#else /* CONFIG_BOOTSPLASH */
 | ||||
| +#define fbcon_set_dummyops(x)
 | ||||
| +#endif /* CONFIG_BOOTSPLASH */
 | ||||
|  #ifdef CONFIG_FB_TILEBLITTING | ||||
|  extern void fbcon_set_tileops(struct vc_data *vc, struct fb_info *info); | ||||
|  #endif | ||||
| diff --git a/include/linux/bootsplash.h b/include/linux/bootsplash.h
 | ||||
| new file mode 100644 | ||||
| index 000000000000..c6dd0b43180d
 | ||||
| --- /dev/null
 | ||||
| +++ b/include/linux/bootsplash.h
 | ||||
| @@ -0,0 +1,43 @@
 | ||||
| +/*
 | ||||
| + * Kernel based bootsplash.
 | ||||
| + *
 | ||||
| + * Authors:
 | ||||
| + * Max Staudt <mstaudt@suse.de>
 | ||||
| + *
 | ||||
| + * SPDX-License-Identifier: GPL-2.0
 | ||||
| + */
 | ||||
| +
 | ||||
| +#ifndef __LINUX_BOOTSPLASH_H
 | ||||
| +#define __LINUX_BOOTSPLASH_H
 | ||||
| +
 | ||||
| +#include <linux/fb.h>
 | ||||
| +
 | ||||
| +
 | ||||
| +#ifdef CONFIG_BOOTSPLASH
 | ||||
| +
 | ||||
| +extern void bootsplash_render_full(struct fb_info *info);
 | ||||
| +
 | ||||
| +extern bool bootsplash_would_render_now(void);
 | ||||
| +
 | ||||
| +extern bool bootsplash_is_enabled(void);
 | ||||
| +extern void bootsplash_disable(void);
 | ||||
| +extern void bootsplash_enable(void);
 | ||||
| +
 | ||||
| +extern void bootsplash_init(void);
 | ||||
| +
 | ||||
| +#else /* CONFIG_BOOTSPLASH */
 | ||||
| +
 | ||||
| +#define bootsplash_render_full(x)
 | ||||
| +
 | ||||
| +#define bootsplash_would_render_now() (false)
 | ||||
| +
 | ||||
| +#define bootsplash_is_enabled() (false)
 | ||||
| +#define bootsplash_disable()
 | ||||
| +#define bootsplash_enable()
 | ||||
| +
 | ||||
| +#define bootsplash_init()
 | ||||
| +
 | ||||
| +#endif /* CONFIG_BOOTSPLASH */
 | ||||
| +
 | ||||
| +
 | ||||
| +#endif
 | ||||
| @ -1,669 +0,0 @@ | ||||
| diff --git a/MAINTAINERS b/MAINTAINERS
 | ||||
| index b5633b56391e..5c237445761e 100644
 | ||||
| --- a/MAINTAINERS
 | ||||
| +++ b/MAINTAINERS
 | ||||
| @@ -3622,6 +3622,7 @@
 | ||||
|  F:     drivers/video/fbdev/core/bootsplash*.* | ||||
|  F:     drivers/video/fbdev/core/dummycon.c | ||||
|  F:     include/linux/bootsplash.h | ||||
| +F:     include/uapi/linux/bootsplash_file.h
 | ||||
|   | ||||
|  BPF [GENERAL] (Safe Dynamic Programs and Tools) | ||||
|  M:     Alexei Starovoitov <ast@kernel.org> | ||||
| diff --git a/drivers/video/fbdev/core/Makefile b/drivers/video/fbdev/core/Makefile
 | ||||
| index 66895321928e..6a8d1bab8a01 100644
 | ||||
| --- a/drivers/video/fbdev/core/Makefile
 | ||||
| +++ b/drivers/video/fbdev/core/Makefile
 | ||||
| @@ -31,4 +31,4 @@ obj-$(CONFIG_FB_SVGALIB)       += svgalib.o
 | ||||
|  obj-$(CONFIG_FB_DDC)           += fb_ddc.o | ||||
|   | ||||
|  obj-$(CONFIG_BOOTSPLASH)       += bootsplash.o bootsplash_render.o \ | ||||
| -                                  dummyblit.o
 | ||||
| +                                  bootsplash_load.o dummyblit.o
 | ||||
| diff --git a/drivers/video/fbdev/core/bootsplash.c b/drivers/video/fbdev/core/bootsplash.c
 | ||||
| index e449755af268..843c5400fefc 100644
 | ||||
| --- a/drivers/video/fbdev/core/bootsplash.c
 | ||||
| +++ b/drivers/video/fbdev/core/bootsplash.c
 | ||||
| @@ -32,6 +32,7 @@
 | ||||
|  #include <linux/workqueue.h> | ||||
|   | ||||
|  #include "bootsplash_internal.h" | ||||
| +#include "uapi/linux/bootsplash_file.h"
 | ||||
|   | ||||
|   | ||||
|  /* | ||||
| @@ -102,10 +103,17 @@ static bool is_fb_compatible(const struct fb_info *info)
 | ||||
|   */ | ||||
|  void bootsplash_render_full(struct fb_info *info) | ||||
|  { | ||||
| +	mutex_lock(&splash_state.data_lock);
 | ||||
| +
 | ||||
|  	if (!is_fb_compatible(info)) | ||||
| -		return;
 | ||||
| +		goto out;
 | ||||
| +
 | ||||
| +	bootsplash_do_render_background(info, splash_state.file);
 | ||||
| +
 | ||||
| +	bootsplash_do_render_pictures(info, splash_state.file);
 | ||||
|   | ||||
| -	bootsplash_do_render_background(info);
 | ||||
| +out:
 | ||||
| +	mutex_unlock(&splash_state.data_lock);
 | ||||
|  } | ||||
|   | ||||
|   | ||||
| @@ -116,6 +124,7 @@ bool bootsplash_would_render_now(void)
 | ||||
|  { | ||||
|  	return !oops_in_progress | ||||
|  		&& !console_blanked | ||||
| +		&& splash_state.file
 | ||||
|  		&& bootsplash_is_enabled(); | ||||
|  } | ||||
|   | ||||
| @@ -252,6 +261,7 @@ static struct platform_driver splash_driver = {
 | ||||
|  void bootsplash_init(void) | ||||
|  { | ||||
|  	int ret; | ||||
| +	struct splash_file_priv *fp;
 | ||||
|   | ||||
|  	/* Initialized already? */ | ||||
|  	if (splash_state.splash_device) | ||||
| @@ -280,8 +290,26 @@ void bootsplash_init(void)
 | ||||
|  	} | ||||
|   | ||||
|   | ||||
| +	mutex_init(&splash_state.data_lock);
 | ||||
| +	set_bit(0, &splash_state.enabled);
 | ||||
| +
 | ||||
|  	INIT_WORK(&splash_state.work_redraw_vc, splash_callback_redraw_vc); | ||||
|   | ||||
| +
 | ||||
| +	if (!splash_state.bootfile || !strlen(splash_state.bootfile))
 | ||||
| +		return;
 | ||||
| +
 | ||||
| +	fp = bootsplash_load_firmware(&splash_state.splash_device->dev,
 | ||||
| +				      splash_state.bootfile);
 | ||||
| +
 | ||||
| +	if (!fp)
 | ||||
| +		goto err;
 | ||||
| +
 | ||||
| +	mutex_lock(&splash_state.data_lock);
 | ||||
| +	splash_state.splash_fb = NULL;
 | ||||
| +	splash_state.file = fp;
 | ||||
| +	mutex_unlock(&splash_state.data_lock);
 | ||||
| +
 | ||||
|  	return; | ||||
|   | ||||
|  err_device: | ||||
| @@ -292,3 +320,7 @@ void bootsplash_init(void)
 | ||||
|  err: | ||||
|  	pr_err("Failed to initialize.\n"); | ||||
|  } | ||||
| +
 | ||||
| +
 | ||||
| +module_param_named(bootfile, splash_state.bootfile, charp, 0444);
 | ||||
| +MODULE_PARM_DESC(bootfile, "Bootsplash file to load on boot");
 | ||||
| diff --git a/drivers/video/fbdev/core/bootsplash_internal.h b/drivers/video/fbdev/core/bootsplash_internal.h
 | ||||
| index b11da5cb90bf..71e2a27ac0b8 100644
 | ||||
| --- a/drivers/video/fbdev/core/bootsplash_internal.h
 | ||||
| +++ b/drivers/video/fbdev/core/bootsplash_internal.h
 | ||||
| @@ -15,15 +15,43 @@
 | ||||
|   | ||||
|  #include <linux/types.h> | ||||
|  #include <linux/fb.h> | ||||
| +#include <linux/firmware.h>
 | ||||
|  #include <linux/kernel.h> | ||||
|  #include <linux/mutex.h> | ||||
|  #include <linux/spinlock.h> | ||||
|   | ||||
| +#include "uapi/linux/bootsplash_file.h"
 | ||||
| +
 | ||||
|   | ||||
|  /* | ||||
|   * Runtime types | ||||
|   */ | ||||
| +struct splash_blob_priv {
 | ||||
| +	struct splash_blob_header *blob_header;
 | ||||
| +	const void *data;
 | ||||
| +};
 | ||||
| +
 | ||||
| +
 | ||||
| +struct splash_pic_priv {
 | ||||
| +	const struct splash_pic_header *pic_header;
 | ||||
| +
 | ||||
| +	struct splash_blob_priv *blobs;
 | ||||
| +	u16 blobs_loaded;
 | ||||
| +};
 | ||||
| +
 | ||||
| +
 | ||||
| +struct splash_file_priv {
 | ||||
| +	const struct firmware *fw;
 | ||||
| +	const struct splash_file_header *header;
 | ||||
| +
 | ||||
| +	struct splash_pic_priv *pics;
 | ||||
| +};
 | ||||
| +
 | ||||
| +
 | ||||
|  struct splash_priv { | ||||
| +	/* Bootup and runtime state */
 | ||||
| +	char *bootfile;
 | ||||
| +
 | ||||
|  	/* | ||||
|  	 * Enabled/disabled state, to be used with atomic bit operations. | ||||
|  	 *   Bit 0: 0 = Splash hidden | ||||
| @@ -43,6 +71,13 @@ struct splash_priv {
 | ||||
|  	struct platform_device *splash_device; | ||||
|   | ||||
|  	struct work_struct work_redraw_vc; | ||||
| +
 | ||||
| +	/* Splash data structures including lock for everything below */
 | ||||
| +	struct mutex data_lock;
 | ||||
| +
 | ||||
| +	struct fb_info *splash_fb;
 | ||||
| +
 | ||||
| +	struct splash_file_priv *file;
 | ||||
|  }; | ||||
|   | ||||
|   | ||||
| @@ -50,6 +85,14 @@ struct splash_priv {
 | ||||
|  /* | ||||
|   * Rendering functions | ||||
|   */ | ||||
| -void bootsplash_do_render_background(struct fb_info *info);
 | ||||
| +void bootsplash_do_render_background(struct fb_info *info,
 | ||||
| +				     const struct splash_file_priv *fp);
 | ||||
| +void bootsplash_do_render_pictures(struct fb_info *info,
 | ||||
| +				   const struct splash_file_priv *fp);
 | ||||
| +
 | ||||
| +
 | ||||
| +void bootsplash_free_file(struct splash_file_priv *fp);
 | ||||
| +struct splash_file_priv *bootsplash_load_firmware(struct device *device,
 | ||||
| +						  const char *path);
 | ||||
|   | ||||
|  #endif | ||||
| diff --git a/drivers/video/fbdev/core/bootsplash_load.c b/drivers/video/fbdev/core/bootsplash_load.c
 | ||||
| new file mode 100644 | ||||
| index 000000000000..fd807571ab7d
 | ||||
| --- /dev/null
 | ||||
| +++ b/drivers/video/fbdev/core/bootsplash_load.c
 | ||||
| @@ -0,0 +1,225 @@
 | ||||
| +/*
 | ||||
| + * Kernel based bootsplash.
 | ||||
| + *
 | ||||
| + * (Loading and freeing functions)
 | ||||
| + *
 | ||||
| + * Authors:
 | ||||
| + * Max Staudt <mstaudt@suse.de>
 | ||||
| + *
 | ||||
| + * SPDX-License-Identifier: GPL-2.0
 | ||||
| + */
 | ||||
| +
 | ||||
| +#define pr_fmt(fmt) "bootsplash: " fmt
 | ||||
| +
 | ||||
| +
 | ||||
| +#include <linux/bootsplash.h>
 | ||||
| +#include <linux/fb.h>
 | ||||
| +#include <linux/firmware.h>
 | ||||
| +#include <linux/kernel.h>
 | ||||
| +#include <linux/mutex.h>
 | ||||
| +#include <linux/printk.h>
 | ||||
| +#include <linux/types.h>
 | ||||
| +#include <linux/vmalloc.h>
 | ||||
| +
 | ||||
| +#include "bootsplash_internal.h"
 | ||||
| +#include "uapi/linux/bootsplash_file.h"
 | ||||
| +
 | ||||
| +
 | ||||
| +
 | ||||
| +
 | ||||
| +/*
 | ||||
| + * Free all vmalloc()'d resources describing a splash file.
 | ||||
| + */
 | ||||
| +void bootsplash_free_file(struct splash_file_priv *fp)
 | ||||
| +{
 | ||||
| +	if (!fp)
 | ||||
| +		return;
 | ||||
| +
 | ||||
| +	if (fp->pics) {
 | ||||
| +		unsigned int i;
 | ||||
| +
 | ||||
| +		for (i = 0; i < fp->header->num_pics; i++) {
 | ||||
| +			struct splash_pic_priv *pp = &fp->pics[i];
 | ||||
| +
 | ||||
| +			if (pp->blobs)
 | ||||
| +				vfree(pp->blobs);
 | ||||
| +		}
 | ||||
| +
 | ||||
| +		vfree(fp->pics);
 | ||||
| +	}
 | ||||
| +
 | ||||
| +	release_firmware(fp->fw);
 | ||||
| +	vfree(fp);
 | ||||
| +}
 | ||||
| +
 | ||||
| +
 | ||||
| +
 | ||||
| +
 | ||||
| +/*
 | ||||
| + * Load a splash screen from a "firmware" file.
 | ||||
| + *
 | ||||
| + * Parsing, and sanity checks.
 | ||||
| + */
 | ||||
| +#ifdef __BIG_ENDIAN
 | ||||
| +	#define BOOTSPLASH_MAGIC BOOTSPLASH_MAGIC_BE
 | ||||
| +#else
 | ||||
| +	#define BOOTSPLASH_MAGIC BOOTSPLASH_MAGIC_LE
 | ||||
| +#endif
 | ||||
| +
 | ||||
| +struct splash_file_priv *bootsplash_load_firmware(struct device *device,
 | ||||
| +						  const char *path)
 | ||||
| +{
 | ||||
| +	const struct firmware *fw;
 | ||||
| +	struct splash_file_priv *fp;
 | ||||
| +	unsigned int i;
 | ||||
| +	const u8 *walker;
 | ||||
| +
 | ||||
| +	if (request_firmware(&fw, path, device))
 | ||||
| +		return NULL;
 | ||||
| +
 | ||||
| +	if (fw->size < sizeof(struct splash_file_header)
 | ||||
| +	    || memcmp(fw->data, BOOTSPLASH_MAGIC, sizeof(fp->header->id))) {
 | ||||
| +		pr_err("Not a bootsplash file.\n");
 | ||||
| +
 | ||||
| +		release_firmware(fw);
 | ||||
| +		return NULL;
 | ||||
| +	}
 | ||||
| +
 | ||||
| +	fp = vzalloc(sizeof(struct splash_file_priv));
 | ||||
| +	if (!fp) {
 | ||||
| +		release_firmware(fw);
 | ||||
| +		return NULL;
 | ||||
| +	}
 | ||||
| +
 | ||||
| +	pr_info("Loading splash file (%li bytes)\n", fw->size);
 | ||||
| +
 | ||||
| +	fp->fw = fw;
 | ||||
| +	fp->header = (struct splash_file_header *)fw->data;
 | ||||
| +
 | ||||
| +	/* Sanity checks */
 | ||||
| +	if (fp->header->version != BOOTSPLASH_VERSION) {
 | ||||
| +		pr_err("Loaded v%d file, but we only support version %d\n",
 | ||||
| +			fp->header->version,
 | ||||
| +			BOOTSPLASH_VERSION);
 | ||||
| +
 | ||||
| +		goto err;
 | ||||
| +	}
 | ||||
| +
 | ||||
| +	if (fw->size < sizeof(struct splash_file_header)
 | ||||
| +		+ fp->header->num_pics
 | ||||
| +			* sizeof(struct splash_pic_header)
 | ||||
| +		+ fp->header->num_blobs
 | ||||
| +			* sizeof(struct splash_blob_header)) {
 | ||||
| +		pr_err("File incomplete.\n");
 | ||||
| +
 | ||||
| +		goto err;
 | ||||
| +	}
 | ||||
| +
 | ||||
| +	/* Read picture headers */
 | ||||
| +	if (fp->header->num_pics) {
 | ||||
| +		fp->pics = vzalloc(fp->header->num_pics
 | ||||
| +				   * sizeof(struct splash_pic_priv));
 | ||||
| +		if (!fp->pics)
 | ||||
| +			goto err;
 | ||||
| +	}
 | ||||
| +
 | ||||
| +	walker = fw->data + sizeof(struct splash_file_header);
 | ||||
| +	for (i = 0; i < fp->header->num_pics; i++) {
 | ||||
| +		struct splash_pic_priv *pp = &fp->pics[i];
 | ||||
| +		struct splash_pic_header *ph = (void *)walker;
 | ||||
| +
 | ||||
| +		pr_debug("Picture %u: Size %ux%u\n", i, ph->width, ph->height);
 | ||||
| +
 | ||||
| +		if (ph->num_blobs < 1) {
 | ||||
| +			pr_err("Picture %u: Zero blobs? Aborting load.\n", i);
 | ||||
| +			goto err;
 | ||||
| +		}
 | ||||
| +
 | ||||
| +		pp->pic_header = ph;
 | ||||
| +		pp->blobs = vzalloc(ph->num_blobs
 | ||||
| +					* sizeof(struct splash_blob_priv));
 | ||||
| +		if (!pp->blobs)
 | ||||
| +			goto err;
 | ||||
| +
 | ||||
| +		walker += sizeof(struct splash_pic_header);
 | ||||
| +	}
 | ||||
| +
 | ||||
| +	/* Read blob headers */
 | ||||
| +	for (i = 0; i < fp->header->num_blobs; i++) {
 | ||||
| +		struct splash_blob_header *bh = (void *)walker;
 | ||||
| +		struct splash_pic_priv *pp;
 | ||||
| +
 | ||||
| +		if (walker + sizeof(struct splash_blob_header)
 | ||||
| +		    > fw->data + fw->size)
 | ||||
| +			goto err;
 | ||||
| +
 | ||||
| +		walker += sizeof(struct splash_blob_header);
 | ||||
| +
 | ||||
| +		if (walker + bh->length > fw->data + fw->size)
 | ||||
| +			goto err;
 | ||||
| +
 | ||||
| +		if (bh->picture_id >= fp->header->num_pics)
 | ||||
| +			goto nextblob;
 | ||||
| +
 | ||||
| +		pp = &fp->pics[bh->picture_id];
 | ||||
| +
 | ||||
| +		pr_debug("Blob %u, pic %u, blobs_loaded %u, num_blobs %u.\n",
 | ||||
| +			 i, bh->picture_id,
 | ||||
| +			 pp->blobs_loaded, pp->pic_header->num_blobs);
 | ||||
| +
 | ||||
| +		if (pp->blobs_loaded >= pp->pic_header->num_blobs)
 | ||||
| +			goto nextblob;
 | ||||
| +
 | ||||
| +		switch (bh->type) {
 | ||||
| +		case 0:
 | ||||
| +			/* Raw 24-bit packed pixels */
 | ||||
| +			if (bh->length != pp->pic_header->width
 | ||||
| +					* pp->pic_header->height * 3) {
 | ||||
| +				pr_err("Blob %u, type 1: Length doesn't match picture.\n",
 | ||||
| +				       i);
 | ||||
| +
 | ||||
| +				goto err;
 | ||||
| +			}
 | ||||
| +			break;
 | ||||
| +		default:
 | ||||
| +			pr_warn("Blob %u, unknown type %u.\n", i, bh->type);
 | ||||
| +			goto nextblob;
 | ||||
| +		}
 | ||||
| +
 | ||||
| +		pp->blobs[pp->blobs_loaded].blob_header = bh;
 | ||||
| +		pp->blobs[pp->blobs_loaded].data = walker;
 | ||||
| +		pp->blobs_loaded++;
 | ||||
| +
 | ||||
| +nextblob:
 | ||||
| +		walker += bh->length;
 | ||||
| +		if (bh->length % 16)
 | ||||
| +			walker += 16 - (bh->length % 16);
 | ||||
| +	}
 | ||||
| +
 | ||||
| +	if (walker != fw->data + fw->size)
 | ||||
| +		pr_warn("Trailing data in splash file.\n");
 | ||||
| +
 | ||||
| +	/* Walk over pictures and ensure all blob slots are filled */
 | ||||
| +	for (i = 0; i < fp->header->num_pics; i++) {
 | ||||
| +		struct splash_pic_priv *pp = &fp->pics[i];
 | ||||
| +
 | ||||
| +		if (pp->blobs_loaded != pp->pic_header->num_blobs) {
 | ||||
| +			pr_err("Picture %u doesn't have all blob slots filled.\n",
 | ||||
| +			       i);
 | ||||
| +
 | ||||
| +			goto err;
 | ||||
| +		}
 | ||||
| +	}
 | ||||
| +
 | ||||
| +	pr_info("Loaded (%ld bytes, %u pics, %u blobs).\n",
 | ||||
| +		fw->size,
 | ||||
| +		fp->header->num_pics,
 | ||||
| +		fp->header->num_blobs);
 | ||||
| +
 | ||||
| +	return fp;
 | ||||
| +
 | ||||
| +
 | ||||
| +err:
 | ||||
| +	bootsplash_free_file(fp);
 | ||||
| +	return NULL;
 | ||||
| +}
 | ||||
| diff --git a/drivers/video/fbdev/core/bootsplash_render.c b/drivers/video/fbdev/core/bootsplash_render.c
 | ||||
| index 4d7e0117f653..2ae36949d0e3 100644
 | ||||
| --- a/drivers/video/fbdev/core/bootsplash_render.c
 | ||||
| +++ b/drivers/video/fbdev/core/bootsplash_render.c
 | ||||
| @@ -19,6 +19,7 @@
 | ||||
|  #include <linux/types.h> | ||||
|   | ||||
|  #include "bootsplash_internal.h" | ||||
| +#include "uapi/linux/bootsplash_file.h"
 | ||||
|   | ||||
|   | ||||
|   | ||||
| @@ -70,16 +71,69 @@ static inline u32 pack_pixel(const struct fb_var_screeninfo *dst_var,
 | ||||
|  } | ||||
|   | ||||
|   | ||||
| -void bootsplash_do_render_background(struct fb_info *info)
 | ||||
| +/*
 | ||||
| + * Copy from source and blend into the destination picture.
 | ||||
| + * Currently assumes that the source picture is 24bpp.
 | ||||
| + * Currently assumes that the destination is <= 32bpp.
 | ||||
| + */
 | ||||
| +static int splash_convert_to_fb(u8 *dst,
 | ||||
| +				const struct fb_var_screeninfo *dst_var,
 | ||||
| +				unsigned int dst_stride,
 | ||||
| +				unsigned int dst_xoff,
 | ||||
| +				unsigned int dst_yoff,
 | ||||
| +				const u8 *src,
 | ||||
| +				unsigned int src_width,
 | ||||
| +				unsigned int src_height)
 | ||||
| +{
 | ||||
| +	unsigned int x, y;
 | ||||
| +	unsigned int src_stride = 3 * src_width; /* Assume 24bpp packed */
 | ||||
| +	u32 dst_octpp = dst_var->bits_per_pixel / 8;
 | ||||
| +
 | ||||
| +	dst_xoff += dst_var->xoffset;
 | ||||
| +	dst_yoff += dst_var->yoffset;
 | ||||
| +
 | ||||
| +	/* Copy with stride and pixel size adjustment */
 | ||||
| +	for (y = 0;
 | ||||
| +	     y < src_height && y + dst_yoff < dst_var->yres_virtual;
 | ||||
| +	     y++) {
 | ||||
| +		const u8 *srcline = src + (y * src_stride);
 | ||||
| +		u8 *dstline = dst + ((y + dst_yoff) * dst_stride)
 | ||||
| +				  + (dst_xoff * dst_octpp);
 | ||||
| +
 | ||||
| +		for (x = 0;
 | ||||
| +		     x < src_width && x + dst_xoff < dst_var->xres_virtual;
 | ||||
| +		     x++) {
 | ||||
| +			u8 red, green, blue;
 | ||||
| +			u32 dstpix;
 | ||||
| +
 | ||||
| +			/* Read pixel */
 | ||||
| +			red = *srcline++;
 | ||||
| +			green = *srcline++;
 | ||||
| +			blue = *srcline++;
 | ||||
| +
 | ||||
| +			/* Write pixel */
 | ||||
| +			dstpix = pack_pixel(dst_var, red, green, blue);
 | ||||
| +			memcpy(dstline, &dstpix, dst_octpp);
 | ||||
| +
 | ||||
| +			dstline += dst_octpp;
 | ||||
| +		}
 | ||||
| +	}
 | ||||
| +
 | ||||
| +	return 0;
 | ||||
| +}
 | ||||
| +
 | ||||
| +
 | ||||
| +void bootsplash_do_render_background(struct fb_info *info,
 | ||||
| +				     const struct splash_file_priv *fp)
 | ||||
|  { | ||||
|  	unsigned int x, y; | ||||
|  	u32 dstpix; | ||||
|  	u32 dst_octpp = info->var.bits_per_pixel / 8; | ||||
|   | ||||
|  	dstpix = pack_pixel(&info->var, | ||||
| -			    0,
 | ||||
| -			    0,
 | ||||
| -			    0);
 | ||||
| +			    fp->header->bg_red,
 | ||||
| +			    fp->header->bg_green,
 | ||||
| +			    fp->header->bg_blue);
 | ||||
|   | ||||
|  	for (y = 0; y < info->var.yres_virtual; y++) { | ||||
|  		u8 *dstline = info->screen_buffer + (y * info->fix.line_length); | ||||
| @@ -91,3 +145,44 @@ void bootsplash_do_render_background(struct fb_info *info)
 | ||||
|  		} | ||||
|  	} | ||||
|  } | ||||
| +
 | ||||
| +
 | ||||
| +void bootsplash_do_render_pictures(struct fb_info *info,
 | ||||
| +				   const struct splash_file_priv *fp)
 | ||||
| +{
 | ||||
| +	unsigned int i;
 | ||||
| +
 | ||||
| +	for (i = 0; i < fp->header->num_pics; i++) {
 | ||||
| +		struct splash_blob_priv *bp;
 | ||||
| +		struct splash_pic_priv *pp = &fp->pics[i];
 | ||||
| +		long dst_xoff, dst_yoff;
 | ||||
| +
 | ||||
| +		if (pp->blobs_loaded < 1)
 | ||||
| +			continue;
 | ||||
| +
 | ||||
| +		bp = &pp->blobs[0];
 | ||||
| +
 | ||||
| +		if (!bp || bp->blob_header->type != 0)
 | ||||
| +			continue;
 | ||||
| +
 | ||||
| +		dst_xoff = (info->var.xres - pp->pic_header->width) / 2;
 | ||||
| +		dst_yoff = (info->var.yres - pp->pic_header->height) / 2;
 | ||||
| +
 | ||||
| +		if (dst_xoff < 0
 | ||||
| +		    || dst_yoff < 0
 | ||||
| +		    || dst_xoff + pp->pic_header->width > info->var.xres
 | ||||
| +		    || dst_yoff + pp->pic_header->height > info->var.yres) {
 | ||||
| +			pr_info_once("Picture %u is out of bounds at current resolution: %dx%d\n"
 | ||||
| +				     "(this will only be printed once every reboot)\n",
 | ||||
| +				     i, info->var.xres, info->var.yres);
 | ||||
| +
 | ||||
| +			continue;
 | ||||
| +		}
 | ||||
| +
 | ||||
| +		/* Draw next splash frame */
 | ||||
| +		splash_convert_to_fb(info->screen_buffer, &info->var,
 | ||||
| +				info->fix.line_length, dst_xoff, dst_yoff,
 | ||||
| +				bp->data,
 | ||||
| +				pp->pic_header->width, pp->pic_header->height);
 | ||||
| +	}
 | ||||
| +}
 | ||||
| diff --git a/include/uapi/linux/bootsplash_file.h b/include/uapi/linux/bootsplash_file.h
 | ||||
| new file mode 100644 | ||||
| index 000000000000..89dc9cca8f0c
 | ||||
| --- /dev/null
 | ||||
| +++ b/include/uapi/linux/bootsplash_file.h
 | ||||
| @@ -0,0 +1,118 @@
 | ||||
| +/*
 | ||||
| + * Kernel based bootsplash.
 | ||||
| + *
 | ||||
| + * (File format)
 | ||||
| + *
 | ||||
| + * Authors:
 | ||||
| + * Max Staudt <mstaudt@suse.de>
 | ||||
| + *
 | ||||
| + * SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note
 | ||||
| + */
 | ||||
| +
 | ||||
| +#ifndef __BOOTSPLASH_FILE_H
 | ||||
| +#define __BOOTSPLASH_FILE_H
 | ||||
| +
 | ||||
| +
 | ||||
| +#define BOOTSPLASH_VERSION 55561
 | ||||
| +
 | ||||
| +
 | ||||
| +#include <linux/kernel.h>
 | ||||
| +#include <linux/types.h>
 | ||||
| +
 | ||||
| +
 | ||||
| +/*
 | ||||
| + * On-disk types
 | ||||
| + *
 | ||||
| + * A splash file consists of:
 | ||||
| + *  - One single 'struct splash_file_header'
 | ||||
| + *  - An array of 'struct splash_pic_header'
 | ||||
| + *  - An array of raw data blocks, each padded to 16 bytes and
 | ||||
| + *    preceded by a 'struct splash_blob_header'
 | ||||
| + *
 | ||||
| + * A single-frame splash may look like this:
 | ||||
| + *
 | ||||
| + * +--------------------+
 | ||||
| + * |                    |
 | ||||
| + * | splash_file_header |
 | ||||
| + * |  -> num_blobs = 1  |
 | ||||
| + * |  -> num_pics = 1   |
 | ||||
| + * |                    |
 | ||||
| + * +--------------------+
 | ||||
| + * |                    |
 | ||||
| + * | splash_pic_header  |
 | ||||
| + * |                    |
 | ||||
| + * +--------------------+
 | ||||
| + * |                    |
 | ||||
| + * | splash_blob_header |
 | ||||
| + * |  -> type = 0       |
 | ||||
| + * |  -> picture_id = 0 |
 | ||||
| + * |                    |
 | ||||
| + * | (raw RGB data)     |
 | ||||
| + * | (pad to 16 bytes)  |
 | ||||
| + * |                    |
 | ||||
| + * +--------------------+
 | ||||
| + *
 | ||||
| + * All multi-byte values are stored on disk in the native format
 | ||||
| + * expected by the system the file will be used on.
 | ||||
| + */
 | ||||
| +#define BOOTSPLASH_MAGIC_BE "Linux bootsplash"
 | ||||
| +#define BOOTSPLASH_MAGIC_LE "hsalpstoob xuniL"
 | ||||
| +
 | ||||
| +struct splash_file_header {
 | ||||
| +	uint8_t  id[16]; /* "Linux bootsplash" (no trailing NUL) */
 | ||||
| +
 | ||||
| +	/* Splash file format version to avoid clashes */
 | ||||
| +	uint16_t version;
 | ||||
| +
 | ||||
| +	/* The background color */
 | ||||
| +	uint8_t bg_red;
 | ||||
| +	uint8_t bg_green;
 | ||||
| +	uint8_t bg_blue;
 | ||||
| +	uint8_t bg_reserved;
 | ||||
| +
 | ||||
| +	/*
 | ||||
| +	 * Number of pic/blobs so we can allocate memory for internal
 | ||||
| +	 * structures ahead of time when reading the file
 | ||||
| +	 */
 | ||||
| +	uint16_t num_blobs;
 | ||||
| +	uint8_t num_pics;
 | ||||
| +
 | ||||
| +	uint8_t padding[103];
 | ||||
| +} __attribute__((__packed__));
 | ||||
| +
 | ||||
| +
 | ||||
| +struct splash_pic_header {
 | ||||
| +	uint16_t width;
 | ||||
| +	uint16_t height;
 | ||||
| +
 | ||||
| +	/*
 | ||||
| +	 * Number of data packages associated with this picture.
 | ||||
| +	 * Currently, the only use for more than 1 is for animations.
 | ||||
| +	 */
 | ||||
| +	uint8_t num_blobs;
 | ||||
| +
 | ||||
| +	uint8_t padding[27];
 | ||||
| +} __attribute__((__packed__));
 | ||||
| +
 | ||||
| +
 | ||||
| +struct splash_blob_header {
 | ||||
| +	/* Length of the data block in bytes. */
 | ||||
| +	uint32_t length;
 | ||||
| +
 | ||||
| +	/*
 | ||||
| +	 * Type of the contents.
 | ||||
| +	 *  0 - Raw RGB data.
 | ||||
| +	 */
 | ||||
| +	uint16_t type;
 | ||||
| +
 | ||||
| +	/*
 | ||||
| +	 * Picture this blob is associated with.
 | ||||
| +	 * Blobs will be added to a picture in the order they are
 | ||||
| +	 * found in the file.
 | ||||
| +	 */
 | ||||
| +	uint8_t picture_id;
 | ||||
| +
 | ||||
| +	uint8_t padding[9];
 | ||||
| +} __attribute__((__packed__));
 | ||||
| +
 | ||||
| +#endif
 | ||||
| @ -1,66 +0,0 @@ | ||||
| diff --git a/drivers/video/fbdev/core/bootsplash.c b/drivers/video/fbdev/core/bootsplash.c
 | ||||
| index 843c5400fefc..815b007f81ca 100644
 | ||||
| --- a/drivers/video/fbdev/core/bootsplash.c
 | ||||
| +++ b/drivers/video/fbdev/core/bootsplash.c
 | ||||
| @@ -112,6 +112,8 @@ void bootsplash_render_full(struct fb_info *info)
 | ||||
|   | ||||
|  	bootsplash_do_render_pictures(info, splash_state.file); | ||||
|   | ||||
| +	bootsplash_do_render_flush(info);
 | ||||
| +
 | ||||
|  out: | ||||
|  	mutex_unlock(&splash_state.data_lock); | ||||
|  } | ||||
| diff --git a/drivers/video/fbdev/core/bootsplash_internal.h b/drivers/video/fbdev/core/bootsplash_internal.h
 | ||||
| index 71e2a27ac0b8..0acb383aa4e3 100644
 | ||||
| --- a/drivers/video/fbdev/core/bootsplash_internal.h
 | ||||
| +++ b/drivers/video/fbdev/core/bootsplash_internal.h
 | ||||
| @@ -89,6 +89,7 @@ void bootsplash_do_render_background(struct fb_info *info,
 | ||||
|  				     const struct splash_file_priv *fp); | ||||
|  void bootsplash_do_render_pictures(struct fb_info *info, | ||||
|  				   const struct splash_file_priv *fp); | ||||
| +void bootsplash_do_render_flush(struct fb_info *info);
 | ||||
|   | ||||
|   | ||||
|  void bootsplash_free_file(struct splash_file_priv *fp); | ||||
| diff --git a/drivers/video/fbdev/core/bootsplash_render.c b/drivers/video/fbdev/core/bootsplash_render.c
 | ||||
| index 2ae36949d0e3..8c09c306ff67 100644
 | ||||
| --- a/drivers/video/fbdev/core/bootsplash_render.c
 | ||||
| +++ b/drivers/video/fbdev/core/bootsplash_render.c
 | ||||
| @@ -186,3 +186,36 @@ void bootsplash_do_render_pictures(struct fb_info *info,
 | ||||
|  				pp->pic_header->width, pp->pic_header->height); | ||||
|  	} | ||||
|  } | ||||
| +
 | ||||
| +
 | ||||
| +void bootsplash_do_render_flush(struct fb_info *info)
 | ||||
| +{
 | ||||
| +	/*
 | ||||
| +	 * FB drivers using deferred_io (such as Xen) need to sync the
 | ||||
| +	 * screen after modifying its contents. When the FB is mmap()ed
 | ||||
| +	 * from userspace, this happens via a dirty pages callback, but
 | ||||
| +	 * when modifying the FB from the kernel, there is no such thing.
 | ||||
| +	 *
 | ||||
| +	 * So let's issue a fake fb_copyarea (copying the FB onto itself)
 | ||||
| +	 * to trick the FB driver into syncing the screen.
 | ||||
| +	 *
 | ||||
| +	 * A few DRM drivers' FB implementations are broken by not using
 | ||||
| +	 * deferred_io when they really should - we match on the known
 | ||||
| +	 * bad ones manually for now.
 | ||||
| +	 */
 | ||||
| +	if (info->fbdefio
 | ||||
| +	    || !strcmp(info->fix.id, "astdrmfb")
 | ||||
| +	    || !strcmp(info->fix.id, "cirrusdrmfb")
 | ||||
| +	    || !strcmp(info->fix.id, "mgadrmfb")) {
 | ||||
| +		struct fb_copyarea area;
 | ||||
| +
 | ||||
| +		area.dx = 0;
 | ||||
| +		area.dy = 0;
 | ||||
| +		area.width = info->var.xres;
 | ||||
| +		area.height = info->var.yres;
 | ||||
| +		area.sx = 0;
 | ||||
| +		area.sy = 0;
 | ||||
| +
 | ||||
| +		info->fbops->fb_copyarea(info, &area);
 | ||||
| +	}
 | ||||
| +}
 | ||||
| @ -1,215 +0,0 @@ | ||||
| diff --git a/drivers/video/fbdev/core/bootsplash_render.c b/drivers/video/fbdev/core/bootsplash_render.c
 | ||||
| index 8c09c306ff67..07e3a4eab811 100644
 | ||||
| --- a/drivers/video/fbdev/core/bootsplash_render.c
 | ||||
| +++ b/drivers/video/fbdev/core/bootsplash_render.c
 | ||||
| @@ -155,6 +155,7 @@ void bootsplash_do_render_pictures(struct fb_info *info,
 | ||||
|  	for (i = 0; i < fp->header->num_pics; i++) { | ||||
|  		struct splash_blob_priv *bp; | ||||
|  		struct splash_pic_priv *pp = &fp->pics[i]; | ||||
| +		const struct splash_pic_header *ph = pp->pic_header;
 | ||||
|  		long dst_xoff, dst_yoff; | ||||
|   | ||||
|  		if (pp->blobs_loaded < 1) | ||||
| @@ -165,8 +166,139 @@ void bootsplash_do_render_pictures(struct fb_info *info,
 | ||||
|  		if (!bp || bp->blob_header->type != 0) | ||||
|  			continue; | ||||
|   | ||||
| -		dst_xoff = (info->var.xres - pp->pic_header->width) / 2;
 | ||||
| -		dst_yoff = (info->var.yres - pp->pic_header->height) / 2;
 | ||||
| +		switch (ph->position) {
 | ||||
| +		case SPLASH_POS_FLAG_CORNER | SPLASH_CORNER_TOP_LEFT:
 | ||||
| +			dst_xoff = 0;
 | ||||
| +			dst_yoff = 0;
 | ||||
| +
 | ||||
| +			dst_xoff += ph->position_offset;
 | ||||
| +			dst_yoff += ph->position_offset;
 | ||||
| +			break;
 | ||||
| +		case SPLASH_POS_FLAG_CORNER | SPLASH_CORNER_TOP:
 | ||||
| +			dst_xoff = info->var.xres - pp->pic_header->width;
 | ||||
| +			dst_xoff /= 2;
 | ||||
| +			dst_yoff = 0;
 | ||||
| +
 | ||||
| +			dst_yoff += ph->position_offset;
 | ||||
| +			break;
 | ||||
| +		case SPLASH_POS_FLAG_CORNER | SPLASH_CORNER_TOP_RIGHT:
 | ||||
| +			dst_xoff = info->var.xres - pp->pic_header->width;
 | ||||
| +			dst_yoff = 0;
 | ||||
| +
 | ||||
| +			dst_xoff -= ph->position_offset;
 | ||||
| +			dst_yoff += ph->position_offset;
 | ||||
| +			break;
 | ||||
| +		case SPLASH_POS_FLAG_CORNER | SPLASH_CORNER_RIGHT:
 | ||||
| +			dst_xoff = info->var.xres - pp->pic_header->width;
 | ||||
| +			dst_yoff = info->var.yres - pp->pic_header->height;
 | ||||
| +			dst_yoff /= 2;
 | ||||
| +
 | ||||
| +			dst_xoff -= ph->position_offset;
 | ||||
| +			break;
 | ||||
| +		case SPLASH_POS_FLAG_CORNER | SPLASH_CORNER_BOTTOM_RIGHT:
 | ||||
| +			dst_xoff = info->var.xres - pp->pic_header->width;
 | ||||
| +			dst_yoff = info->var.yres - pp->pic_header->height;
 | ||||
| +
 | ||||
| +			dst_xoff -= ph->position_offset;
 | ||||
| +			dst_yoff -= ph->position_offset;
 | ||||
| +			break;
 | ||||
| +		case SPLASH_POS_FLAG_CORNER | SPLASH_CORNER_BOTTOM:
 | ||||
| +			dst_xoff = info->var.xres - pp->pic_header->width;
 | ||||
| +			dst_xoff /= 2;
 | ||||
| +			dst_yoff = info->var.yres - pp->pic_header->height;
 | ||||
| +
 | ||||
| +			dst_yoff -= ph->position_offset;
 | ||||
| +			break;
 | ||||
| +		case SPLASH_POS_FLAG_CORNER | SPLASH_CORNER_BOTTOM_LEFT:
 | ||||
| +			dst_xoff = 0 + ph->position_offset;
 | ||||
| +			dst_yoff = info->var.yres - pp->pic_header->height
 | ||||
| +						  - ph->position_offset;
 | ||||
| +			break;
 | ||||
| +		case SPLASH_POS_FLAG_CORNER | SPLASH_CORNER_LEFT:
 | ||||
| +			dst_xoff = 0;
 | ||||
| +			dst_yoff = info->var.yres - pp->pic_header->height;
 | ||||
| +			dst_yoff /= 2;
 | ||||
| +
 | ||||
| +			dst_xoff += ph->position_offset;
 | ||||
| +			break;
 | ||||
| +
 | ||||
| +		case SPLASH_CORNER_TOP_LEFT:
 | ||||
| +			dst_xoff = info->var.xres - pp->pic_header->width;
 | ||||
| +			dst_xoff /= 2;
 | ||||
| +			dst_yoff = info->var.yres - pp->pic_header->height;
 | ||||
| +			dst_yoff /= 2;
 | ||||
| +
 | ||||
| +			dst_xoff -= ph->position_offset;
 | ||||
| +			dst_yoff -= ph->position_offset;
 | ||||
| +			break;
 | ||||
| +		case SPLASH_CORNER_TOP:
 | ||||
| +			dst_xoff = info->var.xres - pp->pic_header->width;
 | ||||
| +			dst_xoff /= 2;
 | ||||
| +			dst_yoff = info->var.yres - pp->pic_header->height;
 | ||||
| +			dst_yoff /= 2;
 | ||||
| +
 | ||||
| +			dst_yoff -= ph->position_offset;
 | ||||
| +			break;
 | ||||
| +		case SPLASH_CORNER_TOP_RIGHT:
 | ||||
| +			dst_xoff = info->var.xres - pp->pic_header->width;
 | ||||
| +			dst_xoff /= 2;
 | ||||
| +			dst_yoff = info->var.yres - pp->pic_header->height;
 | ||||
| +			dst_yoff /= 2;
 | ||||
| +
 | ||||
| +			dst_xoff += ph->position_offset;
 | ||||
| +			dst_yoff -= ph->position_offset;
 | ||||
| +			break;
 | ||||
| +		case SPLASH_CORNER_RIGHT:
 | ||||
| +			dst_xoff = info->var.xres - pp->pic_header->width;
 | ||||
| +			dst_xoff /= 2;
 | ||||
| +			dst_yoff = info->var.yres - pp->pic_header->height;
 | ||||
| +			dst_yoff /= 2;
 | ||||
| +
 | ||||
| +			dst_xoff += ph->position_offset;
 | ||||
| +			break;
 | ||||
| +		case SPLASH_CORNER_BOTTOM_RIGHT:
 | ||||
| +			dst_xoff = info->var.xres - pp->pic_header->width;
 | ||||
| +			dst_xoff /= 2;
 | ||||
| +			dst_yoff = info->var.yres - pp->pic_header->height;
 | ||||
| +			dst_yoff /= 2;
 | ||||
| +
 | ||||
| +			dst_xoff += ph->position_offset;
 | ||||
| +			dst_yoff += ph->position_offset;
 | ||||
| +			break;
 | ||||
| +		case SPLASH_CORNER_BOTTOM:
 | ||||
| +			dst_xoff = info->var.xres - pp->pic_header->width;
 | ||||
| +			dst_xoff /= 2;
 | ||||
| +			dst_yoff = info->var.yres - pp->pic_header->height;
 | ||||
| +			dst_yoff /= 2;
 | ||||
| +
 | ||||
| +			dst_yoff += ph->position_offset;
 | ||||
| +			break;
 | ||||
| +		case SPLASH_CORNER_BOTTOM_LEFT:
 | ||||
| +			dst_xoff = info->var.xres - pp->pic_header->width;
 | ||||
| +			dst_xoff /= 2;
 | ||||
| +			dst_yoff = info->var.yres - pp->pic_header->height;
 | ||||
| +			dst_yoff /= 2;
 | ||||
| +
 | ||||
| +			dst_xoff -= ph->position_offset;
 | ||||
| +			dst_yoff += ph->position_offset;
 | ||||
| +			break;
 | ||||
| +		case SPLASH_CORNER_LEFT:
 | ||||
| +			dst_xoff = info->var.xres - pp->pic_header->width;
 | ||||
| +			dst_xoff /= 2;
 | ||||
| +			dst_yoff = info->var.yres - pp->pic_header->height;
 | ||||
| +			dst_yoff /= 2;
 | ||||
| +
 | ||||
| +			dst_xoff -= ph->position_offset;
 | ||||
| +			break;
 | ||||
| +
 | ||||
| +		default:
 | ||||
| +			/* As a fallback, center the picture. */
 | ||||
| +			dst_xoff = info->var.xres - pp->pic_header->width;
 | ||||
| +			dst_xoff /= 2;
 | ||||
| +			dst_yoff = info->var.yres - pp->pic_header->height;
 | ||||
| +			dst_yoff /= 2;
 | ||||
| +			break;
 | ||||
| +		}
 | ||||
|   | ||||
|  		if (dst_xoff < 0 | ||||
|  		    || dst_yoff < 0 | ||||
| diff --git a/include/uapi/linux/bootsplash_file.h b/include/uapi/linux/bootsplash_file.h
 | ||||
| index 89dc9cca8f0c..71cedcc68933 100644
 | ||||
| --- a/include/uapi/linux/bootsplash_file.h
 | ||||
| +++ b/include/uapi/linux/bootsplash_file.h
 | ||||
| @@ -91,7 +91,32 @@ struct splash_pic_header {
 | ||||
|  	 */ | ||||
|  	uint8_t num_blobs; | ||||
|   | ||||
| -	uint8_t padding[27];
 | ||||
| +	/*
 | ||||
| +	 * Corner to move the picture to / from.
 | ||||
| +	 *  0x00 - Top left
 | ||||
| +	 *  0x01 - Top
 | ||||
| +	 *  0x02 - Top right
 | ||||
| +	 *  0x03 - Right
 | ||||
| +	 *  0x04 - Bottom right
 | ||||
| +	 *  0x05 - Bottom
 | ||||
| +	 *  0x06 - Bottom left
 | ||||
| +	 *  0x07 - Left
 | ||||
| +	 *
 | ||||
| +	 * Flags:
 | ||||
| +	 *  0x10 - Calculate offset from the corner towards the center,
 | ||||
| +	 *         rather than from the center towards the corner
 | ||||
| +	 */
 | ||||
| +	uint8_t position;
 | ||||
| +
 | ||||
| +	/*
 | ||||
| +	 * Pixel offset from the selected position.
 | ||||
| +	 * Example: If the picture is in the top right corner, it will
 | ||||
| +	 *          be placed position_offset pixels from the top and
 | ||||
| +	 *          position_offset pixels from the right margin.
 | ||||
| +	 */
 | ||||
| +	uint16_t position_offset;
 | ||||
| +
 | ||||
| +	uint8_t padding[24];
 | ||||
|  } __attribute__((__packed__)); | ||||
|   | ||||
|   | ||||
| @@ -115,4 +140,22 @@ struct splash_blob_header {
 | ||||
|  	uint8_t padding[9]; | ||||
|  } __attribute__((__packed__)); | ||||
|   | ||||
| +
 | ||||
| +
 | ||||
| +
 | ||||
| +/*
 | ||||
| + * Enums for on-disk types
 | ||||
| + */
 | ||||
| +enum splash_position {
 | ||||
| +	SPLASH_CORNER_TOP_LEFT = 0,
 | ||||
| +	SPLASH_CORNER_TOP = 1,
 | ||||
| +	SPLASH_CORNER_TOP_RIGHT = 2,
 | ||||
| +	SPLASH_CORNER_RIGHT = 3,
 | ||||
| +	SPLASH_CORNER_BOTTOM_RIGHT = 4,
 | ||||
| +	SPLASH_CORNER_BOTTOM = 5,
 | ||||
| +	SPLASH_CORNER_BOTTOM_LEFT = 6,
 | ||||
| +	SPLASH_CORNER_LEFT = 7,
 | ||||
| +	SPLASH_POS_FLAG_CORNER = 0x10,
 | ||||
| +};
 | ||||
| +
 | ||||
|  #endif | ||||
| @ -1,327 +0,0 @@ | ||||
| diff --git a/drivers/video/fbdev/core/bootsplash.c b/drivers/video/fbdev/core/bootsplash.c
 | ||||
| index 815b007f81ca..c8642142cfea 100644
 | ||||
| --- a/drivers/video/fbdev/core/bootsplash.c
 | ||||
| +++ b/drivers/video/fbdev/core/bootsplash.c
 | ||||
| @@ -53,6 +53,14 @@ static void splash_callback_redraw_vc(struct work_struct *ignored)
 | ||||
|  	console_unlock(); | ||||
|  } | ||||
|   | ||||
| +static void splash_callback_animation(struct work_struct *ignored)
 | ||||
| +{
 | ||||
| +	if (bootsplash_would_render_now()) {
 | ||||
| +		/* This will also re-schedule this delayed worker */
 | ||||
| +		splash_callback_redraw_vc(ignored);
 | ||||
| +	}
 | ||||
| +}
 | ||||
| +
 | ||||
|   | ||||
|  static bool is_fb_compatible(const struct fb_info *info) | ||||
|  { | ||||
| @@ -103,17 +111,44 @@ static bool is_fb_compatible(const struct fb_info *info)
 | ||||
|   */ | ||||
|  void bootsplash_render_full(struct fb_info *info) | ||||
|  { | ||||
| +	bool is_update = false;
 | ||||
| +
 | ||||
|  	mutex_lock(&splash_state.data_lock); | ||||
|   | ||||
| -	if (!is_fb_compatible(info))
 | ||||
| -		goto out;
 | ||||
| +	/*
 | ||||
| +	 * If we've painted on this FB recently, we don't have to do
 | ||||
| +	 * the sanity checks and background drawing again.
 | ||||
| +	 */
 | ||||
| +	if (splash_state.splash_fb == info)
 | ||||
| +		is_update = true;
 | ||||
| +
 | ||||
| +
 | ||||
| +	if (!is_update) {
 | ||||
| +		/* Check whether we actually support this FB. */
 | ||||
| +		splash_state.splash_fb = NULL;
 | ||||
| +
 | ||||
| +		if (!is_fb_compatible(info))
 | ||||
| +			goto out;
 | ||||
| +
 | ||||
| +		/* Draw the background only once */
 | ||||
| +		bootsplash_do_render_background(info, splash_state.file);
 | ||||
|   | ||||
| -	bootsplash_do_render_background(info, splash_state.file);
 | ||||
| +		/* Mark this FB as last seen */
 | ||||
| +		splash_state.splash_fb = info;
 | ||||
| +	}
 | ||||
|   | ||||
| -	bootsplash_do_render_pictures(info, splash_state.file);
 | ||||
| +	bootsplash_do_render_pictures(info, splash_state.file, is_update);
 | ||||
|   | ||||
|  	bootsplash_do_render_flush(info); | ||||
|   | ||||
| +	bootsplash_do_step_animations(splash_state.file);
 | ||||
| +
 | ||||
| +	/* Schedule update for animated splash screens */
 | ||||
| +	if (splash_state.file->frame_ms > 0)
 | ||||
| +		schedule_delayed_work(&splash_state.dwork_animation,
 | ||||
| +				      msecs_to_jiffies(
 | ||||
| +				      splash_state.file->frame_ms));
 | ||||
| +
 | ||||
|  out: | ||||
|  	mutex_unlock(&splash_state.data_lock); | ||||
|  } | ||||
| @@ -169,8 +204,14 @@ void bootsplash_enable(void)
 | ||||
|   | ||||
|  	was_enabled = test_and_set_bit(0, &splash_state.enabled); | ||||
|   | ||||
| -	if (!was_enabled)
 | ||||
| +	if (!was_enabled) {
 | ||||
| +		/* Force a full redraw when the splash is re-activated */
 | ||||
| +		mutex_lock(&splash_state.data_lock);
 | ||||
| +		splash_state.splash_fb = NULL;
 | ||||
| +		mutex_unlock(&splash_state.data_lock);
 | ||||
| +
 | ||||
|  		schedule_work(&splash_state.work_redraw_vc); | ||||
| +	}
 | ||||
|  } | ||||
|   | ||||
|   | ||||
| @@ -227,6 +268,14 @@ ATTRIBUTE_GROUPS(splash_dev);
 | ||||
|   */ | ||||
|  static int splash_resume(struct device *device) | ||||
|  { | ||||
| +	/*
 | ||||
| +	 * Force full redraw on resume since we've probably lost the
 | ||||
| +	 * framebuffer's contents meanwhile
 | ||||
| +	 */
 | ||||
| +	mutex_lock(&splash_state.data_lock);
 | ||||
| +	splash_state.splash_fb = NULL;
 | ||||
| +	mutex_unlock(&splash_state.data_lock);
 | ||||
| +
 | ||||
|  	if (bootsplash_would_render_now()) | ||||
|  		schedule_work(&splash_state.work_redraw_vc); | ||||
|   | ||||
| @@ -235,6 +284,7 @@ static int splash_resume(struct device *device)
 | ||||
|   | ||||
|  static int splash_suspend(struct device *device) | ||||
|  { | ||||
| +	cancel_delayed_work_sync(&splash_state.dwork_animation);
 | ||||
|  	cancel_work_sync(&splash_state.work_redraw_vc); | ||||
|   | ||||
|  	return 0; | ||||
| @@ -296,6 +346,8 @@ void bootsplash_init(void)
 | ||||
|  	set_bit(0, &splash_state.enabled); | ||||
|   | ||||
|  	INIT_WORK(&splash_state.work_redraw_vc, splash_callback_redraw_vc); | ||||
| +	INIT_DELAYED_WORK(&splash_state.dwork_animation,
 | ||||
| +			  splash_callback_animation);
 | ||||
|   | ||||
|   | ||||
|  	if (!splash_state.bootfile || !strlen(splash_state.bootfile)) | ||||
| diff --git a/drivers/video/fbdev/core/bootsplash_internal.h b/drivers/video/fbdev/core/bootsplash_internal.h
 | ||||
| index 0acb383aa4e3..b3a74835d90f 100644
 | ||||
| --- a/drivers/video/fbdev/core/bootsplash_internal.h
 | ||||
| +++ b/drivers/video/fbdev/core/bootsplash_internal.h
 | ||||
| @@ -37,6 +37,8 @@ struct splash_pic_priv {
 | ||||
|   | ||||
|  	struct splash_blob_priv *blobs; | ||||
|  	u16 blobs_loaded; | ||||
| +
 | ||||
| +	u16 anim_nextframe;
 | ||||
|  }; | ||||
|   | ||||
|   | ||||
| @@ -45,6 +47,12 @@ struct splash_file_priv {
 | ||||
|  	const struct splash_file_header *header; | ||||
|   | ||||
|  	struct splash_pic_priv *pics; | ||||
| +
 | ||||
| +	/*
 | ||||
| +	 * A local copy of the frame delay in the header.
 | ||||
| +	 * We modify it to keep the code simple.
 | ||||
| +	 */
 | ||||
| +	u16 frame_ms;
 | ||||
|  }; | ||||
|   | ||||
|   | ||||
| @@ -71,6 +79,7 @@ struct splash_priv {
 | ||||
|  	struct platform_device *splash_device; | ||||
|   | ||||
|  	struct work_struct work_redraw_vc; | ||||
| +	struct delayed_work dwork_animation;
 | ||||
|   | ||||
|  	/* Splash data structures including lock for everything below */ | ||||
|  	struct mutex data_lock; | ||||
| @@ -88,8 +97,10 @@ struct splash_priv {
 | ||||
|  void bootsplash_do_render_background(struct fb_info *info, | ||||
|  				     const struct splash_file_priv *fp); | ||||
|  void bootsplash_do_render_pictures(struct fb_info *info, | ||||
| -				   const struct splash_file_priv *fp);
 | ||||
| +				   const struct splash_file_priv *fp,
 | ||||
| +				   bool is_update);
 | ||||
|  void bootsplash_do_render_flush(struct fb_info *info); | ||||
| +void bootsplash_do_step_animations(struct splash_file_priv *fp);
 | ||||
|   | ||||
|   | ||||
|  void bootsplash_free_file(struct splash_file_priv *fp); | ||||
| diff --git a/drivers/video/fbdev/core/bootsplash_load.c b/drivers/video/fbdev/core/bootsplash_load.c
 | ||||
| index fd807571ab7d..1f661b2d4cc9 100644
 | ||||
| --- a/drivers/video/fbdev/core/bootsplash_load.c
 | ||||
| +++ b/drivers/video/fbdev/core/bootsplash_load.c
 | ||||
| @@ -71,6 +71,7 @@ struct splash_file_priv *bootsplash_load_firmware(struct device *device,
 | ||||
|  { | ||||
|  	const struct firmware *fw; | ||||
|  	struct splash_file_priv *fp; | ||||
| +	bool have_anim = false;
 | ||||
|  	unsigned int i; | ||||
|  	const u8 *walker; | ||||
|   | ||||
| @@ -135,6 +136,13 @@ struct splash_file_priv *bootsplash_load_firmware(struct device *device,
 | ||||
|  			goto err; | ||||
|  		} | ||||
|   | ||||
| +		if (ph->anim_type > SPLASH_ANIM_LOOP_FORWARD) {
 | ||||
| +			pr_warn("Picture %u: Unsupported animation type %u.\n",
 | ||||
| +				i, ph->anim_type);
 | ||||
| +
 | ||||
| +			ph->anim_type = SPLASH_ANIM_NONE;
 | ||||
| +		}
 | ||||
| +
 | ||||
|  		pp->pic_header = ph; | ||||
|  		pp->blobs = vzalloc(ph->num_blobs | ||||
|  					* sizeof(struct splash_blob_priv)); | ||||
| @@ -202,6 +210,7 @@ struct splash_file_priv *bootsplash_load_firmware(struct device *device,
 | ||||
|  	/* Walk over pictures and ensure all blob slots are filled */ | ||||
|  	for (i = 0; i < fp->header->num_pics; i++) { | ||||
|  		struct splash_pic_priv *pp = &fp->pics[i]; | ||||
| +		const struct splash_pic_header *ph = pp->pic_header;
 | ||||
|   | ||||
|  		if (pp->blobs_loaded != pp->pic_header->num_blobs) { | ||||
|  			pr_err("Picture %u doesn't have all blob slots filled.\n", | ||||
| @@ -209,8 +218,20 @@ struct splash_file_priv *bootsplash_load_firmware(struct device *device,
 | ||||
|   | ||||
|  			goto err; | ||||
|  		} | ||||
| +
 | ||||
| +		if (ph->anim_type
 | ||||
| +		    && ph->num_blobs > 1
 | ||||
| +		    && ph->anim_loop < pp->blobs_loaded)
 | ||||
| +			have_anim = true;
 | ||||
|  	} | ||||
|   | ||||
| +	if (!have_anim)
 | ||||
| +		/* Disable animation timer if there is nothing to animate */
 | ||||
| +		fp->frame_ms = 0;
 | ||||
| +	else
 | ||||
| +		/* Enforce minimum delay between frames */
 | ||||
| +		fp->frame_ms = max((u16)20, fp->header->frame_ms);
 | ||||
| +
 | ||||
|  	pr_info("Loaded (%ld bytes, %u pics, %u blobs).\n", | ||||
|  		fw->size, | ||||
|  		fp->header->num_pics, | ||||
| diff --git a/drivers/video/fbdev/core/bootsplash_render.c b/drivers/video/fbdev/core/bootsplash_render.c
 | ||||
| index 07e3a4eab811..76033606ca8a 100644
 | ||||
| --- a/drivers/video/fbdev/core/bootsplash_render.c
 | ||||
| +++ b/drivers/video/fbdev/core/bootsplash_render.c
 | ||||
| @@ -148,7 +148,8 @@ void bootsplash_do_render_background(struct fb_info *info,
 | ||||
|   | ||||
|   | ||||
|  void bootsplash_do_render_pictures(struct fb_info *info, | ||||
| -				   const struct splash_file_priv *fp)
 | ||||
| +				   const struct splash_file_priv *fp,
 | ||||
| +				   bool is_update)
 | ||||
|  { | ||||
|  	unsigned int i; | ||||
|   | ||||
| @@ -161,7 +162,11 @@ void bootsplash_do_render_pictures(struct fb_info *info,
 | ||||
|  		if (pp->blobs_loaded < 1) | ||||
|  			continue; | ||||
|   | ||||
| -		bp = &pp->blobs[0];
 | ||||
| +		/* Skip static pictures when refreshing animations */
 | ||||
| +		if (ph->anim_type == SPLASH_ANIM_NONE && is_update)
 | ||||
| +			continue;
 | ||||
| +
 | ||||
| +		bp = &pp->blobs[pp->anim_nextframe];
 | ||||
|   | ||||
|  		if (!bp || bp->blob_header->type != 0) | ||||
|  			continue; | ||||
| @@ -351,3 +356,24 @@ void bootsplash_do_render_flush(struct fb_info *info)
 | ||||
|  		info->fbops->fb_copyarea(info, &area); | ||||
|  	} | ||||
|  } | ||||
| +
 | ||||
| +
 | ||||
| +void bootsplash_do_step_animations(struct splash_file_priv *fp)
 | ||||
| +{
 | ||||
| +	unsigned int i;
 | ||||
| +
 | ||||
| +	/* Step every animation once */
 | ||||
| +	for (i = 0; i < fp->header->num_pics; i++) {
 | ||||
| +		struct splash_pic_priv *pp = &fp->pics[i];
 | ||||
| +
 | ||||
| +		if (pp->blobs_loaded < 2
 | ||||
| +		    || pp->pic_header->anim_loop > pp->blobs_loaded)
 | ||||
| +			continue;
 | ||||
| +
 | ||||
| +		if (pp->pic_header->anim_type == SPLASH_ANIM_LOOP_FORWARD) {
 | ||||
| +			pp->anim_nextframe++;
 | ||||
| +			if (pp->anim_nextframe >= pp->pic_header->num_blobs)
 | ||||
| +				pp->anim_nextframe = pp->pic_header->anim_loop;
 | ||||
| +		}
 | ||||
| +	}
 | ||||
| +}
 | ||||
| diff --git a/include/uapi/linux/bootsplash_file.h b/include/uapi/linux/bootsplash_file.h
 | ||||
| index 71cedcc68933..b3af0a3c6487 100644
 | ||||
| --- a/include/uapi/linux/bootsplash_file.h
 | ||||
| +++ b/include/uapi/linux/bootsplash_file.h
 | ||||
| @@ -77,7 +77,17 @@ struct splash_file_header {
 | ||||
|  	uint16_t num_blobs; | ||||
|  	uint8_t num_pics; | ||||
|   | ||||
| -	uint8_t padding[103];
 | ||||
| +	uint8_t unused_1;
 | ||||
| +
 | ||||
| +	/*
 | ||||
| +	 * Milliseconds to wait before painting the next frame in
 | ||||
| +	 * an animation.
 | ||||
| +	 * This is actually a minimum, as the system is allowed to
 | ||||
| +	 * stall for longer between frames.
 | ||||
| +	 */
 | ||||
| +	uint16_t frame_ms;
 | ||||
| +
 | ||||
| +	uint8_t padding[100];
 | ||||
|  } __attribute__((__packed__)); | ||||
|   | ||||
|   | ||||
| @@ -116,7 +126,23 @@ struct splash_pic_header {
 | ||||
|  	 */ | ||||
|  	uint16_t position_offset; | ||||
|   | ||||
| -	uint8_t padding[24];
 | ||||
| +	/*
 | ||||
| +	 * Animation type.
 | ||||
| +	 *  0 - off
 | ||||
| +	 *  1 - forward loop
 | ||||
| +	 */
 | ||||
| +	uint8_t anim_type;
 | ||||
| +
 | ||||
| +	/*
 | ||||
| +	 * Animation loop point.
 | ||||
| +	 * Actual meaning depends on animation type:
 | ||||
| +	 * Type 0 - Unused
 | ||||
| +	 *      1 - Frame at which to restart the forward loop
 | ||||
| +	 *          (allowing for "intro" frames)
 | ||||
| +	 */
 | ||||
| +	uint8_t anim_loop;
 | ||||
| +
 | ||||
| +	uint8_t padding[22];
 | ||||
|  } __attribute__((__packed__)); | ||||
|   | ||||
|   | ||||
| @@ -158,4 +184,9 @@ enum splash_position {
 | ||||
|  	SPLASH_POS_FLAG_CORNER = 0x10, | ||||
|  }; | ||||
|   | ||||
| +enum splash_anim_type {
 | ||||
| +	SPLASH_ANIM_NONE = 0,
 | ||||
| +	SPLASH_ANIM_LOOP_FORWARD = 1,
 | ||||
| +};
 | ||||
| +
 | ||||
|  #endif | ||||
| @ -1,82 +0,0 @@ | ||||
| diff --git a/drivers/tty/vt/vt.c b/drivers/tty/vt/vt.c
 | ||||
| index 2ebaba16f785..416735ab6dc1 100644
 | ||||
| --- a/drivers/tty/vt/vt.c
 | ||||
| +++ b/drivers/tty/vt/vt.c
 | ||||
| @@ -105,6 +105,7 @@
 | ||||
|  #include <linux/ctype.h> | ||||
|  #include <linux/bsearch.h> | ||||
|  #include <linux/gcd.h> | ||||
| +#include <linux/bootsplash.h>
 | ||||
|   | ||||
|  #define MAX_NR_CON_DRIVER 16 | ||||
|   | ||||
| @@ -4235,6 +4236,7 @@ void do_unblank_screen(int leaving_gfx)
 | ||||
|  	} | ||||
|   | ||||
|  	console_blanked = 0; | ||||
| +	bootsplash_mark_dirty();
 | ||||
|  	if (vc->vc_sw->con_blank(vc, 0, leaving_gfx)) | ||||
|  		/* Low-level driver cannot restore -> do it ourselves */ | ||||
|  		update_screen(vc); | ||||
| diff --git a/drivers/video/fbdev/core/bootsplash.c b/drivers/video/fbdev/core/bootsplash.c
 | ||||
| index c8642142cfea..13fcaabbc2ca 100644
 | ||||
| --- a/drivers/video/fbdev/core/bootsplash.c
 | ||||
| +++ b/drivers/video/fbdev/core/bootsplash.c
 | ||||
| @@ -165,6 +165,13 @@ bool bootsplash_would_render_now(void)
 | ||||
|  		&& bootsplash_is_enabled(); | ||||
|  } | ||||
|   | ||||
| +void bootsplash_mark_dirty(void)
 | ||||
| +{
 | ||||
| +	mutex_lock(&splash_state.data_lock);
 | ||||
| +	splash_state.splash_fb = NULL;
 | ||||
| +	mutex_unlock(&splash_state.data_lock);
 | ||||
| +}
 | ||||
| +
 | ||||
|  bool bootsplash_is_enabled(void) | ||||
|  { | ||||
|  	bool was_enabled; | ||||
| @@ -206,9 +213,7 @@ void bootsplash_enable(void)
 | ||||
|   | ||||
|  	if (!was_enabled) { | ||||
|  		/* Force a full redraw when the splash is re-activated */ | ||||
| -		mutex_lock(&splash_state.data_lock);
 | ||||
| -		splash_state.splash_fb = NULL;
 | ||||
| -		mutex_unlock(&splash_state.data_lock);
 | ||||
| +		bootsplash_mark_dirty();
 | ||||
|   | ||||
|  		schedule_work(&splash_state.work_redraw_vc); | ||||
|  	} | ||||
| @@ -272,9 +277,7 @@ static int splash_resume(struct device *device)
 | ||||
|  	 * Force full redraw on resume since we've probably lost the | ||||
|  	 * framebuffer's contents meanwhile | ||||
|  	 */ | ||||
| -	mutex_lock(&splash_state.data_lock);
 | ||||
| -	splash_state.splash_fb = NULL;
 | ||||
| -	mutex_unlock(&splash_state.data_lock);
 | ||||
| +	bootsplash_mark_dirty();
 | ||||
|   | ||||
|  	if (bootsplash_would_render_now()) | ||||
|  		schedule_work(&splash_state.work_redraw_vc); | ||||
| diff --git a/include/linux/bootsplash.h b/include/linux/bootsplash.h
 | ||||
| index c6dd0b43180d..4075098aaadd 100644
 | ||||
| --- a/include/linux/bootsplash.h
 | ||||
| +++ b/include/linux/bootsplash.h
 | ||||
| @@ -19,6 +19,8 @@ extern void bootsplash_render_full(struct fb_info *info);
 | ||||
|   | ||||
|  extern bool bootsplash_would_render_now(void); | ||||
|   | ||||
| +extern void bootsplash_mark_dirty(void);
 | ||||
| +
 | ||||
|  extern bool bootsplash_is_enabled(void); | ||||
|  extern void bootsplash_disable(void); | ||||
|  extern void bootsplash_enable(void); | ||||
| @@ -31,6 +33,8 @@ extern void bootsplash_init(void);
 | ||||
|   | ||||
|  #define bootsplash_would_render_now() (false) | ||||
|   | ||||
| +#define bootsplash_mark_dirty()
 | ||||
| +
 | ||||
|  #define bootsplash_is_enabled() (false) | ||||
|  #define bootsplash_disable() | ||||
|  #define bootsplash_enable() | ||||
| @ -1,42 +0,0 @@ | ||||
| diff --git a/drivers/tty/vt/keyboard.c b/drivers/tty/vt/keyboard.c
 | ||||
| index f4166263bb3a..a248429194bb 100644
 | ||||
| --- a/drivers/tty/vt/keyboard.c
 | ||||
| +++ b/drivers/tty/vt/keyboard.c
 | ||||
| @@ -49,6 +49,8 @@
 | ||||
|   | ||||
|  #include <asm/irq_regs.h> | ||||
|   | ||||
| +#include <linux/bootsplash.h>
 | ||||
| +
 | ||||
|  /* | ||||
|   * Exported functions/variables | ||||
|   */ | ||||
| @@ -1413,6 +1415,28 @@ static void kbd_keycode(unsigned int key
 | ||||
|  	} | ||||
|  #endif | ||||
|   | ||||
| +	/* Trap keys when bootsplash is shown */
 | ||||
| +	if (bootsplash_would_render_now()) {
 | ||||
| +		/* Deactivate bootsplash on ESC or Alt+Fxx VT switch */
 | ||||
| +		if (keycode >= KEY_F1 && keycode <= KEY_F12) {
 | ||||
| +			bootsplash_disable();
 | ||||
| +
 | ||||
| +			/*
 | ||||
| +			 * No return here since we want to actually
 | ||||
| +			 * perform the VT switch.
 | ||||
| +			 */
 | ||||
| +		} else {
 | ||||
| +			if (keycode == KEY_ESC)
 | ||||
| +				bootsplash_disable();
 | ||||
| +
 | ||||
| +			/*
 | ||||
| +			 * Just drop any other keys.
 | ||||
| +			 * Their effect would be hidden by the splash.
 | ||||
| +			 */
 | ||||
| +			return;
 | ||||
| +		}
 | ||||
| +	}
 | ||||
| +
 | ||||
|  	if (kbd->kbdmode == VC_MEDIUMRAW) { | ||||
|  		/* | ||||
|  		 * This is extended medium raw mode, with keys above 127 | ||||
| @ -1,21 +0,0 @@ | ||||
| diff --git a/drivers/tty/sysrq.c b/drivers/tty/sysrq.c
 | ||||
| index 3ffc1ce29023..bc6a24c9dfa8 100644
 | ||||
| --- a/drivers/tty/sysrq.c
 | ||||
| +++ b/drivers/tty/sysrq.c
 | ||||
| @@ -49,6 +49,7 @@
 | ||||
|  #include <linux/syscalls.h> | ||||
|  #include <linux/of.h> | ||||
|  #include <linux/rcupdate.h> | ||||
| +#include <linux/bootsplash.h>
 | ||||
|   | ||||
|  #include <asm/ptrace.h> | ||||
|  #include <asm/irq_regs.h> | ||||
| @@ -104,6 +105,8 @@ static void sysrq_handle_SAK(int key)
 | ||||
|  { | ||||
|  	struct work_struct *SAK_work = &vc_cons[fg_console].SAK_work; | ||||
|  	schedule_work(SAK_work); | ||||
| +
 | ||||
| +	bootsplash_disable();
 | ||||
|  } | ||||
|  static struct sysrq_key_op sysrq_SAK_op = { | ||||
|  	.handler	= sysrq_handle_SAK, | ||||
| @ -1,21 +0,0 @@ | ||||
| diff --git a/drivers/video/fbdev/core/fbcon.c b/drivers/video/fbdev/core/fbcon.c
 | ||||
| index 9a39a6fcfe98..8a9c67e1c5d8 100644
 | ||||
| --- a/drivers/video/fbdev/core/fbcon.c
 | ||||
| +++ b/drivers/video/fbdev/core/fbcon.c
 | ||||
| @@ -1343,6 +1343,16 @@ static void fbcon_cursor(struct vc_data *vc, int mode)
 | ||||
|  	int y; | ||||
|   	int c = scr_readw((u16 *) vc->vc_pos); | ||||
|   | ||||
| +	/*
 | ||||
| +	 * Disable the splash here so we don't have to hook into
 | ||||
| +	 * vt_console_print() in drivers/tty/vt/vt.c
 | ||||
| +	 *
 | ||||
| +	 * We'd disable the splash just before the call to
 | ||||
| +	 * hide_cursor() anyway, so this spot is just fine.
 | ||||
| +	 */
 | ||||
| +	if (oops_in_progress)
 | ||||
| +		bootsplash_disable();
 | ||||
| +
 | ||||
|  	ops->cur_blink_jiffies = msecs_to_jiffies(vc->vc_cur_blink_ms); | ||||
|   | ||||
|  	if (fbcon_is_inactive(vc, info) || vc->vc_deccm != 1) | ||||
| @ -1,321 +0,0 @@ | ||||
| diff --git a/Documentation/ABI/testing/sysfs-platform-bootsplash b/Documentation/ABI/testing/sysfs-platform-bootsplash
 | ||||
| new file mode 100644 | ||||
| index 000000000000..742c7b035ded
 | ||||
| --- /dev/null
 | ||||
| +++ b/Documentation/ABI/testing/sysfs-platform-bootsplash
 | ||||
| @@ -0,0 +1,11 @@
 | ||||
| +What:		/sys/devices/platform/bootsplash.0/enabled
 | ||||
| +Date:		Oct 2017
 | ||||
| +KernelVersion:	4.14
 | ||||
| +Contact:	Max Staudt <mstaudt@suse.de>
 | ||||
| +Description:
 | ||||
| +		Can be set and read.
 | ||||
| +
 | ||||
| +		0: Splash is disabled.
 | ||||
| +		1: Splash is shown whenever fbcon would show a text console
 | ||||
| +		   (i.e. no graphical application is running), and a splash
 | ||||
| +		   file is loaded.
 | ||||
| diff --git a/Documentation/bootsplash.rst b/Documentation/bootsplash.rst
 | ||||
| new file mode 100644 | ||||
| index 000000000000..611f0c558925
 | ||||
| --- /dev/null
 | ||||
| +++ b/Documentation/bootsplash.rst
 | ||||
| @@ -0,0 +1,285 @@
 | ||||
| +====================
 | ||||
| +The Linux bootsplash
 | ||||
| +====================
 | ||||
| +
 | ||||
| +:Date: November, 2017
 | ||||
| +:Author: Max Staudt <mstaudt@suse.de>
 | ||||
| +
 | ||||
| +
 | ||||
| +The Linux bootsplash is a graphical replacement for the '``quiet``' boot
 | ||||
| +option, typically showing a logo and a spinner animation as the system starts.
 | ||||
| +
 | ||||
| +Currently, it is a part of the Framebuffer Console support, and can be found
 | ||||
| +as ``CONFIG_BOOTSPLASH`` in the kernel configuration. This means that as long
 | ||||
| +as it is enabled, it hijacks fbcon's output and draws a splash screen instead.
 | ||||
| +
 | ||||
| +Purely compiling in the bootsplash will not render it functional - to actually
 | ||||
| +render a splash, you will also need a splash theme file. See the example
 | ||||
| +utility and script in ``tools/bootsplash`` for a live demo.
 | ||||
| +
 | ||||
| +
 | ||||
| +
 | ||||
| +Motivation
 | ||||
| +==========
 | ||||
| +
 | ||||
| +- The '``quiet``' boot option only suppresses most messages during boot, but
 | ||||
| +  errors are still shown.
 | ||||
| +
 | ||||
| +- A user space implementation can only show a logo once user space has been
 | ||||
| +  initialized far enough to allow this. A kernel splash can display a splash
 | ||||
| +  immediately as soon as fbcon can be displayed.
 | ||||
| +
 | ||||
| +- Implementing a splash screen in user space (e.g. Plymouth) is problematic
 | ||||
| +  due to resource conflicts.
 | ||||
| +
 | ||||
| +  For example, if Plymouth is keeping ``/dev/fb0`` (provided via vesafb/efifb)
 | ||||
| +  open, then most DRM drivers can't replace it because the address space is
 | ||||
| +  still busy - thus leading to a VRAM reservation error.
 | ||||
| +
 | ||||
| +  See: https://bugzilla.opensuse.org/show_bug.cgi?id=980750
 | ||||
| +
 | ||||
| +
 | ||||
| +
 | ||||
| +Command line arguments
 | ||||
| +======================
 | ||||
| +
 | ||||
| +``bootsplash.bootfile``
 | ||||
| +  Which file in the initramfs to load.
 | ||||
| +
 | ||||
| +  The splash theme is loaded via request_firmware(), thus to load
 | ||||
| +  ``/lib/firmware/bootsplash/mytheme`` pass the command line:
 | ||||
| +
 | ||||
| +  ``bootsplash.bootfile=bootsplash/mytheme``
 | ||||
| +
 | ||||
| +  Note: The splash file *has to be* in the initramfs, as it needs to be
 | ||||
| +  available when the splash is initialized early on.
 | ||||
| +
 | ||||
| +  Default: none, i.e. a non-functional splash, falling back to showing text.
 | ||||
| +
 | ||||
| +
 | ||||
| +
 | ||||
| +sysfs run-time configuration
 | ||||
| +============================
 | ||||
| +
 | ||||
| +``/sys/devices/platform/bootsplash.0/enabled``
 | ||||
| +  Enable/disable the bootsplash.
 | ||||
| +  The system boots with this set to 1, but will not show a splash unless
 | ||||
| +  a splash theme file is also loaded.
 | ||||
| +
 | ||||
| +
 | ||||
| +
 | ||||
| +Kconfig
 | ||||
| +=======
 | ||||
| +
 | ||||
| +``BOOTSPLASH``
 | ||||
| +  Whether to compile in bootsplash support
 | ||||
| +  (depends on fbcon compiled in, i.e. ``FRAMEBUFFER_CONSOLE=y``)
 | ||||
| +
 | ||||
| +
 | ||||
| +
 | ||||
| +Bootsplash file format
 | ||||
| +======================
 | ||||
| +
 | ||||
| +A file specified in the kernel configuration as ``CONFIG_BOOTSPLASH_FILE``
 | ||||
| +or specified on the command line as ``bootsplash.bootfile`` will be loaded
 | ||||
| +and displayed as soon as fbcon is initialized.
 | ||||
| +
 | ||||
| +
 | ||||
| +Main blocks
 | ||||
| +-----------
 | ||||
| +
 | ||||
| +There are 3 main blocks in each file:
 | ||||
| +
 | ||||
| +  - one File header
 | ||||
| +  -   n Picture headers
 | ||||
| +  -   m (Blob header + payload) blocks
 | ||||
| +
 | ||||
| +
 | ||||
| +Structures
 | ||||
| +----------
 | ||||
| +
 | ||||
| +The on-disk structures are defined in
 | ||||
| +``drivers/video/fbdev/core/bootsplash_file.h`` and represent these blocks:
 | ||||
| +
 | ||||
| +  - ``struct splash_file_header``
 | ||||
| +
 | ||||
| +    Represents the file header, with splash-wide information including:
 | ||||
| +
 | ||||
| +      - The magic string "``Linux bootsplash``" on big-endian platforms
 | ||||
| +        (the reverse on little endian)
 | ||||
| +      - The file format version (for incompatible updates, hopefully never)
 | ||||
| +      - The background color
 | ||||
| +      - Number of picture and blob blocks
 | ||||
| +      - Animation speed (we only allow one delay for all animations)
 | ||||
| +
 | ||||
| +    The file header is followed by the first picture header.
 | ||||
| +
 | ||||
| +
 | ||||
| +  - ``struct splash_picture_header``
 | ||||
| +
 | ||||
| +    Represents an object (picture) drawn on screen, including its immutable
 | ||||
| +    properties:
 | ||||
| +      - Width, height
 | ||||
| +      - Positioning relative to screen corners or in the center
 | ||||
| +      - Animation, if any
 | ||||
| +      - Animation type
 | ||||
| +      - Number of blobs
 | ||||
| +
 | ||||
| +    The picture header is followed by another picture header, up until n
 | ||||
| +    picture headers (as defined in the file header) have been read. Then,
 | ||||
| +    the (blob header, payload) pairs follow.
 | ||||
| +
 | ||||
| +
 | ||||
| +  - ``struct splash_blob_header``
 | ||||
| +    (followed by payload)
 | ||||
| +
 | ||||
| +    Represents one raw data stream. So far, only picture data is defined.
 | ||||
| +
 | ||||
| +    The blob header is followed by a payload, then padding to n*16 bytes,
 | ||||
| +    then (if further blobs are defined in the file header) a further blob
 | ||||
| +    header.
 | ||||
| +
 | ||||
| +
 | ||||
| +Alignment
 | ||||
| +---------
 | ||||
| +
 | ||||
| +The bootsplash file is designed to be loaded into memory as-is.
 | ||||
| +
 | ||||
| +All structures are a multiple of 16 bytes long, all elements therein are
 | ||||
| +aligned to multiples of their length, and the payloads are always padded
 | ||||
| +up to multiples of 16 bytes. This is to allow aligned accesses in all
 | ||||
| +cases while still simply mapping the structures over an in-memory copy of
 | ||||
| +the bootsplash file.
 | ||||
| +
 | ||||
| +
 | ||||
| +Further information
 | ||||
| +-------------------
 | ||||
| +
 | ||||
| +Please see ``drivers/video/fbdev/core/bootsplash_file.h`` for further
 | ||||
| +details and possible values in the file.
 | ||||
| +
 | ||||
| +
 | ||||
| +
 | ||||
| +Hooks - how the bootsplash is integrated
 | ||||
| +========================================
 | ||||
| +
 | ||||
| +``drivers/video/fbdev/core/fbcon.c``
 | ||||
| +  ``fbcon_init()`` calls ``bootsplash_init()``, which loads the default
 | ||||
| +  bootsplash file or the one specified on the kernel command line.
 | ||||
| +
 | ||||
| +  ``fbcon_switch()`` draws the bootsplash when it's active, and is also
 | ||||
| +  one of the callers of ``set_blitting_type()``.
 | ||||
| +
 | ||||
| +  ``set_blitting_type()`` calls ``fbcon_set_dummyops()`` when the
 | ||||
| +  bootsplash is active, overriding the text rendering functions.
 | ||||
| +
 | ||||
| +  ``fbcon_cursor()`` will call ``bootsplash_disable()`` when an oops is
 | ||||
| +  being printed in order to make a kernel panic visible.
 | ||||
| +
 | ||||
| +``drivers/video/fbdev/core/dummyblit.c``
 | ||||
| +  This contains the dummy text rendering functions used to suppress text
 | ||||
| +  output while the bootsplash is shown.
 | ||||
| +
 | ||||
| +``drivers/tty/vt/keyboard.c``
 | ||||
| +  ``kbd_keycode()`` can call ``bootsplash_disable()`` when the user
 | ||||
| +  presses ESC or F1-F12 (changing VT). This is to provide a built-in way
 | ||||
| +  of disabling the splash manually at any time.
 | ||||
| +
 | ||||
| +
 | ||||
| +
 | ||||
| +FAQ: Frequently Asked Questions
 | ||||
| +===============================
 | ||||
| +
 | ||||
| +I want to see the log! How do I show the log?
 | ||||
| +---------------------------------------------
 | ||||
| +
 | ||||
| +Press ESC while the splash is shown, or remove the ``bootsplash.bootfile``
 | ||||
| +parameter from the kernel cmdline. Without that parameter, the bootsplash
 | ||||
| +will boot disabled.
 | ||||
| +
 | ||||
| +
 | ||||
| +Why use FB instead of modern DRM/KMS?
 | ||||
| +-------------------------------------
 | ||||
| +
 | ||||
| +This is a semantic problem:
 | ||||
| + - What memory to draw the splash to?
 | ||||
| + - And what mode will the screen be set to?
 | ||||
| +
 | ||||
| +Using the fbdev emulation solves these issues.
 | ||||
| +
 | ||||
| +Let's start from a bare KMS system, without fbcon, and without fbdev
 | ||||
| +emulation. In this case, as long as userspace doesn't open the KMS
 | ||||
| +device, the state of the screen is undefined. No framebuffer is
 | ||||
| +allocated in video RAM, and no particular mode is set.
 | ||||
| +
 | ||||
| +In this case, we'd have to allocate a framebuffer to show the splash,
 | ||||
| +and set our mode ourselves. This either wastes a screenful of video RAM
 | ||||
| +if the splash is to co-exist with the userspace program's own allocated
 | ||||
| +framebuffer, or there is a flicker as we deactivate and delete the
 | ||||
| +bootsplash's framebuffer and hand control over to userspace. Since we
 | ||||
| +may set a different mode than userspace, we'd also have flicker due
 | ||||
| +to mode switching.
 | ||||
| +
 | ||||
| +This logic is already contained in every KMS driver that performs fbdev
 | ||||
| +emulation. So we might as well use that. And the correct API to do so is
 | ||||
| +fbdev. Plus, we get compatibility with old, pure fbdev drivers for free.
 | ||||
| +With the fbdev emulation, there is *always* a well-defined framebuffer
 | ||||
| +to draw on. And the selection of mode has already been done by the
 | ||||
| +graphics driver, so we don't need to reinvent that wheel, either.
 | ||||
| +Finally, if userspace decides to use /dev/fbX, we don't have to worry
 | ||||
| +about wasting video RAM, either.
 | ||||
| +
 | ||||
| +
 | ||||
| +Why is the bootsplash integrated in fbcon?
 | ||||
| +------------------------------------------
 | ||||
| +
 | ||||
| +Right now, the bootsplash is drawn from within fbcon, as this allows us
 | ||||
| +to easily know *when* to draw - i.e. when we're safe from fbcon and
 | ||||
| +userspace drawing all over our beautiful splash logo.
 | ||||
| +
 | ||||
| +Separating them is not easy - see the to-do list below.
 | ||||
| +
 | ||||
| +
 | ||||
| +
 | ||||
| +TO DO list for future development
 | ||||
| +=================================
 | ||||
| +
 | ||||
| +Second enable/disable switch for the system
 | ||||
| +-------------------------------------------
 | ||||
| +
 | ||||
| +It may be helpful to differentiate between the system and the user
 | ||||
| +switching off the bootsplash. Thus, the system may make it disappear and
 | ||||
| +reappear e.g. for a password prompt, yet once the user has pressed ESC,
 | ||||
| +it could stay gone.
 | ||||
| +
 | ||||
| +
 | ||||
| +Fix buggy DRM/KMS drivers
 | ||||
| +-------------------------
 | ||||
| +
 | ||||
| +Currently, the splash code manually checks for fbdev emulation provided by
 | ||||
| +the ast, cirrus, and mgag200 DRM/KMS drivers.
 | ||||
| +These drivers use a manual mechanism similar to deferred I/O for their FB
 | ||||
| +emulation, and thus need to be manually flushed onto the screen in the same
 | ||||
| +way.
 | ||||
| +
 | ||||
| +This may be improved upon in several ways:
 | ||||
| +
 | ||||
| +1. Changing these drivers to expose the fbdev BO's memory directly, like
 | ||||
| +   bochsdrmfb does.
 | ||||
| +2. Creating a new fb_ops->fb_flush() API to allow the kernel to flush the
 | ||||
| +   framebuffer once the bootsplash has been drawn into it.
 | ||||
| +
 | ||||
| +
 | ||||
| +Separating from fbcon
 | ||||
| +---------------------
 | ||||
| +
 | ||||
| +Separating these two components would yield independence from fbcon being
 | ||||
| +compiled into the kernel, and thus lowering code size in embedded
 | ||||
| +applications.
 | ||||
| +
 | ||||
| +To do this cleanly will involve a clean separation of users of an FB device
 | ||||
| +within the kernel, i.e. fbcon, bootsplash, and userspace. Right now, the
 | ||||
| +legacy fbcon code and VT code co-operate to switch between fbcon and
 | ||||
| +userspace (by setting the VT into KD_GRAPHICS mode). Installing a muxer
 | ||||
| +between these components ensues refactoring of old code and checking for
 | ||||
| +correct locking.
 | ||||
| diff --git a/MAINTAINERS b/MAINTAINERS
 | ||||
| index 5c237445761e..7ffac272434e 100644
 | ||||
| --- a/MAINTAINERS
 | ||||
| +++ b/MAINTAINERS
 | ||||
| @@ -3619,6 +3619,8 @@
 | ||||
|  M:     Max Staudt <mstaudt@suse.de> | ||||
|  L:     linux-fbdev@vger.kernel.org | ||||
|  S:     Maintained | ||||
| +F:     Documentation/ABI/testing/sysfs-platform-bootsplash
 | ||||
| +F:     Documentation/bootsplash.rst
 | ||||
|  F:     drivers/video/fbdev/core/bootsplash*.* | ||||
|  F:     drivers/video/fbdev/core/dummycon.c | ||||
|  F:     include/linux/bootsplash.h | ||||
| @ -1,129 +0,0 @@ | ||||
| diff --git a/Documentation/ABI/testing/sysfs-platform-bootsplash b/Documentation/ABI/testing/sysfs-platform-bootsplash
 | ||||
| index 742c7b035ded..f8f4b259220e 100644
 | ||||
| --- a/Documentation/ABI/testing/sysfs-platform-bootsplash
 | ||||
| +++ b/Documentation/ABI/testing/sysfs-platform-bootsplash
 | ||||
| @@ -9,3 +9,35 @@ Description:
 | ||||
|  		1: Splash is shown whenever fbcon would show a text console | ||||
|  		   (i.e. no graphical application is running), and a splash | ||||
|  		   file is loaded. | ||||
| +
 | ||||
| +What:		/sys/devices/platform/bootsplash.0/drop_splash
 | ||||
| +Date:		Oct 2017
 | ||||
| +KernelVersion:	4.14
 | ||||
| +Contact:	Max Staudt <mstaudt@suse.de>
 | ||||
| +Description:
 | ||||
| +		Can only be set.
 | ||||
| +
 | ||||
| +		Any value written will cause the current splash theme file
 | ||||
| +		to be unloaded and the text console to be redrawn.
 | ||||
| +
 | ||||
| +What:		/sys/devices/platform/bootsplash.0/load_file
 | ||||
| +Date:		Oct 2017
 | ||||
| +KernelVersion:	4.14
 | ||||
| +Contact:	Max Staudt <mstaudt@suse.de>
 | ||||
| +Description:
 | ||||
| +		Can only be set.
 | ||||
| +
 | ||||
| +		Any value written will cause the splash to be disabled and
 | ||||
| +		internal memory structures to be freed.
 | ||||
| +
 | ||||
| +		A firmware path written will cause a new theme file to be
 | ||||
| +		loaded and the current bootsplash to be replaced.
 | ||||
| +		The current enabled/disabled status is not touched.
 | ||||
| +		If the splash is already active, it will be redrawn.
 | ||||
| +
 | ||||
| +		The path has to be a path in /lib/firmware since
 | ||||
| +		request_firmware() is used to fetch the data.
 | ||||
| +
 | ||||
| +		When setting the splash from the shell, echo -n has to be
 | ||||
| +		used as any trailing '\n' newline will be interpreted as
 | ||||
| +		part of the path.
 | ||||
| diff --git a/Documentation/bootsplash.rst b/Documentation/bootsplash.rst
 | ||||
| index 611f0c558925..b35aba5093e8 100644
 | ||||
| --- a/Documentation/bootsplash.rst
 | ||||
| +++ b/Documentation/bootsplash.rst
 | ||||
| @@ -67,6 +67,14 @@ sysfs run-time configuration
 | ||||
|    a splash theme file is also loaded. | ||||
|   | ||||
|   | ||||
| +``/sys/devices/platform/bootsplash.0/drop_splash``
 | ||||
| +  Unload splash data and free memory.
 | ||||
| +
 | ||||
| +``/sys/devices/platform/bootsplash.0/load_file``
 | ||||
| +  Load a splash file from ``/lib/firmware/``.
 | ||||
| +  Note that trailing newlines will be interpreted as part of the file name.
 | ||||
| +
 | ||||
| +
 | ||||
|   | ||||
|  Kconfig | ||||
|  ======= | ||||
| diff --git a/drivers/video/fbdev/core/bootsplash.c b/drivers/video/fbdev/core/bootsplash.c
 | ||||
| index 13fcaabbc2ca..16cb0493629d 100644
 | ||||
| --- a/drivers/video/fbdev/core/bootsplash.c
 | ||||
| +++ b/drivers/video/fbdev/core/bootsplash.c
 | ||||
| @@ -251,11 +251,65 @@ static ssize_t splash_store_enabled(struct device *device,
 | ||||
|  	return count; | ||||
|  } | ||||
|   | ||||
| +static ssize_t splash_store_drop_splash(struct device *device,
 | ||||
| +					struct device_attribute *attr,
 | ||||
| +					const char *buf, size_t count)
 | ||||
| +{
 | ||||
| +	struct splash_file_priv *fp;
 | ||||
| +
 | ||||
| +	if (!buf || !count || !splash_state.file)
 | ||||
| +		return count;
 | ||||
| +
 | ||||
| +	mutex_lock(&splash_state.data_lock);
 | ||||
| +	fp = splash_state.file;
 | ||||
| +	splash_state.file = NULL;
 | ||||
| +	mutex_unlock(&splash_state.data_lock);
 | ||||
| +
 | ||||
| +	/* Redraw the text console */
 | ||||
| +	schedule_work(&splash_state.work_redraw_vc);
 | ||||
| +
 | ||||
| +	bootsplash_free_file(fp);
 | ||||
| +
 | ||||
| +	return count;
 | ||||
| +}
 | ||||
| +
 | ||||
| +static ssize_t splash_store_load_file(struct device *device,
 | ||||
| +				      struct device_attribute *attr,
 | ||||
| +				      const char *buf, size_t count)
 | ||||
| +{
 | ||||
| +	struct splash_file_priv *fp, *fp_old;
 | ||||
| +
 | ||||
| +	if (!count)
 | ||||
| +		return 0;
 | ||||
| +
 | ||||
| +	fp = bootsplash_load_firmware(&splash_state.splash_device->dev,
 | ||||
| +				      buf);
 | ||||
| +
 | ||||
| +	if (!fp)
 | ||||
| +		return -ENXIO;
 | ||||
| +
 | ||||
| +	mutex_lock(&splash_state.data_lock);
 | ||||
| +	fp_old = splash_state.file;
 | ||||
| +	splash_state.splash_fb = NULL;
 | ||||
| +	splash_state.file = fp;
 | ||||
| +	mutex_unlock(&splash_state.data_lock);
 | ||||
| +
 | ||||
| +	/* Update the splash or text console */
 | ||||
| +	schedule_work(&splash_state.work_redraw_vc);
 | ||||
| +
 | ||||
| +	bootsplash_free_file(fp_old);
 | ||||
| +	return count;
 | ||||
| +}
 | ||||
| +
 | ||||
|  static DEVICE_ATTR(enabled, 0644, splash_show_enabled, splash_store_enabled); | ||||
| +static DEVICE_ATTR(drop_splash, 0200, NULL, splash_store_drop_splash);
 | ||||
| +static DEVICE_ATTR(load_file, 0200, NULL, splash_store_load_file);
 | ||||
|   | ||||
|   | ||||
|  static struct attribute *splash_dev_attrs[] = { | ||||
|  	&dev_attr_enabled.attr, | ||||
| +	&dev_attr_drop_splash.attr,
 | ||||
| +	&dev_attr_load_file.attr,
 | ||||
|  	NULL | ||||
|  }; | ||||
|   | ||||
| @ -1,511 +0,0 @@ | ||||
| diff --git a/MAINTAINERS b/MAINTAINERS
 | ||||
| index 7ffac272434e..ddff07cd794c 100644
 | ||||
| --- a/MAINTAINERS
 | ||||
| +++ b/MAINTAINERS
 | ||||
| @@ -3625,6 +3625,7 @@
 | ||||
|  F:     drivers/video/fbdev/core/dummycon.c | ||||
|  F:     include/linux/bootsplash.h | ||||
|  F:     include/uapi/linux/bootsplash_file.h | ||||
| +F:     tools/bootsplash/*
 | ||||
|   | ||||
|  BPF [GENERAL] (Safe Dynamic Programs and Tools) | ||||
|  M:     Alexei Starovoitov <ast@kernel.org> | ||||
| diff --git a/tools/bootsplash/.gitignore b/tools/bootsplash/.gitignore
 | ||||
| new file mode 100644 | ||||
| index 000000000000..091b99a17567
 | ||||
| --- /dev/null
 | ||||
| +++ b/tools/bootsplash/.gitignore
 | ||||
| @@ -0,0 +1 @@
 | ||||
| +bootsplash-packer
 | ||||
| diff --git a/tools/bootsplash/Makefile b/tools/bootsplash/Makefile
 | ||||
| new file mode 100644 | ||||
| index 000000000000..0ad8e8a84942
 | ||||
| --- /dev/null
 | ||||
| +++ b/tools/bootsplash/Makefile
 | ||||
| @@ -0,0 +1,9 @@
 | ||||
| +CC := $(CROSS_COMPILE)gcc
 | ||||
| +CFLAGS := -I../../usr/include
 | ||||
| +
 | ||||
| +PROGS := bootsplash-packer
 | ||||
| +
 | ||||
| +all: $(PROGS)
 | ||||
| +
 | ||||
| +clean:
 | ||||
| +	rm -fr $(PROGS)
 | ||||
| diff --git a/tools/bootsplash/bootsplash-packer.c b/tools/bootsplash/bootsplash-packer.c
 | ||||
| new file mode 100644 | ||||
| index 000000000000..ffb6a8b69885
 | ||||
| --- /dev/null
 | ||||
| +++ b/tools/bootsplash/bootsplash-packer.c
 | ||||
| @@ -0,0 +1,471 @@
 | ||||
| +/*
 | ||||
| + * Kernel based bootsplash.
 | ||||
| + *
 | ||||
| + * (Splash file packer tool)
 | ||||
| + *
 | ||||
| + * Authors:
 | ||||
| + * Max Staudt <mstaudt@suse.de>
 | ||||
| + *
 | ||||
| + * SPDX-License-Identifier: GPL-2.0
 | ||||
| + */
 | ||||
| +
 | ||||
| +#include <endian.h>
 | ||||
| +#include <getopt.h>
 | ||||
| +#include <stdint.h>
 | ||||
| +#include <stdio.h>
 | ||||
| +#include <stdlib.h>
 | ||||
| +#include <string.h>
 | ||||
| +
 | ||||
| +#include <linux/bootsplash_file.h>
 | ||||
| +
 | ||||
| +
 | ||||
| +static void print_help(char *progname)
 | ||||
| +{
 | ||||
| +	printf("Usage: %s [OPTIONS] outfile\n", progname);
 | ||||
| +	printf("\n"
 | ||||
| +	       "Options, executed in order given:\n"
 | ||||
| +	       "  -h, --help                   Print this help message\n"
 | ||||
| +	       "\n"
 | ||||
| +	       "  --bg_red <u8>                Background color (red part)\n"
 | ||||
| +	       "  --bg_green <u8>              Background color (green part)\n"
 | ||||
| +	       "  --bg_blue <u8>               Background color (blue part)\n"
 | ||||
| +	       "  --bg_reserved <u8>           (do not use)\n"
 | ||||
| +	       "  --frame_ms <u16>             Minimum milliseconds between animation steps\n"
 | ||||
| +	       "\n"
 | ||||
| +	       "  --picture                    Start describing the next picture\n"
 | ||||
| +	       "  --pic_width <u16>            Picture width in pixels\n"
 | ||||
| +	       "  --pic_height <u16>           Picture height in pixels\n"
 | ||||
| +	       "  --pic_position <u8>             Coarse picture placement:\n"
 | ||||
| +	       "                                  0x00 - Top left\n"
 | ||||
| +	       "                                  0x01 - Top\n"
 | ||||
| +	       "                                  0x02 - Top right\n"
 | ||||
| +	       "                                  0x03 - Right\n"
 | ||||
| +	       "                                  0x04 - Bottom right\n"
 | ||||
| +	       "                                  0x05 - Bottom\n"
 | ||||
| +	       "                                  0x06 - Bottom left\n"
 | ||||
| +	       "                                  0x07 - Left\n"
 | ||||
| +	       "\n"
 | ||||
| +	       "                                Flags:\n"
 | ||||
| +	       "                                 0x10 - Calculate offset from corner towards center,\n"
 | ||||
| +	       "                                         rather than from center towards corner\n"
 | ||||
| +	       "  --pic_position_offset <u16>  Distance from base position in pixels\n"
 | ||||
| +	       "  --pic_anim_type <u8>         Animation type:\n"
 | ||||
| +	       "                                 0 - None\n"
 | ||||
| +	       "                                 1 - Forward loop\n"
 | ||||
| +	       "  --pic_anim_loop <u8>         Loop point for animation\n"
 | ||||
| +	       "\n"
 | ||||
| +	       "  --blob <filename>            Include next data stream\n"
 | ||||
| +	       "  --blob_type <u16>            Type of data\n"
 | ||||
| +	       "  --blob_picture_id <u8>       Picture to associate this blob with, starting at 0\n"
 | ||||
| +	       "                                 (default: number of last --picture)\n"
 | ||||
| +	       "\n");
 | ||||
| +	printf("This tool will write %s files.\n\n",
 | ||||
| +#if __BYTE_ORDER == __BIG_ENDIAN
 | ||||
| +	       "Big Endian (BE)");
 | ||||
| +#elif __BYTE_ORDER == __LITTLE_ENDIAN
 | ||||
| +	       "Little Endian (LE)");
 | ||||
| +#else
 | ||||
| +#error
 | ||||
| +#endif
 | ||||
| +}
 | ||||
| +
 | ||||
| +
 | ||||
| +struct blob_entry {
 | ||||
| +	struct blob_entry *next;
 | ||||
| +
 | ||||
| +	char *fn;
 | ||||
| +
 | ||||
| +	struct splash_blob_header header;
 | ||||
| +};
 | ||||
| +
 | ||||
| +
 | ||||
| +static void dump_file_header(struct splash_file_header *h)
 | ||||
| +{
 | ||||
| +	printf(" --- File header ---\n");
 | ||||
| +	printf("\n");
 | ||||
| +	printf("  version:     %5u\n", h->version);
 | ||||
| +	printf("\n");
 | ||||
| +	printf("  bg_red:      %5u\n", h->bg_red);
 | ||||
| +	printf("  bg_green:    %5u\n", h->bg_green);
 | ||||
| +	printf("  bg_blue:     %5u\n", h->bg_blue);
 | ||||
| +	printf("  bg_reserved: %5u\n", h->bg_reserved);
 | ||||
| +	printf("\n");
 | ||||
| +	printf("  num_blobs:   %5u\n", h->num_blobs);
 | ||||
| +	printf("  num_pics:    %5u\n", h->num_pics);
 | ||||
| +	printf("\n");
 | ||||
| +	printf("  frame_ms:    %5u\n", h->frame_ms);
 | ||||
| +	printf("\n");
 | ||||
| +}
 | ||||
| +
 | ||||
| +static void dump_pic_header(struct splash_pic_header *ph)
 | ||||
| +{
 | ||||
| +	printf(" --- Picture header ---\n");
 | ||||
| +	printf("\n");
 | ||||
| +	printf("  width:           %5u\n", ph->width);
 | ||||
| +	printf("  height:          %5u\n", ph->height);
 | ||||
| +	printf("\n");
 | ||||
| +	printf("  num_blobs:       %5u\n", ph->num_blobs);
 | ||||
| +	printf("\n");
 | ||||
| +	printf("  position:        %0x3x\n", ph->position);
 | ||||
| +	printf("  position_offset: %5u\n", ph->position_offset);
 | ||||
| +	printf("\n");
 | ||||
| +	printf("  anim_type:       %5u\n", ph->anim_type);
 | ||||
| +	printf("  anim_loop:       %5u\n", ph->anim_loop);
 | ||||
| +	printf("\n");
 | ||||
| +}
 | ||||
| +
 | ||||
| +static void dump_blob(struct blob_entry *b)
 | ||||
| +{
 | ||||
| +	printf(" --- Blob header ---\n");
 | ||||
| +	printf("\n");
 | ||||
| +	printf("  length:     %7u\n", b->header.length);
 | ||||
| +	printf("  type:       %7u\n", b->header.type);
 | ||||
| +	printf("\n");
 | ||||
| +	printf("  picture_id: %7u\n", b->header.picture_id);
 | ||||
| +	printf("\n");
 | ||||
| +}
 | ||||
| +
 | ||||
| +
 | ||||
| +#define OPT_MAX(var, max) \
 | ||||
| +	do { \
 | ||||
| +		if ((var) > max) { \
 | ||||
| +			fprintf(stderr, "--%s: Invalid value\n", \
 | ||||
| +			long_options[option_index].name); \
 | ||||
| +			break; \
 | ||||
| +		} \
 | ||||
| +	} while (0)
 | ||||
| +
 | ||||
| +static struct option long_options[] = {
 | ||||
| +	{"help", 0, 0, 'h'},
 | ||||
| +	{"bg_red", 1, 0, 10001},
 | ||||
| +	{"bg_green", 1, 0, 10002},
 | ||||
| +	{"bg_blue", 1, 0, 10003},
 | ||||
| +	{"bg_reserved", 1, 0, 10004},
 | ||||
| +	{"frame_ms", 1, 0, 10005},
 | ||||
| +	{"picture", 0, 0, 20000},
 | ||||
| +	{"pic_width", 1, 0, 20001},
 | ||||
| +	{"pic_height", 1, 0, 20002},
 | ||||
| +	{"pic_position", 1, 0, 20003},
 | ||||
| +	{"pic_position_offset", 1, 0, 20004},
 | ||||
| +	{"pic_anim_type", 1, 0, 20005},
 | ||||
| +	{"pic_anim_loop", 1, 0, 20006},
 | ||||
| +	{"blob", 1, 0, 30000},
 | ||||
| +	{"blob_type", 1, 0, 30001},
 | ||||
| +	{"blob_picture_id", 1, 0, 30002},
 | ||||
| +	{NULL, 0, NULL, 0}
 | ||||
| +};
 | ||||
| +
 | ||||
| +
 | ||||
| +int main(int argc, char **argv)
 | ||||
| +{
 | ||||
| +	FILE *of;
 | ||||
| +	char *ofn;
 | ||||
| +	int c;
 | ||||
| +	int option_index = 0;
 | ||||
| +
 | ||||
| +	unsigned long ul;
 | ||||
| +	struct splash_file_header fh = {};
 | ||||
| +	struct splash_pic_header ph[255];
 | ||||
| +	struct blob_entry *blob_first = NULL;
 | ||||
| +	struct blob_entry *blob_last = NULL;
 | ||||
| +	struct blob_entry *blob_cur = NULL;
 | ||||
| +
 | ||||
| +	if (argc < 2) {
 | ||||
| +		print_help(argv[0]);
 | ||||
| +		return EXIT_FAILURE;
 | ||||
| +	}
 | ||||
| +
 | ||||
| +
 | ||||
| +	/* Parse and and execute user commands */
 | ||||
| +	while ((c = getopt_long(argc, argv, "h",
 | ||||
| +			  long_options, &option_index)) != -1) {
 | ||||
| +		switch (c) {
 | ||||
| +		case 10001:	/* bg_red */
 | ||||
| +			ul = strtoul(optarg, NULL, 0);
 | ||||
| +			OPT_MAX(ul, 255);
 | ||||
| +			fh.bg_red = ul;
 | ||||
| +			break;
 | ||||
| +		case 10002:	/* bg_green */
 | ||||
| +			ul = strtoul(optarg, NULL, 0);
 | ||||
| +			OPT_MAX(ul, 255);
 | ||||
| +			fh.bg_green = ul;
 | ||||
| +			break;
 | ||||
| +		case 10003:	/* bg_blue */
 | ||||
| +			ul = strtoul(optarg, NULL, 0);
 | ||||
| +			OPT_MAX(ul, 255);
 | ||||
| +			fh.bg_blue = ul;
 | ||||
| +			break;
 | ||||
| +		case 10004:	/* bg_reserved */
 | ||||
| +			ul = strtoul(optarg, NULL, 0);
 | ||||
| +			OPT_MAX(ul, 255);
 | ||||
| +			fh.bg_reserved = ul;
 | ||||
| +			break;
 | ||||
| +		case 10005:	/* frame_ms */
 | ||||
| +			ul = strtoul(optarg, NULL, 0);
 | ||||
| +			OPT_MAX(ul, 65535);
 | ||||
| +			fh.frame_ms = ul;
 | ||||
| +			break;
 | ||||
| +
 | ||||
| +
 | ||||
| +		case 20000:	/* picture */
 | ||||
| +			if (fh.num_pics >= 255) {
 | ||||
| +				fprintf(stderr, "--%s: Picture array full\n",
 | ||||
| +					long_options[option_index].name);
 | ||||
| +				break;
 | ||||
| +			}
 | ||||
| +
 | ||||
| +			fh.num_pics++;
 | ||||
| +			break;
 | ||||
| +
 | ||||
| +		case 20001:	/* pic_width */
 | ||||
| +			ul = strtoul(optarg, NULL, 0);
 | ||||
| +			OPT_MAX(ul, 65535);
 | ||||
| +			ph[fh.num_pics - 1].width = ul;
 | ||||
| +			break;
 | ||||
| +
 | ||||
| +		case 20002:	/* pic_height */
 | ||||
| +			ul = strtoul(optarg, NULL, 0);
 | ||||
| +			OPT_MAX(ul, 65535);
 | ||||
| +			ph[fh.num_pics - 1].height = ul;
 | ||||
| +			break;
 | ||||
| +
 | ||||
| +		case 20003:	/* pic_position */
 | ||||
| +			ul = strtoul(optarg, NULL, 0);
 | ||||
| +			OPT_MAX(ul, 255);
 | ||||
| +			ph[fh.num_pics - 1].position = ul;
 | ||||
| +			break;
 | ||||
| +
 | ||||
| +		case 20004:	/* pic_position_offset */
 | ||||
| +			ul = strtoul(optarg, NULL, 0);
 | ||||
| +			OPT_MAX(ul, 255);
 | ||||
| +			ph[fh.num_pics - 1].position_offset = ul;
 | ||||
| +			break;
 | ||||
| +
 | ||||
| +		case 20005:	/* pic_anim_type */
 | ||||
| +			ul = strtoul(optarg, NULL, 0);
 | ||||
| +			OPT_MAX(ul, 255);
 | ||||
| +			ph[fh.num_pics - 1].anim_type = ul;
 | ||||
| +			break;
 | ||||
| +
 | ||||
| +		case 20006:	/* pic_anim_loop */
 | ||||
| +			ul = strtoul(optarg, NULL, 0);
 | ||||
| +			OPT_MAX(ul, 255);
 | ||||
| +			ph[fh.num_pics - 1].anim_loop = ul;
 | ||||
| +			break;
 | ||||
| +
 | ||||
| +
 | ||||
| +		case 30000:	/* blob */
 | ||||
| +			if (fh.num_blobs >= 65535) {
 | ||||
| +				fprintf(stderr, "--%s: Blob array full\n",
 | ||||
| +					long_options[option_index].name);
 | ||||
| +				break;
 | ||||
| +			}
 | ||||
| +
 | ||||
| +			blob_cur = calloc(1, sizeof(struct blob_entry));
 | ||||
| +			if (!blob_cur) {
 | ||||
| +				fprintf(stderr, "--%s: Out of memory\n",
 | ||||
| +					long_options[option_index].name);
 | ||||
| +				break;
 | ||||
| +			}
 | ||||
| +
 | ||||
| +			blob_cur->fn = optarg;
 | ||||
| +			if (fh.num_pics)
 | ||||
| +				blob_cur->header.picture_id = fh.num_pics - 1;
 | ||||
| +
 | ||||
| +			if (!blob_first)
 | ||||
| +				blob_first = blob_cur;
 | ||||
| +			if (blob_last)
 | ||||
| +				blob_last->next = blob_cur;
 | ||||
| +			blob_last = blob_cur;
 | ||||
| +			fh.num_blobs++;
 | ||||
| +			break;
 | ||||
| +
 | ||||
| +		case 30001:	/* blob_type */
 | ||||
| +			if (!blob_cur) {
 | ||||
| +				fprintf(stderr, "--%s: No blob selected\n",
 | ||||
| +					long_options[option_index].name);
 | ||||
| +				break;
 | ||||
| +			}
 | ||||
| +
 | ||||
| +			ul = strtoul(optarg, NULL, 0);
 | ||||
| +			OPT_MAX(ul, 255);
 | ||||
| +			blob_cur->header.type = ul;
 | ||||
| +			break;
 | ||||
| +
 | ||||
| +		case 30002:	/* blob_picture_id */
 | ||||
| +			if (!blob_cur) {
 | ||||
| +				fprintf(stderr, "--%s: No blob selected\n",
 | ||||
| +					long_options[option_index].name);
 | ||||
| +				break;
 | ||||
| +			}
 | ||||
| +
 | ||||
| +			ul = strtoul(optarg, NULL, 0);
 | ||||
| +			OPT_MAX(ul, 255);
 | ||||
| +			blob_cur->header.picture_id = ul;
 | ||||
| +			break;
 | ||||
| +
 | ||||
| +
 | ||||
| +
 | ||||
| +		case 'h':
 | ||||
| +		case '?':
 | ||||
| +		default:
 | ||||
| +			print_help(argv[0]);
 | ||||
| +			goto EXIT;
 | ||||
| +		} /* switch (c) */
 | ||||
| +	} /* while ((c = getopt_long(...)) != -1) */
 | ||||
| +
 | ||||
| +	/* Consume and drop lone arguments */
 | ||||
| +	while (optind < argc) {
 | ||||
| +		ofn = argv[optind];
 | ||||
| +		optind++;
 | ||||
| +	}
 | ||||
| +
 | ||||
| +
 | ||||
| +	/* Read file lengths */
 | ||||
| +	for (blob_cur = blob_first; blob_cur; blob_cur = blob_cur->next) {
 | ||||
| +		FILE *f;
 | ||||
| +		long pos;
 | ||||
| +		int i;
 | ||||
| +
 | ||||
| +		if (!blob_cur->fn)
 | ||||
| +			continue;
 | ||||
| +
 | ||||
| +		f = fopen(blob_cur->fn, "rb");
 | ||||
| +		if (!f)
 | ||||
| +			goto ERR_FILE_LEN;
 | ||||
| +
 | ||||
| +		if (fseek(f, 0, SEEK_END))
 | ||||
| +			goto ERR_FILE_LEN;
 | ||||
| +
 | ||||
| +		pos = ftell(f);
 | ||||
| +		if (pos < 0 || pos > (1 << 30))
 | ||||
| +			goto ERR_FILE_LEN;
 | ||||
| +
 | ||||
| +		blob_cur->header.length = pos;
 | ||||
| +
 | ||||
| +		fclose(f);
 | ||||
| +		continue;
 | ||||
| +
 | ||||
| +ERR_FILE_LEN:
 | ||||
| +		fprintf(stderr, "Error getting file length (or too long): %s\n",
 | ||||
| +			blob_cur->fn);
 | ||||
| +		if (f)
 | ||||
| +			fclose(f);
 | ||||
| +		continue;
 | ||||
| +	}
 | ||||
| +
 | ||||
| +
 | ||||
| +	/* Set magic headers */
 | ||||
| +#if __BYTE_ORDER == __BIG_ENDIAN
 | ||||
| +	memcpy(&fh.id[0], BOOTSPLASH_MAGIC_BE, 16);
 | ||||
| +#elif __BYTE_ORDER == __LITTLE_ENDIAN
 | ||||
| +	memcpy(&fh.id[0], BOOTSPLASH_MAGIC_LE, 16);
 | ||||
| +#else
 | ||||
| +#error
 | ||||
| +#endif
 | ||||
| +	fh.version = BOOTSPLASH_VERSION;
 | ||||
| +
 | ||||
| +	/* Set blob counts */
 | ||||
| +	for (blob_cur = blob_first; blob_cur; blob_cur = blob_cur->next) {
 | ||||
| +		if (blob_cur->header.picture_id < fh.num_pics)
 | ||||
| +			ph[blob_cur->header.picture_id].num_blobs++;
 | ||||
| +	}
 | ||||
| +
 | ||||
| +
 | ||||
| +	/* Dump structs */
 | ||||
| +	dump_file_header(&fh);
 | ||||
| +
 | ||||
| +	for (ul = 0; ul < fh.num_pics; ul++)
 | ||||
| +		dump_pic_header(&ph[ul]);
 | ||||
| +
 | ||||
| +	for (blob_cur = blob_first; blob_cur; blob_cur = blob_cur->next)
 | ||||
| +		dump_blob(blob_cur);
 | ||||
| +
 | ||||
| +
 | ||||
| +	/* Write to file */
 | ||||
| +	printf("Writing splash to file: %s\n", ofn);
 | ||||
| +	of = fopen(ofn, "wb");
 | ||||
| +	if (!of)
 | ||||
| +		goto ERR_WRITING;
 | ||||
| +
 | ||||
| +	if (fwrite(&fh, sizeof(struct splash_file_header), 1, of) != 1)
 | ||||
| +		goto ERR_WRITING;
 | ||||
| +
 | ||||
| +	for (ul = 0; ul < fh.num_pics; ul++) {
 | ||||
| +		if (fwrite(&ph[ul], sizeof(struct splash_pic_header), 1, of)
 | ||||
| +		    != 1)
 | ||||
| +			goto ERR_WRITING;
 | ||||
| +	}
 | ||||
| +
 | ||||
| +	blob_cur = blob_first;
 | ||||
| +	while (blob_cur) {
 | ||||
| +		struct blob_entry *blob_old = blob_cur;
 | ||||
| +		FILE *f;
 | ||||
| +		char *buf[256];
 | ||||
| +		uint32_t left;
 | ||||
| +
 | ||||
| +		if (fwrite(&blob_cur->header,
 | ||||
| +			   sizeof(struct splash_blob_header), 1, of) != 1)
 | ||||
| +			goto ERR_WRITING;
 | ||||
| +
 | ||||
| +		if (!blob_cur->header.length || !blob_cur->fn)
 | ||||
| +			continue;
 | ||||
| +
 | ||||
| +		f = fopen(blob_cur->fn, "rb");
 | ||||
| +		if (!f)
 | ||||
| +			goto ERR_FILE_COPY;
 | ||||
| +
 | ||||
| +		left = blob_cur->header.length;
 | ||||
| +		while (left >= sizeof(buf)) {
 | ||||
| +			if (fread(buf, sizeof(buf), 1, f) != 1)
 | ||||
| +				goto ERR_FILE_COPY;
 | ||||
| +			if (fwrite(buf, sizeof(buf), 1, of) != 1)
 | ||||
| +				goto ERR_FILE_COPY;
 | ||||
| +			left -= sizeof(buf);
 | ||||
| +		}
 | ||||
| +		if (left) {
 | ||||
| +			if (fread(buf, left, 1, f) != 1)
 | ||||
| +				goto ERR_FILE_COPY;
 | ||||
| +			if (fwrite(buf, left, 1, of) != 1)
 | ||||
| +				goto ERR_FILE_COPY;
 | ||||
| +		}
 | ||||
| +
 | ||||
| +		/* Pad data stream to 16 bytes */
 | ||||
| +		if (left % 16) {
 | ||||
| +			if (fwrite("\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0",
 | ||||
| +					16 - (left % 16), 1, of) != 1)
 | ||||
| +				goto ERR_FILE_COPY;
 | ||||
| +		}
 | ||||
| +
 | ||||
| +		fclose(f);
 | ||||
| +		blob_cur = blob_cur->next;
 | ||||
| +		free(blob_old);
 | ||||
| +		continue;
 | ||||
| +
 | ||||
| +ERR_FILE_COPY:
 | ||||
| +		if (f)
 | ||||
| +			fclose(f);
 | ||||
| +		goto ERR_WRITING;
 | ||||
| +	}
 | ||||
| +
 | ||||
| +	fclose(of);
 | ||||
| +
 | ||||
| +EXIT:
 | ||||
| +	return EXIT_SUCCESS;
 | ||||
| +
 | ||||
| +
 | ||||
| +ERR_WRITING:
 | ||||
| +	fprintf(stderr, "Error writing splash.\n");
 | ||||
| +	fprintf(stderr, "The output file is probably corrupt.\n");
 | ||||
| +	if (of)
 | ||||
| +		fclose(of);
 | ||||
| +
 | ||||
| +	while (blob_cur) {
 | ||||
| +		struct blob_entry *blob_old = blob_cur;
 | ||||
| +
 | ||||
| +		blob_cur = blob_cur->next;
 | ||||
| +		free(blob_old);
 | ||||
| +	}
 | ||||
| +
 | ||||
| +	return EXIT_FAILURE;
 | ||||
| +}
 | ||||
| @ -38,23 +38,6 @@ PATCHES=( | ||||
| 	${FILESDIR}/0101-arm64-dts-pinephone-drop-modem-power-node.patch | ||||
| 	${FILESDIR}/0102-arm64-dts-pinephone-pro-remove-modem-node.patch | ||||
| 	${FILESDIR}/0103-arm64-dts-rk3399-pinephone-pro-add-modem-RI-pin.patch | ||||
| 	#${FILESDIR}/0201-revert-fbcon-remove-now-unusued-softback_lines-cursor-argument.patch | ||||
| 	#${FILESDIR}/0202-revert-fbcon-remove-no-op-fbcon_set_origin.patch | ||||
| 	#${FILESDIR}/0203-revert-fbcon-remove-soft-scrollback-code.patch | ||||
| 		 | ||||
| 	#Bootsplash | ||||
| 	${FILESDIR}/0301-bootsplash.patch | ||||
| 	${FILESDIR}/0302-bootsplash.patch | ||||
| 	${FILESDIR}/0303-bootsplash.patch | ||||
| 	${FILESDIR}/0304-bootsplash.patch | ||||
| 	${FILESDIR}/0305-bootsplash.patch | ||||
| 	${FILESDIR}/0306-bootsplash.patch | ||||
| 	${FILESDIR}/0307-bootsplash.patch | ||||
| 	${FILESDIR}/0308-bootsplash.patch | ||||
| 	${FILESDIR}/0309-bootsplash.patch | ||||
| 	${FILESDIR}/0310-bootsplash.patch | ||||
| 	${FILESDIR}/0311-bootsplash.patch | ||||
| 	${FILESDIR}/0312-bootsplash.patch | ||||
| ) | ||||
| 
 | ||||
| S="${WORKDIR}/linux-${MEGI_TAG}" | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user