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
8 bool fb_center_logo __read_mostly;
9 int fb_logo_count __read_mostly = -1;
10
safe_shift(unsigned int d,int n)11 static inline unsigned int safe_shift(unsigned int d, int n)
12 {
13 return n < 0 ? d >> -n : d << n;
14 }
15
fb_set_logocmap(struct fb_info * info,const struct linux_logo * logo)16 static 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
fb_set_logo_truepalette(struct fb_info * info,const struct linux_logo * logo,u32 * palette)50 static 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
fb_set_logo_directpalette(struct fb_info * info,const struct linux_logo * logo,u32 * palette)82 static 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
fb_set_logo(struct fb_info * info,const struct linux_logo * logo,u8 * dst,int depth)97 static 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 */
174 static 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
fb_rotate_logo_ud(const u8 * in,u8 * out,u32 width,u32 height)182 static 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
fb_rotate_logo_cw(const u8 * in,u8 * out,u32 width,u32 height)192 static 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
fb_rotate_logo_ccw(const u8 * in,u8 * out,u32 width,u32 height)201 static 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
fb_rotate_logo(struct fb_info * info,u8 * dst,struct fb_image * image,int rotate)210 static 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
fb_do_show_logo(struct fb_info * info,struct fb_image * image,int rotate,unsigned int num)239 static 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
fb_show_logo_line(struct fb_info * info,int rotate,const struct linux_logo * logo,int y,unsigned int n)278 static 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
366 static struct logo_data_extra {
367 const struct linux_logo *logo;
368 unsigned int n;
369 } fb_logo_ex[FB_LOGO_EX_NUM_MAX];
370 static unsigned int fb_logo_ex_num;
371
fb_append_extra_logo(const struct linux_logo * logo,unsigned int n)372 void 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
fb_prepare_extra_logos(struct fb_info * info,unsigned int height,unsigned int yres)382 static 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
fb_show_extra_logos(struct fb_info * info,int y,int rotate)406 static 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
fb_prepare_logo(struct fb_info * info,int rotate)418 int 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
fb_show_logo(struct fb_info * info,int rotate)493 int 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