| 1 | |
| 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 |
|
| 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 |
|
| 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 |
|
| 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 |
|
| 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
|
| 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 |
|
| 440 | depth = 4;
|
| 441 | }
|
| 442 |
|
| 443 |
|
| 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 |
|
| 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 | }
|