Compare commits
51
Commits
b77449faeb
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fd397ac41d | ||
|
|
fdbb233cd3 | ||
|
|
21828990c4 | ||
|
|
70fa2016d1 | ||
|
|
f69f08acbb | ||
|
|
f0bea7736d | ||
|
|
3e596f2bad | ||
|
|
615ca7308a | ||
|
|
e7646bbbdb | ||
|
|
9051b8a16e | ||
|
|
5cd5c2e6be | ||
|
|
b1f1cfe32b | ||
|
|
0d23db8a0e | ||
|
|
dac12c22cd | ||
|
|
c8264b92a9 | ||
|
|
8afadb44cb | ||
|
|
ffc9756e67 | ||
|
|
8a74b771e1 | ||
|
|
903218168d | ||
|
|
cc90b34fdb | ||
|
|
788b662d44 | ||
|
|
f7677ac3f1 | ||
|
|
af7d096969 | ||
|
|
c917af0629 | ||
|
|
f6be9e2563 | ||
|
|
39a56153b4 | ||
|
|
724029cacb | ||
|
|
8e45d43116 | ||
|
|
0f81276efc | ||
|
|
f8ded3c30e | ||
|
|
e3ef1b81ad | ||
|
|
821543238d | ||
|
|
86e3ebfe33 | ||
|
|
a28b8ad0b2 | ||
|
|
7f2d4b693f | ||
|
|
3ef98a9fd5 | ||
|
|
0a91131cb0 | ||
|
|
69725658cf | ||
|
|
e25ef470e0 | ||
|
|
990d184bd0 | ||
|
|
9b23d99082 | ||
|
|
4ecec32c5c | ||
|
|
b91b342d5c | ||
|
|
0b7fd37694 | ||
|
|
1c498ffa6a | ||
|
|
771c3a5a98 | ||
|
|
a06ef0192d | ||
|
|
e8407afcdb | ||
|
|
fe10216f8b | ||
|
|
53a9965e7c | ||
|
|
27c13b8dc2 |
+2
-1
@@ -17,7 +17,8 @@ programs/src/*/obj/
|
||||
programs/libs/*/obj/
|
||||
programs/lib/libc/liblibc.a
|
||||
programs/lib/libc/liblibc-full.a
|
||||
programs/lib/libc/obj/libc-merged.o
|
||||
programs/lib/libc/*.o
|
||||
programs/lib/libc/obj/
|
||||
programs/lib/libloader/liblibloader.a
|
||||
programs/gui/icons/
|
||||
CLAUDE.md
|
||||
|
||||
+40
@@ -181,6 +181,20 @@ kernel: kernel-deps
|
||||
programs:
|
||||
$(MAKE) -C programs
|
||||
|
||||
# Build out-of-tree ports (~/dev/mtkports) and stage their app bundles into
|
||||
# programs/bin/apps/. Opt-in: the base ISO ships only what lives in
|
||||
# programs/src/, so this is never a dependency of the default build. Run it
|
||||
# before 'make ramdisk' to include ports in the image.
|
||||
MTKPORTS ?= $(HOME)/dev/mtkports
|
||||
|
||||
.PHONY: ports
|
||||
ports: programs
|
||||
@if [ -d "$(MTKPORTS)" ]; then \
|
||||
$(MAKE) -C "$(MTKPORTS)" install MONTAUKOS="$(CURDIR)"; \
|
||||
else \
|
||||
echo "ports: $(MTKPORTS) not found - skipping (set MTKPORTS=/path/to/mtkports)"; \
|
||||
fi
|
||||
|
||||
.PHONY: ramdisk
|
||||
ramdisk: limine/limine kernel programs
|
||||
mkdir -p programs/bin/boot/limine
|
||||
@@ -245,7 +259,33 @@ release: limine/limine kernel ramdisk
|
||||
cp kernel/bin-$(ARCH)/kernel iso_root/boot/
|
||||
strip iso_root/boot/kernel
|
||||
cp -r programs/bin .release-tmp
|
||||
# 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).
|
||||
# Pass RELEASE_WITH_PORTS=1 to deliberately cut a ports-included image.
|
||||
ifneq ($(RELEASE_WITH_PORTS),1)
|
||||
@for m in $$(find .release-tmp/apps -name .port 2>/dev/null); do \
|
||||
d=$$(dirname $$m); \
|
||||
echo "release: excluding port bundle $$(basename $$d)"; \
|
||||
rm -rf "$$d"; \
|
||||
done
|
||||
else
|
||||
@echo "release: RELEASE_WITH_PORTS=1 - including out-of-tree port bundles"
|
||||
endif
|
||||
# The Montauk SDK (native GCC + binutils and their target sysroot under
|
||||
# 0:/sdk) dominates the image size. Pass RELEASE_WITHOUT_SDK=1 to cut a slim
|
||||
# ISO without it. Only the devkit-owned subtrees go: sdk/tcc and sdk/lua are
|
||||
# installed by their own targets and stay, as does 0:/tmp, which the libc
|
||||
# uses for tmpnam() and not just for compiler intermediates.
|
||||
ifeq ($(RELEASE_WITHOUT_SDK),1)
|
||||
@echo "release: RELEASE_WITHOUT_SDK=1 - excluding the Montauk SDK (0:/sdk dev tools)"
|
||||
rm -rf .release-tmp/sdk/bin .release-tmp/sdk/lib .release-tmp/sdk/include \
|
||||
.release-tmp/sdk/libexec .release-tmp/sdk/x86_64-montauk
|
||||
endif
|
||||
find .release-tmp -name '*.elf' -exec strip {} +
|
||||
# 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
|
||||
# *.elf, so the find above misses it. Unstripped it is ~6 MB against ~0.7 MB.
|
||||
strip .release-tmp/boot/kernel
|
||||
./scripts/mkramdisk.sh .release-tmp iso_root/boot/ramdisk.tar
|
||||
rm -rf .release-tmp
|
||||
mkdir -p iso_root/boot/limine
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# The Montauk Operating System
|
||||
MontaukOS is an operating system written in modern C++. It runs on bare metal and supports various applications, including DOOM, a Wikipedia client, and standard desktop utilities.
|
||||
|
||||

|
||||

|
||||
|
||||
## Features
|
||||
* Modern preemptive multitasking kernel
|
||||
|
||||
@@ -1,3 +1,70 @@
|
||||
MontaukOS 0.1.9 (August 13, 2026)
|
||||
(a28b8ad0b2932203b812e967f35f029f060fd6b5)
|
||||
|
||||
New
|
||||
====
|
||||
* Intel Wi-Fi (AX210/AX211) support
|
||||
* Wi-Fi support in the desktop panel and the Network settings app
|
||||
* wifi command-line tool
|
||||
* NetSurf web browser ported as a native MontaukOS app
|
||||
* SSH server (sshd) with an SSH Server app for managing it
|
||||
* NTP client (configured via 0:/config/ntp.toml and Time applet)
|
||||
* init now discovers services from 0:/config/init.toml rather than a hardcoded sequence
|
||||
* New Display app for managing monitors, modes, and layout
|
||||
* Expanded Intel GPU display management in the kernel
|
||||
* Audio stack now mixes concurrent streams and switches output devices live
|
||||
* New audio device switching UI in Audio app and the desktop panel
|
||||
* Filesystems can be mounted and unmounted at runtime
|
||||
* File metadata across all filesystems, exposed through a new stat syscall
|
||||
* ext2 now saves timestamps and supports utime
|
||||
* New mkdir, rmdir, and memtest commands
|
||||
* Expanded support in the PDF Viewer app
|
||||
* Files shows modified dates in the Properties dialog
|
||||
* Desktop UI consistency improvements
|
||||
* Crash reports now capture a stack snapshot
|
||||
* Userspace heap and virtual memory overhauled
|
||||
* pread, pwrite, strndup, scandir, alphasort, usleep, ftruncate, M_PI, and a non-blocking stdin readiness probe added to libc
|
||||
* DOOM moved out of the OS tree into the mtkports project
|
||||
|
||||
Fixed
|
||||
=====
|
||||
* Numerous kernel concurrency, interrupt-context, and user virtual-address fixes
|
||||
* Hardened the TCP/IP stack and unified the HTTP clients; fixed several networking bugs and regressions
|
||||
* Improved Bluetooth reliability
|
||||
* Corrected Intel GGTT setup and DP AUX EDID reads
|
||||
* IPC handle slots are no longer reused while an object is still being torn down
|
||||
* The ramdisk no longer packs symlinks as zero-byte files
|
||||
* Fixed a ramdisk readdir inconsistency
|
||||
* Processes now inherit their parent's environment
|
||||
* getcwd reports a truncated buffer as ERANGE instead of silently truncating
|
||||
* Improved stdio and argument handling
|
||||
* Clarified Intel iwlwifi firmware licensing
|
||||
|
||||
Included in ISO
|
||||
================
|
||||
|
||||
Two x86_64 images are published for this release.
|
||||
montauk-0.1.9-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:/man), readable via man command
|
||||
* Service and system configuration (0:/config: init.toml, ssh.toml, ntp.toml, timezonedata.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.8 (July 18, 2026)
|
||||
(71c66f803516558012be13ee38cb02a9a0ba476a)
|
||||
|
||||
|
||||
+100
@@ -0,0 +1,100 @@
|
||||
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.
|
||||
-1
Submodule doomgeneric deleted from fc60163949
@@ -22,7 +22,22 @@ namespace Hal {
|
||||
|
||||
// ============================================================================
|
||||
// ACPI encodes PNP IDs as compressed 32-bit EISAIDs.
|
||||
static constexpr uint32_t ByteSwap32(uint32_t value) {
|
||||
return ((value & 0x000000FFu) << 24) |
|
||||
((value & 0x0000FF00u) << 8) |
|
||||
((value & 0x00FF0000u) >> 8) |
|
||||
((value & 0xFF000000u) >> 24);
|
||||
}
|
||||
|
||||
static_assert(ByteSwap32(0x0301D041u) == 0x41D00103u); // PNP0103
|
||||
static_assert(ByteSwap32(0x090CD041u) == 0x41D00C09u); // PNP0C09
|
||||
|
||||
static void DecodeEisaId(uint32_t id, char* out) {
|
||||
// AML exposes the EISAID integer in little-endian byte order, while
|
||||
// the compressed manufacturer and product fields are defined in
|
||||
// display order. Convert it before extracting either field.
|
||||
id = ByteSwap32(id);
|
||||
|
||||
// EISA ID encoding:
|
||||
// Bits 31-16: 3 compressed letters (5 bits each, '@' based)
|
||||
// Bits 15-0: 4 hex digits (product number)
|
||||
|
||||
@@ -12,4 +12,4 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#define MONTAUK_BUILD_NUMBER 89
|
||||
#define MONTAUK_BUILD_NUMBER 184
|
||||
|
||||
@@ -34,6 +34,9 @@ namespace montauk::abi {
|
||||
out->codeSegment = rep.codeSegment;
|
||||
out->flags = rep.flags;
|
||||
out->stackSegment = rep.stackSegment;
|
||||
out->stackWordCount = rep.stackWordCount;
|
||||
for (int i = 0; i < rep.stackWordCount && i < CrashReport::StackSnapshotWords; i++)
|
||||
out->stackWords[i] = rep.stackWords[i];
|
||||
out->pfPresent = rep.pfPresent;
|
||||
out->pfWrite = rep.pfWrite;
|
||||
out->pfUser = rep.pfUser;
|
||||
|
||||
@@ -15,9 +15,16 @@
|
||||
#include <Memory/Paging.hpp>
|
||||
#include <Libraries/Memory.hpp>
|
||||
#include <Ipc/Ipc.hpp>
|
||||
#include <Timekeeping/Time.hpp>
|
||||
#include "Path.hpp"
|
||||
#include <Fs/ProtectedPaths.hpp>
|
||||
|
||||
namespace montauk::abi {
|
||||
static bool CanModifyFilePath(const char* resolved) {
|
||||
uint64_t required = Fs::RequiredFileWriteCapability(resolved);
|
||||
return required == 0 || Sched::HasCapability(required);
|
||||
}
|
||||
|
||||
static int Sys_Open(const char* path) {
|
||||
char resolved[256];
|
||||
if (!ResolveProcessPath(path, resolved, sizeof(resolved))) return -1;
|
||||
@@ -61,9 +68,9 @@ namespace montauk::abi {
|
||||
if (proc == nullptr) return -1;
|
||||
|
||||
// Use a rotating ring of scratch pages below the heap instead of
|
||||
// bumping heapNext on every call. This keeps repeated directory scans
|
||||
// from leaking user heap space while still allowing nested callers to
|
||||
// hold multiple readdir results at once.
|
||||
// extending the heap high-water mark on every call. This keeps repeated
|
||||
// directory scans from consuming user heap address space while still
|
||||
// allowing nested callers to hold multiple readdir results at once.
|
||||
uint32_t slot = proc->readdirCursor % Sched::UserReadDirSlots;
|
||||
proc->readdirCursor = (slot + 1) % Sched::UserReadDirSlots;
|
||||
|
||||
@@ -107,12 +114,14 @@ namespace montauk::abi {
|
||||
static int Sys_FCreate(const char* path) {
|
||||
char resolved[256];
|
||||
if (!ResolveProcessPath(path, resolved, sizeof(resolved))) return -1;
|
||||
if (!CanModifyFilePath(resolved)) return SYS_ERR_PERMISSION;
|
||||
return Ipc::CreateFileHandle(resolved);
|
||||
}
|
||||
|
||||
static int Sys_FDelete(const char* path) {
|
||||
char resolved[256];
|
||||
if (!ResolveProcessPath(path, resolved, sizeof(resolved))) return -1;
|
||||
if (!CanModifyFilePath(resolved)) return SYS_ERR_PERMISSION;
|
||||
return Fs::Vfs::VfsDelete(resolved);
|
||||
}
|
||||
|
||||
@@ -133,9 +142,25 @@ namespace montauk::abi {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int Sys_Utime(const char* path, int64_t atime, int64_t mtime,
|
||||
bool useCurrent) {
|
||||
char resolved[256];
|
||||
if (!ResolveProcessPath(path, resolved, sizeof(resolved))) return -1;
|
||||
// Timestamps are file state like any other: a protected path must not
|
||||
// be mutable through a side door that skips the write check.
|
||||
if (!CanModifyFilePath(resolved)) return SYS_ERR_PERMISSION;
|
||||
if (useCurrent) {
|
||||
int64_t now = Timekeeping::GetUnixTimestamp();
|
||||
atime = now;
|
||||
mtime = now;
|
||||
}
|
||||
return Fs::Vfs::VfsSetTimes(resolved, atime, mtime);
|
||||
}
|
||||
|
||||
static int Sys_FMkdir(const char* path) {
|
||||
char resolved[256];
|
||||
if (!ResolveProcessPath(path, resolved, sizeof(resolved))) return -1;
|
||||
if (!CanModifyFilePath(resolved)) return SYS_ERR_PERMISSION;
|
||||
return Fs::Vfs::VfsMkdir(resolved);
|
||||
}
|
||||
|
||||
@@ -144,6 +169,8 @@ namespace montauk::abi {
|
||||
char resolvedNew[256];
|
||||
if (!ResolveProcessPath(oldPath, resolvedOld, sizeof(resolvedOld))) return -1;
|
||||
if (!ResolveProcessPath(newPath, resolvedNew, sizeof(resolvedNew))) return -1;
|
||||
if (!CanModifyFilePath(resolvedOld) || !CanModifyFilePath(resolvedNew))
|
||||
return SYS_ERR_PERMISSION;
|
||||
return Fs::Vfs::VfsRename(resolvedOld, resolvedNew);
|
||||
}
|
||||
|
||||
|
||||
+307
-55
@@ -4,6 +4,8 @@
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <Memory/UserRange.hpp>
|
||||
#include <cstdint>
|
||||
#include <Sched/Scheduler.hpp>
|
||||
#include <Memory/Paging.hpp>
|
||||
@@ -18,12 +20,16 @@ namespace montauk::abi {
|
||||
struct HeapAlloc {
|
||||
uint64_t va;
|
||||
uint64_t numPages;
|
||||
uint64_t prot;
|
||||
uint64_t allocationId;
|
||||
HeapAlloc* next;
|
||||
};
|
||||
|
||||
static constexpr int MaxHeapAllocs = 512;
|
||||
|
||||
inline HeapAlloc g_heapAllocs[Sched::MaxProcesses][MaxHeapAllocs] = {};
|
||||
inline int g_heapAllocCount[Sched::MaxProcesses] = {};
|
||||
// VM area metadata is dynamically sized. The previous 512-entry array made
|
||||
// valid mappings fail for metadata exhaustion and consumed several MiB of
|
||||
// kernel BSS even for processes with no mappings.
|
||||
inline HeapAlloc* g_heapAllocs[Sched::MaxProcesses] = {};
|
||||
inline uint64_t g_nextHeapAllocationId[Sched::MaxProcesses] = {};
|
||||
inline kcp::Mutex g_heapLocks[Sched::MaxProcesses];
|
||||
|
||||
// Get the process table slot index for the current process
|
||||
@@ -35,12 +41,27 @@ namespace montauk::abi {
|
||||
return (int)(proc - slot0);
|
||||
}
|
||||
|
||||
inline uint64_t Sys_Alloc(uint64_t size) {
|
||||
static constexpr uint64_t VmProtRead = 1;
|
||||
static constexpr uint64_t VmProtWrite = 2;
|
||||
static constexpr uint64_t VmProtExec = 4;
|
||||
|
||||
// Sys_MapAnonymous flags. Populate commits the whole range at mapping
|
||||
// time; without it every page is materialized on first touch.
|
||||
static constexpr uint64_t VmFlagPopulate = 1;
|
||||
|
||||
inline uint64_t Sys_MapAnonymous(uint64_t size, uint64_t prot,
|
||||
uint64_t flags = 0) {
|
||||
auto* proc = Sched::GetCurrentProcessPtr();
|
||||
if (proc == nullptr) return 0;
|
||||
int slot = GetCurrentSlot();
|
||||
if (slot < 0) return 0;
|
||||
|
||||
if ((prot & ~(VmProtRead | VmProtWrite | VmProtExec)) != 0 ||
|
||||
(prot & VmProtRead) == 0 ||
|
||||
((prot & VmProtWrite) != 0 && (prot & VmProtExec) != 0)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Guard against overflow before rounding.
|
||||
if (size > 0xFFFFFFFFFFFF0000ULL) return 0;
|
||||
|
||||
@@ -50,17 +71,6 @@ namespace montauk::abi {
|
||||
|
||||
uint64_t numPages = size / 0x1000;
|
||||
g_heapLocks[slot].Acquire();
|
||||
if (g_heapAllocCount[slot] >= MaxHeapAllocs) {
|
||||
// Out of allocation records, not out of memory. Log it: a
|
||||
// silent 0 here surfaced as bogus downstream errors (BFD
|
||||
// turned NULL mallocs into "file format not recognized").
|
||||
Kt::KernelLogStream(Kt::ERROR, "Heap")
|
||||
<< "pid " << proc->pid << " (" << proc->name
|
||||
<< ") hit MaxHeapAllocs (" << (uint64_t)MaxHeapAllocs
|
||||
<< "), SYS_ALLOC refused";
|
||||
g_heapLocks[slot].Release();
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint64_t userVa = 0;
|
||||
if (!Sched::ReserveUserHeapRange(slot, size, userVa)) {
|
||||
@@ -71,39 +81,67 @@ namespace montauk::abi {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Allocate physical pages and map them into the process
|
||||
uint64_t mappedPages = 0;
|
||||
for (uint64_t i = 0; i < numPages; i++) {
|
||||
void* page = Memory::g_pfa->AllocateZeroed();
|
||||
if (page == nullptr) {
|
||||
Ipc::UnmapAndFreeUserRange(proc->pml4Phys, userVa, mappedPages);
|
||||
g_heapLocks[slot].Release();
|
||||
return 0;
|
||||
}
|
||||
uint64_t physAddr = Memory::SubHHDM((uint64_t)page);
|
||||
if (!Memory::VMM::Paging::MapUserIn(proc->pml4Phys, physAddr, userVa + i * 0x1000)) {
|
||||
Memory::g_pfa->Free(page);
|
||||
Ipc::UnmapAndFreeUserRange(proc->pml4Phys, userVa, mappedPages);
|
||||
g_heapLocks[slot].Release();
|
||||
return 0;
|
||||
}
|
||||
mappedPages++;
|
||||
}
|
||||
// Reserve a VMA without committing physical pages. First access is
|
||||
// satisfied by TryHandleAnonymousPageFault with a zeroed frame.
|
||||
uint64_t allocationId = ++g_nextHeapAllocationId[slot];
|
||||
if (allocationId == 0) allocationId = ++g_nextHeapAllocationId[slot];
|
||||
g_heapAllocs[slot] = new HeapAlloc { userVa, numPages, prot, allocationId,
|
||||
g_heapAllocs[slot] };
|
||||
|
||||
// Track the allocation so Sys_Free can release it
|
||||
Sched::g_allocatedPages[slot] += numPages;
|
||||
g_heapAllocs[slot][g_heapAllocCount[slot]++] = { userVa, numPages };
|
||||
// Populate is best effort: commit as much of the range as the frame
|
||||
// allocator will give up front, and leave the remainder to the fault
|
||||
// path. A caller that is about to touch every page (a decode buffer,
|
||||
// a heap slab) then pays one loop instead of one trap, one mutex
|
||||
// acquire and one VMA walk per 4 KiB.
|
||||
if ((flags & VmFlagPopulate) != 0) {
|
||||
bool writable = (prot & VmProtWrite) != 0;
|
||||
bool executable = (prot & VmProtExec) != 0;
|
||||
// Bounded so one syscall cannot pin an unbounded amount of memory
|
||||
// with the slot's heap lock held. Anything past the cap faults in.
|
||||
static constexpr uint64_t MaxPopulatePages = 64 * 1024 * 1024 / 0x1000;
|
||||
uint64_t populate = numPages < MaxPopulatePages ? numPages
|
||||
: MaxPopulatePages;
|
||||
for (uint64_t i = 0; i < populate; i++) {
|
||||
uint64_t pageVa = userVa + i * 0x1000ULL;
|
||||
void* page = Memory::g_pfa->AllocateZeroed();
|
||||
if (page == nullptr) break;
|
||||
uint64_t phys = Memory::SubHHDM((uint64_t)page);
|
||||
if (!Memory::VMM::Paging::MapUserInPermissions(
|
||||
proc->pml4Phys, phys, pageVa, writable, executable)) {
|
||||
Memory::g_pfa->Free(page);
|
||||
break;
|
||||
}
|
||||
Sched::g_allocatedPages[slot]++;
|
||||
}
|
||||
}
|
||||
|
||||
g_heapLocks[slot].Release();
|
||||
return userVa;
|
||||
}
|
||||
|
||||
inline uint64_t Sys_Alloc(uint64_t size) {
|
||||
return Sys_MapAnonymous(size, VmProtRead | VmProtWrite);
|
||||
}
|
||||
|
||||
// As Sys_Alloc, but commits the pages immediately instead of faulting them
|
||||
// in one at a time.
|
||||
inline uint64_t Sys_AllocEager(uint64_t size) {
|
||||
return Sys_MapAnonymous(size, VmProtRead | VmProtWrite, VmFlagPopulate);
|
||||
}
|
||||
|
||||
// Reset heap allocation tracking for a process slot.
|
||||
// The actual physical pages are freed by Paging::FreeUserHalf() during process cleanup.
|
||||
inline void CleanupHeapForSlot(int slot, uint64_t /*pml4Phys*/) {
|
||||
if (slot < 0 || slot >= Sched::MaxProcesses) return;
|
||||
g_heapLocks[slot].Acquire();
|
||||
g_heapAllocCount[slot] = 0;
|
||||
HeapAlloc* area = g_heapAllocs[slot];
|
||||
while (area != nullptr) {
|
||||
HeapAlloc* next = area->next;
|
||||
delete area;
|
||||
area = next;
|
||||
}
|
||||
g_heapAllocs[slot] = nullptr;
|
||||
g_nextHeapAllocationId[slot] = 0;
|
||||
Sched::g_allocatedPages[slot] = 0;
|
||||
g_heapLocks[slot].Release();
|
||||
}
|
||||
@@ -117,31 +155,245 @@ namespace montauk::abi {
|
||||
|
||||
g_heapLocks[slot].Acquire();
|
||||
|
||||
// Find the allocation record matching this address
|
||||
int idx = -1;
|
||||
for (int i = 0; i < g_heapAllocCount[slot]; i++) {
|
||||
if (g_heapAllocs[slot][i].va == addr) {
|
||||
idx = i;
|
||||
// A protection change can split one allocation into several VMA
|
||||
// records. Resolve the stable allocation ID from the supplied base,
|
||||
// detach every surviving fragment, then tear it down without holding
|
||||
// the VMA lock.
|
||||
HeapAlloc* released = nullptr;
|
||||
HeapAlloc** link = &g_heapAllocs[slot];
|
||||
uint64_t resident = 0;
|
||||
uint64_t allocationId = 0;
|
||||
for (HeapAlloc* area = g_heapAllocs[slot]; area != nullptr;
|
||||
area = area->next) {
|
||||
if (area->va == addr) {
|
||||
allocationId = area->allocationId;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (idx < 0) {
|
||||
if (allocationId == 0) {
|
||||
g_heapLocks[slot].Release();
|
||||
return; // Unknown address — ignore
|
||||
}
|
||||
while (*link != nullptr) {
|
||||
HeapAlloc* area = *link;
|
||||
if (area->allocationId != allocationId) {
|
||||
link = &area->next;
|
||||
continue;
|
||||
}
|
||||
for (uint64_t i = 0; i < area->numPages; i++)
|
||||
if (Memory::VMM::Paging::GetPhysAddr(
|
||||
proc->pml4Phys, area->va + i * 0x1000ULL) != 0)
|
||||
resident++;
|
||||
*link = area->next;
|
||||
area->next = released;
|
||||
released = area;
|
||||
}
|
||||
g_heapLocks[slot].Release();
|
||||
|
||||
uint64_t va = g_heapAllocs[slot][idx].va;
|
||||
uint64_t numPages = g_heapAllocs[slot][idx].numPages;
|
||||
// Unmap and invalidate sibling CPUs before recycling frames. A stale
|
||||
// user TLB entry can otherwise corrupt the frame's next owner.
|
||||
while (released != nullptr) {
|
||||
HeapAlloc* next = released->next;
|
||||
Memory::UnmapAndFreeUserRange(proc->pml4Phys, released->va,
|
||||
released->numPages);
|
||||
Sched::ReleaseUserHeapRange(slot, released->va,
|
||||
released->numPages * 0x1000ULL);
|
||||
delete released;
|
||||
released = next;
|
||||
}
|
||||
|
||||
// Unmap and invalidate sibling CPUs before recycling the frames. A
|
||||
// stale user TLB entry can otherwise corrupt the frame's next owner.
|
||||
Ipc::UnmapAndFreeUserRange(proc->pml4Phys, va, numPages);
|
||||
|
||||
Sched::g_allocatedPages[slot] -= numPages;
|
||||
|
||||
// Remove tracking entry by swapping with the last element
|
||||
g_heapAllocs[slot][idx] = g_heapAllocs[slot][g_heapAllocCount[slot] - 1];
|
||||
g_heapAllocCount[slot]--;
|
||||
g_heapLocks[slot].Acquire();
|
||||
Sched::g_allocatedPages[slot] -= resident;
|
||||
g_heapLocks[slot].Release();
|
||||
}
|
||||
|
||||
inline int64_t Sys_Unmap(uint64_t addr, uint64_t size) {
|
||||
auto* proc = Sched::GetCurrentProcessPtr();
|
||||
int slot = GetCurrentSlot();
|
||||
if (proc == nullptr || slot < 0 || size == 0 || (addr & 0xFFFULL) != 0 ||
|
||||
size > UINT64_MAX - 0xFFFULL) return -1;
|
||||
size = (size + 0xFFFULL) & ~0xFFFULL;
|
||||
if (addr > UINT64_MAX - size) return -1;
|
||||
|
||||
g_heapLocks[slot].Acquire();
|
||||
uint64_t firstPage = 0;
|
||||
uint64_t pages = size / 0x1000ULL;
|
||||
HeapAlloc** link = &g_heapAllocs[slot];
|
||||
while (*link != nullptr) {
|
||||
HeapAlloc* area = *link;
|
||||
uint64_t areaSize = area->numPages * 0x1000ULL;
|
||||
if (addr >= area->va && addr - area->va <= areaSize &&
|
||||
size <= areaSize - (addr - area->va)) {
|
||||
firstPage = (addr - area->va) / 0x1000ULL;
|
||||
break;
|
||||
}
|
||||
link = &area->next;
|
||||
}
|
||||
if (*link == nullptr) { g_heapLocks[slot].Release(); return -1; }
|
||||
|
||||
HeapAlloc* area = *link;
|
||||
uint64_t oldPages = area->numPages;
|
||||
uint64_t oldProt = area->prot;
|
||||
uint64_t tailPages = oldPages - firstPage - pages;
|
||||
|
||||
if (firstPage == 0 && tailPages == 0) {
|
||||
*link = area->next;
|
||||
delete area;
|
||||
} else if (firstPage == 0) {
|
||||
area->va = addr + size;
|
||||
area->numPages = tailPages;
|
||||
} else {
|
||||
area->numPages = firstPage;
|
||||
if (tailPages != 0) {
|
||||
area->next = new HeapAlloc { addr + size, tailPages, oldProt,
|
||||
area->allocationId,
|
||||
area->next };
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t resident = 0;
|
||||
for (uint64_t i = 0; i < pages; i++)
|
||||
if (Memory::VMM::Paging::GetPhysAddr(proc->pml4Phys, addr + i * 0x1000ULL) != 0)
|
||||
resident++;
|
||||
g_heapLocks[slot].Release();
|
||||
|
||||
Memory::UnmapAndFreeUserRange(proc->pml4Phys, addr, pages);
|
||||
Sched::ReleaseUserHeapRange(slot, addr, size);
|
||||
g_heapLocks[slot].Acquire();
|
||||
Sched::g_allocatedPages[slot] -= resident;
|
||||
g_heapLocks[slot].Release();
|
||||
return 0;
|
||||
}
|
||||
|
||||
inline int64_t Sys_Protect(uint64_t addr, uint64_t size, uint64_t prot) {
|
||||
auto* proc = Sched::GetCurrentProcessPtr();
|
||||
int slot = GetCurrentSlot();
|
||||
if (proc == nullptr || slot < 0 || size == 0 || (addr & 0xFFFULL) != 0 ||
|
||||
size > UINT64_MAX - 0xFFFULL ||
|
||||
(prot & ~(VmProtRead | VmProtWrite | VmProtExec)) != 0 ||
|
||||
(prot & VmProtRead) == 0 ||
|
||||
((prot & VmProtWrite) != 0 && (prot & VmProtExec) != 0)) return -1;
|
||||
size = (size + 0xFFFULL) & ~0xFFFULL;
|
||||
if (addr > UINT64_MAX - size) return -1;
|
||||
|
||||
g_heapLocks[slot].Acquire();
|
||||
uint64_t pages = size / 0x1000ULL;
|
||||
|
||||
// Protection changes may span adjacent VMA fragments created by a
|
||||
// previous mprotect. Validate the complete range before changing any
|
||||
// metadata so failure is atomic from userspace's perspective.
|
||||
for (uint64_t page = 0; page < pages; page++) {
|
||||
uint64_t pageVa = addr + page * 0x1000ULL;
|
||||
bool covered = false;
|
||||
for (HeapAlloc* area = g_heapAllocs[slot]; area != nullptr;
|
||||
area = area->next) {
|
||||
uint64_t areaSize = area->numPages * 0x1000ULL;
|
||||
if (pageVa >= area->va && pageVa - area->va < areaSize) {
|
||||
covered = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!covered) { g_heapLocks[slot].Release(); return -1; }
|
||||
}
|
||||
|
||||
uint64_t end = addr + size;
|
||||
for (HeapAlloc* area = g_heapAllocs[slot]; area != nullptr;) {
|
||||
HeapAlloc* oldNext = area->next;
|
||||
uint64_t areaEnd = area->va + area->numPages * 0x1000ULL;
|
||||
uint64_t overlapStart = area->va > addr ? area->va : addr;
|
||||
uint64_t overlapEnd = areaEnd < end ? areaEnd : end;
|
||||
if (overlapStart < overlapEnd) {
|
||||
uint64_t oldVa = area->va;
|
||||
uint64_t oldProt = area->prot;
|
||||
uint64_t prefixPages = (overlapStart - oldVa) / 0x1000ULL;
|
||||
uint64_t protectedPages = (overlapEnd - overlapStart) / 0x1000ULL;
|
||||
uint64_t tailPages = (areaEnd - overlapEnd) / 0x1000ULL;
|
||||
|
||||
if (prefixPages == 0) {
|
||||
area->numPages = protectedPages;
|
||||
area->prot = prot;
|
||||
if (tailPages != 0)
|
||||
area->next = new HeapAlloc { overlapEnd, tailPages,
|
||||
oldProt,
|
||||
area->allocationId,
|
||||
oldNext };
|
||||
} else {
|
||||
area->numPages = prefixPages;
|
||||
auto* middle = new HeapAlloc { overlapStart, protectedPages,
|
||||
prot, area->allocationId,
|
||||
oldNext };
|
||||
area->next = middle;
|
||||
if (tailPages != 0)
|
||||
middle->next = new HeapAlloc { overlapEnd, tailPages,
|
||||
oldProt,
|
||||
area->allocationId,
|
||||
oldNext };
|
||||
}
|
||||
}
|
||||
area = oldNext;
|
||||
}
|
||||
|
||||
bool writable = (prot & VmProtWrite) != 0;
|
||||
bool executable = (prot & VmProtExec) != 0;
|
||||
for (uint64_t i = 0; i < pages; i++) {
|
||||
if (Memory::VMM::Paging::GetPhysAddr(proc->pml4Phys,
|
||||
addr + i * 0x1000ULL) == 0)
|
||||
continue;
|
||||
if (!Memory::VMM::Paging::ProtectUserIn(proc->pml4Phys,
|
||||
addr + i * 0x1000ULL,
|
||||
writable, executable)) {
|
||||
g_heapLocks[slot].Release();
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
g_heapLocks[slot].Release();
|
||||
Memory::ShootdownUserRange(proc->pml4Phys, addr, (uint32_t)pages);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Resolve a non-present access inside an anonymous VMA. This is used for
|
||||
// both ring-3 faults and kernel copies to untouched userspace buffers.
|
||||
inline bool TryHandleAnonymousPageFault(uint64_t faultAddr, uint64_t errorCode) {
|
||||
auto* proc = Sched::GetCurrentProcessPtr();
|
||||
int slot = GetCurrentSlot();
|
||||
if (proc == nullptr || slot < 0 || faultAddr < Sched::UserHeapBase ||
|
||||
faultAddr >= Sched::UserHeapLimit || (errorCode & 1) != 0) return false;
|
||||
|
||||
uint64_t pageVa = faultAddr & ~0xFFFULL;
|
||||
g_heapLocks[slot].Acquire();
|
||||
HeapAlloc* found = nullptr;
|
||||
for (HeapAlloc* area = g_heapAllocs[slot]; area != nullptr; area = area->next) {
|
||||
uint64_t bytes = area->numPages * 0x1000ULL;
|
||||
if (pageVa >= area->va && pageVa - area->va < bytes) {
|
||||
found = area;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (found == nullptr ||
|
||||
((errorCode & (1ULL << 1)) != 0 && (found->prot & VmProtWrite) == 0) ||
|
||||
((errorCode & (1ULL << 4)) != 0 && (found->prot & VmProtExec) == 0)) {
|
||||
g_heapLocks[slot].Release();
|
||||
return false;
|
||||
}
|
||||
|
||||
// A sibling may have resolved the same fault while this CPU waited.
|
||||
if (Memory::VMM::Paging::GetPhysAddr(proc->pml4Phys, pageVa) != 0) {
|
||||
g_heapLocks[slot].Release();
|
||||
return true;
|
||||
}
|
||||
|
||||
void* page = Memory::g_pfa->AllocateZeroed();
|
||||
if (page == nullptr) {
|
||||
g_heapLocks[slot].Release();
|
||||
return false;
|
||||
}
|
||||
uint64_t phys = Memory::SubHHDM((uint64_t)page);
|
||||
bool mapped = Memory::VMM::Paging::MapUserInPermissions(
|
||||
proc->pml4Phys, phys, pageVa,
|
||||
(found->prot & VmProtWrite) != 0, (found->prot & VmProtExec) != 0);
|
||||
if (!mapped) Memory::g_pfa->Free(page);
|
||||
else Sched::g_allocatedPages[slot]++;
|
||||
g_heapLocks[slot].Release();
|
||||
return mapped;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -17,13 +17,13 @@ namespace montauk::abi {
|
||||
|
||||
// Copy strings into fixed-size arrays (user-accessible)
|
||||
const char* name = "MontaukOS";
|
||||
const char* ver = "0.1.9";
|
||||
const char* ver = "0.2.0";
|
||||
for (int i = 0; name[i]; i++) outInfo->osName[i] = name[i];
|
||||
outInfo->osName[9] = '\0';
|
||||
for (int i = 0; ver[i]; i++) outInfo->osVersion[i] = ver[i];
|
||||
outInfo->osVersion[5] = '\0';
|
||||
|
||||
outInfo->apiVersion = 10;
|
||||
outInfo->apiVersion = 11;
|
||||
outInfo->maxProcesses = Sched::MaxProcesses;
|
||||
outInfo->buildNumber = MONTAUK_BUILD_NUMBER;
|
||||
}
|
||||
|
||||
@@ -15,12 +15,15 @@ namespace montauk::abi {
|
||||
|
||||
static constexpr uint32_t RedirOutputStreamCapacity = 64 * 1024;
|
||||
|
||||
static int Sys_SpawnRedir(const char* path, const char* args) {
|
||||
static int Sys_SpawnRedirInternal(
|
||||
const char* path, const char* args,
|
||||
const SpawnCapabilities* capabilities) {
|
||||
char resolved[256];
|
||||
if (!ResolveProcessPath(path, resolved, sizeof(resolved))) return -1;
|
||||
|
||||
int parentSlot = Ipc::CurrentSlot();
|
||||
int childPid = Sched::Spawn(resolved, args, false);
|
||||
int childPid = Sched::Spawn(resolved, args, false, nullptr, 0,
|
||||
capabilities);
|
||||
if (childPid < 0) return -1;
|
||||
|
||||
auto* child = Sched::GetProcessByPid(childPid);
|
||||
@@ -83,28 +86,50 @@ namespace montauk::abi {
|
||||
return childPid;
|
||||
}
|
||||
|
||||
static int Sys_SpawnRedir(const char* path, const char* args) {
|
||||
return Sys_SpawnRedirInternal(path, args, nullptr);
|
||||
}
|
||||
|
||||
static int Sys_SpawnRedirCaps(const char* path, const char* args,
|
||||
const SpawnCapabilities* requested) {
|
||||
auto* parent = Sched::GetCurrentProcessPtr();
|
||||
if (parent == nullptr || requested == nullptr) return -1;
|
||||
|
||||
SpawnCapabilities copy = *requested;
|
||||
if (!ValidCapabilityDelegation(copy, parent->delegableCaps)) {
|
||||
return SYS_ERR_PERMISSION;
|
||||
}
|
||||
return Sys_SpawnRedirInternal(path, args, ©);
|
||||
}
|
||||
|
||||
static int Sys_ChildIoRead(int childPid, char* buf, int maxLen) {
|
||||
auto* child = Sched::GetProcessByPid(childPid);
|
||||
if (child == nullptr || child->parentPid != Sched::GetCurrentPid())
|
||||
return SYS_ERR_PERMISSION;
|
||||
Ipc::HandleSnapshot snapshot;
|
||||
Ipc::Stream* stream = GetRedirOutStream(child, snapshot);
|
||||
if (child == nullptr || !child->redirected || stream == nullptr) return -1;
|
||||
if (!child->redirected || stream == nullptr) return -1;
|
||||
return Ipc::StreamRead(stream, (uint8_t*)buf, maxLen, true);
|
||||
}
|
||||
|
||||
static int Sys_ChildIoWrite(int childPid, const char* data, int len) {
|
||||
auto* child = Sched::GetProcessByPid(childPid);
|
||||
if (child == nullptr || child->parentPid != Sched::GetCurrentPid())
|
||||
return SYS_ERR_PERMISSION;
|
||||
Ipc::HandleSnapshot snapshot;
|
||||
Ipc::Stream* stream = GetRedirInStream(child, snapshot);
|
||||
if (child == nullptr || !child->redirected || stream == nullptr) return -1;
|
||||
if (!child->redirected || stream == nullptr) return -1;
|
||||
return WriteAllToStream(stream, (const uint8_t*)data, len);
|
||||
}
|
||||
|
||||
static int Sys_ChildIoWriteKey(int childPid, const KeyEvent* key) {
|
||||
if (key == nullptr) return -1;
|
||||
auto* child = Sched::GetProcessByPid(childPid);
|
||||
if (child == nullptr || child->parentPid != Sched::GetCurrentPid())
|
||||
return SYS_ERR_PERMISSION;
|
||||
Ipc::HandleSnapshot snapshot;
|
||||
Ipc::Mailbox* mailbox = GetRedirKeyMailbox(child, snapshot);
|
||||
if (child == nullptr || !child->redirected || mailbox == nullptr) return -1;
|
||||
if (!child->redirected || mailbox == nullptr) return -1;
|
||||
|
||||
for (;;) {
|
||||
uint64_t observedWake = Sched::ObserveObjectWake(mailbox);
|
||||
@@ -120,7 +145,9 @@ namespace montauk::abi {
|
||||
|
||||
static int Sys_ChildIoSetTermsz(int childPid, int cols, int rows) {
|
||||
auto* child = Sched::GetProcessByPid(childPid);
|
||||
if (child == nullptr || !child->redirected) return -1;
|
||||
if (child == nullptr || child->parentPid != Sched::GetCurrentPid())
|
||||
return SYS_ERR_PERMISSION;
|
||||
if (!child->redirected) return -1;
|
||||
child->termCols = cols;
|
||||
child->termRows = rows;
|
||||
return 0;
|
||||
|
||||
@@ -15,9 +15,14 @@ namespace montauk::abi {
|
||||
static bool Sys_IsKeyAvailable() {
|
||||
auto* proc = Sched::GetCurrentProcessPtr();
|
||||
if (proc && proc->redirected) {
|
||||
Ipc::HandleSnapshot snapshot;
|
||||
Ipc::Mailbox* mailbox = GetRedirKeyMailbox(proc, snapshot);
|
||||
if (mailbox != nullptr) return Ipc::MailboxHasMessage(mailbox);
|
||||
Ipc::HandleSnapshot inputSnapshot;
|
||||
Ipc::HandleSnapshot mailboxSnapshot;
|
||||
Ipc::Stream* input = GetRedirInStream(proc, inputSnapshot);
|
||||
Ipc::Mailbox* mailbox = GetRedirKeyMailbox(proc, mailboxSnapshot);
|
||||
if (input != nullptr || mailbox != nullptr) {
|
||||
if (input != nullptr && Ipc::StreamHasData(input)) return true;
|
||||
return mailbox != nullptr && Ipc::MailboxHasMessage(mailbox);
|
||||
}
|
||||
}
|
||||
return Drivers::PS2::Keyboard::IsKeyAvailable();
|
||||
}
|
||||
@@ -49,6 +54,9 @@ namespace montauk::abi {
|
||||
outEvent->shift = k.Shift;
|
||||
outEvent->ctrl = k.Ctrl;
|
||||
outEvent->alt = k.Alt;
|
||||
outEvent->capslock = k.CapsLock;
|
||||
outEvent->altgr = k.AltGr;
|
||||
outEvent->extended = k.Extended;
|
||||
}
|
||||
|
||||
static char Sys_GetChar() {
|
||||
@@ -97,4 +105,41 @@ namespace montauk::abi {
|
||||
}
|
||||
return Drivers::PS2::Keyboard::GetChar();
|
||||
}
|
||||
|
||||
/*
|
||||
* Non-blocking counterpart of Sys_GetChar: returns the next character, or
|
||||
* 0 when none is pending. IsKeyAvailable is not enough to make GetChar
|
||||
* safe to call -- a queued key-release or modifier event reports as
|
||||
* "available" but carries no ascii, and GetChar would go on to block. So
|
||||
* the drain has to happen on this side of the syscall boundary.
|
||||
*/
|
||||
static char Sys_GetCharNb() {
|
||||
auto* proc = Sched::GetCurrentProcessPtr();
|
||||
if (proc && proc->redirected) {
|
||||
Ipc::HandleSnapshot inputSnapshot;
|
||||
Ipc::HandleSnapshot mailboxSnapshot;
|
||||
Ipc::Stream* input = GetRedirInStream(proc, inputSnapshot);
|
||||
Ipc::Mailbox* mailbox = GetRedirKeyMailbox(proc, mailboxSnapshot);
|
||||
if (input != nullptr || mailbox != nullptr) {
|
||||
if (input != nullptr) {
|
||||
uint8_t c = 0;
|
||||
if (Ipc::StreamRead(input, &c, 1, true) > 0) return (char)c;
|
||||
}
|
||||
|
||||
while (mailbox != nullptr) {
|
||||
KeyEvent ev{};
|
||||
uint16_t len = sizeof(ev);
|
||||
if (Ipc::MailboxRecv(mailbox, nullptr, &ev, &len, true) <= 0) break;
|
||||
if (ev.pressed && ev.ascii != 0) return ev.ascii;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
while (Drivers::PS2::Keyboard::IsKeyAvailable()) {
|
||||
auto k = Drivers::PS2::Keyboard::GetKey();
|
||||
if (k.Pressed && k.Ascii != 0) return k.Ascii;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#include <Memory/UserRange.hpp>
|
||||
#include <cstdint>
|
||||
#include <Sched/Scheduler.hpp>
|
||||
#include <Sched/ElfLoader.hpp>
|
||||
@@ -133,7 +134,7 @@ namespace montauk::abi {
|
||||
|
||||
auto* proc = Sched::GetCurrentProcessPtr();
|
||||
if (proc != nullptr) {
|
||||
Ipc::UnmapAndFreeUserRange(proc->pml4Phys, libBase,
|
||||
Memory::UnmapAndFreeUserRange(proc->pml4Phys, libBase,
|
||||
(libEnd - libBase) / 0x1000ULL);
|
||||
}
|
||||
|
||||
@@ -202,7 +203,7 @@ namespace montauk::abi {
|
||||
uint64_t libBase = GetLibSlotBase(i);
|
||||
uint64_t libEnd = libBase + Sched::LIB_MAX_SIZE;
|
||||
|
||||
Ipc::UnmapAndFreeUserRange(proc->pml4Phys, libBase,
|
||||
Memory::UnmapAndFreeUserRange(proc->pml4Phys, libBase,
|
||||
(libEnd - libBase) / 0x1000ULL);
|
||||
|
||||
g_libTable[slot][i].inUse = false;
|
||||
|
||||
@@ -31,6 +31,11 @@ namespace montauk::abi {
|
||||
// action; any other value records it as the pending action. Returns the
|
||||
// pending action for queries, or 0 when recording one.
|
||||
static int64_t Sys_PowerRequest(int action) {
|
||||
// Polled by the session leader every second; deliberately silent and
|
||||
// non-destructive, so it neither floods the log nor races login for
|
||||
// the request it is about to hand over by exiting.
|
||||
if (action == POWER_REQ_PEEK) return (int64_t)g_pendingPowerAction;
|
||||
|
||||
if (action == POWER_REQ_QUERY) {
|
||||
int pending = g_pendingPowerAction;
|
||||
g_pendingPowerAction = POWER_REQ_QUERY;
|
||||
|
||||
+133
-38
@@ -43,44 +43,100 @@ namespace montauk::abi {
|
||||
return Sched::LookupExitCode(pid);
|
||||
}
|
||||
|
||||
static int Sys_Spawn(const char* path, const char* args) {
|
||||
// Hand a freshly spawned child the parent's redirected console. Both spawn
|
||||
// syscalls need this: a console tool launched from a GUI terminal must read
|
||||
// its keys from the terminal's mailbox and write its output back up the
|
||||
// stream, whether or not it also carries a capability grant. The child is
|
||||
// created suspended (startReady == false) so its first instruction cannot
|
||||
// run before the channels exist, and is started here once they do.
|
||||
// Returns childPid, or kills the child and returns -1 on failure.
|
||||
static int InheritRedirection(int childPid, Sched::Process* parent, int parentSlot) {
|
||||
auto* child = Sched::GetProcessByPid(childPid);
|
||||
int childSlot = Ipc::SlotForPid(childPid);
|
||||
if (child == nullptr || childSlot < 0 || parentSlot < 0) {
|
||||
Sched::KillProcess(childPid);
|
||||
return -1;
|
||||
}
|
||||
|
||||
child->ioOutHandle = DuplicateHandleBetweenSlots(parentSlot, parent->ioOutHandle, childSlot);
|
||||
child->ioInHandle = DuplicateHandleBetweenSlots(parentSlot, parent->ioInHandle, childSlot);
|
||||
child->ioKeyHandle = DuplicateHandleBetweenSlots(parentSlot, parent->ioKeyHandle, childSlot);
|
||||
|
||||
if (child->ioOutHandle < 0 || child->ioInHandle < 0 || child->ioKeyHandle < 0 ||
|
||||
!ConfigureRedirWaitsetForSlot(childSlot, child)) {
|
||||
Sched::KillProcess(childPid);
|
||||
return -1;
|
||||
}
|
||||
|
||||
child->redirected = true;
|
||||
child->parentPid = parent->pid;
|
||||
child->termCols = parent->termCols;
|
||||
child->termRows = parent->termRows;
|
||||
if (Sched::StartProcess(childPid) < 0) {
|
||||
Sched::KillProcess(childPid);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return childPid;
|
||||
}
|
||||
|
||||
static int Sys_Spawn(const char* path, const char* args,
|
||||
const char* environment = nullptr, uint32_t environmentLength = 0) {
|
||||
char resolved[256];
|
||||
if (!ResolveProcessPath(path, resolved, sizeof(resolved))) return -1;
|
||||
|
||||
auto* parent = Sched::GetCurrentProcessPtr();
|
||||
int parentSlot = Ipc::CurrentSlot();
|
||||
bool inheritRedirection = parent && parent->redirected;
|
||||
int childPid = Sched::Spawn(resolved, args, !inheritRedirection);
|
||||
int childPid = Sched::Spawn(resolved, args, !inheritRedirection,
|
||||
environment, environmentLength);
|
||||
if (childPid < 0) return childPid;
|
||||
|
||||
if (inheritRedirection) {
|
||||
auto* child = Sched::GetProcessByPid(childPid);
|
||||
int childSlot = Ipc::SlotForPid(childPid);
|
||||
if (child == nullptr || childSlot < 0 || parentSlot < 0) {
|
||||
Sched::KillProcess(childPid);
|
||||
return -1;
|
||||
}
|
||||
if (inheritRedirection)
|
||||
return InheritRedirection(childPid, parent, parentSlot);
|
||||
|
||||
child->ioOutHandle = DuplicateHandleBetweenSlots(parentSlot, parent->ioOutHandle, childSlot);
|
||||
child->ioInHandle = DuplicateHandleBetweenSlots(parentSlot, parent->ioInHandle, childSlot);
|
||||
child->ioKeyHandle = DuplicateHandleBetweenSlots(parentSlot, parent->ioKeyHandle, childSlot);
|
||||
return childPid;
|
||||
}
|
||||
|
||||
if (child->ioOutHandle < 0 || child->ioInHandle < 0 || child->ioKeyHandle < 0 ||
|
||||
!ConfigureRedirWaitsetForSlot(childSlot, child)) {
|
||||
Sched::KillProcess(childPid);
|
||||
return -1;
|
||||
}
|
||||
static int Sys_SpawnCaps(const char* path, const char* args,
|
||||
const char* user, const SpawnCapabilities* requested) {
|
||||
auto* parent = Sched::GetCurrentProcessPtr();
|
||||
if (parent == nullptr || requested == nullptr) return -1;
|
||||
|
||||
child->redirected = true;
|
||||
child->parentPid = parent->pid;
|
||||
child->termCols = parent->termCols;
|
||||
child->termRows = parent->termRows;
|
||||
if (Sched::StartProcess(childPid) < 0) {
|
||||
Sched::KillProcess(childPid);
|
||||
return -1;
|
||||
}
|
||||
// Snapshot all security-sensitive userspace inputs before evaluating
|
||||
// them. This prevents another thread from changing a mask or owner
|
||||
// name between validation and process creation.
|
||||
SpawnCapabilities copy = *requested;
|
||||
char childUser[32];
|
||||
const char* userOverride = nullptr;
|
||||
if (user != nullptr) {
|
||||
if (!Sched::HasCapability(CAP_USER_ADMIN))
|
||||
return SYS_ERR_PERMISSION;
|
||||
int i = 0;
|
||||
for (; i < 31 && user[i]; i++) childUser[i] = user[i];
|
||||
childUser[i] = '\0';
|
||||
userOverride = childUser;
|
||||
}
|
||||
|
||||
// Authority may only diminish down the process tree. In particular,
|
||||
// possessing a capability is insufficient to pass it: the parent must
|
||||
// also hold it in its delegable set.
|
||||
if (!ValidCapabilityDelegation(copy, parent->delegableCaps)) {
|
||||
return SYS_ERR_PERMISSION;
|
||||
}
|
||||
|
||||
char resolved[256];
|
||||
if (!ResolveProcessPath(path, resolved, sizeof(resolved))) return -1;
|
||||
|
||||
int parentSlot = Ipc::CurrentSlot();
|
||||
bool inheritRedirection = parent->redirected;
|
||||
int childPid = Sched::Spawn(resolved, args, !inheritRedirection,
|
||||
nullptr, 0, ©, userOverride);
|
||||
if (childPid < 0) return childPid;
|
||||
|
||||
if (inheritRedirection)
|
||||
return InheritRedirection(childPid, parent, parentSlot);
|
||||
|
||||
return childPid;
|
||||
}
|
||||
|
||||
@@ -107,6 +163,24 @@ namespace montauk::abi {
|
||||
return i;
|
||||
}
|
||||
|
||||
static int Sys_GetEnviron(char* buf, uint64_t maxLen) {
|
||||
auto* proc = Sched::GetCurrentProcessPtr();
|
||||
if (proc == nullptr || buf == nullptr || maxLen == 0) return -1;
|
||||
if (maxLen < proc->environmentLength) return -2;
|
||||
for (uint32_t i = 0; i < proc->environmentLength; i++)
|
||||
buf[i] = proc->environment[i];
|
||||
return (int)proc->environmentLength;
|
||||
}
|
||||
|
||||
static int Sys_SetEnviron(const char* buf, uint64_t len) {
|
||||
auto* proc = Sched::GetCurrentProcessPtr();
|
||||
if (proc == nullptr || buf == nullptr || len == 0 ||
|
||||
len > Sched::Process::EnvironmentBytes || buf[len - 1] != '\0') return -1;
|
||||
for (uint64_t i = 0; i < len; i++) proc->environment[i] = buf[i];
|
||||
proc->environmentLength = (uint32_t)len;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int Sys_ProcList(ProcInfo* buf, int maxCount) {
|
||||
if (buf == nullptr || maxCount <= 0) return 0;
|
||||
int count = 0;
|
||||
@@ -130,15 +204,40 @@ namespace montauk::abi {
|
||||
}
|
||||
buf[count].heapUsed = Sched::g_allocatedPages[i] * 0x1000;
|
||||
buf[count].cpuTimeMs = proc->cpuTimeMs;
|
||||
buf[count].permittedCaps = proc->permittedCaps;
|
||||
buf[count].effectiveCaps = proc->effectiveCaps;
|
||||
buf[count].delegableCaps = proc->delegableCaps;
|
||||
count++;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
static int Sys_Kill(int pid) {
|
||||
if (!Sched::HasCapability(CAP_PROCESS_ADMIN)) {
|
||||
int ancestor = pid;
|
||||
bool descendant = false;
|
||||
for (int depth = 0; depth < Sched::MaxProcesses; depth++) {
|
||||
auto* target = Sched::GetProcessByPid(ancestor);
|
||||
if (target == nullptr || target->parentPid < 0) break;
|
||||
if (target->parentPid == Sched::GetCurrentPid()) {
|
||||
descendant = true;
|
||||
break;
|
||||
}
|
||||
ancestor = target->parentPid;
|
||||
}
|
||||
if (!descendant) return SYS_ERR_PERMISSION;
|
||||
}
|
||||
return Sched::KillProcess(pid);
|
||||
}
|
||||
|
||||
static int Sys_SetSession() {
|
||||
return Sched::CreateSession();
|
||||
}
|
||||
|
||||
static int Sys_KillSession(int sessionId) {
|
||||
return Sched::KillSession(sessionId);
|
||||
}
|
||||
|
||||
static int Sys_SetUser(int pid, const char* name) {
|
||||
if (name == nullptr) return -1;
|
||||
auto* target = Sched::GetProcessByPid(pid);
|
||||
@@ -165,10 +264,15 @@ namespace montauk::abi {
|
||||
if (proc == nullptr || buf == nullptr || maxLen == 0) return -1;
|
||||
|
||||
const char* cwd = proc->cwd[0] ? proc->cwd : "0:/";
|
||||
int i = 0;
|
||||
for (; i < (int)maxLen - 1 && cwd[i]; i++) buf[i] = cwd[i];
|
||||
buf[i] = '\0';
|
||||
return i;
|
||||
uint64_t cwdLen = 0;
|
||||
while (cwd[cwdLen] != '\0') cwdLen++;
|
||||
|
||||
// Reserve -1 for non-size failures so libc can distinguish them
|
||||
// from a buffer that can be retried with a larger allocation.
|
||||
if (maxLen <= cwdLen) return -2;
|
||||
|
||||
for (uint64_t i = 0; i <= cwdLen; i++) buf[i] = cwd[i];
|
||||
return (int)cwdLen;
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
@@ -197,15 +301,6 @@ namespace montauk::abi {
|
||||
char resolved[256];
|
||||
if (!ResolveProcessPath(path, resolved, sizeof(resolved))) return -1;
|
||||
|
||||
bool isDriveRoot = false;
|
||||
{
|
||||
int prefixLen = 0;
|
||||
if (ParseDrivePrefix(resolved, &prefixLen) >= 0 &&
|
||||
resolved[prefixLen] == '/' && resolved[prefixLen + 1] == '\0') {
|
||||
isDriveRoot = true;
|
||||
}
|
||||
}
|
||||
|
||||
// ReadDir doubles as the directory-existence probe: it fails
|
||||
// for nonexistent paths and for regular files (directories are
|
||||
// not openable as files anymore, so the old open-based check
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
/*
|
||||
* Sdr.hpp
|
||||
* Software-defined radio receive syscalls.
|
||||
* SYS_SDR_COUNT / INFO / OPEN / CLOSE / START / STOP / READ / SETPARAM / GETPARAM
|
||||
* Thin syscall layer over the generic SDR subsystem (Drivers::Radio::Sdr).
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
#include <Drivers/Radio/Sdr.hpp>
|
||||
|
||||
#include "Syscall.hpp"
|
||||
|
||||
namespace montauk::abi {
|
||||
|
||||
static int64_t Sys_SdrCount() {
|
||||
return (int64_t)Drivers::Radio::Sdr::Count();
|
||||
}
|
||||
|
||||
static int64_t Sys_SdrInfo(int index, SdrDeviceInfo* out) {
|
||||
if (!out) return -1;
|
||||
return Drivers::Radio::Sdr::GetInfo(index, out) ? 0 : -1;
|
||||
}
|
||||
|
||||
static int64_t Sys_SdrOpen(int index) {
|
||||
return (int64_t)Drivers::Radio::Sdr::Open(index);
|
||||
}
|
||||
|
||||
static int64_t Sys_SdrClose(int handle) {
|
||||
return (int64_t)Drivers::Radio::Sdr::Close(handle);
|
||||
}
|
||||
|
||||
static int64_t Sys_SdrStart(int handle) {
|
||||
return (int64_t)Drivers::Radio::Sdr::Start(handle);
|
||||
}
|
||||
|
||||
static int64_t Sys_SdrStop(int handle) {
|
||||
return (int64_t)Drivers::Radio::Sdr::Stop(handle);
|
||||
}
|
||||
|
||||
static int64_t Sys_SdrRead(int handle, uint8_t* buf, uint32_t len) {
|
||||
if (!buf) return -1;
|
||||
return (int64_t)Drivers::Radio::Sdr::Read(handle, buf, len);
|
||||
}
|
||||
|
||||
static int64_t Sys_SdrSetParam(int handle, int param, uint64_t value) {
|
||||
return (int64_t)Drivers::Radio::Sdr::SetParam(handle, param, value);
|
||||
}
|
||||
|
||||
static int64_t Sys_SdrGetParam(int handle, int param) {
|
||||
return (int64_t)Drivers::Radio::Sdr::GetParam(handle, param);
|
||||
}
|
||||
|
||||
}
|
||||
+155
-26
@@ -33,7 +33,7 @@
|
||||
#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 "Audio.hpp" // SYS_AUDIOOPEN, SYS_AUDIOCLOSE, SYS_AUDIOWRITE, SYS_AUDIOCTL
|
||||
#include "BluetoothSyscall.hpp" // SYS_BTSCAN, SYS_BTCONNECT, SYS_BTDISCONNECT, SYS_BTLIST, SYS_BTINFO
|
||||
#include "Sdr.hpp" // SYS_SDR_COUNT, SYS_SDR_INFO, SYS_SDR_OPEN, SYS_SDR_CLOSE, SYS_SDR_START, SYS_SDR_STOP, SYS_SDR_READ, SYS_SDR_SETPARAM, SYS_SDR_GETPARAM
|
||||
#include "Usb.hpp" // generic process-owned USB interface access
|
||||
#include "WifiSyscall.hpp" // SYS_WIFI_SCAN, SYS_WIFI_INFO, SYS_WIFI_CONNECT, SYS_WIFI_DISCONNECT
|
||||
#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
|
||||
@@ -53,6 +53,7 @@ namespace montauk::abi {
|
||||
static constexpr uint64_t kMaxWindowTitleBytes = 256;
|
||||
static constexpr uint64_t kMaxHostnameBytes = 256;
|
||||
static constexpr uint64_t kMaxUserNameBytes = 32;
|
||||
static constexpr uint64_t kMaxUserspaceLogEntryBytes = 1024;
|
||||
|
||||
// ---- Dispatch ----
|
||||
|
||||
@@ -115,9 +116,17 @@ namespace montauk::abi {
|
||||
(int)frame->arg4);
|
||||
case SYS_ALLOC:
|
||||
return (int64_t)Sys_Alloc(frame->arg1);
|
||||
case SYS_ALLOC_EAGER:
|
||||
return (int64_t)Sys_AllocEager(frame->arg1);
|
||||
case SYS_FREE:
|
||||
Sys_Free(frame->arg1);
|
||||
return 0;
|
||||
case SYS_MMAP_ANON:
|
||||
return (int64_t)Sys_MapAnonymous(frame->arg1, frame->arg2);
|
||||
case SYS_MUNMAP:
|
||||
return Sys_Unmap(frame->arg1, frame->arg2);
|
||||
case SYS_MPROTECT:
|
||||
return Sys_Protect(frame->arg1, frame->arg2, frame->arg3);
|
||||
case SYS_GETTICKS:
|
||||
return (int64_t)Sys_GetTicks();
|
||||
case SYS_GETMILLISECONDS:
|
||||
@@ -134,6 +143,8 @@ namespace montauk::abi {
|
||||
return 0;
|
||||
case SYS_GETCHAR:
|
||||
return (int64_t)Sys_GetChar();
|
||||
case SYS_GETCHAR_NB:
|
||||
return (int64_t)Sys_GetCharNb();
|
||||
case SYS_PING:
|
||||
return (int64_t)Sys_Ping((uint32_t)frame->arg1, (uint32_t)frame->arg2);
|
||||
case SYS_SPAWN:
|
||||
@@ -141,8 +152,35 @@ namespace montauk::abi {
|
||||
if (frame->arg2 != 0 && !UserMemory::String(frame->arg2, kMaxArgsBytes)) return -1;
|
||||
return (int64_t)Sys_Spawn((const char*)frame->arg1,
|
||||
UserMemory::IsUserPtr(frame->arg2) ? (const char*)frame->arg2 : nullptr);
|
||||
case SYS_SPAWN_CAPS:
|
||||
if (!UserMemory::String(frame->arg1, kMaxPathBytes)) return -1;
|
||||
if (frame->arg2 != 0 && !UserMemory::String(frame->arg2, kMaxArgsBytes)) return -1;
|
||||
if (frame->arg3 != 0 && !UserMemory::String(frame->arg3, 32)) return -1;
|
||||
if (!UserMemory::Readable<SpawnCapabilities>(frame->arg4)) return -1;
|
||||
return (int64_t)Sys_SpawnCaps(
|
||||
(const char*)frame->arg1,
|
||||
frame->arg2 ? (const char*)frame->arg2 : nullptr,
|
||||
frame->arg3 ? (const char*)frame->arg3 : nullptr,
|
||||
(const SpawnCapabilities*)frame->arg4);
|
||||
case SYS_SPAWN_ENV:
|
||||
if (!UserMemory::String(frame->arg1, kMaxPathBytes)) return -1;
|
||||
if (frame->arg2 != 0 && !UserMemory::String(frame->arg2, kMaxArgsBytes)) return -1;
|
||||
if (frame->arg3 == 0 || frame->arg4 == 0 ||
|
||||
frame->arg4 > Sched::Process::EnvironmentBytes ||
|
||||
!UserMemory::Range(frame->arg3, frame->arg4, false) ||
|
||||
((const char*)frame->arg3)[frame->arg4 - 1] != '\0') return -1;
|
||||
return (int64_t)Sys_Spawn((const char*)frame->arg1,
|
||||
UserMemory::IsUserPtr(frame->arg2) ? (const char*)frame->arg2 : nullptr,
|
||||
(const char*)frame->arg3, (uint32_t)frame->arg4);
|
||||
case SYS_WAITPID:
|
||||
return Sys_WaitPid((int)frame->arg1);
|
||||
case SYS_GETENVIRON:
|
||||
if (!UserMemory::Range(frame->arg1, frame->arg2, true)) return -1;
|
||||
return Sys_GetEnviron((char*)frame->arg1, frame->arg2);
|
||||
case SYS_SETENVIRON:
|
||||
if (frame->arg2 == 0 || frame->arg2 > Sched::Process::EnvironmentBytes ||
|
||||
!UserMemory::Range(frame->arg1, frame->arg2, false)) return -1;
|
||||
return Sys_SetEnviron((const char*)frame->arg1, frame->arg2);
|
||||
case SYS_FBINFO:
|
||||
if (!UserMemory::Writable<FbInfo>(frame->arg1)) return -1;
|
||||
Sys_FbInfo((FbInfo*)frame->arg1);
|
||||
@@ -162,8 +200,11 @@ namespace montauk::abi {
|
||||
(uint64_t)frame->arg2 * sizeof(DisplayModeInfo), true)) return -1;
|
||||
return Sys_DisplayModes((DisplayModeInfo*)frame->arg1, (int)frame->arg2);
|
||||
case SYS_DISPLAYSETMODE:
|
||||
if (!Sched::HasCapability(CAP_DISPLAY_ADMIN)) return SYS_ERR_PERMISSION;
|
||||
return Sys_DisplaySetMode((int)frame->arg1);
|
||||
case SYS_DISPLAYBRIGHTNESS:
|
||||
if ((int64_t)frame->arg1 >= 0 &&
|
||||
!Sched::HasCapability(CAP_DISPLAY_ADMIN)) return SYS_ERR_PERMISSION;
|
||||
return Sys_DisplayBrightness((int)frame->arg1);
|
||||
case SYS_GETEXECPATH:
|
||||
if (!UserMemory::Range(frame->arg2 ? frame->arg1 : frame->arg1, frame->arg2, true)) return -1;
|
||||
@@ -172,24 +213,42 @@ namespace montauk::abi {
|
||||
if (!UserMemory::String(frame->arg1, kMaxPathBytes)) return -1;
|
||||
if (!UserMemory::Writable<FileStat>(frame->arg2)) return -1;
|
||||
return (int64_t)Sys_Stat((const char*)frame->arg1, (FileStat*)frame->arg2);
|
||||
case SYS_UTIME:
|
||||
if (!UserMemory::String(frame->arg1, kMaxPathBytes)) return -1;
|
||||
return (int64_t)Sys_Utime((const char*)frame->arg1,
|
||||
(int64_t)frame->arg2,
|
||||
(int64_t)frame->arg3,
|
||||
frame->arg4 != 0);
|
||||
case SYS_TERMSIZE:
|
||||
return (int64_t)Sys_TermSize();
|
||||
case SYS_GETARGS:
|
||||
if (!UserMemory::Range(frame->arg1, frame->arg2, true)) return -1;
|
||||
return (int64_t)Sys_GetArgs((char*)frame->arg1, frame->arg2);
|
||||
case SYS_RESET:
|
||||
if (!Sched::HasCapability(CAP_POWER_CONTROL)) return SYS_ERR_PERMISSION;
|
||||
Sys_Reset();
|
||||
return 0;
|
||||
case SYS_SHUTDOWN:
|
||||
if (!Sched::HasCapability(CAP_POWER_CONTROL)) return SYS_ERR_PERMISSION;
|
||||
Sys_Shutdown();
|
||||
return 0;
|
||||
case SYS_POWER_REQUEST:
|
||||
if (frame->arg1 != POWER_REQ_QUERY &&
|
||||
frame->arg1 != POWER_REQ_SHUTDOWN &&
|
||||
frame->arg1 != POWER_REQ_REBOOT &&
|
||||
frame->arg1 != POWER_REQ_PEEK) return -1;
|
||||
if (frame->arg1 == POWER_REQ_QUERY) {
|
||||
if (!Sched::HasCapability(CAP_POWER_CONTROL)) return SYS_ERR_PERMISSION;
|
||||
} else if (!Sched::HasCapability(CAP_POWER_REQUEST)) {
|
||||
return SYS_ERR_PERMISSION;
|
||||
}
|
||||
return Sys_PowerRequest((int)frame->arg1);
|
||||
case SYS_GETTIME:
|
||||
if (!UserMemory::Writable<DateTime>(frame->arg1)) return -1;
|
||||
Sys_GetTime((DateTime*)frame->arg1);
|
||||
return 0;
|
||||
case SYS_SETUNIXTIME:
|
||||
if (!Sched::HasCapability(CAP_SET_TIME)) return SYS_ERR_PERMISSION;
|
||||
return Sys_SetUnixTime((int64_t)frame->arg1);
|
||||
case SYS_SOCKET:
|
||||
return (int64_t)Sys_Socket((int)frame->arg1);
|
||||
@@ -215,6 +274,7 @@ namespace montauk::abi {
|
||||
Sys_GetNetCfg((NetCfg*)frame->arg1);
|
||||
return 0;
|
||||
case SYS_SETNETCFG:
|
||||
if (!Sched::HasCapability(CAP_NETWORK_ADMIN)) return SYS_ERR_PERMISSION;
|
||||
if (!UserMemory::Readable<NetCfg>(frame->arg1)) return -1;
|
||||
return (int64_t)Sys_SetNetCfg((const NetCfg*)frame->arg1);
|
||||
case SYS_NETSTATUS:
|
||||
@@ -269,9 +329,10 @@ namespace montauk::abi {
|
||||
case SYS_GETRANDOM:
|
||||
if (!UserMemory::Range(frame->arg1, frame->arg2, true)) return -1;
|
||||
return Sys_GetRandom((uint8_t*)frame->arg1, frame->arg2);
|
||||
case SYS_KLOG:
|
||||
case SYS_LOG:
|
||||
if (!Sched::HasCapability(CAP_LOG_READ)) return SYS_ERR_PERMISSION;
|
||||
if (!UserMemory::Range(frame->arg1, frame->arg2, true)) return -1;
|
||||
return Kt::ReadKernelLog((char*)frame->arg1, frame->arg2);
|
||||
return Kt::ReadKernelLogBuffer((char*)frame->arg1, frame->arg2);
|
||||
case SYS_MOUSESTATE:
|
||||
if (!UserMemory::Writable<MouseState>(frame->arg1)) return -1;
|
||||
Sys_MouseState((MouseState*)frame->arg1);
|
||||
@@ -284,6 +345,14 @@ namespace montauk::abi {
|
||||
if (frame->arg2 != 0 && !UserMemory::String(frame->arg2, kMaxArgsBytes)) return -1;
|
||||
return (int64_t)Sys_SpawnRedir((const char*)frame->arg1,
|
||||
UserMemory::IsUserPtr(frame->arg2) ? (const char*)frame->arg2 : nullptr);
|
||||
case SYS_SPAWN_REDIR_CAPS:
|
||||
if (!UserMemory::String(frame->arg1, kMaxPathBytes)) return -1;
|
||||
if (frame->arg2 != 0 && !UserMemory::String(frame->arg2, kMaxArgsBytes)) return -1;
|
||||
if (!UserMemory::Readable<SpawnCapabilities>(frame->arg3)) return -1;
|
||||
return (int64_t)Sys_SpawnRedirCaps(
|
||||
(const char*)frame->arg1,
|
||||
frame->arg2 ? (const char*)frame->arg2 : nullptr,
|
||||
(const SpawnCapabilities*)frame->arg3);
|
||||
case SYS_CHILDIO_READ:
|
||||
if ((int64_t)frame->arg3 < 0) return -1;
|
||||
if (!UserMemory::Range(frame->arg2, (uint64_t)frame->arg3, true)) return -1;
|
||||
@@ -328,6 +397,11 @@ namespace montauk::abi {
|
||||
return (int64_t)Sys_ProcList((ProcInfo*)frame->arg1, (int)frame->arg2);
|
||||
case SYS_KILL:
|
||||
return (int64_t)Sys_Kill((int)frame->arg1);
|
||||
case SYS_SETSESSION:
|
||||
return (int64_t)Sys_SetSession();
|
||||
case SYS_KILLSESSION:
|
||||
if (!Sched::HasCapability(CAP_PROCESS_ADMIN)) return SYS_ERR_PERMISSION;
|
||||
return (int64_t)Sys_KillSession((int)frame->arg1);
|
||||
case SYS_DEVLIST:
|
||||
if ((int64_t)frame->arg2 < 0) return -1;
|
||||
if (!UserMemory::Range(frame->arg1, (uint64_t)frame->arg2 * sizeof(DevInfo), true)) return -1;
|
||||
@@ -352,21 +426,28 @@ namespace montauk::abi {
|
||||
if (!UserMemory::Range(frame->arg1, (uint64_t)frame->arg2 * sizeof(PartInfo), true)) return -1;
|
||||
return (int64_t)Sys_PartList((PartInfo*)frame->arg1, (int)frame->arg2);
|
||||
case SYS_DISKREAD:
|
||||
if (!Sched::HasCapability(CAP_RAW_STORAGE)) return SYS_ERR_PERMISSION;
|
||||
return (int64_t)Sys_DiskRead((int)frame->arg1, frame->arg2,
|
||||
(uint32_t)frame->arg3, (void*)frame->arg4);
|
||||
case SYS_DISKWRITE:
|
||||
if (!Sched::HasCapability(CAP_RAW_STORAGE)) return SYS_ERR_PERMISSION;
|
||||
return (int64_t)Sys_DiskWrite((int)frame->arg1, frame->arg2,
|
||||
(uint32_t)frame->arg3, (const void*)frame->arg4);
|
||||
case SYS_GPTINIT:
|
||||
if (!Sched::HasCapability(CAP_STORAGE_ADMIN)) return SYS_ERR_PERMISSION;
|
||||
return (int64_t)Sys_GptInit((int)frame->arg1);
|
||||
case SYS_GPTADD:
|
||||
if (!Sched::HasCapability(CAP_STORAGE_ADMIN)) return SYS_ERR_PERMISSION;
|
||||
if (!UserMemory::Readable<GptAddParams>(frame->arg1)) return -1;
|
||||
return (int64_t)Sys_GptAdd((const GptAddParams*)frame->arg1);
|
||||
case SYS_FSMOUNT:
|
||||
if (!Sched::HasCapability(CAP_STORAGE_ADMIN)) return SYS_ERR_PERMISSION;
|
||||
return (int64_t)Sys_FsMount((int)frame->arg1, (int)frame->arg2);
|
||||
case SYS_FS_SYNC:
|
||||
if (!Sched::HasCapability(CAP_STORAGE_ADMIN)) return SYS_ERR_PERMISSION;
|
||||
return Sys_FsSync();
|
||||
case SYS_FSFORMAT:
|
||||
if (!Sched::HasCapability(CAP_STORAGE_ADMIN)) return SYS_ERR_PERMISSION;
|
||||
if (!UserMemory::Readable<FsFormatParams>(frame->arg1)) return -1;
|
||||
return (int64_t)Sys_FsFormat((const FsFormatParams*)frame->arg1);
|
||||
case SYS_AUDIOOPEN:
|
||||
@@ -384,26 +465,42 @@ namespace montauk::abi {
|
||||
return Sys_AudioList((AudioStreamInfo*)frame->arg1, (int)frame->arg2);
|
||||
case SYS_AUDIOWAIT:
|
||||
return Sys_AudioWait(frame->arg1, frame->arg2);
|
||||
case SYS_SDR_COUNT:
|
||||
return Sys_SdrCount();
|
||||
case SYS_SDR_INFO:
|
||||
if (!UserMemory::Writable<SdrDeviceInfo>(frame->arg2)) return -1;
|
||||
return Sys_SdrInfo((int)frame->arg1, (SdrDeviceInfo*)frame->arg2);
|
||||
case SYS_SDR_OPEN:
|
||||
return Sys_SdrOpen((int)frame->arg1);
|
||||
case SYS_SDR_CLOSE:
|
||||
return Sys_SdrClose((int)frame->arg1);
|
||||
case SYS_SDR_START:
|
||||
return Sys_SdrStart((int)frame->arg1);
|
||||
case SYS_SDR_STOP:
|
||||
return Sys_SdrStop((int)frame->arg1);
|
||||
case SYS_SDR_READ:
|
||||
if (!UserMemory::Range(frame->arg2, frame->arg3, true)) return -1;
|
||||
return Sys_SdrRead((int)frame->arg1, (uint8_t*)frame->arg2, (uint32_t)frame->arg3);
|
||||
case SYS_SDR_SETPARAM:
|
||||
return Sys_SdrSetParam((int)frame->arg1, (int)frame->arg2, frame->arg3);
|
||||
case SYS_SDR_GETPARAM:
|
||||
return Sys_SdrGetParam((int)frame->arg1, (int)frame->arg2);
|
||||
case SYS_USB_LIST: {
|
||||
if ((int64_t)frame->arg2 < 0) return USB_ERR_INVALID;
|
||||
uint64_t maxCount = frame->arg2 > 16 ? 16 : frame->arg2;
|
||||
if (!UserMemory::Range(frame->arg1,
|
||||
maxCount * sizeof(UsbInterfaceInfo), true)) return USB_ERR_INVALID;
|
||||
return Sys_UsbList((UsbInterfaceInfo*)frame->arg1, (int)maxCount);
|
||||
}
|
||||
case SYS_USB_CLAIM:
|
||||
if (!Sched::HasCapability(CAP_DEVICE_ADMIN)) return SYS_ERR_PERMISSION;
|
||||
if (frame->arg1 == 0 || frame->arg1 > 16 || frame->arg2 > 255)
|
||||
return USB_ERR_INVALID;
|
||||
return Sys_UsbClaim((uint8_t)frame->arg1, (uint8_t)frame->arg2);
|
||||
case SYS_USB_CLOSE:
|
||||
return Sys_UsbClose((int)frame->arg1);
|
||||
case SYS_USB_CONTROL: {
|
||||
if (!UserMemory::Readable<UsbControlRequest>(frame->arg2)) return USB_ERR_INVALID;
|
||||
UsbControlRequest request = *(const UsbControlRequest*)frame->arg2;
|
||||
if (frame->arg4 > 4096 || frame->arg4 != request.length) return USB_ERR_INVALID;
|
||||
bool deviceToHost = (request.requestType & 0x80) != 0;
|
||||
if (frame->arg4 != 0 &&
|
||||
!UserMemory::Range(frame->arg3, frame->arg4, deviceToHost)) return USB_ERR_INVALID;
|
||||
return Sys_UsbControl((int)frame->arg1, &request,
|
||||
(void*)frame->arg3, (uint32_t)frame->arg4);
|
||||
}
|
||||
case SYS_USB_BULK_IN_START:
|
||||
if (frame->arg2 > 0xffffffffULL || frame->arg3 > 0xffffffffULL)
|
||||
return USB_ERR_INVALID;
|
||||
return Sys_UsbBulkInStart((int)frame->arg1, (uint32_t)frame->arg2,
|
||||
(uint32_t)frame->arg3);
|
||||
case SYS_USB_BULK_IN_STOP:
|
||||
return Sys_UsbBulkInStop((int)frame->arg1);
|
||||
case SYS_USB_BULK_IN_READ:
|
||||
if (frame->arg3 > 0xffffffffULL) return USB_ERR_INVALID;
|
||||
if (!UserMemory::Range(frame->arg2, frame->arg3, true)) return USB_ERR_INVALID;
|
||||
return Sys_UsbBulkInRead((int)frame->arg1, (uint8_t*)frame->arg2,
|
||||
(uint32_t)frame->arg3);
|
||||
case SYS_POWERINFO:
|
||||
if (!UserMemory::Writable<PowerInfo>(frame->arg1)) return -1;
|
||||
return Sys_PowerInfo((PowerInfo*)frame->arg1);
|
||||
@@ -418,16 +515,20 @@ namespace montauk::abi {
|
||||
case SYS_THREAD_SELF:
|
||||
return Sys_ThreadSelf();
|
||||
case SYS_BTSCAN:
|
||||
if (!Sched::HasCapability(CAP_DEVICE_ADMIN)) return SYS_ERR_PERMISSION;
|
||||
if ((int64_t)frame->arg2 < 0) return -1;
|
||||
if (!UserMemory::Range(frame->arg1, (uint64_t)frame->arg2 * sizeof(BtScanResult), true)) return -1;
|
||||
return Sys_BtScan((BtScanResult*)frame->arg1, (int)frame->arg2, (uint32_t)frame->arg3);
|
||||
case SYS_BTCONNECT:
|
||||
if (!Sched::HasCapability(CAP_DEVICE_ADMIN)) return SYS_ERR_PERMISSION;
|
||||
if (!UserMemory::Range(frame->arg1, 6, false)) return -1;
|
||||
return Sys_BtConnect((const uint8_t*)frame->arg1);
|
||||
case SYS_BTDISCONNECT:
|
||||
if (!Sched::HasCapability(CAP_DEVICE_ADMIN)) return SYS_ERR_PERMISSION;
|
||||
if (!UserMemory::Range(frame->arg1, 6, false)) return -1;
|
||||
return Sys_BtDisconnect((const uint8_t*)frame->arg1);
|
||||
case SYS_BTSETADDR:
|
||||
if (!Sched::HasCapability(CAP_DEVICE_ADMIN)) return SYS_ERR_PERMISSION;
|
||||
if (!UserMemory::Range(frame->arg1, 6, false)) return -1;
|
||||
return Sys_BtSetAddr((const uint8_t*)frame->arg1);
|
||||
case SYS_BTBONDS:
|
||||
@@ -435,6 +536,7 @@ namespace montauk::abi {
|
||||
if (!UserMemory::Range(frame->arg1, (uint64_t)frame->arg2 * sizeof(BtBondInfo), true)) return -1;
|
||||
return Sys_BtBonds((BtBondInfo*)frame->arg1, (int)frame->arg2);
|
||||
case SYS_BTFORGET:
|
||||
if (!Sched::HasCapability(CAP_DEVICE_ADMIN)) return SYS_ERR_PERMISSION;
|
||||
if (!UserMemory::Range(frame->arg1, 6, false)) return -1;
|
||||
return Sys_BtForget((const uint8_t*)frame->arg1);
|
||||
case SYS_BTLIST:
|
||||
@@ -445,6 +547,7 @@ namespace montauk::abi {
|
||||
if (!UserMemory::Writable<BtAdapterInfo>(frame->arg1)) return -1;
|
||||
return Sys_BtInfo((BtAdapterInfo*)frame->arg1);
|
||||
case SYS_WIFI_SCAN:
|
||||
if (!Sched::HasCapability(CAP_NETWORK_ADMIN)) return SYS_ERR_PERMISSION;
|
||||
if ((int64_t)frame->arg2 < 0) 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);
|
||||
@@ -452,18 +555,22 @@ namespace montauk::abi {
|
||||
if (!UserMemory::Writable<WifiInfo>(frame->arg1)) return -1;
|
||||
return Sys_WifiInfo((WifiInfo*)frame->arg1);
|
||||
case SYS_WIFI_CONNECT:
|
||||
if (!Sched::HasCapability(CAP_NETWORK_ADMIN)) return SYS_ERR_PERMISSION;
|
||||
if (!UserMemory::String(frame->arg1, 64)) return -1;
|
||||
if (frame->arg2 != 0 && !UserMemory::String(frame->arg2, 128)) return -1;
|
||||
return Sys_WifiConnect((const char*)frame->arg1, (const char*)frame->arg2);
|
||||
case SYS_WIFI_DISCONNECT:
|
||||
if (!Sched::HasCapability(CAP_NETWORK_ADMIN)) return SYS_ERR_PERMISSION;
|
||||
return Sys_WifiDisconnect();
|
||||
case SYS_WIFI_SCAN_START:
|
||||
if (!Sched::HasCapability(CAP_NETWORK_ADMIN)) return SYS_ERR_PERMISSION;
|
||||
return Sys_WifiScanStart((uint32_t)frame->arg1);
|
||||
case SYS_WIFI_RESULTS:
|
||||
if ((int64_t)frame->arg2 < 0) 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);
|
||||
case SYS_WIFI_CONNECT_ASYNC:
|
||||
if (!Sched::HasCapability(CAP_NETWORK_ADMIN)) return SYS_ERR_PERMISSION;
|
||||
if (!UserMemory::String(frame->arg1, 64)) return -1;
|
||||
if (frame->arg2 != 0 && !UserMemory::String(frame->arg2, 128)) return -1;
|
||||
return Sys_WifiConnectAsync((const char*)frame->arg1, (const char*)frame->arg2);
|
||||
@@ -472,12 +579,15 @@ namespace montauk::abi {
|
||||
if (!UserMemory::Range(frame->arg1, (uint64_t)frame->arg2 * sizeof(NetIfInfo), true)) return -1;
|
||||
return Sys_NetIfs((NetIfInfo*)frame->arg1, (int)frame->arg2);
|
||||
case SYS_SUSPEND:
|
||||
if (!Sched::HasCapability(CAP_SUSPEND)) return SYS_ERR_PERMISSION;
|
||||
return Sys_Suspend();
|
||||
case SYS_SETTZ:
|
||||
if (!Sched::HasCapability(CAP_SET_TIME)) return SYS_ERR_PERMISSION;
|
||||
return Sys_SetTZ((int32_t)frame->arg1);
|
||||
case SYS_GETTZ:
|
||||
return Sys_GetTZ();
|
||||
case SYS_SETUSER:
|
||||
if (!Sched::HasCapability(CAP_USER_ADMIN)) return SYS_ERR_PERMISSION;
|
||||
if (!UserMemory::String(frame->arg2, kMaxUserNameBytes)) return -1;
|
||||
return Sys_SetUser((int)frame->arg1, (const char*)frame->arg2);
|
||||
case SYS_GETUSER:
|
||||
@@ -574,6 +684,24 @@ namespace montauk::abi {
|
||||
return Sys_ClipboardClear();
|
||||
case SYS_INPUT_WAIT:
|
||||
return (int64_t)Sys_InputWait(frame->arg1, frame->arg2);
|
||||
case SYS_LOG_WRITE: {
|
||||
if (!UserMemory::String(frame->arg1, kMaxUserspaceLogEntryBytes)) return -1;
|
||||
|
||||
auto* process = Sched::GetCurrentProcessPtr();
|
||||
const char* username = process != nullptr && process->user[0] != '\0'
|
||||
? process->user
|
||||
: "unknown";
|
||||
const char* imageName = process != nullptr && process->name[0] != '\0'
|
||||
? process->name
|
||||
: "unknown";
|
||||
|
||||
Kt::UserspaceLogStream(imageName, username)
|
||||
<< (const char*)frame->arg1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
case SYS_TERMINAL_ATTACHED:
|
||||
return Sys_TerminalAttached();
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
@@ -582,9 +710,9 @@ namespace montauk::abi {
|
||||
// ---- SYSCALL MSR initialization ----
|
||||
|
||||
void InitializeSyscalls() {
|
||||
// Enable SYSCALL/SYSRET in EFER
|
||||
// Enable SYSCALL/SYSRET and no-execute page permissions in EFER.
|
||||
uint64_t efer = Hal::ReadMSR(Hal::IA32_EFER);
|
||||
efer |= 1; // SCE bit (Syscall Enable)
|
||||
efer |= 1 | (1ULL << 11); // SCE | NXE
|
||||
Hal::WriteMSR(Hal::IA32_EFER, efer);
|
||||
|
||||
// STAR: kernel CS in [47:32], sysret base in [63:48]
|
||||
@@ -600,7 +728,8 @@ namespace montauk::abi {
|
||||
Hal::WriteMSR(Hal::IA32_FMASK, 0x200);
|
||||
|
||||
Kt::KernelLogStream(Kt::OK, "Syscall") << "SYSCALL/SYSRET initialized (LSTAR="
|
||||
<< kcp::hex << (uint64_t)SyscallEntry << kcp::dec << ", 154 syscall slots)";
|
||||
<< kcp::hex << (uint64_t)SyscallEntry << kcp::dec << ", "
|
||||
<< (SYS_SPAWN_REDIR_CAPS + 1) << " syscall slots)";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+166
-39
@@ -103,7 +103,7 @@ namespace montauk::abi {
|
||||
/* Random.hpp */
|
||||
static constexpr uint64_t SYS_GETRANDOM = 45;
|
||||
|
||||
static constexpr uint64_t SYS_KLOG = 46;
|
||||
static constexpr uint64_t SYS_LOG = 46;
|
||||
|
||||
/* Mouse.hpp */
|
||||
static constexpr uint64_t SYS_MOUSESTATE = 47;
|
||||
@@ -157,6 +157,12 @@ namespace montauk::abi {
|
||||
static constexpr uint64_t SYS_AUDIOWRITE = 82;
|
||||
static constexpr uint64_t SYS_AUDIOCTL = 83;
|
||||
|
||||
/* Userspace log */
|
||||
static constexpr uint64_t SYS_LOG_WRITE = 176;
|
||||
|
||||
/* Process terminal attachment */
|
||||
static constexpr uint64_t SYS_TERMINAL_ATTACHED = 177;
|
||||
|
||||
// Audio control commands (for SYS_AUDIOCTL).
|
||||
//
|
||||
// Commands 0..3 act on the stream named by the handle argument.
|
||||
@@ -194,6 +200,8 @@ namespace montauk::abi {
|
||||
/* Process.hpp */
|
||||
static constexpr uint64_t SYS_SETUSER = 92;
|
||||
static constexpr uint64_t SYS_GETUSER = 93;
|
||||
static constexpr uint64_t SYS_SETSESSION = 174;
|
||||
static constexpr uint64_t SYS_KILLSESSION = 175;
|
||||
|
||||
/* Filesystem.hpp */
|
||||
static constexpr uint64_t SYS_FRENAME = 94;
|
||||
@@ -267,16 +275,16 @@ namespace montauk::abi {
|
||||
static constexpr uint64_t SYS_BTBONDS = 138;
|
||||
static constexpr uint64_t SYS_BTFORGET = 139;
|
||||
|
||||
/* Sdr.hpp -- software-defined radio receive API */
|
||||
static constexpr uint64_t SYS_SDR_COUNT = 140; // number of receivers
|
||||
static constexpr uint64_t SYS_SDR_INFO = 141; // (index, SdrDeviceInfo*)
|
||||
static constexpr uint64_t SYS_SDR_OPEN = 142; // (index) -> handle
|
||||
static constexpr uint64_t SYS_SDR_CLOSE = 143; // (handle)
|
||||
static constexpr uint64_t SYS_SDR_START = 144; // (handle) begin streaming
|
||||
static constexpr uint64_t SYS_SDR_STOP = 145; // (handle) stop streaming
|
||||
static constexpr uint64_t SYS_SDR_READ = 146; // (handle, buf, len) -> bytes
|
||||
static constexpr uint64_t SYS_SDR_SETPARAM = 147; // (handle, param, value)
|
||||
static constexpr uint64_t SYS_SDR_GETPARAM = 148; // (handle, param) -> value
|
||||
/* Reserved: former SDR API. Kept unavailable to preserve ABI numbering. */
|
||||
static constexpr uint64_t SYS_RESERVED_140 = 140;
|
||||
static constexpr uint64_t SYS_RESERVED_141 = 141;
|
||||
static constexpr uint64_t SYS_RESERVED_142 = 142;
|
||||
static constexpr uint64_t SYS_RESERVED_143 = 143;
|
||||
static constexpr uint64_t SYS_RESERVED_144 = 144;
|
||||
static constexpr uint64_t SYS_RESERVED_145 = 145;
|
||||
static constexpr uint64_t SYS_RESERVED_146 = 146;
|
||||
static constexpr uint64_t SYS_RESERVED_147 = 147;
|
||||
static constexpr uint64_t SYS_RESERVED_148 = 148;
|
||||
|
||||
/* Power.hpp -- CPU power/thermal status */
|
||||
static constexpr uint64_t SYS_POWERINFO = 149; // (PowerInfo*) -> 0, -1 unsupported
|
||||
@@ -307,25 +315,115 @@ namespace montauk::abi {
|
||||
static constexpr uint64_t SYS_WIFI_CONNECT_ASYNC = 164; // (ssid, password) -> 0 accepted, <0 on error
|
||||
static constexpr uint64_t SYS_NETIFS = 165; // (NetIfInfo*, maxCount) -> count
|
||||
|
||||
// Tunable parameters (for SYS_SDR_SETPARAM / SYS_SDR_GETPARAM).
|
||||
static constexpr int SDR_PARAM_FREQ = 0; // center frequency, Hz
|
||||
static constexpr int SDR_PARAM_SAMPLE_RATE = 1; // sample rate, Hz
|
||||
static constexpr int SDR_PARAM_GAIN_MODE = 2; // 0 = auto/AGC, 1 = manual
|
||||
static constexpr int SDR_PARAM_GAIN = 3; // tuner gain, tenths of dB
|
||||
static constexpr int SDR_PARAM_FREQ_CORR = 4; // frequency correction, ppm
|
||||
static constexpr int SDR_PARAM_AGC = 5; // demod digital AGC, 0/1
|
||||
static constexpr int SDR_PARAM_DIRECT_SAMP = 6; // direct sampling: 0=off,1=I,2=Q
|
||||
/* Keyboard.hpp */
|
||||
static constexpr uint64_t SYS_GETCHAR_NB = 166; // () -> ascii, 0 if nothing pending; never blocks
|
||||
|
||||
// Sample formats reported in SdrDeviceInfo.sampleFormat.
|
||||
static constexpr uint8_t SDR_FORMAT_CU8 = 0; // 8-bit unsigned interleaved I/Q
|
||||
// Set path access/modification times. (path, atime, mtime, useCurrent)
|
||||
static constexpr uint64_t SYS_UTIME = 167;
|
||||
static constexpr uint64_t SYS_MMAP_ANON = 168;
|
||||
static constexpr uint64_t SYS_MUNMAP = 169;
|
||||
static constexpr uint64_t SYS_MPROTECT = 170;
|
||||
static constexpr uint64_t SYS_GETENVIRON = 171;
|
||||
static constexpr uint64_t SYS_SETENVIRON = 172;
|
||||
static constexpr uint64_t SYS_SPAWN_ENV = 173;
|
||||
|
||||
/* Generic userspace USB interface access */
|
||||
static constexpr uint64_t SYS_USB_LIST = 178; // (UsbInterfaceInfo*, max) -> count
|
||||
static constexpr uint64_t SYS_USB_CLAIM = 179; // (slot, interface) -> owned handle
|
||||
static constexpr uint64_t SYS_USB_CLOSE = 180; // (handle)
|
||||
static constexpr uint64_t SYS_USB_CONTROL = 181; // (handle, UsbControlRequest*, data, len)
|
||||
static constexpr uint64_t SYS_USB_BULK_IN_START = 182; // (handle, transferBytes, buffers)
|
||||
static constexpr uint64_t SYS_USB_BULK_IN_STOP = 183; // (handle)
|
||||
static constexpr uint64_t SYS_USB_BULK_IN_READ = 184; // (handle, data, len) -> bytes
|
||||
static constexpr uint64_t SYS_SPAWN_CAPS = 185;
|
||||
static constexpr uint64_t SYS_SPAWN_REDIR_CAPS = 186;
|
||||
|
||||
/* Heap.hpp -- as SYS_ALLOC, but commits every page up front instead of
|
||||
faulting them in one at a time. For buffers the caller is about to
|
||||
touch in full (image decode, heap slabs). */
|
||||
static constexpr uint64_t SYS_ALLOC_EAGER = 187; // (bytes) -> va, 0 on failure
|
||||
|
||||
/* Kernel-owned process capabilities. User identities may namespace
|
||||
per-user resources, but never participate in authorization decisions. */
|
||||
static constexpr uint64_t CAP_PROCESS_ADMIN = 1ULL << 0;
|
||||
static constexpr uint64_t CAP_POWER_REQUEST = 1ULL << 1;
|
||||
static constexpr uint64_t CAP_POWER_CONTROL = 1ULL << 2;
|
||||
static constexpr uint64_t CAP_SUSPEND = 1ULL << 3;
|
||||
static constexpr uint64_t CAP_STORAGE_ADMIN = 1ULL << 4;
|
||||
static constexpr uint64_t CAP_RAW_STORAGE = 1ULL << 5;
|
||||
static constexpr uint64_t CAP_NETWORK_ADMIN = 1ULL << 6;
|
||||
static constexpr uint64_t CAP_SET_TIME = 1ULL << 7;
|
||||
static constexpr uint64_t CAP_USER_ADMIN = 1ULL << 8;
|
||||
static constexpr uint64_t CAP_DISPLAY_ADMIN = 1ULL << 9;
|
||||
static constexpr uint64_t CAP_DEVICE_ADMIN = 1ULL << 10;
|
||||
static constexpr uint64_t CAP_LOG_READ = 1ULL << 11;
|
||||
/* Write to the program images the system boots and runs (0:/os,
|
||||
0:/apps). Deliberately separate from CAP_STORAGE_ADMIN: grants are
|
||||
keyed on binary path, so writing an image is equivalent to acquiring
|
||||
whatever that image is granted at its next launch. Formatting a data
|
||||
volume must not carry that authority with it. */
|
||||
static constexpr uint64_t CAP_SYSTEM_IMAGE = 1ULL << 12;
|
||||
static constexpr uint64_t CAP_ALL = (1ULL << 13) - 1;
|
||||
static constexpr uint64_t CAP_STANDARD_SESSION = CAP_POWER_REQUEST | CAP_SUSPEND;
|
||||
static constexpr uint64_t CAP_ADMIN_SESSION =
|
||||
CAP_STANDARD_SESSION | CAP_PROCESS_ADMIN | CAP_STORAGE_ADMIN |
|
||||
CAP_RAW_STORAGE | CAP_NETWORK_ADMIN | CAP_SET_TIME | CAP_USER_ADMIN |
|
||||
CAP_DISPLAY_ADMIN | CAP_DEVICE_ADMIN | CAP_LOG_READ;
|
||||
static_assert((CAP_STANDARD_SESSION & ~CAP_ADMIN_SESSION) == 0);
|
||||
static_assert((CAP_ADMIN_SESSION & CAP_POWER_CONTROL) == 0,
|
||||
"final power control belongs only to the session supervisor");
|
||||
static_assert((CAP_ADMIN_SESSION & CAP_SYSTEM_IMAGE) == 0,
|
||||
"an admin session must not imply authority to rewrite the "
|
||||
"programs it launches; grant CAP_SYSTEM_IMAGE per binary");
|
||||
static constexpr int SYS_ERR_PERMISSION = -13;
|
||||
|
||||
struct SpawnCapabilities {
|
||||
uint64_t permitted;
|
||||
uint64_t effective;
|
||||
uint64_t delegable;
|
||||
};
|
||||
|
||||
constexpr bool ValidCapabilityDelegation(const SpawnCapabilities& child,
|
||||
uint64_t parentDelegable) {
|
||||
return (child.permitted & ~CAP_ALL) == 0 &&
|
||||
(child.effective & ~child.permitted) == 0 &&
|
||||
(child.delegable & ~child.permitted) == 0 &&
|
||||
(child.permitted & ~parentDelegable) == 0 &&
|
||||
(child.delegable & ~parentDelegable) == 0;
|
||||
}
|
||||
static_assert(ValidCapabilityDelegation(
|
||||
{CAP_NETWORK_ADMIN, CAP_NETWORK_ADMIN, 0}, CAP_NETWORK_ADMIN));
|
||||
static_assert(!ValidCapabilityDelegation(
|
||||
{CAP_NETWORK_ADMIN, CAP_NETWORK_ADMIN, CAP_NETWORK_ADMIN}, 0));
|
||||
static_assert(!ValidCapabilityDelegation(
|
||||
{CAP_NETWORK_ADMIN, CAP_NETWORK_ADMIN | CAP_SET_TIME, 0}, CAP_ALL));
|
||||
|
||||
|
||||
// Generic USB errors. Claims are restricted to interfaces without a
|
||||
// bound in-kernel class driver and are owned by the claiming process.
|
||||
static constexpr int USB_ERR_INVALID = -1;
|
||||
static constexpr int USB_ERR_BUSY = -2;
|
||||
static constexpr int USB_ERR_DISCONNECTED = -3;
|
||||
static constexpr int USB_ERR_UNSUPPORTED = -4;
|
||||
static constexpr int USB_ERR_IO = -5;
|
||||
static constexpr int USB_ERR_NO_RESOURCES = -6;
|
||||
static constexpr int USB_ERR_NOT_FOUND = -7;
|
||||
static constexpr int USB_ERR_KERNEL_BOUND = -8;
|
||||
|
||||
// Graceful power-off request actions (SYS_POWER_REQUEST). The desktop posts
|
||||
// a pending action and exits; login.elf reads it, runs the shutdown stages,
|
||||
// then issues the matching SYS_SHUTDOWN / SYS_RESET.
|
||||
//
|
||||
// A request can also be posted from inside the session -- the shell's
|
||||
// shutdown builtin does. login only looks at it once the session leader
|
||||
// exits, so the leader has to notice and stand down: POWER_REQ_PEEK is the
|
||||
// non-destructive read it polls with. Only login consumes (QUERY), so a
|
||||
// leader that peeks cannot swallow the request it is meant to act on.
|
||||
enum PowerRequestAction : int {
|
||||
POWER_REQ_QUERY = 0, // read-and-clear the pending action
|
||||
POWER_REQ_SHUTDOWN = 1,
|
||||
POWER_REQ_REBOOT = 2,
|
||||
POWER_REQ_PEEK = 3, // read the pending action without clearing it
|
||||
};
|
||||
|
||||
static constexpr uint32_t CLIPBOARD_MAX_TEXT_BYTES = 256 * 1024;
|
||||
@@ -446,6 +544,13 @@ namespace montauk::abi {
|
||||
bool shift;
|
||||
bool ctrl;
|
||||
bool alt;
|
||||
bool capslock;
|
||||
// Right Alt held, reported separately from alt so keyboard layouts can
|
||||
// use it as AltGr without swallowing left-Alt shortcuts.
|
||||
bool altgr;
|
||||
// Key came from an E0-prefixed scancode (keypad "/" and Enter share a
|
||||
// scancode with the main-block keys). Layout translation must skip it.
|
||||
bool extended;
|
||||
};
|
||||
|
||||
struct MouseState {
|
||||
@@ -593,8 +698,11 @@ namespace montauk::abi {
|
||||
uint8_t state; // 0=Free, 1=Ready, 2=Running, 3=Blocked, 4=Terminated
|
||||
uint8_t _pad[3];
|
||||
char name[64];
|
||||
uint64_t heapUsed; // heapNext - UserHeapBase (bytes)
|
||||
uint64_t heapUsed; // Distance from UserHeapBase to high-water mark
|
||||
uint64_t cpuTimeMs; // accumulated scheduler runtime
|
||||
uint64_t permittedCaps;
|
||||
uint64_t effectiveCaps;
|
||||
uint64_t delegableCaps;
|
||||
};
|
||||
|
||||
// Bluetooth scan result (returned by SYS_BTSCAN)
|
||||
@@ -631,23 +739,40 @@ namespace montauk::abi {
|
||||
uint8_t _pad[2];
|
||||
};
|
||||
|
||||
// Software-defined radio receiver description (returned by SYS_SDR_INFO).
|
||||
struct SdrDeviceInfo {
|
||||
char name[64]; // e.g. "Realtek RTL2832U"
|
||||
char tuner[32]; // e.g. "Rafael Micro R820T2"
|
||||
char serial[32]; // device serial / bus location
|
||||
uint64_t freqMin; // minimum tunable center frequency, Hz
|
||||
uint64_t freqMax; // maximum tunable center frequency, Hz
|
||||
uint32_t sampleRateMin; // minimum sample rate, Hz
|
||||
uint32_t sampleRateMax; // maximum sample rate, Hz
|
||||
uint32_t numGains; // number of discrete tuner gain steps
|
||||
int32_t gains[32]; // available gains, tenths of dB
|
||||
uint8_t sampleFormat; // SDR_FORMAT_*
|
||||
uint8_t present; // 1 if the underlying hardware is connected
|
||||
uint8_t streaming; // 1 if currently delivering samples
|
||||
uint8_t _pad;
|
||||
uint32_t _pad2;
|
||||
};
|
||||
// One USB interface currently represented by the xHCI device table. A
|
||||
// nonzero kernelDriverBound interface cannot be claimed by userspace.
|
||||
struct UsbInterfaceInfo {
|
||||
uint8_t slotId;
|
||||
uint8_t portId;
|
||||
uint8_t speed; // xHCI speed ID
|
||||
uint8_t interfaceNumber;
|
||||
uint16_t vendorId;
|
||||
uint16_t productId;
|
||||
uint8_t deviceClass;
|
||||
uint8_t interfaceClass;
|
||||
uint8_t interfaceSubClass;
|
||||
uint8_t interfaceProtocol;
|
||||
uint8_t bulkInEndpoint; // USB address, including direction bit
|
||||
uint8_t bulkOutEndpoint;
|
||||
uint16_t bulkInMaxPacket;
|
||||
uint16_t bulkOutMaxPacket;
|
||||
uint8_t kernelDriverBound;
|
||||
uint8_t claimed;
|
||||
uint8_t _reserved[4];
|
||||
} __attribute__((packed));
|
||||
|
||||
// Standard USB setup packet fields. requestType bit 7 determines the data
|
||||
// direction. length must match the data length passed to SYS_USB_CONTROL.
|
||||
struct UsbControlRequest {
|
||||
uint8_t requestType;
|
||||
uint8_t request;
|
||||
uint16_t value;
|
||||
uint16_t index;
|
||||
uint16_t length;
|
||||
} __attribute__((packed));
|
||||
static_assert(sizeof(UsbInterfaceInfo) == 24);
|
||||
static_assert(sizeof(UsbControlRequest) == 8);
|
||||
|
||||
|
||||
// Wi-Fi security suites reported in WifiNetwork.security.
|
||||
static constexpr uint8_t WIFI_SEC_OPEN = 0;
|
||||
@@ -768,6 +893,8 @@ namespace montauk::abi {
|
||||
uint64_t codeSegment;
|
||||
uint64_t flags;
|
||||
uint64_t stackSegment;
|
||||
uint64_t stackWords[8];
|
||||
uint8_t stackWordCount;
|
||||
uint8_t pfPresent : 1;
|
||||
uint8_t pfWrite : 1;
|
||||
uint8_t pfUser : 1;
|
||||
|
||||
@@ -12,6 +12,11 @@
|
||||
|
||||
namespace montauk::abi {
|
||||
|
||||
static int Sys_TerminalAttached() {
|
||||
auto* proc = Sched::GetCurrentProcessPtr();
|
||||
return proc != nullptr && proc->redirected && proc->ioOutHandle >= 0;
|
||||
}
|
||||
|
||||
static void Sys_Print(const char* text) {
|
||||
auto* proc = Sched::GetCurrentProcessPtr();
|
||||
if (proc && proc->redirected) {
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Usb.hpp
|
||||
* Generic userspace USB interface syscall layer.
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <Drivers/USB/UserUsb.hpp>
|
||||
|
||||
namespace montauk::abi {
|
||||
|
||||
static int64_t Sys_UsbList(UsbInterfaceInfo* out, int maxCount) {
|
||||
return Drivers::USB::UserUsb::List(out, maxCount);
|
||||
}
|
||||
|
||||
static int64_t Sys_UsbClaim(uint8_t slotId, uint8_t interfaceNumber) {
|
||||
return Drivers::USB::UserUsb::Claim(slotId, interfaceNumber);
|
||||
}
|
||||
|
||||
static int64_t Sys_UsbClose(int handle) {
|
||||
return Drivers::USB::UserUsb::Close(handle);
|
||||
}
|
||||
|
||||
static int64_t Sys_UsbControl(int handle, const UsbControlRequest* request,
|
||||
void* data, uint32_t dataLen) {
|
||||
if (!request) return USB_ERR_INVALID;
|
||||
return Drivers::USB::UserUsb::Control(handle, *request, data, dataLen);
|
||||
}
|
||||
|
||||
static int64_t Sys_UsbBulkInStart(int handle, uint32_t transferBytes,
|
||||
uint32_t bufferCount) {
|
||||
return Drivers::USB::UserUsb::StartBulkIn(handle, transferBytes, bufferCount);
|
||||
}
|
||||
|
||||
static int64_t Sys_UsbBulkInStop(int handle) {
|
||||
return Drivers::USB::UserUsb::StopBulkIn(handle);
|
||||
}
|
||||
|
||||
static int64_t Sys_UsbBulkInRead(int handle, uint8_t* out, uint32_t maxLen) {
|
||||
return Drivers::USB::UserUsb::ReadBulkIn(handle, out, maxLen);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
#include <Sched/Scheduler.hpp>
|
||||
#include <Memory/Paging.hpp>
|
||||
#include <Api/Heap.hpp>
|
||||
|
||||
namespace montauk::abi::UserMemory {
|
||||
|
||||
@@ -32,6 +33,21 @@ namespace montauk::abi::UserMemory {
|
||||
uint64_t pml4Phys = CurrentPml4();
|
||||
if (pml4Phys == 0) return false;
|
||||
|
||||
if (Memory::VMM::Paging::IsUserRangeAccessible(pml4Phys, addr, size, requireWrite))
|
||||
return true;
|
||||
|
||||
// Anonymous VMAs are demand-committed. Validation precedes the actual
|
||||
// kernel copy, so materialize missing pages here with the same access
|
||||
// type that the copy will perform.
|
||||
uint64_t page = addr & ~0xFFFULL;
|
||||
uint64_t last = (addr + size - 1) & ~0xFFFULL;
|
||||
for (;;) {
|
||||
if (!Memory::VMM::Paging::IsUserRangeAccessible(pml4Phys, page, 1, requireWrite) &&
|
||||
!TryHandleAnonymousPageFault(page, requireWrite ? 2ULL : 0ULL))
|
||||
return false;
|
||||
if (page == last) break;
|
||||
page += 0x1000ULL;
|
||||
}
|
||||
return Memory::VMM::Paging::IsUserRangeAccessible(pml4Phys, addr, size, requireWrite);
|
||||
}
|
||||
|
||||
@@ -58,7 +74,7 @@ namespace montauk::abi::UserMemory {
|
||||
const char* str = (const char*)addr;
|
||||
for (uint64_t i = 0; i < maxLen; i++) {
|
||||
if (!Memory::VMM::Paging::IsUserRangeAccessible(pml4Phys, addr + i, 1, false)) {
|
||||
return false;
|
||||
if (!TryHandleAnonymousPageFault(addr + i, 0)) return false;
|
||||
}
|
||||
if (str[i] == '\0') return true;
|
||||
}
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
/*
|
||||
* main.cpp
|
||||
* Kernel entry point
|
||||
* Copyright (c) 2025 Daniel Hammer, Limine Contributors (via Limine C++ example)
|
||||
* Copyright (c) 2025 Daniel Hammer.
|
||||
* Further copyright information and third party notices can be found at https://montaukos.org/license.txt.
|
||||
*/
|
||||
|
||||
#include <Fs/ProtectedPaths.hpp>
|
||||
#include <Memory/UserRange.hpp>
|
||||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
#include <Boot/Boot.hpp>
|
||||
@@ -29,7 +32,6 @@
|
||||
#include <Drivers/PS2/Keyboard.hpp>
|
||||
#include <Drivers/PS2/Mouse.hpp>
|
||||
#include <Drivers/Init.hpp>
|
||||
#include <Drivers/USB/Bluetooth/Bluetooth.hpp>
|
||||
#include <Graphics/Framebuffer.hpp>
|
||||
#include <Hal/MSR.hpp>
|
||||
#include <Hal/Cpu.hpp>
|
||||
@@ -62,12 +64,7 @@ extern "C" void kmain() {
|
||||
for (std::size_t i = 0; &__init_array[i] != __init_array_end; i++) {
|
||||
__init_array[i]();
|
||||
}
|
||||
|
||||
// Acquire the boot environment through the Montauk Boot Contract. The
|
||||
// active bootloader adapter (see Boot/Protocols/) translates its native
|
||||
// handoff into this bootloader-agnostic structure. A false return means
|
||||
// we cannot even bring up a console (unsupported loader, no HHDM, or no
|
||||
// framebuffer) -- there is nothing to do but halt.
|
||||
|
||||
if (!montauk::boot::Initialize()) {
|
||||
Hal::Halt();
|
||||
}
|
||||
@@ -119,20 +116,13 @@ extern "C" void kmain() {
|
||||
Memory::VMM::g_paging = &g_paging;
|
||||
g_paging.Init((uint64_t)&KernelStartSymbol, ((uint64_t)&KernelEndSymbol - (uint64_t)&KernelStartSymbol), boot.memoryMap, framebuffer);
|
||||
|
||||
// Reprogram PAT so entry 1 = Write-Combining (default is Write-Through).
|
||||
// Must be done after paging init and before any WC mappings.
|
||||
Hal::InitializePAT();
|
||||
Kt::KernelLogStream(OK, "Hal") << "PAT reprogrammed (entry 1 = WC)";
|
||||
|
||||
#endif
|
||||
|
||||
// Initialize the framebuffer early so we can WC-map it before
|
||||
// the bulk of boot logging begins (ACPI, PCI, drivers, etc.)
|
||||
Graphics::Framebuffer::Initialize(framebuffer);
|
||||
|
||||
#if defined (__x86_64__)
|
||||
// Map framebuffer as Write-Combining immediately for faster screen writes.
|
||||
// All subsequent log output benefits from WC burst transfers.
|
||||
Graphics::Framebuffer::MapWriteCombining();
|
||||
#endif
|
||||
|
||||
@@ -145,19 +135,12 @@ extern "C" void kmain() {
|
||||
|
||||
Hal::ApicInitialize(g_acpi.GetXSDT());
|
||||
|
||||
// Set up BSP per-CPU data (GS base) before enabling interrupts.
|
||||
// ISR stubs use SWAPGS which requires GS base to point to CpuData.
|
||||
Smp::InitBsp();
|
||||
|
||||
// Enable hardware P-state scaling and the thermal governor.
|
||||
// Needs GS base (per-CPU data) set up, and must run before the
|
||||
// APs boot so they inherit the shared policy in ApEntry.
|
||||
Hal::CpuPower::InitializeBsp();
|
||||
|
||||
// Now safe to enable interrupts (SWAPGS-aware ISR stubs are installed)
|
||||
asm volatile("sti");
|
||||
|
||||
// Initialize ACPI events (SCI, power button) after APIC is ready
|
||||
Hal::AcpiEvents::Initialize(g_acpi.GetXSDT());
|
||||
|
||||
Pci::Initialize(g_acpi.GetXSDT());
|
||||
@@ -186,29 +169,34 @@ extern "C" void kmain() {
|
||||
|
||||
Fs::InitializeBootFilesystems(boot.modules);
|
||||
|
||||
|
||||
#if defined (__x86_64__)
|
||||
Hal::LoadTSS();
|
||||
#endif
|
||||
|
||||
montauk::abi::InitializeSyscalls();
|
||||
|
||||
Sched::Initialize();
|
||||
Memory::InitUserRange();
|
||||
Fs::LogProtectedPaths();
|
||||
Ipc::Initialize();
|
||||
|
||||
// Boot Application Processors (all subsystems ready, APs can schedule)
|
||||
#if defined (__x86_64__)
|
||||
Smp::BootAPs(boot.smp);
|
||||
#endif
|
||||
|
||||
// Flush any stale PS/2 mouse bytes that accumulated during boot
|
||||
// (edge-triggered IRQs can be lost while spinlocks disable interrupts)
|
||||
#if defined (__x86_64__)
|
||||
Drivers::PS2::Mouse::FlushState();
|
||||
#endif
|
||||
|
||||
Kt::SuppressKernelLog();
|
||||
Sched::Spawn("0:/os/init.elf");
|
||||
|
||||
// Enable preemptive scheduling via the APIC timer
|
||||
Timekeeping::EnableSchedulerTick();
|
||||
|
||||
// Main loop: idle until next interrupt.
|
||||
#if defined (__x86_64__)
|
||||
// Use MWAIT for deeper C-states if available, otherwise HLT.
|
||||
auto* bspCpu = Smp::GetCpuData(0);
|
||||
|
||||
if (bspCpu && bspCpu->hasMwait) {
|
||||
static volatile uint64_t s_bspIdleMonitor = 0;
|
||||
for (;;) {
|
||||
@@ -219,4 +207,9 @@ extern "C" void kmain() {
|
||||
Timekeeping::IdleOnce(false);
|
||||
}
|
||||
}
|
||||
#else
|
||||
for (;;) {
|
||||
Timekeeping::IdleOnce(false);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -5,7 +5,9 @@
|
||||
* Register offsets, firmware command IDs and wire-format structures follow
|
||||
* Intel's dual BSD/GPLv2-licensed iwlwifi API headers, as consolidated in
|
||||
* OpenBSD's if_iwxreg.h (ISC/BSD). Only the subset used by this driver is
|
||||
* kept. See 0:/os/licenses/NOTICES.txt for the full attribution.
|
||||
* kept. MontaukOS relies on the BSD license option; see
|
||||
* montaukos.org/THIRD-PARTY-NOTICES.txt and 0:/os/licenses/NOTICES.txt for
|
||||
* the full attribution and license terms.
|
||||
*
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
* Portions Copyright (c) 2017 Intel Deutschland GmbH
|
||||
|
||||
@@ -440,6 +440,29 @@ namespace Drivers::Net::Wifi {
|
||||
static void ServiceAsync();
|
||||
static void ServiceRecovery();
|
||||
|
||||
bool HasDeferredWork() {
|
||||
if (g_initPending.load(std::memory_order_acquire) &&
|
||||
!g_initialized && Fs::Vfs::IsDriveRegistered(0)) {
|
||||
return true;
|
||||
}
|
||||
if (!g_iwx.Mmio) return false;
|
||||
if (g_iwx.WorkPending) return true;
|
||||
|
||||
uint64_t now = Timekeeping::GetMilliseconds();
|
||||
if (g_scanDeadline != 0 && now >= g_scanDeadline) return true;
|
||||
// The MLME/WPA state machine owns sub-second retransmission timers in
|
||||
// addition to the overall async deadline. Service it until ServiceAsync
|
||||
// observes Connected/Failed/Idle and clears this flag.
|
||||
if (g_asyncConnect) return true;
|
||||
if (g_iwx.State == IwxFwState::Error && g_initialized &&
|
||||
!g_recoveryGaveUp &&
|
||||
(g_lastRecoveryMs == 0 ||
|
||||
now - g_lastRecoveryMs >= RECOVERY_BACKOFF_MS)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void ServiceEvents() {
|
||||
if (!g_iwx.Mmio) return;
|
||||
if (g_iwx.WorkPending) IwxProcessEvents();
|
||||
|
||||
@@ -21,6 +21,10 @@ namespace Drivers::Net::Wifi {
|
||||
// Steady-state event pump (RX ring, notifications). Idle-loop callback.
|
||||
void ServiceEvents();
|
||||
|
||||
// True when firmware initialization, an RX notification, an expired async
|
||||
// deadline, or a due recovery attempt needs idle-context servicing.
|
||||
bool HasDeferredWork();
|
||||
|
||||
bool IsInitialized();
|
||||
bool IsPresent();
|
||||
|
||||
|
||||
@@ -179,7 +179,9 @@ namespace Drivers::PS2::Keyboard {
|
||||
.Shift = g_Modifiers.LeftShift || g_Modifiers.RightShift,
|
||||
.Ctrl = g_Modifiers.LeftCtrl || g_Modifiers.RightCtrl,
|
||||
.Alt = g_Modifiers.LeftAlt || g_Modifiers.RightAlt,
|
||||
.CapsLock = g_Modifiers.CapsLock
|
||||
.CapsLock = g_Modifiers.CapsLock,
|
||||
.AltGr = g_Modifiers.RightAlt,
|
||||
.Extended = true
|
||||
};
|
||||
PushAndNotify(event);
|
||||
|
||||
@@ -237,7 +239,9 @@ namespace Drivers::PS2::Keyboard {
|
||||
.Shift = g_Modifiers.LeftShift || g_Modifiers.RightShift,
|
||||
.Ctrl = g_Modifiers.LeftCtrl || g_Modifiers.RightCtrl,
|
||||
.Alt = g_Modifiers.LeftAlt || g_Modifiers.RightAlt,
|
||||
.CapsLock = g_Modifiers.CapsLock
|
||||
.CapsLock = g_Modifiers.CapsLock,
|
||||
.AltGr = g_Modifiers.RightAlt,
|
||||
.Extended = false
|
||||
};
|
||||
PushAndNotify(event);
|
||||
return;
|
||||
@@ -261,7 +265,9 @@ namespace Drivers::PS2::Keyboard {
|
||||
.Shift = g_Modifiers.LeftShift || g_Modifiers.RightShift,
|
||||
.Ctrl = g_Modifiers.LeftCtrl || g_Modifiers.RightCtrl,
|
||||
.Alt = g_Modifiers.LeftAlt || g_Modifiers.RightAlt,
|
||||
.CapsLock = g_Modifiers.CapsLock
|
||||
.CapsLock = g_Modifiers.CapsLock,
|
||||
.AltGr = g_Modifiers.RightAlt,
|
||||
.Extended = false
|
||||
};
|
||||
|
||||
PushAndNotify(event);
|
||||
@@ -298,7 +304,18 @@ namespace Drivers::PS2::Keyboard {
|
||||
}
|
||||
|
||||
void InjectKeyEvent(const KeyEvent& event) {
|
||||
PushAndNotify(event);
|
||||
// Caps Lock is owned here rather than by each injecting driver: USB HID
|
||||
// reports the key as an ordinary usage and carries no lock state, so
|
||||
// without this the lock would never toggle on a USB keyboard.
|
||||
KeyEvent stamped = event;
|
||||
if (stamped.Scancode == ScCapsLock && stamped.Pressed)
|
||||
g_Modifiers.CapsLock = !g_Modifiers.CapsLock;
|
||||
stamped.CapsLock = g_Modifiers.CapsLock;
|
||||
PushAndNotify(stamped);
|
||||
}
|
||||
|
||||
bool GetCapsLock() {
|
||||
return g_Modifiers.CapsLock;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
@@ -18,6 +18,13 @@ namespace Drivers::PS2::Keyboard {
|
||||
bool Ctrl;
|
||||
bool Alt;
|
||||
bool CapsLock;
|
||||
// Right Alt held. Reported separately from Alt so keyboard layouts can
|
||||
// use it as AltGr without swallowing left-Alt shortcuts.
|
||||
bool AltGr;
|
||||
// Key came from an E0-prefixed (extended) scancode. The prefix is not
|
||||
// part of Scancode, so without this flag keypad "/" is indistinguishable
|
||||
// from the main "/" key and layout translation would rewrite it.
|
||||
bool Extended;
|
||||
};
|
||||
|
||||
// Modifier key state
|
||||
@@ -52,4 +59,8 @@ namespace Drivers::PS2::Keyboard {
|
||||
// Inject a key event from an external source (e.g., USB HID keyboard)
|
||||
void InjectKeyEvent(const KeyEvent& event);
|
||||
|
||||
// Current Caps Lock state. Injecting drivers (USB HID) need this to
|
||||
// compute ASCII, since the lock is toggled centrally here.
|
||||
bool GetCapsLock();
|
||||
|
||||
};
|
||||
|
||||
@@ -1,359 +0,0 @@
|
||||
/*
|
||||
* Sdr.cpp
|
||||
* Generic software-defined radio receive subsystem.
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#include "Sdr.hpp"
|
||||
#include <Memory/Heap.hpp>
|
||||
#include <Libraries/Memory.hpp>
|
||||
#include <Terminal/Terminal.hpp>
|
||||
#include <CppLib/Stream.hpp>
|
||||
|
||||
using namespace Kt;
|
||||
|
||||
namespace Drivers::Radio::Sdr {
|
||||
|
||||
// I/Q ring size per receiver. 256 KiB is ~62 ms of jitter buffer at
|
||||
// 2.048 Msps (2 bytes/sample), which comfortably absorbs scheduling gaps
|
||||
// between a userspace reader's polls.
|
||||
static constexpr uint32_t RING_BYTES = 256 * 1024;
|
||||
|
||||
struct Receiver {
|
||||
bool used;
|
||||
bool opened;
|
||||
bool streaming;
|
||||
|
||||
char name[64];
|
||||
char tuner[32];
|
||||
char serial[32];
|
||||
uint64_t freqMin, freqMax;
|
||||
uint32_t sampleRateMin, sampleRateMax;
|
||||
int gains[MAX_GAINS];
|
||||
uint32_t numGains;
|
||||
uint8_t format;
|
||||
|
||||
ReceiverOps ops;
|
||||
void* ctx;
|
||||
|
||||
// Last-requested configuration (cached for GETPARAM readback).
|
||||
uint64_t freq;
|
||||
uint32_t sampleRate;
|
||||
int gainMode; // 0 = auto, 1 = manual
|
||||
int gain; // tenths of dB
|
||||
int ppm;
|
||||
int agc;
|
||||
int directSamp;
|
||||
|
||||
// I/Q ring buffer (byte FIFO).
|
||||
uint8_t* ring;
|
||||
uint32_t head; // write position
|
||||
uint32_t count; // bytes currently queued
|
||||
uint64_t totalBytes; // lifetime sample bytes delivered
|
||||
uint64_t droppedBytes; // bytes dropped on overflow
|
||||
kcp::Spinlock lock;
|
||||
};
|
||||
|
||||
static Receiver g_rx[MAX_RECEIVERS];
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Helpers
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
static void CopyStr(char* dst, uint32_t cap, const char* src) {
|
||||
uint32_t i = 0;
|
||||
if (src) {
|
||||
for (; i < cap - 1 && src[i]; i++) dst[i] = src[i];
|
||||
}
|
||||
dst[i] = '\0';
|
||||
}
|
||||
|
||||
static Receiver* Lookup(int handle, bool needOpen) {
|
||||
if (handle < 0 || handle >= MAX_RECEIVERS) return nullptr;
|
||||
Receiver& r = g_rx[handle];
|
||||
if (!r.used) return nullptr;
|
||||
if (needOpen && !r.opened) return nullptr;
|
||||
return &r;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Driver-facing API
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
int Register(const ReceiverDesc& desc) {
|
||||
for (int i = 0; i < MAX_RECEIVERS; i++) {
|
||||
if (g_rx[i].used) continue;
|
||||
Receiver& r = g_rx[i];
|
||||
|
||||
// Reset everything except the (non-copyable) spinlock instance.
|
||||
r.opened = false;
|
||||
r.streaming = false;
|
||||
CopyStr(r.name, sizeof(r.name), desc.name);
|
||||
CopyStr(r.tuner, sizeof(r.tuner), desc.tuner);
|
||||
CopyStr(r.serial, sizeof(r.serial), desc.serial);
|
||||
r.freqMin = desc.freqMin;
|
||||
r.freqMax = desc.freqMax;
|
||||
r.sampleRateMin = desc.sampleRateMin;
|
||||
r.sampleRateMax = desc.sampleRateMax;
|
||||
r.numGains = desc.numGains > MAX_GAINS ? MAX_GAINS : desc.numGains;
|
||||
for (uint32_t g = 0; g < r.numGains; g++) r.gains[g] = desc.gains[g];
|
||||
r.format = desc.format;
|
||||
r.ops = desc.ops;
|
||||
r.ctx = desc.ctx;
|
||||
|
||||
r.freq = (desc.freqMin + desc.freqMax) / 2;
|
||||
r.sampleRate = desc.sampleRateMax;
|
||||
r.gainMode = 0;
|
||||
r.gain = 0;
|
||||
r.ppm = 0;
|
||||
r.agc = 0;
|
||||
r.directSamp = 0;
|
||||
|
||||
r.ring = nullptr;
|
||||
r.head = r.count = 0;
|
||||
r.totalBytes = r.droppedBytes = 0;
|
||||
|
||||
r.used = true; // publish last
|
||||
KernelLogStream(OK, "SDR") << "Registered receiver " << (uint64_t)i
|
||||
<< ": " << r.name << " / " << r.tuner;
|
||||
return i;
|
||||
}
|
||||
KernelLogStream(WARNING, "SDR") << "No free receiver slot for " << desc.name;
|
||||
return -1;
|
||||
}
|
||||
|
||||
void Unregister(int idx) {
|
||||
if (idx < 0 || idx >= MAX_RECEIVERS) return;
|
||||
Receiver& r = g_rx[idx];
|
||||
if (!r.used) return;
|
||||
|
||||
if (r.streaming && r.ops.Stop) r.ops.Stop(r.ctx);
|
||||
|
||||
r.lock.Acquire();
|
||||
r.streaming = false;
|
||||
r.opened = false;
|
||||
r.used = false;
|
||||
uint8_t* ring = r.ring;
|
||||
r.ring = nullptr;
|
||||
r.head = r.count = 0;
|
||||
r.lock.Release();
|
||||
|
||||
if (ring) Memory::g_heap->Free(ring);
|
||||
KernelLogStream(INFO, "SDR") << "Unregistered receiver " << (uint64_t)idx;
|
||||
}
|
||||
|
||||
void PushSamples(int idx, const uint8_t* data, uint32_t len) {
|
||||
if (idx < 0 || idx >= MAX_RECEIVERS || !data || len == 0) return;
|
||||
Receiver& r = g_rx[idx];
|
||||
|
||||
r.lock.Acquire();
|
||||
// Re-validate under the lock: Unregister() clears these and frees the
|
||||
// ring while holding the same lock, so an in-flight USB completion can
|
||||
// never write into a freed buffer.
|
||||
if (!r.used || !r.ring) { r.lock.Release(); return; }
|
||||
|
||||
uint32_t space = RING_BYTES - r.count;
|
||||
uint32_t n = len;
|
||||
uint32_t dropped = 0;
|
||||
if (n > space) {
|
||||
// Truncate to a whole number of I/Q byte pairs: dropping an odd
|
||||
// count would swap I and Q for the rest of the stream.
|
||||
n = space & ~1u;
|
||||
dropped = len - n;
|
||||
}
|
||||
|
||||
uint32_t first = RING_BYTES - r.head;
|
||||
if (first > n) first = n;
|
||||
memcpy(r.ring + r.head, data, first);
|
||||
if (n > first) memcpy(r.ring, data + first, n - first);
|
||||
|
||||
r.head = (r.head + n) % RING_BYTES;
|
||||
r.count += n;
|
||||
r.totalBytes += n;
|
||||
r.droppedBytes += dropped;
|
||||
r.lock.Release();
|
||||
}
|
||||
|
||||
bool IsStreaming(int idx) {
|
||||
if (idx < 0 || idx >= MAX_RECEIVERS) return false;
|
||||
return g_rx[idx].used && g_rx[idx].streaming;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Syscall-facing API
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
int Count() {
|
||||
int n = 0;
|
||||
for (int i = 0; i < MAX_RECEIVERS; i++) if (g_rx[i].used) n++;
|
||||
return n;
|
||||
}
|
||||
|
||||
bool GetInfo(int idx, montauk::abi::SdrDeviceInfo* out) {
|
||||
Receiver* r = Lookup(idx, false);
|
||||
if (!r || !out) return false;
|
||||
|
||||
memset(out, 0, sizeof(*out));
|
||||
CopyStr(out->name, sizeof(out->name), r->name);
|
||||
CopyStr(out->tuner, sizeof(out->tuner), r->tuner);
|
||||
CopyStr(out->serial, sizeof(out->serial), r->serial);
|
||||
out->freqMin = r->freqMin;
|
||||
out->freqMax = r->freqMax;
|
||||
out->sampleRateMin = r->sampleRateMin;
|
||||
out->sampleRateMax = r->sampleRateMax;
|
||||
out->numGains = r->numGains;
|
||||
for (uint32_t g = 0; g < r->numGains && g < 32; g++) out->gains[g] = r->gains[g];
|
||||
out->sampleFormat = r->format;
|
||||
out->present = 1;
|
||||
out->streaming = r->streaming ? 1 : 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
int Open(int idx) {
|
||||
Receiver* r = Lookup(idx, false);
|
||||
if (!r) return -1;
|
||||
|
||||
// Single-user OS: an Open always claims the device, reclaiming it from a
|
||||
// previous owner that exited without closing.
|
||||
if (r->streaming && r->ops.Stop) r->ops.Stop(r->ctx);
|
||||
|
||||
if (!r->ring) {
|
||||
r->ring = (uint8_t*)Memory::g_heap->Request(RING_BYTES);
|
||||
if (!r->ring) {
|
||||
KernelLogStream(ERROR, "SDR") << "Ring alloc failed for receiver "
|
||||
<< (uint64_t)idx;
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
r->lock.Acquire();
|
||||
r->head = r->count = 0;
|
||||
r->lock.Release();
|
||||
r->streaming = false;
|
||||
r->opened = true;
|
||||
return idx; // handle == index
|
||||
}
|
||||
|
||||
int Close(int handle) {
|
||||
Receiver* r = Lookup(handle, true);
|
||||
if (!r) return -1;
|
||||
if (r->streaming && r->ops.Stop) r->ops.Stop(r->ctx);
|
||||
r->streaming = false;
|
||||
r->opened = false;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int Start(int handle) {
|
||||
Receiver* r = Lookup(handle, true);
|
||||
if (!r) return -1;
|
||||
// Already streaming: a second Start must not re-arm the driver's
|
||||
// transfer pool (it would double-queue every buffer).
|
||||
if (r->streaming) return 0;
|
||||
|
||||
r->lock.Acquire();
|
||||
r->head = r->count = 0; // discard stale samples before (re)starting
|
||||
r->lock.Release();
|
||||
|
||||
int rc = r->ops.Start ? r->ops.Start(r->ctx) : -1;
|
||||
if (rc == 0) r->streaming = true;
|
||||
return rc;
|
||||
}
|
||||
|
||||
int Stop(int handle) {
|
||||
Receiver* r = Lookup(handle, true);
|
||||
if (!r) return -1;
|
||||
int rc = r->ops.Stop ? r->ops.Stop(r->ctx) : 0;
|
||||
r->streaming = false;
|
||||
return rc;
|
||||
}
|
||||
|
||||
int Read(int handle, uint8_t* buf, uint32_t len) {
|
||||
Receiver* r = Lookup(handle, true);
|
||||
if (!r || !buf || !r->ring) return -1;
|
||||
if (len == 0) return 0;
|
||||
|
||||
// Give the driver a process-context tick (e.g. USB stall recovery)
|
||||
// before draining; do this outside the ring lock since it may issue
|
||||
// blocking USB commands.
|
||||
if (r->streaming && r->ops.Service) r->ops.Service(r->ctx);
|
||||
|
||||
r->lock.Acquire();
|
||||
uint32_t n = r->count < len ? r->count : len;
|
||||
uint32_t tail = (r->head + RING_BYTES - r->count) % RING_BYTES;
|
||||
uint32_t first = RING_BYTES - tail;
|
||||
if (first > n) first = n;
|
||||
memcpy(buf, r->ring + tail, first);
|
||||
if (n > first) memcpy(buf + first, r->ring, n - first);
|
||||
r->count -= n;
|
||||
r->lock.Release();
|
||||
return (int)n;
|
||||
}
|
||||
|
||||
uint32_t Available(int handle) {
|
||||
Receiver* r = Lookup(handle, true);
|
||||
if (!r) return 0;
|
||||
return r->count;
|
||||
}
|
||||
|
||||
int64_t SetParam(int handle, int param, uint64_t value) {
|
||||
Receiver* r = Lookup(handle, true);
|
||||
if (!r) return -1;
|
||||
|
||||
switch (param) {
|
||||
case montauk::abi::SDR_PARAM_FREQ:
|
||||
if (!r->ops.SetFreq) return -1;
|
||||
if (r->ops.SetFreq(r->ctx, value) != 0) return -1;
|
||||
r->freq = value;
|
||||
return 0;
|
||||
case montauk::abi::SDR_PARAM_SAMPLE_RATE:
|
||||
if (!r->ops.SetSampleRate) return -1;
|
||||
if (r->ops.SetSampleRate(r->ctx, (uint32_t)value) != 0) return -1;
|
||||
r->sampleRate = (uint32_t)value;
|
||||
return 0;
|
||||
case montauk::abi::SDR_PARAM_GAIN_MODE:
|
||||
if (!r->ops.SetGainMode) return -1;
|
||||
if (r->ops.SetGainMode(r->ctx, (int)value) != 0) return -1;
|
||||
r->gainMode = (int)value ? 1 : 0;
|
||||
return 0;
|
||||
case montauk::abi::SDR_PARAM_GAIN:
|
||||
if (!r->ops.SetGain) return -1;
|
||||
if (r->ops.SetGain(r->ctx, (int)(int64_t)value) != 0) return -1;
|
||||
r->gain = (int)(int64_t)value;
|
||||
return 0;
|
||||
case montauk::abi::SDR_PARAM_FREQ_CORR:
|
||||
if (!r->ops.SetFreqCorrection) return -1;
|
||||
if (r->ops.SetFreqCorrection(r->ctx, (int)(int64_t)value) != 0) return -1;
|
||||
r->ppm = (int)(int64_t)value;
|
||||
return 0;
|
||||
case montauk::abi::SDR_PARAM_AGC:
|
||||
if (!r->ops.SetAgc) return -1;
|
||||
if (r->ops.SetAgc(r->ctx, (int)value) != 0) return -1;
|
||||
r->agc = (int)value ? 1 : 0;
|
||||
return 0;
|
||||
case montauk::abi::SDR_PARAM_DIRECT_SAMP:
|
||||
if (!r->ops.SetDirectSampling) return -1;
|
||||
if (r->ops.SetDirectSampling(r->ctx, (int)value) != 0) return -1;
|
||||
r->directSamp = (int)value;
|
||||
return 0;
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
int64_t GetParam(int handle, int param) {
|
||||
Receiver* r = Lookup(handle, true);
|
||||
if (!r) return -1;
|
||||
|
||||
switch (param) {
|
||||
case montauk::abi::SDR_PARAM_FREQ: return (int64_t)r->freq;
|
||||
case montauk::abi::SDR_PARAM_SAMPLE_RATE: return (int64_t)r->sampleRate;
|
||||
case montauk::abi::SDR_PARAM_GAIN_MODE: return r->gainMode;
|
||||
case montauk::abi::SDR_PARAM_GAIN: return r->gain;
|
||||
case montauk::abi::SDR_PARAM_FREQ_CORR: return r->ppm;
|
||||
case montauk::abi::SDR_PARAM_AGC: return r->agc;
|
||||
case montauk::abi::SDR_PARAM_DIRECT_SAMP: return r->directSamp;
|
||||
default: return -1;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,119 +0,0 @@
|
||||
/*
|
||||
* Sdr.hpp
|
||||
* Generic software-defined radio (SDR) receive subsystem.
|
||||
*
|
||||
* Hardware-agnostic registry of radio receivers. A concrete driver (e.g. the
|
||||
* RTL-SDR USB driver) registers itself as a receiver by supplying an ops table
|
||||
* and a private context pointer; it then pushes demodulated baseband I/Q
|
||||
* samples into a per-receiver ring buffer via PushSamples(). Userspace reaches
|
||||
* this layer through the SYS_SDR_* syscalls and drains the ring with Read().
|
||||
*
|
||||
* The native sample format is CU8 -- 8-bit unsigned interleaved I/Q -- which is
|
||||
* what the RTL2832U produces; other formats can be advertised per receiver via
|
||||
* SdrDeviceInfo.sampleFormat.
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
#include <CppLib/Spinlock.hpp>
|
||||
#include <Api/Syscall.hpp>
|
||||
|
||||
namespace Drivers::Radio::Sdr {
|
||||
|
||||
static constexpr int MAX_RECEIVERS = 4;
|
||||
static constexpr int MAX_GAINS = 32;
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Receiver ops table -- implemented by a concrete driver.
|
||||
// All calls happen in process/syscall context (never from the sample
|
||||
// callback), so they may block on USB control transfers. Each returns 0 on
|
||||
// success, negative on error. ctx is the receiver's private pointer.
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
struct ReceiverOps {
|
||||
int (*SetFreq)(void* ctx, uint64_t hz);
|
||||
int (*SetSampleRate)(void* ctx, uint32_t hz);
|
||||
int (*SetGainMode)(void* ctx, int manual); // 0 = auto/AGC, 1 = manual
|
||||
int (*SetGain)(void* ctx, int tenthsDb);
|
||||
int (*SetFreqCorrection)(void* ctx, int ppm);
|
||||
int (*SetAgc)(void* ctx, int on); // demod digital AGC
|
||||
int (*SetDirectSampling)(void* ctx, int mode); // 0=off,1=I,2=Q
|
||||
int (*Start)(void* ctx); // arm streaming
|
||||
int (*Stop)(void* ctx); // halt streaming
|
||||
// Optional: process-context housekeeping invoked from Read() while
|
||||
// streaming (e.g. USB stall recovery that cannot run in the ISR). May
|
||||
// be null.
|
||||
void (*Service)(void* ctx);
|
||||
};
|
||||
|
||||
// Static description a driver supplies at registration time.
|
||||
struct ReceiverDesc {
|
||||
const char* name; // e.g. "Realtek RTL2832U"
|
||||
const char* tuner; // e.g. "Rafael Micro R820T2"
|
||||
const char* serial; // bus location / serial string (may be null)
|
||||
uint64_t freqMin; // Hz
|
||||
uint64_t freqMax; // Hz
|
||||
uint32_t sampleRateMin;
|
||||
uint32_t sampleRateMax;
|
||||
const int* gains; // table of tenths-of-dB gain steps (may be null)
|
||||
uint32_t numGains;
|
||||
uint8_t format; // montauk::abi::SDR_FORMAT_*
|
||||
ReceiverOps ops;
|
||||
void* ctx;
|
||||
};
|
||||
|
||||
// =========================================================================
|
||||
// Driver-facing API
|
||||
// =========================================================================
|
||||
|
||||
// Register a receiver. Returns its index [0, MAX_RECEIVERS) or -1 if full.
|
||||
int Register(const ReceiverDesc& desc);
|
||||
|
||||
// Remove a receiver (e.g. on USB unplug). Stops streaming and frees the
|
||||
// ring. Safe to call with an out-of-range / already-removed index.
|
||||
void Unregister(int idx);
|
||||
|
||||
// Push baseband sample bytes into a receiver's ring buffer. Called from the
|
||||
// driver's USB completion callback (possibly interrupt context); never
|
||||
// allocates or blocks. Bytes that do not fit are dropped (counted).
|
||||
void PushSamples(int idx, const uint8_t* data, uint32_t len);
|
||||
|
||||
// True if the receiver is currently in the streaming state (used by drivers
|
||||
// to decide whether to re-arm USB transfers).
|
||||
bool IsStreaming(int idx);
|
||||
|
||||
// =========================================================================
|
||||
// Syscall-facing API
|
||||
// =========================================================================
|
||||
|
||||
// Number of registered receivers.
|
||||
int Count();
|
||||
|
||||
// Fill out an info struct for receiver idx. Returns false if idx invalid.
|
||||
bool GetInfo(int idx, montauk::abi::SdrDeviceInfo* out);
|
||||
|
||||
// Claim a receiver for use. Returns a handle (== idx) or -1 on failure.
|
||||
int Open(int idx);
|
||||
|
||||
// Release a receiver (stops streaming). Returns 0 on success.
|
||||
int Close(int handle);
|
||||
|
||||
// Begin / end sample delivery. Returns 0 on success, negative on error.
|
||||
int Start(int handle);
|
||||
int Stop(int handle);
|
||||
|
||||
// Copy up to len bytes of buffered I/Q out of the ring. Non-blocking;
|
||||
// returns the number of bytes copied (0 when nothing is queued).
|
||||
int Read(int handle, uint8_t* buf, uint32_t len);
|
||||
|
||||
// Number of sample bytes currently queued in the ring.
|
||||
uint32_t Available(int handle);
|
||||
|
||||
// Set / get a tunable parameter (montauk::abi::SDR_PARAM_*). SetParam
|
||||
// returns 0 on success; GetParam returns the cached value or negative on
|
||||
// error.
|
||||
int64_t SetParam(int handle, int param, uint64_t value);
|
||||
int64_t GetParam(int handle, int param);
|
||||
|
||||
}
|
||||
@@ -419,6 +419,14 @@ namespace Drivers::USB::Bluetooth {
|
||||
// ServiceEvents — steady-state event pump (idle loop)
|
||||
// =========================================================================
|
||||
|
||||
bool HasDeferredWork() {
|
||||
if (g_initPending.load(std::memory_order_acquire) &&
|
||||
!g_initialized && Fs::Vfs::IsDriveRegistered(0)) {
|
||||
return true;
|
||||
}
|
||||
return g_initialized && Hci::HasPendingCommands();
|
||||
}
|
||||
|
||||
void ServiceEvents() {
|
||||
if (!g_initialized) return;
|
||||
if (Xhci::InPollContext()) return; // never nest under PollEvents
|
||||
|
||||
@@ -27,6 +27,11 @@ namespace Drivers::USB::Bluetooth {
|
||||
// (PollEvents/DrainEvents/ProcessPendingCommands all self-serialize).
|
||||
void ServiceEvents();
|
||||
|
||||
// True when boot-deferred initialization or queued HCI control work needs
|
||||
// an idle-context service pass. USB receive events are signaled separately
|
||||
// by xHCI and cause the dispatcher to service Bluetooth in the same pass.
|
||||
bool HasDeferredWork();
|
||||
|
||||
// Query adapter state
|
||||
bool IsInitialized();
|
||||
uint8_t GetSlotId();
|
||||
|
||||
@@ -1719,6 +1719,11 @@ namespace Drivers::USB::Bluetooth::Hci {
|
||||
s_active.store(false, std::memory_order_release);
|
||||
}
|
||||
|
||||
bool HasPendingCommands() {
|
||||
return g_pendingTail.load(std::memory_order_acquire) !=
|
||||
g_pendingHead.load(std::memory_order_acquire);
|
||||
}
|
||||
|
||||
bool WaitSecureSendResult(uint32_t timeoutMs, uint8_t* outResult, uint8_t* outStatus) {
|
||||
uint64_t start = Timekeeping::GetMilliseconds();
|
||||
while (Timekeeping::GetMilliseconds() - start < timeoutMs) {
|
||||
|
||||
@@ -331,6 +331,7 @@ namespace Drivers::USB::Bluetooth::Hci {
|
||||
// real confirmed transfers. Call from top-level (e.g. the connect loop),
|
||||
// NOT from an event handler -- event handlers only enqueue.
|
||||
void ProcessPendingCommands();
|
||||
bool HasPendingCommands();
|
||||
|
||||
// ACL TX flow control: outstanding (un-acked) ACL packets, and the
|
||||
// controller's ACL buffer count (Number-Of-Completed-Packets credits). The
|
||||
|
||||
@@ -200,20 +200,28 @@ namespace Drivers::USB::HidKeyboard {
|
||||
return (hidUsage >= 0x39 && hidUsage <= 0x53);
|
||||
}
|
||||
|
||||
// Keys that arrive E0-prefixed on a PS/2 keyboard. They share a scancode
|
||||
// with a main-block key (keypad "/" with "/", keypad Enter with Enter), so
|
||||
// they must be flagged or a keyboard layout would rewrite them.
|
||||
static bool IsExtendedKey(uint8_t hidUsage) {
|
||||
return hidUsage == 0x54 || hidUsage == 0x58; // KP /, KP Enter
|
||||
}
|
||||
|
||||
// Build and inject a KeyEvent into the PS/2 keyboard buffer
|
||||
static void InjectKey(uint8_t scancode, bool pressed, uint8_t modifiers,
|
||||
bool nonChar = false) {
|
||||
bool nonChar = false, bool extended = false) {
|
||||
bool shift = (modifiers & (MOD_LEFT_SHIFT | MOD_RIGHT_SHIFT)) != 0;
|
||||
bool ctrl = (modifiers & (MOD_LEFT_CTRL | MOD_RIGHT_CTRL)) != 0;
|
||||
bool alt = (modifiers & (MOD_LEFT_ALT | MOD_RIGHT_ALT)) != 0;
|
||||
bool caps = Drivers::PS2::Keyboard::GetCapsLock();
|
||||
|
||||
char ascii = 0;
|
||||
if (!nonChar && scancode < 128 && pressed) {
|
||||
if (shift) {
|
||||
ascii = g_ScancodeToAsciiShifted[scancode];
|
||||
} else {
|
||||
ascii = g_ScancodeToAscii[scancode];
|
||||
}
|
||||
// Caps Lock inverts shift for letters only, matching the PS/2 path.
|
||||
char base = g_ScancodeToAscii[scancode];
|
||||
bool upper = shift;
|
||||
if (caps && base >= 'a' && base <= 'z') upper = !shift;
|
||||
ascii = upper ? g_ScancodeToAsciiShifted[scancode] : base;
|
||||
}
|
||||
|
||||
Drivers::PS2::Keyboard::KeyEvent event = {
|
||||
@@ -223,7 +231,9 @@ namespace Drivers::USB::HidKeyboard {
|
||||
.Shift = shift,
|
||||
.Ctrl = ctrl,
|
||||
.Alt = alt,
|
||||
.CapsLock = false
|
||||
.CapsLock = caps, // re-stamped by InjectKeyEvent, which owns the lock
|
||||
.AltGr = (modifiers & MOD_RIGHT_ALT) != 0,
|
||||
.Extended = extended
|
||||
};
|
||||
|
||||
Drivers::PS2::Keyboard::InjectKeyEvent(event);
|
||||
@@ -238,7 +248,9 @@ namespace Drivers::USB::HidKeyboard {
|
||||
.Shift = (modifiers & (MOD_LEFT_SHIFT | MOD_RIGHT_SHIFT)) != 0,
|
||||
.Ctrl = (modifiers & (MOD_LEFT_CTRL | MOD_RIGHT_CTRL)) != 0,
|
||||
.Alt = (modifiers & (MOD_LEFT_ALT | MOD_RIGHT_ALT)) != 0,
|
||||
.CapsLock = false
|
||||
.CapsLock = Drivers::PS2::Keyboard::GetCapsLock(),
|
||||
.AltGr = (modifiers & MOD_RIGHT_ALT) != 0,
|
||||
.Extended = false
|
||||
};
|
||||
|
||||
Drivers::PS2::Keyboard::InjectKeyEvent(event);
|
||||
@@ -271,7 +283,7 @@ namespace Drivers::USB::HidKeyboard {
|
||||
|
||||
uint8_t scancode = g_HidToScancode[key];
|
||||
if (scancode != 0) {
|
||||
InjectKey(scancode, false, modifiers, IsNonCharKey(key));
|
||||
InjectKey(scancode, false, modifiers, IsNonCharKey(key), IsExtendedKey(key));
|
||||
}
|
||||
g_PrevKeys[i] = 0;
|
||||
}
|
||||
@@ -354,7 +366,7 @@ namespace Drivers::USB::HidKeyboard {
|
||||
if (!KeyInArray(key, g_PrevKeys)) {
|
||||
uint8_t scancode = g_HidToScancode[key];
|
||||
if (scancode != 0) {
|
||||
InjectKey(scancode, true, modifiers, IsNonCharKey(key));
|
||||
InjectKey(scancode, true, modifiers, IsNonCharKey(key), IsExtendedKey(key));
|
||||
g_RepeatKey = key;
|
||||
g_RepeatStartMs = now;
|
||||
g_LastRepeatMs = 0;
|
||||
@@ -373,7 +385,7 @@ namespace Drivers::USB::HidKeyboard {
|
||||
if (!KeyInArray(key, keys)) {
|
||||
uint8_t scancode = g_HidToScancode[key];
|
||||
if (scancode != 0) {
|
||||
InjectKey(scancode, false, modifiers, IsNonCharKey(key));
|
||||
InjectKey(scancode, false, modifiers, IsNonCharKey(key), IsExtendedKey(key));
|
||||
}
|
||||
if (key == g_RepeatKey) {
|
||||
g_RepeatKey = 0;
|
||||
@@ -405,7 +417,7 @@ namespace Drivers::USB::HidKeyboard {
|
||||
(now - g_LastRepeatMs) >= TYPEMATIC_PERIOD_MS) {
|
||||
uint8_t scancode = g_HidToScancode[g_RepeatKey];
|
||||
if (scancode != 0) {
|
||||
InjectKey(scancode, true, g_PrevModifiers, IsNonCharKey(g_RepeatKey));
|
||||
InjectKey(scancode, true, g_PrevModifiers, IsNonCharKey(g_RepeatKey), IsExtendedKey(g_RepeatKey));
|
||||
}
|
||||
g_LastRepeatMs = now;
|
||||
}
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
/*
|
||||
* R820t.hpp
|
||||
* Rafael Micro R820T / R820T2 silicon tuner.
|
||||
*
|
||||
* The tuner sits on the RTL2832U's I2C bus; all register traffic is carried
|
||||
* by the demod's I2C repeater (managed by the RtlSdr layer, which enables the
|
||||
* repeater around every call here). This module owns the tuner-side logic:
|
||||
* the init register array, the PLL/VCO frequency synthesis, the RF tracking
|
||||
* filter / mux band selection, and the LNA/Mixer/VGA gain stages.
|
||||
*
|
||||
* Register/algorithm facts follow the publicly documented R820T programming
|
||||
* model (as used by osmocom rtl-sdr); the implementation here is original.
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
|
||||
namespace Drivers::USB::Radio {
|
||||
|
||||
// I2C bus address of the tuner on the RTL2832U (8-bit form).
|
||||
static constexpr uint8_t R820T_I2C_ADDR = 0x34;
|
||||
// Chip-id register (reg 0) reads back this value for an R820T/R820T2.
|
||||
static constexpr uint8_t R820T_CHECK_VAL = 0x69;
|
||||
|
||||
// First writable register; the 27-entry shadow covers regs 0x05..0x1f.
|
||||
static constexpr uint8_t R820T_REG_SHADOW_START = 5;
|
||||
static constexpr uint8_t R820T_NUM_REGS = 27;
|
||||
|
||||
struct R820tDev {
|
||||
uint8_t slotId;
|
||||
uint32_t xtal; // reference crystal, Hz (28.8 MHz on RTL-SDR)
|
||||
uint32_t intFreq; // IF the demod expects the signal at, Hz (3.57 MHz)
|
||||
uint8_t regs[32]; // register shadow (index == register number)
|
||||
bool hasLock; // PLL lock state after the last tune
|
||||
bool inited;
|
||||
};
|
||||
|
||||
// Detect an R820T/R820T2 on the demod I2C bus. The caller must have the
|
||||
// demod's I2C repeater enabled. Returns true if the chip id matches.
|
||||
bool R820tDetect(uint8_t slotId);
|
||||
|
||||
// Initialise the tuner (writes the init register array + base setup). The
|
||||
// caller must have the I2C repeater enabled. Returns true on success.
|
||||
bool R820tInit(R820tDev& d, uint8_t slotId, uint32_t xtal, uint32_t intFreq);
|
||||
|
||||
// Tune to an RF center frequency (Hz). Programs the RF mux band and the PLL
|
||||
// for an LO of rfHz + intFreq. Updates d.hasLock. Repeater must be on.
|
||||
bool R820tSetFreq(R820tDev& d, uint64_t rfHz);
|
||||
|
||||
// Configure gain. manual==0 puts LNA/mixer in AGC; manual!=0 selects the
|
||||
// closest fixed gain to tenthsDb from the LNA+mixer step tables.
|
||||
bool R820tSetGain(R820tDev& d, int manual, int tenthsDb);
|
||||
|
||||
// Put the tuner into standby (mute / power down).
|
||||
void R820tStandby(R820tDev& d);
|
||||
|
||||
// The discrete gain table (tenths of dB), for advertising to userspace.
|
||||
const int* R820tGainTable(int* count);
|
||||
|
||||
}
|
||||
@@ -1,575 +0,0 @@
|
||||
/*
|
||||
* RtlSdr.cpp
|
||||
* Realtek RTL2832U + R820T2 SDR receiver driver.
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#include "RtlSdr.hpp"
|
||||
#include "R820t.hpp"
|
||||
#include <Drivers/Radio/Sdr.hpp>
|
||||
#include <Drivers/USB/Xhci.hpp>
|
||||
#include <Drivers/USB/UsbDevice.hpp>
|
||||
#include <Memory/PageFrameAllocator.hpp>
|
||||
#include <Memory/HHDM.hpp>
|
||||
#include <Libraries/Memory.hpp>
|
||||
#include <Terminal/Terminal.hpp>
|
||||
#include <CppLib/Stream.hpp>
|
||||
#include <CppLib/Spinlock.hpp>
|
||||
#include <Api/Syscall.hpp>
|
||||
#include <atomic>
|
||||
|
||||
using namespace Kt;
|
||||
|
||||
namespace Drivers::USB::Radio {
|
||||
|
||||
// =========================================================================
|
||||
// Constants
|
||||
// =========================================================================
|
||||
|
||||
// Vendor control-transfer request types (vendor, host<->device).
|
||||
static constexpr uint8_t CTRL_OUT = 0x40; // host-to-device, vendor
|
||||
static constexpr uint8_t CTRL_IN = 0xC0; // device-to-host, vendor
|
||||
|
||||
// RTL2832U register blocks (high byte of wIndex; OR 0x10 to write).
|
||||
static constexpr uint8_t BLOCK_USB = 1;
|
||||
static constexpr uint8_t BLOCK_SYS = 2;
|
||||
static constexpr uint8_t BLOCK_IIC = 6;
|
||||
|
||||
// USB / system register addresses.
|
||||
static constexpr uint16_t USB_EPA_CTL = 0x2148;
|
||||
static constexpr uint16_t USB_EPA_MAXPKT = 0x2158;
|
||||
static constexpr uint16_t USB_SYSCTL = 0x2000;
|
||||
static constexpr uint16_t SYS_DEMOD_CTL = 0x3000;
|
||||
static constexpr uint16_t SYS_DEMOD_CTL1 = 0x300b;
|
||||
|
||||
static constexpr uint32_t RTL_XTAL = 28800000; // 28.8 MHz reference
|
||||
static constexpr uint32_t R82XX_IF = 3570000; // IF the demod expects
|
||||
static constexpr uint32_t TWO_POW22 = 1u << 22;
|
||||
|
||||
// =========================================================================
|
||||
// Driver state (single instance -- the common RTL-SDR case)
|
||||
// =========================================================================
|
||||
|
||||
static bool g_present = false;
|
||||
static bool g_hwInited = false;
|
||||
static bool g_streaming = false;
|
||||
static uint8_t g_slotId = 0;
|
||||
static int g_rxIndex = -1;
|
||||
|
||||
static uint8_t* g_ctlBuf = nullptr; // HHDM page for control transfers
|
||||
static kcp::Mutex g_ctlLock; // serialises register access
|
||||
|
||||
static R820tDev g_tuner{};
|
||||
static uint32_t g_rtlXtal = RTL_XTAL; // adjusted by ppm correction
|
||||
static int g_ppm = 0;
|
||||
static int g_manual = 0; // tuner gain mode (0=auto)
|
||||
static int g_gain = 0; // tuner gain, tenths of dB
|
||||
static int g_directSamp = 0; // 0=tuner path, 1=I ADC, 2=Q ADC
|
||||
static uint64_t g_lastFreq = 0; // last successfully tuned freq (Hz)
|
||||
|
||||
// Bulk-IN streaming geometry. We keep BULK_POOL_BUFS transfers of
|
||||
// BULK_XFER_LEN bytes outstanding at once (multi-URB), so the RTL2832U FIFO
|
||||
// always has a TRB to DMA into and never overflows in the window between a
|
||||
// completion and its re-arm -- the single-outstanding scheme dropped ~88% of
|
||||
// samples at 2.048 Msps for exactly that reason. 4 KiB == one DMA page;
|
||||
// 16 x 4 KiB == 64 KiB in flight, ~16 ms of slack at 4 MB/s.
|
||||
static constexpr uint32_t BULK_XFER_LEN = 4096;
|
||||
static constexpr uint32_t BULK_POOL_BUFS = 16;
|
||||
|
||||
// Set by the bulk-IN completion callback when the endpoint halts (cc=6
|
||||
// STALL etc.). Recovery (Reset Endpoint) needs a command wait and so must
|
||||
// run in process context -- serviced from Read() via OpService().
|
||||
static std::atomic<bool> g_bulkStalled{false};
|
||||
|
||||
// =========================================================================
|
||||
// Low-level register access (control transfers via EP0)
|
||||
// =========================================================================
|
||||
|
||||
static bool RegWrite(uint8_t block, uint16_t addr, uint16_t val, uint8_t len) {
|
||||
if (!g_ctlBuf) return false;
|
||||
g_ctlBuf[0] = (len == 1) ? (uint8_t)(val & 0xff) : (uint8_t)(val >> 8);
|
||||
g_ctlBuf[1] = (uint8_t)(val & 0xff);
|
||||
uint16_t index = (uint16_t)((block << 8) | 0x10);
|
||||
return Xhci::ControlTransfer(g_slotId, CTRL_OUT, 0, addr, index, len,
|
||||
g_ctlBuf, false) == Xhci::CC_SUCCESS;
|
||||
}
|
||||
|
||||
static uint8_t DemodRead(uint8_t page, uint16_t addr) {
|
||||
if (!g_ctlBuf) return 0;
|
||||
uint16_t raddr = (uint16_t)((addr << 8) | 0x20);
|
||||
g_ctlBuf[0] = 0;
|
||||
Xhci::ControlTransfer(g_slotId, CTRL_IN, 0, raddr, page, 1, g_ctlBuf, true);
|
||||
return g_ctlBuf[0];
|
||||
}
|
||||
|
||||
static bool DemodWrite(uint8_t page, uint16_t addr, uint16_t val, uint8_t len) {
|
||||
if (!g_ctlBuf) return false;
|
||||
uint16_t waddr = (uint16_t)((addr << 8) | 0x20);
|
||||
uint16_t index = (uint16_t)(0x10 | page);
|
||||
g_ctlBuf[0] = (len == 1) ? (uint8_t)(val & 0xff) : (uint8_t)(val >> 8);
|
||||
g_ctlBuf[1] = (uint8_t)(val & 0xff);
|
||||
bool ok = Xhci::ControlTransfer(g_slotId, CTRL_OUT, 0, waddr, index, len,
|
||||
g_ctlBuf, false) == Xhci::CC_SUCCESS;
|
||||
// Dummy status read after every demod write (reference behaviour);
|
||||
// acts as a write barrier so the register latches before the next op.
|
||||
DemodRead(0x0a, 0x01);
|
||||
return ok;
|
||||
}
|
||||
|
||||
static void SetI2cRepeater(bool on) {
|
||||
DemodWrite(1, 0x01, on ? 0x18 : 0x10, 1);
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// I2C facade for the tuner module
|
||||
// =========================================================================
|
||||
|
||||
bool RtlI2cWrite(uint8_t slotId, uint8_t i2cAddr, const uint8_t* buf, uint8_t len) {
|
||||
if (!g_ctlBuf || len == 0 || len > 64) return false;
|
||||
memcpy(g_ctlBuf, buf, len);
|
||||
uint16_t index = (uint16_t)((BLOCK_IIC << 8) | 0x10);
|
||||
uint32_t cc = Xhci::ControlTransfer(slotId, CTRL_OUT, 0, i2cAddr, index, len,
|
||||
g_ctlBuf, false);
|
||||
if (cc != Xhci::CC_SUCCESS)
|
||||
KernelLogStream(WARNING, "RTL-SDR") << "I2C write cc=" << (uint64_t)cc
|
||||
<< " reg=0x" << base::hex << (uint64_t)buf[0]
|
||||
<< " len=" << base::dec << (uint64_t)len;
|
||||
return cc == Xhci::CC_SUCCESS;
|
||||
}
|
||||
|
||||
bool RtlI2cRead(uint8_t slotId, uint8_t i2cAddr, uint8_t* buf, uint8_t len) {
|
||||
if (!g_ctlBuf || len == 0 || len > 64) return false;
|
||||
uint16_t index = (uint16_t)(BLOCK_IIC << 8);
|
||||
uint32_t cc = Xhci::ControlTransfer(slotId, CTRL_IN, 0, i2cAddr, index, len,
|
||||
g_ctlBuf, true);
|
||||
if (cc != Xhci::CC_SUCCESS) {
|
||||
KernelLogStream(WARNING, "RTL-SDR") << "I2C read cc=" << (uint64_t)cc
|
||||
<< " len=" << (uint64_t)len;
|
||||
return false;
|
||||
}
|
||||
memcpy(buf, g_ctlBuf, len);
|
||||
return true;
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Demodulator bring-up
|
||||
// =========================================================================
|
||||
|
||||
// The 16-tap default FIR (8x int8 then 8x int12) used for the SDR/FM path.
|
||||
static void SetFir() {
|
||||
static const int fir[16] = {
|
||||
-54, -36, -41, -40, -32, -14, 14, 53,
|
||||
101, 156, 215, 273, 327, 372, 404, 421,
|
||||
};
|
||||
uint8_t buf[20];
|
||||
for (int i = 0; i < 8; i++) buf[i] = (uint8_t)(fir[i] & 0xff);
|
||||
for (int i = 0; i < 8; i += 2) {
|
||||
int v0 = fir[8 + i];
|
||||
int v1 = fir[8 + i + 1];
|
||||
buf[8 + i * 3 / 2] = (uint8_t)((v0 >> 4) & 0xff);
|
||||
buf[8 + i * 3 / 2 + 1] = (uint8_t)(((v0 << 4) | ((v1 >> 8) & 0x0f)) & 0xff);
|
||||
buf[8 + i * 3 / 2 + 2] = (uint8_t)(v1 & 0xff);
|
||||
}
|
||||
for (int i = 0; i < 20; i++) DemodWrite(1, (uint16_t)(0x1c + i), buf[i], 1);
|
||||
}
|
||||
|
||||
static bool BasebandInit() {
|
||||
// USB FIFO / endpoint A setup.
|
||||
RegWrite(BLOCK_USB, USB_SYSCTL, 0x09, 1);
|
||||
RegWrite(BLOCK_USB, USB_EPA_MAXPKT, 0x0002, 2);
|
||||
RegWrite(BLOCK_USB, USB_EPA_CTL, 0x1002, 2);
|
||||
|
||||
// Power on the demod.
|
||||
RegWrite(BLOCK_SYS, SYS_DEMOD_CTL1, 0x22, 1);
|
||||
RegWrite(BLOCK_SYS, SYS_DEMOD_CTL, 0xe8, 1);
|
||||
|
||||
// Soft-reset the demod state machine.
|
||||
DemodWrite(1, 0x01, 0x14, 1);
|
||||
DemodWrite(1, 0x01, 0x10, 1);
|
||||
|
||||
// Disable spectrum inversion + clear DDC shift / IF registers.
|
||||
DemodWrite(1, 0x15, 0x00, 1);
|
||||
DemodWrite(1, 0x16, 0x0000, 2);
|
||||
for (int i = 0; i < 6; i++) DemodWrite(1, (uint16_t)(0x16 + i), 0x00, 1);
|
||||
|
||||
SetFir();
|
||||
|
||||
DemodWrite(0, 0x19, 0x05, 1); // enable SDR mode, disable DAGC
|
||||
DemodWrite(1, 0x93, 0xf0, 1);
|
||||
DemodWrite(1, 0x94, 0x0f, 1);
|
||||
DemodWrite(1, 0x11, 0x00, 1); // disable AGC loop
|
||||
DemodWrite(1, 0x04, 0x00, 1);
|
||||
DemodWrite(0, 0x61, 0x60, 1); // disable PID filter
|
||||
DemodWrite(0, 0x06, 0x80, 1); // default ADC I/Q datapath
|
||||
DemodWrite(1, 0xb1, 0x1b, 1); // zero-IF + DC cancel + IQ comp/est
|
||||
DemodWrite(0, 0x0d, 0x83, 1); // disable clock output on TP_CK0
|
||||
return true;
|
||||
}
|
||||
|
||||
// Set the digital downconversion IF frequency the demod searches at.
|
||||
static void SetIfFreq(uint32_t freq) {
|
||||
int32_t ifv = (int32_t)(-(int64_t)((uint64_t)freq * TWO_POW22 / g_rtlXtal));
|
||||
DemodWrite(1, 0x19, (uint16_t)((ifv >> 16) & 0x3f), 1);
|
||||
DemodWrite(1, 0x1a, (uint16_t)((ifv >> 8) & 0xff), 1);
|
||||
DemodWrite(1, 0x1b, (uint16_t)(ifv & 0xff), 1);
|
||||
}
|
||||
|
||||
static void ApplySampleFreqCorrection() {
|
||||
int32_t offs = (int32_t)(-(int64_t)g_ppm * (1 << 24) / 1000000);
|
||||
DemodWrite(1, 0x3f, (uint16_t)(offs & 0xff), 1);
|
||||
DemodWrite(1, 0x3e, (uint16_t)((offs >> 8) & 0x3f), 1);
|
||||
}
|
||||
|
||||
static bool TunerInit() {
|
||||
SetI2cRepeater(true);
|
||||
// Retry detection a few times: an I2C read can transiently come back
|
||||
// wrong if it raced another core's USB activity around bring-up.
|
||||
bool detected = false;
|
||||
for (int attempt = 0; attempt < 4 && !detected; attempt++)
|
||||
detected = R820tDetect(g_slotId);
|
||||
bool ok = detected && R820tInit(g_tuner, g_slotId, g_rtlXtal, R82XX_IF);
|
||||
SetI2cRepeater(false);
|
||||
if (!detected) {
|
||||
KernelLogStream(WARNING, "RTL-SDR") << "no R820T2 tuner found on I2C";
|
||||
return false;
|
||||
}
|
||||
if (!ok) return false;
|
||||
|
||||
// Demod path for the R820T2 low-IF tuner.
|
||||
DemodWrite(1, 0xb1, 0x1a, 1); // disable zero-IF mode
|
||||
DemodWrite(0, 0x08, 0x4d, 1); // enable In-phase ADC input only
|
||||
SetIfFreq(R82XX_IF);
|
||||
DemodWrite(1, 0x15, 0x01, 1); // enable spectrum inversion
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool EnsureInit() {
|
||||
if (g_hwInited) return true;
|
||||
if (!g_present || !g_ctlBuf) return false;
|
||||
if (!BasebandInit()) return false;
|
||||
if (!TunerInit()) return false;
|
||||
g_hwInited = true;
|
||||
KernelLogStream(OK, "RTL-SDR") << "Demod + tuner brought up on slot "
|
||||
<< (uint64_t)g_slotId;
|
||||
return true;
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Tuning / configuration (each holds g_ctlLock via the op wrappers)
|
||||
// =========================================================================
|
||||
|
||||
static int DoSetFreq(uint64_t hz) {
|
||||
if (!EnsureInit()) return -1;
|
||||
if (g_directSamp) {
|
||||
// Tuner is bypassed: tuning is the demod's digital downconverter.
|
||||
SetIfFreq((uint32_t)hz);
|
||||
g_lastFreq = hz;
|
||||
return 0;
|
||||
}
|
||||
SetI2cRepeater(true);
|
||||
bool ok = R820tSetFreq(g_tuner, hz);
|
||||
SetI2cRepeater(false);
|
||||
if (ok) g_lastFreq = hz;
|
||||
return ok ? 0 : -1;
|
||||
}
|
||||
|
||||
static int DoSetSampleRate(uint32_t rate) {
|
||||
if (!EnsureInit()) return -1;
|
||||
// The RTL2832 resampler does not cover 300k..900k.
|
||||
if (rate <= 225000 || rate > 3200000 ||
|
||||
(rate > 300000 && rate <= 900000)) return -1;
|
||||
|
||||
// The ratio uses the NOMINAL crystal frequency: ppm correction is
|
||||
// applied by the demod's sample-frequency-offset registers below, so
|
||||
// baking it into the ratio too would correct the rate twice.
|
||||
uint32_t ratio = (uint32_t)(((uint64_t)RTL_XTAL * TWO_POW22) / rate);
|
||||
ratio &= 0x0ffffffc;
|
||||
DemodWrite(1, 0x9f, (uint16_t)((ratio >> 16) & 0xffff), 2);
|
||||
DemodWrite(1, 0xa1, (uint16_t)(ratio & 0xffff), 2);
|
||||
|
||||
ApplySampleFreqCorrection();
|
||||
DemodWrite(1, 0x01, 0x14, 1); // soft reset
|
||||
DemodWrite(1, 0x01, 0x10, 1);
|
||||
SetIfFreq(g_directSamp ? (uint32_t)g_lastFreq : R82XX_IF);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int DoSetGainMode(int manual) {
|
||||
if (!EnsureInit()) return -1;
|
||||
g_manual = manual ? 1 : 0;
|
||||
SetI2cRepeater(true);
|
||||
bool ok = R820tSetGain(g_tuner, g_manual, g_gain);
|
||||
SetI2cRepeater(false);
|
||||
return ok ? 0 : -1;
|
||||
}
|
||||
|
||||
static int DoSetGain(int tenths) {
|
||||
if (!EnsureInit()) return -1;
|
||||
g_gain = tenths;
|
||||
g_manual = 1; // selecting an explicit gain implies manual mode
|
||||
SetI2cRepeater(true);
|
||||
bool ok = R820tSetGain(g_tuner, 1, g_gain);
|
||||
SetI2cRepeater(false);
|
||||
return ok ? 0 : -1;
|
||||
}
|
||||
|
||||
static int DoSetFreqCorrection(int ppm) {
|
||||
if (!EnsureInit()) return -1;
|
||||
g_ppm = ppm;
|
||||
g_rtlXtal = (uint32_t)((int64_t)RTL_XTAL + (int64_t)RTL_XTAL * ppm / 1000000);
|
||||
g_tuner.xtal = g_rtlXtal;
|
||||
ApplySampleFreqCorrection();
|
||||
// The tuner PLL (and, in direct mode, the DDC) derive from the xtal;
|
||||
// retune so the new correction actually takes effect.
|
||||
if (g_lastFreq) return DoSetFreq(g_lastFreq);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int DoSetAgc(int on) {
|
||||
if (!EnsureInit()) return -1;
|
||||
return DemodWrite(0, 0x19, on ? 0x25 : 0x05, 1) ? 0 : -1;
|
||||
}
|
||||
|
||||
static int DoSetDirectSampling(int mode) {
|
||||
if (!EnsureInit()) return -1;
|
||||
if (mode) {
|
||||
// Bypass the tuner and digitise the ADC input directly.
|
||||
SetI2cRepeater(true);
|
||||
R820tStandby(g_tuner);
|
||||
SetI2cRepeater(false);
|
||||
DemodWrite(1, 0xb1, 0x1a, 1); // disable zero-IF
|
||||
DemodWrite(1, 0x15, 0x00, 1); // no spectrum inversion
|
||||
DemodWrite(0, 0x08, 0x4d, 1); // In-phase ADC input
|
||||
DemodWrite(0, 0x06, (mode == 2) ? 0x90 : 0x80, 1); // Q vs I ADC
|
||||
g_directSamp = mode;
|
||||
// Tuning now happens in the DDC; carry the current frequency over.
|
||||
SetIfFreq((uint32_t)g_lastFreq);
|
||||
} else {
|
||||
// Restore the R820T2 low-IF receive path. Standby powered the
|
||||
// tuner down, so it needs a full re-initialisation.
|
||||
SetI2cRepeater(true);
|
||||
bool ok = R820tInit(g_tuner, g_slotId, g_rtlXtal, R82XX_IF);
|
||||
SetI2cRepeater(false);
|
||||
if (!ok) return -1;
|
||||
SetIfFreq(R82XX_IF);
|
||||
DemodWrite(1, 0x15, 0x01, 1); // enable spectrum inversion
|
||||
DemodWrite(0, 0x06, 0x80, 1); // default ADC I/Q datapath
|
||||
g_directSamp = 0;
|
||||
if (g_lastFreq) return DoSetFreq(g_lastFreq);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Streaming
|
||||
// =========================================================================
|
||||
|
||||
static void TransferCallback(uint8_t slotId, uint8_t epDci,
|
||||
const uint8_t* data, uint32_t length,
|
||||
uint32_t /*completionCode*/) {
|
||||
if (slotId != g_slotId) return;
|
||||
auto* dev = Xhci::GetDevice(slotId);
|
||||
if (!dev) return;
|
||||
|
||||
uint8_t bulkInDci = dev->BulkInEpNum ? (uint8_t)(dev->BulkInEpNum * 2 + 1) : 0;
|
||||
if (epDci != bulkInDci || !g_streaming) return;
|
||||
|
||||
if (data) {
|
||||
// Deliver samples only. The xHCI layer owns the multi-buffer pool
|
||||
// (StartBulkInStream) and re-arms this very buffer automatically once
|
||||
// we return; re-queuing here would double-arm the pool and lap the
|
||||
// ring. PushSamples copies out synchronously, so the buffer is free
|
||||
// to be re-armed the instant this returns.
|
||||
if (length > 0)
|
||||
Drivers::Radio::Sdr::PushSamples(g_rxIndex, data, length);
|
||||
} else {
|
||||
// Error (data==nullptr), e.g. cc=6 STALL: the endpoint is halted.
|
||||
// Clearing it requires a Reset Endpoint command that waits on the
|
||||
// event ring, which is unsafe here (we are inside PollEvents).
|
||||
// Flag it for process-context recovery in OpService(), which resets
|
||||
// the endpoint and re-primes the whole pool.
|
||||
g_bulkStalled.store(true, std::memory_order_relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
static int DoStart() {
|
||||
if (!EnsureInit()) return -1;
|
||||
|
||||
// Reset endpoint-A FIFO so streaming starts on a clean boundary.
|
||||
RegWrite(BLOCK_USB, USB_EPA_CTL, 0x1002, 2); // hold + reset
|
||||
RegWrite(BLOCK_USB, USB_EPA_CTL, 0x0000, 2); // release
|
||||
|
||||
g_bulkStalled.store(false, std::memory_order_relaxed);
|
||||
g_streaming = true;
|
||||
Xhci::RegisterTransferCallback(g_slotId, TransferCallback);
|
||||
|
||||
auto* dev = Xhci::GetDevice(g_slotId);
|
||||
if (dev && dev->BulkInEpNum)
|
||||
Xhci::StartBulkInStream(g_slotId, BULK_XFER_LEN, BULK_POOL_BUFS);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int DoStop() {
|
||||
g_streaming = false;
|
||||
g_bulkStalled.store(false, std::memory_order_relaxed);
|
||||
// Disarm the multi-buffer rotation so no further transfers re-arm, then
|
||||
// hold/reset the FIFO so the device stops producing samples.
|
||||
Xhci::StopBulkInStream(g_slotId);
|
||||
if (g_ctlBuf) RegWrite(BLOCK_USB, USB_EPA_CTL, 0x1002, 2);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Process-context housekeeping called from Read(): recover a halted bulk-IN
|
||||
// endpoint (Reset Endpoint + Set TR Dequeue) and re-arm reception.
|
||||
static void DoService() {
|
||||
if (!g_bulkStalled.load(std::memory_order_relaxed)) return;
|
||||
g_ctlLock.Acquire();
|
||||
if (g_streaming) {
|
||||
Xhci::ResetBulkInEndpoint(g_slotId);
|
||||
Xhci::PrimeBulkInStream(g_slotId);
|
||||
}
|
||||
g_bulkStalled.store(false, std::memory_order_relaxed);
|
||||
g_ctlLock.Release();
|
||||
|
||||
static uint32_t recoveries = 0;
|
||||
if (recoveries < 5) {
|
||||
recoveries++;
|
||||
KernelLogStream(INFO, "RTL-SDR") << "bulk IN stall recovered ("
|
||||
<< (uint64_t)recoveries << ")";
|
||||
}
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Ops table wrappers (lock the control path)
|
||||
// =========================================================================
|
||||
|
||||
static int OpSetFreq(void*, uint64_t hz) {
|
||||
g_ctlLock.Acquire(); int r = DoSetFreq(hz); g_ctlLock.Release(); return r;
|
||||
}
|
||||
static int OpSetSampleRate(void*, uint32_t hz) {
|
||||
g_ctlLock.Acquire(); int r = DoSetSampleRate(hz); g_ctlLock.Release(); return r;
|
||||
}
|
||||
static int OpSetGainMode(void*, int manual) {
|
||||
g_ctlLock.Acquire(); int r = DoSetGainMode(manual); g_ctlLock.Release(); return r;
|
||||
}
|
||||
static int OpSetGain(void*, int tenths) {
|
||||
g_ctlLock.Acquire(); int r = DoSetGain(tenths); g_ctlLock.Release(); return r;
|
||||
}
|
||||
static int OpSetFreqCorrection(void*, int ppm) {
|
||||
g_ctlLock.Acquire(); int r = DoSetFreqCorrection(ppm); g_ctlLock.Release(); return r;
|
||||
}
|
||||
static int OpSetAgc(void*, int on) {
|
||||
g_ctlLock.Acquire(); int r = DoSetAgc(on); g_ctlLock.Release(); return r;
|
||||
}
|
||||
static int OpSetDirectSampling(void*, int mode) {
|
||||
g_ctlLock.Acquire(); int r = DoSetDirectSampling(mode); g_ctlLock.Release(); return r;
|
||||
}
|
||||
static int OpStart(void*) {
|
||||
g_ctlLock.Acquire(); int r = DoStart(); g_ctlLock.Release(); return r;
|
||||
}
|
||||
static int OpStop(void*) {
|
||||
g_ctlLock.Acquire(); int r = DoStop(); g_ctlLock.Release(); return r;
|
||||
}
|
||||
// DoService does its own locking (around the reset), so OpService must not
|
||||
// take g_ctlLock here.
|
||||
static void OpService(void*) { DoService(); }
|
||||
|
||||
// =========================================================================
|
||||
// USB enumeration hooks
|
||||
// =========================================================================
|
||||
|
||||
bool IsRtlSdr(uint16_t vid, uint16_t pid) {
|
||||
if (vid != 0x0bda) return false; // Realtek Semiconductor
|
||||
switch (pid) {
|
||||
// Only the two known RTL2832U ids. In particular 0x2831 is the
|
||||
// RTL2831U, a DIFFERENT demod this driver cannot program.
|
||||
case 0x2832: // RTL2832U (generic)
|
||||
case 0x2838: // RTL2838 (most RTL-SDR.com dongles)
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void RegisterDevice(uint8_t slotId) {
|
||||
if (g_present) {
|
||||
KernelLogStream(WARNING, "RTL-SDR")
|
||||
<< "second RTL-SDR ignored (single instance), slot " << (uint64_t)slotId;
|
||||
return;
|
||||
}
|
||||
|
||||
g_slotId = slotId;
|
||||
g_present = true;
|
||||
g_hwInited = false;
|
||||
g_streaming = false;
|
||||
g_ppm = 0;
|
||||
g_rtlXtal = RTL_XTAL;
|
||||
g_manual = 0;
|
||||
g_gain = 0;
|
||||
g_directSamp = 0;
|
||||
g_lastFreq = 0;
|
||||
g_tuner = R820tDev{};
|
||||
|
||||
g_ctlBuf = (uint8_t*)Memory::g_pfa->AllocateZeroed();
|
||||
if (!g_ctlBuf) {
|
||||
KernelLogStream(ERROR, "RTL-SDR") << "control buffer alloc failed";
|
||||
g_present = false;
|
||||
return;
|
||||
}
|
||||
|
||||
int gainCount = 0;
|
||||
const int* gains = R820tGainTable(&gainCount);
|
||||
|
||||
Drivers::Radio::Sdr::ReceiverDesc desc{};
|
||||
desc.name = "Realtek RTL2832U";
|
||||
desc.tuner = "Rafael Micro R820T2";
|
||||
desc.serial = "USB RTL-SDR";
|
||||
desc.freqMin = 24000000ull;
|
||||
desc.freqMax = 1766000000ull;
|
||||
desc.sampleRateMin = 225001;
|
||||
desc.sampleRateMax = 3200000;
|
||||
desc.gains = gains;
|
||||
desc.numGains = (uint32_t)gainCount;
|
||||
desc.format = montauk::abi::SDR_FORMAT_CU8;
|
||||
desc.ops.SetFreq = OpSetFreq;
|
||||
desc.ops.SetSampleRate = OpSetSampleRate;
|
||||
desc.ops.SetGainMode = OpSetGainMode;
|
||||
desc.ops.SetGain = OpSetGain;
|
||||
desc.ops.SetFreqCorrection = OpSetFreqCorrection;
|
||||
desc.ops.SetAgc = OpSetAgc;
|
||||
desc.ops.SetDirectSampling = OpSetDirectSampling;
|
||||
desc.ops.Start = OpStart;
|
||||
desc.ops.Stop = OpStop;
|
||||
desc.ops.Service = OpService;
|
||||
desc.ctx = nullptr;
|
||||
|
||||
g_rxIndex = Drivers::Radio::Sdr::Register(desc);
|
||||
if (g_rxIndex < 0) {
|
||||
KernelLogStream(ERROR, "RTL-SDR") << "SDR registration failed";
|
||||
Memory::g_pfa->Free(g_ctlBuf);
|
||||
g_ctlBuf = nullptr;
|
||||
g_present = false;
|
||||
return;
|
||||
}
|
||||
|
||||
KernelLogStream(OK, "RTL-SDR") << "RTL-SDR on slot " << (uint64_t)slotId
|
||||
<< " registered as receiver " << (uint64_t)g_rxIndex;
|
||||
}
|
||||
|
||||
void UnregisterDevice(uint8_t slotId) {
|
||||
if (!g_present || slotId != g_slotId) return;
|
||||
|
||||
g_streaming = false;
|
||||
if (g_rxIndex >= 0) Drivers::Radio::Sdr::Unregister(g_rxIndex);
|
||||
g_rxIndex = -1;
|
||||
g_present = false;
|
||||
g_hwInited = false;
|
||||
|
||||
if (g_ctlBuf) {
|
||||
Memory::g_pfa->Free(g_ctlBuf);
|
||||
g_ctlBuf = nullptr;
|
||||
}
|
||||
KernelLogStream(INFO, "RTL-SDR") << "RTL-SDR removed from slot " << (uint64_t)slotId;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
/*
|
||||
* RtlSdr.hpp
|
||||
* Realtek RTL2832U + R820T2 software-defined-radio receiver (RTL-SDR).
|
||||
*
|
||||
* The RTL2832U is a DVB-T demodulator that, in raw mode, streams 8-bit
|
||||
* unsigned I/Q samples over a USB bulk-IN endpoint. This driver brings up the
|
||||
* demodulator + R820T2 tuner, configures the resampler / IF, and feeds the
|
||||
* bulk-IN samples into the generic SDR receive subsystem (Drivers::Radio::Sdr),
|
||||
* which userspace reaches through the SYS_SDR_* syscalls.
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
|
||||
namespace Drivers::USB::Radio {
|
||||
|
||||
// True if a USB VID:PID identifies a supported RTL2832U-based SDR dongle.
|
||||
bool IsRtlSdr(uint16_t vid, uint16_t pid);
|
||||
|
||||
// Called by USB enumeration once the bulk-IN endpoint has been configured.
|
||||
// Registers a receiver with the SDR subsystem; the demod/tuner are brought
|
||||
// up lazily on first use (in process context, never from the USB poll path).
|
||||
void RegisterDevice(uint8_t slotId);
|
||||
|
||||
// Tear down on unplug.
|
||||
void UnregisterDevice(uint8_t slotId);
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// I2C facade used by the R820T2 tuner module. These carry the tuner's
|
||||
// register traffic over the demod's I2C block. The control-transfer mutex
|
||||
// is held by the calling op wrapper, so these do not lock themselves.
|
||||
// -------------------------------------------------------------------------
|
||||
bool RtlI2cWrite(uint8_t slotId, uint8_t i2cAddr, const uint8_t* buf, uint8_t len);
|
||||
bool RtlI2cRead(uint8_t slotId, uint8_t i2cAddr, uint8_t* buf, uint8_t len);
|
||||
|
||||
}
|
||||
@@ -10,7 +10,6 @@
|
||||
#include "HidMouse.hpp"
|
||||
#include "MassStorage.hpp"
|
||||
#include "Bluetooth/Bluetooth.hpp"
|
||||
#include "Radio/RtlSdr.hpp"
|
||||
#include <Terminal/Terminal.hpp>
|
||||
#include <CppLib/Stream.hpp>
|
||||
#include <Memory/HHDM.hpp>
|
||||
@@ -351,11 +350,6 @@ namespace Drivers::USB::UsbDevice {
|
||||
bool foundBulkOut = false;
|
||||
uint16_t hidReportDescLen = 0;
|
||||
|
||||
// RTL-SDR dongles are vendor-class (0xFF) devices identified by VID:PID.
|
||||
// They expose a single bulk-IN endpoint that streams raw I/Q samples;
|
||||
// treat them like the other bulk-capable class drivers below.
|
||||
bool foundRadio = Drivers::USB::Radio::IsRtlSdr(devDesc.idVendor, devDesc.idProduct);
|
||||
|
||||
while (offset + 2 <= totalLen) {
|
||||
uint8_t len = cfgBuf[offset];
|
||||
uint8_t type = cfgBuf[offset + 1];
|
||||
@@ -423,8 +417,8 @@ namespace Drivers::USB::UsbDevice {
|
||||
foundEp = true;
|
||||
}
|
||||
|
||||
// Bluetooth, Mass Storage and RTL-SDR bulk endpoints
|
||||
if (foundBt || currentMsc || foundRadio) {
|
||||
// Bulk endpoints needed by in-kernel Bluetooth and storage drivers.
|
||||
if (foundBt || currentMsc) {
|
||||
if (isIn && xferType == EP_XFER_INTERRUPT && !foundEp) {
|
||||
// HCI event pipe (interrupt IN)
|
||||
dev->InterruptEpNum = ep->bEndpointAddress & 0x0F;
|
||||
@@ -457,6 +451,51 @@ namespace Drivers::USB::UsbDevice {
|
||||
foundBt = true;
|
||||
}
|
||||
|
||||
// No in-kernel class driver recognized this device. Preserve the first
|
||||
// interface and its bulk endpoints so a userspace driver can claim it.
|
||||
// The xHCI slot model currently stores one interface; a future model
|
||||
// can retain every alternate/interface without changing the userspace
|
||||
// claim ABI, which already names the interface number explicitly.
|
||||
bool knownInterface = foundBt || foundMsc ||
|
||||
dev->InterfaceClass == CLASS_HID;
|
||||
if (!knownInterface) {
|
||||
bool inFirstInterface = false;
|
||||
bool haveFirstInterface = false;
|
||||
offset = 0;
|
||||
while (offset + 2 <= totalLen) {
|
||||
uint8_t len = cfgBuf[offset];
|
||||
uint8_t type = cfgBuf[offset + 1];
|
||||
if (len == 0 || offset + len > totalLen) break;
|
||||
|
||||
if (type == DESC_INTERFACE &&
|
||||
offset + sizeof(InterfaceDescriptor) <= totalLen) {
|
||||
if (haveFirstInterface) break;
|
||||
auto* iface = (InterfaceDescriptor*)&cfgBuf[offset];
|
||||
dev->InterfaceClass = iface->bInterfaceClass;
|
||||
dev->InterfaceSubClass = iface->bInterfaceSubClass;
|
||||
dev->InterfaceProtocol = iface->bInterfaceProtocol;
|
||||
dev->InterfaceNumber = iface->bInterfaceNumber;
|
||||
haveFirstInterface = true;
|
||||
inFirstInterface = true;
|
||||
} else if (inFirstInterface && type == DESC_ENDPOINT &&
|
||||
offset + sizeof(EndpointDescriptor) <= totalLen) {
|
||||
auto* ep = (EndpointDescriptor*)&cfgBuf[offset];
|
||||
uint8_t xferType = ep->bmAttributes & EP_XFER_TYPE_MASK;
|
||||
bool isIn = (ep->bEndpointAddress & EP_DIR_IN) != 0;
|
||||
if (xferType == EP_XFER_BULK && isIn && !foundBulkIn) {
|
||||
dev->BulkInEpNum = ep->bEndpointAddress & 0x0F;
|
||||
dev->BulkInMaxPacket = ep->wMaxPacketSize & 0x7FF;
|
||||
foundBulkIn = true;
|
||||
} else if (xferType == EP_XFER_BULK && !isIn && !foundBulkOut) {
|
||||
dev->BulkOutEpNum = ep->bEndpointAddress & 0x0F;
|
||||
dev->BulkOutMaxPacket = ep->wMaxPacketSize & 0x7FF;
|
||||
foundBulkOut = true;
|
||||
}
|
||||
}
|
||||
offset += len;
|
||||
}
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
// Step 8: SET_CONFIGURATION
|
||||
// -----------------------------------------------------------------
|
||||
@@ -657,15 +696,20 @@ namespace Drivers::USB::UsbDevice {
|
||||
// -----------------------------------------------------------------
|
||||
// Step 13: Register with the appropriate class driver
|
||||
// -----------------------------------------------------------------
|
||||
if (dev->InterfaceClass == CLASS_HID && dev->InterfaceProtocol == PROTOCOL_KEYBOARD) {
|
||||
if (foundEp && dev->InterfaceClass == CLASS_HID &&
|
||||
dev->InterfaceProtocol == PROTOCOL_KEYBOARD) {
|
||||
dev->KernelDriverBound = true;
|
||||
HidKeyboard::RegisterDevice(slotId);
|
||||
KernelLogStream(OK, "USB") << "Slot " << (uint64_t)slotId << ": HID Boot Keyboard";
|
||||
} else if (dev->InterfaceClass == CLASS_HID && dev->InterfaceProtocol == PROTOCOL_MOUSE) {
|
||||
} else if (foundEp && dev->InterfaceClass == CLASS_HID &&
|
||||
dev->InterfaceProtocol == PROTOCOL_MOUSE) {
|
||||
dev->KernelDriverBound = true;
|
||||
HidMouse::RegisterDevice(slotId);
|
||||
KernelLogStream(OK, "USB") << "Slot " << (uint64_t)slotId << ": HID Boot Mouse";
|
||||
} else if (dev->InterfaceClass == CLASS_WIRELESS &&
|
||||
dev->InterfaceSubClass == SUBCLASS_RF &&
|
||||
dev->InterfaceProtocol == PROTOCOL_BLUETOOTH) {
|
||||
dev->KernelDriverBound = true;
|
||||
Bluetooth::RegisterAdapter(slotId);
|
||||
KernelLogStream(OK, "USB") << "Slot " << (uint64_t)slotId << ": Bluetooth Adapter"
|
||||
<< " VID:" << base::hex << (uint64_t)dev->VendorId
|
||||
@@ -674,15 +718,10 @@ namespace Drivers::USB::UsbDevice {
|
||||
dev->InterfaceSubClass == SUBCLASS_SCSI &&
|
||||
dev->InterfaceProtocol == PROTOCOL_BULK_ONLY &&
|
||||
foundMsc && foundBulkIn && foundBulkOut) {
|
||||
dev->KernelDriverBound = true;
|
||||
MassStorage::RegisterDevice(slotId);
|
||||
KernelLogStream(OK, "USB") << "Slot " << (uint64_t)slotId
|
||||
<< ": USB Mass Storage";
|
||||
} else if (foundRadio && foundBulkIn) {
|
||||
Drivers::USB::Radio::RegisterDevice(slotId);
|
||||
KernelLogStream(OK, "USB") << "Slot " << (uint64_t)slotId
|
||||
<< ": RTL-SDR receiver"
|
||||
<< " VID:" << base::hex << (uint64_t)dev->VendorId
|
||||
<< " PID:" << (uint64_t)dev->ProductId << base::dec;
|
||||
} else if (foundEp) {
|
||||
KernelLogStream(INFO, "USB") << "Slot " << (uint64_t)slotId
|
||||
<< ": USB device, class=" << (uint64_t)dev->InterfaceClass
|
||||
@@ -692,6 +731,9 @@ namespace Drivers::USB::UsbDevice {
|
||||
<< ": Non-HID device, class=" << (uint64_t)devDesc.bDeviceClass;
|
||||
}
|
||||
|
||||
// Publish to userspace only after endpoint configuration and kernel
|
||||
// class-driver binding decisions are complete.
|
||||
dev->Ready = true;
|
||||
return slotId;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,381 @@
|
||||
/*
|
||||
* UserUsb.cpp
|
||||
* Process-owned access to unbound USB interfaces.
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#include "UserUsb.hpp"
|
||||
|
||||
#include "Xhci.hpp"
|
||||
#include <Sched/Scheduler.hpp>
|
||||
#include <Memory/Heap.hpp>
|
||||
#include <Memory/PageFrameAllocator.hpp>
|
||||
#include <Libraries/Memory.hpp>
|
||||
#include <CppLib/Spinlock.hpp>
|
||||
|
||||
namespace Drivers::USB::UserUsb {
|
||||
|
||||
static constexpr int MaxClaims = Xhci::MAX_SLOTS;
|
||||
static constexpr uint32_t RingBytes = 256 * 1024;
|
||||
static constexpr uint32_t MaxControlBytes = 4096;
|
||||
|
||||
struct ClaimState {
|
||||
bool active;
|
||||
bool connected;
|
||||
bool streaming;
|
||||
uint8_t slotId;
|
||||
uint8_t interfaceNumber;
|
||||
uint16_t generation;
|
||||
int ownerPid;
|
||||
|
||||
uint8_t* ring;
|
||||
uint32_t head;
|
||||
uint32_t count;
|
||||
uint64_t droppedBytes;
|
||||
uint32_t lastCompletionCode;
|
||||
kcp::Spinlock ringLock;
|
||||
};
|
||||
|
||||
static ClaimState g_claims[MaxClaims];
|
||||
// Serializes process-context operations and prevents a sibling thread from
|
||||
// closing a claim while another syscall is using its backing state.
|
||||
static kcp::Mutex g_claimsLock;
|
||||
|
||||
static int MakeHandle(int index, uint16_t generation) {
|
||||
return ((int)generation << 8) | index;
|
||||
}
|
||||
|
||||
static ClaimState* LookupLocked(int handle, bool requireConnected = true) {
|
||||
int index = handle & 0xff;
|
||||
uint16_t generation = (uint16_t)((uint32_t)handle >> 8);
|
||||
if (index < 0 || index >= MaxClaims || generation == 0) return nullptr;
|
||||
|
||||
ClaimState& claim = g_claims[index];
|
||||
if (!claim.active || claim.generation != generation) return nullptr;
|
||||
if (claim.ownerPid != Sched::GetCurrentPid()) return nullptr;
|
||||
if (requireConnected && !claim.connected) return nullptr;
|
||||
return &claim;
|
||||
}
|
||||
|
||||
static bool SlotClaimedLocked(uint8_t slotId) {
|
||||
for (int i = 0; i < MaxClaims; i++) {
|
||||
if (g_claims[i].active && g_claims[i].connected &&
|
||||
g_claims[i].slotId == slotId) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static void CopyInterfaceInfo(uint8_t slotId, const Xhci::UsbDeviceInfo& dev,
|
||||
montauk::abi::UsbInterfaceInfo& out) {
|
||||
memset(&out, 0, sizeof(out));
|
||||
out.slotId = slotId;
|
||||
out.portId = dev.PortId;
|
||||
out.speed = (uint8_t)dev.Speed;
|
||||
out.interfaceNumber = dev.InterfaceNumber;
|
||||
out.vendorId = dev.VendorId;
|
||||
out.productId = dev.ProductId;
|
||||
out.deviceClass = dev.DeviceClass;
|
||||
out.interfaceClass = dev.InterfaceClass;
|
||||
out.interfaceSubClass = dev.InterfaceSubClass;
|
||||
out.interfaceProtocol = dev.InterfaceProtocol;
|
||||
out.bulkInEndpoint = dev.BulkInEpNum ? (uint8_t)(0x80 | dev.BulkInEpNum) : 0;
|
||||
out.bulkOutEndpoint = dev.BulkOutEpNum;
|
||||
out.bulkInMaxPacket = dev.BulkInMaxPacket;
|
||||
out.bulkOutMaxPacket = dev.BulkOutMaxPacket;
|
||||
out.kernelDriverBound = dev.KernelDriverBound ? 1 : 0;
|
||||
out.claimed = SlotClaimedLocked(slotId) ? 1 : 0;
|
||||
}
|
||||
|
||||
int List(montauk::abi::UsbInterfaceInfo* out, int maxCount) {
|
||||
if (!out || maxCount <= 0) return 0;
|
||||
g_claimsLock.Acquire();
|
||||
int count = 0;
|
||||
for (uint8_t slot = 1; slot <= Xhci::MAX_SLOTS && count < maxCount; slot++) {
|
||||
Xhci::UsbDeviceInfo* dev = Xhci::GetDevice(slot);
|
||||
if (!dev || !dev->Active || !dev->Ready) continue;
|
||||
CopyInterfaceInfo(slot, *dev, out[count++]);
|
||||
}
|
||||
g_claimsLock.Release();
|
||||
return count;
|
||||
}
|
||||
|
||||
int Claim(uint8_t slotId, uint8_t interfaceNumber) {
|
||||
int ownerPid = Sched::GetCurrentPid();
|
||||
if (ownerPid < 0 || slotId == 0 || slotId > Xhci::MAX_SLOTS) return montauk::abi::USB_ERR_INVALID;
|
||||
|
||||
g_claimsLock.Acquire();
|
||||
Xhci::UsbDeviceInfo* dev = Xhci::GetDevice(slotId);
|
||||
if (!dev || !dev->Active || !dev->Ready ||
|
||||
dev->InterfaceNumber != interfaceNumber) {
|
||||
g_claimsLock.Release();
|
||||
return montauk::abi::USB_ERR_NOT_FOUND;
|
||||
}
|
||||
if (dev->KernelDriverBound) {
|
||||
g_claimsLock.Release();
|
||||
return montauk::abi::USB_ERR_KERNEL_BOUND;
|
||||
}
|
||||
if (SlotClaimedLocked(slotId)) {
|
||||
g_claimsLock.Release();
|
||||
return montauk::abi::USB_ERR_BUSY;
|
||||
}
|
||||
|
||||
for (int i = 0; i < MaxClaims; i++) {
|
||||
ClaimState& claim = g_claims[i];
|
||||
if (claim.active) continue;
|
||||
uint16_t generation = (uint16_t)(claim.generation + 1);
|
||||
// Keep the encoded int handle positive so conventional `h < 0`
|
||||
// error checks remain valid in userspace.
|
||||
if (generation == 0 || generation > 0x7fff) generation = 1;
|
||||
claim.active = true;
|
||||
claim.connected = true;
|
||||
claim.streaming = false;
|
||||
claim.slotId = slotId;
|
||||
claim.interfaceNumber = interfaceNumber;
|
||||
claim.generation = generation;
|
||||
claim.ownerPid = ownerPid;
|
||||
claim.ring = nullptr;
|
||||
claim.head = 0;
|
||||
claim.count = 0;
|
||||
claim.droppedBytes = 0;
|
||||
claim.lastCompletionCode = Xhci::CC_SUCCESS;
|
||||
int handle = MakeHandle(i, generation);
|
||||
g_claimsLock.Release();
|
||||
return handle;
|
||||
}
|
||||
|
||||
g_claimsLock.Release();
|
||||
return montauk::abi::USB_ERR_NO_RESOURCES;
|
||||
}
|
||||
|
||||
static void TransferCallback(uint8_t slotId, uint8_t epDci,
|
||||
const uint8_t* data, uint32_t length,
|
||||
uint32_t completionCode) {
|
||||
for (int i = 0; i < MaxClaims; i++) {
|
||||
ClaimState& claim = g_claims[i];
|
||||
claim.ringLock.Acquire();
|
||||
if (!claim.active || !claim.connected || !claim.streaming ||
|
||||
claim.slotId != slotId) {
|
||||
claim.ringLock.Release();
|
||||
continue;
|
||||
}
|
||||
|
||||
Xhci::UsbDeviceInfo* dev = Xhci::GetDevice(slotId);
|
||||
uint8_t expectedDci = (dev && dev->BulkInEpNum)
|
||||
? (uint8_t)(dev->BulkInEpNum * 2 + 1) : 0;
|
||||
claim.lastCompletionCode = completionCode;
|
||||
if (epDci != expectedDci || !data || length == 0 || !claim.ring) {
|
||||
claim.ringLock.Release();
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t space = RingBytes - claim.count;
|
||||
uint32_t copied = length < space ? length : space;
|
||||
uint32_t first = RingBytes - claim.head;
|
||||
if (first > copied) first = copied;
|
||||
memcpy(claim.ring + claim.head, data, first);
|
||||
if (copied > first) memcpy(claim.ring, data + first, copied - first);
|
||||
claim.head = (claim.head + copied) % RingBytes;
|
||||
claim.count += copied;
|
||||
claim.droppedBytes += length - copied;
|
||||
claim.ringLock.Release();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
static void CloseLocked(ClaimState& claim) {
|
||||
claim.ringLock.Acquire();
|
||||
bool connected = claim.connected;
|
||||
bool wasStreaming = claim.streaming;
|
||||
uint8_t slotId = claim.slotId;
|
||||
claim.streaming = false;
|
||||
claim.active = false;
|
||||
claim.connected = false;
|
||||
claim.ownerPid = -1;
|
||||
uint8_t* ring = claim.ring;
|
||||
claim.ring = nullptr;
|
||||
claim.head = claim.count = 0;
|
||||
claim.ringLock.Release();
|
||||
|
||||
// Mark the claim inactive before stopping: PollEvents may dispatch a
|
||||
// late completion from StopBulkInStream, and the callback must drop it.
|
||||
if (connected && wasStreaming) Xhci::StopBulkInStream(slotId);
|
||||
if (connected) Xhci::RegisterTransferCallback(slotId, nullptr);
|
||||
if (ring) Memory::g_heap->Free(ring);
|
||||
}
|
||||
|
||||
int Close(int handle) {
|
||||
g_claimsLock.Acquire();
|
||||
ClaimState* claim = LookupLocked(handle, false);
|
||||
if (!claim) {
|
||||
g_claimsLock.Release();
|
||||
return montauk::abi::USB_ERR_INVALID;
|
||||
}
|
||||
CloseLocked(*claim);
|
||||
g_claimsLock.Release();
|
||||
return 0;
|
||||
}
|
||||
|
||||
int Control(int handle, const montauk::abi::UsbControlRequest& request,
|
||||
void* data, uint32_t dataLen) {
|
||||
if (dataLen != request.length || dataLen > MaxControlBytes ||
|
||||
(dataLen != 0 && data == nullptr)) return montauk::abi::USB_ERR_INVALID;
|
||||
|
||||
g_claimsLock.Acquire();
|
||||
ClaimState* claim = LookupLocked(handle);
|
||||
if (!claim) {
|
||||
g_claimsLock.Release();
|
||||
return montauk::abi::USB_ERR_DISCONNECTED;
|
||||
}
|
||||
|
||||
void* dma = nullptr;
|
||||
if (dataLen != 0) {
|
||||
dma = Memory::g_pfa->AllocateZeroed();
|
||||
if (!dma) {
|
||||
g_claimsLock.Release();
|
||||
return montauk::abi::USB_ERR_NO_RESOURCES;
|
||||
}
|
||||
if ((request.requestType & 0x80) == 0) memcpy(dma, data, dataLen);
|
||||
}
|
||||
|
||||
uint32_t cc = Xhci::ControlTransfer(claim->slotId, request.requestType,
|
||||
request.request, request.value, request.index, request.length,
|
||||
dma, (request.requestType & 0x80) != 0);
|
||||
if ((cc == Xhci::CC_SUCCESS || cc == Xhci::CC_SHORT_PACKET) &&
|
||||
dataLen != 0 && (request.requestType & 0x80) != 0) {
|
||||
memcpy(data, dma, dataLen);
|
||||
}
|
||||
if (dma) Memory::g_pfa->Free(dma);
|
||||
g_claimsLock.Release();
|
||||
return (cc == Xhci::CC_SUCCESS || cc == Xhci::CC_SHORT_PACKET)
|
||||
? 0 : montauk::abi::USB_ERR_IO;
|
||||
}
|
||||
|
||||
int StartBulkIn(int handle, uint32_t transferBytes, uint32_t bufferCount) {
|
||||
if (transferBytes == 0 || transferBytes > 4096 ||
|
||||
bufferCount == 0 || bufferCount > 16) return montauk::abi::USB_ERR_INVALID;
|
||||
|
||||
g_claimsLock.Acquire();
|
||||
ClaimState* claim = LookupLocked(handle);
|
||||
if (!claim) {
|
||||
g_claimsLock.Release();
|
||||
return montauk::abi::USB_ERR_DISCONNECTED;
|
||||
}
|
||||
Xhci::UsbDeviceInfo* dev = Xhci::GetDevice(claim->slotId);
|
||||
if (!dev || !dev->BulkInEpNum || !dev->BulkInRing) {
|
||||
g_claimsLock.Release();
|
||||
return montauk::abi::USB_ERR_UNSUPPORTED;
|
||||
}
|
||||
if (claim->streaming) {
|
||||
g_claimsLock.Release();
|
||||
return 0;
|
||||
}
|
||||
if (!claim->ring) claim->ring = (uint8_t*)Memory::g_heap->Request(RingBytes);
|
||||
if (!claim->ring) {
|
||||
g_claimsLock.Release();
|
||||
return montauk::abi::USB_ERR_NO_RESOURCES;
|
||||
}
|
||||
|
||||
claim->ringLock.Acquire();
|
||||
claim->head = claim->count = 0;
|
||||
claim->droppedBytes = 0;
|
||||
claim->lastCompletionCode = Xhci::CC_SUCCESS;
|
||||
claim->streaming = true;
|
||||
claim->ringLock.Release();
|
||||
Xhci::RegisterTransferCallback(claim->slotId, TransferCallback);
|
||||
Xhci::StartBulkInStream(claim->slotId, transferBytes, bufferCount);
|
||||
if (!Xhci::IsBulkInStreamActive(claim->slotId)) {
|
||||
claim->ringLock.Acquire();
|
||||
claim->streaming = false;
|
||||
claim->ringLock.Release();
|
||||
Xhci::RegisterTransferCallback(claim->slotId, nullptr);
|
||||
g_claimsLock.Release();
|
||||
return montauk::abi::USB_ERR_NO_RESOURCES;
|
||||
}
|
||||
g_claimsLock.Release();
|
||||
return 0;
|
||||
}
|
||||
|
||||
int StopBulkIn(int handle) {
|
||||
g_claimsLock.Acquire();
|
||||
ClaimState* claim = LookupLocked(handle, false);
|
||||
if (!claim) {
|
||||
g_claimsLock.Release();
|
||||
return montauk::abi::USB_ERR_INVALID;
|
||||
}
|
||||
claim->ringLock.Acquire();
|
||||
bool stop = claim->connected && claim->streaming;
|
||||
claim->streaming = false;
|
||||
claim->ringLock.Release();
|
||||
if (stop) Xhci::StopBulkInStream(claim->slotId);
|
||||
g_claimsLock.Release();
|
||||
return claim->connected ? 0 : montauk::abi::USB_ERR_DISCONNECTED;
|
||||
}
|
||||
|
||||
int ReadBulkIn(int handle, uint8_t* out, uint32_t maxLen) {
|
||||
if (!out && maxLen != 0) return montauk::abi::USB_ERR_INVALID;
|
||||
g_claimsLock.Acquire();
|
||||
ClaimState* claim = LookupLocked(handle, false);
|
||||
if (!claim) {
|
||||
g_claimsLock.Release();
|
||||
return montauk::abi::USB_ERR_INVALID;
|
||||
}
|
||||
if (!claim->connected && claim->count == 0) {
|
||||
g_claimsLock.Release();
|
||||
return montauk::abi::USB_ERR_DISCONNECTED;
|
||||
}
|
||||
// Failed completions halt the endpoint and cannot be repaired from the
|
||||
// xHCI event callback. Recover in process context so userspace drivers
|
||||
// do not need a host-controller-specific reset API.
|
||||
claim->ringLock.Acquire();
|
||||
bool recover = claim->connected && claim->streaming &&
|
||||
claim->lastCompletionCode != Xhci::CC_SUCCESS &&
|
||||
claim->lastCompletionCode != Xhci::CC_SHORT_PACKET;
|
||||
if (recover) claim->lastCompletionCode = Xhci::CC_SUCCESS;
|
||||
claim->ringLock.Release();
|
||||
if (recover) {
|
||||
Xhci::ResetBulkInEndpoint(claim->slotId);
|
||||
Xhci::PrimeBulkInStream(claim->slotId);
|
||||
}
|
||||
if (!claim->ring || maxLen == 0) {
|
||||
g_claimsLock.Release();
|
||||
return 0;
|
||||
}
|
||||
claim->ringLock.Acquire();
|
||||
uint32_t copied = claim->count < maxLen ? claim->count : maxLen;
|
||||
uint32_t tail = (claim->head + RingBytes - claim->count) % RingBytes;
|
||||
uint32_t first = RingBytes - tail;
|
||||
if (first > copied) first = copied;
|
||||
memcpy(out, claim->ring + tail, first);
|
||||
if (copied > first) memcpy(out + first, claim->ring, copied - first);
|
||||
claim->count -= copied;
|
||||
claim->ringLock.Release();
|
||||
g_claimsLock.Release();
|
||||
return (int)copied;
|
||||
}
|
||||
|
||||
void ReleaseAllForPid(int pid) {
|
||||
if (pid < 0) return;
|
||||
g_claimsLock.Acquire();
|
||||
for (int i = 0; i < MaxClaims; i++) {
|
||||
if (g_claims[i].active && g_claims[i].ownerPid == pid) CloseLocked(g_claims[i]);
|
||||
}
|
||||
g_claimsLock.Release();
|
||||
}
|
||||
|
||||
void DeviceDisconnected(uint8_t slotId) {
|
||||
// Hot-unplug runs in deferred kernel context. Do not take the
|
||||
// process-operation mutex: a control syscall may be polling the same
|
||||
// event queue. The per-claim spinlock is enough to make callbacks and
|
||||
// reads observe the disconnect atomically.
|
||||
for (int i = 0; i < MaxClaims; i++) {
|
||||
ClaimState& claim = g_claims[i];
|
||||
claim.ringLock.Acquire();
|
||||
if (claim.active && claim.slotId == slotId) {
|
||||
claim.connected = false;
|
||||
claim.streaming = false;
|
||||
}
|
||||
claim.ringLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* UserUsb.hpp
|
||||
* Process-owned access to unbound USB interfaces.
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <Api/Syscall.hpp>
|
||||
|
||||
namespace Drivers::USB::UserUsb {
|
||||
|
||||
// Enumerate the interfaces represented by the xHCI device table.
|
||||
int List(montauk::abi::UsbInterfaceInfo* out, int maxCount);
|
||||
|
||||
// Exclusively claim an interface that has no in-kernel class driver.
|
||||
// The returned handle is generation checked and belongs to the calling
|
||||
// process. The current xHCI device model represents one interface per
|
||||
// slot, so a claim is presently exclusive for the whole device slot.
|
||||
int Claim(uint8_t slotId, uint8_t interfaceNumber);
|
||||
int Close(int handle);
|
||||
|
||||
// Issue an EP0 control request. bmRequestType supplies the direction;
|
||||
// dataLen is limited to one DMA page.
|
||||
int Control(int handle, const montauk::abi::UsbControlRequest& request,
|
||||
void* data, uint32_t dataLen);
|
||||
|
||||
// Continuous bulk-IN streaming into a kernel ring. Read is non-blocking.
|
||||
int StartBulkIn(int handle, uint32_t transferBytes, uint32_t bufferCount);
|
||||
int StopBulkIn(int handle);
|
||||
int ReadBulkIn(int handle, uint8_t* out, uint32_t maxLen);
|
||||
|
||||
// Lifetime hooks used by process teardown and USB hot-unplug.
|
||||
void ReleaseAllForPid(int pid);
|
||||
void DeviceDisconnected(uint8_t slotId);
|
||||
|
||||
}
|
||||
@@ -10,7 +10,7 @@
|
||||
#include "HidKeyboard.hpp"
|
||||
#include "HidMouse.hpp"
|
||||
#include "MassStorage.hpp"
|
||||
#include "Radio/RtlSdr.hpp"
|
||||
#include "UserUsb.hpp"
|
||||
#include <Pci/Pci.hpp>
|
||||
#include <Terminal/Terminal.hpp>
|
||||
#include <CppLib/Stream.hpp>
|
||||
@@ -167,8 +167,7 @@ namespace Drivers::USB::Xhci {
|
||||
// nesting (a callback invoked from THIS core's PollEvents); a different
|
||||
// core merely polling must not make a process-context transfer skip its
|
||||
// wait -- that returned CC_SUCCESS before the device filled the buffer
|
||||
// (observed as garbled RTL-SDR register reads while the BT firmware
|
||||
// download was polling on another core).
|
||||
// (observed as corrupted USB control reads while another core was polling).
|
||||
static std::atomic<int> g_pollOwnerCpu{-1};
|
||||
|
||||
// Serialises non-nested (waiting) control transfers so only one EP0
|
||||
@@ -198,7 +197,7 @@ namespace Drivers::USB::Xhci {
|
||||
// re-arms the SAME buffer at the ring tail, so the endpoint is never without
|
||||
// a place to DMA. This closes the gap that single-outstanding bulk IN leaves
|
||||
// between completion and re-arm, during which the device FIFO overflows
|
||||
// (the RTL-SDR ~88% sample-drop at 2.048 Msps). PoolCount==0 => the legacy
|
||||
// under sustained high-rate input. PoolCount==0 selects the legacy
|
||||
// single-buffer path above (used by Bluetooth ACL), unchanged.
|
||||
static constexpr uint32_t BULK_IN_POOL_MAX = 16;
|
||||
static uint8_t* g_bulkInPool[MAX_SLOTS + 1][BULK_IN_POOL_MAX] = {};
|
||||
@@ -206,6 +205,7 @@ namespace Drivers::USB::Xhci {
|
||||
static uint32_t g_bulkInPoolCount[MAX_SLOTS + 1] = {}; // outstanding URBs (0=off)
|
||||
static uint32_t g_bulkInPoolHead[MAX_SLOTS + 1] = {}; // next buffer to complete
|
||||
static uint32_t g_bulkInPoolXferLen[MAX_SLOTS + 1] = {}; // bytes per transfer
|
||||
static kcp::Spinlock g_bulkInPoolLocks[MAX_SLOTS + 1];
|
||||
|
||||
// Transfer callbacks for non-HID class drivers (per slot)
|
||||
static TransferCallback g_transferCallbacks[MAX_SLOTS + 1] = {};
|
||||
@@ -590,15 +590,19 @@ namespace Drivers::USB::Xhci {
|
||||
// buffer is safe -- it will not be DMA'd into again
|
||||
// until the other PoolCount-1 transfers ahead of it
|
||||
// complete (~PoolCount ms of slack).
|
||||
uint32_t i = g_bulkInPoolHead[slotId];
|
||||
uint32_t reqLen = g_bulkInPoolXferLen[slotId];
|
||||
uint32_t len = (residual < reqLen) ? (reqLen - residual) : 0;
|
||||
g_transferCallbacks[slotId](slotId, epDci,
|
||||
g_bulkInPool[slotId][i], len, completionCode);
|
||||
QueueBulkInTransfer(slotId, g_bulkInPool[slotId][i],
|
||||
g_bulkInPoolPhys[slotId][i], reqLen);
|
||||
g_bulkInPoolHead[slotId] =
|
||||
(i + 1) % g_bulkInPoolCount[slotId];
|
||||
g_bulkInPoolLocks[slotId].Acquire();
|
||||
uint32_t poolCount = g_bulkInPoolCount[slotId];
|
||||
if (poolCount > 0) {
|
||||
uint32_t i = g_bulkInPoolHead[slotId];
|
||||
uint32_t reqLen = g_bulkInPoolXferLen[slotId];
|
||||
uint32_t len = (residual < reqLen) ? (reqLen - residual) : 0;
|
||||
g_transferCallbacks[slotId](slotId, epDci,
|
||||
g_bulkInPool[slotId][i], len, completionCode);
|
||||
QueueBulkInTransfer(slotId, g_bulkInPool[slotId][i],
|
||||
g_bulkInPoolPhys[slotId][i], reqLen);
|
||||
g_bulkInPoolHead[slotId] = (i + 1) % poolCount;
|
||||
}
|
||||
g_bulkInPoolLocks[slotId].Release();
|
||||
} else if (epDci == bulkInDci && g_transferCallbacks[slotId]) {
|
||||
// Bulk IN — dispatch via registered callback.
|
||||
// len = actually-transferred bytes (requested -
|
||||
@@ -1149,13 +1153,18 @@ namespace Drivers::USB::Xhci {
|
||||
// single-buffer start relies on.
|
||||
void PrimeBulkInStream(uint8_t slotId) {
|
||||
if (slotId == 0 || slotId > MAX_SLOTS || !g_devices[slotId].Active) return;
|
||||
g_bulkInPoolLocks[slotId].Acquire();
|
||||
uint32_t n = g_bulkInPoolCount[slotId];
|
||||
if (n == 0) return;
|
||||
if (n == 0) {
|
||||
g_bulkInPoolLocks[slotId].Release();
|
||||
return;
|
||||
}
|
||||
g_bulkInPoolHead[slotId] = 0;
|
||||
uint32_t len = g_bulkInPoolXferLen[slotId];
|
||||
for (uint32_t i = 0; i < n; i++)
|
||||
QueueBulkInTransfer(slotId, g_bulkInPool[slotId][i],
|
||||
g_bulkInPoolPhys[slotId][i], len);
|
||||
g_bulkInPoolLocks[slotId].Release();
|
||||
}
|
||||
|
||||
void StartBulkInStream(uint8_t slotId, uint32_t xferLen, uint32_t numBuffers) {
|
||||
@@ -1195,15 +1204,27 @@ namespace Drivers::USB::Xhci {
|
||||
for (uint32_t i = 0; i < numBuffers; i++)
|
||||
QueueBulkInTransfer(slotId, g_bulkInPool[slotId][i],
|
||||
g_bulkInPoolPhys[slotId][i], xferLen);
|
||||
g_bulkInPoolLocks[slotId].Acquire();
|
||||
g_bulkInPoolCount[slotId] = numBuffers;
|
||||
g_bulkInPoolLocks[slotId].Release();
|
||||
}
|
||||
|
||||
bool IsBulkInStreamActive(uint8_t slotId) {
|
||||
if (slotId == 0 || slotId > MAX_SLOTS) return false;
|
||||
g_bulkInPoolLocks[slotId].Acquire();
|
||||
bool active = g_bulkInPoolCount[slotId] != 0;
|
||||
g_bulkInPoolLocks[slotId].Release();
|
||||
return active;
|
||||
}
|
||||
|
||||
void StopBulkInStream(uint8_t slotId) {
|
||||
if (slotId == 0 || slotId > MAX_SLOTS) return;
|
||||
// Disarm the rotation; any late completion now takes the (no-op for SDR)
|
||||
// legacy path and is not re-armed. Buffers are retained for reuse.
|
||||
g_bulkInPoolLocks[slotId].Acquire();
|
||||
bool wasArmed = g_bulkInPoolCount[slotId] != 0;
|
||||
g_bulkInPoolCount[slotId] = 0;
|
||||
g_bulkInPoolLocks[slotId].Release();
|
||||
if (!wasArmed) return;
|
||||
|
||||
// Flush the up-to-PoolCount TRBs still pending on the ring: Stop
|
||||
@@ -1437,9 +1458,7 @@ namespace Drivers::USB::Xhci {
|
||||
}
|
||||
|
||||
static void UnregisterClassDriver(uint8_t slotId, const UsbDeviceInfo& dev) {
|
||||
if (Radio::IsRtlSdr(dev.VendorId, dev.ProductId)) {
|
||||
Radio::UnregisterDevice(slotId);
|
||||
} else if (dev.InterfaceClass == UsbDevice::CLASS_MASS_STORAGE) {
|
||||
if (dev.InterfaceClass == UsbDevice::CLASS_MASS_STORAGE) {
|
||||
MassStorage::UnregisterDevice(slotId);
|
||||
} else if (dev.InterfaceClass == UsbDevice::CLASS_HID &&
|
||||
dev.InterfaceProtocol == UsbDevice::PROTOCOL_KEYBOARD) {
|
||||
@@ -1559,6 +1578,7 @@ namespace Drivers::USB::Xhci {
|
||||
// Device disconnected — deactivate its slot
|
||||
for (uint8_t s = 1; s <= MAX_SLOTS; s++) {
|
||||
if (g_devices[s].Active && g_devices[s].PortId == port + 1) {
|
||||
UserUsb::DeviceDisconnected(s);
|
||||
UnregisterClassDriver(s, g_devices[s]);
|
||||
g_devices[s].Active = false;
|
||||
g_transferCallbacks[s] = nullptr;
|
||||
|
||||
@@ -233,6 +233,7 @@ namespace Drivers::USB::Xhci {
|
||||
|
||||
struct UsbDeviceInfo {
|
||||
bool Active;
|
||||
bool Ready; // descriptors/endpoints and binding are complete
|
||||
uint8_t PortId;
|
||||
uint32_t Speed;
|
||||
uint16_t VendorId;
|
||||
@@ -242,6 +243,7 @@ namespace Drivers::USB::Xhci {
|
||||
uint8_t InterfaceProtocol;
|
||||
uint8_t InterfaceNumber;
|
||||
uint8_t DeviceClass; // bDeviceClass from device descriptor
|
||||
bool KernelDriverBound; // unavailable to a userspace interface claim
|
||||
|
||||
// Interrupt IN endpoint
|
||||
uint8_t InterruptEpNum; // Endpoint number (1-15)
|
||||
@@ -329,7 +331,7 @@ namespace Drivers::USB::Xhci {
|
||||
// Clear a halted bulk IN endpoint (Reset Endpoint + Set TR Dequeue) without
|
||||
// re-arming. Must be called from process context (it issues commands that
|
||||
// wait on the event ring); the caller re-arms with QueueBulkInTransfer.
|
||||
// Used for SDR stream stall recovery (RTL2832 bulk IN can STALL on start).
|
||||
// Used by generic process-owned bulk streams after a transfer stall.
|
||||
void ResetBulkInEndpoint(uint8_t slotId);
|
||||
|
||||
// Clear a halted bulk OUT endpoint and discard the errored/queued TRBs.
|
||||
@@ -352,10 +354,11 @@ namespace Drivers::USB::Xhci {
|
||||
// as it completes. The slot's registered transfer callback receives every
|
||||
// buffer's data but must NOT re-arm itself (the event handler does). This
|
||||
// eliminates the FIFO-overflow gap of single-outstanding bulk IN. Use for
|
||||
// sustained high-rate sources (RTL-SDR I/Q). PrimeBulkInStream re-queues the
|
||||
// sustained high-rate sources. PrimeBulkInStream re-queues the
|
||||
// whole pool after a stall reset; StopBulkInStream disarms the rotation.
|
||||
// All three are process-context calls.
|
||||
void StartBulkInStream(uint8_t slotId, uint32_t xferLen, uint32_t numBuffers);
|
||||
bool IsBulkInStreamActive(uint8_t slotId);
|
||||
void PrimeBulkInStream(uint8_t slotId);
|
||||
void StopBulkInStream(uint8_t slotId);
|
||||
|
||||
|
||||
@@ -101,6 +101,7 @@ namespace Fs {
|
||||
.GetLabel = RdGetLabel,
|
||||
.ReadDirAt = RdReadDirAt,
|
||||
.Stat = RdStat,
|
||||
.SetTimes = nullptr,
|
||||
// Statically allocated: nothing to release.
|
||||
.Unmount = nullptr,
|
||||
};
|
||||
|
||||
@@ -1202,6 +1202,13 @@ namespace Fs::Ext2 {
|
||||
Inode inode;
|
||||
if (!TraversePath(self, path, &inodeNum, &inode)) return -1;
|
||||
|
||||
// Directories are not openable as files, matching the ramdisk. A
|
||||
// handle on one would fail every read/write anyway, and userspace
|
||||
// stat() falls back to open() when it cannot get real metadata:
|
||||
// succeeding here classified every directory as a regular file and
|
||||
// GCC's include-path setup then rejected them as "not a directory".
|
||||
if ((inode.i_mode & IMODE_TYPE_MASK) == IMODE_DIR) return -1;
|
||||
|
||||
for (int i = 0; i < MaxFilesPerInstance; i++) {
|
||||
if (!self.files[i].inUse) {
|
||||
self.files[i].inUse = true;
|
||||
@@ -1234,6 +1241,31 @@ namespace Fs::Ext2 {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int SetTimesImpl(int inst, const char* path, int64_t atime, int64_t mtime) {
|
||||
if (InstanceAt(inst) == nullptr) return -4;
|
||||
if (atime < 0 || atime > 0xFFFFFFFFLL ||
|
||||
mtime < 0 || mtime > 0xFFFFFFFFLL) return -3;
|
||||
auto& self = g_instances[inst];
|
||||
|
||||
uint32_t inodeNum;
|
||||
Inode inode;
|
||||
if (!TraversePath(self, path, &inodeNum, &inode)) return -1;
|
||||
|
||||
inode.i_atime = (uint32_t)atime;
|
||||
inode.i_mtime = (uint32_t)mtime;
|
||||
inode.i_ctime = Ext2Now();
|
||||
if (!WriteInode(self, inodeNum, &inode)) return -4;
|
||||
|
||||
// Open files cache their inode. Keep every alias coherent so a later
|
||||
// write through an existing handle cannot restore stale metadata.
|
||||
for (int i = 0; i < MaxFilesPerInstance; i++) {
|
||||
if (self.files[i].inUse && self.files[i].inodeNum == inodeNum) {
|
||||
self.files[i].inode = inode;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ReadImpl(int inst, int handle, uint8_t* buffer,
|
||||
uint64_t offset, uint64_t size) {
|
||||
if (InstanceAt(inst) == nullptr) return -1;
|
||||
@@ -1830,6 +1862,7 @@ namespace Fs::Ext2 {
|
||||
static int DrvRename(void* c, const char* o, const char* n) { return RenameImpl(CtxToInst(c), o, n); }
|
||||
static const char* DrvGetLabel(void* c) { return GetLabelImpl(CtxToInst(c)); }
|
||||
static int DrvStat(void* c, const char* p, Vfs::StatInfo* o) { return StatImpl(CtxToInst(c), p, o); }
|
||||
static int DrvSetTimes(void* c, const char* p, int64_t a, int64_t m) { return SetTimesImpl(CtxToInst(c), p, a, m); }
|
||||
|
||||
// Release everything the mount owns. The driver is either not registered
|
||||
// yet, or the VFS has deactivated its drive and drained dispatches.
|
||||
@@ -2040,6 +2073,7 @@ namespace Fs::Ext2 {
|
||||
.GetLabel = DrvGetLabel,
|
||||
.ReadDirAt = DrvReadDirAt,
|
||||
.Stat = DrvStat,
|
||||
.SetTimes = DrvSetTimes,
|
||||
.Unmount = DrvUnmount,
|
||||
};
|
||||
|
||||
|
||||
@@ -955,6 +955,10 @@ namespace Fs::Fat32 {
|
||||
ParsedEntry entry;
|
||||
if (!TraversePath(inst, path, &entry)) return -1;
|
||||
|
||||
// Directories are not openable as files; see the matching note in
|
||||
// Ext2::OpenImpl (userspace stat() falls back to open()).
|
||||
if ((entry.attributes & ATTR_DIRECTORY) != 0) return -1;
|
||||
|
||||
// Find a free file handle
|
||||
auto& self = g_instances[inst];
|
||||
for (int i = 0; i < MaxFilesPerInstance; i++) {
|
||||
@@ -2099,6 +2103,7 @@ namespace Fs::Fat32 {
|
||||
.GetLabel = DrvGetLabel,
|
||||
.ReadDirAt = DrvReadDirAt,
|
||||
.Stat = DrvStat,
|
||||
.SetTimes = nullptr,
|
||||
.Unmount = DrvUnmount,
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
/*
|
||||
* ProtectedPaths.cpp
|
||||
* Capability required to modify paths on the booted system volume
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*
|
||||
* Split out of Ipc.cpp: this is filesystem security policy, not IPC. It
|
||||
* lived there only because OpenFileHandleForSlot was its first caller.
|
||||
*/
|
||||
|
||||
#include "ProtectedPaths.hpp"
|
||||
|
||||
#include <Api/Syscall.hpp>
|
||||
#include <Terminal/Terminal.hpp>
|
||||
|
||||
namespace Fs {
|
||||
|
||||
// ==== Protected system paths ====
|
||||
// The capability required to modify (create, write, delete or rename) a
|
||||
// path. The kernel only ever enumerates paths here; it never parses a
|
||||
// policy file. Userspace grant policy lives in 0:/config/capabilities.toml
|
||||
// and can only ever narrow what the kernel already delegated, so no input
|
||||
// to that file can produce authority this table does not already allow.
|
||||
//
|
||||
// Rules apply ONLY to the system volume. Drive 0 is always the boot
|
||||
// ramdisk (Fs/Boot.cpp registers it unconditionally); every other drive is
|
||||
// a partition discovered at probe time, in probe order. A user data disk
|
||||
// that happens to contain an "apps" or "config" directory must not inherit
|
||||
// system protection, and an installed system's files on another volume are
|
||||
// inert data until that disk is booted -- at which point its contents are
|
||||
// themselves the drive-0 ramdisk.
|
||||
// Anything the kernel itself reads belongs here:
|
||||
// guarding only the syscall leaves the file as an unguarded second path to
|
||||
// the same state (bluetooth.toml feeds the BD_ADDR override at bring-up).
|
||||
struct ProtectedPath {
|
||||
const char* pattern; // drive-relative, leading '/'
|
||||
bool prefix; // also match everything beneath the pattern
|
||||
uint64_t capability;
|
||||
};
|
||||
|
||||
static constexpr ProtectedPath g_protectedPaths[] = {
|
||||
// Authentication, first-boot administrator creation, trusted service
|
||||
// activation and capability grants. Readable by anyone; writable only
|
||||
// with administrative authority. The bare directory is listed so it
|
||||
// cannot be renamed or deleted out from under the files inside it.
|
||||
{"/config", false, montauk::abi::CAP_USER_ADMIN},
|
||||
{"/config/users.toml", false, montauk::abi::CAP_USER_ADMIN},
|
||||
{"/config/setup.toml", false, montauk::abi::CAP_USER_ADMIN},
|
||||
{"/config/init.toml", false, montauk::abi::CAP_USER_ADMIN},
|
||||
{"/config/ssh.toml", false, montauk::abi::CAP_USER_ADMIN},
|
||||
{"/config/capabilities.toml",false, montauk::abi::CAP_USER_ADMIN},
|
||||
// Pre-scaled wallpaper the login screen blits before it has decoded
|
||||
// anything. It is drawn on a screen that is about to take a password,
|
||||
// so it must not be plantable by an unprivileged process.
|
||||
{"/config/wallpaper.cache", false, montauk::abi::CAP_USER_ADMIN},
|
||||
// Read by the Bluetooth driver at controller bring-up.
|
||||
{"/config/bluetooth.toml", false, montauk::abi::CAP_DEVICE_ADMIN},
|
||||
// Program images. Capability grants are keyed on binary path, so a
|
||||
// writable image would let an unprivileged process substitute a binary
|
||||
// and inherit the grant the next time a privileged launcher runs it.
|
||||
// This is CAP_SYSTEM_IMAGE and not CAP_STORAGE_ADMIN precisely because
|
||||
// it is the trusted computing base: partitioning and formatting a data
|
||||
// volume is an ordinary administrative act, while replacing the image
|
||||
// of login.elf is a route to every capability the system can issue.
|
||||
{"/apps", true, montauk::abi::CAP_SYSTEM_IMAGE},
|
||||
{"/os", true, montauk::abi::CAP_SYSTEM_IMAGE},
|
||||
};
|
||||
|
||||
static char LowerAscii(char c) {
|
||||
return (c >= 'A' && c <= 'Z') ? (char)(c + ('a' - 'A')) : c;
|
||||
}
|
||||
|
||||
// The volume the running system was booted from.
|
||||
static constexpr uint64_t SystemDrive = 0;
|
||||
|
||||
// Strip the "<digits>:" prefix, but only for the system volume. Returns
|
||||
// nullptr for any other drive, meaning no rule applies to it.
|
||||
static const char* SystemRelativePath(const char* path) {
|
||||
if (path == nullptr) return nullptr;
|
||||
|
||||
const char* p = path;
|
||||
if (*p < '0' || *p > '9') return nullptr;
|
||||
|
||||
uint64_t drive = 0;
|
||||
while (*p >= '0' && *p <= '9') {
|
||||
drive = drive * 10 + (uint64_t)(*p - '0');
|
||||
if (drive > 0xFFFF) return nullptr; // absurd; cannot be a drive
|
||||
p++;
|
||||
}
|
||||
if (*p != ':' || drive != SystemDrive) return nullptr;
|
||||
return p + 1;
|
||||
}
|
||||
|
||||
// Case-insensitive: FAT32 resolves differing cases to the same file, so a
|
||||
// case-sensitive rule would be trivially sidestepped.
|
||||
static bool ProtectedPathMatches(const char* path, const ProtectedPath& rule) {
|
||||
const char* p = path;
|
||||
const char* q = rule.pattern;
|
||||
while (*q) {
|
||||
if (LowerAscii(*p) != LowerAscii(*q)) return false;
|
||||
p++;
|
||||
q++;
|
||||
}
|
||||
if (*p == '\0') return true; // the pattern itself
|
||||
return rule.prefix && *p == '/'; // something beneath it
|
||||
}
|
||||
|
||||
uint64_t RequiredFileWriteCapability(const char* path) {
|
||||
const char* relative = SystemRelativePath(path);
|
||||
if (relative == nullptr) return 0; // not the system volume
|
||||
|
||||
// Overlapping rules accumulate: HasCapability() requires every bit, so
|
||||
// a path covered by two rules demands both.
|
||||
uint64_t required = 0;
|
||||
for (const auto& rule : g_protectedPaths) {
|
||||
if (ProtectedPathMatches(relative, rule)) required |= rule.capability;
|
||||
}
|
||||
return required;
|
||||
}
|
||||
|
||||
void LogProtectedPaths() {
|
||||
for (const auto& rule : g_protectedPaths) {
|
||||
Kt::KernelLogStream(Kt::INFO, "IPC") << "Protected path "
|
||||
<< rule.pattern << (rule.prefix ? "/* " : " ")
|
||||
<< "requires capability mask "
|
||||
<< kcp::hex << rule.capability << kcp::dec;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* ProtectedPaths.hpp
|
||||
* Capability required to modify paths on the booted system volume
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
|
||||
namespace Fs {
|
||||
|
||||
// Capability a process must hold to create, write, delete, rename or
|
||||
// re-timestamp `path`. Returns 0 when the path is unprotected.
|
||||
uint64_t RequiredFileWriteCapability(const char* path);
|
||||
|
||||
// Log the rule table at boot, so a path that should be protected and is
|
||||
// not is visible rather than silently missing.
|
||||
void LogProtectedPaths();
|
||||
|
||||
}
|
||||
+33
-11
@@ -262,6 +262,10 @@ namespace Fs::Ramdisk {
|
||||
}
|
||||
|
||||
int ReadDirAt(const char* path, const char** outNames, int maxEntries, int startIndex) {
|
||||
if (path == nullptr || outNames == nullptr || maxEntries < 0 || startIndex < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Reject outright if path names an existing regular file: without this,
|
||||
// a file with no matching "path/..." children below falls through to
|
||||
// the child-scan loop and returns 0 (empty), which callers that use
|
||||
@@ -269,10 +273,10 @@ namespace Fs::Ramdisk {
|
||||
int selfIdx = FindEntryByPath(path);
|
||||
if (selfIdx >= 0 && !fileTable[selfIdx].isDirectory) return -1;
|
||||
|
||||
// Normalize path: skip leading '/'
|
||||
if (path[0] == '/') path++;
|
||||
|
||||
int pathLen = StrLen(path);
|
||||
char normalized[MaxNameLen];
|
||||
int pathLen = NormalizePath(path, normalized, MaxNameLen);
|
||||
if (pathLen < 0) return -1;
|
||||
path = normalized;
|
||||
int count = 0; // entries written to outNames
|
||||
int seen = 0; // matching direct children scanned so far (for startIndex skip)
|
||||
|
||||
@@ -280,6 +284,7 @@ namespace Fs::Ramdisk {
|
||||
const char* entryName = fileTable[i].name;
|
||||
bool isChild = false;
|
||||
|
||||
const char* childName = entryName;
|
||||
if (pathLen == 0) {
|
||||
// Root directory: find entries without '/' in them (or only trailing '/')
|
||||
bool hasSlash = false;
|
||||
@@ -311,11 +316,14 @@ namespace Fs::Ramdisk {
|
||||
}
|
||||
}
|
||||
isChild = (!hasDeepSlash && restLen > 0);
|
||||
childName = rest;
|
||||
}
|
||||
|
||||
if (!isChild) continue;
|
||||
if (seen++ < startIndex) continue; // skip entries before the requested page
|
||||
outNames[count++] = entryName;
|
||||
// ReadDir follows the same contract as ext2/FAT32: return the
|
||||
// direct child's name, not its archive path relative to root.
|
||||
outNames[count++] = childName;
|
||||
}
|
||||
|
||||
// A path with no directory entry and no children does not exist.
|
||||
@@ -347,10 +355,18 @@ namespace Fs::Ramdisk {
|
||||
uint64_t newCap = entry.size;
|
||||
if (endOffset > newCap) newCap = endOffset;
|
||||
if (newCap < 256) newCap = 256;
|
||||
// Round up to next power of 2 for growth
|
||||
uint64_t rounded = 256;
|
||||
while (rounded < newCap) rounded *= 2;
|
||||
newCap = rounded;
|
||||
// Small files round to the next power of 2, so an appender grows
|
||||
// in a few steps. Large ones round to a page instead: the kernel
|
||||
// heap grows in physically contiguous runs, and doubling an 8 MiB
|
||||
// write into a 16 MiB block asks the frame allocator for twice the
|
||||
// contiguous span the file actually needs.
|
||||
if (newCap < 64 * 1024) {
|
||||
uint64_t rounded = 256;
|
||||
while (rounded < newCap) rounded *= 2;
|
||||
newCap = rounded;
|
||||
} else {
|
||||
newCap = (newCap + 0xFFFULL) & ~0xFFFULL;
|
||||
}
|
||||
|
||||
uint8_t* newBuf = (uint8_t*)Memory::g_heap->Request(newCap);
|
||||
if (newBuf == nullptr) return -1;
|
||||
@@ -366,8 +382,14 @@ namespace Fs::Ramdisk {
|
||||
|
||||
// Grow buffer if needed
|
||||
if (endOffset > entry.capacity) {
|
||||
uint64_t newCap = entry.capacity;
|
||||
while (newCap < endOffset) newCap *= 2;
|
||||
// Double while small, then grow in fixed 1 MiB steps. Doubling all
|
||||
// the way keeps growth amortized but overshoots badly on multi-MiB
|
||||
// files, and every byte of overshoot is a physically contiguous
|
||||
// kernel-heap run this file holds for the rest of the boot.
|
||||
static constexpr uint64_t MaxGrowStep = 1024 * 1024;
|
||||
uint64_t newCap = entry.capacity < 256 ? 256 : entry.capacity;
|
||||
while (newCap < endOffset)
|
||||
newCap += (newCap < MaxGrowStep) ? newCap : MaxGrowStep;
|
||||
|
||||
uint8_t* newBuf = (uint8_t*)Memory::g_heap->Request(newCap);
|
||||
if (newBuf == nullptr) return -1;
|
||||
|
||||
@@ -307,6 +307,21 @@ namespace Fs::Vfs {
|
||||
return result;
|
||||
}
|
||||
|
||||
int VfsSetTimes(const char* path, int64_t atime, int64_t mtime) {
|
||||
int drive;
|
||||
const char* localPath;
|
||||
|
||||
if (!ParsePath(path, drive, localPath)) return -1;
|
||||
|
||||
vfsLock.Acquire();
|
||||
FsDriver* driver = DriverForLocked(drive);
|
||||
int result = (driver != nullptr && driver->SetTimes != nullptr)
|
||||
? driver->SetTimes(driver->ctx, localPath, atime, mtime)
|
||||
: -2;
|
||||
vfsLock.Release();
|
||||
return result;
|
||||
}
|
||||
|
||||
int VfsMkdir(const char* path) {
|
||||
int drive;
|
||||
const char* localPath;
|
||||
|
||||
@@ -65,6 +65,10 @@ namespace Fs::Vfs {
|
||||
// Optional: fill metadata for a path. Drivers that leave this null do
|
||||
// not support stat and VfsStat returns -1 for their paths.
|
||||
int (*Stat)(void* ctx, const char* path, StatInfo* out);
|
||||
// Optional: set access and modification times for a path. Return 0 on
|
||||
// success, -1 for a missing path, -3 for an unrepresentable time, or
|
||||
// -4 for an I/O error. A null hook yields -2 (unsupported).
|
||||
int (*SetTimes)(void* ctx, const char* path, int64_t atime, int64_t mtime);
|
||||
// Optional: release everything this mount owns, including the FsDriver
|
||||
// itself. Called either when registration fails, before the driver has
|
||||
// become reachable, or by UnregisterDrive once the slot is deactivated
|
||||
@@ -90,6 +94,7 @@ namespace Fs::Vfs {
|
||||
|
||||
int VfsDelete(const char* path);
|
||||
int VfsStat(const char* path, StatInfo& out);
|
||||
int VfsSetTimes(const char* path, int64_t atime, int64_t mtime);
|
||||
int VfsReadDir(const char* path, const char** outNames, int maxEntries);
|
||||
int VfsReadDirAt(const char* path, const char** outNames, int maxEntries, int startIndex);
|
||||
int VfsMkdir(const char* path);
|
||||
|
||||
+22
-4
@@ -13,6 +13,7 @@
|
||||
#include <Memory/PageFrameAllocator.hpp>
|
||||
#include <Sched/Scheduler.hpp>
|
||||
#include <Sched/CrashReport.hpp>
|
||||
#include <Api/Heap.hpp>
|
||||
#include <Hal/SmpBoot.hpp>
|
||||
#include <Timekeeping/ApicTimer.hpp>
|
||||
|
||||
@@ -126,6 +127,21 @@ namespace Hal {
|
||||
rep.stackPointer = regs->SP;
|
||||
rep.stackSegment = regs->SS;
|
||||
|
||||
// An indirect CALL pushes its real continuation address before a
|
||||
// bad target faults on instruction fetch. Preserve a small stack
|
||||
// snapshot while the dying process address space is still active;
|
||||
// stackWords[0] will usually identify the caller even when RIP is
|
||||
// corrupted data and cannot itself be symbolicated.
|
||||
constexpr uint64_t snapshotBytes =
|
||||
CrashReport::StackSnapshotWords * sizeof(uint64_t);
|
||||
if (Memory::VMM::Paging::IsUserRangeAccessible(
|
||||
proc->pml4Phys, regs->SP, snapshotBytes, false)) {
|
||||
auto* userStack = (const volatile uint64_t*)regs->SP;
|
||||
for (int word = 0; word < CrashReport::StackSnapshotWords; word++)
|
||||
rep.stackWords[word] = userStack[word];
|
||||
rep.stackWordCount = CrashReport::StackSnapshotWords;
|
||||
}
|
||||
|
||||
if (i == 0x0E) {
|
||||
// Page fault: read CR2 for faulting address
|
||||
asm volatile("mov %%cr2, %0" : "=r"(rep.faultingAddress));
|
||||
@@ -201,10 +217,12 @@ namespace Hal {
|
||||
// pushes past the mapped stack and kernel accesses to not-yet-grown
|
||||
// user stack buffers passed into syscalls.
|
||||
if ((errorCode & 1) == 0 && cpu != nullptr && cpu->currentSlot >= 0
|
||||
&& Sched::GetCurrentPid() >= 0
|
||||
&& Sched::TryGrowUserStack(cr2)) {
|
||||
if (fromUser) asm volatile("swapgs");
|
||||
return;
|
||||
&& Sched::GetCurrentPid() >= 0) {
|
||||
if (montauk::abi::TryHandleAnonymousPageFault(cr2, errorCode) ||
|
||||
Sched::TryGrowUserStack(cr2)) {
|
||||
if (fromUser) asm volatile("swapgs");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Not a growable fault. Hand the RAW frame (error code at offset 0)
|
||||
|
||||
@@ -188,7 +188,7 @@ namespace Smp {
|
||||
|
||||
// --- Program SYSCALL MSRs ---
|
||||
uint64_t efer = Hal::ReadMSR(Hal::IA32_EFER);
|
||||
efer |= 1; // SCE
|
||||
efer |= 1 | (1ULL << 11); // SCE | NXE
|
||||
Hal::WriteMSR(Hal::IA32_EFER, efer);
|
||||
|
||||
uint64_t star = (0x0010ULL << 48) | (0x0008ULL << 32);
|
||||
@@ -218,6 +218,10 @@ namespace Smp {
|
||||
for (;;) {
|
||||
// Pick up thermal-governor frequency changes decided by the BSP.
|
||||
Hal::CpuPower::ApplyPolicyIfChanged();
|
||||
// Runnable work sends this AP a reschedule IPI. Keep its periodic
|
||||
// scheduler tick masked for the entire idle-context pass so long
|
||||
// firmware waits do not keep generating useless timer interrupts.
|
||||
Timekeeping::ApicTimerEnterApIdle();
|
||||
// Any idle core may run bounded USB/NIC bottom halves. Preserve
|
||||
// the AP's ACPI/MWAIT idle selection after servicing them.
|
||||
Timekeeping::ServiceDeferredWork();
|
||||
|
||||
+83
-150
@@ -8,11 +8,13 @@
|
||||
|
||||
#include <Sched/Scheduler.hpp>
|
||||
#include <Fs/Vfs.hpp>
|
||||
#include <Fs/ProtectedPaths.hpp>
|
||||
#include <Net/Tcp.hpp>
|
||||
#include <Net/Udp.hpp>
|
||||
#include <Memory/PageFrameAllocator.hpp>
|
||||
#include <Memory/HHDM.hpp>
|
||||
#include <Memory/Paging.hpp>
|
||||
#include <Memory/UserRange.hpp>
|
||||
#include <Libraries/Memory.hpp>
|
||||
#include <CppLib/Spinlock.hpp>
|
||||
#include <Hal/Apic/Apic.hpp>
|
||||
@@ -82,6 +84,7 @@ namespace Ipc {
|
||||
|
||||
struct File : Object {
|
||||
Fs::Vfs::BackendFile backend;
|
||||
uint64_t writeCapability;
|
||||
};
|
||||
|
||||
struct UdpDgramHeader {
|
||||
@@ -178,149 +181,11 @@ namespace Ipc {
|
||||
|
||||
static void ReleaseRawObject(Object* object);
|
||||
|
||||
// MUST be a Mutex, never a Spinlock. ShootdownUserRange holds this while
|
||||
// waiting for remote CPUs to acknowledge the shootdown IPI, so a CPU that
|
||||
// is queued behind the holder has to stay interruptible long enough to
|
||||
// service that IPI itself. An interrupt-disabling Spinlock here deadlocks
|
||||
// every CPU contending for the lock, and the bounded-retry logic below
|
||||
// then reports it as a "target failed to acknowledge" Panic -- which reads
|
||||
// like a hardware fault rather than a lock-type regression.
|
||||
static kcp::Mutex g_tlbShootdownLock;
|
||||
static volatile uint64_t g_tlbShootdownSeq = 0;
|
||||
static volatile uint64_t g_tlbShootdownPml4 = 0;
|
||||
static volatile uint64_t g_tlbShootdownStartVa = 0;
|
||||
static volatile uint32_t g_tlbShootdownPages = 0;
|
||||
static volatile uint64_t g_tlbShootdownDone[Smp::MaxCPUs] = {};
|
||||
|
||||
static bool CpuCurrentlyUsesPml4(Smp::CpuData* cpu, uint64_t pml4Phys) {
|
||||
if (cpu == nullptr || pml4Phys == 0 || cpu->currentSlot < 0) return false;
|
||||
|
||||
Sched::Process* proc = Sched::GetProcessSlot(cpu->currentSlot);
|
||||
if (proc == nullptr) return false;
|
||||
if (proc->state == Sched::ProcessState::Free) return false;
|
||||
return proc->pml4Phys == pml4Phys;
|
||||
}
|
||||
|
||||
static void InvalidateLocalUserRange(uint64_t startVa, uint32_t pages) {
|
||||
if (pages == 0) return;
|
||||
|
||||
if (pages > 1024) {
|
||||
Memory::VMM::FlushTLB();
|
||||
return;
|
||||
}
|
||||
|
||||
for (uint32_t p = 0; p < pages; p++) {
|
||||
uint64_t va = startVa + (uint64_t)p * 0x1000ULL;
|
||||
asm volatile("invlpg (%0)" :: "r"(va) : "memory");
|
||||
}
|
||||
}
|
||||
|
||||
static void TlbShootdownIpiHandler(uint8_t, bool) {
|
||||
Smp::CpuData* cpu = Smp::GetCurrentCpuData();
|
||||
uint64_t seq = g_tlbShootdownSeq;
|
||||
uint64_t pml4 = g_tlbShootdownPml4;
|
||||
uint64_t startVa = g_tlbShootdownStartVa;
|
||||
uint32_t pages = g_tlbShootdownPages;
|
||||
|
||||
if (CpuCurrentlyUsesPml4(cpu, pml4)) {
|
||||
InvalidateLocalUserRange(startVa, pages);
|
||||
}
|
||||
|
||||
if (cpu != nullptr && cpu->cpuIndex >= 0 && cpu->cpuIndex < Smp::MaxCPUs) {
|
||||
asm volatile("" ::: "memory");
|
||||
g_tlbShootdownDone[cpu->cpuIndex] = seq;
|
||||
}
|
||||
}
|
||||
|
||||
void ShootdownUserRange(uint64_t pml4Phys, uint64_t startVa, uint32_t pages) {
|
||||
if (pml4Phys == 0 || pages == 0) return;
|
||||
|
||||
bool targets[Smp::MaxCPUs] = {};
|
||||
Smp::CpuData* currentCpu = Smp::GetCurrentCpuData();
|
||||
int currentCpuIndex = currentCpu ? currentCpu->cpuIndex : -1;
|
||||
|
||||
g_tlbShootdownLock.Acquire();
|
||||
uint64_t seq = g_tlbShootdownSeq + 1;
|
||||
g_tlbShootdownPml4 = pml4Phys;
|
||||
g_tlbShootdownStartVa = startVa;
|
||||
g_tlbShootdownPages = pages;
|
||||
asm volatile("" ::: "memory");
|
||||
g_tlbShootdownSeq = seq;
|
||||
|
||||
for (int i = 0; i < Smp::GetCpuCount(); i++) {
|
||||
Smp::CpuData* cpu = Smp::GetCpuData(i);
|
||||
if (cpu == nullptr || !cpu->started) continue;
|
||||
|
||||
if (i == currentCpuIndex) {
|
||||
if (CpuCurrentlyUsesPml4(cpu, pml4Phys)) {
|
||||
InvalidateLocalUserRange(startVa, pages);
|
||||
}
|
||||
g_tlbShootdownDone[i] = seq;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!CpuCurrentlyUsesPml4(cpu, pml4Phys)) {
|
||||
g_tlbShootdownDone[i] = seq;
|
||||
continue;
|
||||
}
|
||||
|
||||
targets[i] = true;
|
||||
(void)Hal::LocalApic::SendFixedIpi(cpu->lapicId,
|
||||
Hal::IRQ_VECTOR_BASE + Hal::IRQ_TLB_SHOOTDOWN);
|
||||
}
|
||||
|
||||
for (int i = 0; i < Smp::GetCpuCount(); i++) {
|
||||
if (!targets[i]) continue;
|
||||
uint32_t spins = 0;
|
||||
uint32_t retries = 0;
|
||||
while (g_tlbShootdownDone[i] != seq) {
|
||||
asm volatile("pause");
|
||||
if (++spins < 1000000) continue;
|
||||
|
||||
// Delivery normally completes in a handful of cycles. Retry a
|
||||
// bounded number of times in case the first IPI was lost while
|
||||
// the target changed interrupt state. Continuing without an
|
||||
// acknowledgement would let the caller free frames still
|
||||
// reachable through a remote stale TLB entry, so fail loudly
|
||||
// instead of either corrupting memory or spinning forever.
|
||||
spins = 0;
|
||||
if (++retries > 4) {
|
||||
Panic("TLB shootdown target failed to acknowledge", nullptr);
|
||||
}
|
||||
Smp::CpuData* cpu = Smp::GetCpuData(i);
|
||||
if (cpu != nullptr && cpu->started) {
|
||||
(void)Hal::LocalApic::SendFixedIpi(cpu->lapicId,
|
||||
Hal::IRQ_VECTOR_BASE + Hal::IRQ_TLB_SHOOTDOWN);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
g_tlbShootdownLock.Release();
|
||||
}
|
||||
|
||||
void UnmapAndFreeUserRange(uint64_t pml4Phys, uint64_t startVa, uint64_t pages) {
|
||||
static constexpr uint32_t PagesPerChunk = 64;
|
||||
uint64_t physPages[PagesPerChunk];
|
||||
|
||||
for (uint64_t base = 0; base < pages; base += PagesPerChunk) {
|
||||
uint32_t count = (uint32_t)((pages - base > PagesPerChunk)
|
||||
? PagesPerChunk : pages - base);
|
||||
|
||||
for (uint32_t i = 0; i < count; i++) {
|
||||
uint64_t pageVa = startVa + (base + i) * 0x1000ULL;
|
||||
physPages[i] = Memory::VMM::Paging::GetPhysAddr(pml4Phys, pageVa);
|
||||
Memory::VMM::Paging::UnmapUserIn(pml4Phys, pageVa);
|
||||
}
|
||||
|
||||
ShootdownUserRange(pml4Phys, startVa + base * 0x1000ULL, count);
|
||||
|
||||
for (uint32_t i = 0; i < count; i++) {
|
||||
if (physPages[i] != 0) {
|
||||
Memory::g_pfa->Free((void*)Memory::HHDM(physPages[i]));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// ==========================================================================
|
||||
// Object lifetime
|
||||
// Pool allocation, refcounting and type-dispatched teardown.
|
||||
// Every object type routes through this layer.
|
||||
// ==========================================================================
|
||||
|
||||
static void InitObject(Object& object, HandleType type) {
|
||||
object.type = type;
|
||||
@@ -568,6 +433,11 @@ namespace Ipc {
|
||||
}
|
||||
}
|
||||
|
||||
// ==========================================================================
|
||||
// Handle table
|
||||
// Per-process handle installation, rights, duplication and close.
|
||||
// ==========================================================================
|
||||
|
||||
int CurrentSlot() {
|
||||
auto* proc = Sched::GetCurrentProcessPtr();
|
||||
if (proc == nullptr) return -1;
|
||||
@@ -714,8 +584,17 @@ namespace Ipc {
|
||||
g_handleTableLocks[slot].Acquire();
|
||||
uint64_t& bm0 = g_handleBitmaps[slot][0];
|
||||
uint64_t& bm1 = g_handleBitmaps[slot][1];
|
||||
// Find first zero bit in bitmap (free slot)
|
||||
uint64_t bits0 = ~bm0;
|
||||
// Find first zero bit in bitmap (free slot).
|
||||
//
|
||||
// Handles 0, 1 and 2 are never allocated: to POSIX code those numbers
|
||||
// ARE stdin/stdout/stderr, and the libc routes read()/write() on them
|
||||
// to the terminal (SYS_GETCHAR/SYS_PUTCHAR) rather than to this table.
|
||||
// Handing them out as file handles makes the two meanings collide --
|
||||
// a program's third open() would own "stderr", and every write(2, ...)
|
||||
// it made would land in that file instead of on screen. Reserving them
|
||||
// here rather than at process setup keeps the rule in one place and
|
||||
// immune to a stale bitmap.
|
||||
uint64_t bits0 = ~bm0 & ~0x7ULL;
|
||||
uint64_t bits1 = ~bm1;
|
||||
if (bits0) {
|
||||
int i = __builtin_ctzll(bits0);
|
||||
@@ -808,6 +687,11 @@ namespace Ipc {
|
||||
return InstallHandleForSlot(slot, snapshot.object, snapshot.type, snapshot.rights);
|
||||
}
|
||||
|
||||
// ==========================================================================
|
||||
// Streams
|
||||
// Byte pipes.
|
||||
// ==========================================================================
|
||||
|
||||
Stream* CreateStream(uint32_t capacity) {
|
||||
if (capacity == 0) capacity = DefaultStreamCapacity;
|
||||
|
||||
@@ -991,6 +875,11 @@ namespace Ipc {
|
||||
return hasData;
|
||||
}
|
||||
|
||||
// ==========================================================================
|
||||
// Mailboxes
|
||||
// Discrete message queues.
|
||||
// ==========================================================================
|
||||
|
||||
Mailbox* CreateMailbox() {
|
||||
g_mailboxPoolLock.Acquire();
|
||||
for (int i = 0; i < MaxMailboxes; i++) {
|
||||
@@ -1251,9 +1140,22 @@ namespace Ipc {
|
||||
return hasMsg;
|
||||
}
|
||||
|
||||
// ==========================================================================
|
||||
// Files
|
||||
// Write authority is re-checked against the calling process on every
|
||||
// write, so passing a writable handle to a less privileged process does
|
||||
// not transfer the ability to use it. See Fs/ProtectedPaths.cpp.
|
||||
// ==========================================================================
|
||||
|
||||
int OpenFileHandleForSlot(int slot, const char* path, bool create) {
|
||||
if (slot < 0 || slot >= Sched::MaxProcesses || path == nullptr) return -1;
|
||||
|
||||
uint64_t writeCapability = Fs::RequiredFileWriteCapability(path);
|
||||
if (create && writeCapability != 0 &&
|
||||
!Sched::HasCapability(writeCapability)) {
|
||||
return montauk::abi::SYS_ERR_PERMISSION;
|
||||
}
|
||||
|
||||
Fs::Vfs::BackendFile backend = {-1, -1, 0};
|
||||
int result = create ? Fs::Vfs::CreateBackendFile(path, backend)
|
||||
: Fs::Vfs::OpenBackendFile(path, backend);
|
||||
@@ -1264,6 +1166,7 @@ namespace Ipc {
|
||||
if (g_files[i].active || g_files[i].destroying) continue;
|
||||
InitObject(g_files[i], HandleType::File);
|
||||
g_files[i].backend = backend;
|
||||
g_files[i].writeCapability = writeCapability;
|
||||
g_filePoolLock.Release();
|
||||
|
||||
uint32_t rights = RightRead | RightWait | RightDup;
|
||||
@@ -1309,7 +1212,12 @@ namespace Ipc {
|
||||
HandleSnapshot snapshot;
|
||||
if (!snapshot.Capture(CurrentSlot(), handle)) return -1;
|
||||
if (snapshot.type != HandleType::File || (snapshot.rights & RightWrite) == 0) return -1;
|
||||
return Fs::Vfs::WriteBackendFile(((File*)snapshot.object)->backend, buffer, offset, size);
|
||||
File* file = (File*)snapshot.object;
|
||||
if (file->writeCapability != 0 &&
|
||||
!Sched::HasCapability(file->writeCapability)) {
|
||||
return montauk::abi::SYS_ERR_PERMISSION;
|
||||
}
|
||||
return Fs::Vfs::WriteBackendFile(file->backend, buffer, offset, size);
|
||||
}
|
||||
|
||||
uint64_t FileGetSizeHandle(int handle) {
|
||||
@@ -1319,6 +1227,11 @@ namespace Ipc {
|
||||
return Fs::Vfs::GetBackendFileSize(((File*)snapshot.object)->backend);
|
||||
}
|
||||
|
||||
// ==========================================================================
|
||||
// Sockets
|
||||
// TCP and UDP endpoints.
|
||||
// ==========================================================================
|
||||
|
||||
static Socket* AllocateSocketObject(int type) {
|
||||
g_socketPoolLock.Acquire();
|
||||
for (int i = 0; i < MaxSockets; i++) {
|
||||
@@ -1654,6 +1567,13 @@ namespace Ipc {
|
||||
}
|
||||
}
|
||||
|
||||
// ==========================================================================
|
||||
// Surfaces
|
||||
// Shared pixel buffers mapped into a client address space.
|
||||
// Pages MUST be unmapped from the owner before being freed, or
|
||||
// FreeUserHalf() double-frees them on process exit.
|
||||
// ==========================================================================
|
||||
|
||||
Surface* CreateSurface(uint64_t byteSize) {
|
||||
if (byteSize == 0) byteSize = 0x1000;
|
||||
uint32_t numPages = (uint32_t)((byteSize + 0xFFFu) / 0x1000u);
|
||||
@@ -1739,7 +1659,7 @@ namespace Ipc {
|
||||
for (uint32_t p = m.numPages; p < newPages; p++) {
|
||||
Memory::VMM::Paging::UnmapUserIn(pml4, m.va + (uint64_t)p * 0x1000ULL);
|
||||
}
|
||||
ShootdownUserRange(pml4, startVa, rollbackPages);
|
||||
Memory::ShootdownUserRange(pml4, startVa, rollbackPages);
|
||||
}
|
||||
g_surfaceMapLocks[s].Release();
|
||||
}
|
||||
@@ -1891,7 +1811,7 @@ namespace Ipc {
|
||||
Memory::VMM::Paging::UnmapUserIn(pml4, va);
|
||||
}
|
||||
|
||||
ShootdownUserRange(pml4, baseVa, flushPages);
|
||||
Memory::ShootdownUserRange(pml4, baseVa, flushPages);
|
||||
m.numPages = newPages;
|
||||
}
|
||||
g_surfaceMapLocks[s].Release();
|
||||
@@ -2102,7 +2022,7 @@ namespace Ipc {
|
||||
// releasing the mapping reference can then destroy the surface
|
||||
// and recycle its frames while that sibling writes through its
|
||||
// stale TLB entry. Quiesce every CPU using this PML4 first.
|
||||
ShootdownUserRange(pml4Phys, baseVa, numPages);
|
||||
Memory::ShootdownUserRange(pml4Phys, baseVa, numPages);
|
||||
|
||||
g_surfaceMaps[slot][i].used = false;
|
||||
g_surfaceMaps[slot][i].surface = nullptr;
|
||||
@@ -2118,6 +2038,11 @@ namespace Ipc {
|
||||
return unmapped > 0 ? 0 : -1;
|
||||
}
|
||||
|
||||
// ==========================================================================
|
||||
// Process handles
|
||||
// Wait-only references to a live process.
|
||||
// ==========================================================================
|
||||
|
||||
int OpenProcessHandle(int pid) {
|
||||
g_processPoolLock.Acquire();
|
||||
for (int i = 0; i < MaxProcessObjects; i++) {
|
||||
@@ -2174,6 +2099,11 @@ namespace Ipc {
|
||||
return exited;
|
||||
}
|
||||
|
||||
// ==========================================================================
|
||||
// Signals and waitsets
|
||||
// Readiness computation and multiplexed waiting.
|
||||
// ==========================================================================
|
||||
|
||||
static uint32_t CurrentSocketSignals(Socket* socket, uint32_t rights) {
|
||||
if (socket == nullptr) return SignalNone;
|
||||
|
||||
@@ -2504,6 +2434,10 @@ namespace Ipc {
|
||||
}
|
||||
}
|
||||
|
||||
// ==========================================================================
|
||||
// Teardown and init
|
||||
// ==========================================================================
|
||||
|
||||
void CleanupProcessSlot(int slot, int /*pid*/, uint64_t pml4Phys) {
|
||||
if (slot < 0 || slot >= Sched::MaxProcesses) return;
|
||||
|
||||
@@ -2539,7 +2473,6 @@ namespace Ipc {
|
||||
for (int i = 0; i < Sched::MaxProcesses; i++) {
|
||||
g_processObjectsBySlot[i] = nullptr;
|
||||
}
|
||||
Hal::RegisterIrqHandler(Hal::IRQ_TLB_SHOOTDOWN, TlbShootdownIpiHandler);
|
||||
Kt::KernelLogStream(Kt::OK, "IPC") << "Initialized ("
|
||||
<< (uint64_t)MaxHandlesPerProcess << " handles/process, "
|
||||
<< (uint64_t)MaxStreams << " streams, "
|
||||
|
||||
@@ -171,11 +171,6 @@ namespace Ipc {
|
||||
int WaitsetWaitHandle(int waitsetHandle, WaitsetReady* outReady, uint64_t timeoutMs);
|
||||
|
||||
void NotifyObjectChanged(Object* object);
|
||||
// Invalidate a user range on every CPU currently running the address
|
||||
// space. Call this after removing PTEs and before releasing their frames.
|
||||
void ShootdownUserRange(uint64_t pml4Phys, uint64_t startVa, uint32_t pages);
|
||||
// Safely remove ordinary PFA-backed user mappings and release their frames.
|
||||
void UnmapAndFreeUserRange(uint64_t pml4Phys, uint64_t startVa, uint64_t pages);
|
||||
void CleanupProcessSlot(int slot, int pid, uint64_t pml4Phys);
|
||||
|
||||
}
|
||||
|
||||
@@ -13,13 +13,21 @@ namespace Memory::VMM {
|
||||
static constexpr uint64_t LeafUser = 1ULL << 2;
|
||||
static constexpr uint64_t LeafWriteThrough = 1ULL << 3;
|
||||
static constexpr uint64_t LeafCacheDisabled = 1ULL << 4;
|
||||
static constexpr uint64_t LeafNoExecute = 1ULL << 63;
|
||||
// PageTableEntry's historical 52-bit Address bitfield also spans the
|
||||
// architectural high flag bits. Mask those bits whenever extracting a
|
||||
// physical address, in particular now that anonymous leaves use NX.
|
||||
static constexpr uint64_t kPhysAddrMask = 0xFFFFFFFFFFULL; // PTE bits 12..51
|
||||
|
||||
static inline void SetLeafPte(PageTableEntry* entry, uint64_t physicalAddress,
|
||||
bool user, bool writeThrough, bool cacheDisabled) {
|
||||
uint64_t flags = LeafPresent | LeafWritable;
|
||||
bool user, bool writeThrough, bool cacheDisabled,
|
||||
bool writable = true, bool executable = true) {
|
||||
uint64_t flags = LeafPresent;
|
||||
if (writable) flags |= LeafWritable;
|
||||
if (user) flags |= LeafUser;
|
||||
if (writeThrough) flags |= LeafWriteThrough;
|
||||
if (cacheDisabled) flags |= LeafCacheDisabled;
|
||||
if (!executable) flags |= LeafNoExecute;
|
||||
|
||||
// Replace the complete PTE in one aligned store. Updating individual
|
||||
// bitfields left old PWT/PCD/PAT/accessed state behind when a virtual
|
||||
@@ -230,6 +238,12 @@ namespace Memory::VMM {
|
||||
}
|
||||
|
||||
bool Paging::MapUserIn(std::uint64_t pml4Phys, std::uint64_t physicalAddress, std::uint64_t virtualAddress) {
|
||||
return MapUserInPermissions(pml4Phys, physicalAddress, virtualAddress, true, true);
|
||||
}
|
||||
|
||||
bool Paging::MapUserInPermissions(std::uint64_t pml4Phys, std::uint64_t physicalAddress,
|
||||
std::uint64_t virtualAddress, bool writable,
|
||||
bool executable) {
|
||||
pagingLock.Acquire();
|
||||
if (virtualAddress % 0x1000 != 0 || physicalAddress % 0x1000 != 0) {
|
||||
pagingLock.Release();
|
||||
@@ -266,7 +280,43 @@ namespace Memory::VMM {
|
||||
if (!pml1) { pagingLock.Release(); return false; }
|
||||
|
||||
PageTableEntry* pageEntry = (PageTableEntry*)Memory::HHDM(&pml1->entries[va.GetPageIndex()]);
|
||||
SetLeafPte(pageEntry, physicalAddress, true, false, false);
|
||||
SetLeafPte(pageEntry, physicalAddress, true, false, false, writable, executable);
|
||||
pagingLock.Release();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Paging::ProtectUserIn(std::uint64_t pml4Phys, std::uint64_t virtualAddress,
|
||||
bool writable, bool executable) {
|
||||
if ((virtualAddress & 0xFFFULL) != 0) return false;
|
||||
pagingLock.Acquire();
|
||||
VirtualAddress va(virtualAddress);
|
||||
|
||||
auto walkRead = [](PageTable* table, uint64_t index) -> PageTable* {
|
||||
PageTableEntry* entry = (PageTableEntry*)Memory::HHDM(&table->entries[index]);
|
||||
if (!entry->Present || !entry->Supervisor || entry->LargerPages) return nullptr;
|
||||
return (PageTable*)(entry->Address << 12);
|
||||
};
|
||||
|
||||
PageTable* pml4 = (PageTable*)pml4Phys;
|
||||
auto pml3 = walkRead(pml4, va.GetL4Index());
|
||||
if (!pml3) { pagingLock.Release(); return false; }
|
||||
auto pml2 = walkRead(pml3, va.GetL3Index());
|
||||
if (!pml2) { pagingLock.Release(); return false; }
|
||||
auto pml1 = walkRead(pml2, va.GetL2Index());
|
||||
if (!pml1) { pagingLock.Release(); return false; }
|
||||
|
||||
PageTableEntry* pte = (PageTableEntry*)Memory::HHDM(&pml1->entries[va.GetPageIndex()]);
|
||||
uint64_t raw = *(volatile uint64_t*)pte;
|
||||
if ((raw & LeafPresent) == 0 || (raw & LeafUser) == 0) {
|
||||
pagingLock.Release();
|
||||
return false;
|
||||
}
|
||||
if (writable) raw |= LeafWritable;
|
||||
else raw &= ~LeafWritable;
|
||||
if (executable) raw &= ~LeafNoExecute;
|
||||
else raw |= LeafNoExecute;
|
||||
*(volatile uint64_t*)pte = raw;
|
||||
asm volatile("invlpg (%0)" :: "r"(virtualAddress) : "memory");
|
||||
pagingLock.Release();
|
||||
return true;
|
||||
}
|
||||
@@ -376,7 +426,8 @@ namespace Memory::VMM {
|
||||
// Skip MMIO/WC pages (not PFA-managed)
|
||||
if (pte->WriteThrough || pte->CacheDisabled) continue;
|
||||
|
||||
uint64_t pagePhys = (uint64_t)pte->Address << 12;
|
||||
uint64_t pagePhys =
|
||||
(uint64_t)(pte->Address & kPhysAddrMask) << 12;
|
||||
if (pagePhys != 0) {
|
||||
Memory::g_pfa->Free((void*)Memory::HHDM(pagePhys));
|
||||
}
|
||||
@@ -442,11 +493,6 @@ namespace Memory::VMM {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Mask to extract only the 40-bit physical address from a PTE Address field
|
||||
// (bits 12-51 of the PTE). The PageTableEntry::Address field is 52 bits wide
|
||||
// and includes NX, PK, and software-available bits that must be stripped.
|
||||
static constexpr uint64_t kPhysAddrMask = 0xFFFFFFFFFFULL; // 40 bits
|
||||
|
||||
std::uint64_t Paging::GetPhysAddr(std::uint64_t pml4, std::uint64_t virtualAddress, bool use40BitL1) {
|
||||
VirtualAddress virtualAddressObj(virtualAddress);
|
||||
|
||||
|
||||
@@ -104,6 +104,17 @@ public:
|
||||
// Map a page into an arbitrary PML4 (specified by physical address) with User bit set.
|
||||
static bool MapUserIn(std::uint64_t pml4Phys, std::uint64_t physicalAddress, std::uint64_t virtualAddress);
|
||||
|
||||
// Map ordinary user memory with explicit leaf permissions. Read access
|
||||
// is implied by x86 paging; writable and executable are independent.
|
||||
static bool MapUserInPermissions(std::uint64_t pml4Phys,
|
||||
std::uint64_t physicalAddress,
|
||||
std::uint64_t virtualAddress,
|
||||
bool writable, bool executable);
|
||||
|
||||
// Change leaf permissions on an existing user page.
|
||||
static bool ProtectUserIn(std::uint64_t pml4Phys, std::uint64_t virtualAddress,
|
||||
bool writable, bool executable);
|
||||
|
||||
// Map a page into an arbitrary PML4 with User + Write-Combining attributes.
|
||||
static bool MapUserInWC(std::uint64_t pml4Phys, std::uint64_t physicalAddress, std::uint64_t virtualAddress);
|
||||
|
||||
|
||||
@@ -0,0 +1,172 @@
|
||||
/*
|
||||
* UserRange.cpp
|
||||
* Cross-CPU invalidation and teardown of user address-space mappings
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*
|
||||
* Split out of Ipc.cpp: this is paging and SMP work with no dependency on
|
||||
* any IPC object or handle pool, and it lived there only by history.
|
||||
*/
|
||||
|
||||
#include "UserRange.hpp"
|
||||
|
||||
#include <Sched/Scheduler.hpp>
|
||||
#include <Memory/PageFrameAllocator.hpp>
|
||||
#include <Memory/HHDM.hpp>
|
||||
#include <Memory/Paging.hpp>
|
||||
#include <CppLib/Spinlock.hpp>
|
||||
#include <Hal/Apic/Apic.hpp>
|
||||
#include <Hal/Apic/Interrupts.hpp>
|
||||
#include <Hal/SmpBoot.hpp>
|
||||
#include <Common/Panic.hpp>
|
||||
|
||||
namespace Memory {
|
||||
|
||||
// MUST be a Mutex, never a Spinlock. ShootdownUserRange holds this while
|
||||
// waiting for remote CPUs to acknowledge the shootdown IPI, so a CPU that
|
||||
// is queued behind the holder has to stay interruptible long enough to
|
||||
// service that IPI itself. An interrupt-disabling Spinlock here deadlocks
|
||||
// every CPU contending for the lock, and the bounded-retry logic below
|
||||
// then reports it as a "target failed to acknowledge" Panic -- which reads
|
||||
// like a hardware fault rather than a lock-type regression.
|
||||
static kcp::Mutex g_tlbShootdownLock;
|
||||
static volatile uint64_t g_tlbShootdownSeq = 0;
|
||||
static volatile uint64_t g_tlbShootdownPml4 = 0;
|
||||
static volatile uint64_t g_tlbShootdownStartVa = 0;
|
||||
static volatile uint32_t g_tlbShootdownPages = 0;
|
||||
static volatile uint64_t g_tlbShootdownDone[Smp::MaxCPUs] = {};
|
||||
|
||||
static bool CpuCurrentlyUsesPml4(Smp::CpuData* cpu, uint64_t pml4Phys) {
|
||||
if (cpu == nullptr || pml4Phys == 0 || cpu->currentSlot < 0) return false;
|
||||
|
||||
Sched::Process* proc = Sched::GetProcessSlot(cpu->currentSlot);
|
||||
if (proc == nullptr) return false;
|
||||
if (proc->state == Sched::ProcessState::Free) return false;
|
||||
return proc->pml4Phys == pml4Phys;
|
||||
}
|
||||
|
||||
static void InvalidateLocalUserRange(uint64_t startVa, uint32_t pages) {
|
||||
if (pages == 0) return;
|
||||
|
||||
if (pages > 1024) {
|
||||
Memory::VMM::FlushTLB();
|
||||
return;
|
||||
}
|
||||
|
||||
for (uint32_t p = 0; p < pages; p++) {
|
||||
uint64_t va = startVa + (uint64_t)p * 0x1000ULL;
|
||||
asm volatile("invlpg (%0)" :: "r"(va) : "memory");
|
||||
}
|
||||
}
|
||||
|
||||
static void TlbShootdownIpiHandler(uint8_t, bool) {
|
||||
Smp::CpuData* cpu = Smp::GetCurrentCpuData();
|
||||
uint64_t seq = g_tlbShootdownSeq;
|
||||
uint64_t pml4 = g_tlbShootdownPml4;
|
||||
uint64_t startVa = g_tlbShootdownStartVa;
|
||||
uint32_t pages = g_tlbShootdownPages;
|
||||
|
||||
if (CpuCurrentlyUsesPml4(cpu, pml4)) {
|
||||
InvalidateLocalUserRange(startVa, pages);
|
||||
}
|
||||
|
||||
if (cpu != nullptr && cpu->cpuIndex >= 0 && cpu->cpuIndex < Smp::MaxCPUs) {
|
||||
asm volatile("" ::: "memory");
|
||||
g_tlbShootdownDone[cpu->cpuIndex] = seq;
|
||||
}
|
||||
}
|
||||
|
||||
void ShootdownUserRange(uint64_t pml4Phys, uint64_t startVa, uint32_t pages) {
|
||||
if (pml4Phys == 0 || pages == 0) return;
|
||||
|
||||
bool targets[Smp::MaxCPUs] = {};
|
||||
Smp::CpuData* currentCpu = Smp::GetCurrentCpuData();
|
||||
int currentCpuIndex = currentCpu ? currentCpu->cpuIndex : -1;
|
||||
|
||||
g_tlbShootdownLock.Acquire();
|
||||
uint64_t seq = g_tlbShootdownSeq + 1;
|
||||
g_tlbShootdownPml4 = pml4Phys;
|
||||
g_tlbShootdownStartVa = startVa;
|
||||
g_tlbShootdownPages = pages;
|
||||
asm volatile("" ::: "memory");
|
||||
g_tlbShootdownSeq = seq;
|
||||
|
||||
for (int i = 0; i < Smp::GetCpuCount(); i++) {
|
||||
Smp::CpuData* cpu = Smp::GetCpuData(i);
|
||||
if (cpu == nullptr || !cpu->started) continue;
|
||||
|
||||
if (i == currentCpuIndex) {
|
||||
if (CpuCurrentlyUsesPml4(cpu, pml4Phys)) {
|
||||
InvalidateLocalUserRange(startVa, pages);
|
||||
}
|
||||
g_tlbShootdownDone[i] = seq;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!CpuCurrentlyUsesPml4(cpu, pml4Phys)) {
|
||||
g_tlbShootdownDone[i] = seq;
|
||||
continue;
|
||||
}
|
||||
|
||||
targets[i] = true;
|
||||
(void)Hal::LocalApic::SendFixedIpi(cpu->lapicId,
|
||||
Hal::IRQ_VECTOR_BASE + Hal::IRQ_TLB_SHOOTDOWN);
|
||||
}
|
||||
|
||||
for (int i = 0; i < Smp::GetCpuCount(); i++) {
|
||||
if (!targets[i]) continue;
|
||||
uint32_t spins = 0;
|
||||
uint32_t retries = 0;
|
||||
while (g_tlbShootdownDone[i] != seq) {
|
||||
asm volatile("pause");
|
||||
if (++spins < 1000000) continue;
|
||||
|
||||
// Delivery normally completes in a handful of cycles. Retry a
|
||||
// bounded number of times in case the first IPI was lost while
|
||||
// the target changed interrupt state. Continuing without an
|
||||
// acknowledgement would let the caller free frames still
|
||||
// reachable through a remote stale TLB entry, so fail loudly
|
||||
// instead of either corrupting memory or spinning forever.
|
||||
spins = 0;
|
||||
if (++retries > 4) {
|
||||
Panic("TLB shootdown target failed to acknowledge", nullptr);
|
||||
}
|
||||
Smp::CpuData* cpu = Smp::GetCpuData(i);
|
||||
if (cpu != nullptr && cpu->started) {
|
||||
(void)Hal::LocalApic::SendFixedIpi(cpu->lapicId,
|
||||
Hal::IRQ_VECTOR_BASE + Hal::IRQ_TLB_SHOOTDOWN);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
g_tlbShootdownLock.Release();
|
||||
}
|
||||
|
||||
void UnmapAndFreeUserRange(uint64_t pml4Phys, uint64_t startVa, uint64_t pages) {
|
||||
static constexpr uint32_t PagesPerChunk = 64;
|
||||
uint64_t physPages[PagesPerChunk];
|
||||
|
||||
for (uint64_t base = 0; base < pages; base += PagesPerChunk) {
|
||||
uint32_t count = (uint32_t)((pages - base > PagesPerChunk)
|
||||
? PagesPerChunk : pages - base);
|
||||
|
||||
for (uint32_t i = 0; i < count; i++) {
|
||||
uint64_t pageVa = startVa + (base + i) * 0x1000ULL;
|
||||
physPages[i] = Memory::VMM::Paging::GetPhysAddr(pml4Phys, pageVa);
|
||||
Memory::VMM::Paging::UnmapUserIn(pml4Phys, pageVa);
|
||||
}
|
||||
|
||||
ShootdownUserRange(pml4Phys, startVa + base * 0x1000ULL, count);
|
||||
|
||||
for (uint32_t i = 0; i < count; i++) {
|
||||
if (physPages[i] != 0) {
|
||||
Memory::g_pfa->Free((void*)Memory::HHDM(physPages[i]));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void InitUserRange() {
|
||||
Hal::RegisterIrqHandler(Hal::IRQ_TLB_SHOOTDOWN, TlbShootdownIpiHandler);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* UserRange.hpp
|
||||
* Cross-CPU invalidation and teardown of user address-space mappings
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
|
||||
namespace Memory {
|
||||
|
||||
// Register the TLB-shootdown IPI handler. Must run before any AP is
|
||||
// booted, since a shootdown targets every CPU running the address space.
|
||||
void InitUserRange();
|
||||
|
||||
// Invalidate a user range on every CPU currently running the address
|
||||
// space. Call this after removing PTEs and before releasing their frames.
|
||||
void ShootdownUserRange(uint64_t pml4Phys, uint64_t startVa, uint32_t pages);
|
||||
|
||||
// Safely remove ordinary PFA-backed user mappings and release their frames.
|
||||
void UnmapAndFreeUserRange(uint64_t pml4Phys, uint64_t startVa, uint64_t pages);
|
||||
|
||||
}
|
||||
@@ -11,6 +11,7 @@
|
||||
namespace CrashReport {
|
||||
|
||||
static constexpr int MaxReports = 8;
|
||||
static constexpr int StackSnapshotWords = 8;
|
||||
|
||||
struct Report {
|
||||
int pid;
|
||||
@@ -24,6 +25,8 @@ struct Report {
|
||||
uint64_t codeSegment;
|
||||
uint64_t flags;
|
||||
uint64_t stackSegment;
|
||||
uint64_t stackWords[StackSnapshotWords];
|
||||
uint8_t stackWordCount;
|
||||
|
||||
// Page fault error code bits (vector 0xE / page fault only)
|
||||
uint8_t pfPresent : 1;
|
||||
|
||||
+206
-15
@@ -4,6 +4,7 @@
|
||||
* Copyright (c) 2025-2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#include <Memory/UserRange.hpp>
|
||||
#include "Scheduler.hpp"
|
||||
#include "ElfLoader.hpp"
|
||||
#include <Memory/PageFrameAllocator.hpp>
|
||||
@@ -27,6 +28,7 @@
|
||||
#include <Drivers/Audio/Mixer.hpp>
|
||||
#include <Drivers/Graphics/IntelGPU.hpp>
|
||||
#include <Ipc/Ipc.hpp>
|
||||
#include <Drivers/USB/UserUsb.hpp>
|
||||
|
||||
// Assembly: context switch with CR3 and FPU state parameters
|
||||
extern "C" void SchedContextSwitch(uint64_t* oldRsp, uint64_t newRsp, uint64_t newCR3,
|
||||
@@ -294,7 +296,13 @@ namespace Sched {
|
||||
processTable[i].heapNext = 0;
|
||||
processTable[i].readdirCursor = 0;
|
||||
processTable[i].args[0] = '\0';
|
||||
processTable[i].environment[0] = '\0';
|
||||
processTable[i].environmentLength = 1;
|
||||
processTable[i].user[0] = '\0';
|
||||
processTable[i].permittedCaps = 0;
|
||||
processTable[i].effectiveCaps = 0;
|
||||
processTable[i].delegableCaps = 0;
|
||||
processTable[i].sessionId = -1;
|
||||
processTable[i].cwd[0] = '\0';
|
||||
processTable[i].runningOnCpu = -1;
|
||||
processTable[i].killPending = false;
|
||||
@@ -337,7 +345,10 @@ namespace Sched {
|
||||
<< " process slots, " << (uint64_t)TimeSliceMs << " ms time slice)";
|
||||
}
|
||||
|
||||
int Spawn(const char* vfsPath, const char* args, bool startReady) {
|
||||
int Spawn(const char* vfsPath, const char* args, bool startReady,
|
||||
const char* environment, uint32_t environmentLength,
|
||||
const montauk::abi::SpawnCapabilities* capabilities,
|
||||
const char* userOverride) {
|
||||
schedLock.Acquire();
|
||||
|
||||
int slot = -1;
|
||||
@@ -493,6 +504,8 @@ namespace Sched {
|
||||
proc.kernelStackTop = kernelStackTop;
|
||||
proc.userStackTop = UserStackTop - 8;
|
||||
proc.heapNext = UserHeapBase;
|
||||
for (uint64_t i = 0; i < UserHeapBitmapWords; i++)
|
||||
g_userHeapPageMap[slot][i] = 0;
|
||||
proc.fsBase = tls.fsBase;
|
||||
proc.tlsTemplateVaddr = tls.templateVaddr;
|
||||
proc.tlsFileSize = tls.fileSize;
|
||||
@@ -509,6 +522,9 @@ namespace Sched {
|
||||
|
||||
auto* currentCpu = Smp::GetCurrentCpuData();
|
||||
int parentSlot = currentCpu ? currentCpu->currentSlot : -1;
|
||||
int parentPrimarySlot = parentSlot;
|
||||
if (parentPrimarySlot >= 0 && processTable[parentPrimarySlot].primarySlot >= 0)
|
||||
parentPrimarySlot = processTable[parentPrimarySlot].primarySlot;
|
||||
|
||||
// Copy arguments string into process
|
||||
proc.args[0] = '\0';
|
||||
@@ -520,9 +536,54 @@ namespace Sched {
|
||||
proc.args[i] = '\0';
|
||||
}
|
||||
|
||||
// Inherit user string from parent, or default to "system" if no parent
|
||||
// posix_spawn(envp) supplies an explicit snapshot. Plain spawn calls
|
||||
// omit it and inherit the environment stored on the parent process.
|
||||
if (environment != nullptr && environmentLength > 0 &&
|
||||
environmentLength <= Process::EnvironmentBytes) {
|
||||
for (uint32_t i = 0; i < environmentLength; i++)
|
||||
proc.environment[i] = environment[i];
|
||||
proc.environmentLength = environmentLength;
|
||||
} else if (parentPrimarySlot >= 0) {
|
||||
auto& parent = processTable[parentPrimarySlot];
|
||||
for (uint32_t i = 0; i < parent.environmentLength; i++)
|
||||
proc.environment[i] = parent.environment[i];
|
||||
proc.environmentLength = parent.environmentLength;
|
||||
} else {
|
||||
proc.environment[0] = '\0';
|
||||
proc.environmentLength = 1;
|
||||
}
|
||||
|
||||
// Capabilities are kernel-owned and never inferred from the user name.
|
||||
// A normal userspace spawn receives no privileged authority; callers
|
||||
// must use SYS_SPAWN_CAPS for an explicit, kernel-validated delegation.
|
||||
if (parentPrimarySlot >= 0) {
|
||||
if (capabilities != nullptr) {
|
||||
proc.permittedCaps = capabilities->permitted;
|
||||
proc.effectiveCaps = capabilities->effective;
|
||||
proc.delegableCaps = capabilities->delegable;
|
||||
} else {
|
||||
proc.permittedCaps = 0;
|
||||
proc.effectiveCaps = 0;
|
||||
proc.delegableCaps = 0;
|
||||
}
|
||||
} else {
|
||||
// The kernel-created init process is the root of the capability
|
||||
// delegation tree. No userspace pathname or PID receives this
|
||||
// treatment; it is reached only with no current parent process.
|
||||
proc.permittedCaps = montauk::abi::CAP_ALL;
|
||||
proc.effectiveCaps = montauk::abi::CAP_ALL;
|
||||
proc.delegableCaps = montauk::abi::CAP_ALL;
|
||||
}
|
||||
|
||||
// Inherit user string from parent, or default to "system" if no parent.
|
||||
// An explicit override is accepted only through the validated
|
||||
// SYS_SPAWN_CAPS path.
|
||||
{
|
||||
if (parentSlot >= 0) {
|
||||
if (userOverride != nullptr) {
|
||||
int i = 0;
|
||||
for (; i < 31 && userOverride[i]; i++) proc.user[i] = userOverride[i];
|
||||
proc.user[i] = '\0';
|
||||
} else if (parentSlot >= 0) {
|
||||
int i = 0;
|
||||
for (; i < 31 && processTable[parentSlot].user[i]; i++)
|
||||
proc.user[i] = processTable[parentSlot].user[i];
|
||||
@@ -535,6 +596,13 @@ namespace Sched {
|
||||
}
|
||||
}
|
||||
|
||||
// Process sessions are explicit groups used by supervisors such as
|
||||
// login.elf. A session leader opts in with CreateSession(); all of its
|
||||
// subsequently spawned descendants inherit the same stable ID.
|
||||
proc.sessionId = (parentPrimarySlot >= 0)
|
||||
? processTable[parentPrimarySlot].sessionId
|
||||
: -1;
|
||||
|
||||
{
|
||||
if (parentSlot >= 0 && processTable[parentSlot].cwd[0]) {
|
||||
int i = 0;
|
||||
@@ -650,7 +718,8 @@ namespace Sched {
|
||||
mappedPages++;
|
||||
}
|
||||
if (!ok) {
|
||||
Ipc::UnmapAndFreeUserRange(sharedPml4, base, mappedPages);
|
||||
Memory::UnmapAndFreeUserRange(sharedPml4, base, mappedPages);
|
||||
ReleaseUserHeapRange(primarySlot_, base, numPages * 0x1000ULL);
|
||||
Kt::KernelLogStream(Kt::ERROR, "Sched")
|
||||
<< "Thread TLS allocation failed";
|
||||
return -1;
|
||||
@@ -675,8 +744,11 @@ namespace Sched {
|
||||
// Allocate kernel stack.
|
||||
void* stackMem = Memory::g_pfa->ReallocConsecutive(nullptr, StackPages);
|
||||
if (stackMem == nullptr) {
|
||||
if (threadTlsPages != 0)
|
||||
Ipc::UnmapAndFreeUserRange(sharedPml4, threadTlsBase, threadTlsPages);
|
||||
if (threadTlsPages != 0) {
|
||||
Memory::UnmapAndFreeUserRange(sharedPml4, threadTlsBase, threadTlsPages);
|
||||
ReleaseUserHeapRange(primarySlot_, threadTlsBase,
|
||||
threadTlsPages * 0x1000ULL);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
memset(stackMem, 0, StackSize);
|
||||
@@ -698,8 +770,11 @@ namespace Sched {
|
||||
if (slot < 0) {
|
||||
schedLock.Release();
|
||||
Memory::g_pfa->Free(stackMem, StackPages);
|
||||
if (threadTlsPages != 0)
|
||||
Ipc::UnmapAndFreeUserRange(sharedPml4, threadTlsBase, threadTlsPages);
|
||||
if (threadTlsPages != 0) {
|
||||
Memory::UnmapAndFreeUserRange(sharedPml4, threadTlsBase, threadTlsPages);
|
||||
ReleaseUserHeapRange(primarySlot_, threadTlsBase,
|
||||
threadTlsPages * 0x1000ULL);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -774,20 +849,54 @@ namespace Sched {
|
||||
|
||||
schedLock.Acquire();
|
||||
Process& primary = processTable[primarySlot_];
|
||||
uint64_t base = primary.heapNext;
|
||||
bool validOwner = primary.primarySlot == primarySlot_ &&
|
||||
primary.state != ProcessState::Free &&
|
||||
primary.state != ProcessState::Terminated;
|
||||
bool fits = validOwner && base >= UserHeapBase &&
|
||||
base <= UserHeapLimit && size <= UserHeapLimit - base;
|
||||
if (fits) {
|
||||
primary.heapNext = base + size;
|
||||
outVa = base;
|
||||
bool fits = false;
|
||||
uint64_t pages = size / 0x1000ULL;
|
||||
uint64_t runStart = 0;
|
||||
uint64_t runLength = 0;
|
||||
|
||||
if (validOwner && pages <= UserHeapPages) {
|
||||
for (uint64_t page = 0; page < UserHeapPages; page++) {
|
||||
bool used = (g_userHeapPageMap[primarySlot_][page / 64]
|
||||
& (1ULL << (page % 64))) != 0;
|
||||
if (used) {
|
||||
runLength = 0;
|
||||
continue;
|
||||
}
|
||||
if (runLength == 0) runStart = page;
|
||||
runLength++;
|
||||
if (runLength == pages) {
|
||||
for (uint64_t p = runStart; p < runStart + pages; p++)
|
||||
g_userHeapPageMap[primarySlot_][p / 64] |= 1ULL << (p % 64);
|
||||
outVa = UserHeapBase + runStart * 0x1000ULL;
|
||||
uint64_t end = outVa + size;
|
||||
if (end > primary.heapNext) primary.heapNext = end;
|
||||
fits = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
schedLock.Release();
|
||||
return fits;
|
||||
}
|
||||
|
||||
void ReleaseUserHeapRange(int primarySlot_, uint64_t va, uint64_t size) {
|
||||
if (primarySlot_ < 0 || primarySlot_ >= MaxProcesses || size == 0 ||
|
||||
(va & 0xFFFULL) != 0 || (size & 0xFFFULL) != 0 ||
|
||||
va < UserHeapBase || va > UserHeapLimit || size > UserHeapLimit - va) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint64_t first = (va - UserHeapBase) / 0x1000ULL;
|
||||
uint64_t pages = size / 0x1000ULL;
|
||||
schedLock.Acquire();
|
||||
for (uint64_t p = first; p < first + pages; p++)
|
||||
g_userHeapPageMap[primarySlot_][p / 64] &= ~(1ULL << (p % 64));
|
||||
schedLock.Release();
|
||||
}
|
||||
|
||||
static void FreeSiblingThreadTls(Process& thr) {
|
||||
int primarySlot_ = thr.primarySlot;
|
||||
if (primarySlot_ < 0 || primarySlot_ >= MaxProcesses ||
|
||||
@@ -810,7 +919,8 @@ namespace Sched {
|
||||
uint64_t base = thr.fsBase - blockSize;
|
||||
uint64_t pages = (blockSize + 16 + 0xFFF) / 0x1000;
|
||||
thr.fsBase = 0;
|
||||
Ipc::UnmapAndFreeUserRange(primary.pml4Phys, base, pages);
|
||||
Memory::UnmapAndFreeUserRange(primary.pml4Phys, base, pages);
|
||||
ReleaseUserHeapRange(primarySlot_, base, pages * 0x1000ULL);
|
||||
}
|
||||
|
||||
// Switch away from a slot we have just marked non-runnable while holding
|
||||
@@ -1209,6 +1319,15 @@ namespace Sched {
|
||||
uint8_t* oldFpu = (oldSlot >= 0) ? processTable[oldSlot].fpuState : nullptr;
|
||||
uint8_t* newFpu = processTable[next].fpuState;
|
||||
|
||||
if (oldSlot < 0) {
|
||||
// AP idle loops mask their local periodic timer. Rearm it before
|
||||
// dispatching user work so preemption resumes with the process.
|
||||
// Also pick up a thermal-governor policy epoch that may have
|
||||
// changed while this CPU remained asleep without timer ticks.
|
||||
Hal::CpuPower::ApplyPolicyIfChanged();
|
||||
Timekeeping::ApicTimerLeaveApIdle();
|
||||
}
|
||||
|
||||
LoadUserFsBase(cpu, processTable[next].fsBase);
|
||||
|
||||
// DO NOT release schedLock here! It is held across the context
|
||||
@@ -1333,6 +1452,11 @@ namespace Sched {
|
||||
return &processTable[primary];
|
||||
}
|
||||
|
||||
bool HasCapability(uint64_t capability) {
|
||||
Process* proc = GetCurrentProcessPtr();
|
||||
return proc != nullptr && (proc->effectiveCaps & capability) == capability;
|
||||
}
|
||||
|
||||
Process* GetCurrentThreadPtr() {
|
||||
auto* cpu = Smp::GetCurrentCpuData();
|
||||
int slot = cpu->currentSlot;
|
||||
@@ -1523,6 +1647,11 @@ namespace Sched {
|
||||
// never stranded on the invisible buffer (no-op for non-owners).
|
||||
Drivers::Graphics::IntelGPU::OnProcessExit(exitingPid);
|
||||
|
||||
// USB interface claims are process-owned capabilities. Closing them
|
||||
// here stops DMA streaming and releases exclusivity even when an app
|
||||
// exits without calling usb_close().
|
||||
Drivers::USB::UserUsb::ReleaseAllForPid(exitingPid);
|
||||
|
||||
// Release process-scoped IPC handles/mappings before tearing down the address space.
|
||||
Ipc::CleanupProcessSlot(slot, exitingPid, proc.pml4Phys);
|
||||
montauk::abi::CleanupHeapForSlot(slot, proc.pml4Phys);
|
||||
@@ -1680,6 +1809,68 @@ namespace Sched {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int CreateSession() {
|
||||
schedLock.Acquire();
|
||||
Process* proc = GetCurrentProcessPtr();
|
||||
if (proc == nullptr) {
|
||||
schedLock.Release();
|
||||
return -1;
|
||||
}
|
||||
proc->sessionId = proc->pid;
|
||||
int sessionId = proc->sessionId;
|
||||
schedLock.Release();
|
||||
return sessionId;
|
||||
}
|
||||
|
||||
int KillSession(int sessionId) {
|
||||
if (sessionId < 0) return -1;
|
||||
|
||||
int callerPid = GetCurrentPid();
|
||||
int killed = 0;
|
||||
|
||||
// Mark the complete group in one scheduler-lock transaction. New
|
||||
// children inherit sessionId, so a supervisor can repeat this call
|
||||
// until zero is returned to close the small spawn/exit race cleanly.
|
||||
schedLock.Acquire();
|
||||
for (int i = 0; i < MaxProcesses; i++) {
|
||||
Process& primary = processTable[i];
|
||||
if (primary.primarySlot != i || primary.pid == callerPid ||
|
||||
primary.sessionId != sessionId) {
|
||||
continue;
|
||||
}
|
||||
auto state = primary.state;
|
||||
if (state != ProcessState::Ready && state != ProcessState::Running &&
|
||||
state != ProcessState::Blocked) {
|
||||
continue;
|
||||
}
|
||||
|
||||
primary.exitCode = 256 + 9; /* killed (SIGKILL) */
|
||||
primary.killPending = true;
|
||||
if (primary.state == ProcessState::Blocked) {
|
||||
primary.state = ProcessState::Ready;
|
||||
readyCount++;
|
||||
primary.waitingForPid = -1;
|
||||
primary.waitingOnObject = nullptr;
|
||||
primary.sleepUntilTick = 0;
|
||||
}
|
||||
|
||||
for (int j = 0; j < MaxProcesses; j++) {
|
||||
if (j == i || processTable[j].primarySlot != i) continue;
|
||||
if (processTable[j].state == ProcessState::Running) {
|
||||
processTable[j].killPending = true;
|
||||
}
|
||||
}
|
||||
killed++;
|
||||
}
|
||||
schedLock.Release();
|
||||
|
||||
if (killed > 0) {
|
||||
KickOneIdleCpu(Smp::GetCurrentCpuData()
|
||||
? Smp::GetCurrentCpuData()->cpuIndex : -1);
|
||||
}
|
||||
return killed;
|
||||
}
|
||||
|
||||
int LookupExitCode(int pid) {
|
||||
schedLock.Acquire();
|
||||
int code = 0;
|
||||
|
||||
@@ -33,6 +33,9 @@ namespace Sched {
|
||||
// failed bound check is recoverable (SYS_ALLOC returns null); allowing the
|
||||
// heap to cross this boundary would silently replace library PTEs.
|
||||
static constexpr uint64_t UserHeapLimit = 0x60000000ULL;
|
||||
static constexpr uint64_t UserHeapPages =
|
||||
(UserHeapLimit - UserHeapBase) / 0x1000ULL;
|
||||
static constexpr uint64_t UserHeapBitmapWords = (UserHeapPages + 63) / 64;
|
||||
// Surface mappings use reusable, fixed-size per-process slots instead of
|
||||
// consuming heap VA forever. 64 * 32 MiB occupies [0x7000000000,
|
||||
// 0x7080000000), comfortably below the main user stack.
|
||||
@@ -62,6 +65,7 @@ namespace Sched {
|
||||
};
|
||||
|
||||
struct Process {
|
||||
static constexpr uint32_t EnvironmentBytes = 4096;
|
||||
int pid;
|
||||
ProcessState state;
|
||||
int waitingForPid; // PID this process is blocked on (-1 if none)
|
||||
@@ -76,10 +80,16 @@ namespace Sched {
|
||||
uint64_t pml4Phys; // Physical address of per-process PML4
|
||||
uint64_t kernelStackTop; // Top of kernel stack (for TSS RSP0 / SYSCALL)
|
||||
uint64_t userStackTop; // User-space stack top
|
||||
uint64_t heapNext; // Simple bump allocator for user heap
|
||||
uint64_t heapNext; // High-water mark of the user-heap address space
|
||||
uint32_t readdirCursor; // Next SYS_READDIR scratch slot
|
||||
char args[4096]; // Command-line arguments (set by parent via Spawn)
|
||||
char environment[EnvironmentBytes]; // NUL-separated NAME=VALUE entries
|
||||
uint32_t environmentLength;
|
||||
char user[32]; // Owner user name (inherited from parent on spawn)
|
||||
uint64_t permittedCaps; // Authority owned by this process
|
||||
uint64_t effectiveCaps; // Authority currently usable by syscalls
|
||||
uint64_t delegableCaps; // Authority this process may pass to children
|
||||
int sessionId; // Process-session leader PID (inherited on spawn)
|
||||
char cwd[256]; // Absolute current working directory
|
||||
|
||||
// Thread-local storage. fsBase is loaded into IA32_FS_BASE when
|
||||
@@ -136,7 +146,10 @@ namespace Sched {
|
||||
};
|
||||
|
||||
void Initialize();
|
||||
int Spawn(const char* vfsPath, const char* args = nullptr, bool startReady = true);
|
||||
int Spawn(const char* vfsPath, const char* args = nullptr, bool startReady = true,
|
||||
const char* environment = nullptr, uint32_t environmentLength = 0,
|
||||
const montauk::abi::SpawnCapabilities* capabilities = nullptr,
|
||||
const char* userOverride = nullptr);
|
||||
int StartProcess(int pid);
|
||||
void Schedule();
|
||||
|
||||
@@ -164,6 +177,10 @@ namespace Sched {
|
||||
// Always returns the slot that owns per-process state -- never a sibling thread.
|
||||
Process* GetCurrentProcessPtr();
|
||||
|
||||
// Capability checks always consult kernel-owned process metadata. User
|
||||
// names are deliberately excluded from authorization.
|
||||
bool HasCapability(uint64_t capability);
|
||||
|
||||
// Get a pointer to the currently running thread's slot (may be a sibling).
|
||||
Process* GetCurrentThreadPtr();
|
||||
|
||||
@@ -191,6 +208,7 @@ namespace Sched {
|
||||
// reservation is serialized across sibling threads and bounded below the
|
||||
// fixed shared-library mappings.
|
||||
bool ReserveUserHeapRange(int primarySlot, uint64_t size, uint64_t& outVa);
|
||||
void ReleaseUserHeapRange(int primarySlot, uint64_t va, uint64_t size);
|
||||
|
||||
// Terminate the currently executing thread. If this is the main thread,
|
||||
// the entire process exits (equivalent to ExitProcess).
|
||||
@@ -244,6 +262,12 @@ namespace Sched {
|
||||
// Returns 0 on success, -1 on failure.
|
||||
int KillProcess(int pid);
|
||||
|
||||
// Start a new process session for the caller, or terminate every process
|
||||
// belonging to a session. KillSession returns the number of live members
|
||||
// it signalled; callers can repeat until it returns zero.
|
||||
int CreateSession();
|
||||
int KillSession(int sessionId);
|
||||
|
||||
// Find a process by PID (returns nullptr if not found or not alive)
|
||||
Process* GetProcessByPid(int pid);
|
||||
|
||||
@@ -258,5 +282,9 @@ namespace Sched {
|
||||
|
||||
// Per-process allocated page count (tracked by Heap syscalls, separate from Process struct)
|
||||
inline uint64_t g_allocatedPages[MaxProcesses] = {};
|
||||
// One bit per page in the bounded userspace heap. This is the authoritative
|
||||
// allocation state; unlike heapNext, it makes virtual ranges reusable after
|
||||
// unmap and failed reservations.
|
||||
inline uint64_t g_userHeapPageMap[MaxProcesses][UserHeapBitmapWords] = {};
|
||||
|
||||
}
|
||||
|
||||
@@ -205,12 +205,10 @@ namespace Kt {
|
||||
|
||||
void Putchar(char c) {
|
||||
if (g_kernelLogDepth > 0) {
|
||||
if (c == '\n') {
|
||||
RingBufferAppend('\r');
|
||||
RingBufferAppend('\n');
|
||||
} else {
|
||||
RingBufferAppend(c);
|
||||
}
|
||||
// Keep the log as canonical text. CRLF is only needed by the
|
||||
// framebuffer terminal below; storing it in the ring makes file
|
||||
// consumers treat one logical newline as two line breaks.
|
||||
RingBufferAppend(c);
|
||||
|
||||
if (g_suppressKernelLog) {
|
||||
return;
|
||||
@@ -250,7 +248,7 @@ namespace Kt {
|
||||
g_suppressKernelLog = false;
|
||||
}
|
||||
|
||||
int64_t ReadKernelLog(char* buf, uint64_t size) {
|
||||
int64_t ReadKernelLogBuffer(char* buf, uint64_t size) {
|
||||
if (buf == nullptr || size == 0) return 0;
|
||||
|
||||
uint64_t toRead = g_klogCount;
|
||||
|
||||
@@ -121,7 +121,7 @@ namespace Kt
|
||||
// intentionally lock-free: a panic can occur while another CPU owns the
|
||||
// terminal mutex, and the system is about to halt.
|
||||
void EnablePanicOutput();
|
||||
int64_t ReadKernelLog(char* buf, uint64_t size);
|
||||
int64_t ReadKernelLogBuffer(char* buf, uint64_t size);
|
||||
|
||||
class KernelLogStream {
|
||||
KernelOutStream localStream{};
|
||||
@@ -151,7 +151,7 @@ public:
|
||||
g_kernelLogDepth++;
|
||||
componentName = desiredComponentName;
|
||||
|
||||
localStream << componentName << ": " << "[" << LogLevelToStringWithColor(level) << "] ";
|
||||
localStream << "[kernel/" << componentName << "] " << LogLevelToStringWithColor(level) << ": ";
|
||||
}
|
||||
|
||||
~KernelLogStream() {
|
||||
@@ -168,6 +168,36 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
class UserspaceLogStream {
|
||||
KernelOutStream localStream{};
|
||||
const char* imageName = "";
|
||||
const char* username = "";
|
||||
|
||||
public:
|
||||
|
||||
UserspaceLogStream(const char* desiredImageName, const char* desiredUsername) {
|
||||
g_termLock.Acquire();
|
||||
g_kernelLogDepth++;
|
||||
imageName = desiredImageName;
|
||||
username = desiredUsername;
|
||||
|
||||
localStream << "[user " << username << "@" << imageName << "] ";
|
||||
}
|
||||
|
||||
~UserspaceLogStream() {
|
||||
localStream << newline;
|
||||
g_kernelLogDepth--;
|
||||
g_termLock.Release();
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
UserspaceLogStream &operator<<(T item) {
|
||||
localStream << item;
|
||||
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
extern Kt::KernelOutStream kout;
|
||||
|
||||
@@ -41,8 +41,8 @@ namespace Timekeeping {
|
||||
static constexpr uint32_t DIVIDE_BY_16 = 0x03;
|
||||
|
||||
// The BSP keeps a 1 ms tick for timekeeping and sleep deadlines.
|
||||
// APs use a coarser 10 ms scheduler tick to avoid waking idle cores
|
||||
// 1000 times per second with no useful work to do.
|
||||
// Running APs use a 10 ms scheduler tick. Idle APs mask it entirely and
|
||||
// rely on reschedule IPIs, avoiding periodic package wakeups.
|
||||
static constexpr uint32_t BSP_TICK_INTERVAL_MS = 1;
|
||||
static constexpr uint32_t BSP_TIMER_HZ = 1000 / BSP_TICK_INTERVAL_MS;
|
||||
static constexpr uint32_t AP_TICK_INTERVAL_MS = 10;
|
||||
@@ -220,8 +220,28 @@ namespace Timekeeping {
|
||||
// identical. This avoids PIT contention during AP boot.
|
||||
if (g_ticksPerMs == 0) return;
|
||||
|
||||
// Configure a coarser periodic timer on APs. The scheduler still gets
|
||||
// a 10 ms time slice, but idle APs stop taking 1000 timer interrupts/sec.
|
||||
// Configure the 10 ms scheduler timer for running APs. Their idle loop
|
||||
// masks it after initialization and rearms it when dispatching work.
|
||||
ProgramTimer(true, AP_TICK_INTERVAL_MS);
|
||||
}
|
||||
|
||||
void ApicTimerEnterApIdle() {
|
||||
auto* cpu = Smp::GetCurrentCpuData();
|
||||
if (cpu == nullptr || cpu->cpuIndex == 0 || g_ticksPerMs == 0) return;
|
||||
|
||||
uint32_t lvt = Hal::LocalApic::ReadRegister(Hal::LocalApic::REG_TIMER_LVT);
|
||||
if ((lvt & LVT_MASKED) == 0) {
|
||||
Hal::LocalApic::WriteRegister(Hal::LocalApic::REG_TIMER_LVT,
|
||||
lvt | LVT_MASKED);
|
||||
}
|
||||
}
|
||||
|
||||
void ApicTimerLeaveApIdle() {
|
||||
auto* cpu = Smp::GetCurrentCpuData();
|
||||
if (cpu == nullptr || cpu->cpuIndex == 0 || g_ticksPerMs == 0) return;
|
||||
|
||||
// Reprogram the initial count as well as unmasking. A deep idle state
|
||||
// may have stopped the local timer at an arbitrary point in its period.
|
||||
ProgramTimer(true, AP_TICK_INTERVAL_MS);
|
||||
}
|
||||
|
||||
@@ -239,19 +259,24 @@ namespace Timekeeping {
|
||||
bool wasReserved = cpu->reservedForKernelWork;
|
||||
cpu->reservedForKernelWork = true;
|
||||
|
||||
// Drain USB hot-plug deferred work from any idle core, not just the BSP.
|
||||
if (Drivers::USB::Xhci::HasDeferredWork()) {
|
||||
// Drain USB work only when the MSI path has actually queued something.
|
||||
// Bluetooth shares this controller, so service its protocol queues in
|
||||
// the same pass after xHCI has delivered completion callbacks.
|
||||
bool usbWork = Drivers::USB::Xhci::HasDeferredWork();
|
||||
if (usbWork) {
|
||||
Drivers::USB::Xhci::ProcessDeferredWork();
|
||||
}
|
||||
|
||||
// NIC hard IRQs only acknowledge/mask and queue RX work. Dispatching
|
||||
// Ethernet/TCP/UDP here keeps process-context IPC mutexes out of IRQs.
|
||||
Drivers::Net::E1000::ProcessDeferredWork();
|
||||
Drivers::Net::E1000E::ProcessDeferredWork();
|
||||
if (Drivers::Net::E1000::HasDeferredWork())
|
||||
Drivers::Net::E1000::ProcessDeferredWork();
|
||||
if (Drivers::Net::E1000E::HasDeferredWork())
|
||||
Drivers::Net::E1000E::ProcessDeferredWork();
|
||||
|
||||
// HDA completion IRQs only acknowledge/mask. Resampling and DMA-ring
|
||||
// refill are far too expensive for hard interrupt context.
|
||||
if (cpu->cpuIndex == 0) {
|
||||
if (cpu->cpuIndex == 0 && Drivers::Audio::IntelHda::HasDeferredWork()) {
|
||||
Drivers::Audio::IntelHda::ProcessDeferredWork();
|
||||
}
|
||||
|
||||
@@ -260,7 +285,10 @@ namespace Timekeeping {
|
||||
// seconds and used to stall kmain before the first process spawned.
|
||||
// Cheap no-op unless an adapter is waiting; self-claiming, and safe
|
||||
// to preempt (the scheduler saves/resumes the idle context).
|
||||
Drivers::USB::Bluetooth::ServiceDeferredInit();
|
||||
bool bluetoothWork = usbWork ||
|
||||
Drivers::USB::Bluetooth::HasDeferredWork();
|
||||
if (bluetoothWork)
|
||||
Drivers::USB::Bluetooth::ServiceDeferredInit();
|
||||
|
||||
// Service Bluetooth inbound traffic (the headset's SDP/AVRCP queries,
|
||||
// AVDTP commands, ACL flow-control credits) whenever a core idles.
|
||||
@@ -269,13 +297,16 @@ namespace Timekeeping {
|
||||
// writes got silence (observed: Bose re-dialing SDP during playback,
|
||||
// queries never answered). Self-serializing and a cheap no-op when
|
||||
// the adapter is down.
|
||||
Drivers::USB::Bluetooth::ServiceEvents();
|
||||
if (bluetoothWork)
|
||||
Drivers::USB::Bluetooth::ServiceEvents();
|
||||
|
||||
// Wi-Fi mirrors the Bluetooth split: the firmware load needs the
|
||||
// ramdisk, and the RX/notification ring must be drained outside hard
|
||||
// interrupt context (the MSI handler only latches a flag).
|
||||
Drivers::Net::Wifi::ServiceDeferredInit();
|
||||
Drivers::Net::Wifi::ServiceEvents();
|
||||
if (Drivers::Net::Wifi::HasDeferredWork()) {
|
||||
Drivers::Net::Wifi::ServiceDeferredInit();
|
||||
Drivers::Net::Wifi::ServiceEvents();
|
||||
}
|
||||
|
||||
// Thermal policy records transitions during BSP maintenance; print
|
||||
// them from this explicitly non-interrupt idle path.
|
||||
|
||||
@@ -11,9 +11,14 @@ namespace Timekeeping {
|
||||
// Initialize the APIC timer: calibrate against PIT, start periodic interrupts
|
||||
void ApicTimerInitialize();
|
||||
|
||||
// Initialize the APIC timer on an AP (calibrate + start, no IRQ handler registration)
|
||||
// Initialize the scheduler timer on an AP using the BSP calibration.
|
||||
void ApicTimerInitializeAP();
|
||||
|
||||
// Idle APs are woken for runnable work by the reschedule IPI, so their
|
||||
// periodic scheduler timer can remain masked until a process is dispatched.
|
||||
void ApicTimerEnterApIdle();
|
||||
void ApicTimerLeaveApIdle();
|
||||
|
||||
// Reinitialize the APIC timer after S3 resume using the previously
|
||||
// calibrated tick rate. Skips PIT calibration and IRQ registration
|
||||
// (both survive in RAM). Only reprograms the timer hardware registers.
|
||||
|
||||
@@ -70,14 +70,19 @@ static Timekeeping::DateTime EpochToDate(int64_t epoch) {
|
||||
void Timekeeping::Init(uint16_t Year, uint8_t Month, uint8_t Day, uint8_t Hour, uint8_t Minute, uint8_t Second) {
|
||||
g_bootEpoch = DateToEpoch(Year, Month, Day, Hour, Minute, Second);
|
||||
|
||||
int offH = g_tzOffsetMinutes / 60;
|
||||
int offM = g_tzOffsetMinutes % 60;
|
||||
if (offM < 0) offM = -offM;
|
||||
int64_t absoluteOffset = g_tzOffsetMinutes;
|
||||
char sign = '+';
|
||||
if (absoluteOffset < 0) {
|
||||
sign = '-';
|
||||
absoluteOffset = -absoluteOffset;
|
||||
}
|
||||
|
||||
Kt::KernelLogStream(INFO, "Timekeeping Service") << "Time zone: UTC"
|
||||
<< (offH >= 0 ? "+" : "") << offH
|
||||
<< (offM ? ":" : "") << (offM >= 10 ? "" : (offM ? "0" : ""))
|
||||
<< (offM ? offM : 0);
|
||||
int offH = (int)(absoluteOffset / 60);
|
||||
int offM = (int)(absoluteOffset % 60);
|
||||
auto log = Kt::KernelLogStream(INFO, "Timekeeping Service");
|
||||
log << "Time zone: UTC" << sign << offH;
|
||||
if (offM != 0)
|
||||
log << ":" << (offM < 10 ? "0" : "") << offM;
|
||||
}
|
||||
|
||||
int64_t Timekeeping::GetUnixTimestamp() {
|
||||
|
||||
@@ -1,144 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="description" content="MontaukOS - Montauk API">
|
||||
<title>Montauk API - MontaukOS</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Open+Sans:wght@400;600&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
body {
|
||||
font-family: 'Open Sans', Arial, sans-serif;
|
||||
max-width: 960px;
|
||||
margin: 0 auto;
|
||||
padding: 1em;
|
||||
line-height: 1.5;
|
||||
display: flex;
|
||||
gap: 2em;
|
||||
}
|
||||
p { margin: 0 0 0.5em; }
|
||||
h2 { margin: 0.5em 0; }
|
||||
h3 { margin: 0.75em 0 0.35em; }
|
||||
.sidebar {
|
||||
width: 160px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.sidebar ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
.sidebar li {
|
||||
margin: 0.5em 0;
|
||||
}
|
||||
.sidebar a {
|
||||
color: #0066CC;
|
||||
text-decoration: none;
|
||||
font-weight: 600;
|
||||
}
|
||||
.sidebar a:hover {
|
||||
color: #004499;
|
||||
text-decoration: underline;
|
||||
}
|
||||
.sidebar .current {
|
||||
color: #004499;
|
||||
}
|
||||
.sidebar hr {
|
||||
border: none;
|
||||
border-top: 1px solid #999;
|
||||
margin: 0.75em 0;
|
||||
}
|
||||
.main {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
a { color: #0000EE; }
|
||||
a:visited { color: #0066CC; }
|
||||
hr { border-style: solid; border-width: 1px 0 0 0; border-color: #999; }
|
||||
.center { text-align: center; }
|
||||
.box {
|
||||
border: 1px solid #999;
|
||||
padding: 0.5em;
|
||||
margin: 0.5em 0;
|
||||
}
|
||||
.stub {
|
||||
border: 1px solid #999;
|
||||
background: #f0f0f0;
|
||||
padding: 0.75em;
|
||||
margin: 1em 0;
|
||||
}
|
||||
.stub p { margin: 0; }
|
||||
.doc-list {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
.doc-list li {
|
||||
margin: 0.75em 0;
|
||||
}
|
||||
.doc-list a {
|
||||
font-weight: 600;
|
||||
}
|
||||
.doc-list p {
|
||||
margin: 0.25em 0 0 1.5em;
|
||||
color: #555;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
@media (max-width: 700px) {
|
||||
body {
|
||||
flex-direction: column;
|
||||
gap: 1em;
|
||||
padding: 1em 0.75em;
|
||||
}
|
||||
.sidebar {
|
||||
width: auto;
|
||||
}
|
||||
.sidebar ul {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0 1em;
|
||||
}
|
||||
.sidebar hr {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="sidebar">
|
||||
<ul>
|
||||
<li><a href="../../index.html">Home</a></li>
|
||||
<li><a href="../../downloads.html">Downloads</a></li>
|
||||
<li><a href="../index.html">Documentation</a></li>
|
||||
<li><a href="https://git.montaukos.org/daniel/MontaukOS/issues">Issue tracker</a></li>
|
||||
<li><a href="https://git.montaukos.org/daniel/MontaukOS">Git</a></li>
|
||||
</ul>
|
||||
<hr>
|
||||
<ul>
|
||||
<li><a href="index.html">Application Programmer's Manual</a></li>
|
||||
<li><a href="api.html" class="current">Montauk API</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="main">
|
||||
<div class="center">
|
||||
<h1>Montauk API</h1>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
|
||||
<div class="stub">
|
||||
<p><b>This page is a stub.</b> It has been created as a placeholder while the
|
||||
MontaukOS documentation is reorganised, and does not have any content yet.</p>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
|
||||
<div class="center">
|
||||
<a href="../index.html">Back to Documentation Index</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,144 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="description" content="MontaukOS - Application management">
|
||||
<title>Application management - MontaukOS</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Open+Sans:wght@400;600&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
body {
|
||||
font-family: 'Open Sans', Arial, sans-serif;
|
||||
max-width: 960px;
|
||||
margin: 0 auto;
|
||||
padding: 1em;
|
||||
line-height: 1.5;
|
||||
display: flex;
|
||||
gap: 2em;
|
||||
}
|
||||
p { margin: 0 0 0.5em; }
|
||||
h2 { margin: 0.5em 0; }
|
||||
h3 { margin: 0.75em 0 0.35em; }
|
||||
.sidebar {
|
||||
width: 160px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.sidebar ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
.sidebar li {
|
||||
margin: 0.5em 0;
|
||||
}
|
||||
.sidebar a {
|
||||
color: #0066CC;
|
||||
text-decoration: none;
|
||||
font-weight: 600;
|
||||
}
|
||||
.sidebar a:hover {
|
||||
color: #004499;
|
||||
text-decoration: underline;
|
||||
}
|
||||
.sidebar .current {
|
||||
color: #004499;
|
||||
}
|
||||
.sidebar hr {
|
||||
border: none;
|
||||
border-top: 1px solid #999;
|
||||
margin: 0.75em 0;
|
||||
}
|
||||
.main {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
a { color: #0000EE; }
|
||||
a:visited { color: #0066CC; }
|
||||
hr { border-style: solid; border-width: 1px 0 0 0; border-color: #999; }
|
||||
.center { text-align: center; }
|
||||
.box {
|
||||
border: 1px solid #999;
|
||||
padding: 0.5em;
|
||||
margin: 0.5em 0;
|
||||
}
|
||||
.stub {
|
||||
border: 1px solid #999;
|
||||
background: #f0f0f0;
|
||||
padding: 0.75em;
|
||||
margin: 1em 0;
|
||||
}
|
||||
.stub p { margin: 0; }
|
||||
.doc-list {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
.doc-list li {
|
||||
margin: 0.75em 0;
|
||||
}
|
||||
.doc-list a {
|
||||
font-weight: 600;
|
||||
}
|
||||
.doc-list p {
|
||||
margin: 0.25em 0 0 1.5em;
|
||||
color: #555;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
@media (max-width: 700px) {
|
||||
body {
|
||||
flex-direction: column;
|
||||
gap: 1em;
|
||||
padding: 1em 0.75em;
|
||||
}
|
||||
.sidebar {
|
||||
width: auto;
|
||||
}
|
||||
.sidebar ul {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0 1em;
|
||||
}
|
||||
.sidebar hr {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="sidebar">
|
||||
<ul>
|
||||
<li><a href="../../index.html">Home</a></li>
|
||||
<li><a href="../../downloads.html">Downloads</a></li>
|
||||
<li><a href="../index.html">Documentation</a></li>
|
||||
<li><a href="https://git.montaukos.org/daniel/MontaukOS/issues">Issue tracker</a></li>
|
||||
<li><a href="https://git.montaukos.org/daniel/MontaukOS">Git</a></li>
|
||||
</ul>
|
||||
<hr>
|
||||
<ul>
|
||||
<li><a href="index.html">Application Programmer's Manual</a></li>
|
||||
<li><a href="apps.html" class="current">Application management</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="main">
|
||||
<div class="center">
|
||||
<h1>Application management</h1>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
|
||||
<div class="stub">
|
||||
<p><b>This page is a stub.</b> It has been created as a placeholder while the
|
||||
MontaukOS documentation is reorganised, and does not have any content yet.</p>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
|
||||
<div class="center">
|
||||
<a href="../index.html">Back to Documentation Index</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 8.9 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 18 KiB |
@@ -1,291 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="description" content="MontaukOS dialogs library API">
|
||||
<title>Dialogs Library - MontaukOS</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Open+Sans:wght@400;600&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
body {
|
||||
font-family: 'Open Sans', Arial, sans-serif;
|
||||
max-width: 960px;
|
||||
margin: 0 auto;
|
||||
padding: 1em;
|
||||
line-height: 1.5;
|
||||
display: flex;
|
||||
gap: 2em;
|
||||
}
|
||||
p { margin: 0 0 0.5em; }
|
||||
h2 { margin: 0.5em 0; }
|
||||
h3 { margin: 0.75em 0 0.35em; }
|
||||
.sidebar {
|
||||
width: 160px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.sidebar ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
.sidebar li {
|
||||
margin: 0.5em 0;
|
||||
}
|
||||
.sidebar a {
|
||||
color: #0066CC;
|
||||
text-decoration: none;
|
||||
font-weight: 600;
|
||||
}
|
||||
.sidebar a:hover {
|
||||
color: #004499;
|
||||
text-decoration: underline;
|
||||
}
|
||||
.sidebar .current {
|
||||
color: #004499;
|
||||
}
|
||||
.sidebar hr {
|
||||
border: none;
|
||||
border-top: 1px solid #999;
|
||||
margin: 0.75em 0;
|
||||
}
|
||||
.main {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
a { color: #0000EE; }
|
||||
a:visited { color: #0066CC; }
|
||||
hr { border-style: solid; border-width: 1px 0 0 0; border-color: #999; }
|
||||
pre { background: #f0f0f0; padding: 0.5em; overflow-x: auto; }
|
||||
code { background: #f0f0f0; padding: 0 0.15em; }
|
||||
pre code { padding: 0; }
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
margin: 0.5em 0;
|
||||
}
|
||||
th, td {
|
||||
border: 1px solid #999;
|
||||
padding: 0.35em 0.5em;
|
||||
text-align: left;
|
||||
vertical-align: top;
|
||||
}
|
||||
th { background: #f0f0f0; }
|
||||
.center { text-align: center; }
|
||||
.box {
|
||||
border: 1px solid #999;
|
||||
padding: 0.5em;
|
||||
margin: 0.5em 0;
|
||||
}
|
||||
.figure {
|
||||
margin: 0.75em 0;
|
||||
}
|
||||
.figure img {
|
||||
display: block;
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
border: 1px solid #999;
|
||||
}
|
||||
.figure p {
|
||||
color: #555;
|
||||
font-size: 0.9em;
|
||||
margin: 0.25em 0 0;
|
||||
}
|
||||
@media (max-width: 700px) {
|
||||
body {
|
||||
flex-direction: column;
|
||||
gap: 1em;
|
||||
padding: 1em 0.75em;
|
||||
}
|
||||
.sidebar {
|
||||
width: auto;
|
||||
}
|
||||
.sidebar ul {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0 1em;
|
||||
}
|
||||
.sidebar hr {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="sidebar">
|
||||
<ul>
|
||||
<li><a href="../../index.html">Home</a></li>
|
||||
<li><a href="../../downloads.html">Downloads</a></li>
|
||||
<li><a href="../index.html">Documentation</a></li>
|
||||
<li><a href="https://git.montaukos.org/daniel/MontaukOS/issues">Issue tracker</a></li>
|
||||
<li><a href="https://git.montaukos.org/daniel/MontaukOS">Git</a></li>
|
||||
</ul>
|
||||
<hr>
|
||||
<ul>
|
||||
<li><a href="index.html">Application Programmer's Manual</a></li>
|
||||
<li><a href="dialogs.html" class="current">Dialogs library</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="main">
|
||||
<div class="center">
|
||||
<h1>Dialogs</h1>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
|
||||
<h2>Overview</h2>
|
||||
<p>
|
||||
The dialogs library (at 0:/os/dialogs.lib) provides the following system dialogs across apps:
|
||||
</p>
|
||||
|
||||
<ul>
|
||||
<li>File selection (Open)</li>
|
||||
<li>File save location selection (Save)</li>
|
||||
<li>Print setup/submission</li>
|
||||
<li>Message Box popups</li>
|
||||
</ul>
|
||||
|
||||
<h2>Message Box</h2>
|
||||
<p>
|
||||
<i>message_box</i> displays a popup window with text and buttons.
|
||||
</p>
|
||||
|
||||
<pre><code>gui::dialogs::MessageBoxResult message_box(
|
||||
const char* title,
|
||||
const char* message,
|
||||
gui::dialogs::MessageBoxButtons buttons = gui::dialogs::MESSAGE_BOX_OK,
|
||||
char* out_message = nullptr,
|
||||
int out_message_len = 0);</code></pre>
|
||||
|
||||
<h3>Button Sets</h3>
|
||||
<table>
|
||||
<tr><th>Value</th><th>Buttons</th></tr>
|
||||
<tr><td>MESSAGE_BOX_OK</td><td>OK</td></tr>
|
||||
<tr><td>MESSAGE_BOX_OK_CANCEL</td><td>OK, Cancel</td></tr>
|
||||
<tr><td>MESSAGE_BOX_YES_NO</td><td>Yes, No</td></tr>
|
||||
<tr><td>MESSAGE_BOX_YES_NO_CANCEL</td><td>Yes, No, Cancel</td></tr>
|
||||
</table>
|
||||
|
||||
<h3>Results</h3>
|
||||
<table>
|
||||
<tr><th>Value</th><th>Meaning</th></tr>
|
||||
<tr><td>MESSAGE_BOX_RESULT_OK</td><td>The user selected OK.</td></tr>
|
||||
<tr><td>MESSAGE_BOX_RESULT_CANCEL</td><td>The user selected Cancel or closed a cancelable dialog.</td></tr>
|
||||
<tr><td>MESSAGE_BOX_RESULT_YES</td><td>The user selected Yes.</td></tr>
|
||||
<tr><td>MESSAGE_BOX_RESULT_NO</td><td>The user selected No, or closed a Yes/No dialog.</td></tr>
|
||||
<tr><td>MESSAGE_BOX_RESULT_NONE</td><td>The dialog could not be loaded or invoked.</td></tr>
|
||||
</table>
|
||||
|
||||
<h3>Example</h3>
|
||||
<pre><code>#include <gui/dialogs.hpp>
|
||||
|
||||
void show_confirm() {
|
||||
auto result = gui::dialogs::message_box(
|
||||
"Close Document",
|
||||
"Discard unsaved changes?",
|
||||
gui::dialogs::MESSAGE_BOX_YES_NO_CANCEL);
|
||||
|
||||
if (result == gui::dialogs::MESSAGE_BOX_RESULT_YES) {
|
||||
/* discard and close */
|
||||
}
|
||||
}</code></pre>
|
||||
|
||||
<div class="figure">
|
||||
<img src="assets/discard_dialog.png" width="337" height="181" alt="Message box asking whether to discard unsaved changes">
|
||||
<p>Example <i>MESSAGE_BOX_YES_NO_CANCEL</i> dialog.</p>
|
||||
</div>
|
||||
|
||||
<h2>File Dialogs</h2>
|
||||
<p>
|
||||
File dialog helpers allow applications to use graphical file selection views (similar to the Files app) to select paths for Open/Save operations.
|
||||
</p>
|
||||
|
||||
<pre><code>bool open_file(
|
||||
const char* title,
|
||||
const char* initial_path,
|
||||
char* out_path,
|
||||
int out_path_len,
|
||||
char* out_message = nullptr,
|
||||
int out_message_len = 0);
|
||||
|
||||
bool save_file(
|
||||
const char* title,
|
||||
const char* initial_path,
|
||||
const char* suggested_name,
|
||||
char* out_path,
|
||||
int out_path_len,
|
||||
char* out_message = nullptr,
|
||||
int out_message_len = 0);</code></pre>
|
||||
|
||||
<p>
|
||||
Use <code>initial_path</code> to select the starting directory or current file
|
||||
context. <code>save_file</code> also accepts a <code>suggested_name</code> for
|
||||
the filename field.
|
||||
</p>
|
||||
|
||||
<h3>Open Example</h3>
|
||||
<pre><code>char path[256];
|
||||
char message[160];
|
||||
|
||||
if (gui::dialogs::open_file("Open File", "", path, sizeof(path),
|
||||
message, sizeof(message))) {
|
||||
/* open path */
|
||||
}</code></pre>
|
||||
|
||||
<h3>Save Example</h3>
|
||||
<pre><code>char path[256];
|
||||
|
||||
if (gui::dialogs::save_file("Save File", "", "untitled.txt",
|
||||
path, sizeof(path))) {
|
||||
/* write path */
|
||||
}</code></pre>
|
||||
|
||||
<div class="figure">
|
||||
<img src="assets/save_dialog.png" width="749" height="581" alt="Save file dialog showing folders and a filename field">
|
||||
<p>Save-file dialog as used by the MontaukOS Word Processor app.</p>
|
||||
</div>
|
||||
|
||||
<h2>Print Dialogs</h2>
|
||||
<p>
|
||||
Print dialog helpers allow applications to expose printer configuration to the user, and submit a file for printing.
|
||||
</p>
|
||||
|
||||
<pre><code>bool configure_print(
|
||||
const char* title,
|
||||
const char* initial_printer_uri,
|
||||
const char* job_name,
|
||||
char* out_printer_uri,
|
||||
int out_printer_uri_len,
|
||||
char* out_printer_name,
|
||||
int out_printer_name_len,
|
||||
uint32_t* out_copies = nullptr,
|
||||
char* out_message = nullptr,
|
||||
int out_message_len = 0);
|
||||
|
||||
bool print_file(
|
||||
const char* title,
|
||||
const char* source_path,
|
||||
const char* job_name,
|
||||
char* out_job_id,
|
||||
int out_job_id_len,
|
||||
char* out_message = nullptr,
|
||||
int out_message_len = 0);</code></pre>
|
||||
|
||||
|
||||
<h2>Include</h2>
|
||||
<pre><code>#include <gui/dialogs.hpp></code></pre><br>
|
||||
|
||||
<hr>
|
||||
<p class="center">Copyright © 2026 Montauk Operating System Project. All rights reserved.<br><br>Page last revised 26 May 2026.</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<div class="center">
|
||||
<a href="../index.html">Back to Documentation Index</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,171 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="description" content="MontaukOS Application Programmer's Manual">
|
||||
<title>Application Programmer's Manual - MontaukOS</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Open+Sans:wght@400;600&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
body {
|
||||
font-family: 'Open Sans', Arial, sans-serif;
|
||||
max-width: 960px;
|
||||
margin: 0 auto;
|
||||
padding: 1em;
|
||||
line-height: 1.5;
|
||||
display: flex;
|
||||
gap: 2em;
|
||||
}
|
||||
p { margin: 0 0 0.5em; }
|
||||
h2 { margin: 0.5em 0; }
|
||||
h3 { margin: 0.75em 0 0.35em; }
|
||||
.sidebar {
|
||||
width: 160px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.sidebar ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
.sidebar li {
|
||||
margin: 0.5em 0;
|
||||
}
|
||||
.sidebar a {
|
||||
color: #0066CC;
|
||||
text-decoration: none;
|
||||
font-weight: 600;
|
||||
}
|
||||
.sidebar a:hover {
|
||||
color: #004499;
|
||||
text-decoration: underline;
|
||||
}
|
||||
.sidebar .current {
|
||||
color: #004499;
|
||||
}
|
||||
.sidebar hr {
|
||||
border: none;
|
||||
border-top: 1px solid #999;
|
||||
margin: 0.75em 0;
|
||||
}
|
||||
.main {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
a { color: #0000EE; }
|
||||
a:visited { color: #0066CC; }
|
||||
hr { border-style: solid; border-width: 1px 0 0 0; border-color: #999; }
|
||||
.center { text-align: center; }
|
||||
.box {
|
||||
border: 1px solid #999;
|
||||
padding: 0.5em;
|
||||
margin: 0.5em 0;
|
||||
}
|
||||
.stub {
|
||||
border: 1px solid #999;
|
||||
background: #f0f0f0;
|
||||
padding: 0.75em;
|
||||
margin: 1em 0;
|
||||
}
|
||||
.stub p { margin: 0; }
|
||||
.doc-list {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
.doc-list li {
|
||||
margin: 0.75em 0;
|
||||
}
|
||||
.doc-list a {
|
||||
font-weight: 600;
|
||||
}
|
||||
.doc-list p {
|
||||
margin: 0.25em 0 0 1.5em;
|
||||
color: #555;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
@media (max-width: 700px) {
|
||||
body {
|
||||
flex-direction: column;
|
||||
gap: 1em;
|
||||
padding: 1em 0.75em;
|
||||
}
|
||||
.sidebar {
|
||||
width: auto;
|
||||
}
|
||||
.sidebar ul {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0 1em;
|
||||
}
|
||||
.sidebar hr {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="sidebar">
|
||||
<ul>
|
||||
<li><a href="../../index.html">Home</a></li>
|
||||
<li><a href="../../downloads.html">Downloads</a></li>
|
||||
<li><a href="../index.html">Documentation</a></li>
|
||||
<li><a href="https://git.montaukos.org/daniel/MontaukOS/issues">Issue tracker</a></li>
|
||||
<li><a href="https://git.montaukos.org/daniel/MontaukOS">Git</a></li>
|
||||
</ul>
|
||||
<hr>
|
||||
<ul>
|
||||
<li><a href="index.html" class="current">Application Programmer's Manual</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="main">
|
||||
<div class="center">
|
||||
<h1>Application Programmer's Manual</h1>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
|
||||
<div class="stub">
|
||||
<p><b>This page is a stub.</b> It has been created as a placeholder while the
|
||||
MontaukOS documentation is reorganised, and does not have any content yet.</p>
|
||||
</div>
|
||||
|
||||
<h2>Pages</h2>
|
||||
<ul class="doc-list">
|
||||
<li>
|
||||
<a href="toolchains.html">Toolchains</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="api.html">Montauk API</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="libc.html">C library</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="mtk.html">Montauk GUI Toolkit (MTK)</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="apps.html">Application management</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="sharedlibs.html">Shared libraries</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="porting.html">Application porting guide</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="dialogs.html">Dialogs library</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<hr>
|
||||
|
||||
<div class="center">
|
||||
<a href="../index.html">Back to Documentation Index</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,144 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="description" content="MontaukOS - C library">
|
||||
<title>C library - MontaukOS</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Open+Sans:wght@400;600&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
body {
|
||||
font-family: 'Open Sans', Arial, sans-serif;
|
||||
max-width: 960px;
|
||||
margin: 0 auto;
|
||||
padding: 1em;
|
||||
line-height: 1.5;
|
||||
display: flex;
|
||||
gap: 2em;
|
||||
}
|
||||
p { margin: 0 0 0.5em; }
|
||||
h2 { margin: 0.5em 0; }
|
||||
h3 { margin: 0.75em 0 0.35em; }
|
||||
.sidebar {
|
||||
width: 160px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.sidebar ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
.sidebar li {
|
||||
margin: 0.5em 0;
|
||||
}
|
||||
.sidebar a {
|
||||
color: #0066CC;
|
||||
text-decoration: none;
|
||||
font-weight: 600;
|
||||
}
|
||||
.sidebar a:hover {
|
||||
color: #004499;
|
||||
text-decoration: underline;
|
||||
}
|
||||
.sidebar .current {
|
||||
color: #004499;
|
||||
}
|
||||
.sidebar hr {
|
||||
border: none;
|
||||
border-top: 1px solid #999;
|
||||
margin: 0.75em 0;
|
||||
}
|
||||
.main {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
a { color: #0000EE; }
|
||||
a:visited { color: #0066CC; }
|
||||
hr { border-style: solid; border-width: 1px 0 0 0; border-color: #999; }
|
||||
.center { text-align: center; }
|
||||
.box {
|
||||
border: 1px solid #999;
|
||||
padding: 0.5em;
|
||||
margin: 0.5em 0;
|
||||
}
|
||||
.stub {
|
||||
border: 1px solid #999;
|
||||
background: #f0f0f0;
|
||||
padding: 0.75em;
|
||||
margin: 1em 0;
|
||||
}
|
||||
.stub p { margin: 0; }
|
||||
.doc-list {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
.doc-list li {
|
||||
margin: 0.75em 0;
|
||||
}
|
||||
.doc-list a {
|
||||
font-weight: 600;
|
||||
}
|
||||
.doc-list p {
|
||||
margin: 0.25em 0 0 1.5em;
|
||||
color: #555;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
@media (max-width: 700px) {
|
||||
body {
|
||||
flex-direction: column;
|
||||
gap: 1em;
|
||||
padding: 1em 0.75em;
|
||||
}
|
||||
.sidebar {
|
||||
width: auto;
|
||||
}
|
||||
.sidebar ul {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0 1em;
|
||||
}
|
||||
.sidebar hr {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="sidebar">
|
||||
<ul>
|
||||
<li><a href="../../index.html">Home</a></li>
|
||||
<li><a href="../../downloads.html">Downloads</a></li>
|
||||
<li><a href="../index.html">Documentation</a></li>
|
||||
<li><a href="https://git.montaukos.org/daniel/MontaukOS/issues">Issue tracker</a></li>
|
||||
<li><a href="https://git.montaukos.org/daniel/MontaukOS">Git</a></li>
|
||||
</ul>
|
||||
<hr>
|
||||
<ul>
|
||||
<li><a href="index.html">Application Programmer's Manual</a></li>
|
||||
<li><a href="libc.html" class="current">C library</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="main">
|
||||
<div class="center">
|
||||
<h1>C library</h1>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
|
||||
<div class="stub">
|
||||
<p><b>This page is a stub.</b> It has been created as a placeholder while the
|
||||
MontaukOS documentation is reorganised, and does not have any content yet.</p>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
|
||||
<div class="center">
|
||||
<a href="../index.html">Back to Documentation Index</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,144 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="description" content="MontaukOS - Montauk GUI Toolkit (MTK)">
|
||||
<title>Montauk GUI Toolkit (MTK) - MontaukOS</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Open+Sans:wght@400;600&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
body {
|
||||
font-family: 'Open Sans', Arial, sans-serif;
|
||||
max-width: 960px;
|
||||
margin: 0 auto;
|
||||
padding: 1em;
|
||||
line-height: 1.5;
|
||||
display: flex;
|
||||
gap: 2em;
|
||||
}
|
||||
p { margin: 0 0 0.5em; }
|
||||
h2 { margin: 0.5em 0; }
|
||||
h3 { margin: 0.75em 0 0.35em; }
|
||||
.sidebar {
|
||||
width: 160px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.sidebar ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
.sidebar li {
|
||||
margin: 0.5em 0;
|
||||
}
|
||||
.sidebar a {
|
||||
color: #0066CC;
|
||||
text-decoration: none;
|
||||
font-weight: 600;
|
||||
}
|
||||
.sidebar a:hover {
|
||||
color: #004499;
|
||||
text-decoration: underline;
|
||||
}
|
||||
.sidebar .current {
|
||||
color: #004499;
|
||||
}
|
||||
.sidebar hr {
|
||||
border: none;
|
||||
border-top: 1px solid #999;
|
||||
margin: 0.75em 0;
|
||||
}
|
||||
.main {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
a { color: #0000EE; }
|
||||
a:visited { color: #0066CC; }
|
||||
hr { border-style: solid; border-width: 1px 0 0 0; border-color: #999; }
|
||||
.center { text-align: center; }
|
||||
.box {
|
||||
border: 1px solid #999;
|
||||
padding: 0.5em;
|
||||
margin: 0.5em 0;
|
||||
}
|
||||
.stub {
|
||||
border: 1px solid #999;
|
||||
background: #f0f0f0;
|
||||
padding: 0.75em;
|
||||
margin: 1em 0;
|
||||
}
|
||||
.stub p { margin: 0; }
|
||||
.doc-list {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
.doc-list li {
|
||||
margin: 0.75em 0;
|
||||
}
|
||||
.doc-list a {
|
||||
font-weight: 600;
|
||||
}
|
||||
.doc-list p {
|
||||
margin: 0.25em 0 0 1.5em;
|
||||
color: #555;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
@media (max-width: 700px) {
|
||||
body {
|
||||
flex-direction: column;
|
||||
gap: 1em;
|
||||
padding: 1em 0.75em;
|
||||
}
|
||||
.sidebar {
|
||||
width: auto;
|
||||
}
|
||||
.sidebar ul {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0 1em;
|
||||
}
|
||||
.sidebar hr {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="sidebar">
|
||||
<ul>
|
||||
<li><a href="../../index.html">Home</a></li>
|
||||
<li><a href="../../downloads.html">Downloads</a></li>
|
||||
<li><a href="../index.html">Documentation</a></li>
|
||||
<li><a href="https://git.montaukos.org/daniel/MontaukOS/issues">Issue tracker</a></li>
|
||||
<li><a href="https://git.montaukos.org/daniel/MontaukOS">Git</a></li>
|
||||
</ul>
|
||||
<hr>
|
||||
<ul>
|
||||
<li><a href="index.html">Application Programmer's Manual</a></li>
|
||||
<li><a href="mtk.html" class="current">Montauk GUI Toolkit (MTK)</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="main">
|
||||
<div class="center">
|
||||
<h1>Montauk GUI Toolkit (MTK)</h1>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
|
||||
<div class="stub">
|
||||
<p><b>This page is a stub.</b> It has been created as a placeholder while the
|
||||
MontaukOS documentation is reorganised, and does not have any content yet.</p>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
|
||||
<div class="center">
|
||||
<a href="../index.html">Back to Documentation Index</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,144 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="description" content="MontaukOS - Application porting guide">
|
||||
<title>Application porting guide - MontaukOS</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Open+Sans:wght@400;600&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
body {
|
||||
font-family: 'Open Sans', Arial, sans-serif;
|
||||
max-width: 960px;
|
||||
margin: 0 auto;
|
||||
padding: 1em;
|
||||
line-height: 1.5;
|
||||
display: flex;
|
||||
gap: 2em;
|
||||
}
|
||||
p { margin: 0 0 0.5em; }
|
||||
h2 { margin: 0.5em 0; }
|
||||
h3 { margin: 0.75em 0 0.35em; }
|
||||
.sidebar {
|
||||
width: 160px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.sidebar ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
.sidebar li {
|
||||
margin: 0.5em 0;
|
||||
}
|
||||
.sidebar a {
|
||||
color: #0066CC;
|
||||
text-decoration: none;
|
||||
font-weight: 600;
|
||||
}
|
||||
.sidebar a:hover {
|
||||
color: #004499;
|
||||
text-decoration: underline;
|
||||
}
|
||||
.sidebar .current {
|
||||
color: #004499;
|
||||
}
|
||||
.sidebar hr {
|
||||
border: none;
|
||||
border-top: 1px solid #999;
|
||||
margin: 0.75em 0;
|
||||
}
|
||||
.main {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
a { color: #0000EE; }
|
||||
a:visited { color: #0066CC; }
|
||||
hr { border-style: solid; border-width: 1px 0 0 0; border-color: #999; }
|
||||
.center { text-align: center; }
|
||||
.box {
|
||||
border: 1px solid #999;
|
||||
padding: 0.5em;
|
||||
margin: 0.5em 0;
|
||||
}
|
||||
.stub {
|
||||
border: 1px solid #999;
|
||||
background: #f0f0f0;
|
||||
padding: 0.75em;
|
||||
margin: 1em 0;
|
||||
}
|
||||
.stub p { margin: 0; }
|
||||
.doc-list {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
.doc-list li {
|
||||
margin: 0.75em 0;
|
||||
}
|
||||
.doc-list a {
|
||||
font-weight: 600;
|
||||
}
|
||||
.doc-list p {
|
||||
margin: 0.25em 0 0 1.5em;
|
||||
color: #555;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
@media (max-width: 700px) {
|
||||
body {
|
||||
flex-direction: column;
|
||||
gap: 1em;
|
||||
padding: 1em 0.75em;
|
||||
}
|
||||
.sidebar {
|
||||
width: auto;
|
||||
}
|
||||
.sidebar ul {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0 1em;
|
||||
}
|
||||
.sidebar hr {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="sidebar">
|
||||
<ul>
|
||||
<li><a href="../../index.html">Home</a></li>
|
||||
<li><a href="../../downloads.html">Downloads</a></li>
|
||||
<li><a href="../index.html">Documentation</a></li>
|
||||
<li><a href="https://git.montaukos.org/daniel/MontaukOS/issues">Issue tracker</a></li>
|
||||
<li><a href="https://git.montaukos.org/daniel/MontaukOS">Git</a></li>
|
||||
</ul>
|
||||
<hr>
|
||||
<ul>
|
||||
<li><a href="index.html">Application Programmer's Manual</a></li>
|
||||
<li><a href="porting.html" class="current">Application porting guide</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="main">
|
||||
<div class="center">
|
||||
<h1>Application porting guide</h1>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
|
||||
<div class="stub">
|
||||
<p><b>This page is a stub.</b> It has been created as a placeholder while the
|
||||
MontaukOS documentation is reorganised, and does not have any content yet.</p>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
|
||||
<div class="center">
|
||||
<a href="../index.html">Back to Documentation Index</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,144 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="description" content="MontaukOS - Shared libraries">
|
||||
<title>Shared libraries - MontaukOS</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Open+Sans:wght@400;600&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
body {
|
||||
font-family: 'Open Sans', Arial, sans-serif;
|
||||
max-width: 960px;
|
||||
margin: 0 auto;
|
||||
padding: 1em;
|
||||
line-height: 1.5;
|
||||
display: flex;
|
||||
gap: 2em;
|
||||
}
|
||||
p { margin: 0 0 0.5em; }
|
||||
h2 { margin: 0.5em 0; }
|
||||
h3 { margin: 0.75em 0 0.35em; }
|
||||
.sidebar {
|
||||
width: 160px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.sidebar ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
.sidebar li {
|
||||
margin: 0.5em 0;
|
||||
}
|
||||
.sidebar a {
|
||||
color: #0066CC;
|
||||
text-decoration: none;
|
||||
font-weight: 600;
|
||||
}
|
||||
.sidebar a:hover {
|
||||
color: #004499;
|
||||
text-decoration: underline;
|
||||
}
|
||||
.sidebar .current {
|
||||
color: #004499;
|
||||
}
|
||||
.sidebar hr {
|
||||
border: none;
|
||||
border-top: 1px solid #999;
|
||||
margin: 0.75em 0;
|
||||
}
|
||||
.main {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
a { color: #0000EE; }
|
||||
a:visited { color: #0066CC; }
|
||||
hr { border-style: solid; border-width: 1px 0 0 0; border-color: #999; }
|
||||
.center { text-align: center; }
|
||||
.box {
|
||||
border: 1px solid #999;
|
||||
padding: 0.5em;
|
||||
margin: 0.5em 0;
|
||||
}
|
||||
.stub {
|
||||
border: 1px solid #999;
|
||||
background: #f0f0f0;
|
||||
padding: 0.75em;
|
||||
margin: 1em 0;
|
||||
}
|
||||
.stub p { margin: 0; }
|
||||
.doc-list {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
.doc-list li {
|
||||
margin: 0.75em 0;
|
||||
}
|
||||
.doc-list a {
|
||||
font-weight: 600;
|
||||
}
|
||||
.doc-list p {
|
||||
margin: 0.25em 0 0 1.5em;
|
||||
color: #555;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
@media (max-width: 700px) {
|
||||
body {
|
||||
flex-direction: column;
|
||||
gap: 1em;
|
||||
padding: 1em 0.75em;
|
||||
}
|
||||
.sidebar {
|
||||
width: auto;
|
||||
}
|
||||
.sidebar ul {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0 1em;
|
||||
}
|
||||
.sidebar hr {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="sidebar">
|
||||
<ul>
|
||||
<li><a href="../../index.html">Home</a></li>
|
||||
<li><a href="../../downloads.html">Downloads</a></li>
|
||||
<li><a href="../index.html">Documentation</a></li>
|
||||
<li><a href="https://git.montaukos.org/daniel/MontaukOS/issues">Issue tracker</a></li>
|
||||
<li><a href="https://git.montaukos.org/daniel/MontaukOS">Git</a></li>
|
||||
</ul>
|
||||
<hr>
|
||||
<ul>
|
||||
<li><a href="index.html">Application Programmer's Manual</a></li>
|
||||
<li><a href="sharedlibs.html" class="current">Shared libraries</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="main">
|
||||
<div class="center">
|
||||
<h1>Shared libraries</h1>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
|
||||
<div class="stub">
|
||||
<p><b>This page is a stub.</b> It has been created as a placeholder while the
|
||||
MontaukOS documentation is reorganised, and does not have any content yet.</p>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
|
||||
<div class="center">
|
||||
<a href="../index.html">Back to Documentation Index</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,144 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="description" content="MontaukOS - Toolchains">
|
||||
<title>Toolchains - MontaukOS</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Open+Sans:wght@400;600&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
body {
|
||||
font-family: 'Open Sans', Arial, sans-serif;
|
||||
max-width: 960px;
|
||||
margin: 0 auto;
|
||||
padding: 1em;
|
||||
line-height: 1.5;
|
||||
display: flex;
|
||||
gap: 2em;
|
||||
}
|
||||
p { margin: 0 0 0.5em; }
|
||||
h2 { margin: 0.5em 0; }
|
||||
h3 { margin: 0.75em 0 0.35em; }
|
||||
.sidebar {
|
||||
width: 160px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.sidebar ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
.sidebar li {
|
||||
margin: 0.5em 0;
|
||||
}
|
||||
.sidebar a {
|
||||
color: #0066CC;
|
||||
text-decoration: none;
|
||||
font-weight: 600;
|
||||
}
|
||||
.sidebar a:hover {
|
||||
color: #004499;
|
||||
text-decoration: underline;
|
||||
}
|
||||
.sidebar .current {
|
||||
color: #004499;
|
||||
}
|
||||
.sidebar hr {
|
||||
border: none;
|
||||
border-top: 1px solid #999;
|
||||
margin: 0.75em 0;
|
||||
}
|
||||
.main {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
a { color: #0000EE; }
|
||||
a:visited { color: #0066CC; }
|
||||
hr { border-style: solid; border-width: 1px 0 0 0; border-color: #999; }
|
||||
.center { text-align: center; }
|
||||
.box {
|
||||
border: 1px solid #999;
|
||||
padding: 0.5em;
|
||||
margin: 0.5em 0;
|
||||
}
|
||||
.stub {
|
||||
border: 1px solid #999;
|
||||
background: #f0f0f0;
|
||||
padding: 0.75em;
|
||||
margin: 1em 0;
|
||||
}
|
||||
.stub p { margin: 0; }
|
||||
.doc-list {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
.doc-list li {
|
||||
margin: 0.75em 0;
|
||||
}
|
||||
.doc-list a {
|
||||
font-weight: 600;
|
||||
}
|
||||
.doc-list p {
|
||||
margin: 0.25em 0 0 1.5em;
|
||||
color: #555;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
@media (max-width: 700px) {
|
||||
body {
|
||||
flex-direction: column;
|
||||
gap: 1em;
|
||||
padding: 1em 0.75em;
|
||||
}
|
||||
.sidebar {
|
||||
width: auto;
|
||||
}
|
||||
.sidebar ul {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0 1em;
|
||||
}
|
||||
.sidebar hr {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="sidebar">
|
||||
<ul>
|
||||
<li><a href="../../index.html">Home</a></li>
|
||||
<li><a href="../../downloads.html">Downloads</a></li>
|
||||
<li><a href="../index.html">Documentation</a></li>
|
||||
<li><a href="https://git.montaukos.org/daniel/MontaukOS/issues">Issue tracker</a></li>
|
||||
<li><a href="https://git.montaukos.org/daniel/MontaukOS">Git</a></li>
|
||||
</ul>
|
||||
<hr>
|
||||
<ul>
|
||||
<li><a href="index.html">Application Programmer's Manual</a></li>
|
||||
<li><a href="toolchains.html" class="current">Toolchains</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="main">
|
||||
<div class="center">
|
||||
<h1>Toolchains</h1>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
|
||||
<div class="stub">
|
||||
<p><b>This page is a stub.</b> It has been created as a placeholder while the
|
||||
MontaukOS documentation is reorganised, and does not have any content yet.</p>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
|
||||
<div class="center">
|
||||
<a href="../index.html">Back to Documentation Index</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,156 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="description" content="MontaukOS Documentation">
|
||||
<title>Documentation - MontaukOS</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Open+Sans:wght@400;600&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
body {
|
||||
font-family: 'Open Sans', Arial, sans-serif;
|
||||
max-width: 960px;
|
||||
margin: 0 auto;
|
||||
padding: 1em;
|
||||
line-height: 1.5;
|
||||
display: flex;
|
||||
gap: 2em;
|
||||
}
|
||||
p { margin: 0 0 0.5em; }
|
||||
h2 { margin: 0.5em 0; }
|
||||
h3 { margin: 0.75em 0 0.35em; }
|
||||
.sidebar {
|
||||
width: 160px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.sidebar ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
.sidebar li {
|
||||
margin: 0.5em 0;
|
||||
}
|
||||
.sidebar a {
|
||||
color: #0066CC;
|
||||
text-decoration: none;
|
||||
font-weight: 600;
|
||||
}
|
||||
.sidebar a:hover {
|
||||
color: #004499;
|
||||
text-decoration: underline;
|
||||
}
|
||||
.sidebar .current {
|
||||
color: #004499;
|
||||
}
|
||||
.sidebar hr {
|
||||
border: none;
|
||||
border-top: 1px solid #999;
|
||||
margin: 0.75em 0;
|
||||
}
|
||||
.main {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
a { color: #0000EE; }
|
||||
a:visited { color: #0066CC; }
|
||||
hr { border-style: solid; border-width: 1px 0 0 0; border-color: #999; }
|
||||
.center { text-align: center; }
|
||||
.box {
|
||||
border: 1px solid #999;
|
||||
padding: 0.5em;
|
||||
margin: 0.5em 0;
|
||||
}
|
||||
.stub {
|
||||
border: 1px solid #999;
|
||||
background: #f0f0f0;
|
||||
padding: 0.75em;
|
||||
margin: 1em 0;
|
||||
}
|
||||
.stub p { margin: 0; }
|
||||
.doc-list {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
.doc-list li {
|
||||
margin: 0.75em 0;
|
||||
}
|
||||
.doc-list a {
|
||||
font-weight: 600;
|
||||
}
|
||||
.doc-list p {
|
||||
margin: 0.25em 0 0 1.5em;
|
||||
color: #555;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
@media (max-width: 700px) {
|
||||
body {
|
||||
flex-direction: column;
|
||||
gap: 1em;
|
||||
padding: 1em 0.75em;
|
||||
}
|
||||
.sidebar {
|
||||
width: auto;
|
||||
}
|
||||
.sidebar ul {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0 1em;
|
||||
}
|
||||
.sidebar hr {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="sidebar">
|
||||
<ul>
|
||||
<li><a href="../index.html">Home</a></li>
|
||||
<li><a href="../downloads.html">Downloads</a></li>
|
||||
<li><a href="index.html">Documentation</a></li>
|
||||
<li><a href="https://git.montaukos.org/daniel/MontaukOS/issues">Issue tracker</a></li>
|
||||
<li><a href="https://git.montaukos.org/daniel/MontaukOS">Git</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="main">
|
||||
<div class="center">
|
||||
<h1>Documentation</h1>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
|
||||
<h2>User Documentation</h2>
|
||||
<ul class="doc-list">
|
||||
<li>
|
||||
<a href="usersmanual/index.html">Tutorials</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="man/index.html">Man pages</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<hr>
|
||||
|
||||
<h2>Developer Documentation</h2>
|
||||
<ul class="doc-list">
|
||||
<li>
|
||||
<a href="apm/index.html">Application Programmer's Manual</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="osdev/index.html">Operating System Developer's Manual</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<hr>
|
||||
|
||||
<div class="center">
|
||||
<a href="../index.html">Back to Home</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,166 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="description" content="MontaukOS manual page: dhcp(1) - obtain network configuration via DHCP">
|
||||
<title>dhcp(1) - MontaukOS Manual</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Open+Sans:wght@400;600&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
body {
|
||||
font-family: 'Open Sans', Arial, sans-serif;
|
||||
max-width: 960px;
|
||||
margin: 0 auto;
|
||||
padding: 1em;
|
||||
line-height: 1.5;
|
||||
display: flex;
|
||||
gap: 2em;
|
||||
}
|
||||
p { margin: 0 0 0.5em; }
|
||||
h2 { margin: 0.5em 0; }
|
||||
.sidebar {
|
||||
width: 160px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.sidebar ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
.sidebar li {
|
||||
margin: 0.5em 0;
|
||||
}
|
||||
.sidebar a {
|
||||
color: #0066CC;
|
||||
text-decoration: none;
|
||||
font-weight: 600;
|
||||
}
|
||||
.sidebar a:hover {
|
||||
color: #004499;
|
||||
text-decoration: underline;
|
||||
}
|
||||
.sidebar .current {
|
||||
color: #004499;
|
||||
}
|
||||
.sidebar hr {
|
||||
border: none;
|
||||
border-top: 1px solid #999;
|
||||
margin: 0.75em 0;
|
||||
}
|
||||
.main {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
a { color: #0000EE; }
|
||||
a:visited { color: #0066CC; }
|
||||
hr { border-style: solid; border-width: 1px 0 0 0; border-color: #999; }
|
||||
pre { background: #f0f0f0; padding: 0.5em; overflow-x: auto; }
|
||||
code { background: #f0f0f0; padding: 0 0.15em; }
|
||||
pre code { padding: 0; }
|
||||
.center { text-align: center; }
|
||||
.box {
|
||||
border: 1px solid #999;
|
||||
padding: 0.5em;
|
||||
margin: 0.5em 0;
|
||||
}
|
||||
@media (max-width: 700px) {
|
||||
body {
|
||||
flex-direction: column;
|
||||
gap: 1em;
|
||||
padding: 1em 0.75em;
|
||||
}
|
||||
.sidebar {
|
||||
width: auto;
|
||||
}
|
||||
.sidebar ul {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0 1em;
|
||||
}
|
||||
.sidebar hr {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="sidebar">
|
||||
<ul>
|
||||
<li><a href="../../index.html">Home</a></li>
|
||||
<li><a href="../../downloads.html">Downloads</a></li>
|
||||
<li><a href="../index.html">Documentation</a></li>
|
||||
<li><a href="https://git.montaukos.org/daniel/MontaukOS/issues">Issue tracker</a></li>
|
||||
<li><a href="https://git.montaukos.org/daniel/MontaukOS">Git</a></li>
|
||||
</ul>
|
||||
<hr>
|
||||
<ul>
|
||||
<li><a href="index.html" class="current">Man Pages</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="main">
|
||||
<div class="center">
|
||||
<h1>dhcp(1)</h1>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
|
||||
<pre><code><strong>NAME</strong>
|
||||
dhcp - obtain network configuration via DHCP
|
||||
|
||||
<strong>SYNOPSIS</strong>
|
||||
dhcp
|
||||
|
||||
<strong>DESCRIPTION</strong>
|
||||
The DHCP client automatically obtains an IP address, subnet mask,
|
||||
default gateway, and other network parameters from a DHCP server
|
||||
on the local network using the Dynamic Host Configuration Protocol
|
||||
(RFC 2131).
|
||||
|
||||
On success the network configuration is applied immediately via
|
||||
set_netcfg(). On failure the original configuration is restored.
|
||||
|
||||
The client is run automatically by the init system at boot, but
|
||||
may also be invoked manually from the shell.
|
||||
|
||||
<strong>PROTOCOL</strong>
|
||||
The client performs the standard four-message DHCP exchange:
|
||||
|
||||
1. DHCPDISCOVER Broadcast to 255.255.255.255:67
|
||||
2. DHCPOFFER Server offers an IP address
|
||||
3. DHCPREQUEST Client accepts the offered address
|
||||
4. DHCPACK Server confirms the lease
|
||||
|
||||
The BROADCAST flag (0x8000) is set so that server replies are
|
||||
sent to the broadcast address, since the client has no IP yet.
|
||||
|
||||
Each step has a 10-second timeout. If no response is received
|
||||
the client exits with an error and restores the previous config.
|
||||
|
||||
<strong>OUTPUT</strong>
|
||||
On success the client prints the assigned configuration:
|
||||
|
||||
IP Address, Subnet Mask, Gateway, DNS Server, Lease Time
|
||||
|
||||
<strong>OPTIONS</strong>
|
||||
The DHCP client requests the following options from the server:
|
||||
|
||||
1 Subnet Mask
|
||||
3 Router (default gateway)
|
||||
6 DNS Server
|
||||
51 Lease Time
|
||||
|
||||
<strong>SEE ALSO</strong>
|
||||
ifconfig(1), shell(1), syscalls(2)</code></pre>
|
||||
|
||||
<hr>
|
||||
|
||||
<div class="center">
|
||||
<a href="../index.html">Back to Documentation Index</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,169 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="description" content="MontaukOS manual page: edit(1) - text editor for MontaukOS">
|
||||
<title>edit(1) - MontaukOS Manual</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Open+Sans:wght@400;600&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
body {
|
||||
font-family: 'Open Sans', Arial, sans-serif;
|
||||
max-width: 960px;
|
||||
margin: 0 auto;
|
||||
padding: 1em;
|
||||
line-height: 1.5;
|
||||
display: flex;
|
||||
gap: 2em;
|
||||
}
|
||||
p { margin: 0 0 0.5em; }
|
||||
h2 { margin: 0.5em 0; }
|
||||
.sidebar {
|
||||
width: 160px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.sidebar ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
.sidebar li {
|
||||
margin: 0.5em 0;
|
||||
}
|
||||
.sidebar a {
|
||||
color: #0066CC;
|
||||
text-decoration: none;
|
||||
font-weight: 600;
|
||||
}
|
||||
.sidebar a:hover {
|
||||
color: #004499;
|
||||
text-decoration: underline;
|
||||
}
|
||||
.sidebar .current {
|
||||
color: #004499;
|
||||
}
|
||||
.sidebar hr {
|
||||
border: none;
|
||||
border-top: 1px solid #999;
|
||||
margin: 0.75em 0;
|
||||
}
|
||||
.main {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
a { color: #0000EE; }
|
||||
a:visited { color: #0066CC; }
|
||||
hr { border-style: solid; border-width: 1px 0 0 0; border-color: #999; }
|
||||
pre { background: #f0f0f0; padding: 0.5em; overflow-x: auto; }
|
||||
code { background: #f0f0f0; padding: 0 0.15em; }
|
||||
pre code { padding: 0; }
|
||||
.center { text-align: center; }
|
||||
.box {
|
||||
border: 1px solid #999;
|
||||
padding: 0.5em;
|
||||
margin: 0.5em 0;
|
||||
}
|
||||
@media (max-width: 700px) {
|
||||
body {
|
||||
flex-direction: column;
|
||||
gap: 1em;
|
||||
padding: 1em 0.75em;
|
||||
}
|
||||
.sidebar {
|
||||
width: auto;
|
||||
}
|
||||
.sidebar ul {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0 1em;
|
||||
}
|
||||
.sidebar hr {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="sidebar">
|
||||
<ul>
|
||||
<li><a href="../../index.html">Home</a></li>
|
||||
<li><a href="../../downloads.html">Downloads</a></li>
|
||||
<li><a href="../index.html">Documentation</a></li>
|
||||
<li><a href="https://git.montaukos.org/daniel/MontaukOS/issues">Issue tracker</a></li>
|
||||
<li><a href="https://git.montaukos.org/daniel/MontaukOS">Git</a></li>
|
||||
</ul>
|
||||
<hr>
|
||||
<ul>
|
||||
<li><a href="index.html" class="current">Man Pages</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="main">
|
||||
<div class="center">
|
||||
<h1>edit(1)</h1>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
|
||||
<pre><code><strong>NAME</strong>
|
||||
edit - text editor for MontaukOS
|
||||
|
||||
<strong>SYNOPSIS</strong>
|
||||
edit [filename]
|
||||
|
||||
<strong>DESCRIPTION</strong>
|
||||
edit is an interactive text editor. When invoked with a filename,
|
||||
it opens the file for editing. If the file does not exist, a new
|
||||
empty buffer is created and will be saved to that path on write.
|
||||
|
||||
When invoked without arguments, edit opens an empty buffer. You
|
||||
will be prompted for a filename when saving.
|
||||
|
||||
<strong>KEYBOARD SHORTCUTS</strong>
|
||||
|
||||
<strong>Navigation</strong>
|
||||
Arrow Keys Move cursor up/down/left/right
|
||||
Home Move to start of line
|
||||
End Move to end of line
|
||||
Page Up Scroll up one page
|
||||
Page Down Scroll down one page
|
||||
|
||||
<strong>Editing</strong>
|
||||
Backspace Delete character before cursor
|
||||
Delete Delete character at cursor
|
||||
Enter Insert new line
|
||||
Tab Insert 4 spaces
|
||||
|
||||
<strong>Commands</strong>
|
||||
Ctrl+S Save file
|
||||
Ctrl+Q Quit (warns if unsaved changes)
|
||||
Ctrl+F Search for text
|
||||
Ctrl+G Find next occurrence
|
||||
|
||||
<strong>DISPLAY</strong>
|
||||
The top line shows the filename, a modified indicator [+],
|
||||
and the current cursor position (Ln, Col).
|
||||
|
||||
The bottom line shows keyboard shortcuts or status messages.
|
||||
|
||||
Line numbers are displayed in a gutter on the left side.
|
||||
Lines past the end of the file are marked with ~.
|
||||
|
||||
<strong>EXAMPLES</strong>
|
||||
edit intro.1 Edit a file
|
||||
edit Open a new empty buffer
|
||||
|
||||
<strong>SEE ALSO</strong>
|
||||
cat(1), shell(1)</code></pre>
|
||||
|
||||
<hr>
|
||||
|
||||
<div class="center">
|
||||
<a href="../index.html">Back to Documentation Index</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,180 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="description" content="MontaukOS manual page: fetch(1) - HTTP/HTTPS client for MontaukOS">
|
||||
<title>fetch(1) - MontaukOS Manual</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Open+Sans:wght@400;600&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
body {
|
||||
font-family: 'Open Sans', Arial, sans-serif;
|
||||
max-width: 960px;
|
||||
margin: 0 auto;
|
||||
padding: 1em;
|
||||
line-height: 1.5;
|
||||
display: flex;
|
||||
gap: 2em;
|
||||
}
|
||||
p { margin: 0 0 0.5em; }
|
||||
h2 { margin: 0.5em 0; }
|
||||
.sidebar {
|
||||
width: 160px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.sidebar ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
.sidebar li {
|
||||
margin: 0.5em 0;
|
||||
}
|
||||
.sidebar a {
|
||||
color: #0066CC;
|
||||
text-decoration: none;
|
||||
font-weight: 600;
|
||||
}
|
||||
.sidebar a:hover {
|
||||
color: #004499;
|
||||
text-decoration: underline;
|
||||
}
|
||||
.sidebar .current {
|
||||
color: #004499;
|
||||
}
|
||||
.sidebar hr {
|
||||
border: none;
|
||||
border-top: 1px solid #999;
|
||||
margin: 0.75em 0;
|
||||
}
|
||||
.main {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
a { color: #0000EE; }
|
||||
a:visited { color: #0066CC; }
|
||||
hr { border-style: solid; border-width: 1px 0 0 0; border-color: #999; }
|
||||
pre { background: #f0f0f0; padding: 0.5em; overflow-x: auto; }
|
||||
code { background: #f0f0f0; padding: 0 0.15em; }
|
||||
pre code { padding: 0; }
|
||||
.center { text-align: center; }
|
||||
.box {
|
||||
border: 1px solid #999;
|
||||
padding: 0.5em;
|
||||
margin: 0.5em 0;
|
||||
}
|
||||
@media (max-width: 700px) {
|
||||
body {
|
||||
flex-direction: column;
|
||||
gap: 1em;
|
||||
padding: 1em 0.75em;
|
||||
}
|
||||
.sidebar {
|
||||
width: auto;
|
||||
}
|
||||
.sidebar ul {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0 1em;
|
||||
}
|
||||
.sidebar hr {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="sidebar">
|
||||
<ul>
|
||||
<li><a href="../../index.html">Home</a></li>
|
||||
<li><a href="../../downloads.html">Downloads</a></li>
|
||||
<li><a href="../index.html">Documentation</a></li>
|
||||
<li><a href="https://git.montaukos.org/daniel/MontaukOS/issues">Issue tracker</a></li>
|
||||
<li><a href="https://git.montaukos.org/daniel/MontaukOS">Git</a></li>
|
||||
</ul>
|
||||
<hr>
|
||||
<ul>
|
||||
<li><a href="index.html" class="current">Man Pages</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="main">
|
||||
<div class="center">
|
||||
<h1>fetch(1)</h1>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
|
||||
<pre><code><strong>NAME</strong>
|
||||
fetch - HTTP/HTTPS client for MontaukOS
|
||||
|
||||
<strong>SYNOPSIS</strong>
|
||||
fetch [-v] <url>
|
||||
fetch [-v] <host> <port> [path]
|
||||
|
||||
<strong>DESCRIPTION</strong>
|
||||
fetch performs an HTTP/1.0 GET request and prints the response
|
||||
body to the terminal. Supports both plain HTTP and HTTPS (TLS 1.2)
|
||||
connections. By default only the body is printed.
|
||||
|
||||
In URL mode, the scheme (http:// or https://) determines whether
|
||||
TLS is used. The port defaults to 80 for HTTP and 443 for HTTPS.
|
||||
|
||||
In legacy mode, the host and port are specified as separate
|
||||
arguments and the connection is always plain HTTP.
|
||||
|
||||
The host may be an IP address or a hostname. Hostnames are
|
||||
resolved via the configured DNS server.
|
||||
|
||||
If no path is given, "/" is used.
|
||||
|
||||
<strong>OPTIONS</strong>
|
||||
<strong>-v</strong>
|
||||
Verbose mode. Print connection info, trust anchor count, TLS
|
||||
handshake progress, and the HTTP status/size header before
|
||||
the body.
|
||||
|
||||
<strong>EXAMPLES</strong>
|
||||
fetch https://icanhazip.com
|
||||
Print your public IP address over HTTPS.
|
||||
|
||||
fetch http://icanhazip.com
|
||||
Same, but over plain HTTP.
|
||||
|
||||
fetch -v https://example.com
|
||||
Fetch a page with verbose output showing:
|
||||
Connecting to example.com:443 (HTTPS)...
|
||||
Loaded 128 trust anchors
|
||||
TLS handshake...
|
||||
TLS connection established
|
||||
GET /
|
||||
HTTP 200 OK (1256 bytes)
|
||||
|
||||
fetch 10.0.68.1 80 /
|
||||
Fetch from a local server by IP (legacy syntax).
|
||||
|
||||
<strong>TLS SUPPORT</strong>
|
||||
HTTPS connections use BearSSL for TLS 1.2. Server certificates
|
||||
are validated against the system CA bundle at
|
||||
0:/os/certs/ca-certificates.crt.
|
||||
|
||||
Entropy for the TLS handshake is provided by RDTSC-seeded
|
||||
random data via the SYS_GETRANDOM syscall.
|
||||
|
||||
<strong>KEYBOARD</strong>
|
||||
Ctrl+Q Abort the request
|
||||
|
||||
<strong>SEE ALSO</strong>
|
||||
ping(1), nslookup(1), tcpconnect(1), shell(1), syscalls(2)</code></pre>
|
||||
|
||||
<hr>
|
||||
|
||||
<div class="center">
|
||||
<a href="../index.html">Back to Documentation Index</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,220 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="description" content="MontaukOS manual page: file(2) - file I/O system calls">
|
||||
<title>file(2) - MontaukOS Manual</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Open+Sans:wght@400;600&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
body {
|
||||
font-family: 'Open Sans', Arial, sans-serif;
|
||||
max-width: 960px;
|
||||
margin: 0 auto;
|
||||
padding: 1em;
|
||||
line-height: 1.5;
|
||||
display: flex;
|
||||
gap: 2em;
|
||||
}
|
||||
p { margin: 0 0 0.5em; }
|
||||
h2 { margin: 0.5em 0; }
|
||||
.sidebar {
|
||||
width: 160px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.sidebar ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
.sidebar li {
|
||||
margin: 0.5em 0;
|
||||
}
|
||||
.sidebar a {
|
||||
color: #0066CC;
|
||||
text-decoration: none;
|
||||
font-weight: 600;
|
||||
}
|
||||
.sidebar a:hover {
|
||||
color: #004499;
|
||||
text-decoration: underline;
|
||||
}
|
||||
.sidebar .current {
|
||||
color: #004499;
|
||||
}
|
||||
.sidebar hr {
|
||||
border: none;
|
||||
border-top: 1px solid #999;
|
||||
margin: 0.75em 0;
|
||||
}
|
||||
.main {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
a { color: #0000EE; }
|
||||
a:visited { color: #0066CC; }
|
||||
hr { border-style: solid; border-width: 1px 0 0 0; border-color: #999; }
|
||||
pre { background: #f0f0f0; padding: 0.5em; overflow-x: auto; }
|
||||
code { background: #f0f0f0; padding: 0 0.15em; }
|
||||
pre code { padding: 0; }
|
||||
.center { text-align: center; }
|
||||
.box {
|
||||
border: 1px solid #999;
|
||||
padding: 0.5em;
|
||||
margin: 0.5em 0;
|
||||
}
|
||||
@media (max-width: 700px) {
|
||||
body {
|
||||
flex-direction: column;
|
||||
gap: 1em;
|
||||
padding: 1em 0.75em;
|
||||
}
|
||||
.sidebar {
|
||||
width: auto;
|
||||
}
|
||||
.sidebar ul {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0 1em;
|
||||
}
|
||||
.sidebar hr {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="sidebar">
|
||||
<ul>
|
||||
<li><a href="../../index.html">Home</a></li>
|
||||
<li><a href="../../downloads.html">Downloads</a></li>
|
||||
<li><a href="../index.html">Documentation</a></li>
|
||||
<li><a href="https://git.montaukos.org/daniel/MontaukOS/issues">Issue tracker</a></li>
|
||||
<li><a href="https://git.montaukos.org/daniel/MontaukOS">Git</a></li>
|
||||
</ul>
|
||||
<hr>
|
||||
<ul>
|
||||
<li><a href="index.html" class="current">Man Pages</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="main">
|
||||
<div class="center">
|
||||
<h1>file(2)</h1>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
|
||||
<pre><code><strong>NAME</strong>
|
||||
open, read, getsize, close, readdir - file I/O system calls
|
||||
|
||||
<strong>SYNOPSIS</strong>
|
||||
<strong> int montauk::open(const char* path);</strong>
|
||||
<strong> int montauk::read(int handle, uint8_t* buf, uint64_t offset, uint64_t size);</strong>
|
||||
<strong> uint64_t montauk::getsize(int handle);</strong>
|
||||
<strong> void montauk::close(int handle);</strong>
|
||||
<strong> int montauk::readdir(const char* path, const char** names, int max);</strong>
|
||||
|
||||
<strong>DESCRIPTION</strong>
|
||||
MontaukOS provides a Virtual File System (VFS) with read/write
|
||||
support. Drive 0 is the boot ramdisk; additional drives may be
|
||||
mounted from GPT partitions backed by FAT32 or ext2 (see
|
||||
syscalls(2), STORAGE section). Files are accessed via paths in
|
||||
the format "<drive>:/<path>".
|
||||
|
||||
<strong>open</strong>
|
||||
Opens a file and returns a non-negative handle on success, or a
|
||||
negative value on error (file not found, no free handles).
|
||||
|
||||
int h = montauk::open("0:/os/hello.elf");
|
||||
|
||||
<strong>read</strong>
|
||||
Reads up to 'size' bytes starting at 'offset' into 'buf'.
|
||||
Returns the number of bytes actually read, or negative on error.
|
||||
There is no implicit file position -- the offset is explicit on
|
||||
every call.
|
||||
|
||||
uint8_t buf[512];
|
||||
int n = montauk::read(h, buf, 0, 512);
|
||||
|
||||
<strong>getsize</strong>
|
||||
Returns the total size in bytes of the file.
|
||||
|
||||
uint64_t sz = montauk::getsize(h);
|
||||
|
||||
<strong>close</strong>
|
||||
Closes the file handle and frees kernel resources.
|
||||
|
||||
montauk::close(h);
|
||||
|
||||
<strong>readdir</strong>
|
||||
Lists entries in a directory. Up to 'max' entry names (VFS cap
|
||||
256, driver-backed listings such as 0:/os/ cap 128) are written
|
||||
to the 'names' array. The kernel allocates a user-accessible
|
||||
page for the string data automatically. Directory entries are
|
||||
returned with a trailing slash.
|
||||
|
||||
const char* entries[64];
|
||||
int count = montauk::readdir("0:/", entries, 64);
|
||||
// entries: "os/", "apps/", "man/", "www/", "users/", ...
|
||||
|
||||
For directories that may contain more entries than fit in one
|
||||
call, use montauk::readdir_at(path, names, max, startIndex) and
|
||||
advance startIndex by the returned count until it returns 0.
|
||||
|
||||
<strong>READING PATTERN</strong>
|
||||
The standard pattern for reading a file:
|
||||
|
||||
int h = montauk::open("0:/man/intro.1");
|
||||
uint64_t size = montauk::getsize(h);
|
||||
uint8_t buf[512];
|
||||
uint64_t off = 0;
|
||||
while (off < size) {
|
||||
uint64_t chunk = size - off;
|
||||
if (chunk > 511) chunk = 511;
|
||||
int n = montauk::read(h, buf, off, chunk);
|
||||
if (n <= 0) break;
|
||||
buf[n] = '\0';
|
||||
montauk::print((const char*)buf);
|
||||
off += n;
|
||||
}
|
||||
montauk::close(h);
|
||||
|
||||
<strong>WRITING, DELETING, RENAMING</strong>
|
||||
<strong> int montauk::fcreate(const char* path);</strong>
|
||||
<strong> int montauk::fwrite(int handle, const uint8_t* buf, uint64_t offset, uint64_t size);</strong>
|
||||
<strong> int montauk::fdelete(const char* path);</strong>
|
||||
<strong> int montauk::fmkdir(const char* path);</strong>
|
||||
<strong> int montauk::frename(const char* oldPath, const char* newPath);</strong>
|
||||
|
||||
fcreate creates a new file and returns a handle. fwrite writes
|
||||
bytes at the given offset. fdelete removes a file, fmkdir
|
||||
creates a directory, and frename renames or moves a file or
|
||||
directory (the basis for file manager move operations).
|
||||
|
||||
On drive 0 (the ramdisk), changes persist only until reboot --
|
||||
the ramdisk is reloaded from the USTAR archive on each boot. On
|
||||
disk-backed drives (FAT32/ext2 partitions mounted with
|
||||
montauk::fs_mount), changes are written through to storage; use
|
||||
montauk::fs_sync() to flush caches before power-off.
|
||||
|
||||
<strong>NOTES</strong>
|
||||
Drive 0 is loaded at boot from a USTAR tar archive into RAM.
|
||||
Other drives are mounted on demand from GPT partitions on
|
||||
SATA/NVMe/USB block devices; see syscalls(2), STORAGE and
|
||||
DEVICES sections.
|
||||
|
||||
<strong>SEE ALSO</strong>
|
||||
syscalls(2), spawn(2), malloc(3)</code></pre>
|
||||
|
||||
<hr>
|
||||
|
||||
<div class="center">
|
||||
<a href="../index.html">Back to Documentation Index</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,161 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="description" content="MontaukOS manual page: fontscale(1) - get or set terminal font scale">
|
||||
<title>fontscale(1) - MontaukOS Manual</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Open+Sans:wght@400;600&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
body {
|
||||
font-family: 'Open Sans', Arial, sans-serif;
|
||||
max-width: 960px;
|
||||
margin: 0 auto;
|
||||
padding: 1em;
|
||||
line-height: 1.5;
|
||||
display: flex;
|
||||
gap: 2em;
|
||||
}
|
||||
p { margin: 0 0 0.5em; }
|
||||
h2 { margin: 0.5em 0; }
|
||||
.sidebar {
|
||||
width: 160px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.sidebar ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
.sidebar li {
|
||||
margin: 0.5em 0;
|
||||
}
|
||||
.sidebar a {
|
||||
color: #0066CC;
|
||||
text-decoration: none;
|
||||
font-weight: 600;
|
||||
}
|
||||
.sidebar a:hover {
|
||||
color: #004499;
|
||||
text-decoration: underline;
|
||||
}
|
||||
.sidebar .current {
|
||||
color: #004499;
|
||||
}
|
||||
.sidebar hr {
|
||||
border: none;
|
||||
border-top: 1px solid #999;
|
||||
margin: 0.75em 0;
|
||||
}
|
||||
.main {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
a { color: #0000EE; }
|
||||
a:visited { color: #0066CC; }
|
||||
hr { border-style: solid; border-width: 1px 0 0 0; border-color: #999; }
|
||||
pre { background: #f0f0f0; padding: 0.5em; overflow-x: auto; }
|
||||
code { background: #f0f0f0; padding: 0 0.15em; }
|
||||
pre code { padding: 0; }
|
||||
.center { text-align: center; }
|
||||
.box {
|
||||
border: 1px solid #999;
|
||||
padding: 0.5em;
|
||||
margin: 0.5em 0;
|
||||
}
|
||||
@media (max-width: 700px) {
|
||||
body {
|
||||
flex-direction: column;
|
||||
gap: 1em;
|
||||
padding: 1em 0.75em;
|
||||
}
|
||||
.sidebar {
|
||||
width: auto;
|
||||
}
|
||||
.sidebar ul {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0 1em;
|
||||
}
|
||||
.sidebar hr {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="sidebar">
|
||||
<ul>
|
||||
<li><a href="../../index.html">Home</a></li>
|
||||
<li><a href="../../downloads.html">Downloads</a></li>
|
||||
<li><a href="../index.html">Documentation</a></li>
|
||||
<li><a href="https://git.montaukos.org/daniel/MontaukOS/issues">Issue tracker</a></li>
|
||||
<li><a href="https://git.montaukos.org/daniel/MontaukOS">Git</a></li>
|
||||
</ul>
|
||||
<hr>
|
||||
<ul>
|
||||
<li><a href="index.html" class="current">Man Pages</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="main">
|
||||
<div class="center">
|
||||
<h1>fontscale(1)</h1>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
|
||||
<pre><code><strong>NAME</strong>
|
||||
fontscale - get or set terminal font scale
|
||||
|
||||
<strong>SYNOPSIS</strong>
|
||||
fontscale
|
||||
fontscale <n>
|
||||
fontscale <x> <y>
|
||||
|
||||
<strong>DESCRIPTION</strong>
|
||||
Controls the terminal font scale factor. The Flanterm terminal
|
||||
emulator renders text at a configurable scale multiplier.
|
||||
Increasing the scale makes text larger, which is useful on
|
||||
high-resolution displays or real hardware where text may be
|
||||
too small to read comfortably.
|
||||
|
||||
With no arguments, prints the current scale factor and terminal
|
||||
dimensions.
|
||||
|
||||
With one argument, sets both the horizontal and vertical scale
|
||||
to the same value.
|
||||
|
||||
With two arguments, sets asymmetric horizontal and vertical
|
||||
scale factors independently.
|
||||
|
||||
Valid scale values are 1 through 8. After rescaling, the screen
|
||||
is cleared.
|
||||
|
||||
<strong>OUTPUT</strong>
|
||||
fontscale
|
||||
Scale: 1x1 (160 cols x 50 rows)
|
||||
|
||||
fontscale 2
|
||||
Scale set to 2x2 (80 cols x 25 rows)
|
||||
|
||||
<strong>EXAMPLES</strong>
|
||||
fontscale Show current scale and dimensions
|
||||
fontscale 2 Double the font size
|
||||
fontscale 3 2 3x horizontal, 2x vertical
|
||||
fontscale 1 Reset to default size
|
||||
|
||||
<strong>SEE ALSO</strong>
|
||||
shell(1), syscalls(2)</code></pre>
|
||||
|
||||
<hr>
|
||||
|
||||
<div class="center">
|
||||
<a href="../index.html">Back to Documentation Index</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,180 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="description" content="MontaukOS manual page: framebuffer(2) - direct framebuffer access">
|
||||
<title>framebuffer(2) - MontaukOS Manual</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Open+Sans:wght@400;600&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
body {
|
||||
font-family: 'Open Sans', Arial, sans-serif;
|
||||
max-width: 960px;
|
||||
margin: 0 auto;
|
||||
padding: 1em;
|
||||
line-height: 1.5;
|
||||
display: flex;
|
||||
gap: 2em;
|
||||
}
|
||||
p { margin: 0 0 0.5em; }
|
||||
h2 { margin: 0.5em 0; }
|
||||
.sidebar {
|
||||
width: 160px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.sidebar ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
.sidebar li {
|
||||
margin: 0.5em 0;
|
||||
}
|
||||
.sidebar a {
|
||||
color: #0066CC;
|
||||
text-decoration: none;
|
||||
font-weight: 600;
|
||||
}
|
||||
.sidebar a:hover {
|
||||
color: #004499;
|
||||
text-decoration: underline;
|
||||
}
|
||||
.sidebar .current {
|
||||
color: #004499;
|
||||
}
|
||||
.sidebar hr {
|
||||
border: none;
|
||||
border-top: 1px solid #999;
|
||||
margin: 0.75em 0;
|
||||
}
|
||||
.main {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
a { color: #0000EE; }
|
||||
a:visited { color: #0066CC; }
|
||||
hr { border-style: solid; border-width: 1px 0 0 0; border-color: #999; }
|
||||
pre { background: #f0f0f0; padding: 0.5em; overflow-x: auto; }
|
||||
code { background: #f0f0f0; padding: 0 0.15em; }
|
||||
pre code { padding: 0; }
|
||||
.center { text-align: center; }
|
||||
.box {
|
||||
border: 1px solid #999;
|
||||
padding: 0.5em;
|
||||
margin: 0.5em 0;
|
||||
}
|
||||
@media (max-width: 700px) {
|
||||
body {
|
||||
flex-direction: column;
|
||||
gap: 1em;
|
||||
padding: 1em 0.75em;
|
||||
}
|
||||
.sidebar {
|
||||
width: auto;
|
||||
}
|
||||
.sidebar ul {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0 1em;
|
||||
}
|
||||
.sidebar hr {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="sidebar">
|
||||
<ul>
|
||||
<li><a href="../../index.html">Home</a></li>
|
||||
<li><a href="../../downloads.html">Downloads</a></li>
|
||||
<li><a href="../index.html">Documentation</a></li>
|
||||
<li><a href="https://git.montaukos.org/daniel/MontaukOS/issues">Issue tracker</a></li>
|
||||
<li><a href="https://git.montaukos.org/daniel/MontaukOS">Git</a></li>
|
||||
</ul>
|
||||
<hr>
|
||||
<ul>
|
||||
<li><a href="index.html" class="current">Man Pages</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="main">
|
||||
<div class="center">
|
||||
<h1>framebuffer(2)</h1>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
|
||||
<pre><code><strong>NAME</strong>
|
||||
fb_info, fb_map - direct framebuffer access
|
||||
|
||||
<strong>SYNOPSIS</strong>
|
||||
<strong> void montauk::fb_info(montauk::abi::FbInfo* info);</strong>
|
||||
<strong> void* montauk::fb_map();</strong>
|
||||
|
||||
<strong>DESCRIPTION</strong>
|
||||
These syscalls allow userspace programs to access the linear
|
||||
framebuffer directly for graphical output.
|
||||
|
||||
<strong>fb_info</strong>
|
||||
Fills in an FbInfo structure with the framebuffer geometry:
|
||||
|
||||
montauk::abi::FbInfo fb;
|
||||
montauk::fb_info(&fb);
|
||||
// fb.width, fb.height, fb.pitch, fb.bpp
|
||||
|
||||
The pitch is the number of bytes per scanline (may be larger
|
||||
than width * 4 due to alignment). bpp is always 32.
|
||||
|
||||
<strong>fb_map</strong>
|
||||
Maps the physical framebuffer into the process address space at
|
||||
a fixed virtual address (0x50000000) and returns that address.
|
||||
|
||||
uint32_t* pixels = (uint32_t*)montauk::fb_map();
|
||||
|
||||
Each pixel is a 32-bit value in 0xAARRGGBB format (blue in the
|
||||
low byte). Writing to this memory directly updates the screen.
|
||||
|
||||
<strong>PIXEL FORMAT</strong>
|
||||
Bits 31-24: Alpha (unused, typically 0xFF)
|
||||
Bits 23-16: Red
|
||||
Bits 15-8: Green
|
||||
Bits 7-0: Blue
|
||||
|
||||
Example: red = 0x00FF0000, green = 0x0000FF00, blue = 0x000000FF
|
||||
|
||||
<strong>EXAMPLE</strong>
|
||||
Fill the screen with blue:
|
||||
|
||||
montauk::abi::FbInfo fb;
|
||||
montauk::fb_info(&fb);
|
||||
uint32_t* pixels = (uint32_t*)montauk::fb_map();
|
||||
|
||||
for (uint64_t y = 0; y < fb.height; y++) {
|
||||
uint32_t* row = (uint32_t*)((uint8_t*)pixels + y * fb.pitch);
|
||||
for (uint64_t x = 0; x < fb.width; x++) {
|
||||
row[x] = 0x000000FF;
|
||||
}
|
||||
}
|
||||
|
||||
<strong>NOTES</strong>
|
||||
After mapping, the cursor overlay is not composited. Programs
|
||||
that use the framebuffer take full control of screen output.
|
||||
|
||||
Only one mapping per process is supported. Calling fb_map()
|
||||
multiple times returns the same address.
|
||||
|
||||
<strong>SEE ALSO</strong>
|
||||
syscalls(2), malloc(3)</code></pre>
|
||||
|
||||
<hr>
|
||||
|
||||
<div class="center">
|
||||
<a href="../index.html">Back to Documentation Index</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,264 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="description" content="MontaukOS Man Pages">
|
||||
<title>Man Pages - MontaukOS</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Open+Sans:wght@400;600&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
body {
|
||||
font-family: 'Open Sans', Arial, sans-serif;
|
||||
max-width: 960px;
|
||||
margin: 0 auto;
|
||||
padding: 1em;
|
||||
line-height: 1.5;
|
||||
display: flex;
|
||||
gap: 2em;
|
||||
}
|
||||
p { margin: 0 0 0.5em; }
|
||||
h2 { margin: 0.5em 0; }
|
||||
.sidebar {
|
||||
width: 160px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.sidebar ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
.sidebar li {
|
||||
margin: 0.5em 0;
|
||||
}
|
||||
.sidebar a {
|
||||
color: #0066CC;
|
||||
text-decoration: none;
|
||||
font-weight: 600;
|
||||
}
|
||||
.sidebar a:hover {
|
||||
color: #004499;
|
||||
text-decoration: underline;
|
||||
}
|
||||
.sidebar .current {
|
||||
color: #004499;
|
||||
}
|
||||
.sidebar hr {
|
||||
border: none;
|
||||
border-top: 1px solid #999;
|
||||
margin: 0.75em 0;
|
||||
}
|
||||
.main {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
a { color: #0000EE; }
|
||||
a:visited { color: #0066CC; }
|
||||
hr { border-style: solid; border-width: 1px 0 0 0; border-color: #999; }
|
||||
.box {
|
||||
border: 1px solid #999;
|
||||
padding: 0.5em;
|
||||
margin: 0.5em 0;
|
||||
}
|
||||
.doc-list {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
.doc-list li {
|
||||
margin: 0.75em 0;
|
||||
}
|
||||
.doc-list a {
|
||||
font-weight: 600;
|
||||
font-family: monospace;
|
||||
}
|
||||
.doc-list p {
|
||||
margin: 0.25em 0 0 1.5em;
|
||||
color: #555;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
@media (max-width: 700px) {
|
||||
body {
|
||||
flex-direction: column;
|
||||
gap: 1em;
|
||||
padding: 1em 0.75em;
|
||||
}
|
||||
.sidebar {
|
||||
width: auto;
|
||||
}
|
||||
.sidebar ul {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0 1em;
|
||||
}
|
||||
.sidebar hr {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="sidebar">
|
||||
<ul>
|
||||
<li><a href="../../index.html">Home</a></li>
|
||||
<li><a href="../../downloads.html">Downloads</a></li>
|
||||
<li><a href="../index.html">Documentation</a></li>
|
||||
<li><a href="https://git.montaukos.org/daniel/MontaukOS/issues">Issue tracker</a></li>
|
||||
<li><a href="https://git.montaukos.org/daniel/MontaukOS">Git</a></li>
|
||||
</ul>
|
||||
<hr>
|
||||
<ul>
|
||||
<li><a href="index.html" class="current">Man Pages</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="main">
|
||||
<div class="center">
|
||||
<h1>Man Pages</h1>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
|
||||
<p>
|
||||
Manual pages for MontaukOS, viewable in-system with the <a href="man.html">man(1)</a> command.
|
||||
</p>
|
||||
|
||||
<h2>User Commands (Section 1)</h2>
|
||||
|
||||
<ul class="doc-list">
|
||||
|
||||
<li>
|
||||
<a href="intro.html">intro(1)</a>
|
||||
<p>introduction to MontaukOS userspace</p>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<a href="shell.html">shell(1)</a>
|
||||
<p>MontaukOS interactive command shell</p>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<a href="init.html">init(1)</a>
|
||||
<p>MontaukOS init system</p>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<a href="dhcp.html">dhcp(1)</a>
|
||||
<p>obtain network configuration via DHCP</p>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<a href="fetch.html">fetch(1)</a>
|
||||
<p>HTTP/HTTPS client for MontaukOS</p>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<a href="ping.html">ping(1)</a>
|
||||
<p>send ICMP echo requests</p>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<a href="nslookup.html">nslookup(1)</a>
|
||||
<p>DNS hostname lookup</p>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<a href="fontscale.html">fontscale(1)</a>
|
||||
<p>get or set terminal font scale</p>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<a href="edit.html">edit(1)</a>
|
||||
<p>text editor for MontaukOS</p>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<a href="man.html">man(1)</a>
|
||||
<p>display manual pages</p>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<a href="printctl.html">printctl(1)</a>
|
||||
<p>configure printers and submit print jobs</p>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<a href="printd.html">printd(1)</a>
|
||||
<p>MontaukOS userspace print spooler daemon</p>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<a href="wiki.html">wiki(1)</a>
|
||||
<p>Wikipedia article viewer for MontaukOS</p>
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
|
||||
<h2>System Calls (Section 2)</h2>
|
||||
|
||||
<ul class="doc-list">
|
||||
|
||||
<li>
|
||||
<a href="syscalls.html">syscalls(2)</a>
|
||||
<p>overview of MontaukOS system calls</p>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<a href="spawn.html">spawn(2)</a>
|
||||
<p>create and wait for processes</p>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<a href="file.html">file(2)</a>
|
||||
<p>file I/O system calls</p>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<a href="framebuffer.html">framebuffer(2)</a>
|
||||
<p>direct framebuffer access</p>
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
|
||||
<h2>Library Functions (Section 3)</h2>
|
||||
|
||||
<ul class="doc-list">
|
||||
|
||||
<li>
|
||||
<a href="malloc.html">malloc(3)</a>
|
||||
<p>userspace heap allocation</p>
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
|
||||
<h2>File Formats / Reference (Section 5)</h2>
|
||||
|
||||
<ul class="doc-list">
|
||||
|
||||
<li>
|
||||
<a href="tls-errors.html">tls-errors(5)</a>
|
||||
<p>BearSSL TLS and X.509 error codes</p>
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
|
||||
<h2>Miscellaneous (Section 7)</h2>
|
||||
|
||||
<ul class="doc-list">
|
||||
|
||||
<li>
|
||||
<a href="legal.html">legal(7)</a>
|
||||
<p>MontaukOS legal/copyright information</p>
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
|
||||
<hr>
|
||||
|
||||
<div class="center">
|
||||
<a href="../index.html">Back to Documentation Index</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,157 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="description" content="MontaukOS manual page: init(1) - MontaukOS init system">
|
||||
<title>init(1) - MontaukOS Manual</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Open+Sans:wght@400;600&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
body {
|
||||
font-family: 'Open Sans', Arial, sans-serif;
|
||||
max-width: 960px;
|
||||
margin: 0 auto;
|
||||
padding: 1em;
|
||||
line-height: 1.5;
|
||||
display: flex;
|
||||
gap: 2em;
|
||||
}
|
||||
p { margin: 0 0 0.5em; }
|
||||
h2 { margin: 0.5em 0; }
|
||||
.sidebar {
|
||||
width: 160px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.sidebar ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
.sidebar li {
|
||||
margin: 0.5em 0;
|
||||
}
|
||||
.sidebar a {
|
||||
color: #0066CC;
|
||||
text-decoration: none;
|
||||
font-weight: 600;
|
||||
}
|
||||
.sidebar a:hover {
|
||||
color: #004499;
|
||||
text-decoration: underline;
|
||||
}
|
||||
.sidebar .current {
|
||||
color: #004499;
|
||||
}
|
||||
.sidebar hr {
|
||||
border: none;
|
||||
border-top: 1px solid #999;
|
||||
margin: 0.75em 0;
|
||||
}
|
||||
.main {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
a { color: #0000EE; }
|
||||
a:visited { color: #0066CC; }
|
||||
hr { border-style: solid; border-width: 1px 0 0 0; border-color: #999; }
|
||||
pre { background: #f0f0f0; padding: 0.5em; overflow-x: auto; }
|
||||
code { background: #f0f0f0; padding: 0 0.15em; }
|
||||
pre code { padding: 0; }
|
||||
.center { text-align: center; }
|
||||
.box {
|
||||
border: 1px solid #999;
|
||||
padding: 0.5em;
|
||||
margin: 0.5em 0;
|
||||
}
|
||||
@media (max-width: 700px) {
|
||||
body {
|
||||
flex-direction: column;
|
||||
gap: 1em;
|
||||
padding: 1em 0.75em;
|
||||
}
|
||||
.sidebar {
|
||||
width: auto;
|
||||
}
|
||||
.sidebar ul {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0 1em;
|
||||
}
|
||||
.sidebar hr {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="sidebar">
|
||||
<ul>
|
||||
<li><a href="../../index.html">Home</a></li>
|
||||
<li><a href="../../downloads.html">Downloads</a></li>
|
||||
<li><a href="../index.html">Documentation</a></li>
|
||||
<li><a href="https://git.montaukos.org/daniel/MontaukOS/issues">Issue tracker</a></li>
|
||||
<li><a href="https://git.montaukos.org/daniel/MontaukOS">Git</a></li>
|
||||
</ul>
|
||||
<hr>
|
||||
<ul>
|
||||
<li><a href="index.html" class="current">Man Pages</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="main">
|
||||
<div class="center">
|
||||
<h1>init(1)</h1>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
|
||||
<pre><code><strong>NAME</strong>
|
||||
init - MontaukOS init system
|
||||
|
||||
<strong>SYNOPSIS</strong>
|
||||
Spawned automatically by the kernel as PID 0.
|
||||
|
||||
<strong>DESCRIPTION</strong>
|
||||
init is the first userspace process started by the MontaukOS
|
||||
kernel. It chains system services in sequence, then launches
|
||||
the interactive shell.
|
||||
|
||||
Each service is spawned as a child process. init waits for it
|
||||
to exit before starting the next one. If a service fails to
|
||||
spawn, init logs an error and continues to the next stage.
|
||||
|
||||
Log output is timestamped and color-coded:
|
||||
|
||||
HH:MM:SS INFO init Starting dhcp
|
||||
HH:MM:SS OK init dhcp finished (pid 1)
|
||||
|
||||
<strong>BOOT SEQUENCE</strong>
|
||||
The following services are started in order:
|
||||
|
||||
1. 0:/os/dhcp.elf Obtain network configuration via DHCP
|
||||
2. 0:/os/shell.elf Launch the interactive shell
|
||||
|
||||
After the shell exits, init enters an idle loop.
|
||||
|
||||
<strong>LOG LEVELS</strong>
|
||||
init uses four log levels, each with a distinct color:
|
||||
|
||||
OK Green Service completed successfully
|
||||
INFO Cyan Informational (service starting, etc.)
|
||||
WARN Yellow Non-fatal warning
|
||||
FAIL Red Service failed to start
|
||||
|
||||
<strong>SEE ALSO</strong>
|
||||
dhcp(1), shell(1), syscalls(2)</code></pre>
|
||||
|
||||
<hr>
|
||||
|
||||
<div class="center">
|
||||
<a href="../index.html">Back to Documentation Index</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,212 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="description" content="MontaukOS manual page: intro(1) - introduction to MontaukOS userspace">
|
||||
<title>intro(1) - MontaukOS Manual</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Open+Sans:wght@400;600&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
body {
|
||||
font-family: 'Open Sans', Arial, sans-serif;
|
||||
max-width: 960px;
|
||||
margin: 0 auto;
|
||||
padding: 1em;
|
||||
line-height: 1.5;
|
||||
display: flex;
|
||||
gap: 2em;
|
||||
}
|
||||
p { margin: 0 0 0.5em; }
|
||||
h2 { margin: 0.5em 0; }
|
||||
.sidebar {
|
||||
width: 160px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.sidebar ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
.sidebar li {
|
||||
margin: 0.5em 0;
|
||||
}
|
||||
.sidebar a {
|
||||
color: #0066CC;
|
||||
text-decoration: none;
|
||||
font-weight: 600;
|
||||
}
|
||||
.sidebar a:hover {
|
||||
color: #004499;
|
||||
text-decoration: underline;
|
||||
}
|
||||
.sidebar .current {
|
||||
color: #004499;
|
||||
}
|
||||
.sidebar hr {
|
||||
border: none;
|
||||
border-top: 1px solid #999;
|
||||
margin: 0.75em 0;
|
||||
}
|
||||
.main {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
a { color: #0000EE; }
|
||||
a:visited { color: #0066CC; }
|
||||
hr { border-style: solid; border-width: 1px 0 0 0; border-color: #999; }
|
||||
pre { background: #f0f0f0; padding: 0.5em; overflow-x: auto; }
|
||||
code { background: #f0f0f0; padding: 0 0.15em; }
|
||||
pre code { padding: 0; }
|
||||
.center { text-align: center; }
|
||||
.box {
|
||||
border: 1px solid #999;
|
||||
padding: 0.5em;
|
||||
margin: 0.5em 0;
|
||||
}
|
||||
@media (max-width: 700px) {
|
||||
body {
|
||||
flex-direction: column;
|
||||
gap: 1em;
|
||||
padding: 1em 0.75em;
|
||||
}
|
||||
.sidebar {
|
||||
width: auto;
|
||||
}
|
||||
.sidebar ul {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0 1em;
|
||||
}
|
||||
.sidebar hr {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="sidebar">
|
||||
<ul>
|
||||
<li><a href="../../index.html">Home</a></li>
|
||||
<li><a href="../../downloads.html">Downloads</a></li>
|
||||
<li><a href="../index.html">Documentation</a></li>
|
||||
<li><a href="https://git.montaukos.org/daniel/MontaukOS/issues">Issue tracker</a></li>
|
||||
<li><a href="https://git.montaukos.org/daniel/MontaukOS">Git</a></li>
|
||||
</ul>
|
||||
<hr>
|
||||
<ul>
|
||||
<li><a href="index.html" class="current">Man Pages</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="main">
|
||||
<div class="center">
|
||||
<h1>intro(1)</h1>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
|
||||
<pre><code><strong>NAME</strong>
|
||||
intro - introduction to MontaukOS userspace
|
||||
|
||||
<strong>DESCRIPTION</strong>
|
||||
MontaukOS is a hobbyist 64-bit operating system written in C++20,
|
||||
currently at version 0.1.7 (API version 8). Userspace programs
|
||||
run in Ring 3, are loaded as static ELF64 binaries, and
|
||||
communicate with the kernel through the x86-64 SYSCALL/SYSRET
|
||||
mechanism (150 syscalls -- see syscalls(2)).
|
||||
|
||||
Programs are compiled with a freestanding cross-compiler and
|
||||
linked at virtual address 0x400000. There is no standard C
|
||||
library for C++ programs -- all system interaction goes through
|
||||
the montauk:: syscall wrappers. A desktop environment with a
|
||||
window server, GUI apps, and Bluetooth/audio/networking stacks
|
||||
runs on top of the same syscall API.
|
||||
|
||||
<strong>GETTING STARTED</strong>
|
||||
To write a new system/CLI program, create a directory under
|
||||
programs/src/ with a main.cpp file. The entry point is:
|
||||
|
||||
extern "C" void _start() { ... }
|
||||
|
||||
There is no argc/argv. Use montauk::getargs() to retrieve any
|
||||
arguments passed by the parent process. Include <montauk/syscall.h>
|
||||
for the full typed syscall API. GUI apps additionally use
|
||||
win_create()/win_poll()/win_present() from montauk/Window.hpp
|
||||
(see framebuffer(2)).
|
||||
|
||||
Build with:
|
||||
|
||||
cd programs && make
|
||||
|
||||
System/CLI binaries appear in programs/bin/os/; GUI app bundles
|
||||
(ELF + manifest.toml + icon) appear under programs/bin/apps/<name>/.
|
||||
|
||||
<strong>RAMDISK LAYOUT</strong>
|
||||
The boot ramdisk is mounted as drive 0 with the following
|
||||
directory structure:
|
||||
|
||||
0:/os/ System/CLI binaries (shell, init, man, etc.),
|
||||
plus os-owned data: certs/, firmware/,
|
||||
licenses/, wallpapers/
|
||||
0:/apps/ GUI app bundles, one directory per app
|
||||
(<app>.elf + manifest.toml + icon)
|
||||
0:/config/ System-wide config TOMLs
|
||||
0:/users/<name>/ Per-user home directories (created at
|
||||
login), with Music/, Videos/, Pictures/,
|
||||
config/ subdirectories
|
||||
0:/fonts/ Shared fonts
|
||||
0:/icons/ Shared icons
|
||||
0:/man/ Manual pages
|
||||
0:/www/ Web server content
|
||||
0:/lib/ Lua and TinyCC toolchain payloads
|
||||
0:/boot/ Kernel, bootloader, ramdisk image
|
||||
|
||||
There is no 0:/games/, 0:/common/, 0:/home/, or 0:/etc/ --
|
||||
these were used by earlier single-user releases and no longer
|
||||
exist. Games and other GUI programs (including doom) ship as
|
||||
bundles under 0:/apps/.
|
||||
|
||||
<strong>SHELL</strong>
|
||||
The interactive shell is the primary way to interact with
|
||||
MontaukOS. Commands are resolved against the current directory
|
||||
first, then 0:/os/. Type 'help' at the shell prompt for a list
|
||||
of commands. Use 'man shell' for detailed shell documentation.
|
||||
|
||||
<strong>MAN PAGES</strong>
|
||||
The following man pages are available:
|
||||
|
||||
intro(1) This page
|
||||
shell(1) Shell commands reference
|
||||
init(1) Init system
|
||||
dhcp(1) DHCP client
|
||||
fetch(1) HTTP client
|
||||
ping(1) ICMP ping
|
||||
nslookup(1) DNS lookup
|
||||
fontscale(1) Terminal font scaling
|
||||
edit(1) Text editor
|
||||
man(1) The man command itself
|
||||
printctl(1) Printer control
|
||||
printd(1) Print spooler daemon
|
||||
wiki(1) Wikipedia article viewer
|
||||
legal(7) Copyright and legal information
|
||||
tls-errors(5) TLS/BearSSL error reference
|
||||
syscalls(2) Overview of all syscalls
|
||||
spawn(2) Process spawning
|
||||
file(2) File I/O syscalls
|
||||
framebuffer(2) Framebuffer access
|
||||
malloc(3) Memory allocation
|
||||
|
||||
<strong>SEE ALSO</strong>
|
||||
shell(1), syscalls(2), malloc(3)</code></pre>
|
||||
|
||||
<hr>
|
||||
|
||||
<div class="center">
|
||||
<a href="../index.html">Back to Documentation Index</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,177 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="description" content="MontaukOS manual page: legal(7) - MontaukOS legal/copyright information">
|
||||
<title>legal(7) - MontaukOS Manual</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Open+Sans:wght@400;600&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
body {
|
||||
font-family: 'Open Sans', Arial, sans-serif;
|
||||
max-width: 960px;
|
||||
margin: 0 auto;
|
||||
padding: 1em;
|
||||
line-height: 1.5;
|
||||
display: flex;
|
||||
gap: 2em;
|
||||
}
|
||||
p { margin: 0 0 0.5em; }
|
||||
h2 { margin: 0.5em 0; }
|
||||
.sidebar {
|
||||
width: 160px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.sidebar ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
.sidebar li {
|
||||
margin: 0.5em 0;
|
||||
}
|
||||
.sidebar a {
|
||||
color: #0066CC;
|
||||
text-decoration: none;
|
||||
font-weight: 600;
|
||||
}
|
||||
.sidebar a:hover {
|
||||
color: #004499;
|
||||
text-decoration: underline;
|
||||
}
|
||||
.sidebar .current {
|
||||
color: #004499;
|
||||
}
|
||||
.sidebar hr {
|
||||
border: none;
|
||||
border-top: 1px solid #999;
|
||||
margin: 0.75em 0;
|
||||
}
|
||||
.main {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
a { color: #0000EE; }
|
||||
a:visited { color: #0066CC; }
|
||||
hr { border-style: solid; border-width: 1px 0 0 0; border-color: #999; }
|
||||
pre { background: #f0f0f0; padding: 0.5em; overflow-x: auto; }
|
||||
code { background: #f0f0f0; padding: 0 0.15em; }
|
||||
pre code { padding: 0; }
|
||||
.center { text-align: center; }
|
||||
.box {
|
||||
border: 1px solid #999;
|
||||
padding: 0.5em;
|
||||
margin: 0.5em 0;
|
||||
}
|
||||
@media (max-width: 700px) {
|
||||
body {
|
||||
flex-direction: column;
|
||||
gap: 1em;
|
||||
padding: 1em 0.75em;
|
||||
}
|
||||
.sidebar {
|
||||
width: auto;
|
||||
}
|
||||
.sidebar ul {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0 1em;
|
||||
}
|
||||
.sidebar hr {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="sidebar">
|
||||
<ul>
|
||||
<li><a href="../../index.html">Home</a></li>
|
||||
<li><a href="../../downloads.html">Downloads</a></li>
|
||||
<li><a href="../index.html">Documentation</a></li>
|
||||
<li><a href="https://git.montaukos.org/daniel/MontaukOS/issues">Issue tracker</a></li>
|
||||
<li><a href="https://git.montaukos.org/daniel/MontaukOS">Git</a></li>
|
||||
</ul>
|
||||
<hr>
|
||||
<ul>
|
||||
<li><a href="index.html" class="current">Man Pages</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="main">
|
||||
<div class="center">
|
||||
<h1>legal(7)</h1>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
|
||||
<pre><code><strong>NAME</strong>
|
||||
MontaukOS legal/copyright information
|
||||
|
||||
<strong>DESCRIPTION</strong>
|
||||
Copyright (c) 2025-2026 Daniel Hammer, et al.
|
||||
(includes contributors to other projects, i.e. The Limine Bootloader. Please refer to any other project's own license.)
|
||||
|
||||
MontaukOS is source-available software, provided under the terms of the
|
||||
MontaukOS Software License. The full license text is on this system at
|
||||
0:/os/licenses/LICENSE.txt.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
== License for the Limine C++ template (certain portions derive therefrom) ==
|
||||
Copyright (C) 2023-2026 Mintsuki and contributors.
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||
FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
<strong>THIRD-PARTY COMPONENTS</strong>
|
||||
MontaukOS is distributed together with third-party components that remain
|
||||
under their own licenses, including:
|
||||
|
||||
* Flat Remix icon theme, Copyright (C) Daniel Ruiz de Alegria - GPLv3
|
||||
* DOOM engine (doom.elf, via doomgeneric), Copyright (C) id Software, Inc.
|
||||
and contributors - GPLv2
|
||||
* Limine bootloader, Copyright (C) Mintsuki and contributors - BSD 2-Clause
|
||||
* BearSSL, Copyright (c) Thomas Pornin - MIT
|
||||
* stb_image, Copyright (c) Sean Barrett - MIT
|
||||
* JetBrains Mono font, Copyright The JetBrains Mono Project Authors - OFL-1.1
|
||||
* Noto Serif font, Copyright The Noto Project Authors - OFL-1.1
|
||||
* Roboto font, Copyright The Roboto Project Authors - OFL-1.1
|
||||
* C059 font (URW Base 35), Copyright (C) (URW)++ Design and Development
|
||||
GmbH - AGPLv3 with font-embedding exception
|
||||
* Tiny C Compiler (tcc.elf, 0:/sdk/tcc), Copyright (c) Fabrice Bellard and
|
||||
contributors - LGPL-2.1
|
||||
* Lua (lua.elf, 0:/sdk/lua), Copyright (C) Lua.org, PUC-Rio - MIT
|
||||
* Mozilla CA certificate bundle (0:/os/certs), Mozilla CA Certificate
|
||||
Program - MPL-2.0
|
||||
* Intel Bluetooth firmware (0:/os/firmware/intel), Copyright (c) Intel
|
||||
Corporation - Intel redistributable firmware license
|
||||
* Default wallpaper photo (0:/os/wallpapers), by Nikhil Kumar -
|
||||
Unsplash License
|
||||
|
||||
Full license texts and notices are on this system in 0:/os/licenses/.</code></pre>
|
||||
|
||||
<hr>
|
||||
|
||||
<div class="center">
|
||||
<a href="../index.html">Back to Documentation Index</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,175 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="description" content="MontaukOS manual page: malloc(3) - userspace heap allocation">
|
||||
<title>malloc(3) - MontaukOS Manual</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Open+Sans:wght@400;600&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
body {
|
||||
font-family: 'Open Sans', Arial, sans-serif;
|
||||
max-width: 960px;
|
||||
margin: 0 auto;
|
||||
padding: 1em;
|
||||
line-height: 1.5;
|
||||
display: flex;
|
||||
gap: 2em;
|
||||
}
|
||||
p { margin: 0 0 0.5em; }
|
||||
h2 { margin: 0.5em 0; }
|
||||
.sidebar {
|
||||
width: 160px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.sidebar ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
.sidebar li {
|
||||
margin: 0.5em 0;
|
||||
}
|
||||
.sidebar a {
|
||||
color: #0066CC;
|
||||
text-decoration: none;
|
||||
font-weight: 600;
|
||||
}
|
||||
.sidebar a:hover {
|
||||
color: #004499;
|
||||
text-decoration: underline;
|
||||
}
|
||||
.sidebar .current {
|
||||
color: #004499;
|
||||
}
|
||||
.sidebar hr {
|
||||
border: none;
|
||||
border-top: 1px solid #999;
|
||||
margin: 0.75em 0;
|
||||
}
|
||||
.main {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
a { color: #0000EE; }
|
||||
a:visited { color: #0066CC; }
|
||||
hr { border-style: solid; border-width: 1px 0 0 0; border-color: #999; }
|
||||
pre { background: #f0f0f0; padding: 0.5em; overflow-x: auto; }
|
||||
code { background: #f0f0f0; padding: 0 0.15em; }
|
||||
pre code { padding: 0; }
|
||||
.center { text-align: center; }
|
||||
.box {
|
||||
border: 1px solid #999;
|
||||
padding: 0.5em;
|
||||
margin: 0.5em 0;
|
||||
}
|
||||
@media (max-width: 700px) {
|
||||
body {
|
||||
flex-direction: column;
|
||||
gap: 1em;
|
||||
padding: 1em 0.75em;
|
||||
}
|
||||
.sidebar {
|
||||
width: auto;
|
||||
}
|
||||
.sidebar ul {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0 1em;
|
||||
}
|
||||
.sidebar hr {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="sidebar">
|
||||
<ul>
|
||||
<li><a href="../../index.html">Home</a></li>
|
||||
<li><a href="../../downloads.html">Downloads</a></li>
|
||||
<li><a href="../index.html">Documentation</a></li>
|
||||
<li><a href="https://git.montaukos.org/daniel/MontaukOS/issues">Issue tracker</a></li>
|
||||
<li><a href="https://git.montaukos.org/daniel/MontaukOS">Git</a></li>
|
||||
</ul>
|
||||
<hr>
|
||||
<ul>
|
||||
<li><a href="index.html" class="current">Man Pages</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="main">
|
||||
<div class="center">
|
||||
<h1>malloc(3)</h1>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
|
||||
<pre><code><strong>NAME</strong>
|
||||
malloc, mfree, realloc - userspace heap allocation
|
||||
|
||||
<strong>SYNOPSIS</strong>
|
||||
<strong> void* montauk::malloc(uint64_t size);</strong>
|
||||
<strong> void montauk::mfree(void* ptr);</strong>
|
||||
<strong> void* montauk::realloc(void* ptr, uint64_t size);</strong>
|
||||
|
||||
<strong>DESCRIPTION</strong>
|
||||
The userspace heap provides dynamic memory allocation on top of
|
||||
the kernel's page-mapping syscall (SYS_ALLOC). Include the
|
||||
header <montauk/heap.h> to use these functions.
|
||||
|
||||
<strong>malloc</strong>
|
||||
Allocates 'size' bytes from the free list. Returns a 16-byte
|
||||
aligned pointer, or nullptr on failure. When the free list is
|
||||
empty, it requests more pages from the kernel via SYS_ALLOC
|
||||
(minimum 16 KiB growth, initial seed of 64 KiB).
|
||||
|
||||
char* buf = (char*)montauk::malloc(1024);
|
||||
|
||||
<strong>mfree</strong>
|
||||
Returns the block to the userspace free list. No syscall is
|
||||
made -- the memory stays mapped and is immediately reusable.
|
||||
Passing nullptr is a safe no-op.
|
||||
|
||||
montauk::mfree(buf);
|
||||
|
||||
<strong>realloc</strong>
|
||||
Resizes the allocation to 'size' bytes. Allocates a new block,
|
||||
copies the smaller of old/new sizes, and frees the old block.
|
||||
If ptr is nullptr, behaves like malloc.
|
||||
|
||||
buf = (char*)montauk::realloc(buf, 2048);
|
||||
|
||||
<strong>IMPLEMENTATION</strong>
|
||||
The allocator uses a linked free-list with first-fit search.
|
||||
Blocks larger than needed are split. The allocation header is
|
||||
16 bytes (magic + size). All allocations are 16-byte aligned.
|
||||
|
||||
The heap grows by requesting pages from the kernel via
|
||||
SYS_ALLOC. These pages are never returned to the kernel (since
|
||||
SYS_FREE is currently a no-op), but mfree makes them available
|
||||
for future malloc calls within the process.
|
||||
|
||||
<strong>LOW-LEVEL PAGE API</strong>
|
||||
For large allocations or when direct page control is needed:
|
||||
|
||||
void* montauk::alloc(uint64_t size); // SYS_ALLOC
|
||||
void montauk::free(void* ptr); // SYS_FREE (no-op)
|
||||
|
||||
alloc() maps zeroed pages starting at 0x40000000 and growing
|
||||
upward. Size is rounded up to 4 KiB page boundaries.
|
||||
|
||||
<strong>SEE ALSO</strong>
|
||||
syscalls(2), file(2)</code></pre>
|
||||
|
||||
<hr>
|
||||
|
||||
<div class="center">
|
||||
<a href="../index.html">Back to Documentation Index</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,167 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="description" content="MontaukOS manual page: man(1) - display manual pages">
|
||||
<title>man(1) - MontaukOS Manual</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Open+Sans:wght@400;600&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
body {
|
||||
font-family: 'Open Sans', Arial, sans-serif;
|
||||
max-width: 960px;
|
||||
margin: 0 auto;
|
||||
padding: 1em;
|
||||
line-height: 1.5;
|
||||
display: flex;
|
||||
gap: 2em;
|
||||
}
|
||||
p { margin: 0 0 0.5em; }
|
||||
h2 { margin: 0.5em 0; }
|
||||
.sidebar {
|
||||
width: 160px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.sidebar ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
.sidebar li {
|
||||
margin: 0.5em 0;
|
||||
}
|
||||
.sidebar a {
|
||||
color: #0066CC;
|
||||
text-decoration: none;
|
||||
font-weight: 600;
|
||||
}
|
||||
.sidebar a:hover {
|
||||
color: #004499;
|
||||
text-decoration: underline;
|
||||
}
|
||||
.sidebar .current {
|
||||
color: #004499;
|
||||
}
|
||||
.sidebar hr {
|
||||
border: none;
|
||||
border-top: 1px solid #999;
|
||||
margin: 0.75em 0;
|
||||
}
|
||||
.main {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
a { color: #0000EE; }
|
||||
a:visited { color: #0066CC; }
|
||||
hr { border-style: solid; border-width: 1px 0 0 0; border-color: #999; }
|
||||
pre { background: #f0f0f0; padding: 0.5em; overflow-x: auto; }
|
||||
code { background: #f0f0f0; padding: 0 0.15em; }
|
||||
pre code { padding: 0; }
|
||||
.center { text-align: center; }
|
||||
.box {
|
||||
border: 1px solid #999;
|
||||
padding: 0.5em;
|
||||
margin: 0.5em 0;
|
||||
}
|
||||
@media (max-width: 700px) {
|
||||
body {
|
||||
flex-direction: column;
|
||||
gap: 1em;
|
||||
padding: 1em 0.75em;
|
||||
}
|
||||
.sidebar {
|
||||
width: auto;
|
||||
}
|
||||
.sidebar ul {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0 1em;
|
||||
}
|
||||
.sidebar hr {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="sidebar">
|
||||
<ul>
|
||||
<li><a href="../../index.html">Home</a></li>
|
||||
<li><a href="../../downloads.html">Downloads</a></li>
|
||||
<li><a href="../index.html">Documentation</a></li>
|
||||
<li><a href="https://git.montaukos.org/daniel/MontaukOS/issues">Issue tracker</a></li>
|
||||
<li><a href="https://git.montaukos.org/daniel/MontaukOS">Git</a></li>
|
||||
</ul>
|
||||
<hr>
|
||||
<ul>
|
||||
<li><a href="index.html" class="current">Man Pages</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="main">
|
||||
<div class="center">
|
||||
<h1>man(1)</h1>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
|
||||
<pre><code><strong>NAME</strong>
|
||||
man - display manual pages
|
||||
|
||||
<strong>SYNOPSIS</strong>
|
||||
<strong> man topic</strong>
|
||||
<strong> man section topic</strong>
|
||||
|
||||
<strong>DESCRIPTION</strong>
|
||||
The man command displays manual pages from the ramdisk in a
|
||||
fullscreen pager. Pages are stored as plain text files with
|
||||
simple formatting directives.
|
||||
|
||||
If no section is specified, sections 1 through 7 are searched
|
||||
in order. If a section number is given, only that section is
|
||||
checked.
|
||||
|
||||
<strong>KEY BINDINGS</strong>
|
||||
|
||||
<strong>Navigation</strong>
|
||||
j, Down Arrow Scroll down one line
|
||||
k, Up Arrow Scroll up one line
|
||||
Space, Page Down Scroll down one page
|
||||
b, Page Up Scroll up one page
|
||||
g, Home Go to top
|
||||
G, End Go to bottom
|
||||
q Quit
|
||||
|
||||
<strong>SECTIONS</strong>
|
||||
1 User commands and programs
|
||||
2 System calls (kernel interface)
|
||||
3 Library functions (userspace libraries)
|
||||
7 Miscellaneous (legal, conventions)
|
||||
|
||||
<strong>FILES</strong>
|
||||
Man pages are stored on the ramdisk at:
|
||||
|
||||
0:/man/<topic>.<section>
|
||||
|
||||
For example, man intro reads 0:/man/intro.1
|
||||
|
||||
<strong>EXAMPLES</strong>
|
||||
man intro View the introduction
|
||||
man 2 syscalls View syscall overview (section 2)
|
||||
man malloc View malloc documentation
|
||||
man legal View copyright information
|
||||
|
||||
<strong>SEE ALSO</strong>
|
||||
intro(1), shell(1), syscalls(2)</code></pre>
|
||||
|
||||
<hr>
|
||||
|
||||
<div class="center">
|
||||
<a href="../index.html">Back to Documentation Index</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,154 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="description" content="MontaukOS manual page: nslookup(1) - DNS hostname lookup">
|
||||
<title>nslookup(1) - MontaukOS Manual</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Open+Sans:wght@400;600&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
body {
|
||||
font-family: 'Open Sans', Arial, sans-serif;
|
||||
max-width: 960px;
|
||||
margin: 0 auto;
|
||||
padding: 1em;
|
||||
line-height: 1.5;
|
||||
display: flex;
|
||||
gap: 2em;
|
||||
}
|
||||
p { margin: 0 0 0.5em; }
|
||||
h2 { margin: 0.5em 0; }
|
||||
.sidebar {
|
||||
width: 160px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.sidebar ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
.sidebar li {
|
||||
margin: 0.5em 0;
|
||||
}
|
||||
.sidebar a {
|
||||
color: #0066CC;
|
||||
text-decoration: none;
|
||||
font-weight: 600;
|
||||
}
|
||||
.sidebar a:hover {
|
||||
color: #004499;
|
||||
text-decoration: underline;
|
||||
}
|
||||
.sidebar .current {
|
||||
color: #004499;
|
||||
}
|
||||
.sidebar hr {
|
||||
border: none;
|
||||
border-top: 1px solid #999;
|
||||
margin: 0.75em 0;
|
||||
}
|
||||
.main {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
a { color: #0000EE; }
|
||||
a:visited { color: #0066CC; }
|
||||
hr { border-style: solid; border-width: 1px 0 0 0; border-color: #999; }
|
||||
pre { background: #f0f0f0; padding: 0.5em; overflow-x: auto; }
|
||||
code { background: #f0f0f0; padding: 0 0.15em; }
|
||||
pre code { padding: 0; }
|
||||
.center { text-align: center; }
|
||||
.box {
|
||||
border: 1px solid #999;
|
||||
padding: 0.5em;
|
||||
margin: 0.5em 0;
|
||||
}
|
||||
@media (max-width: 700px) {
|
||||
body {
|
||||
flex-direction: column;
|
||||
gap: 1em;
|
||||
padding: 1em 0.75em;
|
||||
}
|
||||
.sidebar {
|
||||
width: auto;
|
||||
}
|
||||
.sidebar ul {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0 1em;
|
||||
}
|
||||
.sidebar hr {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="sidebar">
|
||||
<ul>
|
||||
<li><a href="../../index.html">Home</a></li>
|
||||
<li><a href="../../downloads.html">Downloads</a></li>
|
||||
<li><a href="../index.html">Documentation</a></li>
|
||||
<li><a href="https://git.montaukos.org/daniel/MontaukOS/issues">Issue tracker</a></li>
|
||||
<li><a href="https://git.montaukos.org/daniel/MontaukOS">Git</a></li>
|
||||
</ul>
|
||||
<hr>
|
||||
<ul>
|
||||
<li><a href="index.html" class="current">Man Pages</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="main">
|
||||
<div class="center">
|
||||
<h1>nslookup(1)</h1>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
|
||||
<pre><code><strong>NAME</strong>
|
||||
nslookup - DNS hostname lookup
|
||||
|
||||
<strong>SYNOPSIS</strong>
|
||||
nslookup <hostname>
|
||||
|
||||
<strong>DESCRIPTION</strong>
|
||||
Resolves a hostname to an IPv4 address using the configured
|
||||
DNS server and prints the result.
|
||||
|
||||
The kernel DNS resolver sends a UDP query to port 53 of the
|
||||
configured DNS server and waits up to 5 seconds for a reply.
|
||||
Results are cached in an 8-entry kernel cache with TTL support.
|
||||
|
||||
<strong>OUTPUT</strong>
|
||||
Server: 10.0.68.1
|
||||
Name: example.com
|
||||
Address: 93.184.216.34
|
||||
Time: 3ms
|
||||
|
||||
If the lookup fails:
|
||||
|
||||
Could not resolve: badhost.invalid
|
||||
|
||||
<strong>DNS CONFIGURATION</strong>
|
||||
The DNS server address is obtained automatically via DHCP.
|
||||
It can also be viewed and set with ifconfig. The default
|
||||
is 10.0.68.1 (QEMU user-mode networking).
|
||||
|
||||
<strong>EXAMPLES</strong>
|
||||
nslookup google.com
|
||||
nslookup icanhazip.com
|
||||
|
||||
<strong>SEE ALSO</strong>
|
||||
ping(1), fetch(1), dhcp(1), ifconfig(1), syscalls(2)</code></pre>
|
||||
|
||||
<hr>
|
||||
|
||||
<div class="center">
|
||||
<a href="../index.html">Back to Documentation Index</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,155 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="description" content="MontaukOS manual page: ping(1) - send ICMP echo requests">
|
||||
<title>ping(1) - MontaukOS Manual</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Open+Sans:wght@400;600&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
body {
|
||||
font-family: 'Open Sans', Arial, sans-serif;
|
||||
max-width: 960px;
|
||||
margin: 0 auto;
|
||||
padding: 1em;
|
||||
line-height: 1.5;
|
||||
display: flex;
|
||||
gap: 2em;
|
||||
}
|
||||
p { margin: 0 0 0.5em; }
|
||||
h2 { margin: 0.5em 0; }
|
||||
.sidebar {
|
||||
width: 160px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.sidebar ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
.sidebar li {
|
||||
margin: 0.5em 0;
|
||||
}
|
||||
.sidebar a {
|
||||
color: #0066CC;
|
||||
text-decoration: none;
|
||||
font-weight: 600;
|
||||
}
|
||||
.sidebar a:hover {
|
||||
color: #004499;
|
||||
text-decoration: underline;
|
||||
}
|
||||
.sidebar .current {
|
||||
color: #004499;
|
||||
}
|
||||
.sidebar hr {
|
||||
border: none;
|
||||
border-top: 1px solid #999;
|
||||
margin: 0.75em 0;
|
||||
}
|
||||
.main {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
a { color: #0000EE; }
|
||||
a:visited { color: #0066CC; }
|
||||
hr { border-style: solid; border-width: 1px 0 0 0; border-color: #999; }
|
||||
pre { background: #f0f0f0; padding: 0.5em; overflow-x: auto; }
|
||||
code { background: #f0f0f0; padding: 0 0.15em; }
|
||||
pre code { padding: 0; }
|
||||
.center { text-align: center; }
|
||||
.box {
|
||||
border: 1px solid #999;
|
||||
padding: 0.5em;
|
||||
margin: 0.5em 0;
|
||||
}
|
||||
@media (max-width: 700px) {
|
||||
body {
|
||||
flex-direction: column;
|
||||
gap: 1em;
|
||||
padding: 1em 0.75em;
|
||||
}
|
||||
.sidebar {
|
||||
width: auto;
|
||||
}
|
||||
.sidebar ul {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0 1em;
|
||||
}
|
||||
.sidebar hr {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="sidebar">
|
||||
<ul>
|
||||
<li><a href="../../index.html">Home</a></li>
|
||||
<li><a href="../../downloads.html">Downloads</a></li>
|
||||
<li><a href="../index.html">Documentation</a></li>
|
||||
<li><a href="https://git.montaukos.org/daniel/MontaukOS/issues">Issue tracker</a></li>
|
||||
<li><a href="https://git.montaukos.org/daniel/MontaukOS">Git</a></li>
|
||||
</ul>
|
||||
<hr>
|
||||
<ul>
|
||||
<li><a href="index.html" class="current">Man Pages</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="main">
|
||||
<div class="center">
|
||||
<h1>ping(1)</h1>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
|
||||
<pre><code><strong>NAME</strong>
|
||||
ping - send ICMP echo requests
|
||||
|
||||
<strong>SYNOPSIS</strong>
|
||||
ping <host>
|
||||
|
||||
<strong>DESCRIPTION</strong>
|
||||
Sends 4 ICMP echo requests to the specified host and prints
|
||||
the round-trip time for each reply.
|
||||
|
||||
The host may be an IP address or a hostname. Hostnames are
|
||||
resolved via the configured DNS server.
|
||||
|
||||
Each request has a 3-second timeout. Requests are sent at
|
||||
1-second intervals.
|
||||
|
||||
<strong>OUTPUT</strong>
|
||||
PING example.com (93.184.216.34)
|
||||
Reply from 93.184.216.34: time=12ms
|
||||
Reply from 93.184.216.34: time=11ms
|
||||
Reply from 93.184.216.34: time=13ms
|
||||
Reply from 93.184.216.34: time=11ms
|
||||
|
||||
If a reply is not received within the timeout:
|
||||
|
||||
Request timed out
|
||||
|
||||
<strong>EXAMPLES</strong>
|
||||
ping 10.0.68.1
|
||||
Ping the gateway by IP address.
|
||||
|
||||
ping google.com
|
||||
Ping by hostname (requires DNS).
|
||||
|
||||
<strong>SEE ALSO</strong>
|
||||
nslookup(1), ifconfig(1), shell(1), syscalls(2)</code></pre>
|
||||
|
||||
<hr>
|
||||
|
||||
<div class="center">
|
||||
<a href="../index.html">Back to Documentation Index</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,150 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="description" content="MontaukOS manual page: printctl(1) - configure printers and submit print jobs">
|
||||
<title>printctl(1) - MontaukOS Manual</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Open+Sans:wght@400;600&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
body {
|
||||
font-family: 'Open Sans', Arial, sans-serif;
|
||||
max-width: 960px;
|
||||
margin: 0 auto;
|
||||
padding: 1em;
|
||||
line-height: 1.5;
|
||||
display: flex;
|
||||
gap: 2em;
|
||||
}
|
||||
p { margin: 0 0 0.5em; }
|
||||
h2 { margin: 0.5em 0; }
|
||||
.sidebar {
|
||||
width: 160px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.sidebar ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
.sidebar li {
|
||||
margin: 0.5em 0;
|
||||
}
|
||||
.sidebar a {
|
||||
color: #0066CC;
|
||||
text-decoration: none;
|
||||
font-weight: 600;
|
||||
}
|
||||
.sidebar a:hover {
|
||||
color: #004499;
|
||||
text-decoration: underline;
|
||||
}
|
||||
.sidebar .current {
|
||||
color: #004499;
|
||||
}
|
||||
.sidebar hr {
|
||||
border: none;
|
||||
border-top: 1px solid #999;
|
||||
margin: 0.75em 0;
|
||||
}
|
||||
.main {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
a { color: #0000EE; }
|
||||
a:visited { color: #0066CC; }
|
||||
hr { border-style: solid; border-width: 1px 0 0 0; border-color: #999; }
|
||||
pre { background: #f0f0f0; padding: 0.5em; overflow-x: auto; }
|
||||
code { background: #f0f0f0; padding: 0 0.15em; }
|
||||
pre code { padding: 0; }
|
||||
.center { text-align: center; }
|
||||
.box {
|
||||
border: 1px solid #999;
|
||||
padding: 0.5em;
|
||||
margin: 0.5em 0;
|
||||
}
|
||||
@media (max-width: 700px) {
|
||||
body {
|
||||
flex-direction: column;
|
||||
gap: 1em;
|
||||
padding: 1em 0.75em;
|
||||
}
|
||||
.sidebar {
|
||||
width: auto;
|
||||
}
|
||||
.sidebar ul {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0 1em;
|
||||
}
|
||||
.sidebar hr {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="sidebar">
|
||||
<ul>
|
||||
<li><a href="../../index.html">Home</a></li>
|
||||
<li><a href="../../downloads.html">Downloads</a></li>
|
||||
<li><a href="../index.html">Documentation</a></li>
|
||||
<li><a href="https://git.montaukos.org/daniel/MontaukOS/issues">Issue tracker</a></li>
|
||||
<li><a href="https://git.montaukos.org/daniel/MontaukOS">Git</a></li>
|
||||
</ul>
|
||||
<hr>
|
||||
<ul>
|
||||
<li><a href="index.html" class="current">Man Pages</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="main">
|
||||
<div class="center">
|
||||
<h1>printctl(1)</h1>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
|
||||
<pre><code><strong>NAME</strong>
|
||||
printctl - configure printers and submit print jobs
|
||||
<strong>SYNOPSIS</strong>
|
||||
<strong>printctl</strong>
|
||||
<em>command</em>
|
||||
[<em>options</em>]
|
||||
<strong>DESCRIPTION</strong>
|
||||
<strong>printctl</strong>
|
||||
manages the MontaukOS userspace print spooler and submits print jobs to IPP printers.
|
||||
<strong>COMMANDS</strong>
|
||||
|
||||
<strong>set-printer <em>URI</em></strong>
|
||||
Store the default printer URI.
|
||||
|
||||
<strong>show-printer</strong>
|
||||
Print the configured default printer URI.
|
||||
|
||||
<strong>print <em>FILE</em> [--printer <em>URI</em>] [--name <em>JOB</em>] [--wait]</strong>
|
||||
Queue a file for printing.
|
||||
|
||||
<strong>test-page [--printer <em>URI</em>] [--wait] [--no-wait]</strong>
|
||||
Generate and queue a simple test page.
|
||||
|
||||
<strong>status [--verbose]</strong>
|
||||
Show daemon state and queued, active, completed, and failed jobs.
|
||||
|
||||
<strong>inspect <em>JOB-ID</em></strong>
|
||||
Show full metadata and debug details for a queued, active, completed, or failed job.
|
||||
|
||||
<strong>probe [<em>URI</em>]</strong>
|
||||
Probe the configured printer, print host and resolution details, and show IPP capability diagnostics.</code></pre>
|
||||
|
||||
<hr>
|
||||
|
||||
<div class="center">
|
||||
<a href="../index.html">Back to Documentation Index</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,130 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="description" content="MontaukOS manual page: printd(1) - MontaukOS userspace print spooler daemon">
|
||||
<title>printd(1) - MontaukOS Manual</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Open+Sans:wght@400;600&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
body {
|
||||
font-family: 'Open Sans', Arial, sans-serif;
|
||||
max-width: 960px;
|
||||
margin: 0 auto;
|
||||
padding: 1em;
|
||||
line-height: 1.5;
|
||||
display: flex;
|
||||
gap: 2em;
|
||||
}
|
||||
p { margin: 0 0 0.5em; }
|
||||
h2 { margin: 0.5em 0; }
|
||||
.sidebar {
|
||||
width: 160px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.sidebar ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
.sidebar li {
|
||||
margin: 0.5em 0;
|
||||
}
|
||||
.sidebar a {
|
||||
color: #0066CC;
|
||||
text-decoration: none;
|
||||
font-weight: 600;
|
||||
}
|
||||
.sidebar a:hover {
|
||||
color: #004499;
|
||||
text-decoration: underline;
|
||||
}
|
||||
.sidebar .current {
|
||||
color: #004499;
|
||||
}
|
||||
.sidebar hr {
|
||||
border: none;
|
||||
border-top: 1px solid #999;
|
||||
margin: 0.75em 0;
|
||||
}
|
||||
.main {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
a { color: #0000EE; }
|
||||
a:visited { color: #0066CC; }
|
||||
hr { border-style: solid; border-width: 1px 0 0 0; border-color: #999; }
|
||||
pre { background: #f0f0f0; padding: 0.5em; overflow-x: auto; }
|
||||
code { background: #f0f0f0; padding: 0 0.15em; }
|
||||
pre code { padding: 0; }
|
||||
.center { text-align: center; }
|
||||
.box {
|
||||
border: 1px solid #999;
|
||||
padding: 0.5em;
|
||||
margin: 0.5em 0;
|
||||
}
|
||||
@media (max-width: 700px) {
|
||||
body {
|
||||
flex-direction: column;
|
||||
gap: 1em;
|
||||
padding: 1em 0.75em;
|
||||
}
|
||||
.sidebar {
|
||||
width: auto;
|
||||
}
|
||||
.sidebar ul {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0 1em;
|
||||
}
|
||||
.sidebar hr {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="sidebar">
|
||||
<ul>
|
||||
<li><a href="../../index.html">Home</a></li>
|
||||
<li><a href="../../downloads.html">Downloads</a></li>
|
||||
<li><a href="../index.html">Documentation</a></li>
|
||||
<li><a href="https://git.montaukos.org/daniel/MontaukOS/issues">Issue tracker</a></li>
|
||||
<li><a href="https://git.montaukos.org/daniel/MontaukOS">Git</a></li>
|
||||
</ul>
|
||||
<hr>
|
||||
<ul>
|
||||
<li><a href="index.html" class="current">Man Pages</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="main">
|
||||
<div class="center">
|
||||
<h1>printd(1)</h1>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
|
||||
<pre><code><strong>NAME</strong>
|
||||
printd - MontaukOS userspace print spooler daemon
|
||||
<strong>SYNOPSIS</strong>
|
||||
<strong>printd</strong>
|
||||
<strong>DESCRIPTION</strong>
|
||||
<strong>printd</strong>
|
||||
monitors the print spool directories, claims queued jobs, and delivers them to IPP printers.
|
||||
|
||||
It is normally launched automatically by
|
||||
<strong>init</strong>(1)
|
||||
and does not require direct user interaction.</code></pre>
|
||||
|
||||
<hr>
|
||||
|
||||
<div class="center">
|
||||
<a href="../index.html">Back to Documentation Index</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,313 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="description" content="MontaukOS manual page: shell(1) - MontaukOS interactive command shell">
|
||||
<title>shell(1) - MontaukOS Manual</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Open+Sans:wght@400;600&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
body {
|
||||
font-family: 'Open Sans', Arial, sans-serif;
|
||||
max-width: 960px;
|
||||
margin: 0 auto;
|
||||
padding: 1em;
|
||||
line-height: 1.5;
|
||||
display: flex;
|
||||
gap: 2em;
|
||||
}
|
||||
p { margin: 0 0 0.5em; }
|
||||
h2 { margin: 0.5em 0; }
|
||||
.sidebar {
|
||||
width: 160px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.sidebar ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
.sidebar li {
|
||||
margin: 0.5em 0;
|
||||
}
|
||||
.sidebar a {
|
||||
color: #0066CC;
|
||||
text-decoration: none;
|
||||
font-weight: 600;
|
||||
}
|
||||
.sidebar a:hover {
|
||||
color: #004499;
|
||||
text-decoration: underline;
|
||||
}
|
||||
.sidebar .current {
|
||||
color: #004499;
|
||||
}
|
||||
.sidebar hr {
|
||||
border: none;
|
||||
border-top: 1px solid #999;
|
||||
margin: 0.75em 0;
|
||||
}
|
||||
.main {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
a { color: #0000EE; }
|
||||
a:visited { color: #0066CC; }
|
||||
hr { border-style: solid; border-width: 1px 0 0 0; border-color: #999; }
|
||||
pre { background: #f0f0f0; padding: 0.5em; overflow-x: auto; }
|
||||
code { background: #f0f0f0; padding: 0 0.15em; }
|
||||
pre code { padding: 0; }
|
||||
.center { text-align: center; }
|
||||
.box {
|
||||
border: 1px solid #999;
|
||||
padding: 0.5em;
|
||||
margin: 0.5em 0;
|
||||
}
|
||||
@media (max-width: 700px) {
|
||||
body {
|
||||
flex-direction: column;
|
||||
gap: 1em;
|
||||
padding: 1em 0.75em;
|
||||
}
|
||||
.sidebar {
|
||||
width: auto;
|
||||
}
|
||||
.sidebar ul {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0 1em;
|
||||
}
|
||||
.sidebar hr {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="sidebar">
|
||||
<ul>
|
||||
<li><a href="../../index.html">Home</a></li>
|
||||
<li><a href="../../downloads.html">Downloads</a></li>
|
||||
<li><a href="../index.html">Documentation</a></li>
|
||||
<li><a href="https://git.montaukos.org/daniel/MontaukOS/issues">Issue tracker</a></li>
|
||||
<li><a href="https://git.montaukos.org/daniel/MontaukOS">Git</a></li>
|
||||
</ul>
|
||||
<hr>
|
||||
<ul>
|
||||
<li><a href="index.html" class="current">Man Pages</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="main">
|
||||
<div class="center">
|
||||
<h1>shell(1)</h1>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
|
||||
<pre><code><strong>NAME</strong>
|
||||
shell - MontaukOS interactive command shell
|
||||
|
||||
<strong>DESCRIPTION</strong>
|
||||
The MontaukOS shell is a command interpreter launched by init
|
||||
after system services have started. It provides command
|
||||
execution, file navigation, shell variables, command chaining,
|
||||
tab completion, and command history.
|
||||
|
||||
Commands are either shell builtins or external programs. When
|
||||
a command is not a builtin, the shell searches for a matching
|
||||
ELF binary and executes it as a child process.
|
||||
|
||||
<strong>COMMAND RESOLUTION</strong>
|
||||
When a non-builtin command is entered, the shell searches for
|
||||
a matching binary in the following order:
|
||||
|
||||
1. <cwd>/<command> (exact name, e.g. "hello.elf")
|
||||
2. <cwd>/<command>.elf
|
||||
3. 0:/os/<command>.elf
|
||||
4. 0:/os/<command> (no extension)
|
||||
5. If on a non-zero drive, the drive root: <drive>:/<command>[.elf]
|
||||
|
||||
A command containing a "/" (or an explicit drive prefix, or a
|
||||
leading "." or "/") is instead treated as a direct path and
|
||||
resolved by the kernel against the process CWD, trying the
|
||||
path as-is and then with ".elf" appended.
|
||||
|
||||
The first match is spawned and the shell waits for it to exit.
|
||||
If no match is found, the shell prints:
|
||||
|
||||
<command>: command not found
|
||||
|
||||
Arguments after the command name are passed to the spawned
|
||||
process.
|
||||
|
||||
<strong>BUILTINS</strong>
|
||||
|
||||
<strong>help</strong>
|
||||
Display a categorized list of available commands.
|
||||
|
||||
<strong>ls [dir]</strong>
|
||||
List files in the current directory, or in the specified
|
||||
directory. Directory entries are shown with a trailing slash.
|
||||
Examples: ls, ls man, ls os
|
||||
|
||||
<strong>cd [dir]</strong>
|
||||
Change the working directory. With no argument, returns to the
|
||||
logged-in user's home directory (0:/users/<user>); with /,
|
||||
returns to the drive root. Use cd .. to go up one level.
|
||||
The shell prompt reflects the current directory.
|
||||
Examples: cd os, cd .., cd
|
||||
|
||||
<strong>pwd</strong>
|
||||
Print the current working directory as an absolute path
|
||||
(e.g. "0:/os").
|
||||
|
||||
<strong>echo [-n] ...</strong>
|
||||
Print the arguments. -n suppresses the trailing newline.
|
||||
|
||||
<strong>set [VAR=value]</strong>
|
||||
With no argument, list all shell variables (built-in and
|
||||
user-defined). With VAR=value, set a variable. With a bare
|
||||
name, print that variable's value.
|
||||
|
||||
<strong>unset VAR</strong>
|
||||
Remove a user-defined shell variable.
|
||||
|
||||
<strong>true / false</strong>
|
||||
Return exit status 0 / 1 without doing anything. Useful with
|
||||
&& and ||.
|
||||
|
||||
<strong>N:</strong>
|
||||
A bare "<number>:" (e.g. "1:") switches the current drive to
|
||||
drive N and resets the working directory to that drive's root.
|
||||
|
||||
<strong>exit</strong>
|
||||
Terminate the shell process (with the last command's exit code).
|
||||
|
||||
<strong>SYNTAX</strong>
|
||||
<strong>Variables</strong>
|
||||
NAME=value Set a shell variable (no leading $)
|
||||
$VAR or ${VAR} Expand a variable's value
|
||||
$? Exit status of the last command
|
||||
$USER, $HOME, $PWD Built-in dynamic variables (session user,
|
||||
home directory, current directory)
|
||||
\$ Escape a literal '$'
|
||||
|
||||
<strong>Tilde expansion</strong>
|
||||
A leading ~ expands to the session home directory
|
||||
(0:/users/<user>) when followed by end-of-string, '/', or a
|
||||
space.
|
||||
|
||||
<strong>Command chaining</strong>
|
||||
cmd1 ; cmd2 Run cmd2 unconditionally after cmd1
|
||||
cmd1 && cmd2 Run cmd2 only if cmd1 succeeded (exit 0)
|
||||
cmd1 || cmd2 Run cmd2 only if cmd1 failed (nonzero exit)
|
||||
|
||||
Single and double quotes protect ;, &&, and || from being
|
||||
treated as separators.
|
||||
|
||||
<strong>Comments</strong>
|
||||
A '#' outside of quotes starts a comment; the rest of the line
|
||||
is ignored.
|
||||
|
||||
<strong>EXTERNAL COMMANDS</strong>
|
||||
All external commands live in 0:/os/ (see COMMAND RESOLUTION).
|
||||
Where a dedicated man page exists it is noted below; run
|
||||
'man <command>' for details.
|
||||
|
||||
<strong>File commands</strong>
|
||||
cat <file> Display file contents
|
||||
edit [file] Text editor -- see edit(1)
|
||||
copy <src> <dst> Copy a file
|
||||
move <src> <dst> Move/rename a file
|
||||
rm <file> Remove a file
|
||||
touch <file> Create an empty file
|
||||
|
||||
<strong>System commands</strong>
|
||||
man <topic> View manual pages -- see man(1)
|
||||
whoami Print the current username
|
||||
info / mtkfetch Show system information
|
||||
date Show current date and time
|
||||
uptime Show system uptime
|
||||
proclist List running processes
|
||||
power CPU power/thermal status (power [watch [secs]])
|
||||
clear Clear the screen and framebuffer
|
||||
fontscale [n] Get or set terminal font scale -- see fontscale(1)
|
||||
lua Lua interpreter
|
||||
tcc TinyCC (in-system C compiler)
|
||||
reset Reboot the system
|
||||
shutdown Shut down the system
|
||||
|
||||
<strong>Network commands</strong>
|
||||
ping <host> Send ICMP echo requests -- see ping(1)
|
||||
nslookup <host> DNS lookup -- see nslookup(1)
|
||||
ifconfig Show/set network configuration
|
||||
tcpconnect <host> <port> Interactive TCP client
|
||||
irc IRC client
|
||||
dhcp DHCP client -- see dhcp(1)
|
||||
fetch <url> HTTP/HTTPS client (TLS 1.2) -- see fetch(1)
|
||||
wiki <title> Wikipedia article viewer -- see wiki(1)
|
||||
httpd HTTP server
|
||||
|
||||
Network commands accept both IP addresses and hostnames.
|
||||
Hostnames are resolved via the configured DNS server.
|
||||
|
||||
<strong>Bluetooth</strong>
|
||||
btlist List connected Bluetooth devices
|
||||
btbonds List bonded (paired) Bluetooth devices
|
||||
|
||||
<strong>Software-defined radio</strong>
|
||||
sdr [freqMHz [rateHz]] Receive and report basic signal
|
||||
statistics from an attached RTL-SDR dongle
|
||||
|
||||
GUI applications (window server programs, not run from the
|
||||
shell prompt as text commands) live under 0:/apps/, one bundle
|
||||
per app -- e.g. doom, terminal, texteditor, spreadsheet,
|
||||
wordprocessor, paint, calculator, network, bluetooth, audio,
|
||||
disks, devexplorer, procmgr, powermgr, printers, timezone,
|
||||
weather, wikipedia. There is no 0:/games/ directory.
|
||||
|
||||
<strong>TAB COMPLETION</strong>
|
||||
Pressing Tab completes the word under the cursor against, in
|
||||
order: executable names in 0:/os/, shell builtins, and file/
|
||||
directory entries in the current directory. A single match is
|
||||
completed inline; multiple matches are listed below the prompt.
|
||||
|
||||
<strong>INPUT</strong>
|
||||
The shell uses non-blocking keyboard input via SYS_GETKEY (with
|
||||
SYS_INPUT_WAIT to sleep between events) to support arrow key
|
||||
detection. Lines are limited to 255 characters.
|
||||
|
||||
<strong>Editing</strong>
|
||||
Backspace Delete character before cursor
|
||||
Tab Tab-complete the current word
|
||||
Enter Execute the command line
|
||||
|
||||
<strong>History</strong>
|
||||
The shell stores the last 32 unique commands. Duplicate
|
||||
consecutive entries are suppressed.
|
||||
|
||||
Up Arrow Recall previous command
|
||||
Down Arrow Recall next command (or clear line)
|
||||
|
||||
<strong>PROMPT</strong>
|
||||
The prompt displays the current drive and working directory:
|
||||
|
||||
0:/> _ (at root of drive 0)
|
||||
0:/os> _ (in os/ directory)
|
||||
1:/> _ (at root of drive 1)
|
||||
|
||||
<strong>SEE ALSO</strong>
|
||||
man(1), intro(1), syscalls(2)</code></pre>
|
||||
|
||||
<hr>
|
||||
|
||||
<div class="center">
|
||||
<a href="../index.html">Back to Documentation Index</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,187 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="description" content="MontaukOS manual page: spawn(2) - create and wait for processes">
|
||||
<title>spawn(2) - MontaukOS Manual</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Open+Sans:wght@400;600&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
body {
|
||||
font-family: 'Open Sans', Arial, sans-serif;
|
||||
max-width: 960px;
|
||||
margin: 0 auto;
|
||||
padding: 1em;
|
||||
line-height: 1.5;
|
||||
display: flex;
|
||||
gap: 2em;
|
||||
}
|
||||
p { margin: 0 0 0.5em; }
|
||||
h2 { margin: 0.5em 0; }
|
||||
.sidebar {
|
||||
width: 160px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.sidebar ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
.sidebar li {
|
||||
margin: 0.5em 0;
|
||||
}
|
||||
.sidebar a {
|
||||
color: #0066CC;
|
||||
text-decoration: none;
|
||||
font-weight: 600;
|
||||
}
|
||||
.sidebar a:hover {
|
||||
color: #004499;
|
||||
text-decoration: underline;
|
||||
}
|
||||
.sidebar .current {
|
||||
color: #004499;
|
||||
}
|
||||
.sidebar hr {
|
||||
border: none;
|
||||
border-top: 1px solid #999;
|
||||
margin: 0.75em 0;
|
||||
}
|
||||
.main {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
a { color: #0000EE; }
|
||||
a:visited { color: #0066CC; }
|
||||
hr { border-style: solid; border-width: 1px 0 0 0; border-color: #999; }
|
||||
pre { background: #f0f0f0; padding: 0.5em; overflow-x: auto; }
|
||||
code { background: #f0f0f0; padding: 0 0.15em; }
|
||||
pre code { padding: 0; }
|
||||
.center { text-align: center; }
|
||||
.box {
|
||||
border: 1px solid #999;
|
||||
padding: 0.5em;
|
||||
margin: 0.5em 0;
|
||||
}
|
||||
@media (max-width: 700px) {
|
||||
body {
|
||||
flex-direction: column;
|
||||
gap: 1em;
|
||||
padding: 1em 0.75em;
|
||||
}
|
||||
.sidebar {
|
||||
width: auto;
|
||||
}
|
||||
.sidebar ul {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0 1em;
|
||||
}
|
||||
.sidebar hr {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="sidebar">
|
||||
<ul>
|
||||
<li><a href="../../index.html">Home</a></li>
|
||||
<li><a href="../../downloads.html">Downloads</a></li>
|
||||
<li><a href="../index.html">Documentation</a></li>
|
||||
<li><a href="https://git.montaukos.org/daniel/MontaukOS/issues">Issue tracker</a></li>
|
||||
<li><a href="https://git.montaukos.org/daniel/MontaukOS">Git</a></li>
|
||||
</ul>
|
||||
<hr>
|
||||
<ul>
|
||||
<li><a href="index.html" class="current">Man Pages</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="main">
|
||||
<div class="center">
|
||||
<h1>spawn(2)</h1>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
|
||||
<pre><code><strong>NAME</strong>
|
||||
spawn, waitpid - create and wait for processes
|
||||
|
||||
<strong>SYNOPSIS</strong>
|
||||
<strong> int montauk::spawn(const char* path, const char* args = nullptr);</strong>
|
||||
<strong> void montauk::waitpid(int pid);</strong>
|
||||
<strong> int montauk::getargs(char* buf, uint64_t maxLen);</strong>
|
||||
|
||||
<strong>DESCRIPTION</strong>
|
||||
|
||||
<strong>spawn</strong>
|
||||
Loads the ELF64 binary at the given VFS path and creates a new
|
||||
process. The path must include the drive prefix, for example:
|
||||
|
||||
int pid = montauk::spawn("0:/os/hello.elf");
|
||||
|
||||
An optional second argument passes a string to the child:
|
||||
|
||||
int pid = montauk::spawn("0:/os/man.elf", "intro");
|
||||
|
||||
The new process gets its own PML4 page table, a 32 KiB stack
|
||||
(at 0x7FFFFF7000-0x7FFFFFF000), and begins executing at the
|
||||
ELF entry point (_start).
|
||||
|
||||
Returns the new process's PID on success, or -1 on failure.
|
||||
Failure occurs when there are no free process slots (max 256),
|
||||
the file cannot be found, or the ELF is invalid.
|
||||
|
||||
<strong>waitpid</strong>
|
||||
Blocks the calling process until the process with the given PID
|
||||
has exited. Internally, this yields the CPU in a loop:
|
||||
|
||||
montauk::waitpid(pid);
|
||||
|
||||
This is how the shell implements foreground process execution --
|
||||
it spawns a child and waits for it to complete before showing
|
||||
the next prompt.
|
||||
|
||||
<strong>EXAMPLES</strong>
|
||||
Spawn a program and wait for it:
|
||||
|
||||
int pid = montauk::spawn("0:/os/hello.elf");
|
||||
if (pid < 0) {
|
||||
montauk::print("spawn failed\n");
|
||||
} else {
|
||||
montauk::waitpid(pid);
|
||||
montauk::print("child exited\n");
|
||||
}
|
||||
|
||||
<strong>getargs</strong>
|
||||
Copies the argument string into buf (up to maxLen bytes, always
|
||||
null-terminated). Returns the number of characters copied, or
|
||||
-1 on error.
|
||||
|
||||
char args[256];
|
||||
montauk::getargs(args, sizeof(args));
|
||||
|
||||
The argument string is set by the parent when calling spawn().
|
||||
If no arguments were provided, the buffer will be empty.
|
||||
|
||||
<strong>NOTES</strong>
|
||||
The _start() entry point receives no argc/argv. Use getargs()
|
||||
to retrieve the argument string passed by the parent process.
|
||||
|
||||
Process exit codes are not yet collected by waitpid.
|
||||
|
||||
<strong>SEE ALSO</strong>
|
||||
syscalls(2), file(2)</code></pre>
|
||||
|
||||
<hr>
|
||||
|
||||
<div class="center">
|
||||
<a href="../index.html">Back to Documentation Index</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,906 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="description" content="MontaukOS manual page: syscalls(2) - overview of MontaukOS system calls">
|
||||
<title>syscalls(2) - MontaukOS Manual</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Open+Sans:wght@400;600&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
body {
|
||||
font-family: 'Open Sans', Arial, sans-serif;
|
||||
max-width: 960px;
|
||||
margin: 0 auto;
|
||||
padding: 1em;
|
||||
line-height: 1.5;
|
||||
display: flex;
|
||||
gap: 2em;
|
||||
}
|
||||
p { margin: 0 0 0.5em; }
|
||||
h2 { margin: 0.5em 0; }
|
||||
.sidebar {
|
||||
width: 160px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.sidebar ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
.sidebar li {
|
||||
margin: 0.5em 0;
|
||||
}
|
||||
.sidebar a {
|
||||
color: #0066CC;
|
||||
text-decoration: none;
|
||||
font-weight: 600;
|
||||
}
|
||||
.sidebar a:hover {
|
||||
color: #004499;
|
||||
text-decoration: underline;
|
||||
}
|
||||
.sidebar .current {
|
||||
color: #004499;
|
||||
}
|
||||
.sidebar hr {
|
||||
border: none;
|
||||
border-top: 1px solid #999;
|
||||
margin: 0.75em 0;
|
||||
}
|
||||
.main {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
a { color: #0000EE; }
|
||||
a:visited { color: #0066CC; }
|
||||
hr { border-style: solid; border-width: 1px 0 0 0; border-color: #999; }
|
||||
pre { background: #f0f0f0; padding: 0.5em; overflow-x: auto; }
|
||||
code { background: #f0f0f0; padding: 0 0.15em; }
|
||||
pre code { padding: 0; }
|
||||
.center { text-align: center; }
|
||||
.box {
|
||||
border: 1px solid #999;
|
||||
padding: 0.5em;
|
||||
margin: 0.5em 0;
|
||||
}
|
||||
@media (max-width: 700px) {
|
||||
body {
|
||||
flex-direction: column;
|
||||
gap: 1em;
|
||||
padding: 1em 0.75em;
|
||||
}
|
||||
.sidebar {
|
||||
width: auto;
|
||||
}
|
||||
.sidebar ul {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0 1em;
|
||||
}
|
||||
.sidebar hr {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="sidebar">
|
||||
<ul>
|
||||
<li><a href="../../index.html">Home</a></li>
|
||||
<li><a href="../../downloads.html">Downloads</a></li>
|
||||
<li><a href="../index.html">Documentation</a></li>
|
||||
<li><a href="https://git.montaukos.org/daniel/MontaukOS/issues">Issue tracker</a></li>
|
||||
<li><a href="https://git.montaukos.org/daniel/MontaukOS">Git</a></li>
|
||||
</ul>
|
||||
<hr>
|
||||
<ul>
|
||||
<li><a href="index.html" class="current">Man Pages</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="main">
|
||||
<div class="center">
|
||||
<h1>syscalls(2)</h1>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
|
||||
<pre><code><strong>NAME</strong>
|
||||
syscalls - overview of MontaukOS system calls
|
||||
|
||||
<strong>DESCRIPTION</strong>
|
||||
MontaukOS provides 150 system calls (numbers 0-149, sparsely
|
||||
assigned -- not every number in the range is in use) for
|
||||
userspace programs. Syscalls use the x86-64 SYSCALL instruction
|
||||
with the following register convention:
|
||||
|
||||
RAX Syscall number (in) / return value (out)
|
||||
RDI Argument 1
|
||||
RSI Argument 2
|
||||
RDX Argument 3
|
||||
R10 Argument 4
|
||||
R8 Argument 5
|
||||
R9 Argument 6
|
||||
|
||||
Include <Api/Syscall.hpp> for the numeric SYS_* constants and
|
||||
ABI structs, and <montauk/syscall.h> for typed wrappers in the
|
||||
montauk:: namespace. This page groups syscalls the same way the
|
||||
kernel source does (one subsystem header per group).
|
||||
|
||||
<strong>PROCESS MANAGEMENT</strong>
|
||||
<strong>SYS_EXIT (0)</strong>
|
||||
Terminate the calling process.
|
||||
[[noreturn]] void montauk::exit(int code = 0);
|
||||
|
||||
<strong>SYS_YIELD (1)</strong>
|
||||
Yield the remainder of the time slice.
|
||||
void montauk::yield();
|
||||
|
||||
<strong>SYS_SLEEP_MS (2)</strong>
|
||||
Sleep for at least the given number of milliseconds.
|
||||
void montauk::sleep_ms(uint64_t ms);
|
||||
|
||||
<strong>SYS_GETPID (3)</strong>
|
||||
Return the PID of the calling process.
|
||||
int montauk::getpid();
|
||||
|
||||
<strong>SYS_SPAWN (20)</strong>
|
||||
Spawn a new process from an ELF binary on the VFS.
|
||||
int montauk::spawn(const char* path, const char* args = nullptr);
|
||||
|
||||
<strong>SYS_WAITPID (23)</strong>
|
||||
Block until the given process has exited.
|
||||
void montauk::waitpid(int pid);
|
||||
|
||||
<strong>SYS_GETARGS (25)</strong>
|
||||
Get the argument string passed to this process at spawn time.
|
||||
int montauk::getargs(char* buf, uint64_t maxLen);
|
||||
|
||||
<strong>SYS_PROCLIST (61)</strong>
|
||||
List running processes (pid, parent, state, name, heap usage,
|
||||
accumulated CPU time).
|
||||
int montauk::proclist(montauk::abi::ProcInfo* buf, int max);
|
||||
|
||||
<strong>SYS_KILL (62)</strong>
|
||||
Terminate another process by PID.
|
||||
int montauk::kill(int pid);
|
||||
|
||||
<strong>SYS_CHDIR (96)</strong>
|
||||
Change the calling process's current working directory.
|
||||
int montauk::chdir(const char* path);
|
||||
|
||||
<strong>SYS_GETCWD (95)</strong>
|
||||
Get the calling process's current working directory.
|
||||
int montauk::getcwd(char* buf, uint64_t maxLen);
|
||||
|
||||
<strong>SYS_SETUSER (92)</strong>
|
||||
Associate a process with a logged-in user name (used by login/session
|
||||
management).
|
||||
int montauk::setuser(int pid, const char* name);
|
||||
|
||||
<strong>SYS_GETUSER (93)</strong>
|
||||
Get the user name associated with the calling process.
|
||||
int montauk::getuser(char* buf, uint64_t maxLen);
|
||||
|
||||
<strong>THREADING</strong>
|
||||
Threads share the spawning process's address space and heap
|
||||
(see montauk/heap.h for the heap lock). Declared in
|
||||
montauk/thread.h.
|
||||
|
||||
<strong>SYS_THREAD_SPAWN (130)</strong>
|
||||
Spawn a new thread in the calling process. Returns a positive
|
||||
TID on success, -1 on failure.
|
||||
int montauk::thread_spawn(ThreadEntry entry, void* arg,
|
||||
uint64_t stack_bytes = 0);
|
||||
|
||||
<strong>SYS_THREAD_EXIT (131)</strong>
|
||||
Terminate only the calling thread. If it is the main thread,
|
||||
the whole process exits.
|
||||
[[noreturn]] void montauk::thread_exit(int code = 0);
|
||||
|
||||
<strong>SYS_THREAD_JOIN (132)</strong>
|
||||
Block until the given TID exits, then reclaim its kernel state.
|
||||
int montauk::thread_join(int tid, int* out_code = nullptr);
|
||||
|
||||
<strong>SYS_THREAD_SELF (133)</strong>
|
||||
Return the calling thread's TID (equals getpid() for the main
|
||||
thread).
|
||||
int montauk::thread_self();
|
||||
|
||||
<strong>CONSOLE I/O</strong>
|
||||
<strong>SYS_PRINT (4)</strong>
|
||||
Write a null-terminated string to the terminal.
|
||||
void montauk::print(const char* text);
|
||||
|
||||
<strong>SYS_PUTCHAR (5)</strong>
|
||||
Write a single character to the terminal.
|
||||
void montauk::putchar(char c);
|
||||
|
||||
<strong>FILE I/O</strong>
|
||||
<strong>SYS_OPEN (6)</strong>
|
||||
Open a file. Returns a handle or negative on error.
|
||||
int montauk::open(const char* path);
|
||||
|
||||
<strong>SYS_READ (7)</strong>
|
||||
Read bytes from a file at a given offset.
|
||||
int montauk::read(int h, uint8_t* buf, uint64_t off, uint64_t sz);
|
||||
|
||||
<strong>SYS_GETSIZE (8)</strong>
|
||||
Get the size of an open file in bytes.
|
||||
uint64_t montauk::getsize(int handle);
|
||||
|
||||
<strong>SYS_CLOSE (9)</strong>
|
||||
Close a file handle.
|
||||
void montauk::close(int handle);
|
||||
|
||||
<strong>SYS_READDIR (10)</strong>
|
||||
List directory entries (max 256 per call for VFS directories,
|
||||
128 for driver-backed listings such as 0:/os/). For larger
|
||||
directories use SYS_READDIR_AT.
|
||||
int montauk::readdir(const char* path, const char** names, int max);
|
||||
|
||||
<strong>SYS_READDIR_AT (136)</strong>
|
||||
Paginated directory read. Returns entries starting at
|
||||
startIndex; call repeatedly with startIndex advanced by the
|
||||
returned count until it returns 0 to enumerate directories of
|
||||
any size.
|
||||
int montauk::readdir_at(const char* path, const char** names,
|
||||
int max, int startIndex);
|
||||
|
||||
<strong>SYS_FWRITE (41)</strong>
|
||||
Write bytes to a file at a given offset.
|
||||
int montauk::fwrite(int handle, const uint8_t* buf,
|
||||
uint64_t offset, uint64_t size);
|
||||
|
||||
<strong>SYS_FCREATE (42)</strong>
|
||||
Create a new file on the target volume. Returns a handle or
|
||||
negative on error.
|
||||
int montauk::fcreate(const char* path);
|
||||
|
||||
<strong>SYS_FDELETE (77)</strong>
|
||||
Delete a file.
|
||||
int montauk::fdelete(const char* path);
|
||||
|
||||
<strong>SYS_FMKDIR (78)</strong>
|
||||
Create a directory.
|
||||
int montauk::fmkdir(const char* path);
|
||||
|
||||
<strong>SYS_FRENAME (94)</strong>
|
||||
Rename or move a file/directory (used as the basis for file
|
||||
manager move operations).
|
||||
int montauk::frename(const char* oldPath, const char* newPath);
|
||||
|
||||
<strong>SYS_DRIVELIST (79)</strong>
|
||||
List mounted drive numbers.
|
||||
int montauk::drivelist(int* outDrives, int max);
|
||||
|
||||
<strong>SYS_DRIVELABEL (124)</strong>
|
||||
Get the volume label of a drive.
|
||||
int montauk::drivelabel(int drive, char* outLabel, int maxLen);
|
||||
|
||||
<strong>SYS_DRIVEKIND (127)</strong>
|
||||
Get the block device kind backing a drive: 0=unknown/ramdisk,
|
||||
1=SATA, 2=SATAPI, 3=NVMe, 4=USB mass storage.
|
||||
int montauk::drivekind(int drive);
|
||||
|
||||
<strong>MEMORY</strong>
|
||||
<strong>SYS_ALLOC (11)</strong>
|
||||
Map zeroed pages into the process address space.
|
||||
void* montauk::alloc(uint64_t size);
|
||||
|
||||
<strong>SYS_FREE (12)</strong>
|
||||
Reserved (currently a no-op).
|
||||
void montauk::free(void* ptr);
|
||||
|
||||
<strong>SYS_MEMSTATS (67)</strong>
|
||||
Get kernel-wide physical memory usage (total/free/used bytes,
|
||||
page size).
|
||||
void montauk::memstats(montauk::abi::MemStats* out);
|
||||
|
||||
<strong>TIMEKEEPING</strong>
|
||||
<strong>SYS_GETTICKS (13)</strong>
|
||||
Get APIC timer ticks since boot.
|
||||
uint64_t montauk::get_ticks();
|
||||
|
||||
<strong>SYS_GETMILLISECONDS (14)</strong>
|
||||
Get milliseconds elapsed since boot.
|
||||
uint64_t montauk::get_milliseconds();
|
||||
|
||||
<strong>SYS_GETTIME (28)</strong>
|
||||
Get the current wall-clock date and time (UTC).
|
||||
Fills a montauk::abi::DateTime struct with Year, Month, Day,
|
||||
Hour, Minute, and Second fields.
|
||||
void montauk::gettime(montauk::abi::DateTime* out);
|
||||
|
||||
<strong>SYS_SETTZ (90)</strong>
|
||||
Set the process/system timezone offset, in minutes from UTC.
|
||||
void montauk::settz(int offset_minutes);
|
||||
|
||||
<strong>SYS_GETTZ (91)</strong>
|
||||
Get the current timezone offset, in minutes from UTC.
|
||||
int montauk::gettz();
|
||||
|
||||
<strong>SYSTEM</strong>
|
||||
<strong>SYS_GETINFO (15)</strong>
|
||||
Get OS name, version string, API version, max process count,
|
||||
and the monotonic kernel build number.
|
||||
void montauk::get_info(montauk::abi::SysInfo* info);
|
||||
|
||||
<strong>KEYBOARD</strong>
|
||||
<strong>SYS_ISKEYAVAILABLE (16)</strong>
|
||||
Check if a key event is pending (non-blocking).
|
||||
bool montauk::is_key_available();
|
||||
|
||||
<strong>SYS_GETKEY (17)</strong>
|
||||
Get the next key event (press or release).
|
||||
void montauk::getkey(montauk::abi::KeyEvent* out);
|
||||
|
||||
<strong>SYS_GETCHAR (18)</strong>
|
||||
Block until a printable character is typed.
|
||||
char montauk::getchar();
|
||||
|
||||
<strong>SYS_INPUT_WAIT (123)</strong>
|
||||
Block until the input serial number differs from
|
||||
observedSerial or the timeout elapses; used to sleep
|
||||
efficiently between input-driven redraws.
|
||||
uint64_t montauk::input_wait(uint64_t observedSerial, uint64_t timeoutMs);
|
||||
|
||||
<strong>MOUSE</strong>
|
||||
<strong>SYS_MOUSESTATE (47)</strong>
|
||||
Get the current mouse position, scroll delta, and button mask.
|
||||
void montauk::mouse_state(montauk::abi::MouseState* out);
|
||||
|
||||
<strong>SYS_SETMOUSEBOUNDS (48)</strong>
|
||||
Set the maximum X/Y the mouse cursor may reach (e.g. framebuffer
|
||||
dimensions).
|
||||
void montauk::set_mouse_bounds(int32_t maxX, int32_t maxY);
|
||||
|
||||
<strong>NETWORKING</strong>
|
||||
<strong>SYS_PING (19)</strong>
|
||||
Send an ICMP echo request and wait for reply.
|
||||
int32_t montauk::ping(uint32_t ip, uint32_t timeoutMs = 3000);
|
||||
|
||||
<strong>SYS_RESOLVE (44)</strong>
|
||||
Resolve a hostname to an IPv4 address via DNS. Sends a UDP
|
||||
query to the configured DNS server and waits up to 5 seconds
|
||||
for a reply. Returns the IP in network byte order, or 0 on
|
||||
failure. IP address strings (e.g. "10.0.0.1") are detected
|
||||
and returned directly without a DNS query.
|
||||
uint32_t montauk::resolve(const char* hostname);
|
||||
|
||||
<strong>SYS_GETNETCFG (37)</strong>
|
||||
Get the current network configuration (IP, mask, gateway, MAC,
|
||||
DNS server).
|
||||
void montauk::get_netcfg(montauk::abi::NetCfg* out);
|
||||
|
||||
<strong>SYS_SETNETCFG (38)</strong>
|
||||
Set the network configuration (IP, mask, gateway, DNS server).
|
||||
int montauk::set_netcfg(const montauk::abi::NetCfg* cfg);
|
||||
|
||||
<strong>SYS_NETSTATUS (125)</strong>
|
||||
Get adapter status including driver name, link state, polling mode,
|
||||
and RX/TX packet counters.
|
||||
int montauk::net_status(montauk::abi::NetStatus* out);
|
||||
|
||||
<strong>SOCKETS</strong>
|
||||
<strong>SYS_SOCKET (29)</strong>
|
||||
Create a socket. type=SOCK_TCP (1) or SOCK_UDP (2).
|
||||
Returns fd or -1.
|
||||
int montauk::socket(int type);
|
||||
|
||||
<strong>SYS_CONNECT (30)</strong>
|
||||
Connect a TCP socket to a remote host.
|
||||
int montauk::connect(int fd, uint32_t ip, uint16_t port);
|
||||
|
||||
<strong>SYS_BIND (31)</strong>
|
||||
Bind a socket to a local port for listening.
|
||||
int montauk::bind(int fd, uint16_t port);
|
||||
|
||||
<strong>SYS_LISTEN (32)</strong>
|
||||
Start listening for incoming TCP connections.
|
||||
int montauk::listen(int fd);
|
||||
|
||||
<strong>SYS_ACCEPT (33)</strong>
|
||||
Accept an incoming connection on a listening socket.
|
||||
Returns a new socket fd for the client connection.
|
||||
int montauk::accept(int fd);
|
||||
|
||||
<strong>SYS_SEND (34)</strong>
|
||||
Send data on a connected socket. Returns bytes sent.
|
||||
int montauk::send(int fd, const void* data, uint32_t len);
|
||||
|
||||
<strong>SYS_RECV (35)</strong>
|
||||
Receive data from a connected socket. Returns bytes
|
||||
received, 0 if no data available, or -1 on close/error.
|
||||
int montauk::recv(int fd, void* buf, uint32_t maxLen);
|
||||
|
||||
<strong>SYS_CLOSESOCK (36)</strong>
|
||||
Close a socket and release its resources.
|
||||
int montauk::closesocket(int fd);
|
||||
|
||||
<strong>SYS_SENDTO (39)</strong>
|
||||
Send a UDP datagram to a specific destination.
|
||||
int montauk::sendto(int fd, const void* data, uint32_t len,
|
||||
uint32_t destIp, uint16_t destPort);
|
||||
|
||||
<strong>SYS_RECVFROM (40)</strong>
|
||||
Receive a UDP datagram. Returns the source address.
|
||||
int montauk::recvfrom(int fd, void* buf, uint32_t maxLen,
|
||||
uint32_t* srcIp, uint16_t* srcPort);
|
||||
|
||||
<strong>FRAMEBUFFER</strong>
|
||||
<strong>SYS_FBINFO (21)</strong>
|
||||
Get framebuffer dimensions and format.
|
||||
void montauk::fb_info(montauk::abi::FbInfo* info);
|
||||
|
||||
<strong>SYS_FBMAP (22)</strong>
|
||||
Map the framebuffer into process memory.
|
||||
void* montauk::fb_map();
|
||||
|
||||
<strong>TERMINAL</strong>
|
||||
<strong>SYS_TERMSIZE (24)</strong>
|
||||
Get terminal dimensions (columns and rows).
|
||||
void montauk::termsize(int* cols, int* rows);
|
||||
|
||||
<strong>SYS_TERMSCALE (43)</strong>
|
||||
Get or set the terminal font scale factor. When scale_x is 0,
|
||||
returns the current scale as (scale_y << 32 | scale_x). When
|
||||
scale_x is non-zero, sets the font scale and returns the new
|
||||
terminal dimensions as (rows << 32 | cols).
|
||||
void montauk::termscale(int scale_x, int scale_y);
|
||||
void montauk::get_termscale(int* scale_x, int* scale_y);
|
||||
|
||||
<strong>RANDOM</strong>
|
||||
<strong>SYS_GETRANDOM (45)</strong>
|
||||
Fill a buffer with random bytes using RDTSC-seeded entropy.
|
||||
Returns the number of bytes written.
|
||||
int64_t montauk::getrandom(void* buf, uint32_t len);
|
||||
|
||||
<strong>POWER MANAGEMENT</strong>
|
||||
<strong>SYS_RESET (26)</strong>
|
||||
Reboot the system.
|
||||
[[noreturn]] void montauk::reset();
|
||||
|
||||
<strong>SYS_SHUTDOWN (27)</strong>
|
||||
Shut down the system.
|
||||
[[noreturn]] void montauk::shutdown();
|
||||
|
||||
<strong>SYS_SUSPEND (89)</strong>
|
||||
Enter ACPI S3 sleep. Returns after wake, 0 on success.
|
||||
int montauk::suspend();
|
||||
|
||||
<strong>SYS_POWER_REQUEST (135)</strong>
|
||||
Cross-process graceful power-off request channel. The desktop
|
||||
posts a pending action (POWER_REQ_SHUTDOWN / POWER_REQ_REBOOT)
|
||||
then exits; login.elf reads it with POWER_REQ_QUERY
|
||||
(read-and-clear), runs the shutdown stages, and finally calls
|
||||
shutdown()/reset(). See montauk::abi::PowerRequestAction.
|
||||
int montauk::power_request(int action);
|
||||
|
||||
<strong>SYS_POWERINFO (149)</strong>
|
||||
Get the CPU power/thermal snapshot (HWP state, throttling,
|
||||
package temperature, base/max/effective frequency). Returns 0
|
||||
on success, -1 if unsupported by the running hardware.
|
||||
int montauk::syscall1(SYS_POWERINFO, (uint64_t)&out);
|
||||
// out: montauk::abi::PowerInfo*
|
||||
|
||||
<strong>KERNEL LOG</strong>
|
||||
<strong>SYS_KLOG (46)</strong>
|
||||
Read from the kernel ring log buffer.
|
||||
int64_t montauk::read_klog(char* buf, uint64_t size);
|
||||
|
||||
<strong>I/O REDIRECTION</strong>
|
||||
Used by the terminal app and similar programs to run a child
|
||||
process with its console I/O captured instead of going directly
|
||||
to the framebuffer console.
|
||||
|
||||
<strong>SYS_SPAWN_REDIR (49)</strong>
|
||||
Spawn a process with its console I/O redirected to the caller.
|
||||
int montauk::spawn_redir(const char* path, const char* args = nullptr);
|
||||
|
||||
<strong>SYS_CHILDIO_READ (50)</strong>
|
||||
Read buffered output produced by a redirected child.
|
||||
int montauk::childio_read(int childPid, char* buf, int maxLen);
|
||||
|
||||
<strong>SYS_CHILDIO_WRITE (51)</strong>
|
||||
Write text input to a redirected child's stdin.
|
||||
int montauk::childio_write(int childPid, const char* data, int len);
|
||||
|
||||
<strong>SYS_CHILDIO_WRITEKEY (52)</strong>
|
||||
Forward a raw key event to a redirected child.
|
||||
int montauk::childio_writekey(int childPid, const montauk::abi::KeyEvent* key);
|
||||
|
||||
<strong>SYS_CHILDIO_SETTERMSZ (53)</strong>
|
||||
Tell a redirected child its terminal dimensions changed.
|
||||
int montauk::childio_settermsz(int childPid, int cols, int rows);
|
||||
|
||||
<strong>WINDOW SERVER</strong>
|
||||
Window server syscalls are used by GUI programs to create and
|
||||
drive an on-screen window (see montauk/Window.hpp for the
|
||||
higher-level win_create/win_poll/win_present wrappers built on
|
||||
top of these).
|
||||
|
||||
<strong>SYS_WINCREATE (54)</strong>
|
||||
Create a window and get its pixel buffer.
|
||||
int montauk::win_create(const char* title, int w, int h,
|
||||
montauk::abi::WinCreateResult* result);
|
||||
|
||||
<strong>SYS_WINDESTROY (55)</strong>
|
||||
Destroy a window.
|
||||
int montauk::win_destroy(int id);
|
||||
|
||||
<strong>SYS_WINPRESENT (56)</strong>
|
||||
Flush the pixel buffer to the screen.
|
||||
uint64_t montauk::win_present(int id);
|
||||
|
||||
<strong>SYS_WINPOLL (57)</strong>
|
||||
Poll the next event (key, mouse, resize, close, scale) for a
|
||||
window.
|
||||
int montauk::win_poll(int id, montauk::abi::WinEvent* event);
|
||||
|
||||
<strong>SYS_WINENUM (58)</strong>
|
||||
Enumerate all windows currently managed by the window server.
|
||||
int montauk::win_enumerate(montauk::abi::WinInfo* info, int max);
|
||||
|
||||
<strong>SYS_WINMAP (59)</strong>
|
||||
Map (or re-map) a window's pixel buffer into the caller's
|
||||
address space.
|
||||
uint64_t montauk::win_map(int id);
|
||||
|
||||
<strong>SYS_WINUNMAP (97)</strong>
|
||||
Unmap a window's pixel buffer from the caller's address space.
|
||||
int montauk::win_unmap(int id);
|
||||
|
||||
<strong>SYS_WINSENDEVENT (60)</strong>
|
||||
Inject an event into a window's event queue.
|
||||
int montauk::win_sendevent(int id, const montauk::abi::WinEvent* event);
|
||||
|
||||
<strong>SYS_WINRESIZE (64)</strong>
|
||||
Resize a window and its pixel buffer.
|
||||
uint64_t montauk::win_resize(int id, int w, int h);
|
||||
|
||||
<strong>SYS_WINSETSCALE (65)</strong>
|
||||
Set the desktop-wide UI scale factor.
|
||||
int montauk::win_setscale(int scale);
|
||||
|
||||
<strong>SYS_WINGETSCALE (66)</strong>
|
||||
Get the desktop-wide UI scale factor.
|
||||
int montauk::win_getscale();
|
||||
|
||||
<strong>SYS_WINSETCURSOR (68)</strong>
|
||||
Set the mouse cursor shown while over a window (0=arrow,
|
||||
1=resize_h, 2=resize_v).
|
||||
int montauk::win_setcursor(int id, int cursor);
|
||||
|
||||
<strong>SYS_WINSETFLAGS (126)</strong>
|
||||
Set window flags (e.g. WIN_FLAG_FULLSCREEN).
|
||||
int montauk::win_setflags(int id, uint32_t flags);
|
||||
|
||||
<strong>DEVICES</strong>
|
||||
<strong>SYS_DEVLIST (63)</strong>
|
||||
Enumerate detected devices (CPU, interrupts, timers, input,
|
||||
USB, network, display, storage, PCI, audio, ACPI) for the
|
||||
device explorer app.
|
||||
int montauk::devlist(montauk::abi::DevInfo* buf, int max);
|
||||
|
||||
<strong>SYS_DISKINFO (69)</strong>
|
||||
Get detailed info for one block device (model, serial, sector
|
||||
size, NCQ/TRIM/SMART support, etc.).
|
||||
int montauk::diskinfo(montauk::abi::DiskInfo* buf, int port);
|
||||
|
||||
<strong>STORAGE</strong>
|
||||
<strong>SYS_PARTLIST (70)</strong>
|
||||
Enumerate GPT partitions across all block devices.
|
||||
int montauk::partlist(montauk::abi::PartInfo* buf, int max);
|
||||
|
||||
<strong>SYS_DISKREAD (71)</strong>
|
||||
Raw, driver-agnostic sector read from a block device.
|
||||
int64_t montauk::disk_read(int blockDev, uint64_t lba,
|
||||
uint32_t sectorCount, void* buf);
|
||||
|
||||
<strong>SYS_DISKWRITE (72)</strong>
|
||||
Raw, driver-agnostic sector write to a block device.
|
||||
int64_t montauk::disk_write(int blockDev, uint64_t lba,
|
||||
uint32_t sectorCount, const void* buf);
|
||||
|
||||
<strong>SYS_GPTINIT (73)</strong>
|
||||
Initialize a fresh GPT partition table on a block device.
|
||||
int montauk::gpt_init(int blockDev);
|
||||
|
||||
<strong>SYS_GPTADD (74)</strong>
|
||||
Add a partition to an existing GPT table.
|
||||
int montauk::gpt_add(const montauk::abi::GptAddParams* params);
|
||||
|
||||
<strong>SYS_FSMOUNT (75)</strong>
|
||||
Mount a partition's filesystem onto a drive number.
|
||||
int montauk::fs_mount(int partIndex, int driveNum);
|
||||
|
||||
<strong>SYS_FSFORMAT (76)</strong>
|
||||
Format a partition with a filesystem (FS_TYPE_FAT32 or
|
||||
FS_TYPE_EXT2).
|
||||
int montauk::fs_format(const montauk::abi::FsFormatParams* params);
|
||||
|
||||
<strong>SYS_FS_SYNC (134)</strong>
|
||||
Flush all block-device write caches and cleanly unmount
|
||||
disk-backed volumes ahead of power-off. Returns the number of
|
||||
volumes unmounted. Part of the graceful shutdown sequence
|
||||
(see SYS_POWER_REQUEST).
|
||||
int montauk::fs_sync();
|
||||
|
||||
<strong>AUDIO</strong>
|
||||
<strong>SYS_AUDIOOPEN (80)</strong>
|
||||
Open a mixer output stream at the given sample rate, channel
|
||||
count, and bit depth. Returns a stream handle.
|
||||
int montauk::audio_open(uint32_t sampleRate, uint8_t channels,
|
||||
uint8_t bitsPerSample);
|
||||
|
||||
<strong>SYS_AUDIOCLOSE (81)</strong>
|
||||
Close an audio stream.
|
||||
void montauk::audio_close(int handle);
|
||||
|
||||
<strong>SYS_AUDIOWRITE (82)</strong>
|
||||
Write PCM samples to an audio stream.
|
||||
int montauk::audio_write(int handle, const void* data, uint32_t size);
|
||||
|
||||
<strong>SYS_AUDIOCTL (83)</strong>
|
||||
Control an audio stream or the global mixer. Commands 0-3 act
|
||||
on the stream named by the handle argument; commands 4-12 act
|
||||
on that stream's routing/mute state or the global master and
|
||||
ignore or reuse the handle as documented below.
|
||||
int montauk::audio_ctl(int handle, int cmd, int value);
|
||||
|
||||
Convenience wrappers (all thin calls onto audio_ctl):
|
||||
audio_set_volume, audio_get_volume AUDIO_CTL_{SET,GET}_VOLUME (0/1)
|
||||
audio_get_pos AUDIO_CTL_GET_POS (2)
|
||||
audio_pause, audio_resume AUDIO_CTL_PAUSE (3)
|
||||
audio_get_output AUDIO_CTL_GET_OUTPUT (4): 0=HDA, 1=Bluetooth
|
||||
audio_set_output AUDIO_CTL_SET_OUTPUT (5): switch all streams
|
||||
(SET_OUTPUT, 5) switch a stream's output route
|
||||
audio_bt_status AUDIO_CTL_BT_STATUS (6)
|
||||
audio_set_master_volume, _get_ AUDIO_CTL_{SET,GET}_MASTER_VOLUME (7/8), 0-100
|
||||
audio_set_mute, audio_get_mute AUDIO_CTL_{SET,GET}_MUTE (9/10), per-stream
|
||||
audio_set_master_mute, _get_ AUDIO_CTL_{SET,GET}_MASTER_MUTE (11/12)
|
||||
|
||||
<strong>SYS_AUDIOLIST (128)</strong>
|
||||
Enumerate active mixer streams (owner PID, name, format,
|
||||
volume, mute/pause state).
|
||||
int montauk::audio_list(montauk::abi::AudioStreamInfo* buf, int maxCount);
|
||||
|
||||
<strong>SYS_AUDIOWAIT (129)</strong>
|
||||
Return the current mixer state serial. With timeoutMs > 0,
|
||||
blocks until the serial differs from prevSerial or the timeout
|
||||
elapses; with timeoutMs == 0 it returns immediately.
|
||||
uint64_t montauk::audio_wait(uint64_t prevSerial, uint64_t timeoutMs);
|
||||
|
||||
<strong>BLUETOOTH</strong>
|
||||
<strong>SYS_BTSCAN (84)</strong>
|
||||
Scan for discoverable Bluetooth devices for up to timeoutMs.
|
||||
int montauk::bt_scan(montauk::abi::BtScanResult* buf, int maxCount,
|
||||
uint32_t timeoutMs);
|
||||
|
||||
<strong>SYS_BTCONNECT (85)</strong>
|
||||
Connect (and pair/bond if needed) to a device by BD_ADDR.
|
||||
int montauk::bt_connect(const uint8_t* bdAddr);
|
||||
|
||||
<strong>SYS_BTDISCONNECT (86)</strong>
|
||||
Disconnect from a device by BD_ADDR.
|
||||
int montauk::bt_disconnect(const uint8_t* bdAddr);
|
||||
|
||||
<strong>SYS_BTLIST (87)</strong>
|
||||
List currently connected devices.
|
||||
int montauk::bt_list(montauk::abi::BtDevInfo* buf, int maxCount);
|
||||
|
||||
<strong>SYS_BTINFO (88)</strong>
|
||||
Get local adapter info (BD_ADDR, name, init/scanning state).
|
||||
int montauk::bt_info(montauk::abi::BtAdapterInfo* buf);
|
||||
|
||||
<strong>SYS_BTSETADDR (137)</strong>
|
||||
Change the adapter's BD_ADDR (6-byte buffer, byte 0 is the
|
||||
least-significant octet). Volatile -- apply after the last
|
||||
controller reset and persist separately to bluetooth.toml.
|
||||
int montauk::bt_set_addr(const uint8_t* bdAddr);
|
||||
|
||||
<strong>SYS_BTBONDS (138)</strong>
|
||||
List bonded (paired) devices.
|
||||
int montauk::bt_bonds(montauk::abi::BtBondInfo* buf, int maxCount);
|
||||
|
||||
<strong>SYS_BTFORGET (139)</strong>
|
||||
Forget a paired device; it must re-pair next time.
|
||||
int montauk::bt_forget(const uint8_t* bdAddr);
|
||||
|
||||
<strong>SOFTWARE-DEFINED RADIO</strong>
|
||||
Receive-only SDR API. Receivers are enumerated by index in
|
||||
[0, SYS_SDR_COUNT); SYS_SDR_OPEN returns a handle used by the
|
||||
rest of the calls. Samples are delivered as interleaved 8-bit
|
||||
unsigned I/Q (CU8, SDR_FORMAT_CU8) from the device's ring
|
||||
buffer. Backed by an RTL-SDR (RTL2832U + R820T2) driver.
|
||||
|
||||
<strong>SYS_SDR_COUNT (140)</strong>
|
||||
Number of available SDR receivers.
|
||||
int montauk::sdr_count();
|
||||
|
||||
<strong>SYS_SDR_INFO (141)</strong>
|
||||
Get static/dynamic info for one receiver by index (name, tuner,
|
||||
frequency/sample-rate ranges, gain steps, present/streaming
|
||||
flags).
|
||||
int montauk::sdr_info(int index, montauk::abi::SdrDeviceInfo* out);
|
||||
|
||||
<strong>SYS_SDR_OPEN (142)</strong>
|
||||
Open a receiver by index. Returns a handle.
|
||||
int montauk::sdr_open(int index);
|
||||
|
||||
<strong>SYS_SDR_CLOSE (143)</strong>
|
||||
Close a receiver handle.
|
||||
int montauk::sdr_close(int handle);
|
||||
|
||||
<strong>SYS_SDR_START (144)</strong>
|
||||
Begin streaming samples.
|
||||
int montauk::sdr_start(int handle);
|
||||
|
||||
<strong>SYS_SDR_STOP (145)</strong>
|
||||
Stop streaming samples.
|
||||
int montauk::sdr_stop(int handle);
|
||||
|
||||
<strong>SYS_SDR_READ (146)</strong>
|
||||
Non-blocking read of queued I/Q samples. Returns bytes copied.
|
||||
int montauk::sdr_read(int handle, void* buf, uint32_t len);
|
||||
|
||||
<strong>SYS_SDR_SETPARAM (147)</strong>
|
||||
Set a tunable parameter (see SDR_PARAM_* below).
|
||||
int montauk::sdr_set_param(int handle, int param, uint64_t value);
|
||||
|
||||
<strong>SYS_SDR_GETPARAM (148)</strong>
|
||||
Get a tunable parameter's current value.
|
||||
int64_t montauk::sdr_get_param(int handle, int param);
|
||||
|
||||
Parameters (montauk::abi::SDR_PARAM_*): FREQ (center frequency,
|
||||
Hz), SAMPLE_RATE (Hz), GAIN_MODE (0=auto/AGC, 1=manual), GAIN
|
||||
(tenths of dB), FREQ_CORR (ppm), AGC (demod digital AGC, 0/1),
|
||||
DIRECT_SAMP (0=off, 1=I, 2=Q). Convenience wrappers exist for
|
||||
each: sdr_set_freq/sdr_get_freq, sdr_set_sample_rate/
|
||||
sdr_get_sample_rate, sdr_set_gain_mode, sdr_set_gain,
|
||||
sdr_set_freq_correction, sdr_set_agc.
|
||||
|
||||
<strong>CLIPBOARD</strong>
|
||||
<strong>SYS_CLIPBOARD_SET_TEXT (119)</strong>
|
||||
Set the system clipboard's text contents (max
|
||||
CLIPBOARD_MAX_TEXT_BYTES, 256 KiB).
|
||||
int montauk::clipboard_set_text(const char* data, uint32_t len);
|
||||
|
||||
<strong>SYS_CLIPBOARD_GET_INFO (120)</strong>
|
||||
Get the clipboard's current size and serial number (for
|
||||
change detection).
|
||||
int montauk::clipboard_get_info(montauk::abi::ClipboardInfo* out);
|
||||
|
||||
<strong>SYS_CLIPBOARD_GET_TEXT (121)</strong>
|
||||
Read the clipboard's text contents.
|
||||
int montauk::clipboard_get_text(char* buf, uint32_t bufLen,
|
||||
uint32_t* outLen, uint64_t* outSerial = nullptr);
|
||||
|
||||
<strong>SYS_CLIPBOARD_CLEAR (122)</strong>
|
||||
Clear the clipboard.
|
||||
int montauk::clipboard_clear();
|
||||
|
||||
<strong>GENERIC IPC</strong>
|
||||
Handle-based IPC primitives underlying streams, mailboxes,
|
||||
waitsets, and shared-memory surfaces (see kernel/src/Ipc/Ipc.hpp).
|
||||
All are accessed via numeric handles with rights-based security
|
||||
and can be waited on with SYS_WAIT_HANDLE or a waitset.
|
||||
|
||||
<strong>SYS_DUPHANDLE (98)</strong>
|
||||
Duplicate a handle (e.g. to hand a copy to a child process).
|
||||
int montauk::dup_handle(int handle);
|
||||
|
||||
<strong>SYS_WAIT_HANDLE (99)</strong>
|
||||
Block until a handle's signals intersect wantedSignals, or
|
||||
timeoutMs elapses. See IPC_SIGNAL_* (READABLE, WRITABLE,
|
||||
PEER_CLOSED, EXITED, READY).
|
||||
uint32_t montauk::wait_handle(int handle, uint32_t wantedSignals,
|
||||
uint64_t timeoutMs = ~0ULL);
|
||||
|
||||
<strong>SYS_STREAM_CREATE (100)</strong>
|
||||
Create a byte-pipe stream, returning a read handle and a write
|
||||
handle.
|
||||
int montauk::stream_create(int* outReadHandle, int* outWriteHandle,
|
||||
uint32_t capacity = 0);
|
||||
|
||||
<strong>SYS_STREAM_READ (101)</strong>
|
||||
Read bytes from a stream handle.
|
||||
int montauk::stream_read(int handle, void* buf, int maxLen);
|
||||
|
||||
<strong>SYS_STREAM_WRITE (102)</strong>
|
||||
Write bytes to a stream handle.
|
||||
int montauk::stream_write(int handle, const void* data, int len);
|
||||
|
||||
<strong>SYS_MAILBOX_CREATE (103)</strong>
|
||||
Create a message-queue mailbox, returning a send handle and a
|
||||
receive handle.
|
||||
int montauk::mailbox_create(int* outSendHandle, int* outRecvHandle);
|
||||
|
||||
<strong>SYS_MAILBOX_SEND (104)</strong>
|
||||
Send a typed message, optionally attaching a handle to
|
||||
transfer to the receiver.
|
||||
int montauk::mailbox_send(int handle, uint32_t msgType, const void* data,
|
||||
uint16_t len, int attachHandle = -1);
|
||||
|
||||
<strong>SYS_MAILBOX_RECV (105)</strong>
|
||||
Receive a message.
|
||||
int montauk::mailbox_recv(int handle, uint32_t* outMsgType, void* data,
|
||||
uint16_t* inOutLen, int* outAttachHandle = nullptr);
|
||||
|
||||
<strong>SYS_WAITSET_CREATE (106)</strong>
|
||||
Create a waitset for multiplexing waits across many handles.
|
||||
int montauk::waitset_create();
|
||||
|
||||
<strong>SYS_WAITSET_ADD (107)</strong>
|
||||
Add a handle and its signal mask to a waitset.
|
||||
int montauk::waitset_add(int waitsetHandle, int targetHandle,
|
||||
uint32_t signals);
|
||||
|
||||
<strong>SYS_WAITSET_REMOVE (108)</strong>
|
||||
Remove an entry from a waitset by index.
|
||||
int montauk::waitset_remove(int waitsetHandle, int index);
|
||||
|
||||
<strong>SYS_WAITSET_WAIT (109)</strong>
|
||||
Block until any member handle's watched signals fire, or
|
||||
timeoutMs elapses.
|
||||
int montauk::waitset_wait(int waitsetHandle, montauk::abi::IpcWaitResult* outReady,
|
||||
uint64_t timeoutMs = ~0ULL);
|
||||
|
||||
<strong>SYS_PROC_OPEN (110)</strong>
|
||||
Open a handle to another process by PID (for waiting on its
|
||||
exit via IPC_SIGNAL_EXITED, etc.).
|
||||
int montauk::proc_open(int pid);
|
||||
|
||||
<strong>SYS_SURFACE_CREATE (111)</strong>
|
||||
Create a shared pixel-buffer surface of byteSize bytes.
|
||||
int montauk::surface_create(uint64_t byteSize);
|
||||
|
||||
<strong>SYS_SURFACE_MAP (112)</strong>
|
||||
Map a surface into the caller's address space.
|
||||
void* montauk::surface_map(int handle);
|
||||
|
||||
<strong>SYS_SURFACE_RESIZE (113)</strong>
|
||||
Resize a surface.
|
||||
int montauk::surface_resize(int handle, uint64_t newSize);
|
||||
|
||||
<strong>SHARED LIBRARIES</strong>
|
||||
<strong>SYS_LOAD_LIB (114)</strong>
|
||||
Load a shared library ELF (.lib) into the caller's address
|
||||
space.
|
||||
int montauk::load_lib(const char* path);
|
||||
|
||||
<strong>SYS_UNLOAD_LIB (115)</strong>
|
||||
Unload a previously loaded library.
|
||||
int montauk::unload_lib(int handle);
|
||||
|
||||
<strong>SYS_DLSYM (116)</strong>
|
||||
Resolve a symbol offset within a loaded library to a callable
|
||||
address.
|
||||
void* montauk::dlsym(int handle, uint64_t symbolOffset);
|
||||
|
||||
<strong>SYS_GETLIBBASE (117)</strong>
|
||||
Get the base virtual address a loaded library was mapped at.
|
||||
uint64_t montauk::get_libbase(int handle);
|
||||
|
||||
<strong>CRASH REPORTING</strong>
|
||||
<strong>SYS_CRASH_REPORT (118)</strong>
|
||||
Retrieve the kernel-filled crash report for the last faulting
|
||||
process (exception vector/name, faulting address, register
|
||||
state, page-fault error bits). Used by the crashpad app.
|
||||
int montauk::crash_report(montauk::abi::CrashReportInfo* out);
|
||||
|
||||
<strong>SEE ALSO</strong>
|
||||
spawn(2), file(2), framebuffer(2), malloc(3), intro(1)</code></pre>
|
||||
|
||||
<hr>
|
||||
|
||||
<div class="center">
|
||||
<a href="../index.html">Back to Documentation Index</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,355 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="description" content="MontaukOS manual page: tls-errors(5) - BearSSL TLS and X.509 error codes">
|
||||
<title>tls-errors(5) - MontaukOS Manual</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Open+Sans:wght@400;600&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
body {
|
||||
font-family: 'Open Sans', Arial, sans-serif;
|
||||
max-width: 960px;
|
||||
margin: 0 auto;
|
||||
padding: 1em;
|
||||
line-height: 1.5;
|
||||
display: flex;
|
||||
gap: 2em;
|
||||
}
|
||||
p { margin: 0 0 0.5em; }
|
||||
h2 { margin: 0.5em 0; }
|
||||
.sidebar {
|
||||
width: 160px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.sidebar ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
.sidebar li {
|
||||
margin: 0.5em 0;
|
||||
}
|
||||
.sidebar a {
|
||||
color: #0066CC;
|
||||
text-decoration: none;
|
||||
font-weight: 600;
|
||||
}
|
||||
.sidebar a:hover {
|
||||
color: #004499;
|
||||
text-decoration: underline;
|
||||
}
|
||||
.sidebar .current {
|
||||
color: #004499;
|
||||
}
|
||||
.sidebar hr {
|
||||
border: none;
|
||||
border-top: 1px solid #999;
|
||||
margin: 0.75em 0;
|
||||
}
|
||||
.main {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
a { color: #0000EE; }
|
||||
a:visited { color: #0066CC; }
|
||||
hr { border-style: solid; border-width: 1px 0 0 0; border-color: #999; }
|
||||
pre { background: #f0f0f0; padding: 0.5em; overflow-x: auto; }
|
||||
code { background: #f0f0f0; padding: 0 0.15em; }
|
||||
pre code { padding: 0; }
|
||||
.center { text-align: center; }
|
||||
.box {
|
||||
border: 1px solid #999;
|
||||
padding: 0.5em;
|
||||
margin: 0.5em 0;
|
||||
}
|
||||
@media (max-width: 700px) {
|
||||
body {
|
||||
flex-direction: column;
|
||||
gap: 1em;
|
||||
padding: 1em 0.75em;
|
||||
}
|
||||
.sidebar {
|
||||
width: auto;
|
||||
}
|
||||
.sidebar ul {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0 1em;
|
||||
}
|
||||
.sidebar hr {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="sidebar">
|
||||
<ul>
|
||||
<li><a href="../../index.html">Home</a></li>
|
||||
<li><a href="../../downloads.html">Downloads</a></li>
|
||||
<li><a href="../index.html">Documentation</a></li>
|
||||
<li><a href="https://git.montaukos.org/daniel/MontaukOS/issues">Issue tracker</a></li>
|
||||
<li><a href="https://git.montaukos.org/daniel/MontaukOS">Git</a></li>
|
||||
</ul>
|
||||
<hr>
|
||||
<ul>
|
||||
<li><a href="index.html" class="current">Man Pages</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="main">
|
||||
<div class="center">
|
||||
<h1>tls-errors(5)</h1>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
|
||||
<pre><code><strong>NAME</strong>
|
||||
tls-errors - BearSSL TLS and X.509 error codes
|
||||
|
||||
<strong>DESCRIPTION</strong>
|
||||
MontaukOS uses BearSSL for TLS 1.2 connections. When a TLS
|
||||
operation fails, an integer error code is reported. This page
|
||||
lists all possible error codes.
|
||||
|
||||
<strong>SSL/TLS ENGINE ERRORS</strong>
|
||||
|
||||
<strong>0 BR_ERR_OK</strong>
|
||||
No error.
|
||||
|
||||
<strong>1 BR_ERR_BAD_PARAM</strong>
|
||||
Caller-provided parameter is incorrect.
|
||||
|
||||
<strong>2 BR_ERR_BAD_STATE</strong>
|
||||
Operation cannot be applied in the current engine state.
|
||||
|
||||
<strong>3 BR_ERR_UNSUPPORTED_VERSION</strong>
|
||||
Incoming protocol or record version is unsupported.
|
||||
|
||||
<strong>4 BR_ERR_BAD_VERSION</strong>
|
||||
Incoming record version does not match the expected version.
|
||||
|
||||
<strong>5 BR_ERR_BAD_LENGTH</strong>
|
||||
Incoming record length is invalid.
|
||||
|
||||
<strong>6 BR_ERR_TOO_LARGE</strong>
|
||||
Incoming record is too large, or buffer is too small for the
|
||||
handshake message to send.
|
||||
|
||||
<strong>7 BR_ERR_BAD_MAC</strong>
|
||||
Decryption found invalid padding, or the record MAC is
|
||||
not correct.
|
||||
|
||||
<strong>8 BR_ERR_NO_RANDOM</strong>
|
||||
No initial entropy was provided and none could be obtained
|
||||
from the OS.
|
||||
|
||||
<strong>9 BR_ERR_UNKNOWN_TYPE</strong>
|
||||
Incoming record type is unknown.
|
||||
|
||||
<strong>10 BR_ERR_UNEXPECTED</strong>
|
||||
Incoming record or message has wrong type for the current
|
||||
engine state.
|
||||
|
||||
<strong>12 BR_ERR_BAD_CCS</strong>
|
||||
ChangeCipherSpec message from the peer has invalid contents.
|
||||
|
||||
<strong>13 BR_ERR_BAD_ALERT</strong>
|
||||
Alert message from the peer has invalid contents (odd length).
|
||||
|
||||
<strong>14 BR_ERR_BAD_HANDSHAKE</strong>
|
||||
Incoming handshake message decoding failed.
|
||||
|
||||
<strong>15 BR_ERR_OVERSIZED_ID</strong>
|
||||
ServerHello contains a session ID larger than 32 bytes.
|
||||
|
||||
<strong>16 BR_ERR_BAD_CIPHER_SUITE</strong>
|
||||
Server wants to use a cipher suite that we did not advertise,
|
||||
or we tried to advertise a cipher suite that we do not support.
|
||||
|
||||
<strong>17 BR_ERR_BAD_COMPRESSION</strong>
|
||||
Server wants to use a compression method that we did not
|
||||
advertise.
|
||||
|
||||
<strong>18 BR_ERR_BAD_FRAGLEN</strong>
|
||||
Server's max fragment length does not match client's.
|
||||
|
||||
<strong>19 BR_ERR_BAD_SECRENEG</strong>
|
||||
Secure renegotiation failed.
|
||||
|
||||
<strong>20 BR_ERR_EXTRA_EXTENSION</strong>
|
||||
Server sent an extension type that we did not announce, or
|
||||
used the same extension type more than once in ServerHello.
|
||||
|
||||
<strong>21 BR_ERR_BAD_SNI</strong>
|
||||
Invalid Server Name Indication contents (when used by the
|
||||
server, this extension shall be empty).
|
||||
|
||||
<strong>22 BR_ERR_BAD_HELLO_DONE</strong>
|
||||
Invalid ServerHelloDone from the server (length is not 0).
|
||||
|
||||
<strong>23 BR_ERR_LIMIT_EXCEEDED</strong>
|
||||
Internal limit exceeded (e.g. server's public key is too
|
||||
large).
|
||||
|
||||
<strong>24 BR_ERR_BAD_FINISHED</strong>
|
||||
Finished message from peer does not match the expected value.
|
||||
|
||||
<strong>25 BR_ERR_RESUME_MISMATCH</strong>
|
||||
Session resumption attempted with a different version or
|
||||
cipher suite.
|
||||
|
||||
<strong>26 BR_ERR_INVALID_ALGORITHM</strong>
|
||||
Unsupported or invalid algorithm (ECDHE curve, signature
|
||||
algorithm, hash function).
|
||||
|
||||
<strong>27 BR_ERR_BAD_SIGNATURE</strong>
|
||||
Invalid signature on ServerKeyExchange or CertificateVerify.
|
||||
|
||||
<strong>28 BR_ERR_WRONG_KEY_USAGE</strong>
|
||||
Peer's public key does not have the proper type or is not
|
||||
allowed for the requested operation.
|
||||
|
||||
<strong>29 BR_ERR_NO_CLIENT_AUTH</strong>
|
||||
Client did not send a certificate upon request, or the client
|
||||
certificate could not be validated.
|
||||
|
||||
<strong>31 BR_ERR_IO</strong>
|
||||
I/O error or premature close on the underlying transport.
|
||||
|
||||
<strong>X.509 CERTIFICATE ERRORS</strong>
|
||||
|
||||
<strong>32 BR_ERR_X509_OK</strong>
|
||||
X.509 validation was successful (not an error).
|
||||
|
||||
<strong>33 BR_ERR_X509_INVALID_VALUE</strong>
|
||||
Invalid value in an ASN.1 structure.
|
||||
|
||||
<strong>34 BR_ERR_X509_TRUNCATED</strong>
|
||||
Truncated certificate.
|
||||
|
||||
<strong>35 BR_ERR_X509_EMPTY_CHAIN</strong>
|
||||
Empty certificate chain (no certificate at all).
|
||||
|
||||
<strong>36 BR_ERR_X509_INNER_TRUNC</strong>
|
||||
Inner element extends beyond outer element size.
|
||||
|
||||
<strong>37 BR_ERR_X509_BAD_TAG_CLASS</strong>
|
||||
Unsupported tag class (application or private).
|
||||
|
||||
<strong>38 BR_ERR_X509_BAD_TAG_VALUE</strong>
|
||||
Unsupported tag value.
|
||||
|
||||
<strong>39 BR_ERR_X509_INDEFINITE_LENGTH</strong>
|
||||
Indefinite length encoding found.
|
||||
|
||||
<strong>40 BR_ERR_X509_EXTRA_ELEMENT</strong>
|
||||
Extraneous element in certificate.
|
||||
|
||||
<strong>41 BR_ERR_X509_UNEXPECTED</strong>
|
||||
Unexpected element in certificate.
|
||||
|
||||
<strong>42 BR_ERR_X509_NOT_CONSTRUCTED</strong>
|
||||
Expected constructed element, but found primitive.
|
||||
|
||||
<strong>43 BR_ERR_X509_NOT_PRIMITIVE</strong>
|
||||
Expected primitive element, but found constructed.
|
||||
|
||||
<strong>44 BR_ERR_X509_PARTIAL_BYTE</strong>
|
||||
BIT STRING length is not a multiple of 8.
|
||||
|
||||
<strong>45 BR_ERR_X509_BAD_BOOLEAN</strong>
|
||||
BOOLEAN value has invalid length.
|
||||
|
||||
<strong>46 BR_ERR_X509_OVERFLOW</strong>
|
||||
Value is off-limits (overflow).
|
||||
|
||||
<strong>47 BR_ERR_X509_BAD_DN</strong>
|
||||
Invalid distinguished name.
|
||||
|
||||
<strong>48 BR_ERR_X509_BAD_TIME</strong>
|
||||
Invalid date/time representation in certificate.
|
||||
|
||||
<strong>49 BR_ERR_X509_UNSUPPORTED</strong>
|
||||
Certificate contains unsupported features that cannot be
|
||||
ignored.
|
||||
|
||||
<strong>50 BR_ERR_X509_LIMIT_EXCEEDED</strong>
|
||||
Key or signature size exceeds internal limits.
|
||||
|
||||
<strong>51 BR_ERR_X509_WRONG_KEY_TYPE</strong>
|
||||
Key type does not match that which was expected.
|
||||
|
||||
<strong>52 BR_ERR_X509_BAD_SIGNATURE</strong>
|
||||
Signature is invalid.
|
||||
|
||||
<strong>53 BR_ERR_X509_TIME_UNKNOWN</strong>
|
||||
Validation time is unknown (no time was set).
|
||||
|
||||
<strong>54 BR_ERR_X509_EXPIRED</strong>
|
||||
Certificate is expired or not yet valid.
|
||||
|
||||
<strong>55 BR_ERR_X509_DN_MISMATCH</strong>
|
||||
Issuer/subject DN mismatch in the chain.
|
||||
|
||||
<strong>56 BR_ERR_X509_BAD_SERVER_NAME</strong>
|
||||
Expected server name was not found in the chain.
|
||||
|
||||
<strong>57 BR_ERR_X509_CRITICAL_EXTENSION</strong>
|
||||
Unknown critical extension in certificate.
|
||||
|
||||
<strong>58 BR_ERR_X509_NOT_CA</strong>
|
||||
Not a CA, or path length constraint violation.
|
||||
|
||||
<strong>59 BR_ERR_X509_FORBIDDEN_KEY_USAGE</strong>
|
||||
Key Usage extension prohibits the intended usage.
|
||||
|
||||
<strong>60 BR_ERR_X509_WEAK_PUBLIC_KEY</strong>
|
||||
Public key found in certificate is too small.
|
||||
|
||||
<strong>62 BR_ERR_X509_NOT_TRUSTED</strong>
|
||||
Chain could not be linked to a trust anchor.
|
||||
|
||||
<strong>FATAL ALERTS</strong>
|
||||
When a fatal alert is received from the peer, the error code
|
||||
is 256 + the TLS alert value. When a fatal alert is sent to
|
||||
the peer, the error code is 512 + the TLS alert value.
|
||||
|
||||
Common alert values:
|
||||
0 close_notify
|
||||
10 unexpected_message
|
||||
20 bad_record_mac
|
||||
40 handshake_failure
|
||||
42 bad_certificate
|
||||
43 unsupported_certificate
|
||||
44 certificate_revoked
|
||||
45 certificate_expired
|
||||
46 certificate_unknown
|
||||
47 illegal_parameter
|
||||
48 unknown_ca
|
||||
50 decode_error
|
||||
51 decrypt_error
|
||||
70 protocol_version
|
||||
71 insufficient_security
|
||||
80 internal_error
|
||||
86 unrecognized_name
|
||||
112 no_application_protocol
|
||||
|
||||
For example, error 296 means a handshake_failure alert was
|
||||
received (256 + 40 = 296).
|
||||
|
||||
<strong>SEE ALSO</strong>
|
||||
fetch(1), syscalls(2)</code></pre>
|
||||
|
||||
<hr>
|
||||
|
||||
<div class="center">
|
||||
<a href="../index.html">Back to Documentation Index</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user