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(&palette_cmap, 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((clut[0] & redmask), redshift) | 76 safe_shift((clut[1] & greenmask), greenshift) | 77 safe_shift((clut[2] & bluemask), 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(&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 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(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 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(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 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(&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 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