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 | |
11 | static inline unsigned int safe_shift(unsigned int d, int n) |
12 | { |
13 | return n < 0 ? d >> -n : d << n; |
14 | } |
15 | |
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(cmap: &palette_cmap, fb_info: info); |
47 | } |
48 | } |
49 | |
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(d: (clut[0] & redmask), n: redshift) | |
76 | safe_shift(d: (clut[1] & greenmask), n: greenshift) | |
77 | safe_shift(d: (clut[2] & bluemask), n: blueshift)); |
78 | clut += 3; |
79 | } |
80 | } |
81 | |
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 | |
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(var: &info->var, fix: &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 | |
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 | |
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 | |
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 | |
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(in: image->data, out: dst, width: image->width, |
217 | height: 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(in: image->data, out: dst, width: image->width, |
222 | height: 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(in: image->data, out: dst, width: image->width, |
229 | height: 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 | |
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 | |
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(size: 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(n: logo->width, size: logo->height, |
314 | GFP_KERNEL); |
315 | if (logo_new == NULL) { |
316 | kfree(objp: 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, dst: logo_new, depth: 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(n: logo->width, size: logo->height, |
348 | GFP_KERNEL); |
349 | if (logo_rotate) |
350 | fb_rotate_logo(info, dst: logo_rotate, image: &image, rotate); |
351 | } |
352 | |
353 | fb_do_show_logo(info, image: &image, rotate, num: n); |
354 | |
355 | kfree(objp: palette); |
356 | if (saved_pseudo_palette != NULL) |
357 | info->pseudo_palette = saved_pseudo_palette; |
358 | kfree(objp: logo_new); |
359 | kfree(objp: 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 | |
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 | |
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 | |
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 | |
418 | int fb_prepare_logo(struct fb_info *info, int rotate) |
419 | { |
420 | int depth = fb_get_color_depth(var: &info->var, fix: &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 | |
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, logo: fb_logo.logo, y: 0, n: count); |
503 | #ifdef CONFIG_FB_LOGO_EXTRA |
504 | y = fb_show_extra_logos(info, y, rotate); |
505 | #endif |
506 | |
507 | return y; |
508 | } |
509 | |