feat(boot): Add SUB-OS boot logo and splash, share the console font, and lift the kernel size ceiling

Boot logo (init/bootlogo.c, init/logo_data.c, scripts/mklogo.py):
  The logo appears in two forms because the framebuffer does not exist for the
  first third of boot. An ASCII rendition goes to the text console and serial
  line from the first moments; once the video driver is up at step [5/14] the
  real bitmap takes over as a graphical splash with a progress bar tied to the
  actual boot stages.

  The asset is 4-bit palette indexed, so 160x160 pixels cost 12.5 KB instead of
  100 KB, and carries a transparency mask so the mark composites onto the
  gradient rather than stamping a flat rectangle over it. scripts/mklogo.py
  regenerates init/logo_data.c from the source PNG.

  Boot reaches the desktop in well under a second, so the splash is held for a
  minimum duration and the bar sweeps to 100% during the hold; without that the
  logo would flash past unseen. `splashtime=<ms>` tunes it and `nosplash`,
  `nogui` or `text` skip it.

Bootloader (boot/stage2.asm):
  stage2 staged the whole kernel in conventional RAM before copying it to 1 MB,
  which capped the kernel at roughly 576 KB before the load ran into video
  memory at 0xA0000. The logo pushed it over that line and the kernel stopped
  booting. Each 32 KB chunk now goes into one fixed low buffer and is copied
  straight to its final address, using unreal mode for the 32-bit reach. The
  ceiling is now the image itself, 1.375 MB.

Console font (lib/font8x8.c):
  fb_draw_char was a stub that ignored its character argument and drew a filled
  rectangle, which is why the splash rendered its text as solid blocks. It now
  renders real glyphs, and the table it uses is shared: the framebuffer console,
  the canvas rasterizer and the desktop compositor had three divergent private
  copies, two of which still carried the `// \` line-continuation defect.

printk console sink (kernel/printk.c):
  printk output can be redirected to a callback, so a caller can run a subsystem
  that reports via printk and capture what it printed. Serial and the dmesg ring
  still see everything, which keeps a captured run debuggable.

Shell builtins (userland/lazybox/shell.c):
  The builtin chain is extracted into shell_execute_builtin(), so the kernel TTY
  and the desktop terminal accept the same vocabulary instead of the GUI seeing
  only the applet table. Callers that cannot be suspended pass allow_blocking =
  false to refuse the animating ones.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01L5yKJT4Wo2cV3rfPrwY5MP
This commit is contained in:
subhobhai943andClaude Opus 5 committed 2026-08-25 01:37:20 +05:30
1 parent 280d7fa2bb
commit ba06264d12
15 files changed
+1711 -153

No files matched your search

+243
View File
@@ -0,0 +1,243 @@
// SUB-OS Boot Logo & Splash Screen
//
// Two presentations of the same artwork, because the framebuffer only exists
// from step [5/14] onward:
//
// * boot_logo_print_text() -- ASCII rendition on the text console and the
// serial line, shown from the very first moments of the boot.
// * boot_logo_splash_*() -- the real bitmap on the framebuffer, with a
// progress bar, once the video driver is up.
//
// The bitmap is 4-bit palette indexed (init/logo_data.c, generated by
// scripts/mklogo.py), so 160x160 pixels cost 12.5 KB rather than 100 KB.
#include <init/bootlogo.h>
#include <init/version.h>
#include <drivers/fb.h>
#include <kernel/printk.h>
#include <lib/string.h>
#include <lib/printf.h>
#include <init/init.h>
#include <arch/arch.h>
#define SPLASH_BG_TOP 0xFF0B132B
#define SPLASH_BG_BOTTOM 0xFF1C2541
#define SPLASH_ACCENT 0xFF38BDF8
#define SPLASH_ACCENT_LO 0xFF0284C7
#define SPLASH_TEXT 0xFFF8FAFC
#define SPLASH_MUTED 0xFF94A3B8
#define SPLASH_TRACK 0xFF1E293B
#define SPLASH_BORDER 0xFF475569
// Boot reaches the desktop within a few hundred milliseconds, so without a
// floor the logo would flash past unseen. Hold it briefly and animate the bar
// to completion during the hold, which also gives the splash a natural finish.
#define SPLASH_MIN_MS 1800
#define SPLASH_FRAME_MS 20
static bool g_splash_active = false;
static uint64_t g_splash_start_ticks = 0;
static uint64_t g_splash_hold_ticks = 0;
static int g_logo_x = 0, g_logo_y = 0, g_logo_scale = 1;
static int g_bar_x = 0, g_bar_y = 0, g_bar_w = 0;
static int g_status_y = 0;
static int g_last_percent = -1;
// ---------------------------------------------------------------------------
// Text console
// ---------------------------------------------------------------------------
void boot_logo_print_text(void) {
printk("\n");
for (int i = 0; boot_logo_ascii[i] != NULL; i++) {
printk(ANSI_BRIGHT_CYAN " %s\n" ANSI_RESET, boot_logo_ascii[i]);
}
printk("\n");
printk(ANSI_BRIGHT_WHITE " S U B - O S\n" ANSI_RESET);
printk(ANSI_BRIGHT_BLACK " Modular Monolithic Kernel %s\n" ANSI_RESET,
SUBOS_VERSION_STRING);
printk("\n");
}
// ---------------------------------------------------------------------------
// Bitmap blitting
// ---------------------------------------------------------------------------
// Unpack one 4-bit index. Two pixels share a byte, high nibble first.
static inline uint8_t logo_index_at(int px, int py) {
int linear = py * boot_logo_width + px;
uint8_t byte = boot_logo_bitmap[linear >> 1];
return (linear & 1) ? (byte & 0x0F) : (byte >> 4);
}
void boot_logo_draw(int x, int y, int scale) {
if (scale < 1) scale = 1;
const fb_info_t* fb = fb_get_info();
if (!fb || !fb->active) return;
for (int py = 0; py < boot_logo_height; py++) {
for (int px = 0; px < boot_logo_width; px++) {
uint8_t index = logo_index_at(px, py);
// Leave the artwork's transparent surround alone so the logo sits
// on the gradient rather than inside a flat rectangle.
if (boot_logo_transparent_mask & (1u << index)) continue;
uint32_t color = boot_logo_palette[index];
if (scale == 1) {
fb_put_pixel((uint32_t)(x + px), (uint32_t)(y + py), color);
} else {
fb_draw_rect((uint32_t)(x + px * scale), (uint32_t)(y + py * scale),
(uint32_t)scale, (uint32_t)scale, color);
}
}
}
}
// ---------------------------------------------------------------------------
// Splash screen
// ---------------------------------------------------------------------------
static void draw_gradient(int width, int height) {
uint32_t top = SPLASH_BG_TOP, bottom = SPLASH_BG_BOTTOM;
int tr = (top >> 16) & 0xFF, tg = (top >> 8) & 0xFF, tb = top & 0xFF;
int br = (bottom >> 16) & 0xFF, bg = (bottom >> 8) & 0xFF, bb = bottom & 0xFF;
for (int y = 0; y < height; y++) {
int r = tr + ((br - tr) * y) / (height ? height : 1);
int g = tg + ((bg - tg) * y) / (height ? height : 1);
int b = tb + ((bb - tb) * y) / (height ? height : 1);
fb_draw_rect(0, (uint32_t)y, (uint32_t)width, 1,
0xFF000000u | ((uint32_t)r << 16) | ((uint32_t)g << 8) | (uint32_t)b);
}
}
static void draw_centered(int y, const char* text, uint32_t color, int screen_w) {
int width = (int)strlen(text) * 8;
fb_draw_string((uint32_t)((screen_w - width) / 2), (uint32_t)y, text, color, 0);
}
bool boot_logo_splash_begin(void) {
const fb_info_t* fb = fb_get_info();
if (!fb || !fb->active || !fb->address || fb->width == 0 || fb->height == 0) {
return false;
}
int sw = (int)fb->width;
int sh = (int)fb->height;
// Double the logo when the panel is tall enough to carry it; otherwise the
// splash would crowd out the progress bar.
g_logo_scale = (sh >= 560 && sw >= 640) ? 2 : 1;
int logo_px = boot_logo_width * g_logo_scale;
g_logo_x = (sw - logo_px) / 2;
g_logo_y = (sh - logo_px) / 2 - 60;
if (g_logo_y < 12) g_logo_y = 12;
g_bar_w = (sw * 45) / 100;
if (g_bar_w > 420) g_bar_w = 420;
g_bar_x = (sw - g_bar_w) / 2;
g_bar_y = g_logo_y + logo_px + 56;
g_status_y = g_bar_y + 26;
draw_gradient(sw, sh);
boot_logo_draw(g_logo_x, g_logo_y, g_logo_scale);
draw_centered(g_logo_y + logo_px + 18, "S U B - O S", SPLASH_TEXT, sw);
draw_centered(g_logo_y + logo_px + 34, SUBOS_VERSION_STRING, SPLASH_MUTED, sw);
// Progress track
fb_draw_rect((uint32_t)g_bar_x, (uint32_t)g_bar_y, (uint32_t)g_bar_w, 12, SPLASH_TRACK);
fb_draw_rect((uint32_t)g_bar_x, (uint32_t)g_bar_y, (uint32_t)g_bar_w, 1, SPLASH_BORDER);
fb_draw_rect((uint32_t)g_bar_x, (uint32_t)(g_bar_y + 11), (uint32_t)g_bar_w, 1, SPLASH_BORDER);
fb_draw_rect((uint32_t)g_bar_x, (uint32_t)g_bar_y, 1, 12, SPLASH_BORDER);
fb_draw_rect((uint32_t)(g_bar_x + g_bar_w - 1), (uint32_t)g_bar_y, 1, 12, SPLASH_BORDER);
draw_centered(sh - 20, "Modular Monolithic Kernel", 0xFF64748B, sw);
g_splash_active = true;
g_last_percent = -1;
g_splash_start_ticks = pit_get_ticks();
// The PIT runs at 100 Hz, so one tick is 10 ms.
uint32_t hold_ms = SPLASH_MIN_MS;
const char* param = init_get_param("splashtime");
if (param) {
int requested = atoi(param);
if (requested >= 0 && requested <= 10000) hold_ms = (uint32_t)requested;
}
g_splash_hold_ticks = hold_ms / 10;
boot_logo_splash_progress(0, "Starting kernel subsystems");
return true;
}
void boot_logo_splash_progress(int percent, const char* status) {
if (!g_splash_active) return;
const fb_info_t* fb = fb_get_info();
if (!fb || !fb->active) return;
if (percent < 0) percent = 0;
if (percent > 100) percent = 100;
// The bar only ever grows, so repaint just the newly filled span.
int filled = ((g_bar_w - 2) * percent) / 100;
if (percent > g_last_percent) {
fb_draw_rect((uint32_t)(g_bar_x + 1), (uint32_t)(g_bar_y + 1),
(uint32_t)filled, 10, SPLASH_ACCENT);
// A darker leading edge gives the fill some depth.
if (filled > 2) {
fb_draw_rect((uint32_t)(g_bar_x + 1), (uint32_t)(g_bar_y + 8),
(uint32_t)filled, 3, SPLASH_ACCENT_LO);
}
g_last_percent = percent;
}
if (status) {
int sw = (int)fb->width;
// Clear the previous status before writing the new one.
fb_draw_rect(0, (uint32_t)g_status_y, (uint32_t)sw, 10, SPLASH_BG_BOTTOM);
char line[80];
snprintf(line, sizeof(line), "%s ... %d%%", status, percent);
draw_centered(g_status_y, line, SPLASH_MUTED, sw);
}
}
void boot_logo_splash_end(void) {
if (!g_splash_active) return;
// Sweep whatever is left of the bar to 100% while waiting out the hold, so
// a fast boot still shows a complete progress animation rather than a jump.
uint64_t deadline = g_splash_start_ticks + g_splash_hold_ticks;
int start_percent = (g_last_percent < 0) ? 0 : g_last_percent;
while (pit_get_ticks() < deadline) {
uint64_t now = pit_get_ticks();
uint64_t elapsed = now - g_splash_start_ticks;
int percent = start_percent;
if (g_splash_hold_ticks > 0) {
int span = 100 - start_percent;
percent = start_percent + (int)((elapsed * (uint64_t)span) / g_splash_hold_ticks);
}
if (percent > 100) percent = 100;
boot_logo_splash_progress(percent, "Starting the desktop environment");
pit_sleep(SPLASH_FRAME_MS);
}
boot_logo_splash_progress(100, "Ready");
g_splash_active = false;
g_last_percent = -1;
}
bool boot_logo_splash_active(void) {
return g_splash_active;
}