SchOS/include/gfx.h
Nexoniarz c80b7bf3b2 init: PID 1 becomes a role -- schinit splits, schinitd supervises
docs/plan/init.md milestones I0 and I1.

**The split.** schinit is PID 1 and does four things: bootstrap the
filesystems, fork schinitd, fork jobs on schinitd's behalf, reap children. No
manifest parsing, no filesystem walking after boot, no allocation. It is the
one program on the machine with no supervisor, so it is written to be too
boring to fail.

schinitd is PID 2 and holds everything else -- /etc/schinit, respawn policy,
throttling -- and is restartable, which is where the bugs are allowed to be.

**Jobs are forked by PID 1, not by schinitd.** That is the load-bearing
detail. schinitd decides and asks; PID 1 acts. So every job is a child of PID
1 at all times, and a supervisor that dies orphans nothing, reparents nothing
and restarts nothing.

**/proc/schinit** is the job table, written by PID 1 whenever it changes. The
kernel holds the text and does not interpret it: a procfs generator runs in
the kernel, and walking PID 1's memory to answer a read would put a userspace
program's format into the kernel, where it would rot.

Verified the way the plan asks for -- `kill 3`, then look:

    schinitd: adopting os.schos.console-shell
    schinitd: adopting os.schos.tty2-shell

/proc/schinit byte-identical across the restart, same pids, same cookies, same
start times, and the shell being typed into never noticed.

The first attempt got I1 wrong in the instructive way: PID 1 published the
table and schinitd never read it back, so the restarted supervisor started
everything a second time -- two console shells on one terminal, which is the
split-brain this plan rejects the two-init scheme for, reached by a different
road. Publishing state is half of it; coming back and believing it is the
other half.

The protocol is include/schinit.h: fixed-size messages, versioned in the
hello, entirely internal. Fixed-size so PID 1 never allocates, and a
supervisor that has gone mad cannot make it run a parser.

Still to come: I2 (heartbeat both ways, so a hang is as detectable as a death)
and I3 (proc_reincarnate, the actual law change).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VQ7Qx8E1q1M1bbCq8QLHS2
2026-09-13 13:57:27 +02:00

85 lines
3.8 KiB
C

#pragma once
// Drawing, and the text console under it, on whatever display is bound.
//
// This was kernel/dev/vga.c, and every consumer called it by that name:
// panic.c drew the crash screen with vga_fill_rect, the console printed with
// vga_putc, /dev/fb0 handed out vga_fb_phys(). None of that code has anything
// to do with a VGA -- it is bitmap drawing over a linear framebuffer, and the
// only VGA-specific part was where the framebuffer came from.
//
// So the driver pushes its scanout in here (gfx_attach_scanout) and everything
// above draws through these calls. A second display driver becomes a second
// caller of one function rather than an edit to the crash screen. This is the
// same arrangement as include/netdev.h, for the same reason.
//
// Not the same thing as the display class in include/display.h. That is the
// compositor's interface -- acquire a back buffer, present a rectangle, wait
// for vblank -- and it is about *frames*. This is the kernel's own drawing
// surface, used by the console and the crash screen, which do not double
// buffer and must keep working when nothing else in the system does.
// A linear framebuffer, exactly as the hardware presents it.
struct fb_info {
volatile uchar *ptr; // kernel-mapped base, or 0 if there is none
uint64 phys; // physical base, for mmap and for page tables
uint64 size; // bytes, stride * height rather than w * h * bpp
uint32 stride; // bytes per row, which may exceed width * bpp/8
uint32 bpp;
uint32 width, height;
};
// A driver publishing the framebuffer it owns. Called once, early: the console
// draws long before any bus is enumerated, so whoever owns the display the
// loader left running has to say so before then.
void gfx_attach_scanout(const struct fb_info *);
// The scanout as attached, for the few callers that need the memory itself --
// /dev/fb0 maps it, and fork copies the mapping. Returns 0 if none.
const struct fb_info *gfx_scanout(void);
int gfx_ready(void);
void gfx_dims(int *w, int *h);
uint32 gfx_width(void);
uint32 gfx_height(void);
uint64 gfx_fb_phys(void);
uint64 gfx_fb_size(void);
uint32 gfx_fb_stride(void);
uint32 gfx_fb_bpp(void);
volatile uchar *gfx_fb_ptr(void);
// Pixels and shapes.
void gfx_draw_pixel(int x, int y, uint32 color);
void gfx_clear(uint32 color);
void gfx_fill_rect(int x, int y, int w, int h, uint32 color);
void gfx_fill_circle(int cx, int cy, int r, uint32 color);
void gfx_draw_logo(int x, int y, uint32 color, uint32 bg);
void gfx_logo_centered(int cy, uint32 color, uint32 bg);
// The built-in 8x16 font: glyphs at a position, and text that leaves the
// cursor alone. The crash screen lays itself out rather than printing, which
// is what needs the second one.
void gfx_draw_char(int x, int y, uchar c, uint32 color);
void gfx_text_at(int x, int y, const char *s, uint32 color);
int gfx_char_w(void);
int gfx_char_h(void);
// The text console: a cursor, scrolling, and the erase operations an ANSI
// escape parser needs. See kernel/core/console.c.
void gfx_putc(int c, uint32 color);
void gfx_puts(int x, int y, char *s, uint32 color);
void gfx_set_bg(uint32 color);
void gfx_screen_reset(void);
int gfx_cols(void);
int gfx_rows(void);
// For the text layer in kernel/dev/vt.c: one cell with its own colours, and
// a screen scroll that leaves the cursor alone.
void gfx_draw_cell(int col, int row, uchar glyph, uint32 fg, uint32 bg);
// A filled rectangle in pixels, with the per-pixel checks hoisted out.
void gfx_fill(int x, int y, int w, int h, uint32 color);
void gfx_scroll_up(uint32 bg);
void gfx_cursor_get(int *col, int *row);
void gfx_cursor_set(int col, int row);
void gfx_erase_display(int mode);
void gfx_erase_line(int mode);