509 lines · c
1// SPDX-License-Identifier: GPL-2.02 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, 0xff56 };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 only64 * 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 on151 * the visual format and color depth of the framebuffer, the DAC, the152 * 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 DAC156 * 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 construct159 * one for temporary use. The "needs_directpalette" or "needs_truepalette" flags160 * will be set.161 *162 * Case 2 - linux_logo_vga16:163 * The number of colors just matches the console colors, thus there is no need164 * 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 two167 * 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 will172 * 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 else306 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_EXTRA364 365#define FB_LOGO_EX_NUM_MAX 10366static 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 else452 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 else465 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_EXTRA487 height = fb_prepare_extra_logos(info, height, yres);488#endif489 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_EXTRA504 y = fb_show_extra_logos(info, y, rotate);505#endif506 507 return y;508}509