// TODO Replace ES_ERROR_UNKNOWN with proper errors. // TODO Clean up the return values for system calls; with FATAL_ERRORs there should need to be less error codes returned. // TODO If a file system call fails with an error indicating the file system is corrupted, or a drive is failing, report the problem to the user. #ifndef IMPLEMENTATION KMutex eventForwardMutex; uintptr_t DoSyscall(EsSyscallType index, uintptr_t argument0, uintptr_t argument1, uintptr_t argument2, uintptr_t argument3, uint64_t flags, bool *fatal, uintptr_t *userStackPointer); #define DO_SYSCALL_BATCHED (2) struct MessageQueue { bool SendMessage(void *target, EsMessage *message); // Returns false if the message queue is full. bool SendMessage(_EsMessageWithObject *message); // Returns false if the message queue is full. bool GetMessage(_EsMessageWithObject *message); #define MESSAGE_QUEUE_MAX_LENGTH (4096) Array<_EsMessageWithObject, K_FIXED> messages; uintptr_t mouseMovedMessage, windowResizedMessage, eyedropResultMessage, keyRepeatMessage; bool pinged; KMutex mutex; KEvent notEmpty; }; #endif #ifdef IMPLEMENTATION bool MessageQueue::SendMessage(void *object, EsMessage *_message) { // TODO Remove unnecessary copy. _EsMessageWithObject message = { object, *_message }; return SendMessage(&message); } bool MessageQueue::SendMessage(_EsMessageWithObject *_message) { // TODO Don't send messages if the process has been terminated. KMutexAcquire(&mutex); EsDefer(KMutexRelease(&mutex)); if (messages.Length() == MESSAGE_QUEUE_MAX_LENGTH) { KernelLog(LOG_ERROR, "Messages", "message dropped", "Message of type %d and target %x has been dropped because queue %x was full.\n", _message->message.type, _message->object, this); return false; } #define MERGE_MESSAGES(variable, change) \ do { \ if (variable && messages[variable - 1].object == _message->object) { \ if (change) EsMemoryCopy(&messages[variable - 1], _message, sizeof(_EsMessageWithObject)); \ } else if (messages.AddPointer(_message)) { \ variable = messages.Length(); \ } else { \ return false; \ } \ } while (0) // NOTE Don't forget to update GetMessage with the merged messages! if (_message->message.type == ES_MSG_MOUSE_MOVED) { MERGE_MESSAGES(mouseMovedMessage, true); } else if (_message->message.type == ES_MSG_WINDOW_RESIZED) { MERGE_MESSAGES(windowResizedMessage, true); } else if (_message->message.type == ES_MSG_EYEDROP_REPORT) { MERGE_MESSAGES(eyedropResultMessage, true); } else if (_message->message.type == ES_MSG_KEY_DOWN && _message->message.keyboard.repeat) { MERGE_MESSAGES(keyRepeatMessage, false); } else { if (!messages.AddPointer(_message)) { return false; } if (_message->message.type == ES_MSG_PING) { pinged = true; } } KEventSet(¬Empty, false, true); return true; } bool MessageQueue::GetMessage(_EsMessageWithObject *_message) { KMutexAcquire(&mutex); EsDefer(KMutexRelease(&mutex)); if (!messages.Length()) { return false; } *_message = messages[0]; messages.Delete(0); if (mouseMovedMessage) mouseMovedMessage--; if (windowResizedMessage) windowResizedMessage--; if (eyedropResultMessage) eyedropResultMessage--; if (keyRepeatMessage) keyRepeatMessage--; pinged = false; if (!messages.Length()) { KEventReset(¬Empty); } return true; } #define CHECK_OBJECT(x) if (!x.valid) SYSCALL_RETURN(ES_FATAL_ERROR_INVALID_HANDLE, !x.softFailure); else x.checked = true #define SYSCALL_BUFFER_LIMIT (64 * 1024 * 1024) // To prevent overflow and DOS attacks. #define SYSCALL_BUFFER(address, length, index, write) \ MMRegion *_region ## index = MMFindAndPinRegion(currentVMM, (address), (length)); \ if (!_region ## index) SYSCALL_RETURN(ES_FATAL_ERROR_INVALID_BUFFER, true); \ EsDefer(if (_region ## index) MMUnpinRegion(currentVMM, _region ## index)); \ if (write && (_region ## index->flags & MM_REGION_READ_ONLY) && (~_region ## index->flags & MM_REGION_COPY_ON_WRITE)) \ SYSCALL_RETURN(ES_FATAL_ERROR_INVALID_BUFFER, true); #define SYSCALL_HANDLE(handle, type, __object, index) \ KObject _object ## index(currentProcess, handle, type); \ CHECK_OBJECT(_object ## index); \ *((void **) &__object) = (_object ## index).object; #define SYSCALL_READ(destination, source, length) \ if (!MMArchIsBufferInUserRange(source, length) || !MMArchSafeCopy((uintptr_t) (destination), source, length)) \ SYSCALL_RETURN(ES_FATAL_ERROR_INVALID_BUFFER, true); #define SYSCALL_READ_HEAP(destination, source, length) \ *(void **) &(destination) = EsHeapAllocate((length), false, K_FIXED); \ if ((length) != 0 && !(destination)) SYSCALL_RETURN(ES_ERROR_INSUFFICIENT_RESOURCES, false); \ EsDefer(EsHeapFree((destination), (length), K_FIXED)); \ SYSCALL_READ(destination, source, length); #define SYSCALL_WRITE(destination, source, length) \ if (!MMArchIsBufferInUserRange(destination, length) || !MMArchSafeCopy(destination, (uintptr_t) (source), length)) \ SYSCALL_RETURN(ES_FATAL_ERROR_INVALID_BUFFER, true); #define SYSCALL_ARGUMENTS uintptr_t argument0, uintptr_t argument1, uintptr_t argument2, uintptr_t argument3, \ Thread *currentThread, Process *currentProcess, MMSpace *currentVMM, uintptr_t *userStackPointer, bool *fatalError #define SYSCALL_IMPLEMENT(_type) uintptr_t Do ## _type (SYSCALL_ARGUMENTS) #define SYSCALL_RETURN(value, fatal) do { *fatalError = fatal; return (value); } while (0) #define SYSCALL_PERMISSION(x) do { if ((x) != (currentProcess->permissions & (x))) { *fatalError = true; return ES_FATAL_ERROR_INSUFFICIENT_PERMISSIONS; } } while (0) typedef uintptr_t (*SyscallFunction)(SYSCALL_ARGUMENTS); #pragma GCC diagnostic ignored "-Wunused-parameter" push SYSCALL_IMPLEMENT(ES_SYSCALL_COUNT) { SYSCALL_RETURN(ES_FATAL_ERROR_UNKNOWN_SYSCALL, true); } SYSCALL_IMPLEMENT(ES_SYSCALL_PRINT) { char *buffer; if (argument1 > SYSCALL_BUFFER_LIMIT) SYSCALL_RETURN(ES_FATAL_ERROR_INVALID_BUFFER, true); SYSCALL_READ_HEAP(buffer, argument0, argument1); EsPrint("%s", argument1, buffer); SYSCALL_RETURN(ES_SUCCESS, false); } SYSCALL_IMPLEMENT(ES_SYSCALL_MEMORY_ALLOCATE) { EsMemoryProtection protection = (EsMemoryProtection) argument2; uint32_t flags = MM_REGION_USER; if (protection == ES_MEMORY_PROTECTION_READ_ONLY) flags |= MM_REGION_READ_ONLY; if (protection == ES_MEMORY_PROTECTION_EXECUTABLE) flags |= MM_REGION_EXECUTABLE; uintptr_t address = (uintptr_t) MMStandardAllocate(currentVMM, argument0, flags, nullptr, argument1 & ES_MEMORY_RESERVE_COMMIT_ALL); SYSCALL_RETURN(address, false); } SYSCALL_IMPLEMENT(ES_SYSCALL_MEMORY_FREE) { if (!MMFree(currentVMM, (void *) argument0, argument1, true /* only allow freeing regions marked with MM_REGION_USER */)) { SYSCALL_RETURN(ES_FATAL_ERROR_INVALID_MEMORY_REGION, true); } SYSCALL_RETURN(ES_SUCCESS, false); } SYSCALL_IMPLEMENT(ES_SYSCALL_MEMORY_COMMIT) { SYSCALL_BUFFER(argument0 << K_PAGE_BITS, argument1 << K_PAGE_BITS, 0, false); argument0 -= _region0->baseAddress >> K_PAGE_BITS; if (argument0 >= _region0->pageCount || argument1 > _region0->pageCount - argument0 || (~_region0->flags & MM_REGION_NORMAL) || (~_region0->flags & MM_REGION_USER) || !argument1) { SYSCALL_RETURN(ES_FATAL_ERROR_INVALID_MEMORY_REGION, true); } bool success = false; if (argument2 == 0) { KMutexAcquire(¤tVMM->reserveMutex); success = MMCommitRange(currentVMM, _region0, argument0, argument1); KMutexRelease(¤tVMM->reserveMutex); } else if (argument2 == 1) { KMutexAcquire(¤tVMM->reserveMutex); success = MMDecommitRange(currentVMM, _region0, argument0, argument1); KMutexRelease(¤tVMM->reserveMutex); } else { SYSCALL_RETURN(ES_FATAL_ERROR_UNKNOWN_SYSCALL, true); } SYSCALL_RETURN(success ? ES_SUCCESS : ES_ERROR_INSUFFICIENT_RESOURCES, false); } SYSCALL_IMPLEMENT(ES_SYSCALL_MEMORY_FAULT_RANGE) { uintptr_t start = argument0 & ~(K_PAGE_SIZE - 1); uintptr_t end = (argument0 + argument1 - 1) & ~(K_PAGE_SIZE - 1); for (uintptr_t page = start; page <= end; page += K_PAGE_SIZE) { if (!MMArchHandlePageFault(page, ES_FLAGS_DEFAULT)) { SYSCALL_RETURN(ES_FATAL_ERROR_INVALID_BUFFER, true); } } SYSCALL_RETURN(ES_SUCCESS, false); } SYSCALL_IMPLEMENT(ES_SYSCALL_PROCESS_CREATE) { SYSCALL_PERMISSION(ES_PERMISSION_PROCESS_CREATE); EsProcessCreationArguments arguments; SYSCALL_READ(&arguments, argument0, sizeof(EsProcessCreationArguments)); if (arguments.permissions == ES_PERMISSION_INHERIT) { arguments.permissions = currentProcess->permissions; } if (arguments.permissions & ~currentProcess->permissions) { SYSCALL_RETURN(ES_FATAL_ERROR_INSUFFICIENT_PERMISSIONS, true); } KObject executableObject(currentProcess, arguments.executable, KERNEL_OBJECT_NODE); CHECK_OBJECT(executableObject); if (((KNode *) executableObject.object)->directoryEntry->type != ES_NODE_FILE) { SYSCALL_RETURN(ES_FATAL_ERROR_INCORRECT_NODE_TYPE, true); } EsProcessInformation processInformation; EsMemoryZero(&processInformation, sizeof(EsProcessInformation)); Process *process = scheduler.SpawnProcess(); if (!process) { SYSCALL_RETURN(ES_ERROR_INSUFFICIENT_RESOURCES, false); } process->creationFlags = arguments.flags; process->creationArguments[CREATION_ARGUMENT_MAIN] = arguments.creationArgument.u; process->permissions = arguments.permissions; // TODO Free the process object if something fails here. if (arguments.environmentBlockBytes) { if (arguments.environmentBlockBytes > SYSCALL_BUFFER_LIMIT) SYSCALL_RETURN(ES_FATAL_ERROR_INVALID_BUFFER, true); SYSCALL_BUFFER((uintptr_t) arguments.environmentBlock, arguments.environmentBlockBytes, 1, false); process->creationArguments[CREATION_ARGUMENT_ENVIRONMENT] = MakeConstantBuffer(arguments.environmentBlock, arguments.environmentBlockBytes, process); } if (arguments.initialMountPointCount) { if (arguments.initialMountPointCount > ES_MOUNT_POINT_MAX_COUNT) SYSCALL_RETURN(ES_FATAL_ERROR_INVALID_BUFFER, true); EsMountPoint *mountPoints = (EsMountPoint *) EsHeapAllocate(arguments.initialMountPointCount * sizeof(EsMountPoint), false, K_FIXED); EsDefer(EsHeapFree(mountPoints, arguments.initialMountPointCount * sizeof(EsMountPoint), K_FIXED)); SYSCALL_READ(mountPoints, (uintptr_t) arguments.initialMountPoints, arguments.initialMountPointCount * sizeof(EsMountPoint)); for (uintptr_t i = 0; i < arguments.initialMountPointCount; i++) { // Open handles to the mount points for the new process. // TODO Handling errors when opening handles. KObject object(currentProcess, mountPoints[i].base, KERNEL_OBJECT_NODE); CHECK_OBJECT(object); if (!mountPoints[i].write) object.flags &= ~_ES_NODE_DIRECTORY_WRITE; OpenHandleToObject(object.object, object.type, object.flags); mountPoints[i].base = process->handleTable.OpenHandle(object.object, object.flags, object.type); } process->creationArguments[CREATION_ARGUMENT_INITIAL_MOUNT_POINTS] = MakeConstantBuffer(mountPoints, arguments.initialMountPointCount * sizeof(EsMountPoint), process); } if (!process->StartWithNode((KNode *) executableObject.object)) { CloseHandleToObject(process, KERNEL_OBJECT_PROCESS); SYSCALL_RETURN(ES_ERROR_UNKNOWN, false); } processInformation.pid = process->id; processInformation.mainThread.tid = process->executableMainThread->id; processInformation.mainThread.handle = currentProcess->handleTable.OpenHandle(process->executableMainThread, 0, KERNEL_OBJECT_THREAD); processInformation.handle = currentProcess->handleTable.OpenHandle(process, 0, KERNEL_OBJECT_PROCESS); SYSCALL_WRITE(argument2, &processInformation, sizeof(EsProcessInformation)); SYSCALL_RETURN(ES_SUCCESS, false); } SYSCALL_IMPLEMENT(ES_SYSCALL_PROCESS_GET_CREATION_ARGUMENT) { KObject object(currentProcess, argument0, KERNEL_OBJECT_PROCESS); CHECK_OBJECT(object); Process *process = (Process *) object.object; if (argument1 >= sizeof(process->creationArguments) / sizeof(process->creationArguments[0])) { SYSCALL_RETURN(ES_FATAL_ERROR_OUT_OF_RANGE, true); } SYSCALL_RETURN(process->creationArguments[argument1], false); } SYSCALL_IMPLEMENT(ES_SYSCALL_SCREEN_FORCE_UPDATE) { if (argument0) { SYSCALL_PERMISSION(ES_PERMISSION_SCREEN_MODIFY); } KMutexAcquire(&windowManager.mutex); if (argument0) { windowManager.Redraw(ES_POINT(0, 0), graphics.frameBuffer.width, graphics.frameBuffer.height, nullptr); } GraphicsUpdateScreen(); KMutexRelease(&windowManager.mutex); SYSCALL_RETURN(ES_SUCCESS, false); } SYSCALL_IMPLEMENT(ES_SYSCALL_EYEDROP_START) { KObject _avoid(currentProcess, argument1, KERNEL_OBJECT_WINDOW); CHECK_OBJECT(_avoid); Window *avoid = (Window *) _avoid.object; windowManager.StartEyedrop(argument0, avoid, argument2); SYSCALL_RETURN(ES_SUCCESS, false); } SYSCALL_IMPLEMENT(ES_SYSCALL_MESSAGE_GET) { _EsMessageWithObject message; EsMemoryZero(&message, sizeof(_EsMessageWithObject)); if (currentProcess->messageQueue.GetMessage(&message)) { SYSCALL_WRITE(argument0, &message, sizeof(_EsMessageWithObject)); SYSCALL_RETURN(ES_SUCCESS, false); } else { SYSCALL_RETURN(ES_ERROR_NO_MESSAGES_AVAILABLE, false); } } SYSCALL_IMPLEMENT(ES_SYSCALL_MESSAGE_WAIT) { currentThread->terminatableState = THREAD_USER_BLOCK_REQUEST; KEventWait(¤tProcess->messageQueue.notEmpty, argument0 /* timeout */); currentThread->terminatableState = THREAD_IN_SYSCALL; SYSCALL_RETURN(ES_SUCCESS, false); } SYSCALL_IMPLEMENT(ES_SYSCALL_WINDOW_CREATE) { SYSCALL_PERMISSION(ES_PERMISSION_WINDOW_MANAGER); if (argument0 == ES_WINDOW_NORMAL) { void *_window = windowManager.CreateEmbeddedWindow(currentProcess, (void *) argument2); SYSCALL_RETURN(currentProcess->handleTable.OpenHandle(_window, 0, KERNEL_OBJECT_EMBEDDED_WINDOW), false); } else { void *_window = windowManager.CreateWindow(currentProcess, (void *) argument2, (EsWindowStyle) argument0); if (!_window) { SYSCALL_RETURN(ES_ERROR_INSUFFICIENT_RESOURCES, false); } else { SYSCALL_RETURN(currentProcess->handleTable.OpenHandle(_window, 0, KERNEL_OBJECT_WINDOW), false); } } } SYSCALL_IMPLEMENT(ES_SYSCALL_WINDOW_CLOSE) { KObject _window(currentProcess, argument0, KERNEL_OBJECT_WINDOW | KERNEL_OBJECT_EMBEDDED_WINDOW); CHECK_OBJECT(_window); KMutexAcquire(&windowManager.mutex); if (_window.type == KERNEL_OBJECT_EMBEDDED_WINDOW) { EmbeddedWindow *window = (EmbeddedWindow *) _window.object; window->Close(); } else { Window *window = (Window *) _window.object; window->Close(); } KMutexRelease(&windowManager.mutex); SYSCALL_RETURN(ES_SUCCESS, false); } SYSCALL_IMPLEMENT(ES_SYSCALL_WINDOW_SET_PROPERTY) { uint8_t property = argument3; KObject _window(currentProcess, argument0, (property & 0x80) ? KERNEL_OBJECT_EMBEDDED_WINDOW : KERNEL_OBJECT_WINDOW); CHECK_OBJECT(_window); Window *window = (Window *) _window.object; EmbeddedWindow *embed = (EmbeddedWindow *) _window.object; if (property == ES_WINDOW_PROPERTY_SOLID) { window->solid = (argument1 & ES_WINDOW_SOLID_TRUE) != 0; window->noClickActivate = (argument1 & ES_WINDOW_SOLID_NO_ACTIVATE) != 0; window->noBringToFront = (argument1 & ES_WINDOW_SOLID_NO_BRING_TO_FRONT) != 0; window->solidInsets = ES_RECT_1I(argument2); KMutexAcquire(&windowManager.mutex); windowManager.MoveCursor(0, 0); KMutexRelease(&windowManager.mutex); } else if (property == ES_WINDOW_PROPERTY_OPAQUE_BOUNDS) { SYSCALL_READ(&window->opaqueBounds, argument1, sizeof(EsRectangle)); } else if (property == ES_WINDOW_PROPERTY_BLUR_BOUNDS) { SYSCALL_READ(&window->blurBounds, argument1, sizeof(EsRectangle)); } else if (property == ES_WINDOW_PROPERTY_ALPHA) { window->alpha = argument1 & 0xFF; } else if (property == ES_WINDOW_PROPERTY_FOCUSED) { KMutexAcquire(&windowManager.mutex); windowManager.ActivateWindow(window); windowManager.MoveCursor(0, 0); KMutexRelease(&windowManager.mutex); } else if (property == ES_WINDOW_PROPERTY_MATERIAL) { window->material = argument1; } else if (property == ES_WINDOW_PROPERTY_EMBED) { KObject _embed(currentProcess, argument1, KERNEL_OBJECT_EMBEDDED_WINDOW | KERNEL_OBJECT_NONE); CHECK_OBJECT(_embed); EmbeddedWindow *embed = (EmbeddedWindow *) _embed.object; KMutexAcquire(&windowManager.mutex); window->SetEmbed(embed); KMutexRelease(&windowManager.mutex); } else if (property == ES_WINDOW_PROPERTY_EMBED_INSETS) { KMutexAcquire(&windowManager.mutex); SYSCALL_READ(&window->embedInsets, argument1, sizeof(EsRectangle)); window->ResizeEmbed(); KMutexRelease(&windowManager.mutex); } else if (property == ES_WINDOW_PROPERTY_OBJECT) { if (embed->owner != currentProcess) { // TODO Permissions. } embed->apiWindow = (void *) argument2; __sync_synchronize(); KMutexAcquire(&windowManager.mutex); if (embed->container) embed->container->ResizeEmbed(); KMutexRelease(&windowManager.mutex); } else if (property == ES_WINDOW_PROPERTY_EMBED_OWNER) { Process *process; SYSCALL_HANDLE(argument1, KERNEL_OBJECT_PROCESS, process, 2); OpenHandleToObject(embed, KERNEL_OBJECT_EMBEDDED_WINDOW); KMutexAcquire(&windowManager.mutex); embed->SetEmbedOwner(process); KMutexRelease(&windowManager.mutex); SYSCALL_RETURN(process->handleTable.OpenHandle(embed, 0, KERNEL_OBJECT_EMBEDDED_WINDOW), false); } else if (property == ES_WINDOW_PROPERTY_RESIZE_CLEAR_COLOR) { embed->resizeClearColor = argument1; } else { SYSCALL_RETURN(ES_FATAL_ERROR_OUT_OF_RANGE, true); } SYSCALL_RETURN(ES_SUCCESS, false); } SYSCALL_IMPLEMENT(ES_SYSCALL_WINDOW_REDRAW) { KObject _window(currentProcess, argument0, KERNEL_OBJECT_WINDOW); CHECK_OBJECT(_window); Window *window = (Window *) _window.object; KMutexAcquire(&windowManager.mutex); window->Update(nullptr, true); GraphicsUpdateScreen(); KMutexRelease(&windowManager.mutex); SYSCALL_RETURN(ES_SUCCESS, false); } SYSCALL_IMPLEMENT(ES_SYSCALL_WINDOW_SET_BITS) { KObject _window(currentProcess, argument0, KERNEL_OBJECT_WINDOW | KERNEL_OBJECT_EMBEDDED_WINDOW); CHECK_OBJECT(_window); EsRectangle region; SYSCALL_READ(®ion, argument1, sizeof(EsRectangle)); if (!ES_RECT_VALID(region)) { SYSCALL_RETURN(ES_SUCCESS, false); } if (region.l < 0 || region.r > (int32_t) graphics.width * 2 || region.t < 0 || region.b > (int32_t) graphics.height * 2 || region.l >= region.r || region.t >= region.b) { SYSCALL_RETURN(ES_SUCCESS, false); } bool isEmbed = _window.type == KERNEL_OBJECT_EMBEDDED_WINDOW; Window *window = isEmbed ? ((EmbeddedWindow *) _window.object)->container : ((Window *) _window.object); if (!window || (isEmbed && currentProcess != ((EmbeddedWindow *) _window.object)->owner)) { SYSCALL_RETURN(ES_SUCCESS, false); } Surface *surface = &window->surface; EsRectangle insets = window->embedInsets; if (isEmbed) { region = Translate(region, insets.l, insets.t); } if (argument3 == WINDOW_SET_BITS_SCROLL_VERTICAL || argument3 == WINDOW_SET_BITS_SCROLL_HORIZONTAL) { ptrdiff_t scrollDelta = argument2; bool scrollVertical = argument3 == WINDOW_SET_BITS_SCROLL_VERTICAL; if (scrollVertical) { if (scrollDelta < 0) region.b += scrollDelta; else region.t += scrollDelta; } else { if (scrollDelta < 0) region.r += scrollDelta; else region.l += scrollDelta; } KMutexAcquire(&windowManager.mutex); if (window->closed || region.l < 0 || region.r > (int32_t) surface->width || region.t < 0 || region.b > (int32_t) surface->height || region.l >= region.r || region.t >= region.b) { } else { surface->Scroll(region, scrollDelta, scrollVertical); window->Update(®ion, true); window->queuedScrollUpdate = true; // Don't update the screen until the rest of the window is painted. } KMutexRelease(&windowManager.mutex); } else { bool skipUpdate = false; SYSCALL_BUFFER(argument2, Width(region) * Height(region) * 4, 1, false); KMutexAcquire(&windowManager.mutex); bool resizeQueued = false; if (argument3 == WINDOW_SET_BITS_AFTER_RESIZE && windowManager.resizeWindow == window) { if (isEmbed) windowManager.resizeReceivedBitsFromEmbed = true; else windowManager.resizeReceivedBitsFromContainer = true; if (windowManager.resizeReceivedBitsFromContainer && windowManager.resizeReceivedBitsFromEmbed) { // Resize complete. resizeQueued = windowManager.resizeQueued; windowManager.resizeQueued = false; windowManager.resizeWindow = nullptr; windowManager.resizeSlow = KGetTimeInMs() - windowManager.resizeStartTimeStampMs >= RESIZE_SLOW_THRESHOLD || windowManager.inspectorWindowCount /* HACK anti-flicker logic interfers with the inspector's logging */; #if 0 EsPrint("Resize complete in %dms%z.\n", KGetTimeInMs() - windowManager.resizeStartTimeStampMs, windowManager.resizeSlow ? " (slow)" : ""); #endif } } if (window->closed) { skipUpdate = true; } else { uintptr_t stride = Width(region) * 4; EsRectangle clippedRegion = EsRectangleIntersection(region, ES_RECT_2S(surface->width, surface->height)); if (argument3 != WINDOW_SET_BITS_AFTER_RESIZE) { skipUpdate = window->UpdateDirect((K_USER_BUFFER uint32_t *) argument2, stride, clippedRegion); } #define SET_BITS_REGION(...) { \ EsRectangle subRegion = EsRectangleIntersection(clippedRegion, ES_RECT_4(__VA_ARGS__)); \ if (ES_RECT_VALID(subRegion)) { surface->SetBits((K_USER_BUFFER const uint8_t *) argument2 \ + stride * (subRegion.t - clippedRegion.t) + 4 * (subRegion.l - clippedRegion.l), stride, subRegion); } } if (window->style == ES_WINDOW_CONTAINER && !isEmbed) { SET_BITS_REGION(0, window->width, 0, insets.t); SET_BITS_REGION(0, insets.l, insets.t, window->height - insets.b); SET_BITS_REGION(window->width - insets.r, window->width, insets.t, window->height - insets.b); SET_BITS_REGION(0, window->width, window->height - insets.b, window->height); } else if (window->style == ES_WINDOW_CONTAINER && isEmbed) { SET_BITS_REGION(insets.l, window->width - insets.r, insets.t, window->height - insets.b); } else { SET_BITS_REGION(0, window->width, 0, window->height); } } window->Update(®ion, !skipUpdate); if (!skipUpdate || window->queuedScrollUpdate) { window->queuedScrollUpdate = false; GraphicsUpdateScreen(); } if (resizeQueued) { window->Move(windowManager.resizeQueuedRectangle, ES_WINDOW_MOVE_DYNAMIC); } KMutexRelease(&windowManager.mutex); } SYSCALL_RETURN(ES_SUCCESS, false); } SYSCALL_IMPLEMENT(ES_SYSCALL_EVENT_CREATE) { KEvent *event = (KEvent *) EsHeapAllocate(sizeof(KEvent), true, K_FIXED); if (!event) SYSCALL_RETURN(ES_ERROR_INSUFFICIENT_RESOURCES, false); event->handles = 1; event->autoReset = argument0; SYSCALL_RETURN(currentProcess->handleTable.OpenHandle(event, 0, KERNEL_OBJECT_EVENT), false); } SYSCALL_IMPLEMENT(ES_SYSCALL_HANDLE_CLOSE) { if (!currentProcess->handleTable.CloseHandle(argument0)) { SYSCALL_RETURN(ES_FATAL_ERROR_INVALID_HANDLE, true); } SYSCALL_RETURN(ES_SUCCESS, false); } SYSCALL_IMPLEMENT(ES_SYSCALL_THREAD_TERMINATE) { bool self = false; { KObject _thread(currentProcess, argument0, KERNEL_OBJECT_THREAD); CHECK_OBJECT(_thread); Thread *thread = (Thread *) _thread.object; if (thread == currentThread) self = true; else scheduler.TerminateThread(thread); } if (self) scheduler.TerminateThread(currentThread); SYSCALL_RETURN(ES_SUCCESS, false); } SYSCALL_IMPLEMENT(ES_SYSCALL_PROCESS_TERMINATE) { // TODO Prevent the termination of the kernel/desktop. bool self = false; { KObject _process(currentProcess, argument0, KERNEL_OBJECT_PROCESS); CHECK_OBJECT(_process); Process *process = (Process *) _process.object; if (process == currentProcess) self = true; else scheduler.TerminateProcess(process, argument1); } if (self) scheduler.TerminateProcess(currentProcess, argument1); SYSCALL_RETURN(ES_SUCCESS, false); } SYSCALL_IMPLEMENT(ES_SYSCALL_THREAD_CREATE) { EsThreadInformation thread; EsMemoryZero(&thread, sizeof(EsThreadInformation)); Thread *threadObject = scheduler.SpawnThread("Syscall", argument0, argument3, SPAWN_THREAD_USERLAND, currentProcess, argument1); if (!threadObject) { SYSCALL_RETURN(ES_ERROR_INSUFFICIENT_RESOURCES, false); } thread.tid = threadObject->id; thread.handle = currentProcess->handleTable.OpenHandle(threadObject, 0, KERNEL_OBJECT_THREAD); SYSCALL_WRITE(argument2, &thread, sizeof(EsThreadInformation)); SYSCALL_RETURN(ES_SUCCESS, false); } SYSCALL_IMPLEMENT(ES_SYSCALL_MEMORY_OPEN) { if (argument0 > ES_SHARED_MEMORY_MAXIMUM_SIZE) SYSCALL_RETURN(ES_FATAL_ERROR_OUT_OF_RANGE, true); if (argument1 && !argument2) SYSCALL_RETURN(ES_FATAL_ERROR_INVALID_BUFFER, true); if (argument2 > ES_SHARED_MEMORY_NAME_MAX_LENGTH) SYSCALL_RETURN(ES_FATAL_ERROR_OUT_OF_RANGE, true); char *name; if (argument2 > SYSCALL_BUFFER_LIMIT) SYSCALL_RETURN(ES_FATAL_ERROR_INVALID_BUFFER, true); SYSCALL_READ_HEAP(name, argument1, argument2); MMSharedRegion *region = MMSharedOpenRegion(name, argument2, argument0, argument3); if (!region) SYSCALL_RETURN(ES_INVALID_HANDLE, false); SYSCALL_RETURN(currentProcess->handleTable.OpenHandle(region, 0, KERNEL_OBJECT_SHMEM), false); } SYSCALL_IMPLEMENT(ES_SYSCALL_MEMORY_MAP_OBJECT) { KObject object(currentProcess, argument0, KERNEL_OBJECT_SHMEM | KERNEL_OBJECT_NODE); CHECK_OBJECT(object); if (object.type == KERNEL_OBJECT_SHMEM) { // TODO Access permissions and modes. MMSharedRegion *region = (MMSharedRegion *) object.object; if (argument2 == ES_MAP_OBJECT_ALL) { argument2 = region->sizeBytes; } uintptr_t address = (uintptr_t) MMMapShared(currentVMM, region, argument1, argument2, MM_REGION_USER); SYSCALL_RETURN(address, false); } else if (object.type == KERNEL_OBJECT_NODE) { KNode *file = (KNode *) object.object; if (file->directoryEntry->type != ES_NODE_FILE) SYSCALL_RETURN(ES_FATAL_ERROR_INCORRECT_NODE_TYPE, true); if (argument3 == ES_MAP_OBJECT_READ_WRITE) { if (!(object.flags & (ES_FILE_WRITE_SHARED | ES_FILE_WRITE))) { SYSCALL_RETURN(ES_FATAL_ERROR_INCORRECT_FILE_ACCESS, true); } } else { if (!(object.flags & (ES_FILE_READ | ES_FILE_READ_SHARED))) { SYSCALL_RETURN(ES_FATAL_ERROR_INCORRECT_FILE_ACCESS, true); } } if (argument2 == ES_MAP_OBJECT_ALL) { argument2 = file->directoryEntry->totalSize; } uintptr_t address = (uintptr_t) MMMapFile(currentVMM, (FSFile *) file, argument1, argument2, argument3, nullptr, 0, MM_REGION_USER); SYSCALL_RETURN(address, false); } KernelPanic("ES_SYSCALL_MEMORY_MAP_OBJECT - Unhandled case.\n"); SYSCALL_RETURN(0, false); } SYSCALL_IMPLEMENT(ES_SYSCALL_CONSTANT_BUFFER_CREATE) { if (argument2 > SYSCALL_BUFFER_LIMIT) SYSCALL_RETURN(ES_FATAL_ERROR_INVALID_BUFFER, true); SYSCALL_BUFFER(argument0, argument2, 1, false); KObject process(currentProcess, argument1, KERNEL_OBJECT_PROCESS); CHECK_OBJECT(process); SYSCALL_RETURN(MakeConstantBuffer((void *) argument0, argument2, (Process *) process.object), false); } SYSCALL_IMPLEMENT(ES_SYSCALL_HANDLE_SHARE) { KObject share(currentProcess, argument0, KERNEL_OBJECT_SHMEM | KERNEL_OBJECT_CONSTANT_BUFFER | KERNEL_OBJECT_PROCESS | KERNEL_OBJECT_DEVICE | KERNEL_OBJECT_NODE | KERNEL_OBJECT_EVENT | KERNEL_OBJECT_PIPE); CHECK_OBJECT(share); KObject _process(currentProcess, argument1, KERNEL_OBJECT_PROCESS); CHECK_OBJECT(_process); Process *process = (Process *) _process.object; uint32_t sharedFlags = share.flags; if (share.type == KERNEL_OBJECT_SHMEM) { sharedFlags = argument2; // TODO Sort out flags. } else if (share.type == KERNEL_OBJECT_NODE) { sharedFlags = (argument2 & 1) && (share.flags & (ES_FILE_WRITE_SHARED | ES_FILE_WRITE)) ? ES_FILE_READ_SHARED : share.flags; } else if (share.type == KERNEL_OBJECT_PIPE) { // TODO Sort out flags. } if (!OpenHandleToObject(share.object, share.type, sharedFlags)) { SYSCALL_RETURN(ES_ERROR_PERMISSION_NOT_GRANTED, false); } else { SYSCALL_RETURN(process->handleTable.OpenHandle(share.object, sharedFlags, share.type), false); } } SYSCALL_IMPLEMENT(ES_SYSCALL_VOLUME_GET_INFORMATION) { if (~currentProcess->permissions & ES_PERMISSION_GET_VOLUME_INFORMATION) { SYSCALL_RETURN(0, false); } KObject object(currentProcess, argument0, KERNEL_OBJECT_NODE); CHECK_OBJECT(object); KNode *node = (KNode *) object.object; KFileSystem *fileSystem = node->fileSystem; EsVolumeInformation information; EsMemoryZero(&information, sizeof(EsVolumeInformation)); EsMemoryCopy(information.label, fileSystem->name, sizeof(fileSystem->name)); information.labelBytes = fileSystem->nameBytes; information.driveType = fileSystem->block->driveType; information.spaceUsed = fileSystem->spaceUsed; information.spaceTotal = fileSystem->spaceTotal; information.id = fileSystem->objectID; information.flags = fileSystem->write ? ES_FLAGS_DEFAULT : ES_VOLUME_READ_ONLY; SYSCALL_WRITE(argument1, &information, sizeof(EsVolumeInformation)); SYSCALL_RETURN(ES_SUCCESS, false); } SYSCALL_IMPLEMENT(ES_SYSCALL_NODE_OPEN) { size_t pathLength = (size_t) argument1; uint64_t flags = (uint64_t) argument2; flags &= ~_ES_NODE_FROM_WRITE_EXCLUSIVE | _ES_NODE_NO_WRITE_BASE; bool needWritePermission = flags & (ES_FILE_WRITE | ES_FILE_WRITE_SHARED | _ES_NODE_DIRECTORY_WRITE); char *path; if (argument1 > K_MAX_PATH) SYSCALL_RETURN(ES_FATAL_ERROR_OUT_OF_RANGE, true); SYSCALL_READ_HEAP(path, argument0, argument1); _EsNodeInformation information; SYSCALL_READ(&information, argument3, sizeof(_EsNodeInformation)); KNode *directory = nullptr; KObject _directory(currentProcess, information.handle, KERNEL_OBJECT_NODE); CHECK_OBJECT(_directory); directory = (KNode *) _directory.object; if (directory->directoryEntry->type != ES_NODE_DIRECTORY) { SYSCALL_RETURN(ES_FATAL_ERROR_INCORRECT_NODE_TYPE, true); } if ((~_directory.flags & _ES_NODE_DIRECTORY_WRITE) && needWritePermission) { SYSCALL_RETURN(ES_ERROR_PERMISSION_NOT_GRANTED, false); } if (~_directory.flags & _ES_NODE_DIRECTORY_WRITE) { flags |= _ES_NODE_NO_WRITE_BASE; } KNodeInformation _information = FSNodeOpen(path, pathLength, flags, directory); if (!_information.node) { SYSCALL_RETURN(_information.error, false); } if (flags & ES_FILE_WRITE) { // Mark this handle as being the exclusive writer for this file. // This way, when the handle is used, OpenHandleToObject succeeds. // The exclusive writer flag will only be removed from the file where countWrite drops to zero. flags |= _ES_NODE_FROM_WRITE_EXCLUSIVE; } EsMemoryZero(&information, sizeof(_EsNodeInformation)); information.type = _information.node->directoryEntry->type; information.fileSize = _information.node->directoryEntry->totalSize; information.directoryChildren = _information.node->directoryEntry->directoryChildren; information.handle = currentProcess->handleTable.OpenHandle(_information.node, flags, KERNEL_OBJECT_NODE); SYSCALL_WRITE(argument3, &information, sizeof(_EsNodeInformation)); SYSCALL_RETURN(ES_SUCCESS, false); } SYSCALL_IMPLEMENT(ES_SYSCALL_NODE_DELETE) { KObject object(currentProcess, argument0, KERNEL_OBJECT_NODE); CHECK_OBJECT(object); KNode *node = (KNode *) object.object; if (object.flags & _ES_NODE_NO_WRITE_BASE) { SYSCALL_RETURN(ES_ERROR_PERMISSION_NOT_GRANTED, false); } if (node->directoryEntry->type == ES_NODE_FILE && (~object.flags & ES_FILE_WRITE)) { SYSCALL_RETURN(ES_FATAL_ERROR_INCORRECT_FILE_ACCESS, true); } if (node->directoryEntry->type == ES_NODE_DIRECTORY || node->directoryEntry->type == ES_NODE_FILE) { SYSCALL_RETURN(FSNodeDelete(node), false); } else { SYSCALL_RETURN(ES_FATAL_ERROR_INCORRECT_NODE_TYPE, true); } } SYSCALL_IMPLEMENT(ES_SYSCALL_NODE_MOVE) { KObject object(currentProcess, argument0, KERNEL_OBJECT_NODE); CHECK_OBJECT(object); KNode *file = (KNode *) object.object; KObject object2(currentProcess, argument1, KERNEL_OBJECT_NODE | KERNEL_OBJECT_NONE); CHECK_OBJECT(object2); KNode *directory = (KNode *) object2.object; char *newPath; if (argument3 > SYSCALL_BUFFER_LIMIT) SYSCALL_RETURN(ES_FATAL_ERROR_INVALID_BUFFER, true); SYSCALL_READ_HEAP(newPath, argument2, argument3); SYSCALL_RETURN(FSNodeMove(file, directory, newPath, (size_t) argument3), false); } SYSCALL_IMPLEMENT(ES_SYSCALL_FILE_READ_SYNC) { if (!argument2) SYSCALL_RETURN(0, false); KObject object(currentProcess, argument0, KERNEL_OBJECT_NODE); CHECK_OBJECT(object); KNode *file = (KNode *) object.object; if (file->directoryEntry->type != ES_NODE_FILE) SYSCALL_RETURN(ES_FATAL_ERROR_INCORRECT_NODE_TYPE, true); SYSCALL_BUFFER(argument3, argument2, 1, false); size_t result = FSFileReadSync(file, (void *) argument3, argument1, argument2, (_region1->flags & MM_REGION_FILE) ? FS_FILE_ACCESS_USER_BUFFER_MAPPED : 0); SYSCALL_RETURN(result, false); } SYSCALL_IMPLEMENT(ES_SYSCALL_FILE_WRITE_SYNC) { if (!argument2) SYSCALL_RETURN(0, false); KObject object(currentProcess, argument0, KERNEL_OBJECT_NODE); CHECK_OBJECT(object); KNode *file = (KNode *) object.object; if (file->directoryEntry->type != ES_NODE_FILE) SYSCALL_RETURN(ES_FATAL_ERROR_INCORRECT_NODE_TYPE, true); SYSCALL_BUFFER(argument3, argument2, 1, true /* write */); if (object.flags & (ES_FILE_WRITE_SHARED | ES_FILE_WRITE)) { size_t result = FSFileWriteSync(file, (void *) argument3, argument1, argument2, (_region1->flags & MM_REGION_FILE) ? FS_FILE_ACCESS_USER_BUFFER_MAPPED : 0); SYSCALL_RETURN(result, false); } else { SYSCALL_RETURN(ES_FATAL_ERROR_INCORRECT_FILE_ACCESS, true); } } SYSCALL_IMPLEMENT(ES_SYSCALL_FILE_GET_SIZE) { KObject object(currentProcess, argument0, KERNEL_OBJECT_NODE); CHECK_OBJECT(object); KNode *file = (KNode *) object.object; if (file->directoryEntry->type != ES_NODE_FILE) SYSCALL_RETURN(ES_FATAL_ERROR_INCORRECT_NODE_TYPE, true); SYSCALL_RETURN(file->directoryEntry->totalSize, false); } SYSCALL_IMPLEMENT(ES_SYSCALL_FILE_RESIZE) { KObject object(currentProcess, argument0, KERNEL_OBJECT_NODE); CHECK_OBJECT(object); KNode *file = (KNode *) object.object; if (file->directoryEntry->type != ES_NODE_FILE) SYSCALL_RETURN(ES_FATAL_ERROR_INCORRECT_NODE_TYPE, true); if (object.flags & (ES_FILE_WRITE_SHARED | ES_FILE_WRITE)) { SYSCALL_RETURN(FSFileResize(file, argument1), false); } else { SYSCALL_RETURN(ES_FATAL_ERROR_INCORRECT_FILE_ACCESS, true); } } SYSCALL_IMPLEMENT(ES_SYSCALL_EVENT_SET) { KObject _event(currentProcess, argument0, KERNEL_OBJECT_EVENT); CHECK_OBJECT(_event); KEvent *event = (KEvent *) _event.object; KEventSet(event, false, true); SYSCALL_RETURN(ES_SUCCESS, false); } SYSCALL_IMPLEMENT(ES_SYSCALL_EVENT_RESET) { KObject _event(currentProcess, argument0, KERNEL_OBJECT_EVENT); CHECK_OBJECT(_event); KEvent *event = (KEvent *) _event.object; KEventReset(event); SYSCALL_RETURN(ES_SUCCESS, false); } SYSCALL_IMPLEMENT(ES_SYSCALL_SLEEP) { KTimer timer = {}; KTimerSet(&timer, (argument0 << 32) | argument1); currentThread->terminatableState = THREAD_USER_BLOCK_REQUEST; KEventWait(&timer.event, ES_WAIT_NO_TIMEOUT); currentThread->terminatableState = THREAD_IN_SYSCALL; KTimerRemove(&timer); SYSCALL_RETURN(ES_SUCCESS, false); } SYSCALL_IMPLEMENT(ES_SYSCALL_WAIT) { if (argument1 >= ES_MAX_WAIT_COUNT - 1 /* leave room for timeout timer */) { SYSCALL_RETURN(ES_FATAL_ERROR_OUT_OF_RANGE, true); } EsHandle handles[ES_MAX_WAIT_COUNT]; SYSCALL_READ(handles, argument0, argument1 * sizeof(EsHandle)); KEvent *events[ES_MAX_WAIT_COUNT]; KObject _objects[ES_MAX_WAIT_COUNT] = {}; for (uintptr_t i = 0; i < argument1; i++) { _objects[i].Initialise(¤tProcess->handleTable, handles[i], KERNEL_OBJECT_PROCESS | KERNEL_OBJECT_THREAD | KERNEL_OBJECT_EVENT | KERNEL_OBJECT_EVENT_SINK); CHECK_OBJECT(_objects[i]); void *object = _objects[i].object; switch (_objects[i].type) { case KERNEL_OBJECT_PROCESS: { events[i] = &((Process *) object)->killedEvent; } break; case KERNEL_OBJECT_THREAD: { events[i] = &((Thread *) object)->killedEvent; } break; case KERNEL_OBJECT_EVENT_SINK: { events[i] = &((EventSink *) object)->available; } break; case KERNEL_OBJECT_EVENT: { events[i] = (KEvent *) object; } break; default: { KernelPanic("DoSyscall - Unexpected wait object type %d.\n", _objects[i].type); } break; } } size_t waitObjectCount = argument1; KTimer timer = {}; if (argument2 != (uintptr_t) ES_WAIT_NO_TIMEOUT) { KTimerSet(&timer, argument2); events[waitObjectCount++] = &timer.event; } uintptr_t waitReturnValue; currentThread->terminatableState = THREAD_USER_BLOCK_REQUEST; waitReturnValue = scheduler.WaitEvents(events, waitObjectCount); currentThread->terminatableState = THREAD_IN_SYSCALL; if (waitReturnValue == argument1) { waitReturnValue = ES_ERROR_TIMEOUT_REACHED; } if (argument2 != (uintptr_t) ES_WAIT_NO_TIMEOUT) { KTimerRemove(&timer); } SYSCALL_RETURN(waitReturnValue, false); } SYSCALL_IMPLEMENT(ES_SYSCALL_WINDOW_SET_CURSOR) { KObject _window(currentProcess, argument0, KERNEL_OBJECT_WINDOW | KERNEL_OBJECT_EMBEDDED_WINDOW); CHECK_OBJECT(_window); uint32_t imageWidth = (argument2 >> 16) & 0xFF; uint32_t imageHeight = (argument2 >> 24) & 0xFF; SYSCALL_BUFFER(argument1, imageWidth * imageHeight * 4, 1, false); KMutexAcquire(&windowManager.mutex); Window *window; if (_window.type == KERNEL_OBJECT_EMBEDDED_WINDOW) { EmbeddedWindow *embeddedWindow = (EmbeddedWindow *) _window.object; window = embeddedWindow->container; if (!window || !window->hoveringOverEmbed || embeddedWindow->owner != currentProcess) { KMutexRelease(&windowManager.mutex); SYSCALL_RETURN(ES_SUCCESS, false); } } else { window = (Window *) _window.object; if (window->hoveringOverEmbed) { KMutexRelease(&windowManager.mutex); SYSCALL_RETURN(ES_SUCCESS, false); } } bool changedCursor = false; bool different = argument1 != windowManager.cursorID || windowManager.cursorShadow != !!(argument3 & (1 << 30)); if (!window->closed && different && !windowManager.eyedropping && (windowManager.hoverWindow == window || !windowManager.hoverWindow)) { windowManager.cursorID = argument1; windowManager.cursorImageOffsetX = (int8_t) ((argument2 >> 0) & 0xFF); windowManager.cursorImageOffsetY = (int8_t) ((argument2 >> 8) & 0xFF); windowManager.cursorXOR = argument3 & (1 << 31); windowManager.cursorShadow = argument3 & (1 << 30); int width = imageWidth + CURSOR_SHADOW_OFFSET; int height = imageHeight + CURSOR_SHADOW_OFFSET; if (windowManager.cursorSurface.Resize(width, height) && windowManager.cursorSwap.Resize(width, height) && windowManager.cursorTemporary.Resize(width, height)) { windowManager.cursorSurface.SetBits((K_USER_BUFFER const void *) argument1, argument3 & 0xFFFFFF, ES_RECT_2S(imageWidth, imageHeight)); if (windowManager.cursorShadow) { windowManager.cursorSurface.CreateCursorShadow(&windowManager.cursorTemporary); } } windowManager.changedCursorImage = true; changedCursor = true; } KMutexRelease(&windowManager.mutex); SYSCALL_RETURN(changedCursor, false); } SYSCALL_IMPLEMENT(ES_SYSCALL_WINDOW_MOVE) { KObject _window(currentProcess, argument0, KERNEL_OBJECT_WINDOW); CHECK_OBJECT(_window); Window *window = (Window *) _window.object; bool success = true; EsRectangle rectangle; if (argument1) { SYSCALL_READ(&rectangle, argument1, sizeof(EsRectangle)); } else { EsMemoryZero(&rectangle, sizeof(EsRectangle)); } KMutexAcquire(&windowManager.mutex); if (argument3 & ES_WINDOW_MOVE_HIDDEN) { windowManager.HideWindow(window); } else { window->Move(rectangle, argument3); } if (argument3 & ES_WINDOW_MOVE_UPDATE_SCREEN) { GraphicsUpdateScreen(); } KMutexRelease(&windowManager.mutex); SYSCALL_RETURN(success ? ES_SUCCESS : ES_ERROR_INVALID_DIMENSIONS, false); } SYSCALL_IMPLEMENT(ES_SYSCALL_WINDOW_TRANSFER_PRESS) { KObject _oldWindow(currentProcess, argument0, KERNEL_OBJECT_WINDOW); CHECK_OBJECT(_oldWindow); Window *oldWindow = (Window *) _oldWindow.object; KObject _newWindow(currentProcess, argument1, KERNEL_OBJECT_WINDOW); CHECK_OBJECT(_newWindow); Window *newWindow = (Window *) _newWindow.object; KMutexAcquire(&windowManager.mutex); if (windowManager.pressedWindow == oldWindow) { windowManager.pressedWindow = newWindow; newWindow->hoveringOverEmbed = false; } KMutexRelease(&windowManager.mutex); SYSCALL_RETURN(ES_SUCCESS, false); } SYSCALL_IMPLEMENT(ES_SYSCALL_WINDOW_FIND_BY_POINT) { SYSCALL_PERMISSION(ES_PERMISSION_SCREEN_MODIFY); KMutexAcquire(&windowManager.mutex); Window *window = windowManager.FindWindowAtPosition(argument1 /* x */, argument2 /* y */, argument3 /* exclude */); EsObjectID id = window ? window->id : 0; KMutexRelease(&windowManager.mutex); SYSCALL_WRITE(argument0, &id, sizeof(id)); SYSCALL_RETURN(ES_SUCCESS, false); } SYSCALL_IMPLEMENT(ES_SYSCALL_CURSOR_POSITION_GET) { EsPoint point = ES_POINT(windowManager.cursorX, windowManager.cursorY); SYSCALL_WRITE(argument0, &point, sizeof(EsPoint)); SYSCALL_RETURN(ES_SUCCESS, false); } SYSCALL_IMPLEMENT(ES_SYSCALL_CURSOR_POSITION_SET) { KMutexAcquire(&windowManager.mutex); // Only allow the cursor position to be modified by the process // if it owns the window that is currently being pressed. bool allowed = false; if (windowManager.pressedWindow) { if (windowManager.pressedWindow->embed) { if (windowManager.pressedWindow->embed->owner == currentProcess) { allowed = true; } } if (windowManager.pressedWindow->owner == currentProcess) { allowed = true; } } if (allowed) { // Preseve the precise offset. int32_t offsetX = windowManager.cursorXPrecise - windowManager.cursorX * K_CURSOR_MOVEMENT_SCALE; int32_t offsetY = windowManager.cursorYPrecise - windowManager.cursorY * K_CURSOR_MOVEMENT_SCALE; windowManager.cursorX = argument0; windowManager.cursorY = argument1; windowManager.cursorXPrecise = argument0 * K_CURSOR_MOVEMENT_SCALE + offsetX; windowManager.cursorYPrecise = argument1 * K_CURSOR_MOVEMENT_SCALE + offsetY; } KMutexRelease(&windowManager.mutex); SYSCALL_RETURN(allowed ? ES_SUCCESS : ES_ERROR_PERMISSION_NOT_GRANTED, false); } SYSCALL_IMPLEMENT(ES_SYSCALL_CURSOR_PROPERTIES_SET) { SYSCALL_PERMISSION(ES_PERMISSION_SCREEN_MODIFY); KMutexAcquire(&windowManager.mutex); windowManager.cursorProperties = argument0; KMutexRelease(&windowManager.mutex); SYSCALL_RETURN(ES_SUCCESS, false); } SYSCALL_IMPLEMENT(ES_SYSCALL_GAME_CONTROLLER_STATE_POLL) { EsGameControllerState gameControllers[ES_GAME_CONTROLLER_MAX_COUNT]; size_t gameControllerCount; KMutexAcquire(&windowManager.gameControllersMutex); gameControllerCount = windowManager.gameControllerCount; EsMemoryCopy(gameControllers, windowManager.gameControllers, sizeof(EsGameControllerState) * gameControllerCount); KMutexRelease(&windowManager.gameControllersMutex); SYSCALL_WRITE(argument0, gameControllers, sizeof(EsGameControllerState) * gameControllerCount); SYSCALL_RETURN(gameControllerCount, false); } SYSCALL_IMPLEMENT(ES_SYSCALL_WINDOW_GET_BOUNDS) { KObject _window(currentProcess, argument0, KERNEL_OBJECT_WINDOW | KERNEL_OBJECT_EMBEDDED_WINDOW); CHECK_OBJECT(_window); EsRectangle rectangle; EsMemoryZero(&rectangle, sizeof(EsRectangle)); KMutexAcquire(&windowManager.mutex); if (_window.type == KERNEL_OBJECT_WINDOW) { Window *window = (Window *) _window.object; rectangle.l = window->position.x; rectangle.t = window->position.y; rectangle.r = window->position.x + window->width; rectangle.b = window->position.y + window->height; } else if (_window.type == KERNEL_OBJECT_EMBEDDED_WINDOW) { EmbeddedWindow *embed = (EmbeddedWindow *) _window.object; Window *window = embed->container; if (window) { rectangle.l = window->position.x + window->embedInsets.l; rectangle.t = window->position.y + window->embedInsets.t; rectangle.r = window->position.x + window->width - window->embedInsets.r; rectangle.b = window->position.y + window->height - window->embedInsets.b; } } KMutexRelease(&windowManager.mutex); SYSCALL_WRITE(argument1, &rectangle, sizeof(EsRectangle)); SYSCALL_RETURN(ES_SUCCESS, false); } SYSCALL_IMPLEMENT(ES_SYSCALL_WINDOW_GET_EMBED_KEYBOARD) { KObject _window(currentProcess, argument0, KERNEL_OBJECT_WINDOW); CHECK_OBJECT(_window); _EsMessageWithObject m; EsMemoryZero(&m, sizeof(_EsMessageWithObject)); KMutexAcquire(&windowManager.mutex); Window *window = (Window *) _window.object; m.object = window->apiWindow; EsMemoryCopy(&m.message, &window->lastEmbedKeyboardMessage, sizeof(EsMessage)); window->lastEmbedKeyboardMessage.type = ES_MSG_INVALID; KMutexRelease(&windowManager.mutex); SYSCALL_WRITE(argument1, &m, sizeof(_EsMessageWithObject)); SYSCALL_RETURN(ES_SUCCESS, false); } SYSCALL_IMPLEMENT(ES_SYSCALL_PROCESS_PAUSE) { KObject _process(currentProcess, argument0, KERNEL_OBJECT_PROCESS); CHECK_OBJECT(_process); Process *process = (Process *) _process.object; scheduler.PauseProcess(process, (bool) argument1); SYSCALL_RETURN(ES_SUCCESS, false); } SYSCALL_IMPLEMENT(ES_SYSCALL_PROCESS_CRASH) { KernelLog(LOG_ERROR, "Syscall", "process crash request", "Process crash request, reason %d\n", argument0); SYSCALL_RETURN(argument0, true); } SYSCALL_IMPLEMENT(ES_SYSCALL_MESSAGE_POST) { KObject object(currentProcess, argument2, KERNEL_OBJECT_PROCESS); CHECK_OBJECT(object); void *process = object.object; _EsMessageWithObject message; SYSCALL_READ(&message.message, argument0, sizeof(EsMessage)); message.object = (void *) argument1; if (((Process *) process)->messageQueue.SendMessage(&message)) { SYSCALL_RETURN(ES_SUCCESS, false); } else { SYSCALL_RETURN(ES_ERROR_MESSAGE_QUEUE_FULL, false); } } SYSCALL_IMPLEMENT(ES_SYSCALL_THREAD_GET_ID) { KObject object(currentProcess, argument0, KERNEL_OBJECT_THREAD | KERNEL_OBJECT_PROCESS); CHECK_OBJECT(object); if (object.type == KERNEL_OBJECT_THREAD) { SYSCALL_RETURN(((Thread *) object.object)->id, false); } else if (object.type == KERNEL_OBJECT_PROCESS) { Process *process = (Process *) object.object; #ifdef ENABLE_POSIX_SUBSYSTEM if (currentThread->posixData && currentThread->posixData->forkProcess) { SYSCALL_RETURN(currentThread->posixData->forkProcess->id, false); } #endif SYSCALL_RETURN(process->id, false); } KernelPanic("ES_SYSCALL_THREAD_GET_ID - Unhandled case.\n"); SYSCALL_RETURN(ES_SUCCESS, false); } SYSCALL_IMPLEMENT(ES_SYSCALL_THREAD_STACK_SIZE) { KObject object(currentProcess, argument0, KERNEL_OBJECT_THREAD); CHECK_OBJECT(object); Thread *thread = (Thread *) object.object; SYSCALL_WRITE(argument1, &thread->userStackCommit, sizeof(thread->userStackCommit)); SYSCALL_WRITE(argument2, &thread->userStackReserve, sizeof(thread->userStackReserve)); bool success = true; if (argument3) { MMRegion *region = MMFindAndPinRegion(currentVMM, thread->userStackBase, thread->userStackReserve); KMutexAcquire(¤tVMM->reserveMutex); if (thread->userStackCommit <= argument3 && argument3 <= thread->userStackReserve && !(argument3 & (K_PAGE_BITS - 1)) && region) { #ifdef K_STACK_GROWS_DOWN success = MMCommitRange(currentVMM, region, (thread->userStackReserve - argument3) / K_PAGE_SIZE, argument3 / K_PAGE_SIZE); #else success = MMCommitRange(currentVMM, region, 0, argument3 / K_PAGE_SIZE); #endif } else { success = false; } if (success) thread->userStackCommit = argument3; KMutexRelease(¤tVMM->reserveMutex); if (region) MMUnpinRegion(currentVMM, region); } SYSCALL_RETURN(success ? ES_SUCCESS : ES_ERROR_INSUFFICIENT_RESOURCES, false); } SYSCALL_IMPLEMENT(ES_SYSCALL_DIRECTORY_ENUMERATE) { KObject _node(currentProcess, argument0, KERNEL_OBJECT_NODE); CHECK_OBJECT(_node); KNode *node = (KNode *) _node.object; if (node->directoryEntry->type != ES_NODE_DIRECTORY) SYSCALL_RETURN(ES_FATAL_ERROR_INCORRECT_NODE_TYPE, true); if (argument2 > SYSCALL_BUFFER_LIMIT / sizeof(EsDirectoryChild)) SYSCALL_RETURN(ES_FATAL_ERROR_INVALID_BUFFER, true); SYSCALL_BUFFER(argument1, argument2 * sizeof(EsDirectoryChild), 1, true /* write */); SYSCALL_RETURN(FSDirectoryEnumerateChildren(node, (K_USER_BUFFER EsDirectoryChild *) argument1, argument2), false); } SYSCALL_IMPLEMENT(ES_SYSCALL_FILE_CONTROL) { KObject object(currentProcess, argument0, KERNEL_OBJECT_NODE); CHECK_OBJECT(object); KNode *file = (KNode *) object.object; if (file->directoryEntry->type != ES_NODE_FILE) { SYSCALL_RETURN(ES_FATAL_ERROR_INCORRECT_NODE_TYPE, true); } SYSCALL_RETURN(FSFileControl(file, argument1), false); } SYSCALL_IMPLEMENT(ES_SYSCALL_BATCH) { EsBatchCall *calls; if (argument1 > SYSCALL_BUFFER_LIMIT / sizeof(EsBatchCall)) SYSCALL_RETURN(ES_FATAL_ERROR_INVALID_BUFFER, true); SYSCALL_READ_HEAP(calls, argument0, sizeof(EsBatchCall) * argument1); size_t count = argument1; for (uintptr_t i = 0; i < count; i++) { EsBatchCall call = calls[i]; bool fatal = false; uintptr_t _returnValue = calls[i].returnValue = DoSyscall(call.index, call.argument0, call.argument1, call.argument2, call.argument3, DO_SYSCALL_BATCHED, &fatal, userStackPointer); if (fatal) SYSCALL_RETURN(_returnValue, true); if (calls->stopBatchIfError && ES_CHECK_ERROR(_returnValue)) break; if (currentThread->terminating) break; } SYSCALL_RETURN(ES_SUCCESS, false); } SYSCALL_IMPLEMENT(ES_SYSCALL_CONSTANT_BUFFER_READ) { KObject _buffer(currentProcess, argument0, KERNEL_OBJECT_CONSTANT_BUFFER); CHECK_OBJECT(_buffer); ConstantBuffer *buffer = (ConstantBuffer *) _buffer.object; if (!argument1) SYSCALL_RETURN(buffer->bytes, false); SYSCALL_WRITE(argument1, buffer + 1, buffer->bytes); SYSCALL_RETURN(ES_SUCCESS, false); } SYSCALL_IMPLEMENT(ES_SYSCALL_PROCESS_GET_STATE) { KObject _process(currentProcess, argument0, KERNEL_OBJECT_PROCESS); CHECK_OBJECT(_process); Process *process = (Process *) _process.object; EsProcessState state; EsMemoryZero(&state, sizeof(EsProcessState)); state.crashReason = process->crashReason; state.id = process->id; state.executableState = process->executableState; state.flags = (process->allThreadsTerminated ? ES_PROCESS_STATE_ALL_THREADS_TERMINATED : 0) | (process->terminating ? ES_PROCESS_STATE_TERMINATING : 0) | (process->crashed ? ES_PROCESS_STATE_CRASHED : 0) | (process->messageQueue.pinged ? ES_PROCESS_STATE_PINGED : 0); SYSCALL_WRITE(argument1, &state, sizeof(EsProcessState)); SYSCALL_RETURN(ES_SUCCESS, false); } SYSCALL_IMPLEMENT(ES_SYSCALL_SHUTDOWN) { SYSCALL_PERMISSION(ES_PERMISSION_SHUTDOWN); KThreadCreate("Shutdown", [] (uintptr_t action) { KernelShutdown(action); }, argument0); SYSCALL_RETURN(ES_SUCCESS, false); } SYSCALL_IMPLEMENT(ES_SYSCALL_WINDOW_GET_ID) { KObject _window(currentProcess, argument0, KERNEL_OBJECT_WINDOW | KERNEL_OBJECT_EMBEDDED_WINDOW); CHECK_OBJECT(_window); if (_window.type == KERNEL_OBJECT_WINDOW) { SYSCALL_RETURN(((Window *) _window.object)->id, false); } else { SYSCALL_RETURN(((EmbeddedWindow *) _window.object)->id, false); } } SYSCALL_IMPLEMENT(ES_SYSCALL_YIELD_SCHEDULER) { ProcessorFakeTimerInterrupt(); SYSCALL_RETURN(ES_SUCCESS, false); } SYSCALL_IMPLEMENT(ES_SYSCALL_SYSTEM_TAKE_SNAPSHOT) { SYSCALL_PERMISSION(ES_PERMISSION_TAKE_SYSTEM_SNAPSHOT); int type = argument0; void *buffer = nullptr; size_t bufferSize = 0; EsDefer(EsHeapFree(buffer, 0, K_FIXED)); switch (type) { case ES_SYSTEM_SNAPSHOT_PROCESSES: { KSpinlockAcquire(&scheduler.lock); size_t count = scheduler.allProcesses.count + 8; bufferSize = sizeof(EsSnapshotProcesses) + sizeof(EsSnapshotProcessesItem) * count; KSpinlockRelease(&scheduler.lock); buffer = EsHeapAllocate(bufferSize, true, K_FIXED); if (!buffer) { SYSCALL_RETURN(ES_ERROR_INSUFFICIENT_RESOURCES, false); } EsMemoryZero(buffer, bufferSize); KSpinlockAcquire(&scheduler.lock); if (scheduler.allProcesses.count < count) { count = scheduler.allProcesses.count; } EsSnapshotProcesses *snapshot = (EsSnapshotProcesses *) buffer; LinkedItem *item = scheduler.allProcesses.firstItem; uintptr_t index = 0; while (item && index < count) { Process *process = item->thisItem; if (process->terminating) goto next; { snapshot->processes[index].pid = process->id; snapshot->processes[index].memoryUsage = process->vmm->commit * K_PAGE_SIZE; snapshot->processes[index].cpuTimeSlices = process->cpuTimeSlices; snapshot->processes[index].idleTimeSlices = process->idleTimeSlices; snapshot->processes[index].handleCount = process->handleTable.handleCount; snapshot->processes[index].isKernel = process->type == PROCESS_KERNEL; snapshot->processes[index].nameBytes = EsCStringLength(process->cExecutableName); EsMemoryCopy(snapshot->processes[index].name, process->cExecutableName, snapshot->processes[index].nameBytes); index++; } next:; item = item->nextItem; } snapshot->count = index; bufferSize = sizeof(EsSnapshotProcesses) + sizeof(EsSnapshotProcessesItem) * index; KSpinlockRelease(&scheduler.lock); } break; default: { SYSCALL_RETURN(ES_FATAL_ERROR_OUT_OF_RANGE, true); } break; } SYSCALL_WRITE(argument1, &bufferSize, sizeof(size_t)); SYSCALL_RETURN(MakeConstantBuffer(buffer, bufferSize, currentProcess), false); } SYSCALL_IMPLEMENT(ES_SYSCALL_PROCESS_OPEN) { SYSCALL_PERMISSION(ES_PERMISSION_PROCESS_OPEN); Process *process = scheduler.OpenProcess(argument0); if (process) { SYSCALL_RETURN(currentProcess->handleTable.OpenHandle(process, 0, KERNEL_OBJECT_PROCESS), false); } else { SYSCALL_RETURN(ES_INVALID_HANDLE, false); } } SYSCALL_IMPLEMENT(ES_SYSCALL_PROCESS_SET_TLS) { currentThread->tlsAddress = argument0; // Set this first, otherwise we could get pre-empted and restore without TLS set. ProcessorSetThreadStorage(argument0); SYSCALL_RETURN(ES_SUCCESS, false); } SYSCALL_IMPLEMENT(ES_SYSCALL_PROCESS_GET_TLS) { SYSCALL_RETURN(currentThread->tlsAddress, false); } SYSCALL_IMPLEMENT(ES_SYSCALL_SCREEN_BOUNDS_GET) { EsRectangle rectangle; EsMemoryZero(&rectangle, sizeof(EsRectangle)); rectangle.l = 0; rectangle.t = 0; rectangle.r = graphics.width; rectangle.b = graphics.height; SYSCALL_WRITE(argument1, &rectangle, sizeof(EsRectangle)); SYSCALL_RETURN(ES_SUCCESS, false); } SYSCALL_IMPLEMENT(ES_SYSCALL_SCREEN_WORK_AREA_SET) { SYSCALL_PERMISSION(ES_PERMISSION_SCREEN_MODIFY); EsRectangle rectangle; SYSCALL_READ(&rectangle, argument1, sizeof(EsRectangle)); windowManager.workArea = rectangle; SYSCALL_RETURN(ES_SUCCESS, false); } SYSCALL_IMPLEMENT(ES_SYSCALL_SCREEN_WORK_AREA_GET) { EsRectangle rectangle = windowManager.workArea; SYSCALL_WRITE(argument1, &rectangle, sizeof(EsRectangle)); SYSCALL_RETURN(ES_SUCCESS, false); } SYSCALL_IMPLEMENT(ES_SYSCALL_MESSAGE_DESKTOP) { char *buffer; if (argument1 > DESKTOP_MESSAGE_SIZE_LIMIT) SYSCALL_RETURN(ES_ERROR_INSUFFICIENT_RESOURCES, false); SYSCALL_READ_HEAP(buffer, argument0, argument1); KObject _window(currentProcess, argument2, KERNEL_OBJECT_EMBEDDED_WINDOW | KERNEL_OBJECT_NONE); CHECK_OBJECT(_window); KObject _pipe(currentProcess, argument3, KERNEL_OBJECT_PIPE | KERNEL_OBJECT_NONE); CHECK_OBJECT(_pipe); EmbeddedWindow *window = (EmbeddedWindow *) _window.object; Pipe *pipe = (Pipe *) _pipe.object; if (pipe && (~_pipe.flags & PIPE_WRITER)) { SYSCALL_RETURN(ES_FATAL_ERROR_INCORRECT_FILE_ACCESS, true); } if (!scheduler.shutdown) { if (pipe) { OpenHandleToObject(pipe, KERNEL_OBJECT_PIPE, PIPE_WRITER); } _EsMessageWithObject m = {}; m.message.type = ES_MSG_DESKTOP; m.message.desktop.buffer = MakeConstantBufferForDesktop(buffer, argument1); m.message.desktop.bytes = argument1; m.message.desktop.windowID = window ? window->id : 0; m.message.desktop.processID = currentProcess->id; m.message.desktop.pipe = pipe ? desktopProcess->handleTable.OpenHandle(pipe, PIPE_WRITER, KERNEL_OBJECT_PIPE) : ES_INVALID_HANDLE; if (!m.message.desktop.buffer || !desktopProcess->messageQueue.SendMessage(&m)) { desktopProcess->handleTable.CloseHandle(m.message.desktop.buffer); desktopProcess->handleTable.CloseHandle(m.message.desktop.pipe); } } SYSCALL_RETURN(ES_SUCCESS, false); } #ifdef ENABLE_POSIX_SUBSYSTEM SYSCALL_IMPLEMENT(ES_SYSCALL_POSIX) { SYSCALL_PERMISSION(ES_PERMISSION_POSIX_SUBSYSTEM); _EsPOSIXSyscall syscall; SYSCALL_READ(&syscall, argument0, sizeof(_EsPOSIXSyscall)); if (syscall.index == 2 /* open */ || syscall.index == 59 /* execve */) { KObject node(currentProcess, syscall.arguments[4], KERNEL_OBJECT_NODE); CHECK_OBJECT(node); syscall.arguments[4] = (long) node.object; if (~node.flags & _ES_NODE_DIRECTORY_WRITE) SYSCALL_RETURN(ES_FATAL_ERROR_INVALID_HANDLE, true); long result = POSIX::DoSyscall(syscall, userStackPointer); SYSCALL_RETURN(result, false); } else if (syscall.index == 109 /* setpgid */) { KObject process(currentProcess, syscall.arguments[0], KERNEL_OBJECT_PROCESS); CHECK_OBJECT(process); syscall.arguments[0] = (long) process.object; long result = POSIX::DoSyscall(syscall, userStackPointer); SYSCALL_RETURN(result, false); } else { long result = POSIX::DoSyscall(syscall, userStackPointer); SYSCALL_RETURN(result, false); } } #else SYSCALL_IMPLEMENT(ES_SYSCALL_POSIX) { SYSCALL_RETURN(ES_FATAL_ERROR_UNKNOWN_SYSCALL, true); } #endif SYSCALL_IMPLEMENT(ES_SYSCALL_PROCESS_GET_STATUS) { Process *process; SYSCALL_HANDLE(argument0, KERNEL_OBJECT_PROCESS, process, 1); SYSCALL_RETURN(process->exitStatus, false); } SYSCALL_IMPLEMENT(ES_SYSCALL_PIPE_CREATE) { Pipe *pipe = (Pipe *) EsHeapAllocate(sizeof(Pipe), true, K_PAGED); if (!pipe) SYSCALL_RETURN(ES_ERROR_INSUFFICIENT_RESOURCES, false); pipe->writers = pipe->readers = 1; KEventSet(&pipe->canWrite); EsHandle readEnd = currentProcess->handleTable.OpenHandle(pipe, PIPE_READER, KERNEL_OBJECT_PIPE); EsHandle writeEnd = currentProcess->handleTable.OpenHandle(pipe, PIPE_WRITER, KERNEL_OBJECT_PIPE); SYSCALL_WRITE(argument0, &readEnd, sizeof(EsHandle)); SYSCALL_WRITE(argument1, &writeEnd, sizeof(EsHandle)); SYSCALL_RETURN(ES_SUCCESS, false); } SYSCALL_IMPLEMENT(ES_SYSCALL_PIPE_READ) { if (!argument2) SYSCALL_RETURN(ES_SUCCESS, false); Pipe *pipe; SYSCALL_HANDLE(argument0, KERNEL_OBJECT_PIPE, pipe, 1); SYSCALL_BUFFER(argument1, argument2, 2, false); SYSCALL_RETURN(pipe->Access((void *) argument1, argument2, false, true), false); } SYSCALL_IMPLEMENT(ES_SYSCALL_PIPE_WRITE) { if (!argument2) SYSCALL_RETURN(ES_SUCCESS, false); Pipe *pipe; SYSCALL_HANDLE(argument0, KERNEL_OBJECT_PIPE, pipe, 1); SYSCALL_BUFFER(argument1, argument2, 2, true /* write */); SYSCALL_RETURN(pipe->Access((void *) argument1, argument2, true, true), false); } SYSCALL_IMPLEMENT(ES_SYSCALL_EVENT_SINK_CREATE) { EventSink *sink = (EventSink *) EsHeapAllocate(sizeof(EventSink), true, K_FIXED); if (!sink) { SYSCALL_RETURN(ES_ERROR_INSUFFICIENT_RESOURCES, false); } sink->ignoreDuplicates = argument0; sink->handles = 1; SYSCALL_RETURN(currentProcess->handleTable.OpenHandle(sink, 0, KERNEL_OBJECT_EVENT_SINK), false); } SYSCALL_IMPLEMENT(ES_SYSCALL_EVENT_FORWARD) { KEvent *event; SYSCALL_HANDLE(argument0, KERNEL_OBJECT_EVENT, event, 1); EventSink *sink; SYSCALL_HANDLE(argument1, KERNEL_OBJECT_EVENT_SINK, sink, 2); EsGeneric data = argument2; bool error = false, limitExceeded = false; KMutexAcquire(&eventForwardMutex); if (!event->sinkTable) { event->sinkTable = (EventSinkTable *) EsHeapAllocate(sizeof(EventSinkTable) * ES_MAX_EVENT_FORWARD_COUNT, true, K_FIXED); if (!event->sinkTable) { error = true; } } if (!error) { limitExceeded = true; for (uintptr_t i = 0; i < ES_MAX_EVENT_FORWARD_COUNT; i++) { if (!event->sinkTable[i].sink) { if (!OpenHandleToObject(sink, KERNEL_OBJECT_EVENT_SINK, 0, false)) { error = true; break; } KSpinlockAcquire(&scheduler.lock); event->sinkTable[i].sink = sink; event->sinkTable[i].data = data; KSpinlockRelease(&scheduler.lock); limitExceeded = false; break; } } } KMutexRelease(&eventForwardMutex); if (limitExceeded) { SYSCALL_RETURN(ES_FATAL_ERROR_OUT_OF_RANGE, true); } else if (error) { SYSCALL_RETURN(ES_ERROR_INSUFFICIENT_RESOURCES, false); } else { SYSCALL_RETURN(0, false); } } SYSCALL_IMPLEMENT(ES_SYSCALL_EVENT_SINK_POP) { EventSink *sink; SYSCALL_HANDLE(argument0, KERNEL_OBJECT_EVENT_SINK, sink, 1); bool empty = false, overflow = false; EsGeneric data = {}; KSpinlockAcquire(&sink->spinlock); if (!sink->queueCount) { if (sink->overflow) { overflow = true; sink->overflow = false; } else { empty = true; } } else { data = sink->queue[sink->queuePosition]; sink->queuePosition++; sink->queueCount--; if (sink->queuePosition == ES_MAX_EVENT_SINK_BUFFER_SIZE) { sink->queuePosition = 0; } } if (!sink->queueCount && !sink->overflow) { KEventReset(&sink->available); // KEvent::Reset doesn't take the scheduler lock, so this won't deadlock! } KSpinlockRelease(&sink->spinlock); SYSCALL_WRITE(argument1, &data, sizeof(EsGeneric)); SYSCALL_RETURN(overflow ? ES_ERROR_EVENT_SINK_OVERFLOW : empty ? ES_ERROR_EVENT_NOT_SET : ES_SUCCESS, false); } SYSCALL_IMPLEMENT(ES_SYSCALL_EVENT_SINK_PUSH) { EventSink *sink; SYSCALL_HANDLE(argument0, KERNEL_OBJECT_EVENT_SINK, sink, 1); KSpinlockAcquire(&scheduler.lock); EsError result = sink->Push(argument1); KSpinlockRelease(&scheduler.lock); SYSCALL_RETURN(result, false); } SYSCALL_IMPLEMENT(ES_SYSCALL_DOMAIN_NAME_RESOLVE) { SYSCALL_PERMISSION(ES_PERMISSION_NETWORKING); if (argument1 > ES_DOMAIN_NAME_MAX_LENGTH) { SYSCALL_RETURN(ES_ERROR_BAD_DOMAIN_NAME, false); } char domainName[ES_DOMAIN_NAME_MAX_LENGTH]; SYSCALL_READ(domainName, argument0, argument1); EsAddress address; EsMemoryZero(&address, sizeof(EsAddress)); KEvent completeEvent = {}; NetDomainNameResolveTask task = {}; task.event = &completeEvent; task.name = domainName; task.nameBytes = (size_t) argument1; task.address = &address; task.callback = NetDomainNameResolve; NetTaskBegin(&task); KEventWait(&completeEvent); if (task.error == ES_SUCCESS) { SYSCALL_WRITE(argument2, &address, sizeof(EsAddress)); } SYSCALL_RETURN(task.error, false); } SYSCALL_IMPLEMENT(ES_SYSCALL_ECHO_REQUEST) { SYSCALL_PERMISSION(ES_PERMISSION_NETWORKING); if (argument1 > ES_ECHO_REQUEST_MAX_LENGTH) { SYSCALL_RETURN(ES_FATAL_ERROR_INVALID_BUFFER, true); } uint8_t data[48]; EsMemoryZero(data, sizeof(data)); SYSCALL_READ(data, argument0, argument1); EsAddress address; SYSCALL_READ(&address, argument2, sizeof(EsAddress)); KEvent completeEvent = {}; NetEchoRequestTask task = {}; task.event = &completeEvent; task.address = &address; task.data = data; task.callback = NetEchoRequest; NetTaskBegin(&task); KEventWait(&completeEvent); if (task.error == ES_SUCCESS) { SYSCALL_WRITE(argument0, data, argument1); } SYSCALL_RETURN(task.error, false); } SYSCALL_IMPLEMENT(ES_SYSCALL_CONNECTION_OPEN) { SYSCALL_PERMISSION(ES_PERMISSION_NETWORKING); EsConnection connection; SYSCALL_READ(&connection, argument0, sizeof(EsConnection)); if (connection.sendBufferBytes < 1024 || connection.receiveBufferBytes < 1024) { SYSCALL_RETURN(ES_ERROR_BUFFER_TOO_SMALL, false); } // TODO Upper limit on buffer sizes? NetConnection *netConnection = NetConnectionOpen(&connection.address, connection.sendBufferBytes, connection.receiveBufferBytes, argument1); if (!netConnection) { SYSCALL_RETURN(ES_ERROR_INSUFFICIENT_RESOURCES, false); } connection.sendBuffer = (uint8_t *) MMMapShared(currentVMM, netConnection->bufferRegion, 0, connection.sendBufferBytes + connection.receiveBufferBytes); connection.receiveBuffer = connection.sendBuffer + connection.sendBufferBytes; if (!connection.sendBuffer) { CloseHandleToObject(netConnection, KERNEL_OBJECT_CONNECTION); SYSCALL_RETURN(ES_ERROR_INSUFFICIENT_RESOURCES, false); } connection.error = ES_SUCCESS; connection.handle = currentProcess->handleTable.OpenHandle(netConnection, 0, KERNEL_OBJECT_CONNECTION); SYSCALL_WRITE(argument0, &connection, sizeof(EsConnection)); SYSCALL_RETURN(ES_SUCCESS, false); } SYSCALL_IMPLEMENT(ES_SYSCALL_CONNECTION_POLL) { SYSCALL_BUFFER(argument0, sizeof(EsConnection), 0, true /* write */); EsConnection *connection = (EsConnection *) argument0; NetConnection *netConnection; SYSCALL_HANDLE(argument3, KERNEL_OBJECT_CONNECTION, netConnection, 1); connection->receiveWritePointer = netConnection->receiveWritePointer; connection->sendReadPointer = netConnection->sendReadPointer; connection->open = netConnection->task.step == TCP_STEP_ESTABLISHED; connection->error = netConnection->task.completed ? netConnection->task.error : ES_SUCCESS; SYSCALL_RETURN(ES_SUCCESS, false); } SYSCALL_IMPLEMENT(ES_SYSCALL_CONNECTION_NOTIFY) { NetConnection *netConnection; SYSCALL_HANDLE(argument3, KERNEL_OBJECT_CONNECTION, netConnection, 1); NetConnectionNotify(netConnection, argument1, argument2); SYSCALL_RETURN(ES_SUCCESS, false); } SYSCALL_IMPLEMENT(ES_SYSCALL_DEBUG_COMMAND) { #ifdef DEBUG_BUILD if (argument0 == 1) { ArchResetCPU(); } else if (argument0 == 2) { KernelPanic("Debug command 2.\n"); } else if (argument0 == 3) { extern char kernelLog[]; extern uintptr_t kernelLogPosition; size_t bytes = kernelLogPosition; if (argument2 < bytes) bytes = argument2; EsMemoryCopy((void *) argument1, kernelLog, bytes); SYSCALL_RETURN(bytes, false); } else if (argument0 == 4) { SYSCALL_BUFFER(argument1, 1, 0, false); if (_region0->data.normal.commitPageCount != (argument3 & 0x7FFFFFFFFFFFFFFF)) { KernelPanic("Commit page count mismatch.\n"); } if (_region0->data.normal.commit.Contains(argument2) != (argument3 >> 63)) { KernelPanic("Commit contains mismatch at %x.\n", argument1); } } else if (argument0 == 6) { // SYSCALL_RETURN(DriversDebugGetEnumeratedPCIDevices((EsPCIDevice *) argument1, argument2), false); } else if (argument0 == 7) { EsAssert(!scheduler.threadEventLog); EsThreadEventLogEntry *buffer = (EsThreadEventLogEntry *) EsHeapAllocate(argument0 * sizeof(EsThreadEventLogEntry), false, K_FIXED); scheduler.threadEventLogAllocated = argument0; scheduler.threadEventLogPosition = 0; __sync_synchronize(); scheduler.threadEventLog = buffer; } else if (argument0 == 8) { SYSCALL_RETURN(scheduler.threadEventLogPosition, false); } else if (argument0 == 9) { SYSCALL_WRITE(argument1, scheduler.threadEventLog, scheduler.threadEventLogPosition * sizeof(EsThreadEventLogEntry)); } else if (argument0 == 10) { scheduler.threadEventLogAllocated = 0; // HACK Wait for threads to stop writing... KEvent event = {}; KEventWait(&event, 1000); EsHeapFree(scheduler.threadEventLog, 0, K_FIXED); scheduler.threadEventLog = nullptr; } else if (argument0 == 12) { EsMemoryStatistics statistics; EsMemoryZero(&statistics, sizeof(statistics)); statistics.fixedHeapAllocationCount = K_FIXED->allocationsCount; statistics.fixedHeapTotalSize = K_FIXED->size; statistics.coreHeapAllocationCount = K_CORE->allocationsCount; statistics.coreHeapTotalSize = K_CORE->size; statistics.cachedNodes = fs.bootFileSystem->cachedNodes.count; statistics.cachedDirectoryEntries = fs.bootFileSystem->cachedDirectoryEntries.count; statistics.totalSurfaceBytes = graphics.totalSurfaceBytes; statistics.commitPageable = pmm.commitPageable; statistics.commitFixed = pmm.commitFixed; statistics.commitLimit = pmm.commitLimit; statistics.commitFixedLimit = pmm.commitFixedLimit; statistics.commitRemaining = MM_REMAINING_COMMIT(); statistics.maximumObjectCachePages = MM_OBJECT_CACHE_PAGES_MAXIMUM(); statistics.approximateObjectCacheSize = pmm.approximateTotalObjectCacheBytes; SYSCALL_WRITE(argument1, &statistics, sizeof(statistics)); } #endif SYSCALL_RETURN(ES_SUCCESS, false); } SyscallFunction syscallFunctions[ES_SYSCALL_COUNT + 1] { #include }; #pragma GCC diagnostic pop uintptr_t DoSyscall(EsSyscallType index, uintptr_t argument0, uintptr_t argument1, uintptr_t argument2, uintptr_t argument3, uint64_t flags, bool *fatal, uintptr_t *userStackPointer) { bool batched = flags & DO_SYSCALL_BATCHED; // Interrupts need to be enabled during system calls, // because many of them block on mutexes or events. ProcessorEnableInterrupts(); Thread *currentThread = GetCurrentThread(); Process *currentProcess = currentThread->process; MMSpace *currentVMM = currentProcess->vmm; if (!batched) { if (currentThread->terminating) { // The thread has been terminated. // Yield the scheduler so it can be removed. ProcessorFakeTimerInterrupt(); } if (currentThread->terminatableState != THREAD_TERMINATABLE) { KernelPanic("DoSyscall - Current thread %x was not terminatable (was %d).\n", currentThread, currentThread->terminatableState); } currentThread->terminatableState = THREAD_IN_SYSCALL; } EsError returnValue = ES_FATAL_ERROR_UNKNOWN_SYSCALL; bool fatalError = true; if (index < ES_SYSCALL_COUNT) { SyscallFunction function = syscallFunctions[index]; if (batched && index == ES_SYSCALL_BATCH) { // This could cause a stack overflow, so it's a fatal error. } else if (function) { returnValue = (EsError) function(argument0, argument1, argument2, argument3, currentThread, currentProcess, currentVMM, userStackPointer, &fatalError); } } if (fatal) *fatal = false; if (fatalError) { if (fatal) { *fatal = true; } else { EsCrashReason reason; EsMemoryZero(&reason, sizeof(EsCrashReason)); reason.errorCode = (EsFatalError) returnValue; reason.duringSystemCall = index; KernelLog(LOG_ERROR, "Syscall", "syscall failure", "Process crashed during system call [%x, %x, %x, %x, %x]\n", index, argument0, argument1, argument2, argument3); scheduler.CrashProcess(currentProcess, &reason); } } if (!batched) { currentThread->terminatableState = THREAD_TERMINATABLE; if (currentThread->terminating || currentThread->paused) { // The thread has been terminated or paused. // Yield the scheduler so it can be removed or sent to the paused thread queue. ProcessorFakeTimerInterrupt(); } } return returnValue; } #endif