SchOS/include/schboot.h
Nexoniarz 30a0d0fbed dbg: the boot menu can turn on remote debugging, in a debug build
schdbgd ships Disabled, and its manifest explains why: a debugger that can read
the process table should not run on a machine whose owner never asked for one.
That default is right and has one bad consequence -- **the machines that most
need debugging are the ones you cannot get a shell on to enable it.** A laptop
with no serial port that stops before it can draw has nowhere to say why, and
`schinitd enable` needs a prompt that does not exist.

So `make DEBUG=1` adds a sixth entry to the F8 menu. Picking it toggles;
booting carries the choice through SCHBOOT_DEBUG_FLAG; the kernel publishes
/proc/bootdebug; schinitd starts the one job that flag may un-disable, with
the manifest's Args now naming 0.0.0.0 -- a debugger reachable only from the
machine being debugged is no use when that machine is the one that will not
talk to you.

Four decisions, since this is a network-reachable debugger:

  compiled out, not hidden   A release build cannot set the flag because the
                             code that would set it is not in the binary. A
                             menu item that is present and refuses is an
                             invitation to make it not refuse.
  consent at the machine     Somebody standing at the keyboard during boot is
                             the strongest consent this system can ask for and
                             the weakest one that is any use on hardware that
                             will not boot.
  one job, named in schinitd Not a manifest key like EnableIfDebug: the general
                             version of "the boot menu starts things that ship
                             off" is worse than the specific one, and this way
                             what the flag can start is one readable line.
  the kernel only publishes  Nothing in the kernel behaves differently, so a
                             debug kernel and a release kernel are the same
                             kernel and a bug cannot be the flag's fault.

/proc/bootdebug is its own file rather than a second line in /proc/bootmode,
because bootmode's whole promise is that reading it is a string compare and
not a parser.

**Also: `tone` stopped lying.** On a machine with no sound card it reported
"the device will not take 16-bit samples", which sends you looking at the
format code instead of at the empty PCI slot -- /dev/dsp exists whether or not
hardware is behind it, and the ioctl is where the absence shows up. ENODEV is
now told apart from a format refusal.

Verified end to end: F8 -> item 6 -> boot -> schinitd says it is starting
schdbgd -> a client outside the VM connected to the forwarded port and ran
`dma`, getting the real arena dump back. The release build has no such entry
in the binary at all, and boots clean.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-24 15:05:23 +02:00

177 lines
9 KiB
C

// SPDX-License-Identifier: Apache-2.0
#pragma once
#ifndef SCHBOOT_H
#define SCHBOOT_H
// The loader/kernel hand-off, in one place.
//
// Included by kernel/arch/x86_64/entry.S and by boot/uefi/main.c, so the two
// cannot disagree about the magic or the layout. Defining it twice is exactly
// how a loader ends up scanning for bytes the kernel never wrote — the first
// attempt here searched for "SCH6" while the assembler had stored 0x53434836,
// which lands in the file as "6HCS".
//
// Keep this header assembler-safe: no typedefs, no structs outside the C
// guard, nothing entry.S cannot preprocess.
// Scanned for in the first 16 KB of the kernel image, 8-byte aligned, exactly
// as the BIOS loader scans for the multiboot magic. Followed by a little-endian
// 64-bit physical address: the kernel's 64-bit entry point.
//
// The values spell "SCH6" "4BOO" when stored little-endian, which is what
// makes them findable by eye in a hex dump of the image.
#define SCH64_MAGIC0 0x36484353 /* "SCH6" */
#define SCH64_MAGIC1 0x4f4f4234 /* "4BOO" */
// ---------------------------------------------------------------------------
// The hand-off block.
//
// One page of low memory that a loader fills in and the kernel reads. Every
// field below was previously #defined in whichever file happened to need it --
// five of them, across two loaders and two kernel files -- which is the same
// hazard as defining the magic twice, only quieter: nothing breaks until one
// copy moves.
//
// 0x30000 is clear of stage2 (loaded at 0x7E00 and well under 0xA000), the
// multiboot kernel-search buffer at 0x10000-0x14000, and the kernel's own load
// address at 0x100000.
//
// Both loaders write it, so a field's meaning must not depend on which one did.
// Where they genuinely differ -- the BIOS path knows what medium it booted
// from, UEFI does not -- the field is defined as "0 when unknown" rather than
// left for the reader to guess.
#define SCHBOOT_BASE 0x30000
// A VBE mode info block, 256 bytes. The UEFI loader fills the same four fields
// out of GOP rather than translating a structure it never saw, so the kernel
// cannot tell which firmware booted it.
#define SCHBOOT_VBE_INFO (SCHBOOT_BASE + 0x000)
#define SCHBOOT_VBE_STRIDE (SCHBOOT_VBE_INFO + 16) /* uint16, bytes/line */
#define SCHBOOT_VBE_WIDTH (SCHBOOT_VBE_INFO + 18) /* uint16 */
#define SCHBOOT_VBE_HEIGHT (SCHBOOT_VBE_INFO + 20) /* uint16 */
#define SCHBOOT_VBE_BPP (SCHBOOT_VBE_INFO + 25) /* uint8 */
#define SCHBOOT_VBE_LFB (SCHBOOT_VBE_INFO + 40) /* uint32, physical */
// Non-zero if the loader was asked for a verbose boot.
#define SCHBOOT_VERBOSE_FLAG (SCHBOOT_BASE + 0x100)
// What the system booted from. SCHBOOT_MEDIA_CD matters because the ISO path
// reads through ATAPI rather than ATA; 0 means "not stated", which is what the
// UEFI loader leaves, since it does not read the kernel through either.
#define SCHBOOT_MEDIA_FLAG (SCHBOOT_BASE + 0x101)
// Remote debugging, chosen at the boot menu.
//
// A byte, set by the loader when somebody picks it out of the F8 menu, read by
// the kernel and published as /proc/bootdebug so that schinit can act on it.
//
// **It exists because the machines that need debugging are the ones that
// cannot be debugged.** A laptop has no serial port, so a kernel that stops
// before it can draw has nowhere to say why; the answer is a debugger reachable
// over the network, and the answer to "when should that be listening" cannot be
// "always" -- schdbgd can read the process table, and a listener nobody asked
// for is not a thing to ship. Choosing it at the boot menu is a deliberate act
// by somebody standing at the machine, which is the strongest consent this
// system can ask for and the weakest one that is any use on hardware that will
// not boot.
//
// Zero on every ordinary boot, including every boot of a build made without
// DEBUG=1 -- where the menu entry does not exist and nothing can set it.
#define SCHBOOT_DEBUG_FLAG (SCHBOOT_BASE + 0x102)
#define SCHBOOT_MEDIA_CD 1
// Real-mode spelling of the same byte, for cdboot.S which has no 32-bit
// addressing yet. Segment and offset, not a flat address.
#define SCHBOOT_MEDIA_SEG 0x3000
#define SCHBOOT_MEDIA_OFF 0x101
// The E820 memory map: a uint32 count, then that many 24-byte entries. Capped
// at 32 so a pathological firmware cannot run past the buffer; the map ends at
// 0x508, which is what leaves 0x600 free below.
#define SCHBOOT_E820_COUNT (SCHBOOT_BASE + 0x200)
#define SCHBOOT_E820_MAP (SCHBOOT_BASE + 0x208)
/* 40, not 32. The map lives at SCHBOOT_E820_MAP (BASE+0x208) and the next
* field is SCHBOOT_RSDP_ADDR at BASE+0x600, so there are 1016 bytes and each
* entry is 24 -- 42 fit. Real UEFI firmware produces far more descriptors than
* QEMU's, and the headroom costs nothing that was being used. */
#define SCHBOOT_E820_MAX 40
// Where the loader leaves the ACPI RSDP, as a little-endian 64-bit physical
// address. Zero means "not provided, go and look for it yourself".
//
// The kernel's own search scans the EBDA and 0xE0000-0x100000, which is a BIOS
// convention. UEFI publishes the RSDP in its configuration table instead and is
// under no obligation to leave a copy anywhere in particular; OVMF does not.
// Without it the kernel finds no MADT, so no IOAPIC, so no interrupt routing --
// the machine boots to a prompt on polled serial output and then ignores every
// key, which looks like a broken console rather than a missing table.
//
#define SCHBOOT_RSDP_ADDR (SCHBOOT_BASE + 0x600)
// A console typeface, read off the boot volume by the loader.
//
// Zero length means "there was none" and the kernel uses the one linked into
// its own image. That fallback is not a degraded mode: an early panic has to
// be readable, and the only font guaranteed to exist before any filesystem
// does is the one in the image.
//
// **Loaded by the loader rather than by the kernel**, which is the whole
// design decision here. The kernel could read the file itself once the root
// filesystem is mounted -- but the console is drawing before that, so
// switching then means re-measuring the cell and re-laying out every terminal
// mid-boot. That wipes the kernel log off the screen at exactly the moment a
// machine being brought up for the first time needs it. Choosing the font
// before the first line is drawn costs one file read and changes nothing
// afterwards.
//
// The pages are allocated as EfiReservedMemoryType, so the E820 handoff marks
// them reserved and the page allocator never hands them out -- the font is
// used *in place*, and nothing in the kernel has to find room for 100 KB of
// contiguous memory.
#define SCHBOOT_FONT_ADDR (SCHBOOT_BASE + 0x608) /* uint64, physical */
#define SCHBOOT_FONT_LEN (SCHBOOT_BASE + 0x610) /* uint32, bytes */
// The display, described by the loader in its own words rather than in VBE's.
//
// The VBE mode-info block above is a *BIOS* structure, and the UEFI loader has
// been filling one in because the kernel already knew how to read it. Three of
// its fields cannot describe a real machine:
//
// - the framebuffer address is **32 bits**. A laptop with a discrete GPU
// routinely puts its aperture above 4 GB, and the loader was casting
// `(UINT32)gop->Mode->FrameBufferBase` -- so the splash was drawn into a
// truncated address, nothing appeared, and the firmware's own text cursor
// was left blinking on an otherwise black screen. That is the whole of the
// "it boots to a white underscore" report.
// - the stride is 16 bits, which runs out at 16383 pixels of 32-bit colour.
// - there is nowhere to say whether the pixels are RGBX or BGRX, and GOP
// reports both.
//
// So the UEFI path says it properly. The VBE block is still written, for the
// BIOS loader and for any kernel that has not been taught to look here; a
// non-zero FB_BASE means this block is authoritative and the other is a copy.
//
// FB_BASE is *physical*, and deliberately not translated: above 4 GB it is
// outside the kernel's direct map and has to be ioremap()ed, which cannot
// happen until there are page tables to put it in.
#define SCHBOOT_FB_BASE (SCHBOOT_BASE + 0x618) /* uint64, physical */
#define SCHBOOT_FB_SIZE (SCHBOOT_BASE + 0x620) /* uint64, bytes */
#define SCHBOOT_FB_WIDTH (SCHBOOT_BASE + 0x628) /* uint32, pixels */
#define SCHBOOT_FB_HEIGHT (SCHBOOT_BASE + 0x62C) /* uint32, pixels */
#define SCHBOOT_FB_STRIDE (SCHBOOT_BASE + 0x630) /* uint32, bytes */
// 0 = RGBX8888, 1 = BGRX8888 -- the two GOP formats with a linear layout, in
// EFI's own numbering. Everything in the kernel draws 0x00RRGGBB, which is
// BGRX in memory, so format 0 is the one that needs its channels swapped.
#define SCHBOOT_FB_FORMAT (SCHBOOT_BASE + 0x634) /* uint32 */
#ifndef __ASSEMBLER__
struct sch64_header {
unsigned int magic0; // SCH64_MAGIC0
unsigned int magic1; // SCH64_MAGIC1
unsigned long long entry; // physical address, entered in long mode
};
#endif /* !__ASSEMBLER__ */
#endif /* SCHBOOT_H */