Compare commits

...
14 Commits
95 changed files with 5410 additions and 2232 deletions
+1 -1
View File
@@ -12,4 +12,4 @@
#pragma once #pragma once
#define MONTAUK_BUILD_NUMBER 150 #define MONTAUK_BUILD_NUMBER 184
+14
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;
@@ -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;
+94 -23
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,12 +204,29 @@ 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);
} }
-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);
}
}
+96 -22
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
@@ -116,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;
@@ -150,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;
@@ -188,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;
@@ -210,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);
@@ -247,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:
@@ -302,6 +330,7 @@ namespace montauk::abi {
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_LOG: 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::ReadKernelLogBuffer((char*)frame->arg1, frame->arg2); return Kt::ReadKernelLogBuffer((char*)frame->arg1, frame->arg2);
case SYS_MOUSESTATE: case SYS_MOUSESTATE:
@@ -316,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;
@@ -363,6 +400,7 @@ namespace montauk::abi {
case SYS_SETSESSION: case SYS_SETSESSION:
return (int64_t)Sys_SetSession(); return (int64_t)Sys_SetSession();
case SYS_KILLSESSION: case SYS_KILLSESSION:
if (!Sched::HasCapability(CAP_PROCESS_ADMIN)) return SYS_ERR_PERMISSION;
return (int64_t)Sys_KillSession((int)frame->arg1); return (int64_t)Sys_KillSession((int)frame->arg1);
case SYS_DEVLIST: case SYS_DEVLIST:
if ((int64_t)frame->arg2 < 0) return -1; if ((int64_t)frame->arg2 < 0) return -1;
@@ -388,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:
@@ -420,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);
@@ -454,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:
@@ -471,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:
@@ -481,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);
@@ -488,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);
@@ -508,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:
@@ -655,7 +729,7 @@ namespace montauk::abi {
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 << ", " << kcp::hex << (uint64_t)SyscallEntry << kcp::dec << ", "
<< (SYS_TERMINAL_ATTACHED + 1) << " syscall slots)"; << (SYS_SPAWN_REDIR_CAPS + 1) << " syscall slots)";
} }
} }
+135 -37
View File
@@ -275,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
@@ -327,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;
@@ -622,6 +700,9 @@ namespace montauk::abi {
char name[64]; char name[64];
uint64_t heapUsed; // Distance from UserHeapBase to high-water mark uint64_t heapUsed; // Distance from UserHeapBase to high-water mark
uint64_t cpuTimeMs; // accumulated scheduler runtime uint64_t cpuTimeMs; // accumulated scheduler runtime
uint64_t permittedCaps;
uint64_t effectiveCaps;
uint64_t delegableCaps;
}; };
// Bluetooth scan result (returned by SYS_BTSCAN) // Bluetooth scan result (returned by SYS_BTSCAN)
@@ -658,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;
+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);
}
}
+4
View File
@@ -5,6 +5,8 @@
* Further copyright information and third party notices can be found at https://montaukos.org/license.txt. * 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>
@@ -174,6 +176,8 @@ extern "C" void kmain() {
montauk::abi::InitializeSyscalls(); montauk::abi::InitializeSyscalls();
Sched::Initialize(); Sched::Initialize();
Memory::InitUserRange();
Fs::LogProtectedPaths();
Ipc::Initialize(); Ipc::Initialize();
#if defined (__x86_64__) #if defined (__x86_64__)
-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);
}
-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;
+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);
}
+52 -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,9 @@ 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].sessionId = -1;
processTable[i].cwd[0] = '\0'; processTable[i].cwd[0] = '\0';
processTable[i].runningOnCpu = -1; processTable[i].runningOnCpu = -1;
@@ -341,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;
@@ -546,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];
@@ -683,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";
@@ -710,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);
} }
@@ -736,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);
} }
@@ -884,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);
} }
@@ -1417,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;
@@ -1607,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);
+10 -1
View File
@@ -86,6 +86,9 @@ namespace Sched {
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) int sessionId; // Process-session leader PID (inherited on spawn)
char cwd[256]; // Absolute current working directory char cwd[256]; // Absolute current working directory
@@ -144,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();
@@ -172,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();
+12 -3
View File
@@ -49,7 +49,7 @@ BINDIR := bin
PROGRAMS := $(notdir $(wildcard src/*)) PROGRAMS := $(notdir $(wildcard src/*))
# Programs with custom Makefiles (built separately). # Programs with custom Makefiles (built separately).
CUSTOM_BUILDS := 2048 fetch wiki wikipedia weather imageviewer fontpreview spreadsheet wordprocessor pdfviewer disks devexplorer installer audio music video bluetooth network display keyboard sshserver terminal syslog procmgr powermgr calculator charmap desktop login shell paint tcc lua screenshot texteditor mandelbrot printers timezone printd printctl dialogs libloader crashpad sshd CUSTOM_BUILDS := 2048 fetch wiki wikipedia weather imageviewer fontpreview spreadsheet wordprocessor pdfviewer disks devexplorer installer audio music video bluetooth network display keyboard sshserver terminal syslog procmgr powermgr calculator charmap desktop login shell paint tcc lua screenshot texteditor mandelbrot printers timezone printd printctl dialogs libloader crashpad sshd rtlsdr sdr
SYSTEM_PROGRAMS := $(filter-out $(CUSTOM_BUILDS),$(PROGRAMS)) SYSTEM_PROGRAMS := $(filter-out $(CUSTOM_BUILDS),$(PROGRAMS))
# Build targets: system programs go to bin/os/, apps go to bin/apps/<name>/. # Build targets: system programs go to bin/os/, apps go to bin/apps/<name>/.
@@ -96,9 +96,9 @@ WPDIR := data/wallpapers
WPSRC := $(wildcard $(WPDIR)/*.jpg) WPSRC := $(wildcard $(WPDIR)/*.jpg)
WPDST := $(patsubst $(WPDIR)/%,$(BINDIR)/os/wallpapers/%,$(WPSRC)) WPDST := $(patsubst $(WPDIR)/%,$(BINDIR)/os/wallpapers/%,$(WPSRC))
.PHONY: all clean 2048 fetch wiki wikipedia weather imageviewer fontpreview spreadsheet wordprocessor pdfviewer disks devexplorer installer audio music video bluetooth network display keyboard sshserver sshd terminal syslog procmgr powermgr calculator charmap login desktop shell paint tcc lua screenshot texteditor mandelbrot printers timezone printd printctl dialogs icons fonts configs osdata bearssl libc tls libjpeg libjpegwrite install-apps libloader crashpad check-syscalls gen-syscalls .PHONY: all clean 2048 fetch wiki wikipedia weather imageviewer fontpreview spreadsheet wordprocessor pdfviewer disks devexplorer installer audio music video bluetooth network display keyboard sshserver sshd terminal syslog procmgr powermgr calculator charmap login desktop shell paint tcc lua screenshot texteditor mandelbrot printers timezone printd printctl dialogs icons fonts configs osdata bearssl libc tls libjpeg libjpegwrite install-apps libloader crashpad rtlsdr sdr check-syscalls gen-syscalls
all: bearssl libc libjpeg libjpegwrite tls libloader devkit $(TARGETS) fetch wiki wikipedia weather imageviewer fontpreview spreadsheet wordprocessor pdfviewer disks devexplorer installer audio music video bluetooth network display sshserver terminal syslog procmgr powermgr calculator charmap 2048 paint tcc lua screenshot texteditor mandelbrot printers timezone printd printctl dialogs login desktop shell sshd icons fonts install-apps crashpad $(MANDST) $(WWWDST) $(CA_CERTS) $(CONFIGDST) $(OSDATADST) $(FWDST) $(LICDST) $(WPDST) all: bearssl libc libjpeg libjpegwrite tls libloader rtlsdr sdr devkit $(TARGETS) fetch wiki wikipedia weather imageviewer fontpreview spreadsheet wordprocessor pdfviewer disks devexplorer installer audio music video bluetooth network display sshserver terminal syslog procmgr powermgr calculator charmap 2048 paint tcc lua screenshot texteditor mandelbrot printers timezone printd printctl dialogs login desktop shell sshd icons fonts install-apps crashpad $(MANDST) $(WWWDST) $(CA_CERTS) $(CONFIGDST) $(OSDATADST) $(FWDST) $(LICDST) $(WPDST)
# Build BearSSL static library (cross-compiled for freestanding x86_64). # Build BearSSL static library (cross-compiled for freestanding x86_64).
BEARSSL_INCLUDES := -isystem $(shell cd .. && pwd)/kernel/freestnd-c-hdrs/x86_64/include -isystem $(abspath include/libc) BEARSSL_INCLUDES := -isystem $(shell cd .. && pwd)/kernel/freestnd-c-hdrs/x86_64/include -isystem $(abspath include/libc)
@@ -139,6 +139,12 @@ tls: bearssl libc
libloader: libc libloader: libc
$(MAKE) -C src/libloader $(MAKE) -C src/libloader
rtlsdr: libc
$(MAKE) -C src/rtlsdr
sdr: libc libloader rtlsdr
$(MAKE) -C src/sdr
# Build fetch via its own Makefile (depends on bearssl, libc, and tls). # Build fetch via its own Makefile (depends on bearssl, libc, and tls).
fetch: bearssl libc tls fetch: bearssl libc tls
$(MAKE) -C src/fetch $(MAKE) -C src/fetch
@@ -423,6 +429,7 @@ ifneq ($(wildcard $(NATIVE_BIN)/as),)
cp -r include/libc/. $(BINDIR)/sdk/include/ cp -r include/libc/. $(BINDIR)/sdk/include/
cp -r include/montauk $(BINDIR)/sdk/include/montauk cp -r include/montauk $(BINDIR)/sdk/include/montauk
cp -r include/Api $(BINDIR)/sdk/include/Api cp -r include/Api $(BINDIR)/sdk/include/Api
cp -r include/rtlsdr $(BINDIR)/sdk/include/rtlsdr
cp -r ../kernel/freestnd-cxx-hdrs/x86_64/include/. $(BINDIR)/sdk/include/ cp -r ../kernel/freestnd-cxx-hdrs/x86_64/include/. $(BINDIR)/sdk/include/
# liblibc-full.a, not liblibc.a: on-OS builds cannot pass # liblibc-full.a, not liblibc.a: on-OS builds cannot pass
# -Wl,-u,_pf_putfloat, so the sysroot ships the variant with the printf # -Wl,-u,_pf_putfloat, so the sysroot ships the variant with the printf
@@ -512,3 +519,5 @@ clean:
$(MAKE) -C src/dialogs clean $(MAKE) -C src/dialogs clean
$(MAKE) -C src/crashpad clean $(MAKE) -C src/crashpad clean
$(MAKE) -C src/libloader clean $(MAKE) -C src/libloader clean
$(MAKE) -C src/rtlsdr clean
$(MAKE) -C src/sdr clean
+126
View File
@@ -0,0 +1,126 @@
# MontaukOS capability grants
#
#
# Capability names: process_admin, power_request, power_control, suspend,
# storage_admin, raw_storage, network_admin, set_time, user_admin,
# display_admin, device_admin, log_read, system_image, and "all".
#
# "all" is every capability except system_image, which must always be named
# explicitly: it is write access to 0:/os and 0:/apps, and since grants are
# keyed on binary path, holding it is equivalent to holding every capability
# the system can issue from the next launch onwards. Nothing is granted it.
#
# A grant is always clamped by the kernel to what the launching process may
# actually delegate, so "all" in a launcher entry means "whatever this session
# was given", not "root".
#
#
# ==== System services (started by init) ====
[grant.dhcp]
path = "0:/os/dhcp.elf"
effective = ["network_admin"]
[grant.ntp]
path = "0:/os/ntp.elf"
effective = ["set_time"]
[grant.login]
path = "0:/os/login.elf"
permitted = ["all"]
effective = [
"power_control", "storage_admin", "device_admin",
"user_admin", "process_admin", "log_read",
]
delegable = [
"power_request", "suspend", "process_admin", "storage_admin",
"raw_storage", "network_admin", "set_time", "user_admin",
"display_admin", "device_admin", "log_read",
]
[grant.sshd]
path = "0:/os/sshd.elf"
effective = ["user_admin"]
[grant.desktop]
path = "0:/os/desktop.elf"
effective = [
"power_request", "suspend", "process_admin", "storage_admin",
"raw_storage", "network_admin", "set_time", "user_admin",
"display_admin", "device_admin", "log_read",
]
delegable = [
"power_request", "suspend", "process_admin", "storage_admin",
"raw_storage", "network_admin", "set_time", "user_admin",
"display_admin", "device_admin", "log_read",
]
# The console session launchers. terminal.elf needs no authority of its own;
# it exists to pass the session's authority to the shell it hosts. The shell
# in turn exercises only power_request and suspend, but delegates on the same
# terms as the desktop so that the tools below work from a console. Both are
# clamped to the launching session: a standard session narrows this to
# power_request and suspend, and an unprivileged one to nothing.
[grant.terminal]
path = "0:/apps/terminal/terminal.elf"
delegable = ["all"]
[grant.shell]
path = "0:/os/shell.elf"
effective = ["power_request", "suspend"]
delegable = ["all"]
# ==== Settings and administrative applications ====
[grant.procmgr]
path = "0:/apps/procmgr/procmgr.elf"
effective = ["process_admin"]
# May restart the DHCP client, so it needs to pass network_admin on.
[grant.network]
path = "0:/apps/network/network.elf"
effective = ["network_admin"]
delegable = ["network_admin"]
[grant.display]
path = "0:/apps/display/display.elf"
effective = ["display_admin"]
[grant.disks]
path = "0:/apps/disks/disks.elf"
effective = ["storage_admin", "raw_storage"]
[grant.installer]
path = "0:/apps/installer/installer.elf"
effective = ["storage_admin", "raw_storage"]
[grant.timezone]
path = "0:/apps/timezone/timezone.elf"
effective = ["set_time"]
[grant.bluetooth]
path = "0:/apps/bluetooth/bluetooth.elf"
effective = ["device_admin"]
[grant.syslog]
path = "0:/apps/syslog/syslog.elf"
effective = ["log_read"]
[grant.sshserver]
path = "0:/apps/sshserver/sshserver.elf"
effective = ["user_admin"]
# ==== Console tools ====
[grant.ifconfig]
path = "0:/os/ifconfig.elf"
effective = ["network_admin"]
[grant.wifi]
path = "0:/os/wifi.elf"
effective = ["network_admin"]
[grant.sdr]
path = "0:/os/sdr.elf"
effective = ["device_admin"]
+127 -37
View File
@@ -191,16 +191,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;
// CPU power/thermal status // 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
@@ -239,28 +239,102 @@ 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;
/* Generic userspace USB interface access */
static constexpr uint64_t SYS_USB_LIST = 178;
static constexpr uint64_t SYS_USB_CLAIM = 179;
static constexpr uint64_t SYS_USB_CLOSE = 180;
static constexpr uint64_t SYS_USB_CONTROL = 181;
static constexpr uint64_t SYS_USB_BULK_IN_START = 182;
static constexpr uint64_t SYS_USB_BULK_IN_STOP = 183;
static constexpr uint64_t SYS_USB_BULK_IN_READ = 184;
static constexpr uint64_t SYS_LOG_WRITE = 176; // (logMessage) -> 0 static constexpr uint64_t SYS_LOG_WRITE = 176; // (logMessage) -> 0
static constexpr uint64_t SYS_TERMINAL_ATTACHED = 177; // () -> 1 when connected to a userspace terminal static constexpr uint64_t SYS_TERMINAL_ATTACHED = 177; // () -> 1 when connected to a userspace terminal
static constexpr uint64_t SYS_SPAWN_CAPS = 185;
static constexpr uint64_t SYS_SPAWN_REDIR_CAPS = 186;
// As SYS_ALLOC, but commits every page up front instead of faulting them
// in one at a time. For buffers the caller is about to touch in full.
static constexpr uint64_t SYS_ALLOC_EAGER = 187; // (bytes) -> va, 0 on failure
// Tunable parameters (for SYS_SDR_SETPARAM / SYS_SDR_GETPARAM). /* Kernel-owned process capabilities. User identities may namespace
static constexpr int SDR_PARAM_FREQ = 0; // center frequency, Hz per-user resources, but never participate in authorization decisions. */
static constexpr int SDR_PARAM_SAMPLE_RATE = 1; // sample rate, Hz static constexpr uint64_t CAP_PROCESS_ADMIN = 1ULL << 0;
static constexpr int SDR_PARAM_GAIN_MODE = 2; // 0 = auto/AGC, 1 = manual static constexpr uint64_t CAP_POWER_REQUEST = 1ULL << 1;
static constexpr int SDR_PARAM_GAIN = 3; // tuner gain, tenths of dB static constexpr uint64_t CAP_POWER_CONTROL = 1ULL << 2;
static constexpr int SDR_PARAM_FREQ_CORR = 4; // frequency correction, ppm static constexpr uint64_t CAP_SUSPEND = 1ULL << 3;
static constexpr int SDR_PARAM_AGC = 5; // demod digital AGC, 0/1 static constexpr uint64_t CAP_STORAGE_ADMIN = 1ULL << 4;
static constexpr int SDR_PARAM_DIRECT_SAMP = 6; // direct sampling: 0=off,1=I,2=Q 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;
// Sample formats reported in SdrDeviceInfo.sampleFormat. struct SpawnCapabilities {
static constexpr uint8_t SDR_FORMAT_CU8 = 0; // 8-bit unsigned interleaved I/Q 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));
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;
@@ -576,23 +650,36 @@ namespace montauk::abi {
uint8_t _pad[2]; uint8_t _pad[2];
}; };
// Software-defined radio receiver description (returned by SYS_SDR_INFO). struct UsbInterfaceInfo {
struct SdrDeviceInfo { uint8_t slotId;
char name[64]; // e.g. "Realtek RTL2832U" uint8_t portId;
char tuner[32]; // e.g. "Rafael Micro R820T2" uint8_t speed;
char serial[32]; // device serial / bus location uint8_t interfaceNumber;
uint64_t freqMin; // minimum tunable center frequency, Hz uint16_t vendorId;
uint64_t freqMax; // maximum tunable center frequency, Hz uint16_t productId;
uint32_t sampleRateMin; // minimum sample rate, Hz uint8_t deviceClass;
uint32_t sampleRateMax; // maximum sample rate, Hz uint8_t interfaceClass;
uint32_t numGains; // number of discrete tuner gain steps uint8_t interfaceSubClass;
int32_t gains[32]; // available gains, tenths of dB uint8_t interfaceProtocol;
uint8_t sampleFormat; // SDR_FORMAT_* uint8_t bulkInEndpoint;
uint8_t present; // 1 if the underlying hardware is connected uint8_t bulkOutEndpoint;
uint8_t streaming; // 1 if currently delivering samples uint16_t bulkInMaxPacket;
uint8_t _pad; uint16_t bulkOutMaxPacket;
uint32_t _pad2; uint8_t kernelDriverBound;
}; uint8_t claimed;
uint8_t _reserved[4];
} __attribute__((packed));
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;
@@ -709,6 +796,9 @@ namespace montauk::abi {
char name[64]; char name[64];
uint64_t heapUsed; // Distance from UserHeapBase to high-water mark uint64_t heapUsed; // Distance from UserHeapBase to high-water mark
uint64_t cpuTimeMs; // accumulated scheduler runtime uint64_t cpuTimeMs; // accumulated scheduler runtime
uint64_t permittedCaps;
uint64_t effectiveCaps;
uint64_t delegableCaps;
}; };
struct MemStats { struct MemStats {
+206 -23
View File
@@ -1,6 +1,6 @@
/* /*
* syntax_ansi.hpp * syntax_ansi.hpp
* ANSI terminal syntax highlighting for C and Lua * ANSI terminal syntax highlighting for C, C++ and Lua
* Used by the MontaukOS CLI text editor * Used by the MontaukOS CLI text editor
* Copyright (c) 2026 Daniel Hammer * Copyright (c) 2026 Daniel Hammer
*/ */
@@ -16,6 +16,7 @@
enum SynLanguage : uint8_t { enum SynLanguage : uint8_t {
SYN_LANG_NONE, SYN_LANG_NONE,
SYN_LANG_C, SYN_LANG_C,
SYN_LANG_CPP,
SYN_LANG_LUA, SYN_LANG_LUA,
}; };
@@ -31,10 +32,15 @@ enum SynToken : uint8_t {
SYN_OPERATOR, SYN_OPERATOR,
}; };
#define SYN_RAW_DELIM_MAX 16
struct SynState { struct SynState {
int in_block_comment; // 0 or 1 (bool stored as int for freestanding) int in_block_comment; // 0 or 1 (bool stored as int for freestanding)
SynToken long_token; SynToken long_token;
int long_bracket_eqs; int long_bracket_eqs;
bool in_raw_string;
char raw_delim[SYN_RAW_DELIM_MAX];
int raw_delim_len;
}; };
// ============================================================================ // ============================================================================
@@ -133,9 +139,45 @@ inline SynToken syn_classify_lua_word(const char* buf, int len) {
return SYN_NORMAL; return SYN_NORMAL;
} }
inline SynToken syn_classify_cpp_word(const char* buf, int len) {
// C++ keywords (on top of the C set, which is checked first)
static const char* keywords[] = {
"alignas", "alignof", "and", "and_eq", "asm", "bitand", "bitor",
"catch", "class", "compl", "concept", "consteval", "constexpr",
"constinit", "const_cast", "co_await", "co_return", "co_yield",
"decltype", "delete", "dynamic_cast", "explicit", "export",
"final", "friend", "mutable", "namespace", "new", "noexcept",
"not", "not_eq", "operator", "or", "or_eq", "override",
"private", "protected", "public", "reinterpret_cast", "requires",
"static_assert", "static_cast", "template", "this", "thread_local",
"throw", "try", "typeid", "typename", "using", "virtual",
"xor", "xor_eq",
};
// C++ library / builtin types
static const char* types[] = {
"char8_t", "char16_t", "char32_t", "wchar_t", "nullptr_t",
"std", "string", "string_view", "vector", "array", "span",
"map", "set", "unordered_map", "unordered_set", "deque", "list",
"pair", "tuple", "optional", "variant", "function",
"unique_ptr", "shared_ptr", "weak_ptr", "initializer_list",
};
SynToken c = syn_classify_c_word(buf, len);
if (c != SYN_NORMAL) return c;
for (int i = 0; i < (int)(sizeof(keywords) / sizeof(keywords[0])); i++) {
if (syn_streq(buf, len, keywords[i])) return SYN_KEYWORD;
}
for (int i = 0; i < (int)(sizeof(types) / sizeof(types[0])); i++) {
if (syn_streq(buf, len, types[i])) return SYN_TYPE;
}
return SYN_NORMAL;
}
inline SynToken syn_classify_word(SynLanguage lang, const char* buf, int len) { inline SynToken syn_classify_word(SynLanguage lang, const char* buf, int len) {
switch (lang) { switch (lang) {
case SYN_LANG_C: return syn_classify_c_word(buf, len); case SYN_LANG_C: return syn_classify_c_word(buf, len);
case SYN_LANG_CPP: return syn_classify_cpp_word(buf, len);
case SYN_LANG_LUA: return syn_classify_lua_word(buf, len); case SYN_LANG_LUA: return syn_classify_lua_word(buf, len);
default: return SYN_NORMAL; default: return SYN_NORMAL;
} }
@@ -146,6 +188,8 @@ inline SynState syn_make_state() {
state.in_block_comment = 0; state.in_block_comment = 0;
state.long_token = SYN_NORMAL; state.long_token = SYN_NORMAL;
state.long_bracket_eqs = -1; state.long_bracket_eqs = -1;
state.in_raw_string = false;
state.raw_delim_len = 0;
return state; return state;
} }
@@ -185,64 +229,157 @@ inline bool syn_match_lua_long_bracket_close(const char* line, int len, int i,
// Number consumption // Number consumption
// ============================================================================ // ============================================================================
inline void syn_scan_digits(const char* line, int len, int& i, bool sep, bool hex) {
while (i < len) {
if (hex ? syn_is_hex(line[i]) : syn_is_digit(line[i])) { i++; continue; }
// C++14 digit separator: a quote wedged between two digits
if (sep && line[i] == '\'' && i + 1 < len &&
(hex ? syn_is_hex(line[i + 1]) : syn_is_digit(line[i + 1]))) {
i += 2;
continue;
}
break;
}
}
inline void syn_consume_number(const char* line, int len, int& i, inline void syn_consume_number(const char* line, int len, int& i,
SynToken* out, int out_len, int c_style_suffixes) { SynToken* out, int out_len, bool c_style_suffixes,
bool digit_sep = false) {
int start = i; int start = i;
if (line[i] == '0' && i + 1 < len && (line[i + 1] == 'x' || line[i + 1] == 'X')) { if (line[i] == '0' && i + 1 < len && (line[i + 1] == 'x' || line[i + 1] == 'X')) {
i += 2; i += 2;
while (i < len && syn_is_hex(line[i])) i++; syn_scan_digits(line, len, i, digit_sep, true);
if (i < len && line[i] == '.' && !(i + 1 < len && line[i + 1] == '.')) { if (i < len && line[i] == '.' && !(i + 1 < len && line[i + 1] == '.')) {
i++; i++;
while (i < len && syn_is_hex(line[i])) i++; syn_scan_digits(line, len, i, digit_sep, true);
} }
if (i < len && (line[i] == 'p' || line[i] == 'P')) { if (i < len && (line[i] == 'p' || line[i] == 'P')) {
int exp = i + 1; int exp = i + 1;
if (exp < len && (line[exp] == '+' || line[exp] == '-')) exp++; if (exp < len && (line[exp] == '+' || line[exp] == '-')) exp++;
if (exp < len && syn_is_digit(line[exp])) { if (exp < len && syn_is_digit(line[exp])) {
i = exp + 1; i = exp;
while (i < len && syn_is_digit(line[i])) i++; syn_scan_digits(line, len, i, digit_sep, false);
} }
} }
} else { } else {
if (line[i] == '.') i++; if (line[i] == '.') i++;
while (i < len && syn_is_digit(line[i])) i++; syn_scan_digits(line, len, i, digit_sep, false);
if (i < len && line[i] == '.' && !(i + 1 < len && line[i + 1] == '.')) { if (i < len && line[i] == '.' && !(i + 1 < len && line[i + 1] == '.')) {
i++; i++;
while (i < len && syn_is_digit(line[i])) i++; syn_scan_digits(line, len, i, digit_sep, false);
} }
if (i < len && (line[i] == 'e' || line[i] == 'E')) { if (i < len && (line[i] == 'e' || line[i] == 'E')) {
int exp = i + 1; int exp = i + 1;
if (exp < len && (line[exp] == '+' || line[exp] == '-')) exp++; if (exp < len && (line[exp] == '+' || line[exp] == '-')) exp++;
if (exp < len && syn_is_digit(line[exp])) { if (exp < len && syn_is_digit(line[exp])) {
i = exp + 1; i = exp;
while (i < len && syn_is_digit(line[i])) i++; syn_scan_digits(line, len, i, digit_sep, false);
} }
} }
} }
if (c_style_suffixes) { if (c_style_suffixes) {
while (i < len && (line[i] == 'u' || line[i] == 'U' || while (i < len && (line[i] == 'u' || line[i] == 'U' ||
line[i] == 'l' || line[i] == 'L' || line[i] == 'l' || line[i] == 'L' ||
line[i] == 'f' || line[i] == 'F')) line[i] == 'f' || line[i] == 'F' ||
line[i] == 'z' || line[i] == 'Z'))
i++; i++;
} }
syn_fill_tokens(out, out_len, start, i, SYN_NUMBER); syn_fill_tokens(out, out_len, start, i, SYN_NUMBER);
} }
// ============================================================================ // ============================================================================
// C highlighting // C highlighting
// ============================================================================ // ============================================================================
// C / C++ raw string helpers
// ============================================================================
inline bool syn_is_raw_string_prefix(const char* buf, int len) {
return syn_streq(buf, len, "R") || syn_streq(buf, len, "LR") ||
syn_streq(buf, len, "uR") || syn_streq(buf, len, "UR") ||
syn_streq(buf, len, "u8R");
}
inline bool syn_is_string_prefix(const char* buf, int len) {
return syn_streq(buf, len, "L") || syn_streq(buf, len, "u") ||
syn_streq(buf, len, "U") || syn_streq(buf, len, "u8");
}
// Colors a raw-string body from `i` up to and including the closing )delim".
// If the terminator is not on this line, leaves state.in_raw_string set so the
// next line continues the literal.
inline void syn_consume_raw_body(const char* line, int len, int& i,
SynToken* out, int out_len, SynState& state) {
while (i < len) {
if (line[i] == ')') {
int j = i + 1;
int k = 0;
while (k < state.raw_delim_len && j < len && line[j] == state.raw_delim[k]) {
j++;
k++;
}
if (k == state.raw_delim_len && j < len && line[j] == '"') {
syn_fill_tokens(out, out_len, i, j + 1, SYN_STRING);
i = j + 1;
state.in_raw_string = false;
state.raw_delim_len = 0;
return;
}
}
syn_set_token(out, out_len, i, SYN_STRING);
i++;
}
}
// Enters a raw string; `line[i]` must be the opening quote.
inline void syn_consume_raw_string(const char* line, int len, int& i,
SynToken* out, int out_len, SynState& state) {
syn_set_token(out, out_len, i, SYN_STRING);
i++;
state.raw_delim_len = 0;
while (i < len && line[i] != '(' && state.raw_delim_len < SYN_RAW_DELIM_MAX) {
state.raw_delim[state.raw_delim_len++] = line[i];
syn_set_token(out, out_len, i, SYN_STRING);
i++;
}
if (i < len && line[i] == '(') {
syn_set_token(out, out_len, i, SYN_STRING);
i++;
state.in_raw_string = true;
syn_consume_raw_body(line, len, i, out, out_len, state);
return;
}
// Malformed (or an over-long delimiter): treat the rest of the line as string
syn_fill_tokens(out, out_len, i, len, SYN_STRING);
i = len;
}
// ============================================================================
// C / C++ highlighting
// ============================================================================
inline void syn_highlight_line_c(const char* line, int len, SynToken* out, int out_len, inline void syn_highlight_line_c(const char* line, int len, SynToken* out, int out_len,
SynState& state) { SynState& state, SynLanguage lang = SYN_LANG_C) {
const bool cpp = (lang == SYN_LANG_CPP);
int i = 0; int i = 0;
while (i < len) { while (i < len) {
// ---- Raw string continuation ----
if (state.in_raw_string) {
syn_consume_raw_body(line, len, i, out, out_len, state);
continue;
}
// ---- Block comment continuation ----
if (state.in_block_comment) { if (state.in_block_comment) {
while (i < len) { while (i < len) {
if (i + 1 < len && line[i] == '*' && line[i + 1] == '/') { if (i + 1 < len && line[i] == '*' && line[i + 1] == '/') {
syn_set_token(out, out_len, i, SYN_COMMENT); syn_set_token(out, out_len, i, SYN_COMMENT);
syn_set_token(out, out_len, i + 1, SYN_COMMENT); syn_set_token(out, out_len, i + 1, SYN_COMMENT);
i += 2; i += 2;
state.in_block_comment = 0; state.in_block_comment = false;
break; break;
} }
syn_set_token(out, out_len, i, SYN_COMMENT); syn_set_token(out, out_len, i, SYN_COMMENT);
@@ -250,40 +387,51 @@ inline void syn_highlight_line_c(const char* line, int len, SynToken* out, int o
} }
continue; continue;
} }
char c = line[i]; char c = line[i];
// ---- Line comment ----
if (c == '/' && i + 1 < len && line[i + 1] == '/') { if (c == '/' && i + 1 < len && line[i + 1] == '/') {
syn_fill_tokens(out, out_len, i, len, SYN_COMMENT); syn_fill_tokens(out, out_len, i, len, SYN_COMMENT);
break; break;
} }
// ---- Block comment start ----
if (c == '/' && i + 1 < len && line[i + 1] == '*') { if (c == '/' && i + 1 < len && line[i + 1] == '*') {
state.in_block_comment = 1; state.in_block_comment = true;
syn_set_token(out, out_len, i, SYN_COMMENT); syn_set_token(out, out_len, i, SYN_COMMENT);
syn_set_token(out, out_len, i + 1, SYN_COMMENT); syn_set_token(out, out_len, i + 1, SYN_COMMENT);
i += 2; i += 2;
continue; continue;
} }
// ---- Preprocessor directive ----
if (c == '#') { if (c == '#') {
int is_pp = 1; // Check that only whitespace precedes the #
bool is_pp = true;
for (int j = 0; j < i; j++) { for (int j = 0; j < i; j++) {
if (line[j] != ' ' && line[j] != '\t') { is_pp = 0; break; } if (line[j] != ' ' && line[j] != '\t') { is_pp = false; break; }
} }
if (is_pp) { if (is_pp) {
while (i < len) { while (i < len) {
// Handle line-comment inside preprocessor
if (i + 1 < len && line[i] == '/' && line[i + 1] == '/') { if (i + 1 < len && line[i] == '/' && line[i + 1] == '/') {
syn_fill_tokens(out, out_len, i, len, SYN_COMMENT); syn_fill_tokens(out, out_len, i, len, SYN_COMMENT);
break; break;
} }
// Handle block comment start inside preprocessor
if (i + 1 < len && line[i] == '/' && line[i + 1] == '*') { if (i + 1 < len && line[i] == '/' && line[i + 1] == '*') {
state.in_block_comment = 1; state.in_block_comment = true;
syn_set_token(out, out_len, i, SYN_COMMENT); syn_set_token(out, out_len, i, SYN_COMMENT);
syn_set_token(out, out_len, i + 1, SYN_COMMENT); syn_set_token(out, out_len, i + 1, SYN_COMMENT);
i += 2; i += 2;
// Continue consuming as comment
while (i < len) { while (i < len) {
if (i + 1 < len && line[i] == '*' && line[i + 1] == '/') { if (i + 1 < len && line[i] == '*' && line[i + 1] == '/') {
syn_set_token(out, out_len, i, SYN_COMMENT); syn_set_token(out, out_len, i, SYN_COMMENT);
syn_set_token(out, out_len, i + 1, SYN_COMMENT); syn_set_token(out, out_len, i + 1, SYN_COMMENT);
i += 2; i += 2;
state.in_block_comment = 0; state.in_block_comment = false;
break; break;
} }
syn_set_token(out, out_len, i, SYN_COMMENT); syn_set_token(out, out_len, i, SYN_COMMENT);
@@ -297,6 +445,8 @@ inline void syn_highlight_line_c(const char* line, int len, SynToken* out, int o
continue; continue;
} }
} }
// ---- String literal ----
if (c == '"') { if (c == '"') {
syn_set_token(out, out_len, i, SYN_STRING); syn_set_token(out, out_len, i, SYN_STRING);
i++; i++;
@@ -317,6 +467,8 @@ inline void syn_highlight_line_c(const char* line, int len, SynToken* out, int o
} }
continue; continue;
} }
// ---- Character literal ----
if (c == '\'') { if (c == '\'') {
syn_set_token(out, out_len, i, SYN_CHAR); syn_set_token(out, out_len, i, SYN_CHAR);
i++; i++;
@@ -337,17 +489,40 @@ inline void syn_highlight_line_c(const char* line, int len, SynToken* out, int o
} }
continue; continue;
} }
// ---- Numbers ----
if (syn_is_digit(c) || (c == '.' && i + 1 < len && syn_is_digit(line[i + 1]))) { if (syn_is_digit(c) || (c == '.' && i + 1 < len && syn_is_digit(line[i + 1]))) {
syn_consume_number(line, len, i, out, out_len, 1); syn_consume_number(line, len, i, out, out_len, true, cpp);
continue; continue;
} }
// ---- Identifiers / keywords / types / literal prefixes ----
if (syn_is_alpha(c)) { if (syn_is_alpha(c)) {
int start = i; int start = i;
while (i < len && syn_is_alnum(line[i])) i++; while (i < len && syn_is_alnum(line[i])) i++;
SynToken tok = syn_classify_word(SYN_LANG_C, line + start, i - start); if (cpp && i < len && line[i] == '"' &&
syn_is_raw_string_prefix(line + start, i - start)) {
syn_fill_tokens(out, out_len, start, i, SYN_STRING);
syn_consume_raw_string(line, len, i, out, out_len, state);
continue;
}
if (cpp && i < len && line[i] == '"' &&
syn_is_string_prefix(line + start, i - start)) {
// The quote itself is handled on the next iteration
syn_fill_tokens(out, out_len, start, i, SYN_STRING);
continue;
}
if (cpp && i < len && line[i] == '\'' &&
syn_is_string_prefix(line + start, i - start)) {
syn_fill_tokens(out, out_len, start, i, SYN_CHAR);
continue;
}
SynToken tok = syn_classify_word(lang, line + start, i - start);
syn_fill_tokens(out, out_len, start, i, tok); syn_fill_tokens(out, out_len, start, i, tok);
continue; continue;
} }
// ---- Operators ----
if (c == '+' || c == '-' || c == '*' || c == '/' || c == '%' || if (c == '+' || c == '-' || c == '*' || c == '/' || c == '%' ||
c == '=' || c == '!' || c == '<' || c == '>' || c == '&' || c == '=' || c == '!' || c == '<' || c == '>' || c == '&' ||
c == '|' || c == '^' || c == '~' || c == '?' || c == ':') { c == '|' || c == '^' || c == '~' || c == '?' || c == ':') {
@@ -355,13 +530,13 @@ inline void syn_highlight_line_c(const char* line, int len, SynToken* out, int o
i++; i++;
continue; continue;
} }
// ---- Everything else (whitespace, braces, parens, etc.) ----
syn_set_token(out, out_len, i, SYN_NORMAL); syn_set_token(out, out_len, i, SYN_NORMAL);
i++; i++;
} }
} }
// ============================================================================
// Lua highlighting
// ============================================================================ // ============================================================================
inline void syn_highlight_line_lua(const char* line, int len, SynToken* out, int out_len, inline void syn_highlight_line_lua(const char* line, int len, SynToken* out, int out_len,
@@ -458,7 +633,8 @@ inline void syn_highlight_line(const char* line, int len, SynToken* out, int out
if (!line || len <= 0) return; if (!line || len <= 0) return;
switch (lang) { switch (lang) {
case SYN_LANG_C: case SYN_LANG_C:
syn_highlight_line_c(line, len, out, out_len, state); case SYN_LANG_CPP:
syn_highlight_line_c(line, len, out, out_len, state, lang);
break; break;
case SYN_LANG_LUA: case SYN_LANG_LUA:
syn_highlight_line_lua(line, len, out, out_len, state); syn_highlight_line_lua(line, len, out, out_len, state);
@@ -489,6 +665,13 @@ inline bool syn_path_ends_with(const char* path, const char* suffix) {
inline SynLanguage syn_detect_language(const char* filepath) { inline SynLanguage syn_detect_language(const char* filepath) {
if (!filepath || filepath[0] == '\0') return SYN_LANG_NONE; if (!filepath || filepath[0] == '\0') return SYN_LANG_NONE;
if (syn_path_ends_with(filepath, ".cpp") || syn_path_ends_with(filepath, ".cc") ||
syn_path_ends_with(filepath, ".cxx") || syn_path_ends_with(filepath, ".c++") ||
syn_path_ends_with(filepath, ".hpp") || syn_path_ends_with(filepath, ".hh") ||
syn_path_ends_with(filepath, ".hxx") || syn_path_ends_with(filepath, ".h++") ||
syn_path_ends_with(filepath, ".ipp") || syn_path_ends_with(filepath, ".tpp") ||
syn_path_ends_with(filepath, ".inl"))
return SYN_LANG_CPP;
if (syn_path_ends_with(filepath, ".c") || syn_path_ends_with(filepath, ".h")) if (syn_path_ends_with(filepath, ".c") || syn_path_ends_with(filepath, ".h"))
return SYN_LANG_C; return SYN_LANG_C;
if (syn_path_ends_with(filepath, ".lua")) if (syn_path_ends_with(filepath, ".lua"))
+112 -40
View File
@@ -10,6 +10,7 @@
#include <montauk/syscall.h> #include <montauk/syscall.h>
#include <montauk/string.h> #include <montauk/string.h>
#include <Api/Syscall.hpp> #include <Api/Syscall.hpp>
#include <montauk/capabilities.h>
namespace gui { namespace gui {
@@ -59,32 +60,47 @@ struct TerminalState {
int csi_current_param; int csi_current_param;
}; };
// ==== Palette ====
//
// The palette is a mutable global rather than a set of constants because the
// terminal lets the user switch themes at runtime. Cells store resolved colors,
// so a theme switch also has to rewrite the cells that were painted with the
// outgoing palette -- see terminal_remap_palette below.
struct TermPalette {
Color bg;
Color fg;
Color cursor;
Color ansi[16];
};
inline constexpr TermPalette TERM_PALETTE_DEFAULT = {
colors::TERM_BG,
colors::TERM_FG,
colors::TERM_FG,
{
Color::from_hex(0x000000), Color::from_hex(0xCC0000),
Color::from_hex(0x4E9A06), Color::from_hex(0xC4A000),
Color::from_hex(0x3465A4), Color::from_hex(0x75507B),
Color::from_hex(0x06989A), Color::from_hex(0xD3D7CF),
Color::from_hex(0x555753), Color::from_hex(0xEF2929),
Color::from_hex(0x8AE234), Color::from_hex(0xFCE94F),
Color::from_hex(0x729FCF), Color::from_hex(0xAD7FA8),
Color::from_hex(0x34E2E2), Color::from_hex(0xEEEEEC),
}
};
inline TermPalette g_term_palette = TERM_PALETTE_DEFAULT;
// Standard ANSI color palette as ARGB pixels // Standard ANSI color palette as ARGB pixels
static inline Color term_ansi_color(int idx) { static inline Color term_ansi_color(int idx) {
switch (idx) { if (idx < 0 || idx > 15) return g_term_palette.fg;
case 0: return Color::from_hex(0x000000); return g_term_palette.ansi[idx];
case 1: return Color::from_hex(0xCC0000);
case 2: return Color::from_hex(0x4E9A06);
case 3: return Color::from_hex(0xC4A000);
case 4: return Color::from_hex(0x3465A4);
case 5: return Color::from_hex(0x75507B);
case 6: return Color::from_hex(0x06989A);
case 7: return Color::from_hex(0xD3D7CF);
case 8: return Color::from_hex(0x555753);
case 9: return Color::from_hex(0xEF2929);
case 10: return Color::from_hex(0x8AE234);
case 11: return Color::from_hex(0xFCE94F);
case 12: return Color::from_hex(0x729FCF);
case 13: return Color::from_hex(0xAD7FA8);
case 14: return Color::from_hex(0x34E2E2);
case 15: return Color::from_hex(0xEEEEEC);
default: return colors::TERM_FG;
}
} }
// ANSI 256-color palette (0-15 = standard, 16-231 = RGB cube, 232-255 = grayscale) // ANSI 256-color palette (0-15 = standard, 16-231 = RGB cube, 232-255 = grayscale)
static inline Color term_ansi_256_color(int idx) { static inline Color term_ansi_256_color(int idx) {
if (idx < 0) return colors::TERM_FG; if (idx < 0) return g_term_palette.fg;
if (idx <= 15) return term_ansi_color(idx); if (idx <= 15) return term_ansi_color(idx);
if (idx <= 231) { if (idx <= 231) {
// 6x6x6 RGB cube: idx 16 = (0,0,0), idx 231 = (5,5,5) // 6x6x6 RGB cube: idx 16 = (0,0,0), idx 231 = (5,5,5)
@@ -118,6 +134,47 @@ static inline void terminal_invalidate_render_cache(TerminalState* t) {
t->render_cursor_visible = false; t->render_cursor_visible = false;
} }
// Translate one already-resolved cell color from an outgoing palette to the
// incoming one. Colors that are not palette entries -- 256-color cube and
// grayscale ramp values -- are left exactly as the program asked for them.
static inline Color term_remap_color(Color c, const TermPalette& from,
const TermPalette& to) {
if (term_color_equal(c, from.bg)) return to.bg;
if (term_color_equal(c, from.fg)) return to.fg;
for (int i = 0; i < 16; i++) {
if (term_color_equal(c, from.ansi[i])) return to.ansi[i];
}
return c;
}
// Repaint an existing terminal in a new palette. Cells carry resolved colors,
// so switching themes without this leaves all scrollback -- and the shell's
// colored prompt -- in the old theme's colors.
static inline void terminal_remap_palette(TerminalState* t,
const TermPalette& from,
const TermPalette& to) {
if (!t || !t->cells) return;
int total = (t->rows + t->max_scrollback) * t->cols;
for (int i = 0; i < total; i++) {
t->cells[i].fg = term_remap_color(t->cells[i].fg, from, to);
t->cells[i].bg = term_remap_color(t->cells[i].bg, from, to);
}
if (t->alt_cells) {
int screen = t->rows * t->cols;
for (int i = 0; i < screen; i++) {
t->alt_cells[i].fg = term_remap_color(t->alt_cells[i].fg, from, to);
t->alt_cells[i].bg = term_remap_color(t->alt_cells[i].bg, from, to);
}
}
t->current_fg = term_remap_color(t->current_fg, from, to);
t->current_bg = term_remap_color(t->current_bg, from, to);
terminal_invalidate_render_cache(t);
t->dirty = true;
}
static inline Color terminal_erase_bg(TerminalState* t) { static inline Color terminal_erase_bg(TerminalState* t) {
return t->current_bg; return t->current_bg;
} }
@@ -162,8 +219,8 @@ static inline void terminal_init_cells(TerminalState* t, int cols, int rows, int
t->scrollback_lines = 0; t->scrollback_lines = 0;
t->max_scrollback = max_sb; t->max_scrollback = max_sb;
t->view_offset = 0; t->view_offset = 0;
t->current_fg = colors::TERM_FG; t->current_fg = g_term_palette.fg;
t->current_bg = colors::TERM_BG; t->current_bg = g_term_palette.bg;
t->cursor_visible = false; t->cursor_visible = false;
t->alt_screen_active = false; t->alt_screen_active = false;
t->reverse_video = false; t->reverse_video = false;
@@ -201,10 +258,10 @@ static inline void terminal_init_cells(TerminalState* t, int cols, int rows, int
return; return;
} }
for (int i = 0; i < total_cells; i++) { for (int i = 0; i < total_cells; i++) {
t->cells[i] = {' ', colors::TERM_FG, colors::TERM_BG}; t->cells[i] = {' ', g_term_palette.fg, g_term_palette.bg};
} }
for (int i = 0; i < screen_cells; i++) { for (int i = 0; i < screen_cells; i++) {
t->alt_cells[i] = {' ', colors::TERM_FG, colors::TERM_BG}; t->alt_cells[i] = {' ', g_term_palette.fg, g_term_palette.bg};
} }
} }
@@ -212,7 +269,19 @@ static inline void terminal_init(TerminalState* t, int cols, int rows) {
terminal_init_cells(t, cols, rows, TERM_MAX_SCROLLBACK); terminal_init_cells(t, cols, rows, TERM_MAX_SCROLLBACK);
t->cursor_visible = true; t->cursor_visible = true;
t->child_pid = montauk::spawn_redir("0:/os/shell.elf"); // A console shell is a session launcher, exactly like the desktop, and is
// granted on exactly the same terms: 0:/config/capabilities.toml decides
// what shell.elf receives, and the kernel clamps that to what this session
// was actually delegated. Deriving the grant here instead would be a
// second, hardcoded list of "capabilities a console may confer" -- one the
// table cannot see and cannot keep in step with.
static constexpr const char* kShellPath = "0:/os/shell.elf";
montauk::abi::SpawnCapabilities caps =
montauk::caps::for_binary(kShellPath, montauk::caps::self_delegable());
t->child_pid = (caps.permitted != 0)
? montauk::spawn_redir_with_caps(kShellPath, nullptr, caps)
: montauk::spawn_redir(kShellPath);
if (t->child_pid > 0) if (t->child_pid > 0)
montauk::childio_settermsz(t->child_pid, cols, rows); montauk::childio_settermsz(t->child_pid, cols, rows);
} }
@@ -245,7 +314,7 @@ static inline void terminal_enter_alt_screen(TerminalState* t) {
TermCell* screen = term_screen_row(t, 0); TermCell* screen = term_screen_row(t, 0);
for (int i = 0; i < total; i++) { for (int i = 0; i < total; i++) {
t->alt_cells[i] = screen[i]; t->alt_cells[i] = screen[i];
screen[i] = {' ', colors::TERM_FG, colors::TERM_BG}; screen[i] = {' ', g_term_palette.fg, g_term_palette.bg};
} }
t->view_offset = 0; t->view_offset = 0;
t->cursor_x = 0; t->cursor_x = 0;
@@ -424,8 +493,8 @@ static inline void terminal_process_csi(TerminalState* t, char cmd) {
} }
if (code == 0) { if (code == 0) {
t->current_fg = colors::TERM_FG; t->current_fg = g_term_palette.fg;
t->current_bg = colors::TERM_BG; t->current_bg = g_term_palette.bg;
t->reverse_video = false; t->reverse_video = false;
} else if (code == 1) { } else if (code == 1) {
// Bold: map to bright version of current color // Bold: map to bright version of current color
@@ -473,15 +542,15 @@ static inline void terminal_process_csi(TerminalState* t, char cmd) {
} else if (code >= 100 && code <= 107) { } else if (code >= 100 && code <= 107) {
t->current_bg = term_ansi_color(code - 100 + 8); t->current_bg = term_ansi_color(code - 100 + 8);
} else if (code == 39) { } else if (code == 39) {
t->current_fg = colors::TERM_FG; t->current_fg = g_term_palette.fg;
} else if (code == 49) { } else if (code == 49) {
t->current_bg = colors::TERM_BG; t->current_bg = g_term_palette.bg;
} }
} }
if (t->csi_param_count == 0) { if (t->csi_param_count == 0) {
// ESC[m with no params = reset // ESC[m with no params = reset
t->current_fg = colors::TERM_FG; t->current_fg = g_term_palette.fg;
t->current_bg = colors::TERM_BG; t->current_bg = g_term_palette.bg;
t->reverse_video = false; t->reverse_video = false;
} }
break; break;
@@ -532,8 +601,8 @@ static inline void terminal_feed(TerminalState* t, const char* data, int len) {
for (int j = 0; j < 8; j++) t->csi_params[j] = 0; for (int j = 0; j < 8; j++) t->csi_params[j] = 0;
} else if (ch == 'c') { } else if (ch == 'c') {
// Reset terminal // Reset terminal
t->current_fg = colors::TERM_FG; t->current_fg = g_term_palette.fg;
t->current_bg = colors::TERM_BG; t->current_bg = g_term_palette.bg;
t->cursor_x = 0; t->cursor_x = 0;
t->cursor_y = 0; t->cursor_y = 0;
t->parse_state = TerminalState::STATE_NORMAL; t->parse_state = TerminalState::STATE_NORMAL;
@@ -627,18 +696,21 @@ static inline void terminal_draw_cursor_pixels(const TermCell& cell,
uint32_t* pixels, int pw, int ph, uint32_t* pixels, int pw, int ph,
int px, int py, int cell_w, int cell_h, int px, int py, int cell_w, int cell_h,
bool use_ttf, GlyphCache* gc) { bool use_ttf, GlyphCache* gc) {
// The block cursor takes its color from the palette and punches the glyph
// out in the background color, so it stays legible on light themes too --
// a hardcoded white block disappears on a white background.
terminal_fill_pixel_rect(pixels, pw, ph, px, py, cell_w, cell_h, terminal_fill_pixel_rect(pixels, pw, ph, px, py, cell_w, cell_h,
colors::WHITE.to_pixel()); g_term_palette.cursor.to_pixel());
if (cell.ch <= 32 && cell.ch >= 0) return; if (cell.ch <= 32 && cell.ch >= 0) return;
if (use_ttf) { if (use_ttf) {
int baseline = py + gc->ascent; int baseline = py + gc->ascent;
fonts::mono->draw_char_to_buffer(pixels, pw, ph, fonts::mono->draw_char_to_buffer(pixels, pw, ph,
px, baseline, (unsigned char)cell.ch, colors::BLACK, gc); px, baseline, (unsigned char)cell.ch, g_term_palette.bg, gc);
} else { } else {
terminal_draw_bitmap_char(pixels, pw, ph, px, py, (unsigned char)cell.ch, terminal_draw_bitmap_char(pixels, pw, ph, px, py, (unsigned char)cell.ch,
colors::BLACK.to_pixel()); g_term_palette.bg.to_pixel());
} }
} }
@@ -700,7 +772,7 @@ static inline void terminal_render(TerminalState* t, uint32_t* pixels, int pw, i
t->render_base_row != base_row || t->render_base_row != base_row ||
t->render_term_cols != t->cols || t->render_term_rows != t->rows; t->render_term_cols != t->cols || t->render_term_rows != t->rows;
uint32_t bg_px = colors::TERM_BG.to_pixel(); uint32_t bg_px = g_term_palette.bg.to_pixel();
if (full_redraw) { if (full_redraw) {
int row_bytes = pw * sizeof(uint32_t); int row_bytes = pw * sizeof(uint32_t);
for (int i = 0; i < pw; i++) pixels[i] = bg_px; for (int i = 0; i < pw; i++) pixels[i] = bg_px;
@@ -779,9 +851,9 @@ static inline void terminal_resize(TerminalState* t, int new_cols, int new_rows)
// Clear new buffers // Clear new buffers
for (int i = 0; i < new_total; i++) for (int i = 0; i < new_total; i++)
new_cells[i] = {' ', colors::TERM_FG, colors::TERM_BG}; new_cells[i] = {' ', g_term_palette.fg, g_term_palette.bg};
for (int i = 0; i < new_rows * new_cols; i++) for (int i = 0; i < new_rows * new_cols; i++)
new_alt[i] = {' ', colors::TERM_FG, colors::TERM_BG}; new_alt[i] = {' ', g_term_palette.fg, g_term_palette.bg};
// Copy content: scrollback + visible screen // Copy content: scrollback + visible screen
int old_content = t->scrollback_lines + t->rows; int old_content = t->scrollback_lines + t->rows;
+99 -15
View File
@@ -163,15 +163,15 @@ extern "C" {
#define MTK_SYS_BTSETADDR 137 #define MTK_SYS_BTSETADDR 137
#define MTK_SYS_BTBONDS 138 #define MTK_SYS_BTBONDS 138
#define MTK_SYS_BTFORGET 139 #define MTK_SYS_BTFORGET 139
#define MTK_SYS_SDR_COUNT 140 #define MTK_SYS_RESERVED_140 140
#define MTK_SYS_SDR_INFO 141 #define MTK_SYS_RESERVED_141 141
#define MTK_SYS_SDR_OPEN 142 #define MTK_SYS_RESERVED_142 142
#define MTK_SYS_SDR_CLOSE 143 #define MTK_SYS_RESERVED_143 143
#define MTK_SYS_SDR_START 144 #define MTK_SYS_RESERVED_144 144
#define MTK_SYS_SDR_STOP 145 #define MTK_SYS_RESERVED_145 145
#define MTK_SYS_SDR_READ 146 #define MTK_SYS_RESERVED_146 146
#define MTK_SYS_SDR_SETPARAM 147 #define MTK_SYS_RESERVED_147 147
#define MTK_SYS_SDR_GETPARAM 148 #define MTK_SYS_RESERVED_148 148
#define MTK_SYS_POWERINFO 149 #define MTK_SYS_POWERINFO 149
#define MTK_SYS_FBFLIP 150 #define MTK_SYS_FBFLIP 150
#define MTK_SYS_GETEXECPATH 151 #define MTK_SYS_GETEXECPATH 151
@@ -201,6 +201,16 @@ extern "C" {
#define MTK_SYS_KILLSESSION 175 #define MTK_SYS_KILLSESSION 175
#define MTK_SYS_LOG_WRITE 176 #define MTK_SYS_LOG_WRITE 176
#define MTK_SYS_TERMINAL_ATTACHED 177 #define MTK_SYS_TERMINAL_ATTACHED 177
#define MTK_SYS_USB_LIST 178
#define MTK_SYS_USB_CLAIM 179
#define MTK_SYS_USB_CLOSE 180
#define MTK_SYS_USB_CONTROL 181
#define MTK_SYS_USB_BULK_IN_START 182
#define MTK_SYS_USB_BULK_IN_STOP 183
#define MTK_SYS_USB_BULK_IN_READ 184
#define MTK_SYS_SPAWN_CAPS 185
#define MTK_SYS_SPAWN_REDIR_CAPS 186
#define MTK_SYS_ALLOC_EAGER 187
/* @SYSCALLS-END */ /* @SYSCALLS-END */
#define MTK_SOCK_TCP 1 #define MTK_SOCK_TCP 1
@@ -210,6 +220,14 @@ extern "C" {
#define MTK_IPC_SIGNAL_PEER_CLOSED (1u << 2) #define MTK_IPC_SIGNAL_PEER_CLOSED (1u << 2)
#define MTK_IPC_SIGNAL_EXITED (1u << 3) #define MTK_IPC_SIGNAL_EXITED (1u << 3)
#define MTK_IPC_SIGNAL_READY (1u << 4) #define MTK_IPC_SIGNAL_READY (1u << 4)
#define MTK_USB_ERR_INVALID (-1)
#define MTK_USB_ERR_BUSY (-2)
#define MTK_USB_ERR_DISCONNECTED (-3)
#define MTK_USB_ERR_UNSUPPORTED (-4)
#define MTK_USB_ERR_IO (-5)
#define MTK_USB_ERR_NO_RESOURCES (-6)
#define MTK_USB_ERR_NOT_FOUND (-7)
#define MTK_USB_ERR_KERNEL_BOUND (-8)
/* Window event types */ /* Window event types */
#define MTK_EVENT_KEY 0 #define MTK_EVENT_KEY 0
@@ -314,6 +332,34 @@ typedef struct {
uint32_t dns_server; uint32_t dns_server;
} mtk_netcfg; } mtk_netcfg;
typedef struct __attribute__((packed)) {
uint8_t slot_id;
uint8_t port_id;
uint8_t speed;
uint8_t interface_number;
uint16_t vendor_id;
uint16_t product_id;
uint8_t device_class;
uint8_t interface_class;
uint8_t interface_subclass;
uint8_t interface_protocol;
uint8_t bulk_in_endpoint;
uint8_t bulk_out_endpoint;
uint16_t bulk_in_max_packet;
uint16_t bulk_out_max_packet;
uint8_t kernel_driver_bound;
uint8_t claimed;
uint8_t reserved[4];
} mtk_usb_interface_info;
typedef struct __attribute__((packed)) {
uint8_t request_type;
uint8_t request;
uint16_t value;
uint16_t index;
uint16_t length;
} mtk_usb_control_request;
typedef struct { typedef struct {
int32_t pid; int32_t pid;
int32_t parent_pid; int32_t parent_pid;
@@ -640,8 +686,8 @@ static inline int mtk_set_unix_time(int64_t unix_seconds) {
return (int)_mtk_syscall1(MTK_SYS_SETUNIXTIME, (long)unix_seconds); return (int)_mtk_syscall1(MTK_SYS_SETUNIXTIME, (long)unix_seconds);
} }
static inline void mtk_settz(int offset_minutes) { static inline int mtk_settz(int offset_minutes) {
_mtk_syscall1(MTK_SYS_SETTZ, (long)offset_minutes); return (int)_mtk_syscall1(MTK_SYS_SETTZ, (long)offset_minutes);
} }
static inline int mtk_gettz(void) { static inline int mtk_gettz(void) {
@@ -778,6 +824,44 @@ static inline void mtk_get_netcfg(mtk_netcfg *out) {
_mtk_syscall1(MTK_SYS_GETNETCFG, (long)out); _mtk_syscall1(MTK_SYS_GETNETCFG, (long)out);
} }
/* ====================================================================
Generic USB interface access
==================================================================== */
static inline int mtk_usb_list(mtk_usb_interface_info *out, int max_count) {
return (int)_mtk_syscall2(MTK_SYS_USB_LIST, (long)out, (long)max_count);
}
static inline int mtk_usb_claim(uint8_t slot_id, uint8_t interface_number) {
return (int)_mtk_syscall2(MTK_SYS_USB_CLAIM, (long)slot_id,
(long)interface_number);
}
static inline int mtk_usb_close(int handle) {
return (int)_mtk_syscall1(MTK_SYS_USB_CLOSE, (long)handle);
}
static inline int mtk_usb_control(int handle, const mtk_usb_control_request *request,
void *data, uint32_t data_len) {
return (int)_mtk_syscall4(MTK_SYS_USB_CONTROL, (long)handle, (long)request,
(long)data, (long)data_len);
}
static inline int mtk_usb_bulk_in_start(int handle, uint32_t transfer_bytes,
uint32_t buffer_count) {
return (int)_mtk_syscall3(MTK_SYS_USB_BULK_IN_START, (long)handle,
(long)transfer_bytes, (long)buffer_count);
}
static inline int mtk_usb_bulk_in_stop(int handle) {
return (int)_mtk_syscall1(MTK_SYS_USB_BULK_IN_STOP, (long)handle);
}
static inline int mtk_usb_bulk_in_read(int handle, void *data, uint32_t data_len) {
return (int)_mtk_syscall3(MTK_SYS_USB_BULK_IN_READ, (long)handle,
(long)data, (long)data_len);
}
/* ==================================================================== /* ====================================================================
Audio Audio
==================================================================== */ ==================================================================== */
@@ -802,12 +886,12 @@ static inline int mtk_audio_ctl(int handle, int cmd, int value) {
Power management Power management
==================================================================== */ ==================================================================== */
static inline void mtk_reset(void) { static inline int mtk_reset(void) {
_mtk_syscall0(MTK_SYS_RESET); return (int)_mtk_syscall0(MTK_SYS_RESET);
} }
static inline void mtk_shutdown(void) { static inline int mtk_shutdown(void) {
_mtk_syscall0(MTK_SYS_SHUTDOWN); return (int)_mtk_syscall0(MTK_SYS_SHUTDOWN);
} }
/* ==================================================================== /* ====================================================================
+1
View File
@@ -21,6 +21,7 @@ typedef struct {
/* Byte-oriented C locale only: one byte, one character. */ /* Byte-oriented C locale only: one byte, one character. */
size_t mbstowcs(wchar_t *dst, const char *src, size_t n); size_t mbstowcs(wchar_t *dst, const char *src, size_t n);
int mblen(const char *s, size_t n); int mblen(const char *s, size_t n);
size_t mbrtowc(wchar_t *pwc, const char *s, size_t n, mbstate_t *ps);
#ifdef __cplusplus #ifdef __cplusplus
} }
+216
View File
@@ -0,0 +1,216 @@
/*
* capabilities.h
* Shared reader for the capability grant table (0:/config/capabilities.toml)
* Copyright (c) 2026 Daniel Hammer
*/
#pragma once
#include <montauk/syscall.h>
#include <montauk/toml.h>
#include <montauk/config.h>
#include <montauk/heap.h>
/*
* Launchers (init, the desktop, the shell) look up the authority a program
* should receive here instead of each carrying its own compiled-in table.
*
* This file is advisory, never authoritative. Every grant still goes
* through SYS_SPAWN_CAPS and is validated in the kernel against the
* caller's own delegable set, so nothing written here can produce authority
* the kernel has not already delegated to the launcher. A missing,
* truncated or hostile file can only ever result in a program receiving
* less authority than intended. That is why the table can live in
* userspace TOML: the kernel enumerates protected paths, userspace
* interprets policy.
*
* Grants are keyed on the resolved binary path, which is what makes the
* table safe to hand to init: pointing a privileged service entry at a
* different executable looks up the new path, finds no entry, and grants
* nothing. The kernel write-protects 0:/apps and 0:/os so the path cannot
* be made to refer to a substituted image.
*/
namespace montauk {
namespace caps {
inline constexpr const char* GRANT_CONFIG = "capabilities";
inline constexpr const char* GRANT_PREFIX = "grant.";
inline constexpr int MAX_SCAN_PROCS = 256;
struct CapName {
const char* name;
uint64_t bit;
};
// Names as they appear in the config file. Kept in the same order as the
// CAP_* bit definitions in Api/Syscall.hpp.
inline constexpr CapName NAMES[] = {
{"process_admin", montauk::abi::CAP_PROCESS_ADMIN},
{"power_request", montauk::abi::CAP_POWER_REQUEST},
{"power_control", montauk::abi::CAP_POWER_CONTROL},
{"suspend", montauk::abi::CAP_SUSPEND},
{"storage_admin", montauk::abi::CAP_STORAGE_ADMIN},
{"raw_storage", montauk::abi::CAP_RAW_STORAGE},
{"network_admin", montauk::abi::CAP_NETWORK_ADMIN},
{"set_time", montauk::abi::CAP_SET_TIME},
{"user_admin", montauk::abi::CAP_USER_ADMIN},
{"display_admin", montauk::abi::CAP_DISPLAY_ADMIN},
{"device_admin", montauk::abi::CAP_DEVICE_ADMIN},
{"log_read", montauk::abi::CAP_LOG_READ},
{"system_image", montauk::abi::CAP_SYSTEM_IMAGE},
};
inline uint64_t bit_for_name(const char* name) {
if (name == nullptr || name[0] == '\0') return 0;
// "all" means "everything this launcher may pass on", which the
// caller-delegable clamp in for_binary() then narrows. It excludes
// CAP_SYSTEM_IMAGE: authority to rewrite a program image is never
// something a wildcard should hand out, only an explicit name.
if (montauk::streq(name, "all"))
return montauk::abi::CAP_ALL & ~montauk::abi::CAP_SYSTEM_IMAGE;
for (const auto& entry : NAMES) {
if (montauk::streq(entry.name, name)) return entry.bit;
}
// Unknown names are ignored rather than rejected. Failing closed
// costs a program some authority; failing open would hand out
// authority nobody asked for.
return 0;
}
// Read an array-of-strings key into a capability mask. A missing key is
// an empty mask, which is the correct default for an absent grant.
inline uint64_t mask_from_key(const montauk::toml::Doc& doc, const char* key) {
montauk::toml::Value* arr = doc.get_array(key);
if (arr == nullptr) return 0;
uint64_t mask = 0;
for (int i = 0; i < arr->array.count; i++) {
montauk::toml::Value* item = arr->array.items[i];
if (item == nullptr || item->type != montauk::toml::Type::String) continue;
mask |= bit_for_name(item->str);
}
return mask;
}
// Append `suffix` to the "grant.<id>." stem of `path_key`.
// Returns false if the key is not of that shape or does not fit.
inline bool build_sibling_key(const char* path_key, const char* suffix,
char* out, int outSz) {
int prefixLen = 0;
for (; GRANT_PREFIX[prefixLen]; prefixLen++) {
if (path_key[prefixLen] != GRANT_PREFIX[prefixLen]) return false;
}
// Copy through the final '.' so "grant.foo.path" yields "grant.foo.".
int lastDot = -1;
for (int i = 0; path_key[i]; i++) {
if (path_key[i] == '.') lastDot = i;
}
if (lastDot < prefixLen) return false;
int n = 0;
for (; n <= lastDot && n < outSz - 1; n++) out[n] = path_key[n];
for (int i = 0; suffix[i] && n < outSz - 1; i++) out[n++] = suffix[i];
out[n] = '\0';
return true;
}
// Look up the grant declared for `binary_path`. Returns false when the
// path has no entry, which is the common case and means "no authority".
inline bool lookup(const char* binary_path,
montauk::abi::SpawnCapabilities& out) {
out = {0, 0, 0};
if (binary_path == nullptr || binary_path[0] == '\0') return false;
montauk::toml::Doc doc = montauk::config::load(GRANT_CONFIG);
bool found = false;
for (int i = 0; i < doc.entries.count && !found; i++) {
montauk::toml::Value* entry = doc.entries.items[i];
if (entry == nullptr || entry->key == nullptr) continue;
if (entry->type != montauk::toml::Type::String) continue;
char sibling[128];
if (!build_sibling_key(entry->key, "path", sibling, sizeof(sibling))) continue;
if (!montauk::streq(sibling, entry->key)) continue;
if (!montauk::streq(entry->str, binary_path)) continue;
build_sibling_key(entry->key, "effective", sibling, sizeof(sibling));
uint64_t effective = mask_from_key(doc, sibling);
build_sibling_key(entry->key, "delegable", sibling, sizeof(sibling));
uint64_t delegable = mask_from_key(doc, sibling);
build_sibling_key(entry->key, "permitted", sibling, sizeof(sibling));
uint64_t permitted = mask_from_key(doc, sibling);
// A grant that does not name `permitted` owns exactly what it can
// use or pass on. Declaring it separately is only needed by a
// supervisor that holds authority in reserve (login).
if (permitted == 0) permitted = effective | delegable;
out.permitted = permitted;
out.effective = effective;
out.delegable = delegable;
found = true;
}
doc.destroy();
return found;
}
// The calling process's own capability masks.
//
// There is no syscall to ask "what am I?", so this scans the process table
// for our own PID. The buffer is heap-allocated because ProcInfo is large
// enough that MAX_SCAN_PROCS of them would be a ~29 KB stack frame.
inline bool self(montauk::abi::SpawnCapabilities& out) {
out = {0, 0, 0};
auto* table = (montauk::abi::ProcInfo*)montauk::malloc(
sizeof(montauk::abi::ProcInfo) * MAX_SCAN_PROCS);
if (table == nullptr) return false;
int count = montauk::proclist(table, MAX_SCAN_PROCS);
int self_pid = montauk::getpid();
bool found = false;
for (int i = 0; i < count; i++) {
if (table[i].pid != self_pid) continue;
out.permitted = table[i].permittedCaps;
out.effective = table[i].effectiveCaps;
out.delegable = table[i].delegableCaps;
found = true;
break;
}
montauk::mfree(table);
return found;
}
inline uint64_t self_delegable() {
montauk::abi::SpawnCapabilities mine;
return self(mine) ? mine.delegable : 0;
}
// Build a spawn request for `binary_path`, clamped to what the caller may
// actually delegate. The kernel enforces the same bound; clamping here
// means a launcher that holds less authority than the table declares
// degrades to a reduced grant instead of failing the spawn outright.
inline montauk::abi::SpawnCapabilities for_binary(const char* binary_path,
uint64_t caller_delegable) {
montauk::abi::SpawnCapabilities caps{0, 0, 0};
// A caller with nothing to delegate cannot produce a non-empty grant,
// so skip the file read entirely. This is the common case: every
// unprivileged session, on every launch.
if (caller_delegable == 0) return caps;
if (!lookup(binary_path, caps)) return caps;
caps.permitted &= caller_delegable;
caps.effective &= caps.permitted;
caps.delegable &= caps.permitted;
return caps;
}
} // namespace caps
} // namespace montauk
+24
View File
@@ -0,0 +1,24 @@
/*
* service_log.h
* Common logging policy for MontaukOS userspace services.
*/
#pragma once
#include <montauk/syscall.h>
namespace montauk {
// Append one newline-free message to the system log. When the service was
// launched from a userspace terminal, echo the same message there as well.
inline void service_log(const char* message) {
if (message == nullptr) return;
write_log(message);
if (terminal_attached()) {
print(message);
print("\n");
}
}
}
+57 -56
View File
@@ -120,6 +120,13 @@ namespace montauk {
inline int spawn(const char* path, const char* args = nullptr) { inline int spawn(const char* path, const char* args = nullptr) {
return (int)syscall2(montauk::abi::SYS_SPAWN, (uint64_t)path, (uint64_t)args); return (int)syscall2(montauk::abi::SYS_SPAWN, (uint64_t)path, (uint64_t)args);
} }
inline int spawn_with_caps(const char* path, const char* args,
const char* user,
const montauk::abi::SpawnCapabilities& capabilities) {
return (int)syscall4(montauk::abi::SYS_SPAWN_CAPS, (uint64_t)path,
(uint64_t)args, (uint64_t)user,
(uint64_t)&capabilities);
}
inline int chdir(const char* path) { inline int chdir(const char* path) {
return (int)syscall1(montauk::abi::SYS_CHDIR, (uint64_t)path); return (int)syscall1(montauk::abi::SYS_CHDIR, (uint64_t)path);
} }
@@ -184,6 +191,10 @@ namespace montauk {
// Memory // Memory
inline void* alloc(uint64_t size) { return (void*)syscall1(montauk::abi::SYS_ALLOC, size); } inline void* alloc(uint64_t size) { return (void*)syscall1(montauk::abi::SYS_ALLOC, size); }
// As alloc(), but the kernel commits the whole range immediately. Use it
// for a buffer that is about to be written end to end: the lazy path costs
// one page fault, one mutex acquire and one VMA walk per 4 KiB.
inline void* alloc_eager(uint64_t size) { return (void*)syscall1(montauk::abi::SYS_ALLOC_EAGER, size); }
inline void free(void* ptr) { syscall1(montauk::abi::SYS_FREE, (uint64_t)ptr); } inline void free(void* ptr) { syscall1(montauk::abi::SYS_FREE, (uint64_t)ptr); }
// Timekeeping // Timekeeping
@@ -391,7 +402,10 @@ namespace montauk {
} }
// Timezone offset (total minutes from UTC) // Timezone offset (total minutes from UTC)
inline void settz(int offset_minutes) { syscall1(montauk::abi::SYS_SETTZ, (uint64_t)(int64_t)offset_minutes); } inline int settz(int offset_minutes) {
return (int)syscall1(montauk::abi::SYS_SETTZ,
(uint64_t)(int64_t)offset_minutes);
}
inline int gettz() { return (int)syscall0(montauk::abi::SYS_GETTZ); } inline int gettz() { return (int)syscall0(montauk::abi::SYS_GETTZ); }
// Random number generation // Random number generation
@@ -400,14 +414,12 @@ namespace montauk {
} }
// Power management // Power management
[[noreturn]] inline void reset() { inline int reset() {
syscall0(montauk::abi::SYS_RESET); return (int)syscall0(montauk::abi::SYS_RESET);
__builtin_unreachable();
} }
[[noreturn]] inline void shutdown() { inline int shutdown() {
syscall0(montauk::abi::SYS_SHUTDOWN); return (int)syscall0(montauk::abi::SYS_SHUTDOWN);
__builtin_unreachable();
} }
inline int suspend() { inline int suspend() {
@@ -423,6 +435,13 @@ namespace montauk {
return (int)syscall1(montauk::abi::SYS_POWER_REQUEST, (uint64_t)(int64_t)action); return (int)syscall1(montauk::abi::SYS_POWER_REQUEST, (uint64_t)(int64_t)action);
} }
// Non-destructive read of the pending request, for a session leader that
// must stand down when something inside its session (the shell's shutdown
// builtin, say) asked for power-off. Returns POWER_REQ_QUERY when idle.
inline int power_request_pending() {
return power_request(montauk::abi::POWER_REQ_PEEK);
}
// Mouse // Mouse
inline void mouse_state(montauk::abi::MouseState* out) { syscall1(montauk::abi::SYS_MOUSESTATE, (uint64_t)out); } inline void mouse_state(montauk::abi::MouseState* out) { syscall1(montauk::abi::SYS_MOUSESTATE, (uint64_t)out); }
inline void set_mouse_bounds(int32_t maxX, int32_t maxY) { inline void set_mouse_bounds(int32_t maxX, int32_t maxY) {
@@ -447,6 +466,13 @@ namespace montauk {
inline int spawn_redir(const char* path, const char* args = nullptr) { inline int spawn_redir(const char* path, const char* args = nullptr) {
return (int)syscall2(montauk::abi::SYS_SPAWN_REDIR, (uint64_t)path, (uint64_t)args); return (int)syscall2(montauk::abi::SYS_SPAWN_REDIR, (uint64_t)path, (uint64_t)args);
} }
inline int spawn_redir_with_caps(
const char* path, const char* args,
const montauk::abi::SpawnCapabilities& capabilities) {
return (int)syscall3(montauk::abi::SYS_SPAWN_REDIR_CAPS,
(uint64_t)path, (uint64_t)args,
(uint64_t)&capabilities);
}
inline int childio_read(int childPid, char* buf, int maxLen) { inline int childio_read(int childPid, char* buf, int maxLen) {
return (int)syscall3(montauk::abi::SYS_CHILDIO_READ, (uint64_t)childPid, (uint64_t)buf, (uint64_t)maxLen); return (int)syscall3(montauk::abi::SYS_CHILDIO_READ, (uint64_t)childPid, (uint64_t)buf, (uint64_t)maxLen);
} }
@@ -661,61 +687,36 @@ namespace montauk {
(uint64_t)maxCount); (uint64_t)maxCount);
} }
// Software-defined radio (Rx). Receivers are identified by index [0, count); // Generic USB access. Only interfaces without a bound kernel class driver
// open() returns a handle used by the rest of the calls. Samples are read // may be claimed. Handles belong to the claiming process and are released
// as interleaved 8-bit unsigned I/Q (CU8) from the device's ring buffer. // automatically when it exits.
inline int sdr_count() { inline int usb_list(montauk::abi::UsbInterfaceInfo* buf, int maxCount) {
return (int)syscall0(montauk::abi::SYS_SDR_COUNT); return (int)syscall2(montauk::abi::SYS_USB_LIST, (uint64_t)buf,
(uint64_t)maxCount);
} }
inline int sdr_info(int index, montauk::abi::SdrDeviceInfo* out) { inline int usb_claim(uint8_t slotId, uint8_t interfaceNumber) {
return (int)syscall2(montauk::abi::SYS_SDR_INFO, (uint64_t)index, (uint64_t)out); return (int)syscall2(montauk::abi::SYS_USB_CLAIM, (uint64_t)slotId,
(uint64_t)interfaceNumber);
} }
inline int sdr_open(int index) { inline int usb_close(int handle) {
return (int)syscall1(montauk::abi::SYS_SDR_OPEN, (uint64_t)index); return (int)syscall1(montauk::abi::SYS_USB_CLOSE, (uint64_t)handle);
} }
inline int sdr_close(int handle) { inline int usb_control(int handle, const montauk::abi::UsbControlRequest* request,
return (int)syscall1(montauk::abi::SYS_SDR_CLOSE, (uint64_t)handle); void* data, uint32_t dataLen) {
return (int)syscall4(montauk::abi::SYS_USB_CONTROL, (uint64_t)handle,
(uint64_t)request, (uint64_t)data, (uint64_t)dataLen);
} }
inline int sdr_start(int handle) { inline int usb_bulk_in_start(int handle, uint32_t transferBytes,
return (int)syscall1(montauk::abi::SYS_SDR_START, (uint64_t)handle); uint32_t bufferCount) {
return (int)syscall3(montauk::abi::SYS_USB_BULK_IN_START, (uint64_t)handle,
(uint64_t)transferBytes, (uint64_t)bufferCount);
} }
inline int sdr_stop(int handle) { inline int usb_bulk_in_stop(int handle) {
return (int)syscall1(montauk::abi::SYS_SDR_STOP, (uint64_t)handle); return (int)syscall1(montauk::abi::SYS_USB_BULK_IN_STOP, (uint64_t)handle);
} }
// Non-blocking: copies up to len bytes of queued I/Q, returns bytes copied. inline int usb_bulk_in_read(int handle, void* data, uint32_t dataLen) {
inline int sdr_read(int handle, void* buf, uint32_t len) { return (int)syscall3(montauk::abi::SYS_USB_BULK_IN_READ, (uint64_t)handle,
return (int)syscall3(montauk::abi::SYS_SDR_READ, (uint64_t)handle, (uint64_t)buf, (uint64_t)len); (uint64_t)data, (uint64_t)dataLen);
}
inline int sdr_set_param(int handle, int param, uint64_t value) {
return (int)syscall3(montauk::abi::SYS_SDR_SETPARAM, (uint64_t)handle, (uint64_t)param, value);
}
inline int64_t sdr_get_param(int handle, int param) {
return syscall2(montauk::abi::SYS_SDR_GETPARAM, (uint64_t)handle, (uint64_t)param);
}
// Convenience wrappers over sdr_set_param / sdr_get_param.
inline int sdr_set_freq(int handle, uint64_t hz) {
return sdr_set_param(handle, montauk::abi::SDR_PARAM_FREQ, hz);
}
inline uint64_t sdr_get_freq(int handle) {
return (uint64_t)sdr_get_param(handle, montauk::abi::SDR_PARAM_FREQ);
}
inline int sdr_set_sample_rate(int handle, uint32_t hz) {
return sdr_set_param(handle, montauk::abi::SDR_PARAM_SAMPLE_RATE, hz);
}
inline uint32_t sdr_get_sample_rate(int handle) {
return (uint32_t)sdr_get_param(handle, montauk::abi::SDR_PARAM_SAMPLE_RATE);
}
inline int sdr_set_gain_mode(int handle, int manual) {
return sdr_set_param(handle, montauk::abi::SDR_PARAM_GAIN_MODE, (uint64_t)manual);
}
inline int sdr_set_gain(int handle, int tenthsDb) {
return sdr_set_param(handle, montauk::abi::SDR_PARAM_GAIN, (uint64_t)(int64_t)tenthsDb);
}
inline int sdr_set_freq_correction(int handle, int ppm) {
return sdr_set_param(handle, montauk::abi::SDR_PARAM_FREQ_CORR, (uint64_t)(int64_t)ppm);
}
inline int sdr_set_agc(int handle, int on) {
return sdr_set_param(handle, montauk::abi::SDR_PARAM_AGC, (uint64_t)on);
} }
// Kernel introspection // Kernel introspection
+10
View File
@@ -219,6 +219,16 @@ namespace user {
return false; return false;
} }
inline bool is_admin(const char* username) {
UserInfo users[MAX_USERS];
int count = load_users(users, MAX_USERS);
for (int i = 0; i < count; i++) {
if (montauk::streq(users[i].username, username))
return montauk::streq(users[i].role, "admin");
}
return false;
}
// ---- User management ---- // ---- User management ----
inline bool create_user(const char* username, const char* display_name, inline bool create_user(const char* username, const char* display_name,
+42
View File
@@ -0,0 +1,42 @@
#pragma once
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
typedef struct rtlsdr_device rtlsdr_device;
typedef struct rtlsdr_device_info {
char name[64];
char tuner[32];
char serial[32];
uint64_t freq_min;
uint64_t freq_max;
uint32_t sample_rate_min;
uint32_t sample_rate_max;
uint32_t num_gains;
int32_t gains[32];
} rtlsdr_device_info;
int rtlsdr_count(void);
int rtlsdr_get_device_info(int index, rtlsdr_device_info* out);
int rtlsdr_open(rtlsdr_device** out, int index);
int rtlsdr_close(rtlsdr_device* dev);
int rtlsdr_set_center_freq(rtlsdr_device* dev, uint64_t hz);
uint64_t rtlsdr_get_center_freq(const rtlsdr_device* dev);
int rtlsdr_set_sample_rate(rtlsdr_device* dev, uint32_t hz);
uint32_t rtlsdr_get_sample_rate(const rtlsdr_device* dev);
int rtlsdr_set_tuner_gain_mode(rtlsdr_device* dev, int manual);
int rtlsdr_set_tuner_gain(rtlsdr_device* dev, int tenths_db);
int rtlsdr_set_freq_correction(rtlsdr_device* dev, int ppm);
int rtlsdr_set_agc_mode(rtlsdr_device* dev, int on);
int rtlsdr_set_direct_sampling(rtlsdr_device* dev, int mode);
int rtlsdr_start(rtlsdr_device* dev);
int rtlsdr_stop(rtlsdr_device* dev);
int rtlsdr_read(rtlsdr_device* dev, void* data, uint32_t length);
#ifdef __cplusplus
}
#endif
+144 -115
View File
@@ -25,19 +25,20 @@
#include <sys/mman.h> #include <sys/mman.h>
#include <sys/time.h> #include <sys/time.h>
#include <unistd.h> #include <unistd.h>
#include <wchar.h>
/* ======================================================================== /* ========================================================================
Raw syscall wrappers (C versions matching kernel ABI) Raw syscall wrappers (C versions matching kernel ABI)
======================================================================== */ ======================================================================== */
static inline long _zos_syscall0(long nr) { static inline long _mtk_syscall0(long nr) {
long ret; long ret;
__asm__ volatile("syscall" : "=a"(ret) : "a"(nr) __asm__ volatile("syscall" : "=a"(ret) : "a"(nr)
: "rcx", "r11", "rdi", "rsi", "rdx", "r8", "r9", "r10", "memory"); : "rcx", "r11", "rdi", "rsi", "rdx", "r8", "r9", "r10", "memory");
return ret; return ret;
} }
static inline long _zos_syscall1(long nr, long a1) { static inline long _mtk_syscall1(long nr, long a1) {
long ret; long ret;
__asm__ volatile( __asm__ volatile(
"mov %[a1], %%rdi\n\t" "mov %[a1], %%rdi\n\t"
@@ -48,7 +49,7 @@ static inline long _zos_syscall1(long nr, long a1) {
return ret; return ret;
} }
static inline long _zos_syscall2(long nr, long a1, long a2) { static inline long _mtk_syscall2(long nr, long a1, long a2) {
long ret; long ret;
__asm__ volatile( __asm__ volatile(
"mov %[a1], %%rdi\n\t" "mov %[a1], %%rdi\n\t"
@@ -60,7 +61,7 @@ static inline long _zos_syscall2(long nr, long a1, long a2) {
return ret; return ret;
} }
static inline long _zos_syscall3(long nr, long a1, long a2, long a3) { static inline long _mtk_syscall3(long nr, long a1, long a2, long a3) {
long ret; long ret;
__asm__ volatile( __asm__ volatile(
"mov %[a1], %%rdi\n\t" "mov %[a1], %%rdi\n\t"
@@ -73,7 +74,7 @@ static inline long _zos_syscall3(long nr, long a1, long a2, long a3) {
return ret; return ret;
} }
static inline long _zos_syscall4(long nr, long a1, long a2, long a3, long a4) { static inline long _mtk_syscall4(long nr, long a1, long a2, long a3, long a4) {
long ret; long ret;
__asm__ volatile( __asm__ volatile(
"mov %[a1], %%rdi\n\t" "mov %[a1], %%rdi\n\t"
@@ -99,10 +100,12 @@ static inline long _zos_syscall4(long nr, long a1, long a2, long a3, long a4) {
#define SYS_CLOSE 9 #define SYS_CLOSE 9
#define SYS_READDIR 10 #define SYS_READDIR 10
#define SYS_ALLOC 11 #define SYS_ALLOC 11
#define SYS_ALLOC_EAGER 187
#define SYS_FREE 12 #define SYS_FREE 12
#define SYS_GETMILLISECONDS 14 #define SYS_GETMILLISECONDS 14
#define SYS_GETCHAR 18 #define SYS_GETCHAR 18
#define SYS_GETCHAR_NB 166 #define SYS_GETCHAR_NB 166
#define SYS_TERMINAL_ATTACHED 177
#define SYS_SPAWN 20 #define SYS_SPAWN 20
#define SYS_WAITPID 23 #define SYS_WAITPID 23
#define SYS_GETARGS 25 #define SYS_GETARGS 25
@@ -273,12 +276,12 @@ static void _tm_from_epoch(time_t epoch, struct tm *out) {
} }
static int _get_tz_offset_minutes(void) { static int _get_tz_offset_minutes(void) {
return (int)_zos_syscall0(SYS_GETTZ); return (int)_mtk_syscall0(SYS_GETTZ);
} }
static time_t _current_time_epoch(void) { static time_t _current_time_epoch(void) {
struct _mtk_datetime dt = {}; struct _mtk_datetime dt = {};
_zos_syscall1(SYS_GETTIME, (long)&dt); _mtk_syscall1(SYS_GETTIME, (long)&dt);
return _epoch_from_datetime((int)dt.year, (int)dt.month, (int)dt.day, return _epoch_from_datetime((int)dt.year, (int)dt.month, (int)dt.day,
(int)dt.hour, (int)dt.minute, (int)dt.second) (int)dt.hour, (int)dt.minute, (int)dt.second)
- (time_t)_get_tz_offset_minutes() * 60; - (time_t)_get_tz_offset_minutes() * 60;
@@ -319,7 +322,7 @@ static int _append_num(char *buf, size_t max, size_t *pos, int value, int width,
static int _path_is_directory(const char *path) { static int _path_is_directory(const char *path) {
const char *names[1]; const char *names[1];
return (int)_zos_syscall3(SYS_READDIR, (long)path, (long)names, 1L) >= 0; return (int)_mtk_syscall3(SYS_READDIR, (long)path, (long)names, 1L) >= 0;
} }
static int _path_local_prefix(const char *path, char *buf, size_t size) { static int _path_local_prefix(const char *path, char *buf, size_t size) {
@@ -383,13 +386,13 @@ static int _sync_environ_to_kernel(void) {
out += len; out += len;
} }
blob[out++] = '\0'; blob[out++] = '\0';
return _zos_syscall2(SYS_SETENVIRON, (long)blob, (long)out) < 0 ? -1 : 0; return _mtk_syscall2(SYS_SETENVIRON, (long)blob, (long)out) < 0 ? -1 : 0;
} }
static void _ensure_environ_initialized(void) { static void _ensure_environ_initialized(void) {
if (_environ_initialized) return; if (_environ_initialized) return;
static char blob[4096]; static char blob[4096];
int len = (int)_zos_syscall2(SYS_GETENVIRON, (long)blob, (long)sizeof(blob)); int len = (int)_mtk_syscall2(SYS_GETENVIRON, (long)blob, (long)sizeof(blob));
if (len > 0) if (len > 0)
__libc_init_environ(blob, (size_t)len); __libc_init_environ(blob, (size_t)len);
else else
@@ -678,6 +681,14 @@ int tolower(int c) { return (c >= 'A' && c <= 'Z') ? c + 32 : c; }
#define HEAP_ALIGN 16ULL #define HEAP_ALIGN 16ULL
#define DIRECT_THRESHOLD (256ULL * 1024ULL) #define DIRECT_THRESHOLD (256ULL * 1024ULL)
/* A direct mapping is one object the caller sized itself, so it is nearly
always written end to end (an image buffer, a loaded file). Committing it
up front replaces one page fault, one kernel mutex acquire and one VMA walk
per 4 KiB with a single loop -- thousands of traps for a decoded image.
Beyond the cap, stay lazy: a very large mapping is more likely to be a
sparsely touched reservation, and eager commit would pin the lot. */
#define EAGER_DIRECT_LIMIT (32ULL * 1024ULL * 1024ULL)
struct HeapHeader { struct HeapHeader {
uint64_t magic; uint64_t magic;
uint64_t requested_size; uint64_t requested_size;
@@ -705,7 +716,7 @@ static volatile uint32_t g_heapLock = 0;
static void heap_lock(void) { static void heap_lock(void) {
while (__atomic_exchange_n(&g_heapLock, 1, __ATOMIC_ACQUIRE) != 0) while (__atomic_exchange_n(&g_heapLock, 1, __ATOMIC_ACQUIRE) != 0)
_zos_syscall0(SYS_YIELD); _mtk_syscall0(SYS_YIELD);
} }
static void heap_unlock(void) { static void heap_unlock(void) {
@@ -804,11 +815,11 @@ static void heap_grow(uint64_t bytes) {
uint64_t slab = (want > g_heap_slab) ? want : g_heap_slab; uint64_t slab = (want > g_heap_slab) ? want : g_heap_slab;
if (g_heap_slab < 4 * 1024 * 1024) g_heap_slab *= 2; if (g_heap_slab < 4 * 1024 * 1024) g_heap_slab *= 2;
void *mem = (void *)_zos_syscall1(SYS_ALLOC, (long)slab); void *mem = (void *)_mtk_syscall1(SYS_ALLOC, (long)slab);
if (mem == NULL && slab > want) { if (mem == NULL && slab > want) {
/* Big slab refused (low memory): retry with the exact need. */ /* Big slab refused (low memory): retry with the exact need. */
slab = want; slab = want;
mem = (void *)_zos_syscall1(SYS_ALLOC, (long)slab); mem = (void *)_mtk_syscall1(SYS_ALLOC, (long)slab);
} }
if (mem != NULL) if (mem != NULL)
heap_insert_overflow(mem, slab); heap_insert_overflow(mem, slab);
@@ -854,8 +865,10 @@ static void *heap_malloc_locked(size_t size) {
if (needed >= DIRECT_THRESHOLD) { if (needed >= DIRECT_THRESHOLD) {
if (needed > UINT64_MAX - 0xFFFULL) return NULL; if (needed > UINT64_MAX - 0xFFFULL) return NULL;
uint64_t mapping_size = (needed + 0xFFFULL) & ~0xFFFULL; uint64_t mapping_size = (needed + 0xFFFULL) & ~0xFFFULL;
long alloc_nr = (mapping_size <= EAGER_DIRECT_LIMIT)
? SYS_ALLOC_EAGER : SYS_ALLOC;
struct HeapHeader *hdr = (struct HeapHeader *) struct HeapHeader *hdr = (struct HeapHeader *)
_zos_syscall1(SYS_ALLOC, (long)mapping_size); _mtk_syscall1(alloc_nr, (long)mapping_size);
if (hdr == NULL) return NULL; if (hdr == NULL) return NULL;
hdr->magic = DIRECT_MAGIC; hdr->magic = DIRECT_MAGIC;
hdr->requested_size = size; hdr->requested_size = size;
@@ -925,7 +938,7 @@ static void heap_free_locked(void *ptr) {
hdr->magic = FREED_MAGIC; hdr->magic = FREED_MAGIC;
if (direct) { if (direct) {
_zos_syscall1(SYS_FREE, (long)hdr); _mtk_syscall1(SYS_FREE, (long)hdr);
return; return;
} }
@@ -1225,13 +1238,13 @@ int atexit(void (*func)(void)) {
void exit(int status) { void exit(int status) {
for (int i = _atexit_count - 1; i >= 0; i--) for (int i = _atexit_count - 1; i >= 0; i--)
_atexit_funcs[i](); _atexit_funcs[i]();
_zos_syscall1(SYS_EXIT, (long)status); _mtk_syscall1(SYS_EXIT, (long)status);
__builtin_unreachable(); __builtin_unreachable();
} }
void abort(void) { void abort(void) {
_zos_syscall1(SYS_PRINT, (long)"abort() called\n"); _mtk_syscall1(SYS_PRINT, (long)"abort() called\n");
_zos_syscall1(SYS_EXIT, 1); _mtk_syscall1(SYS_EXIT, 1);
__builtin_unreachable(); __builtin_unreachable();
} }
@@ -1251,9 +1264,9 @@ static void _system_build_drive_path(int drive, const char *leaf, char *out, siz
} }
static int _system_try_spawn(const char *path, const char *args) { static int _system_try_spawn(const char *path, const char *args) {
int pid = (int)_zos_syscall2(SYS_SPAWN, (long)path, (long)args); int pid = (int)_mtk_syscall2(SYS_SPAWN, (long)path, (long)args);
if (pid < 0) return -1; if (pid < 0) return -1;
_zos_syscall1(SYS_WAITPID, (long)pid); _mtk_syscall1(SYS_WAITPID, (long)pid);
return 0; return 0;
} }
@@ -1610,18 +1623,18 @@ int printf(const char *fmt, ...) {
va_start(ap, fmt); va_start(ap, fmt);
int ret = vsnprintf(_printbuf, sizeof(_printbuf), fmt, ap); int ret = vsnprintf(_printbuf, sizeof(_printbuf), fmt, ap);
va_end(ap); va_end(ap);
_zos_syscall1(SYS_PRINT, (long)_printbuf); _mtk_syscall1(SYS_PRINT, (long)_printbuf);
return ret; return ret;
} }
int puts(const char *s) { int puts(const char *s) {
_zos_syscall1(SYS_PRINT, (long)s); _mtk_syscall1(SYS_PRINT, (long)s);
_zos_syscall1(SYS_PUTCHAR, (long)'\n'); _mtk_syscall1(SYS_PUTCHAR, (long)'\n');
return 0; return 0;
} }
int putchar(int c) { int putchar(int c) {
_zos_syscall1(SYS_PUTCHAR, (long)c); _mtk_syscall1(SYS_PUTCHAR, (long)c);
return c; return c;
} }
@@ -1630,11 +1643,11 @@ int putchar(int c) {
======================================================================== */ ======================================================================== */
void __assert_fail(const char *expr, const char *file, int line, const char *func) { void __assert_fail(const char *expr, const char *file, int line, const char *func) {
_zos_syscall1(SYS_PRINT, (long)"Assertion failed: "); _mtk_syscall1(SYS_PRINT, (long)"Assertion failed: ");
_zos_syscall1(SYS_PRINT, (long)expr); _mtk_syscall1(SYS_PRINT, (long)expr);
_zos_syscall1(SYS_PRINT, (long)" at "); _mtk_syscall1(SYS_PRINT, (long)" at ");
_zos_syscall1(SYS_PRINT, (long)file); _mtk_syscall1(SYS_PRINT, (long)file);
_zos_syscall1(SYS_PRINT, (long)"\n"); _mtk_syscall1(SYS_PRINT, (long)"\n");
(void)line; (void)func; (void)line; (void)func;
abort(); abort();
} }
@@ -1675,12 +1688,12 @@ static int _stdin_line_ready = 0;
static void _stdin_echo_char(char c) { static void _stdin_echo_char(char c) {
if (c == '\b') { if (c == '\b') {
_zos_syscall1(SYS_PUTCHAR, '\b'); _mtk_syscall1(SYS_PUTCHAR, '\b');
_zos_syscall1(SYS_PUTCHAR, ' '); _mtk_syscall1(SYS_PUTCHAR, ' ');
_zos_syscall1(SYS_PUTCHAR, '\b'); _mtk_syscall1(SYS_PUTCHAR, '\b');
return; return;
} }
_zos_syscall1(SYS_PUTCHAR, (unsigned char)c); _mtk_syscall1(SYS_PUTCHAR, (unsigned char)c);
} }
/* Gather keystrokes into _stdin_linebuf. Returns 1 when a complete line is /* Gather keystrokes into _stdin_linebuf. Returns 1 when a complete line is
@@ -1696,8 +1709,8 @@ static int _stdin_pump(int block) {
} }
for (;;) { for (;;) {
int c = block ? (int)_zos_syscall0(SYS_GETCHAR) int c = block ? (int)_mtk_syscall0(SYS_GETCHAR)
: (int)_zos_syscall0(SYS_GETCHAR_NB); : (int)_mtk_syscall0(SYS_GETCHAR_NB);
if (c <= 0) { if (c <= 0) {
if (block) continue; if (block) continue;
return 0; return 0;
@@ -1762,17 +1775,17 @@ FILE *fopen(const char *path, const char *mode) {
want_write = 1; want_write = 1;
if (mode[1] == '+') want_read = 1; if (mode[1] == '+') want_read = 1;
/* Truncate: delete then create */ /* Truncate: delete then create */
_zos_syscall1(SYS_FDELETE, (long)path); _mtk_syscall1(SYS_FDELETE, (long)path);
handle = (int)_zos_syscall1(SYS_FCREATE, (long)path); handle = (int)_mtk_syscall1(SYS_FCREATE, (long)path);
if (handle < 0) return NULL; if (handle < 0) return NULL;
} else if (mode[0] == 'a') { } else if (mode[0] == 'a') {
want_write = 1; want_write = 1;
want_append = 1; want_append = 1;
if (mode[1] == '+') want_read = 1; if (mode[1] == '+') want_read = 1;
/* Try open existing, create if not found */ /* Try open existing, create if not found */
handle = (int)_zos_syscall1(SYS_OPEN, (long)path); handle = (int)_mtk_syscall1(SYS_OPEN, (long)path);
if (handle < 0) { if (handle < 0) {
handle = (int)_zos_syscall1(SYS_FCREATE, (long)path); handle = (int)_mtk_syscall1(SYS_FCREATE, (long)path);
if (handle < 0) return NULL; if (handle < 0) return NULL;
} }
} else { } else {
@@ -1781,15 +1794,15 @@ FILE *fopen(const char *path, const char *mode) {
/* For read and read+ modes, just open existing */ /* For read and read+ modes, just open existing */
if (mode[0] == 'r') { if (mode[0] == 'r') {
handle = (int)_zos_syscall1(SYS_OPEN, (long)path); handle = (int)_mtk_syscall1(SYS_OPEN, (long)path);
if (handle < 0) return NULL; if (handle < 0) return NULL;
} }
unsigned long fileSize = (unsigned long)_zos_syscall1(SYS_GETSIZE, (long)handle); unsigned long fileSize = (unsigned long)_mtk_syscall1(SYS_GETSIZE, (long)handle);
FILE *f = (FILE *)malloc(sizeof(FILE)); FILE *f = (FILE *)malloc(sizeof(FILE));
if (f == NULL) { if (f == NULL) {
_zos_syscall1(SYS_CLOSE, (long)handle); _mtk_syscall1(SYS_CLOSE, (long)handle);
return NULL; return NULL;
} }
@@ -1811,7 +1824,7 @@ int fclose(FILE *stream) {
if (stream == NULL) return EOF; if (stream == NULL) return EOF;
if (stream->is_std) return 0; if (stream->is_std) return 0;
_zos_syscall1(SYS_CLOSE, (long)stream->handle); _mtk_syscall1(SYS_CLOSE, (long)stream->handle);
free(stream); free(stream);
return 0; return 0;
} }
@@ -1842,7 +1855,7 @@ size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream) {
} }
if (total == 0) return 0; if (total == 0) return 0;
int ret = (int)_zos_syscall4(SYS_READ, (long)stream->handle, int ret = (int)_mtk_syscall4(SYS_READ, (long)stream->handle,
(long)ptr, (long)stream->pos, (long)total); (long)ptr, (long)stream->pos, (long)total);
if (ret < 0) { if (ret < 0) {
stream->error = 1; stream->error = 1;
@@ -1861,12 +1874,12 @@ size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream) {
size_t total = size * nmemb; size_t total = size * nmemb;
const char *src = (const char *)ptr; const char *src = (const char *)ptr;
for (size_t i = 0; i < total; i++) for (size_t i = 0; i < total; i++)
_zos_syscall1(SYS_PUTCHAR, (long)(unsigned char)src[i]); _mtk_syscall1(SYS_PUTCHAR, (long)(unsigned char)src[i]);
return nmemb; return nmemb;
} }
size_t total = size * nmemb; size_t total = size * nmemb;
int ret = (int)_zos_syscall4(SYS_FWRITE, (long)stream->handle, int ret = (int)_mtk_syscall4(SYS_FWRITE, (long)stream->handle,
(long)ptr, (long)stream->pos, (long)total); (long)ptr, (long)stream->pos, (long)total);
if (ret < 0) { if (ret < 0) {
stream->error = 1; stream->error = 1;
@@ -1915,7 +1928,7 @@ FILE *freopen(const char *path, const char *mode, FILE *stream) {
if (fresh == NULL) return NULL; if (fresh == NULL) return NULL;
if (!stream->is_std) { if (!stream->is_std) {
_zos_syscall1(SYS_CLOSE, (long)stream->handle); _mtk_syscall1(SYS_CLOSE, (long)stream->handle);
} }
*stream = *fresh; *stream = *fresh;
@@ -1973,7 +1986,7 @@ int fgetc(FILE *stream) {
} }
unsigned char c; unsigned char c;
int ret = (int)_zos_syscall4(SYS_READ, (long)stream->handle, int ret = (int)_mtk_syscall4(SYS_READ, (long)stream->handle,
(long)&c, (long)stream->pos, 1L); (long)&c, (long)stream->pos, 1L);
if (ret <= 0) { if (ret <= 0) {
stream->eof = 1; stream->eof = 1;
@@ -2034,7 +2047,7 @@ int fprintf(FILE *stream, const char *fmt, ...) {
va_end(ap); va_end(ap);
if (stream == NULL || stream->is_std) { if (stream == NULL || stream->is_std) {
_zos_syscall1(SYS_PRINT, (long)buf); _mtk_syscall1(SYS_PRINT, (long)buf);
} else { } else {
fwrite(buf, 1, (size_t)(n > 0 ? n : 0), stream); fwrite(buf, 1, (size_t)(n > 0 ? n : 0), stream);
} }
@@ -2046,7 +2059,7 @@ int vfprintf(FILE *stream, const char *fmt, va_list ap) {
int n = vsnprintf(buf, sizeof(buf), fmt, ap); int n = vsnprintf(buf, sizeof(buf), fmt, ap);
if (stream == NULL || stream->is_std) { if (stream == NULL || stream->is_std) {
_zos_syscall1(SYS_PRINT, (long)buf); _mtk_syscall1(SYS_PRINT, (long)buf);
} else { } else {
fwrite(buf, 1, (size_t)(n > 0 ? n : 0), stream); fwrite(buf, 1, (size_t)(n > 0 ? n : 0), stream);
} }
@@ -2063,20 +2076,20 @@ int vsprintf(char *str, const char *fmt, va_list ap) {
int remove(const char *path) { int remove(const char *path) {
if (path == NULL) return -1; if (path == NULL) return -1;
return (int)_zos_syscall1(SYS_FDELETE, (long)path); return (int)_mtk_syscall1(SYS_FDELETE, (long)path);
} }
int rename(const char *oldpath, const char *newpath) { int rename(const char *oldpath, const char *newpath) {
if (oldpath == NULL || newpath == NULL) return -1; if (oldpath == NULL || newpath == NULL) return -1;
return (int)_zos_syscall2(SYS_FRENAME, (long)oldpath, (long)newpath); return (int)_mtk_syscall2(SYS_FRENAME, (long)oldpath, (long)newpath);
} }
void perror(const char *s) { void perror(const char *s) {
if (s != NULL && s[0] != '\0') { if (s != NULL && s[0] != '\0') {
_zos_syscall1(SYS_PRINT, (long)s); _mtk_syscall1(SYS_PRINT, (long)s);
_zos_syscall1(SYS_PRINT, (long)": "); _mtk_syscall1(SYS_PRINT, (long)": ");
} }
_zos_syscall1(SYS_PRINT, (long)"error\n"); _mtk_syscall1(SYS_PRINT, (long)"error\n");
} }
FILE *tmpfile(void) { FILE *tmpfile(void) {
@@ -2090,9 +2103,9 @@ char *tmpnam(char *s) {
static unsigned long counter = 0; static unsigned long counter = 0;
char *out = (s != NULL) ? s : internal; char *out = (s != NULL) ? s : internal;
_zos_syscall1(SYS_FMKDIR, (long)"0:/tmp"); _mtk_syscall1(SYS_FMKDIR, (long)"0:/tmp");
snprintf(out, L_tmpnam, "0:/tmp/tmp%lu.tmp", snprintf(out, L_tmpnam, "0:/tmp/tmp%lu.tmp",
(unsigned long)_zos_syscall0(SYS_GETMILLISECONDS) + counter++); (unsigned long)_mtk_syscall0(SYS_GETMILLISECONDS) + counter++);
return out; return out;
} }
@@ -2212,9 +2225,9 @@ int access(const char *path, int mode) {
(void)mode; (void)mode;
int h = (int)_zos_syscall1(SYS_OPEN, (long)path); int h = (int)_mtk_syscall1(SYS_OPEN, (long)path);
if (h >= 0) { if (h >= 0) {
_zos_syscall1(SYS_CLOSE, (long)h); _mtk_syscall1(SYS_CLOSE, (long)h);
return 0; return 0;
} }
@@ -2232,33 +2245,33 @@ int open(const char *path, int flags, ...) {
return -1; return -1;
} }
int h = (int)_zos_syscall1(SYS_OPEN, (long)path); int h = (int)_mtk_syscall1(SYS_OPEN, (long)path);
int wants_create = (flags & O_CREAT) != 0; int wants_create = (flags & O_CREAT) != 0;
int wants_trunc = (flags & O_TRUNC) != 0; int wants_trunc = (flags & O_TRUNC) != 0;
if (h < 0) { if (h < 0) {
if (wants_create) { if (wants_create) {
h = (int)_zos_syscall1(SYS_FCREATE, (long)path); h = (int)_mtk_syscall1(SYS_FCREATE, (long)path);
} }
} else if (wants_create && (flags & O_EXCL)) { } else if (wants_create && (flags & O_EXCL)) {
_zos_syscall1(SYS_CLOSE, (long)h); _mtk_syscall1(SYS_CLOSE, (long)h);
errno = EEXIST; errno = EEXIST;
return -1; return -1;
} else if (wants_trunc) { } else if (wants_trunc) {
_zos_syscall1(SYS_CLOSE, (long)h); _mtk_syscall1(SYS_CLOSE, (long)h);
_zos_syscall1(SYS_FDELETE, (long)path); _mtk_syscall1(SYS_FDELETE, (long)path);
h = (int)_zos_syscall1(SYS_FCREATE, (long)path); h = (int)_mtk_syscall1(SYS_FCREATE, (long)path);
} }
if (h >= _FD_POS_MAX) { if (h >= _FD_POS_MAX) {
_zos_syscall1(SYS_CLOSE, (long)h); _mtk_syscall1(SYS_CLOSE, (long)h);
errno = EMFILE; errno = EMFILE;
return -1; return -1;
} }
if (h >= 0) { if (h >= 0) {
_fd_pos[h] = 0; _fd_pos[h] = 0;
if (flags & O_APPEND) { if (flags & O_APPEND) {
_fd_pos[h] = (unsigned long)_zos_syscall1(SYS_GETSIZE, (long)h); _fd_pos[h] = (unsigned long)_mtk_syscall1(SYS_GETSIZE, (long)h);
} }
} }
if (h < 0) { if (h < 0) {
@@ -2302,7 +2315,7 @@ int read(int fd, void *buf, size_t count) {
} }
unsigned long pos = _fd_pos[fd]; unsigned long pos = _fd_pos[fd];
int ret = (int)_zos_syscall4(SYS_READ, (long)fd, (long)buf, (long)pos, (long)count); int ret = (int)_mtk_syscall4(SYS_READ, (long)fd, (long)buf, (long)pos, (long)count);
if (ret > 0) if (ret > 0)
_fd_pos[fd] += (unsigned long)ret; _fd_pos[fd] += (unsigned long)ret;
return ret; return ret;
@@ -2314,7 +2327,7 @@ int write(int fd, const void *buf, size_t count) {
if (fd == STDOUT_FILENO || fd == STDERR_FILENO) { if (fd == STDOUT_FILENO || fd == STDERR_FILENO) {
const char *src = (const char *)buf; const char *src = (const char *)buf;
for (size_t i = 0; i < count; i++) for (size_t i = 0; i < count; i++)
_zos_syscall1(SYS_PUTCHAR, (long)(unsigned char)src[i]); _mtk_syscall1(SYS_PUTCHAR, (long)(unsigned char)src[i]);
return (int)count; return (int)count;
} }
@@ -2324,7 +2337,7 @@ int write(int fd, const void *buf, size_t count) {
} }
unsigned long pos = _fd_pos[fd]; unsigned long pos = _fd_pos[fd];
int ret = (int)_zos_syscall4(SYS_FWRITE, (long)fd, (long)buf, (long)pos, (long)count); int ret = (int)_mtk_syscall4(SYS_FWRITE, (long)fd, (long)buf, (long)pos, (long)count);
if (ret > 0) if (ret > 0)
_fd_pos[fd] += (unsigned long)ret; _fd_pos[fd] += (unsigned long)ret;
return ret; return ret;
@@ -2347,7 +2360,7 @@ int ftruncate(int fd, long length) {
return -1; return -1;
} }
size = (unsigned long)_zos_syscall1(SYS_GETSIZE, (long)fd); size = (unsigned long)_mtk_syscall1(SYS_GETSIZE, (long)fd);
if ((unsigned long)length == size) if ((unsigned long)length == size)
return 0; return 0;
@@ -2361,7 +2374,7 @@ int ftruncate(int fd, long length) {
unsigned long chunk = (unsigned long)length - size; unsigned long chunk = (unsigned long)length - size;
int written; int written;
if (chunk > sizeof(zeros)) chunk = sizeof(zeros); if (chunk > sizeof(zeros)) chunk = sizeof(zeros);
written = (int)_zos_syscall4(SYS_FWRITE, (long)fd, (long)zeros, written = (int)_mtk_syscall4(SYS_FWRITE, (long)fd, (long)zeros,
(long)size, (long)chunk); (long)size, (long)chunk);
if (written <= 0) { if (written <= 0) {
errno = EIO; errno = EIO;
@@ -2388,7 +2401,7 @@ ssize_t pread(int fd, void *buf, size_t count, off_t offset) {
errno = EINVAL; errno = EINVAL;
return -1; return -1;
} }
return (ssize_t)_zos_syscall4(SYS_READ, (long)fd, (long)buf, return (ssize_t)_mtk_syscall4(SYS_READ, (long)fd, (long)buf,
(long)offset, (long)count); (long)offset, (long)count);
} }
@@ -2401,7 +2414,7 @@ ssize_t pwrite(int fd, const void *buf, size_t count, off_t offset) {
errno = EINVAL; errno = EINVAL;
return -1; return -1;
} }
return (ssize_t)_zos_syscall4(SYS_FWRITE, (long)fd, (long)buf, return (ssize_t)_mtk_syscall4(SYS_FWRITE, (long)fd, (long)buf,
(long)offset, (long)count); (long)offset, (long)count);
} }
@@ -2411,7 +2424,7 @@ int close(int fd) {
return -1; return -1;
} }
_fd_pos[fd] = 0; _fd_pos[fd] = 0;
if ((int)_zos_syscall1(SYS_CLOSE, (long)fd) < 0) { if ((int)_mtk_syscall1(SYS_CLOSE, (long)fd) < 0) {
errno = EBADF; errno = EBADF;
return -1; return -1;
} }
@@ -2434,7 +2447,7 @@ long lseek(int fd, long offset, int whence) {
newpos = pos + (unsigned long)offset; newpos = pos + (unsigned long)offset;
break; break;
case 2: /* SEEK_END */ case 2: /* SEEK_END */
newpos = (unsigned long)_zos_syscall1(SYS_GETSIZE, (long)fd) + (unsigned long)offset; newpos = (unsigned long)_mtk_syscall1(SYS_GETSIZE, (long)fd) + (unsigned long)offset;
break; break;
default: default:
errno = EINVAL; errno = EINVAL;
@@ -2449,7 +2462,7 @@ int chdir(const char *path) {
errno = EINVAL; errno = EINVAL;
return -1; return -1;
} }
if ((int)_zos_syscall1(SYS_CHDIR, (long)path) < 0) { if ((int)_mtk_syscall1(SYS_CHDIR, (long)path) < 0) {
errno = ENOENT; errno = ENOENT;
return -1; return -1;
} }
@@ -2461,7 +2474,7 @@ char *getcwd(char *buf, size_t size) {
errno = EINVAL; errno = EINVAL;
return NULL; return NULL;
} }
int result = (int)_zos_syscall2(SYS_GETCWD, (long)buf, (long)size); int result = (int)_mtk_syscall2(SYS_GETCWD, (long)buf, (long)size);
if (result < 0) { if (result < 0) {
errno = result == -2 ? ERANGE : EIO; errno = result == -2 ? ERANGE : EIO;
return NULL; return NULL;
@@ -2470,7 +2483,9 @@ char *getcwd(char *buf, size_t size) {
} }
int isatty(int fd) { int isatty(int fd) {
return fd >= 0 && fd <= 2; if (fd < STDIN_FILENO || fd > STDERR_FILENO)
return 0;
return _mtk_syscall0(SYS_TERMINAL_ATTACHED) != 0;
} }
/* ======================================================================== /* ========================================================================
@@ -2480,7 +2495,7 @@ int isatty(int fd) {
int mkdir(const char *path, unsigned int mode) { int mkdir(const char *path, unsigned int mode) {
(void)mode; (void)mode;
if (path == NULL) return -1; if (path == NULL) return -1;
return (int)_zos_syscall1(SYS_FMKDIR, (long)path); return (int)_mtk_syscall1(SYS_FMKDIR, (long)path);
} }
/* The VFS exposes no inode numbers, so derive a stable id from the path and /* The VFS exposes no inode numbers, so derive a stable id from the path and
@@ -2509,7 +2524,7 @@ int stat(const char *path, struct stat *buf) {
/* Preferred path: real metadata from the filesystem driver. */ /* Preferred path: real metadata from the filesystem driver. */
struct _mtk_filestat st; struct _mtk_filestat st;
if (_zos_syscall2(SYS_STAT, (long)path, (long)&st) == 0) { if (_mtk_syscall2(SYS_STAT, (long)path, (long)&st) == 0) {
buf->st_mode = (mode_t)st.mode; buf->st_mode = (mode_t)st.mode;
/* A driver that reports no type bits still tells us whether the /* A driver that reports no type bits still tells us whether the
entry is a directory; without this S_ISREG/S_ISDIR both fail. */ entry is a directory; without this S_ISREG/S_ISDIR both fail. */
@@ -2527,11 +2542,11 @@ int stat(const char *path, struct stat *buf) {
/* Fallback for a filesystem whose driver has no Stat entry point: probe /* Fallback for a filesystem whose driver has no Stat entry point: probe
with open/readdir as before. Size is recoverable this way, timestamps with open/readdir as before. Size is recoverable this way, timestamps
and permissions are not. */ and permissions are not. */
int h = (int)_zos_syscall1(SYS_OPEN, (long)path); int h = (int)_mtk_syscall1(SYS_OPEN, (long)path);
if (h >= 0) { if (h >= 0) {
buf->st_mode = S_IFREG; buf->st_mode = S_IFREG;
buf->st_size = (off_t)_zos_syscall1(SYS_GETSIZE, (long)h); buf->st_size = (off_t)_mtk_syscall1(SYS_GETSIZE, (long)h);
_zos_syscall1(SYS_CLOSE, (long)h); _mtk_syscall1(SYS_CLOSE, (long)h);
buf->st_blocks = (blkcnt_t)((buf->st_size + 511) / 512); buf->st_blocks = (blkcnt_t)((buf->st_size + 511) / 512);
return 0; return 0;
} }
@@ -2556,7 +2571,7 @@ int fstat(int fd, struct stat *buf) {
cannot report real timestamps or permissions the way stat() does. cannot report real timestamps or permissions the way stat() does.
Callers needing those must stat the path instead. */ Callers needing those must stat the path instead. */
buf->st_mode = S_IFREG; buf->st_mode = S_IFREG;
buf->st_size = (off_t)_zos_syscall1(SYS_GETSIZE, (long)fd); buf->st_size = (off_t)_mtk_syscall1(SYS_GETSIZE, (long)fd);
/* Handles do not map back to paths, so fake a per-handle inode in a /* Handles do not map back to paths, so fake a per-handle inode in a
range disjoint from the path hashes used by stat(). */ range disjoint from the path hashes used by stat(). */
buf->st_ino = 0x4000000000000000UL + (unsigned long)fd; buf->st_ino = 0x4000000000000000UL + (unsigned long)fd;
@@ -2583,7 +2598,7 @@ DIR *opendir(const char *name) {
} }
const char *raw_names[256]; const char *raw_names[256];
int count = (int)_zos_syscall3(SYS_READDIR, (long)name, (long)raw_names, 256L); int count = (int)_mtk_syscall3(SYS_READDIR, (long)name, (long)raw_names, 256L);
if (count < 0) { if (count < 0) {
free(dir); free(dir);
errno = ENOTDIR; errno = ENOTDIR;
@@ -3216,7 +3231,7 @@ int raise(int sig) {
======================================================================== */ ======================================================================== */
clock_t clock(void) { clock_t clock(void) {
return (clock_t)_zos_syscall0(SYS_GETMILLISECONDS); return (clock_t)_mtk_syscall0(SYS_GETMILLISECONDS);
} }
time_t time(time_t *tloc) { time_t time(time_t *tloc) {
@@ -3423,7 +3438,7 @@ size_t strftime(char *s, size_t max, const char *format, const struct tm *tm) {
} }
int gettimeofday(struct timeval *tv, struct timezone *tz) { int gettimeofday(struct timeval *tv, struct timezone *tz) {
long ms = (long)_zos_syscall0(SYS_GETMILLISECONDS); long ms = (long)_mtk_syscall0(SYS_GETMILLISECONDS);
if (tv != NULL) { if (tv != NULL) {
tv->tv_sec = (long)time(NULL); tv->tv_sec = (long)time(NULL);
@@ -3444,7 +3459,7 @@ int gettimeofday(struct timeval *tv, struct timezone *tz) {
======================================================================== */ ======================================================================== */
unsigned int sleep(unsigned int seconds) { unsigned int sleep(unsigned int seconds) {
_zos_syscall1(SYS_SLEEP_MS, (long)seconds * 1000L); _mtk_syscall1(SYS_SLEEP_MS, (long)seconds * 1000L);
return 0; return 0;
} }
@@ -3457,7 +3472,7 @@ int usleep(unsigned long usec) {
unsigned long ms = usec / 1000UL; unsigned long ms = usec / 1000UL;
if (ms == 0 && usec != 0) if (ms == 0 && usec != 0)
ms = 1; ms = 1;
_zos_syscall1(SYS_SLEEP_MS, (long)ms); _mtk_syscall1(SYS_SLEEP_MS, (long)ms);
return 0; return 0;
} }
@@ -3546,7 +3561,7 @@ int utime(const char *path, const struct utimbuf *times) {
return -1; return -1;
} }
long result = _zos_syscall4(SYS_UTIME, (long)path, long result = _mtk_syscall4(SYS_UTIME, (long)path,
times != NULL ? (long)times->actime : 0, times != NULL ? (long)times->actime : 0,
times != NULL ? (long)times->modtime : 0, times != NULL ? (long)times->modtime : 0,
times == NULL); times == NULL);
@@ -3608,6 +3623,20 @@ int mbtowc(wchar_t *pwc, const char *s, size_t n) {
return 1; return 1;
} }
/* Restartable form of mbtowc. The C locale carries no shift state, so ps is
ignored; binutils reaches for this once configure sees mbstate_t. */
size_t mbrtowc(wchar_t *pwc, const char *s, size_t n, mbstate_t *ps) {
(void)ps;
if (s == NULL) return 0; /* query: the C locale is stateless */
if (n == 0) return (size_t)-2; /* incomplete: no bytes to examine */
if (*s == '\0') {
if (pwc != NULL) *pwc = 0;
return 0;
}
if (pwc != NULL) *pwc = (wchar_t)(unsigned char)*s;
return 1;
}
int wctomb(char *s, wchar_t wc) { int wctomb(char *s, wchar_t wc) {
if (s == NULL) return 0; if (s == NULL) return 0;
*s = (char)wc; *s = (char)wc;
@@ -3922,7 +3951,7 @@ int unlink(const char *path) {
errno = EINVAL; errno = EINVAL;
return -1; return -1;
} }
if ((int)_zos_syscall1(SYS_FDELETE, (long)path) < 0) { if ((int)_mtk_syscall1(SYS_FDELETE, (long)path) < 0) {
errno = ENOENT; errno = ENOENT;
return -1; return -1;
} }
@@ -3940,12 +3969,12 @@ int fcntl(int fd, int cmd, ...) {
switch (cmd) { switch (cmd) {
case F_GETFD: case F_GETFD:
case F_GETFL: { case F_GETFL: {
int probe = (int)_zos_syscall1(SYS_DUPHANDLE, (long)fd); int probe = (int)_mtk_syscall1(SYS_DUPHANDLE, (long)fd);
if (probe < 0) { if (probe < 0) {
errno = EBADF; errno = EBADF;
return -1; return -1;
} }
_zos_syscall1(SYS_CLOSE, (long)probe); _mtk_syscall1(SYS_CLOSE, (long)probe);
return (cmd == F_GETFL) ? O_RDWR : 0; return (cmd == F_GETFL) ? O_RDWR : 0;
} }
case F_SETFD: case F_SETFD:
@@ -3960,7 +3989,7 @@ int fcntl(int fd, int cmd, ...) {
} }
pid_t getpid(void) { pid_t getpid(void) {
return (pid_t)_zos_syscall0(SYS_GETPID); return (pid_t)_mtk_syscall0(SYS_GETPID);
} }
/* True dup2 needs kernel support for duplicating into a chosen slot. /* True dup2 needs kernel support for duplicating into a chosen slot.
@@ -3973,22 +4002,22 @@ int dup2(int oldfd, int newfd) {
return -1; return -1;
} }
if (oldfd == newfd) { if (oldfd == newfd) {
int probe = (int)_zos_syscall1(SYS_DUPHANDLE, (long)oldfd); int probe = (int)_mtk_syscall1(SYS_DUPHANDLE, (long)oldfd);
if (probe < 0) { if (probe < 0) {
errno = EBADF; errno = EBADF;
return -1; return -1;
} }
_zos_syscall1(SYS_CLOSE, (long)probe); _mtk_syscall1(SYS_CLOSE, (long)probe);
return newfd; return newfd;
} }
_zos_syscall1(SYS_CLOSE, (long)newfd); _mtk_syscall1(SYS_CLOSE, (long)newfd);
int h = (int)_zos_syscall1(SYS_DUPHANDLE, (long)oldfd); int h = (int)_mtk_syscall1(SYS_DUPHANDLE, (long)oldfd);
if (h < 0) { if (h < 0) {
errno = EBADF; errno = EBADF;
return -1; return -1;
} }
if (h != newfd) { if (h != newfd) {
_zos_syscall1(SYS_CLOSE, (long)h); _mtk_syscall1(SYS_CLOSE, (long)h);
errno = EBUSY; errno = EBUSY;
return -1; return -1;
} }
@@ -4004,7 +4033,7 @@ pid_t waitpid(pid_t pid, int *status, int options) {
errno = ECHILD; /* no process groups / wait-any support */ errno = ECHILD; /* no process groups / wait-any support */
return -1; return -1;
} }
long code = _zos_syscall1(SYS_WAITPID, (long)pid); long code = _mtk_syscall1(SYS_WAITPID, (long)pid);
if (status != NULL) { if (status != NULL) {
if (code >= 256) { if (code >= 256) {
*status = (int)((code - 256) & 0x7F); /* signaled */ *status = (int)((code - 256) & 0x7F); /* signaled */
@@ -4016,7 +4045,7 @@ pid_t waitpid(pid_t pid, int *status, int options) {
} }
void _exit(int status) { void _exit(int status) {
_zos_syscall1(SYS_EXIT, (long)status); _mtk_syscall1(SYS_EXIT, (long)status);
__builtin_unreachable(); __builtin_unreachable();
} }
@@ -4024,7 +4053,7 @@ void _exit(int status) {
accepted for API shape but not delivered as a handler. */ accepted for API shape but not delivered as a handler. */
int kill(pid_t pid, int sig) { int kill(pid_t pid, int sig) {
(void)sig; (void)sig;
if ((int)_zos_syscall1(SYS_KILL, (long)pid) < 0) { if ((int)_mtk_syscall1(SYS_KILL, (long)pid) < 0) {
errno = ESRCH; errno = ESRCH;
return -1; return -1;
} }
@@ -4068,7 +4097,7 @@ void *mmap(void *addr, size_t length, int prot, int flags, int fd,
errno = EINVAL; errno = EINVAL;
return MAP_FAILED; return MAP_FAILED;
} }
void *p = (void *)_zos_syscall2(SYS_MMAP_ANON, (long)length, (long)prot); void *p = (void *)_mtk_syscall2(SYS_MMAP_ANON, (long)length, (long)prot);
if (p == NULL) { if (p == NULL) {
errno = ENOMEM; errno = ENOMEM;
return MAP_FAILED; return MAP_FAILED;
@@ -4082,7 +4111,7 @@ int munmap(void *addr, size_t length) {
errno = EINVAL; errno = EINVAL;
return -1; return -1;
} }
if (_zos_syscall2(SYS_MUNMAP, (long)addr, (long)length) < 0) { if (_mtk_syscall2(SYS_MUNMAP, (long)addr, (long)length) < 0) {
errno = EINVAL; errno = EINVAL;
return -1; return -1;
} }
@@ -4097,7 +4126,7 @@ int mprotect(void *addr, size_t length, int prot) {
errno = EINVAL; errno = EINVAL;
return -1; return -1;
} }
if (_zos_syscall3(SYS_MPROTECT, (long)addr, (long)length, (long)prot) < 0) { if (_mtk_syscall3(SYS_MPROTECT, (long)addr, (long)length, (long)prot) < 0) {
errno = EINVAL; errno = EINVAL;
return -1; return -1;
} }
@@ -4222,7 +4251,7 @@ int posix_spawn(pid_t *pid, const char *path,
long child; long child;
if (envp == NULL) { if (envp == NULL) {
child = _zos_syscall2(SYS_SPAWN, (long)path, (long)argsbuf); child = _mtk_syscall2(SYS_SPAWN, (long)path, (long)argsbuf);
} else { } else {
static char envbuf[4096]; static char envbuf[4096];
size_t envlen = 0; size_t envlen = 0;
@@ -4235,7 +4264,7 @@ int posix_spawn(pid_t *pid, const char *path,
envlen += len; envlen += len;
} }
envbuf[envlen++] = '\0'; envbuf[envlen++] = '\0';
child = _zos_syscall4(SYS_SPAWN_ENV, (long)path, (long)argsbuf, child = _mtk_syscall4(SYS_SPAWN_ENV, (long)path, (long)argsbuf,
(long)envbuf, (long)envlen); (long)envbuf, (long)envlen);
} }
if (child < 0) { if (child < 0) {
@@ -4359,13 +4388,13 @@ int dup(int fd) {
errno = EBADF; errno = EBADF;
return -1; return -1;
} }
int h = (int)_zos_syscall1(SYS_DUPHANDLE, (long)fd); int h = (int)_mtk_syscall1(SYS_DUPHANDLE, (long)fd);
if (h < 0) { if (h < 0) {
errno = EBADF; errno = EBADF;
return -1; return -1;
} }
if (!_fd_pos_valid(h)) { if (!_fd_pos_valid(h)) {
_zos_syscall1(SYS_CLOSE, (long)h); _mtk_syscall1(SYS_CLOSE, (long)h);
errno = EMFILE; errno = EMFILE;
return -1; return -1;
} }
@@ -4396,7 +4425,7 @@ FILE *fdopen(int fd, const char *mode) {
} }
f->handle = fd; f->handle = fd;
f->pos = _fd_pos[fd]; f->pos = _fd_pos[fd];
f->size = (unsigned long)_zos_syscall1(SYS_GETSIZE, (long)fd); f->size = (unsigned long)_mtk_syscall1(SYS_GETSIZE, (long)fd);
f->eof = 0; f->eof = 0;
f->error = 0; f->error = 0;
f->is_std = 0; f->is_std = 0;
@@ -4455,7 +4484,7 @@ char *mktemp(char *template_) {
} }
static const char cs[] = static const char cs[] =
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
unsigned long seed = (unsigned long)_zos_syscall0(SYS_GETMILLISECONDS) unsigned long seed = (unsigned long)_mtk_syscall0(SYS_GETMILLISECONDS)
^ ((unsigned long)getpid() << 16); ^ ((unsigned long)getpid() << 16);
for (int attempt = 0; attempt < 128; attempt++) { for (int attempt = 0; attempt < 128; attempt++) {
unsigned long v = seed + (unsigned long)attempt * 7919UL; unsigned long v = seed + (unsigned long)attempt * 7919UL;
@@ -4463,11 +4492,11 @@ char *mktemp(char *template_) {
template_[len - 6 + i] = cs[v % (sizeof(cs) - 1)]; template_[len - 6 + i] = cs[v % (sizeof(cs) - 1)];
v /= sizeof(cs) - 1; v /= sizeof(cs) - 1;
} }
int h = (int)_zos_syscall1(SYS_OPEN, (long)template_); int h = (int)_mtk_syscall1(SYS_OPEN, (long)template_);
if (h < 0) { if (h < 0) {
return template_; /* name is free */ return template_; /* name is free */
} }
_zos_syscall1(SYS_CLOSE, (long)h); _mtk_syscall1(SYS_CLOSE, (long)h);
} }
template_[0] = '\0'; template_[0] = '\0';
errno = EEXIST; errno = EEXIST;
@@ -4486,7 +4515,7 @@ int mkstemp(char *template_) {
} }
static const char cs[] = static const char cs[] =
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
unsigned long seed = (unsigned long)_zos_syscall0(SYS_GETMILLISECONDS) unsigned long seed = (unsigned long)_mtk_syscall0(SYS_GETMILLISECONDS)
^ ((unsigned long)getpid() << 16); ^ ((unsigned long)getpid() << 16);
for (int attempt = 0; attempt < 128; attempt++) { for (int attempt = 0; attempt < 128; attempt++) {
unsigned long v = seed + (unsigned long)attempt * 7919UL; unsigned long v = seed + (unsigned long)attempt * 7919UL;
+44
View File
@@ -0,0 +1,44 @@
.TH RTLSDR 3
.SH NAME
rtlsdr.lib - userspace RTL2832U and R820T2 radio driver
.SH SYNOPSIS
.nf
#include <rtlsdr/rtlsdr.h>
.fi
.SH DESCRIPTION
The library at
.B 0:/os/rtlsdr.lib
drives supported Realtek RTL2832U USB receivers from userspace through the
generic process-owned USB API. Supported USB product IDs are 0bda:2832 and
0bda:2838. Samples are returned as interleaved unsigned 8-bit I/Q pairs.
Applications load the library with libloader, resolve the
.B rtlsdr_*
symbols declared in the header, enumerate devices, open one, configure it,
start bulk reception, read samples, stop, and close it. Claims are exclusive
and are released by the kernel if the process exits or the device disconnects.
.SH API
.B rtlsdr_count
and
.B rtlsdr_get_device_info
enumerate compatible unbound USB interfaces.
.B rtlsdr_open
claims an interface and returns an opaque device pointer.
.B rtlsdr_set_center_freq,
.B rtlsdr_set_sample_rate,
.B rtlsdr_set_tuner_gain_mode,
.B rtlsdr_set_tuner_gain,
.B rtlsdr_set_freq_correction,
.B rtlsdr_set_agc_mode,
and
.B rtlsdr_set_direct_sampling
configure the receiver.
.B rtlsdr_start,
.B rtlsdr_read,
and
.B rtlsdr_stop
control non-blocking reception.
.SH NOTES
Syscall numbers 140 through 148 are reserved former SDR slots and are not an
API. Device-specific control and tuner programming live entirely in this
library.
+8 -2
View File
@@ -129,8 +129,14 @@
fontscale [n] Get or set terminal font scale -- see fontscale(1) fontscale [n] Get or set terminal font scale -- see fontscale(1)
lua Lua interpreter lua Lua interpreter
tcc TinyCC (in-system C compiler) tcc TinyCC (in-system C compiler)
reset Reboot the system reset / reboot Request a supervised system reboot
shutdown Shut down the system shutdown / poweroff Request a supervised system shutdown
suspend Enter ACPI sleep
Interactive shutdown and reboot are shell builtins: they post a
capability-checked request and exit the console, allowing login.elf to
flush filesystems and perform the final power operation. They do not grant
CAP_POWER_CONTROL to the shell.
.SS Network commands .SS Network commands
ping <host> Send ICMP echo requests -- see ping(1) ping <host> Send ICMP echo requests -- see ping(1)
+313 -99
View File
@@ -3,10 +3,9 @@
syscalls - overview of MontaukOS system calls syscalls - overview of MontaukOS system calls
.SH DESCRIPTION .SH DESCRIPTION
MontaukOS provides 150 system calls (numbers 0-149, sparsely MontaukOS provides 178 system calls (numbers 0-186, with numbers
assigned -- not every number in the range is in use) for 140-148 reserved) for userspace programs. Syscalls use the x86-64
userspace programs. Syscalls use the x86-64 SYSCALL instruction SYSCALL instruction with the following register convention:
with the following register convention:
RAX Syscall number (in) / return value (out) RAX Syscall number (in) / return value (out)
RDI Argument 1 RDI Argument 1
@@ -21,6 +20,66 @@
montauk:: namespace. This page groups syscalls the same way the montauk:: namespace. This page groups syscalls the same way the
kernel source does (one subsystem header per group). kernel source does (one subsystem header per group).
.SH CAPABILITY SECURITY
Privileged authority is stored in kernel-owned process credentials, not
inferred from a process name, PID, executable path, or merely from the
owner name returned by SYS_GETUSER. The owner identity may namespace
per-user resources such as the clipboard, but no owner name implies
administrative authority. The kernel-created init process is
the root of the delegation tree. Login authenticates a user and delegates
the appropriate session capabilities; there is no special "system" user
shortcut in the kernel.
Each process has three uint64_t masks, exposed in ProcInfo:
permitted capabilities owned by the process
effective permitted capabilities accepted by syscall checks
delegable permitted capabilities that may be given to children
Effective and delegable must be subsets of permitted. SYS_SPAWN and the
other ordinary spawn variants give the child no capabilities. A parent
uses SYS_SPAWN_CAPS to make an explicit delegation. Every requested
permitted or delegable bit must be present in the parent's delegable mask,
so a non-delegable grant cannot be propagated through another generation.
Invalid or unauthorized requests return SYS_ERR_PERMISSION (-13).
Capability bits and protected operations are:
CAP_PROCESS_ADMIN kill unrelated processes or whole sessions
CAP_POWER_REQUEST post a graceful shutdown/reboot request
CAP_POWER_CONTROL consume power requests; reset or power off
CAP_SUSPEND enter ACPI sleep
CAP_STORAGE_ADMIN change partitions, mounts, or filesystems
CAP_RAW_STORAGE raw disk reads and writes
CAP_NETWORK_ADMIN change network/Wi-Fi configuration
CAP_SET_TIME set wall-clock time or timezone
CAP_USER_ADMIN manage users/trusted config; override owner at spawn
CAP_DISPLAY_ADMIN set display mode or brightness
CAP_DEVICE_ADMIN claim USB interfaces or change Bluetooth state
CAP_LOG_READ read the kernel log
CAP_SYSTEM_IMAGE write the program images in 0:/os and 0:/apps
CAP_STANDARD_SESSION contains CAP_POWER_REQUEST and CAP_SUSPEND.
CAP_ADMIN_SESSION adds the administrative capabilities above except
CAP_POWER_CONTROL and CAP_SYSTEM_IMAGE. Final shutdown/reset authority is
deliberately retained by login, the trusted session supervisor.
SYS_POWERINFO remains readable without a capability, so the powermgr GUI
and power command are monitors, not privileged power daemons.
CAP_SYSTEM_IMAGE is likewise excluded from every session and from the
"all" wildcard in 0:/config/capabilities.toml, and must be named
explicitly to be granted. Capability grants are keyed on binary path, so
write access to 0:/os or 0:/apps is equivalent to holding whatever those
images are granted the next time a launcher runs them. Partitioning or
formatting a volume is CAP_STORAGE_ADMIN and does not carry it.
The authentication and trusted-service configuration files users.toml,
setup.toml, init.toml, and ssh.toml are readable by ordinary processes but
require CAP_USER_ADMIN to create, replace, delete, or write. The kernel
also protects the 0:/config directory entry against replacement. This
prevents changing a userspace role string from becoming a route to new
kernel authority at the next login or boot.
.SH PROCESS MANAGEMENT .SH PROCESS MANAGEMENT
.B SYS_EXIT (0) .B SYS_EXIT (0)
Terminate the calling process. Terminate the calling process.
@@ -40,17 +99,33 @@
.B SYS_SPAWN (20) .B SYS_SPAWN (20)
Spawn a new process from an ELF binary on the VFS. The child inherits Spawn a new process from an ELF binary on the VFS. The child inherits
a snapshot of the caller's environment. a snapshot of the caller's environment but no capabilities.
int montauk::spawn(const char* path, const char* args = nullptr); int montauk::spawn(const char* path, const char* args = nullptr);
.B SYS_SPAWN_CAPS (185)
Spawn a child with explicit permitted, effective, and delegable masks.
The child masks must satisfy the subset rules described under CAPABILITY
SECURITY. Passing a non-null user override additionally requires the
caller to have effective CAP_USER_ADMIN; null inherits the parent owner.
int montauk::spawn_with_caps(
const char* path, const char* args, const char* user,
const montauk::abi::SpawnCapabilities& capabilities);
.B SYS_WAITPID (23) .B SYS_WAITPID (23)
Block until the given process has exited. Block until the given process has exited. Returns 0-255 for a normal
void montauk::waitpid(int pid); exit, 256 plus the signal number if it was killed or crashed, or 0 if
the PID is unknown or its exit record is no longer available.
int montauk::waitpid(int pid);
.B SYS_GETARGS (25) .B SYS_GETARGS (25)
Get the argument string passed to this process at spawn time. Get the argument string passed to this process at spawn time.
int montauk::getargs(char* buf, uint64_t maxLen); int montauk::getargs(char* buf, uint64_t maxLen);
.B SYS_GETEXECPATH (151)
Copy the absolute path from which this process was spawned into buf.
Returns the copied path length, or -1 on invalid arguments.
int mtk_getexecpath(char* buf, unsigned long maxLen);
.B SYS_GETENVIRON (171), SYS_SETENVIRON (172), SYS_SPAWN_ENV (173) .B SYS_GETENVIRON (171), SYS_SETENVIRON (172), SYS_SPAWN_ENV (173)
Libc process-environment transport. Environment data is encoded as Libc process-environment transport. Environment data is encoded as
consecutive NAME=VALUE strings with a final empty string. Applications consecutive NAME=VALUE strings with a final empty string. Applications
@@ -58,11 +133,13 @@
.B SYS_PROCLIST (61) .B SYS_PROCLIST (61)
List running processes (pid, parent, state, name, heap usage, List running processes (pid, parent, state, name, heap usage,
accumulated CPU time). accumulated CPU time, and permitted/effective/delegable capability masks).
int montauk::proclist(montauk::abi::ProcInfo* buf, int max); int montauk::proclist(montauk::abi::ProcInfo* buf, int max);
.B SYS_KILL (62) .B SYS_KILL (62)
Terminate another process by PID. Terminate a process by PID. Any process may terminate one of its own
descendants. Terminating an unrelated process requires
CAP_PROCESS_ADMIN.
int montauk::kill(int pid); int montauk::kill(int pid);
.B SYS_SETSESSION (174) .B SYS_SETSESSION (174)
@@ -73,6 +150,7 @@
.B SYS_KILLSESSION (175) .B SYS_KILLSESSION (175)
Terminate all live processes in a process session. Returns the number of Terminate all live processes in a process session. Returns the number of
members signalled; repeat until zero to wait for complete teardown. members signalled; repeat until zero to wait for complete teardown.
Requires CAP_PROCESS_ADMIN.
int montauk::killsession(int sessionId); int montauk::killsession(int sessionId);
.B SYS_CHDIR (96) .B SYS_CHDIR (96)
@@ -87,7 +165,8 @@
.B SYS_SETUSER (92) .B SYS_SETUSER (92)
Associate a process with a logged-in user name (used by login/session Associate a process with a logged-in user name (used by login/session
management). management and per-user resource isolation). Requires CAP_USER_ADMIN.
The name never grants capabilities or implies administrator status.
int montauk::setuser(int pid, const char* name); int montauk::setuser(int pid, const char* name);
.B SYS_GETUSER (93) .B SYS_GETUSER (93)
@@ -95,7 +174,7 @@
int montauk::getuser(char* buf, uint64_t maxLen); int montauk::getuser(char* buf, uint64_t maxLen);
.SH THREADING .SH THREADING
Threads share the spawning process's address space and heap Threads share the calling process's address space and heap
(see montauk/heap.h for the heap lock). Declared in (see montauk/heap.h for the heap lock). Declared in
montauk/thread.h. montauk/thread.h.
@@ -146,9 +225,9 @@
void montauk::close(int handle); void montauk::close(int handle);
.B SYS_READDIR (10) .B SYS_READDIR (10)
List directory entries (max 256 per call for VFS directories, List directory entries (max 256 per call for ramdisk directories,
128 for driver-backed listings such as 0:/os/). For larger 128 for FAT32 and ext2 directories). For larger directories use
directories use SYS_READDIR_AT. SYS_READDIR_AT.
int montauk::readdir(const char* path, const char** names, int max); int montauk::readdir(const char* path, const char** names, int max);
.B SYS_READDIR_AT (136) .B SYS_READDIR_AT (136)
@@ -182,6 +261,15 @@
manager move operations). manager move operations).
int montauk::frename(const char* oldPath, const char* newPath); int montauk::frename(const char* oldPath, const char* newPath);
.B SYS_STAT (152)
Get a path's size, timestamps, mode, and directory status.
int montauk::stat(const char* path, montauk::abi::FileStat* out);
.B SYS_UTIME (167)
Set a path's access and modification timestamps. This is the kernel
transport used by the libc utime(3) interface.
int utime(const char* path, const struct utimbuf* times);
.B SYS_DRIVELIST (79) .B SYS_DRIVELIST (79)
List mounted drive numbers. List mounted drive numbers.
int montauk::drivelist(int* outDrives, int max); int montauk::drivelist(int* outDrives, int max);
@@ -234,14 +322,21 @@
uint64_t montauk::get_milliseconds(); uint64_t montauk::get_milliseconds();
.B SYS_GETTIME (28) .B SYS_GETTIME (28)
Get the current wall-clock date and time (UTC). Get the current wall-clock date and time in the configured timezone.
Fills a montauk::abi::DateTime struct with Year, Month, Day, Fills a montauk::abi::DateTime struct with Year, Month, Day,
Hour, Minute, and Second fields. Hour, Minute, and Second fields.
void montauk::gettime(montauk::abi::DateTime* out); void montauk::gettime(montauk::abi::DateTime* out);
.B SYS_SETUNIXTIME (153)
Set the system wall clock from a UTC Unix timestamp. Returns 0 on
success or -1 if the timestamp is outside the supported range. Requires
CAP_SET_TIME.
int montauk::set_unix_time(int64_t unixSeconds);
.B SYS_SETTZ (90) .B SYS_SETTZ (90)
Set the process/system timezone offset, in minutes from UTC. Set the system-wide timezone offset, in minutes from UTC. Requires
void montauk::settz(int offset_minutes); CAP_SET_TIME.
int montauk::settz(int offset_minutes);
.B SYS_GETTZ (91) .B SYS_GETTZ (91)
Get the current timezone offset, in minutes from UTC. Get the current timezone offset, in minutes from UTC.
@@ -276,7 +371,8 @@
Block until the input serial number differs from Block until the input serial number differs from
observedSerial or the timeout elapses; used to sleep observedSerial or the timeout elapses; used to sleep
efficiently between input-driven redraws. efficiently between input-driven redraws.
uint64_t montauk::input_wait(uint64_t observedSerial, uint64_t timeoutMs); uint64_t montauk::input_wait(uint64_t observedSerial,
uint64_t timeoutMs);
.SH MOUSE .SH MOUSE
.B SYS_MOUSESTATE (47) .B SYS_MOUSESTATE (47)
@@ -307,7 +403,8 @@
void montauk::get_netcfg(montauk::abi::NetCfg* out); void montauk::get_netcfg(montauk::abi::NetCfg* out);
.B SYS_SETNETCFG (38) .B SYS_SETNETCFG (38)
Set the network configuration (IP, mask, gateway, DNS server). Set the network configuration (IP, mask, gateway, DNS server). Requires
CAP_NETWORK_ADMIN.
int montauk::set_netcfg(const montauk::abi::NetCfg* cfg); int montauk::set_netcfg(const montauk::abi::NetCfg* cfg);
.B SYS_NETSTATUS (125) .B SYS_NETSTATUS (125)
@@ -315,6 +412,51 @@
and RX/TX packet counters. and RX/TX packet counters.
int montauk::net_status(montauk::abi::NetStatus* out); int montauk::net_status(montauk::abi::NetStatus* out);
.B SYS_NETIFS (165)
List registered link-layer network interfaces. The global IP
configuration belongs to the entry whose active field is set.
int montauk::net_interfaces(montauk::abi::NetIfInfo* buf,
int maxCount);
.SH WI-FI
.B SYS_WIFI_SCAN (158)
Perform a channel scan and block until it finishes or timeoutMs
elapses. Returns the number of results, or -1 if no adapter is ready.
Requires CAP_NETWORK_ADMIN because it changes radio state.
int montauk::wifi_scan(montauk::abi::WifiNetwork* buf,
int maxCount, uint32_t timeoutMs);
.B SYS_WIFI_INFO (159)
Get adapter, link, scan, join, and last-error status.
int montauk::wifi_info(montauk::abi::WifiInfo* out);
.B SYS_WIFI_CONNECT (160)
Join a network and block until the link is up or the attempt fails.
Requires CAP_NETWORK_ADMIN.
int montauk::wifi_connect(const char* ssid,
const char* password);
.B SYS_WIFI_DISCONNECT (161)
Disconnect from the current Wi-Fi network. Requires CAP_NETWORK_ADMIN.
int montauk::wifi_disconnect();
.B SYS_WIFI_SCAN_START (162)
Start a non-blocking channel scan. Returns 0 if started, 1 if a scan
is already running, or -1 if no adapter is ready. Requires
CAP_NETWORK_ADMIN.
int montauk::wifi_scan_start(uint32_t timeoutMs);
.B SYS_WIFI_RESULTS (163)
Copy results from the most recent scan without accessing the radio.
int montauk::wifi_results(montauk::abi::WifiNetwork* buf,
int maxCount);
.B SYS_WIFI_CONNECT_ASYNC (164)
Start a non-blocking network join. Observe SYS_WIFI_INFO for progress
and the final result. Requires CAP_NETWORK_ADMIN.
int montauk::wifi_connect_async(const char* ssid,
const char* password);
.SH SOCKETS .SH SOCKETS
.B SYS_SOCKET (29) .B SYS_SOCKET (29)
Create a socket. type=SOCK_TCP (1) or SOCK_UDP (2). Create a socket. type=SOCK_TCP (1) or SOCK_UDP (2).
@@ -370,6 +512,32 @@
Map the framebuffer into process memory. Map the framebuffer into process memory.
void* montauk::fb_map(); void* montauk::fb_map();
.B SYS_FBFLIP (150)
Flip between double-buffered hardware scanout buffers. Index -1
queries support; index -2 acquires ownership and returns the current
front-buffer index. Flag bit 0 waits for vertical blank.
int64_t montauk::fb_flip(int64_t index, uint64_t flags);
.SH DISPLAY CONTROL
.B SYS_DISPLAYINFO (154)
Get connector, mode, capability, and brightness information.
int montauk::display_info(montauk::abi::DisplayInfo* out);
.B SYS_DISPLAYMODES (155)
Enumerate supported display modes. Returns the number written.
int montauk::display_modes(montauk::abi::DisplayModeInfo* out,
int maxCount);
.B SYS_DISPLAYSETMODE (156)
Switch to a mode returned by SYS_DISPLAYMODES. Requires
CAP_DISPLAY_ADMIN.
int montauk::display_set_mode(int modeIndex);
.B SYS_DISPLAYBRIGHTNESS (157)
Set brightness to 0-100 percent, or pass -1 to query it. Setting requires
CAP_DISPLAY_ADMIN; querying does not.
int montauk::display_brightness(int percent = -1);
.SH TERMINAL .SH TERMINAL
.B SYS_TERMSIZE (24) .B SYS_TERMSIZE (24)
Get terminal dimensions (columns and rows). Get terminal dimensions (columns and rows).
@@ -391,15 +559,18 @@
.SH POWER MANAGEMENT .SH POWER MANAGEMENT
.B SYS_RESET (26) .B SYS_RESET (26)
Reboot the system. Reboot the system. Requires CAP_POWER_CONTROL. A successful call does not
[[noreturn]] void montauk::reset(); return; an unauthorized call returns SYS_ERR_PERMISSION.
int montauk::reset();
.B SYS_SHUTDOWN (27) .B SYS_SHUTDOWN (27)
Shut down the system. Shut down the system. Requires CAP_POWER_CONTROL. A successful call does
[[noreturn]] void montauk::shutdown(); not return; an unauthorized call returns SYS_ERR_PERMISSION.
int montauk::shutdown();
.B SYS_SUSPEND (89) .B SYS_SUSPEND (89)
Enter ACPI S3 sleep. Returns after wake, 0 on success. Enter ACPI S3 sleep. Returns after wake, 0 on success. Requires
CAP_SUSPEND.
int montauk::suspend(); int montauk::suspend();
.B SYS_POWER_REQUEST (135) .B SYS_POWER_REQUEST (135)
@@ -407,19 +578,28 @@
posts a pending action (POWER_REQ_SHUTDOWN / POWER_REQ_REBOOT) posts a pending action (POWER_REQ_SHUTDOWN / POWER_REQ_REBOOT)
then exits; login.elf reads it with POWER_REQ_QUERY then exits; login.elf reads it with POWER_REQ_QUERY
(read-and-clear), runs the shutdown stages, and finally calls (read-and-clear), runs the shutdown stages, and finally calls
shutdown()/reset(). See montauk::abi::PowerRequestAction. shutdown()/reset(). Posting requires CAP_POWER_REQUEST; querying and
consuming the request requires CAP_POWER_CONTROL. See
montauk::abi::PowerRequestAction.
A request may also come from inside a session -- the shell's shutdown
builtin posts one. Since login only reads the request after the session
leader exits, the leader polls POWER_REQ_PEEK, a non-destructive read
requiring only CAP_POWER_REQUEST, and exits when one is pending.
int montauk::power_request(int action); int montauk::power_request(int action);
int montauk::power_request_pending();
.B SYS_POWERINFO (149) .B SYS_POWERINFO (149)
Get the CPU power/thermal snapshot (HWP state, throttling, Get the CPU power/thermal snapshot (HWP state, throttling,
package temperature, base/max/effective frequency). Returns 0 package temperature, base/max/effective frequency). Returns 0
on success, -1 if unsupported by the running hardware. on success, -1 if unsupported by the running hardware.
int montauk::syscall1(SYS_POWERINFO, (uint64_t)&out); montauk::abi::PowerInfo out;
// out: montauk::abi::PowerInfo* int64_t rc = montauk::syscall1(
montauk::abi::SYS_POWERINFO, (uint64_t)&out);
.SH KERNEL LOG .SH KERNEL LOG
.B SYS_LOG (46) .B SYS_LOG (46)
Read from the kernel ring log buffer. Read from the kernel ring log buffer. Requires CAP_LOG_READ.
int64_t montauk::read_log(char* buf, uint64_t size); int64_t montauk::read_log(char* buf, uint64_t size);
.B SYS_LOG_WRITE (176) .B SYS_LOG_WRITE (176)
@@ -432,9 +612,20 @@
to the framebuffer console. to the framebuffer console.
.B SYS_SPAWN_REDIR (49) .B SYS_SPAWN_REDIR (49)
Spawn a process with its console I/O redirected to the caller. Spawn a process with its console I/O redirected to the caller. The child
receives no capabilities.
int montauk::spawn_redir(const char* path, const char* args = nullptr); int montauk::spawn_redir(const char* path, const char* args = nullptr);
.B SYS_SPAWN_REDIR_CAPS (186)
Spawn a redirected child with explicit capability masks. It applies the
same subset and parent-delegable checks as SYS_SPAWN_CAPS. An
administrative console explicitly lets its shell delegate selected
capabilities; the shell's executable policy gives each trusted tool a
delegable mask of zero, preventing further propagation.
int montauk::spawn_redir_with_caps(
const char* path, const char* args,
const montauk::abi::SpawnCapabilities& capabilities);
.B SYS_CHILDIO_READ (50) .B SYS_CHILDIO_READ (50)
Read buffered output produced by a redirected child. Read buffered output produced by a redirected child.
int montauk::childio_read(int childPid, char* buf, int maxLen); int montauk::childio_read(int childPid, char* buf, int maxLen);
@@ -445,7 +636,8 @@
.B SYS_CHILDIO_WRITEKEY (52) .B SYS_CHILDIO_WRITEKEY (52)
Forward a raw key event to a redirected child. Forward a raw key event to a redirected child.
int montauk::childio_writekey(int childPid, const montauk::abi::KeyEvent* key); int montauk::childio_writekey(
int childPid, const montauk::abi::KeyEvent* key);
.B SYS_CHILDIO_SETTERMSZ (53) .B SYS_CHILDIO_SETTERMSZ (53)
Tell a redirected child its terminal dimensions changed. Tell a redirected child its terminal dimensions changed.
@@ -536,37 +728,41 @@
int montauk::partlist(montauk::abi::PartInfo* buf, int max); int montauk::partlist(montauk::abi::PartInfo* buf, int max);
.B SYS_DISKREAD (71) .B SYS_DISKREAD (71)
Raw, driver-agnostic sector read from a block device. Raw, driver-agnostic sector read from a block device. Requires
CAP_RAW_STORAGE.
int64_t montauk::disk_read(int blockDev, uint64_t lba, int64_t montauk::disk_read(int blockDev, uint64_t lba,
uint32_t sectorCount, void* buf); uint32_t sectorCount, void* buf);
.B SYS_DISKWRITE (72) .B SYS_DISKWRITE (72)
Raw, driver-agnostic sector write to a block device. Raw, driver-agnostic sector write to a block device. Requires
CAP_RAW_STORAGE.
int64_t montauk::disk_write(int blockDev, uint64_t lba, int64_t montauk::disk_write(int blockDev, uint64_t lba,
uint32_t sectorCount, const void* buf); uint32_t sectorCount, const void* buf);
.B SYS_GPTINIT (73) .B SYS_GPTINIT (73)
Initialize a fresh GPT partition table on a block device. Initialize a fresh GPT partition table on a block device. Requires
CAP_STORAGE_ADMIN.
int montauk::gpt_init(int blockDev); int montauk::gpt_init(int blockDev);
.B SYS_GPTADD (74) .B SYS_GPTADD (74)
Add a partition to an existing GPT table. Add a partition to an existing GPT table. Requires CAP_STORAGE_ADMIN.
int montauk::gpt_add(const montauk::abi::GptAddParams* params); int montauk::gpt_add(const montauk::abi::GptAddParams* params);
.B SYS_FSMOUNT (75) .B SYS_FSMOUNT (75)
Mount a partition's filesystem onto a drive number. Mount a partition's filesystem onto a drive number. Requires
CAP_STORAGE_ADMIN.
int montauk::fs_mount(int partIndex, int driveNum); int montauk::fs_mount(int partIndex, int driveNum);
.B SYS_FSFORMAT (76) .B SYS_FSFORMAT (76)
Format a partition with a filesystem (FS_TYPE_FAT32 or Format a partition with a filesystem (FS_TYPE_FAT32 or
FS_TYPE_EXT2). FS_TYPE_EXT2). Requires CAP_STORAGE_ADMIN.
int montauk::fs_format(const montauk::abi::FsFormatParams* params); int montauk::fs_format(const montauk::abi::FsFormatParams* params);
.B SYS_FS_SYNC (134) .B SYS_FS_SYNC (134)
Flush all block-device write caches and cleanly unmount Flush all block-device write caches and cleanly unmount
disk-backed volumes ahead of power-off. Returns the number of disk-backed volumes ahead of power-off. Returns the number of
volumes unmounted. Part of the graceful shutdown sequence volumes unmounted. Part of the graceful shutdown sequence
(see SYS_POWER_REQUEST). (see SYS_POWER_REQUEST). Requires CAP_STORAGE_ADMIN.
int montauk::fs_sync(); int montauk::fs_sync();
.SH AUDIO .SH AUDIO
@@ -595,18 +791,21 @@
audio_set_volume, audio_get_volume AUDIO_CTL_{SET,GET}_VOLUME (0/1) audio_set_volume, audio_get_volume AUDIO_CTL_{SET,GET}_VOLUME (0/1)
audio_get_pos AUDIO_CTL_GET_POS (2) audio_get_pos AUDIO_CTL_GET_POS (2)
audio_pause, audio_resume AUDIO_CTL_PAUSE (3) audio_pause, audio_resume AUDIO_CTL_PAUSE (3)
audio_get_output AUDIO_CTL_GET_OUTPUT (4): 0=HDA, 1=Bluetooth audio_get_output GET_OUTPUT (4): 0=HDA, 1=Bluetooth
audio_set_output AUDIO_CTL_SET_OUTPUT (5): switch all streams audio_set_output SET_OUTPUT (5): all streams
(SET_OUTPUT, 5) switch a stream's output route audio_ctl(handle, 5, output) SET_OUTPUT (5): one stream
audio_bt_status AUDIO_CTL_BT_STATUS (6): 0=unavailable, 1=setup, 2=ready audio_bt_status BT_STATUS (6): unavailable/setup/ready
audio_set_master_volume, _get_ AUDIO_CTL_{SET,GET}_MASTER_VOLUME (7/8), 0-100 audio_set_master_volume SET_MASTER_VOLUME (7), 0-100
audio_set_mute, audio_get_mute AUDIO_CTL_{SET,GET}_MUTE (9/10), per-stream audio_get_master_volume GET_MASTER_VOLUME (8)
audio_set_master_mute, _get_ AUDIO_CTL_{SET,GET}_MASTER_MUTE (11/12) audio_set_mute, audio_get_mute MUTE (9/10), per-stream
audio_set_master_mute SET_MASTER_MUTE (11)
audio_get_master_mute GET_MASTER_MUTE (12)
.B SYS_AUDIOLIST (128) .B SYS_AUDIOLIST (128)
Enumerate active mixer streams (owner PID, name, format, Enumerate active mixer streams (owner PID, name, format,
volume, mute/pause state). volume, mute/pause state).
int montauk::audio_list(montauk::abi::AudioStreamInfo* buf, int maxCount); int montauk::audio_list(montauk::abi::AudioStreamInfo* buf,
int maxCount);
.B SYS_AUDIOWAIT (129) .B SYS_AUDIOWAIT (129)
Return the current mixer state serial. With timeoutMs > 0, Return the current mixer state serial. With timeoutMs > 0,
@@ -616,16 +815,18 @@
.SH BLUETOOTH .SH BLUETOOTH
.B SYS_BTSCAN (84) .B SYS_BTSCAN (84)
Scan for discoverable Bluetooth devices for up to timeoutMs. Scan for discoverable Bluetooth devices for up to timeoutMs. Requires
CAP_DEVICE_ADMIN because it changes radio state.
int montauk::bt_scan(montauk::abi::BtScanResult* buf, int maxCount, int montauk::bt_scan(montauk::abi::BtScanResult* buf, int maxCount,
uint32_t timeoutMs); uint32_t timeoutMs);
.B SYS_BTCONNECT (85) .B SYS_BTCONNECT (85)
Connect (and pair/bond if needed) to a device by BD_ADDR. Connect (and pair/bond if needed) to a device by BD_ADDR. Requires
CAP_DEVICE_ADMIN.
int montauk::bt_connect(const uint8_t* bdAddr); int montauk::bt_connect(const uint8_t* bdAddr);
.B SYS_BTDISCONNECT (86) .B SYS_BTDISCONNECT (86)
Disconnect from a device by BD_ADDR. Disconnect from a device by BD_ADDR. Requires CAP_DEVICE_ADMIN.
int montauk::bt_disconnect(const uint8_t* bdAddr); int montauk::bt_disconnect(const uint8_t* bdAddr);
.B SYS_BTLIST (87) .B SYS_BTLIST (87)
@@ -639,7 +840,8 @@
.B SYS_BTSETADDR (137) .B SYS_BTSETADDR (137)
Change the adapter's BD_ADDR (6-byte buffer, byte 0 is the Change the adapter's BD_ADDR (6-byte buffer, byte 0 is the
least-significant octet). Volatile -- apply after the last least-significant octet). Volatile -- apply after the last
controller reset and persist separately to bluetooth.toml. controller reset and persist separately to bluetooth.toml. Requires
CAP_DEVICE_ADMIN.
int montauk::bt_set_addr(const uint8_t* bdAddr); int montauk::bt_set_addr(const uint8_t* bdAddr);
.B SYS_BTBONDS (138) .B SYS_BTBONDS (138)
@@ -647,61 +849,69 @@
int montauk::bt_bonds(montauk::abi::BtBondInfo* buf, int maxCount); int montauk::bt_bonds(montauk::abi::BtBondInfo* buf, int maxCount);
.B SYS_BTFORGET (139) .B SYS_BTFORGET (139)
Forget a paired device; it must re-pair next time. Forget a paired device; it must re-pair next time. Requires
CAP_DEVICE_ADMIN.
int montauk::bt_forget(const uint8_t* bdAddr); int montauk::bt_forget(const uint8_t* bdAddr);
.SH SOFTWARE-DEFINED RADIO .SH GENERIC USB INTERFACES
Receive-only SDR API. Receivers are enumerated by index in Process-owned access to USB interfaces that do not have a bound kernel
[0, SYS_SDR_COUNT); SYS_SDR_OPEN returns a handle used by the class driver. Claims are exclusive and are released automatically at
rest of the calls. Samples are delivered as interleaved 8-bit process exit. The current xHCI device model records one interface per
unsigned I/Q (CU8, SDR_FORMAT_CU8) from the device's ring device slot, so claiming that interface temporarily claims the whole slot.
buffer. Backed by an RTL-SDR (RTL2832U + R820T2) driver. Kernel-owned HID, Bluetooth, mass-storage, and RTL-SDR interfaces are
visible in SYS_USB_LIST but cannot be claimed.
.B SYS_SDR_COUNT (140) .B SYS_USB_LIST (178)
Number of available SDR receivers. List currently connected USB interfaces. Each UsbInterfaceInfo contains
int montauk::sdr_count(); stable identifiers for the current connection, endpoint addresses, maximum
packet sizes, and kernelDriverBound/claimed flags.
int montauk::usb_list(montauk::abi::UsbInterfaceInfo* buf,
int maxCount);
.B SYS_SDR_INFO (141) .B SYS_USB_CLAIM (179)
Get static/dynamic info for one receiver by index (name, tuner, Exclusively claim an unbound interface. Returns a generation-checked handle
frequency/sample-rate ranges, gain steps, present/streaming owned by the calling process. Requires CAP_DEVICE_ADMIN; subsequent
flags). operations are authorized by ownership of that handle.
int montauk::sdr_info(int index, montauk::abi::SdrDeviceInfo* out); int montauk::usb_claim(uint8_t slotId, uint8_t interfaceNumber);
.B SYS_SDR_OPEN (142) .B SYS_USB_CLOSE (180)
Open a receiver by index. Returns a handle. Stop active transfers and release a USB claim.
int montauk::sdr_open(int index); int montauk::usb_close(int handle);
.B SYS_SDR_CLOSE (143) .B SYS_USB_CONTROL (181)
Close a receiver handle. Execute a USB control transfer on endpoint zero. The requestType direction
int montauk::sdr_close(int handle); bit determines whether data is read or written. request.length must equal
dataLen; control payloads are currently limited to 4096 bytes.
int montauk::usb_control(
int handle, const montauk::abi::UsbControlRequest* request,
void* data, uint32_t dataLen);
.B SYS_SDR_START (144) .B SYS_USB_BULK_IN_START (182)
Begin streaming samples. Start a continuous bulk-IN transfer pool. transferBytes is 1..4096 and
int montauk::sdr_start(int handle); bufferCount is 1..16. Completed data is copied into a 256 KiB per-claim
ring buffer.
int montauk::usb_bulk_in_start(int handle, uint32_t transferBytes,
uint32_t bufferCount);
.B SYS_SDR_STOP (145) .B SYS_USB_BULK_IN_STOP (183)
Stop streaming samples. Stop continuous bulk-IN transfers without releasing the claim.
int montauk::sdr_stop(int handle); int montauk::usb_bulk_in_stop(int handle);
.B SYS_SDR_READ (146) .B SYS_USB_BULK_IN_READ (184)
Non-blocking read of queued I/Q samples. Returns bytes copied. Non-blocking read from the bulk-IN ring. Returns bytes copied, zero when no
int montauk::sdr_read(int handle, void* buf, uint32_t len); data is queued, or USB_ERR_DISCONNECTED after queued data has been drained.
int montauk::usb_bulk_in_read(int handle, void* data, uint32_t dataLen);
.B SYS_SDR_SETPARAM (147) Errors are USB_ERR_INVALID (-1), USB_ERR_BUSY (-2),
Set a tunable parameter (see SDR_PARAM_* below). USB_ERR_DISCONNECTED (-3), USB_ERR_UNSUPPORTED (-4), USB_ERR_IO (-5),
int montauk::sdr_set_param(int handle, int param, uint64_t value); USB_ERR_NO_RESOURCES (-6), USB_ERR_NOT_FOUND (-7), and
USB_ERR_KERNEL_BOUND (-8).
.B SYS_SDR_GETPARAM (148) .SH RESERVED SYSCALL NUMBERS
Get a tunable parameter's current value. Syscall numbers 140 through 148 are reserved. They were used by an
int64_t montauk::sdr_get_param(int handle, int param); experimental kernel SDR API and are intentionally not dispatched or exposed
through userspace syscall wrappers. RTL-SDR support is provided by
Parameters (montauk::abi::SDR_PARAM_*): FREQ (center frequency, 0:/os/rtlsdr.lib over the generic userspace USB API.
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.
.SH CLIPBOARD .SH CLIPBOARD
.B SYS_CLIPBOARD_SET_TEXT (119) .B SYS_CLIPBOARD_SET_TEXT (119)
@@ -717,7 +927,8 @@
.B SYS_CLIPBOARD_GET_TEXT (121) .B SYS_CLIPBOARD_GET_TEXT (121)
Read the clipboard's text contents. Read the clipboard's text contents.
int montauk::clipboard_get_text(char* buf, uint32_t bufLen, int montauk::clipboard_get_text(char* buf, uint32_t bufLen,
uint32_t* outLen, uint64_t* outSerial = nullptr); uint32_t* outLen,
uint64_t* outSerial = nullptr);
.B SYS_CLIPBOARD_CLEAR (122) .B SYS_CLIPBOARD_CLEAR (122)
Clear the clipboard. Clear the clipboard.
@@ -762,13 +973,15 @@
.B SYS_MAILBOX_SEND (104) .B SYS_MAILBOX_SEND (104)
Send a typed message, optionally attaching a handle to Send a typed message, optionally attaching a handle to
transfer to the receiver. transfer to the receiver.
int montauk::mailbox_send(int handle, uint32_t msgType, const void* data, int montauk::mailbox_send(int handle, uint32_t msgType,
uint16_t len, int attachHandle = -1); const void* data, uint16_t len,
int attachHandle = -1);
.B SYS_MAILBOX_RECV (105) .B SYS_MAILBOX_RECV (105)
Receive a message. Receive a message.
int montauk::mailbox_recv(int handle, uint32_t* outMsgType, void* data, int montauk::mailbox_recv(int handle, uint32_t* outMsgType, void* data,
uint16_t* inOutLen, int* outAttachHandle = nullptr); uint16_t* inOutLen,
int* outAttachHandle = nullptr);
.B SYS_WAITSET_CREATE (106) .B SYS_WAITSET_CREATE (106)
Create a waitset for multiplexing waits across many handles. Create a waitset for multiplexing waits across many handles.
@@ -786,8 +999,9 @@
.B SYS_WAITSET_WAIT (109) .B SYS_WAITSET_WAIT (109)
Block until any member handle's watched signals fire, or Block until any member handle's watched signals fire, or
timeoutMs elapses. timeoutMs elapses.
int montauk::waitset_wait(int waitsetHandle, montauk::abi::IpcWaitResult* outReady, int montauk::waitset_wait(
uint64_t timeoutMs = ~0ULL); int waitsetHandle, montauk::abi::IpcWaitResult* outReady,
uint64_t timeoutMs = ~0ULL);
.B SYS_PROC_OPEN (110) .B SYS_PROC_OPEN (110)
Open a handle to another process by PID (for waiting on its Open a handle to another process by PID (for waiting on its
@@ -6,33 +6,31 @@
#include "apps_common.hpp" #include "apps_common.hpp"
static void spawn_app(const char* path, const char* args = nullptr) { static void spawn_app(DesktopState* ds, const char* path, const char* args = nullptr) {
if (path && path[0]) { if (path && path[0]) desktop_spawn_app(ds, path, args);
montauk::spawn(path, args);
}
} }
void open_terminal(DesktopState* ds) { void open_terminal(DesktopState* ds) {
const char* home = (ds && ds->home_dir[0]) ? ds->home_dir : nullptr; const char* home = (ds && ds->home_dir[0]) ? ds->home_dir : nullptr;
spawn_app("0:/apps/terminal/terminal.elf", home); spawn_app(ds, "0:/apps/terminal/terminal.elf", home);
} }
void open_calculator(DesktopState* ds) { void open_calculator(DesktopState* ds) {
(void)ds; (void)ds;
spawn_app("0:/apps/calculator/calculator.elf"); spawn_app(ds, "0:/apps/calculator/calculator.elf");
} }
void open_texteditor(DesktopState* ds) { void open_texteditor(DesktopState* ds) {
(void)ds; (void)ds;
spawn_app("0:/apps/texteditor/texteditor.elf"); spawn_app(ds, "0:/apps/texteditor/texteditor.elf");
} }
void open_syslog(DesktopState* ds) { void open_syslog(DesktopState* ds) {
(void)ds; (void)ds;
spawn_app("0:/apps/klog/syslog.elf"); spawn_app(ds, "0:/apps/syslog/syslog.elf");
} }
void open_wordprocessor(DesktopState* ds) { void open_wordprocessor(DesktopState* ds) {
(void)ds; (void)ds;
spawn_app("0:/apps/wordprocessor/wordprocessor.elf"); spawn_app(ds, "0:/apps/wordprocessor/wordprocessor.elf");
} }
+9 -3
View File
@@ -25,6 +25,12 @@ inline void* operator new(unsigned long, void* p) { return p; }
using namespace gui; using namespace gui;
// Central launch policy implemented by desktop_catalog.cpp. Standalone app
// wrappers include this header directly, so keep the declaration here rather
// than only in desktop_internal.hpp.
int desktop_spawn_app(DesktopState* ds, const char* path,
const char* args = nullptr);
// ============================================================================ // ============================================================================
// Minimal snprintf // Minimal snprintf
// ============================================================================ // ============================================================================
@@ -261,8 +267,8 @@ void open_sleep_dialog(DesktopState* ds);
// user returns to the login screen, where the shutdown stages run (Bluetooth // user returns to the login screen, where the shutdown stages run (Bluetooth
// teardown, filesystem flush) before the final ACPI power-off / reset. Pass // teardown, filesystem flush) before the final ACPI power-off / reset. Pass
// montauk::abi::POWER_REQ_SHUTDOWN or montauk::abi::POWER_REQ_REBOOT. // montauk::abi::POWER_REQ_SHUTDOWN or montauk::abi::POWER_REQ_REBOOT.
[[noreturn]] inline void desktop_request_power(int action) { inline void desktop_request_power(int action) {
montauk::power_request(action); if (montauk::power_request(action) == 0)
montauk::exit(0); montauk::exit(0);
} }
bool desktop_poll_external_windows(DesktopState* ds); bool desktop_poll_external_windows(DesktopState* ds);
@@ -319,6 +319,27 @@ void filemanager_on_mouse(Window* win, MouseEvent& ev) {
} }
} }
// Keep the selected row inside the visible list area so arrow-key navigation
// scrolls the view along with the selection.
static void filemanager_scroll_to_selected(Window* win, FileManagerState* fm) {
if (fm->grid_view) return;
if (fm->selected < 0 || fm->selected >= fm->entry_count) return;
int list_y = FM_TOOLBAR_H + FM_PATHBAR_H + FM_HEADER_H;
int list_h = win->content_h - list_y;
if (list_h <= 0) return;
int top = fm->selected * FM_ITEM_H;
int off = fm->scrollbar.scroll_offset;
if (top < off) off = top;
else if (top + FM_ITEM_H > off + list_h) off = top + FM_ITEM_H - list_h;
int max_scroll = fm->entry_count * FM_ITEM_H - list_h;
if (off > max_scroll) off = max_scroll;
if (off < 0) off = 0;
fm->scrollbar.scroll_offset = off;
}
void filemanager_on_key(Window* win, const montauk::abi::KeyEvent& key) { void filemanager_on_key(Window* win, const montauk::abi::KeyEvent& key) {
FileManagerState* fm = (FileManagerState*)win->app_data; FileManagerState* fm = (FileManagerState*)win->app_data;
if (!fm || !key.pressed) return; if (!fm || !key.pressed) return;
@@ -455,6 +476,7 @@ void filemanager_on_key(Window* win, const montauk::abi::KeyEvent& key) {
} else { } else {
if (fm->selected > 0) fm->selected--; if (fm->selected > 0) fm->selected--;
} }
filemanager_scroll_to_selected(win, fm);
} else if (key.scancode == 0x50) { } else if (key.scancode == 0x50) {
// Down arrow // Down arrow
if (fm->grid_view) { if (fm->grid_view) {
@@ -465,6 +487,7 @@ void filemanager_on_key(Window* win, const montauk::abi::KeyEvent& key) {
} else { } else {
if (fm->selected < fm->entry_count - 1) fm->selected++; if (fm->selected < fm->entry_count - 1) fm->selected++;
} }
filemanager_scroll_to_selected(win, fm);
} else if (key.scancode == 0x4B && !key.alt && fm->grid_view) { } else if (key.scancode == 0x4B && !key.alt && fm->grid_view) {
// Left arrow (grid view only) // Left arrow (grid view only)
if (fm->selected > 0) fm->selected--; if (fm->selected > 0) fm->selected--;
+15 -2
View File
@@ -5,6 +5,7 @@
*/ */
#include "desktop_internal.hpp" #include "desktop_internal.hpp"
#include <montauk/capabilities.h>
namespace { namespace {
@@ -24,6 +25,18 @@ static void sort_item_indices(DesktopState* ds, int* indices, int count) {
} // namespace } // namespace
int desktop_spawn_app(DesktopState* ds, const char* path, const char* args) {
if (!ds || !path || !path[0]) return -1;
// The grant table decides what each program may receive; the kernel bounds
// it by what this session was actually delegated, so a non-admin session
// clamps to nothing without the desktop having to decide that itself.
montauk::abi::SpawnCapabilities caps =
montauk::caps::for_binary(path, montauk::caps::self_delegable());
// Applications inherit the desktop's kernel-owned owner identity. An
// explicit user override is reserved for trusted session creators.
return montauk::spawn_with_caps(path, args, nullptr, caps);
}
int desktop_list_item_indices(DesktopState* ds, int desktop_list_item_indices(DesktopState* ds,
DesktopItemSection section, DesktopItemSection section,
int* out, int* out,
@@ -54,9 +67,9 @@ bool desktop_launch_item(DesktopState* ds, const DesktopItem* item) {
case DESKTOP_ITEM_LAUNCH_EXECUTABLE: case DESKTOP_ITEM_LAUNCH_EXECUTABLE:
if (!item->binary_path[0]) return false; if (!item->binary_path[0]) return false;
if (item->launch_with_home) { if (item->launch_with_home) {
return montauk::spawn(item->binary_path, ds->home_dir) >= 0; return desktop_spawn_app(ds, item->binary_path, ds->home_dir) >= 0;
} }
return montauk::spawn(item->binary_path) >= 0; return desktop_spawn_app(ds, item->binary_path) >= 0;
default: default:
return false; return false;
} }
+2 -2
View File
@@ -381,9 +381,9 @@ void gui::desktop_handle_mouse(DesktopState* ds) {
} else if (!row.is_category) { } else if (!row.is_category) {
if (row.external) { if (row.external) {
if (row.launch_with_home) { if (row.launch_with_home) {
montauk::spawn(row.binary_path, ds->home_dir); desktop_spawn_app(ds, row.binary_path, ds->home_dir);
} else { } else {
montauk::spawn(row.binary_path); desktop_spawn_app(ds, row.binary_path);
} }
} else { } else {
desktop_launch_builtin(ds, row.app_id); desktop_launch_builtin(ds, row.app_id);
+13
View File
@@ -834,6 +834,19 @@ void gui::desktop_run(DesktopState* ds) {
if (now >= nextClockPollMs) { if (now >= nextClockPollMs) {
nextClockPollMs = now + 1000; nextClockPollMs = now + 1000;
// A power request can be posted by anything in this session that
// holds CAP_POWER_REQUEST -- the shell's shutdown builtin, run in
// a terminal window, is the common case. login only reads it once
// the session leader exits, so ending the session is our job; it
// is the same handoff desktop_request_power() performs, just
// reached from a request we did not originate.
int pendingPower = montauk::power_request_pending();
if (pendingPower == montauk::abi::POWER_REQ_SHUTDOWN ||
pendingPower == montauk::abi::POWER_REQ_REBOOT) {
montauk::exit(0);
}
uint64_t clockToken = desktop_clock_token(); uint64_t clockToken = desktop_clock_token();
if (clockToken != lastClockToken) { if (clockToken != lastClockToken) {
lastClockToken = clockToken; lastClockToken = clockToken;
+1 -1
View File
@@ -328,7 +328,7 @@ bool desktop_wifi_poll(DesktopState* ds, uint64_t now) {
if (ds->wifi_dhcp_pending && info.connected && ds->cached_net_cfg.ipAddress == 0) { if (ds->wifi_dhcp_pending && info.connected && ds->cached_net_cfg.ipAddress == 0) {
ds->wifi_dhcp_pending = false; ds->wifi_dhcp_pending = false;
ds->wifi_dhcp_waiting = true; ds->wifi_dhcp_waiting = true;
montauk::spawn("0:/os/dhcp.elf"); desktop_spawn_app(ds, "0:/os/dhcp.elf");
wifi_set_status(ds, "Requesting an address..."); wifi_set_status(ds, "Requesting an address...");
changed = true; changed = true;
} else if (ds->wifi_dhcp_pending && info.connected) { } else if (ds->wifi_dhcp_pending && info.connected) {
+28 -27
View File
@@ -6,6 +6,7 @@
*/ */
#include <montauk/syscall.h> #include <montauk/syscall.h>
#include <montauk/service_log.h>
#include <montauk/string.h> #include <montauk/string.h>
#include <libc/stdio.h> #include <libc/stdio.h>
@@ -247,7 +248,7 @@ static constexpr uint32_t BROADCAST_IP = 0xFFFFFFFF;
extern "C" void _start() { extern "C" void _start() {
char msg[256]; char msg[256];
montauk::print("MontaukOS DHCP Client\n"); montauk::service_log("MontaukOS DHCP Client");
// 1. Get MAC address // 1. Get MAC address
montauk::abi::NetCfg origCfg; montauk::abi::NetCfg origCfg;
@@ -255,8 +256,8 @@ extern "C" void _start() {
char macStr[32]; char macStr[32];
format_mac(macStr, sizeof(macStr), origCfg.macAddress); format_mac(macStr, sizeof(macStr), origCfg.macAddress);
snprintf(msg, sizeof(msg), "MAC address: %s\n", macStr); snprintf(msg, sizeof(msg), "MAC address: %s", macStr);
montauk::print(msg); montauk::service_log(msg);
// 2. Set IP to 0.0.0.0 to allow broadcast send/receive // 2. Set IP to 0.0.0.0 to allow broadcast send/receive
montauk::abi::NetCfg zeroCfg; montauk::abi::NetCfg zeroCfg;
@@ -268,13 +269,13 @@ extern "C" void _start() {
// 3. Create UDP socket and bind to port 68 // 3. Create UDP socket and bind to port 68
int fd = montauk::socket(montauk::abi::SOCK_UDP); int fd = montauk::socket(montauk::abi::SOCK_UDP);
if (fd < 0) { if (fd < 0) {
montauk::print("Error: failed to create UDP socket\n"); montauk::service_log("Error: failed to create UDP socket");
montauk::set_netcfg(&origCfg); montauk::set_netcfg(&origCfg);
montauk::exit(1); montauk::exit(1);
} }
if (montauk::bind(fd, DHCP_CLIENT_PORT) < 0) { if (montauk::bind(fd, DHCP_CLIENT_PORT) < 0) {
montauk::print("Error: failed to bind to port 68\n"); montauk::service_log("Error: failed to bind to port 68");
montauk::closesocket(fd); montauk::closesocket(fd);
montauk::set_netcfg(&origCfg); montauk::set_netcfg(&origCfg);
montauk::exit(1); montauk::exit(1);
@@ -284,9 +285,9 @@ extern "C" void _start() {
DhcpPacket pkt; DhcpPacket pkt;
int pktLen = build_discover(&pkt, origCfg.macAddress); int pktLen = build_discover(&pkt, origCfg.macAddress);
montauk::print("Sending DHCPDISCOVER...\n"); montauk::service_log("Sending DHCPDISCOVER...");
if (montauk::sendto(fd, (const void*)&pkt, pktLen, BROADCAST_IP, DHCP_SERVER_PORT) < 0) { if (montauk::sendto(fd, (const void*)&pkt, pktLen, BROADCAST_IP, DHCP_SERVER_PORT) < 0) {
montauk::print("Error: failed to send DISCOVER\n"); montauk::service_log("Error: failed to send DISCOVER");
montauk::closesocket(fd); montauk::closesocket(fd);
montauk::set_netcfg(&origCfg); montauk::set_netcfg(&origCfg);
montauk::exit(1); montauk::exit(1);
@@ -298,7 +299,7 @@ extern "C" void _start() {
uint64_t startMs = montauk::get_milliseconds(); uint64_t startMs = montauk::get_milliseconds();
bool gotOffer = false; bool gotOffer = false;
montauk::print("Waiting for DHCPOFFER...\n"); montauk::service_log("Waiting for DHCPOFFER...");
while (montauk::get_milliseconds() - startMs < 10000) { while (montauk::get_milliseconds() - startMs < 10000) {
uint32_t srcIp; uint32_t srcIp;
uint16_t srcPort; uint16_t srcPort;
@@ -318,7 +319,7 @@ extern "C" void _start() {
} }
if (!gotOffer) { if (!gotOffer) {
montauk::print("Error: no DHCPOFFER received (timeout)\n"); montauk::service_log("Error: no DHCPOFFER received (timeout)");
montauk::closesocket(fd); montauk::closesocket(fd);
montauk::set_netcfg(&origCfg); montauk::set_netcfg(&origCfg);
montauk::exit(1); montauk::exit(1);
@@ -326,15 +327,15 @@ extern "C" void _start() {
char ipStr[32]; char ipStr[32];
format_ip(ipStr, sizeof(ipStr), offer.offeredIp); format_ip(ipStr, sizeof(ipStr), offer.offeredIp);
snprintf(msg, sizeof(msg), "Received OFFER: %s\n", ipStr); snprintf(msg, sizeof(msg), "Received OFFER: %s", ipStr);
montauk::print(msg); montauk::service_log(msg);
// 6. Send REQUEST // 6. Send REQUEST
pktLen = build_request(&pkt, origCfg.macAddress, offer.offeredIp, offer.serverId); pktLen = build_request(&pkt, origCfg.macAddress, offer.offeredIp, offer.serverId);
montauk::print("Sending DHCPREQUEST...\n"); montauk::service_log("Sending DHCPREQUEST...");
if (montauk::sendto(fd, (const void*)&pkt, pktLen, BROADCAST_IP, DHCP_SERVER_PORT) < 0) { if (montauk::sendto(fd, (const void*)&pkt, pktLen, BROADCAST_IP, DHCP_SERVER_PORT) < 0) {
montauk::print("Error: failed to send REQUEST\n"); montauk::service_log("Error: failed to send REQUEST");
montauk::closesocket(fd); montauk::closesocket(fd);
montauk::set_netcfg(&origCfg); montauk::set_netcfg(&origCfg);
montauk::exit(1); montauk::exit(1);
@@ -344,7 +345,7 @@ extern "C" void _start() {
bool gotAck = false; bool gotAck = false;
startMs = montauk::get_milliseconds(); startMs = montauk::get_milliseconds();
montauk::print("Waiting for DHCPACK...\n"); montauk::service_log("Waiting for DHCPACK...");
while (montauk::get_milliseconds() - startMs < 10000) { while (montauk::get_milliseconds() - startMs < 10000) {
uint32_t srcIp; uint32_t srcIp;
uint16_t srcPort; uint16_t srcPort;
@@ -357,7 +358,7 @@ extern "C" void _start() {
break; break;
} }
if (offer.valid && offer.msgType == DHCPNAK) { if (offer.valid && offer.msgType == DHCPNAK) {
montauk::print("Error: received DHCPNAK from server\n"); montauk::service_log("Error: received DHCPNAK from server");
montauk::closesocket(fd); montauk::closesocket(fd);
montauk::set_netcfg(&origCfg); montauk::set_netcfg(&origCfg);
montauk::exit(1); montauk::exit(1);
@@ -372,7 +373,7 @@ extern "C" void _start() {
montauk::closesocket(fd); montauk::closesocket(fd);
if (!gotAck) { if (!gotAck) {
montauk::print("Error: no DHCPACK received (timeout)\n"); montauk::service_log("Error: no DHCPACK received (timeout)");
montauk::set_netcfg(&origCfg); montauk::set_netcfg(&origCfg);
montauk::exit(1); montauk::exit(1);
} }
@@ -386,29 +387,29 @@ extern "C" void _start() {
montauk::set_netcfg(&newCfg); montauk::set_netcfg(&newCfg);
// 9. Print results // 9. Print results
montauk::print("\nDHCP configuration applied:\n"); montauk::service_log("DHCP configuration applied:");
format_ip(ipStr, sizeof(ipStr), offer.offeredIp); format_ip(ipStr, sizeof(ipStr), offer.offeredIp);
snprintf(msg, sizeof(msg), " IP Address: %s\n", ipStr); snprintf(msg, sizeof(msg), " IP Address: %s", ipStr);
montauk::print(msg); montauk::service_log(msg);
format_ip(ipStr, sizeof(ipStr), offer.subnetMask); format_ip(ipStr, sizeof(ipStr), offer.subnetMask);
snprintf(msg, sizeof(msg), " Subnet Mask: %s\n", ipStr); snprintf(msg, sizeof(msg), " Subnet Mask: %s", ipStr);
montauk::print(msg); montauk::service_log(msg);
format_ip(ipStr, sizeof(ipStr), offer.router); format_ip(ipStr, sizeof(ipStr), offer.router);
snprintf(msg, sizeof(msg), " Gateway: %s\n", ipStr); snprintf(msg, sizeof(msg), " Gateway: %s", ipStr);
montauk::print(msg); montauk::service_log(msg);
if (offer.dns != 0) { if (offer.dns != 0) {
format_ip(ipStr, sizeof(ipStr), offer.dns); format_ip(ipStr, sizeof(ipStr), offer.dns);
snprintf(msg, sizeof(msg), " DNS Server: %s\n", ipStr); snprintf(msg, sizeof(msg), " DNS Server: %s", ipStr);
montauk::print(msg); montauk::service_log(msg);
} }
if (offer.leaseTime != 0) { if (offer.leaseTime != 0) {
snprintf(msg, sizeof(msg), " Lease Time: %u seconds\n", offer.leaseTime); snprintf(msg, sizeof(msg), " Lease Time: %u seconds", offer.leaseTime);
montauk::print(msg); montauk::service_log(msg);
} }
montauk::exit(0); montauk::exit(0);
+16 -17
View File
@@ -7,6 +7,7 @@
*/ */
#include <montauk/syscall.h> #include <montauk/syscall.h>
#include <montauk/service_log.h>
#include <montauk/string.h> #include <montauk/string.h>
#include <libc/stdio.h> #include <libc/stdio.h>
@@ -144,10 +145,10 @@ static void log_request(const char* method, const char* path, int status, int bo
montauk::gettime(&dt); montauk::gettime(&dt);
char msg[256]; char msg[256];
snprintf(msg, sizeof(msg), "[%02d:%02d:%02d] %s %s -> %d (%d bytes)\n", snprintf(msg, sizeof(msg), "[%02d:%02d:%02d] %s %s -> %d (%d bytes)",
(int)dt.Hour, (int)dt.Minute, (int)dt.Second, (int)dt.Hour, (int)dt.Minute, (int)dt.Second,
method, path, status, bodyLen); method, path, status, bodyLen);
montauk::print(msg); montauk::service_log(msg);
} }
// ---- Page generators ---- // ---- Page generators ----
@@ -454,9 +455,9 @@ extern "C" void _start() {
uint16_t port = 80; uint16_t port = 80;
if (*arg) { if (*arg) {
if (!parse_uint16(arg, &port)) { if (!parse_uint16(arg, &port)) {
montauk::print("Invalid port: "); char message[96];
montauk::print(arg); snprintf(message, sizeof(message), "Invalid port: %s", arg);
montauk::putchar('\n'); montauk::service_log(message);
montauk::exit(1); montauk::exit(1);
} }
} }
@@ -464,32 +465,30 @@ extern "C" void _start() {
// Create server socket // Create server socket
int listenFd = montauk::socket(montauk::abi::SOCK_TCP); int listenFd = montauk::socket(montauk::abi::SOCK_TCP);
if (listenFd < 0) { if (listenFd < 0) {
montauk::print("Error: failed to create socket\n"); montauk::service_log("Error: failed to create socket");
montauk::exit(1); montauk::exit(1);
} }
// Bind // Bind
if (montauk::bind(listenFd, port) < 0) { if (montauk::bind(listenFd, port) < 0) {
montauk::print("Error: failed to bind to port "); char message[64];
char tmp[8]; snprintf(message, sizeof(message), "Error: failed to bind to port %d", (int)port);
snprintf(tmp, sizeof(tmp), "%d", (int)port); montauk::service_log(message);
montauk::print(tmp);
montauk::putchar('\n');
montauk::closesocket(listenFd); montauk::closesocket(listenFd);
montauk::exit(1); montauk::exit(1);
} }
// Listen // Listen
if (montauk::listen(listenFd) < 0) { if (montauk::listen(listenFd) < 0) {
montauk::print("Error: failed to listen\n"); montauk::service_log("Error: failed to listen");
montauk::closesocket(listenFd); montauk::closesocket(listenFd);
montauk::exit(1); montauk::exit(1);
} }
char msg[128]; char msg[128];
snprintf(msg, sizeof(msg), "MontaukOS httpd listening on port %d\n", (int)port); snprintf(msg, sizeof(msg), "MontaukOS httpd listening on port %d", (int)port);
montauk::print(msg); montauk::service_log(msg);
montauk::print("Press Ctrl+Q between requests to stop.\n\n"); montauk::service_log("Press Ctrl+Q between requests to stop.");
bool running = true; bool running = true;
while (running) { while (running) {
@@ -507,7 +506,7 @@ extern "C" void _start() {
// Accept next client (blocks until a connection arrives) // Accept next client (blocks until a connection arrives)
int clientFd = montauk::accept(listenFd); int clientFd = montauk::accept(listenFd);
if (clientFd < 0) { if (clientFd < 0) {
montauk::print("Warning: accept failed\n"); montauk::service_log("Warning: accept failed");
montauk::yield(); montauk::yield();
continue; continue;
} }
@@ -525,7 +524,7 @@ extern "C" void _start() {
} }
} }
montauk::print("\nShutting down httpd...\n"); montauk::service_log("Shutting down httpd...");
montauk::closesocket(listenFd); montauk::closesocket(listenFd);
montauk::exit(0); montauk::exit(0);
} }
+6 -1
View File
@@ -7,6 +7,7 @@
#include <montauk/syscall.h> #include <montauk/syscall.h>
#include <montauk/config.h> #include <montauk/config.h>
#include <montauk/capabilities.h>
#include <libc/stdio.h> #include <libc/stdio.h>
// ---- ANSI color codes ---- // ---- ANSI color codes ----
@@ -80,7 +81,11 @@ static bool run_service(const char* path, const char* name, bool wait = true) {
snprintf(msg, sizeof(msg), "Starting %s", name); snprintf(msg, sizeof(msg), "Starting %s", name);
log_info(msg); log_info(msg);
int pid = montauk::spawn(path); // Grants are keyed on the executable, not the service id, so repointing a
// privileged entry in init.toml at another program transfers no authority.
montauk::abi::SpawnCapabilities caps =
montauk::caps::for_binary(path, montauk::caps::self_delegable());
int pid = montauk::spawn_with_caps(path, nullptr, "system", caps);
if (pid < 0) { if (pid < 0) {
snprintf(msg, sizeof(msg), "Failed to start %s", name); snprintf(msg, sizeof(msg), "Failed to start %s", name);
log_err(msg); log_err(msg);
+17 -3
View File
@@ -52,16 +52,30 @@ void launch_session(LoginState* ls) {
montauk::memset(ls->password, 0, sizeof(ls->password)); montauk::memset(ls->password, 0, sizeof(ls->password));
ls->password_len = 0; ls->password_len = 0;
// A session may pass on what it holds and no more. Authority is never
// propagated implicitly by spawn(): the desktop and the console both
// consult 0:/config/capabilities.toml to decide which program receives
// which subset, and the kernel clamps that to this set. A standard
// session is delegable for the same reason an admin one is -- otherwise
// its own terminal could not hand the shell the suspend it already has.
montauk::abi::SpawnCapabilities caps{};
caps.permitted = montauk::user::is_admin(ls->username)
? montauk::abi::CAP_ADMIN_SESSION
: montauk::abi::CAP_STANDARD_SESSION;
caps.effective = caps.permitted;
caps.delegable = caps.permitted;
int pid; int pid;
if (ls->session_mode == SESSION_CONSOLE) { if (ls->session_mode == SESSION_CONSOLE) {
char console_args[96]; char console_args[96];
build_console_args(ls, console_args, (int)sizeof(console_args)); build_console_args(ls, console_args, (int)sizeof(console_args));
pid = montauk::spawn("0:/apps/terminal/terminal.elf", console_args); pid = montauk::spawn_with_caps("0:/apps/terminal/terminal.elf",
console_args, ls->username, caps);
} else { } else {
pid = montauk::spawn("0:/os/desktop.elf", ls->username); pid = montauk::spawn_with_caps("0:/os/desktop.elf", ls->username,
ls->username, caps);
} }
if (pid >= 0) { if (pid >= 0) {
montauk::setuser(pid, ls->username);
montauk::waitpid(pid); montauk::waitpid(pid);
if (ls->session_mode == SESSION_DESKTOP) { if (ls->session_mode == SESSION_DESKTOP) {
terminate_desktop_session(pid); terminate_desktop_session(pid);
+8 -6
View File
@@ -250,10 +250,12 @@ void perform_graceful_shutdown(LoginState* ls, int action) {
// ==== Stage 4: dispatch the ACPI power-off / reset ==== // ==== Stage 4: dispatch the ACPI power-off / reset ====
show_stage(ls, heading, rebooting ? "Restarting now..." : "Powering off..."); show_stage(ls, heading, rebooting ? "Restarting now..." : "Powering off...");
if (rebooting) { int rc = rebooting ? montauk::reset() : montauk::shutdown();
montauk::reset(); // A successful power-control syscall never returns. If it does return,
} else { // keep the trusted supervisor alive and make the authorization failure
montauk::shutdown(); // visible instead of falling through an unreachable-code assumption.
} show_stage(ls, heading, rc == montauk::abi::SYS_ERR_PERMISSION
__builtin_unreachable(); ? "Power control permission denied."
: "Power control failed.");
for (;;) montauk::sleep_ms(1000);
} }
+172 -22
View File
@@ -11,25 +11,144 @@ namespace {
// Shipped fallback shown when no wallpaper is configured (see NOTICES.txt). // Shipped fallback shown when no wallpaper is configured (see NOTICES.txt).
constexpr const char* kDefaultWallpaperPath = "0:/os/wallpapers/default.jpg"; constexpr const char* kDefaultWallpaperPath = "0:/os/wallpapers/default.jpg";
// Screen-sized, already tinted pixels, so a re-login skips the JPEG decode and
// the rescale entirely. Kernel-protected (CAP_USER_ADMIN) because it is drawn
// on a screen that is about to take a password.
constexpr const char* kCachePath = "0:/config/wallpaper.cache";
constexpr uint32_t kCacheMagic = 0x4350574DU; // "MWPC"
constexpr uint32_t kCacheVersion = 1;
// The cache lives on the ramdisk, which is kernel heap: past this size it
// costs more memory for the rest of the boot than the decode it saves.
constexpr uint64_t kMaxCacheBytes = 32ULL * 1024 * 1024;
constexpr uint32_t kLoginOverlayAlpha = 0x38; constexpr uint32_t kLoginOverlayAlpha = 0x38;
constexpr uint32_t kLoginOverlayInvAlpha = 255 - kLoginOverlayAlpha; constexpr uint32_t kLoginOverlayInvAlpha = 255 - kLoginOverlayAlpha;
// Fixed-size record. The pixels follow it in the same allocation, so the size
// must stay a multiple of 16 to keep them aligned.
struct CacheHeader {
uint32_t magic;
uint32_t version;
int32_t width; // screen the pixels were scaled for
int32_t height;
uint64_t sourceSize; // source image, as it was when baked
int64_t sourceMtime;
uint32_t overlayAlpha; // tint baked into the pixels
uint32_t reserved;
char sourcePath[256];
uint64_t padding;
};
static_assert(sizeof(CacheHeader) % 16 == 0,
"cache header must stay 16-byte aligned");
static uint8_t dim_component(uint8_t value) { static uint8_t dim_component(uint8_t value) {
uint32_t scaled = kLoginOverlayInvAlpha * value; uint32_t scaled = kLoginOverlayInvAlpha * value;
return (uint8_t)((scaled + 1 + (scaled >> 8)) >> 8); return (uint8_t)((scaled + 1 + (scaled >> 8)) >> 8);
} }
} // namespace // Pick the configured wallpaper, falling back to the shipped one, and return
// the path that actually opens along with its stat record.
bool load_login_wallpaper(LoginState* ls) { bool resolve_source(char* outPath, int cap, montauk::abi::FileStat& outStat) {
auto doc = montauk::config::load("desktop"); auto doc = montauk::config::load("desktop");
char wp[256]; char wp[256];
montauk::strncpy(wp, doc.get_string("wallpaper.path", ""), sizeof(wp)); montauk::strncpy(wp, doc.get_string("wallpaper.path", ""), sizeof(wp));
doc.destroy(); doc.destroy();
int fd = -1; const char* candidates[2] = { wp, kDefaultWallpaperPath };
if (wp[0] != '\0') fd = montauk::open(wp); for (int i = 0; i < 2; i++) {
if (fd < 0) fd = montauk::open(kDefaultWallpaperPath); if (candidates[i][0] == '\0') continue;
int fd = montauk::open(candidates[i]);
if (fd < 0) continue;
montauk::close(fd);
montauk::strncpy(outPath, candidates[i], cap);
montauk::memset(&outStat, 0, sizeof(outStat));
montauk::stat(outPath, &outStat); // best effort: 0/0 still validates
return true;
}
return false;
}
// Allocate one block holding the cache header followed by the screen-sized
// pixel buffer, so saving the cache is a single write with no extra copy.
uint8_t* allocate_blob(int w, int h) {
uint64_t bytes = sizeof(CacheHeader) + (uint64_t)w * h * 4;
return (uint8_t*)montauk::malloc(bytes);
}
bool header_matches(const CacheHeader& h, const LoginState* ls,
const char* srcPath, const montauk::abi::FileStat& st) {
return h.magic == kCacheMagic
&& h.version == kCacheVersion
&& h.width == ls->screen_w
&& h.height == ls->screen_h
&& h.sourceSize == st.size
&& h.sourceMtime == st.mtime
&& h.overlayAlpha == kLoginOverlayAlpha
&& montauk::streq(h.sourcePath, srcPath);
}
bool load_from_cache(LoginState* ls, const char* srcPath,
const montauk::abi::FileStat& st, uint8_t*& blob) {
int fd = montauk::open(kCachePath);
if (fd < 0) return false;
uint64_t pixelBytes = (uint64_t)ls->screen_w * ls->screen_h * 4;
bool ok = montauk::getsize(fd) == sizeof(CacheHeader) + pixelBytes;
CacheHeader header;
if (ok) ok = montauk::read(fd, (uint8_t*)&header, 0, sizeof(header))
== (int)sizeof(header);
if (ok) ok = header_matches(header, ls, srcPath, st);
if (!ok) {
montauk::close(fd);
return false;
}
if (blob == nullptr) blob = allocate_blob(ls->screen_w, ls->screen_h);
if (blob == nullptr) {
montauk::close(fd);
return false;
}
// Read in one call: the buffer is already committed, so this is a single
// kernel-side memcpy out of the ramdisk.
int got = montauk::read(fd, blob + sizeof(CacheHeader), sizeof(CacheHeader),
pixelBytes);
montauk::close(fd);
return got == (int)pixelBytes;
}
void save_to_cache(const LoginState* ls, const char* srcPath,
const montauk::abi::FileStat& st, uint8_t* blob) {
uint64_t pixelBytes = (uint64_t)ls->screen_w * ls->screen_h * 4;
uint64_t total = sizeof(CacheHeader) + pixelBytes;
if (total > kMaxCacheBytes) return;
CacheHeader* header = (CacheHeader*)blob;
montauk::memset(header, 0, sizeof(*header));
header->magic = kCacheMagic;
header->version = kCacheVersion;
header->width = ls->screen_w;
header->height = ls->screen_h;
header->sourceSize = st.size;
header->sourceMtime = st.mtime;
header->overlayAlpha = kLoginOverlayAlpha;
montauk::strncpy(header->sourcePath, srcPath, sizeof(header->sourcePath));
int fd = montauk::fcreate(kCachePath);
if (fd < 0) return; // read-only volume or no authority: not fatal
// One write, so the file is never briefly visible half-written and the
// ramdisk sizes its backing buffer once.
montauk::fwrite(fd, blob, 0, total);
montauk::close(fd);
}
// Decode the source image and scale it to the screen, writing tinted pixels
// into the blob's pixel area.
bool decode_and_scale(LoginState* ls, const char* srcPath, uint8_t*& blob) {
int fd = montauk::open(srcPath);
if (fd < 0) return false; if (fd < 0) return false;
uint64_t size = montauk::getsize(fd); uint64_t size = montauk::getsize(fd);
@@ -59,11 +178,12 @@ bool load_login_wallpaper(LoginState* ls) {
int dst_w = ls->screen_w; int dst_w = ls->screen_w;
int dst_h = ls->screen_h; int dst_h = ls->screen_h;
uint32_t* scaled = (uint32_t*)montauk::malloc((uint64_t)dst_w * dst_h * 4); if (blob == nullptr) blob = allocate_blob(dst_w, dst_h);
if (!scaled) { if (!blob) {
stbi_image_free(rgb); stbi_image_free(rgb);
return false; return false;
} }
uint32_t* scaled = (uint32_t*)(blob + sizeof(CacheHeader));
int src_crop_w, src_crop_h, src_x0, src_y0; int src_crop_w, src_crop_h, src_x0, src_y0;
if ((int64_t)img_w * dst_h > (int64_t)img_h * dst_w) { if ((int64_t)img_w * dst_h > (int64_t)img_h * dst_w) {
@@ -78,29 +198,59 @@ bool load_login_wallpaper(LoginState* ls) {
src_y0 = (img_h - src_crop_h) / 2; src_y0 = (img_h - src_crop_h) / 2;
} }
// Source column per destination column, computed once. Inline, the same
// expression costs one 64-bit divide per pixel -- millions of them, and
// idiv neither pipelines nor vectorizes.
int* col = (int*)montauk::malloc((uint64_t)dst_w * sizeof(int));
if (!col) {
stbi_image_free(rgb);
return false;
}
for (int x = 0; x < dst_w; x++) {
int sx = src_x0 + (int)((int64_t)x * src_crop_w / dst_w);
if (sx < 0) sx = 0;
if (sx >= img_w) sx = img_w - 1;
col[x] = sx * 3;
}
for (int y = 0; y < dst_h; y++) { for (int y = 0; y < dst_h; y++) {
int sy = src_y0 + (int)((int64_t)y * src_crop_h / dst_h); int sy = src_y0 + (int)((int64_t)y * src_crop_h / dst_h);
if (sy < 0) sy = 0; if (sy < 0) sy = 0;
if (sy >= img_h) sy = img_h - 1; if (sy >= img_h) sy = img_h - 1;
const unsigned char* row = rgb + (int64_t)sy * img_w * 3;
uint32_t* dst = scaled + (int64_t)y * dst_w;
for (int x = 0; x < dst_w; x++) { for (int x = 0; x < dst_w; x++) {
int sx = src_x0 + (int)((int64_t)x * src_crop_w / dst_w); const unsigned char* src = row + col[x];
if (sx < 0) sx = 0; dst[x] = 0xFF000000u
if (sx >= img_w) sx = img_w - 1; | ((uint32_t)dim_component(src[0]) << 16)
int si = (sy * img_w + sx) * 3; | ((uint32_t)dim_component(src[1]) << 8)
uint8_t r = dim_component(rgb[si]); | (uint32_t)dim_component(src[2]);
uint8_t g = dim_component(rgb[si + 1]);
uint8_t b = dim_component(rgb[si + 2]);
scaled[y * dst_w + x] = 0xFF000000u
| ((uint32_t)r << 16)
| ((uint32_t)g << 8)
| (uint32_t)b;
} }
} }
montauk::mfree(col);
stbi_image_free(rgb); stbi_image_free(rgb);
ls->bg_wallpaper = scaled; return true;
ls->bg_wallpaper_w = dst_w; }
ls->bg_wallpaper_h = dst_h;
} // namespace
bool load_login_wallpaper(LoginState* ls) {
char srcPath[256];
montauk::abi::FileStat st;
if (!resolve_source(srcPath, sizeof(srcPath), st)) return false;
uint8_t* blob = nullptr;
bool cached = load_from_cache(ls, srcPath, st, blob);
if (!cached && !decode_and_scale(ls, srcPath, blob)) {
if (blob) montauk::mfree(blob);
return false;
}
if (!cached) save_to_cache(ls, srcPath, st, blob);
ls->bg_wallpaper = (uint32_t*)(blob + sizeof(CacheHeader));
ls->bg_wallpaper_w = ls->screen_w;
ls->bg_wallpaper_h = ls->screen_h;
ls->has_wallpaper = true; ls->has_wallpaper = true;
return true; return true;
} }
+17 -3
View File
@@ -25,9 +25,16 @@ static void maybe_run_setup_session() {
montauk::user::set_session(user); montauk::user::set_session(user);
doc.destroy(); doc.destroy();
int pid = montauk::spawn("0:/os/desktop.elf", user); montauk::abi::SpawnCapabilities caps{};
bool admin = montauk::streq(role, "admin");
caps.permitted = caps.effective = admin
? montauk::abi::CAP_ADMIN_SESSION
: montauk::abi::CAP_STANDARD_SESSION;
// Matches launch_session(): a session delegates from what it holds, and
// the grant table decides which program receives which part of it.
caps.delegable = caps.permitted;
int pid = montauk::spawn_with_caps("0:/os/desktop.elf", user, user, caps);
if (pid >= 0) { if (pid >= 0) {
montauk::setuser(pid, user);
montauk::waitpid(pid); montauk::waitpid(pid);
terminate_desktop_session(pid); terminate_desktop_session(pid);
} }
@@ -62,7 +69,6 @@ extern "C" void _start() {
gui::fonts::init(); gui::fonts::init();
montauk::set_mouse_bounds(ls->screen_w - 1, ls->screen_h - 1); montauk::set_mouse_bounds(ls->screen_w - 1, ls->screen_h - 1);
load_login_wallpaper(ls);
// MTK theme (picks up the system accent). The compose buffer is only // MTK theme (picks up the system accent). The compose buffer is only
// needed when the framebuffer pitch is not tightly packed; otherwise the // needed when the framebuffer pitch is not tightly packed; otherwise the
@@ -76,6 +82,14 @@ extern "C" void _start() {
maybe_run_setup_session(); maybe_run_setup_session();
initialize_login_mode(ls); initialize_login_mode(ls);
// Put the login card on screen before touching the wallpaper. Decoding a
// multi-megapixel JPEG takes long enough to read as a hang if nothing has
// been painted yet; drawn first, it lands as a background appearing behind
// a screen the user can already type into. The loop below redraws with the
// wallpaper because first_frame is still set.
draw_login_screen(ls);
load_login_wallpaper(ls);
bool first_frame = true; bool first_frame = true;
uint64_t input_serial = montauk::input_wait(0, 0); uint64_t input_serial = montauk::input_wait(0, 0);
for (;;) { for (;;) {
+7 -1
View File
@@ -951,7 +951,13 @@ static void render() {
} }
static void launch_dhcp() { static void launch_dhcp() {
int pid = montauk::spawn("0:/os/dhcp.elf"); montauk::abi::SpawnCapabilities caps{
montauk::abi::CAP_NETWORK_ADMIN,
montauk::abi::CAP_NETWORK_ADMIN,
0
};
int pid = montauk::spawn_with_caps("0:/os/dhcp.elf", nullptr,
nullptr, caps);
if (pid >= 0) { if (pid >= 0) {
set_status("DHCP client started"); set_status("DHCP client started");
} else { } else {
+5 -3
View File
@@ -6,8 +6,10 @@
#include <montauk/config.h> #include <montauk/config.h>
#include <montauk/ntp.h> #include <montauk/ntp.h>
#include <montauk/service_log.h>
#include <montauk/string.h> #include <montauk/string.h>
#include <montauk/syscall.h> #include <montauk/syscall.h>
#include <libc/stdio.h>
static constexpr uint64_t RETRY_INTERVAL_MS = static constexpr uint64_t RETRY_INTERVAL_MS =
montauk::ntp::MIN_QUERY_INTERVAL_MS; montauk::ntp::MIN_QUERY_INTERVAL_MS;
@@ -15,9 +17,9 @@ static constexpr uint64_t SYNC_INTERVAL_MS = 60ULL * 60ULL * 1000ULL;
static constexpr uint64_t CONFIG_POLL_MS = 5000; static constexpr uint64_t CONFIG_POLL_MS = 5000;
static void log(const char* message) { static void log(const char* message) {
montauk::print("ntp: "); char line[256];
montauk::print(message); snprintf(line, sizeof(line), "ntp: %s", message);
montauk::print("\n"); montauk::service_log(line);
} }
static bool load_settings(char* server, int server_cap) { static bool load_settings(char* server, int server_cap) {
+7 -3
View File
@@ -5,6 +5,7 @@
*/ */
#include <montauk/syscall.h> #include <montauk/syscall.h>
#include <montauk/service_log.h>
#include <print/print.hpp> #include <print/print.hpp>
#include "test_page_jpeg.hpp" #include "test_page_jpeg.hpp"
@@ -64,12 +65,15 @@ static void write_daemon_state(const char* phase, const char* subject = nullptr,
} }
static void log_msg(const char* fmt, ...) { static void log_msg(const char* fmt, ...) {
char body[768];
va_list ap; va_list ap;
va_start(ap, fmt); va_start(ap, fmt);
printf("[printd] "); vsnprintf(body, sizeof(body), fmt, ap);
vprintf(fmt, ap);
printf("\n");
va_end(ap); va_end(ap);
char line[800];
snprintf(line, sizeof(line), "[printd] %s", body);
montauk::service_log(line);
} }
static void set_job_state(JobMeta* job, const char* state, const char* message) { static void set_job_state(JobMeta* job, const char* state, const char* message) {
+20 -1
View File
@@ -7,6 +7,7 @@
#include <montauk/syscall.h> #include <montauk/syscall.h>
#include <montauk/string.h> #include <montauk/string.h>
#include <montauk/heap.h> #include <montauk/heap.h>
#include <montauk/capabilities.h>
#include <gui/mtk.hpp> #include <gui/mtk.hpp>
extern "C" { extern "C" {
@@ -392,8 +393,26 @@ static bool refresh_state(bool force) {
return true; return true;
} }
// Ending another process needs CAP_PROCESS_ADMIN; without it the kernel only
// permits killing our own descendants. Capabilities cannot change over a
// process lifetime and this is consulted from the render path, so resolve it
// once rather than per frame.
static bool g_process_admin = false;
static bool g_process_admin_known = false;
static bool process_admin_granted() {
if (!g_process_admin_known) {
montauk::abi::SpawnCapabilities mine;
montauk::caps::self(mine);
g_process_admin = (mine.effective & montauk::abi::CAP_PROCESS_ADMIN) != 0;
g_process_admin_known = true;
}
return g_process_admin;
}
static bool can_kill_selected() { static bool can_kill_selected() {
return g_pm.active_tab == PM_TAB_PROCESSES return process_admin_granted()
&& g_pm.active_tab == PM_TAB_PROCESSES
&& g_pm.selected >= 0 && g_pm.selected >= 0
&& g_pm.selected < g_pm.proc_count && g_pm.selected < g_pm.proc_count
&& g_pm.procs[g_pm.selected].pid != 0 && g_pm.procs[g_pm.selected].pid != 0
+6 -1
View File
@@ -8,5 +8,10 @@
extern "C" void _start() { extern "C" void _start() {
montauk::print("Rebooting...\n"); montauk::print("Rebooting...\n");
montauk::reset(); int rc = montauk::reset();
if (rc == montauk::abi::SYS_ERR_PERMISSION)
montauk::print("reset: permission denied\n");
else
montauk::print("reset: reboot failed\n");
montauk::exit(1);
} }
+32
View File
@@ -0,0 +1,32 @@
MAKEFLAGS += -rR
.SUFFIXES:
TOOLCHAIN_PREFIX := $(shell cd ../../.. && pwd)/toolchain/local/bin/x86_64-montauk-
CXX := $(TOOLCHAIN_PREFIX)g++
CC := $(TOOLCHAIN_PREFIX)gcc
LD := $(TOOLCHAIN_PREFIX)ld
STRIP := $(TOOLCHAIN_PREFIX)strip
PROG_INC := ../../include
OBJDIR := obj
LIBOUTDIR := ../../bin/os
LIBC_SRC := ../../lib/libc/libc.c
GCC_INCLUDE := $(shell $(CC) -print-file-name=include)
CXXFLAGS := -std=gnu++20 -g -O2 -pipe -Wall -Wextra -ffreestanding -fno-stack-protector -fno-stack-check -fPIC -fvisibility=hidden -fno-rtti -fno-exceptions -ffunction-sections -fdata-sections -mno-80387 -mno-mmx -mno-sse -mno-sse2 -MMD -MP -I $(PROG_INC) -isystem $(PROG_INC)/libc
LIBC_CFLAGS := -std=gnu11 -O2 -ffreestanding -fno-stack-protector -fno-stack-check -fPIC -fvisibility=hidden -ffunction-sections -fdata-sections -isystem $(PROG_INC)/libc -isystem $(GCC_INCLUDE)
LDFLAGS := -shared --build-id=none --gc-sections --hash-style=sysv -m elf_x86_64
OBJS := $(OBJDIR)/rtlsdr.o $(OBJDIR)/r820t.o
TARGET := $(LIBOUTDIR)/rtlsdr.lib
.PHONY: all clean
all: $(TARGET)
$(TARGET): $(OBJS) $(OBJDIR)/libc_pic.o Makefile
mkdir -p $(LIBOUTDIR)
$(LD) $(LDFLAGS) $(OBJS) $(OBJDIR)/libc_pic.o -o $@
$(STRIP) --strip-debug $@
$(OBJDIR)/%.o: %.cpp Makefile
mkdir -p $(OBJDIR)
$(CXX) $(CXXFLAGS) -c $< -o $@
$(OBJDIR)/libc_pic.o: $(LIBC_SRC) Makefile
mkdir -p $(OBJDIR)
$(CC) $(LIBC_CFLAGS) -c $(LIBC_SRC) -o $@
-include $(OBJS:.o=.d)
clean:
rm -rf $(OBJDIR) $(TARGET)
@@ -1,20 +1,17 @@
/* #include "r820t.hpp"
* R820t.cpp
* Rafael Micro R820T / R820T2 silicon tuner driver.
* Copyright (c) 2026 Daniel Hammer
*/
#include "R820t.hpp" namespace {
#include "RtlSdr.hpp" // I2C facade (RtlI2cWrite / RtlI2cRead) struct LogSink { template<class T> LogSink& operator<<(const T&) { return *this; } };
#include <Terminal/Terminal.hpp> namespace base { static constexpr int hex = 0; static constexpr int dec = 0; }
#include <CppLib/Stream.hpp> }
#define KernelLogStream(level, tag) LogSink{}
#define WARNING 0
#define INFO 0
#define ERROR 0
#define OK 0
using namespace Kt; namespace rtlsdr_internal {
namespace Drivers::USB::Radio {
// =========================================================================
// Tuner constant tables
// ========================================================================= // =========================================================================
// Initial register values for registers 0x05..0x1f (27 registers). Written // Initial register values for registers 0x05..0x1f (27 registers). Written
@@ -97,7 +94,7 @@ namespace Drivers::USB::Radio {
uint8_t size = len > 7 ? 7 : len; uint8_t size = len > 7 ? 7 : len;
buf[0] = reg; buf[0] = reg;
for (uint8_t i = 0; i < size; i++) buf[1 + i] = val[pos + i]; for (uint8_t i = 0; i < size; i++) buf[1 + i] = val[pos + i];
if (!RtlI2cWrite(d.slotId, R820T_I2C_ADDR, buf, (uint8_t)(size + 1))) if (!RtlI2cWrite(d.owner, R820T_I2C_ADDR, buf, (uint8_t)(size + 1)))
return false; return false;
for (uint8_t i = 0; i < size; i++) for (uint8_t i = 0; i < size; i++)
if ((reg + i) < 32) d.regs[reg + i] = val[pos + i]; if ((reg + i) < 32) d.regs[reg + i] = val[pos + i];
@@ -129,8 +126,8 @@ namespace Drivers::USB::Radio {
uint8_t raw[16]; uint8_t raw[16];
if (len > sizeof(raw)) len = sizeof(raw); if (len > sizeof(raw)) len = sizeof(raw);
uint8_t ptr = 0x00; uint8_t ptr = 0x00;
if (!RtlI2cWrite(d.slotId, R820T_I2C_ADDR, &ptr, 1)) return false; if (!RtlI2cWrite(d.owner, R820T_I2C_ADDR, &ptr, 1)) return false;
if (!RtlI2cRead(d.slotId, R820T_I2C_ADDR, raw, len)) return false; if (!RtlI2cRead(d.owner, R820T_I2C_ADDR, raw, len)) return false;
for (uint8_t i = 0; i < len; i++) out[i] = BitRev(raw[i]); for (uint8_t i = 0; i < len; i++) out[i] = BitRev(raw[i]);
return true; return true;
} }
@@ -212,16 +209,16 @@ namespace Drivers::USB::Radio {
return ok; return ok;
} }
bool R820tDetect(uint8_t slotId) { bool R820tDetect(rtlsdr_device* owner) {
// Match the reference driver's chip-id probe: set the read pointer to // Match the reference driver's chip-id probe: set the read pointer to
// register 0, then read one byte *without* bit-reversal and compare it // register 0, then read one byte *without* bit-reversal and compare it
// to the raw R820T id. (The bit-reversal only applies to the status // to the raw R820T id. (The bit-reversal only applies to the status
// registers read during tuning, not to this id check.) // registers read during tuning, not to this id check.)
uint8_t ptr = 0x00; uint8_t ptr = 0x00;
RtlI2cWrite(slotId, R820T_I2C_ADDR, &ptr, 1); RtlI2cWrite(owner, R820T_I2C_ADDR, &ptr, 1);
uint8_t raw[1] = {0}; uint8_t raw[1] = {0};
if (!RtlI2cRead(slotId, R820T_I2C_ADDR, raw, 1)) { if (!RtlI2cRead(owner, R820T_I2C_ADDR, raw, 1)) {
KernelLogStream(WARNING, "R820T") << "id read failed (I2C transfer error)"; KernelLogStream(WARNING, "R820T") << "id read failed (I2C transfer error)";
return false; return false;
} }
@@ -231,8 +228,8 @@ namespace Drivers::USB::Radio {
return raw[0] == R820T_CHECK_VAL; return raw[0] == R820T_CHECK_VAL;
} }
bool R820tInit(R820tDev& d, uint8_t slotId, uint32_t xtal, uint32_t intFreq) { bool R820tInit(R820tDev& d, rtlsdr_device* owner, uint32_t xtal, uint32_t intFreq) {
d.slotId = slotId; d.owner = owner;
d.xtal = xtal; d.xtal = xtal;
d.intFreq = intFreq; d.intFreq = intFreq;
d.hasLock = false; d.hasLock = false;
+28
View File
@@ -0,0 +1,28 @@
#pragma once
#include <stdint.h>
struct rtlsdr_device;
namespace rtlsdr_internal {
static constexpr uint8_t R820T_I2C_ADDR = 0x34;
static constexpr uint8_t R820T_CHECK_VAL = 0x69;
static constexpr uint8_t R820T_NUM_REGS = 27;
struct R820tDev {
rtlsdr_device* owner;
uint32_t xtal;
uint32_t intFreq;
uint8_t regs[32];
bool hasLock;
bool inited;
};
bool RtlI2cWrite(rtlsdr_device* owner, uint8_t addr, const uint8_t* data, uint8_t len);
bool RtlI2cRead(rtlsdr_device* owner, uint8_t addr, uint8_t* data, uint8_t len);
bool R820tDetect(rtlsdr_device* owner);
bool R820tInit(R820tDev& dev, rtlsdr_device* owner, uint32_t xtal, uint32_t intFreq);
bool R820tSetFreq(R820tDev& dev, uint64_t hz);
bool R820tSetGain(R820tDev& dev, int manual, int tenthsDb);
void R820tStandby(R820tDev& dev);
const int* R820tGainTable(int* count);
}
+491
View File
@@ -0,0 +1,491 @@
#include <rtlsdr/rtlsdr.h>
#include <montauk/syscall.h>
#include <libc/string.h>
#include "r820t.hpp"
struct rtlsdr_device { int marker; };
namespace rtlsdr_internal {
namespace {
struct LogSink { template<class T> LogSink& operator<<(const T&) { return *this; } };
namespace base { static constexpr int hex=0; static constexpr int dec=0; }
}
#define KernelLogStream(level, tag) LogSink{}
#define WARNING 0
#define INFO 0
#define ERROR 0
#define OK 0
static rtlsdr_device g_storage{};
static rtlsdr_device* g_openDev = nullptr;
static int g_usbHandle = -1;
static bool g_hwInited = false;
static bool g_streaming = false;
static uint8_t g_ctlStorage[4096]{};
static uint8_t* g_ctlBuf = g_ctlStorage;
static R820tDev g_tuner{};
static uint32_t g_rtlXtal = 28800000;
static int g_ppm = 0;
static int g_manual = 0;
static int g_gain = 0;
static int g_directSamp = 0;
static uint64_t g_lastFreq = 0;
static uint32_t g_sampleRate = 0;
namespace UsbCompat {
static constexpr uint32_t CC_SUCCESS = 1;
static uint32_t ControlTransfer(rtlsdr_device*, uint8_t requestType,
uint8_t request, uint16_t value, uint16_t index, uint16_t length,
uint8_t* data, bool) {
montauk::abi::UsbControlRequest setup{requestType, request, value, index, length};
return montauk::usb_control(g_usbHandle, &setup, data, length) == 0 ? CC_SUCCESS : 0;
}
}
// =========================================================================
// 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;
static constexpr uint32_t R82XX_IF = 3570000;
static constexpr uint32_t TWO_POW22 = 1u << 22;
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 UsbCompat::ControlTransfer(g_openDev, CTRL_OUT, 0, addr, index, len,
g_ctlBuf, false) == UsbCompat::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;
UsbCompat::ControlTransfer(g_openDev, 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 = UsbCompat::ControlTransfer(g_openDev, CTRL_OUT, 0, waddr, index, len,
g_ctlBuf, false) == UsbCompat::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(rtlsdr_device* owner, 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 = UsbCompat::ControlTransfer(owner, CTRL_OUT, 0, i2cAddr, index, len,
g_ctlBuf, false);
if (cc != UsbCompat::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 == UsbCompat::CC_SUCCESS;
}
bool RtlI2cRead(rtlsdr_device* owner, 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 = UsbCompat::ControlTransfer(owner, CTRL_IN, 0, i2cAddr, index, len,
g_ctlBuf, true);
if (cc != UsbCompat::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_openDev);
bool ok = detected && R820tInit(g_tuner, g_openDev, 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_openDev || !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_openDev;
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_openDev, 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 bool IsSupported(uint16_t vid, uint16_t pid) {
return vid == 0x0bda && (pid == 0x2832 || pid == 0x2838);
}
static int FindInterface(int wanted, montauk::abi::UsbInterfaceInfo* out) {
montauk::abi::UsbInterfaceInfo interfaces[32]{};
int count = montauk::usb_list(interfaces, 32);
int matched = 0;
for (int i = 0; i < count; ++i) {
if (!IsSupported(interfaces[i].vendorId, interfaces[i].productId)) continue;
if (matched++ == wanted) { if (out) *out = interfaces[i]; return 0; }
}
return -1;
}
static void CopyText(char* dst, uint32_t cap, const char* src) {
uint32_t i = 0;
while (i + 1 < cap && src[i]) { dst[i] = src[i]; ++i; }
dst[i] = 0;
}
static void FillInfo(rtlsdr_device_info* out) {
*out = {};
CopyText(out->name, sizeof(out->name), "Realtek RTL2832U");
CopyText(out->tuner, sizeof(out->tuner), "Rafael Micro R820T2");
CopyText(out->serial, sizeof(out->serial), "USB RTL-SDR");
out->freq_min = 24000000ull;
out->freq_max = 1766000000ull;
out->sample_rate_min = 225001;
out->sample_rate_max = 3200000;
int n = 0;
const int* gains = R820tGainTable(&n);
out->num_gains = n < 32 ? (uint32_t)n : 32;
for (uint32_t i = 0; i < out->num_gains; ++i) out->gains[i] = gains[i];
}
} // namespace rtlsdr_internal
#define RTLSDR_EXPORT extern "C" __attribute__((visibility("default")))
RTLSDR_EXPORT int rtlsdr_count(void) {
montauk::abi::UsbInterfaceInfo info{};
int n = 0;
while (rtlsdr_internal::FindInterface(n, &info) == 0) ++n;
return n;
}
RTLSDR_EXPORT int rtlsdr_get_device_info(int index, rtlsdr_device_info* out) {
if (!out || index < 0 || rtlsdr_internal::FindInterface(index, nullptr) != 0) return -1;
rtlsdr_internal::FillInfo(out);
return 0;
}
RTLSDR_EXPORT int rtlsdr_open(rtlsdr_device** out, int index) {
using namespace rtlsdr_internal;
if (!out || index < 0 || g_openDev) return -1;
montauk::abi::UsbInterfaceInfo info{};
if (FindInterface(index, &info) != 0 || info.kernelDriverBound || info.claimed ||
!info.bulkInEndpoint) return -1;
int handle = montauk::usb_claim(info.slotId, info.interfaceNumber);
if (handle < 0) return handle;
g_usbHandle = handle;
g_openDev = &g_storage;
g_storage.marker = 0x52544c53;
g_hwInited = false;
g_streaming = false;
g_rtlXtal = RTL_XTAL;
g_ppm = g_manual = g_gain = g_directSamp = 0;
g_lastFreq = 0;
g_sampleRate = 0;
g_tuner = {};
*out = g_openDev;
return 0;
}
RTLSDR_EXPORT int rtlsdr_close(rtlsdr_device* dev) {
using namespace rtlsdr_internal;
if (!dev || dev != g_openDev) return -1;
if (g_streaming) {
g_streaming = false;
montauk::usb_bulk_in_stop(g_usbHandle);
RegWrite(BLOCK_USB, USB_EPA_CTL, 0x1002, 2);
}
int result = montauk::usb_close(g_usbHandle);
g_usbHandle = -1;
g_openDev = nullptr;
g_hwInited = false;
return result;
}
RTLSDR_EXPORT int rtlsdr_set_center_freq(rtlsdr_device* dev, uint64_t hz) {
return dev == rtlsdr_internal::g_openDev ? rtlsdr_internal::DoSetFreq(hz) : -1;
}
RTLSDR_EXPORT uint64_t rtlsdr_get_center_freq(const rtlsdr_device* dev) {
return dev == rtlsdr_internal::g_openDev ? rtlsdr_internal::g_lastFreq : 0;
}
RTLSDR_EXPORT int rtlsdr_set_sample_rate(rtlsdr_device* dev, uint32_t hz) {
if (dev != rtlsdr_internal::g_openDev) return -1;
int result = rtlsdr_internal::DoSetSampleRate(hz);
if (result == 0) rtlsdr_internal::g_sampleRate = hz;
return result;
}
RTLSDR_EXPORT uint32_t rtlsdr_get_sample_rate(const rtlsdr_device* dev) {
return dev == rtlsdr_internal::g_openDev ? rtlsdr_internal::g_sampleRate : 0;
}
RTLSDR_EXPORT int rtlsdr_set_tuner_gain_mode(rtlsdr_device* dev, int manual) {
return dev == rtlsdr_internal::g_openDev ? rtlsdr_internal::DoSetGainMode(manual) : -1;
}
RTLSDR_EXPORT int rtlsdr_set_tuner_gain(rtlsdr_device* dev, int gain) {
return dev == rtlsdr_internal::g_openDev ? rtlsdr_internal::DoSetGain(gain) : -1;
}
RTLSDR_EXPORT int rtlsdr_set_freq_correction(rtlsdr_device* dev, int ppm) {
return dev == rtlsdr_internal::g_openDev ? rtlsdr_internal::DoSetFreqCorrection(ppm) : -1;
}
RTLSDR_EXPORT int rtlsdr_set_agc_mode(rtlsdr_device* dev, int on) {
return dev == rtlsdr_internal::g_openDev ? rtlsdr_internal::DoSetAgc(on) : -1;
}
RTLSDR_EXPORT int rtlsdr_set_direct_sampling(rtlsdr_device* dev, int mode) {
if (mode < 0 || mode > 2) return -1;
return dev == rtlsdr_internal::g_openDev ? rtlsdr_internal::DoSetDirectSampling(mode) : -1;
}
RTLSDR_EXPORT int rtlsdr_start(rtlsdr_device* dev) {
using namespace rtlsdr_internal;
if (dev != g_openDev || !EnsureInit()) return -1;
RegWrite(BLOCK_USB, USB_EPA_CTL, 0x1002, 2);
RegWrite(BLOCK_USB, USB_EPA_CTL, 0x0000, 2);
int result = montauk::usb_bulk_in_start(g_usbHandle, 4096, 16);
if (result == 0) g_streaming = true;
return result;
}
RTLSDR_EXPORT int rtlsdr_stop(rtlsdr_device* dev) {
using namespace rtlsdr_internal;
if (dev != g_openDev) return -1;
g_streaming = false;
int result = montauk::usb_bulk_in_stop(g_usbHandle);
RegWrite(BLOCK_USB, USB_EPA_CTL, 0x1002, 2);
return result;
}
RTLSDR_EXPORT int rtlsdr_read(rtlsdr_device* dev, void* data, uint32_t length) {
using namespace rtlsdr_internal;
if (dev != g_openDev || !g_streaming) return -1;
return montauk::usb_bulk_in_read(g_usbHandle, data, length);
}
+22
View File
@@ -0,0 +1,22 @@
MAKEFLAGS += -rR
.SUFFIXES:
TOOLCHAIN_PREFIX := $(shell cd ../../.. && pwd)/toolchain/local/bin/x86_64-montauk-
CXX := $(TOOLCHAIN_PREFIX)g++
PROG_INC := ../../include
LINK_LD := ../../link.ld
OBJDIR := obj
TARGET := ../../bin/os/sdr.elf
LIBS := ../../lib/libloader/liblibloader.a ../../lib/libc/liblibc.a
CXXFLAGS := -std=gnu++20 -g -O2 -pipe -Wall -Wextra -ffreestanding -fno-stack-protector -fno-stack-check -fno-rtti -fno-exceptions -ffunction-sections -fdata-sections -mno-80387 -mno-mmx -mno-sse -mno-sse2 -I $(PROG_INC) -isystem $(PROG_INC)/libc
LDFLAGS := -nostdlib -Wl,--gc-sections -T $(LINK_LD)
.PHONY: all clean
all: $(TARGET)
$(TARGET): $(OBJDIR)/main.o $(LIBS) $(LINK_LD) Makefile
mkdir -p ../../bin/os
$(CXX) $(CXXFLAGS) $(LDFLAGS) $(OBJDIR)/main.o $(LIBS) -o $@
$(OBJDIR)/main.o: main.cpp Makefile
mkdir -p $(OBJDIR)
$(CXX) $(CXXFLAGS) -MMD -MP -c $< -o $@
-include $(OBJDIR)/main.d
clean:
rm -rf $(OBJDIR) $(TARGET)
+75 -30
View File
@@ -2,7 +2,7 @@
* main.cpp * main.cpp
* sdr - software-defined radio receive demo. * sdr - software-defined radio receive demo.
* *
* Exercises the generic SDR Rx API end to end: enumerate receivers, open one, * Exercises the userspace rtlsdr.lib driver end to end: enumerate receivers, open one,
* tune it, configure sample rate / gain, stream raw I/Q for a short window, * tune it, configure sample rate / gain, stream raw I/Q for a short window,
* and report basic signal statistics. The RTL-SDR (RTL2832U/R820T2) driver * and report basic signal statistics. The RTL-SDR (RTL2832U/R820T2) driver
* provides the receiver; with no dongle attached the demo reports that and * provides the receiver; with no dongle attached the demo reports that and
@@ -17,9 +17,49 @@
#include <montauk/syscall.h> #include <montauk/syscall.h>
#include <montauk/string.h> #include <montauk/string.h>
#include <rtlsdr/rtlsdr.h>
#include <libloader/libloader.h>
using namespace montauk; using namespace montauk;
struct RtlApi {
int (*count)();
int (*get_info)(int, rtlsdr_device_info*);
int (*open)(rtlsdr_device**, int);
int (*close)(rtlsdr_device*);
int (*set_freq)(rtlsdr_device*, uint64_t);
uint64_t (*get_freq)(const rtlsdr_device*);
int (*set_rate)(rtlsdr_device*, uint32_t);
uint32_t (*get_rate)(const rtlsdr_device*);
int (*set_gain_mode)(rtlsdr_device*, int);
int (*set_ppm)(rtlsdr_device*, int);
int (*start)(rtlsdr_device*);
int (*stop)(rtlsdr_device*);
int (*read)(rtlsdr_device*, void*, uint32_t);
};
static RtlApi g_rtl{};
static bool load_rtlsdr() {
LibHandle* lib = libloader::dlopen("0:/os/rtlsdr.lib");
if (!lib) return false;
#define LOAD(field, symbol) g_rtl.field = reinterpret_cast<decltype(g_rtl.field)>(libloader::dlsym(lib, symbol)); if (!g_rtl.field) return false
LOAD(count, "rtlsdr_count");
LOAD(get_info, "rtlsdr_get_device_info");
LOAD(open, "rtlsdr_open");
LOAD(close, "rtlsdr_close");
LOAD(set_freq, "rtlsdr_set_center_freq");
LOAD(get_freq, "rtlsdr_get_center_freq");
LOAD(set_rate, "rtlsdr_set_sample_rate");
LOAD(get_rate, "rtlsdr_get_sample_rate");
LOAD(set_gain_mode, "rtlsdr_set_tuner_gain_mode");
LOAD(set_ppm, "rtlsdr_set_freq_correction");
LOAD(start, "rtlsdr_start");
LOAD(stop, "rtlsdr_stop");
LOAD(read, "rtlsdr_read");
#undef LOAD
return true;
}
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Small integer / formatting helpers (freestanding; no libc printf) // Small integer / formatting helpers (freestanding; no libc printf)
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
@@ -105,17 +145,17 @@ static bool next_token(const char** rest, char* tokbuf, int cap) {
static uint8_t g_iq[64 * 1024]; // I/Q read buffer (CU8) static uint8_t g_iq[64 * 1024]; // I/Q read buffer (CU8)
static void print_receiver(int idx, const montauk::abi::SdrDeviceInfo& info) { static void print_receiver(int idx, const rtlsdr_device_info& info) {
print(" ["); put_u64((uint64_t)idx); print("] "); print(" ["); put_u64((uint64_t)idx); print("] ");
print(info.name); print(info.name);
print(" tuner="); print(info.tuner); print(" tuner="); print(info.tuner);
if (info.serial[0]) { print(" ("); print(info.serial); print(")"); } if (info.serial[0]) { print(" ("); print(info.serial); print(")"); }
print("\n freq "); print("\n freq ");
put_mhz(info.freqMin); print(" - "); put_mhz(info.freqMax); put_mhz(info.freq_min); print(" - "); put_mhz(info.freq_max);
print(" rate "); put_u64(info.sampleRateMin); print(" - "); print(" rate "); put_u64(info.sample_rate_min); print(" - ");
put_u64(info.sampleRateMax); print(" Hz\n"); put_u64(info.sample_rate_max); print(" Hz\n");
print(" gains ("); put_u64(info.numGains); print(" steps):"); print(" gains ("); put_u64(info.num_gains); print(" steps):");
uint32_t shown = info.numGains < 32 ? info.numGains : 32; uint32_t shown = info.num_gains < 32 ? info.num_gains : 32;
for (uint32_t g = 0; g < shown; g++) { for (uint32_t g = 0; g < shown; g++) {
putchar(' '); putchar(' ');
put_i64(info.gains[g] / 10); put_i64(info.gains[g] / 10);
@@ -138,9 +178,13 @@ extern "C" void _start() {
if (next_token(&rest, tok, sizeof(tok))) rateHz = (uint32_t)parse_u64(tok); if (next_token(&rest, tok, sizeof(tok))) rateHz = (uint32_t)parse_u64(tok);
print("=== MontaukOS SDR receive demo ===\n\n"); print("=== MontaukOS SDR receive demo ===\n\n");
if (!load_rtlsdr()) {
print("sdr: could not load 0:/os/rtlsdr.lib\n");
montauk::exit(1);
}
// --- Enumerate ------------------------------------------------------- // --- Enumerate -------------------------------------------------------
int count = montauk::sdr_count(); int count = g_rtl.count();
print("SDR receivers detected: "); put_u64((uint64_t)count); print("\n"); print("SDR receivers detected: "); put_u64((uint64_t)count); print("\n");
if (count <= 0) { if (count <= 0) {
print("\nNo SDR receivers are connected.\n"); print("\nNo SDR receivers are connected.\n");
@@ -148,49 +192,50 @@ extern "C" void _start() {
montauk::exit(0); montauk::exit(0);
} }
montauk::abi::SdrDeviceInfo info; rtlsdr_device_info info;
for (int i = 0; i < count; i++) { for (int i = 0; i < count; i++) {
if (montauk::sdr_info(i, &info) == 0) print_receiver(i, info); if (g_rtl.get_info(i, &info) == 0) print_receiver(i, info);
} }
// --- Open + configure receiver 0 ------------------------------------ // --- Open + configure receiver 0 ------------------------------------
int idx = 0; int idx = 0;
if (montauk::sdr_info(idx, &info) != 0) { if (g_rtl.get_info(idx, &info) != 0) {
print("\nsdr: failed to query receiver info\n"); print("\nsdr: failed to query receiver info\n");
montauk::exit(1); montauk::exit(1);
} }
int h = montauk::sdr_open(idx); rtlsdr_device* dev = nullptr;
if (h < 0) { int open_result = g_rtl.open(&dev, idx);
if (open_result < 0) {
print("\nsdr: failed to open receiver 0\n"); print("\nsdr: failed to open receiver 0\n");
montauk::exit(1); montauk::exit(1);
} }
print("\nOpened receiver 0 (handle "); put_u64((uint64_t)h); print(")\n"); print("\nOpened receiver 0\n");
// Clamp the requested tuning to the receiver's advertised limits. // Clamp the requested tuning to the receiver's advertised limits.
if (freqHz < info.freqMin) freqHz = info.freqMin; if (freqHz < info.freq_min) freqHz = info.freq_min;
if (freqHz > info.freqMax) freqHz = info.freqMax; if (freqHz > info.freq_max) freqHz = info.freq_max;
if (rateHz < info.sampleRateMin) rateHz = info.sampleRateMin; if (rateHz < info.sample_rate_min) rateHz = info.sample_rate_min;
if (rateHz > info.sampleRateMax) rateHz = info.sampleRateMax; if (rateHz > info.sample_rate_max) rateHz = info.sample_rate_max;
print("Configuring: "); print("Configuring: ");
if (montauk::sdr_set_sample_rate(h, rateHz) != 0) if (g_rtl.set_rate(dev, rateHz) != 0)
print("\n warning: sample rate rejected"); print("\n warning: sample rate rejected");
if (montauk::sdr_set_freq_correction(h, 0) != 0) { /* optional */ } if (g_rtl.set_ppm(dev, 0) != 0) { /* optional */ }
if (montauk::sdr_set_gain_mode(h, 0) != 0) // 0 = auto/AGC if (g_rtl.set_gain_mode(dev, 0) != 0) // 0 = auto/AGC
print("\n warning: gain mode rejected"); print("\n warning: gain mode rejected");
if (montauk::sdr_set_freq(h, freqHz) != 0) if (g_rtl.set_freq(dev, freqHz) != 0)
print("\n warning: tune rejected (PLL may be unlocked)"); print("\n warning: tune rejected (PLL may be unlocked)");
print("\n center : "); put_mhz(montauk::sdr_get_freq(h)); print("\n"); print("\n center : "); put_mhz(g_rtl.get_freq(dev)); print("\n");
print(" rate : "); put_msps(montauk::sdr_get_sample_rate(h)); print(" rate : "); put_msps(g_rtl.get_rate(dev));
print(" ("); put_u64(montauk::sdr_get_sample_rate(h)); print(" Hz)\n"); print(" ("); put_u64(g_rtl.get_rate(dev)); print(" Hz)\n");
print(" gain : auto (AGC)\n"); print(" gain : auto (AGC)\n");
// --- Stream ---------------------------------------------------------- // --- Stream ----------------------------------------------------------
if (montauk::sdr_start(h) != 0) { if (g_rtl.start(dev) != 0) {
print("\nsdr: failed to start streaming\n"); print("\nsdr: failed to start streaming\n");
montauk::sdr_close(h); g_rtl.close(dev);
montauk::exit(1); montauk::exit(1);
} }
print("\nStreaming I/Q for ~2s ...\n"); print("\nStreaming I/Q for ~2s ...\n");
@@ -203,7 +248,7 @@ extern "C" void _start() {
uint64_t start = montauk::get_milliseconds(); uint64_t start = montauk::get_milliseconds();
uint64_t lastReport = start; uint64_t lastReport = start;
while (montauk::get_milliseconds() - start < 2000) { while (montauk::get_milliseconds() - start < 2000) {
int n = montauk::sdr_read(h, g_iq, sizeof(g_iq)); int n = g_rtl.read(dev, g_iq, sizeof(g_iq));
if (n <= 0) { montauk::sleep_ms(20); continue; } if (n <= 0) { montauk::sleep_ms(20); continue; }
// CU8: interleaved 8-bit unsigned I/Q, 127.5 == zero. // CU8: interleaved 8-bit unsigned I/Q, 127.5 == zero.
@@ -227,7 +272,7 @@ extern "C" void _start() {
} }
} }
montauk::sdr_stop(h); g_rtl.stop(dev);
// --- Report ---------------------------------------------------------- // --- Report ----------------------------------------------------------
uint64_t elapsed = montauk::get_milliseconds() - start; uint64_t elapsed = montauk::get_milliseconds() - start;
@@ -254,7 +299,7 @@ extern "C" void _start() {
print(" peak |sample|: "); put_u64((uint64_t)peak); print(" / 128\n"); print(" peak |sample|: "); put_u64((uint64_t)peak); print(" / 128\n");
} }
montauk::sdr_close(h); g_rtl.close(dev);
print("\nClosed receiver. Done.\n"); print("\nClosed receiver. Done.\n");
montauk::exit(0); montauk::exit(0);
} }
+3 -2
View File
@@ -18,6 +18,7 @@ endif
PROG_INC := ../../include PROG_INC := ../../include
LINK_LD := ../../link.ld LINK_LD := ../../link.ld
BINDIR := ../../bin BINDIR := ../../bin
LIBDIR := ../../lib
OBJDIR := obj OBJDIR := obj
# ---- C++ compiler flags ---- # ---- C++ compiler flags ----
@@ -63,9 +64,9 @@ TARGET := $(BINDIR)/os/shell.elf
all: $(TARGET) all: $(TARGET)
$(TARGET): $(OBJS) $(LINK_LD) Makefile $(TARGET): $(OBJS) $(LINK_LD) Makefile $(LIBDIR)/libc/liblibc.a
mkdir -p $(BINDIR)/os mkdir -p $(BINDIR)/os
$(CXX) $(CXXFLAGS) $(LDFLAGS) $(OBJS) -o $@ $(CXX) $(CXXFLAGS) $(LDFLAGS) $(OBJS) $(LIBDIR)/libc/liblibc.a -o $@
$(OBJDIR)/%.o: %.cpp shell.h Makefile $(OBJDIR)/%.o: %.cpp shell.h Makefile
mkdir -p $(OBJDIR) mkdir -p $(OBJDIR)
+3 -2
View File
@@ -66,8 +66,9 @@ void cmd_help() {
montauk::print(" uptime Show uptime\n"); montauk::print(" uptime Show uptime\n");
montauk::print(" clear Clear the screen\n"); montauk::print(" clear Clear the screen\n");
montauk::print(" fontscale [n] Set terminal font scale (1-8)\n"); montauk::print(" fontscale [n] Set terminal font scale (1-8)\n");
montauk::print(" reset Reboot the system\n"); montauk::print(" shutdown Shut down the system (poweroff, halt)\n");
montauk::print(" shutdown Shut down the system\n"); montauk::print(" reboot Reboot the system (reset)\n");
montauk::print(" suspend Suspend the system\n");
montauk::print("\n"); montauk::print("\n");
montauk::print("Network commands:\n"); montauk::print("Network commands:\n");
montauk::print(" ping <ip> Send ICMP echo requests\n"); montauk::print(" ping <ip> Send ICMP echo requests\n");
+11 -1
View File
@@ -41,7 +41,17 @@ static void print_exit_code(int code) {
static bool try_exec(const char* path, const char* args) { static bool try_exec(const char* path, const char* args) {
if (!file_exists(path)) return false; if (!file_exists(path)) return false;
int pid = montauk::spawn(path, args); // Least-privilege hygiene, NOT a security boundary: this never decides
// what may run, only which capabilities a tool receives. The user in an
// admin console can already do everything these tools do, so the point is
// to keep a bug in one tool from reaching authority it has no use for.
// The shell delegates on the same terms as the desktop; the table decides
// per binary, and the kernel re-checks the bound on every spawn.
montauk::abi::SpawnCapabilities caps =
montauk::caps::for_binary(path, shell_delegable_caps);
int pid = (caps.permitted != 0)
? montauk::spawn_with_caps(path, args, nullptr, caps)
: montauk::spawn(path, args);
if (pid < 0) return false; if (pid < 0) return false;
print_exit_code(montauk::waitpid(pid)); print_exit_code(montauk::waitpid(pid));
return true; return true;
+26
View File
@@ -13,6 +13,7 @@ int current_drive = 0;
int last_exit = 0; int last_exit = 0;
char session_user[32] = ""; char session_user[32] = "";
char session_home[64] = ""; char session_home[64] = "";
uint64_t shell_delegable_caps = 0;
void sync_cwd() { void sync_cwd() {
char abs[128]; char abs[128];
@@ -46,6 +47,8 @@ void read_session() {
scopy(session_home, "0:/users/", sizeof(session_home)); scopy(session_home, "0:/users/", sizeof(session_home));
scat(session_home, session_user, sizeof(session_home)); scat(session_home, session_user, sizeof(session_home));
shell_delegable_caps = montauk::caps::self_delegable();
} }
// ---- Command history ---- // ---- Command history ----
@@ -184,6 +187,29 @@ static int process_command(const char* line) {
if (streq(cmd, "true")) { return 0; } if (streq(cmd, "true")) { return 0; }
if (streq(cmd, "false")) { return 1; } if (streq(cmd, "false")) { return 1; }
// Route interactive power actions through the trusted login supervisor.
// Posting the request and exiting lets terminal.elf close, after which
// login performs filesystem/Bluetooth cleanup and the final power syscall.
if (streq(cmd, "shutdown") || streq(cmd, "poweroff") || streq(cmd, "halt") ||
streq(cmd, "reboot") || streq(cmd, "reset")) {
int action = (streq(cmd, "reboot") || streq(cmd, "reset"))
? montauk::abi::POWER_REQ_REBOOT
: montauk::abi::POWER_REQ_SHUTDOWN;
int rc = montauk::power_request(action);
if (rc == 0) montauk::exit(0);
montauk::print(rc == montauk::abi::SYS_ERR_PERMISSION
? "power: permission denied\n"
: "power: request failed\n");
return 1;
}
if (streq(cmd, "suspend")) {
int rc = montauk::suspend();
if (rc == montauk::abi::SYS_ERR_PERMISSION)
montauk::print("suspend: permission denied\n");
return rc == 0 ? 0 : 1;
}
if (streq(cmd, "pwd")) { if (streq(cmd, "pwd")) {
sync_cwd(); sync_cwd();
char path[128]; char path[128];
+10 -2
View File
@@ -10,6 +10,7 @@
#include <montauk/string.h> #include <montauk/string.h>
#include <montauk/heap.h> #include <montauk/heap.h>
#include <montauk/config.h> #include <montauk/config.h>
#include <montauk/capabilities.h>
using montauk::slen; using montauk::slen;
using montauk::streq; using montauk::streq;
@@ -55,6 +56,7 @@ extern int current_drive;
extern int last_exit; extern int last_exit;
extern char session_user[32]; extern char session_user[32];
extern char session_home[64]; extern char session_home[64];
extern uint64_t shell_delegable_caps;
// ---- Inline path helpers ---- // ---- Inline path helpers ----
@@ -143,5 +145,11 @@ constexpr const char* shell_builtins[] = {
"unset", "unset",
"true", "true",
"false", "false",
"exit" "exit",
}; "shutdown",
"poweroff",
"halt",
"reboot",
"reset",
"suspend"
};
-15
View File
@@ -1,15 +0,0 @@
/*
* main.cpp
* shutdown - Shut down the system
* Copyright (c) 2025-2026 Daniel Hammer
*/
#include <montauk/syscall.h>
extern "C" void _start() {
montauk::print("Shutting down...\n");
// This low-level utility bypasses the login graceful-shutdown view, so flush
// pending writes and unmount disk-backed volumes here before powering off.
montauk::fs_sync();
montauk::shutdown();
}
+12 -11
View File
@@ -5,6 +5,7 @@
#include "crypto.hpp" #include "crypto.hpp"
#include <montauk/service_log.h>
#include <montauk/ssh.h> #include <montauk/ssh.h>
#include <libc/stdio.h> #include <libc/stdio.h>
@@ -1287,17 +1288,17 @@ static void handle(int fd) {
connection.set_timeout(PREAUTH_TIMEOUT_MS); connection.set_timeout(PREAUTH_TIMEOUT_MS);
if (!version(connection)) { if (!version(connection)) {
montauk::print("sshd: client version exchange failed\n"); montauk::service_log("sshd: client version exchange failed");
} else if (!kex(connection)) { } else if (!kex(connection)) {
char message[64]; char message[64];
snprintf(message, sizeof(message), snprintf(message, sizeof(message),
"sshd: key exchange failed (%d)\n", kex_error); "sshd: key exchange failed (%d)", kex_error);
montauk::print(message); montauk::service_log(message);
} else if (!auth(connection, user)) { } else if (!auth(connection, user)) {
char message[64]; char message[64];
snprintf(message, sizeof(message), snprintf(message, sizeof(message),
"sshd: authentication failed (%d)\n", kex_error); "sshd: authentication failed (%d)", kex_error);
montauk::print(message); montauk::service_log(message);
} else { } else {
/* An authenticated session is allowed to sit idle. */ /* An authenticated session is allowed to sit idle. */
connection.clear_timeout(); connection.clear_timeout();
@@ -1309,14 +1310,14 @@ static void handle(int fd) {
extern "C" void _start() { extern "C" void _start() {
if (!rng.init()) { if (!rng.init()) {
montauk::print("sshd: secure random initialization failed\n"); montauk::service_log("sshd: secure random initialization failed");
montauk::exit(1); montauk::exit(1);
} }
if (!hostkey.load()) { if (!hostkey.load()) {
montauk::print("sshd: generating local RSA host key...\n"); montauk::service_log("sshd: generating local RSA host key...");
if (!hostkey.generate(rng)) { if (!hostkey.generate(rng)) {
montauk::print("sshd: host key generation failed\n"); montauk::service_log("sshd: host key generation failed");
montauk::exit(1); montauk::exit(1);
} }
} }
@@ -1329,13 +1330,13 @@ extern "C" void _start() {
int listener = montauk::socket(montauk::abi::SOCK_TCP); int listener = montauk::socket(montauk::abi::SOCK_TCP);
if (listener < 0 || montauk::bind(listener, (uint16_t)port) < 0 || if (listener < 0 || montauk::bind(listener, (uint16_t)port) < 0 ||
montauk::listen(listener) < 0) { montauk::listen(listener) < 0) {
montauk::print("sshd: could not listen\n"); montauk::service_log("sshd: could not listen");
montauk::exit(1); montauk::exit(1);
} }
char message[80]; char message[80];
snprintf(message, sizeof(message), "sshd: listening on port %d\n", port); snprintf(message, sizeof(message), "sshd: listening on port %d", port);
montauk::print(message); montauk::service_log(message);
for (;;) { for (;;) {
int fd = montauk::accept(listener); int fd = montauk::accept(listener);
+1 -1
View File
@@ -41,7 +41,7 @@ LDFLAGS := \
-Wl,--gc-sections \ -Wl,--gc-sections \
-T $(LINK_LD) -T $(LINK_LD)
SRCS := main.cpp stb_truetype_impl.cpp font_data.cpp SRCS := main.cpp settings.cpp stb_truetype_impl.cpp font_data.cpp
OBJS := $(addprefix $(OBJDIR)/,$(SRCS:.cpp=.o)) OBJS := $(addprefix $(OBJDIR)/,$(SRCS:.cpp=.o))
DEPS := $(OBJS:.o=.d) DEPS := $(OBJS:.o=.d)
+174 -31
View File
@@ -1,7 +1,6 @@
/* /*
* main.cpp * main.cpp
* MontaukOS Terminal - standalone Window Server app * MontaukOS Terminal - standalone Window Server app
* Preserves the old desktop-integrated terminal layout and tab behavior.
* *
* Two run modes: * Two run modes:
* - Windowed (default): runs as a Window Server client inside the desktop. * - Windowed (default): runs as a Window Server client inside the desktop.
@@ -21,8 +20,11 @@
#include <gui/framebuffer.hpp> #include <gui/framebuffer.hpp>
#include <gui/standalone.hpp> #include <gui/standalone.hpp>
#include <gui/terminal.hpp> #include <gui/terminal.hpp>
#include <gui/mtk/theme.hpp>
#include <gui/truetype.hpp> #include <gui/truetype.hpp>
#include "settings.hpp"
extern "C" { extern "C" {
#include <stdio.h> #include <stdio.h>
} }
@@ -39,6 +41,8 @@ static constexpr int TERM_TAB_GAP = 4;
static constexpr int TERM_PLUS_W = 28; static constexpr int TERM_PLUS_W = 28;
static constexpr int TERM_PLUS_PAD = 8; static constexpr int TERM_PLUS_PAD = 8;
static constexpr int TERM_TAB_PAD = 8; static constexpr int TERM_TAB_PAD = 8;
static constexpr int TERM_COG_W = 28;
static constexpr int TERM_COG_GAP = 6;
struct TermTabs { struct TermTabs {
TerminalState* tabs[TERM_MAX_TABS]; TerminalState* tabs[TERM_MAX_TABS];
@@ -55,6 +59,9 @@ static bool g_force_redraw = true;
static int g_last_win_w = 0; static int g_last_win_w = 0;
static int g_last_win_h = 0; static int g_last_win_h = 0;
static bool g_show_clock = false; // console (full-screen) mode draws a live clock static bool g_show_clock = false; // console (full-screen) mode draws a live clock
// The settings panel is a Window Server window, so it only exists in windowed
// mode; the console session has no window server to open it in.
static bool g_show_cog = false;
static char g_clock_text[16] = {}; static char g_clock_text[16] = {};
static montauk::abi::ProcInfo g_kill_procs[256]; static montauk::abi::ProcInfo g_kill_procs[256];
static int g_kill_pids[256]; static int g_kill_pids[256];
@@ -180,13 +187,59 @@ static bool term_poll_tabs() {
return changed || g_force_redraw; return changed || g_force_redraw;
} }
// ==== Tab bar colors ====
//
// The tab bar is derived from the active terminal palette rather than
// hardcoded, so a light theme gets a light chrome with dark labels instead of
// the dark-theme strip the terminal originally shipped.
struct TabBarColors {
Color bar_bg;
Color tab_active_bg;
Color tab_inactive_bg;
Color label_active;
Color label_inactive;
Color close_active;
Color close_inactive;
Color furniture;
Color clock;
};
// Chrome shades are cut from the terminal background. A dark background can
// absorb a heavy darkening (the amounts below reproduce the original #1C1C1C
// bar and #262626 tabs on the default palette); a light one only tolerates a
// gentle one before the chrome reads as a different, darker window.
static Color term_shade(Color bg, uint8_t dark_amount, uint8_t light_amount) {
int lum = (bg.r * 30 + bg.g * 59 + bg.b * 11) / 100;
return mtk::darken(bg, lum > 140 ? light_amount : dark_amount);
}
static TabBarColors term_bar_colors() {
Color bg = g_term_palette.bg;
Color fg = g_term_palette.fg;
TabBarColors t;
t.bar_bg = term_shade(bg, 96, 30);
t.tab_active_bg = bg;
t.tab_inactive_bg = term_shade(bg, 40, 12);
t.label_active = fg;
t.label_inactive = mtk::mix(bg, fg, 130);
t.close_active = mtk::mix(bg, fg, 110);
t.close_inactive = mtk::mix(bg, fg, 50);
t.furniture = t.label_inactive;
t.clock = mtk::mix(bg, fg, 175);
return t;
}
// Right-hand tab-bar furniture. In console mode a live clock sits at the far // Right-hand tab-bar furniture. In console mode a live clock sits at the far
// right and the new-tab (+) button shifts left to make room; in windowed mode // right and the new-tab (+) button shifts left to make room; in windowed mode
// there is no clock and the + keeps its original far-right position. // there is no clock and the + keeps its original far-right position.
struct TabBarRight { struct TabBarRight {
int clock_x; int clock_x;
int plus_x; int plus_x;
int cog_x;
bool has_clock; bool has_clock;
bool has_cog;
}; };
static TabBarRight term_tabbar_right(int width) { static TabBarRight term_tabbar_right(int width) {
@@ -200,9 +253,65 @@ static TabBarRight term_tabbar_right(int width) {
right_limit = r.clock_x - 12; right_limit = r.clock_x - 12;
} }
r.plus_x = right_limit - TERM_PLUS_W; r.plus_x = right_limit - TERM_PLUS_W;
// The cog keeps its slot even when the tab limit hides the + button, so it
// does not jump under the pointer as tabs are opened and closed.
r.has_cog = g_show_cog;
r.cog_x = r.plus_x - TERM_COG_GAP - TERM_COG_W;
return r; return r;
} }
static Rect term_cog_rect(int width) {
TabBarRight right = term_tabbar_right(width);
return {right.cog_x, 7, TERM_COG_W, 22};
}
// Flat cog glyph, drawn rather than loaded -- the icon set ships no gear.
//
// The shape is analytic: a disc with the hub bored out, plus eight teeth formed
// by intersecting an outer ring with four symmetric bands (two axis-aligned,
// two diagonal). Coverage is sampled 3x3 per pixel and blended against the
// button fill, so the curves stay smooth at this size without an alpha buffer.
// Units are sixths of a pixel, which keeps pixel centers and subsample offsets
// exact -- this app does no floating point.
static void term_draw_cog(Canvas& c, const Rect& box, Color fg, Color bg) {
static constexpr int R_HUB = 15; // 2.5 px
static constexpr int R_BODY = 33; // 5.5 px
static constexpr int R_OUT = 48; // 8.0 px
static constexpr int TOOTH = 10; // 1.7 px half-width
static constexpr int TOOTH_DIAG = 14; // TOOTH * sqrt(2)
auto inked = [](int dx, int dy) -> bool {
int d2 = dx * dx + dy * dy;
if (d2 <= R_HUB * R_HUB) return false;
if (d2 <= R_BODY * R_BODY) return true;
if (d2 > R_OUT * R_OUT) return false;
int ax = dx < 0 ? -dx : dx;
int ay = dy < 0 ? -dy : dy;
int au = (dx + dy) < 0 ? -(dx + dy) : (dx + dy);
int av = (dx - dy) < 0 ? -(dx - dy) : (dx - dy);
return ax <= TOOTH || ay <= TOOTH ||
au <= TOOTH_DIAG || av <= TOOTH_DIAG;
};
for (int y = 0; y < box.h; y++) {
for (int x = 0; x < box.w; x++) {
int cov = 0;
for (int sy = -2; sy <= 2; sy += 2) {
for (int sx = -2; sx <= 2; sx += 2) {
if (inked(6 * x + 3 + sx - 3 * box.w,
6 * y + 3 + sy - 3 * box.h))
cov++;
}
}
if (cov == 0) continue;
Color px = (cov == 9) ? fg
: mtk::mix(bg, fg, (uint8_t)((cov * 255) / 9));
c.put_pixel(box.x + x, box.y + y, px);
}
}
}
// Refresh the cached clock string from the wall clock; returns true when the // Refresh the cached clock string from the wall clock; returns true when the
// displayed text changed (so the caller can request a repaint). // displayed text changed (so the caller can request a repaint).
static bool term_refresh_clock() { static bool term_refresh_clock() {
@@ -263,8 +372,8 @@ static bool term_render_into(uint32_t* pixels, int width, int height) {
if (!g_force_redraw && !ts->dirty) return false; if (!g_force_redraw && !ts->dirty) return false;
Canvas c(pixels, width, height); Canvas c(pixels, width, height);
Color bar_bg = Color::from_hex(0x1C1C1C); TabBarColors bar = term_bar_colors();
c.fill_rect(0, 0, width, TERM_TAB_BAR_H, bar_bg); c.fill_rect(0, 0, width, TERM_TAB_BAR_H, bar.bar_bg);
int fh = system_font_height(); int fh = system_font_height();
int tab_x = TERM_TAB_PAD; int tab_x = TERM_TAB_PAD;
@@ -277,20 +386,20 @@ static bool term_render_into(uint32_t* pixels, int width, int height) {
if (active) { if (active) {
int ty = 5; int ty = 5;
int th = TERM_TAB_BAR_H - ty; int th = TERM_TAB_BAR_H - ty;
c.fill_rounded_rect(tab_x, ty, TERM_TAB_W, th, 6, colors::TERM_BG); c.fill_rounded_rect(tab_x, ty, TERM_TAB_W, th, 6, bar.tab_active_bg);
c.fill_rect(tab_x, TERM_TAB_BAR_H - 6, TERM_TAB_W, 6, colors::TERM_BG); c.fill_rect(tab_x, TERM_TAB_BAR_H - 6, TERM_TAB_W, 6, bar.tab_active_bg);
int text_y = ty + (th - fh) / 2; int text_y = ty + (th - fh) / 2;
c.text(tab_x + 12, text_y, label, Color::from_hex(0xE0E0E0)); c.text(tab_x + 12, text_y, label, bar.label_active);
c.text(tab_x + TERM_TAB_W - 20, text_y, "x", Color::from_hex(0x707070)); c.text(tab_x + TERM_TAB_W - 20, text_y, "x", bar.close_active);
} else { } else {
int ty = 7; int ty = 7;
int th = 22; int th = 22;
c.fill_rounded_rect(tab_x, ty, TERM_TAB_W, th, 5, Color::from_hex(0x262626)); c.fill_rounded_rect(tab_x, ty, TERM_TAB_W, th, 5, bar.tab_inactive_bg);
int text_y = ty + (th - fh) / 2; int text_y = ty + (th - fh) / 2;
c.text(tab_x + 12, text_y, label, Color::from_hex(0x6E6E6E)); c.text(tab_x + 12, text_y, label, bar.label_inactive);
c.text(tab_x + TERM_TAB_W - 20, text_y, "x", Color::from_hex(0x444444)); c.text(tab_x + TERM_TAB_W - 20, text_y, "x", bar.close_inactive);
} }
tab_x += TERM_TAB_W + TERM_TAB_GAP; tab_x += TERM_TAB_W + TERM_TAB_GAP;
@@ -300,17 +409,23 @@ static bool term_render_into(uint32_t* pixels, int width, int height) {
if (right.has_clock) { if (right.has_clock) {
int text_y = (TERM_TAB_BAR_H - fh) / 2; int text_y = (TERM_TAB_BAR_H - fh) / 2;
c.text(right.clock_x, text_y, g_clock_text, Color::from_hex(0x9A9A9A)); c.text(right.clock_x, text_y, g_clock_text, bar.clock);
} }
if (g_tabs.tab_count < TERM_MAX_TABS) { if (g_tabs.tab_count < TERM_MAX_TABS) {
int plus_h = 22; int plus_h = 22;
int py = 7; int py = 7;
int px = right.plus_x; int px = right.plus_x;
c.fill_rounded_rect(px, py, TERM_PLUS_W, plus_h, 5, Color::from_hex(0x262626)); c.fill_rounded_rect(px, py, TERM_PLUS_W, plus_h, 5, bar.tab_inactive_bg);
int pw_text = text_width(fonts::system_font, "+", fonts::UI_SIZE); int pw_text = text_width(fonts::system_font, "+", fonts::UI_SIZE);
c.text(px + (TERM_PLUS_W - pw_text) / 2, py + (plus_h - fh) / 2, "+", c.text(px + (TERM_PLUS_W - pw_text) / 2, py + (plus_h - fh) / 2, "+",
Color::from_hex(0x6E6E6E)); bar.furniture);
}
if (right.has_cog) {
Rect cog = term_cog_rect(width);
c.fill_rounded_rect(cog.x, cog.y, cog.w, cog.h, 5, bar.tab_inactive_bg);
term_draw_cog(c, cog, bar.furniture, bar.tab_inactive_bg);
} }
uint32_t* term_pixels = pixels + TERM_TAB_BAR_H * width; uint32_t* term_pixels = pixels + TERM_TAB_BAR_H * width;
@@ -376,6 +491,11 @@ static void term_handle_mouse_core(int mx, int my, int scroll, bool left_click,
tab_x += TERM_TAB_W + TERM_TAB_GAP; tab_x += TERM_TAB_W + TERM_TAB_GAP;
} }
if (g_show_cog && term_cog_rect(width).contains(mx, my)) {
termset::open();
return;
}
int px = term_tabbar_right(width).plus_x; int px = term_tabbar_right(width).plus_x;
if (g_tabs.tab_count < TERM_MAX_TABS && if (g_tabs.tab_count < TERM_MAX_TABS &&
mx >= px && mx < px + TERM_PLUS_W) { mx >= px && mx < px + TERM_PLUS_W) {
@@ -421,25 +541,15 @@ static void term_handle_key_core(const montauk::abi::KeyEvent& key,
return; return;
} }
// Ctrl+Plus/Equal: zoom in // Ctrl+Plus/Equal and Ctrl+Minus zoom. Both go through the settings panel so
// the keyboard shortcut and the panel agree on bounds and both persist.
if (key.ctrl && key.pressed && (key.ascii == '+' || key.ascii == '=')) { if (key.ctrl && key.pressed && (key.ascii == '+' || key.ascii == '=')) {
if (fonts::TERM_SIZE < 64) { termset::zoom_font(1);
fonts::TERM_SIZE += 2;
for (int i = 0; i < g_tabs.tab_count; i++)
g_tabs.tabs[i]->dirty = true;
term_request_redraw();
}
return; return;
} }
// Ctrl+Minus: zoom out
if (key.ctrl && key.pressed && key.ascii == '-') { if (key.ctrl && key.pressed && key.ascii == '-') {
if (fonts::TERM_SIZE > 8) { termset::zoom_font(-1);
fonts::TERM_SIZE -= 2;
for (int i = 0; i < g_tabs.tab_count; i++)
g_tabs.tabs[i]->dirty = true;
term_request_redraw();
}
return; return;
} }
@@ -450,6 +560,26 @@ static void term_handle_key(const montauk::abi::KeyEvent& key) {
term_handle_key_core(key, g_win.width, g_win.height); term_handle_key_core(key, g_win.width, g_win.height);
} }
// ==== Settings panel plumbing ====
// Cells carry resolved colors, so a theme switch has to rewrite every tab's
// grid -- including scrollback -- from the outgoing palette to the new one.
static void term_on_theme_changed(const TermPalette& from, const TermPalette& to) {
for (int i = 0; i < g_tabs.tab_count; i++)
terminal_remap_palette(g_tabs.tabs[i], from, to);
term_request_redraw();
}
// A font size change alters the cell metrics; term_render_into reflows the
// grid to the new cell count on the next frame.
static void term_on_font_changed() {
for (int i = 0; i < g_tabs.tab_count; i++) {
terminal_invalidate_render_cache(g_tabs.tabs[i]);
g_tabs.tabs[i]->dirty = true;
}
term_request_redraw();
}
static void term_cleanup() { static void term_cleanup() {
for (int i = 0; i < g_tabs.tab_count; i++) for (int i = 0; i < g_tabs.tab_count; i++)
term_free_tab(g_tabs.tabs[i]); term_free_tab(g_tabs.tabs[i]);
@@ -459,6 +589,8 @@ static void term_cleanup() {
// ==== Windowed mode (Window Server client) ==== // ==== Windowed mode (Window Server client) ====
static void run_windowed() { static void run_windowed() {
g_show_cog = true;
if (!g_win.create("Terminal", INIT_W, INIT_H)) if (!g_win.create("Terminal", INIT_W, INIT_H))
montauk::exit(1); montauk::exit(1);
@@ -468,8 +600,8 @@ static void run_windowed() {
// and the first TrueType render. Paint the dark background and present // and the first TrueType render. Paint the dark background and present
// immediately so the compositor sees terminal colors from frame one. // immediately so the compositor sees terminal colors from frame one.
{ {
uint32_t bar_px = Color::from_hex(0x1C1C1C).to_pixel(); uint32_t bar_px = term_bar_colors().bar_bg.to_pixel();
uint32_t bg_px = colors::TERM_BG.to_pixel(); uint32_t bg_px = g_term_palette.bg.to_pixel();
int bar_pixels = TERM_TAB_BAR_H * g_win.width; int bar_pixels = TERM_TAB_BAR_H * g_win.width;
if (bar_pixels > g_win.width * g_win.height) if (bar_pixels > g_win.width * g_win.height)
bar_pixels = g_win.width * g_win.height; bar_pixels = g_win.width * g_win.height;
@@ -508,6 +640,8 @@ static void run_windowed() {
bool quit = false; bool quit = false;
int r = 0; int r = 0;
termset::poll();
while ((r = g_win.poll(&ev)) > 0) { while ((r = g_win.poll(&ev)) > 0) {
redraw = true; redraw = true;
@@ -544,6 +678,7 @@ static void run_windowed() {
montauk::sleep_ms(16); montauk::sleep_ms(16);
} }
termset::close();
term_cleanup(); term_cleanup();
g_win.destroy(); g_win.destroy();
} }
@@ -577,8 +712,8 @@ static void run_console() {
// Paint the dark background immediately so the first frame is not a flash // Paint the dark background immediately so the first frame is not a flash
// of uninitialized memory while the shell spawns and the first render runs. // of uninitialized memory while the shell spawns and the first render runs.
{ {
uint32_t bar_px = Color::from_hex(0x1C1C1C).to_pixel(); uint32_t bar_px = term_bar_colors().bar_bg.to_pixel();
uint32_t bg_px = colors::TERM_BG.to_pixel(); uint32_t bg_px = g_term_palette.bg.to_pixel();
int bar_pixels = TERM_TAB_BAR_H * sw; int bar_pixels = TERM_TAB_BAR_H * sw;
int total = sw * sh; int total = sw * sh;
if (bar_pixels > total) bar_pixels = total; if (bar_pixels > total) bar_pixels = total;
@@ -702,6 +837,14 @@ extern "C" void _start() {
if (workdir && workdir[0]) if (workdir && workdir[0])
montauk::chdir(workdir); montauk::chdir(workdir);
// Must run before the first cell grid is sized: the font size decides the
// mono cell metrics, and so how many columns and rows fit. It also needs the
// run mode, which picks the fallback theme. Console mode has no settings
// window, but it shares the saved appearance and the zoom shortcut, so it
// needs the callbacks too.
termset::init({term_on_theme_changed, term_on_font_changed});
termset::load_prefs(console_mode);
if (console_mode) if (console_mode)
run_console(); run_console();
else else
+565
View File
@@ -0,0 +1,565 @@
/*
* settings.cpp
* MontaukOS Terminal - settings panel
*
* Copyright (c) 2026 Daniel Hammer
*/
#include "settings.hpp"
#include <montauk/config.h>
#include <montauk/heap.h>
#include <montauk/string.h>
#include <montauk/syscall.h>
#include <gui/gui.hpp>
#include <gui/canvas.hpp>
#include <gui/mtk.hpp>
#include <gui/standalone.hpp>
#include <gui/truetype.hpp>
extern "C" {
#include <stdio.h>
}
using namespace gui;
namespace termset {
// ==== Themes ====
struct ThemeEntry {
const char* id; // stable key written to the config file
const char* name; // shown in the panel
TermPalette palette;
};
// Palette order is the ANSI one: black, red, green, yellow, blue, magenta,
// cyan, white, then the eight bright variants.
static const ThemeEntry kThemes[] = {
{
"montauk-dark", "Montauk Dark",
TERM_PALETTE_DEFAULT
},
{
"pure-black", "Pure Black",
{
Color::from_hex(0x000000), Color::from_hex(0xFFFFFF),
Color::from_hex(0xFFFFFF),
{
Color::from_hex(0x000000), Color::from_hex(0xD00000),
Color::from_hex(0x00C000), Color::from_hex(0xC0C000),
Color::from_hex(0x4060E0), Color::from_hex(0xC000C0),
Color::from_hex(0x00C0C0), Color::from_hex(0xE5E5E5),
Color::from_hex(0x7F7F7F), Color::from_hex(0xFF4040),
Color::from_hex(0x40FF40), Color::from_hex(0xFFFF40),
Color::from_hex(0x6080FF), Color::from_hex(0xFF40FF),
Color::from_hex(0x40FFFF), Color::from_hex(0xFFFFFF),
}
}
},
{
"solarized-dark", "Solarized Dark",
{
Color::from_hex(0x002B36), Color::from_hex(0x839496),
Color::from_hex(0x93A1A1),
{
Color::from_hex(0x073642), Color::from_hex(0xDC322F),
Color::from_hex(0x859900), Color::from_hex(0xB58900),
Color::from_hex(0x268BD2), Color::from_hex(0xD33682),
Color::from_hex(0x2AA198), Color::from_hex(0xEEE8D5),
Color::from_hex(0x586E75), Color::from_hex(0xCB4B16),
Color::from_hex(0x93A1A1), Color::from_hex(0x657B83),
Color::from_hex(0x839496), Color::from_hex(0x6C71C4),
Color::from_hex(0x93A1A1), Color::from_hex(0xFDF6E3),
}
}
},
{
"solarized-light", "Solarized Light",
{
Color::from_hex(0xFDF6E3), Color::from_hex(0x657B83),
Color::from_hex(0x586E75),
{
Color::from_hex(0x073642), Color::from_hex(0xDC322F),
Color::from_hex(0x859900), Color::from_hex(0xB58900),
Color::from_hex(0x268BD2), Color::from_hex(0xD33682),
Color::from_hex(0x2AA198), Color::from_hex(0xEEE8D5),
Color::from_hex(0x586E75), Color::from_hex(0xCB4B16),
Color::from_hex(0x93A1A1), Color::from_hex(0x657B83),
Color::from_hex(0x839496), Color::from_hex(0x6C71C4),
Color::from_hex(0x93A1A1), Color::from_hex(0xFDF6E3),
}
}
},
{
"gruvbox-dark", "Gruvbox Dark",
{
Color::from_hex(0x282828), Color::from_hex(0xEBDBB2),
Color::from_hex(0xEBDBB2),
{
Color::from_hex(0x282828), Color::from_hex(0xCC241D),
Color::from_hex(0x98971A), Color::from_hex(0xD79921),
Color::from_hex(0x458588), Color::from_hex(0xB16286),
Color::from_hex(0x689D6A), Color::from_hex(0xA89984),
Color::from_hex(0x928374), Color::from_hex(0xFB4934),
Color::from_hex(0xB8BB26), Color::from_hex(0xFABD2F),
Color::from_hex(0x83A598), Color::from_hex(0xD3869B),
Color::from_hex(0x8EC07C), Color::from_hex(0xEBDBB2),
}
}
},
{
"nord", "Nord",
{
Color::from_hex(0x2E3440), Color::from_hex(0xD8DEE9),
Color::from_hex(0xD8DEE9),
{
Color::from_hex(0x3B4252), Color::from_hex(0xBF616A),
Color::from_hex(0xA3BE8C), Color::from_hex(0xEBCB8B),
Color::from_hex(0x81A1C1), Color::from_hex(0xB48EAD),
Color::from_hex(0x88C0D0), Color::from_hex(0xE5E9F0),
Color::from_hex(0x4C566A), Color::from_hex(0xD08770),
Color::from_hex(0xB9D4A0), Color::from_hex(0xF0D399),
Color::from_hex(0x8FA8CE), Color::from_hex(0xC3A0BB),
Color::from_hex(0x8FBCBB), Color::from_hex(0xECEFF4),
}
}
},
{
"dracula", "Dracula",
{
Color::from_hex(0x282A36), Color::from_hex(0xF8F8F2),
Color::from_hex(0xF8F8F2),
{
Color::from_hex(0x21222C), Color::from_hex(0xFF5555),
Color::from_hex(0x50FA7B), Color::from_hex(0xF1FA8C),
Color::from_hex(0xBD93F9), Color::from_hex(0xFF79C6),
Color::from_hex(0x8BE9FD), Color::from_hex(0xF8F8F2),
Color::from_hex(0x6272A4), Color::from_hex(0xFF6E6E),
Color::from_hex(0x69FF94), Color::from_hex(0xFFFFA5),
Color::from_hex(0xD6ACFF), Color::from_hex(0xFF92DF),
Color::from_hex(0xA4FFFF), Color::from_hex(0xFFFFFF),
}
}
},
{
"paper-light", "Paper Light",
{
Color::from_hex(0xFFFFFF), Color::from_hex(0x33333A),
Color::from_hex(0x33333A),
{
Color::from_hex(0x2E3436), Color::from_hex(0xC01C28),
Color::from_hex(0x26A269), Color::from_hex(0xA2734C),
Color::from_hex(0x12488B), Color::from_hex(0xA347BA),
Color::from_hex(0x2AA1B3), Color::from_hex(0x8B8E8F),
Color::from_hex(0x5E5C64), Color::from_hex(0xF66151),
Color::from_hex(0x33D17A), Color::from_hex(0xE9AD0C),
Color::from_hex(0x2A7BDE), Color::from_hex(0xC061CB),
Color::from_hex(0x33C7DE), Color::from_hex(0x3D3846),
}
}
},
};
static constexpr int kThemeCount = (int)(sizeof(kThemes) / sizeof(kThemes[0]));
static constexpr int FONT_SIZE_MIN = 8;
static constexpr int FONT_SIZE_DEFAULT = 18;
static constexpr int FONT_SIZE_MAX = 64;
static constexpr int FONT_SIZE_STEP = 2;
// ==== Panel state ====
static constexpr int PANEL_W = 380;
static constexpr int PANEL_H = 452;
static constexpr int PAD = 16;
static constexpr int CARD_PAD = 4;
static constexpr int ROW_H = 34;
static constexpr int ROW_GAP = 2;
static constexpr int STEP_BTN_W = 32;
static constexpr int SWATCH = 12;
static constexpr int SWATCH_GAP = 4;
static constexpr int SWATCH_N = 6;
struct Panel {
int win_id;
uint32_t* pixels;
int width;
int height;
bool open;
int mouse_x;
int mouse_y;
int theme_index;
Callbacks cb;
};
static Panel g_panel = {-1, nullptr, PANEL_W, PANEL_H, false, -1, -1, 0, {nullptr, nullptr}};
// The console session takes over the whole framebuffer with no desktop around
// it, so it defaults to the plain black palette rather than the windowed
// terminal's lighter grey.
static constexpr const char* kConsoleDefaultTheme = "pure-black";
static bool g_console_session = false;
// ==== Preferences ====
static void current_user(char* out, int cap) {
if (montauk::getuser(out, cap) <= 0 || !out[0])
montauk::strcpy(out, "default");
}
static int theme_index_by_id(const char* id) {
for (int i = 0; i < kThemeCount; i++) {
if (montauk::streq(kThemes[i].id, id)) return i;
}
return -1;
}
static int clamp_font_size(int size) {
if (size < FONT_SIZE_MIN) return FONT_SIZE_MIN;
if (size > FONT_SIZE_MAX) return FONT_SIZE_MAX;
return size;
}
static void save_prefs() {
char user[64];
current_user(user, sizeof(user));
montauk::toml::Doc doc = montauk::config::load_user(user, "terminal");
// The console session has no panel, so its theme is a default rather than a
// choice -- writing it back would let a zoom in the console silently retheme
// every windowed terminal. Leaving the key untouched round-trips whatever
// the user actually picked.
if (!g_console_session)
montauk::config::set_string(&doc, "appearance.theme",
kThemes[g_panel.theme_index].id);
montauk::config::set_int(&doc, "appearance.font_size", fonts::TERM_SIZE);
montauk::config::save_user(user, "terminal", &doc);
doc.destroy();
}
void load_prefs(bool console_session) {
g_console_session = console_session;
char user[64];
current_user(user, sizeof(user));
montauk::toml::Doc doc = montauk::config::load_user(user, "terminal");
int idx = theme_index_by_id(doc.get_string("appearance.theme", ""));
int size = (int)doc.get_int("appearance.font_size", fonts::TERM_SIZE);
doc.destroy();
// A saved theme is an explicit choice and wins in either mode; with none
// saved, the full-screen console starts on Pure Black and the windowed
// terminal on the default palette.
if (idx < 0) {
idx = console_session ? theme_index_by_id(kConsoleDefaultTheme) : 0;
if (idx < 0) idx = 0;
}
g_panel.theme_index = idx;
g_term_palette = kThemes[idx].palette;
fonts::TERM_SIZE = clamp_font_size(size);
}
// ==== Applying changes ====
static void select_theme(int idx) {
if (idx < 0 || idx >= kThemeCount || idx == g_panel.theme_index) return;
TermPalette from = g_term_palette;
g_panel.theme_index = idx;
g_term_palette = kThemes[idx].palette;
if (g_panel.cb.theme_changed)
g_panel.cb.theme_changed(from, g_term_palette);
save_prefs();
}
static void step_font_size(int delta) {
int size = clamp_font_size(fonts::TERM_SIZE + delta);
if (size == fonts::TERM_SIZE) return;
fonts::TERM_SIZE = size;
if (g_panel.cb.font_changed)
g_panel.cb.font_changed();
save_prefs();
render();
}
void zoom_font(int direction) {
step_font_size(direction * FONT_SIZE_STEP);
}
// ==== Layout ====
//
// Sections are a muted label over a bordered card, matching the rest of the
// system settings apps. The window titlebar already names the panel, so there
// is deliberately no heading inside it.
struct Layout {
Rect theme_card;
Rect theme_rows[kThemeCount];
Rect font_minus;
Rect font_plus;
Rect font_value;
Rect reset_btn;
Rect close_btn;
int theme_label_y;
int font_label_y;
};
static Layout compute_layout() {
Layout lo = {};
int fh = system_font_height();
int content_w = g_panel.width - PAD * 2;
int y = PAD;
lo.theme_label_y = y;
y += fh + 6;
lo.theme_card = {PAD, y, content_w,
kThemeCount * ROW_H + (kThemeCount - 1) * ROW_GAP + CARD_PAD * 2};
int row_y = y + CARD_PAD;
for (int i = 0; i < kThemeCount; i++) {
lo.theme_rows[i] = {lo.theme_card.x + CARD_PAD, row_y,
content_w - CARD_PAD * 2, ROW_H};
row_y += ROW_H + ROW_GAP;
}
y += lo.theme_card.h + 20;
// Font size is a single setting, so it reads as a plain row -- label left,
// stepper right-aligned to the same content edge as the card above --
// rather than a one-row card.
int ctrl_h = 28;
lo.font_label_y = y + (ctrl_h - fh) / 2;
lo.font_plus = {PAD + content_w - STEP_BTN_W, y, STEP_BTN_W, ctrl_h};
lo.font_value = {lo.font_plus.x - 6 - 56, y, 56, ctrl_h};
lo.font_minus = {lo.font_value.x - 6 - STEP_BTN_W, y, STEP_BTN_W, ctrl_h};
int btn_h = 30;
int btn_y = g_panel.height - PAD - btn_h;
lo.close_btn = {g_panel.width - PAD - 90, btn_y, 90, btn_h};
lo.reset_btn = {PAD, btn_y, 140, btn_h};
return lo;
}
// ==== Rendering ====
// Background, four representative ANSI colors and the foreground, drawn as a
// mini strip so each theme is identifiable without applying it. Every swatch
// gets a hairline border -- without it a white background swatch vanishes into
// the card on the light themes.
static void draw_theme_preview(Canvas& c, const TermPalette& p, int x, int y,
const mtk::Theme& theme) {
Color strip[SWATCH_N] = {
p.bg, p.ansi[1], p.ansi[2], p.ansi[4], p.ansi[5], p.fg
};
for (int i = 0; i < SWATCH_N; i++) {
int sx = x + i * (SWATCH + SWATCH_GAP);
mtk::draw_rounded_frame(c, {sx, y, SWATCH, SWATCH}, 3, strip[i],
mtk::mix(theme.border, strip[i], 80));
}
}
void render() {
if (!g_panel.open || g_panel.win_id < 0 || !g_panel.pixels) return;
Canvas c(g_panel.pixels, g_panel.width, g_panel.height);
mtk::Theme theme = mtk::make_theme();
c.fill(theme.window_bg);
Layout lo = compute_layout();
int fh = system_font_height();
c.text(PAD, lo.theme_label_y, "Theme", theme.text_subtle);
mtk::draw_rounded_frame(c, lo.theme_card, 6, theme.surface_alt, theme.border);
int preview_w = SWATCH_N * (SWATCH + SWATCH_GAP) - SWATCH_GAP;
for (int i = 0; i < kThemeCount; i++) {
const Rect& row = lo.theme_rows[i];
bool selected = (i == g_panel.theme_index);
bool hovered = row.contains(g_panel.mouse_x, g_panel.mouse_y);
if (selected)
mtk::draw_list_row(c, row, true, false, theme);
else if (hovered)
c.fill_rounded_rect(row.x, row.y, row.w, row.h, theme.radius_md,
theme.surface_hover);
c.text(row.x + 10, row.y + (row.h - fh) / 2, kThemes[i].name,
selected ? theme.text_inverse : theme.text);
draw_theme_preview(c, kThemes[i].palette,
row.x + row.w - 10 - preview_w,
row.y + (row.h - SWATCH) / 2, theme);
}
c.text(PAD, lo.font_label_y, "Font size", theme.text);
char value[16];
snprintf(value, sizeof(value), "%d px", fonts::TERM_SIZE);
int vw = text_width(value);
c.text(lo.font_value.x + (lo.font_value.w - vw) / 2,
lo.font_value.y + (lo.font_value.h - fh) / 2, value, theme.text);
bool can_shrink = fonts::TERM_SIZE > FONT_SIZE_MIN;
bool can_grow = fonts::TERM_SIZE < FONT_SIZE_MAX;
mtk::draw_button(c, lo.font_minus, "-", mtk::BUTTON_SECONDARY,
mtk::widget_state(false,
can_shrink && lo.font_minus.contains(
g_panel.mouse_x, g_panel.mouse_y),
can_shrink),
theme);
mtk::draw_button(c, lo.font_plus, "+", mtk::BUTTON_SECONDARY,
mtk::widget_state(false,
can_grow && lo.font_plus.contains(
g_panel.mouse_x, g_panel.mouse_y),
can_grow),
theme);
mtk::draw_button(c, lo.reset_btn, "Reset defaults", mtk::BUTTON_SECONDARY,
mtk::widget_state(false,
lo.reset_btn.contains(g_panel.mouse_x,
g_panel.mouse_y)),
theme);
mtk::draw_button(c, lo.close_btn, "Close", mtk::BUTTON_PRIMARY,
mtk::widget_state(false,
lo.close_btn.contains(g_panel.mouse_x,
g_panel.mouse_y)),
theme);
montauk::win_present(g_panel.win_id);
}
// ==== Input ====
static void reset_defaults() {
select_theme(0);
int target = FONT_SIZE_DEFAULT;
if (target != fonts::TERM_SIZE)
step_font_size(target - fonts::TERM_SIZE);
}
static void handle_mouse(const montauk::abi::WinEvent& ev) {
g_panel.mouse_x = ev.mouse.x;
g_panel.mouse_y = ev.mouse.y;
bool pressed = (ev.mouse.buttons & 1) && !(ev.mouse.prev_buttons & 1);
if (!pressed) return;
Layout lo = compute_layout();
for (int i = 0; i < kThemeCount; i++) {
if (lo.theme_rows[i].contains(g_panel.mouse_x, g_panel.mouse_y)) {
select_theme(i);
return;
}
}
if (lo.font_minus.contains(g_panel.mouse_x, g_panel.mouse_y)) {
step_font_size(-FONT_SIZE_STEP);
} else if (lo.font_plus.contains(g_panel.mouse_x, g_panel.mouse_y)) {
step_font_size(FONT_SIZE_STEP);
} else if (lo.reset_btn.contains(g_panel.mouse_x, g_panel.mouse_y)) {
reset_defaults();
} else if (lo.close_btn.contains(g_panel.mouse_x, g_panel.mouse_y)) {
close();
}
}
static void handle_key(const montauk::abi::KeyEvent& key) {
if (!key.pressed) return;
if (key.scancode == 0x01) { // Escape
close();
return;
}
if (key.ascii == '+' || key.ascii == '=') {
step_font_size(FONT_SIZE_STEP);
} else if (key.ascii == '-') {
step_font_size(-FONT_SIZE_STEP);
} else if (key.scancode == 0x48) { // Up
select_theme(g_panel.theme_index - 1);
} else if (key.scancode == 0x50) { // Down
select_theme(g_panel.theme_index + 1);
}
}
// ==== Lifecycle ====
void init(const Callbacks& cb) {
g_panel.cb = cb;
}
bool is_open() {
return g_panel.open;
}
void open() {
if (g_panel.open) {
render();
return;
}
montauk::abi::WinCreateResult wres;
if (montauk::win_create("Terminal Settings", PANEL_W, PANEL_H, &wres) < 0
|| wres.id < 0)
return;
g_panel.win_id = wres.id;
g_panel.pixels = (uint32_t*)(uintptr_t)wres.pixelVa;
g_panel.width = PANEL_W;
g_panel.height = PANEL_H;
g_panel.mouse_x = -1;
g_panel.mouse_y = -1;
g_panel.open = true;
render();
}
void close() {
if (!g_panel.open) return;
if (g_panel.win_id >= 0)
montauk::win_destroy(g_panel.win_id);
g_panel.win_id = -1;
g_panel.pixels = nullptr;
g_panel.open = false;
}
void poll() {
if (!g_panel.open || g_panel.win_id < 0) return;
bool redraw = false;
montauk::abi::WinEvent ev;
int r;
while ((r = montauk::win_poll(g_panel.win_id, &ev)) > 0) {
if (ev.type == 3) {
close();
return;
}
if (ev.type == 0) {
handle_key(ev.key);
} else if (ev.type == 1) {
handle_mouse(ev);
} else if (ev.type == 2) {
g_panel.width = ev.resize.w;
g_panel.height = ev.resize.h;
g_panel.pixels = (uint32_t*)(uintptr_t)montauk::win_resize(
g_panel.win_id, ev.resize.w, ev.resize.h);
}
redraw = true;
if (!g_panel.open) return;
}
if (r < 0) {
close();
return;
}
if (redraw)
render();
}
} // namespace termset
+48
View File
@@ -0,0 +1,48 @@
/*
* settings.hpp
* MontaukOS Terminal - settings panel
* Copyright (c) 2026 Daniel Hammer
*/
#pragma once
#include <gui/terminal.hpp>
namespace termset {
// The panel owns the appearance preferences; the terminal owns the tabs. These
// callbacks let the panel hand a change back so every tab can be repainted:
// theme_changed also carries the outgoing palette, which the terminal needs to
// translate the colors already baked into its cells.
struct Callbacks {
void (*theme_changed)(const gui::TermPalette& from, const gui::TermPalette& to);
void (*font_changed)();
};
// Load the saved theme and font size and apply them to the globals. Call
// before the first tab is created -- there is nothing to repaint yet, so this
// deliberately does not fire the callbacks. `console_session` selects the
// fallback theme used when nothing is saved yet, and stops the console from
// writing a theme it was only given as a default.
void load_prefs(bool console_session);
void init(const Callbacks& cb);
bool is_open();
// Open (or focus) the settings window.
void open();
void close();
// Pump the settings window's events. No-op when the panel is closed.
void poll();
void render();
// Step the terminal font size by `direction` zoom steps (negative shrinks).
// Shared with the terminal's Ctrl+Plus / Ctrl+Minus shortcut so both routes
// obey the same bounds and both persist.
void zoom_font(int direction);
} // namespace termset
+1 -1
View File
@@ -2,7 +2,7 @@
* main.cpp * main.cpp
* MontaukOS Text Editor - standalone Window Server app * MontaukOS Text Editor - standalone Window Server app
* Single-buffer text editor with line numbers, cursor, scrolling, file I/O, * Single-buffer text editor with line numbers, cursor, scrolling, file I/O,
* syntax highlighting for C and Lua files * syntax highlighting for C, C++ and Lua files
* Copyright (c) 2026 Daniel Hammer * Copyright (c) 2026 Daniel Hammer
*/ */
+176 -17
View File
@@ -1,7 +1,7 @@
/* /*
* syntax_highlight.hpp * syntax_highlight.hpp
* C and Lua syntax highlighting for the MontaukOS text editor * C, C++ and Lua syntax highlighting for the MontaukOS text editor
* Activated for .c, .h, and .lua files * Activated for .c/.h, C++ (.cpp/.hpp/...), and .lua files
* Copyright (c) 2026 Daniel Hammer * Copyright (c) 2026 Daniel Hammer
*/ */
@@ -14,6 +14,7 @@ using namespace gui;
enum SynLanguage : uint8_t { enum SynLanguage : uint8_t {
SYN_LANG_NONE, SYN_LANG_NONE,
SYN_LANG_C, SYN_LANG_C,
SYN_LANG_CPP,
SYN_LANG_LUA, SYN_LANG_LUA,
}; };
@@ -33,10 +34,15 @@ enum SynToken : uint8_t {
SYN_OPERATOR, SYN_OPERATOR,
}; };
#define SYN_RAW_DELIM_MAX 16
struct SynState { struct SynState {
bool in_block_comment; bool in_block_comment;
SynToken long_token; SynToken long_token;
int long_bracket_eqs; int long_bracket_eqs;
bool in_raw_string;
char raw_delim[SYN_RAW_DELIM_MAX];
int raw_delim_len;
}; };
// ============================================================================ // ============================================================================
@@ -153,9 +159,45 @@ inline SynToken syn_classify_lua_word(const char* buf, int len) {
return SYN_NORMAL; return SYN_NORMAL;
} }
inline SynToken syn_classify_cpp_word(const char* buf, int len) {
// C++ keywords (on top of the C set, which is checked first)
static const char* keywords[] = {
"alignas", "alignof", "and", "and_eq", "asm", "bitand", "bitor",
"catch", "class", "compl", "concept", "consteval", "constexpr",
"constinit", "const_cast", "co_await", "co_return", "co_yield",
"decltype", "delete", "dynamic_cast", "explicit", "export",
"final", "friend", "mutable", "namespace", "new", "noexcept",
"not", "not_eq", "operator", "or", "or_eq", "override",
"private", "protected", "public", "reinterpret_cast", "requires",
"static_assert", "static_cast", "template", "this", "thread_local",
"throw", "try", "typeid", "typename", "using", "virtual",
"xor", "xor_eq",
};
// C++ library / builtin types
static const char* types[] = {
"char8_t", "char16_t", "char32_t", "wchar_t", "nullptr_t",
"std", "string", "string_view", "vector", "array", "span",
"map", "set", "unordered_map", "unordered_set", "deque", "list",
"pair", "tuple", "optional", "variant", "function",
"unique_ptr", "shared_ptr", "weak_ptr", "initializer_list",
};
SynToken c = syn_classify_c_word(buf, len);
if (c != SYN_NORMAL) return c;
for (int i = 0; i < (int)(sizeof(keywords) / sizeof(keywords[0])); i++) {
if (syn_streq(buf, len, keywords[i])) return SYN_KEYWORD;
}
for (int i = 0; i < (int)(sizeof(types) / sizeof(types[0])); i++) {
if (syn_streq(buf, len, types[i])) return SYN_TYPE;
}
return SYN_NORMAL;
}
inline SynToken syn_classify_word(SynLanguage lang, const char* buf, int len) { inline SynToken syn_classify_word(SynLanguage lang, const char* buf, int len) {
switch (lang) { switch (lang) {
case SYN_LANG_C: return syn_classify_c_word(buf, len); case SYN_LANG_C: return syn_classify_c_word(buf, len);
case SYN_LANG_CPP: return syn_classify_cpp_word(buf, len);
case SYN_LANG_LUA: return syn_classify_lua_word(buf, len); case SYN_LANG_LUA: return syn_classify_lua_word(buf, len);
default: return SYN_NORMAL; default: return SYN_NORMAL;
} }
@@ -166,6 +208,8 @@ inline SynState syn_make_state() {
state.in_block_comment = false; state.in_block_comment = false;
state.long_token = SYN_NORMAL; state.long_token = SYN_NORMAL;
state.long_bracket_eqs = -1; state.long_bracket_eqs = -1;
state.in_raw_string = false;
state.raw_delim_len = 0;
return state; return state;
} }
@@ -197,38 +241,52 @@ inline bool syn_match_lua_long_bracket_close(const char* line, int len, int i,
return false; return false;
} }
inline void syn_scan_digits(const char* line, int len, int& i, bool sep, bool hex) {
while (i < len) {
if (hex ? syn_is_hex(line[i]) : syn_is_digit(line[i])) { i++; continue; }
// C++14 digit separator: a quote wedged between two digits
if (sep && line[i] == '\'' && i + 1 < len &&
(hex ? syn_is_hex(line[i + 1]) : syn_is_digit(line[i + 1]))) {
i += 2;
continue;
}
break;
}
}
inline void syn_consume_number(const char* line, int len, int& i, inline void syn_consume_number(const char* line, int len, int& i,
SynToken* out, int out_len, bool c_style_suffixes) { SynToken* out, int out_len, bool c_style_suffixes,
bool digit_sep = false) {
int start = i; int start = i;
if (line[i] == '0' && i + 1 < len && (line[i + 1] == 'x' || line[i + 1] == 'X')) { if (line[i] == '0' && i + 1 < len && (line[i + 1] == 'x' || line[i + 1] == 'X')) {
i += 2; i += 2;
while (i < len && syn_is_hex(line[i])) i++; syn_scan_digits(line, len, i, digit_sep, true);
if (i < len && line[i] == '.' && !(i + 1 < len && line[i + 1] == '.')) { if (i < len && line[i] == '.' && !(i + 1 < len && line[i + 1] == '.')) {
i++; i++;
while (i < len && syn_is_hex(line[i])) i++; syn_scan_digits(line, len, i, digit_sep, true);
} }
if (i < len && (line[i] == 'p' || line[i] == 'P')) { if (i < len && (line[i] == 'p' || line[i] == 'P')) {
int exp = i + 1; int exp = i + 1;
if (exp < len && (line[exp] == '+' || line[exp] == '-')) exp++; if (exp < len && (line[exp] == '+' || line[exp] == '-')) exp++;
if (exp < len && syn_is_digit(line[exp])) { if (exp < len && syn_is_digit(line[exp])) {
i = exp + 1; i = exp;
while (i < len && syn_is_digit(line[i])) i++; syn_scan_digits(line, len, i, digit_sep, false);
} }
} }
} else { } else {
if (line[i] == '.') i++; if (line[i] == '.') i++;
while (i < len && syn_is_digit(line[i])) i++; syn_scan_digits(line, len, i, digit_sep, false);
if (i < len && line[i] == '.' && !(i + 1 < len && line[i + 1] == '.')) { if (i < len && line[i] == '.' && !(i + 1 < len && line[i + 1] == '.')) {
i++; i++;
while (i < len && syn_is_digit(line[i])) i++; syn_scan_digits(line, len, i, digit_sep, false);
} }
if (i < len && (line[i] == 'e' || line[i] == 'E')) { if (i < len && (line[i] == 'e' || line[i] == 'E')) {
int exp = i + 1; int exp = i + 1;
if (exp < len && (line[exp] == '+' || line[exp] == '-')) exp++; if (exp < len && (line[exp] == '+' || line[exp] == '-')) exp++;
if (exp < len && syn_is_digit(line[exp])) { if (exp < len && syn_is_digit(line[exp])) {
i = exp + 1; i = exp;
while (i < len && syn_is_digit(line[i])) i++; syn_scan_digits(line, len, i, digit_sep, false);
} }
} }
} }
@@ -236,7 +294,8 @@ inline void syn_consume_number(const char* line, int len, int& i,
if (c_style_suffixes) { if (c_style_suffixes) {
while (i < len && (line[i] == 'u' || line[i] == 'U' || while (i < len && (line[i] == 'u' || line[i] == 'U' ||
line[i] == 'l' || line[i] == 'L' || line[i] == 'l' || line[i] == 'L' ||
line[i] == 'f' || line[i] == 'F')) line[i] == 'f' || line[i] == 'F' ||
line[i] == 'z' || line[i] == 'Z'))
i++; i++;
} }
@@ -255,11 +314,86 @@ inline void syn_consume_number(const char* line, int len, int& i,
// `line` points to the first char of the line, `len` is its length // `line` points to the first char of the line, `len` is its length
// (excluding the newline). `out` can be null if only state tracking is needed. // (excluding the newline). `out` can be null if only state tracking is needed.
// ============================================================================
// C / C++ raw string helpers
// ============================================================================
inline bool syn_is_raw_string_prefix(const char* buf, int len) {
return syn_streq(buf, len, "R") || syn_streq(buf, len, "LR") ||
syn_streq(buf, len, "uR") || syn_streq(buf, len, "UR") ||
syn_streq(buf, len, "u8R");
}
inline bool syn_is_string_prefix(const char* buf, int len) {
return syn_streq(buf, len, "L") || syn_streq(buf, len, "u") ||
syn_streq(buf, len, "U") || syn_streq(buf, len, "u8");
}
// Colors a raw-string body from `i` up to and including the closing )delim".
// If the terminator is not on this line, leaves state.in_raw_string set so the
// next line continues the literal.
inline void syn_consume_raw_body(const char* line, int len, int& i,
SynToken* out, int out_len, SynState& state) {
while (i < len) {
if (line[i] == ')') {
int j = i + 1;
int k = 0;
while (k < state.raw_delim_len && j < len && line[j] == state.raw_delim[k]) {
j++;
k++;
}
if (k == state.raw_delim_len && j < len && line[j] == '"') {
syn_fill_tokens(out, out_len, i, j + 1, SYN_STRING);
i = j + 1;
state.in_raw_string = false;
state.raw_delim_len = 0;
return;
}
}
syn_set_token(out, out_len, i, SYN_STRING);
i++;
}
}
// Enters a raw string; `line[i]` must be the opening quote.
inline void syn_consume_raw_string(const char* line, int len, int& i,
SynToken* out, int out_len, SynState& state) {
syn_set_token(out, out_len, i, SYN_STRING);
i++;
state.raw_delim_len = 0;
while (i < len && line[i] != '(' && state.raw_delim_len < SYN_RAW_DELIM_MAX) {
state.raw_delim[state.raw_delim_len++] = line[i];
syn_set_token(out, out_len, i, SYN_STRING);
i++;
}
if (i < len && line[i] == '(') {
syn_set_token(out, out_len, i, SYN_STRING);
i++;
state.in_raw_string = true;
syn_consume_raw_body(line, len, i, out, out_len, state);
return;
}
// Malformed (or an over-long delimiter): treat the rest of the line as string
syn_fill_tokens(out, out_len, i, len, SYN_STRING);
i = len;
}
// ============================================================================
// C / C++ highlighting
// ============================================================================
inline void syn_highlight_line_c(const char* line, int len, SynToken* out, int out_len, inline void syn_highlight_line_c(const char* line, int len, SynToken* out, int out_len,
SynState& state) { SynState& state, SynLanguage lang = SYN_LANG_C) {
const bool cpp = (lang == SYN_LANG_CPP);
int i = 0; int i = 0;
while (i < len) { while (i < len) {
// ---- Raw string continuation ----
if (state.in_raw_string) {
syn_consume_raw_body(line, len, i, out, out_len, state);
continue;
}
// ---- Block comment continuation ---- // ---- Block comment continuation ----
if (state.in_block_comment) { if (state.in_block_comment) {
while (i < len) { while (i < len) {
@@ -380,15 +514,32 @@ inline void syn_highlight_line_c(const char* line, int len, SynToken* out, int o
// ---- Numbers ---- // ---- Numbers ----
if (syn_is_digit(c) || (c == '.' && i + 1 < len && syn_is_digit(line[i + 1]))) { if (syn_is_digit(c) || (c == '.' && i + 1 < len && syn_is_digit(line[i + 1]))) {
syn_consume_number(line, len, i, out, out_len, true); syn_consume_number(line, len, i, out, out_len, true, cpp);
continue; continue;
} }
// ---- Identifiers / keywords / types ---- // ---- Identifiers / keywords / types / literal prefixes ----
if (syn_is_alpha(c)) { if (syn_is_alpha(c)) {
int start = i; int start = i;
while (i < len && syn_is_alnum(line[i])) i++; while (i < len && syn_is_alnum(line[i])) i++;
SynToken tok = syn_classify_word(SYN_LANG_C, line + start, i - start); if (cpp && i < len && line[i] == '"' &&
syn_is_raw_string_prefix(line + start, i - start)) {
syn_fill_tokens(out, out_len, start, i, SYN_STRING);
syn_consume_raw_string(line, len, i, out, out_len, state);
continue;
}
if (cpp && i < len && line[i] == '"' &&
syn_is_string_prefix(line + start, i - start)) {
// The quote itself is handled on the next iteration
syn_fill_tokens(out, out_len, start, i, SYN_STRING);
continue;
}
if (cpp && i < len && line[i] == '\'' &&
syn_is_string_prefix(line + start, i - start)) {
syn_fill_tokens(out, out_len, start, i, SYN_CHAR);
continue;
}
SynToken tok = syn_classify_word(lang, line + start, i - start);
syn_fill_tokens(out, out_len, start, i, tok); syn_fill_tokens(out, out_len, start, i, tok);
continue; continue;
} }
@@ -508,7 +659,8 @@ inline void syn_highlight_line(const char* line, int len, SynToken* out, int out
switch (lang) { switch (lang) {
case SYN_LANG_C: case SYN_LANG_C:
syn_highlight_line_c(line, len, out, out_len, state); case SYN_LANG_CPP:
syn_highlight_line_c(line, len, out, out_len, state, lang);
break; break;
case SYN_LANG_LUA: case SYN_LANG_LUA:
syn_highlight_line_lua(line, len, out, out_len, state); syn_highlight_line_lua(line, len, out, out_len, state);
@@ -542,6 +694,13 @@ inline bool syn_path_ends_with(const char* path, const char* suffix) {
inline SynLanguage syn_detect_language(const char* filepath) { inline SynLanguage syn_detect_language(const char* filepath) {
if (!filepath || filepath[0] == '\0') return SYN_LANG_NONE; if (!filepath || filepath[0] == '\0') return SYN_LANG_NONE;
if (syn_path_ends_with(filepath, ".cpp") || syn_path_ends_with(filepath, ".cc") ||
syn_path_ends_with(filepath, ".cxx") || syn_path_ends_with(filepath, ".c++") ||
syn_path_ends_with(filepath, ".hpp") || syn_path_ends_with(filepath, ".hh") ||
syn_path_ends_with(filepath, ".hxx") || syn_path_ends_with(filepath, ".h++") ||
syn_path_ends_with(filepath, ".ipp") || syn_path_ends_with(filepath, ".tpp") ||
syn_path_ends_with(filepath, ".inl"))
return SYN_LANG_CPP;
if (syn_path_ends_with(filepath, ".c") || syn_path_ends_with(filepath, ".h")) if (syn_path_ends_with(filepath, ".c") || syn_path_ends_with(filepath, ".h"))
return SYN_LANG_C; return SYN_LANG_C;
if (syn_path_ends_with(filepath, ".lua")) if (syn_path_ends_with(filepath, ".lua"))
+57 -37
View File
@@ -191,16 +191,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;
// CPU power/thermal status // 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
@@ -239,20 +239,27 @@ 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;
/* Generic userspace USB interface access */
static constexpr uint64_t SYS_USB_LIST = 178;
static constexpr uint64_t SYS_USB_CLAIM = 179;
static constexpr uint64_t SYS_USB_CLOSE = 180;
static constexpr uint64_t SYS_USB_CONTROL = 181;
static constexpr uint64_t SYS_USB_BULK_IN_START = 182;
static constexpr uint64_t SYS_USB_BULK_IN_STOP = 183;
static constexpr uint64_t SYS_USB_BULK_IN_READ = 184;
static constexpr uint64_t SYS_LOG_WRITE = 176; // (logMessage) -> 0 static constexpr uint64_t SYS_LOG_WRITE = 176; // (logMessage) -> 0
static constexpr uint64_t SYS_TERMINAL_ATTACHED = 177; // () -> 1 when connected to a userspace terminal static constexpr uint64_t SYS_TERMINAL_ATTACHED = 177; // () -> 1 when connected to a userspace terminal
// Tunable parameters (for SYS_SDR_SETPARAM / SYS_SDR_GETPARAM).
static constexpr int SDR_PARAM_FREQ = 0; // center frequency, Hz
static constexpr int SDR_PARAM_SAMPLE_RATE = 1; // sample rate, Hz
static constexpr int SDR_PARAM_GAIN_MODE = 2; // 0 = auto/AGC, 1 = manual
static constexpr int SDR_PARAM_GAIN = 3; // tuner gain, tenths of dB
static constexpr int SDR_PARAM_FREQ_CORR = 4; // frequency correction, ppm
static constexpr int SDR_PARAM_AGC = 5; // demod digital AGC, 0/1
static constexpr int SDR_PARAM_DIRECT_SAMP = 6; // direct sampling: 0=off,1=I,2=Q
// Sample formats reported in SdrDeviceInfo.sampleFormat. static constexpr int USB_ERR_INVALID = -1;
static constexpr uint8_t SDR_FORMAT_CU8 = 0; // 8-bit unsigned interleaved I/Q 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,
@@ -576,23 +583,36 @@ namespace montauk::abi {
uint8_t _pad[2]; uint8_t _pad[2];
}; };
// Software-defined radio receiver description (returned by SYS_SDR_INFO). struct UsbInterfaceInfo {
struct SdrDeviceInfo { uint8_t slotId;
char name[64]; // e.g. "Realtek RTL2832U" uint8_t portId;
char tuner[32]; // e.g. "Rafael Micro R820T2" uint8_t speed;
char serial[32]; // device serial / bus location uint8_t interfaceNumber;
uint64_t freqMin; // minimum tunable center frequency, Hz uint16_t vendorId;
uint64_t freqMax; // maximum tunable center frequency, Hz uint16_t productId;
uint32_t sampleRateMin; // minimum sample rate, Hz uint8_t deviceClass;
uint32_t sampleRateMax; // maximum sample rate, Hz uint8_t interfaceClass;
uint32_t numGains; // number of discrete tuner gain steps uint8_t interfaceSubClass;
int32_t gains[32]; // available gains, tenths of dB uint8_t interfaceProtocol;
uint8_t sampleFormat; // SDR_FORMAT_* uint8_t bulkInEndpoint;
uint8_t present; // 1 if the underlying hardware is connected uint8_t bulkOutEndpoint;
uint8_t streaming; // 1 if currently delivering samples uint16_t bulkInMaxPacket;
uint8_t _pad; uint16_t bulkOutMaxPacket;
uint32_t _pad2; uint8_t kernelDriverBound;
}; uint8_t claimed;
uint8_t _reserved[4];
} __attribute__((packed));
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;
+90 -9
View File
@@ -163,15 +163,15 @@ extern "C" {
#define MTK_SYS_BTSETADDR 137 #define MTK_SYS_BTSETADDR 137
#define MTK_SYS_BTBONDS 138 #define MTK_SYS_BTBONDS 138
#define MTK_SYS_BTFORGET 139 #define MTK_SYS_BTFORGET 139
#define MTK_SYS_SDR_COUNT 140 #define MTK_SYS_RESERVED_140 140
#define MTK_SYS_SDR_INFO 141 #define MTK_SYS_RESERVED_141 141
#define MTK_SYS_SDR_OPEN 142 #define MTK_SYS_RESERVED_142 142
#define MTK_SYS_SDR_CLOSE 143 #define MTK_SYS_RESERVED_143 143
#define MTK_SYS_SDR_START 144 #define MTK_SYS_RESERVED_144 144
#define MTK_SYS_SDR_STOP 145 #define MTK_SYS_RESERVED_145 145
#define MTK_SYS_SDR_READ 146 #define MTK_SYS_RESERVED_146 146
#define MTK_SYS_SDR_SETPARAM 147 #define MTK_SYS_RESERVED_147 147
#define MTK_SYS_SDR_GETPARAM 148 #define MTK_SYS_RESERVED_148 148
#define MTK_SYS_POWERINFO 149 #define MTK_SYS_POWERINFO 149
#define MTK_SYS_FBFLIP 150 #define MTK_SYS_FBFLIP 150
#define MTK_SYS_GETEXECPATH 151 #define MTK_SYS_GETEXECPATH 151
@@ -201,6 +201,13 @@ extern "C" {
#define MTK_SYS_KILLSESSION 175 #define MTK_SYS_KILLSESSION 175
#define MTK_SYS_LOG_WRITE 176 #define MTK_SYS_LOG_WRITE 176
#define MTK_SYS_TERMINAL_ATTACHED 177 #define MTK_SYS_TERMINAL_ATTACHED 177
#define MTK_SYS_USB_LIST 178
#define MTK_SYS_USB_CLAIM 179
#define MTK_SYS_USB_CLOSE 180
#define MTK_SYS_USB_CONTROL 181
#define MTK_SYS_USB_BULK_IN_START 182
#define MTK_SYS_USB_BULK_IN_STOP 183
#define MTK_SYS_USB_BULK_IN_READ 184
/* @SYSCALLS-END */ /* @SYSCALLS-END */
#define MTK_SOCK_TCP 1 #define MTK_SOCK_TCP 1
@@ -210,6 +217,14 @@ extern "C" {
#define MTK_IPC_SIGNAL_PEER_CLOSED (1u << 2) #define MTK_IPC_SIGNAL_PEER_CLOSED (1u << 2)
#define MTK_IPC_SIGNAL_EXITED (1u << 3) #define MTK_IPC_SIGNAL_EXITED (1u << 3)
#define MTK_IPC_SIGNAL_READY (1u << 4) #define MTK_IPC_SIGNAL_READY (1u << 4)
#define MTK_USB_ERR_INVALID (-1)
#define MTK_USB_ERR_BUSY (-2)
#define MTK_USB_ERR_DISCONNECTED (-3)
#define MTK_USB_ERR_UNSUPPORTED (-4)
#define MTK_USB_ERR_IO (-5)
#define MTK_USB_ERR_NO_RESOURCES (-6)
#define MTK_USB_ERR_NOT_FOUND (-7)
#define MTK_USB_ERR_KERNEL_BOUND (-8)
/* Window event types */ /* Window event types */
#define MTK_EVENT_KEY 0 #define MTK_EVENT_KEY 0
@@ -314,6 +329,34 @@ typedef struct {
uint32_t dns_server; uint32_t dns_server;
} mtk_netcfg; } mtk_netcfg;
typedef struct __attribute__((packed)) {
uint8_t slot_id;
uint8_t port_id;
uint8_t speed;
uint8_t interface_number;
uint16_t vendor_id;
uint16_t product_id;
uint8_t device_class;
uint8_t interface_class;
uint8_t interface_subclass;
uint8_t interface_protocol;
uint8_t bulk_in_endpoint;
uint8_t bulk_out_endpoint;
uint16_t bulk_in_max_packet;
uint16_t bulk_out_max_packet;
uint8_t kernel_driver_bound;
uint8_t claimed;
uint8_t reserved[4];
} mtk_usb_interface_info;
typedef struct __attribute__((packed)) {
uint8_t request_type;
uint8_t request;
uint16_t value;
uint16_t index;
uint16_t length;
} mtk_usb_control_request;
typedef struct { typedef struct {
int32_t pid; int32_t pid;
int32_t parent_pid; int32_t parent_pid;
@@ -778,6 +821,44 @@ static inline void mtk_get_netcfg(mtk_netcfg *out) {
_mtk_syscall1(MTK_SYS_GETNETCFG, (long)out); _mtk_syscall1(MTK_SYS_GETNETCFG, (long)out);
} }
/* ====================================================================
Generic USB interface access
==================================================================== */
static inline int mtk_usb_list(mtk_usb_interface_info *out, int max_count) {
return (int)_mtk_syscall2(MTK_SYS_USB_LIST, (long)out, (long)max_count);
}
static inline int mtk_usb_claim(uint8_t slot_id, uint8_t interface_number) {
return (int)_mtk_syscall2(MTK_SYS_USB_CLAIM, (long)slot_id,
(long)interface_number);
}
static inline int mtk_usb_close(int handle) {
return (int)_mtk_syscall1(MTK_SYS_USB_CLOSE, (long)handle);
}
static inline int mtk_usb_control(int handle, const mtk_usb_control_request *request,
void *data, uint32_t data_len) {
return (int)_mtk_syscall4(MTK_SYS_USB_CONTROL, (long)handle, (long)request,
(long)data, (long)data_len);
}
static inline int mtk_usb_bulk_in_start(int handle, uint32_t transfer_bytes,
uint32_t buffer_count) {
return (int)_mtk_syscall3(MTK_SYS_USB_BULK_IN_START, (long)handle,
(long)transfer_bytes, (long)buffer_count);
}
static inline int mtk_usb_bulk_in_stop(int handle) {
return (int)_mtk_syscall1(MTK_SYS_USB_BULK_IN_STOP, (long)handle);
}
static inline int mtk_usb_bulk_in_read(int handle, void *data, uint32_t data_len) {
return (int)_mtk_syscall3(MTK_SYS_USB_BULK_IN_READ, (long)handle,
(long)data, (long)data_len);
}
/* ==================================================================== /* ====================================================================
Audio Audio
==================================================================== */ ==================================================================== */
@@ -0,0 +1,24 @@
/*
* service_log.h
* Common logging policy for MontaukOS userspace services.
*/
#pragma once
#include <montauk/syscall.h>
namespace montauk {
// Append one newline-free message to the system log. When the service was
// launched from a userspace terminal, echo the same message there as well.
inline void service_log(const char* message) {
if (message == nullptr) return;
write_log(message);
if (terminal_attached()) {
print(message);
print("\n");
}
}
}
+24 -49
View File
@@ -661,61 +661,36 @@ namespace montauk {
(uint64_t)maxCount); (uint64_t)maxCount);
} }
// Software-defined radio (Rx). Receivers are identified by index [0, count); // Generic USB access. Only interfaces without a bound kernel class driver
// open() returns a handle used by the rest of the calls. Samples are read // may be claimed. Handles belong to the claiming process and are released
// as interleaved 8-bit unsigned I/Q (CU8) from the device's ring buffer. // automatically when it exits.
inline int sdr_count() { inline int usb_list(montauk::abi::UsbInterfaceInfo* buf, int maxCount) {
return (int)syscall0(montauk::abi::SYS_SDR_COUNT); return (int)syscall2(montauk::abi::SYS_USB_LIST, (uint64_t)buf,
(uint64_t)maxCount);
} }
inline int sdr_info(int index, montauk::abi::SdrDeviceInfo* out) { inline int usb_claim(uint8_t slotId, uint8_t interfaceNumber) {
return (int)syscall2(montauk::abi::SYS_SDR_INFO, (uint64_t)index, (uint64_t)out); return (int)syscall2(montauk::abi::SYS_USB_CLAIM, (uint64_t)slotId,
(uint64_t)interfaceNumber);
} }
inline int sdr_open(int index) { inline int usb_close(int handle) {
return (int)syscall1(montauk::abi::SYS_SDR_OPEN, (uint64_t)index); return (int)syscall1(montauk::abi::SYS_USB_CLOSE, (uint64_t)handle);
} }
inline int sdr_close(int handle) { inline int usb_control(int handle, const montauk::abi::UsbControlRequest* request,
return (int)syscall1(montauk::abi::SYS_SDR_CLOSE, (uint64_t)handle); void* data, uint32_t dataLen) {
return (int)syscall4(montauk::abi::SYS_USB_CONTROL, (uint64_t)handle,
(uint64_t)request, (uint64_t)data, (uint64_t)dataLen);
} }
inline int sdr_start(int handle) { inline int usb_bulk_in_start(int handle, uint32_t transferBytes,
return (int)syscall1(montauk::abi::SYS_SDR_START, (uint64_t)handle); uint32_t bufferCount) {
return (int)syscall3(montauk::abi::SYS_USB_BULK_IN_START, (uint64_t)handle,
(uint64_t)transferBytes, (uint64_t)bufferCount);
} }
inline int sdr_stop(int handle) { inline int usb_bulk_in_stop(int handle) {
return (int)syscall1(montauk::abi::SYS_SDR_STOP, (uint64_t)handle); return (int)syscall1(montauk::abi::SYS_USB_BULK_IN_STOP, (uint64_t)handle);
} }
// Non-blocking: copies up to len bytes of queued I/Q, returns bytes copied. inline int usb_bulk_in_read(int handle, void* data, uint32_t dataLen) {
inline int sdr_read(int handle, void* buf, uint32_t len) { return (int)syscall3(montauk::abi::SYS_USB_BULK_IN_READ, (uint64_t)handle,
return (int)syscall3(montauk::abi::SYS_SDR_READ, (uint64_t)handle, (uint64_t)buf, (uint64_t)len); (uint64_t)data, (uint64_t)dataLen);
}
inline int sdr_set_param(int handle, int param, uint64_t value) {
return (int)syscall3(montauk::abi::SYS_SDR_SETPARAM, (uint64_t)handle, (uint64_t)param, value);
}
inline int64_t sdr_get_param(int handle, int param) {
return syscall2(montauk::abi::SYS_SDR_GETPARAM, (uint64_t)handle, (uint64_t)param);
}
// Convenience wrappers over sdr_set_param / sdr_get_param.
inline int sdr_set_freq(int handle, uint64_t hz) {
return sdr_set_param(handle, montauk::abi::SDR_PARAM_FREQ, hz);
}
inline uint64_t sdr_get_freq(int handle) {
return (uint64_t)sdr_get_param(handle, montauk::abi::SDR_PARAM_FREQ);
}
inline int sdr_set_sample_rate(int handle, uint32_t hz) {
return sdr_set_param(handle, montauk::abi::SDR_PARAM_SAMPLE_RATE, hz);
}
inline uint32_t sdr_get_sample_rate(int handle) {
return (uint32_t)sdr_get_param(handle, montauk::abi::SDR_PARAM_SAMPLE_RATE);
}
inline int sdr_set_gain_mode(int handle, int manual) {
return sdr_set_param(handle, montauk::abi::SDR_PARAM_GAIN_MODE, (uint64_t)manual);
}
inline int sdr_set_gain(int handle, int tenthsDb) {
return sdr_set_param(handle, montauk::abi::SDR_PARAM_GAIN, (uint64_t)(int64_t)tenthsDb);
}
inline int sdr_set_freq_correction(int handle, int ppm) {
return sdr_set_param(handle, montauk::abi::SDR_PARAM_FREQ_CORR, (uint64_t)(int64_t)ppm);
}
inline int sdr_set_agc(int handle, int on) {
return sdr_set_param(handle, montauk::abi::SDR_PARAM_AGC, (uint64_t)on);
} }
// Kernel introspection // Kernel introspection
+36
View File
@@ -0,0 +1,36 @@
#pragma once
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
typedef struct rtlsdr_device rtlsdr_device;
typedef struct rtlsdr_device_info {
char name[64];
char tuner[32];
char serial[32];
uint64_t freq_min;
uint64_t freq_max;
uint32_t sample_rate_min;
uint32_t sample_rate_max;
uint32_t num_gains;
int32_t gains[32];
} rtlsdr_device_info;
int rtlsdr_count(void);
int rtlsdr_get_device_info(int index, rtlsdr_device_info* out);
int rtlsdr_open(rtlsdr_device** out, int index);
int rtlsdr_close(rtlsdr_device* dev);
int rtlsdr_set_center_freq(rtlsdr_device* dev, uint64_t hz);
uint64_t rtlsdr_get_center_freq(const rtlsdr_device* dev);
int rtlsdr_set_sample_rate(rtlsdr_device* dev, uint32_t hz);
uint32_t rtlsdr_get_sample_rate(const rtlsdr_device* dev);
int rtlsdr_set_tuner_gain_mode(rtlsdr_device* dev, int manual);
int rtlsdr_set_tuner_gain(rtlsdr_device* dev, int tenths_db);
int rtlsdr_set_freq_correction(rtlsdr_device* dev, int ppm);
int rtlsdr_set_agc_mode(rtlsdr_device* dev, int on);
int rtlsdr_set_direct_sampling(rtlsdr_device* dev, int mode);
int rtlsdr_start(rtlsdr_device* dev);
int rtlsdr_stop(rtlsdr_device* dev);
int rtlsdr_read(rtlsdr_device* dev, void* data, uint32_t length);
#ifdef __cplusplus
}
#endif
+8 -3
View File
@@ -3,10 +3,11 @@ CXXFLAGS ?= -std=c++20 -O2 -Wall -Wextra
HTTP_TEST := http_test HTTP_TEST := http_test
SVG_TEST := svg_renderer_test SVG_TEST := svg_renderer_test
RTLSDR_TEST := rtlsdr_r820t_test
.PHONY: all check clean .PHONY: all check clean
all: $(HTTP_TEST) $(SVG_TEST) all: $(HTTP_TEST) $(SVG_TEST) $(RTLSDR_TEST)
$(HTTP_TEST): http_test.cpp $(HTTP_TEST): http_test.cpp
$(CXX) $(CXXFLAGS) -I../programs/include \ $(CXX) $(CXXFLAGS) -I../programs/include \
@@ -15,9 +16,13 @@ $(HTTP_TEST): http_test.cpp
$(SVG_TEST): svg_renderer_test.cpp ../programs/include/gui/svg.hpp $(SVG_TEST): svg_renderer_test.cpp ../programs/include/gui/svg.hpp
$(CXX) $(CXXFLAGS) -I../programs/include $< -o $@ $(CXX) $(CXXFLAGS) -I../programs/include $< -o $@
check: $(HTTP_TEST) $(SVG_TEST) $(RTLSDR_TEST): rtlsdr_r820t_test.cpp ../programs/src/rtlsdr/r820t.cpp
$(CXX) $(CXXFLAGS) -I../programs/src/rtlsdr $^ -o $@
check: $(HTTP_TEST) $(SVG_TEST) $(RTLSDR_TEST)
./$(HTTP_TEST) ./$(HTTP_TEST)
./$(SVG_TEST) ./$(SVG_TEST)
./$(RTLSDR_TEST)
clean: clean:
rm -f $(HTTP_TEST) $(SVG_TEST) rm -f $(HTTP_TEST) $(SVG_TEST) $(RTLSDR_TEST)
+54
View File
@@ -0,0 +1,54 @@
#include "../programs/src/rtlsdr/r820t.hpp"
#include <cassert>
#include <cstdio>
#include <cstring>
#include <vector>
struct rtlsdr_device {};
struct I2cMessage {
uint8_t address;
std::vector<uint8_t> data;
};
static std::vector<I2cMessage> g_writes;
namespace rtlsdr_internal {
bool RtlI2cWrite(rtlsdr_device*, uint8_t address, const uint8_t* data, uint8_t len) {
g_writes.push_back({address, std::vector<uint8_t>(data, data + len)});
return true;
}
bool RtlI2cRead(rtlsdr_device*, uint8_t, uint8_t* data, uint8_t len) {
std::memset(data, 0, len);
if (len == 1) data[0] = R820T_CHECK_VAL;
if (len == 5) data[4] = 0x80; // bit-reversed logical value 0x01
if (len == 3) data[2] = 0x02; // bit-reversed PLL lock bit 0x40
return true;
}
}
int main() {
using namespace rtlsdr_internal;
rtlsdr_device owner;
assert(R820tDetect(&owner));
R820tDev tuner{};
assert(R820tInit(tuner, &owner, 28800000, 3570000));
assert(tuner.inited);
assert(R820tSetFreq(tuner, 100100000));
assert(tuner.hasLock);
assert(R820tSetGain(tuner, 0, 0));
assert(R820tSetGain(tuner, 1, 297));
R820tStandby(tuner);
int gainCount = 0;
const int* gains = R820tGainTable(&gainCount);
assert(gainCount == 29);
assert(gains[0] == 0 && gains[gainCount - 1] == 496);
assert(!g_writes.empty());
for (const auto& message : g_writes) {
assert(message.address == R820T_I2C_ADDR);
assert(!message.data.empty() && message.data.size() <= 8);
}
std::puts("rtlsdr R820T transport test passed");
}