Compare commits
39
Commits
7979acc2eb
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
51a2b3a932 | ||
|
|
8b4a938164 | ||
|
|
be84506fdf | ||
|
|
559c3e2875 | ||
|
|
36291cd1cc | ||
|
|
b781cf8c97 | ||
|
|
7e036c94e7 | ||
|
|
1d00c1c016 | ||
|
|
3310b3d6de | ||
|
|
93d8becce3 | ||
|
|
ffee9002a6 | ||
|
|
808e1d66fd | ||
|
|
8c09ccef66 | ||
|
|
8b6379fb7e | ||
|
|
aae4db01ad | ||
|
|
1bae77ec84 | ||
|
|
dab7d5427b | ||
|
|
0a20385e12 | ||
|
|
8ae19258b1 | ||
|
|
8de98f3ab3 | ||
|
|
d3e49a925d | ||
|
|
07191b1286 | ||
|
|
e047c1e0c0 | ||
|
|
23cc56b933 | ||
|
|
3c2eefe7f4 | ||
|
|
14c7099e3b | ||
|
|
e87bd358a6 | ||
|
|
9e66c3b249 | ||
|
|
31edff3da4 | ||
|
|
f3d475a0d5 | ||
|
|
278b208258 | ||
|
|
f8b8f9610f | ||
|
|
a81298fb24 | ||
|
|
abfd9de8f6 | ||
|
|
60ca9c78c7 | ||
|
|
2aa6360b3d | ||
|
|
32660ddd1a | ||
|
|
a51ee3339f | ||
|
|
f9e21e0f13 |
No files matched your search
@@ -6,12 +6,21 @@
|
|||||||
archives/isos/
|
archives/isos/
|
||||||
montaukos.org/downloads/*.iso
|
montaukos.org/downloads/*.iso
|
||||||
*.hdd
|
*.hdd
|
||||||
|
*.img
|
||||||
|
rpi4-firmware/
|
||||||
qemu.log
|
qemu.log
|
||||||
toolchain/local/
|
toolchain/local/
|
||||||
toolchain/build/
|
toolchain/build/
|
||||||
toolchain/src/
|
toolchain/src/
|
||||||
toolchain/sysroot/
|
toolchain/sysroot/
|
||||||
|
toolchain/sysroot-*/
|
||||||
programs/bin/
|
programs/bin/
|
||||||
|
# Non-x86 userspace builds (programs/mk/arch.mk)
|
||||||
|
programs/bin-*/
|
||||||
|
programs/obj-*/
|
||||||
|
programs/src/*/obj-*/
|
||||||
|
programs/lib/*/obj-*/
|
||||||
|
programs/lib/*/build-*/
|
||||||
programs/obj/
|
programs/obj/
|
||||||
programs/src/*/obj/
|
programs/src/*/obj/
|
||||||
programs/libs/*/obj/
|
programs/libs/*/obj/
|
||||||
|
|||||||
+124
-20
@@ -10,6 +10,32 @@ QEMUFLAGS := -smp 4 -m 2G -d int -D qemu.log -no-reboot
|
|||||||
|
|
||||||
override IMAGE_NAME := montauk-$(ARCH)
|
override IMAGE_NAME := montauk-$(ARCH)
|
||||||
|
|
||||||
|
# KERNEL_ONLY=1 builds a ramdisk holding just the boot files (no
|
||||||
|
# userspace), for bringing up a kernel on a new architecture.
|
||||||
|
KERNEL_ONLY ?= 0
|
||||||
|
|
||||||
|
# programs/ builds x86_64 into bin/, other architectures into bin-<arch>/.
|
||||||
|
ifeq ($(ARCH),x86_64)
|
||||||
|
PROGRAMS_BIN := programs/bin
|
||||||
|
else
|
||||||
|
PROGRAMS_BIN := programs/bin-$(ARCH)
|
||||||
|
endif
|
||||||
|
|
||||||
|
override EFI_LOADER_x86_64 := BOOTX64.EFI
|
||||||
|
override EFI_LOADER_aarch64 := BOOTAA64.EFI
|
||||||
|
override EFI_LOADER_riscv64 := BOOTRISCV64.EFI
|
||||||
|
override EFI_LOADER_loongarch64 := BOOTLOONGARCH64.EFI
|
||||||
|
|
||||||
|
# The host strip handles x86_64 images; other targets use their toolchain's.
|
||||||
|
ifeq ($(ARCH),x86_64)
|
||||||
|
STRIP := strip
|
||||||
|
else
|
||||||
|
STRIP := toolchain/local/bin/$(ARCH)-montauk-strip
|
||||||
|
endif
|
||||||
|
|
||||||
|
# AArch64 UEFI firmware for QEMU (Debian/Ubuntu: qemu-efi-aarch64).
|
||||||
|
AARCH64_EFI_FIRMWARE ?= /usr/share/qemu-efi-aarch64/QEMU_EFI.fd
|
||||||
|
|
||||||
# Toolchain for building the 'limine' executable for the host.
|
# Toolchain for building the 'limine' executable for the host.
|
||||||
HOST_CC := cc
|
HOST_CC := cc
|
||||||
HOST_CFLAGS := -g -O2 -pipe
|
HOST_CFLAGS := -g -O2 -pipe
|
||||||
@@ -51,29 +77,72 @@ run-hdd-x86_64: $(IMAGE_NAME).hdd
|
|||||||
$(QEMUFLAGS)
|
$(QEMUFLAGS)
|
||||||
|
|
||||||
.PHONY: run-aarch64
|
.PHONY: run-aarch64
|
||||||
|
# The machine mirrors the Raspberry Pi 4/400: Cortex-A72 cores, a GICv2, and
|
||||||
|
# keyboard and mouse behind a hub (every USB 2 device on the Pi is: the
|
||||||
|
# VL805's one USB 2 port carries an on-board hub). QEMU's hub is full-speed,
|
||||||
|
# so the transaction-translator path is only exercised on the real board.
|
||||||
|
# The kernel console is mirrored to the PL011 (-serial stdio).
|
||||||
run-aarch64: $(IMAGE_NAME).iso
|
run-aarch64: $(IMAGE_NAME).iso
|
||||||
qemu-system-$(ARCH) \
|
qemu-system-$(ARCH) \
|
||||||
-M virt \
|
-M virt,gic-version=2 \
|
||||||
-cpu cortex-a72 \
|
-cpu cortex-a72 \
|
||||||
-device ramfb \
|
-device ramfb \
|
||||||
-device qemu-xhci,id=xhci \
|
-device qemu-xhci,id=xhci \
|
||||||
-device usb-kbd,bus=xhci.0 \
|
-device usb-hub,bus=xhci.0,port=1 \
|
||||||
-device usb-mouse,bus=xhci.0 \
|
-device usb-kbd,bus=xhci.0,port=1.1 \
|
||||||
-bios /usr/share/ovmf/OVMF.fd \
|
-device usb-mouse,bus=xhci.0,port=1.2 \
|
||||||
|
-bios $(AARCH64_EFI_FIRMWARE) \
|
||||||
-cdrom $(IMAGE_NAME).iso \
|
-cdrom $(IMAGE_NAME).iso \
|
||||||
|
-serial stdio \
|
||||||
$(QEMUFLAGS)
|
$(QEMUFLAGS)
|
||||||
|
|
||||||
.PHONY: run-hdd-aarch64
|
.PHONY: run-hdd-aarch64
|
||||||
run-hdd-aarch64: $(IMAGE_NAME).hdd
|
run-hdd-aarch64: $(IMAGE_NAME).hdd
|
||||||
qemu-system-$(ARCH) \
|
qemu-system-$(ARCH) \
|
||||||
-M virt \
|
-M virt,gic-version=2 \
|
||||||
-cpu cortex-a72 \
|
-cpu cortex-a72 \
|
||||||
-device ramfb \
|
-device ramfb \
|
||||||
-device qemu-xhci,id=xhci \
|
-device qemu-xhci,id=xhci \
|
||||||
-device usb-kbd,bus=xhci.0 \
|
-device usb-hub,bus=xhci.0,port=1 \
|
||||||
-device usb-mouse,bus=xhci.0 \
|
-device usb-kbd,bus=xhci.0,port=1.1 \
|
||||||
-bios /usr/share/ovmf/OVMF.fd \
|
-device usb-mouse,bus=xhci.0,port=1.2 \
|
||||||
-hda $(IMAGE_NAME).hdd \
|
-bios $(AARCH64_EFI_FIRMWARE) \
|
||||||
|
-drive if=none,id=hd0,format=raw,file=$(IMAGE_NAME).hdd \
|
||||||
|
-device virtio-blk-pci,drive=hd0 \
|
||||||
|
-serial stdio \
|
||||||
|
$(QEMUFLAGS)
|
||||||
|
|
||||||
|
# The SD card image on QEMU, with the root read through the SDHCI driver:
|
||||||
|
# the card goes on sdhci-pci as a padded copy (QEMU wants a power-of-two card;
|
||||||
|
# RPI4_QEMU_CARD_MIB, default 4 GiB, leaves the first-boot grow room to work
|
||||||
|
# with; every run starts from a fresh copy, so every run is a first boot),
|
||||||
|
# and since QEMU's UEFI has no SD driver, the card's ESP is also
|
||||||
|
# attached alone (the card's first partition, as its own disk) over virtio
|
||||||
|
# for the firmware to boot Limine from.
|
||||||
|
RPI4_QEMU_CARD_MIB ?= 4096
|
||||||
|
.PHONY: run-rpi4-sd
|
||||||
|
run-rpi4-sd: montauk-rpi4.img
|
||||||
|
cp --sparse=always montauk-rpi4.img rpi4-sd-qemu.img
|
||||||
|
size=$$(stat -c %s rpi4-sd-qemu.img); p=$$(($(RPI4_QEMU_CARD_MIB) * 1048576)); \
|
||||||
|
while [ $$p -lt $$size ]; do p=$$((p * 2)); done; truncate -s $$p rpi4-sd-qemu.img
|
||||||
|
end=$$(PATH=$$PATH:/usr/sbin:/sbin sfdisk -d montauk-rpi4.img | awk -F'[=,]' '/montauk-rpi4.img1/ {print $$2 + $$4}'); \
|
||||||
|
dd if=montauk-rpi4.img of=rpi4-esp-qemu.img bs=512 count=$$end status=none
|
||||||
|
PATH=$$PATH:/usr/sbin:/sbin sfdisk --quiet --delete rpi4-esp-qemu.img 2 2>/dev/null || true
|
||||||
|
qemu-system-aarch64 \
|
||||||
|
-M virt,gic-version=2 \
|
||||||
|
-cpu cortex-a72 \
|
||||||
|
-device ramfb \
|
||||||
|
-device qemu-xhci,id=xhci \
|
||||||
|
-device usb-hub,bus=xhci.0,port=1 \
|
||||||
|
-device usb-kbd,bus=xhci.0,port=1.1 \
|
||||||
|
-device usb-mouse,bus=xhci.0,port=1.2 \
|
||||||
|
-bios $(AARCH64_EFI_FIRMWARE) \
|
||||||
|
-drive if=none,id=esp,format=raw,file=rpi4-esp-qemu.img \
|
||||||
|
-device virtio-blk-pci,drive=esp \
|
||||||
|
-device sdhci-pci \
|
||||||
|
-drive if=none,id=sd,format=raw,file=rpi4-sd-qemu.img \
|
||||||
|
-device sd-card,drive=sd \
|
||||||
|
-serial stdio \
|
||||||
$(QEMUFLAGS)
|
$(QEMUFLAGS)
|
||||||
|
|
||||||
.PHONY: run-riscv64
|
.PHONY: run-riscv64
|
||||||
@@ -196,13 +265,23 @@ ports: programs
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
.PHONY: ramdisk
|
.PHONY: ramdisk
|
||||||
|
ifeq ($(KERNEL_ONLY),1)
|
||||||
|
ramdisk: limine/limine kernel
|
||||||
|
rm -rf .kernel-only-root
|
||||||
|
mkdir -p .kernel-only-root/boot/limine
|
||||||
|
cp -v limine/$(EFI_LOADER_$(ARCH)) limine.conf .kernel-only-root/boot/limine/
|
||||||
|
cp -v kernel/bin-$(ARCH)/kernel .kernel-only-root/boot/
|
||||||
|
./scripts/mkramdisk.sh .kernel-only-root ramdisk.tar
|
||||||
|
rm -rf .kernel-only-root
|
||||||
|
else
|
||||||
ramdisk: limine/limine kernel programs
|
ramdisk: limine/limine kernel programs
|
||||||
mkdir -p programs/bin/boot/limine
|
mkdir -p $(PROGRAMS_BIN)/boot/limine
|
||||||
cp -v limine/BOOTX64.EFI programs/bin/boot/limine/
|
cp -v limine/$(EFI_LOADER_$(ARCH)) $(PROGRAMS_BIN)/boot/limine/
|
||||||
cp -v limine.conf programs/bin/boot/limine/
|
cp -v limine.conf $(PROGRAMS_BIN)/boot/limine/
|
||||||
cp -v kernel/bin-$(ARCH)/kernel programs/bin/boot/
|
cp -v kernel/bin-$(ARCH)/kernel $(PROGRAMS_BIN)/boot/
|
||||||
rm -f programs/bin/boot/ramdisk.tar
|
rm -f $(PROGRAMS_BIN)/boot/ramdisk.tar
|
||||||
./scripts/mkramdisk.sh programs/bin ramdisk.tar
|
./scripts/mkramdisk.sh $(PROGRAMS_BIN) ramdisk.tar
|
||||||
|
endif
|
||||||
|
|
||||||
$(IMAGE_NAME).iso: limine/limine kernel ramdisk
|
$(IMAGE_NAME).iso: limine/limine kernel ramdisk
|
||||||
rm -rf iso_root
|
rm -rf iso_root
|
||||||
@@ -252,13 +331,17 @@ ifeq ($(ARCH),loongarch64)
|
|||||||
endif
|
endif
|
||||||
rm -rf iso_root
|
rm -rf iso_root
|
||||||
|
|
||||||
|
# Example and regression-test programs that are built into 0:/os for
|
||||||
|
# development but have no place on a release image.
|
||||||
|
RELEASE_EXCLUDE_PROGRAMS := rusthello.elf futextest.elf memtest.elf
|
||||||
|
|
||||||
.PHONY: release
|
.PHONY: release
|
||||||
release: limine/limine kernel ramdisk
|
release: limine/limine kernel ramdisk
|
||||||
rm -rf iso_root .release-tmp
|
rm -rf iso_root .release-tmp
|
||||||
mkdir -p iso_root/boot
|
mkdir -p iso_root/boot
|
||||||
cp kernel/bin-$(ARCH)/kernel iso_root/boot/
|
cp kernel/bin-$(ARCH)/kernel iso_root/boot/
|
||||||
strip iso_root/boot/kernel
|
$(STRIP) iso_root/boot/kernel
|
||||||
cp -r programs/bin .release-tmp
|
cp -r $(PROGRAMS_BIN) .release-tmp
|
||||||
# A release ISO ships only in-tree apps, so drop any out-of-tree port bundles
|
# A release ISO ships only in-tree apps, so drop any out-of-tree port bundles
|
||||||
# that happen to be staged in programs/bin/apps (each carries a .port marker).
|
# that happen to be staged in programs/bin/apps (each carries a .port marker).
|
||||||
# Pass RELEASE_WITH_PORTS=1 to deliberately cut a ports-included image.
|
# Pass RELEASE_WITH_PORTS=1 to deliberately cut a ports-included image.
|
||||||
@@ -281,11 +364,12 @@ ifeq ($(RELEASE_WITHOUT_SDK),1)
|
|||||||
rm -rf .release-tmp/sdk/bin .release-tmp/sdk/lib .release-tmp/sdk/include \
|
rm -rf .release-tmp/sdk/bin .release-tmp/sdk/lib .release-tmp/sdk/include \
|
||||||
.release-tmp/sdk/libexec .release-tmp/sdk/x86_64-montauk
|
.release-tmp/sdk/libexec .release-tmp/sdk/x86_64-montauk
|
||||||
endif
|
endif
|
||||||
find .release-tmp -name '*.elf' -exec strip {} +
|
rm -f $(addprefix .release-tmp/os/,$(RELEASE_EXCLUDE_PROGRAMS))
|
||||||
|
find .release-tmp -name '*.elf' -exec $(STRIP) {} +
|
||||||
# The ramdisk carries its own copy of the kernel at 0:/boot/kernel -- that is
|
# The ramdisk carries its own copy of the kernel at 0:/boot/kernel -- that is
|
||||||
# the image the Installer lays down on the target disk -- and it is not named
|
# the image the Installer lays down on the target disk -- and it is not named
|
||||||
# *.elf, so the find above misses it. Unstripped it is ~6 MB against ~0.7 MB.
|
# *.elf, so the find above misses it. Unstripped it is ~6 MB against ~0.7 MB.
|
||||||
strip .release-tmp/boot/kernel
|
$(STRIP) .release-tmp/boot/kernel
|
||||||
./scripts/mkramdisk.sh .release-tmp iso_root/boot/ramdisk.tar
|
./scripts/mkramdisk.sh .release-tmp iso_root/boot/ramdisk.tar
|
||||||
rm -rf .release-tmp
|
rm -rf .release-tmp
|
||||||
mkdir -p iso_root/boot/limine
|
mkdir -p iso_root/boot/limine
|
||||||
@@ -331,6 +415,26 @@ ifeq ($(ARCH),loongarch64)
|
|||||||
endif
|
endif
|
||||||
rm -rf iso_root
|
rm -rf iso_root
|
||||||
|
|
||||||
|
# Raspberry Pi 4/400 SD card image (UEFI firmware + Limine + MontaukOS).
|
||||||
|
# ROOTFS=ext2 (default) puts the system on an ext2 partition read through the
|
||||||
|
# kernel's SD driver; ROOTFS=ramdisk has Limine load ramdisk.tar instead.
|
||||||
|
# Phony so switching ROOTFS always rebuilds the image.
|
||||||
|
ROOTFS ?= ext2
|
||||||
|
.PHONY: rpi4-sd montauk-rpi4.img
|
||||||
|
rpi4-sd: montauk-rpi4.img
|
||||||
|
|
||||||
|
montauk-rpi4.img: limine/limine kernel ramdisk
|
||||||
|
ifneq ($(ARCH),aarch64)
|
||||||
|
$(error rpi4-sd needs ARCH=aarch64)
|
||||||
|
endif
|
||||||
|
./scripts/mk-rpi4-sd.sh kernel/bin-$(ARCH)/kernel ramdisk.tar $@ $(ROOTFS) $(PROGRAMS_BIN)-rpi
|
||||||
|
|
||||||
|
# Graphical imager: write montauk-rpi4.img to an SD card, or update the
|
||||||
|
# installation on one in place (scripts/montauk-imager.py, docs/raspberry-pi.md).
|
||||||
|
.PHONY: rpi4-imager
|
||||||
|
rpi4-imager:
|
||||||
|
./scripts/montauk-imager.py
|
||||||
|
|
||||||
$(IMAGE_NAME).hdd: limine/limine kernel ramdisk
|
$(IMAGE_NAME).hdd: limine/limine kernel ramdisk
|
||||||
rm -f $(IMAGE_NAME).hdd
|
rm -f $(IMAGE_NAME).hdd
|
||||||
dd if=/dev/zero bs=1M count=0 seek=64 of=$(IMAGE_NAME).hdd
|
dd if=/dev/zero bs=1M count=0 seek=64 of=$(IMAGE_NAME).hdd
|
||||||
@@ -362,7 +466,7 @@ endif
|
|||||||
clean:
|
clean:
|
||||||
$(MAKE) -C kernel clean
|
$(MAKE) -C kernel clean
|
||||||
# $(MAKE) -C programs clean
|
# $(MAKE) -C programs clean
|
||||||
rm -rf iso_root .release-tmp $(IMAGE_NAME).iso $(IMAGE_NAME).hdd ramdisk.tar
|
rm -rf iso_root .release-tmp $(IMAGE_NAME).iso $(IMAGE_NAME).hdd montauk-rpi4.img rpi4-sd-qemu.img rpi4-esp-qemu.img ramdisk.tar
|
||||||
|
|
||||||
.PHONY: distclean
|
.PHONY: distclean
|
||||||
distclean:
|
distclean:
|
||||||
|
|||||||
@@ -1,3 +1,78 @@
|
|||||||
|
MontaukOS 0.2.0 (October 14, 2026)
|
||||||
|
(278b20825815ec04a22fd1c2fcfedbd38e116e6d)
|
||||||
|
|
||||||
|
New
|
||||||
|
====
|
||||||
|
* Capability (application permissions) model added
|
||||||
|
* Consent prompts for missing app permissions
|
||||||
|
* New Capabilities app for reviewing and managing app permissions
|
||||||
|
* Unified system status codes; the shell and Files now print a readable message on failure
|
||||||
|
* Text is now UTF-8 throughout, with Unicode key events and dead keys
|
||||||
|
* Keyboard layout support, with English, Norwegian, Czech, Swedish, and Danish layouts
|
||||||
|
* NumLock support
|
||||||
|
* Battery percentage reporting via ACPI, with a battery indicator in the desktop panel
|
||||||
|
* Display brightness control, including on hybrid graphics laptops, with a slider in the desktop panel
|
||||||
|
* Discrete GPUs are powered down automatically on hybrid graphics laptops
|
||||||
|
* ext2 block cache with readahead, speeding up disk-heavy work such as on-OS compilation
|
||||||
|
* Userspace system logging: klog is now syslog, logs identify the user and program, and system services log through montauk::service_log
|
||||||
|
* The system log is written to disk (0:/state/logs) during shutdown
|
||||||
|
* Desktop sessions now own the processes started in them, and those processes are ended on logout
|
||||||
|
* Rust userspace support (no_std)
|
||||||
|
* Futex syscalls (SYS_FUTEX_WAIT/SYS_FUTEX_WAKE) as groundwork for Rust std and pthreads
|
||||||
|
* Process termination codes; abort() now ends the whole process from any thread
|
||||||
|
* isatty() now reports whether a terminal is actually attached
|
||||||
|
* Settings panel and a Pure Black theme in Terminal (the default for Console sessions)
|
||||||
|
* Syntax highlighting for C++, HTML, CSS, and JavaScript in edit and Text Editor
|
||||||
|
* Expanded SVG renderer
|
||||||
|
* New vol command for listing mounted volumes
|
||||||
|
* Revamped desktop application categories
|
||||||
|
* Fonts, icons, and manual pages moved to 0:/os; persistent state now lives in 0:/state
|
||||||
|
* RTL-SDR driver moved from the kernel to userspace
|
||||||
|
|
||||||
|
Fixed
|
||||||
|
=====
|
||||||
|
* Opening a directory as a file is now rejected on ext2 and FAT32, fixing on-OS gcc on installed systems
|
||||||
|
* Fixed several syscall result and status code comparisons
|
||||||
|
* Fixed a TLS layout bug for binaries whose TLS segment has an alignment below 16
|
||||||
|
* A fault in one thread now ends the whole process instead of leaving its sibling threads running
|
||||||
|
* Fixed an ACPI hardware ID endianness bug
|
||||||
|
* Fixed a race in Wi-Fi event processing and other Wi-Fi bugs
|
||||||
|
* DHCP no longer runs when no network device has an active link
|
||||||
|
* Reduced idle CPU wakeups and deferred-work polling
|
||||||
|
* Redirected consoles are now inherited by processes spawned with capabilities
|
||||||
|
* Fixed numpad navigation
|
||||||
|
* Fixed a timezone formatting bug
|
||||||
|
* Fixed the login wallpaper startup delay
|
||||||
|
* Fixed text overlap in file dialogs
|
||||||
|
* Persisted kernel logs are no longer double-spaced
|
||||||
|
* Files lists now scroll with the arrow keys, and volume labels are consistent
|
||||||
|
* mbrtowc added to libc
|
||||||
|
|
||||||
|
Included in ISO
|
||||||
|
================
|
||||||
|
|
||||||
|
Two x86_64 images are published for this release.
|
||||||
|
montauk-0.2.0-sdk-x86_64.iso additionally carries the
|
||||||
|
Montauk SDK (gcc, binutils, etc.)
|
||||||
|
|
||||||
|
* MontaukOS SMP kernel
|
||||||
|
* Userspace bootstrap (0:/os/init.elf) and other essential system utilities
|
||||||
|
* MontaukOS desktop environment and graphical apps
|
||||||
|
* NetSurf web browser (0:/apps/netsurf)
|
||||||
|
* Manual pages (0:/os/man), readable via man command
|
||||||
|
* Service and system configuration (0:/config: init.toml, capabilities.toml, ssh.toml, ntp.toml)
|
||||||
|
* System CA certificates (0:/os/certs/ca-certificates.crt)
|
||||||
|
* Intel Bluetooth controller and Wi-Fi (iwlwifi) firmware (0:/os/firmware/intel)
|
||||||
|
* Third-party license texts and notices (0:/os/licenses)
|
||||||
|
* Tiny C Compiler (tcc) with MontaukOS headers and libraries (0:/sdk/tcc)
|
||||||
|
* Lua 5.4 interpreter (lua) with headers and static library (0:/sdk/lua)
|
||||||
|
* Montauk SDK - native GCC/G++ and binutils (0:/sdk) - SDK image only
|
||||||
|
* Flat Remix icon pack
|
||||||
|
* JetBrains Mono, Noto Serif, Roboto, and C059 Roman (open Century Schoolbook) TrueType fonts
|
||||||
|
* DOOM engine with SDL_mixer audio support (game data not included)
|
||||||
|
* Experimental HTTP server (httpd) and default page (0:/www/index.html)
|
||||||
|
* Default wallpaper - blossoms by Nikhil Kumar, Unsplash (0:/os/wallpapers)
|
||||||
|
|
||||||
MontaukOS 0.1.9 (August 13, 2026)
|
MontaukOS 0.1.9 (August 13, 2026)
|
||||||
(a28b8ad0b2932203b812e967f35f029f060fd6b5)
|
(a28b8ad0b2932203b812e967f35f029f060fd6b5)
|
||||||
|
|
||||||
|
|||||||
+10
-4
@@ -70,10 +70,16 @@ sudo apt install build-essential nasm git curl xorriso mtools gdisk qemu-system-
|
|||||||
|
|
||||||
### Cross-Architecture Builds
|
### Cross-Architecture Builds
|
||||||
|
|
||||||
To build for a different architecture, pass `ARCH=`:
|
The kernel is split into architecture-neutral code and per-architecture
|
||||||
|
ports under `kernel/src/Arch/` (see [porting.md](porting.md)). x86_64 is the
|
||||||
|
only complete port. `aarch64` is a skeleton: the whole kernel compiles and
|
||||||
|
links for it, but paging, exception vectors, the GIC and the timer are not
|
||||||
|
written yet, so it does not boot. Userspace is x86_64-only.
|
||||||
|
|
||||||
|
Non-x86 kernels are built with LLVM (`clang`, plus `ld.lld` for the link):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
make ARCH=aarch64
|
make kernel ARCH=aarch64 # kernel/bin-aarch64/kernel
|
||||||
make ARCH=riscv64
|
make kernel ARCH=aarch64 KERNEL_LD=/path/to/ld.lld
|
||||||
make ARCH=loongarch64
|
./scripts/check-arch-neutral.sh # boundary check + aarch64 compile
|
||||||
```
|
```
|
||||||
+207
@@ -0,0 +1,207 @@
|
|||||||
|
# Porting the Kernel to a New Architecture
|
||||||
|
|
||||||
|
The kernel is written so that everything outside `kernel/src/Arch/` is
|
||||||
|
architecture-neutral. A port is one directory, `kernel/src/Arch/<arch>/`,
|
||||||
|
implementing the interfaces declared in `kernel/src/Arch/*.hpp` (plus a few
|
||||||
|
interface headers elsewhere, listed below). The build compiles the generic
|
||||||
|
tree plus `Arch/$(ARCH)/` only.
|
||||||
|
|
||||||
|
`scripts/check-arch-neutral.sh` enforces the boundary and should pass before
|
||||||
|
every commit that touches the kernel:
|
||||||
|
|
||||||
|
- no inline assembly, assembly sources or architecture `#if`s outside
|
||||||
|
`src/Arch/` (the Limine adapter is the one exception: `limine.h` has
|
||||||
|
per-architecture SMP structures);
|
||||||
|
- no generic file includes a port's private headers (`<Arch/x86_64/...>`);
|
||||||
|
- with clang installed, the kernel compiles for `aarch64` and every symbol
|
||||||
|
the generic code references is defined by the port.
|
||||||
|
|
||||||
|
## Layout
|
||||||
|
|
||||||
|
```
|
||||||
|
kernel/src/Arch/
|
||||||
|
Arch.hpp target selection (MONTAUK_ARCH_*), MONTAUK_ARCH_NAME
|
||||||
|
Cpu.hpp inline CPU primitives (interrupt state, halt, barriers,
|
||||||
|
timestamp counter, TLB, page-table root)
|
||||||
|
Interrupts.hpp IRQ slots, handler registration, legacy line routing,
|
||||||
|
MSI message composition, IPIs
|
||||||
|
Smp.hpp Smp::CpuData (generic fields + arch sub-structs),
|
||||||
|
per-CPU base access, InitBsp / BootAPs
|
||||||
|
Context.hpp thread context: FPU area, initial kernel stack,
|
||||||
|
SwitchContext, ActivateThread, EnterUserMode
|
||||||
|
Abi.hpp user ABI: ELF machine, relocation kinds, TLS variant,
|
||||||
|
exit stub, initial user stack, EFI calling convention
|
||||||
|
Syscall.hpp SyscallFrame contract and entry-point installation
|
||||||
|
Timer.hpp per-CPU tick timer hardware
|
||||||
|
Platform.hpp boot-sequence hooks, platform device list, reboot
|
||||||
|
CpuPower.hpp frequency scaling / thermal governor (may be no-ops)
|
||||||
|
x86_64/ the complete port
|
||||||
|
aarch64/ the port in progress (see below)
|
||||||
|
```
|
||||||
|
|
||||||
|
Other interfaces a port implements:
|
||||||
|
|
||||||
|
| Header | What the port provides |
|
||||||
|
|---|---|
|
||||||
|
| `Memory/Paging.hpp` | the whole `Memory::VMM::Paging` class: page-table format, kernel and user mappings, address-space creation and teardown |
|
||||||
|
| `Io/IoPort.hpp` | port I/O (`Io::In8`...), or the absent-device fallback with `HasPortIo = false` |
|
||||||
|
| `ACPI/AcpiSleep.hpp` | `Initialize` / `IsS3Available` / `Suspend` (S3 via the FACS waking vector is an x86 mechanism) |
|
||||||
|
| `Common/Panic.hpp` | `Arch::PanicFrame` and `Arch::PrintPanicFrame` |
|
||||||
|
|
||||||
|
Interface headers that need inline definitions include the port's header of
|
||||||
|
the same name at their end (`Arch/Cpu.hpp` -> `Arch/<arch>/Cpu.hpp`); those
|
||||||
|
are `Cpu.hpp`, `IoPort.hpp`, `PerCpu.hpp`, `Context.hpp`, `Abi.hpp` and
|
||||||
|
`SyscallFrame.hpp`. Everything else is an ordinary out-of-line function.
|
||||||
|
|
||||||
|
## Boot sequence
|
||||||
|
|
||||||
|
`kmain` (`Boot/Main.cpp`) owns the order and calls the port at fixed points:
|
||||||
|
|
||||||
|
1. `Arch::EarlyInitialize()` - descriptor tables, FPU/SIMD enable. No allocator.
|
||||||
|
2. Frame allocator and heap come up (generic).
|
||||||
|
3. `Arch::InitializeTraps()` - exception vectors.
|
||||||
|
4. `Paging::Init()` switches to the kernel's own page tables, then
|
||||||
|
`Arch::InitializeMemoryAttributes()` (x86: PAT for write-combining).
|
||||||
|
5. ACPI tables are parsed (generic). `Arch::InitializeInterrupts(xsdt)` brings
|
||||||
|
up interrupt controllers, calls `Smp::InitBsp()`, idle and power
|
||||||
|
management; kmain then calls `Arch::EnableInterrupts()`.
|
||||||
|
6. PCI, AML, drivers (generic); `Timekeeping::TimerInitialize()` registers
|
||||||
|
`IRQ_TIMER` and calls `Arch::Timer::InitializeBsp(1)`.
|
||||||
|
7. `Arch::InitializePlatformDevices()` (x86: PS/2).
|
||||||
|
8. `Arch::PrepareUserMode()`, `montauk::abi::InitializeSyscalls()` (calls
|
||||||
|
`Arch::InitializeSyscallEntry()`), scheduler, IPC.
|
||||||
|
9. `Smp::BootAPs()`, `Arch::LateInitialize()`, first process, idle loop.
|
||||||
|
|
||||||
|
## Assumptions the generic kernel makes
|
||||||
|
|
||||||
|
- 64-bit, little-endian, 4 KiB pages.
|
||||||
|
- A higher-half direct map of physical memory (`Memory::HHDM`), as Limine
|
||||||
|
provides on every architecture.
|
||||||
|
- User space in the lower half of each address space, kernel in the upper
|
||||||
|
half; an address space is identified by the physical address of its root
|
||||||
|
table (on ARM, the TTBR0 table; the kernel half lives in TTBR1).
|
||||||
|
- Firmware tables via ACPI (`Hal::ACPI`); device-tree-only platforms would
|
||||||
|
need a discovery path first.
|
||||||
|
- Interrupts are numbered IRQ slots (`Arch::IRQ_COUNT`); slot numbers for
|
||||||
|
MSI-capable drivers are fixed in the drivers.
|
||||||
|
|
||||||
|
## The aarch64 port
|
||||||
|
|
||||||
|
Real: CPU primitives (DAIF, WFI, `CNTVCT_EL0`, TLBI, TTBR0), per-CPU data via
|
||||||
|
`TPIDR_EL1`, the context switch and EL0 entry (`Context.S`), TLS through
|
||||||
|
`TPIDR_EL0`, the ELF/TLS/exit-stub ABI (syscall number in x8), the IRQ slot
|
||||||
|
dispatch table (`ArchIrqDispatch`), panic-frame printing, and:
|
||||||
|
|
||||||
|
- Exception vectors (`Vectors.S`, installed in `EarlyInitialize`). Every
|
||||||
|
exception saves one 288-byte frame that is both `Arch::PanicFrame` and
|
||||||
|
`Arch::SyscallFrame`. EL0 `svc` goes to `SyscallDispatch`; other
|
||||||
|
synchronous exceptions go to `ArchSyncException` (`Exceptions.cpp`), which
|
||||||
|
grows user stacks and lazy anonymous memory, kills faulting processes with
|
||||||
|
a crash report, and panics on kernel faults.
|
||||||
|
- Translation tables (`Paging.cpp`): 4 KiB granule, 48-bit VAs. The kernel
|
||||||
|
half lives in its own TTBR1 table; `KernelRoot()` is the TTBR0 table of
|
||||||
|
kernel contexts (EFI identity mappings only, so null pointers fault). The
|
||||||
|
HHDM maps only the memory map's RAM and firmware regions, never the holes
|
||||||
|
between them (device space on ARM). MAIR: 0 Normal WB, 1 Normal NC
|
||||||
|
(`MapWC`), 2 Device-nGnRE (`MapMMIO`).
|
||||||
|
- GICv2 (`Interrupts.cpp`, from the MADT): IRQ slots map onto INTIDs --
|
||||||
|
`IRQ_TIMER` onto the generic timer's PPI, the MSI slots (24-45) onto the
|
||||||
|
SPIs of a GICv2m MSI frame (`ComposeMsi`), the IPI slots onto SGIs 0/1,
|
||||||
|
and the platform lines (1-23) onto the SPIs `AssignPlatformLine` gives
|
||||||
|
them (see "Interrupt lines" below).
|
||||||
|
Interrupts are completed (EOI) before the handler runs, as on x86; a
|
||||||
|
level-triggered source drops its line first in an ack hook.
|
||||||
|
- Generic timer tick (`Timer.cpp`): the EL1 virtual timer at
|
||||||
|
`CNTFRQ_EL0`, its PPI from the GTDT, re-armed from the ack hook.
|
||||||
|
- PSCI `SYSTEM_RESET` / `SYSTEM_OFF` (`Arch::Reboot` / `Arch::PowerOff`)
|
||||||
|
through the conduit the FADT's ARM_BOOT_ARCH names. Hardware-reduced
|
||||||
|
ACPI has no PM1 block, so `AcpiShutdown` leaves power-off to the port.
|
||||||
|
- `Arch::SyncInstructionCache` after the ELF loader writes code: ARM
|
||||||
|
instruction fetch does not snoop data writes (QEMU hides this; real
|
||||||
|
cores do not). EL0 may maintain caches itself (`SCTLR_EL1.UCI`).
|
||||||
|
|
||||||
|
- SMP (`Smp.cpp`): Limine wakes each secondary CPU (PSCI `CPU_ON`
|
||||||
|
underneath) on the bootloader's tables; it programs its own system
|
||||||
|
registers and the kernel translation regime (`Bringup.hpp`), its banked
|
||||||
|
GIC CPU interface and timer PPI, then idles until a reschedule SGI.
|
||||||
|
TLB maintenance is broadcast (`tlbi ...is`).
|
||||||
|
|
||||||
|
The generic kernel is compiled `-fsigned-char` for AArch64: it was written
|
||||||
|
for x86's signed plain `char`.
|
||||||
|
|
||||||
|
Building it needs `clang` and `ld.lld`; without a system `ld.lld` the
|
||||||
|
kernel makefile uses the one rustup ships
|
||||||
|
(`~/.rustup/toolchains/*/lib/rustlib/*/bin/gcc-ld/ld.lld`), and `KERNEL_LD=`
|
||||||
|
overrides both. `make run ARCH=aarch64` boots the full image (desktop, apps,
|
||||||
|
USB keyboard and mouse) on QEMU `virt` with a GICv2 and Cortex-A72 cores, the
|
||||||
|
Raspberry Pi 4/400's configuration; it needs `qemu-system-aarch64` and
|
||||||
|
`qemu-efi-aarch64`, and the userspace compiler
|
||||||
|
(`./toolchain/build-montauk-toolchain.sh aarch64`). `KERNEL_ONLY=1` builds an
|
||||||
|
image without userspace.
|
||||||
|
|
||||||
|
The kernel console is mirrored to the serial port the ACPI SPCR table names
|
||||||
|
(PL011, SBSA UART, 16550 or the BCM2835 mini UART; `Drivers/Serial/Uart.cpp`),
|
||||||
|
including the log written before the port was found. On QEMU that is
|
||||||
|
`-serial stdio`.
|
||||||
|
|
||||||
|
## Interrupt lines
|
||||||
|
|
||||||
|
Drivers for PCI functions without MSI read the Interrupt Line register and
|
||||||
|
pass it to `Arch::EnableLegacyIrq`. On x86 the firmware fills that register
|
||||||
|
with the function's I/O APIC input (`Arch::PlatformLinesAreFixed()`). On
|
||||||
|
other platforms `Pci::AssignInterruptLines` (`Pci/PciAcpi.cpp`) fills it
|
||||||
|
instead: it finds each pin's global system interrupt in the root bridge's
|
||||||
|
`_PRT` (swizzling through PCI-to-PCI bridges, resolving PNP0C0F link
|
||||||
|
devices through `_CRS`), binds the GSI to a platform line with
|
||||||
|
`Hal::GsiRouting::LineForGsi` and writes the line number into the register.
|
||||||
|
Devices that ACPI describes directly (platform devices) get their line from
|
||||||
|
`LineForGsi` too.
|
||||||
|
|
||||||
|
## DMA
|
||||||
|
|
||||||
|
`Memory/Dma.hpp` serves devices whose DMA does not snoop the CPU caches
|
||||||
|
(ACPI `_CCA = 0`): pages for rings and descriptors come uncached, and
|
||||||
|
`SyncForDevice` / `SyncForCpu` clean and invalidate streaming buffers
|
||||||
|
around a transfer (`Arch::CleanDataCache` / `InvalidateDataCache`). For
|
||||||
|
coherent devices every call is the plain allocation or a no-op. The xHCI
|
||||||
|
driver uses it; it also orders its TRB writes with `Arch::DmaWriteFence`
|
||||||
|
(the cycle bit last, a control TD's Setup TRB after its Data and Status
|
||||||
|
TRBs) and its event reads with `Arch::DmaReadFence`. Those fences cost
|
||||||
|
nothing on x86 and are required on ARM, where stores to normal memory and
|
||||||
|
device registers may otherwise be observed out of order. The other PCI
|
||||||
|
drivers (AHCI, NVMe, e1000, HDA) do not have them yet: they work on QEMU,
|
||||||
|
which does not reorder, but need them before they meet real ARM hardware.
|
||||||
|
|
||||||
|
## Raspberry Pi 4 / 400
|
||||||
|
|
||||||
|
See `docs/raspberry-pi.md`: `make ARCH=aarch64 rpi4-sd`, the firmware it
|
||||||
|
expects and how the Pi's hardware is reached.
|
||||||
|
|
||||||
|
## Userspace
|
||||||
|
|
||||||
|
The programs are architecture-neutral C/C++ built by `<arch>-montauk-gcc`
|
||||||
|
(see `toolchain/README.md`). `programs/mk/arch.mk` holds everything
|
||||||
|
architecture-specific in the build: the toolchain prefix, FP/SIMD flag sets
|
||||||
|
(`ARCH_FP_FLAGS` for GUI apps, `ARCH_NOFP_FLAGS` for system programs) and the
|
||||||
|
output layout. x86_64 keeps `bin/`, `obj/` and libraries next to their
|
||||||
|
sources; other architectures build beside them into `bin-<arch>/`,
|
||||||
|
`obj-<arch>/` and `lib/<name>/build-<arch>/`, so both can be built from one
|
||||||
|
tree. The few architecture-specific sources are the raw system-call
|
||||||
|
wrappers (`#if defined(__x86_64__)` / `__aarch64__` blocks in
|
||||||
|
`montauk/syscall.h`, `libc/montauk.h`, `lib/libc/libc.c` and
|
||||||
|
`lib/libc/crt/crt1.c`), `lib/libc/arch/setjmp-<arch>.S` and the scanout
|
||||||
|
store fence in `gui/framebuffer.hpp`.
|
||||||
|
|
||||||
|
Not built for AArch64: tcc (it generates x86 code), the on-OS SDK and the
|
||||||
|
Rust programs (their `x86_64-unknown-montauk` target).
|
||||||
|
|
||||||
|
## Adding another architecture
|
||||||
|
|
||||||
|
1. Create `kernel/src/Arch/<arch>/` with the six inline headers listed above
|
||||||
|
and stubs for the out-of-line functions (copy `aarch64/` as a template).
|
||||||
|
2. Add the target to `Arch/Arch.hpp` and to the `#if` chains at the end of
|
||||||
|
the dispatching interface headers.
|
||||||
|
3. Add `KernelStartSymbol` / `KernelEndSymbol` to `kernel/linker-<arch>.ld`,
|
||||||
|
and the `ld.lld` emulation to `LD_EMULATION_<arch>` in `kernel/GNUmakefile`.
|
||||||
|
4. Teach `Boot/Protocols/LimineProtocol.cpp` the architecture's MP fields.
|
||||||
|
5. Run `./scripts/check-arch-neutral.sh <arch>` until it passes.
|
||||||
@@ -0,0 +1,344 @@
|
|||||||
|
# Raspberry Pi 4 / 400
|
||||||
|
|
||||||
|
MontaukOS runs on the Raspberry Pi 4 and 400 through the Pi Firmware Task
|
||||||
|
Force's UEFI firmware ([pftf/RPi4](https://github.com/pftf/RPi4), an EDK2
|
||||||
|
build) in ACPI mode, booting Limine like any other UEFI machine.
|
||||||
|
|
||||||
|
## Building and writing the SD card
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./toolchain/build-montauk-toolchain.sh aarch64 # once
|
||||||
|
make ARCH=aarch64 rpi4-sd # -> montauk-rpi4.img
|
||||||
|
sudo dd if=montauk-rpi4.img of=/dev/sdX bs=4M conv=fsync
|
||||||
|
```
|
||||||
|
|
||||||
|
Or use the imager, which also updates a card in place:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make rpi4-imager # = ./scripts/montauk-imager.py
|
||||||
|
```
|
||||||
|
|
||||||
|
### The imager
|
||||||
|
|
||||||
|
`scripts/montauk-imager.py` is a Tk window (`python3-tk`) that lists the SD
|
||||||
|
cards and USB drives attached (never the host's own disks) and what is on
|
||||||
|
them, and either writes a fresh image or updates the MontaukOS on a card. It
|
||||||
|
runs as the user and does the writing by starting itself again through
|
||||||
|
`pkexec` (it asks for the password each time). The same operations work from
|
||||||
|
a terminal: `list`, and as root `flash`, `update` and `expand`
|
||||||
|
(`--help` for the options). Images can be raw or `.gz`, `.xz` or `.zip`
|
||||||
|
compressed; **Build** runs `make ARCH=aarch64 rpi4-sd`.
|
||||||
|
|
||||||
|
- **Write a fresh image** erases the card, writes the image, clears the last
|
||||||
|
MiB of the card (a backup GPT from an earlier OS) and reads the card back to
|
||||||
|
verify it. *Expand* grows the root to fill the card right away instead of
|
||||||
|
leaving it to `firstboot.elf`.
|
||||||
|
- **Update** leaves the partition table alone and finds both partitions by
|
||||||
|
label, so a root that has grown to fill the card (or was resized by hand)
|
||||||
|
stays as it is. On the ESP every file is written except `RPI_EFI.fd`, which
|
||||||
|
holds the firmware's own settings (*Update the UEFI firmware* replaces it);
|
||||||
|
a changed `config.txt` is kept as `config.txt.bak`. On the root, `os/`,
|
||||||
|
`apps/`, `sdk/`, `boot/` get the image's new and changed files; `config/`
|
||||||
|
and `www/` only get files the card lacks, `users/` and `state/` are not
|
||||||
|
touched. `init.toml` becomes the image's, keeping each service's `enabled`
|
||||||
|
from the card and appending services only the card has; `capabilities.toml`
|
||||||
|
becomes the image's plus grants only the card has (old copies: `.old`).
|
||||||
|
`os/firstboot.elf` is only written if the card still has it, so a grown
|
||||||
|
card does not grow again. *Remove system files the new image no longer
|
||||||
|
ships* prunes `os/`, `apps/`, `sdk/` and `boot/` (keeping `os/btkeys.bin`,
|
||||||
|
the Bluetooth bonds). Unchanged files are skipped and each file is
|
||||||
|
replaced by a rename; the volume is checked with `e2fsck` before and after,
|
||||||
|
and its features against what `Fs/Ext2.cpp` mounts.
|
||||||
|
- **Expand** (a host-side first boot) grows the root partition to the end of
|
||||||
|
the card, at most 1 TiB, and the volume with `resize2fs`; `firstboot.elf`
|
||||||
|
then finds nothing to do and deletes itself.
|
||||||
|
|
||||||
|
While it works, a runtime udev rule keeps the desktop from automounting the
|
||||||
|
card's partitions (Linux writes to an ext2 volume as soon as it mounts it).
|
||||||
|
|
||||||
|
`scripts/mk-rpi4-sd.sh` downloads the firmware release once into
|
||||||
|
`rpi4-firmware/` (`PFTF_VERSION=v1.53` by default) and builds an MBR image:
|
||||||
|
|
||||||
|
| Partition | Type | Contents |
|
||||||
|
|---|---|---|
|
||||||
|
| 1 | 0xEF, FAT32 | the firmware files, Limine (`EFI/BOOT/BOOTAA64.EFI`), `boot/kernel` |
|
||||||
|
| 2 | 0x83, ext2, label `MONTAUKROOT` | the system tree (the contents of `ramdisk.tar`) |
|
||||||
|
|
||||||
|
Limine loads only the kernel; the `limine.conf` on the card has no ramdisk
|
||||||
|
module. Finding no ramdisk, the kernel mounts the partition labelled
|
||||||
|
`MONTAUKROOT` as `0:/` (`Fs/Boot.cpp`), read through the SD driver
|
||||||
|
(`Drivers/Storage/Sdhci`), and the ESP after it. Files load as they are
|
||||||
|
used instead of the firmware reading the whole 80 MB tree before boot.
|
||||||
|
The card can also boot from a USB card reader (or the image from a USB
|
||||||
|
stick): the root then comes through `Drivers/USB/MassStorage.cpp`. A USB
|
||||||
|
root can register after the boot scan (a SuperSpeed link still training, a
|
||||||
|
reader still bringing its card up), so a boot that finds no `MONTAUKROOT`
|
||||||
|
waits up to 10 s for one, servicing USB hot-plug meanwhile.
|
||||||
|
Unlike the ramdisk, the root is persistent: changes to `0:/` survive a
|
||||||
|
reboot, so shut down from the desktop rather than pulling power.
|
||||||
|
The image is sized to its contents (`ROOT_FREE_MIB`, default 64, sets the
|
||||||
|
free space on the root) and grows to fill the card on first boot.
|
||||||
|
|
||||||
|
### First boot: growing the root
|
||||||
|
|
||||||
|
Only the SD image ships `0:/os/firstboot.elf` (`programs/src/firstboot`,
|
||||||
|
built into `programs/bin-aarch64-rpi/`, which the script lays over the
|
||||||
|
system tree), and the script adds its init service, ahead of all others,
|
||||||
|
and its capability grant (`storage_admin`, `system_image`) to the image's
|
||||||
|
`init.toml` and `capabilities.toml`. On first boot it shows a status card
|
||||||
|
in the login screen's style while it calls `SYS_FS_GROW` on drive 0 in a
|
||||||
|
loop:
|
||||||
|
|
||||||
|
1. The kernel grows the MBR entry of the root partition as far as the
|
||||||
|
filesystem can use (to the end of the card, or 1 TiB), on a 1 MiB
|
||||||
|
boundary (`Gpt::ResizeMbrPartition`).
|
||||||
|
2. The ext2 driver grows the mounted volume (`Fs/Ext2.cpp`, "Online
|
||||||
|
grow"): each call writes the bitmaps and zeroed inode tables of a few
|
||||||
|
new block groups past the old end, then the last call commits the
|
||||||
|
bitmaps, descriptor table and superblock. The descriptor table grows
|
||||||
|
into the blocks `mke2fs -O resize_inode -E resize=2^28` reserved; the
|
||||||
|
leftover reservation is freed and `resize_inode` cleared, so the volume
|
||||||
|
grows once.
|
||||||
|
|
||||||
|
The new groups' inode tables are the bulk of the writes (0.1% of the card,
|
||||||
|
one inode per 256 KiB), written as long multi-block runs. Once the volume has grown, `firstboot.elf` deletes
|
||||||
|
itself and init skips its (optional) service from then on. If it fails it
|
||||||
|
stays and the next boot tries again; an interrupted grow is safe, since the
|
||||||
|
new groups are only referenced once the superblock is written, and a
|
||||||
|
partition larger than its filesystem is valid.
|
||||||
|
|
||||||
|
`make ARCH=aarch64 rpi4-sd ROOTFS=ramdisk` builds the old layout instead:
|
||||||
|
one FAT32 partition with `boot/ramdisk.tar`, which Limine loads through the
|
||||||
|
firmware. It boots slowly but does not depend on the kernel's SD driver.
|
||||||
|
|
||||||
|
`make ARCH=aarch64 run-rpi4-sd` boots the ext2 image on QEMU with the card
|
||||||
|
on `sdhci-pci`, so the root comes through the same driver. QEMU's UEFI has
|
||||||
|
no SD driver, so the card's ESP is also attached on its own over virtio for
|
||||||
|
the firmware to load Limine from. The card is padded to
|
||||||
|
`RPI4_QEMU_CARD_MIB` (default 4096), and every run starts from a fresh
|
||||||
|
copy, so every run is a first boot.
|
||||||
|
|
||||||
|
The image's `RPI_EFI.fd` comes with a **MontaukOS** boot entry first in the
|
||||||
|
firmware's boot order (`scripts/rpi4-efi-bootorder.py` writes it into the
|
||||||
|
release's empty variable store), so the card boots on its own instead of
|
||||||
|
falling behind the PXE/HTTP entries the firmware creates. The entry names the
|
||||||
|
ESP by the image's MBR disk ID and partition number, so it works from the SD
|
||||||
|
slot or a USB drive. A card whose `RPI_EFI.fd` predates it keeps its old
|
||||||
|
boot order until it is updated with *Update the UEFI firmware*
|
||||||
|
(`--firmware`), which also copies the image's disk ID to the card.
|
||||||
|
|
||||||
|
The firmware defaults are what MontaukOS needs; leave them:
|
||||||
|
|
||||||
|
- **System Table Mode: ACPI.** The kernel reads ACPI only.
|
||||||
|
- **Limit RAM to 3 GB: enabled.** The BCM2711's PCIe can only reach the
|
||||||
|
first 3 GB (its tables say so in `_DMA`). The kernel warns at boot when
|
||||||
|
RAM extends past a device's `_DMA` range.
|
||||||
|
|
||||||
|
## Serial console
|
||||||
|
|
||||||
|
The kernel console (the whole boot log, including what was printed before
|
||||||
|
the port was found) is mirrored to the UART the firmware's SPCR table names.
|
||||||
|
The firmware's `config.txt` applies the `miniuart-bt` overlay, which puts the
|
||||||
|
PL011 on GPIO 14/15: header pins 8 (TX) and 10 (RX), ground on pin 6, 115200
|
||||||
|
8N1, 3.3 V levels. On the Pi 400 the header is on the back.
|
||||||
|
|
||||||
|
## How the hardware is reached
|
||||||
|
|
||||||
|
| Part | Source | Driver |
|
||||||
|
|---|---|---|
|
||||||
|
| 4x Cortex-A72 | MADT, PSCI (TF-A, SMC) | `Arch/aarch64` SMP via Limine MP |
|
||||||
|
| GIC-400 (GICv2) | MADT | `Arch/aarch64/Interrupts.cpp` |
|
||||||
|
| Generic timer | GTDT | `Arch/aarch64/Timer.cpp` |
|
||||||
|
| Display | UEFI GOP (VideoCore) | Limine framebuffer |
|
||||||
|
| Keyboard (built in), USB ports | VL805 xHCI as ACPI `\_SB.SCB0.XHC0` (`PNP0D10`) | `Drivers/USB/Xhci.cpp` platform probe |
|
||||||
|
| USB 2 devices (built-in keyboard too) | VIA hub on the VL805's USB 2 root port | `Drivers/USB/Hub.cpp` |
|
||||||
|
| Reset, power-off | PSCI | `Arch::Reboot`, `Arch::PowerOff` |
|
||||||
|
| Bluetooth (CYW43455 on the 4B, CYW43456 on the 400) | `BCM2EA6` (`\_SB.GDV0.BTH0`) on the mini UART (`BCM2836`) | `Drivers/Bluetooth/Broadcom.cpp`, `Drivers/Serial/BtUart.cpp` |
|
||||||
|
| VideoCore firmware mailbox (radio power, clocks, board MAC) | `BCM2849` (`RPIQ`) | `Drivers/Platform/RpiFirmware.cpp` |
|
||||||
|
| GPIO pin functions | `BCM2845` (`GPI0`) | `Drivers/Platform/RpiGpio.cpp` |
|
||||||
|
| Ethernet (GENET v5 MAC, BCM54213PE PHY) | `BCM6E4E` (`\_SB.ETH0`) | `Drivers/Net/Genet.cpp` |
|
||||||
|
| Wi-Fi (the same CYW4345x, SDIO) | Arasan SD controller `BCM2847`, GPIO 34-39 | `Drivers/Net/Wifi/Brcmf*.cpp` |
|
||||||
|
|
||||||
|
The VL805 sits behind the BCM2711's PCIe, whose config space is not ECAM
|
||||||
|
(the root bridge's `_DSD` says `linux-ecam-quirk-id = "bcm2711"`), so the
|
||||||
|
kernel does not enumerate PCI there. The firmware has already brought up the
|
||||||
|
link and the VL805 and describes the controller as a platform device: a
|
||||||
|
register block at `0x600000000`, GSI 175, and `_CCA = 0`, i.e. its DMA does
|
||||||
|
not snoop the CPU caches. `XHC0._INI` (run by the AML interpreter) enables
|
||||||
|
the controller's PCI memory decoding and bus mastering. The xHCI driver then
|
||||||
|
keeps its rings and contexts in uncached memory, bounces control-transfer
|
||||||
|
data through an uncached page, and cleans/invalidates the caller buffers of
|
||||||
|
bulk transfers (`Memory/Dma.hpp`). It also watches the event ring from the
|
||||||
|
timer tick, so input keeps working if the INTx line never fires.
|
||||||
|
|
||||||
|
The VL805 has one USB 2 root port (port 1) and four USB 3 ones. An on-board
|
||||||
|
VIA hub (`2109:3431`) sits on the USB 2 port, and the USB 2 lines of every
|
||||||
|
socket, and the Pi 400's internal keyboard (`04d9:0007`), hang off it: a
|
||||||
|
keyboard or mouse is always a low/full-speed device behind a high-speed hub,
|
||||||
|
reached through that hub's transaction translator.
|
||||||
|
|
||||||
|
## Bluetooth
|
||||||
|
|
||||||
|
The radio's Bluetooth half speaks HCI over a UART (H4) on GPIO 30-33 (CTS,
|
||||||
|
RTS, TXD, RXD); its power switch, `BT_ON`, is line 0 of the VideoCore
|
||||||
|
firmware's GPIO expander, reached through the mailbox. The firmware tables
|
||||||
|
describe it as `BTH0`, whose `UARTSerialBus` names the UART it uses: with
|
||||||
|
the `miniuart-bt` overlay the PL011 is the console, so Bluetooth has the mini
|
||||||
|
UART. The kernel refuses to take a UART that is the SPCR console.
|
||||||
|
|
||||||
|
At boot the driver probe routes the pins (`ALT5` for the mini UART, `ALT3`
|
||||||
|
for the PL011), starts the UART (interrupt driven, 16 KiB receive and
|
||||||
|
transmit rings) and the H4 transport under the shared HCI layer. Once the
|
||||||
|
root is mounted the idle loop finishes, as for USB adapters:
|
||||||
|
|
||||||
|
1. power-cycle the controller through `BT_ON`, HCI Reset at 115200 baud;
|
||||||
|
2. Read Local Version: the ROM's LMP subversion picks the patch file,
|
||||||
|
`0:/os/firmware/brcm/BCM4345C0.hcd` (0x6119, 4B) or `BCM4345C5.hcd`
|
||||||
|
(0x6606, 400), shipped unmodified from Raspberry Pi OS's bluez-firmware;
|
||||||
|
3. raise both ends to the operating speed (vendor `0xFC18`);
|
||||||
|
4. Download Minidriver (`0xFC2E`), then every record of the `.hcd` file
|
||||||
|
(Write RAM, then Launch RAM), as Linux's `btbcm` does;
|
||||||
|
5. the patched firmware restarts at 115200: reset, raise the speed again.
|
||||||
|
|
||||||
|
The mini UART's FIFO is 8 bytes deep. Its hardware RTS is tried first; if
|
||||||
|
the controller answers with it on, the link runs at 921600 baud, otherwise
|
||||||
|
at 460800 without flow control (what Raspberry Pi OS uses), and at 115200
|
||||||
|
if the UART has no interrupt. The baud rate derives from the VPU core
|
||||||
|
clock, so `mk-rpi4-sd.sh` pins it (`core_freq=500`, `core_freq_min=500` in
|
||||||
|
`config.txt`), as the overlay's documentation requires. A failed fast
|
||||||
|
bring-up is retried entirely at 115200.
|
||||||
|
|
||||||
|
The CYW43456 reports a chip-wide default address (`43:45:C5:00:1F:AC`);
|
||||||
|
the driver replaces it with the board's Ethernet MAC from the firmware.
|
||||||
|
`0:/config/bluetooth.toml` still overrides both (Broadcom `0xFC01`).
|
||||||
|
|
||||||
|
## Ethernet
|
||||||
|
|
||||||
|
The BCM2711's own Gigabit MAC, GENET v5 (a UniMAC behind a DMA engine),
|
||||||
|
talks RGMII to a BCM54213PE PHY at MDIO address 1. The firmware describes
|
||||||
|
it as `BCM6E4E`: a 64 KiB register block, two interrupts (the driver uses
|
||||||
|
the first, which carries the default queue), `_CCA = 0`, and a `_DSD` with
|
||||||
|
`phy-mode` (`rgmii-rxid`: the PHY delays the receive clock, the MAC the
|
||||||
|
transmit clock) and `brcm,max-dma-burst-size`.
|
||||||
|
|
||||||
|
The driver uses only the default queue (ring 16), with all 256 descriptors
|
||||||
|
of each direction. The descriptors live in the MAC's own SRAM, so only the
|
||||||
|
2 KiB packet buffers need cache maintenance. Received frames start two
|
||||||
|
bytes into their buffer (`RBUF_ALIGN_2B`); neither direction uses the
|
||||||
|
64-byte status blocks, so there is no checksum offload. The UniMAC accepts
|
||||||
|
broadcast and its own address only (the MDF filter).
|
||||||
|
|
||||||
|
The interrupt only acknowledges and masks; frames are dispatched from the
|
||||||
|
idle bottom half, which also checks the ring itself, so receive works if
|
||||||
|
the interrupt never arrives. The PHY is polled once a second (its interrupt
|
||||||
|
pin is not described); a link change reprograms the RGMII block and the
|
||||||
|
UniMAC's speed and duplex. A link the firmware already negotiated is kept,
|
||||||
|
otherwise the PHY is reset and advertises 10/100 and 1000 full duplex.
|
||||||
|
|
||||||
|
The MAC address is the board's, read from OTP through the firmware mailbox
|
||||||
|
(the one the Bluetooth driver uses too); the address the firmware's own
|
||||||
|
driver programmed is the fallback. `dhcp.elf` waits up to 10 s for a wired
|
||||||
|
link, since autonegotiation takes about 3 s after the PHY reset.
|
||||||
|
|
||||||
|
## Wi-Fi
|
||||||
|
|
||||||
|
The radio's WLAN half is an SDIO card on the BCM2711's older SD controller,
|
||||||
|
the Arasan-derived EMMC (`BCM2847`, `0x7E300000`); the card slot is on
|
||||||
|
EMMC2, so the two never share a bus. Its pins are GPIO 34-39 (ALT3; clock
|
||||||
|
without a pull, the rest pulled up, as Linux's `sdio_pins`) and its power
|
||||||
|
switch, `WL_ON`, is line 1 of the firmware's GPIO expander. The probe
|
||||||
|
claims the controller only while the slot is on EMMC2 (`BRCME88C` present);
|
||||||
|
if the tables do not describe the Arasan, its address is taken as EMMC2's
|
||||||
|
less 256 KiB.
|
||||||
|
|
||||||
|
It is a FullMAC radio: the firmware does the 802.11 MLME itself and the host
|
||||||
|
sees Ethernet frames. The driver follows Linux's brcmfmac (ISC license,
|
||||||
|
Broadcom; its notice is in `NOTICES.txt`):
|
||||||
|
|
||||||
|
| File | Part |
|
||||||
|
|---|---|
|
||||||
|
| `BrcmfSdio.cpp` | the Arasan as an SDIO host: CMD5/3/7, 4-bit at 25 MHz, CMD52/CMD53, polled PIO, 32-bit register access |
|
||||||
|
| `BrcmfChip.cpp` | backplane window (function 1), clocks, the enumeration ROM's core list, firmware + NVRAM download, function 2, the firmware console |
|
||||||
|
| `BrcmfProto.cpp` | SDPCM framing and transmit credit, CDC ioctls, BDC data, events, the receive pump, the transmit queue |
|
||||||
|
| `BrcmfConnect.cpp` | preinit and CLM, escan, join, keys, link state |
|
||||||
|
|
||||||
|
Once the root is mounted the idle loop:
|
||||||
|
|
||||||
|
1. routes the pins, switches the EMMC clock on, pulses `WL_ON`, enumerates
|
||||||
|
the card (function 1 blocks of 64 bytes, function 2 of 512);
|
||||||
|
2. requests the ALP clock, reads the chip ID: `0x4345` revision 9 is the
|
||||||
|
CYW43456 (Pi 400), any other revision the CYW43455 (Pi 4B), which picks
|
||||||
|
`0:/os/firmware/brcm/brcmfmac43456-sdio.*` or `brcmfmac43455-sdio.*`
|
||||||
|
(shipped unmodified from Raspberry Pi OS's firmware-nonfree);
|
||||||
|
3. halts the ARM Cortex-R4, holds the 802.11 core in reset, sizes the RAM
|
||||||
|
from the CR4's banks, writes the firmware at `0x198000` and the NVRAM
|
||||||
|
(stripped to `key=value\0` pairs and a length token) at the top of RAM,
|
||||||
|
reading both back to verify, puts the reset vector at address 0 and
|
||||||
|
releases the CPU;
|
||||||
|
4. waits for the HT clock, sends the SDPCM protocol version through the
|
||||||
|
mailbox, enables function 2, the F2 watermark and MES busy signalling of
|
||||||
|
Linux's CYW43455 settings, save/restore if the chip has it, and the card
|
||||||
|
interrupt;
|
||||||
|
5. reads `cur_etheraddr` (the NVRAM's shared placeholder address
|
||||||
|
`b8:27:eb:74:f2:6c` is replaced with the board address + 1, as on
|
||||||
|
Raspberry Pi OS), loads the CLM blob (`clmload`, 1400-byte chunks: these
|
||||||
|
firmware builds carry no regulatory data of their own), turns off glom,
|
||||||
|
roaming, ARP/ND offload and power save, enables the events it handles and
|
||||||
|
brings the interface up (`WLC_UP`).
|
||||||
|
|
||||||
|
Nothing uses the controller's interrupt: the bus is swept from the idle
|
||||||
|
loop (and from the timer tick when the cores never idle), every 10 ms while
|
||||||
|
a scan or a connection is active and every 250 ms otherwise, and at once when
|
||||||
|
the controller shows the card interrupt. Every sweep reads the SDIO core's
|
||||||
|
interrupt status, then every frame waiting in function 2 (a 4-byte header
|
||||||
|
read, then the rest). Ioctls wait for transmit credit and their answer by
|
||||||
|
sweeping the bus themselves. Frames are delivered with the bus unlocked, and
|
||||||
|
transmitting never waits for the bus: data frames go into a 32-frame queue
|
||||||
|
that whoever holds the bus, or the next sweep, sends.
|
||||||
|
|
||||||
|
Scans are escans (all channels, active, firmware dwell times); each result
|
||||||
|
event carries a `brcmf_bss_info_le` whose information elements go into the
|
||||||
|
same scan table the Intel driver fills from beacons. A join sets `wpaie`
|
||||||
|
(the RSN element our supplicant builds), `wpa_auth`, `auth` and `wsec`, then
|
||||||
|
`join` with the BSSID and the chanspec escan reported. The 4-way handshake
|
||||||
|
runs in the host's supplicant (`Wpa.cpp`) as for the Intel adapter: EAPOL
|
||||||
|
frames are ordinary Ethernet frames here, and the keys go to the firmware
|
||||||
|
with `wsec_key` once message 4 has left the transmit queue. The radio does
|
||||||
|
AES-CCMP only: GCMP networks are refused, as are WPA3-only (SAE), WEP and
|
||||||
|
TKIP ones.
|
||||||
|
|
||||||
|
Unanswered ioctls (a leaky bucket of three), a halt reported through
|
||||||
|
the mailbox, or repeated bus errors mark the radio failed: the firmware's
|
||||||
|
console and any trap record are printed to the kernel log (`WiFi-FW`), and
|
||||||
|
the facade's recovery powers the radio off and repeats the bring-up (at
|
||||||
|
most three times).
|
||||||
|
|
||||||
|
`tests/wifi/run.sh` drives `BrcmfProto.cpp` and `BrcmfConnect.cpp` with the
|
||||||
|
real supplicant against a simulated firmware: preinit and the CLM chunks,
|
||||||
|
escan parsing, open and WPA2 joins (an independent authenticator checks
|
||||||
|
messages 2 and 4 and the installed keys), the wrong passphrase, refusals,
|
||||||
|
join timeouts and a firmware that stops answering.
|
||||||
|
|
||||||
|
## Not supported yet
|
||||||
|
|
||||||
|
- SD high-speed mode: the driver transfers by polled PIO at 25 MHz on a
|
||||||
|
4-bit bus (multi-block CMD18/CMD25 with Auto CMD12), at most 12.5 MB/s;
|
||||||
|
switching the card to 50 MHz (CMD6) would double that.
|
||||||
|
- Wi-Fi beyond 802.11 station mode on one interface: no access point mode,
|
||||||
|
no SDIO high speed (50 MHz), no glom, no firmware supplicant.
|
||||||
|
- Audio (HDMI via VideoCore, the 3.5 mm jack via PWM).
|
||||||
|
|
||||||
|
## Status
|
||||||
|
|
||||||
|
Bluetooth works on a Pi 400 (CYW43456), including A2DP music to
|
||||||
|
headphones. Ethernet has not run yet: QEMU has no GENET model. Wi-Fi has
|
||||||
|
not run on hardware yet either (QEMU has no model of the radio); only its
|
||||||
|
protocol and connection layers are tested, in the host harness. It is untested on a Pi 4B (CYW43455), and QEMU has no model of
|
||||||
|
the radio.
|
||||||
|
|
||||||
|
Everything else above is verified on QEMU only, including the Pi-specific path:
|
||||||
|
the firmware's own XHC0 table, retargeted at QEMU's xHCI and loaded with the
|
||||||
|
`bcm2711` quirk on QEMU's root bridge, boots to the desktop with non-coherent
|
||||||
|
DMA. Real hardware has not been tried yet. The ext2 root on the SD card boots on QEMU (`run-rpi4-sd`); the
|
||||||
|
first-boot grow has been tested only against the kernel's ext2 code in a
|
||||||
|
host harness (images grown, then checked with `e2fsck`).
|
||||||
+53
-2
@@ -27,6 +27,20 @@ ifeq ($(ARCH),x86_64)
|
|||||||
endif
|
endif
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
# The other architectures have no in-tree cross GCC: default to LLVM, which
|
||||||
|
# cross-compiles to every target (linking needs ld.lld; see docs/porting.md).
|
||||||
|
ifneq ($(ARCH),x86_64)
|
||||||
|
ifeq ($(filter command line environment,$(origin CC)),)
|
||||||
|
CC := clang
|
||||||
|
endif
|
||||||
|
ifeq ($(filter command line environment,$(origin CXX)),)
|
||||||
|
CXX := clang++
|
||||||
|
endif
|
||||||
|
ifeq ($(filter command line environment,$(origin AR)),)
|
||||||
|
AR := ar
|
||||||
|
endif
|
||||||
|
endif
|
||||||
|
|
||||||
# User controllable C compiler command.
|
# User controllable C compiler command.
|
||||||
CC ?= cc
|
CC ?= cc
|
||||||
|
|
||||||
@@ -139,8 +153,10 @@ ifeq ($(ARCH),aarch64)
|
|||||||
override CXX += \
|
override CXX += \
|
||||||
-target aarch64-unknown-none
|
-target aarch64-unknown-none
|
||||||
endif
|
endif
|
||||||
|
# Plain char is signed on x86; the generic kernel is written for that.
|
||||||
override SHARED_FLAGS += \
|
override SHARED_FLAGS += \
|
||||||
-mgeneral-regs-only
|
-mgeneral-regs-only \
|
||||||
|
-fsigned-char
|
||||||
override LDFLAGS += \
|
override LDFLAGS += \
|
||||||
-Wl,-m,aarch64elf
|
-Wl,-m,aarch64elf
|
||||||
endif
|
endif
|
||||||
@@ -197,8 +213,10 @@ override CXXFLAGS += \
|
|||||||
-std=gnu++20 \
|
-std=gnu++20 \
|
||||||
-fno-rtti \
|
-fno-rtti \
|
||||||
-fno-exceptions \
|
-fno-exceptions \
|
||||||
-fmodules-ts \
|
|
||||||
$(SHARED_FLAGS)
|
$(SHARED_FLAGS)
|
||||||
|
ifneq ($(CXX_IS_CLANG),1)
|
||||||
|
override CXXFLAGS += -fmodules-ts
|
||||||
|
endif
|
||||||
|
|
||||||
# Internal linker flags that should not be changed by the user.
|
# Internal linker flags that should not be changed by the user.
|
||||||
override LDFLAGS += \
|
override LDFLAGS += \
|
||||||
@@ -212,6 +230,9 @@ override LDFLAGS += \
|
|||||||
# Use "find" to glob all *.c, *.cpp, *.S, and *.asm files in the tree and obtain the
|
# Use "find" to glob all *.c, *.cpp, *.S, and *.asm files in the tree and obtain the
|
||||||
# object and header dependency file names.
|
# object and header dependency file names.
|
||||||
override SRCFILES := $(shell cd src && find -L * -type f | LC_ALL=C sort)
|
override SRCFILES := $(shell cd src && find -L * -type f | LC_ALL=C sort)
|
||||||
|
# Architecture-specific code lives in src/Arch/<arch>/; only the target's
|
||||||
|
# directory is built. Everything else in src/ is architecture-neutral.
|
||||||
|
override SRCFILES := $(filter-out Arch/%,$(SRCFILES)) $(filter Arch/$(ARCH)/%,$(SRCFILES))
|
||||||
override CFILES := $(filter %.c,$(SRCFILES))
|
override CFILES := $(filter %.c,$(SRCFILES))
|
||||||
override CXXFILES := $(filter %.cpp,$(SRCFILES))
|
override CXXFILES := $(filter %.cpp,$(SRCFILES))
|
||||||
override OBJCFILES := $(filter %.mm,$(SRCFILES))
|
override OBJCFILES := $(filter %.mm,$(SRCFILES))
|
||||||
@@ -253,6 +274,11 @@ $(BUILDNO_HDR): $(filter-out $(BUILDNO_OBJ),$(OBJ)) $(PROG_SRC)
|
|||||||
# on a clean build (where no .d dependency exists yet).
|
# on a clean build (where no .d dependency exists yet).
|
||||||
$(BUILDNO_OBJ): $(BUILDNO_HDR)
|
$(BUILDNO_OBJ): $(BUILDNO_HDR)
|
||||||
|
|
||||||
|
# Compile everything without linking (used by scripts/check-arch-neutral.sh
|
||||||
|
# to verify a port's objects before a cross linker is available).
|
||||||
|
.PHONY: objects
|
||||||
|
objects: $(OBJ) cc-runtime-$(ARCH)/cc-runtime.a
|
||||||
|
|
||||||
# Link rules for building the C compiler runtime.
|
# Link rules for building the C compiler runtime.
|
||||||
cc-runtime-$(ARCH)/cc-runtime.a: GNUmakefile cc-runtime/*
|
cc-runtime-$(ARCH)/cc-runtime.a: GNUmakefile cc-runtime/*
|
||||||
rm -rf cc-runtime-$(ARCH)
|
rm -rf cc-runtime-$(ARCH)
|
||||||
@@ -263,10 +289,35 @@ cc-runtime-$(ARCH)/cc-runtime.a: GNUmakefile cc-runtime/*
|
|||||||
CFLAGS="$(CFLAGS)" \
|
CFLAGS="$(CFLAGS)" \
|
||||||
CPPFLAGS='-isystem ../freestnd-c-hdrs/$(ARCH)/include -DCC_RUNTIME_NO_FLOAT'
|
CPPFLAGS='-isystem ../freestnd-c-hdrs/$(ARCH)/include -DCC_RUNTIME_NO_FLOAT'
|
||||||
|
|
||||||
|
# Clang's generic-ELF driver links cross targets through the host's GCC,
|
||||||
|
# so a clang-built non-x86 kernel is linked by ld.lld directly.
|
||||||
|
ifneq ($(ARCH),x86_64)
|
||||||
|
ifeq ($(CXX_IS_CLANG),1)
|
||||||
|
# Without a system lld, fall back to the one rustup ships.
|
||||||
|
KERNEL_LD ?= $(firstword $(shell command -v ld.lld) \
|
||||||
|
$(wildcard $(HOME)/.rustup/toolchains/*/lib/rustlib/*/bin/gcc-ld/ld.lld))
|
||||||
|
override LD_EMULATION_aarch64 := aarch64elf
|
||||||
|
override LD_EMULATION_riscv64 := elf64lriscv
|
||||||
|
override LD_EMULATION_loongarch64 := elf64loongarch
|
||||||
|
override KERNEL_LDFLAGS := \
|
||||||
|
-m $(LD_EMULATION_$(ARCH)) \
|
||||||
|
--build-id=none \
|
||||||
|
--no-relax \
|
||||||
|
-static \
|
||||||
|
-z max-page-size=0x1000 \
|
||||||
|
--gc-sections \
|
||||||
|
-T linker-$(ARCH).ld
|
||||||
|
endif
|
||||||
|
endif
|
||||||
|
|
||||||
# Link rules for the final executable.
|
# Link rules for the final executable.
|
||||||
bin-$(ARCH)/$(OUTPUT): GNUmakefile linker-$(ARCH).ld $(OBJ) cc-runtime-$(ARCH)/cc-runtime.a
|
bin-$(ARCH)/$(OUTPUT): GNUmakefile linker-$(ARCH).ld $(OBJ) cc-runtime-$(ARCH)/cc-runtime.a
|
||||||
mkdir -p "$$(dirname $@)"
|
mkdir -p "$$(dirname $@)"
|
||||||
|
ifeq ($(KERNEL_LD),)
|
||||||
$(CXX) $(CXXFLAGS) $(LDFLAGS) $(OBJ) cc-runtime-$(ARCH)/cc-runtime.a -o $@
|
$(CXX) $(CXXFLAGS) $(LDFLAGS) $(OBJ) cc-runtime-$(ARCH)/cc-runtime.a -o $@
|
||||||
|
else
|
||||||
|
$(KERNEL_LD) $(KERNEL_LDFLAGS) $(OBJ) cc-runtime-$(ARCH)/cc-runtime.a -o $@
|
||||||
|
endif
|
||||||
|
|
||||||
# Compilation rules for *.c files.
|
# Compilation rules for *.c files.
|
||||||
obj-$(ARCH)/%.c.o: src/%.c GNUmakefile
|
obj-$(ARCH)/%.c.o: src/%.c GNUmakefile
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ SECTIONS
|
|||||||
/* Any address in this region will do, but often 0xffffffff80000000 is chosen as */
|
/* Any address in this region will do, but often 0xffffffff80000000 is chosen as */
|
||||||
/* that is the beginning of the region. */
|
/* that is the beginning of the region. */
|
||||||
. = 0xffffffff80000000;
|
. = 0xffffffff80000000;
|
||||||
|
KernelStartSymbol = .;
|
||||||
|
|
||||||
/* Define a section to contain the Limine requests and assign it to its own PHDR */
|
/* Define a section to contain the Limine requests and assign it to its own PHDR */
|
||||||
.limine_requests : {
|
.limine_requests : {
|
||||||
@@ -74,4 +75,6 @@ SECTIONS
|
|||||||
*(.eh_frame*)
|
*(.eh_frame*)
|
||||||
*(.note .note.*)
|
*(.note .note.*)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
KernelEndSymbol = .;
|
||||||
}
|
}
|
||||||
@@ -16,7 +16,7 @@
|
|||||||
#include <Memory/Paging.hpp>
|
#include <Memory/Paging.hpp>
|
||||||
#include <Memory/Heap.hpp>
|
#include <Memory/Heap.hpp>
|
||||||
#include <Libraries/Memory.hpp>
|
#include <Libraries/Memory.hpp>
|
||||||
#include <Timekeeping/ApicTimer.hpp>
|
#include <Timekeeping/Timer.hpp>
|
||||||
|
|
||||||
using namespace Kt;
|
using namespace Kt;
|
||||||
|
|
||||||
|
|||||||
@@ -7,9 +7,7 @@
|
|||||||
#include "AcpiEvents.hpp"
|
#include "AcpiEvents.hpp"
|
||||||
#include <ACPI/AcpiSleep.hpp>
|
#include <ACPI/AcpiSleep.hpp>
|
||||||
#include <Io/IoPort.hpp>
|
#include <Io/IoPort.hpp>
|
||||||
#include <Hal/Apic/Apic.hpp>
|
#include <Arch/Interrupts.hpp>
|
||||||
#include <Hal/Apic/IoApic.hpp>
|
|
||||||
#include <Hal/Apic/Interrupts.hpp>
|
|
||||||
#include <Terminal/Terminal.hpp>
|
#include <Terminal/Terminal.hpp>
|
||||||
#include <CppLib/Stream.hpp>
|
#include <CppLib/Stream.hpp>
|
||||||
|
|
||||||
@@ -191,24 +189,21 @@ namespace Hal {
|
|||||||
// routed. Clear stale status only after the enables are down.
|
// routed. Clear stale status only after the enables are down.
|
||||||
DisableAndClearGpes();
|
DisableAndClearGpes();
|
||||||
|
|
||||||
// Route SCI to an IRQ vector. The SCI is level-triggered,
|
// Route the SCI to its IRQ slot. The SCI is level-triggered,
|
||||||
// active-low (ACPI spec requirement). The MADT may have an
|
// active-low (ACPI spec requirement). The MADT may have an
|
||||||
// Interrupt Source Override for this IRQ that already sets
|
// Interrupt Source Override for this IRQ that already sets
|
||||||
// these flags; RouteIrq applies overrides automatically.
|
// these flags; RouteLegacyIrq applies overrides automatically.
|
||||||
uint8_t sciVector = IRQ_VECTOR_BASE + (uint8_t)g_sciIrq;
|
|
||||||
uint8_t bspApicId = (uint8_t)LocalApic::GetId();
|
|
||||||
|
|
||||||
// Register the handler before routing to avoid missing events
|
// Register the handler before routing to avoid missing events
|
||||||
RegisterIrqHandler((uint8_t)g_sciIrq, SciHandler);
|
Arch::RegisterIrqHandler((uint8_t)g_sciIrq, SciHandler);
|
||||||
|
|
||||||
// Route via IoApic (applies MADT overrides for polarity/trigger)
|
// Route to the boot CPU (applies firmware polarity/trigger overrides)
|
||||||
IoApic::RouteIrq((uint8_t)g_sciIrq, sciVector, bspApicId);
|
Arch::RouteLegacyIrq((uint8_t)g_sciIrq);
|
||||||
|
|
||||||
g_initialized = true;
|
g_initialized = true;
|
||||||
|
|
||||||
KernelLogStream(OK, "ACPI") << "ACPI events initialized (SCI IRQ "
|
KernelLogStream(OK, "ACPI") << "ACPI events initialized (SCI IRQ "
|
||||||
<< base::dec << (uint64_t)g_sciIrq << ", vector "
|
<< base::dec << (uint64_t)g_sciIrq << ")";
|
||||||
<< (uint64_t)sciVector << ")";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Reinitialize() {
|
void Reinitialize() {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "AcpiShutdown.hpp"
|
#include "AcpiShutdown.hpp"
|
||||||
|
#include <Arch/Cpu.hpp>
|
||||||
#include "AcpiSleep.hpp"
|
#include "AcpiSleep.hpp"
|
||||||
#include "AcpiEvents.hpp"
|
#include "AcpiEvents.hpp"
|
||||||
#include <ACPI/FADT.hpp>
|
#include <ACPI/FADT.hpp>
|
||||||
@@ -37,8 +38,24 @@ namespace Hal {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Map and parse the DSDT to find \_S5_
|
|
||||||
auto* dsdt = (void*)Memory::HHDM(fadt.DsdtAddress);
|
auto* dsdt = (void*)Memory::HHDM(fadt.DsdtAddress);
|
||||||
|
|
||||||
|
AML::InitializeInterpreter(dsdt, xsdt);
|
||||||
|
|
||||||
|
if (AML::GetInterpreter().IsInitialized()) {
|
||||||
|
AcpiDevices::DeviceList devices{};
|
||||||
|
AcpiDevices::EnumerateAll(devices);
|
||||||
|
}
|
||||||
|
|
||||||
|
AcpiSleep::Initialize(xsdt);
|
||||||
|
|
||||||
|
// Hardware-reduced ACPI (ARM) has no PM1 registers: the
|
||||||
|
// architecture powers off instead (Arch::PowerOff).
|
||||||
|
if ((fadt.Flags & FADT::FlagHardwareReduced) || fadt.PM1aControlBlock == 0) {
|
||||||
|
KernelLogStream(INFO, "ACPI") << "No PM1 control block - ACPI S5 shutdown unavailable";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
AML::S5Object s5 = AML::FindS5(dsdt);
|
AML::S5Object s5 = AML::FindS5(dsdt);
|
||||||
|
|
||||||
if (!s5.Valid) {
|
if (!s5.Valid) {
|
||||||
@@ -54,23 +71,6 @@ namespace Hal {
|
|||||||
|
|
||||||
KernelLogStream(OK, "ACPI") << "ACPI shutdown initialized (PM1a=" << base::hex
|
KernelLogStream(OK, "ACPI") << "ACPI shutdown initialized (PM1a=" << base::hex
|
||||||
<< (uint64_t)g_pm1aControlBlock << " SLP_TYPa=" << (uint64_t)g_slpTypA << ")";
|
<< (uint64_t)g_pm1aControlBlock << " SLP_TYPa=" << (uint64_t)g_slpTypA << ")";
|
||||||
|
|
||||||
// Initialize the full AML interpreter with the DSDT.
|
|
||||||
// This loads the entire DSDT namespace, enabling device enumeration,
|
|
||||||
// method evaluation, and field I/O for the rest of the kernel.
|
|
||||||
AML::InitializeInterpreter(dsdt, xsdt);
|
|
||||||
|
|
||||||
// Enumerate ACPI devices now that the namespace is loaded
|
|
||||||
if (AML::GetInterpreter().IsInitialized()) {
|
|
||||||
AcpiDevices::DeviceList devices{};
|
|
||||||
AcpiDevices::EnumerateAll(devices);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize S3 suspend support (reads FACS and \_S3_ from namespace)
|
|
||||||
AcpiSleep::Initialize(xsdt);
|
|
||||||
|
|
||||||
// Note: ACPI event handling (SCI, power button) is initialized
|
|
||||||
// from Main.cpp after the APIC subsystem is ready.
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool IsAvailable() {
|
bool IsAvailable() {
|
||||||
@@ -95,7 +95,7 @@ namespace Hal {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
asm volatile("cli");
|
Arch::DisableInterrupts();
|
||||||
|
|
||||||
// Phase 1: Write SLP_TYP to PM1x_CNT without SLP_EN,
|
// Phase 1: Write SLP_TYP to PM1x_CNT without SLP_EN,
|
||||||
// preserving existing register bits
|
// preserving existing register bits
|
||||||
@@ -110,7 +110,7 @@ namespace Hal {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Flush all caches before entering sleep state
|
// Flush all caches before entering sleep state
|
||||||
asm volatile("wbinvd");
|
Arch::WritebackInvalidateCaches();
|
||||||
|
|
||||||
// Phase 2: Assert SLP_EN to trigger the transition
|
// Phase 2: Assert SLP_EN to trigger the transition
|
||||||
Io::Out16(pm1a | SLP_EN, (uint16_t)g_pm1aControlBlock);
|
Io::Out16(pm1a | SLP_EN, (uint16_t)g_pm1aControlBlock);
|
||||||
@@ -122,7 +122,7 @@ namespace Hal {
|
|||||||
|
|
||||||
// Halt and wait for the hardware to power off
|
// Halt and wait for the hardware to power off
|
||||||
for (;;) {
|
for (;;) {
|
||||||
asm volatile("hlt");
|
Arch::Halt();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -75,21 +75,6 @@ namespace Hal {
|
|||||||
|
|
||||||
// CPU State (saved across S3)
|
// CPU State (saved across S3)
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// Layout must match S3Wake.asm offsets exactly.
|
|
||||||
// No FPU/SSE state — the kernel is compiled with -mno-sse.
|
|
||||||
struct CpuState {
|
|
||||||
uint64_t Rax, Rbx, Rcx, Rdx; // 0x00 - 0x18
|
|
||||||
uint64_t Rsi, Rdi, Rbp, Rsp; // 0x20 - 0x38
|
|
||||||
uint64_t R8, R9, R10, R11; // 0x40 - 0x58
|
|
||||||
uint64_t R12, R13, R14, R15; // 0x60 - 0x78
|
|
||||||
uint64_t Rflags; // 0x80
|
|
||||||
uint64_t Rip; // 0x88
|
|
||||||
uint64_t Cr3; // 0x90
|
|
||||||
uint64_t Cr0, Cr4; // 0x98, 0xA0
|
|
||||||
uint8_t GdtPtr[10]; // 0xA8
|
|
||||||
uint8_t IdtPtr[10]; // 0xB2
|
|
||||||
} __attribute__((aligned(16)));
|
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
@@ -109,5 +94,9 @@ namespace Hal {
|
|||||||
// Returns 0 on successful resume, or -1 if S3 is unavailable.
|
// Returns 0 on successful resume, or -1 if S3 is unavailable.
|
||||||
int Suspend();
|
int Suspend();
|
||||||
|
|
||||||
|
// Initialize/IsS3Available/Suspend are implemented by the
|
||||||
|
// architecture (the FACS waking-vector path is an x86 platform
|
||||||
|
// mechanism); other targets report S3 as unavailable.
|
||||||
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@@ -0,0 +1,127 @@
|
|||||||
|
/*
|
||||||
|
* AcpiWmi.cpp
|
||||||
|
* ACPI-WMI mapper (PNP0C14): find a WMI GUID's data block in _WDG and call
|
||||||
|
* its WMxx method
|
||||||
|
* Copyright (c) 2026 Daniel Hammer
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "AcpiWmi.hpp"
|
||||||
|
#include "AcpiDevices.hpp"
|
||||||
|
#include <ACPI/AML/AmlInterpreter.hpp>
|
||||||
|
#include <Libraries/Memory.hpp>
|
||||||
|
#include <CppLib/Stream.hpp>
|
||||||
|
#include <Terminal/Terminal.hpp>
|
||||||
|
|
||||||
|
using namespace Kt;
|
||||||
|
|
||||||
|
namespace Hal {
|
||||||
|
namespace AcpiWmi {
|
||||||
|
|
||||||
|
static constexpr const char* HidWmi = "PNP0C14";
|
||||||
|
static constexpr uint32_t WdgEntrySize = 20; // GUID, id, count, flags
|
||||||
|
|
||||||
|
static int HexDigit(char c) {
|
||||||
|
if (c >= '0' && c <= '9') return c - '0';
|
||||||
|
if (c >= 'a' && c <= 'f') return c - 'a' + 10;
|
||||||
|
if (c >= 'A' && c <= 'F') return c - 'A' + 10;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ParseGuid(const char* text, uint8_t out[16]) {
|
||||||
|
// Bytes in text order, then the first three fields byte-swapped.
|
||||||
|
uint8_t bytes[16];
|
||||||
|
int count = 0;
|
||||||
|
for (int i = 0; text[i]; i++) {
|
||||||
|
if (text[i] == '-') continue;
|
||||||
|
int hi = HexDigit(text[i]);
|
||||||
|
int lo = text[i + 1] ? HexDigit(text[i + 1]) : -1;
|
||||||
|
if (hi < 0 || lo < 0 || count == 16) return false;
|
||||||
|
bytes[count++] = (uint8_t)(hi << 4 | lo);
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
if (count != 16) return false;
|
||||||
|
static constexpr int order[16] = {3, 2, 1, 0, 5, 4, 7, 6,
|
||||||
|
8, 9, 10, 11, 12, 13, 14, 15};
|
||||||
|
for (int i = 0; i < 16; i++) out[i] = bytes[order[i]];
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool SearchDevice(int32_t device, const uint8_t guid[16], Block& out) {
|
||||||
|
auto& interp = AML::GetInterpreter();
|
||||||
|
AML::Object wdg{};
|
||||||
|
if (!interp.EvaluateChild(device, "_WDG", nullptr, 0, wdg)
|
||||||
|
|| wdg.Type != AML::ObjectType::Buffer)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// The interpreter caps buffers; a _WDG that fills the cap may have
|
||||||
|
// lost entries past it.
|
||||||
|
if (wdg.Buffer.Length >= (uint32_t)AML::MaxBufferLen) {
|
||||||
|
char path[128];
|
||||||
|
interp.GetNamespace().GetNodePath(device, path, sizeof(path));
|
||||||
|
KernelLogStream(WARNING, "WMI") << path << "._WDG may be truncated ("
|
||||||
|
<< base::dec << (uint64_t)wdg.Buffer.Length << " bytes read)";
|
||||||
|
}
|
||||||
|
|
||||||
|
for (uint32_t off = 0; off + WdgEntrySize <= wdg.Buffer.Length; off += WdgEntrySize) {
|
||||||
|
const uint8_t* entry = wdg.Buffer.Data + off;
|
||||||
|
if (memcmp(entry, guid, 16) != 0) continue;
|
||||||
|
out.Device = device;
|
||||||
|
out.ObjectId[0] = (char)entry[16];
|
||||||
|
out.ObjectId[1] = (char)entry[17];
|
||||||
|
out.InstanceCount = entry[18];
|
||||||
|
out.Flags = entry[19];
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Find(const uint8_t guid[16], Block& out) {
|
||||||
|
auto& interp = AML::GetInterpreter();
|
||||||
|
if (!interp.IsInitialized()) return false;
|
||||||
|
auto& ns = interp.GetNamespace();
|
||||||
|
|
||||||
|
bool found = false;
|
||||||
|
ns.WalkDescendants(ns.RootIndex(), AML::ObjectType::Device,
|
||||||
|
[&](int32_t idx, const AML::NamespaceNode*) {
|
||||||
|
if (found) return;
|
||||||
|
char hid[16];
|
||||||
|
if (!AcpiDevices::EvaluateHid(idx, hid, sizeof(hid))) return;
|
||||||
|
if (memcmp(hid, HidWmi, sizeof("PNP0C14")) != 0) return;
|
||||||
|
if (!(AcpiDevices::EvaluateSta(idx) & AcpiDevices::STA_PRESENT)) return;
|
||||||
|
found = SearchDevice(idx, guid, out);
|
||||||
|
});
|
||||||
|
return found;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool EvaluateMethod(const Block& block, uint8_t instance, uint32_t methodId,
|
||||||
|
const uint8_t* input, uint32_t inputLength,
|
||||||
|
AML::Object& result) {
|
||||||
|
if (!(block.Flags & FlagMethod) || instance >= block.InstanceCount)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
AML::Object args[3];
|
||||||
|
args[0].Type = AML::ObjectType::Integer;
|
||||||
|
args[0].Integer = instance;
|
||||||
|
args[1].Type = AML::ObjectType::Integer;
|
||||||
|
args[1].Integer = methodId;
|
||||||
|
if (block.Flags & FlagString) {
|
||||||
|
uint32_t n = inputLength;
|
||||||
|
if (n >= (uint32_t)AML::MaxStringLen) n = AML::MaxStringLen - 1;
|
||||||
|
args[2].Type = AML::ObjectType::String;
|
||||||
|
if (n) memcpy(args[2].String.Data, input, n);
|
||||||
|
args[2].String.Data[n] = '\0';
|
||||||
|
args[2].String.Length = (uint16_t)n;
|
||||||
|
} else {
|
||||||
|
uint32_t n = inputLength;
|
||||||
|
if (n > (uint32_t)AML::MaxBufferLen) n = AML::MaxBufferLen;
|
||||||
|
args[2].Type = AML::ObjectType::Buffer;
|
||||||
|
if (n) memcpy(args[2].Buffer.Data, input, n);
|
||||||
|
args[2].Buffer.Length = n;
|
||||||
|
}
|
||||||
|
|
||||||
|
char method[5] = {'W', 'M', block.ObjectId[0], block.ObjectId[1], '\0'};
|
||||||
|
return AML::GetInterpreter().EvaluateChild(block.Device, method, args, 3, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
/*
|
||||||
|
* AcpiWmi.hpp
|
||||||
|
* ACPI-WMI mapper (PNP0C14): find a WMI GUID's data block in _WDG and call
|
||||||
|
* its WMxx method
|
||||||
|
* Copyright (c) 2026 Daniel Hammer
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include <cstdint>
|
||||||
|
#include <ACPI/AML/AmlNamespace.hpp>
|
||||||
|
|
||||||
|
namespace Hal {
|
||||||
|
namespace AcpiWmi {
|
||||||
|
|
||||||
|
// _WDG flags (Microsoft "Windows Instrumentation: WMI and ACPI").
|
||||||
|
static constexpr uint8_t FlagExpensive = 0x1;
|
||||||
|
static constexpr uint8_t FlagMethod = 0x2;
|
||||||
|
static constexpr uint8_t FlagString = 0x4;
|
||||||
|
static constexpr uint8_t FlagEvent = 0x8;
|
||||||
|
|
||||||
|
// One _WDG entry, bound to the PNP0C14 device that declared it.
|
||||||
|
struct Block {
|
||||||
|
int32_t Device; // namespace node of the PNP0C14 device
|
||||||
|
char ObjectId[2]; // names the WQxx / WSxx / WMxx methods
|
||||||
|
uint8_t InstanceCount;
|
||||||
|
uint8_t Flags;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Parse "603E9613-EF25-4338-A3D0-C46177516DB7" into the byte order
|
||||||
|
// _WDG stores (first three fields little-endian). False if malformed.
|
||||||
|
bool ParseGuid(const char* text, uint8_t out[16]);
|
||||||
|
|
||||||
|
// Search every present PNP0C14 device's _WDG for `guid`. The first
|
||||||
|
// declaration wins, as on Linux.
|
||||||
|
bool Find(const uint8_t guid[16], Block& out);
|
||||||
|
|
||||||
|
// Evaluate WMxx(instance, methodId, input) on a method block. The input
|
||||||
|
// is passed as a String instead of a Buffer when the block says so.
|
||||||
|
bool EvaluateMethod(const Block& block, uint8_t instance, uint32_t methodId,
|
||||||
|
const uint8_t* input, uint32_t inputLength,
|
||||||
|
AML::Object& result);
|
||||||
|
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -9,7 +9,7 @@
|
|||||||
#include "AcpiDevices.hpp"
|
#include "AcpiDevices.hpp"
|
||||||
#include <ACPI/AML/AmlInterpreter.hpp>
|
#include <ACPI/AML/AmlInterpreter.hpp>
|
||||||
#include <CppLib/Spinlock.hpp>
|
#include <CppLib/Spinlock.hpp>
|
||||||
#include <Timekeeping/ApicTimer.hpp>
|
#include <Timekeeping/Timer.hpp>
|
||||||
#include <Terminal/Terminal.hpp>
|
#include <Terminal/Terminal.hpp>
|
||||||
#include <CppLib/Stream.hpp>
|
#include <CppLib/Stream.hpp>
|
||||||
|
|
||||||
|
|||||||
@@ -107,6 +107,8 @@ namespace Hal {
|
|||||||
result.AcpiEnable = fadt->AcpiEnable;
|
result.AcpiEnable = fadt->AcpiEnable;
|
||||||
result.AcpiDisable = fadt->AcpiDisable;
|
result.AcpiDisable = fadt->AcpiDisable;
|
||||||
result.Flags = fadt->Flags;
|
result.Flags = fadt->Flags;
|
||||||
|
result.ArmBootArchFlags = fadt->Header.Length >= offsetof(Table, ArmBootArchFlags) + 2
|
||||||
|
? fadt->ArmBootArchFlags : 0;
|
||||||
result.Valid = true;
|
result.Valid = true;
|
||||||
|
|
||||||
KernelLogStream(INFO, "FADT") << "DSDT at " << base::hex << result.DsdtAddress;
|
KernelLogStream(INFO, "FADT") << "DSDT at " << base::hex << result.DsdtAddress;
|
||||||
|
|||||||
@@ -69,6 +69,13 @@ namespace Hal {
|
|||||||
uint8_t X_GPE1Block[12];
|
uint8_t X_GPE1Block[12];
|
||||||
} __attribute__((packed));
|
} __attribute__((packed));
|
||||||
|
|
||||||
|
// FADT Flags
|
||||||
|
constexpr uint32_t FlagHardwareReduced = 1u << 20;
|
||||||
|
|
||||||
|
// ARM_BOOT_ARCH flags (ACPI 5.1+)
|
||||||
|
constexpr uint16_t ArmBootPsciCompliant = 1u << 0;
|
||||||
|
constexpr uint16_t ArmBootPsciUseHvc = 1u << 1;
|
||||||
|
|
||||||
struct ParsedFADT {
|
struct ParsedFADT {
|
||||||
uint64_t DsdtAddress;
|
uint64_t DsdtAddress;
|
||||||
uint64_t FacsAddress;
|
uint64_t FacsAddress;
|
||||||
@@ -91,6 +98,7 @@ namespace Hal {
|
|||||||
uint8_t AcpiEnable;
|
uint8_t AcpiEnable;
|
||||||
uint8_t AcpiDisable;
|
uint8_t AcpiDisable;
|
||||||
uint32_t Flags;
|
uint32_t Flags;
|
||||||
|
uint16_t ArmBootArchFlags; // 0 when the table predates the field
|
||||||
bool Valid;
|
bool Valid;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
/*
|
||||||
|
* GsiRouting.cpp
|
||||||
|
* Platform interrupt lines for ACPI global system interrupts
|
||||||
|
* Copyright (c) 2026 Daniel Hammer
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "GsiRouting.hpp"
|
||||||
|
#include <Arch/Interrupts.hpp>
|
||||||
|
#include <CppLib/Spinlock.hpp>
|
||||||
|
|
||||||
|
namespace Hal::GsiRouting {
|
||||||
|
|
||||||
|
static constexpr uint8_t FirstLine = 1;
|
||||||
|
static constexpr uint8_t LineCount = 24;
|
||||||
|
|
||||||
|
static uint32_t g_lineGsi[LineCount] = {};
|
||||||
|
static uint8_t g_nextLine = FirstLine;
|
||||||
|
static kcp::Mutex g_lock;
|
||||||
|
|
||||||
|
uint8_t LineForGsi(uint32_t gsi, bool levelTriggered, bool activeLow) {
|
||||||
|
if (Arch::PlatformLinesAreFixed()) return 0;
|
||||||
|
|
||||||
|
g_lock.Acquire();
|
||||||
|
for (uint8_t line = FirstLine; line < g_nextLine; line++) {
|
||||||
|
if (g_lineGsi[line] == gsi) {
|
||||||
|
g_lock.Release();
|
||||||
|
return line;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t line = 0;
|
||||||
|
if (g_nextLine < LineCount &&
|
||||||
|
Arch::AssignPlatformLine(g_nextLine, gsi, levelTriggered, activeLow)) {
|
||||||
|
line = g_nextLine++;
|
||||||
|
g_lineGsi[line] = gsi;
|
||||||
|
}
|
||||||
|
g_lock.Release();
|
||||||
|
return line;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
/*
|
||||||
|
* GsiRouting.hpp
|
||||||
|
* Platform interrupt lines for ACPI global system interrupts
|
||||||
|
* Copyright (c) 2026 Daniel Hammer
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
namespace Hal::GsiRouting {
|
||||||
|
|
||||||
|
// The platform line (1-23) that delivers global system interrupt `gsi`,
|
||||||
|
// assigning one on first use (Arch::AssignPlatformLine); every device on
|
||||||
|
// one GSI shares its line. Drivers register their handler on the line
|
||||||
|
// and call Arch::EnableLegacyIrq. Returns 0 where the platform's lines
|
||||||
|
// are wired by the hardware (x86) or when none is left.
|
||||||
|
uint8_t LineForGsi(uint32_t gsi, bool levelTriggered, bool activeLow);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,160 @@
|
|||||||
|
/*
|
||||||
|
* WmiEcBacklight.cpp
|
||||||
|
* Firmware (EC) panel backlight behind the WMI interface NVIDIA specified
|
||||||
|
* for display-mux laptops
|
||||||
|
* Copyright (c) 2026 Daniel Hammer
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "WmiEcBacklight.hpp"
|
||||||
|
#include "AcpiWmi.hpp"
|
||||||
|
#include "AcpiPower.hpp"
|
||||||
|
#include <ACPI/AML/AmlInterpreter.hpp>
|
||||||
|
#include <CppLib/Spinlock.hpp>
|
||||||
|
#include <CppLib/Stream.hpp>
|
||||||
|
#include <Terminal/Terminal.hpp>
|
||||||
|
|
||||||
|
using namespace Kt;
|
||||||
|
|
||||||
|
namespace Hal {
|
||||||
|
namespace WmiEcBacklight {
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
// Interface
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
static constexpr const char* Guid = "603E9613-EF25-4338-A3D0-C46177516DB7";
|
||||||
|
|
||||||
|
// WMxx method IDs.
|
||||||
|
static constexpr uint32_t MethodLevel = 1;
|
||||||
|
static constexpr uint32_t MethodSource = 2;
|
||||||
|
|
||||||
|
// `mode` values in the argument block.
|
||||||
|
static constexpr uint32_t ModeGet = 0;
|
||||||
|
static constexpr uint32_t ModeSet = 1;
|
||||||
|
static constexpr uint32_t ModeGetMaxLevel = 2;
|
||||||
|
|
||||||
|
static constexpr uint64_t SourceEc = 2; // 1 = GPU, 3 = eDP AUX
|
||||||
|
|
||||||
|
// Lowest level driven, so the panel never goes fully dark.
|
||||||
|
static constexpr int MinPercent = 1;
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
// State
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
static kcp::Mutex g_lock;
|
||||||
|
static bool g_probed = false;
|
||||||
|
static bool g_available = false;
|
||||||
|
static AcpiWmi::Block g_block{};
|
||||||
|
static uint32_t g_maxLevel = 0;
|
||||||
|
|
||||||
|
// Argument block: { u32 mode; u32 val; u32 ret; u32 reserved[3]; }.
|
||||||
|
// The method answers with an Integer. Linux reads it back as the
|
||||||
|
// `ret` field of the same block, which is where ACPICA's copy-out of
|
||||||
|
// an Integer puts the value; a firmware returning the block itself as
|
||||||
|
// a Buffer is read at that same offset.
|
||||||
|
static bool Call(uint32_t method, uint32_t mode, uint32_t value, uint32_t& out) {
|
||||||
|
uint8_t args[24] = {};
|
||||||
|
args[0] = (uint8_t)mode;
|
||||||
|
args[1] = (uint8_t)(mode >> 8);
|
||||||
|
args[2] = (uint8_t)(mode >> 16);
|
||||||
|
args[3] = (uint8_t)(mode >> 24);
|
||||||
|
args[4] = (uint8_t)value;
|
||||||
|
args[5] = (uint8_t)(value >> 8);
|
||||||
|
args[6] = (uint8_t)(value >> 16);
|
||||||
|
args[7] = (uint8_t)(value >> 24);
|
||||||
|
|
||||||
|
AML::Object result{};
|
||||||
|
if (!AcpiWmi::EvaluateMethod(g_block, 0, method, args, sizeof(args), result))
|
||||||
|
return false;
|
||||||
|
if (result.Type == AML::ObjectType::Integer) {
|
||||||
|
out = (uint32_t)result.Integer;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (result.Type == AML::ObjectType::Buffer && result.Buffer.Length >= 12) {
|
||||||
|
const uint8_t* r = result.Buffer.Data + 8;
|
||||||
|
out = r[0] | (uint32_t)r[1] << 8 | (uint32_t)r[2] << 16 | (uint32_t)r[3] << 24;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// A set may legitimately return nothing.
|
||||||
|
out = 0;
|
||||||
|
return mode == ModeSet;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void Probe() {
|
||||||
|
g_probed = true;
|
||||||
|
uint8_t guid[16];
|
||||||
|
if (!AcpiWmi::ParseGuid(Guid, guid) || !AcpiWmi::Find(guid, g_block)) return;
|
||||||
|
if (!(g_block.Flags & AcpiWmi::FlagMethod) || g_block.InstanceCount == 0) return;
|
||||||
|
|
||||||
|
char path[128];
|
||||||
|
AML::GetInterpreter().GetNamespace().GetNodePath(g_block.Device, path, sizeof(path));
|
||||||
|
|
||||||
|
// The interface also exists on machines where the GPU drives the
|
||||||
|
// backlight; there it must be left alone.
|
||||||
|
uint32_t source = 0;
|
||||||
|
if (!Call(MethodSource, ModeGet, 0, source)) {
|
||||||
|
KernelLogStream(INFO, "Backlight") << "WMI EC backlight at " << path
|
||||||
|
<< ": source query failed";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (source != SourceEc) {
|
||||||
|
KernelLogStream(INFO, "Backlight") << "WMI EC backlight at " << path
|
||||||
|
<< " reports source " << base::dec << (uint64_t)source << ", not EC";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t maximum = 0;
|
||||||
|
if (!Call(MethodLevel, ModeGetMaxLevel, 0, maximum) || maximum == 0) {
|
||||||
|
KernelLogStream(WARNING, "Backlight") << "WMI EC backlight at " << path
|
||||||
|
<< ": no maximum level";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
g_maxLevel = maximum;
|
||||||
|
g_available = true;
|
||||||
|
char method[5] = {'W', 'M', g_block.ObjectId[0], g_block.ObjectId[1], '\0'};
|
||||||
|
KernelLogStream(OK, "Backlight") << "Panel brightness via EC (WMI "
|
||||||
|
<< path << "." << method << ", " << base::dec << (uint64_t)maximum
|
||||||
|
<< " levels)";
|
||||||
|
}
|
||||||
|
|
||||||
|
static int ToPercent(uint32_t level) {
|
||||||
|
if (level > g_maxLevel) level = g_maxLevel;
|
||||||
|
return (int)(((uint64_t)level * 100 + g_maxLevel / 2) / g_maxLevel);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
// Public API
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
int Brightness(int percent) {
|
||||||
|
g_lock.Acquire();
|
||||||
|
if (!g_probed) Probe();
|
||||||
|
int result = -1;
|
||||||
|
if (g_available) {
|
||||||
|
uint32_t level = 0;
|
||||||
|
if (percent >= 0) {
|
||||||
|
if (percent > 100) percent = 100;
|
||||||
|
if (percent < MinPercent) percent = MinPercent;
|
||||||
|
uint32_t target = (uint32_t)(((uint64_t)g_maxLevel * (uint32_t)percent + 50) / 100);
|
||||||
|
if (target == 0) target = 1;
|
||||||
|
uint32_t ignored = 0;
|
||||||
|
if (!Call(MethodLevel, ModeSet, target, ignored)) {
|
||||||
|
result = -2;
|
||||||
|
} else {
|
||||||
|
result = Call(MethodLevel, ModeGet, 0, level) ? ToPercent(level)
|
||||||
|
: ToPercent(target);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
result = Call(MethodLevel, ModeGet, 0, level) ? ToPercent(level) : -2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
g_lock.Release();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
/*
|
||||||
|
* WmiEcBacklight.hpp
|
||||||
|
* Firmware (EC) panel backlight behind the WMI interface NVIDIA specified
|
||||||
|
* for display-mux laptops
|
||||||
|
* Copyright (c) 2026 Daniel Hammer
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
namespace Hal {
|
||||||
|
namespace WmiEcBacklight {
|
||||||
|
|
||||||
|
// On a laptop whose panel can be switched between GPUs, firmware may
|
||||||
|
// wire the backlight to the embedded controller instead of either
|
||||||
|
// GPU and expose it through WMI GUID 603E9613-EF25-4338-A3D0-
|
||||||
|
// C46177516DB7. Despite the name the interface is pure firmware: it
|
||||||
|
// works with the discrete GPU powered off. It is used only when the
|
||||||
|
// firmware itself reports the EC as the brightness source, which is
|
||||||
|
// also when GPU PWM writes stop reaching the panel.
|
||||||
|
//
|
||||||
|
// percent < 0 queries. Returns 0..100, -1 when this interface is not
|
||||||
|
// the panel's brightness source, -2 when the firmware call failed.
|
||||||
|
// Discovery runs on first use. May sleep (AML).
|
||||||
|
int Brightness(int percent);
|
||||||
|
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -11,8 +11,8 @@
|
|||||||
#include <Drivers/Audio/IntelHda.hpp>
|
#include <Drivers/Audio/IntelHda.hpp>
|
||||||
#include <Drivers/Audio/Mixer.hpp>
|
#include <Drivers/Audio/Mixer.hpp>
|
||||||
#include <Terminal/Terminal.hpp>
|
#include <Terminal/Terminal.hpp>
|
||||||
#include <Drivers/USB/Bluetooth/Bluetooth.hpp>
|
#include <Drivers/Bluetooth/Bluetooth.hpp>
|
||||||
#include <Drivers/USB/Bluetooth/A2dp.hpp>
|
#include <Drivers/Bluetooth/A2dp.hpp>
|
||||||
#include <Sched/Scheduler.hpp>
|
#include <Sched/Scheduler.hpp>
|
||||||
|
|
||||||
#include "Syscall.hpp"
|
#include "Syscall.hpp"
|
||||||
@@ -55,10 +55,10 @@ namespace montauk::abi {
|
|||||||
return Drivers::Audio::Mixer::SetOutput(
|
return Drivers::Audio::Mixer::SetOutput(
|
||||||
(Drivers::Audio::Mixer::Output)value);
|
(Drivers::Audio::Mixer::Output)value);
|
||||||
if (cmd == AUDIO_CTL_BT_STATUS) {
|
if (cmd == AUDIO_CTL_BT_STATUS) {
|
||||||
if (!Drivers::USB::Bluetooth::IsInitialized()) return 0;
|
if (!Drivers::Bluetooth::IsInitialized()) return 0;
|
||||||
if (Drivers::USB::Bluetooth::A2dp::IsReady()) return 2;
|
if (Drivers::Bluetooth::A2dp::IsReady()) return 2;
|
||||||
return Drivers::USB::Bluetooth::A2dp::GetState()
|
return Drivers::Bluetooth::A2dp::GetState()
|
||||||
== Drivers::USB::Bluetooth::A2dp::State::Idle
|
== Drivers::Bluetooth::A2dp::State::Idle
|
||||||
? 0 : 1;
|
? 0 : 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,8 +6,8 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <Drivers/USB/Bluetooth/Bluetooth.hpp>
|
#include <Drivers/Bluetooth/Bluetooth.hpp>
|
||||||
#include <Drivers/USB/Bluetooth/Hci.hpp>
|
#include <Drivers/Bluetooth/Hci.hpp>
|
||||||
#include <Libraries/Memory.hpp>
|
#include <Libraries/Memory.hpp>
|
||||||
#include <Sched/Scheduler.hpp>
|
#include <Sched/Scheduler.hpp>
|
||||||
#include <Terminal/Terminal.hpp>
|
#include <Terminal/Terminal.hpp>
|
||||||
@@ -19,14 +19,14 @@ namespace montauk::abi {
|
|||||||
|
|
||||||
static int64_t Sys_BtScan(BtScanResult* buf, int maxCount, uint32_t timeoutMs) {
|
static int64_t Sys_BtScan(BtScanResult* buf, int maxCount, uint32_t timeoutMs) {
|
||||||
if (!buf || maxCount <= 0) return InvalidArgument;
|
if (!buf || maxCount <= 0) return InvalidArgument;
|
||||||
if (!Drivers::USB::Bluetooth::IsInitialized()) return NoDevice;
|
if (!Drivers::Bluetooth::IsInitialized()) return NoDevice;
|
||||||
|
|
||||||
// Use a stack buffer for kernel-side results (max 16)
|
// Use a stack buffer for kernel-side results (max 16)
|
||||||
Drivers::USB::Bluetooth::Hci::InquiryDevice tmpBuf[16];
|
Drivers::Bluetooth::Hci::InquiryDevice tmpBuf[16];
|
||||||
int kMax = maxCount;
|
int kMax = maxCount;
|
||||||
if (kMax > 16) kMax = 16;
|
if (kMax > 16) kMax = 16;
|
||||||
|
|
||||||
int found = Drivers::USB::Bluetooth::Scan(tmpBuf, kMax, timeoutMs);
|
int found = Drivers::Bluetooth::Scan(tmpBuf, kMax, timeoutMs);
|
||||||
if (found < 0) return found;
|
if (found < 0) return found;
|
||||||
|
|
||||||
// Convert kernel InquiryDevice to userspace BtScanResult
|
// Convert kernel InquiryDevice to userspace BtScanResult
|
||||||
@@ -47,38 +47,38 @@ namespace montauk::abi {
|
|||||||
|
|
||||||
static int64_t Sys_BtConnect(const uint8_t* bdAddr) {
|
static int64_t Sys_BtConnect(const uint8_t* bdAddr) {
|
||||||
if (!bdAddr) return InvalidArgument;
|
if (!bdAddr) return InvalidArgument;
|
||||||
if (!Drivers::USB::Bluetooth::IsInitialized()) return NoDevice;
|
if (!Drivers::Bluetooth::IsInitialized()) return NoDevice;
|
||||||
|
|
||||||
return (int64_t)Drivers::USB::Bluetooth::Connect(bdAddr);
|
return (int64_t)Drivers::Bluetooth::Connect(bdAddr);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int64_t Sys_BtDisconnect(const uint8_t* bdAddr) {
|
static int64_t Sys_BtDisconnect(const uint8_t* bdAddr) {
|
||||||
if (!bdAddr) return InvalidArgument;
|
if (!bdAddr) return InvalidArgument;
|
||||||
if (!Drivers::USB::Bluetooth::IsInitialized()) return NoDevice;
|
if (!Drivers::Bluetooth::IsInitialized()) return NoDevice;
|
||||||
|
|
||||||
Kt::KernelLogStream(Kt::INFO, "Power") << "pid " << kcp::dec
|
Kt::KernelLogStream(Kt::INFO, "Power") << "pid " << kcp::dec
|
||||||
<< (uint64_t)Sched::GetCurrentPid()
|
<< (uint64_t)Sched::GetCurrentPid()
|
||||||
<< " entered Bluetooth disconnect stage";
|
<< " entered Bluetooth disconnect stage";
|
||||||
return (int64_t)Drivers::USB::Bluetooth::Disconnect(bdAddr);
|
return (int64_t)Drivers::Bluetooth::Disconnect(bdAddr);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int64_t Sys_BtSetAddr(const uint8_t* bdAddr) {
|
static int64_t Sys_BtSetAddr(const uint8_t* bdAddr) {
|
||||||
if (!bdAddr) return InvalidArgument;
|
if (!bdAddr) return InvalidArgument;
|
||||||
if (!Drivers::USB::Bluetooth::IsInitialized()) return NoDevice;
|
if (!Drivers::Bluetooth::IsInitialized()) return NoDevice;
|
||||||
|
|
||||||
// bdAddr is a 6-byte buffer, addr[0] = least-significant octet.
|
// bdAddr is a 6-byte buffer, addr[0] = least-significant octet.
|
||||||
return Drivers::USB::Bluetooth::SetAddress(bdAddr) ? Success : GenericFailure;
|
return Drivers::Bluetooth::SetAddress(bdAddr) ? Success : GenericFailure;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int64_t Sys_BtBonds(BtBondInfo* buf, int maxCount) {
|
static int64_t Sys_BtBonds(BtBondInfo* buf, int maxCount) {
|
||||||
if (!buf || maxCount <= 0) return 0;
|
if (!buf || maxCount <= 0) return 0;
|
||||||
if (!Drivers::USB::Bluetooth::IsInitialized()) return 0;
|
if (!Drivers::Bluetooth::IsInitialized()) return 0;
|
||||||
|
|
||||||
Drivers::USB::Bluetooth::Hci::BondInfo tmpBuf[8];
|
Drivers::Bluetooth::Hci::BondInfo tmpBuf[8];
|
||||||
int kMax = maxCount;
|
int kMax = maxCount;
|
||||||
if (kMax > 8) kMax = 8;
|
if (kMax > 8) kMax = 8;
|
||||||
|
|
||||||
int count = Drivers::USB::Bluetooth::ListBonded(tmpBuf, kMax);
|
int count = Drivers::Bluetooth::ListBonded(tmpBuf, kMax);
|
||||||
|
|
||||||
for (int i = 0; i < count; i++) {
|
for (int i = 0; i < count; i++) {
|
||||||
memcpy(buf[i].bdAddr, tmpBuf[i].Addr, 6);
|
memcpy(buf[i].bdAddr, tmpBuf[i].Addr, 6);
|
||||||
@@ -91,20 +91,20 @@ namespace montauk::abi {
|
|||||||
|
|
||||||
static int64_t Sys_BtForget(const uint8_t* bdAddr) {
|
static int64_t Sys_BtForget(const uint8_t* bdAddr) {
|
||||||
if (!bdAddr) return InvalidArgument;
|
if (!bdAddr) return InvalidArgument;
|
||||||
if (!Drivers::USB::Bluetooth::IsInitialized()) return NoDevice;
|
if (!Drivers::Bluetooth::IsInitialized()) return NoDevice;
|
||||||
|
|
||||||
return (int64_t)Drivers::USB::Bluetooth::ForgetDevice(bdAddr);
|
return (int64_t)Drivers::Bluetooth::ForgetDevice(bdAddr);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int64_t Sys_BtList(BtDevInfo* buf, int maxCount) {
|
static int64_t Sys_BtList(BtDevInfo* buf, int maxCount) {
|
||||||
if (!buf || maxCount <= 0) return 0;
|
if (!buf || maxCount <= 0) return 0;
|
||||||
if (!Drivers::USB::Bluetooth::IsInitialized()) return 0;
|
if (!Drivers::Bluetooth::IsInitialized()) return 0;
|
||||||
|
|
||||||
Drivers::USB::Bluetooth::Hci::ConnectionInfo tmpBuf[4];
|
Drivers::Bluetooth::Hci::ConnectionInfo tmpBuf[4];
|
||||||
int kMax = maxCount;
|
int kMax = maxCount;
|
||||||
if (kMax > 4) kMax = 4;
|
if (kMax > 4) kMax = 4;
|
||||||
|
|
||||||
int count = Drivers::USB::Bluetooth::ListConnected(tmpBuf, kMax);
|
int count = Drivers::Bluetooth::ListConnected(tmpBuf, kMax);
|
||||||
|
|
||||||
for (int i = 0; i < count; i++) {
|
for (int i = 0; i < count; i++) {
|
||||||
memcpy(buf[i].bdAddr, tmpBuf[i].BdAddr, 6);
|
memcpy(buf[i].bdAddr, tmpBuf[i].BdAddr, 6);
|
||||||
@@ -123,14 +123,14 @@ namespace montauk::abi {
|
|||||||
|
|
||||||
memset(buf, 0, sizeof(BtAdapterInfo));
|
memset(buf, 0, sizeof(BtAdapterInfo));
|
||||||
|
|
||||||
if (!Drivers::USB::Bluetooth::IsInitialized()) {
|
if (!Drivers::Bluetooth::IsInitialized()) {
|
||||||
buf->initialized = 0;
|
buf->initialized = 0;
|
||||||
return NoDevice;
|
return NoDevice;
|
||||||
}
|
}
|
||||||
|
|
||||||
buf->initialized = 1;
|
buf->initialized = 1;
|
||||||
memcpy(buf->bdAddr, Drivers::USB::Bluetooth::GetBdAddr(), 6);
|
memcpy(buf->bdAddr, Drivers::Bluetooth::GetBdAddr(), 6);
|
||||||
buf->scanning = Drivers::USB::Bluetooth::Hci::IsInquiryActive() ? 1 : 0;
|
buf->scanning = Drivers::Bluetooth::Hci::IsInquiryActive() ? 1 : 0;
|
||||||
|
|
||||||
// Copy adapter name
|
// Copy adapter name
|
||||||
const char* name = "MontaukOS";
|
const char* name = "MontaukOS";
|
||||||
|
|||||||
@@ -12,4 +12,4 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#define MONTAUK_BUILD_NUMBER 287
|
#define MONTAUK_BUILD_NUMBER 123
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Clipboard.hpp
|
* Clipboard.hpp
|
||||||
* SYS_CLIPBOARD_* plain-text clipboard syscalls
|
* SYS_CLIPBOARD_* syscalls: per-user multi-format clipboard
|
||||||
* Copyright (c) 2026 Daniel Hammer
|
* Copyright (c) 2026 Daniel Hammer
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@@ -11,24 +11,25 @@
|
|||||||
#include <CppLib/Spinlock.hpp>
|
#include <CppLib/Spinlock.hpp>
|
||||||
|
|
||||||
#include "Syscall.hpp"
|
#include "Syscall.hpp"
|
||||||
|
#include "DataTransfer.hpp"
|
||||||
|
|
||||||
namespace montauk::abi {
|
namespace montauk::abi {
|
||||||
|
|
||||||
static constexpr int ClipboardMaxSlots = 32;
|
static constexpr int ClipboardMaxSlots = 32;
|
||||||
|
|
||||||
|
// One clipboard per user. It holds a multi-format transfer; the text
|
||||||
|
// syscalls read and replace its TRANSFER_MIME_TEXT format, so text-only
|
||||||
|
// apps and multi-format apps share the same clipboard.
|
||||||
struct ClipboardSlot {
|
struct ClipboardSlot {
|
||||||
bool inUse;
|
bool inUse;
|
||||||
char user[32];
|
char user[32];
|
||||||
char* text;
|
DataTransfer::Transfer transfer;
|
||||||
uint32_t len;
|
|
||||||
uint64_t serial;
|
uint64_t serial;
|
||||||
};
|
};
|
||||||
|
|
||||||
static ClipboardSlot g_clipboardSlots[ClipboardMaxSlots];
|
static ClipboardSlot g_clipboardSlots[ClipboardMaxSlots];
|
||||||
static kcp::Mutex g_clipboardLock;
|
static kcp::Mutex g_clipboardLock;
|
||||||
|
|
||||||
static int Sys_ClipboardClear();
|
|
||||||
|
|
||||||
static const char* ClipboardCurrentUser() {
|
static const char* ClipboardCurrentUser() {
|
||||||
auto* proc = Sched::GetCurrentProcessPtr();
|
auto* proc = Sched::GetCurrentProcessPtr();
|
||||||
if (proc == nullptr) return nullptr;
|
if (proc == nullptr) return nullptr;
|
||||||
@@ -81,57 +82,73 @@ namespace montauk::abi {
|
|||||||
|
|
||||||
freeSlot->inUse = true;
|
freeSlot->inUse = true;
|
||||||
ClipboardCopyUser(freeSlot->user, user);
|
ClipboardCopyUser(freeSlot->user, user);
|
||||||
freeSlot->text = nullptr;
|
freeSlot->transfer.count = 0;
|
||||||
freeSlot->len = 0;
|
|
||||||
freeSlot->serial = 0;
|
freeSlot->serial = 0;
|
||||||
return freeSlot;
|
return freeSlot;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Swap `incoming` into the caller's clipboard (it becomes empty) and free
|
||||||
|
// what was there. An empty transfer clears the clipboard.
|
||||||
|
static int ClipboardReplace(DataTransfer::Transfer& incoming) {
|
||||||
|
const char* currentUser = ClipboardCurrentUser();
|
||||||
|
if (currentUser == nullptr) {
|
||||||
|
DataTransfer::Free(incoming);
|
||||||
|
return GenericFailure;
|
||||||
|
}
|
||||||
|
|
||||||
|
char user[32];
|
||||||
|
ClipboardCopyUser(user, currentUser);
|
||||||
|
|
||||||
|
DataTransfer::Transfer old;
|
||||||
|
old.count = 0;
|
||||||
|
|
||||||
|
g_clipboardLock.Acquire();
|
||||||
|
ClipboardSlot* slot = ClipboardEnsureSlotLocked(user);
|
||||||
|
if (slot == nullptr) {
|
||||||
|
g_clipboardLock.Release();
|
||||||
|
DataTransfer::Free(incoming);
|
||||||
|
return Busy;
|
||||||
|
}
|
||||||
|
|
||||||
|
old = slot->transfer;
|
||||||
|
slot->transfer = incoming;
|
||||||
|
slot->serial = ClipboardNextSerial(slot->serial);
|
||||||
|
g_clipboardLock.Release();
|
||||||
|
|
||||||
|
incoming.count = 0;
|
||||||
|
DataTransfer::Free(old);
|
||||||
|
return Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int Sys_ClipboardClear() {
|
||||||
|
DataTransfer::Transfer empty;
|
||||||
|
empty.count = 0;
|
||||||
|
return ClipboardReplace(empty);
|
||||||
|
}
|
||||||
|
|
||||||
static int Sys_ClipboardSetText(const char* data, uint32_t len) {
|
static int Sys_ClipboardSetText(const char* data, uint32_t len) {
|
||||||
if (len == 0)
|
if (len == 0)
|
||||||
return Sys_ClipboardClear();
|
return Sys_ClipboardClear();
|
||||||
if (data == nullptr || len > CLIPBOARD_MAX_TEXT_BYTES)
|
if (data == nullptr || len > CLIPBOARD_MAX_TEXT_BYTES)
|
||||||
return InvalidArgument;
|
return InvalidArgument;
|
||||||
if (Memory::g_heap == nullptr)
|
|
||||||
return OutOfMemory;
|
|
||||||
|
|
||||||
for (uint32_t i = 0; i < len; i++) {
|
for (uint32_t i = 0; i < len; i++) {
|
||||||
if (data[i] == '\0')
|
if (data[i] == '\0')
|
||||||
return InvalidArgument;
|
return InvalidArgument;
|
||||||
}
|
}
|
||||||
|
|
||||||
const char* currentUser = ClipboardCurrentUser();
|
DataTransfer::Transfer transfer;
|
||||||
if (currentUser == nullptr) return GenericFailure;
|
int rc = DataTransfer::BuildText(data, len, transfer);
|
||||||
|
if (rc != Success) return rc;
|
||||||
|
return ClipboardReplace(transfer);
|
||||||
|
}
|
||||||
|
|
||||||
char user[32];
|
static int Sys_ClipboardSetData(const TransferItem* items, uint32_t count) {
|
||||||
ClipboardCopyUser(user, currentUser);
|
if (count == 0) return Sys_ClipboardClear();
|
||||||
|
DataTransfer::Transfer transfer;
|
||||||
char* newText = (char*)Memory::g_heap->Request(len + 1);
|
int rc = DataTransfer::BuildFromUser(items, count, transfer);
|
||||||
if (newText == nullptr) return OutOfMemory;
|
if (rc != Success) return rc;
|
||||||
for (uint32_t i = 0; i < len; i++)
|
return ClipboardReplace(transfer);
|
||||||
newText[i] = data[i];
|
|
||||||
newText[len] = '\0';
|
|
||||||
|
|
||||||
char* oldText = nullptr;
|
|
||||||
|
|
||||||
g_clipboardLock.Acquire();
|
|
||||||
ClipboardSlot* slot = ClipboardEnsureSlotLocked(user);
|
|
||||||
if (slot == nullptr) {
|
|
||||||
g_clipboardLock.Release();
|
|
||||||
Memory::g_heap->Free(newText);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
oldText = slot->text;
|
|
||||||
slot->text = newText;
|
|
||||||
slot->len = len;
|
|
||||||
slot->serial = ClipboardNextSerial(slot->serial);
|
|
||||||
g_clipboardLock.Release();
|
|
||||||
|
|
||||||
if (oldText != nullptr)
|
|
||||||
Memory::g_heap->Free(oldText);
|
|
||||||
|
|
||||||
return Success;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static int Sys_ClipboardGetInfo(ClipboardInfo* out) {
|
static int Sys_ClipboardGetInfo(ClipboardInfo* out) {
|
||||||
@@ -150,7 +167,8 @@ namespace montauk::abi {
|
|||||||
g_clipboardLock.Acquire();
|
g_clipboardLock.Acquire();
|
||||||
ClipboardSlot* slot = ClipboardFindSlotLocked(user);
|
ClipboardSlot* slot = ClipboardFindSlotLocked(user);
|
||||||
if (slot != nullptr) {
|
if (slot != nullptr) {
|
||||||
out->textBytes = slot->len;
|
const DataTransfer::Format* text = DataTransfer::Find(slot->transfer, TRANSFER_MIME_TEXT);
|
||||||
|
out->textBytes = text ? text->len : 0;
|
||||||
out->serial = slot->serial;
|
out->serial = slot->serial;
|
||||||
}
|
}
|
||||||
g_clipboardLock.Release();
|
g_clipboardLock.Release();
|
||||||
@@ -171,52 +189,57 @@ namespace montauk::abi {
|
|||||||
|
|
||||||
g_clipboardLock.Acquire();
|
g_clipboardLock.Acquire();
|
||||||
ClipboardSlot* slot = ClipboardFindSlotLocked(user);
|
ClipboardSlot* slot = ClipboardFindSlotLocked(user);
|
||||||
if (slot == nullptr || slot->len == 0) {
|
if (slot != nullptr && outSerial != nullptr)
|
||||||
if (slot != nullptr && outSerial != nullptr)
|
*outSerial = slot->serial;
|
||||||
*outSerial = slot->serial;
|
const DataTransfer::Format* text =
|
||||||
|
slot ? DataTransfer::Find(slot->transfer, TRANSFER_MIME_TEXT) : nullptr;
|
||||||
|
if (text == nullptr || text->len == 0) {
|
||||||
g_clipboardLock.Release();
|
g_clipboardLock.Release();
|
||||||
return Success;
|
return Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
*outLen = slot->len;
|
int rc = DataTransfer::CopyOut(*text, buffer, bufferLen, outLen);
|
||||||
if (outSerial != nullptr) *outSerial = slot->serial;
|
|
||||||
if (buffer == nullptr || bufferLen < slot->len) {
|
|
||||||
g_clipboardLock.Release();
|
|
||||||
return BufferTooSmall;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (uint32_t i = 0; i < slot->len; i++)
|
|
||||||
buffer[i] = slot->text[i];
|
|
||||||
g_clipboardLock.Release();
|
g_clipboardLock.Release();
|
||||||
return Success;
|
return rc;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int Sys_ClipboardClear() {
|
static int Sys_ClipboardFormats(TransferFormatInfo* out, uint32_t max, uint64_t* outSerial) {
|
||||||
|
if (outSerial != nullptr) *outSerial = 0;
|
||||||
|
|
||||||
const char* currentUser = ClipboardCurrentUser();
|
const char* currentUser = ClipboardCurrentUser();
|
||||||
if (currentUser == nullptr) return GenericFailure;
|
if (currentUser == nullptr) return GenericFailure;
|
||||||
|
|
||||||
char user[32];
|
char user[32];
|
||||||
ClipboardCopyUser(user, currentUser);
|
ClipboardCopyUser(user, currentUser);
|
||||||
|
|
||||||
char* oldText = nullptr;
|
g_clipboardLock.Acquire();
|
||||||
|
ClipboardSlot* slot = ClipboardFindSlotLocked(user);
|
||||||
|
uint32_t n = 0;
|
||||||
|
if (slot != nullptr) {
|
||||||
|
if (outSerial != nullptr) *outSerial = slot->serial;
|
||||||
|
n = out ? DataTransfer::ListFormats(slot->transfer, out, max) : 0;
|
||||||
|
if (out == nullptr || max == 0) n = slot->transfer.count;
|
||||||
|
}
|
||||||
|
g_clipboardLock.Release();
|
||||||
|
return (int)n;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int Sys_ClipboardGetData(const char* mime, void* buffer, uint32_t bufferLen, uint32_t* outLen) {
|
||||||
|
if (outLen == nullptr) return InvalidArgument;
|
||||||
|
*outLen = 0;
|
||||||
|
|
||||||
|
const char* currentUser = ClipboardCurrentUser();
|
||||||
|
if (currentUser == nullptr) return GenericFailure;
|
||||||
|
|
||||||
|
char user[32];
|
||||||
|
ClipboardCopyUser(user, currentUser);
|
||||||
|
|
||||||
g_clipboardLock.Acquire();
|
g_clipboardLock.Acquire();
|
||||||
ClipboardSlot* slot = ClipboardEnsureSlotLocked(user);
|
ClipboardSlot* slot = ClipboardFindSlotLocked(user);
|
||||||
if (slot == nullptr) {
|
const DataTransfer::Format* format = slot ? DataTransfer::Find(slot->transfer, mime) : nullptr;
|
||||||
g_clipboardLock.Release();
|
int rc = format ? DataTransfer::CopyOut(*format, buffer, bufferLen, outLen) : NotFound;
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
oldText = slot->text;
|
|
||||||
slot->text = nullptr;
|
|
||||||
slot->len = 0;
|
|
||||||
slot->serial = ClipboardNextSerial(slot->serial);
|
|
||||||
g_clipboardLock.Release();
|
g_clipboardLock.Release();
|
||||||
|
return rc;
|
||||||
if (oldText != nullptr)
|
|
||||||
Memory::g_heap->Free(oldText);
|
|
||||||
|
|
||||||
return Success;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,156 @@
|
|||||||
|
/*
|
||||||
|
* DataTransfer.cpp
|
||||||
|
* Multi-format payloads shared by the clipboard and drag and drop
|
||||||
|
* Copyright (c) 2026 Daniel Hammer
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "DataTransfer.hpp"
|
||||||
|
#include "UserMemory.hpp"
|
||||||
|
#include <Memory/Heap.hpp>
|
||||||
|
|
||||||
|
namespace DataTransfer {
|
||||||
|
|
||||||
|
using namespace montauk::abi;
|
||||||
|
|
||||||
|
static bool MimeEquals(const char* a, const char* b) {
|
||||||
|
int i = 0;
|
||||||
|
for (; a[i] && b[i]; i++) {
|
||||||
|
char ca = (a[i] >= 'A' && a[i] <= 'Z') ? a[i] + 32 : a[i];
|
||||||
|
char cb = (b[i] >= 'A' && b[i] <= 'Z') ? b[i] + 32 : b[i];
|
||||||
|
if (ca != cb) return false;
|
||||||
|
}
|
||||||
|
return a[i] == b[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Length of the "type/subtype" part, before any ";parameters".
|
||||||
|
static int MimeBaseLen(const char* mime) {
|
||||||
|
int n = 0;
|
||||||
|
while (mime[n] && mime[n] != ';') n++;
|
||||||
|
while (n > 0 && mime[n - 1] == ' ') n--;
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool MimeBaseEquals(const char* a, const char* b) {
|
||||||
|
int la = MimeBaseLen(a);
|
||||||
|
int lb = MimeBaseLen(b);
|
||||||
|
if (la != lb) return false;
|
||||||
|
for (int i = 0; i < la; i++) {
|
||||||
|
char ca = (a[i] >= 'A' && a[i] <= 'Z') ? a[i] + 32 : a[i];
|
||||||
|
char cb = (b[i] >= 'A' && b[i] <= 'Z') ? b[i] + 32 : b[i];
|
||||||
|
if (ca != cb) return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ValidMime(const char* mime) {
|
||||||
|
if (mime == nullptr) return false;
|
||||||
|
bool slash = false;
|
||||||
|
uint32_t i = 0;
|
||||||
|
for (; mime[i]; i++) {
|
||||||
|
if (i + 1 >= TRANSFER_MIME_MAX) return false;
|
||||||
|
char c = mime[i];
|
||||||
|
if (c < 0x20 || c > 0x7E) return false;
|
||||||
|
if (c == '/') slash = true;
|
||||||
|
}
|
||||||
|
return i > 0 && slash;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Free(Transfer& transfer) {
|
||||||
|
for (uint32_t i = 0; i < transfer.count; i++) {
|
||||||
|
if (transfer.formats[i].data != nullptr) Memory::g_heap->Free(transfer.formats[i].data);
|
||||||
|
transfer.formats[i].data = nullptr;
|
||||||
|
transfer.formats[i].len = 0;
|
||||||
|
}
|
||||||
|
transfer.count = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int AddFormat(Transfer& transfer, const char* mime, const uint8_t* data, uint32_t len) {
|
||||||
|
if (transfer.count >= TRANSFER_MAX_FORMATS) return InvalidArgument;
|
||||||
|
Format& f = transfer.formats[transfer.count];
|
||||||
|
uint32_t m = 0;
|
||||||
|
for (; mime[m] && m + 1 < TRANSFER_MIME_MAX; m++) f.mime[m] = mime[m];
|
||||||
|
f.mime[m] = '\0';
|
||||||
|
|
||||||
|
// Never hand out a null pointer for an empty format; one byte keeps
|
||||||
|
// "present but empty" distinct from "absent".
|
||||||
|
f.data = (uint8_t*)Memory::g_heap->Request(len > 0 ? len : 1);
|
||||||
|
if (f.data == nullptr) return OutOfMemory;
|
||||||
|
for (uint32_t i = 0; i < len; i++) f.data[i] = data[i];
|
||||||
|
f.len = len;
|
||||||
|
transfer.count++;
|
||||||
|
return Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
int BuildFromUser(const TransferItem* items, uint32_t count, Transfer& out) {
|
||||||
|
out.count = 0;
|
||||||
|
if (items == nullptr || count == 0 || count > TRANSFER_MAX_FORMATS) return InvalidArgument;
|
||||||
|
if (Memory::g_heap == nullptr) return OutOfMemory;
|
||||||
|
|
||||||
|
// Validate everything before allocating anything.
|
||||||
|
uint64_t total = 0;
|
||||||
|
for (uint32_t i = 0; i < count; i++) {
|
||||||
|
if (!UserMemory::String(items[i].mime, TRANSFER_MIME_MAX)) return BadPointer;
|
||||||
|
if (!ValidMime((const char*)items[i].mime)) return InvalidArgument;
|
||||||
|
if (items[i].len > 0 && !UserMemory::Range(items[i].data, items[i].len, false)) return BadPointer;
|
||||||
|
total += items[i].len;
|
||||||
|
for (uint32_t j = 0; j < i; j++) {
|
||||||
|
if (MimeEquals((const char*)items[i].mime, (const char*)items[j].mime))
|
||||||
|
return InvalidArgument;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (total > TRANSFER_MAX_BYTES) return InvalidArgument;
|
||||||
|
|
||||||
|
for (uint32_t i = 0; i < count; i++) {
|
||||||
|
int rc = AddFormat(out, (const char*)items[i].mime,
|
||||||
|
(const uint8_t*)items[i].data, items[i].len);
|
||||||
|
if (rc != Success) {
|
||||||
|
Free(out);
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
int BuildText(const char* text, uint32_t len, Transfer& out) {
|
||||||
|
out.count = 0;
|
||||||
|
if (Memory::g_heap == nullptr) return OutOfMemory;
|
||||||
|
int rc = AddFormat(out, TRANSFER_MIME_TEXT, (const uint8_t*)text, len);
|
||||||
|
if (rc != Success) Free(out);
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Format* Find(const Transfer& transfer, const char* mime) {
|
||||||
|
if (mime == nullptr) return nullptr;
|
||||||
|
for (uint32_t i = 0; i < transfer.count; i++) {
|
||||||
|
if (MimeEquals(transfer.formats[i].mime, mime)) return &transfer.formats[i];
|
||||||
|
}
|
||||||
|
bool has_params = false;
|
||||||
|
for (int i = 0; mime[i]; i++) {
|
||||||
|
if (mime[i] == ';') { has_params = true; break; }
|
||||||
|
}
|
||||||
|
if (has_params) return nullptr;
|
||||||
|
for (uint32_t i = 0; i < transfer.count; i++) {
|
||||||
|
if (MimeBaseEquals(transfer.formats[i].mime, mime)) return &transfer.formats[i];
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t ListFormats(const Transfer& transfer, TransferFormatInfo* out, uint32_t max) {
|
||||||
|
uint32_t n = 0;
|
||||||
|
for (; n < transfer.count && n < max; n++) {
|
||||||
|
for (uint32_t c = 0; c < TRANSFER_MIME_MAX; c++) out[n].mime[c] = transfer.formats[n].mime[c];
|
||||||
|
out[n].len = transfer.formats[n].len;
|
||||||
|
out[n]._reserved = 0;
|
||||||
|
}
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
int CopyOut(const Format& format, void* buffer, uint32_t bufferLen, uint32_t* outLen) {
|
||||||
|
if (outLen != nullptr) *outLen = format.len;
|
||||||
|
if (format.len == 0) return Success;
|
||||||
|
if (buffer == nullptr || bufferLen < format.len) return BufferTooSmall;
|
||||||
|
uint8_t* dst = (uint8_t*)buffer;
|
||||||
|
for (uint32_t i = 0; i < format.len; i++) dst[i] = format.data[i];
|
||||||
|
return Success;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
/*
|
||||||
|
* DataTransfer.hpp
|
||||||
|
* Multi-format payloads shared by the clipboard and drag and drop
|
||||||
|
* Copyright (c) 2026 Daniel Hammer
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include <cstdint>
|
||||||
|
#include "Syscall.hpp"
|
||||||
|
|
||||||
|
// A transfer is a small set of formats, each one MIME type plus bytes, all
|
||||||
|
// owned by the kernel heap. Producers offer every representation they can
|
||||||
|
// (a file list and its paths as text, say) and each consumer reads the one it
|
||||||
|
// understands. The clipboard holds one transfer per user; the window server
|
||||||
|
// holds the active drag's.
|
||||||
|
namespace DataTransfer {
|
||||||
|
|
||||||
|
struct Format {
|
||||||
|
char mime[montauk::abi::TRANSFER_MIME_MAX];
|
||||||
|
uint8_t* data;
|
||||||
|
uint32_t len;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Transfer {
|
||||||
|
Format formats[montauk::abi::TRANSFER_MAX_FORMATS];
|
||||||
|
uint32_t count;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Copy `count` formats in from user memory, validating every pointer the
|
||||||
|
// items carry (the item array itself must already be validated). On
|
||||||
|
// failure `out` is left empty. Returns a StatusCodes value.
|
||||||
|
int BuildFromUser(const montauk::abi::TransferItem* items, uint32_t count, Transfer& out);
|
||||||
|
|
||||||
|
// A transfer holding just `text` as TRANSFER_MIME_TEXT.
|
||||||
|
int BuildText(const char* text, uint32_t len, Transfer& out);
|
||||||
|
|
||||||
|
void Free(Transfer& transfer);
|
||||||
|
|
||||||
|
// Exact match first; a request without parameters ("text/plain") also
|
||||||
|
// matches a stored format that has them ("text/plain;charset=utf-8").
|
||||||
|
const Format* Find(const Transfer& transfer, const char* mime);
|
||||||
|
|
||||||
|
uint32_t ListFormats(const Transfer& transfer, montauk::abi::TransferFormatInfo* out, uint32_t max);
|
||||||
|
|
||||||
|
// Copy a format to a (validated) user buffer. *outLen always receives the
|
||||||
|
// full length; BufferTooSmall when it does not fit, nothing copied then.
|
||||||
|
int CopyOut(const Format& format, void* buffer, uint32_t bufferLen, uint32_t* outLen);
|
||||||
|
|
||||||
|
// MIME string checks shared by every entry point: "type/subtype" with
|
||||||
|
// optional parameters, printable ASCII, shorter than TRANSFER_MIME_MAX.
|
||||||
|
bool ValidMime(const char* mime);
|
||||||
|
}
|
||||||
+30
-18
@@ -6,18 +6,20 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <Hal/Apic/ApicInit.hpp>
|
|
||||||
#include <Drivers/PS2/PS2Controller.hpp>
|
|
||||||
#include <Drivers/USB/Xhci.hpp>
|
#include <Drivers/USB/Xhci.hpp>
|
||||||
#include <Drivers/USB/UsbDevice.hpp>
|
#include <Drivers/USB/UsbDevice.hpp>
|
||||||
#include <Drivers/USB/Bluetooth/Bluetooth.hpp>
|
#include <Drivers/Bluetooth/Bluetooth.hpp>
|
||||||
#include <Drivers/Net/E1000.hpp>
|
#include <Drivers/Net/E1000.hpp>
|
||||||
#include <Drivers/Net/E1000E.hpp>
|
#include <Drivers/Net/E1000E.hpp>
|
||||||
|
#include <Drivers/Net/Genet.hpp>
|
||||||
#include <Drivers/Net/Wifi/Wifi.hpp>
|
#include <Drivers/Net/Wifi/Wifi.hpp>
|
||||||
#include <Drivers/Graphics/IntelGPU.hpp>
|
#include <Drivers/Graphics/IntelGPU.hpp>
|
||||||
#include <Drivers/Storage/Ahci.hpp>
|
#include <Drivers/Storage/Ahci.hpp>
|
||||||
#include <Drivers/Audio/IntelHda.hpp>
|
#include <Drivers/Audio/IntelHda.hpp>
|
||||||
#include <Pci/Pci.hpp>
|
#include <Pci/Pci.hpp>
|
||||||
|
#include <Arch/Arch.hpp>
|
||||||
|
#include <Arch/Platform.hpp>
|
||||||
|
#include <Arch/Smp.hpp>
|
||||||
|
|
||||||
#include "Syscall.hpp"
|
#include "Syscall.hpp"
|
||||||
#include <Drivers/Storage/BlockDevice.hpp>
|
#include <Drivers/Storage/BlockDevice.hpp>
|
||||||
@@ -69,28 +71,21 @@ namespace montauk::abi {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// CPU cores (category 0)
|
// CPU cores (category 0)
|
||||||
int cpuCount = Hal::GetDetectedCpuCount();
|
int cpuCount = Smp::GetDetectedCpuCount();
|
||||||
if (cpuCount > 0) {
|
if (cpuCount > 0) {
|
||||||
char detail[48];
|
char detail[48];
|
||||||
int p = 0;
|
int p = 0;
|
||||||
p = dl_append(detail, p, "x86_64, ", 48);
|
p = dl_append(detail, p, MONTAUK_ARCH_NAME ", ", 48);
|
||||||
p = dl_append_dec(detail, p, cpuCount, 48);
|
p = dl_append_dec(detail, p, cpuCount, 48);
|
||||||
p = dl_append(detail, p, " core(s)", 48);
|
p = dl_append(detail, p, " core(s)", 48);
|
||||||
add(0, "Processor", detail);
|
add(0, "Processor", detail);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Interrupt controllers (category 1)
|
// Interrupt controllers (category 1), timer (2), platform input (3)
|
||||||
add(1, "Local APIC", "");
|
Arch::ListPlatformDevices([](void* ctx, uint8_t category,
|
||||||
add(1, "I/O APIC", "");
|
const char* name, const char* detail) {
|
||||||
|
(*(decltype(add)*)ctx)(category, name, detail);
|
||||||
// Timer (category 2)
|
}, &add);
|
||||||
add(2, "LAPIC Timer", "Local APIC periodic timer");
|
|
||||||
|
|
||||||
// PS/2 Input (category 3)
|
|
||||||
add(3, "PS/2 Keyboard", "IRQ 1");
|
|
||||||
if (Drivers::PS2::IsDualChannel()) {
|
|
||||||
add(3, "PS/2 Mouse", "IRQ 12");
|
|
||||||
}
|
|
||||||
|
|
||||||
// USB devices (category 4)
|
// USB devices (category 4)
|
||||||
if (Drivers::USB::Xhci::IsInitialized()) {
|
if (Drivers::USB::Xhci::IsInitialized()) {
|
||||||
@@ -128,11 +123,24 @@ namespace montauk::abi {
|
|||||||
if (Drivers::Net::E1000E::IsInitialized()) {
|
if (Drivers::Net::E1000E::IsInitialized()) {
|
||||||
add(5, "Intel E1000E", "Gigabit Ethernet (82574L)");
|
add(5, "Intel E1000E", "Gigabit Ethernet (82574L)");
|
||||||
}
|
}
|
||||||
|
if (Drivers::Net::Genet::IsInitialized()) {
|
||||||
|
uint32_t speed = Drivers::Net::Genet::GetLinkSpeed();
|
||||||
|
add(5, "Broadcom GENET", speed == 1000 ? "Gigabit Ethernet (1000 Mbit/s)"
|
||||||
|
: speed == 100 ? "Gigabit Ethernet (100 Mbit/s)"
|
||||||
|
: speed == 10 ? "Gigabit Ethernet (10 Mbit/s)"
|
||||||
|
: "Gigabit Ethernet (no link)");
|
||||||
|
}
|
||||||
if (Drivers::Net::Wifi::IsPresent()) {
|
if (Drivers::Net::Wifi::IsPresent()) {
|
||||||
add(5, "Intel Wi-Fi", Drivers::Net::Wifi::IsLinkUp()
|
add(5, "Intel Wi-Fi", Drivers::Net::Wifi::IsLinkUp()
|
||||||
? "802.11 wireless (connected)"
|
? "802.11 wireless (connected)"
|
||||||
: "802.11 wireless");
|
: "802.11 wireless");
|
||||||
}
|
}
|
||||||
|
// An on-board UART Bluetooth controller (Raspberry Pi) is no USB
|
||||||
|
// device, so it is listed here; USB adapters appear above.
|
||||||
|
if (Drivers::Bluetooth::IsInitialized() &&
|
||||||
|
Drivers::Bluetooth::Hci::GetTransport() == Drivers::Bluetooth::Hci::Transport::Uart) {
|
||||||
|
add(5, "Bluetooth Adapter", "Broadcom, HCI over UART");
|
||||||
|
}
|
||||||
|
|
||||||
// Display (category 6)
|
// Display (category 6)
|
||||||
if (Drivers::Graphics::IntelGPU::IsInitialized()) {
|
if (Drivers::Graphics::IntelGPU::IsInitialized()) {
|
||||||
@@ -154,7 +162,7 @@ namespace montauk::abi {
|
|||||||
add(9, "Intel HDA", detail);
|
add(9, "Intel HDA", detail);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Drivers::USB::Bluetooth::IsInitialized()) {
|
if (Drivers::Bluetooth::IsInitialized()) {
|
||||||
add(9, "Bluetooth Audio", "A2DP (SBC)");
|
add(9, "Bluetooth Audio", "A2DP (SBC)");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -254,6 +262,10 @@ namespace montauk::abi {
|
|||||||
case Drivers::Storage::BLOCK_KIND_USB_MSC:
|
case Drivers::Storage::BLOCK_KIND_USB_MSC:
|
||||||
buf->type = 4;
|
buf->type = 4;
|
||||||
break;
|
break;
|
||||||
|
case Drivers::Storage::BLOCK_KIND_SD:
|
||||||
|
buf->type = 5;
|
||||||
|
buf->rpm = 1;
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,6 +27,23 @@ namespace montauk::abi {
|
|||||||
return required == 0 || Sched::HasCapability(required);
|
return required == 0 || Sched::HasCapability(required);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Drivers fail a create under a missing parent with a bare -1; report it
|
||||||
|
// as NoSuchPath so callers can tell it apart (Rust's create_dir_all only
|
||||||
|
// creates the missing ancestors when mkdir says NotFound).
|
||||||
|
static bool ParentDirExists(const char* resolved) {
|
||||||
|
char parent[256];
|
||||||
|
int len = Lib::strlen(resolved);
|
||||||
|
if (len >= (int)sizeof(parent)) return false;
|
||||||
|
memcpy(parent, resolved, len + 1);
|
||||||
|
while (len > 0 && parent[len - 1] == '/') len--;
|
||||||
|
while (len > 0 && parent[len - 1] != '/') len--;
|
||||||
|
while (len > 0 && parent[len - 1] == '/') len--;
|
||||||
|
if (len == 0 || parent[len - 1] == ':') return true; // drive root
|
||||||
|
parent[len] = '\0';
|
||||||
|
Fs::Vfs::StatInfo info;
|
||||||
|
return Fs::Vfs::VfsStat(parent, info) == 0 && info.isDir;
|
||||||
|
}
|
||||||
|
|
||||||
static int Sys_Open(const char* path) {
|
static int Sys_Open(const char* path) {
|
||||||
char resolved[256];
|
char resolved[256];
|
||||||
if (!ResolveProcessPath(path, resolved, sizeof(resolved))) return InvalidArgument;
|
if (!ResolveProcessPath(path, resolved, sizeof(resolved))) return InvalidArgument;
|
||||||
@@ -77,14 +94,14 @@ namespace montauk::abi {
|
|||||||
proc->readdirCursor = (slot + 1) % Sched::UserReadDirSlots;
|
proc->readdirCursor = (slot + 1) % Sched::UserReadDirSlots;
|
||||||
|
|
||||||
uint64_t userVa = Sched::UserReadDirBase + (uint64_t)slot * 0x1000ULL;
|
uint64_t userVa = Sched::UserReadDirBase + (uint64_t)slot * 0x1000ULL;
|
||||||
uint64_t physAddr = Memory::VMM::Paging::GetPhysAddr(proc->pml4Phys, userVa);
|
uint64_t physAddr = Memory::VMM::Paging::GetPhysAddr(proc->pageTableRoot, userVa);
|
||||||
uint8_t* pageBuf = nullptr;
|
uint8_t* pageBuf = nullptr;
|
||||||
|
|
||||||
if (physAddr == 0) {
|
if (physAddr == 0) {
|
||||||
void* page = Memory::g_pfa->AllocateZeroed();
|
void* page = Memory::g_pfa->AllocateZeroed();
|
||||||
if (page == nullptr) return OutOfMemory;
|
if (page == nullptr) return OutOfMemory;
|
||||||
physAddr = Memory::SubHHDM((uint64_t)page);
|
physAddr = Memory::SubHHDM((uint64_t)page);
|
||||||
if (!Memory::VMM::Paging::MapUserIn(proc->pml4Phys, physAddr, userVa)) {
|
if (!Memory::VMM::Paging::MapUserIn(proc->pageTableRoot, physAddr, userVa)) {
|
||||||
Memory::g_pfa->Free(page);
|
Memory::g_pfa->Free(page);
|
||||||
return OutOfMemory;
|
return OutOfMemory;
|
||||||
}
|
}
|
||||||
@@ -117,6 +134,7 @@ namespace montauk::abi {
|
|||||||
char resolved[256];
|
char resolved[256];
|
||||||
if (!ResolveProcessPath(path, resolved, sizeof(resolved))) return InvalidArgument;
|
if (!ResolveProcessPath(path, resolved, sizeof(resolved))) return InvalidArgument;
|
||||||
if (!CanModifyFilePath(resolved)) return SystemIntegrityError;
|
if (!CanModifyFilePath(resolved)) return SystemIntegrityError;
|
||||||
|
if (!ParentDirExists(resolved)) return NoSuchPath;
|
||||||
return Ipc::CreateFileHandle(resolved);
|
return Ipc::CreateFileHandle(resolved);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -171,6 +189,7 @@ namespace montauk::abi {
|
|||||||
char resolved[256];
|
char resolved[256];
|
||||||
if (!ResolveProcessPath(path, resolved, sizeof(resolved))) return InvalidArgument;
|
if (!ResolveProcessPath(path, resolved, sizeof(resolved))) return InvalidArgument;
|
||||||
if (!CanModifyFilePath(resolved)) return SystemIntegrityError;
|
if (!CanModifyFilePath(resolved)) return SystemIntegrityError;
|
||||||
|
if (!ParentDirExists(resolved)) return NoSuchPath;
|
||||||
return Fs::Vfs::VfsMkdir(resolved);
|
return Fs::Vfs::VfsMkdir(resolved);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+23
-13
@@ -12,6 +12,7 @@
|
|||||||
#include <Graphics/Framebuffer.hpp>
|
#include <Graphics/Framebuffer.hpp>
|
||||||
#include <Drivers/Graphics/IntelGPU.hpp>
|
#include <Drivers/Graphics/IntelGPU.hpp>
|
||||||
#include <ACPI/AcpiVideo.hpp>
|
#include <ACPI/AcpiVideo.hpp>
|
||||||
|
#include <ACPI/WmiEcBacklight.hpp>
|
||||||
#include <Terminal/Terminal.hpp>
|
#include <Terminal/Terminal.hpp>
|
||||||
|
|
||||||
#include "Syscall.hpp"
|
#include "Syscall.hpp"
|
||||||
@@ -82,7 +83,7 @@ namespace montauk::abi {
|
|||||||
|
|
||||||
for (uint64_t i = 0; i < numPages; i++) {
|
for (uint64_t i = 0; i < numPages; i++) {
|
||||||
if (!Memory::VMM::Paging::MapUserInWC(
|
if (!Memory::VMM::Paging::MapUserInWC(
|
||||||
proc->pml4Phys,
|
proc->pageTableRoot,
|
||||||
fbPhys + i * 0x1000,
|
fbPhys + i * 0x1000,
|
||||||
userVa + i * 0x1000
|
userVa + i * 0x1000
|
||||||
)) {
|
)) {
|
||||||
@@ -97,7 +98,7 @@ namespace montauk::abi {
|
|||||||
uint64_t buf1Phys = Drivers::Graphics::IntelGPU::GetBufferPhys(1);
|
uint64_t buf1Phys = Drivers::Graphics::IntelGPU::GetBufferPhys(1);
|
||||||
for (uint64_t i = 0; i < numPages; i++) {
|
for (uint64_t i = 0; i < numPages; i++) {
|
||||||
if (!Memory::VMM::Paging::MapUserInWC(
|
if (!Memory::VMM::Paging::MapUserInWC(
|
||||||
proc->pml4Phys,
|
proc->pageTableRoot,
|
||||||
buf1Phys + i * 0x1000,
|
buf1Phys + i * 0x1000,
|
||||||
userVa + (numPages + i) * 0x1000
|
userVa + (numPages + i) * 0x1000
|
||||||
)) {
|
)) {
|
||||||
@@ -129,29 +130,38 @@ namespace montauk::abi {
|
|||||||
return GPU::Flip((int)index, (flags & 1) != 0, proc->pid);
|
return GPU::Flip((int)index, (flags & 1) != 0, proc->pid);
|
||||||
}
|
}
|
||||||
|
|
||||||
// The firmware's ACPI brightness methods drive the built-in panel, so
|
// Firmware brightness interfaces (EC via WMI, ACPI _BCM) drive the
|
||||||
// they stand in only when that panel is what is on screen (or no native
|
// built-in panel, so they apply only while that panel is what is on
|
||||||
// driver knows what is): never for an external monitor without DDC/CI.
|
// screen (or no native driver knows what is): never for an external
|
||||||
static bool AcpiBrightnessApplies() {
|
// monitor, where they would silently dim a closed lid instead.
|
||||||
|
static bool PanelIsActiveOutput() {
|
||||||
if (!Drivers::Graphics::IntelGPU::IsInitialized()) return true;
|
if (!Drivers::Graphics::IntelGPU::IsInitialized()) return true;
|
||||||
uint8_t type = Drivers::Graphics::IntelGPU::ActiveConnectorType();
|
uint8_t type = Drivers::Graphics::IntelGPU::ActiveConnectorType();
|
||||||
return type == DISPLAY_CONNECTOR_EDP || type == DISPLAY_CONNECTOR_LVDS;
|
return type == DISPLAY_CONNECTOR_EDP || type == DISPLAY_CONNECTOR_LVDS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Order follows who actually owns the backlight. A firmware that names
|
||||||
|
// the EC as the source has taken it away from the GPU, whose PWM then
|
||||||
|
// still accepts writes that reach nothing; so the EC interface goes
|
||||||
|
// first, then the GPU's own controls, then ACPI _BCM as a last resort.
|
||||||
static int DisplayBrightness(int percent) {
|
static int DisplayBrightness(int percent) {
|
||||||
|
bool panel = PanelIsActiveOutput();
|
||||||
|
if (panel) {
|
||||||
|
int rc = Hal::WmiEcBacklight::Brightness(percent);
|
||||||
|
if (rc != -1) return rc;
|
||||||
|
}
|
||||||
int rc = Drivers::Graphics::IntelGPU::Brightness(percent);
|
int rc = Drivers::Graphics::IntelGPU::Brightness(percent);
|
||||||
if (rc == -1 && AcpiBrightnessApplies())
|
if (rc == -1 && panel) rc = Hal::AcpiVideo::Brightness(percent);
|
||||||
rc = Hal::AcpiVideo::Brightness(percent);
|
|
||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int64_t Sys_DisplayInfo(DisplayInfo* out) {
|
static int64_t Sys_DisplayInfo(DisplayInfo* out) {
|
||||||
if (!Drivers::Graphics::IntelGPU::GetDisplayInfo(out)) return NoDevice;
|
if (!Drivers::Graphics::IntelGPU::GetDisplayInfo(out)) return NoDevice;
|
||||||
if (out->brightness < 0 && AcpiBrightnessApplies()) {
|
// The driver only knows its own backends; report what a set would use.
|
||||||
out->brightness = Hal::AcpiVideo::Brightness(-1);
|
int level = DisplayBrightness(-1);
|
||||||
if (out->brightness >= 0) out->capabilities |= DISPLAY_CAP_BRIGHTNESS;
|
out->brightness = level >= 0 ? level : -1;
|
||||||
else out->brightness = -1;
|
if (level >= 0) out->capabilities |= DISPLAY_CAP_BRIGHTNESS;
|
||||||
}
|
else out->capabilities &= ~DISPLAY_CAP_BRIGHTNESS;
|
||||||
return Success;
|
return Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+15
-15
@@ -107,7 +107,7 @@ namespace montauk::abi {
|
|||||||
if (page == nullptr) break;
|
if (page == nullptr) break;
|
||||||
uint64_t phys = Memory::SubHHDM((uint64_t)page);
|
uint64_t phys = Memory::SubHHDM((uint64_t)page);
|
||||||
if (!Memory::VMM::Paging::MapUserInPermissions(
|
if (!Memory::VMM::Paging::MapUserInPermissions(
|
||||||
proc->pml4Phys, phys, pageVa, writable, executable)) {
|
proc->pageTableRoot, phys, pageVa, writable, executable)) {
|
||||||
Memory::g_pfa->Free(page);
|
Memory::g_pfa->Free(page);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -131,7 +131,7 @@ namespace montauk::abi {
|
|||||||
|
|
||||||
// Reset heap allocation tracking for a process slot.
|
// Reset heap allocation tracking for a process slot.
|
||||||
// The actual physical pages are freed by Paging::FreeUserHalf() during process cleanup.
|
// The actual physical pages are freed by Paging::FreeUserHalf() during process cleanup.
|
||||||
inline void CleanupHeapForSlot(int slot, uint64_t /*pml4Phys*/) {
|
inline void CleanupHeapForSlot(int slot, uint64_t /*pageTableRoot*/) {
|
||||||
if (slot < 0 || slot >= Sched::MaxProcesses) return;
|
if (slot < 0 || slot >= Sched::MaxProcesses) return;
|
||||||
g_heapLocks[slot].Acquire();
|
g_heapLocks[slot].Acquire();
|
||||||
HeapAlloc* area = g_heapAllocs[slot];
|
HeapAlloc* area = g_heapAllocs[slot];
|
||||||
@@ -182,7 +182,7 @@ namespace montauk::abi {
|
|||||||
}
|
}
|
||||||
for (uint64_t i = 0; i < area->numPages; i++)
|
for (uint64_t i = 0; i < area->numPages; i++)
|
||||||
if (Memory::VMM::Paging::GetPhysAddr(
|
if (Memory::VMM::Paging::GetPhysAddr(
|
||||||
proc->pml4Phys, area->va + i * 0x1000ULL) != 0)
|
proc->pageTableRoot, area->va + i * 0x1000ULL) != 0)
|
||||||
resident++;
|
resident++;
|
||||||
*link = area->next;
|
*link = area->next;
|
||||||
area->next = released;
|
area->next = released;
|
||||||
@@ -194,7 +194,7 @@ namespace montauk::abi {
|
|||||||
// user TLB entry can otherwise corrupt the frame's next owner.
|
// user TLB entry can otherwise corrupt the frame's next owner.
|
||||||
while (released != nullptr) {
|
while (released != nullptr) {
|
||||||
HeapAlloc* next = released->next;
|
HeapAlloc* next = released->next;
|
||||||
Memory::UnmapAndFreeUserRange(proc->pml4Phys, released->va,
|
Memory::UnmapAndFreeUserRange(proc->pageTableRoot, released->va,
|
||||||
released->numPages);
|
released->numPages);
|
||||||
Sched::ReleaseUserHeapRange(slot, released->va,
|
Sched::ReleaseUserHeapRange(slot, released->va,
|
||||||
released->numPages * 0x1000ULL);
|
released->numPages * 0x1000ULL);
|
||||||
@@ -253,11 +253,11 @@ namespace montauk::abi {
|
|||||||
|
|
||||||
uint64_t resident = 0;
|
uint64_t resident = 0;
|
||||||
for (uint64_t i = 0; i < pages; i++)
|
for (uint64_t i = 0; i < pages; i++)
|
||||||
if (Memory::VMM::Paging::GetPhysAddr(proc->pml4Phys, addr + i * 0x1000ULL) != 0)
|
if (Memory::VMM::Paging::GetPhysAddr(proc->pageTableRoot, addr + i * 0x1000ULL) != 0)
|
||||||
resident++;
|
resident++;
|
||||||
g_heapLocks[slot].Release();
|
g_heapLocks[slot].Release();
|
||||||
|
|
||||||
Memory::UnmapAndFreeUserRange(proc->pml4Phys, addr, pages);
|
Memory::UnmapAndFreeUserRange(proc->pageTableRoot, addr, pages);
|
||||||
Sched::ReleaseUserHeapRange(slot, addr, size);
|
Sched::ReleaseUserHeapRange(slot, addr, size);
|
||||||
g_heapLocks[slot].Acquire();
|
g_heapLocks[slot].Acquire();
|
||||||
Sched::g_allocatedPages[slot] -= resident;
|
Sched::g_allocatedPages[slot] -= resident;
|
||||||
@@ -336,10 +336,10 @@ namespace montauk::abi {
|
|||||||
bool writable = (prot & VmProtWrite) != 0;
|
bool writable = (prot & VmProtWrite) != 0;
|
||||||
bool executable = (prot & VmProtExec) != 0;
|
bool executable = (prot & VmProtExec) != 0;
|
||||||
for (uint64_t i = 0; i < pages; i++) {
|
for (uint64_t i = 0; i < pages; i++) {
|
||||||
if (Memory::VMM::Paging::GetPhysAddr(proc->pml4Phys,
|
if (Memory::VMM::Paging::GetPhysAddr(proc->pageTableRoot,
|
||||||
addr + i * 0x1000ULL) == 0)
|
addr + i * 0x1000ULL) == 0)
|
||||||
continue;
|
continue;
|
||||||
if (!Memory::VMM::Paging::ProtectUserIn(proc->pml4Phys,
|
if (!Memory::VMM::Paging::ProtectUserIn(proc->pageTableRoot,
|
||||||
addr + i * 0x1000ULL,
|
addr + i * 0x1000ULL,
|
||||||
writable, executable)) {
|
writable, executable)) {
|
||||||
g_heapLocks[slot].Release();
|
g_heapLocks[slot].Release();
|
||||||
@@ -347,17 +347,17 @@ namespace montauk::abi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
g_heapLocks[slot].Release();
|
g_heapLocks[slot].Release();
|
||||||
Memory::ShootdownUserRange(proc->pml4Phys, addr, (uint32_t)pages);
|
Memory::ShootdownUserRange(proc->pageTableRoot, addr, (uint32_t)pages);
|
||||||
return Success;
|
return Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Resolve a non-present access inside an anonymous VMA. This is used for
|
// Resolve a non-present access inside an anonymous VMA. This is used for
|
||||||
// both ring-3 faults and kernel copies to untouched userspace buffers.
|
// both ring-3 faults and kernel copies to untouched userspace buffers.
|
||||||
inline bool TryHandleAnonymousPageFault(uint64_t faultAddr, uint64_t errorCode) {
|
inline bool TryHandleAnonymousPageFault(uint64_t faultAddr, const Memory::VMM::PageFault& fault) {
|
||||||
auto* proc = Sched::GetCurrentProcessPtr();
|
auto* proc = Sched::GetCurrentProcessPtr();
|
||||||
int slot = GetCurrentSlot();
|
int slot = GetCurrentSlot();
|
||||||
if (proc == nullptr || slot < 0 || faultAddr < Sched::UserHeapBase ||
|
if (proc == nullptr || slot < 0 || faultAddr < Sched::UserHeapBase ||
|
||||||
faultAddr >= Sched::UserHeapLimit || (errorCode & 1) != 0) return false;
|
faultAddr >= Sched::UserHeapLimit || fault.present) return false;
|
||||||
|
|
||||||
uint64_t pageVa = faultAddr & ~0xFFFULL;
|
uint64_t pageVa = faultAddr & ~0xFFFULL;
|
||||||
g_heapLocks[slot].Acquire();
|
g_heapLocks[slot].Acquire();
|
||||||
@@ -370,14 +370,14 @@ namespace montauk::abi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (found == nullptr ||
|
if (found == nullptr ||
|
||||||
((errorCode & (1ULL << 1)) != 0 && (found->prot & VmProtWrite) == 0) ||
|
(fault.write && (found->prot & VmProtWrite) == 0) ||
|
||||||
((errorCode & (1ULL << 4)) != 0 && (found->prot & VmProtExec) == 0)) {
|
(fault.execute && (found->prot & VmProtExec) == 0)) {
|
||||||
g_heapLocks[slot].Release();
|
g_heapLocks[slot].Release();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// A sibling may have resolved the same fault while this CPU waited.
|
// A sibling may have resolved the same fault while this CPU waited.
|
||||||
if (Memory::VMM::Paging::GetPhysAddr(proc->pml4Phys, pageVa) != 0) {
|
if (Memory::VMM::Paging::GetPhysAddr(proc->pageTableRoot, pageVa) != 0) {
|
||||||
g_heapLocks[slot].Release();
|
g_heapLocks[slot].Release();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -389,7 +389,7 @@ namespace montauk::abi {
|
|||||||
}
|
}
|
||||||
uint64_t phys = Memory::SubHHDM((uint64_t)page);
|
uint64_t phys = Memory::SubHHDM((uint64_t)page);
|
||||||
bool mapped = Memory::VMM::Paging::MapUserInPermissions(
|
bool mapped = Memory::VMM::Paging::MapUserInPermissions(
|
||||||
proc->pml4Phys, phys, pageVa,
|
proc->pageTableRoot, phys, pageVa,
|
||||||
(found->prot & VmProtWrite) != 0, (found->prot & VmProtExec) != 0);
|
(found->prot & VmProtWrite) != 0, (found->prot & VmProtExec) != 0);
|
||||||
if (!mapped) Memory::g_pfa->Free(page);
|
if (!mapped) Memory::g_pfa->Free(page);
|
||||||
else Sched::g_allocatedPages[slot]++;
|
else Sched::g_allocatedPages[slot]++;
|
||||||
|
|||||||
@@ -17,13 +17,13 @@ namespace montauk::abi {
|
|||||||
|
|
||||||
// Copy strings into fixed-size arrays (user-accessible)
|
// Copy strings into fixed-size arrays (user-accessible)
|
||||||
const char* name = "MontaukOS";
|
const char* name = "MontaukOS";
|
||||||
const char* ver = "0.2.0";
|
const char* ver = "0.2.1";
|
||||||
for (int i = 0; name[i]; i++) outInfo->osName[i] = name[i];
|
for (int i = 0; name[i]; i++) outInfo->osName[i] = name[i];
|
||||||
outInfo->osName[9] = '\0';
|
outInfo->osName[9] = '\0';
|
||||||
for (int i = 0; ver[i]; i++) outInfo->osVersion[i] = ver[i];
|
for (int i = 0; ver[i]; i++) outInfo->osVersion[i] = ver[i];
|
||||||
outInfo->osVersion[5] = '\0';
|
outInfo->osVersion[5] = '\0';
|
||||||
|
|
||||||
outInfo->apiVersion = 11;
|
outInfo->apiVersion = 12;
|
||||||
outInfo->maxProcesses = Sched::MaxProcesses;
|
outInfo->maxProcesses = Sched::MaxProcesses;
|
||||||
outInfo->buildNumber = MONTAUK_BUILD_NUMBER;
|
outInfo->buildNumber = MONTAUK_BUILD_NUMBER;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -122,8 +122,9 @@ namespace montauk::abi {
|
|||||||
return WriteAllToStream(stream, (const uint8_t*)data, len);
|
return WriteAllToStream(stream, (const uint8_t*)data, len);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int Sys_ChildIoWriteKey(int childPid, const KeyEvent* key) {
|
// Queue a key event (raw bytes, `len` of them) on a redirected child's key
|
||||||
if (key == nullptr) return InvalidArgument;
|
// mailbox. Readers accept both the 9-byte legacy and the full format.
|
||||||
|
static int ChildIoQueueKey(int childPid, const void* key, uint16_t len) {
|
||||||
auto* child = Sched::GetProcessByPid(childPid);
|
auto* child = Sched::GetProcessByPid(childPid);
|
||||||
if (child == nullptr || child->parentPid != Sched::GetCurrentPid())
|
if (child == nullptr || child->parentPid != Sched::GetCurrentPid())
|
||||||
return PermissionDenied;
|
return PermissionDenied;
|
||||||
@@ -133,7 +134,7 @@ namespace montauk::abi {
|
|||||||
|
|
||||||
for (;;) {
|
for (;;) {
|
||||||
uint64_t observedWake = Sched::ObserveObjectWake(mailbox);
|
uint64_t observedWake = Sched::ObserveObjectWake(mailbox);
|
||||||
int rc = Ipc::MailboxSend(mailbox, 0, key, sizeof(KeyEvent));
|
int rc = Ipc::MailboxSend(mailbox, 0, key, len);
|
||||||
if (rc < 0) return GenericFailure;
|
if (rc < 0) return GenericFailure;
|
||||||
if (rc == 0) {
|
if (rc == 0) {
|
||||||
Sched::BlockOnObjectSince(mailbox, 0, observedWake);
|
Sched::BlockOnObjectSince(mailbox, 0, observedWake);
|
||||||
@@ -143,6 +144,22 @@ namespace montauk::abi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int Sys_ChildIoWriteKey(int childPid, const KeyEventV1* key) {
|
||||||
|
if (key == nullptr) return InvalidArgument;
|
||||||
|
KeyEventV1 copy = *key;
|
||||||
|
return ChildIoQueueKey(childPid, ©, sizeof(copy));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Size-versioned: copies min(size, sizeof(KeyEvent)) bytes (validated
|
||||||
|
// readable by the dispatcher) and zero-fills the rest.
|
||||||
|
static int Sys_ChildIoWriteKeyEx(int childPid, const void* key, uint64_t size) {
|
||||||
|
if (key == nullptr || size < sizeof(KeyEventV1)) return InvalidArgument;
|
||||||
|
KeyEvent ev;
|
||||||
|
memset(&ev, 0, sizeof(ev));
|
||||||
|
memcpy(&ev, key, size < sizeof(ev) ? size : sizeof(ev));
|
||||||
|
return ChildIoQueueKey(childPid, &ev, sizeof(ev));
|
||||||
|
}
|
||||||
|
|
||||||
static int Sys_ChildIoSetTermsz(int childPid, int cols, int rows) {
|
static int Sys_ChildIoSetTermsz(int childPid, int cols, int rows) {
|
||||||
auto* child = Sched::GetProcessByPid(childPid);
|
auto* child = Sched::GetProcessByPid(childPid);
|
||||||
if (child == nullptr || child->parentPid != Sched::GetCurrentPid())
|
if (child == nullptr || child->parentPid != Sched::GetCurrentPid())
|
||||||
|
|||||||
+137
-22
@@ -12,6 +12,107 @@
|
|||||||
#include "Common.hpp"
|
#include "Common.hpp"
|
||||||
|
|
||||||
namespace montauk::abi {
|
namespace montauk::abi {
|
||||||
|
// ---- Key text ----------------------------------------------------------
|
||||||
|
// Keyboard layouts are applied in userspace (desktop, terminal), which send
|
||||||
|
// KeyEvents carrying the Unicode codepoint. Binaries built before that send
|
||||||
|
// 9-byte KeyEventV1s whose `ascii` is a Windows-1252 byte; both kinds share
|
||||||
|
// a redirected child's key mailbox, told apart by message length.
|
||||||
|
|
||||||
|
static uint32_t KeyCp1252ToUnicode(uint8_t b) {
|
||||||
|
static constexpr uint16_t high[32] = {
|
||||||
|
0x20AC, 0x81, 0x201A, 0x0192, 0x201E, 0x2026, 0x2020, 0x2021,
|
||||||
|
0x02C6, 0x2030, 0x0160, 0x2039, 0x0152, 0x8D, 0x017D, 0x8F,
|
||||||
|
0x90, 0x2018, 0x2019, 0x201C, 0x201D, 0x2022, 0x2013, 0x2014,
|
||||||
|
0x02DC, 0x2122, 0x0161, 0x203A, 0x0153, 0x9D, 0x017E, 0x0178,
|
||||||
|
};
|
||||||
|
return (b >= 0x80 && b <= 0x9F) ? high[b - 0x80] : b;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The character a key event types, or 0 for control/navigation keys.
|
||||||
|
static uint32_t KeyTextCodepoint(const KeyEvent& ev) {
|
||||||
|
if (ev.codepoint) return ev.codepoint;
|
||||||
|
uint8_t b = (uint8_t)ev.ascii;
|
||||||
|
if (b < 0x20 || b == 0x7F) return 0;
|
||||||
|
return KeyCp1252ToUnicode(b);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int KeyEncodeUtf8(uint32_t cp, uint8_t* out) {
|
||||||
|
if (cp > 0x10FFFF || (cp >= 0xD800 && cp <= 0xDFFF)) cp = 0xFFFD;
|
||||||
|
if (cp < 0x80) { out[0] = (uint8_t)cp; return 1; }
|
||||||
|
if (cp < 0x800) {
|
||||||
|
out[0] = (uint8_t)(0xC0 | (cp >> 6));
|
||||||
|
out[1] = (uint8_t)(0x80 | (cp & 0x3F));
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
if (cp < 0x10000) {
|
||||||
|
out[0] = (uint8_t)(0xE0 | (cp >> 12));
|
||||||
|
out[1] = (uint8_t)(0x80 | ((cp >> 6) & 0x3F));
|
||||||
|
out[2] = (uint8_t)(0x80 | (cp & 0x3F));
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
out[0] = (uint8_t)(0xF0 | (cp >> 18));
|
||||||
|
out[1] = (uint8_t)(0x80 | ((cp >> 12) & 0x3F));
|
||||||
|
out[2] = (uint8_t)(0x80 | ((cp >> 6) & 0x3F));
|
||||||
|
out[3] = (uint8_t)(0x80 | (cp & 0x3F));
|
||||||
|
return 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void KeyFromDriver(const Drivers::PS2::Keyboard::KeyEvent& k, KeyEvent* out) {
|
||||||
|
memset(out, 0, sizeof(KeyEvent));
|
||||||
|
out->scancode = k.Scancode;
|
||||||
|
out->ascii = k.Ascii;
|
||||||
|
out->pressed = k.Pressed;
|
||||||
|
out->shift = k.Shift;
|
||||||
|
out->ctrl = k.Ctrl;
|
||||||
|
out->alt = k.Alt;
|
||||||
|
out->capslock = k.CapsLock;
|
||||||
|
out->altgr = k.AltGr;
|
||||||
|
out->extended = k.Extended;
|
||||||
|
// The kernel's own scancode table is US ASCII.
|
||||||
|
uint8_t a = (uint8_t)k.Ascii;
|
||||||
|
out->codepoint = (a >= 0x20 && a < 0x7F) ? a : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Receive one key from a redirected child's mailbox as a full KeyEvent,
|
||||||
|
// upgrading legacy 9-byte messages. Returns MailboxRecv's result.
|
||||||
|
static int KeyMailboxRecv(Ipc::Mailbox* mailbox, KeyEvent* out) {
|
||||||
|
memset(out, 0, sizeof(KeyEvent));
|
||||||
|
uint16_t len = sizeof(KeyEvent);
|
||||||
|
int rc = Ipc::MailboxRecv(mailbox, nullptr, out, &len, true);
|
||||||
|
if (rc > 0 && len < sizeof(KeyEvent)) {
|
||||||
|
memset((uint8_t*)out + len, 0, sizeof(KeyEvent) - len);
|
||||||
|
out->codepoint = KeyTextCodepoint(*out);
|
||||||
|
}
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Turn a pressed key into the next getchar byte. Characters above ASCII are
|
||||||
|
// returned as UTF-8, one byte per call; the rest is parked in the process.
|
||||||
|
static bool KeyTakeChar(Sched::Process* proc, const KeyEvent& ev, char* out) {
|
||||||
|
if (!ev.pressed) return false;
|
||||||
|
uint32_t cp = KeyTextCodepoint(ev);
|
||||||
|
if (cp >= 0x80 && proc != nullptr) {
|
||||||
|
int n = KeyEncodeUtf8(cp, proc->getcharUtf8);
|
||||||
|
proc->getcharUtf8Len = (uint8_t)n;
|
||||||
|
proc->getcharUtf8Pos = 1;
|
||||||
|
*out = (char)proc->getcharUtf8[0];
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (ev.ascii != 0 && ev.ascii != KEY_ASCII_SUBSTITUTE) {
|
||||||
|
*out = ev.ascii;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool KeyPendingChar(Sched::Process* proc, char* out) {
|
||||||
|
if (proc == nullptr || proc->getcharUtf8Pos >= proc->getcharUtf8Len) return false;
|
||||||
|
*out = (char)proc->getcharUtf8[proc->getcharUtf8Pos++];
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- Syscalls ------------------------------------------------------------
|
||||||
|
|
||||||
static bool Sys_IsKeyAvailable() {
|
static bool Sys_IsKeyAvailable() {
|
||||||
auto* proc = Sched::GetCurrentProcessPtr();
|
auto* proc = Sched::GetCurrentProcessPtr();
|
||||||
if (proc && proc->redirected) {
|
if (proc && proc->redirected) {
|
||||||
@@ -27,8 +128,8 @@ namespace montauk::abi {
|
|||||||
return Drivers::PS2::Keyboard::IsKeyAvailable();
|
return Drivers::PS2::Keyboard::IsKeyAvailable();
|
||||||
}
|
}
|
||||||
|
|
||||||
static void Sys_GetKey(KeyEvent* outEvent) {
|
// Blocking read of the next key event, in the full format.
|
||||||
if (outEvent == nullptr) return;
|
static void ReadKeyEvent(KeyEvent* outEvent) {
|
||||||
auto* proc = Sched::GetCurrentProcessPtr();
|
auto* proc = Sched::GetCurrentProcessPtr();
|
||||||
if (proc && proc->redirected) {
|
if (proc && proc->redirected) {
|
||||||
Ipc::HandleSnapshot snapshot;
|
Ipc::HandleSnapshot snapshot;
|
||||||
@@ -36,8 +137,7 @@ namespace montauk::abi {
|
|||||||
if (mailbox != nullptr) {
|
if (mailbox != nullptr) {
|
||||||
for (;;) {
|
for (;;) {
|
||||||
uint64_t observedWake = Sched::ObserveObjectWake(mailbox);
|
uint64_t observedWake = Sched::ObserveObjectWake(mailbox);
|
||||||
uint16_t len = sizeof(KeyEvent);
|
int rc = KeyMailboxRecv(mailbox, outEvent);
|
||||||
int rc = Ipc::MailboxRecv(mailbox, nullptr, outEvent, &len, true);
|
|
||||||
if (rc > 0) return;
|
if (rc > 0) return;
|
||||||
if (rc < 0) {
|
if (rc < 0) {
|
||||||
memset(outEvent, 0, sizeof(KeyEvent));
|
memset(outEvent, 0, sizeof(KeyEvent));
|
||||||
@@ -47,20 +147,33 @@ namespace montauk::abi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
auto k = Drivers::PS2::Keyboard::GetKey();
|
KeyFromDriver(Drivers::PS2::Keyboard::GetKey(), outEvent);
|
||||||
outEvent->scancode = k.Scancode;
|
}
|
||||||
outEvent->ascii = k.Ascii;
|
|
||||||
outEvent->pressed = k.Pressed;
|
// SYS_GETKEY: legacy 9-byte event. A new-format event's first 9 bytes are
|
||||||
outEvent->shift = k.Shift;
|
// already a valid KeyEventV1 (ascii holds the 1252 byte or SUB).
|
||||||
outEvent->ctrl = k.Ctrl;
|
static void Sys_GetKey(KeyEventV1* outEvent) {
|
||||||
outEvent->alt = k.Alt;
|
if (outEvent == nullptr) return;
|
||||||
outEvent->capslock = k.CapsLock;
|
KeyEvent ev;
|
||||||
outEvent->altgr = k.AltGr;
|
ReadKeyEvent(&ev);
|
||||||
outEvent->extended = k.Extended;
|
memcpy(outEvent, &ev, sizeof(KeyEventV1));
|
||||||
|
}
|
||||||
|
|
||||||
|
// SYS_GETKEY_EX: size-versioned. The caller's size was validated by the
|
||||||
|
// dispatcher (>= sizeof(KeyEventV1), writable).
|
||||||
|
static int Sys_GetKeyEx(void* outEvent, uint64_t size) {
|
||||||
|
KeyEvent ev;
|
||||||
|
ReadKeyEvent(&ev);
|
||||||
|
uint64_t n = size < sizeof(KeyEvent) ? size : sizeof(KeyEvent);
|
||||||
|
memcpy(outEvent, &ev, n);
|
||||||
|
if (size > n) memset((uint8_t*)outEvent + n, 0, size - n);
|
||||||
|
return Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
static char Sys_GetChar() {
|
static char Sys_GetChar() {
|
||||||
auto* proc = Sched::GetCurrentProcessPtr();
|
auto* proc = Sched::GetCurrentProcessPtr();
|
||||||
|
char pending;
|
||||||
|
if (KeyPendingChar(proc, &pending)) return pending;
|
||||||
if (proc && proc->redirected) {
|
if (proc && proc->redirected) {
|
||||||
Ipc::HandleSnapshot inputSnapshot;
|
Ipc::HandleSnapshot inputSnapshot;
|
||||||
Ipc::HandleSnapshot mailboxSnapshot;
|
Ipc::HandleSnapshot mailboxSnapshot;
|
||||||
@@ -78,11 +191,11 @@ namespace montauk::abi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (mailbox != nullptr) {
|
if (mailbox != nullptr) {
|
||||||
KeyEvent ev{};
|
KeyEvent ev;
|
||||||
uint16_t len = sizeof(ev);
|
int rc = KeyMailboxRecv(mailbox, &ev);
|
||||||
int rc = Ipc::MailboxRecv(mailbox, nullptr, &ev, &len, true);
|
|
||||||
if (rc > 0) {
|
if (rc > 0) {
|
||||||
if (ev.pressed && ev.ascii != 0) return ev.ascii;
|
char c;
|
||||||
|
if (KeyTakeChar(proc, ev, &c)) return c;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (rc < 0 && input == nullptr) return 0;
|
if (rc < 0 && input == nullptr) return 0;
|
||||||
@@ -115,6 +228,8 @@ namespace montauk::abi {
|
|||||||
*/
|
*/
|
||||||
static char Sys_GetCharNb() {
|
static char Sys_GetCharNb() {
|
||||||
auto* proc = Sched::GetCurrentProcessPtr();
|
auto* proc = Sched::GetCurrentProcessPtr();
|
||||||
|
char pending;
|
||||||
|
if (KeyPendingChar(proc, &pending)) return pending;
|
||||||
if (proc && proc->redirected) {
|
if (proc && proc->redirected) {
|
||||||
Ipc::HandleSnapshot inputSnapshot;
|
Ipc::HandleSnapshot inputSnapshot;
|
||||||
Ipc::HandleSnapshot mailboxSnapshot;
|
Ipc::HandleSnapshot mailboxSnapshot;
|
||||||
@@ -127,10 +242,10 @@ namespace montauk::abi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
while (mailbox != nullptr) {
|
while (mailbox != nullptr) {
|
||||||
KeyEvent ev{};
|
KeyEvent ev;
|
||||||
uint16_t len = sizeof(ev);
|
if (KeyMailboxRecv(mailbox, &ev) <= 0) break;
|
||||||
if (Ipc::MailboxRecv(mailbox, nullptr, &ev, &len, true) <= 0) break;
|
char c;
|
||||||
if (ev.pressed && ev.ascii != 0) return ev.ascii;
|
if (KeyTakeChar(proc, ev, &c)) return c;
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ namespace montauk::abi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Load the library into the process's address space
|
// Load the library into the process's address space
|
||||||
uint64_t loadBias = Sched::ElfLoadLib(path, proc->pml4Phys, libSlot);
|
uint64_t loadBias = Sched::ElfLoadLib(path, proc->pageTableRoot, libSlot);
|
||||||
if (loadBias == 0) {
|
if (loadBias == 0) {
|
||||||
Kt::KernelLogStream(Kt::ERROR, "Lib") << "Failed to load library: " << path;
|
Kt::KernelLogStream(Kt::ERROR, "Lib") << "Failed to load library: " << path;
|
||||||
g_libLocks[slot].Release();
|
g_libLocks[slot].Release();
|
||||||
@@ -134,7 +134,7 @@ namespace montauk::abi {
|
|||||||
|
|
||||||
auto* proc = Sched::GetCurrentProcessPtr();
|
auto* proc = Sched::GetCurrentProcessPtr();
|
||||||
if (proc != nullptr) {
|
if (proc != nullptr) {
|
||||||
Memory::UnmapAndFreeUserRange(proc->pml4Phys, libBase,
|
Memory::UnmapAndFreeUserRange(proc->pageTableRoot, libBase,
|
||||||
(libEnd - libBase) / 0x1000ULL);
|
(libEnd - libBase) / 0x1000ULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -203,7 +203,7 @@ namespace montauk::abi {
|
|||||||
uint64_t libBase = GetLibSlotBase(i);
|
uint64_t libBase = GetLibSlotBase(i);
|
||||||
uint64_t libEnd = libBase + Sched::LIB_MAX_SIZE;
|
uint64_t libEnd = libBase + Sched::LIB_MAX_SIZE;
|
||||||
|
|
||||||
Memory::UnmapAndFreeUserRange(proc->pml4Phys, libBase,
|
Memory::UnmapAndFreeUserRange(proc->pageTableRoot, libBase,
|
||||||
(libEnd - libBase) / 0x1000ULL);
|
(libEnd - libBase) / 0x1000ULL);
|
||||||
|
|
||||||
g_libTable[slot][i].inUse = false;
|
g_libTable[slot][i].inUse = false;
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <Sched/Scheduler.hpp>
|
#include <Sched/Scheduler.hpp>
|
||||||
#include <Timekeeping/ApicTimer.hpp>
|
#include <Timekeeping/Timer.hpp>
|
||||||
#include <Net/Icmp.hpp>
|
#include <Net/Icmp.hpp>
|
||||||
#include <Net/Dns.hpp>
|
#include <Net/Dns.hpp>
|
||||||
#include <Net/Ipv4.hpp>
|
#include <Net/Ipv4.hpp>
|
||||||
@@ -18,6 +18,7 @@
|
|||||||
#include <Net/NetIf.hpp>
|
#include <Net/NetIf.hpp>
|
||||||
#include <Drivers/Net/E1000.hpp>
|
#include <Drivers/Net/E1000.hpp>
|
||||||
#include <Drivers/Net/E1000E.hpp>
|
#include <Drivers/Net/E1000E.hpp>
|
||||||
|
#include <Drivers/Net/Genet.hpp>
|
||||||
#include <Drivers/Net/Wifi/Wifi.hpp>
|
#include <Drivers/Net/Wifi/Wifi.hpp>
|
||||||
|
|
||||||
#include "Syscall.hpp"
|
#include "Syscall.hpp"
|
||||||
@@ -157,6 +158,10 @@ namespace montauk::abi {
|
|||||||
out->rxPackets = Drivers::Net::E1000E::GetRxPacketCount();
|
out->rxPackets = Drivers::Net::E1000E::GetRxPacketCount();
|
||||||
out->txPackets = Drivers::Net::E1000E::GetTxPacketCount();
|
out->txPackets = Drivers::Net::E1000E::GetTxPacketCount();
|
||||||
CopyNetStatusDriver(out->driver, "Intel e1000e");
|
CopyNetStatusDriver(out->driver, "Intel e1000e");
|
||||||
|
} else if (Drivers::Net::Genet::IsInitialized()) {
|
||||||
|
out->rxPackets = Drivers::Net::Genet::GetRxPacketCount();
|
||||||
|
out->txPackets = Drivers::Net::Genet::GetTxPacketCount();
|
||||||
|
CopyNetStatusDriver(out->driver, "Broadcom GENET");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+15
-15
@@ -8,13 +8,13 @@
|
|||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <Efi/UEFI.hpp>
|
#include <Efi/UEFI.hpp>
|
||||||
#include <Memory/Paging.hpp>
|
#include <Memory/Paging.hpp>
|
||||||
#include <Io/IoPort.hpp>
|
|
||||||
#include <Graphics/Framebuffer.hpp>
|
#include <Graphics/Framebuffer.hpp>
|
||||||
#include <ACPI/AcpiShutdown.hpp>
|
#include <ACPI/AcpiShutdown.hpp>
|
||||||
#include <ACPI/AcpiSleep.hpp>
|
#include <ACPI/AcpiSleep.hpp>
|
||||||
#include <ACPI/CpuIdle.hpp>
|
|
||||||
#include <ACPI/Battery.hpp>
|
#include <ACPI/Battery.hpp>
|
||||||
#include <Hal/CpuPower.hpp>
|
#include <Arch/Cpu.hpp>
|
||||||
|
#include <Arch/CpuPower.hpp>
|
||||||
|
#include <Arch/Platform.hpp>
|
||||||
#include <Sched/Scheduler.hpp>
|
#include <Sched/Scheduler.hpp>
|
||||||
#include <Terminal/Terminal.hpp>
|
#include <Terminal/Terminal.hpp>
|
||||||
#include <CppLib/Stream.hpp>
|
#include <CppLib/Stream.hpp>
|
||||||
@@ -56,15 +56,13 @@ namespace montauk::abi {
|
|||||||
Kt::KernelLogStream(Kt::INFO, "Power") << "pid " << kcp::dec
|
Kt::KernelLogStream(Kt::INFO, "Power") << "pid " << kcp::dec
|
||||||
<< (uint64_t)Sched::GetCurrentPid() << " entered final reboot stage";
|
<< (uint64_t)Sched::GetCurrentPid() << " entered final reboot stage";
|
||||||
if (Efi::g_ResetSystem) {
|
if (Efi::g_ResetSystem) {
|
||||||
/* Switch to kernel PML4 which has identity-mapped UEFI runtime regions */
|
/* Switch to the kernel address space, which has identity-mapped UEFI runtime regions */
|
||||||
Memory::VMM::LoadCR3(Memory::VMM::g_paging->PML4);
|
Memory::VMM::ActivateKernelAddressSpace();
|
||||||
Efi::g_ResetSystem(Efi::EfiResetCold, 0, 0, nullptr);
|
Efi::g_ResetSystem(Efi::EfiResetCold, 0, 0, nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Fallback: triple fault via null IDT */
|
/* Fallback: architecture-specific reset */
|
||||||
struct [[gnu::packed]] { uint16_t limit; uint64_t base; } nullIdt = {0, 0};
|
Arch::Reboot();
|
||||||
asm volatile("lidt %0; int $0x03" :: "m"(nullIdt));
|
|
||||||
__builtin_unreachable();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void Sys_Shutdown() {
|
static void Sys_Shutdown() {
|
||||||
@@ -75,15 +73,17 @@ namespace montauk::abi {
|
|||||||
Hal::AcpiShutdown::Shutdown();
|
Hal::AcpiShutdown::Shutdown();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Hardware-reduced platforms (PSCI on ARM) */
|
||||||
|
Arch::PowerOff();
|
||||||
|
|
||||||
/* Fallback: EFI ResetSystem */
|
/* Fallback: EFI ResetSystem */
|
||||||
if (Efi::g_ResetSystem) {
|
if (Efi::g_ResetSystem) {
|
||||||
Memory::VMM::LoadCR3(Memory::VMM::g_paging->PML4);
|
Memory::VMM::ActivateKernelAddressSpace();
|
||||||
Efi::g_ResetSystem(Efi::EfiResetShutdown, 0, 0, nullptr);
|
Efi::g_ResetSystem(Efi::EfiResetShutdown, 0, 0, nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Last resort: halt the CPU */
|
/* Last resort: halt the CPU */
|
||||||
asm volatile("cli; hlt");
|
Arch::HaltForever();
|
||||||
__builtin_unreachable();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static int64_t Sys_Suspend() {
|
static int64_t Sys_Suspend() {
|
||||||
@@ -96,8 +96,8 @@ namespace montauk::abi {
|
|||||||
// SYS_POWERINFO. Fills a CPU power/thermal snapshot. Returns -1 when the
|
// SYS_POWERINFO. Fills a CPU power/thermal snapshot. Returns -1 when the
|
||||||
// CPU exposes no power management features (e.g. under QEMU).
|
// CPU exposes no power management features (e.g. under QEMU).
|
||||||
static int64_t Sys_PowerInfo(PowerInfo* out) {
|
static int64_t Sys_PowerInfo(PowerInfo* out) {
|
||||||
Hal::CpuPower::Snapshot s{};
|
Arch::CpuPower::Snapshot s{};
|
||||||
if (!Hal::CpuPower::GetSnapshot(s)) {
|
if (!Arch::CpuPower::GetSnapshot(s)) {
|
||||||
return NoDevice;
|
return NoDevice;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -112,7 +112,7 @@ namespace montauk::abi {
|
|||||||
out->baseMHz = s.BaseMHz;
|
out->baseMHz = s.BaseMHz;
|
||||||
out->maxMHz = s.MaxMHz;
|
out->maxMHz = s.MaxMHz;
|
||||||
out->effMHz = s.EffMHz;
|
out->effMHz = s.EffMHz;
|
||||||
out->apIdleHint = Hal::CpuIdle::GetApIdleHint();
|
out->apIdleHint = s.ApIdleHint;
|
||||||
return Success;
|
return Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#include <Sched/Scheduler.hpp>
|
#include <Sched/Scheduler.hpp>
|
||||||
#include <Timekeeping/ApicTimer.hpp>
|
#include <Timekeeping/Timer.hpp>
|
||||||
#include <Memory/Paging.hpp>
|
#include <Memory/Paging.hpp>
|
||||||
#include <Memory/PageFrameAllocator.hpp>
|
#include <Memory/PageFrameAllocator.hpp>
|
||||||
#include <Memory/HHDM.hpp>
|
#include <Memory/HHDM.hpp>
|
||||||
@@ -220,7 +220,26 @@ namespace montauk::abi {
|
|||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Flag every thread of the caller's process with TerminatedAborted, then
|
||||||
|
// exit this one. A sibling's ExitProcess ends only itself; the main
|
||||||
|
// thread and the other siblings follow via killPending.
|
||||||
|
static void Sys_Abort() {
|
||||||
|
Sched::KillProcess(Sched::GetCurrentPid(), TerminatedAborted);
|
||||||
|
Sched::ExitProcess();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Like Sys_Abort, but a normal exit with the caller's code: every thread
|
||||||
|
// goes, and waiters see exitCode & 0xFF, the same as SYS_EXIT on the main
|
||||||
|
// thread.
|
||||||
|
static void Sys_ExitGroup(int exitCode) {
|
||||||
|
Sched::KillProcess(Sched::GetCurrentPid(), exitCode & 0xFF);
|
||||||
|
Sched::ExitProcess();
|
||||||
|
}
|
||||||
|
|
||||||
static int Sys_Kill(int pid) {
|
static int Sys_Kill(int pid) {
|
||||||
|
// A process may always kill itself (kill(getpid(), ...)); waiters
|
||||||
|
// see TerminatedKilled. abort() uses SYS_ABORT instead.
|
||||||
|
if (pid == Sched::GetCurrentPid()) return Sched::KillProcess(pid);
|
||||||
if (!Sched::HasCapability(CAP_PROCESS_ADMIN)) {
|
if (!Sched::HasCapability(CAP_PROCESS_ADMIN)) {
|
||||||
int ancestor = pid;
|
int ancestor = pid;
|
||||||
bool descendant = false;
|
bool descendant = false;
|
||||||
@@ -302,6 +321,14 @@ namespace montauk::abi {
|
|||||||
return Sched::GetCurrentTid();
|
return Sched::GetCurrentTid();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int Sys_FutexWait(uint64_t addr, uint32_t expected, uint64_t timeoutNs) {
|
||||||
|
return Sched::FutexWait(addr, expected, timeoutNs);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int Sys_FutexWake(uint64_t addr, uint32_t count) {
|
||||||
|
return Sched::FutexWake(addr, count);
|
||||||
|
}
|
||||||
|
|
||||||
static int Sys_Chdir(const char* path) {
|
static int Sys_Chdir(const char* path) {
|
||||||
auto* proc = Sched::GetCurrentProcessPtr();
|
auto* proc = Sched::GetCurrentProcessPtr();
|
||||||
if (proc == nullptr || path == nullptr) return InvalidArgument;
|
if (proc == nullptr || path == nullptr) return InvalidArgument;
|
||||||
|
|||||||
@@ -6,23 +6,23 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
#include <Arch/Cpu.hpp>
|
||||||
|
|
||||||
namespace montauk::abi {
|
namespace montauk::abi {
|
||||||
|
|
||||||
// ---- Random number generation ----
|
// ---- Random number generation ----
|
||||||
// Uses RDTSC mixed with xorshift64* PRNG for entropy.
|
// Uses the CPU timestamp counter mixed with xorshift64* PRNG for entropy.
|
||||||
// RDRAND is intentionally avoided: some firmware disables the RDRAND
|
// RDRAND is intentionally avoided on x86: some firmware disables the
|
||||||
// hardware unit while CPUID still advertises support (bit 30 of ECX),
|
// RDRAND hardware unit while CPUID still advertises support (bit 30 of
|
||||||
// causing #UD on real hardware. RDTSC-based entropy is sufficient for
|
// ECX), causing #UD on real hardware. Timestamp-based entropy is
|
||||||
// seeding BearSSL's PRNG for TLS session keys.
|
// sufficient for seeding BearSSL's PRNG for TLS session keys.
|
||||||
|
|
||||||
static int64_t Sys_GetRandom(uint8_t* buf, uint64_t len) {
|
static int64_t Sys_GetRandom(uint8_t* buf, uint64_t len) {
|
||||||
uint64_t tsc;
|
uint64_t tsc = Arch::ReadTimestamp();
|
||||||
asm volatile("rdtsc; shl $32, %%rdx; or %%rdx, %%rax" : "=a"(tsc) :: "rdx");
|
|
||||||
uint64_t state = tsc;
|
uint64_t state = tsc;
|
||||||
|
|
||||||
for (uint64_t i = 0; i < len; i += 8) {
|
for (uint64_t i = 0; i < len; i += 8) {
|
||||||
asm volatile("rdtsc; shl $32, %%rdx; or %%rdx, %%rax" : "=a"(tsc) :: "rdx");
|
tsc = Arch::ReadTimestamp();
|
||||||
state ^= tsc;
|
state ^= tsc;
|
||||||
state ^= state >> 12;
|
state ^= state >> 12;
|
||||||
state ^= state << 25;
|
state ^= state << 25;
|
||||||
|
|||||||
@@ -163,6 +163,45 @@ namespace montauk::abi {
|
|||||||
return (int64_t)Fs::FsProbe::SyncAndUnmountAll();
|
return (int64_t)Fs::FsProbe::SyncAndUnmountAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Grow a mounted volume to fill the free space after its partition: the
|
||||||
|
// MBR entry first, only as far as the filesystem can use, then the
|
||||||
|
// filesystem a slice per call. Interrupted grows resume from the next
|
||||||
|
// call (or boot), since a partition larger than its filesystem is valid.
|
||||||
|
static int64_t Sys_FsGrow(int drive, FsGrowStatus* out, bool apply) {
|
||||||
|
int partIndex = Fs::FsProbe::GetPartitionForDrive(drive);
|
||||||
|
auto* part = Drivers::Storage::Gpt::GetPartition(partIndex);
|
||||||
|
if (part == nullptr) return UnsupportedOp; // ramdisk, or no such drive
|
||||||
|
|
||||||
|
uint64_t maxSectors = Drivers::Storage::Gpt::GetMbrPartitionMaxSectors(partIndex);
|
||||||
|
if (maxSectors < part->SectorCount) maxSectors = part->SectorCount;
|
||||||
|
|
||||||
|
Fs::Vfs::GrowStatus gs{};
|
||||||
|
int rc = Fs::Vfs::VfsGrow(drive, maxSectors, false, &gs);
|
||||||
|
if (rc == -2) return UnsupportedOp;
|
||||||
|
if (rc < 0) return IoFailure;
|
||||||
|
|
||||||
|
// Leave whatever the filesystem cannot use unpartitioned, with the end
|
||||||
|
// on a 1 MiB boundary like the partitions mk-rpi4-sd.sh makes.
|
||||||
|
uint64_t wanted = (part->StartLba + (gs.targetBytes + 511) / 512 + 2047) / 2048 * 2048
|
||||||
|
- part->StartLba;
|
||||||
|
if (wanted > maxSectors) wanted = maxSectors;
|
||||||
|
|
||||||
|
if (apply && wanted > part->SectorCount) {
|
||||||
|
if (Drivers::Storage::Gpt::ResizeMbrPartition(partIndex, wanted) != 0) return IoFailure;
|
||||||
|
}
|
||||||
|
|
||||||
|
rc = apply ? Fs::Vfs::VfsGrow(drive, part->SectorCount, true, &gs) : 0;
|
||||||
|
if (rc == -2) return UnsupportedOp;
|
||||||
|
if (rc < 0) return IoFailure;
|
||||||
|
|
||||||
|
out->fsBytes = gs.fsBytes;
|
||||||
|
out->partBytes = part->SectorCount * 512;
|
||||||
|
out->targetBytes = gs.targetBytes;
|
||||||
|
out->stepsDone = gs.stepsDone;
|
||||||
|
out->stepsTotal = gs.stepsTotal;
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
// Format a partition with a filesystem. Returns 0 on success, GenericFailure on error.
|
// Format a partition with a filesystem. Returns 0 on success, GenericFailure on error.
|
||||||
static int64_t Sys_FsFormat(const FsFormatParams* params) {
|
static int64_t Sys_FsFormat(const FsFormatParams* params) {
|
||||||
if (params == nullptr) return InvalidArgument;
|
if (params == nullptr) return InvalidArgument;
|
||||||
|
|||||||
+83
-32
@@ -6,8 +6,6 @@
|
|||||||
|
|
||||||
#include "Syscall.hpp"
|
#include "Syscall.hpp"
|
||||||
#include <Terminal/Terminal.hpp>
|
#include <Terminal/Terminal.hpp>
|
||||||
#include <Hal/MSR.hpp>
|
|
||||||
#include <Hal/GDT.hpp>
|
|
||||||
|
|
||||||
/* For common functions used by multiple syscall implementations*/
|
/* For common functions used by multiple syscall implementations*/
|
||||||
#include "Common.hpp"
|
#include "Common.hpp"
|
||||||
@@ -18,7 +16,7 @@
|
|||||||
#include "Terminal.hpp" // SYS_PRINT, SYS_PUTCHAR
|
#include "Terminal.hpp" // SYS_PRINT, SYS_PUTCHAR
|
||||||
#include "Filesystem.hpp" // SYS_OPEN, SYS_READ, SYS_GETSIZE, SYS_CLOSE, SYS_READDIR, SYS_FWRITE, SYS_FCREATE
|
#include "Filesystem.hpp" // SYS_OPEN, SYS_READ, SYS_GETSIZE, SYS_CLOSE, SYS_READDIR, SYS_FWRITE, SYS_FCREATE
|
||||||
#include "Heap.hpp" // SYS_ALLOC, SYS_FREE
|
#include "Heap.hpp" // SYS_ALLOC, SYS_FREE
|
||||||
#include "Time.hpp" // SYS_GETTICKS, SYS_GETMILLISECONDS, SYS_GETTIME
|
#include "Time.hpp" // SYS_GETTICKS, SYS_GETMILLISECONDS, SYS_GETTIME, SYS_GETUNIXTIME
|
||||||
#include "Keyboard.hpp" // SYS_ISKEYAVAILABLE, SYS_GETKEY, SYS_GETCHAR
|
#include "Keyboard.hpp" // SYS_ISKEYAVAILABLE, SYS_GETKEY, SYS_GETCHAR
|
||||||
#include "Info.hpp" // SYS_GETINFO
|
#include "Info.hpp" // SYS_GETINFO
|
||||||
#include "Graphics.hpp" // SYS_FBINFO, SYS_FBMAP, SYS_TERMSIZE, SYS_TERMSCALE
|
#include "Graphics.hpp" // SYS_FBINFO, SYS_FBMAP, SYS_TERMSIZE, SYS_TERMSCALE
|
||||||
@@ -31,7 +29,7 @@
|
|||||||
#include "Device.hpp" // SYS_DEVLIST, SYS_DISKINFO
|
#include "Device.hpp" // SYS_DEVLIST, SYS_DISKINFO
|
||||||
#include "Input.hpp" // SYS_INPUT_WAIT
|
#include "Input.hpp" // SYS_INPUT_WAIT
|
||||||
#include "Storage.hpp" // SYS_PARTLIST, SYS_VOLINFO, SYS_DISKREAD, SYS_DISKWRITE
|
#include "Storage.hpp" // SYS_PARTLIST, SYS_VOLINFO, SYS_DISKREAD, SYS_DISKWRITE
|
||||||
#include "Window.hpp" // SYS_WINCREATE, SYS_WINDESTROY, SYS_WINPRESENT, SYS_WINPOLL, SYS_WINENUM, SYS_WINMAP, SYS_WINSENDEVENT, SYS_WINRESIZE, SYS_WINSETCURSOR, SYS_WINSETFLAGS, SYS_WINSETSCALE, SYS_WINGETSCALE
|
#include "Window.hpp" // SYS_WIN_REQUEST*, SYS_WIN_DRAG_*, SYS_WINCREATE, SYS_WINDESTROY, SYS_WINPRESENT, SYS_WINPOLL, SYS_WINENUM, SYS_WINMAP, SYS_WINSENDEVENT, SYS_WINRESIZE, SYS_WINSETCURSOR, SYS_WINSETFLAGS, SYS_WINSETSCALE, SYS_WINGETSCALE
|
||||||
#include "Audio.hpp" // SYS_AUDIOOPEN, SYS_AUDIOCLOSE, SYS_AUDIOWRITE, SYS_AUDIOCTL
|
#include "Audio.hpp" // SYS_AUDIOOPEN, SYS_AUDIOCLOSE, SYS_AUDIOWRITE, SYS_AUDIOCTL
|
||||||
#include "BluetoothSyscall.hpp" // SYS_BTSCAN, SYS_BTCONNECT, SYS_BTDISCONNECT, SYS_BTLIST, SYS_BTINFO
|
#include "BluetoothSyscall.hpp" // SYS_BTSCAN, SYS_BTCONNECT, SYS_BTDISCONNECT, SYS_BTLIST, SYS_BTINFO
|
||||||
#include "Usb.hpp" // generic process-owned USB interface access
|
#include "Usb.hpp" // generic process-owned USB interface access
|
||||||
@@ -39,12 +37,9 @@
|
|||||||
#include "IpcSyscall.hpp" // SYS_DUPHANDLE, SYS_WAIT_HANDLE, SYS_STREAM_CREATE, SYS_STREAM_READ, SYS_STREAM_WRITE, SYS_MAILBOX_CREATE, SYS_MAILBOX_SEND, SYS_MAILBOX_RECV, SYS_WAITSET_CREATE, SYS_WAITSET_ADD, SYS_WAITSET_REMOVE, SYS_WAITSET_WAIT, SYS_PROC_OPEN, SYS_SURFACE_CREATE, SYS_SURFACE_MAP, SYS_SURFACE_RESIZE
|
#include "IpcSyscall.hpp" // SYS_DUPHANDLE, SYS_WAIT_HANDLE, SYS_STREAM_CREATE, SYS_STREAM_READ, SYS_STREAM_WRITE, SYS_MAILBOX_CREATE, SYS_MAILBOX_SEND, SYS_MAILBOX_RECV, SYS_WAITSET_CREATE, SYS_WAITSET_ADD, SYS_WAITSET_REMOVE, SYS_WAITSET_WAIT, SYS_PROC_OPEN, SYS_SURFACE_CREATE, SYS_SURFACE_MAP, SYS_SURFACE_RESIZE
|
||||||
#include "LibSyscall.hpp" // SYS_LOAD_LIB, SYS_UNLOAD_LIB, SYS_DLSYM
|
#include "LibSyscall.hpp" // SYS_LOAD_LIB, SYS_UNLOAD_LIB, SYS_DLSYM
|
||||||
#include "CrashReportSyscall.hpp" // SYS_CRASH_REPORT
|
#include "CrashReportSyscall.hpp" // SYS_CRASH_REPORT
|
||||||
#include "Clipboard.hpp" // SYS_CLIPBOARD_*
|
#include "Clipboard.hpp" // SYS_CLIPBOARD_* (text and multi-format)
|
||||||
#include "UserMemory.hpp"
|
#include "UserMemory.hpp"
|
||||||
|
|
||||||
// Assembly entry point
|
|
||||||
extern "C" void SyscallEntry();
|
|
||||||
|
|
||||||
namespace montauk::abi {
|
namespace montauk::abi {
|
||||||
|
|
||||||
static constexpr uint64_t kMaxPrintableStringBytes = 4096;
|
static constexpr uint64_t kMaxPrintableStringBytes = 4096;
|
||||||
@@ -139,8 +134,8 @@ namespace montauk::abi {
|
|||||||
case SYS_ISKEYAVAILABLE:
|
case SYS_ISKEYAVAILABLE:
|
||||||
return (int64_t)Sys_IsKeyAvailable();
|
return (int64_t)Sys_IsKeyAvailable();
|
||||||
case SYS_GETKEY:
|
case SYS_GETKEY:
|
||||||
if (!UserMemory::Writable<KeyEvent>(frame->arg1)) return BadPointer;
|
if (!UserMemory::Writable<KeyEventV1>(frame->arg1)) return BadPointer;
|
||||||
Sys_GetKey((KeyEvent*)frame->arg1);
|
Sys_GetKey((KeyEventV1*)frame->arg1);
|
||||||
return Success;
|
return Success;
|
||||||
case SYS_GETCHAR:
|
case SYS_GETCHAR:
|
||||||
return (int64_t)Sys_GetChar();
|
return (int64_t)Sys_GetChar();
|
||||||
@@ -250,6 +245,8 @@ namespace montauk::abi {
|
|||||||
if (!UserMemory::Writable<DateTime>(frame->arg1)) return BadPointer;
|
if (!UserMemory::Writable<DateTime>(frame->arg1)) return BadPointer;
|
||||||
Sys_GetTime((DateTime*)frame->arg1);
|
Sys_GetTime((DateTime*)frame->arg1);
|
||||||
return Success;
|
return Success;
|
||||||
|
case SYS_GETUNIXTIME:
|
||||||
|
return Sys_GetUnixTime();
|
||||||
case SYS_SETUNIXTIME:
|
case SYS_SETUNIXTIME:
|
||||||
if (!Sched::Consent::Require(CAP_SET_TIME)) return CapabilityDenied;
|
if (!Sched::Consent::Require(CAP_SET_TIME)) return CapabilityDenied;
|
||||||
return Sys_SetUnixTime((int64_t)frame->arg1);
|
return Sys_SetUnixTime((int64_t)frame->arg1);
|
||||||
@@ -365,8 +362,8 @@ namespace montauk::abi {
|
|||||||
if (!UserMemory::Range(frame->arg2, (uint64_t)frame->arg3, false)) return BadPointer;
|
if (!UserMemory::Range(frame->arg2, (uint64_t)frame->arg3, false)) return BadPointer;
|
||||||
return (int64_t)Sys_ChildIoWrite((int)frame->arg1, (const char*)frame->arg2, (int)frame->arg3);
|
return (int64_t)Sys_ChildIoWrite((int)frame->arg1, (const char*)frame->arg2, (int)frame->arg3);
|
||||||
case SYS_CHILDIO_WRITEKEY:
|
case SYS_CHILDIO_WRITEKEY:
|
||||||
if (!UserMemory::Readable<KeyEvent>(frame->arg2)) return BadPointer;
|
if (!UserMemory::Readable<KeyEventV1>(frame->arg2)) return BadPointer;
|
||||||
return (int64_t)Sys_ChildIoWriteKey((int)frame->arg1, (const KeyEvent*)frame->arg2);
|
return (int64_t)Sys_ChildIoWriteKey((int)frame->arg1, (const KeyEventV1*)frame->arg2);
|
||||||
case SYS_CHILDIO_SETTERMSZ:
|
case SYS_CHILDIO_SETTERMSZ:
|
||||||
return (int64_t)Sys_ChildIoSetTermsz((int)frame->arg1, (int)frame->arg2, (int)frame->arg3);
|
return (int64_t)Sys_ChildIoSetTermsz((int)frame->arg1, (int)frame->arg2, (int)frame->arg3);
|
||||||
case SYS_WINCREATE:
|
case SYS_WINCREATE:
|
||||||
@@ -400,6 +397,12 @@ namespace montauk::abi {
|
|||||||
return (int64_t)Sys_ProcList((ProcInfo*)frame->arg1, (int)frame->arg2);
|
return (int64_t)Sys_ProcList((ProcInfo*)frame->arg1, (int)frame->arg2);
|
||||||
case SYS_KILL:
|
case SYS_KILL:
|
||||||
return (int64_t)Sys_Kill((int)frame->arg1);
|
return (int64_t)Sys_Kill((int)frame->arg1);
|
||||||
|
case SYS_ABORT:
|
||||||
|
Sys_Abort();
|
||||||
|
return Success;
|
||||||
|
case SYS_EXIT_GROUP:
|
||||||
|
Sys_ExitGroup((int)frame->arg1);
|
||||||
|
return Success;
|
||||||
case SYS_SETSESSION:
|
case SYS_SETSESSION:
|
||||||
return (int64_t)Sys_SetSession();
|
return (int64_t)Sys_SetSession();
|
||||||
case SYS_KILLSESSION:
|
case SYS_KILLSESSION:
|
||||||
@@ -453,6 +456,10 @@ namespace montauk::abi {
|
|||||||
case SYS_FS_SYNC:
|
case SYS_FS_SYNC:
|
||||||
if (!Sched::Consent::Require(CAP_STORAGE_ADMIN)) return CapabilityDenied;
|
if (!Sched::Consent::Require(CAP_STORAGE_ADMIN)) return CapabilityDenied;
|
||||||
return Sys_FsSync();
|
return Sys_FsSync();
|
||||||
|
case SYS_FS_GROW:
|
||||||
|
if (!Sched::Consent::Require(CAP_STORAGE_ADMIN)) return CapabilityDenied;
|
||||||
|
if (!UserMemory::Range(frame->arg2, sizeof(FsGrowStatus), true)) return BadPointer;
|
||||||
|
return Sys_FsGrow((int)frame->arg1, (FsGrowStatus*)frame->arg2, frame->arg3 != 0);
|
||||||
case SYS_FSFORMAT:
|
case SYS_FSFORMAT:
|
||||||
if (!Sched::Consent::Require(CAP_STORAGE_ADMIN)) return CapabilityDenied;
|
if (!Sched::Consent::Require(CAP_STORAGE_ADMIN)) return CapabilityDenied;
|
||||||
if (!UserMemory::Readable<FsFormatParams>(frame->arg1)) return BadPointer;
|
if (!UserMemory::Readable<FsFormatParams>(frame->arg1)) return BadPointer;
|
||||||
@@ -527,6 +534,14 @@ namespace montauk::abi {
|
|||||||
return Sched::Consent::Next((ConsentRequest*)frame->arg1);
|
return Sched::Consent::Next((ConsentRequest*)frame->arg1);
|
||||||
case SYS_CONSENT_REPLY:
|
case SYS_CONSENT_REPLY:
|
||||||
return Sched::Consent::Reply(frame->arg1, (int)frame->arg2);
|
return Sched::Consent::Reply(frame->arg1, (int)frame->arg2);
|
||||||
|
case SYS_GETKEY_EX:
|
||||||
|
if (frame->arg2 < sizeof(KeyEventV1) || frame->arg2 > 4096) return InvalidArgument;
|
||||||
|
if (!UserMemory::Range(frame->arg1, frame->arg2, true)) return BadPointer;
|
||||||
|
return Sys_GetKeyEx((void*)frame->arg1, frame->arg2);
|
||||||
|
case SYS_CHILDIO_WRITEKEY_EX:
|
||||||
|
if (frame->arg3 < sizeof(KeyEventV1) || frame->arg3 > 4096) return InvalidArgument;
|
||||||
|
if (!UserMemory::Range(frame->arg2, frame->arg3, false)) return BadPointer;
|
||||||
|
return (int64_t)Sys_ChildIoWriteKeyEx((int)frame->arg1, (const void*)frame->arg2, frame->arg3);
|
||||||
case SYS_THREAD_SPAWN:
|
case SYS_THREAD_SPAWN:
|
||||||
return Sys_ThreadSpawn(frame->arg1, frame->arg2, frame->arg3);
|
return Sys_ThreadSpawn(frame->arg1, frame->arg2, frame->arg3);
|
||||||
case SYS_THREAD_EXIT:
|
case SYS_THREAD_EXIT:
|
||||||
@@ -537,6 +552,13 @@ namespace montauk::abi {
|
|||||||
return Sys_ThreadJoin((int)frame->arg1, (int*)frame->arg2);
|
return Sys_ThreadJoin((int)frame->arg1, (int*)frame->arg2);
|
||||||
case SYS_THREAD_SELF:
|
case SYS_THREAD_SELF:
|
||||||
return Sys_ThreadSelf();
|
return Sys_ThreadSelf();
|
||||||
|
case SYS_FUTEX_WAIT:
|
||||||
|
// Validation demand-commits the page so the scheduler can
|
||||||
|
// read the word with its lock held without faulting.
|
||||||
|
if (!UserMemory::Readable<uint32_t>(frame->arg1)) return BadPointer;
|
||||||
|
return Sys_FutexWait(frame->arg1, (uint32_t)frame->arg2, frame->arg3);
|
||||||
|
case SYS_FUTEX_WAKE:
|
||||||
|
return Sys_FutexWake(frame->arg1, (uint32_t)frame->arg2);
|
||||||
case SYS_BTSCAN:
|
case SYS_BTSCAN:
|
||||||
if (!Sched::HasCapability(CAP_DEVICE_ADMIN)) return CapabilityDenied;
|
if (!Sched::HasCapability(CAP_DEVICE_ADMIN)) return CapabilityDenied;
|
||||||
if ((int64_t)frame->arg2 < 0) return InvalidArgument;
|
if ((int64_t)frame->arg2 < 0) return InvalidArgument;
|
||||||
@@ -705,6 +727,51 @@ namespace montauk::abi {
|
|||||||
UserMemory::IsUserPtr(frame->arg4) ? (uint64_t*)frame->arg4 : nullptr);
|
UserMemory::IsUserPtr(frame->arg4) ? (uint64_t*)frame->arg4 : nullptr);
|
||||||
case SYS_CLIPBOARD_CLEAR:
|
case SYS_CLIPBOARD_CLEAR:
|
||||||
return Sys_ClipboardClear();
|
return Sys_ClipboardClear();
|
||||||
|
case SYS_CLIPBOARD_SET_DATA:
|
||||||
|
if (frame->arg2 > TRANSFER_MAX_FORMATS) return InvalidArgument;
|
||||||
|
if (!UserMemory::Range(frame->arg1, frame->arg2 * sizeof(TransferItem), false)) return BadPointer;
|
||||||
|
return Sys_ClipboardSetData((const TransferItem*)frame->arg1, (uint32_t)frame->arg2);
|
||||||
|
case SYS_CLIPBOARD_FORMATS: {
|
||||||
|
uint64_t max = frame->arg2 > TRANSFER_MAX_FORMATS ? TRANSFER_MAX_FORMATS : frame->arg2;
|
||||||
|
if (!UserMemory::OptionalRange(frame->arg1, max * sizeof(TransferFormatInfo), true)) return BadPointer;
|
||||||
|
if (!UserMemory::OptionalRange(frame->arg3, sizeof(uint64_t), true)) return BadPointer;
|
||||||
|
return Sys_ClipboardFormats(frame->arg1 ? (TransferFormatInfo*)frame->arg1 : nullptr,
|
||||||
|
(uint32_t)max,
|
||||||
|
frame->arg3 ? (uint64_t*)frame->arg3 : nullptr);
|
||||||
|
}
|
||||||
|
case SYS_CLIPBOARD_GET_DATA:
|
||||||
|
if (!UserMemory::String(frame->arg1, TRANSFER_MIME_MAX)) return BadPointer;
|
||||||
|
if (frame->arg3 > 0 && !UserMemory::Range(frame->arg2, frame->arg3, true)) return BadPointer;
|
||||||
|
if (!UserMemory::Writable<uint32_t>(frame->arg4)) return BadPointer;
|
||||||
|
return Sys_ClipboardGetData((const char*)frame->arg1,
|
||||||
|
frame->arg3 > 0 ? (void*)frame->arg2 : nullptr,
|
||||||
|
(uint32_t)frame->arg3, (uint32_t*)frame->arg4);
|
||||||
|
case SYS_WIN_REQUEST:
|
||||||
|
if (!UserMemory::Readable<WinRequest>(frame->arg2)) return BadPointer;
|
||||||
|
return Sys_WinRequest((int)frame->arg1, (const WinRequest*)frame->arg2);
|
||||||
|
case SYS_WIN_REQUEST_POLL:
|
||||||
|
if (!UserMemory::Writable<WinRequest>(frame->arg1)) return BadPointer;
|
||||||
|
return Sys_WinRequestPoll((WinRequest*)frame->arg1);
|
||||||
|
case SYS_WIN_DRAG_BEGIN:
|
||||||
|
if (!UserMemory::Readable<WinDragBegin>(frame->arg2)) return BadPointer;
|
||||||
|
if (frame->arg4 == 0 || frame->arg4 > TRANSFER_MAX_FORMATS) return InvalidArgument;
|
||||||
|
if (!UserMemory::Range(frame->arg3, frame->arg4 * sizeof(TransferItem), false)) return BadPointer;
|
||||||
|
return Sys_WinDragBegin((int)frame->arg1, (const WinDragBegin*)frame->arg2,
|
||||||
|
(const TransferItem*)frame->arg3, (uint32_t)frame->arg4);
|
||||||
|
case SYS_WIN_DRAG_FORMATS: {
|
||||||
|
uint64_t max = frame->arg3 > TRANSFER_MAX_FORMATS ? TRANSFER_MAX_FORMATS : frame->arg3;
|
||||||
|
if (!UserMemory::OptionalRange(frame->arg2, max * sizeof(TransferFormatInfo), true)) return BadPointer;
|
||||||
|
return Sys_WinDragFormats((uint32_t)frame->arg1,
|
||||||
|
frame->arg2 ? (TransferFormatInfo*)frame->arg2 : nullptr,
|
||||||
|
(uint32_t)max);
|
||||||
|
}
|
||||||
|
case SYS_WIN_DRAG_GET_DATA:
|
||||||
|
if (!UserMemory::String(frame->arg2, TRANSFER_MIME_MAX)) return BadPointer;
|
||||||
|
if (frame->arg4 > 0 && !UserMemory::Range(frame->arg3, frame->arg4, true)) return BadPointer;
|
||||||
|
if (!UserMemory::Writable<uint32_t>(frame->arg5)) return BadPointer;
|
||||||
|
return Sys_WinDragGetData((uint32_t)frame->arg1, (const char*)frame->arg2,
|
||||||
|
frame->arg4 > 0 ? (void*)frame->arg3 : nullptr,
|
||||||
|
(uint32_t)frame->arg4, (uint32_t*)frame->arg5);
|
||||||
case SYS_INPUT_WAIT:
|
case SYS_INPUT_WAIT:
|
||||||
return (int64_t)Sys_InputWait(frame->arg1, frame->arg2);
|
return (int64_t)Sys_InputWait(frame->arg1, frame->arg2);
|
||||||
case SYS_LOG_WRITE: {
|
case SYS_LOG_WRITE: {
|
||||||
@@ -730,29 +797,13 @@ namespace montauk::abi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---- SYSCALL MSR initialization ----
|
// ---- Entry point initialization ----
|
||||||
|
|
||||||
void InitializeSyscalls() {
|
void InitializeSyscalls() {
|
||||||
// Enable SYSCALL/SYSRET and no-execute page permissions in EFER.
|
Arch::InitializeSyscallEntry();
|
||||||
uint64_t efer = Hal::ReadMSR(Hal::IA32_EFER);
|
|
||||||
efer |= 1 | (1ULL << 11); // SCE | NXE
|
|
||||||
Hal::WriteMSR(Hal::IA32_EFER, efer);
|
|
||||||
|
|
||||||
// STAR: kernel CS in [47:32], sysret base in [63:48]
|
Kt::KernelLogStream(Kt::OK, "Syscall") << "System call entry installed ("
|
||||||
// SYSCALL: CS=0x08, SS=0x10
|
<< kcp::dec << (SYS_SPAWN_REDIR_CAPS + 1) << " syscall slots)";
|
||||||
// SYSRET: CS=0x10+16=0x20|RPL3=0x23, SS=0x10+8=0x18|RPL3=0x1B
|
|
||||||
uint64_t star = (0x0010ULL << 48) | (0x0008ULL << 32);
|
|
||||||
Hal::WriteMSR(Hal::IA32_STAR, star);
|
|
||||||
|
|
||||||
// LSTAR: SYSCALL entry point
|
|
||||||
Hal::WriteMSR(Hal::IA32_LSTAR, (uint64_t)SyscallEntry);
|
|
||||||
|
|
||||||
// FMASK: mask IF on SYSCALL entry (bit 9 = IF)
|
|
||||||
Hal::WriteMSR(Hal::IA32_FMASK, 0x200);
|
|
||||||
|
|
||||||
Kt::KernelLogStream(Kt::OK, "Syscall") << "SYSCALL/SYSRET initialized (LSTAR="
|
|
||||||
<< kcp::hex << (uint64_t)SyscallEntry << kcp::dec << ", "
|
|
||||||
<< (SYS_SPAWN_REDIR_CAPS + 1) << " syscall slots)";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
+245
-13
@@ -8,6 +8,7 @@
|
|||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include <Common/StatusCodes.hpp>
|
#include <Common/StatusCodes.hpp>
|
||||||
|
#include <Arch/Syscall.hpp>
|
||||||
|
|
||||||
namespace montauk::abi {
|
namespace montauk::abi {
|
||||||
|
|
||||||
@@ -358,6 +359,55 @@ namespace montauk::abi {
|
|||||||
static constexpr uint64_t SYS_CONSENT_NEXT = 192; // (ConsentRequest*) -> 1 delivered, 0 none queued
|
static constexpr uint64_t SYS_CONSENT_NEXT = 192; // (ConsentRequest*) -> 1 delivered, 0 none queued
|
||||||
static constexpr uint64_t SYS_CONSENT_REPLY = 193; // (requestId, CONSENT_ALLOW/DENY) -> 0, -1 stale id
|
static constexpr uint64_t SYS_CONSENT_REPLY = 193; // (requestId, CONSENT_ALLOW/DENY) -> 0, -1 stale id
|
||||||
|
|
||||||
|
/* Unicode key events. Size-versioned: the caller passes sizeof(its
|
||||||
|
KeyEvent); the kernel copies min(size, its own) bytes and zero-fills the
|
||||||
|
rest, so the struct can grow without another syscall. */
|
||||||
|
static constexpr uint64_t SYS_GETKEY_EX = 194; // (KeyEvent*, size) -> 0; blocks like SYS_GETKEY
|
||||||
|
static constexpr uint64_t SYS_CHILDIO_WRITEKEY_EX = 195; // (childPid, const KeyEvent*, size) -> status
|
||||||
|
|
||||||
|
/* Futexes: process-private wait queues keyed by a user address. WAIT
|
||||||
|
blocks only if *addr still equals `expected` (checked atomically with
|
||||||
|
enrollment). timeoutNs: FUTEX_WAIT_FOREVER = no deadline, 0 = check
|
||||||
|
only; rounded up to the 1 ms scheduler tick. WAIT returns 0 when woken,
|
||||||
|
WouldBlock on value mismatch, TimedOut on deadline. WAKE returns the
|
||||||
|
number of threads woken (count = FUTEX_WAKE_ALL wakes every waiter). */
|
||||||
|
static constexpr uint64_t SYS_FUTEX_WAIT = 196; // (uint32_t* addr, expected, timeoutNs) -> status
|
||||||
|
static constexpr uint64_t SYS_FUTEX_WAKE = 197; // (uint32_t* addr, count) -> woken, or status
|
||||||
|
static constexpr uint64_t FUTEX_WAIT_FOREVER = ~0ULL;
|
||||||
|
static constexpr uint32_t FUTEX_WAKE_ALL = 0xFFFFFFFFu;
|
||||||
|
|
||||||
|
/* Abnormal termination from any thread: ends the whole process (every
|
||||||
|
thread) with exit code TerminatedAborted (256+SIGABRT). SYS_EXIT from a
|
||||||
|
sibling thread ends only that thread, so abort() needs its own call. */
|
||||||
|
static constexpr uint64_t SYS_ABORT = 198; // () -> does not return
|
||||||
|
|
||||||
|
/* Normal termination of the whole process from any thread, with an exit
|
||||||
|
code (0..255) instead of SYS_ABORT's fixed TerminatedAborted. libc
|
||||||
|
exit() and Rust std::process::exit() use it, so exiting from a sibling
|
||||||
|
thread ends the process rather than only that thread. */
|
||||||
|
static constexpr uint64_t SYS_EXIT_GROUP = 199; // (exitCode) -> does not return
|
||||||
|
|
||||||
|
/* Multi-format clipboard (see "Window server messaging" below). The text
|
||||||
|
calls above operate on its TRANSFER_MIME_TEXT format. */
|
||||||
|
static constexpr uint64_t SYS_CLIPBOARD_SET_DATA = 200; // (const TransferItem*, count) -> status
|
||||||
|
static constexpr uint64_t SYS_CLIPBOARD_FORMATS = 201; // (TransferFormatInfo*, max, uint64_t* serial) -> count
|
||||||
|
static constexpr uint64_t SYS_CLIPBOARD_GET_DATA = 202; // (mime, buf, max, uint32_t* len) -> status
|
||||||
|
|
||||||
|
/* Window server requests and drag and drop. */
|
||||||
|
static constexpr uint64_t SYS_WIN_REQUEST = 203; // (windowId, const WinRequest*) -> status
|
||||||
|
static constexpr uint64_t SYS_WIN_REQUEST_POLL = 204; // (WinRequest*) -> 1 / 0; compositor only
|
||||||
|
static constexpr uint64_t SYS_WIN_DRAG_BEGIN = 205; // (windowId or -1 for the compositor's own surface, const WinDragBegin*, const TransferItem*, count) -> serial
|
||||||
|
static constexpr uint64_t SYS_WIN_DRAG_FORMATS = 206; // (serial, TransferFormatInfo*, max) -> count
|
||||||
|
static constexpr uint64_t SYS_WIN_DRAG_GET_DATA = 207; // (serial, mime, buf, max, uint32_t* len) -> status
|
||||||
|
|
||||||
|
/* Storage.hpp -- grow a mounted volume (and its MBR partition) to fill the
|
||||||
|
free space after it. apply=0 only reports; apply=1 does one bounded slice
|
||||||
|
and returns 1 while more remains, 0 once done. CAP_STORAGE_ADMIN. */
|
||||||
|
static constexpr uint64_t SYS_FS_GROW = 208; // (drive, FsGrowStatus*, apply) -> 1 / 0 / status
|
||||||
|
|
||||||
|
/* Time.hpp -- wall clock as UTC unix seconds (SYS_GETTIME is local time). */
|
||||||
|
static constexpr uint64_t SYS_GETUNIXTIME = 209; // () -> seconds
|
||||||
|
|
||||||
/* Kernel-owned process capabilities. User identities may namespace
|
/* Kernel-owned process capabilities. User identities may namespace
|
||||||
per-user resources, but never participate in authorization decisions. */
|
per-user resources, but never participate in authorization decisions. */
|
||||||
static constexpr uint64_t CAP_PROCESS_ADMIN = 1ULL << 0;
|
static constexpr uint64_t CAP_PROCESS_ADMIN = 1ULL << 0;
|
||||||
@@ -600,7 +650,10 @@ namespace montauk::abi {
|
|||||||
uint64_t txPackets;
|
uint64_t txPackets;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct KeyEvent {
|
// Legacy 9-byte key event, read by SYS_GETKEY and written by
|
||||||
|
// SYS_CHILDIO_WRITEKEY. Kept so binaries built before codepoints still work.
|
||||||
|
// `ascii` is a Windows-1252 byte.
|
||||||
|
struct KeyEventV1 {
|
||||||
uint8_t scancode;
|
uint8_t scancode;
|
||||||
char ascii;
|
char ascii;
|
||||||
bool pressed;
|
bool pressed;
|
||||||
@@ -616,6 +669,32 @@ namespace montauk::abi {
|
|||||||
bool extended;
|
bool extended;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Key event with the Unicode text it types (SYS_GETKEY_EX,
|
||||||
|
// SYS_CHILDIO_WRITEKEY_EX, WinEvent). Its first 9 bytes ARE a KeyEventV1, so
|
||||||
|
// a legacy reader sees a valid event. `codepoint` is the character the key
|
||||||
|
// produces (0 for control/navigation keys and dead keys). `ascii` is its
|
||||||
|
// Windows-1252 byte, or KEY_ASCII_SUBSTITUTE when 1252 has none.
|
||||||
|
struct KeyEvent {
|
||||||
|
uint8_t scancode;
|
||||||
|
char ascii;
|
||||||
|
bool pressed;
|
||||||
|
bool shift;
|
||||||
|
bool ctrl;
|
||||||
|
bool alt;
|
||||||
|
bool capslock;
|
||||||
|
bool altgr;
|
||||||
|
bool extended;
|
||||||
|
uint8_t _reserved[3];
|
||||||
|
uint32_t codepoint;
|
||||||
|
};
|
||||||
|
static_assert(sizeof(KeyEventV1) == 9, "KeyEventV1 is frozen ABI");
|
||||||
|
static_assert(sizeof(KeyEvent) == 16, "KeyEvent must fit the WinEvent union");
|
||||||
|
|
||||||
|
// ASCII SUB: `ascii` of a text key whose character has no Windows-1252 byte.
|
||||||
|
// A control character, so legacy code that only accepts ascii >= 0x20 skips
|
||||||
|
// it, while code testing `ascii == 0` for navigation keys is not confused.
|
||||||
|
static constexpr char KEY_ASCII_SUBSTITUTE = 0x1A;
|
||||||
|
|
||||||
struct MouseState {
|
struct MouseState {
|
||||||
int32_t x;
|
int32_t x;
|
||||||
int32_t y;
|
int32_t y;
|
||||||
@@ -649,16 +728,55 @@ namespace montauk::abi {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Window server shared types
|
// Window server shared types
|
||||||
|
enum WinEventType : uint8_t {
|
||||||
|
WIN_EVENT_KEY = 0,
|
||||||
|
WIN_EVENT_MOUSE = 1,
|
||||||
|
WIN_EVENT_RESIZE = 2,
|
||||||
|
WIN_EVENT_CLOSE = 3,
|
||||||
|
WIN_EVENT_SCALE = 4,
|
||||||
|
// Drag and drop (WinEvent::drag). A window may read the drag's data
|
||||||
|
// (SYS_WIN_DRAG_*) once it has received one of these for that serial.
|
||||||
|
WIN_EVENT_DRAG_ENTER = 5, // pointer entered the window during a drag
|
||||||
|
WIN_EVENT_DRAG_OVER = 6, // pointer moved; coalesced like mouse moves
|
||||||
|
WIN_EVENT_DRAG_LEAVE = 7, // pointer left, or the drag was cancelled
|
||||||
|
WIN_EVENT_DROP = 8, // released here; drag.actions = chosen action
|
||||||
|
WIN_EVENT_DRAG_END = 9, // to the source only; drag.actions = action
|
||||||
|
// taken (0 = cancelled / not accepted)
|
||||||
|
};
|
||||||
|
|
||||||
|
// Modifier keys held at the time of a drag event (WinEvent::drag.modifiers).
|
||||||
|
static constexpr uint8_t WIN_MOD_SHIFT = 1u << 0;
|
||||||
|
static constexpr uint8_t WIN_MOD_CTRL = 1u << 1;
|
||||||
|
static constexpr uint8_t WIN_MOD_ALT = 1u << 2;
|
||||||
|
|
||||||
|
// Drag actions; a source offers a set, a drop carries exactly one.
|
||||||
|
static constexpr uint8_t WIN_DRAG_COPY = 1u << 0;
|
||||||
|
static constexpr uint8_t WIN_DRAG_MOVE = 1u << 1;
|
||||||
|
static constexpr uint8_t WIN_DRAG_LINK = 1u << 2;
|
||||||
|
|
||||||
|
struct WinDragEvent {
|
||||||
|
int32_t x, y; // window-local; -1,-1 when not over the window
|
||||||
|
uint32_t serial; // identifies the drag
|
||||||
|
uint8_t actions; // WIN_DRAG_* (see WinEventType)
|
||||||
|
uint8_t modifiers; // WIN_MOD_*
|
||||||
|
uint8_t _pad[2];
|
||||||
|
};
|
||||||
|
static_assert(sizeof(WinDragEvent) == 16, "WinDragEvent must fit the WinEvent union");
|
||||||
|
|
||||||
struct WinEvent {
|
struct WinEvent {
|
||||||
uint8_t type; // 0=key, 1=mouse, 2=resize, 3=close, 4=scale
|
uint8_t type; // WinEventType
|
||||||
uint8_t _pad[3];
|
uint8_t _pad[3];
|
||||||
union {
|
union {
|
||||||
KeyEvent key;
|
KeyEvent key; // 16 bytes: the union was already that size (mouse)
|
||||||
struct { int32_t x, y, scroll; uint8_t buttons, prev_buttons; } mouse;
|
// modifiers (WIN_MOD_*) was padding before; compositors that
|
||||||
|
// predate it send 0.
|
||||||
|
struct { int32_t x, y, scroll; uint8_t buttons, prev_buttons, modifiers; } mouse;
|
||||||
struct { int32_t w, h; } resize;
|
struct { int32_t w, h; } resize;
|
||||||
struct { int32_t scale; } scale;
|
struct { int32_t scale; } scale;
|
||||||
|
WinDragEvent drag; // WIN_EVENT_DRAG_* / WIN_EVENT_DROP
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
static_assert(sizeof(WinEvent) == 20, "WinEvent layout is shared with existing binaries");
|
||||||
|
|
||||||
struct WinInfo {
|
struct WinInfo {
|
||||||
int32_t id;
|
int32_t id;
|
||||||
@@ -679,6 +797,116 @@ namespace montauk::abi {
|
|||||||
uint64_t pixelVa; // VA of pixel buffer in caller's address space
|
uint64_t pixelVa; // VA of pixel buffer in caller's address space
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// ---- Window server messaging ----------------------------------------
|
||||||
|
//
|
||||||
|
// Three pieces, shared by the compositor (the desktop) and every app:
|
||||||
|
// * WinEvent types 5-9 below: drag-and-drop events, compositor -> app.
|
||||||
|
// * WinRequest: typed records an app posts about one of its own windows
|
||||||
|
// (SYS_WIN_REQUEST); only the compositor can collect them
|
||||||
|
// (SYS_WIN_REQUEST_POLL). Unknown types are ignored, so new requests
|
||||||
|
// can be added without breaking older compositors or apps.
|
||||||
|
// * Transfers: multi-format payloads (one MIME type per format) carried
|
||||||
|
// by the clipboard (SYS_CLIPBOARD_*_DATA) and by drags
|
||||||
|
// (SYS_WIN_DRAG_*). The older text clipboard calls read and write the
|
||||||
|
// TRANSFER_MIME_TEXT format of the same clipboard.
|
||||||
|
|
||||||
|
// Transfers -----------------------------------------------------------
|
||||||
|
|
||||||
|
static constexpr uint32_t TRANSFER_MAX_FORMATS = 8;
|
||||||
|
static constexpr uint32_t TRANSFER_MIME_MAX = 64; // incl. NUL
|
||||||
|
static constexpr uint32_t TRANSFER_MAX_BYTES = 256 * 1024; // all formats together
|
||||||
|
|
||||||
|
// Well-known formats. Any other "type/subtype" string is allowed.
|
||||||
|
static constexpr const char* TRANSFER_MIME_TEXT = "text/plain;charset=utf-8";
|
||||||
|
// UTF-8 absolute paths ("0:/..."), one per line.
|
||||||
|
static constexpr const char* TRANSFER_MIME_FILE_LIST = "application/x-montauk-file-list";
|
||||||
|
// Clipboard companion to a file list: "copy" or "cut".
|
||||||
|
static constexpr const char* TRANSFER_MIME_FILE_OP = "application/x-montauk-file-op";
|
||||||
|
|
||||||
|
// One format to store. Pointers are carried as 64-bit values so the
|
||||||
|
// layout is the same for every caller.
|
||||||
|
struct TransferItem {
|
||||||
|
uint64_t mime; // const char*, NUL-terminated, < TRANSFER_MIME_MAX
|
||||||
|
uint64_t data; // const void*
|
||||||
|
uint32_t len;
|
||||||
|
uint32_t _reserved;
|
||||||
|
};
|
||||||
|
static_assert(sizeof(TransferItem) == 24, "TransferItem is ABI");
|
||||||
|
|
||||||
|
struct TransferFormatInfo {
|
||||||
|
char mime[TRANSFER_MIME_MAX];
|
||||||
|
uint32_t len;
|
||||||
|
uint32_t _reserved;
|
||||||
|
};
|
||||||
|
static_assert(sizeof(TransferFormatInfo) == 72, "TransferFormatInfo is ABI");
|
||||||
|
|
||||||
|
// Requests --------------------------------------------------------------
|
||||||
|
|
||||||
|
static constexpr uint32_t WIN_REQUEST_PAYLOAD_MAX = 240;
|
||||||
|
|
||||||
|
struct WinRequest {
|
||||||
|
uint16_t type; // WinRequestType
|
||||||
|
uint16_t size; // payload bytes in use
|
||||||
|
int32_t windowId; // window the request is about (the sender must own it
|
||||||
|
// when posting); -1 if it was destroyed since
|
||||||
|
int32_t senderPid; // filled in by the kernel
|
||||||
|
uint32_t serial; // filled in by the kernel; increases per request
|
||||||
|
uint8_t payload[WIN_REQUEST_PAYLOAD_MAX];
|
||||||
|
};
|
||||||
|
static_assert(sizeof(WinRequest) == 256, "WinRequest is ABI");
|
||||||
|
|
||||||
|
enum WinRequestType : uint16_t {
|
||||||
|
WIN_REQ_LAUNCH_ITEM = 1, // WinReqLaunchItem
|
||||||
|
WIN_REQ_SET_PARENT = 2, // WinReqSetParent
|
||||||
|
WIN_REQ_DRAG_STATUS = 3, // WinReqDragStatus
|
||||||
|
WIN_REQ_ACTIVATE = 4, // no payload: raise and focus the window
|
||||||
|
// Types from here on are generated by the kernel; SYS_WIN_REQUEST
|
||||||
|
// refuses them so an app cannot forge one.
|
||||||
|
WIN_REQ_KERNEL_BASE = 0x8000,
|
||||||
|
WIN_REQ_DRAG_BEGIN = 0x8001, // WinReqDragBegin (from SYS_WIN_DRAG_BEGIN)
|
||||||
|
};
|
||||||
|
|
||||||
|
// Open a desktop catalog item (an app manifest id, or a built-in such as
|
||||||
|
// "desktop-settings"), as if the user had picked it from the launcher, so
|
||||||
|
// it runs with the authority the desktop would give it. `args` reach only
|
||||||
|
// apps whose manifest declares files.types (they take a document path);
|
||||||
|
// anything else starts without them, so a request cannot steer a
|
||||||
|
// privileged app through its command line.
|
||||||
|
struct WinReqLaunchItem {
|
||||||
|
char itemId[48];
|
||||||
|
char args[192];
|
||||||
|
};
|
||||||
|
|
||||||
|
// Mark the window as a dialog of another window owned by the same
|
||||||
|
// process: the compositor centres it over the parent.
|
||||||
|
struct WinReqSetParent {
|
||||||
|
int32_t parentWindowId;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Answer to DRAG_ENTER/DRAG_OVER: the action this window would perform
|
||||||
|
// on a drop at the current position (0 = would refuse). Advisory; it
|
||||||
|
// drives the compositor's drag feedback.
|
||||||
|
struct WinReqDragStatus {
|
||||||
|
uint32_t serial;
|
||||||
|
uint8_t action; // one WIN_DRAG_* or 0
|
||||||
|
uint8_t _pad[3];
|
||||||
|
};
|
||||||
|
|
||||||
|
// Describes a drag for the compositor's feedback. Supplied by the app to
|
||||||
|
// SYS_WIN_DRAG_BEGIN and relayed (with the serial) as WIN_REQ_DRAG_BEGIN.
|
||||||
|
struct WinDragBegin {
|
||||||
|
uint8_t actions; // WIN_DRAG_* offered
|
||||||
|
uint8_t _pad[3];
|
||||||
|
uint32_t itemCount; // for a "3 items" badge; 0 = do not show
|
||||||
|
char label[64]; // short description, e.g. the file name
|
||||||
|
};
|
||||||
|
static_assert(sizeof(WinDragBegin) == 72, "WinDragBegin is ABI");
|
||||||
|
|
||||||
|
struct WinReqDragBegin {
|
||||||
|
uint32_t serial;
|
||||||
|
WinDragBegin info;
|
||||||
|
};
|
||||||
|
|
||||||
struct MemStats {
|
struct MemStats {
|
||||||
uint64_t totalBytes;
|
uint64_t totalBytes;
|
||||||
uint64_t freeBytes;
|
uint64_t freeBytes;
|
||||||
@@ -695,7 +923,7 @@ namespace montauk::abi {
|
|||||||
|
|
||||||
struct DiskInfo {
|
struct DiskInfo {
|
||||||
uint8_t port; // block device index
|
uint8_t port; // block device index
|
||||||
uint8_t type; // 0=none, 1=SATA, 2=SATAPI, 3=NVMe, 4=USB mass storage
|
uint8_t type; // 0=none, 1=SATA, 2=SATAPI, 3=NVMe, 4=USB mass storage, 5=SD card
|
||||||
uint8_t sataGen; // SATA gen (1/2/3)
|
uint8_t sataGen; // SATA gen (1/2/3)
|
||||||
uint8_t _pad0;
|
uint8_t _pad0;
|
||||||
uint64_t sectorCount; // Total user-addressable sectors
|
uint64_t sectorCount; // Total user-addressable sectors
|
||||||
@@ -777,6 +1005,14 @@ namespace montauk::abi {
|
|||||||
char label[32]; // volume label
|
char label[32]; // volume label
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct FsGrowStatus {
|
||||||
|
uint64_t fsBytes; // filesystem size now
|
||||||
|
uint64_t partBytes; // partition size now
|
||||||
|
uint64_t targetBytes; // filesystem size once grown; == fsBytes if it cannot grow
|
||||||
|
uint32_t stepsDone; // progress of the grow, out of stepsTotal
|
||||||
|
uint32_t stepsTotal;
|
||||||
|
};
|
||||||
|
|
||||||
struct ProcInfo {
|
struct ProcInfo {
|
||||||
int32_t pid;
|
int32_t pid;
|
||||||
int32_t parentPid;
|
int32_t parentPid;
|
||||||
@@ -1010,15 +1246,11 @@ namespace montauk::abi {
|
|||||||
uint64_t timestampTick;
|
uint64_t timestampTick;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Stack frame pushed by SyscallEntry.asm
|
// Saved user context of a system call; its layout is the
|
||||||
struct SyscallFrame {
|
// architecture's (see Arch/Syscall.hpp).
|
||||||
uint64_t r15, r14, r13, r12, rbp, rbx; // callee-saved
|
using SyscallFrame = Arch::SyscallFrame;
|
||||||
uint64_t arg6, arg5, arg4, arg3, arg2, arg1;
|
|
||||||
uint64_t syscall_nr;
|
|
||||||
uint64_t user_rflags, user_rip, user_rsp;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Kernel-only: set up SYSCALL MSRs and initialize dispatch
|
// Kernel-only: install the system-call entry point and initialize dispatch
|
||||||
void InitializeSyscalls();
|
void InitializeSyscalls();
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
/*
|
/*
|
||||||
* Time.hpp
|
* Time.hpp
|
||||||
* SYS_GETTICKS, SYS_GETMILLISECONDS, SYS_GETTIME syscalls
|
* SYS_GETTICKS, SYS_GETMILLISECONDS, SYS_GETTIME, SYS_GETUNIXTIME syscalls
|
||||||
* Copyright (c) 2026 Daniel Hammer
|
* Copyright (c) 2026 Daniel Hammer
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#include <Timekeeping/ApicTimer.hpp>
|
#include <Timekeeping/Timer.hpp>
|
||||||
#include <Timekeeping/Time.hpp>
|
#include <Timekeeping/Time.hpp>
|
||||||
|
|
||||||
#include "Syscall.hpp"
|
#include "Syscall.hpp"
|
||||||
@@ -39,6 +39,10 @@ namespace montauk::abi {
|
|||||||
return (int64_t)Timekeeping::GetTZOffset();
|
return (int64_t)Timekeeping::GetTZOffset();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int64_t Sys_GetUnixTime() {
|
||||||
|
return Timekeeping::GetUnixTimestamp();
|
||||||
|
}
|
||||||
|
|
||||||
static int64_t Sys_SetUnixTime(int64_t unixSeconds) {
|
static int64_t Sys_SetUnixTime(int64_t unixSeconds) {
|
||||||
return Timekeeping::SetUnixTimestamp(unixSeconds) ? Success : InvalidArgument;
|
return Timekeeping::SetUnixTimestamp(unixSeconds) ? Success : InvalidArgument;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,9 +19,9 @@ namespace montauk::abi::UserMemory {
|
|||||||
return addr != 0 && addr < USER_SPACE_END;
|
return addr != 0 && addr < USER_SPACE_END;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline uint64_t CurrentPml4() {
|
inline uint64_t CurrentRoot() {
|
||||||
auto* proc = Sched::GetCurrentProcessPtr();
|
auto* proc = Sched::GetCurrentProcessPtr();
|
||||||
return proc ? proc->pml4Phys : 0;
|
return proc ? proc->pageTableRoot : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline bool Range(uint64_t addr, uint64_t size, bool requireWrite = false) {
|
inline bool Range(uint64_t addr, uint64_t size, bool requireWrite = false) {
|
||||||
@@ -30,10 +30,10 @@ namespace montauk::abi::UserMemory {
|
|||||||
if (addr + size < addr) return false;
|
if (addr + size < addr) return false;
|
||||||
if (addr + size > USER_SPACE_END) return false;
|
if (addr + size > USER_SPACE_END) return false;
|
||||||
|
|
||||||
uint64_t pml4Phys = CurrentPml4();
|
uint64_t pageTableRoot = CurrentRoot();
|
||||||
if (pml4Phys == 0) return false;
|
if (pageTableRoot == 0) return false;
|
||||||
|
|
||||||
if (Memory::VMM::Paging::IsUserRangeAccessible(pml4Phys, addr, size, requireWrite))
|
if (Memory::VMM::Paging::IsUserRangeAccessible(pageTableRoot, addr, size, requireWrite))
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
// Anonymous VMAs are demand-committed. Validation precedes the actual
|
// Anonymous VMAs are demand-committed. Validation precedes the actual
|
||||||
@@ -42,13 +42,13 @@ namespace montauk::abi::UserMemory {
|
|||||||
uint64_t page = addr & ~0xFFFULL;
|
uint64_t page = addr & ~0xFFFULL;
|
||||||
uint64_t last = (addr + size - 1) & ~0xFFFULL;
|
uint64_t last = (addr + size - 1) & ~0xFFFULL;
|
||||||
for (;;) {
|
for (;;) {
|
||||||
if (!Memory::VMM::Paging::IsUserRangeAccessible(pml4Phys, page, 1, requireWrite) &&
|
if (!Memory::VMM::Paging::IsUserRangeAccessible(pageTableRoot, page, 1, requireWrite) &&
|
||||||
!TryHandleAnonymousPageFault(page, requireWrite ? 2ULL : 0ULL))
|
!TryHandleAnonymousPageFault(page, {false, requireWrite, false}))
|
||||||
return false;
|
return false;
|
||||||
if (page == last) break;
|
if (page == last) break;
|
||||||
page += 0x1000ULL;
|
page += 0x1000ULL;
|
||||||
}
|
}
|
||||||
return Memory::VMM::Paging::IsUserRangeAccessible(pml4Phys, addr, size, requireWrite);
|
return Memory::VMM::Paging::IsUserRangeAccessible(pageTableRoot, addr, size, requireWrite);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline bool OptionalRange(uint64_t addr, uint64_t size, bool requireWrite = false) {
|
inline bool OptionalRange(uint64_t addr, uint64_t size, bool requireWrite = false) {
|
||||||
@@ -68,13 +68,13 @@ namespace montauk::abi::UserMemory {
|
|||||||
inline bool String(uint64_t addr, uint64_t maxLen) {
|
inline bool String(uint64_t addr, uint64_t maxLen) {
|
||||||
if (!ValidUserPtr(addr) || maxLen == 0) return false;
|
if (!ValidUserPtr(addr) || maxLen == 0) return false;
|
||||||
|
|
||||||
uint64_t pml4Phys = CurrentPml4();
|
uint64_t pageTableRoot = CurrentRoot();
|
||||||
if (pml4Phys == 0) return false;
|
if (pageTableRoot == 0) return false;
|
||||||
|
|
||||||
const char* str = (const char*)addr;
|
const char* str = (const char*)addr;
|
||||||
for (uint64_t i = 0; i < maxLen; i++) {
|
for (uint64_t i = 0; i < maxLen; i++) {
|
||||||
if (!Memory::VMM::Paging::IsUserRangeAccessible(pml4Phys, addr + i, 1, false)) {
|
if (!Memory::VMM::Paging::IsUserRangeAccessible(pageTableRoot, addr + i, 1, false)) {
|
||||||
if (!TryHandleAnonymousPageFault(addr + i, 0)) return false;
|
if (!TryHandleAnonymousPageFault(addr + i, {false, false, false})) return false;
|
||||||
}
|
}
|
||||||
if (str[i] == '\0') return true;
|
if (str[i] == '\0') return true;
|
||||||
}
|
}
|
||||||
|
|||||||
+208
-16
@@ -54,13 +54,13 @@ namespace WinServer {
|
|||||||
Ipc::ReleaseSurface(surface);
|
Ipc::ReleaseSurface(surface);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int UnmapRetiredSnapshotsLocked(int windowId, int callerPid, uint64_t callerPml4) {
|
static int UnmapRetiredSnapshotsLocked(int windowId, int callerPid, uint64_t callerRoot) {
|
||||||
int result = InvalidArgument;
|
int result = InvalidArgument;
|
||||||
for (int i = 0; i < MaxRetiredSnapshots; i++) {
|
for (int i = 0; i < MaxRetiredSnapshots; i++) {
|
||||||
if (!g_retiredSnapshots[i].used || g_retiredSnapshots[i].windowId != windowId) continue;
|
if (!g_retiredSnapshots[i].used || g_retiredSnapshots[i].windowId != windowId) continue;
|
||||||
|
|
||||||
if (g_retiredSnapshots[i].surface != nullptr) {
|
if (g_retiredSnapshots[i].surface != nullptr) {
|
||||||
if (Ipc::UnmapSurfaceForPid(g_retiredSnapshots[i].surface, callerPid, callerPml4) == 0) {
|
if (Ipc::UnmapSurfaceForPid(g_retiredSnapshots[i].surface, callerPid, callerRoot) == 0) {
|
||||||
result = Success;
|
result = Success;
|
||||||
}
|
}
|
||||||
Ipc::ReleaseSurface(g_retiredSnapshots[i].surface);
|
Ipc::ReleaseSurface(g_retiredSnapshots[i].surface);
|
||||||
@@ -73,11 +73,18 @@ namespace WinServer {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void OrphanRequestsLocked(int windowId);
|
||||||
|
|
||||||
static void ReleaseSlotResourcesLocked(WindowSlot& slot, bool unmapOwner) {
|
static void ReleaseSlotResourcesLocked(WindowSlot& slot, bool unmapOwner) {
|
||||||
|
// Queued requests stay deliverable (a dialog may post "open this
|
||||||
|
// app" and close at once), but no longer name a window that could be
|
||||||
|
// handed to someone else.
|
||||||
|
OrphanRequestsLocked((int)(&slot - g_slots));
|
||||||
|
|
||||||
if (unmapOwner && slot.liveSurface != nullptr) {
|
if (unmapOwner && slot.liveSurface != nullptr) {
|
||||||
auto* owner = Sched::GetProcessByPid(slot.ownerPid);
|
auto* owner = Sched::GetProcessByPid(slot.ownerPid);
|
||||||
if (owner != nullptr) {
|
if (owner != nullptr) {
|
||||||
Ipc::UnmapSurfaceForPid(slot.liveSurface, slot.ownerPid, owner->pml4Phys);
|
Ipc::UnmapSurfaceForPid(slot.liveSurface, slot.ownerPid, owner->pageTableRoot);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -94,19 +101,63 @@ namespace WinServer {
|
|||||||
ResetSlotLocked(slot);
|
ResetSlotLocked(slot);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---- Messaging state ----
|
||||||
|
|
||||||
|
static constexpr int MaxRequests = 64;
|
||||||
|
// One app may not hold more than this many queued requests, so a single
|
||||||
|
// misbehaving process cannot starve the others out of the queue.
|
||||||
|
static constexpr int MaxRequestsPerSender = 16;
|
||||||
|
static constexpr int MaxDragReaders = 8;
|
||||||
|
|
||||||
|
static montauk::abi::WinRequest g_requests[MaxRequests];
|
||||||
|
static int g_requestHead = 0; // oldest
|
||||||
|
static int g_requestCount = 0;
|
||||||
|
static uint32_t g_requestSerial = 0;
|
||||||
|
|
||||||
|
struct DragState {
|
||||||
|
bool active;
|
||||||
|
uint32_t serial;
|
||||||
|
int sourceWindow;
|
||||||
|
int sourcePid;
|
||||||
|
DataTransfer::Transfer transfer;
|
||||||
|
int readers[MaxDragReaders];
|
||||||
|
int readerCount;
|
||||||
|
};
|
||||||
|
static DragState g_drag;
|
||||||
|
static uint32_t g_dragSerial = 0;
|
||||||
|
|
||||||
|
static bool IsDragEvent(uint8_t type) {
|
||||||
|
return type >= montauk::abi::WIN_EVENT_DRAG_ENTER && type <= montauk::abi::WIN_EVENT_DRAG_END;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void GrantDragReaderLocked(uint32_t serial, int pid) {
|
||||||
|
if (!g_drag.active || g_drag.serial != serial || pid <= 0) return;
|
||||||
|
if (pid == g_drag.sourcePid) return;
|
||||||
|
for (int i = 0; i < g_drag.readerCount; i++) {
|
||||||
|
if (g_drag.readers[i] == pid) return;
|
||||||
|
}
|
||||||
|
if (g_drag.readerCount < MaxDragReaders) g_drag.readers[g_drag.readerCount++] = pid;
|
||||||
|
}
|
||||||
|
|
||||||
static int SendEventLocked(int windowId, const montauk::abi::WinEvent* event) {
|
static int SendEventLocked(int windowId, const montauk::abi::WinEvent* event) {
|
||||||
if (windowId < 0 || windowId >= MaxWindows || event == nullptr) return InvalidArgument;
|
if (windowId < 0 || windowId >= MaxWindows || event == nullptr) return InvalidArgument;
|
||||||
WindowSlot& slot = g_slots[windowId];
|
WindowSlot& slot = g_slots[windowId];
|
||||||
if (!slot.used || slot.eventMailbox == nullptr) return InvalidArgument;
|
if (!slot.used || slot.eventMailbox == nullptr) return InvalidArgument;
|
||||||
|
|
||||||
int rc = (event->type == 1)
|
// Delivering a drag event is what lets the receiving app read the
|
||||||
? Ipc::MailboxSendCoalescedMouse(slot.eventMailbox, 0, event, sizeof(*event))
|
// drag's data: it needs the formats to decide whether to accept.
|
||||||
|
if (IsDragEvent(event->type)) GrantDragReaderLocked(event->drag.serial, slot.ownerPid);
|
||||||
|
|
||||||
|
bool motion = event->type == montauk::abi::WIN_EVENT_MOUSE ||
|
||||||
|
event->type == montauk::abi::WIN_EVENT_DRAG_OVER;
|
||||||
|
int rc = motion
|
||||||
|
? Ipc::MailboxSendCoalescedMotion(slot.eventMailbox, 0, event, sizeof(*event))
|
||||||
: Ipc::MailboxSend(slot.eventMailbox, 0, event, sizeof(*event));
|
: Ipc::MailboxSend(slot.eventMailbox, 0, event, sizeof(*event));
|
||||||
if (rc < 0) return GenericFailure;
|
if (rc < 0) return GenericFailure;
|
||||||
return rc > 0 ? Success : Busy;
|
return rc > 0 ? Success : Busy;
|
||||||
}
|
}
|
||||||
|
|
||||||
int Create(int ownerPid, uint64_t ownerPml4, const char* title, int w, int h,
|
int Create(int ownerPid, uint64_t ownerRoot, const char* title, int w, int h,
|
||||||
uint64_t& heapNext, uint64_t& outVa) {
|
uint64_t& heapNext, uint64_t& outVa) {
|
||||||
WsGuard guard;
|
WsGuard guard;
|
||||||
|
|
||||||
@@ -143,7 +194,7 @@ namespace WinServer {
|
|||||||
FillSurfaceLocked(snapshot, w, h, InitialWindowPixel);
|
FillSurfaceLocked(snapshot, w, h, InitialWindowPixel);
|
||||||
|
|
||||||
uint64_t userVa = 0;
|
uint64_t userVa = 0;
|
||||||
if (Ipc::MapSurfaceForPid(live, ownerPid, ownerPml4, heapNext, userVa, false) != 0) {
|
if (Ipc::MapSurfaceForPid(live, ownerPid, ownerRoot, heapNext, userVa, false) != 0) {
|
||||||
Ipc::ReleaseMailbox(mailbox, true, true);
|
Ipc::ReleaseMailbox(mailbox, true, true);
|
||||||
Ipc::ReleaseSurface(snapshot);
|
Ipc::ReleaseSurface(snapshot);
|
||||||
Ipc::ReleaseSurface(live);
|
Ipc::ReleaseSurface(live);
|
||||||
@@ -236,7 +287,7 @@ namespace WinServer {
|
|||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint64_t Map(int windowId, int callerPid, uint64_t callerPml4, uint64_t& heapNext) {
|
uint64_t Map(int windowId, int callerPid, uint64_t callerRoot, uint64_t& heapNext) {
|
||||||
WsGuard guard;
|
WsGuard guard;
|
||||||
if (windowId < 0 || windowId >= MaxWindows) return 0;
|
if (windowId < 0 || windowId >= MaxWindows) return 0;
|
||||||
|
|
||||||
@@ -244,23 +295,23 @@ namespace WinServer {
|
|||||||
if (!slot.used || slot.snapshotSurface == nullptr) return 0;
|
if (!slot.used || slot.snapshotSurface == nullptr) return 0;
|
||||||
|
|
||||||
uint64_t outVa = 0;
|
uint64_t outVa = 0;
|
||||||
if (Ipc::MapSurfaceForPid(slot.snapshotSurface, callerPid, callerPml4, heapNext, outVa, false) != 0) {
|
if (Ipc::MapSurfaceForPid(slot.snapshotSurface, callerPid, callerRoot, heapNext, outVa, false) != 0) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
return outVa;
|
return outVa;
|
||||||
}
|
}
|
||||||
|
|
||||||
int Unmap(int windowId, int callerPid, uint64_t callerPml4) {
|
int Unmap(int windowId, int callerPid, uint64_t callerRoot) {
|
||||||
WsGuard guard;
|
WsGuard guard;
|
||||||
if (windowId < 0 || windowId >= MaxWindows) {
|
if (windowId < 0 || windowId >= MaxWindows) {
|
||||||
return UnmapRetiredSnapshotsLocked(windowId, callerPid, callerPml4);
|
return UnmapRetiredSnapshotsLocked(windowId, callerPid, callerRoot);
|
||||||
}
|
}
|
||||||
|
|
||||||
int result = UnmapRetiredSnapshotsLocked(windowId, callerPid, callerPml4);
|
int result = UnmapRetiredSnapshotsLocked(windowId, callerPid, callerRoot);
|
||||||
WindowSlot& slot = g_slots[windowId];
|
WindowSlot& slot = g_slots[windowId];
|
||||||
if (!slot.used || slot.snapshotSurface == nullptr) return result;
|
if (!slot.used || slot.snapshotSurface == nullptr) return result;
|
||||||
|
|
||||||
int current = Ipc::UnmapSurfaceForPid(slot.snapshotSurface, callerPid, callerPml4);
|
int current = Ipc::UnmapSurfaceForPid(slot.snapshotSurface, callerPid, callerRoot);
|
||||||
if (current == 0) return Success;
|
if (current == 0) return Success;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -270,7 +321,7 @@ namespace WinServer {
|
|||||||
return SendEventLocked(windowId, event);
|
return SendEventLocked(windowId, event);
|
||||||
}
|
}
|
||||||
|
|
||||||
int Resize(int windowId, int callerPid, uint64_t ownerPml4, int newW, int newH,
|
int Resize(int windowId, int callerPid, uint64_t ownerRoot, int newW, int newH,
|
||||||
uint64_t& heapNext, uint64_t& outVa) {
|
uint64_t& heapNext, uint64_t& outVa) {
|
||||||
WsGuard guard;
|
WsGuard guard;
|
||||||
if (windowId < 0 || windowId >= MaxWindows) return InvalidArgument;
|
if (windowId < 0 || windowId >= MaxWindows) return InvalidArgument;
|
||||||
@@ -297,7 +348,7 @@ namespace WinServer {
|
|||||||
Ipc::RetainSurface(newSnapshot);
|
Ipc::RetainSurface(newSnapshot);
|
||||||
|
|
||||||
uint64_t newOwnerVa = 0;
|
uint64_t newOwnerVa = 0;
|
||||||
if (Ipc::MapSurfaceForPid(newLive, callerPid, ownerPml4, heapNext, newOwnerVa, false) != 0) {
|
if (Ipc::MapSurfaceForPid(newLive, callerPid, ownerRoot, heapNext, newOwnerVa, false) != 0) {
|
||||||
Ipc::ReleaseSurface(newSnapshot);
|
Ipc::ReleaseSurface(newSnapshot);
|
||||||
Ipc::ReleaseSurface(newLive);
|
Ipc::ReleaseSurface(newLive);
|
||||||
return GenericFailure;
|
return GenericFailure;
|
||||||
@@ -315,7 +366,7 @@ namespace WinServer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (slot.liveSurface != nullptr) {
|
if (slot.liveSurface != nullptr) {
|
||||||
Ipc::UnmapSurfaceForPid(slot.liveSurface, callerPid, ownerPml4);
|
Ipc::UnmapSurfaceForPid(slot.liveSurface, callerPid, ownerRoot);
|
||||||
Ipc::ReleaseSurface(slot.liveSurface);
|
Ipc::ReleaseSurface(slot.liveSurface);
|
||||||
}
|
}
|
||||||
if (slot.snapshotSurface != nullptr) {
|
if (slot.snapshotSurface != nullptr) {
|
||||||
@@ -376,12 +427,153 @@ namespace WinServer {
|
|||||||
return g_uiScale;
|
return g_uiScale;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void OrphanRequestsLocked(int windowId) {
|
||||||
|
for (int i = 0; i < g_requestCount; i++) {
|
||||||
|
montauk::abi::WinRequest& r = g_requests[(g_requestHead + i) % MaxRequests];
|
||||||
|
if (r.windowId == windowId) r.windowId = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void EndDragLocked() {
|
||||||
|
DataTransfer::Free(g_drag.transfer);
|
||||||
|
g_drag.active = false;
|
||||||
|
g_drag.readerCount = 0;
|
||||||
|
}
|
||||||
|
|
||||||
void CleanupProcess(int pid) {
|
void CleanupProcess(int pid) {
|
||||||
WsGuard guard;
|
WsGuard guard;
|
||||||
for (int i = 0; i < MaxWindows; i++) {
|
for (int i = 0; i < MaxWindows; i++) {
|
||||||
if (!g_slots[i].used || g_slots[i].ownerPid != pid) continue;
|
if (!g_slots[i].used || g_slots[i].ownerPid != pid) continue;
|
||||||
ReleaseSlotResourcesLocked(g_slots[i], false);
|
ReleaseSlotResourcesLocked(g_slots[i], false);
|
||||||
}
|
}
|
||||||
|
// Requests the process already posted are still delivered: they are
|
||||||
|
// messages in flight, and a process may well exit right after its
|
||||||
|
// last one ("open this file" from a dialog-only Files).
|
||||||
|
if (g_drag.active && g_drag.sourcePid == pid) EndDragLocked();
|
||||||
|
}
|
||||||
|
|
||||||
|
static int EnqueueRequestLocked(montauk::abi::WinRequest& request) {
|
||||||
|
if (g_requestCount >= MaxRequests) return WouldBlock;
|
||||||
|
request.serial = ++g_requestSerial;
|
||||||
|
if (request.serial == 0) request.serial = ++g_requestSerial;
|
||||||
|
g_requests[(g_requestHead + g_requestCount) % MaxRequests] = request;
|
||||||
|
g_requestCount++;
|
||||||
|
return Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool OwnsWindowLocked(int windowId, int pid) {
|
||||||
|
return windowId >= 0 && windowId < MaxWindows && g_slots[windowId].used &&
|
||||||
|
g_slots[windowId].ownerPid == pid;
|
||||||
|
}
|
||||||
|
|
||||||
|
int PostRequest(int windowId, int callerPid, const montauk::abi::WinRequest& request) {
|
||||||
|
using namespace montauk::abi;
|
||||||
|
if (request.type == 0 || request.type >= WIN_REQ_KERNEL_BASE) return InvalidArgument;
|
||||||
|
if (request.size > WIN_REQUEST_PAYLOAD_MAX) return InvalidArgument;
|
||||||
|
|
||||||
|
WsGuard guard;
|
||||||
|
if (!OwnsWindowLocked(windowId, callerPid)) return PermissionDenied;
|
||||||
|
|
||||||
|
int mine = 0;
|
||||||
|
for (int i = 0; i < g_requestCount; i++) {
|
||||||
|
if (g_requests[(g_requestHead + i) % MaxRequests].senderPid == callerPid) mine++;
|
||||||
|
}
|
||||||
|
if (mine >= MaxRequestsPerSender) return WouldBlock;
|
||||||
|
|
||||||
|
WinRequest copy = request;
|
||||||
|
copy.windowId = windowId;
|
||||||
|
copy.senderPid = callerPid;
|
||||||
|
// Bytes past `size` are not the sender's business to pass along.
|
||||||
|
for (uint32_t i = copy.size; i < WIN_REQUEST_PAYLOAD_MAX; i++) copy.payload[i] = 0;
|
||||||
|
return EnqueueRequestLocked(copy);
|
||||||
|
}
|
||||||
|
|
||||||
|
int PollRequest(montauk::abi::WinRequest* out) {
|
||||||
|
WsGuard guard;
|
||||||
|
while (g_requestCount > 0) {
|
||||||
|
montauk::abi::WinRequest& r = g_requests[g_requestHead];
|
||||||
|
g_requestHead = (g_requestHead + 1) % MaxRequests;
|
||||||
|
g_requestCount--;
|
||||||
|
// A destroyed window's requests arrive with windowId -1 (see
|
||||||
|
// OrphanRequestsLocked); anything else must still match.
|
||||||
|
if (r.windowId >= 0 && !OwnsWindowLocked(r.windowId, r.senderPid)) continue;
|
||||||
|
*out = r;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int DragBegin(int windowId, int callerPid, bool compositor,
|
||||||
|
const montauk::abi::WinDragBegin& info, DataTransfer::Transfer& transfer) {
|
||||||
|
using namespace montauk::abi;
|
||||||
|
WsGuard guard;
|
||||||
|
// The compositor drags from its own surface (desktop icons) with
|
||||||
|
// windowId -1; it already knows about such a drag, so no request.
|
||||||
|
bool fromSurface = compositor && windowId == -1;
|
||||||
|
if (!fromSurface && !OwnsWindowLocked(windowId, callerPid)) {
|
||||||
|
DataTransfer::Free(transfer);
|
||||||
|
return PermissionDenied;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t serial = ++g_dragSerial;
|
||||||
|
if (serial == 0) serial = ++g_dragSerial;
|
||||||
|
|
||||||
|
if (!fromSurface) {
|
||||||
|
WinRequest request = {};
|
||||||
|
request.type = WIN_REQ_DRAG_BEGIN;
|
||||||
|
request.size = sizeof(WinReqDragBegin);
|
||||||
|
request.windowId = windowId;
|
||||||
|
request.senderPid = callerPid;
|
||||||
|
WinReqDragBegin* payload = (WinReqDragBegin*)request.payload;
|
||||||
|
payload->serial = serial;
|
||||||
|
payload->info = info;
|
||||||
|
payload->info.label[sizeof(payload->info.label) - 1] = '\0';
|
||||||
|
int rc = EnqueueRequestLocked(request);
|
||||||
|
if (rc != Success) {
|
||||||
|
DataTransfer::Free(transfer);
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A new drag replaces the previous one (only one pointer exists).
|
||||||
|
if (g_drag.active) EndDragLocked();
|
||||||
|
g_drag.active = true;
|
||||||
|
g_drag.serial = serial;
|
||||||
|
g_drag.sourceWindow = windowId;
|
||||||
|
g_drag.sourcePid = callerPid;
|
||||||
|
g_drag.transfer = transfer;
|
||||||
|
g_drag.readerCount = 0;
|
||||||
|
transfer.count = 0;
|
||||||
|
return (int)serial;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool DragReadableLocked(uint32_t serial, int callerPid, bool compositor) {
|
||||||
|
if (!g_drag.active || g_drag.serial != serial) return false;
|
||||||
|
if (compositor || callerPid == g_drag.sourcePid) return true;
|
||||||
|
for (int i = 0; i < g_drag.readerCount; i++) {
|
||||||
|
if (g_drag.readers[i] == callerPid) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int DragFormats(uint32_t serial, int callerPid, bool compositor,
|
||||||
|
montauk::abi::TransferFormatInfo* out, uint32_t max) {
|
||||||
|
WsGuard guard;
|
||||||
|
if (!g_drag.active || g_drag.serial != serial) return NotFound;
|
||||||
|
if (!DragReadableLocked(serial, callerPid, compositor)) return PermissionDenied;
|
||||||
|
if (out == nullptr || max == 0) return (int)g_drag.transfer.count;
|
||||||
|
return (int)DataTransfer::ListFormats(g_drag.transfer, out, max);
|
||||||
|
}
|
||||||
|
|
||||||
|
int DragGetData(uint32_t serial, int callerPid, bool compositor, const char* mime,
|
||||||
|
void* buffer, uint32_t bufferLen, uint32_t* outLen) {
|
||||||
|
WsGuard guard;
|
||||||
|
if (outLen != nullptr) *outLen = 0;
|
||||||
|
if (!g_drag.active || g_drag.serial != serial) return NotFound;
|
||||||
|
if (!DragReadableLocked(serial, callerPid, compositor)) return PermissionDenied;
|
||||||
|
const DataTransfer::Format* format = DataTransfer::Find(g_drag.transfer, mime);
|
||||||
|
if (format == nullptr) return NotFound;
|
||||||
|
return DataTransfer::CopyOut(*format, buffer, bufferLen, outLen);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -8,6 +8,7 @@
|
|||||||
#include "Syscall.hpp"
|
#include "Syscall.hpp"
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <Ipc/Ipc.hpp>
|
#include <Ipc/Ipc.hpp>
|
||||||
|
#include "DataTransfer.hpp"
|
||||||
|
|
||||||
namespace WinServer {
|
namespace WinServer {
|
||||||
|
|
||||||
@@ -27,16 +28,16 @@ namespace WinServer {
|
|||||||
uint32_t flags;
|
uint32_t flags;
|
||||||
};
|
};
|
||||||
|
|
||||||
int Create(int ownerPid, uint64_t ownerPml4, const char* title, int w, int h,
|
int Create(int ownerPid, uint64_t ownerRoot, const char* title, int w, int h,
|
||||||
uint64_t& heapNext, uint64_t& outVa);
|
uint64_t& heapNext, uint64_t& outVa);
|
||||||
int Destroy(int windowId, int callerPid);
|
int Destroy(int windowId, int callerPid);
|
||||||
int Present(int windowId, int callerPid);
|
int Present(int windowId, int callerPid);
|
||||||
int Poll(int windowId, int callerPid, montauk::abi::WinEvent* outEvent);
|
int Poll(int windowId, int callerPid, montauk::abi::WinEvent* outEvent);
|
||||||
int Enumerate(montauk::abi::WinInfo* outArray, int maxCount);
|
int Enumerate(montauk::abi::WinInfo* outArray, int maxCount);
|
||||||
uint64_t Map(int windowId, int callerPid, uint64_t callerPml4, uint64_t& heapNext);
|
uint64_t Map(int windowId, int callerPid, uint64_t callerRoot, uint64_t& heapNext);
|
||||||
int Unmap(int windowId, int callerPid, uint64_t callerPml4);
|
int Unmap(int windowId, int callerPid, uint64_t callerRoot);
|
||||||
int SendEvent(int windowId, const montauk::abi::WinEvent* event);
|
int SendEvent(int windowId, const montauk::abi::WinEvent* event);
|
||||||
int Resize(int windowId, int callerPid, uint64_t ownerPml4, int newW, int newH,
|
int Resize(int windowId, int callerPid, uint64_t ownerRoot, int newW, int newH,
|
||||||
uint64_t& heapNext, uint64_t& outVa);
|
uint64_t& heapNext, uint64_t& outVa);
|
||||||
void CleanupProcess(int pid);
|
void CleanupProcess(int pid);
|
||||||
int SetCursor(int windowId, int callerPid, int cursor);
|
int SetCursor(int windowId, int callerPid, int cursor);
|
||||||
@@ -44,4 +45,26 @@ namespace WinServer {
|
|||||||
int SetScale(int scale);
|
int SetScale(int scale);
|
||||||
int GetScale();
|
int GetScale();
|
||||||
|
|
||||||
|
// ---- Messaging (see "Window server messaging" in Api/Syscall.hpp) ----
|
||||||
|
|
||||||
|
// Queue an app request about one of the caller's own windows.
|
||||||
|
int PostRequest(int windowId, int callerPid, const montauk::abi::WinRequest& request);
|
||||||
|
// Pop the oldest request (compositor only; the caller checks the cap).
|
||||||
|
// Returns 1 with *out filled, or 0 when the queue is empty.
|
||||||
|
int PollRequest(montauk::abi::WinRequest* out);
|
||||||
|
|
||||||
|
// Start a drag from the caller's window, or (compositor only, windowId
|
||||||
|
// -1) from the desktop surface itself. Takes ownership of `transfer` (it
|
||||||
|
// is emptied either way) and returns the new drag serial (> 0) or a
|
||||||
|
// negative StatusCodes value.
|
||||||
|
int DragBegin(int windowId, int callerPid, bool compositor,
|
||||||
|
const montauk::abi::WinDragBegin& info, DataTransfer::Transfer& transfer);
|
||||||
|
// Read the drag's formats or data. Allowed for the source process, the
|
||||||
|
// compositor, and processes the compositor has sent a drag event for this
|
||||||
|
// serial to.
|
||||||
|
int DragFormats(uint32_t serial, int callerPid, bool compositor,
|
||||||
|
montauk::abi::TransferFormatInfo* out, uint32_t max);
|
||||||
|
int DragGetData(uint32_t serial, int callerPid, bool compositor, const char* mime,
|
||||||
|
void* buffer, uint32_t bufferLen, uint32_t* outLen);
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -2,7 +2,9 @@
|
|||||||
* Window.hpp
|
* Window.hpp
|
||||||
* SYS_WINCREATE, SYS_WINDESTROY, SYS_WINPRESENT, SYS_WINPOLL,
|
* SYS_WINCREATE, SYS_WINDESTROY, SYS_WINPRESENT, SYS_WINPOLL,
|
||||||
* SYS_WINENUM, SYS_WINMAP, SYS_WINSENDEVENT, SYS_WINRESIZE,
|
* SYS_WINENUM, SYS_WINMAP, SYS_WINSENDEVENT, SYS_WINRESIZE,
|
||||||
* SYS_WINSETCURSOR, SYS_WINSETFLAGS, SYS_WINSETSCALE, SYS_WINGETSCALE syscalls
|
* SYS_WINSETCURSOR, SYS_WINSETFLAGS, SYS_WINSETSCALE, SYS_WINGETSCALE syscalls,
|
||||||
|
* and window server messaging: SYS_WIN_REQUEST, SYS_WIN_REQUEST_POLL,
|
||||||
|
* SYS_WIN_DRAG_BEGIN, SYS_WIN_DRAG_FORMATS, SYS_WIN_DRAG_GET_DATA
|
||||||
* Copyright (c) 2026 Daniel Hammer
|
* Copyright (c) 2026 Daniel Hammer
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@@ -20,7 +22,7 @@ namespace montauk::abi {
|
|||||||
if (proc == nullptr) return GenericFailure;
|
if (proc == nullptr) return GenericFailure;
|
||||||
|
|
||||||
uint64_t outVa = 0;
|
uint64_t outVa = 0;
|
||||||
int id = WinServer::Create(proc->pid, proc->pml4Phys, title, w, h,
|
int id = WinServer::Create(proc->pid, proc->pageTableRoot, title, w, h,
|
||||||
proc->heapNext, outVa);
|
proc->heapNext, outVa);
|
||||||
result->id = id;
|
result->id = id;
|
||||||
result->pixelVa = (id >= 0) ? outVa : 0;
|
result->pixelVa = (id >= 0) ? outVa : 0;
|
||||||
@@ -53,13 +55,13 @@ namespace montauk::abi {
|
|||||||
if (!Sched::HasCapability(CAP_COMPOSITOR)) return 0;
|
if (!Sched::HasCapability(CAP_COMPOSITOR)) return 0;
|
||||||
auto* proc = Sched::GetCurrentProcessPtr();
|
auto* proc = Sched::GetCurrentProcessPtr();
|
||||||
if (proc == nullptr) return 0;
|
if (proc == nullptr) return 0;
|
||||||
return WinServer::Map(windowId, proc->pid, proc->pml4Phys, proc->heapNext);
|
return WinServer::Map(windowId, proc->pid, proc->pageTableRoot, proc->heapNext);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int Sys_WinUnmap(int windowId) {
|
static int Sys_WinUnmap(int windowId) {
|
||||||
auto* proc = Sched::GetCurrentProcessPtr();
|
auto* proc = Sched::GetCurrentProcessPtr();
|
||||||
if (proc == nullptr) return GenericFailure;
|
if (proc == nullptr) return GenericFailure;
|
||||||
return WinServer::Unmap(windowId, proc->pid, proc->pml4Phys);
|
return WinServer::Unmap(windowId, proc->pid, proc->pageTableRoot);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int Sys_WinSendEvent(int windowId, const WinEvent* event) {
|
static int Sys_WinSendEvent(int windowId, const WinEvent* event) {
|
||||||
@@ -72,7 +74,7 @@ namespace montauk::abi {
|
|||||||
auto* proc = Sched::GetCurrentProcessPtr();
|
auto* proc = Sched::GetCurrentProcessPtr();
|
||||||
if (proc == nullptr) return 0;
|
if (proc == nullptr) return 0;
|
||||||
uint64_t outVa = 0;
|
uint64_t outVa = 0;
|
||||||
int r = WinServer::Resize(windowId, proc->pid, proc->pml4Phys, newW, newH,
|
int r = WinServer::Resize(windowId, proc->pid, proc->pageTableRoot, newW, newH,
|
||||||
proc->heapNext, outVa);
|
proc->heapNext, outVa);
|
||||||
return (r == 0) ? outVa : 0;
|
return (r == 0) ? outVa : 0;
|
||||||
}
|
}
|
||||||
@@ -92,4 +94,41 @@ namespace montauk::abi {
|
|||||||
static int Sys_WinGetScale() {
|
static int Sys_WinGetScale() {
|
||||||
return WinServer::GetScale();
|
return WinServer::GetScale();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Any app may post requests, but only about its own windows (checked in
|
||||||
|
// WinServer); only the compositor may read them back.
|
||||||
|
static int Sys_WinRequest(int windowId, const WinRequest* request) {
|
||||||
|
if (request == nullptr) return InvalidArgument;
|
||||||
|
WinRequest copy = *request;
|
||||||
|
return WinServer::PostRequest(windowId, Sched::GetCurrentPid(), copy);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int Sys_WinRequestPoll(WinRequest* out) {
|
||||||
|
if (!Sched::HasCapability(CAP_COMPOSITOR)) return CapabilityDenied;
|
||||||
|
if (out == nullptr) return InvalidArgument;
|
||||||
|
return WinServer::PollRequest(out);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int Sys_WinDragBegin(int windowId, const WinDragBegin* info,
|
||||||
|
const TransferItem* items, uint32_t count) {
|
||||||
|
if (info == nullptr) return InvalidArgument;
|
||||||
|
WinDragBegin infoCopy = *info;
|
||||||
|
DataTransfer::Transfer transfer;
|
||||||
|
int rc = DataTransfer::BuildFromUser(items, count, transfer);
|
||||||
|
if (rc != Success) return rc;
|
||||||
|
return WinServer::DragBegin(windowId, Sched::GetCurrentPid(),
|
||||||
|
Sched::HasCapability(CAP_COMPOSITOR), infoCopy, transfer);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int Sys_WinDragFormats(uint32_t serial, TransferFormatInfo* out, uint32_t max) {
|
||||||
|
return WinServer::DragFormats(serial, Sched::GetCurrentPid(),
|
||||||
|
Sched::HasCapability(CAP_COMPOSITOR), out, max);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int Sys_WinDragGetData(uint32_t serial, const char* mime, void* buffer,
|
||||||
|
uint32_t bufferLen, uint32_t* outLen) {
|
||||||
|
return WinServer::DragGetData(serial, Sched::GetCurrentPid(),
|
||||||
|
Sched::HasCapability(CAP_COMPOSITOR), mime,
|
||||||
|
buffer, bufferLen, outLen);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
/*
|
||||||
|
* Abi.hpp
|
||||||
|
* Architecture-specific parts of the user-mode ABI
|
||||||
|
* Copyright (c) 2026 Daniel Hammer
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include <cstdint>
|
||||||
|
#include <Arch/Arch.hpp>
|
||||||
|
|
||||||
|
// What the ELF loader and the process/thread setup code need to know about
|
||||||
|
// the user-mode binary interface of the target. Defined inline by
|
||||||
|
// Arch/<arch>/Abi.hpp.
|
||||||
|
|
||||||
|
namespace Arch {
|
||||||
|
|
||||||
|
// ELF e_machine value accepted for user programs and libraries, and a
|
||||||
|
// name for diagnostics.
|
||||||
|
// constexpr uint16_t ElfMachine;
|
||||||
|
// constexpr const char* ElfMachineName;
|
||||||
|
|
||||||
|
// Calling-convention attribute for UEFI runtime services:
|
||||||
|
// #define ARCH_EFIAPI
|
||||||
|
|
||||||
|
// How the loader must treat a dynamic relocation type.
|
||||||
|
enum class RelocationKind {
|
||||||
|
None, // no-op (type 0)
|
||||||
|
Relative, // *target = loadBias + addend
|
||||||
|
Symbol, // *target = symbol address + addend (64-bit absolute)
|
||||||
|
Unsupported,
|
||||||
|
};
|
||||||
|
inline RelocationKind ClassifyRelocation(uint32_t type);
|
||||||
|
|
||||||
|
// Geometry of one thread's TLS block, as offsets from its page-aligned
|
||||||
|
// base. The variant (I: TLS above the thread pointer, II: below it) is
|
||||||
|
// fixed by the architecture's ELF TLS ABI.
|
||||||
|
struct TlsLayout {
|
||||||
|
uint64_t templateOffset; // where .tdata/.tbss start
|
||||||
|
uint64_t tpOffset; // thread pointer; [TP] holds the self-pointer
|
||||||
|
uint64_t totalSize; // bytes to map, incl. the TCB
|
||||||
|
};
|
||||||
|
inline TlsLayout ComputeTlsLayout(uint64_t memSize, uint64_t pAlign);
|
||||||
|
|
||||||
|
// Emit the user-mode code that calls SYS_EXIT(0). A process's entry
|
||||||
|
// function returns into it.
|
||||||
|
inline void WriteExitStub(uint8_t* page);
|
||||||
|
|
||||||
|
// Prepare the main thread's initial user stack so that returning from
|
||||||
|
// the entry function lands at `returnAddress`. `topPage` is the kernel
|
||||||
|
// mapping of the page just below `stackTop`. Returns the initial user SP.
|
||||||
|
inline uint64_t SetupUserEntryStack(uint8_t* topPage, uint64_t stackTop,
|
||||||
|
uint64_t returnAddress);
|
||||||
|
|
||||||
|
// Initial user SP for a sibling thread whose stack ends at `stackTop`
|
||||||
|
// (the thread trampoline never returns, so no return address is set).
|
||||||
|
inline uint64_t ThreadEntryStackPointer(uint64_t stackTop);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#if MONTAUK_ARCH_X86_64
|
||||||
|
#include <Arch/x86_64/Abi.hpp>
|
||||||
|
#elif MONTAUK_ARCH_AARCH64
|
||||||
|
#include <Arch/aarch64/Abi.hpp>
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
/*
|
||||||
|
* Arch.hpp
|
||||||
|
* Target architecture selection
|
||||||
|
* Copyright (c) 2026 Daniel Hammer
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
// Everything outside src/Arch/ is architecture-neutral and talks to the CPU
|
||||||
|
// only through the interfaces in src/Arch/*.hpp. Each supported architecture
|
||||||
|
// implements them under src/Arch/<arch>/; the build compiles only the
|
||||||
|
// directory matching ARCH. See docs/porting.md.
|
||||||
|
//
|
||||||
|
// Interface headers that need inline (per-arch) definitions include the
|
||||||
|
// matching implementation header at their end, selected below.
|
||||||
|
|
||||||
|
#if defined(__x86_64__)
|
||||||
|
#define MONTAUK_ARCH_X86_64 1
|
||||||
|
#define MONTAUK_ARCH_NAME "x86_64"
|
||||||
|
#elif defined(__aarch64__)
|
||||||
|
#define MONTAUK_ARCH_AARCH64 1
|
||||||
|
#define MONTAUK_ARCH_NAME "aarch64"
|
||||||
|
#else
|
||||||
|
#error "MontaukOS: unsupported target architecture (add one under src/Arch/)"
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
/*
|
||||||
|
* Context.hpp
|
||||||
|
* Architecture-neutral thread context interface
|
||||||
|
* Copyright (c) 2026 Daniel Hammer
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include <cstdint>
|
||||||
|
#include <Arch/Arch.hpp>
|
||||||
|
#include <Arch/Smp.hpp>
|
||||||
|
|
||||||
|
// The scheduler's view of a thread's CPU context: a kernel stack pointer
|
||||||
|
// saved by SwitchContext, an FPU/SIMD save area, and a page-table root.
|
||||||
|
|
||||||
|
#if MONTAUK_ARCH_X86_64
|
||||||
|
#include <Arch/x86_64/Context.hpp>
|
||||||
|
#elif MONTAUK_ARCH_AARCH64
|
||||||
|
#include <Arch/aarch64/Context.hpp>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// The architecture provides (in Arch/<arch>/Context.hpp):
|
||||||
|
// constexpr uint64_t Arch::FpuStateSize, Arch::FpuStateAlign
|
||||||
|
|
||||||
|
namespace Arch {
|
||||||
|
|
||||||
|
// Reset an FPU/SIMD save area to the state a new thread starts with.
|
||||||
|
void InitFpuState(uint8_t* area);
|
||||||
|
|
||||||
|
// Build the initial kernel stack of a new thread so that the first
|
||||||
|
// SwitchContext to it "returns" into `entry`. Returns the saved SP.
|
||||||
|
uint64_t PrepareThreadStack(uint64_t kernelStackTop, void (*entry)());
|
||||||
|
|
||||||
|
// Save the running context (stack pointer to *oldSp, FPU state to
|
||||||
|
// oldFpu when non-null), switch to page-table root `newRoot` if it is
|
||||||
|
// not already active, and resume the context saved at newSp (restoring
|
||||||
|
// newFpu when non-null). Returns when the old context is resumed.
|
||||||
|
void SwitchContext(uint64_t* oldSp, uint64_t newSp, uint64_t newRoot,
|
||||||
|
uint8_t* oldFpu, uint8_t* newFpu);
|
||||||
|
|
||||||
|
// Point this CPU at the thread about to run: the kernel stack used when
|
||||||
|
// it enters the kernel from user mode, and its TLS thread pointer.
|
||||||
|
void ActivateThread(Smp::CpuData* cpu, uint64_t kernelStackTop,
|
||||||
|
uint64_t threadPointer);
|
||||||
|
|
||||||
|
// Drop to user mode at `entry` with stack `userSp`; `arg` is the first
|
||||||
|
// argument register. Interrupts are enabled in user mode.
|
||||||
|
[[noreturn]] void EnterUserMode(uint64_t entry, uint64_t userSp, uint64_t arg);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,121 @@
|
|||||||
|
/*
|
||||||
|
* Cpu.hpp
|
||||||
|
* Architecture-neutral CPU primitives
|
||||||
|
* Copyright (c) 2026 Daniel Hammer
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include <cstdint>
|
||||||
|
#include <Arch/Arch.hpp>
|
||||||
|
|
||||||
|
// Every function here is defined inline by the architecture
|
||||||
|
// (Arch/<arch>/Cpu.hpp), so the primitives cost what the raw instruction
|
||||||
|
// costs. Generic kernel code must use these instead of inline assembly.
|
||||||
|
|
||||||
|
namespace Arch {
|
||||||
|
|
||||||
|
// Saved interrupt-enable state, as returned by SaveAndDisableInterrupts.
|
||||||
|
// Opaque to generic code: only hand it back to RestoreInterrupts.
|
||||||
|
using IrqFlags = uint64_t;
|
||||||
|
|
||||||
|
// ---- Spinning and barriers ----
|
||||||
|
|
||||||
|
// Spin-loop hint (x86 PAUSE, ARM YIELD).
|
||||||
|
inline void Pause();
|
||||||
|
|
||||||
|
// Stop the compiler from caching memory across this point. Emits no
|
||||||
|
// instruction; use it in polling loops on device-written memory.
|
||||||
|
inline void CompilerBarrier() {
|
||||||
|
asm volatile("" ::: "memory");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Order all earlier stores before all later stores, including stores to
|
||||||
|
// write-combining memory (framebuffers) that must become visible.
|
||||||
|
inline void StoreFence();
|
||||||
|
|
||||||
|
// Full read/write memory barrier.
|
||||||
|
inline void MemoryFence();
|
||||||
|
|
||||||
|
// Order this CPU's earlier writes to DMA memory (descriptors, buffers)
|
||||||
|
// before a later write the device acts on: the descriptor's valid/cycle
|
||||||
|
// bit, or a doorbell register. x86 keeps stores in order already.
|
||||||
|
inline void DmaWriteFence();
|
||||||
|
|
||||||
|
// Order the read of a device-written valid/cycle/status field before the
|
||||||
|
// reads of the data it guards.
|
||||||
|
inline void DmaReadFence();
|
||||||
|
|
||||||
|
// ---- Interrupt state ----
|
||||||
|
|
||||||
|
inline void EnableInterrupts();
|
||||||
|
inline void DisableInterrupts();
|
||||||
|
inline bool InterruptsEnabled();
|
||||||
|
|
||||||
|
// Disable interrupts and return the previous state.
|
||||||
|
inline IrqFlags SaveAndDisableInterrupts();
|
||||||
|
|
||||||
|
// Restore an interrupt state returned by SaveAndDisableInterrupts.
|
||||||
|
inline void RestoreInterrupts(IrqFlags flags);
|
||||||
|
|
||||||
|
// ---- Halting ----
|
||||||
|
|
||||||
|
// Wait for the next interrupt. Interrupt state is left as it was; with
|
||||||
|
// interrupts disabled this only returns on an NMI-class event.
|
||||||
|
inline void Halt();
|
||||||
|
|
||||||
|
// Called with interrupts disabled: atomically enable them and wait for
|
||||||
|
// an interrupt, then disable them again before returning. The atomicity
|
||||||
|
// closes the race where a wakeup IRQ lands between the readiness check
|
||||||
|
// and the wait, leaving the CPU asleep until the next timer deadline.
|
||||||
|
inline void HaltWithInterruptsDisabled();
|
||||||
|
|
||||||
|
// Disable interrupts and stop this CPU for good.
|
||||||
|
[[noreturn]] inline void HaltForever();
|
||||||
|
|
||||||
|
// ---- Time and delays ----
|
||||||
|
|
||||||
|
// A free-running, monotonically increasing cycle counter (x86 TSC, ARM
|
||||||
|
// CNTVCT). Its rate is architecture-defined; use it for entropy and
|
||||||
|
// relative timing, not wall-clock time.
|
||||||
|
inline uint64_t ReadTimestamp();
|
||||||
|
|
||||||
|
// Delay for at least about one microsecond. Works with interrupts
|
||||||
|
// disabled and before any clock is calibrated; errs long.
|
||||||
|
inline void IoDelay();
|
||||||
|
|
||||||
|
// ---- Caches and translation ----
|
||||||
|
|
||||||
|
// Write back and invalidate all CPU caches (before power state changes).
|
||||||
|
inline void WritebackInvalidateCaches();
|
||||||
|
|
||||||
|
// Cache maintenance for devices whose DMA does not snoop the CPU caches
|
||||||
|
// (ACPI _CCA = 0). Clean: write dirty lines back so the device reads
|
||||||
|
// what the CPU wrote. Invalidate: write back and discard, so the CPU
|
||||||
|
// then reads what the device wrote. No-ops where DMA is coherent (x86).
|
||||||
|
inline void CleanDataCache(const void* address, uint64_t size);
|
||||||
|
inline void InvalidateDataCache(const void* address, uint64_t size);
|
||||||
|
|
||||||
|
// Make instructions just written to [address, address+size) (any
|
||||||
|
// mapping of them) visible to instruction fetch on every CPU. A no-op
|
||||||
|
// where instruction fetch snoops data writes (x86).
|
||||||
|
inline void SyncInstructionCache(const void* address, uint64_t size);
|
||||||
|
|
||||||
|
// Invalidate this CPU's translation for one virtual page.
|
||||||
|
inline void FlushTlbPage(uint64_t virtualAddress);
|
||||||
|
|
||||||
|
// Invalidate this CPU's non-global translations.
|
||||||
|
inline void FlushTlbAll();
|
||||||
|
|
||||||
|
// Physical address of the active page-table root (x86 CR3, ARM TTBR0).
|
||||||
|
inline uint64_t ReadPageTableRoot();
|
||||||
|
|
||||||
|
// Switch to the page-table root at the given physical address.
|
||||||
|
inline void WritePageTableRoot(uint64_t rootPhys);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#if MONTAUK_ARCH_X86_64
|
||||||
|
#include <Arch/x86_64/Cpu.hpp>
|
||||||
|
#elif MONTAUK_ARCH_AARCH64
|
||||||
|
#include <Arch/aarch64/Cpu.hpp>
|
||||||
|
#endif
|
||||||
@@ -1,14 +1,18 @@
|
|||||||
/*
|
/*
|
||||||
* CpuPower.hpp
|
* CpuPower.hpp
|
||||||
* Intel CPU power management: HWP frequency scaling, C1E promotion,
|
* Architecture-neutral CPU power/thermal management interface
|
||||||
* and the package thermal governor.
|
|
||||||
* Copyright (c) 2026 Daniel Hammer
|
* Copyright (c) 2026 Daniel Hammer
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
|
||||||
namespace Hal {
|
// Frequency scaling, idle-state policy and the thermal governor are CPU
|
||||||
|
// specific (x86_64: Intel HWP, C1E promotion and a package-temperature
|
||||||
|
// governor driven through MSRs). An architecture without them implements
|
||||||
|
// these as no-ops and returns false from GetSnapshot.
|
||||||
|
|
||||||
|
namespace Arch {
|
||||||
namespace CpuPower {
|
namespace CpuPower {
|
||||||
|
|
||||||
// Snapshot of the power/thermal state for SYS_POWERINFO and logging.
|
// Snapshot of the power/thermal state for SYS_POWERINFO and logging.
|
||||||
@@ -24,6 +28,7 @@ namespace Hal {
|
|||||||
uint32_t BaseMHz; // CPUID base frequency (0 = unknown)
|
uint32_t BaseMHz; // CPUID base frequency (0 = unknown)
|
||||||
uint32_t MaxMHz; // CPUID max turbo frequency
|
uint32_t MaxMHz; // CPUID max turbo frequency
|
||||||
uint32_t EffMHz; // measured average active frequency (BSP)
|
uint32_t EffMHz; // measured average active frequency (BSP)
|
||||||
|
uint8_t ApIdleHint; // idle state secondary CPUs enter (arch-defined)
|
||||||
};
|
};
|
||||||
|
|
||||||
// BSP: detect features, enable HWP with a balanced energy preference,
|
// BSP: detect features, enable HWP with a balanced energy preference,
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
/*
|
||||||
|
* Interrupts.hpp
|
||||||
|
* Architecture-neutral hardware interrupt interface
|
||||||
|
* Copyright (c) 2026 Daniel Hammer
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
// Devices and kernel services see interrupts as numbered IRQ slots. The
|
||||||
|
// architecture maps each slot onto its interrupt controller (x86: an IDT
|
||||||
|
// vector fed by the I/O APIC, MSIs and local APIC IPIs; ARM: a GIC INTID)
|
||||||
|
// and calls the registered handler.
|
||||||
|
|
||||||
|
namespace Arch {
|
||||||
|
|
||||||
|
// IRQ handler function type. The parameter is the IRQ slot number.
|
||||||
|
// `fromUser` is derived directly from the interrupted context. Do not
|
||||||
|
// cache it in per-CPU data: an IRQ handler may context-switch and later
|
||||||
|
// resume on another CPU, making per-CPU saved-origin state stale.
|
||||||
|
using IrqHandler = void(*)(uint8_t irq, bool fromUser);
|
||||||
|
|
||||||
|
// Number of IRQ slots: 0-23 platform interrupt lines, 24-45 message-
|
||||||
|
// signalled interrupts (one slot per MSI-capable driver), 46-47 IPIs.
|
||||||
|
constexpr int IRQ_COUNT = 48;
|
||||||
|
|
||||||
|
// The architecture's periodic scheduler/timekeeping timer.
|
||||||
|
constexpr uint8_t IRQ_TIMER = 0;
|
||||||
|
|
||||||
|
// Platform interrupt lines with PC/ISA meaning. Only drivers for PC
|
||||||
|
// devices (PS/2, legacy PCI INTx through the I/O APIC) use these.
|
||||||
|
constexpr uint8_t IRQ_KEYBOARD = 1;
|
||||||
|
constexpr uint8_t IRQ_CASCADE = 2;
|
||||||
|
constexpr uint8_t IRQ_COM2 = 3;
|
||||||
|
constexpr uint8_t IRQ_COM1 = 4;
|
||||||
|
constexpr uint8_t IRQ_FLOPPY = 6;
|
||||||
|
constexpr uint8_t IRQ_RTC = 8;
|
||||||
|
constexpr uint8_t IRQ_MOUSE = 12;
|
||||||
|
constexpr uint8_t IRQ_ATA1 = 14;
|
||||||
|
constexpr uint8_t IRQ_ATA2 = 15;
|
||||||
|
|
||||||
|
// Inter-processor interrupts.
|
||||||
|
constexpr uint8_t IRQ_TLB_SHOOTDOWN = 46;
|
||||||
|
constexpr uint8_t IRQ_RESCHEDULE = 47;
|
||||||
|
|
||||||
|
// Register the handler for an IRQ slot.
|
||||||
|
void RegisterIrqHandler(uint8_t irq, IrqHandler handler);
|
||||||
|
|
||||||
|
// Route platform interrupt line `irq` to IRQ slot `irq` on the boot CPU,
|
||||||
|
// applying the firmware's polarity/trigger overrides, and unmask it.
|
||||||
|
void RouteLegacyIrq(uint8_t irq);
|
||||||
|
|
||||||
|
// Unmask platform interrupt line `irq` (already routed at boot).
|
||||||
|
void EnableLegacyIrq(uint8_t irq);
|
||||||
|
|
||||||
|
// Whether the platform interrupt lines are wired by the hardware (x86:
|
||||||
|
// ISA IRQs and I/O APIC pins, as the firmware programs them into PCI
|
||||||
|
// Interrupt Line registers). Where they are not (ARM), the PCI layer
|
||||||
|
// assigns lines to PCI INTx pins from ACPI _PRT with AssignPlatformLine.
|
||||||
|
bool PlatformLinesAreFixed();
|
||||||
|
|
||||||
|
// Make platform line `irq` (1-23) deliver global system interrupt `gsi`
|
||||||
|
// (ACPI numbering) with the given trigger mode and polarity, so that
|
||||||
|
// EnableLegacyIrq(irq) unmasks it. Only where !PlatformLinesAreFixed().
|
||||||
|
bool AssignPlatformLine(uint8_t irq, uint32_t gsi, bool levelTriggered, bool activeLow);
|
||||||
|
|
||||||
|
// A message-signalled interrupt targeting IRQ slot `irq` on the boot
|
||||||
|
// CPU: the device writes `data` to `address` to raise it. Program these
|
||||||
|
// into a PCI MSI capability or MSI-X table entry. Returns false when the
|
||||||
|
// platform cannot receive MSIs (drivers then fall back to INTx/polling).
|
||||||
|
struct MsiMessage {
|
||||||
|
uint64_t address;
|
||||||
|
uint32_t data;
|
||||||
|
};
|
||||||
|
bool ComposeMsi(uint8_t irq, MsiMessage& out);
|
||||||
|
|
||||||
|
// Raise IPI slot `irq` (IRQ_RESCHEDULE / IRQ_TLB_SHOOTDOWN) on the CPU
|
||||||
|
// with the given Smp index. Returns false if delivery did not complete.
|
||||||
|
bool SendIpi(int cpuIndex, uint8_t irq);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
/*
|
||||||
|
* Platform.hpp
|
||||||
|
* Architecture hooks for kernel bring-up and power-off paths
|
||||||
|
* Copyright (c) 2026 Daniel Hammer
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include <cstdint>
|
||||||
|
#include <ACPI/ACPI.hpp>
|
||||||
|
|
||||||
|
// kmain (Boot/Main.cpp) owns the boot sequence and calls into the
|
||||||
|
// architecture at these points, in this order.
|
||||||
|
|
||||||
|
namespace Arch {
|
||||||
|
|
||||||
|
// First thing after the boot console exists: CPU descriptor tables,
|
||||||
|
// FPU/SIMD enablement. No memory allocation is available yet.
|
||||||
|
void EarlyInitialize();
|
||||||
|
|
||||||
|
// The frame allocator and heap exist: install exception/trap vectors.
|
||||||
|
void InitializeTraps();
|
||||||
|
|
||||||
|
// The kernel page tables are live: program memory-type attributes that
|
||||||
|
// MapWC/MapMMIO rely on (x86 PAT).
|
||||||
|
void InitializeMemoryAttributes();
|
||||||
|
|
||||||
|
// ACPI tables are available: bring up interrupt controllers, the boot
|
||||||
|
// CPU's per-CPU data (Smp::InitBsp), idle-state and power management.
|
||||||
|
// Interrupts stay disabled; kmain enables them afterwards.
|
||||||
|
void InitializeInterrupts(Hal::ACPI::CommonSDTHeader* xsdt);
|
||||||
|
|
||||||
|
// Platform devices that are not discovered on PCI (x86: the PS/2
|
||||||
|
// controller, keyboard and mouse). Called once the timer is running.
|
||||||
|
void InitializePlatformDevices();
|
||||||
|
|
||||||
|
// Right before the first user process is created: anything user-mode
|
||||||
|
// entry needs (x86: load the task register for ring-3 interrupt stacks).
|
||||||
|
void PrepareUserMode();
|
||||||
|
|
||||||
|
// Secondary CPUs are up and the first process is about to be spawned.
|
||||||
|
void LateInitialize();
|
||||||
|
|
||||||
|
// Report devices that belong to the platform rather than a bus (for
|
||||||
|
// SYS_DEVLIST): interrupt controllers (category 1), timers (2) and
|
||||||
|
// platform input devices (3).
|
||||||
|
using PlatformDeviceSink = void (*)(void* context, uint8_t category,
|
||||||
|
const char* name, const char* detail);
|
||||||
|
void ListPlatformDevices(PlatformDeviceSink sink, void* context);
|
||||||
|
|
||||||
|
// Reset the machine without firmware help (last resort after EFI
|
||||||
|
// ResetSystem). Never returns.
|
||||||
|
[[noreturn]] void Reboot();
|
||||||
|
|
||||||
|
// Switch the machine off where ACPI S5 does not apply (hardware-reduced
|
||||||
|
// ACPI: ARM's PSCI SYSTEM_OFF). Returns if the platform cannot.
|
||||||
|
void PowerOff();
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
/*
|
||||||
|
* Smp.hpp
|
||||||
|
* Architecture-neutral per-CPU data and SMP bring-up interface
|
||||||
|
* Copyright (c) 2026 Daniel Hammer
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include <cstdint>
|
||||||
|
#include <Arch/Arch.hpp>
|
||||||
|
#include <Boot/BootInfo.hpp>
|
||||||
|
|
||||||
|
#if MONTAUK_ARCH_X86_64
|
||||||
|
#include <Arch/x86_64/PerCpu.hpp>
|
||||||
|
#elif MONTAUK_ARCH_AARCH64
|
||||||
|
#include <Arch/aarch64/PerCpu.hpp>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// The architecture provides (in Arch/<arch>/PerCpu.hpp):
|
||||||
|
// struct Arch::CpuEntryState -- fields kernel-entry assembly reads at a
|
||||||
|
// fixed offset (8) from the per-CPU base
|
||||||
|
// struct Arch::CpuState -- everything else the port keeps per CPU
|
||||||
|
// void* Arch::CurrentCpuLocal() -- this CPU's CpuData (fast path)
|
||||||
|
// void* Arch::TryCurrentCpuLocal() -- same, or nullptr before InitBsp
|
||||||
|
|
||||||
|
namespace Smp {
|
||||||
|
|
||||||
|
static constexpr int MaxCPUs = 64;
|
||||||
|
|
||||||
|
struct CpuData {
|
||||||
|
// === Fixed layout: read by low-level entry code ===
|
||||||
|
uint64_t selfPtr; // offset 0: pointer to self
|
||||||
|
Arch::CpuEntryState entry; // offset 8: arch kernel-entry fields
|
||||||
|
|
||||||
|
// === Generic fields ===
|
||||||
|
int32_t currentSlot; // process table slot, -1 = idle
|
||||||
|
int cpuIndex; // 0-based CPU index (0 = boot CPU)
|
||||||
|
uint64_t idleSavedSp; // idle context's stack pointer while a process runs
|
||||||
|
volatile bool started; // set by an AP once its init is complete
|
||||||
|
|
||||||
|
// When set, the scheduler tick must NOT pull this (idle) CPU away to
|
||||||
|
// run a ready process: its idle context is busy with long-running
|
||||||
|
// kernel work whose wall-clock-bounded waits would spuriously expire
|
||||||
|
// if the context only ran when nothing else is ready (observed: the
|
||||||
|
// deferred Bluetooth firmware download racing login-screen startup).
|
||||||
|
// Other CPUs schedule normally while this is held.
|
||||||
|
volatile bool reservedForKernelWork = false;
|
||||||
|
|
||||||
|
// Set by the BSP timer when an IRQ bottom half needs process-safe
|
||||||
|
// execution even though runnable user processes keep this CPU busy.
|
||||||
|
// Schedule() consumes it by yielding once to the idle kernel context.
|
||||||
|
bool kernelWorkPending = false;
|
||||||
|
|
||||||
|
// === Architecture-private state ===
|
||||||
|
Arch::CpuState arch;
|
||||||
|
};
|
||||||
|
|
||||||
|
static_assert(__builtin_offsetof(CpuData, selfPtr) == 0,
|
||||||
|
"CpuData: per-CPU base must point at selfPtr");
|
||||||
|
static_assert(__builtin_offsetof(CpuData, entry) == 8,
|
||||||
|
"CpuData: entry-assembly fields must start at offset 8");
|
||||||
|
|
||||||
|
// The current CPU's data.
|
||||||
|
inline CpuData* GetCurrentCpuData() {
|
||||||
|
return (CpuData*)Arch::CurrentCpuLocal();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exception paths may run before InitBsp() has installed the per-CPU
|
||||||
|
// base. This variant returns nullptr in that state instead of faulting
|
||||||
|
// recursively on a bogus base.
|
||||||
|
inline CpuData* TryGetCurrentCpuData() {
|
||||||
|
return (CpuData*)Arch::TryCurrentCpuLocal();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get CPU data by index
|
||||||
|
CpuData* GetCpuData(int index);
|
||||||
|
|
||||||
|
// Number of online CPUs
|
||||||
|
int GetCpuCount();
|
||||||
|
|
||||||
|
// Number of CPUs the firmware describes (available once interrupts are
|
||||||
|
// initialized; may exceed GetCpuCount if some failed to start).
|
||||||
|
// Implemented by the architecture.
|
||||||
|
int GetDetectedCpuCount();
|
||||||
|
|
||||||
|
// Initialize the boot CPU's per-CPU data (call before interrupts are
|
||||||
|
// enabled). Implemented by the architecture.
|
||||||
|
void InitBsp();
|
||||||
|
|
||||||
|
// Boot all secondary CPUs (call after all subsystems are ready).
|
||||||
|
// `smp` is the boot contract's SMP block; if FeatureSmp was not
|
||||||
|
// advertised the caller may still pass it (cpuCount<=1 is a no-op).
|
||||||
|
// Implemented by the architecture.
|
||||||
|
void BootAPs(const montauk::boot::SmpInfo& smp);
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
/*
|
||||||
|
* Syscall.hpp
|
||||||
|
* Architecture-neutral system-call entry interface
|
||||||
|
* Copyright (c) 2026 Daniel Hammer
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include <cstdint>
|
||||||
|
#include <Arch/Arch.hpp>
|
||||||
|
|
||||||
|
#if MONTAUK_ARCH_X86_64
|
||||||
|
#include <Arch/x86_64/SyscallFrame.hpp>
|
||||||
|
#elif MONTAUK_ARCH_AARCH64
|
||||||
|
#include <Arch/aarch64/SyscallFrame.hpp>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// The architecture's system-call entry code saves the user context into an
|
||||||
|
// Arch::SyscallFrame on the thread's kernel stack (Arch/<arch>/
|
||||||
|
// SyscallFrame.hpp defines it, exposing at least `syscall_nr` and `arg1` ..
|
||||||
|
// `arg6`), enables interrupts, and calls
|
||||||
|
//
|
||||||
|
// extern "C" int64_t SyscallDispatch(Arch::SyscallFrame* frame);
|
||||||
|
//
|
||||||
|
// whose result it returns to user mode. Before returning it calls
|
||||||
|
//
|
||||||
|
// extern "C" void SchedExitIfKilled();
|
||||||
|
//
|
||||||
|
// so a kill that arrived during the syscall takes effect at a point where
|
||||||
|
// the syscall holds no locks.
|
||||||
|
|
||||||
|
namespace Arch {
|
||||||
|
|
||||||
|
// Install the system-call entry point on the calling CPU. Called on the
|
||||||
|
// boot CPU by montauk::abi::InitializeSyscalls and by every secondary
|
||||||
|
// CPU during its bring-up.
|
||||||
|
void InitializeSyscallEntry();
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
/*
|
||||||
|
* Timer.hpp
|
||||||
|
* Architecture-neutral per-CPU tick timer interface
|
||||||
|
* Copyright (c) 2026 Daniel Hammer
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
// The hardware behind Timekeeping (Timekeeping/Timer.hpp): a per-CPU timer
|
||||||
|
// that raises Arch::IRQ_TIMER periodically. Timekeeping registers the IRQ
|
||||||
|
// handler and owns all tick accounting; the architecture only calibrates
|
||||||
|
// and programs the hardware (x86: local APIC timer calibrated against the
|
||||||
|
// PIT; ARM: the generic timer).
|
||||||
|
|
||||||
|
namespace Arch::Timer {
|
||||||
|
|
||||||
|
// Calibrate the timer and start the calling (boot) CPU's periodic tick
|
||||||
|
// with the given period. Returns false if no usable timer was found.
|
||||||
|
bool InitializeBsp(uint32_t periodMs);
|
||||||
|
|
||||||
|
// (Re)start the calling CPU's periodic tick, reusing the boot CPU's
|
||||||
|
// calibration: secondary CPU bring-up, leaving idle, and the boot CPU
|
||||||
|
// after S3 resume (timer state does not survive suspend). Only called
|
||||||
|
// after InitializeBsp succeeded.
|
||||||
|
void StartPeriodic(uint32_t periodMs);
|
||||||
|
|
||||||
|
// Mask the calling CPU's tick (idle secondary CPUs rely on IPIs).
|
||||||
|
// Cheap when it is already masked.
|
||||||
|
void Stop();
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
/*
|
||||||
|
* Abi.hpp
|
||||||
|
* aarch64 user-mode ABI details (implements Arch/Abi.hpp)
|
||||||
|
* Copyright (c) 2026 Daniel Hammer
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
// UEFI on AArch64 uses the standard AAPCS64 calling convention.
|
||||||
|
#define ARCH_EFIAPI
|
||||||
|
|
||||||
|
namespace Arch {
|
||||||
|
|
||||||
|
constexpr uint16_t ElfMachine = 183; // EM_AARCH64
|
||||||
|
constexpr const char* ElfMachineName = "aarch64";
|
||||||
|
|
||||||
|
inline RelocationKind ClassifyRelocation(uint32_t type) {
|
||||||
|
switch (type) {
|
||||||
|
case 0: return RelocationKind::None;
|
||||||
|
case 1027: return RelocationKind::Relative; // R_AARCH64_RELATIVE
|
||||||
|
case 257: // R_AARCH64_ABS64
|
||||||
|
case 1025: // R_AARCH64_GLOB_DAT
|
||||||
|
case 1026: return RelocationKind::Symbol; // R_AARCH64_JUMP_SLOT
|
||||||
|
default: return RelocationKind::Unsupported;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Variant I: TP points at a 16-byte TCB and the TLS block follows it,
|
||||||
|
// starting at the next p_align boundary.
|
||||||
|
inline TlsLayout ComputeTlsLayout(uint64_t memSize, uint64_t pAlign) {
|
||||||
|
uint64_t align = pAlign ? pAlign : 1;
|
||||||
|
uint64_t templateOffset = (16 + align - 1) & ~(align - 1);
|
||||||
|
return {templateOffset, 0, templateOffset + memSize};
|
||||||
|
}
|
||||||
|
|
||||||
|
// System calls: number in x8, arguments in x0-x5, `svc #0`.
|
||||||
|
inline void WriteExitStub(uint8_t* page) {
|
||||||
|
uint32_t* code = (uint32_t*)page;
|
||||||
|
code[0] = 0xD2800000; // mov x0, #0 (exit code 0)
|
||||||
|
code[1] = 0xD2800008; // mov x8, #0 (SYS_EXIT = 0)
|
||||||
|
code[2] = 0xD4000001; // svc #0
|
||||||
|
}
|
||||||
|
|
||||||
|
// The return address travels in x30, not on the stack: EnterUserMode
|
||||||
|
// points x30 at the exit stub. The stack only needs 16-byte alignment.
|
||||||
|
inline uint64_t SetupUserEntryStack(uint8_t*, uint64_t stackTop, uint64_t) {
|
||||||
|
return stackTop & ~0xFULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline uint64_t ThreadEntryStackPointer(uint64_t stackTop) {
|
||||||
|
return stackTop & ~0xFULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
/*
|
||||||
|
* AcpiSleep.cpp
|
||||||
|
* aarch64: no ACPI S3 (implements ACPI/AcpiSleep.hpp)
|
||||||
|
* Copyright (c) 2026 Daniel Hammer
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <ACPI/AcpiSleep.hpp>
|
||||||
|
#include <Common/StatusCodes.hpp>
|
||||||
|
|
||||||
|
// Suspend on ARM goes through PSCI (SYSTEM_SUSPEND), not the FACS waking
|
||||||
|
// vector; until that exists, S3 is reported as unavailable.
|
||||||
|
|
||||||
|
namespace Hal::AcpiSleep {
|
||||||
|
|
||||||
|
void Initialize(ACPI::CommonSDTHeader*) {
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsS3Available() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int Suspend() {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
/*
|
||||||
|
* Bringup.hpp
|
||||||
|
* aarch64 per-CPU bring-up shared by the boot CPU and secondaries
|
||||||
|
* (private to the port)
|
||||||
|
* Copyright (c) 2026 Daniel Hammer
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace Arch {
|
||||||
|
|
||||||
|
// System registers every CPU programs for itself: SP_EL1 as the stack,
|
||||||
|
// the exception vectors, SCTLR_EL1 policy bits, FP/SIMD access.
|
||||||
|
// Platform.cpp.
|
||||||
|
void InitializeCpuRegisters();
|
||||||
|
|
||||||
|
// Load the kernel's translation regime (MAIR, TCR, TTBR1 kernel half,
|
||||||
|
// TTBR0 kernel-context table) on the calling CPU. Paging::Init builds
|
||||||
|
// the tables and calls it on the boot CPU. Paging.cpp.
|
||||||
|
void ActivateKernelTranslation();
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,144 @@
|
|||||||
|
/*
|
||||||
|
Context.S
|
||||||
|
aarch64 context switch and first entry to EL0 (see Arch/Context.hpp)
|
||||||
|
Copyright (c) 2026 Daniel Hammer
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* The kernel is built -mgeneral-regs-only; this file alone touches
|
||||||
|
the FP/SIMD registers, to save and restore user state. */
|
||||||
|
.arch_extension fp
|
||||||
|
.arch_extension simd
|
||||||
|
|
||||||
|
.text
|
||||||
|
|
||||||
|
/*
|
||||||
|
* void ArchContextSwitch(uint64_t* oldSp, uint64_t newSp, uint64_t newRoot,
|
||||||
|
* uint8_t* oldFpu, uint8_t* newFpu)
|
||||||
|
* x0 = where to save the old SP x1 = SP to resume
|
||||||
|
* x2 = TTBR0 root for the new side x3/x4 = FP/SIMD save areas (may be 0)
|
||||||
|
*
|
||||||
|
* Frame on the kernel stack (Context.cpp's PrepareThreadStack builds the
|
||||||
|
* same shape): x19..x28, x29, x30 -- 96 bytes. The kernel is built with
|
||||||
|
* -mgeneral-regs-only, so q0-q31/FPCR/FPSR only ever hold user state.
|
||||||
|
*/
|
||||||
|
.global ArchContextSwitch
|
||||||
|
.type ArchContextSwitch, %function
|
||||||
|
ArchContextSwitch:
|
||||||
|
cbz x3, 1f
|
||||||
|
stp q0, q1, [x3, #0]
|
||||||
|
stp q2, q3, [x3, #32]
|
||||||
|
stp q4, q5, [x3, #64]
|
||||||
|
stp q6, q7, [x3, #96]
|
||||||
|
stp q8, q9, [x3, #128]
|
||||||
|
stp q10, q11, [x3, #160]
|
||||||
|
stp q12, q13, [x3, #192]
|
||||||
|
stp q14, q15, [x3, #224]
|
||||||
|
stp q16, q17, [x3, #256]
|
||||||
|
stp q18, q19, [x3, #288]
|
||||||
|
stp q20, q21, [x3, #320]
|
||||||
|
stp q22, q23, [x3, #352]
|
||||||
|
stp q24, q25, [x3, #384]
|
||||||
|
stp q26, q27, [x3, #416]
|
||||||
|
stp q28, q29, [x3, #448]
|
||||||
|
stp q30, q31, [x3, #480]
|
||||||
|
mrs x9, fpcr
|
||||||
|
mrs x10, fpsr
|
||||||
|
add x11, x3, #512
|
||||||
|
stp x9, x10, [x11]
|
||||||
|
1:
|
||||||
|
sub sp, sp, #96
|
||||||
|
stp x19, x20, [sp, #0]
|
||||||
|
stp x21, x22, [sp, #16]
|
||||||
|
stp x23, x24, [sp, #32]
|
||||||
|
stp x25, x26, [sp, #48]
|
||||||
|
stp x27, x28, [sp, #64]
|
||||||
|
stp x29, x30, [sp, #80]
|
||||||
|
mov x9, sp
|
||||||
|
str x9, [x0]
|
||||||
|
|
||||||
|
mov sp, x1
|
||||||
|
|
||||||
|
/* Switch the process half only when it differs (no ASIDs yet, so a
|
||||||
|
switch invalidates this CPU's TLB). */
|
||||||
|
mrs x9, ttbr0_el1
|
||||||
|
and x9, x9, #0x0000fffffffffffe
|
||||||
|
cmp x9, x2
|
||||||
|
b.eq 2f
|
||||||
|
dsb ishst
|
||||||
|
msr ttbr0_el1, x2
|
||||||
|
isb
|
||||||
|
tlbi vmalle1
|
||||||
|
dsb nsh
|
||||||
|
isb
|
||||||
|
2:
|
||||||
|
ldp x19, x20, [sp, #0]
|
||||||
|
ldp x21, x22, [sp, #16]
|
||||||
|
ldp x23, x24, [sp, #32]
|
||||||
|
ldp x25, x26, [sp, #48]
|
||||||
|
ldp x27, x28, [sp, #64]
|
||||||
|
ldp x29, x30, [sp, #80]
|
||||||
|
add sp, sp, #96
|
||||||
|
|
||||||
|
cbz x4, 3f
|
||||||
|
ldp q0, q1, [x4, #0]
|
||||||
|
ldp q2, q3, [x4, #32]
|
||||||
|
ldp q4, q5, [x4, #64]
|
||||||
|
ldp q6, q7, [x4, #96]
|
||||||
|
ldp q8, q9, [x4, #128]
|
||||||
|
ldp q10, q11, [x4, #160]
|
||||||
|
ldp q12, q13, [x4, #192]
|
||||||
|
ldp q14, q15, [x4, #224]
|
||||||
|
ldp q16, q17, [x4, #256]
|
||||||
|
ldp q18, q19, [x4, #288]
|
||||||
|
ldp q20, q21, [x4, #320]
|
||||||
|
ldp q22, q23, [x4, #352]
|
||||||
|
ldp q24, q25, [x4, #384]
|
||||||
|
ldp q26, q27, [x4, #416]
|
||||||
|
ldp q28, q29, [x4, #448]
|
||||||
|
ldp q30, q31, [x4, #480]
|
||||||
|
add x11, x4, #512
|
||||||
|
ldp x9, x10, [x11]
|
||||||
|
msr fpcr, x9
|
||||||
|
msr fpsr, x10
|
||||||
|
3:
|
||||||
|
ret
|
||||||
|
.size ArchContextSwitch, . - ArchContextSwitch
|
||||||
|
|
||||||
|
/*
|
||||||
|
* void ArchEnterUserMode(uint64_t entry, uint64_t userSp, uint64_t arg,
|
||||||
|
* uint64_t returnAddress, uint64_t kernelStackTop)
|
||||||
|
*
|
||||||
|
* Drops to EL0t with interrupts unmasked. The kernel stack is reset to its
|
||||||
|
* top: SP_EL1 is what the next exception from EL0 runs on.
|
||||||
|
*/
|
||||||
|
.global ArchEnterUserMode
|
||||||
|
.type ArchEnterUserMode, %function
|
||||||
|
ArchEnterUserMode:
|
||||||
|
msr daifset, #0xf
|
||||||
|
mov sp, x4
|
||||||
|
msr elr_el1, x0
|
||||||
|
msr sp_el0, x1
|
||||||
|
msr spsr_el1, xzr /* EL0t, DAIF clear */
|
||||||
|
mov x0, x2
|
||||||
|
mov x30, x3
|
||||||
|
mov x1, xzr
|
||||||
|
mov x2, xzr
|
||||||
|
mov x3, xzr
|
||||||
|
mov x4, xzr
|
||||||
|
mov x5, xzr
|
||||||
|
mov x6, xzr
|
||||||
|
mov x7, xzr
|
||||||
|
mov x8, xzr
|
||||||
|
mov x9, xzr
|
||||||
|
mov x10, xzr
|
||||||
|
mov x11, xzr
|
||||||
|
mov x12, xzr
|
||||||
|
mov x13, xzr
|
||||||
|
mov x14, xzr
|
||||||
|
mov x15, xzr
|
||||||
|
mov x16, xzr
|
||||||
|
mov x17, xzr
|
||||||
|
mov x18, xzr
|
||||||
|
mov x29, xzr
|
||||||
|
eret
|
||||||
|
.size ArchEnterUserMode, . - ArchEnterUserMode
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
/*
|
||||||
|
* Context.cpp
|
||||||
|
* aarch64 thread context (implements Arch/Context.hpp)
|
||||||
|
* Copyright (c) 2026 Daniel Hammer
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <Arch/Context.hpp>
|
||||||
|
#include <Libraries/Memory.hpp>
|
||||||
|
#include <Sched/Scheduler.hpp>
|
||||||
|
|
||||||
|
// Context.S
|
||||||
|
extern "C" void ArchContextSwitch(uint64_t* oldSp, uint64_t newSp, uint64_t newRoot,
|
||||||
|
uint8_t* oldFpu, uint8_t* newFpu);
|
||||||
|
extern "C" [[noreturn]] void ArchEnterUserMode(uint64_t entry, uint64_t userSp, uint64_t arg,
|
||||||
|
uint64_t returnAddress, uint64_t kernelStackTop);
|
||||||
|
|
||||||
|
namespace Arch {
|
||||||
|
|
||||||
|
void InitFpuState(uint8_t* area) {
|
||||||
|
// All-zero is the architectural reset state: round-to-nearest, no
|
||||||
|
// exceptions trapped, no flags raised.
|
||||||
|
memset(area, 0, FpuStateSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t PrepareThreadStack(uint64_t kernelStackTop, void (*entry)()) {
|
||||||
|
// The frame Context.S pops: x19..x28, x29, x30 (= return address).
|
||||||
|
uint64_t* sp = (uint64_t*)kernelStackTop - 12;
|
||||||
|
for (int i = 0; i < 11; i++) {
|
||||||
|
sp[i] = 0;
|
||||||
|
}
|
||||||
|
sp[11] = (uint64_t)entry;
|
||||||
|
return (uint64_t)sp;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SwitchContext(uint64_t* oldSp, uint64_t newSp, uint64_t newRoot,
|
||||||
|
uint8_t* oldFpu, uint8_t* newFpu) {
|
||||||
|
ArchContextSwitch(oldSp, newSp, newRoot, oldFpu, newFpu);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ActivateThread(Smp::CpuData* cpu, uint64_t kernelStackTop,
|
||||||
|
uint64_t threadPointer) {
|
||||||
|
cpu->entry.kernelStackTop = kernelStackTop;
|
||||||
|
asm volatile("msr tpidr_el0, %0" :: "r"(threadPointer));
|
||||||
|
}
|
||||||
|
|
||||||
|
void EnterUserMode(uint64_t entry, uint64_t userSp, uint64_t arg) {
|
||||||
|
// Returning from the entry function lands in the exit stub (see
|
||||||
|
// SetupUserEntryStack in Abi.hpp).
|
||||||
|
ArchEnterUserMode(entry, userSp, arg, Sched::ExitStubAddr,
|
||||||
|
Smp::GetCurrentCpuData()->entry.kernelStackTop);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
/*
|
||||||
|
* Context.hpp
|
||||||
|
* aarch64 thread context constants (see Arch/Context.hpp)
|
||||||
|
* Copyright (c) 2026 Daniel Hammer
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
namespace Arch {
|
||||||
|
// q0-q31 followed by FPCR and FPSR (Context.S).
|
||||||
|
constexpr uint64_t FpuStateSize = 32 * 16 + 16;
|
||||||
|
constexpr uint64_t FpuStateAlign = 16;
|
||||||
|
}
|
||||||
@@ -0,0 +1,170 @@
|
|||||||
|
/*
|
||||||
|
* Cpu.hpp
|
||||||
|
* aarch64 implementation of the Arch/Cpu.hpp primitives
|
||||||
|
* Copyright (c) 2026 Daniel Hammer
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
namespace Arch {
|
||||||
|
|
||||||
|
// PSTATE.I in the DAIF view
|
||||||
|
static constexpr uint64_t DaifIrqMask = 1ULL << 7;
|
||||||
|
|
||||||
|
inline void Pause() {
|
||||||
|
asm volatile("yield" ::: "memory");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Device-nGnRE / Normal-NC (write-combining) stores complete in order
|
||||||
|
// once a DSB has been issued.
|
||||||
|
inline void StoreFence() {
|
||||||
|
asm volatile("dsb st" ::: "memory");
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void MemoryFence() {
|
||||||
|
asm volatile("dsb sy" ::: "memory");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Devices are outer-shareable observers.
|
||||||
|
inline void DmaWriteFence() {
|
||||||
|
asm volatile("dmb oshst" ::: "memory");
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void DmaReadFence() {
|
||||||
|
asm volatile("dmb oshld" ::: "memory");
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void EnableInterrupts() {
|
||||||
|
asm volatile("msr daifclr, #2" ::: "memory");
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void DisableInterrupts() {
|
||||||
|
asm volatile("msr daifset, #2" ::: "memory");
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool InterruptsEnabled() {
|
||||||
|
uint64_t daif;
|
||||||
|
asm volatile("mrs %0, daif" : "=r"(daif));
|
||||||
|
return (daif & DaifIrqMask) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline IrqFlags SaveAndDisableInterrupts() {
|
||||||
|
uint64_t daif;
|
||||||
|
asm volatile("mrs %0, daif\n\tmsr daifset, #2" : "=r"(daif) :: "memory");
|
||||||
|
return daif;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void RestoreInterrupts(IrqFlags flags) {
|
||||||
|
asm volatile("msr daif, %0" :: "r"(flags) : "memory");
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void Halt() {
|
||||||
|
asm volatile("wfi" ::: "memory");
|
||||||
|
}
|
||||||
|
|
||||||
|
// WFI wakes on a pending interrupt even while PSTATE.I masks it, so
|
||||||
|
// checking for work and then waiting with IRQs masked cannot lose a
|
||||||
|
// wakeup. Unmask briefly afterwards so the pending IRQ is taken.
|
||||||
|
inline void HaltWithInterruptsDisabled() {
|
||||||
|
asm volatile("wfi\n\tmsr daifclr, #2\n\tisb\n\tmsr daifset, #2" ::: "memory");
|
||||||
|
}
|
||||||
|
|
||||||
|
[[noreturn]] inline void HaltForever() {
|
||||||
|
for (;;) {
|
||||||
|
asm volatile("msr daifset, #0xf\n\twfi" ::: "memory");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline uint64_t ReadTimestamp() {
|
||||||
|
uint64_t value;
|
||||||
|
asm volatile("isb\n\tmrs %0, cntvct_el0" : "=r"(value) :: "memory");
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The generic timer runs from boot at CNTFRQ_EL0 Hz, so it times short
|
||||||
|
// delays without calibration.
|
||||||
|
inline void IoDelay() {
|
||||||
|
uint64_t frequency;
|
||||||
|
asm volatile("mrs %0, cntfrq_el0" : "=r"(frequency));
|
||||||
|
uint64_t ticks = frequency / 1000000 + 1;
|
||||||
|
uint64_t start = ReadTimestamp();
|
||||||
|
while (ReadTimestamp() - start < ticks) {
|
||||||
|
Pause();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ARMv8 has no "clean the whole cache" instruction: set/way maintenance
|
||||||
|
// is only safe from firmware, which PSCI performs on power transitions.
|
||||||
|
// Order outstanding accesses so the firmware call sees them.
|
||||||
|
inline void WritebackInvalidateCaches() {
|
||||||
|
asm volatile("dsb sy\n\tisb" ::: "memory");
|
||||||
|
}
|
||||||
|
|
||||||
|
// The TLBI operand is VA[55:12] in bits [43:0]; the bits above are RES0.
|
||||||
|
inline uint64_t DataCacheLineSize() {
|
||||||
|
uint64_t ctr;
|
||||||
|
asm volatile("mrs %0, ctr_el0" : "=r"(ctr));
|
||||||
|
return 4ULL << ((ctr >> 16) & 0xF); // DminLine
|
||||||
|
}
|
||||||
|
|
||||||
|
// To the point of coherency: where non-snooping device DMA looks.
|
||||||
|
inline void CleanDataCache(const void* address, uint64_t size) {
|
||||||
|
uint64_t line = DataCacheLineSize();
|
||||||
|
uint64_t end = (uint64_t)address + size;
|
||||||
|
for (uint64_t p = (uint64_t)address & ~(line - 1); p < end; p += line) {
|
||||||
|
asm volatile("dc cvac, %0" :: "r"(p) : "memory");
|
||||||
|
}
|
||||||
|
asm volatile("dsb sy" ::: "memory");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean too: a partial line at either end may hold CPU data next to the
|
||||||
|
// buffer, which a plain invalidate would throw away.
|
||||||
|
inline void InvalidateDataCache(const void* address, uint64_t size) {
|
||||||
|
uint64_t line = DataCacheLineSize();
|
||||||
|
uint64_t end = (uint64_t)address + size;
|
||||||
|
for (uint64_t p = (uint64_t)address & ~(line - 1); p < end; p += line) {
|
||||||
|
asm volatile("dc civac, %0" :: "r"(p) : "memory");
|
||||||
|
}
|
||||||
|
asm volatile("dsb sy" ::: "memory");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean the data cache to the point of unification, then invalidate
|
||||||
|
// the whole instruction cache: invalidating by address would only
|
||||||
|
// reach the alias it names on an aliasing (VIPT) instruction cache.
|
||||||
|
inline void SyncInstructionCache(const void* address, uint64_t size) {
|
||||||
|
uint64_t line = DataCacheLineSize();
|
||||||
|
uint64_t start = (uint64_t)address & ~(line - 1);
|
||||||
|
uint64_t end = (uint64_t)address + size;
|
||||||
|
for (uint64_t p = start; p < end; p += line) {
|
||||||
|
asm volatile("dc cvau, %0" :: "r"(p) : "memory");
|
||||||
|
}
|
||||||
|
asm volatile("dsb ish\n\tic ialluis\n\tdsb ish\n\tisb" ::: "memory");
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void FlushTlbPage(uint64_t virtualAddress) {
|
||||||
|
asm volatile("dsb ishst\n\ttlbi vaae1is, %0\n\tdsb ish\n\tisb"
|
||||||
|
:: "r"((virtualAddress >> 12) & ((1ULL << 44) - 1)) : "memory");
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void FlushTlbAll() {
|
||||||
|
asm volatile("dsb ishst\n\ttlbi vmalle1is\n\tdsb ish\n\tisb" ::: "memory");
|
||||||
|
}
|
||||||
|
|
||||||
|
// The process (lower) half of the address space is translated through
|
||||||
|
// TTBR0_EL1; the kernel half through TTBR1_EL1, which never changes.
|
||||||
|
static constexpr uint64_t TtbrAddressMask = 0x0000FFFFFFFFFFFEULL;
|
||||||
|
|
||||||
|
inline uint64_t ReadPageTableRoot() {
|
||||||
|
uint64_t ttbr;
|
||||||
|
asm volatile("mrs %0, ttbr0_el1" : "=r"(ttbr));
|
||||||
|
return ttbr & TtbrAddressMask;
|
||||||
|
}
|
||||||
|
|
||||||
|
// No ASIDs yet: every switch invalidates the old process's entries.
|
||||||
|
inline void WritePageTableRoot(uint64_t rootPhys) {
|
||||||
|
asm volatile("dsb ishst\n\tmsr ttbr0_el1, %0\n\tisb\n\ttlbi vmalle1\n\tdsb nsh\n\tisb"
|
||||||
|
:: "r"(rootPhys) : "memory");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
/*
|
||||||
|
* CpuPower.cpp
|
||||||
|
* aarch64 CPU power management (implements Arch/CpuPower.hpp)
|
||||||
|
* Copyright (c) 2026 Daniel Hammer
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <Arch/CpuPower.hpp>
|
||||||
|
|
||||||
|
// No frequency scaling or thermal governor yet (on ARM these come from
|
||||||
|
// ACPI CPPC / SCMI rather than CPU registers).
|
||||||
|
|
||||||
|
namespace Arch::CpuPower {
|
||||||
|
|
||||||
|
void InitializeBsp() {}
|
||||||
|
void InitializeAp() {}
|
||||||
|
void ApplyPolicyIfChanged() {}
|
||||||
|
void ThermalTick(uint64_t) {}
|
||||||
|
void ServiceDeferredDiagnostics() {}
|
||||||
|
void ReapplyAfterWake() {}
|
||||||
|
|
||||||
|
bool GetSnapshot(Snapshot& out) {
|
||||||
|
out = {};
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,184 @@
|
|||||||
|
/*
|
||||||
|
* Exceptions.cpp
|
||||||
|
* aarch64 synchronous exceptions: faults, user crashes, kernel panics
|
||||||
|
* Copyright (c) 2026 Daniel Hammer
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <Arch/aarch64/InterruptFrame.hpp>
|
||||||
|
#include <Arch/Cpu.hpp>
|
||||||
|
#include <Arch/Smp.hpp>
|
||||||
|
#include <Common/Panic.hpp>
|
||||||
|
#include <Common/TerminationCodes.hpp>
|
||||||
|
#include <CppLib/Stream.hpp>
|
||||||
|
#include <Memory/Paging.hpp>
|
||||||
|
#include <Sched/Scheduler.hpp>
|
||||||
|
#include <Sched/CrashReport.hpp>
|
||||||
|
#include <Api/Heap.hpp>
|
||||||
|
#include <Timekeeping/Timer.hpp>
|
||||||
|
|
||||||
|
using namespace Kt;
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
// ESR_EL1.EC exception classes
|
||||||
|
constexpr uint64_t EcUnknown = 0x00;
|
||||||
|
constexpr uint64_t EcIllegalState = 0x0E;
|
||||||
|
constexpr uint64_t EcInstAbortLower = 0x20;
|
||||||
|
constexpr uint64_t EcInstAbortSame = 0x21;
|
||||||
|
constexpr uint64_t EcPcAlignment = 0x22;
|
||||||
|
constexpr uint64_t EcDataAbortLower = 0x24;
|
||||||
|
constexpr uint64_t EcDataAbortSame = 0x25;
|
||||||
|
constexpr uint64_t EcSpAlignment = 0x26;
|
||||||
|
constexpr uint64_t EcFpException = 0x2C;
|
||||||
|
constexpr uint64_t EcBreakpointLower = 0x30;
|
||||||
|
constexpr uint64_t EcBrk = 0x3C;
|
||||||
|
|
||||||
|
constexpr uint64_t UserSpaceEnd = 0x0000800000000000ULL;
|
||||||
|
|
||||||
|
uint64_t ExceptionClass(const Arch::PanicFrame* frame) {
|
||||||
|
return (frame->esr >> 26) & 0x3F;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsAbort(uint64_t ec) {
|
||||||
|
return ec == EcInstAbortLower || ec == EcInstAbortSame
|
||||||
|
|| ec == EcDataAbortLower || ec == EcDataAbortSame;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fault status code (ISS[5:0]) of an abort: translation faults mean
|
||||||
|
// the page is not mapped, permission faults that it is.
|
||||||
|
bool IsTranslationFault(uint64_t esr) {
|
||||||
|
uint64_t fsc = esr & 0x3F;
|
||||||
|
return fsc >= 0x04 && fsc <= 0x07;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsPermissionFault(uint64_t esr) {
|
||||||
|
uint64_t fsc = esr & 0x3F;
|
||||||
|
return fsc >= 0x0C && fsc <= 0x0F;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* ExceptionName(uint64_t ec) {
|
||||||
|
switch (ec) {
|
||||||
|
case EcUnknown: return "Undefined Instruction";
|
||||||
|
case EcIllegalState: return "Illegal Execution State";
|
||||||
|
case EcInstAbortLower:
|
||||||
|
case EcInstAbortSame: return "Instruction Abort";
|
||||||
|
case EcPcAlignment: return "PC Alignment Fault";
|
||||||
|
case EcDataAbortLower:
|
||||||
|
case EcDataAbortSame: return "Data Abort";
|
||||||
|
case EcSpAlignment: return "SP Alignment Fault";
|
||||||
|
case EcFpException: return "Floating-Point Exception";
|
||||||
|
case EcBrk: return "Breakpoint (BRK)";
|
||||||
|
default:
|
||||||
|
if (ec >= EcBreakpointLower && ec <= 0x35) return "Debug Exception";
|
||||||
|
return "Synchronous Exception";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TerminationCodes TerminationFromClass(uint64_t ec) {
|
||||||
|
if (ec == EcUnknown || ec == EcIllegalState) return TerminatedIllegalInstruction;
|
||||||
|
if (ec == EcBrk || (ec >= EcBreakpointLower && ec <= 0x35)) return TerminatedBreakpoint;
|
||||||
|
if (ec == EcFpException) return TerminatedArithmeticError;
|
||||||
|
return TerminatedAccessViolation;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<size_t N>
|
||||||
|
void CopyName(char (&dst)[N], const char* src) {
|
||||||
|
size_t i = 0;
|
||||||
|
for (; i + 1 < N && src[i]; i++) dst[i] = src[i];
|
||||||
|
dst[i] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Kill the faulting process with a crash report. Never returns.
|
||||||
|
[[noreturn]] void TerminateUserProcess(Arch::PanicFrame* frame, uint64_t ec) {
|
||||||
|
// Teardown can wait on devices and other CPUs; keep the clock running.
|
||||||
|
Arch::EnableInterrupts();
|
||||||
|
|
||||||
|
auto* proc = Sched::GetCurrentProcessPtr();
|
||||||
|
const char* name = ExceptionName(ec);
|
||||||
|
KernelLogStream(ERROR, "Exception") << name << " in process \"" << proc->name
|
||||||
|
<< "\" (pid " << proc->pid << ", ESR 0x" << base::hex << frame->esr
|
||||||
|
<< ", PC 0x" << frame->elr << ") - process terminated";
|
||||||
|
|
||||||
|
CrashReport::Report rep{};
|
||||||
|
rep.valid = true;
|
||||||
|
rep.pid = proc->pid;
|
||||||
|
CopyName(rep.processName, proc->name);
|
||||||
|
rep.exceptionVector = (uint8_t)ec;
|
||||||
|
CopyName(rep.exceptionName, name);
|
||||||
|
rep.instructionPointer = frame->elr;
|
||||||
|
rep.flags = frame->spsr;
|
||||||
|
rep.stackPointer = frame->sp;
|
||||||
|
|
||||||
|
constexpr uint64_t snapshotBytes = CrashReport::StackSnapshotWords * sizeof(uint64_t);
|
||||||
|
if (Memory::VMM::Paging::IsUserRangeAccessible(proc->pageTableRoot, frame->sp,
|
||||||
|
snapshotBytes, false)) {
|
||||||
|
auto* userStack = (const volatile uint64_t*)frame->sp;
|
||||||
|
for (int word = 0; word < CrashReport::StackSnapshotWords; word++)
|
||||||
|
rep.stackWords[word] = userStack[word];
|
||||||
|
rep.stackWordCount = CrashReport::StackSnapshotWords;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IsAbort(ec)) {
|
||||||
|
rep.faultingAddress = frame->far;
|
||||||
|
rep.pfPresent = IsPermissionFault(frame->esr);
|
||||||
|
rep.pfWrite = ec == EcDataAbortLower && (frame->esr & (1ULL << 6)) != 0;
|
||||||
|
rep.pfUser = 1;
|
||||||
|
rep.pfInstructionFetch = ec == EcInstAbortLower;
|
||||||
|
}
|
||||||
|
rep.timestampTick = Timekeeping::GetTicks();
|
||||||
|
|
||||||
|
CrashReport::AddReport(rep);
|
||||||
|
Sched::SpawnCrashPad(proc->pid);
|
||||||
|
|
||||||
|
// As on x86: a sibling thread's fault must end the whole process.
|
||||||
|
int code = TerminationFromClass(ec);
|
||||||
|
if (Sched::GetCurrentThreadPtr() != proc) {
|
||||||
|
Sched::KillProcess(proc->pid, code);
|
||||||
|
} else {
|
||||||
|
Sched::SetProcessExitCode(proc->pid, code);
|
||||||
|
}
|
||||||
|
|
||||||
|
Sched::ExitProcess();
|
||||||
|
__builtin_unreachable();
|
||||||
|
}
|
||||||
|
|
||||||
|
// A not-present user page: anonymous memory mapped lazily or the user
|
||||||
|
// stack growing. Also reached from the kernel touching user buffers.
|
||||||
|
bool TryResolveUserFault(const Arch::PanicFrame* frame, uint64_t ec) {
|
||||||
|
if (!IsAbort(ec) || !IsTranslationFault(frame->esr)) return false;
|
||||||
|
if (frame->far >= UserSpaceEnd) return false;
|
||||||
|
|
||||||
|
auto* cpu = Smp::TryGetCurrentCpuData();
|
||||||
|
if (cpu == nullptr || cpu->currentSlot < 0 || Sched::GetCurrentPid() < 0) return false;
|
||||||
|
|
||||||
|
bool isData = ec == EcDataAbortLower || ec == EcDataAbortSame;
|
||||||
|
Memory::VMM::PageFault fault{false, isData && (frame->esr & (1ULL << 6)) != 0, !isData};
|
||||||
|
return montauk::abi::TryHandleAnonymousPageFault(frame->far, fault)
|
||||||
|
|| Sched::TryGrowUserStack(frame->far);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" void ArchSyncException(Arch::PanicFrame* frame, bool fromUser) {
|
||||||
|
uint64_t ec = ExceptionClass(frame);
|
||||||
|
|
||||||
|
if (TryResolveUserFault(frame, ec)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fromUser && Sched::GetCurrentPid() >= 0) {
|
||||||
|
TerminateUserProcess(frame, ec);
|
||||||
|
}
|
||||||
|
|
||||||
|
Panic(ExceptionName(ec), frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIQs are never routed, SErrors are asynchronous external aborts, and the
|
||||||
|
// kernel neither runs on SP_EL0 nor supports AArch32 user code.
|
||||||
|
extern "C" void ArchUnexpectedException(Arch::PanicFrame* frame, uint64_t kind) {
|
||||||
|
static const char* const names[] = {
|
||||||
|
"Exception on SP_EL0", "Unexpected FIQ", "SError (asynchronous abort)",
|
||||||
|
"AArch32 exception",
|
||||||
|
};
|
||||||
|
Panic(kind < 4 ? names[kind] : "Unexpected exception", frame);
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
/*
|
||||||
|
* Gic.hpp
|
||||||
|
* aarch64 GICv2 interrupt controller (private to the port)
|
||||||
|
* Copyright (c) 2026 Daniel Hammer
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include <cstdint>
|
||||||
|
#include <ACPI/ACPI.hpp>
|
||||||
|
|
||||||
|
namespace Arch::Gic {
|
||||||
|
|
||||||
|
// Bring up the distributor and the calling CPU's interface from the
|
||||||
|
// ACPI MADT. Returns false when no usable GIC is described.
|
||||||
|
bool Initialize(Hal::ACPI::CommonSDTHeader* xsdt);
|
||||||
|
|
||||||
|
bool IsInitialized();
|
||||||
|
|
||||||
|
// The XSDT Initialize was given (for the other ACPI tables the port reads).
|
||||||
|
Hal::ACPI::CommonSDTHeader* CurrentXsdt();
|
||||||
|
|
||||||
|
// Per-CPU part of Initialize, for secondary CPUs.
|
||||||
|
void InitializeCpuInterface();
|
||||||
|
|
||||||
|
// Map a private peripheral interrupt (16-31) onto an IRQ slot and enable
|
||||||
|
// it on the calling CPU.
|
||||||
|
void RoutePpi(uint32_t intid, uint8_t irq, bool edgeTriggered);
|
||||||
|
|
||||||
|
// Called for an IRQ slot after the interrupt is acknowledged and before
|
||||||
|
// it is completed (EOI): level-triggered sources deassert here.
|
||||||
|
using AckHook = void (*)();
|
||||||
|
void SetAckHook(uint8_t irq, AckHook hook);
|
||||||
|
|
||||||
|
// The calling CPU's target bit, as the distributor sees it.
|
||||||
|
uint8_t CurrentTargetMask();
|
||||||
|
|
||||||
|
// Find an ACPI table in the XSDT by signature.
|
||||||
|
Hal::ACPI::CommonSDTHeader* FindTable(Hal::ACPI::CommonSDTHeader* xsdt, const char* signature);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
/*
|
||||||
|
* InterruptFrame.hpp
|
||||||
|
* aarch64 exception frame
|
||||||
|
* Copyright (c) 2026 Daniel Hammer
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
namespace Arch {
|
||||||
|
|
||||||
|
// What the exception vectors (Vectors.S) save before calling into C++.
|
||||||
|
struct PanicFrame {
|
||||||
|
uint64_t x[31]; // x0-x30
|
||||||
|
uint64_t sp; // interrupted SP (SP_EL0 for EL0, else SP_EL1)
|
||||||
|
uint64_t elr; // ELR_EL1: faulting/preferred-return PC
|
||||||
|
uint64_t spsr; // SPSR_EL1
|
||||||
|
uint64_t esr; // ESR_EL1: exception syndrome
|
||||||
|
uint64_t far; // FAR_EL1: faulting address
|
||||||
|
};
|
||||||
|
|
||||||
|
static_assert(sizeof(PanicFrame) == 288, "must match Vectors.S");
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,420 @@
|
|||||||
|
/*
|
||||||
|
* Interrupts.cpp
|
||||||
|
* aarch64 GICv2 driver and IRQ slot dispatch (implements Arch/Interrupts.hpp)
|
||||||
|
* Copyright (c) 2026 Daniel Hammer
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <Arch/Interrupts.hpp>
|
||||||
|
#include <Arch/Smp.hpp>
|
||||||
|
#include <Arch/aarch64/Gic.hpp>
|
||||||
|
#include <Memory/HHDM.hpp>
|
||||||
|
#include <Memory/Paging.hpp>
|
||||||
|
#include <Terminal/Terminal.hpp>
|
||||||
|
#include <CppLib/Stream.hpp>
|
||||||
|
|
||||||
|
// IRQ slots map onto GIC interrupt IDs (INTIDs):
|
||||||
|
// IRQ_TIMER the generic timer's PPI (Timer.cpp routes it)
|
||||||
|
// 24-45 (MSI slots) SPIs of the GICv2m MSI frame, one per slot
|
||||||
|
// IRQ_TLB_SHOOTDOWN,
|
||||||
|
// IRQ_RESCHEDULE SGIs 0 and 1
|
||||||
|
// 1-23 (platform lines) SPIs the PCI layer assigns to INTx pins from
|
||||||
|
// ACPI _PRT (AssignPlatformLine)
|
||||||
|
|
||||||
|
using namespace Kt;
|
||||||
|
|
||||||
|
namespace Arch {
|
||||||
|
|
||||||
|
// Distributor registers
|
||||||
|
static constexpr uint32_t GICD_CTLR = 0x000;
|
||||||
|
static constexpr uint32_t GICD_TYPER = 0x004;
|
||||||
|
static constexpr uint32_t GICD_ISENABLER = 0x100;
|
||||||
|
static constexpr uint32_t GICD_ICENABLER = 0x180;
|
||||||
|
static constexpr uint32_t GICD_ICPENDR = 0x280;
|
||||||
|
static constexpr uint32_t GICD_ICACTIVER = 0x380;
|
||||||
|
static constexpr uint32_t GICD_IPRIORITYR = 0x400;
|
||||||
|
static constexpr uint32_t GICD_ITARGETSR = 0x800;
|
||||||
|
static constexpr uint32_t GICD_ICFGR = 0xC00;
|
||||||
|
static constexpr uint32_t GICD_SGIR = 0xF00;
|
||||||
|
|
||||||
|
// CPU interface registers
|
||||||
|
static constexpr uint32_t GICC_CTLR = 0x00;
|
||||||
|
static constexpr uint32_t GICC_PMR = 0x04;
|
||||||
|
static constexpr uint32_t GICC_BPR = 0x08;
|
||||||
|
static constexpr uint32_t GICC_IAR = 0x0C;
|
||||||
|
static constexpr uint32_t GICC_EOIR = 0x10;
|
||||||
|
|
||||||
|
// GICv2m MSI frame registers
|
||||||
|
static constexpr uint32_t V2M_MSI_TYPER = 0x008;
|
||||||
|
static constexpr uint32_t V2M_MSI_SETSPI_NS = 0x040;
|
||||||
|
|
||||||
|
static constexpr uint32_t SpuriousIntid = 1020;
|
||||||
|
static constexpr uint32_t MaxIntids = 1020;
|
||||||
|
static constexpr uint8_t DefaultPriority = 0xA0;
|
||||||
|
static constexpr uint8_t NoSlot = 0xFF;
|
||||||
|
|
||||||
|
static constexpr uint8_t FirstMsiSlot = 24;
|
||||||
|
static constexpr uint8_t LastMsiSlot = 45;
|
||||||
|
|
||||||
|
// MADT interrupt-controller structures (ACPI 6.x)
|
||||||
|
static constexpr uint8_t MadtGicc = 0x0B;
|
||||||
|
static constexpr uint8_t MadtGicd = 0x0C;
|
||||||
|
static constexpr uint8_t MadtGicMsiFrame = 0x0D;
|
||||||
|
|
||||||
|
struct MadtEntry {
|
||||||
|
uint8_t Type;
|
||||||
|
uint8_t Length;
|
||||||
|
} __attribute__((packed));
|
||||||
|
|
||||||
|
struct MadtGiccEntry {
|
||||||
|
MadtEntry Header;
|
||||||
|
uint16_t Reserved;
|
||||||
|
uint32_t CpuInterfaceNumber;
|
||||||
|
uint32_t AcpiProcessorUid;
|
||||||
|
uint32_t Flags;
|
||||||
|
uint32_t ParkingProtocolVersion;
|
||||||
|
uint32_t PerformanceInterruptGsiv;
|
||||||
|
uint64_t ParkedAddress;
|
||||||
|
uint64_t PhysicalBaseAddress; // GICC
|
||||||
|
uint64_t Gicv;
|
||||||
|
uint64_t Gich;
|
||||||
|
uint32_t VgicMaintenanceInterrupt;
|
||||||
|
uint64_t GicrBaseAddress;
|
||||||
|
uint64_t Mpidr;
|
||||||
|
} __attribute__((packed));
|
||||||
|
|
||||||
|
struct MadtGicdEntry {
|
||||||
|
MadtEntry Header;
|
||||||
|
uint16_t Reserved;
|
||||||
|
uint32_t GicId;
|
||||||
|
uint64_t PhysicalBaseAddress;
|
||||||
|
uint32_t SystemVectorBase;
|
||||||
|
uint8_t GicVersion;
|
||||||
|
} __attribute__((packed));
|
||||||
|
|
||||||
|
struct MadtMsiFrameEntry {
|
||||||
|
MadtEntry Header;
|
||||||
|
uint16_t Reserved;
|
||||||
|
uint32_t FrameId;
|
||||||
|
uint64_t PhysicalBaseAddress;
|
||||||
|
uint32_t Flags; // bit 0: SpiCount/SpiBase override TYPER
|
||||||
|
uint16_t SpiCount;
|
||||||
|
uint16_t SpiBase;
|
||||||
|
} __attribute__((packed));
|
||||||
|
|
||||||
|
static constexpr uint32_t MadtHeaderSize = 44; // SDT header + LAPIC address + flags
|
||||||
|
|
||||||
|
static IrqHandler g_irqHandlers[IRQ_COUNT] = {};
|
||||||
|
static Gic::AckHook g_ackHooks[IRQ_COUNT] = {};
|
||||||
|
static uint8_t g_intidToSlot[MaxIntids];
|
||||||
|
static uint32_t g_slotToIntid[IRQ_COUNT];
|
||||||
|
|
||||||
|
static volatile uint8_t* g_gicd = nullptr;
|
||||||
|
static volatile uint8_t* g_gicc = nullptr;
|
||||||
|
static uint32_t g_intidCount = 0;
|
||||||
|
|
||||||
|
static Hal::ACPI::CommonSDTHeader* g_xsdt = nullptr;
|
||||||
|
|
||||||
|
struct PlatformLine {
|
||||||
|
uint32_t gsi; // 0: unassigned
|
||||||
|
bool edgeTriggered;
|
||||||
|
};
|
||||||
|
static PlatformLine g_platformLines[24] = {};
|
||||||
|
|
||||||
|
static uint64_t g_msiFramePhys = 0;
|
||||||
|
static uint32_t g_msiSpiBase = 0;
|
||||||
|
static uint32_t g_msiSpiCount = 0;
|
||||||
|
|
||||||
|
static inline uint32_t GicdRead(uint32_t reg) {
|
||||||
|
return *(volatile uint32_t*)(g_gicd + reg);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void GicdWrite(uint32_t reg, uint32_t value) {
|
||||||
|
*(volatile uint32_t*)(g_gicd + reg) = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void GicdWrite8(uint32_t reg, uint8_t value) {
|
||||||
|
*(volatile uint8_t*)(g_gicd + reg) = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline uint8_t GicdRead8(uint32_t reg) {
|
||||||
|
return *(volatile uint8_t*)(g_gicd + reg);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline uint32_t GiccRead(uint32_t reg) {
|
||||||
|
return *(volatile uint32_t*)(g_gicc + reg);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void GiccWrite(uint32_t reg, uint32_t value) {
|
||||||
|
*(volatile uint32_t*)(g_gicc + reg) = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
static volatile uint8_t* MapRegisters(uint64_t phys, uint64_t size) {
|
||||||
|
for (uint64_t offset = 0; offset < size; offset += 0x1000) {
|
||||||
|
Memory::VMM::g_paging->MapMMIO(phys + offset, Memory::HHDM(phys + offset));
|
||||||
|
}
|
||||||
|
return (volatile uint8_t*)Memory::HHDM(phys);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void SetConfig(uint32_t intid, bool edgeTriggered) {
|
||||||
|
uint32_t reg = GICD_ICFGR + (intid / 16) * 4;
|
||||||
|
uint32_t shift = (intid % 16) * 2 + 1;
|
||||||
|
uint32_t value = GicdRead(reg);
|
||||||
|
if (edgeTriggered) value |= 1u << shift;
|
||||||
|
else value &= ~(1u << shift);
|
||||||
|
GicdWrite(reg, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void EnableIntid(uint32_t intid) {
|
||||||
|
GicdWrite(GICD_ISENABLER + (intid / 32) * 4, 1u << (intid % 32));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void Bind(uint32_t intid, uint8_t irq) {
|
||||||
|
if (intid >= MaxIntids || irq >= IRQ_COUNT) return;
|
||||||
|
g_intidToSlot[intid] = irq;
|
||||||
|
g_slotToIntid[irq] = intid;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Route a shared peripheral interrupt to the boot CPU and unmask it.
|
||||||
|
static void RouteSpi(uint32_t intid, uint8_t irq, bool edgeTriggered) {
|
||||||
|
if (intid < 32 || intid >= g_intidCount) return;
|
||||||
|
Bind(intid, irq);
|
||||||
|
GicdWrite8(GICD_IPRIORITYR + intid, DefaultPriority);
|
||||||
|
GicdWrite8(GICD_ITARGETSR + intid, Smp::GetCpuData(0)->arch.gicTargetMask);
|
||||||
|
SetConfig(intid, edgeTriggered);
|
||||||
|
EnableIntid(intid);
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace Gic {
|
||||||
|
|
||||||
|
Hal::ACPI::CommonSDTHeader* FindTable(Hal::ACPI::CommonSDTHeader* xsdt, const char* signature) {
|
||||||
|
if (xsdt == nullptr) return nullptr;
|
||||||
|
uint32_t count = (xsdt->Length - sizeof(Hal::ACPI::CommonSDTHeader)) / 8;
|
||||||
|
auto* entries = (uint64_t*)((uint64_t)xsdt + sizeof(Hal::ACPI::CommonSDTHeader));
|
||||||
|
for (uint32_t i = 0; i < count; i++) {
|
||||||
|
auto* header = (Hal::ACPI::CommonSDTHeader*)Memory::HHDM(entries[i]);
|
||||||
|
if (header->Signature[0] == signature[0] && header->Signature[1] == signature[1]
|
||||||
|
&& header->Signature[2] == signature[2] && header->Signature[3] == signature[3]) {
|
||||||
|
return header;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ITARGETSR0-7 are banked: each byte reads back the reading CPU's
|
||||||
|
// own interface bit.
|
||||||
|
uint8_t CurrentTargetMask() {
|
||||||
|
uint32_t value = GicdRead(GICD_ITARGETSR);
|
||||||
|
return (uint8_t)(value | value >> 8 | value >> 16 | value >> 24);
|
||||||
|
}
|
||||||
|
|
||||||
|
void InitializeCpuInterface() {
|
||||||
|
// SGIs and PPIs are banked per CPU: disable the PPIs until a
|
||||||
|
// driver routes one, keep the SGIs (IPIs) enabled.
|
||||||
|
GicdWrite(GICD_ICENABLER, 0xFFFF0000u);
|
||||||
|
GicdWrite(GICD_ISENABLER, 0x0000FFFFu);
|
||||||
|
GicdWrite(GICD_ICPENDR, 0xFFFFFFFFu);
|
||||||
|
for (uint32_t intid = 0; intid < 32; intid++) {
|
||||||
|
GicdWrite8(GICD_IPRIORITYR + intid, DefaultPriority);
|
||||||
|
}
|
||||||
|
|
||||||
|
GiccWrite(GICC_PMR, 0xF0);
|
||||||
|
GiccWrite(GICC_BPR, 0);
|
||||||
|
GiccWrite(GICC_CTLR, 1);
|
||||||
|
|
||||||
|
auto* cpu = Smp::GetCurrentCpuData();
|
||||||
|
cpu->arch.gicTargetMask = CurrentTargetMask();
|
||||||
|
}
|
||||||
|
|
||||||
|
void RoutePpi(uint32_t intid, uint8_t irq, bool edgeTriggered) {
|
||||||
|
if (intid < 16 || intid >= 32) return;
|
||||||
|
Bind(intid, irq);
|
||||||
|
SetConfig(intid, edgeTriggered);
|
||||||
|
EnableIntid(intid);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetAckHook(uint8_t irq, AckHook hook) {
|
||||||
|
if (irq < IRQ_COUNT) g_ackHooks[irq] = hook;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsInitialized() {
|
||||||
|
return g_gicc != nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
Hal::ACPI::CommonSDTHeader* CurrentXsdt() {
|
||||||
|
return g_xsdt;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Initialize(Hal::ACPI::CommonSDTHeader* xsdt) {
|
||||||
|
g_xsdt = xsdt;
|
||||||
|
auto* madt = FindTable(xsdt, "APIC");
|
||||||
|
if (madt == nullptr) {
|
||||||
|
KernelLogStream(ERROR, "GIC") << "No MADT";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t gicdPhys = 0, giccPhys = 0;
|
||||||
|
uint8_t version = 0;
|
||||||
|
auto* cursor = (uint8_t*)madt + MadtHeaderSize;
|
||||||
|
auto* end = (uint8_t*)madt + madt->Length;
|
||||||
|
while (cursor + sizeof(MadtEntry) <= end) {
|
||||||
|
auto* entry = (MadtEntry*)cursor;
|
||||||
|
if (entry->Length < sizeof(MadtEntry)) break;
|
||||||
|
|
||||||
|
if (entry->Type == MadtGicd) {
|
||||||
|
auto* gicd = (MadtGicdEntry*)entry;
|
||||||
|
gicdPhys = gicd->PhysicalBaseAddress;
|
||||||
|
version = gicd->GicVersion;
|
||||||
|
} else if (entry->Type == MadtGicc && giccPhys == 0) {
|
||||||
|
giccPhys = ((MadtGiccEntry*)entry)->PhysicalBaseAddress;
|
||||||
|
} else if (entry->Type == MadtGicMsiFrame && g_msiFramePhys == 0) {
|
||||||
|
auto* frame = (MadtMsiFrameEntry*)entry;
|
||||||
|
g_msiFramePhys = frame->PhysicalBaseAddress;
|
||||||
|
if (frame->Flags & 1) {
|
||||||
|
g_msiSpiBase = frame->SpiBase;
|
||||||
|
g_msiSpiCount = frame->SpiCount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cursor += entry->Length;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Version 0 means "infer from the hardware"; 1 and 2 share a
|
||||||
|
// programming model.
|
||||||
|
if (gicdPhys == 0 || giccPhys == 0 || version > 2) {
|
||||||
|
KernelLogStream(ERROR, "GIC") << "No GICv2 in the MADT (version "
|
||||||
|
<< base::dec << (uint64_t)version << ")";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (uint32_t i = 0; i < MaxIntids; i++) g_intidToSlot[i] = NoSlot;
|
||||||
|
for (int i = 0; i < IRQ_COUNT; i++) g_slotToIntid[i] = SpuriousIntid;
|
||||||
|
Bind(0, IRQ_TLB_SHOOTDOWN);
|
||||||
|
Bind(1, IRQ_RESCHEDULE);
|
||||||
|
|
||||||
|
g_gicd = MapRegisters(gicdPhys, 0x1000);
|
||||||
|
g_gicc = MapRegisters(giccPhys, 0x2000);
|
||||||
|
|
||||||
|
g_intidCount = ((GicdRead(GICD_TYPER) & 0x1F) + 1) * 32;
|
||||||
|
if (g_intidCount > MaxIntids) g_intidCount = MaxIntids;
|
||||||
|
|
||||||
|
// Distributor: everything masked, idle, level-triggered and
|
||||||
|
// aimed at no CPU until RouteSpi claims it.
|
||||||
|
GicdWrite(GICD_CTLR, 0);
|
||||||
|
for (uint32_t intid = 32; intid < g_intidCount; intid += 32) {
|
||||||
|
GicdWrite(GICD_ICENABLER + (intid / 32) * 4, 0xFFFFFFFFu);
|
||||||
|
GicdWrite(GICD_ICPENDR + (intid / 32) * 4, 0xFFFFFFFFu);
|
||||||
|
GicdWrite(GICD_ICACTIVER + (intid / 32) * 4, 0xFFFFFFFFu);
|
||||||
|
}
|
||||||
|
for (uint32_t intid = 32; intid < g_intidCount; intid++) {
|
||||||
|
GicdWrite8(GICD_IPRIORITYR + intid, DefaultPriority);
|
||||||
|
GicdWrite8(GICD_ITARGETSR + intid, 0);
|
||||||
|
}
|
||||||
|
for (uint32_t intid = 32; intid < g_intidCount; intid += 16) {
|
||||||
|
GicdWrite(GICD_ICFGR + (intid / 16) * 4, 0);
|
||||||
|
}
|
||||||
|
GicdWrite(GICD_CTLR, 1);
|
||||||
|
|
||||||
|
InitializeCpuInterface();
|
||||||
|
|
||||||
|
if (g_msiFramePhys != 0) {
|
||||||
|
volatile uint8_t* frame = MapRegisters(g_msiFramePhys, 0x1000);
|
||||||
|
if (g_msiSpiCount == 0) {
|
||||||
|
uint32_t typer = *(volatile uint32_t*)(frame + V2M_MSI_TYPER);
|
||||||
|
g_msiSpiBase = (typer >> 16) & 0x3FF;
|
||||||
|
g_msiSpiCount = typer & 0x3FF;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
KernelLogStream(OK, "GIC") << "GICv2: distributor 0x" << base::hex << gicdPhys
|
||||||
|
<< ", CPU interface 0x" << giccPhys << ", " << base::dec
|
||||||
|
<< (uint64_t)g_intidCount << " interrupt IDs";
|
||||||
|
if (g_msiSpiCount != 0) {
|
||||||
|
KernelLogStream(OK, "GIC") << "GICv2m MSI frame 0x" << base::hex << g_msiFramePhys
|
||||||
|
<< ": SPIs " << base::dec << (uint64_t)g_msiSpiBase << "-"
|
||||||
|
<< (uint64_t)(g_msiSpiBase + g_msiSpiCount - 1);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void RegisterIrqHandler(uint8_t irq, IrqHandler handler) {
|
||||||
|
if (irq >= IRQ_COUNT) return;
|
||||||
|
g_irqHandlers[irq] = handler;
|
||||||
|
KernelLogStream(DEBUG, "IRQ") << "Registered handler for IRQ "
|
||||||
|
<< base::dec << (uint64_t)irq;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PlatformLinesAreFixed() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ACPI GSIs are GIC INTIDs. The GIC knows only active-high levels and
|
||||||
|
// rising edges; an active-low line is inverted on its way in.
|
||||||
|
bool AssignPlatformLine(uint8_t irq, uint32_t gsi, bool levelTriggered, bool) {
|
||||||
|
if (irq == 0 || irq >= 24 || gsi < 32 || gsi >= g_intidCount) return false;
|
||||||
|
g_platformLines[irq] = PlatformLine{ gsi, !levelTriggered };
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RouteLegacyIrq(uint8_t irq) {
|
||||||
|
if (irq >= 24 || g_platformLines[irq].gsi == 0) {
|
||||||
|
KernelLogStream(WARNING, "IRQ") << "aarch64: platform line "
|
||||||
|
<< base::dec << (uint64_t)irq << " is not assigned";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
RouteSpi(g_platformLines[irq].gsi, irq, g_platformLines[irq].edgeTriggered);
|
||||||
|
}
|
||||||
|
|
||||||
|
void EnableLegacyIrq(uint8_t irq) {
|
||||||
|
RouteLegacyIrq(irq);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ComposeMsi(uint8_t irq, MsiMessage& out) {
|
||||||
|
if (g_gicd == nullptr || irq < FirstMsiSlot || irq > LastMsiSlot) return false;
|
||||||
|
uint32_t index = irq - FirstMsiSlot;
|
||||||
|
if (index >= g_msiSpiCount) return false;
|
||||||
|
|
||||||
|
uint32_t spi = g_msiSpiBase + index;
|
||||||
|
if (g_slotToIntid[irq] != spi) {
|
||||||
|
RouteSpi(spi, irq, true);
|
||||||
|
}
|
||||||
|
out = MsiMessage{ g_msiFramePhys + V2M_MSI_SETSPI_NS, spi };
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SendIpi(int cpuIndex, uint8_t irq) {
|
||||||
|
if (g_gicd == nullptr) return false;
|
||||||
|
auto* cpu = Smp::GetCpuData(cpuIndex);
|
||||||
|
if (cpu == nullptr || cpu->arch.gicTargetMask == 0) return false;
|
||||||
|
uint32_t sgi = g_slotToIntid[irq];
|
||||||
|
if (sgi >= 16) return false;
|
||||||
|
|
||||||
|
// Make the IPI's memory effects visible before it is raised.
|
||||||
|
asm volatile("dsb ishst" ::: "memory");
|
||||||
|
GicdWrite(GICD_SGIR, ((uint32_t)cpu->arch.gicTargetMask << 16) | sgi);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// IRQ exception vector (Vectors.S). As on x86, the interrupt is completed
|
||||||
|
// BEFORE the handler runs: the handler may context-switch away and not
|
||||||
|
// come back here for a long time, and an un-EOId interrupt would hold this
|
||||||
|
// CPU's running priority up (no further ticks). Level-triggered sources
|
||||||
|
// get the chance to drop their line first through an ack hook.
|
||||||
|
extern "C" void ArchIrqException(void*, bool fromUser) {
|
||||||
|
using namespace Arch;
|
||||||
|
uint32_t iar = GiccRead(GICC_IAR);
|
||||||
|
uint32_t intid = iar & 0x3FF;
|
||||||
|
if (intid >= SpuriousIntid) return;
|
||||||
|
|
||||||
|
uint8_t irq = g_intidToSlot[intid];
|
||||||
|
if (irq != NoSlot && g_ackHooks[irq] != nullptr) {
|
||||||
|
g_ackHooks[irq]();
|
||||||
|
}
|
||||||
|
GiccWrite(GICC_EOIR, iar);
|
||||||
|
|
||||||
|
if (irq != NoSlot && g_irqHandlers[irq] != nullptr) {
|
||||||
|
g_irqHandlers[irq](irq, fromUser);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
/*
|
||||||
|
* IoPort.hpp
|
||||||
|
* aarch64 port I/O (implements Io/IoPort.hpp)
|
||||||
|
* Copyright (c) 2026 Daniel Hammer
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include <cstdint>
|
||||||
|
#include <Arch/Cpu.hpp>
|
||||||
|
|
||||||
|
// ARM has no I/O address space. Until a PCI host bridge driver maps its I/O
|
||||||
|
// aperture, port accesses behave like accesses to an absent device.
|
||||||
|
|
||||||
|
namespace Io {
|
||||||
|
static constexpr bool HasPortIo = false;
|
||||||
|
|
||||||
|
inline void Out8(uint8_t, uint16_t) {}
|
||||||
|
inline void Out16(uint16_t, uint16_t) {}
|
||||||
|
inline void Out32(uint32_t, uint16_t) {}
|
||||||
|
|
||||||
|
inline uint8_t In8(uint16_t) { return 0xFF; }
|
||||||
|
inline uint16_t In16(uint16_t) { return 0xFFFF; }
|
||||||
|
inline uint32_t In32(uint16_t) { return 0xFFFFFFFF; }
|
||||||
|
|
||||||
|
inline void IoPortWait() {
|
||||||
|
Arch::IoDelay();
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,528 @@
|
|||||||
|
/*
|
||||||
|
* Paging.cpp
|
||||||
|
* aarch64 virtual memory (implements Memory/Paging.hpp)
|
||||||
|
* Copyright (c) 2026 Daniel Hammer
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <Memory/Paging.hpp>
|
||||||
|
#include <Memory/PageFrameAllocator.hpp>
|
||||||
|
#include <Memory/HHDM.hpp>
|
||||||
|
#include <Arch/Cpu.hpp>
|
||||||
|
#include <Common/Panic.hpp>
|
||||||
|
#include <CppLib/Spinlock.hpp>
|
||||||
|
#include <Arch/aarch64/Bringup.hpp>
|
||||||
|
|
||||||
|
// 4 KiB granule, 48-bit virtual addresses, four levels (0-3). The kernel
|
||||||
|
// half is translated through TTBR1_EL1 (g_kernelHalfRoot), which never
|
||||||
|
// changes after Init. The "address space roots" the generic kernel switches
|
||||||
|
// between are TTBR0_EL1 tables: a process's root holds only its own lower
|
||||||
|
// half, and KernelRoot() is the TTBR0 table of kernel contexts, empty apart
|
||||||
|
// from the EFI runtime identity mappings (so null pointers fault).
|
||||||
|
//
|
||||||
|
// The HHDM covers only what the memory map lists as RAM or firmware data:
|
||||||
|
// unlike on x86, the holes between are device space on ARM machines, and a
|
||||||
|
// cacheable mapping of device registers invites speculative accesses.
|
||||||
|
|
||||||
|
namespace Memory::VMM {
|
||||||
|
|
||||||
|
Paging* g_paging = nullptr;
|
||||||
|
|
||||||
|
static std::uint64_t g_hhdmLimit = 0;
|
||||||
|
|
||||||
|
static std::uint64_t g_kernelHalfRoot = 0;
|
||||||
|
|
||||||
|
std::uint64_t Paging::HhdmLimit() { return g_hhdmLimit; }
|
||||||
|
|
||||||
|
// Translation table descriptor bits (VMSAv8-64, stage 1)
|
||||||
|
static constexpr uint64_t DescValid = 1ULL << 0;
|
||||||
|
static constexpr uint64_t DescTable = 1ULL << 1; // table (levels 0-2) or page (level 3)
|
||||||
|
static constexpr uint64_t DescAttrShift = 2; // AttrIndx[2:0], into MAIR_EL1
|
||||||
|
static constexpr uint64_t DescAttrMask = 7ULL << DescAttrShift;
|
||||||
|
static constexpr uint64_t DescApUser = 1ULL << 6; // AP[1]: EL0 may access
|
||||||
|
static constexpr uint64_t DescApReadOnly = 1ULL << 7; // AP[2]
|
||||||
|
static constexpr uint64_t DescShInner = 3ULL << 8;
|
||||||
|
static constexpr uint64_t DescAf = 1ULL << 10; // access flag (no AF faults)
|
||||||
|
static constexpr uint64_t DescNotGlobal = 1ULL << 11;
|
||||||
|
static constexpr uint64_t DescPxn = 1ULL << 53;
|
||||||
|
static constexpr uint64_t DescUxn = 1ULL << 54;
|
||||||
|
static constexpr uint64_t DescAddrMask = 0x0000FFFFFFFFF000ULL;
|
||||||
|
|
||||||
|
// MAIR_EL1 attribute indices
|
||||||
|
static constexpr uint64_t AttrNormal = 0; // write-back, read/write-allocate
|
||||||
|
static constexpr uint64_t AttrNormalNc = 1; // non-cacheable (write-combining)
|
||||||
|
static constexpr uint64_t AttrDevice = 2; // Device-nGnRE
|
||||||
|
static constexpr uint64_t MairValue = 0xFFULL << (8 * AttrNormal)
|
||||||
|
| 0x44ULL << (8 * AttrNormalNc)
|
||||||
|
| 0x04ULL << (8 * AttrDevice);
|
||||||
|
|
||||||
|
static constexpr uint64_t KernelHalfBase = 0xFFFF000000000000ULL;
|
||||||
|
static constexpr uint64_t UserSpaceEnd = 0x0000800000000000ULL;
|
||||||
|
|
||||||
|
// EFI memory types whose runtime regions are device registers
|
||||||
|
static constexpr uint32_t EfiMemoryMappedIo = 11;
|
||||||
|
static constexpr uint32_t EfiMemoryMappedIoPortSpace = 12;
|
||||||
|
|
||||||
|
static kcp::Mutex pagingLock;
|
||||||
|
|
||||||
|
void LockUserPaging() { pagingLock.Acquire(); }
|
||||||
|
void UnlockUserPaging() { pagingLock.Release(); }
|
||||||
|
|
||||||
|
void ActivateKernelAddressSpace() {
|
||||||
|
Arch::WritePageTableRoot(g_paging->KernelRoot());
|
||||||
|
}
|
||||||
|
|
||||||
|
void FlushTLB() {
|
||||||
|
Arch::FlushTlbAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
using Descriptor = uint64_t;
|
||||||
|
|
||||||
|
static inline volatile Descriptor* TableAt(uint64_t phys) {
|
||||||
|
return (volatile Descriptor*)Memory::HHDM(phys);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline uint64_t Index(uint64_t va, int level) {
|
||||||
|
return (va >> (39 - 9 * level)) & 0x1FF;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Order descriptor writes before the table walker (and later
|
||||||
|
// instructions) can observe them.
|
||||||
|
static inline void PublishTables() {
|
||||||
|
asm volatile("dsb ishst\n\tisb" ::: "memory");
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline uint64_t ReadTtbr1() {
|
||||||
|
uint64_t ttbr;
|
||||||
|
asm volatile("mrs %0, ttbr1_el1" : "=r"(ttbr));
|
||||||
|
return ttbr & Arch::TtbrAddressMask;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The level-3 descriptor for `va`, creating intermediate tables.
|
||||||
|
// Returns nullptr when out of memory or when a block mapping covers
|
||||||
|
// `va` (only the bootloader's tables contain blocks).
|
||||||
|
static volatile Descriptor* WalkCreate(uint64_t root, uint64_t va) {
|
||||||
|
uint64_t table = root;
|
||||||
|
for (int level = 0; level < 3; level++) {
|
||||||
|
volatile Descriptor* entry = &TableAt(table)[Index(va, level)];
|
||||||
|
Descriptor desc = *entry;
|
||||||
|
if ((desc & DescValid) == 0) {
|
||||||
|
void* page = Memory::g_pfa->AllocateZeroed();
|
||||||
|
if (page == nullptr) return nullptr;
|
||||||
|
table = Memory::SubHHDM(page);
|
||||||
|
// The zeroed table must be visible before the walker can
|
||||||
|
// reach it.
|
||||||
|
asm volatile("dsb ishst" ::: "memory");
|
||||||
|
*entry = table | DescTable | DescValid;
|
||||||
|
} else if ((desc & DescTable) == 0) {
|
||||||
|
return nullptr;
|
||||||
|
} else {
|
||||||
|
table = desc & DescAddrMask;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &TableAt(table)[Index(va, 3)];
|
||||||
|
}
|
||||||
|
|
||||||
|
// The level-3 descriptor for `va`, or nullptr if a table is missing.
|
||||||
|
static volatile Descriptor* WalkExisting(uint64_t root, uint64_t va) {
|
||||||
|
uint64_t table = root;
|
||||||
|
for (int level = 0; level < 3; level++) {
|
||||||
|
Descriptor desc = TableAt(table)[Index(va, level)];
|
||||||
|
if ((desc & (DescValid | DescTable)) != (DescValid | DescTable)) return nullptr;
|
||||||
|
table = desc & DescAddrMask;
|
||||||
|
}
|
||||||
|
return &TableAt(table)[Index(va, 3)];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Install a leaf. Replacing a live translation (other address or
|
||||||
|
// attributes) needs break-before-make: invalidate, flush, then write.
|
||||||
|
static void SetLeaf(volatile Descriptor* entry, Descriptor desc, uint64_t va) {
|
||||||
|
Descriptor old = *entry;
|
||||||
|
if (old == desc) return;
|
||||||
|
if (old & DescValid) {
|
||||||
|
*entry = 0;
|
||||||
|
Arch::FlushTlbPage(va);
|
||||||
|
}
|
||||||
|
*entry = desc;
|
||||||
|
PublishTables();
|
||||||
|
}
|
||||||
|
|
||||||
|
static Descriptor KernelLeaf(uint64_t phys, uint64_t attr) {
|
||||||
|
Descriptor desc = (phys & DescAddrMask) | DescTable | DescValid | DescAf | DescShInner
|
||||||
|
| (attr << DescAttrShift) | DescUxn;
|
||||||
|
// Kernel memory stays executable, as on x86; device memory never is.
|
||||||
|
if (attr != AttrNormal) desc |= DescPxn;
|
||||||
|
return desc;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Descriptor UserLeaf(uint64_t phys, uint64_t attr, bool writable, bool executable) {
|
||||||
|
Descriptor desc = (phys & DescAddrMask) | DescTable | DescValid | DescAf | DescShInner
|
||||||
|
| DescNotGlobal | (attr << DescAttrShift) | DescApUser | DescPxn;
|
||||||
|
if (!writable) desc |= DescApReadOnly;
|
||||||
|
if (!executable) desc |= DescUxn;
|
||||||
|
return desc;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bool IsUserLeaf(Descriptor desc) {
|
||||||
|
return (desc & (DescValid | DescTable)) == (DescValid | DescTable) && (desc & DescApUser);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Translate through a table tree that may contain block mappings (the
|
||||||
|
// bootloader's). Returns 0 if unmapped.
|
||||||
|
static uint64_t Translate(uint64_t root, uint64_t va) {
|
||||||
|
uint64_t table = root;
|
||||||
|
for (int level = 0; level <= 3; level++) {
|
||||||
|
Descriptor desc = TableAt(table)[Index(va, level)];
|
||||||
|
if ((desc & DescValid) == 0) return 0;
|
||||||
|
|
||||||
|
uint64_t span = 1ULL << (39 - 9 * level);
|
||||||
|
bool leaf = level == 3 || (desc & DescTable) == 0;
|
||||||
|
if (leaf) {
|
||||||
|
if (level == 0) return 0; // no level-0 blocks with 4 KiB granules
|
||||||
|
return ((desc & DescAddrMask) & ~(span - 1)) | (va & (span - 1));
|
||||||
|
}
|
||||||
|
table = desc & DescAddrMask;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Paging::MapEfiRuntime(const montauk::boot::EfiInfo& efi) {
|
||||||
|
if (efi.memoryMap == nullptr || efi.descriptorSize == 0) return;
|
||||||
|
|
||||||
|
struct EfiMemDesc {
|
||||||
|
uint32_t Type;
|
||||||
|
uint64_t PhysicalStart;
|
||||||
|
uint64_t VirtualStart;
|
||||||
|
uint64_t NumberOfPages;
|
||||||
|
uint64_t Attribute;
|
||||||
|
};
|
||||||
|
|
||||||
|
static constexpr uint64_t EFI_MEMORY_RUNTIME = 0x8000000000000000ULL;
|
||||||
|
|
||||||
|
auto* base = (uint8_t*)efi.memoryMap;
|
||||||
|
uint64_t count = efi.memoryMapSize / efi.descriptorSize;
|
||||||
|
|
||||||
|
for (uint64_t i = 0; i < count; i++) {
|
||||||
|
auto* desc = (EfiMemDesc*)(base + i * efi.descriptorSize);
|
||||||
|
if ((desc->Attribute & EFI_MEMORY_RUNTIME) == 0) continue;
|
||||||
|
|
||||||
|
bool device = desc->Type == EfiMemoryMappedIo || desc->Type == EfiMemoryMappedIoPortSpace;
|
||||||
|
for (uint64_t p = 0; p < desc->NumberOfPages; p++) {
|
||||||
|
uint64_t addr = desc->PhysicalStart + p * 0x1000;
|
||||||
|
// Firmware code runs at its HHDM alias (Efi::Init calls the
|
||||||
|
// services there) and reaches its data at physical addresses.
|
||||||
|
// Runtime regions are reserved memory, outside the HHDM.
|
||||||
|
if (device) {
|
||||||
|
MapMMIO(addr, addr);
|
||||||
|
MapMMIO(addr, Memory::HHDM(addr));
|
||||||
|
} else {
|
||||||
|
Map(addr, addr);
|
||||||
|
Map(addr, Memory::HHDM(addr));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FlushTLB();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace Arch {
|
||||||
|
|
||||||
|
// The new tables map the code running here at the same address as the
|
||||||
|
// bootloader's did, so the switch is a register write followed by a
|
||||||
|
// full invalidate.
|
||||||
|
void ActivateKernelTranslation() {
|
||||||
|
using namespace Memory::VMM;
|
||||||
|
|
||||||
|
// 48-bit VAs in both halves, 4 KiB granules, write-back walks of
|
||||||
|
// inner-shareable tables; the physical size is what the CPU has.
|
||||||
|
uint64_t mmfr0;
|
||||||
|
asm volatile("mrs %0, id_aa64mmfr0_el1" : "=r"(mmfr0));
|
||||||
|
uint64_t ips = mmfr0 & 0xF;
|
||||||
|
if (ips > 5) ips = 5; // 52-bit output needs LPA2 descriptors
|
||||||
|
uint64_t tcr = 16ULL // T0SZ
|
||||||
|
| 1ULL << 8 | 1ULL << 10 // IRGN0, ORGN0: write-back
|
||||||
|
| 3ULL << 12 // SH0: inner shareable
|
||||||
|
| 0ULL << 14 // TG0: 4 KiB
|
||||||
|
| 16ULL << 16 // T1SZ
|
||||||
|
| 1ULL << 24 | 1ULL << 26 // IRGN1, ORGN1
|
||||||
|
| 3ULL << 28 // SH1
|
||||||
|
| 2ULL << 30 // TG1: 4 KiB
|
||||||
|
| ips << 32; // IPS
|
||||||
|
|
||||||
|
asm volatile(
|
||||||
|
"dsb ish\n\t"
|
||||||
|
"msr mair_el1, %0\n\t"
|
||||||
|
"msr tcr_el1, %1\n\t"
|
||||||
|
"isb\n\t"
|
||||||
|
"msr ttbr1_el1, %2\n\t"
|
||||||
|
"msr ttbr0_el1, %3\n\t"
|
||||||
|
"isb\n\t"
|
||||||
|
"tlbi vmalle1\n\t"
|
||||||
|
"dsb nsh\n\t"
|
||||||
|
"isb"
|
||||||
|
:: "r"(MairValue), "r"(tcr), "r"(g_kernelHalfRoot), "r"(g_paging->KernelRoot()) : "memory");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace Memory::VMM {
|
||||||
|
|
||||||
|
Paging::Paging() {
|
||||||
|
rootPhys = Memory::SubHHDM(Memory::g_pfa->AllocateZeroed());
|
||||||
|
g_kernelHalfRoot = Memory::SubHHDM(Memory::g_pfa->AllocateZeroed());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Kernel mappings of lower-half addresses (EFI identity mappings) go to
|
||||||
|
// the kernel contexts' TTBR0 table.
|
||||||
|
static uint64_t KernelRootFor(uint64_t kernelContextRoot, uint64_t va) {
|
||||||
|
return va >= KernelHalfBase ? g_kernelHalfRoot : kernelContextRoot;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Paging::Init(std::uint64_t kernelBaseVirt, std::uint64_t kernelSize, const montauk::boot::MemoryMap& memMap,
|
||||||
|
const montauk::boot::Framebuffer& framebuffer) {
|
||||||
|
if (Memory::HHDMBase < KernelHalfBase) {
|
||||||
|
Panic("aarch64: HHDM outside the 48-bit kernel half", nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t bootTtbr1 = ReadTtbr1();
|
||||||
|
uint64_t bootTcr, bootMair;
|
||||||
|
asm volatile("mrs %0, tcr_el1" : "=r"(bootTcr));
|
||||||
|
asm volatile("mrs %0, mair_el1" : "=r"(bootMair));
|
||||||
|
Kt::KernelLogStream(Kt::DEBUG, "VMM") << "Bootloader TCR 0x" << base::hex << bootTcr
|
||||||
|
<< ", MAIR 0x" << bootMair << ", TTBR1 0x" << bootTtbr1;
|
||||||
|
|
||||||
|
// The kernel image, at the physical pages the bootloader loaded it to.
|
||||||
|
for (uint64_t va = kernelBaseVirt; va < kernelBaseVirt + kernelSize; va += 0x1000) {
|
||||||
|
uint64_t phys = Translate(bootTtbr1, va);
|
||||||
|
if (phys == 0) Panic("aarch64: kernel page missing from the bootloader's tables", nullptr);
|
||||||
|
Map(phys, va);
|
||||||
|
}
|
||||||
|
|
||||||
|
// HHDM: every region that is real backing store (see the top of
|
||||||
|
// this file). The limit is published for the AML interpreter.
|
||||||
|
uint64_t maxPhysAddr = 0;
|
||||||
|
for (size_t i = 0; i < memMap.count; i++) {
|
||||||
|
const auto& region = memMap.regions[i];
|
||||||
|
if (region.kind == montauk::boot::MemoryKind::Reserved
|
||||||
|
|| region.kind == montauk::boot::MemoryKind::Unknown) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t start = region.base & ~0xFFFULL;
|
||||||
|
uint64_t end = (region.base + region.length + 0xFFF) & ~0xFFFULL;
|
||||||
|
bool framebufferRegion = region.kind == montauk::boot::MemoryKind::Framebuffer;
|
||||||
|
for (uint64_t phys = start; phys < end; phys += 0x1000) {
|
||||||
|
if (framebufferRegion) MapWC(phys, Memory::HHDM(phys));
|
||||||
|
else Map(phys, Memory::HHDM(phys));
|
||||||
|
}
|
||||||
|
if (end > maxPhysAddr) maxPhysAddr = end;
|
||||||
|
}
|
||||||
|
g_hhdmLimit = maxPhysAddr;
|
||||||
|
|
||||||
|
// The framebuffer may not be listed in the memory map at all.
|
||||||
|
if (framebuffer.address != 0) {
|
||||||
|
uint64_t fbPhys = Memory::SubHHDM(framebuffer.address) & ~0xFFFULL;
|
||||||
|
uint64_t fbPages = (framebuffer.height * framebuffer.pitch + 0xFFF) / 0x1000;
|
||||||
|
for (uint64_t p = 0; p < fbPages; p++) {
|
||||||
|
uint64_t phys = fbPhys + p * 0x1000;
|
||||||
|
MapWC(phys, Memory::HHDM(phys));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The bootloader wrote the image through the data cache. Push it to
|
||||||
|
// the point of unification and drop every instruction-cache line
|
||||||
|
// before the kernel relies on fetching code it has not run yet: a
|
||||||
|
// line still dirty in L1D is invisible to instruction fetch, which
|
||||||
|
// then reads stale DRAM (an "unknown reason" exception on a valid
|
||||||
|
// instruction).
|
||||||
|
Arch::SyncInstructionCache((const void*)kernelBaseVirt, kernelSize);
|
||||||
|
|
||||||
|
Arch::ActivateKernelTranslation();
|
||||||
|
|
||||||
|
Kt::KernelLogStream(Kt::OK, "VMM") << "Switched to kernel translation tables (HHDM limit 0x"
|
||||||
|
<< base::hex << g_hhdmLimit << ")";
|
||||||
|
}
|
||||||
|
|
||||||
|
static void MapKernelPage(uint64_t root, uint64_t phys, uint64_t va, uint64_t attr, const char* what) {
|
||||||
|
if (va % 0x1000 != 0 || phys % 0x1000 != 0) {
|
||||||
|
Panic(what, nullptr);
|
||||||
|
}
|
||||||
|
volatile Descriptor* entry = WalkCreate(KernelRootFor(root, va), va);
|
||||||
|
if (entry == nullptr) {
|
||||||
|
Panic("OOM in Paging (kernel page table allocation)", nullptr);
|
||||||
|
}
|
||||||
|
SetLeaf(entry, KernelLeaf(phys, attr), va);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Paging::Map(std::uint64_t physicalAddress, std::uint64_t virtualAddress) {
|
||||||
|
MapKernelPage(rootPhys, physicalAddress, virtualAddress, AttrNormal,
|
||||||
|
"Value that isn't page-aligned passed as address to Paging::Map!");
|
||||||
|
}
|
||||||
|
|
||||||
|
void Paging::MapMMIO(std::uint64_t physicalAddress, std::uint64_t virtualAddress) {
|
||||||
|
MapKernelPage(rootPhys, physicalAddress, virtualAddress, AttrDevice,
|
||||||
|
"Value that isn't page-aligned passed as address to Paging::MapMMIO!");
|
||||||
|
}
|
||||||
|
|
||||||
|
void Paging::MapWC(std::uint64_t physicalAddress, std::uint64_t virtualAddress) {
|
||||||
|
MapKernelPage(rootPhys, physicalAddress, virtualAddress, AttrNormalNc,
|
||||||
|
"Value that isn't page-aligned passed as address to Paging::MapWC!");
|
||||||
|
}
|
||||||
|
|
||||||
|
void Paging::MapUser(std::uint64_t physicalAddress, std::uint64_t virtualAddress) {
|
||||||
|
if (virtualAddress % 0x1000 != 0 || physicalAddress % 0x1000 != 0) {
|
||||||
|
Panic("Value that isn't page-aligned passed as address to Paging::MapUser!", nullptr);
|
||||||
|
}
|
||||||
|
volatile Descriptor* entry = WalkCreate(rootPhys, virtualAddress);
|
||||||
|
if (entry == nullptr) {
|
||||||
|
Panic("OOM in Paging::MapUser (page table allocation)", nullptr);
|
||||||
|
}
|
||||||
|
SetLeaf(entry, UserLeaf(physicalAddress, AttrNormal, true, true), virtualAddress);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::uint64_t Paging::GetPhysAddr(std::uint64_t root, std::uint64_t virtualAddress) {
|
||||||
|
if (virtualAddress >= KernelHalfBase) {
|
||||||
|
root = g_kernelHalfRoot;
|
||||||
|
}
|
||||||
|
return Translate(root, virtualAddress);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::uint64_t Paging::GetPhysAddr(std::uint64_t virtualAddress) {
|
||||||
|
return GetPhysAddr(rootPhys, virtualAddress);
|
||||||
|
}
|
||||||
|
|
||||||
|
// The kernel half lives in TTBR1, so a new address space starts empty.
|
||||||
|
std::uint64_t Paging::CreateUserAddressSpace() {
|
||||||
|
void* page = Memory::g_pfa->AllocateZeroed();
|
||||||
|
if (page == nullptr) return 0;
|
||||||
|
return Memory::SubHHDM(page);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Paging::MapUserIn(std::uint64_t root, std::uint64_t physicalAddress, std::uint64_t virtualAddress) {
|
||||||
|
return MapUserInPermissions(root, physicalAddress, virtualAddress, true, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool MapUserLeaf(uint64_t root, uint64_t phys, uint64_t va, Descriptor desc, const char* what) {
|
||||||
|
pagingLock.Acquire();
|
||||||
|
if (va % 0x1000 != 0 || phys % 0x1000 != 0) {
|
||||||
|
pagingLock.Release();
|
||||||
|
Panic(what, nullptr);
|
||||||
|
}
|
||||||
|
volatile Descriptor* entry = WalkCreate(root, va);
|
||||||
|
if (entry == nullptr) {
|
||||||
|
pagingLock.Release();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
SetLeaf(entry, desc, va);
|
||||||
|
pagingLock.Release();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Paging::MapUserInPermissions(std::uint64_t root, std::uint64_t physicalAddress,
|
||||||
|
std::uint64_t virtualAddress, bool writable, bool executable) {
|
||||||
|
return MapUserLeaf(root, physicalAddress, virtualAddress,
|
||||||
|
UserLeaf(physicalAddress, AttrNormal, writable, executable),
|
||||||
|
"Non-aligned address in Paging::MapUserIn!");
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Paging::MapUserInWC(std::uint64_t root, std::uint64_t physicalAddress, std::uint64_t virtualAddress) {
|
||||||
|
return MapUserLeaf(root, physicalAddress, virtualAddress,
|
||||||
|
UserLeaf(physicalAddress, AttrNormalNc, true, false),
|
||||||
|
"Non-aligned address in Paging::MapUserInWC!");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Permission changes need no break-before-make, only the TLB flush.
|
||||||
|
bool Paging::ProtectUserIn(std::uint64_t root, std::uint64_t virtualAddress,
|
||||||
|
bool writable, bool executable) {
|
||||||
|
if ((virtualAddress & 0xFFFULL) != 0) return false;
|
||||||
|
pagingLock.Acquire();
|
||||||
|
volatile Descriptor* entry = WalkExisting(root, virtualAddress);
|
||||||
|
if (entry == nullptr || !IsUserLeaf(*entry)) {
|
||||||
|
pagingLock.Release();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Descriptor desc = *entry;
|
||||||
|
if (writable) desc &= ~DescApReadOnly;
|
||||||
|
else desc |= DescApReadOnly;
|
||||||
|
if (executable) desc &= ~DescUxn;
|
||||||
|
else desc |= DescUxn;
|
||||||
|
*entry = desc;
|
||||||
|
Arch::FlushTlbPage(virtualAddress);
|
||||||
|
pagingLock.Release();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Paging::UnmapUserIn(std::uint64_t root, std::uint64_t virtualAddress) {
|
||||||
|
pagingLock.Acquire();
|
||||||
|
volatile Descriptor* entry = WalkExisting(root, virtualAddress);
|
||||||
|
if (entry != nullptr && (*entry & DescValid)) {
|
||||||
|
*entry = 0;
|
||||||
|
Arch::FlushTlbPage(virtualAddress);
|
||||||
|
}
|
||||||
|
pagingLock.Release();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Paging::FreeUserHalf(std::uint64_t root) {
|
||||||
|
// User space is the lower 128 TiB: level-0 entries 0-255.
|
||||||
|
for (int i0 = 0; i0 < 256; i0++) {
|
||||||
|
Descriptor d0 = TableAt(root)[i0];
|
||||||
|
if ((d0 & DescValid) == 0) continue;
|
||||||
|
uint64_t l1 = d0 & DescAddrMask;
|
||||||
|
|
||||||
|
for (int i1 = 0; i1 < 512; i1++) {
|
||||||
|
Descriptor d1 = TableAt(l1)[i1];
|
||||||
|
if ((d1 & DescValid) == 0) continue;
|
||||||
|
uint64_t l2 = d1 & DescAddrMask;
|
||||||
|
|
||||||
|
for (int i2 = 0; i2 < 512; i2++) {
|
||||||
|
Descriptor d2 = TableAt(l2)[i2];
|
||||||
|
if ((d2 & DescValid) == 0) continue;
|
||||||
|
uint64_t l3 = d2 & DescAddrMask;
|
||||||
|
|
||||||
|
for (int i3 = 0; i3 < 512; i3++) {
|
||||||
|
Descriptor leaf = TableAt(l3)[i3];
|
||||||
|
if ((leaf & DescValid) == 0) continue;
|
||||||
|
|
||||||
|
// Write-combining/device pages are not the process's.
|
||||||
|
if (((leaf & DescAttrMask) >> DescAttrShift) != AttrNormal) continue;
|
||||||
|
|
||||||
|
uint64_t pagePhys = leaf & DescAddrMask;
|
||||||
|
if (pagePhys != 0) {
|
||||||
|
Memory::g_pfa->Free((void*)Memory::HHDM(pagePhys));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Memory::g_pfa->Free((void*)Memory::HHDM(l3));
|
||||||
|
}
|
||||||
|
|
||||||
|
Memory::g_pfa->Free((void*)Memory::HHDM(l2));
|
||||||
|
}
|
||||||
|
|
||||||
|
Memory::g_pfa->Free((void*)Memory::HHDM(l1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Paging::IsUserRangeAccessible(std::uint64_t root, std::uint64_t virtualAddress,
|
||||||
|
std::uint64_t size, bool requireWrite) {
|
||||||
|
if (size == 0) return true;
|
||||||
|
if (virtualAddress >= UserSpaceEnd) return false;
|
||||||
|
if (virtualAddress + size < virtualAddress) return false;
|
||||||
|
if (virtualAddress + size > UserSpaceEnd) return false;
|
||||||
|
|
||||||
|
uint64_t page = virtualAddress & ~0xFFFULL;
|
||||||
|
uint64_t lastPage = (virtualAddress + size - 1) & ~0xFFFULL;
|
||||||
|
for (;;) {
|
||||||
|
volatile Descriptor* entry = WalkExisting(root, page);
|
||||||
|
if (entry == nullptr) return false;
|
||||||
|
Descriptor desc = *entry;
|
||||||
|
if (!IsUserLeaf(desc)) return false;
|
||||||
|
if (requireWrite && (desc & DescApReadOnly)) return false;
|
||||||
|
if (page == lastPage) break;
|
||||||
|
page += 0x1000;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
/*
|
||||||
|
* PanicFrame.cpp
|
||||||
|
* aarch64 CPU state for the panic screen
|
||||||
|
* Copyright (c) 2026 Daniel Hammer
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <Common/Panic.hpp>
|
||||||
|
#include <Arch/aarch64/InterruptFrame.hpp>
|
||||||
|
#include <CppLib/BoxUI.hpp>
|
||||||
|
|
||||||
|
static constexpr int BoxWidth = PanicBoxWidth;
|
||||||
|
|
||||||
|
namespace Arch {
|
||||||
|
void PrintPanicFrame(PanicFrame* frame) {
|
||||||
|
PrintBoxedSeparator(kerr, BoxWidth);
|
||||||
|
PrintBoxedLine(kerr, "CPU State:", BoxWidth, true);
|
||||||
|
PrintBoxedHex(kerr, "Exception Class (ESR)", frame->esr >> 26, BoxWidth);
|
||||||
|
PrintBoxedHex(kerr, "Syndrome (ESR)", frame->esr, BoxWidth);
|
||||||
|
PrintBoxedHex(kerr, "Fault Address (FAR)", frame->far, BoxWidth);
|
||||||
|
|
||||||
|
PrintBoxedSeparator(kerr, BoxWidth);
|
||||||
|
PrintBoxedLine(kerr, "Registers:", BoxWidth, true);
|
||||||
|
PrintBoxedHex(kerr, "Program Counter (ELR)", frame->elr, BoxWidth);
|
||||||
|
PrintBoxedHex(kerr, "PSTATE (SPSR)", frame->spsr, BoxWidth);
|
||||||
|
PrintBoxedHex(kerr, "Stack Pointer", frame->sp, BoxWidth);
|
||||||
|
PrintBoxedHex(kerr, "Link Register (x30)", frame->x[30], BoxWidth);
|
||||||
|
|
||||||
|
// An "unknown reason" exception on a kernel PC: show the word the
|
||||||
|
// data side sees there. A valid instruction means instruction fetch
|
||||||
|
// saw something else (cache maintenance); garbage means the code
|
||||||
|
// itself was overwritten. The fetch succeeded, so the page is mapped.
|
||||||
|
if ((frame->esr >> 26) == 0 && frame->elr >= 0xFFFF000000000000ULL && (frame->elr & 3) == 0) {
|
||||||
|
PrintBoxedHex(kerr, "Instruction at PC", *(volatile uint32_t*)frame->elr, BoxWidth);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
/*
|
||||||
|
* PerCpu.hpp
|
||||||
|
* aarch64 per-CPU state (see Arch/Smp.hpp)
|
||||||
|
* Copyright (c) 2026 Daniel Hammer
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
namespace Arch {
|
||||||
|
|
||||||
|
// Read by the exception vectors through TPIDR_EL1.
|
||||||
|
struct CpuEntryState {
|
||||||
|
uint64_t kernelStackTop; // SP_EL1 to use on entry from EL0
|
||||||
|
uint64_t scratch; // vector-private scratch slot
|
||||||
|
};
|
||||||
|
|
||||||
|
struct CpuState {
|
||||||
|
uint64_t mpidr; // MPIDR_EL1 affinity of this CPU
|
||||||
|
uint8_t gicTargetMask; // GICv2 CPU-interface bit (SGI/SPI targets)
|
||||||
|
uint64_t timerPeriod; // tick period in counter ticks, 0 = stopped
|
||||||
|
};
|
||||||
|
|
||||||
|
// TPIDR_EL1 holds the CpuData pointer (zeroed in EarlyInitialize until
|
||||||
|
// Smp::InitBsp installs it).
|
||||||
|
inline void* CurrentCpuLocal() {
|
||||||
|
void* ptr;
|
||||||
|
asm volatile("mrs %0, tpidr_el1" : "=r"(ptr));
|
||||||
|
return ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void* TryCurrentCpuLocal() {
|
||||||
|
return CurrentCpuLocal();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,140 @@
|
|||||||
|
/*
|
||||||
|
* Platform.cpp
|
||||||
|
* aarch64 bring-up hooks (implements Arch/Platform.hpp)
|
||||||
|
* Copyright (c) 2026 Daniel Hammer
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <Arch/Platform.hpp>
|
||||||
|
#include <Arch/Cpu.hpp>
|
||||||
|
#include <Arch/Smp.hpp>
|
||||||
|
#include <Arch/aarch64/Gic.hpp>
|
||||||
|
#include <Arch/aarch64/Bringup.hpp>
|
||||||
|
#include <ACPI/FADT.hpp>
|
||||||
|
#include <Terminal/Terminal.hpp>
|
||||||
|
#include <CppLib/Stream.hpp>
|
||||||
|
|
||||||
|
using namespace Kt;
|
||||||
|
|
||||||
|
extern "C" char ArchExceptionVectors[]; // Vectors.S
|
||||||
|
|
||||||
|
namespace Arch {
|
||||||
|
|
||||||
|
// SCTLR_EL1.WXN: writable memory is execute-never
|
||||||
|
static constexpr uint64_t SctlrWxn = 1ULL << 19;
|
||||||
|
// SCTLR_EL1.UCT/UCI: EL0 may read CTR_EL0 and clean/invalidate caches
|
||||||
|
// by address (what __builtin___clear_cache needs for generated code).
|
||||||
|
static constexpr uint64_t SctlrUct = 1ULL << 15;
|
||||||
|
static constexpr uint64_t SctlrUci = 1ULL << 26;
|
||||||
|
|
||||||
|
// PSCI 0.2+ function IDs (SMC32 calling convention)
|
||||||
|
static constexpr uint64_t PsciSystemOff = 0x84000008;
|
||||||
|
static constexpr uint64_t PsciSystemReset = 0x84000009;
|
||||||
|
|
||||||
|
// How the firmware takes PSCI calls, from the FADT's ARM_BOOT_ARCH.
|
||||||
|
enum class PsciConduit { None, Smc, Hvc };
|
||||||
|
static PsciConduit g_psci = PsciConduit::None;
|
||||||
|
|
||||||
|
static uint64_t PsciCall(uint64_t function) {
|
||||||
|
register uint64_t x0 asm("x0") = function;
|
||||||
|
register uint64_t x1 asm("x1") = 0;
|
||||||
|
register uint64_t x2 asm("x2") = 0;
|
||||||
|
register uint64_t x3 asm("x3") = 0;
|
||||||
|
if (g_psci == PsciConduit::Hvc) {
|
||||||
|
asm volatile("hvc #0" : "+r"(x0), "+r"(x1), "+r"(x2), "+r"(x3) :: "memory");
|
||||||
|
} else if (g_psci == PsciConduit::Smc) {
|
||||||
|
asm volatile("smc #0" : "+r"(x0), "+r"(x1), "+r"(x2), "+r"(x3) :: "memory");
|
||||||
|
}
|
||||||
|
return x0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void InitializePsci(Hal::ACPI::CommonSDTHeader* xsdt) {
|
||||||
|
Hal::FADT::ParsedFADT fadt{};
|
||||||
|
if (!Hal::FADT::Parse(xsdt, fadt) || !fadt.Valid) return;
|
||||||
|
if (!(fadt.ArmBootArchFlags & Hal::FADT::ArmBootPsciCompliant)) {
|
||||||
|
KernelLogStream(WARNING, "PSCI") << "Firmware is not PSCI compliant: no reset or power-off";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
g_psci = (fadt.ArmBootArchFlags & Hal::FADT::ArmBootPsciUseHvc) ? PsciConduit::Hvc : PsciConduit::Smc;
|
||||||
|
KernelLogStream(OK, "PSCI") << "PSCI through " << (g_psci == PsciConduit::Hvc ? "HVC" : "SMC");
|
||||||
|
}
|
||||||
|
|
||||||
|
void InitializeCpuRegisters() {
|
||||||
|
// Run on SP_EL1 so exceptions taken from the kernel arrive on the
|
||||||
|
// stack it is already using (Vectors.S has no SP_EL0 handlers).
|
||||||
|
asm volatile("mov x9, sp\n\tmsr spsel, #1\n\tmov sp, x9" ::: "x9", "memory");
|
||||||
|
|
||||||
|
// The vectors need no allocation, so take over from the
|
||||||
|
// bootloader's right away.
|
||||||
|
asm volatile("msr vbar_el1, %0\n\tisb" :: "r"(ArchExceptionVectors) : "memory");
|
||||||
|
|
||||||
|
// Kernel mappings are RWX (Paging.cpp), so writable must not
|
||||||
|
// imply execute-never; user code may maintain its own caches.
|
||||||
|
uint64_t sctlr;
|
||||||
|
asm volatile("mrs %0, sctlr_el1" : "=r"(sctlr));
|
||||||
|
sctlr &= ~SctlrWxn;
|
||||||
|
sctlr |= SctlrUct | SctlrUci;
|
||||||
|
asm volatile("msr sctlr_el1, %0\n\tisb" :: "r"(sctlr));
|
||||||
|
|
||||||
|
// CPACR_EL1.FPEN = 0b11: FP/SIMD usable at EL0 and EL1 (the kernel
|
||||||
|
// itself is built -mgeneral-regs-only; Context.S saves user state).
|
||||||
|
uint64_t cpacr;
|
||||||
|
asm volatile("mrs %0, cpacr_el1" : "=r"(cpacr));
|
||||||
|
cpacr |= 3ULL << 20;
|
||||||
|
asm volatile("msr cpacr_el1, %0\n\tisb" :: "r"(cpacr));
|
||||||
|
}
|
||||||
|
|
||||||
|
void EarlyInitialize() {
|
||||||
|
// No per-CPU data yet: Smp::TryGetCurrentCpuData must see null.
|
||||||
|
asm volatile("msr tpidr_el1, xzr");
|
||||||
|
|
||||||
|
InitializeCpuRegisters();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Installed by EarlyInitialize.
|
||||||
|
void InitializeTraps() {
|
||||||
|
KernelLogStream(OK, "Arch") << "Installed exception vectors at 0x" << base::hex
|
||||||
|
<< (uint64_t)ArchExceptionVectors;
|
||||||
|
}
|
||||||
|
|
||||||
|
// MAIR_EL1 attribute indices are chosen together with the page-table
|
||||||
|
// format (Paging.cpp), so there is nothing to program separately.
|
||||||
|
void InitializeMemoryAttributes() {
|
||||||
|
}
|
||||||
|
|
||||||
|
void InitializeInterrupts(Hal::ACPI::CommonSDTHeader* xsdt) {
|
||||||
|
Smp::InitBsp();
|
||||||
|
if (!Gic::Initialize(xsdt)) {
|
||||||
|
KernelLogStream(ERROR, "Arch") << "aarch64: no interrupt controller, running without IRQs";
|
||||||
|
}
|
||||||
|
InitializePsci(xsdt);
|
||||||
|
}
|
||||||
|
|
||||||
|
void InitializePlatformDevices() {
|
||||||
|
}
|
||||||
|
|
||||||
|
// EL0 entry needs no global state beyond Arch::ActivateThread.
|
||||||
|
void PrepareUserMode() {
|
||||||
|
}
|
||||||
|
|
||||||
|
void LateInitialize() {
|
||||||
|
}
|
||||||
|
|
||||||
|
void ListPlatformDevices(PlatformDeviceSink sink, void* context) {
|
||||||
|
if (Gic::IsInitialized()) {
|
||||||
|
sink(context, 1, "GICv2", "ARM Generic Interrupt Controller");
|
||||||
|
sink(context, 2, "Generic Timer", "ARM architected timer (EL1 virtual)");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Reboot() {
|
||||||
|
PsciCall(PsciSystemReset);
|
||||||
|
HaltForever();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PowerOff() {
|
||||||
|
if (g_psci != PsciConduit::None) {
|
||||||
|
PsciCall(PsciSystemOff);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,135 @@
|
|||||||
|
/*
|
||||||
|
* Smp.cpp
|
||||||
|
* aarch64 per-CPU data and secondary CPU start-up (implements the arch half of Arch/Smp.hpp)
|
||||||
|
* Copyright (c) 2026 Daniel Hammer
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <Arch/Smp.hpp>
|
||||||
|
#include <Arch/Cpu.hpp>
|
||||||
|
#include <Arch/Syscall.hpp>
|
||||||
|
#include <Arch/aarch64/Bringup.hpp>
|
||||||
|
#include <Arch/aarch64/Gic.hpp>
|
||||||
|
#include <Boot/BootInfo.hpp>
|
||||||
|
#include <Libraries/Memory.hpp>
|
||||||
|
#include <Terminal/Terminal.hpp>
|
||||||
|
#include <CppLib/Stream.hpp>
|
||||||
|
#include <Timekeeping/Timer.hpp>
|
||||||
|
|
||||||
|
using namespace Kt;
|
||||||
|
|
||||||
|
namespace Smp {
|
||||||
|
|
||||||
|
static CpuData g_cpus[MaxCPUs];
|
||||||
|
static int g_cpuCount = 0;
|
||||||
|
static int g_detectedCpuCount = 1;
|
||||||
|
|
||||||
|
CpuData* GetCpuData(int index) {
|
||||||
|
if (index < 0 || index >= g_cpuCount) return nullptr;
|
||||||
|
return &g_cpus[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
int GetCpuCount() {
|
||||||
|
return g_cpuCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
int GetDetectedCpuCount() {
|
||||||
|
return g_detectedCpuCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
void InitBsp() {
|
||||||
|
memset(g_cpus, 0, sizeof(g_cpus));
|
||||||
|
|
||||||
|
CpuData& bsp = g_cpus[0];
|
||||||
|
bsp.selfPtr = (uint64_t)&bsp;
|
||||||
|
bsp.cpuIndex = 0;
|
||||||
|
bsp.currentSlot = -1;
|
||||||
|
bsp.started = true;
|
||||||
|
asm volatile("mrs %0, mpidr_el1" : "=r"(bsp.arch.mpidr));
|
||||||
|
|
||||||
|
asm volatile("msr tpidr_el1, %0" :: "r"(&bsp));
|
||||||
|
g_cpuCount = 1;
|
||||||
|
|
||||||
|
KernelLogStream(OK, "SMP") << "BSP initialized (MPIDR " << base::hex
|
||||||
|
<< bsp.arch.mpidr << ")";
|
||||||
|
}
|
||||||
|
|
||||||
|
// A secondary CPU, woken by the bootloader on its own stack and still
|
||||||
|
// on the bootloader's translation tables. The kernel stashed this CPU's
|
||||||
|
// CpuData in extraArgument.
|
||||||
|
static void ApEntry(montauk::boot::BootCpu* bootCpu) {
|
||||||
|
CpuData* cpu = (CpuData*)bootCpu->extraArgument;
|
||||||
|
|
||||||
|
Arch::InitializeCpuRegisters();
|
||||||
|
Arch::ActivateKernelTranslation();
|
||||||
|
asm volatile("msr tpidr_el1, %0" :: "r"(cpu));
|
||||||
|
|
||||||
|
Arch::Gic::InitializeCpuInterface();
|
||||||
|
Arch::InitializeSyscallEntry();
|
||||||
|
Timekeeping::TimerInitializeAP();
|
||||||
|
|
||||||
|
__atomic_store_n(&cpu->started, true, __ATOMIC_RELEASE);
|
||||||
|
Arch::EnableInterrupts();
|
||||||
|
|
||||||
|
// Runnable work arrives as a reschedule IPI; keep the periodic tick
|
||||||
|
// masked while idle.
|
||||||
|
for (;;) {
|
||||||
|
Timekeeping::TimerEnterApIdle();
|
||||||
|
Timekeeping::ServiceDeferredWork();
|
||||||
|
Arch::DisableInterrupts();
|
||||||
|
Arch::HaltWithInterruptsDisabled();
|
||||||
|
Arch::EnableInterrupts();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void BootAPs(const montauk::boot::SmpInfo& smp) {
|
||||||
|
if (smp.cpus == nullptr || smp.startCpu == nullptr || smp.cpuCount <= 1) {
|
||||||
|
KernelLogStream(INFO, "SMP") << "Single CPU system - no secondary CPUs to start";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
g_detectedCpuCount = (int)smp.cpuCount;
|
||||||
|
|
||||||
|
if (!Arch::Gic::IsInitialized()) {
|
||||||
|
KernelLogStream(WARNING, "SMP") << "No interrupt controller: secondary CPUs left parked";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int apIndex = 1;
|
||||||
|
for (uint64_t i = 0; i < smp.cpuCount && apIndex < MaxCPUs; i++) {
|
||||||
|
montauk::boot::BootCpu& info = smp.cpus[i];
|
||||||
|
if (info.hardwareId == smp.bspHardwareId) continue;
|
||||||
|
|
||||||
|
CpuData& ap = g_cpus[apIndex];
|
||||||
|
ap.selfPtr = (uint64_t)≈
|
||||||
|
ap.cpuIndex = apIndex;
|
||||||
|
ap.currentSlot = -1;
|
||||||
|
ap.started = false;
|
||||||
|
ap.arch.mpidr = info.hardwareId;
|
||||||
|
|
||||||
|
info.extraArgument = (uint64_t)≈
|
||||||
|
// Publish the CpuData before the CPU that reads it is woken.
|
||||||
|
asm volatile("dsb ish" ::: "memory");
|
||||||
|
smp.startCpu(&info, ApEntry);
|
||||||
|
apIndex++;
|
||||||
|
}
|
||||||
|
g_cpuCount = apIndex;
|
||||||
|
|
||||||
|
int online = 1;
|
||||||
|
for (int i = 1; i < g_cpuCount; i++) {
|
||||||
|
uint64_t start = Arch::ReadTimestamp();
|
||||||
|
uint64_t frequency;
|
||||||
|
asm volatile("mrs %0, cntfrq_el0" : "=r"(frequency));
|
||||||
|
while (!__atomic_load_n(&g_cpus[i].started, __ATOMIC_ACQUIRE) &&
|
||||||
|
Arch::ReadTimestamp() - start < frequency) {
|
||||||
|
Arch::Pause();
|
||||||
|
}
|
||||||
|
if (g_cpus[i].started) {
|
||||||
|
online++;
|
||||||
|
} else {
|
||||||
|
KernelLogStream(ERROR, "SMP") << "CPU " << base::dec << (uint64_t)i
|
||||||
|
<< " (MPIDR " << base::hex << g_cpus[i].arch.mpidr << ") failed to start";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
KernelLogStream(OK, "SMP") << base::dec << (uint64_t)online << " CPU(s) online";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
/*
|
||||||
|
* Syscall.cpp
|
||||||
|
* aarch64 system-call entry (implements Arch/Syscall.hpp)
|
||||||
|
* Copyright (c) 2026 Daniel Hammer
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <Arch/Syscall.hpp>
|
||||||
|
|
||||||
|
namespace Arch {
|
||||||
|
|
||||||
|
// `svc #0` arrives through the EL0 synchronous vector installed by
|
||||||
|
// InitializeTraps; there is no per-CPU register to program.
|
||||||
|
void InitializeSyscallEntry() {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
/*
|
||||||
|
* SyscallFrame.hpp
|
||||||
|
* aarch64 system-call frame (see Arch/Syscall.hpp)
|
||||||
|
* Copyright (c) 2026 Daniel Hammer
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
namespace Arch {
|
||||||
|
|
||||||
|
// The exception frame Vectors.S saves for `svc #0` (the same layout as
|
||||||
|
// Arch::PanicFrame): x0-x5 carry the arguments and x8 the system-call
|
||||||
|
// number; the result is written back to x0.
|
||||||
|
struct SyscallFrame {
|
||||||
|
uint64_t arg1, arg2, arg3, arg4, arg5, arg6; // x0-x5
|
||||||
|
uint64_t x6, x7;
|
||||||
|
uint64_t syscall_nr; // x8
|
||||||
|
uint64_t x9_x30[22];
|
||||||
|
uint64_t sp_el0, elr, spsr, esr, far;
|
||||||
|
};
|
||||||
|
|
||||||
|
static_assert(sizeof(SyscallFrame) == 288, "must match Vectors.S");
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,116 @@
|
|||||||
|
/*
|
||||||
|
* Timer.cpp
|
||||||
|
* aarch64 tick timer: the EL1 virtual generic timer (implements Arch/Timer.hpp)
|
||||||
|
* Copyright (c) 2026 Daniel Hammer
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <Arch/Timer.hpp>
|
||||||
|
#include <Arch/Interrupts.hpp>
|
||||||
|
#include <Arch/Cpu.hpp>
|
||||||
|
#include <Arch/Smp.hpp>
|
||||||
|
#include <Arch/aarch64/Gic.hpp>
|
||||||
|
#include <ACPI/ACPI.hpp>
|
||||||
|
#include <Terminal/Terminal.hpp>
|
||||||
|
#include <CppLib/Stream.hpp>
|
||||||
|
|
||||||
|
// The generic timer counts at CNTFRQ_EL0 on every CPU, so it needs no
|
||||||
|
// calibration. Each CPU's virtual timer raises a PPI (the GTDT names it,
|
||||||
|
// normally INTID 27); the tick is re-armed from the interrupt's ack hook,
|
||||||
|
// which also drops the level-triggered line before the EOI.
|
||||||
|
|
||||||
|
using namespace Kt;
|
||||||
|
|
||||||
|
namespace Arch::Timer {
|
||||||
|
|
||||||
|
// CNTV_CTL_EL0
|
||||||
|
static constexpr uint64_t CtlEnable = 1ULL << 0;
|
||||||
|
static constexpr uint64_t CtlMask = 1ULL << 1;
|
||||||
|
|
||||||
|
// GTDT timer flags
|
||||||
|
static constexpr uint32_t GtdtEdgeTriggered = 1u << 0;
|
||||||
|
|
||||||
|
static constexpr uint32_t DefaultVirtualTimerIntid = 27;
|
||||||
|
|
||||||
|
struct Gtdt {
|
||||||
|
Hal::ACPI::CommonSDTHeader Header;
|
||||||
|
uint64_t CntControlBase;
|
||||||
|
uint32_t Reserved;
|
||||||
|
uint32_t SecureEl1Gsiv;
|
||||||
|
uint32_t SecureEl1Flags;
|
||||||
|
uint32_t NonSecureEl1Gsiv;
|
||||||
|
uint32_t NonSecureEl1Flags;
|
||||||
|
uint32_t VirtualTimerGsiv;
|
||||||
|
uint32_t VirtualTimerFlags;
|
||||||
|
} __attribute__((packed));
|
||||||
|
|
||||||
|
static uint64_t g_ticksPerMs = 0;
|
||||||
|
static uint32_t g_intid = DefaultVirtualTimerIntid;
|
||||||
|
static bool g_edgeTriggered = false;
|
||||||
|
|
||||||
|
static inline void WriteCtl(uint64_t value) {
|
||||||
|
asm volatile("msr cntv_ctl_el0, %0\n\tisb" :: "r"(value) : "memory");
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline uint64_t ReadCval() {
|
||||||
|
uint64_t value;
|
||||||
|
asm volatile("mrs %0, cntv_cval_el0" : "=r"(value));
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void WriteCval(uint64_t value) {
|
||||||
|
asm volatile("msr cntv_cval_el0, %0\n\tisb" :: "r"(value) : "memory");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Advance the deadline by one period from the previous one (no drift),
|
||||||
|
// skipping ticks the CPU missed rather than replaying them.
|
||||||
|
static void Rearm() {
|
||||||
|
uint64_t period = Smp::GetCurrentCpuData()->arch.timerPeriod;
|
||||||
|
if (period == 0) {
|
||||||
|
WriteCtl(CtlMask);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
uint64_t now = ReadTimestamp();
|
||||||
|
uint64_t next = ReadCval() + period;
|
||||||
|
if ((int64_t)(next - now) <= 0) next = now + period;
|
||||||
|
WriteCval(next);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ReadGtdt() {
|
||||||
|
auto* gtdt = (Gtdt*)Gic::FindTable(Gic::CurrentXsdt(), "GTDT");
|
||||||
|
if (gtdt == nullptr || gtdt->Header.Length < sizeof(Gtdt)) return;
|
||||||
|
if (gtdt->VirtualTimerGsiv >= 16 && gtdt->VirtualTimerGsiv < 32) {
|
||||||
|
g_intid = gtdt->VirtualTimerGsiv;
|
||||||
|
g_edgeTriggered = (gtdt->VirtualTimerFlags & GtdtEdgeTriggered) != 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool InitializeBsp(uint32_t periodMs) {
|
||||||
|
uint64_t frequency;
|
||||||
|
asm volatile("mrs %0, cntfrq_el0" : "=r"(frequency));
|
||||||
|
if (frequency < 1000 || !Gic::IsInitialized()) return false;
|
||||||
|
g_ticksPerMs = frequency / 1000;
|
||||||
|
|
||||||
|
ReadGtdt();
|
||||||
|
Gic::SetAckHook(IRQ_TIMER, Rearm);
|
||||||
|
StartPeriodic(periodMs);
|
||||||
|
|
||||||
|
KernelLogStream(OK, "Timer") << "Generic timer: " << base::dec << frequency
|
||||||
|
<< " Hz counter, virtual timer INTID " << (uint64_t)g_intid;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// PPIs are banked: each CPU enables its own timer interrupt.
|
||||||
|
void StartPeriodic(uint32_t periodMs) {
|
||||||
|
Gic::RoutePpi(g_intid, IRQ_TIMER, g_edgeTriggered);
|
||||||
|
uint64_t period = g_ticksPerMs * periodMs;
|
||||||
|
Smp::GetCurrentCpuData()->arch.timerPeriod = period;
|
||||||
|
WriteCval(ReadTimestamp() + period);
|
||||||
|
WriteCtl(CtlEnable);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Stop() {
|
||||||
|
Smp::GetCurrentCpuData()->arch.timerPeriod = 0;
|
||||||
|
WriteCtl(CtlMask);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,201 @@
|
|||||||
|
/*
|
||||||
|
Vectors.S
|
||||||
|
aarch64 exception vector table (VBAR_EL1)
|
||||||
|
Copyright (c) 2026 Daniel Hammer
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Every exception saves the same 288-byte frame on SP_EL1, laid out as
|
||||||
|
* Arch::PanicFrame (InterruptFrame.hpp) and Arch::SyscallFrame:
|
||||||
|
*
|
||||||
|
* 0 x0 .. x30 (x30 at 240)
|
||||||
|
* 248 interrupted SP (SP_EL0 from EL0, the pre-exception SP_EL1 from EL1)
|
||||||
|
* 256 ELR_EL1
|
||||||
|
* 264 SPSR_EL1
|
||||||
|
* 272 ESR_EL1
|
||||||
|
* 280 FAR_EL1
|
||||||
|
*
|
||||||
|
* The kernel runs on SP_EL1 (SPSel = 1, see Platform.cpp). From EL0,
|
||||||
|
* SP_EL1 is the top of the thread's kernel stack: ArchEnterUserMode sets
|
||||||
|
* it before the first ERET and every return to EL0 restores it.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define FRAME_SIZE 288
|
||||||
|
#define FRAME_SP 248
|
||||||
|
#define FRAME_ELR 256
|
||||||
|
#define FRAME_SPSR 264
|
||||||
|
#define FRAME_ESR 272
|
||||||
|
#define FRAME_FAR 280
|
||||||
|
|
||||||
|
.text
|
||||||
|
|
||||||
|
.macro SAVE_FRAME el
|
||||||
|
sub sp, sp, #FRAME_SIZE
|
||||||
|
stp x0, x1, [sp, #0]
|
||||||
|
stp x2, x3, [sp, #16]
|
||||||
|
stp x4, x5, [sp, #32]
|
||||||
|
stp x6, x7, [sp, #48]
|
||||||
|
stp x8, x9, [sp, #64]
|
||||||
|
stp x10, x11, [sp, #80]
|
||||||
|
stp x12, x13, [sp, #96]
|
||||||
|
stp x14, x15, [sp, #112]
|
||||||
|
stp x16, x17, [sp, #128]
|
||||||
|
stp x18, x19, [sp, #144]
|
||||||
|
stp x20, x21, [sp, #160]
|
||||||
|
stp x22, x23, [sp, #176]
|
||||||
|
stp x24, x25, [sp, #192]
|
||||||
|
stp x26, x27, [sp, #208]
|
||||||
|
stp x28, x29, [sp, #224]
|
||||||
|
.if \el == 0
|
||||||
|
mrs x21, sp_el0
|
||||||
|
.else
|
||||||
|
add x21, sp, #FRAME_SIZE
|
||||||
|
.endif
|
||||||
|
stp x30, x21, [sp, #240]
|
||||||
|
mrs x22, elr_el1
|
||||||
|
mrs x23, spsr_el1
|
||||||
|
stp x22, x23, [sp, #FRAME_ELR]
|
||||||
|
mrs x24, esr_el1
|
||||||
|
mrs x25, far_el1
|
||||||
|
stp x24, x25, [sp, #FRAME_ESR]
|
||||||
|
.endm
|
||||||
|
|
||||||
|
/* Interrupts are masked again before ELR/SPSR are reloaded: an IRQ taken
|
||||||
|
in between would overwrite them. */
|
||||||
|
.macro RESTORE_FRAME el
|
||||||
|
msr daifset, #2
|
||||||
|
ldp x22, x23, [sp, #FRAME_ELR]
|
||||||
|
msr elr_el1, x22
|
||||||
|
msr spsr_el1, x23
|
||||||
|
.if \el == 0
|
||||||
|
ldr x21, [sp, #FRAME_SP]
|
||||||
|
msr sp_el0, x21
|
||||||
|
.endif
|
||||||
|
ldp x0, x1, [sp, #0]
|
||||||
|
ldp x2, x3, [sp, #16]
|
||||||
|
ldp x4, x5, [sp, #32]
|
||||||
|
ldp x6, x7, [sp, #48]
|
||||||
|
ldp x8, x9, [sp, #64]
|
||||||
|
ldp x10, x11, [sp, #80]
|
||||||
|
ldp x12, x13, [sp, #96]
|
||||||
|
ldp x14, x15, [sp, #112]
|
||||||
|
ldp x16, x17, [sp, #128]
|
||||||
|
ldp x18, x19, [sp, #144]
|
||||||
|
ldp x20, x21, [sp, #160]
|
||||||
|
ldp x22, x23, [sp, #176]
|
||||||
|
ldp x24, x25, [sp, #192]
|
||||||
|
ldp x26, x27, [sp, #208]
|
||||||
|
ldp x28, x29, [sp, #224]
|
||||||
|
ldr x30, [sp, #240]
|
||||||
|
add sp, sp, #FRAME_SIZE
|
||||||
|
eret
|
||||||
|
.endm
|
||||||
|
|
||||||
|
/* Each vector slot is 128 bytes; the handlers live outside the table. */
|
||||||
|
.macro VECTOR target
|
||||||
|
.balign 0x80
|
||||||
|
b \target
|
||||||
|
.endm
|
||||||
|
|
||||||
|
.balign 0x800
|
||||||
|
.global ArchExceptionVectors
|
||||||
|
ArchExceptionVectors:
|
||||||
|
/* Current EL with SP_EL0: the kernel never runs on SP_EL0. */
|
||||||
|
VECTOR el1t_unexpected
|
||||||
|
VECTOR el1t_unexpected
|
||||||
|
VECTOR el1t_unexpected
|
||||||
|
VECTOR el1t_unexpected
|
||||||
|
/* Current EL with SP_ELx */
|
||||||
|
VECTOR el1h_sync
|
||||||
|
VECTOR el1h_irq
|
||||||
|
VECTOR el1h_fiq
|
||||||
|
VECTOR el1h_serror
|
||||||
|
/* Lower EL, AArch64 */
|
||||||
|
VECTOR el0_sync
|
||||||
|
VECTOR el0_irq
|
||||||
|
VECTOR el0_fiq
|
||||||
|
VECTOR el0_serror
|
||||||
|
/* Lower EL, AArch32: not supported */
|
||||||
|
VECTOR el0_aarch32
|
||||||
|
VECTOR el0_aarch32
|
||||||
|
VECTOR el0_aarch32
|
||||||
|
VECTOR el0_aarch32
|
||||||
|
|
||||||
|
/* ---- Exceptions from the kernel ---- */
|
||||||
|
|
||||||
|
el1t_unexpected:
|
||||||
|
SAVE_FRAME 1
|
||||||
|
mov x0, sp
|
||||||
|
mov x1, #0
|
||||||
|
bl ArchUnexpectedException
|
||||||
|
|
||||||
|
el1h_sync:
|
||||||
|
SAVE_FRAME 1
|
||||||
|
mov x0, sp
|
||||||
|
mov x1, #0
|
||||||
|
bl ArchSyncException
|
||||||
|
RESTORE_FRAME 1
|
||||||
|
|
||||||
|
el1h_irq:
|
||||||
|
SAVE_FRAME 1
|
||||||
|
mov x0, sp
|
||||||
|
mov x1, #0
|
||||||
|
bl ArchIrqException
|
||||||
|
RESTORE_FRAME 1
|
||||||
|
|
||||||
|
el1h_fiq:
|
||||||
|
SAVE_FRAME 1
|
||||||
|
mov x0, sp
|
||||||
|
mov x1, #1
|
||||||
|
bl ArchUnexpectedException
|
||||||
|
|
||||||
|
el1h_serror:
|
||||||
|
SAVE_FRAME 1
|
||||||
|
mov x0, sp
|
||||||
|
mov x1, #2
|
||||||
|
bl ArchUnexpectedException
|
||||||
|
|
||||||
|
/* ---- Exceptions from user mode ---- */
|
||||||
|
|
||||||
|
el0_sync:
|
||||||
|
SAVE_FRAME 0
|
||||||
|
lsr x9, x24, #26 /* ESR_EL1.EC */
|
||||||
|
cmp x9, #0x15 /* SVC from AArch64 */
|
||||||
|
b.ne 1f
|
||||||
|
|
||||||
|
msr daifclr, #2
|
||||||
|
mov x0, sp
|
||||||
|
bl SyscallDispatch
|
||||||
|
str x0, [sp, #0]
|
||||||
|
bl SchedExitIfKilled
|
||||||
|
RESTORE_FRAME 0
|
||||||
|
|
||||||
|
1: mov x0, sp
|
||||||
|
mov x1, #1
|
||||||
|
bl ArchSyncException
|
||||||
|
RESTORE_FRAME 0
|
||||||
|
|
||||||
|
el0_irq:
|
||||||
|
SAVE_FRAME 0
|
||||||
|
mov x0, sp
|
||||||
|
mov x1, #1
|
||||||
|
bl ArchIrqException
|
||||||
|
RESTORE_FRAME 0
|
||||||
|
|
||||||
|
el0_fiq:
|
||||||
|
SAVE_FRAME 0
|
||||||
|
mov x0, sp
|
||||||
|
mov x1, #1
|
||||||
|
bl ArchUnexpectedException
|
||||||
|
|
||||||
|
el0_serror:
|
||||||
|
SAVE_FRAME 0
|
||||||
|
mov x0, sp
|
||||||
|
mov x1, #2
|
||||||
|
bl ArchUnexpectedException
|
||||||
|
|
||||||
|
el0_aarch32:
|
||||||
|
SAVE_FRAME 0
|
||||||
|
mov x0, sp
|
||||||
|
mov x1, #3
|
||||||
|
bl ArchUnexpectedException
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
/*
|
||||||
|
* Abi.hpp
|
||||||
|
* x86_64 user-mode ABI details (implements Arch/Abi.hpp)
|
||||||
|
* Copyright (c) 2026 Daniel Hammer
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
// UEFI uses the Microsoft x64 calling convention.
|
||||||
|
#define ARCH_EFIAPI __attribute__((__ms_abi__))
|
||||||
|
|
||||||
|
namespace Arch {
|
||||||
|
|
||||||
|
constexpr uint16_t ElfMachine = 62; // EM_X86_64
|
||||||
|
constexpr const char* ElfMachineName = "x86_64";
|
||||||
|
|
||||||
|
inline RelocationKind ClassifyRelocation(uint32_t type) {
|
||||||
|
switch (type) {
|
||||||
|
case 0: return RelocationKind::None;
|
||||||
|
case 8: return RelocationKind::Relative; // R_X86_64_RELATIVE
|
||||||
|
case 1: // R_X86_64_64
|
||||||
|
case 6: // R_X86_64_GLOB_DAT
|
||||||
|
case 7: return RelocationKind::Symbol; // R_X86_64_JUMP_SLOT
|
||||||
|
default: return RelocationKind::Unsupported;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Variant II. The linker addresses TLS variables at
|
||||||
|
// TP - align_up(memSize, p_align) + offset, so the template must start
|
||||||
|
// exactly that far below TP; rounding p_align up first (as an older
|
||||||
|
// version did) shifts every variable away from its initial value.
|
||||||
|
// TP itself is kept at least 16-byte aligned, which the gap between
|
||||||
|
// base and the template absorbs. The 16 bytes above TP are the TCB.
|
||||||
|
inline TlsLayout ComputeTlsLayout(uint64_t memSize, uint64_t pAlign) {
|
||||||
|
uint64_t align = pAlign ? pAlign : 1;
|
||||||
|
uint64_t block = (memSize + align - 1) & ~(align - 1);
|
||||||
|
uint64_t tpAlign = align < 16 ? 16 : align;
|
||||||
|
uint64_t tp = (block + tpAlign - 1) & ~(tpAlign - 1);
|
||||||
|
return {tp - block, tp, tp + 16};
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void WriteExitStub(uint8_t* stub) {
|
||||||
|
stub[0] = 0x31; stub[1] = 0xFF; // xor edi, edi (exit code 0)
|
||||||
|
stub[2] = 0x31; stub[3] = 0xC0; // xor eax, eax (SYS_EXIT = 0)
|
||||||
|
stub[4] = 0x0F; stub[5] = 0x05; // syscall
|
||||||
|
}
|
||||||
|
|
||||||
|
// The entry function is entered as if CALLed: the return address sits
|
||||||
|
// at [RSP], with RSP = 16n - 8.
|
||||||
|
inline uint64_t SetupUserEntryStack(uint8_t* topPage, uint64_t stackTop,
|
||||||
|
uint64_t returnAddress) {
|
||||||
|
*(uint64_t*)(topPage + 0xFF8) = returnAddress;
|
||||||
|
return stackTop - 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
// SystemV alignment as if CALLed (16n - 8); the slot is never read.
|
||||||
|
inline uint64_t ThreadEntryStackPointer(uint64_t stackTop) {
|
||||||
|
return (stackTop & ~0xFULL) - 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -4,8 +4,10 @@
|
|||||||
* Copyright (c) 2026 Daniel Hammer
|
* Copyright (c) 2026 Daniel Hammer
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "AcpiSleep.hpp"
|
#include <ACPI/AcpiSleep.hpp>
|
||||||
#include "AcpiEvents.hpp"
|
#include <ACPI/AcpiEvents.hpp>
|
||||||
|
#include <Arch/Cpu.hpp>
|
||||||
|
#include <Arch/x86_64/Acpi/S3State.hpp>
|
||||||
#include <ACPI/FADT.hpp>
|
#include <ACPI/FADT.hpp>
|
||||||
#include <ACPI/AML/AmlParser.hpp>
|
#include <ACPI/AML/AmlParser.hpp>
|
||||||
#include <ACPI/AML/AmlInterpreter.hpp>
|
#include <ACPI/AML/AmlInterpreter.hpp>
|
||||||
@@ -14,19 +16,19 @@
|
|||||||
#include <CppLib/Stream.hpp>
|
#include <CppLib/Stream.hpp>
|
||||||
#include <Memory/HHDM.hpp>
|
#include <Memory/HHDM.hpp>
|
||||||
#include <Memory/Paging.hpp>
|
#include <Memory/Paging.hpp>
|
||||||
#include <Hal/GDT.hpp>
|
#include <Arch/x86_64/GDT.hpp>
|
||||||
#include <Hal/Apic/Apic.hpp>
|
#include <Arch/x86_64/Apic/Apic.hpp>
|
||||||
#include <Hal/Apic/IoApic.hpp>
|
#include <Arch/x86_64/Apic/IoApic.hpp>
|
||||||
#include <Hal/Apic/Pic.hpp>
|
#include <Arch/x86_64/Apic/Pic.hpp>
|
||||||
#include <Timekeeping/ApicTimer.hpp>
|
#include <Timekeeping/Timer.hpp>
|
||||||
#include <Hal/CpuPower.hpp>
|
#include <Arch/CpuPower.hpp>
|
||||||
#include <Drivers/Graphics/IntelGPU.hpp>
|
#include <Drivers/Graphics/IntelGPU.hpp>
|
||||||
#include <Drivers/Graphics/DgpuPower.hpp>
|
#include <Drivers/Graphics/DgpuPower.hpp>
|
||||||
#include <Drivers/PS2/PS2Controller.hpp>
|
#include <Drivers/PS2/PS2Controller.hpp>
|
||||||
#include <Graphics/Framebuffer.hpp>
|
#include <Graphics/Framebuffer.hpp>
|
||||||
#include <Libraries/Memory.hpp>
|
#include <Libraries/Memory.hpp>
|
||||||
#include <Hal/MSR.hpp>
|
#include <Arch/x86_64/MSR.hpp>
|
||||||
#include <Hal/SmpBoot.hpp>
|
#include <Arch/Smp.hpp>
|
||||||
#include <Api/Syscall.hpp>
|
#include <Api/Syscall.hpp>
|
||||||
|
|
||||||
using namespace Kt;
|
using namespace Kt;
|
||||||
@@ -186,7 +188,7 @@ namespace Hal {
|
|||||||
<< base::hex << (uint64_t)g_s3SlpTypA
|
<< base::hex << (uint64_t)g_s3SlpTypA
|
||||||
<< " SLP_TYPb=" << base::hex << (uint64_t)g_s3SlpTypB << ")";
|
<< " SLP_TYPb=" << base::hex << (uint64_t)g_s3SlpTypB << ")";
|
||||||
KernelLogStream(INFO, "S3") << "Kernel PML4 phys = " << base::hex
|
KernelLogStream(INFO, "S3") << "Kernel PML4 phys = " << base::hex
|
||||||
<< (uint64_t)Memory::VMM::g_paging->PML4;
|
<< Memory::VMM::g_paging->KernelRoot();
|
||||||
KernelLogStream(INFO, "S3") << "FACS at phys " << base::hex
|
KernelLogStream(INFO, "S3") << "FACS at phys " << base::hex
|
||||||
<< (uint64_t)fadt.FacsAddress << " version=" << base::dec
|
<< (uint64_t)fadt.FacsAddress << " version=" << base::dec
|
||||||
<< (uint64_t)g_facs->Version << " flags=" << base::hex
|
<< (uint64_t)g_facs->Version << " flags=" << base::hex
|
||||||
@@ -314,7 +316,7 @@ namespace Hal {
|
|||||||
// shadow copy of the PML4 at a fixed low-memory address.
|
// shadow copy of the PML4 at a fixed low-memory address.
|
||||||
// Only the PML4 page itself needs to be below 4GB — its entries
|
// Only the PML4 page itself needs to be below 4GB — its entries
|
||||||
// contain full 52-bit physical addresses for sub-tables.
|
// contain full 52-bit physical addresses for sub-tables.
|
||||||
uint64_t kernelPml4Phys = (uint64_t)Memory::VMM::g_paging->PML4;
|
uint64_t kernelPml4Phys = Memory::VMM::g_paging->KernelRoot();
|
||||||
uint8_t* kernelPml4Virt = (uint8_t*)Memory::HHDM(kernelPml4Phys);
|
uint8_t* kernelPml4Virt = (uint8_t*)Memory::HHDM(kernelPml4Phys);
|
||||||
uint8_t* shadowPml4Virt = (uint8_t*)Memory::HHDM((uint64_t)SHADOW_PML4_PHYS);
|
uint8_t* shadowPml4Virt = (uint8_t*)Memory::HHDM((uint64_t)SHADOW_PML4_PHYS);
|
||||||
memcpy(shadowPml4Virt, kernelPml4Virt, 4096);
|
memcpy(shadowPml4Virt, kernelPml4Virt, 4096);
|
||||||
@@ -439,7 +441,7 @@ namespace Hal {
|
|||||||
Hal::InitializePAT();
|
Hal::InitializePAT();
|
||||||
// HWP enable and POWER_CTL reset across S3; re-apply them and
|
// HWP enable and POWER_CTL reset across S3; re-apply them and
|
||||||
// bump the policy epoch so APs re-apply on their next tick.
|
// bump the policy epoch so APs re-apply on their next tick.
|
||||||
Hal::CpuPower::ReapplyAfterWake();
|
Arch::CpuPower::ReapplyAfterWake();
|
||||||
reinitProgress(0x05); // PIC
|
reinitProgress(0x05); // PIC
|
||||||
Hal::DisableLegacyPic();
|
Hal::DisableLegacyPic();
|
||||||
reinitProgress(0x06); // Local APIC
|
reinitProgress(0x06); // Local APIC
|
||||||
@@ -447,7 +449,7 @@ namespace Hal {
|
|||||||
reinitProgress(0x07); // IO APIC
|
reinitProgress(0x07); // IO APIC
|
||||||
Hal::IoApic::Reinitialize();
|
Hal::IoApic::Reinitialize();
|
||||||
reinitProgress(0x08); // APIC Timer
|
reinitProgress(0x08); // APIC Timer
|
||||||
Timekeeping::ApicTimerReinitialize();
|
Timekeeping::TimerReinitialize();
|
||||||
reinitProgress(0x09); // PM1 clear
|
reinitProgress(0x09); // PM1 clear
|
||||||
ClearPM1Status();
|
ClearPM1Status();
|
||||||
reinitProgress(0x0A); // _WAK
|
reinitProgress(0x0A); // _WAK
|
||||||
@@ -463,7 +465,7 @@ namespace Hal {
|
|||||||
reinitProgress(0x0D); // discrete GPU back off
|
reinitProgress(0x0D); // discrete GPU back off
|
||||||
Drivers::Graphics::DgpuPower::Resume();
|
Drivers::Graphics::DgpuPower::Resume();
|
||||||
reinitProgress(0x0E); // sti
|
reinitProgress(0x0E); // sti
|
||||||
asm volatile("sti");
|
Arch::EnableInterrupts();
|
||||||
|
|
||||||
KernelLogStream(OK, "S3") << "Resumed from S3 suspend";
|
KernelLogStream(OK, "S3") << "Resumed from S3 suspend";
|
||||||
|
|
||||||
@@ -517,7 +519,7 @@ namespace Hal {
|
|||||||
Io::Out8(0x00, 0x71);
|
Io::Out8(0x00, 0x71);
|
||||||
|
|
||||||
// 3. Disable interrupts
|
// 3. Disable interrupts
|
||||||
asm volatile("cli");
|
Arch::DisableInterrupts();
|
||||||
|
|
||||||
// 4. Identity-map the trampoline page so the CPU can execute it
|
// 4. Identity-map the trampoline page so the CPU can execute it
|
||||||
// after paging is re-enabled with our CR3 during wake.
|
// after paging is re-enabled with our CR3 during wake.
|
||||||
@@ -525,7 +527,7 @@ namespace Hal {
|
|||||||
Memory::VMM::g_paging->Map(TRAMPOLINE_PHYS, TRAMPOLINE_PHYS);
|
Memory::VMM::g_paging->Map(TRAMPOLINE_PHYS, TRAMPOLINE_PHYS);
|
||||||
|
|
||||||
// 5. Flush all caches
|
// 5. Flush all caches
|
||||||
asm volatile("wbinvd");
|
Arch::WritebackInvalidateCaches();
|
||||||
|
|
||||||
// 6. Set the waking vector in FACS
|
// 6. Set the waking vector in FACS
|
||||||
SetWakingVector();
|
SetWakingVector();
|
||||||
@@ -548,7 +550,7 @@ namespace Hal {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 10. Flush caches again
|
// 10. Flush caches again
|
||||||
asm volatile("wbinvd");
|
Arch::WritebackInvalidateCaches();
|
||||||
|
|
||||||
// 11. Assert SLP_EN — this triggers S3 entry
|
// 11. Assert SLP_EN — this triggers S3 entry
|
||||||
Io::Out16(pm1a | PM1_SLP_EN, (uint16_t)g_pm1aControlBlock);
|
Io::Out16(pm1a | PM1_SLP_EN, (uint16_t)g_pm1aControlBlock);
|
||||||
@@ -563,7 +565,7 @@ namespace Hal {
|
|||||||
// on QEMU/KVM the vCPU must reach HLT before QEMU finalizes S3.
|
// on QEMU/KVM the vCPU must reach HLT before QEMU finalizes S3.
|
||||||
// On resume, firmware jumps to our waking vector.
|
// On resume, firmware jumps to our waking vector.
|
||||||
for (;;) {
|
for (;;) {
|
||||||
asm volatile("hlt");
|
Arch::Halt();
|
||||||
// If we wake from HLT (spurious or real wake), check WAK_STS
|
// If we wake from HLT (spurious or real wake), check WAK_STS
|
||||||
if (g_pm1aEventBlock != 0) {
|
if (g_pm1aEventBlock != 0) {
|
||||||
uint16_t sts = Io::In16((uint16_t)g_pm1aEventBlock);
|
uint16_t sts = Io::In16((uint16_t)g_pm1aEventBlock);
|
||||||
@@ -574,7 +576,7 @@ namespace Hal {
|
|||||||
// If we get here, either S3 failed or we woke immediately.
|
// If we get here, either S3 failed or we woke immediately.
|
||||||
// The normal wake path goes through AcpiResumeLongMode instead.
|
// The normal wake path goes through AcpiResumeLongMode instead.
|
||||||
// Re-enable interrupts as a safety measure.
|
// Re-enable interrupts as a safety measure.
|
||||||
asm volatile("sti");
|
Arch::EnableInterrupts();
|
||||||
|
|
||||||
KernelLogStream(WARNING, "S3") << "S3 entry may have failed (fell through)";
|
KernelLogStream(WARNING, "S3") << "S3 entry may have failed (fell through)";
|
||||||
return 0;
|
return 0;
|
||||||
@@ -5,10 +5,11 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "CpuIdle.hpp"
|
#include "CpuIdle.hpp"
|
||||||
|
#include <Arch/Cpu.hpp>
|
||||||
#include <ACPI/FADT.hpp>
|
#include <ACPI/FADT.hpp>
|
||||||
#include <ACPI/AML/AmlInterpreter.hpp>
|
#include <ACPI/AML/AmlInterpreter.hpp>
|
||||||
#include <Hal/Cpu.hpp>
|
#include <Arch/x86_64/CpuFeatures.hpp>
|
||||||
#include <Hal/SmpBoot.hpp>
|
#include <Arch/Smp.hpp>
|
||||||
#include <Io/IoPort.hpp>
|
#include <Io/IoPort.hpp>
|
||||||
#include <Terminal/Terminal.hpp>
|
#include <Terminal/Terminal.hpp>
|
||||||
#include <CppLib/Stream.hpp>
|
#include <CppLib/Stream.hpp>
|
||||||
@@ -480,7 +481,7 @@ namespace Hal {
|
|||||||
if (hasMwait) {
|
if (hasMwait) {
|
||||||
Hal::IdleWaitWithInterruptsDisabled(addr, CurrentIdleHint());
|
Hal::IdleWaitWithInterruptsDisabled(addr, CurrentIdleHint());
|
||||||
} else {
|
} else {
|
||||||
Hal::HaltWithInterruptsDisabled();
|
Arch::HaltWithInterruptsDisabled();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
File renamed without changes.
@@ -0,0 +1,30 @@
|
|||||||
|
/*
|
||||||
|
* S3State.hpp
|
||||||
|
* CPU state saved across S3 by S3Wake.asm
|
||||||
|
* Copyright (c) 2026 Daniel Hammer
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
namespace Hal {
|
||||||
|
namespace AcpiSleep {
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Layout must match S3Wake.asm offsets exactly.
|
||||||
|
// No FPU/SSE state — the kernel is compiled with -mno-sse.
|
||||||
|
struct CpuState {
|
||||||
|
uint64_t Rax, Rbx, Rcx, Rdx; // 0x00 - 0x18
|
||||||
|
uint64_t Rsi, Rdi, Rbp, Rsp; // 0x20 - 0x38
|
||||||
|
uint64_t R8, R9, R10, R11; // 0x40 - 0x58
|
||||||
|
uint64_t R12, R13, R14, R15; // 0x60 - 0x78
|
||||||
|
uint64_t Rflags; // 0x80
|
||||||
|
uint64_t Rip; // 0x88
|
||||||
|
uint64_t Cr3; // 0x90
|
||||||
|
uint64_t Cr0, Cr4; // 0x98, 0xA0
|
||||||
|
uint8_t GdtPtr[10]; // 0xA8
|
||||||
|
uint8_t IdtPtr[10]; // 0xB2
|
||||||
|
} __attribute__((aligned(16)));
|
||||||
|
|
||||||
|
};
|
||||||
|
};
|
||||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -10,7 +10,7 @@
|
|||||||
#include "IoApic.hpp"
|
#include "IoApic.hpp"
|
||||||
#include "Interrupts.hpp"
|
#include "Interrupts.hpp"
|
||||||
#include <ACPI/MADT.hpp>
|
#include <ACPI/MADT.hpp>
|
||||||
#include <Hal/IDT.hpp>
|
#include <Arch/x86_64/IDT.hpp>
|
||||||
#include <Terminal/Terminal.hpp>
|
#include <Terminal/Terminal.hpp>
|
||||||
#include <CppLib/Stream.hpp>
|
#include <CppLib/Stream.hpp>
|
||||||
#include <Memory/Paging.hpp>
|
#include <Memory/Paging.hpp>
|
||||||
@@ -67,8 +67,8 @@ namespace Hal {
|
|||||||
// Step 7: Route keyboard (IRQ1) and mouse (IRQ12) to BSP
|
// Step 7: Route keyboard (IRQ1) and mouse (IRQ12) to BSP
|
||||||
uint8_t bspApicId = (uint8_t)LocalApic::GetId();
|
uint8_t bspApicId = (uint8_t)LocalApic::GetId();
|
||||||
|
|
||||||
IoApic::RouteIrq(IRQ_KEYBOARD, IRQ_VECTOR_BASE + IRQ_KEYBOARD, bspApicId);
|
IoApic::RouteIrq(Arch::IRQ_KEYBOARD, IRQ_VECTOR_BASE + Arch::IRQ_KEYBOARD, bspApicId);
|
||||||
IoApic::RouteIrq(IRQ_MOUSE, IRQ_VECTOR_BASE + IRQ_MOUSE, bspApicId);
|
IoApic::RouteIrq(Arch::IRQ_MOUSE, IRQ_VECTOR_BASE + Arch::IRQ_MOUSE, bspApicId);
|
||||||
|
|
||||||
// Note: sti is NOT called here. The caller (Main.cpp) enables
|
// Note: sti is NOT called here. The caller (Main.cpp) enables
|
||||||
// interrupts after setting up per-CPU GS base for SMP/SWAPGS.
|
// interrupts after setting up per-CPU GS base for SMP/SWAPGS.
|
||||||
File renamed without changes.
@@ -6,7 +6,9 @@
|
|||||||
|
|
||||||
#include "Interrupts.hpp"
|
#include "Interrupts.hpp"
|
||||||
#include "Apic.hpp"
|
#include "Apic.hpp"
|
||||||
#include <Hal/IDT.hpp>
|
#include "IoApic.hpp"
|
||||||
|
#include <Arch/x86_64/IDT.hpp>
|
||||||
|
#include <Arch/Smp.hpp>
|
||||||
#include <Terminal/Terminal.hpp>
|
#include <Terminal/Terminal.hpp>
|
||||||
#include <CppLib/Stream.hpp>
|
#include <CppLib/Stream.hpp>
|
||||||
|
|
||||||
@@ -14,19 +16,15 @@
|
|||||||
extern "C" void* IrqStubTable[Hal::IRQ_COUNT];
|
extern "C" void* IrqStubTable[Hal::IRQ_COUNT];
|
||||||
extern "C" void IrqStubSpurious();
|
extern "C" void IrqStubSpurious();
|
||||||
|
|
||||||
|
// Local APIC message address range; destination APIC ID in bits 19:12.
|
||||||
|
static constexpr uint64_t MsiAddressBase = 0xFEE00000;
|
||||||
|
|
||||||
namespace Hal {
|
namespace Hal {
|
||||||
// Dispatch table: one handler per IRQ
|
// Dispatch table: one handler per IRQ
|
||||||
static IrqHandler g_irqHandlers[IRQ_COUNT] = {};
|
static Arch::IrqHandler g_irqHandlers[IRQ_COUNT] = {};
|
||||||
|
|
||||||
void RegisterIrqHandler(uint8_t irq, IrqHandler handler) {
|
|
||||||
if (irq >= IRQ_COUNT) return;
|
|
||||||
g_irqHandlers[irq] = handler;
|
|
||||||
Kt::KernelLogStream(Kt::DEBUG, "IRQ") << "Registered handler for IRQ " << base::dec << (uint64_t)irq
|
|
||||||
<< " (vector " << (uint64_t)(IRQ_VECTOR_BASE + irq) << ")";
|
|
||||||
}
|
|
||||||
|
|
||||||
void InitializeIrqHandlers() {
|
void InitializeIrqHandlers() {
|
||||||
// Install IRQ stubs into IDT vectors 32..55
|
// Install IRQ stubs into IDT vectors 32..79
|
||||||
for (int i = 0; i < IRQ_COUNT; i++) {
|
for (int i = 0; i < IRQ_COUNT; i++) {
|
||||||
IDTEncodeInterrupt(IRQ_VECTOR_BASE + i, IrqStubTable[i], 0x8E);
|
IDTEncodeInterrupt(IRQ_VECTOR_BASE + i, IrqStubTable[i], 0x8E);
|
||||||
}
|
}
|
||||||
@@ -41,6 +39,49 @@ namespace Hal {
|
|||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
namespace Arch {
|
||||||
|
void RegisterIrqHandler(uint8_t irq, IrqHandler handler) {
|
||||||
|
if (irq >= IRQ_COUNT) return;
|
||||||
|
Hal::g_irqHandlers[irq] = handler;
|
||||||
|
Kt::KernelLogStream(Kt::DEBUG, "IRQ") << "Registered handler for IRQ " << base::dec << (uint64_t)irq
|
||||||
|
<< " (vector " << (uint64_t)(Hal::IRQ_VECTOR_BASE + irq) << ")";
|
||||||
|
}
|
||||||
|
|
||||||
|
void RouteLegacyIrq(uint8_t irq) {
|
||||||
|
// RouteIrq applies the MADT's Interrupt Source Overrides (polarity,
|
||||||
|
// trigger mode, GSI remapping).
|
||||||
|
Hal::IoApic::RouteIrq(irq, Hal::IRQ_VECTOR_BASE + irq,
|
||||||
|
(uint8_t)Hal::LocalApic::GetId());
|
||||||
|
}
|
||||||
|
|
||||||
|
void EnableLegacyIrq(uint8_t irq) {
|
||||||
|
Hal::IoApic::UnmaskIrq(Hal::IoApic::GetGsiForIrq(irq));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PlatformLinesAreFixed() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AssignPlatformLine(uint8_t, uint32_t, bool, bool) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ComposeMsi(uint8_t irq, MsiMessage& out) {
|
||||||
|
// Physical destination mode, fixed delivery, edge triggered, to the
|
||||||
|
// BSP (APIC ID 0 -- the destination field is left zero, as every
|
||||||
|
// driver has always programmed it).
|
||||||
|
out = MsiMessage{ MsiAddressBase, (uint32_t)(Hal::IRQ_VECTOR_BASE + irq) };
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SendIpi(int cpuIndex, uint8_t irq) {
|
||||||
|
auto* cpu = Smp::GetCpuData(cpuIndex);
|
||||||
|
if (cpu == nullptr) return false;
|
||||||
|
return Hal::LocalApic::SendFixedIpi(cpu->arch.lapicId,
|
||||||
|
Hal::IRQ_VECTOR_BASE + irq);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// C linkage dispatch function called from assembly stubs
|
// C linkage dispatch function called from assembly stubs
|
||||||
extern "C" void HalIrqDispatch(uint64_t irqNumber, uint64_t savedCs) {
|
extern "C" void HalIrqDispatch(uint64_t irqNumber, uint64_t savedCs) {
|
||||||
// Send EOI BEFORE calling the handler. The handler may context-switch
|
// Send EOI BEFORE calling the handler. The handler may context-switch
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
/*
|
||||||
|
* Interrupts.hpp
|
||||||
|
* x86_64 IRQ slot <-> IDT vector mapping
|
||||||
|
* Copyright (c) 2025 Daniel Hammer
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include <cstdint>
|
||||||
|
#include <Arch/Interrupts.hpp>
|
||||||
|
|
||||||
|
namespace Hal {
|
||||||
|
using Arch::IRQ_COUNT;
|
||||||
|
|
||||||
|
// IRQ vector base: IRQ slot N is delivered on IDT vector 32 + N.
|
||||||
|
constexpr uint8_t IRQ_VECTOR_BASE = 32;
|
||||||
|
|
||||||
|
// Install IRQ stubs into the IDT and set up the dispatch table
|
||||||
|
void InitializeIrqHandlers();
|
||||||
|
|
||||||
|
};
|
||||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -0,0 +1,133 @@
|
|||||||
|
/*
|
||||||
|
* ApicTimer.cpp
|
||||||
|
* Local APIC timer, PIT-calibrated (implements Arch/Timer.hpp)
|
||||||
|
* Copyright (c) 2025 Daniel Hammer
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <Arch/Timer.hpp>
|
||||||
|
#include <Arch/Cpu.hpp>
|
||||||
|
#include <Arch/x86_64/Apic/Apic.hpp>
|
||||||
|
#include <Arch/x86_64/Apic/Interrupts.hpp>
|
||||||
|
#include <Io/IoPort.hpp>
|
||||||
|
#include <Terminal/Terminal.hpp>
|
||||||
|
#include <CppLib/Stream.hpp>
|
||||||
|
|
||||||
|
using namespace Kt;
|
||||||
|
|
||||||
|
namespace Arch::Timer {
|
||||||
|
// PIT constants
|
||||||
|
static constexpr uint32_t PIT_FREQUENCY = 1193182; // PIT oscillator frequency in Hz
|
||||||
|
static constexpr uint16_t PIT_CHANNEL2_DATA = 0x42;
|
||||||
|
static constexpr uint16_t PIT_COMMAND = 0x43;
|
||||||
|
static constexpr uint16_t PIT_GATE_PORT = 0x61;
|
||||||
|
|
||||||
|
// APIC timer LVT mode bits
|
||||||
|
static constexpr uint32_t LVT_MASKED = (1 << 16);
|
||||||
|
static constexpr uint32_t LVT_PERIODIC = (1 << 17);
|
||||||
|
|
||||||
|
// APIC timer divide configuration values
|
||||||
|
static constexpr uint32_t DIVIDE_BY_16 = 0x03;
|
||||||
|
|
||||||
|
static uint32_t g_ticksPerMs = 0;
|
||||||
|
|
||||||
|
static uint32_t CountForIntervalMs(uint32_t intervalMs) {
|
||||||
|
return g_ticksPerMs * intervalMs;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ProgramTimer(bool periodic, uint32_t intervalMs) {
|
||||||
|
uint32_t lvt = (Hal::IRQ_VECTOR_BASE + Arch::IRQ_TIMER);
|
||||||
|
if (periodic) {
|
||||||
|
lvt |= LVT_PERIODIC;
|
||||||
|
}
|
||||||
|
|
||||||
|
Hal::LocalApic::WriteRegister(Hal::LocalApic::REG_TIMER_DIVIDE, DIVIDE_BY_16);
|
||||||
|
Hal::LocalApic::WriteRegister(Hal::LocalApic::REG_TIMER_LVT, lvt);
|
||||||
|
Hal::LocalApic::WriteRegister(Hal::LocalApic::REG_TIMER_INITIAL,
|
||||||
|
CountForIntervalMs(intervalMs));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use PIT channel 2 to create a precise delay for calibration.
|
||||||
|
// Returns the number of APIC timer ticks that elapsed during ~10ms.
|
||||||
|
static uint32_t CalibratePit() {
|
||||||
|
// We want to measure ~10ms worth of APIC timer ticks.
|
||||||
|
// PIT count for 10ms: PIT_FREQUENCY / 100 = 11931
|
||||||
|
uint16_t pitCount = PIT_FREQUENCY / 100;
|
||||||
|
|
||||||
|
// Set up PIT channel 2 in one-shot mode (mode 0), lobyte/hibyte
|
||||||
|
// Command: channel 2 (bits 7-6 = 10), lobyte/hibyte (bits 5-4 = 11),
|
||||||
|
// mode 0 (bits 3-1 = 000), binary (bit 0 = 0) => 0xB0
|
||||||
|
Io::Out8(0xB0, PIT_COMMAND);
|
||||||
|
|
||||||
|
// Disable PIT channel 2 gate to prepare
|
||||||
|
uint8_t gate = Io::In8(PIT_GATE_PORT);
|
||||||
|
gate &= ~0x01; // Disable gate (bit 0 = 0)
|
||||||
|
gate &= ~0x02; // Disable speaker (bit 1 = 0)
|
||||||
|
Io::Out8(gate, PIT_GATE_PORT);
|
||||||
|
|
||||||
|
// Load the count value (low byte first, then high byte)
|
||||||
|
Io::Out8((uint8_t)(pitCount & 0xFF), PIT_CHANNEL2_DATA);
|
||||||
|
Io::IoPortWait();
|
||||||
|
Io::Out8((uint8_t)(pitCount >> 8), PIT_CHANNEL2_DATA);
|
||||||
|
|
||||||
|
// Set APIC timer to max count with divide-by-16, one-shot (masked so no IRQ)
|
||||||
|
Hal::LocalApic::WriteRegister(Hal::LocalApic::REG_TIMER_DIVIDE, DIVIDE_BY_16);
|
||||||
|
Hal::LocalApic::WriteRegister(Hal::LocalApic::REG_TIMER_LVT, LVT_MASKED);
|
||||||
|
Hal::LocalApic::WriteRegister(Hal::LocalApic::REG_TIMER_INITIAL, 0xFFFFFFFF);
|
||||||
|
|
||||||
|
// Enable PIT channel 2 gate to start counting
|
||||||
|
gate = Io::In8(PIT_GATE_PORT);
|
||||||
|
gate |= 0x01; // Enable gate (bit 0 = 1)
|
||||||
|
Io::Out8(gate, PIT_GATE_PORT);
|
||||||
|
|
||||||
|
// Wait for PIT channel 2 output to go high (bit 5 of port 0x61)
|
||||||
|
while (!(Io::In8(PIT_GATE_PORT) & 0x20)) {
|
||||||
|
Arch::Pause();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read how many APIC timer ticks elapsed
|
||||||
|
uint32_t currentCount = Hal::LocalApic::ReadRegister(Hal::LocalApic::REG_TIMER_CURRENT);
|
||||||
|
uint32_t elapsed = 0xFFFFFFFF - currentCount;
|
||||||
|
|
||||||
|
// Stop the APIC timer
|
||||||
|
Hal::LocalApic::WriteRegister(Hal::LocalApic::REG_TIMER_INITIAL, 0);
|
||||||
|
|
||||||
|
return elapsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool InitializeBsp(uint32_t periodMs) {
|
||||||
|
KernelLogStream(INFO, "Timer") << "Calibrating APIC timer using PIT channel 2";
|
||||||
|
|
||||||
|
// Calibrate: measure APIC ticks over ~10ms
|
||||||
|
uint32_t ticksIn10ms = CalibratePit();
|
||||||
|
g_ticksPerMs = ticksIn10ms / 10;
|
||||||
|
|
||||||
|
if (g_ticksPerMs == 0) {
|
||||||
|
KernelLogStream(ERROR, "Timer") << "APIC timer calibration failed (0 ticks/ms)";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t timerFreqHz = (uint64_t)g_ticksPerMs * 1000;
|
||||||
|
|
||||||
|
KernelLogStream(OK, "Timer") << "APIC timer: " << base::dec << (uint64_t)g_ticksPerMs
|
||||||
|
<< " ticks/ms (" << timerFreqHz << " Hz, divide-by-16)";
|
||||||
|
|
||||||
|
// Periodic mode on vector IRQ_VECTOR_BASE + IRQ_TIMER
|
||||||
|
ProgramTimer(true, periodMs);
|
||||||
|
|
||||||
|
KernelLogStream(OK, "Timer") << "APIC timer started: initial count="
|
||||||
|
<< base::dec << (uint64_t)CountForIntervalMs(periodMs);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void StartPeriodic(uint32_t periodMs) {
|
||||||
|
ProgramTimer(true, periodMs);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Stop() {
|
||||||
|
uint32_t lvt = Hal::LocalApic::ReadRegister(Hal::LocalApic::REG_TIMER_LVT);
|
||||||
|
if ((lvt & LVT_MASKED) == 0) {
|
||||||
|
Hal::LocalApic::WriteRegister(Hal::LocalApic::REG_TIMER_LVT,
|
||||||
|
lvt | LVT_MASKED);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
File renamed without changes.
@@ -0,0 +1,70 @@
|
|||||||
|
/*
|
||||||
|
* Context.cpp
|
||||||
|
* x86_64 thread context (implements Arch/Context.hpp)
|
||||||
|
* Copyright (c) 2026 Daniel Hammer
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <Arch/Context.hpp>
|
||||||
|
#include <Arch/x86_64/MSR.hpp>
|
||||||
|
#include <Libraries/Memory.hpp>
|
||||||
|
|
||||||
|
// Context.asm: save callee-saved registers + RSP + FPU, switch CR3, restore.
|
||||||
|
extern "C" void SchedContextSwitch(uint64_t* oldRsp, uint64_t newRsp, uint64_t newCR3,
|
||||||
|
uint8_t* oldFpuArea, uint8_t* newFpuArea);
|
||||||
|
|
||||||
|
// SyscallEntry.asm: jump to user mode via IRETQ.
|
||||||
|
// `arg` is delivered as the user-mode RDI (SystemV first argument).
|
||||||
|
extern "C" [[noreturn]] void JumpToUserMode(uint64_t rip, uint64_t rsp, uint64_t arg);
|
||||||
|
|
||||||
|
namespace Arch {
|
||||||
|
|
||||||
|
static constexpr uint32_t IA32_FS_BASE = 0xC0000100;
|
||||||
|
|
||||||
|
void InitFpuState(uint8_t* area) {
|
||||||
|
memset(area, 0, FpuStateSize);
|
||||||
|
*(uint16_t*)&area[0] = 0x037F; // FCW: default x87 control word
|
||||||
|
*(uint32_t*)&area[24] = 0x1F80; // MXCSR: default SSE control/status
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t PrepareThreadStack(uint64_t kernelStackTop, void (*entry)()) {
|
||||||
|
// The frame Context.asm pops: r15, r14, r13, r12, rbx, rbp, then the
|
||||||
|
// return address it RETs to.
|
||||||
|
uint64_t* sp = (uint64_t*)kernelStackTop;
|
||||||
|
*(--sp) = (uint64_t)entry; // return addr
|
||||||
|
*(--sp) = 0; // rbp
|
||||||
|
*(--sp) = 0; // rbx
|
||||||
|
*(--sp) = 0; // r12
|
||||||
|
*(--sp) = 0; // r13
|
||||||
|
*(--sp) = 0; // r14
|
||||||
|
*(--sp) = 0; // r15
|
||||||
|
return (uint64_t)sp;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SwitchContext(uint64_t* oldSp, uint64_t newSp, uint64_t newRoot,
|
||||||
|
uint8_t* oldFpu, uint8_t* newFpu) {
|
||||||
|
SchedContextSwitch(oldSp, newSp, newRoot, oldFpu, newFpu);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ActivateThread(Smp::CpuData* cpu, uint64_t kernelStackTop,
|
||||||
|
uint64_t threadPointer) {
|
||||||
|
// Kernel RSP for SYSCALL entry
|
||||||
|
cpu->entry.kernelRsp = kernelStackTop;
|
||||||
|
|
||||||
|
// TSS RSP0 for hardware interrupts from ring 3
|
||||||
|
cpu->arch.tss->rsp0 = kernelStackTop;
|
||||||
|
|
||||||
|
// The TLS thread pointer is IA32_FS_BASE. Cached per CPU: the MSR
|
||||||
|
// write is skipped when unchanged, so TLS-free processes (pointer 0)
|
||||||
|
// cost one write per CPU ever. Userspace cannot move FS base itself
|
||||||
|
// (CR4.FSGSBASE is off), so the cache cannot go stale.
|
||||||
|
if (cpu->arch.currentFsBase != threadPointer) {
|
||||||
|
cpu->arch.currentFsBase = threadPointer;
|
||||||
|
Hal::WriteMSR(IA32_FS_BASE, threadPointer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void EnterUserMode(uint64_t entry, uint64_t userSp, uint64_t arg) {
|
||||||
|
JumpToUserMode(entry, userSp, arg);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Loaded 100 of 552 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user