Compare commits
61
Commits
5b90869101
..
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 | ||
|
|
e641500b40 | ||
|
|
0a18b2cf2d |
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.
|
||||||
-117
@@ -1,117 +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.
|
|
||||||
|
|
||||||
## 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).
|
|
||||||
|
|
||||||
The `power` program (`programs/src/power`) prints it:
|
|
||||||
|
|
||||||
```
|
|
||||||
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
|
|
||||||
```
|
|
||||||
|
|
||||||
## 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 = .;
|
||||||
}
|
}
|
||||||
+2888
-790
File diff suppressed because it is too large.
Load diff
@@ -7,6 +7,7 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "AmlNamespace.hpp"
|
#include "AmlNamespace.hpp"
|
||||||
|
#include <CppLib/Spinlock.hpp>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
|
||||||
namespace Hal {
|
namespace Hal {
|
||||||
@@ -96,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;
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
@@ -103,7 +133,29 @@ 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
|
||||||
|
// expression evaluation AND block execution, and block nesting is
|
||||||
|
// legitimately deep -- every If/Else/While body is another level, so a
|
||||||
|
// long firmware method reaches well past a handful. The real protection
|
||||||
|
// against running off the stack is HaveStackHeadroom(), which measures
|
||||||
|
// actual usage; this only catches a decode that recurses without
|
||||||
|
// consuming input.
|
||||||
|
static constexpr int MaxExprDepth = 48;
|
||||||
|
|
||||||
|
// How much stack the interpreter may consume before it gives up. Note
|
||||||
|
// this is an ALLOWANCE, not a reserve: frames here are expensive (a
|
||||||
|
// name-resolving EvalTerm frame carries a 256-byte path buffer plus
|
||||||
|
// 272-byte Object temporaries, so ~800 bytes each), and a real
|
||||||
|
// firmware _BIF nests far enough to need tens of them. Sized against
|
||||||
|
// Sched::StackPages -- keep well under it, since the syscall path below
|
||||||
|
// has already spent some of that stack.
|
||||||
|
static constexpr uint64_t StackBudget = 32768;
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
@@ -114,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.
|
||||||
@@ -125,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);
|
||||||
|
|
||||||
@@ -138,18 +214,67 @@ 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
|
||||||
|
// `budget` opcodes. The only way to see where a method that runs to
|
||||||
|
// completion without returning a value actually goes.
|
||||||
|
void SetTrace(int budget) { m_traceBudget = budget; }
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
// Packages
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Read one element of a Package object. Works for both a package
|
||||||
|
// whose elements were computed at run time (materialized in the
|
||||||
|
// package store) and one that is still just the constant AML bytes
|
||||||
|
// it was declared with. A name in a constant package comes back as
|
||||||
|
// 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);
|
||||||
|
|
||||||
|
// Number of elements a Package declares.
|
||||||
|
int GetPackageCount(const Object& package);
|
||||||
|
|
||||||
|
// Give a package stored in a namespace node real per-element
|
||||||
|
// storage so Index() can be written through. Returns the store
|
||||||
|
// index, or -1 if the node does not hold a package.
|
||||||
|
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);
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
@@ -158,8 +283,21 @@ namespace Hal {
|
|||||||
// ============================================================================
|
// ============================================================================
|
||||||
// Read a NameString from AML and produce an absolute path.
|
// Read a NameString from AML and produce an absolute path.
|
||||||
// Advances *pos past the name.
|
// Advances *pos past the name.
|
||||||
|
// Produces the absolute path (rebased onto scopeNode for a
|
||||||
|
// relative name). outRelative, when given, receives the name as it
|
||||||
|
// actually appeared in the AML, and *outSearchable says whether the
|
||||||
|
// ACPI search rule applies to it -- true only when the name carried
|
||||||
|
// no root or parent prefix. Resolution must fall back to
|
||||||
|
// Namespace::SearchUpward() in that case: the absolute path is a
|
||||||
|
// guess at one scope, and the object usually lives in a parent.
|
||||||
int ReadNameString(const uint8_t* aml, uint32_t* pos, int32_t scopeNode,
|
int ReadNameString(const uint8_t* aml, uint32_t* pos, int32_t scopeNode,
|
||||||
char* outPath, int maxLen);
|
char* outPath, int maxLen,
|
||||||
|
char* outRelative = nullptr, int relLen = 0,
|
||||||
|
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);
|
||||||
@@ -170,6 +308,12 @@ 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
|
||||||
|
// advance *pos past it. Names come back as NameRef; locals and
|
||||||
|
// anything else that needs a running method decode to integer 0.
|
||||||
|
bool DecodeConstElement(const uint8_t* data, uint32_t length,
|
||||||
|
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);
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
@@ -187,7 +331,28 @@ namespace Hal {
|
|||||||
Object ReturnValue;
|
Object ReturnValue;
|
||||||
bool Returned;
|
bool Returned;
|
||||||
bool Broken;
|
bool Broken;
|
||||||
int Depth;
|
bool Continued;
|
||||||
|
bool Aborted; // undecodable AML: unwind the method
|
||||||
|
int Depth; // method-call nesting
|
||||||
|
int ExprDepth; // expression/block nesting
|
||||||
|
};
|
||||||
|
|
||||||
|
// Increments a depth counter for the duration of a scope; pairs
|
||||||
|
// with the MaxExprDepth check at each recursive entry point.
|
||||||
|
struct DepthGuard {
|
||||||
|
int& Value;
|
||||||
|
explicit DepthGuard(int& v) : Value(v) { 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);
|
||||||
@@ -196,35 +361,140 @@ namespace Hal {
|
|||||||
// 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.
|
|
||||||
bool EvalTarget(ExecContext& ctx, uint32_t* pos,
|
|
||||||
int32_t& nodeIndex, bool& isLocal, int& localIdx,
|
|
||||||
bool& isArg, int& argIdx);
|
|
||||||
|
|
||||||
// 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);
|
||||||
|
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);
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
// State
|
// State
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
// ExecContext is ~4.4 KB (sixteen Objects, each carrying a
|
||||||
|
// 256-byte buffer inline). One per nested method call on a 16 KiB
|
||||||
|
// kernel stack overflows after three levels, so they come from a
|
||||||
|
// pool instead. The pool is only safe because evaluation is
|
||||||
|
// serialized -- see m_evalLock.
|
||||||
|
// Stack-exhaustion check; `frame` is the address of a local in
|
||||||
|
// the calling frame.
|
||||||
|
bool HaveStackHeadroom(const void* frame) const;
|
||||||
|
|
||||||
|
// One-shot warning when the nesting backstop trips.
|
||||||
|
static void ReportDepthLimit();
|
||||||
|
|
||||||
|
ExecContext* AcquireContext();
|
||||||
|
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
|
||||||
|
int m_ctxInUse;
|
||||||
|
int m_evalNesting; // top-level evaluations in progress
|
||||||
|
uint64_t m_stackFloor; // stack address the interpreter stops at
|
||||||
|
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;
|
||||||
};
|
};
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|||||||
@@ -7,10 +7,118 @@
|
|||||||
#include "AmlNamespace.hpp"
|
#include "AmlNamespace.hpp"
|
||||||
#include <Libraries/Memory.hpp>
|
#include <Libraries/Memory.hpp>
|
||||||
#include <Memory/Heap.hpp>
|
#include <Memory/Heap.hpp>
|
||||||
|
#include <Terminal/Terminal.hpp>
|
||||||
|
#include <CppLib/Stream.hpp>
|
||||||
|
|
||||||
|
using namespace Kt;
|
||||||
|
|
||||||
namespace Hal {
|
namespace Hal {
|
||||||
namespace AML {
|
namespace AML {
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
// Package element store
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
namespace PackageStore {
|
||||||
|
|
||||||
|
struct Entry {
|
||||||
|
Object* Elements;
|
||||||
|
int Count;
|
||||||
|
};
|
||||||
|
|
||||||
|
static Entry s_persistent[MaxPersistent];
|
||||||
|
static Entry s_transient[MaxTransient];
|
||||||
|
static int s_persistentUsed = 0;
|
||||||
|
static int s_transientNext = 0;
|
||||||
|
|
||||||
|
// Indices handed out are 1-based so that a zeroed Object (nodes are
|
||||||
|
// memset before Clear() runs, and callers zero-init freely) never
|
||||||
|
// looks like a reference to entry 0.
|
||||||
|
static constexpr int32_t TransientBase = MaxPersistent;
|
||||||
|
|
||||||
|
static Object* AllocElements(int count) {
|
||||||
|
auto* mem = (Object*)Memory::g_heap->Request(sizeof(Object) * count);
|
||||||
|
if (!mem) return nullptr;
|
||||||
|
for (int i = 0; i < count; i++)
|
||||||
|
mem[i] = Object{};
|
||||||
|
return mem;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t AllocPersistent(int count) {
|
||||||
|
if (count <= 0 || count > MaxPackageElements) return -1;
|
||||||
|
if (s_persistentUsed >= MaxPersistent) return -1;
|
||||||
|
|
||||||
|
Entry& e = s_persistent[s_persistentUsed];
|
||||||
|
e.Elements = AllocElements(count);
|
||||||
|
if (!e.Elements) return -1;
|
||||||
|
e.Count = count;
|
||||||
|
return (int32_t)(s_persistentUsed++) + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t AllocTransient(int count) {
|
||||||
|
if (count <= 0 || count > MaxPackageElements) return -1;
|
||||||
|
|
||||||
|
int slot = s_transientNext;
|
||||||
|
if (slot >= MaxTransient) {
|
||||||
|
// The ring is only rewound between top-level evaluations, so
|
||||||
|
// a method that builds more packages than this reuses the
|
||||||
|
// oldest slots rather than failing outright.
|
||||||
|
slot = 0;
|
||||||
|
s_transientNext = 0;
|
||||||
|
}
|
||||||
|
s_transientNext++;
|
||||||
|
|
||||||
|
Entry& e = s_transient[slot];
|
||||||
|
// Element arrays are sized on first use and reused afterwards;
|
||||||
|
// a larger request reallocates.
|
||||||
|
if (e.Elements == nullptr || e.Count < count) {
|
||||||
|
Object* mem = AllocElements(count);
|
||||||
|
if (!mem) return -1;
|
||||||
|
if (e.Elements) Memory::g_heap->Free(e.Elements);
|
||||||
|
e.Elements = mem;
|
||||||
|
} else {
|
||||||
|
for (int i = 0; i < count; i++)
|
||||||
|
e.Elements[i] = Object{};
|
||||||
|
}
|
||||||
|
e.Count = count;
|
||||||
|
return TransientBase + slot + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ResetTransient() {
|
||||||
|
s_transientNext = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsPersistent(int32_t index) {
|
||||||
|
return index > 0 && index <= MaxPersistent;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Entry* Lookup(int32_t index) {
|
||||||
|
if (index <= 0) return nullptr;
|
||||||
|
int32_t i = index - 1;
|
||||||
|
|
||||||
|
if (i < MaxPersistent) {
|
||||||
|
if (i >= s_persistentUsed) return nullptr;
|
||||||
|
return s_persistent[i].Elements ? &s_persistent[i] : nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
i -= TransientBase;
|
||||||
|
if (i < 0 || i >= MaxTransient) return nullptr;
|
||||||
|
return s_transient[i].Elements ? &s_transient[i] : nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
Object* Element(int32_t index, int element) {
|
||||||
|
Entry* e = Lookup(index);
|
||||||
|
if (!e || element < 0 || element >= e->Count) return nullptr;
|
||||||
|
return &e->Elements[element];
|
||||||
|
}
|
||||||
|
|
||||||
|
int Count(int32_t index) {
|
||||||
|
Entry* e = Lookup(index);
|
||||||
|
return e ? e->Count : 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
Namespace::Namespace() : m_chunkCount(0), m_nodeCount(0) {
|
Namespace::Namespace() : m_chunkCount(0), m_nodeCount(0) {
|
||||||
for (int i = 0; i < MaxChunks; i++)
|
for (int i = 0; i < MaxChunks; i++)
|
||||||
m_chunks[i] = nullptr;
|
m_chunks[i] = nullptr;
|
||||||
@@ -30,7 +138,17 @@ namespace Hal {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool Namespace::AllocChunk() {
|
bool Namespace::AllocChunk() {
|
||||||
if (m_chunkCount >= MaxChunks) return false;
|
if (m_chunkCount >= MaxChunks) {
|
||||||
|
static bool warned = false;
|
||||||
|
if (!warned) {
|
||||||
|
warned = true;
|
||||||
|
KernelLogStream(WARNING, "AML")
|
||||||
|
<< "Namespace node pool full at " << base::dec
|
||||||
|
<< (uint64_t)(MaxChunks * NodesPerChunk)
|
||||||
|
<< " nodes; part of the namespace will be missing";
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
auto* chunk = (NamespaceNode*)Memory::g_heap->Request(
|
auto* chunk = (NamespaceNode*)Memory::g_heap->Request(
|
||||||
sizeof(NamespaceNode) * NodesPerChunk);
|
sizeof(NamespaceNode) * NodesPerChunk);
|
||||||
@@ -45,6 +163,41 @@ namespace Hal {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Namespace::AppendChild(NamespaceNode* parent, int32_t child) {
|
||||||
|
if (parent->ChildCount >= parent->ChildCapacity) {
|
||||||
|
int32_t newCap = parent->ChildCapacity ? parent->ChildCapacity * 2
|
||||||
|
: InitialChildren;
|
||||||
|
|
||||||
|
auto* grown = (int32_t*)Memory::g_heap->Request(sizeof(int32_t) * newCap);
|
||||||
|
if (!grown) {
|
||||||
|
// Losing a child silently costs whole devices, so say it
|
||||||
|
// once rather than leaving a hole nobody can see.
|
||||||
|
static bool warned = false;
|
||||||
|
if (!warned) {
|
||||||
|
warned = true;
|
||||||
|
KernelLogStream(WARNING, "AML")
|
||||||
|
<< "Out of heap growing a namespace node's child list;"
|
||||||
|
<< " part of the namespace will be missing";
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int32_t i = 0; i < parent->ChildCount; i++)
|
||||||
|
grown[i] = parent->ChildIndices[i];
|
||||||
|
for (int32_t i = parent->ChildCount; i < newCap; i++)
|
||||||
|
grown[i] = -1;
|
||||||
|
|
||||||
|
if (parent->ChildIndices)
|
||||||
|
Memory::g_heap->Free(parent->ChildIndices);
|
||||||
|
|
||||||
|
parent->ChildIndices = grown;
|
||||||
|
parent->ChildCapacity = newCap;
|
||||||
|
}
|
||||||
|
|
||||||
|
parent->ChildIndices[parent->ChildCount++] = child;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
int32_t Namespace::AllocNode() {
|
int32_t Namespace::AllocNode() {
|
||||||
// Check if we need a new chunk
|
// Check if we need a new chunk
|
||||||
int32_t capacity = m_chunkCount * NodesPerChunk;
|
int32_t capacity = m_chunkCount * NodesPerChunk;
|
||||||
@@ -144,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];
|
||||||
@@ -165,11 +324,7 @@ namespace Hal {
|
|||||||
// Add to parent's children
|
// Add to parent's children
|
||||||
auto* parent = GetNode(current);
|
auto* parent = GetNode(current);
|
||||||
if (!parent) return -1;
|
if (!parent) return -1;
|
||||||
if (parent->ChildCount < MaxChildren) {
|
if (!AppendChild(parent, child)) return -1;
|
||||||
parent->ChildIndices[parent->ChildCount++] = child;
|
|
||||||
} else {
|
|
||||||
return -1; // too many children
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
current = child;
|
current = child;
|
||||||
}
|
}
|
||||||
@@ -188,6 +343,51 @@ namespace Hal {
|
|||||||
return current;
|
return current;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int32_t Namespace::SearchUpward(const char* relative, int32_t scopeNodeIndex) const {
|
||||||
|
char segments[MaxPathDepth][MaxNameSegLen + 1];
|
||||||
|
int segCount = 0;
|
||||||
|
|
||||||
|
// Split on '.', padding each segment to four characters the way
|
||||||
|
// node names are stored.
|
||||||
|
int i = 0;
|
||||||
|
while (relative[i] && segCount < MaxPathDepth) {
|
||||||
|
char seg[MaxNameSegLen + 1];
|
||||||
|
int n = 0;
|
||||||
|
while (relative[i] && relative[i] != '.' && n < MaxNameSegLen)
|
||||||
|
seg[n++] = relative[i++];
|
||||||
|
seg[n] = '\0';
|
||||||
|
while (relative[i] && relative[i] != '.') i++; // overlong segment
|
||||||
|
if (relative[i] == '.') i++;
|
||||||
|
|
||||||
|
PadSegment(seg, segments[segCount]);
|
||||||
|
segCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (segCount == 0) return -1;
|
||||||
|
|
||||||
|
// Walk up from the current scope looking for the first segment.
|
||||||
|
int32_t scope = scopeNodeIndex;
|
||||||
|
while (scope >= 0) {
|
||||||
|
int32_t found = FindChildByName(scope, segments[0]);
|
||||||
|
if (found >= 0) {
|
||||||
|
// Descend the rest. A scope that matches the first segment
|
||||||
|
// but not the remainder does not win -- keep walking up.
|
||||||
|
int32_t cur = found;
|
||||||
|
bool ok = true;
|
||||||
|
for (int k = 1; k < segCount; k++) {
|
||||||
|
cur = FindChildByName(cur, segments[k]);
|
||||||
|
if (cur < 0) { ok = false; break; }
|
||||||
|
}
|
||||||
|
if (ok) return cur;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto* node = GetNode(scope);
|
||||||
|
scope = node ? node->ParentIndex : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
int32_t Namespace::ResolveName(const char* name, int32_t scopeNodeIndex) const {
|
int32_t Namespace::ResolveName(const char* name, int32_t scopeNodeIndex) const {
|
||||||
// If it starts with '\', it's absolute
|
// If it starts with '\', it's absolute
|
||||||
if (name[0] == '\\')
|
if (name[0] == '\\')
|
||||||
|
|||||||
@@ -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,
|
||||||
};
|
};
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
@@ -55,10 +65,15 @@ namespace Hal {
|
|||||||
// ============================================================================
|
// ============================================================================
|
||||||
static constexpr int MaxNameSegLen = 4;
|
static constexpr int MaxNameSegLen = 4;
|
||||||
static constexpr int MaxPathDepth = 16;
|
static constexpr int MaxPathDepth = 16;
|
||||||
static constexpr int MaxChildren = 64;
|
// Children are held in a heap array that grows on demand, starting at
|
||||||
|
// this many entries. A fixed ceiling here is not survivable: an EC or
|
||||||
|
// GPE scope in a modern laptop DSDT carries well over a hundred named
|
||||||
|
// objects, and dropping the overflow loses whole devices (the battery
|
||||||
|
// among them) with no way to tell from the outside.
|
||||||
|
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 = 16;
|
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;
|
||||||
|
|
||||||
@@ -76,6 +91,14 @@ namespace Hal {
|
|||||||
struct Object {
|
struct Object {
|
||||||
ObjectType Type = ObjectType::None;
|
ObjectType Type = ObjectType::None;
|
||||||
|
|
||||||
|
// Packages keep their raw AML bytes in Buffer (constant elements can
|
||||||
|
// be decoded straight from there), but a package whose elements are
|
||||||
|
// computed or written through Index() needs real per-element
|
||||||
|
// storage. PkgIndex names that storage once it is materialized;
|
||||||
|
// -1 means "not materialized yet". It lives outside the union so a
|
||||||
|
// package can carry both representations at once.
|
||||||
|
int32_t PkgIndex = -1;
|
||||||
|
|
||||||
union {
|
union {
|
||||||
uint64_t Integer;
|
uint64_t Integer;
|
||||||
|
|
||||||
@@ -89,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;
|
||||||
|
|
||||||
@@ -103,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;
|
||||||
@@ -116,7 +173,51 @@ namespace Hal {
|
|||||||
} Processor;
|
} Processor;
|
||||||
};
|
};
|
||||||
|
|
||||||
Object() : Type(ObjectType::None), 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
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Element arrays for materialized packages, kept outside Object so the
|
||||||
|
// tagged union stays small. Entries are split in two: a persistent
|
||||||
|
// region for packages declared with Name() (materialized once and kept
|
||||||
|
// for the life of the namespace) and a transient ring for packages
|
||||||
|
// built inside a method, which is rewound at each top-level evaluation
|
||||||
|
// so repeated calls -- polling _BST, say -- cannot exhaust it.
|
||||||
|
namespace PackageStore {
|
||||||
|
static constexpr int MaxPersistent = 32;
|
||||||
|
static constexpr int MaxTransient = 32;
|
||||||
|
|
||||||
|
// Allocate an element array of `count` objects. Returns -1 when the
|
||||||
|
// store is full or the count is out of range.
|
||||||
|
int32_t AllocPersistent(int count);
|
||||||
|
int32_t AllocTransient(int count);
|
||||||
|
|
||||||
|
// Rewind the transient ring. Called once per top-level evaluation.
|
||||||
|
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
|
||||||
|
// out-of-range element.
|
||||||
|
Object* Element(int32_t index, int element);
|
||||||
|
int Count(int32_t index);
|
||||||
};
|
};
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
@@ -129,16 +230,19 @@ namespace Hal {
|
|||||||
char Name[MaxNameSegLen + 1]; // null-terminated 4-char segment
|
char Name[MaxNameSegLen + 1]; // null-terminated 4-char segment
|
||||||
Object Obj;
|
Object Obj;
|
||||||
int32_t ParentIndex; // -1 for root
|
int32_t ParentIndex; // -1 for root
|
||||||
int32_t ChildIndices[MaxChildren];
|
int32_t* ChildIndices; // heap array, ChildCapacity long
|
||||||
int32_t ChildCount;
|
int32_t ChildCount;
|
||||||
|
int32_t ChildCapacity;
|
||||||
|
|
||||||
|
// Only ever called on a freshly allocated (zeroed) node, so there is
|
||||||
|
// no child array to release here.
|
||||||
void Clear() {
|
void Clear() {
|
||||||
Name[0] = 0;
|
Name[0] = 0;
|
||||||
Obj = Object{};
|
Obj = Object{};
|
||||||
ParentIndex = -1;
|
ParentIndex = -1;
|
||||||
|
ChildIndices = nullptr;
|
||||||
ChildCount = 0;
|
ChildCount = 0;
|
||||||
for (int i = 0; i < MaxChildren; i++)
|
ChildCapacity = 0;
|
||||||
ChildIndices[i] = -1;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -166,6 +270,18 @@ namespace Hal {
|
|||||||
// 3. Root scope
|
// 3. Root scope
|
||||||
int32_t ResolveName(const char* name, int32_t scopeNodeIndex) const;
|
int32_t ResolveName(const char* name, int32_t scopeNodeIndex) const;
|
||||||
|
|
||||||
|
// The ACPI name search rule: for a name written WITHOUT a root or
|
||||||
|
// parent prefix, search the current scope for its first segment,
|
||||||
|
// then each parent scope up to the root, and descend the remaining
|
||||||
|
// segments from wherever it matched. `relative` is the name as it
|
||||||
|
// appeared in the AML ("PBIF", "BAT0.PBIF"), never a path already
|
||||||
|
// rebased onto a scope. Returns -1 if no scope contains it.
|
||||||
|
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;
|
||||||
@@ -217,6 +333,11 @@ namespace Hal {
|
|||||||
private:
|
private:
|
||||||
int32_t AllocNode();
|
int32_t AllocNode();
|
||||||
bool AllocChunk();
|
bool AllocChunk();
|
||||||
|
|
||||||
|
// Append a child index to a parent, growing its array as needed.
|
||||||
|
// Returns false only when the heap is exhausted.
|
||||||
|
bool AppendChild(NamespaceNode* parent, int32_t child);
|
||||||
|
|
||||||
void EnsureRoot();
|
void EnsureRoot();
|
||||||
int32_t FindChildByName(int32_t parentIndex, const char* seg) const;
|
int32_t FindChildByName(int32_t parentIndex, const char* seg) 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();
|
||||||
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@@ -319,6 +319,58 @@ namespace Hal {
|
|||||||
|
|
||||||
// EnumerateAll
|
// EnumerateAll
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Fill one DeviceInfo from a namespace node.
|
||||||
|
static void DescribeDevice(AML::Namespace& ns, int32_t idx, DeviceInfo& dev) {
|
||||||
|
dev.NodeIndex = idx;
|
||||||
|
ns.GetNodePath(idx, dev.Path, 128);
|
||||||
|
|
||||||
|
dev.Status = EvaluateSta(idx);
|
||||||
|
dev.IsPresent = (dev.Status & STA_PRESENT) && (dev.Status & STA_FUNCTIONAL);
|
||||||
|
|
||||||
|
if (!EvaluateHid(idx, dev.HardwareId, 16))
|
||||||
|
dev.HardwareId[0] = '\0';
|
||||||
|
if (!EvaluateUid(idx, dev.UniqueId, 16))
|
||||||
|
dev.UniqueId[0] = '\0';
|
||||||
|
|
||||||
|
dev.Address = EvaluateAdr(idx);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
// FindDeviceByHid
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
bool FindDeviceByHid(const char* hid, DeviceInfo& out) {
|
||||||
|
auto& interp = AML::GetInterpreter();
|
||||||
|
if (!interp.IsInitialized()) return false;
|
||||||
|
|
||||||
|
auto& ns = interp.GetNamespace();
|
||||||
|
int32_t found = -1;
|
||||||
|
|
||||||
|
// Only _HID is evaluated per candidate; the full description is
|
||||||
|
// built once, for the match. WalkDescendants has no early exit, so
|
||||||
|
// the first match wins and later ones are ignored.
|
||||||
|
ns.WalkDescendants(ns.RootIndex(), AML::ObjectType::Device,
|
||||||
|
[&](int32_t idx, [[maybe_unused]] const AML::NamespaceNode* node) {
|
||||||
|
if (found >= 0) return;
|
||||||
|
|
||||||
|
char candidate[16];
|
||||||
|
if (!EvaluateHid(idx, candidate, 16)) return;
|
||||||
|
if (StrEqual(candidate, hid)) found = idx;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (found < 0) return false;
|
||||||
|
|
||||||
|
out = DeviceInfo{};
|
||||||
|
DescribeDevice(ns, found, out);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
// EnumerateAll
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
void EnumerateAll(DeviceList& result) {
|
void EnumerateAll(DeviceList& result) {
|
||||||
auto& interp = AML::GetInterpreter();
|
auto& interp = AML::GetInterpreter();
|
||||||
@@ -326,39 +378,32 @@ namespace Hal {
|
|||||||
|
|
||||||
auto& ns = interp.GetNamespace();
|
auto& ns = interp.GetNamespace();
|
||||||
result.Count = 0;
|
result.Count = 0;
|
||||||
|
result.Truncated = false;
|
||||||
|
|
||||||
// Walk all namespace nodes looking for Device objects
|
// Walk all namespace nodes looking for Device objects
|
||||||
ns.WalkDescendants(ns.RootIndex(), AML::ObjectType::Device,
|
ns.WalkDescendants(ns.RootIndex(), AML::ObjectType::Device,
|
||||||
[&](int32_t idx, [[maybe_unused]] const AML::NamespaceNode* node) {
|
[&](int32_t idx, [[maybe_unused]] const AML::NamespaceNode* node) {
|
||||||
if (result.Count >= MaxDevices) return;
|
if (result.Count >= MaxDevices) {
|
||||||
|
result.Truncated = true;
|
||||||
auto& dev = result.Devices[result.Count];
|
return;
|
||||||
dev.NodeIndex = idx;
|
}
|
||||||
|
|
||||||
// Get the path
|
|
||||||
ns.GetNodePath(idx, dev.Path, 128);
|
|
||||||
|
|
||||||
// Evaluate _STA
|
|
||||||
dev.Status = EvaluateSta(idx);
|
|
||||||
dev.IsPresent = (dev.Status & STA_PRESENT) && (dev.Status & STA_FUNCTIONAL);
|
|
||||||
|
|
||||||
// Evaluate _HID
|
|
||||||
if (!EvaluateHid(idx, dev.HardwareId, 16))
|
|
||||||
dev.HardwareId[0] = '\0';
|
|
||||||
|
|
||||||
// Evaluate _UID
|
|
||||||
if (!EvaluateUid(idx, dev.UniqueId, 16))
|
|
||||||
dev.UniqueId[0] = '\0';
|
|
||||||
|
|
||||||
// Evaluate _ADR
|
|
||||||
dev.Address = EvaluateAdr(idx);
|
|
||||||
|
|
||||||
|
DescribeDevice(ns, idx, result.Devices[result.Count]);
|
||||||
result.Count++;
|
result.Count++;
|
||||||
});
|
});
|
||||||
|
|
||||||
KernelLogStream(OK, "ACPI") << "Enumerated " << base::dec
|
KernelLogStream(OK, "ACPI") << "Enumerated " << base::dec
|
||||||
<< (uint64_t)result.Count << " ACPI devices";
|
<< (uint64_t)result.Count << " ACPI devices";
|
||||||
|
|
||||||
|
// The list is a fixed-size stack object, so a large namespace runs
|
||||||
|
// past it. Say so: a device missing from the log below may simply
|
||||||
|
// be one that did not fit, and looking it up by HID still works.
|
||||||
|
if (result.Truncated) {
|
||||||
|
KernelLogStream(WARNING, "ACPI") << "Device list truncated at "
|
||||||
|
<< base::dec << (uint64_t)MaxDevices
|
||||||
|
<< "; use FindDeviceByHid to reach the rest";
|
||||||
|
}
|
||||||
|
|
||||||
// Log discovered devices
|
// Log discovered devices
|
||||||
for (int i = 0; i < result.Count; i++) {
|
for (int i = 0; i < result.Count; i++) {
|
||||||
auto& dev = result.Devices[i];
|
auto& dev = result.Devices[i];
|
||||||
@@ -408,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
|
||||||
|
|||||||
@@ -45,8 +45,9 @@ namespace Hal {
|
|||||||
struct DeviceList {
|
struct DeviceList {
|
||||||
DeviceInfo Devices[MaxDevices];
|
DeviceInfo Devices[MaxDevices];
|
||||||
int Count;
|
int Count;
|
||||||
|
bool Truncated; // the namespace held more than MaxDevices
|
||||||
|
|
||||||
DeviceList() : Count(0) {}
|
DeviceList() : Count(0), Truncated(false) {}
|
||||||
|
|
||||||
// Find a device by HID. Returns nullptr if not found.
|
// Find a device by HID. Returns nullptr if not found.
|
||||||
const DeviceInfo* FindByHid(const char* hid) const;
|
const DeviceInfo* FindByHid(const char* hid) const;
|
||||||
@@ -64,6 +65,12 @@ namespace Hal {
|
|||||||
// The interpreter must be initialized first (LoadTable called).
|
// The interpreter must be initialized first (LoadTable called).
|
||||||
void EnumerateAll(DeviceList& result);
|
void EnumerateAll(DeviceList& result);
|
||||||
|
|
||||||
|
// Find the first device with the given _HID, walking the namespace
|
||||||
|
// directly. Unlike EnumerateAll this has no device-count ceiling and
|
||||||
|
// needs no multi-kilobyte list on the caller's stack, so it is the way
|
||||||
|
// to reach a specific device on a machine whose namespace is large.
|
||||||
|
bool FindDeviceByHid(const char* hid, DeviceInfo& out);
|
||||||
|
|
||||||
// Evaluate _STA for a device node. Returns STA_DEFAULT if no _STA exists.
|
// Evaluate _STA for a device node. Returns STA_DEFAULT if no _STA exists.
|
||||||
uint32_t EvaluateSta(int32_t deviceNodeIndex);
|
uint32_t EvaluateSta(int32_t deviceNodeIndex);
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -0,0 +1,422 @@
|
|||||||
|
/*
|
||||||
|
* Battery.cpp
|
||||||
|
* ACPI Control Method Battery: _STA / _BIF / _BIX / _BST, plus _PSR on the
|
||||||
|
* AC adapter
|
||||||
|
* Copyright (c) 2026 Daniel Hammer
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "Battery.hpp"
|
||||||
|
#include "AcpiDevices.hpp"
|
||||||
|
#include <ACPI/AML/AmlInterpreter.hpp>
|
||||||
|
#include <CppLib/Spinlock.hpp>
|
||||||
|
#include <Timekeeping/Timer.hpp>
|
||||||
|
#include <Terminal/Terminal.hpp>
|
||||||
|
#include <CppLib/Stream.hpp>
|
||||||
|
|
||||||
|
using namespace Kt;
|
||||||
|
|
||||||
|
namespace Hal {
|
||||||
|
namespace Battery {
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
// State
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// ACPI reports an unreadable field as all-ones rather than zero.
|
||||||
|
static constexpr uint32_t Unknown32 = 0xFFFFFFFFu;
|
||||||
|
|
||||||
|
// The EC transactions behind _BST take a few milliseconds, and the
|
||||||
|
// value only moves on a scale of minutes, so a short cache keeps a
|
||||||
|
// one-second panel repaint from becoming a bus workload.
|
||||||
|
static constexpr uint64_t CacheMs = 2000;
|
||||||
|
|
||||||
|
static bool g_initialized = false;
|
||||||
|
static bool g_haveBattery = false;
|
||||||
|
static bool g_haveAdapter = false;
|
||||||
|
static char g_batteryPath[128];
|
||||||
|
static char g_adapterPath[128];
|
||||||
|
static int32_t g_batteryNode = -1;
|
||||||
|
|
||||||
|
static kcp::Mutex g_lock;
|
||||||
|
|
||||||
|
// Set for the boot-path probe only, so the per-method tracing does not
|
||||||
|
// repeat on every poll once the system is up.
|
||||||
|
static bool g_tracing = false;
|
||||||
|
|
||||||
|
static bool Refresh(Status& out);
|
||||||
|
static void Probe();
|
||||||
|
static Status g_cached{};
|
||||||
|
static bool g_cacheValid = false;
|
||||||
|
static uint64_t g_cacheStamp = 0;
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
// Path helpers
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
static void AppendMethod(const char* devicePath, const char* method, char* out, int maxLen) {
|
||||||
|
int i = 0;
|
||||||
|
while (devicePath[i] && i < maxLen - 6) {
|
||||||
|
out[i] = devicePath[i];
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
// The root scope is written "\" with no separator before the first
|
||||||
|
// segment; every other path takes a dot.
|
||||||
|
if (!(i == 1 && out[0] == '\\'))
|
||||||
|
out[i++] = '.';
|
||||||
|
for (int j = 0; j < 4 && i < maxLen - 1; j++)
|
||||||
|
out[i++] = method[j];
|
||||||
|
out[i] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
static void CopyPath(char* dst, const char* src, int maxLen) {
|
||||||
|
int i = 0;
|
||||||
|
while (src[i] && i < maxLen - 1) {
|
||||||
|
dst[i] = src[i];
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
dst[i] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
// Initialize
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
void Initialize() {
|
||||||
|
if (g_initialized) return;
|
||||||
|
g_initialized = true;
|
||||||
|
|
||||||
|
auto& interp = AML::GetInterpreter();
|
||||||
|
if (!interp.IsInitialized()) return;
|
||||||
|
|
||||||
|
// Searched by HID rather than off an enumerated list: a laptop
|
||||||
|
// namespace runs well past the list's fixed size, and the battery
|
||||||
|
// sits deep under the EC where it would be cut off.
|
||||||
|
AcpiDevices::DeviceInfo bat{};
|
||||||
|
if (AcpiDevices::FindDeviceByHid(AcpiDevices::HID_BATTERY, bat)) {
|
||||||
|
CopyPath(g_batteryPath, bat.Path, sizeof(g_batteryPath));
|
||||||
|
g_batteryNode = bat.NodeIndex;
|
||||||
|
g_haveBattery = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
AcpiDevices::DeviceInfo ac{};
|
||||||
|
if (AcpiDevices::FindDeviceByHid(AcpiDevices::HID_AC_ADAPTER, ac)) {
|
||||||
|
CopyPath(g_adapterPath, ac.Path, sizeof(g_adapterPath));
|
||||||
|
g_haveAdapter = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!g_haveBattery) {
|
||||||
|
// Distinguish "this machine has no battery" from "the namespace
|
||||||
|
// walk found nothing at all", which would point at the walk
|
||||||
|
// rather than the hardware.
|
||||||
|
auto& ns = interp.GetNamespace();
|
||||||
|
int deviceCount = 0;
|
||||||
|
ns.WalkDescendants(ns.RootIndex(), AML::ObjectType::Device,
|
||||||
|
[&](int32_t, const AML::NamespaceNode*) { deviceCount++; });
|
||||||
|
|
||||||
|
KernelLogStream(INFO, "Battery") << "No ACPI battery device (PNP0C0A) among "
|
||||||
|
<< base::dec << (uint64_t)deviceCount << " ACPI devices";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
KernelLogStream(OK, "Battery") << "ACPI battery at " << g_batteryPath
|
||||||
|
<< (g_haveAdapter ? " (AC adapter present)" : " (no AC adapter device)");
|
||||||
|
|
||||||
|
Probe();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Evaluate the battery methods once, here on the boot path, announcing
|
||||||
|
// each before it runs. These are the first firmware methods this kernel
|
||||||
|
// executes that reach the embedded controller, and if one of them takes
|
||||||
|
// the machine down the last line printed says which -- far better than
|
||||||
|
// discovering it when the desktop makes its first query.
|
||||||
|
static void Probe() {
|
||||||
|
KernelLogStream(INFO, "Battery") << "Probing battery methods";
|
||||||
|
|
||||||
|
g_tracing = true;
|
||||||
|
Status s{};
|
||||||
|
bool ok = Refresh(s);
|
||||||
|
g_tracing = false;
|
||||||
|
|
||||||
|
if (!ok) {
|
||||||
|
KernelLogStream(WARNING, "Battery") << "Probe failed; battery status unavailable";
|
||||||
|
g_haveBattery = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_cached = s;
|
||||||
|
g_cacheStamp = 0; // let the first real query refresh
|
||||||
|
g_cacheValid = true;
|
||||||
|
|
||||||
|
if (!s.Present) {
|
||||||
|
KernelLogStream(INFO, "Battery") << "Battery slot empty"
|
||||||
|
<< (s.AcOnline ? " (on AC)" : "");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
KernelLogStream(OK, "Battery") << "Battery at " << base::dec
|
||||||
|
<< (uint64_t)s.Percent << "% ("
|
||||||
|
<< (s.Charging ? "charging" : (s.Discharging ? "discharging" : "idle"))
|
||||||
|
<< ", " << (uint64_t)s.RemainingCapacity << " of "
|
||||||
|
<< (uint64_t)s.FullCapacity << (s.PowerUnitMilliAmps ? " mAh)" : " mWh)");
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsPresent() {
|
||||||
|
return g_haveBattery;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
// Method evaluation
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
static const char* TypeName(AML::ObjectType t) {
|
||||||
|
switch (t) {
|
||||||
|
case AML::ObjectType::None: return "none";
|
||||||
|
case AML::ObjectType::Integer: return "integer";
|
||||||
|
case AML::ObjectType::String: return "string";
|
||||||
|
case AML::ObjectType::Buffer: return "buffer";
|
||||||
|
case AML::ObjectType::Package: return "package";
|
||||||
|
case AML::ObjectType::Device: return "device";
|
||||||
|
case AML::ObjectType::Method: return "method";
|
||||||
|
case AML::ObjectType::OperationRegion: return "opregion";
|
||||||
|
case AML::ObjectType::Field: return "field";
|
||||||
|
default: return "other";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool EvalPackage(const char* devicePath, const char* method, AML::Object& out) {
|
||||||
|
char path[160];
|
||||||
|
AppendMethod(devicePath, method, path, sizeof(path));
|
||||||
|
|
||||||
|
auto& interp = AML::GetInterpreter();
|
||||||
|
|
||||||
|
// Separate "the method is not there" from "it ran and gave us
|
||||||
|
// something that is not a package": those need opposite fixes, and
|
||||||
|
// the failure looks identical from the caller.
|
||||||
|
int32_t node = interp.GetNamespace().FindNode(path);
|
||||||
|
if (node < 0) {
|
||||||
|
if (g_tracing)
|
||||||
|
KernelLogStream(INFO, "Battery") << " " << path << ": absent";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trace the opcodes of the one method whose behaviour we cannot
|
||||||
|
// otherwise explain: it runs to completion, trips no limit, and
|
||||||
|
// still yields no value.
|
||||||
|
bool traceThis = g_tracing && method[1] == 'B';
|
||||||
|
if (traceThis) interp.SetTrace(120);
|
||||||
|
|
||||||
|
bool ok = interp.EvaluateObject(path, out);
|
||||||
|
|
||||||
|
if (traceThis) interp.SetTrace(0);
|
||||||
|
|
||||||
|
if (g_tracing) {
|
||||||
|
KernelLogStream(INFO, "Battery") << " " << path << ": "
|
||||||
|
<< (ok ? "ok" : "FAILED") << ", returned " << TypeName(out.Type)
|
||||||
|
<< " with " << base::dec
|
||||||
|
<< (uint64_t)interp.GetPackageCount(out) << " element(s)";
|
||||||
|
}
|
||||||
|
|
||||||
|
return ok && out.Type == AML::ObjectType::Package;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint32_t ElementInteger(const AML::Object& pkg, int index) {
|
||||||
|
AML::Object elem{};
|
||||||
|
if (!AML::GetInterpreter().GetPackageElement(pkg, index, elem)) return Unknown32;
|
||||||
|
if (elem.Type != AML::ObjectType::Integer) return Unknown32;
|
||||||
|
return (uint32_t)elem.Integer;
|
||||||
|
}
|
||||||
|
|
||||||
|
// _BIX supersedes _BIF and prepends a Revision field, shifting every
|
||||||
|
// shared field by one. Both are read into the same three numbers.
|
||||||
|
struct BatteryInfo {
|
||||||
|
bool Valid;
|
||||||
|
bool PowerUnitMilliAmps;
|
||||||
|
uint32_t DesignCapacity;
|
||||||
|
uint32_t FullCapacity;
|
||||||
|
};
|
||||||
|
|
||||||
|
static bool ReadInfo(BatteryInfo& info) {
|
||||||
|
info = BatteryInfo{};
|
||||||
|
|
||||||
|
AML::Object pkg{};
|
||||||
|
int base = 0;
|
||||||
|
|
||||||
|
if (EvalPackage(g_batteryPath, "_BIX", pkg)) {
|
||||||
|
// _BIX revision 0 starts with Revision; later revisions keep it
|
||||||
|
// in the same place, so the offset is fixed at one.
|
||||||
|
base = 1;
|
||||||
|
} else if (EvalPackage(g_batteryPath, "_BIF", pkg)) {
|
||||||
|
base = 0;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t unit = ElementInteger(pkg, base + 0);
|
||||||
|
uint32_t design = ElementInteger(pkg, base + 1);
|
||||||
|
uint32_t full = ElementInteger(pkg, base + 2);
|
||||||
|
|
||||||
|
info.PowerUnitMilliAmps = (unit != Unknown32) && (unit & 1);
|
||||||
|
info.DesignCapacity = (design == Unknown32) ? 0 : design;
|
||||||
|
info.FullCapacity = (full == Unknown32) ? 0 : full;
|
||||||
|
info.Valid = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool AcOnline() {
|
||||||
|
if (!g_haveAdapter) return false;
|
||||||
|
|
||||||
|
char path[160];
|
||||||
|
AppendMethod(g_adapterPath, "_PSR", path, sizeof(path));
|
||||||
|
|
||||||
|
if (g_tracing)
|
||||||
|
KernelLogStream(INFO, "Battery") << " evaluating " << path;
|
||||||
|
|
||||||
|
AML::Object result{};
|
||||||
|
if (!AML::GetInterpreter().EvaluateObject(path, result)) return false;
|
||||||
|
return result.Type == AML::ObjectType::Integer && result.Integer != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
// GetStatus
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
static bool Refresh(Status& out) {
|
||||||
|
out = Status{};
|
||||||
|
out.MinutesRemaining = -1;
|
||||||
|
out.HasAcAdapter = g_haveAdapter;
|
||||||
|
|
||||||
|
// _STA bit 4 says whether a battery is actually in the slot. A
|
||||||
|
// laptop with the pack removed still has the device.
|
||||||
|
uint32_t sta = (g_batteryNode >= 0)
|
||||||
|
? AcpiDevices::EvaluateSta(g_batteryNode)
|
||||||
|
: AcpiDevices::STA_DEFAULT;
|
||||||
|
|
||||||
|
// Bit 4 is the one that decides everything below, and firmware
|
||||||
|
// clears it for reasons that have nothing to do with the pack
|
||||||
|
// (an EC that has not been told its region handler is live, most
|
||||||
|
// often), so log the raw value rather than just the verdict.
|
||||||
|
if (g_tracing) {
|
||||||
|
KernelLogStream(INFO, "Battery") << " _STA = " << base::hex
|
||||||
|
<< (uint64_t)sta << (((sta & AcpiDevices::STA_BATTERY) != 0)
|
||||||
|
? " (battery present)" : " (battery bit clear)");
|
||||||
|
}
|
||||||
|
|
||||||
|
out.AcOnline = AcOnline();
|
||||||
|
|
||||||
|
// _STA bit 4 is the spec's answer for "is a pack installed", but
|
||||||
|
// it is only as good as the firmware path behind it. Read the
|
||||||
|
// battery methods either way and let the capacity data have the
|
||||||
|
// final say -- see the staSaysAbsent handling below.
|
||||||
|
bool staSaysAbsent = (sta & AcpiDevices::STA_BATTERY) == 0;
|
||||||
|
|
||||||
|
BatteryInfo info{};
|
||||||
|
if (!ReadInfo(info)) {
|
||||||
|
if (g_tracing)
|
||||||
|
KernelLogStream(INFO, "Battery") << " no _BIX/_BIF package";
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
AML::Object bst{};
|
||||||
|
if (!EvalPackage(g_batteryPath, "_BST", bst)) {
|
||||||
|
if (g_tracing)
|
||||||
|
KernelLogStream(INFO, "Battery") << " no _BST package";
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t state = ElementInteger(bst, 0);
|
||||||
|
uint32_t rate = ElementInteger(bst, 1);
|
||||||
|
uint32_t remaining = ElementInteger(bst, 2);
|
||||||
|
uint32_t voltage = ElementInteger(bst, 3);
|
||||||
|
|
||||||
|
if (g_tracing) {
|
||||||
|
KernelLogStream(INFO, "Battery") << " _BIF/_BIX: design="
|
||||||
|
<< base::dec << (uint64_t)info.DesignCapacity
|
||||||
|
<< " lastFull=" << (uint64_t)info.FullCapacity
|
||||||
|
<< (info.PowerUnitMilliAmps ? " (mAh)" : " (mWh)");
|
||||||
|
KernelLogStream(INFO, "Battery") << " _BST: state=" << base::hex
|
||||||
|
<< (uint64_t)state << " rate=" << base::dec << (uint64_t)rate
|
||||||
|
<< " remaining=" << (uint64_t)remaining
|
||||||
|
<< " voltage=" << (uint64_t)voltage << "mV";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trust real capacity data over a status bit that says otherwise:
|
||||||
|
// firmware that reports a pack's charge is describing a pack that
|
||||||
|
// is there.
|
||||||
|
if (staSaysAbsent) {
|
||||||
|
bool looksReal = info.FullCapacity > 0 && info.FullCapacity != Unknown32
|
||||||
|
&& remaining != Unknown32;
|
||||||
|
if (!looksReal) {
|
||||||
|
if (g_tracing)
|
||||||
|
KernelLogStream(INFO, "Battery")
|
||||||
|
<< " _STA says absent and the methods agree";
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (g_tracing)
|
||||||
|
KernelLogStream(WARNING, "Battery")
|
||||||
|
<< " _STA says absent but _BST reports charge; trusting _BST";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state == Unknown32) state = 0;
|
||||||
|
|
||||||
|
out.Present = true;
|
||||||
|
out.Charging = (state & StateCharging) != 0;
|
||||||
|
out.Discharging = (state & StateDischarging) != 0;
|
||||||
|
out.Critical = (state & StateCritical) != 0;
|
||||||
|
|
||||||
|
out.PowerUnitMilliAmps = info.PowerUnitMilliAmps;
|
||||||
|
out.DesignCapacity = info.DesignCapacity;
|
||||||
|
out.FullCapacity = info.FullCapacity;
|
||||||
|
out.RemainingCapacity = (remaining == Unknown32) ? 0 : remaining;
|
||||||
|
out.PresentRate = (rate == Unknown32) ? 0 : rate;
|
||||||
|
out.VoltageMv = (voltage == Unknown32) ? 0 : voltage;
|
||||||
|
|
||||||
|
// Some firmware leaves last-full at zero or below the current
|
||||||
|
// reading after a swap; fall back to the design capacity, and
|
||||||
|
// clamp so the panel can never show 143%.
|
||||||
|
uint32_t full = out.FullCapacity;
|
||||||
|
if (full == 0) full = out.DesignCapacity;
|
||||||
|
|
||||||
|
if (full > 0 && out.RemainingCapacity > 0) {
|
||||||
|
uint64_t pct = ((uint64_t)out.RemainingCapacity * 100) / full;
|
||||||
|
out.Percent = (pct > 100) ? 100 : (uint8_t)pct;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (out.PresentRate > 0 && out.Discharging) {
|
||||||
|
out.MinutesRemaining =
|
||||||
|
(int32_t)(((uint64_t)out.RemainingCapacity * 60) / out.PresentRate);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GetStatus(Status& out) {
|
||||||
|
if (!g_haveBattery) return false;
|
||||||
|
|
||||||
|
g_lock.Acquire();
|
||||||
|
|
||||||
|
uint64_t now = Timekeeping::GetMilliseconds();
|
||||||
|
if (g_cacheValid && now - g_cacheStamp < CacheMs) {
|
||||||
|
out = g_cached;
|
||||||
|
g_lock.Release();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Status fresh{};
|
||||||
|
bool ok = Refresh(fresh);
|
||||||
|
if (ok) {
|
||||||
|
g_cached = fresh;
|
||||||
|
g_cacheStamp = now;
|
||||||
|
g_cacheValid = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
out = ok ? fresh : g_cached;
|
||||||
|
g_lock.Release();
|
||||||
|
return ok || g_cacheValid;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
/*
|
||||||
|
* Battery.hpp
|
||||||
|
* ACPI Control Method Battery (PNP0C0A) and AC adapter (ACPI0003)
|
||||||
|
* Copyright (c) 2026 Daniel Hammer
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
namespace Hal {
|
||||||
|
namespace Battery {
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
// Battery state
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// _BST battery state bits.
|
||||||
|
static constexpr uint32_t StateDischarging = (1 << 0);
|
||||||
|
static constexpr uint32_t StateCharging = (1 << 1);
|
||||||
|
static constexpr uint32_t StateCritical = (1 << 2);
|
||||||
|
|
||||||
|
struct Status {
|
||||||
|
bool Present; // a battery slot exists and reports a battery in it
|
||||||
|
bool Charging;
|
||||||
|
bool Discharging;
|
||||||
|
bool Critical;
|
||||||
|
bool AcOnline; // AC adapter connected (false when no adapter device)
|
||||||
|
bool HasAcAdapter; // an ACPI0003 device exists
|
||||||
|
|
||||||
|
uint8_t Percent; // 0..100, clamped; 0 when capacity is unknown
|
||||||
|
uint32_t RemainingCapacity; // mWh or mAh, per PowerUnitMilliAmps
|
||||||
|
uint32_t FullCapacity; // last full charge capacity, same unit
|
||||||
|
uint32_t DesignCapacity;
|
||||||
|
uint32_t PresentRate; // mW or mA, 0 when unknown
|
||||||
|
uint32_t VoltageMv; // present voltage in mV, 0 when unknown
|
||||||
|
bool PowerUnitMilliAmps; // capacities are mAh/mA rather than mWh/mW
|
||||||
|
|
||||||
|
// Minutes of runtime left at the present rate, or -1 when the rate
|
||||||
|
// is unknown/zero (which includes every charging battery).
|
||||||
|
int32_t MinutesRemaining;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
// API
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Discover battery and AC adapter devices in the ACPI namespace. Safe to
|
||||||
|
// call more than once; only the first call does work.
|
||||||
|
void Initialize();
|
||||||
|
|
||||||
|
// True when at least one battery device was found, whether or not a
|
||||||
|
// battery is currently installed in it.
|
||||||
|
bool IsPresent();
|
||||||
|
|
||||||
|
// Read the current status. Results are cached briefly so that polling
|
||||||
|
// from a panel indicator does not hammer the embedded controller.
|
||||||
|
// Returns false when the machine has no battery device at all.
|
||||||
|
bool GetStatus(Status& out);
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -0,0 +1,326 @@
|
|||||||
|
/*
|
||||||
|
* EmbeddedController.cpp
|
||||||
|
* ACPI Embedded Controller transport (polled, no burst mode)
|
||||||
|
* Copyright (c) 2026 Daniel Hammer
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "EmbeddedController.hpp"
|
||||||
|
#include "ACPI.hpp"
|
||||||
|
#include "AcpiDevices.hpp"
|
||||||
|
#include <ACPI/AML/AmlInterpreter.hpp>
|
||||||
|
#include <CppLib/Spinlock.hpp>
|
||||||
|
#include <Io/IoPort.hpp>
|
||||||
|
#include <Memory/HHDM.hpp>
|
||||||
|
#include <Terminal/Terminal.hpp>
|
||||||
|
#include <CppLib/Stream.hpp>
|
||||||
|
|
||||||
|
using namespace Kt;
|
||||||
|
|
||||||
|
namespace Hal {
|
||||||
|
namespace EmbeddedController {
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
// EC register protocol
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// EC_SC (status, read from the command port)
|
||||||
|
static constexpr uint8_t SC_OBF = (1 << 0); // output buffer full: data is readable
|
||||||
|
static constexpr uint8_t SC_IBF = (1 << 1); // input buffer full: EC has not consumed our byte
|
||||||
|
|
||||||
|
// EC_SC commands (written to the command port)
|
||||||
|
static constexpr uint8_t CMD_READ = 0x80;
|
||||||
|
static constexpr uint8_t CMD_WRITE = 0x81;
|
||||||
|
|
||||||
|
// The EC is a slow microcontroller; a byte transaction is typically a
|
||||||
|
// few hundred microseconds but firmware is allowed to take much longer.
|
||||||
|
// One "tick" is a port-wait (~1us), so this caps a wait near 500ms.
|
||||||
|
static constexpr uint32_t PollTicks = 500000;
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
// State
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
static uint16_t g_dataPort = 0;
|
||||||
|
static uint16_t g_cmdPort = 0;
|
||||||
|
static bool g_available = false;
|
||||||
|
static bool g_initialized = false;
|
||||||
|
// A Mutex, not a Spinlock: EC transactions run only from process and
|
||||||
|
// syscall context, and can spin for milliseconds waiting on the
|
||||||
|
// controller. A Spinlock would hold interrupts off for all of it.
|
||||||
|
static kcp::Mutex g_lock;
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
// ECDT
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// The ECDT gives the EC's ports without needing the AML namespace. It is
|
||||||
|
// optional, and some firmware ships one with zeroed ports, so treat a
|
||||||
|
// zero port as "not described here" and fall through to the namespace.
|
||||||
|
struct EcdtTable {
|
||||||
|
ACPI::CommonSDTHeader Header;
|
||||||
|
uint8_t ControlGas[12];
|
||||||
|
uint8_t DataGas[12];
|
||||||
|
uint32_t Uid;
|
||||||
|
uint8_t GpeBit;
|
||||||
|
// followed by a null-terminated namespace path
|
||||||
|
} __attribute__((packed));
|
||||||
|
|
||||||
|
static bool SignatureMatch(const char* sig, const char* target) {
|
||||||
|
for (int i = 0; i < 4; i++) {
|
||||||
|
if (sig[i] != target[i]) return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode a Generic Address Structure that must describe a 16-bit I/O port.
|
||||||
|
static uint16_t GasIoPort(const uint8_t gas[12]) {
|
||||||
|
if (gas[0] != 1) return 0; // AddressSpaceId 1 == SystemIO
|
||||||
|
|
||||||
|
uint64_t address = 0;
|
||||||
|
for (int i = 0; i < 8; i++)
|
||||||
|
address |= (uint64_t)gas[4 + i] << (i * 8);
|
||||||
|
return address <= 0xFFFF ? (uint16_t)address : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool FromEcdt(ACPI::CommonSDTHeader* xsdt) {
|
||||||
|
if (xsdt == nullptr) return false;
|
||||||
|
|
||||||
|
uint32_t entryCount = (xsdt->Length - sizeof(ACPI::CommonSDTHeader)) / 8;
|
||||||
|
uint64_t* entries = (uint64_t*)((uint64_t)xsdt + sizeof(ACPI::CommonSDTHeader));
|
||||||
|
|
||||||
|
for (uint32_t i = 0; i < entryCount; i++) {
|
||||||
|
auto* header = (ACPI::CommonSDTHeader*)Memory::HHDM(entries[i]);
|
||||||
|
if (!SignatureMatch(header->Signature, "ECDT")) continue;
|
||||||
|
if (header->Length < sizeof(EcdtTable)) continue;
|
||||||
|
|
||||||
|
auto* ecdt = (EcdtTable*)header;
|
||||||
|
uint16_t cmd = GasIoPort(ecdt->ControlGas);
|
||||||
|
uint16_t data = GasIoPort(ecdt->DataGas);
|
||||||
|
if (cmd == 0 || data == 0) continue;
|
||||||
|
|
||||||
|
g_cmdPort = cmd;
|
||||||
|
g_dataPort = data;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
// Namespace fallback
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// _CRS on an EC device lists two I/O resources in a fixed order: data
|
||||||
|
// register first, then command/status. Anything else is not an EC we
|
||||||
|
// know how to drive.
|
||||||
|
static bool FromNamespace() {
|
||||||
|
if (!AML::GetInterpreter().IsInitialized()) return false;
|
||||||
|
|
||||||
|
AcpiDevices::DeviceInfo ec{};
|
||||||
|
if (!AcpiDevices::FindDeviceByHid(AcpiDevices::HID_EC, ec)) return false;
|
||||||
|
|
||||||
|
AML::ResourceList crs{};
|
||||||
|
if (!AcpiDevices::EvaluateCrs(ec.NodeIndex, crs)) return false;
|
||||||
|
|
||||||
|
uint16_t ports[2] = {0, 0};
|
||||||
|
int found = 0;
|
||||||
|
for (int i = 0; i < crs.Count && found < 2; i++) {
|
||||||
|
if (crs.Resources[i].Type == AML::ResourceType::IoPort)
|
||||||
|
ports[found++] = crs.Resources[i].IoPort.Base;
|
||||||
|
else if (crs.Resources[i].Type == AML::ResourceType::FixedIoPort)
|
||||||
|
ports[found++] = crs.Resources[i].FixedIoPort.Base;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (found < 2 || ports[0] == 0 || ports[1] == 0) return false;
|
||||||
|
|
||||||
|
g_dataPort = ports[0];
|
||||||
|
g_cmdPort = ports[1];
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
// Initialize
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
void Initialize(void* xsdt) {
|
||||||
|
if (g_initialized) return;
|
||||||
|
g_initialized = true;
|
||||||
|
|
||||||
|
if (!FromEcdt((ACPI::CommonSDTHeader*)xsdt) && !FromNamespace()) {
|
||||||
|
KernelLogStream(INFO, "EC") << "No ACPI Embedded Controller present";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_available = true;
|
||||||
|
KernelLogStream(OK, "EC") << "Embedded Controller at cmd=" << base::hex
|
||||||
|
<< (uint64_t)g_cmdPort << " data=" << (uint64_t)g_dataPort;
|
||||||
|
|
||||||
|
RunRegMethods();
|
||||||
|
|
||||||
|
KernelLogStream(INFO, "EC") << "EC space dump:";
|
||||||
|
DumpSpace(0x00, 32);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
// _REG
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
void RunRegMethods() {
|
||||||
|
auto& interp = AML::GetInterpreter();
|
||||||
|
if (!interp.IsInitialized()) return;
|
||||||
|
|
||||||
|
auto& ns = interp.GetNamespace();
|
||||||
|
|
||||||
|
// Collect first, evaluate second. Executing AML can create namespace
|
||||||
|
// nodes, which reallocates a parent's child array -- walking the
|
||||||
|
// tree while methods run would be iterating a freed array.
|
||||||
|
static constexpr int MaxRegMethods = 64;
|
||||||
|
int32_t nodes[MaxRegMethods];
|
||||||
|
int count = 0;
|
||||||
|
|
||||||
|
ns.WalkDescendants(ns.RootIndex(), AML::ObjectType::Method,
|
||||||
|
[&](int32_t idx, const AML::NamespaceNode* node) {
|
||||||
|
if (count >= MaxRegMethods) return;
|
||||||
|
if (node->Name[0] == '_' && node->Name[1] == 'R' &&
|
||||||
|
node->Name[2] == 'E' && node->Name[3] == 'G')
|
||||||
|
nodes[count++] = idx;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Arg0 = region space (3, EmbeddedControl), Arg1 = 1 (handler is
|
||||||
|
// connected). A _REG that handles some other space checks Arg0 and
|
||||||
|
// ignores the call, so this is safe to issue namespace-wide.
|
||||||
|
AML::Object args[2]{};
|
||||||
|
args[0].Type = AML::ObjectType::Integer;
|
||||||
|
args[0].Integer = (uint64_t)AML::RegionSpace::EmbeddedControl;
|
||||||
|
args[1].Type = AML::ObjectType::Integer;
|
||||||
|
args[1].Integer = 1;
|
||||||
|
|
||||||
|
int ran = 0;
|
||||||
|
for (int i = 0; i < count; i++) {
|
||||||
|
char path[256];
|
||||||
|
ns.GetNodePath(nodes[i], path, sizeof(path));
|
||||||
|
|
||||||
|
AML::Object result{};
|
||||||
|
bool ok = interp.EvaluateMethod(path, args, 2, result);
|
||||||
|
if (ok) ran++;
|
||||||
|
|
||||||
|
KernelLogStream(INFO, "EC") << " _REG " << path
|
||||||
|
<< (ok ? " ok" : " FAILED");
|
||||||
|
}
|
||||||
|
|
||||||
|
KernelLogStream(OK, "EC") << "Ran " << base::dec << (uint64_t)ran
|
||||||
|
<< " of " << (uint64_t)count << " _REG method(s)";
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
// Diagnostics
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
void DumpSpace(uint8_t base_, int bytes) {
|
||||||
|
if (!g_available) return;
|
||||||
|
|
||||||
|
// Whether the transport works at all is not otherwise observable:
|
||||||
|
// a failed read and a genuinely zero register look identical by the
|
||||||
|
// time firmware AML has turned them into a status value. All-00 or
|
||||||
|
// all-FF here means the handshake is wrong, not that the battery is
|
||||||
|
// missing.
|
||||||
|
for (int row = 0; row < bytes; row += 16) {
|
||||||
|
char line[64];
|
||||||
|
int n = 0;
|
||||||
|
bool anyFailed = false;
|
||||||
|
|
||||||
|
for (int i = 0; i < 16 && (row + i) < bytes; i++) {
|
||||||
|
uint8_t v = 0;
|
||||||
|
if (!Read((uint8_t)(base_ + row + i), v)) anyFailed = true;
|
||||||
|
|
||||||
|
static const char hex[] = "0123456789ABCDEF";
|
||||||
|
line[n++] = hex[(v >> 4) & 0xF];
|
||||||
|
line[n++] = hex[v & 0xF];
|
||||||
|
line[n++] = ' ';
|
||||||
|
}
|
||||||
|
line[n] = '\0';
|
||||||
|
|
||||||
|
KernelLogStream(INFO, "EC") << " " << base::hex
|
||||||
|
<< (uint64_t)(base_ + row) << ": " << line
|
||||||
|
<< (anyFailed ? " (read timeout)" : "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsAvailable() {
|
||||||
|
return g_available;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
// Transport
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Spin until the named status bit reaches the wanted state.
|
||||||
|
static bool WaitStatus(uint8_t mask, bool wantSet) {
|
||||||
|
for (uint32_t i = 0; i < PollTicks; i++) {
|
||||||
|
uint8_t sc = Io::In8(g_cmdPort);
|
||||||
|
if (((sc & mask) != 0) == wantSet) return true;
|
||||||
|
Io::IoPortWait();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Drain a byte the EC left in the output buffer from an earlier aborted
|
||||||
|
// transaction. Without this the next read returns the stale value.
|
||||||
|
static void DrainOutput() {
|
||||||
|
if (Io::In8(g_cmdPort) & SC_OBF)
|
||||||
|
(void)Io::In8(g_dataPort);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Read(uint8_t address, uint8_t& value) {
|
||||||
|
if (!g_available) return false;
|
||||||
|
|
||||||
|
g_lock.Acquire();
|
||||||
|
DrainOutput();
|
||||||
|
|
||||||
|
bool ok = false;
|
||||||
|
if (WaitStatus(SC_IBF, false)) {
|
||||||
|
Io::Out8(CMD_READ, g_cmdPort);
|
||||||
|
if (WaitStatus(SC_IBF, false)) {
|
||||||
|
Io::Out8(address, g_dataPort);
|
||||||
|
if (WaitStatus(SC_OBF, true)) {
|
||||||
|
value = Io::In8(g_dataPort);
|
||||||
|
ok = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
g_lock.Release();
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Write(uint8_t address, uint8_t value) {
|
||||||
|
if (!g_available) return false;
|
||||||
|
|
||||||
|
g_lock.Acquire();
|
||||||
|
DrainOutput();
|
||||||
|
|
||||||
|
bool ok = false;
|
||||||
|
if (WaitStatus(SC_IBF, false)) {
|
||||||
|
Io::Out8(CMD_WRITE, g_cmdPort);
|
||||||
|
if (WaitStatus(SC_IBF, false)) {
|
||||||
|
Io::Out8(address, g_dataPort);
|
||||||
|
if (WaitStatus(SC_IBF, false)) {
|
||||||
|
Io::Out8(value, g_dataPort);
|
||||||
|
ok = WaitStatus(SC_IBF, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
g_lock.Release();
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
/*
|
||||||
|
* EmbeddedController.hpp
|
||||||
|
* ACPI Embedded Controller (PNP0C09) transport
|
||||||
|
* Copyright (c) 2026 Daniel Hammer
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
namespace Hal {
|
||||||
|
namespace EmbeddedController {
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
// Bring-up
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Locate the EC in the ACPI namespace and latch its command/data ports.
|
||||||
|
// Prefers the ECDT table when present (it is valid before the namespace
|
||||||
|
// is loaded); otherwise walks the namespace for PNP0C09 and reads _CRS.
|
||||||
|
// Safe to call more than once; only the first call does work.
|
||||||
|
void Initialize(void* xsdt);
|
||||||
|
|
||||||
|
// True once a usable command/data port pair has been found.
|
||||||
|
bool IsAvailable();
|
||||||
|
|
||||||
|
// Run the namespace's _REG(EmbeddedControl, 1) methods, telling firmware
|
||||||
|
// that EC region access is live. ACPI requires this once a handler for
|
||||||
|
// an address space is installed, and firmware leans on it hard: EC
|
||||||
|
// methods are typically guarded by a flag that _REG sets, and until it
|
||||||
|
// is set they return safe do-nothing answers (a battery reports itself
|
||||||
|
// absent, a thermal zone reports no temperature). Called by Initialize.
|
||||||
|
void RunRegMethods();
|
||||||
|
|
||||||
|
// Log `bytes` bytes of EC address space starting at `base_`. Purely a
|
||||||
|
// diagnostic: it is the only way to tell a broken handshake from a
|
||||||
|
// register that genuinely reads zero.
|
||||||
|
void DumpSpace(uint8_t base_, int bytes);
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
// Transport
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Read/write one byte of the EC's 256-byte address space. Both return
|
||||||
|
// false on timeout or when no EC is present, leaving *value untouched.
|
||||||
|
bool Read(uint8_t address, uint8_t& value);
|
||||||
|
bool Write(uint8_t address, uint8_t value);
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -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 184
|
#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 ----
|
||||||
|
|||||||
+46
-19
@@ -8,12 +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 <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>
|
||||||
@@ -48,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() {
|
||||||
@@ -74,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();
|
||||||
}
|
}
|
||||||
@@ -95,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;
|
||||||
@@ -111,7 +112,33 @@ 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
|
||||||
|
// no ACPI battery device, which is how a desktop-class caller learns not to
|
||||||
|
// draw an indicator at all.
|
||||||
|
static int64_t Sys_BatteryInfo(BatteryInfo* out) {
|
||||||
|
Hal::Battery::Status s{};
|
||||||
|
if (!Hal::Battery::GetStatus(s)) {
|
||||||
|
return NoDevice;
|
||||||
|
}
|
||||||
|
|
||||||
|
out->present = s.Present ? 1 : 0;
|
||||||
|
out->charging = s.Charging ? 1 : 0;
|
||||||
|
out->discharging = s.Discharging ? 1 : 0;
|
||||||
|
out->critical = s.Critical ? 1 : 0;
|
||||||
|
out->acOnline = s.AcOnline ? 1 : 0;
|
||||||
|
out->hasAcAdapter = s.HasAcAdapter ? 1 : 0;
|
||||||
|
out->percent = s.Percent;
|
||||||
|
out->powerUnitMilliAmps = s.PowerUnitMilliAmps ? 1 : 0;
|
||||||
|
out->remainingCapacity = s.RemainingCapacity;
|
||||||
|
out->fullCapacity = s.FullCapacity;
|
||||||
|
out->designCapacity = s.DesignCapacity;
|
||||||
|
out->presentRate = s.PresentRate;
|
||||||
|
out->voltageMv = s.VoltageMv;
|
||||||
|
out->minutesRemaining = s.MinutesRemaining;
|
||||||
|
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+285
-211
@@ -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,52 +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:
|
||||||
|
// Read-only power telemetry, ungated like SYS_POWERINFO: the
|
||||||
|
// panel indicator has to work in an unprivileged session, which
|
||||||
|
// holds no device capability to spend on it.
|
||||||
|
if (!UserMemory::Writable<BatteryInfo>(frame->arg1)) return BadPointer;
|
||||||
|
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);
|
||||||
@@ -555,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,
|
||||||
@@ -642,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);
|
||||||
@@ -664,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'
|
||||||
@@ -698,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)";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
+360
-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 {
|
||||||
|
|
||||||
@@ -343,6 +345,69 @@ namespace montauk::abi {
|
|||||||
touch in full (image decode, heap slabs). */
|
touch in full (image decode, heap slabs). */
|
||||||
static constexpr uint64_t SYS_ALLOC_EAGER = 187; // (bytes) -> va, 0 on failure
|
static constexpr uint64_t SYS_ALLOC_EAGER = 187; // (bytes) -> va, 0 on failure
|
||||||
|
|
||||||
|
/* Battery.hpp -- ACPI battery status */
|
||||||
|
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;
|
||||||
@@ -363,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 |
|
||||||
@@ -375,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
|
||||||
@@ -537,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;
|
||||||
@@ -553,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;
|
||||||
@@ -586,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;
|
||||||
@@ -616,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;
|
||||||
@@ -632,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
|
||||||
@@ -673,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;
|
||||||
@@ -692,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;
|
||||||
@@ -700,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;
|
||||||
};
|
};
|
||||||
@@ -881,6 +1201,26 @@ namespace montauk::abi {
|
|||||||
uint32_t apIdleHint; // MWAIT hint used for AP deep idle
|
uint32_t apIdleHint; // MWAIT hint used for AP deep idle
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Battery status (returned by SYS_BATTERYINFO). A machine with no battery
|
||||||
|
// device fails the call outright; `present` distinguishes a laptop with the
|
||||||
|
// pack removed from one that is running on it.
|
||||||
|
struct BatteryInfo {
|
||||||
|
uint8_t present; // a battery is installed
|
||||||
|
uint8_t charging;
|
||||||
|
uint8_t discharging;
|
||||||
|
uint8_t critical;
|
||||||
|
uint8_t acOnline; // AC adapter connected
|
||||||
|
uint8_t hasAcAdapter; // an AC adapter device exists at all
|
||||||
|
uint8_t percent; // 0..100, 0 when capacity is unknown
|
||||||
|
uint8_t powerUnitMilliAmps; // capacities are mAh/mA, not mWh/mW
|
||||||
|
uint32_t remainingCapacity;
|
||||||
|
uint32_t fullCapacity; // last full charge capacity
|
||||||
|
uint32_t designCapacity;
|
||||||
|
uint32_t presentRate; // mW or mA, 0 when unknown
|
||||||
|
uint32_t voltageMv;
|
||||||
|
int32_t minutesRemaining; // -1 when not estimable (e.g. charging)
|
||||||
|
};
|
||||||
|
|
||||||
// Crash report (filled by kernel on process fault, returned via SYS_CRASH_REPORT)
|
// Crash report (filled by kernel on process fault, returned via SYS_CRASH_REPORT)
|
||||||
struct CrashReportInfo {
|
struct CrashReportInfo {
|
||||||
int pid;
|
int pid;
|
||||||
@@ -906,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;
|
||||||
Loaded 100 of 602 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user