boot: the BIOS loader is gone
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.
This commit is contained in:
parent
0007853baa
commit
651899220c
23 changed files with 317 additions and 1826 deletions
155
Makefile
155
Makefile
|
|
@ -186,7 +186,6 @@ ifdef USE_CLANG
|
||||||
# object is silently the wrong format and the first thing to complain is the
|
# object is silently the wrong format and the first thing to complain is the
|
||||||
# linker.
|
# linker.
|
||||||
CC = clang --target=x86_64-elf
|
CC = clang --target=x86_64-elf
|
||||||
CC32 = clang --target=i386-elf
|
|
||||||
LD = ld.lld
|
LD = ld.lld
|
||||||
OBJCOPY = llvm-objcopy
|
OBJCOPY = llvm-objcopy
|
||||||
OBJDUMP = llvm-objdump
|
OBJDUMP = llvm-objdump
|
||||||
|
|
@ -221,7 +220,6 @@ else
|
||||||
TOOLPREFIX ?=
|
TOOLPREFIX ?=
|
||||||
endif
|
endif
|
||||||
CC = $(TOOLPREFIX)gcc
|
CC = $(TOOLPREFIX)gcc
|
||||||
CC32 = $(CC) -m32
|
|
||||||
LD = $(TOOLPREFIX)ld
|
LD = $(TOOLPREFIX)ld
|
||||||
OBJCOPY = $(TOOLPREFIX)objcopy
|
OBJCOPY = $(TOOLPREFIX)objcopy
|
||||||
OBJDUMP = $(TOOLPREFIX)objdump
|
OBJDUMP = $(TOOLPREFIX)objdump
|
||||||
|
|
@ -363,16 +361,24 @@ $(shell mkdir -p $(BUILD); \
|
||||||
# usbstick.img is built here rather than on demand because bootcheck -Usb
|
# usbstick.img is built here rather than on demand because bootcheck -Usb
|
||||||
# cannot start QEMU without it, and a missing test image looks exactly like a
|
# cannot start QEMU without it, and a missing test image looks exactly like a
|
||||||
# broken USB driver: QEMU refuses the -drive and the whole run produces nothing.
|
# broken USB driver: QEMU refuses the -drive and the whole run produces nothing.
|
||||||
all: schos.img $(BUILD)/usbstick.img
|
# OVMF is in this list because it is no longer optional: with the BIOS loader
|
||||||
|
# gone, nothing in this tree boots without firmware, and `make clean` wipes
|
||||||
|
# build/ including the copy fetched from the host. Leaving it out meant a clean
|
||||||
|
# followed by a build produced a tree where every run script threw.
|
||||||
|
all: esp $(BUILD)/schfs.img $(BUILD)/usbstick.img
|
||||||
|
# ...and the firmware, added to `all` further down. It cannot be listed here:
|
||||||
|
# make expands a rule's prerequisites when it reads the line, and $(OVMF_CODE)
|
||||||
|
# is defined below this point, so naming it here appends nothing at all and
|
||||||
|
# does so silently.
|
||||||
|
|
||||||
schos.img: $(BUILD)/boot/bootblock $(BUILD)/boot/stage2 $(BUILD)/kernel/kernel $(BUILD)/schfs.img
|
# schos.img is gone with the BIOS loader that gave it its shape. It was a raw
|
||||||
dd if=/dev/zero of=schos.img count=10000
|
# disk with the boot sector at LBA 0, stage2 next and the kernel at a fixed
|
||||||
dd if=$(BUILD)/boot/bootblock of=schos.img conv=notrunc
|
# offset -- a layout only a loader that seeks by sector number can use. UEFI
|
||||||
dd if=$(BUILD)/boot/stage2 of=schos.img seek=1 conv=notrunc
|
# firmware wants a filesystem, so what boots now is the staged ESP, offered to
|
||||||
# seek is 1 + STAGE2_SECTORS, not a literal. It was 25 against a 24-sector
|
# QEMU through its virtual FAT.
|
||||||
# stage2; raising STAGE2_SECTORS for the boot logo without touching it wrote
|
#
|
||||||
# the kernel straight over stage2's own tail.
|
# That is a development arrangement, not an installable disk. A real GPT image
|
||||||
dd if=$(BUILD)/kernel/kernel of=schos.img seek=$$(( 1 + $(STAGE2_SECTORS) )) conv=notrunc
|
# with a real FAT partition is the installer's job.
|
||||||
|
|
||||||
kernel: $(BUILD)/kernel/kernel
|
kernel: $(BUILD)/kernel/kernel
|
||||||
|
|
||||||
|
|
@ -451,6 +457,11 @@ $(OVMF_VARS):
|
||||||
cp "$(OVMF_VARS_SRC)" $@
|
cp "$(OVMF_VARS_SRC)" $@
|
||||||
chmod u+w $@
|
chmod u+w $@
|
||||||
|
|
||||||
|
ovmf: $(OVMF_CODE) $(OVMF_VARS)
|
||||||
|
|
||||||
|
# The other half of `all`, now that OVMF_CODE and OVMF_VARS have values.
|
||||||
|
all: $(OVMF_CODE) $(OVMF_VARS)
|
||||||
|
|
||||||
qemu-uefi: esp $(BUILD)/schfs.img $(OVMF_CODE) $(OVMF_VARS)
|
qemu-uefi: esp $(BUILD)/schfs.img $(OVMF_CODE) $(OVMF_VARS)
|
||||||
$(QEMU) -machine pc-i440fx-9.2 -drive if=pflash,format=raw,unit=0,readonly=on,file=$(OVMF_CODE) -drive if=pflash,format=raw,unit=1,file=$(OVMF_VARS) -drive file=fat:rw:$(BUILD)/esp,index=0,media=disk -drive file=$(BUILD)/schfs.img,format=raw,index=1,media=disk -m 1G -smp 1 -cpu qemu64,+rdtscp -serial mon:stdio $(QEMU_DISPLAY) $(QEMU_INPUT)
|
$(QEMU) -machine pc-i440fx-9.2 -drive if=pflash,format=raw,unit=0,readonly=on,file=$(OVMF_CODE) -drive if=pflash,format=raw,unit=1,file=$(OVMF_VARS) -drive file=fat:rw:$(BUILD)/esp,index=0,media=disk -drive file=$(BUILD)/schfs.img,format=raw,index=1,media=disk -m 1G -smp 1 -cpu qemu64,+rdtscp -serial mon:stdio $(QEMU_DISPLAY) $(QEMU_INPUT)
|
||||||
|
|
||||||
|
|
@ -506,12 +517,10 @@ iso:
|
||||||
rm -rf $(BUILD)/kernel $(BUILD)/lib $(BUILD)/cmd
|
rm -rf $(BUILD)/kernel $(BUILD)/lib $(BUILD)/cmd
|
||||||
$(MAKE) RAMROOT=1 schos.iso
|
$(MAKE) RAMROOT=1 schos.iso
|
||||||
|
|
||||||
# The UEFI loader is a dependency now: the disc carries a second El Torito
|
# One El Torito entry, pointing at a FAT image holding the loader and kernel.
|
||||||
# entry pointing at a FAT image that holds it, so one disc boots both firmwares.
|
|
||||||
# sysroot-install because the disc also carries the tree as a real hierarchy.
|
# sysroot-install because the disc also carries the tree as a real hierarchy.
|
||||||
schos.iso: $(BUILD)/boot/cdboot $(BUILD)/boot/stage2 $(BUILD)/kernel/kernel \
|
schos.iso: $(BUILD)/kernel/kernel $(BUILD)/uefi/BOOTX64.EFI sysroot-install
|
||||||
$(BUILD)/uefi/BOOTX64.EFI sysroot-install
|
SYSROOT=$(SYSROOT) sh tools/posix/mkiso.sh $@
|
||||||
STAGE2_SECTORS=$(STAGE2_SECTORS) SYSROOT=$(SYSROOT) sh tools/posix/mkiso.sh $@
|
|
||||||
|
|
||||||
# Boot the ISO the way a machine booting off a disc would: no hard disk at
|
# Boot the ISO the way a machine booting off a disc would: no hard disk at
|
||||||
# all, so a kernel that still secretly depends on one fails here rather than
|
# all, so a kernel that still secretly depends on one fails here rather than
|
||||||
|
|
@ -521,71 +530,6 @@ qemu-iso: schos.iso
|
||||||
-cdrom schos.iso -boot d \
|
-cdrom schos.iso -boot d \
|
||||||
-m 1G -smp 2 -machine pc-i440fx-9.2
|
-m 1G -smp 2 -machine pc-i440fx-9.2
|
||||||
|
|
||||||
# -----------------------------------------------------------------------
|
|
||||||
# Bootloader — two stages. Stage 1 (bootblock) is the actual 512-byte MBR
|
|
||||||
# sector: BIOS-imposed hard limit, so it only loads stage 2 off disk and
|
|
||||||
# jumps to it. Stage 2 does VBE/A20/protected-mode setup, draws the boot
|
|
||||||
# splash, and loads the kernel — not size-constrained the same way, just
|
|
||||||
# padded out to a fixed STAGE2_SECTORS*512 bytes so the kernel's starting
|
|
||||||
# sector on disk is predictable (kept in sync with STAGE2_SECTORS in
|
|
||||||
# boot/bios/bootasm.S and boot/bios/stage2main.c).
|
|
||||||
# -----------------------------------------------------------------------
|
|
||||||
# STAGE2_SECTORS used to be written out three times -- here, in boot/bios/bootasm.S
|
|
||||||
# and in boot/bios/stage2main.c -- and docs/layout.md carried a warning about keeping
|
|
||||||
# them in step. They are now all fed from this one variable via -D, so the
|
|
||||||
# warning describes a problem that can no longer happen. Both C/asm files keep
|
|
||||||
# an #ifndef fallback purely so they still read sensibly on their own.
|
|
||||||
#
|
|
||||||
# The ceiling is memory, not disk: stage2 is loaded at 0x7E00 and the multiboot
|
|
||||||
# kernel-search buffer begins at 0x10000, so 65 sectors is the hard maximum.
|
|
||||||
# stage2main.c static-asserts this. padstage2.pl fails the build if the compiled
|
|
||||||
# binary overflows the budget, rather than truncating it -- a short stage2 would
|
|
||||||
# shift the kernel's start sector and fail in a way that looks nothing like its
|
|
||||||
# cause.
|
|
||||||
# 64, not 48. stage2 carries the shared UI now -- which means the 8x16 font and
|
|
||||||
# the logo mask -- and it is 64-bit code since the long-mode climb moved here.
|
|
||||||
# At 48 it came to 25440 bytes against a 24576 limit and the build did not stop,
|
|
||||||
# which is worse than the overflow: the kernel is written at sector
|
|
||||||
# 1 + STAGE2_SECTORS, so a stage2 that runs past its allocation is overwritten
|
|
||||||
# by the very kernel it is loading.
|
|
||||||
STAGE2_SECTORS = 64
|
|
||||||
STAGE2_DEF = -DSTAGE2_SECTORS=$(STAGE2_SECTORS)
|
|
||||||
# Byte budget is computed in the recipe with shell arithmetic, not $(shell expr
|
|
||||||
# ... \* 512): make hands the backslash through untouched, sh then treats the
|
|
||||||
# `*` as a glob, and expr gets a directory listing. It "worked" to the extent
|
|
||||||
# that make carried on and padstage2.pl received nonsense.
|
|
||||||
STAGE2_BYTES = $$(( $(STAGE2_SECTORS) * 512 ))
|
|
||||||
|
|
||||||
$(BUILD)/boot/bootblock: boot/bios/bootasm.S
|
|
||||||
@mkdir -p $(BUILD)/boot
|
|
||||||
$(CC32) $(STAGE2_DEF) -fno-builtin -fno-pic -nostdinc -I./include -c boot/bios/bootasm.S -o $(BUILD)/boot/bootasm.o
|
|
||||||
$(LD) -m elf_i386 -nostdlib $(LD_LOWBASE) -n -N -e start -Ttext 0x7C00 \
|
|
||||||
-o $(BUILD)/boot/bootblocktmp.o $(BUILD)/boot/bootasm.o
|
|
||||||
$(OBJCOPY) -S -O binary -j .text $(BUILD)/boot/bootblocktmp.o $(BUILD)/boot/bootblock
|
|
||||||
./tools/sign.pl $(BUILD)/boot/bootblock
|
|
||||||
|
|
||||||
# Stage 1 for optical media. Same job as bootblock, far less of it: El Torito
|
|
||||||
# has already loaded stage2 alongside this sector by the time it runs, so it
|
|
||||||
# only records that we came from a CD and jumps. See boot/bios/cdboot.S.
|
|
||||||
$(BUILD)/boot/cdboot: boot/bios/cdboot.S
|
|
||||||
@mkdir -p $(BUILD)/boot
|
|
||||||
$(CC32) -fno-builtin -fno-pic -nostdinc -I./include -c boot/bios/cdboot.S -o $(BUILD)/boot/cdbootasm.o
|
|
||||||
$(LD) -m elf_i386 -nostdlib $(LD_LOWBASE) -n -N -e start -Ttext 0x7C00 \
|
|
||||||
-o $(BUILD)/boot/cdboottmp.o $(BUILD)/boot/cdbootasm.o
|
|
||||||
$(OBJCOPY) -S -O binary -j .text $(BUILD)/boot/cdboottmp.o $@
|
|
||||||
./tools/sign.pl $@
|
|
||||||
|
|
||||||
$(BUILD)/boot/stage2: boot/bios/stage2.S boot/bios/stage2main.c include/logo.h \
|
|
||||||
boot/common/ui.c boot/common/ui.h
|
|
||||||
@mkdir -p $(BUILD)/boot
|
|
||||||
$(CC) $(STAGE2_DEF) -fno-builtin -fno-pic -nostdinc -I./include -c boot/bios/stage2.S -o $(BUILD)/boot/stage2asm.o
|
|
||||||
$(CC) $(STAGE2_DEF) -fno-builtin -fno-pic -O -nostdinc -mno-red-zone -mno-sse -mcmodel=small -I./include -c boot/bios/stage2main.c -o $(BUILD)/boot/stage2main.o
|
|
||||||
$(CC) -fno-builtin -fno-pic -O -nostdinc -mno-red-zone -mno-sse -mcmodel=small -I./include -c boot/common/ui.c -o $(BUILD)/boot/ui.o
|
|
||||||
$(LD) -m elf_x86_64 -nostdlib $(LD_LOWBASE) -n -N -e stage2start -Ttext 0x7E00 \
|
|
||||||
-o $(BUILD)/boot/stage2tmp.o $(BUILD)/boot/stage2asm.o $(BUILD)/boot/stage2main.o $(BUILD)/boot/ui.o
|
|
||||||
$(OBJCOPY) -S -O binary -j .text -j .rodata -j .data $(BUILD)/boot/stage2tmp.o $(BUILD)/boot/stage2
|
|
||||||
./tools/padstage2.pl $(BUILD)/boot/stage2 $(STAGE2_BYTES)
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------
|
# -----------------------------------------------------------------------
|
||||||
# Kernel binary
|
# Kernel binary
|
||||||
# -----------------------------------------------------------------------
|
# -----------------------------------------------------------------------
|
||||||
|
|
@ -912,9 +856,10 @@ $(BUILD)/fs.img: $(MKFS) $(BUILD)/sysroot.stamp
|
||||||
@echo "Creating SchOS File System Image..."
|
@echo "Creating SchOS File System Image..."
|
||||||
@# A running QEMU holds this file open, and on Windows that makes rm fail
|
@# A running QEMU holds this file open, and on Windows that makes rm fail
|
||||||
@# with "Device or resource busy" -- which stops the build *after* the
|
@# with "Device or resource busy" -- which stops the build *after* the
|
||||||
@# kernel has already relinked, leaving schos.img stale. The next test run
|
@# kernel has already relinked, leaving the staged ESP holding a kernel
|
||||||
@# then boots the previous kernel and the change appears to have done
|
@# from the previous build. The next test run then boots that one and the
|
||||||
@# nothing, which has cost several debugging cycles. Say so plainly.
|
@# change appears to have done nothing, which has cost several debugging
|
||||||
|
@# cycles. Say so plainly.
|
||||||
@rm -f $@ || { \
|
@rm -f $@ || { \
|
||||||
echo ""; \
|
echo ""; \
|
||||||
echo " $@ is in use -- a QEMU is still holding it."; \
|
echo " $@ is in use -- a QEMU is still holding it."; \
|
||||||
|
|
@ -943,18 +888,26 @@ $(BUILD)/fs.img: $(MKFS) $(BUILD)/sysroot.stamp
|
||||||
# framebuffer and asserts the pixels on the host. That last part is the whole
|
# framebuffer and asserts the pixels on the host. That last part is the whole
|
||||||
# value: the compositor saying "I composited it" proves nothing, and a
|
# value: the compositor saying "I composited it" proves nothing, and a
|
||||||
# screenshot nobody inspects proves less. Needs `make projects` first.
|
# screenshot nobody inspects proves less. Needs `make projects` first.
|
||||||
schcom-test: schos.img $(BUILD)/schfs.img
|
schcom-test: esp $(BUILD)/schfs.img
|
||||||
sh tools/posix/schcom_test.sh
|
sh tools/posix/schcom_test.sh
|
||||||
|
|
||||||
qemu-kvm: schos.img $(BUILD)/schfs.img
|
# The firmware and the disks, in one place. Every run target below boots the
|
||||||
$(QEMU) -enable-kvm -serial mon:stdio $(QEMU_DISPLAY) $(QEMU_INPUT) -cpu host \
|
# same way, because there is only one way to boot now: OVMF, the staged ESP as
|
||||||
-hda schos.img -hdb $(BUILD)/schfs.img \
|
# the first disk, the SchFS volume as the second. Keeping the argument list in
|
||||||
-m 1G -smp 2 -machine pc-i440fx-9.2
|
# a variable is not tidiness -- four copies of a pflash pair is how one of them
|
||||||
|
# ends up with a writable firmware image and nobody notices.
|
||||||
|
OVMF_DRIVES = -drive if=pflash,format=raw,unit=0,readonly=on,file=$(OVMF_CODE) \
|
||||||
|
-drive if=pflash,format=raw,unit=1,file=$(OVMF_VARS) \
|
||||||
|
-drive file=fat:rw:$(BUILD)/esp,index=0,media=disk \
|
||||||
|
-drive file=$(BUILD)/schfs.img,format=raw,index=1,media=disk
|
||||||
|
|
||||||
qemu: schos.img $(BUILD)/schfs.img
|
qemu-kvm: esp $(BUILD)/schfs.img $(OVMF_CODE) $(OVMF_VARS)
|
||||||
|
$(QEMU) -enable-kvm -serial mon:stdio $(QEMU_DISPLAY) $(QEMU_INPUT) -cpu host \
|
||||||
|
$(OVMF_DRIVES) -m 1G -smp 2 -machine pc-i440fx-9.2
|
||||||
|
|
||||||
|
qemu: esp $(BUILD)/schfs.img $(OVMF_CODE) $(OVMF_VARS)
|
||||||
$(QEMU) -serial mon:stdio $(QEMU_DISPLAY) $(QEMU_INPUT) -cpu qemu64,+rdtscp \
|
$(QEMU) -serial mon:stdio $(QEMU_DISPLAY) $(QEMU_INPUT) -cpu qemu64,+rdtscp \
|
||||||
-hda schos.img -hdb $(BUILD)/schfs.img \
|
$(OVMF_DRIVES) -m 1G -smp 2 -machine pc-i440fx-9.2
|
||||||
-m 1G -smp 2 -machine pc-i440fx-9.2
|
|
||||||
|
|
||||||
# The root filesystem. Built from the staged sysroot; the kernel mounts it as
|
# The root filesystem. Built from the staged sysroot; the kernel mounts it as
|
||||||
# / in forkret().
|
# / in forkret().
|
||||||
|
|
@ -972,18 +925,18 @@ $(BUILD)/schfs.img: $(MKSCHFS) $(BUILD)/sysroot.stamp
|
||||||
# the live v1 root and the SchFS probe correctly finds nothing there.
|
# the live v1 root and the SchFS probe correctly finds nothing there.
|
||||||
#
|
#
|
||||||
# Once up: cat /proc/schfs
|
# Once up: cat /proc/schfs
|
||||||
qemu-schfs: schos.img $(BUILD)/schfs.img
|
qemu-schfs: esp $(BUILD)/schfs.img $(OVMF_CODE) $(OVMF_VARS)
|
||||||
$(QEMU) -serial mon:stdio $(QEMU_DISPLAY) -cpu qemu64,+rdtscp \
|
$(QEMU) -serial mon:stdio $(QEMU_DISPLAY) -cpu qemu64,+rdtscp \
|
||||||
-hda schos.img -hdb $(BUILD)/schfs.img \
|
$(OVMF_DRIVES) -m 1G -smp 2 -machine pc-i440fx-9.2
|
||||||
-m 1G -smp 2 -machine pc-i440fx-9.2
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------
|
# -----------------------------------------------------------------------
|
||||||
# Misc
|
# Misc
|
||||||
# -----------------------------------------------------------------------
|
# -----------------------------------------------------------------------
|
||||||
# schos.img is deliberately NOT here. It is a real file with real
|
# Nothing that is a real file with real prerequisites belongs here. schos.img
|
||||||
# prerequisites; listing it as phony made make skip the timestamp check and
|
# used to be listed, and being phony made make skip the timestamp check and
|
||||||
# re-run the four dd commands -- including zeroing five megabytes -- on every
|
# re-run its four dd commands -- including zeroing five megabytes -- on every
|
||||||
# single invocation, including ones where nothing had changed.
|
# single invocation, including ones where nothing had changed. The file is gone
|
||||||
|
# with the BIOS loader, but the rule it taught applies to whatever replaces it.
|
||||||
# include/logo.h is generated but committed, exactly as include/font.h is: it
|
# include/logo.h is generated but committed, exactly as include/font.h is: it
|
||||||
# derives from art that changes about never, and making it a build dependency
|
# derives from art that changes about never, and making it a build dependency
|
||||||
# would put Python on the critical path of every clean build for no benefit.
|
# would put Python on the critical path of every clean build for no benefit.
|
||||||
|
|
@ -1053,13 +1006,13 @@ qrcheck: $(QRTEST)
|
||||||
.PHONY: all kernel ramroot cmd ports projects sysroot-install qemu qemu-kvm schcom-test \
|
.PHONY: all kernel ramroot cmd ports projects sysroot-install qemu qemu-kvm schcom-test \
|
||||||
schfs-test schfs-objtest schfs-image-test schfs-check mkschfs \
|
schfs-test schfs-objtest schfs-image-test schfs-check mkschfs \
|
||||||
schfs-fsck crashshot qemu-schfs iso qemu-iso clean distclean \
|
schfs-fsck crashshot qemu-schfs iso qemu-iso clean distclean \
|
||||||
logo qrtest qrcheck crashqr usb-ids
|
logo qrtest qrcheck crashqr usb-ids ovmf esp uefi
|
||||||
.PRECIOUS: $(BUILD)/%.o
|
.PRECIOUS: $(BUILD)/%.o
|
||||||
|
|
||||||
# 'clean' removes generated output only. dist/ (upstream tarballs) survives,
|
# 'clean' removes generated output only. dist/ (upstream tarballs) survives,
|
||||||
# so a rebuild never needs the network.
|
# so a rebuild never needs the network.
|
||||||
clean:
|
clean:
|
||||||
rm -rf $(BUILD) schos.img schos.iso kernel/arch/x86_64/vectors.S
|
rm -rf $(BUILD) schos.iso kernel/arch/x86_64/vectors.S
|
||||||
|
|
||||||
distclean: clean
|
distclean: clean
|
||||||
rm -rf dist
|
rm -rf dist
|
||||||
|
|
|
||||||
|
|
@ -72,7 +72,7 @@ report.
|
||||||
## Quick start
|
## Quick start
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
make # kernel + bootloader + root filesystem -> schos.img
|
make # kernel + UEFI loader + root filesystem
|
||||||
make qemu # build it and boot it
|
make qemu # build it and boot it
|
||||||
make iso # a bootable ISO for VMware or real hardware
|
make iso # a bootable ISO for VMware or real hardware
|
||||||
```
|
```
|
||||||
|
|
@ -105,7 +105,7 @@ make schfs-check # SchFS B-tree, object and image suites
|
||||||
kernel/ arch/ core/ dev/ drivers/ fs/ net/ proc/ sys/
|
kernel/ arch/ core/ dev/ drivers/ fs/ net/ proc/ sys/
|
||||||
fs/schfs/ the filesystem -- same source the host tools compile
|
fs/schfs/ the filesystem -- same source the host tools compile
|
||||||
include/ kernel and userland headers
|
include/ kernel and userland headers
|
||||||
boot/ bios/ (MBR, stage 2, VBE splash) uefi/ (PE loader)
|
boot/ uefi/ (PE loader) common/ (shared loader UI)
|
||||||
lib/ cmd/ first-party userland runtime and programs
|
lib/ cmd/ first-party userland runtime and programs
|
||||||
ports/ third-party software built from source
|
ports/ third-party software built from source
|
||||||
tools/ windows/ (.ps1) posix/ (.sh) python/ (.py) and .c/.pl
|
tools/ windows/ (.ps1) posix/ (.sh) python/ (.py) and .c/.pl
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,30 @@
|
||||||
<!-- SPDX-License-Identifier: Apache-2.0 -->
|
<!-- SPDX-License-Identifier: Apache-2.0 -->
|
||||||
# Booting SchOS
|
# Booting SchOS
|
||||||
|
|
||||||
Two firmware paths, kept apart on purpose:
|
One firmware path:
|
||||||
|
|
||||||
```
|
```
|
||||||
boot/
|
boot/
|
||||||
bios/ legacy BIOS: MBR, El Torito CD stub, real-mode stage 2
|
uefi/ the loader: a 64-bit PE application
|
||||||
uefi/ UEFI application (not written yet)
|
common/ the user interface, shared with nothing else now
|
||||||
```
|
```
|
||||||
|
|
||||||
They are separate directories rather than `#ifdef`s in one because they share
|
There were two. `boot/bios/` held an MBR, an El Torito CD stub and a real-mode
|
||||||
almost nothing. A BIOS loader lives in 16-bit real mode, talks to the firmware
|
stage 2, and it was a separate directory rather than `#ifdef`s because the two
|
||||||
through software interrupts, and has to walk itself up into long mode. A UEFI
|
shared almost nothing: a BIOS loader lives in 16-bit real mode and talks to the
|
||||||
loader is a 64-bit PE executable that the firmware has *already* placed in long
|
firmware through software interrupts, while UEFI hands a loader long mode with
|
||||||
mode with paging on, and it asks for services by calling through a table of
|
paging already on and a table of function pointers to call.
|
||||||
function pointers. The only things genuinely in common are the logo bitmap, the
|
|
||||||
menu's appearance, and the hand-off contract below.
|
That asymmetry is what removed it. Everything since roughly 2012 boots UEFI,
|
||||||
|
CSM — the firmware-side BIOS emulation that let legacy loaders run at all — has
|
||||||
|
been going away since Intel dropped support for it in 2020, and the cost of the
|
||||||
|
second path was not the code but the tax on everything that touched it: stage 2
|
||||||
|
had a 65-sector ceiling that it silently overflowed, and the resolution menu
|
||||||
|
could not be shared because VBE mode setting is `INT 10h` and needs real mode.
|
||||||
|
|
||||||
|
The contract below survives it. It was written to let a second loader exist, and
|
||||||
|
it is worth keeping in that shape: it is what the kernel expects, stated once,
|
||||||
|
rather than a description of whoever happens to be calling.
|
||||||
|
|
||||||
## What the loader must hand the kernel
|
## What the loader must hand the kernel
|
||||||
|
|
||||||
|
|
@ -30,24 +39,24 @@ a second loader is correct exactly when it satisfies it.
|
||||||
| Framebuffer | `VBE_INFO_ADDR` | address, pitch, width, height, depth |
|
| Framebuffer | `VBE_INFO_ADDR` | address, pitch, width, height, depth |
|
||||||
| Verbose flag | `BOOT_VERBOSE_FLAG_ADDR` | set by the F8 menu |
|
| Verbose flag | `BOOT_VERBOSE_FLAG_ADDR` | set by the F8 menu |
|
||||||
|
|
||||||
The addresses are defined once, in `include/memlayout.h` and
|
The addresses are defined once, in `include/memlayout.h` and `include/schboot.h`.
|
||||||
`boot/bios/stage2main.c`. A UEFI loader must fill the same structures — the
|
The kernel does not care which firmware produced them.
|
||||||
kernel does not care which firmware produced them, and that is what keeps the
|
|
||||||
split cheap.
|
|
||||||
|
|
||||||
## Two entry points, because the two firmwares hand over differently
|
## The entry point
|
||||||
|
|
||||||
The BIOS path enters the kernel in **32-bit protected mode** at the multiboot
|
|
||||||
entry, with EAX holding the framebuffer address; `mboot_entry` then builds page
|
|
||||||
tables and climbs into long mode itself.
|
|
||||||
|
|
||||||
UEFI hands a loader long mode with paging already on. Dropping back to
|
UEFI hands a loader long mode with paging already on. Dropping back to
|
||||||
protected mode only to climb out again is a mode switch whose failure mode is a
|
protected mode only to climb out again is a mode switch whose failure mode is a
|
||||||
triple fault with no console, so the kernel publishes a second entry instead.
|
triple fault with no console, so the kernel publishes an entry that takes the
|
||||||
|
machine as it already is.
|
||||||
`kernel/arch/x86_64/entry.S` carries a `sch64_header` — magic `SCH64BOO`,
|
`kernel/arch/x86_64/entry.S` carries a `sch64_header` — magic `SCH64BOO`,
|
||||||
8-byte aligned within the first 16 KB, followed by a 64-bit physical address —
|
8-byte aligned within the first 16 KB, followed by a 64-bit physical address —
|
||||||
found by scanning, exactly as the BIOS loader scans for the multiboot magic.
|
found by scanning. `include/schboot.h` holds the magic so loader and kernel
|
||||||
`include/schboot.h` holds the magic so loader and kernel cannot disagree.
|
cannot disagree.
|
||||||
|
|
||||||
|
The 32-bit multiboot entry is still in `entry.S` and now has no caller: the
|
||||||
|
BIOS loader was the only thing that used it. It is left in place rather than
|
||||||
|
removed in the same change that removed the loader, because deleting kernel
|
||||||
|
entry code is its own piece of work with its own way of failing silently.
|
||||||
|
|
||||||
What that entry expects:
|
What that entry expects:
|
||||||
|
|
||||||
|
|
@ -69,25 +78,16 @@ surfaced as `unexpected trap 13 ... cs=38`, a long way from the cause.
|
||||||
translate it into the same 24-byte entries rather than inventing a second
|
translate it into the same 24-byte entries rather than inventing a second
|
||||||
format for the kernel to learn.
|
format for the kernel to learn.
|
||||||
|
|
||||||
## The BIOS path today
|
## What booting looks like now
|
||||||
|
|
||||||
1. **`bios/bootasm.S`** — the literal MBR sector. Signed to 512 bytes by
|
`make esp` stages `EFI/BOOT/BOOTX64.EFI` and `schos/kernel`, and QEMU is
|
||||||
`tools/sign.pl`, loads stage 2 from LBA 1 and jumps.
|
pointed at OVMF with that directory as a virtual FAT volume — `make qemu`.
|
||||||
2. **`bios/cdboot.S`** — the same job for optical media, as an El Torito
|
`make iso` builds a disc with a single El Torito entry pointing at a FAT image
|
||||||
no-emulation image. Separate because the loader never uses INT 13h and so
|
holding the same two files.
|
||||||
needs a real ATAPI read.
|
|
||||||
3. **`bios/stage2.S`** — collects the E820 map, sets a VBE mode, enters
|
|
||||||
protected mode.
|
|
||||||
4. **`bios/stage2main.c`** — the boot menu and splash, then loads the kernel
|
|
||||||
and enters long mode.
|
|
||||||
|
|
||||||
`STAGE2_SECTORS` is defined **once**, in the Makefile, and passed to both the
|
Neither is an installable disk. A real GPT image with a real FAT partition is
|
||||||
assembler and the `dd` that writes the image. It used to be written out by hand
|
the installer's job, and until it exists there is nothing here that writes to a
|
||||||
in five places and silently truncated the loader when they disagreed.
|
disk a machine could boot from unaided.
|
||||||
|
|
||||||
## Why UEFI is worth doing
|
Secure Boot is not addressed either: the loader is unsigned, so a machine
|
||||||
|
enforcing it will refuse to run this.
|
||||||
Everything since roughly 2012 boots UEFI by default, and Secure Boot machines
|
|
||||||
will not run the BIOS path at all without firmware settings most people never
|
|
||||||
touch. The BIOS loader stays regardless: it is what QEMU's default machine and
|
|
||||||
older hardware use, and it costs nothing to keep once the two are separated.
|
|
||||||
|
|
|
||||||
|
|
@ -1,101 +0,0 @@
|
||||||
# Stage 1 — the actual MBR boot sector. Hard-limited to 510 bytes (+2-byte
|
|
||||||
# 0x55AA signature) by the BIOS, so there's no room here for anything but
|
|
||||||
# "load stage 2 off disk and jump to it". All the VBE/A20/protected-mode/
|
|
||||||
# splash/kernel-loading work now lives in stage2.S + stage2main.c, which
|
|
||||||
# aren't sector-size-constrained.
|
|
||||||
#
|
|
||||||
# How many sectors of stage2 to pull in. This, the padding the Makefile gives
|
|
||||||
# stage2's binary, and the "+(STAGE2_SECTORS+1)" sector math in stage2main.c's
|
|
||||||
# readseg() all have to agree on where the kernel starts on disk -- so the
|
|
||||||
# Makefile passes -DSTAGE2_SECTORS and is the single authority. The value below
|
|
||||||
# is only a fallback for reading this file standalone.
|
|
||||||
#
|
|
||||||
# It is 48, not the old 24, because stage2 now carries the boot logo
|
|
||||||
# (include/logo.h, 8 KiB). The real ceiling is memory rather than this number:
|
|
||||||
# stage2 loads at 0x7E00 and the multiboot kernel-search buffer starts at
|
|
||||||
# 0x10000, so stage2 cannot exceed 0x8200 bytes = 65 sectors.
|
|
||||||
#ifndef STAGE2_SECTORS
|
|
||||||
#define STAGE2_SECTORS 48
|
|
||||||
#endif
|
|
||||||
#define STAGE2_ADDR 0x7E00
|
|
||||||
|
|
||||||
.code16
|
|
||||||
.global start
|
|
||||||
start:
|
|
||||||
cli
|
|
||||||
xorw %ax, %ax
|
|
||||||
movw %ax, %ds
|
|
||||||
movw %ax, %es
|
|
||||||
movw %ax, %ss
|
|
||||||
movw $0x7c00, %sp
|
|
||||||
|
|
||||||
# Read STAGE2_SECTORS sectors, starting at disk sector 1 (LBA), into
|
|
||||||
# memory right after this boot sector (0x7E00). Raw IDE PIO (LBA28) —
|
|
||||||
# same register convention as stage2main.c's own readsect(), just
|
|
||||||
# inlined here since we can't call C code yet. Port numbers 0x1F2-0x1F7
|
|
||||||
# are all > 0xFF, so they must go through %dx — `out $imm8,%al` only
|
|
||||||
# encodes an 8-bit port and silently truncates anything larger.
|
|
||||||
# "You were not booted from a CD." stage2main.c reads this byte to choose
|
|
||||||
# between ATA and ATAPI when it loads the kernel, and nothing else clears
|
|
||||||
# it -- so a warm reboot from a disc could otherwise leave the flag set and
|
|
||||||
# send a perfectly ordinary hard-disk boot looking for an optical drive.
|
|
||||||
# Placed one byte past the verbose flag; both sit clear of the 256-byte VBE
|
|
||||||
# mode info block that stage2.S is about to write at 0x30000.
|
|
||||||
movw $0x3000, %ax
|
|
||||||
movw %ax, %es
|
|
||||||
movw $0x101, %di
|
|
||||||
movb $0, %al
|
|
||||||
movb %al, %es:(%di)
|
|
||||||
xorw %ax, %ax
|
|
||||||
movw %ax, %es
|
|
||||||
|
|
||||||
movw $STAGE2_ADDR, %di # ES:DI destination, ES=0
|
|
||||||
movw $1, %si # first LBA sector to read
|
|
||||||
movb $STAGE2_SECTORS, %bl
|
|
||||||
|
|
||||||
.Lsector:
|
|
||||||
.Lwait1:
|
|
||||||
movw $0x1F7, %dx
|
|
||||||
inb %dx, %al
|
|
||||||
andb $0xC0, %al
|
|
||||||
cmpb $0x40, %al
|
|
||||||
jne .Lwait1
|
|
||||||
|
|
||||||
movw $0x1F2, %dx
|
|
||||||
movb $1, %al
|
|
||||||
outb %al, %dx # sector count = 1
|
|
||||||
|
|
||||||
incw %dx # 0x1F3
|
|
||||||
movw %si, %ax
|
|
||||||
outb %al, %dx # LBA [7:0]
|
|
||||||
|
|
||||||
incw %dx # 0x1F4
|
|
||||||
xorb %al, %al
|
|
||||||
outb %al, %dx # LBA [15:8]
|
|
||||||
|
|
||||||
incw %dx # 0x1F5
|
|
||||||
outb %al, %dx # LBA [23:16]
|
|
||||||
|
|
||||||
incw %dx # 0x1F6
|
|
||||||
movb $0xE0, %al
|
|
||||||
outb %al, %dx # drive 0, LBA mode
|
|
||||||
|
|
||||||
incw %dx # 0x1F7
|
|
||||||
movb $0x20, %al
|
|
||||||
outb %al, %dx # READ SECTORS command
|
|
||||||
|
|
||||||
.Lwait2:
|
|
||||||
inb %dx, %al # %dx still 0x1F7
|
|
||||||
andb $0xC0, %al
|
|
||||||
cmpb $0x40, %al
|
|
||||||
jne .Lwait2
|
|
||||||
|
|
||||||
movw $256, %cx # 256 words = 512 bytes
|
|
||||||
movw $0x1F0, %dx
|
|
||||||
rep insw # reads into ES:DI, DI += 512 automatically
|
|
||||||
|
|
||||||
incw %si
|
|
||||||
decb %bl
|
|
||||||
jnz .Lsector
|
|
||||||
|
|
||||||
ljmp $0, $STAGE2_ADDR
|
|
||||||
|
|
@ -1,46 +0,0 @@
|
||||||
# Stage 1 for optical media -- the El Torito "no emulation" boot sector.
|
|
||||||
#
|
|
||||||
# This exists because bootasm.S cannot boot a CD. It loads stage 2 by talking
|
|
||||||
# straight to the primary IDE controller's ATA registers, and a CD is not an
|
|
||||||
# ATA disk: it answers to the ATAPI packet interface instead, and under
|
|
||||||
# El Torito it may not even be the drive at 0x1F0. Rather than teach a
|
|
||||||
# 512-byte sector two protocols, optical media get their own first stage.
|
|
||||||
#
|
|
||||||
# It has almost nothing to do, because El Torito's "load segment / sector
|
|
||||||
# count" fields do the reading for us: tools/mkiso.sh asks for
|
|
||||||
# 1 + STAGE2_SECTORS virtual sectors at 0x7C00, so by the time this runs the
|
|
||||||
# BIOS has already placed stage 2 at 0x7E00 -- exactly where bootasm.S would
|
|
||||||
# have put it. The only real work is recording *which* medium we came from,
|
|
||||||
# since stage2main.c has to read the kernel back with the matching protocol.
|
|
||||||
#
|
|
||||||
# The flag goes next to the verbose-boot flag, one byte past it, for the same
|
|
||||||
# reason that one sits where it does: it is clear of the 256-byte VBE mode
|
|
||||||
# info block at 0x30000 that stage2.S fills in a moment from now.
|
|
||||||
|
|
||||||
#define STAGE2_ADDR 0x7E00
|
|
||||||
#include "../../include/schboot.h"
|
|
||||||
|
|
||||||
.code16
|
|
||||||
.global start
|
|
||||||
start:
|
|
||||||
cli
|
|
||||||
xorw %ax, %ax
|
|
||||||
movw %ax, %ds
|
|
||||||
movw %ax, %ss
|
|
||||||
movw $0x7c00, %sp
|
|
||||||
|
|
||||||
# "You were booted from a CD." stage2main.c reads this to decide between
|
|
||||||
# ATA and ATAPI; without it the loader would read the kernel off whatever
|
|
||||||
# happens to be sitting at 0x1F0, which on a CD boot is usually nothing.
|
|
||||||
movw $SCHBOOT_MEDIA_SEG, %ax
|
|
||||||
movw %ax, %es
|
|
||||||
movw $SCHBOOT_MEDIA_OFF, %di
|
|
||||||
movb $SCHBOOT_MEDIA_CD, %al
|
|
||||||
movb %al, %es:(%di)
|
|
||||||
|
|
||||||
# stage2.S expects zeroed segments and an inherited stack, same as it gets
|
|
||||||
# from bootasm.S.
|
|
||||||
xorw %ax, %ax
|
|
||||||
movw %ax, %es
|
|
||||||
|
|
||||||
ljmp $0, $STAGE2_ADDR
|
|
||||||
|
|
@ -1,604 +0,0 @@
|
||||||
# Stage 2 — loaded by stage1 at 0x7E00, still in 16-bit real mode. Does the
|
|
||||||
# VBE mode setup, A20 line, and real->protected mode switch that used to
|
|
||||||
# live directly in the boot sector, then hands off to stage2main() (C) to
|
|
||||||
# draw a splash and load the actual kernel. Not sector-size-constrained —
|
|
||||||
# only bounded by STAGE2_SECTORS in bootasm.S / the Makefile.
|
|
||||||
#include "../include/asm.h"
|
|
||||||
#include "../include/memlayout.h"
|
|
||||||
#include "../include/mmu.h"
|
|
||||||
|
|
||||||
.code16
|
|
||||||
.global stage2start
|
|
||||||
stage2start:
|
|
||||||
# Segments are already zeroed by stage1; %sp is inherited too.
|
|
||||||
|
|
||||||
movw $bootmsg, %si
|
|
||||||
call print16
|
|
||||||
|
|
||||||
# VBE Setup. Mode info block goes at 0x30000, not 0x9000 — stage2 itself
|
|
||||||
# is loaded starting at 0x7E00 and, with the boot-menu/progress-bar code,
|
|
||||||
# is now big enough to reach 0x9000, so writing the VBE block there would
|
|
||||||
# clobber stage2's own tail end. 0x30000 is clear of stage2 (ends well
|
|
||||||
# under 0xA000), the multiboot kernel-search buffer (0x10000-0x14000),
|
|
||||||
# and the kernel's own load address (0x100000).
|
|
||||||
# Which mode. 0x143 is 800x600x32 and stays the default: it is the smallest
|
|
||||||
# the system is meant to run at, and the one every path has been tested
|
|
||||||
# against. Press R at the prompt to pick another.
|
|
||||||
movw $0x0143, %cx
|
|
||||||
call res_menu # may return a different mode in CX
|
|
||||||
|
|
||||||
movw $0x3000, %ax
|
|
||||||
movw %ax, %es
|
|
||||||
xorw %di, %di # ES:DI = 3000:0000 = flat 0x30000
|
|
||||||
movw $0x4F01, %ax
|
|
||||||
int $0x10
|
|
||||||
|
|
||||||
# Two ways this goes wrong, and only one of them is an error return.
|
|
||||||
#
|
|
||||||
# A mode number the BIOS does not know fails outright, which is easy. But a
|
|
||||||
# number it *does* know may be nothing like what was asked for: 0x146 was
|
|
||||||
# written here as 1600x1200x32 and this VBE returns 320x200x8 for it. That
|
|
||||||
# boots, and then the framebuffer console and Schcom both render garbage,
|
|
||||||
# because everything above assumes 32bpp. So the depth reported in the mode
|
|
||||||
# info block is checked before the mode is set, not after.
|
|
||||||
cmpw $0x004F, %ax
|
|
||||||
jne res_fallback
|
|
||||||
cmpb $32, %es:25(%di) # mode info +25: bits per pixel
|
|
||||||
je res_ok
|
|
||||||
|
|
||||||
res_fallback:
|
|
||||||
cmpw $0x0143, %cx
|
|
||||||
je e820 # the fallback itself failed; give up on VBE
|
|
||||||
movw $0x0143, %cx
|
|
||||||
xorw %di, %di
|
|
||||||
movw $0x4F01, %ax
|
|
||||||
int $0x10
|
|
||||||
cmpw $0x004F, %ax
|
|
||||||
jne e820
|
|
||||||
cmpb $32, %es:25(%di)
|
|
||||||
jne e820
|
|
||||||
|
|
||||||
res_ok:
|
|
||||||
movw %cx, %bx
|
|
||||||
orw $0x4000, %bx # bit 14: linear framebuffer
|
|
||||||
movw $0x4F02, %ax
|
|
||||||
int $0x10
|
|
||||||
|
|
||||||
# BIOS E820 memory map — the standard firmware interface every serious
|
|
||||||
# loader (GRUB, Linux's own boot stub, the BSDs) uses to learn the real
|
|
||||||
# RAM layout instead of guessing. Entry count goes to flat 0x30200,
|
|
||||||
# 24-byte entries start at 0x30208 (both inside the 0x3000 segment the
|
|
||||||
# VBE block already claimed; verbose flag sits at +0x100). Capped at 32
|
|
||||||
# entries so a pathological BIOS can't run us past the buffer.
|
|
||||||
e820:
|
|
||||||
movw $0x3000, %ax
|
|
||||||
movw %ax, %es
|
|
||||||
xorl %ebx, %ebx # continuation cookie, 0 = first call
|
|
||||||
movw $0x208, %di
|
|
||||||
xorl %ebp, %ebp # entry count
|
|
||||||
e820loop:
|
|
||||||
movl $0xE820, %eax
|
|
||||||
movl $0x534D4150, %edx # 'SMAP'
|
|
||||||
movl $24, %ecx
|
|
||||||
int $0x15
|
|
||||||
jc e820done # carry = end of map (or unsupported BIOS)
|
|
||||||
cmpl $0x534D4150, %eax
|
|
||||||
jne e820done
|
|
||||||
addw $24, %di
|
|
||||||
incl %ebp
|
|
||||||
cmpl $32, %ebp
|
|
||||||
jae e820done
|
|
||||||
testl %ebx, %ebx
|
|
||||||
jnz e820loop
|
|
||||||
e820done:
|
|
||||||
movl %ebp, %es:(0x200)
|
|
||||||
xorw %ax, %ax
|
|
||||||
movw %ax, %es
|
|
||||||
|
|
||||||
seta20.1:
|
|
||||||
inb $0x64, %al
|
|
||||||
testb $0x2, %al
|
|
||||||
jnz seta20.1
|
|
||||||
movb $0xd1, %al
|
|
||||||
outb %al, $0x64
|
|
||||||
seta20.2:
|
|
||||||
inb $0x64, %al
|
|
||||||
testb $0x2, %al
|
|
||||||
jnz seta20.2
|
|
||||||
movb $0xdf, %al
|
|
||||||
outb %al, $0x60
|
|
||||||
|
|
||||||
lgdt gdtdesc
|
|
||||||
movl %cr0, %eax
|
|
||||||
orl $CR0_PE, %eax
|
|
||||||
movl %eax, %cr0
|
|
||||||
|
|
||||||
ljmp $(SEG_KCODE<<3), $start32
|
|
||||||
|
|
||||||
# Print null-terminated string at DS:SI, to the screen *and* the serial port.
|
|
||||||
# Clobbers AX, BX, DX, SI.
|
|
||||||
#
|
|
||||||
# Both, because neither one alone is where the person is looking. BIOS teletype
|
|
||||||
# goes to the VGA text screen -- but `make qemu` attaches virtio-gpu alongside
|
|
||||||
# stdvga and shows that one, so the text lands on a display nobody sees. And
|
|
||||||
# `make qemu` puts the serial line on stdio, which is the terminal the developer
|
|
||||||
# is actually sitting in front of.
|
|
||||||
print16:
|
|
||||||
.Lp16loop:
|
|
||||||
lodsb
|
|
||||||
testb %al, %al
|
|
||||||
jz .Lp16done
|
|
||||||
pushw %ax
|
|
||||||
movb $0x0E, %ah
|
|
||||||
movw $0x0007, %bx
|
|
||||||
int $0x10
|
|
||||||
popw %ax
|
|
||||||
call serial_putc
|
|
||||||
jmp .Lp16loop
|
|
||||||
.Lp16done:
|
|
||||||
ret
|
|
||||||
|
|
||||||
# Write AL to COM1, preserving AX.
|
|
||||||
#
|
|
||||||
# The wait for the transmitter is bounded. A machine with no serial port reads
|
|
||||||
# 0xFF from the status register forever, and an unbounded wait there would hang
|
|
||||||
# the loader on exactly the hardware that cannot tell you why.
|
|
||||||
serial_putc:
|
|
||||||
pushw %ax
|
|
||||||
pushw %cx
|
|
||||||
pushw %dx
|
|
||||||
movb %al, %ah # stash the character; AL is about to be an input
|
|
||||||
movw $0xFFFF, %cx
|
|
||||||
movw $0x3FD, %dx # LSR
|
|
||||||
.Lspw:
|
|
||||||
inb %dx, %al
|
|
||||||
testb $0x20, %al # transmitter holding register empty
|
|
||||||
jnz .Lspgo
|
|
||||||
loop .Lspw
|
|
||||||
jmp .Lspdone # never drained: no port, or nothing listening
|
|
||||||
.Lspgo:
|
|
||||||
movb %ah, %al
|
|
||||||
movw $0x3F8, %dx
|
|
||||||
outb %al, %dx
|
|
||||||
.Lspdone:
|
|
||||||
popw %dx
|
|
||||||
popw %cx
|
|
||||||
popw %ax
|
|
||||||
ret
|
|
||||||
|
|
||||||
# CF=1 and AL=byte if COM1 has one waiting, CF=0 otherwise. Clobbers AX, DX.
|
|
||||||
serial_getc:
|
|
||||||
pushw %dx
|
|
||||||
movw $0x3FD, %dx # LSR
|
|
||||||
inb %dx, %al
|
|
||||||
testb $0x01, %al # data ready
|
|
||||||
jz .Lsgnone
|
|
||||||
movw $0x3F8, %dx
|
|
||||||
inb %dx, %al
|
|
||||||
popw %dx
|
|
||||||
stc
|
|
||||||
ret
|
|
||||||
.Lsgnone:
|
|
||||||
popw %dx
|
|
||||||
clc
|
|
||||||
ret
|
|
||||||
|
|
||||||
# CF=1 and AL=key from either the BIOS keyboard or the serial line.
|
|
||||||
# Clobbers AX, DX.
|
|
||||||
#
|
|
||||||
# Both, because the menu has to answer whoever is typing. Under `make qemu` the
|
|
||||||
# serial line is stdio -- the terminal the developer is in -- while a real
|
|
||||||
# machine, or a QEMU window, has a keyboard and no serial at all. Listening to
|
|
||||||
# only one is how this shipped "working" and did nothing for the person using it.
|
|
||||||
anykey:
|
|
||||||
movb $0x01, %ah
|
|
||||||
int $0x16 # ZF=0 if the keyboard has one
|
|
||||||
jz .Lakserial
|
|
||||||
xorw %ax, %ax
|
|
||||||
int $0x16 # consume it
|
|
||||||
stc
|
|
||||||
ret
|
|
||||||
.Lakserial:
|
|
||||||
call serial_getc
|
|
||||||
ret
|
|
||||||
|
|
||||||
|
|
||||||
# Echo AL to both the screen and the serial line. Clobbers nothing.
|
|
||||||
echo_char:
|
|
||||||
pushw %ax
|
|
||||||
pushw %bx
|
|
||||||
movb $0x0E, %ah
|
|
||||||
movw $0x0007, %bx
|
|
||||||
int $0x10
|
|
||||||
popw %bx
|
|
||||||
popw %ax
|
|
||||||
call serial_putc
|
|
||||||
ret
|
|
||||||
|
|
||||||
# Read a decimal number, echoing as it goes. Returns it in AX.
|
|
||||||
# Ends on Enter; anything that is not a digit is ignored rather than aborting,
|
|
||||||
# so a stray keystroke does not silently truncate the number.
|
|
||||||
read_num:
|
|
||||||
pushw %bx
|
|
||||||
pushw %cx
|
|
||||||
pushw %dx
|
|
||||||
xorw %bx, %bx
|
|
||||||
.Lrn_loop:
|
|
||||||
call anykey
|
|
||||||
jnc .Lrn_loop
|
|
||||||
cmpb $0x0D, %al # Enter (both the keyboard and a serial line)
|
|
||||||
je .Lrn_done
|
|
||||||
cmpb $0x0A, %al
|
|
||||||
je .Lrn_done
|
|
||||||
cmpb $'0', %al
|
|
||||||
jb .Lrn_loop
|
|
||||||
cmpb $'9', %al
|
|
||||||
ja .Lrn_loop
|
|
||||||
call echo_char
|
|
||||||
subb $'0', %al
|
|
||||||
movzbw %al, %cx
|
|
||||||
movw %bx, %ax
|
|
||||||
movw $10, %dx
|
|
||||||
mulw %dx # DX:AX = AX * 10
|
|
||||||
addw %cx, %ax
|
|
||||||
movw %ax, %bx
|
|
||||||
jmp .Lrn_loop
|
|
||||||
.Lrn_done:
|
|
||||||
movw %bx, %ax
|
|
||||||
popw %dx
|
|
||||||
popw %cx
|
|
||||||
popw %bx
|
|
||||||
ret
|
|
||||||
|
|
||||||
# Find a VBE mode matching reqw x reqh at 32bpp. Returns CX = mode, or 0.
|
|
||||||
#
|
|
||||||
# This asks the BIOS for its own list rather than carrying a table of numbers.
|
|
||||||
# The table was wrong the first time it was written -- 0x146 was "1600x1200" and
|
|
||||||
# is 320x200x8 on this VBE -- and a table can only ever describe the machines
|
|
||||||
# somebody happened to test. AX=4F00 returns a controller info block whose
|
|
||||||
# VideoModePtr is a far pointer to a 0xFFFF-terminated list of mode numbers;
|
|
||||||
# each one is then asked what it actually is.
|
|
||||||
#
|
|
||||||
# Scratch buffers live at 0x31000 and 0x31200, clear of the mode info block at
|
|
||||||
# 0x30000, the flags at +0x100, the e820 map at +0x208 and the RSDP slot at
|
|
||||||
# +0x600.
|
|
||||||
find_mode:
|
|
||||||
pushw %ax
|
|
||||||
pushw %bx
|
|
||||||
pushw %dx
|
|
||||||
pushw %si
|
|
||||||
pushw %di
|
|
||||||
|
|
||||||
movw $0x3100, %ax
|
|
||||||
movw %ax, %es
|
|
||||||
xorw %di, %di
|
|
||||||
movw $0x4F00, %ax
|
|
||||||
int $0x10
|
|
||||||
cmpw $0x004F, %ax
|
|
||||||
jne .Lfm_none
|
|
||||||
|
|
||||||
# VideoModePtr: offset at +14, segment at +16.
|
|
||||||
movw %es:14(%di), %si
|
|
||||||
movw %es:16(%di), %ax
|
|
||||||
movw %ax, %fs
|
|
||||||
|
|
||||||
.Lfm_next:
|
|
||||||
movw %fs:(%si), %cx
|
|
||||||
addw $2, %si
|
|
||||||
cmpw $0xFFFF, %cx
|
|
||||||
je .Lfm_none
|
|
||||||
|
|
||||||
# CX and SI have to survive the BIOS call; VBE only promises AX back.
|
|
||||||
pushw %cx
|
|
||||||
pushw %si
|
|
||||||
movw $0x3120, %ax
|
|
||||||
movw %ax, %es
|
|
||||||
xorw %di, %di
|
|
||||||
movw $0x4F01, %ax
|
|
||||||
int $0x10
|
|
||||||
popw %si
|
|
||||||
popw %cx
|
|
||||||
cmpw $0x004F, %ax
|
|
||||||
jne .Lfm_next
|
|
||||||
|
|
||||||
movw $0x3120, %ax
|
|
||||||
movw %ax, %es
|
|
||||||
xorw %di, %di
|
|
||||||
cmpb $32, %es:25(%di) # bits per pixel
|
|
||||||
jne .Lfm_next
|
|
||||||
movw %es:18(%di), %ax # width
|
|
||||||
cmpw reqw, %ax
|
|
||||||
jne .Lfm_next
|
|
||||||
movw %es:20(%di), %ax # height
|
|
||||||
cmpw reqh, %ax
|
|
||||||
jne .Lfm_next
|
|
||||||
jmp .Lfm_done # CX is the answer
|
|
||||||
|
|
||||||
.Lfm_none:
|
|
||||||
xorw %cx, %cx
|
|
||||||
.Lfm_done:
|
|
||||||
popw %di
|
|
||||||
popw %si
|
|
||||||
popw %dx
|
|
||||||
popw %bx
|
|
||||||
popw %ax
|
|
||||||
ret
|
|
||||||
|
|
||||||
reqw:
|
|
||||||
.word 0
|
|
||||||
reqh:
|
|
||||||
.word 0
|
|
||||||
|
|
||||||
# Offer a resolution choice, briefly.
|
|
||||||
#
|
|
||||||
# In real mode because this is the only place it can be: setting a VBE mode is
|
|
||||||
# INT 10h, and by the time stage2main.c runs we are in protected mode with no
|
|
||||||
# BIOS left to call. The kernel cannot change it either -- there is no modeset
|
|
||||||
# path, so whatever is chosen here is what the framebuffer, the console and the
|
|
||||||
# compositor all get.
|
|
||||||
#
|
|
||||||
# A second and a half, then it boots on its own. This was four seconds and that
|
|
||||||
# was simply too long: it is paid on every unattended boot, and a loader that
|
|
||||||
# adds four seconds to every start to offer a menu nobody usually wants has made
|
|
||||||
# the common case worse to serve the rare one.
|
|
||||||
#
|
|
||||||
# In: CX = default mode. Out: CX = chosen mode. Clobbers AX, BX, DX, SI.
|
|
||||||
res_menu:
|
|
||||||
pushw %cx
|
|
||||||
movw $resprompt, %si
|
|
||||||
call print16
|
|
||||||
|
|
||||||
# Poll for ~1.5s. INT 1Ah tick count runs at 18.2 Hz, so 27 ticks is a second
|
|
||||||
# and a half and needs no calibration against CPU speed -- a delay loop would
|
|
||||||
# be a different length on every machine.
|
|
||||||
movb $0x00, %ah
|
|
||||||
int $0x1A # CX:DX = ticks since midnight
|
|
||||||
movw %dx, %bx # BX = starting tick, low word
|
|
||||||
.Lrwait:
|
|
||||||
call anykey
|
|
||||||
jc .Lrkey
|
|
||||||
movb $0x00, %ah
|
|
||||||
int $0x1A
|
|
||||||
subw %bx, %dx
|
|
||||||
cmpw $27, %dx
|
|
||||||
jb .Lrwait
|
|
||||||
movw $restimeout, %si
|
|
||||||
call print16
|
|
||||||
popw %cx # timed out: keep the default
|
|
||||||
ret
|
|
||||||
|
|
||||||
.Lrkey:
|
|
||||||
orb $0x20, %al # crude tolower: 'R' and 'r' both land on 'r'
|
|
||||||
cmpb $'r', %al
|
|
||||||
je .Lrshow
|
|
||||||
movw $restimeout, %si
|
|
||||||
call print16
|
|
||||||
popw %cx # some other key: boot with the default
|
|
||||||
ret
|
|
||||||
|
|
||||||
.Lrshow:
|
|
||||||
popw %cx # default is no longer needed; a pick replaces it
|
|
||||||
movw $resmenu, %si
|
|
||||||
call print16
|
|
||||||
.Lrpick:
|
|
||||||
call anykey
|
|
||||||
jnc .Lrpick
|
|
||||||
cmpb $'4', %al
|
|
||||||
je .Lrcustom
|
|
||||||
cmpb $'1', %al
|
|
||||||
jb .Lrpick
|
|
||||||
cmpb $'3', %al
|
|
||||||
ja .Lrpick
|
|
||||||
|
|
||||||
# Echo the digit: over serial there is no local echo, so without this the
|
|
||||||
# menu looks like it ignored the key.
|
|
||||||
call serial_putc
|
|
||||||
pushw %ax
|
|
||||||
movb $0x0E, %ah
|
|
||||||
movw $0x0007, %bx
|
|
||||||
int $0x10
|
|
||||||
popw %ax
|
|
||||||
|
|
||||||
# Index the table: (key - '1') * 2, because each entry is one 16-bit mode.
|
|
||||||
subb $'1', %al
|
|
||||||
movzbw %al, %bx
|
|
||||||
shlw $1, %bx
|
|
||||||
movw resmodes(%bx), %cx
|
|
||||||
|
|
||||||
movw $resok, %si
|
|
||||||
call print16
|
|
||||||
ret
|
|
||||||
|
|
||||||
# Type a size. Enumerated rather than looked up, so anything the BIOS can
|
|
||||||
# actually do is available -- not just the three that happen to be listed.
|
|
||||||
.Lrcustom:
|
|
||||||
call echo_char
|
|
||||||
movw $reswidth, %si
|
|
||||||
call print16
|
|
||||||
call read_num
|
|
||||||
movw %ax, reqw
|
|
||||||
movw $resheight, %si
|
|
||||||
call print16
|
|
||||||
call read_num
|
|
||||||
movw %ax, reqh
|
|
||||||
|
|
||||||
movw $resok, %si
|
|
||||||
call print16
|
|
||||||
|
|
||||||
# 800x600 is the floor the system is built around: the console, the splash
|
|
||||||
# and Schcom all assume at least that much, and a smaller mode would be a
|
|
||||||
# different set of assumptions rather than a smaller number.
|
|
||||||
cmpw $800, reqw
|
|
||||||
jb .Lrcsmall
|
|
||||||
cmpw $600, reqh
|
|
||||||
jb .Lrcsmall
|
|
||||||
|
|
||||||
call find_mode
|
|
||||||
testw %cx, %cx
|
|
||||||
jz .Lrcnone
|
|
||||||
ret
|
|
||||||
|
|
||||||
.Lrcsmall:
|
|
||||||
movw $resmin, %si
|
|
||||||
call print16
|
|
||||||
movw $0x0143, %cx
|
|
||||||
ret
|
|
||||||
|
|
||||||
.Lrcnone:
|
|
||||||
movw $resnone, %si
|
|
||||||
call print16
|
|
||||||
movw $0x0143, %cx
|
|
||||||
ret
|
|
||||||
|
|
||||||
# 32-bit colour throughout: the framebuffer console, the fbdev ioctls and
|
|
||||||
# Schcom all assume 32bpp, and a 24bpp mode would be a different renderer
|
|
||||||
# rather than a different number here.
|
|
||||||
#
|
|
||||||
# Numbers are the VESA/Bochs 0x140 block, and only ones actually verified to
|
|
||||||
# come back as the advertised size at 32bpp are listed. 0x146 was in here as
|
|
||||||
# 1600x1200 and returns 320x200x8, which is why the caller now checks the depth
|
|
||||||
# rather than trusting the table.
|
|
||||||
#
|
|
||||||
# Adding a preset means checking it the same way -- boot it and read the
|
|
||||||
# kernel's "VGA:" line. The real fix is to enumerate the BIOS's own mode list
|
|
||||||
# (INT 10h AX=4F00) and match on width/height, which would also allow typing an
|
|
||||||
# arbitrary size; that is the next step here, not a guess at more numbers.
|
|
||||||
resmodes:
|
|
||||||
.word 0x0143 # 800x600 verified
|
|
||||||
.word 0x0144 # 1024x768 verified
|
|
||||||
.word 0x0145 # 1280x1024 verified
|
|
||||||
|
|
||||||
resprompt:
|
|
||||||
.asciz "Press R for resolution... "
|
|
||||||
restimeout:
|
|
||||||
.asciz "keeping 800x600\r\n"
|
|
||||||
resmenu:
|
|
||||||
.asciz "\r\n 1) 800x600 2) 1024x768 3) 1280x1024 4) type a size\r\n choice: "
|
|
||||||
resok:
|
|
||||||
.asciz "\r\n"
|
|
||||||
reswidth:
|
|
||||||
.asciz " width: "
|
|
||||||
resheight:
|
|
||||||
.asciz "\r\n height: "
|
|
||||||
resmin:
|
|
||||||
.asciz " below the 800x600 minimum, keeping 800x600\r\n"
|
|
||||||
resnone:
|
|
||||||
.asciz " no such 32bpp mode, keeping 800x600\r\n"
|
|
||||||
|
|
||||||
bootmsg:
|
|
||||||
.asciz "SchOS booting...\r\n"
|
|
||||||
|
|
||||||
.code32
|
|
||||||
start32:
|
|
||||||
movw $(SEG_KDATA<<3), %ax
|
|
||||||
movw %ax, %ds
|
|
||||||
movw %ax, %es
|
|
||||||
movw %ax, %ss
|
|
||||||
xorw %ax, %ax
|
|
||||||
movw %ax, %fs
|
|
||||||
movw %ax, %gs
|
|
||||||
|
|
||||||
movl $0x7c00, %esp
|
|
||||||
|
|
||||||
/* ---------------------------------------------------------------------
|
|
||||||
* Climb into long mode here, rather than leaving it to the kernel.
|
|
||||||
*
|
|
||||||
* The kernel has always had a 32-bit multiboot entry that does this climb
|
|
||||||
* itself, and the UEFI loader arrives already in long mode and uses a
|
|
||||||
* separate 64-bit entry. Two entries meant two versions of the setup, and
|
|
||||||
* that is exactly where EFER.SCE went missing on one of them for months:
|
|
||||||
* the 32-bit path set it, the 64-bit path did not, and nothing noticed
|
|
||||||
* until a program tried to return from a syscall.
|
|
||||||
*
|
|
||||||
* Climbing here means both loaders hand over in the same state, through the
|
|
||||||
* same kernel entry, and there is one copy of these preconditions instead of
|
|
||||||
* two that can drift.
|
|
||||||
*
|
|
||||||
* The tables are built exactly where the UEFI loader builds them, and mean
|
|
||||||
* 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.
|
|
||||||
* ------------------------------------------------------------------- */
|
|
||||||
|
|
||||||
/* Zero the two table pages at 0x1000-0x3000. */
|
|
||||||
movl $0x1000, %edi
|
|
||||||
movl $0x2000, %ecx
|
|
||||||
xorl %eax, %eax
|
|
||||||
rep stosb
|
|
||||||
|
|
||||||
/* pml4[0] and pml4[256] both -> the one PDPT at 0x2000. Entry 256 is
|
|
||||||
* 0xffff800000000000 >> 39, and its slot is at 0x1000 + 256*8 = 0x1800. */
|
|
||||||
movl $(0x2000 | PTE_P | PTE_W), 0x1000
|
|
||||||
movl $(0x2000 | PTE_P | PTE_W), 0x1800
|
|
||||||
|
|
||||||
/* pdpt[0..3]: four 1 GB pages covering the low 4 GB. */
|
|
||||||
movl $(0x00000000 | PTE_P | PTE_W | PTE_PS), 0x2000
|
|
||||||
movl $(0x40000000 | PTE_P | PTE_W | PTE_PS), 0x2008
|
|
||||||
movl $(0x80000000 | PTE_P | PTE_W | PTE_PS), 0x2010
|
|
||||||
movl $(0xC0000000 | PTE_P | PTE_W | PTE_PS), 0x2018
|
|
||||||
|
|
||||||
/* PAE. Long mode has no other paging format. */
|
|
||||||
movl %cr4, %eax
|
|
||||||
bts $5, %eax
|
|
||||||
movl %eax, %cr4
|
|
||||||
|
|
||||||
movl $0x1000, %eax
|
|
||||||
movl %eax, %cr3
|
|
||||||
|
|
||||||
/* EFER: LME to enter long mode, SCE so SYSCALL works. SCE belongs here and
|
|
||||||
* not in the kernel, so that whichever loader ran, the kernel starts able
|
|
||||||
* to return from a system call. */
|
|
||||||
movl $MSR_EFER, %ecx
|
|
||||||
rdmsr
|
|
||||||
bts $0, %eax /* SCE */
|
|
||||||
bts $8, %eax /* LME */
|
|
||||||
wrmsr
|
|
||||||
|
|
||||||
/* Paging on: this is the instruction that actually enters long mode. */
|
|
||||||
movl %cr0, %eax
|
|
||||||
orl $CR0_PG, %eax
|
|
||||||
movl %eax, %cr0
|
|
||||||
|
|
||||||
lgdt gdt64desc
|
|
||||||
ljmp $0x08, $start64
|
|
||||||
|
|
||||||
spin:
|
|
||||||
jmp spin
|
|
||||||
|
|
||||||
.code64
|
|
||||||
start64:
|
|
||||||
movw $0x10, %ax
|
|
||||||
movw %ax, %ds
|
|
||||||
movw %ax, %es
|
|
||||||
movw %ax, %ss
|
|
||||||
xorw %ax, %ax
|
|
||||||
movw %ax, %fs
|
|
||||||
movw %ax, %gs
|
|
||||||
|
|
||||||
movq $0x7c00, %rsp
|
|
||||||
call stage2main
|
|
||||||
|
|
||||||
spin64:
|
|
||||||
jmp spin64
|
|
||||||
|
|
||||||
/* A 64-bit GDT. The 32-bit one above got us into protected mode; long mode
|
|
||||||
* needs a code descriptor with the L bit, which a 32-bit descriptor cannot
|
|
||||||
* express. Selector 0x08 is code, 0x10 is data. */
|
|
||||||
.p2align 3
|
|
||||||
gdt64:
|
|
||||||
.quad 0
|
|
||||||
.quad 0x00209A0000000000 /* code: present, DPL0, executable, L=1 */
|
|
||||||
.quad 0x0000920000000000 /* data: present, DPL0, writable */
|
|
||||||
gdt64desc:
|
|
||||||
.word (gdt64desc - gdt64 - 1)
|
|
||||||
.quad gdt64
|
|
||||||
|
|
||||||
.p2align 2
|
|
||||||
gdt:
|
|
||||||
SEG_NULLASM
|
|
||||||
SEG_ASM(STA_X|STA_R, 0x0, 0xffffffff)
|
|
||||||
SEG_ASM(STA_W, 0x0, 0xffffffff)
|
|
||||||
|
|
||||||
gdtdesc:
|
|
||||||
.word (gdtdesc - gdt - 1)
|
|
||||||
.long gdt
|
|
||||||
|
|
@ -1,698 +0,0 @@
|
||||||
#include "../include/types.h"
|
|
||||||
#include "../include/x86.h"
|
|
||||||
#include "../include/memlayout.h"
|
|
||||||
|
|
||||||
#define SECTSIZE 512
|
|
||||||
|
|
||||||
// Kernel starts right after stage2 on disk. The Makefile passes
|
|
||||||
// -DSTAGE2_SECTORS so this, bootasm.S and stage2's padding cannot drift apart;
|
|
||||||
// the value here is a fallback for reading the file on its own.
|
|
||||||
#ifndef STAGE2_SECTORS
|
|
||||||
#define STAGE2_SECTORS 48
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// stage2 is loaded at 0x7E00 and must not reach the multiboot kernel-search
|
|
||||||
// buffer at 0x10000. Nothing checks this at runtime, and the failure it would
|
|
||||||
// produce -- a kernel image partly overwritten by the loader that loaded it --
|
|
||||||
// would be spectacularly hard to read backwards from.
|
|
||||||
_Static_assert(0x7E00 + STAGE2_SECTORS * 512 <= 0x10000,
|
|
||||||
"stage2 would overlap the kernel-search buffer at 0x10000");
|
|
||||||
|
|
||||||
// One palette for the whole system; see include/theme.h. The splash used to
|
|
||||||
// use the panic screen's deep purple and gold, which meant a healthy boot was
|
|
||||||
// dressed identically to a crash.
|
|
||||||
#include "theme.h"
|
|
||||||
// The boot logo, generated from Assets/ by tools/mklogo.py. Shared verbatim
|
|
||||||
// with kernel/dev/vga.c so there is one mask, not two.
|
|
||||||
#include "../common/ui.h"
|
|
||||||
|
|
||||||
// VBE mode info block, written here by stage2.S's VBE call. NOT at 0x9000
|
|
||||||
// (the classic single-stage bootloader's choice) — stage2's own code now
|
|
||||||
// runs from 0x7E00 up to nearly 0x9000 once the boot-menu/progress-bar
|
|
||||||
// logic is included, so a block at 0x9000 would get partially overwritten
|
|
||||||
// by stage2's own tail end. 0x30000 is clear of stage2, the multiboot
|
|
||||||
// kernel-search buffer (0x10000-0x14000), and the kernel's load address
|
|
||||||
// (0x100000).
|
|
||||||
#include "../../include/schboot.h" /* the hand-off block */
|
|
||||||
|
|
||||||
// Fixed low-memory scratch byte, read by kernel/core/main.c's kmain() before
|
|
||||||
// any other init: 1 if the user picked "verbose" from the boot menu. Placed
|
|
||||||
// just past the VBE info block (which is at most 256 bytes), NOT at 0x7000
|
|
||||||
// — kernel/core/main.c's startothers() already uses 0x7000 for the AP
|
|
||||||
// (secondary CPU) boot trampoline. That's write-after-read from this flag's
|
|
||||||
// perspective (verbose is read before startothers() runs), so it wasn't an
|
|
||||||
// active bug, but reusing a documented-elsewhere address is exactly the
|
|
||||||
// kind of thing that turns into one later.
|
|
||||||
|
|
||||||
// Set by boot/bios/cdboot.S when we were booted from optical media. One byte past
|
|
||||||
// the verbose flag, for the same reason that one sits where it does: clear of
|
|
||||||
// the 256-byte VBE mode info block at SCHBOOT_VBE_INFO.
|
|
||||||
|
|
||||||
// E820 memory map, collected by stage2.S in real mode (the same firmware
|
|
||||||
// interface GRUB and Linux's boot stub use). Count at +0x200, then 24-byte
|
|
||||||
// entries: {uint64 base; uint64 length; uint32 type; uint32 acpi_ext}.
|
|
||||||
// The kernel can read the same addresses later; the bootloader uses it to
|
|
||||||
// report detected RAM like any grown-up loader does.
|
|
||||||
#define E820_USABLE 1
|
|
||||||
|
|
||||||
// NOT include/types.h's uint64: that's `unsigned long`, which is 4 bytes
|
|
||||||
// in this file's -m32 build and would silently halve the struct.
|
|
||||||
typedef unsigned long long e820_u64;
|
|
||||||
|
|
||||||
struct e820entry {
|
|
||||||
e820_u64 base;
|
|
||||||
e820_u64 length;
|
|
||||||
uint32 type;
|
|
||||||
uint32 acpi_ext;
|
|
||||||
} __attribute__((packed));
|
|
||||||
|
|
||||||
// Total usable RAM in MB according to the firmware's E820 map.
|
|
||||||
static uint32
|
|
||||||
e820_usable_mb(void)
|
|
||||||
{
|
|
||||||
uint32 count = *(uint32*)SCHBOOT_E820_COUNT;
|
|
||||||
struct e820entry *e = (struct e820entry*)SCHBOOT_E820_MAP;
|
|
||||||
e820_u64 total = 0;
|
|
||||||
if (count > 32) return 0; // garbage count: BIOS didn't fill the map
|
|
||||||
for (uint32 i = 0; i < count; i++)
|
|
||||||
if (e[i].type == E820_USABLE)
|
|
||||||
total += e[i].length;
|
|
||||||
return (uint32)(total >> 20);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Minimal unsigned-to-decimal for the memory line; no printf down here.
|
|
||||||
static char *
|
|
||||||
utoa10(uint32 v, char *buf)
|
|
||||||
{
|
|
||||||
char tmp[12];
|
|
||||||
int n = 0;
|
|
||||||
do { tmp[n++] = '0' + v % 10; v /= 10; } while (v);
|
|
||||||
int i = 0;
|
|
||||||
while (n) buf[i++] = tmp[--n];
|
|
||||||
buf[i] = 0;
|
|
||||||
return buf;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct mbheader {
|
|
||||||
uint32 magic;
|
|
||||||
uint32 flags;
|
|
||||||
uint32 checksum;
|
|
||||||
uint32 header_addr;
|
|
||||||
uint32 load_addr;
|
|
||||||
uint32 load_end_addr;
|
|
||||||
uint32 bss_end_addr;
|
|
||||||
uint32 entry_addr;
|
|
||||||
};
|
|
||||||
|
|
||||||
void readseg(uchar*, uint, uint);
|
|
||||||
|
|
||||||
// Which medium the kernel has to be read back from. cdboot.S sets the flag
|
|
||||||
// this is loaded from; bootasm.S clears it, and 0 means "an ATA disk, the way
|
|
||||||
// it has always been". The ATAPI machinery itself is down at the bottom of
|
|
||||||
// the file next to readsect(), which is the only thing that uses it.
|
|
||||||
#define CD_SECTSIZE 2048
|
|
||||||
|
|
||||||
static int g_from_cd;
|
|
||||||
static int g_atapi_io = 0; // 0 until a drive is found
|
|
||||||
static int g_atapi_slave = 0;
|
|
||||||
static uint g_cd_base512 = 0; // where our boot image starts, in 512s
|
|
||||||
static int atapi_find(void);
|
|
||||||
static int atapi_boot_base(void);
|
|
||||||
|
|
||||||
// --- Framebuffer state ---------------------------------------------------
|
|
||||||
// Standalone drawing (not the kernel's vga.c): paging isn't set up yet, so
|
|
||||||
// the LFB physical address read out of the VBE mode info block is used
|
|
||||||
// directly, no P2V translation needed. Only handles 32bpp (what QEMU's
|
|
||||||
// std VGA actually returns for the mode this bootloader requests) —
|
|
||||||
// anything else just skips all drawing rather than risk drawing garbage.
|
|
||||||
|
|
||||||
static uint8 *g_fb;
|
|
||||||
static uint32 g_stride;
|
|
||||||
static int g_width, g_height;
|
|
||||||
static int g_have_fb;
|
|
||||||
|
|
||||||
static void
|
|
||||||
fb_init(void)
|
|
||||||
{
|
|
||||||
g_stride = *(ushort*)(SCHBOOT_VBE_STRIDE);
|
|
||||||
g_width = *(ushort*)(SCHBOOT_VBE_WIDTH);
|
|
||||||
g_height = *(ushort*)(SCHBOOT_VBE_HEIGHT);
|
|
||||||
uchar bpp = *(uchar*)(SCHBOOT_VBE_BPP);
|
|
||||||
uint32 fbaddr = *(uint32*)(SCHBOOT_VBE_LFB);
|
|
||||||
|
|
||||||
g_have_fb = (bpp == 32 && fbaddr && g_width && g_height);
|
|
||||||
|
|
||||||
// Hand it to the shared UI and stop drawing here. Everything this loader used
|
|
||||||
// to do with pixels -- the mark, the bar, the text -- is now the same code
|
|
||||||
// the UEFI loader runs, so the two cannot look different by accident. They
|
|
||||||
// did: two implementations of one splash, and only one of them ever got a
|
|
||||||
// change.
|
|
||||||
struct ui_fb uifb;
|
|
||||||
uifb.base = (ui_u8 *)(unsigned long)fbaddr;
|
|
||||||
uifb.width = g_width;
|
|
||||||
uifb.height = g_height;
|
|
||||||
uifb.stride = g_stride;
|
|
||||||
ui_init(g_have_fb ? &uifb : 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// --- Boot logo -------------------------------------------------------------
|
|
||||||
//
|
|
||||||
// include/logo.h is a 4-bit coverage mask generated from Assets/ by
|
|
||||||
// tools/mklogo.py (`make logo`). It costs 8 KiB, which is most of the reason
|
|
||||||
// STAGE2_SECTORS went from 24 to 48.
|
|
||||||
//
|
|
||||||
// Colour is SCH_FG, not SCH_ACCENT. The source art is a silver gradient and the
|
|
||||||
// mask preserves its relative values, so compositing coverage straight to
|
|
||||||
// brightness reproduces that gradient. The accent stays reserved for the single
|
|
||||||
// rule under the wordmark -- one spot colour, per include/theme.h.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Pads with trailing spaces (drawn in bg) so re-drawing a shorter string
|
|
||||||
// over a longer one doesn't leave stale glyphs behind.
|
|
||||||
|
|
||||||
// --- Keyboard: raw PS/2 scancode polling, no interrupts, no BIOS calls --
|
|
||||||
// (BIOS INT 16h only works in real mode; we're in protected mode by the
|
|
||||||
// time any of this runs.) Scancode set 1: F8 make code 0x42, arrows are
|
|
||||||
// extended (0xE0 prefix byte), Enter 0x1C, digits '1'-'3' are 0x02-0x04.
|
|
||||||
|
|
||||||
static int
|
|
||||||
kbd_haskey(void)
|
|
||||||
{
|
|
||||||
return inb(0x64) & 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
kbd_readscancode(void)
|
|
||||||
{
|
|
||||||
return inb(0x60);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Blocking read of one logical key: plain scancodes pass through, extended
|
|
||||||
// (0xE0-prefixed) scancodes come back OR'd with 0x100 so callers can tell
|
|
||||||
// "up arrow" (0x148) apart from a plain byte that happens to equal 0x48.
|
|
||||||
static int
|
|
||||||
kbd_getkey(void)
|
|
||||||
{
|
|
||||||
int sc;
|
|
||||||
for (;;) {
|
|
||||||
while (!kbd_haskey())
|
|
||||||
;
|
|
||||||
sc = kbd_readscancode();
|
|
||||||
// 0xE0 is the extended-key prefix, not a break code, even though it
|
|
||||||
// also has bit 7 set — must be checked before the break-code test
|
|
||||||
// below or every arrow/Home/End/etc. press gets silently swallowed.
|
|
||||||
if (sc == 0xE0) {
|
|
||||||
while (!kbd_haskey())
|
|
||||||
;
|
|
||||||
sc = kbd_readscancode();
|
|
||||||
if (sc & 0x80) continue; // extended break code: ignore, try again
|
|
||||||
return 0x100 | sc;
|
|
||||||
}
|
|
||||||
if (sc & 0x80) continue; // plain break code: ignore, try again
|
|
||||||
return sc;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Polls for up to `iterations` busy-loop spins, watching for F8's make
|
|
||||||
// code. Returns 1 if seen, 0 on timeout. Ignores everything else so a
|
|
||||||
// stray keypress during the splash doesn't do anything surprising.
|
|
||||||
static int
|
|
||||||
wait_for_f8(uint32 iterations)
|
|
||||||
{
|
|
||||||
for (volatile uint32 i = 0; i < iterations; i++) {
|
|
||||||
if (kbd_haskey()) {
|
|
||||||
int sc = kbd_readscancode();
|
|
||||||
if (sc == 0x42)
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- ATA drive presence (primary channel: master/slave) ------------------
|
|
||||||
// Standard "floating bus" trick: select the drive, write a distinguishable
|
|
||||||
// pattern to two data-path registers, and read it back. A missing drive
|
|
||||||
// leaves the bus floating and the readback won't match.
|
|
||||||
//
|
|
||||||
// Only the primary channel (0x1F0) is checked. QEMU's IDE emulation
|
|
||||||
// answers the same pattern test "successfully" on the secondary channel
|
|
||||||
// (0x170) even with nothing attached to it — its floating-bus behavior
|
|
||||||
// doesn't match real hardware closely enough to tell present from absent —
|
|
||||||
// so showing it would just be confidently wrong. This project only ever
|
|
||||||
// configures drives on the primary channel (-hda/-hdb) anyway.
|
|
||||||
|
|
||||||
static int
|
|
||||||
ata_present(ushort iobase, int slave)
|
|
||||||
{
|
|
||||||
outb(iobase + 6, slave ? 0xB0 : 0xA0);
|
|
||||||
inb(iobase + 7); inb(iobase + 7); inb(iobase + 7); inb(iobase + 7);
|
|
||||||
outb(iobase + 2, 0xAA);
|
|
||||||
outb(iobase + 3, 0x55);
|
|
||||||
outb(iobase + 2, 0x55);
|
|
||||||
outb(iobase + 3, 0xAA);
|
|
||||||
outb(iobase + 2, 0xAA);
|
|
||||||
outb(iobase + 3, 0x55);
|
|
||||||
return inb(iobase + 2) == 0xAA && inb(iobase + 3) == 0x55;
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Real-time load progress bar -----------------------------------------
|
|
||||||
|
|
||||||
#define BAR_WIDTH 400
|
|
||||||
#define BAR_HEIGHT 18
|
|
||||||
|
|
||||||
// --- Splash layout -------------------------------------------------------
|
|
||||||
//
|
|
||||||
// Every vertical position on the splash derives from here. It used to be a
|
|
||||||
// handful of independent offsets from the screen centre -- wordmark at cy-18,
|
|
||||||
// rule at cy+6, bar at cy+30 -- which was fine while the tallest element was a
|
|
||||||
// line of text. Adding a 128px mark on top of that stack pushed the group's
|
|
||||||
// optical centre about 80px above the screen's, so the whole composition read
|
|
||||||
// as top-heavy with a large void underneath.
|
|
||||||
//
|
|
||||||
// Now the group is measured and centred as a unit. The progress bar counts
|
|
||||||
// towards that height even though it is only on screen during the kernel load:
|
|
||||||
// if it did not, everything would jump upwards the moment the bar appeared.
|
|
||||||
#define SPLASH_MARK_GAP 24 // mark to wordmark
|
|
||||||
#define SPLASH_TEXT_H 16 // one line of the 8x16 font
|
|
||||||
#define SPLASH_RULE_GAP 8 // wordmark to rule
|
|
||||||
#define SPLASH_RULE_H 2
|
|
||||||
#define SPLASH_BAR_GAP 30 // rule to bar; the bar's label lives in here
|
|
||||||
|
|
||||||
#define SPLASH_GROUP_H (LOGO_H + SPLASH_MARK_GAP + SPLASH_TEXT_H + \
|
|
||||||
SPLASH_RULE_GAP + SPLASH_RULE_H + SPLASH_BAR_GAP + BAR_HEIGHT)
|
|
||||||
|
|
||||||
#define SPLASH_MARK_Y ((g_height - SPLASH_GROUP_H) / 2)
|
|
||||||
#define SPLASH_TEXT_Y (SPLASH_MARK_Y + LOGO_H + SPLASH_MARK_GAP)
|
|
||||||
#define SPLASH_RULE_Y (SPLASH_TEXT_Y + SPLASH_TEXT_H + SPLASH_RULE_GAP)
|
|
||||||
#define SPLASH_BAR_Y (SPLASH_RULE_Y + SPLASH_RULE_H + SPLASH_BAR_GAP)
|
|
||||||
|
|
||||||
static uint32 g_load_total, g_load_done;
|
|
||||||
static int g_progress_active;
|
|
||||||
static int g_bar_x, g_bar_y;
|
|
||||||
static int g_last_pct;
|
|
||||||
|
|
||||||
|
|
||||||
// Border never changes once drawn — separated out so it's only ever drawn
|
|
||||||
// once per bar instead of being redrawn on every single tick.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// --- Boot menu -------------------------------------------------------
|
|
||||||
|
|
||||||
static void
|
|
||||||
reboot_to_firmware(void)
|
|
||||||
{
|
|
||||||
ui_clear();
|
|
||||||
ui_text_centered((int)ui_height() / 2 - 8, "Rebooting to firmware...",
|
|
||||||
SCH_ACCENT);
|
|
||||||
outb(0x64, 0xFE); // pulse the keyboard controller's reset line
|
|
||||||
for (;;)
|
|
||||||
;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Returns 1 for "boot verbose", 0 for "boot normal". Only returns at all
|
|
||||||
// once the user picks a boot option; "reboot" never returns.
|
|
||||||
static int
|
|
||||||
boot_menu(void)
|
|
||||||
{
|
|
||||||
const char *items[] = {
|
|
||||||
"Boot SchOS",
|
|
||||||
"Boot SchOS (verbose)",
|
|
||||||
"Reboot to firmware setup",
|
|
||||||
};
|
|
||||||
int nitems = 3;
|
|
||||||
int selected = 0;
|
|
||||||
|
|
||||||
int hda = ata_present(0x1F0, 0);
|
|
||||||
int hdb = ata_present(0x1F0, 1);
|
|
||||||
|
|
||||||
for (;;) {
|
|
||||||
// One shared panel rather than a screen laid out in literals. The drive
|
|
||||||
// list moved into it as ordinary rows: it was three bespoke helpers and a
|
|
||||||
// magic y of 340, which is why it only ever looked right at 800x600.
|
|
||||||
const char *labels[2] = { "Primary Master (hda)", "Primary Slave (hdb)" };
|
|
||||||
const char *values[2] = { hda ? "present" : "-", hdb ? "present" : "-" };
|
|
||||||
(void)labels; (void)values;
|
|
||||||
|
|
||||||
ui_menu("Boot Manager", items, nitems, selected,
|
|
||||||
"Up/Down to choose, Enter to select");
|
|
||||||
|
|
||||||
int key = kbd_getkey();
|
|
||||||
if (key == 0x148) selected = (selected + nitems - 1) % nitems; // up
|
|
||||||
else if (key == 0x150) selected = (selected + 1) % nitems; // down
|
|
||||||
else if (key == 0x02) selected = 0; // '1'
|
|
||||||
else if (key == 0x03) selected = 1; // '2'
|
|
||||||
else if (key == 0x04) { reboot_to_firmware(); } // '3'
|
|
||||||
else if (key == 0x1C) { // Enter
|
|
||||||
if (selected == 2)
|
|
||||||
reboot_to_firmware();
|
|
||||||
return selected == 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Centered one-liner helper; strlen computed here so callers stop
|
|
||||||
// hand-counting characters (three of them had it wrong at some point).
|
|
||||||
|
|
||||||
// "Memory: NNNN MB" — assembled by hand, no printf in the bootloader.
|
|
||||||
|
|
||||||
// The splash, which is now four lines because it is the shared one. The mark
|
|
||||||
// carries the name, so nothing is written under it -- the old "S C H U N I X"
|
|
||||||
// line was the name a second time, in a worse typeface.
|
|
||||||
static void
|
|
||||||
splash(void)
|
|
||||||
{
|
|
||||||
ui_splash("Press F8 for boot options");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Find the kernel's 64-bit entry: the SCH64 header, 8-byte aligned in the first
|
|
||||||
// 16 KB, exactly as include/schboot.h describes it. The same bytes the UEFI
|
|
||||||
// loader looks for, found the same way, because there is one contract.
|
|
||||||
static uint64
|
|
||||||
find_sch64_entry(const uchar *buf, unsigned len)
|
|
||||||
{
|
|
||||||
for (unsigned off = 0; off + sizeof(struct sch64_header) <= len; off += 8) {
|
|
||||||
const struct sch64_header *h = (const struct sch64_header *)(buf + off);
|
|
||||||
if (h->magic0 == SCH64_MAGIC0 && h->magic1 == SCH64_MAGIC1)
|
|
||||||
return h->entry;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
stage2main(void)
|
|
||||||
{
|
|
||||||
struct mbheader *hdr;
|
|
||||||
uint32 *x = (uint32*) 0x10000;
|
|
||||||
uint n;
|
|
||||||
int verbose = 0;
|
|
||||||
|
|
||||||
fb_init();
|
|
||||||
|
|
||||||
// Sort out where the kernel is coming from before anything is drawn, so a
|
|
||||||
// disc we cannot read says so on the splash instead of failing silently
|
|
||||||
// several seconds later in the middle of the progress bar.
|
|
||||||
if (*(uchar*)SCHBOOT_MEDIA_FLAG == SCHBOOT_MEDIA_CD) {
|
|
||||||
if (atapi_find())
|
|
||||||
g_cd_base512 = atapi_boot_base() * (CD_SECTSIZE / SECTSIZE);
|
|
||||||
g_from_cd = (g_atapi_io != 0 && g_cd_base512 != 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
splash();
|
|
||||||
|
|
||||||
// ~0.6s at QEMU TCG speeds (measured), watching for F8. Shortened from
|
|
||||||
// ~1s (5000000) — still enough time to react to, but it's flat overhead
|
|
||||||
// on every single boot whether or not anyone wants the menu.
|
|
||||||
if (wait_for_f8(3000000))
|
|
||||||
verbose = boot_menu();
|
|
||||||
|
|
||||||
*(uchar*)SCHBOOT_VERBOSE_FLAG = verbose;
|
|
||||||
|
|
||||||
// Back to the splash after the menu: the mark and the bar, which is what the
|
|
||||||
// rest of this function is about to fill in.
|
|
||||||
splash();
|
|
||||||
|
|
||||||
// Booted from a disc we then could not read. Falling through would send
|
|
||||||
// readseg() at the primary ATA channel, which on a CD-only machine is empty
|
|
||||||
// -- so it would grind through a bounded wait per sector and then jump into
|
|
||||||
// whatever the uninitialised buffer happened to hold. Stopping with a
|
|
||||||
// legible reason is the only useful thing left to do. This is drawn after
|
|
||||||
// the clear above on purpose: an earlier version put it on the splash,
|
|
||||||
// where it was wiped a moment later and nobody ever saw it.
|
|
||||||
if (*(uchar*)SCHBOOT_MEDIA_FLAG == SCHBOOT_MEDIA_CD && !g_from_cd) {
|
|
||||||
ui_text_centered(g_height / 2, "No readable boot disc found.", SCH_ACCENT);
|
|
||||||
ui_text_centered(g_height / 2 + 20, "The drive did not answer ATAPI.", SCH_DIM);
|
|
||||||
for (;;)
|
|
||||||
asm volatile("hlt");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (g_have_fb)
|
|
||||||
ui_progress_begin("Detecting kernel...", 16384);
|
|
||||||
|
|
||||||
// Read 32 sectors (16KB) looking for the multiboot header.
|
|
||||||
readseg((uchar*)x, 16384, 0);
|
|
||||||
|
|
||||||
for (n = 0; n < 16384/4; n++) {
|
|
||||||
if (x[n] == 0x1BADB002) {
|
|
||||||
if ((x[n] + x[n+1] + x[n+2]) == 0) {
|
|
||||||
hdr = (struct mbheader *) (x + n);
|
|
||||||
|
|
||||||
if (g_have_fb)
|
|
||||||
ui_progress_begin("Loading kernel...", hdr->load_end_addr - hdr->load_addr);
|
|
||||||
|
|
||||||
// Load the actual kernel segments
|
|
||||||
readseg((uchar*) hdr->load_addr,
|
|
||||||
hdr->load_end_addr - hdr->load_addr,
|
|
||||||
(n * 4) - (hdr->header_addr - hdr->load_addr));
|
|
||||||
|
|
||||||
// Zero out BSS
|
|
||||||
if (hdr->bss_end_addr > hdr->load_end_addr)
|
|
||||||
stosb((void*) hdr->load_end_addr, 0, hdr->bss_end_addr - hdr->load_end_addr);
|
|
||||||
|
|
||||||
if (g_have_fb)
|
|
||||||
ui_progress_label("Starting SchOS...");
|
|
||||||
|
|
||||||
// Enter through the kernel's 64-bit entry, not the multiboot one.
|
|
||||||
//
|
|
||||||
// The multiboot header still says where the kernel's segments go --
|
|
||||||
// that part is unchanged -- but stage2 is in long mode now, and the
|
|
||||||
// entry named in that header is 32-bit code whose whole job is to
|
|
||||||
// climb into it. Using it would mean dropping back to 32 bits to redo
|
|
||||||
// a climb already done, and a mode switch is not a thing to do twice
|
|
||||||
// for no reason.
|
|
||||||
//
|
|
||||||
// So the same SCH64 header the UEFI loader looks for is found here,
|
|
||||||
// and both loaders hand over identically: long mode, interrupts off,
|
|
||||||
// CR3 mapping the low 4 GB twice, framebuffer in RBX. One entry, one
|
|
||||||
// set of preconditions, nothing left to drift between two copies.
|
|
||||||
{
|
|
||||||
uint32 fbaddr = *(uint32*)(uint64)(SCHBOOT_VBE_LFB);
|
|
||||||
uint64 entry64 = find_sch64_entry((const uchar*)x, 16384);
|
|
||||||
|
|
||||||
// Nothing to fall back to: the 32-bit entry cannot be reached from
|
|
||||||
// long mode. Halt rather than jump somewhere arbitrary.
|
|
||||||
if (!entry64)
|
|
||||||
for (;;) asm volatile("hlt");
|
|
||||||
|
|
||||||
// RBX, because that is what entry64uefi reads. Not a C call -- the
|
|
||||||
// kernel entry is not a function and has no prologue to match.
|
|
||||||
asm volatile("jmp *%0" : : "r"(entry64), "b"((uint64)fbaddr));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bounded, because there might not be a disk there at all.
|
|
||||||
//
|
|
||||||
// This used to be `while((inb(0x1F7) & 0xC0) != 0x40);`. On a machine with no
|
|
||||||
// ATA drive on the primary channel the status register reads 0xFF forever and
|
|
||||||
// the loader hung on a splash screen with no message and no way to tell what
|
|
||||||
// had gone wrong -- which is exactly what happened the first time a CD boot
|
|
||||||
// fell back to this path. A bounded wait turns that into a failed read, and
|
|
||||||
// stage2main() says so on screen.
|
|
||||||
void waitdisk(void) {
|
|
||||||
for (int i = 0; i < 4000000; i++)
|
|
||||||
if ((inb(0x1F7) & 0xC0) == 0x40)
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
void readsect_ata(void *dst, uint offset) {
|
|
||||||
waitdisk();
|
|
||||||
outb(0x1F2, 1);
|
|
||||||
outb(0x1F3, offset);
|
|
||||||
outb(0x1F4, offset >> 8);
|
|
||||||
outb(0x1F5, offset >> 16);
|
|
||||||
outb(0x1F6, (offset >> 24) | 0xE0);
|
|
||||||
outb(0x1F7, 0x20);
|
|
||||||
waitdisk();
|
|
||||||
insl(0x1F0, dst, SECTSIZE/4);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// ATAPI -- reading the kernel back off a CD.
|
|
||||||
//
|
|
||||||
// An optical drive does not answer the ATA commands above. It speaks the
|
|
||||||
// packet interface: you hand it a SCSI command descriptor block through the
|
|
||||||
// data port and it hands back 2048-byte logical blocks. The register file and
|
|
||||||
// the PIO loop are otherwise the same, which is why this lives here rather
|
|
||||||
// than forcing the loader back into real mode to use INT 13h -- by the time
|
|
||||||
// the kernel is read, stage2.S has long since switched to protected mode and
|
|
||||||
// the BIOS is gone.
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
static void bcopy(void *dst, const void *src, uint n) {
|
|
||||||
uchar *d = dst;
|
|
||||||
const uchar *s = src;
|
|
||||||
while (n--) *d++ = *s++;
|
|
||||||
}
|
|
||||||
|
|
||||||
// A channel has to be reset before its signature means anything.
|
|
||||||
//
|
|
||||||
// ATAPI devices report themselves by leaving 0x14 in LBA-mid and 0xEB in
|
|
||||||
// LBA-high -- but only until something issues a command, after which those
|
|
||||||
// registers hold that command's byte count instead. The BIOS has just
|
|
||||||
// finished reading this very disc in order to load us, so on a CD boot the
|
|
||||||
// signature is *always* stale by the time we look. Resetting the channel is
|
|
||||||
// what puts it back.
|
|
||||||
static void atapi_reset(int io) {
|
|
||||||
int ctrl = io + 0x206; // 0x1F0 -> 0x3F6, 0x170 -> 0x376
|
|
||||||
|
|
||||||
outb(ctrl, 0x04); // SRST
|
|
||||||
for (int i = 0; i < 10000; i++) // hold it well past the required 5us
|
|
||||||
inb(ctrl);
|
|
||||||
outb(ctrl, 0x00);
|
|
||||||
|
|
||||||
for (int i = 0; i < 1000000; i++) // bounded: an empty channel reads 0xFF
|
|
||||||
if (!(inb(io + 7) & 0x80)) // and would otherwise spin forever
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int atapi_find(void) {
|
|
||||||
static const int bases[2] = { 0x1F0, 0x170 };
|
|
||||||
|
|
||||||
for (int b = 0; b < 2; b++) {
|
|
||||||
atapi_reset(bases[b]);
|
|
||||||
for (int s = 0; s < 2; s++) {
|
|
||||||
outb(bases[b] + 6, s ? 0xB0 : 0xA0);
|
|
||||||
// Four throwaway status reads: the standard 400ns settle after a drive
|
|
||||||
// select, before the register file is meaningful.
|
|
||||||
inb(bases[b] + 7); inb(bases[b] + 7);
|
|
||||||
inb(bases[b] + 7); inb(bases[b] + 7);
|
|
||||||
if (inb(bases[b] + 4) == 0x14 && inb(bases[b] + 5) == 0xEB) {
|
|
||||||
g_atapi_io = bases[b];
|
|
||||||
g_atapi_slave = s;
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// One 2048-byte logical block. Returns 0 on success.
|
|
||||||
static int atapi_read_block(uint lba, void *dst) {
|
|
||||||
int io = g_atapi_io;
|
|
||||||
uchar cdb[12];
|
|
||||||
int i;
|
|
||||||
|
|
||||||
outb(io + 6, g_atapi_slave ? 0xB0 : 0xA0);
|
|
||||||
inb(io + 7); inb(io + 7); inb(io + 7); inb(io + 7);
|
|
||||||
|
|
||||||
outb(io + 1, 0); // features: PIO, no DMA
|
|
||||||
outb(io + 4, CD_SECTSIZE & 0xFF); // how much we are willing to take
|
|
||||||
outb(io + 5, CD_SECTSIZE >> 8);
|
|
||||||
outb(io + 7, 0xA0); // PACKET
|
|
||||||
|
|
||||||
// Wait for the drive to ask for the command packet: BSY clear, DRQ set.
|
|
||||||
for (i = 0; i < 0x1000000; i++) {
|
|
||||||
uchar st = inb(io + 7);
|
|
||||||
if (st & 0x01) return -1; // ERR
|
|
||||||
if (!(st & 0x80) && (st & 0x08)) break;
|
|
||||||
}
|
|
||||||
if (i == 0x1000000) return -1;
|
|
||||||
|
|
||||||
// READ(12). Big-endian LBA and transfer length, because SCSI is.
|
|
||||||
for (i = 0; i < 12; i++) cdb[i] = 0;
|
|
||||||
cdb[0] = 0xA8;
|
|
||||||
cdb[2] = (uchar)(lba >> 24);
|
|
||||||
cdb[3] = (uchar)(lba >> 16);
|
|
||||||
cdb[4] = (uchar)(lba >> 8);
|
|
||||||
cdb[5] = (uchar)lba;
|
|
||||||
cdb[9] = 1; // one block
|
|
||||||
outsl(io, cdb, 3); // 12 bytes = 3 dwords
|
|
||||||
|
|
||||||
// Wait for the data itself.
|
|
||||||
for (i = 0; i < 0x1000000; i++) {
|
|
||||||
uchar st = inb(io + 7);
|
|
||||||
if (st & 0x01) return -1;
|
|
||||||
if (!(st & 0x80) && (st & 0x08)) break;
|
|
||||||
}
|
|
||||||
if (i == 0x1000000) return -1;
|
|
||||||
|
|
||||||
insl(io, dst, CD_SECTSIZE / 4);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Where our boot image sits on the disc.
|
|
||||||
//
|
|
||||||
// Sector numbers on a CD are absolute, but readseg() counts from the start of
|
|
||||||
// the boot image -- which on a hard disk is the start of the disk and on a CD
|
|
||||||
// is wherever the ISO's filesystem happened to leave room. El Torito writes
|
|
||||||
// that address down, so we read it rather than guess:
|
|
||||||
//
|
|
||||||
// LBA 17 Boot Record Volume Descriptor, holding a pointer at offset 0x47
|
|
||||||
// catalog validation entry (32 bytes), then the default entry, whose
|
|
||||||
// "Load RBA" at offset 8 is the boot image's LBA
|
|
||||||
//
|
|
||||||
// Returns 0 if the disc does not look like something we booted from.
|
|
||||||
static int atapi_boot_base(void) {
|
|
||||||
static uchar buf[CD_SECTSIZE];
|
|
||||||
uint catalog, image;
|
|
||||||
|
|
||||||
if (atapi_read_block(17, buf) != 0)
|
|
||||||
return 0;
|
|
||||||
if (buf[0] != 0 || buf[1] != 'C' || buf[2] != 'D' || buf[3] != '0' ||
|
|
||||||
buf[4] != '0' || buf[5] != '1')
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
catalog = (uint)buf[0x47] | ((uint)buf[0x48] << 8) |
|
|
||||||
((uint)buf[0x49] << 16) | ((uint)buf[0x4A] << 24);
|
|
||||||
if (catalog == 0)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
if (atapi_read_block(catalog, buf) != 0)
|
|
||||||
return 0;
|
|
||||||
if (buf[32] != 0x88) // default entry not marked bootable
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
image = (uint)buf[40] | ((uint)buf[41] << 8) |
|
|
||||||
((uint)buf[42] << 16) | ((uint)buf[43] << 24);
|
|
||||||
return image;
|
|
||||||
}
|
|
||||||
|
|
||||||
// A CD block holds four 512-byte sectors, and readseg() asks for them one at
|
|
||||||
// a time and in order -- so caching the last block turns four reads into one.
|
|
||||||
// Without it every byte of the kernel would be fetched four times.
|
|
||||||
static uchar g_cd_cache[CD_SECTSIZE];
|
|
||||||
static uint g_cd_cached = 0xFFFFFFFF;
|
|
||||||
|
|
||||||
static void readsect_atapi(void *dst, uint offset) {
|
|
||||||
uint abs = offset + g_cd_base512;
|
|
||||||
uint blk = abs / (CD_SECTSIZE / SECTSIZE);
|
|
||||||
uint sub = abs % (CD_SECTSIZE / SECTSIZE);
|
|
||||||
|
|
||||||
if (blk != g_cd_cached) {
|
|
||||||
if (atapi_read_block(blk, g_cd_cache) != 0) {
|
|
||||||
// Nothing useful to do here: there is no console yet and no way back.
|
|
||||||
// Leaving the buffer alone means the multiboot search simply fails to
|
|
||||||
// match, which stops with the splash up rather than jumping to garbage.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
g_cd_cached = blk;
|
|
||||||
}
|
|
||||||
bcopy(dst, g_cd_cache + sub * SECTSIZE, SECTSIZE);
|
|
||||||
}
|
|
||||||
|
|
||||||
void readsect(void *dst, uint offset) {
|
|
||||||
if (g_from_cd)
|
|
||||||
readsect_atapi(dst, offset);
|
|
||||||
else
|
|
||||||
readsect_ata(dst, offset);
|
|
||||||
ui_progress_tick(SECTSIZE);
|
|
||||||
}
|
|
||||||
|
|
||||||
void readseg(uchar* pa, uint count, uint offset) {
|
|
||||||
uchar* epa = pa + count;
|
|
||||||
pa -= offset % SECTSIZE;
|
|
||||||
// Kernel starts right after stage2 (1 boot sector + STAGE2_SECTORS
|
|
||||||
// stage2 sectors), not immediately after just the boot sector.
|
|
||||||
offset = (offset / SECTSIZE) + (1 + STAGE2_SECTORS);
|
|
||||||
for(; pa < epa; pa += SECTSIZE, offset++)
|
|
||||||
readsect(pa, offset);
|
|
||||||
}
|
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
<!-- SPDX-License-Identifier: Apache-2.0 -->
|
<!-- SPDX-License-Identifier: Apache-2.0 -->
|
||||||
# UEFI loader
|
# UEFI loader
|
||||||
|
|
||||||
Not written yet. This directory exists so the BIOS path could be moved into
|
The loader. This directory once existed only so the BIOS path could be moved
|
||||||
`boot/bios/` without pretending the split was hypothetical.
|
into `boot/bios/` without pretending the split was hypothetical; it is now the
|
||||||
|
only path there is.
|
||||||
|
|
||||||
See `../README.md` for the hand-off contract a loader has to satisfy. The
|
See `../README.md` for the hand-off contract a loader has to satisfy. The
|
||||||
short version: the kernel wants long mode with paging on, its ELF segments
|
short version: the kernel wants long mode with paging on, its ELF segments
|
||||||
|
|
@ -22,7 +23,7 @@ What this will need, in rough order:
|
||||||
parses. Not a second format for the kernel to learn.
|
parses. Not a second format for the kernel to learn.
|
||||||
4. `ExitBootServices` before touching the memory it described.
|
4. `ExitBootServices` before touching the memory it described.
|
||||||
5. Graphics Output Protocol for the framebuffer, filling the same
|
5. Graphics Output Protocol for the framebuffer, filling the same
|
||||||
`VBE_INFO_ADDR` structure the BIOS path fills.
|
`VBE_INFO_ADDR` structure the kernel reads.
|
||||||
|
|
||||||
The image goes at `EFI/BOOT/BOOTX64.EFI` on a FAT ESP, which also means the
|
The image goes at `EFI/BOOT/BOOTX64.EFI` on a FAT ESP, which also means the
|
||||||
ISO gains an El Torito EFI entry alongside the BIOS one it already has.
|
ISO carries a single El Torito EFI entry.
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ Git Bash or MSYS2). The build is developed from both Windows and Linux, so a
|
||||||
change that only works on one of them is a bug.
|
change that only works on one of them is a bug.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
make # kernel + bootloader + root filesystem -> schos.img
|
make # kernel + UEFI loader + root filesystem
|
||||||
make qemu # build and boot it
|
make qemu # build and boot it
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -52,7 +52,7 @@ schos/
|
||||||
|
|
||||||
```
|
```
|
||||||
build/
|
build/
|
||||||
├─ boot/ bootblock, stage2
|
├─ boot/ uefi/ loader, common/ UI
|
||||||
├─ kernel/ object files, the linked kernel, kernel.asm
|
├─ kernel/ object files, the linked kernel, kernel.asm
|
||||||
├─ lib/ libc glue objects, initcode blob
|
├─ lib/ libc glue objects, initcode blob
|
||||||
├─ cmd/ linked first-party programs
|
├─ cmd/ linked first-party programs
|
||||||
|
|
@ -114,8 +114,9 @@ compiled objects first. Switching back is the same — plain `make` after a
|
||||||
`ramroot` build reuses stale objects, so run `rm -rf build/kernel build/lib
|
`ramroot` build reuses stale objects, so run `rm -rf build/kernel build/lib
|
||||||
build/cmd` first (or just `make clean`).
|
build/cmd` first (or just `make clean`).
|
||||||
|
|
||||||
The image costs boot time: stage2 loads the kernel by PIO one sector at a time,
|
The image costs boot time: the loader reads the kernel through the firmware's
|
||||||
so a 6 MB ramroot kernel is visibly slower to start than the 350 KB default.
|
file protocol, so a 6 MB ramroot kernel is visibly slower to start than the
|
||||||
|
350 KB default.
|
||||||
|
|
||||||
**What may go in it is a licensing question, not only a size one.** The image is
|
**What may go in it is a licensing question, not only a size one.** The image is
|
||||||
linked into the kernel binary, so anything in `sysroot/` when it is built becomes
|
linked into the kernel binary, so anything in `sysroot/` when it is built becomes
|
||||||
|
|
@ -154,22 +155,16 @@ Makefile has `--redefine-sym` lines mapping e.g.
|
||||||
blob's build location and those symbol names change; update the Makefile to
|
blob's build location and those symbol names change; update the Makefile to
|
||||||
match or the kernel fails to link.
|
match or the kernel fails to link.
|
||||||
|
|
||||||
**`STAGE2_SECTORS` now lives in exactly one place: the root `Makefile`.** It used
|
**`STAGE2_SECTORS` is gone, with the BIOS loader it sized.** It is recorded here
|
||||||
to be written out by hand in five — `boot/bios/bootasm.S`, `boot/bios/stage2main.c`'s
|
because the shape of the bug outlived it: one number that four files each
|
||||||
`readseg()`, the `dd ... seek=` offset, and twice more inside `tools/posix/mkiso.sh`
|
believed independently — `boot/bios/bootasm.S`, `stage2main.c`'s `readseg()`,
|
||||||
(its stage2 size assertion and `-boot-load-size`). This note used to warn you to
|
the `dd ... seek=` offset, and twice inside `mkiso.sh`. Raising it from 24 to 48
|
||||||
keep three of them in step and did not know about the other two.
|
for the boot logo left the `dd seek=25` offset behind, so the kernel was written
|
||||||
|
directly over stage 2's own tail, and nothing said so.
|
||||||
|
|
||||||
Raising it from 24 to 48 for the boot logo demonstrated the problem: the `dd
|
The fix was to derive all five from one Makefile variable. The lesson worth
|
||||||
seek=25` offset stayed behind, so the kernel was written directly over stage2's
|
keeping is the other half: `tools/padstage2.pl` *failed the build* when stage 2
|
||||||
own tail. Now the Makefile passes `-DSTAGE2_SECTORS` to both boot sources,
|
overflowed its budget rather than truncating it, which is why the overflow to
|
||||||
derives the padding budget and the `seek=` offset from the same variable, and
|
25440 bytes against a 24576 limit was found at all. A layout constant that
|
||||||
exports it to `mkiso.sh`. The `#define`s that remain in the two boot sources are
|
several artifacts must agree on wants a check that stops the build, not a
|
||||||
`#ifndef`-guarded fallbacks so those files still read sensibly alone; the
|
comment asking people to remember.
|
||||||
Makefile always wins.
|
|
||||||
|
|
||||||
Two things still fail the build rather than silently misbooting, and should stay
|
|
||||||
that way: `tools/padstage2.pl` errors if stage2 overflows its budget instead of
|
|
||||||
truncating it, and `boot/bios/stage2main.c` static-asserts that stage2 cannot reach
|
|
||||||
the multiboot kernel-search buffer at `0x10000` — which puts the hard ceiling at
|
|
||||||
65 sectors, since stage2 loads at `0x7E00`.
|
|
||||||
|
|
|
||||||
|
|
@ -82,7 +82,7 @@ else needs it: `make`, `make schfs-check` and `make qemu` all work without.
|
||||||
## 4. Build and check
|
## 4. Build and check
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
make # kernel + root filesystem -> schos.img
|
make # kernel + root filesystem
|
||||||
make schfs-check # B-tree, object and image suites -- all host-native
|
make schfs-check # B-tree, object and image suites -- all host-native
|
||||||
make qemu # boot it
|
make qemu # boot it
|
||||||
```
|
```
|
||||||
|
|
|
||||||
21
run.ps1
21
run.ps1
|
|
@ -45,7 +45,7 @@ if (-not (Test-Path $Qemu)) { throw "QEMU not found: $Qemu (install it, or pass
|
||||||
# The two images QEMU needs. Keep in sync with the 'qemu' target in the
|
# The two images QEMU needs. Keep in sync with the 'qemu' target in the
|
||||||
# Makefile.
|
# Makefile.
|
||||||
#
|
#
|
||||||
# hda the boot disk: bootblock, stage 2 and the kernel
|
# esp the staged EFI system partition: the loader and the kernel
|
||||||
# hdb the root filesystem, mounted as / by forkret()
|
# hdb the root filesystem, mounted as / by forkret()
|
||||||
#
|
#
|
||||||
# hdb was build/fs.img until SchFS became the root. Nothing builds fs.img on
|
# hdb was build/fs.img until SchFS became the root. Nothing builds fs.img on
|
||||||
|
|
@ -53,8 +53,10 @@ if (-not (Test-Path $Qemu)) { throw "QEMU not found: $Qemu (install it, or pass
|
||||||
# longer exists -- QEMU came up with no usable disk on device 1 and the kernel
|
# longer exists -- QEMU came up with no usable disk on device 1 and the kernel
|
||||||
# panicked with SCHFS_ROOT. That is the failure this script is supposed to
|
# panicked with SCHFS_ROOT. That is the failure this script is supposed to
|
||||||
# prevent, so it is worth naming.
|
# prevent, so it is worth naming.
|
||||||
$hda = Join-Path $root 'schos.img'
|
$esp = Join-Path $root 'build/esp'
|
||||||
$hdb = Join-Path $root 'build/schfs.img'
|
$hdb = Join-Path $root 'build/schfs.img'
|
||||||
|
$code = Join-Path $root 'build/OVMF_CODE.fd'
|
||||||
|
$vars = Join-Path $root 'build/OVMF_VARS.fd'
|
||||||
|
|
||||||
if (-not $NoBuild) {
|
if (-not $NoBuild) {
|
||||||
Write-Host '==> building (native, no WSL)' -ForegroundColor Cyan
|
Write-Host '==> building (native, no WSL)' -ForegroundColor Cyan
|
||||||
|
|
@ -88,11 +90,16 @@ if (-not $NoBuild) {
|
||||||
if ($LASTEXITCODE -ne 0) { throw "build failed (exit $LASTEXITCODE)" }
|
if ($LASTEXITCODE -ne 0) { throw "build failed (exit $LASTEXITCODE)" }
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach ($img in @($hda, $hdb)) {
|
foreach ($img in @($hdb, (Join-Path $esp 'EFI/BOOT/BOOTX64.EFI'))) {
|
||||||
if (-not (Test-Path $img)) {
|
if (-not (Test-Path $img)) {
|
||||||
throw "missing image: $img (run without -NoBuild, or: make)"
|
throw "missing: $img (run without -NoBuild, or: make)"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
foreach ($f in @($code, $vars)) {
|
||||||
|
if (-not (Test-Path $f)) { throw "no $f -- run 'make qemu-uefi' once to fetch OVMF" }
|
||||||
|
}
|
||||||
|
$runVars = Join-Path $root 'build/run-OVMF_VARS.fd'
|
||||||
|
Copy-Item -Force $vars $runVars
|
||||||
|
|
||||||
# Guard against the exact failure this script exists to prevent: booting
|
# Guard against the exact failure this script exists to prevent: booting
|
||||||
# against a stale, empty or wrong-format root disk. The first uberblock sits in
|
# against a stale, empty or wrong-format root disk. The first uberblock sits in
|
||||||
|
|
@ -112,7 +119,9 @@ if ($buf[0] -ne 0x46 -or $buf[1] -ne 0x68 -or $buf[2] -ne 0x63 -or $buf[3] -ne 0
|
||||||
}
|
}
|
||||||
|
|
||||||
$qemuArgs = @(
|
$qemuArgs = @(
|
||||||
'-drive', "file=$hda,format=raw,index=0,media=disk"
|
'-drive', "if=pflash,format=raw,unit=0,readonly=on,file=$code"
|
||||||
|
'-drive', "if=pflash,format=raw,unit=1,file=$runVars"
|
||||||
|
'-drive', "file=fat:rw:$esp,index=0,media=disk"
|
||||||
'-drive', "file=$hdb,format=raw,index=1,media=disk"
|
'-drive', "file=$hdb,format=raw,index=1,media=disk"
|
||||||
'-m', '1G'
|
'-m', '1G'
|
||||||
'-smp', '2'
|
'-smp', '2'
|
||||||
|
|
|
||||||
|
|
@ -1,24 +0,0 @@
|
||||||
#!/usr/bin/perl
|
|
||||||
# Zero-pads the stage2 bootloader binary out to a fixed size (STAGE2_SECTORS
|
|
||||||
# * 512 bytes), so the kernel's on-disk starting sector is predictable
|
|
||||||
# regardless of stage2's actual compiled size. Errors out if stage2 has
|
|
||||||
# grown past that budget instead of silently truncating it.
|
|
||||||
|
|
||||||
my $size = $ARGV[1] || die "usage: padstage2.pl <file> <size-in-bytes>\n";
|
|
||||||
|
|
||||||
open(F, $ARGV[0]) || die "open $ARGV[0]: $!";
|
|
||||||
my $n = sysread(F, my $buf, $size + 1);
|
|
||||||
close(F);
|
|
||||||
|
|
||||||
if ($n > $size) {
|
|
||||||
print STDERR "stage2 too large: $n bytes (max $size)\n";
|
|
||||||
exit 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
print STDERR "stage2 is $n bytes (max $size)\n";
|
|
||||||
|
|
||||||
$buf .= "\0" x ($size - $n);
|
|
||||||
|
|
||||||
open(F, ">$ARGV[0]") || die "open >$ARGV[0]: $!";
|
|
||||||
print F $buf;
|
|
||||||
close(F);
|
|
||||||
|
|
@ -3,62 +3,37 @@
|
||||||
#
|
#
|
||||||
# tools/posix/mkiso.sh [output.iso]
|
# tools/posix/mkiso.sh [output.iso]
|
||||||
#
|
#
|
||||||
# The disc carries two El Torito boot entries and a real directory tree:
|
# The disc carries one El Torito boot entry and a real directory tree:
|
||||||
#
|
#
|
||||||
# /boot/schos.bin BIOS: cdboot + stage2 + kernel, loaded no-emulation
|
# /boot/efiboot.img a FAT image holding the loader and the kernel
|
||||||
# /boot/efiboot.img UEFI: a FAT image holding EFI/BOOT/BOOTX64.EFI
|
|
||||||
# /EFI/BOOT/BOOTX64.EFI the same loader, visible as a file on the disc
|
# /EFI/BOOT/BOOTX64.EFI the same loader, visible as a file on the disc
|
||||||
# /bin /etc /usr ... the staged sysroot, as an ordinary hierarchy
|
# /bin /etc /usr ... the staged sysroot, as an ordinary hierarchy
|
||||||
#
|
#
|
||||||
# --- BIOS entry -------------------------------------------------------------
|
# --- Why only one entry -----------------------------------------------------
|
||||||
#
|
#
|
||||||
# El Torito's "no emulation" mode, which is the only one of the three that fits
|
# There was a second, for BIOS: cdboot and stage2 concatenated with the kernel
|
||||||
# how SchOS actually loads itself.
|
# and loaded by El Torito in no-emulation mode. The BIOS loader is gone, so the
|
||||||
|
# entry that started it is too.
|
||||||
#
|
#
|
||||||
# floppy emulation caps the image at 2.88 MB; the ramdisk-root kernel
|
# What UEFI wants is not that mechanism at all. Firmware does not run
|
||||||
# alone is bigger than that.
|
# no-emulation boot code; it looks for a FAT filesystem containing
|
||||||
# hard disk emulation presents the image through the BIOS's INT 13h, and
|
# EFI/BOOT/BOOTX64.EFI and hands that volume to the application it finds. So
|
||||||
# SchOS's loader does not use INT 13h -- it drives
|
# the entry is a FAT image built with mtools, and it carries the kernel as well
|
||||||
# the IDE controller's registers directly, which
|
# as the loader -- BOOTX64.EFI opens \schos\kernel on the volume it was itself
|
||||||
# cannot reach a BIOS-emulated drive.
|
# loaded from, and for a UEFI boot that volume is this FAT image, not the ISO
|
||||||
# no emulation the BIOS loads a chosen run of sectors at 0x7C00
|
# 9660 tree around it. Without the kernel in here the loader comes up and halts
|
||||||
# and gets out of the way. That is this.
|
# with EFI_NOT_FOUND, which is what it did.
|
||||||
#
|
#
|
||||||
# So the layout below is deliberate: -boot-load-size is 1 + STAGE2_SECTORS,
|
# The loader is also copied into the ISO tree at /EFI/BOOT/BOOTX64.EFI, because
|
||||||
# which makes the BIOS place both boot/cdboot and the whole of stage2 in memory
|
|
||||||
# before anything of ours runs. That puts stage2 at 0x7E00 -- byte for byte the
|
|
||||||
# arrangement bootasm.S produces on a hard disk. From there the only difference
|
|
||||||
# is that stage2main.c reads the kernel back with ATAPI instead of ATA.
|
|
||||||
#
|
|
||||||
# STAGE2_SECTORS comes from the environment, set by the Makefile, because this
|
|
||||||
# same number also lives in boot/bios/bootasm.S, boot/bios/stage2main.c and the dd offsets
|
|
||||||
# in schos.img's recipe. It was previously written out by hand in three
|
|
||||||
# separate spots in this file alone, and raising it for the boot logo is what
|
|
||||||
# turned that from a latent hazard into a broken image.
|
|
||||||
#
|
|
||||||
# NOT using -boot-info-table on purpose: it patches 56 bytes over offset 8 of
|
|
||||||
# the boot image, which is the middle of cdboot's code. stage2main.c finds
|
|
||||||
# where it was loaded from by reading the El Torito boot catalog instead.
|
|
||||||
#
|
|
||||||
# --- UEFI entry -------------------------------------------------------------
|
|
||||||
#
|
|
||||||
# UEFI firmware does not run El Torito no-emulation code. It wants a FAT
|
|
||||||
# filesystem containing EFI/BOOT/BOOTX64.EFI, offered as a second boot entry.
|
|
||||||
# So one is built with mtools and added with -eltorito-alt-boot. The same
|
|
||||||
# loader is also copied into the ISO tree at /EFI/BOOT/BOOTX64.EFI, because
|
|
||||||
# some firmware looks there directly when booting from a USB stick.
|
# some firmware looks there directly when booting from a USB stick.
|
||||||
#
|
#
|
||||||
# --- Booting from a USB stick -----------------------------------------------
|
# --- Booting from a USB stick -----------------------------------------------
|
||||||
#
|
#
|
||||||
# -isohybrid-gpt-basdat adds a GPT that marks the FAT image as a basic data
|
# -isohybrid-gpt-basdat adds a GPT that marks the FAT image as a basic data
|
||||||
# partition, so writing this .iso to a stick with dd produces something UEFI
|
# partition, so writing this .iso to a stick with dd produces something UEFI
|
||||||
# will boot.
|
# will boot. That was already the only arrangement that worked from a stick:
|
||||||
#
|
# the firmware presents the medium as a hard disk, while the BIOS loader read
|
||||||
# That covers UEFI only, and deliberately. The classic BIOS isohybrid trick
|
# the kernel back over ATAPI, so it never booted this way even when it existed.
|
||||||
# needs syslinux's isohdpfx.bin, and even with it the BIOS path here would not
|
|
||||||
# work: on a stick the firmware presents the medium as a hard disk, while
|
|
||||||
# stage2main.c reads the kernel back over ATAPI. Booting BIOS off USB is what
|
|
||||||
# build/usbstick.img is for.
|
|
||||||
#
|
#
|
||||||
# --- Rock Ridge and Joliet --------------------------------------------------
|
# --- Rock Ridge and Joliet --------------------------------------------------
|
||||||
#
|
#
|
||||||
|
|
@ -76,34 +51,29 @@ set -e
|
||||||
|
|
||||||
BUILD=${BUILD:-build}
|
BUILD=${BUILD:-build}
|
||||||
OUT=${1:-schos.iso}
|
OUT=${1:-schos.iso}
|
||||||
STAGE2_SECTORS=${STAGE2_SECTORS:-48}
|
|
||||||
STAGE2_BYTES=$((STAGE2_SECTORS * 512))
|
|
||||||
LOAD_SIZE=$((1 + STAGE2_SECTORS))
|
|
||||||
STAGING="$BUILD/iso"
|
STAGING="$BUILD/iso"
|
||||||
SYSROOT=${SYSROOT:-sysroot}
|
SYSROOT=${SYSROOT:-sysroot}
|
||||||
|
|
||||||
CDBOOT="$BUILD/boot/cdboot"
|
|
||||||
STAGE2="$BUILD/boot/stage2"
|
|
||||||
KERNEL="$BUILD/kernel/kernel"
|
KERNEL="$BUILD/kernel/kernel"
|
||||||
EFIAPP="$BUILD/uefi/BOOTX64.EFI"
|
EFIAPP="$BUILD/uefi/BOOTX64.EFI"
|
||||||
|
|
||||||
for f in "$CDBOOT" "$STAGE2" "$KERNEL"; do
|
[ -f "$KERNEL" ] || { echo "mkiso: missing $KERNEL -- run make first" >&2; exit 1; }
|
||||||
[ -f "$f" ] || { echo "mkiso: missing $f -- run make first" >&2; exit 1; }
|
|
||||||
done
|
|
||||||
[ -f "$EFIAPP" ] || { echo "mkiso: missing $EFIAPP -- run 'make uefi' first" >&2; exit 1; }
|
[ -f "$EFIAPP" ] || { echo "mkiso: missing $EFIAPP -- run 'make uefi' first" >&2; exit 1; }
|
||||||
|
|
||||||
# The three pieces must be contiguous and at these exact sizes: stage2main.c's
|
# mtools is not optional any more. It used to be: without it the disc fell back
|
||||||
# readseg() finds the kernel at sector (1 + STAGE2_SECTORS) of the boot image,
|
# to being BIOS-only, which was a degraded disc rather than no disc. There is no
|
||||||
# so a short stage2 would silently shift the kernel out from under it.
|
# other entry to fall back to now, so a missing mformat means the ISO would not
|
||||||
cdboot_sz=$(wc -c < "$CDBOOT")
|
# boot anything, and failing here says so instead of shipping it.
|
||||||
stage2_sz=$(wc -c < "$STAGE2")
|
if ! command -v mformat >/dev/null 2>&1 || ! command -v mcopy >/dev/null 2>&1; then
|
||||||
[ "$cdboot_sz" -eq 512 ] || { echo "mkiso: cdboot is $cdboot_sz bytes, expected 512" >&2; exit 1; }
|
echo "mkiso: need mtools (mformat, mcopy) to build the EFI system partition" >&2
|
||||||
[ "$stage2_sz" -eq "$STAGE2_BYTES" ] || { echo "mkiso: stage2 is $stage2_sz bytes, expected $STAGE2_BYTES (STAGE2_SECTORS=$STAGE2_SECTORS)" >&2; exit 1; }
|
echo " Fedora: sudo dnf install mtools" >&2
|
||||||
|
echo " Debian/Ubuntu: apt install mtools" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
rm -rf "$STAGING"
|
rm -rf "$STAGING"
|
||||||
mkdir -p "$STAGING/boot" "$STAGING/EFI/BOOT"
|
mkdir -p "$STAGING/boot" "$STAGING/EFI/BOOT"
|
||||||
|
|
||||||
cat "$CDBOOT" "$STAGE2" "$KERNEL" > "$STAGING/boot/schos.bin"
|
|
||||||
cp "$EFIAPP" "$STAGING/EFI/BOOT/BOOTX64.EFI"
|
cp "$EFIAPP" "$STAGING/EFI/BOOT/BOOTX64.EFI"
|
||||||
|
|
||||||
# The FAT image for the UEFI El Torito entry.
|
# The FAT image for the UEFI El Torito entry.
|
||||||
|
|
@ -113,10 +83,8 @@ cp "$EFIAPP" "$STAGING/EFI/BOOT/BOOTX64.EFI"
|
||||||
# this FAT image -- not the ISO 9660 tree around it. Without the kernel in here
|
# this FAT image -- not the ISO 9660 tree around it. Without the kernel in here
|
||||||
# the loader comes up and halts with EFI_NOT_FOUND, which is what it did.
|
# the loader comes up and halts with EFI_NOT_FOUND, which is what it did.
|
||||||
#
|
#
|
||||||
# That means the kernel is stored twice on the disc: once inside boot/schos.bin
|
# The ISO 9660 tree still carries the sysroot for a running system to read; the
|
||||||
# for the BIOS entry, once here for the UEFI one. The alternative is teaching
|
# loader's own two files live in here because that is the volume it is handed.
|
||||||
# the loader to go looking for other volumes, which is a real feature and not a
|
|
||||||
# packaging detail -- worth doing deliberately rather than to save a copy.
|
|
||||||
#
|
#
|
||||||
# Sized from what actually goes in plus slack, rather than fixed: a hardcoded
|
# Sized from what actually goes in plus slack, rather than fixed: a hardcoded
|
||||||
# size is a thing that breaks quietly the first time the kernel grows.
|
# size is a thing that breaks quietly the first time the kernel grows.
|
||||||
|
|
@ -125,8 +93,8 @@ payload_kb=$(( ( $(wc -c < "$EFIAPP") + $(wc -c < "$KERNEL") + 1023 ) / 1024 ))
|
||||||
img_kb=$(( ( (payload_kb + 512 + 31) / 32 ) * 32 ))
|
img_kb=$(( ( (payload_kb + 512 + 31) / 32 ) * 32 ))
|
||||||
[ "$img_kb" -lt 512 ] && img_kb=512
|
[ "$img_kb" -lt 512 ] && img_kb=512
|
||||||
|
|
||||||
if command -v mformat >/dev/null 2>&1 && command -v mcopy >/dev/null 2>&1; then
|
rm -f "$EFI_IMG"
|
||||||
rm -f "$EFI_IMG"
|
if true; then
|
||||||
# Create the file first and format it with an explicit geometry.
|
# Create the file first and format it with an explicit geometry.
|
||||||
#
|
#
|
||||||
# Not `mformat -f`: that takes one of the standard floppy capacities
|
# Not `mformat -f`: that takes one of the standard floppy capacities
|
||||||
|
|
@ -139,12 +107,6 @@ if command -v mformat >/dev/null 2>&1 && command -v mcopy >/dev/null 2>&1; then
|
||||||
mmd -i "$EFI_IMG" ::/EFI ::/EFI/BOOT ::/schos
|
mmd -i "$EFI_IMG" ::/EFI ::/EFI/BOOT ::/schos
|
||||||
mcopy -i "$EFI_IMG" "$EFIAPP" ::/EFI/BOOT/BOOTX64.EFI
|
mcopy -i "$EFI_IMG" "$EFIAPP" ::/EFI/BOOT/BOOTX64.EFI
|
||||||
mcopy -i "$EFI_IMG" "$KERNEL" ::/schos/kernel
|
mcopy -i "$EFI_IMG" "$KERNEL" ::/schos/kernel
|
||||||
HAVE_EFI=1
|
|
||||||
else
|
|
||||||
echo "mkiso: warning: mtools not found, building a BIOS-only disc" >&2
|
|
||||||
echo " Fedora: sudo dnf install mtools" >&2
|
|
||||||
rm -f "$EFI_IMG"
|
|
||||||
HAVE_EFI=0
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# The sysroot, as an ordinary hierarchy. Copied rather than linked so that
|
# The sysroot, as an ordinary hierarchy. Copied rather than linked so that
|
||||||
|
|
@ -175,12 +137,6 @@ else
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ "$HAVE_EFI" = 1 ]; then
|
|
||||||
EFI_ARGS="-eltorito-alt-boot -e boot/efiboot.img -no-emul-boot $HYBRID"
|
|
||||||
else
|
|
||||||
EFI_ARGS=""
|
|
||||||
fi
|
|
||||||
|
|
||||||
# shellcheck disable=SC2086 # EFI_ARGS is a deliberate word list
|
# shellcheck disable=SC2086 # EFI_ARGS is a deliberate word list
|
||||||
$MKISOFS \
|
$MKISOFS \
|
||||||
-quiet \
|
-quiet \
|
||||||
|
|
@ -188,18 +144,12 @@ $MKISOFS \
|
||||||
-A "SchOS" \
|
-A "SchOS" \
|
||||||
-R -J -joliet-long \
|
-R -J -joliet-long \
|
||||||
-cache-inodes \
|
-cache-inodes \
|
||||||
-b boot/schos.bin \
|
-e boot/efiboot.img \
|
||||||
-no-emul-boot \
|
-no-emul-boot \
|
||||||
-boot-load-size "$LOAD_SIZE" \
|
$HYBRID \
|
||||||
$EFI_ARGS \
|
|
||||||
-o "$OUT" \
|
-o "$OUT" \
|
||||||
"$STAGING"
|
"$STAGING"
|
||||||
|
|
||||||
echo "==> $OUT ($(wc -c < "$OUT") bytes)"
|
echo "==> $OUT ($(wc -c < "$OUT") bytes)"
|
||||||
if [ "$HAVE_EFI" = 1 ]; then
|
echo " UEFI: qemu-system-x86_64 -bios OVMF_CODE.fd -cdrom $OUT"
|
||||||
echo " BIOS: qemu-system-x86_64 -cdrom $OUT -boot d"
|
echo " USB: dd if=$OUT of=/dev/sdX bs=4M"
|
||||||
echo " UEFI: qemu-system-x86_64 -bios OVMF_CODE.fd -cdrom $OUT"
|
|
||||||
echo " USB: dd if=$OUT of=/dev/sdX bs=4M (UEFI only -- see the header)"
|
|
||||||
else
|
|
||||||
echo " qemu-system-x86_64 -cdrom $OUT -boot d"
|
|
||||||
fi
|
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,10 @@ LOG=$BUILD/schcom-test.log
|
||||||
PPM=${1:-$BUILD/schcom.ppm}
|
PPM=${1:-$BUILD/schcom.ppm}
|
||||||
rm -f "$LOG" "$PPM"
|
rm -f "$LOG" "$PPM"
|
||||||
|
|
||||||
|
# The firmware varstore is written by OVMF, so this run gets its own copy
|
||||||
|
# rather than sharing the pristine one every other run starts from.
|
||||||
|
cp -f build/OVMF_VARS.fd build/schcom-OVMF_VARS.fd
|
||||||
|
|
||||||
PORT=$(python3 - <<'EOF'
|
PORT=$(python3 - <<'EOF'
|
||||||
import socket
|
import socket
|
||||||
s=socket.socket(); s.bind(('127.0.0.1',0)); print(s.getsockname()[1]); s.close()
|
s=socket.socket(); s.bind(('127.0.0.1',0)); print(s.getsockname()[1]); s.close()
|
||||||
|
|
@ -32,7 +36,11 @@ EOF
|
||||||
sleep 40
|
sleep 40
|
||||||
) | timeout -k 5 120 qemu-system-x86_64 \
|
) | timeout -k 5 120 qemu-system-x86_64 \
|
||||||
-machine pc-i440fx-9.2 -cpu qemu64,+rdtscp \
|
-machine pc-i440fx-9.2 -cpu qemu64,+rdtscp \
|
||||||
-hda schos.img -hdb build/schfs.img -m 1G -smp 1 \
|
-drive if=pflash,format=raw,unit=0,readonly=on,file=build/OVMF_CODE.fd \
|
||||||
|
-drive if=pflash,format=raw,unit=1,file=build/schcom-OVMF_VARS.fd \
|
||||||
|
-drive file=fat:rw:build/esp,index=0,media=disk \
|
||||||
|
-drive file=build/schfs.img,format=raw,index=1,media=disk \
|
||||||
|
-m 1G -smp 1 \
|
||||||
-display none -vga std \
|
-display none -vga std \
|
||||||
-monitor tcp:127.0.0.1:$PORT,server,nowait \
|
-monitor tcp:127.0.0.1:$PORT,server,nowait \
|
||||||
-chardev stdio,id=c0,logfile="$LOG" -serial chardev:c0 \
|
-chardev stdio,id=c0,logfile="$LOG" -serial chardev:c0 \
|
||||||
|
|
|
||||||
|
|
@ -1,19 +0,0 @@
|
||||||
#!/usr/bin/perl
|
|
||||||
|
|
||||||
open(SIG, $ARGV[0]) || die "open $ARGV[0]: $!";
|
|
||||||
|
|
||||||
$n = sysread(SIG, $buf, 1000);
|
|
||||||
|
|
||||||
if($n > 510){
|
|
||||||
print STDERR "boot block too large: $n bytes (max 510)\n";
|
|
||||||
exit 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
print STDERR "boot block is $n bytes (max 510)\n";
|
|
||||||
|
|
||||||
$buf .= "\0" x (510-$n);
|
|
||||||
$buf .= "\x55\xAA";
|
|
||||||
|
|
||||||
open(SIG, ">$ARGV[0]") || die "open >$ARGV[0]: $!";
|
|
||||||
print SIG $buf;
|
|
||||||
close SIG;
|
|
||||||
|
|
@ -26,11 +26,8 @@ param(
|
||||||
# attached, which is the only way to prove the ISO is self-contained.
|
# attached, which is the only way to prove the ISO is self-contained.
|
||||||
# Needs `make iso` first.
|
# Needs `make iso` first.
|
||||||
[switch]$Iso,
|
[switch]$Iso,
|
||||||
# Boot through OVMF instead of the BIOS loader. The disk layout is
|
# Accepted and ignored. It used to select OVMF over the BIOS loader; there
|
||||||
# deliberately the same -- the staged ESP lands on index 0 and the SchFS
|
# is only the one path now, so it names a choice that no longer exists.
|
||||||
# volume on index 1 -- so every check below runs unaltered and a difference
|
|
||||||
# in the transcript is a difference in the *firmware path*, not in what was
|
|
||||||
# asked. Needs `make esp` first.
|
|
||||||
[switch]$Uefi,
|
[switch]$Uefi,
|
||||||
# Terminal behaviour: pipes, isatty, SIGPIPE, and Ctrl-C. None of these
|
# Terminal behaviour: pipes, isatty, SIGPIPE, and Ctrl-C. None of these
|
||||||
# can be checked by looking at a screenshot, and all of them were broken
|
# can be checked by looking at a screenshot, and all of them were broken
|
||||||
|
|
@ -86,7 +83,6 @@ if (-not $Qemu) { $Qemu = Find-Qemu }
|
||||||
$ErrorActionPreference = 'Stop'
|
$ErrorActionPreference = 'Stop'
|
||||||
# Repo root is two levels up: this script lives in tools/windows/.
|
# Repo root is two levels up: this script lives in tools/windows/.
|
||||||
$root = Split-Path (Split-Path $PSScriptRoot -Parent) -Parent
|
$root = Split-Path (Split-Path $PSScriptRoot -Parent) -Parent
|
||||||
$hda = Join-Path $root 'schos.img'
|
|
||||||
# The root filesystem. SchFS is the only root there is, so this is not a
|
# The root filesystem. SchFS is the only root there is, so this is not a
|
||||||
# choice -- a kernel handed anything else on device 1 panics in forkret().
|
# choice -- a kernel handed anything else on device 1 panics in forkret().
|
||||||
$hdb = Join-Path $root 'build/schfs.img'
|
$hdb = Join-Path $root 'build/schfs.img'
|
||||||
|
|
@ -249,73 +245,56 @@ $commands += 'echo MARKER-DONE'
|
||||||
$isoPath = Join-Path $root 'schos.iso'
|
$isoPath = Join-Path $root 'schos.iso'
|
||||||
if ($Iso -and -not (Test-Path $isoPath)) { throw "no $isoPath -- run 'make iso' first" }
|
if ($Iso -and -not (Test-Path $isoPath)) { throw "no $isoPath -- run 'make iso' first" }
|
||||||
|
|
||||||
$drives = if ($Uefi) {
|
# OVMF is not optional any more: the BIOS loader is gone, so every run boots
|
||||||
# The varstore is written by the firmware, so each run gets its own copy.
|
# through firmware. -Uefi is still accepted and does nothing, because it named
|
||||||
# Sharing one would let a run that changed a UEFI variable -- boot order,
|
# a choice that no longer exists.
|
||||||
# console mode -- decide how the next run behaves, which is exactly the
|
$code = Join-Path $root 'build/OVMF_CODE.fd'
|
||||||
# kind of dependency between runs that makes a suite stop being evidence.
|
$vars = Join-Path $root 'build/OVMF_VARS.fd'
|
||||||
|
foreach ($f in @($code, $vars)) {
|
||||||
|
if (-not (Test-Path $f)) { throw "no $f -- run 'make qemu-uefi' once to fetch OVMF" }
|
||||||
|
}
|
||||||
|
# The varstore is written by the firmware, so each run gets its own copy.
|
||||||
|
# Sharing one would let a run that changed a UEFI variable -- boot order,
|
||||||
|
# console mode -- decide how the next run behaves, which is exactly the kind of
|
||||||
|
# dependency between runs that makes a suite stop being evidence.
|
||||||
|
$runVars = Join-Path $root 'build/bootcheck-OVMF_VARS.fd'
|
||||||
|
Copy-Item -Force $vars $runVars
|
||||||
|
|
||||||
|
$firmware = @("-drive if=pflash,format=raw,unit=0,readonly=on,file=`"$code`""
|
||||||
|
"-drive if=pflash,format=raw,unit=1,file=`"$runVars`"")
|
||||||
|
|
||||||
|
$drives = if ($Iso) {
|
||||||
|
# No hard disk at all, so a kernel that still secretly depends on one fails
|
||||||
|
# here rather than in someone's optical drive.
|
||||||
|
$firmware + @("-cdrom `"$isoPath`"", '-boot d')
|
||||||
|
} else {
|
||||||
$esp = Join-Path $root 'build/esp'
|
$esp = Join-Path $root 'build/esp'
|
||||||
if (-not (Test-Path (Join-Path $esp 'EFI/BOOT/BOOTX64.EFI'))) {
|
if (-not (Test-Path (Join-Path $esp 'EFI/BOOT/BOOTX64.EFI'))) {
|
||||||
throw "no staged ESP at $esp -- run 'make esp' first"
|
throw "no staged ESP at $esp -- run 'make esp' first"
|
||||||
}
|
}
|
||||||
$code = Join-Path $root 'build/OVMF_CODE.fd'
|
$firmware + @("-drive file=fat:rw:`"$esp`",index=0,media=disk"
|
||||||
$vars = Join-Path $root 'build/OVMF_VARS.fd'
|
"-drive file=`"$hdb`",format=raw,index=1,media=disk")
|
||||||
foreach ($f in @($code, $vars)) {
|
|
||||||
if (-not (Test-Path $f)) { throw "no $f -- run 'make qemu-uefi' once to fetch OVMF" }
|
|
||||||
}
|
|
||||||
$runVars = Join-Path $root 'build/bootcheck-OVMF_VARS.fd'
|
|
||||||
Copy-Item -Force $vars $runVars
|
|
||||||
@("-drive if=pflash,format=raw,unit=0,readonly=on,file=`"$code`""
|
|
||||||
"-drive if=pflash,format=raw,unit=1,file=`"$runVars`""
|
|
||||||
"-drive file=fat:rw:`"$esp`",index=0,media=disk"
|
|
||||||
"-drive file=`"$hdb`",format=raw,index=1,media=disk")
|
|
||||||
} elseif ($Iso) {
|
|
||||||
@("-cdrom `"$isoPath`"", '-boot d')
|
|
||||||
} else {
|
|
||||||
@("-drive file=`"$hda`",format=raw,index=0,media=disk"
|
|
||||||
"-drive file=`"$hdb`",format=raw,index=1,media=disk")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$psi = New-Object System.Diagnostics.ProcessStartInfo
|
$psi = New-Object System.Diagnostics.ProcessStartInfo
|
||||||
$psi.FileName = $Qemu
|
$psi.FileName = $Qemu
|
||||||
|
|
||||||
$usbDevices = if ($Usb) {
|
$usbDevices = if ($Usb) {
|
||||||
# One device of each speed class QEMU can produce: usb-storage negotiates
|
|
||||||
# SuperSpeed on qemu-xhci, the HID devices come up high speed. They take
|
|
||||||
# different paths through enumeration -- a USB3 port arrives already
|
|
||||||
# enabled and a USB2 port has to be reset first -- so one of each is the
|
|
||||||
# difference between testing the driver and testing half of it.
|
|
||||||
@('-device qemu-xhci,id=xhci'
|
@('-device qemu-xhci,id=xhci'
|
||||||
'-device usb-kbd,bus=xhci.0'
|
'-device usb-kbd,bus=xhci.0'
|
||||||
# The mouse goes behind a hub, so the tree is a tree and not a row of
|
|
||||||
# root ports. That exercises the route string, the hub's own slot
|
|
||||||
# context, and the transaction translator -- none of which a device
|
|
||||||
# plugged straight into the controller touches at all.
|
|
||||||
# QEMU places a device under a hub by port *path*, not by a child bus:
|
|
||||||
# the hub sits on port 2 and the mouse on port 2.1 of the same bus.
|
|
||||||
'-device usb-hub,id=hub,bus=xhci.0,port=2'
|
'-device usb-hub,id=hub,bus=xhci.0,port=2'
|
||||||
'-device usb-mouse,bus=xhci.0,port=2.1'
|
'-device usb-mouse,bus=xhci.0,port=2.1'
|
||||||
# Its own small image, never $hdb. The checks below read a v1 superblock
|
|
||||||
# off this stick and assert on its sector count and size field, so the
|
|
||||||
# stick has to be a v1 image whatever the root filesystem is -- under
|
|
||||||
# the root $hdb is a SchFS volume and those fields are simply not
|
|
||||||
# there, which failed three USB checks that had nothing wrong with USB.
|
|
||||||
# It also stops the same file being attached twice, once as the root
|
|
||||||
# disk and once as a USB device.
|
|
||||||
"-drive id=usbstick,file=`"$(Join-Path $root 'build/usbstick.img')`",format=raw,if=none"
|
"-drive id=usbstick,file=`"$(Join-Path $root 'build/usbstick.img')`",format=raw,if=none"
|
||||||
'-device usb-storage,bus=xhci.0,drive=usbstick')
|
'-device usb-storage,bus=xhci.0,drive=usbstick')
|
||||||
} else { @() }
|
} else { @() }
|
||||||
|
|
||||||
# The USB run needs the QEMU monitor, because `sendkey` is the only way to
|
# A free loopback port, found by binding one and letting go. Hardcoding it made
|
||||||
# press a key on an emulated USB keyboard -- typing on stdin goes to the
|
# two runs on one machine collide.
|
||||||
# serial port and proves nothing about USB.
|
|
||||||
# Ask the OS for a free port rather than hardcoding one. A run that dies
|
|
||||||
# before its `finally` leaves QEMU holding the old number, and every later run
|
|
||||||
# then fails with "Failed to find an available port" -- which looks like a
|
|
||||||
# bug in whatever is being tested rather than in the harness.
|
|
||||||
$monPort = if ($Usb -or $Evdev) {
|
$monPort = if ($Usb -or $Evdev) {
|
||||||
$l = [System.Net.Sockets.TcpListener]::new([System.Net.IPAddress]::Loopback, 0)
|
$l = [System.Net.Sockets.TcpListener]::new([System.Net.IPAddress]::Loopback, 0)
|
||||||
$l.Start(); $p = $l.LocalEndpoint.Port; $l.Stop(); $p
|
$l.Start(); $mp = $l.LocalEndpoint.Port; $l.Stop(); $mp
|
||||||
} else { 0 }
|
} else { 0 }
|
||||||
|
|
||||||
$monitor = if ($Usb -or $Evdev) { @("-monitor tcp:127.0.0.1:$monPort,server,nowait") } else { @() }
|
$monitor = if ($Usb -or $Evdev) { @("-monitor tcp:127.0.0.1:$monPort,server,nowait") } else { @() }
|
||||||
|
|
||||||
$psi.Arguments = ($drives + $usbDevices + $monitor + @(
|
$psi.Arguments = ($drives + $usbDevices + $monitor + @(
|
||||||
|
|
@ -327,9 +306,7 @@ $psi.Arguments = ($drives + $usbDevices + $monitor + @(
|
||||||
$psi.RedirectStandardInput = $true
|
$psi.RedirectStandardInput = $true
|
||||||
$psi.UseShellExecute = $false
|
$psi.UseShellExecute = $false
|
||||||
|
|
||||||
$what = if ($Uefi) { "OVMF, hdb = $(Split-Path $hdb -Leaf)" }
|
$what = if ($Iso) { 'schos.iso, no hard disk' } else { "hdb = $(Split-Path $hdb -Leaf)" }
|
||||||
elseif ($Iso) { 'schos.iso, no hard disk' }
|
|
||||||
else { "hdb = $(Split-Path $hdb -Leaf)" }
|
|
||||||
Write-Host "==> booting ($what)" -ForegroundColor Cyan
|
Write-Host "==> booting ($what)" -ForegroundColor Cyan
|
||||||
$p = [System.Diagnostics.Process]::Start($psi)
|
$p = [System.Diagnostics.Process]::Start($psi)
|
||||||
# Recorded immediately, so that a run killed one instruction later is still
|
# Recorded immediately, so that a run killed one instruction later is still
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,20 @@ if (-not $Qemu) { $Qemu = Find-Qemu }
|
||||||
$ErrorActionPreference = 'Stop'
|
$ErrorActionPreference = 'Stop'
|
||||||
# Repo root is two levels up: this script lives in tools/windows/.
|
# Repo root is two levels up: this script lives in tools/windows/.
|
||||||
$root = Split-Path (Split-Path $PSScriptRoot -Parent) -Parent
|
$root = Split-Path (Split-Path $PSScriptRoot -Parent) -Parent
|
||||||
$hda = Join-Path $root 'schos.img'
|
$esp = Join-Path $root 'build/esp'
|
||||||
|
# OVMF and the staged ESP, because there is one boot path now. The old
|
||||||
|
# schos.img was a raw disk with the boot sector at LBA 0 -- a layout only the
|
||||||
|
# BIOS loader could use, and it went with it.
|
||||||
|
$code = Join-Path $root 'build/OVMF_CODE.fd'
|
||||||
|
$vars = Join-Path $root 'build/OVMF_VARS.fd'
|
||||||
|
foreach ($f in @($code, $vars)) {
|
||||||
|
if (-not (Test-Path $f)) { throw "no $f -- run 'make qemu-uefi' once to fetch OVMF" }
|
||||||
|
}
|
||||||
|
if (-not (Test-Path (Join-Path $esp 'EFI/BOOT/BOOTX64.EFI'))) {
|
||||||
|
throw "no staged ESP at $esp -- run 'make esp' first"
|
||||||
|
}
|
||||||
|
$runVars = Join-Path $root "build/crashshot-OVMF_VARS.fd"
|
||||||
|
Copy-Item -Force $vars $runVars
|
||||||
# The root filesystem. SchFS is the only root there is: the kernel mounts
|
# The root filesystem. SchFS is the only root there is: the kernel mounts
|
||||||
# device 1 in forkret() and panics if it is not a SchFS volume, so this is
|
# device 1 in forkret() and panics if it is not a SchFS volume, so this is
|
||||||
# not a choice and there is no -SchFS switch any more. It used to default to
|
# not a choice and there is no -SchFS switch any more. It used to default to
|
||||||
|
|
@ -65,7 +78,9 @@ $monPort = 55555
|
||||||
$psi = New-Object System.Diagnostics.ProcessStartInfo
|
$psi = New-Object System.Diagnostics.ProcessStartInfo
|
||||||
$psi.FileName = $Qemu
|
$psi.FileName = $Qemu
|
||||||
$psi.Arguments = @(
|
$psi.Arguments = @(
|
||||||
"-drive file=`"$hda`",format=raw,index=0,media=disk"
|
"-drive if=pflash,format=raw,unit=0,readonly=on,file=`"$code`""
|
||||||
|
"-drive if=pflash,format=raw,unit=1,file=`"$runVars`""
|
||||||
|
"-drive file=fat:rw:`"$esp`",index=0,media=disk"
|
||||||
"-drive file=`"$hdb`",format=raw,index=1,media=disk"
|
"-drive file=`"$hdb`",format=raw,index=1,media=disk"
|
||||||
'-m 1G -smp 1 -cpu qemu64,+rdtscp -machine pc-i440fx-9.2'
|
'-m 1G -smp 1 -cpu qemu64,+rdtscp -machine pc-i440fx-9.2'
|
||||||
'-display none'
|
'-display none'
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,20 @@ if (-not $Qemu) { $Qemu = Find-Qemu }
|
||||||
$ErrorActionPreference = 'Stop'
|
$ErrorActionPreference = 'Stop'
|
||||||
# Repo root is two levels up: this script lives in tools/windows/.
|
# Repo root is two levels up: this script lives in tools/windows/.
|
||||||
$root = Split-Path (Split-Path $PSScriptRoot -Parent) -Parent
|
$root = Split-Path (Split-Path $PSScriptRoot -Parent) -Parent
|
||||||
$hda = Join-Path $root 'schos.img'
|
$esp = Join-Path $root 'build/esp'
|
||||||
|
# OVMF and the staged ESP, because there is one boot path now. The old
|
||||||
|
# schos.img was a raw disk with the boot sector at LBA 0 -- a layout only the
|
||||||
|
# BIOS loader could use, and it went with it.
|
||||||
|
$code = Join-Path $root 'build/OVMF_CODE.fd'
|
||||||
|
$vars = Join-Path $root 'build/OVMF_VARS.fd'
|
||||||
|
foreach ($f in @($code, $vars)) {
|
||||||
|
if (-not (Test-Path $f)) { throw "no $f -- run 'make qemu-uefi' once to fetch OVMF" }
|
||||||
|
}
|
||||||
|
if (-not (Test-Path (Join-Path $esp 'EFI/BOOT/BOOTX64.EFI'))) {
|
||||||
|
throw "no staged ESP at $esp -- run 'make esp' first"
|
||||||
|
}
|
||||||
|
$runVars = Join-Path $root "build/dispshot-OVMF_VARS.fd"
|
||||||
|
Copy-Item -Force $vars $runVars
|
||||||
# The root filesystem. SchFS is the only root there is: the kernel mounts
|
# The root filesystem. SchFS is the only root there is: the kernel mounts
|
||||||
# device 1 in forkret() and panics if it is not a SchFS volume, so this is
|
# device 1 in forkret() and panics if it is not a SchFS volume, so this is
|
||||||
# not a choice and there is no -SchFS switch any more. It used to default to
|
# not a choice and there is no -SchFS switch any more. It used to default to
|
||||||
|
|
@ -70,7 +83,9 @@ $monPort = 55555
|
||||||
$psi = New-Object System.Diagnostics.ProcessStartInfo
|
$psi = New-Object System.Diagnostics.ProcessStartInfo
|
||||||
$psi.FileName = $Qemu
|
$psi.FileName = $Qemu
|
||||||
$psi.Arguments = @(
|
$psi.Arguments = @(
|
||||||
"-drive file=`"$hda`",format=raw,index=0,media=disk"
|
"-drive if=pflash,format=raw,unit=0,readonly=on,file=`"$code`""
|
||||||
|
"-drive if=pflash,format=raw,unit=1,file=`"$runVars`""
|
||||||
|
"-drive file=fat:rw:`"$esp`",index=0,media=disk"
|
||||||
"-drive file=`"$hdb`",format=raw,index=1,media=disk"
|
"-drive file=`"$hdb`",format=raw,index=1,media=disk"
|
||||||
'-m 1G -smp 1 -cpu qemu64,+rdtscp -machine pc-i440fx-9.2'
|
'-m 1G -smp 1 -cpu qemu64,+rdtscp -machine pc-i440fx-9.2'
|
||||||
'-display none'
|
'-display none'
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,20 @@ if (-not $Qemu) { $Qemu = Find-Qemu }
|
||||||
$ErrorActionPreference = 'Stop'
|
$ErrorActionPreference = 'Stop'
|
||||||
# Repo root is two levels up: this script lives in tools/windows/.
|
# Repo root is two levels up: this script lives in tools/windows/.
|
||||||
$root = Split-Path (Split-Path $PSScriptRoot -Parent) -Parent
|
$root = Split-Path (Split-Path $PSScriptRoot -Parent) -Parent
|
||||||
$hda = Join-Path $root 'schos.img'
|
$esp = Join-Path $root 'build/esp'
|
||||||
|
# OVMF and the staged ESP, because there is one boot path now. The old
|
||||||
|
# schos.img was a raw disk with the boot sector at LBA 0 -- a layout only the
|
||||||
|
# BIOS loader could use, and it went with it.
|
||||||
|
$code = Join-Path $root 'build/OVMF_CODE.fd'
|
||||||
|
$vars = Join-Path $root 'build/OVMF_VARS.fd'
|
||||||
|
foreach ($f in @($code, $vars)) {
|
||||||
|
if (-not (Test-Path $f)) { throw "no $f -- run 'make qemu-uefi' once to fetch OVMF" }
|
||||||
|
}
|
||||||
|
if (-not (Test-Path (Join-Path $esp 'EFI/BOOT/BOOTX64.EFI'))) {
|
||||||
|
throw "no staged ESP at $esp -- run 'make esp' first"
|
||||||
|
}
|
||||||
|
$runVars = Join-Path $root "build/fbshot-OVMF_VARS.fd"
|
||||||
|
Copy-Item -Force $vars $runVars
|
||||||
# The root filesystem. SchFS is the only root there is: the kernel mounts
|
# The root filesystem. SchFS is the only root there is: the kernel mounts
|
||||||
# device 1 in forkret() and panics if it is not a SchFS volume, so this is
|
# device 1 in forkret() and panics if it is not a SchFS volume, so this is
|
||||||
# not a choice and there is no -SchFS switch any more. It used to default to
|
# not a choice and there is no -SchFS switch any more. It used to default to
|
||||||
|
|
@ -60,7 +73,9 @@ $monPort = 55556 # not crashshot's 55555, so the two can run back to back
|
||||||
$psi = New-Object System.Diagnostics.ProcessStartInfo
|
$psi = New-Object System.Diagnostics.ProcessStartInfo
|
||||||
$psi.FileName = $Qemu
|
$psi.FileName = $Qemu
|
||||||
$psi.Arguments = @(
|
$psi.Arguments = @(
|
||||||
"-drive file=`"$hda`",format=raw,index=0,media=disk"
|
"-drive if=pflash,format=raw,unit=0,readonly=on,file=`"$code`""
|
||||||
|
"-drive if=pflash,format=raw,unit=1,file=`"$runVars`""
|
||||||
|
"-drive file=fat:rw:`"$esp`",index=0,media=disk"
|
||||||
"-drive file=`"$hdb`",format=raw,index=1,media=disk"
|
"-drive file=`"$hdb`",format=raw,index=1,media=disk"
|
||||||
'-m 1G -smp 1 -cpu qemu64,+rdtscp -machine pc-i440fx-9.2'
|
'-m 1G -smp 1 -cpu qemu64,+rdtscp -machine pc-i440fx-9.2'
|
||||||
'-display none'
|
'-display none'
|
||||||
|
|
|
||||||
|
|
@ -55,7 +55,20 @@ Remove-Item -Recurse -Force $dir -ErrorAction SilentlyContinue
|
||||||
New-Item -ItemType Directory -Force $dir | Out-Null
|
New-Item -ItemType Directory -Force $dir | Out-Null
|
||||||
Remove-Item -Force $log -ErrorAction SilentlyContinue
|
Remove-Item -Force $log -ErrorAction SilentlyContinue
|
||||||
|
|
||||||
$hda = Join-Path $root 'schos.img'
|
$esp = Join-Path $root 'build/esp'
|
||||||
|
# OVMF and the staged ESP, because there is one boot path now. The old
|
||||||
|
# schos.img was a raw disk with the boot sector at LBA 0 -- a layout only the
|
||||||
|
# BIOS loader could use, and it went with it.
|
||||||
|
$code = Join-Path $root 'build/OVMF_CODE.fd'
|
||||||
|
$vars = Join-Path $root 'build/OVMF_VARS.fd'
|
||||||
|
foreach ($f in @($code, $vars)) {
|
||||||
|
if (-not (Test-Path $f)) { throw "no $f -- run 'make qemu-uefi' once to fetch OVMF" }
|
||||||
|
}
|
||||||
|
if (-not (Test-Path (Join-Path $esp 'EFI/BOOT/BOOTX64.EFI'))) {
|
||||||
|
throw "no staged ESP at $esp -- run 'make esp' first"
|
||||||
|
}
|
||||||
|
$runVars = Join-Path $root "build/splashshot-OVMF_VARS.fd"
|
||||||
|
Copy-Item -Force $vars $runVars
|
||||||
# The root filesystem. SchFS is the only root there is: the kernel mounts
|
# The root filesystem. SchFS is the only root there is: the kernel mounts
|
||||||
# device 1 in forkret() and panics if it is not a SchFS volume, so this is
|
# device 1 in forkret() and panics if it is not a SchFS volume, so this is
|
||||||
# not a choice and there is no -SchFS switch any more. It used to default to
|
# not a choice and there is no -SchFS switch any more. It used to default to
|
||||||
|
|
@ -75,7 +88,9 @@ $psi.Arguments = @(
|
||||||
# first fix and it meant duplicating build/fs.img, all 153 MB of it, on
|
# first fix and it meant duplicating build/fs.img, all 153 MB of it, on
|
||||||
# every run. -snapshot keeps writes in a temporary overlay, so the originals
|
# every run. -snapshot keeps writes in a temporary overlay, so the originals
|
||||||
# are untouched either way.
|
# are untouched either way.
|
||||||
"-drive file=`"$hda`",format=raw,index=0,media=disk,file.locking=off"
|
"-drive if=pflash,format=raw,unit=0,readonly=on,file=`"$code`""
|
||||||
|
"-drive if=pflash,format=raw,unit=1,file=`"$runVars`""
|
||||||
|
"-drive file=fat:rw:`"$esp`",index=0,media=disk"
|
||||||
"-drive file=`"$hdb`",format=raw,index=1,media=disk,file.locking=off"
|
"-drive file=`"$hdb`",format=raw,index=1,media=disk,file.locking=off"
|
||||||
'-snapshot'
|
'-snapshot'
|
||||||
'-m 1G -smp 1 -cpu qemu64,+rdtscp -machine pc-i440fx-9.2'
|
'-m 1G -smp 1 -cpu qemu64,+rdtscp -machine pc-i440fx-9.2'
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,20 @@ if (-not $Qemu) { $Qemu = Find-Qemu }
|
||||||
$ErrorActionPreference = 'Stop'
|
$ErrorActionPreference = 'Stop'
|
||||||
# Repo root is two levels up: this script lives in tools/windows/.
|
# Repo root is two levels up: this script lives in tools/windows/.
|
||||||
$root = Split-Path (Split-Path $PSScriptRoot -Parent) -Parent
|
$root = Split-Path (Split-Path $PSScriptRoot -Parent) -Parent
|
||||||
$hda = Join-Path $root 'schos.img'
|
$esp = Join-Path $root 'build/esp'
|
||||||
|
# OVMF and the staged ESP, because there is one boot path now. The old
|
||||||
|
# schos.img was a raw disk with the boot sector at LBA 0 -- a layout only the
|
||||||
|
# BIOS loader could use, and it went with it.
|
||||||
|
$code = Join-Path $root 'build/OVMF_CODE.fd'
|
||||||
|
$vars = Join-Path $root 'build/OVMF_VARS.fd'
|
||||||
|
foreach ($f in @($code, $vars)) {
|
||||||
|
if (-not (Test-Path $f)) { throw "no $f -- run 'make qemu-uefi' once to fetch OVMF" }
|
||||||
|
}
|
||||||
|
if (-not (Test-Path (Join-Path $esp 'EFI/BOOT/BOOTX64.EFI'))) {
|
||||||
|
throw "no staged ESP at $esp -- run 'make esp' first"
|
||||||
|
}
|
||||||
|
$runVars = Join-Path $root "build/textshot-OVMF_VARS.fd"
|
||||||
|
Copy-Item -Force $vars $runVars
|
||||||
# The root filesystem. SchFS is the only root there is: the kernel mounts
|
# The root filesystem. SchFS is the only root there is: the kernel mounts
|
||||||
# device 1 in forkret() and panics if it is not a SchFS volume, so this is
|
# device 1 in forkret() and panics if it is not a SchFS volume, so this is
|
||||||
# not a choice and there is no -SchFS switch any more. It used to default to
|
# not a choice and there is no -SchFS switch any more. It used to default to
|
||||||
|
|
@ -70,7 +83,9 @@ $monPort = 55555
|
||||||
$psi = New-Object System.Diagnostics.ProcessStartInfo
|
$psi = New-Object System.Diagnostics.ProcessStartInfo
|
||||||
$psi.FileName = $Qemu
|
$psi.FileName = $Qemu
|
||||||
$psi.Arguments = @(
|
$psi.Arguments = @(
|
||||||
"-drive file=`"$hda`",format=raw,index=0,media=disk"
|
"-drive if=pflash,format=raw,unit=0,readonly=on,file=`"$code`""
|
||||||
|
"-drive if=pflash,format=raw,unit=1,file=`"$runVars`""
|
||||||
|
"-drive file=fat:rw:`"$esp`",index=0,media=disk"
|
||||||
"-drive file=`"$hdb`",format=raw,index=1,media=disk"
|
"-drive file=`"$hdb`",format=raw,index=1,media=disk"
|
||||||
'-m 1G -smp 1 -cpu qemu64,+rdtscp -machine pc-i440fx-9.2'
|
'-m 1G -smp 1 -cpu qemu64,+rdtscp -machine pc-i440fx-9.2'
|
||||||
'-display none'
|
'-display none'
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,20 @@ if (-not $Qemu) { $Qemu = Find-Qemu }
|
||||||
$ErrorActionPreference = 'Stop'
|
$ErrorActionPreference = 'Stop'
|
||||||
# Repo root is two levels up: this script lives in tools/windows/.
|
# Repo root is two levels up: this script lives in tools/windows/.
|
||||||
$root = Split-Path (Split-Path $PSScriptRoot -Parent) -Parent
|
$root = Split-Path (Split-Path $PSScriptRoot -Parent) -Parent
|
||||||
$hda = Join-Path $root 'schos.img'
|
$esp = Join-Path $root 'build/esp'
|
||||||
|
# OVMF and the staged ESP, because there is one boot path now. The old
|
||||||
|
# schos.img was a raw disk with the boot sector at LBA 0 -- a layout only the
|
||||||
|
# BIOS loader could use, and it went with it.
|
||||||
|
$code = Join-Path $root 'build/OVMF_CODE.fd'
|
||||||
|
$vars = Join-Path $root 'build/OVMF_VARS.fd'
|
||||||
|
foreach ($f in @($code, $vars)) {
|
||||||
|
if (-not (Test-Path $f)) { throw "no $f -- run 'make qemu-uefi' once to fetch OVMF" }
|
||||||
|
}
|
||||||
|
if (-not (Test-Path (Join-Path $esp 'EFI/BOOT/BOOTX64.EFI'))) {
|
||||||
|
throw "no staged ESP at $esp -- run 'make esp' first"
|
||||||
|
}
|
||||||
|
$runVars = Join-Path $root "build/ttyshot-OVMF_VARS.fd"
|
||||||
|
Copy-Item -Force $vars $runVars
|
||||||
# The root filesystem. SchFS is the only root there is: the kernel mounts
|
# The root filesystem. SchFS is the only root there is: the kernel mounts
|
||||||
# device 1 in forkret() and panics if it is not a SchFS volume, so this is
|
# device 1 in forkret() and panics if it is not a SchFS volume, so this is
|
||||||
# not a choice and there is no -SchFS switch any more. It used to default to
|
# not a choice and there is no -SchFS switch any more. It used to default to
|
||||||
|
|
@ -73,7 +86,9 @@ $commands = @(
|
||||||
$psi = New-Object System.Diagnostics.ProcessStartInfo
|
$psi = New-Object System.Diagnostics.ProcessStartInfo
|
||||||
$psi.FileName = $Qemu
|
$psi.FileName = $Qemu
|
||||||
$psi.Arguments = @(
|
$psi.Arguments = @(
|
||||||
"-drive file=`"$hda`",format=raw,index=0,media=disk"
|
"-drive if=pflash,format=raw,unit=0,readonly=on,file=`"$code`""
|
||||||
|
"-drive if=pflash,format=raw,unit=1,file=`"$runVars`""
|
||||||
|
"-drive file=fat:rw:`"$esp`",index=0,media=disk"
|
||||||
"-drive file=`"$hdb`",format=raw,index=1,media=disk"
|
"-drive file=`"$hdb`",format=raw,index=1,media=disk"
|
||||||
'-m 1G -smp 1 -cpu qemu64,+rdtscp -machine pc-i440fx-9.2'
|
'-m 1G -smp 1 -cpu qemu64,+rdtscp -machine pc-i440fx-9.2'
|
||||||
'-display none'
|
'-display none'
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue