commit d0f7acf9eb9314ffbea9f1f8951fd99cba7a5956 Author: Nexoniarz Date: Sun Aug 9 13:47:01 2026 +0200 Initial commit: Schunix as it stands before the SchOS rename An x86-64 operating system built from xv6, now largely rewritten: SMP, a copy-on-write root filesystem (SchFS), PCI/USB/virtio drivers, a TTF rasteriser, and a Linux-numbered syscall ABI. No history before this point -- the tree was never under version control. This commit exists so the rename that follows has something to undo to. diff --git a/.#README b/.#README new file mode 100644 index 0000000..d097158 Binary files /dev/null and b/.#README differ diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..e5b6a7f --- /dev/null +++ b/.gitattributes @@ -0,0 +1,18 @@ +# Keep LF in the working tree, on Windows included. +# +# Git's autocrlf would otherwise rewrite line endings on checkout, and this +# tree does not tolerate that: the build runs under Git Bash, and a Makefile +# recipe or a .S file with CRLF fails in ways that point nowhere near the +# actual cause. +* text eol=lf + +# Binary, never touched. +*.png binary +*.jpg binary +*.ttf binary +*.img binary +*.iso binary +*.tar.gz binary +*.tgz binary +*.zip binary +*.wad binary diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..dd79aea --- /dev/null +++ b/.gitignore @@ -0,0 +1,24 @@ +# Build output. Everything here is reproducible from `make`. +build/ +schunix.img +schunix.iso +*.o +*.d +*.asm +*.sym + +# The staged root filesystem. Assembled by the build from cmd/, lib/ and +# ports/ -- tracking it would mean committing a second copy of every binary +# on every build. +sysroot/ + +# Upstream tarballs, fetched not authored: musl, toybox, mksh, dash, the DOOM +# WADs and usb.ids. 61 MB of other people's release artefacts, all of which +# the build downloads again if they are missing. The patches that matter are +# under ports/*/patches/ and those *are* tracked. +dist/ + +# Test and screenshot output. +*.log +*.ppm +*.png.tmp diff --git a/Assets/Schunix Full Logo.png b/Assets/Schunix Full Logo.png new file mode 100644 index 0000000..9f8e4bd Binary files /dev/null and b/Assets/Schunix Full Logo.png differ diff --git a/Assets/Schunix Full Logo.svg b/Assets/Schunix Full Logo.svg new file mode 100644 index 0000000..2c298ea --- /dev/null +++ b/Assets/Schunix Full Logo.svg @@ -0,0 +1,159 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Assets/Schunix Small Logo.png b/Assets/Schunix Small Logo.png new file mode 100644 index 0000000..35bf538 Binary files /dev/null and b/Assets/Schunix Small Logo.png differ diff --git a/Assets/Schunix Small Logo.svg b/Assets/Schunix Small Logo.svg new file mode 100644 index 0000000..e880a33 --- /dev/null +++ b/Assets/Schunix Small Logo.svg @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Assets/Schunix Text Logo.png b/Assets/Schunix Text Logo.png new file mode 100644 index 0000000..12d839e Binary files /dev/null and b/Assets/Schunix Text Logo.png differ diff --git a/Assets/Schunix Text Logo.svg b/Assets/Schunix Text Logo.svg new file mode 100644 index 0000000..d27a639 --- /dev/null +++ b/Assets/Schunix Text Logo.svg @@ -0,0 +1,85 @@ + + + + + + + + + + + + + + + + + + + diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..c53f721 --- /dev/null +++ b/LICENSE @@ -0,0 +1,187 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship made available under + the License, as indicated by a copyright notice that is included in + or attached to the work (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean, as submitted to the Licensor for inclusion + in the Work by the copyright owner or by an individual or Legal Entity + authorized to submit on behalf of the copyright owner. For the purposes + of this definition, "contribution" means any work of authorship, + including the original version of the Work and any modifications or + additions to that Work or Derivative Works of the Work. + + "Contributor" shall mean Licensor and any Legal Entity on behalf of + whom a Contribution has been received by the Licensor and included + within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by the combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a cross-claim + or counterclaim in a lawsuit) alleging that the Work or any + Contribution embodied within the Work constitutes patent or + contributory patent infringement, then any patent licenses granted to + You under this License for that Work shall terminate as of the date + such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, You must include a readable copy of the + attribution notices contained within such NOTICE file, in + at least one of the following places: within a NOTICE text + file distributed as part of the Derivative Works; within + the Source form or documentation, if provided along with the + Derivative Works; or, within a display generated by the + Derivative Works, if and wherever such third-party notices + normally appear. The contents of the NOTICE file are for + informational purposes only and do not modify the License. + You may add Your own attribution notices within Derivative + Works that You distribute, alongside or in addition to the + NOTICE text from the Work, provided that such additional + attribution notices cannot be construed as modifying the License. + + You may add Your own license statement for Your modifications and + may provide additional grant of rights to use, copy, modify, and + distribute those modifications as a separate work, subject to the + terms and conditions of this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any conditions of TITLE, + NONINFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. + You are solely responsible for determining the appropriateness of + using or reproducing the Work and assume any risks associated with + Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or exemplary damages of any character arising as a + result of this License or out of the use or inability to use the + Work (even if such Contributor has been advised of the possibility + of such damages). + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may offer such + obligations only on Your behalf and on Your sole responsibility, + not on behalf of any other Contributor, and only if You agree to + indemnify, defend, and hold each Contributor harmless for any + liability incurred by, or claims asserted against, such Contributor + by reason of your accepting any warranty or additional liability. + + END OF TERMS AND CONDITIONS + + Copyright 2026 Nexoniarz + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + NOTE: The kernel (kernel/) is derived from xv6 (MIT License, Copyright + 2006-2019 Frans Kaashoek, Robert Morris, Russ Cox, M.I.T. CSAIL). + The MIT license notice is preserved in LICENSE.MIT and must not be + removed. Third-party software redistributed in a built image -- musl, + toybox and mksh, all under ports/ -- keeps its own license; see NOTICE. + + Which outside code may be copied into Schunix and which may not -- the + BSD/GPL rules, the NetBSD advertising clause, and what is deliberately + never shipped -- is written up in docs/licensing.md. Read it before + copying anyone else's code in. diff --git a/LICENSE.MIT b/LICENSE.MIT new file mode 100644 index 0000000..d9726b6 --- /dev/null +++ b/LICENSE.MIT @@ -0,0 +1,29 @@ +The Schunix kernel is a derivative work of xv6, which carries the licence +below. The MIT licence requires this notice be preserved in all copies and +substantial portions of the software, so this file must survive any future +change to the Schunix licence. See NOTICE and docs/licensing.md. + +-------------------------------------------------------------------------------- + +The xv6 software is: + +Copyright (c) 2006-2019 Frans Kaashoek, Robert Morris, Russ Cox, + Massachusetts Institute of Technology + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..5282139 --- /dev/null +++ b/Makefile @@ -0,0 +1,806 @@ +BUILD = build +SYSROOT = sysroot + +# ----------------------------------------------------------------------- +# Parallelism +# +# Nothing here is order-dependent beyond what the rules already declare, so +# there is no reason to compile one file at a time. Override with `make -j1` +# when you want readable output from a failing build. +# ----------------------------------------------------------------------- +NPROC := $(shell nproc 2>/dev/null || echo 4) +MAKEFLAGS += -j$(NPROC) + +# Kernel object files — organized by subsystem +KERN_ARCH = $(addprefix $(BUILD)/kernel/arch/x86_64/, \ + acpi.o e820.o entry.o trapasm.o swtch.o \ + trap.o vm.o lapic.o ioapic.o mp.o) + +KERN_CORE = $(addprefix $(BUILD)/kernel/core/, \ + main.o kalloc.o dma.o spinlock.o sleeplock.o string.o console.o \ + panic.o uaccess.o qr.o time.o) + +KERN_DEV = $(addprefix $(BUILD)/kernel/dev/, \ + bio.o cdev.o device.o fb.o ide.o input.o irq.o kbd.o mouse.o \ + pci.o text.o ttf.o uart.o vga.o ramdisk.o procfs.o) + +# Drivers for hardware that has to be *found* rather than assumed. Anything +# discovered by walking a bus belongs here; the fixed-address legacy devices +# (console, keyboard, PIT, the IDE ports) stay in dev/. +# +# drivers/// -- one directory per driver, so a driver that grows +# a register header, a second source file or a firmware blob has somewhere to +# put it. Classes match include/device.h's DEVCLASS_*. Everything flat was +# already eight files with four called usb_something, and each driver added made +# the directory harder to read. The compile rule uses `mkdir -p $(dir $@)`, so +# the depth costs nothing. +KERN_DRIVERS = $(BUILD)/kernel/drivers/registry.o +KERN_DRIVERS += $(BUILD)/kernel/drivers/gpu/vga/vga_pci.o +KERN_DRIVERS += $(BUILD)/kernel/drivers/gpu/virtio_gpu/virtio_gpu.o +KERN_DRIVERS += $(BUILD)/kernel/drivers/net/e1000/e1000.o +KERN_DRIVERS += $(BUILD)/kernel/drivers/bus/xhci/xhci.o +KERN_DRIVERS += $(BUILD)/kernel/drivers/usb/core/usb.o +KERN_DRIVERS += $(BUILD)/kernel/drivers/usb/hid/usb_hid.o +KERN_DRIVERS += $(BUILD)/kernel/drivers/usb/storage/usb_storage.o +KERN_DRIVERS += $(BUILD)/kernel/drivers/usb/hub/usb_hub.o + +KERN_FS = $(addprefix $(BUILD)/kernel/fs/, \ + exec.o file.o fs.o log.o pipe.o procfs.o) + +# SchFS. Every one of these is the very same source the host tools compile, so +# the kernel and mkschfs cannot disagree about the on-disk format: there is one +# implementation, found and fixed on the host where a test takes a second. +KERN_FS += $(addprefix $(BUILD)/kernel/fs/schfs/, \ + btree.o space.o vol.o obj.o objw.o kmount.o vfs.o) + +# Sockets. Unix domain only for now; an IP stack becomes more files here +# rather than a reshaping of what is already written. +KERN_NET = $(addprefix $(BUILD)/kernel/net/, \ + unix.o) + +KERN_PROC = $(addprefix $(BUILD)/kernel/proc/, \ + proc.o) + +KERN_SYS = $(addprefix $(BUILD)/kernel/sys/, \ + syscall.o sys_proc.o sys_fs.o sys_mem.o sys_net.o \ + sys_time.o sys_sig.o sys_compat.o sys_poll.o linux_abi.o) + +# vectors.o lives alongside trapasm in the arch dir +KERN_ARCH += $(BUILD)/kernel/arch/x86_64/vectors.o + +KERNOBJS = $(KERN_ARCH) $(KERN_CORE) $(KERN_DEV) $(KERN_DRIVERS) \ + $(KERN_FS) $(KERN_NET) $(KERN_PROC) $(KERN_SYS) +# entry.o must be first in link order; remove it from KERNOBJS for the rule +KERN_NOENTRY = $(filter-out $(BUILD)/kernel/arch/x86_64/entry.o, $(KERNOBJS)) + +# ----------------------------------------------------------------------- +# First-party userland +# +# lib/ startup glue + syscall stubs every cmd/ program links against +# cmd/ one directory per program: cmd//main.c -> sysroot/bin/ +# +# To add a program: mkdir cmd/foo, write cmd/foo/main.c, add "foo" to CMDS. +# Third-party software does NOT go here — see ports/. +# ----------------------------------------------------------------------- +ULIB = $(addprefix $(BUILD)/lib/, start.o glue.o usys.o) +CMDS = init fbinfo shmtest socktest inputtest +CMDBINS = $(addprefix $(BUILD)/cmd/, $(CMDS)) + +# ----------------------------------------------------------------------- +# Toolchain +# ----------------------------------------------------------------------- +UNAME_S := $(shell uname -s) +UNAME_P := $(shell uname -p) + +# Windows, under Git Bash or MSYS2 — WINHOST. +# +# Building natively on Windows rather than in WSL, because WSL2 is a real Linux +# kernel in a Hyper-V VM and so hard-requires virtualisation; turning that on +# puts the Hyper-V hypervisor underneath the whole machine and demotes VMware +# to running nested on top of it, which is a real and permanent cost to pay for +# a build environment. +# +# The usual answer is a cross binutils+gcc, which is a day of work that then +# rots. clang does not need any of it: it is a cross-compiler by construction, +# so one ordinary LLVM install targets both the bare-metal kernel and Linux +# userland, and llvm-objcopy/objdump stand in for binutils. Everything else the +# build shells out to — perl, sed, dd, sha256sum — comes with Git Bash. +ifneq (,$(filter MINGW% MSYS% CYGWIN%,$(UNAME_S))) + WINHOST := 1 +endif + +ifeq ($(UNAME_S),Linux) + ifeq ($(UNAME_P),aarch64) + TOOLPREFIX = x86_64-linux-gnu- + else + TOOLPREFIX = + endif +else ifeq ($(UNAME_S),Darwin) + TOOLPREFIX = x86_64-elf- +endif + +QEMU = qemu-system-x86_64 + +# Display for every QEMU target. +# +# virtio-gpu is what the display class binds, and what a compositor should be +# developed against: presenting is explicit, so the host never shows a +# half-drawn frame. +# +# stdvga stays alongside it and is not optional. The bootloader draws the splash +# through VBE in 16-bit real mode and there is no virtio path that early, so a +# machine with only virtio-gpu boots blind until the kernel is up. Both devices: +# virtio-gpu wins the display class, stdvga carries the splash and text console. +QEMU_DISPLAY = -vga std -device virtio-gpu-pci + +ifdef WINHOST + # An explicit --target is what makes clang emit ELF instead of the COFF it + # would default to on this host. Without it every object is silently the + # wrong format and the first thing to complain is the linker. + CC = clang --target=x86_64-elf + CC32 = clang --target=i386-elf + LD = ld.lld + OBJCOPY = llvm-objcopy + OBJDUMP = llvm-objdump + # ld.lld has no --no-warn-rwx-segments: it does not emit that warning, so + # there is nothing to suppress and the flag is a hard error. + LDFLAGS = -m elf_x86_64 -nostdlib + # The bootloader, the AP trampoline, initcode and the cmd/ programs are all + # linked at addresses below one megabyte. GNU ld places a section wherever + # -Ttext says; lld first checks it against a default image base of 0x400000 + # and refuses, because for a hosted program that would be a bug. Saying the + # base is zero is how you tell it this is not a hosted program. + LD_LOWBASE = --image-base=0 + # Host tools (mkfs, mkschfs, the SchFS suites) are ordinary Windows programs. + # They must be built with mingw-w64 rather than clang's default MSVC target: + # tools/mkfs.c includes and , and the Windows SDK has + # neither. + HOSTCC = gcc + # mingw-w64's gcc warns about things glibc's does not (notably %lld/%zu in + # printf against msvcrt). Those warnings are about the host build only and + # say nothing about the image, so they must not stop it. + HOSTWARN = -Wall + # MinGW gcc appends .exe when the -o name has no extension, so a rule + # whose target lacks it is never considered built -- mkfs was recompiled on + # every single invocation of make, forever, because of this. + EXE = .exe + # `python3` is not on PATH from the python.org installer; `python` is. + PYTHON = python + # Compiler for ports/, which build *Linux userland* against musl -- a third + # target, distinct from both the kernel (bare-metal ELF) and the host tools + # (Windows PE). mingw's gcc cannot do it: it targets Windows, so linking it + # against musl gives undefined `stderr` and `__errno_location`. + # + # -nostdlib with the crt files named explicitly, because clang otherwise + # reaches for gcc's crtbeginT.o and libgcc, which do not exist on this host. + MUSLPREFIX = $(abspath $(BUILD))/ports/musl/prefix + PORTCC = clang --target=x86_64-unknown-linux-musl -fuse-ld=lld + PORTCRT = -nostdlib $(MUSLPREFIX)/lib/crt1.o $(MUSLPREFIX)/lib/crti.o + PORTEND = $(MUSLPREFIX)/lib/crtn.o +else + CC = $(TOOLPREFIX)gcc + CC32 = $(CC) -m32 + LD = $(TOOLPREFIX)ld + OBJCOPY = $(TOOLPREFIX)objcopy + OBJDUMP = $(TOOLPREFIX)objdump + LDFLAGS = -m elf_x86_64 -nostdlib --no-warn-rwx-segments + HOSTCC = gcc + HOSTWARN = -Werror -Wall + EXE = + PYTHON = python3 + LD_LOWBASE = + PORTCC = gcc + PORTCRT = + PORTEND = +endif + +# Host tools are generated output and live under build/, per docs/layout.md. +# $(EXE) matters: MinGW appends it, and a rule whose target lacks it never +# looks satisfied. +MKFS = $(BUILD)/tools/mkfs$(EXE) +MKSCHFS = $(BUILD)/tools/mkschfs$(EXE) +SCHFS_TEST = $(BUILD)/tools/schfs_test$(EXE) +SCHFS_OBJT = $(BUILD)/tools/schfs_objtest$(EXE) +SCHFS_FSCK = $(BUILD)/tools/schfs_fsck$(EXE) + +OPT ?= -O0 +# -ftls-model=local-exec: the kernel is *the* executable, never a shared +# object, so its __thread variables (cpu, proc) are always at a fixed negative +# offset from the thread pointer. Saying so lets the compiler emit a direct +# `movq %rcx, %fs:-0x10` and never materialise the thread pointer as a value. +# +# GCC picked local-exec on its own for this code; clang did not, and generated +# `movq %fs:0x0, %rax; addq $-0x10, %rax; ...` instead -- which reads the TCB +# self-pointer at %fs:0, a word this kernel does not maintain and something +# else has apparently always been scribbling on. Harmless for fifteen years +# because nothing ever read it; instant triple fault the moment a compiler did. +XFLAGS = -m64 -DX64 -mcmodel=large -mtls-direct-seg-refs -mno-red-zone +XFLAGS += -ftls-model=local-exec + +# Probed once, with :=, not on every expansion of CFLAGS. CFLAGS is a +# recursively-expanded variable, so a $(shell) appended to it re-ran the probe +# on every single compile -- one extra gcc process per object file, for an +# answer that cannot change during a build. +NO_SSP := $(shell $(CC) -fno-stack-protector -E -x c /dev/null >/dev/null 2>&1 && echo -fno-stack-protector) + +CFLAGS = -fno-pic -static -fno-builtin -fno-strict-aliasing -Wall -MD -ggdb -fno-omit-frame-pointer +CFLAGS += -ffreestanding -fno-common -nostdlib -I./include $(XFLAGS) $(OPT) +CFLAGS += $(NO_SSP) $(CFLAGS_EXTRA) + +# sys/ also needs to find table.inc via a relative include +CFLAGS_SYS = $(CFLAGS) -I./kernel/sys + + + +# ----------------------------------------------------------------------- +# Ramdisk root (opt-in): make RAMROOT=1 +# +# Links a complete SCHFS image into the kernel and boots from it instead of +# the IDE disk, so the physical disk can be reformatted from a running system. +# Off by default because it adds RAMDISK_BLOCKS KB to the kernel, and stage2 +# loads the kernel by PIO one sector at a time -- a 6 MB kernel takes visibly +# longer to boot than a 350 KB one. +# ----------------------------------------------------------------------- +RAMDISK_BLOCKS ?= 6000 + +# Inodes for the ramdisk root. The inode table is preallocated, so mkfs's +# default of 12000 cost 1501 of those 6000 blocks -- a quarter of the image, to +# describe about 260 files. 2000 is still an order of magnitude of headroom and +# costs 251 blocks. +RAMDISK_NINODES ?= 2000 + +# Kept out of the ramdisk root. +# +# Empty right now, and the machinery is kept anyway, because this is a licensing +# rule before it is a size one: the ramdisk image is linked *into the kernel +# binary*, so anything staged in sysroot/ when it is built becomes part of a +# distributable artifact. Anything that arrives in sysroot/ under a licence that +# cannot be combined with Apache-2.0, or that simply is not ours to redistribute, +# belongs on this list rather than in someone's memory. +# +# This is not hypothetical: a 33 MB test payload once landed here and overflowed +# the 6 MB image, and mkfs's "increase FSSIZE" message then pointed at a constant +# that had nothing to do with it. +# usb.ids is 730 KB of a 6 MB image -- 12% of the ramdisk root -- and nothing +# needs it to boot. It is a lookup table for a userspace tool, so it stays on +# the disk image and out of the kernel binary. +RAMDISK_EXCLUDE = usr/share/usb.ids +RAMDISK_EXCLUDE_ARGS = $(foreach p,$(RAMDISK_EXCLUDE),--exclude $(p)) + +ifdef RAMROOT +CFLAGS += -DRAMDISK_ROOT +RAMDISK_OBJ = $(BUILD)/kernel/ramdisk_obj +else +RAMDISK_OBJ = +endif + +# ----------------------------------------------------------------------- +# Top-level targets +# ----------------------------------------------------------------------- +all: schunix.img + +schunix.img: $(BUILD)/boot/bootblock $(BUILD)/boot/stage2 $(BUILD)/kernel/kernel $(BUILD)/schfs.img + dd if=/dev/zero of=schunix.img count=10000 + dd if=$(BUILD)/boot/bootblock of=schunix.img conv=notrunc + dd if=$(BUILD)/boot/stage2 of=schunix.img seek=1 conv=notrunc + # seek is 1 + STAGE2_SECTORS, not a literal. It was 25 against a 24-sector + # stage2; raising STAGE2_SECTORS for the boot logo without touching it wrote + # the kernel straight over stage2's own tail. + dd if=$(BUILD)/kernel/kernel of=schunix.img seek=$$(( 1 + $(STAGE2_SECTORS) )) conv=notrunc + +kernel: $(BUILD)/kernel/kernel + +# Build the ramdisk-root variant. This is a target rather than just +# "make RAMROOT=1" because toggling RAMROOT changes CFLAGS (-DRAMDISK_ROOT), +# and make has no way to notice that: objects compiled for one setting would be +# silently reused for the other, leaving ROOTDEV pointing at the wrong device. +# Dropping the compiled objects first makes the switch always correct. +ramroot: + rm -rf $(BUILD)/kernel $(BUILD)/lib $(BUILD)/cmd + $(MAKE) RAMROOT=1 + +cmd: $(CMDBINS) + +ports: + $(MAKE) -C ports + +# ----------------------------------------------------------------------- +# Bootable ISO +# +# Always a ramdisk-root build. An ISO that needed a second disk to find its +# root filesystem would not be an install medium, it would be a puzzle -- and +# read-only media cannot host a writable root anyway. Same object-wiping +# preamble as the `ramroot` target, for the same reason: RAMROOT changes +# CFLAGS and make cannot see that. +# ----------------------------------------------------------------------- +iso: + rm -rf $(BUILD)/kernel $(BUILD)/lib $(BUILD)/cmd + $(MAKE) RAMROOT=1 schunix.iso + +schunix.iso: $(BUILD)/boot/cdboot $(BUILD)/boot/stage2 $(BUILD)/kernel/kernel + STAGE2_SECTORS=$(STAGE2_SECTORS) sh tools/mkiso.sh $@ + +# 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 +# in someone's optical drive. +qemu-iso: schunix.iso + $(QEMU) -serial mon:stdio $(QEMU_DISPLAY) -cpu qemu64,+rdtscp \ + -cdrom schunix.iso -boot d \ + -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/bootasm.S and boot/stage2main.c). +# ----------------------------------------------------------------------- +# STAGE2_SECTORS used to be written out three times -- here, in boot/bootasm.S +# and in boot/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. +STAGE2_SECTORS = 48 +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/bootasm.S + @mkdir -p $(BUILD)/boot + $(CC32) $(STAGE2_DEF) -fno-builtin -fno-pic -nostdinc -I./include -c boot/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/cdboot.S. +$(BUILD)/boot/cdboot: boot/cdboot.S + @mkdir -p $(BUILD)/boot + $(CC32) -fno-builtin -fno-pic -nostdinc -I./include -c boot/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/stage2.S boot/stage2main.c include/logo.h + @mkdir -p $(BUILD)/boot + $(CC32) $(STAGE2_DEF) -fno-builtin -fno-pic -nostdinc -I./include -c boot/stage2.S -o $(BUILD)/boot/stage2asm.o + $(CC32) $(STAGE2_DEF) -fno-builtin -fno-pic -O -nostdinc -I./include -c boot/stage2main.c -o $(BUILD)/boot/stage2main.o + $(LD) -m elf_i386 -nostdlib $(LD_LOWBASE) -n -N -e stage2start -Ttext 0x7E00 \ + -o $(BUILD)/boot/stage2tmp.o $(BUILD)/boot/stage2asm.o $(BUILD)/boot/stage2main.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 +# ----------------------------------------------------------------------- +$(BUILD)/kernel/kernel: $(KERNOBJS) $(BUILD)/kernel/initcode_obj $(BUILD)/kernel/entryother_obj $(RAMDISK_OBJ) kernel/kernel.ld + @mkdir -p $(BUILD)/kernel + $(LD) $(LDFLAGS) -T kernel/kernel.ld -o $@ \ + $(BUILD)/kernel/arch/x86_64/entry.o $(KERN_NOENTRY) \ + $(BUILD)/kernel/initcode_obj $(BUILD)/kernel/entryother_obj $(RAMDISK_OBJ) + $(OBJDUMP) -S $@ > $(BUILD)/kernel/kernel.asm + +# The ramdisk root image, and the object wrapping it for the linker. Built from +# the same staged sysroot as the main image, just capped at RAMDISK_BLOCKS -- +# mkfs's content dedup means the whole userland still fits in a few MB. +$(BUILD)/fs-ram.img: $(MKFS) $(BUILD)/sysroot.stamp + @mkdir -p $(BUILD) + @rm -f $@ + $(MKFS) $@ --size $(RAMDISK_BLOCKS) --ninodes $(RAMDISK_NINODES) \ + $(RAMDISK_EXCLUDE_ARGS) --sysroot $(SYSROOT) + +$(BUILD)/kernel/ramdisk_obj: $(BUILD)/fs-ram.img + @mkdir -p $(BUILD)/kernel + $(LD) -r -b binary -m elf_x86_64 -o $@ $< + $(OBJCOPY) \ + --redefine-sym _binary_build_fs_ram_img_start=_binary_ramdisk_start \ + --redefine-sym _binary_build_fs_ram_img_end=_binary_ramdisk_end \ + --redefine-sym _binary_build_fs_ram_img_size=_binary_ramdisk_size \ + $@ + +# AP startup blob +$(BUILD)/kernel/entryother: kernel/arch/x86_64/entryother.S + @mkdir -p $(BUILD)/kernel + $(CC) $(CFLAGS) -fno-pic -nostdinc -I./include -c $< -o $(BUILD)/kernel/entryother.o + $(LD) $(LDFLAGS) $(LD_LOWBASE) -e start -Ttext 0x7000 -o $(BUILD)/kernel/entryothertmp.o $(BUILD)/kernel/entryother.o + $(OBJCOPY) -S -O binary -j .text $(BUILD)/kernel/entryothertmp.o $@ + +# NOTE: ld -r -b binary derives its symbol names from the *path* of the input +# file, so these --redefine-sym pairs must be kept in sync with the build +# directory layout above. Move the file, and the left-hand names change. +$(BUILD)/kernel/entryother_obj: $(BUILD)/kernel/entryother + $(LD) -r -b binary -m elf_x86_64 -o $@ $< + $(OBJCOPY) \ + --redefine-sym _binary_build_kernel_entryother_start=_binary_entryother_start \ + --redefine-sym _binary_build_kernel_entryother_end=_binary_entryother_end \ + --redefine-sym _binary_build_kernel_entryother_size=_binary_entryother_size \ + $@ + +# Init stub (first user-space code, embedded in the kernel image) +$(BUILD)/lib/initcode: lib/initcode.S + @mkdir -p $(BUILD)/lib + $(CC) $(CFLAGS) -fno-pic -nostdinc -I./include -c $< -o $(BUILD)/lib/initcode.o + $(LD) -m elf_x86_64 -nostdlib $(LD_LOWBASE) -N -e start -Ttext 0 -o $(BUILD)/lib/initcodetmp.o $(BUILD)/lib/initcode.o + $(OBJCOPY) -S -O binary $(BUILD)/lib/initcodetmp.o $@ + +$(BUILD)/kernel/initcode_obj: $(BUILD)/lib/initcode + @mkdir -p $(BUILD)/kernel + $(LD) -r -b binary -m elf_x86_64 -o $@ $< + $(OBJCOPY) \ + --redefine-sym _binary_build_lib_initcode_start=_binary_initcode_start \ + --redefine-sym _binary_build_lib_initcode_end=_binary_initcode_end \ + --redefine-sym _binary_build_lib_initcode_size=_binary_initcode_size \ + $@ + +# ----------------------------------------------------------------------- +# vectors.S — generated from perl script +# ----------------------------------------------------------------------- +kernel/arch/x86_64/vectors.S: tools/vectors.pl + perl tools/vectors.pl > $@ + +# ----------------------------------------------------------------------- +# Compile rules for kernel/ +# ----------------------------------------------------------------------- +$(BUILD)/kernel/arch/x86_64/%.o: kernel/arch/x86_64/%.c + @mkdir -p $(BUILD)/kernel/arch/x86_64 + $(CC) $(CFLAGS) -c $< -o $@ + +$(BUILD)/kernel/arch/x86_64/%.o: kernel/arch/x86_64/%.S + @mkdir -p $(BUILD)/kernel/arch/x86_64 + $(CC) $(CFLAGS) -c $< -o $@ + +$(BUILD)/kernel/core/%.o: kernel/core/%.c + @mkdir -p $(BUILD)/kernel/core + $(CC) $(CFLAGS) -c $< -o $@ + +$(BUILD)/kernel/dev/%.o: kernel/dev/%.c + @mkdir -p $(BUILD)/kernel/dev + $(CC) $(CFLAGS) -c $< -o $@ + +# `$(dir $@)` rather than a fixed path: drivers are grouped into one directory +# per device class (gpu/, net/, bus/, usb/), so the output directory varies with +# the target and a hardcoded mkdir only creates the top one. +$(BUILD)/kernel/drivers/%.o: kernel/drivers/%.c + @mkdir -p $(dir $@) + $(CC) $(CFLAGS) -c $< -o $@ + +$(BUILD)/kernel/fs/%.o: kernel/fs/%.c + @mkdir -p $(BUILD)/kernel/fs + $(CC) $(CFLAGS) -c $< -o $@ + +$(BUILD)/kernel/fs/schfs/%.o: kernel/fs/schfs/%.c + @mkdir -p $(BUILD)/kernel/fs/schfs + $(CC) $(CFLAGS) -I./kernel/fs/schfs -c $< -o $@ + +$(BUILD)/kernel/net/%.o: kernel/net/%.c + @mkdir -p $(BUILD)/kernel/net + $(CC) $(CFLAGS) -c $< -o $@ + +$(BUILD)/kernel/proc/%.o: kernel/proc/%.c + @mkdir -p $(BUILD)/kernel/proc + $(CC) $(CFLAGS) -c $< -o $@ + +# sys/ gets an extra -I so syscall.c can do #include "table.inc" +$(BUILD)/kernel/sys/%.o: kernel/sys/%.c + @mkdir -p $(BUILD)/kernel/sys + $(CC) $(CFLAGS_SYS) -c $< -o $@ + +# ----------------------------------------------------------------------- +# First-party userland: lib/ + cmd/ +# ----------------------------------------------------------------------- +$(BUILD)/lib/%.o: lib/%.c + @mkdir -p $(BUILD)/lib + $(CC) $(CFLAGS) -c $< -o $@ + +$(BUILD)/lib/%.o: lib/%.S + @mkdir -p $(BUILD)/lib + $(CC) $(CFLAGS) -c $< -o $@ + +$(BUILD)/cmd/%: cmd/%/main.c $(ULIB) + @mkdir -p $(BUILD)/cmd + $(CC) $(CFLAGS) -c $< -o $(BUILD)/cmd/$*.o + $(LD) $(LDFLAGS) $(LD_LOWBASE) -e _start -Ttext 0x4000 -o $@ $(BUILD)/cmd/$*.o $(ULIB) + $(OBJCOPY) -S $@ + +$(SYSROOT)/bin/%: $(BUILD)/cmd/% | $(SYSROOT)/bin + cp $< $@ + chmod 755 $@ + +$(SYSROOT)/bin: + mkdir -p $@ + +# ----------------------------------------------------------------------- +# Filesystem image +# ----------------------------------------------------------------------- +# ----------------------------------------------------------------------- +# SchFS B-tree torture test (host program). +# +# The B-tree underpins every record type in SchFS, so it is developed and +# tested here -- against a reference model, with simulated crashes -- before +# it is linked into the kernel. Run it after any change to +# kernel/fs/schfs/btree.c. +# ----------------------------------------------------------------------- +schfs-test: $(SCHFS_TEST) + $(SCHFS_TEST) + +# Use ASan/UBSan when the runtime libraries are actually installed; they catch +# exactly the pointer-arithmetic mistakes this code is most exposed to, but not +# every machine has libasan, so fall back rather than failing the build. +SAN := $(shell echo 'int main(void){return 0;}' > /tmp/.santest.c 2>/dev/null && \ + gcc -fsanitize=address,undefined -o /tmp/.santest /tmp/.santest.c \ + >/dev/null 2>&1 && echo -fsanitize=address,undefined) + +SCHFS_SRC = kernel/fs/schfs/btree.c kernel/fs/schfs/btree.h \ + kernel/fs/schfs/space.c kernel/fs/schfs/vol.c \ + kernel/fs/schfs/obj.c kernel/fs/schfs/objw.c \ + kernel/fs/schfs/schfs_int.h include/schfs.h + +# -iquote, not -I: include/ has its own errno.h, stat.h, fcntl.h and types.h, +# and -I would put them ahead of the system ones for a host program that needs +# the real thing. -iquote applies only to "quoted" includes, so btree.h still +# finds schfs.h while still means the C library's. +HOSTCFLAGS = -Wall -Wextra -O1 -g $(SAN) -iquote ./include + +$(SCHFS_TEST): tools/schfs_test.c $(SCHFS_SRC) + @mkdir -p $(BUILD)/tools + @mkdir -p $(BUILD) + $(HOSTCC) $(HOSTCFLAGS) -o $@ tools/schfs_test.c + +# The object layer -- files, directories, and whether deleting things gives the +# space back. Runs against a memory-backed volume, so a full create/write/ +# delete/remount cycle takes milliseconds instead of a rebuild and a boot. +$(SCHFS_OBJT): tools/schfs_objtest.c $(SCHFS_SRC) + @mkdir -p $(BUILD)/tools + @mkdir -p $(BUILD) + $(HOSTCC) $(HOSTCFLAGS) -o $@ tools/schfs_objtest.c + +schfs-objtest: $(SCHFS_OBJT) + $(SCHFS_OBJT) + +# The image writer and checker. Both #include the real filesystem sources, so +# there is exactly one implementation of the on-disk format: a host tool and the +# kernel cannot drift apart because there is nothing to drift from. +$(MKSCHFS): tools/mkschfs.c $(SCHFS_SRC) + @mkdir -p $(BUILD)/tools + @mkdir -p $(BUILD) + $(HOSTCC) $(HOSTCFLAGS) -o $@ tools/mkschfs.c + +$(SCHFS_FSCK): tools/schfs_fsck.c $(SCHFS_SRC) + @mkdir -p $(BUILD)/tools + @mkdir -p $(BUILD) + $(HOSTCC) $(HOSTCFLAGS) -o $@ tools/schfs_fsck.c + +mkschfs: $(MKSCHFS) +schfs-fsck: $(SCHFS_FSCK) + +# End-to-end filesystem test: build an image from a directory tree, read it +# back with schfs_fsck, and diff the result against the original. +# +# The oracle here is the host's own filesystem, which knows nothing about SchFS. +# schfs-test proves the B-tree matches a model written alongside it; this proves +# the *filesystem* matches a tree that exists independently of it, so a writer +# and reader that agree on something wrong are still caught. +schfs-image-test: $(MKSCHFS) $(SCHFS_FSCK) + @sh tools/schfs_roundtrip.sh $(MKSCHFS) $(SCHFS_FSCK) + +# Every SchFS suite. Run this after touching anything under kernel/fs/schfs. +schfs-check: schfs-test schfs-objtest schfs-image-test + +# Crash the kernel on purpose and photograph the result. +# +# The crash screen is the one part of the system you cannot look at by using +# the system, which is exactly why it rots. This makes "does it still render" +# a question with a thirty-second answer. Needs a ramroot build first. +crashshot: + pwsh -File tools/crashshot.ps1 + +# Read the QR code back off a captured crash screen. End-to-end on purpose: it +# reads the pixels that were painted, so payload construction in panic context, +# the encoder, the module scaling and the quiet zone are all covered by whether +# text comes back. Run `make crashshot` first. Needs the same Python packages +# as `qrcheck`. +crashqr: + $(PYTHON) tools/crashqr.py + +$(MKFS): tools/mkfs.c include/fs.h include/stat.h include/param.h + @mkdir -p $(BUILD)/tools + $(HOSTCC) $(HOSTWARN) -I. -o $(MKFS) tools/mkfs.c + +# Stage everything a running system needs into sysroot/, then image it. +# +# ports/ is built via its own Makefile rather than per-port from here, because +# that is where the inter-port ordering lives (mksh and toybox both link +# against musl, whose install prefix is under build/ — so `make clean` wipes it +# and musl must be rebuildable as part of the normal chain, not out-of-band). +# +# On Windows the ports are NOT rebuilt. musl, mksh and toybox are Linux +# userland built by configure scripts and kconfig, and getting those to run +# under Git Bash against a clang cross-target is a separate project from +# building the OS. Their outputs are already staged in sysroot/ and change +# perhaps twice a year, so the cost of not rebuilding them here is close to +# zero -- and it is stated out loud rather than silently skipped, because a +# port that quietly does not rebuild is exactly the kind of thing that wastes +# an afternoon later. +sysroot-install: $(addprefix $(SYSROOT)/bin/, $(CMDS)) +ifdef WINHOST + @echo "ports: skipped (Windows host) -- musl/mksh/toybox are used as already staged in $(SYSROOT)" + @test -f $(SYSROOT)/bin/sh || { echo "ports: $(SYSROOT)/bin/sh is missing and cannot be built here; build ports on a Linux host once" >&2; exit 1; } +else + $(MAKE) -C ports CC=gcc +endif + +# A stamp whose timestamp advances only when the staged tree actually changes. +# +# make cannot depend on "the contents of a directory", and sysroot/ is filled +# by two different builds (cmd/ from here, ports/ recursively), so the images +# below used to depend on the phony sysroot-install -- which meant every image +# was rebuilt on every invocation, whether or not a single byte had moved. +# That cost 100 seconds of a 108-second no-op build. +# +# Path, size and mtime rather than content hashes: reading every staged file +# would undo the point, and a build system that lies about a file whose mtime +# and size both stayed the same has bigger problems. The cmp-then-move keeps +# the stamp's mtime unchanged when the summary is unchanged, which is the +# whole trick -- writing it unconditionally would just move the problem. +$(BUILD)/sysroot.stamp: sysroot-install + @mkdir -p $(BUILD) + @find $(SYSROOT) -type f -printf '%p %s %T@\n' | sort > $@.new + @cmp -s $@.new $@ || mv -f $@.new $@ + @rm -f $@.new + +$(BUILD)/fs.img: $(MKFS) $(BUILD)/sysroot.stamp + @mkdir -p $(BUILD) + @echo "Creating Schunix File System Image..." + @# 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 + @# kernel has already relinked, leaving schunix.img stale. The next test run + @# then boots the previous kernel and the change appears to have done + @# nothing, which has cost several debugging cycles. Say so plainly. + @rm -f $@ || { \ + echo ""; \ + echo " $@ is in use -- a QEMU is still holding it."; \ + echo " Close it, or: pwsh -Command \"Get-Process qemu-system-x86_64 |"; \ + echo " Where-Object { -not \$$_.MainWindowTitle } | Stop-Process -Force\""; \ + echo ""; \ + exit 1; \ + } + $(MKFS) $@ --sysroot $(SYSROOT) + +# ----------------------------------------------------------------------- +# QEMU +# ----------------------------------------------------------------------- +qemu: schunix.img $(BUILD)/schfs.img + $(QEMU) -serial mon:stdio $(QEMU_DISPLAY) -cpu qemu64,+rdtscp \ + -hda schunix.img -hdb $(BUILD)/schfs.img \ + -m 1G -smp 2 -machine pc-i440fx-9.2 + +# The root filesystem. Built from the staged sysroot; the kernel mounts it as +# / in forkret(). +$(BUILD)/schfs.img: $(MKSCHFS) $(BUILD)/sysroot.stamp + @mkdir -p $(BUILD) + @rm -f $@ + ./$(MKSCHFS) $@ --size 8000 --label schunix --sysroot $(SYSROOT) + +# Boot with a SchFS volume attached as the second disk. +# +# Needs the ramdisk-root kernel ("make ramroot" first): with RAMROOT the root +# filesystem comes out of memory and device 1 is free, which is exactly the +# arrangement docs/architecture/schfs.md asks for -- a new-format disk that +# fails to mount cannot stop the system booting. Without RAMROOT, device 1 is +# the live v1 root and the SchFS probe correctly finds nothing there. +# +# Once up: cat /proc/schfs +qemu-schfs: schunix.img $(BUILD)/schfs.img + $(QEMU) -serial mon:stdio $(QEMU_DISPLAY) -cpu qemu64,+rdtscp \ + -hda schunix.img -hdb $(BUILD)/schfs.img \ + -m 1G -smp 2 -machine pc-i440fx-9.2 + +# ----------------------------------------------------------------------- +# Misc +# ----------------------------------------------------------------------- +# schunix.img is deliberately NOT here. It is a real file with real +# prerequisites; listing it as phony made make skip the timestamp check and +# re-run the four dd commands -- including zeroing five megabytes -- on every +# single invocation, including ones where nothing had changed. +# 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 +# would put Python on the critical path of every clean build for no benefit. +# Run this by hand after changing the art. +LOGO_SRC = Assets/Schunix Small Logo.png +LOGO_PX = 128 + +logo: + $(PYTHON) tools/mklogo.py "$(LOGO_SRC)" include/logo.h $(LOGO_PX) + +# The USB vendor/product name database, from linux-usb.org. +# +# Dual-licensed GPL-2.0-or-later **or** 3-clause BSD; Schunix takes the BSD +# option, which is what makes it usable at all. See NOTICE. +# +# Cached in dist/ and refreshed only when you ask, NOT on every build. That was +# the request, and it is the one thing here worth pushing back on: fetching on +# every compilation makes an offline build fail, makes the same source produce +# different images on different days, and puts a third-party web server on the +# critical path of `make`. dist/ exists precisely so a rebuild never needs the +# network -- see docs/layout.md. `make usb-ids` is a deliberate act; the file it +# writes is committed and dated, so what shipped is always knowable. +USB_IDS_URL = http://www.linux-usb.org/usb.ids + +usb-ids: + @echo "==> fetching $(USB_IDS_URL)" + @curl -sfL --max-time 60 -o dist/usb.ids.new "$(USB_IDS_URL)" || \ + { echo " fetch failed; keeping the cached copy"; rm -f dist/usb.ids.new; exit 1; } + @# A truncated or error-page download would otherwise silently replace a + @# good database with rubbish. Check it looks like what it claims to be. + @grep -q "^# Version:" dist/usb.ids.new || \ + { echo " that is not a usb.ids file; keeping the cached copy"; \ + rm -f dist/usb.ids.new; exit 1; } + @mv dist/usb.ids.new dist/usb.ids + @cp dist/usb.ids $(SYSROOT)/usr/share/usb.ids + @grep -m1 "^# Version:" dist/usb.ids + +# Build the kernel's QR encoder on the host and check it. +# +# A QR symbol with a mistyped table entry still looks like a perfectly plausible +# grid of black squares, so the self-checks in qrtest.c cover what can be +# verified mechanically and the PNG covers the rest: point a phone at it. +QRTEST = $(BUILD)/tools/qrtest$(EXE) + +$(QRTEST): tools/qrtest.c kernel/core/qr.c include/qr.h + @mkdir -p $(BUILD)/tools + $(HOSTCC) $(HOSTWARN) -I./include -o $@ tools/qrtest.c + +qrtest: $(QRTEST) + @mkdir -p $(BUILD) + ./$(QRTEST) $(BUILD)/qr.png + +# The check that actually decides whether the encoder works: build a symbol and +# read it back with a real decoder, plus a module-for-module comparison against +# a second encoder. Needs Python packages that are not part of the build, so it +# is not wired into `make` -- install them wherever you like and point +# PYTHONPATH at it: +# +# pip install --target build/pylibs opencv-python-headless numpy segno +# PYTHONPATH=build/pylibs make qrcheck +qrcheck: $(QRTEST) + $(PYTHON) tools/qrcheck.py + +.PHONY: all kernel ramroot cmd ports sysroot-install qemu \ + schfs-test schfs-objtest schfs-image-test schfs-check mkschfs \ + schfs-fsck crashshot qemu-schfs iso qemu-iso clean distclean \ + logo qrtest qrcheck crashqr usb-ids +.PRECIOUS: $(BUILD)/%.o + +# 'clean' removes generated output only. dist/ (upstream tarballs) survives, +# so a rebuild never needs the network. +clean: + rm -rf $(BUILD) schunix.img schunix.iso kernel/arch/x86_64/vectors.S + +distclean: clean + rm -rf dist + +# Header dependencies, from gcc -MD. +# +# This used to be a set of globs including `$(BUILD)/kernel/**/*.d`. make's +# wildcard has no recursive `**`: it behaved as a single `*` and matched +# exactly one level, so build/kernel/core/*.d was picked up while +# build/kernel/arch/x86_64/*.d and build/kernel/fs/schfs/*.d were not. +# Everything under arch/ and schfs/ therefore had NO header dependency +# tracking at all -- editing include/schfs.h or include/mmu.h left stale +# objects that linked without complaint. find actually recurses. +-include $(shell find $(BUILD) -name '*.d' 2>/dev/null) diff --git a/NOTICE b/NOTICE new file mode 100644 index 0000000..e57492b --- /dev/null +++ b/NOTICE @@ -0,0 +1,145 @@ +Schunix +Copyright 2026 Nexoniarz + +This product includes software developed as part of the Schunix project, +licensed under the Apache License, Version 2.0. See LICENSE. + +================================================================================ +Third-party software +================================================================================ + +Schunix incorporates and redistributes software written by others. Their +licences apply to their code and are NOT superseded by the Schunix licence. +A built Schunix image contains binaries derived from all of the following. + +-------------------------------------------------------------------------------- +xv6-x86_64 / xv6 MIT License +-------------------------------------------------------------------------------- +Copyright (c) 2006-2019 Frans Kaashoek, Robert Morris, Russ Cox, + Massachusetts Institute of Technology CSAIL + +The Schunix kernel is a derivative work of xv6. Substantial portions of +kernel/ -- notably the process, trap, virtual memory, buffer cache and v1 +filesystem code -- descend from it, however heavily rewritten. The MIT licence +requires that this notice be preserved in all copies; see LICENSE.MIT. + +-------------------------------------------------------------------------------- +musl libc 1.2.5 MIT License +-------------------------------------------------------------------------------- +Copyright (c) 2005-2020 Rich Felker and contributors + +Built from ports/musl. Provides the C library every userland program links +against. See the extracted source's COPYRIGHT file for the full contributor +list, which the MIT terms require be carried along with binaries. + +-------------------------------------------------------------------------------- +toybox 0.8.12 Zero-Clause BSD (0BSD) +-------------------------------------------------------------------------------- +Copyright (c) 2006-2024 Rob Landley and contributors + +Built from ports/toybox. Supplies the standard command set in /bin. 0BSD +imposes no attribution requirement at all; this entry is courtesy, not +obligation. + +-------------------------------------------------------------------------------- +mksh R59c MirOS Licence +-------------------------------------------------------------------------------- +Copyright (c) 2002-2023 Thorsten Glaser and contributors +Portions derived from the Public Domain Korn Shell (pdksh) + +Built from ports/mksh. Supplies /bin/sh. The MirOS Licence is permissive and +requires the copyright notice be retained. + +-------------------------------------------------------------------------------- +Intel 8254x device identifiers BSD 3-Clause +-------------------------------------------------------------------------------- +Copyright (c) 2001-2020, Intel Corporation +All rights reserved. + +The PCI device IDs in include/e1000.h are transcribed from FreeBSD's +sys/dev/e1000/e1000_hw.h, which carries the Intel 3-clause BSD licence above +(verified: no advertising clause). Only the identifier constants are used -- +data, not implementation. kernel/drivers/net/e1000/e1000.c is written from +Intel's public manual 317453 and contains no FreeBSD or Intel driver code. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the conditions of the 3-clause BSD +licence are met, including retention of this notice and the disclaimer, and +that neither the name of Intel Corporation nor the names of its contributors +are used to endorse or promote products derived from this software without +specific prior written permission. + +-------------------------------------------------------------------------------- +usb.ids -- USB vendor and product database BSD 3-Clause (dual-licensed) +-------------------------------------------------------------------------------- +Maintained by Stephen J. Gowdy +http://www.linux-usb.org/usb.ids + +Distributed by its maintainer under the terms of *either* the GNU General +Public License version 2 or later, *or* the 3-clause BSD Licence. Schunix takes +the BSD option; the GPL option is not exercised and could not be, since +Apache-2.0 and GPL-2.0-only cannot be combined (see docs/licensing.md section +2). + +Shipped verbatim as /usr/share/usb.ids. Cached in dist/ and refreshed with +`make usb-ids`, never automatically -- the version and date are in the file's +own header, so what shipped is always knowable. + +-------------------------------------------------------------------------------- +Roboto Apache-2.0 +-------------------------------------------------------------------------------- +Copyright the Roboto Project Authors (https://github.com/googlefonts/roboto) + +Shipped as /usr/share/fonts/Roboto-Regular.ttf, from ports/Roboto. Licensed +under Apache-2.0 -- the same licence as Schunix itself, so it carries no +obligation beyond this notice. Intended for the desktop, window manager and +compositor. + +-------------------------------------------------------------------------------- +JetBrains Mono SIL OFL 1.1 +-------------------------------------------------------------------------------- +Copyright 2020 The JetBrains Mono Project Authors +(https://github.com/JetBrains/JetBrainsMono) + +Shipped as /usr/share/fonts/JetBrainsMono-Regular.ttf, from ports/JetBrains_Mono +(full licence text at ports/JetBrains_Mono/OFL.txt). Intended for the terminal +and TTY. + +Under the SIL Open Font License 1.1 the font remains under OFL even inside this +Apache-2.0 system -- that does not reach any other part of Schunix -- and it may +not be sold on its own. Bundling it with an operating system is expressly +permitted. JetBrains Mono declares no Reserved Font Name, so a modified version +(a pre-rasterised bitmap, for instance) may keep the name. + +-------------------------------------------------------------------------------- +"New File System For XV6" MIT License +-------------------------------------------------------------------------------- +Copyright (c) Qi Ma, Leyi Pan, Ao Sun, Peiran Xu + +kernel/fs/fs.c and parts of tools/mkfs.c derive from this project, which extends +xv6's file system with features Schunix still uses today: + + - the HTree hashed directory index (BKDRHash, HASHRANGE, the primary/overflow + bucket layout), after Daniel Phillips, "A Directory Index for EXT2", + 5th Annual Linux Showcase & Conference, 2001 + - Ext4-style extents (T_EXTENT) + - the inode bitmap + - symbolic links, file permissions, lseek and append + +Licensed under the MIT License, which permits this use and requires that the +copyright notice and permission notice be retained. This entry is that +retention; it was missing until 2026-08-09 and is recorded here regardless of +what happens to the code, because the obligation attaches to every copy already +made. + +Distinct from xv6 itself (LICENSE.MIT), which this project in turn derives from. + +================================================================================ + +The build downloads upstream tarballs into dist/ and applies the patches in +ports//patches/. Nothing in ports/ is Schunix code; do not add first-party +work there. See docs/layout.md. + +Licence questions -- what may be copied in, what may never be, and how to check +a NetBSD file's clause before using it -- are worked through in +docs/licensing.md. diff --git a/boot/bootasm.S b/boot/bootasm.S new file mode 100644 index 0000000..10cc78f --- /dev/null +++ b/boot/bootasm.S @@ -0,0 +1,101 @@ +# 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 diff --git a/boot/cdboot.S b/boot/cdboot.S new file mode 100644 index 0000000..db3bd3a --- /dev/null +++ b/boot/cdboot.S @@ -0,0 +1,48 @@ +# 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 +#define BOOT_MEDIA_FLAG_SEG 0x3000 +#define BOOT_MEDIA_FLAG_OFF 0x101 +#define BOOT_MEDIA_CD 1 + +.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 $BOOT_MEDIA_FLAG_SEG, %ax + movw %ax, %es + movw $BOOT_MEDIA_FLAG_OFF, %di + movb $BOOT_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 diff --git a/boot/stage2.S b/boot/stage2.S new file mode 100644 index 0000000..340d0f4 --- /dev/null +++ b/boot/stage2.S @@ -0,0 +1,129 @@ +# 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). + movw $0x3000, %ax + movw %ax, %es + xorw %di, %di # ES:DI = 3000:0000 = flat 0x30000 + movw $0x0143, %cx + movw $0x4F01, %ax + int $0x10 + + cmpw $0x004F, %ax + jne e820 + movw $0x4143, %bx + 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 via BIOS teletype (real mode only). +# Clobbers AX, BX, SI. +print16: +.Lp16loop: + lodsb + testb %al, %al + jz .Lp16done + movb $0x0E, %ah + movw $0x0007, %bx + int $0x10 + jmp .Lp16loop +.Lp16done: + ret + +bootmsg: + .asciz "Schunix 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 + call stage2main + +spin: + jmp spin + +.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 diff --git a/boot/stage2main.c b/boot/stage2main.c new file mode 100644 index 0000000..78966e5 --- /dev/null +++ b/boot/stage2main.c @@ -0,0 +1,878 @@ +#include "../include/types.h" +#include "../include/x86.h" +#include "../include/memlayout.h" +#include "../include/font.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 "logo.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). +#define VBE_INFO_ADDR 0x30000 + +// 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. +#define BOOT_VERBOSE_FLAG_ADDR (VBE_INFO_ADDR + 0x100) + +// Set by boot/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 VBE_INFO_ADDR. +#define BOOT_MEDIA_FLAG_ADDR (VBE_INFO_ADDR + 0x101) +#define BOOT_MEDIA_CD 1 + +// 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_COUNT_ADDR (VBE_INFO_ADDR + 0x200) +#define E820_MAP_ADDR (VBE_INFO_ADDR + 0x208) +#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*)E820_COUNT_ADDR; + struct e820entry *e = (struct e820entry*)E820_MAP_ADDR; + 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); +static void draw_centered(int y, const char *s, uint32 color); +static void draw_memory_line(int y, uint32 color); + +// 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*)(VBE_INFO_ADDR + 16); + g_width = *(ushort*)(VBE_INFO_ADDR + 18); + g_height = *(ushort*)(VBE_INFO_ADDR + 20); + uchar bpp = *(uchar*)(VBE_INFO_ADDR + 25); + uint32 fbaddr = *(uint32*)(VBE_INFO_ADDR + 40); + g_fb = (uint8*)fbaddr; + g_have_fb = (bpp == 32 && fbaddr && g_width && g_height); +} + +static void +draw_pixel(int x, int y, uint32 color) +{ + *(uint32*)(g_fb + y * g_stride + x * 4) = color; +} + +// --- 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. +static void +draw_logo(int x0, int y0) +{ + if (!g_have_fb) + return; + for (int dy = 0; dy < LOGO_H; dy++) { + int y = y0 + dy; + if (y < 0 || y >= g_height) + continue; + uint32 *row = (uint32*)(g_fb + y * g_stride); + for (int dx = 0; dx < LOGO_W; dx++) { + int x = x0 + dx; + if (x < 0 || x >= g_width) + continue; + unsigned a = LOGO_INK(dy * LOGO_W + dx); + // Skip empty pixels rather than painting them in the background colour. + // The mask is square but the mark is a circle, so most of it is empty; + // filling that would stamp a visible box onto the splash. + if (a == 0) + continue; + if (a >= 255) { + row[x] = SCH_FG; + continue; + } + // Blend towards SCH_BG per channel. Integer only -- there is no FPU set + // up at this point in boot, and no libgcc to fake one with. + uint32 r = (((SCH_FG >> 16) & 0xFF) * a + ((SCH_BG >> 16) & 0xFF) * (255 - a)) / 255; + uint32 g = (((SCH_FG >> 8) & 0xFF) * a + ((SCH_BG >> 8) & 0xFF) * (255 - a)) / 255; + uint32 b = (((SCH_FG ) & 0xFF) * a + ((SCH_BG ) & 0xFF) * (255 - a)) / 255; + row[x] = (r << 16) | (g << 8) | b; + } + } +} + +static void +fill_rect(int x0, int y0, int w, int h, uint32 color) +{ + // Compute each row's base pointer once instead of re-deriving it (via a + // multiply + add) for every single pixel through draw_pixel() — this is + // the full-screen splash fill, 480,000 pixels at 800x600, done at least + // twice per boot. + for (int y = y0; y < y0 + h; y++) { + uint32 *row = (uint32*)(g_fb + y * g_stride) + x0; + for (int x = 0; x < w; x++) + row[x] = color; + } +} + +static void +draw_char(int x, int y, uchar c, uint32 color, uint32 bg) +{ + if (c > 127) return; + for (int row = 0; row < 16; row++) { + uchar bits = font_data[c][row]; + for (int col = 0; col < 8; col++) + draw_pixel(x + col, y + row, (bits & (0x80 >> col)) ? color : bg); + } +} + +static void +draw_string(int x, int y, const char *s, uint32 color, uint32 bg) +{ + while (*s) { + draw_char(x, y, *s, color, bg); + x += 8; + s++; + } +} + +// Pads with trailing spaces (drawn in bg) so re-drawing a shorter string +// over a longer one doesn't leave stale glyphs behind. +static void +draw_string_padded(int x, int y, const char *s, uint32 color, uint32 bg, int cols) +{ + int n = 0; + while (s[n]) { draw_char(x + n * 8, y, s[n], color, bg); n++; } + while (n < cols) { draw_char(x + n * 8, y, ' ', color, bg); n++; } +} + +// --- 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; + +static void +progress_label(const char *label) +{ + draw_string_padded(g_bar_x, g_bar_y - 22, label, SCH_ACCENT, SCH_BG, BAR_WIDTH / 8); +} + +// Border never changes once drawn — separated out so it's only ever drawn +// once per bar instead of being redrawn on every single tick. +static void +progress_border(void) +{ + for (int x = g_bar_x; x < g_bar_x + BAR_WIDTH; x++) { + draw_pixel(x, g_bar_y, SCH_FG); + draw_pixel(x, g_bar_y + BAR_HEIGHT, SCH_FG); + } + for (int y = g_bar_y; y <= g_bar_y + BAR_HEIGHT; y++) { + draw_pixel(g_bar_x, y, SCH_FG); + draw_pixel(g_bar_x + BAR_WIDTH, y, SCH_FG); + } +} + +static void +progress_fill(int pct) +{ + if (pct > 100) pct = 100; + int fillw = (BAR_WIDTH - 4) * pct / 100; + for (int y = g_bar_y + 2; y < g_bar_y + BAR_HEIGHT - 1; y++) + for (int x = g_bar_x + 2; x < g_bar_x + BAR_WIDTH - 2; x++) + draw_pixel(x, y, (x - g_bar_x - 2 < fillw) ? SCH_ACCENT : SCH_BG); +} + +static void +progress_start(const char *label, uint32 total) +{ + if (!g_have_fb) return; + g_bar_x = (g_width - BAR_WIDTH) / 2; + g_bar_y = SPLASH_BAR_Y; + g_load_total = total; + g_load_done = 0; + g_last_pct = -1; + g_progress_active = 1; + progress_label(label); + progress_border(); + progress_fill(0); + g_last_pct = 0; +} + +static void +progress_tick(uint32 n) +{ + if (!g_have_fb || !g_progress_active) return; + g_load_done += n; + // Deliberately all 32-bit. The uint64 cast this used to carry was there to + // stop `done * 100` overflowing, but a 64-bit divide in 32-bit code calls + // libgcc's __udivdi3, which a freestanding bootloader does not link -- it + // only ever built because uint64 was accidentally 32 bits wide here. + // + // Scaling both sides to KiB first keeps the multiply inside 32 bits for any + // image up to 4 GiB, which is comfortably more than a kernel will ever be. + uint32 done_k = g_load_done >> 10; + uint32 total_k = g_load_total >> 10; + uint32 pct = total_k ? (done_k * 100) / total_k : 0; + if (pct > 100) pct = 100; + // A full-bar redraw is a few thousand pixel writes; without this, a + // ~330KB kernel image redraws it ~650 times (once per 512-byte sector) + // instead of at most 100 (once per percentage point) — the single + // biggest chunk of the bootloader's own wall-clock time. + if ((int)pct == g_last_pct) return; + g_last_pct = (int)pct; + progress_fill((int)pct); +} + +// --- Boot menu ------------------------------------------------------- + +static void +reboot_to_firmware(void) +{ + if (g_have_fb) { + fill_rect(0, 0, g_width, g_height, SCH_BG); + draw_string((g_width - 26 * 8) / 2, g_height / 2 - 8, + "Rebooting to firmware...", SCH_ACCENT, SCH_BG); + } + outb(0x64, 0xFE); // pulse the keyboard controller's reset line + for (;;) + ; +} + +static void +show_drive_line(int row, const char *label, int present) +{ + draw_string(g_width / 2 - 160, 340 + row * 20, label, SCH_FG, SCH_BG); + draw_string(g_width / 2 + 20, 340 + row * 20, + present ? "present" : "not present", + present ? SCH_ACCENT : SCH_DIM, SCH_BG); +} + +// 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 Schunix", + "Boot Schunix (verbose)", + "Reboot to firmware setup", + }; + int nitems = 3; + int selected = 0; + + int hda = ata_present(0x1F0, 0); + int hdb = ata_present(0x1F0, 1); + + for (;;) { + fill_rect(0, 0, g_width, g_height, SCH_BG); + draw_centered(60, "Schunix Boot Manager", SCH_ACCENT); + draw_memory_line(84, SCH_DIM); + + for (int i = 0; i < nitems; i++) { + uint32 fg = (i == selected) ? SCH_ACCENT : SCH_FG; + draw_string(g_width / 2 - 140, 140 + i * 24, (i == selected) ? "> " : " ", fg, SCH_BG); + draw_string(g_width / 2 - 124, 140 + i * 24, items[i], fg, SCH_BG); + } + + draw_string(g_width / 2 - 160, 300, "Detected drives:", SCH_FG, SCH_BG); + show_drive_line(0, "Primary Master (hda)", hda); + show_drive_line(1, "Primary Slave (hdb)", hdb); + + draw_string(g_width / 2 - 168, g_height - 40, + "Up/Down to choose, Enter to select", SCH_DIM, SCH_BG); + + 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). +static void +draw_centered(int y, const char *s, uint32 color) +{ + int n = 0; + while (s[n]) n++; + draw_string((g_width - n * 8) / 2, y, s, color, SCH_BG); +} + +// "Memory: NNNN MB" — assembled by hand, no printf in the bootloader. +static void +draw_memory_line(int y, uint32 color) +{ + char line[32]; + char num[12]; + int i = 0; + const char *pfx = "Memory: "; + while (*pfx) line[i++] = *pfx++; + utoa10(e820_usable_mb(), num); + for (int j = 0; num[j]; j++) line[i++] = num[j]; + const char *sfx = " MB"; + while (*sfx) line[i++] = *sfx++; + line[i] = 0; + draw_centered(y, line, color); +} + +static void +splash(void) +{ + if (!g_have_fb) return; + + fill_rect(0, 0, g_width, g_height, SCH_BG); + + // The product, not the loader. + // + // This used to read "Schunix Boot Manager / version 1.0 / Memory: 1023 MB / + // Press F8 for boot menu...", centred, in gold on purple. Every part of that + // is something a system shows its developer rather than its user: the name + // of the component doing the work, its version, and a memory figure nobody + // asked for. No shipping OS names its bootloader on the splash. + // + // What is left is the name, a rule in the accent colour, and the one hint + // that is actually actionable -- moved to the bottom of the screen, where a + // hint belongs, rather than sitting under the title as though it were part + // of the identity. + // The mark, drawn here in the loader rather than by the kernel for a timing + // reason: the kernel does not reach vga_init() until the last few hundred + // milliseconds of a ~2.8s boot, so a kernel-drawn logo appeared for about one + // frame before console_takeover() wiped it. Here it is up for the whole boot. + draw_logo((g_width - LOGO_W) / 2, SPLASH_MARK_Y); + + draw_centered(SPLASH_TEXT_Y, "S C H U N I X", SCH_FG); + + // A 120x2 rule, centred, in the accent -- slightly wider than the 104px + // wordmark so it reads as an underline rather than a strikethrough. Two + // pixels rather than one: a single-pixel line looks like a rendering + // artefact at this size. + fill_rect((g_width - 120) / 2, SPLASH_RULE_Y, 120, SPLASH_RULE_H, SCH_ACCENT); + + draw_centered(g_height - 40, "Press F8 for boot options", SCH_DIM); +} + +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*)BOOT_MEDIA_FLAG_ADDR == BOOT_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*)BOOT_VERBOSE_FLAG_ADDR = verbose; + + if (g_have_fb) { + fill_rect(0, 0, g_width, g_height, SCH_BG); + draw_string((g_width - 7 * 8) / 2, g_height / 2 - 50, "SCHUNIX", SCH_ACCENT, SCH_BG); + } + + // 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*)BOOT_MEDIA_FLAG_ADDR == BOOT_MEDIA_CD && !g_from_cd) { + draw_centered(g_height / 2, "No readable boot disc found.", SCH_ACCENT); + draw_centered(g_height / 2 + 20, "The drive did not answer ATAPI.", SCH_DIM); + for (;;) + asm volatile("hlt"); + } + + if (g_have_fb) + progress_start("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) + progress_start("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) + progress_label("Starting Schunix..."); + + // Jump to kernel entry. NOT a normal C call: kernel/arch/x86_64/ + // entry.S's mboot_entry reads its "argument" (the framebuffer + // physical address) out of EAX directly, not off the stack — a + // multiboot-style raw-register handoff, not cdecl. A plain + // `((void(*)(uint32))entry)(fbaddr)` pushes fbaddr on the stack + // per cdecl and leaves EAX as whatever happened to be lying around; + // it only ever "worked" because simpler bootmain.c code coincidentally + // left the right value in EAX as a side effect of its own codegen. + // Adding the boot-menu/progress-bar code changed that codegen and + // broke it silently (kernel received garbage instead of the real + // LFB address). Pin it down explicitly instead of hoping. + { + uint32 fbaddr = *(uint32*)(VBE_INFO_ADDR + 40); + uint32 entry = hdr->entry_addr; + asm volatile("jmp *%0" : : "r"(entry), "a"(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); + 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); +} diff --git a/cmd/fbinfo/main.c b/cmd/fbinfo/main.c new file mode 100644 index 0000000..b106441 --- /dev/null +++ b/cmd/fbinfo/main.c @@ -0,0 +1,219 @@ +// fbinfo -- ask /dev/fb0 what it is, and optionally draw on it. +// +// This exists because the framebuffer is the one device whose correctness you +// cannot check by reading kernel source: the numbers have to come back from +// the hardware the bootloader actually programmed. Three places in the kernel +// used to assume the framebuffer lived at 0xfd000000 and was 800x600x32; they +// agreed with QEMU's stdvga by luck, and nothing would have told us when the +// luck ran out. Now `fbinfo` would. +// +// fbinfo print the geometry +// fbinfo fill draw a gradient over the whole screen through mmap +// fbinfo bars draw colour bars through write(), no mmap involved +// +// The two drawing modes take different paths on purpose. If `fill` works and +// `bars` does not, the mapping is right and the device's read/write is wrong; +// if it is the other way round, the reverse. Having both makes a failure say +// which half broke. + +#include "types.h" +#include "user.h" +#include "fcntl.h" +#include "fb.h" + +#define PROT_READ 0x1 +#define PROT_WRITE 0x2 +#define MAP_SHARED 0x01 + +static void +puts(const char *s) +{ + int n = 0; + while (s[n]) n++; + write(1, (void*)s, n); +} + +// Unsigned decimal. No printf in lib/, and pulling one in for six numbers +// would be a worse trade than fourteen lines. +static void +putu(unsigned long v) +{ + char buf[24]; + int i = 0; + + if (v == 0) { + write(1, "0", 1); + return; + } + while (v > 0 && i < (int)sizeof(buf)) { + buf[i++] = (char)('0' + (v % 10)); + v /= 10; + } + while (i > 0) { + i--; + write(1, &buf[i], 1); + } +} + +static void +puthex(unsigned long v) +{ + static const char d[] = "0123456789abcdef"; + char buf[16]; + int i = 0; + + write(1, "0x", 2); + if (v == 0) { + write(1, "0", 1); + return; + } + while (v > 0 && i < (int)sizeof(buf)) { + buf[i++] = d[v & 0xf]; + v >>= 4; + } + while (i > 0) { + i--; + write(1, &buf[i], 1); + } +} + +// Name, then spaces out to a fixed column. Padding inside each string +// literal is what produced "bits_per_pixel32": the longest name happened to +// be exactly as wide as the column, so it got no separator at all and the +// value ran straight into it. +static void +label(const char *name) +{ + int n = 0; + while (name[n]) n++; + write(1, (void*)name, n); + while (n < 16) { write(1, " ", 1); n++; } +} + +static void +field(const char *name, unsigned long v) +{ + label(name); + putu(v); + puts("\n"); +} + +static void +chanline(const char *name, struct fb_bitfield *b) +{ + label(name); + puts("offset "); + putu(b->offset); + puts(", length "); + putu(b->length); + if (b->length == 0) + puts(" (absent)"); + puts("\n"); +} + +int +main(int argc, char *argv[]) +{ + struct fb_var_screeninfo var; + struct fb_fix_screeninfo fix; + int fd; + + fd = open("/dev/fb0", O_RDWR); + if (fd < 0) { + puts("fbinfo: cannot open /dev/fb0\n"); + return 1; + } + + if (ioctl(fd, FBIOGET_VSCREENINFO, &var) < 0) { + puts("fbinfo: FBIOGET_VSCREENINFO failed\n"); + return 1; + } + if (ioctl(fd, FBIOGET_FSCREENINFO, &fix) < 0) { + puts("fbinfo: FBIOGET_FSCREENINFO failed\n"); + return 1; + } + + if (argc < 2) { + label("id"); + puts(fix.id); + puts("\n"); + field("xres", var.xres); + field("yres", var.yres); + field("bits_per_pixel", var.bits_per_pixel); + label("smem_start"); + puthex(fix.smem_start); + puts("\n"); + field("smem_len", fix.smem_len); + field("line_length", fix.line_length); + + // The stride is the number worth staring at. A card is free to pad + // each row, and a program that computes row starts as + // y * xres * bytes_per_pixel instead of y * line_length draws a + // picture that shears progressively down the screen. + if (fix.line_length != var.xres * (var.bits_per_pixel / 8)) { + label("note"); + puts("stride is padded; rows are not xres*bpp/8 apart\n"); + } + + chanline("red", &var.red); + chanline("green", &var.green); + chanline("blue", &var.blue); + chanline("transp", &var.transp); + return 0; + } + + if (argv[1][0] == 'f') { // fill + unsigned char *fb = mmap(0, fix.smem_len, + PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + if (fb == 0 || (long)fb < 0) { + puts("fbinfo: mmap failed\n"); + return 1; + } + unsigned bpp = var.bits_per_pixel / 8; + for (unsigned y = 0; y < var.yres; y++) { + unsigned char *row = fb + (unsigned long)y * fix.line_length; + for (unsigned x = 0; x < var.xres; x++) { + unsigned r = (x * 255) / (var.xres ? var.xres : 1); + unsigned g = (y * 255) / (var.yres ? var.yres : 1); + unsigned long px = ((unsigned long)r << var.red.offset) | + ((unsigned long)g << var.green.offset) | + ((unsigned long)0x80 << var.blue.offset); + unsigned char *p = row + (unsigned long)x * bpp; + for (unsigned b = 0; b < bpp; b++) + p[b] = (unsigned char)(px >> (b * 8)); + } + } + puts("fbinfo: gradient drawn through mmap\n"); + return 0; + } + + if (argv[1][0] == 'b') { // bars + // One scanline at a time through write(). No mapping: this is the + // path that used to be devsw's nullwrite, i.e. a silent no-op. + static unsigned char row[8192]; + unsigned bpp = var.bits_per_pixel / 8; + unsigned len = fix.line_length; + if (len > sizeof(row)) + len = sizeof(row); + + for (unsigned y = 0; y < var.yres; y++) { + unsigned band = (y * 8) / (var.yres ? var.yres : 1); + unsigned long px = + ((unsigned long)((band & 1) ? 255 : 0) << var.red.offset) | + ((unsigned long)((band & 2) ? 255 : 0) << var.green.offset) | + ((unsigned long)((band & 4) ? 255 : 0) << var.blue.offset); + for (unsigned x = 0; x * bpp + bpp <= len; x++) + for (unsigned b = 0; b < bpp; b++) + row[x * bpp + b] = (unsigned char)(px >> (b * 8)); + if (write(fd, row, len) != (int)len) { + puts("fbinfo: short write to /dev/fb0\n"); + return 1; + } + } + puts("fbinfo: colour bars drawn through write()\n"); + return 0; + } + + puts("usage: fbinfo [fill|bars]\n"); + return 1; +} diff --git a/cmd/init/main.c b/cmd/init/main.c new file mode 100644 index 0000000..6d044bb --- /dev/null +++ b/cmd/init/main.c @@ -0,0 +1,124 @@ +// SPDX-License-Identifier: Apache-2.0 +// Schunix — minimal init (PID 1) + +#include "types.h" +#include "user.h" +#include "fcntl.h" + +static void +xputs(int fd, const char *s) +{ + int n = 0; + while (s[n]) n++; + write(fd, (void *)s, n); +} + +int +main(void) +{ + if (getpid() != 1) + exit(); + + // Open console as fd 0; dup to fd 1 (stdout) and fd 2 (stderr). + if (open("console", O_RDWR) < 0) { + mknod("console", 1, 1); + open("console", O_RDWR); + } + dup(0); + dup(0); + + // --- /dev ------------------------------------------------------------ + // Minor numbers under major 1 follow Linux's mem-major layout where it + // costs nothing (null=2, zero=5, full=7, random=8, urandom=9). + // /dev/hole (was minor 4) is gone: it backed an abandoned experiment and + // nothing has referenced it since. + mkdir("/dev"); + mknod("/dev/console", 1, 1); + mknod("/dev/null", 1, 2); + mknod("/dev/keyboard", 1, 3); + mknod("/dev/zero", 1, 5); + mknod("/dev/full", 1, 7); + mknod("/dev/random", 1, 8); + mknod("/dev/urandom", 1, 9); + mknod("/dev/fb0", 2, 0); + mknod("/dev/mouse", 3, 1); + mknod("/dev/tty", 1, 1); // alias for console — must pass TCGETS ioctl + mknod("/dev/ttyS0", 4, 64); + mknod("/dev/stdin", 1, 1); + mknod("/dev/stdout", 1, 1); + mknod("/dev/stderr", 1, 1); + + // Raw input events, Linux evdev layout (kernel/dev/input.c). /dev/keyboard + // and /dev/mouse above are the old character-and-position interfaces; these + // are the ones with key releases and button transitions, which is what a + // game or a compositor needs and what neither of the others can express. + mkdir("/dev/input"); + mknod("/dev/input/event0", 7, 0); // keyboard + mknod("/dev/input/event1", 7, 1); // mouse + + // --- /proc ----------------------------------------------------------- + // Synthesised status files, major 6 (kernel/dev/procfs.c). These are the + // global ones; per-process /proc// directories need synthetic + // directory support in the filesystem, which does not exist yet. + mkdir("/proc"); + // /proc is a real filesystem now (kernel/fs/procfs.c), not a handful of + // mknod'd nodes: it has to be, because one directory per process cannot be + // created ahead of time. This is what makes `ps` and `top` work. + // + // The mknod'd global files below still happen, and are simply covered by + // the mount. They stay for now so that a failure here degrades to the old + // behaviour instead of losing /proc entirely. + mount("proc", "/proc", "proc", 0, 0); + mknod("/proc/meminfo", 6, 1); + mknod("/proc/uptime", 6, 2); + mknod("/proc/cpuinfo", 6, 3); + mknod("/proc/version", 6, 4); + mknod("/proc/stat", 6, 5); + mknod("/proc/loadavg", 6, 6); + mknod("/proc/mounts", 6, 7); + mknod("/proc/filesystems", 6, 8); + // SchFS volume summary: label, generation, free space, and the root + // directory. Reachable whether or not the volume is mounted into the + // namespace, which is what makes it useful when a mount has gone wrong. + mknod("/proc/schfs", 6, 9); + // Write 'c' here to crash on purpose. The crash screen is otherwise the + // one part of the system that can only be seen by having a bug. + mknod("/proc/sysrq", 6, 10); + mknod("/proc/pci", 6, 11); + mknod("/proc/interrupts", 6, 12); + + mkdir("/sys"); + + xputs(1, "\nSchunix\n"); + xputs(1, "init: ready (pid 1)\n"); + + for (;;) { + int pid = fork(); + if (pid < 0) { + xputs(2, "init: fork failed\n"); + sleep(100); + continue; + } + + if (pid == 0) { + // exec.c auto-enables Linux ABI for musl binaries (entry >= 0x100000). + // Do NOT call enable_linux_abi() here — it corrupts Schunix syscall + // numbers for the exec/exit calls that follow in this child. + // Standardized naming: the shell is always /bin/sh, regardless of + // which implementation backs it (dash, mksh, etc.) — no ksh/mksh/ + // other aliases are looked up here. + static char *argv[] = { "sh", 0 }; + exec("/bin/sh", argv); + xputs(2, "init: no shell found; place sh in /bin\n"); + exit(); + } + + // Reap zombies; the loop restarts the shell when it exits. + int wpid; + while ((wpid = wait()) >= 0 && wpid != pid) + ; + // Brief pause before restarting so we don't spin on a missing shell. + sleep(100); + } + return 0; +} diff --git a/cmd/inputtest/main.c b/cmd/inputtest/main.c new file mode 100644 index 0000000..84a7173 --- /dev/null +++ b/cmd/inputtest/main.c @@ -0,0 +1,124 @@ +// inputtest -- read raw events from /dev/input/eventN. +// +// The point of the raw devices is everything the console cannot say: which +// key was released, whether one is still held, and where the mouse went. None +// of that shows up in a character stream, so none of it can be tested by +// typing into a shell. +// +// inputtest read the keyboard until Esc is released +// inputtest mouse read the mouse until interrupted +// inputtest -n N stop after N events (used by the boot check) +// +// Prints one line per event: type, code and value, decoded where there is a +// name for it. + +#include "types.h" +#include "user.h" +#include "fcntl.h" +#include "input.h" + +static void +puts_(const char *s) +{ + int n = 0; + while (s[n]) n++; + write(1, (void*)s, n); +} + +static void +putn_(int v) +{ + char buf[12]; + int i = 0, neg = 0; + if (v < 0) { neg = 1; v = -v; } + if (v == 0) buf[i++] = '0'; + while (v) { buf[i++] = '0' + (v % 10); v /= 10; } + if (neg) buf[i++] = '-'; + while (i--) write(1, &buf[i], 1); +} + +static const char * +typename_(int t) +{ + switch (t) { + case EV_SYN: return "SYN"; + case EV_KEY: return "KEY"; + case EV_REL: return "REL"; + default: return "?"; + } +} + +static int +streq(const char *a, const char *b) +{ + while (*a && *a == *b) { a++; b++; } + return *a == *b; +} + +int +main(int argc, char **argv) +{ + const char *path = "/dev/input/event0"; + int limit = 0, count = 0, i; + int fd; + struct input_event ev; + + for (i = 1; i < argc; i++) { + if (streq(argv[i], "mouse")) + path = "/dev/input/event1"; + else if (streq(argv[i], "-n") && i + 1 < argc) { + const char *p = argv[++i]; + limit = 0; + while (*p >= '0' && *p <= '9') limit = limit * 10 + (*p++ - '0'); + } + } + + fd = open((char*)path, O_RDONLY); + if (fd < 0) { + puts_("inputtest: cannot open "); + puts_(path); + puts_("\n"); + exit(); + } + puts_("inputtest: reading "); + puts_(path); + puts_("\n"); + + for (;;) { + int n = read(fd, &ev, sizeof(ev)); + + // A short read would mean the device handed back part of a struct, + // which would desynchronise every event after it. Saying so is more + // use than silently carrying on with garbage. + if (n == 0) { puts_("inputtest: end of stream\n"); break; } + if (n < 0) { puts_("inputtest: read error\n"); break; } + if (n != sizeof(ev)) { + puts_("inputtest: SHORT READ -- events are not whole\n"); + break; + } + + puts_(" "); + puts_(typename_(ev.type)); + puts_(" code "); + putn_(ev.code); + puts_(" value "); + putn_(ev.value); + if (ev.type == EV_KEY) + puts_(ev.value ? " (press)" : " (release)"); + puts_("\n"); + + // Esc released ends the keyboard run, so a person can stop it without + // a signal. The boot check uses -n instead. + if (ev.type == EV_KEY && ev.code == KEY_ESC && ev.value == 0) + break; + + if (limit && ++count >= limit) { + puts_("inputtest: event limit reached\n"); + break; + } + } + + close(fd); + puts_("inputtest: done\n"); + exit(); +} diff --git a/cmd/shmtest/main.c b/cmd/shmtest/main.c new file mode 100644 index 0000000..ab0ad99 --- /dev/null +++ b/cmd/shmtest/main.c @@ -0,0 +1,112 @@ +// shmtest -- prove that shared memory is actually shared, and that a +// file-backed mmap returns the file. +// +// Both of these are things you cannot check by reading a return value. shmget +// and shmat returned -ENOSYS until now, and mmap of a file "succeeded" while +// handing back zeroed anonymous pages -- a lie that looks exactly like +// success. The only convincing test is for one process to write and a +// different one to read. +// +// shmtest run every check +// +// Prints one line per check, and a final verdict a script can grep for. + +#include "types.h" +#include "user.h" +#include "fcntl.h" + +#define PROT_READ 0x1 +#define PROT_WRITE 0x2 +#define MAP_PRIVATE 0x02 +#define MAP_SHARED 0x01 +#define IPC_PRIVATE 0 +#define IPC_CREAT 01000 + +static int failures; + +static void +puts_(const char *s) +{ + int n = 0; + while (s[n]) n++; + write(1, (void*)s, n); +} + +static void +check(int ok, const char *what) +{ + puts_(ok ? " [ok] " : " FAIL: "); + puts_(what); + puts_("\n"); + if (!ok) failures++; +} + +int +main(void) +{ + // --- shared memory ---------------------------------------------------- + int id = shmget(IPC_PRIVATE, 8192, IPC_CREAT | 0600); + check(id >= 0, "shmget created a segment"); + if (id < 0) { puts_("shmtest: FAILED\n"); return 1; } + + char *p = shmat(id, 0, 0); + check((long)p > 0, "shmat mapped it"); + if ((long)p <= 0) { puts_("shmtest: FAILED\n"); return 1; } + + // A fresh segment must be zeroed, or it leaks whatever the previous + // tenant left in those pages to whoever gets them next. + int zeroed = 1; + for (int i = 0; i < 8192; i++) + if (p[i] != 0) { zeroed = 0; break; } + check(zeroed, "a new segment reads as zeros"); + + p[0] = 'P'; + p[1] = 0; + + int pid = fork(); + if (pid == 0) { + // The child inherited the attachment through fork. If the pages had + // been copied instead of shared, the parent would never see this. + p[0] = 'C'; + p[100] = 'X'; + exit(); + } + wait(); + + check(p[0] == 'C', "a write by the child is visible in the parent"); + check(p[100] == 'X', "and so is one further into the segment"); + + // --- file-backed mmap -------------------------------------------------- + int fd = open("/tmp/shmtest.dat", O_CREATE | O_RDWR); + check(fd >= 0, "created a file to map"); + if (fd >= 0) { + write(fd, "SCHUNIX-MMAP-OK", 15); + close(fd); + } + + fd = open("/tmp/shmtest.dat", O_RDONLY); + if (fd >= 0) { + char *m = mmap(0, 4096, PROT_READ, MAP_PRIVATE, fd, 0); + int ok = ((long)m > 0) && + m[0]=='S' && m[1]=='C' && m[2]=='H' && m[3]=='U' && + m[8]=='M' && m[13]=='O' && m[14]=='K'; + check(ok, "mmap of a file returns the file, not zeros"); + close(fd); + } else { + check(0, "mmap of a file returns the file, not zeros"); + } + + // MAP_SHARED + PROT_WRITE on a file cannot be honoured without a page + // cache, and is refused rather than quietly downgraded to a private copy. + fd = open("/tmp/shmtest.dat", O_RDWR); + if (fd >= 0) { + void *m = mmap(0, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + check((long)m < 0, "writable MAP_SHARED on a file is refused, not faked"); + close(fd); + } + + check(shmdt(p) == 0, "shmdt released the attachment"); + + puts_(failures ? "shmtest: FAILED\n" : "shmtest: all checks passed\n"); + return failures ? 1 : 0; +} diff --git a/cmd/socktest/main.c b/cmd/socktest/main.c new file mode 100644 index 0000000..9c73530 --- /dev/null +++ b/cmd/socktest/main.c @@ -0,0 +1,305 @@ +// socktest -- prove that Unix domain sockets carry data between processes. +// +// Every part of a socket is invisible from a return value. bind() returning 0 +// proves nothing; a socket layer that accepts every call and drops every byte +// would pass a test built on return codes alone. So each check here has one +// process write and a *different* process read, and compares the bytes. +// +// socktest run every check +// +// Prints one line per check and a final verdict a script can grep for. + +#include "types.h" +#include "user.h" +#include "socket.h" + +static int failures; + +static void +puts_(const char *s) +{ + int n = 0; + while (s[n]) n++; + write(1, (void*)s, n); +} + +static void +putn_(int v) +{ + char buf[12]; + int i = 0, neg = 0; + if (v < 0) { neg = 1; v = -v; } + if (v == 0) buf[i++] = '0'; + while (v) { buf[i++] = '0' + (v % 10); v /= 10; } + if (neg) buf[i++] = '-'; + while (i--) write(1, &buf[i], 1); +} + +static void +check(int ok, const char *what) +{ + puts_(ok ? " [ok] " : " FAIL: "); + puts_(what); + puts_("\n"); + if (!ok) failures++; +} + +static int +streq(const char *a, const char *b) +{ + while (*a && *a == *b) { a++; b++; } + return *a == *b; +} + +// Fill a sockaddr_un and return the length to pass to bind/connect: the +// family plus the path plus its NUL, which is what musl computes and what the +// kernel side is written to expect. +static int +mkaddr(struct sockaddr_un *sa, const char *path) +{ + int i = 0; + for (i = 0; i < UNIX_PATH_MAX - 1 && path[i]; i++) + sa->sun_path[i] = path[i]; + sa->sun_path[i] = '\0'; + sa->sun_family = AF_UNIX; + return (int)sizeof(sa->sun_family) + i + 1; +} + +// --------------------------------------------------------------------------- +// 1. A round trip over a named socket, between two processes. +// --------------------------------------------------------------------------- +static void +test_roundtrip(void) +{ + struct sockaddr_un sa; + int len = mkaddr(&sa, "/tmp/socktest"); + int srv, pid, cli, n; + char buf[64]; + + puts_("named socket, two processes:\n"); + + srv = socket(AF_UNIX, SOCK_STREAM, 0); + check(srv >= 0, "socket() returns a descriptor"); + if (srv < 0) return; + + check(bind(srv, (struct sockaddr *)&sa, len) == 0, "bind() to /tmp/socktest"); + check(listen(srv, 4) == 0, "listen()"); + + // A second bind to the same name must be refused, or two servers can + // silently steal each other's clients. + { + int dup_ = socket(AF_UNIX, SOCK_STREAM, 0); + check(bind(dup_, (struct sockaddr *)&sa, len) < 0, + "a second bind to the same name is refused"); + close(dup_); + } + + pid = fork(); + if (pid == 0) { + // Child: the client. Connects, sends a question, reads the answer. + int c = socket(AF_UNIX, SOCK_STREAM, 0); + char reply[64]; + int r; + + close(srv); + if (connect(c, (struct sockaddr *)&sa, len) < 0) { + puts_(" FAIL: child connect()\n"); + exit(); + } + write(c, "ping", 4); + + r = read(c, reply, sizeof(reply) - 1); + if (r < 0) r = 0; + reply[r] = '\0'; + if (!streq(reply, "pong")) + puts_(" FAIL: child did not receive pong\n"); + close(c); + exit(); + } + + cli = accept(srv, 0, 0); + check(cli >= 0, "accept() returns a connection"); + if (cli < 0) { close(srv); wait(); return; } + + n = read(cli, buf, sizeof(buf) - 1); + if (n < 0) n = 0; + buf[n] = '\0'; + check(streq(buf, "ping"), "server read the client's bytes"); + + check(write(cli, "pong", 4) == 4, "server replied on the same socket"); + + close(cli); + close(srv); + wait(); +} + +// --------------------------------------------------------------------------- +// 2. Errors that must actually be errors. +// --------------------------------------------------------------------------- +static void +test_errors(void) +{ + struct sockaddr_un sa; + int len = mkaddr(&sa, "/tmp/nobody-listening"); + int s; + + puts_("error paths:\n"); + + s = socket(AF_UNIX, SOCK_STREAM, 0); + check(connect(s, (struct sockaddr *)&sa, len) < 0, + "connect() to a name nobody bound fails"); + close(s); + + check(socket(AF_INET, SOCK_STREAM, 0) < 0, "AF_INET is refused, not faked"); + check(socket(AF_UNIX, SOCK_DGRAM, 0) < 0, "SOCK_DGRAM is refused, not faked"); + + // read/write on a plain file descriptor must not be mistaken for a socket, + // and socket calls on one must be refused. + check(listen(1, 4) < 0, "listen() on a non-socket fd fails"); +} + +// --------------------------------------------------------------------------- +// 3. End-of-file. A reader whose peer closes must be told, not left hanging. +// --------------------------------------------------------------------------- +static void +test_eof(void) +{ + struct sockaddr_un sa; + int len = mkaddr(&sa, "/tmp/socktest-eof"); + int srv, cli, pid, n; + char buf[16]; + + puts_("end of file:\n"); + + srv = socket(AF_UNIX, SOCK_STREAM, 0); + bind(srv, (struct sockaddr *)&sa, len); + listen(srv, 4); + + pid = fork(); + if (pid == 0) { + int c = socket(AF_UNIX, SOCK_STREAM, 0); + close(srv); + connect(c, (struct sockaddr *)&sa, len); + write(c, "x", 1); + close(c); // the peer goes away with the byte still unread + exit(); + } + + cli = accept(srv, 0, 0); + n = read(cli, buf, sizeof(buf)); + check(n == 1, "the byte sent before close() is still delivered"); + + n = read(cli, buf, sizeof(buf)); + check(n == 0, "the next read returns 0 rather than blocking"); + + close(cli); + close(srv); + wait(); +} + +// --------------------------------------------------------------------------- +// 4. socketpair: bidirectional, no name, no listener. +// --------------------------------------------------------------------------- +static void +test_socketpair(void) +{ + int sv[2], n; + char buf[16]; + + puts_("socketpair:\n"); + + if (socketpair(AF_UNIX, SOCK_STREAM, 0, sv) < 0) { + check(0, "socketpair() succeeds"); + return; + } + check(1, "socketpair() succeeds"); + + write(sv[0], "a", 1); + n = read(sv[1], buf, sizeof(buf)); + check(n == 1 && buf[0] == 'a', "0 -> 1 carries a byte"); + + write(sv[1], "b", 1); + n = read(sv[0], buf, sizeof(buf)); + check(n == 1 && buf[0] == 'b', "1 -> 0 carries a byte, on the same pair"); + + close(sv[0]); + close(sv[1]); +} + +// --------------------------------------------------------------------------- +// 5. A message longer than the socket buffer, so the writer has to block and +// the reader has to drain in more than one go. +// --------------------------------------------------------------------------- +#define BIG 12000 + +static void +test_big(void) +{ + int sv[2], pid, got = 0, ok = 1, i; + static char out[BIG], in[BIG]; + + puts_("a transfer larger than the buffer:\n"); + + for (i = 0; i < BIG; i++) + out[i] = (char)('A' + (i % 26)); + + if (socketpair(AF_UNIX, SOCK_STREAM, 0, sv) < 0) { + check(0, "socketpair() for the big transfer"); + return; + } + + pid = fork(); + if (pid == 0) { + int sent = 0, r; + close(sv[1]); + while (sent < BIG) { + r = write(sv[0], out + sent, BIG - sent); + if (r <= 0) break; + sent += r; + } + close(sv[0]); + exit(); + } + + close(sv[0]); + while (got < BIG) { + int r = read(sv[1], in + got, BIG - got); + if (r <= 0) break; + got += r; + } + close(sv[1]); + wait(); + + check(got == BIG, "every byte arrived"); + if (got != BIG) { + puts_(" got "); + putn_(got); + puts_(" of "); + putn_(BIG); + puts_("\n"); + } + for (i = 0; i < got; i++) + if (in[i] != out[i]) { ok = 0; break; } + check(ok, "and in the right order"); +} + +int +main(void) +{ + puts_("socktest: unix domain sockets\n"); + + test_roundtrip(); + test_errors(); + test_eof(); + test_socketpair(); + test_big(); + + if (failures == 0) + puts_("socktest: ALL CHECKS PASSED\n"); + else { + puts_("socktest: "); + putn_(failures); + puts_(" CHECK(S) FAILED\n"); + } + exit(); +} diff --git a/docs/architecture/kernel-history.md b/docs/architecture/kernel-history.md new file mode 100644 index 0000000..cb3d4b4 --- /dev/null +++ b/docs/architecture/kernel-history.md @@ -0,0 +1,666 @@ +# Kernel history — what broke, and what the fix actually was + +Not a design document. This is the record of bugs that were expensive to find +and whose causes are not visible from the code that fixed them, plus the few +subsystem write-ups (bootloader, boot speed, naming) that have nowhere better +to live. + +It is worth keeping because every entry here is a thing that looked like a +different problem than it was. + +> **Where the old design plan went.** Sections 1-6 of this file used to hold a +> "Segregated Kernel" design: a Ring 0 Alpha / Ring 0 Beta split with driver +> modules isolated by CR3 switching, from an `IDEA.txt` that no longer exists. +> None of it was ever implemented, and it is superseded by +> [schapi.md](schapi.md) — a macOS-shaped system answers driver isolation with +> userspace drivers and a personality boundary, not a bespoke in-kernel ring +> split. It also referenced a Zig toolchain that is now clang/LLVM. Removed +> rather than left to be mistaken for a plan. + +--- + +## 1. Fixed: boot didn't reliably reach a working shell + +Investigated 2026-07-05, before phase 1 started (predates this design, not +caused by it). Originally manifested as `ksh` (mksh) crashing on every +launch, apparently inside musl's mallocng allocator (`get_meta`, +`src/malloc/mallocng/meta.h`, deliberately executing `hlt` on detecting +"corrupted" heap metadata — a `trap 13` GPF at a fixed rip). It wasn't a +musl bug. Six real kernel bugs were found and fixed across three +investigation passes, and mksh boots and runs cleanly with all of them in +place — the calloc assertion was a symptom of real memory/pointer-handling +bugs downstream in the kernel, not anything musl or mallocng did wrong. + +Fixed: +- `copyuvm()` (`kernel/arch/x86_64/vm.c`, used by `fork()`) never copied + `mmap()`-backed `proc->vma[]` regions into the child — only `[0, sz)`. + Fixed via new `copy_vma_range()`, called from `fork()` (`kernel/proc/proc.c`). +- `kmem.freelist` (`kernel/core/kalloc.c`) could get corrupted by a wild + write elsewhere and would silently hand back a bad pointer, panicking far + from the actual cause. Added a canary check (`check_run()`) at the top of + both `kalloc()`/`kfree()` so corruption panics immediately at the point + it's touched, with a clear message. +- No TLB invalidation existed anywhere except the full `lcr3` reload on + context switch — `mappages()`/`vm_unmap_range()` never called `invlpg`. + Fixed by adding `invlpg()` (`include/x86.h`) to both functions. +- **`MSR_STAR` was misconfigured** (`syscallinit()`, `kernel/arch/x86_64/vm.c`): + `SYSRETQ` computes the returned `cs`/`ss` as `STAR[63:48]+16`/`+8`, which + requires the *compat* 32-bit code selector (`SEG_UCODE32`) in that field — + the kernel was putting the real 64-bit `USER_CS` there instead. Every + `sysretq` silently loaded an invalid `cs` (`0x3b`, pointing at the TSS + descriptor)/`ss` pair; QEMU's TCG doesn't validate segment descriptors on + `SYSRET`, so this went unnoticed until a hardware interrupt forced a + strictly-validated `IRETQ` with that bogus `cs` still loaded, which + correctly `#GP`'d. +- `sys_mmap`'s non-`MAP_FIXED` path (`kernel/sys/sys_mem.c`) assigned a fixed + 8MB virtual-address window per `vma[]` slot regardless of the requested + length. A single allocation over 8MB (e.g. `realloc()` growth past that + size) mapped straight into a neighboring live mapping and hit + `mappages()`'s `panic("remap")`. Replaced with a real bump allocator + (`proc->mmap_next`, `include/proc.h`), so every mapping gets its own + disjoint, exactly-sized range regardless of size. +- **`uva2ka()`** (`kernel/arch/x86_64/vm.c`) returned only the page-aligned + base address of a user pointer's page, silently dropping the in-page byte + offset. Direct callers (`sys_ioctl`'s `TIOCGPGRP`/`TCGETS`, `sys_getcwd`, + signal masks, `times()`, `sysinfo()`, ...) read/wrote near the wrong + address whenever the pointer wasn't page-aligned — i.e. almost always. + Fixed at the source so every caller benefits. +- **`fetchint`/`fetchaddr`/`fetchstr`/`argptr`** (`kernel/sys/syscall.c`) + rejected any address `>= proc->sz`, wrongly assuming every valid user + pointer lives in the heap. Stack addresses and `mmap()`'d buffers (used + constantly by real programs, e.g. path-resolution buffers) live above + `proc->sz` and were being rejected outright. Fixed to rely solely on + `uva2ka()`'s page-table validation. + +Bringing in toybox (see "Coreutils" below) surfaced four more, all fixed: +- Linux syscall 58 (`vfork`) wasn't in the syscall-ABI translation table at + all (`kernel/sys/linux_abi.c`) — every external command spawn from a + program that prefers `vfork()` over `fork()` failed outright. Mapped to + the existing `fork()` (a fully correct, if slightly slower, substitute). +- `sys_stat`/`fstat`/`fstatat` (`kernel/sys/sys_fs.c`) filled in Schunix's + own native `struct stat` layout regardless of the caller — musl/toybox + parse a fixed Linux ABI byte layout instead, so `st_mode` was garbage and + tools like `ls` couldn't tell files from directories, or executables from + non-executables. Added `struct linux_stat` + `stati_linux()`, gated on + Linux-ABI mode (`proc->syscall_map`). +- `sys_open`/`sys_openat` required `omode == O_RDONLY` *exactly* to open a + directory, but `opendir()`-style calls legitimately OR in `O_DIRECTORY`/ + `O_CLOEXEC` alongside it. Fixed to check just the access-mode bits + (`O_ACCMODE`, added to `include/fcntl.h`). +- `sys_getdents64` read one `struct dirent` (16 bytes) at a time — a full + `bmap()`+`bread()` per slot. Directories here are hash-indexed (see + `tree_search()`/`tree_expand()` below), so a directory with a few dozen + real entries can have a size in the hundreds of KB, nearly all empty + slots — this made listing such a directory look hung (it wasn't a + deadlock, just thousands of redundant single-slot disk reads). Rewrote it + to read a full block at a time and resolve each entry's type via + `iget(inum)` (exported from `fs.c`, it was `static`) instead of + re-searching the whole directory by name with `dirlookup()` for every + entry. That rewrite's first version introduced a **new** self-deadlock: + the "." entry's inode number equals the directory's own, and `iget()` + correctly hands back the *same, already-locked* `struct inode*` — calling + `ilock()` on it again sleeps forever on a lock the process already holds. + Fixed by special-casing `xde->inum == ip->inum` (we already know it's a + directory, no need to re-lock at all). + +A synthetic stress test (interleaved malloc/free, realloc growth crossing +`MMAP_THRESHOLD`, calloc, large allocations) runs to completion with zero +crashes, and mksh now boots straight to an interactive prompt, runs external +programs, pipes, and `ls`/`mkdir`/`grep` without issue. + +## 2. Naming standardization + +Schunix standardizes on exactly one name per role, regardless of the +underlying implementation: the shell is always installed and exec'd as +`/bin/sh` (currently mksh, built from `ports/mksh/`), and PID 1 is always +`/bin/init`. `cmd/init/main.c`'s exec loop only ever tries `/bin/sh` — no +ksh/mksh/dash/zsh/other-name fallbacks. dash was tried first +(BSD-licensed, minimal) while the boot crash above was still open, then +replaced with mksh once the underlying kernel bugs were fixed and dash's +lack of interactive features (history, completion, line editing) made it +feel unusably bare for day-to-day use; the dash port's source tree was left in +place rather than deleted, in case it's useful again (e.g. as a `/bin/sh` +POSIX-strict target distinct from an interactive login shell). + +## 3. Coreutils + +`ports/toybox/` (0BSD-licensed, https://landley.net/toybox/) provides `ls`, +`cat`, `cp`, `mv`, `rm`, `mkdir`, `grep`, `sed`, `find`, `chmod`, `date`, +`sort`, and more — see the `APPLETS` list in `ports/toybox/Makefile`. It builds +as a single multicall binary; `tools/mkfs.c` doesn't understand symlinks or +hardlink dedup, so `ports/toybox/Makefile`'s `install` target copies the built +binary once per applet name into `sysroot/bin/` rather than symlinking +(costs disk space per applet — currently ~44MB total — instead of one +shared inode, but needed no mkfs changes). + +## 4. Two-stage bootloader + PSOGM splash + +`boot/` used to be a single BIOS boot sector doing everything (VBE mode +set, A20, GDT, protected-mode switch, multiboot kernel scan/load) — silent, +and packed to within 4 bytes of the hard 510-byte MBR limit, so there was +no room to add so much as a print statement. + +Split into two stages, the standard way real bootloaders solve this: + +- **Stage 1** (`boot/bootasm.S`, still the literal MBR sector, still + ≤510 bytes) does exactly one thing: raw IDE PIO (LBA28) reads + `STAGE2_SECTORS` (16) sectors starting at disk sector 1 into memory at + `0x7E00`, then jumps there. Nothing else fits, nothing else is needed. + (Caught a real bug writing this: `outb $0x1F7,%al`-style immediates + silently truncate to 8 bits — ports 0x1F2-0x1F7 are all > 0xFF and need + the `%dx` register form, or you're hitting the wrong I/O port entirely.) +- **Stage 2** (`boot/stage2.S` + `boot/stage2main.c`, loaded at `0x7E00`, + occupies disk sectors 1-16) does everything stage 1 used to skip straight + to, plus a boot banner (BIOS teletype, real mode, before the VBE mode + switch — note this only reaches the video device, not serial, so it + won't show up in a `-serial file:...`-captured log), a splash screen, a + real-time loading bar, and an F8 boot menu (all described below). Not + size-constrained the same way as stage 1 — `tools/padstage2.pl` zero-pads + it out to a fixed `STAGE2_SECTORS*512` (8192) bytes so the kernel's + on-disk starting sector stays predictable regardless of stage2's actual + compiled size, and errors out (like `tools/sign.pl` does for stage 1) if + it ever grows past that budget instead of silently corrupting the layout. + +The kernel now starts at disk sector 17 instead of 1 (`schunix.img`'s `dd +... seek=17` in the root `Makefile`, and the equivalent `+(1+STAGE2_SECTORS)` +in `stage2main.c`'s `readseg()` — all three places that encode +`STAGE2_SECTORS` have to agree, or the kernel won't be found on disk at +all). `boot/bootmain.c` no longer exists; its logic was split between +`stage2.S` (real-mode/protected-mode setup) and `stage2main.c` (splash + +the unchanged multiboot-header-scan-and-load logic). + +The VBE mode info block itself lives at `0x30000`, not the classic +single-stage bootloader's `0x9000` — stage2's own code now runs from +`0x7E00` up to nearly `0x9000` once the boot-menu/progress-bar logic is +included, and a block at `0x9000` gets partially overwritten by stage2's +own tail end (found this the hard way: the kernel's `LFB:` cprintf started +reporting a different wrong value on every boot, because it was reading +whatever stage2 code/data now happened to occupy that address). +`kernel/dev/vga.c`'s `vga_init()` has to agree on `0x30000` too, since it +independently re-reads mode fields from the same block. + +Splash/progress bar/boot menu rendering is a small standalone pixel +renderer in `stage2main.c` (not the kernel's `vga.c` — paging isn't set up +yet, so the LFB physical address from the VBE block is used as a plain +pointer) reusing the kernel's own font data (`include/font.h`) directly. +It fills the framebuffer with PSOGM's purple (matching +`kernel/core/console.c`'s panic screen) and draws "SCHUNIX" centered, then: + +- **Real-time loading bar**: `readsect()` (in `stage2main.c`, not the + kernel) calls `progress_tick()` after every 512-byte disk read, updating + an actual pixel-filled bar against the real total byte count — first a + small one while scanning for the multiboot header, then a second, + properly-sized one once the header's found and the true kernel size is + known, labeled "Loading kernel...". Not simulated/timed — it reflects + real bytes read off disk. +- **F8 boot menu**: `wait_for_f8()` busy-polls the keyboard controller + (`inb(0x64)`/`inb(0x60)`, raw scancodes — real-mode BIOS keyboard calls + aren't available once we're in protected mode) for about a second after + the splash appears. F8 (scancode `0x42`) drops into `boot_menu()`: three + options (boot normal, boot verbose, reboot to firmware setup) navigated + with up/down arrows and Enter, plus a "detected drives" panel from + `ata_present()` — a standard floating-bus presence check (select the + drive, write a pattern to two registers, read it back) on the primary + IDE channel's master/slave (the only channel this project ever actually + puts drives on; QEMU's IDE emulation answers the same test + "successfully" on the unpopulated secondary channel too, so that channel + isn't shown — it'd just be confidently wrong). "Reboot to firmware + setup" pulses the keyboard controller's reset line + (`outb(0x64, 0xFE)`, same technique as `kernel/sys/sys_proc.c`'s + `sys_reboot()`); a real bootloader can't jump into BIOS/UEFI setup + directly, so this is the closest honest equivalent — it gives the user + a fresh POST during which their firmware's own setup-menu key works. +- **Verbose boot**: picking "Boot Schunix (verbose)" writes a flag byte to + `BOOT_VERBOSE_FLAG_ADDR` (`VBE_INFO_ADDR + 0x100`, i.e. just past the VBE + block) before jumping to the kernel. `kernel/core/main.c`'s `kmain()` + reads it into a global `boot_verbose` as its very first action and, if + set, prints a line after every subsystem init call + (`uartearlyinit`, `kinit1`, ..., `sinit`) — real per-step boot logging, + not a canned message. + +Two bugs worth remembering if this code gets touched again: + +- **Keyboard extended-scancode handling**: `0xE0` (the prefix byte for + arrow keys and friends) also has bit 7 set, the same bit used to detect + key-*release* ("break") codes. A naive "skip anything with bit 7 set, + then check for 0xE0" loop skips the 0xE0 byte itself before ever testing + for it, so extended keys silently never register. The 0xE0 check has to + come before the break-code check, not after. +- **The kernel entry call is not a real C function call.** Both the + original single-stage `bootmain()` and the new `stage2main()` do + `((void(*)(uint32))entry)(fbaddr)` — normal cdecl, which pushes `fbaddr` + on the stack. But `kernel/arch/x86_64/entry.S`'s `mboot_entry` reads its + framebuffer argument out of **EAX** (a multiboot-style raw-register + handoff, then `mov %eax,%ebp` / `mov %ebp,%ebx` / `mov %rbx,%rdi` carries + it through the 32→64-bit transition into `kmain()`'s first argument). + The cdecl call never puts anything in EAX — it "worked" for years purely + because simple bootmain.c code happened, as a side effect of its own + codegen, to leave the right value sitting in EAX right up until the + `call` instruction. Adding the splash/menu/progress-bar code changed + that codegen and broke it silently: the kernel booted fine and reported + a plausible-looking but wrong `LFB:` address (garbage left over in EAX + from unrelated prior code, different on every boot). Fixed by pinning + the argument down explicitly with `asm volatile("jmp *%0" : : "r"(entry), + "a"(fbaddr))` instead of hoping the compiler's register allocation + cooperates. If this code moves around again, re-verify with a screendump + taken *immediately* before the jump compared against the kernel's own + `LFB:` log line — they silently diverging is exactly this bug. + +## 5. `*at()` syscalls ignored/rejected real dirfds + +`ls -l`/`ls -lR` (and, by the same code path, `find`/`du`/anything using +toybox's `lib/dirtree.c`) would hang on `ls -l` and either hang or silently +misbehave on recursive listings. Root cause: `kernel/sys/sys_fs.c`'s +`*at()` syscalls (`openat`, `fstatat`, `unlinkat`, `renameat`, `linkat`, +`symlinkat`, `mkdirat`, `mknodat`, `readlinkat`, `faccessat`) only handled +two cases for their `dirfd` argument — `AT_FDCWD` (resolve relative to +`proc->cwd`) or an absolute path (resolve from root, ignoring `dirfd` +entirely). A **real** dirfd with a **relative** path — e.g. +`fstatat(open("/bin", O_RDONLY), "ls", &st, ...)`, the standard, efficient +way any directory-tree walker avoids rebuilding full path strings for every +entry — was either rejected outright (`fstatat`, `unlinkat`, ...) or +silently ignored in favor of `proc->cwd` (`openat`, which never checked at +all — worse than rejecting, since it can silently resolve the *wrong* +file if cwd happens to contain a same-named entry). + +Fixed by adding a real dirfd-relative path resolver: `kernel/fs/fs.c`'s +`namex()` was refactored into `namex_from(struct inode *start, ...)`, +parameterizing what used to be a hardcoded `idup(proc->cwd)`, with two new +exported entry points, `nameifrom()`/`nameiparentfrom()`. `sys_fs.c` gained +`at_dir(dirfd)` (resolves `AT_FDCWD` or a real fd to a starting inode) and +`at_namei()`/`at_nameiparent()` built on top of it, plus a `createat()` +(parameterizing `create()`'s hardcoded `nameiparent()` call the same way). +Every `*at()` syscall now uses these instead of its own inline +absolute-path/`AT_FDCWD`-only check. + +**Known follow-on issue, not yet root-caused**: heavy `ls`/`ls -lR` use +across many commands in one long-running shell session can eventually make +a *later*, unrelated directory hang (reproduced with `ls -lR /var` after +~10 prior commands in the same session; the identical command succeeds +immediately on a fresh boot). Not an inode-table or file-table exhaustion +— both `iget()` and `filealloc()` fail cleanly (panic / return 0 +respectively) when full, and neither fired. Smells like a lock or +reference-count leak somewhere in the *at() paths above, or in toybox's +own fd handling, that only manifests after enough accumulated opens/closes. +Next step: instrument `itable`/`ftable` occupancy counts and watch them +across a long `ls`-heavy session to see what's actually climbing. + +## 6. Boot speed + +Two cheap, real wins in `boot/stage2main.c`, no functional changes: + +- `progress_tick()` (called once per 512-byte disk sector) used to trigger + a full progress-bar redraw — border plus fill, a few thousand pixel + writes — on *every* sector. For the ~330KB kernel image that's ~650 full + redraws (the multiboot-header-scan phase adds another ~32) for a bar + that only has 101 visually distinct states (0-100%). Split into + `progress_border()` (drawn once, in `progress_start()`, since the border + never changes) and `progress_fill()`, and `progress_tick()` now skips the + redraw entirely unless the percentage actually changed. This was the + single biggest chunk of the bootloader's own wall-clock time. +- `fill_rect()` (the full-screen splash fill, 480,000 pixels at 800x600, + run at least twice per boot) used to call `draw_pixel()` per pixel, which + recomputes `g_fb + y*stride + x*4` from scratch every time. Now computes + each row's base pointer once and indexes into it. +- The F8 boot-menu wait was shortened from ~1s (`5000000` busy-loop + iterations) to ~0.6s (`3000000`) — flat overhead on every boot whether + or not the menu is wanted, so it's the most direct lever available + without giving up the feature entirely. + +## 7. `*at()` syscalls, real signal delivery, and mksh job control (in progress) + +`ls -l`/`ls -la` hanging outright turned out to be the same root cause +described in section 11 above (dirfd-relative path resolution) — fixed +and verified extensively (plain `ls`, `ls -l`, `ls -la`, `ls -lR`, piped +output, all confirmed working repeatedly). + +A *second*, deeper issue was found investigating a follow-up report +(`cd` with no `HOME` set, then `cd var`, then `ls` hanging): mksh's +interactive job control (`ports/mksh (mksh R59c source): jobs.c`) blocks in +`sigsuspend()` waiting for `SIGCHLD`, expecting its own handler +(`j_sigchld()`) to run, reap the child via `waitpid(-1, &status, WNOHANG)`, +and update its internal job-state tracking. This kernel had **no real +signal delivery at all** — `sys_rt_sigaction()` was a no-op stub, so no +handler was ever invoked for anything, and `sigsuspend()` just slept out +one tick and returned `-EINTR` unconditionally. mksh's wait loop had no +way to ever learn the child had exited, hanging forever. + +Real signal delivery was implemented from scratch: +- `struct proc` (`include/proc.h`) gained `sig_handler[32]`/ + `sig_restorer[32]` (populated by `sys_rt_sigaction()`, + `kernel/sys/sys_sig.c`), `saved_sigtf` (stashed pre-signal trapframe), + and `in_signal` (re-entrancy guard). `allocproc()` zeroes these for a + freshly claimed ptable slot (they weren't before — a reused slot + silently inherited a previous, unrelated process's handler addresses, + which crashed the *next* process to occupy that slot the moment + anything tried to delivver a signal to it). `fork()` copies handler/ + restorer/mask from parent to child (POSIX semantics) but not pending + signals. +- `kernel/proc/proc.c`'s `exit()` sets `SIGCHLD` pending on the parent. +- `kernel/sys/syscall.c`'s `syscall()` calls `deliver_signals(tf)` in its + tail, right before returning to user mode: if there's a pending, + unblocked signal with a registered handler, it redirects the trapframe + to jump to the handler (`rdi`=signal number per SysV ABI, `rip`=handler) + instead of resuming the interrupted code, having first stashed the + original trapframe in `proc->saved_sigtf`. +- The return address is **not** `sig_restorer[sig]` — checked, `SA_RESTORER` + is not defined anywhere in this musl build, so `sigaction()` never fills + in a restorer; it's always 0, and pushing 0 as a return address crashes + with `rip=0` the instant the handler function returns. Instead, + `deliver_signals()` injects a tiny 7-byte trampoline (`mov eax,15; + syscall` — `rt_sigreturn`) directly onto the user's own stack, below the + current `rsp` (clear of the SysV red zone), and points the return + address at that — the standard technique for when the C library doesn't + supply its own restorer. +- `sys_rt_sigreturn()` restores `*proc->tf` from `proc->saved_sigtf`, + clearing `in_signal`. + +**A real bug found and fixed along the way**: the first version of +`sys_rt_sigaction()` translated the user's `struct sigaction` pointer +*once* via `uva2ka()` and then did a bulk `memset`+copy across the whole +~152-byte structure. `uva2ka()` only guarantees the *page containing that +address* is mapped, not everything up to some further offset — when the +user's pointer landed close enough to a page boundary, the write ran past +the mapped page into whatever physical page happened to be next in kernel +address space (P2V is a flat offset, so "next page" there has nothing to +do with the user's actual next page), corrupting unrelated kernel memory. +This crashed every freshly `exec`'d `/bin/sh` almost immediately, since +mksh calls `sigaction()` during its own startup. Fixed by only touching +the two individual 8-byte fields actually needed (`sa_handler` at offset +0, `sa_restorer` at offset 144), each through its own `uva2ka()` call. + +`sys_waitpid()`/`sys_wait4()` (`kernel/sys/sys_proc.c`) were also rewritten +on top of a new `waitpid(pid, status, options)` in `kernel/proc/proc.c` +(refactored from the old any-child-only, always-blocking `wait()`, which +is now just `waitpid(-1, 0, 0)`): a specific `pid` (not just "any child"), +`WNOHANG` (return 0 immediately instead of blocking), and — importantly — +a genuinely optional status pointer. The old code ran a NULL status +pointer through `argptr()`, which rejects NULL/unmapped addresses outright +(page 0 is deliberately unmapped) — but `waitpid(pid, NULL, WNOHANG)` is +completely standard, and is exactly what mksh's job control does. + +**Status: signal delivery mechanically works** — verified directly (a +debug build logged the handler being invoked with the correct trapframe +and `sys_rt_sigreturn()` restoring cleanly, no crash, repeatable). **mksh's +job control still doesn't reliably progress afterward**, including for +plain non-piped foreground commands, not just the original piped-into-head +case. Root cause not yet isolated. Leading suspects, in order of +likelihood: +- `sys_setpgid()`/`sys_getpgid()` are no-op stubs (`kernel/sys/sys_proc.c`) + — `j_sigchld()` (`ports/mksh (mksh R59c source): jobs.c:1344-1348`) has an early-out + keyed on `j->ppid == procpid`, which may never resolve the way mksh + expects without real process-group tracking. +- `getrusage(RUSAGE_CHILDREN, ...)` — called at the top of `j_sigchld()` — + isn't verified to behave sanely under this kernel's implementation + (`kernel/sys/sys_proc.c`'s `sys_getrusage()` only fills in `ru_utime` + from the *calling* process's own tick count, not accumulated child + time; `RUSAGE_CHILDREN` isn't distinguished from `RUSAGE_SELF` at all). +- Possibly something in how `j_sigchld()` matches a reaped `pid` against + its own `job_list` (`ports/mksh (mksh R59c source): jobs.c:1372-1385`) silently fails + to find the job for reasons not yet traced. + +This was left here deliberately rather than continuing to iterate blind: +SIGCHLD delivery is left **enabled** (`kernel/proc/proc.c`'s `exit()`) +since disabling it guarantees zero chance of mksh's wait loop ever exiting, +whereas leaving it on at least gives the cases that don't hit the +still-unknown remaining gap a chance to work. Next session should start +by instrumenting `j_sigchld()`'s own control flow (or strace-equivalent +logging in the kernel around `sys_setpgid`/`sys_getrusage`/`sys_waitpid` +calls specifically while reproducing `cd; cd var; ls` or even simpler +`ls -l /bin` followed by a second command) rather than re-deriving the +signal-delivery mechanism, which is now confirmed solid. + +--- + +## 8. Two ABI assumptions that only GCC was keeping true + +Found when the build moved from GCC-in-WSL to clang-on-Windows. Neither is a +clang bug; both are places where the code relied on GCC doing something it +never promised, and both had been latent for years. + +### `%fs:0` was being used as scratch — it is the TCB self-pointer + +`syscall_entry` (`kernel/arch/x86_64/trapasm.S`) stashed `%rax` at `%fs:(0)` +while it fetched `proc` to find the kernel stack. The comment called it a +"per-CPU scratch slot". It is not: the x86-64 TLS ABI reserves the word *at* +the thread pointer to hold the thread pointer, and a compiler may read it to +materialise the TP before applying a variable's offset. + +GCC compiles `__thread` access to a direct `%fs:-8` segment reference and never +reads that word, so the clobber was invisible. clang at `-O0` emits +`movq %fs:0, %rax; addq $-8, %rax; (%rax)` — reading back the syscall number +this code had just stored. The first process's first syscall is +`exec` (7), so `proc` resolved to address `7 - 8 = -1` and the kernel +page-faulted recursively until the stack was gone. + +Fixed by moving the scratch to `%fs:(8)`. Positive offsets are safe: the TLS +block lives at negative offsets and `seginit` places the thread pointer 2048 +bytes into a 4096-byte page. + +**How it was found** is the part worth copying next time. Three rounds of +`cprintf` narrowed it to "the word is right at `mpmain`, wrong by the first +trap" and no further — the instrumentation perturbed the timing enough to move +the symptom. A hardware watchpoint through QEMU's gdb stub named the +instruction on the first run: + +``` +qemu ... -S -gdb tcp::26000 +gdb -batch -x tcp.gdb # hbreak mpmain; watch *(unsigned long*)kernel_fs_base +``` + +`seginit` also now writes the self-pointer, which it never did. That is ABI +-correct regardless and costs one store per CPU. + +### `_start` read incoming registers with `register ... asm("rdi")` + +`lib/glue.c` captured `argc`/`argv` from `%rdi`/`%rsi` using GCC local register +variables. Both compilers only guarantee that binding for inline-asm operands; +using it to pick up an incoming argument is undefined. clang ignored it and +read two uninitialised stack slots: + +```asm +movl -0x4(%rbp), %eax # should have been %edi +movq -0x10(%rbp), %rax # should have been %rsi +``` + +Every program still ran. `init`, `shmtest` and `socktest` take no arguments and +passed their entire suites; the only symptom in the whole system was +`inputtest -n 6` ignoring its limit. Moved to `lib/start.S`, where reading +incoming registers is expressible and correct — `exec` passes argc/argv in the +SysV argument registers already, so `_start` calls `main` without touching +them. + +--- + +## 9. The boot looked like a hobby OS, and two things were why + +Not a bug — a design problem, fixed by looking at a screenshot instead of the +code. + +**The display was the kernel log.** Boot printed `pci: 6 devices` and +`vga: display controller 1234:1111, aperture fd000000 (16 MB)` to the screen +the user is looking at. That single fact is most of what separates a hobby +project from a product; every shipping system puts that behind a flag. + +`cons_serial_only` already existed for panic, so the fix was to start it *set*. +Everything still goes out the UART — which is where `tools/bootcheck.ps1` reads +it from, so all five suites kept passing untouched — and `console_takeover()` +clears it on the first `exec()`, the moment userspace genuinely starts. +`vga_init` no longer clears the screen, so the splash survives the whole of +boot rather than being wiped a few hundred milliseconds in. + +**There were three unrelated designs.** The splash was deep purple with gold +text, deliberately matching the *panic* screen. The console was cream with +near-black text. So a healthy boot was dressed as a crash, then abruptly +became an amber terminal. `include/theme.h` is now the one palette, and the +rule is: **normal states are calm and dark; only failure is loud.** PSOGM keeps +the purple entirely to itself, which is what makes it mean something. + +The splash copy went from "Schunix Boot Manager / version 1.0 / Memory: 1023 MB +/ Press F8 for boot menu..." to the product name, a rule in the accent colour, +and the one hint that is actionable — moved to the bottom, where a hint +belongs. No shipping OS names its bootloader on the splash. + +### Three latent bugs it flushed out + +Retheming meant `stage2main.c` included `types.h` properly for the first time, +and the 32-bit build stopped compiling: + +- `_Static_assert(sizeof(uint64) == 8)` fired. `uint64` was `unsigned long`, + which is **4 bytes** in the `-m32` loader — the file even carried a local + `e820_u64` workaround with a comment saying so. Now conditioned on + `__SIZEOF_LONG__` rather than on the platform, which covers LP64, LLP64 *and* + ILP32 correctly. +- `x86.h`'s `stosq` and `xchg` use `rep stosq` and `xchgq`, which do not exist + in 32-bit mode. They only ever compiled because `uint64` was silently + narrowed. Now behind `#ifdef __x86_64__`. +- The progress bar computed `(uint64)done * 100 / total`, a 64-bit divide that + needs libgcc's `__udivdi3` — which a freestanding loader does not link. Now + scaled to KiB and done in 32 bits. + +All three had been sitting in a bootloader that worked fine, waiting for +someone to make `uint64` mean what it says. + +--- + +## 10. A full-screen program typed the whole session into the shell + +Keys reached `/dev/input/event0` **and** the tty line discipline at the same +time. That is correct for two consumers of one keyboard and completely wrong +for a full-screen program: while it had the display, the shell behind it was +quietly accumulating every keystroke, and the first Enter afterwards ran +whatever had piled up. Arrow keys arrived as CP437 glyphs on the console for +good measure. + +The fix is the standard one and it is a *primitive*, not a patch: an exclusive +grab, `EVIOCGRAB`, with Linux's ioctl number. While a device is grabbed its +events still flow to `/dev/input/eventN` but both keyboard drivers withhold the +decoded character from the console. + +Three details that matter more than the feature: + +- **The grab is released by the devsw `close` hook**, not by the program. A + program that grabs the keyboard and then crashes would otherwise leave the + console permanently deaf, with no way to type the command that fixes it. +- **Modifier state is still tracked while grabbed** (`kbdgetc` keeps updating + shift/caps and only skips the character). Otherwise releasing the grab would + hand the line discipline a stuck Shift. +- **The USB path updates `prev[]` before returning**, or the next report would + look like every held key had just been pressed again. + +It is also exactly what Schcom will need, which is why it lives in the input +layer rather than in any one client. + +### The near-miss worth remembering + +The first attempt appeared to work: the harness passed, every assertion green. +It had not worked at all — the ioctl call was never inserted into the client +source, because a scripted string replacement silently failed to match. The +`grep` that should have caught it printed three hits (a comment, a `#define`, +and the *release* call) and was read as confirmation without counting what +they were. + +What actually caught it was reading the transcript rather than the verdict: +four bare `#` prompts after quitting, which is precisely the symptom the change +was supposed to remove. `input_grab()` now announces itself, so "did the grab +happen" is never again a thing anyone has to infer. + +--- + +## 11. `ps` and `top`, and why they never worked + +/proc was a set of character devices: init ran `mknod("/proc/uptime", 6, 2)` and +friends, so every file was a real inode on the root filesystem with a +major/minor behind it. That is fine for a fixed list and cannot work at all for +the thing /proc is mostly for -- one directory per process. You cannot mknod a +directory for a process that does not exist yet and will be gone in a minute. +`ps` calls `dirtree_flagread("/proc")`, got a listing with no numbers in it, and +reported nothing. It was never a `ps` problem. + +So /proc is now a real filesystem behind the same `struct fs_ops` vtable SchFS +uses (`kernel/fs/procfs.c`, `PROCFS_DEV`), mounted by init with +`mount("proc", "/proc", "proc", ...)`. Nothing is stored: directory listings +are generated from the process table when asked, file contents when read. + +Three things worth keeping in mind: + +- **The split is deliberate.** `kernel/fs/procfs.c` owns the *namespace* (what + exists); `kernel/dev/procfs.c` keeps the *generators* (what the global files + say). They change for different reasons, so `proc_gen_global()` is the only + thing that crosses between them. +- **procfs never touches ptable.** `proc_snapshot()` copies what is needed out + from under the lock, so a process that exits between the readdir that listed + it and the open that reads it produces a stale line rather than a + use-after-free. +- **Sizes are reported as zero**, exactly as Linux does. A synthetic file has + no length until it is generated, and generating every one on every `stat()` + to fill in a number almost nothing reads would be absurd. + +### What the suite caught + +Marking every procfs inode read-only broke `echo s > /proc/sysrq`, and three +USB checks failed on it within a minute of the change -- they drive the disk +through sysrq. /proc is read-only *except* sysrq, which is the one deliberate +write target, and the code now says so in one place. + +### Still not right + +`ps -o comm` prints `[init]` rather than `init`. That field comes from +`/proc//exe`, which Linux makes a **symlink** and toybox `readlink()`s; +ours is a regular file, so the readlink fails and toybox brackets the name as +it would for a kernel thread. Default `ps` and `top` output are unaffected. +Fixing it needs symlink support in procfs, which belongs with the rest of the +VFS work rather than as a one-off. + +--- + +## 12. A test program crashed at random, and had been typing its own launch command + +Symptom: a full-screen framebuffer program, launched with arguments, worked +four times and then died with + + pid 9 : trap 14 err 4 on cpu 0 rip 0x2269be addr 0x18--kill proc + +`err 4` is a user-mode read of a non-present page and `addr 0x18` is a null +pointer at offset 24. `llvm-nm` put `rip` inside the routine that opens the +program's overlay view, which dereferences a pointer the program only fills in +once a level is loaded -- at offset 0x18 on x86-64, because the three 64-bit +words of a base struct sit in front of it. So the program had been asked to open +that view before there was anything to show. + +**The technique is the transferable part:** the fault address alone said +"null pointer plus 24", which is nearly useless; `llvm-nm` on the faulting `rip` +turned it into a function name, and the offset then identified the exact field. +Reach for that before adding printfs to a program you did not write. + +The cause was ours, and it was visible in the log the whole time: every run +printed a burst of garbage bytes *before* the grab was taken, and the amount +differed each run. + +**`/dev/input/eventN` had no notion of being opened.** Events were queued from +boot onwards whether or not anything was listening, into one 256-entry ring +with one read cursor. When the program opened the device it inherited the entire +backlog -- including the keystrokes of the command line that had launched it, +which the shell had already consumed -- and replayed them as input. Whether that +crashed depended on which keys happened to be sitting there, which is exactly +the "sometimes" in the report. + +Fixed by giving `struct devsw` an `open` hook to match `close`, and having +`/dev/input`: + +- drop the backlog on open (`r = w`) -- a reader starts from *now*; +- only queue events while `opens > 0`, which is what Linux does: an evdev + device with no clients does not accumulate. + +Linux gets both for free by giving every open its own client queue. One shared +ring plus "start from now" is the same answer for a single reader, which is the +only case that exists today; per-client queues become necessary the moment +Schcom and a client both want the keyboard. + +### Also fixed + +`cd` into a file reported "Operation not permitted". It was right to fail, but +`sys_chdir` returned a bare `-1`, so the shell printed whatever errno happened +to be lying around. It returns `-ENOTDIR` now, and `-ENOENT`/`-EFAULT` on the +other two failure paths -- "Not a directory" sends you to the right place, +"Operation not permitted" sends you looking at permissions. diff --git a/docs/architecture/schapi.md b/docs/architecture/schapi.md new file mode 100644 index 0000000..28c2541 --- /dev/null +++ b/docs/architecture/schapi.md @@ -0,0 +1,239 @@ +# SchAPI, and Schunix as a system + +Status: **direction agreed, not yet implemented.** Nothing in this document +changes what is being built this week; it changes what everything is being +built *towards*. See §7 for what actually happens now. + +--- + +## 1. The decision + +Schunix stops being "a Unix-like kernel with a userland bolted on" and becomes +a **system**: a kernel, a native API, and the services and applications that +sit on it, designed together. + +The model is NeXTSTEP/macOS, not Linux. Specifically: + +- A kernel that provides mechanism, with **more than one personality** on top. +- **POSIX/Unix is a personality, not the identity.** It stays, completely and + seriously, because it is how you get software. It is not the thing programs + are expected to be written against. +- **SchAPI** is the native interface: higher-level, object-shaped, designed for + the GUI and services rather than retrofitted onto 1970s process semantics. + +Two systems are being borrowed from, and they are *different* systems. It is +worth being precise about which, because they disagree: + +| Borrowed | From | Why | +|---|---|---| +| Layered architecture, personalities, native API over a Unix substrate | macOS / NeXTSTEP | It is the only mainstream design that got a modern API onto a Unix base without abandoning either | +| Permanent backward compatibility | **Windows** | macOS is emphatically *not* this — Apple dropped 32-bit apps, Carbon, kexts and OpenGL, each time on a deadline | + +That mix is coherent, but it is a mix. Taking the architecture from Apple and +the compatibility policy from Microsoft is a deliberate choice, and the rest of +this document is mostly about making that choice survivable — because +compatibility-forever is the single most expensive promise an OS can make, and +Windows pays for it every day. + +--- + +## 2. The one mechanism that makes the promise affordable + +**The stable interface is a library, not the syscall table.** + +This is the whole trick, and it is what lets macOS keep decade-old binaries +running while freely renumbering its syscalls. On macOS, nothing may issue a +syscall directly; you link `libSystem`. Apple therefore owes compatibility only +at the library boundary, and the kernel stays editable forever. + +Linux made the opposite choice — the syscall ABI *is* the contract, "we do not +break userspace" — and consequently every syscall mistake ever made is still +there, with a `2` or a `64` suffixed to the name of its replacement. + +So, for Schunix: + +``` + ┌──────────────────────────────────────────────┐ + │ applications, SchWM, SchDE │ + ├──────────────────────────────────────────────┤ + │ SchAPI │ POSIX / Linux personality│ ← THE STABLE BOUNDARY + ├──────────────────┴───────────────────────────┤ + │ libschunix — the only thing that may syscall│ + ├──────────────────────────────────────────────┤ + │ syscall table — PRIVATE, unstable, renumberable + ├──────────────────────────────────────────────┤ + │ kernel │ + └──────────────────────────────────────────────┘ +``` + +Rules that follow, and they are not negotiable if the promise is to hold: + +1. **A program that issues `syscall` directly is unsupported.** It may break at + any release. Only `libschunix` may. +2. `libschunix` exports versioned symbols. An entry point's behaviour never + changes; a changed behaviour is a new version of the symbol. +3. The kernel may renumber, merge, split or delete syscalls at will, provided + `libschunix` still presents the same surface. + +This *reconciles* with the existing decision to adopt Linux syscall numbering +(`docs/roadmap.md`, and the reason musl/toybox/mksh work at all). Linux +numbering does not go away — it becomes the **Linux personality's** entry path, +which is exactly what it already is in practice. It is a compatibility surface +that happens to be entered via `syscall`, and it is frozen because *Linux* +froze it, not because we did. + +--- + +## 3. Stability tiers + +"Nothing can ever be removed" is only affordable if you are precise about what +"anything" covers. Windows' real problem was never the documented API — it was +that undocumented behaviour became load-bearing, and then it was too late. + +Every public interface carries exactly one tier: + +| Tier | Promise | May change | +|---|---|---| +| `stable` | Forever, from the release that introduced it | Never. Additions only. | +| `provisional` | Until the next release | Freely | +| `internal` | None | Freely, without notice | + +**Everything in Schunix today is `provisional`.** The freeze starts at 1.0 and +covers only what is marked `stable` at that moment. This is not a way of +dodging the commitment — it is the only way to make it, because a promise made +now would permanently enshrine, among other things, a `fcntl(F_SETFL)` that +was a documented no-op until last week and a `struct file` whose `O_NONBLOCK` +handling had never been exercised. + +Nothing becomes `stable` by being old. It becomes `stable` by someone deciding +it is right and writing that down. + +--- + +## 4. `compat/` — what it actually means + +The instinct is right: old interfaces should not litter the current tree. But +moving *source* into a folder does not reduce the *runtime* cost, and the +runtime cost is the one that hurts. + +The rule that keeps compatibility cheap: + +> **A compatibility entry point must be implementable as a translation into the +> current API. If it cannot be, that is a defect in the current API — fix the +> current API before freezing it.** + +So `compat/` holds shims, not parallel implementations. A shim: + +- lives in `compat//`, +- is written in terms of today's API and nothing else, +- is never called by new code, +- carries its own tests, which are the only thing that proves the promise is + still being kept. + +When a shim *cannot* be expressed as a translation, that is the alarm. It means +the new API lost information the old one carried, and it is far cheaper to +notice that before 1.0 than to maintain two real implementations forever. That +is precisely the trap Win32 fell into. + +--- + +## 5. Taking from the BSDs + +`src-trunk/` is a NetBSD checkout, and a FreeBSD one is being added alongside +it. Both are reference material and parts bins; neither is built and neither +may ever end up in an image. + +**Two trees is not redundancy.** They are worth having together for three +reasons that a single tree cannot give: + +- Where an RFC is ambiguous — and TCP's are, constantly — two independent + implementations agreeing is far stronger evidence than one implementation + asserting. +- Where one is licence-encumbered, the other may not be. NetBSD's + `tcp_input.c` carries a WIDE Project advertising clause that Berkeley's + rescission does not cover; FreeBSD's equivalent may well be 2-clause. See + [../licensing.md](../licensing.md) §3. +- They have opposite biases, and knowing which you are reading matters: + +| | FreeBSD | NetBSD | +|---|---|---| +| Stack quality | The better stack in absolute terms — faster, more complete, more actively developed | Solid, conservative, less featureful | +| Extractability | Welded to FreeBSD: `epoch(9)`, `uma`, `netisr`, VIMAGE/vnet, `if_t`, a rich locking vocabulary | Portability *is* the project's identity; assumes far less of its host kernel | +| Use it as | The reference you check yourself against | The code you actually lift | + +That table is the same lesson §1 of the roadmap already learned the hard way +with FreeBSD's `sys/dev/usb`: 100k lines welded to newbus, where porting the +USB stack means porting the device framework first. Networking is the same +shape. Read FreeBSD, lift NetBSD, write Schunix. + +### Licensing — read this before copying anything + +NetBSD is not uniformly licensed: 2-clause, UC 3-clause and a minority of +4-clause-with-advertising files all appear, and only the first two are +comfortable. The per-template rules, the measured counts, and the one-line +check to run before copying a file are in [../licensing.md](../licensing.md) +§3. Do not copy from `src-trunk/` without reading it. + +### What is actually worth taking + +Ranked by value, not by size: + +1. **`sys/net`, `sys/netinet` — the TCP/IP stack.** The largest single win + available. Phase 2 of the roadmap wants ARP/IPv4/ICMP/UDP/TCP, and this is a + mature, clean, readable, 2-clause-heavy implementation of exactly that. +2. **`sys/kern/subr_pool.c` — the pool/slab allocator.** Schunix has `kalloc` + and a page at a time. Every subsystem written since has invented its own + fixed-size table to work around that. +3. **`autoconf` — the driver framework** (`config(1)`, `cfdriver`/`cfattach`, + `device_t`). This is the grown-up version of what `kernel/drivers/registry.c` + and `kernel/dev/irq.c` are hand-rolling. Adopt the *model*; do not try to + port the code wholesale. +4. `sys/kern/subr_*` generally — rbtree, workqueue, condvar, kmem. + +Low value: `lib/libc` (musl already fills that role), `sys/dev/usb` (we have a +working xHCI stack written from the spec). + +**Take designs before code.** Every file copied is a file to be understood, +relicensed-in-notice, and maintained forever under the §3 promise. + +--- + +## 6. What SchAPI is, concretely + +Not yet designed. What is decided about it: + +- It is the native interface; POSIX is available beneath it but not the idiom. +- It is reached through `libschunix`, never by direct syscall. +- It is designed *with* Schcom in mind, because a compositor and its clients + are the first real users and their needs (IPC, shared buffers, event streams, + object lifetimes) are what will shape it. +- The IPC primitive comes first. Everything in a NeXT-shaped system rests on + it, and unix domain sockets — already built and working — are the substrate, + not the interface. + +What is explicitly *not* decided: whether it is object-oriented in the Objective-C +sense, what language it is expressed in, and whether it takes Mach's port model +or something smaller. Those need their own document, written when Schcom has +told us what it actually needs. + +--- + +## 7. What changes now + +Almost nothing, on purpose. The ordering does not change: + +- **The native Windows build must boot first.** The clang-built kernel + currently reaches `sinit` and dies on the first trap. A direction is worth + nothing while the tree does not run. +- Then the roadmap continues as written: network, GPU, fonts, Schcom. + +What this document changes *today* is only this: + +1. `src-trunk/` is reference material, excluded from every build and image. +2. New interfaces are marked `provisional` from now on, so that §3 has + something to work with at 1.0. +3. When the network stack is written, NetBSD's is the starting point rather + than a blank file — with the licence check in §5 applied per file. +4. `libschunix` is created when there is a second personality to justify it. + Until then, the *rule* stands even though the library does not exist yet: + no new first-party program issues raw syscalls. diff --git a/docs/architecture/schfs.md b/docs/architecture/schfs.md new file mode 100644 index 0000000..e0dc7e0 --- /dev/null +++ b/docs/architecture/schfs.md @@ -0,0 +1,403 @@ +# SchFS — design + +**Status: phases 1 and 2 are done.** The copy-on-write B-tree, the volume layer, +the object layer, a working image writer and a checker all exist, and the kernel +mounts a SchFS volume read-only at boot. Phase 3 (writes and transaction groups) +is next. See §8 for exactly what is built and how to run its tests. + +Supersedes the on-disk format described in `include/fs.h`. Keeps the name SchFS; +the on-disk magic changes so v1 images are detected and rejected rather than +silently misread. + +The brief was "the best aspects of BeFS, ZFS and btrfs." Those three disagree +with each other in places, so this document says explicitly what is taken from +each and what is deliberately left behind. + +## 1. What each one is actually good at + +**ZFS — never be inconsistent.** Copy-on-write: no block that is part of the +live tree is ever overwritten. An update writes new blocks, then atomically +flips a single root pointer. A crash therefore lands on either the old tree or +the new one — never a half-written mix. Consequence: **no fsck**, ever. Paired +with end-to-end checksums where a block's checksum is stored in its *parent* +pointer, not in the block itself, which makes the whole tree self-validating and +catches misdirected writes and bit rot that a self-stored checksum cannot. + +**btrfs — one data structure, reused everywhere.** btrfs has essentially one +engine: a copy-on-write B-tree. Inodes, directory entries, extent maps, free +space, and snapshots are all just different key types in that same tree. For a +small team this is the single most important idea in the whole design: you write +and debug *one* tricky data structure, then get five subsystems from it. Also +takes extents with **reference counts**, which is what makes snapshots and +reflink copies fall out almost for free. + +**BeFS — the part that feels like a database, at a size one person can build.** +BeFS is a 64-bit journaled filesystem whose distinguishing feature is B+tree +**indexes over file attributes**, with a query interface on top. That is the +"SQL-like" capability, and unlike WinFS it actually shipped. Critically, BeFS is +also the right *scale* model: a small team, a readable codebase, and a book +(Giampaolo, *Practical File System Design*, free online) that describes it +end to end. + +## 2. What is deliberately not taken + +- **No ZFS storage pools / RAID-Z / vdevs.** Multi-disk redundancy is a large + subsystem that solves a problem Schunix does not have yet. +- **No btrfs-style rebalancing or online format conversion.** Enormous + complexity, and it is the historical source of btrfs's worst bugs. +- **No actual SQL.** No parser, no query planner, no relational engine. See §6 — + the useful part of "database filesystem" is the *index*, not the language. + WinFS spent 2003-2006 and a large team on the other interpretation and + shipped nothing. +- **No deduplication.** ZFS's dedup is famous for needing more RAM than anyone + expects. Extent refcounts give reflink/snapshot benefits without it. + +## 3. On-disk shape + +``` ++--------------------+ block 0 boot block (untouched by the FS) ++--------------------+ blocks 1..N uberblock array (128 slots, rotated) ++--------------------+ +| CoW B-tree | one tree, many key types: +| | (ino, INODE, 0) -> inode record +| | (ino, DIRENT, hash) -> directory entry +| | (ino, EXTENT, offset) -> extent mapping +| | (ino, ATTR, nameid) -> extended attribute +| | (idx, INDEX, key) -> attribute index entry ++--------------------+ +| free space | extent-based, tracked as tree keys, not a bitmap ++--------------------+ +``` + +**Uberblock array (from ZFS).** 128 slots written round-robin, each carrying a +transaction number and a checksum. Mount scans all slots and takes the +highest-numbered one that validates. This is what makes crash recovery +"pick the newest good root" instead of "replay a journal." + +**Everything is one B-tree (from btrfs).** Keyed by +`(objectid, type, offset)`, 24 bytes. Adding a feature usually means adding a +key type, not a new on-disk structure. + +**Checksums live in the parent (from ZFS).** Each internal node stores, for +every child pointer, that child's CRC32c. A block that is read is verified +against the pointer that led to it. v1 already chose CRC32c and already has +checksum fields in `dinode` and `superblock` — same algorithm, better placement. + +### Widths — every v1 limit that has to die + +| v1 | limit | v2 | +|---|---|---| +| `dirent.inum` is `ushort` | 65,535 files, total | `uint64` object ids | +| `dinode.size` is `uint` | 4 GB max file | `uint64` | +| `uint` block numbers, BSIZE 1024 | 4 TB volume | `uint64` blocks, 4096-byte | +| `DIRSIZ` 14 | 14-char filenames | variable, up to 255 | +| fixed hash table per dir | every dir ≥113 KB, capped at 119 KB | B+tree, pay per entry | +| `addrs[NDIRECT+3]` | block-at-a-time | extents with refcounts | + +Block size moves 1024 → 4096 to match modern media and make extents worthwhile. + +## 4. What already exists and gets reused + +Not starting from zero: + +- **`struct schfs_extent`** is already defined in `include/fs.h` and unused. + The extent concept is already sketched; v2 gives it refcounts and 64-bit + fields. +- **CRC32c checksum fields** already exist in `dinode` and `superblock`, and + mkfs already computes them. Same algorithm, relocated into parent pointers. +- **`kernel/fs/log.c`** is a working transaction log with `begin_op`/`end_op` + call sites throughout the FS. Under CoW the log is not needed for consistency, + but `begin_op`/`end_op` are exactly the right places to bracket a transaction + group, so the *call sites* survive even though the implementation is replaced. +- **The buffer cache** (`bio.c`) is format-agnostic and needs no change beyond + the block-size constant. + +## 5. Phasing + +Each phase leaves a bootable system. Do not start phase 2 before phase 1 is +solid — the B-tree is the load-bearing piece and everything else assumes it. + +**Phase 1 — the B-tree, in userland first.** Write the CoW B-tree as a plain C +library with a block-device abstraction, and test it on a *host* file with a +fuzz/torture harness (random insert/delete/lookup, simulated crash at every +write point, verify the tree always mounts). This is the one component where a +subtle bug is unfixable later, and testing it in the kernel is far harder. It is +also portable straight into `tools/mkfs`. + +**Phase 2 — read-only mount. DONE.** New magic, uberblock scan, tree walk, inode +and dirent key types. `tools/mkschfs` writes images; the kernel only reads them. +The system boots from a ramdisk root (see `docs/layout.md` and the ramdisk work) +so a broken disk cannot stop you from booting. + +Went further than planned in two places, both cheaply: data blocks carry +checksums too (a `CSUM` key type, as btrfs does — without it "end-to-end +checksums" would cover only metadata, which is the part least likely to rot), +and extents landed early because reserving a file's blocks in one go was +simpler than not doing it. + +Not yet done: SchFS files are not reachable through `open(2)`. The mount is +observable through `/proc/schfs` and through the kernel API in `schfs_int.h`, +but wiring it into `namei`/`readi` means the VFS work that phase 3 needs anyway. + +**Phase 3 — writes, transaction groups. DONE.** Every `schfs_k*` call that +changes anything is one transaction group, committed by writing a new uberblock +before it returns. The full write API — create, link, unlink, rename, write at +an arbitrary offset, truncate — lives in `objw.c` and is exercised by +`tools/schfs_objtest.c`. + +Two things went differently from the plan above, both for reasons worth +recording. + +*Free space is not tracked as tree keys.* Allocating from a free map that lives +in the tree mutates the tree, which allocates, which changes the map; btrfs +resolves that with a fixpoint loop and ZFS with log-structured space maps. SchFS +sidesteps it: the committed tree already says exactly which blocks are live, so +the map is **derived** by walking the tree once at mount and held only in RAM. +It cannot disagree with the filesystem, because it is computed from it, and a +crash raises no question at all. The cost is mount time proportional to metadata +size, and a ceiling on volume size set by how much bitmap fits in memory. If +mount time ever hurts, the map can be cached on disk purely as a hint, +validated against the generation and discarded on a mismatch — an addition, not +a format change. See `kernel/fs/schfs/space.c`. + +*Data blocks needed a generation stamp too.* The B-tree already rewrites its own +nodes in place within a group, since a block that no committed root can reach is +nobody's fallback. Data blocks had no such marker, so every hundred-byte write +copied a whole 4 KB block and split an extent record around it. `schfs_extent` +gained a 64-bit `gen`, and the same rule now applies to file data. + +**Phase 3½ — reachable through `open(2)`. DONE.** SchFS is a mounted filesystem: + +``` +mkdir /mnt +mount -t schfs /dev/hdb /mnt +ls /mnt ; echo hi > /mnt/tmp/w ; cat /mnt/tmp/w ; mkdir /mnt/tmp/d +``` + +The VFS grew a real dispatch layer rather than a set of `if (dev == SCHFS_DEV)` +checks. `struct fs_ops` (include/file.h) is one table per filesystem, chosen by +`iget()` from the device number and hung off the inode; v1's implementations +keep their bodies and are simply named `v1_*`. Adding a third filesystem is a +new table and nothing else. `kernel/fs/schfs/vfs.c` is SchFS's, and it is pure +translation — object ids in, inodes out — because all the filesystem logic +already lives below it. + +Three things had to change on the way, none of them SchFS's fault: + +- `struct inode.inum` and `.size` were 32-bit, capping objects at 2³² and files + at 4 GiB on a filesystem that promises far more. Both are `uint64` now, as are + `readi`/`writei` offsets and `struct file.off`. +- `struct dirent` carries a 16-bit inum and a 14-byte name, so it cannot + represent a SchFS entry at all. `getdents64` is now an fs_op: each filesystem + emits the record stream itself, and `f->off` is whatever position it needs to + resume. v1 walks its hash buckets; SchFS walks a key range and synthesises + "." and "..", which it does not store. +- Mounting is a substitution made during path resolution (`mount_add` / + `mount_cross` in `fs.c`). Nothing on either disk records it. The reverse + direction matters as much as the forward one: `..` at a mounted root has to + leave the mount, and the filesystem underneath has no idea it is mounted + anywhere. + +Three long-standing bugs surfaced the moment real commands ran against it, all +in the shared VFS rather than in SchFS: + +- `include/fcntl.h` used xv6's open flags while every program in the userland + sends Linux's. `O_APPEND` (0x400) landed on `O_TRUNC`, so `cmd >> file` + *truncated* it; `O_TRUNC` (0x200) landed on `O_CREATE`, so `cmd > file` left + whatever was past the new end in place. Nothing about either pointed at a + header. +- `sys_openat` silently dropped `O_TRUNC` and `O_APPEND` while `sys_open` + honoured both, so the same redirection behaved differently depending on which + entry point libc happened to call. +- `rmdir` had no Linux syscall number, so every Linux-ABI program got ENOSYS. + +**Phase 4 — extents with refcounts.** Enables reflink copies and snapshots +nearly for free. + +**Phase 5 — attribute index and query.** See below. + +## 6. The "SQL" part, honestly + +What you want from "works like SQL" is: *ask a question about a lot of files and +get an answer fast, without walking every directory*. That is an **index**, not +a query language. BeFS's design: + +- Files carry typed attributes (`string`, `int`, `time`, …). +- An attribute can be *indexed*: a dedicated B+tree keyed by attribute value, + mapping value → object ids. +- A query is a predicate over indexed attributes, evaluated by intersecting + index ranges. + +In SchFS this is just another key type — `(idx, INDEX, key)` — in the tree +that already exists. That is the whole reason to build phases 1-4 the btrfs way: +the "database filesystem" feature becomes a few hundred lines on top of +machinery already written and debugged, rather than a separate engine. + +A concrete `query()` syscall and a `find`-like tool come after that, and are +small by comparison. + +## 7. Honest cost + +Phases 1-3 are roughly 3,000-4,000 lines and are the bulk of the work. Phase 1 +alone (B-tree + torture harness) is worth doing carefully and unhurriedly; every +later phase compounds on it. + +This is the largest single piece of work in Schunix. It is also entirely +ordinary filesystem engineering with excellent published references +(Giampaolo for BeFS; the btrfs design papers; the ZFS uberblock/checksum-in- +parent scheme) — there is no research risk here, only volume. + +**Do this before the installer.** The installer's entire job is to create and +populate this format. Building it against v1 means building it twice. + +## 7½. Limits + +Three different things get called "the limit", and they are worth keeping apart: +what the **on-disk format** can express, what **this implementation** can +currently mount, and what the **rest of Schunix** can carry. The smallest one is +what you actually hit. + +### The format + +| | limit | where it comes from | +|---|---|---| +| Volume | **64 ZiB** (2⁶⁴ × 4 KiB blocks) | `uint64` block numbers | +| File | **16 EiB** (2⁶⁴ bytes) | `schfs_inode.size` | +| Files per volume | **1.8 × 10¹⁹** | `uint64` object ids | +| Hard links per file | **4.3 × 10⁹** | `uint32 nlink` | +| Filename | **255 bytes** | `SCHFS_NAME_MAX` | +| Entries per directory | no fixed limit | one tree key per name hash | +| Extent | **64 ZiB** in one record | `uint64 nblocks` | +| Tree depth | **8 levels** | `SCHFS_MAX_LEVEL` | +| Block size | **4096 bytes**, fixed | `SCHFS_BLKSIZE`; a mismatch is refused at mount | +| Transaction groups | **1.8 × 10¹⁹** | `uint64` generation, never reused | + +Directory *depth* is unbounded on disk; path resolution is what limits it. + +### This implementation + +| | limit | where it comes from | +|---|---|---| +| Volume, mounted | **64 GiB** | the in-RAM allocation bitmap: `SCHFS_SPACE_PAGES` (512) pages × 32768 blocks | +| Volume, via the buffer cache | **4 TiB** | `bio.c` addresses blocks with a `uint`, and one SchFS block spans four of them | +| RAM per mount | **8 KiB per GiB** of volume | two bitmaps, one bit per block each | +| Inline value in a leaf | **1016 bytes** | `SCHFS_MAX_INLINE` = a quarter of a node | +| Colliding names in one directory | ~**60**, sharing a hash | the packed list for one hash must fit in one inline value | +| Directory entries at 8-byte names | ~**42** per key, unlimited keys | as above | +| Records per leaf / children per node | **127 / 84** | 4 KiB minus a 32-byte header | +| Mount time | one pass over the metadata | the allocation map is derived, not stored | + +The 64 GiB mount ceiling is the one that bites first, and it is one constant: +raising `SCHFS_SPACE_PAGES` to 4096 makes it 512 GiB at 64 KiB of pointers per +mount. It is set where it is because the page-pointer vector lives inside +`struct schfs_vol`, which the kernel holds as static storage. + +### The rest of Schunix + +These are VFS and driver limits rather than SchFS ones: + +- `MAXPATH` caps a path at **128 bytes**. +- `NMOUNT` allows **8** mounts. +- The IDE driver is 28-bit LBA: **128 GiB** per disk. +- A single `read`/`write` call is capped at **4 GiB** (`n` is a `uint`); the + file itself is not. + +The two that used to be here — a 16-bit inode number in `struct dirent` and a +32-bit `inum`/`size` in `struct inode` — are gone. `getdents64` is a per- +filesystem operation now, so SchFS emits its own records with 64-bit inode +numbers and names up to 255 bytes, and the inode carries 64-bit numbers and +sizes throughout. + +## 8. What exists now + +``` +include/schfs.h on-disk format + compile-time layout assertions +kernel/fs/schfs/btree.{c,h} the copy-on-write B-tree and its cursor +kernel/fs/schfs/schfs_int.h volume + object layer interface +kernel/fs/schfs/space.c allocation bitmap, derived from the tree at mount +kernel/fs/schfs/vol.c uberblock ring: open, format, commit +kernel/fs/schfs/obj.c read side: inodes, dirents, extents, path lookup +kernel/fs/schfs/objw.c write side: create, link, unlink, rename, write +kernel/fs/schfs/kmount.c kernel: block device, mount, transaction bracket +kernel/fs/schfs/vfs.c the fs_ops table: SchFS behind open/read/write +tools/mkschfs.c build an image from a directory tree +tools/schfs_fsck.c verify, list, extract +tools/schfs_test.c B-tree torture harness +tools/schfs_objtest.c object layer: files, directories, space accounting +tools/schfs_roundtrip.sh end-to-end filesystem test +tools/mutate.sh mutation-tests the suites themselves +tools/bootcheck.ps1 boots under QEMU and drives the shell +``` + +Everything except `kmount.c` is compiled *both* into the kernel and into the +host tools, so the writer and the reader cannot drift apart: there is one +implementation of the format and nothing to disagree with. + +### Running the tests + +``` +make schfs-check all three suites +make schfs-test B-tree only +make schfs-objtest object layer only +make schfs-image-test round-trip only +``` + +`make schfs-check` after touching anything under `kernel/fs/schfs`. + +The round-trip's oracle is the host's own filesystem: an image is built from a +directory tree, extracted with a separate program, and `diff -r`'d against the +original. It also asserts hard-link counts (which `diff` cannot see), checks +that rebuilding produces byte-identical output, and runs the whole thing a +second time with the name hash cut to 6 bits so the packed-dirent collision path +— which a 64-bit hash would never reach — is actually exercised. + +`schfs_objtest` runs against a memory-backed volume, so a full +create/write/delete/remount cycle takes milliseconds. Its most important check +is the least glamorous: after deleting everything, free space must be back to +exactly what it was on an empty volume. Every other test in the suite still +passes on a filesystem that leaks a block on every delete. + +`tools/mutate.sh` injects six deliberate bugs and fails if any of them goes +undetected. It is how the suites are kept honest; see the note in §9. + +### Verifying on real hardware + +``` +make ramroot root from RAM, so device 1 is free +make build/schfs.img +make qemu-schfs then, at the shell: cat /proc/schfs +``` + +`.\run.ps1 -SchFS` does the same on Windows, and `tools\bootcheck.ps1 -SchFS` +does it unattended and asserts on the transcript. + +## 9. Things learned the hard way + +Recorded because each one cost real time and none is obvious from the code. + +- **The root's object id must be reserved.** `next_objectid` started at + `SCHFS_FIRST_OBJECTID`, which *is* the root's id, so the first directory + created silently became the root. It then contained itself, and every tree + walk recursed until the stack ran out. `schfs_fsck --check` now detects + directory loops explicitly rather than crashing. +- **Cursor buffers alias.** A range callback gets a pointer into the cursor's + block buffer, and almost every readdir caller immediately looks the entry up, + which overwrites it. Directory iteration copies each entry out before calling + back. Extracting a tree with directories reparented under their own children + is what this looks like when it goes wrong. +- **A 4096-byte block does not fit on a 4096-byte kernel stack.** The cursor + exists precisely to avoid one block buffer per tree level, but + `schfs_vol_open` still kept a whole uberblock as a local and blew the stack on + the first boot. The kstack canary caught it cleanly. +- **Extents only merge if you allocate the run first.** Writing an extent record + allocates blocks for its own copy-on-write path, so allocating data one block + at a time guarantees the blocks are never adjacent and every block needs its + own extent. `schfs_file_reserve` claims the whole run up front; an 822 KB + binary is one extent instead of 201. +- **Freed blocks are reusable during a build.** Copy-on-write forbids reusing a + released block because the previously committed tree still points at it — but + a build has committed nothing. Recycling them cut the test image from 3596 + blocks to 374. +- **Reproducibility dies from atime.** mkschfs reads every source file, which + updates its atime, so copying atime into the image made two builds of the same + tree differ. All three timestamps come from mtime. diff --git a/docs/drivers.md b/docs/drivers.md new file mode 100644 index 0000000..a120c61 --- /dev/null +++ b/docs/drivers.md @@ -0,0 +1,130 @@ +# Drivers + +How hardware gets found, claimed and used, and where drivers that are not part +of the build will attach. + +--- + +## 1. The device model + +There is one list of everything attached, whatever bus found it: +`include/device.h` and `kernel/dev/device.c`, readable at `/proc/devices`. + +``` +name bus class driver device +kbd0 platform input ps2kbd PS/2 keyboard +mouse0 platform input ps2mouse PS/2 mouse +uart0 platform other uart8250 16550 serial port +ide0 platform storage ide ATA disk controller +vga0 pci display vga display controller +``` + +Before this there were two registries in `kernel/drivers/registry.c` — an array +of PCI drivers and an array of USB drivers — with nothing that knew about both. +That is sufficient for *binding* and useless for the three things wanted next. + +**Enumeration.** "What is in this machine, and what claimed it" had no answer +covering a USB disk and a PCI display at once. A device manager is exactly that +question. + +**Classes.** A compositor should ask for *the display* and not care whether that +is the VBE framebuffer or virtio-gpu. Without a class every consumer hardcodes a +driver, and adding the second driver means editing the consumers — which is how +a framebuffer address ended up hardcoded in three places here once already. + +**A boundary for drivers that are not compiled in.** They have to attach +somewhere that is not an array fixed at link time. + +Two vocabularies are kept deliberately separate: a device's **bus** is +provenance (`platform`, `pci`, `usb`), its **class** is capability +(`display`, `input`, `storage`, `net`, `bus`). A USB disk and a SATA disk are +both `storage` despite sharing no numbering at all. Where a driver claims a +device, the driver's opinion wins over the descriptor's: `struct usb_driver` has +a `devclass` field separate from the USB interface class, because the descriptor +says what the device *claims* to be and the driver knows what it *made* of it. + +It is a flat list with a bus tag, **not** a device tree. Nothing yet needs to +walk a hierarchy, and a tree nobody traverses is just a harder list. Hot-unplug +would need more; the `in_use` flag is the hook. + +Platform devices are registered in one place +(`platform_register_devices()` in `registry.c`) rather than inside each driver's +init, for the same reason the driver arrays are written out by hand: a list you +can read beats a list assembled by side effects. + +--- + +## 2. Where this is going: a hybrid model + +The intended shape is the NeXTSTEP/macOS one rather than Linux's — a device is +an object with a class and an ops vtable, and consumers talk to the class. + +Two things often get bundled together as "loadable drivers" and they are +separable: + +| | | +|---|---| +| **Loadable modules** | A stable driver ABI, drivers as files loaded at runtime (`.sys`, `.kext`). A linker/loader job on top of the model above. | +| **Drivers out of kernel** | Drivers as user-space processes (macOS DriverKit, which is why kexts are being retired). An IPC and memory-mapping job; the `AF_UNIX` and shared-memory work it needs already exists. | + +The second is more in the spirit of the SchAPI direction and is slower and +larger. Neither is built yet. What exists is the attachment point they both +need. + +--- + +## 3. Linux drivers + +**Decided 2026-08-08.** Linux drivers are not shipped with Schunix and never +will be. The user may install one on their own machine, through a device +manager or equivalent, and Schunix will load it. + +The licensing reasoning matters and is worth stating exactly, because it is +easy to get wrong in both directions: + +- **Distribution is what triggers the GPL.** Shipping a GPL-2.0-only driver in + an Apache-2.0 image is what cannot be done — see [licensing.md](licensing.md) + §2. A user obtaining a driver themselves and loading it on their own machine + is their act, not ours, and is not a distribution by us. +- **Implementing a loader is not copying.** Writing support for a module format + and a driver ABI from published documentation produces our own work. It is a + clean-room implementation, and it stays one only if it is written from + documentation rather than transcribed from Linux source. Reading Linux to + understand *hardware* is fine; translating its code is not, whatever the + identifiers say. + +So the rule for this subsystem is the same as everywhere else in the tree: the +loader is ours and Apache-2.0, what it loads is the user's business, and nothing +GPL ever enters an image we build. `RAMDISK_EXCLUDE` and `NOTPORTS` are the +existing mechanisms for keeping that true mechanically rather than by intention. + +--- + +## 4. Adding a driver today + +1. Write `kernel/drivers///.c` exporting a + `struct pci_driver` or `struct usb_driver`. One directory per driver, so a + driver that grows a register header, a second source file or a firmware blob + has somewhere to put it: + + kernel/drivers/ + registry.c + gpu/vga/ gpu/virtio_gpu/ + net/e1000/ + bus/xhci/ + usb/core/ usb/hid/ usb/storage/ usb/hub/ + +2. Declare it in `kernel/drivers/registry.c` and add it to the array. +3. Add the object to `KERN_DRIVERS` in the `Makefile`. + +Registration in the device model is automatic — `pci_bind()` and +`usb_bind_all()` publish whatever they claim, so a driver cannot forget to. Set +`devclass` on a USB driver, or the device lands in `other`. + +The best source for a new driver is the **specification**, not another kernel: +xHCI, AHCI, virtio, NVMe and the Intel datasheets are public documents with no +licence on what you write from them. `kernel/drivers/bus/xhci/xhci.c` came from the spec, and so did +`net/e1000/` and `gpu/virtio_gpu/`. Where a *table* of constants is genuinely +faster to transcribe than to retype from a PDF -- device IDs, register offsets -- +taking it from a BSD tree is fine after the licence check, and goes in `NOTICE`. +That is data; the logic stays ours. diff --git a/docs/layout.md b/docs/layout.md new file mode 100644 index 0000000..6b39196 --- /dev/null +++ b/docs/layout.md @@ -0,0 +1,175 @@ +# Repository layout + +One rule holds the whole thing together: + +> **A directory contains either hand-written source or generated output. Never both.** + +Everything generated lives under `build/`. Delete `build/` and you lose nothing +that can't be rebuilt (except upstream tarballs, which live in `dist/` and are +deliberately kept out of `clean`). + +``` +schunix/ +├─ boot/ Stage 1 MBR + stage 2 loader (splash, F8 menu, kernel load) +├─ kernel/ The kernel, by subsystem: arch/ core/ dev/ fs/ proc/ sys/ +├─ include/ Headers shared by kernel, userland, and tools/mkfs +├─ lib/ First-party userland runtime: _start glue, syscall stubs, +│ the initcode blob embedded in the kernel, user.ld +├─ cmd/ First-party programs — one directory per program: +│ cmd//main.c → sysroot/bin/ +├─ ports/ Third-party software — one directory per port. +│ Contains ONLY a Makefile and optional patches/. +├─ projects/ Things that run ON Schunix rather than things that ARE it: +│ the compositor, window manager, desktop, and installer. +│ Not part of the kernel build. See projects/README.md. +├─ tools/ Host-side helpers, SOURCE ONLY: mkfs.c, the SchFS test +│ suites, sign.pl, vectors.pl, and the PowerShell harnesses. +│ Their binaries build into build/tools/ -- this directory +│ used to hold both, which is the one rule this layout has. +│ tools/loc.ps1 counts how much of the tree is first-party. +├─ sysroot/ Staging for the disk image. etc/ is hand-maintained; +│ bin/ lib/ sbin/ are populated by `make sysroot-install`. +├─ dist/ Upstream tarball cache. Survives `make clean`. +├─ Refrence/ A FreeBSD checkout (Refrence/freebsd-src), kept as reference +│ material and a parts bin. Never built, never in an image. +│ Read it when a specification is ambiguous; copy from it only +│ after the per-file licence check in docs/licensing.md. +│ A full clone is 2.1 GB and a previous NetBSD one was deleted +│ for that reason. Only sys/dev, sys/net and sys/netinet have +│ ever been read, so a sparse checkout is ~100 MB: +│ git clone --filter=blob:none --sparse Refrence/freebsd-src +│ git -C Refrence/freebsd-src sparse-checkout set │ sys/dev sys/net sys/netinet +├─ src-trunk/ A NetBSD checkout, kept as reference material and a parts +│ bin. NOT built, NOT installed, and it must never reach an +│ image. Copying from it is governed by the per-file licence +│ check in docs/architecture/schapi.md §5 — NetBSD is not +│ uniformly licensed. +├─ docs/ Design docs +└─ build/ ALL generated output (see below) +``` + +## build/ + +``` +build/ +├─ boot/ bootblock, stage2 +├─ kernel/ object files, the linked kernel, kernel.asm +├─ lib/ libc glue objects, initcode blob +├─ cmd/ linked first-party programs +├─ fs.img the filesystem image +└─ ports// + ├─ src/ extracted upstream tarball + ├─ obj/ out-of-tree build directory + └─ prefix/ install prefix (musl's headers and libs land here) +``` + +## Adding your own program + +```sh +mkdir cmd/hello +$EDITOR cmd/hello/main.c # include "types.h", "user.h", ... +``` + +Then add `hello` to `CMDS` in the root `Makefile`. It gets linked against +`lib/` and installed to `sysroot/bin/hello` by `make sysroot-install`. + +## Adding a third-party port + +```sh +mkdir ports/foo +$EDITOR ports/foo/Makefile # start with `include ../mk/port.mk` +``` + +`port.mk` derives everything from the directory name — it gives you `$(SRCDIR)`, +`$(BUILDDIR)`, `$(PREFIX)`, `$(DISTDIR)`, `$(SYSROOT)`, and `$(MUSL)` (the +musl install prefix that every port links against). Follow the pattern in +`ports/mksh/Makefile`: fetch tarball to `$(DISTDIR)`, extract to `$(SRCDIR)`, +build into `$(BUILDDIR)`, install into `$(SYSROOT)`. `clean` removes +`$(PORTDIR)` and nothing else. + +`ports/Makefile` picks up the new directory automatically. If it links against +musl, add `foo: musl` there so ordering is right. + +Local modifications to upstream go in `ports/foo/patches/*.patch` and are +applied after extraction — never by editing files under `build/`, which are +wiped on every clean. + +## Ramdisk root + +```sh +make # normal: root on the IDE disk (build/fs.img as hdb) +make ramroot # root from a SCHFS image linked into the kernel +``` + +`make ramroot` builds `build/fs-ram.img` (default 6000 blocks, override with +`RAMDISK_BLOCKS=`) and links it into the kernel, so the system boots to a shell +with **no data disk attached at all**. That is what makes destructive work on +the real disk survivable — reformatting it, or bringing up a new on-disk format +— and it is the first piece of the installer. + +Use `make ramroot`, not `make RAMROOT=1`: the flag changes `CFLAGS`, which make +cannot see, so objects compiled for one setting would be silently reused for +the other and `ROOTDEV` would point at the wrong device. The target drops the +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 +build/cmd` first (or just `make clean`). + +The image costs boot time: stage2 loads the kernel by PIO one sector at a time, +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 +linked into the kernel binary, so anything in `sysroot/` when it is built becomes +part of a distributable artifact. A file staged there under a licence that +cannot be combined with Apache-2.0 does not merely ride along — it makes the +kernel itself undistributable. `RAMDISK_EXCLUDE` in the Makefile is the list of +paths kept out, and enforcing it in the build rather than relying on someone +remembering to unstage something first is deliberate. It has been needed once +already: a 33 MB test payload landed in `sysroot/` and both overflowed the image +and would have gone into the kernel binary. + +Three knobs, all in the Makefile, none of them `FSSIZE`: + +| | | +|---|---| +| `RAMDISK_BLOCKS` | image size, 6000 (× 1 KiB `BSIZE`) | +| `RAMDISK_NINODES` | inode table size, 2000 | +| `RAMDISK_EXCLUDE` | sysroot-relative paths to omit, with their subtrees | + +`RAMDISK_NINODES` is worth knowing about because the inode table is +*preallocated*: mkfs's default of 12000 inodes cost 1501 of the 6000 blocks — a +quarter of the image — to describe about 260 files. At 2000 it costs 251, and +metadata overall fell from 1596 blocks to 345. + +If `mkfs` reports the image is full, read its message: it now prints the +metadata breakdown and names the right knob for whichever image overflowed. It +used to say only "increase FSSIZE in param.h", which is a constant that has +nothing to do with the ramdisk root and sends you to the wrong file. + +## Two things that will bite you + +**`ld -r -b binary` derives symbol names from the input file's path.** The +kernel embeds two blobs this way (`initcode`, `entryother`), and the root +Makefile has `--redefine-sym` lines mapping e.g. +`_binary_build_lib_initcode_start` → `_binary_initcode_start`. Move either +blob's build location and those symbol names change; update the Makefile to +match or the kernel fails to link. + +**`STAGE2_SECTORS` now lives in exactly one place: the root `Makefile`.** It used +to be written out by hand in five — `boot/bootasm.S`, `boot/stage2main.c`'s +`readseg()`, the `dd ... seek=` offset, and twice more inside `tools/mkiso.sh` +(its stage2 size assertion and `-boot-load-size`). This note used to warn you to +keep three of them in step and did not know about the other two. + +Raising it from 24 to 48 for the boot logo demonstrated the problem: the `dd +seek=25` offset stayed behind, so the kernel was written directly over stage2's +own tail. Now the Makefile passes `-DSTAGE2_SECTORS` to both boot sources, +derives the padding budget and the `seek=` offset from the same variable, and +exports it to `mkiso.sh`. The `#define`s that remain in the two boot sources are +`#ifndef`-guarded fallbacks so those files still read sensibly alone; the +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/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`. diff --git a/docs/licensing.md b/docs/licensing.md new file mode 100644 index 0000000..b3a4df4 --- /dev/null +++ b/docs/licensing.md @@ -0,0 +1,241 @@ +# Licensing + +The one page to check before copying anyone else's code into Schunix. + +Referenced from `LICENSE`, `LICENSE.MIT` and `NOTICE`, all three of which +pointed here long before this file existed. + +--- + +## 1. Schunix is Apache-2.0 + +Copyright Nexoniarz. See `LICENSE`. + +Apache-2.0 was chosen over MIT/BSD for one reason that actually matters at OS +scale: it contains an **express patent grant** and a patent-retaliation clause. +MIT and BSD are silent on patents, which means a contributor can hand you code +and later assert a patent over it. For a kernel that is intended to accept +outside contributions, that silence is the expensive kind. + +What that grant means in practice: Apache-2.0 §2 gives everyone a perpetual, +worldwide, irrevocable licence to reproduce, modify, sublicense and distribute +Schunix. Any additional restriction stated elsewhere — in a README, a website, +or a policy — does not narrow it. The licence text is what holds up. + +If restricted distribution is ever genuinely wanted, that is a licence change +(a bespoke proprietary licence, or something like BUSL-1.1 with a conversion +date), not a wording change, and it means giving up the Apache grant. + +--- + +## 2. What can be copied in + +| Licence | Verdict | Obligation | +|---|---|---| +| Public domain, 0BSD, CC0 | **Yes** | None | +| MIT, ISC | **Yes** | Keep the notice | +| BSD 2-clause | **Yes** | Keep the notice | +| BSD 3-clause | **Yes** | Keep the notice; no endorsement claims | +| BSD **4-clause** (advertising) | **Avoid** — see §3 | Notice *and* an acknowledgement in advertising materials | +| Apache-2.0 | **Yes** | Keep NOTICE contents | +| MPL-2.0 | Case by case | File-level copyleft; keep those files separate | +| **GPL-2.0-only** | **No** | — | +| **GPL-3.0 / LGPL-3.0** | **No** for linked code | — | + +**Every "yes" above requires an entry in `NOTICE`.** That file is the project's +attribution record and the thing that makes the permissive licences actually +satisfied rather than merely tolerated. + +### Why GPL-2.0-only is a hard no + +Apache-2.0 imposes conditions GPLv2 treats as "further restrictions" under its +§6 — principally the patent-retaliation term. The FSF and the ASF agree on +this: **Apache-2.0 code and GPL-2.0-only code cannot be combined into one +distributable work.** It is not a matter of taste or of how carefully the files +are kept apart in the tree; it is that the combined binary cannot satisfy both +licences at once. + +GPL-3.0 fixed this in the other direction (Apache-2.0 is compatible *into* +GPLv3), but that does not help: the result would have to ship under GPLv3, and +Schunix does not. + +**Practical consequence:** BSD drivers can be adopted. Linux drivers cannot, +ever, no matter how convenient. This is why the USB stack was written from the +specification rather than ported. + +--- + +## 3. NetBSD in particular + +`src-trunk/` is a NetBSD checkout kept as reference material and a parts bin +(see [architecture/schapi.md](architecture/schapi.md) §5). NetBSD is **not +uniformly licensed**, and the difference matters per file: + +| Template | Status | +|---|---| +| 2-clause BSD — modern NetBSD Foundation files | **Fine** | +| 3-clause, "Regents of the University of California" | **Fine.** Berkeley formally rescinded the advertising clause in 1999 | +| 4-clause with the advertising clause, **non-UC** copyright holder | **Avoid.** Berkeley's rescission covers Berkeley's copyright, not other people's | + +The last row is real, not hypothetical. Measured on this checkout: + +``` +sys/kern 26 of 220 files carry the advertising clause +sys/dev 500 of 3565 +lib/libc 101 of 1575 +sys/net 5 of 180 ← the TCP/IP stack is nearly clean +``` + +4-clause is still permissive and still redistributable; the burden is an +advertising obligation that accumulates quietly and is tedious to honour +properly. Prefer a 2-clause or UC-Regents file. If a 4-clause file is genuinely +the right answer, it goes into `NOTICE` with the full clause quoted. + +Check before copying, not after: + +```sh +grep -l "All advertising materials" +``` + +### And check *whose* clause it is + +A file can carry several licence blocks from several copyright holders. Finding +the advertising clause is only half the check — Berkeley's rescission covers +Berkeley's copyright and nobody else's, so the question is always *whose* +clause you found. + +Measured on `src-trunk`, the networking tree: + +``` +sys/net 106 files, 5 with the advertising clause +sys/netinet 121 files, 25 " " " +sys/netinet6 51 files, 5 " " " +``` + +Encouraging at first glance — the 25 in `sys/netinet` are almost all SCTP, +DCCP and multicast routing, none of which Schunix needs. `ip_input.c`, +`ip_output.c`, `udp_usrreq.c`, `if_arp.c`, `ip_icmp.c`, `if.c` and `route.c` +are all UC 3-clause, i.e. fine. + +**But `tcp_input.c` and `tcp_output.c` are not.** Their advertising clause +belongs to the **WIDE Project** (the KAME IPv6 people), whose copyright sits at +the top of the file; the UC block is a separate licence further down. Berkeley +never spoke for WIDE, so the rescission does not reach it. The two most +valuable files in the stack are the two encumbered ones. + +That is not fatal — 4-clause is still permissive — but it is exactly the case +where a **second BSD tree earns its disk space**: FreeBSD's TCP has been +through a relicensing effort NetBSD's has not, so the same file there may well +be 2-clause. Check it the same way before assuming either: + +```sh +grep -n "Copyright" | head # who claims it +grep -n -B6 "All advertising" # whose clause is it +``` + +### FreeBSD's TCP is clean where NetBSD's is not + +Checked, 2026-08-08, exactly that way. Against FreeBSD `main`: +`sys/netinet/tcp_input.c` contains **no advertising clause at all**. Its +copyright holders are the UC Regents, Swinburne University, Lawrence Stewart, +the FreeBSD Foundation and Juniper Networks -- all BSD-2 or BSD-3. + +So if BSD networking code is taken, it must come from **FreeBSD, not NetBSD**, +and the specific reason is TCP. This reverses the earlier assumption in +`docs/roadmap.md` that NetBSD was the intended starting point; NetBSD remains +the better-written reference to *read*, but FreeBSD is the one that can be +copied from. + + +### The third option: lwIP + +Before porting either BSD stack, weigh **lwIP** against both. Verified +2026-08-08 from its `COPYING`: 3-clause BSD, Swedish Institute of Computer +Science, **no advertising clause anywhere in the file**. Compatible, one NOTICE +entry, done. + +The licence is the smaller half of the argument. The larger half is that +FreeBSD's `sys/netinet` is not a library -- `tcp_input.c` alone is 122 KB and +reaches into mbufs, `epoch(9)`, netisr, VNET, `uma(9)`, sockbufs, inpcb hash +tables and FreeBSD's locking. Taking it means porting FreeBSD's kernel +infrastructure first and finding the TCP somewhere underneath, which is the same +conclusion `docs/roadmap.md` §1 already reached about `sys/dev/usb`. + +lwIP was written for the case Schunix is in: a small operating system with a raw +Ethernet driver and no infrastructure to speak of. Its port layer is a handful +of files, it has no dependency on the host kernel's allocator or locking, and it +brings ARP, IPv4, ICMP, UDP, TCP, DHCP and DNS. + +### Fonts + +Chosen 2026-08-09, both licences checked at source rather than assumed: + +| Font | Where | Licence | +|---|---|---| +| Roboto | desktop, window manager, compositor | **Apache-2.0** | +| JetBrains Mono | terminal, TTY | **SIL OFL 1.1** | + +Roboto is the same licence as Schunix, so it is the easy case: a NOTICE entry +and nothing else. + +OFL is permissive and bundling is precisely what it is for, with two conditions +worth knowing before anyone subsets or converts a font: + +- **The font stays under OFL** even inside an Apache-2.0 system. It does not + infect anything else; it just cannot be relicensed. +- **It may not be sold on its own.** Shipping it as part of an OS is fine. +- **Reserved Font Names.** JetBrains Mono declares *none* -- its copyright line + names no reserved name -- so a modified version may keep the name. Most OFL + fonts do reserve theirs, and then a derivative must be renamed. + Pre-rasterising a font into bitmaps at build time **is** a modification, so + this is not a theoretical point for this project. + +--- + +## 4. Things that are deliberately not shipped + +Some code is fine to *build and run locally* and impossible to *distribute* +with Schunix. Keeping the distinction sharp is what stops it eroding: + +- **`Refrence/`** — a FreeBSD checkout (`Refrence/freebsd-src`), kept as + reference material and a parts bin. Never built, never in an image. The + directory name is spelled as it is on disk; renaming it is a one-line change + if it ever grates. +- **`src-trunk/`** — a NetBSD checkout, same rules. Currently absent; it was + deleted for disk space and can be re-fetched sparsely if needed. +- **`dist/`** — upstream tarballs and other downloaded blobs. Cached so a + rebuild never needs the network; not part of any image, and not all of it is + redistributable. + +The mechanisms that keep this from eroding, since good intentions have already +failed once here: + +- `NOTPORTS` in `ports/Makefile` — a port that lives in the tree but is never + built by `make`. +- `RAMDISK_EXCLUDE` in the root `Makefile` — paths kept out of the ramdisk root. + This one matters more than it looks: the ramdisk image is linked *into the + kernel binary*, so anything sitting in `sysroot/` when that image is built + becomes part of a distributable artifact. A GPL-2.0 or non-redistributable + file staged there does not merely ride along, it makes the kernel itself + undistributable. + +A previous acceptance test — a ported 1993 shooter, GPL-2.0 engine on +non-redistributable shareware data — was carried under exactly these rules and +has since been removed. It is worth recording *why* it was safe: it was never in +`PORTS`, and its 33 MB of data was excluded from the ramdisk. It is equally +worth recording that the safety depended on two separate opt-outs staying +correct, which is a lot of standing risk for a test that had stopped finding +bugs. See `docs/roadmap.md` Phase 7. + +--- + +## 5. Specifications are not code + +A published specification carries no copyright over an independent +implementation of it. The xHCI, USB, PCI, fbdev and ELF specifications were all +read and implemented directly, which is why those drivers have no licence +entanglement at all despite equivalents existing in both Linux and BSD. + +When a subsystem is small enough to write from the spec, that is usually the +cheapest option in the long run — not because copying is hard, but because a +copied file is a file to be understood, attributed and maintained forever. diff --git a/docs/roadmap.md b/docs/roadmap.md new file mode 100644 index 0000000..c29fa10 --- /dev/null +++ b/docs/roadmap.md @@ -0,0 +1,481 @@ +# Roadmap + +Written 2026-08-04, from the plan: USB, GPU, fonts, networking and sockets, +then a compositor (Schcom), a window manager (SchWM) and a desktop (SchDE), +plus an installer and a `.sip` package format. + +This file records two things that are easy to lose: the *order* the work has +to happen in, and the decisions that constrain it. + +**Superseded in direction, not in order.** Schunix is now aimed at being a +*system* rather than a Unix-like kernel with a userland attached: a NeXTSTEP/ +macOS-shaped layering, with POSIX kept complete but demoted from identity to +personality, and a native API (**SchAPI**) above it. See +[architecture/schapi.md](architecture/schapi.md). That decision changes what +the phases below are building towards; it does not reorder them, and it does +not start until the tree builds and boots natively again. + +That assumption about §1 has since been revised. NetBSD's TCP/IP stack was the +intended starting point; the per-file licence check found that NetBSD's +`tcp_input.c` and `tcp_output.c` carry the WIDE Project's 4-clause advertising +clause, which Berkeley's rescission does not reach. FreeBSD's equivalent is +clean, and lwIP is cleaner still and a far smaller port. See Phase 2 below and +docs/licensing.md §3. + +--- + +## 1. Where drivers can come from + +**BSD: yes. Linux: no.** This is not a preference, it is a licence +incompatibility, and it decides the whole driver strategy. + +The reasoning — Apache-2.0's patent terms counting as "further restrictions" +under GPLv2 §6, which is why the two cannot be combined and redistributed — is +in [licensing.md](licensing.md), along with the per-licence table and the +NetBSD-specific rules. Read it before copying anything. The short version: +BSD-2 and BSD-3 code can be used directly with a `NOTICE` entry; GPL-2.0-only +code can never be, at any distance. + +There is a third source that is better than either: **the specifications**. +xHCI, AHCI, virtio, NVMe, the Intel e1000 and i915 datasheets are all public +documents with no licence attached to what you write from them. That is where +`kernel/drivers/xhci.c` came from. + +One caveat about "port it from BSD" as a plan, learned by looking: FreeBSD's +`sys/dev/usb` is roughly 100k lines welded to newbus, devclass, `bus_dma`, +callouts, taskqueues and FreeBSD's locking. Porting it means porting +FreeBSD's device framework first and finding the USB stack somewhere +underneath. NetBSD's is more portable and the same argument applies with a +smaller multiplier. **BSD is far more valuable as the reference you check +yourself against than as the code you paste.** Read it when the spec is +ambiguous — which it is, often — and write your own. + +Reading Linux for *understanding* is fine and normal. Translating a Linux +driver function-by-function is not; the result is a derivative work whatever +the variable names say. + +--- + +## 2. Ordering, and why it is not the order it was asked in + +The requested order was USB → GPU → fonts → network → GUI → installer. That +is a reasonable order by *interest*. By dependency it wants one change: + +**Shared driver infrastructure comes before all of it.** USB, any GPU beyond +the VBE framebuffer, and any NIC all need the same three things, none of +which existed: + +- a way to register an interrupt handler at runtime (`kernel/dev/irq.c`) +- physically contiguous memory for rings and descriptors + (`kernel/core/dma.c`) +- PCI capability walking and MSI (`kernel/dev/pci.c`) + +That work is done. It was the first step of USB, not a detour from it. + +The other honest note: **USB is the item that does least for the GUI.** Under +QEMU there is already a PS/2 keyboard and mouse, so USB buys real hardware +support — which matters, but not for getting Schcom on screen. Sockets and +raw input events are what actually block the compositor. USB is being done +first because it was asked for first; the ordering below reflects that with +the dependencies fixed. + +--- + +## 3. The plan + +### Phase 1 — USB *(done, bar the boot path)* + +- [x] Shared infrastructure: IRQ registration, DMA arena, PCI capabilities +- [x] xHCI: reset, DCBAA, command ring, event ring, scratchpad +- [x] Port enumeration, Enable Slot, Address Device, control transfers +- [x] Device descriptors read from real devices (USB2 and USB3 paths) +- [x] Configuration and interface descriptor parsing, `SET_CONFIGURATION` +- [x] Interrupt endpoints, driven from the IRQ handler +- [x] HID boot protocol: keyboard and mouse into the existing input paths +- [x] Bulk endpoints, bulk-only transport, SCSI, a block device at + `USBDISK_DEV` — a superblock reads back through `bread()` +- [x] Hubs — route strings, hub slot contexts, per-port reset and power. A + mouse behind a hub enumerates a tier deep, which is what proves it +- [ ] Making a USB disk mountable and bootable (needs the root device to be + selectable rather than compiled in) + +### Phase 2 — Sockets, raw input, and networking + +Do sockets **before** the network stack. **Unix domain sockets are what Schcom +needs** — clients and compositor have to pass messages and share buffers, and +a pipe cannot carry an shm handle. Unix sockets are also far simpler than +TCP/IP, so they are the right thing to build the socket layer's shape around. + +- [x] `socket`/`bind`/`listen`/`accept`/`connect`/`send`/`recv` syscalls, at + their real Linux numbers — they were `NO_LINUX` stubs, so a musl program + calling `socket()` never even reached the stub +- [x] `AF_UNIX` SOCK_STREAM, `socketpair`, `shutdown`; names live in a table + rather than the filesystem (see the comment at the top of + `kernel/net/unix.c` for what that costs) +- [x] Raw input events — `/dev/input/event0` and `event1`, Linux evdev layout, + fed by the PS/2 keyboard and mouse and by USB HID. The console keeps its + own path; both consumers see every key +- [ ] `SCM_RIGHTS` for passing descriptors. Possibly unnecessary: an shm id is + an integer and travels fine over a stream socket, so this waits until + something actually needs to pass an open file +- [x] e1000 driver — `kernel/drivers/net/e1000.c`, from Intel's public manual + 317453. MAC from EEPROM, transmit and receive rings, link detection. + Run with `-netdev user,id=n0 -device e1000,netdev=n0`. + **Verified to carry real traffic**: `echo n > /proc/sysrq` sends a + hand-built ARP request and receives the gateway's reply, which exercises + the descriptor formats, the DD handshake, the tail bookkeeping, the DMA + addresses and the MAC filter in one exchange. That check came before any + protocol stack on purpose -- a stack on an unproven driver fails in ways + indistinguishable from its own bugs +- [ ] ARP, IPv4, ICMP, UDP, then TCP. **Not written from scratch** -- decided + 2026-08-08. Writing TCP is the one part of this where doing it yourself is + a bad trade: it is not the packet format that is hard, it is congestion + control, retransmission, window management and twenty years of accumulated + corner cases that only appear against a real peer. + **Preferred base: lwIP** (BSD-3, no advertising clause, verified). It was + written for exactly this situation -- a small OS with a raw Ethernet + driver -- and its port layer is a handful of files. + **If BSD instead: FreeBSD, not NetBSD.** NetBSD's `tcp_input.c` carries + the WIDE Project's 4-clause advertising clause; FreeBSD's has none. But + FreeBSD's `sys/netinet` is not a library -- it reaches into mbufs, + epoch(9), netisr, VNET and uma(9), so taking it means porting FreeBSD's + kernel infrastructure first. See docs/licensing.md §3. +- [ ] `AF_INET` on top of the same socket layer + +### Phase 3 — GPU + +"Basic GPU driver" has a right answer and a wrong one. The wrong one is +starting with Intel or AMD hardware: modern GPU drivers are enormous and most +of the size is mode setting, power management and a command submission model +that only pays off once there is something to submit. + +- [x] **virtio-gpu**, 2D. `kernel/drivers/virtio_gpu.c`, written from the OASIS + specification. Full path: GET_DISPLAY_INFO, RESOURCE_CREATE_2D, + RESOURCE_ATTACH_BACKING, SET_SCANOUT, TRANSFER_TO_HOST_2D, + RESOURCE_FLUSH. Bound as the display class ahead of the VBE framebuffer. + Run with `-vga std -device virtio-gpu-pci`; keep `-vga std`, because the + bootloader's splash goes through VBE and there is no virtio path that + early +- [ ] A `/dev/dri`-style device with mode setting, so resolution stops being + whatever the bootloader asked VBE for +- [ ] Double buffering and vblank — Schcom needs both to not tear +- [ ] Only then: real hardware, and only 2D/blit acceleration + +### Phase 4 — Fonts + +This is a bigger job than it sounds and it should be sized honestly. A TTF/OTF +rasteriser needs: `cmap`/`glyf`/`loca`/`head`/`hmtx` table parsing, quadratic +béziers for TrueType outlines and *cubic* ones for OpenType/CFF, a scanline +rasteriser with anti-aliasing, and a glyph cache. + +The bigger structural problem is that the console assumes a fixed 8×16 cell +grid throughout — `vga_putc`, the cursor, the ANSI erase operations. Variable +width text is not a font change, it is a text-rendering layer. + +- [x] **The text layer** — `include/text.h` + `kernel/dev/text.c`. Glyph + metrics, a `struct surface` target so a compositor can draw off-screen, + 8-bit coverage with real blending, UTF-8, and `font_ops` so a rasteriser + plugs in as a second provider. This was the actual blocker: the font was + never the problem, the character-cell grid was. +- [ ] Port the console onto it and delete the cell grid +- [x] A TrueType rasteriser — `kernel/dev/ttf.c`, glyf outlines, composite + glyphs, cmap format 4, anti-aliased scanline fill. Host harness at + `tools/ttftest.c` +- [x] A glyph cache — direct-mapped over ASCII into a 48 KB pool +- [ ] Load a font from disk and point the console at it, deleting the cell grid +- [ ] CFF/Type 2 charstrings, so OTF fonts work too (a separate `font_ops` + provider; a bytecode interpreter, not an extension of the glyf path) +- [ ] A rasteriser. `stb_truetype.h` is public domain and therefore usable + as-is if wanted; writing one is also reasonable and more interesting +- [ ] **The typefaces, decided 2026-08-09.** Both verified, not assumed: + **Roboto** for the desktop, window manager and compositor -- + **Apache-2.0**, the same licence as Schunix, so it needs nothing beyond a + NOTICE entry. + **JetBrains Mono** for the terminal and TTY -- **SIL OFL 1.1**, and + notably with *no* Reserved Font Name declared, so it may be modified and + still called JetBrains Mono. That matters more than it sounds: + pre-rasterising a font into bitmaps at build time is a modification, and + an RFN would have forbidden keeping the name on the result. + OFL's one real constraint is that the font may not be sold on its own and + must stay under OFL; bundling it in an Apache-2.0 system is exactly the + case it is written for. +- [ ] Text layout (advance widths, kerning) as a library in `lib/` +- [ ] The console keeps its fixed grid; proportional text belongs to the GUI + +**Font licensing: public domain or very permissive only.** No SIL OFL if it +can be avoided, no "free for personal use", no CC-BY-SA. The constraint is +the same one that governs drivers (§1): a font file is redistributed inside +every Schunix image, so its licence attaches to the image. + +Safe choices, in rough order of usefulness: + +- **CC0 / public domain** — best case, nothing to comply with. +- **MIT / BSD / Apache-2.0 fonts** — fine, needs a `NOTICE` entry. +- **SIL OFL** — usable but has a real string attached: a modified font must + be renamed, and OFL fonts cannot be sold on their own. Workable, but read + it before committing one. + +Whatever is chosen, the licence goes in `NOTICE` **before** the file is +committed, not after. + +### Phase 5 — The GUI, as separate projects + +See `projects/README.md`. Schcom, SchWM and SchDE live outside the kernel +tree on purpose. + +- [ ] **Schcom** — the compositor. Owns the framebuffer, composites client + buffers, routes input. Needs: unix sockets, shm (done), raw input + events, and a GPU driver with double buffering. +- [ ] **SchWM** — window management on top of Schcom's protocol +- [ ] **SchDE** — panel, launcher, settings + +Raw input is the missing kernel piece nobody thinks of until it bites: +keyboard events currently only arrive through the tty line discipline, cooked +and line-buffered and delivered to one foreground process. A compositor needs +key-down/key-up with modifiers and no line discipline at all — an evdev-style +`/dev/input`. + +### Phase 6 — Installer and `.sip` + +`.sip` (Schunix Installation Package), the stated model being WIM in Windows +PE: a *file-based* image rather than a block image, so one file can hold a +whole filesystem, be applied to a partition of any size, and hold several +images that share their common files. + +The pieces for this mostly exist already. `tools/mkschfs.c` does content +dedup with an `(st_dev, st_ino)` fast path; SchFS's B-tree is content +addressed. A sensible `.sip` is: a header, a file index (path, metadata, +content hash), and a blob store keyed by hash, with compression per blob. + +- [ ] `.sip` format specification, written down before any code +- [ ] `tools/mksip` — capture a directory tree into a `.sip` +- [ ] Kernel or userspace apply path +- [ ] An installation environment that boots from the ISO, partitions, + applies the `.sip`, and installs the bootloader + +--- + +### Phase 7 — The full-stack acceptance test *(served its purpose; removed)* + +For a while this phase was a ported copy of a well-known 1993 shooter, kept as +an acceptance test rather than a feature. The reasoning was sound and is worth +keeping even though the test is gone: a large program that somebody else wrote, +which exercises `/dev/fb0`, `/dev/input/event0`, `mmap`, the ELF loader, +`gettimeofday` and a fixed-rate tic loop *simultaneously* and notices when any +of them lie, is a much harsher test than anything written in-house — because it +was not written against our bugs. + +It found real ones, all of which are fixed and covered by narrower tests now: + +- `fcntl(F_SETFL, O_NONBLOCK)` was a documented no-op, so the first read blocked + forever and looked exactly like a hung renderer. +- `/dev/input/eventN` replayed its backlog to each new opener, handing a program + the keystrokes that had launched it as input. +- Without `EVIOCGRAB`, a full-screen program and the shell behind it both read + the same keystrokes. + +It was removed once it stopped finding anything: the port was ~100k lines of +third-party source under licences that can never ship in an Apache-2.0 image, +and carrying it had a standing cost — 33 MB of non-redistributable data staged +into `sysroot/`, which is linked into the kernel binary for a ramdisk-root +build. That combination is a licensing accident waiting to happen, for a test +that was passing every time. + +**What replaced it:** `tools/bootcheck.ps1` with its `-Sock`, `-Evdev` and `-Ps` +modes, plus `tools/splashshot.ps1` for the boot path. These are narrower and do +not prove the whole stack composes, which is the honest cost of the removal. + +**The lesson worth keeping** is the shape of the test, not the program: the next +full-stack test should be something large that we did not write, driven to a +real pixel-level assertion rather than a log line. `tools/splashshot.ps1`'s +colour counting exists because a log-only harness once passed on a blank screen. + +### Phase 8 — The system layer: `schinit`, firmware, and a crash channel + +Three ideas parked here rather than started, because each one is a project and +the phases above are the agreed order. Recorded so they are not re-derived from +scratch later. + +**`schinit`, a launchd-shaped init.** The point of copying launchd rather than +sysvinit is not the config format, it is *demand launching*: launchd holds the +listening socket or watches the path, and starts the job when something actually +arrives. That inverts the dependency graph — jobs stop needing to declare what +must start before them, because the thing they depend on answers on first use. +Schunix already has the two pieces that makes possible: `AF_UNIX` sockets +(`kernel/net/unix.c`) and a real `/proc`. What is missing is a supervisor that +can hold a socket on a job's behalf and hand it over on exec, plus a job +description format. Prerequisite for this being worth anything: `ps` and `top` +must be able to enumerate processes, or the supervisor is unobservable. + +**A crash screen you can work in, and a QR code channel out of it.** PSOGM +(`kernel/core/panic.c`) currently renders and stops. The idea is two staged +things, and the staging matters: + +- *An interactive mode — "a small OS inside the crash".* This is the right + framing and it is worth stating precisely, because it decides the design. At + panic time the kernel is the only thing running, which is exactly why it can + offer what a live system cannot: walk the stack, read another process's + memory, dump page tables, inspect the process table — with no scheduler and no + locking to respect, because nothing else can race you. + + What makes it a *system* rather than a print statement is that it keeps the + base machine's drivers. Concretely, the ones that work with interrupts off: + the VGA/framebuffer (`vga_draw_pixel` is a plain memory write), the PS/2 + keyboard (pollable via port 0x60/0x64), and the serial port. Those three are + a complete console. Everything else — IDE, USB, the timer, anything that + sleeps or takes a sleeplock — is off limits, because the lock it needs may be + precisely the one held by the CPU that just died. + + So the shape is a **polled event loop over preallocated state**: no `kalloc`, + no `sleep`/`wakeup`, no `acquire` on anything a live path might hold, one + static command buffer, and a driver set restricted to the three above. That + restriction is not a limitation to work around later; it is the invariant that + makes the mode trustworthy when everything else is not. Anything that wants + more should be answered by rebooting into a recovery environment instead. + + A useful precedent to read but not copy: DDB (NetBSD/FreeBSD's in-kernel + debugger) is exactly this, and its hard-won rule is the same one — a debugger + that can deadlock is worse than no debugger. +- *A QR code.* **Done.** `kernel/core/qr.c` (byte mode, EC level L, versions + 1–6, no allocation) and a card on the right of the crash screen. A panic + report is worth nothing if it cannot leave a machine whose network stack is + dead, and a phone camera is an out-of-band channel needing no driver on our + side. Deliberately *not* a URL: it encodes the report itself and reaches + nothing. + + It ended up unconditional rather than behind Enter, which is better — it + costs nothing to always draw, and requiring a keypress would mean the one + case where input is least trustworthy is also the case where the report is + hardest to retrieve. Verified end-to-end by `make crashshot && make crashqr`, + which decodes the symbol out of the captured framebuffer. + +**UEFI.** Not a feature so much as the point at which the current bootloader +stops being extendable: it is 16-bit real mode, INT 10h VBE, and PIO ATA, none +of which exist on a UEFI-only machine. The realistic shape is a *second* +loader — an EFI application that gets the framebuffer from GOP, the memory map +from `GetMemoryMap()`, loads the kernel off a FAT ESP, and enters at the same +place `boot/stage2main.c` does — rather than a port of the existing one. Worth +noting that the splash work just done survives that transition: it is a mask, a +palette and a layout, none of which care how the pixels were found. What does +not survive is every assumption in `stage2main.c` about where things live in low +memory (see `docs/layout.md`). + +### SchFS beyond Schunix + +Three ideas parked 2026-08-08, in the order they should happen. + +**An on-disk format version, and an upgrade path.** This one is not optional and +gets harder every day it is postponed: the moment an image exists that someone +cares about, changing the layout means being able to read the old one. The +superblock already carries a version field and a CRC32c; what is missing is a +*policy* — a documented rule for what a version bump means, a refusal to mount +something newer than the driver understands (rather than misreading it), and an +explicit upgrade step. Cheap now, expensive later, and it blocks the other two: +a second implementation is only meaningful against a format that is pinned. + +**A Linux implementation.** The realistic first form is **FUSE**, not a kernel +module — it needs no GPL entanglement (`libfuse` is LGPL and used across a +process boundary), it can reuse the read/write logic already in `tools/mkschfs` +and `tools/schfs_fsck` rather than being written twice, and it turns "does our +filesystem work" into something testable with `fsx` and `xfstests`, which is a +far harsher examination than anything in this tree. A Linux *kernel* module is a +different project and carries the licence problem that +[licensing.md](licensing.md) describes. + +**The missing features.** Worth listing honestly before adding any: no journal +replay across an unclean shutdown beyond what CoW gives for free, no snapshots +(despite the B-tree being copy-on-write, which is most of the work), no extended +attributes, no sparse-file support, and no online `fsck`. Snapshots are the one +where the existing design already pays for most of the cost. + +--- + +## 3b. Known broken + +Two things that are wrong *now*, both reported 2026-08-08 and both diagnosed +rather than merely observed. Neither blocks the GUI work, which is why they are +here and not in a phase. + +### ~~SMP is broken~~ *(fixed 2026-08-08)* + +**SMP works.** One, two and four processors all boot; every CPU reports in with +`cpu%d: starting`, and `/proc/cpuinfo` lists them all. `smp_start_aps` in +`kernel/core/main.c` is 1. + +Five distinct bugs, each hidden behind the one before it, and all invisible +under QEMU because it publishes only one processor in the legacy MP table. +VMware published the real count, which is why the triple fault only ever +appeared there. + +| | | +|---|---| +| CPU enumeration | `mpinit()` read the 1994 MP table. `kernel/arch/x86_64/acpi.c` now reads the ACPI MADT. | +| triple fault | `entry64high` had **no AP branch** — every AP took the BSP's stack and ran `kmain()` twice. Now tests `EBX == 1`. | +| `panic("acquire")` | `seginit()` called `kalloc()` before `%fs` existed; kalloc locks, locking reads `cpu`. BSP now preallocates each AP's page. | +| `panic("sched locks")` | `kernel_fs_base` was **one global** read by `trapasm.S` on every entry, so all CPUs shared one per-CPU block. Now `%gs:0`. | +| null `proc` in `sleep()` | `cpu`/`proc` were `__thread` — per *thread*, so the compiler cached the thread pointer across `sched()`, but `%fs` is per *CPU*. Now `mycpu()`/`myproc()`. | +| `#GP` in `syscall_entry` | The new per-CPU slots landed on `%fs:(8)`, the syscall scratch word — the same memory as `%gs:8`. Moved to +16 and +24. | + +The `__thread` one is the lesson worth keeping: **`__thread` is wrong for +per-CPU kernel data.** It means per thread, and the compiler is entitled to +cache the thread pointer across calls on that basis. A kernel thread changes CPU +at every `sched()`, so the assumption is false for exactly the variables that +most need to be right. `include/proc.h` carries the disassembly that proves it. + +Per-CPU block layout, shared with assembly and not to be reordered: + + +0 self-pointer x86-64 TLS ABI (%fs:0) + +8 syscall scratch trapasm.S parks %rsp here + +16 struct cpu * + +24 struct proc * + +### ~~The kernel demands 224 MB of RAM~~ *(fixed 2026-08-08)* + +`kernel/arch/x86_64/e820.c` reads the firmware's memory map and sets `phystop` +from it. **The floor went from 224 MB to 32 MB**, and the kernel now sees all +the memory a machine actually has: 512 MB in gives `MemTotal: 524160 kB`, where +before it reported 224 MB whatever was installed. + +Three details worth keeping: + +- It takes the end of the usable region **containing the kernel**, not the + highest usable address anywhere. A memory map has reserved holes in it, and + `freerange()` walks straight from the end of the kernel image to `phystop` + handing out every page between — so the global maximum would put firmware + tables on the free list, failing later and elsewhere and looking like anything + but a memory map problem. +- The ceiling is **3 GB**, because `kvmalloc()` maps 0–4 GB as four 1 GB pages + and marks the top one uncached for PCI BARs. +- Below `PHYSTOP_MIN` it prints why and stops, rather than triple faulting in + silence. At 24 MB: `e820: only 23 MB usable; Schunix needs at least 24 MB`. + +The original text is kept below because the reasoning still applies to the SMP +half of §3b, which is the same shape of bug: a constant nobody revisited. + +### The kernel demanded 224 MB of RAM + +**Symptom as reported:** the OS needs ~256 MB, which is absurd next to Windows 98 +or an early Linux. + +**Diagnosis:** it is one constant. `PHYSTOP` in `include/memlayout.h` is +`0xE000000` — 224 MB — inherited unchanged from xv6, and `main()` hands +`P2V(PHYSTOP)` straight to `kinit1()`/`dma_init()`. The kernel therefore +*assumes* that much physical memory exists and walks off the end of RAM if it +does not. Measured 2026-08-08: boots at 224M and above, triple faults at 192M +and below. Nothing is actually being *used* — the requirement is entirely +fictional. + +**The fix is already half-built.** `boot/stage2.S` collects a full E820 memory +map in real mode and leaves it at `VBE_INFO_ADDR + 0x200` (count) and `+ 0x208` +(24-byte entries); `boot/stage2main.c` already parses it to print detected RAM. +**The kernel never reads it.** So: have `main()` take the top of the highest +usable E820 region instead of the constant, clamp it to what the direct map +covers, and keep `PHYSTOP` only as a fallback for the case where the firmware +returned nothing. That should drop the floor to something in the tens of +megabytes and is a contained change. + +--- + +## 4. Kept out of scope on purpose + +- Anything that needs pushing or publishing. This work stays local. diff --git a/include/asm.h b/include/asm.h new file mode 100644 index 0000000..68210d7 --- /dev/null +++ b/include/asm.h @@ -0,0 +1,21 @@ +// +// assembler macros to create x86 segments +// + +#define SEG_NULLASM \ + .word 0, 0; \ + .byte 0, 0, 0, 0 + +// The 0xC0 means the limit is in 4096-byte units +// and (for executable segments) 32-bit mode. +#define SEG_ASM(type,base,lim) \ + .word (((lim) >> 12) & 0xffff), ((base) & 0xffff); \ + .byte (((base) >> 16) & 0xff), (0x90 | (type)), \ + (0xC0 | (((lim) >> 28) & 0xf)), (((base) >> 24) & 0xff) + +#define STA_X 0x8 // Executable segment +#define STA_E 0x4 // Expand down (non-executable segments) +#define STA_C 0x4 // Conforming code segment (executable only) +#define STA_W 0x2 // Writeable (non-executable segments) +#define STA_R 0x2 // Readable (executable segments) +#define STA_A 0x1 // Accessed diff --git a/include/buf.h b/include/buf.h new file mode 100644 index 0000000..3266495 --- /dev/null +++ b/include/buf.h @@ -0,0 +1,14 @@ +struct buf { + int flags; + uint dev; + uint blockno; + struct sleeplock lock; + uint refcnt; + struct buf *prev; // LRU cache list + struct buf *next; + struct buf *qnext; // disk queue + uchar data[BSIZE]; +}; +#define B_VALID 0x2 // buffer has been read from disk +#define B_DIRTY 0x4 // buffer needs to be written to disk + diff --git a/include/config_sinit.h b/include/config_sinit.h new file mode 100644 index 0000000..4f16fac --- /dev/null +++ b/include/config_sinit.h @@ -0,0 +1,3 @@ +static char *const rcinitcmd[] = { "/bin/sh", NULL }; +static char *const rcrebootcmd[] = { "/bin/halt", "-rb", NULL }; +static char *const rcpoweroffcmd[] = { "/bin/halt", "-po", NULL }; diff --git a/include/date.h b/include/date.h new file mode 100644 index 0000000..18e12e0 --- /dev/null +++ b/include/date.h @@ -0,0 +1,15 @@ +#pragma once + +struct rtcdate { + uint second; + uint minute; + uint hour; + uint day; + uint month; + uint year; +}; + +struct timeval { + long tv_sec; + long tv_usec; +}; diff --git a/include/defs.h b/include/defs.h new file mode 100644 index 0000000..11bde89 --- /dev/null +++ b/include/defs.h @@ -0,0 +1,466 @@ +#pragma once + +struct buf; +struct context; +struct file; +struct inode; +struct pipe; +struct proc; +struct rtcdate; +struct spinlock; +struct sleeplock; +struct stat; +struct superblock; +struct trapframe; +struct dirent; + +// entryother.S / syscall.S +void syscall_entry(void); +void ignore_sysret(void); + +// bio.c +void binit(void); +struct buf* bread(uint, uint); +void brelse(struct buf*); +void bwrite(struct buf*); + +// console.c +struct termios; +void consoleinit(void); +extern int tty_fg_pgrp; +// The terminal's line-discipline settings. TCGETS/TCSETS in sys_fs.c are the +// only callers; the console honours ICANON, ECHO, ECHOE, ECHONL, ISIG, +// ICRNL, INLCR, IGNCR, ISTRIP and VMIN. +void cons_get_termios(struct termios *); +void cons_set_termios(const struct termios *); +void cprintf(char*, ...); +void consoleintr(int(*)(void)); +void panic(char*) __attribute__((noreturn)); + +// exec.c +int exec(char*, char**); + +// file.c +struct file* filealloc(void); +int fdalloc(struct file*); +void fileclose(struct file*); +struct file* filedup(struct file*); +void fileinit(void); +int fileread(struct file*, char*, int n); +int file_poll(struct file*, int events); +int filestat(struct file*, struct stat*); +int filewrite(struct file*, char*, int n); + +// fs.c +void fsinit(int); +int dirlink(struct inode*, char*, uint64); +struct inode* dirlookup(struct inode*, char*, uint*); +// Remove the entry `name` at the cookie dirlookup returned in *poff. +int dirunlink(struct inode*, char*, uint); +// 1 if the directory holds no real entries, 0 if it does, -1 on error. +int dirempty(struct inode*); +// Fill `out` with getdents64 records starting at *poff, which is an opaque +// position the filesystem advances. Returns bytes written, 0 at end of +// directory. The inode must be locked. +int getdents(struct inode*, char *out, int cap, uint64 *poff); +// Mount table: a substitution applied during path resolution. Nothing on disk +// records a mount, so unmounting is forgetting. +void mountinit(void); +int mount_add(uint dev, uint64 inum, uint fsdev, uint64 root); +int mount_del_by_root(uint fsdev, uint64 root); +struct inode* ialloc(uint, short); +struct inode* iget(uint, uint64); +struct inode* idup(struct inode*); +void iinit(void); +void ilock(struct inode*); +void iput(struct inode*); +void iunlock(struct inode*); +void iunlockput(struct inode*); +void iupdate(struct inode*); +int namecmp(const char*, const char*); +struct inode* namei(char*); +struct inode* nameiparent(char*, char*); +struct inode* nameifrom(struct inode*, char*); +struct inode* nameiparentfrom(struct inode*, char*, char*); +int readi(struct inode*, int, uint64, uint64, uint); +void stati(struct inode*, struct stat*); +int writei(struct inode*, int, uint64, uint64, uint); +void itrunc(struct inode*); +void itrunc_to(struct inode*, uint64); +unsigned int BKDRHash(char * str); +uint tree_expand(struct inode *ip, uint off); +void tree_search(struct inode* dp, char* name, uint* poff, struct dirent* de,uint add); +void build_dir_tree(struct inode* dp,uint64 pinum); +struct inode* divesymlink(struct inode *); +void readsb(int dev, struct superblock *sb); + +// pci.c -- bus enumeration. Drivers that need to *find* their hardware use +// these; drivers for things that have lived at a fixed address since 1984 +// (the PIC, the PIT, the legacy IDE ports) do not. +struct pci_dev; +struct pci_driver; +void pci_init(void); +uint32 pci_read32(uint8, uint8, uint8, uint8); +uint16 pci_read16(uint8, uint8, uint8, uint8); +uint8 pci_read8(uint8, uint8, uint8, uint8); +void pci_write32(uint8, uint8, uint8, uint8, uint32); +void pci_enable(struct pci_dev *, uint16 bits); +uint8 pci_find_cap(struct pci_dev *, uint8 id); +int pci_msi_enable(struct pci_dev *, int vector, int cpu); +int pci_count(void); +struct pci_dev *pci_get(int); +struct pci_dev *pci_find_class(uint8 class, uint8 subclass); +struct pci_dev *pci_find_id(uint16 vendor, uint16 device); +void pci_bind(const struct pci_driver *); +int pci_info(char *buf, int cap); + +// drivers/registry.c -- the one list of every driver in the kernel. +void pci_bind_all(void); + +// xhci.c — `echo u > /proc/sysrq` +void xhci_dump(void); + +// ide.c +void ideinit(void); +void ideintr(void); +void iderw(struct buf*); +// For probes, not for reads: iderw() panics on an absent drive, so anything +// that is guessing whether a disk exists has to ask here first. +int ide_disk_present(int dev); + +// fs/schfs/kmount.c -- SchFS. +// +// Objects are named by 64-bit object id, not by a path: resolution is the +// caller's business, so these compose with a VFS rather than assuming one. +// Every schfs_k* that changes anything is one transaction and is durable by the +// time it returns. +struct schfs_inode; +void schfs_kmount(uint dev); +int schfs_kmounted(void); +uint64 schfs_klookup(const char *path); +int schfs_kstat(uint64 ino, struct schfs_inode *out); +long schfs_kread(uint64 ino, uint64 off, void *buf, unsigned n); +int schfs_kreaddir(uint64 dir, + int (*fn)(uint64, unsigned short, const char *, + unsigned, void *), + void *arg); +int schfs_kinfo(char *buf, int cap); + +int schfs_kcreate(uint64 dir, const char *name, unsigned namelen, + unsigned short type, uint mode, + unsigned short major, unsigned short minor, uint64 *out); +long schfs_kwrite(uint64 ino, uint64 off, const void *buf, + unsigned n); +int schfs_ktruncate(uint64 ino, uint64 size); +int schfs_kunlink(uint64 dir, const char *name, unsigned namelen); +int schfs_klink(uint64 dir, const char *name, unsigned namelen, + uint64 ino); +int schfs_krename(uint64 olddir, const char *oldname, + unsigned oldlen, uint64 newdir, const char *newname, + unsigned newlen); +int schfs_ksetattr(uint64 ino, const struct schfs_inode *in); +int schfs_ksync(void); + +// Pieces the VFS layer (fs/schfs/vfs.c) needs, where a directory entry and the +// object it names are separate operations. The VFS keeps the link count. +uint64 schfs_klookup_at(uint64 dir, const char *name, unsigned len); +int schfs_kdirempty(uint64 dir); +int schfs_kalloc_object(unsigned short type, uint mode, + uint64 *out); +int schfs_kdiradd(uint64 dir, const char *name, unsigned len, + uint64 ino, unsigned short type); +int schfs_kdirdel(uint64 dir, const char *name, unsigned len); +int schfs_kfree(uint64 ino); +uint64 schfs_kroot(void); + +// ramdisk.c +uint uptime(void); + +// procfs.c +int procread(struct inode*, uint, char*, int); +int procwrite(struct inode*, uint, char*, int); +// Shared with the /proc filesystem (kernel/fs/procfs.c): one owns the +// namespace, the other the content. +int proc_gen_global(int id, char *buf, int cap); +int proc_write_sysrq(int user_src, uint64 src, uint n); +void procfs_mount(void); + +// input.c -- raw evdev event devices +void inputinit(void); +int inputread(struct inode*, uint, char*, int); +int inputwrite(struct inode*, uint, char*, int); +int inputready(struct inode*); +void inputclose(struct inode*); +void inputopen(struct inode*); +int input_ioctl(struct inode*, int request, addr_t arg); +int input_grab(int dev, int on); +int input_is_grabbed(int dev); +void input_report(int dev, int type, int code, int value); +void input_sync(int dev); +int input_ready(int dev); +void input_ps2_scancode(int raw, int e0); +void input_key(int hid_usage, int down); +int input_hid_keycode(int hid_usage); +void input_mouse(int dx, int dy, int buttons); + +void ramdiskinit(void); +void ramdiskrw(struct buf*); +int ramdisk_present(void); + +// usb_storage.c +void usb_storage_rw(struct buf*); +int usb_storage_present(void); +void usb_storage_dump(void); // `echo s > /proc/sysrq` + +// ioapic.c +void ioapicenable(int irq, int cpu); +void ioapicroute(int irq, int cpu, int level_low); +void ioapicmask(int irq); +extern uchar ioapicid; +void ioapicinit(void); + +// dma.c — see include/dma.h for the full contract +void dma_init(void*, void*); +void *dma_alloc(uint64 size, uint64 align); +void dma_free(void*, uint64 size); +addr_t dma_phys(const void*); +uint64 dma_free_bytes(void); +uint64 dma_total_bytes(void); + +// kalloc.c +char* kalloc(void); +uint64 kalloc_free_bytes(void); +void kfree(char*); +void kinit1(void*, void*); +void kinit2(void); + +// kbd.c +void kbdintr(void); + +// mouse.c +void mouseinit(void); +void mouseintr(void); +void mouse_inject(int dx, int dy, int buttons); +int get_mouse_x(void); +int get_mouse_y(void); +uint8 get_mouse_buttons(void); + +// lapic.c +void cmostime(struct rtcdate *r); + +// time.c -- wall-clock time, from the RTC read once at boot plus uptime. +void time_init(void); +void wallclock(uint64 *sec, uint64 *usec); +extern uint64 boot_epoch; +int cpunum(void); +extern volatile uint* lapic; +void lapiceoi(void); +void lapicinit(void); +void lapicstartap(uchar, uint); +void microdelay(int); + +// log.c +void initlog(int dev); +void log_write(struct buf*); +void begin_op(); +void end_op(); + +// mp.c +extern int ismp; +void mpinit(void); + +// pipe.c +int pipealloc(struct file**, struct file**); +void pipeclose(struct pipe*, int); +int piperead(struct pipe*, char*, int); +int pipewrite(struct pipe*, char*, int); + +// net/unix.c +struct socket; +void sockinit(void); +struct socket* socknew(int domain, int type); +void sockclose(struct socket*); +void sockdup(struct socket*); +int sockread(struct socket*, char*, int); +int sockwrite(struct socket*, char*, int); +int sockbind(struct socket*, const char *path); +int socklisten(struct socket*, int backlog); +struct socket* sockaccept(struct socket*); +int sockconnect(struct socket*, const char *path); +int sockreadable(struct socket*); +int sock_hup(struct socket*); +int sock_shutdown(struct socket*, int how); +int sockpair(struct socket **a, struct socket **b); +const char* sockname(struct socket*); + +// proc.c +void exit(int status); +int fork(void); +int growproc(int64); +int kill(int); +int sigsend_pgrp(int pgid, int sig); +int proc_pgid(int pid); +int proc_setpgid(int pid, int pgid); +int proc_setsid(void); +int sigsend(int pid, int sig); +int proc_exists(int); +int proc_num(void); +struct procinfo; +int proc_snapshot(int idx, struct procinfo *out); +int proc_snapshot_pid(int pid, struct procinfo *out); +void pinit(void); +void procdump(void); +void scheduler(void) __attribute__((noreturn)); +void sched(void); +void sleep(void*, struct spinlock*); +int wait(void); +int waitpid(int, int*, int); +void wakeup(void*); +void yield(void); +void sinit(void); +void kstack_check_canary(const char *where); + +// swtch.S +void swtch(struct context**, struct context*); + +// spinlock.c +void acquire(struct spinlock*); +void getcallerpcs(void*, addr_t*); +void getstackpcs(addr_t*, addr_t*); +int holding(struct spinlock*); +void initlock(struct spinlock*, char*); +void release(struct spinlock*); +void pushcli(void); +void popcli(void); + +// sleeplock.c +void acquiresleep(struct sleeplock*); +void releasesleep(struct sleeplock*); +int holdingsleep(struct sleeplock*); +void initsleeplock(struct sleeplock*, char*); + +// string.c +int memcmp(const void*, const void*, uint64); +void* memmove(void*, const void*, uint64); +void* memset(void*, int, uint64); +char* safestrcpy(char*, const char*, int); +int strlen(const char*); +int strnlen(const char*, int); +int strncmp(const char*, const char*, uint64); +char* strncpy(char*, const char*, int); + +// syscall.c +void syscall(struct trapframe *); +void syscallinit(void); +void deliver_signals(struct trapframe *); +int argint(int, int*); +int argptr(int, char**, int); +int argstr(int, char**); +int argaddr(int, addr_t*); +int fetchaddr(addr_t, addr_t*); +int fetchstr(addr_t, char**); +int fetchint(addr_t, int*); + +// trap.c +void idtinit(void); +extern uint ticks; +void tvinit(void); +extern struct spinlock tickslock; + +// uart.c +void uartearlyinit(void); +void uartinit(void); +void uartintr(void); +void uartputc(int); + +// vga.c +void vga_init(uint64); +void vga_draw_pixel(int, int, uint32); +void vga_draw_char(int, int, uchar, uint32); +void vga_puts(int, int, char*, uint32); +void vga_putc(int, uint32); +void vga_clear(uint32); +void vga_screen_reset(void); +void vga_set_bg(uint32); +// Crash-screen primitives: the panic handler lays itself out rather than +// printing. See kernel/core/panic.c. +int vga_ready(void); +void vga_dims(int *w, int *h); +int vga_char_w(void); +int vga_char_h(void); +// Framebuffer geometry. vga.c reads the VBE mode info, so it is the only +// thing that knows where the framebuffer is; nobody else may assume. +uint64 vga_fb_phys(void); +uint64 vga_fb_size(void); +uint32 vga_fb_stride(void); +uint32 vga_fb_bpp(void); +volatile uchar *vga_fb_ptr(void); + +// fb.c -- /dev/fb0, major 2. +void fbclose(struct inode*); +// Called from sys_mmap: a mapping of /dev/fb0 is a licence to draw with no +// further syscalls, so it is the last the kernel hears about it. +void fb_mapped(void); +int fbread(struct inode*, uint, char*, int); +int fbwrite(struct inode*, uint, char*, int); +int fb_ioctl(int request, addr_t arg); +// drivers/net/e1000 -- raw Ethernet. No protocol stack above it yet, so these +// are the whole interface: queue a frame, take a frame, and the MAC. +int e1000_transmit(const void *frame, int len); +int e1000_receive(void *out, int cap); +const unsigned char *e1000_mac(void); +void e1000_selftest(void); + +// registry.c +void platform_register_devices(void); + +void e820_init(void); +int acpi_init(void); +void vga_fill_rect(int x, int y, int w, int h, uint32); +void vga_fill_circle(int cx, int cy, int r, uint32); +void vga_text_at(int, int, const char*, uint32); +void vga_draw_logo(int x, int y, uint32 color, uint32 bg); +void vga_logo_centered(int cy, uint32 color, uint32 bg); +// Cursor addressing and erasing, for the console's ANSI escape parser. +int vga_cols(void); +int vga_rows(void); +void vga_cursor_get(int *col, int *row); +void vga_cursor_set(int col, int row); +void vga_erase_display(int mode); +void vga_erase_line(int mode); + +// console.c / panic.c +void cons_panic_mode(void); +extern int cons_serial_only; +void console_takeover(void); + +// vm.c +void seginit(void); +void kvmalloc(void); +pml4e_t* setupkvm(void); +char* uva2ka(pml4e_t*, char*); +addr_t allocuvm(pml4e_t*, uint64, uint64); +addr_t deallocuvm(pml4e_t*, uint64, uint64); +void freevm(pml4e_t*); +void inituvm(pml4e_t*, char*, uint64); +int loaduvm(pml4e_t*, char*, struct inode*, uint64, uint64); +pml4e_t* copyuvm(pml4e_t*, uint64); +void switchuvm(struct proc*); +void switchkvm(void); +int pipe_poll(struct pipe*, int readable, int writable); +int console_input_ready(void); +int copyin(pml4e_t*, addr_t, void*, uint64); +int copyout(pml4e_t*, addr_t, void*, uint64); +void vm_unmap_range(pml4e_t*, uint64, uint64); +// Unmap without freeing: for pages this process does not own — device memory +// and shared segments. vm_unmap_range() would hand them to kfree. +void vm_unmap_norelease(pml4e_t*, uint64, uint64); +// Shared memory (sys_mem.c). shm_ref/shm_detach maintain the attachment +// count that decides when a segment's pages go back. +void shm_ref(int id); +void shm_detach(int id); +void proc_release_shared(struct proc *); +int copy_vma_range(pml4e_t *dstpgdir, pml4e_t *srcpgdir, uint64 va, uint64 length, int is_fb); +void clearpteu(pml4e_t *pgdir, char *uva); + +#define NELEM(x) (sizeof(x)/sizeof((x)[0])) diff --git a/include/device.h b/include/device.h new file mode 100644 index 0000000..8827dfe --- /dev/null +++ b/include/device.h @@ -0,0 +1,89 @@ +#pragma once + +// The device model: one list of everything attached, whatever bus found it. +// +// Before this there were two registries in kernel/drivers/registry.c -- one +// array of PCI drivers, one of USB drivers -- and nothing that knew about both. +// That is fine for binding drivers to hardware, which is all it was for, and +// useless for the three things wanted next: +// +// *Enumeration.* "What is in this machine, and what claimed it" had no +// answer that covered a USB disk and a PCI display at once. A device manager +// is exactly that question, so it needs exactly this list. +// +// *Classes.* A compositor should ask for "the display" and not care whether +// that is the VBE framebuffer or virtio-gpu. Without a class, every consumer +// hardcodes a driver, and adding the second driver means editing the +// consumers -- which is how a framebuffer address ends up hardcoded in three +// places, as it once was here. +// +// *A boundary for drivers that are not compiled in.* Drivers the user +// installs (see docs/drivers.md) have to attach somewhere that is not "an +// array in registry.c", because that array is fixed at link time. +// +// The shape is deliberately the one NeXTSTEP/macOS uses rather than Linux's: +// a device is an object with a class and an ops vtable, and consumers talk to +// the class. It is *not* a device tree -- there is no parent/child nesting +// here, only a flat list with a bus tag, because nothing yet needs to walk a +// hierarchy and a tree that nobody traverses is just a harder list. + +#define DEV_NAMELEN 24 + +// Which bus found it. This is provenance, not capability -- what the device +// *does* is its class. +enum dev_bus { + DEVBUS_PLATFORM = 0, // found by looking, not by enumerating: PS/2, VBE + DEVBUS_PCI, + DEVBUS_USB, + DEVBUS_NBUS +}; + +// What it is. A consumer asks for a class and gets whatever is bound. +enum dev_class { + DEVCLASS_OTHER = 0, + DEVCLASS_DISPLAY, + DEVCLASS_INPUT, + DEVCLASS_STORAGE, + DEVCLASS_NET, + DEVCLASS_BUS, // a controller that finds more devices (xHCI, hubs) + DEVCLASS_NCLASS +}; + +struct device { + char name[DEV_NAMELEN]; // "vga0", "xhci0" -- unique, human-readable + char desc[48]; // free text for the enumerator + enum dev_bus bus; + enum dev_class class; + + // Where it came from, for the enumerator to print. Zero when not applicable. + uint16 vendor, product; + uint8 b, s, f; // PCI bus/slot/func + + const char *driver; // whatever claimed it, or 0 for unclaimed + void *ops; // class-specific vtable; see display.h + void *priv; // the driver's own state + + int in_use; +}; + +#define NDEVICE 32 + +// Register a device. Returns it, or 0 if the table is full. The name is made +// unique by appending an index, so a caller passes "vga" and gets "vga0". +struct device *device_add(const char *base, enum dev_bus bus, + enum dev_class class, const char *driver); + +// Set the human-readable description. Bounded copy into the fixed field, so +// callers do not each need to know how long it is. +void device_set_desc(struct device *, const char *); + +// Walk. Returns 0 past the end; index is dense over registered devices. +struct device *device_at(int i); +int device_count(void); + +// First device of a class with a bound driver, or 0. This is how a consumer +// finds "the display" without naming a driver. +struct device *device_find_class(enum dev_class class); + +const char *dev_bus_name(enum dev_bus); +const char *dev_class_name(enum dev_class); diff --git a/include/display.h b/include/display.h new file mode 100644 index 0000000..b695d0f --- /dev/null +++ b/include/display.h @@ -0,0 +1,56 @@ +#pragma once + +// The display class: what a consumer talks to instead of a driver. +// +// A compositor should ask for "the display" and get whatever is bound -- +// today the VBE framebuffer, later virtio-gpu. Without this, every consumer +// names a driver, and adding the second display driver means editing all of +// them. That is not hypothetical here: the framebuffer's physical address was +// once hardcoded in three separate places. +// +// Found with device_find_class(DEVCLASS_DISPLAY); see include/device.h. + +struct device; + +struct display_mode { + int width, height; + int bpp; + uint32 stride; // bytes per row of the *visible* framebuffer +}; + +struct display_ops { + void (*mode)(struct device *, struct display_mode *); + + // The buffer to draw into, and its stride. Returns 0 if double buffering is + // unavailable, in which case the caller should draw directly and accept + // tearing rather than not drawing at all. + // + // Allocated on first call, never at boot: it is around two megabytes at + // 800x600, and a machine whose compositor never starts should not pay that. + void *(*acquire)(struct device *, uint32 *stride_out); + + // Copy a rectangle of the back buffer to the display. Coordinates are in + // pixels; the whole screen is (0, 0, width, height). + void (*present)(struct device *, int x, int y, int w, int h); + + // Block until the display is between frames. Optional -- a driver that + // cannot tell leaves this null, and present() then simply tears less than + // drawing directly would. + void (*wait_vblank)(struct device *); +}; + +// The bound display, or 0. Convenience over device_find_class(). +struct device *display_get(void); + +// Wrappers that do nothing gracefully when there is no display or the driver +// left an op null, so callers are not each written around a maybe-null vtable. +int display_mode(struct display_mode *out); +void *display_acquire(uint32 *stride_out); +void display_present(int x, int y, int w, int h); +void display_wait_vblank(void); + +// Drive the whole path once and draw a test pattern. `echo d > /proc/sysrq`. +void display_selftest(void); + +// Draw a text specimen through the display class. `echo t > /proc/sysrq`. +void text_selftest(void); diff --git a/include/dma.h b/include/dma.h new file mode 100644 index 0000000..1a40de9 --- /dev/null +++ b/include/dma.h @@ -0,0 +1,43 @@ +#pragma once + +// Physically contiguous memory for devices that read and write it themselves. +// +// kalloc() hands out 4 KB pages from a free list, in whatever order they were +// released. That is exactly what a page table wants and exactly what a device +// cannot use: an xHCI command ring, a NIC's descriptor array or a virtio +// queue is a single physically contiguous object that the hardware walks +// without any idea that paging exists. Two kalloc'd pages are adjacent in the +// kernel's address space only by luck. +// +// So DMA memory comes from an arena carved out of the top of physical memory +// before kalloc ever sees it, and is handed out contiguous and aligned. +// +// On x86 this memory needs no special cacheability. The PCI bus is cache +// coherent: a device's write invalidates the corresponding cache line, and a +// CPU write is snooped out of the store buffer before the device sees stale +// data. Architectures without that guarantee need explicit cache maintenance +// around every descriptor access, and porting this file is where that would +// go. What is *not* free even here is ordering -- the compiler and the CPU +// will happily reorder stores to a ring, so a driver still needs a barrier +// between filling a descriptor and telling the device about it. + +#define DMA_ARENA_BYTES (4*1024*1024) + +void dma_init(void *vstart, void *vend); + +// Zeroed, physically contiguous, at least `align`-aligned (align must be a +// power of two). Returns a kernel virtual address, or 0. +void *dma_alloc(uint64 size, uint64 align); + +// `size` must be the size the allocation was made with, as in Linux's +// dma_free_coherent(). Tracking it here would mean a second table whose only +// job is to remember something the caller already knows. +void dma_free(void *p, uint64 size); + +// The address to give the device. Kernel memory is direct-mapped, so this is +// just V2P -- but drivers should say what they mean, because on a machine +// with an IOMMU these stop being the same number. +addr_t dma_phys(const void *p); + +uint64 dma_free_bytes(void); +uint64 dma_total_bytes(void); diff --git a/include/e1000.h b/include/e1000.h new file mode 100644 index 0000000..12f63ee --- /dev/null +++ b/include/e1000.h @@ -0,0 +1,165 @@ +#pragma once + +// Intel 8254x gigabit Ethernet — the register and descriptor layout. +// +// From Intel's public "PCI/PCI-X Family of Gigabit Ethernet Controllers +// Software Developer's Manual" (317453). A public document with no licence on +// what you write from it, which is why this is written rather than ported: see +// docs/licensing.md and docs/roadmap.md §1. +// +// QEMU's `-device e1000` is an 82540EM and implements enough of the manual to +// be worth targeting; it is also what the default `-net nic` gives you. + +#define E1000_VENDOR 0x8086 + +// The 8254x family: PCI/PCI-X gigabit controllers that share the register map, +// descriptor format and EEPROM interface this driver implements. +// +// The device IDs are transcribed from FreeBSD's `sys/dev/e1000/e1000_hw.h` +// (Copyright (c) 2001-2020, Intel Corporation, BSD 3-clause -- verified to +// carry no advertising clause; see docs/licensing.md and NOTICE). They are +// data, not logic: the driver below is still written from Intel's manual +// 317453, and nothing of FreeBSD's implementation is used. +// +// This matters more than it looks. The driver bound exactly one device -- the +// 82540EM QEMU emulates -- which is fine for a virtual machine and useless on +// the sort of second-hand hardware a system like this actually gets run on. +// These are the cards in every desktop and server of that generation. +// +// Deliberately *not* included: the PCIe 8257x parts (82571-82574) and anything +// newer. They present a similar register map but differ in NVM access and +// initialisation, and claiming a device this driver has not been shown to +// initialise is worse than not claiming it -- the card binds, reports a MAC of +// nonsense, and nothing works for reasons that look nothing like the cause. +#define E1000_DEV_82540EM 0x100E +#define E1000_DEV_82540EM_LOM 0x1015 +#define E1000_DEV_82540EP_LOM 0x1016 +#define E1000_DEV_82540EP 0x1017 +#define E1000_DEV_82540EP_LP 0x101E +#define E1000_DEV_82541EI 0x1013 +#define E1000_DEV_82541EI_MOBILE 0x1018 +#define E1000_DEV_82541ER_LOM 0x1014 +#define E1000_DEV_82541ER 0x1078 +#define E1000_DEV_82541GI 0x1076 +#define E1000_DEV_82541GI_MOBILE 0x1077 +#define E1000_DEV_82541GI_LF 0x107C +#define E1000_DEV_82544EI_FIBER 0x1009 +#define E1000_DEV_82544GC_COPPER 0x100C +#define E1000_DEV_82544GC_LOM 0x100D +#define E1000_DEV_82545EM_COPPER 0x100F +#define E1000_DEV_82545EM_FIBER 0x1011 +#define E1000_DEV_82545GM_COPPER 0x1026 +#define E1000_DEV_82545GM_FIBER 0x1027 +#define E1000_DEV_82545GM_SERDES 0x1028 +#define E1000_DEV_82546EB_COPPER 0x1010 +#define E1000_DEV_82546EB_FIBER 0x1012 +#define E1000_DEV_82546EB_QUAD 0x101D +#define E1000_DEV_82546GB_COPPER 0x1079 +#define E1000_DEV_82546GB_FIBER 0x107A +#define E1000_DEV_82546GB_SERDES 0x107B +#define E1000_DEV_82547EI 0x1019 + +// --- registers, byte offsets into BAR 0 ------------------------------------ + +#define E1000_CTRL 0x0000 // device control +#define E1000_STATUS 0x0008 // device status +#define E1000_EERD 0x0014 // EEPROM read +#define E1000_ICR 0x00C0 // interrupt cause read +#define E1000_IMS 0x00D0 // interrupt mask set +#define E1000_IMC 0x00D8 // interrupt mask clear + +#define E1000_RCTL 0x0100 // receive control +#define E1000_TCTL 0x0400 // transmit control +#define E1000_TIPG 0x0410 // transmit inter-packet gap + +#define E1000_RDBAL 0x2800 // receive descriptor base, low +#define E1000_RDBAH 0x2804 // receive descriptor base, high +#define E1000_RDLEN 0x2808 // receive descriptor length, in bytes +#define E1000_RDH 0x2810 // receive descriptor head +#define E1000_RDT 0x2818 // receive descriptor tail + +#define E1000_TDBAL 0x3800 +#define E1000_TDBAH 0x3804 +#define E1000_TDLEN 0x3808 +#define E1000_TDH 0x3810 +#define E1000_TDT 0x3818 + +#define E1000_MTA 0x5200 // multicast table array, 128 entries +#define E1000_RAL 0x5400 // receive address low (the MAC filter) +#define E1000_RAH 0x5404 // receive address high + +// CTRL +#define E1000_CTRL_RST (1 << 26) +#define E1000_CTRL_ASDE (1 << 5) // auto-speed detection +#define E1000_CTRL_SLU (1 << 6) // set link up + +// STATUS +#define E1000_STATUS_LU (1 << 1) // link up + +// EERD +#define E1000_EERD_START (1 << 0) +#define E1000_EERD_DONE (1 << 4) +#define E1000_EERD_ADDR_SHIFT 8 +#define E1000_EERD_DATA_SHIFT 16 + +// RCTL +#define E1000_RCTL_EN (1 << 1) // receiver enable +#define E1000_RCTL_SBP (1 << 2) // store bad packets +#define E1000_RCTL_UPE (1 << 3) // unicast promiscuous +#define E1000_RCTL_MPE (1 << 4) // multicast promiscuous +#define E1000_RCTL_LBM_NONE (0 << 6) +#define E1000_RCTL_BAM (1 << 15) // accept broadcast +#define E1000_RCTL_SZ_2048 (0 << 16) // buffer size, with BSEX clear +#define E1000_RCTL_SECRC (1 << 26) // strip the Ethernet CRC + +// TCTL +#define E1000_TCTL_EN (1 << 1) +#define E1000_TCTL_PSP (1 << 3) // pad short packets +#define E1000_TCTL_CT_SHIFT 4 // collision threshold +#define E1000_TCTL_COLD_SHIFT 12 // collision distance + +// Transmit descriptor command bits +#define E1000_TXD_CMD_EOP (1 << 0) // end of packet +#define E1000_TXD_CMD_IFCS (1 << 1) // insert FCS/CRC +#define E1000_TXD_CMD_RS (1 << 3) // report status +#define E1000_TXD_STAT_DD (1 << 0) // descriptor done + +// Receive descriptor status bits +#define E1000_RXD_STAT_DD (1 << 0) +#define E1000_RXD_STAT_EOP (1 << 1) + +// Interrupt causes worth unmasking. +#define E1000_ICR_TXDW (1 << 0) // transmit descriptor written back +#define E1000_ICR_LSC (1 << 2) // link status change +#define E1000_ICR_RXDMT0 (1 << 4) // receive descriptor minimum threshold +#define E1000_ICR_RXT0 (1 << 7) // receiver timer + +// --- descriptors ----------------------------------------------------------- +// +// Both are 16 bytes and the hardware reads them by physical address, so the +// rings must be physically contiguous and 16-byte aligned. + +struct e1000_tx_desc { + uint64 addr; + uint16 length; + uint8 cso; // checksum offset + uint8 cmd; + uint8 status; + uint8 css; // checksum start + uint16 special; +} __attribute__((packed)); + +struct e1000_rx_desc { + uint64 addr; + uint16 length; + uint16 checksum; + uint8 status; + uint8 errors; + uint16 special; +} __attribute__((packed)); + +// Ring sizes. The manual requires the descriptor ring length to be a multiple +// of 128 bytes, i.e. a multiple of 8 descriptors. +#define E1000_NTX 16 +#define E1000_NRX 16 +#define E1000_BUFSZ 2048 diff --git a/include/elf.h b/include/elf.h new file mode 100644 index 0000000..a457d78 --- /dev/null +++ b/include/elf.h @@ -0,0 +1,35 @@ +#define ELF_MAGIC 0x464C457FU + +struct elfhdr { + uint32 magic; + uchar elf[12]; + uint16 type; + uint16 machine; + uint32 version; + uint64 entry; + uint64 phoff; + uint64 shoff; + uint32 flags; + uint16 ehsize; + uint16 phentsize; + uint16 phnum; + uint16 shentsize; + uint16 shnum; + uint16 shstrndx; +}; + +struct proghdr { + uint32 type; + uint32 flags; + uint64 off; + uint64 vaddr; + uint64 paddr; + uint64 filesz; + uint64 memsz; + uint64 align; +}; + +#define ELF_PROG_LOAD 1 +#define ELF_PROG_FLAG_EXEC 1 +#define ELF_PROG_FLAG_WRITE 2 +#define ELF_PROG_FLAG_READ 4 \ No newline at end of file diff --git a/include/errno.h b/include/errno.h new file mode 100644 index 0000000..ea518e6 --- /dev/null +++ b/include/errno.h @@ -0,0 +1,60 @@ +// Error numbers, matching the Linux x86_64 ABI values so that musl-linked +// programs interpret syscall failures correctly. Kernel code returns these +// negated (e.g. `return -ENOSYS;`), exactly like Linux does internally. +#ifndef SCHUNIX_ERRNO_H +#define SCHUNIX_ERRNO_H + +#define EPERM 1 // Operation not permitted +#define ENOENT 2 // No such file or directory +#define ESRCH 3 // No such process +#define EINTR 4 // Interrupted system call +#define EIO 5 // I/O error +#define ENXIO 6 // No such device or address +#define E2BIG 7 // Argument list too long +#define ENOEXEC 8 // Exec format error +#define EBADF 9 // Bad file descriptor +#define ECHILD 10 // No child processes +#define EAGAIN 11 // Try again +#define ENOMEM 12 // Out of memory +#define EACCES 13 // Permission denied +#define EFAULT 14 // Bad address +#define EBUSY 16 // Device or resource busy +#define EEXIST 17 // File exists +#define EXDEV 18 // Cross-device link +#define ENODEV 19 // No such device +#define ENOTDIR 20 // Not a directory +#define EISDIR 21 // Is a directory +#define EINVAL 22 // Invalid argument +#define ENFILE 23 // File table overflow +#define EMFILE 24 // Too many open files +#define ENOTTY 25 // Not a typewriter +#define EFBIG 27 // File too large +#define ENOSPC 28 // No space left on device +#define ESPIPE 29 // Illegal seek +#define EROFS 30 // Read-only file system +#define EMLINK 31 // Too many links +#define EPIPE 32 // Broken pipe +#define ERANGE 34 // Math result not representable +#define ENAMETOOLONG 36 // File name too long +#define ENOSYS 38 // Function not implemented +#define ENOTEMPTY 39 // Directory not empty +#define ELOOP 40 // Too many symbolic links encountered + +// Sockets. Same Linux x86_64 values, so strerror() in a musl-linked program +// prints the message the programmer expected rather than a neighbouring one. +#define ENOTSOCK 88 // Socket operation on non-socket +#define EDESTADDRREQ 89 // Destination address required +#define EMSGSIZE 90 // Message too long +#define EPROTOTYPE 91 // Protocol wrong type for socket +#define ENOPROTOOPT 92 // Protocol not available +#define EPROTONOSUPPORT 93 // Protocol not supported +#define EOPNOTSUPP 95 // Operation not supported on transport endpoint +#define EAFNOSUPPORT 97 // Address family not supported by protocol +#define EADDRINUSE 98 // Address already in use +#define EADDRNOTAVAIL 99 // Cannot assign requested address +#define ECONNRESET 104 // Connection reset by peer +#define EISCONN 106 // Transport endpoint is already connected +#define ENOTCONN 107 // Transport endpoint is not connected +#define ECONNREFUSED 111 // Connection refused + +#endif // SCHUNIX_ERRNO_H diff --git a/include/fb.h b/include/fb.h new file mode 100644 index 0000000..2a0300d --- /dev/null +++ b/include/fb.h @@ -0,0 +1,95 @@ +#pragma once + +// Framebuffer device ABI -- /dev/fb0. +// +// These are Linux's fbdev request numbers and structure layouts, not ours. +// Schunix invented this device, so nothing forced the choice; matching Linux +// was still the right one. A framebuffer is the first thing a graphics +// program looks for, and every such program already knows how to ask +// FBIOGET_VSCREENINFO. Inventing a private struct would have meant patching +// each one we ever port, and -- worse -- a Linux-ABI binary calling +// FBIOGET_VSCREENINFO would have gotten a *differently shaped* answer to a +// request it thought it understood, which is the failure mode that made +// `>>` truncate back when include/fcntl.h held xv6's open flags instead of +// Linux's. Same lesson, applied before it bites rather than after. +// +// Plain C types throughout (no types.h) so userspace can include this +// directly. Both sides are LP64. + +#define FBIOGET_VSCREENINFO 0x4600 +#define FBIOPUT_VSCREENINFO 0x4601 +#define FBIOGET_FSCREENINFO 0x4602 +#define FBIOBLANK 0x4611 + +// fb_fix_screeninfo.type +#define FB_TYPE_PACKED_PIXELS 0 + +// fb_fix_screeninfo.visual +#define FB_VISUAL_TRUECOLOR 2 + +// fb_var_screeninfo.activate +#define FB_ACTIVATE_NOW 0 + +// One colour channel's position within a pixel. `offset` is the bit position +// of the channel's least significant bit; `length` is how many bits it gets. +// A length of 0 means the channel does not exist -- which is how an opaque +// framebuffer reports `transp`. +struct fb_bitfield { + unsigned int offset; + unsigned int length; + unsigned int msb_right; // != 0 means bit 0 is the MSB; never here +}; + +struct fb_var_screeninfo { + unsigned int xres; // visible resolution + unsigned int yres; + unsigned int xres_virtual; // virtual resolution; == visible, no panning + unsigned int yres_virtual; + unsigned int xoffset; // offset of visible area within virtual + unsigned int yoffset; + unsigned int bits_per_pixel; + unsigned int grayscale; // 0 == colour + struct fb_bitfield red; + struct fb_bitfield green; + struct fb_bitfield blue; + struct fb_bitfield transp; + unsigned int nonstd; // 0 == standard pixel format + unsigned int activate; + unsigned int height; // physical size in mm; 0 == unknown, and + unsigned int width; // VBE does not tell us, so it stays 0 + unsigned int accel_flags; + // Timings. The mode is whatever the bootloader's VBE call established and + // we cannot change it, so these are all reported as zero rather than + // invented. + unsigned int pixclock; + unsigned int left_margin; + unsigned int right_margin; + unsigned int upper_margin; + unsigned int lower_margin; + unsigned int hsync_len; + unsigned int vsync_len; + unsigned int sync; + unsigned int vmode; + unsigned int rotate; + unsigned int colorspace; + unsigned int reserved[4]; +}; + +struct fb_fix_screeninfo { + char id[16]; // "schunixfb" + unsigned long smem_start; // physical address of the framebuffer + unsigned int smem_len; // its length in bytes + unsigned int type; + unsigned int type_aux; + unsigned int visual; + unsigned short xpanstep; // 0 == panning unsupported + unsigned short ypanstep; + unsigned short ywrapstep; + unsigned int line_length; // bytes per scanline (the VBE stride, which + // is NOT always xres * bytes_per_pixel) + unsigned long mmio_start; // no separate register aperture + unsigned int mmio_len; + unsigned int accel; // no acceleration + unsigned short capabilities; + unsigned short reserved[2]; +}; diff --git a/include/fcntl.h b/include/fcntl.h new file mode 100644 index 0000000..45b98b1 --- /dev/null +++ b/include/fcntl.h @@ -0,0 +1,43 @@ +#pragma once + +// open(2) flags. +// +// These are the *Linux x86-64* values, and they have to be: every program in +// the userland is built against a libc that sends Linux flag values, and the +// kernel reads them with these constants. They disagreed for a long time, with +// results that looked like filesystem corruption rather than like a mistake -- +// Linux O_APPEND (0x400) landed on Schunix's O_TRUNC, so `cmd >> file` +// truncated the file it was meant to extend, and Linux O_TRUNC (0x200) landed +// on O_CREATE, so `cmd > file` left whatever was past the new end in place. +// Neither failure pointed anywhere near a header. +// +// Do not tidy these into a neat sequence. They are an external interface. + +#define O_RDONLY 0x000000 +#define O_WRONLY 0x000001 +#define O_RDWR 0x000002 +#define O_ACCMODE 0x000003 + +#define O_CREATE 0x000040 /* Linux O_CREAT (0100) */ +#define O_CREAT O_CREATE /* the spelling every program uses */ +#define O_EXCL 0x000080 /* Linux O_EXCL (0200) */ +#define O_NOCTTY 0x000100 /* Linux O_NOCTTY (0400) */ +#define O_TRUNC 0x000200 /* Linux O_TRUNC (01000) */ +#define O_APPEND 0x000400 /* Linux O_APPEND (02000) */ +#define O_NONBLOCK 0x000800 /* Linux O_NONBLOCK (04000); no-op here */ +#define O_DIRECTORY 0x010000 /* Linux O_DIRECTORY (0200000) */ +#define O_NOFOLLOW 0x020000 /* Linux O_NOFOLLOW (0400000) */ +#define O_CLOEXEC 0x080000 /* Linux O_CLOEXEC (02000000) */ + +// Schunix-only, placed above every flag Linux defines so it can never be +// mistaken for one. 0x40000 was used before and collides with Linux O_NOATIME. +#define O_EXTENT 0x8000000 /* sparse-extent hint */ + +#define F_DUPFD 0 +#define F_GETFD 1 +#define F_SETFD 2 +#define F_GETFL 3 +#define F_SETFL 4 +#define F_DUPFD_CLOEXEC 1030 // Linux value + +#define FD_CLOEXEC 1 diff --git a/include/file.h b/include/file.h new file mode 100644 index 0000000..4f4f17e --- /dev/null +++ b/include/file.h @@ -0,0 +1,132 @@ +struct file { + enum { FD_NONE, FD_PIPE, FD_INODE, FD_SOCKET } type; + int ref; // reference count + char readable; + char writable; + // O_NONBLOCK. A property of the open file description, not of the + // descriptor, which is why it lives here and not in myproc()->ofile_flags -- + // dup() shares it and F_SETFL on either fd changes it for both, exactly as + // POSIX says. + char nonblock; + struct pipe *pipe; + struct inode *ip; + struct socket *sock; + uint64 off; +}; + +// --------------------------------------------------------------------------- +// Filesystem operations +// +// One table per filesystem, chosen by iget() from the device number and hung +// off the inode. Everything above this line -- the syscall layer, exec, the +// page cache -- calls the wrappers in fs.c and never learns which filesystem it +// is talking to. +// +// A vtable rather than a chain of `if (dev == SCHFS_DEV)` because there are two +// filesystems today and there will be more: the whole point of the exercise is +// that adding one is a new table, not another branch in fifteen functions. +// +// Every hook takes an inode that is already locked, except ialloc (which has no +// inode yet) and iload (which is what makes one valid in the first place). +// --------------------------------------------------------------------------- + +struct stat; + +struct fs_ops { + const char *name; + + // Fill in the type/size/link fields of a freshly iget()'d inode. + void (*iload)(struct inode *); + // Write those fields back to disk. + void (*istore)(struct inode *); + // The last link is gone and nobody has it open: release the object. + void (*ifree)(struct inode *); + + int (*read)(struct inode *, int user_dst, uint64 dst, uint64 off, uint n); + int (*write)(struct inode *, int user_src, uint64 src, uint64 off, uint n); + // Set the length. Growing is allowed only where the filesystem can represent + // the gap; v1 cannot and ignores it. + void (*truncate)(struct inode *, uint64 newsize); + + // Directory operations. `poff` is an opaque cookie identifying where the + // entry lives, meaningful only to the same filesystem's dirunlink. + struct inode *(*dirlookup)(struct inode *dp, char *name, uint *poff); + int (*dirlink)(struct inode *dp, char *name, uint64 inum); + int (*dirunlink)(struct inode *dp, char *name, uint off); + int (*dirempty)(struct inode *dp); + + // Create an object with no name yet. The caller sets nlink and calls + // dirlink; until it does, the object is unreachable. + struct inode *(*ialloc)(uint dev, short type); + + // Read directory entries into a getdents64-shaped stream. `off` is an opaque + // position that starts at 0 and is echoed back; returns bytes written, 0 at + // the end of the directory, or negative on error. + int (*getdents)(struct inode *dp, char *out, int cap, uint64 *off); +}; + +// In-memory inode. +// +// `inum` and `size` are 64-bit because the filesystem underneath may be: SchFS +// object ids and file sizes are both uint64 on disk, and a 32-bit copy here +// would silently truncate them into a different file. The v1 filesystem uses a +// small fraction of the range and is unaffected. +struct inode { + uint dev; + uint64 inum; + int ref; + struct sleeplock lock; + int valid; + const struct fs_ops *ops; // chosen by iget from dev; never null once held + + short type; + short major; + short minor; + short nlink; + uint64 size; + + uint rwmode; + uint supermode; + uint showmode; + uint atime; + uint mtime; + uint ctime; + uint addrs[NDIRECT+3]; +}; +#define I_VALID 0x2 + +// Device number that means "the mounted SchFS volume". Real disks are 0 and 1, +// and this is deliberately far from them so a stray value never looks like a +// SchFS inode. +#define SCHFS_DEV 64 + +// Device number that means "the synthetic /proc". Like SCHFS_DEV, it is +// deliberately nowhere near a real disk's 0 or 1. +#define PROCFS_DEV 65 + +// table mapping major device number to +// device functions +struct devsw { + int (*read)(struct inode*, uint, char*, int); + int (*write)(struct inode*, uint, char*, int); + // Optional. Returns 0 when a read would block, which is the only thing + // fileread needs to know to honour O_NONBLOCK without every device driver + // growing a flags argument. A device that omits this is treated as always + // ready, which is the right answer for a disk and for /dev/zero. + int (*ready)(struct inode*); + // Optional. The last descriptor for this open file is going away. A device + // that hands a program exclusive-feeling control of the hardware needs this + // to put the hardware back -- /dev/fb0 uses it to give the text console its + // screen back when whatever was drawing on it exits. + void (*close)(struct inode*); + // Optional. A descriptor has just been opened on this device. /dev/input + // uses it to discard events that arrived while nobody was listening -- see + // inputopen(). + void (*open)(struct inode*); +}; + +extern struct devsw devsw[]; + +#define CONSOLE 1 +// Major number of /dev/input/eventN. The minor is the device index. +#define INPUT_MAJOR 7 diff --git a/include/font.h b/include/font.h new file mode 100644 index 0000000..2a2b021 --- /dev/null +++ b/include/font.h @@ -0,0 +1,4627 @@ +/* Schunix OS System Font - 8x16, 256 glyphs. + * + * The upper half is CP437, the IBM PC character set: 0x82 is 'e' with an + * acute, 0xE9 is a capital theta, 0xB0-0xDF are shading and box-drawing. + * + * IGNORE THE PER-GLYPH COMMENTS BELOW FROM 128 UP. They label index N with + * Unicode codepoint N -- so 0xE9 is annotated 'e-acute' when the bitmap it + * sits above is unmistakably a theta. Whatever generated this file assumed + * Latin-1 and was wrong; the bitmaps are the truth, and console.c maps + * Unicode onto them through a table rather than by trusting an index. + * + * This was declared [128][16] until 2026-08-04, so the compiler discarded + * every glyph from 128 up -- 128 of them, already drawn and sitting in this + * file -- with nothing but a wall of "excess elements in array initializer" + * warnings to say so. + */ +unsigned char font_data[256][16] = { + + /* 0 0x00 '^@' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 1 0x01 '^A' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x7e, /* 01111110 */ + 0x81, /* 10000001 */ + 0xa5, /* 10100101 */ + 0x81, /* 10000001 */ + 0x81, /* 10000001 */ + 0xbd, /* 10111101 */ + 0x99, /* 10011001 */ + 0x81, /* 10000001 */ + 0x81, /* 10000001 */ + 0x7e, /* 01111110 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 2 0x02 '^B' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x7e, /* 01111110 */ + 0xff, /* 11111111 */ + 0xdb, /* 11011011 */ + 0xff, /* 11111111 */ + 0xff, /* 11111111 */ + 0xc3, /* 11000011 */ + 0xe7, /* 11100111 */ + 0xff, /* 11111111 */ + 0xff, /* 11111111 */ + 0x7e, /* 01111110 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 3 0x03 '^C' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x6c, /* 01101100 */ + 0xfe, /* 11111110 */ + 0xfe, /* 11111110 */ + 0xfe, /* 11111110 */ + 0xfe, /* 11111110 */ + 0x7c, /* 01111100 */ + 0x38, /* 00111000 */ + 0x10, /* 00010000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 4 0x04 '^D' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x10, /* 00010000 */ + 0x38, /* 00111000 */ + 0x7c, /* 01111100 */ + 0xfe, /* 11111110 */ + 0x7c, /* 01111100 */ + 0x38, /* 00111000 */ + 0x10, /* 00010000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 5 0x05 '^E' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x18, /* 00011000 */ + 0x3c, /* 00111100 */ + 0x3c, /* 00111100 */ + 0xe7, /* 11100111 */ + 0xe7, /* 11100111 */ + 0xe7, /* 11100111 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x3c, /* 00111100 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 6 0x06 '^F' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x18, /* 00011000 */ + 0x3c, /* 00111100 */ + 0x7e, /* 01111110 */ + 0xff, /* 11111111 */ + 0xff, /* 11111111 */ + 0x7e, /* 01111110 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x3c, /* 00111100 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 7 0x07 '^G' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x18, /* 00011000 */ + 0x3c, /* 00111100 */ + 0x3c, /* 00111100 */ + 0x18, /* 00011000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 8 0x08 '^H' */ + 0xff, /* 11111111 */ + 0xff, /* 11111111 */ + 0xff, /* 11111111 */ + 0xff, /* 11111111 */ + 0xff, /* 11111111 */ + 0xff, /* 11111111 */ + 0xe7, /* 11100111 */ + 0xc3, /* 11000011 */ + 0xc3, /* 11000011 */ + 0xe7, /* 11100111 */ + 0xff, /* 11111111 */ + 0xff, /* 11111111 */ + 0xff, /* 11111111 */ + 0xff, /* 11111111 */ + 0xff, /* 11111111 */ + 0xff, /* 11111111 */ + + /* 9 0x09 '^I' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x3c, /* 00111100 */ + 0x66, /* 01100110 */ + 0x42, /* 01000010 */ + 0x42, /* 01000010 */ + 0x66, /* 01100110 */ + 0x3c, /* 00111100 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 10 0x0a '^J' */ + 0xff, /* 11111111 */ + 0xff, /* 11111111 */ + 0xff, /* 11111111 */ + 0xff, /* 11111111 */ + 0xff, /* 11111111 */ + 0xc3, /* 11000011 */ + 0x99, /* 10011001 */ + 0xbd, /* 10111101 */ + 0xbd, /* 10111101 */ + 0x99, /* 10011001 */ + 0xc3, /* 11000011 */ + 0xff, /* 11111111 */ + 0xff, /* 11111111 */ + 0xff, /* 11111111 */ + 0xff, /* 11111111 */ + 0xff, /* 11111111 */ + + /* 11 0x0b '^K' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x1e, /* 00011110 */ + 0x0e, /* 00001110 */ + 0x1a, /* 00011010 */ + 0x32, /* 00110010 */ + 0x78, /* 01111000 */ + 0xcc, /* 11001100 */ + 0xcc, /* 11001100 */ + 0xcc, /* 11001100 */ + 0xcc, /* 11001100 */ + 0x78, /* 01111000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 12 0x0c '^L' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x3c, /* 00111100 */ + 0x66, /* 01100110 */ + 0x66, /* 01100110 */ + 0x66, /* 01100110 */ + 0x66, /* 01100110 */ + 0x3c, /* 00111100 */ + 0x18, /* 00011000 */ + 0x7e, /* 01111110 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 13 0x0d '^M' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x3f, /* 00111111 */ + 0x33, /* 00110011 */ + 0x3f, /* 00111111 */ + 0x30, /* 00110000 */ + 0x30, /* 00110000 */ + 0x30, /* 00110000 */ + 0x30, /* 00110000 */ + 0x70, /* 01110000 */ + 0xf0, /* 11110000 */ + 0xe0, /* 11100000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 14 0x0e '^N' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x7f, /* 01111111 */ + 0x63, /* 01100011 */ + 0x7f, /* 01111111 */ + 0x63, /* 01100011 */ + 0x63, /* 01100011 */ + 0x63, /* 01100011 */ + 0x63, /* 01100011 */ + 0x67, /* 01100111 */ + 0xe7, /* 11100111 */ + 0xe6, /* 11100110 */ + 0xc0, /* 11000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 15 0x0f '^O' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0xdb, /* 11011011 */ + 0x3c, /* 00111100 */ + 0xe7, /* 11100111 */ + 0x3c, /* 00111100 */ + 0xdb, /* 11011011 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 16 0x10 '^P' */ + 0x00, /* 00000000 */ + 0x80, /* 10000000 */ + 0xc0, /* 11000000 */ + 0xe0, /* 11100000 */ + 0xf0, /* 11110000 */ + 0xf8, /* 11111000 */ + 0xfe, /* 11111110 */ + 0xf8, /* 11111000 */ + 0xf0, /* 11110000 */ + 0xe0, /* 11100000 */ + 0xc0, /* 11000000 */ + 0x80, /* 10000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 17 0x11 '^Q' */ + 0x00, /* 00000000 */ + 0x02, /* 00000010 */ + 0x06, /* 00000110 */ + 0x0e, /* 00001110 */ + 0x1e, /* 00011110 */ + 0x3e, /* 00111110 */ + 0xfe, /* 11111110 */ + 0x3e, /* 00111110 */ + 0x1e, /* 00011110 */ + 0x0e, /* 00001110 */ + 0x06, /* 00000110 */ + 0x02, /* 00000010 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 18 0x12 '^R' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x18, /* 00011000 */ + 0x3c, /* 00111100 */ + 0x7e, /* 01111110 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x7e, /* 01111110 */ + 0x3c, /* 00111100 */ + 0x18, /* 00011000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 19 0x13 '^S' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x66, /* 01100110 */ + 0x66, /* 01100110 */ + 0x66, /* 01100110 */ + 0x66, /* 01100110 */ + 0x66, /* 01100110 */ + 0x66, /* 01100110 */ + 0x66, /* 01100110 */ + 0x00, /* 00000000 */ + 0x66, /* 01100110 */ + 0x66, /* 01100110 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 20 0x14 '^T' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x7f, /* 01111111 */ + 0xdb, /* 11011011 */ + 0xdb, /* 11011011 */ + 0xdb, /* 11011011 */ + 0x7b, /* 01111011 */ + 0x1b, /* 00011011 */ + 0x1b, /* 00011011 */ + 0x1b, /* 00011011 */ + 0x1b, /* 00011011 */ + 0x1b, /* 00011011 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 21 0x15 '^U' */ + 0x00, /* 00000000 */ + 0x7c, /* 01111100 */ + 0xc6, /* 11000110 */ + 0x60, /* 01100000 */ + 0x38, /* 00111000 */ + 0x6c, /* 01101100 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0x6c, /* 01101100 */ + 0x38, /* 00111000 */ + 0x0c, /* 00001100 */ + 0xc6, /* 11000110 */ + 0x7c, /* 01111100 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 22 0x16 '^V' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0xfe, /* 11111110 */ + 0xfe, /* 11111110 */ + 0xfe, /* 11111110 */ + 0xfe, /* 11111110 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 23 0x17 '^W' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x18, /* 00011000 */ + 0x3c, /* 00111100 */ + 0x7e, /* 01111110 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x7e, /* 01111110 */ + 0x3c, /* 00111100 */ + 0x18, /* 00011000 */ + 0x7e, /* 01111110 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 24 0x18 '^X' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x18, /* 00011000 */ + 0x3c, /* 00111100 */ + 0x7e, /* 01111110 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 25 0x19 '^Y' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x7e, /* 01111110 */ + 0x3c, /* 00111100 */ + 0x18, /* 00011000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 26 0x1a '^Z' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x18, /* 00011000 */ + 0x0c, /* 00001100 */ + 0xfe, /* 11111110 */ + 0x0c, /* 00001100 */ + 0x18, /* 00011000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 27 0x1b '^[' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x30, /* 00110000 */ + 0x60, /* 01100000 */ + 0xfe, /* 11111110 */ + 0x60, /* 01100000 */ + 0x30, /* 00110000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 28 0x1c '^\' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0xc0, /* 11000000 */ + 0xc0, /* 11000000 */ + 0xc0, /* 11000000 */ + 0xfe, /* 11111110 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 29 0x1d '^]' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x28, /* 00101000 */ + 0x6c, /* 01101100 */ + 0xfe, /* 11111110 */ + 0x6c, /* 01101100 */ + 0x28, /* 00101000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 30 0x1e '^^' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x10, /* 00010000 */ + 0x38, /* 00111000 */ + 0x38, /* 00111000 */ + 0x7c, /* 01111100 */ + 0x7c, /* 01111100 */ + 0xfe, /* 11111110 */ + 0xfe, /* 11111110 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 31 0x1f '^_' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0xfe, /* 11111110 */ + 0xfe, /* 11111110 */ + 0x7c, /* 01111100 */ + 0x7c, /* 01111100 */ + 0x38, /* 00111000 */ + 0x38, /* 00111000 */ + 0x10, /* 00010000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 32 0x20 ' ' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 33 0x21 '!' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x18, /* 00011000 */ + 0x3c, /* 00111100 */ + 0x3c, /* 00111100 */ + 0x3c, /* 00111100 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x00, /* 00000000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 34 0x22 '"' */ + 0x00, /* 00000000 */ + 0x66, /* 01100110 */ + 0x66, /* 01100110 */ + 0x66, /* 01100110 */ + 0x24, /* 00100100 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 35 0x23 '#' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x6c, /* 01101100 */ + 0x6c, /* 01101100 */ + 0xfe, /* 11111110 */ + 0x6c, /* 01101100 */ + 0x6c, /* 01101100 */ + 0x6c, /* 01101100 */ + 0xfe, /* 11111110 */ + 0x6c, /* 01101100 */ + 0x6c, /* 01101100 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 36 0x24 '$' */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x7c, /* 01111100 */ + 0xc6, /* 11000110 */ + 0xc2, /* 11000010 */ + 0xc0, /* 11000000 */ + 0x7c, /* 01111100 */ + 0x06, /* 00000110 */ + 0x06, /* 00000110 */ + 0x86, /* 10000110 */ + 0xc6, /* 11000110 */ + 0x7c, /* 01111100 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 37 0x25 '%' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0xc2, /* 11000010 */ + 0xc6, /* 11000110 */ + 0x0c, /* 00001100 */ + 0x18, /* 00011000 */ + 0x30, /* 00110000 */ + 0x60, /* 01100000 */ + 0xc6, /* 11000110 */ + 0x86, /* 10000110 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 38 0x26 '&' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x38, /* 00111000 */ + 0x6c, /* 01101100 */ + 0x6c, /* 01101100 */ + 0x38, /* 00111000 */ + 0x76, /* 01110110 */ + 0xdc, /* 11011100 */ + 0xcc, /* 11001100 */ + 0xcc, /* 11001100 */ + 0xcc, /* 11001100 */ + 0x76, /* 01110110 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 39 0x27 ''' */ + 0x00, /* 00000000 */ + 0x30, /* 00110000 */ + 0x30, /* 00110000 */ + 0x30, /* 00110000 */ + 0x60, /* 01100000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 40 0x28 '(' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x0c, /* 00001100 */ + 0x18, /* 00011000 */ + 0x30, /* 00110000 */ + 0x30, /* 00110000 */ + 0x30, /* 00110000 */ + 0x30, /* 00110000 */ + 0x30, /* 00110000 */ + 0x30, /* 00110000 */ + 0x18, /* 00011000 */ + 0x0c, /* 00001100 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 41 0x29 ')' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x30, /* 00110000 */ + 0x18, /* 00011000 */ + 0x0c, /* 00001100 */ + 0x0c, /* 00001100 */ + 0x0c, /* 00001100 */ + 0x0c, /* 00001100 */ + 0x0c, /* 00001100 */ + 0x0c, /* 00001100 */ + 0x18, /* 00011000 */ + 0x30, /* 00110000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 42 0x2a '*' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x66, /* 01100110 */ + 0x3c, /* 00111100 */ + 0xff, /* 11111111 */ + 0x3c, /* 00111100 */ + 0x66, /* 01100110 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 43 0x2b '+' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x7e, /* 01111110 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 44 0x2c ',' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x30, /* 00110000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 45 0x2d '-' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0xfe, /* 11111110 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 46 0x2e '.' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 47 0x2f '/' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x02, /* 00000010 */ + 0x06, /* 00000110 */ + 0x0c, /* 00001100 */ + 0x18, /* 00011000 */ + 0x30, /* 00110000 */ + 0x60, /* 01100000 */ + 0xc0, /* 11000000 */ + 0x80, /* 10000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 48 0x30 '0' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x38, /* 00111000 */ + 0x6c, /* 01101100 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0xd6, /* 11010110 */ + 0xd6, /* 11010110 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0x6c, /* 01101100 */ + 0x38, /* 00111000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 49 0x31 '1' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x18, /* 00011000 */ + 0x38, /* 00111000 */ + 0x78, /* 01111000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x7e, /* 01111110 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 50 0x32 '2' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x7c, /* 01111100 */ + 0xc6, /* 11000110 */ + 0x06, /* 00000110 */ + 0x0c, /* 00001100 */ + 0x18, /* 00011000 */ + 0x30, /* 00110000 */ + 0x60, /* 01100000 */ + 0xc0, /* 11000000 */ + 0xc6, /* 11000110 */ + 0xfe, /* 11111110 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 51 0x33 '3' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x7c, /* 01111100 */ + 0xc6, /* 11000110 */ + 0x06, /* 00000110 */ + 0x06, /* 00000110 */ + 0x3c, /* 00111100 */ + 0x06, /* 00000110 */ + 0x06, /* 00000110 */ + 0x06, /* 00000110 */ + 0xc6, /* 11000110 */ + 0x7c, /* 01111100 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 52 0x34 '4' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x0c, /* 00001100 */ + 0x1c, /* 00011100 */ + 0x3c, /* 00111100 */ + 0x6c, /* 01101100 */ + 0xcc, /* 11001100 */ + 0xfe, /* 11111110 */ + 0x0c, /* 00001100 */ + 0x0c, /* 00001100 */ + 0x0c, /* 00001100 */ + 0x1e, /* 00011110 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 53 0x35 '5' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0xfe, /* 11111110 */ + 0xc0, /* 11000000 */ + 0xc0, /* 11000000 */ + 0xc0, /* 11000000 */ + 0xfc, /* 11111100 */ + 0x06, /* 00000110 */ + 0x06, /* 00000110 */ + 0x06, /* 00000110 */ + 0xc6, /* 11000110 */ + 0x7c, /* 01111100 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 54 0x36 '6' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x38, /* 00111000 */ + 0x60, /* 01100000 */ + 0xc0, /* 11000000 */ + 0xc0, /* 11000000 */ + 0xfc, /* 11111100 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0x7c, /* 01111100 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 55 0x37 '7' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0xfe, /* 11111110 */ + 0xc6, /* 11000110 */ + 0x06, /* 00000110 */ + 0x06, /* 00000110 */ + 0x0c, /* 00001100 */ + 0x18, /* 00011000 */ + 0x30, /* 00110000 */ + 0x30, /* 00110000 */ + 0x30, /* 00110000 */ + 0x30, /* 00110000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 56 0x38 '8' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x7c, /* 01111100 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0x7c, /* 01111100 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0x7c, /* 01111100 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 57 0x39 '9' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x7c, /* 01111100 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0x7e, /* 01111110 */ + 0x06, /* 00000110 */ + 0x06, /* 00000110 */ + 0x06, /* 00000110 */ + 0x0c, /* 00001100 */ + 0x78, /* 01111000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 58 0x3a ':' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 59 0x3b ';' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x30, /* 00110000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 60 0x3c '<' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x06, /* 00000110 */ + 0x0c, /* 00001100 */ + 0x18, /* 00011000 */ + 0x30, /* 00110000 */ + 0x60, /* 01100000 */ + 0x30, /* 00110000 */ + 0x18, /* 00011000 */ + 0x0c, /* 00001100 */ + 0x06, /* 00000110 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 61 0x3d '=' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x7e, /* 01111110 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x7e, /* 01111110 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 62 0x3e '>' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x60, /* 01100000 */ + 0x30, /* 00110000 */ + 0x18, /* 00011000 */ + 0x0c, /* 00001100 */ + 0x06, /* 00000110 */ + 0x0c, /* 00001100 */ + 0x18, /* 00011000 */ + 0x30, /* 00110000 */ + 0x60, /* 01100000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 63 0x3f '?' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x7c, /* 01111100 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0x0c, /* 00001100 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x00, /* 00000000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 64 0x40 '@' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x7c, /* 01111100 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0xde, /* 11011110 */ + 0xde, /* 11011110 */ + 0xde, /* 11011110 */ + 0xdc, /* 11011100 */ + 0xc0, /* 11000000 */ + 0x7c, /* 01111100 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 65 0x41 'A' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x10, /* 00010000 */ + 0x38, /* 00111000 */ + 0x6c, /* 01101100 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0xfe, /* 11111110 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 66 0x42 'B' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0xfc, /* 11111100 */ + 0x66, /* 01100110 */ + 0x66, /* 01100110 */ + 0x66, /* 01100110 */ + 0x7c, /* 01111100 */ + 0x66, /* 01100110 */ + 0x66, /* 01100110 */ + 0x66, /* 01100110 */ + 0x66, /* 01100110 */ + 0xfc, /* 11111100 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 67 0x43 'C' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x3c, /* 00111100 */ + 0x66, /* 01100110 */ + 0xc2, /* 11000010 */ + 0xc0, /* 11000000 */ + 0xc0, /* 11000000 */ + 0xc0, /* 11000000 */ + 0xc0, /* 11000000 */ + 0xc2, /* 11000010 */ + 0x66, /* 01100110 */ + 0x3c, /* 00111100 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 68 0x44 'D' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0xf8, /* 11111000 */ + 0x6c, /* 01101100 */ + 0x66, /* 01100110 */ + 0x66, /* 01100110 */ + 0x66, /* 01100110 */ + 0x66, /* 01100110 */ + 0x66, /* 01100110 */ + 0x66, /* 01100110 */ + 0x6c, /* 01101100 */ + 0xf8, /* 11111000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 69 0x45 'E' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0xfe, /* 11111110 */ + 0x66, /* 01100110 */ + 0x62, /* 01100010 */ + 0x68, /* 01101000 */ + 0x78, /* 01111000 */ + 0x68, /* 01101000 */ + 0x60, /* 01100000 */ + 0x62, /* 01100010 */ + 0x66, /* 01100110 */ + 0xfe, /* 11111110 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 70 0x46 'F' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0xfe, /* 11111110 */ + 0x66, /* 01100110 */ + 0x62, /* 01100010 */ + 0x68, /* 01101000 */ + 0x78, /* 01111000 */ + 0x68, /* 01101000 */ + 0x60, /* 01100000 */ + 0x60, /* 01100000 */ + 0x60, /* 01100000 */ + 0xf0, /* 11110000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 71 0x47 'G' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x3c, /* 00111100 */ + 0x66, /* 01100110 */ + 0xc2, /* 11000010 */ + 0xc0, /* 11000000 */ + 0xc0, /* 11000000 */ + 0xde, /* 11011110 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0x66, /* 01100110 */ + 0x3a, /* 00111010 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 72 0x48 'H' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0xfe, /* 11111110 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 73 0x49 'I' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x3c, /* 00111100 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x3c, /* 00111100 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 74 0x4a 'J' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x1e, /* 00011110 */ + 0x0c, /* 00001100 */ + 0x0c, /* 00001100 */ + 0x0c, /* 00001100 */ + 0x0c, /* 00001100 */ + 0x0c, /* 00001100 */ + 0xcc, /* 11001100 */ + 0xcc, /* 11001100 */ + 0xcc, /* 11001100 */ + 0x78, /* 01111000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 75 0x4b 'K' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0xe6, /* 11100110 */ + 0x66, /* 01100110 */ + 0x66, /* 01100110 */ + 0x6c, /* 01101100 */ + 0x78, /* 01111000 */ + 0x78, /* 01111000 */ + 0x6c, /* 01101100 */ + 0x66, /* 01100110 */ + 0x66, /* 01100110 */ + 0xe6, /* 11100110 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 76 0x4c 'L' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0xf0, /* 11110000 */ + 0x60, /* 01100000 */ + 0x60, /* 01100000 */ + 0x60, /* 01100000 */ + 0x60, /* 01100000 */ + 0x60, /* 01100000 */ + 0x60, /* 01100000 */ + 0x62, /* 01100010 */ + 0x66, /* 01100110 */ + 0xfe, /* 11111110 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 77 0x4d 'M' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0xc6, /* 11000110 */ + 0xee, /* 11101110 */ + 0xfe, /* 11111110 */ + 0xfe, /* 11111110 */ + 0xd6, /* 11010110 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 78 0x4e 'N' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0xc6, /* 11000110 */ + 0xe6, /* 11100110 */ + 0xf6, /* 11110110 */ + 0xfe, /* 11111110 */ + 0xde, /* 11011110 */ + 0xce, /* 11001110 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 79 0x4f 'O' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x7c, /* 01111100 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0x7c, /* 01111100 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 80 0x50 'P' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0xfc, /* 11111100 */ + 0x66, /* 01100110 */ + 0x66, /* 01100110 */ + 0x66, /* 01100110 */ + 0x7c, /* 01111100 */ + 0x60, /* 01100000 */ + 0x60, /* 01100000 */ + 0x60, /* 01100000 */ + 0x60, /* 01100000 */ + 0xf0, /* 11110000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 81 0x51 'Q' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x7c, /* 01111100 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0xd6, /* 11010110 */ + 0xde, /* 11011110 */ + 0x7c, /* 01111100 */ + 0x0c, /* 00001100 */ + 0x0e, /* 00001110 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 82 0x52 'R' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0xfc, /* 11111100 */ + 0x66, /* 01100110 */ + 0x66, /* 01100110 */ + 0x66, /* 01100110 */ + 0x7c, /* 01111100 */ + 0x6c, /* 01101100 */ + 0x66, /* 01100110 */ + 0x66, /* 01100110 */ + 0x66, /* 01100110 */ + 0xe6, /* 11100110 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 83 0x53 'S' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x7c, /* 01111100 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0x60, /* 01100000 */ + 0x38, /* 00111000 */ + 0x0c, /* 00001100 */ + 0x06, /* 00000110 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0x7c, /* 01111100 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 84 0x54 'T' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x7e, /* 01111110 */ + 0x7e, /* 01111110 */ + 0x5a, /* 01011010 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x3c, /* 00111100 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 85 0x55 'U' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0x7c, /* 01111100 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 86 0x56 'V' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0x6c, /* 01101100 */ + 0x38, /* 00111000 */ + 0x10, /* 00010000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 87 0x57 'W' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0xd6, /* 11010110 */ + 0xd6, /* 11010110 */ + 0xd6, /* 11010110 */ + 0xfe, /* 11111110 */ + 0xee, /* 11101110 */ + 0x6c, /* 01101100 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 88 0x58 'X' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0x6c, /* 01101100 */ + 0x7c, /* 01111100 */ + 0x38, /* 00111000 */ + 0x38, /* 00111000 */ + 0x7c, /* 01111100 */ + 0x6c, /* 01101100 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 89 0x59 'Y' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x66, /* 01100110 */ + 0x66, /* 01100110 */ + 0x66, /* 01100110 */ + 0x66, /* 01100110 */ + 0x3c, /* 00111100 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x3c, /* 00111100 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 90 0x5a 'Z' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0xfe, /* 11111110 */ + 0xc6, /* 11000110 */ + 0x86, /* 10000110 */ + 0x0c, /* 00001100 */ + 0x18, /* 00011000 */ + 0x30, /* 00110000 */ + 0x60, /* 01100000 */ + 0xc2, /* 11000010 */ + 0xc6, /* 11000110 */ + 0xfe, /* 11111110 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 91 0x5b '[' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x3c, /* 00111100 */ + 0x30, /* 00110000 */ + 0x30, /* 00110000 */ + 0x30, /* 00110000 */ + 0x30, /* 00110000 */ + 0x30, /* 00110000 */ + 0x30, /* 00110000 */ + 0x30, /* 00110000 */ + 0x30, /* 00110000 */ + 0x3c, /* 00111100 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 92 0x5c '\' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x80, /* 10000000 */ + 0xc0, /* 11000000 */ + 0xe0, /* 11100000 */ + 0x70, /* 01110000 */ + 0x38, /* 00111000 */ + 0x1c, /* 00011100 */ + 0x0e, /* 00001110 */ + 0x06, /* 00000110 */ + 0x02, /* 00000010 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 93 0x5d ']' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x3c, /* 00111100 */ + 0x0c, /* 00001100 */ + 0x0c, /* 00001100 */ + 0x0c, /* 00001100 */ + 0x0c, /* 00001100 */ + 0x0c, /* 00001100 */ + 0x0c, /* 00001100 */ + 0x0c, /* 00001100 */ + 0x0c, /* 00001100 */ + 0x3c, /* 00111100 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 94 0x5e '^' */ + 0x10, /* 00010000 */ + 0x38, /* 00111000 */ + 0x6c, /* 01101100 */ + 0xc6, /* 11000110 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 95 0x5f '_' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0xff, /* 11111111 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 96 0x60 '`' */ + 0x00, /* 00000000 */ + 0x30, /* 00110000 */ + 0x18, /* 00011000 */ + 0x0c, /* 00001100 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 97 0x61 'a' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x78, /* 01111000 */ + 0x0c, /* 00001100 */ + 0x7c, /* 01111100 */ + 0xcc, /* 11001100 */ + 0xcc, /* 11001100 */ + 0xcc, /* 11001100 */ + 0x76, /* 01110110 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 98 0x62 'b' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0xe0, /* 11100000 */ + 0x60, /* 01100000 */ + 0x60, /* 01100000 */ + 0x78, /* 01111000 */ + 0x6c, /* 01101100 */ + 0x66, /* 01100110 */ + 0x66, /* 01100110 */ + 0x66, /* 01100110 */ + 0x66, /* 01100110 */ + 0x7c, /* 01111100 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 99 0x63 'c' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x7c, /* 01111100 */ + 0xc6, /* 11000110 */ + 0xc0, /* 11000000 */ + 0xc0, /* 11000000 */ + 0xc0, /* 11000000 */ + 0xc6, /* 11000110 */ + 0x7c, /* 01111100 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 100 0x64 'd' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x1c, /* 00011100 */ + 0x0c, /* 00001100 */ + 0x0c, /* 00001100 */ + 0x3c, /* 00111100 */ + 0x6c, /* 01101100 */ + 0xcc, /* 11001100 */ + 0xcc, /* 11001100 */ + 0xcc, /* 11001100 */ + 0xcc, /* 11001100 */ + 0x76, /* 01110110 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 101 0x65 'e' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x7c, /* 01111100 */ + 0xc6, /* 11000110 */ + 0xfe, /* 11111110 */ + 0xc0, /* 11000000 */ + 0xc0, /* 11000000 */ + 0xc6, /* 11000110 */ + 0x7c, /* 01111100 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 102 0x66 'f' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x1c, /* 00011100 */ + 0x36, /* 00110110 */ + 0x32, /* 00110010 */ + 0x30, /* 00110000 */ + 0x78, /* 01111000 */ + 0x30, /* 00110000 */ + 0x30, /* 00110000 */ + 0x30, /* 00110000 */ + 0x30, /* 00110000 */ + 0x78, /* 01111000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 103 0x67 'g' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x76, /* 01110110 */ + 0xcc, /* 11001100 */ + 0xcc, /* 11001100 */ + 0xcc, /* 11001100 */ + 0xcc, /* 11001100 */ + 0xcc, /* 11001100 */ + 0x7c, /* 01111100 */ + 0x0c, /* 00001100 */ + 0xcc, /* 11001100 */ + 0x78, /* 01111000 */ + 0x00, /* 00000000 */ + + /* 104 0x68 'h' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0xe0, /* 11100000 */ + 0x60, /* 01100000 */ + 0x60, /* 01100000 */ + 0x6c, /* 01101100 */ + 0x76, /* 01110110 */ + 0x66, /* 01100110 */ + 0x66, /* 01100110 */ + 0x66, /* 01100110 */ + 0x66, /* 01100110 */ + 0xe6, /* 11100110 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 105 0x69 'i' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x00, /* 00000000 */ + 0x38, /* 00111000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x3c, /* 00111100 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 106 0x6a 'j' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x06, /* 00000110 */ + 0x06, /* 00000110 */ + 0x00, /* 00000000 */ + 0x0e, /* 00001110 */ + 0x06, /* 00000110 */ + 0x06, /* 00000110 */ + 0x06, /* 00000110 */ + 0x06, /* 00000110 */ + 0x06, /* 00000110 */ + 0x06, /* 00000110 */ + 0x66, /* 01100110 */ + 0x66, /* 01100110 */ + 0x3c, /* 00111100 */ + 0x00, /* 00000000 */ + + /* 107 0x6b 'k' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0xe0, /* 11100000 */ + 0x60, /* 01100000 */ + 0x60, /* 01100000 */ + 0x66, /* 01100110 */ + 0x6c, /* 01101100 */ + 0x78, /* 01111000 */ + 0x78, /* 01111000 */ + 0x6c, /* 01101100 */ + 0x66, /* 01100110 */ + 0xe6, /* 11100110 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 108 0x6c 'l' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x38, /* 00111000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x3c, /* 00111100 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 109 0x6d 'm' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0xec, /* 11101100 */ + 0xfe, /* 11111110 */ + 0xd6, /* 11010110 */ + 0xd6, /* 11010110 */ + 0xd6, /* 11010110 */ + 0xd6, /* 11010110 */ + 0xc6, /* 11000110 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 110 0x6e 'n' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0xdc, /* 11011100 */ + 0x66, /* 01100110 */ + 0x66, /* 01100110 */ + 0x66, /* 01100110 */ + 0x66, /* 01100110 */ + 0x66, /* 01100110 */ + 0x66, /* 01100110 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 111 0x6f 'o' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x7c, /* 01111100 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0x7c, /* 01111100 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 112 0x70 'p' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0xdc, /* 11011100 */ + 0x66, /* 01100110 */ + 0x66, /* 01100110 */ + 0x66, /* 01100110 */ + 0x66, /* 01100110 */ + 0x66, /* 01100110 */ + 0x7c, /* 01111100 */ + 0x60, /* 01100000 */ + 0x60, /* 01100000 */ + 0xf0, /* 11110000 */ + 0x00, /* 00000000 */ + + /* 113 0x71 'q' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x76, /* 01110110 */ + 0xcc, /* 11001100 */ + 0xcc, /* 11001100 */ + 0xcc, /* 11001100 */ + 0xcc, /* 11001100 */ + 0xcc, /* 11001100 */ + 0x7c, /* 01111100 */ + 0x0c, /* 00001100 */ + 0x0c, /* 00001100 */ + 0x1e, /* 00011110 */ + 0x00, /* 00000000 */ + + /* 114 0x72 'r' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0xdc, /* 11011100 */ + 0x76, /* 01110110 */ + 0x66, /* 01100110 */ + 0x60, /* 01100000 */ + 0x60, /* 01100000 */ + 0x60, /* 01100000 */ + 0xf0, /* 11110000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 115 0x73 's' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x7c, /* 01111100 */ + 0xc6, /* 11000110 */ + 0x60, /* 01100000 */ + 0x38, /* 00111000 */ + 0x0c, /* 00001100 */ + 0xc6, /* 11000110 */ + 0x7c, /* 01111100 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 116 0x74 't' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x10, /* 00010000 */ + 0x30, /* 00110000 */ + 0x30, /* 00110000 */ + 0xfc, /* 11111100 */ + 0x30, /* 00110000 */ + 0x30, /* 00110000 */ + 0x30, /* 00110000 */ + 0x30, /* 00110000 */ + 0x36, /* 00110110 */ + 0x1c, /* 00011100 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 117 0x75 'u' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0xcc, /* 11001100 */ + 0xcc, /* 11001100 */ + 0xcc, /* 11001100 */ + 0xcc, /* 11001100 */ + 0xcc, /* 11001100 */ + 0xcc, /* 11001100 */ + 0x76, /* 01110110 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 118 0x76 'v' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0x6c, /* 01101100 */ + 0x38, /* 00111000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 119 0x77 'w' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0xd6, /* 11010110 */ + 0xd6, /* 11010110 */ + 0xd6, /* 11010110 */ + 0xfe, /* 11111110 */ + 0x6c, /* 01101100 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 120 0x78 'x' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0xc6, /* 11000110 */ + 0x6c, /* 01101100 */ + 0x38, /* 00111000 */ + 0x38, /* 00111000 */ + 0x38, /* 00111000 */ + 0x6c, /* 01101100 */ + 0xc6, /* 11000110 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 121 0x79 'y' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0x7e, /* 01111110 */ + 0x06, /* 00000110 */ + 0x0c, /* 00001100 */ + 0xf8, /* 11111000 */ + 0x00, /* 00000000 */ + + /* 122 0x7a 'z' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0xfe, /* 11111110 */ + 0xcc, /* 11001100 */ + 0x18, /* 00011000 */ + 0x30, /* 00110000 */ + 0x60, /* 01100000 */ + 0xc6, /* 11000110 */ + 0xfe, /* 11111110 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 123 0x7b '{' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x0e, /* 00001110 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x70, /* 01110000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x0e, /* 00001110 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 124 0x7c '|' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 125 0x7d '}' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x70, /* 01110000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x0e, /* 00001110 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x70, /* 01110000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 126 0x7e '~' */ + 0x00, /* 00000000 */ + 0x76, /* 01110110 */ + 0xdc, /* 11011100 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 127 0x7f '' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x10, /* 00010000 */ + 0x38, /* 00111000 */ + 0x6c, /* 01101100 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0xfe, /* 11111110 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 128 0x80 '€' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x3c, /* 00111100 */ + 0x66, /* 01100110 */ + 0xc2, /* 11000010 */ + 0xc0, /* 11000000 */ + 0xc0, /* 11000000 */ + 0xc0, /* 11000000 */ + 0xc0, /* 11000000 */ + 0xc2, /* 11000010 */ + 0x66, /* 01100110 */ + 0x3c, /* 00111100 */ + 0x18, /* 00011000 */ + 0x70, /* 01110000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 129 0x81 '' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0xcc, /* 11001100 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0xcc, /* 11001100 */ + 0xcc, /* 11001100 */ + 0xcc, /* 11001100 */ + 0xcc, /* 11001100 */ + 0xcc, /* 11001100 */ + 0xcc, /* 11001100 */ + 0x76, /* 01110110 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 130 0x82 '‚' */ + 0x00, /* 00000000 */ + 0x0c, /* 00001100 */ + 0x18, /* 00011000 */ + 0x30, /* 00110000 */ + 0x00, /* 00000000 */ + 0x7c, /* 01111100 */ + 0xc6, /* 11000110 */ + 0xfe, /* 11111110 */ + 0xc0, /* 11000000 */ + 0xc0, /* 11000000 */ + 0xc6, /* 11000110 */ + 0x7c, /* 01111100 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 131 0x83 'ƒ' */ + 0x00, /* 00000000 */ + 0x10, /* 00010000 */ + 0x38, /* 00111000 */ + 0x6c, /* 01101100 */ + 0x00, /* 00000000 */ + 0x78, /* 01111000 */ + 0x0c, /* 00001100 */ + 0x7c, /* 01111100 */ + 0xcc, /* 11001100 */ + 0xcc, /* 11001100 */ + 0xcc, /* 11001100 */ + 0x76, /* 01110110 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 132 0x84 '„' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0xcc, /* 11001100 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x78, /* 01111000 */ + 0x0c, /* 00001100 */ + 0x7c, /* 01111100 */ + 0xcc, /* 11001100 */ + 0xcc, /* 11001100 */ + 0xcc, /* 11001100 */ + 0x76, /* 01110110 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 133 0x85 '…' */ + 0x00, /* 00000000 */ + 0x60, /* 01100000 */ + 0x30, /* 00110000 */ + 0x18, /* 00011000 */ + 0x00, /* 00000000 */ + 0x78, /* 01111000 */ + 0x0c, /* 00001100 */ + 0x7c, /* 01111100 */ + 0xcc, /* 11001100 */ + 0xcc, /* 11001100 */ + 0xcc, /* 11001100 */ + 0x76, /* 01110110 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 134 0x86 '†' */ + 0x00, /* 00000000 */ + 0x38, /* 00111000 */ + 0x6c, /* 01101100 */ + 0x38, /* 00111000 */ + 0x00, /* 00000000 */ + 0x78, /* 01111000 */ + 0x0c, /* 00001100 */ + 0x7c, /* 01111100 */ + 0xcc, /* 11001100 */ + 0xcc, /* 11001100 */ + 0xcc, /* 11001100 */ + 0x76, /* 01110110 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 135 0x87 '‡' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x7c, /* 01111100 */ + 0xc6, /* 11000110 */ + 0xc0, /* 11000000 */ + 0xc0, /* 11000000 */ + 0xc0, /* 11000000 */ + 0xc6, /* 11000110 */ + 0x7c, /* 01111100 */ + 0x18, /* 00011000 */ + 0x70, /* 01110000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 136 0x88 'ˆ' */ + 0x00, /* 00000000 */ + 0x10, /* 00010000 */ + 0x38, /* 00111000 */ + 0x6c, /* 01101100 */ + 0x00, /* 00000000 */ + 0x7c, /* 01111100 */ + 0xc6, /* 11000110 */ + 0xfe, /* 11111110 */ + 0xc0, /* 11000000 */ + 0xc0, /* 11000000 */ + 0xc6, /* 11000110 */ + 0x7c, /* 01111100 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 137 0x89 '‰' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0xc6, /* 11000110 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x7c, /* 01111100 */ + 0xc6, /* 11000110 */ + 0xfe, /* 11111110 */ + 0xc0, /* 11000000 */ + 0xc0, /* 11000000 */ + 0xc6, /* 11000110 */ + 0x7c, /* 01111100 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 138 0x8a 'Š' */ + 0x00, /* 00000000 */ + 0x60, /* 01100000 */ + 0x30, /* 00110000 */ + 0x18, /* 00011000 */ + 0x00, /* 00000000 */ + 0x7c, /* 01111100 */ + 0xc6, /* 11000110 */ + 0xfe, /* 11111110 */ + 0xc0, /* 11000000 */ + 0xc0, /* 11000000 */ + 0xc6, /* 11000110 */ + 0x7c, /* 01111100 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 139 0x8b '‹' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x66, /* 01100110 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x38, /* 00111000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x3c, /* 00111100 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 140 0x8c 'Œ' */ + 0x00, /* 00000000 */ + 0x18, /* 00011000 */ + 0x3c, /* 00111100 */ + 0x66, /* 01100110 */ + 0x00, /* 00000000 */ + 0x38, /* 00111000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x3c, /* 00111100 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 141 0x8d '' */ + 0x00, /* 00000000 */ + 0x60, /* 01100000 */ + 0x30, /* 00110000 */ + 0x18, /* 00011000 */ + 0x00, /* 00000000 */ + 0x38, /* 00111000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x3c, /* 00111100 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 142 0x8e 'Ž' */ + 0x00, /* 00000000 */ + 0xc6, /* 11000110 */ + 0x00, /* 00000000 */ + 0x10, /* 00010000 */ + 0x38, /* 00111000 */ + 0x6c, /* 01101100 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0xfe, /* 11111110 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 143 0x8f '' */ + 0x38, /* 00111000 */ + 0x6c, /* 01101100 */ + 0x38, /* 00111000 */ + 0x10, /* 00010000 */ + 0x38, /* 00111000 */ + 0x6c, /* 01101100 */ + 0xc6, /* 11000110 */ + 0xfe, /* 11111110 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 144 0x90 '' */ + 0x0c, /* 00001100 */ + 0x18, /* 00011000 */ + 0x00, /* 00000000 */ + 0xfe, /* 11111110 */ + 0x66, /* 01100110 */ + 0x62, /* 01100010 */ + 0x68, /* 01101000 */ + 0x78, /* 01111000 */ + 0x68, /* 01101000 */ + 0x62, /* 01100010 */ + 0x66, /* 01100110 */ + 0xfe, /* 11111110 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 145 0x91 '‘' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0xec, /* 11101100 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0x7e, /* 01111110 */ + 0xd8, /* 11011000 */ + 0xd8, /* 11011000 */ + 0x6e, /* 01101110 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 146 0x92 '’' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x3e, /* 00111110 */ + 0x6c, /* 01101100 */ + 0xcc, /* 11001100 */ + 0xcc, /* 11001100 */ + 0xfe, /* 11111110 */ + 0xcc, /* 11001100 */ + 0xcc, /* 11001100 */ + 0xcc, /* 11001100 */ + 0xcc, /* 11001100 */ + 0xce, /* 11001110 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 147 0x93 '“' */ + 0x00, /* 00000000 */ + 0x10, /* 00010000 */ + 0x38, /* 00111000 */ + 0x6c, /* 01101100 */ + 0x00, /* 00000000 */ + 0x7c, /* 01111100 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0x7c, /* 01111100 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 148 0x94 '”' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0xc6, /* 11000110 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x7c, /* 01111100 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0x7c, /* 01111100 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 149 0x95 '•' */ + 0x00, /* 00000000 */ + 0x60, /* 01100000 */ + 0x30, /* 00110000 */ + 0x18, /* 00011000 */ + 0x00, /* 00000000 */ + 0x7c, /* 01111100 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0x7c, /* 01111100 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 150 0x96 '–' */ + 0x00, /* 00000000 */ + 0x30, /* 00110000 */ + 0x78, /* 01111000 */ + 0xcc, /* 11001100 */ + 0x00, /* 00000000 */ + 0xcc, /* 11001100 */ + 0xcc, /* 11001100 */ + 0xcc, /* 11001100 */ + 0xcc, /* 11001100 */ + 0xcc, /* 11001100 */ + 0xcc, /* 11001100 */ + 0x76, /* 01110110 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 151 0x97 '—' */ + 0x00, /* 00000000 */ + 0x60, /* 01100000 */ + 0x30, /* 00110000 */ + 0x18, /* 00011000 */ + 0x00, /* 00000000 */ + 0xcc, /* 11001100 */ + 0xcc, /* 11001100 */ + 0xcc, /* 11001100 */ + 0xcc, /* 11001100 */ + 0xcc, /* 11001100 */ + 0xcc, /* 11001100 */ + 0x76, /* 01110110 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 152 0x98 '˜' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0xc6, /* 11000110 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0x7e, /* 01111110 */ + 0x06, /* 00000110 */ + 0x0c, /* 00001100 */ + 0x78, /* 01111000 */ + 0x00, /* 00000000 */ + + /* 153 0x99 '™' */ + 0x00, /* 00000000 */ + 0xc6, /* 11000110 */ + 0x00, /* 00000000 */ + 0x7c, /* 01111100 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0x7c, /* 01111100 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 154 0x9a 'š' */ + 0x00, /* 00000000 */ + 0xc6, /* 11000110 */ + 0x00, /* 00000000 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0x7c, /* 01111100 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 155 0x9b '›' */ + 0x00, /* 00000000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x7c, /* 01111100 */ + 0xc6, /* 11000110 */ + 0xc0, /* 11000000 */ + 0xc0, /* 11000000 */ + 0xc0, /* 11000000 */ + 0xc6, /* 11000110 */ + 0x7c, /* 01111100 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 156 0x9c 'œ' */ + 0x00, /* 00000000 */ + 0x38, /* 00111000 */ + 0x6c, /* 01101100 */ + 0x64, /* 01100100 */ + 0x60, /* 01100000 */ + 0xf0, /* 11110000 */ + 0x60, /* 01100000 */ + 0x60, /* 01100000 */ + 0x60, /* 01100000 */ + 0x60, /* 01100000 */ + 0xe6, /* 11100110 */ + 0xfc, /* 11111100 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 157 0x9d '' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x66, /* 01100110 */ + 0x66, /* 01100110 */ + 0x3c, /* 00111100 */ + 0x18, /* 00011000 */ + 0x7e, /* 01111110 */ + 0x18, /* 00011000 */ + 0x7e, /* 01111110 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 158 0x9e 'ž' */ + 0x00, /* 00000000 */ + 0xf8, /* 11111000 */ + 0xcc, /* 11001100 */ + 0xcc, /* 11001100 */ + 0xf8, /* 11111000 */ + 0xc4, /* 11000100 */ + 0xcc, /* 11001100 */ + 0xde, /* 11011110 */ + 0xcc, /* 11001100 */ + 0xcc, /* 11001100 */ + 0xcc, /* 11001100 */ + 0xc6, /* 11000110 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 159 0x9f 'Ÿ' */ + 0x00, /* 00000000 */ + 0x0e, /* 00001110 */ + 0x1b, /* 00011011 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x7e, /* 01111110 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0xd8, /* 11011000 */ + 0x70, /* 01110000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 160 0xa0 ' ' */ + 0x00, /* 00000000 */ + 0x18, /* 00011000 */ + 0x30, /* 00110000 */ + 0x60, /* 01100000 */ + 0x00, /* 00000000 */ + 0x78, /* 01111000 */ + 0x0c, /* 00001100 */ + 0x7c, /* 01111100 */ + 0xcc, /* 11001100 */ + 0xcc, /* 11001100 */ + 0xcc, /* 11001100 */ + 0x76, /* 01110110 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 161 0xa1 '¡' */ + 0x00, /* 00000000 */ + 0x0c, /* 00001100 */ + 0x18, /* 00011000 */ + 0x30, /* 00110000 */ + 0x00, /* 00000000 */ + 0x38, /* 00111000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x3c, /* 00111100 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 162 0xa2 '¢' */ + 0x00, /* 00000000 */ + 0x18, /* 00011000 */ + 0x30, /* 00110000 */ + 0x60, /* 01100000 */ + 0x00, /* 00000000 */ + 0x7c, /* 01111100 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0x7c, /* 01111100 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 163 0xa3 '£' */ + 0x00, /* 00000000 */ + 0x18, /* 00011000 */ + 0x30, /* 00110000 */ + 0x60, /* 01100000 */ + 0x00, /* 00000000 */ + 0xcc, /* 11001100 */ + 0xcc, /* 11001100 */ + 0xcc, /* 11001100 */ + 0xcc, /* 11001100 */ + 0xcc, /* 11001100 */ + 0xcc, /* 11001100 */ + 0x76, /* 01110110 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 164 0xa4 '¤' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x76, /* 01110110 */ + 0xdc, /* 11011100 */ + 0x00, /* 00000000 */ + 0xdc, /* 11011100 */ + 0x66, /* 01100110 */ + 0x66, /* 01100110 */ + 0x66, /* 01100110 */ + 0x66, /* 01100110 */ + 0x66, /* 01100110 */ + 0x66, /* 01100110 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 165 0xa5 '¥' */ + 0x76, /* 01110110 */ + 0xdc, /* 11011100 */ + 0x00, /* 00000000 */ + 0xc6, /* 11000110 */ + 0xe6, /* 11100110 */ + 0xf6, /* 11110110 */ + 0xfe, /* 11111110 */ + 0xde, /* 11011110 */ + 0xce, /* 11001110 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 166 0xa6 '¦' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x3c, /* 00111100 */ + 0x6c, /* 01101100 */ + 0x6c, /* 01101100 */ + 0x3e, /* 00111110 */ + 0x00, /* 00000000 */ + 0x7e, /* 01111110 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 167 0xa7 '§' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x38, /* 00111000 */ + 0x6c, /* 01101100 */ + 0x6c, /* 01101100 */ + 0x38, /* 00111000 */ + 0x00, /* 00000000 */ + 0x7c, /* 01111100 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 168 0xa8 '¨' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x30, /* 00110000 */ + 0x30, /* 00110000 */ + 0x00, /* 00000000 */ + 0x30, /* 00110000 */ + 0x30, /* 00110000 */ + 0x60, /* 01100000 */ + 0xc0, /* 11000000 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0x7c, /* 01111100 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 169 0xa9 '©' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0xfe, /* 11111110 */ + 0xc0, /* 11000000 */ + 0xc0, /* 11000000 */ + 0xc0, /* 11000000 */ + 0xc0, /* 11000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 170 0xaa 'ª' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0xfe, /* 11111110 */ + 0x06, /* 00000110 */ + 0x06, /* 00000110 */ + 0x06, /* 00000110 */ + 0x06, /* 00000110 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 171 0xab '«' */ + 0x00, /* 00000000 */ + 0x60, /* 01100000 */ + 0xe0, /* 11100000 */ + 0x62, /* 01100010 */ + 0x66, /* 01100110 */ + 0x6c, /* 01101100 */ + 0x18, /* 00011000 */ + 0x30, /* 00110000 */ + 0x60, /* 01100000 */ + 0xdc, /* 11011100 */ + 0x86, /* 10000110 */ + 0x0c, /* 00001100 */ + 0x18, /* 00011000 */ + 0x3e, /* 00111110 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 172 0xac '¬' */ + 0x00, /* 00000000 */ + 0x60, /* 01100000 */ + 0xe0, /* 11100000 */ + 0x62, /* 01100010 */ + 0x66, /* 01100110 */ + 0x6c, /* 01101100 */ + 0x18, /* 00011000 */ + 0x30, /* 00110000 */ + 0x66, /* 01100110 */ + 0xce, /* 11001110 */ + 0x9a, /* 10011010 */ + 0x3f, /* 00111111 */ + 0x06, /* 00000110 */ + 0x06, /* 00000110 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 173 0xad '­' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x00, /* 00000000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x3c, /* 00111100 */ + 0x3c, /* 00111100 */ + 0x3c, /* 00111100 */ + 0x18, /* 00011000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 174 0xae '®' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x36, /* 00110110 */ + 0x6c, /* 01101100 */ + 0xd8, /* 11011000 */ + 0x6c, /* 01101100 */ + 0x36, /* 00110110 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 175 0xaf '¯' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0xd8, /* 11011000 */ + 0x6c, /* 01101100 */ + 0x36, /* 00110110 */ + 0x6c, /* 01101100 */ + 0xd8, /* 11011000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 176 0xb0 '°' */ + 0x11, /* 00010001 */ + 0x44, /* 01000100 */ + 0x11, /* 00010001 */ + 0x44, /* 01000100 */ + 0x11, /* 00010001 */ + 0x44, /* 01000100 */ + 0x11, /* 00010001 */ + 0x44, /* 01000100 */ + 0x11, /* 00010001 */ + 0x44, /* 01000100 */ + 0x11, /* 00010001 */ + 0x44, /* 01000100 */ + 0x11, /* 00010001 */ + 0x44, /* 01000100 */ + 0x11, /* 00010001 */ + 0x44, /* 01000100 */ + + /* 177 0xb1 '±' */ + 0x55, /* 01010101 */ + 0xaa, /* 10101010 */ + 0x55, /* 01010101 */ + 0xaa, /* 10101010 */ + 0x55, /* 01010101 */ + 0xaa, /* 10101010 */ + 0x55, /* 01010101 */ + 0xaa, /* 10101010 */ + 0x55, /* 01010101 */ + 0xaa, /* 10101010 */ + 0x55, /* 01010101 */ + 0xaa, /* 10101010 */ + 0x55, /* 01010101 */ + 0xaa, /* 10101010 */ + 0x55, /* 01010101 */ + 0xaa, /* 10101010 */ + + /* 178 0xb2 '²' */ + 0xdd, /* 11011101 */ + 0x77, /* 01110111 */ + 0xdd, /* 11011101 */ + 0x77, /* 01110111 */ + 0xdd, /* 11011101 */ + 0x77, /* 01110111 */ + 0xdd, /* 11011101 */ + 0x77, /* 01110111 */ + 0xdd, /* 11011101 */ + 0x77, /* 01110111 */ + 0xdd, /* 11011101 */ + 0x77, /* 01110111 */ + 0xdd, /* 11011101 */ + 0x77, /* 01110111 */ + 0xdd, /* 11011101 */ + 0x77, /* 01110111 */ + + /* 179 0xb3 '³' */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + + /* 180 0xb4 '´' */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0xf8, /* 11111000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + + /* 181 0xb5 'µ' */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0xf8, /* 11111000 */ + 0x18, /* 00011000 */ + 0xf8, /* 11111000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + + /* 182 0xb6 '¶' */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0xf6, /* 11110110 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + + /* 183 0xb7 '·' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0xfe, /* 11111110 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + + /* 184 0xb8 '¸' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0xf8, /* 11111000 */ + 0x18, /* 00011000 */ + 0xf8, /* 11111000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + + /* 185 0xb9 '¹' */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0xf6, /* 11110110 */ + 0x06, /* 00000110 */ + 0xf6, /* 11110110 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + + /* 186 0xba 'º' */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + + /* 187 0xbb '»' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0xfe, /* 11111110 */ + 0x06, /* 00000110 */ + 0xf6, /* 11110110 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + + /* 188 0xbc '¼' */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0xf6, /* 11110110 */ + 0x06, /* 00000110 */ + 0xfe, /* 11111110 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 189 0xbd '½' */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0xfe, /* 11111110 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 190 0xbe '¾' */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0xf8, /* 11111000 */ + 0x18, /* 00011000 */ + 0xf8, /* 11111000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 191 0xbf '¿' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0xf8, /* 11111000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + + /* 192 0xc0 'À' */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x1f, /* 00011111 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 193 0xc1 'Á' */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0xff, /* 11111111 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 194 0xc2 'Â' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0xff, /* 11111111 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + + /* 195 0xc3 'Ã' */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x1f, /* 00011111 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + + /* 196 0xc4 'Ä' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0xff, /* 11111111 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 197 0xc5 'Å' */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0xff, /* 11111111 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + + /* 198 0xc6 'Æ' */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x1f, /* 00011111 */ + 0x18, /* 00011000 */ + 0x1f, /* 00011111 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + + /* 199 0xc7 'Ç' */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0x37, /* 00110111 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + + /* 200 0xc8 'È' */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0x37, /* 00110111 */ + 0x30, /* 00110000 */ + 0x3f, /* 00111111 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 201 0xc9 'É' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x3f, /* 00111111 */ + 0x30, /* 00110000 */ + 0x37, /* 00110111 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + + /* 202 0xca 'Ê' */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0xf7, /* 11110111 */ + 0x00, /* 00000000 */ + 0xff, /* 11111111 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 203 0xcb 'Ë' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0xff, /* 11111111 */ + 0x00, /* 00000000 */ + 0xf7, /* 11110111 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + + /* 204 0xcc 'Ì' */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0x37, /* 00110111 */ + 0x30, /* 00110000 */ + 0x37, /* 00110111 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + + /* 205 0xcd 'Í' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0xff, /* 11111111 */ + 0x00, /* 00000000 */ + 0xff, /* 11111111 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 206 0xce 'Î' */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0xf7, /* 11110111 */ + 0x00, /* 00000000 */ + 0xf7, /* 11110111 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + + /* 207 0xcf 'Ï' */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0xff, /* 11111111 */ + 0x00, /* 00000000 */ + 0xff, /* 11111111 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 208 0xd0 'Ð' */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0xff, /* 11111111 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 209 0xd1 'Ñ' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0xff, /* 11111111 */ + 0x00, /* 00000000 */ + 0xff, /* 11111111 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + + /* 210 0xd2 'Ò' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0xff, /* 11111111 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + + /* 211 0xd3 'Ó' */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0x3f, /* 00111111 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 212 0xd4 'Ô' */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x1f, /* 00011111 */ + 0x18, /* 00011000 */ + 0x1f, /* 00011111 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 213 0xd5 'Õ' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x1f, /* 00011111 */ + 0x18, /* 00011000 */ + 0x1f, /* 00011111 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + + /* 214 0xd6 'Ö' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x3f, /* 00111111 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + + /* 215 0xd7 '×' */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0xff, /* 11111111 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + + /* 216 0xd8 'Ø' */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0xff, /* 11111111 */ + 0x18, /* 00011000 */ + 0xff, /* 11111111 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + + /* 217 0xd9 'Ù' */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0xf8, /* 11111000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 218 0xda 'Ú' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x1f, /* 00011111 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + + /* 219 0xdb 'Û' */ + 0xff, /* 11111111 */ + 0xff, /* 11111111 */ + 0xff, /* 11111111 */ + 0xff, /* 11111111 */ + 0xff, /* 11111111 */ + 0xff, /* 11111111 */ + 0xff, /* 11111111 */ + 0xff, /* 11111111 */ + 0xff, /* 11111111 */ + 0xff, /* 11111111 */ + 0xff, /* 11111111 */ + 0xff, /* 11111111 */ + 0xff, /* 11111111 */ + 0xff, /* 11111111 */ + 0xff, /* 11111111 */ + 0xff, /* 11111111 */ + + /* 220 0xdc 'Ü' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0xff, /* 11111111 */ + 0xff, /* 11111111 */ + 0xff, /* 11111111 */ + 0xff, /* 11111111 */ + 0xff, /* 11111111 */ + 0xff, /* 11111111 */ + 0xff, /* 11111111 */ + 0xff, /* 11111111 */ + 0xff, /* 11111111 */ + + /* 221 0xdd 'Ý' */ + 0xf0, /* 11110000 */ + 0xf0, /* 11110000 */ + 0xf0, /* 11110000 */ + 0xf0, /* 11110000 */ + 0xf0, /* 11110000 */ + 0xf0, /* 11110000 */ + 0xf0, /* 11110000 */ + 0xf0, /* 11110000 */ + 0xf0, /* 11110000 */ + 0xf0, /* 11110000 */ + 0xf0, /* 11110000 */ + 0xf0, /* 11110000 */ + 0xf0, /* 11110000 */ + 0xf0, /* 11110000 */ + 0xf0, /* 11110000 */ + 0xf0, /* 11110000 */ + + /* 222 0xde 'Þ' */ + 0x0f, /* 00001111 */ + 0x0f, /* 00001111 */ + 0x0f, /* 00001111 */ + 0x0f, /* 00001111 */ + 0x0f, /* 00001111 */ + 0x0f, /* 00001111 */ + 0x0f, /* 00001111 */ + 0x0f, /* 00001111 */ + 0x0f, /* 00001111 */ + 0x0f, /* 00001111 */ + 0x0f, /* 00001111 */ + 0x0f, /* 00001111 */ + 0x0f, /* 00001111 */ + 0x0f, /* 00001111 */ + 0x0f, /* 00001111 */ + 0x0f, /* 00001111 */ + + /* 223 0xdf 'ß' */ + 0xff, /* 11111111 */ + 0xff, /* 11111111 */ + 0xff, /* 11111111 */ + 0xff, /* 11111111 */ + 0xff, /* 11111111 */ + 0xff, /* 11111111 */ + 0xff, /* 11111111 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 224 0xe0 'à' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x76, /* 01110110 */ + 0xdc, /* 11011100 */ + 0xd8, /* 11011000 */ + 0xd8, /* 11011000 */ + 0xd8, /* 11011000 */ + 0xdc, /* 11011100 */ + 0x76, /* 01110110 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 225 0xe1 'á' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x78, /* 01111000 */ + 0xcc, /* 11001100 */ + 0xcc, /* 11001100 */ + 0xcc, /* 11001100 */ + 0xd8, /* 11011000 */ + 0xcc, /* 11001100 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0xcc, /* 11001100 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 226 0xe2 'â' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0xfe, /* 11111110 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0xc0, /* 11000000 */ + 0xc0, /* 11000000 */ + 0xc0, /* 11000000 */ + 0xc0, /* 11000000 */ + 0xc0, /* 11000000 */ + 0xc0, /* 11000000 */ + 0xc0, /* 11000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 227 0xe3 'ã' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0xfe, /* 11111110 */ + 0x6c, /* 01101100 */ + 0x6c, /* 01101100 */ + 0x6c, /* 01101100 */ + 0x6c, /* 01101100 */ + 0x6c, /* 01101100 */ + 0x6c, /* 01101100 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 228 0xe4 'ä' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0xfe, /* 11111110 */ + 0xc6, /* 11000110 */ + 0x60, /* 01100000 */ + 0x30, /* 00110000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x30, /* 00110000 */ + 0x60, /* 01100000 */ + 0xc6, /* 11000110 */ + 0xfe, /* 11111110 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 229 0xe5 'å' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x7e, /* 01111110 */ + 0xd8, /* 11011000 */ + 0xd8, /* 11011000 */ + 0xd8, /* 11011000 */ + 0xd8, /* 11011000 */ + 0xd8, /* 11011000 */ + 0x70, /* 01110000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 230 0xe6 'æ' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x66, /* 01100110 */ + 0x66, /* 01100110 */ + 0x66, /* 01100110 */ + 0x66, /* 01100110 */ + 0x66, /* 01100110 */ + 0x66, /* 01100110 */ + 0x7c, /* 01111100 */ + 0x60, /* 01100000 */ + 0x60, /* 01100000 */ + 0xc0, /* 11000000 */ + 0x00, /* 00000000 */ + + /* 231 0xe7 'ç' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x76, /* 01110110 */ + 0xdc, /* 11011100 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 232 0xe8 'è' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x7e, /* 01111110 */ + 0x18, /* 00011000 */ + 0x3c, /* 00111100 */ + 0x66, /* 01100110 */ + 0x66, /* 01100110 */ + 0x66, /* 01100110 */ + 0x66, /* 01100110 */ + 0x3c, /* 00111100 */ + 0x18, /* 00011000 */ + 0x7e, /* 01111110 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 233 0xe9 'é' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x38, /* 00111000 */ + 0x6c, /* 01101100 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0xfe, /* 11111110 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0x6c, /* 01101100 */ + 0x38, /* 00111000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 234 0xea 'ê' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x38, /* 00111000 */ + 0x6c, /* 01101100 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0x6c, /* 01101100 */ + 0x6c, /* 01101100 */ + 0x6c, /* 01101100 */ + 0x6c, /* 01101100 */ + 0xee, /* 11101110 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 235 0xeb 'ë' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x1e, /* 00011110 */ + 0x30, /* 00110000 */ + 0x18, /* 00011000 */ + 0x0c, /* 00001100 */ + 0x3e, /* 00111110 */ + 0x66, /* 01100110 */ + 0x66, /* 01100110 */ + 0x66, /* 01100110 */ + 0x66, /* 01100110 */ + 0x3c, /* 00111100 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 236 0xec 'ì' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x7e, /* 01111110 */ + 0xdb, /* 11011011 */ + 0xdb, /* 11011011 */ + 0xdb, /* 11011011 */ + 0x7e, /* 01111110 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 237 0xed 'í' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x03, /* 00000011 */ + 0x06, /* 00000110 */ + 0x7e, /* 01111110 */ + 0xdb, /* 11011011 */ + 0xdb, /* 11011011 */ + 0xf3, /* 11110011 */ + 0x7e, /* 01111110 */ + 0x60, /* 01100000 */ + 0xc0, /* 11000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 238 0xee 'î' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x1c, /* 00011100 */ + 0x30, /* 00110000 */ + 0x60, /* 01100000 */ + 0x60, /* 01100000 */ + 0x7c, /* 01111100 */ + 0x60, /* 01100000 */ + 0x60, /* 01100000 */ + 0x60, /* 01100000 */ + 0x30, /* 00110000 */ + 0x1c, /* 00011100 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 239 0xef 'ï' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x7c, /* 01111100 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0xc6, /* 11000110 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 240 0xf0 'ð' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0xfe, /* 11111110 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0xfe, /* 11111110 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0xfe, /* 11111110 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 241 0xf1 'ñ' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x7e, /* 01111110 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x7e, /* 01111110 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 242 0xf2 'ò' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x30, /* 00110000 */ + 0x18, /* 00011000 */ + 0x0c, /* 00001100 */ + 0x06, /* 00000110 */ + 0x0c, /* 00001100 */ + 0x18, /* 00011000 */ + 0x30, /* 00110000 */ + 0x00, /* 00000000 */ + 0x7e, /* 01111110 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 243 0xf3 'ó' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x0c, /* 00001100 */ + 0x18, /* 00011000 */ + 0x30, /* 00110000 */ + 0x60, /* 01100000 */ + 0x30, /* 00110000 */ + 0x18, /* 00011000 */ + 0x0c, /* 00001100 */ + 0x00, /* 00000000 */ + 0x7e, /* 01111110 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 244 0xf4 'ô' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x0e, /* 00001110 */ + 0x1b, /* 00011011 */ + 0x1b, /* 00011011 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + + /* 245 0xf5 'õ' */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0xd8, /* 11011000 */ + 0xd8, /* 11011000 */ + 0xd8, /* 11011000 */ + 0x70, /* 01110000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 246 0xf6 'ö' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x18, /* 00011000 */ + 0x00, /* 00000000 */ + 0x7e, /* 01111110 */ + 0x00, /* 00000000 */ + 0x18, /* 00011000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 247 0xf7 '÷' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x76, /* 01110110 */ + 0xdc, /* 11011100 */ + 0x00, /* 00000000 */ + 0x76, /* 01110110 */ + 0xdc, /* 11011100 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 248 0xf8 'ø' */ + 0x00, /* 00000000 */ + 0x38, /* 00111000 */ + 0x6c, /* 01101100 */ + 0x6c, /* 01101100 */ + 0x38, /* 00111000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 249 0xf9 'ù' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 250 0xfa 'ú' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x18, /* 00011000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 251 0xfb 'û' */ + 0x00, /* 00000000 */ + 0x0f, /* 00001111 */ + 0x0c, /* 00001100 */ + 0x0c, /* 00001100 */ + 0x0c, /* 00001100 */ + 0x0c, /* 00001100 */ + 0x0c, /* 00001100 */ + 0xec, /* 11101100 */ + 0x6c, /* 01101100 */ + 0x6c, /* 01101100 */ + 0x3c, /* 00111100 */ + 0x1c, /* 00011100 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 252 0xfc 'ü' */ + 0x00, /* 00000000 */ + 0x6c, /* 01101100 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0x36, /* 00110110 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 253 0xfd 'ý' */ + 0x00, /* 00000000 */ + 0x3c, /* 00111100 */ + 0x66, /* 01100110 */ + 0x0c, /* 00001100 */ + 0x18, /* 00011000 */ + 0x32, /* 00110010 */ + 0x7e, /* 01111110 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 254 0xfe 'þ' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x7e, /* 01111110 */ + 0x7e, /* 01111110 */ + 0x7e, /* 01111110 */ + 0x7e, /* 01111110 */ + 0x7e, /* 01111110 */ + 0x7e, /* 01111110 */ + 0x7e, /* 01111110 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + + /* 255 0xff 'ÿ' */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + +}; diff --git a/include/fs.h b/include/fs.h new file mode 100644 index 0000000..29bad86 --- /dev/null +++ b/include/fs.h @@ -0,0 +1,203 @@ +// SCHFS - On-disk file system format. +// Both the kernel and user programs use this header file. +// +// Changes from original: +// - struct dinode gains atime, mtime, ctime (timestamps) and checksum +// (CRC32c). Padding reduced from 44 to 28 bytes; struct stays 128 bytes. +// - struct superblock gains checksum field. +// - struct fsv1_extent added for T_EXTENT inode block mapping. +// - OVERFLOW_DIR_START / OVERFLOW_DIR_END replace the bare "113 * BSIZE" +// magic numbers used in fs.c and mkfs.c. + +#ifndef FS_H +#define FS_H + +#define ROOTINO 1 // root i-number +#define BSIZE 1024 // block size + +// Permission mode bits (rwmode field) +#define RW 3 // 11 read+write +#define W_ 1 // 01 write-only +#define R_ 2 // 10 read-only +#define NONE 0 // 00 no access + +// --------------------------------------------------------------------------- +// Superblock +// --------------------------------------------------------------------------- +// Disk layout: +// [ boot | super | log | inode-bitmap | inode-blocks | block-bitmap | data ] + +struct superblock { + uint magic; // Must be FSMAGIC + uint size; // Size of file system image (blocks) + uint nblocks; // Number of data blocks + uint ninodes; // Number of inodes + uint nlog; // Number of log blocks + uint logstart; // Block number of first log block + uint inodestart; // Block number of first inode block + uint bmapstart; // Block number of first free-block bitmap block + uint ibmapstart; // Block number of first inode-bitmap block + uint freeinodes; // Count of free inodes + uint freeblocks; // Count of free blocks + uint checksum; // CRC32c of this struct (field zeroed during computation) +}; + +extern struct superblock sb; + +#define FSMAGIC 0x10203040 + +// --------------------------------------------------------------------------- +// Block / inode geometry +// --------------------------------------------------------------------------- + +#define NDIRECT 12 +#define NINDIRECT (BSIZE / sizeof(uint)) + +// Maximum file size in blocks: direct + 1x + 2x + 3x indirect +#define MAXFILE (NDIRECT \ ++ NINDIRECT \ ++ (NINDIRECT * NINDIRECT) \ ++ (NINDIRECT * NINDIRECT * NINDIRECT)) + +// --------------------------------------------------------------------------- +// Extent descriptor (T_EXTENT inodes only) +// +// Up to MAX_INLINE_EXTENTS are packed into ip->addrs[0..NDIRECT-1]. +// addrs[] = NDIRECT uints = 12*4 = 48 bytes +// each fsv1_extent = 12 bytes → 48/12 = 4 inline extents +// (addrs[NDIRECT], [NDIRECT+1], [NDIRECT+2] remain as indirect pointers) +// +// For files needing more extents, addrs[NDIRECT] points to an indirect block +// of BSIZE/sizeof(struct fsv1_extent) = 85 additional extents. +// --------------------------------------------------------------------------- + +// Renamed from "schfs_extent". The new format in include/schfs.h has its own, +// completely different, struct of that name -- 64-bit fields and a refcount -- +// and any file including both headers hit a redefinition error. Two structs +// with one name and two layouts is a trap worth removing rather than working +// around; this one belongs to the v1 format and dies with it. +struct fsv1_extent { + uint start_block; // logical block number in the file (first of run) + uint start_blkaddr; // physical disk block address + uint len; // number of contiguous blocks in this run +}; + +// Inline extents packed into addrs[0 .. NDIRECT-1] only. +#define MAX_INLINE_EXTENTS (NDIRECT * sizeof(uint) / sizeof(struct fsv1_extent)) + +// --------------------------------------------------------------------------- +// On-disk inode (exactly 128 bytes; IPB = 8 inodes per 1024-byte block) +// +// Byte layout: +// 0 short type 2 +// 2 short major 2 +// 4 short minor 2 +// 6 short nlink 2 +// 8 uint size 4 +// 12 uint rwmode 4 +// 16 uint supermode 4 +// 20 uint addrs[15] 60 T_EXTENT: packed fsv1_extent[4] in [0..11], rest normal +// 80 uint showmode 4 +// 84 uint atime 4 ← last access time (Unix seconds) +// 88 uint mtime 4 ← last modification time +// 92 uint ctime 4 ← last metadata-change time +// 96 uint checksum 4 ← CRC32c (zeroed during computation) +// 100 uchar pad[28] 28 ← was 44; reduced by 16 for the 4 new fields +// 128 bytes total +// --------------------------------------------------------------------------- + +struct dinode { + short type; // file type + short major; // major device number (T_DEVICE only) + short minor; // minor device number (T_DEVICE only) + short nlink; // hard-link count + uint size; // file size in bytes + uint rwmode; // read/write permission flags + uint supermode; // superuser-only flag + uint addrs[NDIRECT + 3]; // block addresses / packed extent entries + uint showmode; // visibility flag + uint atime; // last access time (seconds since epoch) + uint mtime; // last modification time + uint ctime; // last metadata-change time + uint checksum; // CRC32c of this dinode (field = 0 when hashing) + uchar pad[28]; // reserved padding (struct = 128 bytes) +}; + +// Inodes per block +#define IPB (BSIZE / sizeof(struct dinode)) + +// Block containing inode i +#define IBLOCK(i, sb) ((i) / IPB + (sb).inodestart) + +// Bitmap bits per block +#define BPB (BSIZE * 8) + +// Block of free-block bitmap containing bit for block b +#define BBLOCK(b, sb) ((b) / BPB + (sb).bmapstart) + +// Block of inode bitmap containing bit for inode i +#define IBBLOCK(i, sb) ((i) / BPB + (sb).ibmapstart) + +// --------------------------------------------------------------------------- +// Directory entry +// --------------------------------------------------------------------------- + +// 30, not 14. +// +// 14 was xv6's, and the v1 filesystem truncated to it *silently*: a file called +// JetBrainsMono-Regular.ttf was stored as "JetBrainsMono-", could not be opened +// by its real name, and would have collided outright with +// JetBrainsMono-Bold.ttf. Any two files sharing a 14-character prefix mapped to +// one entry. That is data loss, not a display limit. +// +// The value is chosen so that `struct dirent` is exactly 32 bytes, because the +// directory HTree below is laid out in *bytes* and a bucket must stay 256 bytes +// (see DIRENT_BUCKET_BYTES). Going from 16-byte to 32-byte entries therefore +// halves the slots per bucket from 16 to 8 and changes no offset anywhere else +// -- HASHRANGE, the overflow region and MAXFSIZE all keep their values. The +// cost is more overflow use in heavily collided directories, which is a far +// better trade than silently mangling names. +// +// It is still not POSIX's 255. Getting there means reshaping the directory +// format, which wants the on-disk version policy SchFS is getting first. +#define DIRSIZ 30 + +struct dirent { + ushort inum; + char name[DIRSIZ]; +}; + +// --------------------------------------------------------------------------- +// Directory HTree constants +// +// BKDRHash(name) returns a value in [16, 16 + HASHRANGE). +// Each bucket is 256 bytes: 8 dirent slots of 32 bytes. It was 16 slots of 16 +// until DIRSIZ grew; the byte size is what the layout depends on, so it is the +// thing held constant. +// +// With HASHRANGE=443: +// max primary end byte = (16 + 443) * 256 = 117 504 (~114.75 blocks) +// OVERFLOW_DIR_START = 113 * BSIZE = 115 712 +// OVERFLOW_DIR_END = 115 * BSIZE = 117 760 +// MAXFSIZE = 119 * BSIZE = 121 856 +// --------------------------------------------------------------------------- + +#define DIRENT_NUM 7168 +#define FIRST_LAYER_NODE_NUM 112 +#define OVERFLOW_BLOCK (FIRST_LAYER_NODE_NUM + 1) +#define NUM_DIRENT_PER_BLOCK (BSIZE / sizeof(struct dirent)) +#define SECOND_LAYER_NODE_NUM NUM_DIRENT_PER_BLOCK +#define HASHRANGE 443 + +// One hash bucket, in bytes. Every offset in the HTree is derived from this, so +// it must not change when DIRSIZ does -- see the note on DIRSIZ. +#define DIRENT_BUCKET_BYTES 256 +#define DIRENT_PER_BUCKET (DIRENT_BUCKET_BYTES / sizeof(struct dirent)) +#define MAXFSIZE (119 * BSIZE) + +// Overflow bucket: shared between fs.c and mkfs.c. +// These replace the bare "113 * BSIZE" / "115 * BSIZE" literals. +#define OVERFLOW_DIR_START (113 * BSIZE) +#define OVERFLOW_DIR_END (115 * BSIZE) + +#endif // FS_H diff --git a/include/input.h b/include/input.h new file mode 100644 index 0000000..2842de6 --- /dev/null +++ b/include/input.h @@ -0,0 +1,93 @@ +#pragma once + +#include "types.h" + +// Raw input events -- Linux's evdev interface. +// +// The console gives characters. That is the right thing for a shell and the +// wrong thing for everything else: a character stream has no key *releases*, +// no way to tell left shift from right, no notion of a key being held, and no +// mouse at all. A game holding W to walk forward, or a compositor deciding +// which window a click landed in, needs the events underneath the characters. +// +// The layout is Linux's, deliberately. It is what every program that reads input +// already speaks -- SDL, X, libinput, every game engine -- so a program that +// runs on Linux reads input here without a porting layer. It costs nothing to +// match: the struct is four fields. +// +// Nodes are /dev/input/event0 (keyboard) and /dev/input/event1 (mouse). + +struct input_timeval { + int64 tv_sec; + int64 tv_usec; +}; + +struct input_event { + struct input_timeval time; + uint16 type; + uint16 code; + int32 value; +}; + +// Event types. +#define EV_SYN 0x00 +#define EV_KEY 0x01 +#define EV_REL 0x02 +#define EV_ABS 0x03 +#define EV_MSC 0x04 + +#define SYN_REPORT 0 + +// Relative axes. +#define REL_X 0x00 +#define REL_Y 0x01 +#define REL_WHEEL 0x08 + +// Buttons live in the EV_KEY code space alongside keys, which is why the +// keyboard's codes stop well below 0x100. +#define BTN_LEFT 0x110 +#define BTN_RIGHT 0x111 +#define BTN_MIDDLE 0x112 + +// Key codes worth naming. The rest are the AT scancode-set-1 numbers, which +// Linux's keycodes were derived from and still match for everything below +// 0x54 -- that is not a coincidence and it is why the PS/2 path needs no +// translation table for unprefixed bytes. +#define KEY_RESERVED 0 +#define KEY_ESC 1 +#define KEY_ENTER 28 +#define KEY_LEFTCTRL 29 +#define KEY_LEFTSHIFT 42 +#define KEY_RIGHTSHIFT 54 +#define KEY_LEFTALT 56 +#define KEY_SPACE 57 +#define KEY_RIGHTCTRL 97 +#define KEY_RIGHTALT 100 +#define KEY_HOME 102 +#define KEY_UP 103 +#define KEY_PAGEUP 104 +#define KEY_LEFT 105 +#define KEY_RIGHT 106 +#define KEY_END 107 +#define KEY_DOWN 108 +#define KEY_PAGEDOWN 109 +#define KEY_INSERT 110 +#define KEY_DELETE 111 +#define KEY_LEFTMETA 125 +#define KEY_RIGHTMETA 126 + +#define KEY_MAX 255 + +// EVIOCGRAB -- take/release exclusive access to a device. +// +// Linux's _IOW('E', 0x90, int). Spelled as the resulting constant rather than +// built from an _IOW macro because Schunix has no ioctl-encoding macros and +// inventing a set just for this would be a bigger change than the feature. +// A non-zero argument grabs, zero releases; the argument is passed by value. +#define EVIOCGRAB 0x40044590 + +// Which device an event came from. Both are minor numbers under the input +// major, and both are the index into the driver's array. +#define INPUT_KBD 0 +#define INPUT_MOUSE 1 +#define NINPUTDEV 2 diff --git a/include/irq.h b/include/irq.h new file mode 100644 index 0000000..c4285b2 --- /dev/null +++ b/include/irq.h @@ -0,0 +1,43 @@ +#pragma once + +// Hardware interrupt registration. +// +// Until this existed, trap() was a switch statement listing every interrupt +// the kernel knew about, and adding a device meant editing the trap handler. +// That is fine while the devices are the four the PC has always had at fixed +// pins; it stops being fine the moment a device's IRQ is whatever the BIOS +// decided to put in its PCI config space this boot. A driver cannot write +// `case T_IRQ0 + IRQ_XHCI:` because there is no such constant -- it only +// learns its line at probe time. +// +// So: a line has a chain of handlers, a driver adds itself to one at probe +// time, and enabling the line is a consequence of registering rather than a +// separate call somebody can forget. +// +// Chains matter more than they look. PCI interrupt pins are *shared*: four +// INTx lines are routed to a handful of IOAPIC inputs, so two unrelated +// devices routinely land on the same one. A handler must therefore check +// whether its own device actually raised the interrupt and do nothing if it +// did not. + +#define NIRQ 24 // IOAPIC redirection entries on a PC +#define NCHAIN 4 // handlers that may share one line + +typedef void (*irq_handler_t)(void *arg); + +// Returns 0, or -1 if the line is out of range, reserved, or full. +// Registering enables the line; the last unregister masks it again. +// +// `level_low` selects PCI's signalling (level-triggered, active low) rather +// than ISA's (edge-triggered, active high). Getting this wrong does not fail +// cleanly: an edge-configured level interrupt fires once and then wedges, +// because the device keeps the line asserted and no further edge ever comes. +int irq_register(int irq, irq_handler_t fn, void *arg, const char *name, + int level_low); +void irq_unregister(int irq, irq_handler_t fn, void *arg); + +// Called from trap() with the IOAPIC pin number, not the vector. +void irq_dispatch(int irq); + +void irq_init(void); +int irq_info(char *buf, int cap); // /proc/interrupts diff --git a/include/kbd.h b/include/kbd.h new file mode 100644 index 0000000..babbd6e --- /dev/null +++ b/include/kbd.h @@ -0,0 +1,112 @@ +// PC keyboard interface constants + +#define KBSTATP 0x64 // kbd controller status port(I) +#define KBS_DIB 0x01 // kbd data in buffer +#define KBDATAP 0x60 // kbd data port(I) + +#define NO 0 + +#define SHIFT (1<<0) +#define CTL (1<<1) +#define ALT (1<<2) + +#define CAPSLOCK (1<<3) +#define NUMLOCK (1<<4) +#define SCROLLLOCK (1<<5) + +#define E0ESC (1<<6) + +// Special keycodes +#define KEY_HOME 0xE0 +#define KEY_END 0xE1 +#define KEY_UP 0xE2 +#define KEY_DN 0xE3 +#define KEY_LF 0xE4 +#define KEY_RT 0xE5 +#define KEY_PGUP 0xE6 +#define KEY_PGDN 0xE7 +#define KEY_INS 0xE8 +#define KEY_DEL 0xE9 + +// C('A') == Control-A +#define C(x) (x - '@') + +static uchar shiftcode[256] = +{ + [0x1D] CTL, + [0x2A] SHIFT, + [0x36] SHIFT, + [0x38] ALT, + [0x9D] CTL, + [0xB8] ALT +}; + +static uchar togglecode[256] = +{ + [0x3A] CAPSLOCK, + [0x45] NUMLOCK, + [0x46] SCROLLLOCK +}; + +static uchar normalmap[256] = +{ + NO, 0x1B, '1', '2', '3', '4', '5', '6', // 0x00 + '7', '8', '9', '0', '-', '=', '\b', '\t', + 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', // 0x10 + 'o', 'p', '[', ']', '\n', NO, 'a', 's', + 'd', 'f', 'g', 'h', 'j', 'k', 'l', ';', // 0x20 + '\'', '`', NO, '\\', 'z', 'x', 'c', 'v', + 'b', 'n', 'm', ',', '.', '/', NO, '*', // 0x30 + NO, ' ', NO, NO, NO, NO, NO, NO, + NO, NO, NO, NO, NO, NO, NO, '7', // 0x40 + '8', '9', '-', '4', '5', '6', '+', '1', + '2', '3', '0', '.', NO, NO, NO, NO, // 0x50 + [0x9C] '\n', // KP_Enter + [0xB5] '/', // KP_Div + [0xC8] KEY_UP, [0xD0] KEY_DN, + [0xC9] KEY_PGUP, [0xD1] KEY_PGDN, + [0xCB] KEY_LF, [0xCD] KEY_RT, + [0x97] KEY_HOME, [0xCF] KEY_END, + [0xD2] KEY_INS, [0xD3] KEY_DEL +}; + +static uchar shiftmap[256] = +{ + NO, 033, '!', '@', '#', '$', '%', '^', // 0x00 + '&', '*', '(', ')', '_', '+', '\b', '\t', + 'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', // 0x10 + 'O', 'P', '{', '}', '\n', NO, 'A', 'S', + 'D', 'F', 'G', 'H', 'J', 'K', 'L', ':', // 0x20 + '"', '~', NO, '|', 'Z', 'X', 'C', 'V', + 'B', 'N', 'M', '<', '>', '?', NO, '*', // 0x30 + NO, ' ', NO, NO, NO, NO, NO, NO, + NO, NO, NO, NO, NO, NO, NO, '7', // 0x40 + '8', '9', '-', '4', '5', '6', '+', '1', + '2', '3', '0', '.', NO, NO, NO, NO, // 0x50 + [0x9C] '\n', // KP_Enter + [0xB5] '/', // KP_Div + [0xC8] KEY_UP, [0xD0] KEY_DN, + [0xC9] KEY_PGUP, [0xD1] KEY_PGDN, + [0xCB] KEY_LF, [0xCD] KEY_RT, + [0x97] KEY_HOME, [0xCF] KEY_END, + [0xD2] KEY_INS, [0xD3] KEY_DEL +}; + +static uchar ctlmap[256] = +{ + NO, NO, NO, NO, NO, NO, NO, NO, + NO, NO, NO, NO, NO, NO, NO, NO, + C('Q'), C('W'), C('E'), C('R'), C('T'), C('Y'), C('U'), C('I'), + C('O'), C('P'), NO, NO, '\r', NO, C('A'), C('S'), + C('D'), C('F'), C('G'), C('H'), C('J'), C('K'), C('L'), NO, + NO, NO, NO, C('\\'), C('Z'), C('X'), C('C'), C('V'), + C('B'), C('N'), C('M'), NO, NO, C('/'), NO, NO, + [0x9C] '\r', // KP_Enter + [0xB5] C('/'), // KP_Div + [0xC8] KEY_UP, [0xD0] KEY_DN, + [0xC9] KEY_PGUP, [0xD1] KEY_PGDN, + [0xCB] KEY_LF, [0xCD] KEY_RT, + [0x97] KEY_HOME, [0xCF] KEY_END, + [0xD2] KEY_INS, [0xD3] KEY_DEL +}; + diff --git a/include/logo.h b/include/logo.h new file mode 100644 index 0000000..390199e --- /dev/null +++ b/include/logo.h @@ -0,0 +1,534 @@ +#pragma once + +// GENERATED by tools/mklogo.py -- do not edit. Run `make logo`. +// source: Assets/Schunix Small Logo.png +// +// An ink-coverage mask, not colour: 0 is background and full scale is +// solid logo. Whoever draws it picks the colour, so the palette stays in +// include/theme.h and can change without regenerating anything. +// +// Two pixels per byte, high nibble first. Included by boot/stage2main.c +// as well as kernel/dev/vga.c, and stage2 must fit below the 0x10000 +// kernel-search buffer -- hence 4 bits rather than 8. + +#define LOGO_W 128 +#define LOGO_H 128 + +// Ink at linear pixel index i, 0..255. +#define LOGO_INK(i) ((unsigned char)((((i) & 1) ? (logo_mask[(i) >> 1] & 0xF) \ + : (logo_mask[(i) >> 1] >> 4)) * 17)) + +static const unsigned char logo_mask[LOGO_W * LOGO_H / 2] = { + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x23,0x56,0x79,0xaa,0xbb, + 0xbb,0xaa,0x98,0x75,0x42,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x47,0x9b,0xbb,0xbb,0xbb,0xcc,0xcc, + 0xcc,0xcc,0xcc,0xcb,0xbb,0xb9,0x74,0x20,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x47,0x9a,0xaa,0xbb,0xbb,0xbb,0xbb,0xcc,0xcc, + 0xcc,0xcc,0xcc,0xcb,0xbb,0xbb,0xbb,0xba,0x75,0x20,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x15,0x8a,0xaa,0xaa,0xaa,0xbb,0xbb,0xbb,0xbb,0xcc,0xcc, + 0xcc,0xcc,0xcc,0xcb,0xbb,0xbb,0xbb,0xbb,0xbb,0xb9,0x51,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x37,0x99,0xaa,0xaa,0xaa,0xaa,0xbb,0xbb,0xbb,0xbb,0xcc,0xcc, + 0xcc,0xcc,0xcc,0xcb,0xbb,0xbb,0xbb,0xbb,0xbb,0xba,0xaa,0x84,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x01,0x58,0x99,0x99,0xaa,0xaa,0xaa,0xaa,0xbb,0xbb,0xbb,0xbb,0xcc,0xcc, + 0xcc,0xcc,0xcc,0xcb,0xbb,0xbb,0xbb,0xbb,0xbb,0xba,0xaa,0xaa,0x96,0x10,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x01,0x69,0x99,0x99,0x99,0xaa,0xaa,0xaa,0xaa,0xbb,0xbb,0xbb,0xbb,0xcc,0xcc, + 0xcc,0xcc,0xcc,0xcb,0xbb,0xbb,0xbb,0xbb,0xbb,0xba,0xaa,0xaa,0xaa,0xa7,0x10,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x58,0x99,0x99,0x99,0x99,0xaa,0xaa,0xaa,0xaa,0xbb,0xbb,0xba,0x87,0x76,0x66, + 0x66,0x67,0x89,0xab,0xbb,0xbb,0xbb,0xbb,0xbb,0xba,0xaa,0xaa,0xaa,0xaa,0xa6,0x10, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x48,0x88,0x99,0x99,0x99,0x99,0xaa,0xaa,0xaa,0xa9,0x65,0x31,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x13,0x57,0x9b,0xbb,0xbb,0xba,0xaa,0xaa,0xaa,0xaa,0xaa,0x95, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x37, + 0x88,0x88,0x99,0x99,0x99,0x99,0xaa,0xa9,0x63,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x36,0xab,0xba,0xaa,0xaa,0xaa,0xaa,0xaa,0xaa, + 0x93,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x16,0x88, + 0x88,0x88,0x99,0x99,0x99,0x99,0x95,0x20,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x6a,0xaa,0xaa,0xaa,0xaa,0xaa,0xaa, + 0x99,0x71,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x78,0x88, + 0x88,0x88,0x99,0x99,0x99,0x62,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x37,0xaa,0xaa,0xaa,0xaa,0xaa, + 0x99,0x99,0x30,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x57,0x78,0x88, + 0x88,0x88,0x99,0x98,0x51,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x15,0xaa,0xaa,0xaa,0xaa, + 0x99,0x99,0x97,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x16,0x77,0x78,0x88, + 0x88,0x88,0x98,0x50,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x05,0xaa,0xaa,0xaa, + 0x99,0x99,0x99,0x81,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x77,0x77,0x78,0x88, + 0x88,0x88,0x50,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x05,0xaa,0xaa, + 0x99,0x99,0x99,0x99,0x30,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x47,0x77,0x77,0x78,0x88, + 0x88,0x61,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x17,0xaa, + 0x99,0x99,0x99,0x99,0x95,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x04,0x77,0x77,0x77,0x78,0x88, + 0x83,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x49, + 0x99,0x99,0x99,0x99,0x99,0x50,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x57,0x77,0x77,0x77,0x78,0x86, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01, + 0x79,0x99,0x99,0x99,0x99,0x96,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x05,0x67,0x77,0x77,0x77,0x77,0x30, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x04,0x99,0x99,0x99,0x99,0x99,0x70,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x56,0x67,0x77,0x77,0x77,0x61,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x28,0x99,0x99,0x99,0x99,0x97,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x05,0x66,0x67,0x77,0x77,0x76,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x79,0x99,0x99,0x99,0x98,0x60,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x46,0x66,0x67,0x77,0x77,0x50,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x06,0x99,0x99,0x99,0x98,0x85,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x66,0x66,0x67,0x77,0x74,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x59,0x99,0x99,0x98,0x88,0x50,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x26,0x66,0x66,0x67,0x77,0x30,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x04,0x99,0x99,0x98,0x88,0x83,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x56,0x66,0x66,0x67,0x73,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x49,0x99,0x98,0x88,0x88,0x10,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x05,0x66,0x66,0x66,0x67,0x30,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x05,0x99,0x98,0x88,0x88,0x70,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x46,0x66,0x66,0x66,0x64,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x59,0x98,0x88,0x88,0x86,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x02,0x56,0x66,0x66,0x66,0x50,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x06,0x98,0x88,0x88,0x88,0x30,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x05,0x56,0x66,0x66,0x65,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x78,0x88,0x88,0x88,0x71,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x45,0x56,0x66,0x66,0x61,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x18,0x88,0x88,0x88,0x86,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x02,0x55,0x56,0x66,0x66,0x20,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x88,0x88,0x88,0x88,0x30,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x05,0x55,0x56,0x66,0x64,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x68,0x88,0x88,0x88,0x70,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x35,0x55,0x56,0x66,0x60,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x08,0x88,0x88,0x88,0x84,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x55,0x55,0x56,0x66,0x20,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x88,0x88,0x88,0x87,0x10,0x00,0x00,0x00, + 0x00,0x00,0x00,0x03,0x55,0x55,0x56,0x64,0x00,0x00,0x00,0x00,0x05,0xaa,0xaa,0xa9, + 0x99,0x98,0x88,0x88,0x77,0x77,0x66,0x66,0x65,0x55,0x55,0x30,0x00,0x00,0x00,0x00, + 0x00,0x00,0x35,0x54,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x29,0x98,0x10,0x00,0x00,0x68,0x88,0x88,0x88,0x50,0x00,0x00,0x00, + 0x00,0x00,0x00,0x15,0x55,0x55,0x56,0x50,0x00,0x00,0x00,0x00,0x5e,0xee,0xdd,0xdc, + 0xcc,0xcb,0xbb,0xaa,0xaa,0x99,0x98,0x88,0x77,0x77,0x66,0x63,0x00,0x00,0x00,0x00, + 0x00,0x03,0x77,0x77,0x50,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x02,0xbc,0xcc,0xb1,0x00,0x00,0x18,0x88,0x88,0x88,0x81,0x00,0x00,0x00, + 0x00,0x00,0x00,0x35,0x55,0x55,0x56,0x30,0x00,0x00,0x00,0x06,0xee,0xed,0xdd,0xdc, + 0xcc,0xbb,0xba,0xaa,0xa9,0x99,0x88,0x88,0x77,0x76,0x66,0x66,0x10,0x00,0x00,0x00, + 0x00,0x07,0x77,0x77,0x75,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x3b,0xcc,0xcc,0xd8,0x00,0x00,0x04,0x88,0x88,0x88,0x85,0x00,0x00,0x00, + 0x00,0x00,0x01,0x55,0x55,0x55,0x55,0x00,0x00,0x00,0x00,0x6e,0xee,0xdd,0xdd,0xcc, + 0xcb,0xbb,0xba,0xaa,0x99,0x98,0x88,0x87,0x77,0x66,0x66,0x66,0x10,0x00,0x00,0x00, + 0x00,0x07,0x77,0x77,0x78,0x50,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x03,0xbc,0xcc,0xcc,0xd8,0x00,0x00,0x00,0x88,0x88,0x88,0x87,0x10,0x00,0x00, + 0x00,0x00,0x03,0x55,0x55,0x55,0x53,0x00,0x00,0x00,0x05,0xee,0xee,0xdd,0xdc,0xcc, + 0xbb,0xbb,0xaa,0xa9,0x99,0x98,0x88,0x77,0x76,0x66,0x66,0x66,0x10,0x00,0x00,0x00, + 0x00,0x07,0x77,0x77,0x78,0x85,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x2b,0xcc,0xcc,0xcd,0xd8,0x00,0x00,0x00,0x48,0x88,0x88,0x87,0x40,0x00,0x00, + 0x00,0x00,0x04,0x55,0x55,0x55,0x50,0x00,0x00,0x00,0x6e,0xee,0xed,0xdd,0xcc,0xcc, + 0xbb,0xba,0xaa,0x99,0x99,0x88,0x87,0x77,0x76,0x66,0x66,0x62,0x00,0x00,0x00,0x00, + 0x00,0x02,0x77,0x77,0x88,0x88,0x50,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x03,0xbc,0xcc,0xcc,0xdd,0xa1,0x00,0x00,0x00,0x07,0x88,0x88,0x87,0x70,0x00,0x00, + 0x00,0x00,0x25,0x55,0x55,0x55,0x20,0x00,0x00,0x06,0xfe,0xee,0xdd,0xdc,0xa7,0x77, + 0x77,0x66,0x66,0x65,0x55,0x55,0x54,0x44,0x44,0x33,0x44,0x20,0x00,0x00,0x00,0x00, + 0x00,0x00,0x27,0x78,0x88,0x88,0x86,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x3b,0xcc,0xcc,0xcd,0xda,0x00,0x00,0x00,0x00,0x04,0x88,0x88,0x87,0x73,0x00,0x00, + 0x00,0x00,0x45,0x55,0x55,0x55,0x00,0x00,0x00,0x5e,0xee,0xed,0xdd,0xc9,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x03,0x78,0x88,0x88,0x88,0x60,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02, + 0xbc,0xcc,0xcc,0xdd,0xb1,0x00,0x00,0x00,0x00,0x00,0x88,0x88,0x87,0x76,0x00,0x00, + 0x00,0x01,0x45,0x55,0x55,0x53,0x00,0x00,0x05,0xee,0xee,0xdd,0xdd,0x80,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x28,0x88,0x88,0x88,0x86,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x3b, + 0xcc,0xcc,0xcd,0xda,0x10,0x00,0x00,0x00,0x00,0x00,0x58,0x88,0x87,0x77,0x10,0x00, + 0x00,0x02,0x45,0x55,0x55,0x51,0x00,0x00,0x5e,0xee,0xed,0xdd,0xd8,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x02,0x88,0x88,0x88,0x99,0x60,0x00,0x00,0x00,0x00,0x00,0x03,0xbc, + 0xcc,0xcc,0xcd,0xa0,0x00,0x00,0x00,0x00,0x00,0x00,0x28,0x88,0x87,0x77,0x40,0x00, + 0x00,0x04,0x45,0x55,0x55,0x50,0x00,0x04,0xee,0xee,0xed,0xdd,0x90,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x38,0x88,0x88,0x99,0x96,0x00,0x00,0x00,0x00,0x00,0x2b,0xcc, + 0xcc,0xcc,0xdb,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x07,0x88,0x87,0x77,0x70,0x00, + 0x00,0x14,0x45,0x55,0x55,0x20,0x00,0x08,0xee,0xee,0xdd,0xd8,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x03,0x88,0x89,0x99,0x99,0x60,0x00,0x00,0x00,0x02,0xbc,0xcc, + 0xcc,0xcd,0xa1,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x04,0x88,0x87,0x77,0x71,0x00, + 0x00,0x24,0x45,0x55,0x55,0x00,0x00,0x08,0xee,0xed,0xdd,0xc8,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x38,0x99,0x99,0x9a,0xa7,0x00,0x00,0x00,0x2b,0xcc,0xcc, + 0xcc,0xda,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x88,0x87,0x77,0x73,0x00, + 0x00,0x34,0x45,0x55,0x54,0x00,0x00,0x05,0xee,0xdd,0xdc,0xcc,0x80,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x03,0x99,0x99,0xaa,0xaa,0x70,0x00,0x02,0xbb,0xcc,0xcc, + 0xcd,0xb1,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x78,0x87,0x77,0x75,0x00, + 0x00,0x44,0x45,0x55,0x52,0x00,0x00,0x00,0x5d,0xdd,0xdc,0xcc,0xb7,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x39,0x99,0xaa,0xaa,0xa7,0x00,0x2b,0xbc,0xcc,0xcc, + 0xda,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x48,0x87,0x77,0x77,0x00, + 0x01,0x44,0x45,0x55,0x51,0x00,0x00,0x00,0x05,0xdd,0xcc,0xcb,0xbb,0x70,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x9a,0xaa,0xaa,0xab,0x82,0xbb,0xcc,0xcc,0xcc, + 0xa0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x28,0x87,0x77,0x77,0x10, + 0x02,0x44,0x45,0x55,0x50,0x00,0x00,0x00,0x00,0x5c,0xcc,0xbb,0xbb,0xa7,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x3a,0xaa,0xaa,0xbb,0xbb,0xbc,0xcc,0xcc,0xcb, + 0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x07,0x87,0x77,0x77,0x30, + 0x03,0x44,0x45,0x55,0x40,0x00,0x00,0x00,0x00,0x05,0xcc,0xbb,0xba,0xaa,0x75,0x55, + 0x55,0x44,0x44,0x43,0x33,0x33,0x33,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x04,0xaa,0xab,0xbb,0xbb,0xcc,0xcc,0xcc,0xa1, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x06,0x87,0x77,0x77,0x50, + 0x04,0x44,0x45,0x55,0x30,0x00,0x00,0x00,0x00,0x00,0x5b,0xbb,0xaa,0xaa,0x99,0x98, + 0x88,0x77,0x77,0x66,0x66,0x66,0x66,0x50,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x4a,0xbb,0xbb,0xbc,0xcc,0xcc,0xca,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x04,0x87,0x77,0x77,0x60, + 0x04,0x44,0x45,0x55,0x20,0x00,0x00,0x00,0x00,0x00,0x04,0xba,0xaa,0xa9,0x99,0x88, + 0x87,0x77,0x76,0x66,0x66,0x66,0x66,0x65,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x04,0xbb,0xbb,0xcc,0xcc,0xcc,0xb1,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x87,0x77,0x77,0x70, + 0x14,0x44,0x45,0x55,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x4a,0xaa,0x99,0x98,0x88, + 0x77,0x77,0x66,0x66,0x66,0x66,0x66,0x66,0x50,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x6b,0xbb,0xcc,0xcc,0xcc,0x10,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x87,0x77,0x77,0x71, + 0x14,0x44,0x45,0x55,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x04,0xa9,0x99,0x88,0x88, + 0x77,0x76,0x66,0x66,0x66,0x66,0x66,0x67,0x75,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0xab,0xbc,0xcc,0xcc,0xcd,0x90,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x87,0x77,0x77,0x72, + 0x24,0x44,0x45,0x54,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x39,0x98,0x88,0x87, + 0x77,0x66,0x66,0x66,0x66,0x66,0x66,0x77,0x77,0x60,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x3a,0xbb,0xcc,0xcc,0xcc,0xdd,0xd9,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x77,0x77,0x77,0x73, + 0x24,0x44,0x45,0x54,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x76,0x66,0x65, + 0x55,0x55,0x44,0x55,0x55,0x56,0x67,0x77,0x77,0x76,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0xbb,0xbc,0xcc,0xcc,0xcd,0xdd,0xdd,0xa0, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x67,0x77,0x77,0x74, + 0x34,0x44,0x45,0x53,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x02,0x67,0x77,0x77,0x77,0x60,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x2a,0xbb,0xcc,0xcc,0xcc,0xdd,0xdd,0xde,0xea, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x57,0x77,0x77,0x74, + 0x34,0x44,0x45,0x53,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x16,0x77,0x77,0x77,0x76,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x03,0xab,0xbc,0xcc,0xcc,0xa5,0xcd,0xdd,0xee,0xee, + 0x90,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x47,0x77,0x77,0x75, + 0x34,0x44,0x45,0x52,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x77,0x77,0x77,0x77,0x60,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x3b,0xbb,0xbc,0xcc,0xca,0x00,0x4d,0xdd,0xee,0xee, + 0xe9,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x47,0x77,0x77,0x75, + 0x44,0x44,0x45,0x52,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x27,0x77,0x77,0x78,0x86,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x02,0xab,0xbb,0xcc,0xcc,0xb1,0x00,0x05,0xde,0xee,0xee, + 0xdd,0x90,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x37,0x77,0x77,0x76, + 0x44,0x44,0x45,0x52,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x77,0x77,0x88,0x88,0x10,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x3a,0xbb,0xbc,0xcc,0xca,0x10,0x00,0x00,0x4d,0xee,0xed, + 0xdd,0xd9,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x37,0x77,0x77,0x76, + 0x44,0x44,0x45,0x52,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x27,0x77,0x88,0x88,0x10,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x03,0xbb,0xbb,0xcc,0xcc,0xa0,0x00,0x00,0x00,0x05,0xee,0xdd, + 0xdd,0xdd,0x90,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x37,0x77,0x77,0x76, + 0x44,0x44,0x45,0x52,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x78,0x88,0x88,0x10,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x2a,0xbb,0xbc,0xcc,0xca,0x10,0x00,0x00,0x00,0x00,0x5d,0xdd, + 0xdd,0xdd,0xc9,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x37,0x77,0x77,0x76, + 0x44,0x44,0x45,0x52,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x88,0x88,0x88,0x10,0x00,0x00,0x00, + 0x00,0x00,0x00,0x02,0xab,0xbb,0xcc,0xcc,0xa1,0x00,0x00,0x00,0x00,0x00,0x05,0xdd, + 0xdd,0xdd,0xcc,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x37,0x77,0x77,0x76, + 0x44,0x44,0x45,0x52,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x88,0x88,0x88,0x10,0x00,0x00,0x00, + 0x00,0x00,0x00,0x2a,0xbb,0xbb,0xcc,0xca,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x5d, + 0xdd,0xdc,0xcc,0xc8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x37,0x77,0x77,0x76, + 0x34,0x44,0x45,0x52,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x88,0x88,0x88,0x10,0x00,0x00,0x00, + 0x00,0x00,0x02,0xab,0xbb,0xbc,0xcc,0xa1,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x05, + 0xdd,0xcc,0xcc,0xcc,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x47,0x77,0x77,0x76, + 0x34,0x44,0x45,0x53,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x88,0x88,0x88,0x10,0x00,0x00,0x00, + 0x00,0x00,0x2a,0xbb,0xbb,0xcc,0xca,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x5c,0xcc,0xcc,0xcc,0xc8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x47,0x77,0x77,0x75, + 0x34,0x44,0x45,0x53,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x88,0x88,0x88,0x10,0x00,0x00,0x00, + 0x00,0x02,0xab,0xbb,0xbc,0xcc,0xa0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x05,0xcc,0xcc,0xcc,0xbb,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x57,0x77,0x77,0x75, + 0x24,0x44,0x45,0x54,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x88,0x88,0x88,0x10,0x00,0x00,0x00, + 0x00,0x0a,0xbb,0xbb,0xcc,0xca,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x5c,0xcc,0xcb,0xbb,0xb6,0x00,0x00,0x00,0x00,0x00,0x00,0x67,0x77,0x77,0x74, + 0x24,0x44,0x45,0x54,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x88,0x88,0x89,0x10,0x00,0x00,0x00, + 0x00,0x0b,0xbb,0xbc,0xcc,0xa1,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x05,0xcc,0xbb,0xbb,0xb7,0x00,0x00,0x00,0x00,0x00,0x00,0x77,0x77,0x77,0x73, + 0x14,0x44,0x45,0x55,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x88,0x88,0x99,0x10,0x00,0x00,0x00, + 0x00,0x1b,0xbb,0xbc,0xca,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x4b,0xbb,0xbb,0xb7,0x00,0x00,0x00,0x00,0x00,0x00,0x87,0x77,0x77,0x72, + 0x14,0x44,0x45,0x55,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x88,0x89,0x99,0x10,0x00,0x00,0x00, + 0x00,0x1b,0xbb,0xcc,0xc4,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x09,0xbb,0xbb,0xb7,0x00,0x00,0x00,0x00,0x00,0x01,0x87,0x77,0x77,0x71, + 0x04,0x44,0x45,0x55,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x88,0x99,0x99,0x10,0x00,0x00,0x00, + 0x00,0x1b,0xbc,0xcc,0xc4,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x09,0xbb,0xbb,0xa7,0x00,0x00,0x00,0x00,0x00,0x02,0x87,0x77,0x77,0x70, + 0x04,0x44,0x45,0x55,0x20,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x88,0x99,0x99,0x10,0x00,0x00,0x00, + 0x00,0x1b,0xcc,0xcc,0xc4,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x09,0xbb,0xbb,0xa7,0x00,0x00,0x00,0x00,0x00,0x04,0x87,0x77,0x77,0x70, + 0x03,0x44,0x45,0x55,0x40,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x89,0x99,0x99,0x10,0x00,0x00,0x00, + 0x00,0x1c,0xcc,0xcc,0xc4,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x09,0xbb,0xba,0xa7,0x00,0x00,0x00,0x00,0x00,0x06,0x87,0x77,0x77,0x50, + 0x02,0x44,0x45,0x55,0x50,0x00,0x00,0x00,0x05,0x65,0x20,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x99,0x99,0x9a,0x10,0x00,0x00,0x00, + 0x00,0x1c,0xcc,0xcc,0xd4,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x08,0xbb,0xaa,0xa7,0x00,0x00,0x00,0x00,0x00,0x07,0x87,0x77,0x77,0x30, + 0x01,0x44,0x45,0x55,0x51,0x00,0x00,0x00,0x66,0x66,0x62,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x99,0x99,0xaa,0x10,0x00,0x00,0x00, + 0x00,0x1c,0xcc,0xcd,0xd4,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x08,0xba,0xaa,0xa7,0x00,0x00,0x00,0x00,0x00,0x18,0x87,0x77,0x77,0x20, + 0x00,0x44,0x45,0x55,0x52,0x00,0x00,0x03,0x66,0x66,0x66,0x20,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x06,0x99,0x99,0xaa,0x10,0x00,0x00,0x00, + 0x00,0x1c,0xcc,0xdd,0xd4,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x08,0xaa,0xaa,0xa6,0x00,0x00,0x00,0x00,0x00,0x48,0x87,0x77,0x77,0x00, + 0x00,0x34,0x45,0x55,0x54,0x00,0x00,0x03,0x66,0x66,0x66,0x62,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x69,0x99,0x9a,0xaa,0x20,0x00,0x00,0x00, + 0x00,0x1c,0xcc,0xdd,0xd4,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x08,0xaa,0xaa,0xa6,0x00,0x00,0x00,0x00,0x00,0x68,0x87,0x77,0x76,0x00, + 0x00,0x24,0x45,0x55,0x55,0x00,0x00,0x03,0x66,0x66,0x66,0x66,0x20,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x06,0x99,0x99,0xaa,0xaa,0x10,0x00,0x00,0x00, + 0x00,0x1c,0xcd,0xdd,0xd4,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x08,0xaa,0xaa,0xa6,0x00,0x00,0x00,0x00,0x01,0x88,0x87,0x77,0x74,0x00, + 0x00,0x14,0x45,0x55,0x55,0x20,0x00,0x00,0x46,0x66,0x66,0x66,0x72,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x69,0x99,0x9a,0xaa,0xa3,0x00,0x00,0x00,0x00, + 0x00,0x1c,0xdd,0xdd,0xd4,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x08,0xaa,0xaa,0xa6,0x00,0x00,0x00,0x00,0x04,0x88,0x87,0x77,0x72,0x00, + 0x00,0x04,0x45,0x55,0x55,0x40,0x00,0x00,0x04,0x66,0x66,0x67,0x77,0x20,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x06,0x99,0x99,0xaa,0xaa,0x30,0x00,0x00,0x00,0x00, + 0x00,0x1d,0xdd,0xdd,0xe4,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x08,0xaa,0xaa,0xa6,0x00,0x00,0x00,0x00,0x07,0x88,0x87,0x77,0x70,0x00, + 0x00,0x03,0x45,0x55,0x55,0x51,0x00,0x00,0x00,0x46,0x66,0x77,0x77,0x73,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x69,0x99,0x9a,0xaa,0xa4,0x00,0x00,0x00,0x00,0x00, + 0x00,0x1d,0xdd,0xdd,0xe4,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x08,0xaa,0xaa,0xa6,0x00,0x00,0x00,0x00,0x18,0x88,0x87,0x77,0x50,0x00, + 0x00,0x01,0x45,0x55,0x55,0x53,0x00,0x00,0x00,0x04,0x66,0x77,0x77,0x77,0x32,0x22, + 0x22,0x22,0x22,0x22,0x22,0x26,0x99,0x99,0x9a,0xaa,0x30,0x00,0x00,0x00,0x00,0x00, + 0x00,0x1d,0xdd,0xde,0xe4,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x08,0xaa,0xaa,0xa6,0x00,0x00,0x00,0x00,0x48,0x88,0x87,0x77,0x20,0x00, + 0x00,0x00,0x45,0x55,0x55,0x55,0x00,0x00,0x00,0x00,0x47,0x77,0x77,0x77,0x77,0x77, + 0x88,0x88,0x88,0x88,0x88,0x99,0x99,0x99,0xaa,0xa3,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x1d,0xdd,0xee,0xe4,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x08,0xaa,0xaa,0xa6,0x00,0x00,0x00,0x00,0x78,0x88,0x87,0x76,0x00,0x00, + 0x00,0x00,0x25,0x55,0x55,0x55,0x20,0x00,0x00,0x00,0x05,0x77,0x77,0x77,0x77,0x78, + 0x88,0x88,0x88,0x88,0x88,0x99,0x99,0x9a,0xaa,0x40,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x1d,0xde,0xee,0xe4,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x08,0xaa,0xaa,0xa6,0x00,0x00,0x00,0x03,0x88,0x88,0x87,0x73,0x00,0x00, + 0x00,0x00,0x04,0x55,0x55,0x55,0x50,0x00,0x00,0x00,0x00,0x47,0x77,0x77,0x77,0x78, + 0x88,0x88,0x88,0x88,0x89,0x99,0x99,0xaa,0xa3,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x1d,0xee,0xee,0xe4,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x08,0xaa,0xaa,0xa6,0x00,0x00,0x00,0x07,0x88,0x88,0x87,0x70,0x00,0x00, + 0x00,0x00,0x03,0x55,0x55,0x55,0x52,0x00,0x00,0x00,0x00,0x05,0x77,0x77,0x77,0x88, + 0x88,0x88,0x88,0x88,0x99,0x99,0x9a,0xaa,0x30,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x1e,0xee,0xee,0xd4,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x08,0xaa,0xaa,0xa6,0x00,0x00,0x00,0x38,0x88,0x88,0x87,0x50,0x00,0x00, + 0x00,0x00,0x01,0x55,0x55,0x55,0x55,0x00,0x00,0x00,0x00,0x00,0x57,0x77,0x78,0x88, + 0x88,0x88,0x88,0x89,0x99,0x99,0xaa,0xa4,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x0b,0xee,0xee,0xc2,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x05,0xaa,0xaa,0xa4,0x00,0x00,0x00,0x78,0x88,0x88,0x87,0x20,0x00,0x00, + 0x00,0x00,0x00,0x45,0x55,0x55,0x56,0x20,0x00,0x00,0x00,0x00,0x05,0x77,0x88,0x88, + 0x88,0x88,0x88,0x99,0x99,0x99,0xaa,0x30,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0xae,0xec,0x20,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x5a,0xaa,0x30,0x00,0x00,0x04,0x88,0x88,0x88,0x86,0x00,0x00,0x00, + 0x00,0x00,0x00,0x15,0x55,0x55,0x56,0x50,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x01,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x08,0x88,0x88,0x88,0x82,0x00,0x00,0x00, + 0x00,0x00,0x00,0x04,0x55,0x55,0x56,0x64,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x58,0x88,0x88,0x88,0x60,0x00,0x00,0x00, + 0x00,0x00,0x00,0x01,0x55,0x55,0x56,0x66,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x88,0x88,0x88,0x88,0x10,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x35,0x55,0x56,0x66,0x50,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x07,0x88,0x88,0x88,0x85,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x05,0x55,0x56,0x66,0x64,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x58,0x88,0x88,0x88,0x71,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x02,0x55,0x56,0x66,0x66,0x20,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x88,0x88,0x88,0x88,0x40,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x45,0x56,0x66,0x66,0x60,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x18,0x88,0x88,0x88,0x87,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x15,0x56,0x66,0x66,0x65,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x78,0x88,0x88,0x88,0x81,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x03,0x56,0x66,0x66,0x66,0x40,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x05,0x98,0x88,0x88,0x88,0x40,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x56,0x66,0x66,0x66,0x63,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x49,0x98,0x88,0x88,0x87,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x05,0x66,0x66,0x66,0x67,0x30,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x03,0x99,0x98,0x88,0x88,0x71,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x66,0x66,0x66,0x67,0x72,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x39,0x99,0x98,0x88,0x88,0x20,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x36,0x66,0x66,0x67,0x77,0x20,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x03,0x99,0x99,0x98,0x88,0x84,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x04,0x66,0x66,0x67,0x77,0x73,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x49,0x99,0x99,0x98,0x88,0x60,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x46,0x66,0x67,0x77,0x77,0x40,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x04,0x99,0x99,0x99,0x98,0x86,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x05,0x66,0x67,0x77,0x77,0x75,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x69,0x99,0x99,0x99,0x98,0x70,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x56,0x67,0x77,0x77,0x77,0x60,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x17,0x99,0x99,0x99,0x99,0x97,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x05,0x67,0x77,0x77,0x77,0x77,0x20, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x03,0x99,0x99,0x99,0x99,0x99,0x70,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x57,0x77,0x77,0x77,0x78,0x85, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x59,0x99,0x99,0x99,0x99,0x97,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x05,0x77,0x77,0x77,0x78,0x88, + 0x72,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x28, + 0x99,0x99,0x99,0x99,0x99,0x70,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x57,0x77,0x77,0x78,0x88, + 0x88,0x50,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x06,0xaa, + 0x99,0x99,0x99,0x99,0x96,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x04,0x77,0x77,0x78,0x88, + 0x88,0x88,0x40,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x04,0x9a,0xaa, + 0x99,0x99,0x99,0x99,0x50,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x27,0x77,0x78,0x88, + 0x88,0x88,0x98,0x30,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x04,0x9a,0xaa,0xaa, + 0x99,0x99,0x99,0x82,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x67,0x78,0x88, + 0x88,0x88,0x99,0x98,0x30,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x04,0x9a,0xaa,0xaa,0xaa, + 0x99,0x99,0x98,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x04,0x78,0x88, + 0x88,0x88,0x99,0x99,0x98,0x51,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x15,0x9a,0xaa,0xaa,0xaa,0xaa, + 0x99,0x99,0x50,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x17,0x88, + 0x88,0x88,0x99,0x99,0x99,0x99,0x84,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x48,0xaa,0xaa,0xaa,0xaa,0xaa,0xaa, + 0x99,0x82,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x48, + 0x88,0x88,0x99,0x99,0x99,0x99,0xaa,0xa7,0x41,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x15,0x8b,0xba,0xaa,0xaa,0xaa,0xaa,0xaa,0xaa, + 0x95,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01, + 0x58,0x88,0x99,0x99,0x99,0x99,0xaa,0xaa,0xaa,0x97,0x53,0x10,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x01,0x35,0x8a,0xbb,0xbb,0xba,0xaa,0xaa,0xaa,0xaa,0xaa,0xa7, + 0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x01,0x68,0x99,0x99,0x99,0x99,0xaa,0xaa,0xaa,0xaa,0xbb,0xbb,0x98,0x76,0x54,0x44, + 0x44,0x45,0x67,0x8a,0xbb,0xbb,0xbb,0xbb,0xbb,0xba,0xaa,0xaa,0xaa,0xaa,0xa8,0x20, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x02,0x79,0x99,0x99,0x99,0xaa,0xaa,0xaa,0xaa,0xbb,0xbb,0xbb,0xbb,0xcc,0xcc, + 0xcc,0xcc,0xcc,0xcb,0xbb,0xbb,0xbb,0xbb,0xbb,0xba,0xaa,0xaa,0xaa,0xa8,0x30,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x02,0x69,0x99,0x99,0xaa,0xaa,0xaa,0xaa,0xbb,0xbb,0xbb,0xbb,0xcc,0xcc, + 0xcc,0xcc,0xcc,0xcb,0xbb,0xbb,0xbb,0xbb,0xbb,0xba,0xaa,0xaa,0xa7,0x30,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x01,0x58,0x99,0xaa,0xaa,0xaa,0xaa,0xbb,0xbb,0xbb,0xbb,0xcc,0xcc, + 0xcc,0xcc,0xcc,0xcb,0xbb,0xbb,0xbb,0xbb,0xbb,0xba,0xaa,0x95,0x10,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x36,0x9a,0xaa,0xaa,0xaa,0xbb,0xbb,0xbb,0xbb,0xcc,0xcc, + 0xcc,0xcc,0xcc,0xcb,0xbb,0xbb,0xbb,0xbb,0xbb,0xba,0x73,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x68,0xaa,0xaa,0xbb,0xbb,0xbb,0xbb,0xcc,0xcc, + 0xcc,0xcc,0xcc,0xcb,0xbb,0xbb,0xbb,0xbb,0x96,0x30,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x68,0xab,0xbb,0xbb,0xbb,0xcc,0xcc, + 0xcc,0xcc,0xcc,0xcb,0xbb,0xbb,0x96,0x31,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x34,0x67,0x89,0xab,0xbb, + 0xbb,0xba,0x98,0x76,0x53,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +}; diff --git a/include/memlayout.h b/include/memlayout.h new file mode 100644 index 0000000..96340aa --- /dev/null +++ b/include/memlayout.h @@ -0,0 +1,49 @@ +#pragma once + +// Memory layout + +#define EXTMEM 0x100000 // Start of extended memory +// Top of usable physical memory. A *variable*, read from the firmware's E820 +// map at boot -- see kernel/arch/x86_64/e820.c. It used to be the constant +// below, which was not a limit on what the kernel used but an assertion that +// exactly 224 MB existed: less and the allocator walked off the end of RAM, +// more and it was invisible. +// +// The macro spelling is kept so that the twenty-odd call sites read the same as +// they always did. It must not be used before e820_init(), which runs first +// thing in main() for that reason. +#ifndef __ASSEMBLER__ +extern uint64 phystop; +#define PHYSTOP phystop +#endif + +// What to assume when the firmware gives us nothing usable: the old constant, +// so that path behaves exactly as the kernel always did. +#define PHYSTOP_FALLBACK 0xE000000ULL + +// kvmalloc() maps physical 0-4 GB as four 1 GB pages and marks the top one +// uncached, because that is where PCI BARs and the framebuffer sit. 3 GB is +// therefore the most that can be treated as RAM. +#define PHYSTOP_MAX 0xC0000000ULL + +// Below this the kernel image, its page tables and the DMA arena do not fit. +#define PHYSTOP_MIN (24ULL * 1024 * 1024) + +// Key addresses for address space layout +#define KERNBASE 0xFFFF800000000000 // First kernel virtual address +#define KERNLINK (KERNBASE+EXTMEM) // Address where kernel is linked + +#ifndef __ASSEMBLER__ +static inline addr_t v2p(void *a) { + return ((addr_t)(a)) - ((addr_t)KERNBASE); +} +static inline void *p2v(addr_t a) { + return (void *)((a) + ((addr_t)KERNBASE)); +} +#endif + +#define V2P(a) (((addr_t) (a)) - KERNBASE) +#define P2V(a) (((void *) (a)) + KERNBASE) + +#define V2P_WO(x) ((x) - KERNBASE) // same as V2P, but without casts +#define P2V_WO(x) ((x) + KERNBASE) // same as P2V, but without casts diff --git a/include/mmu.h b/include/mmu.h new file mode 100644 index 0000000..d79e294 --- /dev/null +++ b/include/mmu.h @@ -0,0 +1,130 @@ +#pragma once + +// Eflags register +#define FL_CF 0x00000001 // Carry Flag +#define FL_PF 0x00000004 // Parity Flag +#define FL_AF 0x00000010 // Auxiliary carry Flag +#define FL_ZF 0x00000040 // Zero Flag +#define FL_SF 0x00000080 // Sign Flag +#define FL_TF 0x00000100 // Trap Flag +#define FL_IF 0x00000200 // Interrupt Enable +#define FL_DF 0x00000400 // Direction Flag +#define FL_OF 0x00000800 // Overflow Flag +#define FL_IOPL_MASK 0x00003000 // I/O Privilege Level bitmask +#define FL_NT 0x00004000 // Nested Task +#define FL_RF 0x00010000 // Resume Flag +#define FL_VM 0x00020000 // Virtual 8086 mode +#define FL_AC 0x00040000 // Alignment Check +#define FL_VIF 0x00080000 // Virtual Interrupt Flag +#define FL_VIP 0x00100000 // Virtual Interrupt Pending +#define FL_ID 0x00200000 // ID flag + +// Control Register flags +#define CR0_PE 0x00000001 // Protection Enable +#define CR0_MP 0x00000002 // Monitor coProcessor +#define CR0_EM 0x00000004 // Emulation +#define CR0_TS 0x00000008 // Task Switched +#define CR0_ET 0x00000010 // Extension Type +#define CR0_NE 0x00000020 // Numeric Errror +#define CR0_WP 0x00010000 // Write Protect +#define CR0_AM 0x00040000 // Alignment Mask +#define CR0_NW 0x20000000 // Not Writethrough +#define CR0_CD 0x40000000 // Cache Disable +#define CR0_PG 0x80000000 // Paging + +#define CR4_PSE 0x00000010 // Page size extension +#define CR4_PAE 0x00000020 // Physical address extensions +#define CR4_OSXFSR 0x00000200 // OS supports FXSAVE and FXRSTOR +#define CR4_OSXMMEXCPT 0x00000400 // OS supports SSE exceptions + +// Model specific registers +#define MSR_EFER 0xC0000080 // Extended Feature Enable Register +#define MSR_STAR 0xC0000081 // Legacy Mode SYSCALL Target +#define MSR_LSTAR 0xC0000082 // Long Mode SYSCALL Target +#define MSR_CSTAR 0xC0000083 // Compatibility Mode SYSCALL Target +#define MSR_SFMASK 0xC0000084 // SYSCALL Flag Mask + +// Segment selectors +#define SEG_KCODE 1 +#define SEG_KDATA 2 +#define SEG_UCODE32 3 +#define SEG_UDATA 4 +#define SEG_UCODE 5 +#define SEG_KCPU 6 +#define SEG_TSS 7 // TSS takes 2 slots in 64-bit GDT + +#define NSEGS 9 + +#define DPL_USER 0x3 + +#define USER_CS ((SEG_UCODE<<3)|DPL_USER) +#define USER_DS ((SEG_UDATA<<3)|DPL_USER) +#define KERNEL_CS (SEG_KCODE<<3) + +#ifndef __ASSEMBLER__ +// Segment Descriptor +struct segdesc { + uint32 lim_15_0 : 16; // Low bits of segment limit + uint32 base_15_0 : 16; // Low bits of segment base address + uint32 base_23_16 : 8; // Middle bits of segment base address + uint32 type : 4; // Segment type + uint32 s : 1; // 0 = system, 1 = application + uint32 dpl : 2; // Descriptor Privilege Level + uint32 p : 1; // Present + uint32 lim_19_16 : 4; // High bits of segment limit + uint32 avl : 1; // Available for software use + uint32 l : 1; // 1 = 64-bit segment (Long mode) + uint32 db : 1; // 0 = 16-bit, 1 = 32-bit + uint32 g : 1; // Granularity + uint32 base_31_24 : 8; // High bits of segment base address +}; + +// 64-bit System Segment Descriptor (e.g., TSS) +struct desclong { + struct segdesc sd; + uint32 base_63_32; // High bits of 64-bit base address + uint32 res; // Reserved +}; + +#define SEG(type, base, lim, dpl) (struct segdesc) \ +{ ((lim) >> 12) & 0xffff, (uint32)(base) & 0xffff, \ + ((uint32)(base) >> 16) & 0xff, type, 1, dpl, 1, \ + (uint32)(lim) >> 28, 0, 1, 0, 1, (uint32)(base) >> 24 } + +#endif + +// Application segment type bits +#define STA_X 0x8 // Executable segment +#define STA_W 0x2 // Writeable (non-executable) +#define STA_R 0x2 // Readable (executable) + +// System segment type bits +#define STS_T64A 0x9 // Available 64-bit TSS +#define STS_T64B 0xB // Busy 64-bit TSS +#define STS_IG64 0xE // 64-bit Interrupt Gate +#define STS_TG64 0xF // 64-bit Trap Gate + +// Page table structure: 4 levels +#define PMX(va) (((addr_t)(va) >> 39) & 0x1FF) +#define PDPX(va) (((addr_t)(va) >> 30) & 0x1FF) +#define PDX(va) (((addr_t)(va) >> 21) & 0x1FF) +#define PTX(va) (((addr_t)(va) >> 12) & 0x1FF) + +#define PGSIZE 4096 +#define PXMASK 0x1FF + +#define PGROUNDUP(sz) (((sz)+((addr_t)PGSIZE-1)) & ~((addr_t)(PGSIZE-1))) +#define PGROUNDDOWN(a) (((a)) & ~((addr_t)(PGSIZE-1))) + +// Page table/directory entry flags +#define PTE_P 0x001 // Present +#define PTE_W 0x002 // Writeable +#define PTE_U 0x004 // User +#define PTE_PWT 0x008 // Write-Through +#define PTE_PCD 0x010 // Cache-Disable +#define PTE_A 0x020 // Accessed +#define PTE_D 0x040 // Dirty +#define PTE_PS 0x080 // Page Size (Huge Page) + +#define PTE_ADDR(pte) ((addr_t)(pte) & 0x000FFFFFFFFFF000ull) +#define PTE_FLAGS(pte) ((addr_t)(pte) & 0xFFF) diff --git a/include/mp.h b/include/mp.h new file mode 100644 index 0000000..dea5705 --- /dev/null +++ b/include/mp.h @@ -0,0 +1,54 @@ +// See MultiProcessor Specification Version 1.[14] + +struct mp { // floating pointer + uchar signature[4]; // "_MP_" + uint32 physaddr; // 32-bit phys addr of MP config table + uchar length; // 1 + uchar specrev; // [14] + uchar checksum; // all bytes must add up to 0 + uchar type; // MP system config type + uchar imcrp; + uchar reserved[3]; +}; + +struct mpconf { // configuration table header + uchar signature[4]; // "PCMP" + ushort length; // total table length + uchar version; // [14] + uchar checksum; // all bytes must add up to 0 + uchar product[20]; // product id + uint32 oemtable_p; // OEM table pointer + ushort oemlength; // OEM table length + ushort entry; // entry count + uint32 lapicaddr_p; // address of local APIC + ushort xlength; // extended table length + uchar xchecksum; // extended table checksum + uchar reserved; +}; + +struct mpproc { // processor table entry + uchar type; // entry type (0) + uchar apicid; // local APIC id + uchar version; // local APIC verison + uchar flags; // CPU flags + #define MPBOOT 0x02 // This proc is the bootstrap processor. + uchar signature[4]; // CPU signature + uint feature; // feature flags from CPUID instruction + uchar reserved[8]; +}; + +struct mpioapic { // I/O APIC table entry + uchar type; // entry type (2) + uchar apicno; // I/O APIC id + uchar version; // I/O APIC version + uchar flags; // I/O APIC flags + uint32 addr_p; // I/O APIC address +}; + +// Table entry types +#define MPPROC 0x00 // One per processor +#define MPBUS 0x01 // One per bus +#define MPIOAPIC 0x02 // One per I/O APIC +#define MPIOINTR 0x03 // One per bus interrupt source +#define MPLINTR 0x04 // One per system interrupt source + diff --git a/include/param.h b/include/param.h new file mode 100644 index 0000000..7412dfe --- /dev/null +++ b/include/param.h @@ -0,0 +1,28 @@ +#pragma once + +#define NPROC 128 // maximum number of processes +#define KSTACKSIZE 4096 // size of per-process kernel stack (increased for x64) +#define KSTACK_CANARY 0xDEADC0DEDEADC0DEULL // sentinel at kstack[0], see kstack_check_canary() +#define NCPU 16 // maximum number of CPUs +#define NOFILE 128 // open files per process +#define NFILE 512 // open files per system +#define NINODE 200 // maximum number of active i-nodes +#define NDEV 10 // maximum major device number +#define RAMDISK_DEV 2 // memory-backed block device (kernel/dev/ramdisk.c) +#define USBDISK_DEV 3 // USB mass storage (kernel/drivers/usb_storage.c) + +// Device the root filesystem is mounted from. Building with RAMROOT=1 defines +// RAMDISK_ROOT and boots from the SCHFS image linked into the kernel instead +// of the IDE disk, leaving the physical disk free to be reformatted from a +// running system. See kernel/dev/ramdisk.c. +#ifdef RAMDISK_ROOT +#define ROOTDEV RAMDISK_DEV +#else +#define ROOTDEV 1 // device number of file system root disk +#endif +#define MAXARG 32 // max exec arguments +#define MAXPATH 128 // max path +#define MAXOPBLOCKS 30 // max # of blocks any FS op writes +#define LOGSIZE (MAXOPBLOCKS*3) // max data blocks in on-disk log +#define NBUF (MAXOPBLOCKS*3) // size of disk block cache +#define FSSIZE 150000 // size of file system in blocks diff --git a/include/pci.h b/include/pci.h new file mode 100644 index 0000000..7c5eb15 --- /dev/null +++ b/include/pci.h @@ -0,0 +1,110 @@ +#pragma once + +// PCI bus enumeration and driver binding. +// +// Until this existed, every driver in Schunix found its hardware by knowing +// where it was: IDE at 0x1F0 because IDE has always been at 0x1F0, the +// framebuffer wherever the BIOS's VBE call said. That works for exactly the +// devices that predate PCI and for nothing else. A NIC, an AHCI controller, +// a virtio device or a real GPU has no fixed address at all -- you find it by +// asking the bus what is plugged into it, which is what this does. + +// --------------------------------------------------------------------------- +// Config space +// --------------------------------------------------------------------------- + +#define PCI_CONFIG_ADDR 0xCF8 +#define PCI_CONFIG_DATA 0xCFC + +#define PCI_VENDOR_ID 0x00 +#define PCI_DEVICE_ID 0x02 +#define PCI_COMMAND 0x04 +#define PCI_STATUS 0x06 +#define PCI_REVISION 0x08 +#define PCI_PROG_IF 0x09 +#define PCI_SUBCLASS 0x0A +#define PCI_CLASS 0x0B +#define PCI_HEADER_TYPE 0x0E +#define PCI_BAR0 0x10 +#define PCI_SECONDARY_BUS 0x19 +#define PCI_INTERRUPT_LINE 0x3C +#define PCI_INTERRUPT_PIN 0x3D + +// PCI_COMMAND bits a driver usually wants set before touching its device. +#define PCI_CMD_IO 0x0001 +#define PCI_CMD_MEMORY 0x0002 +#define PCI_CMD_BUS_MASTER 0x0004 + +// Base classes worth naming. The full list is long and mostly irrelevant here. +#define PCI_CLASS_STORAGE 0x01 +#define PCI_CLASS_NETWORK 0x02 +#define PCI_CLASS_DISPLAY 0x03 +#define PCI_CLASS_MULTIMEDIA 0x04 +#define PCI_CLASS_BRIDGE 0x06 +#define PCI_CLASS_SERIAL_BUS 0x0C + +#define PCI_STATUS_CAPLIST 0x0010 // device has a capability list +#define PCI_CAP_PTR 0x34 // offset of the first capability + +#define PCI_CAP_MSI 0x05 +#define PCI_CAP_MSIX 0x11 +#define PCI_CAP_VENDOR 0x09 // virtio's configuration lives here +#define PCI_CAP_PCIE 0x10 + +#define PCI_ANY 0xFFFF // wildcard in a match table + +#define NPCI 64 // devices we will remember + +struct pci_bar { + uint64 base; // already masked; 0 means "no BAR here" + uint64 size; // determined by write-ones-and-read-back + int is_io; // I/O port range rather than memory + int is_64; // consumed the following BAR slot too + int prefetch; +}; + +struct pci_dev { + uint8 bus, slot, func; + uint16 vendor, device; + uint8 class, subclass, prog_if, revision; + uint8 irq; // interrupt line, 0xFF when unrouted + uint8 header_type; + struct pci_bar bar[6]; + const char *driver; // name of whatever claimed it, or 0 +}; + +// One row of a driver's match table. Terminate the table with a row whose +// vendor is 0. +struct pci_match { + uint16 vendor, device; // PCI_ANY to ignore + uint8 class, subclass; // used only when vendor is PCI_ANY +}; + +// Declared here rather than defs.h because both take a struct pci_dev, which +// only this header defines. See kernel/dev/device.c for the model they feed. +// device.h is included rather than forward-declaring `enum dev_class`, which +// is a compiler extension and warns. +#include "device.h" + +enum dev_class pci_class_of(const struct pci_dev *); +void pci_describe(const struct pci_dev *, char *out, int cap); + +struct pci_driver { + const char *name; + // Base for the device's name in /proc/devices, if the driver's own name + // makes a bad one. `e1000` plus an index reads as "e10000"; a NIC wants to + // be `eth0`. Optional -- null means use `name`. + const char *devname; + const struct pci_match *match; + // Class-specific vtable, published on the device when this driver claims + // it -- struct display_ops for a display, and so on. Optional; a driver + // with nothing to offer a consumer leaves it null. + // + // It lives on the driver rather than being set inside probe() because + // pci_bind() registers the device *after* probe returns, so a driver has + // nothing to attach it to at the point it would want to. + const void *ops; + // Return 0 to claim the device. A driver that returns non-zero leaves it + // unclaimed for anything else that matches. + int (*probe)(struct pci_dev *); +}; diff --git a/include/posix.h b/include/posix.h new file mode 100644 index 0000000..87757c4 --- /dev/null +++ b/include/posix.h @@ -0,0 +1,289 @@ +// SPDX-License-Identifier: Apache-2.0 +// Schunix — POSIX-compatibility types used by kernel syscall handlers. +#pragma once +#include "types.h" +#include "date.h" + +struct iovec { + void *iov_base; + uint64 iov_len; +}; + +// Linux's IOV_MAX. readv/writev used to cap iovcnt at 16 -- which is legal, +// since POSIX only requires 16 -- and returned EINVAL above it. But programs +// are written against the platform they run on, and on Linux that limit is +// 1024: toybox's `yes` builds a 256-entry iovec and writev()s it in a loop, +// so `yes | head -2` failed with "Invalid argument" before producing a single +// byte. Nothing here costs per-entry memory; the count is just a loop bound. +#define UIO_MAXIOV 1024 + +// Simplified termios (matches Linux x86_64 layout enough for basic use). +struct termios { + uint32 c_iflag; + uint32 c_oflag; + uint32 c_cflag; + uint32 c_lflag; + uint8 c_line; + uint8 c_cc[19]; + uint32 c_ispeed; + uint32 c_ospeed; +}; + +// Terminal flags, with Linux's values. The kernel honours the ones marked +// below; the rest are accepted and stored so a program that sets them and +// reads them back sees what it wrote, which is what cfmakeraw() and every +// line-editing library actually check. +// +// c_iflag +#define IGNBRK 0000001 +#define BRKINT 0000002 +#define IGNPAR 0000004 +#define PARMRK 0000010 +#define INPCK 0000020 +#define ISTRIP 0000040 // honoured: strip the eighth bit +#define INLCR 0000100 // honoured: NL -> CR on input +#define IGNCR 0000200 // honoured: drop CR +#define ICRNL 0000400 // honoured: CR -> NL on input +#define IUCLC 0001000 +#define IXON 0002000 +#define IXANY 0004000 +#define IXOFF 0010000 +#define IMAXBEL 0020000 +#define IUTF8 0040000 + +// c_oflag +#define OPOST 0000001 // honoured: enable output processing +#define OLCUC 0000002 +#define ONLCR 0000004 // honoured: NL -> CR NL on output +#define OCRNL 0000010 +#define ONOCR 0000020 +#define ONLRET 0000040 + +// c_cflag +#define CS8 0000060 +#define CREAD 0000200 +#define HUPCL 0002000 +#define CLOCAL 0004000 + +// c_lflag +#define ISIG 0000001 // honoured: INTR/QUIT/SUSP generate signals +#define ICANON 0000002 // honoured: line-at-a-time with editing +#define ECHO 0000010 // honoured +#define ECHOE 0000020 // honoured: ERASE rubs the character out +#define ECHOK 0000040 +#define ECHONL 0000100 // honoured: echo NL even with ECHO off +#define NOFLSH 0000200 +#define TOSTOP 0000400 +#define IEXTEN 0100000 + +// c_cc indices (Linux order). VMIN/VTIME share slots with VEOF/VEOL, which is +// why they are only meaningful in the mode the other pair is not. +#define VINTR 0 +#define VQUIT 1 +#define VERASE 2 +#define VKILL 3 +#define VEOF 4 +#define VTIME 5 +#define VMIN 6 +#define VSWTC 7 +#define VSTART 8 +#define VSTOP 9 +#define VSUSP 10 +#define VEOL 11 +#define VREPRINT 12 +#define VDISCARD 13 +#define VWERASE 14 +#define VLNEXT 15 +#define VEOL2 16 + +struct winsize { + uint16 ws_row; + uint16 ws_col; + uint16 ws_xpixel; + uint16 ws_ypixel; +}; + +struct rlimit { + uint64 rlim_cur; + uint64 rlim_max; +}; + +// Linux ioctl codes we handle. +#define TCGETS 0x5401 +#define TCSETS 0x5402 +#define TCSETSW 0x5403 +#define TCSETSF 0x5404 +#define TIOCGPGRP 0x540F +#define TIOCSPGRP 0x5410 +#define TIOCGWINSZ 0x5413 +#define TIOCSWINSZ 0x5414 +#define TIOCSCTTY 0x540E +#define TIOCGSERIAL 0x541E +#define FIONREAD 0x541B +#define TIOCEXCL 0x540C +#define TIOCNXCL 0x540D + +// arch_prctl commands +#define ARCH_SET_GS 0x1001 +#define ARCH_SET_FS 0x1002 +#define ARCH_GET_FS 0x1003 +#define ARCH_GET_GS 0x1004 + +// clone flags (subset) +#define CLONE_VM 0x00000100 +#define CLONE_THREAD 0x00010000 +#define CLONE_SIGHAND 0x00000800 + +// futex ops +#define FUTEX_WAIT 0 +#define FUTEX_WAKE 1 +#define FUTEX_WAIT_PRIVATE 128 +#define FUTEX_WAKE_PRIVATE 129 + +// Resource limit codes (POSIX) +#define RLIMIT_AS 9 +#define RLIMIT_STACK 3 +#define RLIMIT_NOFILE 7 + +// clock IDs +#define CLOCK_REALTIME 0 +#define CLOCK_MONOTONIC 1 + +struct timespec { + long tv_sec; + long tv_nsec; +}; + +struct itimerval { + struct timeval it_interval; + struct timeval it_value; +}; + +struct rusage { + struct timeval ru_utime; + struct timeval ru_stime; + long ru_maxrss; + long ru_ixrss; + long ru_idrss; + long ru_isrss; + long ru_minflt; + long ru_majflt; + long ru_nswap; + long ru_inblock; + long ru_oublock; + long ru_msgsnd; + long ru_msgrcv; + long ru_nsignals; + long ru_nvcsw; + long ru_nivcsw; +}; + +struct tms { + long tms_utime; + long tms_stime; + long tms_cutime; + long tms_cstime; +}; + +struct sysinfo { + long uptime; + uint64 loads[3]; + uint64 totalram; + uint64 freeram; + uint64 sharedram; + uint64 bufferram; + uint64 totalswap; + uint64 freeswap; + uint16 procs; + uint16 pad; + uint32 pad2; + uint64 totalhigh; + uint64 freehigh; + uint32 mem_unit; + char _f[8]; +}; + +struct statfs { + int64 f_type; + int64 f_bsize; + int64 f_blocks; + int64 f_bfree; + int64 f_bavail; + int64 f_files; + int64 f_ffree; + int64 f_fsid[2]; + int64 f_namelen; + int64 f_frsize; + int64 f_flags; + int64 f_spare[4]; +}; + +// at-syscall dirfd sentinel +#define AT_FDCWD (-100) +#define AT_REMOVEDIR 0x200 +#define AT_SYMLINK_NOFOLLOW 0x100 +#define AT_EMPTY_PATH 0x1000 +#define AT_NO_AUTOMOUNT 0x800 + +// prctl options +#define PR_SET_NAME 15 +#define PR_GET_NAME 16 +#define PR_SET_DUMPABLE 4 +#define PR_GET_DUMPABLE 3 + +// signal numbers (Linux x86_64 values), used by kill dispatch and +// default-action classification in sigsend() +#define SIGHUP 1 +#define SIGINT 2 +#define SIGQUIT 3 +#define SIGILL 4 +#define SIGABRT 6 +#define SIGBUS 7 +#define SIGFPE 8 +#define SIGKILL 9 +#define SIGUSR1 10 +#define SIGSEGV 11 +#define SIGUSR2 12 +#define SIGPIPE 13 +#define SIGALRM 14 +#define SIGTERM 15 +#define SIGCHLD 17 +#define SIGCONT 18 +#define SIGSTOP 19 +// Job-control and the remaining standard signals. A shell needs the TSTP/ +// TTIN/TTOU trio to implement ^Z and background-job terminal access at all; +// they were simply absent, so any reference to them failed to compile. +#define SIGTRAP 5 +#define SIGTSTP 20 +#define SIGTTIN 21 +#define SIGTTOU 22 +#define SIGURG 23 +#define SIGXCPU 24 +#define SIGXFSZ 25 +#define SIGVTALRM 26 +#define SIGPROF 27 +#define SIGWINCH 28 +#define SIGIO 29 +#define SIGPWR 30 +#define SIGSYS 31 + +// getrusage who +#define RUSAGE_SELF 0 +#define RUSAGE_CHILDREN (-1) + +// fallocate mode +#define FALLOC_FL_KEEP_SIZE 1 + +// poll(2) event bits (Linux values) +#define POLLIN 0x001 +#define POLLPRI 0x002 +#define POLLOUT 0x004 +#define POLLERR 0x008 +#define POLLHUP 0x010 +#define POLLNVAL 0x020 + +struct pollfd { + int fd; + short events; + short revents; +}; diff --git a/include/proc.h b/include/proc.h new file mode 100644 index 0000000..773b3a5 --- /dev/null +++ b/include/proc.h @@ -0,0 +1,240 @@ +#pragma once + +#include "types.h" +#include "traps.h" + +#define NVMA 64 + +struct vma { + uint64 addr; + uint64 length; + int used; + // The pages under this mapping are not ours to free on unmap: either they + // are device memory (the framebuffer) or they belong to something with a + // longer life than this process (a shared memory segment). This was + // `is_fb`, which was the only such case until shared memory existed -- + // and freeing another process's shared pages on exit is exactly the kind + // of bug that shows up somewhere else entirely, hours later. + int no_free; + int shmid; // shared segment this came from, or -1 +}; + +// Per-CPU state +struct cpu { + uchar id; + uchar apicid; // Local APIC ID + struct context *scheduler; // swtch() here to enter scheduler + volatile uint64 started; // Has the CPU started? + int ncli; // Depth of pushcli nesting. + int intena; // Were interrupts enabled before pushcli? + void *local; // CPU-local storage (GS base) +}; + +extern struct cpu cpus[NCPU]; +extern int ncpu; // processors present +extern int ncpu_online; // processors actually running + +// --- per-CPU variables ------------------------------------------------------ +// +// These were `__thread struct cpu *cpu` and `__thread struct proc *proc`, and +// that was wrong in a way that only appears once a second processor runs. +// +// `__thread` means *per thread*, and the compiler is entitled to assume a +// thread's pointer never changes: it reads %fs:0 once and keeps the value in a +// register or spilled on the stack across calls. clang did exactly that in +// sleep(): +// +// movq %fs:0x0, %rax # read the thread pointer +// movq %rax, -0x18(%rbp) # spill it +// callq sched # <-- the CPU can change here +// movq -0x18(%rbp), %rax # reload the OLD pointer +// movq (%rax,%rcx), %rax # proc, from the previous CPU's block +// movq $0x0, 0x40(%rax) # NULL, because that CPU set proc = 0 +// +// But %fs here is *per CPU*, not per thread, and a kernel thread changes CPU at +// every sched(). So the assumption is false for exactly the variables that +// most need to be right, and the failure is a null dereference deep in the +// scheduler on a machine with more than one core. +// +// The fix is to read the value at each use, with no cached base: an explicit +// %gs-relative access that the compiler cannot hoist across a call. `volatile` +// is what forbids that, and the memory clobber on the setters keeps stores in +// order with the rest of the scheduler's bookkeeping. +// +// Layout of the per-CPU block, whose base is in both FS_BASE and GS_BASE, set +// by seginit() in kernel/arch/x86_64/vm.c: +// +// +0 self-pointer required by the x86-64 TLS ABI (%fs:0) +// +8 syscall scratch trapasm.S parks %rsp here across the stack switch +// +16 struct cpu * +// +24 struct proc * +// +// Offset 8 is NOT free. syscall_entry uses %fs:(8) as its scratch slot, and +// because seginit() sets GS base and FS base to the same address, %fs:8 and +// %gs:8 are the same memory. Putting `cpu` there made every syscall overwrite +// it with %rsp -- a #GP in syscall_entry itself, several layers below anything +// that could report it. +// +// trapasm.S reads %gs:24 for proc on the way out to user mode, so these three +// offsets are shared with assembly and must not be reordered. +#define PERCPU_SELF 0 +#define PERCPU_SCRATCH 8 +#define PERCPU_CPU 16 +#define PERCPU_PROC 24 + +static inline struct cpu * +cpu_get(void) +{ + struct cpu *c; + asm volatile("movq %%gs:16, %0" : "=r" (c)); + return c; +} + +static inline void +cpu_set(struct cpu *c) +{ + asm volatile("movq %0, %%gs:16" :: "r" (c) : "memory"); +} + +static inline struct proc * +proc_get(void) +{ + struct proc *p; + asm volatile("movq %%gs:24, %0" : "=r" (p)); + return p; +} + +static inline void +proc_set(struct proc *p) +{ + asm volatile("movq %0, %%gs:24" :: "r" (p) : "memory"); +} + +// Named mycpu()/myproc(), as xv6-riscv does, and *not* spelled `cpu`/`proc`. +// A macro by those names would also rewrite every `struct cpu` and +// `struct proc`, because macros have no namespace and tags do -- which is +// exactly why the variables could carry those names in the first place. +#define mycpu() cpu_get() +#define myproc() proc_get() + +// Saved registers for kernel context switches (x86_64 ABI) +struct context { + uint64 r15; + uint64 r14; + uint64 r13; + uint64 r12; + uint64 rbx; + uint64 rbp; + uint64 rip; +}; + +enum procstate { UNUSED, EMBRYO, SLEEPING, RUNNABLE, RUNNING, ZOMBIE }; + +// Per-process state +// A copy of the interesting fields of one process, taken under ptable.lock. +// procfs reads these rather than struct proc, so nothing outside proc.c needs +// the lock or the layout. +struct procinfo { + int pid, ppid, pgid, sid; + uint uid; + int state; // enum procstate + uint64 sz; + int killed; + char name[16]; + char exe[64]; // full path, for /proc//exe +}; + +struct proc { + uint64 sz; // Size of process memory (bytes) [+0x00] + pml4e_t* pgdir; // Page table (PML4 on x86_64) [+0x08] + char *kstack; // Bottom of kernel stack [+0x10] + uint64 fs_base; // User TLS base (IA32_FS_BASE MSR) [+0x18] ← trapasm.S depends on this offset + enum procstate state; // Process state [+0x20] + int pid; // [+0x24] + struct proc *parent; // [+0x28] + struct trapframe *tf; // [+0x30] + struct context *context; // [+0x38] + void *chan; // [+0x40] + int killed; // [+0x48] + int _pad; // [+0x4c] + struct file *ofile[NOFILE]; // Open files [+0x50] + struct inode *cwd; // follows + char name[16]; // + // The path exec() was given. `name` is only the last component and only 15 + // characters of it, which is what /proc//stat wants; this is the whole + // path, reported by /proc//exe. + // + // Note it is exposed as a regular *file*, not the symlink Linux uses, so + // `cat /proc/1/exe` works but readlink() does not. That is why `ps -o comm` + // still prints "[init]": toybox readlink()s exe and brackets the name when + // that fails. Default ps and top output is unaffected. Fixing it properly + // means symlink support in procfs, which is worth doing with the rest of the + // VFS work rather than on its own. + char exe[64]; + uint64 signal_mask; + uint64 pending_signals; + int alarm_ticks; + char ofile_flags[NOFILE]; + + // Real signal delivery (added for SIGCHLD — see kernel/sys/syscall.c's + // deliver_signals() and kernel/proc/proc.c's exit()). sig_handler[sig]/ + // sig_restorer[sig] come straight from the user's struct sigaction + // (sys_rt_sigaction); 0 means no handler installed. saved_sigtf stashes + // the pre-signal trapframe so sys_rt_sigreturn() can restore it; in_signal + // prevents re-entering a handler while one is already running (we don't + // support nested signal delivery). + uint64 sig_handler[32]; + uint64 sig_restorer[32]; + struct trapframe saved_sigtf; + int in_signal; + + // rt_sigsuspend() support: the pre-suspend mask to restore once the + // handler that woke us finishes (sigsuspend swaps the mask atomically + // for the duration of the wait -- POSIX). sigsusp_active is set while a + // restore is owed; sys_rt_sigreturn() performs it. + uint64 sigsusp_oldmask; + int sigsusp_active; + + struct vma vma[NVMA]; // VMA tracking for mmap + uint64 mmap_next; // next free mmap VA (bump allocator, see sys_mmap) + + // ABI translation (Linux compat syscall number map) + int *syscall_map; + int map_size; + + // Current working directory path; maintained by sys_chdir / sys_fchdir. + char cwd_path[256]; + + // --- POSIX process identity ------------------------------------------- + // Appended at the end of the struct deliberately: trapasm.S hard-codes the + // offsets of the fields at the top (see the _Static_assert on fs_base in + // kernel/proc/proc.c), so nothing may be inserted above them. + + // Exit status, already encoded the way wait(2) reports it -- see + // W_EXITCODE() below. Read by waitpid() before the slot is recycled. + int xstate; + + // Signal that killed us, if any. Set by sigsend() alongside ->killed so the + // eventual exit() can report *which* signal, instead of a blanket SIGKILL. + int killsig; + + // Process group and session. Job control is defined entirely in terms of + // these: a shell puts each pipeline in its own process group and signals the + // group, not individual pids. Without them a shell's wait loop cannot work, + // which is why mksh's job control never made progress. + int pgid; + int sid; + + // Real/effective/saved user and group ids. Nothing enforces permissions with + // them yet (the on-disk format has no owner field until SchFS), but they + // are tracked honestly so getuid()/setuid() stop lying to userland. + uint uid, euid, suid; + uint gid, egid, sgid; +}; +#define FD_CLOEXEC 1 + +// wait(2) status encoding, as understood by musl's WIFEXITED/WEXITSTATUS/ +// WIFSIGNALED/WTERMSIG macros: +// normal exit -> (code & 0xff) << 8 +// killed by signal -> sig & 0x7f +#define W_EXITCODE(code, sig) ((((code) & 0xff) << 8) | ((sig) & 0x7f)) \ No newline at end of file diff --git a/include/qr.h b/include/qr.h new file mode 100644 index 0000000..1c7b762 --- /dev/null +++ b/include/qr.h @@ -0,0 +1,42 @@ +#pragma once + +// A QR encoder, for getting a crash report off a machine whose network stack is +// dead. +// +// A panic report is worth nothing if it cannot leave the machine, and at panic +// time there is no network, no filesystem write that can be trusted, and often +// no second machine on the desk. A phone camera is a working out-of-band +// channel that needs no drivers on our side at all. See docs/roadmap.md Phase 8. +// +// Deliberately small, in three ways: +// +// - Byte mode only. The report is arbitrary bytes; alphanumeric mode would +// pack tighter but only for a restricted character set. +// - Error correction level L only. The screen is a clean, evenly lit, +// high-contrast source -- the failure mode QR's stronger levels protect +// against (dirt, print damage, curved packaging) does not apply. +// - Versions 1 to 6 only. Version 7 is where QR starts requiring an extra +// 18-bit version-information block in two more places on the symbol, and +// version 10 is where the character count field grows and the alignment +// pattern grid becomes 2-dimensional. Stopping at 6 avoids both, and +// 134 bytes is a large crash report. +// +// If a payload ever needs more than 134 bytes, the honest fix is to shorten the +// report -- not to reach for version 10 and its two extra tables. + +#define QR_MAX_VERSION 6 +#define QR_MAX_SIZE (17 + 4 * QR_MAX_VERSION) // 41 modules per side +#define QR_MAX_BYTES 134 + +struct qr { + int size; // modules per side; 0 if unencoded + unsigned char mod[QR_MAX_SIZE][QR_MAX_SIZE]; // 1 = dark + unsigned char fn[QR_MAX_SIZE][QR_MAX_SIZE]; // 1 = function pattern +}; + +// Encode len bytes into q. Picks the smallest version that fits. +// Returns 0, or -1 if len > QR_MAX_BYTES. +int qr_encode(struct qr *q, const unsigned char *data, int len); + +// Largest payload, in bytes, for a given version (1..QR_MAX_VERSION). +int qr_capacity(int version); diff --git a/include/schfs.h b/include/schfs.h new file mode 100644 index 0000000..ffe44d7 --- /dev/null +++ b/include/schfs.h @@ -0,0 +1,246 @@ +// SchFS — on-disk format. +// +// Design rationale lives in docs/architecture/schfs.md. In short: copy-on-write +// with the checksum of a block stored in the pointer that leads to it (ZFS), +// one B-tree reused for every kind of record (btrfs), and attribute indexes as +// just another record type (BeFS). +// +// This header is shared by the kernel and by host tools (tools/mkfs, +// tools/schfs_test), so it must stay free of kernel-only types. + +#ifndef SCHFS_H +#define SCHFS_H + +#include + +#define SCHFS_MAGIC 0x53636846u // "SchF" +#define SCHFS_NODE_MAGIC 0x536e6f64u // "Snod" + +// 4096 to match modern media and make extents worth having. The old format +// used 1024, which is also why its directories were so wasteful. +#define SCHFS_BLKSIZE 4096 + +// Deepest tree we will walk without recursing. 8 levels of a ~84-way internal +// node is far more than any plausible volume needs. +#define SCHFS_MAX_LEVEL 8 + +// --------------------------------------------------------------------------- +// Keys +// +// Every record in the filesystem is addressed by one 24-byte key, sorted by +// (objectid, type, offset). This is the whole reason there is only one tree: +// inodes, directory entries, extent maps and indexes are just different `type` +// values in the same keyspace. +// --------------------------------------------------------------------------- + +#define SCHFS_KEY_INODE 1 // (ino, INODE, 0) -> struct schfs_inode +#define SCHFS_KEY_DIRENT 2 // (dirino,DIRENT, namehash) -> packed dirent list +#define SCHFS_KEY_EXTENT 3 // (ino, EXTENT, fileblk) -> struct schfs_extent +#define SCHFS_KEY_ATTR 4 // (ino, ATTR, namehash) -> attribute value +#define SCHFS_KEY_INDEX 5 // (idxid, INDEX, valuehash) -> objectid +#define SCHFS_KEY_CSUM 6 // (CSUM, CSUM, diskblk) -> uint32 CRC32c + +// Data-block checksums live under one reserved object id, exactly as btrfs +// keeps a checksum tree. Tree nodes carry their checksum in their parent +// pointer, but file *data* blocks have no parent to hold it — without these +// records "end-to-end checksums" would only cover metadata, which is the part +// least likely to rot. +#define SCHFS_CSUM_OBJECTID 1ULL + +// Object ids below this are reserved; the root directory is the first real one. +#define SCHFS_FIRST_OBJECTID 16ULL +#define SCHFS_ROOT_OBJECTID 16ULL + +struct schfs_key { + uint64_t objectid; + uint64_t offset; + uint8_t type; + uint8_t pad[7]; +}; /* 24 bytes */ + +// --------------------------------------------------------------------------- +// Node header — identical for internal nodes and leaves. +// +// `blocknr` is a self-pointer: a block that was written to the wrong place +// still checksums correctly, so the only way to catch a misdirected write is +// for the block to say where it thinks it lives. +// --------------------------------------------------------------------------- + +struct schfs_node_hdr { + uint32_t magic; // SCHFS_NODE_MAGIC + uint32_t crc; // CRC32c of the whole block, this field zeroed + uint64_t blocknr; // where this block believes it lives + uint64_t generation; // transaction that wrote it + uint16_t level; // 0 = leaf, >0 = internal + uint16_t nritems; + uint32_t pad; +}; /* 32 bytes */ + +// Internal node entry: a key and the child it leads to, plus that child's +// checksum and generation. Storing the checksum *here* rather than in the +// child is what makes the tree self-validating. +struct schfs_kptr { + struct schfs_key key; + uint64_t blocknr; + uint64_t generation; + uint32_t crc; + uint32_t pad; +}; /* 48 bytes */ + +// Leaf entry. Items grow forward from the end of the header; their data grows +// backward from the end of the block, exactly like btrfs. `offset` is measured +// from the start of the block. +struct schfs_item { + struct schfs_key key; + uint32_t offset; + uint32_t size; +}; /* 32 bytes */ + +#define SCHFS_NODE_SPACE (SCHFS_BLKSIZE - (int)sizeof(struct schfs_node_hdr)) +#define SCHFS_MAX_KPTR (SCHFS_NODE_SPACE / (int)sizeof(struct schfs_kptr)) +#define SCHFS_MAX_ITEMS (SCHFS_NODE_SPACE / (int)sizeof(struct schfs_item)) + +// Largest value that can live inline in a leaf. Anything bigger becomes an +// extent record pointing at data blocks. +#define SCHFS_MAX_INLINE (SCHFS_NODE_SPACE / 4) + +// --------------------------------------------------------------------------- +// Uberblock +// +// A ring of these is written round-robin. Mounting means reading them all and +// taking the highest generation that checksums — that is the entire crash +// recovery story, in place of a journal replay. +// --------------------------------------------------------------------------- + +#define SCHFS_UBER_COUNT 128 +#define SCHFS_UBER_START 1 // block 0 stays free for a boot sector + +// First block available for the tree and for file data: everything below is +// the boot block and the uberblock ring. +#define SCHFS_FIRST_BLOCK (SCHFS_UBER_START + SCHFS_UBER_COUNT) + +#define SCHFS_VERSION 1 +#define SCHFS_LABEL_MAX 32 + +struct schfs_uberblock { + uint32_t magic; // SCHFS_MAGIC + uint32_t crc; // CRC32c of this struct, this field zeroed + uint32_t version; // SCHFS_VERSION + uint32_t blocksize; // SCHFS_BLKSIZE; a reader must refuse a mismatch + uint64_t generation; // transaction number; highest valid one wins + uint64_t root; // block number of the tree root + uint32_t root_crc; // checksum of that root block + uint32_t pad0; + uint64_t root_generation; + uint64_t total_blocks; + // Informational only. Which blocks are actually free is derived from the + // tree at mount rather than stored, so this is what the volume believed at + // commit time — useful for df before anything is mounted, never trusted. + uint64_t free_blocks; + uint64_t reserved0; + uint64_t next_objectid; + uint64_t root_objectid; // normally SCHFS_ROOT_OBJECTID + uint8_t uuid[16]; + char label[SCHFS_LABEL_MAX]; + uint8_t pad[SCHFS_BLKSIZE - 136]; +}; + +// --------------------------------------------------------------------------- +// Records +// --------------------------------------------------------------------------- + +#define SCHFS_T_FILE 1 +#define SCHFS_T_DIR 2 +#define SCHFS_T_DEV 3 +#define SCHFS_T_SYMLINK 4 + +struct schfs_inode { + uint64_t size; + uint64_t blocks; + uint64_t atime, mtime, ctime; + // Containing directory. Meaningful for directories only, where it is what + // ".." resolves to — neither "." nor ".." is stored on disk, so a directory + // costs nothing until it has real entries. For files with several hard + // links "parent" is simply the first directory that linked them and must + // not be trusted. + uint64_t parent; + uint32_t mode; // real POSIX mode bits, at last + uint32_t uid, gid; + uint32_t nlink; + uint16_t type; // SCHFS_T_* + uint16_t rdev_major, rdev_minor; + uint16_t pad; + uint32_t flags; // SCHFS_I_* — none defined yet + uint32_t gen; // transaction that last changed this inode; + // what an incremental send/snapshot diff needs +}; + +// An extent maps a run of file blocks onto a run of disk blocks. refs is what +// makes snapshots and reflink copies nearly free: sharing a range is a refcount +// bump rather than a data copy. +struct schfs_extent { + uint64_t disk_block; + uint64_t nblocks; + // Transaction group that allocated this run. A run stamped with the group + // currently being built cannot be reached from any committed root, so its + // blocks may be overwritten in place — which is what stops a program that + // writes a hundred bytes at a time from copying a whole 4 KB block, and + // then a whole tree path, on every call. + // + // A full 64 bits, matching the tree's generation counter, because a 32-bit + // one would wrap after four billion commits and a wrapped comparison says + // "same group" about a block belonging to a committed tree — the one + // mistake this field exists to prevent. Only ever trusted together with + // refs == 1; a shared run belongs to somebody else as well. + uint64_t gen; + uint32_t refs; + uint32_t pad; +}; + +// Directory entry payload. The key's offset field holds the name hash, so a +// lookup is one tree descent rather than a directory scan. +// +// Two names in one directory can hash the same, and the key would then be +// identical — the second insert would silently replace the first. So the value +// is a *packed list*: one schfs_dirent_rec plus its name bytes, repeated. A +// 64-bit hash makes lists longer than one vanishingly rare, but "vanishingly +// rare" is not "impossible", and a filesystem that loses a file under a hash +// collision is not one to build on. +struct schfs_dirent_rec { + uint64_t objectid; + uint16_t type; + uint16_t namelen; + uint32_t pad; + /* name bytes follow, namelen of them, not NUL-terminated */ +}; + +#define SCHFS_NAME_MAX 255 + +// Step from one packed dirent to the next. Records are 8-byte aligned so the +// uint64 objectid of the next one is naturally aligned. +#define SCHFS_DIRENT_LEN(namelen) \ + ((unsigned)((sizeof(struct schfs_dirent_rec) + (namelen) + 7) & ~7u)) + +// --------------------------------------------------------------------------- +// Compile-time layout checks. +// +// Every one of these is a number that ends up baked into images on disk. A +// silent change to any of them — adding a field, changing a type, a compiler +// padding a struct differently — makes every existing image unreadable in a way +// that looks like corruption rather than like a mistake. Failing the build is +// enormously cheaper than debugging that. +// --------------------------------------------------------------------------- + +#define SCHFS_ASSERT_SIZE(t, n) \ + typedef char schfs_sizecheck_##t[(sizeof(struct t) == (n)) ? 1 : -1] + +SCHFS_ASSERT_SIZE(schfs_key, 24); +SCHFS_ASSERT_SIZE(schfs_node_hdr, 32); +SCHFS_ASSERT_SIZE(schfs_kptr, 48); +SCHFS_ASSERT_SIZE(schfs_item, 32); +SCHFS_ASSERT_SIZE(schfs_uberblock, SCHFS_BLKSIZE); +SCHFS_ASSERT_SIZE(schfs_inode, 80); +SCHFS_ASSERT_SIZE(schfs_extent, 32); +SCHFS_ASSERT_SIZE(schfs_dirent_rec, 16); + +#endif /* SCHFS_H */ diff --git a/include/sleeplock.h b/include/sleeplock.h new file mode 100644 index 0000000..e389e34 --- /dev/null +++ b/include/sleeplock.h @@ -0,0 +1,9 @@ +// Long-term locks for processes +struct sleeplock { + uint locked; // Is the lock held? + struct spinlock lk; // spinlock protecting this sleep lock + + // For debugging: + char *name; // Name of lock. + int pid; // Process holding lock +}; diff --git a/include/socket.h b/include/socket.h new file mode 100644 index 0000000..9d1f581 --- /dev/null +++ b/include/socket.h @@ -0,0 +1,81 @@ +#pragma once + +#include "types.h" + +// Unix domain sockets. +// +// These exist for Schcom. A compositor and its clients have to exchange +// messages, and a pipe cannot do it: a pipe is one-directional, has no +// notion of a connection, and gives no way for a server to be *found* by a +// client that starts later. A socket with a name in the filesystem is +// exactly the missing piece. +// +// AF_INET is deliberately absent. The socket layer is being built around the +// local case first because that is the one the GUI needs and the one whose +// semantics are simplest; a network stack plugs in underneath the same +// syscalls later rather than reshaping them. + +#define AF_UNIX 1 +#define AF_LOCAL 1 +#define AF_INET 2 // recognised so it can be refused honestly + +#define SOCK_STREAM 1 +#define SOCK_DGRAM 2 + +#define UNIX_PATH_MAX 108 + +#define SHUT_RD 0 +#define SHUT_WR 1 +#define SHUT_RDWR 2 + +struct sockaddr_un { + uint16 sun_family; + char sun_path[UNIX_PATH_MAX]; +}; + +// The generic form syscalls take. Same shape as Linux's, so a musl-linked +// program's struct lines up byte for byte. +struct sockaddr { + uint16 sa_family; + char sa_data[UNIX_PATH_MAX]; +}; + +#ifdef _KERNEL_SOCKET_INTERNALS + +#define NSOCK 32 // sockets in the system +#define SOCKBUF 4096 // receive buffer per connected socket +#define NBACKLOG 8 + +enum sockstate { + SS_FREE = 0, + SS_UNBOUND, + SS_BOUND, + SS_LISTENING, + SS_CONNECTED, +}; + +// Every field here is protected by the socket table's single lock in +// kernel/net/unix.c -- there is deliberately no per-socket lock; see the +// comment at the top of that file for why. +struct socket { + int ref; + int type; + int state; + + char name[UNIX_PATH_MAX]; // the bound path, for a listener + + // A connected socket's peer. Writing to a socket appends to the *peer's* + // buffer, which is what makes one buffer per socket enough for a + // bidirectional connection. + struct socket *peer; + int peer_closed; + + char data[SOCKBUF]; + uint nread, nwrite; + + // Connections waiting to be accepted, on a listener. + struct socket *queue[NBACKLOG]; + int nqueue; +}; + +#endif diff --git a/include/spinlock.h b/include/spinlock.h new file mode 100644 index 0000000..1392bbc --- /dev/null +++ b/include/spinlock.h @@ -0,0 +1,9 @@ +// Mutual exclusion lock. +struct spinlock { + uint64 locked; // Is the lock held? + // For debugging: + char *name; // Name of lock. + struct cpu *cpu; // The cpu holding the lock. + addr_t pcs[10]; // The call stack (an array of program counters) + // that locked the lock. +}; diff --git a/include/stat.h b/include/stat.h new file mode 100644 index 0000000..9152d0c --- /dev/null +++ b/include/stat.h @@ -0,0 +1,24 @@ +#pragma once +#include "types.h" + +#define T_DIR 1 // Directory +#define T_FILE 2 // File +#define T_DEV 3 // Device +#define T_SYMLINK 4 // Divesymlink +#define T_EXTENT 5 // bmap & itrunc + +struct stat { + int32 type; // File type (T_DIR, T_FILE, or T_DEV) + int32 dev; // File system's disk device ID + uint64 ino; // Inode number + int32 nlink; // Number of hard links to file + uint32 pad; // Padding for 64-bit alignment + uint64 size; // Size of file in bytes + uint rwmode; // Permissions + uint supermode; // Mode + uint showmode; // Visiblity + uint atime; // Last access time + uint mtime; // Last modification time + uint ctime; // Last metadata-change time + uint addrs[12+3]; // Block Addresses +}; diff --git a/include/syscall.h b/include/syscall.h new file mode 100644 index 0000000..7d29ceb --- /dev/null +++ b/include/syscall.h @@ -0,0 +1,49 @@ +#pragma once + +// Syscall numbers and definition macros — all derived from +// kernel/sys/table.inc, which is the single source of truth. +// +// Do NOT add SYS_* constants by hand, and do NOT add anything to +// kernel/sys/linux_abi.c: add one SYSCALL() line to table.inc and write the +// body with SYSCALL_DEFINE(). The constant, the dispatch slot and the Linux +// ABI mapping are all generated from that one line. + +#define NSYSCALLS 512 + +// Scratch slot for syscalls with no Linux equivalent. Every such entry lands +// on this one index of the translation map, which is never dispatched (Linux +// x86_64 numbers currently top out around 450). Using a real index rather +// than a negative sentinel keeps the generated map a plain designated +// initialiser instead of needing a second macro. +#define NO_LINUX 511 + +#ifdef __ASSEMBLER__ +// In .S files GCC passes through the C preprocessor but the assembler +// sees the output, so emit .set directives instead of an enum. +#define SYSCALL(nr, lnr, name) .set SYS_##name, nr +#define SYSCALL_ALIAS(lnr, name) +#include "../kernel/sys/table.inc" +#undef SYSCALL +#undef SYSCALL_ALIAS +#else +// In C files generate a proper enum so constants work in switch/designators. +enum syscall_nr { + _SYS_RESERVED = 0, +#define SYSCALL(nr, lnr, name) SYS_##name = (nr), +#define SYSCALL_ALIAS(lnr, name) +#include "../kernel/sys/table.inc" +#undef SYSCALL +#undef SYSCALL_ALIAS +}; + +// Every syscall body is written as: +// +// SYSCALL_DEFINE(getpid) +// { +// return myproc()->pid; +// } +// +// so the signature lives in exactly one place and every implementation is +// findable with a single grep. +#define SYSCALL_DEFINE(name) addr_t sys_##name(void) +#endif diff --git a/include/text.h b/include/text.h new file mode 100644 index 0000000..c9f7c5b --- /dev/null +++ b/include/text.h @@ -0,0 +1,86 @@ +#pragma once + +// Text rendering. +// +// The console draws text by indexing `font_data[256][16]` and stepping the +// cursor eight pixels. That is a *character cell grid*, and it is the thing in +// the way -- not the font. A grid cannot do variable-width glyphs, cannot +// position text anywhere except on a multiple of eight, cannot anti-alias, and +// cannot represent a codepoint above 255. Swapping the bitmap for a nicer +// bitmap changes none of that. +// +// So this is a layer, not a font. Three things it fixes: +// +// *Glyphs have metrics.* A glyph knows its own size, its bearing and how far +// to advance afterwards. That is what variable-width text is; everything else +// follows from it. +// +// *It draws into a surface, not into the screen.* A compositor draws into an +// off-screen buffer and presents it. Rendering that writes straight to the +// VGA aperture is unusable to it, which is why this takes a target. +// +// *The font is behind an interface.* The bitmap font is one implementation. +// A TrueType rasteriser is another, and arrives without any caller changing: +// glyphs carry 8-bit coverage, so an anti-aliased outline renderer already +// has somewhere to put its output. + +struct font; + +// Somewhere to draw. Deliberately not a `struct device` or a framebuffer: the +// same code has to work on the virtio-gpu back buffer, on a window's own +// buffer, and on the VGA aperture, and none of those know about each other. +struct surface { + void *pixels; // 32-bit 0x00RRGGBB, the format everything here uses + uint32 stride; // bytes per row, not pixels -- they differ on real hardware + int width, height; +}; + +// One rendered glyph. `cov` is 8-bit coverage, not a bitmap: 0 is background, +// 255 is solid, and everything between is an anti-aliased edge. A bitmap font +// fills it with 0 and 255 only, and costs nothing for the generality. +struct glyph { + const unsigned char *cov; // w*h bytes, row-major + int w, h; // size of the coverage bitmap + int bearing_x, bearing_y; // offset from the pen to the top-left of `cov` + int advance; // how far the pen moves afterwards +}; + +struct font_ops { + // Fill `out` for `cp`. Returns 0, or -1 if the font has no such glyph -- + // callers substitute rather than failing, because a missing glyph must not + // stop a line of text. + int (*glyph)(struct font *, uint32 cp, struct glyph *out); + int (*height)(struct font *); // line height + int (*baseline)(struct font *); // pen offset from the top of the line +}; + +struct font { + const struct font_ops *ops; + void *priv; +}; + +// The built-in bitmap font, as a font provider. Always available, needs no +// allocation, and is what the console falls back to before any filesystem +// exists to load a real typeface from. +struct font *font_builtin(void); + +// Draw one UTF-8 string. Returns the x advance. +// +// `x, y` is the pen position on the *baseline*, which is how every text API +// that is not a terminal works -- glyphs of different heights then sit on a +// common line instead of being top-aligned. +int text_draw(struct surface *, struct font *, int x, int y, + const char *utf8, uint32 color); + +// Width the same string would occupy, without drawing it. Needed before you +// draw, for centring and for laying out anything at all. +int text_measure(struct font *, const char *utf8); + +// One glyph, for callers doing their own layout. +int text_draw_glyph(struct surface *, struct font *, int x, int y, + uint32 cp, uint32 color); + +// Decode one UTF-8 sequence. Returns the codepoint and advances *p. +// Malformed input yields U+FFFD and consumes one byte, so a decoder loop +// always terminates. +uint32 utf8_next(const char **p); diff --git a/include/theme.h b/include/theme.h new file mode 100644 index 0000000..5ea0c3f --- /dev/null +++ b/include/theme.h @@ -0,0 +1,57 @@ +#pragma once + +// Schunix visual identity — one palette, shared by everything that draws. +// +// This header exists because there were three unrelated designs and the seams +// showed. The bootloader splash was deep purple with gold text, deliberately +// matching the *panic* screen; the kernel console was cream with near-black +// text; the panic screen was the purple both were copying. So a normal boot +// looked like a crash, then abruptly became an amber terminal, and none of the +// three looked like the same product. +// +// The rule now: **normal states are calm and dark; only failure is loud.** +// Boot, console and shell share SCH_BG/SCH_FG. The panic screen keeps the +// full-bleed purple entirely to itself, which is what makes it mean something. +// +// Included by boot/stage2main.c (freestanding, pre-kernel), kernel/dev/vga.c, +// kernel/core/console.c and kernel/core/panic.c. Plain #defines only — no +// types, no code — because stage2 is compiled 32-bit against a different +// world than the kernel. +// +// Colours are 0xRRGGBB. vga_draw_pixel packs them for whatever depth the +// framebuffer actually is. + +// --- the product --------------------------------------------------------- +// Near-black with a slight blue cast rather than pure #000: pure black on an +// LCD reads as "the machine is off", and every serious platform sits a few +// points above it. +#define SCH_BG 0x0C0E13 +#define SCH_FG 0xE6E8EC // primary text +#define SCH_DIM 0x707784 // secondary text, hints, version strings +#define SCH_ACCENT 0x7C5CFF // the one accent: violet, keeping the + // project's purple lineage without the + // eye-searing chroma of the old splash +#define SCH_ACCENT_DIM 0x2A2440 // unused track behind a progress bar +#define SCH_OK 0x4ADE80 // reserved: success states +#define SCH_WARN 0xFBBF24 // reserved: warnings that are not panics + +// --- failure ------------------------------------------------------------- +// PSOGM. Deliberately unlike anything above, because its whole job is to be +// unmistakable. See kernel/core/panic.c. +// +// These are the values panic.c actually renders. It used to carry a second, +// different set of PSOGM_* defines of its own, so the two disagreed about what +// the crash screen looked like and this header was quietly fiction. There was +// also a PSOGM_TITLE in gold here, left over from a splash design that no +// longer exists -- the screen is one background and two weights of text, on +// purpose, because a crash screen with an accent colour is a crash screen +// someone was enjoying designing. +#define PSOGM_BG 0x4B1D9A // the purple +#define PSOGM_FG 0xFFFFFF // the face, the headline, the values +#define PSOGM_DIM 0xC3AEEC // labels and the closing lines + +// The one exception to "two weights of text": the QR card has to be a light +// panel with dark modules, because that is what a scanner needs. It reads as a +// label stuck onto the screen, which is roughly what it is. +#define PSOGM_CARD 0xFFFFFF +#define PSOGM_INK 0x000000 diff --git a/include/traps.h b/include/traps.h new file mode 100644 index 0000000..b8f2756 --- /dev/null +++ b/include/traps.h @@ -0,0 +1,69 @@ +#pragma once + +// x86 trap and interrupt constants. + +// Processor-defined exceptions: +#define T_DIVIDE 0 // divide error +#define T_DEBUG 1 // debug exception +#define T_NMI 2 // non-maskable interrupt +#define T_BRKPT 3 // breakpoint +#define T_OFLOW 4 // overflow +#define T_BOUND 5 // bounds check +#define T_ILLOP 6 // illegal opcode +#define T_DEVICE 7 // device not available +#define T_DBLFLT 8 // double fault +#define T_TSS 10 // invalid task switch segment +#define T_SEGNP 11 // segment not present +#define T_STACK 12 // stack exception +#define T_GPFLT 13 // general protection fault +#define T_PGFLT 14 // page fault +#define T_FPERR 16 // floating point error +#define T_ALIGN 17 // aligment check +#define T_MCHK 18 // machine check +#define T_SIMDERR 19 // SIMD floating point error + +// IRQ offsets +#define T_IRQ0 32 // IRQ 0 corresponds to int T_IRQ + +#define IRQ_TIMER 0 +#define IRQ_KBD 1 +#define IRQ_COM1 4 +#define IRQ_MOUSE 12 +#define IRQ_IDE 14 +#define IRQ_ERROR 19 +#define IRQ_SPURIOUS 31 + +// System call interrupt number for legacy int 0x40 or 0x80 if used +// although syscall/sysret is preferred for x64 +#define T_SYSCALL 0x40 + +#ifndef __ASSEMBLER__ +struct trapframe { + // Registers saved by trapasm.S + uint64 rax; + uint64 rbx; + uint64 rcx; + uint64 rdx; + uint64 rbp; + uint64 rsi; + uint64 rdi; + uint64 r8; + uint64 r9; + uint64 r10; + uint64 r11; + uint64 r12; + uint64 r13; + uint64 r14; + uint64 r15; + + uint64 trapno; + + // Error code and info saved by hardware + uint64 err; + uint64 rip; + uint64 cs; + uint64 rflags; + uint64 rsp; + uint64 ss; +}; +#endif diff --git a/include/ttf.h b/include/ttf.h new file mode 100644 index 0000000..d840e9c --- /dev/null +++ b/include/ttf.h @@ -0,0 +1,49 @@ +#pragma once + +// TrueType outlines, as a font provider for include/text.h. +// +// Scope, and why it is this and not "TTF and OTF": +// +// .ttf and .otf are both sfnt containers -- the same table directory, the same +// cmap, the same hmtx. What differs is how outlines are stored. TrueType puts +// quadratic B-spline contours in a `glyf` table; OpenType/PostScript puts cubic +// béziers in a `CFF ` table encoded as Type 2 charstrings, which is a +// stack-based interpreted format and a separate project. +// +// Checked before writing any of it: JetBrains Mono and Roboto -- the two fonts +// this system is going to use -- both ship `glyf` and neither ships `CFF `. So +// CFF support would buy nothing today, and the honest thing is to implement the +// format the fonts actually use and say so, rather than half-implement two. +// +// Everything here is from Apple's TrueType Reference Manual and the OpenType +// specification, both public documents. + +struct ttf { + const unsigned char *data; + uint32 len; + + uint32 glyf, glyf_len; + uint32 loca, loca_len; + uint32 cmap4; // offset of the format 4 subtable, 0 if none + uint32 hmtx; + int num_glyphs; + int num_hmetrics; + int units_per_em; + int long_loca; // indexToLocFormat: 0 = 16-bit offsets, 1 = 32 + int ascent, descent; + + // Rasterisation target size, in pixels per em. Set by ttf_open. + int px; +}; + +// Parse a font already in memory. Returns 0, or -1 if it is not a TrueType +// font this can read. `data` must outlive the struct -- nothing is copied. +int ttf_open(struct ttf *, const void *data, uint32 len, int pixels_per_em); + +// Rasterise one codepoint into `cov`. Returns 0, or -1 if the font has no such +// glyph or it will not fit. Exposed because the host harness uses it directly. +int ttf_render(struct ttf *, uint32 cp, struct glyph *out, unsigned char *cov, + int covcap, int *ox, int *oy); + +// A font provider over it, for text.h. The struct must outlive the font. +struct font *ttf_font(struct ttf *); diff --git a/include/types.h b/include/types.h new file mode 100644 index 0000000..5dec6bb --- /dev/null +++ b/include/types.h @@ -0,0 +1,79 @@ +#pragma once + +typedef unsigned int uint; +typedef unsigned short ushort; +typedef unsigned char uchar; + +typedef unsigned char uint8; +typedef unsigned short uint16; +typedef unsigned int uint32; + +typedef signed char int8; +typedef signed short int16; +typedef signed int int32; + +// uint64 has to be 64 bits everywhere this header is used, and `unsigned long` +// is not that everywhere. Linux and the x86_64-elf kernel target are LP64, so +// long is 64-bit; Windows is LLP64, where long is 32-bit and only long long is +// 64-bit. +// +// This matters far more than a portability nicety. The host tools -- mkfs, +// mkschfs, the SchFS suites -- include this header *and* the on-disk structure +// definitions in fs.h and schfs.h. Building them on Windows with a 32-bit +// uint64 does not fail: it quietly produces images whose inode numbers, sizes +// and block pointers are half the width the kernel reads back, which shows up +// as a corrupt filesystem long after the tool that wrote it has exited. +// +// Spelled as long/long long rather than 's uint64_t so the `%l` +// format specifiers used throughout the kernel stay correct and untouched. +// Conditioned on the actual width of `long` rather than on the platform, +// because there are three cases and only one of them is about Windows: +// x86_64-elf / Linux LP64 -> long is 64-bit +// Windows LLP64 -> long is 32-bit, long long is 64 +// i386-elf (boot/) ILP32 -> long is 32-bit, and stage2main.c includes +// this header too +#if defined(__SIZEOF_LONG__) && __SIZEOF_LONG__ == 8 +typedef unsigned long uint64; +typedef signed long int64; +#else +typedef unsigned long long uint64; +typedef signed long long int64; +#endif + +// Belt and braces: if the above is ever wrong on some new host, say so at +// compile time rather than in a filesystem image. +_Static_assert(sizeof(uint64) == 8, "uint64 must be 64 bits"); +_Static_assert(sizeof(int64) == 8, "int64 must be 64 bits"); + +typedef uint64 uintptr_t; +typedef int64 intptr_t; +typedef int64 ptrdiff_t; + +typedef uint64 addr_t; +typedef uint64 size_t; +typedef int64 ssize_t; + +#ifndef MKFS +#ifndef _SIGSET_T +#define _SIGSET_T +typedef uint64 sigset_t; +#endif +#endif + +typedef addr_t pte_t; +typedef addr_t pde_t; +typedef addr_t pdpe_t; +typedef addr_t pml4e_t; + +struct mouse_data { + int x; + int y; + uint8 buttons; +}; + +#ifndef NULL +#define NULL ((void*)0) +#endif + +#define TRUE 1 +#define FALSE 0 \ No newline at end of file diff --git a/include/uaccess.h b/include/uaccess.h new file mode 100644 index 0000000..eb172e2 --- /dev/null +++ b/include/uaccess.h @@ -0,0 +1,49 @@ +// User-memory access API, modeled on Linux's . +// +// Rules for kernel code touching user pointers: +// +// 1. Never dereference a user-supplied address without validating it +// first. A syscall argument can point anywhere, including at kernel +// memory or at nothing at all. +// +// 2. Never assume a user buffer is physically contiguous. uva2ka() only +// translates ONE page; the direct-map alias it returns runs into an +// unrelated physical page at the next 4K boundary. Any multi-byte +// access through it must stay within a single page. +// +// These helpers encapsulate both rules. Because Schunix (like old x86 +// without SMAP) keeps the user page table active in kernel mode, a raw +// user virtual address is directly dereferenceable once the whole range +// has been validated — that is what user_ptr() hands back, and why the +// result is safe across page boundaries where a uva2ka() pointer is not. +#ifndef SCHUNIX_UACCESS_H +#define SCHUNIX_UACCESS_H + +#include "types.h" + +// Nonzero if [uaddr, uaddr+size) is entirely mapped, user-accessible +// memory in the current process. size == 0 is always ok. +int access_ok(addr_t uaddr, uint64 size); + +// Validate the whole range and return it as a directly usable pointer +// (the raw user VA). 0 on bad/NULL address. +void *user_ptr(addr_t uaddr, uint64 size); + +// Copy between kernel and user memory, page by page. +// Return 0 on success, -EFAULT on any unmapped page. +int copy_from_user(void *dst, addr_t uaddr, uint64 n); +int copy_to_user(addr_t uaddr, const void *src, uint64 n); + +// Fixed-size scalar variants, in the spirit of Linux's get_user/put_user. +#define get_user(x, uaddr) copy_from_user(&(x), (addr_t)(uaddr), sizeof(x)) +#define put_user(x, uaddr) copy_to_user((addr_t)(uaddr), &(x), sizeof(x)) + +// Longest string fetchstr()/strlen_user() will scan before giving up. +#define USER_STR_MAX 32768 + +// Length of the NUL-terminated string at uaddr (excluding the NUL), +// validating every page it spans. -EFAULT if it runs off mapped memory +// or exceeds USER_STR_MAX. +long strlen_user(addr_t uaddr); + +#endif // SCHUNIX_UACCESS_H diff --git a/include/usb.h b/include/usb.h new file mode 100644 index 0000000..a01ac65 --- /dev/null +++ b/include/usb.h @@ -0,0 +1,270 @@ +#pragma once + +#include "device.h" + +// USB, the parts that are the same whatever host controller you have. +// +// Everything here comes from the USB 2.0 specification chapter 9, which every +// USB device implements regardless of speed, class or vendor. That is what +// makes enumeration possible at all: before you know anything about a device +// you can still ask it what it is, because the question is standardised. + +// bmRequestType +#define USB_DIR_OUT 0x00 +#define USB_DIR_IN 0x80 +#define USB_TYPE_STANDARD 0x00 +#define USB_TYPE_CLASS 0x20 +#define USB_RECIP_DEVICE 0x00 +#define USB_RECIP_INTERFACE 0x01 +#define USB_RECIP_ENDPOINT 0x02 +#define USB_RECIP_OTHER 0x03 // a hub's downstream port + +// bRequest, standard requests +#define USB_REQ_GET_STATUS 0 +#define USB_REQ_CLEAR_FEATURE 1 +#define USB_REQ_SET_FEATURE 3 +#define USB_REQ_SET_ADDRESS 5 +#define USB_REQ_GET_DESCRIPTOR 6 +#define USB_REQ_SET_DESCRIPTOR 7 +#define USB_REQ_GET_CONFIG 8 +#define USB_REQ_SET_CONFIG 9 +#define USB_REQ_GET_INTERFACE 10 +#define USB_REQ_SET_INTERFACE 11 + +// Descriptor types (the high byte of wValue in a GET_DESCRIPTOR) +#define USB_DT_DEVICE 1 +#define USB_DT_CONFIG 2 +#define USB_DT_STRING 3 +#define USB_DT_INTERFACE 4 +#define USB_DT_ENDPOINT 5 + +// Class codes we care about +#define USB_CLASS_HID 0x03 +#define USB_CLASS_MASS_STORAGE 0x08 +#define USB_CLASS_HUB 0x09 + +// HID subclass/protocol. Boot protocol is the one a BIOS uses, and the reason +// a USB keyboard works before any operating system has loaded: the report +// layout is fixed, so it can be read without parsing a report descriptor. +#define USB_HID_SUBCLASS_BOOT 0x01 +#define USB_HID_PROTO_KEYBOARD 0x01 +#define USB_HID_PROTO_MOUSE 0x02 + +// Bus speeds. +// +// These are xHCI's numbering, used as the common currency rather than +// translated at the boundary. That is a deliberate choice: there is one host +// controller, the values have to reach a slot context eventually, and a +// second enum whose only job is to be converted into this one would be +// ceremony. If a second controller ever appears, this is the thing to +// revisit -- but a class driver still has no business including xhci.h. +#define USB_SPEED_FULL 1 // 12 Mb/s +#define USB_SPEED_LOW 2 // 1.5 Mb/s +#define USB_SPEED_HIGH 3 // 480 Mb/s +#define USB_SPEED_SUPER 4 // 5 Gb/s + +// An eight-byte SETUP packet, the header of every control transfer. +struct usb_setup { + uint8 bmRequestType; + uint8 bRequest; + uint16 wValue; + uint16 wIndex; + uint16 wLength; +} __attribute__((packed)); + +struct usb_device_desc { + uint8 bLength; + uint8 bDescriptorType; + uint16 bcdUSB; + uint8 bDeviceClass; + uint8 bDeviceSubClass; + uint8 bDeviceProtocol; + uint8 bMaxPacketSize0; + uint16 idVendor; + uint16 idProduct; + uint16 bcdDevice; + uint8 iManufacturer; + uint8 iProduct; + uint8 iSerialNumber; + uint8 bNumConfigurations; +} __attribute__((packed)); + +struct usb_config_desc { + uint8 bLength; + uint8 bDescriptorType; + uint16 wTotalLength; + uint8 bNumInterfaces; + uint8 bConfigurationValue; + uint8 iConfiguration; + uint8 bmAttributes; + uint8 bMaxPower; +} __attribute__((packed)); + +struct usb_interface_desc { + uint8 bLength; + uint8 bDescriptorType; + uint8 bInterfaceNumber; + uint8 bAlternateSetting; + uint8 bNumEndpoints; + uint8 bInterfaceClass; + uint8 bInterfaceSubClass; + uint8 bInterfaceProtocol; + uint8 iInterface; +} __attribute__((packed)); + +struct usb_endpoint_desc { + uint8 bLength; + uint8 bDescriptorType; + uint8 bEndpointAddress; // bit 7: 1 = IN + uint8 bmAttributes; // bits 1:0: 0 ctrl, 1 iso, 2 bulk, 3 interrupt + uint16 wMaxPacketSize; + uint8 bInterval; +} __attribute__((packed)); + +#define USB_EP_XFERTYPE(a) ((a) & 0x03) +#define USB_EP_BULK 2 +#define USB_EP_INTERRUPT 3 +#define USB_EP_IS_IN(a) (((a) & 0x80) != 0) +#define USB_EP_NUM(a) ((a) & 0x0F) + +// Mass storage: SCSI transparent command set over bulk-only transport. +#define USB_MSC_SUBCLASS_SCSI 0x06 +#define USB_MSC_PROTO_BOT 0x50 + +// --------------------------------------------------------------------------- +// Hubs +// +// A hub is a USB device like any other, and everything it does is done by +// sending it ordinary control requests. There is no separate bus protocol: +// "reset port 3" is a SET_FEATURE addressed to the hub. +// --------------------------------------------------------------------------- + +#define USB_DT_HUB 0x29 + +// Port features, the wValue of SET_FEATURE / CLEAR_FEATURE on a port. +#define PORT_FEAT_ENABLE 1 +#define PORT_FEAT_RESET 4 +#define PORT_FEAT_POWER 8 +#define PORT_FEAT_C_CONNECTION 16 +#define PORT_FEAT_C_ENABLE 17 +#define PORT_FEAT_C_RESET 20 + +// wPortStatus, the first half of a port's GET_STATUS reply. +#define PORTSTAT_CONNECTION (1 << 0) +#define PORTSTAT_ENABLE (1 << 1) +#define PORTSTAT_RESET (1 << 4) +#define PORTSTAT_POWER (1 << 8) +#define PORTSTAT_LOWSPEED (1 << 9) +#define PORTSTAT_HIGHSPEED (1 << 10) + +struct usb_hub_desc { + uint8 bDescLength; + uint8 bDescriptorType; + uint8 bNbrPorts; + uint16 wHubCharacteristics; + uint8 bPwrOn2PwrGood; // in 2 ms units + uint8 bHubContrCurrent; +} __attribute__((packed)); + +// Bits 1:0 of wHubCharacteristics: 00 ganged power, 01 per-port. +#define HUB_CHAR_TT_MASK 0x0060 + +// HID class requests, on the interface rather than the device. +#define USB_HID_SET_IDLE 0x0A +#define USB_HID_SET_PROTOCOL 0x0B +#define USB_HID_PROTOCOL_BOOT 0 + +// --------------------------------------------------------------------------- +// The interface between the USB core and a host controller +// +// One host controller exists (xHCI) and one probably always will, but the +// core has no business knowing about TRBs and slot IDs, and the controller +// has no business knowing what a HID report is. This is where the line goes. +// --------------------------------------------------------------------------- + +struct usb_dev; + +struct usb_hc_ops { + // A control transfer on endpoint 0. `buf`, if any, must be DMA memory. + int (*control)(struct usb_dev *, struct usb_setup *, void *buf, int len); + // Make an endpoint usable. Returns 0, or -1 if the controller refused. + int (*open_ep)(struct usb_dev *, const struct usb_endpoint_desc *); + // Queue one IN transfer. Completion arrives via dev->complete. + int (*queue_in)(struct usb_dev *, int ep_addr, void *buf, int len); + // One bulk transfer, synchronously. Sleeps, so process context only. + // Returns bytes transferred, or -1. + int (*bulk)(struct usb_dev *, int ep_addr, void *buf, int len); + + // Bring up a device found on a downstream port of `hub`. The controller + // works out the route string from the hub's own position. + void (*attach)(struct usb_dev *hub, int hub_port, int speed); + + // Tell the controller this device is a hub with `nports` ports. It has to + // know: a hub is scheduled differently from anything else, and devices + // behind it are addressed relative to it. + int (*set_hub)(struct usb_dev *, int nports, int mtt); +}; + +#define USB_MAXCONF 256 // bytes of configuration descriptor we keep + +struct usb_dev { + const struct usb_hc_ops *ops; + void *hc; // the controller's own handle for this device + int slot; + int speed; + int port; // ROOT hub port this whole branch hangs off + + // Where the device sits in the tree. + // + // A USB device has no address of its own that describes its position -- the + // controller finds it by being told the path. `route` is xHCI's route + // string: five nibbles, one per tier, each holding the downstream port + // number taken at that tier. Tier 0 is a device on a root port, and its + // route is 0. + uint32 route; + int tier; + + // Transaction translator, for a full or low speed device behind a + // high-speed hub. The controller has to know which hub is doing the + // speed conversion on its behalf, because it is the hub, not the device, + // that it actually schedules against. + int tt_slot, tt_port; + + int in_use; + + struct usb_device_desc desc; + uchar conf[USB_MAXCONF]; // the configuration descriptor and everything + int conflen; // that follows it, exactly as the device sent + + // The first interface, which is all a boot-protocol or bulk-only device + // needs, and the endpoints belonging to it. + struct usb_interface_desc iface; + struct usb_endpoint_desc intr_in; + struct usb_endpoint_desc bulk_in; + struct usb_endpoint_desc bulk_out; + int has_intr_in, has_bulk_in, has_bulk_out; + + // Filled in by whichever class driver claimed the device. + const char *driver; + void *drvdata; + void (*complete)(struct usb_dev *, void *buf, int len); +}; + +// One row per class driver. Matching is on the *interface* descriptor, +// because bDeviceClass is very often 0 ("look at the interfaces"). +struct usb_driver { + const char *name; + uint8 class, subclass, protocol; // 0xFF matches anything + // What the rest of the kernel should consider this device to be, once this + // driver has claimed it. Distinct from `class` above, which is the USB + // interface class the descriptor advertises: the driver knows what it made + // of the device, the descriptor only says what the device claims to be. + enum dev_class devclass; + int (*probe)(struct usb_dev *); +}; + +// Called by a host controller once a device is addressed and its device +// descriptor has been read. The core reads the configuration, selects it, and +// offers the device to the class drivers. +void usb_attach(struct usb_dev *dev); + diff --git a/include/user.h b/include/user.h new file mode 100644 index 0000000..7983a2d --- /dev/null +++ b/include/user.h @@ -0,0 +1,117 @@ +#pragma once +#ifndef USER_H +#define USER_H + +#include "types.h" +#include "date.h" + +struct stat; +struct rtcdate; +struct mouse_data; + +// --- system calls --- +int fork(void); +void exit(void) __attribute__((noreturn)); +int wait(void); +int pipe(int*); +int write(int, void*, int); +int read(int, void*, int); +int close(int); +int kill(int); +int exec(char*, char**); +int open(char*, int); +int mknod(char*, short, short); +int unlink(char*); +int fstat(int fd, struct stat*); +int link(char*, char*); +int mkdir(char*); +int chdir(char*); +int dup(int); +int dup2(int, int); +int fcntl(int, int, int); +long lseek(int, long, int); +int getpid(void); +char* sbrk(uint64); +int sleep(int); +int uptime(void); +int vga_sync(void*); +int getmouse(struct mouse_data*); +int reboot(void); +int shutdown(void); +int sigfillset(sigset_t *set); +int sigprocmask(int how, const sigset_t *set, sigset_t *oldset); +int sigwait(const sigset_t *set, int *sig); +int alarm(int seconds); +int waitpid(int pid, int *status, int options); +int setsid(void); +void* mmap(void*, uint64, int, int, int, uint64); +// Shared memory. shmat returns the attach address, or a small negative value +// on failure — there is no errno in this runtime, so check for < 0. +int shmget(int key, uint64 size, int flags); +void* shmat(int id, void *addr, int flags); +int shmdt(void *addr); +int munmap(void*, uint64); +int delete(char*); +int show(char*); +int showstat(char*, struct stat*); +int ftruncate(int, int); +int truncate(char*, int); +int rename(const char*, const char*); +int symlink(char*, char*); +int readlink(char*, char*, int); +int getuid(void); +int geteuid(void); +int nanosleep(void*); +int mprotect(void*, uint64, int); +int ioctl(int, uint64, void*); +char* getcwd(char*, int); +int arch_prctl(int, uint64); +int set_tid_address(int*); +int set_robust_list(void*, int); +int futex(int*, int, int, void*, int*, int); +int clone(uint64, void*, void*, void*, void*); +void exit_group(int) __attribute__((noreturn)); +int wait4(int, int*, int, void*); +int getgid(void); +int getegid(void); +int umask(int); +int writev(int, void*, int); +int readv(int, void*, int); +void* brk(void*); +int prlimit(int, int, void*, void*); +int lstat(const char*, struct stat*); +int access(const char*, int); +int openat(int, const char*, int, ...); +int fchdir(int); +int enable_linux_abi(void); +int getrlimit(int, void*); +int setuid(int); +int setgid(int); +int getsid(int); +int dup3(int, int, int); +int pipe2(int*, int); +int clock_gettime(int, void*); +int getppid(void); +int rt_sigaction(int, void*, void*, uint64); +int mount(const char *src, const char *tgt, const char *fstype, uint64 flags, void *data); + +// Sockets. `struct sockaddr` and the AF_*/SOCK_* constants are in socket.h; +// include it as well as this header to use them. +struct sockaddr; +int socket(int domain, int type, int protocol); +int bind(int fd, const struct sockaddr *addr, int addrlen); +int listen(int fd, int backlog); +int accept(int fd, struct sockaddr *addr, int *addrlen); +int connect(int fd, const struct sockaddr *addr, int addrlen); +int socketpair(int domain, int type, int protocol, int sv[2]); +int sendto(int fd, const void *buf, int len, int flags, const struct sockaddr*, int); +int recvfrom(int fd, void *buf, int len, int flags, struct sockaddr*, int*); +int sockshutdown(int fd, int how); +int setsockopt(int fd, int level, int opt, const void *val, int len); +int getsockopt(int fd, int level, int opt, void *val, int *len); + +// --- ulib.c --- +int stat(const char*, struct stat*); +char* gets(char*, int max); + +#endif // USER_H \ No newline at end of file diff --git a/include/virtio.h b/include/virtio.h new file mode 100644 index 0000000..6719f75 --- /dev/null +++ b/include/virtio.h @@ -0,0 +1,174 @@ +#pragma once + +// virtio 1.0 over PCI — the transport, and virtio-gpu's 2D protocol. +// +// Written from the OASIS specification (virtio 1.1, chapters 2, 4.1 and 5.7), +// which is a public document with no licence on what you write from it. Same +// reasoning as kernel/drivers/xhci.c. +// +// Why virtio-gpu at all, when there is already a framebuffer: the VBE +// framebuffer is a flat block of memory the loader happened to be told about. +// It has no mode setting, no notion of a frame being complete, and no way to +// ask the host to present anything -- so a compositor drawing into it is +// racing the scanout with no way to synchronise. virtio-gpu has resources, +// explicit transfer and an explicit flush, which is the model every real GPU +// driver has. It is the smallest device that teaches the right shape. + +// --- PCI transport --------------------------------------------------------- + +#define VIRTIO_VENDOR 0x1AF4 +// Modern (1.0) device IDs are 0x1040 + the virtio device type. GPU is type 16. +#define VIRTIO_DEV_GPU 0x1050 + +// Vendor-specific PCI capability, which is how a virtio device says where in +// its BARs each configuration structure lives. +#define PCI_CAP_ID_VNDR 0x09 + +#define VIRTIO_PCI_CAP_COMMON_CFG 1 +#define VIRTIO_PCI_CAP_NOTIFY_CFG 2 +#define VIRTIO_PCI_CAP_ISR_CFG 3 +#define VIRTIO_PCI_CAP_DEVICE_CFG 4 + +// Offsets within the common configuration structure. Written out rather than +// declared as a struct: the fields are little-endian by specification and of +// mixed width, and a struct invites the compiler to pad or a caller to forget +// that these are MMIO and must be accessed at exactly the stated width. +#define VCOMMON_DFEAT_SEL 0x00 // le32 +#define VCOMMON_DFEAT 0x04 // le32 +#define VCOMMON_GFEAT_SEL 0x08 // le32 +#define VCOMMON_GFEAT 0x0C // le32 +#define VCOMMON_MSIX_CFG 0x10 // le16 +#define VCOMMON_NUM_QUEUES 0x12 // le16 +#define VCOMMON_STATUS 0x14 // u8 +#define VCOMMON_CFG_GEN 0x15 // u8 +#define VCOMMON_Q_SELECT 0x16 // le16 +#define VCOMMON_Q_SIZE 0x18 // le16 +#define VCOMMON_Q_MSIX 0x1A // le16 +#define VCOMMON_Q_ENABLE 0x1C // le16 +#define VCOMMON_Q_NOTIFY_OFF 0x1E // le16 +#define VCOMMON_Q_DESC 0x20 // le64 +#define VCOMMON_Q_DRIVER 0x28 // le64 +#define VCOMMON_Q_DEVICE 0x30 // le64 + +// device_status bits, set in this order during bring-up. +#define VSTATUS_ACK 1 +#define VSTATUS_DRIVER 2 +#define VSTATUS_DRIVER_OK 4 +#define VSTATUS_FEATURES_OK 8 +#define VSTATUS_FAILED 0x80 + +#define VIRTIO_F_VERSION_1 32 // feature bit: this is a 1.0 device + +// --- split virtqueue ------------------------------------------------------- + +#define VRING_DESC_F_NEXT 1 +#define VRING_DESC_F_WRITE 2 // device writes it; i.e. this is our buffer + +struct vring_desc { + uint64 addr; + uint32 len; + uint16 flags; + uint16 next; +} __attribute__((packed)); + +struct vring_avail { + uint16 flags; + uint16 idx; + uint16 ring[]; +} __attribute__((packed)); + +struct vring_used_elem { + uint32 id; + uint32 len; +} __attribute__((packed)); + +struct vring_used { + uint16 flags; + uint16 idx; + struct vring_used_elem ring[]; +} __attribute__((packed)); + +// --- virtio-gpu, 2D -------------------------------------------------------- + +#define VIRTIO_GPU_CMD_GET_DISPLAY_INFO 0x0100 +#define VIRTIO_GPU_CMD_RESOURCE_CREATE_2D 0x0101 +#define VIRTIO_GPU_CMD_RESOURCE_UNREF 0x0102 +#define VIRTIO_GPU_CMD_SET_SCANOUT 0x0103 +#define VIRTIO_GPU_CMD_RESOURCE_FLUSH 0x0104 +#define VIRTIO_GPU_CMD_TRANSFER_TO_HOST_2D 0x0105 +#define VIRTIO_GPU_CMD_RESOURCE_ATTACH_BACKING 0x0106 + +#define VIRTIO_GPU_RESP_OK_NODATA 0x1100 +#define VIRTIO_GPU_RESP_OK_DISPLAY_INFO 0x1101 + +#define VIRTIO_GPU_MAX_SCANOUTS 16 + +// B8G8R8X8, which is what a 32-bit little-endian 0x00RRGGBB word looks like in +// memory -- the same layout vga.c already draws into, so a framebuffer can be +// handed over without a conversion pass. +#define VIRTIO_GPU_FORMAT_B8G8R8X8_UNORM 2 + +struct virtio_gpu_ctrl_hdr { + uint32 type; + uint32 flags; + uint64 fence_id; + uint32 ctx_id; + uint32 padding; +} __attribute__((packed)); + +struct virtio_gpu_rect { + uint32 x, y, width, height; +} __attribute__((packed)); + +struct virtio_gpu_display_one { + struct virtio_gpu_rect r; + uint32 enabled; + uint32 flags; +} __attribute__((packed)); + +struct virtio_gpu_resp_display_info { + struct virtio_gpu_ctrl_hdr hdr; + struct virtio_gpu_display_one pmodes[VIRTIO_GPU_MAX_SCANOUTS]; +} __attribute__((packed)); + +struct virtio_gpu_resource_create_2d { + struct virtio_gpu_ctrl_hdr hdr; + uint32 resource_id; + uint32 format; + uint32 width; + uint32 height; +} __attribute__((packed)); + +struct virtio_gpu_mem_entry { + uint64 addr; + uint32 length; + uint32 padding; +} __attribute__((packed)); + +struct virtio_gpu_resource_attach_backing { + struct virtio_gpu_ctrl_hdr hdr; + uint32 resource_id; + uint32 nr_entries; +} __attribute__((packed)); + +struct virtio_gpu_set_scanout { + struct virtio_gpu_ctrl_hdr hdr; + struct virtio_gpu_rect r; + uint32 scanout_id; + uint32 resource_id; +} __attribute__((packed)); + +struct virtio_gpu_transfer_to_host_2d { + struct virtio_gpu_ctrl_hdr hdr; + struct virtio_gpu_rect r; + uint64 offset; + uint32 resource_id; + uint32 padding; +} __attribute__((packed)); + +struct virtio_gpu_resource_flush { + struct virtio_gpu_ctrl_hdr hdr; + struct virtio_gpu_rect r; + uint32 resource_id; + uint32 padding; +} __attribute__((packed)); diff --git a/include/x86.h b/include/x86.h new file mode 100644 index 0000000..fb17e91 --- /dev/null +++ b/include/x86.h @@ -0,0 +1,219 @@ +#pragma once + +struct dtr { + ushort limit; + uint64 base; +} __attribute__((packed)); + +static inline uchar +inb(ushort port) +{ + uchar data; + asm volatile("in %1,%0" : "=a" (data) : "d" (port)); + return data; +} + +static inline void +insl(int port, void *addr, int cnt) +{ + asm volatile("cld; rep insl" : + "=D" (addr), "=c" (cnt) : + "d" (port), "0" (addr), "1" (cnt) : + "memory", "cc"); +} + +static inline void +outb(ushort port, uchar data) +{ + asm volatile("out %0,%1" : : "a" (data), "d" (port)); +} + +static inline uint +inl(ushort port) +{ + uint data; + asm volatile("inl %1,%0" : "=a" (data) : "d" (port)); + return data; +} + +static inline void +outw(ushort port, ushort data) +{ + asm volatile("out %0,%1" : : "a" (data), "d" (port)); +} + +// PCI configuration space is reached through a pair of 32-bit ports, so this +// is not optional decoration -- see kernel/dev/pci.c. +static inline void +outl(ushort port, uint data) +{ + asm volatile("outl %0,%1" : : "a" (data), "d" (port)); +} + +static inline void +outsl(int port, const void *addr, int cnt) +{ + asm volatile("cld; rep outsl" : + "=S" (addr), "=c" (cnt) : + "d" (port), "0" (addr), "1" (cnt) : + "cc"); +} + +static inline void +stosb(void *addr, int data, int cnt) +{ + asm volatile("cld; rep stosb" : + "=D" (addr), "=c" (cnt) : + "0" (addr), "1" (cnt), "a" (data) : + "memory", "cc"); +} + +// 64-bit-only helpers. `rep stosq` and `xchgq` do not exist in 32-bit mode, so +// these must not even be parsed by the -m32 bootloader build -- they compiled +// there before only because uint64 was accidentally 32 bits wide, which is the +// bug include/types.h now asserts against. +#ifdef __x86_64__ +static inline void +stosq(void *addr, uint64 data, int cnt) +{ + asm volatile("cld; rep stosq" : + "=D" (addr), "=c" (cnt) : + "0" (addr), "1" (cnt), "a" (data) : + "memory", "cc"); +} + +struct segdesc; + +static inline void +lgdt(struct segdesc *p, int size) +{ + struct dtr d; + d.limit = size - 1; + d.base = (uint64)p; + asm volatile("lgdt (%0)" : : "r" (&d)); +} + +struct gatedesc; + +static inline void +lidt(struct gatedesc *p, int size) +{ + struct dtr d; + d.limit = size - 1; + d.base = (uint64)p; + asm volatile("lidt (%0)" : : "r" (&d)); +} + +static inline void +ltr(ushort sel) +{ + asm volatile("ltr %0" : : "r" (sel)); +} + +static inline uint64 +readeflags(void) +{ + uint64 flags; + asm volatile("pushfq; pop %0" : "=r" (flags)); + return flags; +} + +static inline void +cli(void) +{ + asm volatile("cli"); +} + +static inline void +sti(void) +{ + asm volatile("sti"); +} + +static inline void +hlt(void) +{ + asm volatile("hlt"); +} + +static inline uint64 +xchg(volatile uint64 *addr, uint64 newval) +{ + uint64 result; + asm volatile("lock; xchgq %0, %1" : + "+m" (*addr), "=a" (result) : + "1" (newval) : + "cc"); + return result; +} +#endif /* __x86_64__ */ + +static inline uint64 +rcr2(void) +{ + uint64 val; + asm volatile("mov %%cr2,%0" : "=r" (val)); + return val; +} + +static inline void +lcr3(uint64 val) +{ + asm volatile("mov %0,%%cr3" : : "r" (val)); +} + +// Invalidate the TLB entry for one page. Needed any time a PTE is changed +// or cleared for an address space that may still be the active CR3 — +// otherwise the CPU can keep translating through the stale entry until the +// next full CR3 reload (context switch), silently reading/writing the +// wrong physical page. +static inline void +invlpg(void *va) +{ + asm volatile("invlpg (%0)" : : "r" (va) : "memory"); +} + +static inline void +wrmsr(uint32 msr, uint64 val) +{ + uint32 low = (uint32)val; + uint32 high = (uint32)(val >> 32); + asm volatile("wrmsr" : : "c" (msr), "a" (low), "d" (high)); +} + +static inline uint64 +rdmsr(uint32 msr) +{ + uint32 low, high; + asm volatile("rdmsr" : "=a" (low), "=d" (high) : "c" (msr)); + return ((uint64)high << 32) | low; +} + +static inline uint64 +readcs(void) +{ + uint64 val; + asm volatile("mov %%cs, %0" : "=r" (val)); + return val; +} + +// CPUID. `leaf` in EAX, results in EAX/EBX/ECX/EDX. +// +// %rbx is callee-saved *and* the PIC base register on some builds, so it is +// exchanged out rather than named as an operand -- naming it directly is the +// classic way to get "inline asm clobbers reserved register" or, worse, silent +// corruption in the caller. +static inline void +cpuid_leaf(uint32 leaf, uint32 *a, uint32 *b, uint32 *c, uint32 *d) +{ + uint32 ra, rb, rc, rd; + asm volatile("xchgq %%rbx, %q1\n\t" + "cpuid\n\t" + "xchgq %%rbx, %q1" + : "=a"(ra), "=&r"(rb), "=c"(rc), "=d"(rd) + : "0"(leaf), "2"(0)); + if (a) *a = ra; + if (b) *b = rb; + if (c) *c = rc; + if (d) *d = rd; +} diff --git a/include/xhci.h b/include/xhci.h new file mode 100644 index 0000000..d0c5760 --- /dev/null +++ b/include/xhci.h @@ -0,0 +1,161 @@ +#pragma once + +// eXtensible Host Controller Interface -- the register and data-structure +// definitions from the xHCI 1.2 specification. +// +// Why xHCI and not UHCI/OHCI/EHCI: xHCI is the only host controller interface +// that speaks every USB speed, so one driver covers USB 1.1 through 3.x. The +// older controllers would each need their own, and a machine with all of them +// needs the companion-controller handover dance on top. There is no hardware +// worth supporting that has no xHCI. + +// --------------------------------------------------------------------------- +// Capability registers -- at the start of BAR0, read-only +// --------------------------------------------------------------------------- +#define XHCI_CAPLENGTH 0x00 // byte: where the operational regs begin +#define XHCI_HCIVERSION 0x02 // word +#define XHCI_HCSPARAMS1 0x04 +#define XHCI_HCSPARAMS2 0x08 +#define XHCI_HCSPARAMS3 0x0C +#define XHCI_HCCPARAMS1 0x10 +#define XHCI_DBOFF 0x14 // doorbell array offset from BAR0 +#define XHCI_RTSOFF 0x18 // runtime register offset from BAR0 + +#define HCS1_MAXSLOTS(x) ((x) & 0xFF) +#define HCS1_MAXINTRS(x) (((x) >> 8) & 0x7FF) +#define HCS1_MAXPORTS(x) (((x) >> 24) & 0xFF) + +// Scratchpad buffer count is split across two fields, high part first. +#define HCS2_SPB_HI(x) (((x) >> 21) & 0x1F) +#define HCS2_SPB_LO(x) (((x) >> 27) & 0x1F) + +#define HCC1_AC64 (1 << 0) // 64-bit addressing +#define HCC1_CSZ (1 << 2) // contexts are 64 bytes, not 32 +#define HCC1_XECP(x) (((x) >> 16) & 0xFFFF) // extended caps, in dwords + +// --------------------------------------------------------------------------- +// Operational registers -- at BAR0 + CAPLENGTH +// --------------------------------------------------------------------------- +#define XHCI_USBCMD 0x00 +#define XHCI_USBSTS 0x04 +#define XHCI_PAGESIZE 0x08 +#define XHCI_DNCTRL 0x14 +#define XHCI_CRCR 0x18 // 64-bit +#define XHCI_DCBAAP 0x30 // 64-bit +#define XHCI_CONFIG 0x38 +#define XHCI_PORTSC(p) (0x400 + 0x10 * ((p) - 1)) // ports are 1-based + +#define CMD_RS (1 << 0) // run/stop +#define CMD_HCRST (1 << 1) // host controller reset +#define CMD_INTE (1 << 2) // interrupter enable +#define CMD_HSEE (1 << 3) + +#define STS_HCH (1 << 0) // halted +#define STS_HSE (1 << 2) // host system error +#define STS_EINT (1 << 3) // event interrupt (write 1 to clear) +#define STS_PCD (1 << 4) // port change detect (write 1 to clear) +#define STS_CNR (1 << 11) // controller not ready + +#define CRCR_RCS (1 << 0) // ring cycle state + +// PORTSC. The change bits are write-1-to-clear, and so is PED -- writing back +// a value read from this register clears whatever happened to be set. Every +// write has to mask them off first. +#define PORT_CCS (1 << 0) // current connect status +#define PORT_PED (1 << 1) // port enabled (RW1C!) +#define PORT_OCA (1 << 3) +#define PORT_PR (1 << 4) // port reset +#define PORT_PLS(x) (((x) >> 5) & 0xF) +#define PORT_PP (1 << 9) // port power +#define PORT_SPEED(x) (((x) >> 10) & 0xF) +#define PORT_CSC (1 << 17) +#define PORT_PEC (1 << 18) +#define PORT_PRC (1 << 21) +#define PORT_RW1C 0x00FE0002u // change bits 17..23, plus PED + +// PORTSC speed encoding. These are the same numbers as USB_SPEED_* in usb.h, +// which is not a coincidence -- the core uses this encoding directly so that +// nothing has to be translated on its way to a slot context. +#define XSPEED_FULL USB_SPEED_FULL +#define XSPEED_LOW USB_SPEED_LOW +#define XSPEED_HIGH USB_SPEED_HIGH +#define XSPEED_SUPER USB_SPEED_SUPER + +// --------------------------------------------------------------------------- +// Runtime registers -- at BAR0 + RTSOFF. Interrupter n is at 0x20 + 32*n. +// --------------------------------------------------------------------------- +#define XHCI_MFINDEX 0x00 +#define XHCI_IR0 0x20 +#define IR_IMAN 0x00 +#define IR_IMOD 0x04 +#define IR_ERSTSZ 0x08 +#define IR_ERSTBA 0x10 // 64-bit +#define IR_ERDP 0x18 // 64-bit + +#define IMAN_IP (1 << 0) // interrupt pending (RW1C) +#define IMAN_IE (1 << 1) // interrupt enable +#define ERDP_EHB (1 << 3) // event handler busy (RW1C) + +// --------------------------------------------------------------------------- +// Transfer Request Blocks +// --------------------------------------------------------------------------- +struct trb { + uint64 param; + uint32 status; + uint32 control; +} __attribute__((packed)); + +#define TRB_TYPE(t) (((t) & 0x3F) << 10) +#define TRB_GET_TYPE(c) (((c) >> 10) & 0x3F) +#define TRB_C (1 << 0) // cycle bit +#define TRB_TC (1 << 1) // toggle cycle (link TRBs) +#define TRB_ISP (1 << 2) +#define TRB_CH (1 << 4) // chain +#define TRB_IOC (1 << 5) // interrupt on completion +#define TRB_IDT (1 << 6) // immediate data +#define TRB_SLOT(s) ((uint32)(s) << 24) +#define TRB_GET_SLOT(c) (((c) >> 24) & 0xFF) +#define TRB_GET_EPID(c) (((c) >> 16) & 0x1F) // transfer events + +// Transfer TRBs +#define TRB_NORMAL 1 +#define TRB_SETUP 2 +#define TRB_DATA 3 +#define TRB_STATUS 4 +#define TRB_LINK 6 +// Command TRBs +#define TRB_ENABLE_SLOT 9 +#define TRB_DISABLE_SLOT 10 +#define TRB_ADDRESS_DEV 11 +#define TRB_CONFIG_EP 12 +#define TRB_EVAL_CTX 13 +#define TRB_RESET_EP 14 +#define TRB_NOOP_CMD 23 +// Event TRBs +#define TRB_XFER_EVENT 32 +#define TRB_CMD_COMPLETE 33 +#define TRB_PORT_STATUS 34 + +#define TRB_CC(status) (((status) >> 24) & 0xFF) +#define CC_SUCCESS 1 +#define CC_SHORT_PACKET 13 + +// Setup Stage transfer type, and Data Stage direction. +#define TRT_NONE 0 +#define TRT_OUT 2 +#define TRT_IN 3 +#define DIR_IN (1 << 16) + +// Endpoint types in an endpoint context. Direction is part of the type, and +// the numbering is not arbitrary: OUT types are 1-3, control is 4, and the IN +// types are the OUT ones plus four. +#define EPTYPE_ISOCH_OUT 1 +#define EPTYPE_BULK_OUT 2 +#define EPTYPE_INT_OUT 3 +#define EPTYPE_CONTROL 4 +#define EPTYPE_ISOCH_IN 5 +#define EPTYPE_BULK_IN 6 +#define EPTYPE_INT_IN 7 + +// Doorbell targets: 0 is the command ring; for a device slot, DCI 1 is EP0. +#define DCI_EP0 1 diff --git a/kernel/arch/x86_64/acpi.c b/kernel/arch/x86_64/acpi.c new file mode 100644 index 0000000..7d42537 --- /dev/null +++ b/kernel/arch/x86_64/acpi.c @@ -0,0 +1,264 @@ +// Find the processors, the way firmware from this century describes them. +// +// mpinit() walks the Intel MP table, which is from 1994. Modern firmware fills +// it in minimally or omits it: measured 2026-08-08, QEMU/SeaBIOS with -smp 4 +// publishes a 208-byte MP table containing exactly *one* processor entry, then +// buses and interrupt assignments. Everything since has expected the operating +// system to read the ACPI MADT instead. +// +// The consequence was not that SMP was slow, it was that ncpu was 1 and SMP was +// silently never exercised. Machines whose firmware does publish a full MP +// table -- VMware, older hardware -- took the other path, started their APs, +// and triple faulted. So this file is a prerequisite for debugging that: until +// the CPUs are visible under QEMU there is no way to reproduce the crash except +// on the hardware it happens on. +// +// Written from the ACPI specification. No table layout here is copied from +// another kernel; they are all in chapter 5 of the published standard. + +#include "types.h" +#include "defs.h" +#include "param.h" +#include "memlayout.h" +#include "mmu.h" +#include "proc.h" +#include "x86.h" + +extern struct cpu cpus[NCPU]; +extern int ncpu; +extern uchar ioapicid; +extern volatile uint *lapic; + +// The Root System Description Pointer. Found by signature, not by being told +// where it is -- it sits either in the Extended BIOS Data Area or in the BIOS +// read-only region, on a 16-byte boundary. +struct acpi_rsdp { + char sig[8]; // "RSD PTR " + uint8 checksum; // over the first 20 bytes + char oemid[6]; + uint8 revision; // 0 = ACPI 1.0 (RSDT), 2+ = 2.0 (XSDT) + uint32 rsdt_addr; + // ACPI 2.0 and later only, and only valid when revision >= 2. + uint32 length; + uint64 xsdt_addr; + uint8 ext_checksum; // over `length` bytes + uint8 reserved[3]; +} __attribute__((packed)); + +// Every ACPI table starts with this. +struct acpi_sdt { + char sig[4]; + uint32 length; + uint8 revision; + uint8 checksum; + char oemid[6]; + char oem_table_id[8]; + uint32 oem_revision; + uint32 creator_id; + uint32 creator_revision; +} __attribute__((packed)); + +// The Multiple APIC Description Table: signature "APIC". +struct acpi_madt { + struct acpi_sdt h; + uint32 lapic_addr; + uint32 flags; // bit 0: legacy 8259 PICs are present +} __attribute__((packed)); + +#define MADT_PCAT_COMPAT 1 + +// MADT entry types. Only these three are read; the rest are interrupt +// routing overrides that ioapic.c does not consult yet. +#define MADT_LAPIC 0 +#define MADT_IOAPIC 1 +#define MADT_LAPIC_OVERRIDE 5 + +struct madt_entry { + uint8 type; + uint8 length; +} __attribute__((packed)); + +struct madt_lapic { + struct madt_entry e; + uint8 acpi_id; + uint8 apic_id; + uint32 flags; // bit 0: enabled, bit 1: online-capable +} __attribute__((packed)); + +#define LAPIC_ENABLED 1 +#define LAPIC_ONLINE_CAPABLE 2 + +struct madt_ioapic { + struct madt_entry e; + uint8 id; + uint8 reserved; + uint32 addr; + uint32 gsi_base; +} __attribute__((packed)); + +struct madt_lapic_override { + struct madt_entry e; + uint16 reserved; + uint64 addr; +} __attribute__((packed)); + +static int +checksum_ok(const void *p, uint32 len) +{ + const uint8 *b = p; + uint8 sum = 0; + for (uint32 i = 0; i < len; i++) + sum += b[i]; + return sum == 0; +} + +// Scan a physical range for the RSDP signature. It is 16-byte aligned by +// specification, which makes this cheap enough to do twice. +static struct acpi_rsdp * +scan_rsdp(uint64 base, uint64 len) +{ + for (uint64 off = 0; off + sizeof(struct acpi_rsdp) <= len; off += 16) { + struct acpi_rsdp *r = (struct acpi_rsdp *)P2V(base + off); + if (memcmp(r->sig, "RSD PTR ", 8) != 0) + continue; + // Signature alone is not enough: the string can appear in unrelated data, + // and following a bad pointer here means faulting before there is anything + // to report the fault with. + if (!checksum_ok(r, 20)) + continue; + return r; + } + return 0; +} + +static struct acpi_rsdp * +find_rsdp(void) +{ + // The EBDA, whose segment address the BIOS leaves at 0x40E. + uint16 ebda_seg = *(uint16 *)P2V(0x40E); + if (ebda_seg) { + struct acpi_rsdp *r = scan_rsdp((uint64)ebda_seg << 4, 1024); + if (r) + return r; + } + return scan_rsdp(0xE0000, 0x20000); +} + +// Walk the RSDT or XSDT looking for a table by signature. +// +// Two nearly identical loops rather than one clever one: the only difference is +// the pointer width -- 32-bit entries in the RSDT, 64-bit in the XSDT -- and +// reading a 64-bit pointer out of a 32-bit array is exactly the sort of bug +// that produces a plausible address pointing at nothing. +static struct acpi_sdt * +find_table(struct acpi_rsdp *rsdp, const char *sig) +{ + struct acpi_sdt *root; + int wide; + + if (rsdp->revision >= 2 && rsdp->xsdt_addr) { + root = (struct acpi_sdt *)P2V(rsdp->xsdt_addr); + wide = 1; + } else { + root = (struct acpi_sdt *)P2V((uint64)rsdp->rsdt_addr); + wide = 0; + } + + if (!checksum_ok(root, root->length)) + return 0; + + uint32 n = (root->length - sizeof(struct acpi_sdt)) / (wide ? 8 : 4); + for (uint32 i = 0; i < n; i++) { + uint64 pa; + if (wide) { + // Byte-wise, because the array is packed and 8-byte entries in it are + // not guaranteed to be 8-byte aligned. + uint8 *p = (uint8 *)(root + 1) + i * 8; + pa = 0; + for (int b = 7; b >= 0; b--) + pa = (pa << 8) | p[b]; + } else { + pa = ((uint32 *)(root + 1))[i]; + } + struct acpi_sdt *t = (struct acpi_sdt *)P2V(pa); + if (memcmp(t->sig, sig, 4) == 0) + return t; + } + return 0; +} + +// Returns 1 if the processors were enumerated, 0 to fall back to mpinit(). +int +acpi_init(void) +{ + struct acpi_rsdp *rsdp = find_rsdp(); + if (!rsdp) + return 0; + + struct acpi_madt *madt = (struct acpi_madt *)find_table(rsdp, "APIC"); + if (!madt || !checksum_ok(madt, madt->h.length)) + return 0; + + lapic = (uint *)P2V((uint64)madt->lapic_addr); + + uint8 *p = (uint8 *)madt + sizeof(struct acpi_madt); + uint8 *e = (uint8 *)madt + madt->h.length; + int found_ioapic = 0; + + while (p + sizeof(struct madt_entry) <= e) { + struct madt_entry *ent = (struct madt_entry *)p; + // A zero length would spin here forever. Firmware tables are not always + // well formed, and an infinite loop this early is indistinguishable from a + // hang with no output at all. + if (ent->length < sizeof(struct madt_entry)) + break; + + switch (ent->type) { + case MADT_LAPIC: { + struct madt_lapic *l = (struct madt_lapic *)p; + // Skip processors that are neither enabled nor online-capable: those are + // sockets the firmware is describing but not offering. + if (!(l->flags & (LAPIC_ENABLED | LAPIC_ONLINE_CAPABLE))) + break; + if (ncpu < NCPU) { + cpus[ncpu].apicid = l->apic_id; + ncpu++; + } + break; + } + case MADT_IOAPIC: { + struct madt_ioapic *io = (struct madt_ioapic *)p; + // The first one only. Multi-I/O-APIC routing needs the interrupt source + // overrides too, which nothing here reads yet, so claiming to support it + // would be worse than not. + if (!found_ioapic) { + ioapicid = io->id; + found_ioapic = 1; + } + break; + } + case MADT_LAPIC_OVERRIDE: { + struct madt_lapic_override *o = (struct madt_lapic_override *)p; + lapic = (uint *)P2V(o->addr); + break; + } + } + p += ent->length; + } + + if (ncpu == 0) + return 0; + + // The 8259s are still there and still wired to the CPU. On a system with an + // IMCR, this switches interrupt delivery from the PICs to the APIC; on one + // without, the ports are unclaimed and the writes go nowhere. mpinit() does + // the same thing guarded by the MP table's imcrp flag. + if (madt->flags & MADT_PCAT_COMPAT) { + outb(0x22, 0x70); + outb(0x23, inb(0x23) | 1); + } + + cprintf("acpi: %d cpu%s, lapic at %p, ioapic id %d\n", + ncpu, ncpu == 1 ? "" : "s", lapic, ioapicid); + return 1; +} diff --git a/kernel/arch/x86_64/e820.c b/kernel/arch/x86_64/e820.c new file mode 100644 index 0000000..bc977b1 --- /dev/null +++ b/kernel/arch/x86_64/e820.c @@ -0,0 +1,105 @@ +// Find out how much memory this machine actually has. +// +// PHYSTOP used to be a constant: 0xE000000, 224 MB, inherited from xv6 and +// never revisited. That is not a limit on how much memory the kernel *uses* -- +// it is an assertion that exactly that much exists. Boot a machine with less +// and kinit1() walks off the end of RAM into nothing; boot one with more and +// every byte above 224 MB is invisible. Measured before this file existed: +// Schunix booted at 224 MB and triple faulted at 192 MB, for no reason at all. +// +// The firmware has known the answer the whole time. boot/stage2.S asks it with +// INT 0x15 / E820 in real mode -- the same interface Linux's boot stub and GRUB +// use -- and leaves the result in low memory. stage2main.c already parses it to +// print detected RAM on the splash. The kernel simply never read it. + +#include "types.h" +#include "defs.h" +#include "memlayout.h" +#include "mmu.h" + +// Set before kinit1() and never again. Starts at the old constant so that a +// crash between entry and e820_init() behaves the way it always did rather +// than in some new way. +uint64 phystop = PHYSTOP_FALLBACK; + +// Where stage2 left the map. Must match E820_COUNT_ADDR / E820_MAP_ADDR in +// boot/stage2main.c, which are themselves relative to VBE_INFO_ADDR. +#define E820_COUNT_ADDR (0x30000 + 0x200) +#define E820_MAP_ADDR (0x30000 + 0x208) + +#define E820_USABLE 1 +#define E820_MAXENTRIES 32 + +struct e820entry { + uint64 base; + uint64 length; + uint32 type; + uint32 acpi_ext; +} __attribute__((packed)); + +void +e820_init(void) +{ + uint32 count = *(uint32 *)P2V(E820_COUNT_ADDR); + struct e820entry *e = (struct e820entry *)P2V(E820_MAP_ADDR); + + // A count of zero means the firmware declined; anything absurd means we are + // reading something that is not a memory map. Either way the constant is the + // only honest answer left. + if (count == 0 || count > E820_MAXENTRIES) { + cprintf("e820: no usable map (count %d), assuming %d MB\n", + count, (int)(PHYSTOP_FALLBACK / (1024 * 1024))); + return; + } + + // The end of the usable region that *contains the kernel*, not the highest + // usable address anywhere. + // + // This distinction matters and is easy to get wrong. A memory map is not one + // run of RAM: there are reserved holes in it -- ACPI tables, firmware + // scratch, the legacy hole below 1 MB -- and kalloc's freerange() walks + // straight from the end of the kernel image to phystop handing out every page + // between. Taking the global maximum would step over a reserved region and + // put firmware tables on the free list, which fails later, elsewhere, and + // looks like anything but a memory map problem. + uint64 top = 0; + for (uint32 i = 0; i < count; i++) { + if (e[i].type != E820_USABLE) + continue; + uint64 base = e[i].base, end = base + e[i].length; + if (base <= EXTMEM && EXTMEM < end) { + top = end; + break; + } + } + + if (top == 0) { + cprintf("e820: no usable region covers 0x%x, assuming %d MB\n", + EXTMEM, (int)(PHYSTOP_FALLBACK / (1024 * 1024))); + return; + } + + // kvmalloc() maps physical 0-4 GB with four 1 GB pages, and marks the top one + // uncached because that is where PCI BARs and the framebuffer live. So 3 GB + // is the ceiling for anything the kernel will treat as RAM: above it the + // direct map either does not exist or is device memory that would be + // catastrophically slow to allocate from and is not ours to hand out. + if (top > PHYSTOP_MAX) + top = PHYSTOP_MAX; + + top &= ~(uint64)(PGSIZE - 1); + + // Below this the kernel image, its page tables and the DMA arena do not fit, + // and continuing produces a failure much harder to read than this line. + if (top < PHYSTOP_MIN) { + cprintf("e820: only %d MB usable; Schunix needs at least %d MB\n", + (int)(top / (1024 * 1024)), (int)(PHYSTOP_MIN / (1024 * 1024))); + // Left at the fallback deliberately rather than lowered: a machine this + // small is going to fail, and it should fail having said why. + return; + } + + phystop = top; + cprintf("e820: %d MB usable (%d entries)\n", + (int)(phystop / (1024 * 1024)), count); +} diff --git a/kernel/arch/x86_64/entry.S b/kernel/arch/x86_64/entry.S new file mode 100644 index 0000000..5ead585 --- /dev/null +++ b/kernel/arch/x86_64/entry.S @@ -0,0 +1,174 @@ +/* entry.S + * + * Copyright (c) 2013 Brian Swetland + * Modified by Nexoniarz + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#define mboot_magic 0x1badb002 +#define mboot_flags 0x00010000 +#include "mmu.h" + +.code64 +.global mboot_header +.global mboot_entry + +mboot_header: + .long mboot_magic + .long mboot_flags + .long (-mboot_magic -mboot_flags) + .long mboot_load_addr + .long mboot_load_addr + .long mboot_load_end + .long mboot_bss_end + .long mboot_entry_addr + +.code32 +mboot_entry: + mov %eax, %ebp + + # bootstrap page tables (0x1000 - 0x3000) + xor %eax, %eax + mov $0x1000, %edi + mov $0x2000, %ecx + rep stosb + + # Bootstrap page tables (Identity mapping 4GB) + # PML4T[0] i PML4T[256] -> PDPT pod 0x2000 + mov $(0x2000 | PTE_P | PTE_W), %edx + mov %edx, 0x1000 + mov %edx, 0x1800 + + # PDPT mapping 4GB using 1GB huge pages + mov $(0x00000000 | PTE_P | PTE_PS | PTE_W), %edx + mov %edx, 0x2000 + mov $(0x40000000 | PTE_P | PTE_PS | PTE_W), %edx + mov %edx, 0x2008 + mov $(0x80000000 | PTE_P | PTE_PS | PTE_W), %edx + mov %edx, 0x2010 + mov $(0xC0000000 | PTE_P | PTE_PS | PTE_W), %edx + mov %edx, 0x2018 + + mov %ebp, %ebx + +.global entry32mp +entry32mp: + mov $0x1000, %eax + mov %eax, %cr3 + + lgdt (gdtr64 - mboot_header + mboot_load_addr) + + mov %cr4, %eax + bts $5, %eax + mov %eax, %cr4 + + mov $MSR_EFER, %ecx + rdmsr + bts $0, %eax + bts $8, %eax + wrmsr + + mov %cr0, %eax + orl $(CR0_PG | CR0_WP | CR0_MP), %eax + mov %eax, %cr0 + + ljmp $8, $(entry64low - mboot_header + mboot_load_addr) + +.align 16 +gdtr64: + .word gdt64_end - gdt64_begin - 1; + .quad gdt64_begin - mboot_header + mboot_load_addr + +.align 16 +gdt64_begin: + .long 0x00000000 + .long 0x00000000 + .long 0x00000000 # 64-bit code + .long 0x00209800 + .long 0x00000000 # 64-bit data + .long 0x00009000 +gdt64_end: + +.align 16 +.code64 +entry64low: + movabs $entry64high, %rax + jmp *%rax + +.global _start +_start: +entry64high: + xor %rax, %rax + mov %ax, %ss + mov %ax, %ds + mov %ax, %es + mov %ax, %fs + mov %ax, %gs + + # Is this an application processor, or the boot processor? + # + # This check used to be a comment saying it had not been written yet, and + # every AP fell straight through into the BSP path below: it set RSP to the + # *boot processor's* stack, passed its own flag to kmain() as a framebuffer + # address, and re-ran the entire bootstrap -- e820_init, kinit1, kvmalloc -- + # on a second core sharing one stack with the first. That is the SMP triple + # fault. QEMU hid it for years by never reporting more than one CPU (see + # kernel/arch/x86_64/acpi.c); VMware did not. + # + # EBX is the discriminator, and entryother.S sets it to 1 immediately before + # entering long mode for exactly this purpose. It is safe: on the boot + # processor EBX carries the VESA framebuffer address from the loader, which is + # either zero or a physical MMIO address, and can never be 1. + cmp $1, %ebx + je entry64mp + + # Setup initial stack + movabs $0xFFFF800000010000, %rax + mov %rax, %rsp + + # Pass VESA address to main (1st argument in RDI) + mov %rbx, %rdi + + call kmain + +.global __deadloop +__deadloop: + jmp . + +entry64mp: + mov $0x7000, %rax + mov -16(%rax), %rsp + jmp mpenter + +.global wrmsr +wrmsr: + mov %rdi, %rcx + mov %rsi, %rax + shr $32, %rsi + mov %rsi, %rdx + wrmsr + retq + +.global ignore_sysret +ignore_sysret: + mov $-38, %rax + sysretq \ No newline at end of file diff --git a/kernel/arch/x86_64/entryother.S b/kernel/arch/x86_64/entryother.S new file mode 100644 index 0000000..a2e98e6 --- /dev/null +++ b/kernel/arch/x86_64/entryother.S @@ -0,0 +1,78 @@ +#include "asm.h" +#include "memlayout.h" +#include "mmu.h" + +# Each non-boot CPU ("AP") is started up in response to a STARTUP +# IPI from the boot CPU. Section B.4.2 of the Multi-Processor +# Specification says that the AP will start in real mode with CS:IP +# set to XY00:0000, where XY is an 8-bit value sent with the +# STARTUP. Thus this code must start at a 4096-byte boundary. +# +# Because this code sets DS to zero, it must sit +# at an address in the low 2^16 bytes. +# +# Startothers (in main.c) sends the STARTUPs one at a time. +# It copies this code (start) at 0x7000. It puts the address of +# a newly allocated per-core stack in start-4,the address of the +# place to jump to (mpenter) in start-8, and the physical address +# of entrypgdir in start-12. +# +# This code is identical to bootasm.S except: +# - it does not need to enable A20 +# - it uses the address at start-4, start-8, and start-12 + +.code16 +.global start +start: + cli + + xorw %ax,%ax + movw %ax,%ds + movw %ax,%es + movw %ax,%ss + + lgdt gdtdesc + movl %cr0, %eax + orl $CR0_PE, %eax + movl %eax, %cr0 + + ljmpl $(SEG_KCODE<<3), $(start32) +//PAGEBREAK! + +.code32 +start32: + movw $(SEG_KDATA<<3), %ax + movw %ax, %ds + movw %ax, %es + movw %ax, %ss + movw $0, %ax + movw %ax, %fs + movw %ax, %gs + + # defer paging until we switch to 64bit mode + # set ebx=1 so shared boot code knows we're booting a secondary core + mov $1, %ebx + + # Switch to the stack allocated by startothers() + movl (start-4), %esp + # Call mpenter() + call *(start-8) + + movw $0x8a00, %ax + movw %ax, %dx + outw %ax, %dx + movw $0x8ae0, %ax + outw %ax, %dx +spin: + jmp spin + +.p2align 2 +gdt: + SEG_NULLASM + SEG_ASM(STA_X|STA_R, 0, 0xffffffff) + SEG_ASM(STA_W, 0, 0xffffffff) + + +gdtdesc: + .word (gdtdesc - gdt - 1) + .long gdt diff --git a/kernel/arch/x86_64/ioapic.c b/kernel/arch/x86_64/ioapic.c new file mode 100644 index 0000000..3fc6525 --- /dev/null +++ b/kernel/arch/x86_64/ioapic.c @@ -0,0 +1,102 @@ +// The I/O APIC manages hardware interrupts for an SMP system. +// http://www.intel.com/design/chipsets/datashts/29056601.pdf +// See also picirq.c. + +#include "types.h" +#include "defs.h" +#include "traps.h" +#include "memlayout.h" + +#define IOAPIC 0xFEC00000 // Default physical address of IO APIC + +#define REG_ID 0x00 // Register index: ID +#define REG_VER 0x01 // Register index: version +#define REG_TABLE 0x10 // Redirection table base + +// The redirection table starts at REG_TABLE and uses +// two registers to configure each interrupt. +// The first (low) register in a pair contains configuration bits. +// The second (high) register contains a bitmask telling which +// CPUs can serve that interrupt. +#define INT_DISABLED 0x00010000 // Interrupt disabled +#define INT_LEVEL 0x00008000 // Level-triggered (vs edge-) +#define INT_ACTIVELOW 0x00002000 // Active low (vs high) +#define INT_LOGICAL 0x00000800 // Destination is CPU id (vs APIC ID) + +volatile struct ioapic *ioapic; + +// IO APIC MMIO structure: write reg, then read or write data. +struct ioapic { + uint reg; + uint pad[3]; + uint data; +}; + +static uint +ioapicread(int reg) +{ + ioapic->reg = reg; + return ioapic->data; +} + +static void +ioapicwrite(int reg, uint data) +{ + ioapic->reg = reg; + ioapic->data = data; +} + +void +ioapicinit(void) +{ + int i, id, maxintr; + + ioapic = P2V((volatile struct ioapic*)IOAPIC); + maxintr = (ioapicread(REG_VER) >> 16) & 0xFF; + id = ioapicread(REG_ID) >> 24; + if(id != ioapicid) + cprintf("ioapicinit: id isn't equal to ioapicid; not a MP\n"); + + // Mark all interrupts edge-triggered, active high, disabled, + // and not routed to any CPUs. + for(i = 0; i <= maxintr; i++){ + ioapicwrite(REG_TABLE+2*i, INT_DISABLED | (T_IRQ0 + i)); + ioapicwrite(REG_TABLE+2*i+1, 0); + } +} + +// Route an interrupt to a CPU and unmask it. +// +// `level_low` picks PCI's signalling instead of ISA's. This distinction did +// not matter while every interrupt in the kernel came from an ISA-era device +// at a fixed pin, all of which are edge-triggered and active high -- which is +// why the original ioapicenable() hardcoded exactly that. A PCI device wired +// through an INTx pin is the opposite on both counts, and configuring it as +// edge/high does not fail cleanly: the device raises the line, we take one +// interrupt, and because the level never goes away there is no second edge +// and the device is never heard from again. +void +ioapicroute(int irq, int cpunum, int level_low) +{ + uint lo = T_IRQ0 + irq; + + if(level_low) + lo |= INT_LEVEL | INT_ACTIVELOW; + + // Program the destination before unmasking, so the entry is never briefly + // live pointing at CPU 0 when it was meant for another. + ioapicwrite(REG_TABLE+2*irq+1, cpunum << 24); + ioapicwrite(REG_TABLE+2*irq, lo); +} + +void +ioapicmask(int irq) +{ + ioapicwrite(REG_TABLE+2*irq, INT_DISABLED | (T_IRQ0 + irq)); +} + +void +ioapicenable(int irq, int cpunum) +{ + ioapicroute(irq, cpunum, 0); +} diff --git a/kernel/arch/x86_64/lapic.c b/kernel/arch/x86_64/lapic.c new file mode 100644 index 0000000..404bda7 --- /dev/null +++ b/kernel/arch/x86_64/lapic.c @@ -0,0 +1,245 @@ +// The local APIC manages internal (non-I/O) interrupts. +// See Chapter 8 & Appendix C of Intel processor manual volume 3. + +#include "param.h" +#include "types.h" +#include "defs.h" +#include "date.h" +#include "memlayout.h" +#include "traps.h" +#include "mmu.h" +#include "x86.h" +#include "proc.h" // ncpu + +// Local APIC registers, divided by 4 for use as uint[] indices. +#define ID (0x0020/4) // ID +#define VER (0x0030/4) // Version +#define TPR (0x0080/4) // Task Priority +#define EOI (0x00B0/4) // EOI +#define SVR (0x00F0/4) // Spurious Interrupt Vector + #define ENABLE 0x00000100 // Unit Enable +#define ESR (0x0280/4) // Error Status +#define ICRLO (0x0300/4) // Interrupt Command + #define SINIT 0x00000500 // INIT/RESET + #define STARTUP 0x00000600 // Startup IPI + #define DELIVS 0x00001000 // Delivery status + #define ASSERT 0x00004000 // Assert interrupt (vs deassert) + #define LEVEL 0x00008000 // Level triggered + #define BCAST 0x00080000 // Send to all APICs, including self. + #define BUSY 0x00001000 +#define ICRHI (0x0310/4) // Interrupt Command [63:32] +#define TIMER (0x0320/4) // Local Vector Table 0 (TIMER) + #define X1 0x0000000B // divide counts by 1 + #define PERIODIC 0x00020000 // Periodic +#define PCINT (0x0340/4) // Performance Counter LVT +#define LINT0 (0x0350/4) // Local Vector Table 1 (LINT0) +#define LINT1 (0x0360/4) // Local Vector Table 2 (LINT1) +#define ERROR (0x0370/4) // Local Vector Table 3 (ERROR) + #define MASKED 0x00010000 // Interrupt masked +#define TICR (0x0380/4) // Timer Initial Count +#define TCCR (0x0390/4) // Timer Current Count +#define TDCR (0x03E0/4) // Timer Divide Configuration + +volatile uint *lapic; // Initialized in mp.c + +static void +lapicw(int index, int value) +{ + lapic[index] = value; + lapic[ID]; // wait for write to finish, by reading +} + +void +lapicinit(void) +{ + if(!lapic) + return; + + // Enable local APIC; set spurious interrupt vector. + lapicw(SVR, ENABLE | (T_IRQ0 + IRQ_SPURIOUS)); + + // The timer repeatedly counts down at bus frequency + // from lapic[TICR] and then issues an interrupt. + // If xv6 cared more about precise timekeeping, + // TICR would be calibrated using an external time source. + lapicw(TDCR, X1); + lapicw(TIMER, PERIODIC | (T_IRQ0 + IRQ_TIMER)); + lapicw(TICR, 10000000); + + // Disable logical interrupt lines. + lapicw(LINT0, MASKED); + lapicw(LINT1, MASKED); + + // Disable performance counter overflow interrupts + // on machines that provide that interrupt entry. + if(((lapic[VER]>>16) & 0xFF) >= 4) + lapicw(PCINT, MASKED); + + // Map error interrupt to IRQ_ERROR. + lapicw(ERROR, T_IRQ0 + IRQ_ERROR); + + // Clear error status register (requires back-to-back writes). + lapicw(ESR, 0); + lapicw(ESR, 0); + + // Ack any outstanding interrupts. + lapicw(EOI, 0); + + // Send an Init Level De-Assert to synchronise arbitration ID's. + lapicw(ICRHI, 0); + lapicw(ICRLO, BCAST | SINIT | LEVEL); + while(lapic[ICRLO] & DELIVS) + ; + + // Enable interrupts on the APIC (but not on the processor). + lapicw(TPR, 0); +} + +int +cpunum(void) +{ + int apicid, i; + + // Cannot call cpu when interrupts are enabled: + // result not guaranteed to last long enough to be used! + // Would prefer to panic but even printing is chancy here: + // almost everything, including cprintf and panic, calls cpu, + // often indirectly through acquire and release. + if(readeflags()&FL_IF){ + static int n; + if(n++ == 0) + cprintf("cpu called from %x with interrupts enabled\n", + __builtin_return_address(0)); + } + + if (!lapic) + return 0; + + apicid = lapic[ID] >> 24; + for (i = 0; i < ncpu; ++i) { + if (cpus[i].apicid == apicid) + return i; + } + panic("unknown apicid\n"); +} + +// Acknowledge interrupt. +void +lapiceoi(void) +{ + if(lapic) + lapicw(EOI, 0); +} + +// Spin for a given number of microseconds. +// On real hardware would want to tune this dynamically. +void +microdelay(int us) +{ +} + +#define CMOS_PORT 0x70 +#define CMOS_RETURN 0x71 + +// Start additional processor running entry code at addr. +// See Appendix B of MultiProcessor Specification. +void +lapicstartap(uchar apicid, uint addr) +{ + int i; + ushort *wrv; + + // "The BSP must initialize CMOS shutdown code to 0AH + // and the warm reset vector (DWORD based at 40:67) to point at + // the AP startup code prior to the [universal startup algorithm]." + outb(CMOS_PORT, 0xF); // offset 0xF is shutdown code + outb(CMOS_PORT+1, 0x0A); + wrv = (ushort*)P2V((0x40<<4 | 0x67)); // Warm reset vector + wrv[0] = 0; + wrv[1] = addr >> 4; + + // "Universal startup algorithm." + // Send INIT (level-triggered) interrupt to reset other CPU. + lapicw(ICRHI, apicid<<24); + lapicw(ICRLO, SINIT | LEVEL | ASSERT); + microdelay(200); + lapicw(ICRLO, SINIT | LEVEL); + microdelay(100); // should be 10ms, but too slow in Bochs! + + // Send startup IPI (twice!) to enter code. + // Regular hardware is supposed to only accept a STARTUP + // when it is in the halted state due to an INIT. So the second + // should be ignored, but it is part of the official Intel algorithm. + // Bochs complains about the second one. Too bad for Bochs. + for(i = 0; i < 2; i++){ + lapicw(ICRHI, apicid<<24); + lapicw(ICRLO, STARTUP | (addr>>12)); + microdelay(200); + } +} + +#define CMOS_STATA 0x0a +#define CMOS_STATB 0x0b +#define CMOS_UIP (1 << 7) // RTC update in progress + +#define SECS 0x00 +#define MINS 0x02 +#define HOURS 0x04 +#define DAY 0x07 +#define MONTH 0x08 +#define YEAR 0x09 + +static uint cmos_read(uint reg) +{ + outb(CMOS_PORT, reg); + microdelay(200); + + return inb(CMOS_RETURN); +} + +static void fill_rtcdate(struct rtcdate *r) +{ + r->second = cmos_read(SECS); + r->minute = cmos_read(MINS); + r->hour = cmos_read(HOURS); + r->day = cmos_read(DAY); + r->month = cmos_read(MONTH); + r->year = cmos_read(YEAR); +} +//PAGEBREAK! + +// qemu seems to use 24-hour GWT and the values are BCD encoded +void cmostime(struct rtcdate *r) +{ + struct rtcdate t1, t2; + int sb, bcd; + + sb = cmos_read(CMOS_STATB); + + bcd = (sb & (1 << 2)) == 0; + + // make sure CMOS doesn't modify time while we read it + for(;;) { + fill_rtcdate(&t1); + if(cmos_read(CMOS_STATA) & CMOS_UIP) + continue; + fill_rtcdate(&t2); + if(memcmp(&t1, &t2, sizeof(t1)) == 0) + break; + } + + // convert + if(bcd) { +#define CONV(x) (t1.x = ((t1.x >> 4) * 10) + (t1.x & 0xf)) + CONV(second); + CONV(minute); + CONV(hour ); + CONV(day ); + CONV(month ); + CONV(year ); +#undef CONV + } + + *r = t1; + r->year += 2000; +} diff --git a/kernel/arch/x86_64/mp.c b/kernel/arch/x86_64/mp.c new file mode 100644 index 0000000..9e68624 --- /dev/null +++ b/kernel/arch/x86_64/mp.c @@ -0,0 +1,159 @@ +// Multiprocessor support +// Search memory for MP description structures. +// http://developer.intel.com/design/pentium/datashts/24201606.pdf + +#include "types.h" +#include "defs.h" +#include "param.h" +#include "memlayout.h" +#include "mp.h" +#include "x86.h" +#include "mmu.h" +#include "proc.h" + +struct cpu cpus[NCPU]; +// Processors the firmware says exist. +int ncpu; + +// Processors actually running, which is not the same number and must not be +// confused with it. Enumeration is cheap and startup is where the difficulty +// is: right now the APs are found and deliberately not started, so this stays +// at 1 while ncpu is 2, 4 or 16. +// +// /proc/cpuinfo reports *this* one, matching Linux, where cpuinfo lists online +// CPUs. Reporting ncpu there told a VM with four processors that it had four +// working ones, which was a comfortable thing to read and false. +int ncpu_online = 1; +uchar ioapicid; + +static uchar +sum(uchar *addr, int len) +{ + int i, sum; + + sum = 0; + for(i=0; iphysaddr == 0) + return 0; + conf = (struct mpconf*) P2V((addr_t) mp->physaddr); + if(memcmp(conf, "PCMP", 4) != 0) + return 0; + if(conf->version != 1 && conf->version != 4) + return 0; + if(sum((uchar*)conf, conf->length) != 0) + return 0; + *pmp = mp; + return conf; +} + +void +mpinit(void) +{ + uchar *p, *e; + struct mp *mp; + struct mpconf *conf; + struct mpproc *mproc; + struct mpioapic *ioapic; + + if((conf = mpconfig(&mp)) == 0) { + cprintf("No other CPUs found.\n"); + return; + } + lapic = (uint*)P2V(conf->lapicaddr_p); + for(p=(uchar*)(conf+1), e=(uchar*)conf+conf->length; papicid; // apicid may differ from ncpu + ncpu++; + } + p += sizeof(struct mpproc); + continue; + case MPIOAPIC: + ioapic = (struct mpioapic*)p; + ioapicid = ioapic->apicno; + p += sizeof(struct mpioapic); + continue; + case MPBUS: + case MPIOINTR: + case MPLINTR: + p += 8; + continue; + default: + panic("Major problem parsing mp config."); + break; + } + } + // "Seems we are" is doing a lot of work here, and the number is usually + // wrong. This walks the *legacy Intel MP table* (1994), which modern firmware + // fills in minimally or not at all -- QEMU/SeaBIOS emits exactly one + // processor entry regardless of -smp, because it expects the OS to enumerate + // CPUs from the ACPI MADT instead. Verified 2026-08-08: with -smp 4 the table + // is 208 bytes and contains one type-0 entry, then buses, an I/O APIC and + // interrupt assignments. So ncpu is 1 on QEMU no matter what, and SMP is + // silently never exercised there. + // + // See docs/roadmap.md "Known broken" -- this is half of the SMP bug, and the + // reason the other half (AP startup triple-faulting) cannot be reproduced + // under QEMU until the MADT is parsed. + cprintf("Seems we are SMP, ncpu = %d\n",ncpu); + if(mp->imcrp){ + // Bochs doesn't support IMCR, so this doesn't run on Bochs. + // But it would on real hardware. + outb(0x22, 0x70); // Select IMCR + outb(0x23, inb(0x23) | 1); // Mask external interrupts. + } +} diff --git a/kernel/arch/x86_64/swtch.S b/kernel/arch/x86_64/swtch.S new file mode 100644 index 0000000..c8005d8 --- /dev/null +++ b/kernel/arch/x86_64/swtch.S @@ -0,0 +1,30 @@ +# Context switch +# +# void swtch(struct context **old, struct context *new); +# +# Save current register context in old +# and then load register context from new. + +.global swtch +swtch: + # Save old callee-save registers + pushq %rbp + pushq %rbx + pushq %r12 + pushq %r13 + pushq %r14 + pushq %r15 + + # Switch stacks + movq %rsp, (%rdi) + movq %rsi, %rsp + + # Load new callee-save registers + popq %r15 + popq %r14 + popq %r13 + popq %r12 + popq %rbx + popq %rbp + + retq #?? diff --git a/kernel/arch/x86_64/trap.c b/kernel/arch/x86_64/trap.c new file mode 100644 index 0000000..e79397e --- /dev/null +++ b/kernel/arch/x86_64/trap.c @@ -0,0 +1,159 @@ +#include "types.h" +#include "defs.h" +#include "irq.h" +#include "param.h" +#include "memlayout.h" +#include "mmu.h" +#include "proc.h" +#include "x86.h" +#include "traps.h" +#include "spinlock.h" +#include "posix.h" + +uint *idt; +extern addr_t vectors[]; +struct spinlock tickslock; +uint ticks; + +static void +mkgate(uint *idt, uint n, addr_t kva, uint pl) +{ + uint64 addr = (uint64) kva; + + n *= 4; + idt[n+0] = (addr & 0xFFFF) | (KERNEL_CS << 16); + idt[n+1] = (addr & 0xFFFF0000) | 0x8E00 | ((pl & 3) << 13); + idt[n+2] = addr >> 32; + idt[n+3] = 0; +} + +void idtinit(void) +{ + struct { + ushort limit; + uint64 base; + } __attribute__((packed)) idtdesc; + + idtdesc.limit = PGSIZE - 1; + idtdesc.base = (uint64)idt; + + asm volatile("lidt %0" : : "m" (idtdesc)); +} + +void tvinit(void) +{ + int n; + idt = (uint*) kalloc(); + memset(idt, 0, PGSIZE); + for (n = 0; n < 256; n++) + mkgate(idt, n, vectors[n], 0); + mkgate(idt, T_SYSCALL, vectors[T_SYSCALL], 3); +} + +uint +uptime(void) +{ + uint xticks; + + acquire(&tickslock); + xticks = ticks; + release(&tickslock); + return xticks; +} + +// Which signal a CPU fault corresponds to, so a process killed by a fault is +// reported to wait() as died-by-signal with a sensible cause rather than a +// blanket SIGKILL. +static int +trapsig(uint64 trapno) +{ + switch(trapno){ + case T_DIVIDE: return SIGFPE; + case T_ILLOP: return SIGILL; + case T_GPFLT: return SIGSEGV; + case T_PGFLT: return SIGSEGV; + case T_BRKPT: return SIGTRAP; + case T_DEBUG: return SIGTRAP; + default: return SIGSEGV; + } +} + +void +trap(struct trapframe *tf) +{ + kstack_check_canary("trap entry"); + switch(tf->trapno){ + case T_IRQ0 + IRQ_TIMER: + if(cpunum() == 0){ + acquire(&tickslock); + ticks++; + wakeup(&ticks); + release(&tickslock); + } + lapiceoi(); + break; + case T_IRQ0 + IRQ_SPURIOUS: + cprintf("cpu%d: spurious interrupt at %p:%p\n", + cpunum(), tf->cs, tf->rip); + lapiceoi(); + break; + + default: + // Every other IOAPIC pin goes through the registration table. The device + // interrupts used to be listed here one case at a time, which cannot work + // for anything found on the PCI bus: a driver does not know its line + // until it reads the device's config space at probe time, so there is no + // constant to write a case label for. See include/irq.h. + if(tf->trapno >= T_IRQ0 && tf->trapno < T_IRQ0 + NIRQ){ + irq_dispatch(tf->trapno - T_IRQ0); + lapiceoi(); + break; + } + + if(myproc() == 0 || (tf->cs&3) == 0){ + cprintf("unexpected trap %d from cpu %d rip %p (cr2=0x%p) err=0x%p\n", + tf->trapno, cpunum(), tf->rip, rcr2(), tf->err); + if (myproc()) + cprintf("proc id: %d\n", myproc()->pid); + if (tf->trapno == T_GPFLT) { + uint64 *iretframe = (uint64*)tf->rsp; + cprintf("iretq frame @ %p: rip=%p cs=%p rflags=%p rsp=%p ss=%p\n", + iretframe, iretframe[0], iretframe[1], iretframe[2], + iretframe[3], iretframe[4]); + if (myproc()) + cprintf("this tf=%p myproc()->tf=%p myproc()->kstack=%p (tf-kstack=%p)\n", + tf, myproc()->tf, myproc()->kstack, (uint64)tf - (uint64)myproc()->kstack); + } + panic("trap"); + } + cprintf("pid %d %s: trap %d err %d on cpu %d " + "rip 0x%p addr 0x%p--kill proc\n", + myproc()->pid, myproc()->name, tf->trapno, tf->err, cpunum(), tf->rip, + rcr2()); + // General-purpose register dump. This used to be followed by ~25 lines + // that walked musl's mallocng group/meta structures out of rax/rdi and + // dumped the user stack -- scaffolding for the boot-crash investigation + // recorded in docs/architecture/kernel-history.md section 1. That + // investigation is closed and the underlying kernel bugs are fixed, so + // the allocator-specific part is gone; the registers stay, since they are + // useful for any fault. + if (tf->trapno == T_GPFLT) { + cprintf(" rax=%p rbx=%p rcx=%p rdx=%p rsi=%p rdi=%p rbp=%p rsp=%p\n", + tf->rax, tf->rbx, tf->rcx, tf->rdx, tf->rsi, tf->rdi, tf->rbp, tf->rsp); + } + myproc()->killed = 1; + if(myproc()->killsig == 0) + myproc()->killsig = trapsig(tf->trapno); + } + + if(myproc() && myproc()->killed && (tf->cs&3) == DPL_USER) + exit(W_EXITCODE(0, myproc()->killsig ? myproc()->killsig : SIGKILL)); + + if(myproc() && myproc()->state == RUNNING && tf->trapno == T_IRQ0+IRQ_TIMER) + yield(); + + if(myproc() && myproc()->killed && (tf->cs&3) == DPL_USER) + exit(W_EXITCODE(0, myproc()->killsig ? myproc()->killsig : SIGKILL)); + + kstack_check_canary("trap exit"); +} diff --git a/kernel/arch/x86_64/trapasm.S b/kernel/arch/x86_64/trapasm.S new file mode 100644 index 0000000..faf9bee --- /dev/null +++ b/kernel/arch/x86_64/trapasm.S @@ -0,0 +1,257 @@ +# trapasm.S — kernel/user entry/exit with FS_BASE swapping for TLS +# +# The kernel uses __thread struct cpu *cpu and __thread struct proc *proc, +# accessed via %fs (IA32_FS_BASE MSR, 0xC0000100). seginit() sets this to +# kernel_fs_base. User-space TLS (musl) uses the same MSR via arch_prctl +# ARCH_SET_FS (saved in proc->fs_base). +# +# Rule: on ENTRY from user (syscall or interrupt), restore kernel FS_BASE. +# on EXIT to user (sysretq or iretq), write proc->fs_base to MSR. +# +# proc->fs_base is at byte offset 0x18 (24) inside struct proc. +# proc pointer is at %gs:24 (PERCPU_PROC in include/proc.h); cpu is at %gs:16. + +# ----------------------------------------------------------------------- +# Helper macro: write kernel_fs_base to IA32_FS_BASE. +# Clobbers %rax, %rcx, %rdx. Caller must save them if needed. +# ----------------------------------------------------------------------- +# Read from %gs, not from a global. +# +# This used to be `movq kernel_fs_base(%rip), %rax`, and kernel_fs_base was a +# single global that every CPU wrote during seginit(). With one processor that +# is merely untidy. With two it is fatal: whichever CPU ran seginit() last wins, +# and from the first trap onward *every* CPU restores that one CPU's per-CPU +# block -- so `cpu` and `proc` are shared, pushcli/popcli nesting counts collide, +# and the kernel dies in panic("sched locks") shortly after the APs start. +# +# GS base is set per CPU by seginit() to the same value as FS base, and %gs:0 +# holds the block's self-pointer. So this reads *this* CPU's kernel FS base with +# no global involved. +# +# No swapgs, deliberately. The usual reason for it is that userspace owns GS; +# here it does not -- sys_arch_prctl() implements only ARCH_SET_FS/ARCH_GET_FS +# and rejects everything else, musl uses FS for TLS on x86-64, and the only +# other writes to %gs are selector loads in entry.S and entryother.S that happen +# before seginit(). GS is the kernel's alone, so it can simply stay pointing at +# the per-CPU block. If ARCH_SET_GS is ever implemented, this must become a +# proper swapgs pair. +.macro RESTORE_KERNEL_FS + movq %gs:0, %rax + movq %rax, %rdx + shrq $32, %rdx + movl $0xC0000100, %ecx + wrmsr +.endm + +# ----------------------------------------------------------------------- +# Helper macro: write proc->fs_base to IA32_FS_BASE. +# Requires %fs to still hold the kernel base. +# Clobbers %rax, %rcx, %rdx. Caller must save them if needed. +# ----------------------------------------------------------------------- +.macro WRITE_USER_FS + # proc, from the per-CPU block at %gs:24 (PERCPU_PROC in include/proc.h). + # This used to be %fs:(-8), the __thread slot, which no longer exists -- + # see the comment in include/proc.h for why those stopped being __thread. + movq %gs:24, %rax # proc pointer + movq 0x18(%rax), %rax # proc->fs_base (offset 24 = 0x18) + movq %rax, %rdx + shrq $32, %rdx + movl $0xC0000100, %ecx + wrmsr +.endm + +# ----------------------------------------------------------------------- +# alltraps — IDT interrupt/fault entry +# Stack on entry (from user mode, CPU has done privilege switch): +# [rsp+0] trapno (pushed by vectors.S) +# [rsp+8] errorcode +# [rsp+16] rip +# [rsp+24] cs ← bit 1:0 == 3 means user mode +# [rsp+32] rflags +# [rsp+40] user_rsp +# [rsp+48] user_ss +# ----------------------------------------------------------------------- +.global alltraps +alltraps: + # Always restore kernel FS_BASE before touching %fs:, even when the + # saved cs says we were already in kernel mode. A fault/interrupt can + # land in that narrow kernel-mode window after syscall_trapret/trapret + # have already done WRITE_USER_FS (cs is still 0x8 there, but FS_BASE + # is the *user* value) — the old cs-based skip trusted that combination + # never happens and read %fs:(-8) as garbage (proc/cpu == NULL) in that + # case. RESTORE_KERNEL_FS is just a wrmsr of an already-known value when + # we truly were in the kernel, so doing it unconditionally is cheap and + # always correct. + pushq %rax + pushq %rcx + pushq %rdx + RESTORE_KERNEL_FS + popq %rdx + popq %rcx + popq %rax + + # Build trap frame. + pushq %r15 + pushq %r14 + pushq %r13 + pushq %r12 + pushq %r11 + pushq %r10 + pushq %r9 + pushq %r8 + pushq %rdi + pushq %rsi + pushq %rbp + pushq %rdx + pushq %rcx + pushq %rbx + pushq %rax + + movq %rsp, %rdi # frame in arg1 + callq trap +# Return falls through to trapret... + +.global trapret +trapret: + popq %rax + popq %rbx + popq %rcx + popq %rdx + popq %rbp + popq %rsi + popq %rdi + popq %r8 + popq %r9 + popq %r10 + popq %r11 + popq %r12 + popq %r13 + popq %r14 + popq %r15 + + addq $16, %rsp # discard trapno and errorcode + + # If returning to user mode, write proc->fs_base to FS_BASE MSR. + # Stack now: [rsp+0]=rip, [rsp+8]=cs, [rsp+16]=rflags, [rsp+24]=rsp, [rsp+32]=ss + testb $3, 8(%rsp) + jz .Ltrapret_kret + + # cli before WRITE_USER_FS: close the same race as in syscall_trapret. + # iretq will atomically restore RFLAGS (IF=1) when switching to user mode. + pushq %rax + pushq %rcx + pushq %rdx + cli + WRITE_USER_FS + popq %rdx + popq %rcx + popq %rax + +.Ltrapret_kret: + iretq + +# ----------------------------------------------------------------------- +# syscall_entry — SYSCALL instruction entry point +# On entry: rcx=user rip, r11=user rflags, rsp=user stack, IF cleared. +# FS_BASE may be the user's TLS value (set at last sysretq). +# ----------------------------------------------------------------------- +.global syscall_entry +syscall_entry: + # Swap FS_BASE to kernel value BEFORE any %fs: access. + # Save rax/rcx/rdx to single-CPU scratch globals (IF is off, no race). + movq %rax, _sc_rax(%rip) + movq %rcx, _sc_rcx(%rip) # user rip (SYSCALL stores it here) + movq %rdx, _sc_rdx(%rip) + RESTORE_KERNEL_FS # clobbers rax, rcx, rdx — that's OK + movq _sc_rax(%rip), %rax + movq _sc_rcx(%rip), %rcx + movq _sc_rdx(%rip), %rdx + + # Switch to kernel stack: store user rsp at top of proc->kstack. + # + # The scratch slot is %fs:(8), NOT %fs:(0). %fs:(0) is the TCB + # self-pointer -- the x86-64 TLS ABI reserves the word at the thread + # pointer to hold the thread pointer itself, and a compiler is entitled to + # read it to materialise the TP before applying a variable's offset. + # + # It used to be %fs:(0), and that survived for years only because GCC + # compiles `proc`/`cpu` into direct `%fs:-8` segment references and never + # touches the word. clang at -O0 does `movq %fs:0, %rax; addq $-8, %rax; + # (%rax)` instead -- so it read the syscall number this line had just + # stored (7, SYS_exec, from initcode), computed 7-8 = -1, and page-faulted + # on the very first syscall of the very first process. + # + # Positive offsets are safe: the TLS block occupies negative offsets + # (-16 = cpu, -8 = proc) and seginit puts the thread pointer 2048 bytes + # into a 4096-byte page, so %fs:(8) onward is 2040 bytes we own outright. + movq %rax, %fs:(8) # save rax in per-CPU scratch slot + movq %gs:24, %rax # proc pointer (PERCPU_PROC) + movq 0x10(%rax), %rax # proc->kstack (offset 16) + addq $(4096-16), %rax # point to tf->rsp slot + movq %rsp, (%rax) # save user rsp + movq %rax, %rsp # switch to kernel stack + movq %fs:(8), %rax # restore rax + + pushq %r11 # rflags + pushq $0 # cs (ignored) + pushq %rcx # rip (next user instruction) + + pushq $0 # err + pushq $0 # trapno (ignored) + + pushq %r15 + pushq %r14 + pushq %r13 + pushq %r12 + pushq %r11 + pushq %r10 + pushq %r9 + pushq %r8 + pushq %rdi + pushq %rsi + pushq %rbp + pushq %rdx + pushq %rcx + pushq %rbx + pushq %rax + + movq %rsp, %rdi # frame in arg1 + callq syscall +# Return falls through to syscall_trapret... + +# ----------------------------------------------------------------------- +# syscall_trapret — return path for SYSCALL-based syscalls +# ----------------------------------------------------------------------- +.global syscall_trapret +syscall_trapret: + # Disable interrupts BEFORE writing user FS_BASE to the MSR. + # Without cli here, a timer IRQ between WRITE_USER_FS and the cli below + # would arrive in ring-0 with user FS_BASE set, causing %fs:(-8) to return + # garbage (proc=NULL) → panic in the interrupt handler. + # rax/rcx/rdx are clobbered by WRITE_USER_FS then restored by the pops. + cli + WRITE_USER_FS + + popq %rax + popq %rbx + popq %rcx + popq %rdx + popq %rbp + popq %rsi + popq %rdi + popq %r8 + popq %r9 + popq %r10 + popq %r11 + popq %r12 + popq %r13 + popq %r14 + popq %r15 + + addq $40, %rsp # discard trapno, errorcode, rip, cs, rflags + + # Mask interrupts: sysretq is unsafe with IF set on non-canonical RSP. + # See https://www.felixcloutier.com/x86/sysret + cli + movq (%rsp), %rsp # restore the user stack + sysretq diff --git a/kernel/arch/x86_64/vectors.S b/kernel/arch/x86_64/vectors.S new file mode 100644 index 0000000..264bb77 --- /dev/null +++ b/kernel/arch/x86_64/vectors.S @@ -0,0 +1,1281 @@ +# generated by vectors.pl - do not edit +# handlers +.global alltraps +vector0: + push $0 + push $0 + jmp alltraps +vector1: + push $0 + push $1 + jmp alltraps +vector2: + push $0 + push $2 + jmp alltraps +vector3: + push $0 + push $3 + jmp alltraps +vector4: + push $0 + push $4 + jmp alltraps +vector5: + push $0 + push $5 + jmp alltraps +vector6: + push $0 + push $6 + jmp alltraps +vector7: + push $0 + push $7 + jmp alltraps +vector8: + push $8 + jmp alltraps +vector9: + push $0 + push $9 + jmp alltraps +vector10: + push $10 + jmp alltraps +vector11: + push $11 + jmp alltraps +vector12: + push $12 + jmp alltraps +vector13: + push $13 + jmp alltraps +vector14: + push $14 + jmp alltraps +vector15: + push $0 + push $15 + jmp alltraps +vector16: + push $0 + push $16 + jmp alltraps +vector17: + push $17 + jmp alltraps +vector18: + push $0 + push $18 + jmp alltraps +vector19: + push $0 + push $19 + jmp alltraps +vector20: + push $0 + push $20 + jmp alltraps +vector21: + push $0 + push $21 + jmp alltraps +vector22: + push $0 + push $22 + jmp alltraps +vector23: + push $0 + push $23 + jmp alltraps +vector24: + push $0 + push $24 + jmp alltraps +vector25: + push $0 + push $25 + jmp alltraps +vector26: + push $0 + push $26 + jmp alltraps +vector27: + push $0 + push $27 + jmp alltraps +vector28: + push $0 + push $28 + jmp alltraps +vector29: + push $0 + push $29 + jmp alltraps +vector30: + push $0 + push $30 + jmp alltraps +vector31: + push $0 + push $31 + jmp alltraps +vector32: + push $0 + push $32 + jmp alltraps +vector33: + push $0 + push $33 + jmp alltraps +vector34: + push $0 + push $34 + jmp alltraps +vector35: + push $0 + push $35 + jmp alltraps +vector36: + push $0 + push $36 + jmp alltraps +vector37: + push $0 + push $37 + jmp alltraps +vector38: + push $0 + push $38 + jmp alltraps +vector39: + push $0 + push $39 + jmp alltraps +vector40: + push $0 + push $40 + jmp alltraps +vector41: + push $0 + push $41 + jmp alltraps +vector42: + push $0 + push $42 + jmp alltraps +vector43: + push $0 + push $43 + jmp alltraps +vector44: + push $0 + push $44 + jmp alltraps +vector45: + push $0 + push $45 + jmp alltraps +vector46: + push $0 + push $46 + jmp alltraps +vector47: + push $0 + push $47 + jmp alltraps +vector48: + push $0 + push $48 + jmp alltraps +vector49: + push $0 + push $49 + jmp alltraps +vector50: + push $0 + push $50 + jmp alltraps +vector51: + push $0 + push $51 + jmp alltraps +vector52: + push $0 + push $52 + jmp alltraps +vector53: + push $0 + push $53 + jmp alltraps +vector54: + push $0 + push $54 + jmp alltraps +vector55: + push $0 + push $55 + jmp alltraps +vector56: + push $0 + push $56 + jmp alltraps +vector57: + push $0 + push $57 + jmp alltraps +vector58: + push $0 + push $58 + jmp alltraps +vector59: + push $0 + push $59 + jmp alltraps +vector60: + push $0 + push $60 + jmp alltraps +vector61: + push $0 + push $61 + jmp alltraps +vector62: + push $0 + push $62 + jmp alltraps +vector63: + push $0 + push $63 + jmp alltraps +vector64: + push $0 + push $64 + jmp alltraps +vector65: + push $0 + push $65 + jmp alltraps +vector66: + push $0 + push $66 + jmp alltraps +vector67: + push $0 + push $67 + jmp alltraps +vector68: + push $0 + push $68 + jmp alltraps +vector69: + push $0 + push $69 + jmp alltraps +vector70: + push $0 + push $70 + jmp alltraps +vector71: + push $0 + push $71 + jmp alltraps +vector72: + push $0 + push $72 + jmp alltraps +vector73: + push $0 + push $73 + jmp alltraps +vector74: + push $0 + push $74 + jmp alltraps +vector75: + push $0 + push $75 + jmp alltraps +vector76: + push $0 + push $76 + jmp alltraps +vector77: + push $0 + push $77 + jmp alltraps +vector78: + push $0 + push $78 + jmp alltraps +vector79: + push $0 + push $79 + jmp alltraps +vector80: + push $0 + push $80 + jmp alltraps +vector81: + push $0 + push $81 + jmp alltraps +vector82: + push $0 + push $82 + jmp alltraps +vector83: + push $0 + push $83 + jmp alltraps +vector84: + push $0 + push $84 + jmp alltraps +vector85: + push $0 + push $85 + jmp alltraps +vector86: + push $0 + push $86 + jmp alltraps +vector87: + push $0 + push $87 + jmp alltraps +vector88: + push $0 + push $88 + jmp alltraps +vector89: + push $0 + push $89 + jmp alltraps +vector90: + push $0 + push $90 + jmp alltraps +vector91: + push $0 + push $91 + jmp alltraps +vector92: + push $0 + push $92 + jmp alltraps +vector93: + push $0 + push $93 + jmp alltraps +vector94: + push $0 + push $94 + jmp alltraps +vector95: + push $0 + push $95 + jmp alltraps +vector96: + push $0 + push $96 + jmp alltraps +vector97: + push $0 + push $97 + jmp alltraps +vector98: + push $0 + push $98 + jmp alltraps +vector99: + push $0 + push $99 + jmp alltraps +vector100: + push $0 + push $100 + jmp alltraps +vector101: + push $0 + push $101 + jmp alltraps +vector102: + push $0 + push $102 + jmp alltraps +vector103: + push $0 + push $103 + jmp alltraps +vector104: + push $0 + push $104 + jmp alltraps +vector105: + push $0 + push $105 + jmp alltraps +vector106: + push $0 + push $106 + jmp alltraps +vector107: + push $0 + push $107 + jmp alltraps +vector108: + push $0 + push $108 + jmp alltraps +vector109: + push $0 + push $109 + jmp alltraps +vector110: + push $0 + push $110 + jmp alltraps +vector111: + push $0 + push $111 + jmp alltraps +vector112: + push $0 + push $112 + jmp alltraps +vector113: + push $0 + push $113 + jmp alltraps +vector114: + push $0 + push $114 + jmp alltraps +vector115: + push $0 + push $115 + jmp alltraps +vector116: + push $0 + push $116 + jmp alltraps +vector117: + push $0 + push $117 + jmp alltraps +vector118: + push $0 + push $118 + jmp alltraps +vector119: + push $0 + push $119 + jmp alltraps +vector120: + push $0 + push $120 + jmp alltraps +vector121: + push $0 + push $121 + jmp alltraps +vector122: + push $0 + push $122 + jmp alltraps +vector123: + push $0 + push $123 + jmp alltraps +vector124: + push $0 + push $124 + jmp alltraps +vector125: + push $0 + push $125 + jmp alltraps +vector126: + push $0 + push $126 + jmp alltraps +vector127: + push $0 + push $127 + jmp alltraps +vector128: + push $0 + push $128 + jmp alltraps +vector129: + push $0 + push $129 + jmp alltraps +vector130: + push $0 + push $130 + jmp alltraps +vector131: + push $0 + push $131 + jmp alltraps +vector132: + push $0 + push $132 + jmp alltraps +vector133: + push $0 + push $133 + jmp alltraps +vector134: + push $0 + push $134 + jmp alltraps +vector135: + push $0 + push $135 + jmp alltraps +vector136: + push $0 + push $136 + jmp alltraps +vector137: + push $0 + push $137 + jmp alltraps +vector138: + push $0 + push $138 + jmp alltraps +vector139: + push $0 + push $139 + jmp alltraps +vector140: + push $0 + push $140 + jmp alltraps +vector141: + push $0 + push $141 + jmp alltraps +vector142: + push $0 + push $142 + jmp alltraps +vector143: + push $0 + push $143 + jmp alltraps +vector144: + push $0 + push $144 + jmp alltraps +vector145: + push $0 + push $145 + jmp alltraps +vector146: + push $0 + push $146 + jmp alltraps +vector147: + push $0 + push $147 + jmp alltraps +vector148: + push $0 + push $148 + jmp alltraps +vector149: + push $0 + push $149 + jmp alltraps +vector150: + push $0 + push $150 + jmp alltraps +vector151: + push $0 + push $151 + jmp alltraps +vector152: + push $0 + push $152 + jmp alltraps +vector153: + push $0 + push $153 + jmp alltraps +vector154: + push $0 + push $154 + jmp alltraps +vector155: + push $0 + push $155 + jmp alltraps +vector156: + push $0 + push $156 + jmp alltraps +vector157: + push $0 + push $157 + jmp alltraps +vector158: + push $0 + push $158 + jmp alltraps +vector159: + push $0 + push $159 + jmp alltraps +vector160: + push $0 + push $160 + jmp alltraps +vector161: + push $0 + push $161 + jmp alltraps +vector162: + push $0 + push $162 + jmp alltraps +vector163: + push $0 + push $163 + jmp alltraps +vector164: + push $0 + push $164 + jmp alltraps +vector165: + push $0 + push $165 + jmp alltraps +vector166: + push $0 + push $166 + jmp alltraps +vector167: + push $0 + push $167 + jmp alltraps +vector168: + push $0 + push $168 + jmp alltraps +vector169: + push $0 + push $169 + jmp alltraps +vector170: + push $0 + push $170 + jmp alltraps +vector171: + push $0 + push $171 + jmp alltraps +vector172: + push $0 + push $172 + jmp alltraps +vector173: + push $0 + push $173 + jmp alltraps +vector174: + push $0 + push $174 + jmp alltraps +vector175: + push $0 + push $175 + jmp alltraps +vector176: + push $0 + push $176 + jmp alltraps +vector177: + push $0 + push $177 + jmp alltraps +vector178: + push $0 + push $178 + jmp alltraps +vector179: + push $0 + push $179 + jmp alltraps +vector180: + push $0 + push $180 + jmp alltraps +vector181: + push $0 + push $181 + jmp alltraps +vector182: + push $0 + push $182 + jmp alltraps +vector183: + push $0 + push $183 + jmp alltraps +vector184: + push $0 + push $184 + jmp alltraps +vector185: + push $0 + push $185 + jmp alltraps +vector186: + push $0 + push $186 + jmp alltraps +vector187: + push $0 + push $187 + jmp alltraps +vector188: + push $0 + push $188 + jmp alltraps +vector189: + push $0 + push $189 + jmp alltraps +vector190: + push $0 + push $190 + jmp alltraps +vector191: + push $0 + push $191 + jmp alltraps +vector192: + push $0 + push $192 + jmp alltraps +vector193: + push $0 + push $193 + jmp alltraps +vector194: + push $0 + push $194 + jmp alltraps +vector195: + push $0 + push $195 + jmp alltraps +vector196: + push $0 + push $196 + jmp alltraps +vector197: + push $0 + push $197 + jmp alltraps +vector198: + push $0 + push $198 + jmp alltraps +vector199: + push $0 + push $199 + jmp alltraps +vector200: + push $0 + push $200 + jmp alltraps +vector201: + push $0 + push $201 + jmp alltraps +vector202: + push $0 + push $202 + jmp alltraps +vector203: + push $0 + push $203 + jmp alltraps +vector204: + push $0 + push $204 + jmp alltraps +vector205: + push $0 + push $205 + jmp alltraps +vector206: + push $0 + push $206 + jmp alltraps +vector207: + push $0 + push $207 + jmp alltraps +vector208: + push $0 + push $208 + jmp alltraps +vector209: + push $0 + push $209 + jmp alltraps +vector210: + push $0 + push $210 + jmp alltraps +vector211: + push $0 + push $211 + jmp alltraps +vector212: + push $0 + push $212 + jmp alltraps +vector213: + push $0 + push $213 + jmp alltraps +vector214: + push $0 + push $214 + jmp alltraps +vector215: + push $0 + push $215 + jmp alltraps +vector216: + push $0 + push $216 + jmp alltraps +vector217: + push $0 + push $217 + jmp alltraps +vector218: + push $0 + push $218 + jmp alltraps +vector219: + push $0 + push $219 + jmp alltraps +vector220: + push $0 + push $220 + jmp alltraps +vector221: + push $0 + push $221 + jmp alltraps +vector222: + push $0 + push $222 + jmp alltraps +vector223: + push $0 + push $223 + jmp alltraps +vector224: + push $0 + push $224 + jmp alltraps +vector225: + push $0 + push $225 + jmp alltraps +vector226: + push $0 + push $226 + jmp alltraps +vector227: + push $0 + push $227 + jmp alltraps +vector228: + push $0 + push $228 + jmp alltraps +vector229: + push $0 + push $229 + jmp alltraps +vector230: + push $0 + push $230 + jmp alltraps +vector231: + push $0 + push $231 + jmp alltraps +vector232: + push $0 + push $232 + jmp alltraps +vector233: + push $0 + push $233 + jmp alltraps +vector234: + push $0 + push $234 + jmp alltraps +vector235: + push $0 + push $235 + jmp alltraps +vector236: + push $0 + push $236 + jmp alltraps +vector237: + push $0 + push $237 + jmp alltraps +vector238: + push $0 + push $238 + jmp alltraps +vector239: + push $0 + push $239 + jmp alltraps +vector240: + push $0 + push $240 + jmp alltraps +vector241: + push $0 + push $241 + jmp alltraps +vector242: + push $0 + push $242 + jmp alltraps +vector243: + push $0 + push $243 + jmp alltraps +vector244: + push $0 + push $244 + jmp alltraps +vector245: + push $0 + push $245 + jmp alltraps +vector246: + push $0 + push $246 + jmp alltraps +vector247: + push $0 + push $247 + jmp alltraps +vector248: + push $0 + push $248 + jmp alltraps +vector249: + push $0 + push $249 + jmp alltraps +vector250: + push $0 + push $250 + jmp alltraps +vector251: + push $0 + push $251 + jmp alltraps +vector252: + push $0 + push $252 + jmp alltraps +vector253: + push $0 + push $253 + jmp alltraps +vector254: + push $0 + push $254 + jmp alltraps +vector255: + push $0 + push $255 + jmp alltraps + +# vector table +.data +.global vectors +vectors: + .quad vector0 + .quad vector1 + .quad vector2 + .quad vector3 + .quad vector4 + .quad vector5 + .quad vector6 + .quad vector7 + .quad vector8 + .quad vector9 + .quad vector10 + .quad vector11 + .quad vector12 + .quad vector13 + .quad vector14 + .quad vector15 + .quad vector16 + .quad vector17 + .quad vector18 + .quad vector19 + .quad vector20 + .quad vector21 + .quad vector22 + .quad vector23 + .quad vector24 + .quad vector25 + .quad vector26 + .quad vector27 + .quad vector28 + .quad vector29 + .quad vector30 + .quad vector31 + .quad vector32 + .quad vector33 + .quad vector34 + .quad vector35 + .quad vector36 + .quad vector37 + .quad vector38 + .quad vector39 + .quad vector40 + .quad vector41 + .quad vector42 + .quad vector43 + .quad vector44 + .quad vector45 + .quad vector46 + .quad vector47 + .quad vector48 + .quad vector49 + .quad vector50 + .quad vector51 + .quad vector52 + .quad vector53 + .quad vector54 + .quad vector55 + .quad vector56 + .quad vector57 + .quad vector58 + .quad vector59 + .quad vector60 + .quad vector61 + .quad vector62 + .quad vector63 + .quad vector64 + .quad vector65 + .quad vector66 + .quad vector67 + .quad vector68 + .quad vector69 + .quad vector70 + .quad vector71 + .quad vector72 + .quad vector73 + .quad vector74 + .quad vector75 + .quad vector76 + .quad vector77 + .quad vector78 + .quad vector79 + .quad vector80 + .quad vector81 + .quad vector82 + .quad vector83 + .quad vector84 + .quad vector85 + .quad vector86 + .quad vector87 + .quad vector88 + .quad vector89 + .quad vector90 + .quad vector91 + .quad vector92 + .quad vector93 + .quad vector94 + .quad vector95 + .quad vector96 + .quad vector97 + .quad vector98 + .quad vector99 + .quad vector100 + .quad vector101 + .quad vector102 + .quad vector103 + .quad vector104 + .quad vector105 + .quad vector106 + .quad vector107 + .quad vector108 + .quad vector109 + .quad vector110 + .quad vector111 + .quad vector112 + .quad vector113 + .quad vector114 + .quad vector115 + .quad vector116 + .quad vector117 + .quad vector118 + .quad vector119 + .quad vector120 + .quad vector121 + .quad vector122 + .quad vector123 + .quad vector124 + .quad vector125 + .quad vector126 + .quad vector127 + .quad vector128 + .quad vector129 + .quad vector130 + .quad vector131 + .quad vector132 + .quad vector133 + .quad vector134 + .quad vector135 + .quad vector136 + .quad vector137 + .quad vector138 + .quad vector139 + .quad vector140 + .quad vector141 + .quad vector142 + .quad vector143 + .quad vector144 + .quad vector145 + .quad vector146 + .quad vector147 + .quad vector148 + .quad vector149 + .quad vector150 + .quad vector151 + .quad vector152 + .quad vector153 + .quad vector154 + .quad vector155 + .quad vector156 + .quad vector157 + .quad vector158 + .quad vector159 + .quad vector160 + .quad vector161 + .quad vector162 + .quad vector163 + .quad vector164 + .quad vector165 + .quad vector166 + .quad vector167 + .quad vector168 + .quad vector169 + .quad vector170 + .quad vector171 + .quad vector172 + .quad vector173 + .quad vector174 + .quad vector175 + .quad vector176 + .quad vector177 + .quad vector178 + .quad vector179 + .quad vector180 + .quad vector181 + .quad vector182 + .quad vector183 + .quad vector184 + .quad vector185 + .quad vector186 + .quad vector187 + .quad vector188 + .quad vector189 + .quad vector190 + .quad vector191 + .quad vector192 + .quad vector193 + .quad vector194 + .quad vector195 + .quad vector196 + .quad vector197 + .quad vector198 + .quad vector199 + .quad vector200 + .quad vector201 + .quad vector202 + .quad vector203 + .quad vector204 + .quad vector205 + .quad vector206 + .quad vector207 + .quad vector208 + .quad vector209 + .quad vector210 + .quad vector211 + .quad vector212 + .quad vector213 + .quad vector214 + .quad vector215 + .quad vector216 + .quad vector217 + .quad vector218 + .quad vector219 + .quad vector220 + .quad vector221 + .quad vector222 + .quad vector223 + .quad vector224 + .quad vector225 + .quad vector226 + .quad vector227 + .quad vector228 + .quad vector229 + .quad vector230 + .quad vector231 + .quad vector232 + .quad vector233 + .quad vector234 + .quad vector235 + .quad vector236 + .quad vector237 + .quad vector238 + .quad vector239 + .quad vector240 + .quad vector241 + .quad vector242 + .quad vector243 + .quad vector244 + .quad vector245 + .quad vector246 + .quad vector247 + .quad vector248 + .quad vector249 + .quad vector250 + .quad vector251 + .quad vector252 + .quad vector253 + .quad vector254 + .quad vector255 diff --git a/kernel/arch/x86_64/vm.c b/kernel/arch/x86_64/vm.c new file mode 100644 index 0000000..fa23d52 --- /dev/null +++ b/kernel/arch/x86_64/vm.c @@ -0,0 +1,631 @@ +#include "param.h" +#include "types.h" +#include "defs.h" +#include "x86.h" +#include "memlayout.h" +#include "mmu.h" +#include "proc.h" + +int mappages(pml4e_t *pgdir, void *va, addr_t size, addr_t pa, int perm); +static pte_t * walkpgdir(pml4e_t *pml4, const void *va, int alloc); + + +// `cpu` and `proc` are no longer __thread variables; they live at fixed offsets +// in the per-CPU block and are read through %gs at each use. See the long +// comment in include/proc.h for why -- the short version is that __thread means +// per *thread*, the compiler caches the thread pointer across calls on that +// basis, and a kernel thread changes CPU at every sched(). + +// Kernel FS_BASE set by seginit(); read by trapasm.S to restore after user TLS +uint64 kernel_fs_base; +// Single-CPU scratch for syscall_entry FS swap (safe while IF cleared by SYSCALL/SFMASK) +uint64 _sc_rax, _sc_rcx, _sc_rdx; + +static pml4e_t *kpml4; +static pdpe_t *kpdpt; + +struct tassdesc { + uint lim_15_0 : 16; + uint base_15_0 : 16; + uint base_23_16 : 8; + uint type : 4; + uint s : 1; + uint dpl : 2; + uint p : 1; + uint lim_19_16 : 4; + uint avl : 1; + uint l : 1; + uint d : 1; + uint g : 1; + uint base_31_24 : 8; + uint32 base_63_32; + uint32 res; +}; + +void +syscallinit(void) +{ + // SYSRETQ does NOT use USER_CS/USER_DS directly. Per the AMD64/Intel + // convention, it computes CS = STAR[63:48]+16 and SS = STAR[63:48]+8 + // (RPL forced to 3), so STAR[63:48] must hold the *compat* 32-bit user + // code selector (SEG_UCODE32), relying on the GDT's consecutive + // [UCODE32, UDATA, UCODE] layout — NOT USER_CS itself. Using USER_CS + // here made every sysretq load CS=USER_CS+16 (an invalid selector, + // pointing past UCODE into the TSS descriptor) and SS=USER_CS+8. QEMU's + // TCG apparently doesn't validate segment descriptors on SYSRET, so this + // went unnoticed until an interrupt forced a strictly-validated iretq + // with that same bogus CS still loaded, which correctly #GP'd. + wrmsr(MSR_STAR, + ((((uint64)((SEG_UCODE32 << 3) | DPL_USER) << 16) | (uint64)KERNEL_CS) << 32)); + wrmsr(MSR_LSTAR, (addr_t)syscall_entry); + wrmsr(MSR_CSTAR, (addr_t)ignore_sysret); + wrmsr(MSR_SFMASK, FL_TF|FL_DF|FL_IF|FL_IOPL_MASK|FL_AC|FL_NT); +} + +void +seginit(void) +{ + struct segdesc *gdt; + uint *tss; + uint64 addr; + void *local; + struct cpu *c; + + // The per-CPU page, preallocated by the boot processor for every AP. + // + // It must not be kalloc()'d here on an application processor. kalloc takes a + // spinlock; acquire() calls holding(); holding() reads `cpu`, which is a + // __thread variable reached through %fs -- and %fs is set up four lines + // below. So an AP allocating its own page reads the per-CPU pointer before + // the per-CPU pointer exists, and the kernel dies in panic("acquire") with + // every lock appearing to be held. + // + // startothers() fills c->local in before starting each AP, on the boot + // processor, where locking works. + c = &cpus[cpunum()]; + local = c->local; + if (local == 0) { + local = kalloc(); // the boot processor's own, taken normally + memset(local, 0, PGSIZE); + } + + gdt = (struct segdesc*) local; + tss = (uint*) (((char*) local) + 1024); + tss[16] = 0x00680000; + + kernel_fs_base = ((uint64) local) + 2048; + wrmsr(0xC0000100, kernel_fs_base); + + // The TCB self-pointer: the word at %fs:0 must hold the thread pointer. + // + // This is part of the x86-64 TLS ABI and it was missing, which worked only + // by accident. GCC with -mtls-direct-seg-refs compiles `cpu = c` into a + // single `movq %rcx, %fs:-0x10` and never needs the thread pointer as a + // value, so nothing ever read this word. clang materialises it instead -- + // `movq %fs:0x0, %rax; addq $-0x10, %rax; movq %rcx, (%rax)` -- which read + // the zero left by the memset above and stored through address + // 0xfffffffffffffff0, three instructions later. + // + // Writing it costs one store per CPU and removes the dependency on a + // particular compiler's TLS optimisation. + *(uint64 *) kernel_fs_base = kernel_fs_base; + + // Clear the two per-CPU slots before anything can read them. Their offsets + // are PERCPU_CPU and PERCPU_PROC in include/proc.h and are shared with + // trapasm.S, which reads %gs:16 for proc on the way out to user mode. + ((uint64 *) kernel_fs_base)[2] = 0; // struct cpu * (PERCPU_CPU) + ((uint64 *) kernel_fs_base)[3] = 0; // struct proc * (PERCPU_PROC) + + // The same address in GS base, per CPU. + // + // trapasm.S needs to find *this* CPU's kernel FS base on every entry from + // user mode, and it has no register to work from at that instant -- which is + // why it used to read the kernel_fs_base global. That global is written by + // every CPU during its own seginit(), so with more than one processor the + // last writer wins and every CPU restores one CPU's per-CPU block. GS base is + // per-CPU in hardware, so %gs:0 answers the question with no global at all. + // + // Safe because GS belongs entirely to the kernel here: sys_arch_prctl() + // implements ARCH_SET_FS and ARCH_GET_FS only, and musl uses FS for TLS on + // x86-64. See the comment on RESTORE_KERNEL_FS. + wrmsr(0xC0000101, kernel_fs_base); // IA32_GS_BASE + + c->local = local; + + cpu_set(c); + proc_set(0); + + // Two of the entries below are deliberately null descriptors, and neither is + // dead code even though both look it: + // + // SEG_UCODE32 is never loaded, but SYSRETQ computes CS from STAR[63:48]+16 + // and SS from +8, so the *slot* must exist to keep the + // [UCODE32, UDATA, UCODE] triple consecutive. See syscallinit. + // SEG_KCPU is a leftover slot number from 32-bit xv6, where %gs held the + // per-CPU pointer. Schunix uses FS_BASE/TLS instead, but the + // index has to stay occupied or SEG_TSS stops landing at 7. + // + // Deleting either moves every descriptor after it and breaks sysret. + addr = (uint64) tss; + gdt[0] = (struct segdesc) {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + gdt[SEG_KCODE] = SEG(STA_X|STA_R, 0, 0, 0); + gdt[SEG_KDATA] = SEG(STA_W, 0, 0, 0); + gdt[SEG_UCODE32] = (struct segdesc) {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + gdt[SEG_UDATA] = SEG(STA_W, 0, 0, DPL_USER); + gdt[SEG_UCODE] = SEG(STA_X|STA_R, 0, 0, DPL_USER); + gdt[SEG_KCPU] = (struct segdesc) {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + + struct tassdesc *td = (struct tassdesc*)(gdt + SEG_TSS); + td->lim_15_0 = 0x67; + td->base_15_0 = addr & 0xffff; + td->base_23_16 = (addr >> 16) & 0xff; + td->type = STS_T64A; + td->s = 0; + td->dpl = 0; + td->p = 1; + td->lim_19_16 = 0; + td->avl = 0; + td->l = 0; + td->d = 0; + td->g = 0; + td->base_31_24 = (addr >> 24) & 0xff; + td->base_63_32 = (addr >> 32) & 0xffffffff; + td->res = 0; + + lgdt((void*) gdt, (NSEGS+1) * sizeof(struct segdesc)); + ltr(SEG_TSS << 3); +} + +pml4e_t* +setupkvm(void) +{ + pml4e_t *pml4 = (pml4e_t*) kalloc(); + if(pml4 == 0) return 0; + memset(pml4, 0, PGSIZE); + + pml4[PMX(KERNBASE)] = v2p(kpdpt) | PTE_P | PTE_W; + + // The framebuffer is deliberately NOT mapped here. + // + // This used to identity-map 0xfd000000 for 800*600*4 bytes, user-writable, + // into every process that ever existed. Three things were wrong with that. + // The address and the size were both guesses that only hold on QEMU's + // stdvga at one resolution; setupkvm runs before vga_init, so on the first + // call nothing had even read the VBE block yet; and every process could + // scribble on the screen without asking anyone. + // + // Nothing used it -- userspace reaches the framebuffer through + // mmap("/dev/fb0"), which maps the real base at the real size. So it is + // gone, and a process gets the framebuffer exactly when it asks for it. + + return pml4; +} + +void +kvmalloc(void) +{ + kpml4 = (pml4e_t*) kalloc(); + memset(kpml4, 0, PGSIZE); + + kpdpt = (pdpe_t*) kalloc(); + memset(kpdpt, 0, PGSIZE); + + kpml4[0] = v2p(kpdpt) | PTE_P | PTE_W; + kpml4[PMX(KERNBASE)] = v2p(kpdpt) | PTE_P | PTE_W; + + for(uint64 i = 0; i < 4; i++) { + uint64 flags = PTE_PS | PTE_P | PTE_W; + if(i >= 3) { + flags |= PTE_PCD | PTE_PWT; + } + kpdpt[i] = (i << 30) | flags; + } + + switchkvm(); +} + +void +switchuvm(struct proc *p) +{ + pushcli(); + if(p->pgdir == 0) + panic("switchuvm: no pgdir"); + uint *tss = (uint*) (((char*) mycpu()->local) + 1024); + const addr_t stktop = (addr_t)p->kstack + KSTACKSIZE; + tss[1] = (uint)stktop; + tss[2] = (uint)(stktop >> 32); + lcr3(v2p(p->pgdir)); + popcli(); +} + +static pte_t * +walkpgdir(pml4e_t *pml4, const void *va, int alloc) +{ + pml4e_t *pml4e; + pdpe_t *pdp, *pdpe; + pde_t *pd, *pde; + pte_t *pgtab; + + pml4e = &pml4[PMX(va)]; + if(*pml4e & PTE_P) + pdp = (pdpe_t*)P2V(PTE_ADDR(*pml4e)); + else { + if(!alloc || (pdp = (pdpe_t*)kalloc()) == 0) + return 0; + memset(pdp, 0, PGSIZE); + *pml4e = V2P(pdp) | PTE_P | PTE_W | PTE_U; + } + + pdpe = &pdp[PDPX(va)]; + if(*pdpe & PTE_P) + pd = (pde_t*)P2V(PTE_ADDR(*pdpe)); + else { + if(!alloc || (pd = (pde_t*)kalloc()) == 0) + return 0; + memset(pd, 0, PGSIZE); + *pdpe = V2P(pd) | PTE_P | PTE_W | PTE_U; + } + + pde = &pd[PDX(va)]; + if(*pde & PTE_P) + pgtab = (pte_t*)P2V(PTE_ADDR(*pde)); + else { + if(!alloc || (pgtab = (pte_t*)kalloc()) == 0) + return 0; + memset(pgtab, 0, PGSIZE); + *pde = V2P(pgtab) | PTE_P | PTE_W | PTE_U; + } + + return (pte_t*)&pgtab[PTX(va)]; +} + +void +switchkvm(void) +{ + lcr3(v2p(kpml4)); +} + +int +mappages(pml4e_t *pgdir, void *va, addr_t size, addr_t pa, int perm) +{ + char *a, *last; + pte_t *pte; + + a = (char*)PGROUNDDOWN((addr_t)va); + last = (char*)PGROUNDDOWN(((addr_t)va) + size - 1); + for(;;){ + if((pte = walkpgdir(pgdir, a, 1)) == 0) + return -1; + if(*pte & PTE_P) + panic("remap"); + *pte = pa | perm | PTE_P | PTE_U; + invlpg(a); + if(a == last) + break; + a += PGSIZE; + pa += PGSIZE; + } + return 0; +} + +void +vm_unmap_range(pml4e_t *pgdir, uint64 va, uint64 length) +{ + uint64 a = PGROUNDDOWN(va); + uint64 end = PGROUNDUP(va + length); + for (; a < end; a += PGSIZE) { + pte_t *pte = walkpgdir(pgdir, (void*)a, 0); + if (pte && (*pte & PTE_P)) { + uint64 pa = PTE_ADDR(*pte); + kfree((char*)P2V(pa)); + *pte = 0; + invlpg((void*)a); + } + } +} + +// Tear down a mapping without freeing what is underneath it. +// +// vm_unmap_range() kfree()s every page it unmaps, which is right for an +// anonymous mapping and catastrophic for anything shared: the framebuffer is +// device memory that was never allocated, and a shared memory segment's pages +// belong to the segment, not to whichever process happens to be detaching. +// Handing either of those to kfree puts them on the free list while another +// process is still reading and writing them. +void +vm_unmap_norelease(pml4e_t *pgdir, uint64 va, uint64 length) +{ + uint64 a = PGROUNDDOWN(va); + uint64 end = PGROUNDUP(va + length); + for (; a < end; a += PGSIZE) { + pte_t *pte = walkpgdir(pgdir, (void*)a, 0); + if (pte && (*pte & PTE_P)) { + *pte = 0; + invlpg((void*)a); + } + } +} + +// Duplicate an mmap()'d VMA region from src into dst for fork(). +// Framebuffer mappings are re-pointed at the same physical MMIO range +// (shared, never copied/freed); anonymous mappings get fresh pages with +// the parent's contents copied in — mirrors what copyuvm() does for the +// [0, sz) heap range, which never covers mmap()'d addresses. +int +copy_vma_range(pml4e_t *dstpgdir, pml4e_t *srcpgdir, uint64 va, uint64 length, int share) +{ + uint64 a = PGROUNDDOWN(va); + uint64 end = PGROUNDUP(va + length); + for (; a < end; a += PGSIZE) { + if (share) { + // Point the child at the very same physical pages, whatever they are. + // + // This used to special-case the framebuffer and map vga_fb_phys() + // explicitly. Reading the parent's own PTE instead covers device memory + // and shared memory segments with one rule, and cannot disagree with + // what the parent actually has mapped -- which the explicit version + // could, and did, when the address was hardcoded. + pte_t *spte = walkpgdir(srcpgdir, (void*)a, 0); + if (!spte || !(*spte & PTE_P)) + continue; + if (mappages(dstpgdir, (void*)a, PGSIZE, PTE_ADDR(*spte), PTE_FLAGS(*spte)) < 0) + return -1; + continue; + } + pte_t *spte = walkpgdir(srcpgdir, (void*)a, 0); + if (!spte || !(*spte & PTE_P)) + continue; + char *mem = kalloc(); + if (!mem) + return -1; + memmove(mem, (char*)P2V(PTE_ADDR(*spte)), PGSIZE); + if (mappages(dstpgdir, (void*)a, PGSIZE, V2P(mem), PTE_FLAGS(*spte)) < 0) { + kfree(mem); + return -1; + } + } + return 0; +} + +void +inituvm(pde_t *pgdir, char *sinit, uint64 sz) +{ + char *mem; + + if(sz >= PGSIZE) + panic("inituvm: more than a page"); + mem = kalloc(); + memset(mem, 0, PGSIZE); + mappages(pgdir, 0, PGSIZE, v2p(mem), PTE_W|PTE_U); + memmove(mem, sinit, sz); +} + +int +loaduvm(pml4e_t *pgdir, char *addr, struct inode *ip, uint64 offset, uint64 sz) +{ + uint64 done, n, off_in_page; + addr_t pa; + pte_t *pte; + char *cur_page; + + for(done = 0; done < sz; done += n){ + cur_page = (char*)PGROUNDDOWN((uint64)(addr + done)); + off_in_page = (uint64)(addr + done) - (uint64)cur_page; + if((pte = walkpgdir(pgdir, cur_page, 0)) == 0) + panic("loaduvm: address should exist"); + pa = PTE_ADDR(*pte); + n = PGSIZE - off_in_page; + if(n > sz - done) + n = sz - done; + if(readi(ip, 0, (uint64)(P2V(pa) + off_in_page), offset + done, n) != (int)n) + return -1; + } + return 0; +} + +uint64 +allocuvm(pml4e_t *pgdir, uint64 oldsz, uint64 newsz) +{ + char *mem; + addr_t a; + + if(newsz >= KERNBASE) + return 0; + if(newsz < oldsz) + return oldsz; + + a = PGROUNDUP(oldsz); + for(; a < newsz; a += PGSIZE){ + mem = kalloc(); + if(mem == 0){ + deallocuvm(pgdir, newsz, oldsz); + return 0; + } + memset(mem, 0, PGSIZE); + if(mappages(pgdir, (char*)a, PGSIZE, V2P(mem), PTE_W|PTE_U) < 0){ + deallocuvm(pgdir, newsz, oldsz); + kfree(mem); + return 0; + } + } + return newsz; +} + +uint64 +deallocuvm(pml4e_t *pgdir, uint64 oldsz, uint64 newsz) +{ + pte_t *pte; + addr_t a, pa; + + if(newsz >= oldsz) + return oldsz; + + a = PGROUNDUP(newsz); + for(; a < oldsz; a += PGSIZE){ + pte = walkpgdir(pgdir, (char*)a, 0); + if(pte && (*pte & PTE_P) != 0){ + pa = PTE_ADDR(*pte); + if(pa == 0) + panic("kfree"); + if(pa < PHYSTOP) { + char *v = P2V(pa); + kfree(v); + } + *pte = 0; + } + } + return newsz; +} + +void +freevm(pml4e_t *pml4) +{ + uint i, j, k, l; + pdpe_t *pdp; + pde_t *pd; + pte_t *pt; + + if(pml4 == 0) + panic("freevm: no pgdir"); + + for(i = 0; i < (512/2); i++){ + if(pml4[i] & PTE_P){ + pdp = (pdpe_t*)P2V(PTE_ADDR(pml4[i])); + for(j = 0; j < 512; j++){ + if(pdp[j] & PTE_P){ + pd = (pde_t*)P2V(PTE_ADDR(pdp[j])); + for(k = 0; k < 512; k++){ + if(pd[k] & PTE_P) { + pt = (pte_t*)P2V(PTE_ADDR(pd[k])); + for(l = 0; l < 512; l++){ + if(pt[l] & PTE_P) { + addr_t pa = PTE_ADDR(pt[l]); + if(pa < PHYSTOP && pa != 0) { + kfree(P2V(pa)); + } + } + } + kfree((char*)pt); + } + } + kfree((char*)pd); + } + } + kfree((char*)pdp); + } + } + kfree((char*)pml4); +} + +void +clearpteu(pml4e_t *pgdir, char *uva) +{ + pte_t *pte; + pte = walkpgdir(pgdir, uva, 0); + if(pte == 0) + panic("clearpteu"); + *pte &= ~PTE_U; +} + +pml4e_t* +copyuvm(pml4e_t *pgdir, uint64 sz) +{ + pml4e_t *d; + pte_t *pte; + addr_t pa, i, flags; + char *mem; + + if((d = setupkvm()) == 0) + return 0; + for(i = 0; i < sz; i += PGSIZE){ + if((pte = walkpgdir(pgdir, (void*)i, 0)) == 0) + continue; + if(!(*pte & PTE_P)) + continue; + pa = PTE_ADDR(*pte); + flags = PTE_FLAGS(*pte); + + if(pa < PHYSTOP) { + if((mem = kalloc()) == 0) + goto bad; + memmove(mem, (char*)P2V(pa), PGSIZE); + if(mappages(d, (void*)i, PGSIZE, V2P(mem), flags) < 0) { + kfree(mem); + goto bad; + } + } + } + return d; + + bad: + freevm(d); + return 0; +} + +char* +uva2ka(pml4e_t *pgdir, char *uva) +{ + if((addr_t)uva >= KERNBASE) + return 0; + + pte_t *pte; + pte = walkpgdir(pgdir, uva, 0); + if(pte == 0 || (*pte & PTE_P) == 0) + return 0; + if((*pte & PTE_U) == 0) + return 0; + addr_t pa = (*pte) & 0x000FFFFFFFFFF000ull; + // Include uva's in-page offset — callers dereference the returned pointer + // directly at uva's address, not just somewhere within its page. + return (char*)P2V(pa) + ((addr_t)uva & 0xFFF); +} + +int +copyout(pml4e_t *pgdir, addr_t va, void *p, uint64 len) +{ + char *buf, *pa0; + addr_t n, va0; + + buf = (char*)p; + while(len > 0){ + va0 = PGROUNDDOWN(va); + pa0 = uva2ka(pgdir, (char*)va0); + + if(pa0 == 0) { + return -1; + } + + n = PGSIZE - (va - va0); + if(n > len) + n = len; + memmove(pa0 + (va - va0), buf, n); + + len -= n; + buf += n; + va += n; + } + return 0; +} + +int +copyin(pml4e_t *pgdir, addr_t srcva, void *dst, uint64 len) +{ + uint64 n, va0; + char *pa0; + char *dst_buf = (char*)dst; + + while(len > 0){ + va0 = PGROUNDDOWN(srcva); + pa0 = uva2ka(pgdir, (char*)va0); + if(pa0 == 0) + return -1; + n = PGSIZE - (srcva - va0); + if(n > len) + n = len; + memmove(dst_buf, pa0 + (srcva - va0), n); + len -= n; + dst_buf += n; + srcva += n; + } + return 0; +} diff --git a/kernel/core/console.c b/kernel/core/console.c new file mode 100644 index 0000000..6062e24 --- /dev/null +++ b/kernel/core/console.c @@ -0,0 +1,810 @@ +#include +#include "types.h" +#include "defs.h" +#include "theme.h" +#include "irq.h" +#include "param.h" +#include "traps.h" +#include "spinlock.h" +#include "sleeplock.h" +#include "fs.h" +#include "file.h" +#include "memlayout.h" +#include "mmu.h" +#include "proc.h" +#include "posix.h" +#include "x86.h" + +void consputc(int); +// Set by panic() once the crash screen has been painted: after that, consputc +// spins instead of writing, so nothing else can scribble over the report. +int panicked = 0; + +// When set, console output goes to the serial line only and the framebuffer +// is left alone. +// +// **Starts set, on purpose.** The display is the product; the serial line is +// the diagnostics. A machine that boots by printing "pci: 6 devices" and +// "vga: display controller 1234:1111, aperture fd000000" to the screen the +// user is looking at is showing them a developer's log, and that single fact +// is most of what makes an OS look like a hobby project. +// +// Nothing is lost: every one of those lines still goes out the UART, which is +// where tools/bootcheck.ps1 reads them from and where you want them when +// something goes wrong. console_takeover() clears this once userspace is up +// and the display genuinely belongs to the console. The F8 verbose boot never +// sets it, so "show me everything" still works. +int cons_serial_only = 1; +extern void vga_screen_reset(void); + +// Text colour used when drawing to the framebuffer. Changed by SGR escapes +// (see sgr() below); reset to the default by ESC[0m and ESC c. +// +// This was initialised to 7, a leftover from when it was a VGA text-mode +// attribute byte. vga_putc takes a 32-bit RGB value, so 7 meant "very dark +// blue" -- it looked black against the cream background purely by accident. +static uint32 cons_fg = SCH_FG; + +static struct { + struct spinlock lock; + int locking; +} cons; + +static char digits[] = "0123456789abcdef"; + +static void +print_int(int64 xx, int base, int sign) +{ + char buf[64]; + int i; + uint64 x; + + if(sign && (sign = (xx < 0))) + x = -xx; + else + x = xx; + + i = 0; + do { + buf[i++] = digits[x % base]; + } while((x /= base) != 0); + + if(sign) + buf[i++] = '-'; + + while(--i >= 0) + consputc(buf[i]); +} + +void +cprintf(char *fmt, ...) +{ + va_list ap; + int i, c, locking; + char *s; + + va_start(ap, fmt); + + locking = cons.locking; + if (locking) + acquire(&cons.lock); + + if (fmt == 0) + panic("null fmt"); + + for (i = 0; (c = fmt[i] & 0xff) != 0; i++) { + if (c != '%') { + consputc(c); + continue; + } + c = fmt[++i] & 0xff; + if (c == 0) + break; + switch(c) { + case 'd': + print_int(va_arg(ap, int), 10, 1); + break; + case 'u': + print_int(va_arg(ap, uint32), 10, 0); + break; + case 'l': + print_int(va_arg(ap, int64), 10, 1); + break; + case 'x': + print_int(va_arg(ap, uint32), 16, 0); + break; + case 'p': + print_int(va_arg(ap, uint64), 16, 0); + break; + case 'b': + print_int(va_arg(ap, uint32), 2, 0); + break; + case 's': + if ((s = va_arg(ap, char*)) == 0) + s = "(null)"; + while (*s) + consputc(*(s++)); + break; + case '%': + consputc('%'); + break; + default: + consputc('%'); + consputc(c); + break; + } + } + + if (locking) + release(&cons.lock); +} + +// Hand the console over to panic(). +// +// Two things change. The lock stops being taken, so a panic can report itself +// from anywhere — including from inside a routine that already holds it. And +// output stops going to the framebuffer, because from here on the framebuffer +// belongs to the crash screen: without this the report would scroll straight +// over the panel that is trying to display it. +void +cons_panic_mode(void) +{ + cons.locking = 0; + cons_serial_only = 1; +} + +// Hand the display over to the text console. +// +// Called once, when userspace is far enough along that the screen should stop +// being a boot splash and start being a terminal. Until this runs the kernel +// has been talking to the serial line only (see cons_serial_only), so this is +// the moment the framebuffer changes owner -- which is why it clears first +// rather than scrolling the shell prompt in underneath the splash. +void +console_takeover(void) +{ + if (!cons_serial_only) + return; // verbose boot: the console never left + cons_serial_only = 0; + vga_screen_reset(); +} + +// Foreground process group of the console, i.e. the controlling terminal. +// tcgetpgrp()/tcsetpgrp() read and write this through TIOCGPGRP/TIOCSPGRP in +// sys_fs.c. Initialised to 1 -- init's process group -- so the first shell, +// which inherits that group, already sees itself as the foreground job and +// does not immediately stop itself with SIGTTIN. +int tty_fg_pgrp = 1; + +#define BACKSPACE 0x100 + +// --------------------------------------------------------------------------- +// Output: an ANSI escape parser, and UTF-8 +// +// The console used to print every byte it was given, which meant every escape +// sequence any program emitted appeared on screen as literal text -- `clear` +// drew "^[[2J^[[H" and left the screen exactly as it found it. Colour, cursor +// addressing, and anything full-screen were all equally impossible. +// +// The serial line does NOT go through any of this. On the other end of it is +// a real terminal emulator that already understands ANSI far better than this +// ever will, so escapes are passed through to it verbatim and interpreted +// only for the framebuffer. Two consumers, two right answers. +// --------------------------------------------------------------------------- + +// The sixteen ANSI colours as RGB, for a linear framebuffer. +// +// consoleinit() used to program these into the VGA DAC with set_vga_palette(), +// which does nothing at all in a 32bpp linear mode -- there is no palette to +// index. The colours had to live somewhere real, and this is it. +static const uint32 ansi_rgb[16] = { + 0x000000, 0xAA0000, 0x00AA00, 0xAA5500, // black red green yellow(brown) + 0x0000AA, 0xAA00AA, 0x00AAAA, 0xAAAAAA, // blue magenta cyan white + 0x555555, 0xFF5555, 0x55FF55, 0xFFFF55, // bright versions + 0x5555FF, 0xFF55FF, 0x55FFFF, 0xFFFFFF, +}; + +#define CONS_DEFAULT_FG SCH_FG // see include/theme.h +#define CONS_DEFAULT_BG SCH_BG + +static uint32 cons_bg = CONS_DEFAULT_BG; +static int cons_bold = 0; + +// Escape parser state. +enum { E_NORMAL, E_ESC, E_CSI }; +static int esc_state = E_NORMAL; +static int esc_parm[8]; +static int esc_nparm; +static int esc_private; // saw '?' after CSI +static int saved_col, saved_row; + +// UTF-8 decoder state. A codepoint arrives as up to four bytes and has to be +// reassembled before it can be turned into a glyph. +static uint32 u8_cp; +static int u8_need; + +// --------------------------------------------------------------------------- +// Unicode -> glyph +// +// The font's upper half is CP437, not Latin-1, so a codepoint cannot be used +// as an index. It looks like it can -- font.h's own comments say so, labelling +// each glyph with the Unicode character of the same number -- but the bitmaps +// disagree: index 0xE9 draws a capital theta, and rendering U+00E9 there turns +// "cafe" into "caf" followed by a theta. Every accented character came out as +// an unrelated symbol, consistently enough to look almost deliberate. +// +// So: a table. Entry i covers codepoint 0x80 + i, and 0 means "this font has +// no glyph for that". The C1 range (0x80-0x9F) has no printable characters at +// all, hence the leading run of zeros. +// --------------------------------------------------------------------------- +static const uchar latin1_to_cp437[128] = { + /* 0x80-0x9F: C1 controls, nothing to draw */ + 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, + /* 0xA0 */ 0x20, 0xAD, 0x9B, 0x9C, 0, 0x9D, 0, 0x15, + /* 0xA8 */ 0, 0, 0xA6, 0xAE, 0xAA, 0x2D, 0, 0, + /* 0xB0 */ 0xF8, 0xF1, 0xFD, 0, 0, 0xE6, 0x14, 0xFA, + /* 0xB8 */ 0, 0, 0xA7, 0xAF, 0xAC, 0xAB, 0, 0xA8, + /* 0xC0 */ 0, 0, 0, 0, 0x8E, 0x8F, 0x92, 0x80, + /* 0xC8 */ 0, 0x90, 0, 0, 0, 0, 0, 0, + /* 0xD0 */ 0, 0xA5, 0, 0, 0, 0, 0x99, 0, + /* 0xD8 */ 0, 0, 0, 0, 0x9A, 0, 0, 0xE1, + /* 0xE0 */ 0x85, 0xA0, 0x83, 0, 0x84, 0x86, 0x91, 0x87, + /* 0xE8 */ 0x8A, 0x82, 0x88, 0x89, 0x8D, 0xA1, 0x8C, 0x8B, + /* 0xF0 */ 0, 0xA4, 0x95, 0xA2, 0x93, 0, 0x94, 0xF6, + /* 0xF8 */ 0, 0x97, 0xA3, 0x96, 0x81, 0, 0, 0x98, +}; + +// The handful of characters above U+00FF that CP437 does have. Box drawing +// and block elements matter because they are what every text-mode program +// draws frames and progress bars out of. +struct cp_pair { uint32 cp; uchar glyph; }; +static const struct cp_pair cp437_extra[] = { + { 0x2022, 0x07 }, /* bullet */ + { 0x2500, 0xC4 }, { 0x2502, 0xB3 }, /* light horizontal/vertical */ + { 0x250C, 0xDA }, { 0x2510, 0xBF }, + { 0x2514, 0xC0 }, { 0x2518, 0xD9 }, + { 0x251C, 0xC3 }, { 0x2524, 0xB4 }, + { 0x252C, 0xC2 }, { 0x2534, 0xC1 }, { 0x253C, 0xC5 }, + { 0x2550, 0xCD }, { 0x2551, 0xBA }, /* double */ + { 0x2554, 0xC9 }, { 0x2557, 0xBB }, + { 0x255A, 0xC8 }, { 0x255D, 0xBC }, + { 0x2580, 0xDF }, { 0x2584, 0xDC }, { 0x2588, 0xDB }, + { 0x258C, 0xDD }, { 0x2590, 0xDE }, + { 0x2591, 0xB0 }, { 0x2592, 0xB1 }, { 0x2593, 0xB2 }, + { 0x25A0, 0xFE }, /* black square */ + { 0x221A, 0xFB }, { 0x221E, 0xEC }, /* root, infinity */ + { 0x2260, 0, }, { 0x2264, 0xF3 }, { 0x2265, 0xF2 }, + { 0x2248, 0xF7 }, { 0x2261, 0xF0 }, +}; + +// Returns the glyph to draw for a codepoint, or 0 if there is none. +static int +glyph_for(uint32 cp) +{ + if (cp < 0x80) + return (int)cp; // ASCII is CP437's lower half + if (cp < 0x100) + return latin1_to_cp437[cp - 0x80]; + + for (unsigned i = 0; i < NELEM(cp437_extra); i++) + if (cp437_extra[i].cp == cp) + return cp437_extra[i].glyph; + return 0; +} + +static void +sgr(void) +{ + for (int i = 0; i < esc_nparm; i++) { + int p = esc_parm[i]; + + if (p == 0) { + cons_fg = CONS_DEFAULT_FG; + cons_bg = CONS_DEFAULT_BG; + cons_bold = 0; + } else if (p == 1) { + cons_bold = 1; + // Re-apply brightness to a colour already chosen. + for (int j = 0; j < 8; j++) + if (cons_fg == ansi_rgb[j]) { cons_fg = ansi_rgb[j + 8]; break; } + } else if (p == 22) { + cons_bold = 0; + } else if (p >= 30 && p <= 37) { + cons_fg = ansi_rgb[(p - 30) + (cons_bold ? 8 : 0)]; + } else if (p == 39) { + cons_fg = CONS_DEFAULT_FG; + } else if (p >= 40 && p <= 47) { + cons_bg = ansi_rgb[p - 40]; + } else if (p == 49) { + cons_bg = CONS_DEFAULT_BG; + } else if (p >= 90 && p <= 97) { + cons_fg = ansi_rgb[(p - 90) + 8]; + } else if (p >= 100 && p <= 107) { + cons_bg = ansi_rgb[(p - 100) + 8]; + } + } + vga_set_bg(cons_bg); +} + +// One final byte of a CSI sequence, with esc_parm[] already filled in. +static void +csi_dispatch(int final) +{ + int col, row; + int n = esc_nparm > 0 ? esc_parm[0] : 0; + int n1 = n ? n : 1; // most sequences default to 1 + + vga_cursor_get(&col, &row); + + switch (final) { + case 'A': vga_cursor_set(col, row - n1); break; + case 'B': vga_cursor_set(col, row + n1); break; + case 'C': vga_cursor_set(col + n1, row); break; + case 'D': vga_cursor_set(col - n1, row); break; + case 'E': vga_cursor_set(0, row + n1); break; + case 'F': vga_cursor_set(0, row - n1); break; + case 'G': vga_cursor_set(n1 - 1, row); break; + case 'd': vga_cursor_set(col, n1 - 1); break; + + case 'H': case 'f': { + int r = esc_nparm > 0 && esc_parm[0] ? esc_parm[0] : 1; + int c = esc_nparm > 1 && esc_parm[1] ? esc_parm[1] : 1; + vga_cursor_set(c - 1, r - 1); // ANSI counts from 1 + break; + } + + case 'J': vga_erase_display(n); break; + case 'K': vga_erase_line(n); break; + + case 'm': sgr(); break; + + case 's': saved_col = col; saved_row = row; break; + case 'u': vga_cursor_set(saved_col, saved_row); break; + + // Cursor visibility (DECTCEM) and the other private modes. There is no + // hardware cursor being drawn, so there is nothing to show or hide -- + // swallowing them silently is the correct behaviour, and far better than + // printing "?25l" into the middle of someone's output. + case 'h': case 'l': break; + + default: break; // unknown: drop it, do not print it + } +} + +// Feed one output byte to the framebuffer side. Returns nothing; the serial +// side has already had the raw byte. +static void +vga_feed(int c) +{ + switch (esc_state) { + case E_ESC: + if (c == '[') { + esc_state = E_CSI; + esc_nparm = 0; + esc_private = 0; + for (int i = 0; i < 8; i++) esc_parm[i] = 0; + return; + } + esc_state = E_NORMAL; + if (c == 'c') { // RIS -- full reset + cons_fg = CONS_DEFAULT_FG; + cons_bg = CONS_DEFAULT_BG; + vga_screen_reset(); + } + return; // other two-byte escapes: ignored + + case E_CSI: + if (c == '?') { esc_private = 1; return; } + if (c >= '0' && c <= '9') { + if (esc_nparm == 0) esc_nparm = 1; + if (esc_nparm <= 8) + esc_parm[esc_nparm - 1] = esc_parm[esc_nparm - 1] * 10 + (c - '0'); + return; + } + if (c == ';') { + if (esc_nparm < 8) esc_nparm++; + return; + } + esc_state = E_NORMAL; + if (!esc_private) + csi_dispatch(c); + return; + } + + // E_NORMAL + if (c == '\x1b') { esc_state = E_ESC; return; } + + // UTF-8 continuation. A codepoint the font cannot draw becomes '?' -- the + // one thing it must not become is a *different* character, which is exactly + // what indexing the font by codepoint did. + if (u8_need > 0) { + if ((c & 0xC0) != 0x80) { // malformed: abandon the sequence + u8_need = 0; + vga_putc('?', cons_fg); + return; + } + u8_cp = (u8_cp << 6) | (c & 0x3F); + if (--u8_need > 0) + return; + int g = glyph_for(u8_cp); + vga_putc(g ? g : '?', cons_fg); + return; + } + + if (c >= 0xF0) { u8_cp = c & 0x07; u8_need = 3; return; } + else if (c >= 0xE0) { u8_cp = c & 0x0F; u8_need = 2; return; } + else if (c >= 0xC0) { u8_cp = c & 0x1F; u8_need = 1; return; } + else if (c >= 0x80) { vga_putc('?', cons_fg); return; } // stray continuation + + vga_putc(c, cons_fg); +} + +void +consputc(int c) +{ + if (panicked) { + cli(); + for(;;) hlt(); + } + + if (c == BACKSPACE) { + uartputc('\b'); uartputc(' '); uartputc('\b'); + if (!cons_serial_only) + vga_putc(c, cons_fg); + return; + } + + uartputc(c); + if (!cons_serial_only) + vga_feed(c & 0xff); +} + +#define INPUT_BUF 256 +struct { + struct spinlock lock; + char buf[INPUT_BUF]; + uint r; + uint w; + uint e; + uint abort_read; + // Set by EOF (Ctrl-D) on an empty line. Canonical-mode end of input is not + // a character -- it is the *absence* of one -- so it cannot be signalled by + // putting something in the buffer. The old code pushed a literal ^D in and + // had consoleread() pick it back out, which meant a ^D typed mid-line was + // indistinguishable from one typed alone. + uint eof; +} input; + +#define C(x) ((x)-'@') + +// --------------------------------------------------------------------------- +// The line discipline +// +// TCGETS used to return a hardcoded set of flags and TCSETS threw its argument +// away, so the terminal was permanently in one mode: line-at-a-time, always +// echoing. Anything wanting a keypress at a time -- a pager, an editor, a +// shell doing its own line editing, which is most of what zsh is -- had no way +// to ask for one. This is the real thing: a struct programs can write, that +// changes what the kernel actually does. +// --------------------------------------------------------------------------- + +static struct termios tio = { + .c_iflag = ICRNL, + .c_oflag = OPOST | ONLCR, + .c_cflag = CS8 | CREAD | CLOCAL, + .c_lflag = ISIG | ICANON | ECHO | ECHOE, + .c_cc = { + [VINTR] = C('C'), [VQUIT] = C('\\'), [VERASE] = '\x7f', [VKILL] = C('U'), + [VEOF] = C('D'), [VSUSP] = C('Z'), [VWERASE] = C('W'), [VMIN] = 1, + }, + .c_ispeed = 15, .c_ospeed = 15, +}; + +void +cons_get_termios(struct termios *out) +{ + acquire(&input.lock); + *out = tio; + release(&input.lock); +} + +void +cons_set_termios(const struct termios *in) +{ + acquire(&input.lock); + tio = *in; + // Switching out of canonical mode has to hand over whatever has been typed + // but not yet submitted, or a shell that turns off ICANON mid-line silently + // eats the characters already in the edit buffer. + if (!(tio.c_lflag & ICANON)) + input.w = input.e; + release(&input.lock); + wakeup(&input.r); +} + +static void +echo(int c) +{ + if (c == '\n' || c == '\r') { + if ((tio.c_lflag & ECHO) || (tio.c_lflag & ECHONL)) + consputc('\n'); + return; + } + if (!(tio.c_lflag & ECHO)) + return; + // Control characters echo as ^X, the way every other terminal shows them. + if (c < 32 && c != '\t') { + consputc('^'); + consputc(c + '@'); + } else { + consputc(c); + } +} + +// Rub out the last character of the current line. +static void +erase_one(void) +{ + if (input.e == input.w) + return; + input.e--; + if (tio.c_lflag & ECHOE) + consputc(BACKSPACE); +} + +void +consoleintr(int (*getc)(void)) +{ + int c; + + acquire(&input.lock); + while((c = getc()) >= 0){ + if (c == 0) + continue; + + // Input mapping, before anything looks at the character. + if (tio.c_iflag & ISTRIP) c &= 0x7f; + if (c == '\r') { + if (tio.c_iflag & IGNCR) continue; + if (tio.c_iflag & ICRNL) c = '\n'; + } else if (c == '\n' && (tio.c_iflag & INLCR)) { + c = '\r'; + } + + // Signals. These are the characters that must not reach the reader. + // + // Ctrl-C used to do `myproc()->killed = 1` -- and `proc` in an interrupt + // handler is whatever happened to be running on this CPU when the key was + // pressed, which is almost never the program the user is looking at. It + // also set a kill flag rather than raising a signal, so nothing could + // catch it and the shell never learned the job had died. Both wrong; + // both fixed by sending a real signal to the foreground process group, + // which the kernel has been tracking in tty_fg_pgrp all along. + if (tio.c_lflag & ISIG) { + int sig = 0; + if (c == tio.c_cc[VINTR]) sig = SIGINT; + else if (c == tio.c_cc[VQUIT]) sig = SIGQUIT; + else if (c == tio.c_cc[VSUSP]) sig = SIGTSTP; + if (sig) { + echo(c); + input.e = input.w = input.r; // discard the partial line + input.abort_read = 1; + release(&input.lock); + sigsend_pgrp(tty_fg_pgrp, sig); + acquire(&input.lock); + wakeup(&input.r); + continue; + } + } + + // Ctrl-P: dump the process table. Not POSIX, but it is the one debugging + // aid that works when everything else has stopped responding. + if (c == C('P')) { + procdump(); + continue; + } + + if (tio.c_lflag & ICANON) { + if (c == tio.c_cc[VERASE] || c == C('H')) { + erase_one(); + continue; + } + if (c == tio.c_cc[VKILL]) { + while (input.e != input.w && + input.buf[(input.e-1) % INPUT_BUF] != '\n') + erase_one(); + continue; + } + if (c == tio.c_cc[VWERASE]) { + while (input.e != input.w && + input.buf[(input.e-1) % INPUT_BUF] == ' ') + erase_one(); + while (input.e != input.w && + input.buf[(input.e-1) % INPUT_BUF] != ' ') + erase_one(); + continue; + } + if (c == tio.c_cc[VEOF]) { + if (input.e == input.w) + input.eof = 1; // empty line: end of input + input.w = input.e; // otherwise just submit what is there + wakeup(&input.r); + continue; + } + } + + if (input.e - input.r >= INPUT_BUF) + continue; // buffer full: drop it + + // Everything else goes in, including bytes above 127 -- those are UTF-8 + // continuation bytes, and the old `c >= 32 && c <= 126` test silently + // refused every non-ASCII character anyone tried to type. + input.buf[input.e++ % INPUT_BUF] = c; + echo(c); + + // Canonical mode hands over a line at a time; raw mode hands over + // everything as it arrives. + if (!(tio.c_lflag & ICANON) || + c == '\n' || input.e == input.r + INPUT_BUF) { + input.w = input.e; + wakeup(&input.r); + } + } + release(&input.lock); +} + +int +consoleread(struct inode *ip, uint off, char *dst, int n) +{ + uint target; + int c; + static uint32 prng_seed = 0x9A7B3C1D; + + if (ip->minor == 2) { + return 0; + } + + if (ip->minor == 4) { + for (int i = 0; i < n; i++) { + prng_seed ^= prng_seed << 13; + prng_seed ^= prng_seed >> 17; + prng_seed ^= prng_seed << 5; + dst[i] = prng_seed & 0xFF; + } + return n; + } + + iunlock(ip); + target = n; + acquire(&input.lock); + while(n > 0){ + while(input.r == input.w){ + if (input.eof) { + input.eof = 0; + release(&input.lock); + ilock(ip); + return 0; // end of input + } + if (myproc()->killed || input.abort_read) { + input.abort_read = 0; + release(&input.lock); + ilock(ip); + return -1; + } + sleep(&input.r, &input.lock); + } + c = input.buf[input.r++ % INPUT_BUF]; + *dst++ = c; + --n; + + // Canonical mode returns a line at a time. Raw mode returns whatever has + // arrived: a program that asked for a keypress must not be made to wait + // for a newline that is never coming. + if (tio.c_lflag & ICANON) { + if (c == '\n') + break; + } else { + if (target - n >= (uint)(tio.c_cc[VMIN] ? tio.c_cc[VMIN] : 1)) + break; + } + } + release(&input.lock); + ilock(ip); + + return target - n; +} + +// Nonzero if a completed input line is waiting — poll(2)'s POLLIN answer +// for the console tty. Mirrors the r/w cursors consoleread() blocks on. +int +console_input_ready(void) +{ + int ready; + + acquire(&input.lock); + ready = input.r != input.w; + release(&input.lock); + return ready; +} + +int +consolewrite(struct inode *ip, uint off, char *buf, int n) +{ + int i; + + if (ip->minor == 2 || ip->minor == 3 || ip->minor == 4) { + return n; + } + + iunlock(ip); + acquire(&cons.lock); + for(i = 0; i < n; i++) + consputc(buf[i] & 0xff); + release(&cons.lock); + ilock(ip); + + return n; +} + +void +disable_blink(void) +{ + inb(0x3DA); + outb(0x3C0, 0x10 | 0x20); + uchar dummy = inb(0x3C1); + outb(0x3C0, dummy & ~0x08); +} + +static void +set_vga_palette(uchar index, uchar r, uchar g, uchar b) +{ + outb(0x3C6, 0xFF); + outb(0x3C8, index); + outb(0x3C9, r & 0x3F); + outb(0x3C9, g & 0x3F); + outb(0x3C9, b & 0x3F); +} + +// The keyboard handler lives in kbd.c, but the console is what owns the +// keyboard as an input source, so this is where the line gets claimed. +static void +kbd_irq(void *arg) +{ + (void)arg; + kbdintr(); +} + +void +consoleinit(void) +{ + initlock(&cons.lock, "console"); + initlock(&input.lock, "input"); + + memset(&input, 0, sizeof(input)); + + devsw[CONSOLE].write = consolewrite; + devsw[CONSOLE].read = consoleread; + cons.locking = 1; + + disable_blink(); + + set_vga_palette(0, 0, 0, 0); // Black + set_vga_palette(1, 0, 0, 42); // Blue + set_vga_palette(2, 0, 42, 0); // Green + set_vga_palette(3, 0, 42, 42); // Cyan + set_vga_palette(4, 42, 0, 0); // Red + set_vga_palette(5, 42, 0, 42); // Magenta + set_vga_palette(6, 42, 21, 0); // Brown + set_vga_palette(7, 42, 42, 42); // Light Gray + set_vga_palette(8, 21, 21, 21); // Dark Gray + set_vga_palette(9, 21, 21, 63); // Light Blue + set_vga_palette(10, 21, 63, 21); // Light Green + set_vga_palette(11, 21, 63, 63); // Light Cyan + set_vga_palette(12, 63, 21, 21); // Light Red + set_vga_palette(13, 21, 21, 63); // Light Magenta + set_vga_palette(14, 63, 63, 21); // Yellow + set_vga_palette(15, 63, 63, 63); // White + + irq_register(IRQ_KBD, kbd_irq, 0, "ps2-kbd", 0); +} diff --git a/kernel/core/dma.c b/kernel/core/dma.c new file mode 100644 index 0000000..d926db7 --- /dev/null +++ b/kernel/core/dma.c @@ -0,0 +1,191 @@ +// Physically contiguous memory for devices. See include/dma.h. + +#include "types.h" +#include "defs.h" +#include "param.h" +#include "memlayout.h" +#include "mmu.h" +#include "spinlock.h" +#include "dma.h" + +#define DMA_GRAIN 64 // one cache line; also xHCI's ring alignment +#define NEXTENT 64 // free extents we can track + +struct extent { + addr_t base; // kernel virtual address + uint64 len; +}; + +static struct { + struct spinlock lock; + struct extent free[NEXTENT]; + int n; // free extents, kept sorted by base + addr_t lo, hi; + uint64 total; +} arena; + +static uint64 +roundup(uint64 v, uint64 to) +{ + return (v + to - 1) & ~(to - 1); +} + +void +dma_init(void *vstart, void *vend) +{ + initlock(&arena.lock, "dma"); + arena.lo = roundup((addr_t)vstart, PGSIZE); + arena.hi = (addr_t)vend & ~(addr_t)(PGSIZE - 1); + if(arena.hi <= arena.lo) + panic("dma_init: empty arena"); + arena.free[0].base = arena.lo; + arena.free[0].len = arena.hi - arena.lo; + arena.n = 1; + arena.total = arena.free[0].len; +} + +// Remove free[i], shifting the tail down. +static void +drop(int i) +{ + int j; + for(j = i; j + 1 < arena.n; j++) + arena.free[j] = arena.free[j+1]; + arena.n--; +} + +// Insert a free extent at index i, shifting the tail up. Returns 0 if the +// table is full, in which case the memory is leaked rather than corrupting +// the list -- a leak is recoverable by rebooting, a corrupt free list hands +// two drivers the same ring. +static int +insert(int i, addr_t base, uint64 len) +{ + int j; + + if(arena.n >= NEXTENT) + return 0; + for(j = arena.n; j > i; j--) + arena.free[j] = arena.free[j-1]; + arena.free[i].base = base; + arena.free[i].len = len; + arena.n++; + return 1; +} + +void * +dma_alloc(uint64 size, uint64 align) +{ + int i; + addr_t start; + + if(size == 0) + return 0; + if(align < DMA_GRAIN) + align = DMA_GRAIN; + if(align & (align - 1)) + panic("dma_alloc: alignment not a power of two"); + size = roundup(size, DMA_GRAIN); + + acquire(&arena.lock); + + for(i = 0; i < arena.n; i++){ + addr_t base = arena.free[i].base; + uint64 len = arena.free[i].len; + uint64 lead; + + start = roundup(base, align); + lead = start - base; + if(lead + size > len) + continue; + + // Three cases: the request consumes the extent, sits at its head, sits + // at its tail, or sits in the middle and splits it in two. + if(lead == 0 && size == len){ + drop(i); + } else if(lead == 0){ + arena.free[i].base += size; + arena.free[i].len -= size; + } else if(lead + size == len){ + arena.free[i].len = lead; + } else { + uint64 tail_len = len - lead - size; + arena.free[i].len = lead; + if(!insert(i + 1, start + size, tail_len)){ + // No room to record the tail. Give the whole extent to the caller + // rather than lose track of where the tail was. + arena.free[i].len = len; + continue; + } + } + + release(&arena.lock); + memset((void*)start, 0, size); + return (void*)start; + } + + release(&arena.lock); + cprintf("dma_alloc: out of DMA memory (wanted %d bytes, %d free)\n", + (int)size, (int)dma_free_bytes()); + return 0; +} + +void +dma_free(void *p, uint64 size) +{ + addr_t base = (addr_t)p; + int i; + + if(p == 0) + return; + size = roundup(size, DMA_GRAIN); + if(base < arena.lo || base + size > arena.hi) + panic("dma_free: not DMA memory"); + + acquire(&arena.lock); + + // Find the first extent that starts after this block, so the list stays + // sorted and neighbours end up adjacent for coalescing. + for(i = 0; i < arena.n && arena.free[i].base < base; i++) + ; + + if(i > 0 && arena.free[i-1].base + arena.free[i-1].len == base){ + arena.free[i-1].len += size; // merge with the block before + i--; + } else if(!insert(i, base, size)){ + release(&arena.lock); + cprintf("dma_free: extent table full, leaking %d bytes\n", (int)size); + return; + } + + if(i + 1 < arena.n && + arena.free[i].base + arena.free[i].len == arena.free[i+1].base){ + arena.free[i].len += arena.free[i+1].len; // and with the block after + drop(i + 1); + } + + release(&arena.lock); +} + +addr_t +dma_phys(const void *p) +{ + return V2P((addr_t)p); +} + +uint64 +dma_free_bytes(void) +{ + uint64 n = 0; + int i; + + for(i = 0; i < arena.n; i++) + n += arena.free[i].len; + return n; +} + +uint64 +dma_total_bytes(void) +{ + return arena.total; +} diff --git a/kernel/core/kalloc.c b/kernel/core/kalloc.c new file mode 100644 index 0000000..82aeecf --- /dev/null +++ b/kernel/core/kalloc.c @@ -0,0 +1,124 @@ +// Physical memory allocator, intended to allocate +// memory for user processes, kernel stacks, page table pages, +// and pipe buffers. Allocates 4096-byte pages. + +#include "types.h" +#include "defs.h" +#include "param.h" +#include "memlayout.h" +#include "mmu.h" +#include "spinlock.h" + +void freerange(void *vstart, void *vend); +extern char end[]; // first address after kernel loaded from ELF file + + +struct run { + struct run *next; +}; + +struct { + struct spinlock lock; + int use_lock; + struct run *freelist; +} kmem; + +// Catch kmem.freelist corruption at the point it's touched, rather than +// letting a bad pointer be silently returned by kalloc() and crash whatever +// code dereferences it minutes later on an unrelated call stack. +static void +check_run(struct run *r, char *where) +{ + if(r == 0) + return; + if((addr_t)r % PGSIZE || (char*)r < end || V2P(r) >= PHYSTOP){ + cprintf("kalloc: corrupt freelist entry %p at %s\n", r, where); + panic("kalloc: corrupt freelist"); + } +} + +void +kinit1(void *vstart, void *vend) +{ + initlock(&kmem.lock, "kmem"); + kmem.use_lock = 0; + kmem.freelist = 0; // empty + freerange(vstart, vend); +} + +void +kinit2() +{ + kmem.use_lock = 1; +} + +void +freerange(void *vstart, void *vend) +{ + char *p; + p = (char*)PGROUNDUP((addr_t)vstart); + for(; p + PGSIZE <= (char*)vend; p += PGSIZE) + kfree(p); +} + +// Free the page of physical memory pointed at by v, +// which normally should have been returned by a +// call to kalloc(). (The exception is when +// initializing the allocator; see kinit above.) +void +kfree(char *v) +{ + struct run *r; + + if((addr_t)v % PGSIZE || v < end || V2P(v) >= PHYSTOP) + panic("kfree"); + + // Fill with junk to catch dangling refs. + memset(v, 1, PGSIZE); + + if(kmem.use_lock) + acquire(&kmem.lock); + check_run(kmem.freelist, "kfree entry"); + r = (struct run*)v; + r->next = kmem.freelist; + kmem.freelist = r; + if(kmem.use_lock) + release(&kmem.lock); +} + +// Allocate one 4096-byte page of physical memory. +// Returns a pointer that the kernel can use. +// Returns 0 if the memory cannot be allocated. +char* +kalloc(void) +{ + struct run *r; + + if(kmem.use_lock) + acquire(&kmem.lock); + check_run(kmem.freelist, "kalloc entry"); + r = kmem.freelist; + if(r) + kmem.freelist = r->next; + + if(kmem.use_lock) + release(&kmem.lock); + return (char*)r; +} + +// Total free physical memory, in bytes. Walks the free list; only /proc +// readers call it, so the O(n) scan is not on any hot path. +uint64 +kalloc_free_bytes(void) +{ + struct run *r; + uint64 n = 0; + + if(kmem.use_lock) + acquire(&kmem.lock); + for(r = kmem.freelist; r; r = r->next) + n += PGSIZE; + if(kmem.use_lock) + release(&kmem.lock); + return n; +} diff --git a/kernel/core/main.c b/kernel/core/main.c new file mode 100644 index 0000000..f95f9d0 --- /dev/null +++ b/kernel/core/main.c @@ -0,0 +1,231 @@ +#include "types.h" +#include "defs.h" +#include "param.h" +#include "memlayout.h" +#include "mmu.h" +#include "proc.h" +#include "x86.h" +#include "irq.h" +#include "dma.h" + +static void startothers(void); +static void mpmain(void) __attribute__((noreturn)); +extern char end[]; // first address after kernel loaded from ELF file + +// Set by stage2main.c (boot/stage2main.c, BOOT_VERBOSE_FLAG_ADDR) when the +// user picks "Boot Schunix (verbose)" from the F8 boot menu; read once here, +// before anything else, so subsystem init can log its own steps. +int boot_verbose; + +// Must match BOOT_VERBOSE_FLAG_ADDR in boot/stage2main.c exactly: just past +// the VBE info block, not 0x7000 (startothers() below uses that address for +// the AP boot trampoline). +#define BOOT_VERBOSE_FLAG_ADDR (0x30000 + 0x100) + +// Bootstrap processor starts running C code here. +// Allocate a real stack and switch to it, first +// doing some setup required for memory allocator to work. +int +kmain(uint32 fb_addr) +{ + boot_verbose = *(uchar*)P2V(BOOT_VERBOSE_FLAG_ADDR); + + uartearlyinit(); + if (boot_verbose) cprintf("verbose boot: uartearlyinit done\n"); + // The DMA arena is carved off the top of physical memory *before* kalloc is + // given the rest, because a device needs physically contiguous memory and + // kalloc's free list makes no such promise -- two pages from it are adjacent + // only by chance. Taking it first is the only way to be sure the region + // exists; asking for it later would mean asking kalloc for something it + // fundamentally cannot provide. See include/dma.h. + // Before anything touches PHYSTOP, which is now a variable read from the + // firmware's memory map rather than a constant asserting 224 MB exists. + e820_init(); + kinit1(end, P2V(PHYSTOP - DMA_ARENA_BYTES)); + if (boot_verbose) cprintf("verbose boot: kinit1 done\n"); + dma_init(P2V(PHYSTOP - DMA_ARENA_BYTES), P2V(PHYSTOP)); + if (boot_verbose) cprintf("verbose boot: dma_init done\n"); + kvmalloc(); + if (boot_verbose) cprintf("verbose boot: kvmalloc done\n"); + // ACPI first, the legacy MP table only if it finds nothing. Modern firmware + // publishes a complete MADT and an MP table with one processor in it, so + // asking the older interface first means never seeing more than one CPU. + // See kernel/arch/x86_64/acpi.c. + if (!acpi_init()) + mpinit(); + if (boot_verbose) cprintf("verbose boot: cpu enumeration done\n"); + lapicinit(); + if (boot_verbose) cprintf("verbose boot: lapicinit done\n"); + tvinit(); + if (boot_verbose) cprintf("verbose boot: tvinit done\n"); + seginit(); + if (boot_verbose) cprintf("verbose boot: seginit done\n"); + ioapicinit(); + if (boot_verbose) cprintf("verbose boot: ioapicinit done\n"); + irq_init(); // must precede any driver init that registers a handler + if (boot_verbose) cprintf("verbose boot: irq_init done\n"); + // Before the keyboard and mouse, which start producing events the moment + // their handlers are registered. + inputinit(); + if (boot_verbose) cprintf("verbose boot: inputinit done\n"); + consoleinit(); + if (boot_verbose) cprintf("verbose boot: consoleinit done\n"); + uartinit(); + if (boot_verbose) cprintf("verbose boot: uartinit done\n"); + mouseinit(); + if (boot_verbose) cprintf("verbose boot: mouseinit done\n"); + vga_init(fb_addr); + if (boot_verbose) cprintf("verbose boot: vga_init done (fb=0x%x)\n", fb_addr); + // No splash drawing here on purpose. The loader owns the splash, logo and + // all, and vga_init() deliberately does not clear -- so it is still on the + // display right through to console_takeover(). Painting it again from here + // would land in the last few hundred milliseconds of boot and change nothing. + // After vga_init, because the display driver cross-checks the framebuffer + // address it finds on the bus against the one VBE reported -- and it can + // only do that once vga.c has read the VBE block. + // The devices that are simply known to be on a PC rather than enumerated by + // any bus. Registered before pci_init() so that /proc/devices lists them + // first, which puts the machine's fixed furniture above whatever happened to + // be plugged into it. + platform_register_devices(); + pci_init(); + if (boot_verbose) cprintf("verbose boot: pci_init done\n"); + pinit(); + if (boot_verbose) cprintf("verbose boot: pinit done\n"); + binit(); + if (boot_verbose) cprintf("verbose boot: binit done\n"); + fileinit(); + if (boot_verbose) cprintf("verbose boot: fileinit done\n"); + sockinit(); + if (boot_verbose) cprintf("verbose boot: sockinit done\n"); + // After the timer is running, so boot_epoch can subtract the uptime so far, + // and before userspace, so the very first timestamp any program takes is + // already real. Reading the RTC is slow, which is exactly why it happens once + // here rather than on every gettimeofday(). + time_init(); + ideinit(); + if (boot_verbose) cprintf("verbose boot: ideinit done\n"); + ramdiskinit(); + if (boot_verbose) cprintf("verbose boot: ramdiskinit done\n"); + // Start the application processors. + // + // This was broken from the beginning of the project and is fixed as of + // 2026-08-08. Five distinct bugs, each hidden behind the one before it, and + // all of them invisible under QEMU because it publishes only one processor in + // the legacy MP table -- see kernel/arch/x86_64/acpi.c. VMware published the + // real count, which is why the triple fault only ever appeared there. + // + // 1. entry64high had no AP branch at all; the check was a comment saying it + // had never been written. Every AP fell into the boot processor's path, + // took the BSP's stack, and ran kmain() a second time. + // 2. seginit() called kalloc() for its per-CPU page. kalloc locks, locking + // reads `cpu`, and `cpu` is what seginit() was about to set up. + // 3. kernel_fs_base was one global for all CPUs, and trapasm.S read it on + // every entry from user mode, so both CPUs shared one per-CPU block. + // 4. `cpu` and `proc` were __thread. That means per *thread*, so the compiler + // cached the thread pointer across calls -- but %fs here is per *CPU*, and + // a kernel thread changes CPU at every sched(). See include/proc.h. + // 5. The new per-CPU slots collided with syscall_entry's scratch word at + // %fs:(8), which is the same memory as %gs:8. + // + int smp_start_aps = 1; + if (smp_start_aps) { + startothers(); + if (boot_verbose) cprintf("verbose boot: startothers done\n"); + } else if (ncpu > 1) { + cprintf("smp: %d cpus found, running on 1 (AP startup is broken)\n", ncpu); + } + kinit2(); + if (boot_verbose) cprintf("verbose boot: kinit2 done\n"); + sinit(); + if (boot_verbose) cprintf("verbose boot: sinit done\n"); + mpmain(); +} + +// Other CPUs jump here from entryother.S. +void +mpenter(void) +{ + switchkvm(); + seginit(); + lapicinit(); + mpmain(); +} + +// Enable SSE/FXSAVE so user binaries can use XMM registers. +static void +enable_sse(void) +{ + uint64 cr0, cr4; + asm volatile("mov %%cr0, %0" : "=r"(cr0)); + cr0 &= ~(1UL << 2); // clear CR0.EM (no FP emulation) + cr0 |= (1UL << 1); // set CR0.MP (monitor coprocessor) + asm volatile("mov %0, %%cr0" :: "r"(cr0)); + asm volatile("mov %%cr4, %0" : "=r"(cr4)); + cr4 |= (1UL << 9); // CR4.OSFXSR — OS handles FXSAVE/FXRSTOR + cr4 |= (1UL << 10); // CR4.OSXMMEXCPT — OS handles SSE exceptions + asm volatile("mov %0, %%cr4" :: "r"(cr4)); +} + +// Common CPU setup code. +static void +mpmain(void) +{ + cprintf("cpu%d: starting\n", cpunum()); + enable_sse(); // allow user XMM/SSE instructions + idtinit(); // load idt register + syscallinit(); // syscall set up + xchg(&mycpu()->started, 1); // tell startothers() we're up + scheduler(); // start running processes +} + +void entry32mp(void); + +// Start the non-boot (AP) processors. +static void +startothers(void) +{ + extern uchar _binary_entryother_start[], _binary_entryother_size[]; + uchar *code; + struct cpu *c; + char *stack; + + // Write entry code to unused memory at 0x7000. + // The linker has placed the image of entryother.S in + // _binary_entryother_start. + code = P2V(0x7000); + memmove(code, _binary_entryother_start, + (addr_t)_binary_entryother_size); + + for(c = cpus; c < cpus+ncpu; c++){ + if(c == cpus+cpunum()) // We've started already. + continue; + + // Tell entryother.S what stack to use, where to enter, and what + // pgdir to use. We cannot use kpgdir yet, because the AP processor + // is running in low memory, so we use entrypgdir for the APs too. + // The AP's per-CPU page, allocated here rather than by the AP itself. + // seginit() explains why: an AP cannot call kalloc() before %fs is set up, + // because kalloc locks and locking reads the per-CPU pointer. + if (c->local == 0) { + c->local = kalloc(); + memset(c->local, 0, PGSIZE); + } + + stack = kalloc(); + *(uint32*)(code-4) = 0x8000; // enough stack to get us to entry64mp + *(uint32*)(code-8) = v2p(entry32mp); + *(uint64*)(code-16) = (uint64) (stack + KSTACKSIZE); + + lapicstartap(c->apicid, V2P(code)); + + // wait for cpu to finish mpmain() + while(c->started == 0) + ; + + // Counted here, on the boot processor, after the AP has actually reported + // in -- not by the AP itself. This is serialised by the loop, so it needs + // no atomic, and it cannot overcount a processor that started and died. + ncpu_online++; + } +} diff --git a/kernel/core/panic.c b/kernel/core/panic.c new file mode 100644 index 0000000..903d962 --- /dev/null +++ b/kernel/core/panic.c @@ -0,0 +1,543 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// PSOGM — the Schunix crash screen. +// +// The name is a nod to the Amiga's "Guru Meditation", but nothing else about +// this is a nod to 1985. Three things it is built around: +// +// *It does not clear the screen.* Whatever was being displayed stays there, +// dimmed and pushed back, with the report floating over it. A crash screen that +// wipes the display tells you the machine is gone; one that leaves your work +// visible underneath tells you it stopped, which is both truer and much less +// alarming. +// +// *It is not frightening.* No red, no capitals, no "FATAL". A calm sentence, a +// face that is obviously a cartoon, and then the facts. Whoever is reading this +// is already having a bad time. +// +// *It says considerably more than it needs to.* The screen is the only artefact +// of a kernel panic — there is no core dump and no log to go back to — so +// everything cheap to collect is collected: which CPU, which process, how far +// into the boot, where the kernel is loaded, and a call chain. Anything left +// out here is gone. +// +// Everything is also written to the serial console, in the same order. That is +// what tools/bootcheck.ps1 reads, and it is what you get when the crash happens +// before there is a framebuffer at all. + +#include "types.h" +#include "defs.h" +#include "param.h" +#include "memlayout.h" +#include "mmu.h" +#include "x86.h" +#include "proc.h" +#include "spinlock.h" +// Purple, because the name says so, and white on it because nothing else is +// legible at every brightness a monitor might be set to. The palette lives in +// theme.h with everything else; this file used to keep its own copy under the +// same names but with different values, which meant theme.h described a crash +// screen nobody had ever seen. +#include "theme.h" +#include "qr.h" + +extern uint ticks; +extern int panicked; + +/* ------------------------------------------------------------------ */ +/* Small formatting helpers */ +/* */ +/* cprintf goes to the console, which during a panic is also the screen */ +/* being painted over -- so the panel builds its strings itself and */ +/* places them with vga_text_at. */ +/* ------------------------------------------------------------------ */ + +static char * +put_str(char *p, char *end, const char *s) +{ + while (*s && p < end - 1) + *p++ = *s++; + *p = '\0'; + return p; +} + +static char * +put_dec(char *p, char *end, uint64 v) +{ + char tmp[24]; + int i = 0; + if (v == 0) + tmp[i++] = '0'; + while (v && i < (int)sizeof(tmp)) { tmp[i++] = '0' + (v % 10); v /= 10; } + while (i-- && p < end - 1) + *p++ = tmp[i]; + *p = '\0'; + return p; +} + +static char * +put_hex(char *p, char *end, uint64 v, int digits) +{ + const char *d = "0123456789abcdef"; + if (p < end - 2) { *p++ = '0'; *p++ = 'x'; } + for (int i = digits - 1; i >= 0 && p < end - 1; i--) + *p++ = d[(v >> (i * 4)) & 0xF]; + *p = '\0'; + return p; +} + +/* ------------------------------------------------------------------ */ +/* The face */ +/* */ +/* ":(" — two dots and a parenthesis, drawn large. It is a sad face */ +/* only if you tilt your head, which is exactly why it works: it is */ +/* legible as a mood without being a picture of anything, and it does */ +/* not pretend the machine has feelings about what just happened. */ +/* */ +/* Drawn from primitives rather than stored as a bitmap, so it scales */ +/* with the display and costs no space in a kernel image that stage2 */ +/* loads one sector at a time by PIO. */ +/* ------------------------------------------------------------------ */ + +static int +isqrt(int v) +{ + if (v <= 0) + return 0; + + // Newton's method, iterated only while the estimate is still *decreasing*. + // + // The obvious "stop when it stops changing" version does not terminate: on a + // non-square it settles into a two-cycle rather than a fixed point, and + // isqrt(3) alternates between 1 and 2 forever. In a panic handler that is a + // hang with the screen half drawn and no way to find out why. + int x = v; + int y = (x + 1) / 2; + while (y < x) { + x = y; + y = (x + v / x) / 2; + } + return x; +} + +// The "(" of ":(" — the left half of a squashed circle, so the tips point right +// and the middle bulges left, the way the glyph does. +static void +draw_paren(int cx, int cy, int h, int thick, uint32 color) +{ + int r = h / 2; + for (int dy = -r; dy <= r; dy++) { + int dx = isqrt(r * r - dy * dy); + // Narrow it: a true half-circle reads as a bracket, not a parenthesis. + int x = cx - (dx * 45) / 100; + vga_fill_circle(x, cy + dy, thick / 2, color); + } +} + +static void +draw_sad_face(int x, int y, int h, uint32 color) +{ + int dot = h / 12; + int gap = h / 4; + + // The colon. + vga_fill_circle(x + dot, y + h / 2 - gap, dot, color); + vga_fill_circle(x + dot, y + h / 2 + gap, dot, color); + + // The parenthesis, a little to the right of it. + draw_paren(x + dot * 2 + h / 2, y + h / 2, h, dot * 2, color); +} + +/* ------------------------------------------------------------------ */ +/* The report */ +/* ------------------------------------------------------------------ */ + +struct field { + const char *label; + char value[72]; +}; + +#define NFIELD 10 + +static struct field fields[NFIELD]; +static int nfields; + +static struct field * +field(const char *label) +{ + if (nfields >= NFIELD) + return &fields[NFIELD - 1]; + struct field *f = &fields[nfields++]; + f->label = label; + f->value[0] = '\0'; + return f; +} + +// Collect everything worth knowing while the machine is still in the state +// that produced the fault. Interrupts are already off and no lock is taken: +// a panic that deadlocks trying to report itself tells you nothing at all. +// A short, shoutable name for the failure, derived from the panic message +// rather than declared at every call site. +// +// Windows stop codes are genuinely useful: they are the thing you search for, +// and they stay the same across builds while the prose around them changes. +// Taking the message up to its first colon (or its first few words) gets that +// for free -- "sysrq: crash requested" becomes SYSRQ, "short filewrite" becomes +// SHORT_FILEWRITE -- with no risk of a call site forgetting to supply one. +static void +stop_code(char *out, int cap, const char *reason) +{ + int n = 0; + for (int i = 0; reason[i] && n < cap - 1; i++) { + char c = reason[i]; + if (c == ':') + break; + if (c == ' ' || c == '\t') { + if (n == 0 || out[n-1] == '_') + continue; + out[n++] = '_'; + continue; + } + if (c >= 'a' && c <= 'z') c = c - 'a' + 'A'; + if ((c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_') + out[n++] = c; + } + while (n > 0 && out[n-1] == '_') + n--; + if (n == 0) + { out[n++] = 'P'; out[n++] = 'A'; out[n++] = 'N'; out[n++] = 'I'; out[n++] = 'C'; } + out[n] = '\0'; +} + +static void +gather(char *reason, addr_t *pcs, int npcs) +{ + char *e; + struct field *f; + + nfields = 0; + + f = field("Stop code"); + stop_code(f->value, sizeof(f->value), reason); + + f = field("Reason"); + put_str(f->value, f->value + sizeof(f->value), reason); + + f = field("CPU"); + e = f->value + sizeof(f->value); + put_dec(f->value, e, mycpu() ? (uint64)mycpu()->id : 0); + if (!mycpu()) + put_str(f->value, e, "unknown (very early boot)"); + + f = field("Process"); + e = f->value + sizeof(f->value); + if (myproc()) { + char *p = put_str(f->value, e, myproc()->name); + p = put_str(p, e, " pid "); + put_dec(p, e, (uint64)myproc()->pid); + } else { + put_str(f->value, e, "none -- in the kernel, not on anybody's behalf"); + } + + f = field("Uptime"); + e = f->value + sizeof(f->value); + { + char *p = put_dec(f->value, e, (uint64)ticks); + p = put_str(p, e, " ticks (about "); + p = put_dec(p, e, (uint64)ticks / 100); + put_str(p, e, " s)"); + } + + // Every address below is a link-time address. Printing where the kernel + // actually sits is what lets addr2line resolve them afterwards, and it is + // the single most useful line here when the trace is all you have. + f = field("Kernel base"); + e = f->value + sizeof(f->value); + put_hex(f->value, e, (uint64)KERNBASE, 16); + + f = field("Fault PC"); + e = f->value + sizeof(f->value); + if (npcs > 0 && pcs[0]) + put_hex(f->value, e, (uint64)pcs[0], 16); + else + put_str(f->value, e, "unavailable"); +} + +/* ------------------------------------------------------------------ */ +/* The QR code */ +/* */ +/* A panic report is only worth what can be carried away from it, and */ +/* at this point there is no network, no log to write to, and often no */ +/* second machine on the desk. Photographing the screen works but */ +/* turns a call chain into something that has to be retyped by hand, */ +/* with sixteen hex digits per frame and no way to tell 8 from B. */ +/* */ +/* A phone camera is a working out-of-band channel that needs no */ +/* driver on our side at all. See kernel/core/qr.c. */ +/* ------------------------------------------------------------------ */ + +// Deliberately not a URL. This encodes the report itself, so it goes wherever +// the person holding the phone decides -- and reaches nothing on its own. +// +// Compact because the budget is 134 bytes for the largest symbol this encoder +// builds, and a crash with a deep call chain is exactly when the budget is +// tightest. Two decisions buy most of the room: frames are stored as offsets +// from the kernel base rather than absolute addresses, which halves 16 hex +// digits to 8, and the fields are single letters. The base is included, so +// nothing is lost -- addr2line wants the link-time address and can be given it. +// +// Fields are appended in priority order and the loop simply stops when the +// buffer is full, so a long reason string costs call-chain depth rather than +// producing a report that will not encode. +static int +build_qr_payload(char *buf, int cap, char *reason, addr_t *pcs, int npcs) +{ + char *p = buf, *end = buf + cap; + + // Magic and format version, so whatever reads this later can tell what it is + // looking at and can change the layout without ambiguity. + p = put_str(p, end, "SXPSOGM1\n"); + + // The reason, not the stop code: the stop code is derived from the reason by + // stop_code() and carries strictly less, so encoding both would spend bytes + // on something the reader can recompute. + // + // Held back from the end of the buffer so that a long message costs some of + // the call chain but never all of it. A report with a sentence and no + // addresses is much harder to act on than one with addresses and a truncated + // sentence. + { + char *reason_end = end - 34; // three frames and their separators + if (reason_end > p) + p = put_str(p, reason_end, reason); + } + + p = put_str(p, end, "\nc"); + p = put_dec(p, end, mycpu() ? (uint64)mycpu()->id : 0); + p = put_str(p, end, " p"); + p = put_dec(p, end, myproc() ? (uint64)myproc()->pid : 0); + p = put_str(p, end, " t"); + p = put_dec(p, end, (uint64)ticks); + if (myproc()) { + p = put_str(p, end, " "); + p = put_str(p, end, myproc()->name); + } + + p = put_str(p, end, "\nb"); + p = put_hex(p, end, (uint64)KERNBASE, 16); + + p = put_str(p, end, "\n"); + for (int i = 0; i < npcs && pcs[i]; i++) { + // Nine bytes: eight hex digits and the separator. Stopping here rather than + // letting put_hex truncate matters -- half an address in a crash report is + // worse than one fewer address, because it looks like a real one. + if (end - p < 10) + break; + if (i) + p = put_str(p, end, " "); + // put_hex prefixes "0x", which is 14 wasted bytes across seven frames. + { + const char *d = "0123456789abcdef"; + uint64 off = (uint64)pcs[i] - (uint64)KERNBASE; + for (int k = 7; k >= 0; k--) + *p++ = d[(off >> (k * 4)) & 0xF]; + *p = '\0'; + } + } + + return (int)(p - buf); +} + +// One static symbol rather than a local: struct qr is about 3.4 KB and the +// panic path runs on whatever stack the fault left behind, which may not have +// that much room. It is only ever touched here, after everything else has +// stopped. +static struct qr panic_qr; + +// Returns the width used, so the text column knows what it may not overlap. +static int +draw_qr_panel(int sw, int sh, int ch, char *reason, addr_t *pcs, int npcs) +{ + char payload[QR_MAX_BYTES]; + int n = build_qr_payload(payload, sizeof(payload), reason, pcs, npcs); + + if (n <= 0 || qr_encode(&panic_qr, (unsigned char *)payload, n) != 0) + return 0; + + // Four modules of quiet zone, which the standard requires and which decoders + // genuinely need -- without it many will not find the symbol at all. + const int quiet = 4; + int cells = panic_qr.size + 2 * quiet; + + // Scale to about a quarter of the display, then clamp. The lower bound is + // what a phone can still resolve from a photograph of a monitor; the upper + // bound stops the thing dominating a screen whose actual job is the text. + int scale = (sw / 4) / cells; + if (scale < 3) scale = 3; + if (scale > 8) scale = 8; + + int card = cells * scale; + int x0 = sw - card - sw / 24; + int y0 = sh / 8; + + vga_fill_rect(x0, y0, card, card, PSOGM_CARD); + for (int y = 0; y < panic_qr.size; y++) + for (int x = 0; x < panic_qr.size; x++) + if (panic_qr.mod[y][x]) + vga_fill_rect(x0 + (x + quiet) * scale, y0 + (y + quiet) * scale, + scale, scale, PSOGM_INK); + + vga_text_at(x0, y0 + card + ch, "Scan for a text copy", PSOGM_DIM); + vga_text_at(x0, y0 + card + ch * 2 + 2, "of this report.", PSOGM_DIM); + + return card + sw / 24; +} + +// The frame pointers, one per line, as one string per row. +static void +trace_line(char *buf, int cap, int i, addr_t pc) +{ + char *e = buf + cap; + char *p = put_str(buf, e, " #"); + p = put_dec(p, e, (uint64)i); + p = put_str(p, e, i < 10 ? " " : " "); + put_hex(p, e, (uint64)pc, 16); +} + +/* ------------------------------------------------------------------ */ + +static void +paint(char *reason, addr_t *pcs, int npcs) +{ + int sw, sh; + vga_dims(&sw, &sh); + + int ch = vga_char_h(); + int cw = vga_char_w(); + + // The whole display, one colour. An earlier version dimmed the previous + // screen and floated a panel over it, which looked good and was wrong: the + // thing is called a Purple Screen, and half a screen of somebody's abandoned + // work behind the report is a distraction at the worst possible moment. + vga_clear(PSOGM_BG); + // Glyphs are drawn with their background filled, so the console's cream has + // to become the background or every line of text arrives in its own box. + vga_set_bg(PSOGM_BG); + + // The symbol goes first, because it decides how much width the text has. It + // is drawn before anything can fail: if the encoder refuses the payload the + // screen simply has no card and the report is unchanged. + draw_qr_panel(sw, sh, ch, reason, pcs, npcs); + + int left = sw / 8; + if (left > 110) left = 110; + int y = sh / 8; + + int face_h = 96; + draw_sad_face(left, y, face_h, PSOGM_FG); + y += face_h + ch * 2; + + // Left-aligned, not centred. Centred text reads as a poster; this is a + // report, and a report is read down the left edge. + vga_text_at(left, y, "Schunix ran into a problem and needs to restart.", + PSOGM_FG); + y += ch + ch / 2; + vga_text_at(left, y, "Nothing on your disks was touched by this.", + PSOGM_DIM); + + // A rule in the boot splash's accent, the same width as the one under the + // wordmark there, tying this screen to the one the machine started with. + // + // Equal space above and below on purpose: at any less it sits close enough to + // the line above to read as an underline of that sentence rather than as a + // divider before the facts. + y += ch * 2; + vga_fill_rect(left, y, 120, 2, SCH_ACCENT); + y += ch; + + // Facts: quiet labels down the left, bright values in a second column. + int vx = left + 14 * cw; + for (int i = 0; i < nfields; i++) { + vga_text_at(left, y, fields[i].label, PSOGM_DIM); + vga_text_at(vx, y, fields[i].value, PSOGM_FG); + y += ch + 2; + } + + y += ch; + vga_text_at(left, y, "Call chain", PSOGM_DIM); + y += ch + 2; + + for (int i = 0; i < npcs && pcs[i]; i++) { + char line[48]; + trace_line(line, sizeof(line), i, pcs[i]); + vga_text_at(vx - 3 * cw, y, line, PSOGM_FG); + y += ch; + if (y > sh - ch * 5) + break; + } + + // Two closing lines, bottom left: what to do with the numbers above, and + // what this screen is called, so it can be searched for by name. Shortened + // from the full command line, which at 62 characters ran under the QR card. + vga_text_at(left, sh - ch * 3, + "Resolve: addr2line -e kernel
", PSOGM_DIM); + vga_text_at(left, sh - ch * 3 + ch + 4, + "PSOGM -- Purple Screen of Guru Meditation", PSOGM_DIM); +} + +// Same report, down the serial line. This is the copy that survives: it is what +// bootcheck reads, and the only one that exists when the framebuffer does not. +static void +report_serial(char *reason, addr_t *pcs, int npcs) +{ + cprintf("\n"); + cprintf(" :(\n\n"); + cprintf(" Schunix ran into a problem and needs to restart.\n"); + cprintf(" PSOGM -- Purple Screen of Guru Meditation\n\n"); + + for (int i = 0; i < nfields; i++) { + // Pad the label out so the values line up, without a printf width + // specifier -- cprintf has no such thing. + int n = 0; + while (fields[i].label[n]) n++; + cprintf(" %s", fields[i].label); + for (int pad = n; pad < 14; pad++) + cprintf(" "); + cprintf("%s\n", fields[i].value); + } + + cprintf("\n Call chain\n"); + for (int i = 0; i < npcs && pcs[i]; i++) + cprintf(" #%d %p\n", i, pcs[i]); + + cprintf("\n Resolve with: addr2line -e build/kernel/kernel
\n"); + cprintf(" Reason: %s\n", reason); + cprintf(" System halted.\n"); +} + +__attribute__((noreturn)) +void +panic(char *s) +{ + addr_t pcs[10]; + + cli(); + // Stop every other path from writing to the console. consputc checks this + // and spins, which is what keeps another CPU from painting over the report + // while it is being drawn. + cons_panic_mode(); + + for (int i = 0; i < 10; i++) + pcs[i] = 0; + getcallerpcs(&s, pcs); + + gather(s, pcs, 10); + report_serial(s, pcs, 10); + + if (vga_ready()) + paint(s, pcs, 10); + + panicked = 1; + for (;;) + hlt(); +} diff --git a/kernel/core/qr.c b/kernel/core/qr.c new file mode 100644 index 0000000..f24d41c --- /dev/null +++ b/kernel/core/qr.c @@ -0,0 +1,456 @@ +// QR encoder. See include/qr.h for why it exists and why it is this small. +// +// Freestanding on purpose: no allocation, no floating point, no libc beyond +// memset/memmove. It has to run in panic context, where the heap may be exactly +// what is broken. Every buffer here is either on the caller's struct or a +// bounded local. +// +// The structure follows ISO/IEC 18004 in the order the standard describes it, +// which is also the only order in which any of it can be checked: +// +// 1. encode the payload into data codewords +// 2. append Reed-Solomon error correction, per block +// 3. interleave the blocks +// 4. draw the function patterns (finders, timing, alignment, format area) +// 5. lay the codeword bits into the remaining modules in a zigzag +// 6. try all 8 masks, score each, keep the best +// +// Verified by scanning the output with a phone, which is the only test that +// matters and is also literally the feature. + +#ifdef QR_HOST +#include +#else +#include "types.h" +#include "defs.h" +#endif + +#include "qr.h" + +// --------------------------------------------------------------------------- +// Version table +// --------------------------------------------------------------------------- +// +// Per version, at error correction level L: total codewords, error correction +// codewords per block, and number of blocks. These are from the standard's +// tables and cannot be derived, so they are the one part of this file that has +// to be right by transcription rather than by reasoning. +// +// A useful check on them: total - ecc*blocks must be divisible by blocks for +// every row below, because levels 1..6 at L happen to have equally sized +// blocks. qr_encode() asserts exactly that at runtime, which is what turns a +// mistyped number here into a visible failure instead of an unscannable symbol. +static const struct { + short total; // total codewords in the symbol + unsigned char ecc; // EC codewords per block + unsigned char blocks; // number of RS blocks +} vinfo[QR_MAX_VERSION + 1] = { + { 0, 0, 0 }, // version 0 does not exist; keeps indexing natural + { 26, 7, 1 }, + { 44, 10, 1 }, + { 70, 15, 1 }, + { 100, 20, 1 }, + { 134, 26, 1 }, + { 172, 18, 2 }, +}; + +int +qr_capacity(int version) +{ + if (version < 1 || version > QR_MAX_VERSION) + return 0; + // Data codewords, less the two bytes of framing: a 4-bit mode indicator, an + // 8-bit character count (versions 1-9), and a 4-bit terminator come to + // exactly two codewords' worth of overhead. + int data = vinfo[version].total - vinfo[version].ecc * vinfo[version].blocks; + return data - 2; +} + +// --------------------------------------------------------------------------- +// GF(256) and Reed-Solomon +// --------------------------------------------------------------------------- + +// Multiply in GF(256) modulo x^8 + x^4 + x^3 + x^2 + 1 (0x11D). +// +// Done by shift-and-add rather than log/exp tables. Two 256-byte tables plus +// their initialisation is more code than this loop, and this needs no init step +// at all -- which matters when the caller is a panic handler that cannot assume +// any particular earlier function ran. +static unsigned char +gmul(unsigned char x, unsigned char y) +{ + unsigned char z = 0; + for (int i = 7; i >= 0; i--) { + z = (unsigned char)((z << 1) ^ ((z >> 7) * 0x1D)); + z ^= (unsigned char)(((y >> i) & 1) * x); + } + return z; +} + +// Coefficients of the degree-`degree` generator polynomial, highest power +// first, with the leading 1 left implicit. +static void +rs_divisor(int degree, unsigned char *result) +{ + memset(result, 0, degree); + result[degree - 1] = 1; // start at the monomial x^0 + + // Multiply by (x - r) for r = 1, a, a^2, ... Each pass shifts the polynomial + // up one degree and folds in the new root. + unsigned char root = 1; + for (int i = 0; i < degree; i++) { + for (int j = 0; j < degree; j++) { + result[j] = gmul(result[j], root); + if (j + 1 < degree) + result[j] ^= result[j + 1]; + } + root = gmul(root, 0x02); + } +} + +// The remainder of data[] divided by the generator: the EC codewords. +static void +rs_remainder(const unsigned char *data, int len, + const unsigned char *gen, int degree, unsigned char *result) +{ + memset(result, 0, degree); + for (int i = 0; i < len; i++) { + unsigned char factor = data[i] ^ result[0]; + memmove(&result[0], &result[1], degree - 1); + result[degree - 1] = 0; + for (int j = 0; j < degree; j++) + result[j] ^= gmul(gen[j], factor); + } +} + +// --------------------------------------------------------------------------- +// Module access +// --------------------------------------------------------------------------- + +static void +set_fn(struct qr *q, int x, int y, int dark) +{ + if (x < 0 || y < 0 || x >= q->size || y >= q->size) + return; + q->mod[y][x] = (unsigned char)(dark != 0); + q->fn[y][x] = 1; +} + +static int +absi(int v) +{ + return v < 0 ? -v : v; +} + +static int +cheb(int dx, int dy) +{ + int ax = absi(dx), ay = absi(dy); + return ax > ay ? ax : ay; +} + +// --------------------------------------------------------------------------- +// Function patterns +// --------------------------------------------------------------------------- + +// A finder plus its separator, as one 9x9 stamp centred on (cx, cy). +// +// Writing it by Chebyshev distance rather than as a literal bitmap is not +// cleverness for its own sake: the concentric rings *are* the definition -- dark +// 3x3 core, light ring, dark ring, light separator -- and the edge stamps run +// off the symbol, which set_fn() clips. +static void +draw_finder(struct qr *q, int cx, int cy) +{ + for (int dy = -4; dy <= 4; dy++) + for (int dx = -4; dx <= 4; dx++) { + int d = cheb(dx, dy); + set_fn(q, cx + dx, cy + dy, d != 2 && d != 4); + } +} + +// The 5x5 alignment pattern: dark 3x3 outline with a light ring inside. +static void +draw_alignment(struct qr *q, int cx, int cy) +{ + for (int dy = -2; dy <= 2; dy++) + for (int dx = -2; dx <= 2; dx++) + set_fn(q, cx + dx, cy + dy, cheb(dx, dy) != 1); +} + +// The 15-bit format information, twice, plus the always-dark module. +// +// Called once with the real mask at the end, and once early with mask 0 purely +// to *reserve* these modules -- the zigzag below skips anything marked as a +// function pattern, so the reservation has to exist before data is placed. +static void +draw_format(struct qr *q, int mask) +{ + // 0b01 is error correction level L. Five data bits, then a BCH(15,5) check + // computed with generator 0x537, then XOR 0x5412 so an all-zero format never + // produces an all-zero pattern. + int data = (1 << 3) | mask; + int rem = data; + for (int i = 0; i < 10; i++) + rem = (rem << 1) ^ ((rem >> 9) * 0x537); + int bits = ((data << 10) | rem) ^ 0x5412; + + int n = q->size; + + // First copy: down the left of the top-right finder, then left along the top + // of the bottom-left one. The 6/7 and 7/8 jogs skip the timing pattern. + for (int i = 0; i <= 5; i++) + set_fn(q, 8, i, (bits >> i) & 1); + set_fn(q, 8, 7, (bits >> 6) & 1); + set_fn(q, 8, 8, (bits >> 7) & 1); + set_fn(q, 7, 8, (bits >> 8) & 1); + for (int i = 9; i < 15; i++) + set_fn(q, 14 - i, 8, (bits >> i) & 1); + + // Second copy, so the format survives damage to either corner. + for (int i = 0; i < 8; i++) + set_fn(q, n - 1 - i, 8, (bits >> i) & 1); + for (int i = 8; i < 15; i++) + set_fn(q, 8, n - 15 + i, (bits >> i) & 1); + + set_fn(q, 8, n - 8, 1); // the dark module, always set, always here +} + +static void +draw_function_patterns(struct qr *q) +{ + int n = q->size; + + // Timing patterns first: the finders overwrite their ends, which is correct. + for (int i = 0; i < n; i++) { + set_fn(q, 6, i, i % 2 == 0); + set_fn(q, i, 6, i % 2 == 0); + } + + draw_finder(q, 3, 3); + draw_finder(q, n - 4, 3); + draw_finder(q, 3, n - 4); + + // For versions 2-6 the alignment centres are {6, n-7}, and three of those + // four combinations collide with a finder. Only the bottom-right survives, + // which is why there is no position table here. Version 7 would need one. + if (q->size > 21) + draw_alignment(q, n - 7, n - 7); + + draw_format(q, 0); // reserve; overwritten once a mask is chosen +} + +// --------------------------------------------------------------------------- +// Masking +// --------------------------------------------------------------------------- + +static int +mask_bit(int mask, int x, int y) +{ + switch (mask) { + case 0: return (x + y) % 2 == 0; + case 1: return y % 2 == 0; + case 2: return x % 3 == 0; + case 3: return (x + y) % 3 == 0; + case 4: return (x / 3 + y / 2) % 2 == 0; + case 5: return x * y % 2 + x * y % 3 == 0; + case 6: return (x * y % 2 + x * y % 3) % 2 == 0; + case 7: return ((x + y) % 2 + x * y % 3) % 2 == 0; + } + return 0; +} + +static void +apply_mask(struct qr *q, int mask) +{ + for (int y = 0; y < q->size; y++) + for (int x = 0; x < q->size; x++) + if (!q->fn[y][x]) + q->mod[y][x] ^= (unsigned char)mask_bit(mask, x, y); +} + +// The standard's four penalty rules. Lower is better. +// +// A decoder will read any legal mask, so this is not correctness -- it is scan +// reliability, which is the whole point when the reader is a handheld camera +// pointed at a screen. Rule 3 in particular exists to stop the data accidentally +// containing something that looks like a finder pattern. +static int +penalty(const struct qr *q) +{ + int n = q->size, score = 0, dark = 0; + + for (int y = 0; y < n; y++) { + for (int x = 0; x < n; x++) + dark += q->mod[y][x]; + + // Rule 1, both directions: runs of five or more identical modules. + for (int dir = 0; dir < 2; dir++) { + int run = 1; + int prev = dir ? q->mod[0][y] : q->mod[y][0]; + for (int i = 1; i < n; i++) { + int cur = dir ? q->mod[i][y] : q->mod[y][i]; + if (cur == prev) { + run++; + } else { + if (run >= 5) score += 3 + (run - 5); + run = 1; + prev = cur; + } + } + if (run >= 5) score += 3 + (run - 5); + } + } + + // Rule 2: every 2x2 block of one colour. + for (int y = 0; y + 1 < n; y++) + for (int x = 0; x + 1 < n; x++) { + int c = q->mod[y][x]; + if (c == q->mod[y][x + 1] && c == q->mod[y + 1][x] && c == q->mod[y + 1][x + 1]) + score += 3; + } + + // Rule 3: the finder-lookalike 1011101 with four light modules on either + // side, in either direction. + for (int dir = 0; dir < 2; dir++) + for (int a = 0; a < n; a++) { + unsigned bits = 0; + for (int b = 0; b < n; b++) { + int m = dir ? q->mod[b][a] : q->mod[a][b]; + bits = ((bits << 1) | (unsigned)m) & 0x7FF; + if (b >= 10 && (bits == 0x05D || bits == 0x5D0)) + score += 40; + } + } + + // Rule 4: deviation from an even split of dark and light, in 5% steps. + int total = n * n; + int k = (absi(dark * 20 - total * 10) + total - 1) / total; + score += k * 10; + + return score; +} + +// --------------------------------------------------------------------------- +// Encode +// --------------------------------------------------------------------------- + +static void +put_bits(unsigned char *buf, int *pos, unsigned val, int n) +{ + for (int i = n - 1; i >= 0; i--) { + buf[*pos >> 3] |= (unsigned char)(((val >> i) & 1) << (7 - (*pos & 7))); + (*pos)++; + } +} + +int +qr_encode(struct qr *q, const unsigned char *data, int len) +{ + int v; + + if (len < 0) + return -1; + for (v = 1; v <= QR_MAX_VERSION; v++) + if (len <= qr_capacity(v)) + break; + if (v > QR_MAX_VERSION) + return -1; + + int total = vinfo[v].total; + int ecclen = vinfo[v].ecc; + int blocks = vinfo[v].blocks; + int datacw = total - ecclen * blocks; + + // The table claims equal-sized blocks for every version this file supports. + // If that is ever untrue the interleave below is silently wrong, so check it + // rather than trust it. + if (datacw % blocks != 0) + return -1; + int blocklen = datacw / blocks; + + // --- 1. payload -> data codewords --------------------------------------- + unsigned char cw[172]; // >= max total codewords + memset(cw, 0, sizeof(cw)); + + int pos = 0; + put_bits(cw, &pos, 0x4, 4); // byte mode + put_bits(cw, &pos, (unsigned)len, 8); + for (int i = 0; i < len; i++) + put_bits(cw, &pos, data[i], 8); + + // Terminator, up to four zero bits, then round up to a whole codeword. + int spare = datacw * 8 - pos; + put_bits(cw, &pos, 0, spare < 4 ? spare : 4); + if (pos & 7) + put_bits(cw, &pos, 0, 8 - (pos & 7)); + + // Fill the rest with the standard alternating pad bytes. They are specified + // values, not arbitrary filler: a decoder uses the character count to know + // where the data ends, but the pad pattern keeps the symbol from having large + // uniform regions that mask selection would then have to fight. + for (int i = pos / 8; i < datacw; i++) + cw[i] = (i - pos / 8) % 2 == 0 ? 0xEC : 0x11; + + // --- 2 and 3. error correction, then interleave ------------------------- + unsigned char gen[30]; + unsigned char ec[4][30]; + rs_divisor(ecclen, gen); + for (int b = 0; b < blocks; b++) + rs_remainder(&cw[b * blocklen], blocklen, gen, ecclen, ec[b]); + + unsigned char out[172]; + int o = 0; + for (int i = 0; i < blocklen; i++) + for (int b = 0; b < blocks; b++) + out[o++] = cw[b * blocklen + i]; + for (int i = 0; i < ecclen; i++) + for (int b = 0; b < blocks; b++) + out[o++] = ec[b][i]; + + // --- 4. function patterns ------------------------------------------------ + memset(q, 0, sizeof(*q)); + q->size = 17 + 4 * v; + draw_function_patterns(q); + + // --- 5. zigzag placement ------------------------------------------------- + // + // Two-module-wide columns walked from the right edge leftwards, alternating + // upward and downward. Column 6 is skipped entirely because it is the vertical + // timing pattern -- without that step every subsequent column lands one place + // out and the symbol is quietly unreadable. + int bit = 0; + int nbits = o * 8; + for (int right = q->size - 1; right >= 1; right -= 2) { + if (right == 6) + right = 5; + for (int vert = 0; vert < q->size; vert++) { + for (int j = 0; j < 2; j++) { + int x = right - j; + int upward = ((right + 1) & 2) == 0; + int y = upward ? q->size - 1 - vert : vert; + if (!q->fn[y][x] && bit < nbits) { + q->mod[y][x] = (unsigned char)((out[bit >> 3] >> (7 - (bit & 7))) & 1); + bit++; + } + } + } + } + + // --- 6. pick a mask ------------------------------------------------------ + int best = 0, bestscore = 0; + for (int m = 0; m < 8; m++) { + apply_mask(q, m); + draw_format(q, m); + int s = penalty(q); + if (m == 0 || s < bestscore) { + bestscore = s; + best = m; + } + apply_mask(q, m); // XOR again to undo; masking is involutive + } + apply_mask(q, best); + draw_format(q, best); + + return 0; +} diff --git a/kernel/core/sleeplock.c b/kernel/core/sleeplock.c new file mode 100644 index 0000000..987dd34 --- /dev/null +++ b/kernel/core/sleeplock.c @@ -0,0 +1,55 @@ +// Sleeping locks + +#include "types.h" +#include "defs.h" +#include "param.h" +#include "x86.h" +#include "memlayout.h" +#include "mmu.h" +#include "proc.h" +#include "spinlock.h" +#include "sleeplock.h" + +void +initsleeplock(struct sleeplock *lk, char *name) +{ + initlock(&lk->lk, "sleep lock"); + lk->name = name; + lk->locked = 0; + lk->pid = 0; +} + +void +acquiresleep(struct sleeplock *lk) +{ + acquire(&lk->lk); + while (lk->locked) { + sleep(lk, &lk->lk); + } + lk->locked = 1; + if (myproc()) { + lk->pid = myproc()->pid; + } else { + lk->pid = 0; + } + release(&lk->lk); +} + +void +releasesleep(struct sleeplock *lk) +{ + acquire(&lk->lk); + lk->locked = 0; + lk->pid = 0; + wakeup(lk); + release(&lk->lk); +} + +int +holdingsleep(struct sleeplock *lk) +{ + acquire(&lk->lk); + int r = lk->locked; + release(&lk->lk); + return r; +} diff --git a/kernel/core/spinlock.c b/kernel/core/spinlock.c new file mode 100644 index 0000000..7b593db --- /dev/null +++ b/kernel/core/spinlock.c @@ -0,0 +1,115 @@ +#include "types.h" +#include "defs.h" +#include "param.h" +#include "x86.h" +#include "memlayout.h" +#include "mmu.h" +#include "proc.h" +#include "spinlock.h" + +void +initlock(struct spinlock *lk, char *name) +{ + lk->name = name; + lk->locked = 0; + lk->cpu = 0; +} + +void +acquire(struct spinlock *lk) +{ + pushcli(); + if(holding(lk)) + panic("acquire"); + + { + uint64 spins = 0; + while(xchg(&lk->locked, 1) != 0){ + if(++spins == 200000000ULL){ + cprintf("acquire: stuck on \"%s\" held by cpu=%p pcs0=%p pcs1=%p pcs2=%p, acquirer cpu=%p\n", + lk->name, lk->cpu, lk->pcs[0], lk->pcs[1], lk->pcs[2], mycpu()); + panic("acquire: deadlock"); + } + } + } + + __sync_synchronize(); + + lk->cpu = mycpu(); + getcallerpcs(&lk, lk->pcs); +} + +void +release(struct spinlock *lk) +{ + if(!holding(lk)) + panic("release"); + + lk->pcs[0] = 0; + lk->cpu = 0; + + __sync_synchronize(); + + asm volatile("movq $0, %0" : "+m" (lk->locked) : ); + + popcli(); +} + +void +getcallerpcs(void *v, addr_t pcs[]) +{ + addr_t *rbp; + + asm volatile("mov %%rbp, %0" : "=r" (rbp)); + getstackpcs(rbp, pcs); +} + +void +getstackpcs(addr_t *rbp, addr_t pcs[]) +{ + int i; + + for(i = 0; i < 10; i++){ + if(rbp == 0 || rbp < (addr_t*)KERNBASE || rbp == (addr_t*)0xffffffff) + break; + pcs[i] = rbp[1]; + rbp = (addr_t*)rbp[0]; + } + for(; i < 10; i++) + pcs[i] = 0; +} + +int +holding(struct spinlock *lock) +{ + return lock->locked && lock->cpu == mycpu(); +} + +void +pushcli(void) +{ + int eflags; + + eflags = readeflags(); + cli(); + if(mycpu() == 0){ + extern uint64 kernel_fs_base; + cprintf("pushcli: cpu==NULL cs=0x%p kernel_fs_base=0x%p actual_fs_base=0x%p\n", + readcs(), kernel_fs_base, rdmsr(0xC0000100)); + panic("pushcli: cpu is NULL"); + } + if(mycpu()->ncli == 0) + mycpu()->intena = eflags & FL_IF; + mycpu()->ncli += 1; +} + +void +popcli(void) +{ + if(readeflags()&FL_IF) + panic("popcli - interruptible"); + if(--mycpu()->ncli < 0) + panic("popcli"); + if(mycpu()->ncli == 0 && mycpu()->intena) + sti(); +} diff --git a/kernel/core/string.c b/kernel/core/string.c new file mode 100644 index 0000000..91f50ce --- /dev/null +++ b/kernel/core/string.c @@ -0,0 +1,113 @@ +#include "types.h" +#include "x86.h" + +void* +memset(void *dst, int c, uint64 n) +{ + uint64 c64; + + if ((addr_t)dst % 8 == 0 && n % 8 == 0) { + c &= 0xFF; + c64 = (uint64)c; + c64 = (c64 << 56) | (c64 << 48) | (c64 << 40) | (c64 << 32) | + (c64 << 24) | (c64 << 16) | (c64 << 8) | c64; + stosq(dst, c64, n / 8); + } else { + stosb(dst, c, n); + } + return dst; +} + +int +memcmp(const void *v1, const void *v2, uint n) +{ + const uchar *s1, *s2; + + s1 = v1; + s2 = v2; + while(n-- > 0){ + if(*s1 != *s2) + return *s1 - *s2; + s1++, s2++; + } + + return 0; +} + +void* +memmove(void *dst, const void *src, uint n) +{ + const char *s; + char *d; + + s = src; + d = dst; + if(s < d && s + n > d){ + s += n; + d += n; + while(n-- > 0) + *--d = *--s; + } else + while(n-- > 0) + *d++ = *s++; + + return dst; +} + +void* +memcpy(void *dst, const void *src, uint n) +{ + return memmove(dst, src, n); +} + +int +strncmp(const char *p, const char *q, uint n) +{ + while(n > 0 && *p && *p == *q) + n--, p++, q++; + if(n == 0) + return 0; + return (uchar)*p - (uchar)*q; +} + +char* +strncpy(char *s, const char *t, int n) +{ + char *os = s; + while(n-- > 0 && (*s++ = *t++) != 0) + ; + while(n-- > 0) + *s++ = 0; + return os; +} + +char* +safestrcpy(char *s, const char *t, int n) +{ + char *os = s; + if(n <= 0) + return os; + while(--n > 0 && (*s++ = *t++) != 0) + ; + *s = 0; + return os; +} + +int +strlen(const char *s) +{ + int n; + + for(n = 0; s[n]; n++) + ; + return n; +} + +int +strnlen(const char *s, int max) +{ + int n; + for(n = 0; n < max && s[n]; n++) + ; + return n; +} diff --git a/kernel/core/time.c b/kernel/core/time.c new file mode 100644 index 0000000..d61f6a6 --- /dev/null +++ b/kernel/core/time.c @@ -0,0 +1,116 @@ +// Wall-clock time. +// +// The kernel has always known how long it had been running -- `ticks`, bumped +// 100 times a second by the timer interrupt -- and has never known what time it +// was. sys_gettimeofday() returned `ticks / 100` as though it were seconds +// since the epoch, so every Schunix machine believed it was the 1st of January +// 1970 and that the date advanced at the rate of its own uptime. `date` printed +// a number that barely moved, which is what it looks like when a clock is +// counting from the wrong place rather than not counting at all. +// +// cmostime() in kernel/arch/x86_64/lapic.c has been able to read the hardware +// clock the whole time. Nothing called it. +// +// The arrangement here is the usual one: read the RTC once at boot, convert it +// to a Unix timestamp, and add uptime to it thereafter. The RTC is slow to read +// (it is polled over two I/O ports and has to be sampled twice to catch it +// mid-update) so reading it on every gettimeofday would be absurd; and `ticks` +// is a perfectly good interval clock once it has something to count from. + +#include "types.h" +#include "defs.h" +#include "date.h" +#include "param.h" +#include "spinlock.h" + +extern uint ticks; + +// Seconds between the Unix epoch and the moment this machine booted. Zero until +// time_init() runs, which makes the behaviour before that point exactly what it +// used to be -- uptime presented as absolute time -- rather than something new +// and differently wrong. +uint64 boot_epoch; + +// Days from 1970-01-01 to the given civil date, proleptic Gregorian. +// +// Howard Hinnant's days_from_civil. It is worth using rather than a loop over +// years because it is branchless, exact for any year, and has no table to get +// wrong; the alternative -- accumulating 365 or 366 days per year since 1970 -- +// gets leap centuries wrong unless you remember that 1900 was not a leap year +// and 2000 was, which is precisely the sort of thing that is discovered on a +// February morning years later. +// +// The trick is shifting the year so that March is month 1, which puts the leap +// day at the *end* of the year and makes the leap rule depend only on the year +// number rather than on where in the year you are. +static int64 +days_from_civil(int64 y, unsigned m, unsigned d) +{ + y -= m <= 2; + const int64 era = (y >= 0 ? y : y - 399) / 400; + const unsigned yoe = (unsigned)(y - era * 400); // [0, 399] + const unsigned doy = (153 * (m + (m > 2 ? -3 : 9)) + 2) / 5 + d - 1; + const unsigned doe = yoe * 365 + yoe / 4 - yoe / 100 + doy; // [0, 146096] + return era * 146097 + (int64)doe - 719468; +} + +// Read the hardware clock and work out when we booted. +// +// The RTC is read in UTC. That is not universally true on a PC -- a machine +// that has had Windows installed on it usually keeps the RTC in local time -- +// but there is nowhere to store a timezone yet and no way to ask, and UTC is +// the honest default for a system with no configuration. When there is a +// settings store, this is where the offset goes. +void +time_init(void) +{ + struct rtcdate r; + + cmostime(&r); + + // cmostime() returns a two-digit year on hardware that only stores one. + // 2000-2099 is the right guess now and stays right for a lifetime; a machine + // reporting 70 means the battery is flat, and the epoch is as good an answer + // as any for that. + int64 year = r.year; + if (year < 100) + year += 2000; + + // A wildly implausible reading means the CMOS is not telling us anything. + // Leaving boot_epoch at zero keeps the old behaviour, which is wrong but + // predictable, rather than putting the machine somewhere in the Bronze Age. + if (r.month < 1 || r.month > 12 || r.day < 1 || r.day > 31 || + r.hour > 23 || r.minute > 59 || r.second > 60 || year < 1970) { + cprintf("time: CMOS clock is not plausible, staying at the epoch\n"); + return; + } + + int64 days = days_from_civil(year, r.month, r.day); + uint64 now = (uint64)(days * 86400) + r.hour * 3600 + r.minute * 60 + r.second; + + // Subtract the uptime so far, so boot_epoch is the moment of boot rather than + // the moment this function ran. It is only a fraction of a second, but it + // keeps boot_epoch meaning exactly what its name says. + uint64 up = ticks / 100; + boot_epoch = now > up ? now - up : now; + + cprintf("time: %d-%d-%d %d:%d:%d UTC from the hardware clock\n", + (int)year, r.month, r.day, r.hour, r.minute, r.second); +} + +// Seconds and microseconds since the epoch. One place, so gettimeofday, +// clock_gettime and anything added later cannot disagree about what time it is. +void +wallclock(uint64 *sec, uint64 *usec) +{ + uint xticks; + + acquire(&tickslock); + xticks = ticks; + release(&tickslock); + + if (sec) + *sec = boot_epoch + xticks / 100; + if (usec) + *usec = (uint64)(xticks % 100) * 10000; +} diff --git a/kernel/core/uaccess.c b/kernel/core/uaccess.c new file mode 100644 index 0000000..2280e64 --- /dev/null +++ b/kernel/core/uaccess.c @@ -0,0 +1,79 @@ +// User-memory access helpers — see include/uaccess.h for the rules. +// +// Everything here validates against the *current* process's page table +// (myproc()->pgdir), which is also the live CR3 while a syscall runs, so a +// range that passes access_ok() can be dereferenced directly by kernel +// code for the remainder of the syscall (single-threaded processes can't +// unmap it out from under us mid-call). + +#include "types.h" +#include "defs.h" +#include "param.h" +#include "memlayout.h" +#include "mmu.h" +#include "proc.h" +#include "errno.h" +#include "uaccess.h" + +int +access_ok(addr_t uaddr, uint64 size) +{ + addr_t a, end; + + if(size == 0) + return 1; + end = uaddr + size; + if(end < uaddr || end > KERNBASE) + return 0; + for(a = PGROUNDDOWN(uaddr); a < end; a += PGSIZE) + if(uva2ka(myproc()->pgdir, (char*)a) == 0) + return 0; + return 1; +} + +void* +user_ptr(addr_t uaddr, uint64 size) +{ + if(uaddr == 0 || !access_ok(uaddr, size)) + return 0; + return (void*)uaddr; +} + +int +copy_from_user(void *dst, addr_t uaddr, uint64 n) +{ + if(uaddr + n < uaddr || uaddr + n > KERNBASE) + return -EFAULT; + if(copyin(myproc()->pgdir, uaddr, dst, n) < 0) + return -EFAULT; + return 0; +} + +int +copy_to_user(addr_t uaddr, const void *src, uint64 n) +{ + if(uaddr + n < uaddr || uaddr + n > KERNBASE) + return -EFAULT; + if(copyout(myproc()->pgdir, uaddr, (void*)src, n) < 0) + return -EFAULT; + return 0; +} + +long +strlen_user(addr_t uaddr) +{ + addr_t a = uaddr; + + while(a - uaddr < USER_STR_MAX){ + // Scan through the per-page kernel alias so an unmapped page is a + // clean failure instead of a fault. + char *ka = uva2ka(myproc()->pgdir, (char*)a); + if(ka == 0) + return -EFAULT; + addr_t pgend = PGROUNDDOWN(a) + PGSIZE; + for(; a < pgend; a++, ka++) + if(*ka == 0) + return a - uaddr; + } + return -EFAULT; +} diff --git a/kernel/dev/bio.c b/kernel/dev/bio.c new file mode 100644 index 0000000..7685c87 --- /dev/null +++ b/kernel/dev/bio.c @@ -0,0 +1,160 @@ +// Buffer cache. +// +// The buffer cache is a linked list of buf structures holding +// cached copies of disk block contents. Caching disk blocks +// in memory reduces the number of disk reads and also provides +// a synchronization point for disk blocks used by multiple processes. +// +// Interface: +// * To get a buffer for a particular disk block, call bread. +// * After changing buffer data, call bwrite to write it to disk. +// * When done with the buffer, call brelse. +// * Do not use the buffer after calling brelse. +// * Only one process at a time can use a buffer, +// so do not keep them longer than necessary. +// +// The implementation uses two state flags internally: +// * B_VALID: the buffer data has been read from the disk. +// * B_DIRTY: the buffer data has been modified +// and needs to be written to disk. + +#include "types.h" +#include "defs.h" +#include "param.h" +#include "spinlock.h" +#include "sleeplock.h" +#include "fs.h" +#include "buf.h" + +struct { + struct spinlock lock; + struct buf buf[NBUF]; + + // Linked list of all buffers, through prev/next. + // head.next is most recently used. + struct buf head; +} bcache; + +void +binit(void) +{ + struct buf *b; + + initlock(&bcache.lock, "bcache"); +//PAGEBREAK! + + // Create linked list of buffers + bcache.head.prev = &bcache.head; + bcache.head.next = &bcache.head; + for(b = bcache.buf; b < bcache.buf+NBUF; b++){ + b->next = bcache.head.next; + b->prev = &bcache.head; + initsleeplock(&b->lock, "buffer"); + bcache.head.next->prev = b; + bcache.head.next = b; + } +} + +// Route a request to the driver that owns b->dev. Everything above this line +// is device-agnostic; everything below picks a backend. RAMDISK_DEV is served +// out of memory (kernel/dev/ramdisk.c), USBDISK_DEV by bulk transfers to a +// USB stick, everything else by the IDE driver. +static void +bdevrw(struct buf *b) +{ + if(b->dev == RAMDISK_DEV){ + ramdiskrw(b); + return; + } + if(b->dev == USBDISK_DEV){ + usb_storage_rw(b); + return; + } + iderw(b); +} + +// Look through buffer cache for block on device dev. +// If not found, allocate a buffer. +// In either case, return locked buffer. +static struct buf* +bget(uint dev, uint blockno) +{ + struct buf *b; + + acquire(&bcache.lock); + + // Is the block already cached? + for(b = bcache.head.next; b != &bcache.head; b = b->next){ + if(b->dev == dev && b->blockno == blockno){ + b->refcnt++; + release(&bcache.lock); + acquiresleep(&b->lock); + return b; + } + } + + // Not cached; recycle some unused buffer and clean buffer + // "clean" because B_DIRTY and not locked means log.c + // hasn't yet committed the changes to the buffer. + for(b = bcache.head.prev; b != &bcache.head; b = b->prev){ + if(b->refcnt == 0 && (b->flags & B_DIRTY) == 0) { + b->dev = dev; + b->blockno = blockno; + b->flags = 0; + b->refcnt = 1; + release(&bcache.lock); + acquiresleep(&b->lock); + return b; + } + } + panic("bget: no buffers"); +} + +// Return a locked buf with the contents of the indicated block. +struct buf* +bread(uint dev, uint blockno) +{ + struct buf *b; + + b = bget(dev, blockno); + if(!(b->flags & B_VALID)) { + bdevrw(b); + } + return b; +} + +// Write b's contents to disk. Must be locked. +void +bwrite(struct buf *b) +{ + if(!holdingsleep(&b->lock)) + panic("bwrite"); + b->flags |= B_DIRTY; + bdevrw(b); +} + +// Release a locked buffer. +// Move to the head of the MRU list. +void +brelse(struct buf *b) +{ + if(!holdingsleep(&b->lock)) + panic("brelse"); + + releasesleep(&b->lock); + + acquire(&bcache.lock); + b->refcnt--; + if (b->refcnt == 0) { + // no one is waiting for it. + b->next->prev = b->prev; + b->prev->next = b->next; + b->next = bcache.head.next; + b->prev = &bcache.head; + bcache.head.next->prev = b; + bcache.head.next = b; + } + + release(&bcache.lock); +} +//PAGEBREAK! diff --git a/kernel/dev/cdev.c b/kernel/dev/cdev.c new file mode 100644 index 0000000..004fe9f --- /dev/null +++ b/kernel/dev/cdev.c @@ -0,0 +1,107 @@ +#include "types.h" +#include "defs.h" +#include "param.h" +#include "spinlock.h" +#include "sleeplock.h" +#include "fs.h" +#include "file.h" +#include "x86.h" +#include "errno.h" + +extern int consoleread(struct inode*, uint, char*, int); +extern int consolewrite(struct inode*, uint, char*, int); +extern int mouseread(struct inode*, uint, char*, int); + +static uint64 rseed = 0x2A99554433221100ULL; + +int +nullwrite(struct inode *ip, uint off, char *dst, int n) +{ + return n; +} + +int +nullread(struct inode *ip, uint off, char *dst, int n) +{ + return 0; +} + +int +zeroread(struct inode *ip, uint off, char *dst, int n) +{ + memset(dst, 0, n); + return n; +} + +int +randomread(struct inode *ip, uint off, char *dst, int n) +{ + for(int i = 0; i < n; i++){ + rseed = rseed * 6364136223846793005ULL + 1442695040888963407ULL; + dst[i] = (rseed >> 32) & 0xFF; + } + return n; +} + +// /dev/full — reads as an endless run of zero bytes, every write fails with +// ENOSPC. Programs use it to exercise their out-of-disk-space paths. +int +fullwrite(struct inode *ip, uint off, char *dst, int n) +{ + return -ENOSPC; +} + +int +mousewrite(struct inode *ip, uint off, char *dst, int n) +{ + return n; +} + +int +memread(struct inode *ip, uint off, char *dst, int n) +{ + switch(ip->minor) { + case 1: return consoleread(ip, off, dst, n); + case 2: return nullread(ip, off, dst, n); + case 3: return consoleread(ip, off, dst, n); + case 5: return zeroread(ip, off, dst, n); + case 7: return zeroread(ip, off, dst, n); // /dev/full reads as zeros + case 8: + case 9: return randomread(ip, off, dst, n); + default: return -1; + } +} + +int +memwrite(struct inode *ip, uint off, char *dst, int n) +{ + switch(ip->minor) { + case 1: return consolewrite(ip, off, dst, n); + case 7: return fullwrite(ip, off, dst, n); // /dev/full: always ENOSPC + case 2: + case 5: + case 8: + case 9: return nullwrite(ip, off, dst, n); + default: return -1; + } +} + +struct devsw devsw[] = { + [0] = { 0, 0 }, + [1] = { memread, memwrite }, + // /dev/fb0 (major 2) — the framebuffer, kernel/dev/fb.c. This used to be + // { nullread, nullwrite }, so writing pixels to it succeeded and did + // nothing. + // The close hook gives the text console its screen back when whatever was + // drawing on the framebuffer exits — see fbclose(). + [2] = { fbread, fbwrite, 0, fbclose }, + [3] = { mouseread, mousewrite }, + [4] = { 0, 0 }, + // /dev/tty (major 5) — alias for the console + [5] = { memread, memwrite }, + // /proc status files (major 6) — see kernel/dev/procfs.c + [6] = { procread, procwrite }, + // /dev/input/eventN (major 7) — raw evdev events, kernel/dev/input.c. The + // minor is the device index, so event0 is the keyboard and event1 the mouse. + [7] = { inputread, inputwrite, inputready, inputclose, inputopen }, +}; diff --git a/kernel/dev/device.c b/kernel/dev/device.c new file mode 100644 index 0000000..db0906a --- /dev/null +++ b/kernel/dev/device.c @@ -0,0 +1,308 @@ +// The device model. See include/device.h for why it exists. +// +// A fixed table rather than a linked list: this is populated once during boot +// by whichever bus found each device, and read afterwards. Nothing is removed +// yet -- hot-unplug would need it, and when that arrives the in_use flag is +// already the hook for it. +// +// No lock. Every device_add() happens during single-threaded boot, before +// userspace exists, and every reader afterwards only walks. If a bus ever +// enumerates at runtime -- USB hotplug is the obvious one -- this needs a +// spinlock, and the reader in procfs needs to take it. + +#include "types.h" +#include "defs.h" +#include "param.h" +#include "device.h" +#include "display.h" +#include "theme.h" +#include "text.h" + +static struct device devices[NDEVICE]; +static int ndevices; + +static const char *bus_names[DEVBUS_NBUS] = { + [DEVBUS_PLATFORM] = "platform", + [DEVBUS_PCI] = "pci", + [DEVBUS_USB] = "usb", +}; + +static const char *class_names[DEVCLASS_NCLASS] = { + [DEVCLASS_OTHER] = "other", + [DEVCLASS_DISPLAY] = "display", + [DEVCLASS_INPUT] = "input", + [DEVCLASS_STORAGE] = "storage", + [DEVCLASS_NET] = "net", + [DEVCLASS_BUS] = "bus", +}; + +const char * +dev_bus_name(enum dev_bus b) +{ + if (b < 0 || b >= DEVBUS_NBUS || !bus_names[b]) + return "?"; + return bus_names[b]; +} + +const char * +dev_class_name(enum dev_class c) +{ + if (c < 0 || c >= DEVCLASS_NCLASS || !class_names[c]) + return "?"; + return class_names[c]; +} + +// How many devices already share this base name, so "vga" becomes vga0, vga1. +static int +count_base(const char *base) +{ + int n = 0; + for (int i = 0; i < ndevices; i++) { + const char *a = devices[i].name, *b = base; + while (*b && *a == *b) { a++; b++; } + // Matched the whole base and the rest is the index we appended. + if (*b == '\0' && *a >= '0' && *a <= '9') + n++; + } + return n; +} + +struct device * +device_add(const char *base, enum dev_bus bus, enum dev_class class, + const char *driver) +{ + if (ndevices >= NDEVICE) + return 0; + + struct device *d = &devices[ndevices++]; + d->bus = bus; + d->class = class; + d->driver = driver; + d->in_use = 1; + + int idx = count_base(base); + int n = 0; + while (base[n] && n < DEV_NAMELEN - 3) { + d->name[n] = base[n]; + n++; + } + // Single digit is enough for NDEVICE of 32 per base name, and a two-digit + // path that is never taken is a two-digit path that is never tested. + if (idx > 9) + idx = 9; + d->name[n++] = (char)('0' + idx); + d->name[n] = '\0'; + + return d; +} + +void +device_set_desc(struct device *d, const char *s) +{ + if (!d) + return; + int n = 0; + while (s[n] && n < (int)sizeof(d->desc) - 1) { + d->desc[n] = s[n]; + n++; + } + d->desc[n] = '\0'; +} + +struct device * +device_at(int i) +{ + if (i < 0 || i >= ndevices) + return 0; + return &devices[i]; +} + +int +device_count(void) +{ + return ndevices; +} + +struct device * +device_find_class(enum dev_class class) +{ + for (int i = 0; i < ndevices; i++) + // Unclaimed devices are deliberately skipped. A display nobody drives is + // not a display you can draw on, and returning it would push the "is there + // a driver" check out to every caller. + if (devices[i].class == class && devices[i].driver) + return &devices[i]; + return 0; +} + +// --- the display class ----------------------------------------------------- +// +// Thin wrappers over device_find_class() plus a null check on each op. They +// exist so that consumers are not each written around a vtable that may be +// absent -- a compositor asking to present on a machine with no display driver +// should be a no-op, not a null dereference in the middle of a frame. + +struct device * +display_get(void) +{ + return device_find_class(DEVCLASS_DISPLAY); +} + +int +display_mode(struct display_mode *out) +{ + struct device *d = display_get(); + if (!d || !d->ops || !((struct display_ops *)d->ops)->mode) + return -1; + ((struct display_ops *)d->ops)->mode(d, out); + return 0; +} + +void * +display_acquire(uint32 *stride_out) +{ + struct device *d = display_get(); + if (!d || !d->ops || !((struct display_ops *)d->ops)->acquire) + return 0; + return ((struct display_ops *)d->ops)->acquire(d, stride_out); +} + +void +display_present(int x, int y, int w, int h) +{ + struct device *d = display_get(); + if (!d || !d->ops || !((struct display_ops *)d->ops)->present) + return; + ((struct display_ops *)d->ops)->present(d, x, y, w, h); +} + +void +display_wait_vblank(void) +{ + struct device *d = display_get(); + if (!d || !d->ops || !((struct display_ops *)d->ops)->wait_vblank) + return; + ((struct display_ops *)d->ops)->wait_vblank(d); +} + +// Drive the whole display path once and report what happened: find the class, +// read the mode, acquire a back buffer, draw into RAM, present it. +// +// A self-test rather than a unit test because none of this can be checked +// without a display: the assertion is "did the right pixels reach the screen", +// and the only instrument for that is a screendump. Triggered with +// `echo d > /proc/sysrq` and photographed by tools/dispshot.ps1. +// +// The pattern is chosen to be wrong in visible ways. Four solid quadrants in +// theme colours catch a stride error (the bands would slant) and a bpp error +// (the colours would be wrong); the white frame one pixel inside the edge +// catches an off-by-one in the present rectangle; and the fact that it is +// drawn entirely into RAM before a single byte reaches the framebuffer is what +// double buffering means. +void +display_selftest(void) +{ + struct display_mode m; + uint32 stride; + + if (display_mode(&m) != 0) { + cprintf("display: no display bound\n"); + return; + } + cprintf("display: %dx%d %dbpp stride %d\n", m.width, m.height, m.bpp, m.stride); + + unsigned char *b = display_acquire(&stride); + if (!b) { + cprintf("display: no back buffer available\n"); + return; + } + + static const uint32 quad[4] = { + SCH_ACCENT, SCH_ACCENT_DIM, SCH_DIM, SCH_BG + }; + for (int y = 0; y < m.height; y++) { + uint32 *row = (uint32 *)(b + (uint64)y * stride); + int top = y < m.height / 2; + for (int x = 0; x < m.width; x++) { + int left = x < m.width / 2; + row[x] = quad[(top ? 0 : 2) + (left ? 0 : 1)]; + } + } + for (int x = 1; x < m.width - 1; x++) { + *(uint32 *)(b + 1 * (uint64)stride + x * 4) = SCH_FG; + *(uint32 *)(b + (uint64)(m.height - 2) * stride + x * 4) = SCH_FG; + } + for (int y = 1; y < m.height - 1; y++) { + *(uint32 *)(b + (uint64)y * stride + 1 * 4) = SCH_FG; + *(uint32 *)(b + (uint64)y * stride + (m.width - 2) * 4) = SCH_FG; + } + + // Between frames, so the blit does not land halfway down a scan-out. + display_wait_vblank(); + display_present(0, 0, m.width, m.height); + cprintf("display: presented a full frame\n"); +} + +// Draw a text specimen through the display class. `echo t > /proc/sysrq`. +// +// It exercises the three things a character-cell console cannot do, so a +// screenshot says whether the layer works rather than whether it compiled: +// glyphs on a common baseline at arbitrary x (not multiples of eight), text +// blended over an existing background rather than drawn into cleared cells, and +// codepoints above 255. +void +text_selftest(void) +{ + struct display_mode m; + uint32 stride; + + if (display_mode(&m) != 0) { + cprintf("text: no display\n"); + return; + } + unsigned char *b = display_acquire(&stride); + if (!b) { + cprintf("text: no back buffer\n"); + return; + } + + struct surface s = { b, stride, m.width, m.height }; + struct font *f = font_builtin(); + + // A background with structure in it, so blending is visible as blending + // rather than as drawing onto a flat colour. + for (int y = 0; y < m.height; y++) { + uint32 *row = (uint32 *)(b + (uint64)y * stride); + for (int x = 0; x < m.width; x++) + row[x] = ((x / 32 + y / 32) & 1) ? SCH_BG : SCH_ACCENT_DIM; + } + + int base = f->ops->baseline(f); + int line = f->ops->height(f) + 6; + int y = 80; + + text_draw(&s, f, 60, y + base, "Schunix text rendering", SCH_FG); + y += line * 2; + + // Deliberately not on a multiple of eight. On the old console this string + // could only start at x = 56 or x = 64. + text_draw(&s, f, 61, y + base, "positioned at x=61, off the cell grid", SCH_DIM); + y += line; + text_draw(&s, f, 63, y + base, "and here at x=63", SCH_DIM); + y += line * 2; + + // Above U+00FF, which a 256-entry table cannot index at all. + text_draw(&s, f, 60, y + base, + "utf-8: \xe2\x94\x8c\xe2\x94\x80\xe2\x94\x90 " + "\xe2\x96\x88\xe2\x96\x93\xe2\x96\x92\xe2\x96\x91 " + "\xc2\xb0 \xc2\xb1 \xe2\x86\x92 \xe2\x80\xa2", SCH_ACCENT); + y += line * 2; + + // Measure, then centre on it -- the thing you cannot do without metrics. + const char *msg = "measured and centred"; + int w = text_measure(f, msg); + text_draw(&s, f, (m.width - w) / 2, y + base, msg, SCH_FG); + + display_present(0, 0, m.width, m.height); + cprintf("text: drew a specimen, \"%s\" measures %d px\n", msg, w); +} diff --git a/kernel/dev/fb.c b/kernel/dev/fb.c new file mode 100644 index 0000000..8c652cf --- /dev/null +++ b/kernel/dev/fb.c @@ -0,0 +1,202 @@ +// /dev/fb0 -- the linear framebuffer as a file. +// +// Until now this node existed but was wired to major 2, whose devsw entry was +// { nullread, nullwrite }: reads returned end-of-file and writes were thrown +// away, silently. The only thing that actually worked was mmap, and only +// because sys_mmap special-cased the major number. +// +// Reads and writes here address the framebuffer by byte offset, so lseek +// positions the "cursor" and a whole frame can be written with one write(). +// That is slower than mmap and always will be -- it is a copy -- but it is +// what makes the device inspectable from a shell, which is worth a great deal +// while there is no GUI yet to inspect it with. + +#include "types.h" +#include "defs.h" +#include "param.h" +#include "mmu.h" +#include "proc.h" +#include "spinlock.h" +#include "sleeplock.h" +#include "fs.h" +#include "file.h" +#include "fb.h" +#include "uaccess.h" +#include "errno.h" + +// Describe one colour channel. A channel that does not exist gets length 0, +// which is how callers are supposed to detect "no alpha" rather than by +// assuming from bits_per_pixel. +static void +chan(struct fb_bitfield *f, unsigned int offset, unsigned int length) +{ + f->offset = offset; + f->length = length; + f->msb_right = 0; +} + +// Pixel layout for the depths vga_draw_pixel knows how to write. Keeping the +// two in step matters: this is the kernel telling userspace how to build a +// pixel, and if it disagrees with how the kernel builds one, every colour +// drawn by a program is wrong in a way that looks like a hardware fault. +static void +pixel_format(struct fb_var_screeninfo *v, unsigned int bpp) +{ + switch (bpp) { + case 32: + chan(&v->red, 16, 8); chan(&v->green, 8, 8); chan(&v->blue, 0, 8); + chan(&v->transp, 24, 0); // the byte is there; nothing honours it + break; + case 24: + chan(&v->red, 16, 8); chan(&v->green, 8, 8); chan(&v->blue, 0, 8); + chan(&v->transp, 0, 0); + break; + case 16: + // RGB565, matching the packing vga_draw_pixel does for this depth. + chan(&v->red, 11, 5); chan(&v->green, 5, 6); chan(&v->blue, 0, 5); + chan(&v->transp, 0, 0); + break; + default: + chan(&v->red, 0, 0); chan(&v->green, 0, 0); chan(&v->blue, 0, 0); + chan(&v->transp, 0, 0); + break; + } +} + +int +fbread(struct inode *ip, uint off, char *dst, int n) +{ + volatile uchar *fb = vga_fb_ptr(); + uint64 size = vga_fb_size(); + + if (!fb) + return -ENODEV; + if (n < 0) + return -EINVAL; + if ((uint64)off >= size) + return 0; // past the end of the screen: EOF + if ((uint64)off + (uint64)n > size) + n = (int)(size - (uint64)off); + + memmove(dst, (void*)(fb + off), n); + return n; +} + +// Set once something has drawn through this device, so that fbclose knows +// whether the screen still belongs to the text console. Reading the +// framebuffer, or asking it its geometry, does not count -- `fbinfo` with no +// arguments must not wipe its own output off the screen. +static int fb_dirty; + +// Called from sys_mmap when /dev/fb0 is mapped. A mapping is a licence to +// draw, and the drawing happens with no further syscalls, so this is the last +// point at which the kernel hears about it at all. +void +fb_mapped(void) +{ + fb_dirty = 1; +} + +int +fbwrite(struct inode *ip, uint off, char *src, int n) +{ + volatile uchar *fb = vga_fb_ptr(); + uint64 size = vga_fb_size(); + + if (!fb) + return -ENODEV; + if (n < 0) + return -EINVAL; + if ((uint64)off >= size) + return -ENOSPC; // a screen cannot be extended + if ((uint64)off + (uint64)n > size) + n = (int)(size - (uint64)off); + + memmove((void*)(fb + off), src, n); + fb_dirty = 1; + return n; +} + +// The last descriptor on /dev/fb0 is going away. +// +// Whatever was drawing has finished, and the screen still holds its final +// frame -- so the shell prompt underneath is invisible and the machine looks +// hung. It is not: the process exited cleanly and the console is writing +// happily into a framebuffer nobody can read any more. There is no character +// backing store to repaint from (the console draws glyphs straight into the +// framebuffer and scrolls with a memmove), so the honest recovery is to clear +// and go home rather than to pretend the old text can be restored. +void +fbclose(struct inode *ip) +{ + if (!fb_dirty) + return; + fb_dirty = 0; + + // Said out loud, because a screen clearing on its own is otherwise + // indistinguishable from a bug -- and this is the only place in the system + // that wipes the display without being asked to. + cprintf("fb0: released by pid %d, console restored\n", myproc() ? myproc()->pid : -1); + vga_screen_reset(); +} + +// FBIO* handling, called from sys_ioctl once it has established that the fd +// refers to this device. `arg` is the raw user address; each request +// validates it for the size it is actually going to touch, rather than for +// the largest struct here -- fb_fix_screeninfo is half the size of +// fb_var_screeninfo, and a buffer sitting near the end of a mapping is a +// legitimate place to put the smaller one. +int +fb_ioctl(int request, addr_t arg) +{ + int w, h; + + if (!vga_fb_ptr()) + return -ENODEV; + + vga_dims(&w, &h); + + switch (request) { + case FBIOGET_VSCREENINFO: { + struct fb_var_screeninfo *v = + (struct fb_var_screeninfo *)user_ptr(arg, sizeof(*v)); + if (!v) + return -EFAULT; + memset(v, 0, sizeof(*v)); + v->xres = v->xres_virtual = (unsigned int)w; + v->yres = v->yres_virtual = (unsigned int)h; + v->bits_per_pixel = vga_fb_bpp(); + v->activate = FB_ACTIVATE_NOW; + pixel_format(v, v->bits_per_pixel); + // height/width (physical millimetres) and every timing field stay zero: + // VBE does not report them and a guess would be worse than an admission. + return 0; + } + + case FBIOGET_FSCREENINFO: { + struct fb_fix_screeninfo *f = + (struct fb_fix_screeninfo *)user_ptr(arg, sizeof(*f)); + if (!f) + return -EFAULT; + memset(f, 0, sizeof(*f)); + safestrcpy(f->id, "schunixfb", sizeof(f->id)); + f->smem_start = (unsigned long)vga_fb_phys(); + f->smem_len = (unsigned int)vga_fb_size(); + f->type = FB_TYPE_PACKED_PIXELS; + f->visual = FB_VISUAL_TRUECOLOR; + f->line_length = vga_fb_stride(); + return 0; + } + + // The mode came from the bootloader's VBE call and we have no way to + // change it. Saying so is more useful than accepting the request and + // leaving the caller to discover, by drawing, that nothing happened. + case FBIOPUT_VSCREENINFO: + return -EINVAL; + + case FBIOBLANK: + return -EINVAL; + } + + return -ENOTTY; +} diff --git a/kernel/dev/ide.c b/kernel/dev/ide.c new file mode 100644 index 0000000..be33f9c --- /dev/null +++ b/kernel/dev/ide.c @@ -0,0 +1,208 @@ +// Simple PIO-based (non-DMA) IDE driver code. + +#include "types.h" +#include "defs.h" +#include "irq.h" +#include "param.h" +#include "memlayout.h" +#include "mmu.h" +#include "proc.h" +#include "x86.h" +#include "traps.h" +#include "spinlock.h" +#include "sleeplock.h" +#include "fs.h" +#include "buf.h" + +#define SECTOR_SIZE 512 +#define IDE_BSY 0x80 +#define IDE_DRDY 0x40 +#define IDE_DF 0x20 +#define IDE_ERR 0x01 + +#define IDE_CMD_READ 0x20 +#define IDE_CMD_WRITE 0x30 +#define IDE_CMD_RDMUL 0xc4 +#define IDE_CMD_WRMUL 0xc5 + +// idequeue points to the buf now being read/written to the disk. +// idequeue->qnext points to the next buf to be processed. +// You must hold idelock while manipulating queue. + +static struct spinlock idelock; +static struct buf *idequeue; + +static int havedisk0; +static int havedisk1; +static void idestart(struct buf*); + +// Wait for the drive to become ready, or give up. +// +// This used to be an unbounded `while`. On a machine with nothing attached to +// the primary channel the status register reads 0xFF forever, so the very +// first call from ideinit() hung the kernel -- no panic, no message, just a +// dead machine right after the VGA banner. That is not an exotic +// configuration: it is every CD boot, and most hardware newer than IDE. +// +// The bound is deliberately enormous. A real drive answers in microseconds; +// this only expires when there is nothing there at all. +static int +idewait(int checkerr) +{ + int r = 0; + + for(int i = 0; i < 20000000; i++){ + r = inb(0x1f7); + if((r & (IDE_BSY|IDE_DRDY)) == IDE_DRDY){ + if(checkerr && (r & (IDE_DF|IDE_ERR)) != 0) + return -1; + return 0; + } + } + return -1; +} + +static void +ide_irq(void *arg) +{ + (void)arg; + ideintr(); +} + +void +ideinit(void) +{ + initlock(&idelock, "ide"); + // ISA signalling: edge-triggered, active high. Everything at a fixed + // legacy port is wired this way; PCI devices are not. + irq_register(IRQ_IDE, ide_irq, 0, "ide", 0); + + // A timeout here means there is no drive on this channel. Say so and leave + // havedisk0/1 clear: with a ramdisk root there is nothing else the system + // needs from IDE, and booting on is strictly better than stopping. + if(idewait(0) < 0){ + cprintf("ide: no drive on the primary channel\n"); + return; + } + havedisk0 = 1; + + // Check if disk 1 is present + outb(0x1f6, 0xe0 | (1<<4)); + for(int i=0; i<1000; i++){ + if(inb(0x1f7) != 0){ + havedisk1 = 1; + break; + } + } + + // Switch back to disk 0. + outb(0x1f6, 0xe0 | (0<<4)); +} + +// Is there actually a drive there? Callers that probe -- rather than callers +// that know what they are looking for -- have to ask, because reading an +// absent drive is a bug and iderw() treats it as one. +int +ide_disk_present(int dev) +{ + return dev == 0 ? havedisk0 : havedisk1; +} + +// Start the request for b. Caller must hold idelock. +static void +idestart(struct buf *b) +{ + if(b == 0) + panic("idestart"); + if(b->blockno >= FSSIZE) + panic("incorrect blockno"); + int sector_per_block = BSIZE/SECTOR_SIZE; + int sector = b->blockno * sector_per_block; + int read_cmd = (sector_per_block == 1) ? IDE_CMD_READ : IDE_CMD_RDMUL; + int write_cmd = (sector_per_block == 1) ? IDE_CMD_WRITE : IDE_CMD_WRMUL; + + if (sector_per_block > 7) panic("idestart"); + + idewait(0); + outb(0x3f6, 0); // generate interrupt + outb(0x1f2, sector_per_block); // number of sectors + outb(0x1f3, sector & 0xff); + outb(0x1f4, (sector >> 8) & 0xff); + outb(0x1f5, (sector >> 16) & 0xff); + outb(0x1f6, 0xe0 | ((b->dev&1)<<4) | ((sector>>24)&0x0f)); + if(b->flags & B_DIRTY){ + outb(0x1f7, write_cmd); + outsl(0x1f0, b->data, BSIZE/4); + } else { + outb(0x1f7, read_cmd); + } +} + +// Interrupt handler. +void +ideintr(void) +{ + struct buf *b; + + // First queued buffer is the active request. + acquire(&idelock); + if((b = idequeue) == 0){ + release(&idelock); + // cprintf("spurious IDE interrupt\n"); + return; + } + idequeue = b->qnext; + + // Read data if needed. + if(!(b->flags & B_DIRTY) && idewait(1) >= 0) + insl(0x1f0, b->data, BSIZE/4); + + // Wake process waiting for this buf. + b->flags |= B_VALID; + b->flags &= ~B_DIRTY; + wakeup(b); + + // Start disk on next buf in queue. + if(idequeue != 0) + idestart(idequeue); + + release(&idelock); +} + +//PAGEBREAK! +// Sync buf with disk. +// If B_DIRTY is set, write buf to disk, clear B_DIRTY, set B_VALID. +// Else if B_VALID is not set, read buf from disk, set B_VALID. +void +iderw(struct buf *b) +{ + struct buf **pp; + + if(!holdingsleep(&b->lock)) + panic("iderw: buf not locked"); + if((b->flags & (B_VALID|B_DIRTY)) == B_VALID) + panic("iderw: nothing to do"); + if(b->dev != 0 && !havedisk1) + panic("iderw: ide disk 1 not present"); + if(b->dev == 0 && !havedisk0) + panic("iderw: ide disk 0 not present"); + + acquire(&idelock); //DOC:acquire-lock + + // Append b to idequeue. + b->qnext = 0; + for(pp=&idequeue; *pp; pp=&(*pp)->qnext) //DOC:insert-queue + ; + *pp = b; + + // Start disk if necessary. + if(idequeue == b) + idestart(b); + + // Wait for request to finish. + while((b->flags & (B_VALID|B_DIRTY)) != B_VALID){ + sleep(b, &idelock); + } + + release(&idelock); +} diff --git a/kernel/dev/input.c b/kernel/dev/input.c new file mode 100644 index 0000000..7f019ac --- /dev/null +++ b/kernel/dev/input.c @@ -0,0 +1,394 @@ +// Raw input event devices -- /dev/input/event0 and /dev/input/event1. +// +// Everything that produces input feeds this: the PS/2 keyboard and mouse, and +// the USB HID driver. The console keeps its own path and is unaffected; these +// nodes are a *second* consumer of the same interrupts, so a program can read +// keys while the shell still gets characters. +// +// Two devices rather than one because they are two devices. A compositor +// grabs the pointer and leaves the keyboard alone, or the other way round, and +// merging them into a single stream makes that impossible to express. + +#include "types.h" +#include "defs.h" +#include "param.h" +#include "mmu.h" +#include "proc.h" +#include "spinlock.h" +#include "sleeplock.h" +#include "fs.h" +#include "file.h" +#include "input.h" +#include "errno.h" + +#define NEVENT 256 // per device; ~10 seconds of frantic typing + +struct input_dev { + struct spinlock lock; + struct input_event ev[NEVENT]; + uint r, w; // free-running; the difference is the count + + // Exclusive grab (EVIOCGRAB). While set, keys still arrive here but stop + // being handed to the console's line discipline. + // + // Without this a full-screen program does not really own the keyboard: it + // reads /dev/input/event0 *and* the shell behind it reads the same keystrokes + // off the tty. The observed failure was a full-screen program quietly typing + // its entire session into the shell underneath, so the first Enter after + // exiting ran whatever had accumulated. The grab is also exactly what a + // compositor needs, which is why it lives here rather than in any one client. + int grabbed; + + // How many descriptors are open on this device. + // + // Events are only queued while this is non-zero, and the backlog is dropped + // on the first open. Both halves matter: without them the ring is a log of + // every key pressed since boot, and the next program to open the device is + // handed the lot as though the user had just typed it. + int opens; +}; + +static struct input_dev idev[NINPUTDEV]; +static int input_up; + +extern uint ticks; + +void +inputinit(void) +{ + int i; + + for(i = 0; i < NINPUTDEV; i++) + initlock(&idev[i].lock, "input"); + + // Producers run from interrupt handlers and can fire before this point in + // boot. Until the locks exist, an event has nowhere safe to go. + __sync_synchronize(); + input_up = 1; +} + +// Append one event. Called from interrupt context, so it never sleeps and +// never blocks on a full ring. +void +input_report(int dev, int type, int code, int value) +{ + struct input_dev *d; + struct input_event *e; + + if(!input_up || dev < 0 || dev >= NINPUTDEV) + return; + d = &idev[dev]; + + // Nobody is listening, so there is nowhere for this to go. Linux does the + // same: an evdev device with no clients does not accumulate. + if(d->opens == 0) + return; + + acquire(&d->lock); + + // A full ring drops the *oldest* event, not the newest. Dropping the newest + // is the obvious choice and it is wrong: the event most likely to be lost is + // then a key release, and a lost release leaves the key held down forever + // as far as every reader is concerned. Losing the oldest costs history, not + // state. + if(d->w - d->r >= NEVENT) + d->r++; + + e = &d->ev[d->w % NEVENT]; + e->time.tv_sec = ticks / 100; + e->time.tv_usec = (ticks % 100) * 10000; + e->type = (uint16)type; + e->code = (uint16)code; + e->value = (int32)value; + d->w++; + + release(&d->lock); +} + +// End a report. A reader that acts on each event as it arrives will see a +// diagonal mouse move as two separate moves; the SYN is how it knows the two +// axes belong to the same physical motion. +void +input_sync(int dev) +{ + if(!input_up || dev < 0 || dev >= NINPUTDEV) + return; + input_report(dev, EV_SYN, SYN_REPORT, 0); + wakeup(&idev[dev]); +} + +int +input_ready(int dev) +{ + int n; + + if(!input_up || dev < 0 || dev >= NINPUTDEV) + return 0; + acquire(&idev[dev].lock); + n = idev[dev].w != idev[dev].r; + release(&idev[dev].lock); + return n; +} + +// A descriptor has been opened on this device. +// +// The backlog is discarded, which is the whole point. Events queued before a +// program opened the device were meant for whoever had it *then* -- almost +// always the shell, which already consumed them as a command line. Replaying +// them into the new owner means a program is handed the keystrokes that +// launched it, as input, the instant it starts. That was a real crash and not a +// cosmetic one: the replayed characters were interpreted as commands, and one of +// them asked for a mode the program had not initialised yet. +// +// Linux gets this for free by giving every open its own client queue. One +// shared ring plus "start from now" is the same answer for a single reader, +// which is the case that exists. +void +inputopen(struct inode *ip) +{ + int dev = ip->minor; + + if(dev < 0 || dev >= NINPUTDEV) + return; + acquire(&idev[dev].lock); + idev[dev].opens++; + idev[dev].r = idev[dev].w; // start from now, not from boot + release(&idev[dev].lock); +} + +// The devsw ready hook: would a read block? +int +inputready(struct inode *ip) +{ + return input_ready(ip->minor); +} + +// Is this device grabbed? Asked by the keyboard drivers before they hand a +// character to the console. +int +input_is_grabbed(int dev) +{ + if(!input_up || dev < 0 || dev >= NINPUTDEV) + return 0; + return idev[dev].grabbed; +} + +// EVIOCGRAB. Linux's semantics: one holder at a time, and it is not an error +// to grab what you already hold. +int +input_grab(int dev, int on) +{ + if(!input_up || dev < 0 || dev >= NINPUTDEV) + return -ENODEV; + on = on ? 1 : 0; + if(idev[dev].grabbed != on){ + idev[dev].grabbed = on; + // Said out loud. A grabbed keyboard is a console that has gone deaf on + // purpose, and that is indistinguishable from one that has gone deaf by + // accident unless the kernel says which it was. + cprintf("input: event%d %s by pid %d\n", dev, + on ? "grabbed" : "released", myproc() ? myproc()->pid : -1); + } + return 0; +} + +// The last descriptor on this device is going away. +// +// Releasing the grab here is not tidiness, it is the difference between a +// crashed game and a machine you have to reboot: a program that grabs the +// keyboard and dies without ungrabbing would leave the console permanently +// deaf. The close hook makes the release unconditional. +void +inputclose(struct inode *ip) +{ + int dev = ip->minor; + + if(dev < 0 || dev >= NINPUTDEV) + return; + if(idev[dev].opens > 0) + idev[dev].opens--; + if(idev[dev].grabbed){ + idev[dev].grabbed = 0; + cprintf("input: event%d grab released\n", dev); + } +} + +// ioctl on /dev/input/eventN. Only EVIOCGRAB so far; the EVIOCG* query +// requests (name, id, capability bitmaps) can arrive when something needs +// them. +int +input_ioctl(struct inode *ip, int request, addr_t arg) +{ + int on; + + if(request == EVIOCGRAB){ + // Linux passes the flag *by value* in the argument, not by pointer. + on = (int)arg; + return input_grab(ip->minor, on); + } + return -ENOTTY; +} + +// A read returns whole events only. Handing back a partial struct would +// desynchronise the reader permanently -- every later event would be split +// across two reads and reassembled wrong. +int +inputread(struct inode *ip, uint off, char *dst, int n) +{ + struct input_dev *d; + int got = 0; + int dev = ip->minor; + + if(dev < 0 || dev >= NINPUTDEV) + return -ENODEV; + if(n < (int)sizeof(struct input_event)) + return -EINVAL; + d = &idev[dev]; + + acquire(&d->lock); + while(d->r == d->w){ + if(myproc()->killed){ + release(&d->lock); + return -EINTR; + } + sleep(d, &d->lock); + } + + while(d->r != d->w && got + (int)sizeof(struct input_event) <= n){ + memmove(dst + got, &d->ev[d->r % NEVENT], sizeof(struct input_event)); + d->r++; + got += sizeof(struct input_event); + } + release(&d->lock); + return got; +} + +// evdev accepts writes to set keyboard LEDs. There are no LEDs to set here, +// and a program that writes one should not see an error for it. +int +inputwrite(struct inode *ip, uint off, char *src, int n) +{ + return n; +} + +// --------------------------------------------------------------------------- +// Producers +// --------------------------------------------------------------------------- + +// AT scancode set 1, E0-prefixed. The unprefixed bytes need no table at all: +// Linux's keycodes were numbered from this set and still match it below 0x54. +// These are the ones that do not. +static const struct { uint8 sc; uint8 key; } e0map[] = { + {0x1C, 96}, // KP_ENTER + {0x1D, KEY_RIGHTCTRL}, + {0x35, 98}, // KP_SLASH + {0x38, KEY_RIGHTALT}, + {0x47, KEY_HOME}, + {0x48, KEY_UP}, + {0x49, KEY_PAGEUP}, + {0x4B, KEY_LEFT}, + {0x4D, KEY_RIGHT}, + {0x4F, KEY_END}, + {0x50, KEY_DOWN}, + {0x51, KEY_PAGEDOWN}, + {0x52, KEY_INSERT}, + {0x53, KEY_DELETE}, + {0x5B, KEY_LEFTMETA}, + {0x5C, KEY_RIGHTMETA}, +}; + +// One byte from the PS/2 port, before it has been turned into a character. +void +input_ps2_scancode(int raw, int e0) +{ + int code = raw & 0x7F; + int down = (raw & 0x80) ? 0 : 1; + int key = 0; + unsigned i; + + if(e0){ + for(i = 0; i < sizeof(e0map)/sizeof(e0map[0]); i++) + if(e0map[i].sc == code){ + key = e0map[i].key; + break; + } + if(key == 0) + return; // an E0 key nothing has a name for + } else { + if(code == 0 || code > 0x58) + return; + key = code; + } + + input_report(INPUT_KBD, EV_KEY, key, down); + input_sync(INPUT_KBD); +} + +// USB HID usage -> Linux keycode. The mapping is not derivable: HID numbers +// keys by position on a US layout starting at 'a', and the AT set numbers them +// by the order IBM wired the matrix. The table is the same one Linux carries +// for the same reason. +static const uint8 hid_keycode[] = { + 0, 0, 0, 0, 30, 48, 46, 32, 18, 33, 34, 35, 23, 36, 37, 38, + 50, 49, 24, 25, 16, 19, 31, 20, 22, 47, 17, 45, 21, 44, 2, 3, + 4, 5, 6, 7, 8, 9, 10, 11, 28, 1, 14, 15, 57, 12, 13, 26, + 27, 43, 43, 39, 40, 41, 51, 52, 53, 58, 59, 60, 61, 62, 63, 64, + 65, 66, 67, 68, 87, 88, 99, 70,119,110,102,104,111,107,109,106, + 105,108,103, 69, 98, 55, 74, 78, 96, 79, 80, 81, 75, 76, 77, 71, + 72, 73, 82, 83, 86,127,116,117, +}; + +// The eight modifier bits in a boot report, in bit order. +static const uint8 hid_modkey[8] = { + KEY_LEFTCTRL, KEY_LEFTSHIFT, KEY_LEFTALT, KEY_LEFTMETA, + KEY_RIGHTCTRL, KEY_RIGHTSHIFT, KEY_RIGHTALT, KEY_RIGHTMETA, +}; + +int +input_hid_keycode(int usage) +{ + if(usage >= 0xE0 && usage <= 0xE7) + return hid_modkey[usage - 0xE0]; + if(usage < 0 || usage >= (int)sizeof(hid_keycode)) + return 0; + return hid_keycode[usage]; +} + +void +input_key(int usage, int down) +{ + int key = input_hid_keycode(usage); + + if(key == 0) + return; + input_report(INPUT_KBD, EV_KEY, key, down); + input_sync(INPUT_KBD); +} + +// One mouse report: relative motion plus the current button state. `buttons` +// is a bitmask in the order both PS/2 and HID boot protocol use -- left, +// right, middle -- which is why neither caller has to reorder it. +void +input_mouse(int dx, int dy, int buttons) +{ + static int last; + static const int btncode[3] = { BTN_LEFT, BTN_RIGHT, BTN_MIDDLE }; + int i; + + if(dx) + input_report(INPUT_MOUSE, EV_REL, REL_X, dx); + if(dy) + input_report(INPUT_MOUSE, EV_REL, REL_Y, dy); + + // Only transitions. A button held down for a second is one press event, not + // a hundred -- a reader that has to filter repeats itself cannot tell a held + // button from a very fast double-click. + for(i = 0; i < 3; i++){ + int now = (buttons >> i) & 1; + if(now != ((last >> i) & 1)) + input_report(INPUT_MOUSE, EV_KEY, btncode[i], now); + } + last = buttons; + + input_sync(INPUT_MOUSE); +} diff --git a/kernel/dev/irq.c b/kernel/dev/irq.c new file mode 100644 index 0000000..578b834 --- /dev/null +++ b/kernel/dev/irq.c @@ -0,0 +1,214 @@ +// Hardware interrupt registration. See include/irq.h for why this exists. + +#include "types.h" +#include "defs.h" +#include "param.h" +#include "traps.h" +#include "spinlock.h" +#include "irq.h" + +struct irq_action { + irq_handler_t fn; + void *arg; + const char *name; + uint64 count; +}; + +static struct { + struct irq_action act[NCHAIN]; + int n; // handlers installed; only ever grows during boot + int enabled; + int level_low; + uint64 unhandled; +} line[NIRQ]; + +// Guards registration against itself. The *dispatch* path deliberately does +// not take it: an interrupt handler that blocked on a lock held by whoever +// was registering would deadlock the CPU it fired on. Instead registration +// publishes an entry by writing all of its fields, issuing a barrier, and +// only then incrementing n -- so a concurrent dispatcher either does not see +// the entry at all or sees it complete. +// +// Removal is the harder direction, because compacting the array *does* move +// entries a dispatcher may be walking. irq_unregister therefore masks the +// line first and only re-routes it once the chain has been rewritten: with +// the line masked no new interrupt can arrive, and any handler already +// running holds no reference past its own return. +static struct spinlock irqlock; + +void +irq_init(void) +{ + initlock(&irqlock, "irq"); +} + +// IRQ_ERROR and IRQ_SPURIOUS are not IOAPIC pins -- they are local APIC +// vectors that xv6 numbered as though they were, so T_IRQ0+19 and T_IRQ0+31 +// are already spoken for. A PCI device routed to pin 19 would deliver on the +// same vector as the LAPIC error interrupt. Refuse it here rather than let it +// be debugged later. +static int +reserved(int irq) +{ + return irq == IRQ_ERROR || irq == IRQ_SPURIOUS || irq == IRQ_TIMER; +} + +int +irq_register(int irq, irq_handler_t fn, void *arg, const char *name, + int level_low) +{ + struct irq_action *a; + + if(irq < 0 || irq >= NIRQ || fn == 0) + return -1; + if(reserved(irq)){ + cprintf("irq_register: %s wants irq %d, which is reserved\n", name, irq); + return -1; + } + + acquire(&irqlock); + + if(line[irq].n >= NCHAIN){ + release(&irqlock); + cprintf("irq_register: irq %d chain full, %s not attached\n", irq, name); + return -1; + } + + // Two devices sharing a line must agree on how it is signalled; they + // physically cannot be wired both ways. + if(line[irq].n > 0 && line[irq].level_low != level_low){ + release(&irqlock); + cprintf("irq_register: irq %d shared with conflicting trigger mode " + "(%s)\n", irq, name); + return -1; + } + + a = &line[irq].act[line[irq].n]; + a->fn = fn; + a->arg = arg; + a->name = name; + a->count = 0; + __sync_synchronize(); // publish the entry before it is reachable + line[irq].n++; + + if(!line[irq].enabled){ + line[irq].level_low = level_low; + ioapicroute(irq, 0, level_low); + line[irq].enabled = 1; + } + + release(&irqlock); + return 0; +} + +void +irq_unregister(int irq, irq_handler_t fn, void *arg) +{ + int i, j; + + if(irq < 0 || irq >= NIRQ) + return; + + acquire(&irqlock); + for(i = 0; i < line[irq].n; i++){ + if(line[irq].act[i].fn == fn && line[irq].act[i].arg == arg){ + // Mask before touching the chain, always -- not only when this is the + // last handler. Compacting the array moves entries that a dispatcher on + // another CPU may be walking, so the line has to be quiet for the + // duration, and then brought back only if anything is left on it. + ioapicmask(irq); + line[irq].enabled = 0; + + for(j = i; j + 1 < line[irq].n; j++) + line[irq].act[j] = line[irq].act[j+1]; + line[irq].n--; + __sync_synchronize(); + + if(line[irq].n > 0){ + ioapicroute(irq, 0, line[irq].level_low); + line[irq].enabled = 1; + } + break; + } + } + release(&irqlock); +} + +void +irq_dispatch(int irq) +{ + int i, n; + + if(irq < 0 || irq >= NIRQ) + return; + + n = line[irq].n; + if(n == 0){ + line[irq].unhandled++; + return; + } + + // Every handler on a shared line is offered the interrupt; each one is + // responsible for recognising that its device is idle and returning. + for(i = 0; i < n; i++){ + line[irq].act[i].count++; + line[irq].act[i].fn(line[irq].act[i].arg); + } +} + +// --------------------------------------------------------------------------- +// /proc/interrupts +// --------------------------------------------------------------------------- + +static int +emit(char *buf, int cap, int off, const char *s) +{ + while(*s && off < cap) + buf[off++] = *s++; + return off; +} + +static int +emit_dec(char *buf, int cap, int off, uint64 v, int width) +{ + char tmp[24]; + int n = 0, i; + + if(v == 0) + tmp[n++] = '0'; + while(v > 0){ + tmp[n++] = '0' + (v % 10); + v /= 10; + } + for(i = n; i < width && off < cap; i++) + buf[off++] = ' '; + while(n > 0 && off < cap) + buf[off++] = tmp[--n]; + return off; +} + +int +irq_info(char *buf, int cap) +{ + int irq, i, off = 0; + + for(irq = 0; irq < NIRQ; irq++){ + if(line[irq].n == 0 && line[irq].unhandled == 0) + continue; + off = emit_dec(buf, cap, off, irq, 4); + off = emit(buf, cap, off, ": "); + for(i = 0; i < line[irq].n; i++){ + off = emit_dec(buf, cap, off, line[irq].act[i].count, 10); + off = emit(buf, cap, off, " "); + off = emit(buf, cap, off, + line[irq].act[i].name ? line[irq].act[i].name : "?"); + off = emit(buf, cap, off, i + 1 < line[irq].n ? ", " : ""); + } + if(line[irq].n == 0) + off = emit(buf, cap, off, "(unhandled)"); + if(line[irq].unhandled) + off = emit(buf, cap, off, " [spurious]"); + off = emit(buf, cap, off, line[irq].level_low ? " level\n" : " edge\n"); + } + return off; +} diff --git a/kernel/dev/kbd.c b/kernel/dev/kbd.c new file mode 100644 index 0000000..2808870 --- /dev/null +++ b/kernel/dev/kbd.c @@ -0,0 +1,75 @@ +#include "types.h" +#include "x86.h" +#include "defs.h" +#include "kbd.h" +#include "input.h" + +int +kbdgetc(void) +{ + static uint shift; + static uchar *charcode[4] = { + normalmap, shiftmap, ctlmap, ctlmap + }; + uint st, data, c; + + st = inb(KBSTATP); + if((st & KBS_DIB) == 0) + return -1; + + if(st & 0x20) + return -1; + + data = inb(KBDATAP); + + if(data == 0xE0){ + shift |= E0ESC; + return 0; + } + + // Feed /dev/input/event0 before the byte is turned into a character. This + // has to happen here rather than in a second handler because the byte can + // only be read from the port once -- whoever reads it owes it to everyone + // else. The console path below is untouched: both consumers see every key. + input_ps2_scancode(data, (shift & E0ESC) != 0); + + // While something holds an exclusive grab on the keyboard, the console gets + // nothing. The shift/caps state below is still tracked, so releasing the + // grab does not leave the line discipline believing a modifier is held. + if(input_is_grabbed(INPUT_KBD)){ + if(data & 0x80){ + data = (shift & E0ESC ? data : data & 0x7F); + shift &= ~(shiftcode[data] | E0ESC); + } else { + shift |= shiftcode[data]; + shift ^= togglecode[data]; + } + return 0; + } + + if(data & 0x80){ + data = (shift & E0ESC ? data : data & 0x7F); + shift &= ~(shiftcode[data] | E0ESC); + return 0; + } else if(shift & E0ESC){ + data |= 0x80; + shift &= ~E0ESC; + } + + shift |= shiftcode[data]; + shift ^= togglecode[data]; + c = charcode[shift & (CTL | SHIFT)][data]; + if(shift & CAPSLOCK){ + if('a' <= c && c <= 'z') + c += 'A' - 'a'; + else if('A' <= c && c <= 'Z') + c += 'a' - 'A'; + } + return c; +} + +void +kbdintr(void) +{ + consoleintr(kbdgetc); +} diff --git a/kernel/dev/mouse.c b/kernel/dev/mouse.c new file mode 100644 index 0000000..3f3a25f --- /dev/null +++ b/kernel/dev/mouse.c @@ -0,0 +1,214 @@ +#include "types.h" +#include "defs.h" +#include "irq.h" +#include "x86.h" +#include "traps.h" +#include "spinlock.h" +#include "sleeplock.h" +#include "fs.h" +#include "file.h" + +#define KBD_S_STAT 0x64 +#define KBD_S_DATA 0x60 +#define KBD_C_WRITE 0xD4 +#define MOUSE_ACK 0xFA + +struct { + struct spinlock lock; + int x; + int y; + uint8 buttons; +} mouse_state; + +static void +mouse_wait(int type) +{ + int timeout = 100000; + if(type == 0){ + while(timeout-- && !(inb(KBD_S_STAT) & 0x01)); + } else { + while(timeout-- && (inb(KBD_S_STAT) & 0x02)); + } +} + +static void +mouse_write(uchar data) +{ + mouse_wait(1); + outb(KBD_S_STAT, KBD_C_WRITE); + mouse_wait(1); + outb(KBD_S_DATA, data); +} + +static uchar +mouse_read(void) +{ + mouse_wait(0); + return inb(KBD_S_DATA); +} + +static void +mouse_irq(void *arg) +{ + (void)arg; + mouseintr(); +} + +// Move the pointer. Caller holds mouse_state.lock. +// +// Positive dy is *down*, matching both the screen and USB HID. The PS/2 +// packet reports the opposite, which is why its caller negates first -- +// there is one pointer and two protocols disagreeing about which way is up, +// and the conversion belongs at the edge rather than in here. +static void +mouse_move_locked(int dx, int dy) +{ + mouse_state.x += dx; + mouse_state.y += dy; + + if(mouse_state.x < 0) mouse_state.x = 0; + if(mouse_state.y < 0) mouse_state.y = 0; + if(mouse_state.x >= 800) mouse_state.x = 799; + if(mouse_state.y >= 600) mouse_state.y = 599; +} + +// The same pointer, moved by something that is not the PS/2 port. USB HID +// uses this; anything else that produces motion events should too. +void +mouse_inject(int dx, int dy, int buttons) +{ + acquire(&mouse_state.lock); + mouse_state.buttons = buttons; + mouse_move_locked(dx, dy); + release(&mouse_state.lock); + + input_mouse(dx, dy, buttons); +} + +void +mouseintr(void) +{ + static uchar buf[3]; + static int phase = 0; + uchar status; + + status = inb(KBD_S_STAT); + + if(!(status & 0x01) || !(status & 0x20)){ + return; + } + + uchar data = inb(KBD_S_DATA); + + acquire(&mouse_state.lock); + + if(phase == 0 && !(data & 0x08)){ + release(&mouse_state.lock); + return; + } + + buf[phase++] = data; + + if(phase == 3){ + phase = 0; + + mouse_state.buttons = buf[0] & 0x07; + int dx = (buf[0] & 0x10) ? (int)buf[1] - 256 : buf[1]; + int dy = (buf[0] & 0x20) ? (int)buf[2] - 256 : buf[2]; + + // The overflow bits mean the delta saturated and is meaningless; moving + // by it would throw the pointer across the screen. + if(!(buf[0] & 0x40) && !(buf[0] & 0x80)) + mouse_move_locked(dx, -dy); + else + dx = dy = 0; + + release(&mouse_state.lock); + + // Outside the lock: input_report takes its own, and holding two at once + // is how a lock ordering rule gets invented by accident. + input_mouse(dx, -dy, buf[0] & 0x07); + return; + } + + release(&mouse_state.lock); +} + +int get_mouse_x(void) { + int x; + acquire(&mouse_state.lock); + x = mouse_state.x; + release(&mouse_state.lock); + return x; +} + +int get_mouse_y(void) { + int y; + acquire(&mouse_state.lock); + y = mouse_state.y; + release(&mouse_state.lock); + return y; +} + +uint8 get_mouse_buttons(void) { + uint8 b; + acquire(&mouse_state.lock); + b = mouse_state.buttons; + release(&mouse_state.lock); + return b; +} + +int +mouseread(struct inode *ip, uint off, char *dst, int n) +{ + struct mouse_data m; + int size = sizeof(struct mouse_data); + + if(n < size) { + return -1; + } + + m.x = get_mouse_x(); + m.y = get_mouse_y(); + m.buttons = get_mouse_buttons(); + + memmove(dst, &m, size); + + return size; +} + +void +mouseinit(void) +{ + initlock(&mouse_state.lock, "mouse"); + mouse_state.x = 400; + mouse_state.y = 300; + + while(inb(KBD_S_STAT) & 0x01) + inb(KBD_S_DATA); + + mouse_wait(1); + outb(KBD_S_STAT, 0xA8); + + mouse_wait(1); + outb(KBD_S_STAT, 0x20); + mouse_wait(0); + uchar cb = inb(KBD_S_DATA); + + cb |= 0x02; + cb |= 0x01; + cb &= ~0x20; + + mouse_wait(1); + outb(KBD_S_STAT, 0x60); + mouse_wait(1); + outb(KBD_S_DATA, cb); + + mouse_write(0xF6); + mouse_read(); + + mouse_write(0xF4); + mouse_read(); + + irq_register(IRQ_MOUSE, mouse_irq, 0, "ps2-mouse", 0); +} \ No newline at end of file diff --git a/kernel/dev/pci.c b/kernel/dev/pci.c new file mode 100644 index 0000000..b8f8d78 --- /dev/null +++ b/kernel/dev/pci.c @@ -0,0 +1,492 @@ +// PCI bus enumeration. +// +// Everything here is done through the two-port configuration mechanism at +// 0xCF8/0xCFC: write a (bus, slot, function, register) address to one port, +// read or write the value at the other. It is the oldest and least clever way +// to reach config space, and it is the one that works everywhere without +// first having to find an ACPI table to tell you where the memory-mapped +// version lives. +// +// The scan is recursive rather than a flat sweep of all 256 buses. A flat +// sweep does find everything on a PC, but it also probes 8192 slots that are +// not there, and it cannot tell you which bridge a device sits behind -- and +// that matters as soon as interrupt routing does. + +#include "types.h" +#include "defs.h" +#include "param.h" +#include "x86.h" +#include "spinlock.h" +#include "sleeplock.h" +#include "fs.h" +#include "file.h" +#include "pci.h" +#include "device.h" + +static struct pci_dev devs[NPCI]; +static int ndevs; + +static void scan_bus(uint8 bus); + +// --------------------------------------------------------------------------- +// Configuration space access +// --------------------------------------------------------------------------- + +static uint32 +cfg_addr(uint8 bus, uint8 slot, uint8 func, uint8 off) +{ + return 0x80000000u + | ((uint32)bus << 16) + | ((uint32)slot << 11) + | ((uint32)func << 8) + | (off & 0xFC); // the low two bits are not part of the address +} + +uint32 +pci_read32(uint8 bus, uint8 slot, uint8 func, uint8 off) +{ + outl(PCI_CONFIG_ADDR, cfg_addr(bus, slot, func, off)); + return inl(PCI_CONFIG_DATA); +} + +void +pci_write32(uint8 bus, uint8 slot, uint8 func, uint8 off, uint32 val) +{ + outl(PCI_CONFIG_ADDR, cfg_addr(bus, slot, func, off)); + outl(PCI_CONFIG_DATA, val); +} + +// Config space is only addressable in dwords, so narrower reads are a shift +// and a mask of the containing dword rather than a narrower bus cycle. +uint16 +pci_read16(uint8 bus, uint8 slot, uint8 func, uint8 off) +{ + return (uint16)(pci_read32(bus, slot, func, off) >> ((off & 2) * 8)); +} + +uint8 +pci_read8(uint8 bus, uint8 slot, uint8 func, uint8 off) +{ + return (uint8)(pci_read32(bus, slot, func, off) >> ((off & 3) * 8)); +} + +void +pci_enable(struct pci_dev *d, uint16 bits) +{ + uint32 cmd = pci_read32(d->bus, d->slot, d->func, PCI_COMMAND); + pci_write32(d->bus, d->slot, d->func, PCI_COMMAND, cmd | bits); +} + +// --------------------------------------------------------------------------- +// Capabilities +// +// Everything a device can do beyond the 64 bytes of standard config header -- +// MSI, MSI-X, power management, and the whole of virtio's configuration -- is +// described by a singly linked list of capability structures living in the +// device's config space. A driver that needs any of it has to walk that list. +// --------------------------------------------------------------------------- + +// Returns the config-space offset of the first capability with this ID, or 0. +uint8 +pci_find_cap(struct pci_dev *d, uint8 id) +{ + uint8 off; + int guard; + + if(!(pci_read16(d->bus, d->slot, d->func, PCI_STATUS) & PCI_STATUS_CAPLIST)) + return 0; + + off = pci_read8(d->bus, d->slot, d->func, PCI_CAP_PTR) & 0xFC; + + // The list is built by the device, so it can be circular if the device is + // broken or if we misread it. 48 is more capabilities than the 192 usable + // bytes can hold; past that we are certainly looping. + for(guard = 0; off >= 0x40 && guard < 48; guard++){ + if(pci_read8(d->bus, d->slot, d->func, off) == id) + return off; + off = pci_read8(d->bus, d->slot, d->func, off + 1) & 0xFC; + } + return 0; +} + +// Point a device's MSI at a CPU and a vector. +// +// MSI is not a wire: it is a memory write the device performs to a magic +// address that the local APIC decodes as an interrupt. That makes it strictly +// better than INTx for anything modern -- no sharing, no level/edge question, +// and no chance of the interrupt arriving before the DMA it announces, since +// both are posted writes travelling the same path in order. +// +// Returns 0 on success, -1 if the device has no MSI capability. +int +pci_msi_enable(struct pci_dev *d, int vector, int cpu) +{ + uint8 cap = pci_find_cap(d, PCI_CAP_MSI); + uint16 ctl; + + if(cap == 0) + return -1; + + ctl = pci_read16(d->bus, d->slot, d->func, cap + 2); + + // 0xFEE00000 | (apic id << 12) is the LAPIC's message address; the data + // word is the vector, with delivery mode 000 (fixed) and edge trigger. + pci_write32(d->bus, d->slot, d->func, cap + 4, + 0xFEE00000u | ((uint32)cpu << 12)); + + if(ctl & (1 << 7)){ // 64-bit address capable: data moved to +12 + pci_write32(d->bus, d->slot, d->func, cap + 8, 0); + pci_write32(d->bus, d->slot, d->func, cap + 12, (uint32)vector); + } else { + pci_write32(d->bus, d->slot, d->func, cap + 8, (uint32)vector); + } + + // Enable, and request exactly one vector (multiple-message-enable = 0). + // Message control shares its dword with the capability ID and next pointer, + // and config space is only writable a dword at a time, so read-modify-write + // the whole thing rather than clobbering the list this capability is on. + { + uint32 dw = pci_read32(d->bus, d->slot, d->func, cap); + ctl = (ctl & ~0x0070) | 1; + dw = (dw & 0x0000FFFF) | ((uint32)ctl << 16); + pci_write32(d->bus, d->slot, d->func, cap, dw); + } + + // INTx must be masked or the device will signal both. + pci_write32(d->bus, d->slot, d->func, PCI_COMMAND, + pci_read32(d->bus, d->slot, d->func, PCI_COMMAND) | (1 << 10)); + return 0; +} + +// --------------------------------------------------------------------------- +// Base address registers +// --------------------------------------------------------------------------- + +// A BAR does not report its own size. You find it by writing all ones and +// reading back: the device leaves the bits it does not decode as zero, so the +// size is one more than the inverse of what comes back. The original value +// has to be put back afterwards -- this is a live device whose decoder is +// briefly pointed at the top of the address space while we look. +static void +read_bars(struct pci_dev *d) +{ + for (int i = 0; i < 6; i++) { + uint8 off = PCI_BAR0 + i * 4; + uint32 lo = pci_read32(d->bus, d->slot, d->func, off); + + if (lo == 0) + continue; + + pci_write32(d->bus, d->slot, d->func, off, 0xFFFFFFFF); + uint32 mask = pci_read32(d->bus, d->slot, d->func, off); + pci_write32(d->bus, d->slot, d->func, off, lo); + + if (mask == 0) + continue; + + if (lo & 1) { // I/O space + d->bar[i].is_io = 1; + d->bar[i].base = lo & ~0x3u; + d->bar[i].size = (~(mask & ~0x3u) + 1) & 0xFFFF; + continue; + } + + d->bar[i].prefetch = (lo >> 3) & 1; + + if (((lo >> 1) & 3) == 2) { // 64-bit BAR: eats the next slot + uint32 hi = pci_read32(d->bus, d->slot, d->func, off + 4); + pci_write32(d->bus, d->slot, d->func, off + 4, 0xFFFFFFFF); + uint32 hmask = pci_read32(d->bus, d->slot, d->func, off + 4); + pci_write32(d->bus, d->slot, d->func, off + 4, hi); + + d->bar[i].is_64 = 1; + d->bar[i].base = ((uint64)hi << 32) | (lo & ~0xFu); + d->bar[i].size = ~(((uint64)hmask << 32) | (mask & ~0xFu)) + 1; + i++; // the upper half is not its own BAR + continue; + } + + d->bar[i].base = lo & ~0xFu; + d->bar[i].size = (~(mask & ~0xFu) + 1) & 0xFFFFFFFF; + } +} + +// --------------------------------------------------------------------------- +// Enumeration +// --------------------------------------------------------------------------- + +static void +add_func(uint8 bus, uint8 slot, uint8 func) +{ + uint16 vendor = pci_read16(bus, slot, func, PCI_VENDOR_ID); + + if (vendor == 0xFFFF) // nothing plugged in + return; + + if (ndevs >= NPCI) { + cprintf("pci: more than %d devices, ignoring the rest\n", NPCI); + return; + } + + struct pci_dev *d = &devs[ndevs++]; + d->bus = bus; + d->slot = slot; + d->func = func; + + d->vendor = vendor; + d->device = pci_read16(bus, slot, func, PCI_DEVICE_ID); + d->revision = pci_read8(bus, slot, func, PCI_REVISION); + d->prog_if = pci_read8(bus, slot, func, PCI_PROG_IF); + d->subclass = pci_read8(bus, slot, func, PCI_SUBCLASS); + d->class = pci_read8(bus, slot, func, PCI_CLASS); + d->header_type = pci_read8(bus, slot, func, PCI_HEADER_TYPE); + d->irq = pci_read8(bus, slot, func, PCI_INTERRUPT_LINE); + + // Only header type 0 has six BARs. A bridge (type 1) has two and a very + // different layout, and reading it as though it had six would hand out + // window registers as if they were device apertures. + if ((d->header_type & 0x7F) == 0) + read_bars(d); + + // Walk down through a PCI-to-PCI bridge to whatever is behind it. + if ((d->header_type & 0x7F) == 1) { + uint8 secondary = pci_read8(bus, slot, func, PCI_SECONDARY_BUS); + if (secondary != 0 && secondary != bus) + scan_bus(secondary); + } +} + +static void +scan_bus(uint8 bus) +{ + for (uint8 slot = 0; slot < 32; slot++) { + if (pci_read16(bus, slot, 0, PCI_VENDOR_ID) == 0xFFFF) + continue; + + add_func(bus, slot, 0); + + // Bit 7 of the header type says the device has more than one function. + // Probing all eight unconditionally is harmless on real hardware but + // produces phantom entries on some emulated devices, which answer every + // function with a copy of function 0. + if (pci_read8(bus, slot, 0, PCI_HEADER_TYPE) & 0x80) + for (uint8 func = 1; func < 8; func++) + add_func(bus, slot, func); + } +} + +// --------------------------------------------------------------------------- +// Lookup +// --------------------------------------------------------------------------- + +int +pci_count(void) +{ + return ndevs; +} + +struct pci_dev * +pci_get(int i) +{ + return (i >= 0 && i < ndevs) ? &devs[i] : 0; +} + +struct pci_dev * +pci_find_class(uint8 class, uint8 subclass) +{ + for (int i = 0; i < ndevs; i++) + if (devs[i].class == class && devs[i].subclass == subclass) + return &devs[i]; + return 0; +} + +struct pci_dev * +pci_find_id(uint16 vendor, uint16 device) +{ + for (int i = 0; i < ndevs; i++) + if (devs[i].vendor == vendor && devs[i].device == device) + return &devs[i]; + return 0; +} + +// --------------------------------------------------------------------------- +// Driver binding +// --------------------------------------------------------------------------- + +static int +matches(const struct pci_match *m, struct pci_dev *d) +{ + if (m->vendor != PCI_ANY) + return m->vendor == d->vendor && + (m->device == PCI_ANY || m->device == d->device); + + // A class match, for drivers that handle a kind of device rather than a + // particular model. + return m->class == d->class && + (m->subclass == 0xFF || m->subclass == d->subclass); +} + +// Offer every unclaimed device to a driver. Called by pci_init() for each +// entry in the registry; see kernel/drivers/registry.c. +// Defined further down with the /proc/pci formatting it was written for. +static const char *class_name(uint8 class, uint8 subclass); + +// Translate a PCI class code into the bus-agnostic one the device model uses. +// +// The two vocabularies are deliberately separate. PCI's class byte describes +// how the card presents itself on that bus; DEVCLASS describes what the rest of +// the kernel can *do* with it, and a USB disk has to land in the same bucket as +// a SATA one despite sharing no numbering with it at all. +enum dev_class +pci_class_of(const struct pci_dev *d) +{ + switch (d->class) { + case PCI_CLASS_DISPLAY: return DEVCLASS_DISPLAY; + case PCI_CLASS_STORAGE: return DEVCLASS_STORAGE; + case PCI_CLASS_NETWORK: return DEVCLASS_NET; + case PCI_CLASS_SERIAL_BUS: return DEVCLASS_BUS; + default: return DEVCLASS_OTHER; + } +} + +// Human-readable description, the same text /proc/pci prints for the device. +void +pci_describe(const struct pci_dev *d, char *out, int cap) +{ + const char *s = class_name(d->class, d->subclass); + int n = 0; + while (s[n] && n < cap - 1) { + out[n] = s[n]; + n++; + } + out[n] = '\0'; +} + +void +pci_bind(const struct pci_driver *drv) +{ + for (int i = 0; i < ndevs; i++) { + struct pci_dev *d = &devs[i]; + if (d->driver) + continue; + for (const struct pci_match *m = drv->match; m->vendor || m->class; m++) { + if (!matches(m, d)) + continue; + if (drv->probe(d) == 0) { + d->driver = drv->name; + // Publish it in the bus-agnostic device list. Done here rather than in + // each driver's probe() so that a driver cannot forget to, and so the + // list is populated in bind order -- which is the order a device + // manager should show. + struct device *dev = device_add(drv->devname ? drv->devname : drv->name, + DEVBUS_PCI, pci_class_of(d), drv->name); + if (dev) { + dev->ops = (void *)drv->ops; + dev->vendor = d->vendor; + dev->product = d->device; + dev->b = d->bus; dev->s = d->slot; dev->f = d->func; + pci_describe(d, dev->desc, sizeof(dev->desc)); + } + } + break; + } + } +} + +// --------------------------------------------------------------------------- + +// Fixed-width lowercase hex, the way lspci prints addresses. +static int +put_hex(char *out, uint64 v, int digits) +{ + static const char d[] = "0123456789abcdef"; + for (int i = digits - 1; i >= 0; i--) + *out++ = d[(v >> (i * 4)) & 0xF]; + return digits; +} + +static int +put_str(char *out, const char *s) +{ + int n = 0; + while (s[n]) { out[n] = s[n]; n++; } + return n; +} + +static const char * +class_name(uint8 class, uint8 subclass) +{ + switch (class) { + case PCI_CLASS_STORAGE: + return subclass == 0x01 ? "IDE controller" + : subclass == 0x06 ? "SATA controller" + : subclass == 0x08 ? "NVMe controller" + : "storage controller"; + case PCI_CLASS_NETWORK: return "network controller"; + case PCI_CLASS_DISPLAY: return "display controller"; + case PCI_CLASS_MULTIMEDIA: return "multimedia device"; + case PCI_CLASS_BRIDGE: + return subclass == 0x00 ? "host bridge" + : subclass == 0x01 ? "ISA bridge" + : subclass == 0x04 ? "PCI bridge" + : "bridge"; + case PCI_CLASS_SERIAL_BUS: + return subclass == 0x03 ? "USB controller" : "serial bus controller"; + } + return "device"; +} + +// Fills a buffer for /proc/pci. Returns bytes written. +// +// There is no snprintf in the kernel, so this appends by hand in the same +// style as kernel/dev/procfs.c. The output is deliberately lspci-shaped -- +// "bus:slot.func vendor:device description" -- because that is the format +// anyone reading it will already know how to scan. +int +pci_info(char *buf, int cap) +{ + int len = 0; + + for (int i = 0; i < ndevs; i++) { + struct pci_dev *d = &devs[i]; + + if (len > cap - 96) // room for one more line + break; + + len += put_hex(buf + len, d->bus, 2); + buf[len++] = ':'; + len += put_hex(buf + len, d->slot, 2); + buf[len++] = '.'; + len += put_hex(buf + len, d->func, 1); + buf[len++] = ' '; + len += put_hex(buf + len, d->vendor, 4); + buf[len++] = ':'; + len += put_hex(buf + len, d->device, 4); + buf[len++] = ' '; + len += put_str(buf + len, class_name(d->class, d->subclass)); + len += put_str(buf + len, " ["); + len += put_str(buf + len, d->driver ? d->driver : "-"); + buf[len++] = ']'; + buf[len++] = '\n'; + } + + return len; +} + +void +pci_init(void) +{ + ndevs = 0; + + // If there is no host bridge at 0:0.0 there is no PCI, and every config + // read will come back 0xFFFFFFFF. Saying so once beats 8192 silent probes. + if (pci_read16(0, 0, 0, PCI_VENDOR_ID) == 0xFFFF) { + cprintf("pci: no host bridge; bus not present\n"); + return; + } + + scan_bus(0); + cprintf("pci: %d device%s\n", ndevs, ndevs == 1 ? "" : "s"); + + pci_bind_all(); +} diff --git a/kernel/dev/procfs.c b/kernel/dev/procfs.c new file mode 100644 index 0000000..95aea5a --- /dev/null +++ b/kernel/dev/procfs.c @@ -0,0 +1,431 @@ +// /proc — synthesised kernel status files. +// +// Each file is a character device node under major PROCFS_MAJOR, distinguished +// by its minor number, created by init at boot. Reading one renders the whole +// text into a scratch buffer and returns the slice starting at the caller's +// file offset, so a normal read-until-zero loop terminates. +// +// SCOPE: these are the *global* files. Per-process directories (/proc//…) +// are not here, because they need something this kernel does not have yet: +// synthetic directories, i.e. a directory whose entries are generated at +// lookup and getdents time rather than read from disk. That is a real VFS +// feature touching iget/ilock/dirlookup/getdents, and it is the natural +// companion to the SchFS work rather than a bolt-on to the current fs.c. +// The namespace (which files exist, including one directory per process) is +// kernel/fs/procfs.c; this file only generates the *content* of the global +// files. `ps` and `top` work through the former. + +#include "types.h" +#include "defs.h" +#include "irq.h" +#include "x86.h" +#include "device.h" +#include "display.h" +#include "param.h" +#include "memlayout.h" +#include "mmu.h" +#include "spinlock.h" +#include "sleeplock.h" +#include "fs.h" +#include "file.h" +#include "proc.h" +#include "uaccess.h" +#include "errno.h" + +#define PROC_BUFSZ 2048 + +// --- tiny formatting helpers (no snprintf in the kernel) ------------------- + +struct sbuf { + char *p; + int len; + int cap; +}; + +static void +sb_putc(struct sbuf *sb, char c) +{ + if (sb->len < sb->cap - 1) + sb->p[sb->len++] = c; +} + +static void +sb_puts(struct sbuf *sb, const char *s) +{ + while (*s) + sb_putc(sb, *s++); +} + +// A string in a fixed-width column, space-padded. cprintf has no width +// specifier and neither does this buffer, so tables build their own columns. +// Over-long values are allowed to run past their column rather than be +// truncated: a misaligned row is easier to read than a mangled name. +static void +sb_pad(struct sbuf *sb, const char *s, int width) +{ + int n = 0; + while (s[n]) { + sb_putc(sb, s[n]); + n++; + } + while (n++ < width) + sb_putc(sb, ' '); +} + +// Four lowercase hex digits, zero-padded. PCI and USB identifiers are always +// written this way, and a lookup table keyed on "8086:100e" does not match +// "8086:100E" or "8086:10e". +static void +sb_puthex4(struct sbuf *sb, uint32 v) +{ + static const char hex[] = "0123456789abcdef"; + for (int i = 3; i >= 0; i--) + sb_putc(sb, hex[(v >> (i * 4)) & 0xF]); +} + +static void +sb_putu(struct sbuf *sb, uint64 v) +{ + char tmp[24]; + int i = 0; + if (v == 0) { sb_putc(sb, '0'); return; } + while (v && i < (int)sizeof(tmp)) { tmp[i++] = '0' + (v % 10); v /= 10; } + while (i--) sb_putc(sb, tmp[i]); +} + +// "