ui_panel was written for this screen during the loader restyle and then
never called -- dead code sitting in boot/common/ui.c waiting for the row
that would use it. This is that row: F8 -> System Information, showing
firmware vendor and revision, UEFI revision, CPU brand, usable memory,
current mode and whether ACPI was found.
Everything is read when the screen is drawn rather than captured at
startup, because the resolution row above it can have changed the mode,
and a panel still reporting what the machine booted with is worse than no
panel at all.
Two facts are deliberately read through the same code that feeds the
kernel -- efi_type_to_e820() for what counts as usable memory, and
find_rsdp() for ACPI. A panel with its own idea of either would be a
second implementation to keep in step, and the ACPI line exists precisely
because a missing RSDP means no MADT, no IOAPIC and no SMP, which has
already been debugged once from the far end.
The screenshot caught a bug in the screen it was taken to verify: UEFI
revision read "21587.2286". EFI_TABLE_HEADER begins with a UINT64
Signature, so Revision is at offset 8 and I had read offset 4 -- the
upper half of "IBI SYST", formatted as a version number. It now reads 2.7
under OVMF, which is correct.
Verified by driving the menu over the QEMU monitor and reading the
pixels: five rows with the hint updated to 1-5, the panel centred, and
every value cross-checkable -- EDK II, firmware 1.0, UEFI 2.7, "QEMU
Virtual CPU version 2.5+" against -cpu qemu64, 1018 MB of the 1 GB given,
1280x800 matching what fbshot captures, ACPI present. The highlight sits
9 px inside both panel borders, symmetric.
F8 has a 1.5 s window, so the capture presses it repeatedly through boot
rather than guessing when OVMF finishes -- and only F8, since any other
key breaks the wait and boots.
Also: every ESP drive now says format=raw. QEMU warned about probing the
format on each of the fifteen or so places that attach it, which is new
noise on what is now the only boot path. 110 [ok], no failures, no
warnings.
1449 lines of boot/bios/, tools/sign.pl and tools/padstage2.pl, plus the
build rules, the disk image and the ISO entry that existed to carry them.
Everything since roughly 2012 boots UEFI, and CSM -- the firmware-side
BIOS emulation that lets a legacy loader run on such a machine at all --
has been going away since Intel dropped support for it in 2020. So this
is not a path being retired early; it is one whose remaining hardware is
already the exception.
The cost was never the code, it was the tax on everything near it. Stage
2 had a 65-sector ceiling it overflowed silently -- 25440 bytes against
24576 -- and because the kernel is written at sector 1 + STAGE2_SECTORS,
an overflowing loader was overwritten by the very kernel it was loading.
The resolution list could not be shared with UEFI either: VBE mode
setting is INT 10h, so matching it needed unreal mode, which is the
riskiest code in the tree for a feature that already worked elsewhere.
What replaces schos.img is the staged ESP, handed to QEMU as a virtual
FAT volume. That is a development arrangement and not an installable
disk; a real GPT image with a real FAT partition is the installer's job,
and until it exists nothing here writes a disk a machine boots unaided.
Secure Boot is untouched too -- the loader is unsigned.
The ISO keeps one El Torito entry, the FAT image holding loader and
kernel. It never booted from a USB stick via BIOS anyway: the firmware
presents the stick as a hard disk while stage2main.c read the kernel back
over ATAPI.
The 32-bit multiboot entry in entry.S now has no caller and is left in
place. Deleting kernel entry code is its own change with its own way of
failing silently, and it should not ride along with this one.
Ported with it: run.ps1, the six screenshot tools, and schcom_test.sh,
each with its own OVMF varstore copy so no run inherits another's UEFI
variables.
Two things this shook out. OVMF now belongs in `all`: nothing boots
without firmware, `make clean` wipes build/ including the copy fetched
from the host, and a clean followed by a build left every run script
throwing. And it cannot be listed as a prerequisite at the top of the
file -- make expands prerequisites when it reads the line, and
$(OVMF_CODE) is defined further down, so naming it there appends nothing
and says nothing.
Verified from a clean tree: 110 [ok] and no failures on the full suite,
the ISO boots with no hard disk attached, schfs-check passes, schcom's
end-to-end pixel test passes, and fbshot captures a real 1280x800 frame.
The BIOS loader drew its own splash and the UEFI loader drew a different
one, which is how two implementations of a single screen end up looking
nothing alike: only one of them ever got a change. stage2main.c's
fourteen drawing functions are deleted and it calls boot/common/ui.c,
which is what the UEFI loader already used. There is one splash now.
What changed in the look, in order of how much it mattered:
- No product name under the mark. The name is *in* the mark, and
setting it again underneath read as a placeholder nobody removed.
- The mark is scaled to the bar's width and sits directly above it, so
the two are one object rather than a large logo with an unrelated
line beneath. Box-filtered down from the source mask, never up:
point-sampling a coverage mask drops half the edge pixels and puts
holes in the thin strokes.
- Metal rather than flat grey. A vertical gradient with a knee near the
top where a highlight falls, applied to the mark, the bar fill and
panel headings. A single colour on a dark ground looks printed; the
knee is what makes it look lit. No texture -- at this size noise
reads as dirt.
- The boot manager is a panel with a lit selected row, replacing a
screen laid out in literals with a magic y of 340 that only ever
looked right at 800x600.
Three defects found by reading pixels rather than code:
- the UEFI loader kept calling print() after the splash, and ConOut on
a graphical firmware renders into the same framebuffer -- a
screenshot showed firmware text across our mark from the left edge.
Diagnostics go to COM1 directly now, which is where they were being
read from anyway, and the console is cleared before drawing so the
firmware has nothing to put back.
- SPLASH_UP was declared and read but never assigned, so that redirect
was dead code and the "serial works" reading was OVMF's own console.
- stage2 overflowed its allocation -- 25440 bytes against 24576 -- and
the build did not stop. The kernel is written at sector
1 + STAGE2_SECTORS, so a stage2 past its allocation is overwritten by
the kernel it is loading. STAGE2_SECTORS is 64 now, and the padding
tool's check does fire; it had simply not been re-run.
Verified from the host on both: BIOS 800x600 and UEFI 1280x800 both draw
a centred group with zero stray pixels, UEFI's spanning x 576-703 which
is exactly the mark width centred. bootcheck 108 [ok], iso builds.
Still text-mode: the resolution menu. On BIOS it necessarily is -- it
runs in real mode before any framebuffer exists, which is the whole
reason it can set one. On UEFI it could be graphical and is not yet.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The first piece of the two loaders that is genuinely common code rather
than a contract they both honour. It can be, because the difference
between the firmwares is entirely in how a framebuffer is *obtained* --
VBE in real mode, or GOP -- and not in what you do with one afterwards.
boot/common/ui.c takes a base, a size and a stride and draws; it knows
nothing about disks, memory maps or mode setting, which is what lets it
compile into a 16/32/64-bit BIOS loader and a PE/COFF UEFI application
without either leaking into it.
The look is deliberately quiet: dark ground, the mark centred, one thin
bar beneath it, nothing on screen that is not load-bearing. The whole
group is laid out from the centre outwards rather than from the top down,
so it sits identically at 800x600 and at 1600x1200 instead of drifting up
the screen as the mode grows.
UEFI had no graphical splash at all -- it printed text to a console the
QEMU window is usually not even showing. It draws the same screen as the
BIOS path now, with the bar tracking bytes of kernel actually written
rather than a segment count, which says nothing about how long each takes.
Two ordering bugs found by looking at the pixels rather than the code:
setup_framebuffer ran *after* the kernel was read, so every progress tick
went to a framebuffer that did not exist yet and the splash then painted
over the result; and that also put the resolution prompt after the
slowest step instead of at the start. The display is set up first now.
Verified from the host with screendump: background is exactly SCH_BG, the
bar is 4px tall and width/4 wide at y=495 on a 1280x800 screen, which is
where centring the group puts it. UEFI still boots to cpu0; BIOS
bootcheck 27/27.
Not yet shared: the BIOS loader still draws with its own copy of this
code. Deleting that copy in favour of this one is the next step and the
point of the exercise -- until then this is one loader using shared code
and one duplicating it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The BIOS loader handed the kernel a 32-bit entry that climbed into long
mode itself; the UEFI loader arrived already there and used a separate
64-bit entry. Two entries meant two copies of the same preconditions, and
that is not a hypothetical cost -- it is exactly where EFER.SCE went
missing for months, set on one path and not the other, invisible until a
program tried to return from a syscall.
stage2 does the climb now. It builds the page tables where the UEFI
loader builds them and meaning the same thing -- 4 GB identity-mapped
with 1 GB pages, and the same 4 GB again at 0xffff800000000000, because
entry64low's first instruction is a movabs to a high address and identity
alone triple faults on it -- sets PAE, LME and SCE, enables paging, and
far jumps through a GDT with an L-bit code descriptor, which a 32-bit
descriptor cannot express.
stage2main.c is compiled for x86_64 and runs in long mode. It still reads
the multiboot header to find where the kernel's segments go, but enters
through the SCH64 header rather than the entry named there: that entry is
32-bit code whose only job is the climb, and dropping back to 32 bits to
redo work already done is a mode switch for no reason. If the SCH64
header is absent it halts, because there is no longer anything to fall
back to.
So both loaders now hand over identically -- long mode, interrupts off,
CR3 mapping the low 4 GB twice, framebuffer in RBX -- and there is one
set of preconditions instead of two that can drift.
Verified on every path that exists: BIOS to schinit; BIOS with -smp 2,
which brings up cpu1 through the AP trampoline's own separate climb; the
resolution menu still selecting 1024x768 over serial; the ISO, which
loads through ATAPI and a ramdisk root; UEFI unchanged at 1018 MB in 7
regions; bootcheck 108 [ok]. stage2 is 21988 of 24576 bytes.
The 32-bit mboot_entry stays in entry.S. Nothing of ours uses it now, but
it is what makes the kernel loadable by a multiboot loader that is not
ours, and removing it is a separate decision from this one.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Groundwork for usb-tablet, which is the fix for the guest cursor and the
host's never being in the same place: a PS/2 mouse reports how far it
moved, two pointers accumulating deltas independently drift apart, and no
amount of care in the compositor can make them agree. An absolute device
reports where it is, so they coincide by construction.
- the HID driver no longer matches the boot subclass only. usb-tablet
declares subclass 0 and was refused at the door; hid_probe decides
what an interface actually is, and still declines what it cannot make
sense of.
- a non-boot HID interface is taken to be an absolute pointer and is
not asked for boot protocol, which it does not have -- that request
failing is what stopped it binding.
- tablet_report decodes the fixed 6-byte layout (buttons, 16-bit X,
16-bit Y, wheel) and scales the logical range to pixels there,
because it is the only place that knows the range.
- input_mouse_abs reports EV_ABS/ABS_X/ABS_Y so a reader can tell an
absolute position from a delta, and mouse_set_abs places the pointer
rather than nudging it.
- Schcom treats EV_ABS as authoritative and EV_REL as a nudge.
The device binds and identifies correctly: "usb-hid: tablet (absolute) on
slot 1, endpoint 0x81 every 4 ms".
It is NOT finished, and QEMU_INPUT is empty by default because of it. No
reports have yet been observed arriving on the tablet's interrupt
endpoint, so making it the default pointer would trade a cursor that
drifts for one that does not move at all. Opt in with
make qemu QEMU_INPUT='-device qemu-xhci,id=xhci -device usb-tablet,bus=xhci.0'
Next step is to find out why nothing arrives: whether the first transfer
is queued but never completes, whether a completion needs re-arming for
this endpoint, or whether QEMU is not routing motion to the tablet at all
(`info mice`).
bootcheck 108 [ok], schcom-test passes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Clients connect over a Unix socket, are handed a shared buffer, draw into
it and commit; Schcom composites and presents. Both of the hard blockers
its README listed -- unix domain sockets and raw input events -- have been
done for a while, so nothing was in the way.
The model is Wayland's, on purpose. A surface is not a window: it is a
rectangle of pixels with no policy attached, because placement belongs to
SchWM and the moment this program starts deciding where windows go the
split has failed. Nothing is shown until COMMIT, so a client halfway
through drawing is never presented. The compositor tells clients when to
draw rather than letting them render frames nobody sees.
Wayland is MIT-licensed and supporting it later is intended, so the
divergences are the ones that are cheap to undo: a buffer handle is a
System V shm id rather than an fd over SCM_RIGHTS, and the message
carries a handle_kind field so switching is a new value rather than a
protocol break. Every message carries its own length, so an unknown
message is skipped instead of desynchronising the stream -- which is what
lets a v0 compositor survive a v1 client.
The compositor owns the shm segment, not the client: it has to survive a
client crashing mid-frame, and it must not be something a client can
resize underneath it. A dropped connection takes its surfaces and their
segments with it.
Composites into a back buffer and copies once, because compositing
straight into the framebuffer shows every intermediate state -- the
flicker would be the whole desktop being repainted under the user's eyes.
projects/ gets build glue for the first time: `make projects` builds
against the sysroot's libc and stages into sysroot/bin. It is not a
dependency of anything above it, and `make` never descends there.
Verified by `make schcom-test`, which boots SchOS, runs the compositor and
a client, screendumps the framebuffer and asserts the pixels on the host.
That last part is the point -- the compositor reporting that it
composited something proves nothing, and a screenshot nobody inspects
proves less. It checks the client's colour mid-surface, the compositor's
background, and the white border exactly at the requested origin, so
"arrived" and "arrived at the wrong offset" cannot pass for each other.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`make ramroot` wiped the objects on the way in, and nothing wiped on the
way out. So a plain `make` afterwards kept every RAMDISK_ROOT object and
linked the 24 MB ramdisk back into a kernel that no longer used it: 25 MB
where a clean build is 836 KB. It still booted -- from the ramdisk -- and
the only outward symptom was stage2 having thirty times as much to load
through PIO, one sector at a time. That is the "booting got slower"
report this came from.
A stamp file records which setting the objects were built with, and the
tree is dropped when it changes in either direction.
qemu-kvm is opt-in and the default stays TCG, because the suite's timings
were established there. Measured with the guest timing itself,
`ls /bin | wc -l` goes 2.9s -> 1.1s. Boot does not improve and gets
slightly worse: every PIO port access in stage2 is a VM exit under KVM.
Retracts a claim from earlier today. The "2.2x speedup" attributed to the
region-based allocator was a measurement artifact -- the probe sent a
command, slept a fixed 50s on the host, then read /proc/uptime, so it
timed the sleep rather than the command, through a guest clock that drifts
under load. Bracketing the command inside one guest command line shows no
measurable difference. The e820 change is still correct; it is simply not
a performance change.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Nothing outside the kernel could touch a disk. sys_mount does not even
look at the device it is handed -- it mounts the one SchFS volume found
at boot and ignores `source` -- so a program had no way to read a
partition table, format a volume or write a boot sector. An installer is
a program that needs all three, so this is the first piece of it.
A character device in devsw rather than a separate block layer, because
that is where the offset already lives: fileread/filewrite pass f->off
and advance it by what comes back, which is everything a seekable device
needs. The minor is the IDE device number, the same numbering iderw()
uses; two schemes for the same disks would be a bug waiting to happen.
Writes bypass every filesystem above them, which is inherent to a raw
device and why an installer must target a disk it has not mounted.
The nodes are created unconditionally, like every other entry in /dev.
That matters more than it looks: with no node, `echo x > /dev/hdb`
silently creates an ordinary file of that name and appears to work. It
did, and it agreed with itself on read-back; only the host's copy of the
disk showed nothing had been written.
st_rdev was never filled, so every node in /dev reported "0, 0" under
ls -l. Harmless while nothing read it, wrong as soon as something has to
tell two disks apart. Filled with the full Linux dev_t encoding rather
than (major << 8 | minor), which only works while both stay small.
Verified against the host as an independent oracle, both directions: the
guest wrote to /dev/hdb and the host saw those bytes at offset 0; the
host wrote at offset 4096 and the guest read them back with dd skip=.
ls -l now shows 1,1 for console, 2,0 for fb0 and 8,1 for hdb.
Known limit, commented in disk.c: devsw passes the offset as a 32-bit
uint, so a raw disk is addressable to 4 GB. That must be widened before
anyone installs onto a real disk.
bootcheck 108 [ok] with -Fb -Tty -Usb -Sock -Evdev -Ps.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The disc carried one file -- boot/schos.bin -- with 8.3 uppercase names,
one El Torito entry, and no way to boot a UEFI machine. Now:
/boot/schos.bin BIOS entry, unchanged: no-emulation, 1+STAGE2_SECTORS
/boot/efiboot.img UEFI entry: a FAT image built with mtools
/EFI/BOOT/BOOTX64.EFI the loader, also visible as a file
/bin /etc /usr ... the staged sysroot as an ordinary tree
Rock Ridge and Joliet restore real names, permissions and symlinks;
-cache-inodes matters more than it looks, because the sysroot is a toybox
multicall binary with a couple of hundred hard links to it and without
inode caching each link becomes its own copy.
-isohybrid-gpt-basdat adds a GPT so the .iso can be dd'd to a stick and
booted by UEFI. That is UEFI only and deliberately so: the BIOS isohybrid
trick needs syslinux's isohdpfx.bin, and even with it this loader reads
the kernel back over ATAPI, which a stick presented as a hard disk cannot
serve. usbstick.img is what that is for.
The FAT image carries the kernel as well as the loader, because
BOOTX64.EFI opens \schos\kernel on the volume it was loaded from, and for
a UEFI boot that is the FAT image rather than the ISO 9660 tree. Without
it the loader halted with EFI_NOT_FOUND.
Also fixes `make iso` and `make ramroot`, which were already broken:
fs-ram.img was still built by the v1 mkfs while the kernel accepts only
SchFS as root, so the ramdisk was a format it could not mount and it
panicked with "schfs root: no SchFS volume on the root device". It is a
mkschfs image now, which needed --exclude in mkschfs -- dropping the
exclude list was not an option, because the ramdisk is linked into the
kernel binary and what goes in it is a licensing question. RAMDISK_NINODES
is gone with the v1 image: SchFS has no preallocated inode table.
Verified: BIOS ISO boots to a shell and runs commands over serial
(241 files in /bin, /etc/motd reads back). UEFI ISO boots to a prompt.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
tools/posix/setup-fedora.sh installs the toolchain and reports what it
cannot: PowerShell has no Fedora package, and bootcheck is the test
suite, so the script says how to get pwsh rather than failing quietly.
It also checks /dev/kvm, since a Ryzen box can run the tests at native
speed.
docs/moving-to-linux.md is the actual procedure: copy the repo, restore
the memory, install, build. The memory travels inside the repo and
`memory-sync.sh import` puts it under the key this machine derives from
its own path -- /home/nexoniarz/Documents/SchOS becomes
-home-nexoniarz-Documents-SchOS, where D:\DEV\SchOS was D--DEV-SchOS.
OVMF discovery no longer assumes QEMU's Windows layout. Fedora ships
edk2-ovmf to /usr/share/edk2/ovmf/, Debian and Arch differ again, so the
Makefile searches a list and names the packages when it finds nothing.
Tested through the shell rather than $(wildcard): on Windows make is a
native binary using Win32 file calls, while `command -v` returns an MSYS
path like /c/Users/..., which $(wildcard) cannot see -- it returned
nothing on a machine that had the firmware.
Two things only that machine can test: `make ports` builds musl, toybox
and mksh from source, which Windows cannot do at all; and the SchFS
round-trip needs symlinks and hard links, so it has never actually run.
Loads the kernel from the ESP, places its ELF segments, records the
framebuffer, translates the UEFI memory map into E820 entries, exits boot
services, builds the page tables the kernel expects and jumps. Verified
under OVMF: the kernel starts, brings up PCI, VGA, e1000 and the RTC, and
reaches "cpu0: starting".
UEFI structures are written from the specification rather than importing
gnu-efi or EDK II -- a large dependency for two hundred lines of use, and
a licensing question the project does not need to answer. clang emits
PE/COFF directly, so this needs no new toolchain.
The kernel gained a second entry point. The multiboot entry is 32-bit
code that climbs into long mode itself, and firmware that already put us
there would have to drop back down only to climb out again -- a mode
switch whose failure mode is a triple fault with no console. So entry.S
publishes its 64-bit entry the same way it publishes the multiboot
header: a magic to scan for, SCH64BOO, with include/schboot.h owning the
constants so the two cannot disagree. That header goes *after* the
multiboot header, never before: header_addr says the multiboot header
lives at mboot_load_addr, and the BIOS loader computes the kernel's file
offset from it.
The kernel loads its own GDT at that entry rather than the loader doing
it. Arriving in long mode means arriving on the firmware's descriptors,
and the first thing depending on the kernel's own selectors faulted --
"unexpected trap 13 ... cs=38", a long way from the cause.
KNOWN LIMITATION, diagnosed and not yet fixed: e820_init takes the usable
run *containing the kernel*, which under OVMF ends at 8 MB -- the
firmware places an ACPI NVS region at 0x800000, so the run is genuinely
fragmented. The map is truthful and untruncated (21 entries); the
assumption of one contiguous run is BIOS-shaped. The fix is for the
kernel to free each usable region rather than one range, which touches
kalloc and deserves its own change rather than the tail of this one.
BIOS boot is unaffected: 27 checks, no failures.
LOGO_SRC named "Assets/SchOS Small Logo.png", which no longer exists --
the small logo was replaced by the full logo, the same mark slightly
redrawn. `make logo` had been failing since. Points at the full logo now;
not the text logo, which is the wordmark and would be illegible scaled to
128 px. Verified by photographing the splash: the mark renders as the
circle with Sch over OS.
/etc lived only in sysroot/, which is generated and gitignored, so
/etc/passwd, /etc/group, /etc/profile, /etc/kshrc and /etc/motd would all
have been missing from a fresh clone -- and the machine boots to a shell
that reads them. They are tracked under etc/ now and installed by
etc-install alongside the schinit manifests.
/etc/motd still said "Schunix": the rename swept tracked files, and this
one was excluded from version control at the time. Fixed.
Noted while checking: sysroot/bin/sh still contains the symbol
__schunix_tp, so the staged mksh predates the rename of the musl TLS
patch to __schos_tp. Harmless -- it is an internal symbol -- but it
confirms the staged ports are older than the patches that produce them,
and they cannot be rebuilt on a Windows host. It resolves the next time
ports are built on Linux.
Milestone 1 of three: manifests, RunAtLoad, KeepAlive with throttling,
and reaping. Replaces the hardcoded fork-a-shell loop in cmd/init, which
stays in the tree for now.
Jobs are sectioned key=value files in /etc/schinit/*.job, kept in the
repo under etc/ and installed into the sysroot -- sysroot/ is generated
and gitignored, so anything written only there is lost on a fresh clone,
which is already true of /etc/passwd and should not become true of the
boot configuration. Unknown keys and sections are ignored rather than
fatal: a manifest written for a newer schinit must not stop this one
booting the machine.
Socket sections are parsed and recorded but not yet held. That is
milestone 2 and it is the whole reason for copying launchd rather than
sysvinit -- the supervisor holds the listener and starts the job on first
connect, which inverts the dependency graph and makes runlevels a
non-concept. The key exists now so manifests need not change later.
getdents64 is exposed to userland for this. The kernel has had it since
the Linux ABI landed, but nothing declared it in user.h or lib/usys.S, so
first-party programs had no way to list a directory at all.
Two things found by testing rather than reasoning:
- setsid() gives a job its own session, and a session has no
controlling terminal. The console then delivers input only to the
foreground process group it tracks, so the shell came up, echoed
every keystroke through the line discipline, and executed none of
them -- indistinguishable from a hung shell. ControllingTerminal=yes
claims it with TIOCSCTTY/TIOCSPGRP; off by default, since a daemon
has no business seizing the console.
- the first bootstrap() dropped /dev/input/event0-1 and most of /proc,
because it was transcribed from the part of the old init that had
been read rather than all of it. -Evdev caught it.
build/usbstick.img is built by `all` now: without it QEMU refuses the
-drive and the entire -Usb run produces no output, which reads as a
broken USB driver.
Seven suites green: base 27, Tty 51, Ps 32, Sock 64, Fb 36, Usb 31,
Evdev 29. make schfs-check passes.
boot/bios/ now holds the MBR, the El Torito stub and the real-mode stage
2; boot/uefi/ is empty and documented.
Separate directories rather than #ifdefs, because the two share almost
nothing. A BIOS loader lives in 16-bit real mode, reaches the firmware
through software interrupts, and walks itself up into long mode. A UEFI
loader is a 64-bit PE that the firmware has already placed in long mode
with paging on, calling services through a table of function pointers.
What they do share is the hand-off contract, which boot/README.md now
writes down: ELF segments placed, long mode, a 24-byte-entry memory map
at E820_MAP_ADDR, framebuffer geometry at VBE_INFO_ADDR.
That contract is the point of documenting it now. UEFI's GetMemoryMap
returns EFI_MEMORY_DESCRIPTORs, and the right move is to translate them
into the entries kernel/arch/x86_64/e820.c already parses rather than
teach the kernel a second format.
Build output paths under $(BUILD)/boot are unchanged; only sources moved.
Two bugs reported from actual use, plus a test harness that had stopped
testing.
init could be killed by Ctrl-C, taking the machine with it. SIGINT goes
to the foreground process group, init is pgid 1, and any window where
tty_fg_pgrp is 1 -- before a shell claims the terminal, after one exits,
or between the two while the key is held -- lands it on init. init has
no handler, so killed was set, exit() ran, and exit() panics outright on
initproc. Spamming Ctrl-C at a frozen program is exactly the input that
finds that window. POSIX and Linux both discard a default-action signal
for pid 1; init_ignores() now does the same, SIGKILL included, because
init is not killable from userspace on any Unix.
Everything was slow because BSIZE was 1024 while a SchFS block is 4096,
so every block cost four IDE requests, each with its own interrupt and
sleep/wakeup. exec() of the 800 KB toybox binary meant ~800 round trips.
The giveaway: `seq 1 200`, which touches no file and prints nothing,
cost the same 2.0s as `cat /bin/toybox`, and `ls | wc -l` cost exactly
double -- both dominated by loading the same binary twice.
BSIZE is 4096 now, one request per SchFS block, and NBUF is 256 blocks
(1 MB) rather than 90 KB -- smaller than the binary every command execs,
so nothing was ever still resident. ideinit() issues SET MULTIPLE MODE,
which is what makes a 4096-byte transfer legal: READ/WRITE MULTIPLE move
the drive's multiple count per interrupt and nothing had ever set it,
which is the real reason idestart() carried a sector_per_block > 7 panic.
seq 1 200 2.02s -> 0.06s
ls /bin 2.76s -> 0.68s
ls /bin | wc -l 4.68s -> 0.73s
And the harness. bootcheck called Find-Qemu from a param() default,
which is bound before any function in the body exists -- so it died on
its first line and printed an error containing neither "FAIL" nor
"Exception", the two things being grepped for. It had not run since it
was made host-portable. Worse, an earlier index-based edit had deleted
the Expect blocks for -Tty, -Usb, -Sock, -Ps and -Evdev, so five of the
seven modes asserted nothing at all. Restored, and the USB run gets its
own 16 MB build/usbstick.img: it had been pointed at build/fs.img, which
nothing builds since SchFS became root, so QEMU refused to start.
242 checks across seven modes, all passing, and make schfs-check green.
tools/windows/ .ps1 (pwsh, so usable from Linux and macOS too)
tools/posix/ .sh
tools/python/ .py
tools/ .c and .pl
The .c and .pl files stay at the top level because they are not
host-specific: the build compiles or invokes them on every platform.
Call sites updated in the Makefile, ports/mk/port.mk, docs and the
project memory. The one thing that does not survive a move on its own is
a script deriving the repo root from its own location -- all nine
PowerShell tools used Split-Path $PSScriptRoot -Parent, and memory-sync.sh
used dirname/.., both of which quietly become tools/ one level deeper.
Two levels now.
Two pre-existing Windows failures surfaced while verifying, both real and
neither caused by the move:
- schfs_fsck.c did not compile under mingw at all: --extract uses
POSIX mkdir(path,mode), symlink() and link(), none of which mingw
has. That took the whole tool with it, and `make schfs-check` with
it. mkdir maps to _mkdir; symlink and link fail loudly rather than
writing a regular file in their place, because an extracted tree
that differs from the image while reporting success defeats the
point of --extract.
- the round-trip test then failed at `ln -s`, since Git Bash cannot
create symlinks without developer mode. It skips with a message now.
Not a fudge: mkschfs's mingw build cannot see a symlink either, so
the test would have compared a tree with none against an image with
none and called it a pass -- coverage reporting itself as present
while being absent.
Verified on Windows: build clean, all seven bootcheck suites green,
make schfs-check passes.
Developed from both Windows and Linux, so nothing in the build should
care which host it runs on. It did, in four places.
Toolchain: one path instead of two. clang is a cross-compiler by
construction, so a single LLVM install targets bare-metal ELF, 16/32-bit
code below 1 MB, and Linux/musl userland from any host -- which is how
the Windows build already worked. The GNU branch beside it had drifted
into passing --no-warn-rwx-segments, a hard error on lld, which is the
default linker on both FreeBSD and macOS; and it required a brewed
x86_64-elf-gcc before anything built on a Mac at all. GNU is kept as a
fallback (USE_GNU=1) but clang+lld is chosen whenever both are present.
Host tools use the host's own cc; only mingw needs naming explicitly.
tools/mkschfs.c included <sys/sysmacros.h> unconditionally on non-mingw
hosts. That header is glibc/musl only -- macOS and the BSDs have neither
the header nor a need for it, since major()/minor() come from
<sys/types.h> there. Now guarded on __linux__.
Ports verified tarballs with `sha256sum -c -`, which does not exist on
macOS (shasum), FreeBSD/NetBSD (sha256), or OpenBSD (cksum -a sha256),
and each spells its check mode differently. tools/sha256check.sh picks
whichever of the five the host has and compares digests itself.
nproc is coreutils, so absent on macOS and BSD; falls through to sysctl
-n hw.ncpu before defaulting to 4.
The PowerShell tools now run under pwsh on any OS: QEMU is found on PATH
(the scoop location is a Windows-only fallback), and path literals use
forward slashes -- Join-Path $root 'build\x' produces a *filename
containing a backslash* on a Unix host. The backslash sweep is limited
to known directory prefixes on purpose: bootcheck sends
printf 'caf\303\251' to the guest to test UTF-8, and a blanket
substitution would have quietly changed what that asserts.
Also fixes six tools left booting build/fs.img, which stopped being
bootable when SchFS became the root filesystem: crashshot, dispshot,
fbshot, splashshot, textshot and ttyshot all take build/schfs.img now,
and the -SchFS switches that used to select it are gone.
Verified on Windows: full build, and bootcheck green. The Linux, macOS
and BSD paths are written from their documented behaviour and are NOT
executed here -- this machine has no WSL and no second host.
Removes the "-unix" element and with it any trademark exposure to The
Open Group.
The split follows the ecosystem layout: SchOS is the system, and the
kernel has its own name. Kernel-identity strings are the only places
that take SchIFT -- uname(2) sysname, /proc/version, and the two licence
sentences that say "kernel" outright. That is what every Unix does here:
`uname -s` on Linux reports the kernel, not the distribution. Everything
a user reads as the product -- panic screen, boot menu, error prefixes,
docs, LICENSE, NOTICE -- is SchOS.
schunix.img is schos.img, the logo assets and the musl TLS patch are
renamed, and __SCHUNIX__ is __SCHOS__ (the define and the patch that
consumes it moved together).
Two things the rename broke, both found by the bootcheck suite:
- shmtest wrote "SCHUNIX-MMAP-OK" with a hardcoded length of 15 and
compared it back by hand-counted offsets (m[3]=='U', m[14]=='K').
The shorter marker left the write running two bytes past the literal
and the compare reading past the data, failing a file-backed mmap
that was working correctly. Now written and compared through one
constant with sizeof.
- bootcheck asserted a 31-byte file after appending to
"hello-from-schunix"; the shorter string makes it 29.
Both are the same mistake: a string literal whose length is written out
somewhere else. Full suite green -- base, Tty, Ps, Sock, Fb, Usb, Evdev.
An x86-64 operating system built from xv6, now largely rewritten: SMP,
a copy-on-write root filesystem (SchFS), PCI/USB/virtio drivers, a TTF
rasteriser, and a Linux-numbered syscall ABI.
No history before this point -- the tree was never under version control.
This commit exists so the rename that follows has something to undo to.