1// SPDX-License-Identifier: GPL-2.0
2
3#include <linux/fb.h>
4#include <linux/linux_logo.h>
5
6#include "fb_internal.h"
7
8bool fb_center_logo __read_mostly;
9int fb_logo_count __read_mostly = -1;
10
11static inline unsigned int safe_shift(unsigned int d, int n)
12{
13	return n < 0 ? d >> -n : d << n;
14}
15
16static void fb_set_logocmap(struct fb_info *info,
17				   const struct linux_logo *logo)
18{
19	struct fb_cmap palette_cmap;
20	u16 palette_green[16];
21	u16 palette_blue[16];
22	u16 palette_red[16];
23	int i, j, n;
24	const unsigned char *clut = logo->clut;
25
26	palette_cmap.start = 0;
27	palette_cmap.len = 16;
28	palette_cmap.red = palette_red;
29	palette_cmap.green = palette_green;
30	palette_cmap.blue = palette_blue;
31	palette_cmap.transp = NULL;
32
33	for (i = 0; i < logo->clutsize; i += n) {
34		n = logo->clutsize - i;
35		/* palette_cmap provides space for only 16 colors at once */
36		if (n > 16)
37			n = 16;
38		palette_cmap.start = 32 + i;
39		palette_cmap.len = n;
40		for (j = 0; j < n; ++j) {
41			palette_cmap.red[j] = clut[0] << 8 | clut[0];
42			palette_cmap.green[j] = clut[1] << 8 | clut[1];
43			palette_cmap.blue[j] = clut[2] << 8 | clut[2];
44			clut += 3;
45		}
46		fb_set_cmap(&palette_cmap, info);
47	}
48}
49
50static void  fb_set_logo_truepalette(struct fb_info *info,
51					    const struct linux_logo *logo,
52					    u32 *palette)
53{
54	static const unsigned char mask[] = {
55		0, 0x80, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc, 0xfe, 0xff
56	};
57	unsigned char redmask, greenmask, bluemask;
58	int redshift, greenshift, blueshift;
59	int i;
60	const unsigned char *clut = logo->clut;
61
62	/*
63	 * We have to create a temporary palette since console palette is only
64	 * 16 colors long.
65	 */
66	/* Bug: Doesn't obey msb_right ... (who needs that?) */
67	redmask   = mask[info->var.red.length   < 8 ? info->var.red.length   : 8];
68	greenmask = mask[info->var.green.length < 8 ? info->var.green.length : 8];
69	bluemask  = mask[info->var.blue.length  < 8 ? info->var.blue.length  : 8];
70	redshift   = info->var.red.offset   - (8 - info->var.red.length);
71	greenshift = info->var.green.offset - (8 - info->var.green.length);
72	blueshift  = info->var.blue.offset  - (8 - info->var.blue.length);
73
74	for (i = 0; i < logo->clutsize; i++) {
75		palette[i+32] = (safe_shift((clut[0] & redmask), redshift) |
76				 safe_shift((clut[1] & greenmask), greenshift) |
77				 safe_shift((clut[2] & bluemask), blueshift));
78		clut += 3;
79	}
80}
81
82static void fb_set_logo_directpalette(struct fb_info *info,
83					     const struct linux_logo *logo,
84					     u32 *palette)
85{
86	int redshift, greenshift, blueshift;
87	int i;
88
89	redshift = info->var.red.offset;
90	greenshift = info->var.green.offset;
91	blueshift = info->var.blue.offset;
92
93	for (i = 32; i < 32 + logo->clutsize; i++)
94		palette[i] = i << redshift | i << greenshift | i << blueshift;
95}
96
97static void fb_set_logo(struct fb_info *info,
98			       const struct linux_logo *logo, u8 *dst,
99			       int depth)
100{
101	int i, j, k;
102	const u8 *src = logo->data;
103	u8 xor = (info->fix.visual == FB_VISUAL_MONO01) ? 0xff : 0;
104	u8 fg = 1, d;
105
106	switch (fb_get_color_depth(&info->var, &info->fix)) {
107	case 1:
108		fg = 1;
109		break;
110	case 2:
111		fg = 3;
112		break;
113	default:
114		fg = 7;
115		break;
116	}
117
118	if (info->fix.visual == FB_VISUAL_MONO01 ||
119	    info->fix.visual == FB_VISUAL_MONO10)
120		fg = ~((u8) (0xfff << info->var.green.length));
121
122	switch (depth) {
123	case 4:
124		for (i = 0; i < logo->height; i++)
125			for (j = 0; j < logo->width; src++) {
126				*dst++ = *src >> 4;
127				j++;
128				if (j < logo->width) {
129					*dst++ = *src & 0x0f;
130					j++;
131				}
132			}
133		break;
134	case 1:
135		for (i = 0; i < logo->height; i++) {
136			for (j = 0; j < logo->width; src++) {
137				d = *src ^ xor;
138				for (k = 7; k >= 0 && j < logo->width; k--) {
139					*dst++ = ((d >> k) & 1) ? fg : 0;
140					j++;
141				}
142			}
143		}
144		break;
145	}
146}
147
148/*
149 * Three (3) kinds of logo maps exist.  linux_logo_clut224 (>16 colors),
150 * linux_logo_vga16 (16 colors) and linux_logo_mono (2 colors).  Depending on
151 * the visual format and color depth of the framebuffer, the DAC, the
152 * pseudo_palette, and the logo data will be adjusted accordingly.
153 *
154 * Case 1 - linux_logo_clut224:
155 * Color exceeds the number of console colors (16), thus we set the hardware DAC
156 * using fb_set_cmap() appropriately.  The "needs_cmapreset"  flag will be set.
157 *
158 * For visuals that require color info from the pseudo_palette, we also construct
159 * one for temporary use. The "needs_directpalette" or "needs_truepalette" flags
160 * will be set.
161 *
162 * Case 2 - linux_logo_vga16:
163 * The number of colors just matches the console colors, thus there is no need
164 * to set the DAC or the pseudo_palette.  However, the bitmap is packed, ie,
165 * each byte contains color information for two pixels (upper and lower nibble).
166 * To be consistent with fb_imageblit() usage, we therefore separate the two
167 * nibbles into separate bytes. The "depth" flag will be set to 4.
168 *
169 * Case 3 - linux_logo_mono:
170 * This is similar with Case 2.  Each byte contains information for 8 pixels.
171 * We isolate each bit and expand each into a byte. The "depth" flag will
172 * be set to 1.
173 */
174static struct logo_data {
175	int depth;
176	int needs_directpalette;
177	int needs_truepalette;
178	int needs_cmapreset;
179	const struct linux_logo *logo;
180} fb_logo __read_mostly;
181
182static void fb_rotate_logo_ud(const u8 *in, u8 *out, u32 width, u32 height)
183{
184	u32 size = width * height, i;
185
186	out += size - 1;
187
188	for (i = size; i--; )
189		*out-- = *in++;
190}
191
192static void fb_rotate_logo_cw(const u8 *in, u8 *out, u32 width, u32 height)
193{
194	int i, j, h = height - 1;
195
196	for (i = 0; i < height; i++)
197		for (j = 0; j < width; j++)
198			out[height * j + h - i] = *in++;
199}
200
201static void fb_rotate_logo_ccw(const u8 *in, u8 *out, u32 width, u32 height)
202{
203	int i, j, w = width - 1;
204
205	for (i = 0; i < height; i++)
206		for (j = 0; j < width; j++)
207			out[height * (w - j) + i] = *in++;
208}
209
210static void fb_rotate_logo(struct fb_info *info, u8 *dst,
211			   struct fb_image *image, int rotate)
212{
213	u32 tmp;
214
215	if (rotate == FB_ROTATE_UD) {
216		fb_rotate_logo_ud(image->data, dst, image->width,
217				  image->height);
218		image->dx = info->var.xres - image->width - image->dx;
219		image->dy = info->var.yres - image->height - image->dy;
220	} else if (rotate == FB_ROTATE_CW) {
221		fb_rotate_logo_cw(image->data, dst, image->width,
222				  image->height);
223		swap(image->width, image->height);
224		tmp = image->dy;
225		image->dy = image->dx;
226		image->dx = info->var.xres - image->width - tmp;
227	} else if (rotate == FB_ROTATE_CCW) {
228		fb_rotate_logo_ccw(image->data, dst, image->width,
229				   image->height);
230		swap(image->width, image->height);
231		tmp = image->dx;
232		image->dx = image->dy;
233		image->dy = info->var.yres - image->height - tmp;
234	}
235
236	image->data = dst;
237}
238
239static void fb_do_show_logo(struct fb_info *info, struct fb_image *image,
240			    int rotate, unsigned int num)
241{
242	unsigned int x;
243
244	if (image->width > info->var.xres || image->height > info->var.yres)
245		return;
246
247	if (rotate == FB_ROTATE_UR) {
248		for (x = 0;
249		     x < num && image->dx + image->width <= info->var.xres;
250		     x++) {
251			info->fbops->fb_imageblit(info, image);
252			image->dx += image->width + 8;
253		}
254	} else if (rotate == FB_ROTATE_UD) {
255		u32 dx = image->dx;
256
257		for (x = 0; x < num && image->dx <= dx; x++) {
258			info->fbops->fb_imageblit(info, image);
259			image->dx -= image->width + 8;
260		}
261	} else if (rotate == FB_ROTATE_CW) {
262		for (x = 0;
263		     x < num && image->dy + image->height <= info->var.yres;
264		     x++) {
265			info->fbops->fb_imageblit(info, image);
266			image->dy += image->height + 8;
267		}
268	} else if (rotate == FB_ROTATE_CCW) {
269		u32 dy = image->dy;
270
271		for (x = 0; x < num && image->dy <= dy; x++) {
272			info->fbops->fb_imageblit(info, image);
273			image->dy -= image->height + 8;
274		}
275	}
276}
277
278static int fb_show_logo_line(struct fb_info *info, int rotate,
279			     const struct linux_logo *logo, int y,
280			     unsigned int n)
281{
282	u32 *palette = NULL, *saved_pseudo_palette = NULL;
283	unsigned char *logo_new = NULL, *logo_rotate = NULL;
284	struct fb_image image;
285
286	/* Return if the frame buffer is not mapped or suspended */
287	if (logo == NULL || info->state != FBINFO_STATE_RUNNING ||
288	    info->fbops->owner)
289		return 0;
290
291	image.depth = 8;
292	image.data = logo->data;
293
294	if (fb_logo.needs_cmapreset)
295		fb_set_logocmap(info, logo);
296
297	if (fb_logo.needs_truepalette ||
298	    fb_logo.needs_directpalette) {
299		palette = kmalloc(256 * 4, GFP_KERNEL);
300		if (palette == NULL)
301			return 0;
302
303		if (fb_logo.needs_truepalette)
304			fb_set_logo_truepalette(info, logo, palette);
305		else
306			fb_set_logo_directpalette(info, logo, palette);
307
308		saved_pseudo_palette = info->pseudo_palette;
309		info->pseudo_palette = palette;
310	}
311
312	if (fb_logo.depth <= 4) {
313		logo_new = kmalloc_array(logo->width, logo->height,
314					 GFP_KERNEL);
315		if (logo_new == NULL) {
316			kfree(palette);
317			if (saved_pseudo_palette)
318				info->pseudo_palette = saved_pseudo_palette;
319			return 0;
320		}
321		image.data = logo_new;
322		fb_set_logo(info, logo, logo_new, fb_logo.depth);
323	}
324
325	if (fb_center_logo) {
326		int xres = info->var.xres;
327		int yres = info->var.yres;
328
329		if (rotate == FB_ROTATE_CW || rotate == FB_ROTATE_CCW) {
330			xres = info->var.yres;
331			yres = info->var.xres;
332		}
333
334		while (n && (n * (logo->width + 8) - 8 > xres))
335			--n;
336		image.dx = (xres - (n * (logo->width + 8) - 8)) / 2;
337		image.dy = y ?: (yres - logo->height) / 2;
338	} else {
339		image.dx = 0;
340		image.dy = y;
341	}
342
343	image.width = logo->width;
344	image.height = logo->height;
345
346	if (rotate) {
347		logo_rotate = kmalloc_array(logo->width, logo->height,
348					    GFP_KERNEL);
349		if (logo_rotate)
350			fb_rotate_logo(info, logo_rotate, &image, rotate);
351	}
352
353	fb_do_show_logo(info, &image, rotate, n);
354
355	kfree(palette);
356	if (saved_pseudo_palette != NULL)
357		info->pseudo_palette = saved_pseudo_palette;
358	kfree(logo_new);
359	kfree(logo_rotate);
360	return image.dy + logo->height;
361}
362
363#ifdef CONFIG_FB_LOGO_EXTRA
364
365#define FB_LOGO_EX_NUM_MAX 10
366static struct logo_data_extra {
367	const struct linux_logo *logo;
368	unsigned int n;
369} fb_logo_ex[FB_LOGO_EX_NUM_MAX];
370static unsigned int fb_logo_ex_num;
371
372void fb_append_extra_logo(const struct linux_logo *logo, unsigned int n)
373{
374	if (!n || fb_logo_ex_num == FB_LOGO_EX_NUM_MAX)
375		return;
376
377	fb_logo_ex[fb_logo_ex_num].logo = logo;
378	fb_logo_ex[fb_logo_ex_num].n = n;
379	fb_logo_ex_num++;
380}
381
382static int fb_prepare_extra_logos(struct fb_info *info, unsigned int height,
383				  unsigned int yres)
384{
385	unsigned int i;
386
387	/* FIXME: logo_ex supports only truecolor fb. */
388	if (info->fix.visual != FB_VISUAL_TRUECOLOR)
389		fb_logo_ex_num = 0;
390
391	for (i = 0; i < fb_logo_ex_num; i++) {
392		if (fb_logo_ex[i].logo->type != fb_logo.logo->type) {
393			fb_logo_ex[i].logo = NULL;
394			continue;
395		}
396		height += fb_logo_ex[i].logo->height;
397		if (height > yres) {
398			height -= fb_logo_ex[i].logo->height;
399			fb_logo_ex_num = i;
400			break;
401		}
402	}
403	return height;
404}
405
406static int fb_show_extra_logos(struct fb_info *info, int y, int rotate)
407{
408	unsigned int i;
409
410	for (i = 0; i < fb_logo_ex_num; i++)
411		y = fb_show_logo_line(info, rotate,
412				      fb_logo_ex[i].logo, y, fb_logo_ex[i].n);
413
414	return y;
415}
416#endif /* CONFIG_FB_LOGO_EXTRA */
417
418int fb_prepare_logo(struct fb_info *info, int rotate)
419{
420	int depth = fb_get_color_depth(&info->var, &info->fix);
421	unsigned int yres;
422	int height;
423
424	memset(&fb_logo, 0, sizeof(struct logo_data));
425
426	if (info->flags & FBINFO_MISC_TILEBLITTING ||
427	    info->fbops->owner || !fb_logo_count)
428		return 0;
429
430	if (info->fix.visual == FB_VISUAL_DIRECTCOLOR) {
431		depth = info->var.blue.length;
432		if (info->var.red.length < depth)
433			depth = info->var.red.length;
434		if (info->var.green.length < depth)
435			depth = info->var.green.length;
436	}
437
438	if (info->fix.visual == FB_VISUAL_STATIC_PSEUDOCOLOR && depth > 4) {
439		/* assume console colormap */
440		depth = 4;
441	}
442
443	/* Return if no suitable logo was found */
444	fb_logo.logo = fb_find_logo(depth);
445
446	if (!fb_logo.logo)
447		return 0;
448
449	if (rotate == FB_ROTATE_UR || rotate == FB_ROTATE_UD)
450		yres = info->var.yres;
451	else
452		yres = info->var.xres;
453
454	if (fb_logo.logo->height > yres) {
455		fb_logo.logo = NULL;
456		return 0;
457	}
458
459	/* What depth we asked for might be different from what we get */
460	if (fb_logo.logo->type == LINUX_LOGO_CLUT224)
461		fb_logo.depth = 8;
462	else if (fb_logo.logo->type == LINUX_LOGO_VGA16)
463		fb_logo.depth = 4;
464	else
465		fb_logo.depth = 1;
466
467
468	if (fb_logo.depth > 4 && depth > 4) {
469		switch (info->fix.visual) {
470		case FB_VISUAL_TRUECOLOR:
471			fb_logo.needs_truepalette = 1;
472			break;
473		case FB_VISUAL_DIRECTCOLOR:
474			fb_logo.needs_directpalette = 1;
475			fb_logo.needs_cmapreset = 1;
476			break;
477		case FB_VISUAL_PSEUDOCOLOR:
478			fb_logo.needs_cmapreset = 1;
479			break;
480		}
481	}
482
483	height = fb_logo.logo->height;
484	if (fb_center_logo)
485		height += (yres - fb_logo.logo->height) / 2;
486#ifdef CONFIG_FB_LOGO_EXTRA
487	height = fb_prepare_extra_logos(info, height, yres);
488#endif
489
490	return height;
491}
492
493int fb_show_logo(struct fb_info *info, int rotate)
494{
495	unsigned int count;
496	int y;
497
498	if (!fb_logo_count)
499		return 0;
500
501	count = fb_logo_count < 0 ? num_online_cpus() : fb_logo_count;
502	y = fb_show_logo_line(info, rotate, fb_logo.logo, 0, count);
503#ifdef CONFIG_FB_LOGO_EXTRA
504	y = fb_show_extra_logos(info, y, rotate);
505#endif
506
507	return y;
508}
509