Compare commits
59
Commits
e641500b40
...
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 | ||
|
|
7979acc2eb | ||
|
|
ef1e69ae83 | ||
|
|
c91de69189 | ||
|
|
41de95b277 | ||
|
|
24f88ec4c6 | ||
|
|
7f12e1a150 | ||
|
|
63b4bd51e8 | ||
|
|
2e3055f5f8 | ||
|
|
5e4e8902c2 | ||
|
|
8a3ffb2be1 | ||
|
|
93894b8fe5 | ||
|
|
2eee6696ef | ||
|
|
ae5e84592d | ||
|
|
c54181103f | ||
|
|
95e8a739d4 | ||
|
|
75e85f24ef | ||
|
|
80b5beb17d | ||
|
|
420a7c996c | ||
|
|
36cbee1415 | ||
|
|
f95bce01d1 |
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.
|
||||||
-168
@@ -1,168 +0,0 @@
|
|||||||
# CPU Power and Thermal Management
|
|
||||||
|
|
||||||
MontaukOS manages CPU frequency, idle states, and package temperature on
|
|
||||||
Intel hardware. Without this, a modern many-core laptop part (the reference
|
|
||||||
target is a TUXEDO Gemini Gen2 with an i9-13900HX, 24 cores / 32 threads)
|
|
||||||
runs at whatever P-state the firmware left it, never drops below C1 when
|
|
||||||
idle, and heats until the hardware throttles at TjMax - or until the
|
|
||||||
embedded controller intervenes (on Clevo boards: forced full-volume beeping,
|
|
||||||
then a hard power-off).
|
|
||||||
|
|
||||||
Everything below is feature-gated by CPUID, so virtual machines (KVM masks
|
|
||||||
the power bits in CPUID.06H) and non-Intel CPUs skip it safely.
|
|
||||||
|
|
||||||
## Components
|
|
||||||
|
|
||||||
### HWP frequency scaling (`Hal/CpuPower.cpp`)
|
|
||||||
|
|
||||||
`Hal::CpuPower::InitializeBsp()` enables Hardware P-states
|
|
||||||
(IA32_PM_ENABLE), reads the performance range from IA32_HWP_CAPABILITIES,
|
|
||||||
and programs IA32_HWP_REQUEST with:
|
|
||||||
|
|
||||||
- min = lowest performance level, max = highest
|
|
||||||
- desired = 0 (fully autonomous hardware frequency selection)
|
|
||||||
- EPP = 0x80 (balanced; same as Linux "balance_performance")
|
|
||||||
|
|
||||||
This is the same mechanism intel_pstate uses on Linux and is the single
|
|
||||||
biggest thermal win: the CPU idles near its minimum frequency and ramps
|
|
||||||
only while there is work. APs apply the same request in `ApEntry` via
|
|
||||||
`InitializeAp()` (HWP request MSRs are per-logical-CPU).
|
|
||||||
|
|
||||||
CPUs with EPB but no HWP get IA32_ENERGY_PERF_BIAS = 6 (balanced) instead.
|
|
||||||
|
|
||||||
### C1E promotion
|
|
||||||
|
|
||||||
MSR_POWER_CTL bit 1 lets HLT-idle drop core voltage/frequency to minimum
|
|
||||||
(C1E) instead of parking at the current ratio. Set per-core on the BSP and
|
|
||||||
every AP. Gated on the DTS CPUID bit as a real-hardware heuristic, because
|
|
||||||
MSR_POWER_CTL is not architectural and hypervisors may not emulate it.
|
|
||||||
|
|
||||||
### Thermal governor (`Hal/CpuPower.cpp`)
|
|
||||||
|
|
||||||
A closed-loop passive-cooling controller on the BSP, stepped from
|
|
||||||
`Sched::RunBspMaintenance()` (reached from both the idle loop and the
|
|
||||||
timer tick, so it keeps running under full load). Every 500 ms it reads
|
|
||||||
the package digital thermal sensor (IA32_PACKAGE_THERM_STATUS readout
|
|
||||||
relative to TjMax from MSR_TEMPERATURE_TARGET) and adjusts the HWP
|
|
||||||
performance ceiling:
|
|
||||||
|
|
||||||
- **>= 85 C (passive):** step the ceiling down 2 ratio units (~200 MHz)
|
|
||||||
- **>= 94 C (hot):** step down 8 units per interval
|
|
||||||
- **<= 78 C (resume):** step back up 1 unit per interval
|
|
||||||
- between resume and passive: hold (hysteresis)
|
|
||||||
|
|
||||||
The ceiling propagates through a policy epoch counter: each CPU compares
|
|
||||||
the epoch in `Sched::Tick()` (and the AP idle loop) against the last one
|
|
||||||
it applied and rewrites its own IA32_HWP_REQUEST when it changed, since
|
|
||||||
MSR writes only affect the executing core. APs tick every 10 ms, so a
|
|
||||||
throttle decision reaches all cores within one tick.
|
|
||||||
|
|
||||||
Throttle engage/release transitions are logged (WARNING/OK, "CpuPower").
|
|
||||||
|
|
||||||
### Deep AP idle (`ACPI/CpuIdle.cpp`)
|
|
||||||
|
|
||||||
APs use deep MWAIT C-states while idle; C6 power-gates the core entirely,
|
|
||||||
which is where most of the idle-heat win lives on many-core parts.
|
|
||||||
|
|
||||||
The hint comes from ACPI `_CST` FFH entries (the GAS address low byte is
|
|
||||||
the MWAIT hint, same convention as Linux), validated against the CPUID.05H
|
|
||||||
sub-state enumeration and capped at the C6 family (0x2F). With no usable
|
|
||||||
`_CST`, the deepest CPUID-enumerated hint (up to C6) is used.
|
|
||||||
|
|
||||||
**The BSP stays in C1 (HLT).** On some laptops deep MWAIT states freeze
|
|
||||||
the LAPIC timer even though ARAT is advertised, and the BSP's periodic
|
|
||||||
1 ms tick is the system timekeeper (see the comment in
|
|
||||||
`Timekeeping::IdleOnce`). APs have no timekeeping duty: an idle AP is
|
|
||||||
woken by the reschedule IPI, and its coarse tick resumes on wake.
|
|
||||||
|
|
||||||
### S3 wake
|
|
||||||
|
|
||||||
IA32_PM_ENABLE, HWP requests, and MSR_POWER_CTL do not survive suspend.
|
|
||||||
`Hal::CpuPower::ReapplyAfterWake()` runs in the S3 resume path, redoes the
|
|
||||||
BSP setup, and bumps the policy epoch so every AP re-applies its HWP
|
|
||||||
request (and re-checks C1E) on its next tick.
|
|
||||||
|
|
||||||
### Battery (`ACPI/Battery.cpp`, `ACPI/EmbeddedController.cpp`)
|
|
||||||
|
|
||||||
Battery state comes from the ACPI Control Method Battery
|
|
||||||
(`_HID PNP0C0A`) and, for the AC adapter, `ACPI0003`:
|
|
||||||
|
|
||||||
- `_STA` bit 4 says whether a pack is installed. A laptop with the pack
|
|
||||||
removed still has the device, which is why "has a battery device" and
|
|
||||||
"has a battery" are separate answers.
|
|
||||||
- `_BIX` (or `_BIF` on older firmware) gives the power unit (mWh vs mAh),
|
|
||||||
the design capacity, and the last full charge capacity.
|
|
||||||
- `_BST` gives the charge/discharge state, the present rate, the remaining
|
|
||||||
capacity and the present voltage.
|
|
||||||
- `_PSR` on the adapter says whether it is plugged in.
|
|
||||||
|
|
||||||
Percentage is remaining / last-full, clamped to 100 and falling back to the
|
|
||||||
design capacity when firmware reports last-full as zero. Results are cached
|
|
||||||
for two seconds so that a panel repaint does not turn into EC traffic.
|
|
||||||
|
|
||||||
Almost all of this reaches the CPU through the **embedded controller**.
|
|
||||||
`Hal::EmbeddedController` finds the EC's command/data port pair from the
|
|
||||||
ECDT table when one exists, and otherwise from `_CRS` on the `PNP0C09`
|
|
||||||
device, then drives the polled read/write protocol (`0x80`/`0x81` with
|
|
||||||
OBF/IBF handshaking). It is wired into the AML interpreter as the
|
|
||||||
`EmbeddedControl` region space, so any `OperationRegion` in the DSDT that
|
|
||||||
targets the EC now works -- battery methods being the first user.
|
|
||||||
|
|
||||||
Supporting `_BST` also needed real package element storage in the AML
|
|
||||||
interpreter. Firmware overwhelmingly builds its result package with
|
|
||||||
`Store(x, Index(BST0, n))`, which requires `Index()` to name a writable
|
|
||||||
element rather than yield a value. `AML::PackageStore` provides that: a
|
|
||||||
persistent region for packages declared with `Name()`, and a transient ring
|
|
||||||
rewound at each top-level evaluation for packages built inside a method, so
|
|
||||||
polling `_BST` cannot exhaust it.
|
|
||||||
|
|
||||||
## Diagnostics
|
|
||||||
|
|
||||||
`SYS_POWERINFO` (149) fills a `PowerInfo` struct: package temperature,
|
|
||||||
TjMax, HWP state and performance range, the governor's current ceiling,
|
|
||||||
CPUID base/turbo frequency, the measured average active frequency
|
|
||||||
(APERF/MPERF over the 500 ms governor window), and the AP idle MWAIT hint.
|
|
||||||
Returns -1 when the CPU exposes no power features (e.g. under QEMU).
|
|
||||||
|
|
||||||
`SYS_BATTERYINFO` (188) fills a `BatteryInfo` struct: percentage, charge
|
|
||||||
state, AC presence, capacities, present rate, voltage and an estimated time
|
|
||||||
remaining. It returns -1 on a machine with no battery device at all, which
|
|
||||||
is how the desktop panel decides not to draw an indicator. Like
|
|
||||||
`SYS_POWERINFO` it is ungated read-only telemetry: the panel indicator has
|
|
||||||
to work in an unprivileged session, which holds no device capability to
|
|
||||||
spend on it.
|
|
||||||
|
|
||||||
The `power` program (`programs/src/power`) prints both:
|
|
||||||
|
|
||||||
```
|
|
||||||
power one-shot snapshot
|
|
||||||
power watch 1 Hz samples for 60 s (temp, avg freq, ceiling)
|
|
||||||
power watch 300 same, for 300 s
|
|
||||||
```
|
|
||||||
|
|
||||||
Boot log lines to look for (Kernel Log app):
|
|
||||||
|
|
||||||
```
|
|
||||||
[CpuPower] HWP enabled: perf range 4-54, EPP=0x80
|
|
||||||
[CpuPower] Thermal governor armed: TjMax=100C, passive=85C, hot=94C, ...
|
|
||||||
[CpuIdle] AP deep idle: MWAIT hint 0x20
|
|
||||||
[EC] Embedded Controller at cmd=66 data=62
|
|
||||||
[Battery] ACPI battery at \_SB.PCI0.LPCB.EC0.BAT0 (AC adapter present)
|
|
||||||
```
|
|
||||||
|
|
||||||
The desktop panel shows a battery icon left of the Wi-Fi and network icons,
|
|
||||||
using the icon pack's `battery-level-N[-charging]-symbolic` glyphs (one per
|
|
||||||
10% step, reloaded only when the step changes). Clicking it opens a popout
|
|
||||||
with the percentage in numbers, the charge state, time remaining and raw
|
|
||||||
capacity. The icon is absent entirely when `SYS_BATTERYINFO` fails, so a
|
|
||||||
desktop machine shows nothing there.
|
|
||||||
|
|
||||||
## Tuning
|
|
||||||
|
|
||||||
The governor constants live at the top of `Hal/CpuPower.cpp`
|
|
||||||
(`PassiveTempC`, `HotTempC`, `ResumeTempC`, `GovernorIntervalMs`, step
|
|
||||||
sizes) and were chosen to keep the package well below both TjMax (100 C
|
|
||||||
on the i9-13900HX) and the Clevo EC's emergency thresholds. If the fans
|
|
||||||
still spin up aggressively under sustained load, lower `PassiveTempC`
|
|
||||||
first; if builds feel throttled while cool, raise the recovery rate
|
|
||||||
instead of the trip points.
|
|
||||||
@@ -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`).
|
||||||
-100
@@ -1,100 +0,0 @@
|
|||||||
to-do: rewrite & convert to html for docs pages
|
|
||||||
|
|
||||||
# SSH
|
|
||||||
|
|
||||||
MontaukOS ships an SSH-2 server, `sshd`, that gives a remote client a shell on
|
|
||||||
the machine. It speaks enough of the protocol to interoperate with stock
|
|
||||||
OpenSSH clients:
|
|
||||||
|
|
||||||
```
|
|
||||||
ssh dan@montauk-box interactive shell
|
|
||||||
ssh dan@montauk-box 'ls 0:/' run one command
|
|
||||||
```
|
|
||||||
|
|
||||||
Remote access is **off by default**. Turn it on from Settings > SSH Server,
|
|
||||||
which flips `services.ssh.enabled` in `0:/config/init.toml`; `init` starts the
|
|
||||||
daemon on the next boot.
|
|
||||||
|
|
||||||
Two gates stand in front of a login: the account must exist in the MontaukOS
|
|
||||||
user database with a valid password, and it must be listed in the `[allow]`
|
|
||||||
table of `0:/config/ssh.toml`. A user absent from that table is refused even
|
|
||||||
with the right password. The Settings applet maintains the table; `sshd` never
|
|
||||||
writes it.
|
|
||||||
|
|
||||||
## Layout
|
|
||||||
|
|
||||||
```
|
|
||||||
programs/src/sshd/
|
|
||||||
main.cpp transport framing, key exchange, auth, session/channel loop
|
|
||||||
crypto.hpp host key storage, DH group 14 arithmetic, cipher/MAC wrappers
|
|
||||||
programs/src/sshserver/
|
|
||||||
main.cpp Settings applet: enable the service, edit the allow table
|
|
||||||
programs/include/montauk/ssh.h
|
|
||||||
shared policy helpers used by both
|
|
||||||
```
|
|
||||||
|
|
||||||
The crypto primitives (SHA-256, HMAC, AES-CTR, RSA signing, the DRBG) come from
|
|
||||||
BearSSL. The modular exponentiation for Diffie-Hellman is in `crypto.hpp`
|
|
||||||
because BearSSL exposes no public bignum API.
|
|
||||||
|
|
||||||
## What it implements
|
|
||||||
|
|
||||||
| | |
|
|
||||||
|---|---|
|
|
||||||
| Key exchange | `diffie-hellman-group14-sha256` |
|
|
||||||
| Host key | `rsa-sha2-256`, 2048-bit, generated on first start |
|
|
||||||
| Cipher | `aes128-ctr` |
|
|
||||||
| MAC | `hmac-sha2-256` |
|
|
||||||
| Compression | none |
|
|
||||||
| Auth | password only |
|
|
||||||
|
|
||||||
Client-initiated rekeying works, so long sessions survive OpenSSH's 1 GiB /
|
|
||||||
one-hour rekey threshold. Channel windows are tracked in both directions, so
|
|
||||||
sessions that move more than a window's worth of data keep flowing.
|
|
||||||
|
|
||||||
## What it does not
|
|
||||||
|
|
||||||
No public key authentication, no port or X11 forwarding, no SFTP or SCP. One
|
|
||||||
session channel per connection, and connections are served one at a time --
|
|
||||||
there is no `fork`, so a second client waits for the first to finish. A
|
|
||||||
pre-authentication timeout keeps an idle peer from holding the daemon shut.
|
|
||||||
|
|
||||||
`exec` requests are run by typing the command into an interactive shell rather
|
|
||||||
than executing it directly, so the prompt and the echoed command come back
|
|
||||||
mixed into the output. This is why `scp` and friends will not work as-is.
|
|
||||||
|
|
||||||
## The host key
|
|
||||||
|
|
||||||
Generated on first start and written to `0:/config/ssh_hostkey.toml`, separate
|
|
||||||
from the policy file so the unprivileged Settings applet -- which rewrites
|
|
||||||
`ssh.toml` whenever the allow table changes -- never round-trips private key
|
|
||||||
material.
|
|
||||||
|
|
||||||
That separation is hygiene, not protection: **the filesystem has no permission
|
|
||||||
model**, so any local process can read the host key. Closing that hole needs
|
|
||||||
FS-level access control, at which point the key file should be restricted to
|
|
||||||
the account `sshd` runs as. A key found in an older `ssh.toml` is migrated to
|
|
||||||
the new file on first start so the host key does not change under existing
|
|
||||||
clients.
|
|
||||||
|
|
||||||
## Debugging
|
|
||||||
|
|
||||||
`sshd` calls `montauk::print`, which reaches the boot console only. **It has no
|
|
||||||
usable diagnostics once the desktop is up**, pending the unified syslog.
|
|
||||||
|
|
||||||
Worth understanding, because it catches out anything written as a daemon:
|
|
||||||
`montauk::print` is `SYS_PRINT`, which writes the kernel *terminal*, not the
|
|
||||||
kernel *log*. Only in-kernel `KernelLogStream` writes raise `g_kernelLogDepth`,
|
|
||||||
and only those append to the ring buffer `SYS_LOG` reads -- so daemon output
|
|
||||||
never shows up in `klog`. On top of that, `Sys_Print` returns early once
|
|
||||||
`g_suppressKernelLog` is set, which the desktop does at startup, so the output is
|
|
||||||
discarded outright from then on. `init` spawns services with `spawn` rather than
|
|
||||||
`spawn_redir`, so there is no stream to capture either.
|
|
||||||
|
|
||||||
Until there is somewhere for it to go, debugging a handshake means watching the
|
|
||||||
boot console, or temporarily pointing the print calls at a file.
|
|
||||||
|
|
||||||
Failures print a numeric code -- `key exchange failed (N)`, `authentication
|
|
||||||
failed (N)` -- where N identifies the step; see the `kex_error` assignments in
|
|
||||||
`main.cpp`. Running the client with `ssh -vvv` and lining its trace up against
|
|
||||||
those codes is usually the fastest way to find where a handshake diverged.
|
|
||||||
-395
@@ -1,395 +0,0 @@
|
|||||||
to-do: rewrite & convert to html for docs pages
|
|
||||||
|
|
||||||
# Wi-Fi
|
|
||||||
|
|
||||||
MontaukOS drives Intel AX210-family adapters (the reference part is the AX211).
|
|
||||||
The driver scans, joins open and WPA2/WPA3-PSK networks, and presents itself to
|
|
||||||
the network stack as an ordinary Ethernet interface, so `dhcp`, `ping`, `nslookup`
|
|
||||||
and anything speaking sockets work over Wi-Fi exactly as they do over a cable.
|
|
||||||
|
|
||||||
```
|
|
||||||
wifi scan list nearby networks
|
|
||||||
wifi connect <ssid> <passphrase> join one
|
|
||||||
dhcp pick up an address
|
|
||||||
wifi status what you are connected to
|
|
||||||
wifi saved / wifi forget <ssid> networks remembered for next time
|
|
||||||
```
|
|
||||||
|
|
||||||
There is a graphical path too: a Wi-Fi entry in the desktop panel and a
|
|
||||||
Wi-Fi tab in the Network app. See "The desktop side" below.
|
|
||||||
|
|
||||||
## Layout
|
|
||||||
|
|
||||||
```
|
|
||||||
kernel/src/Drivers/Net/Wifi/
|
|
||||||
IwxTrans.cpp PCIe transport: MMIO, MSI-X, DMA rings, firmware boot,
|
|
||||||
host commands, RX processing, frame TX, key installation
|
|
||||||
IwxFw.cpp .ucode / .pnvm TLV parsing
|
|
||||||
IwxMvm.cpp post-ALIVE init, NVM, UMAC scan, RX dispatch
|
|
||||||
IwxConnect.cpp MLME: contexts, authenticate, associate, 802.11 <-> Ethernet
|
|
||||||
Wpa.cpp WPA2/WPA3-PSK supplicant (EAPOL 4-way + group rekey)
|
|
||||||
Ieee80211.hpp frame, element and RSN constants
|
|
||||||
Wifi.cpp subsystem facade: probe, scan table, syscalls, netif hooks
|
|
||||||
kernel/src/Libraries/Crypto.cpp SHA-1/SHA-256, HMAC, PBKDF2, AES, CMAC
|
|
||||||
kernel/src/Net/NetIf.cpp interface registry the Ethernet layer uses
|
|
||||||
```
|
|
||||||
|
|
||||||
## Joining a network
|
|
||||||
|
|
||||||
`SYS_WIFI_CONNECT` blocks until the link is up or the attempt fails, and
|
|
||||||
returns a `WIFI_ERR_*` code the `wifi` tool turns into a specific message
|
|
||||||
(wrong passphrase, unsupported security, AP out of range, and so on).
|
|
||||||
|
|
||||||
The sequence:
|
|
||||||
|
|
||||||
1. **Look up the BSS.** The SSID is matched against the scan table, strongest
|
|
||||||
signal first. If it is not there, one scan is run automatically and the
|
|
||||||
lookup retried, so `wifi connect` works without scanning first.
|
|
||||||
2. **Negotiate ciphers.** The AP's RSN element (kept verbatim in the scan
|
|
||||||
table) picks the pairwise cipher and AKM. CCMP is preferred over GCMP,
|
|
||||||
plain PSK over PSK-SHA256.
|
|
||||||
3. **Derive the PMK.** PBKDF2-HMAC-SHA1 over the passphrase with the SSID as
|
|
||||||
salt, 4096 iterations. A 64-character hex string is taken as a raw PSK
|
|
||||||
instead.
|
|
||||||
4. **Bring up firmware contexts.** PHY, MAC, binding and station, then one TX
|
|
||||||
queue on the management TID.
|
|
||||||
5. **Authenticate and associate.** Open-system authentication, then an
|
|
||||||
association request carrying the SSID, supported rates and, for encrypted
|
|
||||||
networks, the RSN element the supplicant built. Both are retransmitted up
|
|
||||||
to four times at 400 ms.
|
|
||||||
6. **Run the 4-way handshake.** EAPOL-Key messages 1-4, then the pairwise key
|
|
||||||
and the group key go into the firmware with `ADD_STA_KEY`.
|
|
||||||
7. **Report the link up.** Only now does `NetIf` see `wlan0` as usable.
|
|
||||||
|
|
||||||
## The data path
|
|
||||||
|
|
||||||
Once associated the driver translates between 802.11 and Ethernet II:
|
|
||||||
|
|
||||||
- **TX** - an Ethernet frame becomes a to-DS 802.11 data header plus an
|
|
||||||
RFC 1042 LLC/SNAP shim carrying the EtherType. The protected bit is set
|
|
||||||
once keys are installed and the firmware does the CCMP encryption.
|
|
||||||
- **RX** - the firmware decrypts and strips the MIC but leaves the 8-byte
|
|
||||||
CCMP header, which is skipped; the LLC/SNAP shim is replaced by an Ethernet
|
|
||||||
header built from addresses 1 and 3. EAPOL frames are diverted to the
|
|
||||||
supplicant instead.
|
|
||||||
|
|
||||||
One TX queue carries management frames, EAPOL and non-QoS data. The firmware
|
|
||||||
maps non-QoS data onto the management TID anyway, and the driver never
|
|
||||||
negotiates block-ack sessions that would need per-TID queues.
|
|
||||||
|
|
||||||
### Threading
|
|
||||||
|
|
||||||
The RX path runs under `IwxProcessEvents()`'s reentrancy guard and **must not
|
|
||||||
send a host command** - the command's completion is pumped by the very
|
|
||||||
function it would be re-entering. Anything needing a command (post-association
|
|
||||||
context updates, key installation, and therefore the whole EAPOL handshake) is
|
|
||||||
queued and applied from `IwxConnectService()`, which every idling core calls
|
|
||||||
and which is itself serialized. Transmitting is safe from either context: it
|
|
||||||
only writes a descriptor and rings the doorbell.
|
|
||||||
|
|
||||||
### Never wait on the clock under a spinlock
|
|
||||||
|
|
||||||
`kcp::Spinlock::Acquire()` does `cli`, and `Timekeeping::GetMilliseconds()` is
|
|
||||||
driven by the APIC timer interrupt. A wall-clock timeout inside a spinlock
|
|
||||||
therefore cannot expire: the counter never advances, the loop never ends, and
|
|
||||||
the machine locks solid with interrupts off - no mouse, no keyboard, no
|
|
||||||
scheduler. This is the same trap `ApicTimer.cpp` documents for the idle path.
|
|
||||||
|
|
||||||
`IwxSendCmd` holds a lock across a wait for the firmware's reply, so that lock
|
|
||||||
is a `kcp::Mutex` (which keeps interrupts enabled), and the wait carries a spin
|
|
||||||
cap as well as the clock check so it terminates even if the clock is somehow
|
|
||||||
stuck. It also bails immediately once the firmware is known to have asserted,
|
|
||||||
because nothing after that will ever be answered.
|
|
||||||
|
|
||||||
A firmware command that goes unanswered now dumps the firmware's own error
|
|
||||||
table on the first timeout - that names the command that asserted - and gives
|
|
||||||
up on the adapter after three, rather than stalling for seconds per command.
|
|
||||||
|
|
||||||
### Sample the clock after the work, not before it
|
|
||||||
|
|
||||||
`IwxConnectService()` sends host commands, and every one of them is a round
|
|
||||||
trip to the adapter that takes real milliseconds. Each step also stamps the
|
|
||||||
timestamp its deadline is measured from - `EnterState()` sets
|
|
||||||
`g_stateEnteredMs`, transmitting sets `g_lastTxMs`. A `now` read at the top of
|
|
||||||
the pass is therefore *older* than the stamps it is about to be compared
|
|
||||||
against, and because these are unsigned counters, `now - g_stateEnteredMs`
|
|
||||||
wraps to about 2^64 instead of going negative. Every deadline in the pass then
|
|
||||||
reads as long expired.
|
|
||||||
|
|
||||||
The symptom was a join that failed the instant it succeeded:
|
|
||||||
|
|
||||||
```
|
|
||||||
WiFi: [OK] Associated, AID 3
|
|
||||||
WiFi: [WARNING] Connection failed: timed out while joining the network
|
|
||||||
WiFi: [INFO] EAPOL frame received (99 bytes)
|
|
||||||
```
|
|
||||||
|
|
||||||
The two post-association context commands moved the clock forward, the timeout
|
|
||||||
check compared a stale `now` against the `g_stateEnteredMs` they had just set,
|
|
||||||
and the contexts came down before the access point's first EAPOL frame could
|
|
||||||
arrive - which is why message 1 shows up *after* the failure. So the clock is
|
|
||||||
read only once the command-sending work in the pass is done, and the
|
|
||||||
comparisons go through `Elapsed()`, which refuses to underflow.
|
|
||||||
|
|
||||||
### Teardown unwinds contexts in the order they depend on each other
|
|
||||||
|
|
||||||
Firmware 89 asserts when a context is taken away while another still points at
|
|
||||||
it, and the assert names the command rather than the reason. Three of these
|
|
||||||
have been hit so far:
|
|
||||||
|
|
||||||
| UMAC error | Cause |
|
|
||||||
|---|---|
|
|
||||||
| 0x2010330F | PHY binding and link activation folded into one LINK_CONFIG |
|
|
||||||
| 0x2010330E | link removed while its station still existed |
|
|
||||||
| 0x2000320F | link deactivated while the MAC was still marked associated |
|
|
||||||
|
|
||||||
The last one is the teardown side of the same rule. While
|
|
||||||
`MAC_CONFIG.is_assoc` is set, the firmware's MAC context owns the link carrying
|
|
||||||
the BSS, so `TearDown()` sends a `MAC_CONFIG` MODIFY clearing `is_assoc` first,
|
|
||||||
and only then removes the station, deactivates the link, removes the link,
|
|
||||||
removes the MAC and drops the PHY context - the order
|
|
||||||
`iwl_mvm_mld_vif_cfg_changed_station` and the paths below it use on the way
|
|
||||||
down. `tests/wifi/ap_mlme.py` pins that order.
|
|
||||||
|
|
||||||
### A bad access point must not become a bad computer
|
|
||||||
|
|
||||||
Three separate defects turned "the Wi-Fi connection went wrong" into "the whole
|
|
||||||
machine went wrong". They are worth keeping straight because they have nothing
|
|
||||||
to do with each other beyond sharing a trigger.
|
|
||||||
|
|
||||||
**The command wait is a busy spin on a core the scheduler has been told not to
|
|
||||||
touch.** `ServiceEvents()` runs from `ApicTimer::ServiceDeferredWork()`, which
|
|
||||||
sets `cpu->reservedForKernelWork` so a bottom half holding a process-context
|
|
||||||
mutex cannot be preempted into the process that would wait on it. The
|
|
||||||
scheduler honours that by refusing to place any process on that core and by
|
|
||||||
skipping it for the reschedule IPI, and on the BSP `RunBspMaintenance()` - the
|
|
||||||
thing that wakes sleeping processes - is not reached until the pass returns.
|
|
||||||
`IwxSendCmd` then waits for the firmware by spinning on `IwxDelayUs`, up to a
|
|
||||||
second. One pass of `TearDown()` is eight commands. Against an adapter that has
|
|
||||||
stopped answering, that pinned a core for the better part of ten seconds. The
|
|
||||||
symptom is not a Wi-Fi symptom at all: the cursor crawls, windows stop
|
|
||||||
repainting, everything stutters.
|
|
||||||
|
|
||||||
So a service pass now carries a budget - `IwxBeginServicePass()` /
|
|
||||||
`IwxEndServicePass()` - of roughly one command's worth of waiting in total.
|
|
||||||
Commands that do not fit are not sent at all (an abandoned command still holds
|
|
||||||
its ring slot, and a late answer would be misread as the *next* command's
|
|
||||||
completion) and are retried on the next trip round the idle loop. The bring-up
|
|
||||||
path does not bracket itself: it reserves its CPU deliberately and has nothing
|
|
||||||
to starve, so it keeps the full per-command timeout.
|
|
||||||
|
|
||||||
**Counting consecutive failures never fires on the failure that matters.** The
|
|
||||||
give-up rule was three unanswered commands in a row, with any success resetting
|
|
||||||
the count. An adapter that answers some commands and drops others - which is
|
|
||||||
exactly what a marginal link leaves the firmware doing - therefore never
|
|
||||||
reached the cutoff, while every drop still cost a full timeout. The stall was
|
|
||||||
not a one-off; it repeated indefinitely, and the driver never concluded
|
|
||||||
anything was wrong. It is a leaky bucket now: a failure adds one, a success
|
|
||||||
drains one, so a firmware failing even a fraction of its commands trips the
|
|
||||||
cutoff in bounded time.
|
|
||||||
|
|
||||||
**`IwxFwState::Error` was a one-way door.** Nothing anywhere cleared it. A
|
|
||||||
single firmware assert left `StartJoin()` returning `WIFI_ERR_NO_ADAPTER` -
|
|
||||||
which the user sees as *no adapter is ready* - for the rest of the uptime, and
|
|
||||||
the only way back was a reboot. Disconnecting and reconnecting could not help:
|
|
||||||
there was nothing to reconnect with. Since `IwxReadFirmware()` is idempotent
|
|
||||||
and the parsed image stays resident, the cure is to stop the device and run the
|
|
||||||
same bring-up again, which `ServiceRecovery()` now does. It is rate-limited to
|
|
||||||
one attempt per five seconds and capped at three, because the reset has its own
|
|
||||||
multi-second handshakes and an adapter that will not come back after three
|
|
||||||
tries is genuinely broken - repeating it forever would be its own kind of
|
|
||||||
stall.
|
|
||||||
|
|
||||||
### The link is up only while the access point says so
|
|
||||||
|
|
||||||
`IwxLinkUp()` was nothing but `g_state == Connected`, and nothing moved it off
|
|
||||||
that state unless the access point was polite enough to send a
|
|
||||||
deauthentication frame. An access point that simply stops being there - a phone
|
|
||||||
hotspot that sleeps, wanders off channel, or drops the station without saying
|
|
||||||
so - left the link reported as up indefinitely. `NetIf::Active()` went on
|
|
||||||
choosing `wlan0`, every packet went into the void, and the desktop showed a
|
|
||||||
healthy connection while nothing resolved and nothing connected.
|
|
||||||
|
|
||||||
Beacon loss cannot be used to notice this: once associated, `MacConfigCmd`
|
|
||||||
stops asking for beacons and the firmware tracks them itself. What is
|
|
||||||
observable is that our own frames stop being acknowledged, since `IwxTxComplete`
|
|
||||||
gets a status per frame. A long enough run of failures - with any
|
|
||||||
acknowledgement, or any frame received from the BSS, restarting the count -
|
|
||||||
means the access point is gone, and the link comes down so the stack can fall
|
|
||||||
back to a cable and the panel can report the truth.
|
|
||||||
|
|
||||||
The threshold is counted in frames rather than seconds on purpose: a link that
|
|
||||||
is merely idle has nothing to send and must not be torn down for it. And
|
|
||||||
`IwxTxComplete` runs inside the RX pump, so it only sets a flag; the teardown
|
|
||||||
happens in `IwxConnectService()`, where sending the commands it needs is
|
|
||||||
allowed. `tests/wifi/ap_mlme.py` pins both halves of that.
|
|
||||||
|
|
||||||
## The desktop side
|
|
||||||
|
|
||||||
### Nothing in the GUI may block
|
|
||||||
|
|
||||||
`SYS_WIFI_SCAN` sweeps for up to twenty seconds and `SYS_WIFI_CONNECT` waits
|
|
||||||
out a whole handshake. Either one called from `desktop.elf` would freeze the
|
|
||||||
compositor - and with it the mouse, the panel and every window - for seconds at
|
|
||||||
a time. So the same work has a second, non-blocking entry point:
|
|
||||||
|
|
||||||
| | |
|
|
||||||
|---|---|
|
|
||||||
| `SYS_WIFI_SCAN_START` | starts a sweep and returns immediately |
|
|
||||||
| `SYS_WIFI_RESULTS` | copies the scan table out without touching the radio |
|
|
||||||
| `SYS_WIFI_CONNECT_ASYNC` | starts a join and returns immediately |
|
|
||||||
| `SYS_NETIFS` | lists the registered interfaces (see below) |
|
|
||||||
|
|
||||||
`WifiInfo` grew the fields that make polling enough to follow along:
|
|
||||||
`scanning` and `scanGeneration` for the sweep, `joining` and `connState` for
|
|
||||||
the handshake, and `lastError` for how the last join ended. The GUI reads
|
|
||||||
`SYS_WIFI_INFO` every 400 ms while something is in flight and every three
|
|
||||||
seconds otherwise.
|
|
||||||
|
|
||||||
The deadlines belong to the kernel, not the caller: `ServiceAsync()` runs from
|
|
||||||
`ServiceEvents()` - after the RX pump has returned, so it is allowed to send
|
|
||||||
commands - and aborts a scan that overruns, tears down a join that stalls, and
|
|
||||||
records `lastError` when one fails. A failed join is unwound the moment it
|
|
||||||
fails, so `connState` is back to idle by the time anyone looks; `lastError` is
|
|
||||||
what survives to be reported.
|
|
||||||
|
|
||||||
### Wired and wireless are separate on the panel
|
|
||||||
|
|
||||||
The IP configuration is global to the stack, so "which interface does this
|
|
||||||
address belong to?" is not a question `SYS_GETNETCFG` can answer. `SYS_NETIFS`
|
|
||||||
reports each registered interface with its name, MAC, link state and an
|
|
||||||
`active` flag - the one `NetIf::Active()` currently sends through. The Ethernet
|
|
||||||
popup shows the address only while the wired interface is the active one, and
|
|
||||||
says "Not in use" when the cable is up but Wi-Fi is carrying the traffic; the
|
|
||||||
Wi-Fi popup does the mirror image. Each icon appears only when its hardware
|
|
||||||
does: no wired interface, no Ethernet icon; no adapter, no Wi-Fi icon.
|
|
||||||
|
|
||||||
The Wi-Fi icon stays white whatever the radio is doing. State belongs in the
|
|
||||||
popup, and an icon that changes colour next to the clock is just noise.
|
|
||||||
|
|
||||||
Picking a network that needs a passphrase opens a real window - created with
|
|
||||||
`desktop_create_window()` and the four callbacks, exactly like the reboot and
|
|
||||||
shutdown dialogs in `dialogs.cpp` - rather than something painted into the
|
|
||||||
panel overlay. It therefore has a title bar, can be dragged and closed, appears
|
|
||||||
in the window list, and gets its text field, checkboxes and buttons from the
|
|
||||||
same `mtk` widgets the settings apps use.
|
|
||||||
|
|
||||||
### What runs at startup
|
|
||||||
|
|
||||||
`desktop.elf` keeps looking for an adapter until one appears (firmware loads
|
|
||||||
well after login), starts one scan as soon as the firmware reports ready, and
|
|
||||||
when the results land joins the strongest saved network that is in range. Once
|
|
||||||
the link is up, and only if no address is configured, it spawns `dhcp.elf`.
|
|
||||||
|
|
||||||
### Saved networks
|
|
||||||
|
|
||||||
`0:/config/wifi.toml` holds them:
|
|
||||||
|
|
||||||
```toml
|
|
||||||
[wifi]
|
|
||||||
autoconnect = true
|
|
||||||
|
|
||||||
[network.0]
|
|
||||||
ssid = "Home"
|
|
||||||
psk = "passphrase"
|
|
||||||
```
|
|
||||||
|
|
||||||
**No default copy of this file ships in the image.** It is created on the first
|
|
||||||
save, the way `bluetooth.toml`, `display.toml` and `session.toml` are. A shipped
|
|
||||||
default looks harmless - it only documents the schema - but it is laid down
|
|
||||||
again by anything that refreshes the system files, and it takes the user's saved
|
|
||||||
networks with it when it lands. That is exactly what happened the first time
|
|
||||||
this was written, and it is why the schema is documented here instead.
|
|
||||||
|
|
||||||
`programs/include/montauk/wifi.h` is the one implementation of reading,
|
|
||||||
writing and searching that file, shared by the panel, the Network app and the
|
|
||||||
`wifi` command, so all three agree on the schema. The passphrase is stored as
|
|
||||||
typed because that is what the join needs - the kernel derives the PMK from it,
|
|
||||||
or takes a 64-character hex string as a raw PSK. There is no key store to hide
|
|
||||||
it in: anyone who can read `0:/config` can read the passphrases.
|
|
||||||
|
|
||||||
## The interface registry
|
|
||||||
|
|
||||||
`Net::NetIf` replaced the Ethernet layer's direct calls into the E1000
|
|
||||||
drivers. Drivers register a name, a kind, and three function pointers; the
|
|
||||||
stack sends through `NetIf::Active()`, which prefers a wired interface with a
|
|
||||||
link and otherwise takes the first interface reporting one. A Wi-Fi-only
|
|
||||||
machine therefore has no link until it joins a network, and a machine with a
|
|
||||||
cable plugged in keeps using it.
|
|
||||||
|
|
||||||
`SYS_NETSTATUS` reports whichever interface is active, so `ifconfig` shows the
|
|
||||||
wireless counters once Wi-Fi is carrying traffic.
|
|
||||||
|
|
||||||
## What is supported
|
|
||||||
|
|
||||||
| | |
|
|
||||||
|---|---|
|
|
||||||
| Open networks | yes |
|
|
||||||
| WPA2-PSK, CCMP or GCMP | yes |
|
|
||||||
| WPA2-PSK-SHA256 | yes |
|
|
||||||
| WPA3 transition mode (PSK advertised alongside SAE) | yes, joins via PSK |
|
|
||||||
| Group key rekeying | yes |
|
|
||||||
| WPA3-only (SAE) | no |
|
|
||||||
| Management frame protection required (MFPR) | no |
|
|
||||||
| WEP, original WPA / TKIP | no |
|
|
||||||
| 802.1X enterprise (EAP) | no |
|
|
||||||
| Block-ack aggregation, HT/VHT/HE rates | no - legacy rates only |
|
|
||||||
|
|
||||||
SAE needs finite-field or elliptic-curve arithmetic that does not belong in
|
|
||||||
this kernel, and MFP needs BIP. Both are rejected up front with a specific
|
|
||||||
log line rather than failing partway through a handshake. Mixed WPA/WPA2
|
|
||||||
networks that still broadcast under TKIP are refused for the same reason: the
|
|
||||||
pairwise key would install but every broadcast frame would be dropped, which
|
|
||||||
looks like a working connection that cannot get a DHCP lease.
|
|
||||||
|
|
||||||
## Crypto
|
|
||||||
|
|
||||||
`kernel/src/Libraries/Crypto.cpp` exists because the supplicant runs in the
|
|
||||||
kernel and BearSSL is a userspace library. It provides SHA-1, SHA-256, HMAC
|
|
||||||
over both, PBKDF2-HMAC-SHA1, AES-128/256, RFC 3394 key wrap/unwrap and
|
|
||||||
AES-CMAC. It is not a general-purpose crypto library and should not be used
|
|
||||||
as one.
|
|
||||||
|
|
||||||
## Testing
|
|
||||||
|
|
||||||
`./tests/wifi/run.sh` compiles the shipping sources for the host against a
|
|
||||||
small shim and drives them from Python. It is the real `Crypto.cpp`,
|
|
||||||
`Wpa.cpp` and `IwxConnect.cpp`, not a copy, with only the transport stubbed.
|
|
||||||
|
|
||||||
- **Crypto primitives** against the published vectors - FIPS-197 for AES,
|
|
||||||
RFC 2202/4231 for HMAC, RFC 3394 for key wrap, RFC 4493 for CMAC, and the
|
|
||||||
IEEE 802.11i Annex H.4 WPA passphrase vectors for PBKDF2.
|
|
||||||
- **The supplicant** against an independent authenticator using `hashlib` and
|
|
||||||
`cryptography`: messages 2 and 4 carry MICs that verify under a PTK the AP
|
|
||||||
derived itself, the installed TK and GTK match the AP's, a wrong passphrase
|
|
||||||
produces a MIC the AP rejects, group rekeys and message-3 retransmissions
|
|
||||||
are answered, and RSN negotiation picks the right suites across eight
|
|
||||||
real-world information elements.
|
|
||||||
- **The MLME and data path** against a simulated AP that decodes every frame
|
|
||||||
the driver emits: the authentication request, the association request and
|
|
||||||
its elements (SSID, rates, capabilities, RSN), the handshake carried inside
|
|
||||||
real 802.11 data frames, key installation arguments, and the encapsulation
|
|
||||||
both ways - to-DS addressing, the protected bit, LLC/SNAP, sequence numbers,
|
|
||||||
broadcast delivery, and the filtering of foreign-BSSID and null-data frames.
|
|
||||||
Also the branches: open networks, retransmission and give-up when the AP is
|
|
||||||
silent, authentication and association rejections, and an AP-initiated
|
|
||||||
deauthentication bringing the link down.
|
|
||||||
- **Firmware context ordering** - the MLD command sizes and field offsets
|
|
||||||
against the decoded Linux trace, and the order the teardown unwinds the
|
|
||||||
contexts in, which is what the asserts above are about.
|
|
||||||
|
|
||||||
The harness clock advances on every host command
|
|
||||||
(`CMD_ROUND_TRIP_MS` in `mlme_harness.cpp`) rather than standing still. That
|
|
||||||
detail matters: a frozen clock makes every elapsed-time comparison in the
|
|
||||||
service loop trivially true or trivially false, and hid the underflow described
|
|
||||||
under "Sample the clock after the work, not before it" - the host tests passed
|
|
||||||
while the adapter could not join a network at all. Anything that reads
|
|
||||||
`Timekeeping::GetMilliseconds()` should be tested with time actually moving.
|
|
||||||
|
|
||||||
What is left needs the adapter, because it is the firmware's opinion rather
|
|
||||||
than the driver's logic: whether the firmware accepts the TX command and TFD
|
|
||||||
layout and actually radiates the frames, whether `ADD_STA_KEY` installs the
|
|
||||||
keys the driver asks for, whether the RX MPDU descriptor is read correctly off
|
|
||||||
real receptions, and whether association succeeds against a real AP's timing
|
|
||||||
and rate expectations. None of that can be exercised in QEMU, which has no
|
|
||||||
AX210-family device to emulate.
|
|
||||||
+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 = .;
|
||||||
}
|
}
|
||||||
+2653
-1073
File diff suppressed because it is too large.
Load diff
@@ -97,6 +97,35 @@ namespace Hal {
|
|||||||
static constexpr uint8_t ConcatResOp = 0x84;
|
static constexpr uint8_t ConcatResOp = 0x84;
|
||||||
static constexpr uint8_t ToHexStringOp = 0x98;
|
static constexpr uint8_t ToHexStringOp = 0x98;
|
||||||
static constexpr uint8_t ToDecimalStringOp = 0x97;
|
static constexpr uint8_t ToDecimalStringOp = 0x97;
|
||||||
|
static constexpr uint8_t ExternalOp = 0x15;
|
||||||
|
static constexpr uint8_t RefOfOp = 0x71;
|
||||||
|
static constexpr uint8_t FindSetLeftBitOp = 0x81;
|
||||||
|
static constexpr uint8_t FindSetRightBitOp = 0x82;
|
||||||
|
static constexpr uint8_t NotifyOp = 0x86;
|
||||||
|
static constexpr uint8_t MatchOp = 0x89;
|
||||||
|
static constexpr uint8_t ObjectTypeOp = 0x8E;
|
||||||
|
static constexpr uint8_t CreateQWordFieldOp = 0x8F;
|
||||||
|
static constexpr uint8_t ToStringOp = 0x9C;
|
||||||
|
static constexpr uint8_t CopyObjectOp = 0x9D;
|
||||||
|
static constexpr uint8_t MidOp = 0x9E;
|
||||||
|
static constexpr uint8_t ContinueOp = 0x9F;
|
||||||
|
static constexpr uint8_t BreakPointOp = 0xCC;
|
||||||
|
|
||||||
|
// More extended opcodes (after ExtOpPrefix)
|
||||||
|
static constexpr uint8_t CondRefOfOp = 0x12;
|
||||||
|
static constexpr uint8_t CreateFieldOp = 0x13;
|
||||||
|
static constexpr uint8_t LoadTableOp = 0x1F;
|
||||||
|
static constexpr uint8_t LoadOp = 0x20;
|
||||||
|
static constexpr uint8_t SignalOp = 0x24;
|
||||||
|
static constexpr uint8_t WaitOp = 0x25;
|
||||||
|
static constexpr uint8_t ResetOp = 0x26;
|
||||||
|
static constexpr uint8_t FromBCDOp = 0x28;
|
||||||
|
static constexpr uint8_t ToBCDOp = 0x29;
|
||||||
|
static constexpr uint8_t UnloadOp = 0x2A;
|
||||||
|
static constexpr uint8_t DebugOp = 0x31;
|
||||||
|
static constexpr uint8_t FatalOp = 0x32;
|
||||||
|
static constexpr uint8_t TimerOp = 0x33;
|
||||||
|
static constexpr uint8_t DataRegionOp = 0x88;
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
@@ -104,7 +133,11 @@ namespace Hal {
|
|||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
static constexpr int MaxCallDepth = 16;
|
static constexpr int MaxCallDepth = 16;
|
||||||
static constexpr int MaxLoopIterations = 1024;
|
|
||||||
|
// ACPICA's ceiling. Firmware polls hardware in While loops with a
|
||||||
|
// Sleep or Stall in the body (link training, power-good, L2/L3 ready)
|
||||||
|
// and expects more than a token number of passes.
|
||||||
|
static constexpr int MaxLoopIterations = 0xFFFF;
|
||||||
|
|
||||||
// Backstop cap on evaluation nesting. This counter is shared by
|
// Backstop cap on evaluation nesting. This counter is shared by
|
||||||
// expression evaluation AND block execution, and block nesting is
|
// expression evaluation AND block execution, and block nesting is
|
||||||
@@ -133,8 +166,10 @@ namespace Hal {
|
|||||||
public:
|
public:
|
||||||
Interpreter();
|
Interpreter();
|
||||||
|
|
||||||
// Parse a DSDT or SSDT table into the namespace.
|
// Parse a DSDT or SSDT table into the namespace. Tables may be
|
||||||
// tableData points to the CommonSDTHeader (HHDM-mapped).
|
// loaded one after another; each method remembers which table
|
||||||
|
// its body lives in. tableData points to the CommonSDTHeader
|
||||||
|
// (HHDM-mapped) and must stay mapped.
|
||||||
bool LoadTable(void* tableData);
|
bool LoadTable(void* tableData);
|
||||||
|
|
||||||
// Evaluate a named object, returning its value.
|
// Evaluate a named object, returning its value.
|
||||||
@@ -144,6 +179,28 @@ namespace Hal {
|
|||||||
// Evaluate a method with arguments.
|
// Evaluate a method with arguments.
|
||||||
bool EvaluateMethod(const char* path, const Object* args, int argCount, Object& result);
|
bool EvaluateMethod(const char* path, const Object* args, int argCount, Object& result);
|
||||||
|
|
||||||
|
// Evaluate a node: a method is run with `args`, a field is read,
|
||||||
|
// anything else hands back its value (non-data objects as a
|
||||||
|
// Reference to the node).
|
||||||
|
bool EvaluateNode(int32_t node, const Object* args, int argCount, Object& result);
|
||||||
|
|
||||||
|
// Find the child of `parent` named `seg` ("_PS3", "_ON"), -1 if
|
||||||
|
// there is none. Short names are padded with '_'.
|
||||||
|
int32_t FindChild(int32_t parent, const char* seg) const;
|
||||||
|
|
||||||
|
// EvaluateNode() on a named child. False when it does not exist.
|
||||||
|
bool EvaluateChild(int32_t parent, const char* seg, const Object* args,
|
||||||
|
int argCount, Object& result);
|
||||||
|
|
||||||
|
// Turn a Reference, or a NameRef/String naming an object relative
|
||||||
|
// to `scopeNode`, into a node index. -1 when it names nothing.
|
||||||
|
int32_t ResolveReference(const Object& obj, int32_t scopeNode) const;
|
||||||
|
|
||||||
|
// The PCI function a Device node describes: its _ADR on the bus
|
||||||
|
// behind its parent (a root bridge's _BBN, or a PCI-to-PCI
|
||||||
|
// bridge's secondary bus). False when it cannot be placed.
|
||||||
|
bool GetPciAddress(int32_t deviceNode, uint8_t& bus, uint8_t& dev, uint8_t& fn);
|
||||||
|
|
||||||
// Read a field value. Returns integer.
|
// Read a field value. Returns integer.
|
||||||
bool ReadField(int32_t nodeIndex, uint64_t& value);
|
bool ReadField(int32_t nodeIndex, uint64_t& value);
|
||||||
|
|
||||||
@@ -157,6 +214,10 @@ namespace Hal {
|
|||||||
// Check if the interpreter has been initialized
|
// Check if the interpreter has been initialized
|
||||||
bool IsInitialized() const { return m_initialized; }
|
bool IsInitialized() const { return m_initialized; }
|
||||||
|
|
||||||
|
// Nodes [0, DsdtNodeCount()) came from the DSDT. Nodes past it were
|
||||||
|
// added by SSDTs (or by methods run since).
|
||||||
|
int32_t DsdtNodeCount() const { return m_dsdtNodeCount; }
|
||||||
|
|
||||||
// Log every opcode executed, with its offset, for the next
|
// Log every opcode executed, with its offset, for the next
|
||||||
// `budget` opcodes. The only way to see where a method that runs to
|
// `budget` opcodes. The only way to see where a method that runs to
|
||||||
// completion without returning a value actually goes.
|
// completion without returning a value actually goes.
|
||||||
@@ -170,8 +231,10 @@ namespace Hal {
|
|||||||
// Read one element of a Package object. Works for both a package
|
// Read one element of a Package object. Works for both a package
|
||||||
// whose elements were computed at run time (materialized in the
|
// whose elements were computed at run time (materialized in the
|
||||||
// package store) and one that is still just the constant AML bytes
|
// package store) and one that is still just the constant AML bytes
|
||||||
// it was declared with. Returns false when the object is not a
|
// it was declared with. A name in a constant package comes back as
|
||||||
// package or the index is out of range.
|
// a NameRef unless it resolves (from the root) to plain data, in
|
||||||
|
// which case its value is returned. Returns false when the object
|
||||||
|
// is not a package or the index is out of range.
|
||||||
bool GetPackageElement(const Object& package, int index, Object& result);
|
bool GetPackageElement(const Object& package, int index, Object& result);
|
||||||
|
|
||||||
// Number of elements a Package declares.
|
// Number of elements a Package declares.
|
||||||
@@ -183,17 +246,35 @@ namespace Hal {
|
|||||||
int32_t MaterializeNodePackage(int32_t nodeIndex);
|
int32_t MaterializeNodePackage(int32_t nodeIndex);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
struct ExecContext;
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// Parsing (table load)
|
// Parsing (table load)
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
// ctx is the method being executed when a definition appears in
|
||||||
|
// a method body, nullptr at table load. Operands that are TermArgs
|
||||||
|
// (region offsets, buffer sizes, bank values) are evaluated in it.
|
||||||
bool ParseBlock(const uint8_t* aml, uint32_t offset, uint32_t endOffset,
|
bool ParseBlock(const uint8_t* aml, uint32_t offset, uint32_t endOffset,
|
||||||
int32_t scopeNode);
|
int32_t scopeNode);
|
||||||
|
|
||||||
bool ParseNamedObject(const uint8_t* aml, uint32_t* pos, uint32_t endOffset,
|
bool ParseNamedObject(const uint8_t* aml, uint32_t* pos, uint32_t endOffset,
|
||||||
int32_t scopeNode);
|
int32_t scopeNode, ExecContext* ctx = nullptr);
|
||||||
|
|
||||||
bool ParseExtendedOp(const uint8_t* aml, uint32_t* pos, uint32_t endOffset,
|
bool ParseExtendedOp(const uint8_t* aml, uint32_t* pos, uint32_t endOffset,
|
||||||
int32_t scopeNode);
|
int32_t scopeNode, ExecContext* ctx = nullptr);
|
||||||
|
|
||||||
|
// The FieldList shared by Field, IndexField and BankField.
|
||||||
|
void ParseFieldList(const uint8_t* aml, uint32_t* pos, uint32_t fieldEnd,
|
||||||
|
int32_t scopeNode, uint8_t fieldFlags, const Object& proto);
|
||||||
|
|
||||||
|
// Evaluate a TermArg operand of a definition: in the running
|
||||||
|
// method, or in the table-load context.
|
||||||
|
bool EvalOperand(ExecContext* ctx, const uint8_t* aml, uint32_t* pos,
|
||||||
|
uint32_t endOffset, int32_t scopeNode, Object& result);
|
||||||
|
|
||||||
|
// CreateBitField .. CreateQWordField and CreateField.
|
||||||
|
bool CreateBufferField(ExecContext& ctx, uint32_t* pos, uint32_t endOffset,
|
||||||
|
uint8_t op);
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
@@ -214,6 +295,10 @@ namespace Hal {
|
|||||||
char* outRelative = nullptr, int relLen = 0,
|
char* outRelative = nullptr, int relLen = 0,
|
||||||
bool* outSearchable = nullptr);
|
bool* outSearchable = nullptr);
|
||||||
|
|
||||||
|
// Read a NameString and resolve it with the search rule. -1 when
|
||||||
|
// it names nothing.
|
||||||
|
int32_t ReadAndResolveName(const uint8_t* aml, uint32_t* pos, int32_t scopeNode);
|
||||||
|
|
||||||
// Read a single 4-char NameSeg from AML. Advances *pos.
|
// Read a single 4-char NameSeg from AML. Advances *pos.
|
||||||
void ReadNameSeg(const uint8_t* aml, uint32_t* pos, char* outSeg);
|
void ReadNameSeg(const uint8_t* aml, uint32_t* pos, char* outSeg);
|
||||||
|
|
||||||
@@ -225,11 +310,10 @@ namespace Hal {
|
|||||||
uint32_t DecodePkgLength(const uint8_t* aml, uint32_t* pos);
|
uint32_t DecodePkgLength(const uint8_t* aml, uint32_t* pos);
|
||||||
|
|
||||||
// Decode one constant package element out of raw AML bytes and
|
// Decode one constant package element out of raw AML bytes and
|
||||||
// advance *pos past it. Non-constant elements (name references,
|
// advance *pos past it. Names come back as NameRef; locals and
|
||||||
// locals) decode to integer 0, which is what they were worth
|
// anything else that needs a running method decode to integer 0.
|
||||||
// before the package store existed.
|
|
||||||
bool DecodeConstElement(const uint8_t* data, uint32_t length,
|
bool DecodeConstElement(const uint8_t* data, uint32_t length,
|
||||||
uint32_t* pos, Object& out);
|
uint32_t* pos, Object& out) const;
|
||||||
uint64_t DecodeInteger(const uint8_t* aml, uint32_t* pos);
|
uint64_t DecodeInteger(const uint8_t* aml, uint32_t* pos);
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
@@ -247,6 +331,8 @@ namespace Hal {
|
|||||||
Object ReturnValue;
|
Object ReturnValue;
|
||||||
bool Returned;
|
bool Returned;
|
||||||
bool Broken;
|
bool Broken;
|
||||||
|
bool Continued;
|
||||||
|
bool Aborted; // undecodable AML: unwind the method
|
||||||
int Depth; // method-call nesting
|
int Depth; // method-call nesting
|
||||||
int ExprDepth; // expression/block nesting
|
int ExprDepth; // expression/block nesting
|
||||||
};
|
};
|
||||||
@@ -259,36 +345,117 @@ namespace Hal {
|
|||||||
~DepthGuard() { Value--; }
|
~DepthGuard() { Value--; }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Where a Store lands.
|
||||||
|
struct Target {
|
||||||
|
enum class Kind : uint8_t { None, Node, Local, Arg, PkgElement, BufByte };
|
||||||
|
Kind What = Kind::None;
|
||||||
|
int32_t Node = -1;
|
||||||
|
int Index = 0; // local/arg number, or element index
|
||||||
|
int32_t PkgStore = -1;
|
||||||
|
Object* Buf = nullptr; // BufByte: the buffer being indexed
|
||||||
|
};
|
||||||
|
|
||||||
bool ExecuteBlock(ExecContext& ctx, uint32_t offset, uint32_t endOffset);
|
bool ExecuteBlock(ExecContext& ctx, uint32_t offset, uint32_t endOffset);
|
||||||
bool ExecuteOpcode(ExecContext& ctx, uint32_t* pos, uint32_t endOffset);
|
bool ExecuteOpcode(ExecContext& ctx, uint32_t* pos, uint32_t endOffset);
|
||||||
|
|
||||||
// Evaluate a term (expression that produces a value) within an execution context.
|
// Evaluate a term (expression that produces a value) within an execution context.
|
||||||
bool EvalTerm(ExecContext& ctx, uint32_t* pos, uint32_t endOffset, Object& result);
|
bool EvalTerm(ExecContext& ctx, uint32_t* pos, uint32_t endOffset, Object& result);
|
||||||
|
|
||||||
// Evaluate a "SuperName" target for Store operations.
|
// Extended (0x5B-prefixed) opcodes in expression or statement position.
|
||||||
// Returns the node index for named targets, or handles locals/args.
|
bool EvalExtOp(ExecContext& ctx, uint32_t* pos, uint32_t endOffset, Object& result);
|
||||||
// If isLocal/isArg is set, localIdx/argIdx contains the index.
|
|
||||||
// A target may also be Index(package, n), which names a package
|
|
||||||
// element rather than a node, local or arg. pkgStore/pkgElement
|
|
||||||
// carry that case (pkgStore > 0 when it applies).
|
|
||||||
bool EvalTarget(ExecContext& ctx, uint32_t* pos,
|
|
||||||
int32_t& nodeIndex, bool& isLocal, int& localIdx,
|
|
||||||
bool& isArg, int& argIdx,
|
|
||||||
int32_t& pkgStore, int& pkgElement);
|
|
||||||
|
|
||||||
// Store a value to a target (node, local, or arg).
|
// EvalTerm's opcode handlers. They live out of line so the
|
||||||
void StoreToTarget(ExecContext& ctx, const Object& value,
|
// recursive path only carries the locals of the one running.
|
||||||
int32_t nodeIndex, bool isLocal, int localIdx,
|
bool EvalBufferOp(ExecContext& ctx, uint32_t* pos, uint32_t endOffset, Object& result);
|
||||||
bool isArg, int argIdx,
|
bool EvalPackageOp(ExecContext& ctx, uint32_t* pos, uint32_t endOffset, Object& result);
|
||||||
int32_t pkgStore = -1, int pkgElement = 0);
|
bool EvalBinary(ExecContext& ctx, uint32_t* pos, uint32_t endOffset, Object& result);
|
||||||
|
bool EvalUnary(ExecContext& ctx, uint32_t* pos, uint32_t endOffset, Object& result);
|
||||||
|
bool EvalDivide(ExecContext& ctx, uint32_t* pos, uint32_t endOffset, Object& result);
|
||||||
|
bool EvalStoreOp(ExecContext& ctx, uint32_t* pos, uint32_t endOffset, Object& result);
|
||||||
|
bool EvalReference(ExecContext& ctx, uint32_t* pos, uint32_t endOffset, Object& result);
|
||||||
|
bool EvalIndexOp(ExecContext& ctx, uint32_t* pos, uint32_t endOffset, Object& result);
|
||||||
|
bool EvalMatchOp(ExecContext& ctx, uint32_t* pos, uint32_t endOffset, Object& result);
|
||||||
|
bool EvalMidOp(ExecContext& ctx, uint32_t* pos, uint32_t endOffset, Object& result);
|
||||||
|
bool EvalNameRef(ExecContext& ctx, uint32_t* pos, uint32_t endOffset, Object& result);
|
||||||
|
bool CallMethod(ExecContext& ctx, int32_t node, uint32_t* pos, uint32_t endOffset,
|
||||||
|
Object& result);
|
||||||
|
bool IndexElement(const Object& source, uint64_t idx, Object& out);
|
||||||
|
void ReportUnresolved(ExecContext& ctx, uint32_t namePos);
|
||||||
|
|
||||||
|
// Target forms that need operand evaluation of their own.
|
||||||
|
bool EvalIndexTarget(ExecContext& ctx, uint32_t* pos, Target& target);
|
||||||
|
bool EvalDerefTarget(ExecContext& ctx, uint32_t* pos, Target& target);
|
||||||
|
|
||||||
|
// A package element: like EvalTerm, except that a name is a
|
||||||
|
// reference to the object rather than a call or a read.
|
||||||
|
bool EvalPackageElement(ExecContext& ctx, uint32_t* pos, uint32_t endOffset,
|
||||||
|
Object& result);
|
||||||
|
|
||||||
|
// Parse a SuperName/Target operand.
|
||||||
|
bool EvalTarget(ExecContext& ctx, uint32_t* pos, Target& target);
|
||||||
|
|
||||||
|
// Store a value to a target, with ACPI's implicit conversion for
|
||||||
|
// named data objects.
|
||||||
|
void StoreToTarget(ExecContext& ctx, const Object& value, const Target& target);
|
||||||
|
|
||||||
|
// Parse a Target operand and store `value` there.
|
||||||
|
void StoreResult(ExecContext& ctx, uint32_t* pos, const Object& value);
|
||||||
|
|
||||||
|
// The current value behind a target (for Increment and friends).
|
||||||
|
Object ReadTarget(ExecContext& ctx, const Target& target);
|
||||||
|
|
||||||
|
// Run a method node. Caller holds m_evalLock.
|
||||||
|
bool InvokeMethodNode(int32_t node, const Object* args, int argCount,
|
||||||
|
Object& result, int depth, int exprDepth);
|
||||||
|
|
||||||
|
// Run a method in a context already acquired and holding its
|
||||||
|
// arguments; releases the context.
|
||||||
|
bool RunMethodIn(ExecContext* ctx, int32_t node, int depth, int exprDepth,
|
||||||
|
Object& result);
|
||||||
|
|
||||||
|
// The value of a node as an operand: fields read, methods run
|
||||||
|
// without arguments, non-data objects become References.
|
||||||
|
bool NodeValue(int32_t node, Object& result, int depth, int exprDepth);
|
||||||
|
|
||||||
|
// Log an opcode we cannot decode and stop the method: carrying on
|
||||||
|
// past it would execute operand bytes as code.
|
||||||
|
bool Abort(ExecContext& ctx, uint32_t pos, const char* why);
|
||||||
|
|
||||||
|
// Native methods (\_OSI).
|
||||||
|
bool RunNativeMethod(uint8_t which, const Object* args, int argCount, Object& result);
|
||||||
|
|
||||||
|
// Install the OS-defined objects (\_OSI, \_REV, \_OS_, \_GL_).
|
||||||
|
void InstallPredefined();
|
||||||
|
|
||||||
|
// Device identification (for PCI_Config regions).
|
||||||
|
bool IntegerChild(int32_t device, const char* seg, uint64_t fallback,
|
||||||
|
uint64_t& value, int depth);
|
||||||
|
bool IsPciRootBridge(int32_t device, int depth);
|
||||||
|
bool ResolvePciAddress(int32_t node, uint8_t& bus, uint8_t& dev,
|
||||||
|
uint8_t& fn, int depth);
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
// Field I/O
|
// Field I/O
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
bool ReadRegion(RegionSpace space, uint64_t address, uint32_t bitWidth, uint64_t& value);
|
// Access `bits` (little-endian, bitLength long) of a field unit.
|
||||||
bool WriteRegion(RegionSpace space, uint64_t address, uint32_t bitWidth, uint64_t value);
|
bool AccessField(int32_t fieldNode, bool write, uint8_t* bits);
|
||||||
|
bool ReadFieldObject(int32_t fieldNode, Object& value);
|
||||||
|
bool WriteFieldObject(int32_t fieldNode, const Object& value);
|
||||||
|
|
||||||
|
// One aligned access unit of a field's backing store.
|
||||||
|
bool FieldUnitIo(const Object& field, uint64_t byteOffset, uint32_t width,
|
||||||
|
bool write, uint64_t& value);
|
||||||
|
|
||||||
|
// Region access at byte `offset` into the region, `width` bytes.
|
||||||
|
bool ReadRegion(int32_t regionNode, uint64_t offset, uint32_t width, uint64_t& value);
|
||||||
|
bool WriteRegion(int32_t regionNode, uint64_t offset, uint32_t width, uint64_t value);
|
||||||
|
bool RegionIo(int32_t regionNode, uint64_t offset, uint32_t width,
|
||||||
|
bool write, uint64_t& value);
|
||||||
|
|
||||||
|
bool ReadBufferField(const Object& field, Object& value);
|
||||||
|
bool WriteBufferField(const Object& field, const Object& value);
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
@@ -310,16 +477,23 @@ namespace Hal {
|
|||||||
ExecContext* AcquireContext();
|
ExecContext* AcquireContext();
|
||||||
void ReleaseContext();
|
void ReleaseContext();
|
||||||
|
|
||||||
|
// Run `body` as a top-level evaluation: serialized, with the stack
|
||||||
|
// budget anchored here.
|
||||||
|
template<typename Fn> bool TopLevel(Fn body);
|
||||||
|
|
||||||
Namespace m_ns;
|
Namespace m_ns;
|
||||||
const uint8_t* m_dsdt;
|
|
||||||
uint32_t m_dsdtLength;
|
|
||||||
bool m_initialized;
|
bool m_initialized;
|
||||||
|
bool m_predefined;
|
||||||
|
int32_t m_dsdtNodeCount;
|
||||||
|
|
||||||
ExecContext* m_ctxPool; // MaxCallDepth + 1 entries, heap
|
ExecContext* m_ctxPool; // MaxCallDepth + 1 entries, heap
|
||||||
int m_ctxInUse;
|
int m_ctxInUse;
|
||||||
int m_evalNesting; // top-level evaluations in progress
|
int m_evalNesting; // top-level evaluations in progress
|
||||||
uint64_t m_stackFloor; // stack address the interpreter stops at
|
uint64_t m_stackFloor; // stack address the interpreter stops at
|
||||||
int m_traceBudget; // opcodes left to log, 0 = off
|
int m_traceBudget; // opcodes left to log, 0 = off
|
||||||
|
ExecContext* m_loadCtx; // table-load operand context
|
||||||
|
uint64_t m_delayedUs; // total Sleep/Stall time requested
|
||||||
|
int m_methodScan; // >0 while scanning a method body at load
|
||||||
kcp::Mutex m_evalLock;
|
kcp::Mutex m_evalLock;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -89,6 +89,10 @@ namespace Hal {
|
|||||||
s_transientNext = 0;
|
s_transientNext = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool IsPersistent(int32_t index) {
|
||||||
|
return index > 0 && index <= MaxPersistent;
|
||||||
|
}
|
||||||
|
|
||||||
static Entry* Lookup(int32_t index) {
|
static Entry* Lookup(int32_t index) {
|
||||||
if (index <= 0) return nullptr;
|
if (index <= 0) return nullptr;
|
||||||
int32_t i = index - 1;
|
int32_t i = index - 1;
|
||||||
@@ -293,6 +297,12 @@ namespace Hal {
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int32_t Namespace::FindChild(int32_t parentIndex, const char* seg) const {
|
||||||
|
char padded[MaxNameSegLen + 1];
|
||||||
|
PadSegment(seg, padded);
|
||||||
|
return FindChildByName(parentIndex, padded);
|
||||||
|
}
|
||||||
|
|
||||||
int32_t Namespace::CreateNode(const char* absolutePath) {
|
int32_t Namespace::CreateNode(const char* absolutePath) {
|
||||||
EnsureRoot();
|
EnsureRoot();
|
||||||
char segments[MaxPathDepth][MaxNameSegLen + 1];
|
char segments[MaxPathDepth][MaxNameSegLen + 1];
|
||||||
|
|||||||
@@ -31,6 +31,16 @@ namespace Hal {
|
|||||||
ThermalZone,
|
ThermalZone,
|
||||||
PowerResource,
|
PowerResource,
|
||||||
BufferField,
|
BufferField,
|
||||||
|
Event,
|
||||||
|
// A reference to a namespace node: what RefOf() yields, and what a
|
||||||
|
// name evaluates to when it names something that is not data (a
|
||||||
|
// device or power resource listed in a _PRx package, say).
|
||||||
|
Reference,
|
||||||
|
// A name inside a constant package, kept as written ("PG00",
|
||||||
|
// "\_SB.PCI0.PEG0") because its scope is only known to whoever
|
||||||
|
// reads the package. Interpreter::ResolveReference() turns it
|
||||||
|
// into a node.
|
||||||
|
NameRef,
|
||||||
};
|
};
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
@@ -63,7 +73,7 @@ namespace Hal {
|
|||||||
static constexpr int InitialChildren = 8;
|
static constexpr int InitialChildren = 8;
|
||||||
static constexpr int MaxStringLen = 64;
|
static constexpr int MaxStringLen = 64;
|
||||||
static constexpr int MaxBufferLen = 256;
|
static constexpr int MaxBufferLen = 256;
|
||||||
static constexpr int MaxPackageElements = 32;
|
static constexpr int MaxPackageElements = 64;
|
||||||
static constexpr int MaxMethodArgs = 7;
|
static constexpr int MaxMethodArgs = 7;
|
||||||
static constexpr int MaxMethodLocals = 8;
|
static constexpr int MaxMethodLocals = 8;
|
||||||
|
|
||||||
@@ -102,10 +112,23 @@ namespace Hal {
|
|||||||
uint32_t Length;
|
uint32_t Length;
|
||||||
} Buffer;
|
} Buffer;
|
||||||
|
|
||||||
|
// A package's constant form: its element bytes, left in the
|
||||||
|
// table they were declared in, and the declared element count. ACPI tables stay
|
||||||
|
// mapped for the life of the kernel, so pointing at them is
|
||||||
|
// both cheaper and free of the size cap a copy would carry --
|
||||||
|
// a GPIO community table runs well past MaxBufferLen.
|
||||||
|
struct {
|
||||||
|
const uint8_t* Data; // first element
|
||||||
|
uint32_t Length; // bytes of element encodings
|
||||||
|
uint32_t Count; // NumElements
|
||||||
|
} Package;
|
||||||
|
|
||||||
struct {
|
struct {
|
||||||
uint8_t ArgCount; // bits 0-2 of method flags
|
uint8_t ArgCount; // bits 0-2 of method flags
|
||||||
bool Serialized; // bit 3
|
bool Serialized; // bit 3
|
||||||
uint32_t AmlOffset; // offset into DSDT AML where the method body starts
|
uint8_t Native; // NativeMethod; 0 = AML
|
||||||
|
const uint8_t* Table; // the table the body lives in
|
||||||
|
uint32_t AmlOffset; // offset into Table where the method body starts
|
||||||
uint32_t AmlLength; // length of the method body
|
uint32_t AmlLength; // length of the method body
|
||||||
} Method;
|
} Method;
|
||||||
|
|
||||||
@@ -116,12 +139,33 @@ namespace Hal {
|
|||||||
} Region;
|
} Region;
|
||||||
|
|
||||||
struct {
|
struct {
|
||||||
uint32_t RegionNodeIndex; // index of the parent OperationRegion node
|
// Field / BankField: the OperationRegion node.
|
||||||
|
// IndexField: the index register field.
|
||||||
|
uint32_t RegionNodeIndex;
|
||||||
uint32_t BitOffset;
|
uint32_t BitOffset;
|
||||||
uint32_t BitLength;
|
uint32_t BitLength;
|
||||||
uint8_t AccessType; // 0=Any, 1=Byte, 2=Word, 3=DWord, 4=QWord, 5=Buffer
|
uint8_t AccessType; // 0=Any, 1=Byte, 2=Word, 3=DWord, 4=QWord, 5=Buffer
|
||||||
|
uint8_t UpdateRule; // 0=Preserve, 1=WriteAsOnes, 2=WriteAsZeros
|
||||||
|
uint8_t Kind; // FieldKind
|
||||||
|
int32_t DataNodeIndex; // IndexField: the data register field
|
||||||
|
int32_t BankNodeIndex; // BankField: the bank select field
|
||||||
|
uint64_t BankValue; // BankField: value selecting this bank
|
||||||
} Field;
|
} Field;
|
||||||
|
|
||||||
|
// CreateXxxField over a buffer. Source points at the object
|
||||||
|
// holding the buffer -- a namespace node's, or a local/arg slot
|
||||||
|
// of the method that created the field -- so reads and writes
|
||||||
|
// go through to it.
|
||||||
|
struct {
|
||||||
|
Object* Source;
|
||||||
|
uint32_t BitOffset;
|
||||||
|
uint32_t BitLength;
|
||||||
|
} BufferField;
|
||||||
|
|
||||||
|
struct {
|
||||||
|
int32_t Node;
|
||||||
|
} Reference;
|
||||||
|
|
||||||
struct {
|
struct {
|
||||||
uint8_t ProcId;
|
uint8_t ProcId;
|
||||||
uint32_t PblkAddr;
|
uint32_t PblkAddr;
|
||||||
@@ -132,6 +176,18 @@ namespace Hal {
|
|||||||
Object() : Type(ObjectType::None), PkgIndex(-1), Integer(0) {}
|
Object() : Type(ObjectType::None), PkgIndex(-1), Integer(0) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum class FieldKind : uint8_t {
|
||||||
|
Normal = 0,
|
||||||
|
Index = 1,
|
||||||
|
Bank = 2,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Methods the OS provides rather than the firmware.
|
||||||
|
enum class NativeMethod : uint8_t {
|
||||||
|
None = 0,
|
||||||
|
Osi = 1, // \_OSI
|
||||||
|
};
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
// Package element store
|
// Package element store
|
||||||
@@ -155,6 +211,9 @@ namespace Hal {
|
|||||||
// Rewind the transient ring. Called once per top-level evaluation.
|
// Rewind the transient ring. Called once per top-level evaluation.
|
||||||
void ResetTransient();
|
void ResetTransient();
|
||||||
|
|
||||||
|
// True for an entry that outlives the evaluation that made it.
|
||||||
|
bool IsPersistent(int32_t index);
|
||||||
|
|
||||||
// Element access. Returns nullptr for an unknown entry or an
|
// Element access. Returns nullptr for an unknown entry or an
|
||||||
// out-of-range element.
|
// out-of-range element.
|
||||||
Object* Element(int32_t index, int element);
|
Object* Element(int32_t index, int element);
|
||||||
@@ -219,6 +278,10 @@ namespace Hal {
|
|||||||
// rebased onto a scope. Returns -1 if no scope contains it.
|
// rebased onto a scope. Returns -1 if no scope contains it.
|
||||||
int32_t SearchUpward(const char* relative, int32_t scopeNodeIndex) const;
|
int32_t SearchUpward(const char* relative, int32_t scopeNodeIndex) const;
|
||||||
|
|
||||||
|
// Find the child of `parent` named `seg`; a short name is padded
|
||||||
|
// with '_' the way node names are stored. Returns -1 if absent.
|
||||||
|
int32_t FindChild(int32_t parentIndex, const char* seg) const;
|
||||||
|
|
||||||
// Get a node by index.
|
// Get a node by index.
|
||||||
NamespaceNode* GetNode(int32_t index);
|
NamespaceNode* GetNode(int32_t index);
|
||||||
const NamespaceNode* GetNode(int32_t index) const;
|
const NamespaceNode* GetNode(int32_t index) const;
|
||||||
|
|||||||
@@ -9,6 +9,8 @@
|
|||||||
#include <ACPI/ACPI.hpp>
|
#include <ACPI/ACPI.hpp>
|
||||||
#include <Terminal/Terminal.hpp>
|
#include <Terminal/Terminal.hpp>
|
||||||
#include <CppLib/Stream.hpp>
|
#include <CppLib/Stream.hpp>
|
||||||
|
#include <Memory/HHDM.hpp>
|
||||||
|
#include <Memory/Heap.hpp>
|
||||||
|
|
||||||
using namespace Kt;
|
using namespace Kt;
|
||||||
|
|
||||||
@@ -239,15 +241,144 @@ namespace Hal {
|
|||||||
// Full interpreter initialization
|
// Full interpreter initialization
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
void InitializeInterpreter(void* dsdtData) {
|
void InitializeInterpreter(void* dsdtData, ACPI::CommonSDTHeader* xsdt) {
|
||||||
auto& interp = GetInterpreter();
|
auto& interp = GetInterpreter();
|
||||||
if (!interp.LoadTable(dsdtData)) {
|
if (!interp.LoadTable(dsdtData)) {
|
||||||
KernelLogStream(ERROR, "AML") << "Failed to load DSDT into AML interpreter";
|
KernelLogStream(ERROR, "AML") << "Failed to load DSDT into AML interpreter";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SSDTs extend the DSDT's namespace and are loaded after it, in
|
||||||
|
// XSDT order. They are where most laptops keep CPU power tables,
|
||||||
|
// and -- the reason they matter here -- the discrete GPU's power
|
||||||
|
// control (_PR3 power resources, _PS3, NVIDIA/AMD _DSM).
|
||||||
|
int ssdts = 0;
|
||||||
|
if (xsdt) {
|
||||||
|
uint32_t entryCount = (xsdt->Length - sizeof(ACPI::CommonSDTHeader)) / 8;
|
||||||
|
auto* entries = (uint64_t*)((uint64_t)xsdt + sizeof(ACPI::CommonSDTHeader));
|
||||||
|
for (uint32_t i = 0; i < entryCount; i++) {
|
||||||
|
if (entries[i] == 0) continue;
|
||||||
|
auto* table = (ACPI::CommonSDTHeader*)Memory::HHDM(entries[i]);
|
||||||
|
const char* sig = table->Signature;
|
||||||
|
bool isSsdt = sig[0] == 'S' && sig[1] == 'S' && sig[2] == 'D' && sig[3] == 'T';
|
||||||
|
bool isPsdt = sig[0] == 'P' && sig[1] == 'S' && sig[2] == 'D' && sig[3] == 'T';
|
||||||
|
if (!isSsdt && !isPsdt) continue;
|
||||||
|
if (table->Length <= sizeof(ACPI::CommonSDTHeader)) continue;
|
||||||
|
if (interp.LoadTable(table)) ssdts++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
KernelLogStream(OK, "AML") << "Loaded DSDT + " << base::dec << (uint64_t)ssdts
|
||||||
|
<< " SSDT(s)";
|
||||||
|
|
||||||
auto& ns = interp.GetNamespace();
|
auto& ns = interp.GetNamespace();
|
||||||
KernelLogStream(OK, "AML") << "Namespace: " << base::dec
|
KernelLogStream(OK, "AML") << "Namespace: " << base::dec
|
||||||
<< (uint64_t)ns.NodeCount() << " / " << (uint64_t)ns.MaxCapacity() << " nodes (dynamic)";
|
<< (uint64_t)ns.NodeCount() << " / " << (uint64_t)ns.MaxCapacity() << " nodes (dynamic)";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
// Namespace initialization (_REG, _INI)
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
static bool SegIs(const char* name, const char* seg) {
|
||||||
|
return name[0] == seg[0] && name[1] == seg[1] &&
|
||||||
|
name[2] == seg[2] && name[3] == seg[3];
|
||||||
|
}
|
||||||
|
|
||||||
|
void InitializeNamespaceObjects() {
|
||||||
|
auto& interp = GetInterpreter();
|
||||||
|
if (!interp.IsInitialized()) return;
|
||||||
|
auto& ns = interp.GetNamespace();
|
||||||
|
|
||||||
|
// Collect first, evaluate second: executing AML creates nodes,
|
||||||
|
// which reallocates a parent's child array under a live walk.
|
||||||
|
int32_t capacity = ns.NodeCount();
|
||||||
|
auto* regs = (int32_t*)Memory::g_heap->Request(sizeof(int32_t) * capacity);
|
||||||
|
auto* devices = (int32_t*)Memory::g_heap->Request(sizeof(int32_t) * capacity);
|
||||||
|
if (!regs || !devices) {
|
||||||
|
if (regs) Memory::g_heap->Free(regs);
|
||||||
|
if (devices) Memory::g_heap->Free(devices);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int regCount = 0, deviceCount = 0;
|
||||||
|
|
||||||
|
ns.WalkDescendants(ns.RootIndex(), ObjectType::None,
|
||||||
|
[&](int32_t idx, const NamespaceNode* node) {
|
||||||
|
if (node->Obj.Type == ObjectType::Method && SegIs(node->Name, "_REG") &&
|
||||||
|
regCount < capacity)
|
||||||
|
regs[regCount++] = idx;
|
||||||
|
if (node->Obj.Type == ObjectType::Device && deviceCount < capacity)
|
||||||
|
devices[deviceCount++] = idx;
|
||||||
|
});
|
||||||
|
|
||||||
|
// _REG(PCI_Config, connect). A _REG for another space checks Arg0
|
||||||
|
// and ignores the call, as with the EC's.
|
||||||
|
Object args[2]{};
|
||||||
|
args[0].Type = ObjectType::Integer;
|
||||||
|
args[0].Integer = (uint64_t)RegionSpace::PciConfig;
|
||||||
|
args[1].Type = ObjectType::Integer;
|
||||||
|
args[1].Integer = 1;
|
||||||
|
for (int i = 0; i < regCount; i++) {
|
||||||
|
Object result{};
|
||||||
|
interp.EvaluateNode(regs[i], args, 2, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
// \_SB._INI runs before any device's.
|
||||||
|
int32_t sb = ns.FindNode("\\_SB_");
|
||||||
|
if (sb >= 0) {
|
||||||
|
Object result{};
|
||||||
|
if (interp.EvaluateChild(sb, "_INI", nullptr, 0, result))
|
||||||
|
KernelLogStream(INFO, "AML") << "Ran \\_SB._INI";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Then each device, parents first (WalkDescendants is pre-order).
|
||||||
|
// A device that is neither present nor functional takes its whole
|
||||||
|
// subtree with it; one that is functional but absent skips only
|
||||||
|
// its own _INI.
|
||||||
|
static constexpr int MaxSkipped = 256;
|
||||||
|
int32_t skipped[MaxSkipped];
|
||||||
|
int skippedCount = 0;
|
||||||
|
int ran = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < deviceCount; i++) {
|
||||||
|
int32_t dev = devices[i];
|
||||||
|
|
||||||
|
bool underSkipped = false;
|
||||||
|
for (int32_t p = ns.GetNode(dev) ? ns.GetNode(dev)->ParentIndex : -1;
|
||||||
|
p > 0 && !underSkipped; p = ns.GetNode(p) ? ns.GetNode(p)->ParentIndex : -1) {
|
||||||
|
for (int k = 0; k < skippedCount; k++)
|
||||||
|
if (skipped[k] == p) { underSkipped = true; break; }
|
||||||
|
}
|
||||||
|
if (underSkipped) continue;
|
||||||
|
|
||||||
|
uint64_t sta = 0x0F;
|
||||||
|
Object staObj{};
|
||||||
|
if (interp.EvaluateChild(dev, "_STA", nullptr, 0, staObj) &&
|
||||||
|
staObj.Type == ObjectType::Integer)
|
||||||
|
sta = staObj.Integer;
|
||||||
|
|
||||||
|
if (!(sta & 0x01)) {
|
||||||
|
if (!(sta & 0x08) && skippedCount < MaxSkipped)
|
||||||
|
skipped[skippedCount++] = dev;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (interp.FindChild(dev, "_INI") < 0) continue;
|
||||||
|
Object result{};
|
||||||
|
bool ok = interp.EvaluateChild(dev, "_INI", nullptr, 0, result);
|
||||||
|
ran++;
|
||||||
|
|
||||||
|
char path[128];
|
||||||
|
ns.GetNodePath(dev, path, sizeof(path));
|
||||||
|
KernelLogStream(DEBUG, "AML") << " _INI " << path << (ok ? "" : " FAILED");
|
||||||
|
}
|
||||||
|
|
||||||
|
KernelLogStream(OK, "AML") << "Namespace initialized: " << base::dec
|
||||||
|
<< (uint64_t)regCount << " _REG, " << (uint64_t)ran << " device _INI";
|
||||||
|
|
||||||
|
Memory::g_heap->Free(regs);
|
||||||
|
Memory::g_heap->Free(devices);
|
||||||
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@@ -5,6 +5,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
#include <ACPI/ACPI.hpp>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
|
||||||
namespace Hal {
|
namespace Hal {
|
||||||
@@ -27,11 +28,20 @@ namespace Hal {
|
|||||||
// interpreter or namespace.
|
// interpreter or namespace.
|
||||||
SleepObject FindSleepState(void* dsdtData, int state);
|
SleepObject FindSleepState(void* dsdtData, int state);
|
||||||
|
|
||||||
// Initialize the AML interpreter with the DSDT.
|
// Initialize the AML interpreter with the DSDT, then every SSDT the
|
||||||
// This loads the full table into the namespace and enables
|
// XSDT lists (xsdt may be null to load the DSDT alone).
|
||||||
|
// This loads the full tables into the namespace and enables
|
||||||
// method evaluation, device enumeration, and field access.
|
// method evaluation, device enumeration, and field access.
|
||||||
// Should be called during boot after ACPI table discovery.
|
// Should be called during boot after ACPI table discovery.
|
||||||
void InitializeInterpreter(void* dsdtData);
|
void InitializeInterpreter(void* dsdtData, ACPI::CommonSDTHeader* xsdt);
|
||||||
|
|
||||||
|
// Run the namespace initialization the OS owes the firmware once its
|
||||||
|
// address-space handlers are up: _REG for PCI_Config, then \_SB._INI
|
||||||
|
// and every present device's _INI, parents first. Firmware works out
|
||||||
|
// which OS it is running under (OSYS, from _OSI) in these, and gates
|
||||||
|
// much of its later behaviour -- device power control included -- on
|
||||||
|
// the answer. Call once the Embedded Controller and PCI are up.
|
||||||
|
void InitializeNamespaceObjects();
|
||||||
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@@ -453,15 +453,14 @@ namespace Hal {
|
|||||||
auto* nsNode = ns.GetNode(node);
|
auto* nsNode = ns.GetNode(node);
|
||||||
if (!nsNode) return result;
|
if (!nsNode) return result;
|
||||||
|
|
||||||
if (nsNode->Obj.Type == AML::ObjectType::Package) {
|
if (nsNode->Obj.Type == AML::ObjectType::Package && nsNode->Obj.Package.Data) {
|
||||||
// Parse the raw package buffer to extract SLP_TYP values
|
// Parse the raw package elements to extract SLP_TYP values
|
||||||
const uint8_t* data = nsNode->Obj.Buffer.Data;
|
const uint8_t* data = nsNode->Obj.Package.Data;
|
||||||
uint32_t len = nsNode->Obj.Buffer.Length;
|
uint32_t len = nsNode->Obj.Package.Length;
|
||||||
if (len < 2) return result;
|
if (len < 1) return result;
|
||||||
|
|
||||||
// NumElements
|
uint32_t numElements = nsNode->Obj.Package.Count;
|
||||||
uint8_t numElements = data[0];
|
uint32_t pos = 0;
|
||||||
uint32_t pos = 1;
|
|
||||||
|
|
||||||
if (numElements >= 1 && pos < len) {
|
if (numElements >= 1 && pos < len) {
|
||||||
// Decode first element
|
// Decode first element
|
||||||
|
|||||||
@@ -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() {
|
||||||
|
|||||||
@@ -0,0 +1,336 @@
|
|||||||
|
/*
|
||||||
|
* AcpiPower.cpp
|
||||||
|
* ACPI device power management: _PSx methods, _PRx power resources,
|
||||||
|
* _DSM calls, and finding the namespace node behind a PCI function
|
||||||
|
* Copyright (c) 2026 Daniel Hammer
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "AcpiPower.hpp"
|
||||||
|
#include <ACPI/AML/AmlInterpreter.hpp>
|
||||||
|
#include <Memory/Heap.hpp>
|
||||||
|
#include <Libraries/Memory.hpp>
|
||||||
|
#include <Terminal/Terminal.hpp>
|
||||||
|
#include <CppLib/Stream.hpp>
|
||||||
|
|
||||||
|
using namespace Kt;
|
||||||
|
|
||||||
|
namespace Hal {
|
||||||
|
namespace AcpiPower {
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
// PCI <-> ACPI
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
struct PciMapEntry {
|
||||||
|
int32_t Node;
|
||||||
|
uint8_t Bus, Dev, Fn;
|
||||||
|
uint8_t Score;
|
||||||
|
};
|
||||||
|
|
||||||
|
static PciMapEntry* g_map = nullptr;
|
||||||
|
static int g_mapCount = 0;
|
||||||
|
static bool g_mapBuilt = false;
|
||||||
|
|
||||||
|
bool HasObject(int32_t device, const char* name) {
|
||||||
|
return AML::GetInterpreter().FindChild(device, name) >= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// How much power management a node carries. Picks the real node out
|
||||||
|
// of duplicates at one address.
|
||||||
|
static uint8_t PowerScore(int32_t node) {
|
||||||
|
uint8_t score = 0;
|
||||||
|
if (HasObject(node, "_PR0") || HasObject(node, "_PR3")) score += 8;
|
||||||
|
if (HasObject(node, "_PS3")) score += 4;
|
||||||
|
if (HasObject(node, "_DSM")) score += 2;
|
||||||
|
if (HasObject(node, "_OFF") || HasObject(node, "_PS0")) score += 1;
|
||||||
|
return score;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void BuildPciMap() {
|
||||||
|
auto& interp = AML::GetInterpreter();
|
||||||
|
auto& ns = interp.GetNamespace();
|
||||||
|
g_mapBuilt = true;
|
||||||
|
g_mapCount = 0;
|
||||||
|
if (!interp.IsInitialized()) return;
|
||||||
|
|
||||||
|
// Device nodes with an _ADR, collected before anything is
|
||||||
|
// evaluated (evaluation may add nodes).
|
||||||
|
int32_t capacity = ns.NodeCount();
|
||||||
|
auto* nodes = (int32_t*)Memory::g_heap->Request(sizeof(int32_t) * capacity);
|
||||||
|
if (!nodes) return;
|
||||||
|
int count = 0;
|
||||||
|
int32_t sb = ns.FindNode("\\_SB_");
|
||||||
|
if (sb < 0) sb = ns.RootIndex();
|
||||||
|
ns.WalkDescendants(sb, AML::ObjectType::Device,
|
||||||
|
[&](int32_t idx, const AML::NamespaceNode*) {
|
||||||
|
if (count < capacity && ns.FindChild(idx, "_ADR") >= 0)
|
||||||
|
nodes[count++] = idx;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (g_map) Memory::g_heap->Free(g_map);
|
||||||
|
g_map = (PciMapEntry*)Memory::g_heap->Request(sizeof(PciMapEntry) * (count ? count : 1));
|
||||||
|
if (!g_map) { Memory::g_heap->Free(nodes); return; }
|
||||||
|
|
||||||
|
for (int i = 0; i < count; i++) {
|
||||||
|
uint8_t bus, dev, fn;
|
||||||
|
if (!interp.GetPciAddress(nodes[i], bus, dev, fn)) continue;
|
||||||
|
g_map[g_mapCount++] = { nodes[i], bus, dev, fn, PowerScore(nodes[i]) };
|
||||||
|
}
|
||||||
|
|
||||||
|
Memory::g_heap->Free(nodes);
|
||||||
|
}
|
||||||
|
|
||||||
|
void InvalidatePciMap() {
|
||||||
|
g_mapBuilt = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t FindPciDevice(uint8_t bus, uint8_t dev, uint8_t fn) {
|
||||||
|
if (!g_mapBuilt) BuildPciMap();
|
||||||
|
|
||||||
|
int32_t best = -1;
|
||||||
|
int bestScore = -1;
|
||||||
|
for (int i = 0; i < g_mapCount; i++) {
|
||||||
|
const auto& e = g_map[i];
|
||||||
|
if (e.Bus != bus || e.Dev != dev || e.Fn != fn) continue;
|
||||||
|
if (e.Score > bestScore) {
|
||||||
|
best = e.Node;
|
||||||
|
bestScore = e.Score;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return best;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
// Power resources
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
static int AddUnique(int32_t* out, int count, int max, int32_t node) {
|
||||||
|
if (node < 0) return count;
|
||||||
|
for (int i = 0; i < count; i++)
|
||||||
|
if (out[i] == node) return count;
|
||||||
|
if (count < max) out[count++] = node;
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The resources one _PRx object lists. The list is copied out before
|
||||||
|
// anything else is evaluated: a computed package lives in storage the
|
||||||
|
// next evaluation reuses.
|
||||||
|
static int CollectList(int32_t device, const char* name, int32_t* out, int count, int max) {
|
||||||
|
auto& interp = AML::GetInterpreter();
|
||||||
|
auto& ns = interp.GetNamespace();
|
||||||
|
|
||||||
|
AML::Object pkg{};
|
||||||
|
if (!interp.EvaluateChild(device, name, nullptr, 0, pkg)) return count;
|
||||||
|
if (pkg.Type != AML::ObjectType::Package) return count;
|
||||||
|
|
||||||
|
int n = interp.GetPackageCount(pkg);
|
||||||
|
int32_t found[MaxResources];
|
||||||
|
int foundCount = 0;
|
||||||
|
for (int i = 0; i < n && foundCount < MaxResources; i++) {
|
||||||
|
AML::Object elem{};
|
||||||
|
if (!interp.GetPackageElement(pkg, i, elem)) break;
|
||||||
|
int32_t node = interp.ResolveReference(elem, device);
|
||||||
|
auto* nn = ns.GetNode(node);
|
||||||
|
if (nn && nn->Obj.Type == AML::ObjectType::PowerResource)
|
||||||
|
found[foundCount++] = node;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < foundCount; i++)
|
||||||
|
count = AddUnique(out, count, max, found[i]);
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
int CollectResources(int32_t device, int32_t* out, int max) {
|
||||||
|
int count = 0;
|
||||||
|
count = CollectList(device, "_PR0", out, count, max);
|
||||||
|
count = CollectList(device, "_PR1", out, count, max);
|
||||||
|
count = CollectList(device, "_PR2", out, count, max);
|
||||||
|
count = CollectList(device, "_PR3", out, count, max);
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ResourceState(int32_t resource) {
|
||||||
|
AML::Object sta{};
|
||||||
|
if (!AML::GetInterpreter().EvaluateChild(resource, "_STA", nullptr, 0, sta))
|
||||||
|
return -1;
|
||||||
|
return (ResultInteger(sta) & 1) ? 1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Does a present device outside `owners` list this resource for D0?
|
||||||
|
// Turning off a rail another device is running on is how a GPU
|
||||||
|
// power-down would take out, say, an NVMe drive sharing a slot rail.
|
||||||
|
static bool NeededElsewhere(int32_t resource, const int32_t* owners, int ownerCount) {
|
||||||
|
auto& interp = AML::GetInterpreter();
|
||||||
|
auto& ns = interp.GetNamespace();
|
||||||
|
|
||||||
|
int32_t capacity = ns.NodeCount();
|
||||||
|
auto* users = (int32_t*)Memory::g_heap->Request(sizeof(int32_t) * capacity);
|
||||||
|
if (!users) return true; // cannot tell: assume shared
|
||||||
|
int count = 0;
|
||||||
|
ns.WalkDescendants(ns.RootIndex(), AML::ObjectType::Device,
|
||||||
|
[&](int32_t idx, const AML::NamespaceNode*) {
|
||||||
|
if (count < capacity && ns.FindChild(idx, "_PR0") >= 0)
|
||||||
|
users[count++] = idx;
|
||||||
|
});
|
||||||
|
|
||||||
|
bool shared = false;
|
||||||
|
for (int i = 0; i < count && !shared; i++) {
|
||||||
|
int32_t dev = users[i];
|
||||||
|
|
||||||
|
// Everything at or below an owner goes down with it: the
|
||||||
|
// card's own child nodes, a duplicate placeholder node at the
|
||||||
|
// same address, a function the firmware hid from PCI.
|
||||||
|
bool own = false;
|
||||||
|
for (int32_t a = dev; a > 0 && !own; ) {
|
||||||
|
for (int k = 0; k < ownerCount; k++)
|
||||||
|
if (owners[k] == a) { own = true; break; }
|
||||||
|
auto* an = ns.GetNode(a);
|
||||||
|
a = an ? an->ParentIndex : -1;
|
||||||
|
}
|
||||||
|
if (own) continue;
|
||||||
|
|
||||||
|
int32_t list[MaxResources];
|
||||||
|
int n = CollectList(dev, "_PR0", list, 0, MaxResources);
|
||||||
|
bool listed = false;
|
||||||
|
for (int k = 0; k < n; k++)
|
||||||
|
if (list[k] == resource) { listed = true; break; }
|
||||||
|
if (!listed) continue;
|
||||||
|
|
||||||
|
// Only a device that is actually there counts.
|
||||||
|
AML::Object sta{};
|
||||||
|
uint64_t status = 0x0F;
|
||||||
|
if (interp.EvaluateChild(dev, "_STA", nullptr, 0, sta))
|
||||||
|
status = ResultInteger(sta);
|
||||||
|
if (status & 0x01) {
|
||||||
|
char path[128];
|
||||||
|
ns.GetNodePath(dev, path, sizeof(path));
|
||||||
|
KernelLogStream(INFO, "AcpiPower") << " power resource also used by "
|
||||||
|
<< path << "; leaving it on";
|
||||||
|
shared = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Memory::g_heap->Free(users);
|
||||||
|
return shared;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
// Device power transitions
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
void EnterD3Cold(int32_t device, const int32_t* owners, int ownerCount,
|
||||||
|
D3ColdReport& report) {
|
||||||
|
auto& interp = AML::GetInterpreter();
|
||||||
|
auto& ns = interp.GetNamespace();
|
||||||
|
report = D3ColdReport{};
|
||||||
|
if (device < 0) return;
|
||||||
|
|
||||||
|
char path[128];
|
||||||
|
ns.GetNodePath(device, path, sizeof(path));
|
||||||
|
|
||||||
|
// Gather the resources first: _PS3 may change what _PRx reports.
|
||||||
|
int32_t resources[MaxResources];
|
||||||
|
int count = CollectResources(device, resources, MaxResources);
|
||||||
|
|
||||||
|
if (HasObject(device, "_PS3")) {
|
||||||
|
AML::Object result{};
|
||||||
|
bool ok = interp.EvaluateChild(device, "_PS3", nullptr, 0, result);
|
||||||
|
report.RanPs3 = true;
|
||||||
|
KernelLogStream(INFO, "AcpiPower") << " " << path << "._PS3"
|
||||||
|
<< (ok ? "" : " (failed)");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Off in reverse order of listing (ACPI turns resources off in
|
||||||
|
// reverse ResourceOrder; firmware lists them in that order).
|
||||||
|
for (int i = count - 1; i >= 0; i--) {
|
||||||
|
int32_t res = resources[i];
|
||||||
|
char rpath[128];
|
||||||
|
ns.GetNodePath(res, rpath, sizeof(rpath));
|
||||||
|
|
||||||
|
if (ResourceState(res) == 0) {
|
||||||
|
KernelLogStream(INFO, "AcpiPower") << " " << rpath << " already off";
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (NeededElsewhere(res, owners, ownerCount)) {
|
||||||
|
report.ResourcesShared++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
AML::Object result{};
|
||||||
|
bool ok = interp.EvaluateChild(res, "_OFF", nullptr, 0, result);
|
||||||
|
int after = ResourceState(res);
|
||||||
|
if (ok) report.ResourcesOff++;
|
||||||
|
if (after == 1) report.ResourcesStillOn++;
|
||||||
|
KernelLogStream(INFO, "AcpiPower") << " " << rpath << "._OFF"
|
||||||
|
<< (ok ? "" : " (failed)")
|
||||||
|
<< (after == 0 ? ", now off" : after == 1 ? ", still on" : "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
// _DSM
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
AML::Object IntegerArg(uint64_t value) {
|
||||||
|
AML::Object o{};
|
||||||
|
o.Type = AML::ObjectType::Integer;
|
||||||
|
o.Integer = value;
|
||||||
|
return o;
|
||||||
|
}
|
||||||
|
|
||||||
|
AML::Object BufferArg(const uint8_t* bytes, uint32_t length) {
|
||||||
|
AML::Object o{};
|
||||||
|
o.Type = AML::ObjectType::Buffer;
|
||||||
|
if (length > AML::MaxBufferLen) length = AML::MaxBufferLen;
|
||||||
|
memcpy(o.Buffer.Data, bytes, length);
|
||||||
|
o.Buffer.Length = length;
|
||||||
|
return o;
|
||||||
|
}
|
||||||
|
|
||||||
|
AML::Object EmptyPackageArg() {
|
||||||
|
AML::Object o{};
|
||||||
|
o.Type = AML::ObjectType::Package;
|
||||||
|
o.Package.Data = nullptr;
|
||||||
|
o.Package.Length = 0;
|
||||||
|
o.Package.Count = 0;
|
||||||
|
return o;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t ResultInteger(const AML::Object& result) {
|
||||||
|
if (result.Type == AML::ObjectType::Integer) return result.Integer;
|
||||||
|
if (result.Type == AML::ObjectType::Buffer) {
|
||||||
|
uint64_t v = 0;
|
||||||
|
for (uint32_t i = 0; i < result.Buffer.Length && i < 8; i++)
|
||||||
|
v |= (uint64_t)result.Buffer.Data[i] << (8 * i);
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool EvaluateDsm(int32_t device, const uint8_t uuid[16], uint64_t revision,
|
||||||
|
uint64_t function, const AML::Object& arg3, AML::Object& result) {
|
||||||
|
auto& interp = AML::GetInterpreter();
|
||||||
|
if (device < 0 || interp.FindChild(device, "_DSM") < 0) return false;
|
||||||
|
|
||||||
|
AML::Object args[4];
|
||||||
|
args[0] = BufferArg(uuid, 16);
|
||||||
|
args[1] = IntegerArg(revision);
|
||||||
|
args[2] = IntegerArg(function);
|
||||||
|
args[3] = arg3;
|
||||||
|
return interp.EvaluateChild(device, "_DSM", args, 4, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t DsmFunctions(int32_t device, const uint8_t uuid[16], uint64_t revision,
|
||||||
|
const AML::Object& arg3) {
|
||||||
|
AML::Object result{};
|
||||||
|
if (!EvaluateDsm(device, uuid, revision, 0, arg3, result)) return 0;
|
||||||
|
uint64_t mask = ResultInteger(result);
|
||||||
|
// Bit 0 clear means "no functions for this UUID/revision".
|
||||||
|
return (mask & 1) ? mask : 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
/*
|
||||||
|
* AcpiPower.hpp
|
||||||
|
* ACPI device power management: _PSx methods, _PRx power resources,
|
||||||
|
* _DSM calls, and finding the namespace node behind a PCI function
|
||||||
|
* Copyright (c) 2026 Daniel Hammer
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include <cstdint>
|
||||||
|
#include <ACPI/AML/AmlNamespace.hpp>
|
||||||
|
|
||||||
|
namespace Hal {
|
||||||
|
namespace AcpiPower {
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
// PCI <-> ACPI
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// The Device node describing PCI function bus:dev.fn, or -1. When
|
||||||
|
// firmware declares more than one node at the same address (a generic
|
||||||
|
// placeholder plus a vendor SSDT's real one is common for GPUs), the
|
||||||
|
// one carrying power-management objects wins.
|
||||||
|
int32_t FindPciDevice(uint8_t bus, uint8_t dev, uint8_t fn);
|
||||||
|
|
||||||
|
// Forget the PCI address map built by FindPciDevice (bus numbers may
|
||||||
|
// change across a resume).
|
||||||
|
void InvalidatePciMap();
|
||||||
|
|
||||||
|
// True when `device` has a child object named `name` ("_PS3").
|
||||||
|
bool HasObject(int32_t device, const char* name);
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
// Power resources
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
static constexpr int MaxResources = 8;
|
||||||
|
|
||||||
|
// The power resources the device lists in _PR0.._PR3 (deduplicated).
|
||||||
|
int CollectResources(int32_t device, int32_t* out, int max);
|
||||||
|
|
||||||
|
// _STA of a power resource: 1 on, 0 off, -1 unknown.
|
||||||
|
int ResourceState(int32_t resource);
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
// Device power transitions
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
struct D3ColdReport {
|
||||||
|
bool RanPs3;
|
||||||
|
int ResourcesOff; // resources whose _OFF was run
|
||||||
|
int ResourcesShared; // left on: some other present device needs them
|
||||||
|
int ResourcesStillOn; // _OFF ran but _STA still says on
|
||||||
|
};
|
||||||
|
|
||||||
|
// Put an ACPI device into D3cold the way the spec orders it: _PS3,
|
||||||
|
// then every power resource it depends on off, last-listed first. A
|
||||||
|
// resource that a device outside `owners` also depends on is left
|
||||||
|
// on -- `owners` lists the nodes being powered down together (the
|
||||||
|
// functions of one card and the port above them), which may freely
|
||||||
|
// share, as may anything below them in the namespace. The PCI side
|
||||||
|
// (PMCSR to D3hot) is the caller's to do first.
|
||||||
|
void EnterD3Cold(int32_t device, const int32_t* owners, int ownerCount,
|
||||||
|
D3ColdReport& report);
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
// _DSM
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Evaluate _DSM(uuid, revision, function, arg3) on a device. False
|
||||||
|
// when the device has no _DSM or it failed.
|
||||||
|
bool EvaluateDsm(int32_t device, const uint8_t uuid[16], uint64_t revision,
|
||||||
|
uint64_t function, const AML::Object& arg3, AML::Object& result);
|
||||||
|
|
||||||
|
// The function bitmap _DSM function 0 reports for (uuid, revision),
|
||||||
|
// 0 when unsupported. Bit 0 set means "some functions exist".
|
||||||
|
uint64_t DsmFunctions(int32_t device, const uint8_t uuid[16], uint64_t revision,
|
||||||
|
const AML::Object& arg3);
|
||||||
|
|
||||||
|
// Helpers for building _DSM / method arguments.
|
||||||
|
AML::Object IntegerArg(uint64_t value);
|
||||||
|
AML::Object BufferArg(const uint8_t* bytes, uint32_t length);
|
||||||
|
AML::Object EmptyPackageArg();
|
||||||
|
|
||||||
|
// Integer value of a method result: an integer as is, a buffer read
|
||||||
|
// little-endian (up to 8 bytes).
|
||||||
|
uint64_t ResultInteger(const AML::Object& result);
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -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);
|
|
||||||
|
|
||||||
// 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,212 @@
|
|||||||
|
/*
|
||||||
|
* AcpiVideo.cpp
|
||||||
|
* ACPI video extensions (_BCL/_BCM/_BQC) panel brightness fallback
|
||||||
|
* Copyright (c) 2026 Daniel Hammer
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "AcpiVideo.hpp"
|
||||||
|
#include "AcpiPower.hpp"
|
||||||
|
#include <ACPI/AML/AmlInterpreter.hpp>
|
||||||
|
#include <Pci/Pci.hpp>
|
||||||
|
#include <CppLib/Spinlock.hpp>
|
||||||
|
#include <CppLib/Stream.hpp>
|
||||||
|
#include <Terminal/Terminal.hpp>
|
||||||
|
|
||||||
|
using namespace Kt;
|
||||||
|
|
||||||
|
namespace Hal {
|
||||||
|
namespace AcpiVideo {
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
// State
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
static constexpr int MaxLevels = 101;
|
||||||
|
|
||||||
|
static kcp::Mutex g_lock;
|
||||||
|
static bool g_probed = false;
|
||||||
|
static int32_t g_output = -1; // output device carrying _BCM
|
||||||
|
static bool g_hasBqc = false;
|
||||||
|
static uint32_t g_levels[MaxLevels]; // ascending, de-duplicated
|
||||||
|
static int g_levelCount = 0;
|
||||||
|
static int g_percent = -1; // last level set, when no _BQC
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
// Discovery
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
static bool HasMethod(int32_t node, const char* name) {
|
||||||
|
return AML::GetInterpreter().FindChild(node, name) >= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// _ADR display output IDs (ACPI 6.x, B.4.2): bits 11:8 are the type,
|
||||||
|
// 4 = internal flat panel. The panel is the output to drive.
|
||||||
|
static int32_t FindOutput() {
|
||||||
|
auto& interp = AML::GetInterpreter();
|
||||||
|
auto& ns = interp.GetNamespace();
|
||||||
|
|
||||||
|
int32_t fallback = -1;
|
||||||
|
auto& devices = Pci::GetDevices();
|
||||||
|
for (uint64_t i = 0; i < devices.size(); i++) {
|
||||||
|
const auto& d = devices[i];
|
||||||
|
if (d.ClassCode != 0x03) continue;
|
||||||
|
int32_t gpu = AcpiPower::FindPciDevice(d.Bus, d.Device, d.Function);
|
||||||
|
if (gpu < 0) continue;
|
||||||
|
|
||||||
|
int32_t panel = -1;
|
||||||
|
ns.ForEachChild(gpu, AML::ObjectType::Device,
|
||||||
|
[&](int32_t idx, const AML::NamespaceNode*) {
|
||||||
|
if (!HasMethod(idx, "_BCM") || !HasMethod(idx, "_BCL")) return true;
|
||||||
|
AML::Object adr{};
|
||||||
|
uint64_t id = interp.EvaluateChild(idx, "_ADR", nullptr, 0, adr)
|
||||||
|
? AcpiPower::ResultInteger(adr) : 0;
|
||||||
|
if (((id >> 8) & 0xF) == 0x4) {
|
||||||
|
panel = idx;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (fallback < 0) fallback = idx;
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
if (panel >= 0) return panel;
|
||||||
|
}
|
||||||
|
if (fallback >= 0) return fallback;
|
||||||
|
|
||||||
|
// Some firmware hangs the output device off a bridge the PCI scan
|
||||||
|
// could not map; any _BCM in the namespace is the only one there is.
|
||||||
|
ns.WalkDescendants(ns.RootIndex(), AML::ObjectType::Method,
|
||||||
|
[&](int32_t idx, const AML::NamespaceNode* n) {
|
||||||
|
if (fallback >= 0 || n->Name[0] != '_' || n->Name[1] != 'B'
|
||||||
|
|| n->Name[2] != 'C' || n->Name[3] != 'M')
|
||||||
|
return;
|
||||||
|
const AML::NamespaceNode* node = ns.GetNode(idx);
|
||||||
|
if (node && node->ParentIndex >= 0 && HasMethod(node->ParentIndex, "_BCL"))
|
||||||
|
fallback = node->ParentIndex;
|
||||||
|
});
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
// _BCL: { level on AC, level on battery, supported levels... }. Only
|
||||||
|
// the supported list counts; firmware often repeats or misorders it.
|
||||||
|
static bool ReadLevels() {
|
||||||
|
auto& interp = AML::GetInterpreter();
|
||||||
|
AML::Object bcl{};
|
||||||
|
if (!interp.EvaluateChild(g_output, "_BCL", nullptr, 0, bcl)
|
||||||
|
|| bcl.Type != AML::ObjectType::Package)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
g_levelCount = 0;
|
||||||
|
int count = interp.GetPackageCount(bcl);
|
||||||
|
for (int i = 2; i < count; i++) {
|
||||||
|
AML::Object elem{};
|
||||||
|
if (!interp.GetPackageElement(bcl, i, elem)
|
||||||
|
|| elem.Type != AML::ObjectType::Integer)
|
||||||
|
continue;
|
||||||
|
uint32_t level = (uint32_t)elem.Integer;
|
||||||
|
int pos = 0;
|
||||||
|
while (pos < g_levelCount && g_levels[pos] < level) pos++;
|
||||||
|
if (pos < g_levelCount && g_levels[pos] == level) continue;
|
||||||
|
if (g_levelCount == MaxLevels) break;
|
||||||
|
for (int j = g_levelCount; j > pos; j--) g_levels[j] = g_levels[j - 1];
|
||||||
|
g_levels[pos] = level;
|
||||||
|
g_levelCount++;
|
||||||
|
}
|
||||||
|
return g_levelCount >= 2 && g_levels[g_levelCount - 1] > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void Probe() {
|
||||||
|
g_probed = true;
|
||||||
|
auto& interp = AML::GetInterpreter();
|
||||||
|
if (!interp.IsInitialized()) return;
|
||||||
|
|
||||||
|
g_output = FindOutput();
|
||||||
|
if (g_output < 0) return;
|
||||||
|
if (!ReadLevels()) {
|
||||||
|
g_output = -1;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
g_hasBqc = HasMethod(g_output, "_BQC");
|
||||||
|
// Without _BQC the level is only known once we set one; until
|
||||||
|
// then firmware's boot level is assumed to be full brightness.
|
||||||
|
if (!g_hasBqc) g_percent = 100;
|
||||||
|
|
||||||
|
char path[128];
|
||||||
|
interp.GetNamespace().GetNodePath(g_output, path, sizeof(path));
|
||||||
|
KernelLogStream(INFO, "AcpiVideo") << "Brightness via " << path
|
||||||
|
<< "._BCM, " << base::dec << (uint64_t)g_levelCount << " levels"
|
||||||
|
<< (g_hasBqc ? ", _BQC" : "");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
// Levels
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
static int LevelToPercent(uint32_t level) {
|
||||||
|
uint32_t top = g_levels[g_levelCount - 1];
|
||||||
|
if (level > top) level = top;
|
||||||
|
return (int)(((uint64_t)level * 100 + top / 2) / top);
|
||||||
|
}
|
||||||
|
|
||||||
|
// The supported level closest to the requested share of the top one.
|
||||||
|
static uint32_t PercentToLevel(int percent) {
|
||||||
|
uint64_t target = (uint64_t)g_levels[g_levelCount - 1] * (uint32_t)percent / 100;
|
||||||
|
uint32_t best = g_levels[0];
|
||||||
|
uint64_t bestDistance = ~0ULL;
|
||||||
|
for (int i = 0; i < g_levelCount; i++) {
|
||||||
|
uint64_t distance = g_levels[i] > target ? g_levels[i] - target
|
||||||
|
: target - g_levels[i];
|
||||||
|
if (distance < bestDistance) {
|
||||||
|
bestDistance = distance;
|
||||||
|
best = g_levels[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return best;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int Query() {
|
||||||
|
if (g_hasBqc) {
|
||||||
|
AML::Object result{};
|
||||||
|
if (AML::GetInterpreter().EvaluateChild(g_output, "_BQC", nullptr, 0, result)
|
||||||
|
&& result.Type == AML::ObjectType::Integer)
|
||||||
|
return LevelToPercent((uint32_t)result.Integer);
|
||||||
|
}
|
||||||
|
return g_percent;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
// Public API
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
int Brightness(int percent) {
|
||||||
|
g_lock.Acquire();
|
||||||
|
if (!g_probed) Probe();
|
||||||
|
if (g_output < 0) {
|
||||||
|
g_lock.Release();
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int result;
|
||||||
|
if (percent < 0) {
|
||||||
|
result = Query();
|
||||||
|
} else {
|
||||||
|
if (percent > 100) percent = 100;
|
||||||
|
uint32_t level = PercentToLevel(percent);
|
||||||
|
AML::Object arg = AcpiPower::IntegerArg(level);
|
||||||
|
AML::Object ignored{};
|
||||||
|
if (AML::GetInterpreter().EvaluateChild(g_output, "_BCM", &arg, 1, ignored)) {
|
||||||
|
g_percent = LevelToPercent(level);
|
||||||
|
int reported = Query();
|
||||||
|
result = reported >= 0 ? reported : g_percent;
|
||||||
|
} else {
|
||||||
|
result = -2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
g_lock.Release();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
/*
|
||||||
|
* AcpiVideo.hpp
|
||||||
|
* ACPI video extensions (_BCL/_BCM/_BQC) panel brightness fallback
|
||||||
|
* Copyright (c) 2026 Daniel Hammer
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
namespace Hal {
|
||||||
|
namespace AcpiVideo {
|
||||||
|
|
||||||
|
// Panel brightness through the firmware's ACPI video output device,
|
||||||
|
// for machines no native backlight driver covers (a panel wired to a
|
||||||
|
// discrete GPU, a GPU Montauk has no driver for). percent < 0 queries;
|
||||||
|
// otherwise the nearest level the firmware lists is selected. Returns
|
||||||
|
// the resulting 0..100, -1 when the firmware has no brightness
|
||||||
|
// methods, -2 when evaluating them failed. Discovery runs on first
|
||||||
|
// use. May sleep (AML).
|
||||||
|
int Brightness(int percent);
|
||||||
|
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -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"
|
||||||
@@ -36,12 +36,12 @@ namespace montauk::abi {
|
|||||||
bitsPerSample, pid, name);
|
bitsPerSample, pid, name);
|
||||||
}
|
}
|
||||||
|
|
||||||
return -1;
|
return NoDevice;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int64_t Sys_AudioClose(int handle) {
|
static int64_t Sys_AudioClose(int handle) {
|
||||||
Drivers::Audio::Mixer::Close(handle);
|
Drivers::Audio::Mixer::Close(handle);
|
||||||
return 0;
|
return Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int64_t Sys_AudioWrite(int handle, const uint8_t* data, uint32_t size) {
|
static int64_t Sys_AudioWrite(int handle, const uint8_t* data, uint32_t size) {
|
||||||
@@ -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>
|
||||||
@@ -18,15 +18,15 @@
|
|||||||
namespace montauk::abi {
|
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 -1;
|
if (!buf || maxCount <= 0) return InvalidArgument;
|
||||||
if (!Drivers::USB::Bluetooth::IsInitialized()) return -1;
|
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
|
||||||
@@ -46,39 +46,39 @@ namespace montauk::abi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static int64_t Sys_BtConnect(const uint8_t* bdAddr) {
|
static int64_t Sys_BtConnect(const uint8_t* bdAddr) {
|
||||||
if (!bdAddr) return -1;
|
if (!bdAddr) return InvalidArgument;
|
||||||
if (!Drivers::USB::Bluetooth::IsInitialized()) return -1;
|
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 -1;
|
if (!bdAddr) return InvalidArgument;
|
||||||
if (!Drivers::USB::Bluetooth::IsInitialized()) return -1;
|
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 -1;
|
if (!bdAddr) return InvalidArgument;
|
||||||
if (!Drivers::USB::Bluetooth::IsInitialized()) return -1;
|
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) ? 0 : -1;
|
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);
|
||||||
@@ -90,21 +90,21 @@ namespace montauk::abi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static int64_t Sys_BtForget(const uint8_t* bdAddr) {
|
static int64_t Sys_BtForget(const uint8_t* bdAddr) {
|
||||||
if (!bdAddr) return -1;
|
if (!bdAddr) return InvalidArgument;
|
||||||
if (!Drivers::USB::Bluetooth::IsInitialized()) return -1;
|
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);
|
||||||
@@ -119,18 +119,18 @@ namespace montauk::abi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static int64_t Sys_BtInfo(BtAdapterInfo* buf) {
|
static int64_t Sys_BtInfo(BtAdapterInfo* buf) {
|
||||||
if (!buf) return -1;
|
if (!buf) return InvalidArgument;
|
||||||
|
|
||||||
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 -1;
|
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";
|
||||||
@@ -138,7 +138,7 @@ namespace montauk::abi {
|
|||||||
for (; i < 63 && name[i]; i++) buf->name[i] = name[i];
|
for (; i < 63 && name[i]; i++) buf->name[i] = name[i];
|
||||||
buf->name[i] = '\0';
|
buf->name[i] = '\0';
|
||||||
|
|
||||||
return 0;
|
return Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -12,4 +12,4 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#define MONTAUK_BUILD_NUMBER 220
|
#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,66 +82,84 @@ 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int Sys_ClipboardSetText(const char* data, uint32_t len) {
|
// Swap `incoming` into the caller's clipboard (it becomes empty) and free
|
||||||
if (len == 0)
|
// what was there. An empty transfer clears the clipboard.
|
||||||
return Sys_ClipboardClear();
|
static int ClipboardReplace(DataTransfer::Transfer& incoming) {
|
||||||
if (data == nullptr || len > CLIPBOARD_MAX_TEXT_BYTES || Memory::g_heap == nullptr)
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
for (uint32_t i = 0; i < len; i++) {
|
|
||||||
if (data[i] == '\0')
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
const char* currentUser = ClipboardCurrentUser();
|
const char* currentUser = ClipboardCurrentUser();
|
||||||
if (currentUser == nullptr) return -1;
|
if (currentUser == nullptr) {
|
||||||
|
DataTransfer::Free(incoming);
|
||||||
|
return GenericFailure;
|
||||||
|
}
|
||||||
|
|
||||||
char user[32];
|
char user[32];
|
||||||
ClipboardCopyUser(user, currentUser);
|
ClipboardCopyUser(user, currentUser);
|
||||||
|
|
||||||
char* newText = (char*)Memory::g_heap->Request(len + 1);
|
DataTransfer::Transfer old;
|
||||||
if (newText == nullptr) return -1;
|
old.count = 0;
|
||||||
for (uint32_t i = 0; i < len; i++)
|
|
||||||
newText[i] = data[i];
|
|
||||||
newText[len] = '\0';
|
|
||||||
|
|
||||||
char* oldText = nullptr;
|
|
||||||
|
|
||||||
g_clipboardLock.Acquire();
|
g_clipboardLock.Acquire();
|
||||||
ClipboardSlot* slot = ClipboardEnsureSlotLocked(user);
|
ClipboardSlot* slot = ClipboardEnsureSlotLocked(user);
|
||||||
if (slot == nullptr) {
|
if (slot == nullptr) {
|
||||||
g_clipboardLock.Release();
|
g_clipboardLock.Release();
|
||||||
Memory::g_heap->Free(newText);
|
DataTransfer::Free(incoming);
|
||||||
return -1;
|
return Busy;
|
||||||
}
|
}
|
||||||
|
|
||||||
oldText = slot->text;
|
old = slot->transfer;
|
||||||
slot->text = newText;
|
slot->transfer = incoming;
|
||||||
slot->len = len;
|
|
||||||
slot->serial = ClipboardNextSerial(slot->serial);
|
slot->serial = ClipboardNextSerial(slot->serial);
|
||||||
g_clipboardLock.Release();
|
g_clipboardLock.Release();
|
||||||
|
|
||||||
if (oldText != nullptr)
|
incoming.count = 0;
|
||||||
Memory::g_heap->Free(oldText);
|
DataTransfer::Free(old);
|
||||||
|
return Success;
|
||||||
|
}
|
||||||
|
|
||||||
return 0;
|
static int Sys_ClipboardClear() {
|
||||||
|
DataTransfer::Transfer empty;
|
||||||
|
empty.count = 0;
|
||||||
|
return ClipboardReplace(empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int Sys_ClipboardSetText(const char* data, uint32_t len) {
|
||||||
|
if (len == 0)
|
||||||
|
return Sys_ClipboardClear();
|
||||||
|
if (data == nullptr || len > CLIPBOARD_MAX_TEXT_BYTES)
|
||||||
|
return InvalidArgument;
|
||||||
|
|
||||||
|
for (uint32_t i = 0; i < len; i++) {
|
||||||
|
if (data[i] == '\0')
|
||||||
|
return InvalidArgument;
|
||||||
|
}
|
||||||
|
|
||||||
|
DataTransfer::Transfer transfer;
|
||||||
|
int rc = DataTransfer::BuildText(data, len, transfer);
|
||||||
|
if (rc != Success) return rc;
|
||||||
|
return ClipboardReplace(transfer);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int Sys_ClipboardSetData(const TransferItem* items, uint32_t count) {
|
||||||
|
if (count == 0) return Sys_ClipboardClear();
|
||||||
|
DataTransfer::Transfer transfer;
|
||||||
|
int rc = DataTransfer::BuildFromUser(items, count, transfer);
|
||||||
|
if (rc != Success) return rc;
|
||||||
|
return ClipboardReplace(transfer);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int Sys_ClipboardGetInfo(ClipboardInfo* out) {
|
static int Sys_ClipboardGetInfo(ClipboardInfo* out) {
|
||||||
if (out == nullptr) return -1;
|
if (out == nullptr) return InvalidArgument;
|
||||||
|
|
||||||
out->textBytes = 0;
|
out->textBytes = 0;
|
||||||
out->_pad = 0;
|
out->_pad = 0;
|
||||||
out->serial = 0;
|
out->serial = 0;
|
||||||
|
|
||||||
const char* currentUser = ClipboardCurrentUser();
|
const char* currentUser = ClipboardCurrentUser();
|
||||||
if (currentUser == nullptr) return -1;
|
if (currentUser == nullptr) return GenericFailure;
|
||||||
|
|
||||||
char user[32];
|
char user[32];
|
||||||
ClipboardCopyUser(user, currentUser);
|
ClipboardCopyUser(user, currentUser);
|
||||||
@@ -148,73 +167,79 @@ 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();
|
||||||
return 0;
|
return Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int Sys_ClipboardGetText(char* buffer, uint32_t bufferLen, uint32_t* outLen, uint64_t* outSerial) {
|
static int Sys_ClipboardGetText(char* buffer, uint32_t bufferLen, uint32_t* outLen, uint64_t* outSerial) {
|
||||||
if (outLen == nullptr) return -1;
|
if (outLen == nullptr) return InvalidArgument;
|
||||||
|
|
||||||
*outLen = 0;
|
*outLen = 0;
|
||||||
if (outSerial != nullptr) *outSerial = 0;
|
if (outSerial != nullptr) *outSerial = 0;
|
||||||
|
|
||||||
const char* currentUser = ClipboardCurrentUser();
|
const char* currentUser = ClipboardCurrentUser();
|
||||||
if (currentUser == nullptr) return -1;
|
if (currentUser == nullptr) return GenericFailure;
|
||||||
|
|
||||||
char user[32];
|
char user[32];
|
||||||
ClipboardCopyUser(user, currentUser);
|
ClipboardCopyUser(user, currentUser);
|
||||||
|
|
||||||
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 0;
|
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 -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (uint32_t i = 0; i < slot->len; i++)
|
|
||||||
buffer[i] = slot->text[i];
|
|
||||||
g_clipboardLock.Release();
|
g_clipboardLock.Release();
|
||||||
return 0;
|
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 -1;
|
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 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
+34
-22
@@ -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)");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -225,10 +233,10 @@ namespace montauk::abi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static int Sys_DiskInfo(DiskInfo* buf, int blockDevIndex) {
|
static int Sys_DiskInfo(DiskInfo* buf, int blockDevIndex) {
|
||||||
if (buf == nullptr) return -1;
|
if (buf == nullptr) return InvalidArgument;
|
||||||
|
|
||||||
auto* bdev = Drivers::Storage::GetBlockDevice(blockDevIndex);
|
auto* bdev = Drivers::Storage::GetBlockDevice(blockDevIndex);
|
||||||
if (!bdev) return -1;
|
if (!bdev) return NoDevice;
|
||||||
|
|
||||||
// Zero the struct first so SATA-specific fields default to 0
|
// Zero the struct first so SATA-specific fields default to 0
|
||||||
for (unsigned i = 0; i < sizeof(DiskInfo); i++)
|
for (unsigned i = 0; i < sizeof(DiskInfo); i++)
|
||||||
@@ -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;
|
||||||
}
|
}
|
||||||
@@ -303,8 +315,8 @@ namespace montauk::abi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If type is still 0, this block device is not recognized
|
// If type is still 0, this block device is not recognized
|
||||||
if (buf->type == 0) return -1;
|
if (buf->type == 0) return UnsupportedOp;
|
||||||
|
|
||||||
return 0;
|
return Success;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -20,14 +20,33 @@
|
|||||||
#include <Fs/ProtectedPaths.hpp>
|
#include <Fs/ProtectedPaths.hpp>
|
||||||
|
|
||||||
namespace montauk::abi {
|
namespace montauk::abi {
|
||||||
|
// Writes refused here return SystemIntegrityError: the path is protected
|
||||||
|
// by Fs/ProtectedPaths, not merely gated on an ordinary capability.
|
||||||
static bool CanModifyFilePath(const char* resolved) {
|
static bool CanModifyFilePath(const char* resolved) {
|
||||||
uint64_t required = Fs::RequiredFileWriteCapability(resolved);
|
uint64_t required = Fs::RequiredFileWriteCapability(resolved);
|
||||||
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 -1;
|
if (!ResolveProcessPath(path, resolved, sizeof(resolved))) return InvalidArgument;
|
||||||
return Ipc::OpenFileHandle(resolved);
|
return Ipc::OpenFileHandle(resolved);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -53,8 +72,8 @@ namespace montauk::abi {
|
|||||||
static int Sys_ReadDirAt(const char* path, const char** outNames, int maxEntries,
|
static int Sys_ReadDirAt(const char* path, const char** outNames, int maxEntries,
|
||||||
int startIndex) {
|
int startIndex) {
|
||||||
char resolved[256];
|
char resolved[256];
|
||||||
if (!ResolveProcessPath(path, resolved, sizeof(resolved))) return -1;
|
if (!ResolveProcessPath(path, resolved, sizeof(resolved))) return InvalidArgument;
|
||||||
if (startIndex < 0) return -1;
|
if (startIndex < 0) return InvalidArgument;
|
||||||
|
|
||||||
// Get entries from VFS into a kernel-local array
|
// Get entries from VFS into a kernel-local array
|
||||||
const char* kernelNames[256];
|
const char* kernelNames[256];
|
||||||
@@ -65,7 +84,7 @@ namespace montauk::abi {
|
|||||||
|
|
||||||
// Allocate a user-accessible page for string data via process heap
|
// Allocate a user-accessible page for string data via process heap
|
||||||
auto* proc = Sched::GetCurrentProcessPtr();
|
auto* proc = Sched::GetCurrentProcessPtr();
|
||||||
if (proc == nullptr) return -1;
|
if (proc == nullptr) return GenericFailure;
|
||||||
|
|
||||||
// Use a rotating ring of scratch pages below the heap instead of
|
// Use a rotating ring of scratch pages below the heap instead of
|
||||||
// extending the heap high-water mark on every call. This keeps repeated
|
// extending the heap high-water mark on every call. This keeps repeated
|
||||||
@@ -75,16 +94,16 @@ 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 -1;
|
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 -1;
|
return OutOfMemory;
|
||||||
}
|
}
|
||||||
pageBuf = (uint8_t*)page;
|
pageBuf = (uint8_t*)page;
|
||||||
} else {
|
} else {
|
||||||
@@ -113,25 +132,26 @@ namespace montauk::abi {
|
|||||||
|
|
||||||
static int Sys_FCreate(const char* path) {
|
static int Sys_FCreate(const char* path) {
|
||||||
char resolved[256];
|
char resolved[256];
|
||||||
if (!ResolveProcessPath(path, resolved, sizeof(resolved))) return -1;
|
if (!ResolveProcessPath(path, resolved, sizeof(resolved))) return InvalidArgument;
|
||||||
if (!CanModifyFilePath(resolved)) return SYS_ERR_PERMISSION;
|
if (!CanModifyFilePath(resolved)) return SystemIntegrityError;
|
||||||
|
if (!ParentDirExists(resolved)) return NoSuchPath;
|
||||||
return Ipc::CreateFileHandle(resolved);
|
return Ipc::CreateFileHandle(resolved);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int Sys_FDelete(const char* path) {
|
static int Sys_FDelete(const char* path) {
|
||||||
char resolved[256];
|
char resolved[256];
|
||||||
if (!ResolveProcessPath(path, resolved, sizeof(resolved))) return -1;
|
if (!ResolveProcessPath(path, resolved, sizeof(resolved))) return InvalidArgument;
|
||||||
if (!CanModifyFilePath(resolved)) return SYS_ERR_PERMISSION;
|
if (!CanModifyFilePath(resolved)) return SystemIntegrityError;
|
||||||
return Fs::Vfs::VfsDelete(resolved);
|
return Fs::Vfs::VfsDelete(resolved);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int Sys_Stat(const char* path, FileStat* out) {
|
static int Sys_Stat(const char* path, FileStat* out) {
|
||||||
if (out == nullptr) return -1;
|
if (out == nullptr) return InvalidArgument;
|
||||||
char resolved[256];
|
char resolved[256];
|
||||||
if (!ResolveProcessPath(path, resolved, sizeof(resolved))) return -1;
|
if (!ResolveProcessPath(path, resolved, sizeof(resolved))) return InvalidArgument;
|
||||||
|
|
||||||
Fs::Vfs::StatInfo info;
|
Fs::Vfs::StatInfo info;
|
||||||
if (Fs::Vfs::VfsStat(resolved, info) != 0) return -1;
|
if (Fs::Vfs::VfsStat(resolved, info) != 0) return NoSuchPath;
|
||||||
|
|
||||||
out->size = info.size;
|
out->size = info.size;
|
||||||
out->mtime = info.mtime;
|
out->mtime = info.mtime;
|
||||||
@@ -139,38 +159,47 @@ namespace montauk::abi {
|
|||||||
out->atime = info.atime;
|
out->atime = info.atime;
|
||||||
out->mode = info.mode;
|
out->mode = info.mode;
|
||||||
out->isDir = info.isDir ? 1u : 0u;
|
out->isDir = info.isDir ? 1u : 0u;
|
||||||
return 0;
|
return Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int Sys_Utime(const char* path, int64_t atime, int64_t mtime,
|
static int Sys_Utime(const char* path, int64_t atime, int64_t mtime,
|
||||||
bool useCurrent) {
|
bool useCurrent) {
|
||||||
char resolved[256];
|
char resolved[256];
|
||||||
if (!ResolveProcessPath(path, resolved, sizeof(resolved))) return -1;
|
if (!ResolveProcessPath(path, resolved, sizeof(resolved))) return InvalidArgument;
|
||||||
// Timestamps are file state like any other: a protected path must not
|
// Timestamps are file state like any other: a protected path must not
|
||||||
// be mutable through a side door that skips the write check.
|
// be mutable through a side door that skips the write check.
|
||||||
if (!CanModifyFilePath(resolved)) return SYS_ERR_PERMISSION;
|
if (!CanModifyFilePath(resolved)) return SystemIntegrityError;
|
||||||
if (useCurrent) {
|
if (useCurrent) {
|
||||||
int64_t now = Timekeeping::GetUnixTimestamp();
|
int64_t now = Timekeeping::GetUnixTimestamp();
|
||||||
atime = now;
|
atime = now;
|
||||||
mtime = now;
|
mtime = now;
|
||||||
}
|
}
|
||||||
return Fs::Vfs::VfsSetTimes(resolved, atime, mtime);
|
// VfsSetTimes has its own codes: -1 not found, -2 unsupported by the
|
||||||
|
// filesystem, -3 timestamp out of range, -4 I/O error.
|
||||||
|
switch (Fs::Vfs::VfsSetTimes(resolved, atime, mtime)) {
|
||||||
|
case 0: return Success;
|
||||||
|
case -2: return UnsupportedOp;
|
||||||
|
case -3: return InvalidArgument;
|
||||||
|
case -4: return IoFailure;
|
||||||
|
default: return NoSuchPath;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static int Sys_FMkdir(const char* path) {
|
static int Sys_FMkdir(const char* path) {
|
||||||
char resolved[256];
|
char resolved[256];
|
||||||
if (!ResolveProcessPath(path, resolved, sizeof(resolved))) return -1;
|
if (!ResolveProcessPath(path, resolved, sizeof(resolved))) return InvalidArgument;
|
||||||
if (!CanModifyFilePath(resolved)) return SYS_ERR_PERMISSION;
|
if (!CanModifyFilePath(resolved)) return SystemIntegrityError;
|
||||||
|
if (!ParentDirExists(resolved)) return NoSuchPath;
|
||||||
return Fs::Vfs::VfsMkdir(resolved);
|
return Fs::Vfs::VfsMkdir(resolved);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int Sys_FRename(const char* oldPath, const char* newPath) {
|
static int Sys_FRename(const char* oldPath, const char* newPath) {
|
||||||
char resolvedOld[256];
|
char resolvedOld[256];
|
||||||
char resolvedNew[256];
|
char resolvedNew[256];
|
||||||
if (!ResolveProcessPath(oldPath, resolvedOld, sizeof(resolvedOld))) return -1;
|
if (!ResolveProcessPath(oldPath, resolvedOld, sizeof(resolvedOld))) return InvalidArgument;
|
||||||
if (!ResolveProcessPath(newPath, resolvedNew, sizeof(resolvedNew))) return -1;
|
if (!ResolveProcessPath(newPath, resolvedNew, sizeof(resolvedNew))) return InvalidArgument;
|
||||||
if (!CanModifyFilePath(resolvedOld) || !CanModifyFilePath(resolvedNew))
|
if (!CanModifyFilePath(resolvedOld) || !CanModifyFilePath(resolvedNew))
|
||||||
return SYS_ERR_PERMISSION;
|
return SystemIntegrityError;
|
||||||
return Fs::Vfs::VfsRename(resolvedOld, resolvedNew);
|
return Fs::Vfs::VfsRename(resolvedOld, resolvedNew);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,8 @@
|
|||||||
#include <Memory/HHDM.hpp>
|
#include <Memory/HHDM.hpp>
|
||||||
#include <Graphics/Framebuffer.hpp>
|
#include <Graphics/Framebuffer.hpp>
|
||||||
#include <Drivers/Graphics/IntelGPU.hpp>
|
#include <Drivers/Graphics/IntelGPU.hpp>
|
||||||
|
#include <ACPI/AcpiVideo.hpp>
|
||||||
|
#include <ACPI/WmiEcBacklight.hpp>
|
||||||
#include <Terminal/Terminal.hpp>
|
#include <Terminal/Terminal.hpp>
|
||||||
|
|
||||||
#include "Syscall.hpp"
|
#include "Syscall.hpp"
|
||||||
@@ -81,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
|
||||||
)) {
|
)) {
|
||||||
@@ -96,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
|
||||||
)) {
|
)) {
|
||||||
@@ -118,18 +120,49 @@ namespace montauk::abi {
|
|||||||
if ((int64_t)index == -1) {
|
if ((int64_t)index == -1) {
|
||||||
return GPU::FlipSupported() ? 1 : 0;
|
return GPU::FlipSupported() ? 1 : 0;
|
||||||
}
|
}
|
||||||
if (!GPU::FlipSupported()) return -1;
|
if (!GPU::FlipSupported()) return UnsupportedOp;
|
||||||
|
|
||||||
auto* proc = Sched::GetCurrentProcessPtr();
|
auto* proc = Sched::GetCurrentProcessPtr();
|
||||||
if ((int64_t)index == -2) {
|
if ((int64_t)index == -2) {
|
||||||
return proc != nullptr ? GPU::AcquireFlip(proc->pid) : -1;
|
return proc != nullptr ? GPU::AcquireFlip(proc->pid) : GenericFailure;
|
||||||
}
|
}
|
||||||
if (proc == nullptr) return -1;
|
if (proc == nullptr) return GenericFailure;
|
||||||
return GPU::Flip((int)index, (flags & 1) != 0, proc->pid);
|
return GPU::Flip((int)index, (flags & 1) != 0, proc->pid);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Firmware brightness interfaces (EC via WMI, ACPI _BCM) drive the
|
||||||
|
// built-in panel, so they apply only while that panel is what is on
|
||||||
|
// screen (or no native driver knows what is): never for an external
|
||||||
|
// monitor, where they would silently dim a closed lid instead.
|
||||||
|
static bool PanelIsActiveOutput() {
|
||||||
|
if (!Drivers::Graphics::IntelGPU::IsInitialized()) return true;
|
||||||
|
uint8_t type = Drivers::Graphics::IntelGPU::ActiveConnectorType();
|
||||||
|
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) {
|
||||||
|
bool panel = PanelIsActiveOutput();
|
||||||
|
if (panel) {
|
||||||
|
int rc = Hal::WmiEcBacklight::Brightness(percent);
|
||||||
|
if (rc != -1) return rc;
|
||||||
|
}
|
||||||
|
int rc = Drivers::Graphics::IntelGPU::Brightness(percent);
|
||||||
|
if (rc == -1 && panel) rc = Hal::AcpiVideo::Brightness(percent);
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
static int64_t Sys_DisplayInfo(DisplayInfo* out) {
|
static int64_t Sys_DisplayInfo(DisplayInfo* out) {
|
||||||
return Drivers::Graphics::IntelGPU::GetDisplayInfo(out) ? 0 : -1;
|
if (!Drivers::Graphics::IntelGPU::GetDisplayInfo(out)) return NoDevice;
|
||||||
|
// The driver only knows its own backends; report what a set would use.
|
||||||
|
int level = DisplayBrightness(-1);
|
||||||
|
out->brightness = level >= 0 ? level : -1;
|
||||||
|
if (level >= 0) out->capabilities |= DISPLAY_CAP_BRIGHTNESS;
|
||||||
|
else out->capabilities &= ~DISPLAY_CAP_BRIGHTNESS;
|
||||||
|
return Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int64_t Sys_DisplayModes(DisplayModeInfo* out, int maxCount) {
|
static int64_t Sys_DisplayModes(DisplayModeInfo* out, int maxCount) {
|
||||||
@@ -137,11 +170,20 @@ namespace montauk::abi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static int64_t Sys_DisplaySetMode(int modeIndex) {
|
static int64_t Sys_DisplaySetMode(int modeIndex) {
|
||||||
return Drivers::Graphics::IntelGPU::SetDisplayMode(modeIndex);
|
// The driver answers -2 for a mode that would need link retraining
|
||||||
|
// and -3 for a modeset that failed and was rolled back.
|
||||||
|
int64_t rc = Drivers::Graphics::IntelGPU::SetDisplayMode(modeIndex);
|
||||||
|
if (rc == -2) return UnsupportedOp;
|
||||||
|
if (rc == -3) return IoFailure;
|
||||||
|
return rc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// percent < 0 queries. Returns 0..100, NoDevice when the active output
|
||||||
|
// has no controllable backlight, IoFailure when the hardware refused.
|
||||||
static int64_t Sys_DisplayBrightness(int percent) {
|
static int64_t Sys_DisplayBrightness(int percent) {
|
||||||
return Drivers::Graphics::IntelGPU::Brightness(percent);
|
int rc = DisplayBrightness(percent);
|
||||||
|
if (rc >= 0) return rc;
|
||||||
|
return rc == -1 ? NoDevice : IoFailure;
|
||||||
}
|
}
|
||||||
|
|
||||||
static uint64_t Sys_TermSize() {
|
static uint64_t Sys_TermSize() {
|
||||||
|
|||||||
+24
-24
@@ -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);
|
||||||
@@ -211,9 +211,9 @@ namespace montauk::abi {
|
|||||||
auto* proc = Sched::GetCurrentProcessPtr();
|
auto* proc = Sched::GetCurrentProcessPtr();
|
||||||
int slot = GetCurrentSlot();
|
int slot = GetCurrentSlot();
|
||||||
if (proc == nullptr || slot < 0 || size == 0 || (addr & 0xFFFULL) != 0 ||
|
if (proc == nullptr || slot < 0 || size == 0 || (addr & 0xFFFULL) != 0 ||
|
||||||
size > UINT64_MAX - 0xFFFULL) return -1;
|
size > UINT64_MAX - 0xFFFULL) return InvalidArgument;
|
||||||
size = (size + 0xFFFULL) & ~0xFFFULL;
|
size = (size + 0xFFFULL) & ~0xFFFULL;
|
||||||
if (addr > UINT64_MAX - size) return -1;
|
if (addr > UINT64_MAX - size) return InvalidArgument;
|
||||||
|
|
||||||
g_heapLocks[slot].Acquire();
|
g_heapLocks[slot].Acquire();
|
||||||
uint64_t firstPage = 0;
|
uint64_t firstPage = 0;
|
||||||
@@ -229,7 +229,7 @@ namespace montauk::abi {
|
|||||||
}
|
}
|
||||||
link = &area->next;
|
link = &area->next;
|
||||||
}
|
}
|
||||||
if (*link == nullptr) { g_heapLocks[slot].Release(); return -1; }
|
if (*link == nullptr) { g_heapLocks[slot].Release(); return InvalidArgument; }
|
||||||
|
|
||||||
HeapAlloc* area = *link;
|
HeapAlloc* area = *link;
|
||||||
uint64_t oldPages = area->numPages;
|
uint64_t oldPages = area->numPages;
|
||||||
@@ -253,16 +253,16 @@ 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;
|
||||||
g_heapLocks[slot].Release();
|
g_heapLocks[slot].Release();
|
||||||
return 0;
|
return Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline int64_t Sys_Protect(uint64_t addr, uint64_t size, uint64_t prot) {
|
inline int64_t Sys_Protect(uint64_t addr, uint64_t size, uint64_t prot) {
|
||||||
@@ -272,9 +272,9 @@ namespace montauk::abi {
|
|||||||
size > UINT64_MAX - 0xFFFULL ||
|
size > UINT64_MAX - 0xFFFULL ||
|
||||||
(prot & ~(VmProtRead | VmProtWrite | VmProtExec)) != 0 ||
|
(prot & ~(VmProtRead | VmProtWrite | VmProtExec)) != 0 ||
|
||||||
(prot & VmProtRead) == 0 ||
|
(prot & VmProtRead) == 0 ||
|
||||||
((prot & VmProtWrite) != 0 && (prot & VmProtExec) != 0)) return -1;
|
((prot & VmProtWrite) != 0 && (prot & VmProtExec) != 0)) return InvalidArgument;
|
||||||
size = (size + 0xFFFULL) & ~0xFFFULL;
|
size = (size + 0xFFFULL) & ~0xFFFULL;
|
||||||
if (addr > UINT64_MAX - size) return -1;
|
if (addr > UINT64_MAX - size) return InvalidArgument;
|
||||||
|
|
||||||
g_heapLocks[slot].Acquire();
|
g_heapLocks[slot].Acquire();
|
||||||
uint64_t pages = size / 0x1000ULL;
|
uint64_t pages = size / 0x1000ULL;
|
||||||
@@ -293,7 +293,7 @@ namespace montauk::abi {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!covered) { g_heapLocks[slot].Release(); return -1; }
|
if (!covered) { g_heapLocks[slot].Release(); return InvalidArgument; }
|
||||||
}
|
}
|
||||||
|
|
||||||
uint64_t end = addr + size;
|
uint64_t end = addr + size;
|
||||||
@@ -336,28 +336,28 @@ 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();
|
||||||
return -1;
|
return GenericFailure;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
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 0;
|
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;
|
||||||
}
|
}
|
||||||
|
|||||||
+39
-22
@@ -19,18 +19,18 @@ namespace montauk::abi {
|
|||||||
const char* path, const char* args,
|
const char* path, const char* args,
|
||||||
const SpawnCapabilities* capabilities) {
|
const SpawnCapabilities* capabilities) {
|
||||||
char resolved[256];
|
char resolved[256];
|
||||||
if (!ResolveProcessPath(path, resolved, sizeof(resolved))) return -1;
|
if (!ResolveProcessPath(path, resolved, sizeof(resolved))) return InvalidArgument;
|
||||||
|
|
||||||
int parentSlot = Ipc::CurrentSlot();
|
int parentSlot = Ipc::CurrentSlot();
|
||||||
int childPid = Sched::Spawn(resolved, args, false, nullptr, 0,
|
int childPid = Sched::Spawn(resolved, args, false, nullptr, 0,
|
||||||
capabilities);
|
capabilities);
|
||||||
if (childPid < 0) return -1;
|
if (childPid < 0) return childPid;
|
||||||
|
|
||||||
auto* child = Sched::GetProcessByPid(childPid);
|
auto* child = Sched::GetProcessByPid(childPid);
|
||||||
int childSlot = Ipc::SlotForPid(childPid);
|
int childSlot = Ipc::SlotForPid(childPid);
|
||||||
if (child == nullptr || childSlot < 0 || parentSlot < 0) {
|
if (child == nullptr || childSlot < 0 || parentSlot < 0) {
|
||||||
Sched::KillProcess(childPid);
|
Sched::KillProcess(childPid);
|
||||||
return -1;
|
return GenericFailure;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ipc::Stream* outStream = Ipc::CreateStream(RedirOutputStreamCapacity);
|
Ipc::Stream* outStream = Ipc::CreateStream(RedirOutputStreamCapacity);
|
||||||
@@ -41,7 +41,7 @@ namespace montauk::abi {
|
|||||||
if (inStream != nullptr) Ipc::ReleaseStream(inStream, false, false);
|
if (inStream != nullptr) Ipc::ReleaseStream(inStream, false, false);
|
||||||
if (keyMailbox != nullptr) Ipc::ReleaseMailbox(keyMailbox, false, false);
|
if (keyMailbox != nullptr) Ipc::ReleaseMailbox(keyMailbox, false, false);
|
||||||
Sched::KillProcess(childPid);
|
Sched::KillProcess(childPid);
|
||||||
return -1;
|
return OutOfMemory;
|
||||||
}
|
}
|
||||||
|
|
||||||
int parentOutHandle = Ipc::InstallHandleForSlot(
|
int parentOutHandle = Ipc::InstallHandleForSlot(
|
||||||
@@ -71,7 +71,7 @@ namespace montauk::abi {
|
|||||||
if (child->ioInHandle < 0) Ipc::ReleaseStream(inStream, false, false);
|
if (child->ioInHandle < 0) Ipc::ReleaseStream(inStream, false, false);
|
||||||
if (child->ioKeyHandle < 0) Ipc::ReleaseMailbox(keyMailbox, false, false);
|
if (child->ioKeyHandle < 0) Ipc::ReleaseMailbox(keyMailbox, false, false);
|
||||||
Sched::KillProcess(childPid);
|
Sched::KillProcess(childPid);
|
||||||
return -1;
|
return GenericFailure;
|
||||||
}
|
}
|
||||||
|
|
||||||
child->redirected = true;
|
child->redirected = true;
|
||||||
@@ -80,7 +80,7 @@ namespace montauk::abi {
|
|||||||
child->termRows = 25;
|
child->termRows = 25;
|
||||||
if (Sched::StartProcess(childPid) < 0) {
|
if (Sched::StartProcess(childPid) < 0) {
|
||||||
Sched::KillProcess(childPid);
|
Sched::KillProcess(childPid);
|
||||||
return -1;
|
return GenericFailure;
|
||||||
}
|
}
|
||||||
|
|
||||||
return childPid;
|
return childPid;
|
||||||
@@ -93,11 +93,11 @@ namespace montauk::abi {
|
|||||||
static int Sys_SpawnRedirCaps(const char* path, const char* args,
|
static int Sys_SpawnRedirCaps(const char* path, const char* args,
|
||||||
const SpawnCapabilities* requested) {
|
const SpawnCapabilities* requested) {
|
||||||
auto* parent = Sched::GetCurrentProcessPtr();
|
auto* parent = Sched::GetCurrentProcessPtr();
|
||||||
if (parent == nullptr || requested == nullptr) return -1;
|
if (parent == nullptr || requested == nullptr) return InvalidArgument;
|
||||||
|
|
||||||
SpawnCapabilities copy = *requested;
|
SpawnCapabilities copy = *requested;
|
||||||
if (!ValidCapabilityDelegation(copy, parent->delegableCaps)) {
|
if (!ValidCapabilityDelegation(copy, parent->delegableCaps)) {
|
||||||
return SYS_ERR_PERMISSION;
|
return CapabilityDenied;
|
||||||
}
|
}
|
||||||
return Sys_SpawnRedirInternal(path, args, ©);
|
return Sys_SpawnRedirInternal(path, args, ©);
|
||||||
}
|
}
|
||||||
@@ -105,51 +105,68 @@ namespace montauk::abi {
|
|||||||
static int Sys_ChildIoRead(int childPid, char* buf, int maxLen) {
|
static int Sys_ChildIoRead(int childPid, char* buf, int maxLen) {
|
||||||
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 SYS_ERR_PERMISSION;
|
return PermissionDenied;
|
||||||
Ipc::HandleSnapshot snapshot;
|
Ipc::HandleSnapshot snapshot;
|
||||||
Ipc::Stream* stream = GetRedirOutStream(child, snapshot);
|
Ipc::Stream* stream = GetRedirOutStream(child, snapshot);
|
||||||
if (!child->redirected || stream == nullptr) return -1;
|
if (!child->redirected || stream == nullptr) return InvalidArgument;
|
||||||
return Ipc::StreamRead(stream, (uint8_t*)buf, maxLen, true);
|
return Ipc::StreamRead(stream, (uint8_t*)buf, maxLen, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int Sys_ChildIoWrite(int childPid, const char* data, int len) {
|
static int Sys_ChildIoWrite(int childPid, const char* data, int 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 SYS_ERR_PERMISSION;
|
return PermissionDenied;
|
||||||
Ipc::HandleSnapshot snapshot;
|
Ipc::HandleSnapshot snapshot;
|
||||||
Ipc::Stream* stream = GetRedirInStream(child, snapshot);
|
Ipc::Stream* stream = GetRedirInStream(child, snapshot);
|
||||||
if (!child->redirected || stream == nullptr) return -1;
|
if (!child->redirected || stream == nullptr) return InvalidArgument;
|
||||||
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 -1;
|
// 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 SYS_ERR_PERMISSION;
|
return PermissionDenied;
|
||||||
Ipc::HandleSnapshot snapshot;
|
Ipc::HandleSnapshot snapshot;
|
||||||
Ipc::Mailbox* mailbox = GetRedirKeyMailbox(child, snapshot);
|
Ipc::Mailbox* mailbox = GetRedirKeyMailbox(child, snapshot);
|
||||||
if (!child->redirected || mailbox == nullptr) return -1;
|
if (!child->redirected || mailbox == nullptr) return InvalidArgument;
|
||||||
|
|
||||||
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 -1;
|
if (rc < 0) return GenericFailure;
|
||||||
if (rc == 0) {
|
if (rc == 0) {
|
||||||
Sched::BlockOnObjectSince(mailbox, 0, observedWake);
|
Sched::BlockOnObjectSince(mailbox, 0, observedWake);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
return 0;
|
return Success;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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())
|
||||||
return SYS_ERR_PERMISSION;
|
return PermissionDenied;
|
||||||
if (!child->redirected) return -1;
|
if (!child->redirected) return InvalidArgument;
|
||||||
child->termCols = cols;
|
child->termCols = cols;
|
||||||
child->termRows = rows;
|
child->termRows = rows;
|
||||||
return 0;
|
return Success;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -20,15 +20,15 @@ namespace montauk::abi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static int Sys_StreamCreate(int* outReadHandle, int* outWriteHandle, uint32_t capacity) {
|
static int Sys_StreamCreate(int* outReadHandle, int* outWriteHandle, uint32_t capacity) {
|
||||||
if (outReadHandle == nullptr || outWriteHandle == nullptr) return -1;
|
if (outReadHandle == nullptr || outWriteHandle == nullptr) return InvalidArgument;
|
||||||
|
|
||||||
int readHandle = -1;
|
int readHandle = -1;
|
||||||
int writeHandle = -1;
|
int writeHandle = -1;
|
||||||
if (Ipc::CreateStreamHandlePair(capacity, readHandle, writeHandle) < 0) return -1;
|
if (Ipc::CreateStreamHandlePair(capacity, readHandle, writeHandle) < 0) return GenericFailure;
|
||||||
|
|
||||||
*outReadHandle = readHandle;
|
*outReadHandle = readHandle;
|
||||||
*outWriteHandle = writeHandle;
|
*outWriteHandle = writeHandle;
|
||||||
return 0;
|
return Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int Sys_StreamRead(int handle, uint8_t* buffer, int maxLen) {
|
static int Sys_StreamRead(int handle, uint8_t* buffer, int maxLen) {
|
||||||
@@ -40,15 +40,15 @@ namespace montauk::abi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static int Sys_MailboxCreate(int* outSendHandle, int* outRecvHandle) {
|
static int Sys_MailboxCreate(int* outSendHandle, int* outRecvHandle) {
|
||||||
if (outSendHandle == nullptr || outRecvHandle == nullptr) return -1;
|
if (outSendHandle == nullptr || outRecvHandle == nullptr) return InvalidArgument;
|
||||||
|
|
||||||
int sendHandle = -1;
|
int sendHandle = -1;
|
||||||
int recvHandle = -1;
|
int recvHandle = -1;
|
||||||
if (Ipc::CreateMailboxHandlePair(sendHandle, recvHandle) < 0) return -1;
|
if (Ipc::CreateMailboxHandlePair(sendHandle, recvHandle) < 0) return GenericFailure;
|
||||||
|
|
||||||
*outSendHandle = sendHandle;
|
*outSendHandle = sendHandle;
|
||||||
*outRecvHandle = recvHandle;
|
*outRecvHandle = recvHandle;
|
||||||
return 0;
|
return Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int Sys_MailboxSend(int handle, uint32_t msgType, const void* data, uint16_t len, int attachHandle) {
|
static int Sys_MailboxSend(int handle, uint32_t msgType, const void* data, uint16_t len, int attachHandle) {
|
||||||
|
|||||||
+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();
|
||||||
@@ -115,15 +115,15 @@ namespace montauk::abi {
|
|||||||
// Decrements refcount; actually unmaps when refcount reaches 0.
|
// Decrements refcount; actually unmaps when refcount reaches 0.
|
||||||
inline int Sys_UnloadLib(uint64_t handle) {
|
inline int Sys_UnloadLib(uint64_t handle) {
|
||||||
int slot = GetCurrentSlot();
|
int slot = GetCurrentSlot();
|
||||||
if (slot < 0) return -1;
|
if (slot < 0) return GenericFailure;
|
||||||
|
|
||||||
int libSlot = (int)handle - 1;
|
int libSlot = (int)handle - 1;
|
||||||
if (libSlot < 0 || libSlot >= MaxLibsPerProcess) return -1;
|
if (libSlot < 0 || libSlot >= MaxLibsPerProcess) return InvalidArgument;
|
||||||
|
|
||||||
g_libLocks[slot].Acquire();
|
g_libLocks[slot].Acquire();
|
||||||
if (!g_libTable[slot][libSlot].inUse) {
|
if (!g_libTable[slot][libSlot].inUse) {
|
||||||
g_libLocks[slot].Release();
|
g_libLocks[slot].Release();
|
||||||
return -1;
|
return InvalidArgument;
|
||||||
}
|
}
|
||||||
|
|
||||||
g_libTable[slot][libSlot].refcount--;
|
g_libTable[slot][libSlot].refcount--;
|
||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -144,7 +144,7 @@ namespace montauk::abi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
g_libLocks[slot].Release();
|
g_libLocks[slot].Release();
|
||||||
return 0;
|
return Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Resolve a symbol in a loaded library.
|
// Resolve a symbol in a loaded library.
|
||||||
@@ -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");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -164,7 +169,7 @@ namespace montauk::abi {
|
|||||||
// wired and wireless status apart: the IP configuration is global, so the
|
// wired and wireless status apart: the IP configuration is global, so the
|
||||||
// only way to say which interface owns it is `active`.
|
// only way to say which interface owns it is `active`.
|
||||||
static int Sys_NetIfs(NetIfInfo* out, int maxCount) {
|
static int Sys_NetIfs(NetIfInfo* out, int maxCount) {
|
||||||
if (out == nullptr || maxCount <= 0) return -1;
|
if (out == nullptr || maxCount <= 0) return InvalidArgument;
|
||||||
|
|
||||||
const auto* active = ::Net::NetIf::Active();
|
const auto* active = ::Net::NetIf::Active();
|
||||||
int count = ::Net::NetIf::Count();
|
int count = ::Net::NetIf::Count();
|
||||||
@@ -193,12 +198,12 @@ namespace montauk::abi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static int Sys_SetNetCfg(const NetCfg* in) {
|
static int Sys_SetNetCfg(const NetCfg* in) {
|
||||||
if (in == nullptr) return -1;
|
if (in == nullptr) return InvalidArgument;
|
||||||
Net::SetIpAddress(in->ipAddress);
|
Net::SetIpAddress(in->ipAddress);
|
||||||
Net::SetSubnetMask(in->subnetMask);
|
Net::SetSubnetMask(in->subnetMask);
|
||||||
Net::SetGateway(in->gateway);
|
Net::SetGateway(in->gateway);
|
||||||
Net::SetDnsServer(in->dnsServer);
|
Net::SetDnsServer(in->dnsServer);
|
||||||
return 0;
|
return Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---- DNS resolve ----
|
// ---- DNS resolve ----
|
||||||
|
|||||||
+21
-21
@@ -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>
|
||||||
@@ -49,22 +49,20 @@ namespace montauk::abi {
|
|||||||
Kt::KernelLogStream(Kt::INFO, "Power") << "pid " << kcp::dec
|
Kt::KernelLogStream(Kt::INFO, "Power") << "pid " << kcp::dec
|
||||||
<< (uint64_t)Sched::GetCurrentPid()
|
<< (uint64_t)Sched::GetCurrentPid()
|
||||||
<< " posted graceful power request " << (uint64_t)action;
|
<< " posted graceful power request " << (uint64_t)action;
|
||||||
return 0;
|
return Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void Sys_Reset() {
|
static void Sys_Reset() {
|
||||||
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,20 +73,22 @@ 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() {
|
||||||
if (!Hal::AcpiSleep::IsS3Available()) {
|
if (!Hal::AcpiSleep::IsS3Available()) {
|
||||||
return -1; // S3 not supported
|
return UnsupportedOp; // S3 not supported
|
||||||
}
|
}
|
||||||
return (int64_t)Hal::AcpiSleep::Suspend();
|
return (int64_t)Hal::AcpiSleep::Suspend();
|
||||||
}
|
}
|
||||||
@@ -96,9 +96,9 @@ 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 -1;
|
return NoDevice;
|
||||||
}
|
}
|
||||||
|
|
||||||
out->hwpActive = s.HwpActive ? 1 : 0;
|
out->hwpActive = s.HwpActive ? 1 : 0;
|
||||||
@@ -112,8 +112,8 @@ 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 0;
|
return Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
// SYS_BATTERYINFO. Fills a battery snapshot. Returns -1 on a machine with
|
// SYS_BATTERYINFO. Fills a battery snapshot. Returns -1 on a machine with
|
||||||
@@ -122,7 +122,7 @@ namespace montauk::abi {
|
|||||||
static int64_t Sys_BatteryInfo(BatteryInfo* out) {
|
static int64_t Sys_BatteryInfo(BatteryInfo* out) {
|
||||||
Hal::Battery::Status s{};
|
Hal::Battery::Status s{};
|
||||||
if (!Hal::Battery::GetStatus(s)) {
|
if (!Hal::Battery::GetStatus(s)) {
|
||||||
return -1;
|
return NoDevice;
|
||||||
}
|
}
|
||||||
|
|
||||||
out->present = s.Present ? 1 : 0;
|
out->present = s.Present ? 1 : 0;
|
||||||
@@ -139,6 +139,6 @@ namespace montauk::abi {
|
|||||||
out->presentRate = s.PresentRate;
|
out->presentRate = s.PresentRate;
|
||||||
out->voltageMv = s.VoltageMv;
|
out->voltageMv = s.VoltageMv;
|
||||||
out->minutesRemaining = s.MinutesRemaining;
|
out->minutesRemaining = s.MinutesRemaining;
|
||||||
return 0;
|
return Success;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
+65
-30
@@ -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>
|
||||||
@@ -49,13 +49,13 @@ namespace montauk::abi {
|
|||||||
// stream, whether or not it also carries a capability grant. The child is
|
// stream, whether or not it also carries a capability grant. The child is
|
||||||
// created suspended (startReady == false) so its first instruction cannot
|
// created suspended (startReady == false) so its first instruction cannot
|
||||||
// run before the channels exist, and is started here once they do.
|
// run before the channels exist, and is started here once they do.
|
||||||
// Returns childPid, or kills the child and returns -1 on failure.
|
// Returns childPid, or kills the child and returns GenericFailure.
|
||||||
static int InheritRedirection(int childPid, Sched::Process* parent, int parentSlot) {
|
static int InheritRedirection(int childPid, Sched::Process* parent, int parentSlot) {
|
||||||
auto* child = Sched::GetProcessByPid(childPid);
|
auto* child = Sched::GetProcessByPid(childPid);
|
||||||
int childSlot = Ipc::SlotForPid(childPid);
|
int childSlot = Ipc::SlotForPid(childPid);
|
||||||
if (child == nullptr || childSlot < 0 || parentSlot < 0) {
|
if (child == nullptr || childSlot < 0 || parentSlot < 0) {
|
||||||
Sched::KillProcess(childPid);
|
Sched::KillProcess(childPid);
|
||||||
return -1;
|
return GenericFailure;
|
||||||
}
|
}
|
||||||
|
|
||||||
child->ioOutHandle = DuplicateHandleBetweenSlots(parentSlot, parent->ioOutHandle, childSlot);
|
child->ioOutHandle = DuplicateHandleBetweenSlots(parentSlot, parent->ioOutHandle, childSlot);
|
||||||
@@ -65,7 +65,7 @@ namespace montauk::abi {
|
|||||||
if (child->ioOutHandle < 0 || child->ioInHandle < 0 || child->ioKeyHandle < 0 ||
|
if (child->ioOutHandle < 0 || child->ioInHandle < 0 || child->ioKeyHandle < 0 ||
|
||||||
!ConfigureRedirWaitsetForSlot(childSlot, child)) {
|
!ConfigureRedirWaitsetForSlot(childSlot, child)) {
|
||||||
Sched::KillProcess(childPid);
|
Sched::KillProcess(childPid);
|
||||||
return -1;
|
return GenericFailure;
|
||||||
}
|
}
|
||||||
|
|
||||||
child->redirected = true;
|
child->redirected = true;
|
||||||
@@ -74,7 +74,7 @@ namespace montauk::abi {
|
|||||||
child->termRows = parent->termRows;
|
child->termRows = parent->termRows;
|
||||||
if (Sched::StartProcess(childPid) < 0) {
|
if (Sched::StartProcess(childPid) < 0) {
|
||||||
Sched::KillProcess(childPid);
|
Sched::KillProcess(childPid);
|
||||||
return -1;
|
return GenericFailure;
|
||||||
}
|
}
|
||||||
|
|
||||||
return childPid;
|
return childPid;
|
||||||
@@ -83,7 +83,7 @@ namespace montauk::abi {
|
|||||||
static int Sys_Spawn(const char* path, const char* args,
|
static int Sys_Spawn(const char* path, const char* args,
|
||||||
const char* environment = nullptr, uint32_t environmentLength = 0) {
|
const char* environment = nullptr, uint32_t environmentLength = 0) {
|
||||||
char resolved[256];
|
char resolved[256];
|
||||||
if (!ResolveProcessPath(path, resolved, sizeof(resolved))) return -1;
|
if (!ResolveProcessPath(path, resolved, sizeof(resolved))) return InvalidArgument;
|
||||||
|
|
||||||
auto* parent = Sched::GetCurrentProcessPtr();
|
auto* parent = Sched::GetCurrentProcessPtr();
|
||||||
int parentSlot = Ipc::CurrentSlot();
|
int parentSlot = Ipc::CurrentSlot();
|
||||||
@@ -101,7 +101,7 @@ namespace montauk::abi {
|
|||||||
static int Sys_SpawnCaps(const char* path, const char* args,
|
static int Sys_SpawnCaps(const char* path, const char* args,
|
||||||
const char* user, const SpawnCapabilities* requested) {
|
const char* user, const SpawnCapabilities* requested) {
|
||||||
auto* parent = Sched::GetCurrentProcessPtr();
|
auto* parent = Sched::GetCurrentProcessPtr();
|
||||||
if (parent == nullptr || requested == nullptr) return -1;
|
if (parent == nullptr || requested == nullptr) return InvalidArgument;
|
||||||
|
|
||||||
// Snapshot all security-sensitive userspace inputs before evaluating
|
// Snapshot all security-sensitive userspace inputs before evaluating
|
||||||
// them. This prevents another thread from changing a mask or owner
|
// them. This prevents another thread from changing a mask or owner
|
||||||
@@ -111,7 +111,7 @@ namespace montauk::abi {
|
|||||||
const char* userOverride = nullptr;
|
const char* userOverride = nullptr;
|
||||||
if (user != nullptr) {
|
if (user != nullptr) {
|
||||||
if (!Sched::HasCapability(CAP_USER_ADMIN))
|
if (!Sched::HasCapability(CAP_USER_ADMIN))
|
||||||
return SYS_ERR_PERMISSION;
|
return CapabilityDenied;
|
||||||
int i = 0;
|
int i = 0;
|
||||||
for (; i < 31 && user[i]; i++) childUser[i] = user[i];
|
for (; i < 31 && user[i]; i++) childUser[i] = user[i];
|
||||||
childUser[i] = '\0';
|
childUser[i] = '\0';
|
||||||
@@ -122,11 +122,11 @@ namespace montauk::abi {
|
|||||||
// possessing a capability is insufficient to pass it: the parent must
|
// possessing a capability is insufficient to pass it: the parent must
|
||||||
// also hold it in its delegable set.
|
// also hold it in its delegable set.
|
||||||
if (!ValidCapabilityDelegation(copy, parent->delegableCaps)) {
|
if (!ValidCapabilityDelegation(copy, parent->delegableCaps)) {
|
||||||
return SYS_ERR_PERMISSION;
|
return CapabilityDenied;
|
||||||
}
|
}
|
||||||
|
|
||||||
char resolved[256];
|
char resolved[256];
|
||||||
if (!ResolveProcessPath(path, resolved, sizeof(resolved))) return -1;
|
if (!ResolveProcessPath(path, resolved, sizeof(resolved))) return InvalidArgument;
|
||||||
|
|
||||||
int parentSlot = Ipc::CurrentSlot();
|
int parentSlot = Ipc::CurrentSlot();
|
||||||
bool inheritRedirection = parent->redirected;
|
bool inheritRedirection = parent->redirected;
|
||||||
@@ -140,10 +140,19 @@ namespace montauk::abi {
|
|||||||
return childPid;
|
return childPid;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The calling process's own capability masks.
|
||||||
|
static int Sys_GetCaps(SpawnCapabilities* out) {
|
||||||
|
auto* proc = Sched::GetCurrentProcessPtr();
|
||||||
|
if (proc == nullptr || out == nullptr) return InvalidArgument;
|
||||||
|
out->effective = proc->effectiveCaps;
|
||||||
|
out->delegable = proc->delegableCaps;
|
||||||
|
return Success;
|
||||||
|
}
|
||||||
|
|
||||||
// Copy the absolute path this process was spawned from (argv[0]).
|
// Copy the absolute path this process was spawned from (argv[0]).
|
||||||
static int Sys_GetExecPath(char* buf, uint64_t maxLen) {
|
static int Sys_GetExecPath(char* buf, uint64_t maxLen) {
|
||||||
auto* proc = Sched::GetCurrentProcessPtr();
|
auto* proc = Sched::GetCurrentProcessPtr();
|
||||||
if (proc == nullptr || buf == nullptr || maxLen == 0) return -1;
|
if (proc == nullptr || buf == nullptr || maxLen == 0) return InvalidArgument;
|
||||||
int i = 0;
|
int i = 0;
|
||||||
for (; i < (int)maxLen - 1 && proc->name[i]; i++) {
|
for (; i < (int)maxLen - 1 && proc->name[i]; i++) {
|
||||||
buf[i] = proc->name[i];
|
buf[i] = proc->name[i];
|
||||||
@@ -154,7 +163,7 @@ namespace montauk::abi {
|
|||||||
|
|
||||||
static int Sys_GetArgs(char* buf, uint64_t maxLen) {
|
static int Sys_GetArgs(char* buf, uint64_t maxLen) {
|
||||||
auto* proc = Sched::GetCurrentProcessPtr();
|
auto* proc = Sched::GetCurrentProcessPtr();
|
||||||
if (proc == nullptr || buf == nullptr || maxLen == 0) return -1;
|
if (proc == nullptr || buf == nullptr || maxLen == 0) return InvalidArgument;
|
||||||
int i = 0;
|
int i = 0;
|
||||||
for (; i < (int)maxLen - 1 && proc->args[i]; i++) {
|
for (; i < (int)maxLen - 1 && proc->args[i]; i++) {
|
||||||
buf[i] = proc->args[i];
|
buf[i] = proc->args[i];
|
||||||
@@ -165,8 +174,8 @@ namespace montauk::abi {
|
|||||||
|
|
||||||
static int Sys_GetEnviron(char* buf, uint64_t maxLen) {
|
static int Sys_GetEnviron(char* buf, uint64_t maxLen) {
|
||||||
auto* proc = Sched::GetCurrentProcessPtr();
|
auto* proc = Sched::GetCurrentProcessPtr();
|
||||||
if (proc == nullptr || buf == nullptr || maxLen == 0) return -1;
|
if (proc == nullptr || buf == nullptr || maxLen == 0) return InvalidArgument;
|
||||||
if (maxLen < proc->environmentLength) return -2;
|
if (maxLen < proc->environmentLength) return BufferTooSmall;
|
||||||
for (uint32_t i = 0; i < proc->environmentLength; i++)
|
for (uint32_t i = 0; i < proc->environmentLength; i++)
|
||||||
buf[i] = proc->environment[i];
|
buf[i] = proc->environment[i];
|
||||||
return (int)proc->environmentLength;
|
return (int)proc->environmentLength;
|
||||||
@@ -175,10 +184,10 @@ namespace montauk::abi {
|
|||||||
static int Sys_SetEnviron(const char* buf, uint64_t len) {
|
static int Sys_SetEnviron(const char* buf, uint64_t len) {
|
||||||
auto* proc = Sched::GetCurrentProcessPtr();
|
auto* proc = Sched::GetCurrentProcessPtr();
|
||||||
if (proc == nullptr || buf == nullptr || len == 0 ||
|
if (proc == nullptr || buf == nullptr || len == 0 ||
|
||||||
len > Sched::Process::EnvironmentBytes || buf[len - 1] != '\0') return -1;
|
len > Sched::Process::EnvironmentBytes || buf[len - 1] != '\0') return InvalidArgument;
|
||||||
for (uint64_t i = 0; i < len; i++) proc->environment[i] = buf[i];
|
for (uint64_t i = 0; i < len; i++) proc->environment[i] = buf[i];
|
||||||
proc->environmentLength = (uint32_t)len;
|
proc->environmentLength = (uint32_t)len;
|
||||||
return 0;
|
return Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int Sys_ProcList(ProcInfo* buf, int maxCount) {
|
static int Sys_ProcList(ProcInfo* buf, int maxCount) {
|
||||||
@@ -204,7 +213,6 @@ namespace montauk::abi {
|
|||||||
}
|
}
|
||||||
buf[count].heapUsed = Sched::g_allocatedPages[i] * 0x1000;
|
buf[count].heapUsed = Sched::g_allocatedPages[i] * 0x1000;
|
||||||
buf[count].cpuTimeMs = proc->cpuTimeMs;
|
buf[count].cpuTimeMs = proc->cpuTimeMs;
|
||||||
buf[count].permittedCaps = proc->permittedCaps;
|
|
||||||
buf[count].effectiveCaps = proc->effectiveCaps;
|
buf[count].effectiveCaps = proc->effectiveCaps;
|
||||||
buf[count].delegableCaps = proc->delegableCaps;
|
buf[count].delegableCaps = proc->delegableCaps;
|
||||||
count++;
|
count++;
|
||||||
@@ -212,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;
|
||||||
@@ -225,7 +252,7 @@ namespace montauk::abi {
|
|||||||
}
|
}
|
||||||
ancestor = target->parentPid;
|
ancestor = target->parentPid;
|
||||||
}
|
}
|
||||||
if (!descendant) return SYS_ERR_PERMISSION;
|
if (!descendant) return PermissionDenied;
|
||||||
}
|
}
|
||||||
return Sched::KillProcess(pid);
|
return Sched::KillProcess(pid);
|
||||||
}
|
}
|
||||||
@@ -239,19 +266,19 @@ namespace montauk::abi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static int Sys_SetUser(int pid, const char* name) {
|
static int Sys_SetUser(int pid, const char* name) {
|
||||||
if (name == nullptr) return -1;
|
if (name == nullptr) return InvalidArgument;
|
||||||
auto* target = Sched::GetProcessByPid(pid);
|
auto* target = Sched::GetProcessByPid(pid);
|
||||||
if (target == nullptr) return -1;
|
if (target == nullptr) return InvalidArgument;
|
||||||
int i = 0;
|
int i = 0;
|
||||||
for (; i < 31 && name[i]; i++)
|
for (; i < 31 && name[i]; i++)
|
||||||
target->user[i] = name[i];
|
target->user[i] = name[i];
|
||||||
target->user[i] = '\0';
|
target->user[i] = '\0';
|
||||||
return 0;
|
return Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int Sys_GetUser(char* buf, uint64_t maxLen) {
|
static int Sys_GetUser(char* buf, uint64_t maxLen) {
|
||||||
auto* proc = Sched::GetCurrentProcessPtr();
|
auto* proc = Sched::GetCurrentProcessPtr();
|
||||||
if (proc == nullptr || buf == nullptr || maxLen == 0) return -1;
|
if (proc == nullptr || buf == nullptr || maxLen == 0) return InvalidArgument;
|
||||||
int i = 0;
|
int i = 0;
|
||||||
for (; i < (int)maxLen - 1 && proc->user[i]; i++)
|
for (; i < (int)maxLen - 1 && proc->user[i]; i++)
|
||||||
buf[i] = proc->user[i];
|
buf[i] = proc->user[i];
|
||||||
@@ -261,15 +288,15 @@ namespace montauk::abi {
|
|||||||
|
|
||||||
static int Sys_GetCwd(char* buf, uint64_t maxLen) {
|
static int Sys_GetCwd(char* buf, uint64_t maxLen) {
|
||||||
auto* proc = Sched::GetCurrentProcessPtr();
|
auto* proc = Sched::GetCurrentProcessPtr();
|
||||||
if (proc == nullptr || buf == nullptr || maxLen == 0) return -1;
|
if (proc == nullptr || buf == nullptr || maxLen == 0) return InvalidArgument;
|
||||||
|
|
||||||
const char* cwd = proc->cwd[0] ? proc->cwd : "0:/";
|
const char* cwd = proc->cwd[0] ? proc->cwd : "0:/";
|
||||||
uint64_t cwdLen = 0;
|
uint64_t cwdLen = 0;
|
||||||
while (cwd[cwdLen] != '\0') cwdLen++;
|
while (cwd[cwdLen] != '\0') cwdLen++;
|
||||||
|
|
||||||
// Reserve -1 for non-size failures so libc can distinguish them
|
// BufferTooSmall tells libc the call can be retried with a larger
|
||||||
// from a buffer that can be retried with a larger allocation.
|
// allocation (ERANGE), unlike the other failures.
|
||||||
if (maxLen <= cwdLen) return -2;
|
if (maxLen <= cwdLen) return BufferTooSmall;
|
||||||
|
|
||||||
for (uint64_t i = 0; i <= cwdLen; i++) buf[i] = cwd[i];
|
for (uint64_t i = 0; i <= cwdLen; i++) buf[i] = cwd[i];
|
||||||
return (int)cwdLen;
|
return (int)cwdLen;
|
||||||
@@ -294,12 +321,20 @@ 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 -1;
|
if (proc == nullptr || path == nullptr) return InvalidArgument;
|
||||||
|
|
||||||
char resolved[256];
|
char resolved[256];
|
||||||
if (!ResolveProcessPath(path, resolved, sizeof(resolved))) return -1;
|
if (!ResolveProcessPath(path, resolved, sizeof(resolved))) return InvalidArgument;
|
||||||
|
|
||||||
// ReadDir doubles as the directory-existence probe: it fails
|
// ReadDir doubles as the directory-existence probe: it fails
|
||||||
// for nonexistent paths and for regular files (directories are
|
// for nonexistent paths and for regular files (directories are
|
||||||
@@ -307,12 +342,12 @@ namespace montauk::abi {
|
|||||||
// would reject every valid target).
|
// would reject every valid target).
|
||||||
{
|
{
|
||||||
const char* entries[1];
|
const char* entries[1];
|
||||||
if (Fs::Vfs::VfsReadDir(resolved, entries, 1) < 0) return -1;
|
if (Fs::Vfs::VfsReadDir(resolved, entries, 1) < 0) return NoSuchPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
int i = 0;
|
int i = 0;
|
||||||
for (; i < 255 && resolved[i]; i++) proc->cwd[i] = resolved[i];
|
for (; i < 255 && resolved[i]; i++) proc->cwd[i] = resolved[i];
|
||||||
proc->cwd[i] = '\0';
|
proc->cwd[i] = '\0';
|
||||||
return 0;
|
return Success;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -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;
|
||||||
|
|||||||
+104
-23
@@ -29,7 +29,7 @@ namespace montauk::abi {
|
|||||||
// Fill a user buffer with partition info. Returns the number of entries written.
|
// Fill a user buffer with partition info. Returns the number of entries written.
|
||||||
static int Sys_PartList(PartInfo* buf, int maxCount) {
|
static int Sys_PartList(PartInfo* buf, int maxCount) {
|
||||||
if (buf == nullptr || maxCount <= 0) return 0;
|
if (buf == nullptr || maxCount <= 0) return 0;
|
||||||
if (!UserMemory::Range((uint64_t)buf, (uint64_t)maxCount * sizeof(PartInfo), true)) return -1;
|
if (!UserMemory::Range((uint64_t)buf, (uint64_t)maxCount * sizeof(PartInfo), true)) return BadPointer;
|
||||||
|
|
||||||
int total = Drivers::Storage::Gpt::GetPartitionCount();
|
int total = Drivers::Storage::Gpt::GetPartitionCount();
|
||||||
int count = total < maxCount ? total : maxCount;
|
int count = total < maxCount ? total : maxCount;
|
||||||
@@ -57,47 +57,89 @@ namespace montauk::abi {
|
|||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read sectors from a block device. Returns bytes read, or -1 on error.
|
// Fill a user buffer with one entry per mounted volume. Returns the number
|
||||||
|
// of entries written, or BadPointer for a bad buffer.
|
||||||
|
static int Sys_VolInfo(VolInfo* buf, int maxCount) {
|
||||||
|
if (buf == nullptr || maxCount <= 0) return 0;
|
||||||
|
if (!UserMemory::Range((uint64_t)buf, (uint64_t)maxCount * sizeof(VolInfo), true)) return BadPointer;
|
||||||
|
|
||||||
|
int drives[Fs::Vfs::MaxDrives];
|
||||||
|
int n = Fs::Vfs::VfsDriveList(drives, Fs::Vfs::MaxDrives);
|
||||||
|
if (n > maxCount) n = maxCount;
|
||||||
|
|
||||||
|
for (int i = 0; i < n; i++) {
|
||||||
|
VolInfo& v = buf[i];
|
||||||
|
v = VolInfo{};
|
||||||
|
v.drive = drives[i];
|
||||||
|
v.blockDev = -1;
|
||||||
|
v.partIndex = -1;
|
||||||
|
|
||||||
|
Fs::Vfs::VfsDriveLabel(v.drive, v.label, sizeof(v.label));
|
||||||
|
Fs::Vfs::VfsDriveFsType(v.drive, v.fsType, sizeof(v.fsType));
|
||||||
|
|
||||||
|
v.partIndex = Fs::FsProbe::GetPartitionForDrive(v.drive);
|
||||||
|
if (v.partIndex < 0) continue; // ramdisk: no backing partition
|
||||||
|
|
||||||
|
auto* part = Drivers::Storage::Gpt::GetPartition(v.partIndex);
|
||||||
|
if (part == nullptr) continue;
|
||||||
|
|
||||||
|
v.blockDev = part->BlockDevIndex;
|
||||||
|
v.startLba = part->StartLba;
|
||||||
|
v.sectorCount = part->SectorCount;
|
||||||
|
dl_strcpy_s(v.partName, part->Name, sizeof(v.partName));
|
||||||
|
|
||||||
|
auto* dev = Drivers::Storage::GetBlockDevice(v.blockDev);
|
||||||
|
if (dev == nullptr) continue;
|
||||||
|
|
||||||
|
v.kind = dev->Kind;
|
||||||
|
v.sectorSize = dev->SectorSize;
|
||||||
|
dl_strcpy_s(v.model, dev->Model, sizeof(v.model));
|
||||||
|
}
|
||||||
|
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read sectors from a block device. Returns bytes read, or a status code.
|
||||||
static int64_t Sys_DiskRead(int blockDev, uint64_t lba, uint32_t count, void* buffer) {
|
static int64_t Sys_DiskRead(int blockDev, uint64_t lba, uint32_t count, void* buffer) {
|
||||||
if (buffer == nullptr || count == 0) return -1;
|
if (buffer == nullptr || count == 0) return InvalidArgument;
|
||||||
if (count > 128) return -1; // same limit as AHCI
|
if (count > 128) return InvalidArgument; // same limit as AHCI
|
||||||
|
|
||||||
auto* dev = Drivers::Storage::GetBlockDevice(blockDev);
|
auto* dev = Drivers::Storage::GetBlockDevice(blockDev);
|
||||||
if (!dev) return -1;
|
if (!dev) return NoDevice;
|
||||||
|
|
||||||
if (lba >= dev->SectorCount || count > dev->SectorCount - lba) return -1;
|
if (lba >= dev->SectorCount || count > dev->SectorCount - lba) return InvalidArgument;
|
||||||
if (!UserMemory::Range((uint64_t)buffer, (uint64_t)count * dev->SectorSize, true)) return -1;
|
if (!UserMemory::Range((uint64_t)buffer, (uint64_t)count * dev->SectorSize, true)) return BadPointer;
|
||||||
|
|
||||||
if (!dev->ReadSectors(dev->Ctx, lba, count, buffer)) return -1;
|
if (!dev->ReadSectors(dev->Ctx, lba, count, buffer)) return IoFailure;
|
||||||
|
|
||||||
return (int64_t)(count * dev->SectorSize);
|
return (int64_t)(count * dev->SectorSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write sectors to a block device. Returns bytes written, or -1 on error.
|
// Write sectors to a block device. Returns bytes written, or a status code.
|
||||||
static int64_t Sys_DiskWrite(int blockDev, uint64_t lba, uint32_t count, const void* buffer) {
|
static int64_t Sys_DiskWrite(int blockDev, uint64_t lba, uint32_t count, const void* buffer) {
|
||||||
if (buffer == nullptr || count == 0) return -1;
|
if (buffer == nullptr || count == 0) return InvalidArgument;
|
||||||
if (count > 128) return -1;
|
if (count > 128) return InvalidArgument;
|
||||||
|
|
||||||
auto* dev = Drivers::Storage::GetBlockDevice(blockDev);
|
auto* dev = Drivers::Storage::GetBlockDevice(blockDev);
|
||||||
if (!dev) return -1;
|
if (!dev) return NoDevice;
|
||||||
|
|
||||||
if (lba >= dev->SectorCount || count > dev->SectorCount - lba) return -1;
|
if (lba >= dev->SectorCount || count > dev->SectorCount - lba) return InvalidArgument;
|
||||||
if (!UserMemory::Range((uint64_t)buffer, (uint64_t)count * dev->SectorSize, false)) return -1;
|
if (!UserMemory::Range((uint64_t)buffer, (uint64_t)count * dev->SectorSize, false)) return BadPointer;
|
||||||
|
|
||||||
if (!dev->WriteSectors(dev->Ctx, lba, count, buffer)) return -1;
|
if (!dev->WriteSectors(dev->Ctx, lba, count, buffer)) return IoFailure;
|
||||||
|
|
||||||
return (int64_t)(count * dev->SectorSize);
|
return (int64_t)(count * dev->SectorSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize a new GPT on a block device. Returns 0 on success, -1 on error.
|
// Initialize a new GPT on a block device. Returns 0 on success, GenericFailure on error.
|
||||||
static int64_t Sys_GptInit(int blockDev) {
|
static int64_t Sys_GptInit(int blockDev) {
|
||||||
Fs::FsProbe::UnmountPartitionsForBlockDevice(blockDev);
|
Fs::FsProbe::UnmountPartitionsForBlockDevice(blockDev);
|
||||||
return (int64_t)Drivers::Storage::Gpt::InitializeGpt(blockDev);
|
return (int64_t)Drivers::Storage::Gpt::InitializeGpt(blockDev);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add a partition to an existing GPT. Returns partition index on success, -1 on error.
|
// Add a partition to an existing GPT. Returns partition index on success, GenericFailure on error.
|
||||||
static int64_t Sys_GptAdd(const GptAddParams* params) {
|
static int64_t Sys_GptAdd(const GptAddParams* params) {
|
||||||
if (params == nullptr) return -1;
|
if (params == nullptr) return InvalidArgument;
|
||||||
|
|
||||||
Drivers::Storage::Gpt::Guid typeGuid;
|
Drivers::Storage::Gpt::Guid typeGuid;
|
||||||
__builtin_memcpy(&typeGuid, ¶ms->typeGuid, sizeof(typeGuid));
|
__builtin_memcpy(&typeGuid, ¶ms->typeGuid, sizeof(typeGuid));
|
||||||
@@ -107,7 +149,7 @@ namespace montauk::abi {
|
|||||||
typeGuid, params->name);
|
typeGuid, params->name);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mount a partition as a VFS drive. Returns 0 on success, -1 on error.
|
// Mount a partition as a VFS drive. Returns 0 on success, GenericFailure on error.
|
||||||
static int64_t Sys_FsMount(int partIndex, int driveNum) {
|
static int64_t Sys_FsMount(int partIndex, int driveNum) {
|
||||||
return (int64_t)Fs::FsProbe::MountPartition(partIndex, driveNum);
|
return (int64_t)Fs::FsProbe::MountPartition(partIndex, driveNum);
|
||||||
}
|
}
|
||||||
@@ -121,12 +163,51 @@ namespace montauk::abi {
|
|||||||
return (int64_t)Fs::FsProbe::SyncAndUnmountAll();
|
return (int64_t)Fs::FsProbe::SyncAndUnmountAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Format a partition with a filesystem. Returns 0 on success, -1 on error.
|
// 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.
|
||||||
static int64_t Sys_FsFormat(const FsFormatParams* params) {
|
static int64_t Sys_FsFormat(const FsFormatParams* params) {
|
||||||
if (params == nullptr) return -1;
|
if (params == nullptr) return InvalidArgument;
|
||||||
|
|
||||||
auto* part = Drivers::Storage::Gpt::GetPartition(params->partIndex);
|
auto* part = Drivers::Storage::Gpt::GetPartition(params->partIndex);
|
||||||
if (!part) return -1;
|
if (!part) return InvalidArgument;
|
||||||
|
|
||||||
switch (params->fsType) {
|
switch (params->fsType) {
|
||||||
case FS_TYPE_FAT32:
|
case FS_TYPE_FAT32:
|
||||||
@@ -138,7 +219,7 @@ namespace montauk::abi {
|
|||||||
part->BlockDevIndex, part->StartLba, part->SectorCount,
|
part->BlockDevIndex, part->StartLba, part->SectorCount,
|
||||||
params->label[0] ? params->label : nullptr);
|
params->label[0] ? params->label : nullptr);
|
||||||
default:
|
default:
|
||||||
return -1; // unknown filesystem type
|
return UnsupportedOp; // unknown filesystem type
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+280
-212
@@ -6,18 +6,17 @@
|
|||||||
|
|
||||||
#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"
|
||||||
|
#include <Sched/Consent.hpp>
|
||||||
|
|
||||||
/* Syscall impl. includes */
|
/* Syscall impl. includes */
|
||||||
#include "Process.hpp" // SYS_EXIT, SYS_YIELD, SYS_SLEEP_MS, SYS_GETPID, SYS_WAITPID, SYS_SPAWN, SYS_GETARGS, SYS_PROCLIST, SYS_KILL
|
#include "Process.hpp" // SYS_EXIT, SYS_YIELD, SYS_SLEEP_MS, SYS_GETPID, SYS_WAITPID, SYS_SPAWN, SYS_GETARGS, SYS_PROCLIST, SYS_KILL
|
||||||
#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
|
||||||
@@ -29,8 +28,8 @@
|
|||||||
#include "MemInfo.hpp" // SYS_MEMSTATS
|
#include "MemInfo.hpp" // SYS_MEMSTATS
|
||||||
#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_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
|
||||||
@@ -38,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;
|
||||||
@@ -61,54 +57,54 @@ namespace montauk::abi {
|
|||||||
switch (frame->syscall_nr) {
|
switch (frame->syscall_nr) {
|
||||||
case SYS_EXIT: {
|
case SYS_EXIT: {
|
||||||
Sys_Exit((int)frame->arg1);
|
Sys_Exit((int)frame->arg1);
|
||||||
return 0;
|
return Success;
|
||||||
}
|
}
|
||||||
case SYS_YIELD:
|
case SYS_YIELD:
|
||||||
Sys_Yield();
|
Sys_Yield();
|
||||||
return 0;
|
return Success;
|
||||||
case SYS_SLEEP_MS:
|
case SYS_SLEEP_MS:
|
||||||
Sys_SleepMs(frame->arg1);
|
Sys_SleepMs(frame->arg1);
|
||||||
return 0;
|
return Success;
|
||||||
case SYS_GETPID:
|
case SYS_GETPID:
|
||||||
return (int64_t)Sys_GetPid();
|
return (int64_t)Sys_GetPid();
|
||||||
case SYS_PRINT:
|
case SYS_PRINT:
|
||||||
if (!UserMemory::String(frame->arg1, kMaxPrintableStringBytes)) return -1;
|
if (!UserMemory::String(frame->arg1, kMaxPrintableStringBytes)) return BadPointer;
|
||||||
Sys_Print((const char*)frame->arg1);
|
Sys_Print((const char*)frame->arg1);
|
||||||
return 0;
|
return Success;
|
||||||
case SYS_PUTCHAR:
|
case SYS_PUTCHAR:
|
||||||
Sys_Putchar((char)frame->arg1);
|
Sys_Putchar((char)frame->arg1);
|
||||||
return 0;
|
return Success;
|
||||||
case SYS_OPEN:
|
case SYS_OPEN:
|
||||||
if (!UserMemory::String(frame->arg1, kMaxPathBytes)) return -1;
|
if (!UserMemory::String(frame->arg1, kMaxPathBytes)) return BadPointer;
|
||||||
return (int64_t)Sys_Open((const char*)frame->arg1);
|
return (int64_t)Sys_Open((const char*)frame->arg1);
|
||||||
case SYS_READ:
|
case SYS_READ:
|
||||||
if (!UserMemory::Range(frame->arg2, frame->arg4, true)) return -1;
|
if (!UserMemory::Range(frame->arg2, frame->arg4, true)) return BadPointer;
|
||||||
return (int64_t)Sys_Read((int)frame->arg1, (uint8_t*)frame->arg2,
|
return (int64_t)Sys_Read((int)frame->arg1, (uint8_t*)frame->arg2,
|
||||||
frame->arg3, frame->arg4);
|
frame->arg3, frame->arg4);
|
||||||
case SYS_GETSIZE:
|
case SYS_GETSIZE:
|
||||||
return (int64_t)Sys_GetSize((int)frame->arg1);
|
return (int64_t)Sys_GetSize((int)frame->arg1);
|
||||||
case SYS_CLOSE:
|
case SYS_CLOSE:
|
||||||
Sys_Close((int)frame->arg1);
|
Sys_Close((int)frame->arg1);
|
||||||
return 0;
|
return Success;
|
||||||
case SYS_READDIR:
|
case SYS_READDIR:
|
||||||
if ((int64_t)frame->arg3 < 0) return -1;
|
if ((int64_t)frame->arg3 < 0) return InvalidArgument;
|
||||||
if (!UserMemory::String(frame->arg1, kMaxPathBytes)) return -1;
|
if (!UserMemory::String(frame->arg1, kMaxPathBytes)) return BadPointer;
|
||||||
if (!UserMemory::Range(frame->arg2,
|
if (!UserMemory::Range(frame->arg2,
|
||||||
(uint64_t)((frame->arg3 > 256) ? 256 : frame->arg3) * sizeof(const char*),
|
(uint64_t)((frame->arg3 > 256) ? 256 : frame->arg3) * sizeof(const char*),
|
||||||
true)) {
|
true)) {
|
||||||
return -1;
|
return BadPointer;
|
||||||
}
|
}
|
||||||
return (int64_t)Sys_ReadDir((const char*)frame->arg1,
|
return (int64_t)Sys_ReadDir((const char*)frame->arg1,
|
||||||
(const char**)frame->arg2,
|
(const char**)frame->arg2,
|
||||||
(int)frame->arg3);
|
(int)frame->arg3);
|
||||||
case SYS_READDIR_AT:
|
case SYS_READDIR_AT:
|
||||||
if ((int64_t)frame->arg3 < 0) return -1;
|
if ((int64_t)frame->arg3 < 0) return InvalidArgument;
|
||||||
if ((int64_t)frame->arg4 < 0) return -1;
|
if ((int64_t)frame->arg4 < 0) return InvalidArgument;
|
||||||
if (!UserMemory::String(frame->arg1, kMaxPathBytes)) return -1;
|
if (!UserMemory::String(frame->arg1, kMaxPathBytes)) return BadPointer;
|
||||||
if (!UserMemory::Range(frame->arg2,
|
if (!UserMemory::Range(frame->arg2,
|
||||||
(uint64_t)((frame->arg3 > 256) ? 256 : frame->arg3) * sizeof(const char*),
|
(uint64_t)((frame->arg3 > 256) ? 256 : frame->arg3) * sizeof(const char*),
|
||||||
true)) {
|
true)) {
|
||||||
return -1;
|
return BadPointer;
|
||||||
}
|
}
|
||||||
return (int64_t)Sys_ReadDirAt((const char*)frame->arg1,
|
return (int64_t)Sys_ReadDirAt((const char*)frame->arg1,
|
||||||
(const char**)frame->arg2,
|
(const char**)frame->arg2,
|
||||||
@@ -120,7 +116,7 @@ namespace montauk::abi {
|
|||||||
return (int64_t)Sys_AllocEager(frame->arg1);
|
return (int64_t)Sys_AllocEager(frame->arg1);
|
||||||
case SYS_FREE:
|
case SYS_FREE:
|
||||||
Sys_Free(frame->arg1);
|
Sys_Free(frame->arg1);
|
||||||
return 0;
|
return Success;
|
||||||
case SYS_MMAP_ANON:
|
case SYS_MMAP_ANON:
|
||||||
return (int64_t)Sys_MapAnonymous(frame->arg1, frame->arg2);
|
return (int64_t)Sys_MapAnonymous(frame->arg1, frame->arg2);
|
||||||
case SYS_MUNMAP:
|
case SYS_MUNMAP:
|
||||||
@@ -132,15 +128,15 @@ namespace montauk::abi {
|
|||||||
case SYS_GETMILLISECONDS:
|
case SYS_GETMILLISECONDS:
|
||||||
return (int64_t)Sys_GetMilliseconds();
|
return (int64_t)Sys_GetMilliseconds();
|
||||||
case SYS_GETINFO:
|
case SYS_GETINFO:
|
||||||
if (!UserMemory::Writable<SysInfo>(frame->arg1)) return -1;
|
if (!UserMemory::Writable<SysInfo>(frame->arg1)) return BadPointer;
|
||||||
Sys_GetInfo((SysInfo*)frame->arg1);
|
Sys_GetInfo((SysInfo*)frame->arg1);
|
||||||
return 0;
|
return Success;
|
||||||
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 -1;
|
if (!UserMemory::Writable<KeyEventV1>(frame->arg1)) return BadPointer;
|
||||||
Sys_GetKey((KeyEvent*)frame->arg1);
|
Sys_GetKey((KeyEventV1*)frame->arg1);
|
||||||
return 0;
|
return Success;
|
||||||
case SYS_GETCHAR:
|
case SYS_GETCHAR:
|
||||||
return (int64_t)Sys_GetChar();
|
return (int64_t)Sys_GetChar();
|
||||||
case SYS_GETCHAR_NB:
|
case SYS_GETCHAR_NB:
|
||||||
@@ -148,73 +144,75 @@ namespace montauk::abi {
|
|||||||
case SYS_PING:
|
case SYS_PING:
|
||||||
return (int64_t)Sys_Ping((uint32_t)frame->arg1, (uint32_t)frame->arg2);
|
return (int64_t)Sys_Ping((uint32_t)frame->arg1, (uint32_t)frame->arg2);
|
||||||
case SYS_SPAWN:
|
case SYS_SPAWN:
|
||||||
if (!UserMemory::String(frame->arg1, kMaxPathBytes)) return -1;
|
if (!UserMemory::String(frame->arg1, kMaxPathBytes)) return BadPointer;
|
||||||
if (frame->arg2 != 0 && !UserMemory::String(frame->arg2, kMaxArgsBytes)) return -1;
|
if (frame->arg2 != 0 && !UserMemory::String(frame->arg2, kMaxArgsBytes)) return BadPointer;
|
||||||
return (int64_t)Sys_Spawn((const char*)frame->arg1,
|
return (int64_t)Sys_Spawn((const char*)frame->arg1,
|
||||||
UserMemory::IsUserPtr(frame->arg2) ? (const char*)frame->arg2 : nullptr);
|
UserMemory::IsUserPtr(frame->arg2) ? (const char*)frame->arg2 : nullptr);
|
||||||
case SYS_SPAWN_CAPS:
|
case SYS_SPAWN_CAPS:
|
||||||
if (!UserMemory::String(frame->arg1, kMaxPathBytes)) return -1;
|
if (!UserMemory::String(frame->arg1, kMaxPathBytes)) return BadPointer;
|
||||||
if (frame->arg2 != 0 && !UserMemory::String(frame->arg2, kMaxArgsBytes)) return -1;
|
if (frame->arg2 != 0 && !UserMemory::String(frame->arg2, kMaxArgsBytes)) return BadPointer;
|
||||||
if (frame->arg3 != 0 && !UserMemory::String(frame->arg3, 32)) return -1;
|
if (frame->arg3 != 0 && !UserMemory::String(frame->arg3, 32)) return BadPointer;
|
||||||
if (!UserMemory::Readable<SpawnCapabilities>(frame->arg4)) return -1;
|
if (!UserMemory::Readable<SpawnCapabilities>(frame->arg4)) return BadPointer;
|
||||||
return (int64_t)Sys_SpawnCaps(
|
return (int64_t)Sys_SpawnCaps(
|
||||||
(const char*)frame->arg1,
|
(const char*)frame->arg1,
|
||||||
frame->arg2 ? (const char*)frame->arg2 : nullptr,
|
frame->arg2 ? (const char*)frame->arg2 : nullptr,
|
||||||
frame->arg3 ? (const char*)frame->arg3 : nullptr,
|
frame->arg3 ? (const char*)frame->arg3 : nullptr,
|
||||||
(const SpawnCapabilities*)frame->arg4);
|
(const SpawnCapabilities*)frame->arg4);
|
||||||
case SYS_SPAWN_ENV:
|
case SYS_SPAWN_ENV:
|
||||||
if (!UserMemory::String(frame->arg1, kMaxPathBytes)) return -1;
|
if (!UserMemory::String(frame->arg1, kMaxPathBytes)) return BadPointer;
|
||||||
if (frame->arg2 != 0 && !UserMemory::String(frame->arg2, kMaxArgsBytes)) return -1;
|
if (frame->arg2 != 0 && !UserMemory::String(frame->arg2, kMaxArgsBytes)) return BadPointer;
|
||||||
if (frame->arg3 == 0 || frame->arg4 == 0 ||
|
if (frame->arg4 == 0 || frame->arg4 > Sched::Process::EnvironmentBytes)
|
||||||
frame->arg4 > Sched::Process::EnvironmentBytes ||
|
return InvalidArgument;
|
||||||
!UserMemory::Range(frame->arg3, frame->arg4, false) ||
|
if (frame->arg3 == 0 || !UserMemory::Range(frame->arg3, frame->arg4, false))
|
||||||
((const char*)frame->arg3)[frame->arg4 - 1] != '\0') return -1;
|
return BadPointer;
|
||||||
|
if (((const char*)frame->arg3)[frame->arg4 - 1] != '\0') return InvalidArgument;
|
||||||
return (int64_t)Sys_Spawn((const char*)frame->arg1,
|
return (int64_t)Sys_Spawn((const char*)frame->arg1,
|
||||||
UserMemory::IsUserPtr(frame->arg2) ? (const char*)frame->arg2 : nullptr,
|
UserMemory::IsUserPtr(frame->arg2) ? (const char*)frame->arg2 : nullptr,
|
||||||
(const char*)frame->arg3, (uint32_t)frame->arg4);
|
(const char*)frame->arg3, (uint32_t)frame->arg4);
|
||||||
case SYS_WAITPID:
|
case SYS_WAITPID:
|
||||||
return Sys_WaitPid((int)frame->arg1);
|
return Sys_WaitPid((int)frame->arg1);
|
||||||
case SYS_GETENVIRON:
|
case SYS_GETENVIRON:
|
||||||
if (!UserMemory::Range(frame->arg1, frame->arg2, true)) return -1;
|
if (!UserMemory::Range(frame->arg1, frame->arg2, true)) return BadPointer;
|
||||||
return Sys_GetEnviron((char*)frame->arg1, frame->arg2);
|
return Sys_GetEnviron((char*)frame->arg1, frame->arg2);
|
||||||
case SYS_SETENVIRON:
|
case SYS_SETENVIRON:
|
||||||
if (frame->arg2 == 0 || frame->arg2 > Sched::Process::EnvironmentBytes ||
|
if (frame->arg2 == 0 || frame->arg2 > Sched::Process::EnvironmentBytes)
|
||||||
!UserMemory::Range(frame->arg1, frame->arg2, false)) return -1;
|
return InvalidArgument;
|
||||||
|
if (!UserMemory::Range(frame->arg1, frame->arg2, false)) return BadPointer;
|
||||||
return Sys_SetEnviron((const char*)frame->arg1, frame->arg2);
|
return Sys_SetEnviron((const char*)frame->arg1, frame->arg2);
|
||||||
case SYS_FBINFO:
|
case SYS_FBINFO:
|
||||||
if (!UserMemory::Writable<FbInfo>(frame->arg1)) return -1;
|
if (!UserMemory::Writable<FbInfo>(frame->arg1)) return BadPointer;
|
||||||
Sys_FbInfo((FbInfo*)frame->arg1);
|
Sys_FbInfo((FbInfo*)frame->arg1);
|
||||||
return 0;
|
return Success;
|
||||||
case SYS_FBMAP:
|
case SYS_FBMAP:
|
||||||
return (int64_t)Sys_FbMap();
|
return (int64_t)Sys_FbMap();
|
||||||
case SYS_FBFLIP:
|
case SYS_FBFLIP:
|
||||||
return Sys_FbFlip(frame->arg1, frame->arg2);
|
return Sys_FbFlip(frame->arg1, frame->arg2);
|
||||||
case SYS_DISPLAYINFO:
|
case SYS_DISPLAYINFO:
|
||||||
if (!UserMemory::Writable<DisplayInfo>(frame->arg1)) return -1;
|
if (!UserMemory::Writable<DisplayInfo>(frame->arg1)) return BadPointer;
|
||||||
return Sys_DisplayInfo((DisplayInfo*)frame->arg1);
|
return Sys_DisplayInfo((DisplayInfo*)frame->arg1);
|
||||||
case SYS_DISPLAYMODES:
|
case SYS_DISPLAYMODES:
|
||||||
// The Intel driver publishes at most 32 modes. Bounding this
|
// The Intel driver publishes at most 32 modes. Bounding this
|
||||||
// here also keeps the userspace range calculation overflow-free.
|
// here also keeps the userspace range calculation overflow-free.
|
||||||
if ((int64_t)frame->arg2 < 0 || frame->arg2 > 32) return -1;
|
if ((int64_t)frame->arg2 < 0 || frame->arg2 > 32) return InvalidArgument;
|
||||||
if (!UserMemory::Range(frame->arg1,
|
if (!UserMemory::Range(frame->arg1,
|
||||||
(uint64_t)frame->arg2 * sizeof(DisplayModeInfo), true)) return -1;
|
(uint64_t)frame->arg2 * sizeof(DisplayModeInfo), true)) return BadPointer;
|
||||||
return Sys_DisplayModes((DisplayModeInfo*)frame->arg1, (int)frame->arg2);
|
return Sys_DisplayModes((DisplayModeInfo*)frame->arg1, (int)frame->arg2);
|
||||||
case SYS_DISPLAYSETMODE:
|
case SYS_DISPLAYSETMODE:
|
||||||
if (!Sched::HasCapability(CAP_DISPLAY_ADMIN)) return SYS_ERR_PERMISSION;
|
if (!Sched::Consent::Require(CAP_DISPLAY_ADMIN)) return CapabilityDenied;
|
||||||
return Sys_DisplaySetMode((int)frame->arg1);
|
return Sys_DisplaySetMode((int)frame->arg1);
|
||||||
case SYS_DISPLAYBRIGHTNESS:
|
case SYS_DISPLAYBRIGHTNESS:
|
||||||
if ((int64_t)frame->arg1 >= 0 &&
|
if ((int64_t)frame->arg1 >= 0 &&
|
||||||
!Sched::HasCapability(CAP_DISPLAY_ADMIN)) return SYS_ERR_PERMISSION;
|
!Sched::Consent::Require(CAP_BRIGHTNESS)) return CapabilityDenied;
|
||||||
return Sys_DisplayBrightness((int)frame->arg1);
|
return Sys_DisplayBrightness((int)frame->arg1);
|
||||||
case SYS_GETEXECPATH:
|
case SYS_GETEXECPATH:
|
||||||
if (!UserMemory::Range(frame->arg2 ? frame->arg1 : frame->arg1, frame->arg2, true)) return -1;
|
if (!UserMemory::Range(frame->arg2 ? frame->arg1 : frame->arg1, frame->arg2, true)) return BadPointer;
|
||||||
return Sys_GetExecPath((char*)frame->arg1, frame->arg2);
|
return Sys_GetExecPath((char*)frame->arg1, frame->arg2);
|
||||||
case SYS_STAT:
|
case SYS_STAT:
|
||||||
if (!UserMemory::String(frame->arg1, kMaxPathBytes)) return -1;
|
if (!UserMemory::String(frame->arg1, kMaxPathBytes)) return BadPointer;
|
||||||
if (!UserMemory::Writable<FileStat>(frame->arg2)) return -1;
|
if (!UserMemory::Writable<FileStat>(frame->arg2)) return BadPointer;
|
||||||
return (int64_t)Sys_Stat((const char*)frame->arg1, (FileStat*)frame->arg2);
|
return (int64_t)Sys_Stat((const char*)frame->arg1, (FileStat*)frame->arg2);
|
||||||
case SYS_UTIME:
|
case SYS_UTIME:
|
||||||
if (!UserMemory::String(frame->arg1, kMaxPathBytes)) return -1;
|
if (!UserMemory::String(frame->arg1, kMaxPathBytes)) return BadPointer;
|
||||||
return (int64_t)Sys_Utime((const char*)frame->arg1,
|
return (int64_t)Sys_Utime((const char*)frame->arg1,
|
||||||
(int64_t)frame->arg2,
|
(int64_t)frame->arg2,
|
||||||
(int64_t)frame->arg3,
|
(int64_t)frame->arg3,
|
||||||
@@ -222,33 +220,35 @@ namespace montauk::abi {
|
|||||||
case SYS_TERMSIZE:
|
case SYS_TERMSIZE:
|
||||||
return (int64_t)Sys_TermSize();
|
return (int64_t)Sys_TermSize();
|
||||||
case SYS_GETARGS:
|
case SYS_GETARGS:
|
||||||
if (!UserMemory::Range(frame->arg1, frame->arg2, true)) return -1;
|
if (!UserMemory::Range(frame->arg1, frame->arg2, true)) return BadPointer;
|
||||||
return (int64_t)Sys_GetArgs((char*)frame->arg1, frame->arg2);
|
return (int64_t)Sys_GetArgs((char*)frame->arg1, frame->arg2);
|
||||||
case SYS_RESET:
|
case SYS_RESET:
|
||||||
if (!Sched::HasCapability(CAP_POWER_CONTROL)) return SYS_ERR_PERMISSION;
|
if (!Sched::HasCapability(CAP_POWER_CONTROL)) return CapabilityDenied;
|
||||||
Sys_Reset();
|
Sys_Reset();
|
||||||
return 0;
|
return Success;
|
||||||
case SYS_SHUTDOWN:
|
case SYS_SHUTDOWN:
|
||||||
if (!Sched::HasCapability(CAP_POWER_CONTROL)) return SYS_ERR_PERMISSION;
|
if (!Sched::HasCapability(CAP_POWER_CONTROL)) return CapabilityDenied;
|
||||||
Sys_Shutdown();
|
Sys_Shutdown();
|
||||||
return 0;
|
return Success;
|
||||||
case SYS_POWER_REQUEST:
|
case SYS_POWER_REQUEST:
|
||||||
if (frame->arg1 != POWER_REQ_QUERY &&
|
if (frame->arg1 != POWER_REQ_QUERY &&
|
||||||
frame->arg1 != POWER_REQ_SHUTDOWN &&
|
frame->arg1 != POWER_REQ_SHUTDOWN &&
|
||||||
frame->arg1 != POWER_REQ_REBOOT &&
|
frame->arg1 != POWER_REQ_REBOOT &&
|
||||||
frame->arg1 != POWER_REQ_PEEK) return -1;
|
frame->arg1 != POWER_REQ_PEEK) return InvalidArgument;
|
||||||
if (frame->arg1 == POWER_REQ_QUERY) {
|
if (frame->arg1 == POWER_REQ_QUERY) {
|
||||||
if (!Sched::HasCapability(CAP_POWER_CONTROL)) return SYS_ERR_PERMISSION;
|
if (!Sched::HasCapability(CAP_POWER_CONTROL)) return CapabilityDenied;
|
||||||
} else if (!Sched::HasCapability(CAP_POWER_REQUEST)) {
|
} else if (!Sched::HasCapability(CAP_POWER_REQUEST)) {
|
||||||
return SYS_ERR_PERMISSION;
|
return CapabilityDenied;
|
||||||
}
|
}
|
||||||
return Sys_PowerRequest((int)frame->arg1);
|
return Sys_PowerRequest((int)frame->arg1);
|
||||||
case SYS_GETTIME:
|
case SYS_GETTIME:
|
||||||
if (!UserMemory::Writable<DateTime>(frame->arg1)) return -1;
|
if (!UserMemory::Writable<DateTime>(frame->arg1)) return BadPointer;
|
||||||
Sys_GetTime((DateTime*)frame->arg1);
|
Sys_GetTime((DateTime*)frame->arg1);
|
||||||
return 0;
|
return Success;
|
||||||
|
case SYS_GETUNIXTIME:
|
||||||
|
return Sys_GetUnixTime();
|
||||||
case SYS_SETUNIXTIME:
|
case SYS_SETUNIXTIME:
|
||||||
if (!Sched::HasCapability(CAP_SET_TIME)) return SYS_ERR_PERMISSION;
|
if (!Sched::Consent::Require(CAP_SET_TIME)) return CapabilityDenied;
|
||||||
return Sys_SetUnixTime((int64_t)frame->arg1);
|
return Sys_SetUnixTime((int64_t)frame->arg1);
|
||||||
case SYS_SOCKET:
|
case SYS_SOCKET:
|
||||||
return (int64_t)Sys_Socket((int)frame->arg1);
|
return (int64_t)Sys_Socket((int)frame->arg1);
|
||||||
@@ -261,114 +261,114 @@ namespace montauk::abi {
|
|||||||
case SYS_ACCEPT:
|
case SYS_ACCEPT:
|
||||||
return (int64_t)Sys_Accept((int)frame->arg1);
|
return (int64_t)Sys_Accept((int)frame->arg1);
|
||||||
case SYS_SEND:
|
case SYS_SEND:
|
||||||
if (!UserMemory::Range(frame->arg2, frame->arg3, false)) return -1;
|
if (!UserMemory::Range(frame->arg2, frame->arg3, false)) return BadPointer;
|
||||||
return (int64_t)Sys_Send((int)frame->arg1, (const uint8_t*)frame->arg2, (uint32_t)frame->arg3);
|
return (int64_t)Sys_Send((int)frame->arg1, (const uint8_t*)frame->arg2, (uint32_t)frame->arg3);
|
||||||
case SYS_RECV:
|
case SYS_RECV:
|
||||||
if (!UserMemory::Range(frame->arg2, frame->arg3, true)) return -1;
|
if (!UserMemory::Range(frame->arg2, frame->arg3, true)) return BadPointer;
|
||||||
return (int64_t)Sys_Recv((int)frame->arg1, (uint8_t*)frame->arg2, (uint32_t)frame->arg3);
|
return (int64_t)Sys_Recv((int)frame->arg1, (uint8_t*)frame->arg2, (uint32_t)frame->arg3);
|
||||||
case SYS_CLOSESOCK:
|
case SYS_CLOSESOCK:
|
||||||
Sys_CloseSock((int)frame->arg1);
|
Sys_CloseSock((int)frame->arg1);
|
||||||
return 0;
|
return Success;
|
||||||
case SYS_GETNETCFG:
|
case SYS_GETNETCFG:
|
||||||
if (!UserMemory::Writable<NetCfg>(frame->arg1)) return -1;
|
if (!UserMemory::Writable<NetCfg>(frame->arg1)) return BadPointer;
|
||||||
Sys_GetNetCfg((NetCfg*)frame->arg1);
|
Sys_GetNetCfg((NetCfg*)frame->arg1);
|
||||||
return 0;
|
return Success;
|
||||||
case SYS_SETNETCFG:
|
case SYS_SETNETCFG:
|
||||||
if (!Sched::HasCapability(CAP_NETWORK_ADMIN)) return SYS_ERR_PERMISSION;
|
if (!Sched::Consent::Require(CAP_NETWORK_ADMIN)) return CapabilityDenied;
|
||||||
if (!UserMemory::Readable<NetCfg>(frame->arg1)) return -1;
|
if (!UserMemory::Readable<NetCfg>(frame->arg1)) return BadPointer;
|
||||||
return (int64_t)Sys_SetNetCfg((const NetCfg*)frame->arg1);
|
return (int64_t)Sys_SetNetCfg((const NetCfg*)frame->arg1);
|
||||||
case SYS_NETSTATUS:
|
case SYS_NETSTATUS:
|
||||||
if (!UserMemory::Writable<NetStatus>(frame->arg1)) return -1;
|
if (!UserMemory::Writable<NetStatus>(frame->arg1)) return BadPointer;
|
||||||
Sys_NetStatus((NetStatus*)frame->arg1);
|
Sys_NetStatus((NetStatus*)frame->arg1);
|
||||||
return 0;
|
return Success;
|
||||||
case SYS_SENDTO:
|
case SYS_SENDTO:
|
||||||
if (!UserMemory::Range(frame->arg2, frame->arg3, false)) return -1;
|
if (!UserMemory::Range(frame->arg2, frame->arg3, false)) return BadPointer;
|
||||||
return (int64_t)Sys_SendTo((int)frame->arg1, (const uint8_t*)frame->arg2,
|
return (int64_t)Sys_SendTo((int)frame->arg1, (const uint8_t*)frame->arg2,
|
||||||
(uint32_t)frame->arg3, (uint32_t)frame->arg4,
|
(uint32_t)frame->arg3, (uint32_t)frame->arg4,
|
||||||
(uint16_t)frame->arg5);
|
(uint16_t)frame->arg5);
|
||||||
case SYS_RECVFROM:
|
case SYS_RECVFROM:
|
||||||
if (!UserMemory::Range(frame->arg2, frame->arg3, true)) return -1;
|
if (!UserMemory::Range(frame->arg2, frame->arg3, true)) return BadPointer;
|
||||||
if (!UserMemory::OptionalRange(frame->arg4, sizeof(uint32_t), true)) return -1;
|
if (!UserMemory::OptionalRange(frame->arg4, sizeof(uint32_t), true)) return BadPointer;
|
||||||
if (!UserMemory::OptionalRange(frame->arg5, sizeof(uint16_t), true)) return -1;
|
if (!UserMemory::OptionalRange(frame->arg5, sizeof(uint16_t), true)) return BadPointer;
|
||||||
return (int64_t)Sys_RecvFrom((int)frame->arg1, (uint8_t*)frame->arg2,
|
return (int64_t)Sys_RecvFrom((int)frame->arg1, (uint8_t*)frame->arg2,
|
||||||
(uint32_t)frame->arg3,
|
(uint32_t)frame->arg3,
|
||||||
UserMemory::IsUserPtr(frame->arg4) ? (uint32_t*)frame->arg4 : nullptr,
|
UserMemory::IsUserPtr(frame->arg4) ? (uint32_t*)frame->arg4 : nullptr,
|
||||||
UserMemory::IsUserPtr(frame->arg5) ? (uint16_t*)frame->arg5 : nullptr);
|
UserMemory::IsUserPtr(frame->arg5) ? (uint16_t*)frame->arg5 : nullptr);
|
||||||
case SYS_FWRITE:
|
case SYS_FWRITE:
|
||||||
if (!UserMemory::Range(frame->arg2, frame->arg4, false)) return -1;
|
if (!UserMemory::Range(frame->arg2, frame->arg4, false)) return BadPointer;
|
||||||
return (int64_t)Sys_FWrite((int)frame->arg1, (const uint8_t*)frame->arg2,
|
return (int64_t)Sys_FWrite((int)frame->arg1, (const uint8_t*)frame->arg2,
|
||||||
frame->arg3, frame->arg4);
|
frame->arg3, frame->arg4);
|
||||||
case SYS_FCREATE:
|
case SYS_FCREATE:
|
||||||
if (!UserMemory::String(frame->arg1, kMaxPathBytes)) return -1;
|
if (!UserMemory::String(frame->arg1, kMaxPathBytes)) return BadPointer;
|
||||||
return (int64_t)Sys_FCreate((const char*)frame->arg1);
|
return (int64_t)Sys_FCreate((const char*)frame->arg1);
|
||||||
case SYS_FDELETE:
|
case SYS_FDELETE:
|
||||||
if (!UserMemory::String(frame->arg1, kMaxPathBytes)) return -1;
|
if (!UserMemory::String(frame->arg1, kMaxPathBytes)) return BadPointer;
|
||||||
return (int64_t)Sys_FDelete((const char*)frame->arg1);
|
return (int64_t)Sys_FDelete((const char*)frame->arg1);
|
||||||
case SYS_FMKDIR:
|
case SYS_FMKDIR:
|
||||||
if (!UserMemory::String(frame->arg1, kMaxPathBytes)) return -1;
|
if (!UserMemory::String(frame->arg1, kMaxPathBytes)) return BadPointer;
|
||||||
return (int64_t)Sys_FMkdir((const char*)frame->arg1);
|
return (int64_t)Sys_FMkdir((const char*)frame->arg1);
|
||||||
case SYS_FRENAME:
|
case SYS_FRENAME:
|
||||||
if (!UserMemory::String(frame->arg1, kMaxPathBytes)) return -1;
|
if (!UserMemory::String(frame->arg1, kMaxPathBytes)) return BadPointer;
|
||||||
if (!UserMemory::String(frame->arg2, kMaxPathBytes)) return -1;
|
if (!UserMemory::String(frame->arg2, kMaxPathBytes)) return BadPointer;
|
||||||
return (int64_t)Sys_FRename((const char*)frame->arg1, (const char*)frame->arg2);
|
return (int64_t)Sys_FRename((const char*)frame->arg1, (const char*)frame->arg2);
|
||||||
case SYS_DRIVELIST:
|
case SYS_DRIVELIST:
|
||||||
if ((int64_t)frame->arg2 < 0) return -1;
|
if ((int64_t)frame->arg2 < 0) return InvalidArgument;
|
||||||
if (!UserMemory::Range(frame->arg1, (uint64_t)frame->arg2 * sizeof(int), true)) return -1;
|
if (!UserMemory::Range(frame->arg1, (uint64_t)frame->arg2 * sizeof(int), true)) return BadPointer;
|
||||||
return (int64_t)Sys_DriveList((int*)frame->arg1, (int)frame->arg2);
|
return (int64_t)Sys_DriveList((int*)frame->arg1, (int)frame->arg2);
|
||||||
case SYS_DRIVELABEL:
|
case SYS_DRIVELABEL:
|
||||||
if ((int64_t)frame->arg3 <= 0) return -1;
|
if ((int64_t)frame->arg3 <= 0) return InvalidArgument;
|
||||||
if (!UserMemory::Range(frame->arg2, frame->arg3, true)) return -1;
|
if (!UserMemory::Range(frame->arg2, frame->arg3, true)) return BadPointer;
|
||||||
return (int64_t)Sys_DriveLabel((int)frame->arg1, (char*)frame->arg2, (int)frame->arg3);
|
return (int64_t)Sys_DriveLabel((int)frame->arg1, (char*)frame->arg2, (int)frame->arg3);
|
||||||
case SYS_DRIVEKIND:
|
case SYS_DRIVEKIND:
|
||||||
return (int64_t)Sys_DriveKind((int)frame->arg1);
|
return (int64_t)Sys_DriveKind((int)frame->arg1);
|
||||||
case SYS_TERMSCALE:
|
case SYS_TERMSCALE:
|
||||||
return Sys_TermScale(frame->arg1, frame->arg2);
|
return Sys_TermScale(frame->arg1, frame->arg2);
|
||||||
case SYS_RESOLVE:
|
case SYS_RESOLVE:
|
||||||
if (!UserMemory::String(frame->arg1, kMaxHostnameBytes)) return -1;
|
if (!UserMemory::String(frame->arg1, kMaxHostnameBytes)) return BadPointer;
|
||||||
return Sys_Resolve((const char*)frame->arg1);
|
return Sys_Resolve((const char*)frame->arg1);
|
||||||
case SYS_GETRANDOM:
|
case SYS_GETRANDOM:
|
||||||
if (!UserMemory::Range(frame->arg1, frame->arg2, true)) return -1;
|
if (!UserMemory::Range(frame->arg1, frame->arg2, true)) return BadPointer;
|
||||||
return Sys_GetRandom((uint8_t*)frame->arg1, frame->arg2);
|
return Sys_GetRandom((uint8_t*)frame->arg1, frame->arg2);
|
||||||
case SYS_LOG:
|
case SYS_LOG:
|
||||||
if (!Sched::HasCapability(CAP_LOG_READ)) return SYS_ERR_PERMISSION;
|
if (!Sched::Consent::Require(CAP_LOG_READ)) return CapabilityDenied;
|
||||||
if (!UserMemory::Range(frame->arg1, frame->arg2, true)) return -1;
|
if (!UserMemory::Range(frame->arg1, frame->arg2, true)) return BadPointer;
|
||||||
return Kt::ReadKernelLogBuffer((char*)frame->arg1, frame->arg2);
|
return Kt::ReadKernelLogBuffer((char*)frame->arg1, frame->arg2);
|
||||||
case SYS_MOUSESTATE:
|
case SYS_MOUSESTATE:
|
||||||
if (!UserMemory::Writable<MouseState>(frame->arg1)) return -1;
|
if (!UserMemory::Writable<MouseState>(frame->arg1)) return BadPointer;
|
||||||
Sys_MouseState((MouseState*)frame->arg1);
|
Sys_MouseState((MouseState*)frame->arg1);
|
||||||
return 0;
|
return Success;
|
||||||
case SYS_SETMOUSEBOUNDS:
|
case SYS_SETMOUSEBOUNDS:
|
||||||
Sys_SetMouseBounds((int32_t)frame->arg1, (int32_t)frame->arg2);
|
Sys_SetMouseBounds((int32_t)frame->arg1, (int32_t)frame->arg2);
|
||||||
return 0;
|
return Success;
|
||||||
case SYS_SPAWN_REDIR:
|
case SYS_SPAWN_REDIR:
|
||||||
if (!UserMemory::String(frame->arg1, kMaxPathBytes)) return -1;
|
if (!UserMemory::String(frame->arg1, kMaxPathBytes)) return BadPointer;
|
||||||
if (frame->arg2 != 0 && !UserMemory::String(frame->arg2, kMaxArgsBytes)) return -1;
|
if (frame->arg2 != 0 && !UserMemory::String(frame->arg2, kMaxArgsBytes)) return BadPointer;
|
||||||
return (int64_t)Sys_SpawnRedir((const char*)frame->arg1,
|
return (int64_t)Sys_SpawnRedir((const char*)frame->arg1,
|
||||||
UserMemory::IsUserPtr(frame->arg2) ? (const char*)frame->arg2 : nullptr);
|
UserMemory::IsUserPtr(frame->arg2) ? (const char*)frame->arg2 : nullptr);
|
||||||
case SYS_SPAWN_REDIR_CAPS:
|
case SYS_SPAWN_REDIR_CAPS:
|
||||||
if (!UserMemory::String(frame->arg1, kMaxPathBytes)) return -1;
|
if (!UserMemory::String(frame->arg1, kMaxPathBytes)) return BadPointer;
|
||||||
if (frame->arg2 != 0 && !UserMemory::String(frame->arg2, kMaxArgsBytes)) return -1;
|
if (frame->arg2 != 0 && !UserMemory::String(frame->arg2, kMaxArgsBytes)) return BadPointer;
|
||||||
if (!UserMemory::Readable<SpawnCapabilities>(frame->arg3)) return -1;
|
if (!UserMemory::Readable<SpawnCapabilities>(frame->arg3)) return BadPointer;
|
||||||
return (int64_t)Sys_SpawnRedirCaps(
|
return (int64_t)Sys_SpawnRedirCaps(
|
||||||
(const char*)frame->arg1,
|
(const char*)frame->arg1,
|
||||||
frame->arg2 ? (const char*)frame->arg2 : nullptr,
|
frame->arg2 ? (const char*)frame->arg2 : nullptr,
|
||||||
(const SpawnCapabilities*)frame->arg3);
|
(const SpawnCapabilities*)frame->arg3);
|
||||||
case SYS_CHILDIO_READ:
|
case SYS_CHILDIO_READ:
|
||||||
if ((int64_t)frame->arg3 < 0) return -1;
|
if ((int64_t)frame->arg3 < 0) return InvalidArgument;
|
||||||
if (!UserMemory::Range(frame->arg2, (uint64_t)frame->arg3, true)) return -1;
|
if (!UserMemory::Range(frame->arg2, (uint64_t)frame->arg3, true)) return BadPointer;
|
||||||
return (int64_t)Sys_ChildIoRead((int)frame->arg1, (char*)frame->arg2, (int)frame->arg3);
|
return (int64_t)Sys_ChildIoRead((int)frame->arg1, (char*)frame->arg2, (int)frame->arg3);
|
||||||
case SYS_CHILDIO_WRITE:
|
case SYS_CHILDIO_WRITE:
|
||||||
if ((int64_t)frame->arg3 < 0) return -1;
|
if ((int64_t)frame->arg3 < 0) return InvalidArgument;
|
||||||
if (!UserMemory::Range(frame->arg2, (uint64_t)frame->arg3, false)) return -1;
|
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 -1;
|
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:
|
||||||
if (!UserMemory::String(frame->arg1, kMaxWindowTitleBytes)) return -1;
|
if (!UserMemory::String(frame->arg1, kMaxWindowTitleBytes)) return BadPointer;
|
||||||
if (!UserMemory::Writable<WinCreateResult>(frame->arg4)) return -1;
|
if (!UserMemory::Writable<WinCreateResult>(frame->arg4)) return BadPointer;
|
||||||
return (int64_t)Sys_WinCreate((const char*)frame->arg1, (int)frame->arg2,
|
return (int64_t)Sys_WinCreate((const char*)frame->arg1, (int)frame->arg2,
|
||||||
(int)frame->arg3, (WinCreateResult*)frame->arg4);
|
(int)frame->arg3, (WinCreateResult*)frame->arg4);
|
||||||
case SYS_WINDESTROY:
|
case SYS_WINDESTROY:
|
||||||
@@ -376,38 +376,44 @@ namespace montauk::abi {
|
|||||||
case SYS_WINPRESENT:
|
case SYS_WINPRESENT:
|
||||||
return (int64_t)Sys_WinPresent((int)frame->arg1);
|
return (int64_t)Sys_WinPresent((int)frame->arg1);
|
||||||
case SYS_WINPOLL:
|
case SYS_WINPOLL:
|
||||||
if (!UserMemory::Writable<WinEvent>(frame->arg2)) return -1;
|
if (!UserMemory::Writable<WinEvent>(frame->arg2)) return BadPointer;
|
||||||
return (int64_t)Sys_WinPoll((int)frame->arg1, (WinEvent*)frame->arg2);
|
return (int64_t)Sys_WinPoll((int)frame->arg1, (WinEvent*)frame->arg2);
|
||||||
case SYS_WINENUM:
|
case SYS_WINENUM:
|
||||||
if ((int64_t)frame->arg2 < 0) return -1;
|
if ((int64_t)frame->arg2 < 0) return InvalidArgument;
|
||||||
if (!UserMemory::Range(frame->arg1, (uint64_t)frame->arg2 * sizeof(WinInfo), true)) return -1;
|
if (!UserMemory::Range(frame->arg1, (uint64_t)frame->arg2 * sizeof(WinInfo), true)) return BadPointer;
|
||||||
return (int64_t)Sys_WinEnum((WinInfo*)frame->arg1, (int)frame->arg2);
|
return (int64_t)Sys_WinEnum((WinInfo*)frame->arg1, (int)frame->arg2);
|
||||||
case SYS_WINMAP:
|
case SYS_WINMAP:
|
||||||
return (int64_t)Sys_WinMap((int)frame->arg1);
|
return (int64_t)Sys_WinMap((int)frame->arg1);
|
||||||
case SYS_WINUNMAP:
|
case SYS_WINUNMAP:
|
||||||
return (int64_t)Sys_WinUnmap((int)frame->arg1);
|
return (int64_t)Sys_WinUnmap((int)frame->arg1);
|
||||||
case SYS_WINSENDEVENT:
|
case SYS_WINSENDEVENT:
|
||||||
if (!UserMemory::Readable<WinEvent>(frame->arg2)) return -1;
|
if (!UserMemory::Readable<WinEvent>(frame->arg2)) return BadPointer;
|
||||||
return (int64_t)Sys_WinSendEvent((int)frame->arg1, (const WinEvent*)frame->arg2);
|
return (int64_t)Sys_WinSendEvent((int)frame->arg1, (const WinEvent*)frame->arg2);
|
||||||
case SYS_WINRESIZE:
|
case SYS_WINRESIZE:
|
||||||
return (int64_t)Sys_WinResize((int)frame->arg1, (int)frame->arg2, (int)frame->arg3);
|
return (int64_t)Sys_WinResize((int)frame->arg1, (int)frame->arg2, (int)frame->arg3);
|
||||||
case SYS_PROCLIST:
|
case SYS_PROCLIST:
|
||||||
if ((int64_t)frame->arg2 < 0) return -1;
|
if ((int64_t)frame->arg2 < 0) return InvalidArgument;
|
||||||
if (!UserMemory::Range(frame->arg1, (uint64_t)frame->arg2 * sizeof(ProcInfo), true)) return -1;
|
if (!UserMemory::Range(frame->arg1, (uint64_t)frame->arg2 * sizeof(ProcInfo), true)) return BadPointer;
|
||||||
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:
|
||||||
if (!Sched::HasCapability(CAP_PROCESS_ADMIN)) return SYS_ERR_PERMISSION;
|
if (!Sched::HasCapability(CAP_PROCESS_ADMIN)) return CapabilityDenied;
|
||||||
return (int64_t)Sys_KillSession((int)frame->arg1);
|
return (int64_t)Sys_KillSession((int)frame->arg1);
|
||||||
case SYS_DEVLIST:
|
case SYS_DEVLIST:
|
||||||
if ((int64_t)frame->arg2 < 0) return -1;
|
if ((int64_t)frame->arg2 < 0) return InvalidArgument;
|
||||||
if (!UserMemory::Range(frame->arg1, (uint64_t)frame->arg2 * sizeof(DevInfo), true)) return -1;
|
if (!UserMemory::Range(frame->arg1, (uint64_t)frame->arg2 * sizeof(DevInfo), true)) return BadPointer;
|
||||||
return (int64_t)Sys_DevList((DevInfo*)frame->arg1, (int)frame->arg2);
|
return (int64_t)Sys_DevList((DevInfo*)frame->arg1, (int)frame->arg2);
|
||||||
case SYS_DISKINFO:
|
case SYS_DISKINFO:
|
||||||
if (!UserMemory::Writable<DiskInfo>(frame->arg1)) return -1;
|
if (!UserMemory::Writable<DiskInfo>(frame->arg1)) return BadPointer;
|
||||||
return (int64_t)Sys_DiskInfo((DiskInfo*)frame->arg1, (int)frame->arg2);
|
return (int64_t)Sys_DiskInfo((DiskInfo*)frame->arg1, (int)frame->arg2);
|
||||||
case SYS_WINSETSCALE:
|
case SYS_WINSETSCALE:
|
||||||
return (int64_t)Sys_WinSetScale((int)frame->arg1);
|
return (int64_t)Sys_WinSetScale((int)frame->arg1);
|
||||||
@@ -418,50 +424,58 @@ namespace montauk::abi {
|
|||||||
case SYS_WINSETFLAGS:
|
case SYS_WINSETFLAGS:
|
||||||
return (int64_t)Sys_WinSetFlags((int)frame->arg1, (uint32_t)frame->arg2);
|
return (int64_t)Sys_WinSetFlags((int)frame->arg1, (uint32_t)frame->arg2);
|
||||||
case SYS_MEMSTATS:
|
case SYS_MEMSTATS:
|
||||||
if (!UserMemory::Writable<MemStats>(frame->arg1)) return -1;
|
if (!UserMemory::Writable<MemStats>(frame->arg1)) return BadPointer;
|
||||||
Sys_MemStats((MemStats*)frame->arg1);
|
Sys_MemStats((MemStats*)frame->arg1);
|
||||||
return 0;
|
return Success;
|
||||||
case SYS_PARTLIST:
|
case SYS_PARTLIST:
|
||||||
if ((int64_t)frame->arg2 < 0) return -1;
|
if ((int64_t)frame->arg2 < 0) return InvalidArgument;
|
||||||
if (!UserMemory::Range(frame->arg1, (uint64_t)frame->arg2 * sizeof(PartInfo), true)) return -1;
|
if (!UserMemory::Range(frame->arg1, (uint64_t)frame->arg2 * sizeof(PartInfo), true)) return BadPointer;
|
||||||
return (int64_t)Sys_PartList((PartInfo*)frame->arg1, (int)frame->arg2);
|
return (int64_t)Sys_PartList((PartInfo*)frame->arg1, (int)frame->arg2);
|
||||||
|
case SYS_VOLINFO:
|
||||||
|
if ((int64_t)frame->arg2 < 0) return InvalidArgument;
|
||||||
|
if (!UserMemory::Range(frame->arg1, (uint64_t)frame->arg2 * sizeof(VolInfo), true)) return BadPointer;
|
||||||
|
return (int64_t)Sys_VolInfo((VolInfo*)frame->arg1, (int)frame->arg2);
|
||||||
case SYS_DISKREAD:
|
case SYS_DISKREAD:
|
||||||
if (!Sched::HasCapability(CAP_RAW_STORAGE)) return SYS_ERR_PERMISSION;
|
if (!Sched::HasCapability(CAP_RAW_STORAGE)) return CapabilityDenied;
|
||||||
return (int64_t)Sys_DiskRead((int)frame->arg1, frame->arg2,
|
return (int64_t)Sys_DiskRead((int)frame->arg1, frame->arg2,
|
||||||
(uint32_t)frame->arg3, (void*)frame->arg4);
|
(uint32_t)frame->arg3, (void*)frame->arg4);
|
||||||
case SYS_DISKWRITE:
|
case SYS_DISKWRITE:
|
||||||
if (!Sched::HasCapability(CAP_RAW_STORAGE)) return SYS_ERR_PERMISSION;
|
if (!Sched::HasCapability(CAP_RAW_STORAGE)) return CapabilityDenied;
|
||||||
return (int64_t)Sys_DiskWrite((int)frame->arg1, frame->arg2,
|
return (int64_t)Sys_DiskWrite((int)frame->arg1, frame->arg2,
|
||||||
(uint32_t)frame->arg3, (const void*)frame->arg4);
|
(uint32_t)frame->arg3, (const void*)frame->arg4);
|
||||||
case SYS_GPTINIT:
|
case SYS_GPTINIT:
|
||||||
if (!Sched::HasCapability(CAP_STORAGE_ADMIN)) return SYS_ERR_PERMISSION;
|
if (!Sched::Consent::Require(CAP_STORAGE_ADMIN)) return CapabilityDenied;
|
||||||
return (int64_t)Sys_GptInit((int)frame->arg1);
|
return (int64_t)Sys_GptInit((int)frame->arg1);
|
||||||
case SYS_GPTADD:
|
case SYS_GPTADD:
|
||||||
if (!Sched::HasCapability(CAP_STORAGE_ADMIN)) return SYS_ERR_PERMISSION;
|
if (!Sched::Consent::Require(CAP_STORAGE_ADMIN)) return CapabilityDenied;
|
||||||
if (!UserMemory::Readable<GptAddParams>(frame->arg1)) return -1;
|
if (!UserMemory::Readable<GptAddParams>(frame->arg1)) return BadPointer;
|
||||||
return (int64_t)Sys_GptAdd((const GptAddParams*)frame->arg1);
|
return (int64_t)Sys_GptAdd((const GptAddParams*)frame->arg1);
|
||||||
case SYS_FSMOUNT:
|
case SYS_FSMOUNT:
|
||||||
if (!Sched::HasCapability(CAP_STORAGE_ADMIN)) return SYS_ERR_PERMISSION;
|
if (!Sched::Consent::Require(CAP_STORAGE_ADMIN)) return CapabilityDenied;
|
||||||
return (int64_t)Sys_FsMount((int)frame->arg1, (int)frame->arg2);
|
return (int64_t)Sys_FsMount((int)frame->arg1, (int)frame->arg2);
|
||||||
case SYS_FS_SYNC:
|
case SYS_FS_SYNC:
|
||||||
if (!Sched::HasCapability(CAP_STORAGE_ADMIN)) return SYS_ERR_PERMISSION;
|
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::HasCapability(CAP_STORAGE_ADMIN)) return SYS_ERR_PERMISSION;
|
if (!Sched::Consent::Require(CAP_STORAGE_ADMIN)) return CapabilityDenied;
|
||||||
if (!UserMemory::Readable<FsFormatParams>(frame->arg1)) return -1;
|
if (!UserMemory::Readable<FsFormatParams>(frame->arg1)) return BadPointer;
|
||||||
return (int64_t)Sys_FsFormat((const FsFormatParams*)frame->arg1);
|
return (int64_t)Sys_FsFormat((const FsFormatParams*)frame->arg1);
|
||||||
case SYS_AUDIOOPEN:
|
case SYS_AUDIOOPEN:
|
||||||
return Sys_AudioOpen((uint32_t)frame->arg1, (uint8_t)frame->arg2, (uint8_t)frame->arg3);
|
return Sys_AudioOpen((uint32_t)frame->arg1, (uint8_t)frame->arg2, (uint8_t)frame->arg3);
|
||||||
case SYS_AUDIOCLOSE:
|
case SYS_AUDIOCLOSE:
|
||||||
return Sys_AudioClose((int)frame->arg1);
|
return Sys_AudioClose((int)frame->arg1);
|
||||||
case SYS_AUDIOWRITE:
|
case SYS_AUDIOWRITE:
|
||||||
if (!UserMemory::Range(frame->arg2, frame->arg3, false)) return -1;
|
if (!UserMemory::Range(frame->arg2, frame->arg3, false)) return BadPointer;
|
||||||
return Sys_AudioWrite((int)frame->arg1, (const uint8_t*)frame->arg2, (uint32_t)frame->arg3);
|
return Sys_AudioWrite((int)frame->arg1, (const uint8_t*)frame->arg2, (uint32_t)frame->arg3);
|
||||||
case SYS_AUDIOCTL:
|
case SYS_AUDIOCTL:
|
||||||
return Sys_AudioCtl((int)frame->arg1, (int)frame->arg2, (int)frame->arg3);
|
return Sys_AudioCtl((int)frame->arg1, (int)frame->arg2, (int)frame->arg3);
|
||||||
case SYS_AUDIOLIST:
|
case SYS_AUDIOLIST:
|
||||||
if ((int64_t)frame->arg2 < 0) return -1;
|
if ((int64_t)frame->arg2 < 0) return InvalidArgument;
|
||||||
if (!UserMemory::Range(frame->arg1, (uint64_t)frame->arg2 * sizeof(AudioStreamInfo), true)) return -1;
|
if (!UserMemory::Range(frame->arg1, (uint64_t)frame->arg2 * sizeof(AudioStreamInfo), true)) return BadPointer;
|
||||||
return Sys_AudioList((AudioStreamInfo*)frame->arg1, (int)frame->arg2);
|
return Sys_AudioList((AudioStreamInfo*)frame->arg1, (int)frame->arg2);
|
||||||
case SYS_AUDIOWAIT:
|
case SYS_AUDIOWAIT:
|
||||||
return Sys_AudioWait(frame->arg1, frame->arg2);
|
return Sys_AudioWait(frame->arg1, frame->arg2);
|
||||||
@@ -502,58 +516,83 @@ namespace montauk::abi {
|
|||||||
return Sys_UsbBulkInRead((int)frame->arg1, (uint8_t*)frame->arg2,
|
return Sys_UsbBulkInRead((int)frame->arg1, (uint8_t*)frame->arg2,
|
||||||
(uint32_t)frame->arg3);
|
(uint32_t)frame->arg3);
|
||||||
case SYS_POWERINFO:
|
case SYS_POWERINFO:
|
||||||
if (!UserMemory::Writable<PowerInfo>(frame->arg1)) return -1;
|
if (!UserMemory::Writable<PowerInfo>(frame->arg1)) return BadPointer;
|
||||||
return Sys_PowerInfo((PowerInfo*)frame->arg1);
|
return Sys_PowerInfo((PowerInfo*)frame->arg1);
|
||||||
case SYS_BATTERYINFO:
|
case SYS_BATTERYINFO:
|
||||||
// Read-only power telemetry, ungated like SYS_POWERINFO: the
|
// Read-only power telemetry, ungated like SYS_POWERINFO: the
|
||||||
// panel indicator has to work in an unprivileged session, which
|
// panel indicator has to work in an unprivileged session, which
|
||||||
// holds no device capability to spend on it.
|
// holds no device capability to spend on it.
|
||||||
if (!UserMemory::Writable<BatteryInfo>(frame->arg1)) return -1;
|
if (!UserMemory::Writable<BatteryInfo>(frame->arg1)) return BadPointer;
|
||||||
return Sys_BatteryInfo((BatteryInfo*)frame->arg1);
|
return Sys_BatteryInfo((BatteryInfo*)frame->arg1);
|
||||||
|
case SYS_GETCAPS:
|
||||||
|
if (!UserMemory::Writable<SpawnCapabilities>(frame->arg1)) return BadPointer;
|
||||||
|
return Sys_GetCaps((SpawnCapabilities*)frame->arg1);
|
||||||
|
case SYS_CONSENT_REGISTER:
|
||||||
|
return Sched::Consent::Register();
|
||||||
|
case SYS_CONSENT_NEXT:
|
||||||
|
if (!UserMemory::Writable<ConsentRequest>(frame->arg1)) return BadPointer;
|
||||||
|
return Sched::Consent::Next((ConsentRequest*)frame->arg1);
|
||||||
|
case SYS_CONSENT_REPLY:
|
||||||
|
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:
|
||||||
Sys_ThreadExit((int)frame->arg1);
|
Sys_ThreadExit((int)frame->arg1);
|
||||||
return 0;
|
return Success;
|
||||||
case SYS_THREAD_JOIN:
|
case SYS_THREAD_JOIN:
|
||||||
if (frame->arg2 != 0 && !UserMemory::Writable<int>(frame->arg2)) return -1;
|
if (frame->arg2 != 0 && !UserMemory::Writable<int>(frame->arg2)) return BadPointer;
|
||||||
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 SYS_ERR_PERMISSION;
|
if (!Sched::HasCapability(CAP_DEVICE_ADMIN)) return CapabilityDenied;
|
||||||
if ((int64_t)frame->arg2 < 0) return -1;
|
if ((int64_t)frame->arg2 < 0) return InvalidArgument;
|
||||||
if (!UserMemory::Range(frame->arg1, (uint64_t)frame->arg2 * sizeof(BtScanResult), true)) return -1;
|
if (!UserMemory::Range(frame->arg1, (uint64_t)frame->arg2 * sizeof(BtScanResult), true)) return BadPointer;
|
||||||
return Sys_BtScan((BtScanResult*)frame->arg1, (int)frame->arg2, (uint32_t)frame->arg3);
|
return Sys_BtScan((BtScanResult*)frame->arg1, (int)frame->arg2, (uint32_t)frame->arg3);
|
||||||
case SYS_BTCONNECT:
|
case SYS_BTCONNECT:
|
||||||
if (!Sched::HasCapability(CAP_DEVICE_ADMIN)) return SYS_ERR_PERMISSION;
|
if (!Sched::HasCapability(CAP_DEVICE_ADMIN)) return CapabilityDenied;
|
||||||
if (!UserMemory::Range(frame->arg1, 6, false)) return -1;
|
if (!UserMemory::Range(frame->arg1, 6, false)) return BadPointer;
|
||||||
return Sys_BtConnect((const uint8_t*)frame->arg1);
|
return Sys_BtConnect((const uint8_t*)frame->arg1);
|
||||||
case SYS_BTDISCONNECT:
|
case SYS_BTDISCONNECT:
|
||||||
if (!Sched::HasCapability(CAP_DEVICE_ADMIN)) return SYS_ERR_PERMISSION;
|
if (!Sched::HasCapability(CAP_DEVICE_ADMIN)) return CapabilityDenied;
|
||||||
if (!UserMemory::Range(frame->arg1, 6, false)) return -1;
|
if (!UserMemory::Range(frame->arg1, 6, false)) return BadPointer;
|
||||||
return Sys_BtDisconnect((const uint8_t*)frame->arg1);
|
return Sys_BtDisconnect((const uint8_t*)frame->arg1);
|
||||||
case SYS_BTSETADDR:
|
case SYS_BTSETADDR:
|
||||||
if (!Sched::HasCapability(CAP_DEVICE_ADMIN)) return SYS_ERR_PERMISSION;
|
if (!Sched::HasCapability(CAP_DEVICE_ADMIN)) return CapabilityDenied;
|
||||||
if (!UserMemory::Range(frame->arg1, 6, false)) return -1;
|
if (!UserMemory::Range(frame->arg1, 6, false)) return BadPointer;
|
||||||
return Sys_BtSetAddr((const uint8_t*)frame->arg1);
|
return Sys_BtSetAddr((const uint8_t*)frame->arg1);
|
||||||
case SYS_BTBONDS:
|
case SYS_BTBONDS:
|
||||||
if ((int64_t)frame->arg2 < 0) return -1;
|
if ((int64_t)frame->arg2 < 0) return InvalidArgument;
|
||||||
if (!UserMemory::Range(frame->arg1, (uint64_t)frame->arg2 * sizeof(BtBondInfo), true)) return -1;
|
if (!UserMemory::Range(frame->arg1, (uint64_t)frame->arg2 * sizeof(BtBondInfo), true)) return BadPointer;
|
||||||
return Sys_BtBonds((BtBondInfo*)frame->arg1, (int)frame->arg2);
|
return Sys_BtBonds((BtBondInfo*)frame->arg1, (int)frame->arg2);
|
||||||
case SYS_BTFORGET:
|
case SYS_BTFORGET:
|
||||||
if (!Sched::HasCapability(CAP_DEVICE_ADMIN)) return SYS_ERR_PERMISSION;
|
if (!Sched::HasCapability(CAP_DEVICE_ADMIN)) return CapabilityDenied;
|
||||||
if (!UserMemory::Range(frame->arg1, 6, false)) return -1;
|
if (!UserMemory::Range(frame->arg1, 6, false)) return BadPointer;
|
||||||
return Sys_BtForget((const uint8_t*)frame->arg1);
|
return Sys_BtForget((const uint8_t*)frame->arg1);
|
||||||
case SYS_BTLIST:
|
case SYS_BTLIST:
|
||||||
if ((int64_t)frame->arg2 < 0) return -1;
|
if ((int64_t)frame->arg2 < 0) return InvalidArgument;
|
||||||
if (!UserMemory::Range(frame->arg1, (uint64_t)frame->arg2 * sizeof(BtDevInfo), true)) return -1;
|
if (!UserMemory::Range(frame->arg1, (uint64_t)frame->arg2 * sizeof(BtDevInfo), true)) return BadPointer;
|
||||||
return Sys_BtList((BtDevInfo*)frame->arg1, (int)frame->arg2);
|
return Sys_BtList((BtDevInfo*)frame->arg1, (int)frame->arg2);
|
||||||
case SYS_BTINFO:
|
case SYS_BTINFO:
|
||||||
if (!UserMemory::Writable<BtAdapterInfo>(frame->arg1)) return -1;
|
if (!UserMemory::Writable<BtAdapterInfo>(frame->arg1)) return BadPointer;
|
||||||
return Sys_BtInfo((BtAdapterInfo*)frame->arg1);
|
return Sys_BtInfo((BtAdapterInfo*)frame->arg1);
|
||||||
case SYS_WIFI_SCAN:
|
case SYS_WIFI_SCAN:
|
||||||
if (!Sched::HasCapability(CAP_NETWORK_ADMIN)) return SYS_ERR_PERMISSION;
|
if (!Sched::Consent::Require(CAP_NETWORK_ADMIN)) return SYS_ERR_PERMISSION;
|
||||||
if ((int64_t)frame->arg2 < 0) return -1;
|
if ((int64_t)frame->arg2 < 0) return -1;
|
||||||
if (!UserMemory::Range(frame->arg1, (uint64_t)frame->arg2 * sizeof(WifiNetwork), true)) return -1;
|
if (!UserMemory::Range(frame->arg1, (uint64_t)frame->arg2 * sizeof(WifiNetwork), true)) return -1;
|
||||||
return Sys_WifiScan((WifiNetwork*)frame->arg1, (int)frame->arg2, (uint32_t)frame->arg3);
|
return Sys_WifiScan((WifiNetwork*)frame->arg1, (int)frame->arg2, (uint32_t)frame->arg3);
|
||||||
@@ -561,81 +600,81 @@ namespace montauk::abi {
|
|||||||
if (!UserMemory::Writable<WifiInfo>(frame->arg1)) return -1;
|
if (!UserMemory::Writable<WifiInfo>(frame->arg1)) return -1;
|
||||||
return Sys_WifiInfo((WifiInfo*)frame->arg1);
|
return Sys_WifiInfo((WifiInfo*)frame->arg1);
|
||||||
case SYS_WIFI_CONNECT:
|
case SYS_WIFI_CONNECT:
|
||||||
if (!Sched::HasCapability(CAP_NETWORK_ADMIN)) return SYS_ERR_PERMISSION;
|
if (!Sched::Consent::Require(CAP_NETWORK_ADMIN)) return SYS_ERR_PERMISSION;
|
||||||
if (!UserMemory::String(frame->arg1, 64)) return -1;
|
if (!UserMemory::String(frame->arg1, 64)) return -1;
|
||||||
if (frame->arg2 != 0 && !UserMemory::String(frame->arg2, 128)) return -1;
|
if (frame->arg2 != 0 && !UserMemory::String(frame->arg2, 128)) return -1;
|
||||||
return Sys_WifiConnect((const char*)frame->arg1, (const char*)frame->arg2);
|
return Sys_WifiConnect((const char*)frame->arg1, (const char*)frame->arg2);
|
||||||
case SYS_WIFI_DISCONNECT:
|
case SYS_WIFI_DISCONNECT:
|
||||||
if (!Sched::HasCapability(CAP_NETWORK_ADMIN)) return SYS_ERR_PERMISSION;
|
if (!Sched::Consent::Require(CAP_NETWORK_ADMIN)) return SYS_ERR_PERMISSION;
|
||||||
return Sys_WifiDisconnect();
|
return Sys_WifiDisconnect();
|
||||||
case SYS_WIFI_SCAN_START:
|
case SYS_WIFI_SCAN_START:
|
||||||
if (!Sched::HasCapability(CAP_NETWORK_ADMIN)) return SYS_ERR_PERMISSION;
|
if (!Sched::Consent::Require(CAP_NETWORK_ADMIN)) return SYS_ERR_PERMISSION;
|
||||||
return Sys_WifiScanStart((uint32_t)frame->arg1);
|
return Sys_WifiScanStart((uint32_t)frame->arg1);
|
||||||
case SYS_WIFI_RESULTS:
|
case SYS_WIFI_RESULTS:
|
||||||
if ((int64_t)frame->arg2 < 0) return -1;
|
if ((int64_t)frame->arg2 < 0) return -1;
|
||||||
if (!UserMemory::Range(frame->arg1, (uint64_t)frame->arg2 * sizeof(WifiNetwork), true)) return -1;
|
if (!UserMemory::Range(frame->arg1, (uint64_t)frame->arg2 * sizeof(WifiNetwork), true)) return -1;
|
||||||
return Sys_WifiResults((WifiNetwork*)frame->arg1, (int)frame->arg2);
|
return Sys_WifiResults((WifiNetwork*)frame->arg1, (int)frame->arg2);
|
||||||
case SYS_WIFI_CONNECT_ASYNC:
|
case SYS_WIFI_CONNECT_ASYNC:
|
||||||
if (!Sched::HasCapability(CAP_NETWORK_ADMIN)) return SYS_ERR_PERMISSION;
|
if (!Sched::Consent::Require(CAP_NETWORK_ADMIN)) return SYS_ERR_PERMISSION;
|
||||||
if (!UserMemory::String(frame->arg1, 64)) return -1;
|
if (!UserMemory::String(frame->arg1, 64)) return -1;
|
||||||
if (frame->arg2 != 0 && !UserMemory::String(frame->arg2, 128)) return -1;
|
if (frame->arg2 != 0 && !UserMemory::String(frame->arg2, 128)) return -1;
|
||||||
return Sys_WifiConnectAsync((const char*)frame->arg1, (const char*)frame->arg2);
|
return Sys_WifiConnectAsync((const char*)frame->arg1, (const char*)frame->arg2);
|
||||||
case SYS_NETIFS:
|
case SYS_NETIFS:
|
||||||
if ((int64_t)frame->arg2 < 0) return -1;
|
if ((int64_t)frame->arg2 < 0) return InvalidArgument;
|
||||||
if (!UserMemory::Range(frame->arg1, (uint64_t)frame->arg2 * sizeof(NetIfInfo), true)) return -1;
|
if (!UserMemory::Range(frame->arg1, (uint64_t)frame->arg2 * sizeof(NetIfInfo), true)) return BadPointer;
|
||||||
return Sys_NetIfs((NetIfInfo*)frame->arg1, (int)frame->arg2);
|
return Sys_NetIfs((NetIfInfo*)frame->arg1, (int)frame->arg2);
|
||||||
case SYS_SUSPEND:
|
case SYS_SUSPEND:
|
||||||
if (!Sched::HasCapability(CAP_SUSPEND)) return SYS_ERR_PERMISSION;
|
if (!Sched::HasCapability(CAP_SUSPEND)) return CapabilityDenied;
|
||||||
return Sys_Suspend();
|
return Sys_Suspend();
|
||||||
case SYS_SETTZ:
|
case SYS_SETTZ:
|
||||||
if (!Sched::HasCapability(CAP_SET_TIME)) return SYS_ERR_PERMISSION;
|
if (!Sched::Consent::Require(CAP_SET_TIME)) return CapabilityDenied;
|
||||||
return Sys_SetTZ((int32_t)frame->arg1);
|
return Sys_SetTZ((int32_t)frame->arg1);
|
||||||
case SYS_GETTZ:
|
case SYS_GETTZ:
|
||||||
return Sys_GetTZ();
|
return Sys_GetTZ();
|
||||||
case SYS_SETUSER:
|
case SYS_SETUSER:
|
||||||
if (!Sched::HasCapability(CAP_USER_ADMIN)) return SYS_ERR_PERMISSION;
|
if (!Sched::HasCapability(CAP_USER_ADMIN)) return CapabilityDenied;
|
||||||
if (!UserMemory::String(frame->arg2, kMaxUserNameBytes)) return -1;
|
if (!UserMemory::String(frame->arg2, kMaxUserNameBytes)) return BadPointer;
|
||||||
return Sys_SetUser((int)frame->arg1, (const char*)frame->arg2);
|
return Sys_SetUser((int)frame->arg1, (const char*)frame->arg2);
|
||||||
case SYS_GETUSER:
|
case SYS_GETUSER:
|
||||||
if (!UserMemory::Range(frame->arg1, frame->arg2, true)) return -1;
|
if (!UserMemory::Range(frame->arg1, frame->arg2, true)) return BadPointer;
|
||||||
return Sys_GetUser((char*)frame->arg1, frame->arg2);
|
return Sys_GetUser((char*)frame->arg1, frame->arg2);
|
||||||
case SYS_GETCWD:
|
case SYS_GETCWD:
|
||||||
if (!UserMemory::Range(frame->arg1, frame->arg2, true)) return -1;
|
if (!UserMemory::Range(frame->arg1, frame->arg2, true)) return BadPointer;
|
||||||
return Sys_GetCwd((char*)frame->arg1, frame->arg2);
|
return Sys_GetCwd((char*)frame->arg1, frame->arg2);
|
||||||
case SYS_CHDIR:
|
case SYS_CHDIR:
|
||||||
if (!UserMemory::String(frame->arg1, kMaxPathBytes)) return -1;
|
if (!UserMemory::String(frame->arg1, kMaxPathBytes)) return BadPointer;
|
||||||
return Sys_Chdir((const char*)frame->arg1);
|
return Sys_Chdir((const char*)frame->arg1);
|
||||||
case SYS_DUPHANDLE:
|
case SYS_DUPHANDLE:
|
||||||
return Sys_DupHandle((int)frame->arg1);
|
return Sys_DupHandle((int)frame->arg1);
|
||||||
case SYS_WAIT_HANDLE:
|
case SYS_WAIT_HANDLE:
|
||||||
return (int64_t)Sys_WaitHandle((int)frame->arg1, (uint32_t)frame->arg2, frame->arg3);
|
return (int64_t)Sys_WaitHandle((int)frame->arg1, (uint32_t)frame->arg2, frame->arg3);
|
||||||
case SYS_STREAM_CREATE:
|
case SYS_STREAM_CREATE:
|
||||||
if (!UserMemory::Writable<int>(frame->arg1) || !UserMemory::Writable<int>(frame->arg2)) return -1;
|
if (!UserMemory::Writable<int>(frame->arg1) || !UserMemory::Writable<int>(frame->arg2)) return BadPointer;
|
||||||
return Sys_StreamCreate((int*)frame->arg1, (int*)frame->arg2, (uint32_t)frame->arg3);
|
return Sys_StreamCreate((int*)frame->arg1, (int*)frame->arg2, (uint32_t)frame->arg3);
|
||||||
case SYS_STREAM_READ:
|
case SYS_STREAM_READ:
|
||||||
if ((int64_t)frame->arg3 < 0) return -1;
|
if ((int64_t)frame->arg3 < 0) return InvalidArgument;
|
||||||
if (!UserMemory::Range(frame->arg2, (uint64_t)frame->arg3, true)) return -1;
|
if (!UserMemory::Range(frame->arg2, (uint64_t)frame->arg3, true)) return BadPointer;
|
||||||
return Sys_StreamRead((int)frame->arg1, (uint8_t*)frame->arg2, (int)frame->arg3);
|
return Sys_StreamRead((int)frame->arg1, (uint8_t*)frame->arg2, (int)frame->arg3);
|
||||||
case SYS_STREAM_WRITE:
|
case SYS_STREAM_WRITE:
|
||||||
if ((int64_t)frame->arg3 < 0) return -1;
|
if ((int64_t)frame->arg3 < 0) return InvalidArgument;
|
||||||
if (!UserMemory::Range(frame->arg2, (uint64_t)frame->arg3, false)) return -1;
|
if (!UserMemory::Range(frame->arg2, (uint64_t)frame->arg3, false)) return BadPointer;
|
||||||
return Sys_StreamWrite((int)frame->arg1, (const uint8_t*)frame->arg2, (int)frame->arg3);
|
return Sys_StreamWrite((int)frame->arg1, (const uint8_t*)frame->arg2, (int)frame->arg3);
|
||||||
case SYS_MAILBOX_CREATE:
|
case SYS_MAILBOX_CREATE:
|
||||||
if (!UserMemory::Writable<int>(frame->arg1) || !UserMemory::Writable<int>(frame->arg2)) return -1;
|
if (!UserMemory::Writable<int>(frame->arg1) || !UserMemory::Writable<int>(frame->arg2)) return BadPointer;
|
||||||
return Sys_MailboxCreate((int*)frame->arg1, (int*)frame->arg2);
|
return Sys_MailboxCreate((int*)frame->arg1, (int*)frame->arg2);
|
||||||
case SYS_MAILBOX_SEND:
|
case SYS_MAILBOX_SEND:
|
||||||
if (frame->arg4 > 0 && !UserMemory::Range(frame->arg3, frame->arg4, false)) return -1;
|
if (frame->arg4 > 0 && !UserMemory::Range(frame->arg3, frame->arg4, false)) return BadPointer;
|
||||||
return Sys_MailboxSend((int)frame->arg1, (uint32_t)frame->arg2,
|
return Sys_MailboxSend((int)frame->arg1, (uint32_t)frame->arg2,
|
||||||
(const void*)frame->arg3, (uint16_t)frame->arg4,
|
(const void*)frame->arg3, (uint16_t)frame->arg4,
|
||||||
(int)frame->arg5);
|
(int)frame->arg5);
|
||||||
case SYS_MAILBOX_RECV:
|
case SYS_MAILBOX_RECV:
|
||||||
if (!UserMemory::Writable<uint16_t>(frame->arg4)) return -1;
|
if (!UserMemory::Writable<uint16_t>(frame->arg4)) return BadPointer;
|
||||||
if (frame->arg2 != 0 && !UserMemory::Writable<uint32_t>(frame->arg2)) return -1;
|
if (frame->arg2 != 0 && !UserMemory::Writable<uint32_t>(frame->arg2)) return BadPointer;
|
||||||
if (frame->arg3 != 0) {
|
if (frame->arg3 != 0) {
|
||||||
uint16_t maxCopy = *(uint16_t*)frame->arg4;
|
uint16_t maxCopy = *(uint16_t*)frame->arg4;
|
||||||
if (!UserMemory::Range(frame->arg3, maxCopy, true)) return -1;
|
if (!UserMemory::Range(frame->arg3, maxCopy, true)) return BadPointer;
|
||||||
}
|
}
|
||||||
if (frame->arg5 != 0 && !UserMemory::Writable<int>(frame->arg5)) return -1;
|
if (frame->arg5 != 0 && !UserMemory::Writable<int>(frame->arg5)) return BadPointer;
|
||||||
return Sys_MailboxRecv((int)frame->arg1,
|
return Sys_MailboxRecv((int)frame->arg1,
|
||||||
UserMemory::IsUserPtr(frame->arg2) ? (uint32_t*)frame->arg2 : nullptr,
|
UserMemory::IsUserPtr(frame->arg2) ? (uint32_t*)frame->arg2 : nullptr,
|
||||||
UserMemory::IsUserPtr(frame->arg3) ? (void*)frame->arg3 : nullptr,
|
UserMemory::IsUserPtr(frame->arg3) ? (void*)frame->arg3 : nullptr,
|
||||||
@@ -648,7 +687,7 @@ namespace montauk::abi {
|
|||||||
case SYS_WAITSET_REMOVE:
|
case SYS_WAITSET_REMOVE:
|
||||||
return Sys_WaitsetRemove((int)frame->arg1, (int)frame->arg2);
|
return Sys_WaitsetRemove((int)frame->arg1, (int)frame->arg2);
|
||||||
case SYS_WAITSET_WAIT:
|
case SYS_WAITSET_WAIT:
|
||||||
if (frame->arg2 != 0 && !UserMemory::Writable<IpcWaitResult>(frame->arg2)) return -1;
|
if (frame->arg2 != 0 && !UserMemory::Writable<IpcWaitResult>(frame->arg2)) return BadPointer;
|
||||||
return Sys_WaitsetWait((int)frame->arg1,
|
return Sys_WaitsetWait((int)frame->arg1,
|
||||||
UserMemory::IsUserPtr(frame->arg2) ? (IpcWaitResult*)frame->arg2 : nullptr,
|
UserMemory::IsUserPtr(frame->arg2) ? (IpcWaitResult*)frame->arg2 : nullptr,
|
||||||
frame->arg3);
|
frame->arg3);
|
||||||
@@ -670,28 +709,73 @@ namespace montauk::abi {
|
|||||||
case SYS_GETLIBBASE:
|
case SYS_GETLIBBASE:
|
||||||
return (int64_t)Sys_GetLibBase(frame->arg1);
|
return (int64_t)Sys_GetLibBase(frame->arg1);
|
||||||
case SYS_CRASH_REPORT:
|
case SYS_CRASH_REPORT:
|
||||||
if (!UserMemory::Writable<CrashReportInfo>(frame->arg1)) return -1;
|
if (!UserMemory::Writable<CrashReportInfo>(frame->arg1)) return BadPointer;
|
||||||
return Sys_CrashReport((CrashReportInfo*)frame->arg1);
|
return Sys_CrashReport((CrashReportInfo*)frame->arg1);
|
||||||
case SYS_CLIPBOARD_SET_TEXT:
|
case SYS_CLIPBOARD_SET_TEXT:
|
||||||
if (frame->arg2 > 0 && !UserMemory::Range(frame->arg1, frame->arg2, false)) return -1;
|
if (frame->arg2 > 0 && !UserMemory::Range(frame->arg1, frame->arg2, false)) return BadPointer;
|
||||||
return Sys_ClipboardSetText((const char*)frame->arg1, (uint32_t)frame->arg2);
|
return Sys_ClipboardSetText((const char*)frame->arg1, (uint32_t)frame->arg2);
|
||||||
case SYS_CLIPBOARD_GET_INFO:
|
case SYS_CLIPBOARD_GET_INFO:
|
||||||
if (!UserMemory::Writable<ClipboardInfo>(frame->arg1)) return -1;
|
if (!UserMemory::Writable<ClipboardInfo>(frame->arg1)) return BadPointer;
|
||||||
return Sys_ClipboardGetInfo((ClipboardInfo*)frame->arg1);
|
return Sys_ClipboardGetInfo((ClipboardInfo*)frame->arg1);
|
||||||
case SYS_CLIPBOARD_GET_TEXT:
|
case SYS_CLIPBOARD_GET_TEXT:
|
||||||
if (frame->arg2 > 0 && !UserMemory::Range(frame->arg1, frame->arg2, true)) return -1;
|
if (frame->arg2 > 0 && !UserMemory::Range(frame->arg1, frame->arg2, true)) return BadPointer;
|
||||||
if (!UserMemory::Writable<uint32_t>(frame->arg3)) return -1;
|
if (!UserMemory::Writable<uint32_t>(frame->arg3)) return BadPointer;
|
||||||
if (frame->arg4 != 0 && !UserMemory::Writable<uint64_t>(frame->arg4)) return -1;
|
if (frame->arg4 != 0 && !UserMemory::Writable<uint64_t>(frame->arg4)) return BadPointer;
|
||||||
return Sys_ClipboardGetText(UserMemory::IsUserPtr(frame->arg1) ? (char*)frame->arg1 : nullptr,
|
return Sys_ClipboardGetText(UserMemory::IsUserPtr(frame->arg1) ? (char*)frame->arg1 : nullptr,
|
||||||
(uint32_t)frame->arg2,
|
(uint32_t)frame->arg2,
|
||||||
(uint32_t*)frame->arg3,
|
(uint32_t*)frame->arg3,
|
||||||
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: {
|
||||||
if (!UserMemory::String(frame->arg1, kMaxUserspaceLogEntryBytes)) return -1;
|
if (!UserMemory::String(frame->arg1, kMaxUserspaceLogEntryBytes)) return BadPointer;
|
||||||
|
|
||||||
auto* process = Sched::GetCurrentProcessPtr();
|
auto* process = Sched::GetCurrentProcessPtr();
|
||||||
const char* username = process != nullptr && process->user[0] != '\0'
|
const char* username = process != nullptr && process->user[0] != '\0'
|
||||||
@@ -704,38 +788,22 @@ namespace montauk::abi {
|
|||||||
Kt::UserspaceLogStream(imageName, username)
|
Kt::UserspaceLogStream(imageName, username)
|
||||||
<< (const char*)frame->arg1;
|
<< (const char*)frame->arg1;
|
||||||
|
|
||||||
return 0;
|
return Success;
|
||||||
}
|
}
|
||||||
case SYS_TERMINAL_ATTACHED:
|
case SYS_TERMINAL_ATTACHED:
|
||||||
return Sys_TerminalAttached();
|
return Sys_TerminalAttached();
|
||||||
default:
|
default:
|
||||||
return -1;
|
return BadInvocation;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---- 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)";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
+337
-24
@@ -7,6 +7,8 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
|
#include <Common/StatusCodes.hpp>
|
||||||
|
#include <Arch/Syscall.hpp>
|
||||||
|
|
||||||
namespace montauk::abi {
|
namespace montauk::abi {
|
||||||
|
|
||||||
@@ -346,6 +348,66 @@ namespace montauk::abi {
|
|||||||
/* Battery.hpp -- ACPI battery status */
|
/* Battery.hpp -- ACPI battery status */
|
||||||
static constexpr uint64_t SYS_BATTERYINFO = 188; // (BatteryInfo*) -> 0, -1 no battery device
|
static constexpr uint64_t SYS_BATTERYINFO = 188; // (BatteryInfo*) -> 0, -1 no battery device
|
||||||
|
|
||||||
|
/* Process.hpp -- the caller's own capability masks. */
|
||||||
|
static constexpr uint64_t SYS_GETCAPS = 189; // (SpawnCapabilities*) -> 0, -1 bad pointer
|
||||||
|
|
||||||
|
/* Storage.hpp */
|
||||||
|
static constexpr uint64_t SYS_VOLINFO = 190; // (VolInfo*, maxCount) -> count, -1 bad pointer
|
||||||
|
|
||||||
|
/* Consent.hpp */
|
||||||
|
static constexpr uint64_t SYS_CONSENT_REGISTER = 191; // () -> 0, -13 lacks CAP_COMPOSITOR, -1 another agent is live
|
||||||
|
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
|
||||||
|
|
||||||
|
/* 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;
|
||||||
@@ -366,8 +428,21 @@ namespace montauk::abi {
|
|||||||
whatever that image is granted at its next launch. Formatting a data
|
whatever that image is granted at its next launch. Formatting a data
|
||||||
volume must not carry that authority with it. */
|
volume must not carry that authority with it. */
|
||||||
static constexpr uint64_t CAP_SYSTEM_IMAGE = 1ULL << 12;
|
static constexpr uint64_t CAP_SYSTEM_IMAGE = 1ULL << 12;
|
||||||
static constexpr uint64_t CAP_ALL = (1ULL << 13) - 1;
|
/* Map other processes' windows and deliver input events to them
|
||||||
static constexpr uint64_t CAP_STANDARD_SESSION = CAP_POWER_REQUEST | CAP_SUSPEND;
|
(SYS_WINMAP, SYS_WINSENDEVENT): the desktop compositor's authority.
|
||||||
|
Anything holding it can read every window's pixels and click on any
|
||||||
|
window, including a consent prompt it is itself waiting on, so it is
|
||||||
|
never part of a session mask, never part of "all", and never
|
||||||
|
delegated by the compositor to what it launches. */
|
||||||
|
static constexpr uint64_t CAP_COMPOSITOR = 1ULL << 13;
|
||||||
|
/* Set the backlight of the active display (SYS_DISPLAYBRIGHTNESS).
|
||||||
|
A seat preference rather than display administration, so every
|
||||||
|
session holds it; CAP_DISPLAY_ADMIN keeps mode setting. Reading the
|
||||||
|
level needs nothing. */
|
||||||
|
static constexpr uint64_t CAP_BRIGHTNESS = 1ULL << 14;
|
||||||
|
static constexpr uint64_t CAP_ALL = (1ULL << 15) - 1;
|
||||||
|
static constexpr uint64_t CAP_STANDARD_SESSION =
|
||||||
|
CAP_POWER_REQUEST | CAP_SUSPEND | CAP_BRIGHTNESS;
|
||||||
static constexpr uint64_t CAP_ADMIN_SESSION =
|
static constexpr uint64_t CAP_ADMIN_SESSION =
|
||||||
CAP_STANDARD_SESSION | CAP_PROCESS_ADMIN | CAP_STORAGE_ADMIN |
|
CAP_STANDARD_SESSION | CAP_PROCESS_ADMIN | CAP_STORAGE_ADMIN |
|
||||||
CAP_RAW_STORAGE | CAP_NETWORK_ADMIN | CAP_SET_TIME | CAP_USER_ADMIN |
|
CAP_RAW_STORAGE | CAP_NETWORK_ADMIN | CAP_SET_TIME | CAP_USER_ADMIN |
|
||||||
@@ -378,28 +453,63 @@ namespace montauk::abi {
|
|||||||
static_assert((CAP_ADMIN_SESSION & CAP_SYSTEM_IMAGE) == 0,
|
static_assert((CAP_ADMIN_SESSION & CAP_SYSTEM_IMAGE) == 0,
|
||||||
"an admin session must not imply authority to rewrite the "
|
"an admin session must not imply authority to rewrite the "
|
||||||
"programs it launches; grant CAP_SYSTEM_IMAGE per binary");
|
"programs it launches; grant CAP_SYSTEM_IMAGE per binary");
|
||||||
|
static_assert((CAP_ADMIN_SESSION & CAP_COMPOSITOR) == 0,
|
||||||
|
"a session must not delegate the compositor's authority; "
|
||||||
|
"login grants it to the desktop directly");
|
||||||
|
// Capability denial for the Wi-Fi and USB syscalls only, which keep their
|
||||||
|
// own WIFI_ERR_* / USB_ERR_* spaces; everything else returns
|
||||||
|
// CapabilityDenied from StatusCodes.
|
||||||
static constexpr int SYS_ERR_PERMISSION = -13;
|
static constexpr int SYS_ERR_PERMISSION = -13;
|
||||||
|
|
||||||
|
// A process holds two independent sets: what it may use (effective) and
|
||||||
|
// what it may pass to children (delegable). A launcher can delegate what
|
||||||
|
// it never uses itself, so neither set bounds the other.
|
||||||
struct SpawnCapabilities {
|
struct SpawnCapabilities {
|
||||||
uint64_t permitted;
|
|
||||||
uint64_t effective;
|
uint64_t effective;
|
||||||
uint64_t delegable;
|
uint64_t delegable;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Both of the child's sets must come out of the parent's delegable set.
|
||||||
constexpr bool ValidCapabilityDelegation(const SpawnCapabilities& child,
|
constexpr bool ValidCapabilityDelegation(const SpawnCapabilities& child,
|
||||||
uint64_t parentDelegable) {
|
uint64_t parentDelegable) {
|
||||||
return (child.permitted & ~CAP_ALL) == 0 &&
|
return ((child.effective | child.delegable) & ~CAP_ALL) == 0 &&
|
||||||
(child.effective & ~child.permitted) == 0 &&
|
(child.effective & ~parentDelegable) == 0 &&
|
||||||
(child.delegable & ~child.permitted) == 0 &&
|
|
||||||
(child.permitted & ~parentDelegable) == 0 &&
|
|
||||||
(child.delegable & ~parentDelegable) == 0;
|
(child.delegable & ~parentDelegable) == 0;
|
||||||
}
|
}
|
||||||
static_assert(ValidCapabilityDelegation(
|
static_assert(ValidCapabilityDelegation(
|
||||||
{CAP_NETWORK_ADMIN, CAP_NETWORK_ADMIN, 0}, CAP_NETWORK_ADMIN));
|
{CAP_NETWORK_ADMIN, 0}, CAP_NETWORK_ADMIN));
|
||||||
|
static_assert(ValidCapabilityDelegation(
|
||||||
|
{0, CAP_NETWORK_ADMIN}, CAP_NETWORK_ADMIN));
|
||||||
static_assert(!ValidCapabilityDelegation(
|
static_assert(!ValidCapabilityDelegation(
|
||||||
{CAP_NETWORK_ADMIN, CAP_NETWORK_ADMIN, CAP_NETWORK_ADMIN}, 0));
|
{CAP_NETWORK_ADMIN, CAP_NETWORK_ADMIN}, 0));
|
||||||
static_assert(!ValidCapabilityDelegation(
|
static_assert(!ValidCapabilityDelegation(
|
||||||
{CAP_NETWORK_ADMIN, CAP_NETWORK_ADMIN | CAP_SET_TIME, 0}, CAP_ALL));
|
{CAP_NETWORK_ADMIN | CAP_SET_TIME, 0}, CAP_NETWORK_ADMIN));
|
||||||
|
static_assert(!ValidCapabilityDelegation(
|
||||||
|
{0, CAP_NETWORK_ADMIN | CAP_SET_TIME}, CAP_NETWORK_ADMIN));
|
||||||
|
|
||||||
|
/* Capabilities a user can grant at first use through a consent prompt.
|
||||||
|
A consent grant never exceeds what the process's parent could have
|
||||||
|
delegated to it at spawn (its consent ceiling), is never delegable
|
||||||
|
onwards, and is issued only by the session's consent agent -- so it is
|
||||||
|
deferred delegation, not amplification. */
|
||||||
|
static constexpr uint64_t CAP_CONSENT_ELIGIBLE =
|
||||||
|
CAP_STORAGE_ADMIN | CAP_NETWORK_ADMIN | CAP_SET_TIME |
|
||||||
|
CAP_DISPLAY_ADMIN | CAP_LOG_READ | CAP_BRIGHTNESS;
|
||||||
|
static_assert((CAP_CONSENT_ELIGIBLE & ~CAP_ADMIN_SESSION) == 0,
|
||||||
|
"consent can only hand out authority an admin session holds");
|
||||||
|
|
||||||
|
static constexpr int CONSENT_DENY = 0;
|
||||||
|
static constexpr int CONSENT_ALLOW = 1;
|
||||||
|
// A request left unanswered this long is denied (and not remembered).
|
||||||
|
static constexpr uint64_t CONSENT_TIMEOUT_MS = 120000;
|
||||||
|
|
||||||
|
struct ConsentRequest {
|
||||||
|
uint64_t id;
|
||||||
|
uint64_t capability; // exactly one CAP_* bit
|
||||||
|
int32_t pid;
|
||||||
|
uint32_t imageProtected; // 1 when writing the image needs CAP_SYSTEM_IMAGE
|
||||||
|
char imagePath[256];
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
// Generic USB errors. Claims are restricted to interfaces without a
|
// Generic USB errors. Claims are restricted to interfaces without a
|
||||||
@@ -540,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;
|
||||||
@@ -556,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;
|
||||||
@@ -589,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;
|
||||||
@@ -619,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;
|
||||||
@@ -635,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
|
||||||
@@ -676,6 +964,28 @@ namespace montauk::abi {
|
|||||||
char typeName[24]; // human-readable type name
|
char typeName[24]; // human-readable type name
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// One mounted volume (returned by SYS_VOLINFO). A volume is a single
|
||||||
|
// mounted filesystem, which is what a drive number names: one physical
|
||||||
|
// disk with three partitions occupies three drive numbers.
|
||||||
|
//
|
||||||
|
// A volume with no backing partition -- the boot ramdisk -- reports
|
||||||
|
// blockDev and partIndex as -1 and leaves the geometry fields zero.
|
||||||
|
struct VolInfo {
|
||||||
|
int32_t drive; // VFS drive number ("N:/")
|
||||||
|
int32_t blockDev; // backing block device index, -1 if none
|
||||||
|
int32_t partIndex; // global GPT partition index, -1 if none
|
||||||
|
uint32_t kind; // BlockDeviceKind: 0=none/ramdisk, 1=SATA, 2=SATAPI, 3=NVMe, 4=USB MSC
|
||||||
|
uint64_t startLba; // partition start LBA, 0 if none
|
||||||
|
uint64_t sectorCount; // partition length in sectors, 0 if none
|
||||||
|
uint32_t sectorSize; // logical sector size in bytes, 0 if none
|
||||||
|
uint32_t _pad0;
|
||||||
|
char label[36]; // filesystem volume label, "" if unset
|
||||||
|
char fsType[12]; // "ext2", "FAT32", "ramdisk", "" if unknown
|
||||||
|
char partName[72]; // GPT partition name, "" if none
|
||||||
|
char model[41]; // backing disk model, "" if none
|
||||||
|
char _pad1[3];
|
||||||
|
};
|
||||||
|
|
||||||
struct GptAddParams {
|
struct GptAddParams {
|
||||||
int32_t blockDev;
|
int32_t blockDev;
|
||||||
uint32_t _pad0;
|
uint32_t _pad0;
|
||||||
@@ -695,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;
|
||||||
@@ -703,7 +1021,6 @@ namespace montauk::abi {
|
|||||||
char name[64];
|
char name[64];
|
||||||
uint64_t heapUsed; // Distance from UserHeapBase to high-water mark
|
uint64_t heapUsed; // Distance from UserHeapBase to high-water mark
|
||||||
uint64_t cpuTimeMs; // accumulated scheduler runtime
|
uint64_t cpuTimeMs; // accumulated scheduler runtime
|
||||||
uint64_t permittedCaps;
|
|
||||||
uint64_t effectiveCaps;
|
uint64_t effectiveCaps;
|
||||||
uint64_t delegableCaps;
|
uint64_t delegableCaps;
|
||||||
};
|
};
|
||||||
@@ -929,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"
|
||||||
@@ -32,14 +32,18 @@ namespace montauk::abi {
|
|||||||
|
|
||||||
static int64_t Sys_SetTZ(int32_t offsetMinutes) {
|
static int64_t Sys_SetTZ(int32_t offsetMinutes) {
|
||||||
Timekeeping::SetTZOffset(offsetMinutes);
|
Timekeeping::SetTZOffset(offsetMinutes);
|
||||||
return 0;
|
return Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int64_t Sys_GetTZ() {
|
static int64_t Sys_GetTZ() {
|
||||||
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) ? 0 : -1;
|
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;
|
||||||
}
|
}
|
||||||
|
|||||||
+253
-56
@@ -54,14 +54,14 @@ 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 = -1;
|
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 = 0;
|
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,22 +101,67 @@ namespace WinServer {
|
|||||||
ResetSlotLocked(slot);
|
ResetSlotLocked(slot);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int SendEventLocked(int windowId, const montauk::abi::WinEvent* event) {
|
// ---- Messaging state ----
|
||||||
if (windowId < 0 || windowId >= MaxWindows || event == nullptr) return -1;
|
|
||||||
WindowSlot& slot = g_slots[windowId];
|
|
||||||
if (!slot.used || slot.eventMailbox == nullptr) return -1;
|
|
||||||
|
|
||||||
int rc = (event->type == 1)
|
static constexpr int MaxRequests = 64;
|
||||||
? Ipc::MailboxSendCoalescedMouse(slot.eventMailbox, 0, event, sizeof(*event))
|
// One app may not hold more than this many queued requests, so a single
|
||||||
: Ipc::MailboxSend(slot.eventMailbox, 0, event, sizeof(*event));
|
// misbehaving process cannot starve the others out of the queue.
|
||||||
return rc > 0 ? 0 : -1;
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
int Create(int ownerPid, uint64_t ownerPml4, const char* title, int w, int h,
|
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) {
|
||||||
|
if (windowId < 0 || windowId >= MaxWindows || event == nullptr) return InvalidArgument;
|
||||||
|
WindowSlot& slot = g_slots[windowId];
|
||||||
|
if (!slot.used || slot.eventMailbox == nullptr) return InvalidArgument;
|
||||||
|
|
||||||
|
// Delivering a drag event is what lets the receiving app read the
|
||||||
|
// 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));
|
||||||
|
if (rc < 0) return GenericFailure;
|
||||||
|
return rc > 0 ? Success : Busy;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
if (w <= 0 || h <= 0 || w > 16384 || h > 16384) return -1;
|
if (w <= 0 || h <= 0 || w > 16384 || h > 16384) return InvalidArgument;
|
||||||
|
|
||||||
int slotIdx = -1;
|
int slotIdx = -1;
|
||||||
for (int i = 0; i < MaxWindows; i++) {
|
for (int i = 0; i < MaxWindows; i++) {
|
||||||
@@ -118,7 +170,7 @@ namespace WinServer {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (slotIdx < 0) return -1;
|
if (slotIdx < 0) return GenericFailure;
|
||||||
|
|
||||||
uint64_t bufSize = (uint64_t)w * (uint64_t)h * 4ULL;
|
uint64_t bufSize = (uint64_t)w * (uint64_t)h * 4ULL;
|
||||||
Ipc::Surface* live = Ipc::CreateSurface(bufSize);
|
Ipc::Surface* live = Ipc::CreateSurface(bufSize);
|
||||||
@@ -128,7 +180,7 @@ namespace WinServer {
|
|||||||
if (live != nullptr) Ipc::ReleaseSurface(live);
|
if (live != nullptr) Ipc::ReleaseSurface(live);
|
||||||
if (snapshot != nullptr) Ipc::ReleaseSurface(snapshot);
|
if (snapshot != nullptr) Ipc::ReleaseSurface(snapshot);
|
||||||
if (mailbox != nullptr) Ipc::ReleaseMailbox(mailbox, false, false);
|
if (mailbox != nullptr) Ipc::ReleaseMailbox(mailbox, false, false);
|
||||||
return -1;
|
return OutOfMemory;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ipc::RetainSurface(live);
|
Ipc::RetainSurface(live);
|
||||||
@@ -142,11 +194,11 @@ 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);
|
||||||
return -1;
|
return GenericFailure;
|
||||||
}
|
}
|
||||||
|
|
||||||
WindowSlot& slot = g_slots[slotIdx];
|
WindowSlot& slot = g_slots[slotIdx];
|
||||||
@@ -176,40 +228,41 @@ namespace WinServer {
|
|||||||
|
|
||||||
int Destroy(int windowId, int callerPid) {
|
int Destroy(int windowId, int callerPid) {
|
||||||
WsGuard guard;
|
WsGuard guard;
|
||||||
if (windowId < 0 || windowId >= MaxWindows) return -1;
|
if (windowId < 0 || windowId >= MaxWindows) return InvalidArgument;
|
||||||
|
|
||||||
WindowSlot& slot = g_slots[windowId];
|
WindowSlot& slot = g_slots[windowId];
|
||||||
if (!slot.used || slot.ownerPid != callerPid) return -1;
|
if (!slot.used) return InvalidArgument;
|
||||||
|
if (slot.ownerPid != callerPid) return PermissionDenied;
|
||||||
|
|
||||||
ReleaseSlotResourcesLocked(slot, true);
|
ReleaseSlotResourcesLocked(slot, true);
|
||||||
return 0;
|
return Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
int Present(int windowId, int callerPid) {
|
int Present(int windowId, int callerPid) {
|
||||||
WsGuard guard;
|
WsGuard guard;
|
||||||
if (windowId < 0 || windowId >= MaxWindows) return -1;
|
if (windowId < 0 || windowId >= MaxWindows) return InvalidArgument;
|
||||||
|
|
||||||
WindowSlot& slot = g_slots[windowId];
|
WindowSlot& slot = g_slots[windowId];
|
||||||
if (!slot.used || slot.ownerPid != callerPid ||
|
if (!slot.used || slot.liveSurface == nullptr || slot.snapshotSurface == nullptr)
|
||||||
slot.liveSurface == nullptr || slot.snapshotSurface == nullptr) {
|
return InvalidArgument;
|
||||||
return -1;
|
if (slot.ownerPid != callerPid) return PermissionDenied;
|
||||||
}
|
|
||||||
|
|
||||||
if (Ipc::CopySurface(slot.snapshotSurface, slot.liveSurface) != 0) return -1;
|
if (Ipc::CopySurface(slot.snapshotSurface, slot.liveSurface) != 0) return GenericFailure;
|
||||||
slot.dirty = true;
|
slot.dirty = true;
|
||||||
return 0;
|
return Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
int Poll(int windowId, int callerPid, montauk::abi::WinEvent* outEvent) {
|
int Poll(int windowId, int callerPid, montauk::abi::WinEvent* outEvent) {
|
||||||
WsGuard guard;
|
WsGuard guard;
|
||||||
if (windowId < 0 || windowId >= MaxWindows || outEvent == nullptr) return -1;
|
if (windowId < 0 || windowId >= MaxWindows || outEvent == nullptr) return InvalidArgument;
|
||||||
|
|
||||||
WindowSlot& slot = g_slots[windowId];
|
WindowSlot& slot = g_slots[windowId];
|
||||||
if (!slot.used || slot.ownerPid != callerPid || slot.eventMailbox == nullptr) return -1;
|
if (!slot.used || slot.eventMailbox == nullptr) return InvalidArgument;
|
||||||
|
if (slot.ownerPid != callerPid) return PermissionDenied;
|
||||||
|
|
||||||
uint16_t len = sizeof(*outEvent);
|
uint16_t len = sizeof(*outEvent);
|
||||||
int rc = Ipc::MailboxRecv(slot.eventMailbox, nullptr, outEvent, &len, true);
|
int rc = Ipc::MailboxRecv(slot.eventMailbox, nullptr, outEvent, &len, true);
|
||||||
if (rc < 0) return -1;
|
if (rc < 0) return GenericFailure;
|
||||||
return rc > 0 ? 1 : 0;
|
return rc > 0 ? 1 : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -234,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;
|
||||||
|
|
||||||
@@ -242,24 +295,24 @@ 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 0;
|
if (current == 0) return Success;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -268,17 +321,18 @@ 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 -1;
|
if (windowId < 0 || windowId >= MaxWindows) return InvalidArgument;
|
||||||
|
|
||||||
WindowSlot& slot = g_slots[windowId];
|
WindowSlot& slot = g_slots[windowId];
|
||||||
if (!slot.used || slot.ownerPid != callerPid) return -1;
|
if (!slot.used) return InvalidArgument;
|
||||||
if (newW <= 0 || newH <= 0 || newW > 16384 || newH > 16384) return -1;
|
if (slot.ownerPid != callerPid) return PermissionDenied;
|
||||||
|
if (newW <= 0 || newH <= 0 || newW > 16384 || newH > 16384) return InvalidArgument;
|
||||||
if (newW == slot.width && newH == slot.height) {
|
if (newW == slot.width && newH == slot.height) {
|
||||||
outVa = slot.ownerVa;
|
outVa = slot.ownerVa;
|
||||||
return 0;
|
return Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint64_t bufSize = (uint64_t)newW * (uint64_t)newH * 4ULL;
|
uint64_t bufSize = (uint64_t)newW * (uint64_t)newH * 4ULL;
|
||||||
@@ -287,17 +341,17 @@ namespace WinServer {
|
|||||||
if (newLive == nullptr || newSnapshot == nullptr) {
|
if (newLive == nullptr || newSnapshot == nullptr) {
|
||||||
if (newLive != nullptr) Ipc::ReleaseSurface(newLive);
|
if (newLive != nullptr) Ipc::ReleaseSurface(newLive);
|
||||||
if (newSnapshot != nullptr) Ipc::ReleaseSurface(newSnapshot);
|
if (newSnapshot != nullptr) Ipc::ReleaseSurface(newSnapshot);
|
||||||
return -1;
|
return OutOfMemory;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ipc::RetainSurface(newLive);
|
Ipc::RetainSurface(newLive);
|
||||||
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 -1;
|
return GenericFailure;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (slot.liveSurface != nullptr) {
|
if (slot.liveSurface != nullptr) {
|
||||||
@@ -312,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) {
|
||||||
@@ -327,27 +381,29 @@ namespace WinServer {
|
|||||||
slot.dirty = true;
|
slot.dirty = true;
|
||||||
|
|
||||||
outVa = newOwnerVa;
|
outVa = newOwnerVa;
|
||||||
return 0;
|
return Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
int SetCursor(int windowId, int callerPid, int cursor) {
|
int SetCursor(int windowId, int callerPid, int cursor) {
|
||||||
WsGuard guard;
|
WsGuard guard;
|
||||||
if (windowId < 0 || windowId >= MaxWindows) return -1;
|
if (windowId < 0 || windowId >= MaxWindows) return InvalidArgument;
|
||||||
|
|
||||||
WindowSlot& slot = g_slots[windowId];
|
WindowSlot& slot = g_slots[windowId];
|
||||||
if (!slot.used || slot.ownerPid != callerPid) return -1;
|
if (!slot.used) return InvalidArgument;
|
||||||
|
if (slot.ownerPid != callerPid) return PermissionDenied;
|
||||||
slot.cursor = (uint8_t)cursor;
|
slot.cursor = (uint8_t)cursor;
|
||||||
return 0;
|
return Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
int SetFlags(int windowId, int callerPid, uint32_t flags) {
|
int SetFlags(int windowId, int callerPid, uint32_t flags) {
|
||||||
WsGuard guard;
|
WsGuard guard;
|
||||||
if (windowId < 0 || windowId >= MaxWindows) return -1;
|
if (windowId < 0 || windowId >= MaxWindows) return InvalidArgument;
|
||||||
|
|
||||||
WindowSlot& slot = g_slots[windowId];
|
WindowSlot& slot = g_slots[windowId];
|
||||||
if (!slot.used || slot.ownerPid != callerPid) return -1;
|
if (!slot.used) return InvalidArgument;
|
||||||
|
if (slot.ownerPid != callerPid) return PermissionDenied;
|
||||||
slot.flags = flags & montauk::abi::WIN_FLAG_FULLSCREEN;
|
slot.flags = flags & montauk::abi::WIN_FLAG_FULLSCREEN;
|
||||||
return 0;
|
return Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
int SetScale(int scale) {
|
int SetScale(int scale) {
|
||||||
@@ -364,19 +420,160 @@ namespace WinServer {
|
|||||||
SendEventLocked(i, &ev);
|
SendEventLocked(i, &ev);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return 0;
|
return Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
int GetScale() {
|
int GetScale() {
|
||||||
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);
|
||||||
|
|
||||||
}
|
}
|
||||||
+56
-11
@@ -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
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@@ -15,16 +17,16 @@
|
|||||||
namespace montauk::abi {
|
namespace montauk::abi {
|
||||||
|
|
||||||
static int Sys_WinCreate(const char* title, int w, int h, WinCreateResult* result) {
|
static int Sys_WinCreate(const char* title, int w, int h, WinCreateResult* result) {
|
||||||
if (result == nullptr || title == nullptr) return -1;
|
if (result == nullptr || title == nullptr) return InvalidArgument;
|
||||||
auto* proc = Sched::GetCurrentProcessPtr();
|
auto* proc = Sched::GetCurrentProcessPtr();
|
||||||
if (proc == nullptr) return -1;
|
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;
|
||||||
return id >= 0 ? 0 : -1;
|
return id >= 0 ? Success : id;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int Sys_WinDestroy(int windowId) {
|
static int Sys_WinDestroy(int windowId) {
|
||||||
@@ -36,7 +38,7 @@ namespace montauk::abi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static int Sys_WinPoll(int windowId, WinEvent* outEvent) {
|
static int Sys_WinPoll(int windowId, WinEvent* outEvent) {
|
||||||
if (outEvent == nullptr) return -1;
|
if (outEvent == nullptr) return InvalidArgument;
|
||||||
return WinServer::Poll(windowId, Sched::GetCurrentPid(), outEvent);
|
return WinServer::Poll(windowId, Sched::GetCurrentPid(), outEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -45,20 +47,26 @@ namespace montauk::abi {
|
|||||||
return WinServer::Enumerate(outArray, maxCount);
|
return WinServer::Enumerate(outArray, maxCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Mapping another process's window and injecting input into it are the
|
||||||
|
// compositor's job alone. Left open, any app could read every window's
|
||||||
|
// pixels or click on any window -- including a prompt asking the user
|
||||||
|
// whether to grant that same app authority.
|
||||||
static uint64_t Sys_WinMap(int windowId) {
|
static uint64_t Sys_WinMap(int windowId) {
|
||||||
|
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 -1;
|
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) {
|
||||||
if (event == nullptr) return -1;
|
if (!Sched::HasCapability(CAP_COMPOSITOR)) return CapabilityDenied;
|
||||||
|
if (event == nullptr) return InvalidArgument;
|
||||||
return WinServer::SendEvent(windowId, event);
|
return WinServer::SendEvent(windowId, event);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -66,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;
|
||||||
}
|
}
|
||||||
@@ -86,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,18 +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/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;
|
||||||
@@ -128,9 +131,9 @@ namespace Hal {
|
|||||||
const char* stepNames[] = {
|
const char* stepNames[] = {
|
||||||
"?", "GDT", "TSS", "Syscalls", "PAT", "PIC",
|
"?", "GDT", "TSS", "Syscalls", "PAT", "PIC",
|
||||||
"LocalAPIC", "IoAPIC", "APICTimer", "PM1Clear",
|
"LocalAPIC", "IoAPIC", "APICTimer", "PM1Clear",
|
||||||
"WAK", "PS2", "IntelGPU", "sti"
|
"WAK", "PS2", "IntelGPU", "dGPU", "sti"
|
||||||
};
|
};
|
||||||
const char* stepName = reinitStep <= 0x0D ? stepNames[reinitStep] : "?";
|
const char* stepName = reinitStep <= 0x0E ? stepNames[reinitStep] : "?";
|
||||||
KernelLogStream(WARNING, "S3") << "Reinit stopped after step 0x"
|
KernelLogStream(WARNING, "S3") << "Reinit stopped after step 0x"
|
||||||
<< base::hex << (uint64_t)reinitStep << " (" << stepName
|
<< base::hex << (uint64_t)reinitStep << " (" << stepName
|
||||||
<< ") - next step crashed/hung";
|
<< ") - next step crashed/hung";
|
||||||
@@ -185,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
|
||||||
@@ -313,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);
|
||||||
@@ -438,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
|
||||||
@@ -446,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
|
||||||
@@ -459,8 +462,10 @@ namespace Hal {
|
|||||||
if (Drivers::Graphics::IntelGPU::IsInitialized()) {
|
if (Drivers::Graphics::IntelGPU::IsInitialized()) {
|
||||||
Drivers::Graphics::IntelGPU::Reinitialize();
|
Drivers::Graphics::IntelGPU::Reinitialize();
|
||||||
}
|
}
|
||||||
reinitProgress(0x0D); // sti
|
reinitProgress(0x0D); // discrete GPU back off
|
||||||
asm volatile("sti");
|
Drivers::Graphics::DgpuPower::Resume();
|
||||||
|
reinitProgress(0x0E); // sti
|
||||||
|
Arch::EnableInterrupts();
|
||||||
|
|
||||||
KernelLogStream(OK, "S3") << "Resumed from S3 suspend";
|
KernelLogStream(OK, "S3") << "Resumed from S3 suspend";
|
||||||
|
|
||||||
@@ -514,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.
|
||||||
@@ -522,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();
|
||||||
@@ -545,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);
|
||||||
@@ -560,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);
|
||||||
@@ -571,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>
|
||||||
@@ -249,13 +250,17 @@ namespace Hal {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static bool ParseCstObject(const char* path, const AML::Object& obj) {
|
static bool ParseCstObject(const char* path, const AML::Object& obj) {
|
||||||
if (obj.Type != AML::ObjectType::Package || obj.Buffer.Length < 2) {
|
if (obj.Type != AML::ObjectType::Package || !obj.Package.Data ||
|
||||||
|
obj.Package.Length < 1) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t pos = 1; // package element count byte
|
// Package.Data starts at the first element (the state count).
|
||||||
|
const uint8_t* data = obj.Package.Data;
|
||||||
|
uint32_t length = obj.Package.Length;
|
||||||
|
uint32_t pos = 0;
|
||||||
uint64_t declaredStateCount = 0;
|
uint64_t declaredStateCount = 0;
|
||||||
if (!DecodeInteger(obj.Buffer.Data, obj.Buffer.Length, pos, declaredStateCount)) {
|
if (!DecodeInteger(data, length, pos, declaredStateCount)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -263,7 +268,7 @@ namespace Hal {
|
|||||||
for (uint64_t i = 0; i < declaredStateCount && parsedCount < MaxIdleStates; i++) {
|
for (uint64_t i = 0; i < declaredStateCount && parsedCount < MaxIdleStates; i++) {
|
||||||
const uint8_t* nestedData = nullptr;
|
const uint8_t* nestedData = nullptr;
|
||||||
uint32_t nestedLength = 0;
|
uint32_t nestedLength = 0;
|
||||||
if (!DecodePackageObject(obj.Buffer.Data, obj.Buffer.Length, pos, nestedData, nestedLength)) {
|
if (!DecodePackageObject(data, length, pos, nestedData, nestedLength)) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -310,7 +315,10 @@ namespace Hal {
|
|||||||
auto& ns = interp.GetNamespace();
|
auto& ns = interp.GetNamespace();
|
||||||
char path[256];
|
char path[256];
|
||||||
|
|
||||||
for (int32_t nodeIndex = 0; nodeIndex < ns.NodeCount(); nodeIndex++) {
|
// DSDT processors only, for the same reason as the _CST scan.
|
||||||
|
int32_t limit = interp.DsdtNodeCount();
|
||||||
|
if (limit == 0) limit = ns.NodeCount();
|
||||||
|
for (int32_t nodeIndex = 0; nodeIndex < limit; nodeIndex++) {
|
||||||
auto* node = ns.GetNode(nodeIndex);
|
auto* node = ns.GetNode(nodeIndex);
|
||||||
if (node == nullptr || node->Obj.Type != AML::ObjectType::Processor) {
|
if (node == nullptr || node->Obj.Type != AML::ObjectType::Processor) {
|
||||||
continue;
|
continue;
|
||||||
@@ -473,7 +481,7 @@ namespace Hal {
|
|||||||
if (hasMwait) {
|
if (hasMwait) {
|
||||||
Hal::IdleWaitWithInterruptsDisabled(addr, CurrentIdleHint());
|
Hal::IdleWaitWithInterruptsDisabled(addr, CurrentIdleHint());
|
||||||
} else {
|
} else {
|
||||||
Hal::HaltWithInterruptsDisabled();
|
Arch::HaltWithInterruptsDisabled();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -522,7 +530,13 @@ namespace Hal {
|
|||||||
auto& ns = interp.GetNamespace();
|
auto& ns = interp.GetNamespace();
|
||||||
char path[256];
|
char path[256];
|
||||||
|
|
||||||
for (int32_t nodeIndex = 0; nodeIndex < ns.NodeCount(); nodeIndex++) {
|
// DSDT objects only. SSDTs are loaded too now (for device power
|
||||||
|
// methods), and many carry processor _CST tables; those C-state
|
||||||
|
// lists have never been exercised against the deep-idle timer
|
||||||
|
// behaviour on real laptops, so they stay out until they have.
|
||||||
|
int32_t limit = interp.DsdtNodeCount();
|
||||||
|
if (limit == 0) limit = ns.NodeCount();
|
||||||
|
for (int32_t nodeIndex = 0; nodeIndex < limit; nodeIndex++) {
|
||||||
auto* node = ns.GetNode(nodeIndex);
|
auto* node = ns.GetNode(nodeIndex);
|
||||||
if (node == nullptr) continue;
|
if (node == nullptr) continue;
|
||||||
if (!IsName(node->Name, "_CST")) continue;
|
if (!IsName(node->Name, "_CST")) continue;
|
||||||
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.
Loaded 100 of 591 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user