Files
Arch-R/scripts/archr-init.c
Douglas Teles e4294ab47b Two-image build pipeline, clone support, initramfs splash
- Clone variant: mainline U-Boot v2025.10, boot.scr, clone DTS (type5)
- Initramfs splash: archr-init.c with embedded BMP, sub-second display
- Panel wizard: auto-detect with fsync persistence, evdev X-button reset
- Hotkeys: adc-keys volume for clone, HP jack detection, brightness persist
- Boot.ini: ASCII-only (no Unicode), GPIO check removed
- ES wrapper: removed framebuffer blanking (preserves splash)
- Build pipeline: both variants, splash generation, panel DTBOs
- .gitignore: exclude large bootloader trees and build artifacts

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 22:38:47 -03:00

326 lines
9.3 KiB
C

/*
* archr-init — initramfs /init for Arch R
*
* Shows splash on /dev/fb0 IMMEDIATELY after kernel boot (before systemd).
* Splash image is EMBEDDED in the binary (no file I/O needed).
* Then mounts the real root filesystem and switch_root to /sbin/init.
*
* Initramfs contents:
* /init → this binary (static aarch64, includes splash data)
* /dev/ → empty (devtmpfs mounted here)
* /proc/ → empty (proc mounted temporarily)
* /newroot/ → empty (real root mounted here)
*
* Build:
* xxd -i splash.bmp > splash_data.h
* aarch64-linux-gnu-gcc -static -O2 -o archr-init archr-init.c
*/
#include <string.h>
#include <stdint.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/mount.h>
#include <sys/stat.h>
#include <linux/fb.h>
#include <errno.h>
#include <time.h>
/* Embedded splash image (generated by xxd -i splash.bmp) */
#include "splash_data.h"
/* ---- Logging (dmesg + in-memory buffer, flushed to file after root mount) ---- */
static int kmsg_fd = -1;
#define LOG_BUF_SIZE 4096
static char log_buf[LOG_BUF_SIZE];
static int log_pos = 0;
/* Simple snprintf — avoid pulling in full stdio */
static int fmt_int(char *buf, int val)
{
if (val < 0) { *buf++ = '-'; val = -val; return 1 + fmt_int(buf, val); }
if (val >= 10) { int n = fmt_int(buf, val / 10); return n + fmt_int(buf + n, val % 10); }
*buf = '0' + val;
return 1;
}
static void klog(const char *msg)
{
/* Save msg pointer before we iterate */
const char *saved_msg = msg;
/* Write to dmesg */
if (kmsg_fd < 0)
kmsg_fd = open("/dev/kmsg", O_WRONLY);
if (kmsg_fd >= 0) {
char buf[256];
int i = 0;
const char *prefix = "archr-init: ";
while (*prefix) buf[i++] = *prefix++;
while (*msg && i < 250) buf[i++] = *msg++;
buf[i++] = '\n';
write(kmsg_fd, buf, i);
}
/* Buffer for file log (using saved pointer) */
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
if (log_pos < LOG_BUF_SIZE - 200) {
log_pos += fmt_int(log_buf + log_pos, (int)ts.tv_sec);
log_buf[log_pos++] = '.';
int ms = (int)(ts.tv_nsec / 1000000);
if (ms < 100) log_buf[log_pos++] = '0';
if (ms < 10) log_buf[log_pos++] = '0';
log_pos += fmt_int(log_buf + log_pos, ms);
log_buf[log_pos++] = ' ';
while (*saved_msg && log_pos < LOG_BUF_SIZE - 2)
log_buf[log_pos++] = *saved_msg++;
log_buf[log_pos++] = '\n';
}
}
static void klog_num(const char *prefix, int val)
{
char buf[128];
int i = 0;
while (*prefix && i < 100) buf[i++] = *prefix++;
i += fmt_int(buf + i, val);
buf[i] = 0;
klog(buf);
}
static void flush_log(const char *path)
{
int fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (fd >= 0) {
write(fd, log_buf, log_pos);
close(fd);
}
}
/* ---- BMP parsing (from embedded data) ---- */
#pragma pack(push, 1)
typedef struct {
uint16_t type;
uint32_t size;
uint16_t reserved1;
uint16_t reserved2;
uint32_t offset;
} BMPFileHeader;
typedef struct {
uint32_t size;
int32_t width;
int32_t height;
uint16_t planes;
uint16_t bpp;
uint32_t compression;
uint32_t image_size;
int32_t x_ppm;
int32_t y_ppm;
uint32_t colors_used;
uint32_t colors_important;
} BMPInfoHeader;
#pragma pack(pop)
/* ---- Splash display (writes embedded BMP to /dev/fb0) ---- */
static void show_splash(void)
{
/* Validate embedded BMP data */
/* splash_data.h defines: unsigned char splash_bmp[] and unsigned int splash_bmp_len */
extern unsigned char splash_bmp[];
extern unsigned int splash_bmp_len;
if (splash_bmp_len < sizeof(BMPFileHeader) + sizeof(BMPInfoHeader)) {
klog("splash: embedded data too small");
return;
}
BMPFileHeader *fh = (BMPFileHeader *)splash_bmp;
BMPInfoHeader *ih = (BMPInfoHeader *)(splash_bmp + sizeof(BMPFileHeader));
if (fh->type != 0x4D42 || (ih->bpp != 24 && ih->bpp != 32)) {
klog("splash: invalid BMP header");
return;
}
int32_t bmp_w = ih->width;
int32_t bmp_h = ih->height > 0 ? ih->height : -ih->height;
int bottom_up = ih->height > 0;
uint32_t src_bpp = ih->bpp / 8;
uint32_t src_row_padded = (bmp_w * src_bpp + 3) & ~3;
uint8_t *pixel_data = splash_bmp + fh->offset;
klog("splash: BMP parsed from embedded data");
/* Wait for fb0 (DRM probe might still be finishing) */
int fb_fd = -1;
int retries;
for (retries = 0; retries < 30; retries++) {
fb_fd = open("/dev/fb0", O_RDWR);
if (fb_fd >= 0) break;
usleep(100000); /* 100ms, max 3s */
}
if (fb_fd < 0) {
klog("splash: fb0 NOT FOUND after 3s");
return;
}
klog_num("splash: fb0 opened, retries=", retries);
struct fb_var_screeninfo vinfo;
struct fb_fix_screeninfo finfo;
if (ioctl(fb_fd, FBIOGET_VSCREENINFO, &vinfo) < 0 ||
ioctl(fb_fd, FBIOGET_FSCREENINFO, &finfo) < 0) {
klog("splash: ioctl failed");
close(fb_fd);
return;
}
uint32_t fb_w = vinfo.xres;
uint32_t fb_h = vinfo.yres;
uint32_t fb_bpp_bytes = vinfo.bits_per_pixel / 8;
uint32_t stride = finfo.line_length;
uint32_t fb_size = stride * fb_h;
klog_num("splash: fb width=", (int)fb_w);
klog_num("splash: fb height=", (int)fb_h);
klog_num("splash: fb bpp=", (int)vinfo.bits_per_pixel);
/* Write directly to fb0 — row by row to avoid large malloc */
/* First: clear the framebuffer (black) */
{
uint8_t zero[2560]; /* enough for 640px * 4bpp */
memset(zero, 0, sizeof(zero));
lseek(fb_fd, 0, SEEK_SET);
for (uint32_t y = 0; y < fb_h; y++)
write(fb_fd, zero, stride < sizeof(zero) ? stride : sizeof(zero));
}
/* Then: write BMP pixels */
uint32_t copy_w = (uint32_t)bmp_w < fb_w ? (uint32_t)bmp_w : fb_w;
uint32_t copy_h = (uint32_t)bmp_h < fb_h ? (uint32_t)bmp_h : fb_h;
uint8_t row_buf[2560]; /* enough for 640px * 4bpp */
for (uint32_t y = 0; y < copy_h; y++) {
uint32_t src_y = bottom_up ? (bmp_h - 1 - y) : y;
uint8_t *src_row = pixel_data + src_y * src_row_padded;
memset(row_buf, 0, stride < sizeof(row_buf) ? stride : sizeof(row_buf));
for (uint32_t x = 0; x < copy_w; x++) {
uint8_t b = src_row[x * src_bpp + 0];
uint8_t g = src_row[x * src_bpp + 1];
uint8_t r = src_row[x * src_bpp + 2];
if (fb_bpp_bytes == 4) {
row_buf[x * 4 + 0] = b;
row_buf[x * 4 + 1] = g;
row_buf[x * 4 + 2] = r;
row_buf[x * 4 + 3] = 0xFF;
} else if (fb_bpp_bytes == 2) {
uint16_t c = ((r >> 3) << 11) | ((g >> 2) << 5) | (b >> 3);
row_buf[x * 2 + 0] = c & 0xFF;
row_buf[x * 2 + 1] = (c >> 8) & 0xFF;
}
}
lseek(fb_fd, y * stride, SEEK_SET);
write(fb_fd, row_buf, stride < sizeof(row_buf) ? stride : sizeof(row_buf));
}
klog("splash: written to fb0");
close(fb_fd);
}
/* ---- Main: initramfs /init ---- */
int main(int argc, char *argv[])
{
/* 1. Mount devtmpfs on /dev (kernel creates device nodes here) */
mkdir("/dev", 0755);
mount("devtmpfs", "/dev", "devtmpfs", 0, NULL);
klog("=== INITRAMFS STARTED ===");
/* 2. Show splash from embedded data — no file I/O needed */
show_splash();
/* 3. Parse root= and rw from kernel cmdline */
mkdir("/proc", 0755);
mount("proc", "/proc", "proc", 0, NULL);
char cmdline[4096];
memset(cmdline, 0, sizeof(cmdline));
int fd = open("/proc/cmdline", O_RDONLY);
if (fd >= 0) {
read(fd, cmdline, sizeof(cmdline) - 1);
close(fd);
}
umount("/proc");
/* Extract root= device path */
char rootdev[256] = "/dev/mmcblk1p2"; /* default: original R36S */
char *p = strstr(cmdline, "root=");
if (p) {
p += 5;
int i = 0;
while (*p && *p != ' ' && i < 255)
rootdev[i++] = *p++;
rootdev[i] = 0;
}
/* Check rw flag */
unsigned long mflags = MS_NOATIME;
if (strstr(cmdline, " rw"))
mflags |= 0; /* rw is default without MS_RDONLY */
else
mflags |= MS_RDONLY;
klog(rootdev);
/* 4. Mount real root (retry — MMC might still be probing) */
mkdir("/newroot", 0755);
int mounted = 0;
int mount_retries;
for (mount_retries = 0; mount_retries < 50; mount_retries++) {
if (mount(rootdev, "/newroot", "ext4", mflags, NULL) == 0) {
mounted = 1;
break;
}
usleep(100000); /* 100ms, max 5s total */
}
if (!mounted) {
klog("FATAL: could not mount root!");
for (;;) sleep(60);
}
klog_num("root mounted, retries=", mount_retries);
/* 5. Write diagnostic log to root filesystem */
klog("switch_root");
flush_log("/newroot/var/log/archr-init.log");
/* 6. Move /dev to new root */
mount("/dev", "/newroot/dev", NULL, MS_MOVE, NULL);
/* 7. switch_root: pivot to real rootfs and exec systemd */
if (kmsg_fd >= 0) close(kmsg_fd);
chdir("/newroot");
mount(".", "/", NULL, MS_MOVE, NULL);
chroot(".");
chdir("/");
execl("/sbin/init", "/sbin/init", NULL);
/* Should never reach here */
return 1;
}