mirror of
https://github.com/archr-linux/Arch-R.git
synced 2026-03-31 14:41:55 -07:00
- 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>
326 lines
9.3 KiB
C
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;
|
|
}
|