Theldus's blog

Ricing my Tux boot logo

By Davidson Francis | Published: 2025-04-21 | Last Modified: 2025-05-03

Before

Image 1: Before and after: one waifu/image for each CPU core

I've been using Slackware as my main OS for over 10 years, and one thing I have always liked is the 'Tuxes' at the top representing the number of cores I have. I know this isn't exclusive to Slackware, but anyone who's used it with LILO knows exactly what I'm talking about.

Anyway, I've always wanted to customize the Tux with something more, shall we say, interesting. However, I never intended in putting a single image to replace the Tux, seems boring, why not several? For example, showing specifically the number of cores I have.

This post shows what is needed to do to add as many images as we want, and display them individually on the screen, just as shown in the first image of this post. Spoiler: we have to patch the kernel!.

💡 Note: The entire procedure shown here is focused on Slackware, but all the steps can be perfectly adapted to any Linux distro.

How does it work?

There's no official way to, for example, via GRUB or LILO arguments, define images to be loaded during startup, but Linux being open-source, that won't stop us, right?

I believe the whole process can be divided into three steps:

1) Image preparation

The images must meet the following criteria:

All the steps above can be summarized with the following command:

$ pngtopnm img1.png | ppmquant -fs 223 | pnmtoplainpnm > logo_cpu0_clut224.ppm
$ pngtopnm img2.png | ppmquant -fs 223 | pnmtoplainpnm > logo_cpu1_clut224.ppm
$ pngtopnm img3.png | ppmquant -fs 223 | pnmtoplainpnm > logo_cpu2_clut224.ppm
$ pngtopnm img4.png | ppmquant -fs 223 | pnmtoplainpnm > logo_cpu3_clut224.ppm
...

ℹ️ Info: For those who liked my images, you can download the 8 I've created, already in PPM format and at 80x80 resolution, here.

2) Integrating images into the kernel build

Once you have the converted images, move them to: /usr/src/linux-XYZ/drivers/video/logo and apply the patch below to add them to the build system: Kconfig and Makefile, as in:

# 1) Move the images to the kernel logo folder
$ cp logo_cpu0_clut224.ppm /usr/src/linux-XYZ/drivers/video/logo
$ ...
$ cp logo_cpu4_clut224.ppm /usr/src/linux-XYZ/drivers/video/logo

# 2) Apply the logo.patch to the kernel
$ cd /usr/src/linux-XYZ/drivers/video/logo
$ patch -p1 < /path/to/logo.patch
logo.patch
diff -ruN --color logo-old/Kconfig logo/Kconfig
--- logo-old/Kconfig	2022-02-01 13:27:16.000000000 -0300
+++ logo/Kconfig	2025-04-21 02:23:23.000000000 -0300
@@ -28,6 +28,10 @@
 	bool "Standard 224-color Linux logo"
 	default y

+config LOGO_CPUSWAIFUS_CLUT224
+	bool "Show cute CPUs waifus for each core"
+	default y
+
 config LOGO_DEC_CLUT224
 	bool "224-color Digital Equipment Corporation Linux logo"
 	depends on MACH_DECSTATION || ALPHA
diff -ruN --color logo-old/Makefile logo/Makefile
--- logo-old/Makefile	2022-02-01 13:27:16.000000000 -0300
+++ logo/Makefile	2025-04-21 02:23:11.000000000 -0300
@@ -13,6 +13,7 @@
 obj-$(CONFIG_LOGO_SUPERH_MONO)		+= logo_superh_mono.o
 obj-$(CONFIG_LOGO_SUPERH_VGA16)		+= logo_superh_vga16.o
 obj-$(CONFIG_LOGO_SUPERH_CLUT224)	+= logo_superh_clut224.o
+obj-$(CONFIG_LOGO_CPUSWAIFUS_CLUT224)   += logo_cpu0_clut224.o logo_cpu1_clut224.o logo_cpu2_clut224.o logo_cpu3_clut224.o

 obj-$(CONFIG_SPU_BASE)			+= logo_spe_clut224.o


File: website/assets/src/boot-logo/logo.patch

The idea is simple: the config LOGO_CPUSWAIFUS_CLUT224 is added to the build system ('y' as default), and the object files corresponding to the images (to be generated automatically) are linked to this config, i.e., they are only added if the kernel is compiled with this config enabled.

3) Patching the drawing routines!

In short, the main logic for drawing the Tux resides in the files:

And it works as follows: the function fb_show_logo() is invoked with the image rotation parameter and the framebuffer structure. It, in turn, obtains the number of online CPU cores and invokes fb_show_logo_line(), which, in addition to other parameters, receives the logo to be drawn.

This second function performs additional checks and prepares an additional structure, dynamically allocated for drawing the logo, which is finally done by the third function, fb_do_show_logo(). This last function, which receives this newly allocated structure, draws the same image num times.

In general terms:
Function signatures:
--------------------
int fb_show_logo(struct fb_info *info, int rotate);

Params:
  fb_info: Holds many framebuffer important properties
           (include/linux/fb.h)
  rotate:  Whether the logo should be rotated or not
           0 = no rotation

===

static int fb_show_logo_line(
  struct fb_info *info,
  int rotate,
  const struct linux_logo *logo,
  int y,
  unsigned int n);

Params:
  logo: actual linux logo (include/linux/linux_logo.h)
      struct linux_logo {
        int type;
        unsigned int width;
        unsigned int height;
        unsigned int clutsize;
        const unsigned char *clut;
        const unsigned char *data;
      };
  y: Y-coordinate to draw, starts at 0
  n: Number of times the logo should repeat

===

void fb_do_show_logo(
  struct fb_info  *info,
  struct fb_image *image,
  int rotate,
  unsigned int num)

 Params:
   fb_image: A dynamically allocated copy of linux_logo with
             additional data. (include/uapi/linux/fb.h)
      struct fb_image {
        __u32 dx;            /* Where to place image         */
        __u32 dy;
        __u32 width;         /* Size of image                */
        __u32 height;
        __u32 fg_color;      /* Only used when a mono bitmap */
        __u32 bg_color;
        __u8  depth;         /* Depth of the image           */
        const char *data;    /* Pointer to image data        */
        struct fb_cmap cmap; /* color map info               */
      };

A typical execution for 224-color image, no rotation, 4 cores:
--------------------------------------------------------------
fb_show_logo (info, 0)
  | ncpus = 4
  |
   \->  fb_show_logo_line(info, 0, actual_logo, 0, ncpus)
          | struct fb_image = <copy> actual_logo
          |
           \-> fb_do_show_logo(info, fb_image, 0, ncpus)

Can you see where this is going? The whole mechanism is basically ready, and we do not need significant changes for this to work. Note, for example, that the fb_do_show_logo() function already considers the starting X-axis for drawing the next Tux, but this is defined as 0 in the previous function (in image.dx = 0).

The following patch (core-fbmem.patch) then does three main things:

  1. Modifies the function signatures so that the X-axis can be considered when drawing the other images.
  2. Adds the list of images defined earlier.
  3. Defines a new function, which then iterates over the list of images, recalculating the X-axis, and passing only '1' as the number of redraws, since fb_do_show_logo() should draw a different image each time.

Apply it with:

$ cd /usr/src/linux-XYZ/drivers/video/fbdev/core
$ patch -p1 < /path/to/core-fbmem.patch
core-fbmem.patch (kernel < v6.7)
diff -ruN --color core-old/fbmem.c core/fbmem.c
--- core-old/fbmem.c	2022-02-01 13:27:16.000000000 -0300
+++ core/fbmem.c	2025-04-21 20:53:49.000000000 -0300
@@ -186,6 +186,18 @@

 #ifdef CONFIG_LOGO

+extern const struct linux_logo logo_cpu0_clut224;
+extern const struct linux_logo logo_cpu1_clut224;
+extern const struct linux_logo logo_cpu2_clut224;
+extern const struct linux_logo logo_cpu3_clut224;
+
+static const struct linux_logo *logo_cpuswaifus[] = {
+	&logo_cpu0_clut224,
+	&logo_cpu1_clut224,
+	&logo_cpu2_clut224,
+	&logo_cpu3_clut224
+};
+
 static inline unsigned safe_shift(unsigned d, int n)
 {
 	return n < 0 ? d >> -n : d << n;
@@ -456,7 +468,7 @@
 }

 static int fb_show_logo_line(struct fb_info *info, int rotate,
-			     const struct linux_logo *logo, int y,
+			     const struct linux_logo *logo, int x, int y,
 			     unsigned int n)
 {
 	u32 *palette = NULL, *saved_pseudo_palette = NULL;
@@ -516,7 +528,7 @@
 		image.dx = (xres - n * (logo->width + 8) - 8) / 2;
 		image.dy = y ?: (yres - logo->height) / 2;
 	} else {
-		image.dx = 0;
+		image.dx = x;
 		image.dy = y;
 	}

@@ -590,7 +602,7 @@

 	for (i = 0; i < fb_logo_ex_num; i++)
 		y = fb_show_logo_line(info, rotate,
-				      fb_logo_ex[i].logo, y, fb_logo_ex[i].n);
+				      fb_logo_ex[i].logo, 0, y, fb_logo_ex[i].n);

 	return y;
 }
@@ -685,6 +697,23 @@
 	return fb_prepare_extra_logos(info, height, yres);
 }

+static int fb_show_logo_cpus_waifus(struct fb_info *info, int x, int y,
+				    int rotate, unsigned int cpus)
+{
+	unsigned int i;
+	unsigned int amnt;
+	unsigned int nlogos = ARRAY_SIZE(logo_cpuswaifus);
+
+	amnt = (cpus <= nlogos) ? cpus : nlogos;
+
+	for (i = 0; i < amnt; i++) {
+		y = fb_show_logo_line(info, rotate, logo_cpuswaifus[i], x, 0, 1);
+		x += logo_cpuswaifus[i]->width + 8;
+	}
+
+	return y;
+}
+
 int fb_show_logo(struct fb_info *info, int rotate)
 {
 	unsigned int count;
@@ -694,8 +723,13 @@
 		return 0;

 	count = fb_logo_count < 0 ? num_online_cpus() : fb_logo_count;
-	y = fb_show_logo_line(info, rotate, fb_logo.logo, 0, count);
+
+#ifdef CONFIG_LOGO_CPUSWAIFUS_CLUT224
+	y = fb_show_logo_cpus_waifus(info, 0, 0, rotate, count);
+#else
+	y = fb_show_logo_line(info, rotate, fb_logo.logo, 0, 0, count);
 	y = fb_show_extra_logos(info, y, rotate);
+#endif

 	return y;
 }

File: website/assets/src/boot-logo/core-fbmem.patch

⚠️ Warning: The patch above was made and tested only on kernel v5.15.19, but should work without issues on kernels prior to v6.7 (i.e., kernels before 2024-01-07). The reason for this is that from v6.7 onwards, a slight refactoring was done, and pieces of code were moved from fbmem.c to fb-logo.c (in the same folder) for better code organization.

However, the idea remains exactly the same, and therefore, I leave the possible adaptations, which are trivial, as an exercise for the reader 😁.

I should also point out that there may be small differences even in v5.X kernels. On another machine I have, which uses v5.4.186, there is a tiny difference in the fb_show_logo() function, so the patch does not apply cleanly. However, as mentioned, these are small changes that are simple to fix manually.

💡 Note: 03-May: A huge thanks to @CodeAsm who ported my patches to v6.14.4, so you certainly want to check it out his patch here.

Kernel build!

Once patched, just build the kernel and use it. The exact procedure may vary depending on your Linux distro, but the procedures below (for Slackware) should serve as a guide for any environment:

$ cd /usr/src/new-linux-XYZ

# Get your current .config and generate a new one based on yours
$ zcat /proc/config.gz > .config
$ make olddefconfig

# Build kernel
$ make -j$(nproc) bzImage

# Build and install modules
$ make -j4 modules && make modules_install

# Copy kernel to /boot and adjust symlinks
$ cp arch/x86/boot/bzImage /boot/vmlinuz-waifu-XYZ
$ cp System.map /boot/System.map-XYZ
$ cp .config /boot/config-XYZ
$ cd /boot
$ rm System.map
$ rm config
$ ln -s System.map-XYZ System.map
$ ln -s config-XYZ config

# Make a initrd with:
$ mkinitrd -c -k XYZ -f ext4 -r /dev/sdx1 -m ext4 -u -o /boot/initrd-waifu.gz

# Update your LILO
$ vim /etc/lilo.conf
...
image = /boot/vmlinuz-waifu-XYZ
  root = /dev/sdx1
  label = XYZ-waifu
  initrd = /boot/initrd-waifu.gz
  read-only
...

$ lilo
$ sudo reboot

!! BONUS !!

I was quite satisfied with the final result, and I didn’t want to make any more changes until... my post was removed from a certain subreddit for being considered 'fluff'.

Anyway, maybe they just don’t like anime girls? I don’t know, but the posts on my blog are mine, and here I can be as much of a weeb as I want.

Without further ado, I present to you animated boot logos:


Do you like it?

Yes!, you saw it correctly, now we have animation instead of boring static Tuxes.

The basic idea behind how it works is simple: create a kernel thread and draw the logos in a loop indefinitely with some sleep interval between the drawings, and voilà, we have animation.

However, things are never exactly as we expect, and I had two main issues that took me quite a while to understand:

  1. Drawing the logos produced kernel panic during the boot process, and it took me a long time to understand that for some reason the kernel was deallocating the memory portion related to the logo structures. It simply does not exist during the entire lifetime of the kernel, and because of that, I was having segfault at the kernel level.
  2. Even if the previous item was resolved, I also needed some way to keep my logos at the top of the screen. You might have noticed that by default the Tuxes disappear after a simple clear or running htop for example, I don't want that!

The solution for both points wasn't complicated:

  1. Instead of trying to locate where this happens in the kernel, a simpler solution: duplicate the structures in a dynamic memory portion that I control (i.e., kmalloc()+memcpy()), and this indeed solved the first problem.
  2. For the second point, enter DECOM, or DEC Origin Mode.

DEC Origin Mode

While I was reading the VT102 driver in the Linux kernel (drivers/tty/vt.c) to try to gain some insight about what could be done, I came across some curious things: some TTY routines (like gotoxy()) have some checks for vt_decom. If vt_decom was enabled, then conveniently the TTY considered a 'top' for calculating the Y-coordinates, that is, movements were relative to a margin, not 0-based, and bingo!, this is exactly what we're looking for.

💡 Note: DEC Origin Mode (DECOM) is a terminal control mode inherited from the old VT100 and VT102 terminals. When active, it redefines the cursor coordinate system to consider an upper limit (top margin) and, in some cases, lower limit (bottom margin), instead of treating the entire screen as an absolute grid. This allows operations such as cursor movement, scrolling, and line erasure to be performed only within a delimited region, useful for applications that need to preserve headers or footers. In the Linux kernel, this behavior is controlled by the vt_decom flag!

This 'top', in turn, is conveniently calculated by the fbcon_prepare_logo() routine (drivers/video/fbdev/fbcon.c) and in fact the top is considered by the TTY when performing scrolls, otherwise, the Tuxes at the top wouldn't make sense, right? However, there were two issues: a) the "decom" mode wasn't enabled by default, b) even if it was, a "tty reset" disables the mode and clears the previously defined top coordinates.

The challenge then became to investigate the kernel source for: a) places where DECOM mode was forcefully disabled (such as on TTY reset!), b) places where the top could be reset. Once these points were identified, it was then possible to make my images remain at the top indefinitely without interfering with other applications.

New kernel patch!

The patch below, again, was made and tested on kernel v5.15.19, and can be applied without issues up to the most recent LTS: v5.15.181 (2025-05-02). For newer or older versions, please make the necessary adjustments.

To apply it, just do:

$ cd /usr/src/linux-5.15.181
$ patch -p1 < /path/to/patch-animated.patch
patching file drivers/tty/vt/vt.c
patching file drivers/video/fbdev/core/fbcon.c
Hunk #1 succeeded at 1260 (offset -3 lines).
Hunk #2 succeeded at 2082 (offset 38 lines).
Hunk #3 succeeded at 2191 (offset 38 lines).
patching file drivers/video/fbdev/core/fbmem.c
Hunk #1 succeeded at 38 (offset 2 lines).
Hunk #2 succeeded at 192 (offset 2 lines).
Hunk #3 succeeded at 492 (offset 2 lines).
Hunk #4 succeeded at 552 with fuzz 1 (offset 2 lines).
Hunk #5 succeeded at 626 (offset 2 lines).
Hunk #6 succeeded at 721 (offset 2 lines).
Hunk #7 succeeded at 880 (offset 2 lines).
patching file drivers/video/logo/Kconfig
patching file drivers/video/logo/Makefile
patching file include/linux/fb.h
Hunk #2 succeeded at 625 (offset 12 lines).

Patch:

diff --git a/drivers/tty/vt/vt.c b/drivers/tty/vt/vt.c
index 7359c3e..5c206ab 100644
--- a/drivers/tty/vt/vt.c
+++ b/drivers/tty/vt/vt.c
@@ -1322,8 +1322,13 @@ static int vc_do_resize(struct tty_struct *tty, struct vc_data *vc,
 	kfree(oldscreen);
 
 	/* do part of a reset_terminal() */
-	vc->vc_top = 0;
-	vc->vc_bottom = vc->vc_rows;
+
+	/* only reset the top if not built with waifu mode */
+	if (!vc->vc_decom) {
+		vc->vc_top = 0;
+		vc->vc_bottom = vc->vc_rows;
+	}
+
 	gotoxy(vc, vc->state.x, vc->state.y);
 	save_cur(vc);
 
@@ -1909,8 +1914,12 @@ static void set_mode(struct vc_data *vc, int on_off)
 				}
 				break;
 			case 6:			/* Origin relative/absolute */
-				vc->vc_decom = on_off;
-				gotoxay(vc, 0, 0);
+				/* Only allow changes on DEC Mode if
+				 * not using our special build =). */
+				if (!vc->vc_decom) {
+					vc->vc_decom = on_off;
+					gotoxay(vc, 0, 0);
+				}
 				break;
 			case 7:			/* Autowrap on/off */
 				vc->vc_decawm = on_off;
@@ -2083,8 +2092,12 @@ static void reset_terminal(struct vc_data *vc, int do_clear)
 {
 	unsigned int i;
 
-	vc->vc_top		= 0;
-	vc->vc_bottom		= vc->vc_rows;
+	/* Only change top if not in DEC Mode. */
+	if (!vc->vc_decom) {
+		vc->vc_top	  = 0;
+		vc->vc_bottom = vc->vc_rows;
+	}
+
 	vc->vc_state		= ESnormal;
 	vc->vc_priv		= EPecma;
 	vc->vc_translate	= set_translate(LAT1_MAP, vc);
@@ -2100,7 +2113,10 @@ static void reset_terminal(struct vc_data *vc, int do_clear)
 	vc->vc_toggle_meta	= 0;
 
 	vc->vc_decscnm		= 0;
-	vc->vc_decom		= 0;
+
+	/* Do *not* reset decom.
+	vc->vc_decom		= 0;  */
+
 	vc->vc_decawm		= 1;
 	vc->vc_deccm		= global_cursor_default;
 	vc->vc_decim		= 0;
@@ -2492,7 +2508,10 @@ static void do_con_trol(struct tty_struct *tty, struct vc_data *vc, int c)
 			/* Minimum allowed region is 2 lines */
 			if (vc->vc_par[0] < vc->vc_par[1] &&
 			    vc->vc_par[1] <= vc->vc_rows) {
-				vc->vc_top = vc->vc_par[0] - 1;
+
+				if (!vc->vc_decom)
+					vc->vc_top = vc->vc_par[0] - 1;
+
 				vc->vc_bottom = vc->vc_par[1];
 				gotoxay(vc, 0, 0);
 			}
diff --git a/drivers/video/fbdev/core/fbcon.c b/drivers/video/fbdev/core/fbcon.c
index 22bb389..99a328a 100644
--- a/drivers/video/fbdev/core/fbcon.c
+++ b/drivers/video/fbdev/core/fbcon.c
@@ -1263,7 +1263,9 @@ static void fbcon_clear(struct vc_data *vc, int sy, int sx, int height,
 		return;
 
 	if (sy < vc->vc_top && vc->vc_top == logo_lines) {
-		vc->vc_top = 0;
+		if (!vc->vc_decom)
+			vc->vc_top = 0;
+
 		/*
 		 * If the font dimensions are not an integral of the display
 		 * dimensions then the ops->clear below won't end up clearing
@@ -2042,13 +2044,15 @@ static int fbcon_switch(struct vc_data *vc)
 	info = registered_fb[con2fb_map[vc->vc_num]];
 	ops = info->fbcon_par;
 
-	if (logo_shown >= 0) {
-		struct vc_data *conp2 = vc_cons[logo_shown].d;
+	if (!vc->vc_decom) {
+		if (logo_shown >= 0) {
+			struct vc_data *conp2 = vc_cons[logo_shown].d;
 
-		if (conp2->vc_top == logo_lines
-		    && conp2->vc_bottom == conp2->vc_rows)
-			conp2->vc_top = 0;
-		logo_shown = FBCON_LOGO_CANSHOW;
+			if (conp2->vc_top == logo_lines
+			    && conp2->vc_bottom == conp2->vc_rows)
+				conp2->vc_top = 0;
+			logo_shown = FBCON_LOGO_CANSHOW;
+		}
 	}
 
 	prev_console = ops->currcon;
@@ -2149,11 +2153,18 @@ static int fbcon_switch(struct vc_data *vc)
 
 		logo_shown = fg_console;
 		/* This is protected above by initmem_freed */
-		fb_show_logo(info, ops->rotate);
+		fb_show_logo(info, ops->rotate, vc);
 		update_region(vc,
 			      vc->vc_origin + vc->vc_size_row * vc->vc_top,
 			      vc->vc_size_row * (vc->vc_bottom -
 						 vc->vc_top) / 2);
+		/*
+		 * Enable DEC Origin Mode, so our top remains reserved
+		 * for our logo.
+		 */
+#ifdef CONFIG_LOGO_CPUSWAIFUS_CLUT224
+		vc->vc_decom = 1;
+#endif
 		return 0;
 	}
 	return 1;
diff --git a/drivers/video/fbdev/core/fbmem.c b/drivers/video/fbdev/core/fbmem.c
index 7bd5e2a..eaa8130 100644
--- a/drivers/video/fbdev/core/fbmem.c
+++ b/drivers/video/fbdev/core/fbmem.c
@@ -36,6 +36,10 @@
 #include <linux/mem_encrypt.h>
 #include <linux/pci.h>
 
+#include <linux/vt_kern.h>
+#include <linux/kthread.h>
+#include <linux/delay.h>
+
 #include <asm/fb.h>
 
 
@@ -186,6 +190,36 @@ EXPORT_SYMBOL(fb_get_buffer_offset);
 
 #ifdef CONFIG_LOGO
 
+extern const struct linux_logo logo_frm0_clut224;
+extern const struct linux_logo logo_frm1_clut224;
+extern const struct linux_logo logo_frm2_clut224;
+extern const struct linux_logo logo_frm3_clut224;
+
+extern const struct linux_logo logo_frm4_clut224;
+extern const struct linux_logo logo_frm5_clut224;
+extern const struct linux_logo logo_frm6_clut224;
+extern const struct linux_logo logo_frm7_clut224;
+
+extern const struct linux_logo logo_frm8_clut224;
+extern const struct linux_logo logo_frm9_clut224;
+extern const struct linux_logo logo_frm10_clut224;
+extern const struct linux_logo logo_frm11_clut224;
+
+extern const struct linux_logo logo_frm12_clut224;
+extern const struct linux_logo logo_frm13_clut224;
+extern const struct linux_logo logo_frm14_clut224;
+extern const struct linux_logo logo_frm15_clut224;
+
+static const struct linux_logo *logo_orig_waifu_frames[] = {
+&logo_frm0_clut224,  &logo_frm1_clut224,  &logo_frm2_clut224,  &logo_frm3_clut224,
+&logo_frm4_clut224,  &logo_frm5_clut224,  &logo_frm6_clut224,  &logo_frm7_clut224,
+&logo_frm8_clut224,  &logo_frm9_clut224,  &logo_frm10_clut224, &logo_frm11_clut224,
+&logo_frm12_clut224, &logo_frm13_clut224, &logo_frm14_clut224, &logo_frm15_clut224,
+};
+
+static struct linux_logo waifu_frames_fixed[16];
+
+
 static inline unsigned safe_shift(unsigned d, int n)
 {
 	return n < 0 ? d >> -n : d << n;
@@ -456,7 +490,7 @@ static void fb_do_show_logo(struct fb_info *info, struct fb_image *image,
 }
 
 static int fb_show_logo_line(struct fb_info *info, int rotate,
-			     const struct linux_logo *logo, int y,
+			     const struct linux_logo *logo, int x, int y,
 			     unsigned int n)
 {
 	u32 *palette = NULL, *saved_pseudo_palette = NULL;
@@ -516,7 +550,7 @@ static int fb_show_logo_line(struct fb_info *info, int rotate,
 		image.dx = (xres - n * (logo->width + 8) - 8) / 2;
 		image.dy = y ?: (yres - logo->height) / 2;
 	} else {
-		image.dx = 0;
+		image.dx = x;
 		image.dy = y;
 	}
 
@@ -590,7 +624,7 @@ static int fb_show_extra_logos(struct fb_info *info, int y, int rotate)
 
 	for (i = 0; i < fb_logo_ex_num; i++)
 		y = fb_show_logo_line(info, rotate,
-				      fb_logo_ex[i].logo, y, fb_logo_ex[i].n);
+				      fb_logo_ex[i].logo, 0, y, fb_logo_ex[i].n);
 
 	return y;
 }
@@ -685,7 +719,157 @@ int fb_prepare_logo(struct fb_info *info, int rotate)
 	return fb_prepare_extra_logos(info, height, yres);
 }
 
-int fb_show_logo(struct fb_info *info, int rotate)
+struct kthr_waifu_info {
+	struct task_struct *thread;
+	struct fb_info *info;
+	struct vc_data *vc;
+	int ncpus;
+	int rotate;
+	int x;
+} wi = {0};
+
+static void* dup_linux_logo(
+	struct linux_logo *const logo_dst,
+	const struct linux_logo *const logo_src)
+{
+	unsigned char *clut;
+	unsigned char *data;
+	int csize;
+	int dsize;
+
+	csize = logo_src->clutsize * 3;
+	dsize = logo_src->width * logo_src->height;
+
+	logo_dst->type     = logo_src->type;
+	logo_dst->width    = logo_src->width;
+	logo_dst->height   = logo_src->height;
+	logo_dst->clutsize = logo_src->clutsize;
+	clut               = kmalloc(csize, GFP_KERNEL);
+	if (!clut) {
+		pr_info("dup_linux_logo: Unable to duplicate clut!\n");
+		return NULL;
+	}
+	data               = kmalloc(dsize, GFP_KERNEL);
+	if (!data) {
+		pr_info("dup_linux_logo: Unable to duplicate data!\n");
+		kfree(clut);
+		return NULL;
+	}
+
+	memcpy(clut, logo_src->clut, csize);
+	memcpy(data, logo_src->data, dsize);
+	logo_dst->clut = clut;
+	logo_dst->data = data;
+	return logo_dst;
+}
+
+static void free_linux_logo(const struct linux_logo *const logo)
+{
+	if (!logo)
+		return;
+	if (logo->clut)
+		kfree(logo->clut);
+	if (logo->data)
+		kfree(logo->data);
+}
+
+static void free_all_linux_logos(void)
+{
+	unsigned int nframes;
+	int i;
+
+	nframes = ARRAY_SIZE(logo_orig_waifu_frames);
+	for (i = 0; i < nframes; i++)
+		free_linux_logo(&waifu_frames_fixed[i]);
+}
+
+static int kthread_show_anim_waifu(void *unused)
+{
+	int i;
+	int x;
+	unsigned idx;
+	unsigned int nframes;
+	((void)unused);
+
+	nframes = ARRAY_SIZE(logo_orig_waifu_frames);
+
+	idx = 0;
+
+	allow_signal(SIGKILL);
+	pr_info("kthread_show_anim_waifu: started!!\n");
+
+	while (!kthread_should_stop()) {
+		if (signal_pending(wi.thread))
+            break;
+
+		/* Sleep for 500ms, you might want to configure this
+		 * if you think its too fast/slow/resource-intensive.
+		 */
+		msleep_interruptible(CONFIG_LOGO_CPUWAIFUS_DELAY);
+
+		x = wi.x;
+		for (i = 0; i < wi.ncpus; i++) {
+			fb_show_logo_line(
+				wi.info,
+				wi.rotate,
+				&waifu_frames_fixed[idx],
+				x,
+				0,     /* y. */
+				1);    /* 1 logo per time. */
+
+			x += waifu_frames_fixed[idx].width + 8;
+		}
+		idx = (idx + 1) % nframes;
+	}
+
+	wi.vc->vc_decom = 0;
+	wi.vc->vc_top   = 0;
+
+	free_all_linux_logos();
+
+	pr_info("kthread_show_anim_waifu: stopped!\n");
+	do_exit(0);
+	return 0;
+}
+
+static int fb_show_logo_cpus_waifus(struct fb_info *info, int x, int y,
+				    int rotate, unsigned int ncpus, struct vc_data *vc)
+{
+	unsigned int i;
+	unsigned int nframes;
+
+	nframes = ARRAY_SIZE(logo_orig_waifu_frames);
+
+	for (i = 0; i < nframes; i++) {
+		if (!dup_linux_logo(&waifu_frames_fixed[i],
+			logo_orig_waifu_frames[i])) {
+			break;
+		}
+	}
+
+	if (i != nframes) {
+		free_all_linux_logos();
+		return y;
+	}
+
+	wi.rotate = rotate;
+	wi.ncpus  = ncpus;
+	wi.info   = info;
+	wi.x      = x;
+	wi.vc     = vc;
+	wi.thread = kthread_run(kthread_show_anim_waifu, NULL,
+		"kthread_waifu");
+
+	if (wi.thread)
+		pr_info("fb_show_logo_cpus_waifus: kthread created!");
+	else
+		pr_info("fb_show_logo_cpus_waifus: kthread NOT created!");
+
+	return y;
+}
+
+
+int fb_show_logo(struct fb_info *info, int rotate, struct vc_data *vc)
 {
 	unsigned int count;
 	int y;
@@ -694,14 +878,19 @@ int fb_show_logo(struct fb_info *info, int rotate)
 		return 0;
 
 	count = fb_logo_count < 0 ? num_online_cpus() : fb_logo_count;
-	y = fb_show_logo_line(info, rotate, fb_logo.logo, 0, count);
+
+#ifdef CONFIG_LOGO_CPUSWAIFUS_CLUT224
+	y = fb_show_logo_cpus_waifus(info, 0, 0, rotate, count, vc);
+#else
+	y = fb_show_logo_line(info, rotate, fb_logo.logo, 0, 0, count);
 	y = fb_show_extra_logos(info, y, rotate);
+#endif
 
 	return y;
 }
 #else
 int fb_prepare_logo(struct fb_info *info, int rotate) { return 0; }
-int fb_show_logo(struct fb_info *info, int rotate) { return 0; }
+int fb_show_logo(struct fb_info *info, int rotate, struct vc_data *vc) { return 0; }
 #endif /* CONFIG_LOGO */
 EXPORT_SYMBOL(fb_prepare_logo);
 EXPORT_SYMBOL(fb_show_logo);
diff --git a/drivers/video/logo/Kconfig b/drivers/video/logo/Kconfig
index 6d6f8c0..11a20ee 100644
--- a/drivers/video/logo/Kconfig
+++ b/drivers/video/logo/Kconfig
@@ -28,6 +28,14 @@ config LOGO_LINUX_CLUT224
 	bool "Standard 224-color Linux logo"
 	default y
 
+config LOGO_CPUSWAIFUS_CLUT224
+	bool "Shows an animated image for each core"
+	default y
+
+config LOGO_CPUWAIFUS_DELAY
+	int "Delay (in ms) between each frame"
+	default 500
+
 config LOGO_DEC_CLUT224
 	bool "224-color Digital Equipment Corporation Linux logo"
 	depends on MACH_DECSTATION || ALPHA
diff --git a/drivers/video/logo/Makefile b/drivers/video/logo/Makefile
index 895c60b..60f98c9 100644
--- a/drivers/video/logo/Makefile
+++ b/drivers/video/logo/Makefile
@@ -13,6 +13,11 @@ obj-$(CONFIG_LOGO_SUN_CLUT224)		+= logo_sun_clut224.o
 obj-$(CONFIG_LOGO_SUPERH_MONO)		+= logo_superh_mono.o
 obj-$(CONFIG_LOGO_SUPERH_VGA16)		+= logo_superh_vga16.o
 obj-$(CONFIG_LOGO_SUPERH_CLUT224)	+= logo_superh_clut224.o
+obj-$(CONFIG_LOGO_CPUSWAIFUS_CLUT224) += \
+	logo_frm0_clut224.o  logo_frm1_clut224.o  logo_frm2_clut224.o  logo_frm3_clut224.o  \
+	logo_frm4_clut224.o  logo_frm5_clut224.o  logo_frm6_clut224.o  logo_frm7_clut224.o  \
+	logo_frm8_clut224.o  logo_frm9_clut224.o  logo_frm10_clut224.o logo_frm11_clut224.o \
+	logo_frm12_clut224.o logo_frm13_clut224.o logo_frm14_clut224.o logo_frm15_clut224.o
 
 obj-$(CONFIG_SPU_BASE)			+= logo_spe_clut224.o
 
diff --git a/include/linux/fb.h b/include/linux/fb.h
index 02f362c..3700dd3 100644
--- a/include/linux/fb.h
+++ b/include/linux/fb.h
@@ -8,6 +8,7 @@
 
 #define FBIO_CURSOR            _IOWR('F', 0x08, struct fb_cursor_user)
 
+#include <linux/console.h>
 #include <linux/fs.h>
 #include <linux/init.h>
 #include <linux/workqueue.h>
@@ -612,7 +613,7 @@ extern int remove_conflicting_framebuffers(struct apertures_struct *a,
 					   const char *name, bool primary);
 extern bool is_firmware_framebuffer(struct apertures_struct *a);
 extern int fb_prepare_logo(struct fb_info *fb_info, int rotate);
-extern int fb_show_logo(struct fb_info *fb_info, int rotate);
+extern int fb_show_logo(struct fb_info *fb_info, int rotate, struct vc_data *vc);
 extern char* fb_get_buffer_offset(struct fb_info *info, struct fb_pixmap *buf, u32 size);
 extern void fb_pad_unaligned_buffer(u8 *dst, u32 d_pitch, u8 *src, u32 idx,
 				u32 height, u32 shift_high, u32 shift_low, u32 mod);

File: website/assets/src/boot-logo/patch-animated.patch

Sorry for the size, but this time the patch grew a bit more than I expected. The frame sequence (if you want to use Torako) can be downloaded here.

Final thoughts

I'm quite happy with the result, and the idea can be expanded to many other things, such as displaying a different logo depending on the CPU model, and any other things not defined at build time.

Realistically, I don't expect others to actually use this, but I had a lot of fun doing this, and I had to share. Things not always goes smooth, though:

not-so-scary

Image 2: not-so-scary kernel panic I had during my patch 😂 (click to enlarge)

but it's also part of the fun =).

This post is licensed under CC BY 4.0 by the author.