metal-cos/sys/kern/syscall.c
2023-09-09 20:18:08 -04:00

789 lines
16 KiB
C

/*
* Copyright (c) 2013-2023 Ali Mashtizadeh
* All rights reserved.
*/
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
#include <errno.h>
#include <sys/kassert.h>
#include <sys/kmem.h>
#include <sys/ktime.h>
#include <sys/ktimer.h>
#include <sys/thread.h>
#include <sys/loader.h>
#include <sys/syscall.h>
#include <sys/disk.h>
#include <sys/vfs.h>
#include <sys/vfsuio.h>
#include <sys/nic.h>
#include <sys/sysctl.h>
Handle *Console_OpenHandle();
uint64_t
Syscall_Time()
{
return KTime_GetEpochNS();
}
uint64_t
Syscall_GetPID()
{
Thread *cur = Sched_Current();
uint64_t pid = cur->proc->pid;
Thread_Release(cur);
return pid;
}
void
Syscall_Exit(uint64_t status)
{
Thread *cur = Sched_Current();
// Request each thread to exit
// Wait for all threads to exit
// Write exit code
cur->proc->exitCode = status;
// Exit this thread
Sched_SetZombie(cur);
Thread_Release(cur);
Sched_Scheduler();
// Should not return
Panic("Returned to exited thread!\n");
return;
}
uint64_t
Syscall_Spawn(uint64_t user_path, uint64_t user_argv)
{
int status;
char path[512];
void *pg;
char *arg;
VNode *file;
Process *proc;
Thread *thr;
Thread *cur;
status = Copy_StrIn(user_path, &path, sizeof(path));
if (status != 0)
return SYSCALL_PACK(status, 0);
Log(syscall, "Spawn(%s)\n", path);
arg = PAlloc_AllocPage();
if (!arg) {
return SYSCALL_PACK(ENOMEM, 0);
}
/* Copy argument pointers */
for (int i = 0; i < 8; i++) {
uintptr_t off = sizeof(uintptr_t)*i;
status = Copy_In(user_argv+off, arg+sizeof(uintptr_t)*(1+i), sizeof(uintptr_t));
if (status != 0) {
PAlloc_Release(arg);
return SYSCALL_PACK(status, 0);
}
if (*(uintptr_t *)(arg+sizeof(uintptr_t)*(1+i)) == 0)
break;
}
/* Copy each argument in */
char *argstart = arg+sizeof(uintptr_t)*8;
for (int i = 1; i < 8; i++) {
uintptr_t *str = (uintptr_t *)(arg+sizeof(uintptr_t)*i);
if (*str == 0)
break;
status = Copy_StrIn(*str, argstart, 256); // XXX: Make sure there's no overrun
if (status != 0) {
PAlloc_Release(arg);
return SYSCALL_PACK(status, 0);
}
*str = (uintptr_t)argstart;
argstart += strlen(argstart)+1;
}
pg = PAlloc_AllocPage();
if (!pg) {
PAlloc_Release(arg);
return SYSCALL_PACK(ENOMEM, 0);
}
file = VFS_Lookup(path);
if (!file) {
PAlloc_Release(pg);
PAlloc_Release(arg);
return SYSCALL_PACK(ENOENT, 0);
}
status = VFS_Open(file);
if (status < 0) {
Log(syscall, "Error VFS_Open\n");
// Release & free
PAlloc_Release(pg);
PAlloc_Release(arg);
return SYSCALL_PACK(status, 0);
}
status = VFS_Read(file, pg, 0, 1024);
if (status < 0) {
Log(syscall, "Error VFS_Read\n");
// Release & free
VFS_Close(file);
PAlloc_Release(pg);
PAlloc_Release(arg);
return SYSCALL_PACK(status, 0);
}
if (!Loader_CheckHeader(pg)) {
VFS_Close(file);
PAlloc_Release(pg);
PAlloc_Release(arg);
return SYSCALL_PACK(EINVAL, 0);
}
cur = Sched_Current();
proc = Process_Create(cur->proc, path);
thr = Thread_Create(proc);
Thread_Release(cur);
Log(syscall, "SPAWN %lx\n", thr);
Handle *handle = Console_OpenHandle();
Handle_Add(proc, handle);
handle = Console_OpenHandle();
Handle_Add(proc, handle);
handle = Console_OpenHandle();
Handle_Add(proc, handle);
Loader_Load(thr, file, pg, 1024);
/* Initialize the trap frame for entering into the process. */
Thread_SetupUThread(thr, proc->entrypoint, MEM_USERSPACE_STKTOP - PGSIZE);
/* Translate mapping for stack page */
argstart = (char *)DMPA2VA(PMap_Translate(thr->space, MEM_USERSPACE_STKTOP - PGSIZE));
argstart += sizeof(uintptr_t)*8;
uintptr_t offset = sizeof(uintptr_t)*8;
/* Copy each argument and update the pointer array */
uintptr_t *arg_ap = (uintptr_t *)arg;
for (int i = 1; i < 8; i++) {
uintptr_t *str = (uintptr_t *)(arg+sizeof(uintptr_t)*i);
if (*str == 0) {
arg_ap[0] = i - 1;
arg_ap[i] = 0; // XXX: Environment
break;
}
size_t len = strlen((char *)arg_ap[i])+1;
strcpy(argstart, (char *)arg_ap[i]);
arg_ap[i] = (uintptr_t)(MEM_USERSPACE_STKTOP - PGSIZE + offset);
offset += len;
argstart += len;
}
/* Copy the argument pointer array */
argstart = (char *)DMPA2VA(PMap_Translate(thr->space, MEM_USERSPACE_STKTOP - PGSIZE));
memcpy(argstart, arg, sizeof(uintptr_t)*8);
VFS_Close(file);
Sched_SetRunnable(thr);
return SYSCALL_PACK(0, proc->pid);
}
uint64_t
Syscall_Wait(uint64_t pid)
{
uint64_t status;
Thread *cur = Sched_Current();
status = Process_Wait(cur->proc, pid);
Thread_Release(cur);
return status;
}
uint64_t
Syscall_MMap(uint64_t addr, uint64_t len, uint64_t prot)
{
Thread *cur = Sched_Current();
bool status;
status = PMap_AllocMap(cur->space, addr, len, PTE_W);
Thread_Release(cur);
if (!status) {
// XXX: Need to unmap PMap_Unmap(cur->space, addr, pgs);
return 0;
} else {
return addr;
}
}
uint64_t
Syscall_MUnmap(uint64_t addr, uint64_t len)
{
Thread *cur = Sched_Current();
uint64_t p;
for (p = 0; p < len; p += PGSIZE)
{
// Free page
}
PMap_Unmap(cur->space, addr, len /= PGSIZE);
Thread_Release(cur);
return 0;
}
uint64_t
Syscall_MProtect(uint64_t addr, uint64_t len, uint64_t prot)
{
//Thread *cur = Sched_Current();
NOT_IMPLEMENTED();
return 0;
}
uint64_t
Syscall_Read(uint64_t fd, uint64_t addr, uint64_t off, uint64_t length)
{
uint64_t status;
Thread *cur = Sched_Current();
Handle *handle = Handle_Lookup(cur->proc, fd);
if (handle == NULL) {
status = -EBADF;
} else {
status = (handle->read)(handle, (void *)addr, off, length);
}
Thread_Release(cur);
return status;
}
uint64_t
Syscall_Write(uint64_t fd, uint64_t addr, uint64_t off, uint64_t length)
{
uint64_t status;
Thread *cur = Sched_Current();
Handle *handle = Handle_Lookup(cur->proc, fd);
if (handle == NULL) {
status = -EBADF;
} else {
status = (handle->write)(handle, (void *)addr, off, length);
}
Thread_Release(cur);
return status;
}
uint64_t
Syscall_Flush(uint64_t fd)
{
uint64_t status;
Thread *cur = Sched_Current();
Handle *handle = Handle_Lookup(cur->proc, fd);
if (handle == NULL) {
status = -EBADF;
} else {
status = (handle->flush)(handle);
}
Thread_Release(cur);
return status;
}
// XXX: Cleanup
Handle *Console_OpenHandle();
uint64_t
Syscall_Open(uint64_t user_path, uint64_t flags)
{
uint64_t handleNo;
Thread *cur = Sched_Current();
int status;
char path[256];
status = Copy_StrIn(user_path, &path, sizeof(path));
if (status != 0) {
Thread_Release(cur);
return status;
}
if (strncmp("/dev/", path, 5) == 0) {
if (strcmp("/dev/console", path) == 0) {
Handle *handle = Console_OpenHandle();
handleNo = Handle_Add(cur->proc, handle);
Thread_Release(cur);
return handleNo;
}
Thread_Release(cur);
return -ENOENT;
}
Handle *handle;
status = VFSUIO_Open(path, &handle);
if (status != 0) {
Thread_Release(cur);
return status;
}
handleNo = Handle_Add(cur->proc, handle);
Thread_Release(cur);
return handleNo;
}
uint64_t
Syscall_Close(uint64_t fd)
{
uint64_t status;
Thread *cur = Sched_Current();
Handle *handle = Handle_Lookup(cur->proc, fd);
if (handle == NULL) {
status = -EBADF;
} else {
status = (handle->close)(handle);
}
Thread_Release(cur);
return status;
}
uint64_t
Syscall_Stat(uint64_t user_path, uint64_t user_stat)
{
int status;
char path[256];
struct stat sb;
status = Copy_StrIn(user_path, &path, sizeof(path));
if (status != 0) {
return status;
}
// VFS_Stat
status = VFS_Stat(path, &sb);
if (status != 0) {
return status;
}
status = Copy_Out(&sb, user_stat, sizeof(struct stat));
if (status != 0) {
return status;
}
return 0;
}
uint64_t
Syscall_ReadDir(uint64_t fd, char *user_buf, size_t len, uintptr_t user_off)
{
int status, rstatus;
Thread *cur = Sched_Current();
Handle *handle = Handle_Lookup(cur->proc, fd);
uint64_t offset;
if (handle == NULL) {
Thread_Release(cur);
return -EBADF;
}
status = Copy_In(user_off, &offset, sizeof(offset));
if (status != 0) {
Thread_Release(cur);
return status;
}
if (handle->type != HANDLE_TYPE_FILE) {
Thread_Release(cur);
return -ENOTDIR;
}
rstatus = VFS_ReadDir(handle->vnode, user_buf, len, &offset);
if (rstatus < 0) {
Thread_Release(cur);
return rstatus;
}
status = Copy_Out(&offset, user_off, sizeof(offset));
if (status != 0) {
Thread_Release(cur);
return status;
}
Thread_Release(cur);
return rstatus;
}
uint64_t
Syscall_ThreadCreate(uint64_t rip, uint64_t arg)
{
uint64_t threadId;
Thread *curThread = Sched_Current();
Thread *newThread = Thread_UThreadCreate(curThread, rip, arg);
Thread_Release(curThread);
if (newThread == NULL) {
return SYSCALL_PACK(ENOMEM, 0);
}
threadId = newThread->tid;
Sched_SetRunnable(newThread);
return SYSCALL_PACK(0, threadId);
}
uint64_t
Syscall_GetTID()
{
Thread *cur = Sched_Current();
uint64_t tid = cur->tid;
Thread_Release(cur);
return tid;
}
void
Syscall_ThreadExit(uint64_t status)
{
Thread *cur = Sched_Current();
// Encode this like POSIX
cur->exitValue = status;
Sched_SetZombie(cur);
Semaphore_Release(&cur->proc->zombieSemaphore);
Thread_Release(cur);
Sched_Scheduler();
// Should not return
Panic("Returned to exited thread!\n");
}
static void
ThreadWakeupHelper(void *arg)
{
Thread *thr = (Thread *)arg;
Sched_SetRunnable(thr);
KTimer_Release(thr->timerEvt);
thr->timerEvt = NULL;
Thread_Release(thr);
}
uint64_t
Syscall_ThreadSleep(uint64_t time)
{
Thread *cur = Sched_Current();
// If the sleep time is zero just yield
if (time != 0) {
Thread_Retain(cur);
cur->timerEvt = KTimer_Create(time, ThreadWakeupHelper, cur);
if (cur->timerEvt == NULL) {
Thread_Release(cur);
Thread_Release(cur);
return -ENOMEM;
}
Sched_SetWaiting(cur);
}
Sched_Scheduler();
Thread_Release(cur);
return 0;
}
uint64_t
Syscall_ThreadWait(uint64_t tid)
{
uint64_t status;
Thread *cur = Sched_Current();
/*
* Acquire the zombie semaphore see if the specified thread has exited or
* any thread if tid == 0. If the specified thread hasn't exited wait
* again on the semaphore. POSIX does not give any guarentees if multiple
* threads wait on the same thread and neither do we.
*
* As a precaution we call Sched_Scheduler to prevent looping on the
* semaphore acquire-release.
*/
while (1) {
Semaphore_Acquire(&cur->proc->zombieSemaphore);
status = Thread_Wait(cur, tid);
if (SYSCALL_ERRCODE(status) != EAGAIN) {
Thread_Release(cur);
return status;
}
Semaphore_Release(&cur->proc->zombieSemaphore);
Sched_Scheduler();
}
}
uint64_t
Syscall_NICStat(uint64_t nicNo, uint64_t user_stat)
{
int status;
NIC *nic;
nic = NIC_GetByID(nicNo);
if (nic == NULL) {
return ENOENT;
}
status = Copy_Out(nic, user_stat, sizeof(NIC));
if (status != 0) {
return status;
}
return 0;
}
uint64_t
Syscall_NICSend(uint64_t nicNo, uint64_t user_mbuf)
{
int status;
NIC *nic;
MBuf mbuf;
status = Copy_In(user_mbuf, &mbuf, sizeof(mbuf));
if (status != 0) {
return SYSCALL_PACK(status, 0);
}
nic = NIC_GetByID(nicNo);
if (nic == NULL) {
return SYSCALL_PACK(ENOENT, 0);
}
// Pin Memory
(nic->tx)(nic, &mbuf, NULL, NULL);
// Unpin Memory
return 0;
}
uint64_t
Syscall_NICRecv(uint64_t nicNo, uint64_t user_mbuf)
{
int status;
NIC *nic;
MBuf mbuf;
status = Copy_In(user_mbuf, &mbuf, sizeof(mbuf));
if (status != 0) {
return SYSCALL_PACK(status, 0);
}
nic = NIC_GetByID(nicNo);
if (nic == NULL) {
return SYSCALL_PACK(ENOENT, 0);
}
// Pin Memory
(nic->rx)(nic, &mbuf, NULL, NULL);
// Unpin Memory
return 0;
}
uint64_t
Syscall_SysCtl(uint64_t user_node, uint64_t user_oldval, uint64_t user_newval)
{
uint64_t status;
char node[64];
status = Copy_StrIn(user_node, &node, sizeof(node));
if (status != 0) {
return SYSCALL_PACK(status, 0);
}
uint64_t scType = SysCtl_GetType(node);
if (scType == SYSCTL_TYPE_INVALID) {
return SYSCALL_PACK(ENOENT, 0);
}
if (user_oldval != 0) {
switch (scType) {
case SYSCTL_TYPE_STR: {
SysCtlString *scStr = SysCtl_GetObject(node);
status = Copy_Out(scStr, user_oldval, sizeof(*scStr));
break;
}
case SYSCTL_TYPE_INT: {
SysCtlInt *scInt = SysCtl_GetObject(node);
status = Copy_Out(scInt, user_oldval, sizeof(*scInt));
break;
}
case SYSCTL_TYPE_BOOL: {
SysCtlBool *scBool = SysCtl_GetObject(node);
status = Copy_Out(scBool, user_oldval, sizeof(scBool));
break;
}
default: {
status = EINVAL;
}
}
if (status != 0) {
return SYSCALL_PACK(status, 0);
}
}
if (user_newval != 0) {
switch (scType) {
case SYSCTL_TYPE_STR: {
SysCtlString scStr;
status = Copy_In(user_newval, &scStr, sizeof(scStr));
if (status != 0) {
return SYSCALL_PACK(status, 0);
}
status = SysCtl_SetObject(node, (void *)&scStr);
break;
}
case SYSCTL_TYPE_INT: {
SysCtlInt scInt;
status = Copy_In(user_newval, &scInt, sizeof(scInt));
if (status != 0) {
return SYSCALL_PACK(status, 0);
}
status = SysCtl_SetObject(node, (void *)&scInt);
break;
}
case SYSCTL_TYPE_BOOL: {
SysCtlBool scBool;
status = Copy_In(user_newval, &scBool, sizeof(scBool));
if (status != 0) {
return SYSCALL_PACK(status, 0);
}
status = SysCtl_SetObject(node, (void *)&scBool);
break;
}
default: {
status = EINVAL;
}
}
}
return SYSCALL_PACK(status, 0);
}
uint64_t
Syscall_FSMount(uint64_t user_mntpt, uint64_t user_device, uint64_t flags)
{
return SYSCALL_PACK(ENOSYS, 0);
}
uint64_t
Syscall_FSUnmount(uint64_t user_mntpt)
{
return SYSCALL_PACK(ENOSYS, 0);
}
uint64_t
Syscall_FSInfo(uint64_t user_fsinfo, uint64_t max)
{
return SYSCALL_PACK(ENOSYS, 0);
}
uint64_t
Syscall_Entry(uint64_t syscall, uint64_t a1, uint64_t a2,
uint64_t a3, uint64_t a4, uint64_t a5)
{
switch (syscall)
{
case SYSCALL_NULL:
return 0;
case SYSCALL_TIME:
return Syscall_Time();
case SYSCALL_GETPID:
return Syscall_GetPID();
case SYSCALL_EXIT:
Syscall_Exit(a1);
return 0; // To eliminate warning
case SYSCALL_SPAWN:
return Syscall_Spawn(a1, a2);
case SYSCALL_WAIT:
return Syscall_Wait(a1);
case SYSCALL_MMAP:
return Syscall_MMap(a1, a2, a3);
case SYSCALL_MUNMAP:
return Syscall_MUnmap(a1, a2);
case SYSCALL_MPROTECT:
return Syscall_MProtect(a1, a2, a3);
case SYSCALL_READ:
return Syscall_Read(a1, a2, a3, a4);
case SYSCALL_WRITE:
return Syscall_Write(a1, a2, a3, a4);
case SYSCALL_FLUSH:
return Syscall_Flush(a1);
case SYSCALL_OPEN:
return Syscall_Open(a1, a2);
case SYSCALL_CLOSE:
return Syscall_Close(a1);
case SYSCALL_STAT:
return Syscall_Stat(a1, a2);
case SYSCALL_READDIR:
return Syscall_ReadDir(a1, (char *)a2, a3, a4);
case SYSCALL_THREADCREATE:
return Syscall_ThreadCreate(a1, a2);
case SYSCALL_GETTID:
return Syscall_GetTID();
case SYSCALL_THREADEXIT:
Syscall_ThreadExit(a1);
return 0;
case SYSCALL_THREADSLEEP:
return Syscall_ThreadSleep(a1);
case SYSCALL_THREADWAIT:
return Syscall_ThreadWait(a1);
case SYSCALL_NICSTAT:
return Syscall_NICStat(a1, a2);
case SYSCALL_NICSEND:
return Syscall_NICSend(a1, a2);
case SYSCALL_NICRECV:
return Syscall_NICRecv(a1, a2);
case SYSCALL_SYSCTL:
return Syscall_SysCtl(a1, a2, a3);
case SYSCALL_FSMOUNT:
return Syscall_FSMount(a1, a2, a3);
case SYSCALL_FSUNMOUNT:
return Syscall_FSUnmount(a1);
case SYSCALL_FSINFO:
return Syscall_FSInfo(a1, a2);
default:
return SYSCALL_PACK(ENOSYS, 0);
}
}