Compare commits

...
31 Commits
Author SHA1 Message Date
daniel fd397ac41d fix: reject opening directories as files in ext2 and fat32, add mbrtowc to libc 2026-08-30 09:50:11 +02:00
daniel fdbb233cd3 fix: fix login wallpaper startup delay 2026-08-30 08:05:31 +02:00
daniel 21828990c4 cleanup: remove redundant shutdown.elf, update shell help/completions 2026-08-29 19:10:35 +02:00
daniel 70fa2016d1 fix: make Files lists scroll with arrow keys 2026-08-29 19:07:02 +02:00
daniel f69f08acbb fix: inherit redirected console in SYS_SPAWN_CAPS 2026-08-29 18:47:42 +02:00
daniel f0bea7736d feat: add C++ syntax highlighting to edit and Text Editor 2026-08-29 18:36:56 +02:00
daniel 3e596f2bad feat: add Pure Black Terminal theme & make it default for Console session 2026-08-29 18:32:33 +02:00
daniel 615ca7308a feat: add settings panel to Terminal 2026-08-29 18:24:07 +02:00
daniel e7646bbbdb feat: implement kernel capability model 2026-08-29 16:53:04 +02:00
daniel 9051b8a16e cleanup: update syscall man page 2026-08-29 12:38:25 +02:00
daniel 5cd5c2e6be fix: move RTL-SDR to userspace 2026-08-29 10:02:32 +02:00
daniel b1f1cfe32b feat: implement montauk::service_log & add support to system services 2026-08-28 11:56:29 +02:00
daniel 0d23db8a0e cleanup: change _zos to _mtk 2026-08-28 09:15:06 +02:00
daniel dac12c22cd feat: update isatty libc function to use SYS_TERMINAL_ATTACHED syscall 2026-08-28 09:14:02 +02:00
daniel c8264b92a9 feat: add is_terminal_attached syscall 2026-08-27 18:11:21 +02:00
daniel 8afadb44cb feat: expand and fix issues in SVG renderer 2026-08-26 12:04:38 +02:00
daniel ffc9756e67 feat: identify userspace logs by user and image 2026-08-22 18:48:38 +02:00
daniel 8a74b771e1 fix: update template, prevent double-spaced persisted kernel logs 2026-08-22 12:47:13 +02:00
daniel 903218168d split montaukos.org website out of OS source tree 2026-08-22 07:36:47 +02:00
daniel cc90b34fdb cleanup: update stale comments 2026-08-15 21:18:03 +02:00
daniel 788b662d44 fix: reduce idle CPU wakeups and deferred-work polling 2026-08-15 16:50:36 +02:00
daniel f7677ac3f1 fix: fix timezone formatting bug 2026-08-15 16:36:09 +02:00
daniel af7d096969 fix: fix ACPI hardware-ID endianness bug 2026-08-15 16:34:57 +02:00
daniel c917af0629 feat: login - write system log to disk (0:/os/logs/montaukos) during shutdown, limit disk flush stage to non-ramdisk storage devices 2026-08-15 11:21:14 +02:00
daniel f6be9e2563 cleanup: remove dead code in Api/Process.hpp, remove redundant logtest program 2026-08-14 19:14:49 +02:00
daniel 39a56153b4 fix: consistency in Files app volume labeling 2026-08-14 18:38:57 +02:00
daniel 724029cacb feat: add userspace system logging, rename klog to syslog, rename relevant syscalls and API functions 2026-08-14 18:37:27 +02:00
daniel 8e45d43116 refactor: kernel entry point - add architecture preprocessor checks and move module to Boot 2026-08-14 11:28:32 +02:00
daniel 0f81276efc feat: add inherited sessions and desktop session teardown 2026-08-14 10:33:20 +02:00
daniel f8ded3c30e readme - update readme image 2026-08-14 09:12:54 +02:00
daniel e3ef1b81ad fix: fix text overlap bug in file dialogs and remove unnecessary hint in Keyboard applet 2026-08-13 18:10:00 +02:00
341 changed files with 8546 additions and 14894 deletions
+1 -1
View File
@@ -1,7 +1,7 @@
# The Montauk Operating System # 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. 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.
![MontaukOS screenshot](images/MontaukOS-2.png) ![MontaukOS screenshot](https://montaukos.org/images/montaukos-demo.jpg)
## Features ## Features
* Modern preemptive multitasking kernel * Modern preemptive multitasking kernel
+1 -1
View File
@@ -85,7 +85,7 @@ usable diagnostics once the desktop is up**, pending the unified syslog.
Worth understanding, because it catches out anything written as a daemon: Worth understanding, because it catches out anything written as a daemon:
`montauk::print` is `SYS_PRINT`, which writes the kernel *terminal*, not the `montauk::print` is `SYS_PRINT`, which writes the kernel *terminal*, not the
kernel *log*. Only in-kernel `KernelLogStream` writes raise `g_kernelLogDepth`, kernel *log*. Only in-kernel `KernelLogStream` writes raise `g_kernelLogDepth`,
and only those append to the ring buffer `SYS_KLOG` reads -- so daemon output 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 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 `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 discarded outright from then on. `init` spawns services with `spawn` rather than
+15
View File
@@ -22,7 +22,22 @@ namespace Hal {
// ============================================================================ // ============================================================================
// ACPI encodes PNP IDs as compressed 32-bit EISAIDs. // 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) { 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: // EISA ID encoding:
// Bits 31-16: 3 compressed letters (5 bits each, '@' based) // Bits 31-16: 3 compressed letters (5 bits each, '@' based)
// Bits 15-0: 4 hex digits (product number) // Bits 15-0: 4 hex digits (product number)
+1 -1
View File
@@ -12,4 +12,4 @@
#pragma once #pragma once
#define MONTAUK_BUILD_NUMBER 116 #define MONTAUK_BUILD_NUMBER 184
+17 -3
View File
@@ -17,8 +17,14 @@
#include <Ipc/Ipc.hpp> #include <Ipc/Ipc.hpp>
#include <Timekeeping/Time.hpp> #include <Timekeeping/Time.hpp>
#include "Path.hpp" #include "Path.hpp"
#include <Fs/ProtectedPaths.hpp>
namespace montauk::abi { 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) { static int Sys_Open(const char* path) {
char resolved[256]; char resolved[256];
if (!ResolveProcessPath(path, resolved, sizeof(resolved))) return -1; if (!ResolveProcessPath(path, resolved, sizeof(resolved))) return -1;
@@ -62,9 +68,9 @@ namespace montauk::abi {
if (proc == nullptr) return -1; if (proc == nullptr) return -1;
// Use a rotating ring of scratch pages below the heap instead of // Use a rotating ring of scratch pages below the heap instead of
// bumping heapNext on every call. This keeps repeated directory scans // extending the heap high-water mark on every call. This keeps repeated
// from leaking user heap space while still allowing nested callers to // directory scans from consuming user heap address space while still
// hold multiple readdir results at once. // allowing nested callers to hold multiple readdir results at once.
uint32_t slot = proc->readdirCursor % Sched::UserReadDirSlots; uint32_t slot = proc->readdirCursor % Sched::UserReadDirSlots;
proc->readdirCursor = (slot + 1) % Sched::UserReadDirSlots; proc->readdirCursor = (slot + 1) % Sched::UserReadDirSlots;
@@ -108,12 +114,14 @@ namespace montauk::abi {
static int Sys_FCreate(const char* path) { static int Sys_FCreate(const char* path) {
char resolved[256]; char resolved[256];
if (!ResolveProcessPath(path, resolved, sizeof(resolved))) return -1; if (!ResolveProcessPath(path, resolved, sizeof(resolved))) return -1;
if (!CanModifyFilePath(resolved)) return SYS_ERR_PERMISSION;
return Ipc::CreateFileHandle(resolved); return Ipc::CreateFileHandle(resolved);
} }
static int Sys_FDelete(const char* path) { static int Sys_FDelete(const char* path) {
char resolved[256]; char resolved[256];
if (!ResolveProcessPath(path, resolved, sizeof(resolved))) return -1; if (!ResolveProcessPath(path, resolved, sizeof(resolved))) return -1;
if (!CanModifyFilePath(resolved)) return SYS_ERR_PERMISSION;
return Fs::Vfs::VfsDelete(resolved); return Fs::Vfs::VfsDelete(resolved);
} }
@@ -138,6 +146,9 @@ namespace montauk::abi {
bool useCurrent) { bool useCurrent) {
char resolved[256]; char resolved[256];
if (!ResolveProcessPath(path, resolved, sizeof(resolved))) return -1; if (!ResolveProcessPath(path, resolved, sizeof(resolved))) return -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) { if (useCurrent) {
int64_t now = Timekeeping::GetUnixTimestamp(); int64_t now = Timekeeping::GetUnixTimestamp();
atime = now; atime = now;
@@ -149,6 +160,7 @@ namespace montauk::abi {
static int Sys_FMkdir(const char* path) { static int Sys_FMkdir(const char* path) {
char resolved[256]; char resolved[256];
if (!ResolveProcessPath(path, resolved, sizeof(resolved))) return -1; if (!ResolveProcessPath(path, resolved, sizeof(resolved))) return -1;
if (!CanModifyFilePath(resolved)) return SYS_ERR_PERMISSION;
return Fs::Vfs::VfsMkdir(resolved); return Fs::Vfs::VfsMkdir(resolved);
} }
@@ -157,6 +169,8 @@ namespace montauk::abi {
char resolvedNew[256]; char resolvedNew[256];
if (!ResolveProcessPath(oldPath, resolvedOld, sizeof(resolvedOld))) return -1; if (!ResolveProcessPath(oldPath, resolvedOld, sizeof(resolvedOld))) return -1;
if (!ResolveProcessPath(newPath, resolvedNew, sizeof(resolvedNew))) 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); return Fs::Vfs::VfsRename(resolvedOld, resolvedNew);
} }
+43 -4
View File
@@ -5,6 +5,7 @@
*/ */
#pragma once #pragma once
#include <Memory/UserRange.hpp>
#include <cstdint> #include <cstdint>
#include <Sched/Scheduler.hpp> #include <Sched/Scheduler.hpp>
#include <Memory/Paging.hpp> #include <Memory/Paging.hpp>
@@ -44,7 +45,12 @@ namespace montauk::abi {
static constexpr uint64_t VmProtWrite = 2; static constexpr uint64_t VmProtWrite = 2;
static constexpr uint64_t VmProtExec = 4; static constexpr uint64_t VmProtExec = 4;
inline uint64_t Sys_MapAnonymous(uint64_t size, uint64_t prot) { // 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(); auto* proc = Sched::GetCurrentProcessPtr();
if (proc == nullptr) return 0; if (proc == nullptr) return 0;
int slot = GetCurrentSlot(); int slot = GetCurrentSlot();
@@ -82,6 +88,33 @@ namespace montauk::abi {
g_heapAllocs[slot] = new HeapAlloc { userVa, numPages, prot, allocationId, g_heapAllocs[slot] = new HeapAlloc { userVa, numPages, prot, allocationId,
g_heapAllocs[slot] }; g_heapAllocs[slot] };
// 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(); g_heapLocks[slot].Release();
return userVa; return userVa;
} }
@@ -90,6 +123,12 @@ namespace montauk::abi {
return Sys_MapAnonymous(size, VmProtRead | VmProtWrite); 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. // Reset heap allocation tracking for a process slot.
// The actual physical pages are freed by Paging::FreeUserHalf() during process cleanup. // The actual physical pages are freed by Paging::FreeUserHalf() during process cleanup.
inline void CleanupHeapForSlot(int slot, uint64_t /*pml4Phys*/) { inline void CleanupHeapForSlot(int slot, uint64_t /*pml4Phys*/) {
@@ -155,7 +194,7 @@ namespace montauk::abi {
// user TLB entry can otherwise corrupt the frame's next owner. // user TLB entry can otherwise corrupt the frame's next owner.
while (released != nullptr) { while (released != nullptr) {
HeapAlloc* next = released->next; HeapAlloc* next = released->next;
Ipc::UnmapAndFreeUserRange(proc->pml4Phys, released->va, Memory::UnmapAndFreeUserRange(proc->pml4Phys, released->va,
released->numPages); released->numPages);
Sched::ReleaseUserHeapRange(slot, released->va, Sched::ReleaseUserHeapRange(slot, released->va,
released->numPages * 0x1000ULL); released->numPages * 0x1000ULL);
@@ -218,7 +257,7 @@ namespace montauk::abi {
resident++; resident++;
g_heapLocks[slot].Release(); g_heapLocks[slot].Release();
Ipc::UnmapAndFreeUserRange(proc->pml4Phys, addr, pages); Memory::UnmapAndFreeUserRange(proc->pml4Phys, addr, pages);
Sched::ReleaseUserHeapRange(slot, addr, size); Sched::ReleaseUserHeapRange(slot, addr, size);
g_heapLocks[slot].Acquire(); g_heapLocks[slot].Acquire();
Sched::g_allocatedPages[slot] -= resident; Sched::g_allocatedPages[slot] -= resident;
@@ -308,7 +347,7 @@ namespace montauk::abi {
} }
} }
g_heapLocks[slot].Release(); g_heapLocks[slot].Release();
Ipc::ShootdownUserRange(proc->pml4Phys, addr, (uint32_t)pages); Memory::ShootdownUserRange(proc->pml4Phys, addr, (uint32_t)pages);
return 0; return 0;
} }
+1 -1
View File
@@ -23,7 +23,7 @@ namespace montauk::abi {
for (int i = 0; ver[i]; i++) outInfo->osVersion[i] = ver[i]; for (int i = 0; ver[i]; i++) outInfo->osVersion[i] = ver[i];
outInfo->osVersion[5] = '\0'; outInfo->osVersion[5] = '\0';
outInfo->apiVersion = 10; outInfo->apiVersion = 11;
outInfo->maxProcesses = Sched::MaxProcesses; outInfo->maxProcesses = Sched::MaxProcesses;
outInfo->buildNumber = MONTAUK_BUILD_NUMBER; outInfo->buildNumber = MONTAUK_BUILD_NUMBER;
} }
+33 -6
View File
@@ -15,12 +15,15 @@ namespace montauk::abi {
static constexpr uint32_t RedirOutputStreamCapacity = 64 * 1024; 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]; char resolved[256];
if (!ResolveProcessPath(path, resolved, sizeof(resolved))) return -1; if (!ResolveProcessPath(path, resolved, sizeof(resolved))) return -1;
int parentSlot = Ipc::CurrentSlot(); 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; if (childPid < 0) return -1;
auto* child = Sched::GetProcessByPid(childPid); auto* child = Sched::GetProcessByPid(childPid);
@@ -83,28 +86,50 @@ namespace montauk::abi {
return childPid; 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, &copy);
}
static int Sys_ChildIoRead(int childPid, char* buf, int maxLen) { static int Sys_ChildIoRead(int childPid, char* buf, int maxLen) {
auto* child = Sched::GetProcessByPid(childPid); auto* child = Sched::GetProcessByPid(childPid);
if (child == nullptr || child->parentPid != Sched::GetCurrentPid())
return SYS_ERR_PERMISSION;
Ipc::HandleSnapshot snapshot; Ipc::HandleSnapshot snapshot;
Ipc::Stream* stream = GetRedirOutStream(child, 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); return Ipc::StreamRead(stream, (uint8_t*)buf, maxLen, true);
} }
static int Sys_ChildIoWrite(int childPid, const char* data, int len) { static int Sys_ChildIoWrite(int childPid, const char* data, int len) {
auto* child = Sched::GetProcessByPid(childPid); auto* child = Sched::GetProcessByPid(childPid);
if (child == nullptr || child->parentPid != Sched::GetCurrentPid())
return SYS_ERR_PERMISSION;
Ipc::HandleSnapshot snapshot; Ipc::HandleSnapshot snapshot;
Ipc::Stream* stream = GetRedirInStream(child, 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); return WriteAllToStream(stream, (const uint8_t*)data, len);
} }
static int Sys_ChildIoWriteKey(int childPid, const KeyEvent* key) { static int Sys_ChildIoWriteKey(int childPid, const KeyEvent* key) {
if (key == nullptr) return -1; if (key == nullptr) return -1;
auto* child = Sched::GetProcessByPid(childPid); auto* child = Sched::GetProcessByPid(childPid);
if (child == nullptr || child->parentPid != Sched::GetCurrentPid())
return SYS_ERR_PERMISSION;
Ipc::HandleSnapshot snapshot; Ipc::HandleSnapshot snapshot;
Ipc::Mailbox* mailbox = GetRedirKeyMailbox(child, snapshot); Ipc::Mailbox* mailbox = GetRedirKeyMailbox(child, snapshot);
if (child == nullptr || !child->redirected || mailbox == nullptr) return -1; if (!child->redirected || mailbox == nullptr) return -1;
for (;;) { for (;;) {
uint64_t observedWake = Sched::ObserveObjectWake(mailbox); uint64_t observedWake = Sched::ObserveObjectWake(mailbox);
@@ -120,7 +145,9 @@ namespace montauk::abi {
static int Sys_ChildIoSetTermsz(int childPid, int cols, int rows) { static int Sys_ChildIoSetTermsz(int childPid, int cols, int rows) {
auto* child = Sched::GetProcessByPid(childPid); auto* child = Sched::GetProcessByPid(childPid);
if (child == nullptr || !child->redirected) return -1; if (child == nullptr || child->parentPid != Sched::GetCurrentPid())
return SYS_ERR_PERMISSION;
if (!child->redirected) return -1;
child->termCols = cols; child->termCols = cols;
child->termRows = rows; child->termRows = rows;
return 0; return 0;
+3 -2
View File
@@ -4,6 +4,7 @@
* Copyright (c) 2026 Daniel Hammer * Copyright (c) 2026 Daniel Hammer
*/ */
#include <Memory/UserRange.hpp>
#include <cstdint> #include <cstdint>
#include <Sched/Scheduler.hpp> #include <Sched/Scheduler.hpp>
#include <Sched/ElfLoader.hpp> #include <Sched/ElfLoader.hpp>
@@ -133,7 +134,7 @@ namespace montauk::abi {
auto* proc = Sched::GetCurrentProcessPtr(); auto* proc = Sched::GetCurrentProcessPtr();
if (proc != nullptr) { if (proc != nullptr) {
Ipc::UnmapAndFreeUserRange(proc->pml4Phys, libBase, Memory::UnmapAndFreeUserRange(proc->pml4Phys, libBase,
(libEnd - libBase) / 0x1000ULL); (libEnd - libBase) / 0x1000ULL);
} }
@@ -202,7 +203,7 @@ namespace montauk::abi {
uint64_t libBase = GetLibSlotBase(i); uint64_t libBase = GetLibSlotBase(i);
uint64_t libEnd = libBase + Sched::LIB_MAX_SIZE; uint64_t libEnd = libBase + Sched::LIB_MAX_SIZE;
Ipc::UnmapAndFreeUserRange(proc->pml4Phys, libBase, Memory::UnmapAndFreeUserRange(proc->pml4Phys, libBase,
(libEnd - libBase) / 0x1000ULL); (libEnd - libBase) / 0x1000ULL);
g_libTable[slot][i].inUse = false; g_libTable[slot][i].inUse = false;
+5
View File
@@ -31,6 +31,11 @@ namespace montauk::abi {
// action; any other value records it as the pending action. Returns the // action; any other value records it as the pending action. Returns the
// pending action for queries, or 0 when recording one. // pending action for queries, or 0 when recording one.
static int64_t Sys_PowerRequest(int action) { 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) { if (action == POWER_REQ_QUERY) {
int pending = g_pendingPowerAction; int pending = g_pendingPowerAction;
g_pendingPowerAction = POWER_REQ_QUERY; g_pendingPowerAction = POWER_REQ_QUERY;
+102 -32
View File
@@ -43,6 +43,43 @@ namespace montauk::abi {
return Sched::LookupExitCode(pid); return Sched::LookupExitCode(pid);
} }
// 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, static int Sys_Spawn(const char* path, const char* args,
const char* environment = nullptr, uint32_t environmentLength = 0) { const char* environment = nullptr, uint32_t environmentLength = 0) {
char resolved[256]; char resolved[256];
@@ -55,34 +92,51 @@ namespace montauk::abi {
environment, environmentLength); environment, environmentLength);
if (childPid < 0) return childPid; if (childPid < 0) return childPid;
if (inheritRedirection) { if (inheritRedirection)
auto* child = Sched::GetProcessByPid(childPid); return InheritRedirection(childPid, parent, parentSlot);
int childSlot = Ipc::SlotForPid(childPid);
if (child == nullptr || childSlot < 0 || parentSlot < 0) {
Sched::KillProcess(childPid);
return -1;
}
child->ioOutHandle = DuplicateHandleBetweenSlots(parentSlot, parent->ioOutHandle, childSlot); return childPid;
child->ioInHandle = DuplicateHandleBetweenSlots(parentSlot, parent->ioInHandle, childSlot); }
child->ioKeyHandle = DuplicateHandleBetweenSlots(parentSlot, parent->ioKeyHandle, childSlot);
if (child->ioOutHandle < 0 || child->ioInHandle < 0 || child->ioKeyHandle < 0 || static int Sys_SpawnCaps(const char* path, const char* args,
!ConfigureRedirWaitsetForSlot(childSlot, child)) { const char* user, const SpawnCapabilities* requested) {
Sched::KillProcess(childPid); auto* parent = Sched::GetCurrentProcessPtr();
return -1; if (parent == nullptr || requested == nullptr) return -1;
}
child->redirected = true; // Snapshot all security-sensitive userspace inputs before evaluating
child->parentPid = parent->pid; // them. This prevents another thread from changing a mask or owner
child->termCols = parent->termCols; // name between validation and process creation.
child->termRows = parent->termRows; SpawnCapabilities copy = *requested;
if (Sched::StartProcess(childPid) < 0) { char childUser[32];
Sched::KillProcess(childPid); const char* userOverride = nullptr;
return -1; 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, &copy, userOverride);
if (childPid < 0) return childPid;
if (inheritRedirection)
return InheritRedirection(childPid, parent, parentSlot);
return childPid; return childPid;
} }
@@ -150,15 +204,40 @@ namespace montauk::abi {
} }
buf[count].heapUsed = Sched::g_allocatedPages[i] * 0x1000; buf[count].heapUsed = Sched::g_allocatedPages[i] * 0x1000;
buf[count].cpuTimeMs = proc->cpuTimeMs; buf[count].cpuTimeMs = proc->cpuTimeMs;
buf[count].permittedCaps = proc->permittedCaps;
buf[count].effectiveCaps = proc->effectiveCaps;
buf[count].delegableCaps = proc->delegableCaps;
count++; count++;
} }
return count; return count;
} }
static int Sys_Kill(int pid) { 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); 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) { static int Sys_SetUser(int pid, const char* name) {
if (name == nullptr) return -1; if (name == nullptr) return -1;
auto* target = Sched::GetProcessByPid(pid); auto* target = Sched::GetProcessByPid(pid);
@@ -222,15 +301,6 @@ namespace montauk::abi {
char resolved[256]; char resolved[256];
if (!ResolveProcessPath(path, resolved, sizeof(resolved))) return -1; 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 // ReadDir doubles as the directory-existence probe: it fails
// for nonexistent paths and for regular files (directories are // for nonexistent paths and for regular files (directories are
// not openable as files anymore, so the old open-based check // not openable as files anymore, so the old open-based check
-55
View File
@@ -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);
}
}
+122 -24
View File
@@ -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 "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 "Audio.hpp" // SYS_AUDIOOPEN, SYS_AUDIOCLOSE, SYS_AUDIOWRITE, SYS_AUDIOCTL
#include "BluetoothSyscall.hpp" // SYS_BTSCAN, SYS_BTCONNECT, SYS_BTDISCONNECT, SYS_BTLIST, SYS_BTINFO #include "BluetoothSyscall.hpp" // SYS_BTSCAN, SYS_BTCONNECT, SYS_BTDISCONNECT, SYS_BTLIST, SYS_BTINFO
#include "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 "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 "IpcSyscall.hpp" // SYS_DUPHANDLE, SYS_WAIT_HANDLE, SYS_STREAM_CREATE, SYS_STREAM_READ, SYS_STREAM_WRITE, SYS_MAILBOX_CREATE, SYS_MAILBOX_SEND, SYS_MAILBOX_RECV, SYS_WAITSET_CREATE, SYS_WAITSET_ADD, SYS_WAITSET_REMOVE, SYS_WAITSET_WAIT, SYS_PROC_OPEN, SYS_SURFACE_CREATE, SYS_SURFACE_MAP, SYS_SURFACE_RESIZE
#include "LibSyscall.hpp" // SYS_LOAD_LIB, SYS_UNLOAD_LIB, SYS_DLSYM #include "LibSyscall.hpp" // SYS_LOAD_LIB, SYS_UNLOAD_LIB, SYS_DLSYM
@@ -53,6 +53,7 @@ namespace montauk::abi {
static constexpr uint64_t kMaxWindowTitleBytes = 256; static constexpr uint64_t kMaxWindowTitleBytes = 256;
static constexpr uint64_t kMaxHostnameBytes = 256; static constexpr uint64_t kMaxHostnameBytes = 256;
static constexpr uint64_t kMaxUserNameBytes = 32; static constexpr uint64_t kMaxUserNameBytes = 32;
static constexpr uint64_t kMaxUserspaceLogEntryBytes = 1024;
// ---- Dispatch ---- // ---- Dispatch ----
@@ -115,6 +116,8 @@ namespace montauk::abi {
(int)frame->arg4); (int)frame->arg4);
case SYS_ALLOC: case SYS_ALLOC:
return (int64_t)Sys_Alloc(frame->arg1); return (int64_t)Sys_Alloc(frame->arg1);
case SYS_ALLOC_EAGER:
return (int64_t)Sys_AllocEager(frame->arg1);
case SYS_FREE: case SYS_FREE:
Sys_Free(frame->arg1); Sys_Free(frame->arg1);
return 0; return 0;
@@ -149,6 +152,16 @@ namespace montauk::abi {
if (frame->arg2 != 0 && !UserMemory::String(frame->arg2, kMaxArgsBytes)) return -1; if (frame->arg2 != 0 && !UserMemory::String(frame->arg2, kMaxArgsBytes)) return -1;
return (int64_t)Sys_Spawn((const char*)frame->arg1, return (int64_t)Sys_Spawn((const char*)frame->arg1,
UserMemory::IsUserPtr(frame->arg2) ? (const char*)frame->arg2 : nullptr); UserMemory::IsUserPtr(frame->arg2) ? (const char*)frame->arg2 : nullptr);
case SYS_SPAWN_CAPS:
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: case SYS_SPAWN_ENV:
if (!UserMemory::String(frame->arg1, kMaxPathBytes)) return -1; if (!UserMemory::String(frame->arg1, kMaxPathBytes)) return -1;
if (frame->arg2 != 0 && !UserMemory::String(frame->arg2, kMaxArgsBytes)) return -1; if (frame->arg2 != 0 && !UserMemory::String(frame->arg2, kMaxArgsBytes)) return -1;
@@ -187,8 +200,11 @@ namespace montauk::abi {
(uint64_t)frame->arg2 * sizeof(DisplayModeInfo), true)) return -1; (uint64_t)frame->arg2 * sizeof(DisplayModeInfo), true)) return -1;
return Sys_DisplayModes((DisplayModeInfo*)frame->arg1, (int)frame->arg2); return Sys_DisplayModes((DisplayModeInfo*)frame->arg1, (int)frame->arg2);
case SYS_DISPLAYSETMODE: case SYS_DISPLAYSETMODE:
if (!Sched::HasCapability(CAP_DISPLAY_ADMIN)) return SYS_ERR_PERMISSION;
return Sys_DisplaySetMode((int)frame->arg1); return Sys_DisplaySetMode((int)frame->arg1);
case SYS_DISPLAYBRIGHTNESS: case SYS_DISPLAYBRIGHTNESS:
if ((int64_t)frame->arg1 >= 0 &&
!Sched::HasCapability(CAP_DISPLAY_ADMIN)) return SYS_ERR_PERMISSION;
return Sys_DisplayBrightness((int)frame->arg1); return Sys_DisplayBrightness((int)frame->arg1);
case SYS_GETEXECPATH: case SYS_GETEXECPATH:
if (!UserMemory::Range(frame->arg2 ? frame->arg1 : frame->arg1, frame->arg2, true)) return -1; if (!UserMemory::Range(frame->arg2 ? frame->arg1 : frame->arg1, frame->arg2, true)) return -1;
@@ -209,18 +225,30 @@ namespace montauk::abi {
if (!UserMemory::Range(frame->arg1, frame->arg2, true)) return -1; if (!UserMemory::Range(frame->arg1, frame->arg2, true)) return -1;
return (int64_t)Sys_GetArgs((char*)frame->arg1, frame->arg2); return (int64_t)Sys_GetArgs((char*)frame->arg1, frame->arg2);
case SYS_RESET: case SYS_RESET:
if (!Sched::HasCapability(CAP_POWER_CONTROL)) return SYS_ERR_PERMISSION;
Sys_Reset(); Sys_Reset();
return 0; return 0;
case SYS_SHUTDOWN: case SYS_SHUTDOWN:
if (!Sched::HasCapability(CAP_POWER_CONTROL)) return SYS_ERR_PERMISSION;
Sys_Shutdown(); Sys_Shutdown();
return 0; return 0;
case SYS_POWER_REQUEST: 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); return Sys_PowerRequest((int)frame->arg1);
case SYS_GETTIME: case SYS_GETTIME:
if (!UserMemory::Writable<DateTime>(frame->arg1)) return -1; if (!UserMemory::Writable<DateTime>(frame->arg1)) return -1;
Sys_GetTime((DateTime*)frame->arg1); Sys_GetTime((DateTime*)frame->arg1);
return 0; return 0;
case SYS_SETUNIXTIME: case SYS_SETUNIXTIME:
if (!Sched::HasCapability(CAP_SET_TIME)) return SYS_ERR_PERMISSION;
return Sys_SetUnixTime((int64_t)frame->arg1); return Sys_SetUnixTime((int64_t)frame->arg1);
case SYS_SOCKET: case SYS_SOCKET:
return (int64_t)Sys_Socket((int)frame->arg1); return (int64_t)Sys_Socket((int)frame->arg1);
@@ -246,6 +274,7 @@ namespace montauk::abi {
Sys_GetNetCfg((NetCfg*)frame->arg1); Sys_GetNetCfg((NetCfg*)frame->arg1);
return 0; return 0;
case SYS_SETNETCFG: case SYS_SETNETCFG:
if (!Sched::HasCapability(CAP_NETWORK_ADMIN)) return SYS_ERR_PERMISSION;
if (!UserMemory::Readable<NetCfg>(frame->arg1)) return -1; if (!UserMemory::Readable<NetCfg>(frame->arg1)) return -1;
return (int64_t)Sys_SetNetCfg((const NetCfg*)frame->arg1); return (int64_t)Sys_SetNetCfg((const NetCfg*)frame->arg1);
case SYS_NETSTATUS: case SYS_NETSTATUS:
@@ -300,9 +329,10 @@ namespace montauk::abi {
case SYS_GETRANDOM: case SYS_GETRANDOM:
if (!UserMemory::Range(frame->arg1, frame->arg2, true)) return -1; if (!UserMemory::Range(frame->arg1, frame->arg2, true)) return -1;
return Sys_GetRandom((uint8_t*)frame->arg1, frame->arg2); 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; 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: case SYS_MOUSESTATE:
if (!UserMemory::Writable<MouseState>(frame->arg1)) return -1; if (!UserMemory::Writable<MouseState>(frame->arg1)) return -1;
Sys_MouseState((MouseState*)frame->arg1); Sys_MouseState((MouseState*)frame->arg1);
@@ -315,6 +345,14 @@ namespace montauk::abi {
if (frame->arg2 != 0 && !UserMemory::String(frame->arg2, kMaxArgsBytes)) return -1; if (frame->arg2 != 0 && !UserMemory::String(frame->arg2, kMaxArgsBytes)) return -1;
return (int64_t)Sys_SpawnRedir((const char*)frame->arg1, return (int64_t)Sys_SpawnRedir((const char*)frame->arg1,
UserMemory::IsUserPtr(frame->arg2) ? (const char*)frame->arg2 : nullptr); UserMemory::IsUserPtr(frame->arg2) ? (const char*)frame->arg2 : nullptr);
case SYS_SPAWN_REDIR_CAPS:
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: case SYS_CHILDIO_READ:
if ((int64_t)frame->arg3 < 0) return -1; if ((int64_t)frame->arg3 < 0) return -1;
if (!UserMemory::Range(frame->arg2, (uint64_t)frame->arg3, true)) return -1; if (!UserMemory::Range(frame->arg2, (uint64_t)frame->arg3, true)) return -1;
@@ -359,6 +397,11 @@ namespace montauk::abi {
return (int64_t)Sys_ProcList((ProcInfo*)frame->arg1, (int)frame->arg2); return (int64_t)Sys_ProcList((ProcInfo*)frame->arg1, (int)frame->arg2);
case SYS_KILL: case SYS_KILL:
return (int64_t)Sys_Kill((int)frame->arg1); return (int64_t)Sys_Kill((int)frame->arg1);
case SYS_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: case SYS_DEVLIST:
if ((int64_t)frame->arg2 < 0) return -1; if ((int64_t)frame->arg2 < 0) return -1;
if (!UserMemory::Range(frame->arg1, (uint64_t)frame->arg2 * sizeof(DevInfo), true)) return -1; if (!UserMemory::Range(frame->arg1, (uint64_t)frame->arg2 * sizeof(DevInfo), true)) return -1;
@@ -383,21 +426,28 @@ namespace montauk::abi {
if (!UserMemory::Range(frame->arg1, (uint64_t)frame->arg2 * sizeof(PartInfo), true)) return -1; if (!UserMemory::Range(frame->arg1, (uint64_t)frame->arg2 * sizeof(PartInfo), true)) return -1;
return (int64_t)Sys_PartList((PartInfo*)frame->arg1, (int)frame->arg2); return (int64_t)Sys_PartList((PartInfo*)frame->arg1, (int)frame->arg2);
case SYS_DISKREAD: case SYS_DISKREAD:
if (!Sched::HasCapability(CAP_RAW_STORAGE)) return SYS_ERR_PERMISSION;
return (int64_t)Sys_DiskRead((int)frame->arg1, frame->arg2, return (int64_t)Sys_DiskRead((int)frame->arg1, frame->arg2,
(uint32_t)frame->arg3, (void*)frame->arg4); (uint32_t)frame->arg3, (void*)frame->arg4);
case SYS_DISKWRITE: case SYS_DISKWRITE:
if (!Sched::HasCapability(CAP_RAW_STORAGE)) return SYS_ERR_PERMISSION;
return (int64_t)Sys_DiskWrite((int)frame->arg1, frame->arg2, return (int64_t)Sys_DiskWrite((int)frame->arg1, frame->arg2,
(uint32_t)frame->arg3, (const void*)frame->arg4); (uint32_t)frame->arg3, (const void*)frame->arg4);
case SYS_GPTINIT: case SYS_GPTINIT:
if (!Sched::HasCapability(CAP_STORAGE_ADMIN)) return SYS_ERR_PERMISSION;
return (int64_t)Sys_GptInit((int)frame->arg1); return (int64_t)Sys_GptInit((int)frame->arg1);
case SYS_GPTADD: case SYS_GPTADD:
if (!Sched::HasCapability(CAP_STORAGE_ADMIN)) return SYS_ERR_PERMISSION;
if (!UserMemory::Readable<GptAddParams>(frame->arg1)) return -1; if (!UserMemory::Readable<GptAddParams>(frame->arg1)) return -1;
return (int64_t)Sys_GptAdd((const GptAddParams*)frame->arg1); return (int64_t)Sys_GptAdd((const GptAddParams*)frame->arg1);
case SYS_FSMOUNT: case SYS_FSMOUNT:
if (!Sched::HasCapability(CAP_STORAGE_ADMIN)) return SYS_ERR_PERMISSION;
return (int64_t)Sys_FsMount((int)frame->arg1, (int)frame->arg2); return (int64_t)Sys_FsMount((int)frame->arg1, (int)frame->arg2);
case SYS_FS_SYNC: case SYS_FS_SYNC:
if (!Sched::HasCapability(CAP_STORAGE_ADMIN)) return SYS_ERR_PERMISSION;
return Sys_FsSync(); return Sys_FsSync();
case SYS_FSFORMAT: case SYS_FSFORMAT:
if (!Sched::HasCapability(CAP_STORAGE_ADMIN)) return SYS_ERR_PERMISSION;
if (!UserMemory::Readable<FsFormatParams>(frame->arg1)) return -1; if (!UserMemory::Readable<FsFormatParams>(frame->arg1)) return -1;
return (int64_t)Sys_FsFormat((const FsFormatParams*)frame->arg1); return (int64_t)Sys_FsFormat((const FsFormatParams*)frame->arg1);
case SYS_AUDIOOPEN: case SYS_AUDIOOPEN:
@@ -415,26 +465,42 @@ namespace montauk::abi {
return Sys_AudioList((AudioStreamInfo*)frame->arg1, (int)frame->arg2); return Sys_AudioList((AudioStreamInfo*)frame->arg1, (int)frame->arg2);
case SYS_AUDIOWAIT: case SYS_AUDIOWAIT:
return Sys_AudioWait(frame->arg1, frame->arg2); return Sys_AudioWait(frame->arg1, frame->arg2);
case SYS_SDR_COUNT: case SYS_USB_LIST: {
return Sys_SdrCount(); if ((int64_t)frame->arg2 < 0) return USB_ERR_INVALID;
case SYS_SDR_INFO: uint64_t maxCount = frame->arg2 > 16 ? 16 : frame->arg2;
if (!UserMemory::Writable<SdrDeviceInfo>(frame->arg2)) return -1; if (!UserMemory::Range(frame->arg1,
return Sys_SdrInfo((int)frame->arg1, (SdrDeviceInfo*)frame->arg2); maxCount * sizeof(UsbInterfaceInfo), true)) return USB_ERR_INVALID;
case SYS_SDR_OPEN: return Sys_UsbList((UsbInterfaceInfo*)frame->arg1, (int)maxCount);
return Sys_SdrOpen((int)frame->arg1); }
case SYS_SDR_CLOSE: case SYS_USB_CLAIM:
return Sys_SdrClose((int)frame->arg1); if (!Sched::HasCapability(CAP_DEVICE_ADMIN)) return SYS_ERR_PERMISSION;
case SYS_SDR_START: if (frame->arg1 == 0 || frame->arg1 > 16 || frame->arg2 > 255)
return Sys_SdrStart((int)frame->arg1); return USB_ERR_INVALID;
case SYS_SDR_STOP: return Sys_UsbClaim((uint8_t)frame->arg1, (uint8_t)frame->arg2);
return Sys_SdrStop((int)frame->arg1); case SYS_USB_CLOSE:
case SYS_SDR_READ: return Sys_UsbClose((int)frame->arg1);
if (!UserMemory::Range(frame->arg2, frame->arg3, true)) return -1; case SYS_USB_CONTROL: {
return Sys_SdrRead((int)frame->arg1, (uint8_t*)frame->arg2, (uint32_t)frame->arg3); if (!UserMemory::Readable<UsbControlRequest>(frame->arg2)) return USB_ERR_INVALID;
case SYS_SDR_SETPARAM: UsbControlRequest request = *(const UsbControlRequest*)frame->arg2;
return Sys_SdrSetParam((int)frame->arg1, (int)frame->arg2, frame->arg3); if (frame->arg4 > 4096 || frame->arg4 != request.length) return USB_ERR_INVALID;
case SYS_SDR_GETPARAM: bool deviceToHost = (request.requestType & 0x80) != 0;
return Sys_SdrGetParam((int)frame->arg1, (int)frame->arg2); 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: case SYS_POWERINFO:
if (!UserMemory::Writable<PowerInfo>(frame->arg1)) return -1; if (!UserMemory::Writable<PowerInfo>(frame->arg1)) return -1;
return Sys_PowerInfo((PowerInfo*)frame->arg1); return Sys_PowerInfo((PowerInfo*)frame->arg1);
@@ -449,16 +515,20 @@ namespace montauk::abi {
case SYS_THREAD_SELF: case SYS_THREAD_SELF:
return Sys_ThreadSelf(); return Sys_ThreadSelf();
case SYS_BTSCAN: case SYS_BTSCAN:
if (!Sched::HasCapability(CAP_DEVICE_ADMIN)) return SYS_ERR_PERMISSION;
if ((int64_t)frame->arg2 < 0) return -1; if ((int64_t)frame->arg2 < 0) return -1;
if (!UserMemory::Range(frame->arg1, (uint64_t)frame->arg2 * sizeof(BtScanResult), true)) 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); return Sys_BtScan((BtScanResult*)frame->arg1, (int)frame->arg2, (uint32_t)frame->arg3);
case SYS_BTCONNECT: case SYS_BTCONNECT:
if (!Sched::HasCapability(CAP_DEVICE_ADMIN)) return SYS_ERR_PERMISSION;
if (!UserMemory::Range(frame->arg1, 6, false)) return -1; if (!UserMemory::Range(frame->arg1, 6, false)) return -1;
return Sys_BtConnect((const uint8_t*)frame->arg1); return Sys_BtConnect((const uint8_t*)frame->arg1);
case SYS_BTDISCONNECT: case SYS_BTDISCONNECT:
if (!Sched::HasCapability(CAP_DEVICE_ADMIN)) return SYS_ERR_PERMISSION;
if (!UserMemory::Range(frame->arg1, 6, false)) return -1; if (!UserMemory::Range(frame->arg1, 6, false)) return -1;
return Sys_BtDisconnect((const uint8_t*)frame->arg1); return Sys_BtDisconnect((const uint8_t*)frame->arg1);
case SYS_BTSETADDR: case SYS_BTSETADDR:
if (!Sched::HasCapability(CAP_DEVICE_ADMIN)) return SYS_ERR_PERMISSION;
if (!UserMemory::Range(frame->arg1, 6, false)) return -1; if (!UserMemory::Range(frame->arg1, 6, false)) return -1;
return Sys_BtSetAddr((const uint8_t*)frame->arg1); return Sys_BtSetAddr((const uint8_t*)frame->arg1);
case SYS_BTBONDS: case SYS_BTBONDS:
@@ -466,6 +536,7 @@ namespace montauk::abi {
if (!UserMemory::Range(frame->arg1, (uint64_t)frame->arg2 * sizeof(BtBondInfo), true)) return -1; if (!UserMemory::Range(frame->arg1, (uint64_t)frame->arg2 * sizeof(BtBondInfo), true)) return -1;
return Sys_BtBonds((BtBondInfo*)frame->arg1, (int)frame->arg2); return Sys_BtBonds((BtBondInfo*)frame->arg1, (int)frame->arg2);
case SYS_BTFORGET: case SYS_BTFORGET:
if (!Sched::HasCapability(CAP_DEVICE_ADMIN)) return SYS_ERR_PERMISSION;
if (!UserMemory::Range(frame->arg1, 6, false)) return -1; if (!UserMemory::Range(frame->arg1, 6, false)) return -1;
return Sys_BtForget((const uint8_t*)frame->arg1); return Sys_BtForget((const uint8_t*)frame->arg1);
case SYS_BTLIST: case SYS_BTLIST:
@@ -476,6 +547,7 @@ namespace montauk::abi {
if (!UserMemory::Writable<BtAdapterInfo>(frame->arg1)) return -1; if (!UserMemory::Writable<BtAdapterInfo>(frame->arg1)) return -1;
return Sys_BtInfo((BtAdapterInfo*)frame->arg1); return Sys_BtInfo((BtAdapterInfo*)frame->arg1);
case SYS_WIFI_SCAN: case SYS_WIFI_SCAN:
if (!Sched::HasCapability(CAP_NETWORK_ADMIN)) return SYS_ERR_PERMISSION;
if ((int64_t)frame->arg2 < 0) return -1; if ((int64_t)frame->arg2 < 0) return -1;
if (!UserMemory::Range(frame->arg1, (uint64_t)frame->arg2 * sizeof(WifiNetwork), true)) return -1; if (!UserMemory::Range(frame->arg1, (uint64_t)frame->arg2 * sizeof(WifiNetwork), true)) return -1;
return Sys_WifiScan((WifiNetwork*)frame->arg1, (int)frame->arg2, (uint32_t)frame->arg3); return Sys_WifiScan((WifiNetwork*)frame->arg1, (int)frame->arg2, (uint32_t)frame->arg3);
@@ -483,18 +555,22 @@ namespace montauk::abi {
if (!UserMemory::Writable<WifiInfo>(frame->arg1)) return -1; if (!UserMemory::Writable<WifiInfo>(frame->arg1)) return -1;
return Sys_WifiInfo((WifiInfo*)frame->arg1); return Sys_WifiInfo((WifiInfo*)frame->arg1);
case SYS_WIFI_CONNECT: case SYS_WIFI_CONNECT:
if (!Sched::HasCapability(CAP_NETWORK_ADMIN)) return SYS_ERR_PERMISSION;
if (!UserMemory::String(frame->arg1, 64)) return -1; if (!UserMemory::String(frame->arg1, 64)) return -1;
if (frame->arg2 != 0 && !UserMemory::String(frame->arg2, 128)) return -1; if (frame->arg2 != 0 && !UserMemory::String(frame->arg2, 128)) return -1;
return Sys_WifiConnect((const char*)frame->arg1, (const char*)frame->arg2); return Sys_WifiConnect((const char*)frame->arg1, (const char*)frame->arg2);
case SYS_WIFI_DISCONNECT: case SYS_WIFI_DISCONNECT:
if (!Sched::HasCapability(CAP_NETWORK_ADMIN)) return SYS_ERR_PERMISSION;
return Sys_WifiDisconnect(); return Sys_WifiDisconnect();
case SYS_WIFI_SCAN_START: case SYS_WIFI_SCAN_START:
if (!Sched::HasCapability(CAP_NETWORK_ADMIN)) return SYS_ERR_PERMISSION;
return Sys_WifiScanStart((uint32_t)frame->arg1); return Sys_WifiScanStart((uint32_t)frame->arg1);
case SYS_WIFI_RESULTS: case SYS_WIFI_RESULTS:
if ((int64_t)frame->arg2 < 0) return -1; if ((int64_t)frame->arg2 < 0) return -1;
if (!UserMemory::Range(frame->arg1, (uint64_t)frame->arg2 * sizeof(WifiNetwork), true)) return -1; if (!UserMemory::Range(frame->arg1, (uint64_t)frame->arg2 * sizeof(WifiNetwork), true)) return -1;
return Sys_WifiResults((WifiNetwork*)frame->arg1, (int)frame->arg2); return Sys_WifiResults((WifiNetwork*)frame->arg1, (int)frame->arg2);
case SYS_WIFI_CONNECT_ASYNC: case SYS_WIFI_CONNECT_ASYNC:
if (!Sched::HasCapability(CAP_NETWORK_ADMIN)) return SYS_ERR_PERMISSION;
if (!UserMemory::String(frame->arg1, 64)) return -1; if (!UserMemory::String(frame->arg1, 64)) return -1;
if (frame->arg2 != 0 && !UserMemory::String(frame->arg2, 128)) return -1; if (frame->arg2 != 0 && !UserMemory::String(frame->arg2, 128)) return -1;
return Sys_WifiConnectAsync((const char*)frame->arg1, (const char*)frame->arg2); return Sys_WifiConnectAsync((const char*)frame->arg1, (const char*)frame->arg2);
@@ -503,12 +579,15 @@ namespace montauk::abi {
if (!UserMemory::Range(frame->arg1, (uint64_t)frame->arg2 * sizeof(NetIfInfo), true)) return -1; if (!UserMemory::Range(frame->arg1, (uint64_t)frame->arg2 * sizeof(NetIfInfo), true)) return -1;
return Sys_NetIfs((NetIfInfo*)frame->arg1, (int)frame->arg2); return Sys_NetIfs((NetIfInfo*)frame->arg1, (int)frame->arg2);
case SYS_SUSPEND: case SYS_SUSPEND:
if (!Sched::HasCapability(CAP_SUSPEND)) return SYS_ERR_PERMISSION;
return Sys_Suspend(); return Sys_Suspend();
case SYS_SETTZ: case SYS_SETTZ:
if (!Sched::HasCapability(CAP_SET_TIME)) return SYS_ERR_PERMISSION;
return Sys_SetTZ((int32_t)frame->arg1); return Sys_SetTZ((int32_t)frame->arg1);
case SYS_GETTZ: case SYS_GETTZ:
return Sys_GetTZ(); return Sys_GetTZ();
case SYS_SETUSER: case SYS_SETUSER:
if (!Sched::HasCapability(CAP_USER_ADMIN)) return SYS_ERR_PERMISSION;
if (!UserMemory::String(frame->arg2, kMaxUserNameBytes)) return -1; if (!UserMemory::String(frame->arg2, kMaxUserNameBytes)) return -1;
return Sys_SetUser((int)frame->arg1, (const char*)frame->arg2); return Sys_SetUser((int)frame->arg1, (const char*)frame->arg2);
case SYS_GETUSER: case SYS_GETUSER:
@@ -605,6 +684,24 @@ namespace montauk::abi {
return Sys_ClipboardClear(); return Sys_ClipboardClear();
case SYS_INPUT_WAIT: case SYS_INPUT_WAIT:
return (int64_t)Sys_InputWait(frame->arg1, frame->arg2); return (int64_t)Sys_InputWait(frame->arg1, frame->arg2);
case SYS_LOG_WRITE: {
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: default:
return -1; return -1;
} }
@@ -631,7 +728,8 @@ namespace montauk::abi {
Hal::WriteMSR(Hal::IA32_FMASK, 0x200); Hal::WriteMSR(Hal::IA32_FMASK, 0x200);
Kt::KernelLogStream(Kt::OK, "Syscall") << "SYSCALL/SYSRET initialized (LSTAR=" Kt::KernelLogStream(Kt::OK, "Syscall") << "SYSCALL/SYSRET initialized (LSTAR="
<< kcp::hex << (uint64_t)SyscallEntry << kcp::dec << ", 171 syscall slots)"; << kcp::hex << (uint64_t)SyscallEntry << kcp::dec << ", "
<< (SYS_SPAWN_REDIR_CAPS + 1) << " syscall slots)";
} }
} }
+145 -39
View File
@@ -103,7 +103,7 @@ namespace montauk::abi {
/* Random.hpp */ /* Random.hpp */
static constexpr uint64_t SYS_GETRANDOM = 45; static constexpr uint64_t SYS_GETRANDOM = 45;
static constexpr uint64_t SYS_KLOG = 46; static constexpr uint64_t SYS_LOG = 46;
/* Mouse.hpp */ /* Mouse.hpp */
static constexpr uint64_t SYS_MOUSESTATE = 47; 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_AUDIOWRITE = 82;
static constexpr uint64_t SYS_AUDIOCTL = 83; 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). // Audio control commands (for SYS_AUDIOCTL).
// //
// Commands 0..3 act on the stream named by the handle argument. // Commands 0..3 act on the stream named by the handle argument.
@@ -194,6 +200,8 @@ namespace montauk::abi {
/* Process.hpp */ /* Process.hpp */
static constexpr uint64_t SYS_SETUSER = 92; static constexpr uint64_t SYS_SETUSER = 92;
static constexpr uint64_t SYS_GETUSER = 93; static constexpr uint64_t SYS_GETUSER = 93;
static constexpr uint64_t SYS_SETSESSION = 174;
static constexpr uint64_t SYS_KILLSESSION = 175;
/* Filesystem.hpp */ /* Filesystem.hpp */
static constexpr uint64_t SYS_FRENAME = 94; 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_BTBONDS = 138;
static constexpr uint64_t SYS_BTFORGET = 139; static constexpr uint64_t SYS_BTFORGET = 139;
/* Sdr.hpp -- software-defined radio receive API */ /* Reserved: former SDR API. Kept unavailable to preserve ABI numbering. */
static constexpr uint64_t SYS_SDR_COUNT = 140; // number of receivers static constexpr uint64_t SYS_RESERVED_140 = 140;
static constexpr uint64_t SYS_SDR_INFO = 141; // (index, SdrDeviceInfo*) static constexpr uint64_t SYS_RESERVED_141 = 141;
static constexpr uint64_t SYS_SDR_OPEN = 142; // (index) -> handle static constexpr uint64_t SYS_RESERVED_142 = 142;
static constexpr uint64_t SYS_SDR_CLOSE = 143; // (handle) static constexpr uint64_t SYS_RESERVED_143 = 143;
static constexpr uint64_t SYS_SDR_START = 144; // (handle) begin streaming static constexpr uint64_t SYS_RESERVED_144 = 144;
static constexpr uint64_t SYS_SDR_STOP = 145; // (handle) stop streaming static constexpr uint64_t SYS_RESERVED_145 = 145;
static constexpr uint64_t SYS_SDR_READ = 146; // (handle, buf, len) -> bytes static constexpr uint64_t SYS_RESERVED_146 = 146;
static constexpr uint64_t SYS_SDR_SETPARAM = 147; // (handle, param, value) static constexpr uint64_t SYS_RESERVED_147 = 147;
static constexpr uint64_t SYS_SDR_GETPARAM = 148; // (handle, param) -> value static constexpr uint64_t SYS_RESERVED_148 = 148;
/* Power.hpp -- CPU power/thermal status */ /* Power.hpp -- CPU power/thermal status */
static constexpr uint64_t SYS_POWERINFO = 149; // (PowerInfo*) -> 0, -1 unsupported static constexpr uint64_t SYS_POWERINFO = 149; // (PowerInfo*) -> 0, -1 unsupported
@@ -319,25 +327,103 @@ namespace montauk::abi {
static constexpr uint64_t SYS_SETENVIRON = 172; static constexpr uint64_t SYS_SETENVIRON = 172;
static constexpr uint64_t SYS_SPAWN_ENV = 173; static constexpr uint64_t SYS_SPAWN_ENV = 173;
// Tunable parameters (for SYS_SDR_SETPARAM / SYS_SDR_GETPARAM). /* Generic userspace USB interface access */
static constexpr int SDR_PARAM_FREQ = 0; // center frequency, Hz static constexpr uint64_t SYS_USB_LIST = 178; // (UsbInterfaceInfo*, max) -> count
static constexpr int SDR_PARAM_SAMPLE_RATE = 1; // sample rate, Hz static constexpr uint64_t SYS_USB_CLAIM = 179; // (slot, interface) -> owned handle
static constexpr int SDR_PARAM_GAIN_MODE = 2; // 0 = auto/AGC, 1 = manual static constexpr uint64_t SYS_USB_CLOSE = 180; // (handle)
static constexpr int SDR_PARAM_GAIN = 3; // tuner gain, tenths of dB static constexpr uint64_t SYS_USB_CONTROL = 181; // (handle, UsbControlRequest*, data, len)
static constexpr int SDR_PARAM_FREQ_CORR = 4; // frequency correction, ppm static constexpr uint64_t SYS_USB_BULK_IN_START = 182; // (handle, transferBytes, buffers)
static constexpr int SDR_PARAM_AGC = 5; // demod digital AGC, 0/1 static constexpr uint64_t SYS_USB_BULK_IN_STOP = 183; // (handle)
static constexpr int SDR_PARAM_DIRECT_SAMP = 6; // direct sampling: 0=off,1=I,2=Q 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;
// Sample formats reported in SdrDeviceInfo.sampleFormat. /* Heap.hpp -- as SYS_ALLOC, but commits every page up front instead of
static constexpr uint8_t SDR_FORMAT_CU8 = 0; // 8-bit unsigned interleaved I/Q 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 // Graceful power-off request actions (SYS_POWER_REQUEST). The desktop posts
// a pending action and exits; login.elf reads it, runs the shutdown stages, // a pending action and exits; login.elf reads it, runs the shutdown stages,
// then issues the matching SYS_SHUTDOWN / SYS_RESET. // 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 { enum PowerRequestAction : int {
POWER_REQ_QUERY = 0, // read-and-clear the pending action POWER_REQ_QUERY = 0, // read-and-clear the pending action
POWER_REQ_SHUTDOWN = 1, POWER_REQ_SHUTDOWN = 1,
POWER_REQ_REBOOT = 2, 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; static constexpr uint32_t CLIPBOARD_MAX_TEXT_BYTES = 256 * 1024;
@@ -612,8 +698,11 @@ namespace montauk::abi {
uint8_t state; // 0=Free, 1=Ready, 2=Running, 3=Blocked, 4=Terminated uint8_t state; // 0=Free, 1=Ready, 2=Running, 3=Blocked, 4=Terminated
uint8_t _pad[3]; uint8_t _pad[3];
char name[64]; 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 cpuTimeMs; // accumulated scheduler runtime
uint64_t permittedCaps;
uint64_t effectiveCaps;
uint64_t delegableCaps;
}; };
// Bluetooth scan result (returned by SYS_BTSCAN) // Bluetooth scan result (returned by SYS_BTSCAN)
@@ -650,23 +739,40 @@ namespace montauk::abi {
uint8_t _pad[2]; uint8_t _pad[2];
}; };
// Software-defined radio receiver description (returned by SYS_SDR_INFO). // One USB interface currently represented by the xHCI device table. A
struct SdrDeviceInfo { // nonzero kernelDriverBound interface cannot be claimed by userspace.
char name[64]; // e.g. "Realtek RTL2832U" struct UsbInterfaceInfo {
char tuner[32]; // e.g. "Rafael Micro R820T2" uint8_t slotId;
char serial[32]; // device serial / bus location uint8_t portId;
uint64_t freqMin; // minimum tunable center frequency, Hz uint8_t speed; // xHCI speed ID
uint64_t freqMax; // maximum tunable center frequency, Hz uint8_t interfaceNumber;
uint32_t sampleRateMin; // minimum sample rate, Hz uint16_t vendorId;
uint32_t sampleRateMax; // maximum sample rate, Hz uint16_t productId;
uint32_t numGains; // number of discrete tuner gain steps uint8_t deviceClass;
int32_t gains[32]; // available gains, tenths of dB uint8_t interfaceClass;
uint8_t sampleFormat; // SDR_FORMAT_* uint8_t interfaceSubClass;
uint8_t present; // 1 if the underlying hardware is connected uint8_t interfaceProtocol;
uint8_t streaming; // 1 if currently delivering samples uint8_t bulkInEndpoint; // USB address, including direction bit
uint8_t _pad; uint8_t bulkOutEndpoint;
uint32_t _pad2; 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. // Wi-Fi security suites reported in WifiNetwork.security.
static constexpr uint8_t WIFI_SEC_OPEN = 0; static constexpr uint8_t WIFI_SEC_OPEN = 0;
+5
View File
@@ -12,6 +12,11 @@
namespace montauk::abi { 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) { static void Sys_Print(const char* text) {
auto* proc = Sched::GetCurrentProcessPtr(); auto* proc = Sched::GetCurrentProcessPtr();
if (proc && proc->redirected) { if (proc && proc->redirected) {
+44
View File
@@ -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);
}
}
@@ -1,9 +1,12 @@
/* /*
* main.cpp * main.cpp
* Kernel entry point * 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 <cstdint>
#include <cstddef> #include <cstddef>
#include <Boot/Boot.hpp> #include <Boot/Boot.hpp>
@@ -29,7 +32,6 @@
#include <Drivers/PS2/Keyboard.hpp> #include <Drivers/PS2/Keyboard.hpp>
#include <Drivers/PS2/Mouse.hpp> #include <Drivers/PS2/Mouse.hpp>
#include <Drivers/Init.hpp> #include <Drivers/Init.hpp>
#include <Drivers/USB/Bluetooth/Bluetooth.hpp>
#include <Graphics/Framebuffer.hpp> #include <Graphics/Framebuffer.hpp>
#include <Hal/MSR.hpp> #include <Hal/MSR.hpp>
#include <Hal/Cpu.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++) { for (std::size_t i = 0; &__init_array[i] != __init_array_end; i++) {
__init_array[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()) { if (!montauk::boot::Initialize()) {
Hal::Halt(); Hal::Halt();
} }
@@ -119,20 +116,13 @@ extern "C" void kmain() {
Memory::VMM::g_paging = &g_paging; Memory::VMM::g_paging = &g_paging;
g_paging.Init((uint64_t)&KernelStartSymbol, ((uint64_t)&KernelEndSymbol - (uint64_t)&KernelStartSymbol), boot.memoryMap, framebuffer); 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(); Hal::InitializePAT();
Kt::KernelLogStream(OK, "Hal") << "PAT reprogrammed (entry 1 = WC)";
#endif #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); Graphics::Framebuffer::Initialize(framebuffer);
#if defined (__x86_64__) #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(); Graphics::Framebuffer::MapWriteCombining();
#endif #endif
@@ -145,19 +135,12 @@ extern "C" void kmain() {
Hal::ApicInitialize(g_acpi.GetXSDT()); 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(); 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(); Hal::CpuPower::InitializeBsp();
// Now safe to enable interrupts (SWAPGS-aware ISR stubs are installed)
asm volatile("sti"); asm volatile("sti");
// Initialize ACPI events (SCI, power button) after APIC is ready
Hal::AcpiEvents::Initialize(g_acpi.GetXSDT()); Hal::AcpiEvents::Initialize(g_acpi.GetXSDT());
Pci::Initialize(g_acpi.GetXSDT()); Pci::Initialize(g_acpi.GetXSDT());
@@ -186,29 +169,34 @@ extern "C" void kmain() {
Fs::InitializeBootFilesystems(boot.modules); Fs::InitializeBootFilesystems(boot.modules);
#if defined (__x86_64__)
Hal::LoadTSS(); Hal::LoadTSS();
#endif
montauk::abi::InitializeSyscalls(); montauk::abi::InitializeSyscalls();
Sched::Initialize(); Sched::Initialize();
Memory::InitUserRange();
Fs::LogProtectedPaths();
Ipc::Initialize(); Ipc::Initialize();
// Boot Application Processors (all subsystems ready, APs can schedule) #if defined (__x86_64__)
Smp::BootAPs(boot.smp); Smp::BootAPs(boot.smp);
#endif
// Flush any stale PS/2 mouse bytes that accumulated during boot #if defined (__x86_64__)
// (edge-triggered IRQs can be lost while spinlocks disable interrupts)
Drivers::PS2::Mouse::FlushState(); Drivers::PS2::Mouse::FlushState();
#endif
Kt::SuppressKernelLog(); Kt::SuppressKernelLog();
Sched::Spawn("0:/os/init.elf"); Sched::Spawn("0:/os/init.elf");
// Enable preemptive scheduling via the APIC timer
Timekeeping::EnableSchedulerTick(); Timekeeping::EnableSchedulerTick();
// Main loop: idle until next interrupt. #if defined (__x86_64__)
// Use MWAIT for deeper C-states if available, otherwise HLT. // Use MWAIT for deeper C-states if available, otherwise HLT.
auto* bspCpu = Smp::GetCpuData(0); auto* bspCpu = Smp::GetCpuData(0);
if (bspCpu && bspCpu->hasMwait) { if (bspCpu && bspCpu->hasMwait) {
static volatile uint64_t s_bspIdleMonitor = 0; static volatile uint64_t s_bspIdleMonitor = 0;
for (;;) { for (;;) {
@@ -219,4 +207,9 @@ extern "C" void kmain() {
Timekeeping::IdleOnce(false); Timekeeping::IdleOnce(false);
} }
} }
#else
for (;;) {
Timekeeping::IdleOnce(false);
}
#endif
} }
+23
View File
@@ -440,6 +440,29 @@ namespace Drivers::Net::Wifi {
static void ServiceAsync(); static void ServiceAsync();
static void ServiceRecovery(); 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() { void ServiceEvents() {
if (!g_iwx.Mmio) return; if (!g_iwx.Mmio) return;
if (g_iwx.WorkPending) IwxProcessEvents(); if (g_iwx.WorkPending) IwxProcessEvents();
+4
View File
@@ -21,6 +21,10 @@ namespace Drivers::Net::Wifi {
// Steady-state event pump (RX ring, notifications). Idle-loop callback. // Steady-state event pump (RX ring, notifications). Idle-loop callback.
void ServiceEvents(); 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 IsInitialized();
bool IsPresent(); bool IsPresent();
-359
View File
@@ -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;
}
}
}
-119
View File
@@ -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) // 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() { void ServiceEvents() {
if (!g_initialized) return; if (!g_initialized) return;
if (Xhci::InPollContext()) return; // never nest under PollEvents if (Xhci::InPollContext()) return; // never nest under PollEvents
@@ -27,6 +27,11 @@ namespace Drivers::USB::Bluetooth {
// (PollEvents/DrainEvents/ProcessPendingCommands all self-serialize). // (PollEvents/DrainEvents/ProcessPendingCommands all self-serialize).
void ServiceEvents(); 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 // Query adapter state
bool IsInitialized(); bool IsInitialized();
uint8_t GetSlotId(); uint8_t GetSlotId();
+5
View File
@@ -1719,6 +1719,11 @@ namespace Drivers::USB::Bluetooth::Hci {
s_active.store(false, std::memory_order_release); 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) { bool WaitSecureSendResult(uint32_t timeoutMs, uint8_t* outResult, uint8_t* outStatus) {
uint64_t start = Timekeeping::GetMilliseconds(); uint64_t start = Timekeeping::GetMilliseconds();
while (Timekeeping::GetMilliseconds() - start < timeoutMs) { while (Timekeeping::GetMilliseconds() - start < timeoutMs) {
+1
View File
@@ -331,6 +331,7 @@ namespace Drivers::USB::Bluetooth::Hci {
// real confirmed transfers. Call from top-level (e.g. the connect loop), // real confirmed transfers. Call from top-level (e.g. the connect loop),
// NOT from an event handler -- event handlers only enqueue. // NOT from an event handler -- event handlers only enqueue.
void ProcessPendingCommands(); void ProcessPendingCommands();
bool HasPendingCommands();
// ACL TX flow control: outstanding (un-acked) ACL packets, and the // ACL TX flow control: outstanding (un-acked) ACL packets, and the
// controller's ACL buffer count (Number-Of-Completed-Packets credits). The // controller's ACL buffer count (Number-Of-Completed-Packets credits). The
-61
View File
@@ -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);
}
-575
View File
@@ -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;
}
}
-37
View File
@@ -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);
}
+58 -16
View File
@@ -10,7 +10,6 @@
#include "HidMouse.hpp" #include "HidMouse.hpp"
#include "MassStorage.hpp" #include "MassStorage.hpp"
#include "Bluetooth/Bluetooth.hpp" #include "Bluetooth/Bluetooth.hpp"
#include "Radio/RtlSdr.hpp"
#include <Terminal/Terminal.hpp> #include <Terminal/Terminal.hpp>
#include <CppLib/Stream.hpp> #include <CppLib/Stream.hpp>
#include <Memory/HHDM.hpp> #include <Memory/HHDM.hpp>
@@ -351,11 +350,6 @@ namespace Drivers::USB::UsbDevice {
bool foundBulkOut = false; bool foundBulkOut = false;
uint16_t hidReportDescLen = 0; 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) { while (offset + 2 <= totalLen) {
uint8_t len = cfgBuf[offset]; uint8_t len = cfgBuf[offset];
uint8_t type = cfgBuf[offset + 1]; uint8_t type = cfgBuf[offset + 1];
@@ -423,8 +417,8 @@ namespace Drivers::USB::UsbDevice {
foundEp = true; foundEp = true;
} }
// Bluetooth, Mass Storage and RTL-SDR bulk endpoints // Bulk endpoints needed by in-kernel Bluetooth and storage drivers.
if (foundBt || currentMsc || foundRadio) { if (foundBt || currentMsc) {
if (isIn && xferType == EP_XFER_INTERRUPT && !foundEp) { if (isIn && xferType == EP_XFER_INTERRUPT && !foundEp) {
// HCI event pipe (interrupt IN) // HCI event pipe (interrupt IN)
dev->InterruptEpNum = ep->bEndpointAddress & 0x0F; dev->InterruptEpNum = ep->bEndpointAddress & 0x0F;
@@ -457,6 +451,51 @@ namespace Drivers::USB::UsbDevice {
foundBt = true; 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 // Step 8: SET_CONFIGURATION
// ----------------------------------------------------------------- // -----------------------------------------------------------------
@@ -657,15 +696,20 @@ namespace Drivers::USB::UsbDevice {
// ----------------------------------------------------------------- // -----------------------------------------------------------------
// Step 13: Register with the appropriate class driver // 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); HidKeyboard::RegisterDevice(slotId);
KernelLogStream(OK, "USB") << "Slot " << (uint64_t)slotId << ": HID Boot Keyboard"; 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); HidMouse::RegisterDevice(slotId);
KernelLogStream(OK, "USB") << "Slot " << (uint64_t)slotId << ": HID Boot Mouse"; KernelLogStream(OK, "USB") << "Slot " << (uint64_t)slotId << ": HID Boot Mouse";
} else if (dev->InterfaceClass == CLASS_WIRELESS && } else if (dev->InterfaceClass == CLASS_WIRELESS &&
dev->InterfaceSubClass == SUBCLASS_RF && dev->InterfaceSubClass == SUBCLASS_RF &&
dev->InterfaceProtocol == PROTOCOL_BLUETOOTH) { dev->InterfaceProtocol == PROTOCOL_BLUETOOTH) {
dev->KernelDriverBound = true;
Bluetooth::RegisterAdapter(slotId); Bluetooth::RegisterAdapter(slotId);
KernelLogStream(OK, "USB") << "Slot " << (uint64_t)slotId << ": Bluetooth Adapter" KernelLogStream(OK, "USB") << "Slot " << (uint64_t)slotId << ": Bluetooth Adapter"
<< " VID:" << base::hex << (uint64_t)dev->VendorId << " VID:" << base::hex << (uint64_t)dev->VendorId
@@ -674,15 +718,10 @@ namespace Drivers::USB::UsbDevice {
dev->InterfaceSubClass == SUBCLASS_SCSI && dev->InterfaceSubClass == SUBCLASS_SCSI &&
dev->InterfaceProtocol == PROTOCOL_BULK_ONLY && dev->InterfaceProtocol == PROTOCOL_BULK_ONLY &&
foundMsc && foundBulkIn && foundBulkOut) { foundMsc && foundBulkIn && foundBulkOut) {
dev->KernelDriverBound = true;
MassStorage::RegisterDevice(slotId); MassStorage::RegisterDevice(slotId);
KernelLogStream(OK, "USB") << "Slot " << (uint64_t)slotId KernelLogStream(OK, "USB") << "Slot " << (uint64_t)slotId
<< ": USB Mass Storage"; << ": 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) { } else if (foundEp) {
KernelLogStream(INFO, "USB") << "Slot " << (uint64_t)slotId KernelLogStream(INFO, "USB") << "Slot " << (uint64_t)slotId
<< ": USB device, class=" << (uint64_t)dev->InterfaceClass << ": USB device, class=" << (uint64_t)dev->InterfaceClass
@@ -692,6 +731,9 @@ namespace Drivers::USB::UsbDevice {
<< ": Non-HID device, class=" << (uint64_t)devDesc.bDeviceClass; << ": 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; return slotId;
} }
+381
View File
@@ -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();
}
}
}
+38
View File
@@ -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);
}
+37 -17
View File
@@ -10,7 +10,7 @@
#include "HidKeyboard.hpp" #include "HidKeyboard.hpp"
#include "HidMouse.hpp" #include "HidMouse.hpp"
#include "MassStorage.hpp" #include "MassStorage.hpp"
#include "Radio/RtlSdr.hpp" #include "UserUsb.hpp"
#include <Pci/Pci.hpp> #include <Pci/Pci.hpp>
#include <Terminal/Terminal.hpp> #include <Terminal/Terminal.hpp>
#include <CppLib/Stream.hpp> #include <CppLib/Stream.hpp>
@@ -167,8 +167,7 @@ namespace Drivers::USB::Xhci {
// nesting (a callback invoked from THIS core's PollEvents); a different // nesting (a callback invoked from THIS core's PollEvents); a different
// core merely polling must not make a process-context transfer skip its // core merely polling must not make a process-context transfer skip its
// wait -- that returned CC_SUCCESS before the device filled the buffer // wait -- that returned CC_SUCCESS before the device filled the buffer
// (observed as garbled RTL-SDR register reads while the BT firmware // (observed as corrupted USB control reads while another core was polling).
// download was polling on another core).
static std::atomic<int> g_pollOwnerCpu{-1}; static std::atomic<int> g_pollOwnerCpu{-1};
// Serialises non-nested (waiting) control transfers so only one EP0 // 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 // 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 // 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 // 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. // single-buffer path above (used by Bluetooth ACL), unchanged.
static constexpr uint32_t BULK_IN_POOL_MAX = 16; static constexpr uint32_t BULK_IN_POOL_MAX = 16;
static uint8_t* g_bulkInPool[MAX_SLOTS + 1][BULK_IN_POOL_MAX] = {}; 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_bulkInPoolCount[MAX_SLOTS + 1] = {}; // outstanding URBs (0=off)
static uint32_t g_bulkInPoolHead[MAX_SLOTS + 1] = {}; // next buffer to complete static uint32_t g_bulkInPoolHead[MAX_SLOTS + 1] = {}; // next buffer to complete
static uint32_t g_bulkInPoolXferLen[MAX_SLOTS + 1] = {}; // bytes per transfer 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) // Transfer callbacks for non-HID class drivers (per slot)
static TransferCallback g_transferCallbacks[MAX_SLOTS + 1] = {}; 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 // buffer is safe -- it will not be DMA'd into again
// until the other PoolCount-1 transfers ahead of it // until the other PoolCount-1 transfers ahead of it
// complete (~PoolCount ms of slack). // complete (~PoolCount ms of slack).
uint32_t i = g_bulkInPoolHead[slotId]; g_bulkInPoolLocks[slotId].Acquire();
uint32_t reqLen = g_bulkInPoolXferLen[slotId]; uint32_t poolCount = g_bulkInPoolCount[slotId];
uint32_t len = (residual < reqLen) ? (reqLen - residual) : 0; if (poolCount > 0) {
g_transferCallbacks[slotId](slotId, epDci, uint32_t i = g_bulkInPoolHead[slotId];
g_bulkInPool[slotId][i], len, completionCode); uint32_t reqLen = g_bulkInPoolXferLen[slotId];
QueueBulkInTransfer(slotId, g_bulkInPool[slotId][i], uint32_t len = (residual < reqLen) ? (reqLen - residual) : 0;
g_bulkInPoolPhys[slotId][i], reqLen); g_transferCallbacks[slotId](slotId, epDci,
g_bulkInPoolHead[slotId] = g_bulkInPool[slotId][i], len, completionCode);
(i + 1) % g_bulkInPoolCount[slotId]; 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]) { } else if (epDci == bulkInDci && g_transferCallbacks[slotId]) {
// Bulk IN — dispatch via registered callback. // Bulk IN — dispatch via registered callback.
// len = actually-transferred bytes (requested - // len = actually-transferred bytes (requested -
@@ -1149,13 +1153,18 @@ namespace Drivers::USB::Xhci {
// single-buffer start relies on. // single-buffer start relies on.
void PrimeBulkInStream(uint8_t slotId) { void PrimeBulkInStream(uint8_t slotId) {
if (slotId == 0 || slotId > MAX_SLOTS || !g_devices[slotId].Active) return; if (slotId == 0 || slotId > MAX_SLOTS || !g_devices[slotId].Active) return;
g_bulkInPoolLocks[slotId].Acquire();
uint32_t n = g_bulkInPoolCount[slotId]; uint32_t n = g_bulkInPoolCount[slotId];
if (n == 0) return; if (n == 0) {
g_bulkInPoolLocks[slotId].Release();
return;
}
g_bulkInPoolHead[slotId] = 0; g_bulkInPoolHead[slotId] = 0;
uint32_t len = g_bulkInPoolXferLen[slotId]; uint32_t len = g_bulkInPoolXferLen[slotId];
for (uint32_t i = 0; i < n; i++) for (uint32_t i = 0; i < n; i++)
QueueBulkInTransfer(slotId, g_bulkInPool[slotId][i], QueueBulkInTransfer(slotId, g_bulkInPool[slotId][i],
g_bulkInPoolPhys[slotId][i], len); g_bulkInPoolPhys[slotId][i], len);
g_bulkInPoolLocks[slotId].Release();
} }
void StartBulkInStream(uint8_t slotId, uint32_t xferLen, uint32_t numBuffers) { 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++) for (uint32_t i = 0; i < numBuffers; i++)
QueueBulkInTransfer(slotId, g_bulkInPool[slotId][i], QueueBulkInTransfer(slotId, g_bulkInPool[slotId][i],
g_bulkInPoolPhys[slotId][i], xferLen); g_bulkInPoolPhys[slotId][i], xferLen);
g_bulkInPoolLocks[slotId].Acquire();
g_bulkInPoolCount[slotId] = numBuffers; 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) { void StopBulkInStream(uint8_t slotId) {
if (slotId == 0 || slotId > MAX_SLOTS) return; if (slotId == 0 || slotId > MAX_SLOTS) return;
// Disarm the rotation; any late completion now takes the (no-op for SDR) // 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. // legacy path and is not re-armed. Buffers are retained for reuse.
g_bulkInPoolLocks[slotId].Acquire();
bool wasArmed = g_bulkInPoolCount[slotId] != 0; bool wasArmed = g_bulkInPoolCount[slotId] != 0;
g_bulkInPoolCount[slotId] = 0; g_bulkInPoolCount[slotId] = 0;
g_bulkInPoolLocks[slotId].Release();
if (!wasArmed) return; if (!wasArmed) return;
// Flush the up-to-PoolCount TRBs still pending on the ring: Stop // 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) { static void UnregisterClassDriver(uint8_t slotId, const UsbDeviceInfo& dev) {
if (Radio::IsRtlSdr(dev.VendorId, dev.ProductId)) { if (dev.InterfaceClass == UsbDevice::CLASS_MASS_STORAGE) {
Radio::UnregisterDevice(slotId);
} else if (dev.InterfaceClass == UsbDevice::CLASS_MASS_STORAGE) {
MassStorage::UnregisterDevice(slotId); MassStorage::UnregisterDevice(slotId);
} else if (dev.InterfaceClass == UsbDevice::CLASS_HID && } else if (dev.InterfaceClass == UsbDevice::CLASS_HID &&
dev.InterfaceProtocol == UsbDevice::PROTOCOL_KEYBOARD) { dev.InterfaceProtocol == UsbDevice::PROTOCOL_KEYBOARD) {
@@ -1559,6 +1578,7 @@ namespace Drivers::USB::Xhci {
// Device disconnected — deactivate its slot // Device disconnected — deactivate its slot
for (uint8_t s = 1; s <= MAX_SLOTS; s++) { for (uint8_t s = 1; s <= MAX_SLOTS; s++) {
if (g_devices[s].Active && g_devices[s].PortId == port + 1) { if (g_devices[s].Active && g_devices[s].PortId == port + 1) {
UserUsb::DeviceDisconnected(s);
UnregisterClassDriver(s, g_devices[s]); UnregisterClassDriver(s, g_devices[s]);
g_devices[s].Active = false; g_devices[s].Active = false;
g_transferCallbacks[s] = nullptr; g_transferCallbacks[s] = nullptr;
+5 -2
View File
@@ -233,6 +233,7 @@ namespace Drivers::USB::Xhci {
struct UsbDeviceInfo { struct UsbDeviceInfo {
bool Active; bool Active;
bool Ready; // descriptors/endpoints and binding are complete
uint8_t PortId; uint8_t PortId;
uint32_t Speed; uint32_t Speed;
uint16_t VendorId; uint16_t VendorId;
@@ -242,6 +243,7 @@ namespace Drivers::USB::Xhci {
uint8_t InterfaceProtocol; uint8_t InterfaceProtocol;
uint8_t InterfaceNumber; uint8_t InterfaceNumber;
uint8_t DeviceClass; // bDeviceClass from device descriptor uint8_t DeviceClass; // bDeviceClass from device descriptor
bool KernelDriverBound; // unavailable to a userspace interface claim
// Interrupt IN endpoint // Interrupt IN endpoint
uint8_t InterruptEpNum; // Endpoint number (1-15) 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 // Clear a halted bulk IN endpoint (Reset Endpoint + Set TR Dequeue) without
// re-arming. Must be called from process context (it issues commands that // re-arming. Must be called from process context (it issues commands that
// wait on the event ring); the caller re-arms with QueueBulkInTransfer. // 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); void ResetBulkInEndpoint(uint8_t slotId);
// Clear a halted bulk OUT endpoint and discard the errored/queued TRBs. // 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 // 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 // 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 // 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. // whole pool after a stall reset; StopBulkInStream disarms the rotation.
// All three are process-context calls. // All three are process-context calls.
void StartBulkInStream(uint8_t slotId, uint32_t xferLen, uint32_t numBuffers); void StartBulkInStream(uint8_t slotId, uint32_t xferLen, uint32_t numBuffers);
bool IsBulkInStreamActive(uint8_t slotId);
void PrimeBulkInStream(uint8_t slotId); void PrimeBulkInStream(uint8_t slotId);
void StopBulkInStream(uint8_t slotId); void StopBulkInStream(uint8_t slotId);
+7
View File
@@ -1202,6 +1202,13 @@ namespace Fs::Ext2 {
Inode inode; Inode inode;
if (!TraversePath(self, path, &inodeNum, &inode)) return -1; 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++) { for (int i = 0; i < MaxFilesPerInstance; i++) {
if (!self.files[i].inUse) { if (!self.files[i].inUse) {
self.files[i].inUse = true; self.files[i].inUse = true;
+4
View File
@@ -955,6 +955,10 @@ namespace Fs::Fat32 {
ParsedEntry entry; ParsedEntry entry;
if (!TraversePath(inst, path, &entry)) return -1; 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 // Find a free file handle
auto& self = g_instances[inst]; auto& self = g_instances[inst];
for (int i = 0; i < MaxFilesPerInstance; i++) { for (int i = 0; i < MaxFilesPerInstance; i++) {
+128
View File
@@ -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;
}
}
}
+20
View File
@@ -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();
}
+20 -6
View File
@@ -355,10 +355,18 @@ namespace Fs::Ramdisk {
uint64_t newCap = entry.size; uint64_t newCap = entry.size;
if (endOffset > newCap) newCap = endOffset; if (endOffset > newCap) newCap = endOffset;
if (newCap < 256) newCap = 256; if (newCap < 256) newCap = 256;
// Round up to next power of 2 for growth // Small files round to the next power of 2, so an appender grows
uint64_t rounded = 256; // in a few steps. Large ones round to a page instead: the kernel
while (rounded < newCap) rounded *= 2; // heap grows in physically contiguous runs, and doubling an 8 MiB
newCap = rounded; // 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); uint8_t* newBuf = (uint8_t*)Memory::g_heap->Request(newCap);
if (newBuf == nullptr) return -1; if (newBuf == nullptr) return -1;
@@ -374,8 +382,14 @@ namespace Fs::Ramdisk {
// Grow buffer if needed // Grow buffer if needed
if (endOffset > entry.capacity) { if (endOffset > entry.capacity) {
uint64_t newCap = entry.capacity; // Double while small, then grow in fixed 1 MiB steps. Doubling all
while (newCap < endOffset) newCap *= 2; // 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); uint8_t* newBuf = (uint8_t*)Memory::g_heap->Request(newCap);
if (newBuf == nullptr) return -1; if (newBuf == nullptr) return -1;
+4
View File
@@ -218,6 +218,10 @@ namespace Smp {
for (;;) { for (;;) {
// Pick up thermal-governor frequency changes decided by the BSP. // Pick up thermal-governor frequency changes decided by the BSP.
Hal::CpuPower::ApplyPolicyIfChanged(); 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 // Any idle core may run bounded USB/NIC bottom halves. Preserve
// the AP's ACPI/MWAIT idle selection after servicing them. // the AP's ACPI/MWAIT idle selection after servicing them.
Timekeeping::ServiceDeferredWork(); Timekeeping::ServiceDeferredWork();
+72 -148
View File
@@ -8,11 +8,13 @@
#include <Sched/Scheduler.hpp> #include <Sched/Scheduler.hpp>
#include <Fs/Vfs.hpp> #include <Fs/Vfs.hpp>
#include <Fs/ProtectedPaths.hpp>
#include <Net/Tcp.hpp> #include <Net/Tcp.hpp>
#include <Net/Udp.hpp> #include <Net/Udp.hpp>
#include <Memory/PageFrameAllocator.hpp> #include <Memory/PageFrameAllocator.hpp>
#include <Memory/HHDM.hpp> #include <Memory/HHDM.hpp>
#include <Memory/Paging.hpp> #include <Memory/Paging.hpp>
#include <Memory/UserRange.hpp>
#include <Libraries/Memory.hpp> #include <Libraries/Memory.hpp>
#include <CppLib/Spinlock.hpp> #include <CppLib/Spinlock.hpp>
#include <Hal/Apic/Apic.hpp> #include <Hal/Apic/Apic.hpp>
@@ -82,6 +84,7 @@ namespace Ipc {
struct File : Object { struct File : Object {
Fs::Vfs::BackendFile backend; Fs::Vfs::BackendFile backend;
uint64_t writeCapability;
}; };
struct UdpDgramHeader { struct UdpDgramHeader {
@@ -178,149 +181,11 @@ namespace Ipc {
static void ReleaseRawObject(Object* object); 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 // Object lifetime
// is queued behind the holder has to stay interruptible long enough to // Pool allocation, refcounting and type-dispatched teardown.
// service that IPI itself. An interrupt-disabling Spinlock here deadlocks // Every object type routes through this layer.
// 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]));
}
}
}
}
static void InitObject(Object& object, HandleType type) { static void InitObject(Object& object, HandleType type) {
object.type = type; object.type = type;
@@ -568,6 +433,11 @@ namespace Ipc {
} }
} }
// ==========================================================================
// Handle table
// Per-process handle installation, rights, duplication and close.
// ==========================================================================
int CurrentSlot() { int CurrentSlot() {
auto* proc = Sched::GetCurrentProcessPtr(); auto* proc = Sched::GetCurrentProcessPtr();
if (proc == nullptr) return -1; if (proc == nullptr) return -1;
@@ -817,6 +687,11 @@ namespace Ipc {
return InstallHandleForSlot(slot, snapshot.object, snapshot.type, snapshot.rights); return InstallHandleForSlot(slot, snapshot.object, snapshot.type, snapshot.rights);
} }
// ==========================================================================
// Streams
// Byte pipes.
// ==========================================================================
Stream* CreateStream(uint32_t capacity) { Stream* CreateStream(uint32_t capacity) {
if (capacity == 0) capacity = DefaultStreamCapacity; if (capacity == 0) capacity = DefaultStreamCapacity;
@@ -1000,6 +875,11 @@ namespace Ipc {
return hasData; return hasData;
} }
// ==========================================================================
// Mailboxes
// Discrete message queues.
// ==========================================================================
Mailbox* CreateMailbox() { Mailbox* CreateMailbox() {
g_mailboxPoolLock.Acquire(); g_mailboxPoolLock.Acquire();
for (int i = 0; i < MaxMailboxes; i++) { for (int i = 0; i < MaxMailboxes; i++) {
@@ -1260,9 +1140,22 @@ namespace Ipc {
return hasMsg; 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) { int OpenFileHandleForSlot(int slot, const char* path, bool create) {
if (slot < 0 || slot >= Sched::MaxProcesses || path == nullptr) return -1; 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}; Fs::Vfs::BackendFile backend = {-1, -1, 0};
int result = create ? Fs::Vfs::CreateBackendFile(path, backend) int result = create ? Fs::Vfs::CreateBackendFile(path, backend)
: Fs::Vfs::OpenBackendFile(path, backend); : Fs::Vfs::OpenBackendFile(path, backend);
@@ -1273,6 +1166,7 @@ namespace Ipc {
if (g_files[i].active || g_files[i].destroying) continue; if (g_files[i].active || g_files[i].destroying) continue;
InitObject(g_files[i], HandleType::File); InitObject(g_files[i], HandleType::File);
g_files[i].backend = backend; g_files[i].backend = backend;
g_files[i].writeCapability = writeCapability;
g_filePoolLock.Release(); g_filePoolLock.Release();
uint32_t rights = RightRead | RightWait | RightDup; uint32_t rights = RightRead | RightWait | RightDup;
@@ -1318,7 +1212,12 @@ namespace Ipc {
HandleSnapshot snapshot; HandleSnapshot snapshot;
if (!snapshot.Capture(CurrentSlot(), handle)) return -1; if (!snapshot.Capture(CurrentSlot(), handle)) return -1;
if (snapshot.type != HandleType::File || (snapshot.rights & RightWrite) == 0) 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) { uint64_t FileGetSizeHandle(int handle) {
@@ -1328,6 +1227,11 @@ namespace Ipc {
return Fs::Vfs::GetBackendFileSize(((File*)snapshot.object)->backend); return Fs::Vfs::GetBackendFileSize(((File*)snapshot.object)->backend);
} }
// ==========================================================================
// Sockets
// TCP and UDP endpoints.
// ==========================================================================
static Socket* AllocateSocketObject(int type) { static Socket* AllocateSocketObject(int type) {
g_socketPoolLock.Acquire(); g_socketPoolLock.Acquire();
for (int i = 0; i < MaxSockets; i++) { for (int i = 0; i < MaxSockets; i++) {
@@ -1663,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) { Surface* CreateSurface(uint64_t byteSize) {
if (byteSize == 0) byteSize = 0x1000; if (byteSize == 0) byteSize = 0x1000;
uint32_t numPages = (uint32_t)((byteSize + 0xFFFu) / 0x1000u); uint32_t numPages = (uint32_t)((byteSize + 0xFFFu) / 0x1000u);
@@ -1748,7 +1659,7 @@ namespace Ipc {
for (uint32_t p = m.numPages; p < newPages; p++) { for (uint32_t p = m.numPages; p < newPages; p++) {
Memory::VMM::Paging::UnmapUserIn(pml4, m.va + (uint64_t)p * 0x1000ULL); Memory::VMM::Paging::UnmapUserIn(pml4, m.va + (uint64_t)p * 0x1000ULL);
} }
ShootdownUserRange(pml4, startVa, rollbackPages); Memory::ShootdownUserRange(pml4, startVa, rollbackPages);
} }
g_surfaceMapLocks[s].Release(); g_surfaceMapLocks[s].Release();
} }
@@ -1900,7 +1811,7 @@ namespace Ipc {
Memory::VMM::Paging::UnmapUserIn(pml4, va); Memory::VMM::Paging::UnmapUserIn(pml4, va);
} }
ShootdownUserRange(pml4, baseVa, flushPages); Memory::ShootdownUserRange(pml4, baseVa, flushPages);
m.numPages = newPages; m.numPages = newPages;
} }
g_surfaceMapLocks[s].Release(); g_surfaceMapLocks[s].Release();
@@ -2111,7 +2022,7 @@ namespace Ipc {
// releasing the mapping reference can then destroy the surface // releasing the mapping reference can then destroy the surface
// and recycle its frames while that sibling writes through its // and recycle its frames while that sibling writes through its
// stale TLB entry. Quiesce every CPU using this PML4 first. // 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].used = false;
g_surfaceMaps[slot][i].surface = nullptr; g_surfaceMaps[slot][i].surface = nullptr;
@@ -2127,6 +2038,11 @@ namespace Ipc {
return unmapped > 0 ? 0 : -1; return unmapped > 0 ? 0 : -1;
} }
// ==========================================================================
// Process handles
// Wait-only references to a live process.
// ==========================================================================
int OpenProcessHandle(int pid) { int OpenProcessHandle(int pid) {
g_processPoolLock.Acquire(); g_processPoolLock.Acquire();
for (int i = 0; i < MaxProcessObjects; i++) { for (int i = 0; i < MaxProcessObjects; i++) {
@@ -2183,6 +2099,11 @@ namespace Ipc {
return exited; return exited;
} }
// ==========================================================================
// Signals and waitsets
// Readiness computation and multiplexed waiting.
// ==========================================================================
static uint32_t CurrentSocketSignals(Socket* socket, uint32_t rights) { static uint32_t CurrentSocketSignals(Socket* socket, uint32_t rights) {
if (socket == nullptr) return SignalNone; if (socket == nullptr) return SignalNone;
@@ -2513,6 +2434,10 @@ namespace Ipc {
} }
} }
// ==========================================================================
// Teardown and init
// ==========================================================================
void CleanupProcessSlot(int slot, int /*pid*/, uint64_t pml4Phys) { void CleanupProcessSlot(int slot, int /*pid*/, uint64_t pml4Phys) {
if (slot < 0 || slot >= Sched::MaxProcesses) return; if (slot < 0 || slot >= Sched::MaxProcesses) return;
@@ -2548,7 +2473,6 @@ namespace Ipc {
for (int i = 0; i < Sched::MaxProcesses; i++) { for (int i = 0; i < Sched::MaxProcesses; i++) {
g_processObjectsBySlot[i] = nullptr; g_processObjectsBySlot[i] = nullptr;
} }
Hal::RegisterIrqHandler(Hal::IRQ_TLB_SHOOTDOWN, TlbShootdownIpiHandler);
Kt::KernelLogStream(Kt::OK, "IPC") << "Initialized (" Kt::KernelLogStream(Kt::OK, "IPC") << "Initialized ("
<< (uint64_t)MaxHandlesPerProcess << " handles/process, " << (uint64_t)MaxHandlesPerProcess << " handles/process, "
<< (uint64_t)MaxStreams << " streams, " << (uint64_t)MaxStreams << " streams, "
-5
View File
@@ -171,11 +171,6 @@ namespace Ipc {
int WaitsetWaitHandle(int waitsetHandle, WaitsetReady* outReady, uint64_t timeoutMs); int WaitsetWaitHandle(int waitsetHandle, WaitsetReady* outReady, uint64_t timeoutMs);
void NotifyObjectChanged(Object* object); 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); void CleanupProcessSlot(int slot, int pid, uint64_t pml4Phys);
} }
+172
View File
@@ -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);
}
}
+23
View File
@@ -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);
}
+131 -7
View File
@@ -4,6 +4,7 @@
* Copyright (c) 2025-2026 Daniel Hammer * Copyright (c) 2025-2026 Daniel Hammer
*/ */
#include <Memory/UserRange.hpp>
#include "Scheduler.hpp" #include "Scheduler.hpp"
#include "ElfLoader.hpp" #include "ElfLoader.hpp"
#include <Memory/PageFrameAllocator.hpp> #include <Memory/PageFrameAllocator.hpp>
@@ -27,6 +28,7 @@
#include <Drivers/Audio/Mixer.hpp> #include <Drivers/Audio/Mixer.hpp>
#include <Drivers/Graphics/IntelGPU.hpp> #include <Drivers/Graphics/IntelGPU.hpp>
#include <Ipc/Ipc.hpp> #include <Ipc/Ipc.hpp>
#include <Drivers/USB/UserUsb.hpp>
// Assembly: context switch with CR3 and FPU state parameters // Assembly: context switch with CR3 and FPU state parameters
extern "C" void SchedContextSwitch(uint64_t* oldRsp, uint64_t newRsp, uint64_t newCR3, extern "C" void SchedContextSwitch(uint64_t* oldRsp, uint64_t newRsp, uint64_t newCR3,
@@ -297,6 +299,10 @@ namespace Sched {
processTable[i].environment[0] = '\0'; processTable[i].environment[0] = '\0';
processTable[i].environmentLength = 1; processTable[i].environmentLength = 1;
processTable[i].user[0] = '\0'; 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].cwd[0] = '\0';
processTable[i].runningOnCpu = -1; processTable[i].runningOnCpu = -1;
processTable[i].killPending = false; processTable[i].killPending = false;
@@ -340,7 +346,9 @@ namespace Sched {
} }
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 char* environment, uint32_t environmentLength,
const montauk::abi::SpawnCapabilities* capabilities,
const char* userOverride) {
schedLock.Acquire(); schedLock.Acquire();
int slot = -1; int slot = -1;
@@ -545,9 +553,37 @@ namespace Sched {
proc.environmentLength = 1; proc.environmentLength = 1;
} }
// Inherit user string from parent, or default to "system" if no parent // 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; int i = 0;
for (; i < 31 && processTable[parentSlot].user[i]; i++) for (; i < 31 && processTable[parentSlot].user[i]; i++)
proc.user[i] = processTable[parentSlot].user[i]; proc.user[i] = processTable[parentSlot].user[i];
@@ -560,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]) { if (parentSlot >= 0 && processTable[parentSlot].cwd[0]) {
int i = 0; int i = 0;
@@ -675,7 +718,7 @@ namespace Sched {
mappedPages++; mappedPages++;
} }
if (!ok) { if (!ok) {
Ipc::UnmapAndFreeUserRange(sharedPml4, base, mappedPages); Memory::UnmapAndFreeUserRange(sharedPml4, base, mappedPages);
ReleaseUserHeapRange(primarySlot_, base, numPages * 0x1000ULL); ReleaseUserHeapRange(primarySlot_, base, numPages * 0x1000ULL);
Kt::KernelLogStream(Kt::ERROR, "Sched") Kt::KernelLogStream(Kt::ERROR, "Sched")
<< "Thread TLS allocation failed"; << "Thread TLS allocation failed";
@@ -702,7 +745,7 @@ namespace Sched {
void* stackMem = Memory::g_pfa->ReallocConsecutive(nullptr, StackPages); void* stackMem = Memory::g_pfa->ReallocConsecutive(nullptr, StackPages);
if (stackMem == nullptr) { if (stackMem == nullptr) {
if (threadTlsPages != 0) { if (threadTlsPages != 0) {
Ipc::UnmapAndFreeUserRange(sharedPml4, threadTlsBase, threadTlsPages); Memory::UnmapAndFreeUserRange(sharedPml4, threadTlsBase, threadTlsPages);
ReleaseUserHeapRange(primarySlot_, threadTlsBase, ReleaseUserHeapRange(primarySlot_, threadTlsBase,
threadTlsPages * 0x1000ULL); threadTlsPages * 0x1000ULL);
} }
@@ -728,7 +771,7 @@ namespace Sched {
schedLock.Release(); schedLock.Release();
Memory::g_pfa->Free(stackMem, StackPages); Memory::g_pfa->Free(stackMem, StackPages);
if (threadTlsPages != 0) { if (threadTlsPages != 0) {
Ipc::UnmapAndFreeUserRange(sharedPml4, threadTlsBase, threadTlsPages); Memory::UnmapAndFreeUserRange(sharedPml4, threadTlsBase, threadTlsPages);
ReleaseUserHeapRange(primarySlot_, threadTlsBase, ReleaseUserHeapRange(primarySlot_, threadTlsBase,
threadTlsPages * 0x1000ULL); threadTlsPages * 0x1000ULL);
} }
@@ -876,7 +919,7 @@ namespace Sched {
uint64_t base = thr.fsBase - blockSize; uint64_t base = thr.fsBase - blockSize;
uint64_t pages = (blockSize + 16 + 0xFFF) / 0x1000; uint64_t pages = (blockSize + 16 + 0xFFF) / 0x1000;
thr.fsBase = 0; thr.fsBase = 0;
Ipc::UnmapAndFreeUserRange(primary.pml4Phys, base, pages); Memory::UnmapAndFreeUserRange(primary.pml4Phys, base, pages);
ReleaseUserHeapRange(primarySlot_, base, pages * 0x1000ULL); ReleaseUserHeapRange(primarySlot_, base, pages * 0x1000ULL);
} }
@@ -1276,6 +1319,15 @@ namespace Sched {
uint8_t* oldFpu = (oldSlot >= 0) ? processTable[oldSlot].fpuState : nullptr; uint8_t* oldFpu = (oldSlot >= 0) ? processTable[oldSlot].fpuState : nullptr;
uint8_t* newFpu = processTable[next].fpuState; 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); LoadUserFsBase(cpu, processTable[next].fsBase);
// DO NOT release schedLock here! It is held across the context // DO NOT release schedLock here! It is held across the context
@@ -1400,6 +1452,11 @@ namespace Sched {
return &processTable[primary]; return &processTable[primary];
} }
bool HasCapability(uint64_t capability) {
Process* proc = GetCurrentProcessPtr();
return proc != nullptr && (proc->effectiveCaps & capability) == capability;
}
Process* GetCurrentThreadPtr() { Process* GetCurrentThreadPtr() {
auto* cpu = Smp::GetCurrentCpuData(); auto* cpu = Smp::GetCurrentCpuData();
int slot = cpu->currentSlot; int slot = cpu->currentSlot;
@@ -1590,6 +1647,11 @@ namespace Sched {
// never stranded on the invisible buffer (no-op for non-owners). // never stranded on the invisible buffer (no-op for non-owners).
Drivers::Graphics::IntelGPU::OnProcessExit(exitingPid); 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. // Release process-scoped IPC handles/mappings before tearing down the address space.
Ipc::CleanupProcessSlot(slot, exitingPid, proc.pml4Phys); Ipc::CleanupProcessSlot(slot, exitingPid, proc.pml4Phys);
montauk::abi::CleanupHeapForSlot(slot, proc.pml4Phys); montauk::abi::CleanupHeapForSlot(slot, proc.pml4Phys);
@@ -1747,6 +1809,68 @@ namespace Sched {
return 0; 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) { int LookupExitCode(int pid) {
schedLock.Acquire(); schedLock.Acquire();
int code = 0; int code = 0;
+21 -4
View File
@@ -80,12 +80,16 @@ namespace Sched {
uint64_t pml4Phys; // Physical address of per-process PML4 uint64_t pml4Phys; // Physical address of per-process PML4
uint64_t kernelStackTop; // Top of kernel stack (for TSS RSP0 / SYSCALL) uint64_t kernelStackTop; // Top of kernel stack (for TSS RSP0 / SYSCALL)
uint64_t userStackTop; // User-space stack top 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 uint32_t readdirCursor; // Next SYS_READDIR scratch slot
char args[4096]; // Command-line arguments (set by parent via Spawn) char args[4096]; // Command-line arguments (set by parent via Spawn)
char environment[EnvironmentBytes]; // NUL-separated NAME=VALUE entries char environment[EnvironmentBytes]; // NUL-separated NAME=VALUE entries
uint32_t environmentLength; uint32_t environmentLength;
char user[32]; // Owner user name (inherited from parent on spawn) 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 char cwd[256]; // Absolute current working directory
// Thread-local storage. fsBase is loaded into IA32_FS_BASE when // Thread-local storage. fsBase is loaded into IA32_FS_BASE when
@@ -143,7 +147,9 @@ namespace Sched {
void Initialize(); 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 char* environment = nullptr, uint32_t environmentLength = 0,
const montauk::abi::SpawnCapabilities* capabilities = nullptr,
const char* userOverride = nullptr);
int StartProcess(int pid); int StartProcess(int pid);
void Schedule(); void Schedule();
@@ -171,6 +177,10 @@ namespace Sched {
// Always returns the slot that owns per-process state -- never a sibling thread. // Always returns the slot that owns per-process state -- never a sibling thread.
Process* GetCurrentProcessPtr(); 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). // Get a pointer to the currently running thread's slot (may be a sibling).
Process* GetCurrentThreadPtr(); Process* GetCurrentThreadPtr();
@@ -252,6 +262,12 @@ namespace Sched {
// Returns 0 on success, -1 on failure. // Returns 0 on success, -1 on failure.
int KillProcess(int pid); 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) // Find a process by PID (returns nullptr if not found or not alive)
Process* GetProcessByPid(int pid); Process* GetProcessByPid(int pid);
@@ -266,8 +282,9 @@ namespace Sched {
// Per-process allocated page count (tracked by Heap syscalls, separate from Process struct) // Per-process allocated page count (tracked by Heap syscalls, separate from Process struct)
inline uint64_t g_allocatedPages[MaxProcesses] = {}; inline uint64_t g_allocatedPages[MaxProcesses] = {};
// One bit per page in the bounded userspace heap. Unlike heapNext, this // One bit per page in the bounded userspace heap. This is the authoritative
// makes virtual ranges reusable after unmap and failed reservations. // allocation state; unlike heapNext, it makes virtual ranges reusable after
// unmap and failed reservations.
inline uint64_t g_userHeapPageMap[MaxProcesses][UserHeapBitmapWords] = {}; inline uint64_t g_userHeapPageMap[MaxProcesses][UserHeapBitmapWords] = {};
} }
+5 -7
View File
@@ -205,12 +205,10 @@ namespace Kt {
void Putchar(char c) { void Putchar(char c) {
if (g_kernelLogDepth > 0) { if (g_kernelLogDepth > 0) {
if (c == '\n') { // Keep the log as canonical text. CRLF is only needed by the
RingBufferAppend('\r'); // framebuffer terminal below; storing it in the ring makes file
RingBufferAppend('\n'); // consumers treat one logical newline as two line breaks.
} else { RingBufferAppend(c);
RingBufferAppend(c);
}
if (g_suppressKernelLog) { if (g_suppressKernelLog) {
return; return;
@@ -250,7 +248,7 @@ namespace Kt {
g_suppressKernelLog = false; 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; if (buf == nullptr || size == 0) return 0;
uint64_t toRead = g_klogCount; uint64_t toRead = g_klogCount;
+32 -2
View File
@@ -121,7 +121,7 @@ namespace Kt
// intentionally lock-free: a panic can occur while another CPU owns the // intentionally lock-free: a panic can occur while another CPU owns the
// terminal mutex, and the system is about to halt. // terminal mutex, and the system is about to halt.
void EnablePanicOutput(); void EnablePanicOutput();
int64_t ReadKernelLog(char* buf, uint64_t size); int64_t ReadKernelLogBuffer(char* buf, uint64_t size);
class KernelLogStream { class KernelLogStream {
KernelOutStream localStream{}; KernelOutStream localStream{};
@@ -151,7 +151,7 @@ public:
g_kernelLogDepth++; g_kernelLogDepth++;
componentName = desiredComponentName; componentName = desiredComponentName;
localStream << componentName << ": " << "[" << LogLevelToStringWithColor(level) << "] "; localStream << "[kernel/" << componentName << "] " << LogLevelToStringWithColor(level) << ": ";
} }
~KernelLogStream() { ~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; extern Kt::KernelOutStream kout;
+44 -13
View File
@@ -41,8 +41,8 @@ namespace Timekeeping {
static constexpr uint32_t DIVIDE_BY_16 = 0x03; static constexpr uint32_t DIVIDE_BY_16 = 0x03;
// The BSP keeps a 1 ms tick for timekeeping and sleep deadlines. // 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 // Running APs use a 10 ms scheduler tick. Idle APs mask it entirely and
// 1000 times per second with no useful work to do. // rely on reschedule IPIs, avoiding periodic package wakeups.
static constexpr uint32_t BSP_TICK_INTERVAL_MS = 1; 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 BSP_TIMER_HZ = 1000 / BSP_TICK_INTERVAL_MS;
static constexpr uint32_t AP_TICK_INTERVAL_MS = 10; static constexpr uint32_t AP_TICK_INTERVAL_MS = 10;
@@ -220,8 +220,28 @@ namespace Timekeeping {
// identical. This avoids PIT contention during AP boot. // identical. This avoids PIT contention during AP boot.
if (g_ticksPerMs == 0) return; if (g_ticksPerMs == 0) return;
// Configure a coarser periodic timer on APs. The scheduler still gets // Configure the 10 ms scheduler timer for running APs. Their idle loop
// a 10 ms time slice, but idle APs stop taking 1000 timer interrupts/sec. // 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); ProgramTimer(true, AP_TICK_INTERVAL_MS);
} }
@@ -239,19 +259,24 @@ namespace Timekeeping {
bool wasReserved = cpu->reservedForKernelWork; bool wasReserved = cpu->reservedForKernelWork;
cpu->reservedForKernelWork = true; cpu->reservedForKernelWork = true;
// Drain USB hot-plug deferred work from any idle core, not just the BSP. // Drain USB work only when the MSI path has actually queued something.
if (Drivers::USB::Xhci::HasDeferredWork()) { // 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(); Drivers::USB::Xhci::ProcessDeferredWork();
} }
// NIC hard IRQs only acknowledge/mask and queue RX work. Dispatching // NIC hard IRQs only acknowledge/mask and queue RX work. Dispatching
// Ethernet/TCP/UDP here keeps process-context IPC mutexes out of IRQs. // Ethernet/TCP/UDP here keeps process-context IPC mutexes out of IRQs.
Drivers::Net::E1000::ProcessDeferredWork(); if (Drivers::Net::E1000::HasDeferredWork())
Drivers::Net::E1000E::ProcessDeferredWork(); Drivers::Net::E1000::ProcessDeferredWork();
if (Drivers::Net::E1000E::HasDeferredWork())
Drivers::Net::E1000E::ProcessDeferredWork();
// HDA completion IRQs only acknowledge/mask. Resampling and DMA-ring // HDA completion IRQs only acknowledge/mask. Resampling and DMA-ring
// refill are far too expensive for hard interrupt context. // 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(); Drivers::Audio::IntelHda::ProcessDeferredWork();
} }
@@ -260,7 +285,10 @@ namespace Timekeeping {
// seconds and used to stall kmain before the first process spawned. // seconds and used to stall kmain before the first process spawned.
// Cheap no-op unless an adapter is waiting; self-claiming, and safe // Cheap no-op unless an adapter is waiting; self-claiming, and safe
// to preempt (the scheduler saves/resumes the idle context). // 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, // Service Bluetooth inbound traffic (the headset's SDP/AVRCP queries,
// AVDTP commands, ACL flow-control credits) whenever a core idles. // 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, // writes got silence (observed: Bose re-dialing SDP during playback,
// queries never answered). Self-serializing and a cheap no-op when // queries never answered). Self-serializing and a cheap no-op when
// the adapter is down. // 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 // Wi-Fi mirrors the Bluetooth split: the firmware load needs the
// ramdisk, and the RX/notification ring must be drained outside hard // ramdisk, and the RX/notification ring must be drained outside hard
// interrupt context (the MSI handler only latches a flag). // interrupt context (the MSI handler only latches a flag).
Drivers::Net::Wifi::ServiceDeferredInit(); if (Drivers::Net::Wifi::HasDeferredWork()) {
Drivers::Net::Wifi::ServiceEvents(); Drivers::Net::Wifi::ServiceDeferredInit();
Drivers::Net::Wifi::ServiceEvents();
}
// Thermal policy records transitions during BSP maintenance; print // Thermal policy records transitions during BSP maintenance; print
// them from this explicitly non-interrupt idle path. // them from this explicitly non-interrupt idle path.
+6 -1
View File
@@ -11,9 +11,14 @@ namespace Timekeeping {
// Initialize the APIC timer: calibrate against PIT, start periodic interrupts // Initialize the APIC timer: calibrate against PIT, start periodic interrupts
void ApicTimerInitialize(); 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(); 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 // Reinitialize the APIC timer after S3 resume using the previously
// calibrated tick rate. Skips PIT calibration and IRQ registration // calibrated tick rate. Skips PIT calibration and IRQ registration
// (both survive in RAM). Only reprograms the timer hardware registers. // (both survive in RAM). Only reprograms the timer hardware registers.
+12 -7
View File
@@ -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) { 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); g_bootEpoch = DateToEpoch(Year, Month, Day, Hour, Minute, Second);
int offH = g_tzOffsetMinutes / 60; int64_t absoluteOffset = g_tzOffsetMinutes;
int offM = g_tzOffsetMinutes % 60; char sign = '+';
if (offM < 0) offM = -offM; if (absoluteOffset < 0) {
sign = '-';
absoluteOffset = -absoluteOffset;
}
Kt::KernelLogStream(INFO, "Timekeeping Service") << "Time zone: UTC" int offH = (int)(absoluteOffset / 60);
<< (offH >= 0 ? "+" : "") << offH int offM = (int)(absoluteOffset % 60);
<< (offM ? ":" : "") << (offM >= 10 ? "" : (offM ? "0" : "")) auto log = Kt::KernelLogStream(INFO, "Timekeeping Service");
<< (offM ? offM : 0); log << "Time zone: UTC" << sign << offH;
if (offM != 0)
log << ":" << (offM < 10 ? "0" : "") << offM;
} }
int64_t Timekeeping::GetUnixTimestamp() { int64_t Timekeeping::GetUnixTimestamp() {
-144
View File
@@ -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 Programming 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>
-144
View File
@@ -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 Programming 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

-236
View File
@@ -1,236 +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 userspace configuration and TOML API">
<title>Configuration and TOML - 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.75em; }
h2 { margin: 1.25em 0 0.5em; }
h3 { margin: 1em 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; }
pre {
background: #f4f4f4;
border: 1px solid #ccc;
padding: 0.75em;
overflow-x: auto;
}
code { font-family: monospace; }
@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 Programming Manual</a></li>
<li><a href="config.html" class="current">Configuration and TOML</a></li>
</ul>
</div>
<div class="main">
<div class="center">
<h1>Configuration and TOML</h1>
</div>
<hr>
<p>MontaukOS provides a small header-only C++ API for reading and writing
TOML configuration files from userspace. Include
<code>&lt;montauk/config.h&gt;</code> for file-backed configuration and
<code>&lt;montauk/toml.h&gt;</code> for the in-memory TOML document model.</p>
<h2>Configuration locations</h2>
<p>System configuration is stored in <code>0:/config</code>. The API accepts a
name without the <code>.toml</code> extension:</p>
<pre><code>0:/config/desktop.toml
0:/config/network.toml</code></pre>
<p>Per-user configuration is stored below the user directory:</p>
<pre><code>0:/users/&lt;username&gt;/config/&lt;name&gt;.toml</code></pre>
<h2>Loading and saving</h2>
<pre><code>#include &lt;montauk/config.h&gt;
auto doc = montauk::config::load("desktop");
const char* theme = doc.get_string("appearance.theme", "light");
montauk::config::set_string(&amp;doc, "appearance.theme", "dark");
int result = montauk::config::save("desktop", &amp;doc);
doc.destroy();</code></pre>
<p><code>load()</code> returns an initialized empty document if the file does
not exist. <code>save()</code> creates the configuration directory and returns
<code>0</code> on success or a negative value on error. Saving rewrites the
whole file.</p>
<h3>System configuration API</h3>
<pre><code>toml::Doc config::load(const char* name);
int config::save(const char* name, toml::Doc* doc);
int config::remove(const char* name);</code></pre>
<h3>Per-user configuration API</h3>
<pre><code>toml::Doc config::load_user(const char* username, const char* name);
int config::save_user(const char* username,
const char* name,
toml::Doc* doc);</code></pre>
<p>For example:</p>
<pre><code>auto doc = montauk::config::load_user("alice", "desktop");
bool clock24 = doc.get_bool("display.clock_24h", false);
montauk::config::set_bool(&amp;doc, "display.clock_24h", true);
montauk::config::save_user("alice", "desktop", &amp;doc);
doc.destroy();</code></pre>
<h2>Reading values</h2>
<p>Keys may use dotted paths corresponding to TOML tables:</p>
<pre><code>[server]
host = "pool.ntp.org"
port = 123
enabled = true</code></pre>
<pre><code>const char* host = doc.get_string("server.host", "localhost");
int64_t port = doc.get_int("server.port", 80);
bool enabled = doc.get_bool("server.enabled", false);</code></pre>
<p>Typed getters return their default when the key is absent or has another
type. The available value types are:</p>
<pre><code>toml::Type::String
toml::Type::Int
toml::Type::Bool
toml::Type::Array
toml::Type::Table</code></pre>
<p>Arrays and tables are returned as <code>toml::Value*</code>. Their children
are available through <code>value-&gt;array.items</code> and
<code>value-&gt;array.count</code>:</p>
<pre><code>auto* names = doc.get_array("server.names");
if (names) {
for (int i = 0; i &lt; names-&gt;array.count; ++i) {
auto* item = names-&gt;array.items[i];
if (item-&gt;type == montauk::toml::Type::String)
montauk::print(item-&gt;str);
}
}</code></pre>
<h2>Modifying documents</h2>
<pre><code>void config::set_string(toml::Doc* doc,
const char* key, const char* value);
void config::set_int(toml::Doc* doc,
const char* key, int64_t value);
void config::set_bool(toml::Doc* doc,
const char* key, bool value);
bool config::unset(toml::Doc* doc, const char* key);</code></pre>
<p>The setters overwrite an existing value or append a new one. The
<code>unset()</code> return value is <code>true</code> when a matching key was
removed.</p>
<h2>Parsing and serialization</h2>
<p>Use <code>toml::parse()</code> when TOML is already available in memory:</p>
<pre><code>const char* text =
"[server]\n"
"port = 8080\n"
"enabled = true\n";
auto doc = montauk::toml::parse(text);
int64_t port = doc.get_int("server.port");
doc.destroy();</code></pre>
<p>A document can be serialized to newly allocated TOML text:</p>
<pre><code>char* text = montauk::config::serialize(&amp;doc);
// Use text...
montauk::mfree(text);</code></pre>
<p>Serialization produces normalized TOML and does not preserve comments or
the original formatting.</p>
<h2>Memory ownership</h2>
<p><code>toml::Doc</code> owns its parsed values and strings. Every document
returned by <code>load()</code>, <code>load_user()</code>, or
<code>toml::parse()</code> must eventually be released with
<code>doc.destroy()</code>.</p>
<p>When constructing a document manually, initialize it before using the
mutation helpers:</p>
<pre><code>montauk::toml::Doc doc;
doc.init();
montauk::config::set_bool(&amp;doc, "enabled", true);
montauk::config::save("example", &amp;doc);
doc.destroy();</code></pre>
<h2>Supported TOML features</h2>
<p>The userspace parser supports strings, literal and multiline strings,
integers (including hexadecimal, octal, and binary forms), booleans, arrays,
tables, inline tables, dotted keys, and comments.</p>
<p>There are no typed float or datetime accessors. Callers should also treat
configuration names and usernames as safe path components, since they are
used to construct filesystem paths.</p>
<hr>
<div class="center">
<a href="index.html">Back to Application Programming Manual</a>
</div>
</div>
</body>
</html>
-291
View File
@@ -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 Programming 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 &lt;gui/dialogs.hpp&gt;
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 &lt;gui/dialogs.hpp&gt;</code></pre><br>
<hr>
<p class="center">Copyright &copy; 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>
-174
View File
@@ -1,174 +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 Programming Manual">
<title>Application Programming 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 Programming Manual</a></li>
</ul>
</div>
<div class="main">
<div class="center">
<h1>Application Programming 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="config.html">Configuration and TOML</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>
-144
View File
@@ -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 Programming 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>
-144
View File
@@ -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 Programming 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>
-144
View File
@@ -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 Programming 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>
-144
View File
@@ -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 Programming 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>
-144
View File
@@ -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 Programming 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>
-156
View File
@@ -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 Programming Manual</a>
</li>
<li>
<a href="osdev/index.html">Operating System Development Manual</a>
</li>
</ul>
<hr>
<div class="center">
<a href="../index.html">Back to Home</a>
</div>
</div>
</body>
</html>
-166
View File
@@ -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>
-169
View File
@@ -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>
-180
View File
@@ -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] &lt;url&gt;
fetch [-v] &lt;host&gt; &lt;port&gt; [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>
-220
View File
@@ -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 "&lt;drive&gt;:/&lt;path&gt;".
<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 &lt; size) {
uint64_t chunk = size - off;
if (chunk &gt; 511) chunk = 511;
int n = montauk::read(h, buf, off, chunk);
if (n &lt;= 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>
-161
View File
@@ -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 &lt;n&gt;
fontscale &lt;x&gt; &lt;y&gt;
<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>
-180
View File
@@ -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(&amp;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(&amp;fb);
uint32_t* pixels = (uint32_t*)montauk::fb_map();
for (uint64_t y = 0; y &lt; fb.height; y++) {
uint32_t* row = (uint32_t*)((uint8_t*)pixels + y * fb.pitch);
for (uint64_t x = 0; x &lt; 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>
-264
View File
@@ -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>
-157
View File
@@ -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>
-212
View File
@@ -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 &lt;montauk/syscall.h&gt;
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 &amp;&amp; make
System/CLI binaries appear in programs/bin/os/; GUI app bundles
(ELF + manifest.toml + icon) appear under programs/bin/apps/&lt;name&gt;/.
<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
(&lt;app&gt;.elf + manifest.toml + icon)
0:/config/ System-wide config TOMLs
0:/users/&lt;name&gt;/ 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>
-177
View File
@@ -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>
-175
View File
@@ -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 &lt;montauk/heap.h&gt; 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>
-167
View File
@@ -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/&lt;topic&gt;.&lt;section&gt;
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>
-154
View File
@@ -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 &lt;hostname&gt;
<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>
-155
View File
@@ -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 &lt;host&gt;
<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>
-150
View File
@@ -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>
-130
View File
@@ -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>
-313
View File
@@ -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. &lt;cwd&gt;/&lt;command&gt; (exact name, e.g. "hello.elf")
2. &lt;cwd&gt;/&lt;command&gt;.elf
3. 0:/os/&lt;command&gt;.elf
4. 0:/os/&lt;command&gt; (no extension)
5. If on a non-zero drive, the drive root: &lt;drive&gt;:/&lt;command&gt;[.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:
&lt;command&gt;: 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/&lt;user&gt;); 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
&amp;&amp; and ||.
<strong>N:</strong>
A bare "&lt;number&gt;:" (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/&lt;user&gt;) when followed by end-of-string, '/', or a
space.
<strong>Command chaining</strong>
cmd1 ; cmd2 Run cmd2 unconditionally after cmd1
cmd1 &amp;&amp; cmd2 Run cmd2 only if cmd1 succeeded (exit 0)
cmd1 || cmd2 Run cmd2 only if cmd1 failed (nonzero exit)
Single and double quotes protect ;, &amp;&amp;, 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 &lt;command&gt;' for details.
<strong>File commands</strong>
cat &lt;file&gt; Display file contents
edit [file] Text editor -- see edit(1)
copy &lt;src&gt; &lt;dst&gt; Copy a file
move &lt;src&gt; &lt;dst&gt; Move/rename a file
rm &lt;file&gt; Remove a file
touch &lt;file&gt; Create an empty file
<strong>System commands</strong>
man &lt;topic&gt; 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 &lt;host&gt; Send ICMP echo requests -- see ping(1)
nslookup &lt;host&gt; DNS lookup -- see nslookup(1)
ifconfig Show/set network configuration
tcpconnect &lt;host&gt; &lt;port&gt; Interactive TCP client
irc IRC client
dhcp DHCP client -- see dhcp(1)
fetch &lt;url&gt; HTTP/HTTPS client (TLS 1.2) -- see fetch(1)
wiki &lt;title&gt; 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:/&gt; _ (at root of drive 0)
0:/os&gt; _ (in os/ directory)
1:/&gt; _ (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>
-187
View File
@@ -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 &lt; 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>
-906
View File
@@ -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 &lt;Api/Syscall.hpp&gt; for the numeric SYS_* constants and
ABI structs, and &lt;montauk/syscall.h&gt; 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 &lt;&lt; 32 | scale_x). When
scale_x is non-zero, sets the font scale and returns the new
terminal dimensions as (rows &lt;&lt; 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)&amp;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 &gt; 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>
-355
View File
@@ -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>
-182
View File
@@ -1,182 +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: wiki(1) - Wikipedia article viewer for MontaukOS">
<title>wiki(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>wiki(1)</h1>
</div>
<hr>
<pre><code><strong>NAME</strong>
wiki - Wikipedia article viewer for MontaukOS
<strong>SYNOPSIS</strong>
wiki &lt;title&gt;
wiki -f &lt;title&gt;
wiki -s &lt;query&gt;
<strong>DESCRIPTION</strong>
wiki fetches and displays Wikipedia articles in the terminal.
It connects to en.wikipedia.org over HTTPS (TLS 1.2) and
uses the Wikipedia REST and Action APIs to retrieve article
content as plain text.
Articles are displayed in a fullscreen interactive pager with
color-coded headings and word-wrapped text. Multi-word titles
are accepted as separate arguments and joined automatically.
<strong>OPTIONS</strong>
<strong>-f</strong>
Full article mode. Display the complete article text instead
of just the summary. Section headings are color-coded.
<strong>-s</strong>
Search mode. Search Wikipedia for articles matching the
query and display a numbered list of up to 10 results.
Press a number key to view that article's summary.
<strong>EXAMPLES</strong>
wiki Linux
Show a summary of the Linux article.
wiki -f C programming language
Show the full text of the C programming language article.
wiki -s operating system
Search for articles related to "operating system".
<strong>TLS SUPPORT</strong>
Connections use BearSSL for TLS 1.2. Server certificates
are validated against the system CA bundle at
0:/os/certs/ca-certificates.crt.
<strong>KEYBOARD</strong>
<strong>Article pager</strong>
j / Down Scroll down one line
k / Up Scroll up one line
Space / PgDn Scroll down one page
b / PgUp Scroll up one page
g / Home Jump to top
G / End Jump to bottom
q Quit pager
<strong>Search results</strong>
1-9, 0 View article (0 = result 10)
q Quit search
<strong>General</strong>
Ctrl+Q Abort during network request
<strong>SEE ALSO</strong>
fetch(1), ping(1), nslookup(1), shell(1)</code></pre>
<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: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

-144
View File
@@ -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 - Bootloader contract">
<title>Bootloader contract - 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">OS Development Manual</a></li>
<li><a href="bootloader.html" class="current">Bootloader contract</a></li>
</ul>
</div>
<div class="main">
<div class="center">
<h1>Bootloader contract</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>
-144
View File
@@ -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 - Compositor">
<title>Compositor - 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">OS Development Manual</a></li>
<li><a href="compositor.html" class="current">Compositor</a></li>
</ul>
</div>
<div class="main">
<div class="center">
<h1>Compositor</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>
-157
View File
@@ -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 - MontaukOS Desktop">
<title>MontaukOS Desktop - 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">OS Development Manual</a></li>
<li><a href="desktop.html" class="current">MontaukOS Desktop</a></li>
</ul>
</div>
<div class="main">
<div class="center">
<h1>MontaukOS Desktop</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="compositor.html">Compositor</a>
</li>
<li>
<a href="panel.html">Panel</a>
</li>
<li>
<a href="files.html">Files app</a>
</li>
</ul>
<hr>
<div class="center">
<a href="../index.html">Back to Documentation Index</a>
</div>
</div>
</body>
</html>
-200
View File
@@ -1,200 +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 Files app (File Manager)">
<title>Files app - 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;
}
.stub {
border: 1px solid #999;
background: #f0f0f0;
padding: 0.75em;
margin: 1em 0;
}
.stub p { margin: 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">OS Development Manual</a></li>
<li><a href="files.html" class="current">Files app</a></li>
</ul>
</div>
<div class="main">
<div class="center">
<h1>File Manager</h1>
</div>
<hr>
<h2>Overview</h2>
<p>
The Files app provides an interface that allows users of MontaukOS to:
</p>
<ul>
<li>Navigate and manage files on available volumes</li>
<li>View and access user libraries</li>
<li>View and access installed applications</li>
<li>View and access installed system configuration applets</li>
</ul>
<br>
<div class="figure">
<img src="assets/files.png" alt="Files app displaying Computer view with user libraries, ramdisk volume, Settings, and Apps folder.">
<p>Files app displaying Computer view with user libraries, ramdisk volume, Settings, and Apps folder.</p>
</div>
<hr>
<h2>Technical notes</h2>
<ul>
<li>The Files app is one of two windowed applications (the other being the Desktop Settings applet) compiled directly into the desktop's binary (<i>0:/os/desktop.elf</i>) rather than being a separate application. Crashes of the Files app may therefore cause the entire desktop process to crash, kicking the user back to the login screen (<i>0:/os/login.elf</i>).</li>
<br>
<li>Most of the Files app does not use the MTK toolkit, relying on custom GUI views to render complex file views; however, Properties and delete confirmation windows within the Files app do rely on the MTK toolkit.</li>
</ul>
<br>
<hr>
<h2>Files virtual folders</h2>
<h3>Apps folder</h3>
<div class="figure">
<img src="assets/files_apps_view.png" alt="Apps folder in the Files app displaying installed applications on a MontaukOS development build.">
<p>Apps folder in the Files app displaying installed applications on a MontaukOS development build.</p>
</div>
<h3>Settings folder</h3>
<div class="stub">
<p><b>This section 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>
<br>
<hr>
<p class="center">Copyright &copy; 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>
-144
View File
@@ -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 - Hardware abstraction">
<title>Hardware abstraction - 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">OS Development Manual</a></li>
<li><a href="hal.html" class="current">Hardware abstraction</a></li>
</ul>
</div>
<div class="main">
<div class="center">
<h1>Hardware abstraction</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>
-190
View File
@@ -1,190 +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 Operating System Development Manual">
<title>Operating System Development 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">OS Development Manual</a></li>
</ul>
</div>
<div class="main">
<div class="center">
<h1>Operating System Development 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>Kernel architecture</h2>
<ul class="doc-list">
<li>
<a href="bootloader.html">Bootloader contract</a>
</li>
<li>
<a href="smp.html">SMP</a>
</li>
<li>
<a href="vfs.html">Virtual File System (VFS)</a>
</li>
<li>
<a href="syscalls.html">System calls</a>
</li>
<li>
<a href="ipc.html">IPC</a>
</li>
<li>
<a href="hal.html">Hardware abstraction</a>
</li>
<li>
<a href="networking.html">Networking</a>
</li>
<li>
<a href="power.html">Power management</a>
</li>
<li>
<a href="winserver.html">Window Server</a>
</li>
</ul>
<h2>Userspace architecture</h2>
<ul class="doc-list">
<li>
<a href="init.html">Init system</a>
</li>
<li>
<a href="shell.html">MontaukOS Shell</a>
</li>
<li>
<a href="desktop.html">MontaukOS Desktop</a>
</li>
<li>
<a href="multiuser.html">Multi-user system</a>
</li>
</ul>
<hr>
<div class="center">
<a href="../index.html">Back to Documentation Index</a>
</div>
</div>
</body>
</html>
-144
View File
@@ -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 - Init system">
<title>Init system - 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">OS Development Manual</a></li>
<li><a href="init.html" class="current">Init system</a></li>
</ul>
</div>
<div class="main">
<div class="center">
<h1>Init system</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>
-144
View File
@@ -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 - IPC">
<title>IPC - 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">OS Development Manual</a></li>
<li><a href="ipc.html" class="current">IPC</a></li>
</ul>
</div>
<div class="main">
<div class="center">
<h1>IPC</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>
-144
View File
@@ -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 - Multi-user system">
<title>Multi-user system - 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">OS Development Manual</a></li>
<li><a href="multiuser.html" class="current">Multi-user system</a></li>
</ul>
</div>
<div class="main">
<div class="center">
<h1>Multi-user system</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>
-144
View File
@@ -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 - Networking">
<title>Networking - 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">OS Development Manual</a></li>
<li><a href="networking.html" class="current">Networking</a></li>
</ul>
</div>
<div class="main">
<div class="center">
<h1>Networking</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>
-144
View File
@@ -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 - Panel">
<title>Panel - 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">OS Development Manual</a></li>
<li><a href="panel.html" class="current">Panel</a></li>
</ul>
</div>
<div class="main">
<div class="center">
<h1>Panel</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>
-144
View File
@@ -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 - Power management">
<title>Power 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">OS Development Manual</a></li>
<li><a href="power.html" class="current">Power management</a></li>
</ul>
</div>
<div class="main">
<div class="center">
<h1>Power 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>
-144
View File
@@ -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 - MontaukOS Shell">
<title>MontaukOS Shell - 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">OS Development Manual</a></li>
<li><a href="shell.html" class="current">MontaukOS Shell</a></li>
</ul>
</div>
<div class="main">
<div class="center">
<h1>MontaukOS Shell</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>
-144
View File
@@ -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 - SMP">
<title>SMP - 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">OS Development Manual</a></li>
<li><a href="smp.html" class="current">SMP</a></li>
</ul>
</div>
<div class="main">
<div class="center">
<h1>SMP</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>

Some files were not shown because too many files have changed in this diff Show More