hide email

This commit is contained in:
nakst 2021-08-13 21:22:26 +01:00
commit f9ebd743cc
358 changed files with 278369 additions and 0 deletions

9
.gitignore vendored Normal file
View File

@ -0,0 +1,9 @@
*.swp
*.swo
tags
.project.gf
bin/
cross/
old/
root/

238
CONTRIBUTING.md Normal file
View File

@ -0,0 +1,238 @@
# Contributing Guidelines
## Map
- `apps/` Builtin applications.
- `boot/` Contains files for booting the operating system.
- `x86/` ...on x86.
- `esfs-stage1.s` Loads `loader.s` from the start of a EsFS volume and passes control to it.
- `esfs-stage2.s` Provides basic read-only EsFS functions for `loader.s`.
- `loader.s` Loads the kernel and passes control to it.
- `mbr.s` Finds and loads a bootable partition.
- `uefi.c` UEFI bootloader first stage.
- `uefi_loader.s` UEFI bootloader second stage.
- `vbe.s` Sets the VBE graphics mode.
- `desktop/` Contains files for Desktop, which provides both the desktop environment, and a layer between applications and the kernel.
- `api.cpp` API initialisation and internal messaging.
- `api.s` API functions that must be implemented in assembly.
- `calculator.cpp` Evaluates basic math expressions.
- `crti.s, ctrn.s` Global constructors and destructors setup.
- `cstdlib.cpp` Provides the system call interface for the POSIX subsystem.
- `desktop.cpp` Desktop. Manages windows and the taskbar.
- `glue.cpp` Entry point for applications using the POSIX subsystem.
- `gui.cpp` The GUI.
- `icons.header` A list of available icons.
- `list_view.cpp` A list view control for the GUI.
- `os.header` The header file containing the API's definitions.
- `prefix.h` The header file prefix for C/C++.
- `renderer.cpp` Provides visual style management and software rendering.
- `renderer2.cpp` Vector graphics rendering.
- `start.cpp` Entry point for all applications.
- `syscall.cpp` Kernel system call wrappers.
- `text.cpp` Text rendering and text-based GUI elements.
- `drivers/` Kernel drivers.
- `acpi.cpp` A layer between the kernel and ACPICA. Also starts application processors on SMP systems.
- `ata.cpp` A ATA/IDE driver.
- `esfs2.cpp` The EssenceFS filesystem driver.
- `fat.cpp` The FAT12 filesystem driver.
- `hda.cpp` Intel HD Audio driver.
- `pci.cpp` Finds devices on the PCI bus.
- `ps2.cpp` A driver for PS/2 keyboard and mice.
- `vbe.cpp` Basic VBE SVGA driver.
- `vga.cpp` Basic VGA driver.
- `kernel/` The kernel and its drivers.
- `audio.cpp` Audio system.
- `config.mtsrc` System configuration. Describes all the modules that should be built, and when they should be loaded.
- `devices.cpp` The device and IO manager.
- `elf.cpp` Parses and loads ELF executables and kernel modules.
- `graphics.cpp` Graphics system. Mostly deprecated.
- `kernel.h` Internal kernel definitions. Includes all other source files in the kernel.
- `main.cpp` Kernel initilisation and shutdown.
- `memory.cpp` Physical, virtual and shared memory management.
- `module.h` Kernel API available to driver modules.
- `objects.cpp` Manages object and handles shared between the kernel and applications.
- `posix.cpp` The (optional) POSIX subsystem.
- `scheduler.cpp` A scheduler, and manager of threads and processes.
- `symbols.cpp` Locating kernel symbols.
- `synchronisation.cpp` Defines synchronisation primitives. Closely linked with the scheduler.
- `syscall.cpp` Defers system calls to other parts of the kernel.
- `terminal.cpp` Kernel debugging and serial output.
- `vfs.cpp` The virtual filesystem.
- `windows.cpp` The window manager. Passes messages from HID devices to applications.
- `x86_64.cpp` Code for the x64 architecture.
- `x86_64.h` Definitions specific to the x64 architecture.
- `x86_64.s` Assembly code for the x64 architecture.
- `ports/` A mess of ported applications. Enter with caution.
- `res/` Resources, such as fonts and visual styles.
- `Fonts` Fonts used by the GUI.
- `Icons` Icon packs used by the GUI.
- `Sample Images` Sample images.
- `Themes` Themes for the user interface..
- `shared/` Shared code between the componenets of the operating system.
- `arena.cpp` Fixed-size allocations.
- `avl_tree.cpp` Balanced binary tree, and maps.
- `bitset.cpp` Managing sparse bitsets.
- `common.cpp` Common functions.
- `format_string.cpp` Locale-dependent text formatter.
- `hash.cpp` Hash functions.
- `heap.cpp` Heap allocator.
- `linked_list.cpp` Doubly-linked lists.
- `stb_ds.h`, `stb_image.h`, `stb_sprintf.h` STB libraries.
- `style_parser.cpp` Parsing visual style specifiers.
- `unicode.cpp` Functions for managing Unicode and UTF-8 strings.
- `vga_font.cpp` A fallback bitmap font.
- `util/` Utilities for building the operating system.
- `build.c` The build system.
- `esfs2.h` A version of EssenceFS for use on linux from the command line.
- `header_generator.c` Language independent header generation.
- `render_svg.c` Renders SVG icons.
## Code Style
Functions and structures names use `PascalCase`.
Variables use `camelCase`, while constants and macros use `SCREAMING_SNAKE_CASE`.
Tabs are `\t`, and are 8 characters in size.
Braces are placed at the end of the line:
if (a > b) {
...
}
Blocks are always surrounded by newlines, and always have braces.
int x = 5;
if (x < 6) {
x++; // Postfix operators are preferred.
}
Exception: If there are lot of short, linked blocks, then they may be written like this-
if (width == DIMENSION_PUSH) { bool a = grid->widths[i] == DIMENSION_PUSH; grid->widths[i] = DIMENSION_PUSH; if (!a) pushH++; }
else if (grid->widths[i] < width && grid->widths[i] != DIMENSION_PUSH) grid->widths[i] = width;
if (height == DIMENSION_PUSH) { bool a = grid->heights[j] == DIMENSION_PUSH; grid->heights[j] = DIMENSION_PUSH; if (!a) pushV++; }
else if (grid->heights[j] < height && grid->heights[j] != DIMENSION_PUSH) grid->heights[j] = height;
Function names are always descriptive, and use prepositions and conjuctions if neccesary.
EsFileReadAll // Symbols provided by the API are prefixed with Es-.
EsDrawSurface
EsMemoryZero
Variable names are usually descriptive, but sometimes shortened names are used for short-lived variables.
EsMessage m = {};
m.type = OS_MESSAGE_MEASURE;
EsMessagePost(&m);
Operators are padded with spaces on either side.
bounds.left = (grid->bounds.left + grid->bounds.right) / 2 - 4;
A space should be placed between a cast and its expression.
int x = (float) y;
Although the operating system is written in C++, most C++ features are avoided.
However, the kernel uses a lot of member functions.
struct Window {
void Update(bool fromUser);
void SetCursorStyle(OSCursorStyle style);
void NeedWMTimer(int hz);
void Destroy();
bool Move(OSRectangle &newBounds);
void ClearImage();
Mutex mutex; // Mutex for drawing to the window. Also needed when moving the window.
Surface *surface;
OSPoint position;
size_t width, height;
...
}
Default arguments often provided as functions grow over time.
There is no limit on function size. However, you should avoid regularly exceeding 120 columns.
Pointers are declared like this: `Type *name;`.
Identifiers may be prefixed with `i`, `e` or `c` to indicate inclusive, exclusive or C-style-zero-terminated-string respectively.
## Kernel and Driver Development
See `module.h` for definitions available to driver developers. See `drivers/fat.cpp` and `drivers/ata.cpp` for simple examples of the driver API.
The following subroutines may be of interest:
void KWaitMicroseconds(uint64_t mcs); // Spin until a given number of microseconds have elapsed.
void EsPrint(const char *format, ...); // Print a message to serial output. (Ctrl+Alt+3 in Qemu)
void KernelPanic(const char *format, ...); // Print a message and halt the OS.
Defer(<statement>); // Defer a statement. Deferred statements will be executed in reverse order when they go out of scope.
size_t EsCStringLength(const char *string); // Get the length of a zero-terminated string.
void EsMemoryCopy(void *destination, const void *source, size_t bytes); // Copy memory forwards.
void EsMemoryZero(void *destination, size_t bytes); // Zero a buffer.
void EsMemoryMove(void *start, void *end, intptr_t amount, bool zeroEmptySpace); // Move a memory region left (amount < 0) or right (amount > 0).
int EsMemoryCopy(const void *a, const void *b, size_t bytes); // Compare two memory regions. Returns 0 if equal.
uint8_t EsSumBytes(uint8_t *source, size_t bytes); // Calculate the 8-bit sum of the bytes in a buffer.
size_t EsStringFormat(char *buffer, size_t bufferLength, const char *format, ...); // Format a string. Returns the length.
uint8_t EsGetRandomByte(); // Get a non-secure random byte.
void EsSort(void *base, size_t count, size_t size, int (*compare)(const void *, const void *, void *), void *callbackArgument); // Sort an array of count items of size size.
uint32_t CalculateCRC32(void *buffer, size_t length); // Calculate the CRC32 checksum of a buffer.
void ProcessorEnableInterrupts(); // Enable interrupts.
void ProcessorDisableInterrupts(); // Disable interrupts. Critical interrupts, such as TLB shootdown IPIs, will remain enabled.
void ProcessorOut<x>(uint16_t port, uint<x>_t value); // Write to an IO port.
uint<x>_t ProcessorIn<x>(uint16_t port); // Read from an IO port.
uint64_t ProcessorReadTimeStamp(); // Read the time stamp in ticks. acpi.timestampTicksPerMs gives the number of ticks per millisecond.
bool KRegisterIRQ(uintptr_t interruptIndex, IRQHandler handler, void *context, const char *cOwner); // Register an IRQ handler. Returns false if the IRQ could not be registered. The handler should return false if its devices was not responsible for the IRQ.
void *MMMapPhysical(MMSpace *space /* = kernelMMSpace */, uintptr_t offset, size_t bytes); // Memory mapped IO.
void *EsHeapAllocate(size_t size, bool zero); // Allocate memory from the heap.
void EsHeapFree(void *pointer); // Free memory from the heap.
bool KThreadCreate(const char *cName, void (*startAddress)(uintptr_t), uintptr_t argument = 0); // Create a thread.
Synchronisation:
void Mutex::Acquire();
void Mutex::Release();
void Mutex::AssertLocked();
void Spinlock::Acquire(); // Disables interrupts.
void Spinlock::Release();
void Spinlock::AssertLocked();
bool Event::Set();
void Event::Reset();
bool Event::Pool();
bool Event::Wait(uintptr_t timeoutMs); // Return false on timeout.
// event.autoReset determines whether the event will automatically reset after Poll() or Wait() return.
Linked lists:
LinkedList<T>::InsertStart(LinkedItem<T> *item); // Insert an item at the start of a linked list.
LinkedList<T>::InsertEnd(LinkedItem<T> *item); // Insert an item at the end of a linked list.
LinkedList<T>::Remove(LinkedItem<T> *item); // Remove an item from a linked list.
struct LinkedList<T> {
LinkedItem<T> *firstItem; // The start of the linked list.
LinkedItem<T> *lastItem; // The end of the linked list.
size_t count; // The number of items in the linked list.
}
struct LinkedItem<T> {
LinkedItem<T> *previousItem; // The previous item in the linked list.
LinkedItem<T> *nextItem; // The next item in the linked list.
LinkedList<T> *list; // The list the item is in.
T *thisItem; // A pointer to the item itself.
}
## Contributors
Put your name here!
- nakst
- Brett R. Toomey
- vtlmks
- Aleksander Birkeland

32
LICENSE.md Normal file
View File

@ -0,0 +1,32 @@
MIT License
Copyright (c) 2017-2021 nakst and other contributors (see CONTRIBUTING.md).
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
This project also include the work of other projects.
All trademarks are registered trademarks of their respective owners.
They licenses may be found in the following files:
- util/nanosvg.h
- shared/stb_image.h, shared/stb_sprintf.h, shared/stb_ds.h and util/stb_truetype.h
- res/Fonts/Hack License.txt, res/Fonts/Inter License.txt
- res/Icons/elementary Icons License.txt
- res/Media/Licenses.txt
- Ported applications have their licenses in their respective folders.
Please tell me if I've forgotten something!

91
README.md Normal file
View File

@ -0,0 +1,91 @@
# **Essence** — An Operating System
![Screenshot of the OS running in an emulator, showing File Manager, and the new tab screen.](https://essence.handmade.network/static/media/image/p-essence-s1.png)
![Screenshot of the OS running in an emulator, showing GCC running under the POSIX subsystem.](https://essence.handmade.network/static/media/image/p-essence-s3.png)
![Screenshot of the OS running in an emulator, showing the shutdown dialog.](https://essence.handmade.network/static/media/image/p-essence-s5.png)
## Support
To support development, you can donate to my Patreon: https://www.patreon.com/nakst.
## Features
Kernel
* Audio mixer.
* Filesystem independent cache manager.
* Memory manager with shared memory, memory-mapped files and multithreaded paging zeroing and working set balancing.
* Networking stack for TCP/IP.
* Scheduler with multiple priority levels and priority inversion.
* On-demand module loading.
* Virtual filesystem.
* Window manager.
* Optional POSIX subsystem, capable of running GCC and some Busybox tools.
Applications
* File Manager
* Text Editor
* IRC Client
* System Monitor
Ports
* Bochs
* GCC and Binutils
* FFmpeg
* Mesa (for software-rendered OpenGL)
* Musl
Drivers
* Power management: ACPI with ACPICA.
* Secondary storage: IDE, AHCI and NVMe.
* Graphics: BGA and SVGA.
* Read-write filesystems: EssenceFS.
* Read-only filesystems: Ext2, FAT, NTFS, ISO9660.
* Audio: HD Audio.
* NICs: 8254x.
* USB: XHCI, bulk storage devices, human interface devices.
Desktop
* Custom user interface library.
* Software vector renderer with complex animation support.
* Tabbed windows.
* Multi-lingual text rendering and layout with FreeType and Harfbuzz.
## Discussion
Visit https://essence.handmade.network/forums.
## Building
**Warning: This software is still in development. Expect bugs.**
### Linux
Download this project's source.
git clone --depth=1 https://gitlab.com/nakst/essence.git/
Start the build system.
./start.sh
Follow the on-screen instructions to build a cross compiler.
Once complete, you can test the operating system in an emulator.
* Please note that by default a checked build is produced, which runs additional checks at runtime (such as heap validation on every allocation and deallocation). This may impact the performance during testing.
* If you have Qemu installed, run `t2` in the build system.
* If you have VirtualBox installed, make a 128MB drive called `vbox.vdi` in the `bin` folder, attach it as a to a virtual machine called "Essence" (choose "Windows 7 64-bit" as the OS), and run `v` in the build system.
## Configuration
From within the build system, run the command `config` to open the configuration editor. Click an option to change its value, and then click the `Save` button. You changes are saved locally, and will not be uploaded by Git. Not all configurations are likely to work; if you don't know what you're doing, it's probably best to stick with the defaults.
## Generating the API header
If you want your project to target Essence, you need to generate the API header for your programming language of choice.
g++ -o bin/build_core util/build_core.c
bin/build_core headers <language> <path-to-output-file>
Currently supported languages are 'c' (also works for C++), 'zig' and 'odin'.
There is currently no documentation for the API; for examples of how to use the API, consult the standard applications in the `apps/` folder of the source tree. A minimal application is demonstrated in `apps/hello.c` and `apps/hello.ini`. By placing your application's `.ini` file in the `apps/` folder, it will be automatically built by the build system.

17
apps/bochs.ini Normal file
View File

@ -0,0 +1,17 @@
[general]
name=Bochs
icon=icon_applications_development
permission_posix_subsystem=1
[build]
custom_compile_command=cp root/Applications/POSIX/bin/bochs root/Applications/Bochs.esx
require=root/Applications/POSIX/bin/bochs
[@file_type]
extension=bochsrc
name=Bochs configuration
icon=icon_applications_development
[@handler]
extension=bochsrc
action=open

114
apps/file_manager.ini Normal file
View File

@ -0,0 +1,114 @@
[general]
name=File Manager
icon=icon_system_file_manager
permission_all_files=1
use_single_process=1
[build]
source=apps/file_manager/main.cpp
[@file_type]
extension=esx
name=Executable
icon=icon_application_default_icon
[@file_type]
extension=ini
name=Configuration file
icon=icon_application_x_desktop
[@file_type]
extension=esx_symbols
name=Debugging data
icon=icon_extension
[@file_type]
extension=exe
name=PC executable
icon=icon_application_x_ms_dos_executable
[@file_type]
extension=png
name=PNG image
icon=icon_image_x_generic
[@file_type]
extension=jpg
name=JPG image
icon=icon_image_x_generic
[@file_type]
extension=ttf
name=TrueType font
icon=icon_font_x_generic
[@file_type]
extension=otf
name=OpenType font
icon=icon_font_x_generic
[@file_type]
extension=a
name=Static library
icon=icon_extension
[@file_type]
extension=dat
name=Database
icon=icon_office_database
[@file_type]
extension=icon_pack
name=Icon pack
icon=icon_package_x_generic
[@file_type]
extension=ekm
name=Kernel module
icon=icon_extension
[@file_type]
extension=o
name=Binary object
icon=icon_extension
[@file_type]
extension=c
name=C source
icon=icon_text_x_csrc
[@file_type]
extension=cpp
name=C++ source
icon=icon_text_x_csrc
[@file_type]
extension=h
name=C header
icon=icon_text_x_csrc
[@file_type]
extension=txt
name=Text file
icon=icon_text
[@file_type]
extension=md
name=Markdown file
icon=icon_text_markdown
[@file_type]
extension=ogg
name=OGG audio
icon=icon_audio_x_generic
[@file_type]
extension=mp4
name=MP4 video
icon=icon_view_list_video_symbolic
[@file_type]
extension=wav
name=Wave audio
icon=icon_audio_x_generic
ES_ICON_AUDIO_X_GENERIC

View File

@ -0,0 +1,170 @@
void CommandRename(Instance *instance, EsElement *, EsCommand *) {
intptr_t index = -1;
for (uintptr_t i = 0; i < instance->listContents.Length(); i++) {
ListEntry *entry = &instance->listContents[i];
if (entry->selected) {
index = i;
break;
}
}
EsAssert(index != -1);
instance->rename.textbox = EsListViewCreateInlineTextbox(instance->list, 0, index, ES_LIST_VIEW_INLINE_TEXTBOX_COPY_EXISTING_TEXT);
instance->rename.index = index;
FolderEntry *entry = instance->listContents[index].entry;
if (entry->extensionOffset != entry->nameBytes) {
// Don't include the file extension in the initial selection.
EsTextboxSetSelection(instance->rename.textbox, 0, 0, 0, entry->extensionOffset - 1);
}
instance->rename.textbox->messageUser = [] (EsElement *element, EsMessage *message) {
if (message->type == ES_MSG_TEXTBOX_EDIT_END) {
Instance *instance = element->instance;
String name = {};
name.text = EsTextboxGetContents((EsTextbox *) element, &name.bytes);
name.allocated = name.bytes;
if (!name.bytes || message->endEdit.rejected) {
StringDestroy(&name);
} else {
FolderEntry *entry = instance->listContents[instance->rename.index].entry;
String oldName = entry->GetName();
BlockingTaskQueue(instance, {
.string = name,
.string2 = StringDuplicate(oldName),
.cDescription = interfaceString_FileManagerRenameTask,
.callback = [] (Instance *instance, Task *task) {
if (StringEquals(task->string, task->string2)) {
task->result = ES_SUCCESS;
} else {
task->result = instance->folder->itemHandler->renameItem(instance->folder, task->string2, task->string);
}
},
.then = [] (Instance *instance, Task *task) {
if (task->result != ES_SUCCESS) {
InstanceReportError(instance, ERROR_RENAME_ITEM, task->result);
} else {
Folder *folder = instance->folder;
size_t newPathBytes;
char *newPath = EsStringAllocateAndFormat(&newPathBytes, "%s%s", STRFMT(instance->folder->path), STRFMT(task->string));
size_t oldPathBytes;
char *oldPath = EsStringAllocateAndFormat(&oldPathBytes, "%s%s", STRFMT(instance->folder->path), STRFMT(task->string2));
FolderPathMoved(instance, { .text = oldPath, .bytes = oldPathBytes }, { .text = newPath, .bytes = newPathBytes });
EsDirectoryChild information = {};
EsPathQueryInformation(newPath, newPathBytes, &information);
uint64_t id = FolderRemoveEntryAndUpdateInstances(folder, STRING(task->string2));
FolderAddEntryAndUpdateInstances(folder, STRING(task->string), &information, instance, false, id);
EsHeapFree(oldPath);
EsHeapFree(newPath);
}
StringDestroy(&task->string);
StringDestroy(&task->string2);
},
});
}
EsElementDestroy(element);
}
return 0;
};
}
void CommandNewFolder(Instance *instance, EsElement *, EsCommand *) {
String name = StringAllocateAndFormat("%z", interfaceString_FileManagerNewFolderName);
BlockingTaskQueue(instance, {
.string = name,
.cDescription = interfaceString_FileManagerNewFolderTask,
.callback = [] (Instance *instance, Task *task) {
task->result = instance->folder->itemHandler->createChildFolder(instance->folder, &task->string, true);
},
.then = [] (Instance *instance, Task *task) {
if (task->result != ES_SUCCESS) {
InstanceReportError(instance, ERROR_NEW_FOLDER, task->result);
} else {
Folder *folder = instance->folder;
EsDirectoryChild information = {};
information.type = ES_NODE_DIRECTORY;
FolderAddEntryAndUpdateInstances(folder, STRING(task->string), &information, instance, false);
CommandRename(instance, nullptr, nullptr);
}
StringDestroy(&task->string);
},
});
}
void InstanceRegisterCommands(Instance *instance) {
uint32_t stableCommandID = 1;
EsCommandRegister(&instance->commandGoBackwards, instance, [] (Instance *instance, EsElement *, EsCommand *) {
EsAssert(instance->pathBackwardHistory.Length());
HistoryEntry entry = instance->pathBackwardHistory.Pop();
StringDestroy(&instance->delayedFocusItem);
instance->delayedFocusItem = entry.focusedItem;
InstanceLoadFolder(instance, entry.path, LOAD_FOLDER_BACK);
}, stableCommandID++, "Backspace|Alt+Left");
EsCommandRegister(&instance->commandGoForwards, instance, [] (Instance *instance, EsElement *, EsCommand *) {
EsAssert(instance->pathForwardHistory.Length());
HistoryEntry entry = instance->pathForwardHistory.Pop();
StringDestroy(&instance->delayedFocusItem);
instance->delayedFocusItem = entry.focusedItem;
InstanceLoadFolder(instance, entry.path, LOAD_FOLDER_FORWARD);
}, stableCommandID++, "Alt+Right");
EsCommandRegister(&instance->commandGoParent, instance, [] (Instance *instance, EsElement *, EsCommand *) {
String parent = PathGetParent(instance->folder->path);
InstanceLoadFolder(instance, StringDuplicate(parent));
}, stableCommandID++, "Alt+Up");
EsCommandRegister(&instance->commandRefresh, instance, [] (Instance *instance, EsElement *, EsCommand *) {
EsMutexAcquire(&loadedFoldersMutex);
FolderRefresh(instance->folder);
EsMutexRelease(&loadedFoldersMutex);
}, stableCommandID++, "F5");
EsCommandRegister(&instance->commandNewFolder, instance, CommandNewFolder, stableCommandID++, "Ctrl+Shift+N");
EsCommandRegister(&instance->commandRename, instance, CommandRename, stableCommandID++, "F2");
EsCommandRegister(&instance->commandViewDetails, instance, [] (Instance *instance, EsElement *, EsCommand *) {
instance->viewSettings.viewType = VIEW_DETAILS;
InstanceRefreshViewType(instance);
InstanceViewSettingsUpdated(instance);
}, stableCommandID++);
EsCommandRegister(&instance->commandViewTiles, instance, [] (Instance *instance, EsElement *, EsCommand *) {
instance->viewSettings.viewType = VIEW_TILES;
InstanceRefreshViewType(instance);
InstanceViewSettingsUpdated(instance);
}, stableCommandID++);
EsCommandRegister(&instance->commandViewThumbnails, instance, [] (Instance *instance, EsElement *, EsCommand *) {
instance->viewSettings.viewType = VIEW_THUMBNAILS;
InstanceRefreshViewType(instance);
InstanceViewSettingsUpdated(instance);
}, stableCommandID++);
EsCommandSetDisabled(&instance->commandViewDetails, false);
EsCommandSetDisabled(&instance->commandViewTiles, false);
EsCommandSetDisabled(&instance->commandViewThumbnails, false);
EsCommandSetCheck(&instance->commandViewDetails, ES_CHECK_CHECKED, false);
}

View File

@ -0,0 +1,597 @@
#define NAMESPACE_HANDLER_FILE_SYSTEM (1)
#define NAMESPACE_HANDLER_DRIVES_PAGE (2)
#define NAMESPACE_HANDLER_ROOT (3) // Acts as a container handler where needed.
#define NAMESPACE_HANDLER_INVALID (4) // For when a folder does not exist.
EsMutex loadedFoldersMutex;
Array<Folder *> loadedFolders;
#define MAXIMUM_FOLDERS_WITH_NO_ATTACHED_INSTANCES (20)
Array<Folder *> foldersWithNoAttachedInstances;
/////////////////////////////////
bool FSDirHandlesPath(String path) {
EsDirectoryChild information;
if (EsPathQueryInformation(STRING(path), &information)) {
return information.type == ES_NODE_DIRECTORY;
} else {
return false;
}
}
EsError FSDirCreateChildFolder(Folder *folder, String *name, bool findUniqueName) {
size_t pathBytes;
char *path;
if (findUniqueName) {
const char *extra = " ";
path = EsStringAllocateAndFormat(&pathBytes, "%s%s%z", STRFMT(folder->path), STRFMT((*name)), extra);
pathBytes = EsPathFindUniqueName(path, pathBytes - EsCStringLength(extra), pathBytes);
StringDestroy(name);
*name = StringAllocateAndFormat("%s", pathBytes - folder->path.bytes, path + folder->path.bytes);
} else {
path = EsStringAllocateAndFormat(&pathBytes, "%s%s", STRFMT(folder->path), STRFMT((*name)));
}
EsError error = EsPathCreate(path, pathBytes, ES_NODE_DIRECTORY, false);
EsHeapFree(path);
return error;
}
EsError FSDirRenameItem(Folder *folder, String oldName, String newName) {
size_t oldPathBytes;
char *oldPath = EsStringAllocateAndFormat(&oldPathBytes, "%s%s", STRFMT(folder->path), STRFMT(oldName));
size_t newPathBytes;
char *newPath = EsStringAllocateAndFormat(&newPathBytes, "%s%s", STRFMT(folder->path), STRFMT(newName));
EsError error = EsPathMove(oldPath, oldPathBytes, newPath, newPathBytes);
EsHeapFree(oldPath);
EsHeapFree(newPath);
return error;
};
EsError FSDirEnumerate(Folder *folder) {
// Get the initial directory children.
// TODO Recurse mode.
EsNodeType type;
if (!EsPathExists(STRING(folder->path), &type) || type != ES_NODE_DIRECTORY) {
return ES_ERROR_FILE_DOES_NOT_EXIST;
}
EsDirectoryChild *buffer = nullptr;
ptrdiff_t _entryCount = EsDirectoryEnumerateChildren(STRING(folder->path), &buffer);
if (ES_CHECK_ERROR(_entryCount)) {
EsHeapFree(buffer);
return (EsError) _entryCount;
}
FolderAddEntries(folder, buffer, _entryCount);
EsHeapFree(buffer);
return ES_SUCCESS;
}
void FSDirGetTotalSize(Folder *folder) {
EsDirectoryChild information;
if (EsPathQueryInformation(STRING(folder->path), &information)) {
folder->spaceUsed = information.fileSize;
folder->spaceTotal = 0;
}
}
String FSDirGetPathForChildFolder(Folder *folder, String item) {
return StringAllocateAndFormat("%s%s/", STRFMT(folder->path), STRFMT(item));
}
/////////////////////////////////
bool DrivesPageHandlesPath(String path) {
return StringEquals(path, StringFromLiteralWithSize(INTERFACE_STRING(FileManagerDrivesPage)));
}
uint32_t DrivesPageGetFileType(String path) {
path = PathRemoveTrailingSlash(path);
String string = PathGetSection(path, PathCountSections(path));
EsVolumeInformation volume;
if (EsMountPointGetVolumeInformation(STRING(string), &volume)) {
if (volume.driveType == ES_DRIVE_TYPE_HDD ) return KNOWN_FILE_TYPE_DRIVE_HDD;
if (volume.driveType == ES_DRIVE_TYPE_SSD ) return KNOWN_FILE_TYPE_DRIVE_SSD;
if (volume.driveType == ES_DRIVE_TYPE_CDROM ) return KNOWN_FILE_TYPE_DRIVE_CDROM;
if (volume.driveType == ES_DRIVE_TYPE_USB_MASS_STORAGE) return KNOWN_FILE_TYPE_DRIVE_USB_MASS_STORAGE;
return KNOWN_FILE_TYPE_DRIVE_HDD;
}
return KNOWN_FILE_TYPE_DRIVE_HDD;
}
void DrivesPageGetTotalSize(Folder *folder) {
EsVolumeInformation information;
if (EsMountPointGetVolumeInformation(STRING(folder->path), &information)) {
folder->spaceUsed = information.spaceUsed;
folder->spaceTotal = information.spaceTotal;
}
}
void DrivesPageGetVisibleName(EsBuffer *buffer, String path) {
path = PathRemoveTrailingSlash(path);
String string = PathGetSection(path, PathCountSections(path));
EsVolumeInformation volume;
if (EsMountPointGetVolumeInformation(STRING(string), &volume)) {
EsBufferFormat(buffer, "%s", volume.labelBytes, volume.label);
return;
}
EsBufferFormat(buffer, "%s", STRFMT(string));
}
EsError DrivesPageEnumerate(Folder *folder) {
EsMutexAcquire(&drivesMutex);
EsDirectoryChild information = {};
information.type = ES_NODE_DIRECTORY;
for (uintptr_t i = 0; i < drives.Length(); i++) {
Drive *drive = &drives[i];
information.fileSize = drive->information.spaceTotal;
FolderAddEntry(folder, drive->prefix, drive->prefixBytes, &information);
}
EsMutexRelease(&drivesMutex);
return ES_SUCCESS;
}
String DrivesPageGetPathForChildFolder(Folder *, String item) {
return StringAllocateAndFormat("%s/", STRFMT(item));
}
void DrivesPageGetDefaultViewSettings(Folder *, FolderViewSettings *settings) {
settings->viewType = VIEW_TILES;
}
/////////////////////////////////
uint32_t NamespaceDefaultGetFileType(String) {
return KNOWN_FILE_TYPE_DIRECTORY;
}
void NamespaceDefaultGetVisibleName(EsBuffer *buffer, String path) {
String string = PathGetSection(path, -1);
EsBufferFormat(buffer, "%s", STRFMT(string));
}
uint32_t NamespaceRootGetFileType(String path) {
if (StringEquals(path, StringFromLiteralWithSize(INTERFACE_STRING(FileManagerDrivesPage)))) {
return KNOWN_FILE_TYPE_DRIVES_PAGE;
} else {
return KNOWN_FILE_TYPE_DIRECTORY;
}
}
void NamespaceRootGetVisibleName(EsBuffer *buffer, String path) {
EsBufferFormat(buffer, "%s", STRFMT(StringSlice(path, 0, path.bytes - 1)));
}
EsError NamespaceInvalidEnumerate(Folder *folder) {
folder->cEmptyMessage = folder->driveRemoved ? interfaceString_FileManagerInvalidDrive : interfaceString_FileManagerInvalidPath;
return ES_SUCCESS;
}
NamespaceHandler namespaceHandlers[] = {
{
.type = NAMESPACE_HANDLER_DRIVES_PAGE,
.rootContainerHandlerType = NAMESPACE_HANDLER_ROOT,
.handlesPath = DrivesPageHandlesPath,
.getFileType = DrivesPageGetFileType,
.getVisibleName = DrivesPageGetVisibleName,
.getTotalSize = DrivesPageGetTotalSize,
.getPathForChildFolder = DrivesPageGetPathForChildFolder,
.getDefaultViewSettings = DrivesPageGetDefaultViewSettings,
.enumerate = DrivesPageEnumerate,
},
{
.type = NAMESPACE_HANDLER_FILE_SYSTEM,
.rootContainerHandlerType = NAMESPACE_HANDLER_DRIVES_PAGE,
.handlesPath = FSDirHandlesPath,
.getFileType = NamespaceDefaultGetFileType,
.getVisibleName = NamespaceDefaultGetVisibleName,
.getTotalSize = FSDirGetTotalSize,
.getPathForChildFolder = FSDirGetPathForChildFolder,
.createChildFolder = FSDirCreateChildFolder,
.renameItem = FSDirRenameItem,
.enumerate = FSDirEnumerate,
},
{
.type = NAMESPACE_HANDLER_ROOT,
.handlesPath = [] (String) { return false; },
.getFileType = NamespaceRootGetFileType,
.getVisibleName = NamespaceRootGetVisibleName,
},
{
.type = NAMESPACE_HANDLER_INVALID,
.rootContainerHandlerType = NAMESPACE_HANDLER_ROOT,
.handlesPath = [] (String) { return true; },
.getFileType = NamespaceDefaultGetFileType,
.getVisibleName = NamespaceDefaultGetVisibleName,
.enumerate = NamespaceInvalidEnumerate,
},
};
void NamespaceFindHandlersForPath(NamespaceHandler **itemHandler, NamespaceHandler **containerHandler, String path) {
String pathContainer = PathGetParent(path);
*itemHandler = *containerHandler = nullptr;
for (uintptr_t i = 0; i < sizeof(namespaceHandlers) / sizeof(namespaceHandlers[0]); i++) {
if (!(*itemHandler) && namespaceHandlers[i].handlesPath(path)) {
*itemHandler = &namespaceHandlers[i];
}
if (pathContainer.bytes && !(*containerHandler) && namespaceHandlers[i].handlesPath(pathContainer)) {
*containerHandler = &namespaceHandlers[i];
}
}
if (!pathContainer.bytes && (*itemHandler)) {
for (uintptr_t i = 0; i < sizeof(namespaceHandlers) / sizeof(namespaceHandlers[0]); i++) {
if (namespaceHandlers[i].type == (*itemHandler)->rootContainerHandlerType) {
*containerHandler = &namespaceHandlers[i];
break;
}
}
}
}
uint32_t NamespaceGetIcon(String path) {
NamespaceHandler *itemHandler, *containerHandler;
NamespaceFindHandlersForPath(&itemHandler, &containerHandler, path);
if (containerHandler) {
return knownFileTypes[containerHandler->getFileType(path)].iconID;
} else {
return ES_ICON_FOLDER;
}
}
void NamespaceGetVisibleName(EsBuffer *buffer, String path) {
NamespaceHandler *itemHandler, *containerHandler;
NamespaceFindHandlersForPath(&itemHandler, &containerHandler, path);
if (containerHandler) {
containerHandler->getVisibleName(buffer, path);
} else {
String string = PathGetSection(path, -1);
EsBufferFormat(buffer, "%s", STRFMT(string));
}
}
/////////////////////////////////
void BookmarkAdd(String path, bool saveConfiguration = true) {
bookmarks.Add(StringDuplicate(path));
if (saveConfiguration) ConfigurationSave();
for (uintptr_t i = 0; i < instances.Length(); i++) {
EsListViewInsert(instances[i]->placesView, PLACES_VIEW_GROUP_BOOKMARKS, bookmarks.Length(), bookmarks.Length());
}
}
void BookmarkRemove(String path) {
for (uintptr_t i = 0; i < bookmarks.Length(); i++) {
if (0 == EsStringCompareRaw(STRING(bookmarks[i]), STRING(path))) {
bookmarks.Delete(i);
for (uintptr_t i = 0; i < instances.Length(); i++) {
EsListViewRemove(instances[i]->placesView, PLACES_VIEW_GROUP_BOOKMARKS, i + 1, i + 1);
}
ConfigurationSave();
return;
}
}
EsAssert(false);
}
/////////////////////////////////
void FolderEntryCloseHandle(Folder *folder, FolderEntry *entry) {
entry->handles--;
if (!entry->handles) {
if (entry->name != entry->internalName) {
EsHeapFree(entry->internalName);
}
EsHeapFree(entry->name);
EsArenaFree(&folder->entryArena, entry);
}
}
void FolderDestroy(Folder *folder) {
for (uintptr_t i = 0; i < folder->entries.slotCount; i++) {
if (folder->entries.slots[i].key.used) {
FolderEntryCloseHandle(folder, (FolderEntry *) folder->entries.slots[i].value);
}
}
EsHeapFree(folder);
}
void FolderDetachInstance(Instance *instance) {
Folder *folder = instance->folder;
if (!folder) return;
instance->folder = nullptr;
folder->attachedInstances.FindAndDeleteSwap(instance, true);
if (!folder->attachedInstances.Length() && !folder->attachingInstances.Length()) {
foldersWithNoAttachedInstances.Add(folder);
if (foldersWithNoAttachedInstances.Length() > MAXIMUM_FOLDERS_WITH_NO_ATTACHED_INSTANCES) {
Folder *leastRecentlyUsed = foldersWithNoAttachedInstances[0];
loadedFolders.FindAndDeleteSwap(leastRecentlyUsed, true);
foldersWithNoAttachedInstances.Delete(0);
FolderDestroy(leastRecentlyUsed);
}
}
}
FolderEntry *FolderAddEntry(Folder *folder, const char *_name, size_t nameBytes, EsDirectoryChild *information, uint64_t id) {
char *name = (char *) EsHeapAllocate(nameBytes + 1, false);
name[nameBytes] = 0;
EsMemoryCopy(name, _name, nameBytes);
FolderEntry *entry = (FolderEntry *) HashTableGetLong(&folder->entries, name, nameBytes);
if (!entry) {
entry = (FolderEntry *) EsArenaAllocate(&folder->entryArena, true);
HashTablePutLong(&folder->entries, name, nameBytes, entry, false);
static uint64_t nextEntryID = 1;
entry->id = id ?: __sync_fetch_and_add(&nextEntryID, 1);
entry->handles = 1;
entry->name = name;
entry->nameBytes = nameBytes;
entry->extensionOffset = PathGetExtension(entry->GetName()).text - name;
entry->internalName = name;
entry->internalNameBytes = nameBytes;
if (folder->itemHandler->getVisibleName != NamespaceDefaultGetVisibleName) {
String path = StringAllocateAndFormat("%s%s", STRFMT(folder->path), STRFMT(entry->GetName()));
uint8_t _buffer[256];
EsBuffer buffer = { .out = _buffer, .bytes = sizeof(_buffer) };
folder->itemHandler->getVisibleName(&buffer, path);
entry->nameBytes = buffer.position;
entry->name = (char *) EsHeapAllocate(buffer.position, false);
EsMemoryCopy(entry->name, _buffer, buffer.position);
StringDestroy(&path);
}
} else {
EsHeapFree(name);
name = entry->name;
entry->previousSize = entry->size;
}
entry->size = information->fileSize;
entry->isFolder = information->type == ES_NODE_DIRECTORY;
entry->sizeUnknown = entry->isFolder && information->directoryChildren == ES_DIRECTORY_CHILDREN_UNKNOWN;
return entry;
}
void FolderAddEntries(Folder *folder, EsDirectoryChild *buffer, size_t entryCount) {
for (uintptr_t i = 0; i < entryCount; i++) {
FolderAddEntry(folder, buffer[i].name, buffer[i].nameBytes, &buffer[i]);
}
}
void FolderAddEntryAndUpdateInstances(Folder *folder, const char *name, size_t nameBytes,
EsDirectoryChild *information, Instance *selectItem, bool mutexAlreadyAcquired, uint64_t id = 0) {
if (!mutexAlreadyAcquired) EsMutexAcquire(&loadedFoldersMutex);
if (folder->containerHandler->getTotalSize) {
folder->containerHandler->getTotalSize(folder);
}
FolderEntry *entry = FolderAddEntry(folder, name, nameBytes, information, id);
ListEntry listEntry = { .entry = entry };
for (uintptr_t i = 0; i < folder->attachedInstances.Length(); i++) {
Instance *instance = folder->attachedInstances[i];
listEntry.selected = instance == selectItem;
InstanceAddSingle(instance, listEntry);
InstanceUpdateStatusString(instance);
}
if (!mutexAlreadyAcquired) EsMutexRelease(&loadedFoldersMutex);
}
uint64_t FolderRemoveEntryAndUpdateInstances(Folder *folder, const char *name, size_t nameBytes) {
EsMutexAcquire(&loadedFoldersMutex);
FolderEntry *entry = (FolderEntry *) HashTableGetLong(&folder->entries, name, nameBytes);
uint64_t id = 0;
if (entry) {
for (uintptr_t i = 0; i < folder->attachedInstances.Length(); i++) {
InstanceRemoveSingle(folder->attachedInstances[i], entry);
}
id = entry->id;
HashTableDeleteLong(&folder->entries, name, nameBytes);
FolderEntryCloseHandle(folder, entry);
}
EsMutexRelease(&loadedFoldersMutex);
return id;
}
void FolderPathMoved(Instance *instance, String oldPath, String newPath) {
_EsPathAnnouncePathMoved(instance, STRING(oldPath), STRING(newPath));
EsMutexAcquire(&loadedFoldersMutex);
for (uintptr_t i = 0; i < loadedFolders.Length(); i++) {
Folder *folder = loadedFolders[i];
if (PathReplacePrefix(&folder->path, oldPath, newPath)) {
for (uintptr_t i = 0; i < folder->attachedInstances.Length(); i++) {
InstanceFolderPathChanged(folder->attachedInstances[i], false);
}
}
}
EsMutexRelease(&loadedFoldersMutex);
for (uintptr_t i = 0; i < bookmarks.Length(); i++) {
PathReplacePrefix(&bookmarks[i], oldPath, newPath);
}
for (uintptr_t i = 0; i < instances.Length(); i++) {
EsListViewInvalidateAll(instances[i]->placesView);
for (uintptr_t j = 0; j < instances[i]->pathBackwardHistory.Length(); j++) {
HistoryEntry *entry = &instances[i]->pathBackwardHistory[j];
PathReplacePrefix(&entry->path, oldPath, newPath);
if (StringStartsWith(oldPath, entry->path) && StringEquals(entry->focusedItem, StringSlice(oldPath, entry->path.bytes, -1))) {
StringDestroy(&entry->focusedItem);
entry->focusedItem = StringDuplicate(StringSlice(newPath, entry->path.bytes, -1));
}
}
for (uintptr_t j = 0; j < instances[i]->pathForwardHistory.Length(); j++) {
HistoryEntry *entry = &instances[i]->pathForwardHistory[j];
PathReplacePrefix(&entry->path, oldPath, newPath);
if (StringStartsWith(oldPath, entry->path) && StringEquals(entry->focusedItem, StringSlice(oldPath, entry->path.bytes, -1))) {
StringDestroy(&entry->focusedItem);
entry->focusedItem = StringDuplicate(StringSlice(newPath, entry->path.bytes, -1));
}
}
}
for (uintptr_t i = 0; i < folderViewSettings.Length(); i++) {
String knownPath = StringFromLiteralWithSize(folderViewSettings[i].path, folderViewSettings[i].pathBytes);
if (knownPath.bytes - oldPath.bytes + newPath.bytes >= sizeof(folderViewSettings[i].path)) {
continue;
}
if (!PathHasPrefix(knownPath, oldPath)) {
continue;
}
String after = StringSlice(knownPath, oldPath.bytes, -1);
folderViewSettings[i].pathBytes = EsStringFormat(folderViewSettings[i].path, sizeof(folderViewSettings[i].path),
"%s%s", STRFMT(newPath), STRFMT(after));
}
ConfigurationSave();
}
EsError FolderAttachInstance(Instance *instance, String path, bool recurse, Folder **newFolder) {
// TODO Don't modify attachedInstances/attachingInstances/loadedFolders without the message mutex!
// And then we can remove loadedFoldersMutex.
// (Called on the blocking task thread.)
Folder *folder = nullptr;
NamespaceHandler *itemHandler = nullptr, *containerHandler = nullptr;
EsError error;
bool driveRemoved = false;
// Check if we've already loaded the folder.
EsMutexAcquire(&loadedFoldersMutex);
for (uintptr_t i = 0; i < loadedFolders.Length(); i++) {
if (StringEquals(loadedFolders[i]->path, path) && loadedFolders[i]->recurse == recurse) {
if (!loadedFolders[i]->refreshing) {
folder = loadedFolders[i];
goto success;
} else {
driveRemoved = loadedFolders[i]->driveRemoved;
}
}
}
EsMutexRelease(&loadedFoldersMutex);
// Find the handler for the path.
NamespaceFindHandlersForPath(&itemHandler, &containerHandler, path);
if (!itemHandler || !containerHandler) {
return ES_ERROR_FILE_DOES_NOT_EXIST;
}
// Load a new folder.
folder = (Folder *) EsHeapAllocate(sizeof(Folder), true);
folder->path = StringDuplicate(path);
folder->recurse = recurse;
folder->itemHandler = itemHandler;
folder->containerHandler = containerHandler;
folder->cEmptyMessage = interfaceString_FileManagerEmptyFolderView;
folder->driveRemoved = driveRemoved;
EsArenaInitialise(&folder->entryArena, 1048576, sizeof(FolderEntry));
// TODO Make this asynchronous for some folder providers, or recursive requests
// (that is, immediately present to the user while streaming data in).
error = itemHandler->enumerate(folder);
folder->driveRemoved = false;
folder->refreshing = false;
if (error != ES_SUCCESS) {
StringDestroy(&folder->path);
EsHeapFree(folder);
return error;
}
if (containerHandler->getTotalSize) {
containerHandler->getTotalSize(folder);
}
EsMutexAcquire(&loadedFoldersMutex);
loadedFolders.Add(folder);
success:;
foldersWithNoAttachedInstances.FindAndDeleteSwap(folder, false);
folder->attachingInstances.Add(instance);
EsMutexRelease(&loadedFoldersMutex);
*newFolder = folder;
return ES_SUCCESS;
}
void FolderRefresh(Folder *folder) {
// EsMutexAssertLocked(&loadedFoldersMutex);
if (folder->refreshing) {
return;
}
folder->refreshing = true;
for (uintptr_t i = 0; i < folder->attachedInstances.Length(); i++) {
InstanceLoadFolder(folder->attachedInstances[i], StringDuplicate(folder->path), LOAD_FOLDER_REFRESH);
}
}

573
apps/file_manager/main.cpp Normal file
View File

@ -0,0 +1,573 @@
#define ES_INSTANCE_TYPE Instance
#include <essence.h>
#include <shared/strings.cpp>
#include <shared/hash_table.cpp>
#include <shared/array.cpp>
#define IMPLEMENTATION
#include <shared/array.cpp>
#undef IMPLEMENTATION
// TODO Possible candidates for moving in the core API:
// - String/paths utils
// - Blocking/non-blocking task systems
// TODO Don't show modals if a folder can't be loaded.
// Instead, show a list view with an error message,
// and disable interactions.
#define SETTINGS_FILE "|Settings:/Default.ini"
#define ERROR_LOAD_FOLDER (1)
#define ERROR_NEW_FOLDER (2)
#define ERROR_RENAME_ITEM (3)
#define PLACES_VIEW_GROUP_BOOKMARKS (0)
#define PLACES_VIEW_GROUP_DRIVES (1)
#define MESSAGE_BLOCKING_TASK_COMPLETE ((EsMessageType) (ES_MSG_USER_START + 1))
#define MESSAGE_NON_BLOCKING_TASK_COMPLETE ((EsMessageType) (ES_MSG_USER_START + 2))
#define VIEW_DETAILS (0)
#define VIEW_TILES (1)
#define VIEW_THUMBNAILS (2)
const char *errorTypeStrings[] = {
interfaceString_FileManagerUnknownError,
interfaceString_FileManagerOpenFolderError,
interfaceString_FileManagerNewFolderError,
interfaceString_FileManagerRenameItemError,
};
#include "string.cpp"
EsListViewColumn folderOutputColumns[] = {
#define COLUMN_NAME (0)
{ INTERFACE_STRING(FileManagerColumnName), ES_LIST_VIEW_COLUMN_HAS_MENU },
#define COLUMN_TYPE (1)
{ INTERFACE_STRING(FileManagerColumnType), ES_LIST_VIEW_COLUMN_HAS_MENU },
#define COLUMN_SIZE (2)
{ INTERFACE_STRING(FileManagerColumnSize), ES_LIST_VIEW_COLUMN_HAS_MENU | ES_LIST_VIEW_COLUMN_RIGHT_ALIGNED },
};
#define LOAD_FOLDER_BACK (1)
#define LOAD_FOLDER_FORWARD (2)
#define LOAD_FOLDER_START (3)
#define LOAD_FOLDER_REFRESH (4)
#define LOAD_FOLDER_NO_FOCUS (1 << 8)
struct FolderEntry {
uint16_t handles;
bool isFolder, sizeUnknown;
uint8_t nameBytes, extensionOffset, internalNameBytes; // 0 -> 256.
char *name, *internalName;
EsFileOffset size, previousSize;
uint64_t id;
inline String GetName() {
return { .text = name, .bytes = nameBytes ?: 256u, .allocated = nameBytes ?: 256u };
}
inline String GetInternalName() {
return { .text = internalName, .bytes = internalNameBytes ?: 256u, .allocated = internalNameBytes ?: 256u };
}
inline String GetExtension() {
uintptr_t offset = extensionOffset ?: 256u;
return { .text = name + offset, .bytes = (nameBytes ?: 256u) - offset };
}
};
struct ListEntry {
FolderEntry *entry;
bool selected;
};
struct Task {
EsGeneric context, context2;
String string, string2;
uint64_t id;
EsError result;
const char *cDescription;
Instance *instance;
void (*callback)(Instance *instance, Task *task);
void (*then)(Instance *instance, Task *task); // Called on the main thread.
};
struct HistoryEntry {
String path;
String focusedItem;
};
struct Drive {
const char *prefix;
size_t prefixBytes;
EsVolumeInformation information;
};
struct FolderViewSettings {
uint16_t sortColumn;
uint8_t viewType;
};
struct FolderViewSettingsEntry {
char path[160];
FolderViewSettings settings;
uint8_t pathBytes;
};
struct Thumbnail {
uint32_t *bits;
uint32_t width, height;
uintptr_t referenceCount;
uint32_t generatingTasksInProgress;
};
struct Instance : EsInstance {
// Interface elements.
EsListView *list;
EsListView *placesView;
EsTextbox *breadcrumbBar;
EsButton *newFolderButton;
EsTextDisplay *status;
union {
struct {
EsTextbox *textbox;
uintptr_t index;
} rename;
};
// Path and history.
String path;
Array<HistoryEntry> pathBackwardHistory;
Array<HistoryEntry> pathForwardHistory;
// Commands.
EsCommand commandGoBackwards, commandGoForwards, commandGoParent;
EsCommand commandNewFolder, commandRename;
EsCommand commandViewDetails, commandViewTiles, commandViewThumbnails;
EsCommand commandRefresh;
// Active folder.
struct Folder *folder;
// Sorted and filtered list contents.
Array<ListEntry> listContents;
size_t selectedItemCount;
EsFileOffset selectedItemsTotalSize;
String delayedFocusItem;
FolderViewSettings viewSettings;
// Blocking task thread.
// Tasks that block the use of the instance,
// but display progress and can be (optionally) cancelled.
// Shows the dialog after some threshold.
#define BLOCKING_TASK_DIALOG_THRESHOLD_MS (100)
Task blockingTask;
volatile bool blockingTaskInProgress, blockingTaskReachedTimeout;
volatile uint32_t blockingTaskID;
};
struct NamespaceHandler {
uint8_t type;
uint8_t rootContainerHandlerType;
bool (*handlesPath)(String path);
uint32_t (*getFileType)(String path);
void (*getVisibleName)(EsBuffer *buffer, String path);
void (*getTotalSize)(Folder *folder); // Possibly called on the blocking task thread.
String (*getPathForChildFolder)(Folder *folder, String item);
void (*getDefaultViewSettings)(Folder *folder, FolderViewSettings *settings);
// Called on the blocking task thread:
EsError (*createChildFolder)(Folder *folder, String *name, bool findUniqueName); // Optional.
EsError (*renameItem)(Folder *folder, String oldName, String name); // Optional.
EsError (*enumerate)(Folder *folder);
};
struct Folder {
HashTable entries;
EsArena entryArena;
Array<Instance *> attachedInstances;
Array<Instance *> attachingInstances;
String path;
bool recurse;
bool refreshing;
bool driveRemoved;
EsFileOffset spaceTotal;
EsFileOffset spaceUsed;
NamespaceHandler *itemHandler, *containerHandler;
const char *cEmptyMessage;
};
void InstanceReportError(struct Instance *instance, int error, EsError code);
bool InstanceLoadFolder(Instance *instance, String path /* takes ownership */, int historyMode = 0);
void InstanceUpdateStatusString(Instance *instance);
void InstanceViewSettingsUpdated(Instance *instance);
void InstanceRefreshViewType(Instance *instance);
void InstanceFolderPathChanged(Instance *instance, bool fromLoadFolder);
void InstanceAddContents(struct Instance *instance, HashTable *newEntries);
void InstanceAddSingle(struct Instance *instance, ListEntry newEntry);
void InstanceRemoveContents(struct Instance *instance);
ListEntry InstanceRemoveSingle(Instance *instance, FolderEntry *folderEntry);
ListEntry *InstanceGetSelectedListEntry(Instance *instance);
void ListItemCreated(EsElement *element, uintptr_t index, bool fromFolderRename);
FolderEntry *FolderAddEntry(Folder *folder, const char *_name, size_t nameBytes, EsDirectoryChild *information, uint64_t id = 0);
void FolderAddEntries(Folder *folder, EsDirectoryChild *buffer, size_t entryCount);
uint32_t NamespaceDefaultGetFileType(String);
Array<Drive> drives;
EsMutex drivesMutex;
Array<Instance *> instances;
Array<String> bookmarks;
#define FOLDER_VIEW_SETTINGS_MAXIMUM_ENTRIES (10000)
Array<FolderViewSettingsEntry> folderViewSettings;
HashStore<uint64_t, Thumbnail> thumbnailCache;
// Non-blocking task thread.
EsHandle nonBlockingTaskWorkAvailable;
EsMutex nonBlockingTaskMutex;
Array<Task *> nonBlockingTasks;
// Styles.
const EsStyle styleFolderView = {
.inherit = ES_STYLE_LIST_VIEW,
.metrics = {
.mask = ES_THEME_METRICS_MINIMUM_WIDTH | ES_THEME_METRICS_PREFERRED_WIDTH,
.preferredWidth = 200,
.minimumWidth = 150,
},
};
const EsStyle styleFolderViewTiled = {
.inherit = ES_STYLE_LIST_VIEW,
.metrics = {
.mask = ES_THEME_METRICS_MINIMUM_WIDTH | ES_THEME_METRICS_PREFERRED_WIDTH | ES_THEME_METRICS_GAP_WRAP | ES_THEME_METRICS_GAP_MINOR,
.preferredWidth = 200,
.minimumWidth = 150,
.gapMinor = 5,
.gapWrap = 5,
},
};
const EsStyle styleFolderItemThumbnail = {
.inherit = ES_STYLE_LIST_ITEM_TILE,
.metrics = {
.mask = ES_THEME_METRICS_PREFERRED_WIDTH | ES_THEME_METRICS_PREFERRED_HEIGHT
| ES_THEME_METRICS_ICON_SIZE | ES_THEME_METRICS_LAYOUT_VERTICAL | ES_THEME_METRICS_INSETS | ES_THEME_METRICS_TEXT_ALIGN,
.insets = ES_RECT_2(5, 10),
.preferredWidth = 170,
.preferredHeight = 150,
.textAlign = ES_TEXT_H_CENTER | ES_TEXT_V_CENTER | ES_TEXT_ELLIPSIS,
.iconSize = 80,
.layoutVertical = true,
},
};
const EsStyle stylePlacesView = {
.inherit = ES_STYLE_LIST_VIEW,
.metrics = {
.mask = ES_THEME_METRICS_MINIMUM_WIDTH | ES_THEME_METRICS_PREFERRED_WIDTH | ES_THEME_METRICS_INSETS
| ES_THEME_METRICS_GAP_WRAP | ES_THEME_METRICS_GAP_MAJOR,
.insets = ES_RECT_1(8),
.preferredWidth = 200,
.minimumWidth = 150,
.gapMajor = 16,
.gapWrap = 16,
},
};
void BlockingTaskThread(EsGeneric _instance) {
Instance *instance = (Instance *) _instance.p;
instance->blockingTask.callback(instance, &instance->blockingTask);
EsMessage m = { MESSAGE_BLOCKING_TASK_COMPLETE };
m.user.context1.p = instance;
m.user.context2.u = instance->blockingTaskID;
EsMessagePost(nullptr, &m);
}
void BlockingTaskComplete(Instance *instance) {
EsAssert(instance->blockingTaskInProgress); // Task should have been in progress.
instance->blockingTaskInProgress = false;
if (instance->blockingTaskReachedTimeout) EsDialogClose(instance->window);
Task *task = &instance->blockingTask;
if (task->then) task->then(instance, task);
}
void BlockingTaskQueue(Instance *instance, Task task) {
EsAssert(!instance->blockingTaskInProgress); // Cannot queue a new blocking task if the previous has not finished.
instance->blockingTask = task;
instance->blockingTaskInProgress = true;
EsThreadInformation thread;
EsThreadCreate(BlockingTaskThread, &thread, instance);
ptrdiff_t result = EsWait(&thread.handle, 1, BLOCKING_TASK_DIALOG_THRESHOLD_MS);
EsHandleClose(thread.handle);
if (result == ES_ERROR_TIMEOUT_REACHED) {
instance->blockingTaskReachedTimeout = true;
EsDialogShowAlert(instance->window, task.cDescription, -1, INTERFACE_STRING(FileManagerOngoingTaskDescription), ES_ICON_TOOLS_TIMER_SYMBOLIC);
// TODO Progress bar; cancelling tasks.
} else {
instance->blockingTaskReachedTimeout = false;
BlockingTaskComplete(instance);
instance->blockingTaskID++; // Prevent the task being completed twice.
}
}
void NonBlockingTaskThread(EsGeneric) {
while (true) {
EsWait(&nonBlockingTaskWorkAvailable, 1, ES_WAIT_NO_TIMEOUT);
while (true) {
EsMutexAcquire(&nonBlockingTaskMutex);
if (!nonBlockingTasks.Length()) {
EsMutexRelease(&nonBlockingTaskMutex);
break;
}
Task *task = nonBlockingTasks[0];
nonBlockingTasks.Delete(0);
EsMutexRelease(&nonBlockingTaskMutex);
task->callback(nullptr, task);
EsMessage m = { MESSAGE_NON_BLOCKING_TASK_COMPLETE };
m.user.context2.p = task;
EsMessagePost(nullptr, &m);
}
}
}
void NonBlockingTaskQueue(Task _task) {
// NOTE We can't store instances in tasks on the non-blocking queue thread,
// because the instances might be destroyed while the task is in progress!
Task *task = (Task *) EsHeapAllocate(sizeof(Task), false);
EsMemoryCopy(task, &_task, sizeof(Task));
EsMutexAcquire(&nonBlockingTaskMutex);
nonBlockingTasks.Add(task);
EsMutexRelease(&nonBlockingTaskMutex);
EsEventSet(nonBlockingTaskWorkAvailable);
}
void NonBlockingTaskComplete(EsMessage *message) {
Task *task = (Task *) message->user.context2.p;
if (task->then) task->then(nullptr, task);
EsHeapFree(task);
}
void ConfigurationSave() {
EsBuffer buffer = {};
buffer.canGrow = true;
EsBufferFormat(&buffer, "[bookmarks]\n");
for (uintptr_t i = 0; i < bookmarks.Length(); i++) {
EsBufferFormat(&buffer, "=%s\n", STRFMT(bookmarks[i]));
}
for (uintptr_t i = 0; i < folderViewSettings.Length(); i++) {
FolderViewSettingsEntry *entry = &folderViewSettings[i];
EsBufferFormat(&buffer, "\n[@folder]\npath=%z\nsort_column=%d\nview_type=%d\n",
entry->path, entry->settings.sortColumn, entry->settings.viewType);
}
EsFileWriteAll(EsLiteral(SETTINGS_FILE), buffer.out, buffer.position);
EsHeapFree(buffer.out);
}
#include "type_database.cpp"
#include "folder.cpp"
#include "commands.cpp"
#include "ui.cpp"
void DriveRemove(const char *prefix, size_t prefixBytes) {
if (!prefixBytes || prefix[0] == '|') return;
EsMutexAcquire(&drivesMutex);
bool found = false;
for (uintptr_t index = 0; index < drives.Length(); index++) {
if (0 == EsStringCompareRaw(prefix, prefixBytes, drives[index].prefix, drives[index].prefixBytes)) {
drives.Delete(index);
found = true;
for (uintptr_t i = 0; i < instances.Length(); i++) {
EsListViewRemove(instances[i]->placesView, PLACES_VIEW_GROUP_DRIVES, index, index);
}
break;
}
}
EsAssert(found);
EsMutexRelease(&drivesMutex);
EsMutexAcquire(&loadedFoldersMutex);
for (uintptr_t i = 0; i < loadedFolders.Length(); i++) {
Folder *folder = loadedFolders[i];
if (folder->itemHandler->type == NAMESPACE_HANDLER_DRIVES_PAGE
|| StringStartsWith(folder->path, StringFromLiteralWithSize(prefix, prefixBytes))) {
folder->driveRemoved = true;
FolderRefresh(folder);
}
}
EsMutexRelease(&loadedFoldersMutex);
}
void DriveAdd(const char *prefix, size_t prefixBytes) {
if (!prefixBytes || prefix[0] == '|') return;
EsMutexAcquire(&drivesMutex);
Drive drive = {};
drive.prefix = prefix;
drive.prefixBytes = prefixBytes;
EsMountPointGetVolumeInformation(prefix, prefixBytes, &drive.information);
drives.Add(drive);
for (uintptr_t i = 0; i < instances.Length(); i++) {
EsListViewInsert(instances[i]->placesView, PLACES_VIEW_GROUP_DRIVES, drives.Length(), drives.Length());
}
EsMutexRelease(&drivesMutex);
for (uintptr_t i = 0; i < instances.Length(); i++) {
if (instances[i]->folder->itemHandler->type == NAMESPACE_HANDLER_DRIVES_PAGE
|| StringStartsWith(instances[i]->folder->path, StringFromLiteralWithSize(prefix, prefixBytes))) {
FolderRefresh(instances[i]->folder);
}
}
}
void LoadSettings() {
EsINIState state = { (char *) EsFileReadAll(EsLiteral(SETTINGS_FILE), &state.bytes) };
FolderViewSettings *folder = nullptr;
while (EsINIParse(&state)) {
if (state.value && 0 == EsStringCompareRaw(state.section, state.sectionBytes, EsLiteral("bookmarks"))) {
String string = {};
string.text = state.value, string.bytes = state.valueBytes;
BookmarkAdd(string, false);
} else if (0 == EsStringCompareRaw(state.sectionClass, state.sectionClassBytes, EsLiteral("folder"))) {
if (0 == EsStringCompareRaw(state.key, state.keyBytes, EsLiteral("path"))) {
if (state.keyBytes < sizeof(folderViewSettings[0].path)) {
FolderViewSettingsEntry *entry = folderViewSettings.Add();
EsMemoryCopy(entry->path, state.value, state.valueBytes);
entry->pathBytes = state.valueBytes;
folder = &entry->settings;
}
} else if (folder && 0 == EsStringCompareRaw(state.key, state.keyBytes, EsLiteral("sort_column"))) {
folder->sortColumn = EsIntegerParse(state.value, state.valueBytes);
} else if (folder && 0 == EsStringCompareRaw(state.key, state.keyBytes, EsLiteral("view_type"))) {
folder->viewType = EsIntegerParse(state.value, state.valueBytes);
}
}
}
}
void _start() {
_init();
AddKnownFileTypes();
LoadSettings();
// Enumerate drives.
EsMountPointEnumerate([] (const char *prefix, size_t prefixBytes, EsGeneric) {
DriveAdd(prefix, prefixBytes);
}, 0);
// Start the non-blocking task threads.
nonBlockingTaskWorkAvailable = EsEventCreate(true /* autoReset */);
EsThreadInformation _nonBlockingTaskThread = {};
for (uintptr_t i = 0; i < EsSystemGetConstant(ES_SYSTEM_CONSTANT_OPTIMAL_WORK_QUEUE_THREAD_COUNT); i++) {
EsThreadCreate(NonBlockingTaskThread, &_nonBlockingTaskThread, nullptr);
}
// Process messages.
while (true) {
EsMessage *message = EsMessageReceive();
if (message->type == ES_MSG_INSTANCE_CREATE) {
Instance *instance = EsInstanceCreate(message, INTERFACE_STRING(FileManagerTitle));
instances.Add(instance);
InstanceCreateUI(instance);
} else if (message->type == ES_MSG_INSTANCE_DESTROY) {
// TODO Cleanup/cancel any unfinished non-blocking tasks before we get here!
Instance *instance = message->instanceDestroy.instance;
InstanceDestroy(instance);
instances.FindAndDeleteSwap(instance, true);
} else if (message->type == ES_MSG_REGISTER_FILE_SYSTEM) {
DriveAdd(message->registerFileSystem.mountPoint->prefix, message->registerFileSystem.mountPoint->prefixBytes);
} else if (message->type == ES_MSG_UNREGISTER_FILE_SYSTEM) {
DriveRemove(message->unregisterFileSystem.mountPoint->prefix, message->unregisterFileSystem.mountPoint->prefixBytes);
} else if (message->type == ES_MSG_FILE_MANAGER_FILE_MODIFIED) {
char *_path = (char *) EsHeapAllocate(message->user.context2.u, false);
EsConstantBufferRead(message->user.context1.u, _path);
String fullPath = StringFromLiteralWithSize(_path, message->user.context2.u);
size_t pathSectionCount = PathCountSections(fullPath);
for (uintptr_t i = 0; i < pathSectionCount; i++) {
String path = PathGetParent(fullPath, i + 1);
String file = PathGetSection(fullPath, i + 1);
String folder = PathGetParent(path);
EsDirectoryChild information = {};
if (EsPathQueryInformation(STRING(path), &information)) {
EsMutexAcquire(&loadedFoldersMutex);
for (uintptr_t i = 0; i < loadedFolders.Length(); i++) {
if (loadedFolders[i]->itemHandler->type != NAMESPACE_HANDLER_FILE_SYSTEM) continue;
if (EsStringCompareRaw(STRING(loadedFolders[i]->path), STRING(folder))) continue;
FolderAddEntryAndUpdateInstances(loadedFolders[i], file.text, file.bytes, &information, nullptr, true);
}
EsMutexRelease(&loadedFoldersMutex);
}
}
EsHandleClose(message->user.context1.u);
EsHeapFree(_path);
} else if (message->type == MESSAGE_BLOCKING_TASK_COMPLETE) {
Instance *instance = (Instance *) message->user.context1.p;
if (message->user.context2.u == instance->blockingTaskID) BlockingTaskComplete(instance);
} else if (message->type == MESSAGE_NON_BLOCKING_TASK_COMPLETE) {
NonBlockingTaskComplete(message);
}
}
}

View File

@ -0,0 +1,201 @@
struct String {
char *text;
size_t bytes, allocated;
};
String StringAllocateAndFormat(const char *format, ...) {
String string = {};
va_list arguments;
va_start(arguments, format);
string.text = EsStringAllocateAndFormatV(&string.bytes, format, arguments);
va_end(arguments);
string.allocated = string.bytes;
return string;
}
String StringFromLiteral(const char *literal) {
String string = {};
string.text = (char *) literal;
string.bytes = EsCStringLength(literal);
return string;
}
String StringFromLiteralWithSize(const char *literal, ptrdiff_t bytes) {
String string = {};
string.text = (char *) literal;
string.bytes = bytes == -1 ? EsCStringLength(literal) : bytes;
return string;
}
void StringAppend(String *string, String with) {
if (string->bytes + with.bytes > string->allocated) {
string->allocated = (string->allocated + with.bytes) * 2;
string->text = (char *) EsHeapReallocate(string->text, string->allocated, false);
}
EsMemoryCopy(string->text + string->bytes, with.text, with.bytes);
string->bytes += with.bytes;
}
void StringDestroy(String *string) {
EsAssert(string->allocated == string->bytes); // Attempting to free a partial string.
EsHeapFree(string->text);
string->text = nullptr;
string->bytes = string->allocated = 0;
}
String StringDuplicate(String string) {
String result = {};
result.bytes = result.allocated = string.bytes;
result.text = (char *) EsHeapAllocate(result.bytes + 1, false);
result.text[result.bytes] = 0;
EsMemoryCopy(result.text, string.text, result.bytes);
return result;
}
inline bool StringStartsWith(String a, String b) {
return a.bytes >= b.bytes && 0 == EsMemoryCompare(a.text, b.text, b.bytes);
}
inline bool StringEndsWith(String a, String b) {
return a.bytes >= b.bytes && 0 == EsMemoryCompare(a.text + a.bytes - b.bytes, b.text, b.bytes);
}
inline bool StringEquals(String a, String b) {
return a.bytes == b.bytes && 0 == EsMemoryCompare(a.text, b.text, a.bytes);
}
String StringSlice(String string, uintptr_t offset, ptrdiff_t length) {
if (length == -1) {
length = string.bytes - offset;
}
string.text += offset;
string.bytes = length;
string.allocated = 0;
return string;
}
#define STRING(x) x.text, x.bytes
#define STRFMT(x) x.bytes, x.text
uintptr_t PathCountSections(String string) {
ptrdiff_t sectionCount = 0;
for (uintptr_t i = 0; i < string.bytes; i++) {
if (string.text[i] == '/') {
sectionCount++;
}
}
return sectionCount;
}
String PathGetSection(String string, ptrdiff_t index) {
String output = {};
size_t stringBytes = string.bytes;
char *text = string.text;
if (index < 0) {
index += PathCountSections(string);
}
if (index < 0) {
return output;
}
uintptr_t i = 0, bytes = 0;
for (; index && i < stringBytes; i++) {
if (text[i] == '/') {
index--;
}
}
if (index) {
return output;
}
output.text = text + i;
for (; i < stringBytes; i++) {
if (text[i] == '/') {
break;
} else {
bytes++;
}
}
output.bytes = bytes;
return output;
}
String PathGetParent(String string, uintptr_t index) {
if (index == PathCountSections(string)) return string;
String section = PathGetSection(string, index);
string.bytes = section.bytes + section.text - string.text + 1;
return string;
}
String PathGetExtension(String string) {
String extension = {};
int lastSeparator = 0;
for (intptr_t i = string.bytes - 1; i >= 0; i--) {
if (string.text[i] == '.') {
lastSeparator = i;
break;
}
}
if (!lastSeparator && string.text[0] != '.') {
extension.text = string.text + string.bytes;
extension.bytes = 0;
return extension;
} else {
extension.text = string.text + lastSeparator + 1;
extension.bytes = string.bytes - lastSeparator - 1;
return extension;
}
}
String PathGetParent(String string) {
size_t newPathBytes = 0;
for (uintptr_t i = 0; i < string.bytes - 1; i++) {
if (string.text[i] == '/') {
newPathBytes = i + 1;
}
}
String result = {};
result.bytes = newPathBytes;
result.text = string.text;
return result;
}
bool PathHasPrefix(String path, String prefix) {
return StringStartsWith(path, prefix) && path.bytes > prefix.bytes && path.text[prefix.bytes] == '/';
}
bool PathReplacePrefix(String *knownPath, String oldPath, String newPath) {
if (PathHasPrefix(*knownPath, oldPath)) {
String after = StringSlice(*knownPath, oldPath.bytes, -1);
String path = StringAllocateAndFormat("%s%s", STRFMT(newPath), STRFMT(after));
StringDestroy(knownPath);
*knownPath = path;
return true;
}
return false;
}
String PathRemoveTrailingSlash(String path) {
if (path.bytes && path.text[path.bytes - 1] == '/') {
path.bytes--;
}
return path;
}

View File

@ -0,0 +1,110 @@
struct FileType {
char *name;
size_t nameBytes;
uint32_t iconID;
int64_t openHandler;
// TODO Allow applications to register their own thumbnail generators.
bool hasThumbnailGenerator;
};
Array<FileType> knownFileTypes;
HashStore<char, uintptr_t /* index into knownFileTypes */> knownFileTypesByExtension;
void AddKnownFileTypes() {
#define ADD_FILE_TYPE(_extension, _name, _iconID) \
{ \
FileType type = {}; \
type.name = (char *) _name; \
type.iconID = _iconID; \
uintptr_t index = knownFileTypes.Length(); \
knownFileTypes.Add(type); \
*knownFileTypesByExtension.Put(_extension, EsCStringLength(_extension)) = index; \
}
#define KNOWN_FILE_TYPE_DIRECTORY (0)
ADD_FILE_TYPE("", interfaceString_CommonItemFolder, ES_ICON_FOLDER);
#define KNOWN_FILE_TYPE_UNKNOWN (1)
ADD_FILE_TYPE("", interfaceString_CommonItemFile, ES_ICON_UNKNOWN);
#define KNOWN_FILE_TYPE_DRIVE_HDD (2)
ADD_FILE_TYPE("", interfaceString_CommonDriveHDD, ES_ICON_DRIVE_HARDDISK);
#define KNOWN_FILE_TYPE_DRIVE_SSD (3)
ADD_FILE_TYPE("", interfaceString_CommonDriveSSD, ES_ICON_DRIVE_HARDDISK_SOLIDSTATE);
#define KNOWN_FILE_TYPE_DRIVE_CDROM (4)
ADD_FILE_TYPE("", interfaceString_CommonDriveCDROM, ES_ICON_MEDIA_OPTICAL);
#define KNOWN_FILE_TYPE_DRIVE_USB_MASS_STORAGE (5)
ADD_FILE_TYPE("", interfaceString_CommonDriveUSBMassStorage, ES_ICON_DRIVE_REMOVABLE_MEDIA_USB);
#define KNOWN_FILE_TYPE_DRIVES_PAGE (6)
ADD_FILE_TYPE("", interfaceString_FileManagerDrivesPage, ES_ICON_COMPUTER_LAPTOP);
size_t groupCount;
EsSystemConfigurationGroup *groups = EsSystemConfigurationReadAll(&groupCount);
for (uintptr_t i = 0; i < groupCount; i++) {
EsSystemConfigurationGroup *group = groups + i;
if (EsStringCompareRaw(group->sectionClass, group->sectionClassBytes, EsLiteral("file_type"))) {
continue;
}
FileType type = {};
type.name = EsSystemConfigurationGroupReadString(group, "name", -1, &type.nameBytes);
type.openHandler = EsSystemConfigurationGroupReadInteger(group, "open", -1);
char *iconName = EsSystemConfigurationGroupReadString(group, "icon", -1);
if (iconName) {
type.iconID = EsIconIDFromString(iconName);
EsHeapFree(iconName);
}
char *extension = EsSystemConfigurationGroupReadString(group, "extension", -1);
// TODO Proper thumbnail generator registrations.
if (0 == EsCRTstrcmp(extension, "jpg") || 0 == EsCRTstrcmp(extension, "png") || 0 == EsCRTstrcmp(extension, "bmp")) {
type.hasThumbnailGenerator = true;
}
uintptr_t index = knownFileTypes.Length();
knownFileTypes.Add(type);
*knownFileTypesByExtension.Put(extension, EsCStringLength(extension)) = index;
}
}
FileType *FolderEntryGetType(Folder *folder, FolderEntry *entry) {
if (entry->isFolder) {
if (folder->itemHandler->getFileType != NamespaceDefaultGetFileType) {
String path = StringAllocateAndFormat("%s%s", STRFMT(folder->path), STRFMT(entry->GetInternalName()));
FileType *type = &knownFileTypes[folder->itemHandler->getFileType(path)];
StringDestroy(&path);
return type;
} else {
return &knownFileTypes[KNOWN_FILE_TYPE_DIRECTORY];
}
} else {
String extension = entry->GetExtension();
char buffer[32];
uintptr_t i = 0;
for (; i < extension.bytes && i < 32; i++) {
if (EsCRTisupper(extension.text[i])) {
buffer[i] = EsCRTtolower(extension.text[i]);
} else {
buffer[i] = extension.text[i];
}
}
uintptr_t index = knownFileTypesByExtension.Get1(buffer, i);
return &knownFileTypes[index ? index : KNOWN_FILE_TYPE_UNKNOWN];
}
}
uint32_t IconFromDriveType(uint8_t driveType) {
if (driveType == ES_DRIVE_TYPE_HDD ) return ES_ICON_DRIVE_HARDDISK;
if (driveType == ES_DRIVE_TYPE_SSD ) return ES_ICON_DRIVE_HARDDISK_SOLIDSTATE;
if (driveType == ES_DRIVE_TYPE_CDROM ) return ES_ICON_MEDIA_OPTICAL;
if (driveType == ES_DRIVE_TYPE_USB_MASS_STORAGE) return ES_ICON_DRIVE_REMOVABLE_MEDIA_USB;
return ES_ICON_DRIVE_HARDDISK;
}

1079
apps/file_manager/ui.cpp Normal file

File diff suppressed because it is too large Load Diff

942
apps/fly.cpp Normal file
View File

@ -0,0 +1,942 @@
// TODO Icon.
// TODO Music.
// TODO Draw() with rotation.
// TODO Saving with new file IO.
#include <essence.h>
EsInstance *instance;
uint32_t backgroundColor;
int musicIndex;
bool keysPressedSpace, keysPressedEscape, keysPressedX;
uint32_t previousControllerButtons[ES_GAME_CONTROLLER_MAX_COUNT];
uint32_t *targetBits;
size_t targetWidth, targetHeight, targetStride;
double updateTimeAccumulator;
float gameScale;
uint32_t gameOffsetX, gameOffsetY, gameWidth, gameHeight;
#define GAME_SIZE (380)
#define BRAND "fly"
struct Texture {
uint32_t width, height;
uint32_t *bits;
};
void Transform(float *destination, float *left, float *right) {
float d[6];
d[0] = left[0] * right[0] + left[1] * right[3];
d[1] = left[0] * right[1] + left[1] * right[4];
d[2] = left[0] * right[2] + left[1] * right[5] + left[2];
d[3] = left[3] * right[0] + left[4] * right[3];
d[4] = left[3] * right[1] + left[4] * right[4];
d[5] = left[3] * right[2] + left[4] * right[5] + left[5];
destination[0] = d[0];
destination[1] = d[1];
destination[2] = d[2];
destination[3] = d[3];
destination[4] = d[4];
destination[5] = d[5];
}
__attribute__((optimize("-O2")))
void Draw(Texture *texture,
float x, float y, float w = -1, float h = -1,
float sx = 0, float sy = 0, float sw = -1, float sh = -1,
float _a = 1, float _r = 1, float _g = 1, float _b = 1,
float rot = 0) {
(void) rot;
if (sw == -1 && sh == -1) sw = texture->width, sh = texture->height;
if (w == -1 && h == -1) w = sw, h = sh;
if (_a <= 0) return;
if (x + w < 0 || y + h < 0 || x > GAME_SIZE || y > GAME_SIZE) return;
x *= gameScale, y *= gameScale, w *= gameScale, h *= gameScale;
float m[6] = {};
float m1[6] = { sw / w, 0, 0, 0, sh / h, 0 };
float m2[6] = { 1, 0, -x * (sw / w), 0, 1, -y * (sh / h) };
Transform(m, m2, m1);
intptr_t yStart = y - 1, yEnd = y + h + 1, xStart = x - 1, xEnd = x + w + 1;
// if (rot) yStart -= h * 0.45f, yEnd += h * 0.45f, xStart -= w * 0.45f, xEnd += w * 0.45f;
if (yStart < 0) yStart = 0;
if (yStart > gameHeight) yStart = gameHeight;
if (yEnd < 0) yEnd = 0;
if (yEnd > gameHeight) yEnd = gameHeight;
if (xStart < 0) xStart = 0;
if (xStart > gameWidth) xStart = gameWidth;
if (xEnd < 0) xEnd = 0;
if (xEnd > gameWidth) xEnd = gameWidth;
m[2] += m[0] * xStart, m[5] += m[3] * xStart;
uint32_t *scanlineStart = targetBits + (gameOffsetY + yStart) * targetStride / 4 + (gameOffsetX + xStart);
uint32_t r = _r * 255, g = _g * 255, b = _b * 255, a = _a * 255;
for (intptr_t y = yStart; y < yEnd; y++, scanlineStart += targetStride / 4) {
uint32_t *output = scanlineStart;
float tx = m[1] * y + m[2];
float ty = m[4] * y + m[5];
for (intptr_t x = xStart; x < xEnd; x++, output++, tx += m[0], ty += m[3]) {
if (tx + sx < 0 || ty + sy < 0) continue;
uintptr_t txi = tx + sx, tyi = ty + sy;
if (txi < sx || tyi < sy || txi >= sx + sw || tyi >= sy + sh) continue;
uint32_t modified = texture->bits[tyi * texture->width + txi];
if (!(modified & 0xFF000000)) continue;
uint32_t original = *output;
uint32_t alpha1 = (((modified & 0xFF000000) >> 24) * a) >> 8;
uint32_t alpha2 = (255 - alpha1) << 8;
uint32_t r2 = alpha2 * ((original & 0x00FF0000) >> 16);
uint32_t g2 = alpha2 * ((original & 0x0000FF00) >> 8);
uint32_t b2 = alpha2 * ((original & 0x000000FF) >> 0);
uint32_t r1 = r * alpha1 * ((modified & 0x00FF0000) >> 16);
uint32_t g1 = g * alpha1 * ((modified & 0x0000FF00) >> 8);
uint32_t b1 = b * alpha1 * ((modified & 0x000000FF) >> 0);
*output = 0xFF000000
| (0x00FF0000 & ((r1 + r2) << 0))
| (0x0000FF00 & ((g1 + g2) >> 8))
| (0x000000FF & ((b1 + b2) >> 16));
}
}
}
void CreateTexture(Texture *texture, const char *cName) {
size_t dataBytes;
const void *data = EsEmbeddedFileGet(cName, &dataBytes);
texture->bits = (uint32_t *) EsImageLoad(data, dataBytes, &texture->width, &texture->height, 4);
EsAssert(texture->bits);
}
void ExitGame() {
EsInstanceDestroy(instance);
}
///////////////////////////////////////////////////////////
#define TAG_ALL (-1)
#define TAG_SOLID (-2)
#define TAG_KILL (-3)
#define TAG_WORLD (-4)
#define TILE_SIZE (16)
struct Entity {
uint8_t tag;
uint8_t layer;
bool solid, kill, hide;
char symbol;
int8_t frameCount, stepsPerFrame;
Texture *texture;
float texOffX, texOffY, w, h;
int uid;
void (*create)(Entity *);
void (*destroy)(Entity *);
void (*stepAlways)(Entity *); // Called even if level editing.
void (*step)(Entity *);
void (*draw)(Entity *);
void (*drawAfter)(Entity *);
float x, y, px, py;
int stepIndex;
bool isUsed, isDestroyed;
union {
struct {
float cx, cy;
float th, dth;
float dths;
int respawn, respawnGrow, dash;
} player;
struct {
int random;
} block;
struct {
float vel;
} moveBlock;
struct {
float th, cx, cy;
} spin;
struct {
float vx, vy, th, dth;
int life;
uint32_t col;
} star;
};
void Destroy() {
if (isDestroyed) return;
isDestroyed = true;
if (destroy) destroy(this);
}
};
Texture textureDashMessage, textureKeyMessage, textureKey, textureControlsMessage, textureWhite;
Entity typePlayer, typeBlock, typeCheck, typeMoveH, typeStar, typeMoveV, typePowerup, typeShowMessage, typeKey, typeLock, typeSpin, typeEnd;
// All the entities that can be loaded from the rooms.
Entity *entityTypes[] = {
&typeBlock, &typeCheck, &typeMoveH, &typeMoveV, &typePowerup, &typeKey, &typeLock, &typeSpin, &typeEnd,
nullptr,
};
int levelTick;
bool justLoaded = true;
#define MAX_ENTITIES (1000)
struct SaveState {
float checkX, checkY;
int roomX, roomY;
bool hasDash, hasKey;
int deathCount;
uint32_t check;
};
struct GameState : SaveState {
Entity entities[MAX_ENTITIES];
int world;
Entity *player;
};
GameState state;
Entity *AddEntity(Entity *templateEntity, float x, float y, int uid = 0) {
for (int i = 0; i < MAX_ENTITIES; i++) {
if (!state.entities[i].isUsed) {
EsMemoryCopy(state.entities + i, templateEntity, sizeof(Entity));
state.entities[i].isUsed = true;
if (!state.entities[i].frameCount) state.entities[i].frameCount = 1;
if (!state.entities[i].stepsPerFrame) state.entities[i].stepsPerFrame = 1;
state.entities[i].x = state.entities[i].px = x;
state.entities[i].y = state.entities[i].py = y;
if (!state.entities[i].w) state.entities[i].w = state.entities[i].texture->width - 1;
if (!state.entities[i].h) state.entities[i].h = state.entities[i].texture->height / state.entities[i].frameCount - 1;
state.entities[i].uid = uid;
if (state.entities[i].create) state.entities[i].create(state.entities + i);
return state.entities + i;
}
}
static Entity fake = {};
return &fake;
}
Entity *FindEntity(float x, float y, float w, float h, int tag, Entity *exclude) {
for (int i = 0; i < MAX_ENTITIES; i++) {
if (state.entities[i].isUsed && !state.entities[i].isDestroyed
&& (state.entities[i].tag == tag
|| tag == TAG_ALL
|| (tag == TAG_SOLID && state.entities[i].solid)
|| (tag == TAG_KILL && state.entities[i].kill)
)
&& state.entities + i != exclude) {
if (x <= state.entities[i].x + state.entities[i].w && state.entities[i].x <= x + w
&& y <= state.entities[i].y + state.entities[i].h && state.entities[i].y <= y + h) {
return state.entities + i;
}
}
}
return nullptr;
}
char roomName[16];
void UpdateRoomName() {
roomName[0] = 'R';
roomName[1] = (state.roomX / 10) + '0';
roomName[2] = (state.roomX % 10) + '0';
roomName[3] = '_';
roomName[4] = (state.roomY / 10) + '0';
roomName[5] = (state.roomY % 10) + '0';
roomName[6] = '.';
roomName[7] = 'D';
roomName[8] = 'A';
roomName[9] = 'T';
roomName[10] = 0;
}
#define ROOM_ID ((state.roomX - 7) + (state.roomY - 9) * 6)
void LoadRoom() {
state.world = 0;
UpdateRoomName();
roomName[6] = '_';
const uint8_t *buffer = (const uint8_t *) EsEmbeddedFileGet(roomName);
for (int i = 0; i < MAX_ENTITIES; i++) {
if (!state.entities[i].isUsed || state.entities[i].isDestroyed) continue;
if (state.entities[i].tag != typePlayer.tag) {
state.entities[i].Destroy();
} else {
state.entities[i].px = state.entities[i].x;
state.entities[i].py = state.entities[i].y;
}
}
int p = 0;
int iir = 0;
while (true) {
uint8_t tag = buffer[p++];
if (!tag) break;
float x = *(float *) (buffer + p); p += 4;
float y = *(float *) (buffer + p); p += 4;
if (tag == (uint8_t) TAG_WORLD) {
state.world = x;
}
for (int i = 0; entityTypes[i]; i++) {
if (entityTypes[i]->tag == tag) {
AddEntity(entityTypes[i], x, y, (state.roomX << 24) | (state.roomY << 16) | iir);
iir++;
}
}
}
musicIndex = state.world;
}
void CalculateCheck() {
state.check = 0;
uint8_t *buffer = (uint8_t *) &state;
uint32_t check = 0x1D471D47;
for (uintptr_t i = 0; i < sizeof(SaveState); i++) {
check ^= ((uint32_t) buffer[i] + 10) * (i + 100);
}
state.check = check;
}
float FadeInOut(float t) {
if (t < 0.3f) return t / 0.3f;
else if (t < 0.7f) return 1;
else return 1 - (t - 0.7f) / 0.3f;
}
void InitialiseGame() {
state.roomX = 10;
state.roomY = 10;
CreateTexture(&textureWhite, "white_png");
CreateTexture(&textureKey, "key_png");
CreateTexture(&textureDashMessage, "dash_msg_png");
CreateTexture(&textureKeyMessage, "key_msg_png");
CreateTexture(&textureControlsMessage, "controls_png");
int tag = 1;
{
static Texture texture;
CreateTexture(&texture, "player_png");
typePlayer.tag = tag++;
typePlayer.texture = &texture;
typePlayer.frameCount = 6;
typePlayer.stepsPerFrame = 5;
typePlayer.layer = 1;
typePlayer.step = [] (Entity *entity) {
if (entity->player.respawn) {
if (entity->stepIndex > entity->player.respawn) {
entity->player.respawn = 0;
entity->hide = false;
entity->player.cx = state.checkX;
entity->player.cy = state.checkY;
entity->player.respawnGrow = entity->stepIndex + 20;
entity->player.dash = 0;
} else {
return;
}
}
if (!entity->player.respawnGrow && !entity->player.dash) {
if (keysPressedSpace) {
entity->player.cx += (entity->x - entity->player.cx) * 2;
entity->player.cy += (entity->y - entity->player.cy) * 2;
entity->player.th += 3.15f;
entity->player.dth = -entity->player.dth;
entity->player.dths = 5;
} else if (keysPressedX && state.hasDash) {
entity->player.dash = 10;
}
}
float rd = 40;
if (entity->player.respawnGrow) {
if (entity->stepIndex > entity->player.respawnGrow) {
entity->player.respawnGrow = 0;
} else {
rd *= 1 - (entity->player.respawnGrow - entity->stepIndex) / 20.0f;
}
}
entity->x = rd * EsCRTcosf(entity->player.th) + entity->player.cx;
entity->y = rd * EsCRTsinf(entity->player.th) + entity->player.cy + 5 * EsCRTsinf(4.71f + entity->stepIndex * 0.1f);
if (entity->player.dash) {
float pt = entity->player.th - entity->player.dth;
float px = rd * EsCRTcosf(pt) + entity->player.cx;
float py = rd * EsCRTsinf(pt) + entity->player.cy + 5 * EsCRTsinf(4.71f + (entity->stepIndex - 1) * 0.1f);
float dx = entity->x - px;
float dy = entity->y - py;
entity->player.cx += dx * 3.2f;
entity->player.cy += dy * 3.2f;
entity->player.dash--;
AddEntity(&typeStar, entity->x + 8, entity->y + 8)->star.col = 0xFF;
} else {
entity->player.th += entity->player.dth;
}
if (entity->player.respawnGrow) {
entity->px = entity->x;
entity->py = entity->y;
}
if (entity->player.dths) {
entity->player.th += entity->player.dth;
entity->player.dths--;
entity->stepIndex = 5 * 3;
}
if (FindEntity(entity->x + 5, entity->y + 5, entity->w - 10, entity->h - 10, TAG_KILL, 0)) {
for (int i = 0; i < 20; i++) {
AddEntity(&typeStar, entity->x + 8, entity->y + 8)->star.col = 0xFF0000;
}
entity->hide = true;
entity->player.respawn = entity->stepIndex + 20;
state.deathCount++;
}
if (entity->x > GAME_SIZE - 8) {
entity->x -= GAME_SIZE;
entity->player.cx -= GAME_SIZE;
state.checkX -= GAME_SIZE;
state.roomX++;
LoadRoom();
} else if (entity->x < -8) {
entity->x += GAME_SIZE;
entity->player.cx += GAME_SIZE;
state.checkX += GAME_SIZE;
state.roomX--;
LoadRoom();
}
if (entity->y > GAME_SIZE - 8) {
entity->y -= GAME_SIZE;
entity->player.cy -= GAME_SIZE;
state.checkY -= GAME_SIZE;
state.roomY++;
LoadRoom();
} else if (entity->y < -8) {
entity->y += GAME_SIZE;
entity->player.cy += GAME_SIZE;
state.checkY += GAME_SIZE;
state.roomY--;
LoadRoom();
}
};
typePlayer.create = [] (Entity *entity) {
state.player = entity;
entity->player.cx = entity->x;
entity->player.cy = entity->y;
entity->player.dth = 0.06f;
};
}
{
static Texture texture;
CreateTexture(&texture, "block_png");
static Texture block2;
CreateTexture(&block2, "block2_png");
typeBlock.tag = tag++;
typeBlock.texture = &texture;
typeBlock.kill = true;
typeBlock.hide = true;
typeBlock.layer = 2;
typeBlock.create = [] (Entity *entity) {
uint8_t r = EsRandomU8();
if (r < 20) {
entity->block.random = 1;
} else if (r < 50) {
entity->block.random = 2;
} else {
entity->block.random = 0;
}
};
typeBlock.stepAlways = [] (Entity *entity) {
if (FindEntity(entity->x + 1, entity->y + 1, entity->w - 2, entity->h - 2, entity->tag, entity)) {
entity->Destroy();
}
};
typeBlock.drawAfter = [] (Entity *entity) {
if (!FindEntity(entity->x - 16, entity->y, 1, 1, entity->tag, entity)) {
Draw(&block2, entity->x - 16, entity->y, 16, 16, 0, entity->block.random * 16, 16, 16, 1);
}
if (!FindEntity(entity->x + 16, entity->y, 1, 1, entity->tag, entity)) {
Draw(&block2, entity->x + 16, entity->y, 16, 16, 32, entity->block.random * 16, 16, 16, 1);
}
if (!FindEntity(entity->x, entity->y - 16, 1, 1, entity->tag, entity)) {
Draw(&block2, entity->x, entity->y - 16, 16, 16, 16, entity->block.random * 16, 16, 16, 1);
}
if (!FindEntity(entity->x, entity->y + 16, 1, 1, entity->tag, entity)) {
Draw(&block2, entity->x, entity->y + 16, 16, 16, 48, entity->block.random * 16, 16, 16, 1);
}
};
}
{
static Texture check1, check2;
CreateTexture(&check1, "check1_png");
CreateTexture(&check2, "check2_png");
typeCheck.tag = tag++;
typeCheck.texture = &check1;
typeCheck.step = [] (Entity *entity) {
if (state.checkX == entity->x && state.checkY == entity->y) {
entity->texture = &check2;
} else {
entity->texture = &check1;
}
if (FindEntity(entity->x - 4, entity->y - 4, entity->w + 8, entity->h + 8, typePlayer.tag, 0)) {
if (state.checkX != entity->x || state.checkY != entity->y) {
for (int i = 0; i < 10; i++) AddEntity(&typeStar, entity->x + 8, entity->y + 8)->star.col = 0xFFFFFF;
}
state.checkX = entity->x;
state.checkY = entity->y;
CalculateCheck();
EsFileWriteAll("|Settings:/Save.dat", -1, &state, sizeof(SaveState));
}
};
}
{
static Texture texture;
CreateTexture(&texture, "moveblock_png");
typeMoveH.tag = tag++;
typeMoveH.texture = &texture;
typeMoveH.kill = true;
typeMoveH.w = 16;
typeMoveH.h = 16;
typeMoveH.texOffX = -4;
typeMoveH.texOffY = -4;
typeMoveH.create = [] (Entity *entity) {
entity->moveBlock.vel = -4;
};
typeMoveH.step = [] (Entity *entity) {
entity->x += entity->moveBlock.vel;
if (FindEntity(entity->x, entity->y, entity->w, entity->h, typeBlock.tag, 0)) {
entity->moveBlock.vel = -entity->moveBlock.vel;
}
};
}
{
// Removed entity.
tag++;
}
{
static Texture texture;
CreateTexture(&texture, "star_png");
typeStar.texture = &texture;
typeStar.tag = tag++;
typeStar.hide = true; // Draw manually.
typeStar.layer = 2;
typeStar.create = [] (Entity *entity) {
float th = EsRandomU8() / 255.0 * 6.24;
float sp = EsRandomU8() / 255.0 * 0.5 + 0.5;
entity->star.vx = sp * EsCRTcosf(th);
entity->star.vy = sp * EsCRTsinf(th);
entity->star.life = EsRandomU8();
entity->star.dth = (EsRandomU8() / 255.0f - 0.5f) * 0.2f;
};
typeStar.step = [] (Entity *entity) {
entity->x += entity->star.vx;
entity->y += entity->star.vy;
entity->star.th += entity->star.dth;
if (entity->star.life < entity->stepIndex) {
entity->Destroy();
}
};
typeStar.drawAfter = [] (Entity *entity) {
Draw(entity->texture, entity->x - 4, entity->y - 4, -1, -1, 0, 0, -1, -1,
1.0 - (float) entity->stepIndex / entity->star.life,
((entity->star.col >> 16) & 0xFF) / 255.0f,
((entity->star.col >> 8) & 0xFF) / 255.0f,
((entity->star.col >> 0) & 0xFF) / 255.0f,
entity->star.th);
};
}
{
static Texture texture;
CreateTexture(&texture, "moveblock_png");
typeMoveV.tag = tag++;
typeMoveV.texture = &texture;
typeMoveV.kill = true;
typeMoveV.w = 16;
typeMoveV.h = 16;
typeMoveV.texOffX = -4;
typeMoveV.texOffY = -4;
typeMoveV.create = [] (Entity *entity) {
entity->moveBlock.vel = -4;
};
typeMoveV.step = [] (Entity *entity) {
entity->y += entity->moveBlock.vel;
if (FindEntity(entity->x, entity->y, entity->w, entity->h, typeBlock.tag, 0)) {
entity->moveBlock.vel = -entity->moveBlock.vel;
}
};
}
{
static Texture texture;
CreateTexture(&texture, "powerup_png");
typePowerup.tag = tag++;
typePowerup.texture = &texture;
typePowerup.step = [] (Entity *entity) {
if (state.hasDash) {
entity->Destroy();
return;
}
if (FindEntity(entity->x, entity->y, entity->w, entity->h, typePlayer.tag, 0)) {
state.hasDash = true;
AddEntity(&typeShowMessage, 0, 0)->texture = &textureDashMessage;
entity->Destroy();
}
};
}
{
typeShowMessage.texture = &textureKeyMessage;
typeShowMessage.tag = tag++;
typeShowMessage.hide = true;
typeShowMessage.draw = [] (Entity *entity) {
Draw(entity->texture, entity->x, entity->y, -1, -1, 0, 0, -1, -1, FadeInOut(entity->stepIndex / 180.0));
if (entity->stepIndex > 180) entity->Destroy();
};
}
{
typeKey.tag = tag++;
typeKey.texture = &textureKey;
typeKey.step = [] (Entity *entity) {
if (state.hasKey) {
entity->Destroy();
} else if (FindEntity(entity->x, entity->y, entity->w, entity->h, typePlayer.tag, 0)) {
state.hasKey = true;
AddEntity(&typeShowMessage, 0, 0)->texture = &textureKeyMessage;
entity->Destroy();
for (int i = 0; i < 10; i++) AddEntity(&typeStar, entity->x + 8, entity->y + 8)->star.col = 0xFFFFFF;
}
};
}
{
static Texture texture;
CreateTexture(&texture, "lock_png");
typeLock.tag = tag++;
typeLock.texture = &texture;
typeLock.kill = true;
typeLock.step = [] (Entity *entity) {
if (state.hasKey) {
for (int i = 0; i < 1; i++) AddEntity(&typeStar, entity->x + 8, entity->y + 8)->star.col = 0x000000;
entity->Destroy();
}
};
}
{
static Texture texture;
CreateTexture(&texture, "moveblock_png");
typeSpin.tag = tag++;
typeSpin.texture = &texture;
typeSpin.kill = true;
typeSpin.w = 16;
typeSpin.h = 16;
typeSpin.texOffX = -4;
typeSpin.texOffY = -4;
typeSpin.create = [] (Entity *entity) {
entity->spin.cx = entity->x;
entity->spin.cy = entity->y;
};
typeSpin.step = [] (Entity *entity) {
entity->x = 60 * EsCRTcosf(entity->spin.th) + entity->spin.cx;
entity->y = 60 * EsCRTsinf(entity->spin.th) + entity->spin.cy;
entity->spin.th += 0.04f;
};
}
{
static Texture msg1, msg2, msg3, num;
CreateTexture(&msg1, "end1_png");
CreateTexture(&msg2, "end2_png");
CreateTexture(&msg3, "end3_png");
CreateTexture(&num, "numbers_png");
typeEnd.tag = tag++;
typeEnd.texture = &textureKey;
typeEnd.hide = true;
typeEnd.create = [] (Entity *) {
state.player->Destroy();
};
typeEnd.draw = [] (Entity *entity) {
float t = entity->stepIndex / 180.0f;
if (t < 1) {
Draw(&msg1, 40, 150, -1, -1, 0, 0, -1, -1, FadeInOut(t));
} else if (t < 2) {
Draw(&msg2, 40, 150, -1, -1, 0, 0, -1, -1, FadeInOut(t - 1));
} else if (t < 3) {
Draw(&msg3, 40, 150, -1, -1, 0, 0, -1, -1, FadeInOut(t - 2));
int p = state.deathCount;
char digits[10];
int dc = 0;
if (p == 0) {
digits[dc++] = 0;
} else {
while (p) {
digits[dc++] = p % 10;
p /= 10;
}
}
int w = dc * 16;
for (int i = dc - 1; i >= 0; i--) {
Draw(&num, 40 + 150 - w / 2 + (dc - 1 - i) * 16,
150 + 33, 16, 30, 16 * digits[i], 0, 16, 30, FadeInOut(t - 2));
}
} else {
ExitGame();
}
};
}
state.checkX = GAME_SIZE / 2;
state.checkY = GAME_SIZE / 2 - 20;
size_t loadedStateBytes;
SaveState *loadedState = (SaveState *) EsFileReadAll("|Settings:/Save.dat", -1, &loadedStateBytes);
bool noSave = true;
if (loadedStateBytes == sizeof(SaveState)) {
EsMemoryCopy(&state, loadedState, loadedStateBytes);
uint32_t oldCheck = state.check;
CalculateCheck();
EsAssert(oldCheck == state.check);
noSave = false;
}
LoadRoom();
AddEntity(&typePlayer, state.checkX, state.checkY);
if (noSave) {
AddEntity(&typeShowMessage, 0, GAME_SIZE - 65)->texture = &textureControlsMessage;
}
}
void UpdateGame() {
if (keysPressedEscape) {
ExitGame();
}
for (int i = 0; i < MAX_ENTITIES; i++) {
if (state.entities[i].isUsed) {
state.entities[i].stepIndex++;
state.entities[i].px += (state.entities[i].x - state.entities[i].px) * 0.5f;
state.entities[i].py += (state.entities[i].y - state.entities[i].py) * 0.5f;
}
}
for (int i = 0; i < MAX_ENTITIES; i++) if (state.entities[i].isUsed && state.entities[i].stepAlways) state.entities[i].stepAlways(state.entities + i);
for (int i = 0; i < MAX_ENTITIES; i++) if (state.entities[i].isUsed && state.entities[i].step) {
state.entities[i].step(state.entities + i);
}
for (int i = 0; i < MAX_ENTITIES; i++) {
if (state.entities[i].isUsed && state.entities[i].isDestroyed) state.entities[i].isUsed = false;
}
levelTick++;
state.world %= 3;
if (state.world == 0) {
backgroundColor = 0xbef1b1;
} else if (state.world == 1) {
backgroundColor = 0xcee5f1;
} else if (state.world == 2) {
backgroundColor = 0xf3bdf6;
}
}
void RenderGame() {
for (int layer = -1; layer <= 3; layer++) {
for (int i = 0; i < MAX_ENTITIES; i++) {
Entity *entity = state.entities + i;
if (!entity->isUsed) continue;
if (entity->layer != layer) continue;
if (!entity->texture) continue;
if (entity->hide) continue;
int frame = entity->stepsPerFrame >= 0 ? ((entity->stepIndex / entity->stepsPerFrame) % entity->frameCount) : (-entity->stepsPerFrame - 1);
Draw(entity->texture, (int) (entity->px + entity->texOffX + 0.5f),
(int) (entity->py + entity->texOffY + 0.5f),
entity->texture->width, entity->texture->height / entity->frameCount,
0, entity->texture->height / entity->frameCount * frame,
entity->texture->width, entity->texture->height / entity->frameCount);
}
}
for (int i = 0; i < MAX_ENTITIES; i++) if (state.entities[i].isUsed && state.entities[i].draw ) state.entities[i].draw (state.entities + i);
for (int i = 0; i < MAX_ENTITIES; i++) if (state.entities[i].isUsed && state.entities[i].drawAfter ) state.entities[i].drawAfter (state.entities + i);
if (state.hasKey) {
Draw(&textureKey, GAME_SIZE - 20, 4);
}
}
///////////////////////////////////////////////////////////
int ProcessCanvasMessage(EsElement *element, EsMessage *message) {
if (message->type == ES_MSG_ANIMATE) {
message->animate.complete = false;
updateTimeAccumulator += message->animate.deltaMs / 1000.0;
while (updateTimeAccumulator > 1 / 60.0) {
{
EsGameControllerState state[ES_GAME_CONTROLLER_MAX_COUNT];
size_t count = EsGameControllerStatePoll(state);
for (uintptr_t i = 0; i < count; i++) {
if (state[i].buttons & (1 << 0) && (~previousControllerButtons[i] & (1 << 0))) {
keysPressedSpace = true;
} else if (state[i].buttons & (1 << 1) && (~previousControllerButtons[i] & (1 << 1))) {
keysPressedX = true;
}
previousControllerButtons[i] = state[i].buttons;
}
}
UpdateGame();
updateTimeAccumulator -= 1 / 60.0;
keysPressedSpace = keysPressedEscape = keysPressedX = false;
}
EsElementRepaint(element);
} else if (message->type == ES_MSG_KEY_DOWN && !message->keyboard.repeat) {
if (message->keyboard.scancode == ES_SCANCODE_SPACE || message->keyboard.scancode == ES_SCANCODE_Z) {
keysPressedSpace = true;
} else if (message->keyboard.scancode == ES_SCANCODE_ESCAPE) {
keysPressedEscape = true;
} else if (message->keyboard.scancode == ES_SCANCODE_X) {
keysPressedX = true;
}
} else if (message->type == ES_MSG_PAINT) {
EsPainter *painter = message->painter;
EsPaintTargetStartDirectAccess(painter->target, &targetBits, nullptr, nullptr, &targetStride);
targetBits = (uint32_t *) ((uint8_t *) targetBits + targetStride * painter->offsetY + 4 * painter->offsetX);
targetWidth = painter->width, targetHeight = painter->height;
gameScale = (float) painter->width / GAME_SIZE;
if (gameScale * GAME_SIZE > painter->height) gameScale = (float) painter->height / GAME_SIZE;
if (gameScale > 1) gameScale = EsCRTfloorf(gameScale);
gameWidth = GAME_SIZE * gameScale, gameHeight = GAME_SIZE * gameScale;
gameOffsetX = painter->width / 2 - gameWidth / 2;
gameOffsetY = painter->height / 2 - gameHeight / 2;
// TODO Clear margins.
Draw(&textureWhite, 0, 0, GAME_SIZE, GAME_SIZE, 0, 0, 1, 1, 1,
((backgroundColor >> 16) & 0xFF) / 255.0f,
((backgroundColor >> 8) & 0xFF) / 255.0f,
((backgroundColor >> 0) & 0xFF) / 255.0f);
RenderGame();
EsPaintTargetEndDirectAccess(painter->target);
}
return 0;
}
void _start() {
_init();
while (true) {
EsMessage *message = EsMessageReceive();
if (message->type == ES_MSG_INSTANCE_CREATE) {
instance = EsInstanceCreate(message, BRAND);
EsWindow *window = instance->window;
EsPanel *container = EsPanelCreate(window, ES_CELL_FILL, ES_STYLE_PANEL_WINDOW_DIVIDER);
EsElement *canvas = EsCustomElementCreate(container, ES_CELL_FILL | ES_ELEMENT_FOCUSABLE, {});
canvas->messageUser = ProcessCanvasMessage;
EsElementStartAnimating(canvas);
EsElementFocus(canvas);
InitialiseGame();
}
}
}

66
apps/fly.ini Normal file
View File

@ -0,0 +1,66 @@
[general]
name=Fly
[build]
source=apps/fly.cpp
[embed]
bgm1_mid=res/Fly Assets/bgm1.mid
bgm2_mid=res/Fly Assets/bgm2.mid
bgm3_mid=res/Fly Assets/bgm3.mid
block2_png=res/Fly Assets/block2.png
block_png=res/Fly Assets/block.png
check1_png=res/Fly Assets/check1.png
check2_png=res/Fly Assets/check2.png
controls_png=res/Fly Assets/controls.png
dash_msg_png=res/Fly Assets/dash_msg.png
end1_png=res/Fly Assets/end1.png
end2_png=res/Fly Assets/end2.png
end3_png=res/Fly Assets/end3.png
key_msg_png=res/Fly Assets/key_msg.png
key_png=res/Fly Assets/key.png
lock_png=res/Fly Assets/lock.png
moveblock_png=res/Fly Assets/moveblock.png
numbers_png=res/Fly Assets/numbers.png
player_png=res/Fly Assets/player.png
powerup_png=res/Fly Assets/powerup.png
R07_12_DAT=res/Fly Assets/R07_12.DAT
R07_13_DAT=res/Fly Assets/R07_13.DAT
R07_14_DAT=res/Fly Assets/R07_14.DAT
R07_16_DAT=res/Fly Assets/R07_16.DAT
R07_17_DAT=res/Fly Assets/R07_17.DAT
R07_18_DAT=res/Fly Assets/R07_18.DAT
R08_12_DAT=res/Fly Assets/R08_12.DAT
R08_13_DAT=res/Fly Assets/R08_13.DAT
R08_14_DAT=res/Fly Assets/R08_14.DAT
R08_16_DAT=res/Fly Assets/R08_16.DAT
R08_17_DAT=res/Fly Assets/R08_17.DAT
R08_18_DAT=res/Fly Assets/R08_18.DAT
R09_10_DAT=res/Fly Assets/R09_10.DAT
R09_11_DAT=res/Fly Assets/R09_11.DAT
R09_12_DAT=res/Fly Assets/R09_12.DAT
R09_13_DAT=res/Fly Assets/R09_13.DAT
R09_14_DAT=res/Fly Assets/R09_14.DAT
R09_15_DAT=res/Fly Assets/R09_15.DAT
R09_16_DAT=res/Fly Assets/R09_16.DAT
R09_17_DAT=res/Fly Assets/R09_17.DAT
R09_18_DAT=res/Fly Assets/R09_18.DAT
R10_09_DAT=res/Fly Assets/R10_09.DAT
R10_10_DAT=res/Fly Assets/R10_10.DAT
R10_11_DAT=res/Fly Assets/R10_11.DAT
R10_12_DAT=res/Fly Assets/R10_12.DAT
R10_13_DAT=res/Fly Assets/R10_13.DAT
R10_14_DAT=res/Fly Assets/R10_14.DAT
R10_15_DAT=res/Fly Assets/R10_15.DAT
R10_16_DAT=res/Fly Assets/R10_16.DAT
R11_09_DAT=res/Fly Assets/R11_09.DAT
R11_10_DAT=res/Fly Assets/R11_10.DAT
R11_11_DAT=res/Fly Assets/R11_11.DAT
R11_13_DAT=res/Fly Assets/R11_13.DAT
R11_14_DAT=res/Fly Assets/R11_14.DAT
R11_15_DAT=res/Fly Assets/R11_15.DAT
R12_09_DAT=res/Fly Assets/R12_09.DAT
R12_10_DAT=res/Fly Assets/R12_10.DAT
R12_11_DAT=res/Fly Assets/R12_11.DAT
star_png=res/Fly Assets/star.png
white_png=res/Fly Assets/white.png

414
apps/font_book.cpp Normal file
View File

@ -0,0 +1,414 @@
#define ES_INSTANCE_TYPE Instance
#include <essence.h>
// TODO Previewing font files from the database.
// TODO Installing fonts.
// TODO Character map.
// TODO Searching/filtering fonts.
// TODO Single instance.
#include <shared/array.cpp>
#define IMPLEMENTATION
#include <shared/array.cpp>
#include <shared/strings.cpp>
#define SETTINGS_FILE "|Settings:/Default.ini"
struct Instance : EsInstance {
Array<EsFontInformation> fonts;
EsPanel *switcher;
EsListView *fontList;
EsTextbox *fontSizeTextbox;
EsTextbox *previewTextTextbox;
EsPanel *fontPreview;
uint16_t fontPreviewID;
EsElement *fontListToolbar;
EsElement *fontPreviewToolbar;
int fontSize, fontVariant;
char *previewText;
size_t previewTextBytes;
};
EsStyle styleFontList = {
.inherit = ES_STYLE_PANEL_FILLED,
.metrics = {
.mask = ES_THEME_METRICS_GAP_MINOR | ES_THEME_METRICS_GAP_WRAP | ES_THEME_METRICS_INSETS,
.insets = ES_RECT_1(25),
.gapMinor = 15,
.gapWrap = 15,
},
};
EsStyle styleFontItem = {
.inherit = ES_STYLE_PANEL_SHEET,
.metrics = {
.mask = ES_THEME_METRICS_PREFERRED_WIDTH | ES_THEME_METRICS_PREFERRED_HEIGHT,
.preferredWidth = 300,
.preferredHeight = 250,
},
};
EsStyle styleFontInformationPanel = {
.metrics = {
.mask = ES_THEME_METRICS_INSETS | ES_THEME_METRICS_GAP_MAJOR,
.insets = ES_RECT_1(15),
.gapMajor = 10,
},
};
EsStyle styleFontName = {
.inherit = ES_STYLE_TEXT_LABEL_SECONDARY,
.metrics = {
.mask = ES_THEME_METRICS_TEXT_SIZE,
.textSize = 10,
},
};
EsStyle styleFontInformationRow = {
.metrics = {
.mask = ES_THEME_METRICS_GAP_MAJOR,
.gapMajor = 10,
},
};
EsStyle styleFontPreviewPage = {
.inherit = ES_STYLE_PANEL_DOCUMENT,
.metrics = {
.mask = ES_THEME_METRICS_INSETS,
.insets = ES_RECT_1(20),
},
};
inline int ClampInteger(int low, int high, int integer) {
if (integer < low) return low;
if (integer > high) return high;
return integer;
}
bool LoadSettings(Instance *instance) {
EsINIState state = {};
char *file = (char *) EsFileReadAll(EsLiteral(SETTINGS_FILE), &state.bytes);
if (!state.buffer) {
return false;
}
while (EsINIParse(&state)) {
if (state.value && 0 == EsStringCompareRaw(state.section, state.sectionBytes, EsLiteral("preview"))) {
if (0 == EsStringCompareRaw(state.key, state.keyBytes, EsLiteral("size"))) {
instance->fontSize = EsIntegerParse(state.value, state.valueBytes);
} else if (0 == EsStringCompareRaw(state.key, state.keyBytes, EsLiteral("variant"))) {
instance->fontVariant = EsIntegerParse(state.value, state.valueBytes);
} else if (0 == EsStringCompareRaw(state.key, state.keyBytes, EsLiteral("text"))) {
instance->previewTextBytes = state.valueBytes;
instance->previewText = (char *) EsHeapAllocate(instance->previewTextBytes, false);
EsMemoryCopy(instance->previewText, state.value, instance->previewTextBytes);
}
}
}
EsHeapFree(file);
return true;
}
void SaveSettings(Instance *instance) {
EsBuffer buffer = { .canGrow = true };
EsBufferFormat(&buffer, "[preview]\nsize=%d\nvariant=%d\ntext=%s\n",
instance->fontSize, instance->fontVariant, instance->previewTextBytes, instance->previewText);
EsFileWriteAll(EsLiteral(SETTINGS_FILE), buffer.out, buffer.position);
EsHeapFree(buffer.out);
}
int FontPreviewMessage(EsElement *element, EsMessage *message) {
if (message->type == ES_MSG_PAINT) {
// TODO Cache the text plan?
EsTextPlanProperties properties = {};
properties.flags = ES_TEXT_PLAN_SINGLE_USE | ES_TEXT_PLAN_TRIM_SPACES | ES_TEXT_WRAP | ES_TEXT_ELLIPSIS | ES_TEXT_PLAN_NO_FONT_SUBSTITUTION;
EsRectangle bounds = EsPainterBoundsInset(message->painter);
EsTextRun runs[2] = {};
EsElementGetTextStyle(element, &runs[0].style);
runs[0].style.font.family = element->userData.u;
runs[0].style.font.weight = element->instance->fontVariant % 10;
runs[0].style.font.italic = element->instance->fontVariant / 10;
runs[0].style.size = element->instance->fontSize;
runs[1].offset = element->instance->previewTextBytes;
EsTextPlan *plan = EsTextPlanCreate(&properties, bounds, element->instance->previewText, runs, 1);
EsDrawText(message->painter, plan, bounds);
}
return 0;
}
int FontListMessage(EsElement *element, EsMessage *message) {
Instance *instance = element->instance;
if (message->type == ES_MSG_LIST_VIEW_CREATE_ITEM) {
EsPanel *panel = EsPanelCreate(message->createItem.item, ES_CELL_FILL, &styleFontInformationPanel);
EsFontInformation *font = &instance->fonts[message->createItem.index.u];
EsPanel *row = EsPanelCreate(panel, ES_CELL_H_FILL | ES_PANEL_HORIZONTAL, &styleFontInformationRow);
// EsIconDisplayCreate(row, ES_FLAGS_DEFAULT, ES_STYLE_ICON_DISPLAY_SMALL, ES_ICON_FONT_X_GENERIC);
EsTextDisplayCreate(row, ES_FLAGS_DEFAULT, &styleFontName, font->name, font->nameBytes);
EsSpacerCreate(row, ES_CELL_H_FILL, ES_STYLE_SEPARATOR_HORIZONTAL);
EsElement *preview = EsCustomElementCreate(panel, ES_CELL_FILL, ES_STYLE_TEXT_PARAGRAPH);
preview->userData = font->id;
preview->messageUser = FontPreviewMessage;
size_t variants = 0;
for (uintptr_t i = 0; i < 16; i++) {
if (font->availableWeightsNormal & (1 << i)) variants++;
if (font->availableWeightsItalic & (1 << i)) variants++;
}
row = EsPanelCreate(panel, ES_CELL_H_FILL | ES_PANEL_HORIZONTAL, &styleFontInformationRow);
char description[256]; // TODO Localization.
size_t descriptionBytes = EsStringFormat(description, sizeof(description), "%s " HYPHENATION_POINT " %d variant%z",
font->categoryBytes, font->category, variants, variants == 1 ? "" : "s");
EsTextDisplayCreate(row, ES_CELL_H_FILL | ES_CELL_V_BOTTOM, ES_STYLE_TEXT_LABEL_SECONDARY, description, descriptionBytes);
EsButton *openButton = EsButtonCreate(row, ES_BUTTON_NOT_FOCUSABLE);
EsButtonSetIcon(openButton, ES_ICON_GO_NEXT_SYMBOLIC);
}
return 0;
}
void FontSizeTextboxUpdate(Instance *instance) {
char result[64];
size_t resultBytes = EsStringFormat(result, sizeof(result), "%d", instance->fontSize);
EsTextboxSelectAll(instance->fontSizeTextbox);
EsTextboxInsert(instance->fontSizeTextbox, result, resultBytes);
EsListViewInvalidateAll(instance->fontList);
}
int FontSizeTextboxMessage(EsElement *element, EsMessage *message) {
EsTextbox *textbox = (EsTextbox *) element;
Instance *instance = element->instance;
if (message->type == ES_MSG_TEXTBOX_EDIT_END) {
char *expression = EsTextboxGetContents(textbox);
EsCalculationValue value = EsCalculateFromUserExpression(expression);
EsHeapFree(expression);
if (value.error) {
return ES_REJECTED;
} else {
instance->fontSize = ClampInteger(6, 300, value.number);
FontSizeTextboxUpdate(instance);
}
} else if (message->type == ES_MSG_TEXTBOX_NUMBER_DRAG_DELTA) {
instance->fontSize = ClampInteger(6, 300, instance->fontSize + message->numberDragDelta.delta * (message->numberDragDelta.fast ? 10 : 1));
FontSizeTextboxUpdate(instance);
} else {
return 0;
}
return ES_HANDLED;
}
int PreviewTextTextboxMessage(EsElement *element, EsMessage *message) {
if (message->type == ES_MSG_TEXTBOX_UPDATED) {
EsHeapFree(element->instance->previewText);
element->instance->previewText = EsTextboxGetContents((EsTextbox *) element, &element->instance->previewTextBytes);
EsListViewInvalidateAll(element->instance->fontList);
} else {
return 0;
}
return ES_HANDLED;
}
void VariantsPopupCreate(Instance *instance, EsElement *element, EsCommand *) {
EsMenu *menu = EsMenuCreate(element, ES_FLAGS_DEFAULT);
EsPanel *panel = EsPanelCreate(menu, ES_PANEL_HORIZONTAL, ES_STYLE_PANEL_POPUP);
EsListView *list = EsListViewCreate(panel, ES_LIST_VIEW_CHOICE_SELECT | ES_LIST_VIEW_FIXED_ITEMS, ES_STYLE_LIST_CHOICE_BORDERED);
EsListViewInsertFixedItem(list, INTERFACE_STRING(FontBookVariantNormal100), 1);
EsListViewInsertFixedItem(list, INTERFACE_STRING(FontBookVariantNormal200), 2);
EsListViewInsertFixedItem(list, INTERFACE_STRING(FontBookVariantNormal300), 3);
EsListViewInsertFixedItem(list, INTERFACE_STRING(FontBookVariantNormal400), 4);
EsListViewInsertFixedItem(list, INTERFACE_STRING(FontBookVariantNormal500), 5);
EsListViewInsertFixedItem(list, INTERFACE_STRING(FontBookVariantNormal600), 6);
EsListViewInsertFixedItem(list, INTERFACE_STRING(FontBookVariantNormal700), 7);
EsListViewInsertFixedItem(list, INTERFACE_STRING(FontBookVariantNormal800), 8);
EsListViewInsertFixedItem(list, INTERFACE_STRING(FontBookVariantNormal900), 9);
EsListViewInsertFixedItem(list, INTERFACE_STRING(FontBookVariantItalic100), 11);
EsListViewInsertFixedItem(list, INTERFACE_STRING(FontBookVariantItalic200), 12);
EsListViewInsertFixedItem(list, INTERFACE_STRING(FontBookVariantItalic300), 13);
EsListViewInsertFixedItem(list, INTERFACE_STRING(FontBookVariantItalic400), 14);
EsListViewInsertFixedItem(list, INTERFACE_STRING(FontBookVariantItalic500), 15);
EsListViewInsertFixedItem(list, INTERFACE_STRING(FontBookVariantItalic600), 16);
EsListViewInsertFixedItem(list, INTERFACE_STRING(FontBookVariantItalic700), 17);
EsListViewInsertFixedItem(list, INTERFACE_STRING(FontBookVariantItalic800), 18);
EsListViewInsertFixedItem(list, INTERFACE_STRING(FontBookVariantItalic900), 19);
EsListViewSelectFixedItem(list, instance->fontVariant);
list->messageUser = [] (EsElement *element, EsMessage *message) {
if (message->type == ES_MSG_LIST_VIEW_SELECT) {
Instance *instance = element->instance;
EsGeneric selected;
if (EsListViewGetSelectedFixedItem(((EsListView *) element), &selected)) {
instance->fontVariant = selected.i;
EsListViewInvalidateAll(element->instance->fontList);
}
}
return 0;
};
EsMenuShow(menu);
}
void LoadFontsFromDatabase(Instance *instance) {
EsFontDatabaseEnumerate([] (const EsFontInformation *information, EsGeneric context) {
((Instance *) context.p)->fonts.AddPointer(information);
}, instance);
EsSort(instance->fonts.array, instance->fonts.Length(), sizeof(EsFontInformation), [] (const void *left, const void *right, EsGeneric) {
EsFontInformation *fontLeft = (EsFontInformation *) left;
EsFontInformation *fontRight = (EsFontInformation *) right;
int x = EsStringCompare(fontLeft->name, fontLeft->nameBytes, fontRight->name, fontRight->nameBytes);
if (x) return x;
return EsStringCompareRaw(fontLeft->name, fontLeft->nameBytes, fontRight->name, fontRight->nameBytes);
}, 0);
EsListViewInsert(instance->fontList, 0, 0, instance->fonts.Length() - 1);
}
void BackCommand(Instance *instance, EsElement *, EsCommand *) {
EsPanelSwitchTo(instance->switcher, instance->fontList, ES_TRANSITION_NONE);
EsWindowSwitchToolbar(instance->window, instance->fontListToolbar, ES_TRANSITION_SLIDE_DOWN);
}
void _start() {
_init();
while (true) {
EsMessage *message = EsMessageReceive();
if (message->type == ES_MSG_INSTANCE_CREATE) {
Instance *instance = EsInstanceCreate(message, INTERFACE_STRING(FontBookTitle));
EsWindowSetIcon(instance->window, ES_ICON_APPLICATIONS_FONTS);
EsPanel *rootPanel = EsPanelCreate(instance->window, ES_CELL_FILL, ES_STYLE_PANEL_WINDOW_DIVIDER);
instance->switcher = EsPanelCreate(rootPanel, ES_CELL_FILL | ES_PANEL_SWITCHER);
// Settings:
if (!LoadSettings(instance)) {
instance->fontSize = 24;
instance->fontVariant = 4 /* regular */;
instance->previewTextBytes = EsCStringLength(interfaceString_FontBookPreviewTextDefault);
instance->previewText = (char *) EsHeapAllocate(instance->previewTextBytes, false);
EsMemoryCopy(instance->previewText, interfaceString_FontBookPreviewTextDefault, instance->previewTextBytes);
}
// Font list page:
uint64_t flags = ES_CELL_FILL | ES_LIST_VIEW_TILED | ES_LIST_VIEW_CENTER_TILES;
EsListView *fontList = EsListViewCreate(instance->switcher, flags, &styleFontList, &styleFontItem);
EsListViewSetMaximumItemsPerBand(fontList, 4);
EsListViewInsertGroup(fontList, 0);
instance->fontList = fontList;
fontList->messageUser = FontListMessage;
fontList->accessKey = 'F';
LoadFontsFromDatabase(instance);
EsPanelSwitchTo(instance->switcher, instance->fontList, ES_TRANSITION_NONE);
// Font preview page:
instance->fontPreview = EsPanelCreate(instance->switcher, ES_CELL_FILL | ES_PANEL_V_SCROLL_AUTO, &styleFontPreviewPage);
// Font list toolbar:
EsElement *toolbar = EsWindowGetToolbar(instance->window, true /* create new */);
instance->fontListToolbar = toolbar;
EsPanel *section = EsPanelCreate(toolbar, ES_PANEL_HORIZONTAL);
EsTextDisplayCreate(section, ES_FLAGS_DEFAULT, 0, INTERFACE_STRING(FontBookTextSize));
instance->fontSizeTextbox = EsTextboxCreate(section, ES_TEXTBOX_EDIT_BASED, ES_STYLE_TEXTBOX_BORDERED_SINGLE_COMPACT);
instance->fontSizeTextbox->messageUser = FontSizeTextboxMessage;
instance->fontSizeTextbox->accessKey = 'S';
EsTextboxUseNumberOverlay(instance->fontSizeTextbox, false);
FontSizeTextboxUpdate(instance);
EsSpacerCreate(toolbar, ES_CELL_H_FILL);
section = EsPanelCreate(toolbar, ES_PANEL_HORIZONTAL);
EsTextDisplayCreate(section, ES_FLAGS_DEFAULT, 0, INTERFACE_STRING(FontBookPreviewText));
instance->previewTextTextbox = EsTextboxCreate(section, ES_FLAGS_DEFAULT, ES_STYLE_TEXTBOX_BORDERED_SINGLE);
instance->previewTextTextbox->messageUser = PreviewTextTextboxMessage;
instance->previewTextTextbox->accessKey = 'P';
EsTextboxInsert(instance->previewTextTextbox, instance->previewText, instance->previewTextBytes, false);
EsSpacerCreate(toolbar, ES_CELL_H_FILL);
EsButton *button = EsButtonCreate(toolbar, ES_BUTTON_DROPDOWN, {}, INTERFACE_STRING(FontBookVariants));
button->accessKey = 'V';
EsButtonOnCommand(button, VariantsPopupCreate);
// Font preview toolbar:
toolbar = EsWindowGetToolbar(instance->window, true /* create new */);
instance->fontPreviewToolbar = toolbar;
button = EsButtonCreate(toolbar, ES_FLAGS_DEFAULT, 0, INTERFACE_STRING(FontBookNavigationBack));
button->accessKey = 'B';
EsButtonSetIcon(button, ES_ICON_GO_PREVIOUS_SYMBOLIC);
EsButtonOnCommand(button, BackCommand);
} else if (message->type == ES_MSG_INSTANCE_OPEN) {
if (!message->instanceOpen.update) {
Instance *instance = message->instanceOpen.instance;
EsFontInformation information = {};
information.availableWeightsNormal = 1 << 4 /* regular */;
instance->fontPreviewID = EsFontDatabaseInsertFile(&information, message->instanceOpen.file);
// TODO Check that the font is valid.
EsElementDestroyContents(instance->fontPreview);
EsPanel *titleRow = EsPanelCreate(instance->fontPreview, ES_CELL_H_CENTER | ES_PANEL_HORIZONTAL, &styleFontInformationRow);
EsIconDisplayCreate(titleRow, ES_FLAGS_DEFAULT, ES_STYLE_ICON_DISPLAY, ES_ICON_FONT_X_GENERIC);
EsTextDisplayCreate(titleRow, ES_FLAGS_DEFAULT, ES_STYLE_TEXT_HEADING0, message->instanceOpen.name, message->instanceOpen.nameBytes);
EsSpacerCreate(instance->fontPreview, ES_FLAGS_DEFAULT, 0, 0, 20);
int sizes[] = { 12, 18, 24, 36, 48, 60, 72, 0 };
for (uintptr_t i = 0; sizes[i]; i++) {
EsPanel *row = EsPanelCreate(instance->fontPreview, ES_CELL_H_FILL | ES_PANEL_HORIZONTAL, &styleFontInformationRow);
char buffer[64];
EsTextDisplayCreate(row, ES_FLAGS_DEFAULT, 0, buffer, EsStringFormat(buffer, sizeof(buffer), "%d", sizes[i]));
EsTextDisplay *display = EsTextDisplayCreate(row, ES_TEXT_DISPLAY_NO_FONT_SUBSTITUTION);
const char *string = interfaceString_FontBookPreviewTextLongDefault;
EsTextRun runs[2] = {};
EsElementGetTextStyle(display, &runs[0].style);
runs[0].style.size = sizes[i];
runs[0].style.font.family = instance->fontPreviewID;
runs[1].offset = EsCStringLength(string);
EsTextDisplaySetStyledContents(display, string, runs, 1);
}
EsPanelSwitchTo(instance->switcher, instance->fontPreview, ES_TRANSITION_NONE);
EsWindowSwitchToolbar(instance->window, instance->fontPreviewToolbar, ES_TRANSITION_SLIDE_UP);
}
EsInstanceOpenComplete(message, true);
} else if (message->type == ES_MSG_INSTANCE_DESTROY) {
SaveSettings(message->instanceDestroy.instance);
EsHeapFree(message->instanceDestroy.instance->previewText);
// TODO Remove the font added to the font database.
}
}
}

14
apps/font_book.ini Normal file
View File

@ -0,0 +1,14 @@
[general]
name=Font Book
icon=icon_applications_fonts
[build]
source=apps/font_book.cpp
[@handler]
extension=ttf
action=open
[@handler]
extension=otf
action=open

282
apps/gl_test.c Normal file
View File

@ -0,0 +1,282 @@
// gcc -o tri tri.c -lOSMesa && ./tri
// x86_64-essence-gcc -o root/tri ports/mesa/tri.c -lOSMesa -lstdc++ -lz -g -D ESSENCE_WINDOW -D MODERN_GL
#include <GL/osmesa.h>
#include <GL/gl.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <pthread.h>
#include <assert.h>
#include <string.h>
#define IMAGE_WIDTH (700)
#define IMAGE_HEIGHT (600)
uint32_t *buffer;
#ifdef MODERN_GL
static GLenum (*glCheckFramebufferStatus)(GLenum target);
static GLint (*glGetUniformLocation)(GLuint program, const GLchar *name);
static GLuint (*glCreateProgram)();
static GLuint (*glCreateShader)(GLenum type);
static void (*glAttachShader)(GLuint program, GLuint shader);
static void (*glBindBuffer)(GLenum target, GLuint buffer);
static void (*glBindFramebuffer)(GLenum target, GLuint framebuffer);
static void (*glBindVertexArray)(GLuint array);
static void (*glBufferData)(GLenum target, GLsizeiptr size, const void *data, GLenum usage);
static void (*glCompileShader)(GLuint shader);
static void (*glDeleteFramebuffers)(GLsizei n, const GLuint *framebuffers);
static void (*glDrawBuffers)(GLsizei n, const GLenum *bufs);
static void (*glEnableVertexAttribArray)();
static void (*glFramebufferTexture2D)(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level);
static void (*glGenBuffers)(GLsizei n, GLuint *buffers);
static void (*glGenFramebuffers)(GLsizei n, GLuint *framebuffers);
static void (*glGenVertexArrays)(GLsizei n, GLuint *arrays);
static void (*glGetProgramInfoLog)(GLuint program, GLsizei bufSize, GLsizei *length, GLchar *infoLog);
static void (*glGetShaderInfoLog)(GLuint shader, GLsizei bufSize, GLsizei *length, GLchar *infoLog);
static void (*glGetShaderiv)(GLuint shader, GLenum pname, GLint *param);
static void (*glLinkProgram)(GLuint program);
static void (*glShaderSource)(GLuint shader, GLsizei count, const GLchar *const *string, const GLint *length);
static void (*glUniform1f)(GLint location, GLfloat v0);
static void (*glUniform1i)(GLint location, GLint v0);
static void (*glUniform4f)(GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3);
static void (*glUniformMatrix4fv)(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
static void (*glUseProgram)(GLuint program);
static void (*glValidateProgram)(GLuint program);
static void (*glVertexAttribPointer)(GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const void *pointer);
#define GL_CLAMP_TO_EDGE 0x812F
#define GL_VERTEX_SHADER 0x8B31
#define GL_FRAGMENT_SHADER 0x8B30
#define GL_COMPILE_STATUS 0x8B81
#define GL_LINK_STATUS 0x8B82
#define GL_TEXTURE0 0x84C0
#define GL_ARRAY_BUFFER 0x8892
#define GL_STATIC_DRAW 0x88E4
#define GL_MULTISAMPLE 0x809D
#define GL_ELEMENT_ARRAY_BUFFER 0x8893
#define GL_COLOR_ATTACHMENT0 0x8CE0
#define GL_FRAMEBUFFER 0x8D40
#define GL_FRAMEBUFFER_COMPLETE 0x8CD5
#define GL_TEXTURE1 0x84C1
#define GL_TEXTURE2 0x84C2
#define GL_BGRA 0x80E1
#endif
#ifdef ESSENCE_WINDOW
#include <essence.h>
int CanvasCallback(EsElement *element, EsMessage *message) {
if (message->type == ES_MSG_PAINT_BACKGROUND) {
*response = ES_HANDLED;
int ox = message->painter->width / 2 - IMAGE_WIDTH / 2;
int oy = message->painter->height / 2 - IMAGE_HEIGHT / 2;
EsRectangle bounds = { ox, ox + IMAGE_WIDTH, oy, oy + IMAGE_HEIGHT };
EsDrawBitmap(message->painter, bounds, buffer, IMAGE_WIDTH * 4, ES_DRAW_BITMAP_OPAQUE);
#if 0
EsPainter *painter = message->painter;
size_t stride;
uint32_t *bits;
EsPaintTargetStartDirectAccess(painter->target, &bits, NULL, NULL, &stride);
int width = painter->width, height = painter->height;
int ox = width / 2 - IMAGE_WIDTH / 2;
int oy = height / 2 - IMAGE_HEIGHT / 2;
stride /= 4;
uint32_t *start = bits + painter->offsetX + painter->offsetY * stride;
for (int i = 0; i < height; i++) {
uint32_t *destination = start + i * stride;
for (int j = 0; j < width; j++, destination++) {
int sx = j - ox, sy = i - oy;
if (sy >= 0 && sy < IMAGE_HEIGHT && sx >= 0 && sx < IMAGE_WIDTH) {
*destination = buffer[sy * IMAGE_WIDTH + sx];
} else {
*destination = 0xFF000000;
}
}
}
#endif
}
return ES_NOT_HANDLED;
}
#endif
int main(int argc, char **argv) {
#ifndef MODERN_GL
OSMesaContext context = OSMesaCreateContextExt(OSMESA_RGBA, 16, 0, 0, NULL);
buffer = (uint32_t *) malloc(IMAGE_WIDTH * IMAGE_HEIGHT * 4);
OSMesaMakeCurrent(context, buffer, GL_UNSIGNED_BYTE, IMAGE_WIDTH, IMAGE_HEIGHT);
glClearColor(0, 0, 0, 1);
glClear(GL_COLOR_BUFFER_BIT);
glLoadIdentity();
glBegin(GL_TRIANGLES);
glColor4f(1, 0, 0, 1);
glVertex2f(-1, -1);
glColor4f(0, 1, 0, 1);
glVertex2f(0, 1);
glColor4f(0, 0, 1, 1);
glVertex2f(1, -1);
glEnd();
#else
const int contextAttributes[] = {
OSMESA_FORMAT, OSMESA_RGBA,
OSMESA_DEPTH_BITS, 16,
OSMESA_PROFILE, OSMESA_CORE_PROFILE,
OSMESA_CONTEXT_MAJOR_VERSION, 3,
OSMESA_CONTEXT_MINOR_VERSION, 0,
0
};
OSMesaContext context = OSMesaCreateContextAttribs(contextAttributes, NULL);
buffer = (uint32_t *) malloc(IMAGE_WIDTH * IMAGE_HEIGHT * 4);
OSMesaMakeCurrent(context, buffer, GL_UNSIGNED_BYTE, IMAGE_WIDTH, IMAGE_HEIGHT);
#define LOADEXT(x) *(void **) &x = (void *) OSMesaGetProcAddress(#x); assert(x);
LOADEXT(glAttachShader);
LOADEXT(glBindBuffer);
LOADEXT(glBindFramebuffer);
LOADEXT(glBindVertexArray);
LOADEXT(glBufferData);
LOADEXT(glCheckFramebufferStatus);
LOADEXT(glCompileShader);
LOADEXT(glCreateProgram);
LOADEXT(glCreateShader);
LOADEXT(glDeleteFramebuffers);
LOADEXT(glDrawBuffers);
LOADEXT(glEnableVertexAttribArray);
LOADEXT(glFramebufferTexture2D);
LOADEXT(glGenBuffers);
LOADEXT(glGenFramebuffers);
LOADEXT(glGenVertexArrays);
LOADEXT(glGetProgramInfoLog);
LOADEXT(glGetShaderInfoLog);
LOADEXT(glGetShaderiv);
LOADEXT(glGetUniformLocation);
LOADEXT(glLinkProgram);
LOADEXT(glShaderSource);
LOADEXT(glUniform1f);
LOADEXT(glUniform1i);
LOADEXT(glUniform4f);
LOADEXT(glUniformMatrix4fv);
LOADEXT(glUseProgram);
LOADEXT(glValidateProgram);
LOADEXT(glVertexAttribPointer);
#undef LOADEXT
glClearColor(0, 0, 0, 1);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glEnable(GL_MULTISAMPLE);
int squareVBO, squareIBO;
float squareVBOArray[] = { -1, -1, 0.5f, /**/ -1, 1, 0.5f, /**/ 1, 1, 0.5f, /**/ 1, -1, 0.5f };
unsigned squareIBOArray[] = { 0, 1, 2, 0, 2, 3 };
glGenBuffers(1, &squareVBO);
glBindBuffer(GL_ARRAY_BUFFER, squareVBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(squareVBOArray), squareVBOArray, GL_STATIC_DRAW);
glGenBuffers(1, &squareIBO);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, squareIBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(squareIBOArray), squareIBOArray, GL_STATIC_DRAW);
const char *vertexShaderSource =
"#version 330\n"
"layout(location = 0) in vec3 Position;\n"
"uniform mat4 transform;\n"
"out vec2 TexCoord0;\n"
"void main() { \n"
" gl_Position = transform * vec4(Position, 1.0);\n"
"}\n";
const char *fragmentShaderSource =
"#version 330\n"
"layout(location = 0) out vec4 FragColor;\n"
"in vec2 TexCoord0;\n"
"uniform vec4 blendColor;\n"
"void main() { \n"
" FragColor = blendColor;\n"
"}\n";
const char *shaderSources[] = { vertexShaderSource, fragmentShaderSource };
int shaderSourceLengths[] = { strlen(vertexShaderSource), strlen(fragmentShaderSource) };
unsigned shader = glCreateProgram();
char shaderInfoLog[1024];
unsigned vertexShader = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertexShader, 1, shaderSources + 0, shaderSourceLengths + 0);
glCompileShader(vertexShader);
glGetShaderInfoLog(vertexShader, sizeof(shaderInfoLog), NULL, shaderInfoLog);
glAttachShader(shader, vertexShader);
printf("Vertex shader log: '%s'\n", shaderInfoLog);
unsigned fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragmentShader, 1, shaderSources + 1, shaderSourceLengths + 1);
glCompileShader(fragmentShader);
glGetShaderInfoLog(fragmentShader, sizeof(shaderInfoLog), NULL, shaderInfoLog);
glAttachShader(shader, fragmentShader);
printf("Fragment shader log: '%s'\n", shaderInfoLog);
glLinkProgram(shader);
glValidateProgram(shader);
int shaderBlendColor = glGetUniformLocation(shader, "blendColor");
int shaderTransform = glGetUniformLocation(shader, "transform");
glBindFramebuffer(GL_FRAMEBUFFER, 0);
unsigned vertexArray;
glGenVertexArrays(1, &vertexArray);
glBindVertexArray(vertexArray);
glEnableVertexAttribArray(0);
glEnableVertexAttribArray(1);
glBindBuffer(GL_ARRAY_BUFFER, squareVBO);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, squareIBO);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (const GLvoid *) 0);
glClear(GL_COLOR_BUFFER_BIT);
glUseProgram(shader);
float transform[] = { 0.5f, 0, 0, 0, /**/ 0, 0.5f, 0, 0, /**/ 0, 0, 1, 0, /**/ 0, 0, 0, 1 };
glUniformMatrix4fv(shaderTransform, 1, GL_TRUE, transform);
glUniform4f(shaderBlendColor, 1, 0, 1, 1);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
#endif
glFinish();
#ifndef ESSENCE_WINDOW
FILE *out = fopen("test.ppm", "wb");
fprintf(out, "P6\n%d %d\n255\n", IMAGE_WIDTH, IMAGE_HEIGHT);
for (int j = 0; j < IMAGE_HEIGHT; j++) {
for (int i = 0; i < IMAGE_WIDTH; i++) {
fwrite(buffer + j * IMAGE_WIDTH + i, 1, 3, out);
}
}
fclose(out);
OSMesaDestroyContext(context);
#else
while (true) {
EsMessage *message = EsMessageReceive();
if (message->type == ES_MSG_INSTANCE_CREATE) {
EsInstance *instance = EsInstanceCreate(message, "GL Test");
EsWindowSetTitle(instance->window, "GL Test", -1);
EsCustomElementCreate(instance->window, ES_CELL_FILL, (EsElementProperties) { .callback = CanvasCallback });
}
}
#endif
free(buffer);
printf("all done :)\n");
return 0;
}

6
apps/gl_test.ini Normal file
View File

@ -0,0 +1,6 @@
[general]
name=GL Test
[build]
custom_compile_command=x86_64-essence-gcc -o "root/Applications/GL Test/Entry.esx" apps/gl_test.c -lOSMesa -lstdc++ -lz -g -D ESSENCE_WINDOW
require=root/Applications/POSIX/lib/libOSMesa.a

29
apps/hello.c Normal file
View File

@ -0,0 +1,29 @@
// Include the Essence system header.
#include <essence.h>
void _start() {
// We're not using the C standard library,
// so we need to initialise global constructors manually.
_init();
while (true) {
// Receive a message from the system.
EsMessage *message = EsMessageReceive();
if (message->type == ES_MSG_INSTANCE_CREATE) {
// The system wants us to create an instance of our application.
// Call EsInstanceCreate with the message and application name.
EsInstance *instance = EsInstanceCreate(message, "Hello", -1);
// Create a text display with the "Hello, world!" message.
EsTextDisplayCreate(
instance->window, // Add the text display to the instance's window.
ES_CELL_FILL, // The text display should fill the window.
ES_STYLE_PANEL_WINDOW_BACKGROUND, // Use the window background style.
"Hello, world!", -1); // Pass -1 for a zero-terminated string.
// Keep receiving messages in a loop,
// so the system can handle input messages for the window.
}
}
}

5
apps/hello.ini Normal file
View File

@ -0,0 +1,5 @@
[general]
name=Hello
[build]
source=apps/hello.c

840
apps/image_editor.cpp Normal file
View File

@ -0,0 +1,840 @@
// TODO Saving.
// TODO Don't use an EsPaintTarget for the bitmap?
// TODO Show brush preview.
// TODO Other tools: text, selection.
// TODO Resize and crop image.
// TODO Clipboard.
// TODO Clearing textbox undo from EsTextboxInsert?
// TODO Handling out of memory.
// TODO Color palette.
// TODO More brushes?
// TODO Grid.
// TODO Zoom and pan with EsCanvasPane.
// TODO Status bar.
// TODO Clearing undo if too much memory is used.
#define ES_INSTANCE_TYPE Instance
#include <essence.h>
#include <shared/array.cpp>
#include <shared/strings.cpp>
#ifdef OS_ESSENCE
#define IMPLEMENTATION
#include <shared/array.cpp>
#endif
#define TILE_SIZE (128)
struct Tile {
size_t referenceCount;
uint64_t ownerID;
uint32_t bits[TILE_SIZE * TILE_SIZE];
};
struct Image {
Tile **tiles;
size_t tileCountX, tileCountY;
uint64_t id;
uint32_t width, height;
};
struct Instance : EsInstance {
EsElement *canvas;
EsColorWell *colorWell;
EsTextbox *brushSize;
EsPanel *toolPanel;
EsButton *toolDropdown;
EsPaintTarget *bitmap;
uint32_t bitmapWidth, bitmapHeight;
Image image;
uint64_t nextImageID;
EsCommand commandBrush;
EsCommand commandFill;
EsCommand commandRectangle;
EsCommand commandSelect;
EsCommand commandText;
// Data while drawing:
EsRectangle modifiedBounds;
float previousPointX, previousPointY;
bool dragged;
};
const EsStyle styleBitmapSizeTextbox = {
.inherit = ES_STYLE_TEXTBOX_BORDERED_SINGLE_COMPACT,
.metrics = {
.mask = ES_THEME_METRICS_PREFERRED_WIDTH,
.preferredWidth = 70,
},
};
const EsStyle styleImageMenuTable = {
.metrics = {
.mask = ES_THEME_METRICS_GAP_ALL | ES_THEME_METRICS_INSETS,
.insets = ES_RECT_4(20, 20, 5, 8),
.gapMajor = 6,
.gapMinor = 6,
},
};
Image ImageFork(Instance *instance, Image image, uint32_t newWidth = 0, uint32_t newHeight = 0) {
Image copy = image;
copy.width = newWidth ?: image.width;
copy.height = newHeight ?: image.height;
copy.tileCountX = newWidth ? (newWidth + TILE_SIZE - 1) / TILE_SIZE : image.tileCountX;
copy.tileCountY = newHeight ? (newHeight + TILE_SIZE - 1) / TILE_SIZE : image.tileCountY;
copy.id = instance->nextImageID++;
copy.tiles = (Tile **) EsHeapAllocate(copy.tileCountX * copy.tileCountY * sizeof(Tile *), true);
for (uintptr_t y = 0; y < copy.tileCountY; y++) {
for (uintptr_t x = 0; x < copy.tileCountX; x++) {
uintptr_t source = y * image.tileCountX + x;
uintptr_t destination = y * copy.tileCountX + x;
if (y < image.tileCountY && x < image.tileCountX && image.tiles[source]) {
image.tiles[source]->referenceCount++;
copy.tiles[destination] = image.tiles[source];
}
}
}
return copy;
}
void ImageDelete(Image image) {
for (uintptr_t i = 0; i < image.tileCountX * image.tileCountY; i++) {
image.tiles[i]->referenceCount--;
if (!image.tiles[i]->referenceCount) {
// EsPrint("free tile %d, %d from image %d\n", i % image.tileCountX, i / image.tileCountX, image.tiles[i]->ownerID);
EsHeapFree(image.tiles[i]);
}
}
EsHeapFree(image.tiles);
}
void ImageCopyToPaintTarget(Instance *instance, const Image *image) {
uint32_t *bits;
size_t width, height, stride;
EsPaintTargetStartDirectAccess(instance->bitmap, &bits, &width, &height, &stride);
for (int32_t i = 0; i < (int32_t) image->tileCountY; i++) {
for (int32_t j = 0; j < (int32_t) image->tileCountX; j++) {
Tile *tile = image->tiles[i * image->tileCountX + j];
int32_t copyWidth = TILE_SIZE, copyHeight = TILE_SIZE;
if (j * TILE_SIZE + copyWidth > (int32_t) width) {
copyWidth = width - j * TILE_SIZE;
}
if (i * TILE_SIZE + copyHeight > (int32_t) height) {
copyHeight = height - i * TILE_SIZE;
}
if (tile) {
for (int32_t y = 0; y < copyHeight; y++) {
for (int32_t x = 0; x < copyWidth; x++) {
bits[stride / 4 * (y + i * TILE_SIZE) + (x + j * TILE_SIZE)] = tile->bits[y * TILE_SIZE + x];
}
}
} else {
for (int32_t y = 0; y < copyHeight; y++) {
for (int32_t x = 0; x < copyWidth; x++) {
bits[stride / 4 * (y + i * TILE_SIZE) + (x + j * TILE_SIZE)] = 0;
}
}
}
}
}
EsPaintTargetEndDirectAccess(instance->bitmap);
}
Tile *ImageUpdateTile(Image *image, uint32_t x, uint32_t y, bool copyOldBits) {
EsAssert(x < image->tileCountX && y < image->tileCountY);
Tile **tileReference = image->tiles + y * image->tileCountX + x;
Tile *tile = *tileReference;
if (!tile || tile->ownerID != image->id) {
if (tile && tile->referenceCount == 1) {
tile->ownerID = image->id;
// EsPrint("reuse tile %d, %d for image %d\n", x, y, image->id);
} else {
Tile *old = tile;
if (old) old->referenceCount--;
*tileReference = tile = (Tile *) EsHeapAllocate(sizeof(Tile), false);
tile->referenceCount = 1;
tile->ownerID = image->id;
if (copyOldBits && old) {
EsMemoryCopy(tile->bits, old->bits, sizeof(old->bits));
}
// EsPrint("allocate new tile %d, %d for image %d\n", x, y, image->id);
}
}
return tile;
}
void ImageCopyFromPaintTarget(Instance *instance, Image *image, EsRectangle modifiedBounds) {
uint32_t *bits;
size_t width, height, stride;
EsPaintTargetStartDirectAccess(instance->bitmap, &bits, &width, &height, &stride);
modifiedBounds = EsRectangleIntersection(modifiedBounds, ES_RECT_4(0, width, 0, height));
for (int32_t i = modifiedBounds.t / TILE_SIZE; i <= modifiedBounds.b / TILE_SIZE; i++) {
for (int32_t j = modifiedBounds.l / TILE_SIZE; j <= modifiedBounds.r / TILE_SIZE; j++) {
if ((uint32_t) j >= image->tileCountX || (uint32_t) i >= image->tileCountY) {
continue;
}
Tile *tile = ImageUpdateTile(image, j, i, false);
int32_t copyWidth = TILE_SIZE, copyHeight = TILE_SIZE;
if (j * TILE_SIZE + copyWidth > (int32_t) width) {
copyWidth = width - j * TILE_SIZE;
}
if (i * TILE_SIZE + copyHeight > (int32_t) height) {
copyHeight = height - i * TILE_SIZE;
}
for (int32_t y = 0; y < copyHeight; y++) {
for (int32_t x = 0; x < copyWidth; x++) {
tile->bits[y * TILE_SIZE + x] = bits[stride / 4 * (y + i * TILE_SIZE) + (x + j * TILE_SIZE)];
}
}
}
}
EsPaintTargetEndDirectAccess(instance->bitmap);
}
void ImageUndoMessage(const void *item, EsUndoManager *manager, EsMessage *message) {
const Image *image = (const Image *) item;
Instance *instance = EsUndoGetInstance(manager);
if (message->type == ES_MSG_UNDO_INVOKE) {
EsUndoPush(manager, ImageUndoMessage, &instance->image, sizeof(Image));
instance->image = *image;
if (instance->bitmapWidth != image->width || instance->bitmapHeight != image->height) {
instance->bitmapWidth = image->width;
instance->bitmapHeight = image->height;
EsPaintTargetDestroy(instance->bitmap);
instance->bitmap = EsPaintTargetCreate(instance->bitmapWidth, instance->bitmapHeight, false);
EsElementRelayout(EsElementGetLayoutParent(instance->canvas));
}
ImageCopyToPaintTarget(instance, image);
EsElementRepaint(instance->canvas);
} else if (message->type == ES_MSG_UNDO_CANCEL) {
ImageDelete(*image);
}
}
int BrushSizeMessage(EsElement *element, EsMessage *message) {
EsTextbox *textbox = (EsTextbox *) element;
if (message->type == ES_MSG_TEXTBOX_NUMBER_DRAG_DELTA) {
double oldValue = EsTextboxGetContentsAsDouble(textbox);
double newValue = oldValue + message->numberDragDelta.delta * (message->numberDragDelta.fast ? 1 : 0.1);
if (newValue < 1) {
newValue = 1;
} else if (newValue > 1000) {
newValue = 1000;
}
char result[64];
size_t resultBytes = EsStringFormat(result, sizeof(result), "%d.%d", (int) newValue, (int) (newValue * 10) % 10);
EsTextboxSelectAll(textbox);
EsTextboxInsert(textbox, result, resultBytes);
}
return 0;
}
EsRectangle DrawFill(Instance *instance, EsPoint point) {
uint32_t color = 0xFF000000 | EsColorWellGetRGB(instance->colorWell);
EsRectangle modifiedBounds = ES_RECT_4(point.x, point.x, point.y, point.y);
if ((uint32_t) point.x >= instance->bitmapWidth || (uint32_t) point.y >= instance->bitmapHeight) {
return {};
}
uint32_t *bits;
size_t width, height, stride;
EsPaintTargetStartDirectAccess(instance->bitmap, &bits, &width, &height, &stride);
stride /= 4;
Array<EsPoint> pointsToVisit = {};
uint32_t replaceColor = bits[point.y * stride + point.x];
if (replaceColor != color) pointsToVisit.Add(point);
while (pointsToVisit.Length()) {
EsPoint startPoint = pointsToVisit.Pop();
if (startPoint.y < modifiedBounds.t) {
modifiedBounds.t = startPoint.y;
}
if (startPoint.y > modifiedBounds.b) {
modifiedBounds.b = startPoint.y;
}
for (ptrdiff_t delta = -1; delta <= 1; delta += 2) {
EsPoint point = startPoint;
uint32_t *pointer = bits + point.y * stride + point.x;
bool spaceAbove = false;
bool spaceBelow = false;
if (delta == 1) {
point.x += delta;
pointer += delta;
if (point.x == (int32_t) width) {
break;
}
}
while (true) {
if (*pointer != replaceColor) {
break;
}
*pointer = color;
if (point.x < modifiedBounds.l) {
modifiedBounds.l = point.x;
}
if (point.x > modifiedBounds.r) {
modifiedBounds.r = point.x;
}
if (point.y) {
if (!spaceAbove && pointer[-stride] == replaceColor) {
spaceAbove = true;
pointsToVisit.Add({ point.x, point.y - 1 });
} else if (spaceAbove && pointer[-stride] != replaceColor) {
spaceAbove = false;
}
}
if (point.y != (int32_t) height - 1) {
if (!spaceBelow && pointer[stride] == replaceColor) {
spaceBelow = true;
pointsToVisit.Add({ point.x, point.y + 1 });
} else if (spaceBelow && pointer[stride] != replaceColor) {
spaceBelow = false;
}
}
point.x += delta;
pointer += delta;
if (point.x == (int32_t) width || point.x < 0) {
break;
}
}
}
}
modifiedBounds.r++, modifiedBounds.b += 2;
pointsToVisit.Free();
EsPaintTargetEndDirectAccess(instance->bitmap);
EsElementRepaint(instance->canvas, &modifiedBounds);
return modifiedBounds;
}
EsRectangle DrawLine(Instance *instance, bool force = false) {
EsPoint point = EsMouseGetPosition(instance->canvas);
float brushSize = EsTextboxGetContentsAsDouble(instance->brushSize);
float spacing = brushSize * 0.1f;
uint32_t color = 0xFF000000 | EsColorWellGetRGB(instance->colorWell);
EsRectangle modifiedBounds = ES_RECT_4(instance->bitmapWidth, 0, instance->bitmapHeight, 0);
uint32_t *bits;
size_t width, height, stride;
EsPaintTargetStartDirectAccess(instance->bitmap, &bits, &width, &height, &stride);
stride /= 4;
// Draw the line.
while (true) {
float dx = point.x - instance->previousPointX;
float dy = point.y - instance->previousPointY;
float distance = EsCRTsqrtf(dx * dx + dy * dy);
if (distance < spacing && !force) {
break;
}
int32_t x0 = instance->previousPointX;
int32_t y0 = instance->previousPointY;
EsRectangle bounds = ES_RECT_4(x0 - brushSize / 2 - 1, x0 + brushSize / 2 + 1,
y0 - brushSize / 2 - 1, y0 + brushSize / 2 + 1);
bounds = EsRectangleIntersection(bounds, ES_RECT_4(0, instance->bitmapWidth, 0, instance->bitmapHeight));
modifiedBounds = EsRectangleBounding(modifiedBounds, bounds);
for (int32_t y = bounds.t; y < bounds.b; y++) {
for (int32_t x = bounds.l; x < bounds.r; x++) {
float distance = (x - x0) * (x - x0) + (y - y0) * (y - y0);
if (distance < brushSize * brushSize * 0.25f) {
bits[y * stride + x] = color;
}
}
}
if (force) {
break;
}
instance->previousPointX += dx / distance * spacing;
instance->previousPointY += dy / distance * spacing;
}
// Repaint the canvas.
modifiedBounds.r++, modifiedBounds.b++;
EsPaintTargetEndDirectAccess(instance->bitmap);
EsElementRepaint(instance->canvas, &modifiedBounds);
return modifiedBounds;
}
int CanvasMessage(EsElement *element, EsMessage *message) {
Instance *instance = element->instance;
if (message->type == ES_MSG_PAINT) {
EsPainter *painter = message->painter;
EsRectangle bounds = EsPainterBoundsInset(painter);
EsRectangle area = ES_RECT_4(bounds.l, bounds.l + instance->bitmapWidth, bounds.t, bounds.t + instance->bitmapHeight);
EsDrawPaintTarget(painter, instance->bitmap, area, ES_RECT_4(0, instance->bitmapWidth, 0, instance->bitmapHeight), 0xFF);
if (instance->commandRectangle.check == ES_CHECK_CHECKED && instance->dragged) {
EsRectangle rectangle = instance->modifiedBounds;
rectangle.l += painter->offsetX, rectangle.r += painter->offsetX;
rectangle.t += painter->offsetY, rectangle.b += painter->offsetY;
EsDrawBlock(painter, rectangle, 0xFF000000 | EsColorWellGetRGB(instance->colorWell));
}
} else if ((message->type == ES_MSG_MOUSE_LEFT_DRAG || message->type == ES_MSG_MOUSE_MOVED)
&& EsMouseIsLeftHeld() && instance->commandBrush.check == ES_CHECK_CHECKED) {
instance->modifiedBounds = EsRectangleBounding(DrawLine(instance), instance->modifiedBounds);
} else if (message->type == ES_MSG_MOUSE_LEFT_DRAG && instance->commandRectangle.check == ES_CHECK_CHECKED) {
EsRectangle previous = instance->modifiedBounds;
EsPoint point = EsMouseGetPosition(element);
EsRectangle rectangle;
if (point.x < instance->previousPointX) rectangle.l = point.x, rectangle.r = instance->previousPointX + 1;
else rectangle.l = instance->previousPointX, rectangle.r = point.x + 1;
if (point.y < instance->previousPointY) rectangle.t = point.y, rectangle.b = instance->previousPointY + 1;
else rectangle.t = instance->previousPointY, rectangle.b = point.y + 1;
instance->modifiedBounds = rectangle;
EsRectangle bounding = EsRectangleBounding(rectangle, previous);
EsElementRepaint(element, &bounding);
instance->dragged = true;
} else if (message->type == ES_MSG_MOUSE_LEFT_DOWN && instance->commandBrush.check == ES_CHECK_CHECKED) {
EsUndoPush(instance->undoManager, ImageUndoMessage, &instance->image, sizeof(Image));
instance->image = ImageFork(instance, instance->image);
EsPoint point = EsMouseGetPosition(element);
instance->previousPointX = point.x, instance->previousPointY = point.y;
instance->modifiedBounds = ES_RECT_4(instance->bitmapWidth, 0, instance->bitmapHeight, 0);
DrawLine(instance, true);
} else if (message->type == ES_MSG_MOUSE_LEFT_DOWN && instance->commandRectangle.check == ES_CHECK_CHECKED) {
EsPoint point = EsMouseGetPosition(element);
instance->previousPointX = point.x, instance->previousPointY = point.y;
instance->dragged = false;
} else if (message->type == ES_MSG_MOUSE_LEFT_UP && instance->commandBrush.check == ES_CHECK_CHECKED) {
ImageCopyFromPaintTarget(instance, &instance->image, instance->modifiedBounds);
} else if (message->type == ES_MSG_MOUSE_LEFT_UP && instance->commandRectangle.check == ES_CHECK_CHECKED && instance->dragged) {
instance->dragged = false;
EsUndoPush(instance->undoManager, ImageUndoMessage, &instance->image, sizeof(Image));
instance->image = ImageFork(instance, instance->image);
EsPainter painter = {};
painter.clip = ES_RECT_4(0, instance->bitmapWidth, 0, instance->bitmapHeight);
painter.target = instance->bitmap;
EsDrawBlock(&painter, instance->modifiedBounds, 0xFF000000 | EsColorWellGetRGB(instance->colorWell));
ImageCopyFromPaintTarget(instance, &instance->image, instance->modifiedBounds);
} else if (message->type == ES_MSG_MOUSE_LEFT_UP && instance->commandFill.check == ES_CHECK_CHECKED) {
EsUndoPush(instance->undoManager, ImageUndoMessage, &instance->image, sizeof(Image));
instance->image = ImageFork(instance, instance->image);
EsRectangle modifiedBounds = DrawFill(instance, EsMouseGetPosition(element));
ImageCopyFromPaintTarget(instance, &instance->image, modifiedBounds);
} else if (message->type == ES_MSG_GET_CURSOR) {
message->cursorStyle = ES_CURSOR_CROSS_HAIR_PICK;
} else if (message->type == ES_MSG_GET_WIDTH) {
message->measure.width = instance->bitmapWidth;
} else if (message->type == ES_MSG_GET_HEIGHT) {
message->measure.height = instance->bitmapHeight;
} else {
return 0;
}
return ES_HANDLED;
}
void CommandSelectTool(Instance *instance, EsElement *, EsCommand *command) {
if (command->check == ES_CHECK_CHECKED) {
return;
}
EsCommandSetCheck(&instance->commandBrush, ES_CHECK_UNCHECKED, false);
EsCommandSetCheck(&instance->commandFill, ES_CHECK_UNCHECKED, false);
EsCommandSetCheck(&instance->commandRectangle, ES_CHECK_UNCHECKED, false);
EsCommandSetCheck(&instance->commandSelect, ES_CHECK_UNCHECKED, false);
EsCommandSetCheck(&instance->commandText, ES_CHECK_UNCHECKED, false);
EsCommandSetCheck(command, ES_CHECK_CHECKED, false);
if (command == &instance->commandBrush) EsButtonSetIcon(instance->toolDropdown, ES_ICON_DRAW_FREEHAND);
if (command == &instance->commandFill) EsButtonSetIcon(instance->toolDropdown, ES_ICON_COLOR_FILL);
if (command == &instance->commandRectangle) EsButtonSetIcon(instance->toolDropdown, ES_ICON_DRAW_RECTANGLE);
if (command == &instance->commandSelect) EsButtonSetIcon(instance->toolDropdown, ES_ICON_OBJECT_GROUP);
if (command == &instance->commandText) EsButtonSetIcon(instance->toolDropdown, ES_ICON_DRAW_TEXT);
instance->dragged = false;
}
int BitmapSizeTextboxMessage(EsElement *element, EsMessage *message) {
EsTextbox *textbox = (EsTextbox *) element;
Instance *instance = textbox->instance;
if (message->type == ES_MSG_TEXTBOX_EDIT_END || message->type == ES_MSG_TEXTBOX_NUMBER_DRAG_END) {
char *expression = EsTextboxGetContents(textbox);
EsCalculationValue value = EsCalculateFromUserExpression(expression);
EsHeapFree(expression);
if (value.error) {
return ES_REJECTED;
}
if (value.number < 1) value.number = 1;
else if (value.number > 20000) value.number = 20000;
int newSize = (int) (value.number + 0.5);
char result[64];
size_t resultBytes = EsStringFormat(result, sizeof(result), "%d", newSize);
EsTextboxSelectAll(textbox);
EsTextboxInsert(textbox, result, resultBytes);
int oldSize = textbox->userData.i ? instance->bitmapHeight : instance->bitmapWidth;
if (oldSize == newSize) {
return ES_HANDLED;
}
EsRectangle clearRegion;
if (textbox->userData.i) {
instance->bitmapHeight = newSize;
clearRegion = ES_RECT_4(0, instance->bitmapWidth, oldSize, newSize);
} else {
instance->bitmapWidth = newSize;
clearRegion = ES_RECT_4(oldSize, newSize, 0, instance->bitmapHeight);
}
EsUndoPush(instance->undoManager, ImageUndoMessage, &instance->image, sizeof(Image));
instance->image = ImageFork(instance, instance->image, instance->bitmapWidth, instance->bitmapHeight);
EsPaintTargetDestroy(instance->bitmap);
instance->bitmap = EsPaintTargetCreate(instance->bitmapWidth, instance->bitmapHeight, false);
ImageCopyToPaintTarget(instance, &instance->image);
EsPainter painter = {};
painter.clip = ES_RECT_4(0, instance->bitmapWidth, 0, instance->bitmapHeight);
painter.target = instance->bitmap;
EsDrawBlock(&painter, clearRegion, 0xFFFFFFFF);
ImageCopyFromPaintTarget(instance, &instance->image, clearRegion);
EsElementRelayout(EsElementGetLayoutParent(instance->canvas));
return ES_HANDLED;
} else if (message->type == ES_MSG_TEXTBOX_NUMBER_DRAG_DELTA) {
int oldValue = EsTextboxGetContentsAsDouble(textbox);
int newValue = oldValue + message->numberDragDelta.delta * (message->numberDragDelta.fast ? 10 : 1);
if (newValue < 1) newValue = 1;
else if (newValue > 20000) newValue = 20000;
char result[64];
size_t resultBytes = EsStringFormat(result, sizeof(result), "%d", newValue);
EsTextboxSelectAll(textbox);
EsTextboxInsert(textbox, result, resultBytes);
return ES_HANDLED;
}
return 0;
}
void ImageTransform(EsMenu *menu, EsGeneric context) {
Instance *instance = menu->instance;
EsUndoPush(instance->undoManager, ImageUndoMessage, &instance->image, sizeof(Image));
uint32_t *bits;
size_t width, height, stride;
EsPaintTargetStartDirectAccess(instance->bitmap, &bits, &width, &height, &stride);
EsPaintTarget *newTarget = nullptr;
uint32_t *newBits = nullptr;
size_t newStride = 0;
if (context.i == 1 || context.i == 2) {
instance->image = ImageFork(instance, instance->image, height, width);
newTarget = EsPaintTargetCreate(height, width, false);
EsPaintTargetStartDirectAccess(newTarget, &newBits, &height, &width, &newStride);
} else {
instance->image = ImageFork(instance, instance->image);
}
if (context.i == 1 /* rotate left */) {
for (uintptr_t i = 0; i < height; i++) {
for (uintptr_t j = 0; j < width; j++) {
newBits[(width - j - 1) * newStride / 4 + i] = bits[i * stride / 4 + j];
}
}
} else if (context.i == 2 /* rotate right */) {
for (uintptr_t i = 0; i < height; i++) {
for (uintptr_t j = 0; j < width; j++) {
newBits[j * newStride / 4 + (height - i - 1)] = bits[i * stride / 4 + j];
}
}
} else if (context.i == 3 /* flip horizontally */) {
for (uintptr_t i = 0; i < height; i++) {
for (uintptr_t j = 0; j < width / 2; j++) {
uint32_t temporary = bits[i * stride / 4 + j];
bits[i * stride / 4 + j] = bits[i * stride / 4 + (width - j - 1)];
bits[i * stride / 4 + (width - j - 1)] = temporary;
}
}
} else if (context.i == 4 /* flip vertically */) {
for (uintptr_t i = 0; i < height / 2; i++) {
for (uintptr_t j = 0; j < width; j++) {
uint32_t temporary = bits[i * stride / 4 + j];
bits[i * stride / 4 + j] = bits[(height - i - 1) * stride / 4 + j];
bits[(height - i - 1) * stride / 4 + j] = temporary;
}
}
}
EsPaintTargetEndDirectAccess(instance->bitmap);
if (newTarget) {
EsPaintTargetDestroy(instance->bitmap);
instance->bitmap = newTarget;
size_t width, height;
EsPaintTargetGetSize(instance->bitmap, &width, &height);
instance->bitmapWidth = width;
instance->bitmapHeight = height;
EsElementRelayout(EsElementGetLayoutParent(instance->canvas));
}
ImageCopyFromPaintTarget(instance, &instance->image, ES_RECT_4(0, instance->bitmapWidth, 0, instance->bitmapHeight));
EsElementRepaint(instance->canvas);
}
void MenuTools(Instance *instance, EsElement *element, EsCommand *) {
EsMenu *menu = EsMenuCreate(element);
EsMenuAddCommandsFromToolbar(menu, instance->toolPanel);
EsMenuShow(menu);
}
void MenuImage(Instance *instance, EsElement *element, EsCommand *) {
EsMenu *menu = EsMenuCreate(element);
EsMenuAddItem(menu, ES_MENU_ITEM_HEADER, INTERFACE_STRING(ImageEditorCanvasSize));
EsPanel *table = EsPanelCreate(menu, ES_PANEL_HORIZONTAL | ES_PANEL_TABLE, &styleImageMenuTable);
EsPanelSetBands(table, 2, 2);
char buffer[64];
size_t bytes;
EsTextbox *textbox;
bytes = EsStringFormat(buffer, sizeof(buffer), "%d", instance->bitmapWidth);
EsTextDisplayCreate(table, ES_CELL_H_RIGHT, ES_STYLE_TEXT_LABEL, INTERFACE_STRING(ImageEditorPropertyWidth));
textbox = EsTextboxCreate(table, ES_TEXTBOX_EDIT_BASED, &styleBitmapSizeTextbox);
EsTextboxInsert(textbox, buffer, bytes, false);
textbox->userData.i = 0;
textbox->messageUser = BitmapSizeTextboxMessage;
EsTextboxUseNumberOverlay(textbox, true);
bytes = EsStringFormat(buffer, sizeof(buffer), "%d", instance->bitmapHeight);
EsTextDisplayCreate(table, ES_CELL_H_RIGHT, ES_STYLE_TEXT_LABEL, INTERFACE_STRING(ImageEditorPropertyHeight));
textbox = EsTextboxCreate(table, ES_TEXTBOX_EDIT_BASED, &styleBitmapSizeTextbox);
EsTextboxInsert(textbox, buffer, bytes, false);
textbox->userData.i = 1;
textbox->messageUser = BitmapSizeTextboxMessage;
EsTextboxUseNumberOverlay(textbox, true);
EsMenuAddSeparator(menu);
EsMenuAddItem(menu, ES_MENU_ITEM_HEADER, INTERFACE_STRING(ImageEditorImageTransformations));
EsMenuAddItem(menu, ES_FLAGS_DEFAULT, INTERFACE_STRING(ImageEditorRotateLeft), ImageTransform, 1);
EsMenuAddItem(menu, ES_FLAGS_DEFAULT, INTERFACE_STRING(ImageEditorRotateRight), ImageTransform, 2);
EsMenuAddItem(menu, ES_FLAGS_DEFAULT, INTERFACE_STRING(ImageEditorFlipHorizontally), ImageTransform, 3);
EsMenuAddItem(menu, ES_FLAGS_DEFAULT, INTERFACE_STRING(ImageEditorFlipVertically), ImageTransform, 4);
EsMenuShow(menu);
}
void InstanceCreate(EsMessage *message) {
Instance *instance = EsInstanceCreate(message, INTERFACE_STRING(ImageEditorTitle));
EsElement *toolbar = EsWindowGetToolbar(instance->window);
// Register commands.
EsCommandRegister(&instance->commandBrush, instance, CommandSelectTool, 1, "N", true);
EsCommandRegister(&instance->commandFill, instance, CommandSelectTool, 2, "Shift+B", true);
EsCommandRegister(&instance->commandRectangle, instance, CommandSelectTool, 3, "Shift+R", true);
EsCommandRegister(&instance->commandSelect, instance, CommandSelectTool, 4, "R", false);
EsCommandRegister(&instance->commandText, instance, CommandSelectTool, 5, "T", false);
EsCommandSetCheck(&instance->commandBrush, ES_CHECK_CHECKED, false);
// Create the toolbar.
EsButton *button;
button = EsButtonCreate(toolbar, ES_BUTTON_DROPDOWN, ES_STYLE_PUSH_BUTTON_TOOLBAR_BIG, INTERFACE_STRING(ImageEditorImage));
EsButtonSetIcon(button, ES_ICON_IMAGE_X_GENERIC);
button->accessKey = 'I';
EsSpacerCreate(toolbar, ES_FLAGS_DEFAULT);
EsButtonOnCommand(button, MenuImage);
button = EsButtonCreate(toolbar, ES_FLAGS_DEFAULT, ES_STYLE_PUSH_BUTTON_TOOLBAR_MEDIUM);
EsCommandAddButton(EsCommandByID(instance, ES_COMMAND_UNDO), button);
EsButtonSetIcon(button, ES_ICON_EDIT_UNDO_SYMBOLIC);
button->accessKey = 'U';
button = EsButtonCreate(toolbar, ES_FLAGS_DEFAULT, ES_STYLE_PUSH_BUTTON_TOOLBAR_MEDIUM);
EsCommandAddButton(EsCommandByID(instance, ES_COMMAND_REDO), button);
EsButtonSetIcon(button, ES_ICON_EDIT_REDO_SYMBOLIC);
button->accessKey = 'R';
EsSpacerCreate(toolbar, ES_CELL_FILL);
button = instance->toolDropdown = EsButtonCreate(toolbar, ES_BUTTON_DROPDOWN, ES_STYLE_PUSH_BUTTON_TOOLBAR_BIG, INTERFACE_STRING(ImageEditorPickTool));
EsButtonSetIcon(button, ES_ICON_DRAW_FREEHAND);
EsButtonOnCommand(button, MenuTools);
button->accessKey = 'T';
instance->toolPanel = EsPanelCreate(toolbar, ES_PANEL_HORIZONTAL, ES_STYLE_PANEL_TOOLBAR);
button = EsButtonCreate(instance->toolPanel, ES_FLAGS_DEFAULT, ES_STYLE_PUSH_BUTTON_TOOLBAR_BIG, INTERFACE_STRING(ImageEditorToolBrush));
EsCommandAddButton(&instance->commandBrush, button);
EsButtonSetIcon(button, ES_ICON_DRAW_FREEHAND);
button->accessKey = 'B';
button = EsButtonCreate(instance->toolPanel, ES_FLAGS_DEFAULT, ES_STYLE_PUSH_BUTTON_TOOLBAR_BIG, INTERFACE_STRING(ImageEditorToolFill));
EsCommandAddButton(&instance->commandFill, button);
EsButtonSetIcon(button, ES_ICON_COLOR_FILL);
button->accessKey = 'F';
button = EsButtonCreate(instance->toolPanel, ES_FLAGS_DEFAULT, ES_STYLE_PUSH_BUTTON_TOOLBAR_BIG, INTERFACE_STRING(ImageEditorToolRectangle));
EsCommandAddButton(&instance->commandRectangle, button);
EsButtonSetIcon(button, ES_ICON_DRAW_RECTANGLE);
button->accessKey = 'E';
button = EsButtonCreate(instance->toolPanel, ES_FLAGS_DEFAULT, ES_STYLE_PUSH_BUTTON_TOOLBAR_BIG, INTERFACE_STRING(ImageEditorToolSelect));
EsCommandAddButton(&instance->commandSelect, button);
EsButtonSetIcon(button, ES_ICON_OBJECT_GROUP);
button->accessKey = 'S';
button = EsButtonCreate(instance->toolPanel, ES_FLAGS_DEFAULT, ES_STYLE_PUSH_BUTTON_TOOLBAR_BIG, INTERFACE_STRING(ImageEditorToolText));
EsCommandAddButton(&instance->commandText, button);
EsButtonSetIcon(button, ES_ICON_DRAW_TEXT);
button->accessKey = 'T';
EsWindowAddSizeAlternative(instance->window, instance->toolDropdown, instance->toolPanel, 1100, 0);
EsSpacerCreate(toolbar, ES_CELL_FILL);
EsPanel *section = EsPanelCreate(toolbar, ES_PANEL_HORIZONTAL);
EsTextDisplayCreate(section, ES_FLAGS_DEFAULT, 0, INTERFACE_STRING(ImageEditorPropertyColor));
instance->colorWell = EsColorWellCreate(section, ES_FLAGS_DEFAULT, 0);
instance->colorWell->accessKey = 'C';
EsSpacerCreate(toolbar, ES_FLAGS_DEFAULT, 0, 5, 0);
section = EsPanelCreate(toolbar, ES_PANEL_HORIZONTAL);
EsTextDisplayCreate(section, ES_FLAGS_DEFAULT, 0, INTERFACE_STRING(ImageEditorPropertyBrushSize));
instance->brushSize = EsTextboxCreate(section, ES_TEXTBOX_EDIT_BASED, ES_STYLE_TEXTBOX_BORDERED_SINGLE_COMPACT);
instance->brushSize->messageUser = BrushSizeMessage;
EsTextboxUseNumberOverlay(instance->brushSize, false);
EsTextboxInsert(instance->brushSize, EsLiteral("5.0"));
instance->brushSize->accessKey = 'Z';
EsSpacerCreate(toolbar, ES_FLAGS_DEFAULT, 0, 1, 0);
// Create the user interface.
EsWindowSetIcon(instance->window, ES_ICON_MULTIMEDIA_PHOTO_MANAGER);
EsCanvasPane *canvasPane = EsCanvasPaneCreate(instance->window, ES_CELL_FILL, ES_STYLE_PANEL_WINDOW_BACKGROUND);
instance->canvas = EsCustomElementCreate(canvasPane, ES_CELL_FILL | ES_ELEMENT_FOCUSABLE);
instance->canvas->messageUser = CanvasMessage;
EsElementFocus(instance->canvas, false);
// Setup the paint target and the image.
instance->bitmapWidth = 500;
instance->bitmapHeight = 400;
instance->bitmap = EsPaintTargetCreate(instance->bitmapWidth, instance->bitmapHeight, false);
EsPainter painter = {};
painter.clip = ES_RECT_4(0, instance->bitmapWidth, 0, instance->bitmapHeight);
painter.target = instance->bitmap;
EsDrawBlock(&painter, painter.clip, 0xFFFFFFFF);
instance->image = ImageFork(instance, {}, instance->bitmapWidth, instance->bitmapHeight);
ImageCopyFromPaintTarget(instance, &instance->image, painter.clip);
}
void _start() {
while (true) {
EsMessage *message = EsMessageReceive();
if (message->type == ES_MSG_INSTANCE_CREATE) {
InstanceCreate(message);
} else if (message->type == ES_MSG_INSTANCE_OPEN) {
Instance *instance = message->instanceOpen.instance;
size_t fileSize;
uint8_t *file = (uint8_t *) EsFileStoreReadAll(message->instanceOpen.file, &fileSize);
if (!file) {
EsInstanceOpenComplete(message, false);
continue;
}
uint32_t width, height;
uint8_t *bits = EsImageLoad(file, fileSize, &width, &height, 4);
EsHeapFree(file);
if (!bits) {
EsInstanceOpenComplete(message, false, INTERFACE_STRING(ImageEditorUnsupportedFormat));
continue;
}
EsPaintTargetDestroy(instance->bitmap);
ImageDelete(instance->image);
instance->bitmapWidth = width;
instance->bitmapHeight = height;
instance->bitmap = EsPaintTargetCreate(instance->bitmapWidth, instance->bitmapHeight, false);
EsPainter painter = {};
painter.clip = ES_RECT_4(0, instance->bitmapWidth, 0, instance->bitmapHeight);
painter.target = instance->bitmap;
EsDrawBitmap(&painter, painter.clip, (uint32_t *) bits, width * 4, 0xFF);
instance->image = ImageFork(instance, {}, instance->bitmapWidth, instance->bitmapHeight);
ImageCopyFromPaintTarget(instance, &instance->image, painter.clip);
EsElementRelayout(EsElementGetLayoutParent(instance->canvas));
EsHeapFree(bits);
EsInstanceOpenComplete(message, true);
} else if (message->type == ES_MSG_INSTANCE_DESTROY) {
Instance *instance = message->instanceDestroy.instance;
EsPaintTargetDestroy(instance->bitmap);
ImageDelete(instance->image);
}
}
}

15
apps/image_editor.ini Normal file
View File

@ -0,0 +1,15 @@
[general]
name=Image Editor
icon=icon_multimedia_photo_manager
use_single_process=1
[build]
source=apps/image_editor.cpp
[@handler]
extension=jpg
action=open
[@handler]
extension=png
action=open

327
apps/irc_client.cpp Normal file
View File

@ -0,0 +1,327 @@
// TODO Don't use EsTextbox for the output..
// TODO Put the connection settings in a Panel.Popup.
#define ES_INSTANCE_TYPE Instance
#include <essence.h>
struct Instance : EsInstance {
EsTextbox *textboxNick;
EsTextbox *textboxAddress;
EsTextbox *textboxPort;
EsTextbox *textboxOutput;
EsTextbox *textboxInput;
EsButton *buttonConnect;
EsThreadInformation networkingThread;
EsMutex inputCommandMutex;
char *inputCommand;
size_t inputCommandBytes;
};
const EsStyle styleSmallTextbox = {
.inherit = ES_STYLE_TEXTBOX_BORDERED_SINGLE,
.metrics = {
.mask = ES_THEME_METRICS_PREFERRED_WIDTH,
.preferredWidth = 100,
},
};
const EsStyle styleOutputTextbox = {
.inherit = ES_STYLE_TEXTBOX_NO_BORDER,
.metrics = {
.mask = ES_THEME_METRICS_FONT_FAMILY,
.fontFamily = ES_FONT_MONOSPACED,
},
};
const EsStyle styleInputTextbox = {
.inherit = ES_STYLE_TEXTBOX_BORDERED_SINGLE,
.metrics = {
.mask = ES_THEME_METRICS_FONT_FAMILY,
.fontFamily = ES_FONT_MONOSPACED,
},
};
int TextboxInputCallback(EsElement *element, EsMessage *message) {
Instance *instance = element->instance;
if (message->type == ES_MSG_KEY_DOWN) {
if (message->keyboard.scancode == ES_SCANCODE_ENTER) {
size_t inputCommandBytes = 0;
char *inputCommand = EsTextboxGetContents(instance->textboxInput, &inputCommandBytes);
if (inputCommandBytes) {
EsMutexAcquire(&instance->inputCommandMutex);
EsHeapFree(instance->inputCommand);
instance->inputCommand = inputCommand;
instance->inputCommandBytes = inputCommandBytes;
EsMutexRelease(&instance->inputCommandMutex);
} else {
EsHeapFree(inputCommand);
}
EsTextboxClear(instance->textboxInput, false);
return ES_HANDLED;
}
}
return 0;
}
void NetworkingThread(EsGeneric argument) {
Instance *instance = (Instance *) argument.p;
char errorMessage[4096];
size_t errorMessageBytes = 0;
char message[4096];
size_t messageBytes = 0;
char nick[64];
size_t nickBytes = 0;
char *password = nullptr;
size_t passwordBytes = 0;
char *address = nullptr;
size_t addressBytes = 0;
char buffer[1024];
uintptr_t bufferPosition = 0;
EsConnection connection = {};
connection.sendBufferBytes = 65536;
connection.receiveBufferBytes = 65536;
{
EsMessageMutexAcquire();
address = EsTextboxGetContents(instance->textboxAddress, &addressBytes);
EsMessageMutexRelease();
EsError error = EsAddressResolve(address, addressBytes, ES_FLAGS_DEFAULT, &connection.address);
if (error != ES_SUCCESS) {
errorMessageBytes = EsStringFormat(errorMessage, sizeof(errorMessage),
"The address name '%s' could not be found.\n", addressBytes, address);
goto exit;
}
}
{
EsMessageMutexAcquire();
size_t portBytes;
char *port = EsTextboxGetContents(instance->textboxPort, &portBytes);
EsMessageMutexRelease();
connection.address.port = EsIntegerParse(port, portBytes);
EsHeapFree(port);
}
{
EsMessageMutexAcquire();
char *_nick = EsTextboxGetContents(instance->textboxNick, &nickBytes);
EsMessageMutexRelease();
if (nickBytes > sizeof(nick)) nickBytes = sizeof(nick);
EsMemoryCopy(nick, _nick, nickBytes);
EsHeapFree(_nick);
for (uintptr_t i = 0; i < nickBytes; i++) {
if (nick[i] == ':') {
password = nick + i + 1;
passwordBytes = nickBytes - i - 1;
nickBytes = i;
}
}
}
{
EsError error = EsConnectionOpen(&connection, ES_CONNECTION_OPEN_WAIT);
if (error != ES_SUCCESS) {
errorMessageBytes = EsStringFormat(errorMessage, sizeof(errorMessage),
"Could not open the connection (%d).", error);
goto exit;
}
messageBytes = EsStringFormat(message, sizeof(message), "%z%s%zNICK %s\r\nUSER %s localhost %s :%s\r\n",
password ? "PASS " : "", passwordBytes, password, password ? "\r\n" : "",
nickBytes, nick, nickBytes, nick, addressBytes, address, nickBytes, nick);
EsConnectionWriteSync(&connection, message, messageBytes);
}
while (true) {
// TODO Ping the server every 2 minutes.
// TODO If we've received no messages for 5 minutes, timeout.
uintptr_t inputBytes = 0;
while (true) {
char *inputCommand = nullptr;
size_t inputCommandBytes = 0;
EsMutexAcquire(&instance->inputCommandMutex);
inputCommand = instance->inputCommand;
inputCommandBytes = instance->inputCommandBytes;
instance->inputCommand = nullptr;
instance->inputCommandBytes = 0;
EsMutexRelease(&instance->inputCommandMutex);
if (inputCommand) {
messageBytes = EsStringFormat(message, sizeof(message), "%s\r\n", inputCommandBytes, inputCommand);
EsConnectionWriteSync(&connection, message, messageBytes);
EsHeapFree(inputCommand);
}
size_t bytesRead;
EsError error = EsConnectionRead(&connection, buffer + bufferPosition, sizeof(buffer) - bufferPosition, &bytesRead);
if (error != ES_SUCCESS) {
errorMessageBytes = EsStringFormat(errorMessage, sizeof(errorMessage), "The connection was lost (%d).", error);
goto exit;
}
bufferPosition += bytesRead;
if (bufferPosition >= 2) {
for (uintptr_t i = 0; i < bufferPosition - 1; i++) {
if (buffer[i] == '\r' && buffer[i + 1] == '\n') {
buffer[i] = 0;
inputBytes = i + 2;
goto gotMessage;
}
}
}
if (bufferPosition == sizeof(buffer)) {
errorMessageBytes = EsStringFormat(errorMessage, sizeof(errorMessage), "The server sent an invalid message.");
goto exit;
}
}
gotMessage:;
EsMessageMutexAcquire();
EsTextboxInsert(instance->textboxOutput, buffer);
EsTextboxInsert(instance->textboxOutput, "\n");
EsMessageMutexRelease();
{
EsPrint("=================\n%z\n", buffer);
const char *command = nullptr, *user = nullptr, *parameters = nullptr, *text = nullptr;
char *position = buffer;
if (*position == ':') {
user = ++position;
while (*position && *position != ' ') position++;
if (*position) *position++ = 0;
} else {
user = address;
}
while (*position && *position == ' ') position++;
command = position;
while (*position && *position != ' ') position++;
if (*position) *position++ = 0;
while (*position && *position == ' ') position++;
if (*position != ':') {
parameters = position;
while (*position && *position != ' ') position++;
if (*position) *position++ = 0;
}
while (*position && *position == ' ') position++;
if (*position == ':') text = position + 1;
if (0 == EsCRTstrcmp(command, "PING")) {
messageBytes = EsStringFormat(message, sizeof(message), "PONG :%z\r\n", text ?: parameters);
EsConnectionWriteSync(&connection, message, messageBytes);
}
EsPrint("command: '%z'\nuser: '%z'\nparameters: '%z'\ntext: '%z'\n\n", command, user, parameters, text);
}
EsMemoryMove(buffer + inputBytes, buffer + bufferPosition, -inputBytes, false);
bufferPosition -= inputBytes;
}
exit:;
if (connection.handle) {
EsConnectionClose(&connection);
}
EsMessageMutexAcquire();
if (errorMessageBytes) {
EsDialogShowAlert(instance->window, EsLiteral("Connection failed"), errorMessage, errorMessageBytes,
ES_ICON_DIALOG_ERROR, ES_DIALOG_ALERT_OK_BUTTON);
}
EsElementSetDisabled(instance->textboxAddress, false);
EsElementSetDisabled(instance->textboxNick, false);
EsElementSetDisabled(instance->textboxPort, false);
EsElementSetDisabled(instance->textboxInput, true);
EsElementSetDisabled(instance->buttonConnect, false);
EsMessageMutexRelease();
EsHeapFree(address);
}
void ConnectCommand(Instance *instance, EsElement *, EsCommand *) {
EsElementSetDisabled(instance->textboxAddress, true);
EsElementSetDisabled(instance->textboxNick, true);
EsElementSetDisabled(instance->textboxPort, true);
EsElementSetDisabled(instance->textboxInput, false);
EsElementSetDisabled(instance->buttonConnect, true);
EsThreadCreate(NetworkingThread, &instance->networkingThread, instance);
}
void _start() {
_init();
while (true) {
EsMessage *message = EsMessageReceive();
if (message->type == ES_MSG_INSTANCE_CREATE) {
// Create an new instance.
Instance *instance = EsInstanceCreate(message, "IRC Client");
EsWindow *window = instance->window;
EsWindowSetIcon(window, ES_ICON_INTERNET_CHAT);
// Create the toolbar.
EsElement *toolbar = EsWindowGetToolbar(window);
EsPanel *section = EsPanelCreate(toolbar, ES_PANEL_HORIZONTAL);
EsTextDisplayCreate(section, ES_FLAGS_DEFAULT, 0, EsLiteral("Nick:"));
instance->textboxNick = EsTextboxCreate(section, ES_FLAGS_DEFAULT, &styleSmallTextbox);
EsSpacerCreate(toolbar, ES_FLAGS_DEFAULT, nullptr, 5, 0);
section = EsPanelCreate(toolbar, ES_PANEL_HORIZONTAL);
EsTextDisplayCreate(section, ES_FLAGS_DEFAULT, 0, EsLiteral("Address:"));
instance->textboxAddress = EsTextboxCreate(section, ES_FLAGS_DEFAULT, &styleSmallTextbox);
EsSpacerCreate(toolbar, ES_FLAGS_DEFAULT, nullptr, 5, 0);
section = EsPanelCreate(toolbar, ES_PANEL_HORIZONTAL);
EsTextDisplayCreate(section, ES_FLAGS_DEFAULT, 0, EsLiteral("Port:"));
instance->textboxPort = EsTextboxCreate(section, ES_FLAGS_DEFAULT, &styleSmallTextbox);
EsSpacerCreate(toolbar, ES_CELL_H_FILL);
instance->buttonConnect = EsButtonCreate(toolbar, ES_FLAGS_DEFAULT, 0, EsLiteral("Connect"));
EsButtonOnCommand(instance->buttonConnect, ConnectCommand);
// Create the main area.
EsPanel *panel = EsPanelCreate(window, ES_PANEL_VERTICAL | ES_CELL_FILL, ES_STYLE_PANEL_WINDOW_DIVIDER);
instance->textboxOutput = EsTextboxCreate(panel, ES_CELL_FILL | ES_TEXTBOX_MULTILINE, &styleOutputTextbox);
EsPanelCreate(panel, ES_CELL_H_FILL, ES_STYLE_SEPARATOR_HORIZONTAL);
EsPanel *inputArea = EsPanelCreate(panel, ES_PANEL_HORIZONTAL | ES_CELL_H_FILL, ES_STYLE_PANEL_FILLED);
instance->textboxInput = EsTextboxCreate(inputArea, ES_CELL_FILL, &styleInputTextbox);
instance->textboxInput->messageUser = TextboxInputCallback;
EsElementSetDisabled(instance->textboxInput);
}
}
}

7
apps/irc_client.ini Normal file
View File

@ -0,0 +1,7 @@
[general]
name=IRC Client
icon=icon_internet_chat
use_single_process=1
[build]
source=apps/irc_client.cpp

500
apps/markdown_viewer.cpp Normal file
View File

@ -0,0 +1,500 @@
#define ES_INSTANCE_TYPE Instance
#include <essence.h>
#include <shared/strings.cpp>
#include <ports/md4c/md4c.c>
#include <shared/array.cpp>
#define IMPLEMENTATION
#include <shared/array.cpp>
#undef IMPLEMENTATION
// TODO Inline code background?
// TODO When resizing the window, maintain the scroll position.
// TODO Table of contents/navigation pane.
// TODO Searching.
// TODO Images.
// TODO Proper link support.
// TODO Embedding markdown viewers into other applications.
// #define DEBUG_PARSER_OUTPUT
struct Span {
bool em, strong, monospaced, link;
uintptr_t offset;
};
struct Instance : EsInstance {
EsElement *root;
EsElement *active;
Array<Span> spans;
Array<char> text;
int32_t paddingBefore;
bool inBlockQuote;
int inListDepth;
int tableColumnCount;
int debugNestDepth;
};
#define MAKE_TEXT_STYLE(_textColor, _textSize, _fontFamily, _fontWeight, _isItalic) \
{ \
.metrics = { \
.mask = ES_THEME_METRICS_TEXT_COLOR | ES_THEME_METRICS_TEXT_ALIGN | ES_THEME_METRICS_TEXT_SIZE \
| ES_THEME_METRICS_FONT_FAMILY | ES_THEME_METRICS_FONT_WEIGHT \
| ES_THEME_METRICS_IS_ITALIC, \
.textColor = _textColor, \
.textAlign = ES_TEXT_H_LEFT | ES_TEXT_V_TOP | ES_TEXT_WRAP, \
.textSize = (int) (_textSize * 0.64 + 0.5), \
.fontFamily = _fontFamily, \
.fontWeight = _fontWeight, \
.isItalic = _isItalic, \
}, \
}
#define PARAGRAPH_PADDING_BEFORE (0)
#define PARAGRAPH_PADDING_AFTER (16)
#define H1_PADDING_BEFORE (16)
#define H1_PADDING_AFTER (16)
#define H2_PADDING_BEFORE (24)
#define H2_PADDING_AFTER (16)
#define H3_PADDING_BEFORE (24)
#define H3_PADDING_AFTER (16)
#define HEADING_UNDERLINE_GAP (7)
#define HR_PADDING_BEFORE (24)
#define HR_PADDING_AFTER (24)
#define QUOTE_PADDING_BEFORE (16)
#define QUOTE_PADDING_AFTER (0)
#define TABLE_PADDING_BEFORE (16)
#define TABLE_PADDING_AFTER (16)
#define COLOR_BACKGROUND (0xFFFDFDFD)
#define COLOR_GRAY1 (0xFFE1E4E8)
#define COLOR_GRAY2 (0xFFEBEDEF)
#define COLOR_GRAY3 (0xFFF6F8FA)
#define COLOR_TEXT1 (0xFF24292E)
#define COLOR_TEXT2 (0xFF5A636D)
#define COLOR_TEXT_LINK (0xFF0366D6)
EsStyle styleBackground = {
.appearance = {
.enabled = true,
.backgroundColor = COLOR_BACKGROUND,
},
};
EsStyle styleRoot = {
.metrics = {
.mask = ES_THEME_METRICS_INSETS | ES_THEME_METRICS_PREFERRED_WIDTH,
.insets = ES_RECT_4(32, 32, 16, 32),
.preferredWidth = 800,
},
};
EsStyle styleQuote = {
.metrics = {
.mask = ES_THEME_METRICS_INSETS,
.insets = ES_RECT_4(20, 16, 0, 0),
},
.appearance = {
.enabled = true,
.borderColor = COLOR_GRAY1,
.borderSize = ES_RECT_4(4, 0, 0, 0),
},
};
EsStyle styleList = {
.metrics = {
.mask = ES_THEME_METRICS_GAP_MAJOR,
.gapMajor = 5,
},
};
EsStyle styleHeadingUnderline = {
.metrics = {
.mask = ES_THEME_METRICS_PREFERRED_HEIGHT,
.preferredHeight = 1,
},
.appearance = {
.enabled = true,
.backgroundColor = COLOR_GRAY2,
},
};
EsStyle styleCodeBlock = {
.metrics = {
.mask = ES_THEME_METRICS_INSETS | ES_THEME_METRICS_CLIP_ENABLED,
.insets = ES_RECT_4(16, 16, 10, 10),
.clipEnabled = true,
},
.appearance = {
.enabled = true,
.backgroundColor = COLOR_GRAY3,
},
};
EsStyle styleHorizontalRule = {
.metrics = {
.mask = ES_THEME_METRICS_PREFERRED_HEIGHT,
.preferredHeight = 4,
},
.appearance = {
.enabled = true,
.backgroundColor = COLOR_GRAY1,
},
};
EsStyle styleTable = {
};
EsStyle styleTD = {
.inherit = ES_STYLE_TEXT_PARAGRAPH,
.metrics = {
.mask = ES_THEME_METRICS_INSETS | ES_THEME_METRICS_TEXT_SIZE | ES_THEME_METRICS_TEXT_COLOR,
.insets = ES_RECT_4(8, 8, 8, 0),
.textColor = COLOR_TEXT1,
.textSize = (int) (16 * 0.64 + 0.5),
},
};
EsStyle styleTH = {
.inherit = ES_STYLE_TEXT_PARAGRAPH,
.metrics = {
.mask = ES_THEME_METRICS_INSETS | ES_THEME_METRICS_TEXT_SIZE | ES_THEME_METRICS_TEXT_COLOR | ES_THEME_METRICS_FONT_WEIGHT,
.insets = ES_RECT_4(8, 8, 0, 4),
.textColor = COLOR_TEXT1,
.textSize = (int) (16 * 0.64 + 0.5),
.fontWeight = 5,
},
.appearance = {
.enabled = true,
.borderColor = COLOR_GRAY1,
.borderSize = ES_RECT_4(0, 0, 0, 1),
},
};
EsStyle styleHeading1 = MAKE_TEXT_STYLE(COLOR_TEXT1, 32, ES_FONT_SANS, 6, false);
EsStyle styleHeading2 = MAKE_TEXT_STYLE(COLOR_TEXT1, 24, ES_FONT_SANS, 6, false);
EsStyle styleHeading3 = MAKE_TEXT_STYLE(COLOR_TEXT1, 20, ES_FONT_SANS, 6, false);
EsStyle styleParagraph = MAKE_TEXT_STYLE(COLOR_TEXT1, 16, ES_FONT_SANS, 4, false);
EsStyle styleQuoteParagraph = MAKE_TEXT_STYLE(COLOR_TEXT2, 16, ES_FONT_SANS, 4, false);
EsStyle styleCode = MAKE_TEXT_STYLE(COLOR_TEXT1, 16, ES_FONT_MONOSPACED, 4, false);
const char *blockTypes[] = {
"MD_BLOCK_DOC",
"MD_BLOCK_QUOTE",
"MD_BLOCK_UL",
"MD_BLOCK_OL",
"MD_BLOCK_LI",
"MD_BLOCK_HR",
"MD_BLOCK_H",
"MD_BLOCK_CODE",
"MD_BLOCK_HTML",
"MD_BLOCK_P",
"MD_BLOCK_TABLE",
"MD_BLOCK_THEAD",
"MD_BLOCK_TBODY",
"MD_BLOCK_TR",
"MD_BLOCK_TH",
"MD_BLOCK_TD",
};
const char *spanTypes[] = {
"MD_SPAN_EM",
"MD_SPAN_STRONG",
"MD_SPAN_A",
"MD_SPAN_IMG",
"MD_SPAN_CODE",
"MD_SPAN_DEL",
"MD_SPAN_LATEXMATH",
"MD_SPAN_LATEXMATH_DISPLAY",
"MD_SPAN_WIKILINK",
"MD_SPAN_U",
};
const char *textTypes[] = {
"MD_TEXT_NORMAL",
"MD_TEXT_NULLCHAR",
"MD_TEXT_BR",
"MD_TEXT_SOFTBR",
"MD_TEXT_ENTITY",
"MD_TEXT_CODE",
"MD_TEXT_HTML",
"MD_TEXT_LATEXMATH",
};
void AddPadding(Instance *instance, int32_t before, int32_t after) {
if (instance->inListDepth) return;
if (before < instance->paddingBefore) before = instance->paddingBefore;
EsSpacerCreate(instance->active, ES_FLAGS_DEFAULT, nullptr, 0, before);
instance->paddingBefore = after;
}
void CreateStyledTextDisplay(Instance *instance, EsStyle *style, uint64_t flags = ES_CELL_H_FILL) {
EsTextDisplay *display = EsTextDisplayCreate(instance->active, flags, style);
EsTextRun *runs = (EsTextRun *) EsHeapAllocate(sizeof(EsTextRun) * (instance->spans.Length() + 1), false);
for (uintptr_t i = 0; i < instance->spans.Length(); i++) {
runs[i].offset = instance->spans[i].offset;
EsElementGetTextStyle(display, &runs[i].style);
if (instance->spans[i].link) { runs[i].style.decorations |= ES_TEXT_DECORATION_UNDERLINE; runs[i].style.color = COLOR_TEXT_LINK; }
if (instance->spans[i].em) runs[i].style.font.italic = true;
if (instance->spans[i].strong) runs[i].style.font.weight = 7;
if (instance->spans[i].monospaced) runs[i].style.font.family = ES_FONT_MONOSPACED;
runs[i].style.decorationsColor = runs[i].style.color;
}
runs[instance->spans.Length()].offset = instance->text.Length();
EsTextDisplaySetStyledContents(display, instance->text.array, runs, instance->spans.Length());
EsHeapFree(runs);
}
#ifdef DEBUG_PARSER_OUTPUT
void ParserOutputPrintIndentation(Instance *instance) {
for (int i = 0; i < instance->debugNestDepth; i++) {
EsPrint(" ");
}
}
#endif
int ParserEnterBlock(MD_BLOCKTYPE type, void *detail, void *_instance) {
Instance *instance = (Instance *) _instance;
#ifdef DEBUG_PARSER_OUTPUT
ParserOutputPrintIndentation(instance);
EsPrint(">> Enter block %z\n", blockTypes[type]);
instance->debugNestDepth++;
#endif
(void) detail;
if (instance->inListDepth && instance->text.Length()) {
CreateStyledTextDisplay(instance, &styleParagraph);
}
instance->text.SetLength(0);
instance->spans.SetLength(0);
Span span = {};
instance->spans.Add(span);
if (type == MD_BLOCK_UL) {
AddPadding(instance, PARAGRAPH_PADDING_BEFORE, PARAGRAPH_PADDING_AFTER);
instance->active = EsListDisplayCreate(instance->active, ES_CELL_H_FILL | ES_LIST_DISPLAY_BULLETED, ES_STYLE_LIST_DISPLAY_DEFAULT);
} else if (type == MD_BLOCK_OL) {
AddPadding(instance, PARAGRAPH_PADDING_BEFORE, PARAGRAPH_PADDING_AFTER);
EsListDisplay *display = EsListDisplayCreate(instance->active, ES_CELL_H_FILL | ES_LIST_DISPLAY_NUMBERED, ES_STYLE_LIST_DISPLAY_DEFAULT);
instance->active = display;
EsListDisplaySetCounterStart(display, ((MD_BLOCK_OL_DETAIL *) detail)->start - 1);
} else if (type == MD_BLOCK_QUOTE) {
AddPadding(instance, QUOTE_PADDING_BEFORE, QUOTE_PADDING_AFTER);
instance->active = EsPanelCreate(instance->active, ES_CELL_H_FILL, &styleQuote);
instance->inBlockQuote = true;
} else if (type == MD_BLOCK_LI) {
instance->active = EsPanelCreate(instance->active, ES_CELL_H_FILL, &styleList);
instance->inListDepth++;
} else if (type == MD_BLOCK_TABLE) {
AddPadding(instance, TABLE_PADDING_BEFORE, TABLE_PADDING_AFTER);
EsPanel *table = EsPanelCreate(instance->active, ES_PANEL_TABLE | ES_PANEL_HORIZONTAL | ES_CELL_H_SHRINK, &styleTable);
instance->active = table;
instance->tableColumnCount = 0;
}
return 0;
}
int ParserLeaveBlock(MD_BLOCKTYPE type, void *detail, void *_instance) {
Instance *instance = (Instance *) _instance;
#ifdef DEBUG_PARSER_OUTPUT
instance->debugNestDepth--;
ParserOutputPrintIndentation(instance);
EsPrint(">> Leave block %z\n", blockTypes[type]);
#endif
if (type == MD_BLOCK_P) {
if (instance->text.Length()) {
if (type == MD_BLOCK_P) {
AddPadding(instance, PARAGRAPH_PADDING_BEFORE, PARAGRAPH_PADDING_AFTER);
}
CreateStyledTextDisplay(instance, instance->inBlockQuote ? &styleQuoteParagraph : &styleParagraph);
}
} else if (type == MD_BLOCK_LI) {
if (instance->text.Length()) CreateStyledTextDisplay(instance, &styleParagraph);
instance->text.SetLength(0);
instance->spans.SetLength(0);
instance->active = EsElementGetLayoutParent(instance->active);
instance->inListDepth--;
} else if (type == MD_BLOCK_TD || type == MD_BLOCK_TH) {
if (type == MD_BLOCK_TH) instance->tableColumnCount++;
CreateStyledTextDisplay(instance, type == MD_BLOCK_TH ? &styleTH : &styleTD, ES_CELL_H_EXPAND | ES_CELL_V_EXPAND | ES_CELL_H_SHRINK | ES_CELL_V_SHRINK);
instance->text.SetLength(0);
instance->spans.SetLength(0);
} else if (type == MD_BLOCK_H) {
unsigned level = ((MD_BLOCK_H_DETAIL *) detail)->level;
if (level == 1) AddPadding(instance, H1_PADDING_BEFORE, H1_PADDING_AFTER);
else if (level == 2) AddPadding(instance, H2_PADDING_BEFORE, H2_PADDING_AFTER);
else AddPadding(instance, H3_PADDING_BEFORE, H3_PADDING_AFTER);
CreateStyledTextDisplay(instance, level == 1 ? &styleHeading1 : level == 2 ? &styleHeading2 : &styleHeading3);
if (level <= 2) {
EsSpacerCreate(instance->active, ES_FLAGS_DEFAULT, nullptr, 0, HEADING_UNDERLINE_GAP);
EsSpacerCreate(instance->active, ES_CELL_H_FILL, &styleHeadingUnderline, 0, 0);
}
} else if (type == MD_BLOCK_CODE) {
MD_BLOCK_CODE_DETAIL *code = (MD_BLOCK_CODE_DETAIL *) detail;
AddPadding(instance, PARAGRAPH_PADDING_BEFORE, PARAGRAPH_PADDING_AFTER);
EsElement *wrapper = EsPanelCreate(instance->active, ES_CELL_H_FILL | ES_PANEL_HORIZONTAL | ES_PANEL_H_SCROLL_AUTO, &styleCodeBlock);
EsTextDisplay *display = EsTextDisplayCreate(wrapper, ES_TEXT_DISPLAY_PREFORMATTED, &styleCode, instance->text.array, instance->text.Length());
if (0 == EsStringCompare(code->lang.text, code->lang.size, EsLiteral("ini"))) {
EsTextDisplaySetupSyntaxHighlighting(display, ES_SYNTAX_HIGHLIGHTING_LANGUAGE_INI);
} else if (0 == EsStringCompare(code->lang.text, code->lang.size, EsLiteral("c"))
|| 0 == EsStringCompare(code->lang.text, code->lang.size, EsLiteral("cpp"))
|| 0 == EsStringCompare(code->lang.text, code->lang.size, EsLiteral("c++"))) {
EsTextDisplaySetupSyntaxHighlighting(display, ES_SYNTAX_HIGHLIGHTING_LANGUAGE_C);
}
} else if (type == MD_BLOCK_UL || type == MD_BLOCK_OL || type == MD_BLOCK_QUOTE) {
instance->active = EsElementGetLayoutParent(instance->active);
instance->inBlockQuote = false;
} else if (type == MD_BLOCK_HR) {
AddPadding(instance, HR_PADDING_BEFORE, HR_PADDING_AFTER);
EsSpacerCreate(instance->active, ES_CELL_H_FILL, &styleHorizontalRule, 0, 0);
} else if (type == MD_BLOCK_TABLE) {
EsPanel *panel = (EsPanel *) instance->active;
EsPanelSetBands(panel, instance->tableColumnCount);
EsPanelTableSetChildCells(panel);
EsPanelBand column = {};
column.preferredSize = column.minimumSize = column.maximumSize = ES_PANEL_BAND_SIZE_DEFAULT;
column.pull = 1; // Shrink all columns with equal weight.
EsPanelSetBandsAll(panel, &column);
instance->active = EsElementGetLayoutParent(instance->active);
} else {
// EsPrint("Unhandled block of type %z.\n", blockTypes[type]);
}
return 0;
}
int ParserEnterSpan(MD_SPANTYPE type, void *detail, void *_instance) {
Instance *instance = (Instance *) _instance;
#ifdef DEBUG_PARSER_OUTPUT
ParserOutputPrintIndentation(instance);
EsPrint(">> Enter span %z\n", spanTypes[type]);
instance->debugNestDepth++;
#endif
(void) detail;
if (type == MD_SPAN_EM || type == MD_SPAN_STRONG || type == MD_SPAN_CODE || type == MD_SPAN_A) {
Span span = instance->spans.Last();
if (type == MD_SPAN_EM) span.em = true;
if (type == MD_SPAN_STRONG) span.strong = true;
if (type == MD_SPAN_CODE) span.monospaced = true;
if (type == MD_SPAN_A) span.link = true;
span.offset = instance->text.Length();
instance->spans.Add(span);
}
return 0;
}
int ParserLeaveSpan(MD_SPANTYPE type, void *detail, void *_instance) {
Instance *instance = (Instance *) _instance;
#ifdef DEBUG_PARSER_OUTPUT
instance->debugNestDepth--;
ParserOutputPrintIndentation(instance);
EsPrint(">> Leave span %z\n", spanTypes[type]);
#endif
(void) detail;
if (type == MD_SPAN_EM || type == MD_SPAN_STRONG || type == MD_SPAN_CODE || type == MD_SPAN_A) {
Span span = instance->spans.Last();
if (type == MD_SPAN_EM) span.em = false;
if (type == MD_SPAN_STRONG) span.strong = false;
if (type == MD_SPAN_CODE) span.monospaced = false;
if (type == MD_SPAN_A) span.link = false;
span.offset = instance->text.Length();
instance->spans.Add(span);
}
return 0;
}
int ParserText(MD_TEXTTYPE type, const MD_CHAR *text, MD_SIZE size, void *_instance) {
(void) type;
Instance *instance = (Instance *) _instance;
#ifdef DEBUG_PARSER_OUTPUT
ParserOutputPrintIndentation(instance);
EsPrint(">> Text %z, %x: %s\n", textTypes[type], text, size, text);
#endif
char *buffer = instance->text.InsertMany(instance->text.Length(), size);
EsMemoryCopy(buffer, text, size);
return 0;
}
void ProcessApplicationMessage(EsMessage *message) {
if (message->type == ES_MSG_INSTANCE_CREATE) {
Instance *instance = EsInstanceCreate(message, INTERFACE_STRING(MarkdownViewerTitle));
EsInstanceSetClassViewer(instance, nullptr);
EsWindow *window = instance->window;
EsPanel *wrapper = EsPanelCreate(instance->window, ES_CELL_FILL, ES_STYLE_PANEL_WINDOW_DIVIDER);
EsPanel *background = EsPanelCreate(wrapper, ES_CELL_FILL | ES_PANEL_V_SCROLL_AUTO, &styleBackground);
instance->root = EsPanelCreate(background, ES_CELL_H_SHRINK, &styleRoot);
EsWindowSetIcon(window, ES_ICON_TEXT_MARKDOWN);
} else if (message->type == ES_MSG_INSTANCE_OPEN) {
Instance *instance = message->instanceOpen.instance;
if (message->instanceOpen.update) {
EsElementStartTransition(instance->root, ES_TRANSITION_ZOOM_IN);
}
EsElementDestroyContents(instance->root);
size_t fileSize;
char *file = (char *) EsFileStoreReadAll(message->instanceOpen.file, &fileSize);
if (!file || !EsUTF8IsValid(file, fileSize)) {
EsInstanceOpenComplete(message, false);
return;
}
MD_PARSER parser = {};
parser.flags = MD_DIALECT_GITHUB | MD_FLAG_NOHTML;
parser.enter_block = ParserEnterBlock;
parser.leave_block = ParserLeaveBlock;
parser.enter_span = ParserEnterSpan;
parser.leave_span = ParserLeaveSpan;
parser.text = ParserText;
instance->active = instance->root;
int result = md_parse(file, fileSize, &parser, instance);
if (result) EsElementDestroyContents(instance->root); // An error occurred.
EsInstanceOpenComplete(message, result == 0);
EsHeapFree(file);
EsElementRelayout(instance->root);
instance->spans.Free();
instance->text.Free();
}
}
void _start() {
_init();
while (true) {
ProcessApplicationMessage(EsMessageReceive());
}
}

11
apps/markdown_viewer.ini Normal file
View File

@ -0,0 +1,11 @@
[general]
name=Markdown Viewer
icon=icon_text_markdown
use_single_process=1
[build]
source=apps/markdown_viewer.cpp
[@handler]
extension=md
action=open

311
apps/posix_launcher.cpp Normal file
View File

@ -0,0 +1,311 @@
#include <essence.h>
#include <shared/strings.cpp>
#define _GNU_SOURCE
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <time.h>
#define MSG_RECEIVED_OUTPUT ((EsMessageType) (ES_MSG_USER_START + 1))
EsInstance *instance;
EsHandle commandEvent;
char outputBuffer[262144];
uintptr_t outputBufferPosition;
EsMutex mutex;
volatile bool runningCommand;
int stdinWritePipe;
char *command;
EsTextbox *textboxOutput, *textboxInput;
const EsStyle styleMonospacedTextbox = {
.inherit = ES_STYLE_TEXTBOX_NO_BORDER,
.metrics = {
.mask = ES_THEME_METRICS_FONT_FAMILY | ES_THEME_METRICS_TEXT_SIZE,
.textSize = 12,
.fontFamily = ES_FONT_MONOSPACED,
},
};
char *ParseArgument(char **position) {
char *start = *position;
while (*start == ' ') {
start++;
}
if (!(*start)) {
return nullptr;
}
char *end = start;
while ((*end != ' ' || (end != start && end[-1] == '\\')) && *end) {
end++;
}
if (*end) {
*end = 0;
end++;
}
*position = end;
return start;
}
void WriteToOutputTextbox(const char *string, ptrdiff_t stringBytes) {
if (stringBytes == -1) {
stringBytes = EsCRTstrlen(string);
}
bool done = false, postMessage = false;
while (true) {
EsMutexAcquire(&mutex);
if (outputBufferPosition + stringBytes <= sizeof(outputBuffer)) {
EsMemoryCopy(outputBuffer + outputBufferPosition, string, stringBytes);
postMessage = outputBufferPosition == 0;
outputBufferPosition += stringBytes;
done = true;
}
EsMutexRelease(&mutex);
if (!done) {
// The main thread is busy. Wait a little bit before trying again.
EsSleep(100);
} else {
break;
}
}
if (postMessage) {
EsMessage m = {};
m.type = MSG_RECEIVED_OUTPUT;
EsMessagePost(nullptr, &m);
}
}
void RunCommandThread() {
char *argv[64];
int argc = 0;
char executable[4096];
int status;
int standardOutputPipe[2];
int standardInputPipe[2];
pid_t pid;
struct timespec startTime, endTime;
char *envp[5] = {
(char *) "LANG=en_US.UTF-8",
(char *) "HOME=/",
(char *) "PATH=/Applications/POSIX/bin",
(char *) "TMPDIR=/Applications/POSIX/tmp",
nullptr
};
char *commandPosition = command;
while (argc < 63) {
argv[argc] = ParseArgument(&commandPosition);
if (!argv[argc]) break;
argc++;
}
if (!argc) {
goto done;
}
argv[argc] = nullptr;
if (0 == EsCRTstrcmp(argv[0], "run")) {
if (argc != 2) {
WriteToOutputTextbox("\nUsage: run <absolute path to esx>\n", -1);
} else {
EsApplicationRunTemporary(instance, argv[1], EsCStringLength(argv[1]));
}
WriteToOutputTextbox("\n----------------\n", -1);
goto done;
} else if (0 == EsCRTstrcmp(argv[0], "cd")) {
if (argc != 2) {
WriteToOutputTextbox("\nUsage: cd <path>\n", -1);
} else {
chdir(argv[1]);
WriteToOutputTextbox("\nNew working directory:\n", -1);
WriteToOutputTextbox(getcwd(nullptr, 0), -1);
WriteToOutputTextbox("\n", -1);
}
WriteToOutputTextbox("\n----------------\n", -1);
goto done;
}
if (argv[0][0] == '/') {
executable[EsStringFormat(executable, sizeof(executable) - 1, "%z", argv[0])] = 0;
} else {
executable[EsStringFormat(executable, sizeof(executable) - 1, "/Applications/POSIX/bin/%z", argv[0])] = 0;
}
clock_gettime(CLOCK_MONOTONIC, &startTime);
pipe(standardOutputPipe);
pipe(standardInputPipe);
pid = vfork();
if (pid == 0) {
dup2(standardInputPipe[0], 0);
dup2(standardOutputPipe[1], 1);
dup2(standardOutputPipe[1], 2);
close(standardInputPipe[0]);
close(standardOutputPipe[1]);
execve(executable, argv, envp);
WriteToOutputTextbox("\nThe executable failed to load.\n", -1);
_exit(-1);
} else if (pid == -1) {
// TODO Report the error.
}
close(standardInputPipe[0]);
close(standardOutputPipe[1]);
EsMutexAcquire(&mutex);
stdinWritePipe = standardInputPipe[1];
EsMutexRelease(&mutex);
while (true) {
char buffer[1024];
ssize_t bytesRead = read(standardOutputPipe[0], buffer, 1024);
if (bytesRead <= 0) {
break;
} else {
WriteToOutputTextbox(buffer, bytesRead);
}
}
EsMutexAcquire(&mutex);
stdinWritePipe = 0;
EsMutexRelease(&mutex);
close(standardInputPipe[1]);
close(standardOutputPipe[0]);
wait4(-1, &status, 0, NULL);
clock_gettime(CLOCK_MONOTONIC, &endTime);
{
double startTimeS = startTime.tv_sec + startTime.tv_nsec / 1000000000.0;
double endTimeS = endTime.tv_sec + endTime.tv_nsec / 1000000000.0;
char buffer[256];
size_t bytes = EsStringFormat(buffer, sizeof(buffer), "\nProcess exited with status %d.\nExecution time: %Fs.\n", status >> 8, endTimeS - startTimeS);
WriteToOutputTextbox(buffer, bytes);
WriteToOutputTextbox("\n----------------\n", -1);
}
done:;
EsHeapFree(command);
__sync_synchronize();
runningCommand = false;
}
int ProcessTextboxInputMessage(EsElement *, EsMessage *message) {
if (message->type == ES_MSG_KEY_DOWN) {
if (message->keyboard.scancode == ES_SCANCODE_ENTER
&& !message->keyboard.modifiers
&& EsTextboxGetLineLength(textboxInput)) {
char *data = EsTextboxGetContents(textboxInput);
if (!runningCommand) {
runningCommand = true;
command = data;
EsTextboxInsert(textboxOutput, "\n> ", -1, false);
EsTextboxInsert(textboxOutput, command, -1, false);
EsTextboxInsert(textboxOutput, "\n", -1, false);
EsTextboxEnsureCaretVisible(textboxOutput, false);
EsEventSet(commandEvent);
} else {
EsTextboxInsert(textboxOutput, data, -1, false);
EsTextboxInsert(textboxOutput, "\n", -1, false);
EsMutexAcquire(&mutex);
if (stdinWritePipe) {
write(stdinWritePipe, data, EsCStringLength(data));
write(stdinWritePipe, "\n", 1);
}
EsMutexRelease(&mutex);
EsHeapFree(data);
}
EsTextboxClear(textboxInput, false);
return ES_HANDLED;
}
}
return 0;
}
void MessageLoopThread(EsGeneric) {
EsMessageMutexAcquire();
while (true) {
EsMessage *message = EsMessageReceive();
if (message->type == ES_MSG_INSTANCE_CREATE) {
EsAssert(!instance);
instance = EsInstanceCreate(message, "POSIX Launcher");
EsWindow *window = instance->window;
EsWindowSetIcon(window, ES_ICON_UTILITIES_TERMINAL);
EsPanel *panel = EsPanelCreate(window, ES_PANEL_VERTICAL | ES_CELL_FILL, ES_STYLE_PANEL_WINDOW_BACKGROUND);
textboxOutput = EsTextboxCreate(panel, ES_TEXTBOX_MULTILINE | ES_CELL_FILL, &styleMonospacedTextbox);
EsSpacerCreate(panel, ES_CELL_H_FILL, ES_STYLE_SEPARATOR_HORIZONTAL);
textboxInput = EsTextboxCreate(panel, ES_CELL_H_FILL, &styleMonospacedTextbox);
textboxInput->messageUser = ProcessTextboxInputMessage;
EsElementFocus(textboxInput);
} else if (message->type == MSG_RECEIVED_OUTPUT) {
EsMutexAcquire(&mutex);
if (outputBufferPosition) {
// EsPrint("Inserting %d bytes...\n", outputBufferPosition);
EsTextboxMoveCaretRelative(textboxOutput, ES_TEXTBOX_MOVE_CARET_ALL);
EsTextboxInsert(textboxOutput, outputBuffer, outputBufferPosition, false);
EsTextboxEnsureCaretVisible(textboxOutput, false);
outputBufferPosition = 0;
}
EsMutexRelease(&mutex);
}
}
}
int main(int argc, char **argv) {
(void) argc;
(void) argv;
commandEvent = EsEventCreate(true);
EsMessageMutexRelease();
EsThreadCreate(MessageLoopThread, nullptr, 0);
#if 0
runningCommand = true;
command = (char *) EsHeapAllocate(128, true);
EsStringFormat(command, 128, "gcc es/util/build_core.c -g");
EsEventSet(commandEvent);
#endif
while (true) {
EsWaitSingle(commandEvent);
RunCommandThread();
}
return 0;
}

9
apps/posix_launcher.ini Normal file
View File

@ -0,0 +1,9 @@
[general]
name=POSIX Launcher
icon=icon_utilities_terminal
permission_posix_subsystem=1
permission_run_temporary_application=1
[build]
source=apps/posix_launcher.cpp
link_flags=-Lroot/Applications/POSIX/lib -lc bin/crtglue.o bin/crt1.o

497
apps/system_monitor.cpp Normal file
View File

@ -0,0 +1,497 @@
#define ES_INSTANCE_TYPE Instance
#include <essence.h>
#include <shared/array.cpp>
#define IMPLEMENTATION
#include <shared/array.cpp>
#undef IMPLEMENTATION
// TODO Single instance.
// TODO Sorting lists.
// TODO Processes: handle/thread count; IO statistics; more memory information.
struct Instance : EsInstance {
EsPanel *switcher;
EsTextbox *textboxGeneralLog;
EsListView *listViewProcesses;
EsListView *listViewPCIDevices;
EsPanel *panelMemoryStatistics;
int index;
EsCommand commandTerminateProcess;
Array<EsTextDisplay *> textDisplaysMemory;
};
#define REFRESH_INTERVAL (1000)
#define DISPLAY_PROCESSES (1)
#define DISPLAY_GENERAL_LOG (3)
#define DISPLAY_PCI_DEVICES (6)
#define DISPLAY_MEMORY (12)
EsListViewColumn listViewProcessesColumns[] = {
{ EsLiteral("Name"), 0, 150 },
{ EsLiteral("PID"), ES_LIST_VIEW_COLUMN_RIGHT_ALIGNED, 150 },
{ EsLiteral("Memory"), ES_LIST_VIEW_COLUMN_RIGHT_ALIGNED, 150 },
{ EsLiteral("CPU"), ES_LIST_VIEW_COLUMN_RIGHT_ALIGNED, 150 },
{ EsLiteral("Handles"), ES_LIST_VIEW_COLUMN_RIGHT_ALIGNED, 150 },
};
EsListViewColumn listViewContextSwitchesColumns[] = {
{ EsLiteral("Time stamp (ms)"), ES_LIST_VIEW_COLUMN_RIGHT_ALIGNED, 150 },
{ EsLiteral("CPU"), ES_LIST_VIEW_COLUMN_RIGHT_ALIGNED, 150 },
{ EsLiteral("Process"), 0, 150 },
{ EsLiteral("Thread"), 0, 150 },
{ EsLiteral("Count"), 0, 150 },
};
EsListViewColumn listViewPCIDevicesColumns[] = {
{ EsLiteral("Driver"), 0, 200 },
{ EsLiteral("Device ID"), 0, 200 },
{ EsLiteral("Class"), 0, 250 },
{ EsLiteral("Subclass"), 0, 250 },
{ EsLiteral("ProgIF"), 0, 150 },
{ EsLiteral("Bus"), 0, 100 },
{ EsLiteral("Slot"), 0, 100 },
{ EsLiteral("Function"), 0, 100 },
{ EsLiteral("Interrupt pin"), 0, 100 },
{ EsLiteral("Interrupt line"), 0, 100 },
{ EsLiteral("BAR0"), 0, 250 },
{ EsLiteral("BAR1"), 0, 250 },
{ EsLiteral("BAR2"), 0, 250 },
{ EsLiteral("BAR3"), 0, 250 },
{ EsLiteral("BAR4"), 0, 250 },
{ EsLiteral("BAR5"), 0, 250 },
};
const EsStyle styleMonospacedTextbox = {
.inherit = ES_STYLE_TEXTBOX_NO_BORDER,
.metrics = {
.mask = ES_THEME_METRICS_FONT_FAMILY,
.fontFamily = ES_FONT_MONOSPACED,
},
};
const EsStyle stylePanelMemoryStatistics = {
.inherit = ES_STYLE_PANEL_FILLED,
.metrics = {
.mask = ES_THEME_METRICS_GAP_ALL,
.gapMajor = 5,
.gapMinor = 5,
},
};
const EsStyle stylePanelMemoryCommands = {
.metrics = {
.mask = ES_THEME_METRICS_INSETS | ES_THEME_METRICS_GAP_ALL,
.insets = ES_RECT_4(0, 0, 0, 5),
.gapMajor = 5,
.gapMinor = 5,
},
};
const char *pciClassCodeStrings[] = {
"Unknown",
"Mass storage controller",
"Network controller",
"Display controller",
"Multimedia controller",
"Memory controller",
"Bridge controller",
"Simple communication controller",
"Base system peripheral",
"Input device controller",
"Docking station",
"Processor",
"Serial bus controller",
"Wireless controller",
"Intelligent controller",
"Satellite communication controller",
"Encryption controller",
"Signal processing controller",
};
const char *pciSubclassCodeStrings1[] = {
"SCSI bus controller",
"IDE controller",
"Floppy disk controller",
"IPI bus controller",
"RAID controller",
"ATA controller",
"Serial ATA",
"Serial attached SCSI",
"Non-volatile memory controller",
};
const char *pciSubclassCodeStrings12[] = {
"FireWire (IEEE 1394) controller",
"ACCESS bus",
"SSA",
"USB controller",
"Fibre channel",
"SMBus",
"InfiniBand",
"IPMI interface",
"SERCOS interface (IEC 61491)",
"CANbus",
};
const char *pciProgIFStrings12_3[] = {
"UHCI",
"OHCI",
"EHCI",
"XHCI",
};
struct ProcessItem {
EsSnapshotProcessesItem data;
uintptr_t cpuUsage;
};
char generalLogBuffer[256 * 1024];
char contextSwitchesBuffer[2 * 1024 * 1024];
EsPCIDevice pciDevices[1024];
Array<ProcessItem> processes;
int64_t selectedPID = -2;
ProcessItem *FindProcessByPID(Array<ProcessItem> snapshot, int64_t pid) {
for (uintptr_t i = 0; i < snapshot.Length(); i++) {
if (pid == snapshot[i].data.pid) {
return &snapshot[i];
}
}
return nullptr;
}
void UpdateProcesses(Instance *instance) {
Array<ProcessItem> previous = processes;
processes = {};
size_t bufferSize;
EsHandle handle = EsTakeSystemSnapshot(ES_SYSTEM_SNAPSHOT_PROCESSES, &bufferSize);
EsSnapshotProcesses *snapshot = (EsSnapshotProcesses *) EsHeapAllocate(bufferSize, false);
EsConstantBufferRead(handle, snapshot);
EsHandleClose(handle);
for (uintptr_t i = 0; i < snapshot->count; i++) {
ProcessItem item = {};
item.data = snapshot->processes[i];
processes.Add(item);
if (snapshot->processes[i].isKernel) {
ProcessItem item = {};
item.data.cpuTimeSlices = snapshot->processes[i].idleTimeSlices;
item.data.pid = -1;
const char *idle = "CPU idle";
item.data.nameBytes = EsCStringLength(idle);
EsMemoryCopy(item.data.name, idle, item.data.nameBytes);
processes.Add(item);
}
}
EsHeapFree(snapshot);
for (uintptr_t i = 0; i < previous.Length(); i++) {
if (!FindProcessByPID(processes, previous[i].data.pid)) {
EsListViewRemove(instance->listViewProcesses, 0, i, i);
previous.Delete(i--);
}
}
for (uintptr_t i = 0; i < processes.Length(); i++) {
processes[i].cpuUsage = processes[i].data.cpuTimeSlices;
ProcessItem *item = FindProcessByPID(previous, processes[i].data.pid);
if (item) processes[i].cpuUsage -= item->data.cpuTimeSlices;
}
int64_t totalCPUTimeSlices = 0;
for (uintptr_t i = 0; i < processes.Length(); i++) {
totalCPUTimeSlices += processes[i].cpuUsage;
}
int64_t percentageSum = 0;
for (uintptr_t i = 0; i < processes.Length(); i++) {
processes[i].cpuUsage = processes[i].cpuUsage * 100 / totalCPUTimeSlices;
percentageSum += processes[i].cpuUsage;
}
while (percentageSum < 100 && percentageSum) {
for (uintptr_t i = 0; i < processes.Length(); i++) {
if (processes[i].cpuUsage && percentageSum < 100) {
processes[i].cpuUsage++, percentageSum++;
}
}
}
for (uintptr_t i = 0; i < processes.Length(); i++) {
if (!FindProcessByPID(previous, processes[i].data.pid)) {
EsListViewInsert(instance->listViewProcesses, 0, i, i);
}
}
EsListViewInvalidateAll(instance->listViewProcesses);
EsCommandSetDisabled(&instance->commandTerminateProcess, selectedPID < 0 || !FindProcessByPID(processes, selectedPID));
EsTimerSet(REFRESH_INTERVAL, [] (EsGeneric context) {
Instance *instance = (Instance *) context.p;
if (instance->index == DISPLAY_PROCESSES) {
UpdateProcesses(instance);
}
}, instance);
previous.Free();
}
void UpdateDisplay(Instance *instance, int index) {
instance->index = index;
if (index != DISPLAY_PROCESSES) {
EsCommandSetDisabled(&instance->commandTerminateProcess, true);
}
if (index == DISPLAY_PROCESSES) {
UpdateProcesses(instance);
EsPanelSwitchTo(instance->switcher, instance->listViewProcesses, ES_TRANSITION_NONE);
EsElementFocus(instance->listViewProcesses);
} else if (index == DISPLAY_GENERAL_LOG) {
size_t bytes = EsSyscall(ES_SYSCALL_DEBUG_COMMAND, index, (uintptr_t) generalLogBuffer, sizeof(generalLogBuffer), 0);
EsTextboxSelectAll(instance->textboxGeneralLog);
EsTextboxInsert(instance->textboxGeneralLog, generalLogBuffer, bytes);
EsTextboxEnsureCaretVisible(instance->textboxGeneralLog, false);
EsPanelSwitchTo(instance->switcher, instance->textboxGeneralLog, ES_TRANSITION_NONE);
} else if (index == DISPLAY_PCI_DEVICES) {
size_t count = EsSyscall(ES_SYSCALL_DEBUG_COMMAND, index, (uintptr_t) pciDevices, sizeof(pciDevices) / sizeof(pciDevices[0]), 0);
EsListViewRemoveAll(instance->listViewPCIDevices, 0);
EsListViewInsert(instance->listViewPCIDevices, 0, 0, count - 1);
EsPanelSwitchTo(instance->switcher, instance->listViewPCIDevices, ES_TRANSITION_NONE);
} else if (index == DISPLAY_MEMORY) {
EsMemoryStatistics statistics = {};
EsSyscall(ES_SYSCALL_DEBUG_COMMAND, index, (uintptr_t) &statistics, 0, 0);
EsPanelSwitchTo(instance->switcher, instance->panelMemoryStatistics, ES_TRANSITION_NONE);
if (!instance->textDisplaysMemory.Length()) {
EsPanel *panel = EsPanelCreate(instance->panelMemoryStatistics, ES_CELL_H_FILL | ES_PANEL_HORIZONTAL, &stylePanelMemoryCommands);
EsButton *button;
button = EsButtonCreate(panel, ES_FLAGS_DEFAULT, 0, EsLiteral("Leak 1 MB"));
EsButtonOnCommand(button, [] (Instance *, EsElement *, EsCommand *) { EsMemoryReserve(0x100000); });
button = EsButtonCreate(panel, ES_FLAGS_DEFAULT, 0, EsLiteral("Leak 4 MB"));
EsButtonOnCommand(button, [] (Instance *, EsElement *, EsCommand *) { EsMemoryReserve(0x400000); });
button = EsButtonCreate(panel, ES_FLAGS_DEFAULT, 0, EsLiteral("Leak 16 MB"));
EsButtonOnCommand(button, [] (Instance *, EsElement *, EsCommand *) { EsMemoryReserve(0x1000000); });
button = EsButtonCreate(panel, ES_FLAGS_DEFAULT, 0, EsLiteral("Leak 64 MB"));
EsButtonOnCommand(button, [] (Instance *, EsElement *, EsCommand *) { EsMemoryReserve(0x4000000); });
button = EsButtonCreate(panel, ES_FLAGS_DEFAULT, 0, EsLiteral("Leak 256 MB"));
EsButtonOnCommand(button, [] (Instance *, EsElement *, EsCommand *) { EsMemoryReserve(0x10000000); });
EsSpacerCreate(instance->panelMemoryStatistics, ES_CELL_H_FILL);
}
char buffer[256];
size_t bytes;
uintptr_t index = 0;
#define ADD_MEMORY_STATISTIC_DISPLAY(label, ...) \
bytes = EsStringFormat(buffer, sizeof(buffer), __VA_ARGS__); \
if (instance->textDisplaysMemory.Length() == index) { \
EsTextDisplayCreate(instance->panelMemoryStatistics, ES_CELL_H_PUSH | ES_CELL_H_RIGHT, 0, EsLiteral(label)); \
instance->textDisplaysMemory.Add(EsTextDisplayCreate(instance->panelMemoryStatistics, ES_CELL_H_PUSH | ES_CELL_H_LEFT)); \
} \
EsTextDisplaySetContents(instance->textDisplaysMemory[index++], buffer, bytes)
ADD_MEMORY_STATISTIC_DISPLAY("Fixed heap allocation count:", "%d", statistics.fixedHeapAllocationCount);
ADD_MEMORY_STATISTIC_DISPLAY("Fixed heap graphics surfaces:", "%D (%d B)",
statistics.totalSurfaceBytes, statistics.totalSurfaceBytes);
ADD_MEMORY_STATISTIC_DISPLAY("Fixed heap normal size:", "%D (%d B)",
statistics.fixedHeapTotalSize - statistics.totalSurfaceBytes, statistics.fixedHeapTotalSize - statistics.totalSurfaceBytes);
ADD_MEMORY_STATISTIC_DISPLAY("Fixed heap total size:", "%D (%d B)",
statistics.fixedHeapTotalSize, statistics.fixedHeapTotalSize);
ADD_MEMORY_STATISTIC_DISPLAY("Core heap allocation count:", "%d", statistics.coreHeapAllocationCount);
ADD_MEMORY_STATISTIC_DISPLAY("Core heap total size:", "%D (%d B)", statistics.coreHeapTotalSize, statistics.coreHeapTotalSize);
ADD_MEMORY_STATISTIC_DISPLAY("Cached boot FS nodes:", "%d", statistics.cachedNodes);
ADD_MEMORY_STATISTIC_DISPLAY("Cached boot FS directory entries:", "%d", statistics.cachedDirectoryEntries);
ADD_MEMORY_STATISTIC_DISPLAY("Maximum object cache size:", "%D (%d pages)", statistics.maximumObjectCachePages * ES_PAGE_SIZE,
statistics.maximumObjectCachePages);
ADD_MEMORY_STATISTIC_DISPLAY("Approximate object cache size:", "%D (%d pages)", statistics.approximateObjectCacheSize,
statistics.approximateObjectCacheSize / ES_PAGE_SIZE);
ADD_MEMORY_STATISTIC_DISPLAY("Commit (pageable):", "%D (%d pages)", statistics.commitPageable * ES_PAGE_SIZE, statistics.commitPageable);
ADD_MEMORY_STATISTIC_DISPLAY("Commit (fixed):", "%D (%d pages)", statistics.commitFixed * ES_PAGE_SIZE, statistics.commitFixed);
ADD_MEMORY_STATISTIC_DISPLAY("Commit (total):", "%D (%d pages)", (statistics.commitPageable + statistics.commitFixed) * ES_PAGE_SIZE,
statistics.commitPageable + statistics.commitFixed);
ADD_MEMORY_STATISTIC_DISPLAY("Commit limit:", "%D (%d pages)", statistics.commitLimit * ES_PAGE_SIZE, statistics.commitLimit);
ADD_MEMORY_STATISTIC_DISPLAY("Commit fixed limit:", "%D (%d pages)", statistics.commitFixedLimit * ES_PAGE_SIZE, statistics.commitFixedLimit);
ADD_MEMORY_STATISTIC_DISPLAY("Commit remaining:", "%D (%d pages)", statistics.commitRemaining * ES_PAGE_SIZE, statistics.commitRemaining);
EsTimerSet(REFRESH_INTERVAL, [] (EsGeneric context) {
Instance *instance = (Instance *) context.p;
if (instance->index == DISPLAY_MEMORY) {
UpdateDisplay(instance, DISPLAY_MEMORY);
}
}, instance);
}
}
#define GET_CONTENT(...) EsBufferFormat(message->getContent.buffer, __VA_ARGS__)
int ListViewProcessesCallback(EsElement *element, EsMessage *message) {
if (message->type == ES_MSG_LIST_VIEW_GET_CONTENT) {
int column = message->getContent.column, index = message->getContent.index.i;
ProcessItem *item = &processes[index];
if (column == 0) GET_CONTENT("%s", item->data.nameBytes, item->data.name);
else if (column == 1) { if (item->data.pid == -1) GET_CONTENT("n/a"); else GET_CONTENT("%d", item->data.pid); }
else if (column == 2) GET_CONTENT("%D", item->data.memoryUsage);
else if (column == 3) GET_CONTENT("%d%%", item->cpuUsage);
else if (column == 4) GET_CONTENT("%d", item->data.handleCount);
else EsAssert(false);
} else if (message->type == ES_MSG_LIST_VIEW_IS_SELECTED) {
message->selectItem.isSelected = processes[message->selectItem.index.u].data.pid == selectedPID;
} else if (message->type == ES_MSG_LIST_VIEW_SELECT && message->selectItem.isSelected) {
selectedPID = processes[message->selectItem.index.u].data.pid;
EsCommandSetDisabled(&element->instance->commandTerminateProcess, selectedPID < 0 || !FindProcessByPID(processes, selectedPID));
} else {
return 0;
}
return ES_HANDLED;
}
int ListViewPCIDevicesCallback(EsElement *, EsMessage *message) {
if (message->type == ES_MSG_LIST_VIEW_GET_CONTENT) {
int column = message->getContent.column, index = message->getContent.index.i;
EsPCIDevice *entry = pciDevices + index;
if (column == 0) {
GET_CONTENT("%s", entry->driverNameBytes, entry->driverName);
} else if (column == 1) {
GET_CONTENT("%x", entry->deviceID);
} else if (column == 2) {
const char *string = entry->classCode < sizeof(pciClassCodeStrings) / sizeof(pciClassCodeStrings[0])
? pciClassCodeStrings[entry->classCode] : "Unknown";
GET_CONTENT("%d - %z", entry->classCode, string);
} else if (column == 3) {
const char *string =
entry->classCode == 1 && entry->subclassCode < sizeof(pciSubclassCodeStrings1) / sizeof(const char *)
? pciSubclassCodeStrings1 [entry->subclassCode]
: entry->classCode == 12 && entry->subclassCode < sizeof(pciSubclassCodeStrings12) / sizeof(const char *)
? pciSubclassCodeStrings12[entry->subclassCode] : "";
GET_CONTENT("%d%z%z", entry->subclassCode, *string ? " - " : "", string);
} else if (column == 4) {
const char *string =
entry->classCode == 12 && entry->subclassCode == 3 && entry->progIF / 0x10 < sizeof(pciProgIFStrings12_3) / sizeof(const char *)
? pciProgIFStrings12_3[entry->progIF / 0x10] : "";
GET_CONTENT("%d%z%z", entry->progIF, *string ? " - " : "", string);
} else if (column == 5) {
GET_CONTENT("%d", entry->bus);
} else if (column == 6) {
GET_CONTENT("%d", entry->slot);
} else if (column == 7) {
GET_CONTENT("%d", entry->function);
} else if (column == 8) {
GET_CONTENT("%d", entry->interruptPin);
} else if (column == 9) {
GET_CONTENT("%d", entry->interruptLine);
} else if (column == 10) {
GET_CONTENT("%x, %D", entry->baseAddresses[0], entry->baseAddressesSizes[0]);
} else if (column == 11) {
GET_CONTENT("%x, %D", entry->baseAddresses[1], entry->baseAddressesSizes[1]);
} else if (column == 12) {
GET_CONTENT("%x, %D", entry->baseAddresses[2], entry->baseAddressesSizes[2]);
} else if (column == 13) {
GET_CONTENT("%x, %D", entry->baseAddresses[3], entry->baseAddressesSizes[3]);
} else if (column == 14) {
GET_CONTENT("%x, %D", entry->baseAddresses[4], entry->baseAddressesSizes[4]);
} else if (column == 15) {
GET_CONTENT("%x, %D", entry->baseAddresses[5], entry->baseAddressesSizes[5]);
} else {
EsAssert(false);
}
return ES_HANDLED;
}
return 0;
}
void AddTab(EsElement *toolbar, uintptr_t index, const char *label, bool asDefault = false) {
EsButton *button = EsButtonCreate(toolbar, ES_BUTTON_RADIOBOX, 0, label);
button->userData.u = index;
EsButtonOnCommand(button, [] (Instance *instance, EsElement *element, EsCommand *) {
if (EsButtonGetCheck((EsButton *) element) == ES_CHECK_CHECKED) {
UpdateDisplay(instance, element->userData.u);
}
});
if (asDefault) EsButtonSetCheck(button, ES_CHECK_CHECKED);
}
void AddListView(EsListView **pointer, EsElement *switcher, EsUICallbackFunction callback, EsListViewColumn *columns, size_t columnsSize, uint64_t additionalFlags) {
*pointer = EsListViewCreate(switcher, ES_CELL_FILL | ES_LIST_VIEW_COLUMNS | additionalFlags);
(*pointer)->messageUser = callback;
EsListViewSetColumns(*pointer, columns, columnsSize / sizeof(EsListViewColumn));
EsListViewInsertGroup(*pointer, 0);
}
void TerminateProcess(Instance *instance, EsElement *, EsCommand *) {
if (selectedPID == 0 /* Kernel */) {
// Terminating the kernel process is a meaningless action; the closest equivalent is shutting down.
EsSystemShowShutdownDialog(instance);
return;
}
EsHandle handle = EsProcessOpen(selectedPID);
if (handle) {
EsProcessTerminate(handle, 1);
} else {
EsRectangle bounds = EsElementGetWindowBounds(instance->listViewProcesses);
EsAnnouncementShow(instance->window, ES_FLAGS_DEFAULT, (bounds.l + bounds.r) / 2, (bounds.t + bounds.b) / 2, EsLiteral("Could not terminate process"));
}
}
void ProcessApplicationMessage(EsMessage *message) {
if (message->type == ES_MSG_INSTANCE_CREATE) {
Instance *instance = EsInstanceCreate(message, "System Monitor");
EsCommandRegister(&instance->commandTerminateProcess, instance, TerminateProcess, 1, "Del", false);
EsWindow *window = instance->window;
EsWindowSetIcon(window, ES_ICON_UTILITIES_SYSTEM_MONITOR);
EsPanel *switcher = EsPanelCreate(window, ES_CELL_FILL | ES_PANEL_SWITCHER, ES_STYLE_PANEL_WINDOW_DIVIDER);
instance->switcher = switcher;
instance->textboxGeneralLog = EsTextboxCreate(switcher, ES_TEXTBOX_MULTILINE | ES_CELL_FILL, &styleMonospacedTextbox);
AddListView(&instance->listViewProcesses, switcher, ListViewProcessesCallback,
listViewProcessesColumns, sizeof(listViewProcessesColumns), ES_LIST_VIEW_SINGLE_SELECT);
AddListView(&instance->listViewPCIDevices, switcher, ListViewPCIDevicesCallback,
listViewPCIDevicesColumns, sizeof(listViewPCIDevicesColumns), ES_FLAGS_DEFAULT);
instance->panelMemoryStatistics = EsPanelCreate(switcher,
ES_CELL_FILL | ES_PANEL_TABLE | ES_PANEL_HORIZONTAL | ES_PANEL_V_SCROLL_AUTO, &stylePanelMemoryStatistics);
EsPanelSetBands(instance->panelMemoryStatistics, 2 /* columns */);
EsElement *toolbar = EsWindowGetToolbar(window);
AddTab(toolbar, DISPLAY_PROCESSES, "Processes", true);
AddTab(toolbar, DISPLAY_GENERAL_LOG, "System log");
AddTab(toolbar, DISPLAY_PCI_DEVICES, "PCI devices");
AddTab(toolbar, DISPLAY_MEMORY, "Memory");
} else if (message->type == ES_MSG_INSTANCE_DESTROY) {
processes.Free();
}
}
void _start() {
_init();
while (true) {
ProcessApplicationMessage(EsMessageReceive());
}
}

8
apps/system_monitor.ini Normal file
View File

@ -0,0 +1,8 @@
[general]
name=System Monitor
icon=icon_utilities_system_monitor
permission_manage_processes=1
permission_shutdown=1
[build]
source=apps/system_monitor.cpp

180
apps/test.cpp Normal file
View File

@ -0,0 +1,180 @@
#include <essence.h>
#include <shared/strings.cpp>
// #include <shared/stb_ds.h>
EsTextbox *textbox;
#if 0
char *master;
char **undo;
void OffsetToLineAndByte(uintptr_t offset, int32_t *_line, int32_t *_byte) {
int32_t line = 0, byte = 0;
for (uintptr_t i = 0; i < offset; i++) {
if (master[i] == '\n') {
line++;
byte = 0;
} else {
byte++;
}
}
*_line = line;
*_byte = byte;
}
void Compare() {
size_t bytes;
char *contents = EsTextboxGetContents(textbox, &bytes);
// EsPrint("\tContents: '%e'\n\tMaster: '%e'\n", bytes, contents, arrlenu(master), master);
EsAssert(bytes == arrlenu(master));
EsAssert(0 == EsMemoryCompare(master, contents, bytes));
EsHeapFree(contents);
}
void FakeUndoItem(const void *, EsUndoManager *manager, EsMessage *message) {
if (message->type == ES_MSG_UNDO_INVOKE) {
EsUndoPush(manager, FakeUndoItem, nullptr, 0);
}
}
void AddUndoItem() {
char *copy = nullptr;
arrsetlen(copy, arrlenu(master));
EsMemoryCopy(copy, master, arrlenu(copy));
arrput(undo, copy);
}
void Complete() {
EsUndoPush(textbox->instance->undoManager, FakeUndoItem, nullptr, 0);
EsUndoEndGroup(textbox->instance->undoManager);
}
void Insert(uintptr_t offset, const char *string, size_t stringBytes) {
if (!stringBytes) return;
AddUndoItem();
// EsPrint("Insert '%e' at %d.\n", stringBytes, string, offset);
int32_t line, byte;
OffsetToLineAndByte(offset, &line, &byte);
EsTextboxSetSelection(textbox, line, byte, line, byte);
EsTextboxInsert(textbox, string, stringBytes);
arrinsn(master, offset, stringBytes);
EsMemoryCopy(master + offset, string, stringBytes);
Compare();
Complete();
}
void Delete(uintptr_t from, uintptr_t to) {
if (from == to) return;
AddUndoItem();
// EsPrint("Delete from %d to %d.\n", from, to);
int32_t fromLine, fromByte, toLine, toByte;
OffsetToLineAndByte(from, &fromLine, &fromByte);
OffsetToLineAndByte(to, &toLine, &toByte);
EsTextboxSetSelection(textbox, fromLine, fromByte, toLine, toByte);
EsTextboxInsert(textbox, 0, 0);
if (to > from) arrdeln(master, from, to - from);
else arrdeln(master, to, from - to);
Compare();
Complete();
}
void Test() {
EsRandomSeed(10);
EsTextboxSetUndoManager(textbox, textbox->instance->undoManager);
while (true) {
uint8_t action = EsRandomU8();
if (action < 0x70) {
size_t stringBytes = EsRandomU8() & 0x1F;
char string[0x20];
for (uintptr_t i = 0; i < stringBytes; i++) {
string[i] = EsRandomU8() < 0x40 ? '\n' : ((EsRandomU8() % 26) + 'a');
}
Insert(EsRandomU64() % (arrlenu(master) + 1), string, stringBytes);
} else if (action < 0xE0) {
if (arrlenu(master)) {
Delete(EsRandomU64() % arrlenu(master), EsRandomU64() % arrlenu(master));
}
} else {
if (!EsUndoIsEmpty(textbox->instance->undoManager, false)) {
// EsPrint("Undo.\n");
EsUndoInvokeGroup(textbox->instance->undoManager, false);
arrfree(master);
master = arrlast(undo);
arrpop(undo);
Compare();
}
}
}
}
#endif
const EsStyle stylePanel = {
.inherit = ES_STYLE_PANEL_WINDOW_BACKGROUND,
.metrics = {
.mask = ES_THEME_METRICS_INSETS | ES_THEME_METRICS_GAP_MAJOR,
.insets = ES_RECT_1(20),
.gapMajor = 1,
},
};
int TestCanvasMessage(EsElement *, EsMessage *message) {
if (message->type == ES_MSG_PAINT) {
size_t dataBytes;
const void *data = EsEmbeddedFileGet("test", &dataBytes);
if (data) EsDrawVectorFile(message->painter, EsPainterBoundsClient(message->painter), data, dataBytes);
} else if (message->type == ES_MSG_GET_WIDTH) {
message->measure.width = 256;
} else if (message->type == ES_MSG_GET_HEIGHT) {
message->measure.height = 256;
}
return 0;
}
void InitialiseInstance(EsInstance *instance) {
// EsPanel *panel = EsPanelCreate(instance->window, ES_CELL_FILL, ES_STYLE_PANEL_WINDOW_DIVIDER);
// textbox = EsTextboxCreate(panel, ES_CELL_FILL | ES_TEXTBOX_ALLOW_TABS | ES_TEXTBOX_MULTILINE, ES_STYLE_TEXTBOX_NO_BORDER);
// Test();
EsPanel *panel = EsPanelCreate(instance->window, ES_CELL_FILL, &stylePanel);
EsButtonOnCommand(EsButtonCreate(panel, ES_FLAGS_DEFAULT, 0, "Crash"), [] (EsInstance *, EsElement *, EsCommand *) { EsAssert(false); });
EsButtonOnCommand(EsButtonCreate(panel, ES_FLAGS_DEFAULT, 0, "Hang"), [] (EsInstance *, EsElement *, EsCommand *) { while (true); });
EsButtonOnCommand(EsButtonCreate(panel, ES_FLAGS_DEFAULT, 0, "Wait"), [] (EsInstance *, EsElement *, EsCommand *) { EsSleep(8000); });
EsButtonOnCommand(EsButtonCreate(panel, ES_FLAGS_DEFAULT, 0, "Wait, then crash"), [] (EsInstance *, EsElement *, EsCommand *) { EsSleep(8000); EsAssert(false); });
EsButtonOnCommand(EsButtonCreate(panel, ES_FLAGS_DEFAULT, 0, "Announcement 1"), [] (EsInstance *, EsElement *element, EsCommand *) {
EsRectangle bounds = EsElementGetWindowBounds(element);
EsAnnouncementShow(element->window, ES_FLAGS_DEFAULT, (bounds.l + bounds.r) / 2, (bounds.t + bounds.b) / 2, "Hello, world!", -1);
});
EsButtonOnCommand(EsButtonCreate(panel, ES_FLAGS_DEFAULT, 0, "Announcement 2"), [] (EsInstance *, EsElement *element, EsCommand *) {
EsRectangle bounds = EsElementGetWindowBounds(element);
EsAnnouncementShow(element->window, ES_FLAGS_DEFAULT, (bounds.l + bounds.r) / 2, (bounds.t + bounds.b) / 2, INTERFACE_STRING(DesktopApplicationStartupError));
});
EsButtonCreate(panel, ES_FLAGS_DEFAULT, 0, "Push");
EsButtonCreate(panel, ES_FLAGS_DEFAULT, 0, "Push");
EsTextboxUseNumberOverlay(EsTextboxCreate(panel, ES_TEXTBOX_EDIT_BASED), true);
EsCustomElementCreate(panel)->messageUser = TestCanvasMessage;
}
void _start() {
_init();
while (true) {
EsMessage *message = EsMessageReceive();
if (message->type == ES_MSG_INSTANCE_CREATE) {
InitialiseInstance(EsInstanceCreate(message, "Test App"));
}
}
}

8
apps/test.ini Normal file
View File

@ -0,0 +1,8 @@
[general]
name=Test
icon=icon_system_software_install
use_single_process=1
[build]
source=apps/test.cpp
link_flags=-Lroot/Applications/POSIX/lib -lc

334
apps/text_editor.cpp Normal file
View File

@ -0,0 +1,334 @@
#define ES_INSTANCE_TYPE Instance
#include <essence.h>
#include <shared/strings.cpp>
// TODO Document save/load model, then merge into API.
// TODO Replace toolbar, then merge into API.
// TODO Merge Format menu into API.
// TODO Word wrap (textbox feature).
// TODO Possible extension features:
// - Block selection
// - Folding
// - Tab settings and auto-indent
// - Macros
// - Status bar
// - Goto line
// - Find in files
// - Convert case
// - Sort lines
// - Trim trailing space
// - Indent/comment/join/split shortcuts
const EsStyle styleFormatPopupColumn = {
.metrics = {
.mask = ES_THEME_METRICS_GAP_MAJOR,
.gapMajor = 5,
},
};
const EsInstanceClassEditorSettings editorSettings = {
INTERFACE_STRING(TextEditorNewFileName),
INTERFACE_STRING(TextEditorNewDocument),
ES_ICON_TEXT,
};
struct Instance : EsInstance {
EsTextbox *textboxDocument,
*textboxSearch;
EsElement *toolbarMain, *toolbarSearch;
EsTextDisplay *displaySearch;
EsButton *buttonFormat;
EsCommand commandFindNext,
commandFindPrevious,
commandFind,
commandFormat;
uint32_t syntaxHighlightingLanguage;
};
void Find(Instance *instance, bool backwards) {
EsWindowSwitchToolbar(instance->window, instance->toolbarSearch, ES_TRANSITION_SLIDE_UP);
size_t needleBytes;
char *needle = EsTextboxGetContents(instance->textboxSearch, &needleBytes);
int32_t line0, byte0, line1, byte1;
EsTextboxGetSelection(instance->textboxDocument, &line0, &byte0, &line1, &byte1);
if (backwards) {
if (line1 < line0) {
line0 = line1;
byte0 = byte1;
} else if (line1 == line0 && byte1 < byte0) {
byte0 = byte1;
}
} else {
if (line1 > line0) {
line0 = line1;
byte0 = byte1;
} else if (line1 == line0 && byte1 > byte0) {
byte0 = byte1;
}
}
bool found = EsTextboxFind(instance->textboxDocument, needle, needleBytes, &line0, &byte0, backwards ? ES_TEXTBOX_FIND_BACKWARDS : ES_FLAGS_DEFAULT);
if (found) {
EsTextDisplaySetContents(instance->displaySearch, "");
EsTextboxSetSelection(instance->textboxDocument, line0, byte0, line0, byte0 + needleBytes);
EsTextboxEnsureCaretVisible(instance->textboxDocument, true);
EsElementFocus(instance->textboxDocument);
} else if (!needleBytes) {
EsTextDisplaySetContents(instance->displaySearch, INTERFACE_STRING(CommonSearchPrompt2));
EsElementFocus(instance->textboxSearch);
} else {
EsTextDisplaySetContents(instance->displaySearch, INTERFACE_STRING(CommonSearchNoMatches));
EsElementFocus(instance->textboxSearch);
}
EsHeapFree(needle);
}
void SetLanguage(Instance *instance, uint32_t newLanguage) {
EsTextStyle textStyle = {};
EsTextboxGetTextStyle(instance->textboxDocument, &textStyle);
textStyle.font.family = newLanguage ? ES_FONT_MONOSPACED : ES_FONT_SANS;
EsTextboxSetTextStyle(instance->textboxDocument, &textStyle);
instance->syntaxHighlightingLanguage = newLanguage;
EsTextboxSetupSyntaxHighlighting(instance->textboxDocument, newLanguage);
}
void FormatPopupCreate(Instance *instance) {
EsMenu *menu = EsMenuCreate(instance->buttonFormat, ES_FLAGS_DEFAULT);
EsPanel *panel = EsPanelCreate(menu, ES_PANEL_HORIZONTAL, ES_STYLE_PANEL_POPUP);
{
EsPanel *column = EsPanelCreate(panel, ES_FLAGS_DEFAULT, &styleFormatPopupColumn);
EsTextDisplayCreate(column, ES_CELL_H_EXPAND, ES_STYLE_TEXT_LABEL, INTERFACE_STRING(CommonFormatSize));
EsListView *list = EsListViewCreate(column, ES_LIST_VIEW_CHOICE_SELECT | ES_LIST_VIEW_FIXED_ITEMS, ES_STYLE_LIST_CHOICE_BORDERED);
const int presetSizes[] = {
8, 9, 10, 11, 12, 13,
14, 16,
18, 24, 30,
36, 48, 60,
72, 96, 120, 144,
};
for (uintptr_t i = 0; i < sizeof(presetSizes) / sizeof(presetSizes[0]); i++) {
char buffer[64];
EsListViewInsertFixedItem(list, buffer, EsStringFormat(buffer, sizeof(buffer), "%d pt", presetSizes[i]), presetSizes[i]);
}
EsTextStyle textStyle = {};
EsTextboxGetTextStyle(instance->textboxDocument, &textStyle);
EsListViewSelectFixedItem(list, textStyle.size);
list->messageUser = [] (EsElement *element, EsMessage *message) {
if (message->type == ES_MSG_LIST_VIEW_SELECT) {
Instance *instance = element->instance;
EsGeneric newSize;
if (EsListViewGetSelectedFixedItem(((EsListView *) element), &newSize)) {
EsTextStyle textStyle = {};
EsTextboxGetTextStyle(instance->textboxDocument, &textStyle);
textStyle.size = newSize.u;
EsTextboxSetTextStyle(instance->textboxDocument, &textStyle);
}
}
return 0;
};
}
{
EsPanel *column = EsPanelCreate(panel, ES_FLAGS_DEFAULT, &styleFormatPopupColumn);
EsTextDisplayCreate(column, ES_CELL_H_EXPAND, ES_STYLE_TEXT_LABEL, INTERFACE_STRING(CommonFormatLanguage));
EsListView *list = EsListViewCreate(column, ES_LIST_VIEW_CHOICE_SELECT | ES_LIST_VIEW_FIXED_ITEMS, ES_STYLE_LIST_CHOICE_BORDERED);
EsListViewInsertFixedItem(list, INTERFACE_STRING(CommonFormatPlainText), 0);
EsListViewInsertFixedItem(list, "C/C++", -1, ES_SYNTAX_HIGHLIGHTING_LANGUAGE_C);
EsListViewInsertFixedItem(list, "Ini file", -1, ES_SYNTAX_HIGHLIGHTING_LANGUAGE_INI);
EsListViewSelectFixedItem(list, instance->syntaxHighlightingLanguage);
list->messageUser = [] (EsElement *element, EsMessage *message) {
if (message->type == ES_MSG_LIST_VIEW_SELECT) {
Instance *instance = element->instance;
EsGeneric newLanguage;
if (EsListViewGetSelectedFixedItem(((EsListView *) element), &newLanguage)) {
SetLanguage(instance, newLanguage.u);
}
}
return 0;
};
}
EsMenuShow(menu);
}
void ProcessApplicationMessage(EsMessage *message) {
if (message->type == ES_MSG_INSTANCE_CREATE) {
Instance *instance = EsInstanceCreate(message, INTERFACE_STRING(TextEditorTitle));
EsInstanceSetClassEditor(instance, &editorSettings);
EsWindow *window = instance->window;
EsWindowSetIcon(window, ES_ICON_ACCESSORIES_TEXT_EDITOR);
EsButton *button;
// Commands:
uint32_t stableID = 1;
EsCommandRegister(&instance->commandFindNext, instance, [] (Instance *instance, EsElement *, EsCommand *) {
Find(instance, false);
}, stableID++, "F3");
EsCommandRegister(&instance->commandFindPrevious, instance, [] (Instance *instance, EsElement *, EsCommand *) {
Find(instance, true);
}, stableID++, "Shift+F3");
EsCommandRegister(&instance->commandFind, instance, [] (Instance *instance, EsElement *, EsCommand *) {
EsWindowSwitchToolbar(instance->window, instance->toolbarSearch, ES_TRANSITION_ZOOM_OUT);
EsElementFocus(instance->textboxSearch);
}, stableID++, "Ctrl+F");
EsCommandRegister(&instance->commandFormat, instance, [] (Instance *instance, EsElement *, EsCommand *) {
FormatPopupCreate(instance);
}, stableID++, "Ctrl+Alt+T");
EsCommandSetDisabled(&instance->commandFindNext, false);
EsCommandSetDisabled(&instance->commandFindPrevious, false);
EsCommandSetDisabled(&instance->commandFind, false);
EsCommandSetDisabled(&instance->commandFormat, false);
// Content:
EsPanel *panel = EsPanelCreate(window, ES_CELL_FILL, ES_STYLE_PANEL_WINDOW_DIVIDER);
instance->textboxDocument = EsTextboxCreate(panel,
ES_CELL_FILL | ES_TEXTBOX_MULTILINE | ES_TEXTBOX_ALLOW_TABS | ES_TEXTBOX_MARGIN,
ES_STYLE_TEXTBOX_NO_BORDER);
instance->textboxDocument->cName = "document";
EsTextboxSetUndoManager(instance->textboxDocument, instance->undoManager);
EsElementFocus(instance->textboxDocument);
// Main toolbar:
EsElement *toolbarMain = instance->toolbarMain = EsWindowGetToolbar(window, true);
EsToolbarAddFileMenu(toolbarMain);
button = EsButtonCreate(toolbarMain, ES_FLAGS_DEFAULT, {}, INTERFACE_STRING(CommonSearchOpen));
button->accessKey = 'S';
EsButtonSetIcon(button, ES_ICON_EDIT_FIND_SYMBOLIC);
EsButtonOnCommand(button, [] (Instance *instance, EsElement *, EsCommand *) {
EsWindowSwitchToolbar(instance->window, instance->toolbarSearch, ES_TRANSITION_SLIDE_UP);
EsElementFocus(instance->textboxSearch);
});
button = EsButtonCreate(toolbarMain, ES_BUTTON_DROPDOWN, {}, INTERFACE_STRING(CommonFormatPopup));
button->accessKey = 'M';
EsButtonSetIcon(button, ES_ICON_FORMAT_TEXT_LARGER_SYMBOLIC);
EsCommandAddButton(&instance->commandFormat, button);
instance->buttonFormat = button;
EsWindowSwitchToolbar(window, toolbarMain, ES_TRANSITION_NONE);
// Search toolbar:
EsElement *toolbarSearch = instance->toolbarSearch = EsWindowGetToolbar(window, true);
button = EsButtonCreate(toolbarSearch, ES_FLAGS_DEFAULT, 0);
button->cName = "go back", button->accessKey = 'X';
EsButtonSetIcon(button, ES_ICON_GO_FIRST_SYMBOLIC);
EsButtonOnCommand(button, [] (Instance *instance, EsElement *, EsCommand *) {
EsWindowSwitchToolbar(instance->window, instance->toolbarMain, ES_TRANSITION_SLIDE_DOWN);
});
EsPanel *section = EsPanelCreate(toolbarSearch, ES_PANEL_HORIZONTAL);
EsTextDisplayCreate(section, ES_FLAGS_DEFAULT, 0, INTERFACE_STRING(CommonSearchPrompt));
instance->textboxSearch = EsTextboxCreate(section, ES_FLAGS_DEFAULT, {});
instance->textboxSearch->cName = "search textbox";
instance->textboxSearch->accessKey = 'S';
instance->textboxSearch->messageUser = [] (EsElement *element, EsMessage *message) {
Instance *instance = element->instance;
if (message->type == ES_MSG_KEY_DOWN && message->keyboard.scancode == ES_SCANCODE_ENTER) {
EsCommand *command = (message->keyboard.modifiers & ES_MODIFIER_SHIFT) ? &instance->commandFindPrevious : &instance->commandFindNext;
command->callback(instance, element, command);
return ES_HANDLED;
} else if (message->type == ES_MSG_KEY_DOWN && message->keyboard.scancode == ES_SCANCODE_ESCAPE) {
EsWindowSwitchToolbar(instance->window, instance->toolbarMain, ES_TRANSITION_SLIDE_DOWN);
EsElementFocus(instance->textboxDocument);
return ES_HANDLED;
} else if (message->type == ES_MSG_FOCUSED_START) {
EsTextboxSelectAll(instance->textboxSearch);
}
return 0;
};
instance->displaySearch = EsTextDisplayCreate(toolbarSearch, ES_CELL_H_FILL, {}, "");
button = EsButtonCreate(toolbarSearch, ES_FLAGS_DEFAULT, {}, INTERFACE_STRING(CommonSearchNext));
button->accessKey = 'N';
EsCommandAddButton(&instance->commandFindNext, button);
button = EsButtonCreate(toolbarSearch, ES_FLAGS_DEFAULT, {}, INTERFACE_STRING(CommonSearchPrevious));
button->accessKey = 'P';
EsCommandAddButton(&instance->commandFindPrevious, button);
} else if (message->type == ES_MSG_INSTANCE_OPEN) {
Instance *instance = message->instanceOpen.instance;
size_t fileSize;
char *file = (char *) EsFileStoreReadAll(message->instanceOpen.file, &fileSize);
if (!file) {
EsInstanceOpenComplete(message, false);
} else if (!EsUTF8IsValid(file, fileSize)) {
EsInstanceOpenComplete(message, false);
} else {
EsTextboxSelectAll(instance->textboxDocument);
EsTextboxInsert(instance->textboxDocument, file, fileSize);
EsTextboxSetSelection(instance->textboxDocument, 0, 0, 0, 0);
EsElementRelayout(instance->textboxDocument);
if (EsStringEndsWith(message->instanceOpen.name, message->instanceOpen.nameBytes, EsLiteral(".c"), true)
|| EsStringEndsWith(message->instanceOpen.name, message->instanceOpen.nameBytes, EsLiteral(".cpp"), true)
|| EsStringEndsWith(message->instanceOpen.name, message->instanceOpen.nameBytes, EsLiteral(".h"), true)) {
SetLanguage(instance, ES_SYNTAX_HIGHLIGHTING_LANGUAGE_C);
} else if (EsStringEndsWith(message->instanceOpen.name, message->instanceOpen.nameBytes, EsLiteral(".ini"), true)) {
SetLanguage(instance, ES_SYNTAX_HIGHLIGHTING_LANGUAGE_INI);
} else {
SetLanguage(instance, 0);
}
EsInstanceOpenComplete(message, true);
}
} else if (message->type == ES_MSG_INSTANCE_SAVE) {
Instance *instance = message->instanceSave.instance;
size_t byteCount;
char *contents = EsTextboxGetContents(instance->textboxDocument, &byteCount);
EsFileStoreWriteAll(message->instanceSave.file, contents, byteCount);
EsHeapFree(contents);
EsInstanceSaveComplete(message, true);
}
}
void _start() {
_init();
while (true) {
ProcessApplicationMessage(EsMessageReceive());
}
}

27
apps/text_editor.ini Normal file
View File

@ -0,0 +1,27 @@
[general]
name=Text Editor
icon=icon_accessories_text_editor
use_single_process=1
[build]
source=apps/text_editor.cpp
[@handler]
extension=txt
action=open
[@handler]
extension=cpp
action=open
[@handler]
extension=h
action=open
[@handler]
extension=c
action=open
[@handler]
extension=ini
action=open

178
boot/x86/esfs-stage1.s Normal file
View File

@ -0,0 +1,178 @@
[bits 16]
[org 0x7C00]
%define superblock 0x8000
%define temporary_load_buffer 0x9000
%define block_group_descriptor_table 0x10000
%define directory_load_location 0x1000
%define stage2_load_location 0x1000
%define inode_table_load_buffer 0x20000
%define magic_breakpoint xchg bx,bx
;%define magic_breakpoint
start:
; Setup segment registers
cli
mov ax,0
mov ds,ax
mov es,ax
mov fs,ax
mov gs,ax
mov ss,ax
mov sp,0x7C00
jmp 0x0:.continue
.continue:
sti
; Save the information passed from the MBR
mov [drive_number],dl
mov [partition_entry],si
mov [use_emu_read],dh
; Get drive parameters.
or dh,dh
jz .skip_params
mov ah,0x08
xor di,di
int 0x13
mov si,error_cannot_read_disk
jc error
and cx,63
mov [max_sectors],cx
inc dh
shr dx,8
mov [max_heads],dx
.skip_params:
; Print a startup message
mov si,startup_message
.loop:
lodsb
or al,al
jz .end
mov ah,0xE
int 0x10
jmp .loop
.end:
; Load the second stage
mov cx,15
mov eax,1
mov edi,stage2_load_location
call load_sectors
mov dl,[drive_number]
mov si,[partition_entry]
mov dh,[use_emu_read]
mov bx,[max_sectors]
mov cx,[max_heads]
jmp 0:stage2_load_location
load_sectors:
; Load CX sectors from sector EAX to buffer [EDI]. Returns end of buffer in EDI.
pusha
push edi
; Add the partition offset to EAX
mov bx,[partition_entry]
mov ebx,[bx + 8]
add eax,ebx
; Load 1 sector
mov [read_structure.lba],eax
mov ah,0x42
mov dl,[drive_number]
mov si,read_structure
cmp byte [use_emu_read],1
je .use_emu
int 0x13
jmp .done_read
.use_emu:
call load_sector_emu
.done_read:
; Check for error
mov si,error_cannot_read_disk
jc error
; Copy the data to its destination
pop edi
mov cx,0x200
mov eax,edi
shr eax,4
and eax,0xF000
mov es,ax
mov si,temporary_load_buffer
rep movsb
; Go to the next sector
popa
add edi,0x200
inc eax
loop load_sectors
ret
load_sector_emu:
mov di,[read_structure.lba]
xor ax,ax
mov es,ax
mov bx,0x9000
; Calculate cylinder and head.
mov ax,di
xor dx,dx
div word [max_sectors]
xor dx,dx
div word [max_heads]
push dx ; remainder - head
mov ch,al ; quotient - cylinder
shl ah,6
mov cl,ah
; Calculate sector.
mov ax,di
xor dx,dx
div word [max_sectors]
inc dx
or cl,dl
; Load the sector.
pop dx
mov dh,dl
mov dl,[drive_number]
mov ax,0x0201
int 0x13
ret
error:
; Print an error message
lodsb
or al,al
jz .break
mov ah,0xE
int 0x10
jmp error
; Break indefinitely
.break:
cli
hlt
startup_message: db "Booting operating system...",10,13,0
drive_number: db 0
use_emu_read: db 0
partition_entry: dw 0
max_sectors: dw 0
max_heads: dw 0
read_structure: ; Data for the extended read calls
dw 0x10
dw 1
dd temporary_load_buffer
.lba: dq 0
error_cannot_read_disk: db "Error: The disk could not be read. (S1)",0
times (0x200 - ($-$$)) nop

134
boot/x86/esfs-stage2.s Normal file
View File

@ -0,0 +1,134 @@
%macro FilesystemInitialise 0
%define superblock 0x8000
%define kernel_file_entry 0x8800
%endmacro
%macro FilesystemGetKernelSize 0
load_kernel:
; Load the superblock.
mov eax,16
mov edi,superblock
mov cx,1
call load_sectors
mov ax,superblock / 16
mov fs,ax
; Check the signature.
mov eax,[fs:0]
cmp eax,0x73734521
mov si,ErrorBadFilesystem
jne error
; Check the read version.
mov ax,[fs:48]
cmp ax,10
mov si,ErrorBadFilesystem
jg error
; Save the OS installation identifier.
mov eax,[fs:152]
mov [os_installation_identifier + 0],eax
mov eax,[fs:156]
mov [os_installation_identifier + 4],eax
mov eax,[fs:160]
mov [os_installation_identifier + 8],eax
mov eax,[fs:164]
mov [os_installation_identifier + 12],eax
; Load the kernel's file entry.
mov eax,[fs:184] ; Get the block containing the kernel.
mul dword [fs:64] ; Multiply by block size.
shr eax,9 ; Divide by sector size.
mov cx,2 ; 2 sectors - 1024 bytes.
mov edi,[fs:192] ; The offset into the block.
shr edi,9 ; Divide by sector size.
add eax,edi ; Add to the first sector.
mov edi,kernel_file_entry
call load_sectors
; Find the data stream.
FindDataStreamLoop:
mov bx,[KernelDataStreamPosition]
mov eax,[fs:bx]
cmp ax,1
je SaveKernelSize
shr eax,16
add [KernelDataStreamPosition],ax
cmp bx,0xC00
mov si,ErrorUnexpectedFileProblem
jge error
jmp FindDataStreamLoop
; Save the size of the kernel.
SaveKernelSize:
mov eax,[fs:0x838]
mov [kernel_size],eax
mov si,error_kernel_too_large
cmp eax,0x300000
jg error
%endmacro
%macro FilesystemLoadKernelIntoBuffer 0
; Check that the kernel is stored as DATA_INDIRECT file.
CheckForDataIndirect:
mov ax,superblock / 16
mov fs,ax
mov bx,[KernelDataStreamPosition]
mov ax,[fs:bx+4]
mov si,ErrorUnexpectedFileProblem
cmp al,2
jne error
; Load each extent from the file.
LoadEachExtent:
mov edi,[kernel_buffer]
mov si,[fs:bx+6]
add bx,32
; TODO More than 1 extent. (We don't do the offset correctly).
push si
cmp si,1
mov si,ErrorUnexpectedFileProblem
jne error
pop si
; Load the blocks.
.ExtentLoop:
; TODO Other extents than offset = 1, count = 2.
mov al,[fs:bx]
cmp al,0x08
push si
mov si,ErrorUnexpectedFileProblem
jne error
pop si
mov eax,[fs:64]
shr eax,9 ; EAX = Sectors per block.
xor ecx,ecx
mov cx,[fs:bx+2]
xchg ch,cl
mul cx
mov cx,ax ; CX = Count.
xor eax,eax
mov al,[fs:bx+1]
mul dword [fs:64]
shr eax,9 ; EAX = Block.
xchg bx,bx
call load_sectors
add bx,4
; Go to the next extent.
sub si,1
cmp si,0
jne .ExtentLoop
%endmacro
%macro FilesystemSpecificCode 0
KernelDataStreamPosition: dw 0x850
ErrorBadFilesystem: db "Invalid boot EsFS volume.",0
ErrorUnexpectedFileProblem: db "The kernel file could not be loaded.",0
%endmacro

913
boot/x86/loader.s Normal file
View File

@ -0,0 +1,913 @@
[bits 16]
[org 0x1000]
; This is missing any fileSystem specific macros.
%define vesa_info 0x7000
%define os_installation_identifier 0x7FF0
%define temporary_load_buffer 0x9000
%define page_directory 0x40000
%define page_directory_length 0x20000
%define memory_map 0x60000
%define indirect_block_buffer 0x64000
; %define magic_breakpoint xchg bx,bx
%define magic_breakpoint
start:
mov esp,0x7C00
; Save information passed from stage 1
mov [drive_number],dl
mov [partition_entry],si
mov [use_emu_read],dh
mov [max_sectors],bx
mov [max_heads],cx
; @FilesystemSpecific
FilesystemInitialise
check_pci:
; Check the computer has PCI
mov ax,0xB101
xor edi,edi
int 0x1A
mov si,error_no_pci
jc error
or ah,ah
jnz error
check_cpuid:
; Check the CPU has CPUID
mov dword [24],.no_cpuid
mov eax,0
cpuid
jmp .has_cpuid
.no_cpuid:
mov si,error_no_cpuid
jmp error
.has_cpuid:
check_msr:
; Check the CPU has MSRs
mov dword [24],.no_msr
mov ecx,0xC0000080
rdmsr
jmp .has_msr
.no_msr:
mov si,error_no_msr
jmp error
.has_msr:
enable_a20:
; Enable the A20 line, if necessary
cli
call check_a20
jc .a20_enabled
mov ax,0x2401
int 0x15
call check_a20
jc .a20_enabled
mov si,error_cannot_enable_a20_line
jmp error
.a20_enabled:
sti
identity_paging:
; Map the first 4MB to itself for the bootloader to do work in protected mode
mov eax,page_directory / 16
mov es,ax
; Clear the page directory
xor eax,eax
mov ecx,0x400
xor di,di
rep stosd
; Recursive map the directory
mov dword [es:0x3FF * 4],page_directory | 3
; Put the first table in the directory
mov dword [es:0],(page_directory + 0x1000) | 3
; Fill the table
mov edi,0x1000
mov cx,0x400
mov eax,3
.loop:
mov [es:edi],eax
add edi,4
add eax,0x1000
loop .loop
; Set the pointer to the page directory
mov eax,page_directory
mov cr3,eax
load_gdt:
; Load the GDT
lgdt [gdt_data.gdt]
inform_bios_mixed_mode:
mov eax,0xEC00
mov ebx,3 ; may switch between legacy and long mode
int 0x15
load_memory_map:
; Load the memory map
xor ebx,ebx
; Set FS to access the memory map
mov ax,0
mov es,ax
mov ax,memory_map / 16
mov fs,ax
; Loop through each memory map entry
.loop:
mov di,.entry
mov edx,0x534D4150
mov ecx,24
mov eax,0xE820
mov byte [.acpi],1
int 0x15
jc .finished
; Check the BIOS call worked
cmp eax,0x534D4150
jne .fail
; pusha
; mov di,.entry
; call .print_bytes
; popa
; Check if this is usable memory
cmp dword [.type],1
jne .try_next
cmp dword [.size],0
je .try_next
cmp dword [.acpi],0
je .try_next
; Check that the region is big enough
mov eax,[.size]
and eax,~0x3FFF
or eax,eax
jz .try_next
; Check that the base is above 1MB
cmp dword [.base + 4],0
jne .base_good
cmp dword [.base],0x100000
jl .try_next
.base_good:
; Align the base to the nearest page
mov eax,[.base]
and eax,0xFFF
or eax,eax
jz .base_aligned
mov eax,[.base]
and eax,~0xFFF
add eax,0x1000
mov [.base],eax
sub dword [.size],0x1000
sbb dword [.size + 4],0
.base_aligned:
; Align the size to the nearest page
mov eax,[.size]
and eax,~0xFFF
mov [.size],eax
; Convert the size from bytes to 4KB pages
mov eax,[.size]
shr eax,12
push ebx
mov ebx,[.size + 4]
shl ebx,20
add eax,ebx
pop ebx
mov [.size],eax
mov dword [.size + 4],0
; Store the entry
push ebx
mov ebx,[.pointer]
mov eax,[.base]
mov [fs:bx],eax
mov eax,[.base + 4]
mov [fs:bx + 4],eax
mov eax,[.size]
mov [fs:bx + 8],eax
add [.total_memory],eax
mov eax,[.size + 4]
adc [.total_memory + 4],eax
mov [fs:bx + 12],eax
add dword [.pointer],16
pop ebx
; Continue to the next entry
.try_next:
or ebx,ebx
jnz .loop
; Make sure that there were enough entries
.finished:
mov eax,[.pointer]
shr eax,4
or eax,eax
jz .fail
; Clear the base value for the entry after last
mov ebx,[.pointer]
mov dword [fs:bx],0
mov dword [fs:bx + 4],0
; Store the total memory
mov eax,[.total_memory]
mov dword [fs:bx + 8],eax
mov eax,[.total_memory + 4]
mov dword [fs:bx + 12],eax
; Load the kernel!
jmp load_kernel
; Display an error message if we could not load the memory map
.fail:
mov si,error_could_not_get_memory_map
jmp error
.pointer: dd 0
.entry:
.base: dq 0
.size: dq 0
.type: dd 0
.acpi: dd 0
.total_memory: dq 0
; @FilesystemSpecific
FilesystemGetKernelSize
allocate_kernel_buffer:
; Switch to protected mode
push ds
push es
push ss
cli
mov eax,cr0
or eax,0x80000001
mov cr0,eax
jmp 0x8:.pmode
; Set the data segment registers
[bits 32]
.pmode:
mov ax,0x10
mov ds,ax
mov es,ax
mov ss,ax
; Work out the size of memory we'll allocate
mov ecx,[kernel_size]
shr ecx,12
inc ecx
mov edx,ecx
shl edx,12
; For every memory region
xor ebx,ebx
.memory_region_loop:
; Is this the region starting at 1MB?
mov eax,[ebx + memory_map + 4]
or eax,eax
jnz .try_next_memory_region
mov eax,[ebx + memory_map]
cmp eax,0x100000
jne .try_next_memory_region
; Check the region has enough pages remaining
mov eax,[ebx + memory_map + 8]
cmp eax,ecx
jl .try_next_memory_region
; Remove ECX pages from the region
mov eax,[ebx + memory_map + 0]
mov [kernel_buffer],eax
add eax,edx
mov [ebx + memory_map + 0],eax
sub dword [ebx + memory_map + 8],ecx
sbb dword [ebx + memory_map + 12],0
jmp .found_buffer
; Go to the next memory region
.try_next_memory_region:
add ebx,16
mov eax,[load_memory_map.pointer]
cmp ebx,eax
jne .memory_region_loop
mov si,error_no_memory
jmp error_32
.found_buffer:
; Switch to 16-bit mode
mov eax,cr0
and eax,0x7FFFFFFF
mov cr0,eax
jmp 0x18:.rmode
; Switch to real mode
[bits 16]
.rmode:
mov eax,cr0
and eax,0x7FFFFFFE
mov cr0,eax
jmp 0x0:.finish
; Go to error
.finish:
pop ss
pop es
pop ds
; @FilesystemSpecific
FilesystemLoadKernelIntoBuffer
enable_video_mode:
call vbe_init
jmp enable_video_mode_done
%include "boot/x86/vbe.s"
enable_video_mode_done:
setup_elf:
; Switch to protected mode
cli
mov eax,cr0
or eax,0x80000001
mov cr0,eax
jmp 0x8:.pmode
; Set the data segment registers
[bits 32]
.pmode:
mov ax,0x10
mov ds,ax
mov es,ax
mov ss,ax
; Check the ELF data is correct
mov ebx,[kernel_buffer]
mov esi,error_bad_kernel
cmp dword [ebx + 0],0x464C457F
jne error_32
cmp byte [ebx + 4],2
je setup_elf_64
jne error_32
setup_elf_64:
; Check that the processor is 64-bit
mov ecx,0x80000001
cpuid
mov esi,error_no_long_mode
test eax,0x20000000
jnz error_32
; Disable paging
mov eax,cr0
and eax,0x7FFFFFFF
mov cr0,eax
; Identity map the first 4MB
mov dword [page_table_allocation_location],0x5000
mov ecx,page_directory_length
xor eax,eax
mov edi,page_directory
rep stosb
mov dword [page_directory + 0x1000 - 16],page_directory | 3
mov dword [page_directory],(page_directory + 0x1000) | 7
mov dword [page_directory + 0x1000],(page_directory + 0x2000) | 7
mov dword [page_directory + 0x2000],(page_directory + 0x3000) | 7
mov dword [page_directory + 0x2000 + 8],(page_directory + 0x4000) | 7
mov edi,page_directory + 0x3000
mov eax,0x000003
mov ebx,0x200003
mov ecx,0x400
.identity_loop:
mov [edi],eax
add edi,8
add eax,0x1000
loop .identity_loop
mov eax,page_directory
mov cr3,eax
; Enable long mode
mov eax,cr4
or eax,32
mov cr4,eax
mov ecx,0xC0000080
rdmsr
or eax,256
wrmsr
mov eax,cr0
or eax,0x80000000
mov cr0,eax
; Go to 64-bit mode
jmp 0x48:.start_64_bit_mode
[bits 64]
.start_64_bit_mode:
mov rax,0x50
mov ds,rax
mov es,rax
mov ss,rax
; Check the ELF data is correct
mov rbx,[kernel_buffer]
mov rsi,error_bad_kernel
cmp byte [rbx + 5],1
jne error_64
cmp byte [rbx + 7],0
jne error_64
cmp byte [rbx + 16],2
jne error_64
cmp byte [rbx + 18],0x3E
jne error_64
; Find the program headers
; RAX = ELF header, RBX = program headers
mov rax,rbx
mov rbx,[rax + 32]
add rbx,rax
; ECX = entries, EDX = size of entry
movzx rcx,word [rax + 56]
movzx rdx,word [rax + 54]
; Loop through each program header
.loop_program_headers:
push rax
push rcx
push rdx
push rbx
; Only deal with load segments
mov eax,[rbx]
cmp eax,1
jne .next_entry
; Work out how many physical pages we need to allocate
mov rcx,[rbx + 40]
shr rcx,12
inc rcx
; Get the starting virtual address
mov rax,[rbx + 16]
shl rax,16
shr rax,16
mov [.target_page],rax
; For every frame in the segment
.frame_loop:
xor rbx,rbx
; For every memory region
.memory_region_loop:
; Check the region has enough pages remaining
mov rax,[rbx + memory_map + 8]
or rax,rax
jz .try_next_memory_region
; Remove one page from the region
mov rax,[rbx + memory_map + 0]
mov [.physical_page],rax
add rax,0x1000
mov [rbx + memory_map + 0],rax
sub qword [rbx + memory_map + 8],1
jmp .found_physical_page
; Go to the next memory region
.try_next_memory_region:
add rbx,16
mov eax,[load_memory_map.pointer]
cmp ebx,eax
jne .memory_region_loop
mov si,error_no_memory
jmp error_64
; Map the page into virtual memory
.found_physical_page:
; Make sure we have a PDP
mov rax,[.target_page]
shr rax,39
mov r8,0xFFFFFF7FBFDFE000
mov rbx,[r8 + rax * 8]
cmp rbx,0
jne .has_pdp
mov rbx,[page_table_allocation_location]
add rbx,page_directory
or rbx,7
mov [r8 + rax * 8],rbx
add qword [page_table_allocation_location],0x1000
mov rax,cr3
mov cr3,rax
.has_pdp:
; Make sure we have a PD
mov rax,[.target_page]
shr rax,30
mov r8,0xFFFFFF7FBFC00000
mov rbx,[r8 + rax * 8]
cmp rbx,0
jne .has_pd
mov rbx,[page_table_allocation_location]
add rbx,page_directory
or rbx,7
mov [r8 + rax * 8],rbx
add qword [page_table_allocation_location],0x1000
mov rax,cr3
mov cr3,rax
.has_pd:
; Make sure we have a PT
mov rax,[.target_page]
shr rax,21
mov r8,0xFFFFFF7F80000000
mov rbx,[r8 + rax * 8]
cmp rbx,0
jne .has_pt
mov rbx,[page_table_allocation_location]
add rbx,page_directory
or rbx,7
mov [r8 + rax * 8],rbx
add qword [page_table_allocation_location],0x1000
mov rax,cr3
mov cr3,rax
.has_pt:
; Map the page!
mov rax,[.target_page]
shr rax,12
mov rbx,[.physical_page]
or rbx,0x103
shl rax,3
xor r8,r8
mov r8,0xFFFFFF00
shl r8,32
add rax,r8
mov [rax],rbx
mov rbx,[.target_page]
invlpg [rbx]
; Go to the next frame
add qword [.target_page],0x1000
dec rcx
or rcx,rcx
jnz .frame_loop
; Restore the pointer to the segment
pop rbx
push rbx
; Clear the memory
mov rcx,[rbx + 40]
xor rax,rax
mov rdi,[rbx + 16]
rep stosb
; Copy the memory
mov rcx,[rbx + 32]
mov rsi,[rbx + 8]
add rsi,[kernel_buffer]
mov rdi,[ebx + 16]
rep movsb
; Go to the next entry
.next_entry:
pop rbx
pop rdx
pop rcx
pop rax
add rbx,rdx
dec rcx
or rcx,rcx
jnz .loop_program_headers
jmp run_kernel64
.target_page: dq 0
.physical_page: dq 0
run_kernel64:
; Get the start address of the kernel
mov rbx,[kernel_buffer]
mov rcx,[rbx + 24]
; Let the kernel use the memory that was used to store the executable
xor eax,eax
mov ebx,[load_memory_map.pointer]
mov [memory_map + ebx + 4],eax
mov [memory_map + ebx + 12],eax
mov [memory_map + ebx + 16],eax
mov [memory_map + ebx + 20],eax
mov eax,[memory_map + ebx + 8]
mov [memory_map + ebx + 24],eax
mov eax,[memory_map + ebx + 12]
mov [memory_map + ebx + 28],eax
mov eax,[kernel_buffer]
mov [memory_map + ebx],eax
mov eax,[kernel_size]
shr eax,12
mov [memory_map + ebx + 8],eax
; Map the first MB at 0xFFFFFE0000000000 --> 0xFFFFFE0000100000
mov rdi,0xFFFFFF7FBFDFE000
mov rax,[rdi]
mov rdi,0xFFFFFF7FBFDFEFE0
mov [rdi],rax
mov rax,cr3
mov cr3,rax
; Use the new linear address of the GDT
mov rax,0xFFFFFE0000000000
add qword [gdt_data.gdt2],rax
lgdt [gdt_data.gdt]
; Execute the kernel's _start function
xor rdi,rdi
mov rsi,1
jmp rcx
error_64:
jmp far [.error_32_ind]
.error_32_ind: dq error_32
dd 8
[bits 32]
error_32:
; Switch to 16-bit mode
mov eax,cr0
and eax,0x7FFFFFFF
mov cr0,eax
jmp 0x18:.rmode
; Switch to real mode
[bits 16]
.rmode:
mov eax,cr0
and eax,0x7FFFFFFE
mov cr0,eax
jmp 0x0:.finish
; Go to error
.finish:
mov ax,0
mov ds,ax
mov es,ax
mov ss,ax
jmp error
check_a20:
; Set the carry flag if the A20 line is enabled
mov ax,0
mov es,ax
mov ax,0xFFFF
mov fs,ax
mov byte [es:0x600],0
mov byte [fs:0x610],0xFF
cmp byte [es:0x600],0xFF
je .enabled
stc
ret
.enabled:
clc
ret
error:
; Print an error message
lodsb
or al,al
jz .break
mov ah,0xE
int 0x10
jmp error
; Break indefinitely
.break:
cli
hlt
gdt_data:
.null_entry: dq 0
.code_entry: dd 0xFFFF ; 0x08
db 0
dw 0xCF9A
db 0
.data_entry: dd 0xFFFF ; 0x10
db 0
dw 0xCF92
db 0
.code_entry_16: dd 0xFFFF ; 0x18
db 0
dw 0x0F9A
db 0
.data_entry_16: dd 0xFFFF ; 0x20
db 0
dw 0x0F92
db 0
.user_code: dd 0xFFFF ; 0x2B
db 0
dw 0xCFFA
db 0
.user_data: dd 0xFFFF ; 0x33
db 0
dw 0xCFF2
db 0
.tss: dd 0x68 ; 0x38
db 0
dw 0xE9
db 0
dq 0
.code_entry64: dd 0xFFFF ; 0x48
db 0
dw 0xAF9A
db 0
.data_entry64: dd 0xFFFF ; 0x50
db 0
dw 0xAF92
db 0
.user_code64: dd 0xFFFF ; 0x5B
db 0
dw 0xAFFA
db 0
.user_data64: dd 0xFFFF ; 0x63
db 0
dw 0xAFF2
db 0
.user_code64c: dd 0xFFFF ; 0x6B
db 0
dw 0xAFFA
db 0
.gdt: dw (gdt_data.gdt - gdt_data - 1)
.gdt2: dq gdt_data
; @FilesystemSpecific
FilesystemSpecificCode
load_sectors:
; Load CX sectors from sector EAX to EDI
pushad
push edi
; Add the partition offset to EAX
mov bx,[partition_entry]
mov ebx,[bx + 8]
add eax,ebx
; Load 1 sector
mov [read_structure.lba],eax
mov ah,0x42
mov dl,[drive_number]
mov si,read_structure
cmp byte [use_emu_read],1
je .use_emu
int 0x13
jmp .done_read
.use_emu:
call load_sector_emu
.done_read:
; Check for error
mov si,error_cannot_read_disk
jc error
; Copy the data to its destination
pop edi
call move_sector_to_target
; Go to the next sector
popad
add edi,0x200
inc eax
loop load_sectors
ret
; Move data from the temporary load buffer to EDI
move_sector_to_target:
push ss
push ds
push es
; Switch to protected mode
cli
mov eax,cr0
or eax,0x80000001
mov cr0,eax
jmp 0x8:.pmode
; Set the data segment registers
[bits 32]
.pmode:
mov ax,0x10
mov ds,ax
mov es,ax
mov ss,ax
; Copy the data
mov ecx,0x200
mov esi,temporary_load_buffer
rep movsb
; Switch to 16-bit mode
mov eax,cr0
and eax,0x7FFFFFFF
mov cr0,eax
jmp 0x18:.rmode
; Switch to real mode
[bits 16]
.rmode:
mov eax,cr0
and eax,0x7FFFFFFE
mov cr0,eax
jmp 0x0:.finish
; Return to load_sectors
.finish:
pop es
pop ds
pop ss
sti
ret
load_sector_emu:
mov di,[read_structure.lba]
xor ax,ax
mov es,ax
mov bx,0x9000
; Calculate cylinder and head.
mov ax,di
xor dx,dx
div word [max_sectors]
xor dx,dx
div word [max_heads]
push dx ; remainder - head
mov ch,al ; quotient - cylinder
shl ah,6
mov cl,ah
; Calculate sector.
mov ax,di
xor dx,dx
div word [max_sectors]
inc dx
or cl,dl
; Load the sector.
pop dx
mov dh,dl
mov dl,[drive_number]
mov ax,0x0201
int 0x13
ret
read_structure:
; Data for the extended read calls
dw 0x10
dw 1
dd temporary_load_buffer
.lba: dq 0
drive_number: db 0
use_emu_read: db 0
partition_entry: dw 0
max_sectors: dw 0
max_heads: dw 0
page_table_allocation_location: dq 0 ; Relative to page_directory.
kernel_buffer: dq 0
kernel_size: dq 0
error_cannot_enable_a20_line: db "Error: Cannot enable the A20 line",0
error_could_not_get_memory_map: db "Error: Could not get the memory map from the BIOS",0
error_no_pci: db "Error: Could not find the PCI bus",0
error_no_cpuid: db "Error: CPU does not have the CPUID instruction",0
error_no_msr: db "Error: CPU does not have the RDMSR instruction",0
error_cannot_find_file: db "Error: A file necessary for booting could not found",0
error_cannot_read_disk: db "Error: The disk could not be read.",0
error_file_too_large: db "Error: The file was too large to be loaded (more than 256KB).",0
error_kernel_too_large: db "Error: The kernel was too large for the 3MB buffer.",0
error_bad_kernel: db "Error: Invalid or unsupported kernel ELF format.",0
error_no_memory: db "Error: Not enough memory to load kernel"
error_no_long_mode: db "Error: The kernel is compiled for a 64-bit processor but the current processor is 32-bit only.",0
error_could_not_set_video_mode: db "Error: Could not set video mode 1024x768x24.",0
reached_end: db "Reached end of stage 2 bootloader.",0

144
boot/x86/mbr-emu.s Normal file
View File

@ -0,0 +1,144 @@
[bits 16]
[org 0x600]
start:
; Setup segment registers and the stack
cli
mov ax,0
mov ds,ax
mov es,ax
mov fs,ax
mov gs,ax
mov ss,ax
mov sp,0x7C00
sti
; Clear the screen
mov ax,0
int 0x10
mov ax,3
int 0x10
; Relocate to 0x600
cld
mov si,0x7C00
mov di,0x600
mov cx,0x200
rep movsb
jmp 0x0:find_partition
find_partition:
; Save the drive number
mov byte [drive_number],dl
; Get drive parameters.
mov ah,0x08
xor di,di
int 0x13
mov si,error_cannot_read_disk
jc error
and cx,63
mov [max_sectors],cx
inc dh
shr dx,8
mov [max_heads],dx
mov si,error_bad_geometry
or cx,cx
jz error
or dx,dx
jz error
; Find the bootable flag (0x80)
mov bx,partition_entry_1
cmp byte [bx],0x80
je found_partition
mov bx,partition_entry_2
cmp byte [bx],0x80
je found_partition
mov bx,partition_entry_3
cmp byte [bx],0x80
je found_partition
mov bx,partition_entry_4
cmp byte [bx],0x80
je found_partition
; No bootable partition
mov si,error_no_bootable_partition
jmp error
found_partition:
; Load the first sector of the partition at 0x7C00
push bx
mov di,[bx + 8]
mov bx,0x7C00
call load_sector
; Jump to the partition's boot sector
mov dl,[drive_number]
pop si
mov dh,0x01
jmp 0x0:0x7C00
error:
; Print an error message
lodsb
or al,al
jz .break
mov ah,0xE
int 0x10
jmp error
; Break indefinitely
.break:
cli
hlt
; di - LBA.
; es:bx - buffer
load_sector:
; Calculate cylinder and head.
mov ax,di
xor dx,dx
div word [max_sectors]
xor dx,dx
div word [max_heads]
push dx ; remainder - head
mov ch,al ; quotient - cylinder
shl ah,6
mov cl,ah
; Calculate sector.
mov ax,di
xor dx,dx
div word [max_sectors]
inc dx
or cl,dl
; Load the sector.
pop dx
mov dh,dl
mov dl,[drive_number]
mov ax,0x0201
int 0x13
mov si,error_cannot_read_disk
jc error
ret
error_cannot_read_disk: db "Error: The disk could not be read. (MBR)",0
error_no_bootable_partition: db "Error: No bootable partition could be found on the disk.",0
error_bad_geometry: db 'Error: The BIOS reported invalid disk geometry.',0
drive_number: db 0
max_sectors: dw 0
max_heads: dw 0
times (0x1B4 - ($-$$)) nop
disk_identifier: times 10 db 0
partition_entry_1: times 16 db 0
partition_entry_2: times 16 db 0
partition_entry_3: times 16 db 0
partition_entry_4: times 16 db 0
dw 0xAA55

106
boot/x86/mbr.s Normal file
View File

@ -0,0 +1,106 @@
[bits 16]
[org 0x600]
start:
; Setup segment registers and the stack
cli
mov ax,0
mov ds,ax
mov es,ax
mov fs,ax
mov gs,ax
mov ss,ax
mov sp,0x7C00
sti
; Clear the screen
mov ax,0
int 0x10
mov ax,3
int 0x10
; Relocate to 0x600
cld
mov si,0x7C00
mov di,0x600
mov cx,0x200
rep movsb
jmp 0x0:find_partition
drive_number: db 0
find_partition:
; Save the drive number
mov byte [drive_number],dl
; Find the bootable flag (0x80)
mov bx,partition_entry_1
cmp byte [bx], 0x80
je found_partition
mov bx,partition_entry_2
cmp byte [bx], 0x80
je found_partition
mov bx,partition_entry_3
cmp byte [bx], 0x80
je found_partition
mov bx,partition_entry_4
cmp byte [bx], 0x80
je found_partition
; No bootable partition
mov si,error_no_bootable_partition
jmp error
found_partition:
; Load the first sector of the partition at 0x7C00
push bx
mov eax,[bx + 8]
mov [read_structure.lba],eax
mov ah,0x42
mov dl,[drive_number]
push dx
mov si,read_structure
int 0x13
; Check for an error
mov si,error_cannot_read_disk
jc error
; Jump to the partition's boot sector
pop dx
pop si
xor dh,dh
jmp 0x0:0x7C00
error:
; Print an error message
lodsb
or al,al
jz .break
mov ah,0xE
int 0x10
jmp error
; Break indefinitely
.break:
cli
hlt
read_structure: ; Data for the extended read calls
dw 0x10
dw 1
dd 0x7C00
.lba: dq 0
error_cannot_read_disk: db "Error: The disk could not be read. (MBR)",0
error_no_bootable_partition: db "Error: No bootable partition could be found on the disk.",0
times (0x1B4 - ($-$$)) nop
disk_identifier: times 10 db 0
partition_entry_1: times 16 db 0
partition_entry_2: times 16 db 0
partition_entry_3: times 16 db 0
partition_entry_4: times 16 db 0
dw 0xAA55

404
boot/x86/uefi.c Normal file
View File

@ -0,0 +1,404 @@
#include <efi.h>
#include <efilib.h>
#define ENTRIES_PER_PAGE_TABLE (512)
#define ENTRIES_PER_PAGE_TABLE_BITS (9)
#define K_PAGE_SIZE (4096)
#define K_PAGE_BITS (12)
typedef struct __attribute__((packed)) GDTData {
uint16_t length;
uint64_t address;
} GDTData;
typedef struct MemoryRegion {
uintptr_t base, pages;
} MemoryRegion;
#define MAX_MEMORY_REGIONS (1024)
MemoryRegion memoryRegions[1024];
#define KERNEL_BUFFER_SIZE (1048576)
#define kernelBuffer ((char *) 0x200000)
#define IID_BUFFER_SIZE (64)
char iidBuffer[IID_BUFFER_SIZE];
#define MEMORY_MAP_BUFFER_SIZE (16384)
char memoryMapBuffer[MEMORY_MAP_BUFFER_SIZE];
#define VESA_VM_INFO_ONLY
#define ARCH_64
#include "../../kernel/graphics.cpp"
#include "../../kernel/elf.cpp"
void ZeroMemory(void *pointer, uint64_t size) {
char *d = (char *) pointer;
for (uintptr_t i = 0; i < size; i++) {
d[i] = 0;
}
}
EFI_STATUS EFIAPI efi_main(EFI_HANDLE imageHandle, EFI_SYSTEM_TABLE *systemTable) {
UINTN mapKey;
uint32_t *framebuffer, horizontalResolution, verticalResolution, pixelsPerScanline;
InitializeLib(imageHandle, systemTable);
ElfHeader *header;
Print(L"Loading OS...\n");
// Make sure 0x100000 -> 0x300000 is identity mapped.
{
EFI_PHYSICAL_ADDRESS address = 0x100000;
if (EFI_SUCCESS != uefi_call_wrapper(ST->BootServices->AllocatePages, 4, AllocateAddress, EfiLoaderData, 0x200, &address)) {
Print(L"Error: Could not map 0x100000 -> 0x180000.\n");
while (1);
}
}
// Find the RSDP.
{
for (uintptr_t i = 0; i < systemTable->NumberOfTableEntries; i++) {
EFI_CONFIGURATION_TABLE *entry = systemTable->ConfigurationTable + i;
if (entry->VendorGuid.Data1 == 0x8868E871 && entry->VendorGuid.Data2 == 0xE4F1 && entry->VendorGuid.Data3 == 0x11D3
&& entry->VendorGuid.Data4[0] == 0xBC && entry->VendorGuid.Data4[1] == 0x22 && entry->VendorGuid.Data4[2] == 0x00
&& entry->VendorGuid.Data4[3] == 0x80 && entry->VendorGuid.Data4[4] == 0xC7 && entry->VendorGuid.Data4[5] == 0x3C
&& entry->VendorGuid.Data4[6] == 0x88 && entry->VendorGuid.Data4[7] == 0x81) {
*((uint64_t *) 0x107FE8) = (uint64_t) entry->VendorTable;
Print(L"The RSDP can be found at 0x%x.\n", entry->VendorTable);
}
}
}
// Read the kernel, IID and loader files.
{
EFI_GUID loadedImageProtocolGUID = LOADED_IMAGE_PROTOCOL;
EFI_GUID simpleFilesystemProtocolGUID = SIMPLE_FILE_SYSTEM_PROTOCOL;
EFI_LOADED_IMAGE_PROTOCOL *loadedImageProtocol;
EFI_SIMPLE_FILE_SYSTEM_PROTOCOL *simpleFilesystemProtocol;
EFI_FILE *filesystemRoot, *kernelFile, *iidFile, *loaderFile;
UINTN size;
if (EFI_SUCCESS != uefi_call_wrapper(ST->BootServices->OpenProtocol, 6, imageHandle, &loadedImageProtocolGUID, &loadedImageProtocol, imageHandle, NULL, EFI_OPEN_PROTOCOL_GET_PROTOCOL)) {
Print(L"Error: Could not open protocol 1.\n");
while (1);
}
EFI_HANDLE deviceHandle = loadedImageProtocol->DeviceHandle;
if (EFI_SUCCESS != uefi_call_wrapper(ST->BootServices->OpenProtocol, 6, deviceHandle, &simpleFilesystemProtocolGUID, &simpleFilesystemProtocol, imageHandle, NULL, EFI_OPEN_PROTOCOL_GET_PROTOCOL)) {
Print(L"Error: Could not open procotol 2.\n");
while (1);
}
if (EFI_SUCCESS != uefi_call_wrapper(simpleFilesystemProtocol->OpenVolume, 2, simpleFilesystemProtocol, &filesystemRoot)) {
Print(L"Error: Could not open ESP volume.\n");
while (1);
}
if (EFI_SUCCESS != uefi_call_wrapper(filesystemRoot->Open, 5, filesystemRoot, &kernelFile, L"EssenceKernel.esx", EFI_FILE_MODE_READ, 0)) {
Print(L"Error: Could not open EssenceKernel.esx.\n");
while (1);
}
size = KERNEL_BUFFER_SIZE;
if (EFI_SUCCESS != uefi_call_wrapper(kernelFile->Read, 3, kernelFile, &size, kernelBuffer)) {
Print(L"Error: Could not load EssenceKernel.esx.\n");
while (1);
}
Print(L"Kernel size: %d bytes\n", size);
if (size == KERNEL_BUFFER_SIZE) {
Print(L"Kernel too large to fit into buffer.\n");
while (1);
}
if (EFI_SUCCESS != uefi_call_wrapper(filesystemRoot->Open, 5, filesystemRoot, &iidFile, L"EssenceIID.dat", EFI_FILE_MODE_READ, 0)) {
Print(L"Error: Could not open EssenceIID.dat.\n");
while (1);
}
size = IID_BUFFER_SIZE;
if (EFI_SUCCESS != uefi_call_wrapper(iidFile->Read, 3, iidFile, &size, iidBuffer)) {
Print(L"Error: Could not load EssenceIID.dat.\n");
while (1);
}
if (EFI_SUCCESS != uefi_call_wrapper(filesystemRoot->Open, 5, filesystemRoot, &loaderFile, L"EssenceLoader.bin", EFI_FILE_MODE_READ, 0)) {
Print(L"Error: Could not open EssenceLoader.bin.\n");
while (1);
}
size = 0x80000;
if (EFI_SUCCESS != uefi_call_wrapper(loaderFile->Read, 3, loaderFile, &size, (char *) 0x180000)) {
Print(L"Error: Could not load EssenceLoader.bin.\n");
while (1);
}
}
#if 0
// Print the memory map.
{
UINTN descriptorSize, descriptorVersion, size = MEMORY_MAP_BUFFER_SIZE;
if (EFI_SUCCESS != uefi_call_wrapper(ST->BootServices->GetMemoryMap, 5, &size, (EFI_MEMORY_DESCRIPTOR *) memoryMapBuffer, &mapKey, &descriptorSize, &descriptorVersion)) {
Print(L"Error: Could not get memory map.\n");
while (1);
}
WCHAR *memoryTypes[] = {
L"EfiReservedMemoryType",
L"EfiLoaderCode",
L"EfiLoaderData",
L"EfiBootServicesCode",
L"EfiBootServicesData",
L"EfiRuntimeServicesCode",
L"EfiRuntimeServicesData",
L"EfiConventionalMemory",
L"EfiUnusableMemory",
L"EfiACPIReclaimMemory",
L"EfiACPIMemoryNVS",
L"EfiMemoryMappedIO",
L"EfiMemoryMappedIOPortSpace",
L"EfiPalCode",
L"EfiMaxMemoryType",
};
for (uintptr_t i = 0; i < size / descriptorSize; i++) {
EFI_MEMORY_DESCRIPTOR *descriptor = (EFI_MEMORY_DESCRIPTOR *) (memoryMapBuffer + i * descriptorSize);
Print(L"memory %s: %llx -> %llx\n", memoryTypes[descriptor->Type], descriptor->PhysicalStart, descriptor->PhysicalStart + descriptor->NumberOfPages * 0x1000 - 1);
}
}
#endif
// Get the graphics mode information.
{
EFI_GRAPHICS_OUTPUT_PROTOCOL *graphicsOutputProtocol;
EFI_GUID graphicsOutputProtocolGUID = EFI_GRAPHICS_OUTPUT_PROTOCOL_GUID;
if (EFI_SUCCESS != uefi_call_wrapper(ST->BootServices->LocateProtocol, 3, &graphicsOutputProtocolGUID, NULL, &graphicsOutputProtocol)) {
Print(L"Error: Could not open protocol 3.\n");
while (1);
}
int32_t desiredMode = -1;
Print(L"Select a graphics mode:\n");
for (uint32_t i = 0; i < graphicsOutputProtocol->Mode->MaxMode; i++) {
EFI_GRAPHICS_OUTPUT_MODE_INFORMATION *modeInformation;
UINTN modeInformationSize;
uefi_call_wrapper(graphicsOutputProtocol->QueryMode, 4, graphicsOutputProtocol, i, &modeInformationSize, &modeInformation);
if (modeInformation->HorizontalResolution >= 800 && modeInformation->VerticalResolution >= 600) {
Print(L" %d: %d by %d\n", i + 1, modeInformation->HorizontalResolution, modeInformation->VerticalResolution);
}
}
Print(L"> ");
while (1) {
UINTN index;
uefi_call_wrapper(ST->BootServices->WaitForEvent, 3, 1, &systemTable->ConIn->WaitForKey, &index);
EFI_INPUT_KEY key;
uefi_call_wrapper(systemTable->ConIn->ReadKeyStroke, 2, systemTable->ConIn, &key);
if (key.UnicodeChar >= '0' && key.UnicodeChar <= '9') {
if (desiredMode == -1) desiredMode = 0;
desiredMode = (desiredMode * 10) + key.UnicodeChar - '0';
Print(L"%c", key.UnicodeChar);
} else if (key.UnicodeChar == 13) {
break;
}
}
Print(L"\n");
if (desiredMode != -1) {
Print(L"Setting mode %d...\n", desiredMode);
desiredMode--;
EFI_GRAPHICS_OUTPUT_MODE_INFORMATION *modeInformation;
UINTN modeInformationSize;
uefi_call_wrapper(graphicsOutputProtocol->QueryMode, 4, graphicsOutputProtocol, desiredMode, &modeInformationSize, &modeInformation);
horizontalResolution = modeInformation->HorizontalResolution;
verticalResolution = modeInformation->VerticalResolution;
pixelsPerScanline = modeInformation->PixelsPerScanLine;
uefi_call_wrapper(graphicsOutputProtocol->SetMode, 2, graphicsOutputProtocol, desiredMode);
} else {
horizontalResolution = graphicsOutputProtocol->Mode->Info->HorizontalResolution;
verticalResolution = graphicsOutputProtocol->Mode->Info->VerticalResolution;
pixelsPerScanline = graphicsOutputProtocol->Mode->Info->PixelsPerScanLine;
}
framebuffer = (uint32_t *) graphicsOutputProtocol->Mode->FrameBufferBase;
}
// Get the memory map.
{
UINTN descriptorSize, descriptorVersion, size = MEMORY_MAP_BUFFER_SIZE;
if (EFI_SUCCESS != uefi_call_wrapper(ST->BootServices->GetMemoryMap, 5, &size, (EFI_MEMORY_DESCRIPTOR *) memoryMapBuffer, &mapKey, &descriptorSize, &descriptorVersion)) {
Print(L"Error: Could not get memory map.\n");
while (1);
}
uintptr_t memoryRegionCount = 0;
for (uintptr_t i = 0; i < size / descriptorSize && memoryRegionCount != MAX_MEMORY_REGIONS - 1; i++) {
EFI_MEMORY_DESCRIPTOR *descriptor = (EFI_MEMORY_DESCRIPTOR *) (memoryMapBuffer + i * descriptorSize);
if (descriptor->Type == EfiConventionalMemory && descriptor->PhysicalStart >= 0x300000) {
memoryRegions[memoryRegionCount].base = descriptor->PhysicalStart;
memoryRegions[memoryRegionCount].pages = descriptor->NumberOfPages;
memoryRegionCount++;
}
}
memoryRegions[memoryRegionCount].base = 0;
}
// Exit boot services.
{
if (EFI_SUCCESS != uefi_call_wrapper(ST->BootServices->ExitBootServices, 2, imageHandle, mapKey)) {
Print(L"Error: Could not exit boot services.\n");
while (1);
}
}
// Identity map the first 3MB for the loader.
{
uint64_t *paging = (uint64_t *) 0x140000;
ZeroMemory(paging, 0x5000);
paging[0x1FE] = 0x140003;
paging[0x000] = 0x141003;
paging[0x200] = 0x142003;
paging[0x400] = 0x143003;
paging[0x401] = 0x144003;
for (uintptr_t i = 0; i < 0x400; i++) {
paging[0x600 + i] = (i * 0x1000) | 3;
}
}
// Copy the installation ID across.
{
uint8_t *destination = (uint8_t *) (0x107FF0);
for (uintptr_t i = 0; i < 16; i++) {
char c1 = iidBuffer[i * 3 + 0];
char c2 = iidBuffer[i * 3 + 1];
if (c1 >= '0' && c1 <= '9') c1 -= '0'; else c1 -= 'A' - 10;
if (c2 >= '0' && c2 <= '9') c2 -= '0'; else c2 -= 'A' - 10;
destination[i] = (c2) | ((c1) << 4);
}
}
// Copy the graphics information across.
{
struct VESAVideoModeInformation *destination = (struct VESAVideoModeInformation *) (0x107000);
destination->widthPixels = horizontalResolution;
destination->heightPixels = verticalResolution;
destination->bufferPhysical = (uintptr_t) framebuffer; // TODO 64-bit framebuffers.
destination->bytesPerScanlineLinear = pixelsPerScanline * 4;
destination->bitsPerPixel = 32;
}
// Allocate and map memory for the kernel.
{
uint64_t nextPageTable = 0x1C0000;
header = (ElfHeader *) kernelBuffer;
ElfProgramHeader *programHeaders = (ElfProgramHeader *) (kernelBuffer + header->programHeaderTable);
uintptr_t programHeaderEntrySize = header->programHeaderEntrySize;
for (uintptr_t i = 0; i < header->programHeaderEntries; i++) {
ElfProgramHeader *header = (ElfProgramHeader *) ((uint8_t *) programHeaders + programHeaderEntrySize * i);
if (header->type != 1) continue;
uintptr_t pagesToAllocate = header->segmentSize >> 12;
if (header->segmentSize & 0xFFF) pagesToAllocate++;
uintptr_t physicalAddress = 0;
for (uintptr_t j = 0; j < MAX_MEMORY_REGIONS; j++) {
MemoryRegion *region = memoryRegions + j;
if (!region->base) break;
if (region->pages < pagesToAllocate) continue;
physicalAddress = region->base;
region->pages -= pagesToAllocate;
region->base += pagesToAllocate << 12;
break;
}
if (!physicalAddress) {
// TODO Error handling.
*((uint32_t *) framebuffer + 3) = 0xFFFF00FF;
while (1);
}
for (uintptr_t j = 0; j < pagesToAllocate; j++, physicalAddress += 0x1000) {
uintptr_t virtualAddress = header->virtualAddress + j * K_PAGE_SIZE;
physicalAddress &= 0xFFFFFFFFFFFFF000;
virtualAddress &= 0x0000FFFFFFFFF000;
uintptr_t indexL4 = (virtualAddress >> (K_PAGE_BITS + ENTRIES_PER_PAGE_TABLE_BITS * 3)) & (ENTRIES_PER_PAGE_TABLE - 1);
uintptr_t indexL3 = (virtualAddress >> (K_PAGE_BITS + ENTRIES_PER_PAGE_TABLE_BITS * 2)) & (ENTRIES_PER_PAGE_TABLE - 1);
uintptr_t indexL2 = (virtualAddress >> (K_PAGE_BITS + ENTRIES_PER_PAGE_TABLE_BITS * 1)) & (ENTRIES_PER_PAGE_TABLE - 1);
uintptr_t indexL1 = (virtualAddress >> (K_PAGE_BITS + ENTRIES_PER_PAGE_TABLE_BITS * 0)) & (ENTRIES_PER_PAGE_TABLE - 1);
uint64_t *tableL4 = (uint64_t *) 0x140000;
if (!(tableL4[indexL4] & 1)) {
tableL4[indexL4] = nextPageTable | 7;
ZeroMemory((void *) nextPageTable, K_PAGE_SIZE);
nextPageTable += K_PAGE_SIZE;
}
uint64_t *tableL3 = (uint64_t *) (tableL4[indexL4] & ~(K_PAGE_SIZE - 1));
if (!(tableL3[indexL3] & 1)) {
tableL3[indexL3] = nextPageTable | 7;
ZeroMemory((void *) nextPageTable, K_PAGE_SIZE);
nextPageTable += K_PAGE_SIZE;
}
uint64_t *tableL2 = (uint64_t *) (tableL3[indexL3] & ~(K_PAGE_SIZE - 1));
if (!(tableL2[indexL2] & 1)) {
tableL2[indexL2] = nextPageTable | 7;
ZeroMemory((void *) nextPageTable, K_PAGE_SIZE);
nextPageTable += K_PAGE_SIZE;
}
uint64_t *tableL1 = (uint64_t *) (tableL2[indexL2] & ~(K_PAGE_SIZE - 1));
uintptr_t value = physicalAddress | 3;
tableL1[indexL1] = value;
}
}
}
// Copy the memory regions information across.
{
MemoryRegion *destination = (MemoryRegion *) 0x160000;
for (uintptr_t i = 0; i < MAX_MEMORY_REGIONS; i++) {
destination[i] = memoryRegions[i];
}
}
// Start the loader.
{
((void (*)()) 0x180000)();
}
while (1);
return EFI_SUCCESS;
}

156
boot/x86/uefi_loader.s Normal file
View File

@ -0,0 +1,156 @@
[bits 64]
[org 0x180000]
[section .text]
; 0x107000 Graphics info
; 0x107FE8 RSDP address
; 0x107FF0 Installation ID
; 0x140000-0x150000 Identity paging tables
; 0x160000-0x170000 Memory regions
; 0x180000-0x1C0000 Loader (this)
; 0x1C0000-0x1E0000 Kernel paging tables
; 0x1F0000-0x200000 Stack
; 0x200000-0x300000 Kernel
%define memory_map 0x160000
%define kernel_buffer 0x200000
start:
; Setup the environment.
lgdt [gdt_data.gdt]
mov rax,0x140000
mov cr3,rax
mov rax,0x200000
mov rsp,rax
mov rax,0x50
mov ds,rax
mov es,rax
mov ss,rax
; Find the program headers
; RAX = ELF header, RBX = program headers
mov rax,kernel_buffer
mov rbx,[rax + 32]
add rbx,rax
; ECX = entries, EDX = size of entry
movzx rcx,word [rax + 56]
movzx rdx,word [rax + 54]
; Loop through each program header
.loop_program_headers:
push rax
push rcx
; Only deal with load segments
mov eax,[rbx]
cmp eax,1
jne .next_entry
; Clear the memory
mov rcx,[rbx + 40]
xor rax,rax
mov rdi,[rbx + 16]
rep stosb
; Copy the memory
mov rcx,[rbx + 32]
mov rsi,[rbx + 8]
add rsi,kernel_buffer
mov rdi,[rbx + 16]
rep movsb
; Go to the next entry
.next_entry:
pop rcx
pop rax
add rbx,rdx
dec rcx
or rcx,rcx
jnz .loop_program_headers
jmp run_kernel64
run_kernel64:
; Get the start address of the kernel
mov rbx,kernel_buffer
mov rcx,[rbx + 24]
; Map the first MB at 0xFFFFFE0000000000 --> 0xFFFFFE0000100000
mov rax,[0xFFFFFF7FBFDFE000]
mov [0xFFFFFF7FBFDFEFE0],rax
mov rax,cr3
mov cr3,rax
; Use the new linear address of the GDT
mov rax,0xFFFFFE0000000000
add qword [gdt_data.gdt2],rax
lgdt [gdt_data.gdt]
call set_cs
; Execute the kernel's _start function
mov rdi,0x100000
mov rsi,2
jmp rcx
set_cs:
pop rax
push 0x48
push rax
db 0x48, 0xCB
gdt_data:
.null_entry: dq 0
.code_entry: dd 0xFFFF ; 0x08
db 0
dw 0xCF9A
db 0
.data_entry: dd 0xFFFF ; 0x10
db 0
dw 0xCF92
db 0
.code_entry_16: dd 0xFFFF ; 0x18
db 0
dw 0x0F9A
db 0
.data_entry_16: dd 0xFFFF ; 0x20
db 0
dw 0x0F92
db 0
.user_code: dd 0xFFFF ; 0x2B
db 0
dw 0xCFFA
db 0
.user_data: dd 0xFFFF ; 0x33
db 0
dw 0xCFF2
db 0
.tss: dd 0x68 ; 0x38
db 0
dw 0xE9
db 0
dq 0
.code_entry64: dd 0xFFFF ; 0x48
db 0
dw 0xAF9A
db 0
.data_entry64: dd 0xFFFF ; 0x50
db 0
dw 0xAF92
db 0
.user_code64: dd 0xFFFF ; 0x5B
db 0
dw 0xAFFA
db 0
.user_data64: dd 0xFFFF ; 0x63
db 0
dw 0xAFF2
db 0
.user_code64c: dd 0xFFFF ; 0x6B
db 0
dw 0xAFFA
db 0
.gdt: dw (gdt_data.gdt - gdt_data - 1)
.gdt2: dq gdt_data

361
boot/x86/vbe.s Normal file
View File

@ -0,0 +1,361 @@
vbe_init:
mov ax,vesa_info >> 4
mov es,ax
xor di,di
%ifndef BOOT_USE_VBE
jmp vbe_bad
%endif
; Get EDID information.
mov ax,0x4F15
mov bl,1
xor cx,cx
xor dx,dx
xor di,di
int 0x10
cmp ax,0x4F
jne .no_edid
cmp byte [es:1],0xFF
jne .no_edid
mov al,[es:0x38]
mov ah,[es:0x3A]
shr ah,4
mov bl,[es:0x3B]
mov bh,[es:0x3D]
shr bh,4
or ax,ax
jz .no_edid
or bx,bx
jz .no_edid
mov [vbe_best_width],ax
mov [vbe_best_height],bx
mov byte [vbe_has_edid],1
jmp .no_flat_panel
.no_edid:
; Get flat panel information.
mov ax,0x4F11
mov bx,1
xor di,di
int 0x10
cmp ax,0x4F
jne .no_flat_panel
mov ax,[es:0x00]
mov bx,[es:0x02]
or ax,ax
jz .no_flat_panel
or bx,bx
jz .no_flat_panel
cmp ax,4096
ja .no_flat_panel
cmp bx,4096
ja .no_flat_panel
mov [vbe_best_width],ax
mov [vbe_best_height],bx
.no_flat_panel:
; Get SVGA information.
xor di,di
mov ax,0x4F00
int 0x10
cmp ax,0x4F
jne vbe_bad
; Load the list of available modes.
add di,0x200
mov eax,[es:14]
cmp eax,0
je .find_done
mov ax,[es:16]
mov fs,ax
mov si,[es:14]
xor cx,cx
.find_loop:
mov ax,[fs:si]
cmp ax,0xFFFF
je .find_done
mov [es:di],ax
add di,2
add si,2
jmp .find_loop
.find_done:
; Add standard modes (if necessary).
mov word [es:di],0xFFFF
cmp di,0x200
jne .added_modes
mov word [es:di + 0],257
mov word [es:di + 2],259
mov word [es:di + 4],261
mov word [es:di + 6],263
mov word [es:di + 8],273
mov word [es:di + 10],276
mov word [es:di + 12],279
mov word [es:di + 14],282
mov word [es:di + 16],274
mov word [es:di + 18],277
mov word [es:di + 20],280
mov word [es:di + 22],283
mov word [es:di + 24],0xFFFF
.added_modes:
; Check which of these modes can be used.
mov si,0x200
mov di,0x200
.check_loop:
mov cx,[es:si]
mov [es:di],cx
cmp cx,0xFFFF
je .check_done
push di
push si
mov ax,0x4F01
xor di,di
or cx,(1 << 14)
int 0x10
pop si
pop di
add si,2
cmp ax,0x4F ; Interrupt failed.
jne .check_loop
cmp byte [es:0x19],24 ; We only support 24-bit and 32-bit modes currently.
je .valid_bpp
cmp byte [es:0x19],32
je .valid_bpp
jne .check_loop
.valid_bpp:
cmp word [es:0x14],480 ; We support a minimum vertical resolution of 480 pixels.
jl .check_loop
mov ax,[vbe_best_width]
cmp [es:0x12],ax
jne .not_best_mode
mov ax,[vbe_best_height]
cmp [es:0x14],ax
jne .not_best_mode
mov ax,[es:di]
mov [vbe_best_mode],ax
.not_best_mode:
add di,2
jmp .check_loop
.check_done:
; If we found a best mode, use that.
mov bx,[vbe_best_mode]
or bx,bx
jnz .set_graphics_mode
.no_best_mode:
; Print a list of the available modes.
mov si,vbe_s_select_video_mode
call vbe_print_string
mov bx,0x200
mov cx,1
.print_loop:
mov dx,[es:bx]
cmp dx,0xFFFF
je .print_done
cmp cx,21 ; Maximum of 20 options. TODO Scrolling!
je .print_done
xor di,di
push cx
mov ax,0x4F01
mov cx,dx
or cx,(1 << 14)
int 0x10
pop cx
mov si,vbe_s_left_bracket
call vbe_print_string
mov ax,cx
call vbe_print_decimal
mov si,vbe_s_right_bracket
call vbe_print_string
mov ax,[es:0x12]
call vbe_print_decimal
mov si,vbe_s_by
call vbe_print_string
mov ax,[es:0x14]
call vbe_print_decimal
mov si,vbe_s_space
call vbe_print_string
xor ah,ah
mov al,[es:0x19]
call vbe_print_decimal
mov si,vbe_s_bpp
call vbe_print_string
call vbe_print_newline
inc cx
add bx,2
jmp .print_loop
.print_done:
; Let the user select a mode.
mov dx,cx
dec dx
xor cx,cx
.select_loop:
cmp cx,dx
jb .c1
mov cx,0
.c1:
call vbe_set_highlighted_line
xor ax,ax
int 0x16
shr ax,8
cmp ax,72
jne .k11
dec cx
.k11:
cmp ax,80
jne .k12
inc cx
.k12:
cmp ax,28
jne .select_loop
; Set the graphics mode.
mov di,cx
shl di,1
add di,0x200
mov bx,[es:di]
.set_graphics_mode:
or bx,(1 << 14)
mov cx,bx
mov ax,0x4F02
int 0x10
cmp ax,0x4F
jne vbe_failed
; Save information about the mode for the kernel.
mov ax,0x4F01
xor di,di
int 0x10
mov byte [es:0],1 ; valid
mov al,[es:0x19]
mov [es:1],al ; bpp
mov ax,[es:0x12]
mov [es:2],ax ; width
mov ax,[es:0x14]
mov [es:4],ax ; height
mov ax,[es:0x10]
mov [es:6],ax ; stride
mov eax,[es:40]
mov [es:8],eax ; buffer
xor eax,eax
mov [es:12],eax
mov ax,0x4F15
mov bl,1
xor cx,cx
xor dx,dx
mov di,0x10
int 0x10
mov al,[vbe_has_edid]
shl al,1
or [es:0],al
ret
vbe_bad:
mov byte [es:di],0
ret
vbe_failed:
mov si,vbe_s_failed
call vbe_print_string
jmp vbe_init.select_loop
vbe_print_newline:
pusha
mov ah,0xE
mov al,13
int 0x10
mov ah,0xE
mov al,10
int 0x10
popa
ret
vbe_print_space:
pusha
mov ah,0xE
mov al,' '
int 0x10
popa
ret
vbe_print_string: ; Input - SI.
pusha
.loop:
lodsb
or al,al
jz .done
mov ah,0xE
int 0x10
jmp .loop
.done:
popa
ret
vbe_print_decimal: ; Input - AX.
pusha
mov bx,.buffer
mov cx,10
.next:
xor dx,dx
div cx
add dx,'0'
mov [bx],dl
inc bx
cmp ax,0
jne .next
.loop:
dec bx
mov al,[bx]
mov ah,0xE
int 0x10
cmp bx,.buffer
jne .loop
popa
ret
.buffer: db 0, 0, 0, 0, 0
vbe_set_highlighted_line: ; Input - CX
pusha
mov ax,0xB800
mov fs,ax
mov di,1
mov dx,(80 * 25)
.clear_loop:
mov byte [fs:di],0x07
add di,2
dec dx
cmp dx,0
jne .clear_loop
mov ax,cx
add ax,2
mov cx,160
mul cx
mov dx,80
mov di,ax
inc di
.highlight_loop:
mov byte [fs:di],0x70
add di,2
dec dx
cmp dx,0
jne .highlight_loop
popa
ret
vbe_s_select_video_mode: db 'Select a video mode: [use up/down then press enter]',13,10,0
vbe_s_left_bracket: db '(',0
vbe_s_right_bracket: db ') ',0
vbe_s_by: db 'x',0
vbe_s_space: db ' ',0
vbe_s_bpp: db 'bpp',0
vbe_s_failed: db 'This graphics mode could not be selected. Please try a different one.',13,10,0
vbe_best_width: dw 0
vbe_best_height: dw 0
vbe_best_mode: dw 0
vbe_has_edid: db 0

1591
desktop/api.cpp Normal file

File diff suppressed because it is too large Load Diff

99
desktop/api.s Normal file
View File

@ -0,0 +1,99 @@
[section .text]
[global _APISyscall]
_APISyscall:
push rbp
push rbx
push r15
push r14
push r13
push r12
push r11
push rcx
mov r12,rsp
syscall
mov rsp,r12
pop rcx
pop r11
pop r12
pop r13
pop r14
pop r15
pop rbx
pop rbp
ret
[global _EsCRTsetjmp]
_EsCRTsetjmp:
mov [rdi + 0x00],rsp
mov [rdi + 0x08],rbp
mov [rdi + 0x10],rbx
mov [rdi + 0x18],r12
mov [rdi + 0x20],r13
mov [rdi + 0x28],r14
mov [rdi + 0x30],r15
mov rax,[rsp]
mov [rdi + 0x38],rax
xor rax,rax
ret
[global _EsCRTlongjmp]
_EsCRTlongjmp:
mov rsp,[rdi + 0x00]
mov rbp,[rdi + 0x08]
mov rbx,[rdi + 0x10]
mov r12,[rdi + 0x18]
mov r13,[rdi + 0x20]
mov r14,[rdi + 0x28]
mov r15,[rdi + 0x30]
mov rax,[rdi + 0x38]
mov [rsp],rax
mov rax,rsi
cmp rax,0
jne .return
mov rax,1
.return:
ret
[global EsTimeStamp]
EsTimeStamp:
rdtsc
shl rdx,32
or rax,rdx
ret
[global EsCRTsqrt]
EsCRTsqrt:
sqrtsd xmm0,xmm0
ret
[global EsCRTsqrtf]
EsCRTsqrtf:
sqrtss xmm0,xmm0
ret
[global ProcessorCheckStackAlignment]
ProcessorCheckStackAlignment:
mov rax,rsp
and rax,15
cmp rax,8
jne $
ret
[global ProcessorTLSRead]
ProcessorTLSRead:
mov rax,[fs:rdi]
ret
[global ProcessorTLSWrite]
ProcessorTLSWrite:
mov [fs:rdi],rsi
ret
[global __cyg_profile_func_enter]
__cyg_profile_func_enter:
ret
[global __cyg_profile_func_exit]
__cyg_profile_func_exit:
ret

12
desktop/crt1.c Normal file
View File

@ -0,0 +1,12 @@
#include <essence.h>
int main(int argc, char **argv, char **envp);
int __libc_start_main(int (*main)(int, char **, char **), int argc, char **argv);
void _start() {
int argc;
char **argv;
EsPOSIXInitialise(&argc, &argv);
__libc_start_main(main, argc, argv);
}

5
desktop/crtglue.c Normal file
View File

@ -0,0 +1,5 @@
#include <essence.h>
long OSMakeLinuxSystemCall(long n, long a1, long a2, long a3, long a4, long a5, long a6) {
return EsPOSIXSystemCall(n, a1, a2, a3, a4, a5, a6);
}

11
desktop/crti.s Normal file
View File

@ -0,0 +1,11 @@
[section .init]
[global _init]
_init:
push rbp
mov rbp, rsp
[section .fini]
[global _fini]
_fini:
push rbp
mov rbp, rsp

7
desktop/crtn.s Normal file
View File

@ -0,0 +1,7 @@
[section .init]
pop rbp
ret
[section .fini]
pop rbp
ret

1933
desktop/desktop.cpp Normal file

File diff suppressed because it is too large Load Diff

458
desktop/docs.md Normal file
View File

@ -0,0 +1,458 @@
# Random number generator
## Definitions
```c
uint8_t EsRandomU8();
uint64_t EsRandomU64();
void EsRandomAddEntropy(uint64_t x);
void EsRandomSeed(uint64_t x);
```
## Description
Used to generate pseudo-random numbers. **Note**: the algorithm used is not suitable for cryptographic or statistical applications. These functions are thread-safe.
`EsRandomU8` generates a single byte of random data, and `EsRandomU64` generates a random `uint64_t`.
`EsRandomSeed` begins a new sequence of random numbers. For a given seed, subsequent calls to `EsRandomU64` will form the same sequence every time.
`EsRandomAddEntropy` is used to move to a different point in the sequence of numbers, where there is no obvious link between the input value and the new sequence position.
## Example
```c
EsPrint("A random number between 1 and 100 is:\n", 1 + (EsRandomU64() % 100));
```
# Performance timers
## Definitions
```c
void EsPerformanceTimerPush();
double EsPerformanceTimerPop();
```
## Description
Used to accurately time sections of code.
`EsPerformanceTimerPush` pushes the current time onto a stack. `EsPerformanceTimerPop` removes the top item, and returns the elapsed time since that item was added.
The stack must not exceed more than 100 items.
## Example
```c
EsPerformanceTimerPush();
EsPerformanceTimerPush();
PerformStep1();
double timeStep1 = EsPerformanceTimerPop();
EsPerformanceTimerPush();
PerformStep2();
double timeStep2 = EsPerformanceTimerPop();
double timeTotal = EsPerformanceTimerPop();
EsPrint("Total time: %F seconds.\n", timeTotal);
EsPrint("\tStep 1 took %F seconds.\n", timeStep1);
EsPrint("\tStep 2 took %F seconds.\n", timeStep2);
```
# Threads
## Definitions
```c
struct EsThreadInformation {
EsHandle handle;
uint64_t tid;
}
#define ES_CURRENT_THREAD ((EsHandle) (0x10))
typedef void (*EsThreadEntryFunction)(EsGeneric argument);
EsError EsThreadCreate(EsThreadEntryFunction entryFunction, EsThreadInformation *information, EsGeneric argument);
uint64_t EsThreadGetID(EsHandle thread);
void EsThreadTerminate(EsHandle thread);
```
## Description
Threads are used to execute code in parallel. Threads can be created and terminated. A process can manipulate threads via handles. A handle to a thread can be closed with `EsHandleClose`. Each thread has a unique ID. A thread ID will not be reused until all handles to the thread that previously used the ID have been closed. Threads may not necessarily execute in parallel; the system may simulate the effect of parallel execution by causing the CPU to switch rapidly between which thread it is executing.
`EsThreadCreate` creates a new thread, starting at the provided `entryFunction`, which will be passed `argument`. After the call, `information` will be filled with a `handle` to the newly created thread, and the thread's unique ID in `tid`. This function returns `ES_SUCCESS` if the thread was successfully created.
`EsThreadGetID` gets the ID of a thread from its handle.
`EsThreadTerminate` instructs a thread to terminate. If the thread is executing privileged code at the time of the request, it will complete the prviledged code before terminating. If a thread is waiting on a synchronisation object, such as a mutex or event, it will stop waiting and terminate regardless. **Note**: if a thread owns a mutex or spinlock when it is terminated, it will **not** release the object.
A thread can always use the handle `ES_CURRENT_THREAD` to access itself. This handle should not be closed.
## Example
```c
void MyThread(EsGeneric number) {
while (true) {
EsPrint("Thread %d has ID %d!\n", number.i, EsThreadGetID(ES_CURRENT_THREAD));
}
}
for (uintptr_t number = 1; number <= 5; i++) {
EsThreadInformation information;
EsError error = EsThreadCreate(MyThread, &information, number);
if (error != ES_SUCCESS) {
EsPrint("Thread %d could not be created.\n", number);
} else {
EsPrint("Started thread %d with ID %d.\n", number, information.tid);
// Close the handle to the thread.
EsHandleClose(information.handle):
}
}
```
# Mutexes
## Definitions
```c
void EsMutexAcquire(EsMutex *mutex);
void EsMutexDestroy(EsMutex *mutex);
void EsMutexRelease(EsMutex *mutex);
```
## Description
A mutex is a synchronisation primitive. Threads can *acquire* and *release* it. Only one thread can have acquired the mutex at a time. Before another thread can acquire it, the original thread must release it. When a thread tries to acquire an already-acquired mutex, it will wait until the mutex is released, and then proceed to acquire it.
The `EsMutex` structure contains a mutex. It should be initialised to zero. When the mutex is no longer needed, it can be destroyed with `EsMutexDestroy`. A mutex must not be acquired when it is destroyed.
To acquire a mutex, call `EsMutexAcquire` with a pointer to the mutex. To release a mutex, call `EsMutexRelease`. A thread must not attempt to acquire a mutex it already owns, and it must not attempt to release a mutex it does not own.
## Example
In this example, the function `IncrementCount` can safely be called on different threads at the same time.
```c
EsMutex mutex;
#define INITIAL_COUNT (10)
void IncrementCount(int *count) {
EsMutexAcquire(&mutex);
if (count == 0) {
// count is uninitialised, so initialise it now.
*count = INITIAL_COUNT;
}
count++;
EsMutexRelease(&mutex);
}
```
Without the mutex, unexpected behaviour may occur, as the effective operation of threads may be arbitrarily interleaved.
Consider two threads, A and B, that both call `IncrementCount`:
- Thread A enters IncrementCount and sees that `count = 0`.
- Thread B enters IncrementCount and sees that `count = 0`.
- Thread A sets `count` to `10`.
- Thread A increments `count` by `1` to `11`.
- Thread B sets `count` to `10`.
- Thread B increments `count` by `1` to `11`.
Despite `IncrementCount` being called twice, the count is only incremented once.
Another possibility is:
- Thread A enters IncrementCount and reads `count` into a register; it has the value `10`.
- Thread A adds `1` to the register, giving `11`.
- Thread B enters IncrementCount and reads `count` into a register; it has the value `10`.
- Thread B adds `1` to the register, giving `11`.
- Thread A stores the value in its register into `count`, `11`.
- Thread B stores the value in its register into `count`, `11`.
Again, despite `IncrementCount` being called twice, the count is only incremented once.
## Deadlock
Mutexes must be acquired in a consistent order. For example, if the following pattern appears in your code:
1. Acquire mutex X.
2. Acquire mutex Y.
3. Release mutex Y.
4. Release mutex X.
Then the following pattern must not occur:
1. Acquire mutex Y.
2. Acquire mutex X.
3. Release mutex X.
4. Release mutex Y.
To explain why, suppose thread A executes the first pattern and thread B executes the second.
- Thread A acquires mutex X.
- Thread B acquires mutex Y.
- Thread A attempts to acquire mutex Y. Mutex Y is owned by thread B, so thread A starts waiting for thread B to release it.
- Thread B attempts to acquire mutex X. Mutex X is owned by thread A, so thread B starts waiting for thread A to release it.
- Both threads will continue to wait indefinitely for the other to perform an operation.
# INI files
## Definitions
```c
struct EsINIState {
char *buffer, *sectionClass, *section, *key, *value;
size_t bytes, sectionClassBytes, sectionBytes, keyBytes, valueBytes;
};
bool EsINIParse(EsINIState *s);
bool EsINIPeek(EsINIState *s);
size_t EsINIFormat(EsINIState *s, char *buffer, size_t bytes);
void EsINIZeroTerminate(EsINIState *s);
```
## Description
To parse an INI file, first initialise a blank EsINIState structure. Set `buffer` to point to the INI data, and set `bytes` to the byte count of the data. Then, call `EsINIParse` repeatedly, until it returns false, indicating it has reached the end of the data. After each call to `EsINIParse`, the fields of `EsINIState` are updated to give the information about the last parsed line in the INI file. `EsINIPeek` is the same as `EsINIParse` except it does not advance to the next line in the INI data. Fields in `EsINIState` that are not applicable to the parsed line are set to empty strings. Comment lines set `key` to `;` and `value` contains the comment itself. Aside for empty strings, the fields in `EsINIState` will always point into the provided buffer.
For example, the line `[@hello world]` will set `sectionClass` to `"hello"`, `section` to `"world"`, and `key` and `value` to empty strings. When followed by the line `k=v`, `sectionClass` and `section` will retain their previous values, and `key` will be set to `"k"` and `value` will be set to `"v"`.
`EsINIFormat` formats the contents of `EsINIState` into a buffer. `bytes` gives the size of `buffer`. The return value gives the number of bytes written to `buffer`; this is clipped to the end of the buffer.
`EsINIZeroTerminate` zero-terminates `sectionClass`, `section`, `key` and `value` in the `EsINIState`. It cannot be used after calling `EsINIPeek`.
## Example
```c
size_t fileBytes;
void *file = EsFileReadAll(EsLiteral("|Settings:/Default.ini"), &fileBytes);
EsINIState s = {};
s.buffer = file;
s.bytes = fileBytes;
while (EsINIParse(&s)) {
EsINIZeroTerminate(&s);
EsPrint("section = %z, key = %z, value = %z\n", s.section, s.key, s.value);
}
EsHeapFree(file);
```
# Heap allocator
## Definitions
```c
void *EsHeapAllocate(size_t size, bool zeroMemory, EsHeap *heap = ES_NULL);
void EsHeapFree(void *address, size_t expectedSize = 0, EsHeap *heap = ES_NULL);
void *EsHeapReallocate(void *oldAddress, size_t newAllocationSize, bool zeroNewSpace, EsHeap *heap = ES_NULL);
void EsHeapValidate();
```
## Description
The heap allocator is a general-purpose allocator. It is thread-safe. It is designed to handle regions of memory of arbitrary size, and arbitrary lifetime.
To allocate memory, call `EsHeapAllocate`. Set `size` to be the size of the region in bytes. Set `zeroMemory` to `true` for the contents of the region to be automatically zeroed, otherwise `false`. Set `heap` to `NULL`; this parameter is reserved. The return value is the address of the start of allocated region. It will be suitably aligned, given the specified region size and current processor architecture. If `size` is `0`, then `NULL` is returned.
To free memory, call `EsHeapFree`. Set `address` to be the start of the previously allocated region. Optionally, set `expectedSize` to be the size of the allocated region; if non-zero, the system will assert that this value is correct. Set `heap` to `NULL`; this parameter is reserved. If `address` is `NULL`, this function will do nothing.
To grow or shrink an existing region, call `EsHeapReallocate`. Set `oldAddress` to be the start of the previously allocated region. Set `newAllocationSize` to be new the size of the region. Set `heap` to `NULL`; this parameter is reserved. If `oldAddress` is `NULL`, this call is equivalent to `EsHeapAllocate`. If `newAllocationSize` is `0`, this call is equivalent to `EsHeapFree`. The return value gives the new start address of the region. This may be the same as `oldAddress`, if the region was able to change size in place. If the region was not able to change size in place, the old contents will be copied to the new region. If `zeroNewSpace` is set, and `newAllocationSize` is greater than the previous size of the region, then the newly accessible bytes at the end of the region will be cleared to zero.
`EsHeapValidate` will check the current process's heap for errors. If it finds an error, it will crash the process. Do not rely on any behaviour of this function. It is only intended to aid debugging memory errors.
## Example
```c
int *array = (int *) EsHeapAllocate(sizeof(int) * 10, true);
for (int i = 0; i < 5; i++) {
array[i] = i + 1;
}
for (int i = 0; i < 10; i++) {
EsPrint("%d ", array[i]); // Prints 1 2 3 4 5 0 0 0 0 0.
}
EsPrint("\n");
array = (int *) EsHeapReallocate(array, sizeof(int) * 15, true);
for (int i = 0; i < 15; i++) {
EsPrint("%d ", array[i]); // Prints 1 2 3 4 5 0 0 0 0 0 0 0 0 0 0.
}
EsHeapFree(array);
```
# Rectangles
## Definitions
```c
struct EsRectangle {
int32_t l;
int32_t r;
int32_t t;
int32_t b;
};
#define ES_RECT_1(x) ((EsRectangle) { (int32_t) (x), (int32_t) (x), (int32_t) (x), (int32_t) (x) })
#define ES_RECT_1I(x) ((EsRectangle) { (int32_t) (x), (int32_t) -(x), (int32_t) (x), (int32_t) -(x) })
#define ES_RECT_2(x, y) ((EsRectangle) { (int32_t) (x), (int32_t) (x), (int32_t) (y), (int32_t) (y) })
#define ES_RECT_2I(x, y) ((EsRectangle) { (int32_t) (x), (int32_t) -(x), (int32_t) (y), (int32_t) -(y) })
#define ES_RECT_2S(x, y) ((EsRectangle) { 0, (int32_t) (x), 0, (int32_t) (y) })
#define ES_RECT_4(x, y, z, w) ((EsRectangle) { (int32_t) (x), (int32_t) (y), (int32_t) (z), (int32_t) (w) })
#define ES_RECT_4PD(x, y, w, h) ((EsRectangle) { (int32_t) (x), (int32_t) ((x) + (w)), (int32_t) (y), (int32_t) ((y) + (h)) })
#define ES_RECT_WIDTH(_r) ((_r).r - (_r).l)
#define ES_RECT_HEIGHT(_r) ((_r).b - (_r).t)
#define ES_RECT_TOTAL_H(_r) ((_r).r + (_r).l)
#define ES_RECT_TOTAL_V(_r) ((_r).b + (_r).t)
#define ES_RECT_ALL(_r) (_r).l, (_r).r, (_r).t, (_r).b
#define ES_RECT_VALID(_r) (ES_RECT_WIDTH(_r) > 0 && ES_RECT_HEIGHT(_r) > 0)
EsRectangle EsRectangleAdd(EsRectangle a, EsRectangle b);
EsRectangle EsRectangleAddBorder(EsRectangle rectangle, EsRectangle border);
EsRectangle EsRectangleBounding(EsRectangle a, EsRectangle b);
EsRectangle EsRectangleCenter(EsRectangle parent, EsRectangle child);
EsRectangle EsRectangleCut(EsRectangle a, int32_t amount, char side);
EsRectangle EsRectangleFit(EsRectangle parent, EsRectangle child, bool allowScalingUp);
EsRectangle EsRectangleIntersection(EsRectangle a, EsRectangle b);
EsRectangle EsRectangleSplit(EsRectangle *a, int32_t amount, char side, int32_t gap = 0);
EsRectangle EsRectangleSubtract(EsRectangle a, EsRectangle b);
EsRectangle EsRectangleTranslate(EsRectangle a, EsRectangle b);
bool EsRectangleEquals(EsRectangle a, EsRectangle b);
bool EsRectangleContains(EsRectangle a, int32_t x, int32_t y);
```
## Description
`EsRectangle` is used to store an integral rectangular region. `l` gives the offset of the left edge, `r` the right edge, `t` the top edge, and `b` the bottom edge. Note that this means the rectangle does not contain the pixels with x coordinate `r` and y coordinate `b`. The edges are stored are 32-bit signed integers. TODO Diagram.
`EsRectangleAdd` performs a component-wise sum of two rectangles.
`EsRectangleAddBorder` is similar to `EsRectangleAdd`, but it negates the `r` and `b` fields of `border` before the addition. TODO Diagram.
`EsRectangleSubtract` is similar to `EsRectangleAdd`, but it negates all fields of the second rectangle before the addition. TODO Diagram.
`EsRectangleTranslate` is similar to `EsRectangleAdd`, but it sets `r` to `l` and `b` to `t` in the second rectangle before the addition. TODO Diagram.
`EsRectangleBounding` computes the smallest possible rectangle that contains both parameters. TODO Diagram.
`EsRectangleCenter` centers the `child` rectangle within the `parent` rectangle. The origin of `child` is ignored; only its dimensions matter. TODO Diagram.
`EsRectangleCut` cuts a slice of a rectangle, by moving one of the edges of the input rectangle. The returned rectangle is the slice that was cut off. `side` determines the edge; it is a single character, set to be the same as the name of the field that will be modified. If `amount` is positive, then the edge will be moved inwards by that amount. If `amount` is negative, then the edge will be moved outwards by the absolute value. TODO Diagram.
`EsRectangleSplit` is similar to `EsRectangleCut`, except it modifies the input rectangle so that it contains the modified rectangle after a side is moved. `gap` may be optionally specified to change the distance between the two returned rectangles. TODO Diagram.
`EsRectangleFit` resizes and moves the `child` rectangle, while preserving its aspect ratio, so that it fits in, and is centered in, `parent`. If `allowScalingUp` is set to `false`, the function will never resize `child` to a larger size. TODO Diagram.
`EsRectangleIntersection` computes the intersection of the two parameters. TODO Diagram.
`EsRectangleEquals` returns `true` if the rectangles are identical, otherwise `false`.
`EsRectangleContains` returns `true` if the rectangle contains the specified point, otherwise `false`. Recall, as noted above, if `x` is `r` or larger, the point is considered to not be inside the rectangle, and similarly if `y` is `b` or larger.
`ES_RECT_1` creates a rectangle where all fields are the same value. `ES_RECT_1I` creates a rectangle similarly, except `r` and `b` are negated.
`ES_RECT_2` creates a rectangle where `l` and `r` are the same value, and `t` and `b` are the same value. `ES_RECT_2I` creates a rectangle similarly, except `r` and `b` are negated. `ES_RECT_2S` creates a rectangle with its top-left corner at `(0, 0)`, with the specified width and height.
`ES_RECT_4` creates a rectangle with the specified values of `l`, `r`, `t` and `b`. `ES_RECT_4PD` creates a rectangle with its top-left corner at `(x, y)` and a width of `w` and height of `h`.
`ES_RECT_WIDTH` returns the width of a rectangle. `ES_RECT_HEIGHT` returns the height. `ES_RECT_TOTAL_H` returns the sum of the left and right components. `ES_RECT_TOTAL_V` returns the sum of the top and bottom coponents.
`ES_RECT_ALL` splits a rectangle into its four components, separated by commas.
`ES_RECT_VALID` returns `true` if the rectangle has positive width and height, otherwise `false`.
# Text plans
## Definitions
```c
struct EsTextRun {
EsTextStyle style;
uint32_t offset;
};
struct EsTextPlanProperties {
EsCString cLanguage;
uint32_t flags;
int maxLines;
};
#define ES_TEXT_H_LEFT (1 << 0)
#define ES_TEXT_H_CENTER (1 << 1)
#define ES_TEXT_H_RIGHT (1 << 2)
#define ES_TEXT_V_TOP (1 << 3)
#define ES_TEXT_V_CENTER (1 << 4)
#define ES_TEXT_V_BOTTOM (1 << 5)
#define ES_TEXT_ELLIPSIS (1 << 6)
#define ES_TEXT_WRAP (1 << 7)
#define ES_TEXT_PLAN_SINGLE_USE (1 << 8)
#define ES_TEXT_PLAN_TRIM_SPACES (1 << 9)
#define ES_TEXT_PLAN_RTL (1 << 10)
#define ES_TEXT_PLAN_CLIP_UNBREAKABLE_LINES (1 << 11)
EsTextPlan *EsTextPlanCreate(EsTextPlanProperties *properties, EsRectangle bounds, const char *string, const EsTextRun *textRuns, size_t textRunCount);
int EsTextPlanGetWidth(EsTextPlan *plan);
int EsTextPlanGetHeight(EsTextPlan *plan);
size_t EsTextPlanGetLineCount(EsTextPlan *plan);
void EsTextPlanDestroy(EsTextPlan *plan);
void EsTextPlanReplaceStyleRenderProperties(EsTextPlan *plan, EsTextStyle *style);
```
## Description
Before you can draw text, you first need to create a *text plan*, which contains all the necessary information to draw the text. The advantage of using text plans is that it enables you to draw the same block of text multiple times without needing the text shaping and layout to be recalculated.
To create a text plan, use `EsTextPlanCreate`.
- `properties`: Contains properties to apply while laying out the text. `cLanguage` is the BCP 47 language tag as a string; if `NULL`, the default language is used. `maxLines` contains the maximum number of lines allowed in the layout, after which lines will be clipped; if `0`, the number of lines will be unlimited. `flags` contains any combination of the following constants:
- `ES_TEXT_H/V_...`: Sets the alignment of the text in the bounds.
- `ES_TEXT_WRAP`: The text is allowed to wrap when it reaches the end of a line.
- `ES_TEXT_ELLIPSIS`: If the text is to be truncated, an ellipsis will be inserted.
- `ES_TEXT_PLAN_SINGLE_USE`: Set to automatically destroy the text plan after the first time it is drawn.
- `ES_TEXT_PLAN_TRIM_SPACES`: Removes any leading and trailing spaces from each line.
- `ES_TEXT_PLAN_RTL`: Sets the default text direction to right-to-left.
- `ES_TEXT_PLAN_CLIP_UNBREAKABLE_LINES`: If a word is to long to be word-wrapped, prevent it being from being wrapped regardless, and clip it instead.
- `ES_TEXT_PLAN_NO_FONT_SUBSTITUTION`: Prevents font substitution. Otherwise, glyphs missing from the selected font will be drawn in an automatically-selected font that does support them.
- `bounds`: Gives the width and height of the bounds in which the text will be drawn. Only the dimensions of this rectangle matters. It is unused if word wrapping is disabled, and no text alignment flags are specified.
- `string`: Gives the text string, in UTF-8. This string should remain accessible until the text plan is destroyed.
- `textRuns`: An array of text runs, describing the offset and style of each run in the text. The length of each run is automatically calculated as the difference between its offset and the offset of the next run in the array. The first run should have an offset of `0`. The last item in the array should have its `offset` field set to be the total length of the input string, i.e. the end of the last run. The `style` field of the last item is ignored.
- `textRunCount`: The number of text runs in the array. **Note** that the last item in the array should not be included in this count, because it itself does not describe a run, it is only used to indicate the end position of the actual last run.
Once a text plan has been created, it can be drawn with `EsDrawText`. Information about the calculated text layout can be accessed with `EsTextPlanGetWidth` (returns the horizontal size of the layout), `EsTextPlanGetHeight` (returns the vertical size), and `EsTextPlanGetLineCount` (returns the number of lines in the layout, including lines containing wrapped text). The text plan can be destroyed with `EsTextPlanDestroy`.
After a text plan is created, some of the style properties can be replaced. These are called the *render properties*, because they only are used when the text is rendered, and do not affect the layout. These are the `color`, `blur`, `decorations` and `decorationsColor` fields in `EsTextStyle`. They can be replaced using `EsTextPlanReplaceStyleRenderProperties`. Note that this replaces the properties for all text runs in the text plan.
## Example
```c
void DrawElementText(EsElement *element, EsRectangle bounds, const char *string, size_t stringBytes) {
EsTextRun textRun[2] = {};
EsElementGetTextStyle(element, &textRun[0].style);
textRun[0].offset = 0;
textRun[1].offset = stringBytes;
EsTextPlanProperties properties = {};
EsTextPlan *plan = EsTextPlanCreate(&properties, bounds, string, textRun, 1);
EsDrawText(painter, plan, bounds, nullptr, nullptr);
EsTextPlanDestroy(plan);
}
```
# CRT functions
## Description
The Essence API provides a number of functions from the C standard library which may be used without requiring the POSIX subsystem. See the API header file for an authoritative list of which functions are available. Please note that the functions may not be fully compliant with standards where it would cause unwanted complexity. The functions are prefixed with `EsCRT`; to use the functions without needing this prefix, define `ES_CRT_WITHOUT_PREFIX` before including the API header.

7259
desktop/gui.cpp Normal file

File diff suppressed because it is too large Load Diff

1067
desktop/icons.header Normal file

File diff suppressed because it is too large Load Diff

2616
desktop/list_view.cpp Normal file

File diff suppressed because it is too large Load Diff

2399
desktop/os.header Normal file

File diff suppressed because it is too large Load Diff

775
desktop/posix.cpp Normal file
View File

@ -0,0 +1,775 @@
#define ES_API
#define ES_FORWARD(x) x
#define ES_EXTERN_FORWARD extern "C"
#define ES_DIRECT_API
#include <essence.h>
#ifdef ENABLE_POSIX_SUBSYSTEM
#include <shared/array.cpp>
extern "C" void *ProcessorTLSRead(uintptr_t offset);
extern "C" void ProcessorTLSWrite(uintptr_t offset, void *value);
extern ptrdiff_t tlsStorageOffset;
EsMountPoint *NodeFindMountPoint(const char *prefix, size_t prefixBytes);
EsProcessStartupInformation *ProcessGetStartupInformation();
#define _POSIX_SOURCE
#define _GNU_SOURCE
#define __NEED_struct_iovec
#define __NEED_sigset_t
#define __NEED_struct_timespec
#define __NEED_time_t
#include <limits.h>
#include <bits/syscall.h>
#include <bits/alltypes.h>
#include <signal.h>
#include <sys/sysinfo.h>
#include <sys/resource.h>
#include <sys/stat.h>
#include <bits/ioctl.h>
#include <fcntl.h>
#include <dirent.h>
#include <unistd.h>
#include <poll.h>
#include <sys/utsname.h>
#include <errno.h>
#include <sys/mman.h>
#include <stdlib.h>
#include <sched.h>
#include <elf.h>
struct ChildProcess {
uint64_t id;
EsHandle handle;
};
char *workingDirectory;
Array<ChildProcess> childProcesses;
#ifdef DEBUG_BUILD
double syscallTimeSpent[1024];
uint64_t syscallCallCount[1024];
#endif
const char *syscallNames[] = {
"read", "write", "open", "close", "stat", "fstat", "lstat", "poll",
"lseek", "mmap", "mprotect", "munmap", "brk", "rt_sigaction", "rt_sigprocmask", "rt_sigreturn",
"ioctl", "pread64", "pwrite64", "readv", "writev", "access", "pipe", "select",
"sched_yield", "mremap", "msync", "mincore", "madvise", "shmget", "shmat", "shmctl",
"dup", "dup2", "pause", "nanosleep", "getitimer", "alarm", "setitimer", "getpid",
"sendfile", "socket", "connect", "accept", "sendto", "recvfrom", "sendmsg", "recvmsg",
"shutdown", "bind", "listen", "getsockname", "getpeername", "socketpair", "setsockopt", "getsockopt",
"clone", "fork", "vfork", "execve", "exit", "wait4", "kill", "uname",
"semget", "semop", "semctl", "shmdt", "msgget", "msgsnd", "msgrcv", "msgctl",
"fcntl", "flock", "fsync", "fdatasync", "truncate", "ftruncate", "getdents", "getcwd",
"chdir", "fchdir", "rename", "mkdir", "rmdir", "creat", "link", "unlink",
"symlink", "readlink", "chmod", "fchmod", "chown", "fchown", "lchown", "umask",
"gettimeofday", "getrlimit", "getrusage", "sysinfo", "times", "ptrace", "getuid", "syslog",
"getgid", "setuid", "setgid", "geteuid", "getegid", "setpgid", "getppid", "getpgrp",
"setsid", "setreuid", "setregid", "getgroups", "setgroups", "setresuid", "getresuid", "setresgid",
"getresgid", "getpgid", "setfsuid", "setfsgid", "getsid", "capget", "capset", "rt_sigpending",
"rt_sigtimedwait", "rt_sigqueueinfo", "rt_sigsuspend", "sigaltstack", "utime", "mknod", "uselib", "personality",
"ustat", "statfs", "fstatfs", "sysfs", "getpriority", "setpriority", "sched_setparam", "sched_getparam",
"sched_setscheduler", "sched_getscheduler", "sched_get_priority_max", "sched_get_priority_min", "sched_rr_get_interval", "mlock", "munlock", "mlockall",
"munlockall", "vhangup", "modify_ldt", "pivot_root", "_sysctl", "prctl", "arch_prctl", "adjtimex",
"setrlimit", "chroot", "sync", "acct", "settimeofday", "mount", "umount2", "swapon",
"swapoff", "reboot", "sethostname", "setdomainname", "iopl", "ioperm", "create_module", "init_module",
"delete_module", "get_kernel_syms", "query_module", "quotactl", "nfsservctl", "getpmsg", "putpmsg", "afs_syscall",
"tuxcall", "security", "gettid", "readahead", "setxattr", "lsetxattr", "fsetxattr", "getxattr",
"lgetxattr", "fgetxattr", "listxattr", "llistxattr", "flistxattr", "removexattr", "lremovexattr", "fremovexattr",
"tkill", "time", "futex", "sched_setaffinity", "sched_getaffinity", "set_thread_area", "io_setup", "io_destroy",
"io_getevents", "io_submit", "io_cancel", "get_thread_area", "lookup_dcookie", "epoll_create", "epoll_ctl_old", "epoll_wait_old",
"remap_file_pages", "getdents64", "set_tid_address", "restart_syscall", "semtimedop", "fadvise64", "timer_create", "timer_settime",
"timer_gettime", "timer_getoverrun", "timer_delete", "clock_settime", "clock_gettime", "clock_getres", "clock_nanosleep", "exit_group",
"epoll_wait", "epoll_ctl", "tgkill", "utimes", "vserver", "mbind", "set_mempolicy", "get_mempolicy",
"mq_open", "mq_unlink", "mq_timedsend", "mq_timedreceive", "mq_notify", "mq_getsetattr", "kexec_load", "waitid",
"add_key", "request_key", "keyctl", "ioprio_set", "ioprio_get", "inotify_init", "inotify_add_watch", "inotify_rm_watch",
"migrate_pages", "openat", "mkdirat", "mknodat", "fchownat", "futimesat", "newfstatat", "unlinkat",
"renameat", "linkat", "symlinkat", "readlinkat", "fchmodat", "faccessat", "pselect6", "ppoll",
"unshare", "set_robust_list", "get_robust_list", "splice", "tee", "sync_file_range", "vmsplice", "move_pages",
"utimensat", "epoll_pwait", "signalfd", "timerfd_create", "eventfd", "fallocate", "timerfd_settime", "timerfd_gettime",
"accept4", "signalfd4", "eventfd2", "epoll_create1", "dup3", "pipe2", "inotify_init1", "preadv",
"pwritev", "rt_tgsigqueueinfo", "perf_event_open", "recvmmsg", "fanotify_init", "fanotify_mark", "prlimit64", "name_to_handle_at",
"open_by_handle_at", "clock_adjtime", "syncfs", "sendmmsg", "setns", "getcpu", "process_vm_readv", "process_vm_writev",
"kcmp", "finit_module", "sched_setattr", "sched_getattr", "renameat2", "seccomp", "getrandom", "memfd_create",
"kexec_file_load", "bpf", "execveat", "userfaultfd", "membarrier", "mlock2", "copy_file_range", "preadv2",
"pwritev2", "pkey_mprotect", "pkey_alloc", "pkey_free", "statx",
};
extern "C" void ProcessorCheckStackAlignment();
char *EsPOSIXConvertPath(const char *path, size_t *outNameLength, bool addPOSIXMountPointPrefix) {
const char *posixNames[2] = { path[0] != '/' ? workingDirectory : nullptr, path };
size_t posixNameLengths[2] = { path[0] != '/' ? EsCStringLength(workingDirectory) : 0, EsCStringLength(path) };
char *name = (char *) EsHeapAllocate(posixNameLengths[0] + posixNameLengths[1] + (addPOSIXMountPointPrefix ? 7 : 0) + 2 /* space for / and NUL; see chdir */, true);
if (!name) return nullptr;
size_t nameLength = 0;
if (addPOSIXMountPointPrefix) name += 7;
for (uintptr_t i = 0; i < 2; i++) {
while (posixNameLengths[i]) {
const char *entry = posixNames[i];
size_t entryLength = 0;
while (posixNameLengths[i]) {
posixNameLengths[i]--;
posixNames[i]++;
if (entry[entryLength] == '/') break;
entryLength++;
}
if (!entryLength || (entryLength == 1 && entry[0] == '.')) {
// Ignore.
} else if (entryLength == 2 && entry[0] == '.' && entry[1] == '.' && nameLength) {
while (name[--nameLength] != '/');
} else {
name[nameLength++] = '/';
EsMemoryCopy(name + nameLength, entry, entryLength);
nameLength += entryLength;
}
}
}
if (!nameLength) {
nameLength++;
name[0] = '/';
}
if (addPOSIXMountPointPrefix) {
name -= 7;
nameLength += 7;
EsMemoryCopy(name, "|POSIX:", 7);
}
if (outNameLength) *outNameLength = nameLength;
name[nameLength] = 0;
return name;
}
long EsPOSIXSystemCall(long n, long a1, long a2, long a3, long a4, long a5, long a6) {
#ifdef DEBUG_BUILD
ProcessorCheckStackAlignment();
#endif
long returnValue = 0;
_EsPOSIXSyscall syscall = { n, a1, a2, a3, a4, a5, a6 };
#ifdef DEBUG_BUILD
double startTime = EsTimeStampMs();
static double processStartTime = 0;
if (!processStartTime) {
processStartTime = startTime;
}
if (n == SYS_exit_group) {
double processExecutionTime = startTime - processStartTime;
EsPrint("=== System call performance ===\n");
int array[sizeof(syscallNames) / sizeof(syscallNames[0])];
for (uintptr_t i = 0; i < sizeof(array) / sizeof(array[0]); i++) {
array[i] = i;
}
EsCRTqsort(array, sizeof(array) / sizeof(array[0]), sizeof(array[0]), [] (const void *_left, const void *_right) {
int left = *(int *) _left, right = *(int *) _right;
if (syscallTimeSpent[left] > syscallTimeSpent[right]) return -1;
if (syscallTimeSpent[left] < syscallTimeSpent[right]) return 1;
return 0;
});
double total = 0;
for (uintptr_t i = 0; i < sizeof(array) / sizeof(array[0]); i++) {
if (!syscallTimeSpent[array[i]]) break;
EsPrint("%z - %Fms - %d calls\n", syscallNames[array[i]], syscallTimeSpent[array[i]], syscallCallCount[array[i]]);
total += syscallTimeSpent[array[i]];
}
EsPrint("Total time in system calls: %Fms\n", total);
EsPrint("Total run time of process: %Fms\n", processExecutionTime);
}
#endif
switch (n) {
case SYS_open: {
size_t pathBytes;
char *path = EsPOSIXConvertPath((const char *) a1, &pathBytes, false);
syscall.arguments[0] = (long) path;
syscall.arguments[4] = (long) NodeFindMountPoint(EsLiteral("|POSIX:"))->base;
syscall.arguments[6] = (long) pathBytes;
returnValue = EsSyscall(ES_SYSCALL_POSIX, (uintptr_t) &syscall, 0, 0, 0);
// EsPrint("SYS_open '%s' with handle %d\n", pathBytes, path, returnValue);
EsHeapFree(path);
} break;
case SYS_vfork: {
long result = EsSyscall(ES_SYSCALL_POSIX, (uintptr_t) &syscall, 0, 0, 0);
if (result > 0) {
EsHandle handle = result;
ChildProcess pid = { EsProcessGetID(handle), handle };
childProcesses.Add(pid);
returnValue = pid.id;
}
} break;
case SYS_pipe: {
syscall.index = SYS_pipe2;
syscall.arguments[1] = 0;
returnValue = EsSyscall(ES_SYSCALL_POSIX, (uintptr_t) &syscall, 0, 0, 0);
} break;
case SYS_close: {
// EsPrint("SYS_close handle %d\n", a1);
returnValue = EsSyscall(ES_SYSCALL_POSIX, (uintptr_t) &syscall, 0, 0, 0);
} break;
case SYS_pipe2:
case SYS_writev:
case SYS_fcntl:
case SYS_dup2:
case SYS_write:
case SYS_readv:
case SYS_lseek:
case SYS_read:
case SYS_fstat:
case SYS_sysinfo:
case SYS_getdents64:
case SYS_exit_group:
case SYS_ioctl: {
returnValue = EsSyscall(ES_SYSCALL_POSIX, (uintptr_t) &syscall, 0, 0, 0);
} break;
case SYS_chdir: {
char *simplified = EsPOSIXConvertPath((const char *) a1, nullptr, false);
EsHeapFree(workingDirectory);
size_t oldLength = EsCStringLength(simplified);
simplified[oldLength] = '/';
simplified[oldLength + 1] = 0;
workingDirectory = simplified;
} break;
case SYS_getpid: {
// Run the system call directly, so that the kernel can handle the vfork()'d case.
returnValue = EsSyscall(ES_SYSCALL_THREAD_GET_ID, ES_CURRENT_PROCESS, 0, 0, 0);
} break;
case SYS_gettid: {
returnValue = EsThreadGetID(ES_CURRENT_THREAD);
} break;
case SYS_getcwd: {
size_t bytes = EsCStringLength(workingDirectory) + 1;
char *destination = (char *) a1;
if (bytes > (size_t) a2) {
returnValue = -ERANGE;
} else {
EsMemoryCopy(destination, workingDirectory, bytes);
if (workingDirectory[bytes - 2] == '/' && bytes > 2) destination[bytes - 2] = 0;
returnValue = a1;
}
} break;
case SYS_getppid:
case SYS_getuid:
case SYS_getgid:
case SYS_getegid:
case SYS_geteuid: {
// TODO.
} break;
case SYS_getrusage: {
// TODO.
struct rusage *buffer = (struct rusage *) a2;
EsMemoryZero(buffer, sizeof(struct rusage));
} break;
case SYS_unlink: {
_EsNodeInformation node;
node.handle = NodeFindMountPoint(EsLiteral("|POSIX:"))->base;
size_t pathBytes;
char *path = EsPOSIXConvertPath((const char *) a1, &pathBytes, false);
EsError error = EsSyscall(ES_SYSCALL_NODE_OPEN, (uintptr_t) path, pathBytes, ES_NODE_FAIL_IF_NOT_FOUND | ES_FILE_WRITE_EXCLUSIVE, (uintptr_t) &node);
EsHeapFree(path);
if (error == ES_ERROR_FILE_DOES_NOT_EXIST) returnValue = -ENOENT;
else if (error == ES_ERROR_PATH_NOT_TRAVERSABLE) returnValue = -ENOTDIR;
else if (error == ES_ERROR_FILE_IN_EXCLUSIVE_USE) returnValue = -EBUSY;
else if (error == ES_ERROR_DRIVE_CONTROLLER_REPORTED || error == ES_ERROR_CORRUPT_DATA) returnValue = -EIO;
else if (error != ES_SUCCESS) returnValue = -EACCES;
else {
error = EsSyscall(ES_SYSCALL_NODE_DELETE, node.handle, 0, 0, 0);
EsHandleClose(node.handle);
if (error == ES_ERROR_DRIVE_CONTROLLER_REPORTED || error == ES_ERROR_CORRUPT_DATA) returnValue = -EIO;
else if (error != ES_SUCCESS) returnValue = -EACCES;
}
} break;
case SYS_truncate: {
_EsNodeInformation node;
node.handle = NodeFindMountPoint(EsLiteral("|POSIX:"))->base;
size_t pathBytes;
char *path = EsPOSIXConvertPath((const char *) a1, &pathBytes, false);
EsError error = EsSyscall(ES_SYSCALL_NODE_OPEN, (uintptr_t) path, pathBytes, ES_NODE_FAIL_IF_NOT_FOUND | ES_FILE_WRITE_EXCLUSIVE, (uintptr_t) &node);
EsHeapFree(path);
if (error == ES_ERROR_FILE_DOES_NOT_EXIST) returnValue = -ENOENT;
else if (error == ES_ERROR_PATH_NOT_TRAVERSABLE) returnValue = -ENOTDIR;
else if (error == ES_ERROR_FILE_IN_EXCLUSIVE_USE) returnValue = -EBUSY;
else if (error == ES_ERROR_DRIVE_CONTROLLER_REPORTED || error == ES_ERROR_CORRUPT_DATA) returnValue = -EIO;
else if (error != ES_SUCCESS) returnValue = -EACCES;
else if (node.type == ES_NODE_DIRECTORY) { returnValue = -EISDIR; EsHandleClose(node.handle); }
else {
EsError error = EsFileResize(node.handle, a2);
EsHandleClose(node.handle);
if (error == ES_ERROR_DRIVE_CONTROLLER_REPORTED || error == ES_ERROR_CORRUPT_DATA) returnValue = -EIO;
else if (error != ES_SUCCESS) returnValue = -EACCES;
}
} break;
case SYS_execve: {
// NOTE We can't use EsHeapAllocate since the system call never returns.
size_t pathBytes;
char *_path = EsPOSIXConvertPath((const char *) a1, &pathBytes, false);
char *path = (char *) __builtin_alloca(pathBytes);
EsMemoryCopy(path, _path, pathBytes);
char **argv = (char **) a2;
char **envp = (char **) a3;
size_t environmentSize = 2;
for (uintptr_t i = 0; argv[i]; i++) environmentSize += EsCStringLength(argv[i]) + 1;
for (uintptr_t i = 0; envp[i]; i++) environmentSize += EsCStringLength(envp[i]) + 1;
bool environmentContainsWorkingDirectory = false;
for (uintptr_t i = 0; envp[i]; i++) {
if (0 == EsMemoryCompare("PWD=", envp[i], 4)) {
environmentContainsWorkingDirectory = true;
break;
}
}
if (!environmentContainsWorkingDirectory) {
environmentSize += 4 + EsCStringLength(workingDirectory) + 1;
}
char newEnvironment[environmentSize];
char *position = newEnvironment;
EsMemoryZero(newEnvironment, environmentSize);
for (uintptr_t i = 0; argv[i]; i++) {
size_t length = EsCStringLength(argv[i]) + 1;
EsMemoryCopy(position, argv[i], length);
position += length;
}
position++;
for (uintptr_t i = 0; envp[i]; i++) {
size_t length = EsCStringLength(envp[i]) + 1;
EsMemoryCopy(position, envp[i], length);
position += length;
}
if (!environmentContainsWorkingDirectory) {
size_t length = 4 + EsCStringLength(workingDirectory) + 1;
EsMemoryCopy(position, "PWD=", 4);
EsMemoryCopy(position + 4, workingDirectory, EsCStringLength(workingDirectory) + 1);
position += length;
}
syscall.arguments[0] = (long) path;
syscall.arguments[1] = (long) pathBytes;
syscall.arguments[2] = (long) newEnvironment;
syscall.arguments[3] = (long) environmentSize;
syscall.arguments[4] = (long) NodeFindMountPoint(EsLiteral("|POSIX:"))->base;
returnValue = EsSyscall(ES_SYSCALL_POSIX, (uintptr_t) &syscall, 0, 0, 0);
} break;
case SYS_access: {
// We don't support file permissions yet, so just check the file exists.
int fd = EsPOSIXSystemCall(SYS_open, a1, O_PATH, 0, 0, 0, 0);
if (fd < 0) returnValue = fd;
else {
returnValue = 0;
EsPOSIXSystemCall(SYS_close, fd, 0, 0, 0, 0, 0);
}
} break;
case SYS_lstat:
case SYS_stat: {
int fd = EsPOSIXSystemCall(SYS_open, a1, O_PATH, 0, 0, 0, 0);
if (fd < 0) returnValue = fd;
else {
returnValue = EsPOSIXSystemCall(SYS_fstat, fd, a2, 0, 0, 0, 0);
EsPOSIXSystemCall(SYS_close, fd, 0, 0, 0, 0, 0);
}
} break;
case SYS_readlink: {
if (0 == EsMemoryCompare((void *) a1, EsLiteral("/proc/self/fd/"))) {
// The process is trying to get the path of a file descriptor.
syscall.index = ES_POSIX_SYSCALL_GET_POSIX_FD_PATH;
syscall.arguments[0] = EsCRTatoi((char *) a1 + EsCStringLength("/proc/self/fd/"));
returnValue = EsSyscall(ES_SYSCALL_POSIX, (uintptr_t) &syscall, 0, 0, 0);
} else {
// We don't support symbolic links, so the output is the same as the input.
int length = EsCStringLength((char *) a1);
EsMemoryZero((void *) a2, a3);
EsMemoryCopy((void *) a2, (void *) a1, length > a3 ? a3 : length);
returnValue = length > a3 ? a3 : length;
}
} break;
case SYS_set_tid_address: {
// TODO Support set_child_tid and clear_child_tid addresses.
returnValue = EsThreadGetID(ES_CURRENT_THREAD);
} break;
case SYS_brk: {
returnValue = -1;
} break;
case SYS_mremap: {
returnValue = -ENOMEM;
} break;
case SYS_mmap: {
bool read = a3 & PROT_READ, write = a3 & PROT_WRITE, none = a3 == PROT_NONE;
if (a4 & MAP_FIXED) {
returnValue = -ENOMEM;
} else if ((a4 == (MAP_ANON | MAP_PRIVATE)) && (a5 == -1) && (a6 == 0) && ((read && write) || none)) {
returnValue = (long) EsMemoryReserve(a2, ES_MEMORY_PROTECTION_READ_WRITE, none ? 0 : ES_MEMORY_RESERVE_COMMIT_ALL);
} else {
EsPanic("Unsupported mmap [%x, %x, %x, %x, %x, %x]\n", a1, a2, a3, a4, a5, a6);
}
} break;
case SYS_munmap: {
void *address = (void *) a1;
size_t length = (size_t) a2;
if (length == 0 || ((uintptr_t) address & (ES_PAGE_SIZE - 1))) {
returnValue = -EINVAL;
} else {
EsMemoryUnreserve(address, length);
}
} break;
case SYS_mprotect: {
void *address = (void *) a1;
size_t length = (size_t) a2;
int protection = (int) a3;
if (protection == (PROT_READ | PROT_WRITE)) {
returnValue = EsMemoryCommit(address, length) ? 0 : -ENOMEM;
} else if (protection == 0) {
returnValue = EsMemoryDecommit(address, length) ? 0 : -ENOMEM;
} else {
EsPanic("Unsupported mprotect [%x, %x, %x, %x, %x, %x]\n", a1, a2, a3, a4, a5, a6);
}
} break;
case SYS_prlimit64: {
// You can't access other process's resources.
if (a1 && a1 != (long) EsSyscall(ES_SYSCALL_THREAD_GET_ID, ES_CURRENT_PROCESS, 0, 0, 0)) {
returnValue = -EPERM;
break;
}
struct rlimit *newLimit = (struct rlimit *) a3;
if (newLimit && a2 != RLIMIT_STACK) {
returnValue = -EPERM;
break;
}
struct rlimit *limit = (struct rlimit *) a4;
if (a2 == RLIMIT_STACK) {
size_t current, maximum;
EsError error = EsSyscall(ES_SYSCALL_THREAD_STACK_SIZE, ES_CURRENT_THREAD,
(uintptr_t) &current, (uintptr_t) &maximum, newLimit ? newLimit->rlim_cur : 0);
if (limit) {
limit->rlim_cur = current;
limit->rlim_max = maximum;
}
if (error != ES_SUCCESS) returnValue = -EINVAL;
} else if (a2 == RLIMIT_AS) {
if (limit) limit->rlim_cur = limit->rlim_max = RLIM_INFINITY;
} else if (a2 == RLIMIT_RSS) {
if (limit) limit->rlim_cur = limit->rlim_max = 0x10000000; // 256MB. This value is fake. TODO
} else if (a2 == RLIMIT_NOFILE) {
if (limit) limit->rlim_cur = limit->rlim_max = 1048576;
} else {
EsPanic("Unsupported prlimit64 [%x]\n", a2);
}
} break;
case SYS_setitimer:
case SYS_madvise:
case SYS_umask:
case SYS_chmod:
case SYS_rt_sigaction:
case SYS_rt_sigprocmask: {
// TODO Support signals.
// Ignore.
} break;
case SYS_clock_gettime: {
// We'll ignore the clockid_t in a1, since we don't have proper timekeeping yet.
struct timespec *tp = (struct timespec *) a2;
uint64_t timeStamp = EsTimeStamp();
uint64_t unitsPerMicrosecond = EsSystemGetConstant(ES_SYSTEM_CONSTANT_TIME_STAMP_UNITS_PER_MICROSECOND);
uint64_t microseconds = timeStamp / unitsPerMicrosecond;
tp->tv_sec = microseconds / 1000000;
tp->tv_nsec = (microseconds % 1000000) * 1000;
} break;
case SYS_wait4: {
if ((a3 & ~3) || a4 || a1 < -1 || !a1) {
EsPanic("Unsupported wait4 [%x/%x/%x/%x]\n", a1, a2, a3, a4);
}
int *wstatus = (int *) a2;
int options = a3;
bool foundChild = false;
uintptr_t childIndex = 0;
if (a1 > 0) {
for (uintptr_t i = 0; i < childProcesses.Length(); i++) {
if (childProcesses[i].id == (uint64_t) a1) {
foundChild = true;
childIndex = i;
break;
}
}
} else if (a1 == -1) {
foundChild = childProcesses.Length();
}
if (!foundChild) {
returnValue = -ECHILD;
} else {
returnValue = 0;
if (~options & 1 /* WNOHANG */) {
if (a1 == -1) {
EsHandle *handles = (EsHandle *) __builtin_alloca(childProcesses.Length() * sizeof(EsHandle));
for (uintptr_t i = 0; i < childProcesses.Length(); i++) {
handles[i] = childProcesses[i].handle;
}
EsWait(handles, childProcesses.Length(), ES_WAIT_NO_TIMEOUT);
} else {
EsWaitSingle(childProcesses[childIndex].handle);
}
}
for (uintptr_t i = 0; i < childProcesses.Length(); i++) {
if (a1 > 0 && childProcesses[i].id != (uint64_t) a1) {
continue;
}
EsHandle handle = childProcesses[i].handle;
EsProcessState state;
EsProcessGetState(handle, &state);
if (state.flags & ES_PROCESS_STATE_ALL_THREADS_TERMINATED) {
returnValue = childProcesses[i].id;
*wstatus = (EsProcessGetExitStatus(handle) & 0xFF) << 8;
EsHandleClose(handle);
childProcesses.Delete(i);
break;
}
}
}
} break;
case SYS_sched_getaffinity: {
// TODO Getting the correct number of CPUs.
// TODO Getting the affinity for other processes.
cpu_set_t *set = (cpu_set_t *) a3;
EsCRTmemset(set, 0, a2);
CPU_SET(0, set);
} break;
case SYS_mkdir: {
size_t pathBytes;
char *path = EsPOSIXConvertPath((const char *) a1, &pathBytes, true);
EsError error = EsPathCreate(path, pathBytes, ES_NODE_DIRECTORY, false);
if (error == ES_ERROR_INSUFFICIENT_RESOURCES) returnValue = -ENOMEM;
else if (error == ES_ERROR_FILE_ALREADY_EXISTS) returnValue = -EEXIST;
else if (error == ES_ERROR_PATH_NOT_TRAVERSABLE) returnValue = -ENOENT;
else if (error == ES_ERROR_PATH_NOT_WITHIN_MOUNTED_VOLUME) returnValue = -ENOENT;
else if (error == ES_ERROR_FILE_ON_READ_ONLY_VOLUME) returnValue = -EPERM;
EsHeapFree(path);
} break;
case SYS_uname: {
struct utsname *buffer = (struct utsname *) a1;
EsCRTstrcpy(buffer->sysname, "Essence");
EsCRTstrcpy(buffer->release, "0.0.0");
EsCRTstrcpy(buffer->version, "0.0.0");
EsCRTstrcpy(buffer->machine, "Unknown");
} break;
case SYS_setpgid: {
if (a1 < 0) {
returnValue = -EINVAL;
} else {
EsHandle process = EsProcessOpen(a1);
if (process != ES_INVALID_HANDLE) {
syscall.arguments[0] = process;
returnValue = EsSyscall(ES_SYSCALL_POSIX, (uintptr_t) &syscall, 0, 0, 0);
EsHandleClose(process);
} else {
returnValue = -ESRCH;
}
}
} break;
case SYS_rename: {
size_t oldPathBytes;
char *oldPath = EsPOSIXConvertPath((const char *) a1, &oldPathBytes, true);
size_t newPathBytes;
char *newPath = EsPOSIXConvertPath((const char *) a2, &newPathBytes, true);
EsError error = EsPathMove(oldPath, oldPathBytes, newPath, newPathBytes);
EsHeapFree(oldPath);
EsHeapFree(newPath);
// TODO More return values.
if (error == ES_ERROR_FILE_DOES_NOT_EXIST) returnValue = -ENOENT;
else if (error == ES_ERROR_PATH_NOT_TRAVERSABLE) returnValue = -ENOTDIR;
else if (error == ES_ERROR_FILE_IN_EXCLUSIVE_USE) returnValue = -EBUSY;
else if (error == ES_ERROR_DRIVE_CONTROLLER_REPORTED || error == ES_ERROR_CORRUPT_DATA) returnValue = -EIO;
else if (error != ES_SUCCESS) returnValue = -EACCES;
} break;
case -1000: {
// Update thread local storage:
void *apiTLS = ProcessorTLSRead(tlsStorageOffset);
EsSyscall(ES_SYSCALL_PROCESS_SET_TLS, a1, 0, 0, 0);
tlsStorageOffset = -a2;
ProcessorTLSWrite(tlsStorageOffset, apiTLS);
} break;
default: {
EsPanic("Unknown linux syscall %d = %z.\nArguments: %x, %x, %x, %x, %x, %x\n",
n, syscallNames[n], a1, a2, a3, a4, a5, a6);
} break;
}
#ifdef DEBUG_BUILD
double endTime = EsTimeStampMs();
syscallTimeSpent[n] += endTime - startTime;
syscallCallCount[n]++;
#endif
// EsPrint(":: %z %x %x %x -> %x; %Fms\n", syscallNames[n], a1, a2, a3, returnValue, endTime - startTime);
return returnValue;
}
void EsPOSIXInitialise(int *argc, char ***argv) {
// Get the arguments and environment.
EsHandle environmentHandle = EsSyscall(ES_SYSCALL_PROCESS_GET_CREATION_ARGUMENT, ES_CURRENT_PROCESS, CREATION_ARGUMENT_ENVIRONMENT, 0, 0);
char *environmentBuffer = (char *) "./application\0\0LANG=en_US.UTF-8\0PWD=/\0HOME=/\0PATH=/Applications/POSIX/bin\0TMPDIR=/Applications/POSIX/tmp\0\0";
if (environmentHandle) {
environmentBuffer = (char *) EsHeapAllocate(ARG_MAX, false);
EsConstantBufferRead((EsHandle) environmentHandle, environmentBuffer);
EsHandleClose((EsHandle) environmentHandle);
}
// Extract the arguments and environment variables.
uintptr_t position = 0;
char *start = environmentBuffer;
Array<void *> _argv = {};
*argc = 0;
for (int i = 0; i < 2; i++) {
while (position < ARG_MAX) {
if (!environmentBuffer[position]) {
_argv.Add(start);
start = environmentBuffer + position + 1;
if (i == 0) {
*argc = *argc + 1;
}
if (!environmentBuffer[position + 1]) {
start = environmentBuffer + position + 2;
_argv.Add(nullptr);
break;
}
}
position++;
}
position += 2;
}
// Copy the working directory string.
for (uintptr_t i = *argc + 1; i < _argv.Length(); i++) {
if (_argv[i] && 0 == EsMemoryCompare("PWD=", _argv[i], 4)) {
size_t length = EsCStringLength((char *) _argv[i]) - 4;
workingDirectory = (char *) EsHeapAllocate(length + 2, false);
workingDirectory[length] = 0, workingDirectory[length + 1] = 0;
EsMemoryCopy(workingDirectory, (char *) _argv[i] + 4, length);
if (workingDirectory[length - 1] != '/') workingDirectory[length] = '/';
}
}
// Add the auxillary vectors.
EsProcessStartupInformation *startupInformation = ProcessGetStartupInformation();
#ifdef ARCH_X86_64
Elf64_Phdr *tlsHeader = (Elf64_Phdr *) EsHeapAllocate(sizeof(Elf64_Phdr), true);
tlsHeader->p_type = PT_TLS;
tlsHeader->p_flags = 4 /* read */;
tlsHeader->p_vaddr = startupInformation->tlsImageStart;
tlsHeader->p_filesz = startupInformation->tlsImageBytes;
tlsHeader->p_memsz = startupInformation->tlsBytes;
tlsHeader->p_align = 8;
_argv.Add((void *) AT_PHNUM);
_argv.Add((void *) 1);
_argv.Add((void *) AT_PHENT);
_argv.Add((void *) sizeof(Elf64_Phdr));
_argv.Add((void *) AT_PHDR);
_argv.Add((void *) tlsHeader);
#else
#error "no architecture TLS support"
#endif
_argv.Add((void *) AT_PAGESZ);
_argv.Add((void *) ES_PAGE_SIZE);
_argv.Add(nullptr);
// Return argv.
*argv = (char **) _argv.array;
}
#endif

371
desktop/prefix.h Normal file
View File

@ -0,0 +1,371 @@
// ----------------- Includes:
#ifndef IncludedEssenceAPIHeader
#define IncludedEssenceAPIHeader
#include <limits.h>
#ifndef KERNEL
#include <stdint.h>
#include <stddef.h>
#include <stdbool.h>
#endif
#include <stdarg.h>
// --------- C++/C differences:
#ifdef __cplusplus
#define ES_EXTERN_C extern "C"
#define ES_CONSTRUCTOR(x) x
#define ES_NULL nullptr
// Scoped defer: http://www.gingerbill.org/article/defer-in-cpp.html
template <typename F> struct _EsDefer4 { F f; _EsDefer4(F f) : f(f) {} ~_EsDefer4() { f(); } };
template <typename F> _EsDefer4<F> _EsDeferFunction(F f) { return _EsDefer4<F>(f); }
#define EsDEFER_1(x, y) x ## y
#define EsDEFER_2(x, y) EsDEFER_1(x, y)
#define EsDEFER_3(x) EsDEFER_2(x, __COUNTER__)
#define _EsDefer5(code) auto EsDEFER_3(_defer_) = _EsDeferFunction([&](){code;})
#define EsDefer(code) _EsDefer5(code)
union EsGeneric {
uintptr_t u;
intptr_t i;
void *p;
inline EsGeneric() = default;
inline EsGeneric(uintptr_t y) { u = y; }
inline EsGeneric( intptr_t y) { i = y; }
inline EsGeneric(unsigned y) { u = y; }
inline EsGeneric( int y) { i = y; }
inline EsGeneric( void *y) { p = y; }
inline bool operator==(EsGeneric r) const { return r.u == u; }
};
#else
#define ES_EXTERN_C extern
#define ES_CONSTRUCTOR(x)
#define ES_NULL 0
typedef union {
uintptr_t u;
intptr_t i;
void *p;
} EsGeneric;
typedef struct EsElementPublic EsElementPublic;
#endif
// --------- Macros:
#ifdef ARCH_X86_64
#define ES_API_BASE ((void **) 0x1000)
#define ES_SHARED_MEMORY_MAXIMUM_SIZE ((size_t) (1024) * 1024 * 1024 * 1024)
#define ES_PAGE_SIZE (4096)
#define ES_PAGE_BITS (12)
typedef struct EsCRTjmp_buf {
uintptr_t rsp, rbp, rbx, r12, r13, r14, r15, rip;
} EsCRTjmp_buf;
ES_EXTERN_C int _EsCRTsetjmp(EsCRTjmp_buf *env);
ES_EXTERN_C __attribute__((noreturn)) void _EsCRTlongjmp(EsCRTjmp_buf *env, int val);
#define EsCRTsetjmp(x) _EsCRTsetjmp(&(x))
#define EsCRTlongjmp(x, y) _EsCRTlongjmp(&(x), (y))
#endif
#define EsContainerOf(type, member, pointer) ((type *) ((uint8_t *) pointer - offsetof(type, member)))
#define ES_CHECK_ERROR(x) (((intptr_t) (x)) < (ES_SUCCESS))
#define ES_RECT_1(x) ((EsRectangle) { (int32_t) (x), (int32_t) (x), (int32_t) (x), (int32_t) (x) })
#define ES_RECT_1I(x) ((EsRectangle) { (int32_t) (x), (int32_t) -(x), (int32_t) (x), (int32_t) -(x) })
#define ES_RECT_2(x, y) ((EsRectangle) { (int32_t) (x), (int32_t) (x), (int32_t) (y), (int32_t) (y) })
#define ES_RECT_2I(x, y) ((EsRectangle) { (int32_t) (x), (int32_t) -(x), (int32_t) (y), (int32_t) -(y) })
#define ES_RECT_2S(x, y) ((EsRectangle) { 0, (int32_t) (x), 0, (int32_t) (y) })
#define ES_RECT_4(x, y, z, w) ((EsRectangle) { (int32_t) (x), (int32_t) (y), (int32_t) (z), (int32_t) (w) })
#define ES_RECT_4PD(x, y, w, h) ((EsRectangle) { (int32_t) (x), (int32_t) ((x) + (w)), (int32_t) (y), (int32_t) ((y) + (h)) })
#define ES_RECT_WIDTH(_r) ((_r).r - (_r).l)
#define ES_RECT_HEIGHT(_r) ((_r).b - (_r).t)
#define ES_RECT_TOTAL_H(_r) ((_r).r + (_r).l)
#define ES_RECT_TOTAL_V(_r) ((_r).b + (_r).t)
#define ES_RECT_SIZE(_r) ES_RECT_WIDTH(_r), ES_RECT_HEIGHT(_r)
#define ES_RECT_TOP_LEFT(_r) (_r).l, (_r).t
#define ES_RECT_BOTTOM_LEFT(_r) (_r).l, (_r).b
#define ES_RECT_BOTTOM_RIGHT(_r) (_r).r, (_r).b
#define ES_RECT_ALL(_r) (_r).l, (_r).r, (_r).t, (_r).b
#define ES_RECT_VALID(_r) (ES_RECT_WIDTH(_r) > 0 && ES_RECT_HEIGHT(_r) > 0)
#define ES_POINT(x, y) ((EsPoint) { (int32_t) (x), (int32_t) (y) })
#define EsKeyboardIsAltHeld() (EsKeyboardGetModifiers() & ES_MODIFIER_ALT)
#define EsKeyboardIsCtrlHeld() (EsKeyboardGetModifiers() & ES_MODIFIER_CTRL)
#define EsKeyboardIsShiftHeld() (EsKeyboardGetModifiers() & ES_MODIFIER_SHIFT)
#define ES_MEMORY_MOVE_BACKWARDS -
#define EsWaitSingle(object) EsWait(&object, 1, ES_WAIT_NO_TIMEOUT)
#define EsObjectUnmap EsMemoryUnreserve
#define EsLiteral(x) (char *) x, EsCStringLength((char *) x)
#define ES_STYLE_CAST(x) ((EsStyle *) (uintptr_t) (x))
#ifndef ES_INSTANCE_TYPE
#define ES_INSTANCE_TYPE struct EsInstance
#else
struct ES_INSTANCE_TYPE;
#endif
#ifdef __cplusplus
#define EsInstanceCreate(_message, ...) (static_cast<ES_INSTANCE_TYPE *>(_EsInstanceCreate(sizeof(ES_INSTANCE_TYPE), _message, __VA_ARGS__)))
#else
#define EsInstanceCreate(_message, ...) ((ES_INSTANCE_TYPE *) _EsInstanceCreate(sizeof(ES_INSTANCE_TYPE), _message, __VA_ARGS__))
#endif
#define ES_SAMPLE_FORMAT_BYTES_PER_SAMPLE(x) \
((x) == ES_SAMPLE_FORMAT_U8 ? 1 : (x) == ES_SAMPLE_FORMAT_S16LE ? 2 : 4)
#define ES_EXTRACT_BITS(value, end, start) (((value) >> (start)) & ((1 << ((end) - (start) + 1)) - 1)) // Moves the bits to the start.
#define ES_ISOLATE_BITS(value, end, start) (((value)) & (((1 << ((end) - (start) + 1)) - 1) << (start))) // Keeps the bits in place.
#ifndef KERNEL
#ifdef ES_API
ES_EXTERN_C uintptr_t _APISyscall(uintptr_t argument0, uintptr_t argument1, uintptr_t argument2, uintptr_t unused, uintptr_t argument3, uintptr_t argument4);
#define EsSyscall(a, b, c, d, e) _APISyscall((a), (b), (c), 0, (d), (e))
#define _EsSyscall _APISyscall
#else
#define EsSyscall(a, b, c, d, e) _EsSyscall((a), (b), (c), 0, (d), (e))
#endif
#endif
// --------- Algorithms:
#define ES_MACRO_SORT(_name, _type, _compar, _contextType) void _name(_type *base, size_t nmemb, _contextType context) { \
(void) context; \
if (nmemb <= 1) return; \
\
if (nmemb <= 16) { \
for (uintptr_t i = 1; i < nmemb; i++) { \
for (intptr_t j = i; j > 0; j--) { \
_type *_left = base + j, *_right = _left - 1; \
int result; _compar if (result >= 0) break; \
\
_type swap = base[j]; \
base[j] = base[j - 1]; \
base[j - 1] = swap; \
} \
} \
\
return; \
} \
\
intptr_t i = -1, j = nmemb; \
\
while (true) { \
_type *_left, *_right = base; \
int result; \
\
while (true) { _left = base + ++i; _compar if (result >= 0) break; } \
while (true) { _left = base + --j; _compar if (result <= 0) break; } \
\
if (i >= j) break; \
\
_type swap = base[i]; \
base[i] = base[j]; \
base[j] = swap; \
} \
\
_name(base, ++j, context); \
_name(base + j, nmemb - j, context); \
} \
#define ES_MACRO_SEARCH(_count, _compar, _result, _found) \
do { \
if (_count) { \
intptr_t low = 0; \
intptr_t high = _count - 1; \
\
while (low <= high) { \
uintptr_t index = ((high - low) >> 1) + low; \
int result; \
_compar \
\
if (result < 0) { \
high = index - 1; \
} else if (result > 0) { \
low = index + 1; \
} else { \
_result = index; \
_found = true; \
break; \
} \
} \
\
if (high < low) { \
_result = low; \
_found = false; \
} \
} else { \
_result = 0; \
_found = false; \
} \
} while (0)
// --------- Misc:
typedef uint64_t _EsLongConstant;
typedef long double EsLongDouble;
typedef const char *EsCString;
#ifndef ES_API
ES_EXTERN_C void _init();
ES_EXTERN_C void _start();
#endif
#define EsAssert(x) do { if (!(x)) { EsAssertionFailure(__FILE__, __LINE__); } } while (0)
#define EsCRTassert EsAssert
#define ES_INFINITY __builtin_inff()
#define ES_PI (3.1415926535897932384626433832795028841971693994)
// --------- Internal APIs:
#if defined(ES_API) || defined(KERNEL)
struct EsProcessStartupInformation {
bool isDesktop;
uintptr_t applicationStartAddress;
uintptr_t tlsImageStart;
uintptr_t tlsImageBytes;
uintptr_t tlsBytes; // All bytes after the image are to be zeroed.
};
struct _EsPOSIXSyscall {
intptr_t index;
intptr_t arguments[7];
};
#define BLEND_WINDOW_MATERIAL_NONE (0)
#define BLEND_WINDOW_MATERIAL_GLASS (1)
#define BLEND_WINDOW_MATERIAL_LIGHT_BLUR (2)
#ifdef ARCH_X86_64
#define BUNDLE_FILE_MAP_ADDRESS (0x100000000UL)
#endif
struct BundleHeader {
#define BUNDLE_SIGNATURE (0x63BDAF45)
uint32_t signature;
uint32_t version;
uint32_t fileCount;
uint32_t _unused;
uint64_t mapAddress;
};
struct BundleFile {
uint64_t nameCRC64;
uint64_t bytes;
uint64_t offset;
};
#ifdef KERNEL
#define K_BOOT_DRIVE ""
#else
#define K_BOOT_DRIVE "0:"
#endif
#define K_OS_FOLDER K_BOOT_DRIVE "/Essence"
#define K_DESKTOP_EXECUTABLE K_OS_FOLDER "/Desktop.esx"
#define K_SYSTEM_CONFIGURATION K_OS_FOLDER "/System Configuration.ini"
#define CREATION_ARGUMENT_MAIN (0)
#define CREATION_ARGUMENT_ENVIRONMENT (1)
#define CREATION_ARGUMENT_INITIAL_MOUNT_POINTS (2)
#define WINDOW_SET_BITS_NORMAL (0)
#define WINDOW_SET_BITS_SCROLL_HORIZONTAL (1)
#define WINDOW_SET_BITS_SCROLL_VERTICAL (2)
#define WINDOW_SET_BITS_AFTER_RESIZE (3)
#define SHUTDOWN_ACTION_POWER_OFF (1)
#define SHUTDOWN_ACTION_RESTART (2)
#ifdef __cplusplus
extern "C" const void *EsBufferRead(struct EsBuffer *buffer, size_t readBytes);
extern "C" const void *EsBufferReadMany(struct EsBuffer *buffer, size_t a, size_t b);
extern "C" void *EsBufferWrite(EsBuffer *buffer, const void *source, size_t writeBytes);
#define EsBuffer_MEMBER_FUNCTIONS \
inline const void *Read(size_t readBytes) { return EsBufferRead(this, readBytes); } \
inline const void *Read(size_t a, size_t b) { return EsBufferReadMany(this, a, b); } \
inline void *Write(const void *source, size_t writeBytes) { return EsBufferWrite(this, source, writeBytes); }
#endif
#define ES_POSIX_SYSCALL_GET_POSIX_FD_PATH (0x10000)
#endif
// --------- CRT function macros:
#ifdef ES_CRT_WITHOUT_PREFIX
#define abs EsCRTabs
#define acosf EsCRTacosf
#define asinf EsCRTasinf
#define assert EsCRTassert
#define atan2f EsCRTatan2f
#define atanf EsCRTatanf
#define atoi EsCRTatoi
#define bsearch EsCRTbsearch
#define calloc EsCRTcalloc
#define ceil EsCRTceil
#define ceilf EsCRTceilf
#define cosf EsCRTcosf
#define exp EsCRTexp
#define exp2f EsCRTexp2f
#define fabs EsCRTfabs
#define fabsf EsCRTfabsf
#define floor EsCRTfloor
#define floorf EsCRTfloorf
#define fmodf EsCRTfmodf
#define free EsCRTfree
#define getenv EsCRTgetenv
#define isalpha EsCRTisalpha
#define isdigit EsCRTisdigit
#define isnanf EsCRTisnanf
#define isspace EsCRTisspace
#define isupper EsCRTisupper
#define isxdigit EsCRTisxdigit
#define malloc EsCRTmalloc
#define memchr EsCRTmemchr
#define memcmp EsCRTmemcmp
#define memcpy EsCRTmemcpy
#define memmove EsCRTmemmove
#define memset EsCRTmemset
#define powf EsCRTpowf
#define qsort EsCRTqsort
#define rand EsCRTrand
#define realloc EsCRTrealloc
#define sinf EsCRTsinf
#define snprintf EsCRTsnprintf
#define sprintf EsCRTsprintf
#define sqrt EsCRTsqrt
#define sqrtf EsCRTsqrtf
#define strcat EsCRTstrcat
#define strchr EsCRTstrchr
#define strcmp EsCRTstrcmp
#define strcpy EsCRTstrcpy
#define strdup EsCRTstrdup
#define strerror EsCRTstrerror
#define strlen EsCRTstrlen
#define strncmp EsCRTstrncmp
#define strncpy EsCRTstrncpy
#define strnlen EsCRTstrnlen
#define strstr EsCRTstrstr
#define strtol EsCRTstrtol
#define strtoul EsCRTstrtoul
#define tolower EsCRTtolower
#define vsnprintf EsCRTvsnprintf
#endif

925
desktop/renderer.cpp Normal file
View File

@ -0,0 +1,925 @@
// TODO Fix glitches.
// TODO RAST_REPEAT_NORMAL is wrong with negative values.
#ifdef IN_DESIGNER
#define RAST_ARRAY(x) x *
#define RAST_ARRAY_ADD arrput
#define RAST_ARRAY_CLEAR arrclear
#define RAST_ARRAY_DELETE_SWAP arrdelswap
#define RAST_ARRAY_FREE arrfree
#define RAST_ARRAY_INSERT arrinsn
#define RAST_ARRAY_LAST arrlast
#define RAST_ARRAY_LENGTH arrlen
#define RAST_ARRAY_LENGTH_U arrlenu
#else
#define RAST_ARRAY(x) Array<x>
#define RAST_ARRAY_ADD(x, y) ((x).Add(y))
#define RAST_ARRAY_CLEAR(x) ((x).SetLength(0))
#define RAST_ARRAY_DELETE_SWAP(x, y) ((x).DeleteSwap(y))
#define RAST_ARRAY_FREE(x) ((x).Free())
#define RAST_ARRAY_INSERT(x, y, z) ((x).InsertMany(y, z))
#define RAST_ARRAY_LAST(x) ((x).Last())
#define RAST_ARRAY_LENGTH(x) ((intptr_t) (x).Length())
#define RAST_ARRAY_LENGTH_U(x) ((x).Length())
#endif
typedef struct RastVertex {
float x, y;
} RastVertex;
typedef struct RastEdge {
float xf, yf, xt, yt;
float dx, dy;
int sign;
} RastEdge;
typedef struct RastShape {
RAST_ARRAY(RastEdge) edges; /* sorted by yf; yf <= yt */
int left, right, top, bottom;
} RastShape;
typedef struct RastSurface {
uint32_t *buffer;
int width, height, stride;
float *area, *areaFill;
bool customBuffer;
} RastSurface;
typedef enum RastPaintType {
RAST_PAINT_NONE,
RAST_PAINT_SOLID,
RAST_PAINT_CHECKERBOARD,
RAST_PAINT_LINEAR_GRADIENT,
RAST_PAINT_RADIAL_GRADIENT,
RAST_PAINT_ANGULAR_GRADIENT,
RAST_PAINT_NOISE,
} RastPaintType;
typedef enum RastRepeatMode {
RAST_REPEAT_CLAMP,
RAST_REPEAT_NORMAL,
RAST_REPEAT_MIRROR,
} RastRepeatMode;
typedef struct RastPaint {
RastPaintType type;
union {
struct {
uint32_t color;
float alpha;
} solid;
struct {
uint32_t color1, color2;
float alpha1, alpha2;
int size;
} checkboard;
struct {
uint32_t *color;
float *alpha;
float transform[6];
RastRepeatMode repeatMode;
} gradient;
struct {
uint32_t color;
float minimum, maximum;
} noise;
};
} RastPaint;
typedef struct RastGradientStop {
uint32_t color;
float position;
} RastGradientStop;
typedef struct RastPathSegment {
uintptr_t uptoVertex;
} RastPathSegment;
typedef struct RastPath {
RAST_ARRAY(RastPathSegment) segments;
RAST_ARRAY(RastVertex) vertices;
} RastPath;
typedef enum RastLineJoinMode {
// Determines how convex segments are joined.
// Concave segments are always mitered with infinite limit.
RAST_LINE_JOIN_MITER, // Force bevels with miterLimit = 0.
RAST_LINE_JOIN_ROUND,
} RastLineJoinMode;
typedef enum RastLineCapMode {
RAST_LINE_CAP_FLAT,
RAST_LINE_CAP_SQUARE,
RAST_LINE_CAP_ROUND,
} RastLineCapMode;
typedef struct RastContourStyle {
float internalWidth, externalWidth;
RastLineJoinMode joinMode;
float miterLimit;
RastLineCapMode capMode;
// TODO Markers.
} RastContourStyle;
typedef struct RastDash {
float length, gap;
RastContourStyle *style;
} RastDash;
#define RAST_ADD_VERTEX_MINIMUM_DISTANCE_SQUARED (0.1f * 0.1f)
#define RAST_GRADIENT_COLORS (256)
#define RAST_ROUND_TOLERANCE (0.25f)
#define RAST_FLATTEN_TOLERANCE (0.25f)
#define RAST_GRADIENT_NOISE (0.005f)
#define RAST_AVERAGE_VERTICES(a, b) { ((a).x + (b).x) * 0.5f, ((a).y + (b).y) * 0.5f }
bool RastSurfaceInitialise(RastSurface *surface, int width, int height, bool customBuffer) {
surface->width = width;
surface->height = height;
surface->stride = 4 * width;
surface->area = (float *) EsHeapAllocate(sizeof(float) * width, true);
surface->areaFill = (float *) EsHeapAllocate(sizeof(float) * (width + 1), true);
surface->customBuffer = customBuffer;
if (!customBuffer) {
surface->buffer = (uint32_t *) EsHeapAllocate(4 * width * height, true);
}
return surface->buffer && surface->area && surface->areaFill;
}
void RastSurfaceDestroy(RastSurface *surface) {
EsHeapFree(surface->area);
EsHeapFree(surface->areaFill);
if (!surface->customBuffer) {
EsHeapFree(surface->buffer);
}
}
#ifndef IN_DESIGNER
ES_MACRO_SORT(RastEdgesSort, RastEdge, { result = _left->yf > _right->yf ? 1 : _left->yf < _right->yf ? -1 : 0; }, void *);
#else
int _RastEdgeCompare(const void *left, const void *right) {
const RastEdge *_left = (const RastEdge *) left;
const RastEdge *_right = (const RastEdge *) right;
return _left->yf > _right->yf ? 1 : _left->yf < _right->yf ? -1 : 0;
}
void RastEdgesSort(RastEdge *edges, size_t count, void *_unused) {
(void) _unused;
qsort(edges, count, sizeof(RastEdge), _RastEdgeCompare);
}
#endif
float _RastRepeat(RastRepeatMode mode, float p) {
if (mode == RAST_REPEAT_CLAMP) {
if (p < 0) return 0;
if (p > 1) return 1;
return p;
} else if (mode == RAST_REPEAT_MIRROR) {
p = EsCRTfabsf(EsCRTfmodf(p, 2.0f));
if (p > 1) return 2 - p;
return p;
} else {
return EsCRTfabsf(EsCRTfmodf(p, 1.0f));
}
}
void _RastShapeDestroy(RastShape shape) {
RAST_ARRAY_FREE(shape.edges);
}
void RastSurfaceFill(RastSurface surface, RastShape shape, RastPaint paint, bool evenOdd) {
if (paint.type == RAST_PAINT_LINEAR_GRADIENT
|| paint.type == RAST_PAINT_RADIAL_GRADIENT
|| paint.type == RAST_PAINT_ANGULAR_GRADIENT) {
if (!paint.gradient.color) {
_RastShapeDestroy(shape);
return;
}
}
RAST_ARRAY(RastEdge) active = { 0 };
int edgePosition = 0;
if (shape.left < 0) shape.left = 0;
if (shape.right > surface.width) shape.right = surface.width;
if (shape.top < 0) shape.top = 0;
if (shape.bottom > surface.height) shape.bottom = surface.height;
// Split edges that cross the left side of the shape.
int initialShapeEdges = RAST_ARRAY_LENGTH(shape.edges);
for (int i = 0; i < initialShapeEdges; i++) {
RastEdge *a = &shape.edges[i];
float y0 = a->yf;
float y1 = a->yt;
float x0 = a->xf;
float x1 = a->xt;
bool flipped = false;
if (x0 > x1) {
float t = x0;
x0 = x1, x1 = t;
flipped = true;
}
if (x0 < shape.left && x1 >= shape.left) {
RastEdge e = *a;
if (flipped) {
y1 += (shape.left - x0) * a->dy;
a->xt = e.xf = e.xt = shape.left;
e.yf = a->yt = y1;
e.dx = 0;
} else {
y0 += (shape.left - x0) * a->dy;
a->xt = e.xf = a->xf = shape.left;
e.yf = a->yt = y0;
a->dx = 0;
}
RAST_ARRAY_ADD(shape.edges, e);
}
}
RastEdgesSort(&shape.edges[0], RAST_ARRAY_LENGTH(shape.edges), NULL);
if (paint.type == RAST_PAINT_CHECKERBOARD && paint.checkboard.size < 1) paint.checkboard.size = 1;
for (int scanline = shape.top; scanline < shape.bottom; scanline++) {
// Remove edges above this scanline.
for (int i = 0; i < RAST_ARRAY_LENGTH(active); i++) {
if (active[i].yt < scanline) {
RAST_ARRAY_DELETE_SWAP(active, i);
i--;
}
}
// Add edges that start within this scanline.
while (edgePosition < RAST_ARRAY_LENGTH(shape.edges)) {
RastEdge *e = &shape.edges[edgePosition];
if (e->yf < scanline + 1.0f) {
RAST_ARRAY_ADD(active, *e);
edgePosition++;
} else {
break;
}
}
// If there are no active edges, don't process the scanline.
if (!RAST_ARRAY_LENGTH(active)) {
continue;
}
// Calculate the signed area covered by each active edge.
for (int i = 0; i < RAST_ARRAY_LENGTH(active); i++) {
RastEdge *a = &active[i];
// Calculate the range of pixels the edge crosses on the scanline.
float top = scanline, bottom = scanline + 1;
float y0 = (a->yf > top) ? a->yf : top;
float y1 = (a->yt < bottom) ? a->yt : bottom;
float x0 = (y0 - a->yf) * a->dx + a->xf;
float x1 = (y1 - a->yf) * a->dx + a->xf;
float dy = a->dy;
bool flipped = false;
if (x1 < x0) {
// Convert NE-SW edge to NW-SE.
// Flipping the edge preserves signed area.
float t = y0;
y0 = top + bottom - y1;
y1 = top + bottom - t;
t = x0, x0 = x1, x1 = t;
dy = -dy;
flipped = true;
}
if (x1 < shape.left) {
x0 = x1 = shape.left;
} else if (x0 < shape.left) {
y0 += (shape.left - x0) * dy;
x0 = shape.left;
}
if (x1 >= shape.right) {
y1 += (x1 - shape.right + 1) * dy;
x1 = shape.right - 1;
}
if (y1 <= y0 || x0 >= shape.right || x1 < shape.left || EsCRTisnanf(x0) || EsCRTisnanf(x1) || EsCRTisnanf(y0) || EsCRTisnanf(y1)) {
continue;
}
if (EsCRTfloorf(x0) == EsCRTfloorf(x1)) {
// Edge crosses one pixel on this scanline,
// forming a trapezium.
float right = EsCRTfloorf(x0 + 1);
float p = a->sign * (y1 - y0);
int xs = (int) x0;
surface.area[xs] += p * (right - x0 + right - x1) * 0.5f;
surface.areaFill[xs + 1] += p;
} else {
// Edge crosses multiple pixels on this scanline.
// The first pixel is a triangle.
float tx = EsCRTfloorf(x0 + 1);
float th = dy * (tx - x0);
int xs = (int) x0, xf = (int) x1;
surface.area[xs] += a->sign * (tx - x0) * th * 0.5f;
// The middle pixels are trapeziums.
float pa = th + 0.5f * dy;
for (int j = xs + 1; j < xf; j++, pa += dy) surface.area[j] += a->sign * pa;
// The final pixel is a removed triangle.
float p = a->sign * (y1 - y0);
float fx = EsCRTfloorf(x1);
float fy = a->yf + a->dy * (fx - a->xf);
if (flipped) fy = top + bottom - fy;
surface.area[xf] += p - a->sign * 0.5f * (x1 - fx) * (y1 - fy);
surface.areaFill[xf + 1] += p;
}
}
// Calculate the final coverage of each pixel.
float cumulativeArea = 0;
uint32_t *destination = (uint32_t *) ((uint8_t *) surface.buffer + scanline * surface.stride + 4 * shape.left);
float textureDX = paint.gradient.transform[0];
float texturePX = shape.left * textureDX + (float) scanline * paint.gradient.transform[1] + paint.gradient.transform[2];
float textureDY = paint.gradient.transform[3];
float texturePY = shape.left * textureDY + (float) scanline * paint.gradient.transform[4] + paint.gradient.transform[5];
if (paint.type == RAST_PAINT_NOISE) {
textureDX = 1;
texturePX = 0;
}
for (int i = shape.left; i < shape.right; i++) {
cumulativeArea += surface.areaFill[i];
float a = surface.area[i] + cumulativeArea;
if (evenOdd) {
a = EsCRTfmodf(AbsoluteFloat(a), 2);
if (a > 1) a = 2 - a;
} else {
if (a < 0) a = -a;
if (a > 1) a = 1;
}
if (a > 0.0039f) {
if (paint.type == RAST_PAINT_SOLID) {
uint8_t c = (uint8_t) (a * paint.solid.alpha * 255.0f);
if (c) BlendPixel(destination, (c << 24) | paint.solid.color, true);
} else if (paint.type == RAST_PAINT_CHECKERBOARD) {
if (((i - shape.left) / paint.checkboard.size + (scanline - shape.top) / paint.checkboard.size) & 1) {
uint8_t c = (uint8_t) (a * paint.checkboard.alpha2 * 255.0f);
if (c) BlendPixel(destination, (c << 24) | paint.checkboard.color2, true);
} else {
uint8_t c = (uint8_t) (a * paint.checkboard.alpha1 * 255.0f);
if (c) BlendPixel(destination, (c << 24) | paint.checkboard.color1, true);
}
} else if (paint.type == RAST_PAINT_LINEAR_GRADIENT) {
float p = _RastRepeat(paint.gradient.repeatMode, texturePX);
int pi = (int) ((RAST_GRADIENT_COLORS - 1) * p);
EsAssert(pi >= 0 && pi < RAST_GRADIENT_COLORS); // Invalid gradient index.
uint8_t c = (uint8_t) (a * paint.gradient.alpha[pi] * 255.0f);
if (c) BlendPixel(destination, (c << 24) | paint.gradient.color[pi], true);
} else if (paint.type == RAST_PAINT_RADIAL_GRADIENT) {
float p = EsCRTsqrtf(texturePX * texturePX + texturePY * texturePY);
if (p < 0) p = 0;
if (p > 1) p = 1;
int pi = (int) ((RAST_GRADIENT_COLORS - 1) * p);
uint8_t c = (uint8_t) (a * paint.gradient.alpha[pi] * 255.0f);
if (c) BlendPixel(destination, (c << 24) | paint.gradient.color[pi], true);
} else if (paint.type == RAST_PAINT_ANGULAR_GRADIENT) {
float p = EsCRTatan2f(texturePY, texturePX) * 0.159154943091f + 0.5f;
if (p < 0) p = 0;
if (p > 1) p = 1;
int pi = (int) ((RAST_GRADIENT_COLORS - 1) * p);
uint8_t c = (uint8_t) (a * paint.gradient.alpha[pi] * 255.0f);
if (c) BlendPixel(destination, (c << 24) | paint.gradient.color[pi], true);
} else if (paint.type == RAST_PAINT_NOISE) {
union { float f; uint32_t u; } noise = { texturePX + texturePY };
noise.u += noise.u << 10;
noise.u ^= noise.u >> 6;
noise.u += noise.u << 3;
noise.u ^= noise.u >> 11;
noise.u += noise.u << 15;
noise.u &= 0x7FFFFF;
noise.u |= 0x3F800000;
noise.f /= 2;
noise.f *= paint.noise.maximum - paint.noise.minimum;
noise.f += paint.noise.minimum;
uint8_t c = (uint8_t) (a * noise.f * 255.0f);
if (c) BlendPixel(destination, (c << 24) | paint.noise.color, true);
}
}
surface.area[i] = surface.areaFill[i] = 0;
destination++;
texturePX += textureDX;
texturePY += textureDY;
}
surface.areaFill[shape.right] = 0;
}
RAST_ARRAY_FREE(active);
_RastShapeDestroy(shape);
}
void _RastShapeAddEdges(RastShape *shape, RastVertex *vertices, int sign, bool open, int vertexCount) {
if (vertexCount <= 1) return;
for (int i = 0; i < vertexCount - (open ? 1 : 0); i++) {
RastVertex *from = &vertices[i];
RastVertex *to = &vertices[(i + 1) % vertexCount];
if (from->y == to->y) continue;
int p = RAST_ARRAY_LENGTH(shape->edges);
RAST_ARRAY_INSERT(shape->edges, p, 1);
RastEdge *edge = &shape->edges[p];
if (from->y < to->y) {
edge->xf = from->x, edge->yf = from->y;
edge->xt = to->x, edge->yt = to->y;
edge->sign = sign;
} else {
edge->xf = to->x, edge->yf = to->y;
edge->xt = from->x, edge->yt = from->y;
edge->sign = -sign;
}
edge->dx = (edge->xt - edge->xf) / (edge->yt - edge->yf);
edge->dy = 1.0f / edge->dx;
if (edge->xf < shape->left) shape->left = edge->xf;
if (edge->xt < shape->left) shape->left = edge->xt;
if (edge->yf < shape->top) shape->top = edge->yf;
if (edge->xf + 1 > shape->right) shape->right = edge->xf + 1;
if (edge->xt + 1 > shape->right) shape->right = edge->xt + 1;
if (edge->yt + 1 > shape->bottom) shape->bottom = edge->yt + 1;
}
}
void RastPathCloseSegment(RastPath *path) {
if (!RAST_ARRAY_LENGTH_U(path->segments) || RAST_ARRAY_LAST(path->segments).uptoVertex != RAST_ARRAY_LENGTH_U(path->vertices)) {
RastPathSegment segment = {};
segment.uptoVertex = RAST_ARRAY_LENGTH_U(path->vertices);
RAST_ARRAY_ADD(path->segments, segment);
}
}
RastShape RastShapeCreateSolid(RastPath *path) {
if (RAST_ARRAY_LENGTH(path->vertices) < 3) return (RastShape) { 0 };
RastPathCloseSegment(path);
RastShape shape = { .left = INT_MAX, .top = INT_MAX };
uintptr_t start = 0;
for (uintptr_t i = 0; i < RAST_ARRAY_LENGTH_U(path->segments); i++) {
_RastShapeAddEdges(&shape, &path->vertices[start], 1, false, path->segments[i].uptoVertex - start);
start = path->segments[i].uptoVertex;
}
return shape;
}
void _RastJoinMeter(RAST_ARRAY(RastVertex) *output, RastVertex *from1, RastVertex *to1, RastVertex *from2, RastVertex *to2, RastVertex *point, float miterLimit) {
// TODO For internal contours, this can generate vertices forming anticlockwise regions if the contour width is large enough.
// (You can't see it though because we use a non-zero fill rule.)
float a = to1->x - from1->x, b = from2->x - to2->x, e = from2->x - from1->x;
float c = to1->y - from1->y, d = from2->y - to2->y, f = from2->y - from1->y;
float j = (d * e - b * f) / (d * a - b * c);
RastVertex v = { from1->x + j * (to1->x - from1->x), from1->y + j * (to1->y - from1->y) };
if ((v.x - point->x) * (v.x - point->x) + (v.y - point->y) * (v.y - point->y) <= miterLimit * miterLimit) {
RAST_ARRAY_ADD(*output, v);
} else {
// TODO Move bevel to miter limit.
RAST_ARRAY_ADD(*output, *to1);
RAST_ARRAY_ADD(*output, *from2);
}
}
void _RastJoinArc(RAST_ARRAY(RastVertex) *output, float fromAngle, float toAngle, int divisions, RastVertex *point, float width) {
for (int j = 0; j < divisions; j++) {
float angle = fromAngle + j * (toAngle - fromAngle) / (divisions - 1);
RastVertex v = { point->x + EsCRTcosf(angle) * width, point->y + EsCRTsinf(angle) * width };
if (j == 0 || j == divisions - 1) {
RAST_ARRAY_ADD(*output, v);
continue;
}
RastVertex l = RAST_ARRAY_LAST(*output);
int dx = v.x - l.x, dy = v.y - l.y;
if (dx * dx + dy * dy > RAST_ADD_VERTEX_MINIMUM_DISTANCE_SQUARED) {
RAST_ARRAY_ADD(*output, v);
}
}
}
void _RastJoinRound(RAST_ARRAY(RastVertex) *output, RastVertex *from1, RastVertex *to1, RastVertex *from2,
RastVertex *to2, RastVertex *point, float width, int divisions, bool internal) {
float fromAngle = EsCRTatan2f(to1->y - point->y, to1->x - point->x);
float toAngle = EsCRTatan2f(from2->y - point->y, from2->x - point->x);
if (toAngle > fromAngle) {
toAngle -= 6.2831853071f;
}
if (internal == (fromAngle - toAngle < 3.141592653f)) {
_RastJoinMeter(output, from1, to1, from2, to2, point, ES_INFINITY);
return;
}
if (internal) {
toAngle += 6.2831853071f;
}
_RastJoinArc(output, fromAngle, toAngle, divisions, point, width);
}
void _RastShapeCreateContour(RastShape *shape, RastPath *path, RastContourStyle style, bool open) {
RAST_ARRAY(RastVertex) vertices = path->vertices;
size_t vertexCount = RAST_ARRAY_LENGTH(vertices);
if (vertexCount < 2) {
return;
}
if (!open) {
RastVertex first = vertices[0], last = RAST_ARRAY_LAST(vertices);
float dx = first.x - last.x, dy = first.y - last.y;
if (dx * dx + dy * dy < RAST_ADD_VERTEX_MINIMUM_DISTANCE_SQUARED) {
vertexCount--;
}
}
if (vertexCount <= 1) {
return;
} else if (vertexCount == 2) {
open = true;
}
RastVertex cap1, cap2, cap3, cap4;
RAST_ARRAY(RastVertex) path1 = { 0 };
RAST_ARRAY(RastVertex) path2 = { 0 };
for (int internal = 0; internal < 2; internal++) {
float width = internal ? style.internalWidth : style.externalWidth;
RastVertex *capStart = internal ? &cap4 : &cap1;
RastVertex *capEnd = internal ? &cap3 : &cap2;
for (uintptr_t i = 0; i < vertexCount; i++) {
RastVertex *from = &vertices[(i + 0) % vertexCount],
*to = &vertices[(i + 1) % vertexCount];
float dx = to->x - from->x, dy = to->y - from->y;
float scale = (internal ? -width : width) / EsCRTsqrtf(dx * dx + dy * dy);
float ox = -dy * scale, oy = dx * scale;
RastVertex cf = { from->x + ox, from->y + oy };
RastVertex ct = { to->x + ox, to->y + oy };
RAST_ARRAY_ADD(path1, cf);
RAST_ARRAY_ADD(path1, ct);
}
if (open) RAST_ARRAY_ADD(path2, (*capStart = path1[0]));
if (style.joinMode == RAST_LINE_JOIN_MITER) {
for (int i = 0; i < RAST_ARRAY_LENGTH(path1) - (open ? 4 : 0); i += 2) {
RastVertex *p1 = &vertices[(i / 2 + 0) % vertexCount],
*p2 = &vertices[(i / 2 + 1) % vertexCount],
*p3 = &vertices[(i / 2 + 2) % vertexCount];
float cross = (p2->x - p1->x) * (p3->y - p2->y) - (p2->y - p1->y) * (p3->x - p2->x);
_RastJoinMeter(&path2, &path1[i], &path1[i + 1], &path1[(i + 2) % RAST_ARRAY_LENGTH(path1)],
&path1[(i + 3) % RAST_ARRAY_LENGTH(path1)], &vertices[(i / 2 + 1) % vertexCount],
(internal ? (cross > 0) : (cross < 0)) ? style.miterLimit : ES_INFINITY);
}
} else if (style.joinMode == RAST_LINE_JOIN_ROUND) {
int divisions = EsCRTceilf(3.14159265358f * 0.5f / EsCRTacosf(width / (width + RAST_ROUND_TOLERANCE))) + 1;
for (int i = 0; i < RAST_ARRAY_LENGTH(path1) - (open ? 4 : 0); i += 2) {
_RastJoinRound(&path2, &path1[i], &path1[i + 1], &path1[(i + 2) % RAST_ARRAY_LENGTH(path1)],
&path1[(i + 3) % RAST_ARRAY_LENGTH(path1)], &vertices[(i / 2 + 1) % vertexCount], width, divisions, internal);
}
}
if (open) RAST_ARRAY_ADD(path2, (*capEnd = path1[RAST_ARRAY_LENGTH(path1) - 3]));
_RastShapeAddEdges(shape, &path2[0], internal ? -1 : 1, open, RAST_ARRAY_LENGTH(path2));
RAST_ARRAY_CLEAR(path1);
RAST_ARRAY_CLEAR(path2);
}
if (open) {
// TODO Cap styles don't work if the contour is not centered (i.e. internalWidth != externalWidth).
for (int i = 0; i < 2; i++) {
RastVertex c = i ? cap4 : cap2, d = i ? cap1 : cap3;
RastVertex *from = &vertices[(i ? 1 : (vertexCount - 2))];
RastVertex *to = &vertices[(i ? 0 : (vertexCount - 1))];
RAST_ARRAY_ADD(path1, c);
if (style.capMode == RAST_LINE_CAP_SQUARE) {
float dx = to->x - from->x, dy = to->y - from->y;
float scale = 0.5f * (style.internalWidth + style.externalWidth) / EsCRTsqrtf(dx * dx + dy * dy);
RastVertex c0 = { .x = c.x + scale * dx, .y = c.y + scale * dy };
RastVertex d0 = { .x = d.x + scale * dx, .y = d.y + scale * dy };
RAST_ARRAY_ADD(path1, c0);
RAST_ARRAY_ADD(path1, d0);
} else if (style.capMode == RAST_LINE_CAP_ROUND) {
float angle = EsCRTatan2f(d.y - to->y, d.x - to->x);
float width = 0.5f * (style.internalWidth + style.externalWidth);
int divisions = EsCRTceilf(3.14159265358f * 0.5f / EsCRTacosf(width / (width + RAST_ROUND_TOLERANCE))) + 1;
_RastJoinArc(&path1, angle + 3.14159265358f, angle, divisions, to, width);
}
RAST_ARRAY_ADD(path1, d);
_RastShapeAddEdges(shape, &path1[0], 1, true, RAST_ARRAY_LENGTH(path1));
RAST_ARRAY_CLEAR(path1);
}
}
RAST_ARRAY_FREE(path1);
RAST_ARRAY_FREE(path2);
}
RastShape RastShapeCreateContour(RastPath *path, RastContourStyle style, bool open) {
RastShape shape = { .left = INT_MAX, .top = INT_MAX };
_RastShapeCreateContour(&shape, path, style, open);
if (RAST_ARRAY_LENGTH(shape.edges) == 0) return (RastShape) { 0 };
return shape;
}
void _RastPathAddVertex(RastPath *path, RastVertex vertex) {
if (RAST_ARRAY_LENGTH(path->vertices)) {
RastVertex last = RAST_ARRAY_LAST(path->vertices);
float dx = last.x - vertex.x, dy = last.y - vertex.y;
if (dx * dx + dy * dy < RAST_ADD_VERTEX_MINIMUM_DISTANCE_SQUARED) {
return;
}
}
RAST_ARRAY_ADD(path->vertices, vertex);
}
RastShape RastShapeCreateDashed(RastPath *path, RastDash *dashStyles, size_t dashStyleCount, bool open) {
RAST_ARRAY(RastVertex) vertices = path->vertices;
size_t vertexCount = RAST_ARRAY_LENGTH(vertices);
if (dashStyleCount < 1 || vertexCount < 2) {
return (RastShape) { 0 };
}
RastDash *style = dashStyles + 0;
RastVertex from = vertices[0];
RastVertex *to = &vertices[1];
RastPath dash = {};
RastShape shape = { .left = INT_MAX, .top = INT_MAX };
if (!open) {
from = RAST_ARRAY_LAST(vertices);
to = &vertices[0];
}
while (to != &vertices[vertexCount]) {
float accumulatedLength = 0;
_RastPathAddVertex(&dash, from);
while (to != &vertices[vertexCount]) {
float dx = to->x - from.x, dy = to->y - from.y;
float distance = EsCRTsqrtf(dx * dx + dy * dy);
if (accumulatedLength + distance >= style->length) {
float fraction = (style->length - accumulatedLength) / distance;
RastVertex stop = { from.x + fraction * dx, from.y + fraction * dy };
_RastPathAddVertex(&dash, stop);
from = stop;
break;
}
accumulatedLength += distance;
from = *to;
_RastPathAddVertex(&dash, from);
to++;
}
_RastShapeCreateContour(&shape, &dash, *style->style, true);
accumulatedLength = 0;
while (to != &vertices[vertexCount]) {
float dx = to->x - from.x, dy = to->y - from.y;
float distance = EsCRTsqrtf(dx * dx + dy * dy);
if (accumulatedLength + distance >= style->gap) {
float fraction = (style->gap - accumulatedLength) / distance;
RastVertex stop = { from.x + fraction * dx, from.y + fraction * dy };
from = stop;
break;
}
accumulatedLength += distance;
from = *to;
to++;
}
style++;
if (style == dashStyles + dashStyleCount) {
style = dashStyles + 0;
}
RAST_ARRAY_CLEAR(dash.vertices);
}
return shape;
}
RastVertex _RastVertexScale(RastVertex vertex, RastVertex scale) {
return (RastVertex) { vertex.x * scale.x, vertex.y * scale.y };
}
void _RastFlattenBezierRecursive(RastPath *path, RastVertex v1, RastVertex v2, RastVertex v3, RastVertex v4, int level) {
if (level > 8) return;
RastVertex v12 = RAST_AVERAGE_VERTICES(v1, v2);
RastVertex v23 = RAST_AVERAGE_VERTICES(v2, v3);
RastVertex v34 = RAST_AVERAGE_VERTICES(v3, v4);
RastVertex delta = { v4.x - v1.x, v4.y - v1.y };
float d = AbsoluteFloat((v2.x - v4.x) * delta.y + (v4.y - v2.y) * delta.x)
+ AbsoluteFloat((v3.x - v4.x) * delta.y + (v4.y - v3.y) * delta.x);
if (d * d < RAST_FLATTEN_TOLERANCE * (delta.x * delta.x + delta.y * delta.y)) {
_RastPathAddVertex(path, v4);
return;
}
RastVertex v123 = RAST_AVERAGE_VERTICES(v12, v23);
RastVertex v234 = RAST_AVERAGE_VERTICES(v23, v34);
RastVertex v1234 = RAST_AVERAGE_VERTICES(v123, v234);
_RastFlattenBezierRecursive(path, v1, v12, v123, v1234, level + 1);
_RastFlattenBezierRecursive(path, v1234, v234, v34, v4, level + 1);
}
void RastPathAppendBezier(RastPath *path, const RastVertex *vertices, size_t vertexCount, RastVertex scale) {
if (vertexCount < 4) return;
_RastPathAddVertex(path, _RastVertexScale(vertices[0], scale));
for (uintptr_t i = 0; i < vertexCount - 3; i += 3) {
// TODO Scale the control points such that the center of the curve is aligned?
_RastFlattenBezierRecursive(path, _RastVertexScale(vertices[i + 0], scale), _RastVertexScale(vertices[i + 1], scale),
_RastVertexScale(vertices[i + 2], scale), _RastVertexScale(vertices[i + 3], scale), 0);
}
}
void RastPathAppendLinear(RastPath *path, RastVertex *vertices, size_t vertexCount, RastVertex scale) {
if (!vertexCount) return;
for (uintptr_t i = 0; i < vertexCount; i++) {
_RastPathAddVertex(path, _RastVertexScale(vertices[i], scale));
}
}
void RastPathTranslate(RastPath *path, float x, float y) {
if (!x && !y) return;
for (uintptr_t i = 0; i < RAST_ARRAY_LENGTH_U(path->vertices); i++) {
path->vertices[i].x += x;
path->vertices[i].y += y;
}
}
void RastPathTransform(RastPath *path, float *matrix) {
for (uintptr_t i = 0; i < RAST_ARRAY_LENGTH_U(path->vertices); i++) {
float x = path->vertices[i].x, y = path->vertices[i].y;
path->vertices[i].x = matrix[0] * x + matrix[1] * y + matrix[2];
path->vertices[i].y = matrix[3] * x + matrix[4] * y + matrix[5];
}
}
void RastPathDestroy(RastPath *path) {
RAST_ARRAY_FREE(path->segments);
RAST_ARRAY_FREE(path->vertices);
}
float _RastInterpolateWithGamma(float from, float to, float progress) {
from = from * from;
to = to * to;
return EsCRTsqrtf(from + progress * (to - from));
}
float _RastInterpolateSimple(float from, float to, float progress) {
return from + progress * (to - from);
}
void RastGradientInitialise(RastPaint *paint, RastGradientStop *stops, size_t stopCount, bool useGammaInterpolation) {
if (!stopCount) {
return;
}
paint->gradient.color = (uint32_t *) EsHeapAllocate(4 * RAST_GRADIENT_COLORS, false);
paint->gradient.alpha = (float *) EsHeapAllocate(sizeof(float) * RAST_GRADIENT_COLORS, false);
for (uintptr_t stop = 0; stop < stopCount - 1; stop++) {
float fa = ((stops[stop + 0].color >> 24) & 0xFF) / 255.0f;
float fb = ((stops[stop + 0].color >> 16) & 0xFF) / 255.0f;
float fg = ((stops[stop + 0].color >> 8) & 0xFF) / 255.0f;
float fr = ((stops[stop + 0].color >> 0) & 0xFF) / 255.0f;
float ta = ((stops[stop + 1].color >> 24) & 0xFF) / 255.0f;
float tb = ((stops[stop + 1].color >> 16) & 0xFF) / 255.0f;
float tg = ((stops[stop + 1].color >> 8) & 0xFF) / 255.0f;
float tr = ((stops[stop + 1].color >> 0) & 0xFF) / 255.0f;
int fi = RAST_GRADIENT_COLORS * (stop == 0 ? 0 : stops[stop + 0].position);
int ti = RAST_GRADIENT_COLORS * (stop == stopCount - 2 ? 1 : stops[stop + 1].position);
if (fi < 0) fi = 0;
if (ti > RAST_GRADIENT_COLORS) ti = RAST_GRADIENT_COLORS;
for (int i = fi; i < ti; i++) {
float p = (float) (i - fi) / (ti - fi);
paint->gradient.alpha[i] = fa + (ta - fa) * p;
if (useGammaInterpolation) {
paint->gradient.color[i] = (uint32_t) (_RastInterpolateWithGamma(fr, tr, p) * 255.0f) << 0
| (uint32_t) (_RastInterpolateWithGamma(fg, tg, p) * 255.0f) << 8
| (uint32_t) (_RastInterpolateWithGamma(fb, tb, p) * 255.0f) << 16;
} else {
paint->gradient.color[i] = (uint32_t) (_RastInterpolateSimple(fr, tr, p) * 255.0f) << 0
| (uint32_t) (_RastInterpolateSimple(fg, tg, p) * 255.0f) << 8
| (uint32_t) (_RastInterpolateSimple(fb, tb, p) * 255.0f) << 16;
}
}
}
}
void RastGradientDestroy(RastPaint *paint) {
if (paint->type == RAST_PAINT_LINEAR_GRADIENT
|| paint->type == RAST_PAINT_RADIAL_GRADIENT
|| paint->type == RAST_PAINT_ANGULAR_GRADIENT) {
EsHeapFree(paint->gradient.color);
EsHeapFree(paint->gradient.alpha);
}
}
#ifndef IN_DESIGNER
void EsDrawLine(EsPainter *painter, float *vertices, size_t vertexCount, uint32_t color, float width, uint32_t flags) {
RastSurface surface = {};
surface.buffer = (uint32_t *) painter->target->bits;
surface.stride = painter->target->stride;
if (RastSurfaceInitialise(&surface, painter->target->width, painter->target->height, true)) {
RastPath path = {};
RastPathAppendLinear(&path, (RastVertex *) vertices, vertexCount, { 1, 1 });
RastContourStyle style = {};
style.externalWidth = width / 2.0f;
style.internalWidth = width / 2.0f;
style.capMode = (flags & ES_DRAW_LINE_CAP_ROUND) ? RAST_LINE_CAP_ROUND
: (flags & ES_DRAW_LINE_CAP_SQUARE) ? RAST_LINE_CAP_SQUARE
: RAST_LINE_CAP_FLAT;
RastShape shape = RastShapeCreateContour(&path, style, true);
RastPaint paint = {};
paint.type = RAST_PAINT_SOLID;
paint.solid.color = color & 0xFFFFFF;
paint.solid.alpha = (color >> 24) / 255.0f;
RastSurfaceFill(surface, shape, paint, false);
RastPathDestroy(&path);
}
RastSurfaceDestroy(&surface);
}
#endif

113
desktop/styles.header Normal file
View File

@ -0,0 +1,113 @@
define_private ES_STYLE__TEST_STYLE (ES_STYLE_CAST(1385))
define_private ES_STYLE_ACCESS_KEY_HINT (ES_STYLE_CAST(1221))
define_private ES_STYLE_ANNOUNCEMENT (ES_STYLE_CAST(1511))
define_private ES_STYLE_BREADCRUMB_BAR_CRUMB (ES_STYLE_CAST(1223))
define_private ES_STYLE_BREADCRUMB_BAR_OVERFLOW_ICON (ES_STYLE_CAST(1225))
define_private ES_STYLE_BREADCRUMB_BAR_PANEL (ES_STYLE_CAST(1227))
define ES_STYLE_BUTTON_GROUP_CONTAINER (ES_STYLE_CAST(1229))
define ES_STYLE_BUTTON_GROUP_ITEM (ES_STYLE_CAST(1231))
define ES_STYLE_BUTTON_GROUP_SEPARATOR (ES_STYLE_CAST(1233))
define_private ES_STYLE_CANVAS_SHADOW (ES_STYLE_CAST(1451))
define_private ES_STYLE_CHECKBOX_NORMAL (ES_STYLE_CAST(1235))
define_private ES_STYLE_CHECKBOX_RADIOBOX (ES_STYLE_CAST(1237))
define_private ES_STYLE_COLOR_CHOSEN_POINT (ES_STYLE_CAST(1241))
define_private ES_STYLE_COLOR_CIRCLE (ES_STYLE_CAST(1243))
define_private ES_STYLE_COLOR_HEX_TEXTBOX (ES_STYLE_CAST(1245))
define_private ES_STYLE_COLOR_PICKER_MAIN_PANEL (ES_STYLE_CAST(1247))
define_private ES_STYLE_COLOR_SLIDER (ES_STYLE_CAST(1249))
define_private ES_STYLE_CONTAINER_WINDOW_ACTIVE (ES_STYLE_CAST(1251))
define_private ES_STYLE_CONTAINER_WINDOW_INACTIVE (ES_STYLE_CAST(1255))
define ES_STYLE_DIALOG_BUTTON_AREA (ES_STYLE_CAST(1259))
define ES_STYLE_DIALOG_CONTENT (ES_STYLE_CAST(1261))
define ES_STYLE_DIALOG_HEADING (ES_STYLE_CAST(1263))
define ES_STYLE_ICON_DISPLAY (ES_STYLE_CAST(1265))
define ES_STYLE_ICON_DISPLAY_SMALL (ES_STYLE_CAST(1543))
define ES_STYLE_INSTALLER_ROOT (ES_STYLE_CAST(1267))
define ES_STYLE_LIST_CHOICE_BORDERED (ES_STYLE_CAST(1429))
define ES_STYLE_LIST_CHOICE_ITEM (ES_STYLE_CAST(1435))
define_private ES_STYLE_LIST_COLUMN_HEADER (ES_STYLE_CAST(1269))
define_private ES_STYLE_LIST_COLUMN_HEADER_ITEM (ES_STYLE_CAST(1271))
define_private ES_STYLE_LIST_COLUMN_HEADER_ITEM_HAS_MENU (ES_STYLE_CAST(1273))
define_private ES_STYLE_LIST_COLUMN_HEADER_SPLITTER (ES_STYLE_CAST(1275))
define_private ES_STYLE_LIST_GROUP_HEADER_CELL (ES_STYLE_CAST(1277))
define ES_STYLE_LIST_ITEM (ES_STYLE_CAST(1279))
define ES_STYLE_LIST_ITEM_GROUP_FOOTER (ES_STYLE_CAST(1281))
define ES_STYLE_LIST_ITEM_GROUP_HEADER (ES_STYLE_CAST(1283))
define ES_STYLE_LIST_ITEM_TILE (ES_STYLE_CAST(1285))
define_private ES_STYLE_LIST_PRIMARY_CELL (ES_STYLE_CAST(1287))
define_private ES_STYLE_LIST_SECONDARY_CELL (ES_STYLE_CAST(1289))
define_private ES_STYLE_LIST_SELECTION_BOX (ES_STYLE_CAST(1291))
define ES_STYLE_LIST_VIEW (ES_STYLE_CAST(1293))
define ES_STYLE_LIST_VIEW_BORDERED (ES_STYLE_CAST(1295))
define ES_STYLE_LIST_DISPLAY_DEFAULT (ES_STYLE_CAST(1441))
define_private ES_STYLE_MARKER_DOWN_ARROW (ES_STYLE_CAST(1297))
define_private ES_STYLE_MARKER_UP_ARROW (ES_STYLE_CAST(1501))
define_private ES_STYLE_MENU_ITEM_HEADER (ES_STYLE_CAST(1299))
define_private ES_STYLE_MENU_ITEM_NORMAL (ES_STYLE_CAST(1301))
define_private ES_STYLE_MENU_SEPARATOR_HORIZONTAL (ES_STYLE_CAST(1303))
define_private ES_STYLE_MENU_SEPARATOR_VERTICAL (ES_STYLE_CAST(1305))
define_private ES_STYLE_PANEL_CONTAINER_WINDOW_ROOT (ES_STYLE_CAST(1307))
define_private ES_STYLE_PANEL_CRASH_INFO (ES_STYLE_CAST(1309))
define ES_STYLE_PANEL_DIALOG_ROOT (ES_STYLE_CAST(1311))
define ES_STYLE_PANEL_DOCUMENT (ES_STYLE_CAST(1547))
define ES_STYLE_PANEL_FILLED (ES_STYLE_CAST(1313))
define ES_STYLE_PANEL_GROUP_BOX (ES_STYLE_CAST(1315))
define_private ES_STYLE_PANEL_INSPECTOR_WINDOW_CONTAINER (ES_STYLE_CAST(1317))
define_private ES_STYLE_PANEL_INSPECTOR_WINDOW_ROOT (ES_STYLE_CAST(1319))
define_private ES_STYLE_PANEL_MENU_COLUMN (ES_STYLE_CAST(1321))
define_private ES_STYLE_PANEL_MENU_CONTAINER (ES_STYLE_CAST(1323))
define_private ES_STYLE_PANEL_MENU_ROOT (ES_STYLE_CAST(1325))
define_private ES_STYLE_PANEL_MODAL_OVERLAY (ES_STYLE_CAST(1327))
define_private ES_STYLE_PANEL_NORMAL_WINDOW_ROOT (ES_STYLE_CAST(1329))
define ES_STYLE_PANEL_POPUP (ES_STYLE_CAST(1331))
define ES_STYLE_PANEL_SHEET (ES_STYLE_CAST(1333))
define_private ES_STYLE_PANEL_SHUTDOWN_OVERLAY (ES_STYLE_CAST(1335))
define ES_STYLE_PANEL_STATUS_BAR (ES_STYLE_CAST(1489))
define ES_STYLE_PANEL_TOOLBAR (ES_STYLE_CAST(1337))
define ES_STYLE_PANEL_TOOLBAR_ROOT (ES_STYLE_CAST(1339))
define ES_STYLE_PANEL_WINDOW_BACKGROUND (ES_STYLE_CAST(1341))
define ES_STYLE_PANEL_WINDOW_DIVIDER (ES_STYLE_CAST(1343))
define ES_STYLE_PANEL_WINDOW_WITH_STATUS_BAR_CONTENT (ES_STYLE_CAST(1483))
define ES_STYLE_PUSH_BUTTON_DANGEROUS (ES_STYLE_CAST(1345))
define_private ES_STYLE_PUSH_BUTTON_NORMAL (ES_STYLE_CAST(1347))
define_private ES_STYLE_PUSH_BUTTON_NORMAL_COLOR_WELL (ES_STYLE_CAST(1349))
define_private ES_STYLE_PUSH_BUTTON_SCROLLBAR_DOWN (ES_STYLE_CAST(1351))
define_private ES_STYLE_PUSH_BUTTON_SCROLLBAR_LEFT (ES_STYLE_CAST(1353))
define_private ES_STYLE_PUSH_BUTTON_SCROLLBAR_RIGHT (ES_STYLE_CAST(1355))
define_private ES_STYLE_PUSH_BUTTON_SCROLLBAR_UP (ES_STYLE_CAST(1357))
define ES_STYLE_PUSH_BUTTON_STATUS_BAR (ES_STYLE_CAST(1495))
define ES_STYLE_PUSH_BUTTON_TOOLBAR (ES_STYLE_CAST(1359))
define ES_STYLE_PUSH_BUTTON_TOOLBAR_BIG (ES_STYLE_CAST(1457))
define ES_STYLE_PUSH_BUTTON_TOOLBAR_MEDIUM (ES_STYLE_CAST(1461))
define_private ES_STYLE_SCROLLBAR_BAR_HORIZONTAL (ES_STYLE_CAST(1363))
define_private ES_STYLE_SCROLLBAR_BAR_VERTICAL (ES_STYLE_CAST(1365))
define_private ES_STYLE_SCROLLBAR_THUMB_HORIZONTAL (ES_STYLE_CAST(1367))
define_private ES_STYLE_SCROLLBAR_THUMB_VERTICAL (ES_STYLE_CAST(1369))
define_private ES_STYLE_SCROLLBAR_PAD (ES_STYLE_CAST(1371))
define ES_STYLE_SEPARATOR_HORIZONTAL (ES_STYLE_CAST(1373))
define_private ES_STYLE_SPLIT_BAR_HORIZONTAL (ES_STYLE_CAST(1375))
define_private ES_STYLE_SPLIT_BAR_VERTICAL (ES_STYLE_CAST(1377))
define_private ES_STYLE_TASK_BAR_BAR (ES_STYLE_CAST(1379))
define_private ES_STYLE_TASK_BAR_BUTTON (ES_STYLE_CAST(1381))
define_private ES_STYLE_TASK_BAR_EXTRA (ES_STYLE_CAST(1507))
define_private ES_STYLE_TASK_BAR_NEW_WINDOW (ES_STYLE_CAST(1383))
define ES_STYLE_TEXT_HEADING0 (ES_STYLE_CAST(1387))
define ES_STYLE_TEXT_HEADING2 (ES_STYLE_CAST(1389))
define_private ES_STYLE_TEXT_HEADING3 (ES_STYLE_CAST(1423))
define ES_STYLE_TEXT_LABEL (ES_STYLE_CAST(1391))
define ES_STYLE_TEXT_LABEL_INVERTED (ES_STYLE_CAST(1393))
define ES_STYLE_TEXT_LABEL_SECONDARY (ES_STYLE_CAST(1395))
define ES_STYLE_TEXT_PARAGRAPH (ES_STYLE_CAST(1397))
define ES_STYLE_TEXT_TOOLBAR (ES_STYLE_CAST(1553))
define ES_STYLE_TEXTBOX_BORDERED_MULTILINE (ES_STYLE_CAST(1399))
define ES_STYLE_TEXTBOX_BORDERED_SINGLE (ES_STYLE_CAST(1401))
define ES_STYLE_TEXTBOX_BORDERED_SINGLE_COMPACT (ES_STYLE_CAST(1403))
define_private ES_STYLE_TEXTBOX_INLINE (ES_STYLE_CAST(1477))
define_private ES_STYLE_TEXTBOX_MARGIN (ES_STYLE_CAST(1415))
define ES_STYLE_TEXTBOX_NO_BORDER (ES_STYLE_CAST(1405))
define ES_STYLE_TEXTBOX_TRANSPARENT (ES_STYLE_CAST(1445))
define_private ES_STYLE_WINDOW_TAB_ACTIVE (ES_STYLE_CAST(1407))
define_private ES_STYLE_WINDOW_TAB_CLOSE_BUTTON (ES_STYLE_CAST(1469))
define_private ES_STYLE_WINDOW_TAB_INACTIVE (ES_STYLE_CAST(1409))
define_private ES_STYLE_WINDOW_TAB_BAND (ES_STYLE_CAST(1411))
define_private ES_STYLE_WINDOW_TAB_BAND_NEW (ES_STYLE_CAST(1361))

658
desktop/syscall.cpp Normal file
View File

@ -0,0 +1,658 @@
ThreadLocalStorage *GetThreadLocalStorage() {
return (ThreadLocalStorage *) ProcessorTLSRead(tlsStorageOffset);
}
double EsTimeStampMs() {
if (!api.systemConstants[ES_SYSTEM_CONSTANT_TIME_STAMP_UNITS_PER_MICROSECOND]) {
return 0;
} else {
return (double) EsTimeStamp() / api.systemConstants[ES_SYSTEM_CONSTANT_TIME_STAMP_UNITS_PER_MICROSECOND] / 1000;
}
}
void *EsMemoryReserve(size_t size, EsMemoryProtection protection, uint32_t flags) {
intptr_t result = EsSyscall(ES_SYSCALL_MEMORY_ALLOCATE, size, flags, protection, 0);
if (result >= 0) {
return (void *) result;
} else {
return nullptr;
}
}
void EsMemoryUnreserve(void *address, size_t size) {
EsSyscall(ES_SYSCALL_MEMORY_FREE, (uintptr_t) address, size, 0, 0);
}
bool EsMemoryCommit(void *pointer, size_t bytes) {
EsAssert(((uintptr_t) pointer & (ES_PAGE_SIZE - 1)) == 0 && (bytes & (ES_PAGE_SIZE - 1)) == 0); // Misaligned pointer/bytes in EsMemoryCommit.
return ES_SUCCESS == (intptr_t) EsSyscall(ES_SYSCALL_MEMORY_COMMIT, (uintptr_t) pointer >> ES_PAGE_BITS, bytes >> ES_PAGE_BITS, 0, 0);
}
void EsMemoryFaultRange(const void *pointer, size_t bytes, uint32_t flags) {
EsSyscall(ES_SYSCALL_MEMORY_FAULT_RANGE, (uintptr_t) pointer, bytes, flags, 0);
}
bool EsMemoryDecommit(void *pointer, size_t bytes) {
EsAssert(((uintptr_t) pointer & (ES_PAGE_SIZE - 1)) == 0 && (bytes & (ES_PAGE_SIZE - 1)) == 0); // Misaligned pointer/bytes in EsMemoryDecommit.
return ES_SUCCESS == (intptr_t) EsSyscall(ES_SYSCALL_MEMORY_COMMIT, (uintptr_t) pointer >> ES_PAGE_BITS, bytes >> ES_PAGE_BITS, 1, 0);
}
EsError EsProcessCreate(EsProcessCreationArguments *arguments, EsProcessInformation *information) {
EsProcessInformation _information;
if (!information) information = &_information;
EsError error = EsSyscall(ES_SYSCALL_PROCESS_CREATE, (uintptr_t) arguments, 0, (uintptr_t) information, 0);
if (error == ES_SUCCESS && information == &_information) {
EsHandleClose(information->handle);
EsHandleClose(information->mainThread.handle);
}
return error;
}
EsError EsMessagePost(EsElement *target, EsMessage *message) {
EsMutexAcquire(&api.postBoxMutex);
_EsMessageWithObject m = { target, *message };
bool success = api.postBox.Add(m);
if (api.postBox.Length() == 1 && success) {
EsMessage m;
m.type = ES_MSG_WAKEUP;
success = ES_SUCCESS == (EsError) EsSyscall(ES_SYSCALL_MESSAGE_POST, (uintptr_t) &m, 0, ES_CURRENT_PROCESS, 0);
}
EsMutexRelease(&api.postBoxMutex);
return success ? ES_SUCCESS : ES_ERROR_INSUFFICIENT_RESOURCES;
}
EsError EsMessagePostRemote(EsHandle process, EsMessage *message) {
return EsSyscall(ES_SYSCALL_MESSAGE_POST, (uintptr_t) message, 0, process, 0);
}
EsHandle EsEventCreate(bool autoReset) {
return EsSyscall(ES_SYSCALL_EVENT_CREATE, autoReset, 0, 0, 0);
}
void EsEventSet(EsHandle handle) {
EsSyscall(ES_SYSCALL_EVENT_SET, handle, 0, 0, 0);
}
void EsEventReset(EsHandle handle) {
EsSyscall(ES_SYSCALL_EVENT_RESET, handle, 0, 0, 0);
}
EsError EsHandleClose(EsHandle handle) {
return EsSyscall(ES_SYSCALL_HANDLE_CLOSE, handle, 0, 0, 0);
}
void EsThreadTerminate(EsHandle thread) {
EsSyscall(ES_SYSCALL_THREAD_TERMINATE, thread, 0, 0, 0);
}
void EsProcessTerminate(EsHandle process, int status) {
EsSyscall(ES_SYSCALL_PROCESS_TERMINATE, process, status, 0, 0);
}
void EsProcessTerminateCurrent() {
EsSyscall(ES_SYSCALL_PROCESS_TERMINATE, ES_CURRENT_PROCESS, 0, 0, 0);
}
int EsProcessGetExitStatus(EsHandle process) {
return EsSyscall(ES_SYSCALL_PROCESS_GET_STATUS, process, 0, 0, 0);
}
void ThreadInitialise();
void ThreadEntry(EsGeneric argument, EsThreadEntryFunction entryFunction) {
ThreadInitialise();
entryFunction(argument);
EsThreadTerminate(ES_CURRENT_THREAD);
}
EsError EsThreadCreate(EsThreadEntryFunction entryFunction, EsThreadInformation *information, EsGeneric argument) {
EsThreadInformation discard = {};
if (!information) {
information = &discard;
}
EsError error = EsSyscall(ES_SYSCALL_THREAD_CREATE, (uintptr_t) ThreadEntry, (uintptr_t) entryFunction, (uintptr_t) information, argument.u);
if (error == ES_SUCCESS && information == &discard) {
EsHandleClose(information->handle);
}
return error;
}
EsError EsFileWriteAll(const char *filePath, ptrdiff_t filePathLength, const void *data, size_t sizes) {
return EsFileWriteAllGather(filePath, filePathLength, &data, &sizes, 1);
}
EsError EsFileWriteAllGather(const char *filePath, ptrdiff_t filePathLength, const void **data, size_t *sizes, size_t gatherCount) {
if (filePathLength == -1) {
filePathLength = EsCStringLength(filePath);
}
EsFileInformation information = EsFileOpen((char *) filePath, filePathLength, ES_FILE_WRITE_EXCLUSIVE | ES_NODE_CREATE_DIRECTORIES);
if (ES_SUCCESS != information.error) {
return information.error;
}
EsError error = EsFileWriteAllGatherFromHandle(information.handle, data, sizes, gatherCount);
EsHandleClose(information.handle);
return error;
}
EsError EsFileWriteAllFromHandle(EsHandle handle, const void *data, size_t sizes) {
return EsFileWriteAllGatherFromHandle(handle, &data, &sizes, 1);
}
EsError EsFileWriteAllGatherFromHandle(EsHandle handle, const void **data, size_t *sizes, size_t gatherCount) {
size_t fileSize = 0;
for (uintptr_t i = 0; i < gatherCount; i++) {
fileSize += sizes[i];
}
EsError error = EsFileResize(handle, fileSize);
if (ES_CHECK_ERROR(error)) return error;
size_t offset = 0;
for (uintptr_t i = 0; i < gatherCount; i++) {
error = EsFileWriteSync(handle, offset, sizes[i], data[i]);
if (ES_CHECK_ERROR(error)) return error;
offset += sizes[i];
}
error = EsFileControl(handle, ES_FILE_CONTROL_NOTIFY_MONITORS | ES_FILE_CONTROL_FLUSH);
return error;
}
void *EsFileReadAllFromHandle(EsHandle handle, size_t *fileSize, EsError *error) {
if (error) *error = ES_SUCCESS;
EsFileOffset size = EsFileGetSize(handle);
if (fileSize) *fileSize = size;
#ifdef KERNEL
void *buffer = EsHeapAllocate(size + 1, false, K_PAGED);
#else
void *buffer = EsHeapAllocate(size + 1, false);
#endif
if (!buffer) {
if (error) *error = ES_ERROR_INSUFFICIENT_RESOURCES;
return nullptr;
}
((char *) buffer)[size] = 0;
uintptr_t result = EsFileReadSync(handle, 0, size, buffer);
if (size != result) {
#ifdef KERNEL
EsHeapFree(buffer, size + 1, K_PAGED);
#else
EsHeapFree(buffer);
#endif
buffer = nullptr;
if (error) *error = (EsError) result;
}
return buffer;
}
void *EsFileReadAll(const char *filePath, ptrdiff_t filePathLength, size_t *fileSize, EsError *error) {
if (error) *error = ES_SUCCESS;
EsFileInformation information = EsFileOpen((char *) filePath, filePathLength, ES_FILE_READ | ES_NODE_FAIL_IF_NOT_FOUND);
if (ES_SUCCESS != information.error) {
if (error) *error = information.error;
return nullptr;
}
void *buffer = EsFileReadAllFromHandle(information.handle, fileSize);
EsHandleClose(information.handle);
return buffer;
}
EsHandle EsMemoryOpen(size_t size, const char *name, ptrdiff_t nameLength, unsigned flags) {
if (nameLength == -1) nameLength = EsCStringLength(name);
return EsSyscall(ES_SYSCALL_MEMORY_OPEN, size, (uintptr_t) name, nameLength, flags);
}
EsHandle EsMemoryShare(EsHandle sharedMemoryRegion, EsHandle targetProcess, bool readOnly) {
return EsSyscall(ES_SYSCALL_MEMORY_SHARE, sharedMemoryRegion, targetProcess, readOnly, 0);
}
void *EsObjectMap(EsHandle sharedMemoryRegion, uintptr_t offset, size_t size, unsigned flags) {
intptr_t result = EsSyscall(ES_SYSCALL_MEMORY_MAP_OBJECT, sharedMemoryRegion, offset, size, flags);
if (result >= 0) {
return (void *) result;
} else {
return nullptr;
}
}
EsFileInformation EsFileOpen(const char *path, ptrdiff_t pathLength, uint32_t flags) {
if (pathLength == -1) {
pathLength = EsCStringLength(path);
}
_EsNodeInformation node;
EsError result = NodeOpen(path, pathLength, flags, &node);
if (result == ES_SUCCESS && node.type == ES_NODE_DIRECTORY) {
result = ES_ERROR_INCORRECT_NODE_TYPE;
EsHandleClose(node.handle);
}
EsFileInformation information = {};
if (result == ES_SUCCESS) {
information.handle = node.handle;
information.size = node.fileSize;
}
information.error = result;
return information;
}
size_t EsFileReadSync(EsHandle handle, EsFileOffset offset, size_t size, void *buffer) {
intptr_t result = EsSyscall(ES_SYSCALL_FILE_READ_SYNC, handle, offset, size, (uintptr_t) buffer);
return result;
}
size_t EsFileWriteSync(EsHandle handle, EsFileOffset offset, size_t size, const void *buffer) {
intptr_t result = EsSyscall(ES_SYSCALL_FILE_WRITE_SYNC, handle, offset, size, (uintptr_t) buffer);
return result;
}
EsFileOffset EsFileGetSize(EsHandle handle) {
return EsSyscall(ES_SYSCALL_FILE_GET_SIZE, handle, 0, 0, 0);
}
EsError EsFileResize(EsHandle handle, EsFileOffset newSize) {
return EsSyscall(ES_SYSCALL_FILE_RESIZE, handle, newSize, 0, 0);
}
void *EsFileStoreReadAll(EsFileStore *file, size_t *fileSize) {
if (file->error != ES_SUCCESS) return nullptr;
if (file->type == FILE_STORE_HANDLE) {
return EsFileReadAllFromHandle(file->handle, fileSize, &file->error);
} else if (file->type == FILE_STORE_PATH) {
return EsFileReadAll(file->path, file->pathBytes, fileSize, &file->error);
} else {
EsAssert(false);
return nullptr;
}
}
bool EsFileStoreWriteAll(EsFileStore *file, const void *data, size_t dataBytes) {
if (file->error == ES_SUCCESS) {
if (file->type == FILE_STORE_HANDLE) {
file->error = EsFileWriteAllFromHandle(file->handle, data, dataBytes);
} else if (file->type == FILE_STORE_PATH) {
file->error = EsFileWriteAll(file->path, file->pathBytes, data, dataBytes);
} else {
EsAssert(false);
}
}
return file->error == ES_SUCCESS;
}
EsFileOffsetDifference EsFileStoreGetSize(EsFileStore *file) {
if (file->type == FILE_STORE_HANDLE) {
return EsFileGetSize(file->handle);
} else if (file->type == FILE_STORE_PATH) {
EsDirectoryChild information;
if (EsPathQueryInformation(file->path, file->pathBytes, &information)) {
return file->pathBytes;
} else {
return -1;
}
} else {
EsAssert(false);
return 0;
}
}
void *EsFileStoreMap(EsFileStore *file, size_t *fileSize, uint32_t flags) {
if (file->type == FILE_STORE_HANDLE) {
EsFileOffsetDifference size = EsFileStoreGetSize(file);
if (size == -1) return nullptr;
*fileSize = size;
return EsObjectMap(file->handle, 0, size, flags);
} else if (file->type == FILE_STORE_PATH) {
return EsFileMap(file->path, file->pathBytes, fileSize, flags);
} else {
EsAssert(false);
return nullptr;
}
}
uintptr_t EsWait(EsHandle *handles, size_t count, uintptr_t timeoutMs) {
return EsSyscall(ES_SYSCALL_WAIT, (uintptr_t) handles, count, timeoutMs, 0);
}
void EsProcessPause(EsHandle process, bool resume) {
EsSyscall(ES_SYSCALL_PROCESS_PAUSE, process, resume, 0, 0);
}
uint64_t EsThreadGetID(EsHandle thread) {
if (thread == ES_CURRENT_THREAD) {
return GetThreadLocalStorage()->id;
} else {
return EsSyscall(ES_SYSCALL_THREAD_GET_ID, thread, 0, 0, 0);
}
}
uintptr_t EsProcessGetID(EsHandle process) {
return EsSyscall(ES_SYSCALL_THREAD_GET_ID, process, 0, 0, 0);
}
ptrdiff_t EsDirectoryEnumerateChildrenFromHandle(EsHandle directory, EsDirectoryChild *buffer, size_t size) {
if (!size) return 0;
return EsSyscall(ES_SYSCALL_DIRECTORY_ENUMERATE, directory, (uintptr_t) buffer, size, 0);
}
#ifndef KERNEL
ptrdiff_t EsDirectoryEnumerateChildren(const char *path, ptrdiff_t pathBytes, EsDirectoryChild **buffer) {
*buffer = nullptr;
if (pathBytes == -1) pathBytes = EsCStringLength(path);
_EsNodeInformation node;
EsError error = NodeOpen(path, pathBytes, ES_NODE_FAIL_IF_NOT_FOUND | ES_NODE_DIRECTORY, &node);
if (error != ES_SUCCESS) return error;
if (node.directoryChildren == ES_DIRECTORY_CHILDREN_UNKNOWN) {
node.directoryChildren = 4194304 / sizeof(EsDirectoryChild); // TODO Grow the buffer until all entries fit.
}
*buffer = (EsDirectoryChild *) EsHeapAllocate(sizeof(EsDirectoryChild) * node.directoryChildren, true);
ptrdiff_t result = EsDirectoryEnumerateChildrenFromHandle(node.handle, *buffer, node.directoryChildren);
if (ES_CHECK_ERROR(result)) { EsHeapFree(*buffer); *buffer = nullptr; }
EsHandleClose(node.handle);
return result;
}
#endif
void EsBatch(EsBatchCall *calls, size_t count) {
#if 0
for (uintptr_t i = 0; i < count; i++) {
EsBatchCall *call = calls + i;
// ... modify system call for version changes ...
}
#endif
EsSyscall(ES_SYSCALL_BATCH, (uintptr_t) calls, count, 0, 0);
}
EsError EsPathDelete(const char *path, ptrdiff_t pathBytes) {
_EsNodeInformation node;
if (pathBytes == -1) pathBytes = EsCStringLength(path);
EsError error = NodeOpen(path, pathBytes, ES_NODE_FAIL_IF_NOT_FOUND | ES_FILE_WRITE_EXCLUSIVE, &node);
if (ES_CHECK_ERROR(error)) return error;
error = EsSyscall(ES_SYSCALL_NODE_DELETE, node.handle, 0, 0, 0);
EsHandleClose(node.handle);
return error;
}
void *EsFileMap(const char *path, ptrdiff_t pathBytes, size_t *fileSize, uint32_t flags) {
EsFileInformation information = EsFileOpen(path, pathBytes,
ES_NODE_FAIL_IF_NOT_FOUND | ((flags & ES_MAP_OBJECT_READ_WRITE) ? ES_FILE_WRITE_EXCLUSIVE : ES_FILE_READ));
if (ES_CHECK_ERROR(information.error)) {
return nullptr;
}
void *base = EsObjectMap(information.handle, 0, information.size, flags);
EsHandleClose(information.handle);
if (fileSize) *fileSize = information.size;
return base;
}
EsError EsPathMove(const char *oldPath, ptrdiff_t oldPathBytes, const char *newPath, ptrdiff_t newPathBytes) {
if (oldPathBytes == -1) oldPathBytes = EsCStringLength(oldPath);
if (newPathBytes == -1) newPathBytes = EsCStringLength(newPath);
_EsNodeInformation node = {};
_EsNodeInformation directory = {};
EsError error;
error = NodeOpen(oldPath, oldPathBytes, ES_NODE_FAIL_IF_NOT_FOUND, &node);
if (error != ES_SUCCESS) return error;
uintptr_t s = 0;
for (intptr_t i = 0; i < newPathBytes; i++) if (newPath[i] == '/') s = i + 1;
error = NodeOpen(newPath, s, ES_NODE_DIRECTORY | ES_NODE_FAIL_IF_NOT_FOUND, &directory);
if (error != ES_SUCCESS) { EsHandleClose(node.handle); return error; }
error = EsSyscall(ES_SYSCALL_NODE_MOVE, node.handle, directory.handle, (uintptr_t) newPath + s, newPathBytes - s);
EsHandleClose(node.handle);
EsHandleClose(directory.handle);
return error;
}
bool EsPathExists(const char *path, ptrdiff_t pathBytes, EsNodeType *type) {
if (pathBytes == -1) pathBytes = EsCStringLength(path);
_EsNodeInformation node = {};
EsError error = NodeOpen(path, pathBytes, ES_NODE_FAIL_IF_NOT_FOUND, &node);
if (error != ES_SUCCESS) return false;
EsHandleClose(node.handle);
if (type) *type = node.type;
return true;
}
bool EsPathQueryInformation(const char *path, ptrdiff_t pathBytes, EsDirectoryChild *information) {
if (pathBytes == -1) pathBytes = EsCStringLength(path);
_EsNodeInformation node = {};
EsError error = NodeOpen(path, pathBytes, ES_NODE_FAIL_IF_NOT_FOUND, &node);
if (error != ES_SUCCESS) return false;
EsHandleClose(node.handle);
information->type = node.type;
information->fileSize = node.fileSize;
information->directoryChildren = node.directoryChildren;
return true;
}
EsError EsPathCreate(const char *path, ptrdiff_t pathBytes, EsNodeType type, bool createLeadingDirectories) {
if (pathBytes == -1) pathBytes = EsCStringLength(path);
_EsNodeInformation node = {};
EsError error = NodeOpen(path, pathBytes,
ES_NODE_FAIL_IF_FOUND | type | (createLeadingDirectories ? ES_NODE_CREATE_DIRECTORIES : 0),
&node);
if (error != ES_SUCCESS) return error;
EsHandleClose(node.handle);
return ES_SUCCESS;
}
EsError EsFileControl(EsHandle file, uint32_t flags) {
return EsSyscall(ES_SYSCALL_FILE_CONTROL, file, flags, 0, 0);
}
void EsConstantBufferRead(EsHandle buffer, void *output) {
EsSyscall(ES_SYSCALL_CONSTANT_BUFFER_READ, buffer, (uintptr_t) output, 0, 0);
}
void EsProcessGetState(EsHandle process, EsProcessState *state) {
EsSyscall(ES_SYSCALL_PROCESS_GET_STATE, process, (uintptr_t) state, 0, 0);
}
void EsSchedulerYield() {
EsSyscall(ES_SYSCALL_YIELD_SCHEDULER, 0, 0, 0, 0);
}
void EsSleep(uint64_t milliseconds) {
EsSyscall(ES_SYSCALL_SLEEP, milliseconds >> 32, milliseconds & 0xFFFFFFFF, 0, 0);
}
EsHandle EsTakeSystemSnapshot(int type, size_t *bufferSize) {
return EsSyscall(ES_SYSCALL_SYSTEM_TAKE_SNAPSHOT, type, (uintptr_t) bufferSize, 0, 0);
}
EsHandle EsProcessOpen(uint64_t pid) {
// TODO This won't work correctly if arguments to system call are 32-bit.
return EsSyscall(ES_SYSCALL_PROCESS_OPEN, pid, 0, 0, 0);
}
#ifndef KERNEL
EsHandle EsConstantBufferShare(EsHandle constantBuffer, EsHandle targetProcess) {
return EsSyscall(ES_SYSCALL_CONSTANT_BUFFER_SHARE, constantBuffer, targetProcess, 0, 0);
}
EsHandle EsConstantBufferCreate(const void *data, size_t dataBytes, EsHandle targetProcess) {
return EsSyscall(ES_SYSCALL_CONSTANT_BUFFER_CREATE, (uintptr_t) data, targetProcess, dataBytes, 0);
}
size_t EsConstantBufferGetSize(EsHandle buffer) {
return EsSyscall(ES_SYSCALL_CONSTANT_BUFFER_READ, buffer, 0, 0, 0);
}
#endif
EsError EsAddressResolve(const char *domain, ptrdiff_t domainBytes, uint32_t flags, EsAddress *address) {
return EsSyscall(ES_SYSCALL_DOMAIN_NAME_RESOLVE, (uintptr_t) domain, domainBytes, (uintptr_t) address, flags);
}
EsError EsConnectionOpen(EsConnection *connection, uint32_t flags) {
connection->error = ES_SUCCESS;
connection->open = false;
EsError error = EsSyscall(ES_SYSCALL_CONNECTION_OPEN, (uintptr_t) connection, flags, 0, 0);
if (error == ES_SUCCESS && (flags & ES_CONNECTION_OPEN_WAIT)) {
while (!connection->open && connection->error == ES_SUCCESS) {
EsConnectionPoll(connection);
}
return connection->error;
} else {
return error;
}
}
void EsConnectionPoll(EsConnection *connection) {
EsSyscall(ES_SYSCALL_CONNECTION_POLL, (uintptr_t) connection, 0, 0, connection->handle);
}
void EsConnectionNotify(EsConnection *connection) {
EsSyscall(ES_SYSCALL_CONNECTION_NOTIFY, 0, connection->sendWritePointer, connection->receiveReadPointer, connection->handle);
}
void EsConnectionClose(EsConnection *connection) {
EsObjectUnmap(connection->sendBuffer);
EsHandleClose(connection->handle);
}
EsError EsConnectionWriteSync(EsConnection *connection, const void *_data, size_t dataBytes) {
const uint8_t *data = (const uint8_t *) _data;
while (dataBytes) {
EsConnectionPoll(connection);
if (connection->error != ES_SUCCESS) {
return connection->error;
}
size_t space = connection->sendWritePointer >= connection->sendReadPointer
? connection->sendBufferBytes - connection->sendWritePointer
: connection->sendReadPointer - connection->sendWritePointer - 1;
if (!space) {
continue;
}
size_t bytesToWrite = space > dataBytes ? dataBytes : space;
EsMemoryCopy(connection->sendBuffer + connection->sendWritePointer, data, bytesToWrite);
data += bytesToWrite, dataBytes -= bytesToWrite;
connection->sendWritePointer = (connection->sendWritePointer + bytesToWrite) % connection->sendBufferBytes;
EsConnectionNotify(connection);
}
return ES_SUCCESS;
}
EsError EsConnectionRead(EsConnection *connection, void *_buffer, size_t bufferBytes, size_t *bytesRead) {
uint8_t *buffer = (uint8_t *) _buffer;
*bytesRead = 0;
EsConnectionPoll(connection);
if (connection->error != ES_SUCCESS) {
return connection->error;
}
while (bufferBytes && connection->receiveReadPointer != connection->receiveWritePointer) {
size_t bytesAvailable = connection->receiveReadPointer > connection->receiveWritePointer
? connection->receiveBufferBytes - connection->receiveReadPointer
: connection->receiveWritePointer - connection->receiveReadPointer;
size_t bytesToRead = bufferBytes > bytesAvailable ? bytesAvailable : bufferBytes;
EsMemoryCopy(buffer, connection->receiveBuffer + connection->receiveReadPointer, bytesToRead);
connection->receiveReadPointer = (connection->receiveReadPointer + bytesToRead) % connection->receiveBufferBytes;
buffer += bytesToRead, bufferBytes -= bytesToRead;
*bytesRead += bytesToRead;
EsConnectionNotify(connection);
}
return ES_SUCCESS;
}
void EsEventForward(EsHandle event, EsHandle eventSink, EsGeneric data) {
EsSyscall(ES_SYSCALL_EVENT_FORWARD, event, eventSink, data.u, 0);
}
EsHandle EsEventSinkCreate(bool ignoreDuplicates) {
return EsSyscall(ES_SYSCALL_EVENT_SINK_CREATE, ignoreDuplicates, 0, 0, 0);
}
EsError EsEventSinkPop(EsHandle eventSink, EsGeneric *data) {
EsGeneric unused; if (!data) data = &unused;
return EsSyscall(ES_SYSCALL_EVENT_SINK_POP, eventSink, (uintptr_t) data, 0, 0);
}
EsError EsEventSinkPush(EsHandle eventSink, EsGeneric data) {
return EsSyscall(ES_SYSCALL_EVENT_SINK_PUSH, eventSink, data.u, 0, 0);
}
size_t EsGameControllerStatePoll(EsGameControllerState *buffer) {
return EsSyscall(ES_SYSCALL_GAME_CONTROLLER_STATE_POLL, (uintptr_t) buffer, 0, 0, 0);
}
#define CLIPBOARD_FORMAT_TEXT (1)
EsError EsClipboardAddText(EsClipboard clipboard, const char *text, ptrdiff_t textBytes) {
EsMessageMutexCheck();
if (textBytes == -1) {
textBytes = EsCStringLength(text);
}
return EsSyscall(ES_SYSCALL_CLIPBOARD_ADD, clipboard, CLIPBOARD_FORMAT_TEXT, (uintptr_t) text, textBytes);
}
bool EsClipboardHasText(EsClipboard clipboard) {
return EsSyscall(ES_SYSCALL_CLIPBOARD_HAS, clipboard, CLIPBOARD_FORMAT_TEXT, 0, 0);
}
char *EsClipboardReadText(EsClipboard clipboard, size_t *bytes) {
*bytes = 0;
EsHandle handle = EsSyscall(ES_SYSCALL_CLIPBOARD_READ, clipboard, CLIPBOARD_FORMAT_TEXT, 0, (uintptr_t) bytes);
if (handle == ES_INVALID_HANDLE) return nullptr;
char *buffer = (char *) EsHeapAllocate(*bytes, false);
if (!buffer) return nullptr;
EsConstantBufferRead(handle, buffer);
EsHandleClose(handle);
return buffer;
}

4984
desktop/text.cpp Normal file

File diff suppressed because it is too large Load Diff

2209
desktop/theme.cpp Normal file

File diff suppressed because it is too large Load Diff

1073
drivers/acpi.cpp Normal file

File diff suppressed because it is too large Load Diff

886
drivers/ahci.cpp Normal file
View File

@ -0,0 +1,886 @@
#include <module.h>
// TODO Inserting/removing CDs.
#define GENERAL_TIMEOUT (5000)
#define COMMAND_LIST_SIZE (0x400)
#define RECEIVED_FIS_SIZE (0x100)
#define PRDT_ENTRY_COUNT (0x48) // If one page each, this covers more than CC_ACTIVE_SECTION_SIZE. This must be a multiple of 8.
#define COMMAND_TABLE_SIZE (0x80 + PRDT_ENTRY_COUNT * 0x10)
// Global registers.
#define RD_REGISTER_CAP() pci->ReadBAR32(5, 0x00) // HBA capababilities.
#define RD_REGISTER_GHC() pci->ReadBAR32(5, 0x04) // Global host control.
#define WR_REGISTER_GHC(x) pci->WriteBAR32(5, 0x04, x)
#define RD_REGISTER_IS() pci->ReadBAR32(5, 0x08) // Interrupt status.
#define WR_REGISTER_IS(x) pci->WriteBAR32(5, 0x08, x)
#define RD_REGISTER_PI() pci->ReadBAR32(5, 0x0C) // Ports implemented.
#define RD_REGISTER_CAP2() pci->ReadBAR32(5, 0x24) // HBA capababilities extended.
#define RD_REGISTER_BOHC() pci->ReadBAR32(5, 0x28) // BIOS/OS handoff control and status.
#define WR_REGISTER_BOHC(x) pci->WriteBAR32(5, 0x28, x)
// Port-specific registers.
#define RD_REGISTER_PCLB(p) pci->ReadBAR32(5, 0x100 + (p) * 0x80) // Command list base address (low DWORD).
#define WR_REGISTER_PCLB(p, x) pci->WriteBAR32(5, 0x100 + (p) * 0x80, x)
#define RD_REGISTER_PCLBU(p) pci->ReadBAR32(5, 0x104 + (p) * 0x80) // Command list base address (high DWORD).
#define WR_REGISTER_PCLBU(p, x) pci->WriteBAR32(5, 0x104 + (p) * 0x80, x)
#define RD_REGISTER_PFB(p) pci->ReadBAR32(5, 0x108 + (p) * 0x80) // FIS base address (low DWORD).
#define WR_REGISTER_PFB(p, x) pci->WriteBAR32(5, 0x108 + (p) * 0x80, x)
#define RD_REGISTER_PFBU(p) pci->ReadBAR32(5, 0x10C + (p) * 0x80) // FIS base address (high DWORD).
#define WR_REGISTER_PFBU(p, x) pci->WriteBAR32(5, 0x10C + (p) * 0x80, x)
#define RD_REGISTER_PIS(p) pci->ReadBAR32(5, 0x110 + (p) * 0x80) // Interrupt status.
#define WR_REGISTER_PIS(p, x) pci->WriteBAR32(5, 0x110 + (p) * 0x80, x)
#define RD_REGISTER_PIE(p) pci->ReadBAR32(5, 0x114 + (p) * 0x80) // Interrupt enable.
#define WR_REGISTER_PIE(p, x) pci->WriteBAR32(5, 0x114 + (p) * 0x80, x)
#define RD_REGISTER_PCMD(p) pci->ReadBAR32(5, 0x118 + (p) * 0x80) // Command and status.
#define WR_REGISTER_PCMD(p, x) pci->WriteBAR32(5, 0x118 + (p) * 0x80, x)
#define RD_REGISTER_PTFD(p) pci->ReadBAR32(5, 0x120 + (p) * 0x80) // Task file data.
#define RD_REGISTER_PSIG(p) pci->ReadBAR32(5, 0x124 + (p) * 0x80) // Signature.
#define RD_REGISTER_PSSTS(p) pci->ReadBAR32(5, 0x128 + (p) * 0x80) // SATA status.
#define RD_REGISTER_PSCTL(p) pci->ReadBAR32(5, 0x12C + (p) * 0x80) // SATA control.
#define WR_REGISTER_PSCTL(p, x) pci->WriteBAR32(5, 0x12C + (p) * 0x80, x)
#define RD_REGISTER_PSERR(p) pci->ReadBAR32(5, 0x130 + (p) * 0x80) // SATA error.
#define WR_REGISTER_PSERR(p, x) pci->WriteBAR32(5, 0x130 + (p) * 0x80, x)
#define RD_REGISTER_PCI(p) pci->ReadBAR32(5, 0x138 + (p) * 0x80) // Command issue.
#define WR_REGISTER_PCI(p, x) pci->WriteBAR32(5, 0x138 + (p) * 0x80, x)
struct AHCIPort {
bool connected, atapi, ssd;
uint32_t *commandList;
uint8_t *commandTables;
size_t sectorBytes;
uint64_t sectorCount;
KWorkGroup *commandContexts[32]; // Set to indicate command in use.
uint64_t commandStartTimeStamps[32];
uint32_t runningCommands;
KSpinlock commandSpinlock;
KEvent commandSlotsAvailable;
char model[41];
};
struct AHCIController : KDevice {
KPCIDevice *pci;
uint32_t capabilities, capabilities2;
bool dma64Supported;
size_t commandSlotCount;
KTimer timeoutTimer;
#define MAX_PORTS (32)
AHCIPort ports[MAX_PORTS];
void Initialise();
bool Access(uintptr_t port, uint64_t offsetBytes, size_t countBytes, int operation,
KDMABuffer *buffer, uint64_t flags, KWorkGroup *dispatchGroup);
bool HandleIRQ();
bool SendSingleCommand(uintptr_t port);
void DumpState();
};
struct AHCIDrive : KBlockDevice {
AHCIController *controller;
uintptr_t port;
};
struct InterruptEvent {
uint64_t timeStamp;
uint32_t globalInterruptStatus;
uint32_t port0CommandsRunning;
uint32_t port0CommandsIssued;
bool complete;
};
volatile uintptr_t recentInterruptEventsPointer;
volatile InterruptEvent recentInterruptEvents[64];
void AHCIController::DumpState() {
uint64_t timeStamp = KGetTimeInMs();
EsPrint("AHCI controller state:\n");
EsPrint("\t--- Registers ---\n");
EsPrint("\t\tHBA capabilities: %x.\n", RD_REGISTER_CAP());
EsPrint("\t\tGlobal host control: %x.\n", RD_REGISTER_GHC());
EsPrint("\t\tInterrupt status: %x.\n", RD_REGISTER_IS());
EsPrint("\t\tPorts implemented: %x.\n", RD_REGISTER_PI());
EsPrint("\t\tHBA capabilities extended: %x.\n", RD_REGISTER_CAP2());
EsPrint("\t\tBIOS/OS handoff control and status: %x.\n", RD_REGISTER_BOHC());
for (uintptr_t i = 0; i < MAX_PORTS; i++) {
AHCIPort *port = ports + i;
if (!port->connected) {
continue;
}
EsPrint("\t--- Port %d ---\n", i);
EsPrint("\t\tCommand list base address (low DWORD): %x.\n", RD_REGISTER_PCLB(i));
EsPrint("\t\tCommand list base address (high DWORD): %x.\n", RD_REGISTER_PCLBU(i));
EsPrint("\t\tFIS base address (low DWORD): %x.\n", RD_REGISTER_PFB(i));
EsPrint("\t\tFIS base address (high DWORD): %x.\n", RD_REGISTER_PFBU(i));
EsPrint("\t\tInterrupt status: %x.\n", RD_REGISTER_PIS(i));
EsPrint("\t\tInterrupt enable: %x.\n", RD_REGISTER_PIE(i));
EsPrint("\t\tCommand and status: %x.\n", RD_REGISTER_PCMD(i));
EsPrint("\t\tTask file data: %x.\n", RD_REGISTER_PTFD(i));
EsPrint("\t\tSignature: %x.\n", RD_REGISTER_PSIG(i));
EsPrint("\t\tSATA status: %x.\n", RD_REGISTER_PSSTS(i));
EsPrint("\t\tSATA error: %x.\n", RD_REGISTER_PSERR(i));
EsPrint("\t\tCommand issue: %x.\n", RD_REGISTER_PCI(i));
EsPrint("\t\tATAPI: %d.\n", port->atapi);
EsPrint("\t\tBytes per sector: %D.\n", port->sectorBytes);
EsPrint("\t\tTotal capacity: %D.\n", port->sectorBytes * port->sectorCount);
EsPrint("\t\tCommand slots available: %d.\n", port->commandSlotsAvailable.state);
EsPrint("\t\tRunning commands: %x.\n", port->runningCommands);
uint8_t *receivedFIS = (uint8_t *) port->commandList + 0x400;
EsPrint("\t\tReceived FIS D2H register: type %X, interrupt %X, status %X, error %X, device/head %X, sector %x, sector count %x.\n",
receivedFIS[0x40 + 0], receivedFIS[0x40 + 1], receivedFIS[0x40 + 2], receivedFIS[0x40 + 3], receivedFIS[0x40 + 7],
(uint64_t) receivedFIS[0x40 + 4] | ((uint64_t) receivedFIS[0x40 + 5] << 8) | ((uint64_t) receivedFIS[0x40 + 6] << 16)
| ((uint64_t) receivedFIS[0x40 + 8] << 24) | ((uint64_t) receivedFIS[0x40 + 9] << 32) | ((uint64_t) receivedFIS[0x40 + 10] << 40),
(uint64_t) receivedFIS[0x40 + 12] | ((uint64_t) receivedFIS[0x40 + 13] << 8));
EsPrint("\t\tReceived FIS set device bits: type %X, interrupt %X, status %X, error %X.\n",
receivedFIS[0x58 + 0], receivedFIS[0x58 + 1], receivedFIS[0x58 + 2], receivedFIS[0x58 + 3]);
EsPrint("\t\tReceived FIS DMA setup: type %X, flags %X, buffer identifier low %x, buffer identifier high %x, buffer offset %x, transfer count %x.\n",
receivedFIS[0], receivedFIS[1], ((uint32_t *) receivedFIS)[1],
((uint32_t *) receivedFIS)[2], ((uint32_t *) receivedFIS)[4], ((uint32_t *) receivedFIS)[5]);
for (uintptr_t j = 0; j < 32; j++) {
if (~port->runningCommands & (1 << j)) continue;
EsPrint("\t\tCommand %d: started %dms ago for dispatch group %x.\n", j,
timeStamp - port->commandStartTimeStamps[j], port->commandContexts[j]);
EsPrint("\t\t\tDW0 %x: %d FIS bytes, %z, %z, %d PRDT entries.\n",
port->commandList[j * 8 + 0], (port->commandList[j * 8 + 0]) & 31,
((port->commandList[j * 8 + 0]) & (1 << 5)) ? "SATAPI" : "SATA",
((port->commandList[j * 8 + 0]) & (1 << 6)) ? "write" : "read",
port->commandList[j * 8 + 0] >> 16);
EsPrint("\t\t\tDW1 transferring %D.\n", port->commandList[j * 8 + 1]);
EsPrint("\t\t\tDW2/3 command table base address: %x.\n", *(uint64_t *) &port->commandList[j * 8 + 2]);
uint8_t *commandFIS = port->commandTables + COMMAND_TABLE_SIZE * j;
EsPrint("\t\t\tH2D FIS: type %X, command %X, features %X, device/head %X, features 2 %X, control %X, sector %x, sector count %x.\n",
commandFIS[0], commandFIS[2], commandFIS[3], commandFIS[7], commandFIS[11], commandFIS[15],
(uint64_t) commandFIS[4] | ((uint64_t) commandFIS[5] << 8) | ((uint64_t) commandFIS[6] << 16)
| ((uint64_t) commandFIS[8] << 24) | ((uint64_t) commandFIS[9] << 32) | ((uint64_t) commandFIS[10] << 40),
(uint64_t) commandFIS[12] | ((uint64_t) commandFIS[13] << 8));
uint8_t *atapiCommand = commandFIS + 64;
if (((port->commandList[j * 8 + 0]) & (1 << 5))) {
EsPrint("\t\t\tATAPI command: %X %X %X %X %X %X %X %X %X %X %X %X %X %X %X %X.\n",
atapiCommand[0], atapiCommand[1], atapiCommand[2], atapiCommand[3],
atapiCommand[4], atapiCommand[5], atapiCommand[6], atapiCommand[7],
atapiCommand[8], atapiCommand[9], atapiCommand[10], atapiCommand[11],
atapiCommand[12], atapiCommand[13], atapiCommand[14], atapiCommand[15]);
}
uint32_t *prdt = (uint32_t *) (commandFIS + 128);
for (uintptr_t k = 0; k < (port->commandList[j * 8 + 0] >> 16); k++) {
EsPrint("\t\t\tPRDT entry %d: base address %x, byte count %x%z.\n",
k, *(uint64_t *) (prdt + k * 4), (prdt[k * 4 + 3] & 0xFFFFFFF) + 1,
(prdt[k * 4 + 3] & 0x80000000) ? ", interrupt on completion" : "");
}
}
}
EsPrint("\t--- Most recent interrupts ---\n");
for (uintptr_t i = 0; i < sizeof(recentInterruptEvents) / sizeof(recentInterruptEvents[0]); i++) {
if (recentInterruptEventsPointer) recentInterruptEventsPointer--;
else recentInterruptEventsPointer = sizeof(recentInterruptEvents) / sizeof(recentInterruptEvents[0]) - 1;
volatile InterruptEvent *event = recentInterruptEvents + recentInterruptEventsPointer;
EsPrint("\t\tEvent %d: fired %dms ago, GIS %x, CI0 %x, CR0 %x%z.\n",
i, timeStamp - event->timeStamp,
event->globalInterruptStatus, event->port0CommandsIssued,
event->port0CommandsRunning, event->complete ? "" : ", incomplete");
}
}
bool AHCIController::Access(uintptr_t portIndex, uint64_t offsetBytes, size_t countBytes, int operation,
KDMABuffer *buffer, uint64_t, KWorkGroup *dispatchGroup) {
AHCIPort *port = ports + portIndex;
#if 0
// TODO Temporary.
if (operation == K_ACCESS_WRITE) {
KernelPanic("AHCIController::Access - Attempted write.\n");
}
#endif
// Find a command slot to use.
uintptr_t commandIndex = 0;
while (true) {
KSpinlockAcquire(&port->commandSpinlock);
uint32_t commandsAvailable = ~RD_REGISTER_PCI(portIndex);
bool found = false;
for (uintptr_t i = 0; i < commandSlotCount; i++) {
if ((commandsAvailable & (1 << i)) && !port->commandContexts[i]) {
commandIndex = i;
found = true;
break;
}
}
if (!found) {
KEventReset(&port->commandSlotsAvailable);
} else {
port->commandContexts[commandIndex] = dispatchGroup;
}
KSpinlockRelease(&port->commandSpinlock);
if (!found) {
KEventWait(&port->commandSlotsAvailable);
} else {
break;
}
}
// Setup the command FIS.
uint32_t countSectors = countBytes / port->sectorBytes;
uint64_t offsetSectors = offsetBytes / port->sectorBytes;
if (countSectors & ~0xFFFF) {
KernelPanic("AHCIController::Access - Too many sectors to read.\n");
}
uint32_t *commandFIS = (uint32_t *) (port->commandTables + COMMAND_TABLE_SIZE * commandIndex);
commandFIS[0] = 0x27 /* H2D */ | (1 << 15) /* command */ | ((operation == K_ACCESS_WRITE ? 0x35 /* write DMA 48 */ : 0x25 /* read DMA 48 */) << 16);
commandFIS[1] = (offsetSectors & 0xFFFFFF) | (1 << 30);
commandFIS[2] = (offsetSectors >> 24) & 0xFFFFFF;
commandFIS[3] = countSectors & 0xFFFF;
commandFIS[4] = 0;
// Setup the PRDT.
size_t prdtEntryCount = 0;
uint32_t *prdt = (uint32_t *) (port->commandTables + COMMAND_TABLE_SIZE * commandIndex + 0x80);
while (!KDMABufferIsComplete(buffer)) {
if (prdtEntryCount == PRDT_ENTRY_COUNT) {
KernelPanic("AHCIController::Access - Too many PRDT entries.\n");
}
KDMASegment segment = KDMABufferNextSegment(buffer);
prdt[0 + 4 * prdtEntryCount] = segment.physicalAddress;
prdt[1 + 4 * prdtEntryCount] = segment.physicalAddress >> 32;
prdt[2 + 4 * prdtEntryCount] = 0;
prdt[3 + 4 * prdtEntryCount] = (segment.byteCount - 1) | (segment.isLast ? (1 << 31) /* IRQ when done */ : 0);
prdtEntryCount++;
}
// Setup the command list entry, and issue the command.
port->commandList[commandIndex * 8 + 0] = 5 /* FIS is 5 DWORDs */ | (prdtEntryCount << 16) | (operation == K_ACCESS_WRITE ? (1 << 6) : 0);
port->commandList[commandIndex * 8 + 1] = 0;
// Setup SCSI command if ATAPI.
if (port->atapi) {
port->commandList[commandIndex * 8 + 0] |= (1 << 5) /* ATAPI */;
commandFIS[0] = 0x27 /* H2D */ | (1 << 15) /* command */ | (0xA0 /* packet */ << 16);
commandFIS[1] = countBytes << 8;
uint8_t *scsiCommand = (uint8_t *) commandFIS + 0x40;
EsMemoryZero(scsiCommand, 10);
scsiCommand[0] = 0xA8 /* READ (12) */;
scsiCommand[2] = (offsetSectors >> 0x18) & 0xFF;
scsiCommand[3] = (offsetSectors >> 0x10) & 0xFF;
scsiCommand[4] = (offsetSectors >> 0x08) & 0xFF;
scsiCommand[5] = (offsetSectors >> 0x00) & 0xFF;
scsiCommand[9] = countSectors;
}
// Start executing the command.
KSpinlockAcquire(&port->commandSpinlock);
port->runningCommands |= 1 << commandIndex;
__sync_synchronize();
WR_REGISTER_PCI(portIndex, 1 << commandIndex);
port->commandStartTimeStamps[commandIndex] = KGetTimeInMs();
KSpinlockRelease(&port->commandSpinlock);
return true;
}
bool AHCIController::HandleIRQ() {
uint32_t globalInterruptStatus = RD_REGISTER_IS();
KernelLog(LOG_VERBOSE, "AHCI", "received IRQ", "Received IRQ with status: %x.\n", globalInterruptStatus);
if (!globalInterruptStatus) return false;
WR_REGISTER_IS(globalInterruptStatus);
volatile InterruptEvent *event = recentInterruptEvents + recentInterruptEventsPointer;
event->timeStamp = KGetTimeInMs();
event->globalInterruptStatus = globalInterruptStatus;
event->complete = false;
recentInterruptEventsPointer = (recentInterruptEventsPointer + 1) % (sizeof(recentInterruptEvents) / sizeof(recentInterruptEvents[0]));
bool commandCompleted = false;
for (uintptr_t i = 0; i < MAX_PORTS; i++) {
if (~globalInterruptStatus & (1 << i)) continue;
uint32_t interruptStatus = RD_REGISTER_PIS(i);
if (!interruptStatus) continue;
WR_REGISTER_PIS(i, interruptStatus);
AHCIPort *port = ports + i;
if (interruptStatus & ((1 << 30) | (1 << 29) | (1 << 28) | (1 << 27) | (1 << 26) | (1 << 24) | (1 << 23))) {
KernelLog(LOG_ERROR, "AHCI", "error IRQ", "Received IRQ error interrupt status bit set: %x.\n", interruptStatus);
KSpinlockAcquire(&port->commandSpinlock);
// Stop command processing.
WR_REGISTER_PCMD(i, RD_REGISTER_PCMD(i) & ~(1 << 0));
// Fail all outstanding commands.
for (uintptr_t j = 0; j < 32; j++) {
if (port->runningCommands & (1 << j)) {
port->commandContexts[j]->End(false /* failed */);
port->commandContexts[j] = nullptr;
}
}
port->runningCommands = 0;
KEventSet(&port->commandSlotsAvailable, false, true /* maybe already set */);
// Restart command processing.
WR_REGISTER_PSERR(i, 0xFFFFFFFF);
KTimeout timeout(5);
while ((RD_REGISTER_PCMD(i) & (1 << 15)) && !timeout.Hit());
WR_REGISTER_PCMD(i, RD_REGISTER_PCMD(i) | (1 << 0));
KSpinlockRelease(&port->commandSpinlock);
continue;
}
KSpinlockAcquire(&port->commandSpinlock);
uint32_t commandsIssued = RD_REGISTER_PCI(i);
if (i == 0) event->port0CommandsIssued = commandsIssued, event->port0CommandsRunning = port->runningCommands;
for (uintptr_t j = 0; j < 32; j++) {
if (~port->runningCommands & (1 << j)) continue; // Command not started.
if (commandsIssued & (1 << j)) continue; // Command still running.
// The command has completed.
port->commandContexts[j]->End(true /* success */);
port->commandContexts[j] = nullptr;
KEventSet(&port->commandSlotsAvailable, false, true /* maybe already set */);
port->runningCommands &= ~(1 << j);
commandCompleted = true;
}
KSpinlockRelease(&port->commandSpinlock);
}
if (commandCompleted) {
KSwitchThreadAfterIRQ();
}
event->complete = true;
return true;
}
void TimeoutTimerHit(EsGeneric argument) {
AHCIController *controller = (AHCIController *) argument.p;
uint64_t currentTimeStamp = KGetTimeInMs();
for (uintptr_t i = 0; i < MAX_PORTS; i++) {
AHCIPort *port = controller->ports + i;
KSpinlockAcquire(&port->commandSpinlock);
for (uintptr_t j = 0; j < controller->commandSlotCount; j++) {
if ((port->runningCommands & (1 << j))
&& port->commandStartTimeStamps[j] + GENERAL_TIMEOUT < currentTimeStamp) {
KernelLog(LOG_ERROR, "AHCI", "command timeout", "Command %d on port %d timed out.\n", j, i);
port->commandContexts[j]->End(false /* failure */);
port->commandContexts[j] = nullptr;
port->runningCommands &= ~(1 << j);
// Don't set the commandSlotsAvailable event, since the controller still thinks the command is in use.
// TODO What happens if there are no commands left?
}
}
KSpinlockRelease(&port->commandSpinlock);
}
KTimerSet(&controller->timeoutTimer, GENERAL_TIMEOUT, TimeoutTimerHit, controller);
}
bool AHCIController::SendSingleCommand(uintptr_t port) {
KTimeout timeout(GENERAL_TIMEOUT);
// Wait for the port to be ready, then issue the command.
while ((RD_REGISTER_PTFD(port) & ((1 << 7) | (1 << 3))) && !timeout.Hit());
if (timeout.Hit()) {
KernelLog(LOG_ERROR, "AHCI", "port hung", "Port %d bits DRQ/BSY won't clear.\n", port);
return false;
}
__sync_synchronize();
WR_REGISTER_PCI(port, 1 << 0);
// Wait for command to complete.
bool complete = false;
while (!timeout.Hit()) {
if (~RD_REGISTER_PCI(port) & (1 << 0)) {
complete = true;
break;
}
}
return complete;
}
void AHCIController::Initialise() {
// Perform BIOS/OS handoff, if necessary.
if (RD_REGISTER_CAP2() & (1 << 0)) {
KernelLog(LOG_INFO, "AHCI", "perform handoff", "Performing BIOS/OS handoff...\n");
WR_REGISTER_BOHC(RD_REGISTER_BOHC() | (1 << 1));
KTimeout timeout(25 /* ms */);
uint32_t status;
while (true) {
status = RD_REGISTER_BOHC();
if (~status & (1 << 0)) break;
if (timeout.Hit()) break;
}
if (status & (1 << 0)) {
KEvent event = {};
KernelLog(LOG_ERROR, "AHCI", "handoff error", "BIOS/OS handoff did not succeed, waiting 2 seconds to proceed...\n");
KEventWait(&event, 2000 /* ms */);
}
}
// Reset controller.
{
KTimeout timeout(GENERAL_TIMEOUT);
WR_REGISTER_GHC(RD_REGISTER_GHC() | (1 << 0));
while ((RD_REGISTER_GHC() & (1 << 0)) && !timeout.Hit());
if (timeout.Hit()) {
KernelLog(LOG_ERROR, "AHCI", "reset controller timeout", "The controller did not reset within the timeout.\n");
return;
}
}
// Register IRQ handler.
KIRQHandler handler = [] (uintptr_t, void *context) { return ((AHCIController *) context)->HandleIRQ(); };
if (!pci->EnableSingleInterrupt(handler, this, "AHCI")) {
KernelLog(LOG_ERROR, "AHCI", "IRQ registration failure", "Could not register intrrupt handler.\n");
return;
}
// Enable AHCI mode and interrupts.
WR_REGISTER_GHC(RD_REGISTER_GHC() | (1 << 31) | (1 << 1));
capabilities = RD_REGISTER_CAP();
capabilities2 = RD_REGISTER_CAP2();
commandSlotCount = ((capabilities >> 8) & 31) + 1;
dma64Supported = capabilities & (1 << 31);
#ifdef ARCH_64
if (!dma64Supported) {
KernelLog(LOG_ERROR, "AHCI", "controller cannot DMA", "The controller reports it cannot use 64-bit addresses in DMA transfer.\n");
return;
}
#endif
// Work out which ports have drives connected.
size_t maximumNumberOfPorts = (capabilities & 31) + 1;
size_t portsFound = 0;
uint32_t portsImplemented = RD_REGISTER_PI();
for (uintptr_t i = 0; i < MAX_PORTS; i++) {
if (portsImplemented & (1 << i)) {
portsFound++;
if (portsFound <= maximumNumberOfPorts) {
ports[i].connected = true;
}
}
}
KernelLog(LOG_INFO, "AHCI", "information", "Capabilities: %x, %x. Command slot count: %d. Implemented ports: %x.\n",
capabilities, capabilities2, commandSlotCount, portsImplemented);
// Setup the command lists, FISes and command tables.
for (uintptr_t i = 0; i < MAX_PORTS; i++) {
if (!ports[i].connected) continue;
size_t bytesNeeded = COMMAND_LIST_SIZE + RECEIVED_FIS_SIZE + COMMAND_TABLE_SIZE * commandSlotCount;
uint8_t *virtualAddress;
uintptr_t physicalAddress;
if (!MMPhysicalAllocateAndMap(bytesNeeded, K_PAGE_SIZE, dma64Supported ? 64 : 32, true, MM_REGION_NOT_CACHEABLE, &virtualAddress, &physicalAddress)) {
KernelLog(LOG_ERROR, "AHCI", "allocation failure", "Could not allocate physical memory for port %d.\n", i);
break;
}
ports[i].commandList = (uint32_t *) virtualAddress;
ports[i].commandTables = virtualAddress + COMMAND_LIST_SIZE + RECEIVED_FIS_SIZE;
// Set the registers to the physical addresses.
WR_REGISTER_PCLB(i, physicalAddress);
if (dma64Supported) WR_REGISTER_PCLBU(i, physicalAddress >> 32);
WR_REGISTER_PFB(i, (physicalAddress + 0x400));
if (dma64Supported) WR_REGISTER_PFBU(i, (physicalAddress + 0x400) >> 32);
// Point each command list entry to the corresponding command table.
uint32_t *commandList = ports[i].commandList;
for (uintptr_t j = 0; j < commandSlotCount; j++) {
uintptr_t address = physicalAddress + COMMAND_LIST_SIZE + RECEIVED_FIS_SIZE + COMMAND_TABLE_SIZE * j;
commandList[j * 8 + 2] = address;
commandList[j * 8 + 3] = address >> 32;
}
// Reset the port.
KTimeout timeout(GENERAL_TIMEOUT);
const uint32_t runningBits = ((1 << 0 /* start */) | (1 << 4 /* receive FIS enable */)
| (1 << 15 /* command list running */) | (1 << 14 /* receive FIS running */));
while (true) {
uint32_t status = RD_REGISTER_PCMD(i);
if (!(status & runningBits) || timeout.Hit()) {
break;
}
// Stop command list processing and receive FIS.
WR_REGISTER_PCMD(i, status & ~((1 << 0) | (1 << 4)));
}
if (RD_REGISTER_PCMD(i) & runningBits) {
KernelLog(LOG_ERROR, "AHCI", "reset port timeout", "Resetting port %d timed out. PCMD: %x.\n",
i, RD_REGISTER_PCMD(i));
ports[i].connected = false;
continue;
}
// Clear IRQs.
WR_REGISTER_PIE(i, RD_REGISTER_PIE(i) & 0x0E3FFF00);
WR_REGISTER_PIS(i, RD_REGISTER_PIS(i));
// Enable receive FIS and activate the drive.
WR_REGISTER_PSCTL(i, RD_REGISTER_PSCTL(i) | (3 << 8)); // Disable transitions to partial and slumber states.
WR_REGISTER_PCMD(i, (RD_REGISTER_PCMD(i) & 0x0FFFFFFF)
| (1 << 1 /* spin up */) | (1 << 2 /* power on */) | (1 << 4 /* FIS receive */) | (1 << 28 /* activate */));
KTimeout linkTimeout(10);
while ((RD_REGISTER_PSSTS(i) & 0x0F) != 3 && !linkTimeout.Hit());
if ((RD_REGISTER_PSSTS(i) & 0x0F) != 3) {
KernelLog(LOG_ERROR, "AHCI", "activate port timeout", "Activating port %d timed out. PSSTS: %x.\n",
i, RD_REGISTER_PSSTS(i));
ports[i].connected = false;
continue;
}
// Clear errors.
WR_REGISTER_PSERR(i, RD_REGISTER_PSERR(i));
// Wait for device to be ready.
while ((RD_REGISTER_PTFD(i) & 0x88 /* BSY and DRQ */) && !timeout.Hit());
if (RD_REGISTER_PTFD(i) & 0x88) {
KernelLog(LOG_ERROR, "AHCI", "port ready timeout", "Port %d hung at busy state. PTFD: %x.\n",
i, RD_REGISTER_PTFD(i));
ports[i].connected = false;
continue;
}
// Start command list processing.
KernelLog(LOG_INFO, "AHCI", "start command processing", "Starting command processing for port %d...\n", i);
WR_REGISTER_PCMD(i, RD_REGISTER_PCMD(i) | (1 << 0));
// Enable interrupts.
KernelLog(LOG_INFO, "AHCI", "enable interrupts", "Enabling interrupts for port %d...\n", i);
WR_REGISTER_PIE(i, RD_REGISTER_PIE(i) | (1 << 5) /* descriptor complete */ | (1 << 0) /* D2H */
| (1 << 30) | (1 << 29) | (1 << 28) | (1 << 27) | (1 << 26) | (1 << 24) | (1 << 23) /* errors */);
}
// Read the status and signature for each implemented port to work out if it is connected.
for (uintptr_t i = 0; i < MAX_PORTS; i++) {
if (!ports[i].connected) continue;
uint32_t status = RD_REGISTER_PSSTS(i);
if ((status & 0x00F) != 0x003 || (status & 0x0F0) == 0x000 || (status & 0xF00) != 0x100) {
ports[i].connected = false;
KernelLog(LOG_INFO, "AHCI", "no drive", "No drive connected to port %d (1).\n", i);
continue;
}
uint32_t signature = RD_REGISTER_PSIG(i);
if (signature == 0x00000101) {
// SATA drive.
KernelLog(LOG_INFO, "AHCI", "found drive", "Found SATA drive on port %d.\n", i);
} else if (signature == 0xEB140101) {
// SATAPI drive.
ports[i].atapi = true;
KernelLog(LOG_INFO, "AHCI", "found drive", "Found SATAPI drive on port %d.\n", i);
} else if (!signature) {
// No drive connected.
ports[i].connected = false;
KernelLog(LOG_INFO, "AHCI", "no drive", "No drive connected to port %d (2).\n", i);
} else {
KernelLog(LOG_ERROR, "AHCI", "unrecognised drive signature", "Unrecognised drive signature %x on port %d.\n", signature, i);
ports[i].connected = false;
}
}
// Identify each connected drive.
uint16_t *identifyData;
uintptr_t identifyDataPhysical;
if (!MMPhysicalAllocateAndMap(0x200, K_PAGE_SIZE, dma64Supported ? 64 : 32, true, MM_REGION_NOT_CACHEABLE, (uint8_t **) &identifyData, &identifyDataPhysical)) {
KernelLog(LOG_ERROR, "AHCI", "allocation failure", "Could not allocate physical memory for identify data buffer.\n");
return;
}
KernelLog(LOG_INFO, "AHCI", "identify data", "Identify data buffer allocated at physical address %x.\n", identifyDataPhysical);
for (uintptr_t i = 0; i < MAX_PORTS; i++) {
if (!ports[i].connected) continue;
EsMemoryZero(identifyData, 0x200);
// Setup the command list entry.
ports[i].commandList[0] = 5 /* FIS is 5 DWORDs */ | (1 << 16) /* 1 PRDT entry */;
ports[i].commandList[1] = 0;
// Setup the command FIS.
uint8_t opcode = ports[i].atapi ? 0xA1 /* IDENTIFY PACKET */ : 0xEC /* IDENTIFY */;
uint32_t *commandFIS = (uint32_t *) ports[i].commandTables;
commandFIS[0] = 0x27 /* H2D */ | (1 << 15) /* command */ | (opcode << 16);
commandFIS[1] = commandFIS[2] = commandFIS[3] = commandFIS[4] = 0;
// Setup the PRDT.
uint32_t *prdt = (uint32_t *) (ports[i].commandTables + 0x80);
prdt[0] = identifyDataPhysical;
prdt[1] = identifyDataPhysical >> 32;
prdt[2] = 0;
prdt[3] = 0x200 - 1;
KernelLog(LOG_INFO, "AHCI", "identifying drive", "Sending IDENTIFY command to port %d...\n", i);
if (!SendSingleCommand(i)) {
KernelLog(LOG_ERROR, "AHCI", "identify failure", "Could not read identify data for port %d.\n", i);
WR_REGISTER_PCMD(i, RD_REGISTER_PCMD(i) & ~(1 << 0)); // Stop command processing.
ports[i].connected = false;
continue;
}
ports[i].sectorBytes = 0x200;
if ((identifyData[106] & (1 << 14)) && (~identifyData[106] & (1 << 15)) && (identifyData[106] & (1 << 12))) {
// Device has a logical sector size larger than 0x200 bytes.
ports[i].sectorBytes = (uint32_t) identifyData[117] | ((uint32_t) identifyData[118] << 16);
}
ports[i].sectorCount = ((uint64_t) identifyData[100] << 0) + ((uint64_t) identifyData[101] << 16)
+ ((uint64_t) identifyData[102] << 32) + ((uint64_t) identifyData[103] << 48);
if (!((identifyData[49] & (1 << 9)) && (identifyData[49] & (1 << 8)))) {
KernelLog(LOG_ERROR, "AHCI", "unsupported feature", "Drive on port %d does not support a required feature.\n", i);
ports[i].connected = false;
continue;
}
if (ports[i].atapi) {
// Send a read capacity command.
ports[i].commandList[0] = 5 /* FIS is 5 DWORDs */ | (1 << 16) /* 1 PRDT entry */ | (1 << 5) /* ATAPI */;
commandFIS[0] = 0x27 /* H2D */ | (1 << 15) /* command */ | (0xA0 /* packet */ << 16);
commandFIS[1] = 8 /* maximum byte count transfer */ << 8;
prdt[3] = 8 - 1;
uint8_t *scsiCommand = (uint8_t *) commandFIS + 0x40;
EsMemoryZero(scsiCommand, 10);
scsiCommand[0] = 0x25 /* READ CAPACITY (10) */;
if (!SendSingleCommand(i)) {
KernelLog(LOG_ERROR, "AHCI", "identify failure", "Could not read SCSI read capacity data for port %d.\n", i);
WR_REGISTER_PCMD(i, RD_REGISTER_PCMD(i) & ~(1 << 0)); // Stop command processing.
ports[i].connected = false;
continue;
}
uint8_t *capacity = (uint8_t *) identifyData;
ports[i].sectorCount = (((uint64_t) capacity[3] << 0) + ((uint64_t) capacity[2] << 8)
+ ((uint64_t) capacity[1] << 16) + ((uint64_t) capacity[0] << 24)) + 1;
ports[i].sectorBytes = ((uint64_t) capacity[7] << 0) + ((uint64_t) capacity[6] << 8)
+ ((uint64_t) capacity[5] << 16) + ((uint64_t) capacity[4] << 24);
}
if (ports[i].sectorCount <= 128 || (ports[i].sectorBytes & 0x1FF) || !ports[i].sectorBytes || ports[i].sectorBytes > 0x1000) {
KernelLog(LOG_ERROR, "AHCI", "unsupported feature", "Drive on port %d has invalid sector configuration (count: %d, size: %D).\n",
i, ports[i].sectorCount, ports[i].sectorBytes);
ports[i].connected = false;
continue;
}
for (uintptr_t j = 0; j < 20; j++) {
ports[i].model[j * 2 + 0] = identifyData[27 + j] >> 8;
ports[i].model[j * 2 + 1] = identifyData[27 + j] & 0xFF;
}
ports[i].model[40] = 0;
for (uintptr_t j = 39; j > 0; j--) {
if (ports[i].model[j] == ' ') {
ports[i].model[j] = 0;
} else {
break;
}
}
ports[i].ssd = identifyData[217] == 1;
for (uintptr_t i = 10; i < 20; i++) identifyData[i] = (identifyData[i] >> 8) | (identifyData[i] << 8);
for (uintptr_t i = 23; i < 27; i++) identifyData[i] = (identifyData[i] >> 8) | (identifyData[i] << 8);
for (uintptr_t i = 27; i < 47; i++) identifyData[i] = (identifyData[i] >> 8) | (identifyData[i] << 8);
KernelLog(LOG_INFO, "AHCI", "identified drive", "Identified drive on port %d; serial - '%s', firmware - '%s', model - '%s', sector size - %D, sector count - %d.\n",
i, 20, identifyData + 10, 8, identifyData + 23, 40, identifyData + 27, ports[i].sectorBytes, ports[i].sectorCount);
}
MMFree(MMGetKernelSpace(), identifyData);
MMPhysicalFree(identifyDataPhysical);
// Start the timeout timer.
KTimerSet(&timeoutTimer, GENERAL_TIMEOUT, TimeoutTimerHit, this);
// Register drives.
for (uintptr_t i = 0; i < MAX_PORTS; i++) {
if (!ports[i].connected) continue;
AHCIDrive *device = (AHCIDrive *) KDeviceCreate("AHCI drive", this, sizeof(AHCIDrive));
if (!device) {
KernelLog(LOG_ERROR, "AHCI", "allocation failure", "Could not create device for port %d.\n", i);
break;
}
device->controller = this;
device->port = i;
device->sectorSize = ports[i].sectorBytes;
device->sectorCount = ports[i].sectorCount;
device->maxAccessSectorCount = ports[i].atapi ? (65535 / device->sectorSize)
: ((PRDT_ENTRY_COUNT - 1 /* need one extra if not page aligned */) * K_PAGE_SIZE / device->sectorSize);
device->readOnly = ports[i].atapi;
device->cModel = ports[i].model;
device->driveType = ports[i].atapi ? ES_DRIVE_TYPE_CDROM : ports[i].ssd ? ES_DRIVE_TYPE_SSD : ES_DRIVE_TYPE_HDD;
device->access = [] (KBlockDeviceAccessRequest request) {
AHCIDrive *drive = (AHCIDrive *) request.device;
request.dispatchGroup->Start();
if (!drive->controller->Access(drive->port, request.offset, request.count, request.operation,
request.buffer, request.flags, request.dispatchGroup)) {
request.dispatchGroup->End(false);
}
};
FSRegisterBlockDevice(device);
}
}
static void DeviceAttach(KDevice *_parent) {
KPCIDevice *parent = (KPCIDevice *) _parent;
AHCIController *device = (AHCIController *) KDeviceCreate("AHCI controller", parent, sizeof(AHCIController));
if (!device) return;
device->pci = parent;
device->dumpState = [] (KDevice *device) {
((AHCIController *) device)->DumpState();
};
// Enable PCI features.
parent->EnableFeatures(K_PCI_FEATURE_INTERRUPTS
| K_PCI_FEATURE_BUSMASTERING_DMA
| K_PCI_FEATURE_MEMORY_SPACE_ACCESS
| K_PCI_FEATURE_BAR_5);
// Initialise the controller.
device->Initialise();
}
KDriver driverAHCI = {
.attach = DeviceAttach,
};

81
drivers/bga.cpp Normal file
View File

@ -0,0 +1,81 @@
#include <module.h>
struct BGADisplay : KGraphicsTarget {
};
BGADisplay *vboxDisplay;
void BGAUpdateScreen(K_USER_BUFFER const uint8_t *source, uint32_t sourceWidth, uint32_t sourceHeight, uint32_t sourceStride,
uint32_t destinationX, uint32_t destinationY) {
GraphicsUpdateScreen32(source, sourceWidth, sourceHeight, sourceStride, destinationX, destinationY,
vboxDisplay->screenWidth, vboxDisplay->screenHeight,
vboxDisplay->screenWidth * 4,
((KPCIDevice *) vboxDisplay->parent)->baseAddressesVirtual[0]);
}
void BGADebugPutBlock(uintptr_t x, uintptr_t y, bool toggle) {
GraphicsDebugPutBlock32(x, y, toggle,
vboxDisplay->screenWidth, vboxDisplay->screenHeight,
vboxDisplay->screenWidth * 4,
((KPCIDevice *) vboxDisplay->parent)->baseAddressesVirtual[0]);
}
void BGADebugClearScreen() {
GraphicsDebugClearScreen32(vboxDisplay->screenWidth, vboxDisplay->screenHeight,
vboxDisplay->screenWidth * 4,
((KPCIDevice *) vboxDisplay->parent)->baseAddressesVirtual[0]);
}
void BGADeviceAttached(KDevice *_parent) {
KPCIDevice *parent = (KPCIDevice *) _parent;
BGADisplay *device = (BGADisplay *) KDeviceCreate("BGA", parent, sizeof(BGADisplay));
if (!device) return;
parent->EnableFeatures(K_PCI_FEATURE_IO_PORT_ACCESS | K_PCI_FEATURE_MEMORY_SPACE_ACCESS | K_PCI_FEATURE_BAR_0);
if (KGraphicsIsTargetRegistered()) {
return;
}
ProcessorOut16(0x01CE, 0 /* version */);
uint16_t version = ProcessorIn16(0x01CF);
KernelLog(LOG_INFO, "BGA", "version", "Detected version %X%X.\n", version >> 8, version);
if (version < 0xB0C0 || version > 0xB0C5) {
KernelLog(LOG_INFO, "BGA", "unsupported version", "Currently, the only supported versions are B0C0-B0C5.\n");
return;
}
// Set the mode.
ProcessorOut16(0x01CE, 4 /* enable */);
ProcessorOut16(0x01CF, 0);
ProcessorOut16(0x01CE, 1 /* x resolution */);
ProcessorOut16(0x01CF, BGA_RESOLUTION_WIDTH);
ProcessorOut16(0x01CE, 2 /* y resolution */);
ProcessorOut16(0x01CF, BGA_RESOLUTION_HEIGHT);
ProcessorOut16(0x01CE, 3 /* bpp */);
ProcessorOut16(0x01CF, 32);
ProcessorOut16(0x01CE, 4 /* enable */);
ProcessorOut16(0x01CF, 0x41 /* linear frame-buffer */);
// Setup the graphics target.
device->updateScreen = BGAUpdateScreen;
device->debugPutBlock = BGADebugPutBlock;
device->debugClearScreen = BGADebugClearScreen;
ProcessorOut16(0x01CE, 1 /* x resolution */);
device->screenWidth = ProcessorIn16(0x01CF);
ProcessorOut16(0x01CE, 2 /* y resolution */);
device->screenHeight = ProcessorIn16(0x01CF);
// Register the display.
KernelLog(LOG_INFO, "BGA", "register target",
"Registering graphics target with resolution %d by %d. Linear framebuffer is at virtual address %x.\n",
device->screenWidth, device->screenHeight, parent->baseAddressesVirtual[0]);
vboxDisplay = device;
KRegisterGraphicsTarget(device);
}
KDriver driverBGA = {
.attach = BGADeviceAttached,
};

2009
drivers/esfs2.cpp Normal file

File diff suppressed because it is too large Load Diff

650
drivers/ext2.cpp Normal file
View File

@ -0,0 +1,650 @@
// TODO Validation of all fields.
// TODO Contiguous block reading in Scan and Enumerate.
// TODO Make GetDataBlock use (not yet implemented) system block cache.
#include <module.h>
struct SuperBlock {
uint32_t inodeCount;
uint32_t blockCount;
uint32_t reservedBlockCount;
uint32_t unallocatedBlockCount;
uint32_t unallocatedInodeCount;
uint32_t superBlockContainer;
uint32_t blockSizeExponent;
uint32_t fragmentSizeExponent;
uint32_t blocksPerBlockGroup;
uint32_t fragmentsPerBlockGroup;
uint32_t inodesPerBlockGroup;
uint32_t lastMountTime;
uint32_t lastWriteTime;
uint16_t mountsSinceLastCheck;
uint16_t mountsAllowedBetweenChecks;
uint16_t signature;
uint16_t state;
uint16_t errorHandling;
uint16_t minorVersion;
uint32_t lastCheckTime;
uint32_t intervalCheckTime;
uint32_t creatorID;
uint32_t majorVersion;
uint16_t superUserID;
uint16_t superGroupID;
uint32_t firstNonReservedInode;
uint16_t inodeStructureBytes;
uint16_t blockGroupOfSuperBlock;
uint32_t optionalFeatures;
uint32_t requiredFeatures;
uint32_t writeFeatures;
uint8_t fileSystemID[16];
uint8_t volumeName[16];
uint8_t lastMountPath[64];
uint32_t compressionAlgorithms;
uint8_t preallocateFileBlocks;
uint8_t preallocateDirectoryBlocks;
uint16_t _unused0;
uint8_t journalID[16];
uint32_t journalInode;
uint32_t journalDevice;
uint32_t orphanInodeList;
};
struct BlockGroupDescriptor {
uint32_t blockUsageBitmap;
uint32_t inodeUsageBitmap;
uint32_t inodeTable;
uint16_t unallocatedBlockCount;
uint16_t unallocatedInodeCount;
uint16_t directoryCount;
uint8_t _unused1[14];
};
struct Inode {
#define INODE_TYPE_DIRECTORY (0x4000)
#define INODE_TYPE_REGULAR (0x8000)
uint16_t type;
uint16_t userID;
uint32_t fileSizeLow;
uint32_t accessTime;
uint32_t creationTime;
uint32_t modificationTime;
uint32_t deletionTime;
uint16_t groupID;
uint16_t hardLinkCount;
uint32_t usedSectorCount;
uint32_t flags;
uint32_t _unused0;
uint32_t directBlockPointers[12];
uint32_t indirectBlockPointers[3];
uint32_t generation;
uint32_t extendedAttributeBlock;
uint32_t fileSizeHigh;
uint32_t fragmentBlock;
uint8_t osSpecific[12];
};
struct DirectoryEntry {
uint32_t inode;
uint16_t entrySize;
uint8_t nameLengthLow;
union {
uint8_t nameLengthHigh;
#define DIRENT_TYPE_REGULAR (1)
#define DIRENT_TYPE_DIRECTORY (2)
uint8_t type;
};
// Followed by name.
};
struct FSNode {
struct Volume *volume;
Inode inode;
};
struct Volume : KDevice {
KFileSystem *fileSystem;
SuperBlock superBlock;
BlockGroupDescriptor *blockGroupDescriptorTable;
size_t blockBytes;
};
static bool Mount(Volume *volume) {
#define MOUNT_FAILURE(message) do { KernelLog(LOG_ERROR, "Ext2", "mount failure", "Mount - " message); return false; } while (0)
// Load the superblock.
uint8_t *sectorBuffer = (uint8_t *) EsHeapAllocate(volume->fileSystem->block->sectorSize, false, K_FIXED);
if (!sectorBuffer) {
MOUNT_FAILURE("Could not allocate buffer.\n");
}
EsDefer(EsHeapFree(sectorBuffer, volume->fileSystem->block->sectorSize, K_FIXED));
{
if (!volume->fileSystem->Access(1024, volume->fileSystem->block->sectorSize, K_ACCESS_READ, sectorBuffer, ES_FLAGS_DEFAULT)) {
MOUNT_FAILURE("Could not read boot sector.\n");
}
EsMemoryCopy(&volume->superBlock, sectorBuffer, sizeof(SuperBlock));
if (volume->superBlock.majorVersion < 1) {
MOUNT_FAILURE("Volumes below major version 1 not supprted.\n");
}
if (volume->superBlock.requiredFeatures != 2) {
MOUNT_FAILURE("Volume uses unsupported features that are required to read it.\n");
}
if (volume->superBlock.inodeStructureBytes < sizeof(Inode)) {
MOUNT_FAILURE("Inode structure size too small.\n");
}
volume->blockBytes = 1024 << volume->superBlock.blockSizeExponent;
if (volume->blockBytes < volume->fileSystem->block->sectorSize) {
MOUNT_FAILURE("Block size smaller than drive sector size.\n");
}
}
// Load the block group descriptor table.
{
uint32_t blockGroupCount = (volume->superBlock.blockCount + volume->superBlock.blocksPerBlockGroup - 1) / volume->superBlock.blocksPerBlockGroup;
uint32_t firstBlockContainingBlockGroupDescriptorTable = volume->blockBytes == 1024 ? 2 : 1;
uint32_t blockGroupDescriptorTableLengthInBlocks = (blockGroupCount * sizeof(BlockGroupDescriptor) + volume->blockBytes - 1) / volume->blockBytes;
volume->blockGroupDescriptorTable = (BlockGroupDescriptor *) EsHeapAllocate(blockGroupDescriptorTableLengthInBlocks * volume->blockBytes, false, K_FIXED);
if (!volume->blockGroupDescriptorTable) {
MOUNT_FAILURE("Could not allocate the block group descriptor table.\n");
}
if (!volume->fileSystem->Access(firstBlockContainingBlockGroupDescriptorTable * volume->blockBytes,
blockGroupDescriptorTableLengthInBlocks * volume->blockBytes,
K_ACCESS_READ, volume->blockGroupDescriptorTable, ES_FLAGS_DEFAULT)) {
MOUNT_FAILURE("Could not read the block group descriptor table from the drive.\n");
}
}
// Load the root directory.
{
uint32_t inode = 2;
uint32_t blockGroup = (inode - 1) / volume->superBlock.inodesPerBlockGroup;
uint32_t indexInInodeTable = (inode - 1) % volume->superBlock.inodesPerBlockGroup;
uint32_t sectorInInodeTable = (indexInInodeTable * volume->superBlock.inodeStructureBytes) / volume->fileSystem->block->sectorSize;
uint32_t offsetInSector = (indexInInodeTable * volume->superBlock.inodeStructureBytes) % volume->fileSystem->block->sectorSize;
BlockGroupDescriptor *blockGroupDescriptor = volume->blockGroupDescriptorTable + blockGroup;
if (!volume->fileSystem->Access(blockGroupDescriptor->inodeTable * volume->blockBytes
+ sectorInInodeTable * volume->fileSystem->block->sectorSize,
volume->fileSystem->block->sectorSize,
K_ACCESS_READ, sectorBuffer, ES_FLAGS_DEFAULT)) {
MOUNT_FAILURE("Could not read the inode table.\n");
}
volume->fileSystem->rootDirectory->driverNode = EsHeapAllocate(sizeof(FSNode), true, K_FIXED);
if (!volume->fileSystem->rootDirectory->driverNode) {
MOUNT_FAILURE("Could not allocate root node.\n");
}
FSNode *root = (FSNode *) volume->fileSystem->rootDirectory->driverNode;
root->volume = volume;
EsMemoryCopy(&root->inode, sectorBuffer + offsetInSector, sizeof(Inode));
volume->fileSystem->rootDirectoryInitialChildren = root->inode.fileSizeLow / sizeof(DirectoryEntry); // TODO This is a terrible upper-bound!
if ((root->inode.type & 0xF000) != INODE_TYPE_DIRECTORY) {
MOUNT_FAILURE("Root directory is not a directory.\n");
}
}
return true;
}
static uint32_t GetDataBlock(Volume *volume, Inode *node, uint64_t blockIndex, uint8_t *blockBuffer) {
#define CHECK_BLOCK_INDEX() if (offset == 0 || offset / volume->fileSystem->block->sectorSize > volume->fileSystem->block->sectorCount) { \
KernelLog(LOG_ERROR, "Ext2", "invalid block index", "GetDataBlock - Block out of bounds.\n"); return 0; }
#define GET_DATA_BLOCK_ACCESS_FAILURE() do { KernelLog(LOG_ERROR, "Ext2", "block access failure", "GetDataBlock - Could not read block.\n"); return 0; } while (0)
size_t blockPointersPerBlock = volume->blockBytes / 4;
uint32_t *blockPointers = (uint32_t *) blockBuffer;
uint64_t offset;
if (blockIndex < 12) {
offset = node->directBlockPointers[blockIndex];
CHECK_BLOCK_INDEX();
return offset;
}
blockIndex -= 12;
if (blockIndex < blockPointersPerBlock) {
offset = node->indirectBlockPointers[0] * volume->blockBytes;
CHECK_BLOCK_INDEX();
if (!volume->fileSystem->Access(offset, volume->blockBytes, K_ACCESS_READ, blockBuffer, ES_FLAGS_DEFAULT)) GET_DATA_BLOCK_ACCESS_FAILURE();
offset = blockPointers[blockIndex];
CHECK_BLOCK_INDEX();
return offset;
}
blockIndex -= blockPointersPerBlock;
if (blockIndex < blockPointersPerBlock * blockPointersPerBlock) {
offset = node->indirectBlockPointers[1] * volume->blockBytes;
CHECK_BLOCK_INDEX();
if (!volume->fileSystem->Access(offset, volume->blockBytes, K_ACCESS_READ, blockBuffer, ES_FLAGS_DEFAULT)) GET_DATA_BLOCK_ACCESS_FAILURE();
offset = blockPointers[blockIndex / blockPointersPerBlock];
CHECK_BLOCK_INDEX();
if (!volume->fileSystem->Access(offset, volume->blockBytes, K_ACCESS_READ, blockBuffer, ES_FLAGS_DEFAULT)) GET_DATA_BLOCK_ACCESS_FAILURE();
offset = blockPointers[blockIndex % blockPointersPerBlock];
CHECK_BLOCK_INDEX();
return offset;
}
blockIndex -= blockPointersPerBlock * blockPointersPerBlock;
if (blockIndex < blockPointersPerBlock * blockPointersPerBlock * blockPointersPerBlock) {
offset = node->indirectBlockPointers[2] * volume->blockBytes;
CHECK_BLOCK_INDEX();
if (!volume->fileSystem->Access(offset, volume->blockBytes, K_ACCESS_READ, blockBuffer, ES_FLAGS_DEFAULT)) GET_DATA_BLOCK_ACCESS_FAILURE();
offset = blockPointers[blockIndex / blockPointersPerBlock / blockPointersPerBlock];
CHECK_BLOCK_INDEX();
if (!volume->fileSystem->Access(offset, volume->blockBytes, K_ACCESS_READ, blockBuffer, ES_FLAGS_DEFAULT)) GET_DATA_BLOCK_ACCESS_FAILURE();
offset = blockPointers[(blockIndex / blockPointersPerBlock) % blockPointersPerBlock];
CHECK_BLOCK_INDEX();
if (!volume->fileSystem->Access(offset, volume->blockBytes, K_ACCESS_READ, blockBuffer, ES_FLAGS_DEFAULT)) GET_DATA_BLOCK_ACCESS_FAILURE();
offset = blockPointers[blockIndex % blockPointersPerBlock];
CHECK_BLOCK_INDEX();
return offset;
}
KernelLog(LOG_ERROR, "Ext2", "invalid index in inode", "GetDataBlock - Index %d out of bounds.\n", blockIndex);
return 0;
}
static EsError Enumerate(KNode *node) {
#define ENUMERATE_FAILURE(message) do { KernelLog(LOG_ERROR, "Ext2", "enumerate failure", "Enumerate - " message); return ES_ERROR_UNKNOWN; } while (0)
FSNode *directory = (FSNode *) node->driverNode;
Volume *volume = directory->volume;
uint8_t *blockBuffer = (uint8_t *) EsHeapAllocate(volume->blockBytes, false, K_FIXED);
if (!blockBuffer) {
ENUMERATE_FAILURE("Could not allocate buffer.\n");
}
EsDefer(EsHeapFree(blockBuffer, volume->blockBytes, K_FIXED));
uint32_t blocksInDirectory = directory->inode.fileSizeLow / volume->blockBytes;
for (uintptr_t i = 0; i < blocksInDirectory; i++) {
uint32_t block = GetDataBlock(volume, &directory->inode, i, blockBuffer);
if (!block) {
return ES_ERROR_DRIVE_CONTROLLER_REPORTED;
}
if (!volume->fileSystem->Access((uint64_t) block * volume->blockBytes, volume->blockBytes, K_ACCESS_READ, blockBuffer, ES_FLAGS_DEFAULT)) {
ENUMERATE_FAILURE("Could not read block.\n");
}
uintptr_t positionInBlock = 0;
while (positionInBlock + sizeof(DirectoryEntry) < volume->blockBytes) {
DirectoryEntry *entry = (DirectoryEntry *) (blockBuffer + positionInBlock);
if (entry->entrySize > volume->blockBytes - positionInBlock
|| entry->nameLengthLow > volume->blockBytes - positionInBlock - sizeof(DirectoryEntry)) {
ENUMERATE_FAILURE("Invalid directory entry size.\n");
}
KNodeMetadata metadata = {};
const char *name = (const char *) (blockBuffer + positionInBlock + sizeof(DirectoryEntry));
size_t nameBytes = entry->nameLengthLow;
metadata.type = entry->type == DIRENT_TYPE_DIRECTORY ? ES_NODE_DIRECTORY : entry->type == DIRENT_TYPE_REGULAR ? ES_NODE_FILE : ES_NODE_INVALID;
if (metadata.type == ES_NODE_DIRECTORY) {
metadata.directoryChildren = ES_DIRECTORY_CHILDREN_UNKNOWN;
}
if (metadata.type != ES_NODE_INVALID
&& !(nameBytes == 1 && name[0] == '.')
&& !(nameBytes == 2 && name[0] == '.' && name[1] == '.')) {
EsError error = FSDirectoryEntryFound(node, &metadata, &entry->inode, name, nameBytes, false);
if (error != ES_SUCCESS) {
return error;
}
}
positionInBlock += entry->entrySize;
}
}
return ES_SUCCESS;
}
static EsError Scan(const char *name, size_t nameBytes, KNode *_directory) {
#define SCAN_FAILURE(message) do { KernelLog(LOG_ERROR, "Ext2", "scan failure", "Scan - " message); return ES_ERROR_UNKNOWN; } while (0)
if (nameBytes == 2 && name[0] == '.' && name[1] == '.') return ES_ERROR_FILE_DOES_NOT_EXIST;
if (nameBytes == 1 && name[0] == '.') return ES_ERROR_FILE_DOES_NOT_EXIST;
FSNode *directory = (FSNode *) _directory->driverNode;
Volume *volume = directory->volume;
DirectoryEntry *entry = nullptr;
uint8_t *blockBuffer = (uint8_t *) EsHeapAllocate(volume->blockBytes, false, K_FIXED);
if (!blockBuffer) {
SCAN_FAILURE("Could not allocate buffer.\n");
}
EsDefer(EsHeapFree(blockBuffer, volume->blockBytes, K_FIXED));
uint32_t blocksInDirectory = directory->inode.fileSizeLow / volume->blockBytes;
uint32_t inode = 0;
for (uintptr_t i = 0; i < blocksInDirectory; i++) {
uint32_t block = GetDataBlock(volume, &directory->inode, i, blockBuffer);
if (!block) {
return ES_ERROR_UNKNOWN;
}
if (!volume->fileSystem->Access((uint64_t) block * volume->blockBytes, volume->blockBytes, K_ACCESS_READ, blockBuffer, ES_FLAGS_DEFAULT)) {
SCAN_FAILURE("Could not read block.\n");
}
uintptr_t positionInBlock = 0;
while (positionInBlock + sizeof(DirectoryEntry) < volume->blockBytes) {
entry = (DirectoryEntry *) (blockBuffer + positionInBlock);
if (entry->entrySize > volume->blockBytes - positionInBlock
|| entry->nameLengthLow > volume->blockBytes - positionInBlock - sizeof(DirectoryEntry)) {
SCAN_FAILURE("Invalid directory entry size.\n");
}
if (entry->nameLengthLow == nameBytes && 0 == EsMemoryCompare(name, blockBuffer + positionInBlock + sizeof(DirectoryEntry), nameBytes)) {
inode = entry->inode;
goto foundInode;
}
positionInBlock += entry->entrySize;
}
}
return ES_ERROR_FILE_DOES_NOT_EXIST;
foundInode:;
if (inode >= volume->superBlock.inodeCount || inode == 0) {
SCAN_FAILURE("Invalid inode index.\n");
}
KNodeMetadata metadata = {};
if (entry->type == DIRENT_TYPE_DIRECTORY) {
metadata.type = ES_NODE_DIRECTORY;
metadata.directoryChildren = ES_DIRECTORY_CHILDREN_UNKNOWN;
} else if (entry->type == DIRENT_TYPE_REGULAR) {
metadata.type = ES_NODE_FILE;
} else {
SCAN_FAILURE("Unsupported file type.\n");
}
return FSDirectoryEntryFound(_directory, &metadata, &inode, name, nameBytes, false);
}
static EsError Load(KNode *_directory, KNode *node, KNodeMetadata *metadata, const void *entryData) {
uint32_t inode = *(uint32_t *) entryData;
FSNode *directory = (FSNode *) _directory->driverNode;
Volume *volume = directory->volume;
uint8_t *blockBuffer = (uint8_t *) EsHeapAllocate(volume->blockBytes, false, K_FIXED);
if (!blockBuffer) {
return ES_ERROR_INSUFFICIENT_RESOURCES;
}
EsDefer(EsHeapFree(blockBuffer, volume->blockBytes, K_FIXED));
uint32_t blockGroup = (inode - 1) / volume->superBlock.inodesPerBlockGroup;
uint32_t indexInInodeTable = (inode - 1) % volume->superBlock.inodesPerBlockGroup;
uint32_t sectorInInodeTable = (indexInInodeTable * volume->superBlock.inodeStructureBytes) / volume->fileSystem->block->sectorSize;
uint32_t offsetInSector = (indexInInodeTable * volume->superBlock.inodeStructureBytes) % volume->fileSystem->block->sectorSize;
BlockGroupDescriptor *blockGroupDescriptor = volume->blockGroupDescriptorTable + blockGroup;
if (!volume->fileSystem->Access(blockGroupDescriptor->inodeTable * volume->blockBytes
+ sectorInInodeTable * volume->fileSystem->block->sectorSize,
volume->fileSystem->block->sectorSize,
K_ACCESS_READ, blockBuffer, ES_FLAGS_DEFAULT)) {
return ES_ERROR_DRIVE_CONTROLLER_REPORTED;
}
FSNode *data = (FSNode *) EsHeapAllocate(sizeof(FSNode), true, K_FIXED);
if (!data) {
return ES_ERROR_INSUFFICIENT_RESOURCES;
}
data->volume = volume;
node->driverNode = data;
EsMemoryCopy(&data->inode, blockBuffer + offsetInSector, sizeof(Inode));
if ((data->inode.type & 0xF000) == INODE_TYPE_DIRECTORY) {
if (metadata->type != ES_NODE_DIRECTORY) {
EsHeapFree(data, sizeof(FSNode), K_FIXED);
return ES_ERROR_CORRUPT_DATA;
}
metadata->directoryChildren = data->inode.fileSizeLow / sizeof(DirectoryEntry); // TODO This is a terrible upper-bound!
} else if ((data->inode.type & 0xF000) == INODE_TYPE_REGULAR) {
if (metadata->type != ES_NODE_FILE) {
EsHeapFree(data, sizeof(FSNode), K_FIXED);
return ES_ERROR_CORRUPT_DATA;
}
metadata->totalSize = (uint64_t) data->inode.fileSizeLow | ((uint64_t) data->inode.fileSizeHigh << 32);
} else {
if (metadata->type != ES_NODE_INVALID) {
EsHeapFree(data, sizeof(FSNode), K_FIXED);
return ES_ERROR_CORRUPT_DATA;
}
}
return ES_SUCCESS;
}
struct ReadDispatchGroup : KWorkGroup {
uint64_t extentIndex;
uint64_t extentCount;
uint8_t *extentBuffer;
Volume *volume;
void QueueExtent() {
if (!extentCount) return;
volume->fileSystem->Access(extentIndex * volume->blockBytes,
volume->blockBytes * extentCount, K_ACCESS_READ, extentBuffer, ES_FLAGS_DEFAULT, this);
}
void QueueBlock(Volume *_volume, uint64_t index, uint8_t *buffer) {
if (extentIndex + extentCount == index && extentCount
&& extentBuffer + extentCount * volume->blockBytes == buffer) {
extentCount++;
} else {
QueueExtent();
extentIndex = index;
extentCount = 1;
extentBuffer = buffer;
volume = _volume;
}
}
bool Read() {
QueueExtent();
return Wait();
}
};
static size_t Read(KNode *node, void *_buffer, EsFileOffset offset, EsFileOffset count) {
#define READ_FAILURE(message) do { KernelLog(LOG_ERROR, "Ext2", "read failure", "Read - " message); return ES_ERROR_UNKNOWN; } while (0)
FSNode *file = (FSNode *) node->driverNode;
Volume *volume = file->volume;
uint8_t *blockBuffer = (uint8_t *) EsHeapAllocate(volume->blockBytes, false, K_FIXED);
if (!blockBuffer) {
READ_FAILURE("Could not allocate sector buffer.\n");
}
EsDefer(EsHeapFree(blockBuffer, volume->blockBytes, K_FIXED));
uint8_t *outputBuffer = (uint8_t *) _buffer;
uintptr_t outputPosition = 0;
uint32_t firstBlock = offset / volume->blockBytes,
lastBlock = (offset + count) / volume->blockBytes,
currentBlock = firstBlock;
ReadDispatchGroup dispatchGroup = {};
dispatchGroup.Initialise();
while (currentBlock <= lastBlock) {
uint32_t block = GetDataBlock(volume, &file->inode, currentBlock, blockBuffer);
if (!block) {
return false;
}
uintptr_t readStart = currentBlock == firstBlock ? (offset % volume->blockBytes) : 0;
uintptr_t readEnd = currentBlock == lastBlock ? ((offset + count) % volume->blockBytes) : volume->blockBytes;
bool readEntireBlock = readStart == 0 && readEnd == volume->blockBytes;
if (readEntireBlock) {
dispatchGroup.QueueBlock(volume, block, outputBuffer + outputPosition);
outputPosition += volume->blockBytes;
} else {
if (!volume->fileSystem->Access((uint64_t) block * volume->blockBytes, volume->blockBytes, K_ACCESS_READ, blockBuffer, ES_FLAGS_DEFAULT)) {
READ_FAILURE("Could not read blocks from drive.\n");
}
EsMemoryCopy(outputBuffer + outputPosition, blockBuffer + readStart, readEnd - readStart);
outputPosition += readEnd - readStart;
}
currentBlock++;
}
bool success = dispatchGroup.Read();
if (!success) {
READ_FAILURE("Could not read blocks from drive.\n");
return false;
}
return true;
}
static void Close(KNode *node) {
EsHeapFree(node->driverNode, sizeof(FSNode), K_FIXED);
}
static void DeviceAttach(KDevice *parent) {
Volume *volume = (Volume *) KDeviceCreate("ext2", parent, sizeof(Volume));
if (!volume) {
KernelLog(LOG_ERROR, "Ext2", "allocate error", "Could not allocate Volume structure.\n");
return;
}
volume->fileSystem = (KFileSystem *) parent;
if (volume->fileSystem->block->sectorSize & 0x1FF) {
KernelLog(LOG_ERROR, "Ext2", "incorrect sector size", "Expected sector size to be a multiple of 512, but drive's sectors are %D.\n",
volume->fileSystem->block->sectorSize);
KDeviceDestroy(volume);
return;
}
if (!Mount(volume)) {
KernelLog(LOG_ERROR, "Ext2", "mount failure", "Could not mount Ext2 volume.\n");
EsHeapFree(volume->fileSystem->rootDirectory->driverNode, 0, K_FIXED);
EsHeapFree(volume->blockGroupDescriptorTable, 0, K_FIXED);
KDeviceDestroy(volume);
return;
}
volume->fileSystem->read = Read;
volume->fileSystem->scan = Scan;
volume->fileSystem->load = Load;
volume->fileSystem->enumerate = Enumerate;
volume->fileSystem->close = Close;
volume->fileSystem->spaceTotal = volume->superBlock.blockCount * volume->blockBytes;
volume->fileSystem->spaceUsed = (volume->superBlock.blockCount - volume->superBlock.unallocatedBlockCount) * volume->blockBytes;
volume->fileSystem->nameBytes = sizeof(volume->superBlock.volumeName);
if (volume->fileSystem->nameBytes > sizeof(volume->fileSystem->name)) volume->fileSystem->nameBytes = sizeof(volume->fileSystem->name);
EsMemoryCopy(volume->fileSystem->name, volume->superBlock.volumeName, volume->fileSystem->nameBytes);
for (uintptr_t i = 0; i < volume->fileSystem->nameBytes; i++) {
if (!volume->fileSystem->name[i]) {
volume->fileSystem->nameBytes = i;
}
}
volume->fileSystem->directoryEntryDataBytes = sizeof(uint32_t);
volume->fileSystem->nodeDataBytes = sizeof(FSNode);
KernelLog(LOG_INFO, "Ext2", "register file system", "Registering file system with name '%s'.\n",
volume->fileSystem->nameBytes, volume->fileSystem->name);
FSRegisterFileSystem(volume->fileSystem);
}
KDriver driverExt2 = {
.attach = DeviceAttach,
};

514
drivers/fat.cpp Normal file
View File

@ -0,0 +1,514 @@
// TODO Validation of all fields.
// TODO Don't load entire FAT in memory.
// TODO Long file names.
#include <module.h>
#define SECTOR_SIZE (512)
struct SuperBlockCommon {
uint8_t _unused0[11];
uint16_t bytesPerSector;
uint8_t sectorsPerCluster;
uint16_t reservedSectors;
uint8_t fatCount;
uint16_t rootDirectoryEntries;
uint16_t totalSectors;
uint8_t mediaDescriptor;
uint16_t sectorsPerFAT16;
uint16_t sectorsPerTrack;
uint16_t heads;
uint32_t hiddenSectors;
uint32_t largeSectorCount;
} __attribute__((packed));
struct SuperBlock16 : SuperBlockCommon {
uint8_t deviceID;
uint8_t flags;
uint8_t signature;
uint32_t serial;
uint8_t label[11];
uint64_t systemIdentifier;
uint8_t _unused1[450];
} __attribute__((packed));
struct SuperBlock32 : SuperBlockCommon {
uint32_t sectorsPerFAT32;
uint16_t flags;
uint16_t version;
uint32_t rootDirectoryCluster;
uint16_t fsInfoSector;
uint16_t backupBootSector;
uint8_t _unused0[12];
uint8_t deviceID;
uint8_t flags2;
uint8_t signature;
uint32_t serial;
uint8_t label[11];
uint64_t systemIdentifier;
uint8_t _unused1[422];
} __attribute__((packed));
struct DirectoryEntry {
uint8_t name[11];
uint8_t attributes;
uint8_t _reserved0;
uint8_t creationTimeSeconds;
uint16_t creationTime;
uint16_t creationDate;
uint16_t accessedDate;
uint16_t firstClusterHigh;
uint16_t modificationTime;
uint16_t modificationDate;
uint16_t firstClusterLow;
uint32_t fileSizeBytes;
} __attribute__((packed));
struct Volume : KDevice {
KFileSystem *fileSystem;
union {
char _unused0[SECTOR_SIZE];
SuperBlock16 sb16;
SuperBlock32 sb32;
SuperBlockCommon superBlock;
};
KNode *root;
uint8_t *fat;
uintptr_t sectorOffset;
uint32_t terminateCluster;
#define TYPE_FAT12 (12)
#define TYPE_FAT16 (16)
#define TYPE_FAT32 (32)
int type;
DirectoryEntry *rootDirectory;
};
struct DirectoryEntryReference {
uint32_t cluster, offset;
};
struct FSNode {
Volume *volume;
DirectoryEntry entry;
// The root directory is loaded during fileSystem mount.
// If this is non-null, run directory data from here.
DirectoryEntry *rootDirectory;
};
static uint32_t NextCluster(Volume *volume, uint32_t currentCluster) {
if (volume->type == TYPE_FAT12) {
uint8_t byte1 = volume->fat[currentCluster * 3 / 2 + 0],
byte2 = volume->fat[currentCluster * 3 / 2 + 1];
if (currentCluster & 1) currentCluster = (byte2 << 4) + (byte1 >> 4);
else currentCluster = (byte2 << 8) + (byte1 >> 0);
return currentCluster & 0xFFF;
} else if (volume->type == TYPE_FAT16) {
return ((uint16_t *) volume->fat)[currentCluster];
} else if (volume->type == TYPE_FAT32) {
return ((uint32_t *) volume->fat)[currentCluster];
} else {
KernelPanic("[FAT] NextCluster - Unsupported FAT type.\n");
return 0;
}
}
static uint32_t CountUsedClusters(Volume *volume) {
size_t total = 0;
if (volume->type == TYPE_FAT12) {
total = volume->sb16.sectorsPerFAT16 * volume->superBlock.bytesPerSector * 2 / 3;
} else if (volume->type == TYPE_FAT16) {
total = volume->sb16.sectorsPerFAT16 * volume->superBlock.bytesPerSector / 2;
} else if (volume->type == TYPE_FAT32) {
total = volume->sb16.sectorsPerFAT16 * volume->superBlock.bytesPerSector / 4;
}
size_t count = 0;
for (uintptr_t i = 0; i < total; i++) {
if (NextCluster(volume, i)) {
count++;
}
}
return count;
}
static EsError Load(KNode *_directory, KNode *_node, KNodeMetadata *, const void *entryData) {
FSNode *directory = (FSNode *) _directory->driverNode;
Volume *volume = directory->volume;
SuperBlockCommon *superBlock = &volume->superBlock;
uint8_t *clusterBuffer = (uint8_t *) EsHeapAllocate(superBlock->sectorsPerCluster * SECTOR_SIZE, false, K_FIXED);
if (!clusterBuffer) return ES_ERROR_INSUFFICIENT_RESOURCES;
EsDefer(EsHeapFree(clusterBuffer, 0, K_FIXED));
DirectoryEntryReference reference = *(DirectoryEntryReference *) entryData;
DirectoryEntry entry;
if (!directory->rootDirectory) {
if (!volume->fileSystem->Access((reference.cluster * superBlock->sectorsPerCluster + volume->sectorOffset) * SECTOR_SIZE,
superBlock->sectorsPerCluster * SECTOR_SIZE, K_ACCESS_READ, (uint8_t *) clusterBuffer, ES_FLAGS_DEFAULT)) {
return ES_ERROR_UNKNOWN;
}
entry = *(DirectoryEntry *) (clusterBuffer + reference.offset);
} else {
entry = directory->rootDirectory[reference.offset];
}
FSNode *node = (FSNode *) EsHeapAllocate(sizeof(FSNode), true, K_FIXED);
if (!node) {
EsHeapFree(node, 0, K_FIXED);
return ES_ERROR_INSUFFICIENT_RESOURCES;
}
_node->driverNode = node;
node->volume = volume;
node->entry = entry;
return ES_SUCCESS;
}
static size_t Read(KNode *node, void *_buffer, EsFileOffset offset, EsFileOffset count) {
#define READ_FAILURE(message) do { KernelLog(LOG_ERROR, "FAT", "read failure", "Read - " message); return ES_ERROR_UNKNOWN; } while (0)
FSNode *file = (FSNode *) node->driverNode;
Volume *volume = file->volume;
SuperBlockCommon *superBlock = &volume->superBlock;
uint8_t *clusterBuffer = (uint8_t *) EsHeapAllocate(superBlock->sectorsPerCluster * SECTOR_SIZE, false, K_FIXED);
EsDefer(EsHeapFree(clusterBuffer, 0, K_FIXED));
if (!clusterBuffer) READ_FAILURE("Could not allocate cluster buffer.\n");
uint8_t *outputBuffer = (uint8_t *) _buffer;
uint64_t firstCluster = offset / (SECTOR_SIZE * superBlock->sectorsPerCluster);
uint32_t currentCluster = file->entry.firstClusterLow + (file->entry.firstClusterHigh << 16);
for (uintptr_t i = 0; i < firstCluster; i++) currentCluster = NextCluster(volume, currentCluster);
offset %= (SECTOR_SIZE * superBlock->sectorsPerCluster);
while (count) {
uint32_t bytesFromThisCluster = superBlock->sectorsPerCluster * SECTOR_SIZE - offset;
if (bytesFromThisCluster > count) bytesFromThisCluster = count;
if (!volume->fileSystem->Access((currentCluster * superBlock->sectorsPerCluster + volume->sectorOffset) * SECTOR_SIZE,
superBlock->sectorsPerCluster * SECTOR_SIZE, K_ACCESS_READ,
(uint8_t *) clusterBuffer, ES_FLAGS_DEFAULT)) {
READ_FAILURE("Could not read cluster.\n");
}
EsMemoryCopy(outputBuffer, clusterBuffer + offset, bytesFromThisCluster);
count -= bytesFromThisCluster, outputBuffer += bytesFromThisCluster, offset = 0;
currentCluster = NextCluster(volume, currentCluster);
}
return true;
}
static EsError Scan(const char *_name, size_t nameLength, KNode *node) {
#define SCAN_FAILURE(message) do { KernelLog(LOG_ERROR, "FAT", "scan failure", "Scan - " message); return ES_ERROR_UNKNOWN; } while (0)
#define SCAN_FAILURE_2(message) do { KernelLog(LOG_ERROR, "FAT", "scan failure", "Scan - " message); goto failure; } while (0)
uint8_t name[] = " ";
{
uintptr_t i = 0, j = 0;
bool inExtension = false;
while (i < nameLength) {
if (j == 11) return ES_ERROR_FILE_DOES_NOT_EXIST; // Name too long.
uint8_t c = _name[i++];
if (c == '.' && !inExtension) j = 8, inExtension = true;
else name[j++] = (c >= 'a' && c <= 'z') ? (c + 'A' - 'a') : c;
}
}
FSNode *directory = (FSNode *) node->driverNode;
Volume *volume = directory->volume;
SuperBlockCommon *superBlock = &volume->superBlock;
uint8_t *clusterBuffer = (uint8_t *) EsHeapAllocate(superBlock->sectorsPerCluster * SECTOR_SIZE, false, K_FIXED);
EsDefer(EsHeapFree(clusterBuffer, 0, K_FIXED));
if (!clusterBuffer) SCAN_FAILURE("Could not allocate cluster buffer.\n");
uint32_t currentCluster = directory->entry.firstClusterLow + (directory->entry.firstClusterHigh << 16);
uintptr_t directoryPosition = 0;
while (currentCluster < volume->terminateCluster) {
if (!directory->rootDirectory) {
if (!volume->fileSystem->Access((currentCluster * superBlock->sectorsPerCluster + volume->sectorOffset) * SECTOR_SIZE,
superBlock->sectorsPerCluster * SECTOR_SIZE, K_ACCESS_READ, (uint8_t *) clusterBuffer, ES_FLAGS_DEFAULT)) {
SCAN_FAILURE("Could not read cluster.\n");
}
}
for (uintptr_t i = 0; i < superBlock->sectorsPerCluster * SECTOR_SIZE / sizeof(DirectoryEntry); i++, directoryPosition++) {
DirectoryEntry *entry = directory->rootDirectory ? (directory->rootDirectory + directoryPosition) : ((DirectoryEntry *) clusterBuffer + i);
if (entry->name[0] == 0xE5 || entry->attributes == 0x0F || (entry->attributes & 8)) goto nextEntry;
if (!entry->name[0]) return ES_ERROR_FILE_DOES_NOT_EXIST;
for (uintptr_t j = 0; j < 11; j++) {
uint8_t c = entry->name[j];
if (name[j] != ((c >= 'a' && c <= 'z') ? (c + 'A' - 'a') : c)) {
goto nextEntry;
}
}
{
KNodeMetadata metadata = {};
metadata.type = (entry->attributes & 0x10) ? ES_NODE_DIRECTORY : ES_NODE_FILE;
if (metadata.type == ES_NODE_FILE) {
metadata.totalSize = entry->fileSizeBytes;
} else if (metadata.type == ES_NODE_DIRECTORY) {
uint32_t currentCluster = entry->firstClusterLow + (entry->firstClusterHigh << 16);
while (currentCluster < volume->terminateCluster) {
currentCluster = NextCluster(volume, currentCluster);
metadata.directoryChildren += SECTOR_SIZE * superBlock->sectorsPerCluster / sizeof(DirectoryEntry);
}
}
DirectoryEntryReference reference = {};
reference.cluster = directory->rootDirectory ? 0 : currentCluster;
reference.offset = directory->rootDirectory ? directoryPosition : i;
return FSDirectoryEntryFound(node, &metadata, &reference, _name, nameLength, false);
}
nextEntry:;
}
if (!directory->rootDirectory) {
currentCluster = NextCluster(volume, currentCluster);
}
}
return ES_ERROR_FILE_DOES_NOT_EXIST;
}
static EsError Enumerate(KNode *node) {
#define ENUMERATE_FAILURE(message) do { KernelLog(LOG_ERROR, "FAT", "enumerate failure", "Enumerate - " message); return ES_ERROR_UNKNOWN; } while (0)
FSNode *directory = (FSNode *) node->driverNode;
Volume *volume = directory->volume;
SuperBlockCommon *superBlock = &volume->superBlock;
uint8_t *clusterBuffer = (uint8_t *) EsHeapAllocate(superBlock->sectorsPerCluster * SECTOR_SIZE, false, K_FIXED);
EsDefer(EsHeapFree(clusterBuffer, 0, K_FIXED));
if (!clusterBuffer) ENUMERATE_FAILURE("Could not allocate cluster buffer.\n");
uint32_t currentCluster = directory->entry.firstClusterLow + (directory->entry.firstClusterHigh << 16);
uint64_t directoryPosition = 0;
while (currentCluster < volume->terminateCluster) {
if (!directory->rootDirectory) {
if (!volume->fileSystem->Access((currentCluster * superBlock->sectorsPerCluster + volume->sectorOffset) * SECTOR_SIZE,
superBlock->sectorsPerCluster * SECTOR_SIZE, K_ACCESS_READ, (uint8_t *) clusterBuffer, ES_FLAGS_DEFAULT)) {
ENUMERATE_FAILURE("Could not read cluster.\n");
}
}
for (uintptr_t i = 0; i < superBlock->sectorsPerCluster * SECTOR_SIZE / sizeof(DirectoryEntry); i++, directoryPosition++) {
DirectoryEntry *entry = directory->rootDirectory ? (directory->rootDirectory + directoryPosition) : ((DirectoryEntry *) clusterBuffer + i);
if (entry->name[0] == 0xE5 || entry->attributes == 0x0F || (entry->attributes & 8)) continue;
if (!entry->name[0]) {
return ES_SUCCESS;
}
uint8_t name[12];
size_t nameLength = 0;
bool hasExtension = entry->name[8] != ' ' || entry->name[9] != ' ' || entry->name[10] != ' ';
if (entry->name[0] == '.' && (entry->name[1] == '.' || entry->name[1] == ' ') && entry->name[2] == ' ') {
continue;
}
for (uintptr_t i = 0; i < 11; i++) {
if (i == 8 && hasExtension) name[nameLength++] = '.';
if (entry->name[i] != ' ') name[nameLength++] = entry->name[i];
}
KNodeMetadata metadata = {};
metadata.type = (entry->attributes & 0x10) ? ES_NODE_DIRECTORY : ES_NODE_FILE;
if (metadata.type == ES_NODE_DIRECTORY) {
metadata.directoryChildren = ES_DIRECTORY_CHILDREN_UNKNOWN;
} else if (metadata.type == ES_NODE_FILE) {
metadata.totalSize = entry->fileSizeBytes;
}
DirectoryEntryReference reference = {};
reference.cluster = directory->rootDirectory ? 0 : currentCluster;
reference.offset = directory->rootDirectory ? directoryPosition : i;
EsError error = FSDirectoryEntryFound(node, &metadata, &reference,
(const char *) name, nameLength, false);
if (error != ES_SUCCESS) {
return error;
}
}
if (!directory->rootDirectory) {
currentCluster = NextCluster(volume, currentCluster);
}
}
return ES_SUCCESS;
}
static bool Mount(Volume *volume) {
#define MOUNT_FAILURE(message) do { KernelLog(LOG_ERROR, "FAT", "mount failure", "Mount - " message); goto failure; } while (0)
{
SuperBlockCommon *superBlock = &volume->superBlock;
if (!volume->fileSystem->Access(0, SECTOR_SIZE, K_ACCESS_READ, (uint8_t *) superBlock, ES_FLAGS_DEFAULT)) MOUNT_FAILURE("Could not read superBlock.\n");
uint32_t sectorCount = superBlock->totalSectors ?: superBlock->largeSectorCount;
uint32_t clusterCount = sectorCount / superBlock->sectorsPerCluster;
uint32_t sectorsPerFAT = 0;
if (clusterCount < 0x00000FF5) {
volume->type = TYPE_FAT12;
volume->terminateCluster = 0xFF8;
sectorsPerFAT = volume->sb16.sectorsPerFAT16;
} else if (clusterCount < 0x0000FFF5) {
volume->type = TYPE_FAT16;
volume->terminateCluster = 0xFFF8;
sectorsPerFAT = volume->sb16.sectorsPerFAT16;
} else if (clusterCount < 0x0FFFFFF5) {
volume->type = TYPE_FAT32;
volume->terminateCluster = 0xFFFFFF8;
sectorsPerFAT = volume->sb32.sectorsPerFAT32;
} else {
MOUNT_FAILURE("Unsupported cluster count. Maybe ExFAT?\n");
}
uint32_t rootDirectoryOffset = superBlock->reservedSectors + superBlock->fatCount * sectorsPerFAT;
uint32_t rootDirectorySectors = (superBlock->rootDirectoryEntries * sizeof(DirectoryEntry) + (SECTOR_SIZE - 1)) / SECTOR_SIZE;
volume->sectorOffset = rootDirectoryOffset + rootDirectorySectors - 2 * superBlock->sectorsPerCluster;
volume->fat = (uint8_t *) EsHeapAllocate(sectorsPerFAT * SECTOR_SIZE, true, K_FIXED);
if (!volume->fat) MOUNT_FAILURE("Could not allocate FAT.\n");
if (!volume->fileSystem->Access(superBlock->reservedSectors * SECTOR_SIZE,
sectorsPerFAT * SECTOR_SIZE, K_ACCESS_READ, volume->fat, ES_FLAGS_DEFAULT)) MOUNT_FAILURE("Could not read FAT.\n");
volume->fileSystem->spaceUsed = CountUsedClusters(volume) * superBlock->sectorsPerCluster * superBlock->bytesPerSector;
volume->fileSystem->spaceTotal = volume->fileSystem->block->sectorSize * volume->fileSystem->block->sectorCount;
volume->fileSystem->rootDirectory->driverNode = EsHeapAllocate(sizeof(FSNode), true, K_FIXED);
if (!volume->fileSystem->rootDirectory->driverNode) MOUNT_FAILURE("Could not allocate root node.\n");
FSNode *root = (FSNode *) volume->fileSystem->rootDirectory->driverNode;
root->volume = volume;
if (volume->type == TYPE_FAT32) {
root->entry.firstClusterLow = volume->sb32.rootDirectoryCluster & 0xFFFF;
root->entry.firstClusterHigh = (volume->sb32.rootDirectoryCluster >> 16) & 0xFFFF;
uint32_t currentCluster = volume->sb32.rootDirectoryCluster;
while (currentCluster < volume->terminateCluster) {
currentCluster = NextCluster(volume, currentCluster);
volume->fileSystem->rootDirectoryInitialChildren += SECTOR_SIZE * superBlock->sectorsPerCluster / sizeof(DirectoryEntry);
}
} else {
root->rootDirectory = (DirectoryEntry *) EsHeapAllocate(rootDirectorySectors * SECTOR_SIZE, true, K_FIXED);
volume->rootDirectory = root->rootDirectory;
if (!volume->fileSystem->Access(rootDirectoryOffset * SECTOR_SIZE, rootDirectorySectors * SECTOR_SIZE,
K_ACCESS_READ, (uint8_t *) root->rootDirectory, ES_FLAGS_DEFAULT)) {
MOUNT_FAILURE("Could not read root directory.\n");
}
for (uintptr_t i = 0; i < superBlock->rootDirectoryEntries; i++) {
if (root->rootDirectory[i].name[0] == 0xE5 || root->rootDirectory[i].attributes == 0x0F || (root->rootDirectory[i].attributes & 0x08)) continue;
else if (root->rootDirectory[i].name[0] == 0x00) break;
else volume->fileSystem->rootDirectoryInitialChildren++;
}
}
return true;
}
failure:
if (volume->root && volume->root->driverNode) EsHeapFree(((FSNode *) volume->root->driverNode)->rootDirectory, 0, K_FIXED);
if (volume->root) EsHeapFree(volume->root->driverNode, 0, K_FIXED);
EsHeapFree(volume->root, 0, K_FIXED);
EsHeapFree(volume->fat, 0, K_FIXED);
return false;
}
static void Close(KNode *node) {
EsHeapFree(node->driverNode, sizeof(FSNode), K_FIXED);
}
static void DeviceAttach(KDevice *parent) {
Volume *volume = (Volume *) KDeviceCreate("FAT", parent, sizeof(Volume));
if (!volume) {
KernelLog(LOG_ERROR, "FAT", "allocate error", "EntryFAT - Could not allocate Volume structure.\n");
return;
}
volume->fileSystem = (KFileSystem *) parent;
if (!volume->fileSystem) {
KernelLog(LOG_ERROR, "FAT", "device error", "EntryFAT - Could not create file system device.\n");
KDeviceDestroy(volume);
return;
}
if (volume->fileSystem->block->sectorSize != SECTOR_SIZE) {
KernelLog(LOG_ERROR, "FAT", "mount failure", "EntryFAT - Unsupported sector size.\n");
KDeviceDestroy(volume);
return;
}
if (!Mount(volume)) {
KernelLog(LOG_ERROR, "FAT", "mount failure", "EntryFAT - Could not mount FAT volume.\n");
KDeviceDestroy(volume);
return;
}
volume->fileSystem->read = Read;
volume->fileSystem->load = Load;
volume->fileSystem->scan = Scan;
volume->fileSystem->enumerate = Enumerate;
volume->fileSystem->close = Close;
if (volume->type == TYPE_FAT32) {
volume->fileSystem->nameBytes = sizeof(volume->sb32.label);
EsMemoryCopy(volume->fileSystem->name, volume->sb32.label, volume->fileSystem->nameBytes);
} else {
if (volume->rootDirectory[0].attributes & 8) {
volume->fileSystem->nameBytes = sizeof(volume->rootDirectory[0].name);
EsMemoryCopy(volume->fileSystem->name, volume->rootDirectory[0].name, volume->fileSystem->nameBytes);
} else {
volume->fileSystem->nameBytes = sizeof(volume->sb16.label);
EsMemoryCopy(volume->fileSystem->name, volume->sb16.label, volume->fileSystem->nameBytes);
}
}
volume->fileSystem->rootDirectory->driverNode = volume->root;
volume->fileSystem->directoryEntryDataBytes = sizeof(DirectoryEntryReference);
volume->fileSystem->nodeDataBytes = sizeof(FSNode);
FSRegisterFileSystem(volume->fileSystem);
}
KDriver driverFAT = {
.attach = DeviceAttach,
};

520
drivers/hda.cpp Normal file
View File

@ -0,0 +1,520 @@
#include <module.h>
#define RD_REGISTER_GCAP() controller->pci->ReadBAR16(0, 0x00) // Global capabilities.
#define RD_REGISTER_VMIN() controller->pci->ReadBAR8(0, 0x02) // Minor version number.
#define RD_REGISTER_VMAJ() controller->pci->ReadBAR8(0, 0x03) // Major version number.
#define RD_REGISTER_GCTL() controller->pci->ReadBAR32(0, 0x08) // Global control.
#define WR_REGISTER_GCTL(x) controller->pci->WriteBAR32(0, 0x08, x)
#define RD_REGISTER_STATESTS() controller->pci->ReadBAR16(0, 0x0E) // State change status.
#define RD_REGISTER_INTCTL() controller->pci->ReadBAR32(0, 0x20) // Interrupt control.
#define WR_REGISTER_INTCTL(x) controller->pci->WriteBAR32(0, 0x20, x)
#define RD_REGISTER_INTSTS() controller->pci->ReadBAR32(0, 0x24) // Interrupt status.
#define WR_REGISTER_CORBLBASE(x) controller->pci->WriteBAR32(0, 0x40, x) // CORB base address.
#define WR_REGISTER_CORBUBASE(x) controller->pci->WriteBAR32(0, 0x44, x)
#define RD_REGISTER_CORBWP(x) controller->pci->ReadBAR16(0, 0x48) // CORB write pointer.
#define WR_REGISTER_CORBWP(x) controller->pci->WriteBAR16(0, 0x48, x)
#define RD_REGISTER_CORBRP(x) controller->pci->ReadBAR16(0, 0x4A) // CORB read pointer.
#define WR_REGISTER_CORBRP(x) controller->pci->WriteBAR16(0, 0x4A, x)
#define RD_REGISTER_CORBCTL() controller->pci->ReadBAR8(0, 0x4C) // CORB control.
#define WR_REGISTER_CORBCTL(x) controller->pci->WriteBAR8(0, 0x4C, x)
#define RD_REGISTER_CORBSIZE() controller->pci->ReadBAR8(0, 0x4E) // CORB size.
#define WR_REGISTER_CORBSIZE(x) controller->pci->WriteBAR8(0, 0x4E, x)
#define WR_REGISTER_RIRBLBASE(x) controller->pci->WriteBAR32(0, 0x50, x) // RIRB base address.
#define WR_REGISTER_RIRBUBASE(x) controller->pci->WriteBAR32(0, 0x54, x)
#define RD_REGISTER_RIRBWP(x) controller->pci->ReadBAR16(0, 0x58) // RIRB write pointer.
#define WR_REGISTER_RIRBWP(x) controller->pci->WriteBAR16(0, 0x58, x)
#define RD_REGISTER_RINTCNT() controller->pci->ReadBAR16(0, 0x5A) // Response interrupt count.
#define WR_REGISTER_RINTCNT(x) controller->pci->WriteBAR16(0, 0x5A, x)
#define RD_REGISTER_RIRBCTL() controller->pci->ReadBAR8(0, 0x5C) // RIRB control.
#define WR_REGISTER_RIRBCTL(x) controller->pci->WriteBAR8(0, 0x5C, x)
#define RD_REGISTER_RIRBSTS() controller->pci->ReadBAR8(0, 0x5D) // RIRB status.
#define WR_REGISTER_RIRBSTS(x) controller->pci->WriteBAR8(0, 0x5D, x)
#define RD_REGISTER_RIRBSIZE() controller->pci->ReadBAR8(0, 0x5E) // RIRB size.
#define WR_REGISTER_RIRBSIZE(x) controller->pci->WriteBAR8(0, 0x5E, x)
#define WR_REGISTER_ImmComOut(x) controller->pci->WriteBAR32(0, 0x60, x) // Immediate command output.
#define RD_REGISTER_ImmComIn() controller->pci->ReadBAR32(0, 0x64) // Immediate command input.
#define RD_REGISTER_ImmComStat() controller->pci->ReadBAR16(0, 0x68) // Immediate command status.
#define WR_REGISTER_ImmComStat(x) controller->pci->WriteBAR16(0, 0x68, x)
#define RD_REGISTER_SDCTL(n) controller->pci->ReadBAR32(0, 0x80 + 0x20 * (n)) // Stream descriptor control.
#define WR_REGISTER_SDCTL(n, x) controller->pci->WriteBAR32(0, 0x80 + 0x20 * (n), x)
#define RD_REGISTER_SDSTS(n) controller->pci->ReadBAR8(0, 0x83 + 0x20 * (n)) // Stream descriptor status.
#define WR_REGISTER_SDSTS(n, x) controller->pci->WriteBAR8(0, 0x83 + 0x20 * (n), x)
#define RD_REGISTER_SDLPIB(n) controller->pci->ReadBAR32(0, 0x84 + 0x20 * (n)) // Stream descriptor link position in cyclic buffer.
#define WR_REGISTER_SDCBL(n, x) controller->pci->WriteBAR32(0, 0x88 + 0x20 * (n), x) // Stream descriptor cyclic buffer length.
#define RD_REGISTER_SDLVI(n) controller->pci->ReadBAR16(0, 0x8C + 0x20 * (n)) // Stream descriptor last valid index.
#define WR_REGISTER_SDLVI(n, x) controller->pci->WriteBAR16(0, 0x8C + 0x20 * (n), x)
#define RD_REGISTER_SDFMT(n) controller->pci->ReadBAR16(0, 0x92 + 0x20 * (n)) // Stream descriptor format.
#define WR_REGISTER_SDFMT(n, x) controller->pci->WriteBAR16(0, 0x92 + 0x20 * (n), x)
#define WR_REGISTER_SDBDPL(n, x) controller->pci->WriteBAR32(0, 0x98 + 0x20 * (n), x) // Stream descriptor BDL pointer lower base address.
#define WR_REGISTER_SDBDPU(n, x) controller->pci->WriteBAR32(0, 0x9C + 0x20 * (n), x) // Stream descriptor BDL pointer upper base address.
#define READ_PARAMETER_VENDOR_ID (0xF0000)
#define READ_PARAMETER_REVISION_ID (0xF0002)
#define READ_PARAMETER_CHILD_NODES (0xF0004)
#define READ_PARAMETER_FUNCTION_GROUP_TYPE (0xF0005)
#define READ_PARAMETER_AUDIO_FUNCTION_CAPABILITIES (0xF0008)
#define READ_PARAMETER_AUDIO_WIDGET_CAPABILITIES (0xF0009)
#define READ_PARAMETER_FORMAT_CAPABILITIES (0xF000A)
#define READ_PARAMETER_STREAM_FORMATS (0xF000B)
#define READ_PARAMETER_PIN_CAPABILITIES (0xF000C)
#define READ_PARAMETER_INPUT_AMP_CAPABILITIES (0xF000D)
#define READ_PARAMETER_CONNECTION_LIST_LENGTH (0xF000E)
#define READ_PARAMETER_OUTPUT_AMP_CAPABILITIES (0xF0012)
#define COMMAND_GET_CONNECTION_LIST_ENTRY(offset) (0xF0200 | (offset))
#define COMMAND_SET_CONNECTION_SELECT(index) (0x70100 | (index))
#define COMMAND_GET_AMPLIFIER_GAIN_MUTE(out, left, index) (0xB0000 | ((out) ? (1 << 15) : 0) | ((left) ? (1 << 13) : 0) | (index))
#define COMMAND_SET_AMPLIFIER_GAIN_MUTE(out, left, index, mute, gain) (0x30000 | ((out) ? (1 << 15) : (1 << 14)) | ((left) ? (1 << 13) : (1 << 12)) \
| ((index) << 8) | ((mute) ? (1 << 7) : 0) | (gain))
#define COMMAND_SET_CONVERTER_FORMAT(format) (0x20000 | (format))
#define COMMAND_SET_STREAM_NUMBER(stream, channel) (0x70600 | ((stream) << 4) | ((channel) << 0))
#define COMMAND_GET_PIN_WIDGET_CONTROL() (0xF0700)
#define COMMAND_SET_PIN_WIDGET_CONTROL(control) (0x70700 | (control))
#define COMMAND_PIN_SENSE() (0xF0900)
#define COMMAND_GET_PIN_CONFIGURATION() (0xF1C00)
#define COMMAND_RESET() (0x7FF00)
#define HDA_WIDGET_AUDIO_OUTPUT (0)
#define HDA_WIDGET_AUDIO_INPUT (1)
#define HDA_WIDGET_PIN_COMPLEX (4)
#define PIN_MAYBE_CONNECTED (0)
#define PIN_UNCONNECTED (1)
#define PIN_CONNECTED (2)
struct HDAWidget : KDevice {
uint32_t codec;
uint32_t node;
uint32_t functionGroup;
uint32_t type;
#define MAXIMUM_INPUTS (32)
uint8_t inputs[MAXIMUM_INPUTS];
union {
struct {
uint32_t pinCapabilities, pinConfiguration;
uint8_t pinIsConnected;
bool pinIsInput, pinIsOutput;
};
};
};
struct HDAController : KDevice {
KPCIDevice *pci;
size_t outputStreamsSupported;
size_t inputStreamsSupported;
size_t bidirectionalStreamsSupported;
size_t corbEntries, rirbEntries;
uint8_t *corbVirtual, *rirbVirtual;
uintptr_t corbPhysical, rirbPhysical;
uintptr_t corbWritePointer, rirbReadPointer;
uint32_t rirbLastSolicitedResponse;
KEvent rirbReceivedSolicitedResponse;
};
static const char *const widgetTypeStrings[] = {
"Audio output",
"Audio input",
"Audio mixer",
"Audio selector",
"Pin complex",
"Power widget",
"Volume knob",
"Beep generator",
};
static const char *const portConnectivityStrings[] = {
"jack",
"none",
"integrated",
"jack and integrated",
};
static const char *const locationHighStrings[] = {
"external",
"internal",
"separate",
"other",
};
static const char *const locationLowStrings[] = {
"??",
"rear",
"front",
"left",
"right",
"top",
"bottom",
"special",
"special",
"special",
"??",
"??",
"??",
"??",
"??",
"??",
};
static const char *const defaultDeviceStrings[] = {
"line out",
"speaker",
"HP out",
"CD",
"SPDIF out",
"digital other out",
"modem line side",
"modem handset side",
"line in",
"AUX",
"microphone in",
"telephony",
"SPDIF in",
"digital other in",
"??",
"??",
};
static const char *const connectionTypeStrings[] = {
"unknown",
"1/8\" stereo/mono",
"1/4\" stereo/mono",
"ATAPI internal",
"RCA",
"optical",
"other digital",
"other analog",
"multichannel analog (DIN)",
"XLR/Professional",
"RJ-11 (Modem)",
"combination",
"??",
"??",
"??",
"??",
};
static const char *const colorStrings[] = {
"unknown",
"black",
"grey",
"blue",
"green",
"red",
"orange",
"yellow",
"purple",
"pink",
"??",
"??",
"??",
"??",
"white",
"other",
};
static bool HDAControllerSendCommand(HDAController *controller, uint32_t codec, uint32_t node, uint32_t data, uint32_t *response) {
// TODO Test wrap-around.
uint32_t command = (codec << 28) | (node << 20) | data;
uintptr_t corbWritePointer = controller->corbWritePointer;
corbWritePointer = (corbWritePointer + 1) % controller->corbEntries;
((volatile uint32_t *) controller->corbVirtual)[corbWritePointer] = command;
WR_REGISTER_CORBWP(ES_ISOLATE_BITS(RD_REGISTER_CORBWP(), 15, 8) | corbWritePointer);
controller->corbWritePointer = corbWritePointer;
if (!KEventWait(&controller->rirbReceivedSolicitedResponse, 500 /* half a second timeout */)) return false;
if (response) *response = controller->rirbLastSolicitedResponse;
return true;
}
static bool HDAControllerHandleIRQ(uintptr_t, void *context) {
HDAController *controller = (HDAController *) context;
uint32_t interruptStatus = RD_REGISTER_INTSTS();
if (~interruptStatus & (1 << 31 /* global interrupt status */)) {
return false;
}
if (interruptStatus & (1 << 30)) {
uint8_t rirbStatus = RD_REGISTER_RIRBSTS();
WR_REGISTER_RIRBSTS(rirbStatus);
if (rirbStatus & (1 << 0 /* response interrupt */)) {
uint8_t rirbWritePointer = RD_REGISTER_RIRBWP();
while (controller->rirbReadPointer != rirbWritePointer) {
controller->rirbReadPointer = (controller->rirbReadPointer + 1) % controller->rirbEntries;
uint32_t response = ((volatile uint32_t *) controller->rirbVirtual)[controller->rirbReadPointer * 2 + 0];
uint32_t extended = ((volatile uint32_t *) controller->rirbVirtual)[controller->rirbReadPointer * 2 + 1];
if (~extended & (1 << 4)) {
controller->rirbLastSolicitedResponse = response;
KEventSet(&controller->rirbReceivedSolicitedResponse);
}
}
}
}
return true;
}
static void HDAControllerExploreFunctionGroup(HDAController *controller, uint32_t codec, uint32_t functionGroupNode) {
uint32_t type, childNodeCount;
if (!HDAControllerSendCommand(controller, codec, functionGroupNode, READ_PARAMETER_FUNCTION_GROUP_TYPE, &type)
|| !HDAControllerSendCommand(controller, codec, functionGroupNode, READ_PARAMETER_CHILD_NODES, &childNodeCount)) {
return;
}
uint32_t firstChildNode = ES_EXTRACT_BITS(childNodeCount, 23, 16);
childNodeCount = ES_EXTRACT_BITS(childNodeCount, 7, 0);
type = ES_EXTRACT_BITS(type, 7, 0);
KernelLog(LOG_INFO, "HDA", "found function group", "Found function group with type %d (%z), and child nodes %d to %d.\n",
type, type == 1 ? "audio" : type == 2 ? "modem" : "??", firstChildNode, firstChildNode + childNodeCount - 1);
for (uintptr_t j = firstChildNode; j < firstChildNode + childNodeCount; j++) {
uint32_t widgetCapabilities;
if (!HDAControllerSendCommand(controller, codec, j, READ_PARAMETER_AUDIO_WIDGET_CAPABILITIES, &widgetCapabilities)) {
continue;
}
uint32_t widgetType = ES_EXTRACT_BITS(widgetCapabilities, 23, 20);
HDAWidget *widget = (HDAWidget *) KDeviceCreate("HD Audio widget", controller, sizeof(HDAWidget));
widget->codec = codec;
widget->node = j;
widget->functionGroup = functionGroupNode;
widget->type = widgetType;
KernelLog(LOG_INFO, "HDA", "found widget", "Widget at node %d has type \"%z\".\n",
widget->node, widgetType >= sizeof(widgetTypeStrings) / sizeof(widgetTypeStrings[0]) ? "Other" : widgetTypeStrings[widgetType]);
if (widgetCapabilities & (1 << 8)) {
uint32_t connectionListLength, connectionList;
if (HDAControllerSendCommand(controller, codec, widget->node, READ_PARAMETER_CONNECTION_LIST_LENGTH, &connectionListLength)
&& (~connectionListLength & (1 << 7) /* long form not supported */)
&& ES_EXTRACT_BITS(connectionListLength, 6, 0)) {
uintptr_t index = 0;
for (uintptr_t command = 0; command < (connectionListLength + 3) / 4; command++) {
if (!HDAControllerSendCommand(controller, codec, widget->node, COMMAND_GET_CONNECTION_LIST_ENTRY(command * 4), &connectionList)) {
break;
}
for (uintptr_t i = 0; i < (connectionListLength - command * 4) && index < MAXIMUM_INPUTS; i++) {
uint8_t entry = connectionList >> (i * 8);
if ((entry & 0x80) && index) {
for (uintptr_t node = widget->inputs[index - 1]; node <= (entry & 0x7F) && index < MAXIMUM_INPUTS; node++) {
widget->inputs[index++] = node;
}
} else {
widget->inputs[index++] = entry;
}
}
}
}
}
for (uintptr_t i = 0; i < MAXIMUM_INPUTS; i++) {
if (!widget->inputs[i]) break;
KernelLog(LOG_INFO, "HDA", "widget connection", "Widget %d has possible input %d.\n", widget->node, widget->inputs[i]);
}
}
for (uintptr_t i = 0; i < controller->children.Length(); i++) {
HDAWidget *widget = (HDAWidget *) controller->children[i];
if (widget->type == HDA_WIDGET_PIN_COMPLEX) {
if (!HDAControllerSendCommand(controller, codec, widget->node, READ_PARAMETER_PIN_CAPABILITIES, &widget->pinCapabilities)
|| !HDAControllerSendCommand(controller, codec, widget->node, COMMAND_GET_PIN_CONFIGURATION(), &widget->pinConfiguration)) {
continue;
}
widget->pinIsOutput = widget->pinCapabilities & (1 << 4);
widget->pinIsInput = widget->pinCapabilities & (1 << 5);
KernelLog(LOG_INFO, "HDA", "pin information", "Pin %d has capabilities %x and configuration %x.%z%z\n",
widget->node, widget->pinCapabilities, widget->pinConfiguration,
widget->pinIsOutput ? " Output." : "", widget->pinIsInput ? " Input." : "");
if (!widget->pinIsOutput && !widget->pinIsInput) {
continue;
}
uint32_t portConnectivity = ES_EXTRACT_BITS(widget->pinConfiguration, 31, 30);
uint32_t location = ES_EXTRACT_BITS(widget->pinConfiguration, 29, 24);
uint32_t defaultDevice = ES_EXTRACT_BITS(widget->pinConfiguration, 23, 20);
uint32_t connectionType = ES_EXTRACT_BITS(widget->pinConfiguration, 19, 16);
uint32_t color = ES_EXTRACT_BITS(widget->pinConfiguration, 15, 12);
KernelLog(LOG_INFO, "HDA", "pin information", "Connectivity: %z; location: %z %z; default device: %z; connection type: %z; color: %z.\n",
portConnectivityStrings[portConnectivity],
locationHighStrings[ES_EXTRACT_BITS(location, 5, 4)], locationLowStrings[ES_EXTRACT_BITS(location, 3, 0)],
defaultDeviceStrings[defaultDevice], connectionTypeStrings[connectionType], colorStrings[color]);
if (widget->pinCapabilities & (1 << 2)) {
uint32_t pinSense;
if (HDAControllerSendCommand(controller, codec, widget->node, COMMAND_PIN_SENSE(), &pinSense)) {
widget->pinIsConnected = (pinSense & (1 << 31)) ? PIN_CONNECTED : PIN_UNCONNECTED;
KernelLog(LOG_INFO, "HDA", "pin sense", "Pin sense: %z.\n", widget->pinIsConnected == PIN_CONNECTED ? "connected" : "unconnected");
}
}
// TODO Register the device with the audio subsystem.
}
}
}
static void HDAControllerDestroy(KDevice *_controller) {
HDAController *controller = (HDAController *) _controller;
if (controller->corbVirtual) MMPhysicalFreeAndUnmap(controller->corbVirtual, controller->corbPhysical);
if (controller->rirbVirtual) MMPhysicalFreeAndUnmap(controller->rirbVirtual, controller->rirbPhysical);
// TODO Unregister interrupt handler.
}
static void HDAControllerAttach(KDevice *_parent) {
HDAController *controller = (HDAController *) KDeviceCreate("HD Audio controller", _parent, sizeof(HDAController));
if (!controller) {
return;
}
controller->destroy = HDAControllerDestroy;
controller->rirbReceivedSolicitedResponse.autoReset = true;
controller->pci = (KPCIDevice *) _parent;
controller->pci->EnableFeatures(K_PCI_FEATURE_INTERRUPTS | K_PCI_FEATURE_BUSMASTERING_DMA
| K_PCI_FEATURE_MEMORY_SPACE_ACCESS | K_PCI_FEATURE_BAR_0);
uint16_t globalCapabilities = RD_REGISTER_GCAP();
bool supports64BitAddresses = globalCapabilities & (1 << 0);
#ifdef ARCH_64
if (!supports64BitAddresses) {
KernelLog(LOG_ERROR, "HDA", "controller unsupported", "Controller does not support 64-bit addresses.\n");
KDeviceDestroy(controller);
return;
}
#endif
controller->outputStreamsSupported = ES_EXTRACT_BITS(globalCapabilities, 15, 12);
controller->inputStreamsSupported = ES_EXTRACT_BITS(globalCapabilities, 11, 8);
controller->bidirectionalStreamsSupported = ES_EXTRACT_BITS(globalCapabilities, 7, 3);
KernelLog(LOG_INFO, "HDA", "global capabilities", "Controller supports %d output streams, %d input streams and %d bidi streams.\n",
controller->outputStreamsSupported, controller->inputStreamsSupported, controller->bidirectionalStreamsSupported);
KernelLog(LOG_INFO, "HDA", "version", "Controller reports version %d.%d.\n",
RD_REGISTER_VMAJ(), RD_REGISTER_VMIN());
KTimeout timeout(1000); // The initialisation process shouldn't take more than a second.
#define CHECK_TIMEOUT(message) \
if (timeout.Hit()) { \
KernelLog(LOG_ERROR, "HDA", "timeout", "Timeout during initialization: " message ".\n"); \
KDeviceDestroy(controller); \
return; \
}
// Reset the controller.
WR_REGISTER_GCTL(RD_REGISTER_GCTL() & ~(1 << 0 /* CRST */));
while ((RD_REGISTER_GCTL() & (1 << 0)) && timeout.Hit());
CHECK_TIMEOUT("clear CRST bit");
WR_REGISTER_GCTL(RD_REGISTER_GCTL() | (1 << 0 /* CRST */));
while ((~RD_REGISTER_GCTL() & (1 << 0)) && timeout.Hit());
CHECK_TIMEOUT("set CRST bit");
// Setup CORB/RIRB.
uint8_t corbSize = RD_REGISTER_CORBSIZE();
controller->corbEntries = (corbSize & (1 << 6)) ? 256 : (corbSize & (1 << 5)) ? 16 : (corbSize & (1 << 4)) ? 2 : 0;
uint8_t rirbSize = RD_REGISTER_RIRBSIZE();
controller->rirbEntries = (rirbSize & (1 << 6)) ? 256 : (rirbSize & (1 << 5)) ? 16 : (rirbSize & (1 << 4)) ? 2 : 0;
if (!controller->corbEntries || !controller->rirbEntries) {
KernelLog(LOG_ERROR, "HDA", "unsupported", "Controller does not support any recognised CORB/RIRB sizes.\n");
KDeviceDestroy(controller);
return;
}
if (!MMPhysicalAllocateAndMap(controller->corbEntries * 4, 128, 0, true,
MM_REGION_NOT_CACHEABLE, &controller->corbVirtual, &controller->corbPhysical)
|| !MMPhysicalAllocateAndMap(controller->rirbEntries * 8, 128, 0, true,
MM_REGION_NOT_CACHEABLE, &controller->rirbVirtual, &controller->rirbPhysical)) {
KernelLog(LOG_ERROR, "HDA", "insufficient resources", "Could not allocate memory for CORB/RIRB.\n");
KDeviceDestroy(controller);
return;
}
WR_REGISTER_CORBSIZE(ES_ISOLATE_BITS(RD_REGISTER_CORBSIZE(), 7, 2) | (controller->corbEntries == 16 ? 1 : controller->corbEntries == 256 ? 2 : 0));
WR_REGISTER_RIRBSIZE(ES_ISOLATE_BITS(RD_REGISTER_RIRBSIZE(), 7, 2) | (controller->rirbEntries == 16 ? 1 : controller->rirbEntries == 256 ? 2 : 0));
WR_REGISTER_CORBLBASE(controller->corbPhysical & 0xFFFFFFFF);
if (supports64BitAddresses) WR_REGISTER_CORBUBASE(controller->corbPhysical >> 32);
WR_REGISTER_RIRBLBASE(controller->rirbPhysical & 0xFFFFFFFF);
if (supports64BitAddresses) WR_REGISTER_RIRBUBASE(controller->rirbPhysical >> 32);
WR_REGISTER_RINTCNT(ES_ISOLATE_BITS(RD_REGISTER_RINTCNT(), 15, 8) | 1 /* interrupt after every response */);
WR_REGISTER_CORBCTL(ES_ISOLATE_BITS(RD_REGISTER_CORBCTL(), 7, 2) | (1 << 1 /* run */) | (1 << 0 /* interrupt on error */));
WR_REGISTER_RIRBCTL(ES_ISOLATE_BITS(RD_REGISTER_RIRBCTL(), 7, 3) | (1 << 1 /* run */) | (1 << 0 /* interrupt on response */));
if ((~RD_REGISTER_CORBCTL() & (1 << 1)) || (~RD_REGISTER_RIRBCTL() & (1 << 1))) {
KernelLog(LOG_ERROR, "HDA", "start error", "Could not start the CORB/RIRB.\n");
KDeviceDestroy(controller);
return;
}
// Setup interrupts.
if (!controller->pci->EnableSingleInterrupt(HDAControllerHandleIRQ, controller, "HDA")) {
KernelLog(LOG_ERROR, "HDA", "insufficient resources", "Could not register interrupt handler.\n");
KDeviceDestroy(controller);
return;
}
WR_REGISTER_INTCTL((1 << 31) | (1 << 30));
// Enumerate codecs.
uint16_t codecs = RD_REGISTER_STATESTS();
for (uintptr_t i = 0; i < 15; i++) {
if (~codecs & (1 << i)) {
continue;
}
uint32_t vendorID, childNodeCount;
if (HDAControllerSendCommand(controller, i, 0, READ_PARAMETER_VENDOR_ID, &vendorID)
&& HDAControllerSendCommand(controller, i, 0, READ_PARAMETER_CHILD_NODES, &childNodeCount)) {
uint32_t firstChildNode = ES_EXTRACT_BITS(childNodeCount, 23, 16);
childNodeCount = ES_EXTRACT_BITS(childNodeCount, 7, 0);
KernelLog(LOG_INFO, "HDA", "found codec", "Found codec with vendor ID %x, and child nodes %d to %d.\n",
vendorID, firstChildNode, firstChildNode + childNodeCount - 1);
for (uintptr_t j = firstChildNode; j < firstChildNode + childNodeCount; j++) {
HDAControllerExploreFunctionGroup(controller, i, j);
}
}
}
// TODO Enable unsolicited responses.
// TODO Support hotplugging.
KernelLog(LOG_INFO, "HDA", "ready", "Controller %x successfully initialized.\n", controller);
}
KDriver driverHDAudio = {
.attach = HDAControllerAttach,
};

499
drivers/i8254x.cpp Normal file
View File

@ -0,0 +1,499 @@
// TODO Initialise on a separate thread.
// TODO Checksum off-loading.
#include <module.h>
#define RD_REGISTER_CTRL() Read(0x00) // Device control.
#define WR_REGISTER_CTRL(x) Write(0x00, (x) & ~0x20000416)
#define RD_REGISTER_STATUS() Read(0x08) // Device status.
#define RD_REGISTER_EECD() Read(0x10) // EEPROM data.
#define RD_REGISTER_EERD() Read(0x14) // EEPROM read.
#define WR_REGISTER_EERD(x) Write(0x14, x)
#define WR_REGISTER_FCAL(x) Write(0x28, x) // Flow control low.
#define WR_REGISTER_FCAH(x) Write(0x2C, x) // Flow control high.
#define WR_REGISTER_FCT(x) Write(0x30, x) // Flow control type.
#define RD_REGISTER_ICR() Read(0xC0) // Interrupt cause read.
#define RD_REGISTER_IMS() Read(0xD0) // Interrupt mask set/read.
#define WR_REGISTER_IMS(x) Write(0xD0, x)
#define WR_REGISTER_IMC(x) Write(0xD8, x) // Interrupt mask clear.
#define RD_REGISTER_RCTL() Read(0x100) // Receive control.
#define WR_REGISTER_RCTL(x) Write(0x100, (x) & ~0xF9204C01)
#define RD_REGISTER_RDBAL() Read(0x2800) // Receive descriptor base address low.
#define WR_REGISTER_RDBAL(x) Write(0x2800, x)
#define RD_REGISTER_RDBAH() Read(0x2804) // Receive descriptor base address high.
#define WR_REGISTER_RDBAH(x) Write(0x2804, x)
#define RD_REGISTER_RDLEN() Read(0x2808) // Receive descriptor length.
#define WR_REGISTER_RDLEN(x) Write(0x2808, x)
#define RD_REGISTER_RDH() Read(0x2810) // Receive descriptor head.
#define WR_REGISTER_RDH(x) Write(0x2810, x)
#define RD_REGISTER_RDT() Read(0x2818) // Receive descriptor tail.
#define WR_REGISTER_RDT(x) Write(0x2818, x)
#define WR_REGISTER_MTA(x, y) Write(0x5200 + x * 4, y) // Multicast table array.
#define RD_REGISTER_RAL() Read(0x5400) // Receive address low.
#define WR_REGISTER_RAL(x) Write(0x5400, x)
#define RD_REGISTER_RAH() Read(0x5404) // Receive address high.
#define WR_REGISTER_RAH(x) Write(0x5404, x)
#define RD_REGISTER_TCTL() Read(0x400) // Transmit control.
#define WR_REGISTER_TCTL(x) Write(0x400, (x) & ~0xFC800005)
#define RD_REGISTER_TDBAL() Read(0x3800) // Transmit descriptor base address low.
#define WR_REGISTER_TDBAL(x) Write(0x3800, x)
#define RD_REGISTER_TDBAH() Read(0x3804) // Transmit descriptor base address high.
#define WR_REGISTER_TDBAH(x) Write(0x3804, x)
#define RD_REGISTER_TDLEN() Read(0x3808) // Transmit descriptor length.
#define WR_REGISTER_TDLEN(x) Write(0x3808, x)
#define RD_REGISTER_TDH() Read(0x3810) // Transmit descriptor head.
#define WR_REGISTER_TDH(x) Write(0x3810, x)
#define RD_REGISTER_TDT() Read(0x3818) // Transmit descriptor tail.
#define WR_REGISTER_TDT(x) Write(0x3818, x)
#define RECEIVE_DESCRIPTOR_COUNT (64)
#define TRANSMIT_DESCRIPTOR_COUNT (64)
#define RECEIVE_BUFFER_SIZE (8192)
#define TRANSMIT_BUFFER_SIZE (8192)
#define TRANSFER_BUFFER_EXTRA (16) // TODO What are these for?
struct ReceiveDescriptor {
uint64_t address;
uint16_t length;
uint16_t checksum;
uint8_t status, errors;
uint16_t special;
};
struct TransmitDescriptor {
uint64_t address;
uint16_t length;
uint8_t checksumOffset;
uint8_t command;
uint8_t status;
uint8_t checksumStartField;
uint16_t special;
};
struct Controller : NetInterface {
KPCIDevice *pci;
bool hasEEPROM;
ReceiveDescriptor *receiveDescriptors;
TransmitDescriptor *transmitDescriptors;
uint8_t *receiveBuffers[RECEIVE_DESCRIPTOR_COUNT];
void *transmitBuffers[TRANSMIT_DESCRIPTOR_COUNT];
uintptr_t receiveTail;
uintptr_t transmitTail;
// Used by the dispatch thread.
uint8_t *dispatchBuffers[RECEIVE_DESCRIPTOR_COUNT];
size_t dispatchByteCounts[RECEIVE_DESCRIPTOR_COUNT];
uintptr_t dispatchPhysicalAddresses[RECEIVE_DESCRIPTOR_COUNT];
KMutex transmitMutex;
KEvent receiveEvent;
uint32_t Read(uintptr_t offset);
void Write(uintptr_t offset, uint32_t value);
bool ReadEEPROM(uint8_t address, uint16_t *data);
void Initialise();
bool Transmit(void *dataVirtual, uintptr_t dataPhysical, size_t dataBytes);
bool HandleIRQ();
void DispatchThread();
void DumpState();
};
void Controller::DumpState() {
EsPrint("I8254x controller state:\n");
EsPrint("\t--- Internal ---\n");
EsPrint("\t\tHas EEPROM: %z.\n", hasEEPROM ? "yes" : "no");
EsPrint("\t\tMAC address: %X:%X:%X:%X:%X:%X.\n", macAddress.d[0], macAddress.d[1], macAddress.d[2],
macAddress.d[3], macAddress.d[4], macAddress.d[5]);
EsPrint("\t--- Registers ---\n");
EsPrint("\t\tDevice control: %x.\n", RD_REGISTER_CTRL());
EsPrint("\t\tDevice status: %x.\n", RD_REGISTER_STATUS());
EsPrint("\t\tEEPROM data: %x.\n", RD_REGISTER_EECD());
EsPrint("\t\tEEPROM read: %x.\n", RD_REGISTER_EERD());
EsPrint("\t\tReceive descriptor base address: %x.\n", (uint64_t) RD_REGISTER_RDBAL() | ((uint64_t) RD_REGISTER_RDBAH() << 32));
EsPrint("\t\tReceive descriptor length: %x.\n", RD_REGISTER_RDLEN());
EsPrint("\t\tReceive descriptor head: %x.\n", RD_REGISTER_RDH());
EsPrint("\t\tReceive descriptor tail: %x.\n", RD_REGISTER_RDT());
EsPrint("\t\tReceive control: %x.\n", RD_REGISTER_RCTL());
EsPrint("\t\tReceive MAC address: %x.\n", (uint64_t) RD_REGISTER_RAL() | ((uint64_t) RD_REGISTER_RAH() << 32));
EsPrint("\t\tTransmit descriptor base address: %x.\n", (uint64_t) RD_REGISTER_TDBAL() | ((uint64_t) RD_REGISTER_TDBAH() << 32));
EsPrint("\t\tTransmit descriptor length: %x.\n", RD_REGISTER_TDLEN());
EsPrint("\t\tTransmit descriptor head: %x.\n", RD_REGISTER_TDH());
EsPrint("\t\tTransmit descriptor tail: %x.\n", RD_REGISTER_TDT());
EsPrint("\t\tTransmit control: %x.\n", RD_REGISTER_TCTL());
}
bool Controller::Transmit(void *dataVirtual, uintptr_t dataPhysical, size_t dataBytes) {
if (!dataBytes) {
KernelPanic("Controller::Transmit - dataBytes is zero.\n");
}
KMutexAcquire(&transmitMutex);
EsDefer(KMutexRelease(&transmitMutex));
// Get a next index to use for the transmit descriptor.
uint32_t head = RD_REGISTER_TDH();
uint32_t index = transmitTail;
uint32_t tail = (index + 1) % TRANSMIT_DESCRIPTOR_COUNT;
if (head == tail) {
// Wait upto 20ms for the head to move.
KTimeout timeout(20);
while (!timeout.Hit() && (RD_REGISTER_TDH() == tail));
head = RD_REGISTER_TDH();
}
if (head == tail) {
KernelLog(LOG_ERROR, "I8254x", "transmit overrun", "Attempting to transmit a packet with the head at the same position as the tail.\n");
return false;
}
// Free any unused transmit buffers.
if (transmitBuffers[index]) {
NetTransmitBufferReturn(transmitBuffers[index]);
}
for (uintptr_t i = tail; i != head; i = (i + 1) % TRANSMIT_DESCRIPTOR_COUNT) {
if (transmitBuffers[i]) {
NetTransmitBufferReturn(transmitBuffers[i]);
transmitBuffers[i] = nullptr;
}
}
// Set up transmit descriptor.
transmitDescriptors[index].length = dataBytes;
transmitDescriptors[index].status = 0;
transmitDescriptors[index].address = dataPhysical;
transmitDescriptors[index].command = (1 << 0 /* end of packet */) | (1 << 1 /* insert CRC in Ethernet packet */) | (1 << 3 /* report status */);
transmitBuffers[index] = dataVirtual;
if ((dataPhysical >> K_PAGE_BITS) != ((dataPhysical + dataBytes - 1) >> K_PAGE_BITS)) {
KernelPanic("Controller::Transmit - Data spanned over page boundary.\n");
}
// Submit the transmit descriptor to the controller.
transmitTail = tail;
__sync_synchronize();
WR_REGISTER_TDT(tail);
return true;
}
void Controller::DispatchThread() {
while (true) {
KEvent *events[] = { &receiveEvent };
KWaitEvents(events, 1);
NetInterfaceSetConnected(this, RD_REGISTER_STATUS() & (1 << 1));
uint32_t tail = receiveTail, head = RD_REGISTER_RDH();
uintptr_t dispatchCount = 0;
while (true) {
uint32_t nextTail = (tail + 1) % RECEIVE_DESCRIPTOR_COUNT;
if (nextTail == head) {
// Keep the tail one behind the head, otherwise controller assumes queue is empty of usable slots.
break;
}
if (~receiveDescriptors[nextTail].status & (1 << 0 /* descriptor done */)) {
break;
}
tail = nextTail;
uint16_t status = receiveDescriptors[tail].status;
receiveDescriptors[tail].status = 0;
if (~status & (1 << 1 /* end of packet */)) {
KernelLog(LOG_ERROR, "I8254x", "clear EOP bit", "Received descriptor with clear end of packet bit; this is unsupported.\n");
goto next;
}
if (receiveDescriptors[tail].errors) {
KernelLog(LOG_ERROR, "I8254x", "received error", "Received descriptor with error bits %X set.\n", receiveDescriptors[tail].errors);
goto next;
}
if (receiveDescriptors[tail].length < 60) {
KernelLog(LOG_ERROR, "I8254x", "short packet", "Received descriptor with packet less than 60 bytes; this is unsupported.\n");
goto next;
}
KernelLog(LOG_VERBOSE, "I8254x", "received packet", "Received packet at index %d with length %D.\n", tail, receiveDescriptors[tail].length);
uint8_t *newVirtualAddress;
uintptr_t newPhysicalAddress;
if (!MMPhysicalAllocateAndMap(RECEIVE_BUFFER_SIZE + TRANSFER_BUFFER_EXTRA,
16, 64, false, 0, &newVirtualAddress, &newPhysicalAddress)) {
// If we couldn't allocate memory, dispatch the buffer immediately.
NetInterfaceReceive(this, receiveBuffers[tail], receiveDescriptors[tail].length, NET_PACKET_ETHERNET);
} else {
// Queue the buffer to be dispatched.
dispatchBuffers[dispatchCount] = receiveBuffers[tail];
dispatchByteCounts[dispatchCount] = receiveDescriptors[tail].length;
dispatchPhysicalAddresses[dispatchCount] = receiveDescriptors[tail].address;
dispatchCount++;
receiveBuffers[tail] = newVirtualAddress;
receiveDescriptors[tail].address = newPhysicalAddress;
}
next:;
}
__sync_synchronize();
receiveTail = tail;
WR_REGISTER_RDT(tail);
for (uintptr_t i = 0; i < dispatchCount; i++) {
NetInterfaceReceive(this, dispatchBuffers[i], dispatchByteCounts[i], NET_PACKET_ETHERNET);
MMFree(MMGetKernelSpace(), dispatchBuffers[i], RECEIVE_BUFFER_SIZE + TRANSFER_BUFFER_EXTRA);
MMPhysicalFree(dispatchPhysicalAddresses[i], false, (RECEIVE_BUFFER_SIZE + TRANSFER_BUFFER_EXTRA + K_PAGE_SIZE - 1) / K_PAGE_SIZE);
}
}
}
bool Controller::HandleIRQ() {
uint32_t cause = RD_REGISTER_ICR();
if (!cause) {
return false;
}
KernelLog(LOG_VERBOSE, "I8254x", "received IRQ", "Received IRQ with cause %x.\n", cause);
if (cause & (1 << 2)) {
KernelLog(LOG_INFO, "I8254x", "link status change", "Link is now %z.\n",
(RD_REGISTER_STATUS() & (1 << 1)) ? "up" : "down");
KEventSet(&receiveEvent, false, true);
}
if (cause & (1 << 6)) {
KernelLog(LOG_ERROR, "I8254x", "receive underrun", "Controller reported receive underrun; packets have been lost.\n");
}
if (cause & ((1 << 6) | (1 << 7) | (1 << 4))) {
KEventSet(&receiveEvent, false, true);
}
return true;
}
uint32_t Controller::Read(uintptr_t offset) {
if (pci->baseAddresses[0] & 1) {
pci->WriteBAR32(0, 0, offset);
return pci->ReadBAR32(0, 4);
} else {
return pci->ReadBAR32(0, offset);
}
}
void Controller::Write(uintptr_t offset, uint32_t value) {
if (pci->baseAddresses[0] & 1) {
pci->WriteBAR32(0, 0, offset);
pci->WriteBAR32(0, 4, value);
} else {
pci->WriteBAR32(0, offset, value);
}
}
bool Controller::ReadEEPROM(uint8_t address, uint16_t *data) {
if (!hasEEPROM) {
KernelPanic("Controller::ReadEEPROM - EEPROM not present.\n");
}
WR_REGISTER_EERD(1 | ((uint32_t) address << 8));
KTimeout timeout(20);
while (!timeout.Hit() && (RD_REGISTER_EERD() & (1 << 4)));
if (data) *data = RD_REGISTER_EERD() >> 16;
return RD_REGISTER_EERD() & (1 << 4);
}
void Controller::Initialise() {
KEvent wait = {};
// Create a thread for dispatching received packets.
receiveEvent.autoReset = true;
if (!KThreadCreate("I8254xDispatch", [] (uintptr_t self) { ((Controller *) self)->DispatchThread(); }, (uintptr_t) this)) {
KernelLog(LOG_ERROR, "I8254x", "thread error", "Could not create the dispatch thread.\n");
return;
}
// Detect the EEPROM.
hasEEPROM = true;
hasEEPROM = ReadEEPROM(0, nullptr);
// Reset the controller.
uint32_t controlReset = RD_REGISTER_CTRL();
WR_REGISTER_CTRL(controlReset | (1 << 31 /* PHY_RST */));
RD_REGISTER_STATUS();
KEventWait(&wait, 20 /* specification says minimum time is 10ms, double for safety */);
WR_REGISTER_CTRL(controlReset | (1 << 26 /* RST */));
RD_REGISTER_STATUS();
KEventWait(&wait, 20);
if (RD_REGISTER_CTRL() & (1 << 26)) {
KernelLog(LOG_ERROR, "I8254x", "reset timeout", "Reset bit in control register did not clear after 20ms.\n");
return;
}
// Configure the control register.
uint32_t controlClearBits = (1 << 3 /* LRST */) | (1 << 31 /* PHY_RST */) | (1 << 7 /* ILOS */) | (1 << 30 /* VME */);
uint32_t controlSetBits = (1 << 5 /* ASDE */) | (1 << 6 /* SLU */);
WR_REGISTER_CTRL((RD_REGISTER_CTRL() & ~controlClearBits) | controlSetBits);
// Allocate receive and transmit descriptors and their buffers.
uintptr_t receiveDescriptorsPhysical, transmitDescriptorsPhysical;
if (!MMPhysicalAllocateAndMap(sizeof(ReceiveDescriptor) * RECEIVE_DESCRIPTOR_COUNT, 16, 64, true,
0, (uint8_t **) &receiveDescriptors, &receiveDescriptorsPhysical)) {
KernelLog(LOG_ERROR, "I8254x", "allocation failure", "Could not allocate receive descriptors.\n");
return;
}
if (!MMPhysicalAllocateAndMap(sizeof(TransmitDescriptor) * TRANSMIT_DESCRIPTOR_COUNT, 16, 64, true,
0, (uint8_t **) &transmitDescriptors, &transmitDescriptorsPhysical)) {
KernelLog(LOG_ERROR, "I8254x", "allocation failure", "Could not allocate transmit descriptors.\n");
return;
}
for (uintptr_t i = 0; i < RECEIVE_DESCRIPTOR_COUNT; i++) {
if (!MMPhysicalAllocateAndMap(RECEIVE_BUFFER_SIZE + TRANSFER_BUFFER_EXTRA,
16, 64, false, 0, receiveBuffers + i, &receiveDescriptors[i].address)) {
KernelLog(LOG_ERROR, "I8254x", "allocation failure", "Could not allocate receive buffers.\n");
return;
}
}
// Disable flow control.
WR_REGISTER_FCAL(0);
WR_REGISTER_FCAH(0);
WR_REGISTER_FCT(0);
// Get the MAC address.
if (hasEEPROM) {
if (!ReadEEPROM(0, (uint16_t *) &macAddress.d[0])
|| !ReadEEPROM(1, (uint16_t *) &macAddress.d[2])
|| !ReadEEPROM(2, (uint16_t *) &macAddress.d[4])) {
KernelLog(LOG_ERROR, "I8254x", "EEPROM error", "Could not read the MAC address from the EEPROM.\n");
return;
}
} else {
macAddress64 = ((uint64_t) RD_REGISTER_RAL() | ((uint64_t) RD_REGISTER_RAH() << 32)) & 0xFFFFFFFFFFFF;
}
// Enable interrupts and the register the handler.
WR_REGISTER_IMC((1 << 17) - 1);
WR_REGISTER_IMS((1 << 6 /* RXO */) | (1 << 7 /* RXT */) | (1 << 4 /* RXDMT */) | (1 << 2 /* LSC */));
RD_REGISTER_ICR();
if (!pci->EnableSingleInterrupt([] (uintptr_t, void *context) { return ((Controller *) context)->HandleIRQ(); }, this, "I8254x")) {
KernelLog(LOG_ERROR, "I8254x", "IRQ registration failure", "Could not register IRQ %d.\n", pci->interruptLine);
return;
}
// Setup receive registers.
WR_REGISTER_RAL(macAddress64 & 0xFFFFFFFF);
WR_REGISTER_RAH(((macAddress64 >> 32) & 0xFFFF) | (1 << 31 /* AV */));
WR_REGISTER_RDBAL(receiveDescriptorsPhysical & 0xFFFFFFFF);
WR_REGISTER_RDBAH(receiveDescriptorsPhysical >> 32);
WR_REGISTER_RDLEN(sizeof(ReceiveDescriptor) * RECEIVE_DESCRIPTOR_COUNT);
WR_REGISTER_RDH(0);
WR_REGISTER_RDT(RECEIVE_DESCRIPTOR_COUNT - 1);
receiveTail = RECEIVE_DESCRIPTOR_COUNT - 1;
for (uintptr_t i = 0; i < 128; i++) {
// Clear the multicast table array.
WR_REGISTER_MTA(i, 0);
}
uint32_t receiveControlClearBits = (1 << 2) | (1 << 5) | (1 << 6) | (1 << 7) | (1 << 16) | (1 << 17) | (1 << 25) | (1 << 26);
uint32_t receiveControlSetBits = (1 << 1) | (1 << 4) | (1 << 15);
WR_REGISTER_RCTL((RD_REGISTER_RCTL() & ~receiveControlClearBits) | receiveControlSetBits);
// Setup transmit registers.
WR_REGISTER_TDBAL(transmitDescriptorsPhysical & 0xFFFFFFFF);
WR_REGISTER_TDBAH(transmitDescriptorsPhysical >> 32);
WR_REGISTER_TDLEN(sizeof(TransmitDescriptor) * TRANSMIT_DESCRIPTOR_COUNT);
WR_REGISTER_TDH(0);
WR_REGISTER_TDT(0);
transmitTail = 0;
// TODO Is the TCTL.COLD value correct?
WR_REGISTER_TCTL((1 << 1) | (1 << 3) | (0x0F << 4) | (0x40 << 12));
// Register the device.
transmit = [] (NetInterface *self, void *dataVirtual, uintptr_t dataPhysical, size_t dataBytes) {
return ((Controller *) self)->Transmit(dataVirtual, dataPhysical, dataBytes);
};
KRegisterNetInterface(this);
if (RD_REGISTER_STATUS() & (1 << 1 /* link up */)) {
NetInterfaceSetConnected(this, true);
}
}
static void DeviceAttach(KDevice *_parent) {
KPCIDevice *parent = (KPCIDevice *) _parent;
Controller *device = (Controller *) KDeviceCreate("I8254x", parent, sizeof(Controller));
if (!device) return;
device->shutdown = [] (KDevice *device) {
NetInterfaceShutdown((Controller *) device);
// Wait a little bit for the transmit descriptors to be processed.
// TODO Can this be done asynchronously?
KEvent wait = {};
KEventWait(&wait, 50);
};
parent->EnableFeatures(K_PCI_FEATURE_MEMORY_SPACE_ACCESS
| K_PCI_FEATURE_BUSMASTERING_DMA
| K_PCI_FEATURE_INTERRUPTS
| K_PCI_FEATURE_IO_PORT_ACCESS
| K_PCI_FEATURE_BAR_0);
KernelLog(LOG_INFO, "I8254x", "found controller", "Found I8254x controller with ID %x.\n", parent->deviceID);
device->pci = parent;
device->Initialise();
// device->DumpState();
}
KDriver driverI8254x = {
.attach = DeviceAttach,
};

602
drivers/ide.cpp Normal file
View File

@ -0,0 +1,602 @@
// TODO Asynchronous timeout.
// TODO Inserting/removing ATAPI devices.
#include <module.h>
#define ATA_BUSES 2
#define ATA_DRIVES (ATA_BUSES * 2)
#define ATA_SECTOR_SIZE (512)
#define ATA_TIMEOUT (10000)
#define ATAPI_SECTOR_SIZE (2048)
#define ATA_REGISTER(_bus, _reg) (_reg != -1 ? ((_bus ? 0x170 : 0x1F0) + _reg) : (_bus ? 0x376 : 0x3F6))
#define ATA_IRQ(_bus) (_bus ? 15 : 14)
#define ATA_DATA 0
#define ATA_FEATURES 1
#define ATA_SECTOR_COUNT 2
#define ATA_LBA1 3
#define ATA_LBA2 4
#define ATA_LBA3 5
#define ATA_DRIVE_SELECT 6
#define ATA_STATUS 7
#define ATA_COMMAND 7
#define ATA_DCR -1
#define ATA_IDENTIFY 0xEC
#define ATA_IDENTIFY_PACKET 0xA1
#define ATA_READ_PIO 0x20
#define ATA_READ_PIO_48 0x24
#define ATA_READ_DMA 0xC8
#define ATA_READ_DMA_48 0x25
#define ATA_WRITE_PIO 0x30
#define ATA_WRITE_PIO_48 0x34
#define ATA_PACKET 0xA0
#define ATA_WRITE_DMA 0xCA
#define ATA_WRITE_DMA_48 0x35
#define DMA_REGISTER(_bus, _reg) 4, ((_bus ? (_reg + 8) : _reg))
#define DMA_COMMAND 0
#define DMA_STATUS 2
#define DMA_PRDT 4
struct PRD {
volatile uint32_t base;
volatile uint16_t size;
volatile uint16_t end;
};
struct ATAOperation {
void *buffer;
uintptr_t offsetIntoSector, readIndex;
size_t countBytes, sectorsNeededToLoad;
uint8_t operation, readingData, bus, slave;
bool pio;
};
struct ATAController : KDevice {
void Initialise();
bool Access(uintptr_t drive, uint64_t sector, size_t count, int operation, uint8_t *buffer); // Returns true on success.
bool AccessStart(int bus, int slave, uint64_t sector, uintptr_t offsetIntoSector, size_t sectorsNeededToLoad, size_t countBytes, int operation, uint8_t *buffer, bool atapi);
bool AccessEnd(int bus, int slave);
void SetDrive(int bus, int slave, int extra = 0);
void Unblock();
KPCIDevice *pci;
uint64_t sectorCount[ATA_DRIVES];
bool isATAPI[ATA_DRIVES];
KSemaphore semaphore;
PRD *prdts[ATA_BUSES];
void *buffers[ATA_BUSES];
KEvent irqs[ATA_BUSES];
uint16_t identifyData[ATA_SECTOR_SIZE / 2];
volatile ATAOperation op;
KMutex blockedPacketsMutex;
};
static ATAController *ataController;
struct ATADrive : KBlockDevice {
uintptr_t index;
};
void ATAController::SetDrive(int bus, int slave, int extra) {
ProcessorOut8(ATA_REGISTER(bus, ATA_DRIVE_SELECT), extra | 0xA0 | (slave << 4));
for (int i = 0; i < 4; i++) ProcessorIn8(ATA_REGISTER(bus, ATA_STATUS));
}
bool ATAController::AccessStart(int bus, int slave, uint64_t sector, uintptr_t offsetIntoSector, size_t sectorsNeededToLoad, size_t countBytes,
int operation, uint8_t *_buffer, bool atapi) {
uint16_t *buffer = (uint16_t *) _buffer;
bool s48 = false;
// Start a timeout.
KTimeout timeout(1000);
while ((ProcessorIn8(ATA_REGISTER(bus, ATA_STATUS)) & 0x80) && !timeout.Hit());
if (timeout.Hit()) return false;
if (atapi) {
SetDrive(bus, slave);
ProcessorOut8(ATA_REGISTER(bus, ATA_FEATURES), 1); // Using DMA.
uint32_t maxByteCount = sectorsNeededToLoad * ATAPI_SECTOR_SIZE;
if (maxByteCount > 65535) KernelPanic("ATAController::AccessStart - Access too large for ATAPI drive (max 64KB).\n");
ProcessorOut8(ATA_REGISTER(bus, ATA_LBA2), maxByteCount & 0xFF);
ProcessorOut8(ATA_REGISTER(bus, ATA_LBA3), (maxByteCount >> 8) & 0xFF);
} else if (sector >= 0x10000000) {
s48 = true;
SetDrive(bus, slave, 0x40);
ProcessorOut8(ATA_REGISTER(bus, ATA_SECTOR_COUNT), 0);
ProcessorOut8(ATA_REGISTER(bus, ATA_SECTOR_COUNT), sectorsNeededToLoad);
// Set the sector to access.
// The drive will keep track of the previous and current values of these registers,
// allowing it to construct a 48-bit sector number.
ProcessorOut8(ATA_REGISTER(bus, ATA_LBA3), sector >> 40);
ProcessorOut8(ATA_REGISTER(bus, ATA_LBA2), sector >> 32);
ProcessorOut8(ATA_REGISTER(bus, ATA_LBA1), sector >> 24);
ProcessorOut8(ATA_REGISTER(bus, ATA_LBA3), sector >> 16);
ProcessorOut8(ATA_REGISTER(bus, ATA_LBA2), sector >> 8);
ProcessorOut8(ATA_REGISTER(bus, ATA_LBA1), sector >> 0);
} else {
SetDrive(bus, slave, 0x40 | (sector >> 24));
ProcessorOut8(ATA_REGISTER(bus, ATA_SECTOR_COUNT), sectorsNeededToLoad);
ProcessorOut8(ATA_REGISTER(bus, ATA_LBA3), sector >> 16);
ProcessorOut8(ATA_REGISTER(bus, ATA_LBA2), sector >> 8);
ProcessorOut8(ATA_REGISTER(bus, ATA_LBA1), sector >> 0);
}
KEvent *event = irqs + bus;
event->autoReset = false;
KEventReset(event);
// Save the operation information.
op.buffer = buffer;
op.offsetIntoSector = offsetIntoSector;
op.countBytes = countBytes;
op.operation = operation;
op.readingData = false;
op.readIndex = 0;
op.sectorsNeededToLoad = sectorsNeededToLoad;
op.pio = false;
op.bus = bus;
op.slave = slave;
{
// Make sure the previous request has completed.
ProcessorIn8(ATA_REGISTER(bus, ATA_STATUS));
pci->ReadBAR8(DMA_REGISTER(bus, DMA_STATUS));
// Prepare the PRDT and buffer
prdts[bus]->size = sectorsNeededToLoad * (atapi ? ATAPI_SECTOR_SIZE : ATA_SECTOR_SIZE);
if (operation == K_ACCESS_WRITE) EsMemoryCopy((uint8_t *) buffers[bus] + offsetIntoSector, buffer, countBytes);
// Set the mode.
pci->WriteBAR8(DMA_REGISTER(bus, DMA_COMMAND), operation == K_ACCESS_WRITE ? 0 : 8);
pci->WriteBAR8(DMA_REGISTER(bus, DMA_STATUS), 6);
// Wait for the RDY bit to set.
while (!(ProcessorIn8(ATA_REGISTER(bus, ATA_STATUS)) & (1 << 6)) && !timeout.Hit());
if (timeout.Hit()) return false;
// Issue the command.
if (atapi) ProcessorOut8(ATA_REGISTER(bus, ATA_COMMAND), ATA_PACKET);
else if (s48) ProcessorOut8(ATA_REGISTER(bus, ATA_COMMAND), operation == K_ACCESS_READ ? ATA_READ_DMA_48 : ATA_WRITE_DMA_48);
else ProcessorOut8(ATA_REGISTER(bus, ATA_COMMAND), operation == K_ACCESS_READ ? ATA_READ_DMA : ATA_WRITE_DMA );
// Wait for the DRQ bit to set.
while (!(ProcessorIn8(ATA_REGISTER(bus, ATA_DCR)) & (1 << 3)) && !timeout.Hit());
if (timeout.Hit()) return false;
if (atapi) {
uint8_t packet[12] = {};
packet[0] = 0xA8; // Read sectors.
packet[2] = (sector >> 0x18) & 0xFF;
packet[3] = (sector >> 0x10) & 0xFF;
packet[4] = (sector >> 0x08) & 0xFF;
packet[5] = (sector >> 0x00) & 0xFF;
packet[9] = sectorsNeededToLoad;
// Wait for the BSY bit to clear.
while ((ProcessorIn8(ATA_REGISTER(bus, ATA_STATUS)) & (1 << 7)) && !timeout.Hit());
if (timeout.Hit()) return false;
// Send the ATAPI command.
ProcessorOut16(ATA_REGISTER(bus, ATA_DATA), ((uint16_t *) packet)[0]);
ProcessorOut16(ATA_REGISTER(bus, ATA_DATA), ((uint16_t *) packet)[1]);
ProcessorOut16(ATA_REGISTER(bus, ATA_DATA), ((uint16_t *) packet)[2]);
ProcessorOut16(ATA_REGISTER(bus, ATA_DATA), ((uint16_t *) packet)[3]);
ProcessorOut16(ATA_REGISTER(bus, ATA_DATA), ((uint16_t *) packet)[4]);
ProcessorOut16(ATA_REGISTER(bus, ATA_DATA), ((uint16_t *) packet)[5]);
}
pci->WriteBAR8(DMA_REGISTER(bus, DMA_COMMAND), operation == K_ACCESS_WRITE ? 1 : 9);
if (pci->ReadBAR8(DMA_REGISTER(bus, DMA_STATUS)) & 2) return false;
if (ProcessorIn8(ATA_REGISTER(bus, ATA_DCR)) & 33) return false;
}
return true;
}
bool ATAController::AccessEnd(int bus, int slave) {
(void) slave;
KEvent *event = irqs + bus;
{
// Wait for the command to complete.
KEventWait(event, ATA_TIMEOUT);
// Copy the data that we read.
ATAOperation *op = (ATAOperation *) &ataController->op;
if (op->buffer && op->operation == K_ACCESS_READ) {
// EsPrint("copying %d to %x\n", op->countBytes, op->buffer);
EsMemoryCopy((void *) op->buffer, (uint8_t *) ataController->buffers[op->bus] + op->offsetIntoSector, op->countBytes);
// EsPrint("done\n");
}
// Check for error.
if (ProcessorIn8(ATA_REGISTER(bus, ATA_STATUS)) & 33) return false;
if (pci->ReadBAR8(DMA_REGISTER(bus, DMA_STATUS)) & 3) return false;
// Check if the command has completed.
if (!KEventPoll(event)) {
return false;
}
return true;
}
}
void ATAController::Unblock() {
KMutexAcquire(&blockedPacketsMutex);
// EsPrint("unblock!\n");
KSemaphoreReturn(&semaphore, 1);
KMutexRelease(&blockedPacketsMutex);
}
bool ATAController::Access(uintptr_t drive, uint64_t offset, size_t countBytes, int operation, uint8_t *_buffer) {
bool atapi = isATAPI[drive];
uint64_t sectorSize = atapi ? ATAPI_SECTOR_SIZE : ATA_SECTOR_SIZE;
uint64_t sector = offset / sectorSize;
uint64_t offsetIntoSector = offset % sectorSize;
uint64_t sectorsNeededToLoad = (countBytes + offsetIntoSector + sectorSize - 1) / sectorSize;
uintptr_t bus = drive >> 1;
uintptr_t slave = drive & 1;
if (drive >= ATA_DRIVES) KernelPanic("ATAController::Access - Drive %d exceedes the maximum number of ATA driver (%d).\n", drive, ATA_DRIVES);
if (atapi && operation == K_ACCESS_WRITE) KernelPanic("ATAController::Access - Drive %d is an ATAPI drive. ATAPI write operations are currently not supported.\n", drive);
if (!sectorCount[drive] && !atapi) KernelPanic("ATAController::Access - Drive %d is invalid.\n", drive);
if ((sector > sectorCount[drive] || (sector + sectorsNeededToLoad) > sectorCount[drive]) && !atapi) KernelPanic("ATAController::Access - Attempt to access sector %d when drive only has %d sectors.\n", sector, sectorCount[drive]);
if (sectorsNeededToLoad > 64) KernelPanic("ATAController::Access - Attempt to read more than 64 consecutive sectors in 1 function call.\n");
// Lock the driver.
{
while (true) {
KEventWait(&semaphore.available, ES_WAIT_NO_TIMEOUT);
KMutexAcquire(&blockedPacketsMutex);
if (semaphore.units) {
KSemaphoreTake(&semaphore, 1);
break;
}
KMutexRelease(&blockedPacketsMutex);
}
KMutexRelease(&blockedPacketsMutex);
}
// EsPrint("locked (%d/%x)!\n", countBytes, _buffer);
op.bus = bus;
op.slave = slave;
if (!AccessStart(bus, slave, sector, offsetIntoSector, sectorsNeededToLoad, countBytes, operation, _buffer, atapi)) {
KSemaphoreReturn(&semaphore, 1);
return false;
}
bool result = AccessEnd(bus, slave);
Unblock();
return result;
}
bool ATAIRQHandler(uintptr_t interruptIndex, void *) {
int bus = interruptIndex - ATA_IRQ(0);
// Acknowledge the interrupt.
ProcessorIn8(ATA_REGISTER(bus, ATA_STATUS));
ataController->pci->ReadBAR8(DMA_REGISTER(bus, DMA_STATUS));
// *Don't* queue an asynchronous task.
// First of all, we don't need to (it's slower),
// and secondly, we need to Sync() nodes we're closing during process handle table termination,
// which takes place in the asynchronous task thread. (Meaning we'd get deadlock).
// TODO Is there a better way to do this, preventing similar bugs in the future?
ATAOperation *op = (ATAOperation *) &ataController->op;
KEvent *event = ataController->irqs + op->bus;
if (op->pio) {
KEventSet(event);
} else if (!(ataController->pci->ReadBAR8(DMA_REGISTER(op->bus, DMA_STATUS)) & 4)) {
// The interrupt bit was not set, so the IRQ must have been generated by a different device.
} else {
if (!event->state) {
// Stop the transfer.
ataController->pci->WriteBAR8(DMA_REGISTER(op->bus, DMA_COMMAND), 0);
KEventSet(event);
} else {
KernelLog(LOG_ERROR, "IDE", "too many IRQs", "ATAIRQHandler - Received more interrupts than expected.\n");
}
}
KSwitchThreadAfterIRQ();
return true;
}
inline uint32_t ByteSwap32(uint32_t x) {
return ((x & 0xFF000000) >> 24)
| ((x & 0x000000FF) << 24)
| ((x & 0x00FF0000) >> 8)
| ((x & 0x0000FF00) << 8);
}
void ATAController::Initialise() {
KSemaphoreReturn(&semaphore, 1);
KernelLog(LOG_INFO, "IDE", "found controller", "ATAController::Initialise - Found an ATA controller.\n");
for (uintptr_t bus = 0; bus < ATA_BUSES; bus++) {
// If the status is 0xFF, then the bus does not exist.
if (ProcessorIn8(ATA_REGISTER(bus, ATA_STATUS)) == 0xFF) {
continue;
}
// Check that the LBA registers are RW.
ProcessorOut8(ATA_REGISTER(bus, ATA_LBA1), 0xAB);
ProcessorOut8(ATA_REGISTER(bus, ATA_LBA2), 0xCD);
ProcessorOut8(ATA_REGISTER(bus, ATA_LBA3), 0xEF);
// Otherwise, the bus doesn't exist.
if (ProcessorIn8(ATA_REGISTER(bus, ATA_LBA1) != 0xAB)) continue;
if (ProcessorIn8(ATA_REGISTER(bus, ATA_LBA2) != 0xCD)) continue;
if (ProcessorIn8(ATA_REGISTER(bus, ATA_LBA3) != 0xEF)) continue;
// Clear the device command register.
ProcessorOut8(ATA_REGISTER(bus, ATA_DCR), 0);
int dmaDrivesOnBus = 0;
int drivesOnBus = 0;
uint8_t status;
size_t drivesPerBus = 2;
for (uintptr_t slave = 0; slave < drivesPerBus; slave++) {
// Issue the IDENTIFY command to the drive.
SetDrive(bus, slave);
ProcessorOut8(ATA_REGISTER(bus, ATA_LBA2), 0);
ProcessorOut8(ATA_REGISTER(bus, ATA_LBA3), 0);
ProcessorOut8(ATA_REGISTER(bus, ATA_COMMAND), ATA_IDENTIFY);
// Start a timeout.
KTimeout timeout(100);
// Check for error.
bool atapi = false;
if (ProcessorIn8(ATA_REGISTER(bus, ATA_STATUS)) & 32) continue;
if (ProcessorIn8(ATA_REGISTER(bus, ATA_STATUS)) & 1) {
uint8_t a = ProcessorIn8(ATA_REGISTER(bus, ATA_LBA2));
uint8_t b = ProcessorIn8(ATA_REGISTER(bus, ATA_LBA3));
if (a == 0x14 && b == 0xEB) {
atapi = true;
ProcessorOut8(ATA_REGISTER(bus, ATA_COMMAND), ATA_IDENTIFY_PACKET);
} else {
continue;
}
}
// Wait for the drive to be ready for the data transfer.
while ((ProcessorIn8(ATA_REGISTER(bus, ATA_STATUS)) & 0x80) && !timeout.Hit());
if (timeout.Hit()) continue;
while ((!(status = ProcessorIn8(ATA_REGISTER(bus, ATA_STATUS)) & 9)) && !timeout.Hit());
if (timeout.Hit()) continue;
if (status & 33) continue;
// Transfer the data.
for (uintptr_t i = 0; i < 256; i++) {
identifyData[i] = ProcessorIn16(ATA_REGISTER(bus, ATA_DATA));
}
// Check if the device supports LBA/DMA.
if (!(identifyData[49] & 0x200)) continue;
if (!(identifyData[49] & 0x100)) continue;
dmaDrivesOnBus |= 1;
drivesOnBus |= 1;
// Work out the number of sectors in the drive.
uint32_t lba28Sectors = ((uint32_t) identifyData[60] << 0) + ((uint32_t) identifyData[61] << 16);
uint64_t lba48Sectors = ((uint64_t) identifyData[100] << 0) + ((uint64_t) identifyData[101] << 16) +
((uint64_t) identifyData[102] << 32) + ((uint64_t) identifyData[103] << 48);
bool supportsLBA48 = lba48Sectors && (identifyData[83] & (1 << 10));
uint64_t sectors = supportsLBA48 ? lba48Sectors : lba28Sectors;
sectorCount[slave + bus * 2] = sectors;
if (atapi) {
isATAPI[slave + bus * 2] = true;
KernelLog(LOG_INFO, "IDE", "found drive", "ATAController::Initialise - Found ATAPI drive: %d/%d%z.\n",
bus, slave, supportsLBA48 ? "; supports LBA48" : "");
} else {
KernelLog(LOG_INFO, "IDE", "found drive", "ATAController::Initialise - Found ATA drive: %d/%d with %x sectors%z.\n",
bus, slave, sectors, supportsLBA48 ? "; supports LBA48" : "");
}
}
if (dmaDrivesOnBus) {
uint8_t *dataVirtual;
uintptr_t dataPhysical;
if (!MMPhysicalAllocateAndMap(131072, 131072, 32, true, ES_FLAGS_DEFAULT, &dataVirtual, &dataPhysical)) {
KernelLog(LOG_ERROR, "IDE", "allocation failure", "ATAController::Initialise - Could not allocate memory for DMA on bus %d.\n", bus);
sectorCount[bus * 2 + 0] = sectorCount[bus * 2 + 1] = 0;
drivesOnBus = 0;
continue;
}
PRD *prdt = (PRD *) dataVirtual;
prdt->end = 0x8000;
prdt->base = dataPhysical + 65536;
prdts[bus] = prdt;
void *buffer = (void *) (dataVirtual + 65536);
buffers[bus] = buffer;
pci->WriteBAR32(DMA_REGISTER(bus, DMA_PRDT), dataPhysical);
}
if (drivesOnBus) {
if (!KRegisterIRQ(ATA_IRQ(bus), ATAIRQHandler, nullptr, "IDE")) {
KernelLog(LOG_ERROR, "IDE", "IRQ registration failure", "ATAController::Initialise - Could not register IRQ for bus %d.\n", bus);
// Disable the drives on this bus.
sectorCount[bus * 2 + 0] = 0;
sectorCount[bus * 2 + 1] = 0;
isATAPI[bus * 2 + 0] = false;
isATAPI[bus * 2 + 1] = false;
}
}
for (uintptr_t slave = 0; slave < 2; slave++) {
if (sectorCount[slave + bus * 2]) {
bool success = Access(bus * 2 + slave, 0, ATA_SECTOR_SIZE, K_ACCESS_READ, (uint8_t *) identifyData);
if (!success) {
KernelLog(LOG_ERROR, "IDE", "test read failure", "ATAController::Initialise - Could not perform test read on drive.\n");
continue;
}
success = Access(bus * 2 + slave, 0, ATA_SECTOR_SIZE, K_ACCESS_WRITE, (uint8_t *) identifyData);
if (!success) {
KernelLog(LOG_ERROR, "IDE", "test write failure", "ATAController::Initialise - Could not perform test write to drive.\n");
}
} else if (isATAPI[slave + bus * 2]) {
KEvent *event = irqs + bus;
event->autoReset = false;
KEventReset(event);
op.bus = bus;
op.slave = slave;
op.pio = true;
uint16_t capacity[4];
SetDrive(bus, slave);
ProcessorOut8(ATA_REGISTER(bus, ATA_LBA2), 0);
ProcessorOut8(ATA_REGISTER(bus, ATA_LBA3), 8);
ProcessorOut8(ATA_REGISTER(bus, ATA_FEATURES), 0);
ProcessorOut8(ATA_REGISTER(bus, ATA_COMMAND), ATA_PACKET);
KTimeout timeout(100);
if (ProcessorIn8(ATA_REGISTER(bus, ATA_STATUS)) & 33) {
goto readCapacityFailure;
}
while ((ProcessorIn8(ATA_REGISTER(bus, ATA_STATUS)) & 0x80) && !timeout.Hit());
if (timeout.Hit()) goto readCapacityFailure;
while ((!(status = ProcessorIn8(ATA_REGISTER(bus, ATA_STATUS)) & 9)) && !timeout.Hit());
if (timeout.Hit()) goto readCapacityFailure;
if (status & 33) goto readCapacityFailure;
ProcessorOut16(ATA_REGISTER(bus, ATA_DATA), 0x0025);
ProcessorOut16(ATA_REGISTER(bus, ATA_DATA), 0x0000);
ProcessorOut16(ATA_REGISTER(bus, ATA_DATA), 0x0000);
ProcessorOut16(ATA_REGISTER(bus, ATA_DATA), 0x0000);
ProcessorOut16(ATA_REGISTER(bus, ATA_DATA), 0x0000);
ProcessorOut16(ATA_REGISTER(bus, ATA_DATA), 0x0000);
KEventWait(event, ATA_TIMEOUT);
KEventReset(event);
while ((ProcessorIn8(ATA_REGISTER(bus, ATA_STATUS)) & 0x80) && !timeout.Hit());
if (timeout.Hit()) goto readCapacityFailure;
while ((!(status = ProcessorIn8(ATA_REGISTER(bus, ATA_STATUS)) & 9)) && !timeout.Hit());
if (timeout.Hit()) goto readCapacityFailure;
if (status & 33) goto readCapacityFailure;
capacity[0] = ProcessorIn16(ATA_REGISTER(bus, ATA_DATA));
capacity[1] = ProcessorIn16(ATA_REGISTER(bus, ATA_DATA));
capacity[2] = ProcessorIn16(ATA_REGISTER(bus, ATA_DATA));
capacity[3] = ProcessorIn16(ATA_REGISTER(bus, ATA_DATA));
KEventWait(event, ATA_TIMEOUT);
KEventReset(event);
{
uint32_t blockCount = ByteSwap32(capacity[0] | ((uint32_t) capacity[1] << 16)) + 1;
uint32_t blockLength = ByteSwap32(capacity[2] | ((uint32_t) capacity[3] << 16));
if (blockLength != ATAPI_SECTOR_SIZE) {
KernelLog(LOG_ERROR, "IDE", "unsupported ATAPI block length",
"ATAController::Initialise - ATAPI drive reported block length of %d bytes, which is unsupported.\n", blockLength);
continue;
}
KernelLog(LOG_INFO, "IDE", "ATAPI capacity", "ATAController::Initialise - ATAPI drive has %d blocks (%D).\n",
blockCount, blockCount * ATAPI_SECTOR_SIZE);
sectorCount[slave + bus * 2] = blockCount;
continue;
}
readCapacityFailure:;
KernelLog(LOG_ERROR, "IDE", "read capacity failure", "ATAController::Initialise - Could not read capacity of ATAPI drive %d/%d.\n", bus, slave);
continue;
}
}
}
for (uintptr_t i = 0; i < ATA_DRIVES; i++) {
if (sectorCount[i]) {
// Register the drive.
ATADrive *device = (ATADrive *) KDeviceCreate("IDE drive", this, sizeof(ATADrive));
if (!device) {
KernelLog(LOG_ERROR, "IDE", "allocation failure", "Could not create device for drive %d.\n", i);
break;
}
device->index = i;
device->sectorSize = isATAPI[i] ? ATAPI_SECTOR_SIZE : ATA_SECTOR_SIZE;
device->sectorCount = sectorCount[i];
device->maxAccessSectorCount = isATAPI[i] ? 31 : 64;
device->readOnly = isATAPI[i];
device->driveType = isATAPI[i] ? ES_DRIVE_TYPE_CDROM : ES_DRIVE_TYPE_HDD;
device->access = [] (KBlockDeviceAccessRequest request) {
request.dispatchGroup->Start();
bool success = ataController->Access(((ATADrive *) request.device)->index,
request.offset, request.count, request.operation, (uint8_t *) KDMABufferGetVirtualAddress(request.buffer));
request.dispatchGroup->End(success);
};
FSRegisterBlockDevice(device);
}
}
}
static void DeviceAttach(KDevice *_parent) {
KPCIDevice *parent = (KPCIDevice *) _parent;
if (ataController) {
KernelLog(LOG_ERROR, "IDE", "multiple controllers", "EntryIDE - Attempt to register multiple IDE controllers; ignored.\n");
return;
}
ATAController *device = (ATAController *) KDeviceCreate("IDE controller", parent, sizeof(ATAController));
if (!device) return;
ataController = device;
device->pci = parent;
// Enable busmastering DMA and interrupts.
parent->EnableFeatures(K_PCI_FEATURE_INTERRUPTS | K_PCI_FEATURE_BUSMASTERING_DMA | K_PCI_FEATURE_BAR_4);
// Initialise the controller.
device->Initialise();
}
KDriver driverIDE = {
.attach = DeviceAttach,
};

545
drivers/iso9660.cpp Normal file
View File

@ -0,0 +1,545 @@
// TODO Validation of all fields.
#include <module.h>
#define SECTOR_SIZE (2048)
struct LBE16 {
#ifdef __BIG_ENDIAN__
uint16_t _u, x;
#else
uint16_t x, _u;
#endif
};
struct LBE32 {
#ifdef __BIG_ENDIAN__
uint32_t _u, x;
#else
uint32_t x, _u;
#endif
};
struct DateTime {
char year[4];
char month[2];
char day[2];
char hour[2];
char minute[2];
char second[2];
char centiseconds[2];
int8_t timeZoneOffset;
} __attribute__((packed));
struct DateTime2 {
uint8_t year;
uint8_t month;
uint8_t day;
uint8_t hour;
uint8_t minute;
uint8_t second;
int8_t timeZoneOffset;
} __attribute__((packed));
struct DirectoryRecord {
uint8_t length;
uint8_t extendedAttributeLength;
LBE32 extentStart;
LBE32 extentSize;
DateTime2 recordingTime;
uint8_t flags;
uint8_t interleavedUnitSize;
uint8_t interleavedGapSize;
LBE16 volumeSequenceNumber;
uint8_t fileNameBytes;
} __attribute__((packed));
struct PrimaryDescriptor {
uint8_t typeCode;
char signature[5];
uint8_t version;
uint8_t _unused0;
char systemIdentifier[32];
char volumeIdentifier[32];
uint8_t _unused1[8];
LBE32 volumeSize;
uint8_t _unused2[32];
LBE16 volumeSetSize;
LBE16 volumeSequenceNumber;
LBE16 logicalBlockSize;
LBE32 pathTableSize;
uint32_t pathTableLittle;
uint32_t optionalPathTableLittle;
uint32_t pathTableBig;
uint32_t optionalPathTableBig;
DirectoryRecord rootDirectory;
char rootDirectoryName;
char volumeSetIdentifier[128];
char publisherIdentifier[128];
char dataPreparerIdentifier[128];
char applicationIdentifier[128];
char copyrightFileIdentifier[38];
char abstractFileIdentifier[36];
char bibliographicFileIdentifier[37];
DateTime volumeCreationTime;
DateTime volumeModificationTime;
DateTime volumeExpirationTime;
DateTime volumeEffectiveTime;
uint8_t fileStructureVersion;
uint8_t _unused3;
char applicationSpecific[512];
uint8_t _unused4[653];
} __attribute__((packed));
struct DirectoryRecordReference {
uint32_t sector, offset;
};
struct FSNode {
struct Volume *volume;
DirectoryRecord record;
};
struct Volume : KDevice {
KFileSystem *fileSystem;
PrimaryDescriptor primaryDescriptor;
};
static EsError ScanInternal(const char *name, size_t nameBytes, KNode *_directory, DirectoryRecord *_record = nullptr);
static bool Mount(Volume *volume) {
#define MOUNT_FAILURE(message) do { KernelLog(LOG_ERROR, "ISO9660", "mount failure", "Mount - " message); return false; } while (0)
uintptr_t descriptorIndex = 0;
while (true) {
if (!volume->fileSystem->Access(32768 + SECTOR_SIZE * descriptorIndex, SECTOR_SIZE, K_ACCESS_READ, &volume->primaryDescriptor, ES_FLAGS_DEFAULT)) {
MOUNT_FAILURE("Could not access descriptor list.\n");
}
if (0 != EsMemoryCompare(volume->primaryDescriptor.signature, "CD001", 5)) {
MOUNT_FAILURE("Invalid descriptor signature.\n");
}
if (volume->primaryDescriptor.typeCode == 1) {
break;
}
if (volume->primaryDescriptor.typeCode == 0xFF) {
MOUNT_FAILURE("Could not find primary descriptor in descriptor list.\n");
}
if (++descriptorIndex > 16) {
MOUNT_FAILURE("Could not find end of descriptor list.\n");
}
}
if (volume->primaryDescriptor.version != 1 || volume->primaryDescriptor.fileStructureVersion != 1) {
MOUNT_FAILURE("Unsupported fileSystem version.\n");
}
if (volume->primaryDescriptor.logicalBlockSize.x != SECTOR_SIZE) {
MOUNT_FAILURE("Unsupported block size.\n");
}
{
FSNode *root = (FSNode *) EsHeapAllocate(sizeof(FSNode), true, K_FIXED);
if (!root) {
MOUNT_FAILURE("Could not allocate root node.\n");
}
volume->fileSystem->rootDirectory->driverNode = root;
volume->fileSystem->rootDirectoryInitialChildren = volume->primaryDescriptor.rootDirectory.extentSize.x / sizeof(DirectoryRecord);
root->volume = volume;
root->record = volume->primaryDescriptor.rootDirectory;
}
{
// Is this the boot disc?
EsUniqueIdentifier identifier = KGetBootIdentifier();
if (0 != EsMemoryCompare("Essence::", volume->primaryDescriptor.applicationSpecific, 9)) {
goto notBoot;
}
if (EsMemoryCompare(&identifier, volume->primaryDescriptor.applicationSpecific + 9, 16)) {
goto notBoot;
}
DirectoryRecord record = {};
ScanInternal(EsLiteral("ESSENCE.DAT;1"), volume->fileSystem->rootDirectory, &record);
record.extentSize.x = (record.extentSize.x + SECTOR_SIZE - 1) / SECTOR_SIZE;
if (!record.length || record.extentStart.x >= volume->fileSystem->block->sectorCount
|| record.extentSize.x >= volume->fileSystem->block->sectorCount - record.extentStart.x) {
goto notBoot;
}
// Load the first sector to look at the MBR.
uint8_t *firstSector = (uint8_t *) EsHeapAllocate(SECTOR_SIZE, false, K_FIXED);
if (!firstSector) {
KernelLog(LOG_ERROR, "ISO9660", "allocation failure", "Could not allocate sector buffer to check MBR.\n");
goto notBoot;
}
EsDefer(EsHeapFree(firstSector, SECTOR_SIZE, K_FIXED));
if (!volume->fileSystem->Access(record.extentStart.x * SECTOR_SIZE, SECTOR_SIZE, K_ACCESS_READ, firstSector, ES_FLAGS_DEFAULT)) {
goto notBoot;
}
uint32_t sectorOffset = ((uint32_t) firstSector[0x1BE + 8] << 0) + ((uint32_t) firstSector[0x1BE + 9] << 8)
+ ((uint32_t) firstSector[0x1BE + 10] << 16) + ((uint32_t) firstSector[0x1BE + 11] << 24);
sectorOffset /= (SECTOR_SIZE / 512); // Convert to disc sectors.
if (sectorOffset >= record.extentSize.x) {
goto notBoot;
}
record.extentStart.x += sectorOffset;
record.extentSize.x -= sectorOffset;
KernelLog(LOG_INFO, "ISO9660", "found boot disc", "Found boot disc. Image at %d/%d.\n",
record.extentStart.x, record.extentSize.x);
FSPartitionDeviceCreate(volume->fileSystem->block, record.extentStart.x, record.extentSize.x, ES_FLAGS_DEFAULT, "CD-ROM boot partition");
}
notBoot:;
return true;
}
static size_t Read(KNode *node, void *_buffer, EsFileOffset offset, EsFileOffset count) {
#define READ_FAILURE(message) do { KernelLog(LOG_ERROR, "ISO9660", "read failure", "Read - " message); return ES_ERROR_UNKNOWN; } while (0)
FSNode *file = (FSNode *) node->driverNode;
Volume *volume = file->volume;
uint8_t *sectorBuffer = (uint8_t *) EsHeapAllocate(SECTOR_SIZE, false, K_FIXED);
if (!sectorBuffer) {
READ_FAILURE("Could not allocate sector buffer.\n");
}
EsDefer(EsHeapFree(sectorBuffer, SECTOR_SIZE, K_FIXED));
uint8_t *outputBuffer = (uint8_t *) _buffer;
uint64_t firstSector = offset / SECTOR_SIZE;
uint32_t lba = file->record.extentStart.x + firstSector;
offset %= SECTOR_SIZE;
while (count) {
if (offset || count < SECTOR_SIZE) {
if (!volume->fileSystem->Access(lba * SECTOR_SIZE, SECTOR_SIZE, K_ACCESS_READ, sectorBuffer, ES_FLAGS_DEFAULT)) {
READ_FAILURE("Could not read file sector.\n");
}
uint64_t bytesToRead = (count > SECTOR_SIZE - offset) ? (SECTOR_SIZE - offset) : count;
EsMemoryCopy(outputBuffer, sectorBuffer + offset, bytesToRead);
lba++, count -= bytesToRead, offset = 0, outputBuffer += bytesToRead;
} else {
uint64_t sectorsToRead = count / SECTOR_SIZE;
if (!volume->fileSystem->Access(lba * SECTOR_SIZE, sectorsToRead * SECTOR_SIZE, K_ACCESS_READ, outputBuffer, ES_FLAGS_DEFAULT)) {
READ_FAILURE("Could not read file sectors.\n");
}
lba += sectorsToRead, count -= SECTOR_SIZE * sectorsToRead, outputBuffer += SECTOR_SIZE * sectorsToRead;
}
}
return true;
}
static EsError Enumerate(KNode *node) {
#define ENUMERATE_FAILURE(message) do { KernelLog(LOG_ERROR, "ISO9660", "enumerate failure", "Enumerate - " message); return false; } while (0)
FSNode *directory = (FSNode *) node->driverNode;
Volume *volume = directory->volume;
// TODO Load multiple sectors at once?
uint8_t *sectorBuffer = (uint8_t *) EsHeapAllocate(SECTOR_SIZE, false, K_FIXED);
if (!sectorBuffer) {
ENUMERATE_FAILURE("Could not allocate sector buffer.\n");
}
EsDefer(EsHeapFree(sectorBuffer, SECTOR_SIZE, K_FIXED));
uint32_t currentSector = directory->record.extentStart.x;
uint32_t remainingBytes = directory->record.extentSize.x;
while (remainingBytes) {
bool accessResult = volume->fileSystem->Access(currentSector * SECTOR_SIZE, SECTOR_SIZE, K_ACCESS_READ, (uint8_t *) sectorBuffer, ES_FLAGS_DEFAULT);
if (!accessResult) {
ENUMERATE_FAILURE("Could not read sector.\n");
}
uintptr_t positionInSector = 0;
while (positionInSector < SECTOR_SIZE && positionInSector < remainingBytes) {
DirectoryRecord *record = (DirectoryRecord *) (sectorBuffer + positionInSector);
if (!record->length) {
break;
}
if (positionInSector + record->length > SECTOR_SIZE || record->length < sizeof(DirectoryRecord)) {
ENUMERATE_FAILURE("Invalid directory record.\n");
}
if (record->fileNameBytes <= 2) {
goto nextEntry;
}
{
KNodeMetadata metadata = {};
size_t nameBytes = record->fileNameBytes;
for (uintptr_t i = 0; i < record->fileNameBytes; i++) {
if (((char *) (record + 1))[i] == ';') {
nameBytes = i;
break;
}
}
metadata.type = (record->flags & (1 << 1)) ? ES_NODE_DIRECTORY : ES_NODE_FILE;
if (metadata.type == ES_NODE_DIRECTORY) {
metadata.directoryChildren = ES_DIRECTORY_CHILDREN_UNKNOWN;
metadata.totalSize = 0;
} else if (metadata.type == ES_NODE_FILE) {
metadata.totalSize = record->extentSize.x;
}
DirectoryRecordReference reference = {};
reference.sector = currentSector;
reference.offset = positionInSector;
EsError error = FSDirectoryEntryFound(node, &metadata, &reference,
(const char *) (record + 1), nameBytes, false);
if (error != ES_SUCCESS) {
return error;
}
}
nextEntry:;
positionInSector += record->length;
}
if (remainingBytes < SECTOR_SIZE) {
remainingBytes = 0;
} else {
remainingBytes -= SECTOR_SIZE;
}
}
return ES_SUCCESS;
}
static EsError ScanInternal(const char *name, size_t nameBytes, KNode *_directory, DirectoryRecord *_record) {
#define SCAN_FAILURE(message) do { KernelLog(LOG_ERROR, "ISO9660", "scan failure", "Scan - " message); return ES_ERROR_UNKNOWN; } while (0)
// Check for invalid characters.
for (uintptr_t i = 0; i < nameBytes; i++) {
bool validCharacter = name[i] == '.' || name[i] == ';' || name[i] == '_'
|| (name[i] >= 'A' && name[i] <= 'Z')
|| (name[i] >= '0' && name[i] <= '9');
if (!validCharacter) {
return ES_ERROR_FILE_DOES_NOT_EXIST;
}
}
FSNode *directory = (FSNode *) _directory->driverNode;
Volume *volume = directory->volume;
// TODO Load multiple sectors at once?
uint8_t *sectorBuffer = (uint8_t *) EsHeapAllocate(SECTOR_SIZE, false, K_FIXED);
if (!sectorBuffer) {
SCAN_FAILURE("Could not allocate sector buffer.\n");
}
EsDefer(EsHeapFree(sectorBuffer, SECTOR_SIZE, K_FIXED));
uint32_t currentSector = directory->record.extentStart.x;
uint32_t remainingBytes = directory->record.extentSize.x;
while (remainingBytes) {
bool accessResult = volume->fileSystem->Access(currentSector * SECTOR_SIZE, SECTOR_SIZE, K_ACCESS_READ, (uint8_t *) sectorBuffer, ES_FLAGS_DEFAULT);
if (!accessResult) {
SCAN_FAILURE("Could not read sector.\n");
}
uintptr_t positionInSector = 0;
while (positionInSector < SECTOR_SIZE && positionInSector < remainingBytes) {
DirectoryRecord *record = (DirectoryRecord *) (sectorBuffer + positionInSector);
if (!record->length) {
break;
}
if (positionInSector + record->length > SECTOR_SIZE || record->length < sizeof(DirectoryRecord)) {
SCAN_FAILURE("Invalid directory record.\n");
}
if (record->fileNameBytes <= 2) {
goto nextEntry;
}
if (!((nameBytes == record->fileNameBytes && 0 == EsMemoryCompare(record + 1, name, nameBytes))
|| (nameBytes + 2 == record->fileNameBytes && 0 == EsMemoryCompare(record + 1, name, nameBytes)
&& 0 == EsMemoryCompare((char *) (record + 1) + record->fileNameBytes - 2, ";1", 2)))) {
goto nextEntry;
}
if (_record) {
EsMemoryCopy(_record, record, sizeof(DirectoryRecord));
return ES_SUCCESS;
}
{
KNodeMetadata metadata = {};
metadata.type = (record->flags & (1 << 1)) ? ES_NODE_DIRECTORY : ES_NODE_FILE;
if (metadata.type == ES_NODE_DIRECTORY) {
metadata.directoryChildren = ES_DIRECTORY_CHILDREN_UNKNOWN;
metadata.totalSize = 0;
} else if (metadata.type == ES_NODE_FILE) {
metadata.totalSize = record->extentSize.x;
}
DirectoryRecordReference reference = {};
reference.sector = currentSector;
reference.offset = positionInSector;
return FSDirectoryEntryFound(_directory, &metadata, &reference,
name, nameBytes, false);
}
nextEntry:;
positionInSector += record->length;
}
if (remainingBytes < SECTOR_SIZE) {
remainingBytes = 0;
} else {
remainingBytes -= SECTOR_SIZE;
}
}
return ES_ERROR_FILE_DOES_NOT_EXIST;
}
static EsError Scan(const char *name, size_t nameBytes, KNode *_directory) {
return ScanInternal(name, nameBytes, _directory);
}
static EsError Load(KNode *_directory, KNode *_node, KNodeMetadata *, const void *entryData) {
DirectoryRecordReference reference = *(DirectoryRecordReference *) entryData;
FSNode *directory = (FSNode *) _directory->driverNode;
Volume *volume = directory->volume;
uint8_t *sectorBuffer = (uint8_t *) EsHeapAllocate(SECTOR_SIZE, false, K_FIXED);
if (!sectorBuffer) {
return ES_ERROR_INSUFFICIENT_RESOURCES;
}
EsDefer(EsHeapFree(sectorBuffer, SECTOR_SIZE, K_FIXED));
if (!volume->fileSystem->Access(reference.sector * SECTOR_SIZE, SECTOR_SIZE, K_ACCESS_READ, (uint8_t *) sectorBuffer, ES_FLAGS_DEFAULT)) {
return ES_ERROR_DRIVE_CONTROLLER_REPORTED;
}
FSNode *data = (FSNode *) EsHeapAllocate(sizeof(FSNode), true, K_FIXED);
if (!data) {
return ES_ERROR_INSUFFICIENT_RESOURCES;
}
data->volume = volume;
_node->driverNode = data;
DirectoryRecord *record = (DirectoryRecord *) (sectorBuffer + reference.offset);
EsMemoryCopy(&data->record, record, sizeof(DirectoryRecord));
return ES_SUCCESS;
}
static void Close(KNode *node) {
EsHeapFree(node->driverNode, sizeof(FSNode), K_FIXED);
}
static void DeviceAttach(KDevice *parent) {
Volume *volume = (Volume *) KDeviceCreate("ISO9660", parent, sizeof(Volume));
if (!volume) {
KernelLog(LOG_ERROR, "ISO9660", "allocate error", "EntryISO9660 - Could not allocate Volume structure.\n");
return;
}
volume->fileSystem = (KFileSystem *) parent;
if (volume->fileSystem->block->sectorSize != SECTOR_SIZE) {
KernelLog(LOG_ERROR, "ISO9660", "incorrect sector size", "EntryISO9660 - Expected 2KB sectors, but drive's sectors are %D.\n",
volume->fileSystem->block->sectorSize);
KDeviceDestroy(volume);
return;
}
if (!Mount(volume)) {
KernelLog(LOG_ERROR, "ISO9660", "mount failure", "EntryISO9660 - Could not mount ISO9660 volume.\n");
KDeviceDestroy(volume);
return;
}
volume->fileSystem->read = Read;
volume->fileSystem->scan = Scan;
volume->fileSystem->load = Load;
volume->fileSystem->enumerate = Enumerate;
volume->fileSystem->close = Close;
volume->fileSystem->spaceUsed = volume->primaryDescriptor.volumeSize.x * volume->primaryDescriptor.logicalBlockSize.x;
volume->fileSystem->spaceTotal = volume->fileSystem->spaceUsed;
volume->fileSystem->nameBytes = sizeof(volume->primaryDescriptor.volumeIdentifier);
if (volume->fileSystem->nameBytes > sizeof(volume->fileSystem->name)) volume->fileSystem->nameBytes = sizeof(volume->fileSystem->name);
EsMemoryCopy(volume->fileSystem->name, volume->primaryDescriptor.volumeIdentifier, volume->fileSystem->nameBytes);
for (intptr_t i = volume->fileSystem->nameBytes - 1; i >= 0; i--) {
if (volume->fileSystem->name[i] == ' ') {
volume->fileSystem->nameBytes--;
} else {
break;
}
}
volume->fileSystem->directoryEntryDataBytes = sizeof(DirectoryRecordReference);
volume->fileSystem->nodeDataBytes = sizeof(FSNode);
KernelLog(LOG_INFO, "ISO9660", "register file system", "EntryISO9660 - Registering file system with name '%s'.\n",
volume->fileSystem->nameBytes, volume->fileSystem->name);
FSRegisterFileSystem(volume->fileSystem);
}
KDriver driverISO9660 = {
.attach = DeviceAttach,
};

1265
drivers/ntfs.cpp Normal file

File diff suppressed because it is too large Load Diff

878
drivers/nvme.cpp Normal file
View File

@ -0,0 +1,878 @@
#include <module.h>
// TODO Sometimes completion interrupts get missed?
// TODO How many IO completion/submission queues should we create, and how many entries should they contain?
// TODO Command timeout.
#define GENERAL_TIMEOUT (5000)
#define RD_REGISTER_CAP() pci-> ReadBAR64(0, 0x00) // Controller capababilities.
#define WR_REGISTER_CAP(x) pci->WriteBAR64(0, 0x00, x)
#define RD_REGISTER_VS() pci-> ReadBAR32(0, 0x08) // Version.
#define WR_REGISTER_VS(x) pci->WriteBAR32(0, 0x08, x)
#define RD_REGISTER_INTMS() pci-> ReadBAR32(0, 0x0C) // Interrupt mask set.
#define WR_REGISTER_INTMS(x) pci->WriteBAR32(0, 0x0C, x)
#define RD_REGISTER_INTMC() pci-> ReadBAR32(0, 0x10) // Interrupt mask clear.
#define WR_REGISTER_INTMC(x) pci->WriteBAR32(0, 0x10, x)
#define RD_REGISTER_CC() pci-> ReadBAR32(0, 0x14) // Controller configuration.
#define WR_REGISTER_CC(x) pci->WriteBAR32(0, 0x14, x)
#define RD_REGISTER_CSTS() pci-> ReadBAR32(0, 0x1C) // Controller status.
#define WR_REGISTER_CSTS(x) pci->WriteBAR32(0, 0x1C, x)
#define RD_REGISTER_AQA() pci-> ReadBAR32(0, 0x24) // Admin queue attributes.
#define WR_REGISTER_AQA(x) pci->WriteBAR32(0, 0x24, x)
#define RD_REGISTER_ASQ() pci-> ReadBAR64(0, 0x28) // Admin submission queue base address.
#define WR_REGISTER_ASQ(x) pci->WriteBAR64(0, 0x28, x)
#define RD_REGISTER_ACQ() pci-> ReadBAR64(0, 0x30) // Admin completion queue base address.
#define WR_REGISTER_ACQ(x) pci->WriteBAR64(0, 0x30, x)
#define RD_REGISTER_SQTDBL(i) pci-> ReadBAR32(0, 0x1000 + doorbellStride * (2 * (i) + 0)) // Submission queue tail doorbell.
#define WR_REGISTER_SQTDBL(i, x) pci->WriteBAR32(0, 0x1000 + doorbellStride * (2 * (i) + 0), x)
#define RD_REGISTER_CQHDBL(i) pci-> ReadBAR32(0, 0x1000 + doorbellStride * (2 * (i) + 1)) // Completion queue head doorbell.
#define WR_REGISTER_CQHDBL(i, x) pci->WriteBAR32(0, 0x1000 + doorbellStride * (2 * (i) + 1), x)
#define ADMIN_QUEUE_ENTRY_COUNT (2)
#define IO_QUEUE_ENTRY_COUNT (256)
#define SUBMISSION_QUEUE_ENTRY_BYTES (64)
#define COMPLETION_QUEUE_ENTRY_BYTES (16)
struct NVMeController : KDevice {
KPCIDevice *pci;
uint64_t capabilities;
uint32_t version;
size_t doorbellStride;
uint64_t readyTransitionTimeout;
uint64_t maximumDataTransferBytes;
uint32_t rtd3EntryLatencyUs;
uint16_t maximumOutstandingCommands;
uint8_t *adminCompletionQueue, *adminSubmissionQueue;
uint32_t adminCompletionQueueHead, adminSubmissionQueueTail;
KEvent adminCompletionQueueReceived;
bool adminCompletionQueuePhase;
uint32_t adminCompletionQueueLastResult;
uint16_t adminCompletionQueueLastStatus;
uint8_t *ioCompletionQueue, *ioSubmissionQueue;
uint32_t ioCompletionQueueHead, ioSubmissionQueueTail;
volatile uint32_t ioSubmissionQueueHead;
bool ioCompletionQueuePhase;
KEvent ioSubmissionQueueNonFull;
KSpinlock ioQueueSpinlock;
KWorkGroup *dispatchGroups[IO_QUEUE_ENTRY_COUNT];
uint64_t prpListPages[IO_QUEUE_ENTRY_COUNT];
uint64_t *prpListVirtual;
void Initialise();
void Shutdown();
bool HandleIRQ();
bool IssueAdminCommand(const void *command, uint32_t *result);
bool Access(struct NVMeDrive *drive, uint64_t offsetBytes, size_t countBytes, int operation,
KDMABuffer *buffer, uint64_t flags, KWorkGroup *dispatchGroup);
void DumpState();
};
struct NVMeDrive : KBlockDevice {
NVMeController *controller;
uint32_t nsid;
};
const char *genericCommandStatusValues[] = {
"Successful completion",
"Invalid command opcode",
"Invalid field in command",
"Command ID conflict",
"Data transfer error",
"Commands aborted due to powerloss notification",
"Internal error",
"Command abort requested",
"Command aborted due to SQ deletion",
"Command aborted due to failed fused command",
"Command aborted due to missing fused command",
"Invalid namespace or format",
"Command sequence error",
"Invalid SGL segment descriptor",
"Invalid number of SGL descriptors",
"Data SGL length invalid",
"Metadata SGL length invalid",
"SGL descriptor type invalid",
"Invalid use of controller memory buffer",
"PRP offset invalid",
"Atomic write unit exceeded",
"Operation denied",
"SGL offset invalid",
"Reserved",
"Host identifier inconsistent format",
"Keep alive timer expired",
"Keep alive timeout invalid",
"Command aborted due to preempt and abort",
"Sanitize failed",
"Sanitize in progress",
"SGL data block granularity invalid",
"Command not supported for queue in CMB",
"Namespace is write protected",
"Command interrupted",
"Transient transport error",
};
const char *genericCommandStatusValuesNVM[] = {
"LBA out of range",
"Capacity exceeded",
"Namespace not ready",
"Reservation conflict",
"Format in progress",
};
const char *commandSpecificStatusValues[] = {
"Completion queue invalid",
"Invalid queue identifier",
"Invalid queue size",
"Abort command limit exceeded",
"Reserved",
"Asynchronous event request limit exceeded",
"Invalid firmware slot",
"Invalid firmware image",
"Invalid interrupt vector",
"Invalid log page",
"Invalid format",
"Firmware activation requirse conventional reset",
"Invalid queue deletion",
"Feature identifier not saveable",
"Feature not changeable",
"Feature not namespace specific",
"Firmware activation requires NVM subsystem reset",
"Firmware activation requires controller level reset",
"Firmware activation requires maximum time violation",
"Firmware activation prohibited",
"Overlapping range",
"Namespace insufficient capacity",
"Namespace identifier unavailable",
"Reserved",
"Namespace already attached",
"Namespace is private",
"Namespace not attached",
"Thin provisioning not supported",
"Controller list invalid",
"Device self-test in progress",
"Boot partition write prohibited",
"Invalid controller identifier",
"Invalid secondary controller state",
"Invalid number of controller resources",
"Invalid resource identifier",
"Sanitize prohibited while persistent memory region is enabled",
"ANA group identifier invalid",
"ANA attach failed",
};
const char *commandSpecificStatusValuesNVM[] = {
"Confliciting attributes",
"Invalid protection information",
"Attempted write to read only range",
};
const char *mediaAndDataIntegrityErrorValuesNVM[] = {
"Write fault",
"Unrecovered read error",
"End-to-end guard check error",
"End-to-end application tag check error",
"End-to-end reference tag check error",
"Compare failure",
"Access denied",
"Dealocated or unwritten logical block",
};
const char *pathRelatedStatusValues[] = {
"Internal path error",
"Asymmetric access persistent loss",
"Asymmetric access inaccessible",
"Asymmetric access transition",
};
const char *GetErrorMessage(uint8_t statusCodeType, uint8_t statusCode) {
if (statusCodeType == 0) {
if (statusCode < sizeof(genericCommandStatusValues) / sizeof(genericCommandStatusValues[0])) {
return genericCommandStatusValues[statusCode];
} else if (statusCode > 0x80 && (uint8_t) (statusCode - 0x80) < sizeof(genericCommandStatusValuesNVM) / sizeof(genericCommandStatusValuesNVM[0])) {
return genericCommandStatusValuesNVM[statusCode - 0x80];
}
} else if (statusCodeType == 1) {
if (statusCode < sizeof(commandSpecificStatusValues) / sizeof(commandSpecificStatusValues[0])) {
return commandSpecificStatusValues[statusCode];
} else if (statusCode > 0x80 && (uint8_t) (statusCode - 0x80) < sizeof(commandSpecificStatusValuesNVM) / sizeof(commandSpecificStatusValuesNVM[0])) {
return commandSpecificStatusValuesNVM[statusCode - 0x80];
}
} else if (statusCodeType == 2) {
if (statusCode > 0x80 && (uint8_t) (statusCode - 0x80) < sizeof(mediaAndDataIntegrityErrorValuesNVM) / sizeof(mediaAndDataIntegrityErrorValuesNVM[0])) {
return mediaAndDataIntegrityErrorValuesNVM[statusCode - 0x80];
}
} else if (statusCodeType == 3) {
if (statusCode < sizeof(pathRelatedStatusValues) / sizeof(pathRelatedStatusValues[0])) {
return pathRelatedStatusValues[statusCode];
}
}
return "Unknown error";
}
void NVMeController::DumpState() {
EsPrint("NVMe controller state:\n");
EsPrint("\t--- Registers ---\n");
EsPrint("\t\tController capababilities: %x.\n", RD_REGISTER_CAP());
EsPrint("\t\tVersion: %x.\n", RD_REGISTER_VS());
EsPrint("\t\tInterrupt mask set: %x.\n", RD_REGISTER_INTMS());
EsPrint("\t\tInterrupt mask clear: %x.\n", RD_REGISTER_INTMC());
EsPrint("\t\tController configuration: %x.\n", RD_REGISTER_CC());
EsPrint("\t\tController status: %x.\n", RD_REGISTER_CSTS());
EsPrint("\t\tAdmin queue attributes: %x.\n", RD_REGISTER_AQA());
EsPrint("\t\tAdmin submission queue base address: %x.\n", RD_REGISTER_ASQ());
EsPrint("\t\tAdmin completion queue base address: %x.\n", RD_REGISTER_ACQ());
EsPrint("\t\tAdmin submission queue tail doorbell: %x.\n", RD_REGISTER_SQTDBL(0));
EsPrint("\t\tAdmin completion queue head doorbell: %x.\n", RD_REGISTER_CQHDBL(0));
EsPrint("\t\tIO submission queue tail doorbell: %x.\n", RD_REGISTER_SQTDBL(1));
EsPrint("\t\tIO completion queue head doorbell: %x.\n", RD_REGISTER_CQHDBL(1));
EsPrint("\t--- Internal ---\n");
EsPrint("\t\tAdmin completion queue: %x.\n", adminCompletionQueue);
EsPrint("\t\tAdmin completion queue head: %x.\n", adminCompletionQueueHead);
EsPrint("\t\tAdmin completion queue phase: %d.\n", adminCompletionQueuePhase);
EsPrint("\t\tAdmin submission queue: %x.\n", adminSubmissionQueue);
EsPrint("\t\tAdmin submission queue tail: %x.\n", adminSubmissionQueueTail);
EsPrint("\t\tIO completion queue: %x.\n", ioCompletionQueue);
EsPrint("\t\tIO completion queue head: %x.\n", ioCompletionQueueHead);
EsPrint("\t\tIO completion queue phase: %d.\n", ioCompletionQueuePhase);
EsPrint("\t\tIO submission queue: %x.\n", ioSubmissionQueue);
EsPrint("\t\tIO submission queue tail: %x.\n", ioSubmissionQueueTail);
EsPrint("\t\tIO submission queue head: %x.\n", ioSubmissionQueueHead);
EsPrint("\t\tIO submission queue non full: %d.\n", ioSubmissionQueueNonFull.state);
EsPrint("\t\tPRP list virtual: %x.\n", prpListVirtual);
EsPrint("\t--- Outstanding commands ---\n");
for (uintptr_t i = ioSubmissionQueueHead; i != ioSubmissionQueueTail; i = (i + 1) % IO_QUEUE_ENTRY_COUNT) {
EsPrint("\t\t(%d) %x, %x, %x, %x, %x, %x, %x, %x.\n", i,
((uint64_t *) ioSubmissionQueue)[i * 8 + 0], ((uint64_t *) ioSubmissionQueue)[i * 8 + 1],
((uint64_t *) ioSubmissionQueue)[i * 8 + 2], ((uint64_t *) ioSubmissionQueue)[i * 8 + 3],
((uint64_t *) ioSubmissionQueue)[i * 8 + 4], ((uint64_t *) ioSubmissionQueue)[i * 8 + 5],
((uint64_t *) ioSubmissionQueue)[i * 8 + 6], ((uint64_t *) ioSubmissionQueue)[i * 8 + 7]);
}
}
bool NVMeController::IssueAdminCommand(const void *command, uint32_t *result) {
EsMemoryCopy(adminSubmissionQueue + adminSubmissionQueueTail * SUBMISSION_QUEUE_ENTRY_BYTES, command, SUBMISSION_QUEUE_ENTRY_BYTES);
adminSubmissionQueueTail = (adminSubmissionQueueTail + 1) % ADMIN_QUEUE_ENTRY_COUNT;
KEventReset(&adminCompletionQueueReceived);
__sync_synchronize();
WR_REGISTER_SQTDBL(0, adminSubmissionQueueTail);
if (!KEventWait(&adminCompletionQueueReceived, GENERAL_TIMEOUT)) {
// TODO Timeout. Now what?
KernelLog(LOG_ERROR, "NVMe", "admin command timeout", "Admin command timeout when sending command %x.\n", command);
return false;
}
if (adminCompletionQueueLastStatus) {
bool doNotRetry = adminCompletionQueueLastStatus & 0x8000;
bool more = adminCompletionQueueLastStatus & 0x4000;
uint8_t commandRetryDelay = (adminCompletionQueueLastStatus >> 12) & 0x03;
uint8_t statusCodeType = (adminCompletionQueueLastStatus >> 9) & 0x07;
uint8_t statusCode = (adminCompletionQueueLastStatus >> 1) & 0xFF;
KernelLog(LOG_ERROR, "NVMe", "admin command failed", "Admin command failed - '%z': %z%zretry delay - %d, type - %d, code - %d.\n",
GetErrorMessage(statusCodeType, statusCode),
doNotRetry ? "do not retry, " : "", more ? "more info in log page, " : "", commandRetryDelay, statusCodeType, statusCode);
return false;
}
if (result) *result = adminCompletionQueueLastResult;
return true;
}
bool NVMeController::Access(struct NVMeDrive *drive, uint64_t offsetBytes, size_t countBytes, int operation,
KDMABuffer *buffer, uint64_t, KWorkGroup *dispatchGroup) {
// TODO Temporary.
// if (operation == K_ACCESS_WRITE) KernelPanic("NVMeController::Access - Attempted write.\n");
// Build the PRPs.
KDMASegment segment1 = KDMABufferNextSegment(buffer);
uint64_t prp1 = segment1.physicalAddress, prp2 = 0;
if (!segment1.isLast) {
KDMASegment segment2 = KDMABufferNextSegment(buffer, true /* peek */);
if (segment2.isLast) prp2 = segment2.physicalAddress;
}
retry:;
KSpinlockAcquire(&ioQueueSpinlock);
// Is there space in the submission queue?
uintptr_t newTail = (ioSubmissionQueueTail + 1) % IO_QUEUE_ENTRY_COUNT;
bool submissionQueueFull = newTail == ioSubmissionQueueHead;
if (!submissionQueueFull) {
KernelLog(LOG_VERBOSE, "NVMe", "start access", "Start access of %d, offset %D, count %D, using slot %d.\n",
drive->nsid, offsetBytes, countBytes, ioSubmissionQueueTail);
uint64_t offsetSector = offsetBytes / drive->sectorSize;
uint64_t countSectors = countBytes / drive->sectorSize;
// Build the PRP list.
if (!prp2) {
prp2 = prpListPages[ioSubmissionQueueTail];
MMArchRemap(MMGetKernelSpace(), prpListVirtual, prp2);
uintptr_t index = 0;
while (!KDMABufferIsComplete(buffer)) {
if (index == K_PAGE_SIZE / sizeof(uint64_t)) {
KernelPanic("NVMeController::Access - Out of bounds in PRP list.\n");
}
prpListVirtual[index++] = KDMABufferNextSegment(buffer).physicalAddress;
}
}
// Create the command.
uint32_t *command = (uint32_t *) (ioSubmissionQueue + ioSubmissionQueueTail * SUBMISSION_QUEUE_ENTRY_BYTES);
command[0] = (ioSubmissionQueueTail << 16) /* command identifier */ | (operation == K_ACCESS_WRITE ? 0x01 : 0x02) /* opcode */;
command[1] = drive->nsid;
command[2] = command[3] = command[4] = command[5] = 0;
command[6] = prp1 & 0xFFFFFFFF;
command[7] = (prp1 >> 32) & 0xFFFFFFFF;
command[8] = prp2 & 0xFFFFFFFF;
command[9] = (prp2 >> 32) & 0xFFFFFFFF;
command[10] = offsetSector & 0xFFFFFFFF;
command[11] = (offsetSector >> 32) & 0xFFFFFFFF;
command[12] = (countSectors - 1) & 0xFFFF;
command[13] = command[14] = command[15] = 0;
// Store the dispatch group, and update the queue tail.
dispatchGroups[ioSubmissionQueueTail] = dispatchGroup;
ioSubmissionQueueTail = newTail;
__sync_synchronize();
WR_REGISTER_SQTDBL(1, newTail);
} else {
KEventReset(&ioSubmissionQueueNonFull);
}
KSpinlockRelease(&ioQueueSpinlock);
if (submissionQueueFull) {
// Wait for the controller to consume an entry in the submission queue.
KEventWait(&ioSubmissionQueueNonFull);
goto retry;
}
return true;
}
bool NVMeController::HandleIRQ() {
bool fromAdmin = false, fromIO = false;
// Check the phase bit of the completion queue head entry.
if (adminCompletionQueue && (adminCompletionQueue[adminCompletionQueueHead * COMPLETION_QUEUE_ENTRY_BYTES + 14] & (1 << 0)) != adminCompletionQueuePhase) {
fromAdmin = true;
adminCompletionQueueLastResult = *(uint32_t *) (adminCompletionQueue + adminCompletionQueueHead * COMPLETION_QUEUE_ENTRY_BYTES + 0);
adminCompletionQueueLastStatus = *(uint16_t *) (adminCompletionQueue + adminCompletionQueueHead * COMPLETION_QUEUE_ENTRY_BYTES + 14) & 0xFFFE;
// Advance the queue head.
adminCompletionQueueHead++;
if (adminCompletionQueueHead == ADMIN_QUEUE_ENTRY_COUNT) {
adminCompletionQueuePhase = !adminCompletionQueuePhase;
adminCompletionQueueHead = 0;
}
WR_REGISTER_CQHDBL(0, adminCompletionQueueHead);
// Signal the event.
KEventSet(&adminCompletionQueueReceived);
}
// Check the phase bit of the IO completion queue head entry.
while (ioCompletionQueue && (ioCompletionQueue[ioCompletionQueueHead * COMPLETION_QUEUE_ENTRY_BYTES + 14] & (1 << 0)) != ioCompletionQueuePhase) {
fromIO = true;
uint16_t index = *(uint16_t *) (ioCompletionQueue + ioCompletionQueueHead * COMPLETION_QUEUE_ENTRY_BYTES + 12);
uint16_t status = *(uint16_t *) (ioCompletionQueue + ioCompletionQueueHead * COMPLETION_QUEUE_ENTRY_BYTES + 14) & 0xFFFE;
KernelLog(LOG_VERBOSE, "NVMe", "end access", "End access of slot %d.\n", index);
if (index >= IO_QUEUE_ENTRY_COUNT) {
KernelLog(LOG_ERROR, "NVMe", "invalid completion entry", "Completion entry reported invalid command index of %d.\n",
index);
} else {
KWorkGroup *dispatchGroup = dispatchGroups[index];
if (status) {
uint8_t statusCodeType = (status >> 9) & 0x07, statusCode = (status >> 1) & 0xFF;
KernelLog(LOG_ERROR, "NVMe", "command failed", "Command failed with status %X/%X: %z.\n",
statusCodeType, statusCode, GetErrorMessage(statusCodeType, statusCode));
dispatchGroup->End(false /* failed */);
} else {
dispatchGroup->End(true /* success */);
}
dispatchGroups[index] = nullptr;
}
// Indicate the submission queue entry was consumed.
__sync_synchronize();
ioSubmissionQueueHead = *(uint16_t *) (ioCompletionQueue + ioCompletionQueueHead * COMPLETION_QUEUE_ENTRY_BYTES + 8);
KEventSet(&ioSubmissionQueueNonFull, false, true);
// Advance the queue head.
ioCompletionQueueHead++;
if (ioCompletionQueueHead == IO_QUEUE_ENTRY_COUNT) {
ioCompletionQueuePhase = !ioCompletionQueuePhase;
ioCompletionQueueHead = 0;
}
WR_REGISTER_CQHDBL(1, ioCompletionQueueHead);
}
return fromAdmin || fromIO;
}
void NVMeController::Initialise() {
capabilities = RD_REGISTER_CAP();
version = RD_REGISTER_VS();
KernelLog(LOG_INFO, "NVMe", "initial register dump",
"Registers at initialisation: capabilities - %x; version - %x, configuration - %x, status - %x, admin queue attributes - %x. Mapped at %x.\n",
capabilities, version, RD_REGISTER_CC(), RD_REGISTER_CSTS(), RD_REGISTER_AQA());
// Check the version is acceptable.
if ((version >> 16) < 1) {
KernelLog(LOG_ERROR, "NVMe", "unsupported version", "Controller reports major version 0, which is not supported.\n");
return;
}
if ((version >> 16) == 1 && ((version >> 8) & 0xFF) < 1) {
KernelLog(LOG_ERROR, "NVMe", "unsupported version", "Controller reports version before 1.1, which is not supported.\n");
return;
}
// Check the capabilities are acceptable.
if ((capabilities & 0xFFFF) == 0) {
KernelLog(LOG_ERROR, "NVMe", "unsupported capabilities", "Invalid CAP.MQES value, expected at least 1.\n");
return;
}
if (~capabilities & (1UL << 37)) {
KernelLog(LOG_ERROR, "NVMe", "unsupported capabilities", "Controller does not support NVMe command set.\n");
return;
}
if (((capabilities >> 48) & 0xF) > K_PAGE_BITS - 12) {
KernelLog(LOG_ERROR, "NVMe", "unsupported capabilities", "Controller requires a minimum page size greater than the host uses.\n");
return;
}
if (((capabilities >> 52) & 0xF) < K_PAGE_BITS - 12) {
KernelLog(LOG_ERROR, "NVMe", "unsupported capabilities", "Controller requires a maximum page size less than the host uses.\n");
return;
}
doorbellStride = 4 << ((capabilities >> 32) & 0xF);
readyTransitionTimeout = ((capabilities >> 24) & 0xFF) * 500;
uint32_t previousConfiguration = RD_REGISTER_CC();
// Reset the controller.
if (previousConfiguration & (1 << 0)) {
KTimeout timeout(readyTransitionTimeout);
while ((~RD_REGISTER_CSTS() & (1 << 0)) && !timeout.Hit());
if (timeout.Hit()) { KernelLog(LOG_ERROR, "NVMe", "reset timeout", "Timeout during reset sequence (1).\n"); return; }
WR_REGISTER_CC(RD_REGISTER_CC() & ~(1 << 0));
}
{
KTimeout timeout(readyTransitionTimeout);
while ((RD_REGISTER_CSTS() & (1 << 0)) && !timeout.Hit());
if (timeout.Hit()) { KernelLog(LOG_ERROR, "NVMe", "reset timeout", "Timeout during reset sequence (2).\n"); return; }
}
// Configure the controller to use the NVMe command set, the host page size, the IO queue entry size, and round robin arbitration.
WR_REGISTER_CC((RD_REGISTER_CC() & (0xFF00000F)) | (0x00460000) | ((K_PAGE_BITS - 12) << 7));
// Configure the admin queues to use our desired entry count, and allocate memory for them.
WR_REGISTER_AQA((RD_REGISTER_AQA() & 0xF000F000) | ((ADMIN_QUEUE_ENTRY_COUNT - 1) << 16) | (ADMIN_QUEUE_ENTRY_COUNT - 1));
uint64_t adminSubmissionQueueBytes = ADMIN_QUEUE_ENTRY_COUNT * SUBMISSION_QUEUE_ENTRY_BYTES;
uint64_t adminCompletionQueueBytes = ADMIN_QUEUE_ENTRY_COUNT * COMPLETION_QUEUE_ENTRY_BYTES;
uint64_t adminQueuePages = (adminSubmissionQueueBytes + K_PAGE_SIZE - 1) / K_PAGE_SIZE + (adminCompletionQueueBytes + K_PAGE_SIZE - 1) / K_PAGE_SIZE;
uintptr_t adminQueuePhysicalAddress = MMPhysicalAllocate(MM_PHYSICAL_ALLOCATE_CAN_FAIL | MM_PHYSICAL_ALLOCATE_COMMIT_NOW | MM_PHYSICAL_ALLOCATE_ZEROED,
adminQueuePages, (4096 + K_PAGE_SIZE - 1) / K_PAGE_SIZE);
if (!adminQueuePhysicalAddress) {
KernelLog(LOG_ERROR, "NVMe", "allocation failure", "Could not allocate %d pages of contiguous physical memory for admin queues.\n", adminQueuePages);
return;
}
uintptr_t adminSubmissionQueuePhysicalAddress = adminQueuePhysicalAddress;
uintptr_t adminCompletionQueuePhysicalAddress = adminQueuePhysicalAddress + ((adminSubmissionQueueBytes + K_PAGE_SIZE - 1) & ~(K_PAGE_SIZE - 1));
WR_REGISTER_ASQ(adminSubmissionQueuePhysicalAddress);
WR_REGISTER_ACQ(adminCompletionQueuePhysicalAddress);
adminSubmissionQueue = (uint8_t *) MMMapPhysical(MMGetKernelSpace(), adminSubmissionQueuePhysicalAddress, adminSubmissionQueueBytes, ES_FLAGS_DEFAULT);
adminCompletionQueue = (uint8_t *) MMMapPhysical(MMGetKernelSpace(), adminCompletionQueuePhysicalAddress, adminCompletionQueueBytes, ES_FLAGS_DEFAULT);
if (!adminSubmissionQueue || !adminCompletionQueue) {
KernelLog(LOG_ERROR, "NVMe", "allocation failure", "Could not map admin queues.\n");
return;
}
KernelLog(LOG_INFO, "NVMe", "admin queue configuration", "Configured admin queues to use physical addresses %x and %x with %d entries each.\n",
adminSubmissionQueuePhysicalAddress, adminCompletionQueuePhysicalAddress, ADMIN_QUEUE_ENTRY_COUNT);
// Start the controller.
WR_REGISTER_CC(RD_REGISTER_CC() | (1 << 0));
{
KTimeout timeout(readyTransitionTimeout);
while (!timeout.Hit()) {
uint32_t status = RD_REGISTER_CSTS();
if (status & (1 << 1)) {
KernelLog(LOG_ERROR, "NVMe", "fatal error", "Fatal error while enabling controller.\n");
return;
} else if (status & (1 << 0)) {
break;
}
}
if (timeout.Hit()) { KernelLog(LOG_ERROR, "NVMe", "reset timeout", "Timeout during reset sequence (3).\n"); return; }
}
// Enable IRQs for the admin queue, and register our interrupt handler.
if (!pci->EnableSingleInterrupt([] (uintptr_t, void *context) { return ((NVMeController *) context)->HandleIRQ(); }, this, "NVMe")) {
KernelLog(LOG_ERROR, "NVMe", "IRQ registration failure", "Could not register IRQ %d.\n", pci->interruptLine);
return;
}
WR_REGISTER_INTMC(1 << 0);
// Identify controller.
uintptr_t identifyDataPhysicalAddress = MMPhysicalAllocate(MM_PHYSICAL_ALLOCATE_CAN_FAIL | MM_PHYSICAL_ALLOCATE_COMMIT_NOW | MM_PHYSICAL_ALLOCATE_ZEROED,
(4096 * 2 + K_PAGE_SIZE - 1) / K_PAGE_SIZE);
if (!identifyDataPhysicalAddress) {
KernelLog(LOG_ERROR, "NVMe", "allocation failure", "Could not allocate physical memory for receiving identify data.\n");
return;
}
uint8_t *identifyData = (uint8_t *) MMMapPhysical(MMGetKernelSpace(), identifyDataPhysicalAddress, 4096 * 2, ES_FLAGS_DEFAULT);
if (!identifyData) {
KernelLog(LOG_ERROR, "NVMe", "allocation failure", "Could not map memory for receiving identify data.\n");
return;
}
{
uint32_t command[16] = {};
command[0] = 0x06; // Identify opcode.
command[6] = identifyDataPhysicalAddress & 0xFFFFFFFF;
command[7] = (identifyDataPhysicalAddress >> 32) & 0xFFFFFFFF;
command[10] = 0x01; // Identify controller.
if (!IssueAdminCommand(command, nullptr)) {
KernelLog(LOG_ERROR, "NVMe", "identify controller failure", "The identify controller admin command failed.\n");
return;
}
maximumDataTransferBytes = identifyData[77] ? (1 << (12 + identifyData[77] + (((capabilities >> 48) & 0xF)))) : 0;
rtd3EntryLatencyUs = *(uint32_t *) (identifyData + 88);
maximumOutstandingCommands = *(uint16_t *) (identifyData + 514);
if (rtd3EntryLatencyUs > 250 * 1000) {
rtd3EntryLatencyUs = 250 * 1000; // Maximum shutdown delay: 250ms.
}
if (identifyData[111] > 0x01) {
KernelLog(LOG_ERROR, "NVMe", "unsupported controller type", "Controller type %X is not supported. Only IO controllers are currently supported.\n",
identifyData[111]);
return;
}
KernelLog(LOG_INFO, "NVMe", "controller identify data", "Controller identify reported the following information: "
"serial number - '%s', model number - '%s', firmware revision - '%s', "
"maximum data transfer - %D, RTD3 entry latency - %dus, maximum outstanding commands - %d.\n",
20, identifyData + 4, 40, identifyData + 24, 8, identifyData + 64,
maximumDataTransferBytes, rtd3EntryLatencyUs, maximumOutstandingCommands);
if (maximumDataTransferBytes == 0 || maximumDataTransferBytes >= 2097152) {
maximumDataTransferBytes = 2097152;
}
}
// Reset the software progress marker.
{
// TODO How to check if this feature is supported?
uint32_t command[16] = {};
command[0] = 0x09; // Set features opcode.
command[10] = 0x80; // Software progress marker feature.
command[11] = 0x00; // Reset to 0.
IssueAdminCommand(command, nullptr); // Ignore errors.
}
// Create IO completion queue.
{
uint64_t bytes = IO_QUEUE_ENTRY_COUNT * COMPLETION_QUEUE_ENTRY_BYTES;
uint64_t pages = (bytes + K_PAGE_SIZE - 1) / K_PAGE_SIZE;
uintptr_t physicalAddress = MMPhysicalAllocate(MM_PHYSICAL_ALLOCATE_CAN_FAIL | MM_PHYSICAL_ALLOCATE_COMMIT_NOW | MM_PHYSICAL_ALLOCATE_ZEROED, pages);
if (!physicalAddress) {
KernelLog(LOG_ERROR, "NVMe", "allocation failure", "Could not allocate IO completion queue memory.\n");
return;
}
ioCompletionQueue = (uint8_t *) MMMapPhysical(MMGetKernelSpace(), physicalAddress, bytes, ES_FLAGS_DEFAULT);
if (!ioCompletionQueue) {
KernelLog(LOG_ERROR, "NVMe", "allocation failure", "Could not map IO completion queue memory.\n");
return;
}
uint32_t command[16] = {};
command[0] = 0x05; // Create IO completion queue opcode.
command[6] = physicalAddress & 0xFFFFFFFF;
command[7] = (physicalAddress >> 32) & 0xFFFFFFFF;
command[10] = 1 /* identifier */ | ((IO_QUEUE_ENTRY_COUNT - 1) << 16);
command[11] = (1 << 0) /* physically contiguous */ | (1 << 1) /* interrupts enabled */;
if (!IssueAdminCommand(command, nullptr)) {
KernelLog(LOG_ERROR, "NVMe", "create queue failure", "Could not create the IO completion queue.\n");
return;
}
}
// Create IO submission queue.
{
uint64_t bytes = IO_QUEUE_ENTRY_COUNT * SUBMISSION_QUEUE_ENTRY_BYTES;
uint64_t pages = (bytes + K_PAGE_SIZE - 1) / K_PAGE_SIZE;
uintptr_t physicalAddress = MMPhysicalAllocate(MM_PHYSICAL_ALLOCATE_CAN_FAIL | MM_PHYSICAL_ALLOCATE_COMMIT_NOW | MM_PHYSICAL_ALLOCATE_ZEROED, pages);
if (!physicalAddress) {
KernelLog(LOG_ERROR, "NVMe", "allocation failure", "Could not allocate IO submission queue memory.\n");
return;
}
ioSubmissionQueue = (uint8_t *) MMMapPhysical(MMGetKernelSpace(), physicalAddress, bytes, ES_FLAGS_DEFAULT);
if (!ioSubmissionQueue) {
KernelLog(LOG_ERROR, "NVMe", "allocation failure", "Could not map IO submission queue memory.\n");
return;
}
uint32_t command[16] = {};
command[0] = 0x01; // Create IO submission queue opcode.
command[6] = physicalAddress & 0xFFFFFFFF;
command[7] = (physicalAddress >> 32) & 0xFFFFFFFF;
command[10] = 1 /* identifier */ | ((IO_QUEUE_ENTRY_COUNT - 1) << 16);
command[11] = (1 << 0) /* physically contiguous */ | (1 << 16) /* completion queue identifier */;
if (!IssueAdminCommand(command, nullptr)) {
KernelLog(LOG_ERROR, "NVMe", "create queue failure", "Could not create the IO submission queue.\n");
return;
}
}
// Allocate physical memory for PRP lists.
{
for (uintptr_t i = 0; i < IO_QUEUE_ENTRY_COUNT; i++) {
prpListPages[i] = MMPhysicalAllocate(MM_PHYSICAL_ALLOCATE_CAN_FAIL | MM_PHYSICAL_ALLOCATE_COMMIT_NOW, 1);
if (!prpListPages[i]) {
KernelLog(LOG_ERROR, "NVMe", "allocation failure", "Could not allocate physical memory for PRP lists.\n");
return;
}
}
prpListVirtual = (uint64_t *) MMMapPhysical(MMGetKernelSpace(), prpListPages[0], K_PAGE_SIZE, ES_FLAGS_DEFAULT);
if (!prpListVirtual) {
KernelLog(LOG_ERROR, "NVMe", "allocation failure", "Could not allocate virtual memory to modify PRP lists.\n");
return;
}
}
// Identify active namespace IDs.
uint32_t nsid = 0;
while (true) {
uint32_t command[16] = {};
command[0] = 0x06; // Identify opcode.
command[1] = nsid; // List NSIDs greater than the last one we saw.
command[6] = identifyDataPhysicalAddress & 0xFFFFFFFF;
command[7] = (identifyDataPhysicalAddress >> 32) & 0xFFFFFFFF;
command[10] = 0x02; // Identify active namespace IDs.
if (!IssueAdminCommand(command, nullptr)) {
KernelLog(LOG_ERROR, "NVMe", "identify controller failure", "The identify controller admin command failed.\n");
return;
}
for (uintptr_t i = 0; i < 1024; i++) {
nsid = ((uint32_t *) identifyData)[i];
if (!nsid) {
goto allNamespacesIdentified;
}
KernelLog(LOG_INFO, "NVMe", "active namespace ID", "Namespace ID %d is active.\n", nsid);
// Identify the namespace.
command[0] = 0x06; // Identify opcode.
command[1] = nsid;
command[6] = (identifyDataPhysicalAddress + 4096) & 0xFFFFFFFF;
command[7] = ((identifyDataPhysicalAddress + 4096) >> 32) & 0xFFFFFFFF;
command[10] = 0x00; // Identify namespace.
if (!IssueAdminCommand(command, nullptr)) {
KernelLog(LOG_ERROR, "NVMe", "identify namespace failure", "Could not identify namespace %d.\n", nsid);
continue;
}
uint8_t formattedLBASize = identifyData[4096 + 26];
uint32_t lbaFormat = *(uint32_t *) (identifyData + 4096 + 128 + 4 * (formattedLBASize & 0xF));
if (lbaFormat & 0xFFFF) {
KernelLog(LOG_ERROR, "NVMe", "metadata unsupported", "Namespace %d has %D of metadata per block, which is unsupported.\n",
nsid, lbaFormat & 0xFFFF);
continue;
}
uint8_t sectorBytesExponent = (lbaFormat >> 16) & 0xFF;
if (sectorBytesExponent < 9 || sectorBytesExponent > 16) {
KernelLog(LOG_ERROR, "NVMe", "unsupported block size", "Namespace %d uses blocks of size 2^%d bytes, which is unsupported.\n",
nsid, sectorBytesExponent);
continue;
}
uint64_t sectorBytes = 1 << sectorBytesExponent;
uint64_t capacity = *(uint64_t *) (identifyData + 4096 + 8) * sectorBytes;
bool readOnly = identifyData[99] & (1 << 0);
KernelLog(LOG_INFO, "NVMe", "namespace identified", "Identifier namespace %d with sectors of size %D, and a capacity of %D.%z\n",
nsid, sectorBytes, capacity, readOnly ? " The namespace is read-only." : "");
NVMeDrive *device = (NVMeDrive *) KDeviceCreate("NVMe namespace", this, sizeof(NVMeDrive));
if (!device) {
KernelLog(LOG_ERROR, "NVMe", "allocation failure", "Could not create device for namespace %d.\n", nsid);
goto allNamespacesIdentified;
}
device->controller = this;
device->nsid = nsid;
device->sectorSize = sectorBytes;
device->sectorCount = capacity / sectorBytes;
device->maxAccessSectorCount = maximumDataTransferBytes / sectorBytes;
device->readOnly = readOnly;
device->driveType = ES_DRIVE_TYPE_SSD;
device->access = [] (KBlockDeviceAccessRequest request) {
NVMeDrive *drive = (NVMeDrive *) request.device;
request.dispatchGroup->Start();
if (!drive->controller->Access(drive, request.offset, request.count, request.operation,
request.buffer, request.flags, request.dispatchGroup)) {
request.dispatchGroup->End(false);
}
};
FSRegisterBlockDevice(device);
}
}
allNamespacesIdentified:;
}
void NVMeController::Shutdown() {
// Delete the IO queues.
uint32_t command[16] = {};
command[0] = 0x00; // Delete IO submission queue opcode.
command[10] = 1 /* identifier */;
IssueAdminCommand(command, nullptr);
command[0] = 0x04; // Delete IO completion queue opcode.
IssueAdminCommand(command, nullptr);
// Inform the controller of shutdown.
WR_REGISTER_CC(RD_REGISTER_CC() | (1 << 14));
// Wait for shutdown processing to complete.
KTimeout timeout(rtd3EntryLatencyUs / 1000 + 1);
while (!timeout.Hit() && (RD_REGISTER_CSTS() & 12) != 8);
}
static void DeviceAttach(KDevice *_parent) {
KPCIDevice *parent = (KPCIDevice *) _parent;
NVMeController *device = (NVMeController *) KDeviceCreate("NVMe controller", parent, sizeof(NVMeController));
if (!device) return;
device->pci = parent;
device->shutdown = [] (KDevice *device) {
((NVMeController *) device)->Shutdown();
};
device->dumpState = [] (KDevice *device) {
((NVMeController *) device)->DumpState();
};
KernelLog(LOG_INFO, "NVMe", "found controller", "Found NVMe controller at PCI function %d/%d/%d.\n", parent->bus, parent->slot, parent->function);
// Enable PCI features.
parent->EnableFeatures(K_PCI_FEATURE_INTERRUPTS
| K_PCI_FEATURE_BUSMASTERING_DMA
| K_PCI_FEATURE_MEMORY_SPACE_ACCESS
| K_PCI_FEATURE_BAR_0);
// Initialise the controller.
device->Initialise();
};
KDriver driverNVMe = {
.attach = DeviceAttach,
};

554
drivers/pci.cpp Normal file
View File

@ -0,0 +1,554 @@
// TODO Warn on Read/WriteBAR if port IO/memory space access is disabled in the command register.
#include <module.h>
#define PCI_CONFIG (0xCF8)
#define PCI_DATA (0xCFC)
struct PCIController : KDevice {
inline uint32_t ReadConfig(uint8_t bus, uint8_t device, uint8_t function, uint8_t offset, int size = 32);
inline void WriteConfig(uint8_t bus, uint8_t device, uint8_t function, uint8_t offset, uint32_t value, int size = 32);
#define PCI_BUS_DO_NOT_SCAN 0
#define PCI_BUS_SCAN_NEXT 1
#define PCI_BUS_SCANNED 2
uint8_t busScanStates[256];
bool foundUSBController;
void Enumerate();
void EnumerateFunction(int bus, int device, int function, int *busesToScan);
};
const char *classCodeStrings[] = {
"Unknown",
"Mass storage controller",
"Network controller",
"Display controller",
"Multimedia controller",
"Memory controller",
"Bridge controller",
"Simple communication controller",
"Base system peripheral",
"Input device controller",
"Docking station",
"Processor",
"Serial bus controller",
"Wireless controller",
"Intelligent controller",
"Satellite communication controller",
"Encryption controller",
"Signal processing controller",
};
const char *subclassCodeStrings1[] = {
"SCSI bus controller",
"IDE controller",
"Floppy disk controller",
"IPI bus controller",
"RAID controller",
"ATA controller",
"Serial ATA",
"Serial attached SCSI",
"Non-volatile memory controller",
};
const char *subclassCodeStrings12[] = {
"FireWire (IEEE 1394) controller",
"ACCESS bus",
"SSA",
"USB controller",
"Fibre channel",
"SMBus",
"InfiniBand",
"IPMI interface",
"SERCOS interface (IEC 61491)",
"CANbus",
};
const char *progIFStrings12_3[] = {
"UHCI",
"OHCI",
"EHCI",
"XHCI",
};
uint8_t KPCIDevice::ReadBAR8(uintptr_t index, uintptr_t offset) {
uint32_t baseAddress = baseAddresses[index];
uint8_t result;
if (baseAddress & 1) {
result = ProcessorIn8((baseAddress & ~3) + offset);
} else {
result = *(volatile uint8_t *) (baseAddressesVirtual[index] + offset);
}
KernelLog(LOG_VERBOSE, "PCI", "read BAR", "ReadBAR8 - %x, %x, %x, %x\n", this, index, offset, result);
return result;
}
void KPCIDevice::WriteBAR8(uintptr_t index, uintptr_t offset, uint8_t value) {
uint32_t baseAddress = baseAddresses[index];
KernelLog(LOG_VERBOSE, "PCI", "write BAR", "WriteBAR8 - %x, %x, %x, %x\n", this, index, offset, value);
if (baseAddress & 1) {
ProcessorOut8((baseAddress & ~3) + offset, value);
} else {
*(volatile uint8_t *) (baseAddressesVirtual[index] + offset) = value;
}
}
uint16_t KPCIDevice::ReadBAR16(uintptr_t index, uintptr_t offset) {
uint32_t baseAddress = baseAddresses[index];
uint16_t result;
if (baseAddress & 1) {
result = ProcessorIn16((baseAddress & ~3) + offset);
} else {
result = *(volatile uint16_t *) (baseAddressesVirtual[index] + offset);
}
KernelLog(LOG_VERBOSE, "PCI", "read BAR", "ReadBAR16 - %x, %x, %x, %x\n", this, index, offset, result);
return result;
}
void KPCIDevice::WriteBAR16(uintptr_t index, uintptr_t offset, uint16_t value) {
uint32_t baseAddress = baseAddresses[index];
KernelLog(LOG_VERBOSE, "PCI", "write BAR", "WriteBAR16 - %x, %x, %x, %x\n", this, index, offset, value);
if (baseAddress & 1) {
ProcessorOut16((baseAddress & ~3) + offset, value);
} else {
*(volatile uint16_t *) (baseAddressesVirtual[index] + offset) = value;
}
}
uint32_t KPCIDevice::ReadBAR32(uintptr_t index, uintptr_t offset) {
uint32_t baseAddress = baseAddresses[index];
uint32_t result;
if (baseAddress & 1) {
result = ProcessorIn32((baseAddress & ~3) + offset);
} else {
result = *(volatile uint32_t *) (baseAddressesVirtual[index] + offset);
}
KernelLog(LOG_VERBOSE, "PCI", "read BAR", "ReadBAR32 - %x, %x, %x, %x\n", this, index, offset, result);
return result;
}
void KPCIDevice::WriteBAR32(uintptr_t index, uintptr_t offset, uint32_t value) {
uint32_t baseAddress = baseAddresses[index];
KernelLog(LOG_VERBOSE, "PCI", "write BAR", "WriteBAR32 - %x, %x, %x, %x\n", this, index, offset, value);
if (baseAddress & 1) {
ProcessorOut32((baseAddress & ~3) + offset, value);
} else {
*(volatile uint32_t *) (baseAddressesVirtual[index] + offset) = value;
}
}
uint64_t KPCIDevice::ReadBAR64(uintptr_t index, uintptr_t offset) {
uint32_t baseAddress = baseAddresses[index];
uint64_t result;
if (baseAddress & 1) {
result = (uint64_t) ReadBAR32(index, offset) | ((uint64_t) ReadBAR32(index, offset + 4) << 32);
} else {
result = *(volatile uint64_t *) (baseAddressesVirtual[index] + offset);
}
KernelLog(LOG_VERBOSE, "PCI", "read BAR", "ReadBAR64 - %x, %x, %x, %x\n", this, index, offset, result);
return result;
}
void KPCIDevice::WriteBAR64(uintptr_t index, uintptr_t offset, uint64_t value) {
uint32_t baseAddress = baseAddresses[index];
KernelLog(LOG_VERBOSE, "PCI", "write BAR", "WriteBAR64 - %x, %x, %x, %x\n", this, index, offset, value);
if (baseAddress & 1) {
WriteBAR32(index, offset, value & 0xFFFFFFFF);
WriteBAR32(index, offset + 4, (value >> 32) & 0xFFFFFFFF);
} else {
*(volatile uint64_t *) (baseAddressesVirtual[index] + offset) = value;
}
}
// Spinlock since some drivers need to access it in IRQs (e.g. ACPICA).
// Also can't be part of PCIController since PCI is initialised after ACPICA.
static KSpinlock configSpaceSpinlock;
static PCIController *pci;
uint32_t KPCIReadConfig(uint8_t bus, uint8_t device, uint8_t function, uint8_t offset, int size) {
return pci->ReadConfig(bus, device, function, offset, size);
}
void KPCIWriteConfig(uint8_t bus, uint8_t device, uint8_t function, uint8_t offset, uint32_t value, int size) {
pci->WriteConfig(bus, device, function, offset, value, size);
}
uint32_t PCIController::ReadConfig(uint8_t bus, uint8_t device, uint8_t function, uint8_t offset, int size) {
KSpinlockAcquire(&configSpaceSpinlock);
EsDefer(KSpinlockRelease(&configSpaceSpinlock));
if (offset & 3) KernelPanic("PCIController::ReadConfig - offset is not 4-byte aligned.");
ProcessorOut32(PCI_CONFIG, (uint32_t) (0x80000000 | (bus << 16) | (device << 11) | (function << 8) | offset));
if (size == 8) return ProcessorIn8(PCI_DATA);
if (size == 16) return ProcessorIn16(PCI_DATA);
if (size == 32) return ProcessorIn32(PCI_DATA);
KernelPanic("PCIController::ReadConfig - Invalid size %d.\n", size);
return 0;
}
void PCIController::WriteConfig(uint8_t bus, uint8_t device, uint8_t function, uint8_t offset, uint32_t value, int size) {
KSpinlockAcquire(&configSpaceSpinlock);
EsDefer(KSpinlockRelease(&configSpaceSpinlock));
if (offset & 3) KernelPanic("PCIController::WriteConfig - offset is not 4-byte aligned.");
ProcessorOut32(PCI_CONFIG, (uint32_t) (0x80000000 | (bus << 16) | (device << 11) | (function << 8) | offset));
if (size == 8) ProcessorOut8(PCI_DATA, value);
else if (size == 16) ProcessorOut16(PCI_DATA, value);
else if (size == 32) ProcessorOut32(PCI_DATA, value);
else KernelPanic("PCIController::WriteConfig - Invalid size %d.\n", size);
}
void KPCIDevice::WriteConfig8(uintptr_t offset, uint8_t value) {
pci->WriteConfig(bus, slot, function, offset, value, 8);
}
uint8_t KPCIDevice::ReadConfig8(uintptr_t offset) {
return pci->ReadConfig(bus, slot, function, offset, 8);
}
void KPCIDevice::WriteConfig16(uintptr_t offset, uint16_t value) {
pci->WriteConfig(bus, slot, function, offset, value, 16);
}
uint16_t KPCIDevice::ReadConfig16(uintptr_t offset) {
return pci->ReadConfig(bus, slot, function, offset, 16);
}
void KPCIDevice::WriteConfig32(uintptr_t offset, uint32_t value) {
pci->WriteConfig(bus, slot, function, offset, value, 32);
}
uint32_t KPCIDevice::ReadConfig32(uintptr_t offset) {
return pci->ReadConfig(bus, slot, function, offset, 32);
}
bool KPCIDevice::EnableSingleInterrupt(KIRQHandler irqHandler, void *context, const char *cOwnerName) {
if (EnableMSI(irqHandler, context, cOwnerName)) {
return true;
}
EnableFeatures(K_PCI_FEATURE_INTERRUPTS);
if (KRegisterIRQ(interruptLine, irqHandler, context, cOwnerName)) {
return true;
}
return false;
}
bool KPCIDevice::EnableMSI(KIRQHandler irqHandler, void *context, const char *cOwnerName) {
// Find the MSI capability.
uint16_t status = ReadConfig32(0x04) >> 16;
if (~status & (1 << 4)) {
KernelLog(LOG_ERROR, "PCI", "no MSI support", "Device does not support MSI.\n");
return false;
}
uint8_t pointer = ReadConfig8(0x34);
uintptr_t index = 0;
while (pointer && index++ < 0xFF) {
uint32_t dw = ReadConfig32(pointer);
uint8_t nextPointer = (dw >> 8) & 0xFF;
uint8_t id = dw & 0xFF;
if (id != 5) {
pointer = nextPointer;
continue;
}
KMSIInformation msi = KRegisterMSI(irqHandler, context, cOwnerName);
if (!msi.address) {
KernelLog(LOG_ERROR, "PCI", "register MSI failure", "Could not register MSI.\n");
return false;
}
uint16_t control = (dw >> 16) & 0xFFFF;
if (msi.data & ~0xFFFF) {
KUnregisterMSI(msi.tag);
KernelLog(LOG_ERROR, "PCI", "unsupported MSI data", "PCI only supports 16 bits of MSI data. Requested: %x.\n", msi.data);
return false;
}
if (msi.address & 3) {
KUnregisterMSI(msi.tag);
KernelLog(LOG_ERROR, "PCI", "unsupported MSI address", "PCI requires DWORD alignment of MSI address. Requested: %x.\n", msi.address);
return false;
}
#ifdef ARCH_64
if ((msi.address & 0xFFFFFFFF00000000) && (~control & (1 << 7))) {
KUnregisterMSI(msi.tag);
KernelLog(LOG_ERROR, "PCI", "unsupported MSI address", "MSI does not support 64-bit addresses. Requested: %x.\n", msi.address);
return false;
}
#endif
control = (control & ~(7 << 4) /* don't allow modifying data */)
| (1 << 0 /* enable MSI */);
dw = (dw & 0x0000FFFF) | (control << 16);
WriteConfig32(pointer + 0, dw);
WriteConfig32(pointer + 4, msi.address & 0xFFFFFFFF);
if (control & (1 << 7)) {
WriteConfig32(pointer + 8, msi.address >> 32);
WriteConfig16(pointer + 12, (ReadConfig16(pointer + 12) & 0x3800) | msi.data);
if (control & (1 << 8)) WriteConfig32(pointer + 16, 0);
} else {
WriteConfig16(pointer + 8, msi.data);
if (control & (1 << 8)) WriteConfig32(pointer + 12, 0);
}
return true;
}
KernelLog(LOG_ERROR, "PCI", "no MSI support", "Device does not support MSI (2).\n");
return false;
}
bool KPCIDevice::EnableFeatures(uint64_t features) {
uint32_t config = ReadConfig32(4);
if (features & K_PCI_FEATURE_INTERRUPTS) config &= ~(1 << 10);
if (features & K_PCI_FEATURE_BUSMASTERING_DMA) config |= 1 << 2;
if (features & K_PCI_FEATURE_MEMORY_SPACE_ACCESS) config |= 1 << 1;
if (features & K_PCI_FEATURE_IO_PORT_ACCESS) config |= 1 << 0;
WriteConfig32(4, config);
if (ReadConfig32(4) != config) {
KernelLog(LOG_ERROR, "PCI", "configuration update", "Could not update the configuration for device %x.\n", this);
return false;
}
for (int i = 0; i < 6; i++) {
if (~features & (1 << i)) {
continue;
}
if (baseAddresses[i] & 1) {
continue; // The BAR is an IO port.
}
bool size64 = baseAddresses[i] & 4;
if (!(baseAddresses[i] & 8)) {
// TODO Not prefetchable.
}
uint64_t address, size;
if (size64) {
WriteConfig32(0x10 + 4 * i, 0xFFFFFFFF);
WriteConfig32(0x10 + 4 * (i + 1), 0xFFFFFFFF);
size = ReadConfig32(0x10 + 4 * i);
size |= (uint64_t) ReadConfig32(0x10 + 4 * (i + 1)) << 32;
WriteConfig32(0x10 + 4 * i, baseAddresses[i]);
WriteConfig32(0x10 + 4 * (i + 1), baseAddresses[i + 1]);
address = baseAddresses[i];
address |= (uint64_t) baseAddresses[i + 1] << 32;
} else {
WriteConfig32(0x10 + 4 * i, 0xFFFFFFFF);
size = ReadConfig32(0x10 + 4 * i);
size |= (uint64_t) 0xFFFFFFFF << 32;
WriteConfig32(0x10 + 4 * i, baseAddresses[i]);
address = baseAddresses[i];
}
if (!size || !address) {
KernelLog(LOG_ERROR, "PCI", "enable device BAR", "Could not enable BAR %d for device %x.\n", i, this);
return false;
}
size &= ~15;
size = ~size + 1;
address &= ~15;
// TODO Do we sometimes have to allocate the physical address ourselves..?
// If the driver wants to allow WC caching later, it can, with MMAllowWriteCombiningCaching.
baseAddressesVirtual[i] = (uint8_t *) MMMapPhysical(MMGetKernelSpace(), address, size, MM_REGION_NOT_CACHEABLE);
baseAddressesPhysical[i] = address;
baseAddressesSizes[i] = size;
MMCheckUnusable(address, size);
KernelLog(LOG_INFO, "PCI", "enable device BAR", "BAR %d has address %x and size %x, mapped to %x.\n",
i, address, size, baseAddressesVirtual[i]);
}
return true;
}
bool EnumeratePCIDrivers(KInstalledDriver *driver, KDevice *_device) {
KPCIDevice *device = (KPCIDevice *) _device;
int classCode = -1, subclassCode = -1, progIF = -1;
bool foundAnyDeviceIDs = false, foundMatchingDeviceID = false;
EsINIState s = {};
s.buffer = driver->config, s.bytes = driver->configBytes;
while (EsINIParse(&s)) {
if (0 == EsStringCompareRaw(s.key, s.keyBytes, EsLiteral("classCode"))) classCode = EsIntegerParse(s.value, s.valueBytes);
if (0 == EsStringCompareRaw(s.key, s.keyBytes, EsLiteral("subclassCode"))) subclassCode = EsIntegerParse(s.value, s.valueBytes);
if (0 == EsStringCompareRaw(s.key, s.keyBytes, EsLiteral("progIF"))) progIF = EsIntegerParse(s.value, s.valueBytes);
if (0 == EsStringCompareRaw(s.key, s.keyBytes, EsLiteral("deviceID"))) {
foundAnyDeviceIDs = true;
if (device->deviceID == EsIntegerParse(s.value, s.valueBytes)) {
foundMatchingDeviceID = true;
}
}
}
if (classCode != -1 && device->classCode != classCode) return false;
if (subclassCode != -1 && device->subclassCode != subclassCode) return false;
if (progIF != -1 && device->progIF != progIF) return false;
return !foundAnyDeviceIDs || foundMatchingDeviceID;
}
void PCIController::EnumerateFunction(int bus, int device, int function, int *busesToScan) {
uint32_t deviceID = ReadConfig(bus, device, function, 0x00);
if ((deviceID & 0xFFFF) == 0xFFFF) return;
uint32_t deviceClass = ReadConfig(bus, device, function, 0x08);
uint32_t interruptInformation = ReadConfig(bus, device, function, 0x3C);
KPCIDevice *pciDevice = (KPCIDevice *) KDeviceCreate("PCI function", this, sizeof(KPCIDevice));
if (!pciDevice) return;
pciDevice->classCode = (deviceClass >> 24) & 0xFF;
pciDevice->subclassCode = (deviceClass >> 16) & 0xFF;
pciDevice->progIF = (deviceClass >> 8) & 0xFF;
pciDevice->bus = bus;
pciDevice->slot = device;
pciDevice->function = function;
pciDevice->interruptPin = (interruptInformation >> 8) & 0xFF;
pciDevice->interruptLine = (interruptInformation >> 0) & 0xFF;
pciDevice->deviceID = ReadConfig(bus, device, function, 0);
pciDevice->subsystemID = ReadConfig(bus, device, function, 0x2C);
for (int i = 0; i < 6; i++) {
pciDevice->baseAddresses[i] = pciDevice->ReadConfig32(0x10 + 4 * i);
}
const char *classCodeString = pciDevice->classCode < sizeof(classCodeStrings) / sizeof(classCodeStrings[0])
? classCodeStrings[pciDevice->classCode] : "Unknown";
const char *subclassCodeString =
pciDevice->classCode == 1 && pciDevice->subclassCode < sizeof(subclassCodeStrings1) / sizeof(const char *)
? subclassCodeStrings1 [pciDevice->subclassCode]
: pciDevice->classCode == 12 && pciDevice->subclassCode < sizeof(subclassCodeStrings12) / sizeof(const char *)
? subclassCodeStrings12[pciDevice->subclassCode] : "";
const char *progIFString =
pciDevice->classCode == 12 && pciDevice->subclassCode == 3 && pciDevice->progIF / 0x10 < sizeof(progIFStrings12_3) / sizeof(const char *)
? progIFStrings12_3[pciDevice->progIF / 0x10] : "";
KernelLog(LOG_INFO, "PCI", "enumerate device",
"Found PCI device at %d/%d/%d with ID %x. Class code: %X '%z'. Subclass code: %X '%z'. Prog IF: %X '%z'. Interrupt pin: %d. Interrupt line: %d.\n",
bus, device, function, deviceID,
pciDevice->classCode, classCodeString, pciDevice->subclassCode, subclassCodeString, pciDevice->progIF, progIFString,
pciDevice->interruptPin, pciDevice->interruptLine);
if (pciDevice->classCode == 0x06 && pciDevice->subclassCode == 0x04 /* PCI bridge */) {
uint8_t secondaryBus = (ReadConfig(bus, device, function, 0x18) >> 8) & 0xFF;
if (busScanStates[secondaryBus] == PCI_BUS_DO_NOT_SCAN) {
KernelLog(LOG_INFO, "PCI", "PCI bridge", "PCI bridge to bus %d.\n", secondaryBus);
*busesToScan = *busesToScan + 1;
busScanStates[secondaryBus] = PCI_BUS_SCAN_NEXT;
}
}
bool foundDriver = KDeviceAttach(pciDevice, "PCI", EnumeratePCIDrivers);
if (foundDriver && pciDevice->classCode == 12 && pciDevice->subclassCode == 3) {
// The USB controller is responsible for disabling PS/2 emulation, and calling KPS2SafeToInitialise.
KernelLog(LOG_INFO, "PCI", "found USB", "Found USB controller.\n");
foundUSBController = true;
}
}
void PCIController::Enumerate() {
uint32_t baseHeaderType = ReadConfig(0, 0, 0, 0x0C);
int baseBuses = (baseHeaderType & 0x80) ? 8 : 1;
int busesToScan = 0;
for (int baseBus = 0; baseBus < baseBuses; baseBus++) {
uint32_t deviceID = ReadConfig(0, 0, baseBus, 0x00);
if ((deviceID & 0xFFFF) == 0xFFFF) continue;
busScanStates[baseBus] = PCI_BUS_SCAN_NEXT;
busesToScan++;
}
if (!busesToScan) {
KernelPanic("PCIController::Enumerate - No buses found\n");
}
while (busesToScan) {
for (int bus = 0; bus < 256; bus++) {
if (busScanStates[bus] != PCI_BUS_SCAN_NEXT) continue;
KernelLog(LOG_INFO, "PCI", "scan bus", "Scanning bus %d...\n", bus);
busScanStates[bus] = PCI_BUS_SCANNED;
busesToScan--;
for (int device = 0; device < 32; device++) {
uint32_t deviceID = ReadConfig(bus, device, 0, 0x00);
if ((deviceID & 0xFFFF) == 0xFFFF) continue;
uint32_t headerType = (ReadConfig(bus, device, 0, 0x0C) >> 16) & 0xFF;
int functions = (headerType & 0x80) ? 8 : 1;
for (int function = 0; function < functions; function++) {
EnumerateFunction(bus, device, function, &busesToScan);
}
}
}
}
}
static void DeviceAttach(KDevice *parent) {
if (pci) {
KernelLog(LOG_ERROR, "PCI", "multiple PCI controllers", "EntryPCI - Attempt to register multiple PCI controllers; ignored.\n");
return;
}
pci = (PCIController *) KDeviceCreate("PCI controller", parent, sizeof(PCIController));
if (pci) {
pci->Enumerate();
if (!pci->foundUSBController) {
KernelLog(LOG_INFO, "PCI", "no USB", "No USB controller found; initialising PS/2...\n");
KPS2SafeToInitialise();
}
}
}
KDriver driverPCI = {
.attach = DeviceAttach,
};

566
drivers/ps2.cpp Normal file
View File

@ -0,0 +1,566 @@
// TODO Scrolling.
#include <module.h>
struct PS2Update {
union {
struct {
volatile int xMovement, yMovement;
volatile unsigned buttons;
};
struct {
volatile unsigned scancode;
};
};
};
struct PS2 {
void Initialise(KDevice *parentDevice);
void EnableDevices(unsigned which);
void DisableDevices(unsigned which);
void FlushOutputBuffer();
void SendCommand(uint8_t command);
uint8_t ReadByte(KTimeout *timeout);
void WriteByte(KTimeout *timeout, uint8_t value);
bool SetupKeyboard(KTimeout *timeout);
bool SetupMouse(KTimeout *timeout);
bool PollRead(uint8_t *value, bool forMouse);
void WaitInputBuffer();
uint8_t mouseType, scancodeSet;
size_t channels;
bool registeredIRQs;
bool initialised;
volatile uintptr_t lastUpdatesIndex;
PS2Update lastUpdates[16];
KSpinlock lastUpdatesLock;
KMutex mutex;
};
PS2 ps2;
uint16_t scancodeConversionTable1[] = {
0, ES_SCANCODE_ESCAPE, ES_SCANCODE_1, ES_SCANCODE_2, ES_SCANCODE_3, ES_SCANCODE_4, ES_SCANCODE_5, ES_SCANCODE_6,
ES_SCANCODE_7, ES_SCANCODE_8, ES_SCANCODE_9, ES_SCANCODE_0, ES_SCANCODE_HYPHEN, ES_SCANCODE_EQUALS, ES_SCANCODE_BACKSPACE, ES_SCANCODE_TAB,
ES_SCANCODE_Q, ES_SCANCODE_W, ES_SCANCODE_E, ES_SCANCODE_R, ES_SCANCODE_T, ES_SCANCODE_Y, ES_SCANCODE_U, ES_SCANCODE_I,
ES_SCANCODE_O, ES_SCANCODE_P, ES_SCANCODE_LEFT_BRACE, ES_SCANCODE_RIGHT_BRACE, ES_SCANCODE_ENTER, ES_SCANCODE_LEFT_CTRL, ES_SCANCODE_A, ES_SCANCODE_S,
ES_SCANCODE_D, ES_SCANCODE_F, ES_SCANCODE_G, ES_SCANCODE_H, ES_SCANCODE_J, ES_SCANCODE_K, ES_SCANCODE_L, ES_SCANCODE_PUNCTUATION_3,
ES_SCANCODE_PUNCTUATION_4, ES_SCANCODE_PUNCTUATION_5, ES_SCANCODE_LEFT_SHIFT, ES_SCANCODE_PUNCTUATION_1, ES_SCANCODE_Z, ES_SCANCODE_X, ES_SCANCODE_C, ES_SCANCODE_V,
ES_SCANCODE_B, ES_SCANCODE_N, ES_SCANCODE_M, ES_SCANCODE_COMMA, ES_SCANCODE_PERIOD, ES_SCANCODE_SLASH, ES_SCANCODE_RIGHT_SHIFT, ES_SCANCODE_NUM_MULTIPLY,
ES_SCANCODE_LEFT_ALT, ES_SCANCODE_SPACE, ES_SCANCODE_CAPS_LOCK, ES_SCANCODE_F1, ES_SCANCODE_F2, ES_SCANCODE_F3, ES_SCANCODE_F4, ES_SCANCODE_F5,
ES_SCANCODE_F6, ES_SCANCODE_F7, ES_SCANCODE_F8, ES_SCANCODE_F9, ES_SCANCODE_F10, ES_SCANCODE_NUM_LOCK, ES_SCANCODE_SCROLL_LOCK, ES_SCANCODE_NUM_7,
ES_SCANCODE_NUM_8, ES_SCANCODE_NUM_9, ES_SCANCODE_NUM_SUBTRACT, ES_SCANCODE_NUM_4, ES_SCANCODE_NUM_5, ES_SCANCODE_NUM_6, ES_SCANCODE_NUM_ADD, ES_SCANCODE_NUM_1,
ES_SCANCODE_NUM_2, ES_SCANCODE_NUM_3, ES_SCANCODE_NUM_0, ES_SCANCODE_NUM_POINT, 0, 0, 0, ES_SCANCODE_F11,
ES_SCANCODE_F12, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
ES_SCANCODE_MM_PREVIOUS, 0, 0, 0, 0, 0, 0, 0,
0, ES_SCANCODE_MM_NEXT, 0, 0, ES_SCANCODE_NUM_ENTER, ES_SCANCODE_RIGHT_CTRL, 0, 0,
ES_SCANCODE_MM_MUTE, ES_SCANCODE_MM_CALC, ES_SCANCODE_MM_PAUSE, 0, ES_SCANCODE_MM_STOP, 0, 0, 0,
0, 0, ES_SCANCODE_MM_QUIETER, 0, 0, 0, 0, 0,
ES_SCANCODE_MM_LOUDER, 0, ES_SCANCODE_WWW_HOME, 0, 0, ES_SCANCODE_NUM_DIVIDE, 0, 0,
ES_SCANCODE_RIGHT_ALT, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, ES_SCANCODE_HOME,
ES_SCANCODE_UP_ARROW, ES_SCANCODE_PAGE_UP, 0, ES_SCANCODE_LEFT_ARROW, 0, ES_SCANCODE_RIGHT_ARROW, 0, ES_SCANCODE_END,
ES_SCANCODE_DOWN_ARROW, ES_SCANCODE_PAGE_DOWN, ES_SCANCODE_INSERT, ES_SCANCODE_DELETE, 0, 0, 0, 0,
0, 0, 0, ES_SCANCODE_LEFT_FLAG, ES_SCANCODE_RIGHT_FLAG, ES_SCANCODE_CONTEXT_MENU, ES_SCANCODE_ACPI_POWER, ES_SCANCODE_ACPI_SLEEP,
0, 0, 0, ES_SCANCODE_ACPI_WAKE, 0, ES_SCANCODE_WWW_SEARCH, ES_SCANCODE_WWW_STARRED, ES_SCANCODE_WWW_REFRESH,
ES_SCANCODE_WWW_STOP, ES_SCANCODE_WWW_FORWARD, ES_SCANCODE_WWW_BACK, ES_SCANCODE_MM_FILES, ES_SCANCODE_MM_EMAIL, ES_SCANCODE_MM_SELECT, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
};
uint16_t scancodeConversionTable2[] = {
0, ES_SCANCODE_F9, 0, ES_SCANCODE_F5, ES_SCANCODE_F3, ES_SCANCODE_F1, ES_SCANCODE_F2, ES_SCANCODE_F12,
0, ES_SCANCODE_F10, ES_SCANCODE_F8, ES_SCANCODE_F6, ES_SCANCODE_F4, ES_SCANCODE_TAB, ES_SCANCODE_PUNCTUATION_5, 0,
0, ES_SCANCODE_LEFT_ALT, ES_SCANCODE_LEFT_SHIFT, 0, ES_SCANCODE_LEFT_CTRL, ES_SCANCODE_Q, ES_SCANCODE_1, 0,
0, 0, ES_SCANCODE_Z, ES_SCANCODE_S, ES_SCANCODE_A, ES_SCANCODE_W, ES_SCANCODE_2, 0,
0, ES_SCANCODE_C, ES_SCANCODE_X, ES_SCANCODE_D, ES_SCANCODE_E, ES_SCANCODE_4, ES_SCANCODE_3, 0,
0, ES_SCANCODE_SPACE, ES_SCANCODE_V, ES_SCANCODE_F, ES_SCANCODE_T, ES_SCANCODE_R, ES_SCANCODE_5, 0,
0, ES_SCANCODE_N, ES_SCANCODE_B, ES_SCANCODE_H, ES_SCANCODE_G, ES_SCANCODE_Y, ES_SCANCODE_6, 0,
0, 0, ES_SCANCODE_M, ES_SCANCODE_J, ES_SCANCODE_U, ES_SCANCODE_7, ES_SCANCODE_8, 0,
0, ES_SCANCODE_COMMA, ES_SCANCODE_K, ES_SCANCODE_I, ES_SCANCODE_O, ES_SCANCODE_0, ES_SCANCODE_9, 0,
0, ES_SCANCODE_PERIOD, ES_SCANCODE_SLASH, ES_SCANCODE_L, ES_SCANCODE_PUNCTUATION_3, ES_SCANCODE_P, ES_SCANCODE_HYPHEN, 0,
0, 0, ES_SCANCODE_PUNCTUATION_4, 0, ES_SCANCODE_LEFT_BRACE, ES_SCANCODE_EQUALS, 0, 0,
ES_SCANCODE_CAPS_LOCK, ES_SCANCODE_RIGHT_SHIFT, ES_SCANCODE_ENTER, ES_SCANCODE_RIGHT_BRACE, 0, ES_SCANCODE_PUNCTUATION_1, 0, 0,
0, 0, 0, 0, 0, 0, ES_SCANCODE_BACKSPACE, 0,
0, ES_SCANCODE_NUM_1, 0, ES_SCANCODE_NUM_4, ES_SCANCODE_NUM_7, 0, 0, 0,
ES_SCANCODE_NUM_0, ES_SCANCODE_NUM_POINT, ES_SCANCODE_NUM_2, ES_SCANCODE_NUM_5, ES_SCANCODE_NUM_6, ES_SCANCODE_NUM_8, ES_SCANCODE_ESCAPE, ES_SCANCODE_NUM_LOCK,
ES_SCANCODE_F11, ES_SCANCODE_NUM_ADD, ES_SCANCODE_NUM_3, ES_SCANCODE_NUM_SUBTRACT, ES_SCANCODE_NUM_MULTIPLY, ES_SCANCODE_NUM_9, ES_SCANCODE_SCROLL_LOCK, 0,
0, 0, 0, ES_SCANCODE_F7, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, ES_SCANCODE_PAUSE, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
ES_SCANCODE_WWW_SEARCH, ES_SCANCODE_RIGHT_ALT, ES_SCANCODE_PRINT_SCREEN, 0, ES_SCANCODE_RIGHT_CTRL, ES_SCANCODE_MM_PREVIOUS, 0, 0,
ES_SCANCODE_WWW_STARRED, 0, 0, 0, 0, 0, 0, ES_SCANCODE_LEFT_FLAG,
ES_SCANCODE_WWW_REFRESH, ES_SCANCODE_MM_QUIETER, 0, ES_SCANCODE_MM_MUTE, 0, 0, 0, ES_SCANCODE_CONTEXT_MENU,
ES_SCANCODE_WWW_STOP, 0, 0, ES_SCANCODE_MM_CALC, 0, 0, 0, 0,
ES_SCANCODE_WWW_FORWARD, 0, ES_SCANCODE_MM_LOUDER, 0, ES_SCANCODE_MM_PAUSE, 0, 0, ES_SCANCODE_ACPI_POWER,
ES_SCANCODE_WWW_BACK, 0, ES_SCANCODE_WWW_HOME, ES_SCANCODE_MM_STOP, 0, 0, 0, ES_SCANCODE_ACPI_SLEEP,
ES_SCANCODE_MM_FILES, 0, 0, 0, 0, 0, 0, 0,
ES_SCANCODE_MM_EMAIL, 0, ES_SCANCODE_NUM_DIVIDE, 0, 0, ES_SCANCODE_MM_NEXT, 0, 0,
ES_SCANCODE_MM_SELECT, 0, 0, 0, 0, 0, 0, 0,
0, 0, ES_SCANCODE_NUM_ENTER, 0, 0, 0, ES_SCANCODE_ACPI_WAKE, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, ES_SCANCODE_END, 0, ES_SCANCODE_LEFT_ARROW, ES_SCANCODE_HOME, 0, 0, 0,
ES_SCANCODE_INSERT, ES_SCANCODE_DELETE, ES_SCANCODE_DOWN_ARROW, 0, ES_SCANCODE_RIGHT_ARROW, ES_SCANCODE_UP_ARROW, 0, 0,
0, 0, ES_SCANCODE_PAGE_DOWN, 0, 0, ES_SCANCODE_PAGE_UP, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
};
// Status register.
#define PS2_OUTPUT_FULL (1 << 0)
#define PS2_INPUT_FULL (1 << 1)
#define PS2_MOUSE_BYTE (1 << 5)
// Mouse types.
#define PS2_MOUSE_NORMAL (0)
#define PS2_MOUSE_SCROLL (3)
#define PS2_MOUSE_5_BUTTON (4)
// Controller commands.
#define PS2_DISABLE_FIRST (0xAD)
#define PS2_ENABLE_FIRST (0xAE)
#define PS2_TEST_FIRST (0xAB)
#define PS2_DISABLE_SECOND (0xA7)
#define PS2_ENABLE_SECOND (0xA8)
#define PS2_WRITE_SECOND (0xD4)
#define PS2_TEST_SECOND (0xA9)
#define PS2_TEST_CONTROLER (0xAA)
#define PS2_READ_CONFIG (0x20)
#define PS2_WRITE_CONFIG (0x60)
// Controller configuration.
#define PS2_FIRST_IRQ_MASK (1 << 0)
#define PS2_SECOND_IRQ_MASK (1 << 1)
#define PS2_SECOND_CLOCK (1 << 5)
#define PS2_TRANSLATION (1 << 6)
// IRQs.
#define PS2_FIRST_IRQ (1)
#define PS2_SECOND_IRQ (12)
// Ports.
#define PS2_PORT_DATA (0x60)
#define PS2_PORT_STATUS (0x64)
#define PS2_PORT_COMMAND (0x64)
// Keyboard commands.
#define PS2_KEYBOARD_RESET (0xFF)
#define PS2_KEYBOARD_ENABLE (0xF4)
#define PS2_KEYBOARD_DISABLE (0xF5)
#define PS2_KEYBOARD_REPEAT (0xF3)
#define PS2_KEYBOARD_SET_LEDS (0xED)
#define PS2_KEYBOARD_SCANCODE_SET (0xF0)
// Mouse commands.
#define PS2_MOUSE_RESET (0xFF)
#define PS2_MOUSE_ENABLE (0xF4)
#define PS2_MOUSE_DISABLE (0xF5)
#define PS2_MOUSE_SAMPLE_RATE (0xF3)
#define PS2_MOUSE_READ (0xEB)
#define PS2_MOUSE_RESOLUTION (0xE8)
void PS2MouseUpdated(EsGeneric _update) {
PS2Update *update = (PS2Update *) _update.p;
KCursorUpdate(update->xMovement, update->yMovement, update->buttons);
}
void PS2KeyboardUpdated(EsGeneric _update) {
PS2Update *update = (PS2Update *) _update.p;
KernelLog(LOG_VERBOSE, "PS/2", "keyboard update", "Received scancode %x.\n", update->scancode);
KKeyPress(update->scancode);
}
void PS2::WaitInputBuffer() {
while (ProcessorIn8(PS2_PORT_STATUS) & PS2_INPUT_FULL);
}
bool PS2::PollRead(uint8_t *value, bool forMouse) {
uint8_t status = ProcessorIn8(PS2_PORT_STATUS);
if (status & PS2_MOUSE_BYTE && !forMouse) return false;
if (!(status & PS2_MOUSE_BYTE) && forMouse) return false;
if (status & PS2_OUTPUT_FULL) {
*value = ProcessorIn8(PS2_PORT_DATA);
if (*value == 0xE1 && !forMouse) {
KDebugKeyPressed();
}
EsRandomAddEntropy(*value);
return true;
} else {
return false;
}
}
int PS2ReadKey() {
static uint8_t firstByte = 0, secondByte = 0, thirdByte = 0;
static size_t bytesFound = 0;
if (bytesFound == 0) {
if (!ps2.PollRead(&firstByte, false)) return 0;
bytesFound++;
if (firstByte != 0xE0 && firstByte != 0xF0) {
goto sequenceFinished;
} else return 0;
} else if (bytesFound == 1) {
if (!ps2.PollRead(&secondByte, false)) return 0;
bytesFound++;
if (secondByte != 0xF0) {
goto sequenceFinished;
} else return 0;
} else if (bytesFound == 2) {
if (!ps2.PollRead(&thirdByte, false)) return 0;
bytesFound++;
goto sequenceFinished;
}
sequenceFinished:
KernelLog(LOG_VERBOSE, "PS/2", "keyboard data", "Keyboard data: %X%X%X\n", firstByte, secondByte, thirdByte);
int scancode = 0;
if (ps2.scancodeSet == 1) {
if (firstByte == 0xE0) {
if (secondByte & 0x80) {
scancode = secondByte | K_SCANCODE_KEY_RELEASED;
} else {
scancode = secondByte | 0x80;
}
} else {
if (firstByte & 0x80) {
scancode = (firstByte & ~0x80) | K_SCANCODE_KEY_RELEASED;
} else {
scancode = firstByte;
}
}
} else if (ps2.scancodeSet == 2) {
if (firstByte == 0xE0) {
if (secondByte == 0xF0) {
scancode = K_SCANCODE_KEY_RELEASED | (1 << 8) | thirdByte;
} else {
scancode = K_SCANCODE_KEY_PRESSED | (1 << 8) | secondByte;
}
} else {
if (firstByte == 0xF0) {
scancode = K_SCANCODE_KEY_RELEASED | (0 << 8) | secondByte;
} else {
scancode = K_SCANCODE_KEY_PRESSED | (0 << 8) | firstByte;
}
}
}
uint16_t *table = ps2.scancodeSet == 2 ? scancodeConversionTable2 : scancodeConversionTable1;
firstByte = 0;
secondByte = 0;
thirdByte = 0;
bytesFound = 0;
return (scancode & (1 << 15)) | table[scancode & ~(1 << 15)];
}
int KWaitKey() {
if (!ps2.channels) ProcessorHalt();
int scancode;
while (!(scancode = PS2ReadKey()));
return scancode;
}
bool PS2IRQHandler(uintptr_t interruptIndex, void *) {
if (!ps2.channels) return false;
if (ps2.channels == 1 && interruptIndex == 12) return false;
if (interruptIndex == 12) {
static uint8_t firstByte = 0, secondByte = 0, thirdByte = 0;
static size_t bytesFound = 0;
if (bytesFound == 0) {
if (!ps2.PollRead(&firstByte, true)) return false;
if (!(firstByte & 8)) return false;
bytesFound++;
return true;
} else if (bytesFound == 1) {
if (!ps2.PollRead(&secondByte, true)) return false;
bytesFound++;
return true;
} else if (bytesFound == 2) {
if (!ps2.PollRead(&thirdByte, true)) return false;
bytesFound++;
}
KernelLog(LOG_VERBOSE, "PS/2", "mouse data", "Mouse data: %X%X%X\n", firstByte, secondByte, thirdByte);
KSpinlockAcquire(&ps2.lastUpdatesLock);
PS2Update *update = ps2.lastUpdates + ps2.lastUpdatesIndex;
ps2.lastUpdatesIndex = (ps2.lastUpdatesIndex + 1) % 16;
KSpinlockRelease(&ps2.lastUpdatesLock);
update->xMovement = secondByte - ((firstByte << 4) & 0x100);
update->yMovement = -(thirdByte - ((firstByte << 3) & 0x100));
update->buttons = ((firstByte & (1 << 0)) ? K_LEFT_BUTTON : 0)
| ((firstByte & (1 << 1)) ? K_RIGHT_BUTTON : 0)
| ((firstByte & (1 << 2)) ? K_MIDDLE_BUTTON : 0);
KRegisterAsyncTask(PS2MouseUpdated, update, false);
firstByte = 0;
secondByte = 0;
thirdByte = 0;
bytesFound = 0;
} else if (interruptIndex == 1) {
KernelLog(LOG_VERBOSE, "PS/2", "keyboard IRQ", "Received keyboard IRQ.\n");
int scancode = PS2ReadKey();
if (scancode) {
KSpinlockAcquire(&ps2.lastUpdatesLock);
PS2Update *update = ps2.lastUpdates + ps2.lastUpdatesIndex;
ps2.lastUpdatesIndex = (ps2.lastUpdatesIndex + 1) % 16;
KSpinlockRelease(&ps2.lastUpdatesLock);
update->scancode = scancode;
KRegisterAsyncTask(PS2KeyboardUpdated, update, false);
}
} else {
KernelPanic("PS2IRQHandler - Incorrect interrupt index.\n", interruptIndex);
}
return true;
}
void PS2::DisableDevices(unsigned which) {
WaitInputBuffer();
// EsPrint("ps2 first write...\n");
if (which & 1) ProcessorOut8(PS2_PORT_COMMAND, PS2_DISABLE_FIRST);
// EsPrint("ps2 first write end\n");
WaitInputBuffer();
if (which & 2) ProcessorOut8(PS2_PORT_COMMAND, PS2_DISABLE_SECOND);
}
void PS2::EnableDevices(unsigned which) {
WaitInputBuffer();
if (which & 1) ProcessorOut8(PS2_PORT_COMMAND, PS2_ENABLE_FIRST);
WaitInputBuffer();
if (which & 2) ProcessorOut8(PS2_PORT_COMMAND, PS2_ENABLE_SECOND);
}
void PS2::FlushOutputBuffer() {
while (ProcessorIn8(PS2_PORT_STATUS) & PS2_OUTPUT_FULL) {
ProcessorIn8(PS2_PORT_DATA);
}
}
void PS2::SendCommand(uint8_t command) {
WaitInputBuffer();
ProcessorOut8(PS2_PORT_COMMAND, command);
}
uint8_t PS2::ReadByte(KTimeout *timeout) {
while (!(ProcessorIn8(PS2_PORT_STATUS) & PS2_OUTPUT_FULL) && !timeout->Hit());
return ProcessorIn8(PS2_PORT_DATA);
}
void PS2::WriteByte(KTimeout *timeout, uint8_t value) {
while ((ProcessorIn8(PS2_PORT_STATUS) & PS2_INPUT_FULL) && !timeout->Hit());
if (timeout->Hit()) return;
ProcessorOut8(PS2_PORT_DATA, value);
}
bool PS2::SetupKeyboard(KTimeout *timeout) {
WaitInputBuffer();
ProcessorOut8(PS2_PORT_DATA, PS2_KEYBOARD_ENABLE);
if (ReadByte(timeout) != 0xFA) return false;
WaitInputBuffer();
ProcessorOut8(PS2_PORT_DATA, PS2_KEYBOARD_SCANCODE_SET);
if (ReadByte(timeout) != 0xFA) return false;
WaitInputBuffer();
ProcessorOut8(PS2_PORT_DATA, 0);
if (ReadByte(timeout) != 0xFA) return false;
scancodeSet = ReadByte(timeout) & 3;
KernelLog(LOG_INFO, "PS/2", "scancode set", "Keyboard reports it is using scancode set %d.\n", scancodeSet);
WaitInputBuffer();
ProcessorOut8(PS2_PORT_DATA, PS2_KEYBOARD_REPEAT);
if (ReadByte(timeout) != 0xFA) return false;
WaitInputBuffer();
ProcessorOut8(PS2_PORT_DATA, 0);
if (ReadByte(timeout) != 0xFA) return false;
return true;
}
bool PS2::SetupMouse(KTimeout *timeout) {
// TODO Mouse with scroll wheel detection.
WaitInputBuffer();
ProcessorOut8(PS2_PORT_COMMAND, PS2_WRITE_SECOND);
WaitInputBuffer();
ProcessorOut8(PS2_PORT_DATA, PS2_MOUSE_RESET);
if (ReadByte(timeout) != 0xFA) return false;
if (ReadByte(timeout) != 0xAA) return false;
if (ReadByte(timeout) != 0x00) return false;
WaitInputBuffer();
ProcessorOut8(PS2_PORT_COMMAND, PS2_WRITE_SECOND);
WaitInputBuffer();
ProcessorOut8(PS2_PORT_DATA, PS2_MOUSE_SAMPLE_RATE);
if (ReadByte(timeout) != 0xFA) return false;
WaitInputBuffer();
ProcessorOut8(PS2_PORT_COMMAND, PS2_WRITE_SECOND);
WaitInputBuffer();
ProcessorOut8(PS2_PORT_DATA, 100);
if (ReadByte(timeout) != 0xFA) return false;
WaitInputBuffer();
ProcessorOut8(PS2_PORT_COMMAND, PS2_WRITE_SECOND);
WaitInputBuffer();
ProcessorOut8(PS2_PORT_DATA, PS2_MOUSE_RESOLUTION);
if (ReadByte(timeout) != 0xFA) return false;
WaitInputBuffer();
ProcessorOut8(PS2_PORT_COMMAND, PS2_WRITE_SECOND);
WaitInputBuffer();
ProcessorOut8(PS2_PORT_DATA, 3);
if (ReadByte(timeout) != 0xFA) return false;
WaitInputBuffer();
ProcessorOut8(PS2_PORT_COMMAND, PS2_WRITE_SECOND);
WaitInputBuffer();
ProcessorOut8(PS2_PORT_DATA, PS2_MOUSE_ENABLE);
if (ReadByte(timeout) != 0xFA) return false;
return true;
}
void PS2::Initialise(KDevice *parentDevice) {
KMutexAcquire(&mutex);
EsDefer(KMutexRelease(&mutex));
if (initialised) {
return;
}
initialised = true;
channels = 0;
KTimeout timeout(10000);
FlushOutputBuffer();
// TODO PS/2 detection with ACPI.
DisableDevices(1 | 2);
FlushOutputBuffer();
WaitInputBuffer();
ProcessorOut8(PS2_PORT_COMMAND, PS2_READ_CONFIG);
uint8_t configurationByte = ReadByte(&timeout);
WaitInputBuffer();
ProcessorOut8(PS2_PORT_COMMAND, PS2_WRITE_CONFIG);
WriteByte(&timeout, configurationByte & ~(PS2_FIRST_IRQ_MASK | PS2_SECOND_IRQ_MASK | PS2_TRANSLATION));
if (timeout.Hit()) return;
SendCommand(PS2_TEST_CONTROLER);
if (ReadByte(&timeout) != 0x55) return;
bool hasMouse = false;
if (configurationByte & PS2_SECOND_CLOCK) {
EnableDevices(2);
WaitInputBuffer();
ProcessorOut8(PS2_PORT_COMMAND, PS2_READ_CONFIG);
configurationByte = ReadByte(&timeout);
if (!(configurationByte & PS2_SECOND_CLOCK)) {
hasMouse = true;
DisableDevices(2);
}
}
{
WaitInputBuffer();
ProcessorOut8(PS2_PORT_COMMAND, PS2_TEST_FIRST);
uint8_t b = ReadByte(&timeout);
if (b) return;
if (timeout.Hit()) return;
channels = 1;
}
if (hasMouse) {
WaitInputBuffer();
ProcessorOut8(PS2_PORT_COMMAND, PS2_TEST_SECOND);
if (!ReadByte(&timeout) && !timeout.Hit()) channels = 2;
}
EnableDevices(1 | 2);
if (!SetupKeyboard(&timeout) || timeout.Hit()) {
channels = 0;
return;
}
if (!SetupMouse(&timeout) || timeout.Hit()) {
channels = 1;
}
{
WaitInputBuffer();
ProcessorOut8(PS2_PORT_COMMAND, PS2_READ_CONFIG);
uint8_t configurationByte = ReadByte(&timeout);
WaitInputBuffer();
ProcessorOut8(PS2_PORT_COMMAND, PS2_WRITE_CONFIG);
WriteByte(&timeout, configurationByte | PS2_FIRST_IRQ_MASK | PS2_SECOND_IRQ_MASK);
}
if (!registeredIRQs) {
KRegisterIRQ(PS2_FIRST_IRQ, PS2IRQHandler, nullptr, "PS2");
KRegisterIRQ(PS2_SECOND_IRQ, PS2IRQHandler, nullptr, "PS2");
registeredIRQs = true;
}
KDevice *controller = KDeviceCreate("PS/2 controller", parentDevice, sizeof(KDevice));
KDeviceCreate("PS/2 keyboard", controller, sizeof(KDevice));
if (channels == 2) KDeviceCreate("PS/2 mouse", controller, sizeof(KDevice));
KernelLog(LOG_INFO, "PS/2", "controller initialised", "Setup PS/2 controller%z.\n", channels == 2 ? ", with a mouse" : "");
}
static void DeviceAttach(KDevice *parentDevice) {
ps2.Initialise(parentDevice);
}
KDriver driverPS2 = {
.attach = DeviceAttach,
};

285
drivers/svga.cpp Normal file
View File

@ -0,0 +1,285 @@
#include <module.h>
#include <kernel/x86_64.h>
struct VideoModeInformation {
uint8_t valid : 1, edidValid : 1;
uint8_t bitsPerPixel;
uint16_t widthPixels, heightPixels;
uint16_t bytesPerScanlineLinear;
uint64_t bufferPhysical;
uint8_t edid[128];
};
VideoModeInformation *vbeMode;
uint32_t screenWidth, screenHeight, strideX, strideY;
volatile uint8_t *linearBuffer;
#if 0
float colorBlindnessMatrix[3][9] = {
{
// Protanopia.
0.171, 0.829, 0,
0.171, 0.829, 0,
-0.005, 0.005, 1,
},
{
// Deuteranopia.
0.330, 0.670, 0,
0.330, 0.670, 0,
-0.028, 0.028, 1,
},
{
// Tritanopia.
1, 0.127, -0.127,
0, 0.874, 0.126,
0, 0.874, 0.126,
},
};
// #define SIMULATE_COLOR_BLINDNESS (1)
#endif
void UpdateScreen_32_XRGB(K_USER_BUFFER const uint8_t *source, uint32_t sourceWidth, uint32_t sourceHeight, uint32_t sourceStride,
uint32_t destinationX, uint32_t destinationY) {
GraphicsUpdateScreen32(source, sourceWidth, sourceHeight, sourceStride,
destinationX, destinationY, screenWidth, screenHeight, strideY, linearBuffer);
}
void DebugPutBlock_32_XRGB(uintptr_t x, uintptr_t y, bool toggle) {
GraphicsDebugPutBlock32(x, y, toggle, screenWidth, screenHeight, strideY, linearBuffer);
}
void DebugClearScreen_32_XRGB() {
GraphicsDebugClearScreen32(screenWidth, screenHeight, strideY, linearBuffer);
}
void UpdateScreen_24_RGB(K_USER_BUFFER const uint8_t *source, uint32_t sourceWidth, uint32_t sourceHeight, uint32_t sourceStride,
uint32_t destinationX, uint32_t destinationY) {
GraphicsUpdateScreen24(source, sourceWidth, sourceHeight, sourceStride,
destinationX, destinationY, screenWidth, screenHeight, strideY, linearBuffer);
}
void DebugPutBlock_24_RGB(uintptr_t x, uintptr_t y, bool toggle) {
if (toggle) {
linearBuffer[y * strideY + x * 3 + 0] += 0x4C;
linearBuffer[y * strideY + x * 3 + 1] += 0x4C;
linearBuffer[y * strideY + x * 3 + 2] += 0x4C;
} else {
linearBuffer[y * strideY + x * 3 + 0] = 0xFF;
linearBuffer[y * strideY + x * 3 + 1] = 0xFF;
linearBuffer[y * strideY + x * 3 + 2] = 0xFF;
}
linearBuffer[(y + 1) * strideY + (x + 1) * 3 + 0] = 0;
linearBuffer[(y + 1) * strideY + (x + 1) * 3 + 1] = 0;
linearBuffer[(y + 1) * strideY + (x + 1) * 3 + 2] = 0;
}
void DebugClearScreen_24_RGB() {
for (uintptr_t i = 0; i < screenWidth * screenHeight * 3; i += 3) {
linearBuffer[i + 2] = 0x18;
linearBuffer[i + 1] = 0x7E;
linearBuffer[i + 0] = 0xCF;
}
}
void InitialiseVBE(KDevice *parent) {
if (KGraphicsIsTargetRegistered()) {
return;
}
vbeMode = (VideoModeInformation *) MMMapPhysical(MMGetKernelSpace(), 0x7000 + GetBootloaderInformationOffset(),
sizeof(VideoModeInformation), ES_FLAGS_DEFAULT);
if (!vbeMode->valid) {
return;
}
if (vbeMode->edidValid) {
for (uintptr_t i = 0; i < 128; i++) {
EsPrint("EDID byte %d: %X.\n", i, vbeMode->edid[i]);
}
}
KGraphicsTarget *target = (KGraphicsTarget *) KDeviceCreate("VBE", parent, sizeof(KGraphicsTarget));
linearBuffer = (uint8_t *) MMMapPhysical(MMGetKernelSpace(), vbeMode->bufferPhysical,
vbeMode->bytesPerScanlineLinear * vbeMode->heightPixels, MM_REGION_WRITE_COMBINING);
screenWidth = target->screenWidth = vbeMode->widthPixels;
screenHeight = target->screenHeight = vbeMode->heightPixels;
strideX = vbeMode->bitsPerPixel >> 3;
strideY = vbeMode->bytesPerScanlineLinear;
if (vbeMode->bitsPerPixel == 32) {
target->updateScreen = UpdateScreen_32_XRGB;
target->debugPutBlock = DebugPutBlock_32_XRGB;
target->debugClearScreen = DebugClearScreen_32_XRGB;
} else {
target->updateScreen = UpdateScreen_24_RGB;
target->debugPutBlock = DebugPutBlock_24_RGB;
target->debugClearScreen = DebugClearScreen_24_RGB;
}
// TODO Other color modes.
KRegisterGraphicsTarget(target);
}
#if 0
#define VGA_AC_INDEX 0x3C0
#define VGA_AC_WRITE 0x3C0
#define VGA_AC_READ 0x3C1
#define VGA_MISC_WRITE 0x3C2
#define VGA_MISC_READ 0x3CC
#define VGA_SEQ_INDEX 0x3C4
#define VGA_SEQ_DATA 0x3C5
#define VGA_DAC_READ_INDEX 0x3C7
#define VGA_DAC_WRITE_INDEX 0x3C8
#define VGA_DAC_DATA 0x3C9
#define VGA_GC_INDEX 0x3CE
#define VGA_GC_DATA 0x3CF
#define VGA_CRTC_INDEX 0x3D4
#define VGA_CRTC_DATA 0x3D5
#define VGA_INSTAT_READ 0x3DA
uint8_t vgaMode18[] = {
0xE3, 0x03, 0x01, 0x08, 0x00, 0x06, 0x5F, 0x4F,
0x50, 0x82, 0x54, 0x80, 0x0B, 0x3E, 0x00, 0x40,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xEA, 0x0C,
0xDF, 0x28, 0x00, 0xE7, 0x04, 0xE3, 0xFF, 0x00,
0x00, 0x00, 0x00, 0x03, 0x00, 0x05, 0x0F, 0xFF,
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x14, 0x07,
0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F,
0x01, 0x00, 0x0F, 0x00, 0x00,
};
volatile uint8_t *vgaAddress;
#define VGA_SCREEN_WIDTH (640)
#define VGA_SCREEN_HEIGHT (480)
uint8_t egaPaletteConverter[4][64] = {
{ 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1,
0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, },
{ 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 1, 1, 1,
0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 1, 1, 1, },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, },
{ 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1,
0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, },
};
void VGAUpdateScreen(uint8_t *_source, uint8_t *modifiedScanlineBitset, KModifiedScanline *modifiedScanlines) {
for (int plane = 0; plane < 4; plane++) {
uint8_t *source = _source;
ProcessorOut8(VGA_SEQ_INDEX, 2);
ProcessorOut8(VGA_SEQ_DATA, 1 << plane);
for (uintptr_t y_ = 0; y_ < VGA_SCREEN_HEIGHT / 8; y_++) {
if (modifiedScanlineBitset[y_] == 0) {
source += VGA_SCREEN_WIDTH * 32;
continue;
}
for (uintptr_t y = 0; y < 8; y++) {
uint8_t *sourceStart = source;
if ((modifiedScanlineBitset[y_] & (1 << y)) == 0) {
source += VGA_SCREEN_WIDTH * 4;
continue;
}
KModifiedScanline *scanline = modifiedScanlines + y + (y_ << 3);
uintptr_t x = scanline->minimumX & ~7;
source += 4 * x;
while (x < scanline->maximumX) {
uint8_t v = 0;
for (int i = 7; i >= 0; i--) {
if (egaPaletteConverter[plane][((source[0] >> 6) & 3) | ((source[1] >> 4) & 12) | ((source[2] >> 2) & 48)]) {
v |= 1 << i;
}
source += 4;
}
vgaAddress[(y + y_ * 8) * 80 + (x >> 3)] = v;
x += 8;
}
source = sourceStart + VGA_SCREEN_WIDTH * 4;
}
}
}
}
void VGAPutBlock(uintptr_t x, uintptr_t y, bool toggle) {
for (int plane = 0; plane < 4; plane++) {
ProcessorOut8(VGA_SEQ_INDEX, 2);
ProcessorOut8(VGA_SEQ_DATA, 1 << plane);
if (toggle) {
vgaAddress[y * 80 + x / 8] ^= 1 << (7 - (x & 7));
} else {
vgaAddress[y * 80 + x / 8] |= 1 << (7 - (x & 7));
}
}
}
void VGAClearScreen() {
for (int plane = 0; plane < 4; plane++) {
ProcessorOut8(VGA_SEQ_INDEX, 2);
ProcessorOut8(VGA_SEQ_DATA, 1 << plane);
EsMemoryZero((void *) vgaAddress, VGA_SCREEN_WIDTH / 8 * VGA_SCREEN_HEIGHT);
}
}
void InitialiseVGA(KDevice *parent) {
if (KGraphicsIsTargetRegistered()) {
return;
}
vgaAddress = (uint8_t *) MMMapPhysical(MMGetKernelSpace(), 0xA0000, 0x10000, MM_REGION_WRITE_COMBINING);
uint8_t *registers = vgaMode18;
ProcessorOut8(VGA_MISC_WRITE, *registers++);
for (int i = 0; i < 5; i++) { ProcessorOut8(VGA_SEQ_INDEX, i); ProcessorOut8(VGA_SEQ_DATA, *registers++); }
ProcessorOut8(VGA_CRTC_INDEX, 0x03);
ProcessorOut8(VGA_CRTC_DATA, ProcessorIn8(VGA_CRTC_DATA) | 0x80);
ProcessorOut8(VGA_CRTC_INDEX, 0x11);
ProcessorOut8(VGA_CRTC_DATA, ProcessorIn8(VGA_CRTC_DATA) & ~0x80);
registers[0x03] |= 0x80;
registers[0x11] &= ~0x80;
for (int i = 0; i < 25; i++) { ProcessorOut8(VGA_CRTC_INDEX, i); ProcessorOut8(VGA_CRTC_DATA, *registers++); }
for (int i = 0; i < 9; i++) { ProcessorOut8(VGA_GC_INDEX, i); ProcessorOut8(VGA_GC_DATA, *registers++); }
for (int i = 0; i < 21; i++) { ProcessorIn8(VGA_INSTAT_READ); ProcessorOut8(VGA_AC_INDEX, i); ProcessorOut8(VGA_AC_WRITE, *registers++); }
ProcessorIn8(VGA_INSTAT_READ);
ProcessorOut8(VGA_AC_INDEX, 0x20);
KGraphicsTarget *target = (KGraphicsTarget *) KDeviceCreate("VGA", parent, sizeof(KGraphicsTarget));
target->screenWidth = VGA_SCREEN_WIDTH;
target->screenHeight = VGA_SCREEN_HEIGHT;
target->updateScreen = VGAUpdateScreen;
target->debugPutBlock = VGAPutBlock;
target->debugClearScreen = VGAClearScreen;
target->reducedColors = true;
// TODO Debug callbacks.
KRegisterGraphicsTarget(target);
}
#endif
KDriver driverSVGA = {
.attach = InitialiseVBE,
};

340
drivers/usb.cpp Normal file
View File

@ -0,0 +1,340 @@
#include <module.h>
#define SETUP_FLAG_D2H (0x80)
#define DESCRIPTOR_DEVICE (1)
#define DESCRIPTOR_CONFIGURATION (2)
#define DESCRIPTOR_STRING (3)
#define DESCRIPTOR_INTERFACE (4)
#define DESCRIPTOR_ENDPOINT (5)
KUSBDescriptorHeader *KUSBDevice::GetCommonDescriptor(uint8_t type, uintptr_t index) {
uintptr_t position = selectedConfigurationOffset;
while (position < configurationDescriptorsBytes) {
KUSBDescriptorHeader *header = (KUSBDescriptorHeader *) (configurationDescriptors + position);
if (header->descriptorType == DESCRIPTOR_INTERFACE || header->descriptorType == DESCRIPTOR_CONFIGURATION) {
return nullptr;
} else if (header->descriptorType == type) {
if (index) {
index--;
} else {
return header;
}
}
position += header->length;
}
return nullptr;
}
struct SynchronousTransfer {
KEvent complete;
size_t *bytesNotTransferred;
bool success;
};
void SynchronousTransferCallback(ptrdiff_t bytesNotTransferred, EsGeneric context) {
SynchronousTransfer *transfer = (SynchronousTransfer *) context.p;
if (bytesNotTransferred != -1) {
if (transfer->bytesNotTransferred) {
transfer->success = true;
*transfer->bytesNotTransferred = bytesNotTransferred;
} else if (!bytesNotTransferred) {
transfer->success = true;
}
}
KEventSet(&transfer->complete);
}
bool KUSBDevice::RunTransfer(KUSBEndpointDescriptor *endpoint, void *buffer, size_t bufferBytes, size_t *bytesNotTransferred) {
SynchronousTransfer transfer = {};
transfer.bytesNotTransferred = bytesNotTransferred;
queueTransfer(this, endpoint, SynchronousTransferCallback, buffer, bufferBytes, &transfer);
KEventWait(&transfer.complete);
return transfer.success;
}
bool KUSBDevice::GetString(uint8_t index, char *buffer, size_t bufferBytes) {
uint16_t wideBuffer[127];
if (!bufferBytes || !index) {
return false;
}
uint16_t transferred = 0;
if (!controlTransfer(this, SETUP_FLAG_D2H, 0x06 /* get descriptor */,
((uint16_t) DESCRIPTOR_STRING << 8) | (uint16_t) index, 0,
wideBuffer, sizeof(wideBuffer), K_ACCESS_READ, &transferred)) {
return false;
}
if (transferred < 4) {
return false;
}
size_t inputCharactersRemaining = (*(uint8_t *) wideBuffer) / 2 - 1;
if ((size_t) (transferred / 2 - 1) < inputCharactersRemaining) {
inputCharactersRemaining = transferred / 2 - 1;
}
uint16_t *inputPosition = wideBuffer + 1;
uintptr_t bufferPosition = 0;
while (inputCharactersRemaining) {
uint32_t c = *inputPosition;
inputCharactersRemaining--;
inputPosition++;
if (c >= 0xD800 && c < 0xDC00 && inputCharactersRemaining) {
uint32_t c2 = *inputPosition;
if (c2 >= 0xDC00 && c2 < 0xE000) {
inputCharactersRemaining--;
inputPosition++;
c = ((c - 0xD800) << 10) + (c2 - 0xDC00) + 0x10000;
}
}
size_t encodedBytes = utf8_encode(c, nullptr);
if (bufferPosition + encodedBytes < bufferBytes) {
utf8_encode(c, buffer + bufferPosition);
bufferPosition += encodedBytes;
} else {
break;
}
}
buffer[bufferPosition] = 0;
return true;
}
bool USBInterfaceClassCheck(KInstalledDriver *driver, KDevice *device) {
KUSBInterfaceDescriptor *descriptor = &((KUSBDevice *) device)->interfaceDescriptor;
int classCode = -1, subclassCode = -1, protocol = -1;
EsINIState s = {};
s.buffer = driver->config, s.bytes = driver->configBytes;
while (EsINIParse(&s)) {
if (0 == EsStringCompareRaw(s.key, s.keyBytes, EsLiteral("classCode"))) classCode = EsIntegerParse(s.value, s.valueBytes);
if (0 == EsStringCompareRaw(s.key, s.keyBytes, EsLiteral("subclassCode"))) subclassCode = EsIntegerParse(s.value, s.valueBytes);
if (0 == EsStringCompareRaw(s.key, s.keyBytes, EsLiteral("protocol"))) protocol = EsIntegerParse(s.value, s.valueBytes);
}
if (classCode == -1 && subclassCode == -1 && protocol == -1) {
return false;
}
if (classCode != -1 && descriptor->interfaceClass != classCode) return false;
if (subclassCode != -1 && descriptor->interfaceSubclass != subclassCode) return false;
if (protocol != -1 && descriptor->interfaceProtocol != protocol) return false;
return true;
}
bool USBProductIDCheck(KInstalledDriver *driver, KDevice *device) {
KUSBDeviceDescriptor *descriptor = &((KUSBDevice *) device)->deviceDescriptor;
int productID = -1, vendorID = -1, version = -1;
EsINIState s = {};
s.buffer = driver->config, s.bytes = driver->configBytes;
while (EsINIParse(&s)) {
if (0 == EsStringCompareRaw(s.key, s.keyBytes, EsLiteral("productID"))) productID = EsIntegerParse(s.value, s.valueBytes);
if (0 == EsStringCompareRaw(s.key, s.keyBytes, EsLiteral("vendorID"))) vendorID = EsIntegerParse(s.value, s.valueBytes);
if (0 == EsStringCompareRaw(s.key, s.keyBytes, EsLiteral("version"))) version = EsIntegerParse(s.value, s.valueBytes);
}
if (productID == -1 && vendorID == -1 && version == -1) {
return false;
}
if (productID != -1 && descriptor->productID != productID) return false;
if (vendorID != -1 && descriptor->vendorID != vendorID) return false;
if (version != -1 && descriptor->deviceVersion != version) return false;
return true;
}
void KRegisterUSBDevice(KUSBDevice *device) {
EsDefer(KDeviceCloseHandle(device));
bool foundInterfaceDescriptor = false;
{
// Get the device descriptor.
uint16_t transferred;
if (!device->controlTransfer(device, SETUP_FLAG_D2H, 0x06 /* get descriptor */,
(DESCRIPTOR_DEVICE << 8) | 0x00, 0, &device->deviceDescriptor,
sizeof(KUSBDeviceDescriptor), K_ACCESS_READ, &transferred)
|| transferred != sizeof(KUSBDeviceDescriptor)) {
return;
}
if (device->deviceDescriptor.length < sizeof(KUSBDeviceDescriptor)
|| device->deviceDescriptor.descriptorType != DESCRIPTOR_DEVICE
|| device->deviceDescriptor.configurationCount == 0) {
KernelLog(LOG_ERROR, "USB", "invalid device descriptor", "Device descriptor is invalid or unsupported.\n");
return;
}
}
KernelLog(LOG_INFO, "USB", "device identification", "Device has identification %W/%W/%W.\n",
device->deviceDescriptor.vendorID, device->deviceDescriptor.productID, device->deviceDescriptor.deviceVersion);
{
// Get strings.
char buffer[256];
if (device->GetString(device->deviceDescriptor.manufacturerString, buffer, sizeof(buffer))) {
KernelLog(LOG_INFO, "USB", "device manufacturer", "Device manufacturer string: '%z'.\n", buffer);
} else {
goto skipStrings;
}
if (device->GetString(device->deviceDescriptor.productString, buffer, sizeof(buffer))) {
KernelLog(LOG_INFO, "USB", "device product", "Device product string: '%z'.\n", buffer);
} else {
goto skipStrings;
}
if (device->GetString(device->deviceDescriptor.serialNumberString, buffer, sizeof(buffer))) {
KernelLog(LOG_INFO, "USB", "device serial number", "Device serial number string: '%z'.\n", buffer);
} else {
goto skipStrings;
}
skipStrings:;
}
{
// Get the configuration descriptor.
uint16_t transferred;
if (!device->controlTransfer(device, SETUP_FLAG_D2H, 0x06 /* get descriptor */,
(DESCRIPTOR_CONFIGURATION << 8) | 0x00, 0, &device->configurationDescriptor,
sizeof(KUSBConfigurationDescriptor), K_ACCESS_READ, &transferred)
|| transferred != sizeof(KUSBConfigurationDescriptor)) {
return;
}
if (device->configurationDescriptor.totalLength < sizeof(KUSBConfigurationDescriptor)
|| device->configurationDescriptor.descriptorType != DESCRIPTOR_CONFIGURATION
|| !device->configurationDescriptor.interfaceCount
|| !device->configurationDescriptor.configurationIndex) {
KernelLog(LOG_ERROR, "USB", "invalid configuration descriptor", "Invalid field in configuration descriptor.\n");
return;
}
}
{
// Read the rest of the configuration descriptors.
uint8_t *buffer = (uint8_t *) EsHeapAllocate(device->configurationDescriptor.totalLength, false, K_FIXED);
if (!buffer) {
KernelLog(LOG_ERROR, "USB", "allocation failure", "Could not allocate buffer to read all configuration descriptors.\n");
return;
}
uint16_t transferred;
if (!device->controlTransfer(device, SETUP_FLAG_D2H, 0x06 /* get descriptor */,
(DESCRIPTOR_CONFIGURATION << 8) | 0x00, 0, buffer,
device->configurationDescriptor.totalLength, K_ACCESS_READ, &transferred)
|| transferred != device->configurationDescriptor.totalLength) {
return;
}
device->configurationDescriptors = buffer;
device->configurationDescriptorsBytes = device->configurationDescriptor.totalLength;
}
{
// Check the configuration descriptors are valid.
uintptr_t position = 0;
uintptr_t configurationsSeen = 0;
while (position < device->configurationDescriptorsBytes) {
if (position >= device->configurationDescriptorsBytes - sizeof(KUSBDescriptorHeader)) {
KernelLog(LOG_ERROR, "USB", "descriptor invalid length", "Remaining %D, too small for descriptor.\n",
device->configurationDescriptorsBytes - position);
return;
}
KUSBDescriptorHeader *header = (KUSBDescriptorHeader *) (device->configurationDescriptors + position);
if (header->length < sizeof(KUSBDescriptorHeader) || header->length > device->configurationDescriptorsBytes - position) {
KernelLog(LOG_ERROR, "USB", "descriptor invalid length", "Given length %D, remaining %D.\n",
header->length, device->configurationDescriptorsBytes - position);
return;
}
if (header->descriptorType == DESCRIPTOR_CONFIGURATION
&& header->length >= sizeof(KUSBConfigurationDescriptor)) {
configurationsSeen++;
}
if (header->descriptorType == DESCRIPTOR_INTERFACE
&& header->length >= sizeof(KUSBInterfaceDescriptor)
&& !foundInterfaceDescriptor
&& configurationsSeen == 1) {
device->interfaceDescriptor = *(KUSBInterfaceDescriptor *) header;
device->selectedConfigurationOffset = position + header->length;
foundInterfaceDescriptor = true;
}
position += header->length;
}
}
{
// Look for a driver that matches the vendor/product.
if (KDeviceAttach(device, "USB", USBProductIDCheck)) {
return;
}
// Otherwise, pick the default configuration and interface.
if (!foundInterfaceDescriptor) {
KernelLog(LOG_ERROR, "USB", "no interface descriptor",
"The device does not have any interface descriptors for the default configuration.");
}
KernelLog(LOG_INFO, "USB", "device interface", "Device has interface %d with identification %X/%X/%X.\n",
device->interfaceDescriptor.interfaceIndex, device->interfaceDescriptor.interfaceClass,
device->interfaceDescriptor.interfaceSubclass, device->interfaceDescriptor.interfaceProtocol);
if (!device->selectConfigurationAndInterface(device)) {
KernelLog(LOG_ERROR, "USB", "select interface failure", "Could not select configuration and interface for device.\n");
return;
}
if (KDeviceAttach(device, "USB", USBInterfaceClassCheck)) {
return;
}
KernelLog(LOG_ERROR, "USB", "no driver", "No driver could be found for the device.\n");
// TODO Show an error message to the user.
}
}
KDriver driverUSB;

223
drivers/usb_bulk.cpp Normal file
View File

@ -0,0 +1,223 @@
#include <module.h>
// TODO STALL handling.
// TODO Resetting the device on error.
// TODO Command timeout.
struct Device : KDevice {
KUSBDevice *parent;
KUSBEndpointDescriptor *inputEndpoint, *outputEndpoint;
uint8_t maximumLUN;
KMutex mutex;
void Initialise();
bool DoTransfer(struct CommandBlock *block, void *buffer);
};
struct Drive : KBlockDevice {
Device *device;
uint8_t lun;
};
struct CommandBlock {
#define COMMAND_BLOCK_SIGNATURE (0x43425355)
uint32_t signature;
uint32_t tag; // Returned in the corresponding command status.
uint32_t transferBytes;
#define COMMAND_FLAGS_INPUT (0x80)
#define COMMAND_FLAGS_OUTPUT (0x00)
uint8_t flags;
uint8_t lun;
uint8_t commandBytes;
uint8_t command[16];
} __attribute__((packed));
struct CommandStatus {
#define COMMAND_STATUS_SIGNATURE (0x53425355)
uint32_t signature;
uint32_t tag;
uint32_t residue;
#define STATUS_FAILED (1)
#define STATUS_PHASE_ERROR (2)
uint8_t status;
} __attribute__((packed));
bool Device::DoTransfer(CommandBlock *block, void *buffer) {
KMutexAcquire(&mutex);
EsDefer(KMutexRelease(&mutex));
block->signature = COMMAND_BLOCK_SIGNATURE;
block->tag = EsRandomU64() & 0xFFFFFFFF;
KernelLog(LOG_VERBOSE, "USBBulk", "transfer", "Transferring %D to %x, %z, LUN %d, command %X %X %X %X %X %X %X %X %X %X %X %X %X %X %X %X (%D).\n",
block->transferBytes, buffer, block->flags == COMMAND_FLAGS_INPUT ? "input" : "output", block->lun,
block->command[0], block->command[1], block->command[2], block->command[3],
block->command[4], block->command[5], block->command[6], block->command[7],
block->command[8], block->command[9], block->command[10], block->command[11],
block->command[12], block->command[13], block->command[14], block->command[15], block->commandBytes);
// Send the command block to the output endpoint.
if (!parent->RunTransfer(outputEndpoint, block, sizeof(CommandBlock), nullptr)) {
KernelLog(LOG_ERROR, "USBBulk", "send command block error", "Could not send the command block to the device.\n");
return false;
}
// Perform the transfer.
if (!parent->RunTransfer(block->flags == COMMAND_FLAGS_INPUT ? inputEndpoint : outputEndpoint, buffer, block->transferBytes, nullptr)) {
KernelLog(LOG_ERROR, "USBBulk", "transfer error", "Could not transfer with the device.\n");
return false;
}
// Read the command status from the input endpoint.
CommandStatus status = {};
if (!parent->RunTransfer(inputEndpoint, &status, sizeof(CommandStatus), nullptr)) {
KernelLog(LOG_ERROR, "USBBulk", "read command status error", "Could not read the command status from the device.\n");
return false;
}
if (status.signature != COMMAND_STATUS_SIGNATURE
|| status.tag != block->tag
|| status.residue
|| status.status) {
KernelLog(LOG_ERROR, "USBBulk", "command unsuccessful", "Command status indicates it was unsuccessful: "
"signature: %x, tag: %x (%x), residue: %D, status: %d.\n",
status.signature, status.tag, block->tag, status.residue, status.status);
return false;
}
return true;
}
void DriveAccess(KBlockDeviceAccessRequest request) {
Drive *drive = (Drive *) request.device;
Device *device = drive->device;
request.dispatchGroup->Start();
uint32_t offsetSectors = request.offset / drive->sectorSize;
uint32_t countSectors = request.count / drive->sectorSize;
CommandBlock command = {
.transferBytes = (uint32_t) request.count,
.flags = (uint8_t) (request.operation == K_ACCESS_WRITE ? COMMAND_FLAGS_OUTPUT : COMMAND_FLAGS_INPUT),
.lun = drive->lun,
.commandBytes = 10,
.command = {
[0] = (uint8_t) (request.operation == K_ACCESS_WRITE ? 0x2A /* WRITE (12) */ : 0x28 /* READ (12) */),
[1] = 0,
[2] = (uint8_t) (offsetSectors >> 0x18),
[3] = (uint8_t) (offsetSectors >> 0x10),
[4] = (uint8_t) (offsetSectors >> 0x08),
[5] = (uint8_t) (offsetSectors >> 0x00),
[6] = 0,
[7] = (uint8_t) (countSectors >> 0x08),
[8] = (uint8_t) (countSectors >> 0x00),
},
};
request.dispatchGroup->End(device->DoTransfer(&command, (void *) KDMABufferGetVirtualAddress(request.buffer)));
}
void Device::Initialise() {
uint16_t transferred;
// Find the input and output endpoints.
for (uintptr_t i = 0; true; i++) {
KUSBEndpointDescriptor *endpoint = (KUSBEndpointDescriptor *) parent->GetCommonDescriptor(0x05 /* endpoint */, i);
if (!endpoint) {
break;
} else if (endpoint->IsBulk() && endpoint->IsInput() && !inputEndpoint) {
inputEndpoint = endpoint;
} else if (endpoint->IsBulk() && endpoint->IsOutput() && !outputEndpoint) {
outputEndpoint = endpoint;
}
}
if (!inputEndpoint || !outputEndpoint) {
KernelLog(LOG_ERROR, "USBBulk", "endpoint missing", "Could not find both bulk endpoints.\n");
return;
}
// Reset the mass storage device.
if (!parent->controlTransfer(parent, 0b00100001, 0b11111111, 0, parent->interfaceDescriptor.interfaceIndex, nullptr, 0, K_ACCESS_WRITE, &transferred)) {
KernelLog(LOG_ERROR, "USBBulk", "reset failure", "Could not reset the mass storage device.\n");
return;
}
// Get the maximum LUN.
parent->controlTransfer(parent, 0b10100001, 0b11111110, 0, parent->interfaceDescriptor.interfaceIndex, &maximumLUN, 1, K_ACCESS_READ, &transferred);
KernelLog(LOG_INFO, "USBBulk", "maximum LUN", "Device reports maximum LUN of %d.\n", maximumLUN);
for (uintptr_t i = 0; i <= maximumLUN; i++) {
// Get the capacity of the LUN.
CommandBlock command = {
.transferBytes = 8,
.flags = COMMAND_FLAGS_INPUT,
.lun = (uint8_t) i,
.commandBytes = 10,
.command = { [0] = 0x25 /* READ CAPACITY (10) */ },
};
uint8_t capacity[8];
if (!DoTransfer(&command, capacity)) {
KernelLog(LOG_ERROR, "USBBulk", "read capacity error", "Could not read the capacity of LUN %d.\n", i);
continue;
}
uint32_t sectorCount = (((uint32_t) capacity[3] << 0) + ((uint32_t) capacity[2] << 8)
+ ((uint32_t) capacity[1] << 16) + ((uint32_t) capacity[0] << 24)) + 1;
uint32_t sectorBytes = ((uint32_t) capacity[7] << 0) + ((uint32_t) capacity[6] << 8)
+ ((uint32_t) capacity[5] << 16) + ((uint32_t) capacity[4] << 24);
KernelLog(LOG_INFO, "USBBulk", "capacity", "LUN %d has capacity of %D (one sector is %D).\n",
i, (uint64_t) sectorCount * sectorBytes, sectorBytes);
// Register the drive.
Drive *drive = (Drive *) KDeviceCreate("USB bulk drive", this, sizeof(Drive));
if (!drive) {
KernelLog(LOG_ERROR, "USBBulk", "allocation failure", "Could not create drive for LUN %d.\n", i);
break;
}
drive->device = this;
drive->lun = i;
drive->sectorSize = sectorBytes;
drive->sectorCount = sectorCount;
drive->maxAccessSectorCount = 262144 / sectorBytes; // TODO How to determine this? What does the USB layer support?
drive->readOnly = false; // TODO How to detect this?
drive->access = DriveAccess;
drive->driveType = ES_DRIVE_TYPE_USB_MASS_STORAGE;
FSRegisterBlockDevice(drive);
}
}
static void DeviceAttach(KDevice *parent) {
Device *device = (Device *) KDeviceCreate("USB bulk", parent, sizeof(Device));
if (!device) {
KernelLog(LOG_ERROR, "USBBulk", "allocation failure", "Could not allocate device structure.\n");
return;
}
device->parent = (KUSBDevice *) parent;
device->Initialise();
KDeviceCloseHandle(device);
}
KDriver driverUSBBulk = {
.attach = DeviceAttach,
};

737
drivers/usb_hid.cpp Normal file
View File

@ -0,0 +1,737 @@
#include <module.h>
// TODO Key repeat not working on Qemu.
struct ReportItem {
uint32_t usage, application, arrayCount;
int32_t logicalMinimum, logicalMaximum;
uint8_t reportPrefix;
uint8_t bits;
uint8_t group;
#define REPORT_ITEM_CONSTANT (1 << 0)
#define REPORT_ITEM_RELATIVE (1 << 1)
#define REPORT_ITEM_WRAP (1 << 2)
#define REPORT_ITEM_NON_LINEAR (1 << 3)
#define REPORT_ITEM_SIGNED (1 << 4)
#define REPORT_ITEM_ARRAY (1 << 5)
uint8_t flags;
#define REPORT_ITEM_INPUT (1)
#define REPORT_ITEM_OUTPUT (2)
#define REPORT_ITEM_FEATURE (3)
uint8_t type;
};
struct BitBuffer {
const uint8_t *buffer;
size_t bytes;
uintptr_t index;
void Discard(size_t count);
uint32_t ReadUnsigned(size_t count);
int32_t ReadSigned(size_t count);
};
struct GameController {
uint64_t id;
uint8_t reportPrefix;
};
struct HIDDevice : KDevice {
KUSBDevice *device;
Array<ReportItem, K_FIXED> reportItems;
bool usesReportPrefixes;
Array<GameController, K_FIXED> gameControllers;
KUSBEndpointDescriptor *reportEndpoint;
uint8_t *lastReport;
size_t lastReportBytes;
void Initialise();
bool ParseReportDescriptor(const uint8_t *report, size_t reportBytes);
void ReportReceived(BitBuffer *buffer);
};
struct HIDDescriptorLink {
uint8_t type;
uint8_t length[2];
};
struct HIDDescriptor : KUSBDescriptorHeader {
uint8_t specification[2];
uint8_t countryCode;
uint8_t linkCount;
HIDDescriptorLink links[1];
};
struct ReportGlobalState {
int32_t logicalMinimum, logicalMaximum;
uint16_t usagePage;
uint8_t reportSize, reportCount;
uint8_t reportID;
};
struct ReportLocalState {
#define USAGE_ARRAY_SIZE (32)
uint32_t usages[USAGE_ARRAY_SIZE];
uint32_t usageMinimum, usageMaximum;
uint8_t usageCount;
#define DELIMITER_NONE (0)
#define DELIMITER_FIRST (1)
#define DELIMITER_IGNORE (2)
uint8_t delimiterState;
};
struct UsageString {
uint32_t usage;
const char *string;
};
#define HID_APPLICATION_MOUSE (0x010002)
#define HID_APPLICATION_JOYSTICK (0x010004)
#define HID_APPLICATION_KEYBOARD (0x010006)
#define HID_USAGE_X_AXIS (0x010030)
#define HID_USAGE_Y_AXIS (0x010031)
#define HID_USAGE_Z_AXIS (0x010032)
#define HID_USAGE_X_ROTATION (0x010033)
#define HID_USAGE_Y_ROTATION (0x010034)
#define HID_USAGE_Z_ROTATION (0x010035)
#define HID_USAGE_HAT_SWITCH (0x010039)
#define HID_USAGE_KEYCODES (0x070000)
#define HID_USAGE_BUTTON_1 (0x090001)
#define HID_USAGE_BUTTON_2 (0x090002)
#define HID_USAGE_BUTTON_3 (0x090003)
#define HID_USAGE_BUTTON_16 (0x090010)
UsageString usageStrings[] = {
{ 0x000000, "padding" },
// Generic desktop page.
{ 0x010001, "pointer" },
{ 0x010002, "mouse" },
{ 0x010004, "joystick" },
{ 0x010005, "gamepad" },
{ 0x010006, "keyboard" },
{ 0x010007, "keypad" },
{ 0x010008, "multi-axis controller" },
{ 0x010009, "tablet PC system controls" },
{ 0x010030, "X axis" },
{ 0x010031, "Y axis" },
{ 0x010032, "Z axis" },
{ 0x010033, "X rotation" },
{ 0x010034, "Y rotation" },
{ 0x010035, "Z rotation" },
{ 0x010036, "slider" },
{ 0x010037, "dial" },
{ 0x010038, "wheel" },
{ 0x010039, "hat switch" },
// Keyboard/keypad page.
{ 0x070000, "keycodes" },
{ 0x0700E0, "left ctrl" },
{ 0x0700E1, "left shift" },
{ 0x0700E2, "left alt" },
{ 0x0700E3, "left gui" },
{ 0x0700E4, "right ctrl" },
{ 0x0700E5, "right shift" },
{ 0x0700E6, "right alt" },
{ 0x0700E7, "right gui" },
// LED page.
{ 0x080001, "num lock" },
{ 0x080002, "caps lock" },
{ 0x080003, "scroll lock" },
{ 0x080004, "compose" },
{ 0x080005, "kana" },
// Button page.
{ 0x090001, "button 1" },
{ 0x090002, "button 2" },
{ 0x090003, "button 3" },
{ 0x090004, "button 4" },
{ 0x090005, "button 5" },
{ 0x090006, "button 6" },
{ 0x090007, "button 7" },
{ 0x090008, "button 8" },
{ 0x090009, "button 9" },
{ 0x09000A, "button 10" },
{ 0x09000B, "button 11" },
{ 0x09000C, "button 12" },
{ 0x09000D, "button 13" },
{ 0x09000E, "button 14" },
{ 0x09000F, "button 15" },
{ 0x090010, "button 16" },
};
const char *LookupUsageString(uint32_t usage) {
if (usage > 0xFF000000) {
return "vendor-specific";
}
for (uintptr_t i = 0; i < sizeof(usageStrings) / sizeof(usageStrings[0]); i++) {
if (usageStrings[i].usage == usage) {
return usageStrings[i].string;
}
}
EsPrint("unknown usage %x\n", usage);
return "unknown";
}
void BitBuffer::Discard(size_t count) {
index += count;
}
uint32_t BitBuffer::ReadUnsigned(size_t count) {
uint32_t result = 0;
uint32_t bit = 0;
while (bit != count) {
uintptr_t byte = index >> 3;
if (byte >= bytes) {
break;
}
if (buffer[byte] & (1 << (index & 7))) {
result |= 1 << bit;
}
bit++, index++;
}
return result;
}
int32_t BitBuffer::ReadSigned(size_t count) {
if (!count) return 0;
uint32_t result = ReadUnsigned(count);
if (result & (1 << (count - 1))) {
for (uintptr_t i = count; i < 32; i++) {
result |= 1 << i;
}
}
return result;
}
bool HIDDevice::ParseReportDescriptor(const uint8_t *report, size_t reportBytes) {
#define REPORT_GLOBAL_STACK_SIZE (8)
ReportGlobalState global[REPORT_GLOBAL_STACK_SIZE] = {};
uintptr_t gIndex = 0;
ReportLocalState local = {};
uint32_t application = 0;
uint8_t group = 0;
uintptr_t position = 0;
while (position < reportBytes) {
uint8_t header = report[position];
if (header == 0xFE) {
// Long items, unused.
if (position + 3 > reportBytes) return false;
position += 3 + report[position + 1];
continue;
}
uint8_t size = header & 3;
uint8_t type = header & ~3;
position++;
if (size == 3) size++;
if (position + size > reportBytes) return false;
uint32_t uData = 0;
int32_t sData = 0;
for (uintptr_t i = 0; i < size; i++) {
uData |= report[position + i] << (i * 8);
}
sData = uData;
if (size && (report[position + size - 1] & 0x80)) {
for (uintptr_t i = size; i < 4; i++) {
sData |= 0xFF << (i * 8);
}
}
position += size;
switch (type) {
case 0b00000100: { global[gIndex].usagePage = uData; } break;
case 0b00010100: { global[gIndex].logicalMinimum = sData; } break;
case 0b00100100: { global[gIndex].logicalMaximum = sData; } break;
case 0b01110100: { global[gIndex].reportSize = uData; } break;
case 0b10010100: { global[gIndex].reportCount = uData; } break;
case 0b10000100: {
global[gIndex].reportID = uData;
if (uData) usesReportPrefixes = true;
} break;
case 0b10100100: {
if (gIndex + 1 == REPORT_GLOBAL_STACK_SIZE) return false;
gIndex++;
global[gIndex] = global[gIndex - 1];
} break;
case 0b10110100: {
if (gIndex == 0) return false;
gIndex--;
} break;
case 0b00001000: {
if (local.usageCount == USAGE_ARRAY_SIZE) return false;
if (local.delimiterState != DELIMITER_IGNORE) {
local.usages[local.usageCount++] = uData | (size < 4 ? (global[gIndex].usagePage << 16) : 0);
}
if (local.delimiterState == DELIMITER_FIRST) local.delimiterState = DELIMITER_IGNORE;
} break;
case 0b00011000: { local.usageMinimum = uData | (size < 4 ? (global[gIndex].usagePage << 16) : 0); } break;
case 0b00101000: { local.usageMaximum = uData | (size < 4 ? (global[gIndex].usagePage << 16) : 0); } break;
case 0b10101000: {
if (uData) local.delimiterState = DELIMITER_FIRST;
else local.delimiterState = DELIMITER_NONE;
} break;
case 0b10100000: {
if (uData == 1) {
if (local.usageCount == 0) return false;
application = local.usages[0];
group++;
}
} break;
case 0b10010000:
case 0b10110000:
case 0b10000000: {
for (uintptr_t i = 0; i < global[gIndex].reportCount; i++) {
ReportItem item = {
.application = application,
.logicalMinimum = global[gIndex].logicalMinimum,
.logicalMaximum = global[gIndex].logicalMaximum,
.reportPrefix = global[gIndex].reportID,
.bits = global[gIndex].reportSize,
};
if (type == 0b10000000) {
item.type = REPORT_ITEM_INPUT;
} else if (type == 0b10010000) {
item.type = REPORT_ITEM_OUTPUT;
} else if (type == 0b10110000) {
item.type = REPORT_ITEM_FEATURE;
}
if ( uData & (1 << 0)) item.flags |= REPORT_ITEM_CONSTANT;
if (~uData & (1 << 1)) item.flags |= REPORT_ITEM_ARRAY;
if ( uData & (1 << 2)) item.flags |= REPORT_ITEM_RELATIVE;
if ( uData & (1 << 3)) item.flags |= REPORT_ITEM_WRAP;
if ( uData & (1 << 4)) item.flags |= REPORT_ITEM_NON_LINEAR;
if (item.logicalMinimum < 0 || item.logicalMaximum < 0) item.flags |= REPORT_ITEM_SIGNED;
if (local.usageCount) {
item.usage = local.usages[i >= local.usageCount ? local.usageCount - 1 : i];
} else {
item.usage = (i > local.usageMaximum - local.usageMinimum) ? local.usageMaximum : (local.usageMinimum + i);
}
if (item.flags & REPORT_ITEM_ARRAY) {
item.arrayCount = global[gIndex].reportCount;
}
if (!reportItems.Add(item)) {
return false;
}
KernelLog(LOG_INFO, "USBHID", "parsed report item",
"Parsed report item - group: %d, application: '%z', usage: '%z', range: %i->%i, report: %d, bits: %d, "
"flags: %z%z%z%z%z%z, type: %z, array count: %d\n",
group, LookupUsageString(item.application), LookupUsageString(item.usage),
item.logicalMinimum, item.logicalMaximum,
item.reportPrefix, item.bits,
(item.flags & REPORT_ITEM_CONSTANT) ? "constant|" : "",
(item.flags & REPORT_ITEM_RELATIVE) ? "relative|" : "",
(item.flags & REPORT_ITEM_WRAP) ? "wrap|" : "",
(item.flags & REPORT_ITEM_NON_LINEAR) ? "non-linear|" : "",
(item.flags & REPORT_ITEM_ARRAY) ? "array|" : "",
(item.flags & REPORT_ITEM_SIGNED) ? "signed" : "unsigned",
item.type == REPORT_ITEM_INPUT ? "input" : item.type == REPORT_ITEM_OUTPUT ? "output" : "feature", item.arrayCount);
if (item.application == HID_APPLICATION_KEYBOARD) {
cDebugName = "USB HID keyboard";
} else if (item.application == HID_APPLICATION_MOUSE) {
cDebugName = "USB HID mouse";
} else if (item.application == HID_APPLICATION_JOYSTICK) {
cDebugName = "USB HID joystick";
}
if (item.flags & REPORT_ITEM_ARRAY) {
break;
}
}
} break;
}
if ((type & 0b00001100) == 0) {
EsMemoryZero(&local, sizeof(ReportLocalState));
}
}
return true;
}
void ReportReceivedCallback(ptrdiff_t bytesNotTransferred, EsGeneric context) {
HIDDevice *device = (HIDDevice *) context.p;
size_t bytesTransferred = device->reportEndpoint->GetMaximumPacketSize() - bytesNotTransferred;
if (bytesNotTransferred == -1) {
KernelLog(LOG_ERROR, "USBHID", "report transfer failure", "Report transfer failed.\n");
bytesTransferred = 0;
}
BitBuffer buffer = { device->lastReport, bytesTransferred };
device->ReportReceived(&buffer);
}
void HIDDevice::ReportReceived(BitBuffer *buffer) {
uint8_t prefix = 0;
if (usesReportPrefixes) {
prefix = buffer->ReadUnsigned(8);
}
#ifdef TRACE_REPORTS
EsPrint("-- report (%d) --\n", prefix);
#endif
bool mouseEvent = false;
int mouseXMovement = 0, mouseYMovement = 0, mouseButtons = 0;
bool keyboardEvent = false;
uint16_t keysDown[32];
size_t keysDownCount = 0;
bool gameControllerEvent = false;
EsGameControllerState controllerState = {};
controllerState.directionalPad = 15;
controllerState.analogCount = 2;
for (uintptr_t i = 0; i < gameControllers.Length(); i++) {
if (gameControllers[i].reportPrefix == prefix) {
controllerState.id = gameControllers[i].id;
}
}
for (uintptr_t i = 0; i < reportItems.Length(); i++) {
ReportItem *item = &reportItems[i];
if (item->type != REPORT_ITEM_INPUT || item->reportPrefix != prefix) {
continue;
}
#ifdef TRACE_REPORTS
uintptr_t startIndex = buffer->index;
EsPrint("%d/%z: ", item->group, LookupUsageString(item->usage));
size_t count = (item->flags & REPORT_ITEM_ARRAY) ? item->arrayCount : 1;
for (uintptr_t i = 0; i < count; i++) {
if (item->flags & REPORT_ITEM_SIGNED) {
EsPrint("%i", buffer->ReadSigned(item->bits));
} else {
EsPrint("%d", buffer->ReadUnsigned(item->bits));
}
if (i != count - 1) {
EsPrint(", ");
}
}
EsPrint("\n");
buffer->index = startIndex;
#endif
if (item->flags & REPORT_ITEM_ARRAY) {
bool handled = false;
if (item->application == HID_APPLICATION_KEYBOARD) {
if (item->usage == HID_USAGE_KEYCODES) {
for (uintptr_t i = 0; i < item->arrayCount; i++) {
uint32_t scancode = buffer->ReadUnsigned(item->bits);
keyboardEvent = true;
if (scancode > 0 && scancode < 0x200 && keysDownCount != 32) {
keysDown[keysDownCount++] = scancode;
}
}
}
}
if (!handled) {
buffer->Discard(item->bits * item->arrayCount);
}
} else {
bool handled = false;
if (item->application == HID_APPLICATION_MOUSE) {
// TODO Handle unsigned, absolute, and wrapping movements.
mouseEvent = true;
handled = true;
if (item->usage == HID_USAGE_X_AXIS) {
mouseXMovement = buffer->ReadSigned(item->bits);
} else if (item->usage == HID_USAGE_Y_AXIS) {
mouseYMovement = buffer->ReadSigned(item->bits);
} else if (item->usage == HID_USAGE_BUTTON_1) {
if (buffer->ReadUnsigned(item->bits)) mouseButtons |= 1 << 0;
} else if (item->usage == HID_USAGE_BUTTON_2) {
if (buffer->ReadUnsigned(item->bits)) mouseButtons |= 1 << 2;
} else if (item->usage == HID_USAGE_BUTTON_3) {
if (buffer->ReadUnsigned(item->bits)) mouseButtons |= 1 << 1;
} else {
handled = false;
}
} else if (item->application == HID_APPLICATION_KEYBOARD) {
handled = true;
if (item->usage > HID_USAGE_KEYCODES && item->usage < HID_USAGE_KEYCODES + 0x200) {
handled = true;
keyboardEvent = true;
if (buffer->ReadUnsigned(item->bits) && keysDownCount != 32) {
keysDown[keysDownCount++] = item->usage - HID_USAGE_KEYCODES;
}
}
} else if (item->application == HID_APPLICATION_JOYSTICK) {
handled = true;
if (item->usage >= HID_USAGE_BUTTON_1 && item->usage <= HID_USAGE_BUTTON_16) {
gameControllerEvent = true;
if (buffer->ReadUnsigned(item->bits)) {
controllerState.buttons |= 1 << controllerState.buttonCount;
}
controllerState.buttonCount++;
} else if (item->usage == HID_USAGE_X_AXIS) {
gameControllerEvent = true;
controllerState.analog[0].x = buffer->ReadUnsigned(item->bits) * 0xFF / item->logicalMaximum;
} else if (item->usage == HID_USAGE_Y_AXIS) {
gameControllerEvent = true;
controllerState.analog[0].y = buffer->ReadUnsigned(item->bits) * 0xFF / item->logicalMaximum;
} else if (item->usage == HID_USAGE_Z_AXIS) {
gameControllerEvent = true;
controllerState.analog[0].z = buffer->ReadUnsigned(item->bits) * 0xFF / item->logicalMaximum;
} else if (item->usage == HID_USAGE_X_ROTATION) {
gameControllerEvent = true;
controllerState.analog[1].x = buffer->ReadUnsigned(item->bits) * 0xFF / item->logicalMaximum;
} else if (item->usage == HID_USAGE_Y_ROTATION) {
gameControllerEvent = true;
controllerState.analog[1].y = buffer->ReadUnsigned(item->bits) * 0xFF / item->logicalMaximum;
} else if (item->usage == HID_USAGE_Z_ROTATION) {
gameControllerEvent = true;
controllerState.analog[1].z = buffer->ReadUnsigned(item->bits) * 0xFF / item->logicalMaximum;
} else if (item->usage == HID_USAGE_HAT_SWITCH) {
gameControllerEvent = true;
controllerState.directionalPad = buffer->ReadUnsigned(item->bits);
} else {
handled = false;
}
}
if (!handled) {
buffer->Discard(item->bits);
}
}
}
if (mouseEvent) KCursorUpdate(mouseXMovement, mouseYMovement, mouseButtons);
if (keyboardEvent) KKeyboardUpdate(keysDown, keysDownCount);
if (gameControllerEvent) KGameControllerUpdateState(&controllerState);
if (device->flags & K_DEVICE_REMOVED) {
KDeviceCloseHandle(this);
} else {
if (!device->queueTransfer(device, reportEndpoint, ReportReceivedCallback,
lastReport, reportEndpoint->GetMaximumPacketSize(), this)) {
KernelLog(LOG_ERROR, "USBHID", "setup transfer failure", "Could not setup the interrupt input transfer to receive the next report packet.\n");
KDeviceCloseHandle(this);
}
}
}
void HIDDevice::Initialise() {
// Find the HID descriptor.
HIDDescriptor *hidDescriptor = (HIDDescriptor *) device->GetCommonDescriptor(0x21, 0);
if (!hidDescriptor) {
KernelLog(LOG_ERROR, "USBHID", "missing descriptor", "Could not find the HID descriptor.\n");
return;
} else if (hidDescriptor->length < sizeof(HIDDescriptor) || hidDescriptor->linkCount == 0
|| (hidDescriptor->linkCount - 1) * sizeof(HIDDescriptorLink) + sizeof(HIDDescriptor) > hidDescriptor->length) {
KernelLog(LOG_ERROR, "USBHID", "bad descriptor length", "HID descriptor too short (%D) for %d links.\n",
hidDescriptor->length, hidDescriptor->linkCount);
return;
}
// Get the size of the report descriptor.
size_t reportBytes = 0;
for (uintptr_t i = 0; i < hidDescriptor->linkCount; i++) {
if (hidDescriptor->links[i].type == 0x22) {
reportBytes = (size_t) hidDescriptor->links[i].length[0] | ((size_t) hidDescriptor->links[i].length[1] << 8);
}
}
if (!reportBytes) {
KernelLog(LOG_ERROR, "USBHID", "no report descriptor", "Could not find report descriptor link in HID descriptor.\n");
return;
}
// Switch to the report protocol.
if (!device->controlTransfer(device, 0x21, 0x0B /* set protocol */, 1 /* report protocol */,
device->interfaceDescriptor.interfaceIndex, nullptr, 0, K_ACCESS_WRITE, nullptr)) {
KernelLog(LOG_ERROR, "USBHID", "set protocol failure", "Could not switch to the report protocol.\n");
return;
}
// Get the report descriptor and parse it.
uint8_t *report = (uint8_t *) EsHeapAllocate(reportBytes, false, K_FIXED);
if (!report) {
KernelLog(LOG_ERROR, "USBHID", "allocation failure", "Could not allocate buffer to store the report descriptor.\n");
return;
}
EsDefer(EsHeapFree(report, reportBytes, K_FIXED));
uint16_t transferred = 0;
if (!device->controlTransfer(device, 0x81, 0x06, 0x22 << 8, 0, report, reportBytes, K_ACCESS_READ, &transferred)
|| transferred != reportBytes) {
KernelLog(LOG_ERROR, "USBHID", "no report descriptor", "Could not read the report descriptor from the device.\n");
return;
}
if (!ParseReportDescriptor(report, reportBytes)) {
KernelLog(LOG_ERROR, "USBHID", "invalid report descriptor", "Could not parse the report descriptor.\n");
return;
}
// Set idle.
if (!device->controlTransfer(device, 0x21, 0x0A /* set idle */, 0 /* infinite duration, apply to all report IDs */,
device->interfaceDescriptor.interfaceIndex, nullptr, 0, K_ACCESS_WRITE, nullptr)) {
KernelLog(LOG_ERROR, "USBHID", "enable idle failure", "Could not enable idle mode on the device.\n");
return;
}
// Get the interrupt-in endpoint descriptor.
KUSBEndpointDescriptor *endpoint = nullptr;
{
uintptr_t index = 0;
while (true) {
KUSBEndpointDescriptor *e = (KUSBEndpointDescriptor *) device->GetCommonDescriptor(0x05 /* endpoint */, index++);
if (!e) {
break;
} else if (e->IsInterrupt() && e->IsInput()) {
endpoint = e;
break;
}
}
}
lastReport = (uint8_t *) EsHeapAllocate(endpoint->GetMaximumPacketSize(), true, K_FIXED);
if (!lastReport) {
KernelLog(LOG_ERROR, "USBHID", "allocation failure", "Could not allocate buffer to store received reports.\n");
return;
}
// Start receiving interrupt packets.
reportEndpoint = endpoint;
KDeviceOpenHandle(this);
if (!device->queueTransfer(device, endpoint, ReportReceivedCallback, lastReport, endpoint->GetMaximumPacketSize(), this)) {
KernelLog(LOG_ERROR, "USBHID", "setup transfer failure", "Could not setup the interrupt input transfer to receive report packets.\n");
KDeviceCloseHandle(this);
return;
}
// If this is a game controller, tell the window manager it's been connected.
{
uint64_t seen = 0;
for (uintptr_t i = 0; i < reportItems.Length(); i++) {
ReportItem *item = &reportItems[i];
if (item->application == HID_APPLICATION_JOYSTICK
&& item->reportPrefix < 64
&& (~seen & (1 << item->reportPrefix))) {
seen |= (1 << item->reportPrefix);
GameController controller = {};
controller.id = KGameControllerConnect();
if (controller.id) {
controller.reportPrefix = item->reportPrefix;
gameControllers.Add(controller);
}
}
}
}
}
static void DeviceDestroy(KDevice *_device) {
HIDDevice *device = (HIDDevice *) _device;
for (uintptr_t i = 0; i < device->gameControllers.Length(); i++) {
KGameControllerDisconnect(device->gameControllers[i].id);
}
device->reportItems.Free();
device->gameControllers.Free();
EsHeapFree(device->lastReport, 0, K_FIXED);
}
static void DeviceAttach(KDevice *parent) {
HIDDevice *device = (HIDDevice *) KDeviceCreate("USB HID", parent, sizeof(HIDDevice));
if (!device) {
KernelLog(LOG_ERROR, "USBHID", "allocation failure", "Could not allocate HIDDevice structure.\n");
return;
}
device->destroy = DeviceDestroy;
device->device = (KUSBDevice *) parent;
device->Initialise();
KDeviceCloseHandle(device);
}
KDriver driverUSBHID = {
.attach = DeviceAttach,
};

1282
drivers/xhci.cpp Normal file

File diff suppressed because it is too large Load Diff

0
kernel/audio.cpp Normal file
View File

1220
kernel/cache.cpp Normal file

File diff suppressed because it is too large Load Diff

146
kernel/config.ini Normal file
View File

@ -0,0 +1,146 @@
[@driver Root]
builtin=1
; Subsystems.
[@driver Networking]
parent=Root
builtin=1
[@driver USB]
source=drivers/usb.cpp
parent=Root
builtin=1
; Architectures.
[@driver ACPI]
arch=x86_common
parent=Root
builtin=1
; Base devices.
[@driver PCI]
source=drivers/pci.cpp
arch=x86_common
builtin=1
[@driver SVGA]
source=drivers/svga.cpp
arch=x86_common
builtin=1
[@driver PS2]
source=drivers/ps2.cpp
arch=x86_common
builtin=1
; PCI devices.
[@driver IDE]
source=drivers/ide.cpp
builtin=1
arch=x86_common
parent=PCI
classCode=0x01
subclassCode=0x01
[@driver AHCI]
source=drivers/ahci.cpp
builtin=1
parent=PCI
classCode=0x01
subclassCode=0x06
[@driver NVMe]
source=drivers/nvme.cpp
builtin=1
parent=PCI
classCode=0x01
subclassCode=0x08
progIF=0x02
[@driver HDAudio]
name=HDAudio
source=drivers/hda.cpp
builtin=1
parent=PCI
classCode=0x04
subclassCode=0x03
[@driver xHCI]
source=drivers/xhci.cpp
builtin=1
parent=PCI
classCode=0x0C
subclassCode=0x03
progIF=0x30
[@driver BGA]
source=drivers/bga.cpp
builtin=1
parent=PCI
deviceID=0xBEEF80EE
deviceID=0x11111234
[@driver I8254x]
source=drivers/i8254x.cpp
builtin=1
parent=PCI
deviceID=0x100E8086
; USB devices.
[@driver USBHID]
source=drivers/usb_hid.cpp
builtin=1
parent=USB
classCode=0x03
[@driver USBBulk]
source=drivers/usb_bulk.cpp
builtin=1
parent=USB
classCode=0x08
subclassCode=0x06
protocol=0x50
; File systems.
[@driver EssenceFS]
source=drivers/esfs2.cpp
builtin=1
parent=Files
signature_offset=0x2000
signature=!EssenceFS2-----
[@driver FAT]
source=drivers/fat.cpp
builtin=1
parent=Files
signature_offset=0x26
signature=)
signature_offset=0x42
signature=)
[@driver ISO9660]
source=drivers/iso9660.cpp
builtin=1
parent=Files
signature_offset=0x8001
signature=CD001
[@driver NTFS]
source=drivers/ntfs.cpp
builtin=1
parent=Files
signature_offset=3
signature=NTFS
[@driver Ext2]
source=drivers/ext2.cpp
builtin=1
parent=Files
signature_offset=1080
signature=

324
kernel/drivers.cpp Normal file
View File

@ -0,0 +1,324 @@
#ifndef IMPLEMENTATION
struct DeviceAttachData {
KDevice *parentDevice;
KInstalledDriver *installedDriver;
};
KDevice *deviceTreeRoot;
KMutex deviceTreeMutex;
Array<DeviceAttachData, K_FIXED> delayedDevices;
Array<KInstalledDriver, K_FIXED> installedDrivers;
#endif
#ifdef IMPLEMENTATION
void *ResolveKernelSymbol(const char *name, size_t nameBytes);
KDevice *KDeviceCreate(const char *cDebugName, KDevice *parent, size_t bytes) {
if (bytes < sizeof(KDevice)) {
KernelPanic("KDeviceCreate - Device structure size is too small (less than KDevice).\n");
}
KDevice *device = (KDevice *) EsHeapAllocate(bytes, true, K_FIXED);
if (!device) return nullptr;
device->parent = parent;
device->cDebugName = cDebugName;
device->handles = 2; // One handle for the creator, and another closed when the device is removed (by the parent).
if (parent) {
KMutexAcquire(&deviceTreeMutex);
if (!parent->children.Add(device)) {
EsHeapFree(device, bytes, K_FIXED);
device = nullptr;
}
KMutexRelease(&deviceTreeMutex);
return device;
} else {
if (deviceTreeRoot) {
KernelPanic("KDeviceCreate - Root device already created.\n");
}
return (deviceTreeRoot = device);
}
}
void DeviceDestroy(KDevice *device) {
device->children.Free();
if (device->destroy) device->destroy(device);
EsHeapFree(device, 0, K_FIXED);
}
void KDeviceDestroy(KDevice *device) {
KMutexAcquire(&deviceTreeMutex);
device->handles = 0;
if (device->children.Length()) {
KernelPanic("KDeviceDestroy - Device %x has children.\n", device);
}
while (!device->handles && !device->children.Length()) {
device->parent->children.FindAndDeleteSwap(device, true /* fail if not found */);
KDevice *parent = device->parent;
DeviceDestroy(device);
device = parent;
}
KMutexRelease(&deviceTreeMutex);
}
void KDeviceOpenHandle(KDevice *device) {
KMutexAcquire(&deviceTreeMutex);
if (!device->handles) KernelPanic("KDeviceOpenHandle - Device %s has no handles.\n", device);
device->handles++;
KMutexRelease(&deviceTreeMutex);
}
void KDeviceCloseHandle(KDevice *device) {
KMutexAcquire(&deviceTreeMutex);
if (!device->handles) KernelPanic("KDeviceCloseHandle - Device %s has no handles.\n", device);
device->handles--;
while (!device->handles && !device->children.Length()) {
device->parent->children.FindAndDeleteSwap(device, true /* fail if not found */);
KDevice *parent = device->parent;
DeviceDestroy(device);
device = parent;
}
KMutexRelease(&deviceTreeMutex);
}
void DeviceRemovedRecurse(KDevice *device) {
if (device->flags & K_DEVICE_REMOVED) KernelPanic("DeviceRemovedRecurse - Device %x already removed.\n", device);
device->flags |= K_DEVICE_REMOVED;
for (uintptr_t i = 0; i < device->children.Length(); i++) {
KDevice *child = device->children[i];
DeviceRemovedRecurse(child);
if (!child->handles) KernelPanic("DeviceRemovedRecurse - Child device %s has no handles.\n", child);
child->handles--;
if (child->handles || child->children.Length()) continue;
device->children.DeleteSwap(i);
DeviceDestroy(child);
i--;
}
if (device->removed) {
device->removed(device);
}
}
void KDeviceRemoved(KDevice *device) {
KMutexAcquire(&deviceTreeMutex);
DeviceRemovedRecurse(device);
KMutexRelease(&deviceTreeMutex);
KDeviceCloseHandle(device);
}
const KDriver *DriverLoad(KInstalledDriver *installedDriver) {
static KMutex mutex = {};
KMutexAcquire(&mutex);
EsDefer(KMutexRelease(&mutex));
if (installedDriver->loadedDriver) {
return installedDriver->loadedDriver;
}
KDriver *driver = nullptr;
char *driverVariable = nullptr;
size_t driverVariableBytes = 0;
EsINIState s = {};
s.buffer = installedDriver->config;
s.bytes = installedDriver->configBytes;
while (EsINIParse(&s)) {
if (0 == EsStringCompareRaw(s.key, s.keyBytes, EsLiteral("driver"))) {
driverVariable = s.value;
driverVariableBytes = s.valueBytes;
}
}
char *buffer = (char *) EsHeapAllocate(K_MAX_PATH, true, K_FIXED);
if (!buffer) return nullptr;
EsDefer(EsHeapFree(buffer, K_MAX_PATH, K_FIXED));
KModule *module = (KModule *) EsHeapAllocate(sizeof(KModule), true, K_FIXED);
if (!module) return nullptr;
module->path = buffer;
module->pathBytes = EsStringFormat(buffer, K_MAX_PATH, K_OS_FOLDER "/Modules/%s.ekm",
installedDriver->nameBytes, installedDriver->name);
module->resolveSymbol = ResolveKernelSymbol;
EsError error = KLoadELFModule(module);
if (error == ES_SUCCESS) {
KernelLog(LOG_INFO, "Modules", "module loaded", "Successfully loaded module '%s'.\n",
installedDriver->nameBytes, installedDriver->name);
driver = (KDriver *) KFindSymbol(module, driverVariable, driverVariableBytes);
if (!driver) {
KernelLog(LOG_ERROR, "Modules", "bad module", "DriverLoad - Could not find driver symbol in module '%s'.\n",
installedDriver->nameBytes, installedDriver->name);
}
} else {
KernelLog(LOG_ERROR, "Modules", "module load failure", "Could not load module '%s' (error = %d).\n",
installedDriver->nameBytes, installedDriver->name, error);
EsHeapFree(module, sizeof(KModule), K_FIXED);
}
return (installedDriver->loadedDriver = driver);
}
void DeviceAttach(DeviceAttachData attach) {
TS("DeviceAttach to %s\n", attach.installedDriver->nameBytes, attach.installedDriver->name);
if (attach.parentDevice) {
KDeviceOpenHandle(attach.parentDevice);
}
KMutexAcquire(&deviceTreeMutex);
if (!attach.installedDriver->builtin && !fs.bootFileSystem) {
KernelLog(LOG_INFO, "Modules", "delayed device", "Delaying attach device to driver '%s' until boot file system mounted.\n",
attach.installedDriver->nameBytes, attach.installedDriver->name);
delayedDevices.Add(attach);
KMutexRelease(&deviceTreeMutex);
return;
}
KernelLog(LOG_INFO, "Modules", "device attach", "Attaching device to driver '%s'.\n",
attach.installedDriver->nameBytes, attach.installedDriver->name);
const KDriver *driver = DriverLoad(attach.installedDriver);
KMutexRelease(&deviceTreeMutex);
if (driver && driver->attach) {
driver->attach(attach.parentDevice);
}
if (attach.parentDevice) {
KDeviceCloseHandle(attach.parentDevice);
}
}
bool KDeviceAttach(KDevice *parentDevice, const char *cName, KDriverIsImplementorCallback callback) {
size_t nameBytes = EsCStringLength(cName);
for (uintptr_t i = installedDrivers.Length(); i > 0; i--) {
if (0 == EsStringCompareRaw(cName, nameBytes, installedDrivers[i - 1].parent, installedDrivers[i - 1].parentBytes)
&& callback(&installedDrivers[i - 1], parentDevice)) {
DeviceAttach({ .parentDevice = parentDevice, .installedDriver = &installedDrivers[i - 1] });
return true;
}
}
return false;
}
void KDeviceAttachAll(KDevice *parentDevice, const char *cName) {
size_t nameBytes = EsCStringLength(cName);
for (uintptr_t i = 0; i < installedDrivers.Length(); i++) {
if (0 == EsStringCompareRaw(cName, nameBytes, installedDrivers[i].parent, installedDrivers[i].parentBytes)) {
DeviceAttach({ .parentDevice = parentDevice, .installedDriver = &installedDrivers[i] });
}
}
}
bool KDeviceAttachByName(KDevice *parentDevice, const char *cName) {
size_t nameBytes = EsCStringLength(cName);
for (uintptr_t i = 0; i < installedDrivers.Length(); i++) {
if (0 == EsStringCompareRaw(cName, nameBytes, installedDrivers[i].name, installedDrivers[i].nameBytes)) {
DeviceAttach({ .parentDevice = parentDevice, .installedDriver = &installedDrivers[i] });
return true;
}
}
return false;
}
void DeviceRootAttach(KDevice *parentDevice) {
// Load all the root drivers and create their devices.
KDeviceAttachAll(KDeviceCreate("root", parentDevice, sizeof(KDevice)), "Root");
// Check we have found the drive from which we booted.
// TODO Decide the timeout.
if (!KEventWait(&fs.foundBootFileSystemEvent, 10000)) {
KernelPanic("DeviceRootAttach - Could not find the boot file system.\n");
}
// Load any devices that were waiting for the boot file system to be loaded.
for (uintptr_t i = 0; i < delayedDevices.Length(); i++) {
DeviceAttach(delayedDevices[i]);
KDeviceCloseHandle(delayedDevices[i].parentDevice);
}
delayedDevices.Free();
}
KDriver driverRoot = {
.attach = DeviceRootAttach,
};
void DriversInitialise() {
// Add the builtin drivers to the database.
for (uintptr_t i = 0; i < sizeof(builtinDrivers) / sizeof(builtinDrivers[0]); i++) {
installedDrivers.Add(builtinDrivers[i]);
}
// Attach to the root device.
DeviceAttach({ .parentDevice = nullptr, .installedDriver = &installedDrivers[0] });
}
void DriversDumpStateRecurse(KDevice *device) {
if (device->dumpState) {
device->dumpState(device);
}
for (uintptr_t i = 0; i < device->children.Length(); i++) {
DriversDumpStateRecurse(device->children[i]);
}
}
void DriversDumpState() {
KMutexAcquire(&deviceTreeMutex);
DriversDumpStateRecurse(deviceTreeRoot);
KMutexRelease(&deviceTreeMutex);
}
void DriversShutdownRecurse(KDevice *device) {
for (uintptr_t i = 0; i < device->children.Length(); i++) {
DriversShutdownRecurse(device->children[i]);
}
if (device->shutdown) {
device->shutdown(device);
}
}
void DriversShutdown() {
KMutexAcquire(&deviceTreeMutex);
DriversShutdownRecurse(deviceTreeRoot);
KMutexRelease(&deviceTreeMutex);
}
#endif

422
kernel/elf.cpp Normal file
View File

@ -0,0 +1,422 @@
// TODO Use a custom executable format?
#define MEMORY_MAPPED_EXECUTABLES
#ifndef IMPLEMENTATION
struct ElfHeader {
uint32_t magicNumber; // 0x7F followed by 'ELF'
uint8_t bits; // 1 = 32 bit, 2 = 64 bit
uint8_t endianness; // 1 = LE, 2 = BE
uint8_t version1;
uint8_t abi; // 0 = System V
uint8_t _unused0[8];
uint16_t type; // 1 = relocatable, 2 = executable, 3 = shared
uint16_t instructionSet; // 0x03 = x86, 0x28 = ARM, 0x3E = x86-64, 0xB7 = AArch64
uint32_t version2;
#ifdef ARCH_32
uint32_t entry;
uint32_t programHeaderTable;
uint32_t sectionHeaderTable;
uint32_t flags;
uint16_t headerSize;
uint16_t programHeaderEntrySize;
uint16_t programHeaderEntries;
uint16_t sectionHeaderEntrySize;
uint16_t sectionHeaderEntries;
uint16_t sectionNameIndex;
#else
uint64_t entry;
uint64_t programHeaderTable;
uint64_t sectionHeaderTable;
uint32_t flags;
uint16_t headerSize;
uint16_t programHeaderEntrySize;
uint16_t programHeaderEntries;
uint16_t sectionHeaderEntrySize;
uint16_t sectionHeaderEntries;
uint16_t sectionNameIndex;
#endif
};
#ifdef ARCH_32
struct ElfSectionHeader {
uint32_t name;
uint32_t type;
uint32_t flags;
uint32_t address;
uint32_t offset;
uint32_t size;
uint32_t link;
uint32_t info;
uint32_t align;
uint32_t entrySize;
};
struct ElfProgramHeader {
uint32_t type; // 0 = unused, 1 = load, 2 = dynamic, 3 = interp, 4 = note
uint32_t fileOffset;
uint32_t virtualAddress;
uint32_t _unused0;
uint32_t dataInFile;
uint32_t segmentSize;
uint32_t flags; // 1 = executable, 2 = writable, 4 = readable
uint32_t alignment;
};
struct ElfRelocation {
uint32_t offset;
uint32_t info;
int32_t addend;
};
struct ElfSymbol {
uint32_t name, value, size;
uint8_t info, _reserved1;
uint16_t sectionIndex;
};
#else
struct ElfSectionHeader {
uint32_t name; // Offset into section header->sectionNameIndex.
uint32_t type; // 4 = rela
uint64_t flags;
uint64_t address;
uint64_t offset;
uint64_t size;
uint32_t link;
uint32_t info;
uint64_t align;
uint64_t entrySize;
};
struct ElfProgramHeader{
uint32_t type; // 0 = unused, 1 = load, 2 = dynamic, 3 = interp, 4 = note
uint32_t flags; // 1 = executable, 2 = writable, 4 = readable
uint64_t fileOffset;
uint64_t virtualAddress;
uint64_t _unused0;
uint64_t dataInFile;
uint64_t segmentSize;
uint64_t alignment;
};
struct ElfRelocation {
uint64_t offset;
uint64_t info;
int64_t addend;
};
struct ElfSymbol {
uint32_t name;
uint8_t info, _reserved1;
uint16_t sectionIndex;
uint64_t value, size;
};
#endif
#else
EsError KLoadELF(KNode *node, KLoadedExecutable *executable) {
Process *thisProcess = GetCurrentThread()->process;
uintptr_t executableOffset = 0;
size_t fileSize = FSNodeGetTotalSize(node);
{
BundleHeader header;
size_t bytesRead = FSFileReadSync(node, (uint8_t *) &header, 0, sizeof(BundleHeader), 0);
if (bytesRead != sizeof(BundleHeader)) return ES_ERROR_UNSUPPORTED_EXECUTABLE;
if (header.signature == BUNDLE_SIGNATURE
&& header.fileCount < 0x100000
&& header.fileCount * sizeof(BundleFile) + sizeof(BundleHeader) < fileSize) {
if (!header.mapAddress) {
header.mapAddress = BUNDLE_FILE_MAP_ADDRESS;
}
#ifdef ARCH_X86_64
if (header.mapAddress > 0x8000000000000000UL || header.mapAddress < 0x1000 || fileSize > 0x1000000000000UL
|| header.mapAddress & (K_PAGE_SIZE - 1)) {
return ES_ERROR_UNSUPPORTED_EXECUTABLE;
}
#else
#error Unimplemented.
#endif
if (header.version != 1) {
return ES_ERROR_UNSUPPORTED_EXECUTABLE;
}
// Map the bundle file.
if (!MMMapFile(thisProcess->vmm, (FSFile *) node,
0, fileSize, ES_MAP_OBJECT_READ_ONLY,
(uint8_t *) header.mapAddress)) {
return ES_ERROR_INSUFFICIENT_RESOURCES;
}
// Look for the executable in the bundle.
#ifdef ARCH_X86_64
uint64_t name = CalculateCRC64(EsLiteral("Executable (x86_64)"));
#endif
BundleFile *files = (BundleFile *) ((BundleHeader *) header.mapAddress + 1);
bool found = false;
for (uintptr_t i = 0; i < header.fileCount; i++) {
if (files[i].nameCRC64 == name) {
executableOffset = files[i].offset;
found = true;
break;
}
}
if (executableOffset >= fileSize || !found) {
return ES_ERROR_UNSUPPORTED_EXECUTABLE;
}
}
}
// EsPrint("executableOffset: %x\n", executableOffset);
ElfHeader header;
size_t bytesRead = FSFileReadSync(node, (uint8_t *) &header, executableOffset, sizeof(ElfHeader), 0);
if (bytesRead != sizeof(ElfHeader)) return ES_ERROR_UNSUPPORTED_EXECUTABLE;
size_t programHeaderEntrySize = header.programHeaderEntrySize;
if (header.magicNumber != 0x464C457F) return ES_ERROR_UNSUPPORTED_EXECUTABLE;
if (header.bits != 2) return ES_ERROR_UNSUPPORTED_EXECUTABLE;
if (header.endianness != 1) return ES_ERROR_UNSUPPORTED_EXECUTABLE;
if (header.abi != 0) return ES_ERROR_UNSUPPORTED_EXECUTABLE;
if (header.type != 2) return ES_ERROR_UNSUPPORTED_EXECUTABLE;
if (header.instructionSet != 0x3E) return ES_ERROR_UNSUPPORTED_EXECUTABLE;
ElfProgramHeader *programHeaders = (ElfProgramHeader *) EsHeapAllocate(programHeaderEntrySize * header.programHeaderEntries, false, K_PAGED);
EsDefer(EsHeapFree(programHeaders, 0, K_PAGED));
bytesRead = FSFileReadSync(node, (uint8_t *) programHeaders, executableOffset + header.programHeaderTable, programHeaderEntrySize * header.programHeaderEntries, 0);
if (bytesRead != programHeaderEntrySize * header.programHeaderEntries) return ES_ERROR_UNSUPPORTED_EXECUTABLE;
for (uintptr_t i = 0; i < header.programHeaderEntries; i++) {
ElfProgramHeader *header = (ElfProgramHeader *) ((uint8_t *) programHeaders + programHeaderEntrySize * i);
if (header->type == 1 /* PT_LOAD */) {
#ifdef ARCH_X86_64
if (header->virtualAddress > 0x8000000000000000UL || header->virtualAddress < 0x1000 || header->segmentSize > 0x1000000000000UL) {
return ES_ERROR_UNSUPPORTED_EXECUTABLE;
}
#else
#error Unimplemented.
#endif
#if 0
EsPrint("FileOffset %x VirtualAddress %x SegmentSize %x DataInFile %x\n",
header->fileOffset, header->virtualAddress, header->segmentSize, header->dataInFile);
#endif
void *success;
#ifndef MEMORY_MAPPED_EXECUTABLES
success = MMStandardAllocate(thisProcess->vmm, RoundUp(header->segmentSize, K_PAGE_SIZE), ES_FLAGS_DEFAULT,
(uint8_t *) RoundDown(header->virtualAddress, K_PAGE_SIZE));
if (success) {
bytesRead = FSFileReadSync(node, (void *) header->virtualAddress, executableOffset + header->fileOffset, header->dataInFile, 0);
if (bytesRead != header->dataInFile) return ES_ERROR_UNSUPPORTED_EXECUTABLE;
}
#else
uintptr_t fileStart = RoundDown(header->virtualAddress, K_PAGE_SIZE);
uintptr_t fileOffset = RoundDown(header->fileOffset, K_PAGE_SIZE);
uintptr_t zeroStart = RoundUp(header->virtualAddress + header->dataInFile, K_PAGE_SIZE);
uintptr_t end = RoundUp(header->virtualAddress + header->segmentSize, K_PAGE_SIZE);
// TODO This doesn't need to be all COPY_ON_WRITE.
// EsPrint("MMMapFile - %x, %x, %x, %x\n", fileStart, fileOffset, zeroStart, end);
success = MMMapFile(thisProcess->vmm, (FSFile *) node,
executableOffset + fileOffset, zeroStart - fileStart,
ES_MAP_OBJECT_COPY_ON_WRITE,
(uint8_t *) fileStart, end - zeroStart);
if (success) {
uint8_t *from = (uint8_t *) header->virtualAddress + header->dataInFile;
EsMemoryZero(from, (uint8_t *) zeroStart - from);
}
#endif
if (!success) return ES_ERROR_INSUFFICIENT_RESOURCES;
} else if (header->type == 7 /* PT_TLS */) {
executable->tlsImageStart = header->virtualAddress;
executable->tlsImageBytes = header->dataInFile;
executable->tlsBytes = header->segmentSize;
KernelLog(LOG_INFO, "ELF", "executable TLS", "Executable requests %d bytes of TLS, with %d bytes from the image at %x.\n",
executable->tlsBytes, executable->tlsImageBytes, executable->tlsImageStart);
}
}
executable->startAddress = header.entry;
return ES_SUCCESS;
}
uintptr_t KFindSymbol(KModule *module, const char *name, size_t nameBytes) {
uint8_t *buffer = module->buffer;
ElfHeader *header = (ElfHeader *) buffer;
for (uintptr_t i = 0; i < header->sectionHeaderEntries; i++) {
ElfSectionHeader *section = (ElfSectionHeader *) (buffer + header->sectionHeaderTable + header->sectionHeaderEntrySize * i);
if (section->type != 2 /* SHT_SYMTAB */) continue;
ElfSectionHeader *strings = (ElfSectionHeader *) (buffer + header->sectionHeaderTable + header->sectionHeaderEntrySize * section->link);
for (uintptr_t i = 0; i < section->size / sizeof(ElfSymbol); i++) {
ElfSymbol *symbol = (ElfSymbol *) (buffer + section->offset + i * sizeof(ElfSymbol));
if (!symbol->name) continue;
if (symbol->sectionIndex == 0 /* SHN_UNDEF */ || (symbol->info >> 4) != 1 /* STB_GLOBAL */) continue;
uint8_t *symbolName = buffer + symbol->name + strings->offset;
if (0 == EsStringCompareRaw(name, nameBytes, (const char *) symbolName, -1)) {
return symbol->value;
}
}
}
return 0;
}
uint8_t *modulesLocation = (uint8_t *) MM_MODULES_START;
KMutex modulesMutex;
uint8_t *AllocateForModule(size_t size) {
KMutexAssertLocked(&modulesMutex);
if ((uintptr_t) modulesLocation + RoundUp(size, K_PAGE_SIZE) > MM_MODULES_START + MM_MODULES_SIZE) return nullptr;
uint8_t *buffer = (uint8_t *) MMStandardAllocate(kernelMMSpace, size, MM_REGION_FIXED, modulesLocation);
if (!buffer) return nullptr;
modulesLocation += RoundUp(size, K_PAGE_SIZE);
return (uint8_t *) buffer;
}
EsError KLoadELFModule(KModule *module) {
KMutexAcquire(&modulesMutex);
EsDefer(KMutexRelease(&modulesMutex));
uint64_t fileFlags = ES_FILE_READ | ES_NODE_FAIL_IF_NOT_FOUND;
KNodeInformation node = FSNodeOpen(module->path, module->pathBytes, fileFlags);
if (node.error != ES_SUCCESS) return node.error;
uint8_t *buffer = AllocateForModule(node.node->directoryEntry->totalSize);
module->buffer = buffer;
{
// TODO Free module buffer on error.
EsDefer(CloseHandleToObject(node.node, KERNEL_OBJECT_NODE, fileFlags));
if (!buffer) {
return ES_ERROR_INSUFFICIENT_RESOURCES;
}
if (node.node->directoryEntry->totalSize != (size_t) FSFileReadSync(node.node, buffer, 0, node.node->directoryEntry->totalSize, 0)) {
return ES_ERROR_UNKNOWN;
}
}
ElfHeader *header = (ElfHeader *) buffer;
uint8_t *sectionStringTable = buffer + ((ElfSectionHeader *) (buffer + header->sectionHeaderTable + header->sectionHeaderEntrySize * header->sectionNameIndex))->offset;
(void) sectionStringTable;
for (uintptr_t i = 0; i < header->sectionHeaderEntries; i++) {
ElfSectionHeader *section = (ElfSectionHeader *) (buffer + header->sectionHeaderTable + header->sectionHeaderEntrySize * i);
if (section->type != 8 /* SHT_NOBITS */ || !section->size) continue;
uint8_t *memory = AllocateForModule(section->size);
if (!memory) return ES_ERROR_INSUFFICIENT_RESOURCES; // TODO Free allocations.
section->offset = memory - buffer;
}
bool unresolvedSymbols = false;
for (uintptr_t i = 0; i < header->sectionHeaderEntries; i++) {
ElfSectionHeader *section = (ElfSectionHeader *) (buffer + header->sectionHeaderTable + header->sectionHeaderEntrySize * i);
if (section->type != 2 /* SHT_SYMTAB */) continue;
// EsPrint("%d: '%z' - symbol table\n", i, sectionStringTable + section->name);
ElfSectionHeader *strings = (ElfSectionHeader *) (buffer + header->sectionHeaderTable + header->sectionHeaderEntrySize * section->link);
for (uintptr_t i = 0; i < section->size / sizeof(ElfSymbol); i++) {
ElfSymbol *symbol = (ElfSymbol *) (buffer + section->offset + i * sizeof(ElfSymbol));
uint8_t *name = buffer + symbol->name + strings->offset;
if (symbol->sectionIndex == 0 /* SHN_UNDEF */) {
if (!symbol->name) continue;
// TODO Check that EsCStringLength stays within bounds.
void *address = module->resolveSymbol((const char *) name, EsCStringLength((const char *) name));
if (!address) {
unresolvedSymbols = true;
} else {
symbol->value = (uintptr_t) address;
}
} else if (symbol->sectionIndex < header->sectionHeaderEntries) {
ElfSectionHeader *section = (ElfSectionHeader *) (buffer + header->sectionHeaderTable + header->sectionHeaderEntrySize * symbol->sectionIndex);
symbol->value += (uintptr_t) buffer + section->offset;
}
// EsPrint("'%z' -> %x\n", name, symbol->value);
}
}
if (unresolvedSymbols) {
return ES_ERROR_COULD_NOT_RESOLVE_SYMBOL;
}
for (uintptr_t i = 0; i < header->sectionHeaderEntries; i++) {
ElfSectionHeader *section = (ElfSectionHeader *) (buffer + header->sectionHeaderTable + header->sectionHeaderEntrySize * i);
if (section->type != 4 /* SHT_RELA */) continue;
ElfSectionHeader *target = (ElfSectionHeader *) (buffer + header->sectionHeaderTable + header->sectionHeaderEntrySize * section->info);
ElfSectionHeader *symbols = (ElfSectionHeader *) (buffer + header->sectionHeaderTable + header->sectionHeaderEntrySize * section->link);
ElfSectionHeader *strings = (ElfSectionHeader *) (buffer + header->sectionHeaderTable + header->sectionHeaderEntrySize * symbols->link);
(void) strings;
// EsPrint("%d: '%z' - relocation table (for %x)\n", i, sectionStringTable + section->name, target->offset);
for (uintptr_t i = 0; i < section->size / sizeof(ElfRelocation); i++) {
ElfRelocation *relocation = (ElfRelocation *) (buffer + section->offset + i * sizeof(ElfRelocation));
ElfSymbol *symbol = (ElfSymbol *) (buffer + symbols->offset + (relocation->info >> 32) * sizeof(ElfSymbol));
uintptr_t offset = relocation->offset + target->offset, type = relocation->info & 0xFF;
// EsPrint("\t%d: %z (%x), %d, %x, %x\n", i, buffer + symbol->name + strings->offset, symbol->value, type, offset, relocation->addend);
uintptr_t result = symbol->value + relocation->addend;
#ifdef ARCH_X86_64
if (type == 0) {}
else if (type == 10 /* R_X86_64_32 */) *((uint32_t *) (buffer + offset)) = result;
else if (type == 11 /* R_X86_64_32S */) *((uint32_t *) (buffer + offset)) = result;
else if (type == 1 /* R_X86_64_64 */) *((uint64_t *) (buffer + offset)) = result;
else if (type == 2 /* R_X86_64_PC32 */) *((uint32_t *) (buffer + offset)) = result - ((uint64_t) buffer + offset);
else if (type == 24 /* R_X86_64_PC64 */) *((uint64_t *) (buffer + offset)) = result - ((uint64_t) buffer + offset);
else if (type == 4 /* R_X86_64_PLT32 */) *((uint32_t *) (buffer + offset)) = result - ((uint64_t) buffer + offset);
#endif
else return ES_ERROR_UNSUPPORTED_FEATURE;
}
}
KGetKernelVersionCallback getVersion = (KGetKernelVersionCallback) KFindSymbol(module, EsLiteral("GetKernelVersion"));
if (!getVersion || getVersion() != KERNEL_VERSION) {
KernelLog(LOG_ERROR, "Modules", "invalid module kernel version",
"KLoadELFModule - Attempted to load module '%s' for invalid kernel version.\n", module->pathBytes, module->path);
return ES_ERROR_UNSUPPORTED_FEATURE;
}
return ES_SUCCESS;
}
#endif

1916
kernel/files.cpp Normal file

File diff suppressed because it is too large Load Diff

562
kernel/graphics.cpp Normal file
View File

@ -0,0 +1,562 @@
#ifndef IMPLEMENTATION
struct Surface : EsPaintTarget {
bool Resize(size_t newResX, size_t newResY, uint32_t clearColor = 0, bool copyOldBits = false);
void Copy(Surface *source, EsPoint destinationPoint, EsRectangle sourceRegion, bool addToModifiedRegion);
void Draw(Surface *source, EsRectangle destinationRegion, int sourceX, int sourceY, uint16_t alpha);
void BlendWindow(Surface *source, EsPoint destinationPoint, EsRectangle sourceRegion, int material, uint8_t alpha, EsRectangle materialRegion);
void Blur(EsRectangle region, EsRectangle clip);
void SetBits(K_USER_BUFFER const void *bits, uintptr_t stride, EsRectangle region);
void Scroll(EsRectangle region, ptrdiff_t delta, bool vertical);
EsRectangle modifiedRegion;
};
struct Graphics {
KGraphicsTarget *target;
size_t width, height;
Surface frameBuffer;
bool debuggerActive;
size_t totalSurfaceBytes;
};
void GraphicsUpdateScreen(K_USER_BUFFER void *bits = nullptr, EsRectangle *bounds = nullptr, uintptr_t stride = 0);
Graphics graphics;
#else
void GraphicsUpdateScreen(K_USER_BUFFER void *bits, EsRectangle *bounds, uintptr_t bitsStride) {
KMutexAssertLocked(&windowManager.mutex);
if (windowManager.resizeWindow && windowManager.resizeStartTimeStampMs + RESIZE_FLICKER_TIMEOUT_MS > KGetTimeInMs()
&& !windowManager.inspectorWindowCount /* HACK see note in the SET_BITS syscall */) {
return;
}
if (bounds && (Width(*bounds) <= 0 || Height(*bounds) <= 0)) {
return;
}
int cursorX = windowManager.cursorX + windowManager.cursorImageOffsetX - (bounds ? bounds->l : 0);
int cursorY = windowManager.cursorY + windowManager.cursorImageOffsetY - (bounds ? bounds->t : 0);
Surface *sourceSurface;
Surface _sourceSurface;
EsRectangle _bounds;
if (bits) {
sourceSurface = &_sourceSurface;
EsMemoryZero(sourceSurface, sizeof(Surface));
sourceSurface->bits = bits;
sourceSurface->width = Width(*bounds);
sourceSurface->height = Height(*bounds);
sourceSurface->stride = bitsStride;
} else {
sourceSurface = &graphics.frameBuffer;
_bounds = ES_RECT_4(0, sourceSurface->width, 0, sourceSurface->height);
bounds = &_bounds;
}
EsRectangle cursorBounds = ES_RECT_4(cursorX, cursorX + windowManager.cursorSwap.width, cursorY, cursorY + windowManager.cursorSwap.height);
EsRectangleClip(ES_RECT_4(0, Width(*bounds), 0, Height(*bounds)), cursorBounds, &cursorBounds);
windowManager.cursorSwap.Copy(sourceSurface, ES_POINT(0, 0), cursorBounds, true);
windowManager.changedCursorImage = false;
int cursorImageWidth = windowManager.cursorSurface.width, cursorImageHeight = windowManager.cursorSurface.height;
sourceSurface->Draw(&windowManager.cursorSurface, ES_RECT_4(cursorX, cursorX + cursorImageWidth, cursorY, cursorY + cursorImageHeight), 0, 0,
windowManager.cursorXOR ? ES_DRAW_BITMAP_XOR : 0xFF);
if (bits) {
graphics.target->updateScreen((K_USER_BUFFER const uint8_t *) bits,
sourceSurface->width, sourceSurface->height,
sourceSurface->stride, bounds->l, bounds->t);
} else {
if (Width(sourceSurface->modifiedRegion) > 0 && Height(sourceSurface->modifiedRegion) > 0) {
uint8_t *bits = (uint8_t *) sourceSurface->bits
+ sourceSurface->modifiedRegion.l * 4
+ sourceSurface->modifiedRegion.t * sourceSurface->stride;
graphics.target->updateScreen(bits, Width(sourceSurface->modifiedRegion), Height(sourceSurface->modifiedRegion),
sourceSurface->width * 4, sourceSurface->modifiedRegion.l, sourceSurface->modifiedRegion.t);
sourceSurface->modifiedRegion = { (int32_t) graphics.width, 0, (int32_t) graphics.height, 0 };
}
}
sourceSurface->Copy(&windowManager.cursorSwap, ES_POINT(cursorBounds.l, cursorBounds.t), ES_RECT_4(0, Width(cursorBounds), 0, Height(cursorBounds)), true);
}
bool KGraphicsIsTargetRegistered() {
return graphics.target ? true : false;
}
void KRegisterGraphicsTarget(KGraphicsTarget *target) {
// TODO Locking.
if (graphics.target) return;
graphics.target = target;
graphics.width = target->screenWidth;
graphics.height = target->screenHeight;
graphics.frameBuffer.Resize(graphics.width, graphics.height);
windowManager.Initialise();
EsMessage m;
EsMemoryZero(&m, sizeof(EsMessage));
m.type = ES_MSG_SET_SCREEN_RESOLUTION;
desktopProcess->messageQueue.SendMessage(nullptr, &m);
}
bool Surface::Resize(size_t newResX, size_t newResY, uint32_t clearColor, bool copyOldBits) {
// Check the surface is within our working size limits.
if (!newResX || !newResY || newResX >= 32767 || newResY >= 32767) {
return false;
}
if (width == newResX && height == newResY) {
return true;
}
uint8_t *newBits = (uint8_t *) EsHeapAllocate(newResX * newResY * 4, !copyOldBits, K_PAGED);
if (!newBits) {
return false;
}
int oldWidth = width, oldHeight = height, oldStride = stride;
void *oldBits = bits;
width = newResX, height = newResY, bits = newBits;
stride = newResX * 4;
EsPainter painter;
painter.clip = ES_RECT_4(0, width, 0, height);
painter.target = this;
if (copyOldBits) {
EsDrawBitmap(&painter, ES_RECT_4(0, oldWidth, 0, oldHeight), (uint32_t *) oldBits, oldStride, ES_DRAW_BITMAP_OPAQUE);
if (clearColor) {
EsDrawBlock(&painter, ES_RECT_4(oldWidth, width, 0, height), clearColor);
EsDrawBlock(&painter, ES_RECT_4(0, oldWidth, oldHeight, height), clearColor);
} else {
EsDrawClear(&painter, ES_RECT_4(oldWidth, width, 0, height));
EsDrawClear(&painter, ES_RECT_4(0, oldWidth, oldHeight, height));
}
}
EsHeapFree(oldBits, 0, K_PAGED);
__sync_fetch_and_add(&graphics.totalSurfaceBytes, newResX * newResY * 4 - oldWidth * oldHeight * 4);
return true;
}
void Surface::Copy(Surface *source, EsPoint destinationPoint, EsRectangle sourceRegion, bool addToModifiedRegion) {
EsRectangle destinationRegion = ES_RECT_4(destinationPoint.x, destinationPoint.x + Width(sourceRegion),
destinationPoint.y, destinationPoint.y + Height(sourceRegion));
if (addToModifiedRegion) {
modifiedRegion = EsRectangleBounding(destinationRegion, modifiedRegion);
EsRectangleClip(modifiedRegion, ES_RECT_4(0, width, 0, height), &modifiedRegion);
}
EsPainter painter;
painter.clip = ES_RECT_4(0, width, 0, height);
painter.target = this;
uint8_t *sourceBits = (uint8_t *) source->bits + source->stride * sourceRegion.t + 4 * sourceRegion.l;
EsDrawBitmap(&painter, destinationRegion, (uint32_t *) sourceBits, source->stride, ES_DRAW_BITMAP_OPAQUE);
}
void Surface::SetBits(K_USER_BUFFER const void *_bits, uintptr_t sourceStride, EsRectangle bounds) {
if (Width(bounds) < 0 || Height(bounds) < 0 || bounds.l < 0 || bounds.t < 0 || bounds.r > (int32_t) width || bounds.b > (int32_t) height) {
KernelPanic("Surface::SetBits - Invalid bounds %R for surface %x.\n", bounds, this);
}
if (Width(bounds) == 0 || Height(bounds) == 0) {
return;
}
modifiedRegion = EsRectangleBounding(bounds, modifiedRegion);
uint32_t *rowStart = (uint32_t *) bits + bounds.l + bounds.t * stride / 4;
K_USER_BUFFER const uint32_t *sourceRowStart = (K_USER_BUFFER const uint32_t *) _bits;
for (uintptr_t i = bounds.t; i < (uintptr_t) bounds.b; i++, rowStart += stride / 4, sourceRowStart += sourceStride / 4) {
size_t count = Width(bounds);
uint32_t *destination = rowStart;
K_USER_BUFFER const uint32_t *bits = sourceRowStart;
do {
*destination = *bits;
destination++, bits++, count--;
} while (count);
}
}
void Surface::Scroll(EsRectangle region, ptrdiff_t delta, bool vertical) {
if (vertical) {
if (delta > 0) {
for (intptr_t i = region.t; i < region.b; i++) {
for (intptr_t j = region.l; j < region.r; j++) {
((uint32_t *) bits)[j + (i - delta) * stride / 4] = ((uint32_t *) bits)[j + i * stride / 4];
}
}
} else {
for (intptr_t i = region.b - 1; i >= region.t; i--) {
for (intptr_t j = region.l; j < region.r; j++) {
((uint32_t *) bits)[j + (i - delta) * stride / 4] = ((uint32_t *) bits)[j + i * stride / 4];
}
}
}
} else {
if (delta > 0) {
for (intptr_t i = region.t; i < region.b; i++) {
for (intptr_t j = region.l; j < region.r; j++) {
((uint32_t *) bits)[j - delta + i * stride / 4] = ((uint32_t *) bits)[j + i * stride / 4];
}
}
} else {
// TODO.
}
}
}
#define C0(p) ((p & 0x000000FF) >> 0x00)
#define C1(p) ((p & 0x0000FF00) >> 0x08)
#define C2(p) ((p & 0x00FF0000) >> 0x10)
#define C3(p) ((p & 0xFF000000) >> 0x18)
__attribute__((optimize("-O2")))
void BlurRegionOfImage(uint32_t *image, int width, int height, int stride, uint16_t *kernel, uintptr_t repeat) {
if (width <= 3 || height <= 3) {
return;
}
for (int y = 0; y < height; y++) {
for (uintptr_t i = 0; i < repeat; i++) {
uint32_t *start = image + stride * y;
uint32_t a = start[0], b = start[0], c = start[0], d = start[0], e = start[1], f = start[2], g = 0;
uint32_t *u = start, *v = start + 3;
for (int i = 0; i < width; i++, u++, v++) {
if (i + 3 < width) g = *v;
*u = (((C0(a) * kernel[0] + C0(b) * kernel[1] + C0(c) * kernel[2] + C0(d) * kernel[3] + C0(e)
* kernel[4] + C0(f) * kernel[5] + C0(g) * kernel[6]) >> 8) << 0x00)
+ (((C1(a) * kernel[0] + C1(b) * kernel[1] + C1(c) * kernel[2] + C1(d) * kernel[3] + C1(e)
* kernel[4] + C1(f) * kernel[5] + C1(g) * kernel[6]) >> 8) << 0x08)
+ (((C2(a) * kernel[0] + C2(b) * kernel[1] + C2(c) * kernel[2] + C2(d) * kernel[3] + C2(e)
* kernel[4] + C2(f) * kernel[5] + C2(g) * kernel[6]) >> 8) << 0x10)
+ (C3(d) << 0x18);
a = b, b = c, c = d, d = e, e = f, f = g;
}
}
}
for (int x = 0; x < width; x++) {
for (uintptr_t i = 0; i < repeat; i++) {
uint32_t *start = image + x;
uint32_t a = start[0], b = start[0], c = start[0], d = start[0], e = start[stride], f = start[stride * 2], g = 0;
uint32_t *u = start, *v = start + 3 * stride;
for (int i = 0; i < height; i++, u += stride, v += stride) {
if (i + 3 < height) g = *v;
*u = (((C0(a) * kernel[0] + C0(b) * kernel[1] + C0(c) * kernel[2] + C0(d) * kernel[3] + C0(e)
* kernel[4] + C0(f) * kernel[5] + C0(g) * kernel[6]) >> 8) << 0x00)
+ (((C1(a) * kernel[0] + C1(b) * kernel[1] + C1(c) * kernel[2] + C1(d) * kernel[3] + C1(e)
* kernel[4] + C1(f) * kernel[5] + C1(g) * kernel[6]) >> 8) << 0x08)
+ (((C2(a) * kernel[0] + C2(b) * kernel[1] + C2(c) * kernel[2] + C2(d) * kernel[3] + C2(e)
* kernel[4] + C2(f) * kernel[5] + C2(g) * kernel[6]) >> 8) << 0x10)
+ (C3(d) << 0x18);
a = b, b = c, c = d, d = e, e = f, f = g;
}
}
}
}
__attribute__((optimize("-O2")))
void BlurRegionOfImage(uint32_t *image, int width, int height, int stride, uintptr_t repeat) {
if (width <= 3 || height <= 3) {
return;
}
for (uintptr_t i = 0; i < repeat; i++) {
uint32_t *start = image;
for (int y = 0; y < height; y++) {
uint32_t a = start[0], b = start[0], c = start[0], d = start[0], e = start[1], f = start[2], g = 0;
uint32_t *u = start, *v = start + 3;
for (int i = 0; i < width; i++, u++, v++) {
if (i + 3 < width) g = *v;
*u = (((C0(a) * 0x07 + C0(b) * 0x1A + C0(c) * 0x38 + C0(d) * 0x49 + C0(e) * 0x38 + C0(f) * 0x1A + C0(g) * 0x07) >> 8) << 0x00)
+ (((C1(a) * 0x07 + C1(b) * 0x1A + C1(c) * 0x38 + C1(d) * 0x49 + C1(e) * 0x38 + C1(f) * 0x1A + C1(g) * 0x07) >> 8) << 0x08)
+ (((C2(a) * 0x07 + C2(b) * 0x1A + C2(c) * 0x38 + C2(d) * 0x49 + C2(e) * 0x38 + C2(f) * 0x1A + C2(g) * 0x07) >> 8) << 0x10)
+ (C3(d) << 0x18);
a = b, b = c, c = d, d = e, e = f, f = g;
}
start += stride;
}
start = image;
for (int x = 0; x < width; x++) {
uint32_t a = start[0], b = start[0], c = start[0], d = start[0], e = start[stride], f = start[stride * 2], g = 0;
uint32_t *u = start, *v = start + 3 * stride;
for (int i = 0; i < height; i++, u += stride, v += stride) {
if (i + 3 < height) g = *v;
*u = (((C0(a) * 0x07 + C0(b) * 0x1A + C0(c) * 0x38 + C0(d) * 0x49 + C0(e) * 0x38 + C0(f) * 0x1A + C0(g) * 0x07) >> 8) << 0x00)
+ (((C1(a) * 0x07 + C1(b) * 0x1A + C1(c) * 0x38 + C1(d) * 0x49 + C1(e) * 0x38 + C1(f) * 0x1A + C1(g) * 0x07) >> 8) << 0x08)
+ (((C2(a) * 0x07 + C2(b) * 0x1A + C2(c) * 0x38 + C2(d) * 0x49 + C2(e) * 0x38 + C2(f) * 0x1A + C2(g) * 0x07) >> 8) << 0x10)
+ (C3(d) << 0x18);
a = b, b = c, c = d, d = e, e = f, f = g;
}
start++;
}
}
}
void Surface::Blur(EsRectangle region, EsRectangle clip) {
#if 1
if (!EsRectangleClip(region, ES_RECT_4(0, width, 0, height), &region)) {
return;
}
if (!EsRectangleClip(region, clip, &region)) {
return;
}
BlurRegionOfImage((uint32_t *) ((uint8_t *) bits + region.l * 4 + region.t * stride), Width(region), Height(region), width, 1);
#else
EsPainter painter;
painter.clip = ES_RECT_4(0, width, 0, height);
painter.target = this;
EsDrawInvert(&painter, EsRectangleIntersection(region, clip));
#endif
}
__attribute__((optimize("-O2")))
void Surface::BlendWindow(Surface *source, EsPoint destinationPoint, EsRectangle sourceRegion, int material, uint8_t alpha, EsRectangle materialRegion) {
if (destinationPoint.x < 0) { sourceRegion.l -= destinationPoint.x; destinationPoint.x = 0; }
if (destinationPoint.y < 0) { sourceRegion.t -= destinationPoint.y; destinationPoint.y = 0; }
if (destinationPoint.x + sourceRegion.r - sourceRegion.l >= (int) width) sourceRegion.r -= destinationPoint.x + sourceRegion.r - sourceRegion.l - width;
if (destinationPoint.y + sourceRegion.b - sourceRegion.t >= (int) height) sourceRegion.b -= destinationPoint.y + sourceRegion.b - sourceRegion.t - height;
if (sourceRegion.r > (int) source->width) sourceRegion.r = source->width;
if (sourceRegion.b > (int) source->height) sourceRegion.b = source->height;
if (sourceRegion.r <= sourceRegion.l) return;
if (sourceRegion.b <= sourceRegion.t) return;
if (sourceRegion.l < 0) return;
if (sourceRegion.t < 0) return;
EsRectangle destinationRegion = ES_RECT_4(destinationPoint.x, destinationPoint.x + Width(sourceRegion),
destinationPoint.y, destinationPoint.y + Height(sourceRegion));
modifiedRegion = EsRectangleBounding(destinationRegion, modifiedRegion);
if (material == BLEND_WINDOW_MATERIAL_GLASS || material == BLEND_WINDOW_MATERIAL_LIGHT_BLUR) {
int repeat = material == BLEND_WINDOW_MATERIAL_GLASS ? 3 : 1;
#ifndef SIMPLE_GRAPHICS
if (alpha == 0xFF) {
BlurRegionOfImage((uint32_t *) bits + materialRegion.l + materialRegion.t * width,
Width(materialRegion), Height(materialRegion), width, repeat);
} else {
uint16_t kernel[] = { 0x07, 0x1A, 0x38, 0, 0x38, 0x1A, 0x07 };
uint16_t sum = 0;
for (uintptr_t i = 0; i < 7; i++) {
kernel[i] = kernel[i] * alpha / 0xFF;
sum += kernel[i];
}
kernel[3] = 0xFF - sum;
BlurRegionOfImage((uint32_t *) bits + materialRegion.l + materialRegion.t * width,
Width(materialRegion), Height(materialRegion), width, kernel, repeat);
}
#else
(void) materialRegion;
(void) alpha;
#endif
}
intptr_t y = sourceRegion.t;
uint8_t *destinationPixel = (uint8_t *) bits + destinationPoint.y * stride + destinationPoint.x * 4;
uint8_t *sourcePixel = (uint8_t *) source->bits + sourceRegion.t * source->stride + sourceRegion.l * 4;
#ifndef SIMPLE_GRAPHICS
__m128i constantAlphaMask = _mm_set1_epi32(0xFF000000);
__m128i constantAlpha = _mm_set1_epi32(alpha);
__m128i constant255 = _mm_set1_epi32(0xFF);
#endif
while (y < sourceRegion.b) {
size_t countX = sourceRegion.r - sourceRegion.l;
uint8_t *a = destinationPixel, *b = sourcePixel;
while (countX >= 4) {
__m128i sourceValue = _mm_loadu_si128((__m128i *) sourcePixel);
#ifndef SIMPLE_GRAPHICS
__m128i destinationValue = _mm_loadu_si128((__m128i *) destinationPixel);
if (alpha != 0xFF) {
sourceValue = _mm_or_si128(_mm_andnot_si128(constantAlphaMask, sourceValue),
_mm_and_si128(_mm_slli_epi32(_mm_mullo_epi16(_mm_srli_epi32(sourceValue, 24), constantAlpha), 16), constantAlphaMask));
}
__m128i alpha = _mm_srli_epi32(sourceValue, 24);
__m128i red = _mm_mullo_epi16(_mm_and_si128(_mm_srli_epi32(sourceValue, 0), constant255), alpha);
__m128i green = _mm_mullo_epi16(_mm_and_si128(_mm_srli_epi32(sourceValue, 8), constant255), alpha);
__m128i blue = _mm_mullo_epi16(_mm_and_si128(_mm_srli_epi32(sourceValue, 16), constant255), alpha);
alpha = _mm_sub_epi32(constant255, alpha);
red = _mm_srli_epi32(_mm_add_epi32(red, _mm_mullo_epi16(_mm_and_si128(_mm_srli_epi32(destinationValue, 0), constant255), alpha)), 8);
green = _mm_srli_epi32(_mm_add_epi32(green, _mm_mullo_epi16(_mm_and_si128(_mm_srli_epi32(destinationValue, 8), constant255), alpha)), 8);
blue = _mm_srli_epi32(_mm_add_epi32(blue, _mm_mullo_epi16(_mm_and_si128(_mm_srli_epi32(destinationValue, 16), constant255), alpha)), 8);
sourceValue = _mm_or_si128(_mm_slli_epi32(red, 0), _mm_or_si128(_mm_slli_epi32(green, 8), _mm_slli_epi32(blue, 16)));
#endif
_mm_storeu_si128((__m128i *) destinationPixel, sourceValue);
destinationPixel += 16;
sourcePixel += 16;
countX -= 4;
}
while (countX >= 1) {
uint32_t modified = *(uint32_t *) sourcePixel;
#ifndef SIMPLE_GRAPHICS
uint32_t original = *(uint32_t *) destinationPixel;
if (alpha != 0xFF) modified = (modified & 0xFFFFFF) | (((((modified & 0xFF000000) >> 24) * alpha) << 16) & 0xFF000000);
uint32_t m1 = (modified & 0xFF000000) >> 24;
uint32_t m2 = 255 - m1;
uint32_t r2 = m2 * (original & 0x00FF00FF);
uint32_t g2 = m2 * (original & 0x0000FF00);
uint32_t r1 = m1 * (modified & 0x00FF00FF);
uint32_t g1 = m1 * (modified & 0x0000FF00);
uint32_t result = (0x0000FF00 & ((g1 + g2) >> 8)) | (0x00FF00FF & ((r1 + r2) >> 8));
#else
uint32_t result = modified;
#endif
*(uint32_t *) destinationPixel = result;
destinationPixel += 4;
sourcePixel += 4;
countX -= 1;
}
y++;
destinationPixel = a + stride;
sourcePixel = b + source->stride;
}
}
void Surface::Draw(Surface *source, EsRectangle destinationRegion, int sourceX, int sourceY, uint16_t alpha) {
modifiedRegion = EsRectangleBounding(destinationRegion, modifiedRegion);
EsRectangleClip(modifiedRegion, ES_RECT_4(0, width, 0, height), &modifiedRegion);
EsPainter painter;
painter.clip = ES_RECT_4(0, width, 0, height);
painter.target = this;
uint8_t *sourceBits = (uint8_t *) source->bits + source->stride * sourceY + 4 * sourceX;
EsDrawBitmap(&painter, destinationRegion, (uint32_t *) sourceBits, source->stride, alpha);
}
void GraphicsUpdateScreen32(K_USER_BUFFER const uint8_t *_source, uint32_t sourceWidth, uint32_t sourceHeight, uint32_t sourceStride,
uint32_t destinationX, uint32_t destinationY,
uint32_t screenWidth, uint32_t screenHeight, uint32_t stride, volatile uint8_t *pixel) {
uint32_t *destinationRowStart = (uint32_t *) (pixel + destinationX * 4 + destinationY * stride);
const uint32_t *sourceRowStart = (const uint32_t *) _source;
if (destinationX > screenWidth || sourceWidth > screenWidth - destinationX
|| destinationY > screenHeight || sourceHeight > screenHeight - destinationY) {
KernelPanic("GraphicsUpdateScreen32 - Update region outside graphics target bounds.\n");
}
for (uintptr_t y = 0; y < sourceHeight; y++, destinationRowStart += stride / 4, sourceRowStart += sourceStride / 4) {
uint32_t *destination = destinationRowStart;
const uint32_t *source = sourceRowStart;
for (uintptr_t x = 0; x < sourceWidth; x++) {
*destination = *source;
destination++, source++;
}
}
}
void GraphicsUpdateScreen24(K_USER_BUFFER const uint8_t *_source, uint32_t sourceWidth, uint32_t sourceHeight, uint32_t sourceStride,
uint32_t destinationX, uint32_t destinationY,
uint32_t screenWidth, uint32_t screenHeight, uint32_t stride, volatile uint8_t *pixel) {
uint8_t *destinationRowStart = (uint8_t *) (pixel + destinationX * 3 + destinationY * stride);
const uint8_t *sourceRowStart = _source;
if (destinationX > screenWidth || sourceWidth > screenWidth - destinationX
|| destinationY > screenHeight || sourceHeight > screenHeight - destinationY) {
KernelPanic("GraphicsUpdateScreen32 - Update region outside graphics target bounds.\n");
}
for (uintptr_t y = 0; y < sourceHeight; y++, destinationRowStart += stride, sourceRowStart += sourceStride) {
uint8_t *destination = destinationRowStart;
const uint8_t *source = sourceRowStart;
for (uintptr_t x = 0; x < sourceWidth; x++) {
*destination++ = *source++;
*destination++ = *source++;
*destination++ = *source++;
source++;
}
}
}
void GraphicsDebugPutBlock32(uintptr_t x, uintptr_t y, bool toggle,
unsigned screenWidth, unsigned screenHeight, unsigned stride, volatile uint8_t *linearBuffer) {
(void) screenWidth;
(void) screenHeight;
if (toggle) {
linearBuffer[y * stride + x * 4 + 0] += 0x4C;
linearBuffer[y * stride + x * 4 + 1] += 0x4C;
linearBuffer[y * stride + x * 4 + 2] += 0x4C;
} else {
linearBuffer[y * stride + x * 4 + 0] = 0xFF;
linearBuffer[y * stride + x * 4 + 1] = 0xFF;
linearBuffer[y * stride + x * 4 + 2] = 0xFF;
}
linearBuffer[(y + 1) * stride + (x + 1) * 4 + 0] = 0;
linearBuffer[(y + 1) * stride + (x + 1) * 4 + 1] = 0;
linearBuffer[(y + 1) * stride + (x + 1) * 4 + 2] = 0;
}
void GraphicsDebugClearScreen32(unsigned screenWidth, unsigned screenHeight, unsigned stride, volatile uint8_t *linearBuffer) {
for (uintptr_t i = 0; i < screenHeight; i++) {
for (uintptr_t j = 0; j < screenWidth * 4; j += 4) {
if (graphics.debuggerActive) {
linearBuffer[i * stride + j + 2] = 0x18;
linearBuffer[i * stride + j + 1] = 0x7E;
linearBuffer[i * stride + j + 0] = 0xCF;
} else {
linearBuffer[i * stride + j + 2] >>= 1;
linearBuffer[i * stride + j + 1] >>= 1;
linearBuffer[i * stride + j + 0] >>= 1;
}
}
}
}
#undef C0
#undef C1
#undef C2
#undef C3
#endif

194
kernel/kernel.h Normal file
View File

@ -0,0 +1,194 @@
#ifndef IMPLEMENTATION
#define K_IN_CORE_KERNEL
#define K_PRIVATE
#include "module.h"
//////////////////////////////////
// TODO Determine the best values for these constants.
// Wait 1 second before running the write behind thread. (Ignored if MM_AVAILABLE_PAGES() is below the low threshold.)
#define CC_WAIT_FOR_WRITE_BEHIND (1000)
// Describes the virtual memory covering a section of a file.
#define CC_ACTIVE_SECTION_SIZE ((EsFileOffset) 262144)
// Maximum number of active sections on the modified list. If exceeded, writers will wait for it to drop before retrying.
#define CC_MAX_MODIFIED (33554432 / CC_ACTIVE_SECTION_SIZE)
// The size of the kernel's address space used for mapping active sections.
#if defined(ARCH_32)
#define CC_SECTION_BYTES (ClampIntptr(0, 64L * 1024 * 1024, pmm.commitFixedLimit * K_PAGE_SIZE / 4))
#elif defined(ARCH_64)
#define CC_SECTION_BYTES (ClampIntptr(0, 1024L * 1024 * 1024, pmm.commitFixedLimit * K_PAGE_SIZE / 4))
#endif
// When we reach a critical number of pages, FIXED allocations start failing,
// and page faults are blocked, unless you are on a page generating thread (the modified page writer or the balancer).
#define MM_CRITICAL_AVAILABLE_PAGES_THRESHOLD (1048576 / K_PAGE_SIZE)
// The number of pages at which balancing starts.
#define MM_LOW_AVAILABLE_PAGES_THRESHOLD (16777216 / K_PAGE_SIZE)
// The number of pages past MM_LOW_AVAILABLE_PAGES_THRESHOLD to aim for when balancing.
#define MM_PAGES_TO_FIND_BALANCE (4194304 / K_PAGE_SIZE)
// The number of pages in the zero list before signaling the page zeroing thread.
#define MM_ZERO_PAGE_THRESHOLD (16)
// The amount of commit reserved specifically for page generating threads.
#define MM_CRITICAL_REMAINING_COMMIT_THRESHOLD (1048576 / K_PAGE_SIZE)
// The number of objects that are trimmed from a MMObjectCache at a time.
#define MM_OBJECT_CACHE_TRIM_GROUP_COUNT (1024)
// The current target maximum size for the object caches. (This uses the approximate sizes of objects.)
// We want to keep a reasonable amount of commit available at all times,
// since when the kernel is allocating memory it might not be able to wait for the caches to be trimmed without deadlock.
// So, try to keep the commit quota used by the object caches at most half the available space.
#define MM_NON_CACHE_MEMORY_PAGES() (pmm.commitFixed + pmm.commitPageable - pmm.approximateTotalObjectCacheBytes / K_PAGE_SIZE)
#define MM_OBJECT_CACHE_PAGES_MAXIMUM() ((pmm.commitLimit - MM_NON_CACHE_MEMORY_PAGES()) / 2)
#define PHYSICAL_MEMORY_MANIPULATION_REGION_PAGES (16)
#define POOL_CACHE_COUNT (16)
//////////////////////////////////
#include <shared/ini.h>
#define EsAssertionFailure(file, line) KernelPanic("%z:%d - EsAssertionFailure called.\n", file, line)
#ifdef ARCH_X86_64
#include "x86_64.cpp"
#endif
struct AsyncTask {
KAsyncTaskCallback callback;
void *argument;
struct MMSpace *addressSpace;
};
struct CPULocalStorage {
// Must be the first fields; used in __cyg_profile_func_enter.
uintptr_t kernelFunctionCallCount;
struct Thread *currentThread,
*idleThread,
*asyncTaskThread;
struct InterruptContext *panicContext;
bool irqSwitchThread, schedulerReady, inIRQ;
unsigned processorID;
size_t spinlockCount;
ArchCPU *archCPU;
// TODO Have separate interrupt task threads and system worker threads (with no task limit).
#define MAX_ASYNC_TASKS (256)
volatile AsyncTask asyncTasks[MAX_ASYNC_TASKS];
volatile uint8_t asyncTasksRead, asyncTasksWrite;
};
//////////////////////////////////
void KernelInitialise();
void KernelShutdown(uintptr_t action);
void ArchInitialise();
void ArchShutdown(uintptr_t action);
extern "C" void ArchResetCPU();
extern "C" void ArchSpeakerBeep();
extern "C" void ArchNextTimer(size_t ms); // Receive a TIMER_INTERRUPT in ms ms.
InterruptContext *ArchInitialiseThread(uintptr_t kernelStack, uintptr_t kernelStackSize, struct Thread *thread,
uintptr_t startAddress, uintptr_t argument1, uintptr_t argument2,
bool userland, uintptr_t stack, uintptr_t userStackSize);
uint64_t timeStampTicksPerMs;
EsUniqueIdentifier installationID; // The identifier of this OS installation, given to us by the bootloader.
struct Process *desktopProcess;
KSpinlock ipiLock;
//////////////////////////////////
#endif
#include <shared/avl_tree.cpp>
#include <shared/bitset.cpp>
#include <shared/range_set.cpp>
#include "memory.cpp"
#ifndef IMPLEMENTATION
#include <shared/heap.cpp>
#include <shared/arena.cpp>
#else
#include <shared/array.cpp>
#endif
#include "objects.cpp"
#include "syscall.cpp"
#include "scheduler.cpp"
#include "synchronisation.cpp"
#include "drivers.cpp"
#include "elf.cpp"
#include "graphics.cpp"
#include "cache.cpp"
#include "files.cpp"
#include "windows.cpp"
#include "networking.cpp"
#ifdef ENABLE_POSIX_SUBSYSTEM
#include "posix.cpp"
#endif
#include "terminal.cpp"
#ifdef IMPLEMENTATION
//////////////////////////////////
extern "C" uint64_t KGetTimeInMs() {
if (!timeStampTicksPerMs) return 0;
return scheduler.timeMs;
}
uint64_t KGetTimeStampTicksPerMs() {
return timeStampTicksPerMs;
}
uint64_t KGetTimeStampTicksPerUs() {
return timeStampTicksPerMs / 1000;
}
bool KInIRQ() {
return GetLocalStorage()->inIRQ;
}
bool KBootedFromEFI() {
extern uint32_t bootloaderID;
return bootloaderID == 2;
}
void KSwitchThreadAfterIRQ() {
GetLocalStorage()->irqSwitchThread = true;
}
EsUniqueIdentifier KGetBootIdentifier() {
return installationID;
}
//////////////////////////////////
#ifdef ARCH_X86_64
#include "x86_64.h"
#include <drivers/acpi.cpp>
#include "x86_64.cpp"
#endif
#endif

29
kernel/main.cpp Normal file
View File

@ -0,0 +1,29 @@
// TODO Prevent Meltdown/Spectre exploits.
// TODO Kernel debugger.
// TODO Passing data to userspace - zeroing padding bits of structures.
// TODO Remove file extensions?
// TODO Thread-local variables for native applications (already working under the POSIX subsystem).
#include "kernel.h"
#define IMPLEMENTATION
#include "kernel.h"
extern "C" void KernelMain() {
scheduler.SpawnProcess(PROCESS_KERNEL); // Spawn the kernel process.
ArchInitialise(); // Start processors and initialise CPULocalStorage.
scheduler.started = true; // Start the pre-emptive scheduler.
// Continues in KernelInitialise.
}
void KernelInitialise() {
desktopProcess = scheduler.SpawnProcess(PROCESS_DESKTOP); // Spawn the desktop process.
DriversInitialise(); // Load the root device.
desktopProcess->Start(EsLiteral(K_DESKTOP_EXECUTABLE)); // Start the desktop process.
}
void KernelShutdown(uintptr_t action) {
scheduler.Shutdown(); // Kill user processes.
FSShutdown(); // Flush file cache and unmount filesystems.
DriversShutdown(); // Inform drivers of shutdown.
ArchShutdown(action); // Power off or restart the computer.
}

2305
kernel/memory.cpp Normal file

File diff suppressed because it is too large Load Diff

1014
kernel/module.h Normal file

File diff suppressed because it is too large Load Diff

2167
kernel/networking.cpp Normal file

File diff suppressed because it is too large Load Diff

701
kernel/objects.cpp Normal file
View File

@ -0,0 +1,701 @@
// TODO Reject opening handles if the handle table has been destroyed!!
#ifndef IMPLEMENTATION
inline KernelObjectType operator|(KernelObjectType a, KernelObjectType b) {
return (KernelObjectType) ((int) a | (int) b);
}
struct Handle {
void *object;
uint32_t flags;
KernelObjectType type;
};
struct ConstantBuffer {
volatile size_t handles;
size_t bytes : 48,
isPaged : 1;
// Data follows.
};
struct Pipe {
#define PIPE_READER (1)
#define PIPE_WRITER (2)
#define PIPE_BUFFER_SIZE (K_PAGE_SIZE)
#define PIPE_CLOSED (0)
volatile char buffer[PIPE_BUFFER_SIZE];
volatile size_t writers, readers;
volatile uintptr_t writePosition, readPosition, unreadData;
KEvent canWrite, canRead;
KMutex mutex;
size_t Access(void *buffer, size_t bytes, bool write, bool userBlockRequest);
};
struct EventSink {
KEvent available;
KSpinlock spinlock; // Take after the scheduler's spinlock.
volatile size_t handles;
uintptr_t queuePosition;
size_t queueCount;
bool overflow, ignoreDuplicates;
EsGeneric queue[ES_MAX_EVENT_SINK_BUFFER_SIZE];
EsError Push(EsGeneric data);
};
struct EventSinkTable {
EventSink *sink;
EsGeneric data;
};
size_t totalHandleCount;
struct HandleTableL2 {
#define HANDLE_TABLE_L2_ENTRIES (256)
Handle t[HANDLE_TABLE_L2_ENTRIES];
};
struct HandleTableL1 {
#define HANDLE_TABLE_L1_ENTRIES (256)
HandleTableL2 *t[HANDLE_TABLE_L1_ENTRIES];
uint16_t u[HANDLE_TABLE_L1_ENTRIES];
};
struct HandleTable {
HandleTableL1 l1r;
KMutex lock;
Process *process;
bool destroyed;
uint32_t handleCount;
EsHandle OpenHandle(void *_object, uint32_t _flags, KernelObjectType _type, EsHandle at = ES_INVALID_HANDLE);
bool CloseHandle(EsHandle handle);
// Resolve the handle if it is valid and return the type in type.
// The initial value of type is used as a mask of expected object types for the handle.
void ResolveHandle(struct KObject *object, EsHandle handle);
void Destroy();
};
struct KObject {
void Initialise(HandleTable *handleTable, EsHandle _handle, KernelObjectType _type);
KObject() { EsMemoryZero(this, sizeof(*this)); }
KObject(Process *process, EsHandle _handle, KernelObjectType _type);
KObject(HandleTable *handleTable, EsHandle _handle, KernelObjectType _type);
~KObject() {
if (!checked && valid) {
KernelPanic("KObject - Object not checked!\n");
}
if (parentObject && close) {
CloseHandleToObject(parentObject, parentType, parentFlags);
}
}
void *object, *parentObject;
uint32_t flags, parentFlags;
KernelObjectType type, parentType;
bool valid, checked, close, softFailure;
};
void InitialiseObjectManager();
#endif
#ifdef IMPLEMENTATION
KObject::KObject(Process *process, EsHandle _handle, KernelObjectType _type) {
EsMemoryZero(this, sizeof(*this));
Initialise(&process->handleTable, _handle, _type);
}
KObject::KObject(HandleTable *handleTable, EsHandle _handle, KernelObjectType _type) {
EsMemoryZero(this, sizeof(*this));
Initialise(handleTable, _handle, _type);
}
void KObject::Initialise(HandleTable *handleTable, EsHandle _handle, KernelObjectType _type) {
type = _type;
handleTable->ResolveHandle(this, _handle);
parentObject = object, parentType = type, parentFlags = flags;
if (!valid) {
KernelLog(LOG_ERROR, "Object Manager", "invalid handle", "KObject::Initialise - Invalid handle %d for type mask %x.\n", _handle, _type);
}
}
// A lock used to change the handle count on several objects.
// TODO Make changing handle count lockless wherever possible?
KMutex objectHandleCountChange;
// TODO Use uint64_t for handle counts, or restrict OpenHandleToObject to some maximum (...but most callers don't check if OpenHandleToObject succeeds).
bool OpenHandleToObject(void *object, KernelObjectType type, uint32_t flags, bool maybeHasNoHandles) {
bool hadNoHandles = false, failed = false;
switch (type) {
case KERNEL_OBJECT_EVENT: {
KMutexAcquire(&objectHandleCountChange);
KEvent *event = (KEvent *) object;
if (!event->handles) hadNoHandles = true;
else event->handles++;
KMutexRelease(&objectHandleCountChange);
} break;
case KERNEL_OBJECT_PROCESS: {
KSpinlockAcquire(&scheduler.lock);
Process *process = (Process *) object;
if (!process->handles) hadNoHandles = true;
else process->handles++; // NOTE Scheduler::OpenProcess and MMBalanceThread also adjust process handles.
KernelLog(LOG_VERBOSE, "Scheduler", "open process handle", "Opened handle to process %d; %d handles.\n", process->id, process->handles);
KSpinlockRelease(&scheduler.lock);
} break;
case KERNEL_OBJECT_THREAD: {
KSpinlockAcquire(&scheduler.lock);
Thread *thread = (Thread *) object;
if (!thread->handles) hadNoHandles = true;
else thread->handles++;
KSpinlockRelease(&scheduler.lock);
} break;
case KERNEL_OBJECT_SHMEM: {
MMSharedRegion *region = (MMSharedRegion *) object;
KMutexAcquire(&region->mutex);
if (!region->handles) hadNoHandles = true;
else region->handles++;
KMutexRelease(&region->mutex);
} break;
case KERNEL_OBJECT_WINDOW: {
// NOTE The handle count of Window object is modified elsewhere.
Window *window = (Window *) object;
hadNoHandles = 0 == __sync_fetch_and_add(&window->handles, 1);
} break;
case KERNEL_OBJECT_EMBEDDED_WINDOW: {
EmbeddedWindow *window = (EmbeddedWindow *) object;
hadNoHandles = 0 == __sync_fetch_and_add(&window->handles, 1);
} break;
case KERNEL_OBJECT_CONSTANT_BUFFER: {
ConstantBuffer *buffer = (ConstantBuffer *) object;
KMutexAcquire(&objectHandleCountChange);
if (!buffer->handles) hadNoHandles = true;
else buffer->handles++;
KMutexRelease(&objectHandleCountChange);
} break;
#ifdef ENABLE_POSIX_SUBSYSTEM
case KERNEL_OBJECT_POSIX_FD: {
POSIXFile *file = (POSIXFile *) object;
KMutexAcquire(&file->mutex);
if (!file->handles) hadNoHandles = true;
else file->handles++;
KMutexRelease(&file->mutex);
} break;
#endif
case KERNEL_OBJECT_NODE: {
failed = ES_SUCCESS != FSNodeOpenHandle((KNode *) object, flags, FS_NODE_OPEN_HANDLE_STANDARD);
} break;
case KERNEL_OBJECT_PIPE: {
Pipe *pipe = (Pipe *) object;
KMutexAcquire(&pipe->mutex);
if (((flags & PIPE_READER) && !pipe->readers)
|| ((flags & PIPE_WRITER) && !pipe->writers)) {
hadNoHandles = true;
} else {
if (flags & PIPE_READER) {
pipe->readers++;
}
if (flags & PIPE_WRITER) {
pipe->writers++;
}
}
KMutexRelease(&pipe->mutex);
} break;
case KERNEL_OBJECT_EVENT_SINK: {
EventSink *sink = (EventSink *) object;
KSpinlockAcquire(&sink->spinlock);
if (!sink->handles) hadNoHandles = true;
else sink->handles++;
KSpinlockRelease(&sink->spinlock);
} break;
case KERNEL_OBJECT_CONNECTION: {
NetConnection *connection = (NetConnection *) object;
hadNoHandles = 0 == __sync_fetch_and_add(&connection->handles, 1);
} break;
default: {
KernelPanic("OpenHandleToObject - Cannot open object of type %x.\n", type);
} break;
}
if (hadNoHandles) {
if (maybeHasNoHandles) {
return false;
} else {
KernelPanic("OpenHandleToObject - Object %x of type %x had no handles.\n", object, type);
}
}
return !failed;
}
void CloseHandleToObject(void *object, KernelObjectType type, uint32_t flags) {
switch (type) {
case KERNEL_OBJECT_PROCESS: {
CloseHandleToProcess(object);
} break;
case KERNEL_OBJECT_THREAD: {
CloseHandleToThread(object);
} break;
case KERNEL_OBJECT_NODE: {
FSNodeCloseHandle((KNode *) object, flags);
} break;
case KERNEL_OBJECT_EVENT: {
KEvent *event = (KEvent *) object;
KMutexAcquire(&objectHandleCountChange);
bool destroy = event->handles == 1;
event->handles--;
KMutexRelease(&objectHandleCountChange);
if (destroy) {
if (event->sinkTable) {
for (uintptr_t i = 0; i < ES_MAX_EVENT_FORWARD_COUNT; i++) {
if (event->sinkTable[i].sink) {
CloseHandleToObject(event->sinkTable[i].sink, KERNEL_OBJECT_EVENT_SINK, 0);
}
}
EsHeapFree(event->sinkTable, sizeof(EventSinkTable) * ES_MAX_EVENT_FORWARD_COUNT, K_FIXED);
}
EsHeapFree(event, sizeof(KEvent), K_FIXED);
}
} break;
case KERNEL_OBJECT_CONSTANT_BUFFER: {
ConstantBuffer *buffer = (ConstantBuffer *) object;
KMutexAcquire(&objectHandleCountChange);
bool destroy = buffer->handles == 1;
buffer->handles--;
KMutexRelease(&objectHandleCountChange);
if (destroy) {
EsHeapFree(object, sizeof(ConstantBuffer) + buffer->bytes, buffer->isPaged ? K_PAGED : K_FIXED);
}
} break;
case KERNEL_OBJECT_SHMEM: {
MMSharedRegion *region = (MMSharedRegion *) object;
KMutexAcquire(&region->mutex);
bool destroy = region->handles == 1;
region->handles--;
KMutexRelease(&region->mutex);
if (destroy) {
MMSharedDestroyRegion(region);
}
} break;
case KERNEL_OBJECT_WINDOW: {
Window *window = (Window *) object;
unsigned previous = __sync_fetch_and_sub(&window->handles, 1);
if (!previous) KernelPanic("CloseHandleToObject - Window %x has no handles.\n", window);
if (previous == 2) {
KEventSet(&windowManager.windowsToCloseEvent, false, true /* maybe already set */);
} else if (previous == 1) {
window->Destroy();
}
} break;
case KERNEL_OBJECT_EMBEDDED_WINDOW: {
EmbeddedWindow *window = (EmbeddedWindow *) object;
unsigned previous = __sync_fetch_and_sub(&window->handles, 1);
if (!previous) KernelPanic("CloseHandleToObject - EmbeddedWindow %x has no handles.\n", window);
if (previous == 2) {
KEventSet(&windowManager.windowsToCloseEvent, false, true /* maybe already set */);
} else if (previous == 1) {
window->Destroy();
}
} break;
#ifdef ENABLE_POSIX_SUBSYSTEM
case KERNEL_OBJECT_POSIX_FD: {
POSIXFile *file = (POSIXFile *) object;
KMutexAcquire(&file->mutex);
file->handles--;
bool destroy = !file->handles;
KMutexRelease(&file->mutex);
if (destroy) {
if (file->type == POSIX_FILE_NORMAL || file->type == POSIX_FILE_DIRECTORY) CloseHandleToObject(file->node, KERNEL_OBJECT_NODE, file->openFlags);
if (file->type == POSIX_FILE_PIPE) CloseHandleToObject(file->pipe, KERNEL_OBJECT_PIPE, file->openFlags);
EsHeapFree(file->path, 0, K_FIXED);
EsHeapFree(file->directoryBuffer, file->directoryBufferLength, K_PAGED);
EsHeapFree(file, sizeof(POSIXFile), K_FIXED);
}
} break;
#endif
case KERNEL_OBJECT_PIPE: {
Pipe *pipe = (Pipe *) object;
KMutexAcquire(&pipe->mutex);
if (flags & PIPE_READER) {
pipe->readers--;
if (!pipe->readers) {
// If there are no more readers, wake up any blocking writers.
KEventSet(&pipe->canWrite, false, true);
}
}
if (flags & PIPE_WRITER) {
pipe->writers--;
if (!pipe->writers) {
// If there are no more writers, wake up any blocking readers.
KEventSet(&pipe->canRead, false, true);
}
}
bool destroy = pipe->readers == 0 && pipe->writers == 0;
KMutexRelease(&pipe->mutex);
if (destroy) {
EsHeapFree(pipe, sizeof(Pipe), K_PAGED);
}
} break;
case KERNEL_OBJECT_EVENT_SINK: {
EventSink *sink = (EventSink *) object;
KSpinlockAcquire(&sink->spinlock);
bool destroy = sink->handles == 1;
sink->handles--;
KSpinlockRelease(&sink->spinlock);
if (destroy) {
EsHeapFree(sink, sizeof(EventSink), K_FIXED);
}
} break;
case KERNEL_OBJECT_CONNECTION: {
NetConnection *connection = (NetConnection *) object;
unsigned previous = __sync_fetch_and_sub(&connection->handles, 1);
if (!previous) KernelPanic("CloseHandleToObject - NetConnection %x has no handles.\n", connection);
if (previous == 1) NetConnectionClose(connection);
} break;
default: {
KernelPanic("CloseHandleToObject - Cannot close object of type %x.\n", type);
} break;
}
}
bool HandleTable::CloseHandle(EsHandle handle) {
if (handle > HANDLE_TABLE_L1_ENTRIES * HANDLE_TABLE_L2_ENTRIES) {
return false;
}
KMutexAcquire(&lock);
HandleTableL1 *l1 = &l1r;
HandleTableL2 *l2 = l1->t[handle / HANDLE_TABLE_L2_ENTRIES];
if (!l2) { KMutexRelease(&lock); return false; }
Handle *_handle = l2->t + (handle % HANDLE_TABLE_L2_ENTRIES);
KernelObjectType type = _handle->type;
uint64_t flags = _handle->flags;
void *object = _handle->object;
if (!object) { KMutexRelease(&lock); return false; }
EsMemoryZero(_handle, sizeof(Handle));
l1->u[handle / HANDLE_TABLE_L2_ENTRIES]--;
handleCount--;
KMutexRelease(&lock);
__sync_fetch_and_sub(&totalHandleCount, 1);
CloseHandleToObject(object, type, flags);
return true;
}
void HandleTable::ResolveHandle(KObject *object, EsHandle handle) {
KernelObjectType requestedType = object->type;
object->type = COULD_NOT_RESOLVE_HANDLE;
// Special handles.
if (handle == ES_CURRENT_THREAD && (requestedType & KERNEL_OBJECT_THREAD)) {
object->type = KERNEL_OBJECT_THREAD, object->valid = true, object->object = GetCurrentThread();
return;
} else if (handle == ES_CURRENT_PROCESS && (requestedType & KERNEL_OBJECT_PROCESS)) {
object->type = KERNEL_OBJECT_PROCESS, object->valid = true, object->object = GetCurrentThread()->process;
return;
} else if (handle == ES_INVALID_HANDLE && (requestedType & KERNEL_OBJECT_NONE)) {
object->type = KERNEL_OBJECT_NONE, object->valid = true;
return;
}
// Check that the handle is within the correct bounds.
if ((!handle) || handle >= HANDLE_TABLE_L1_ENTRIES * HANDLE_TABLE_L2_ENTRIES) {
return;
}
KMutexAcquire(&lock);
EsDefer(KMutexRelease(&lock));
HandleTableL2 *l2 = l1r.t[handle / HANDLE_TABLE_L2_ENTRIES];
if (l2) {
Handle *_handle = l2->t + (handle % HANDLE_TABLE_L2_ENTRIES);
if ((_handle->type & requestedType) && (_handle->object)) {
// Open a handle to the object so that it can't be destroyed while the system call is still using it.
// The handle is closed in the KObject's destructor.
if (OpenHandleToObject(_handle->object, _handle->type, _handle->flags)) {
object->type = _handle->type, object->object = _handle->object, object->flags = _handle->flags;
object->valid = object->close = true;
}
}
}
}
// TODO Switch the order of flags and type, so that the default value of flags can be 0.
EsHandle HandleTable::OpenHandle(void *object, uint32_t flags, KernelObjectType type, EsHandle at) {
KMutexAcquire(&lock);
EsDefer(KMutexRelease(&lock));
Handle handle = {};
handle.object = object;
handle.flags = flags;
handle.type = type;
{
if (destroyed) goto error;
if (!handle.object) {
KernelPanic("HandleTable::OpenHandle - Invalid object.\n");
}
HandleTableL1 *l1 = &l1r;
uintptr_t l1Index = HANDLE_TABLE_L1_ENTRIES;
for (uintptr_t i = 1 /* The first set of handles are reserved. */; i < HANDLE_TABLE_L1_ENTRIES; i++) {
if (at) i = at / HANDLE_TABLE_L2_ENTRIES;
if (l1->u[i] != HANDLE_TABLE_L2_ENTRIES) {
l1->u[i]++;
handleCount++;
l1Index = i;
break;
}
if (at) goto error;
}
if (l1Index == HANDLE_TABLE_L1_ENTRIES) goto error;
if (!l1->t[l1Index]) l1->t[l1Index] = (HandleTableL2 *) EsHeapAllocate(sizeof(HandleTableL2), true, K_FIXED);
HandleTableL2 *l2 = l1->t[l1Index];
uintptr_t l2Index = HANDLE_TABLE_L2_ENTRIES;
for (uintptr_t i = 0; i < HANDLE_TABLE_L2_ENTRIES; i++) {
if (at) i = at % HANDLE_TABLE_L2_ENTRIES;
if (!l2->t[i].object) {
l2Index = i;
break;
}
if (at) goto error;
}
if (l2Index == HANDLE_TABLE_L2_ENTRIES) KernelPanic("HandleTable::OpenHandle - Unexpected lack of free handles.\n");
Handle *_handle = l2->t + l2Index;
*_handle = handle;
__sync_fetch_and_add(&totalHandleCount, 1);
EsHandle index = l2Index + (l1Index * HANDLE_TABLE_L2_ENTRIES);
return index;
}
error:;
// TODO Close the handle to the object with CloseHandleToObject?
return ES_INVALID_HANDLE;
}
void HandleTable::Destroy() {
KMutexAcquire(&lock);
EsDefer(KMutexRelease(&lock));
if (destroyed) {
return;
}
destroyed = true;
HandleTableL1 *l1 = &l1r;
for (uintptr_t i = 0; i < HANDLE_TABLE_L1_ENTRIES; i++) {
if (!l1->u[i]) continue;
for (uintptr_t k = 0; k < HANDLE_TABLE_L2_ENTRIES; k++) {
Handle *handle = &l1->t[i]->t[k];
if (handle->object) CloseHandleToObject(handle->object, handle->type, handle->flags);
}
EsHeapFree(l1->t[i], 0, K_FIXED);
}
}
ConstantBuffer *MakeConstantBuffer(K_USER_BUFFER const void *data, size_t bytes) {
ConstantBuffer *buffer = (ConstantBuffer *) EsHeapAllocate(sizeof(ConstantBuffer) + bytes, false, K_FIXED);
if (!buffer) return nullptr;
EsMemoryZero(buffer, sizeof(ConstantBuffer));
buffer->handles = 1;
buffer->bytes = bytes;
EsMemoryCopy(buffer + 1, data, buffer->bytes);
return buffer;
}
EsHandle MakeConstantBuffer(K_USER_BUFFER const void *data, size_t bytes, Process *process) {
void *object = MakeConstantBuffer(data, bytes);
if (!object) {
return ES_INVALID_HANDLE;
}
EsHandle h = process->handleTable.OpenHandle(object, 0, KERNEL_OBJECT_CONSTANT_BUFFER);
if (h == ES_INVALID_HANDLE) {
CloseHandleToObject(object, KERNEL_OBJECT_CONSTANT_BUFFER, 0);
return ES_INVALID_HANDLE;
}
return h;
}
EsHandle MakeConstantBufferForDesktop(K_USER_BUFFER const void *data, size_t bytes) {
return MakeConstantBuffer(data, bytes, desktopProcess);
}
size_t Pipe::Access(void *_buffer, size_t bytes, bool write, bool user) {
size_t amount = 0;
Thread *currentThread = GetCurrentThread();
// EsPrint("--> %z %d\n", write ? "Write" : "Read", bytes);
while (bytes) {
if (user) currentThread->terminatableState = THREAD_USER_BLOCK_REQUEST;
if (write) {
// Wait until we can write to the pipe.
KEventWait(&canWrite, ES_WAIT_NO_TIMEOUT);
} else {
// Wait until we can read from the pipe.
KEventWait(&canRead, ES_WAIT_NO_TIMEOUT);
}
if (user) {
currentThread->terminatableState = THREAD_IN_SYSCALL;
if (currentThread->terminating) goto done;
}
KMutexAcquire(&mutex);
EsDefer(KMutexRelease(&mutex));
if (write) {
// EsPrint("Write:\n");
size_t spaceAvailable = PIPE_BUFFER_SIZE - unreadData;
size_t toWrite = bytes > spaceAvailable ? spaceAvailable : bytes;
size_t spaceAvailableRight = PIPE_BUFFER_SIZE - writePosition;
size_t toWriteRight = toWrite > spaceAvailableRight ? spaceAvailableRight : toWrite;
size_t toWriteLeft = toWrite - toWriteRight;
// EsPrint("\tunread: %d; wp: %d\n", unreadData, writePosition);
// EsPrint("\t%d, %d, %d, %d, %d\n", spaceAvailable, spaceAvailableRight, toWrite, toWriteRight, toWriteLeft);
EsMemoryCopy((uint8_t *) buffer + writePosition, _buffer, toWriteRight);
EsMemoryCopy((uint8_t *) buffer, (uint8_t *) _buffer + toWriteRight, toWriteLeft);
writePosition += toWrite;
writePosition %= PIPE_BUFFER_SIZE;
unreadData += toWrite;
bytes -= toWrite;
_buffer = (uint8_t *) _buffer + toWrite;
amount += toWrite;
KEventSet(&canRead, false, true);
if (!readers) {
// EsPrint("\tPipe closed\n");
// Nobody is reading from the pipe, so there's no point writing to it.
goto done;
} else if (PIPE_BUFFER_SIZE == unreadData) {
KEventReset(&canWrite);
// EsPrint("\treset canWrite\n");
}
} else {
// EsPrint("Read:\n");
size_t dataAvailable = unreadData;
size_t toRead = bytes > dataAvailable ? dataAvailable : bytes;
size_t spaceAvailableRight = PIPE_BUFFER_SIZE - readPosition;
size_t toReadRight = toRead > spaceAvailableRight ? spaceAvailableRight : toRead;
size_t toReadLeft = toRead - toReadRight;
// EsPrint("\tunread: %d; rp: %d\n", unreadData, readPosition);
// EsPrint("\t%d, %d, %d, %d, %d\n", dataAvailable, spaceAvailableRight, toRead, toReadRight, toReadLeft);
EsMemoryCopy(_buffer, (uint8_t *) buffer + readPosition, toReadRight);
EsMemoryCopy((uint8_t *) _buffer + toReadRight, (uint8_t *) buffer, toReadLeft);
readPosition += toRead;
readPosition %= PIPE_BUFFER_SIZE;
unreadData -= toRead;
bytes -= toRead;
_buffer = (uint8_t *) _buffer + toRead;
amount += toRead;
KEventSet(&canWrite, false, true);
if (!writers) {
// Nobody is writing to the pipe, so there's no point reading from it.
// EsPrint("\tPipe closed\n");
goto done;
} else if (!unreadData) {
KEventReset(&canRead);
}
// Don't block when reading from pipes after the first chunk of data.
// TODO Change this behaviour?
goto done;
}
}
done:;
// EsPrint("<-- %d (%d remaining, %z)\n", amount, bytes, write ? "Write" : "Read");
return amount;
}
#endif

821
kernel/posix.cpp Normal file
View File

@ -0,0 +1,821 @@
#ifndef IMPLEMENTATION
struct POSIXFile {
uint64_t posixFlags; // The flags given in SYS_open.
volatile size_t handles;
char *path; // Resolved path.
EsFileOffset offsetIntoFile;
uint64_t openFlags; // The flags used to open the object.
KMutex mutex;
KNode *node;
Pipe *pipe;
void *directoryBuffer;
size_t directoryBufferLength;
#define POSIX_FILE_TERMINAL (1)
#define POSIX_FILE_NORMAL (2)
#define POSIX_FILE_PIPE (3)
#define POSIX_FILE_ZERO (4)
#define POSIX_FILE_NULL (5)
#define POSIX_FILE_DIRECTORY (6)
int type;
};
namespace POSIX {
uintptr_t DoSyscall(_EsPOSIXSyscall syscall, uintptr_t *userStackPointer);
KMutex forkMutex;
KMutex threadPOSIXDataMutex;
}
struct POSIXThread {
void *forkStack;
size_t forkStackSize;
uintptr_t forkUSP;
Process *forkProcess;
};
#define SYSCALL_BUFFER_POSIX(address, length, index, write) \
MMRegion *_region ## index = MMFindAndPinRegion(currentVMM, (address), (length)); \
if (!_region ## index && !fromKernel) { KernelLog(LOG_ERROR, "POSIX", "EFAULT", "POSIX application EFAULT at %x.\n", address); return -EFAULT; } \
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)) \
{ KernelLog(LOG_ERROR, "POSIX", "EFAULT", "POSIX application EFAULT (2) at %x.\n", address); return -EFAULT; }
#define SYSCALL_BUFFER_ALLOW_NULL_POSIX(address, length, index) \
MMRegion *_region ## index = MMFindAndPinRegion(currentVMM, (address), (length)); \
EsDefer(if (_region ## index) MMUnpinRegion(currentVMM, _region ## index));
#define SYSCALL_HANDLE_POSIX(handle, __object, index) \
KObject _object ## index(handleTable, ConvertStandardInputTo3(handle), KERNEL_OBJECT_POSIX_FD); \
*((void **) &__object) = _object ## index .object; \
if (! _object ## index .valid) return -EBADF; else _object ## index .checked = true; \
#endif
#ifdef IMPLEMENTATION
#define _POSIX_SOURCE
#define _GNU_SOURCE
namespace POSIX {
#include <bits/syscall.h>
#include <bits/alltypes.h>
#include <bits/fcntl.h>
#include <bits/ioctl.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/sysinfo.h>
#include <fcntl.h>
#include <errno.h>
EsHandle ConvertStandardInputTo3(EsHandle handle) {
if (handle) {
return handle;
} else {
return 3;
}
}
intptr_t Read(POSIXFile *file, K_USER_BUFFER void *base, size_t length, bool baseMappedToFile) {
if (!length) return 0;
// EsPrint("Read %d bytes from %x into %x\n", length, file, base);
if (file->type == POSIX_FILE_TERMINAL) {
return 0;
} else if (file->type == POSIX_FILE_PIPE) {
return file->pipe->Access(base, length, false, true);
} else if (file->type == POSIX_FILE_NORMAL) {
// EsPrint("READ '%z' %d/%d\n", file->path, file->offsetIntoFile, length);
KMutexAcquire(&file->mutex);
EsDefer(KMutexRelease(&file->mutex));
size_t count = FSFileReadSync(file->node, (void *) base, file->offsetIntoFile, length, baseMappedToFile ? FS_FILE_ACCESS_USER_BUFFER_MAPPED : 0);
if (ES_CHECK_ERROR(count)) return -EIO;
else { file->offsetIntoFile += count; return count; }
} else if (file->type == POSIX_FILE_ZERO) {
EsMemoryZero(base, length);
return length;
} else if (file->type == POSIX_FILE_NULL) {
return 0;
} else if (file->type == POSIX_FILE_DIRECTORY) {
return 0;
}
return -EACCES;
}
intptr_t Write(POSIXFile *file, K_USER_BUFFER void *base, size_t length, bool baseMappedToFile) {
if (file->posixFlags & O_APPEND) {
// TODO Make this atomic.
file->offsetIntoFile = file->node->directoryEntry->totalSize;
}
if (!length) return 0;
if (file->type == POSIX_FILE_TERMINAL) {
EsPrint("%s", length, base);
return length;
} else if (file->type == POSIX_FILE_PIPE) {
// EsPrint("Write %d bytes to pipe %x...\n", length, file->pipe);
return file->pipe->Access(base, length, true, true);
} else if (file->type == POSIX_FILE_NORMAL) {
// EsPrint("WRITE '%z' %d/%d\n", file->path, file->offsetIntoFile, length);
KMutexAcquire(&file->mutex);
EsDefer(KMutexRelease(&file->mutex));
size_t count = FSFileWriteSync(file->node, (void *) base, file->offsetIntoFile, length,
(baseMappedToFile ? FS_FILE_ACCESS_USER_BUFFER_MAPPED : 0));
if (ES_CHECK_ERROR(count)) return -EIO;
else { file->offsetIntoFile += count; return count; }
} else if (file->type == POSIX_FILE_ZERO || file->type == POSIX_FILE_NULL) {
return length;
} else if (file->type == POSIX_FILE_DIRECTORY) {
return length;
}
return -EACCES;
}
void Stat(int type, KNode *node, struct stat *buffer) {
EsMemoryZero(buffer, sizeof(struct stat));
if (type == POSIX_FILE_NORMAL) {
buffer->st_ino = node->id;
buffer->st_mode = S_IFREG;
buffer->st_nlink = 1;
if (node->directoryEntry->type == ES_NODE_FILE) {
buffer->st_size = node->directoryEntry->totalSize;
buffer->st_blksize = 512;
buffer->st_blocks = buffer->st_size / 512;
}
} else if (type == POSIX_FILE_DIRECTORY) {
buffer->st_ino = node->id;
buffer->st_mode = S_IFDIR;
buffer->st_nlink = 1;
} else if (type == POSIX_FILE_PIPE) {
buffer->st_mode = S_IFIFO;
} else {
buffer->st_mode = S_IFCHR;
}
}
void CloneFileDescriptorTable(Process *forkProcess, HandleTable *handleTable) {
HandleTable *source = handleTable,
*destination = &forkProcess->handleTable;
KMutexAcquire(&source->lock);
EsDefer(KMutexRelease(&source->lock));
HandleTableL1 *l1 = &source->l1r;
for (uintptr_t i = 0; i < HANDLE_TABLE_L1_ENTRIES; i++) {
if (!l1->u[i]) continue;
HandleTableL2 *l2 = l1->t[i];
for (uintptr_t k = 0; k < HANDLE_TABLE_L2_ENTRIES; k++) {
Handle *handle = l2->t + k;
if (!handle->object) continue;
if (handle->type != KERNEL_OBJECT_POSIX_FD) continue;
if (handle->flags & FD_CLOEXEC) continue;
POSIXFile *file = (POSIXFile *) handle->object;
OpenHandleToObject(file, KERNEL_OBJECT_POSIX_FD, handle->flags);
destination->OpenHandle(handle->object, handle->flags, handle->type, k + i * HANDLE_TABLE_L2_ENTRIES);
}
}
}
uintptr_t DoSyscall(_EsPOSIXSyscall syscall, uintptr_t *userStackPointer) {
Thread *currentThread = GetCurrentThread();
Process *currentProcess = currentThread->process;
MMSpace *currentVMM = currentProcess->vmm;
HandleTable *handleTable = &currentProcess->handleTable;
bool fromKernel = false;
if (!currentThread->posixData) {
KMutexAcquire(&threadPOSIXDataMutex);
if (!currentThread->posixData) currentThread->posixData = (POSIXThread *) EsHeapAllocate(sizeof(POSIXThread), true, K_FIXED);
KMutexRelease(&threadPOSIXDataMutex);
}
if (currentThread->posixData->forkProcess) {
handleTable = &currentThread->posixData->forkProcess->handleTable;
}
// EsPrint("%x, %x, %x, %x, %x, %x, %x, %x\n", syscall.index, syscall.arguments[0], syscall.arguments[1], syscall.arguments[2],
// syscall.arguments[3], syscall.arguments[4], syscall.arguments[5], syscall.arguments[6]);
switch (syscall.index) {
case SYS_open: {
if (syscall.arguments[6] > SYSCALL_BUFFER_LIMIT) return -ENOMEM;
SYSCALL_BUFFER_POSIX(syscall.arguments[0], syscall.arguments[6], 1, false);
char *path2 = (char *) syscall.arguments[0];
size_t pathLength = syscall.arguments[6];
char *path = (char *) EsHeapAllocate(pathLength, false, K_FIXED);
if (!path) return -ENOMEM;
EsMemoryCopy(path, path2, pathLength);
EsDefer(EsHeapFree(path, 0, K_FIXED));
int flags = syscall.arguments[1];
uint64_t openFlags = ES_FLAGS_DEFAULT;
POSIXFile *file = (POSIXFile *) EsHeapAllocate(sizeof(POSIXFile), true, K_FIXED);
if (!file) return -ENOMEM;
file->posixFlags = flags;
file->handles = 1;
const char *devZero = "/dev/zero";
const char *devNull = "/dev/null";
// EsPrint("Open: %s, %x\n", pathLength, path, flags);
if ((EsCStringLength(devZero) == pathLength && 0 == EsMemoryCompare(path, devZero, pathLength))) {
file->type = POSIX_FILE_ZERO;
} else if (EsCStringLength(devNull) == pathLength && 0 == EsMemoryCompare(path, devNull, pathLength)) {
file->type = POSIX_FILE_NULL;
} else {
if (flags & O_EXCL) openFlags |= ES_NODE_FAIL_IF_FOUND;
else if (flags & O_CREAT) {}
else openFlags |= ES_NODE_FAIL_IF_NOT_FOUND;
if (flags & O_DIRECTORY) openFlags |= ES_NODE_DIRECTORY;
if (flags & O_APPEND) openFlags |= ES_FILE_WRITE;
else if (flags & O_RDWR) openFlags |= ES_FILE_WRITE;
else if (flags & O_WRONLY) openFlags |= ES_FILE_WRITE;
else if (!(flags & O_PATH)) openFlags |= ES_FILE_READ;
KNodeInformation information = FSNodeOpen(path, pathLength, openFlags, (KNode *) syscall.arguments[4]);
if (!information.node) {
EsHeapFree(file, sizeof(POSIXFile), K_FIXED);
return (information.error == ES_ERROR_FILE_DOES_NOT_EXIST || information.error == ES_ERROR_PATH_NOT_TRAVERSABLE) ? -ENOENT : -EACCES;
}
if (information.node->directoryEntry->type == ES_NODE_DIRECTORY && !(flags & O_DIRECTORY) && !(flags & O_PATH)) {
EsHeapFree(file, sizeof(POSIXFile), K_FIXED);
CloseHandleToObject(information.node, KERNEL_OBJECT_NODE, openFlags);
return -EISDIR;
}
file->node = information.node;
file->openFlags = openFlags;
file->type = file->node->directoryEntry->type == ES_NODE_DIRECTORY ? POSIX_FILE_DIRECTORY : POSIX_FILE_NORMAL;
}
file->path = (char *) EsHeapAllocate(pathLength + 1, true, K_FIXED);
EsMemoryCopy(file->path, path, pathLength);
EsHandle fd = handleTable->OpenHandle(file, (flags & O_CLOEXEC) ? FD_CLOEXEC : 0, KERNEL_OBJECT_POSIX_FD);
// EsPrint("OPEN '%s' %z\n", pathLength, path, (openFlags & ES_NODE_WRITE_ACCESS) ? "Write" : ((openFlags & ES_NODE_READ_ACCESS) ? "Read" : "None"));
if (!fd) {
if (file->type == POSIX_FILE_NORMAL || file->type == POSIX_FILE_DIRECTORY) {
CloseHandleToObject(file->node, KERNEL_OBJECT_NODE, openFlags);
}
EsHeapFree(file->path, pathLength + 1, K_FIXED);
EsHeapFree(file, sizeof(POSIXFile), K_FIXED);
return -ENFILE;
}
if ((flags & O_TRUNC) && file->type == POSIX_FILE_NORMAL) {
FSFileResize(file->node, 0);
}
return fd;
} break;
case ES_POSIX_SYSCALL_GET_POSIX_FD_PATH: {
if (syscall.arguments[2] > SYSCALL_BUFFER_LIMIT) return -ENOMEM;
POSIXFile *file;
SYSCALL_HANDLE_POSIX(syscall.arguments[0], file, 1);
SYSCALL_BUFFER_POSIX(syscall.arguments[1], syscall.arguments[2], 2, true);
KMutexAcquire(&file->mutex);
EsDefer(KMutexRelease(&file->mutex));
int length = EsCStringLength(file->path);
if (length > syscall.arguments[2]) length = syscall.arguments[2];
EsMemoryZero((void *) syscall.arguments[1], syscall.arguments[2]);
EsMemoryCopy((void *) syscall.arguments[1], file->path, length);
return length;
} break;
case SYS_close: {
if (!handleTable->CloseHandle(ConvertStandardInputTo3(syscall.arguments[0]))) {
return -EBADF;
}
return 0;
} break;
case SYS_fstat: {
POSIXFile *file;
SYSCALL_HANDLE_POSIX(syscall.arguments[0], file, 1);
SYSCALL_BUFFER_POSIX(syscall.arguments[1], sizeof(struct stat), 1, true);
struct stat temp;
KMutexAcquire(&file->mutex);
Stat(file->type, file->node, &temp);
KMutexRelease(&file->mutex);
EsMemoryCopy((struct stat *) syscall.arguments[1], &temp, sizeof(struct stat));
return 0;
} break;
case SYS_fcntl: {
POSIXFile *file;
SYSCALL_HANDLE_POSIX(syscall.arguments[0], file, 1);
if (syscall.arguments[1] == F_GETFD) {
return _object1.flags;
} else if (syscall.arguments[1] == F_SETFD) {
KObject object;
uint64_t newFlags = syscall.arguments[2];
handleTable->ResolveHandle(&object, syscall.arguments[0], &newFlags);
} else if (syscall.arguments[1] == F_GETFL) {
return file->posixFlags;
} else if (syscall.arguments[1] == F_DUPFD) {
OpenHandleToObject(file, KERNEL_OBJECT_POSIX_FD, _object1.flags);
int fd = handleTable->OpenHandle(_object1.object, _object1.flags, _object1.type);
if (!fd) {
CloseHandleToObject(file, KERNEL_OBJECT_POSIX_FD, _object1.flags);
return -EBUSY;
} else return fd;
} else if (syscall.arguments[1] == F_DUPFD_CLOEXEC) {
KObject object;
uint64_t newFlags = syscall.arguments[2];
handleTable->ResolveHandle(&object, syscall.arguments[0], &newFlags);
OpenHandleToObject(file, KERNEL_OBJECT_POSIX_FD, _object1.flags);
int fd = handleTable->OpenHandle(_object1.object, _object1.flags, _object1.type);
if (!fd) {
CloseHandleToObject(file, KERNEL_OBJECT_POSIX_FD, _object1.flags);
return -EBUSY;
} else return fd;
} else {
KernelPanic("POSIX::DoSyscall - Unimplemented fcntl %d.\n", syscall.arguments[1]);
}
} break;
case SYS_lseek: {
POSIXFile *file;
SYSCALL_HANDLE_POSIX(syscall.arguments[0], file, 1);
KMutexAcquire(&file->mutex);
EsDefer(KMutexRelease(&file->mutex));
if (syscall.arguments[2] == SEEK_SET) {
file->offsetIntoFile = syscall.arguments[1];
} else if (syscall.arguments[2] == SEEK_CUR) {
file->offsetIntoFile += syscall.arguments[1];
} else if (syscall.arguments[2] == SEEK_END && file->type == POSIX_FILE_NORMAL) {
file->offsetIntoFile = file->node->directoryEntry->totalSize + syscall.arguments[1];
} else {
return -EINVAL;
}
return file->offsetIntoFile;
} break;
case SYS_ioctl: {
POSIXFile *file;
SYSCALL_HANDLE_POSIX(syscall.arguments[0], file, 1);
KMutexAcquire(&file->mutex);
EsDefer(KMutexRelease(&file->mutex));
if (syscall.arguments[1] == TIOCGWINSZ) {
if (file->type == POSIX_FILE_TERMINAL || file->type == POSIX_FILE_PIPE) {
SYSCALL_BUFFER_POSIX(syscall.arguments[1], sizeof(struct winsize), 1, true);
struct winsize *size = (struct winsize *) syscall.arguments[2];
size->ws_row = 80;
size->ws_col = 25;
size->ws_xpixel = 800;
size->ws_ypixel = 800;
return 0;
} else {
return -EINVAL;
}
}
} break;
case SYS_read: {
POSIXFile *file;
SYSCALL_HANDLE_POSIX(syscall.arguments[0], file, 1);
SYSCALL_BUFFER_POSIX(syscall.arguments[1], syscall.arguments[2], 3, false);
return Read(file, (void *) syscall.arguments[1], syscall.arguments[2], _region3->flags & MM_REGION_FILE);
} break;
case SYS_readv: {
POSIXFile *file;
if (syscall.arguments[2] > 1024) return -EINVAL;
SYSCALL_HANDLE_POSIX(syscall.arguments[0], file, 1);
SYSCALL_BUFFER_POSIX(syscall.arguments[1], syscall.arguments[2] * sizeof(struct iovec *), 2, false);
struct iovec *vectors = (struct iovec *) EsHeapAllocate(syscall.arguments[2] * sizeof(struct iovec), false, K_FIXED);
if (!vectors) return -ENOMEM;
EsMemoryCopy(vectors, (void *) syscall.arguments[1], syscall.arguments[2] * sizeof(struct iovec));
EsDefer(EsHeapFree(vectors, syscall.arguments[2] * sizeof(struct iovec), K_FIXED));
size_t bytesRead = 0;
for (uintptr_t i = 0; i < (uintptr_t) syscall.arguments[2]; i++) {
if (!vectors[i].iov_len) continue;
SYSCALL_BUFFER_POSIX((uintptr_t) vectors[i].iov_base, vectors[i].iov_len, 3, false);
intptr_t result = Read(file, vectors[i].iov_base, vectors[i].iov_len, _region3->flags & MM_REGION_FILE);
if (result < 0) return result;
bytesRead += result;
}
return bytesRead;
} break;
case SYS_write: {
POSIXFile *file;
SYSCALL_HANDLE_POSIX(syscall.arguments[0], file, 1);
SYSCALL_BUFFER_POSIX(syscall.arguments[1], syscall.arguments[2], 3, true);
if (file->type == POSIX_FILE_NORMAL && !(file->openFlags & (ES_FILE_WRITE | ES_FILE_WRITE_EXCLUSIVE))) {
return -EACCES;
}
return Write(file, (void *) syscall.arguments[1], syscall.arguments[2], _region3->flags & MM_REGION_FILE);
} break;
case SYS_writev: {
POSIXFile *file;
if (syscall.arguments[2] > 1024) return -EINVAL;
SYSCALL_HANDLE_POSIX(syscall.arguments[0], file, 1);
SYSCALL_BUFFER_POSIX(syscall.arguments[1], syscall.arguments[2] * sizeof(struct iovec *), 2, false);
struct iovec *vectors = (struct iovec *) EsHeapAllocate(syscall.arguments[2] * sizeof(struct iovec), false, K_FIXED);
if (!vectors) return -ENOMEM;
EsMemoryCopy(vectors, (void *) syscall.arguments[1], syscall.arguments[2] * sizeof(struct iovec));
EsDefer(EsHeapFree(vectors, syscall.arguments[2] * sizeof(struct iovec), K_FIXED));
size_t bytesWritten = 0;
if (file->type == POSIX_FILE_NORMAL && !(file->openFlags & (ES_FILE_WRITE | ES_FILE_WRITE_EXCLUSIVE))) {
return -EACCES;
}
for (uintptr_t i = 0; i < (uintptr_t) syscall.arguments[2]; i++) {
if (!vectors[i].iov_len) continue;
// EsPrint("writev %d: %x/%d\n", i, vectors[i].iov_base, vectors[i].iov_len);
SYSCALL_BUFFER_POSIX((uintptr_t) vectors[i].iov_base, vectors[i].iov_len, 3, true);
intptr_t result = Write(file, vectors[i].iov_base, vectors[i].iov_len, _region3->flags & MM_REGION_FILE);
if (result < 0) return result;
bytesWritten += result;
}
return bytesWritten;
} break;
case SYS_vfork: {
// To vfork: save the stack and return 0.
// To exec*: create the new process, restore the state of our stack, then return the new process's ID.
KMutexAcquire(&forkMutex);
EsDefer(KMutexRelease(&forkMutex));
// Did we complete the last vfork?
if (currentThread->posixData->forkStack) return -ENOMEM;
// EsPrint("vfork->\n");
// Allocate the process.
Process *forkProcess = currentThread->posixData->forkProcess = scheduler.SpawnProcess();
forkProcess->pgid = currentProcess->pgid;
// Clone our FDs.
CloneFileDescriptorTable(forkProcess, handleTable);
// Save the state of the user's stack.
currentThread->posixData->forkStackSize = currentThread->userStackCommit;
currentThread->posixData->forkStack = (void *) EsHeapAllocate(currentThread->posixData->forkStackSize, false, K_PAGED);
#ifdef K_STACK_GROWS_DOWN
EsMemoryCopy(currentThread->posixData->forkStack,
(void *) (currentThread->userStackBase + currentThread->userStackReserve - currentThread->posixData->forkStackSize),
currentThread->posixData->forkStackSize);
#else
EsMemoryCopy(currentThread->posixData->forkStack, (void *) currentThread->userStackBase, currentThread->posixData->forkStackSize);
#endif
currentThread->posixData->forkUSP = *userStackPointer;
// Return 0.
return 0;
} break;
case SYS_execve: {
KMutexAcquire(&forkMutex);
EsDefer(KMutexRelease(&forkMutex));
// Are we vforking?
if (!currentThread->posixData->forkStack) return -ENOMEM;
if (syscall.arguments[1] > K_MAX_PATH) return -ENOMEM;
if (syscall.arguments[3] > SYSCALL_BUFFER_LIMIT) return -ENOMEM;
// EsPrint("<-execve\n");
SYSCALL_BUFFER_POSIX(syscall.arguments[0], syscall.arguments[1], 1, false);
SYSCALL_BUFFER_POSIX(syscall.arguments[2], syscall.arguments[3], 2, false);
// Setup the process object.
char *path = (char *) EsHeapAllocate(syscall.arguments[1], false, K_FIXED);
if (!path) return -ENOMEM;
EsMemoryCopy(path, (void *) syscall.arguments[0], syscall.arguments[1]);
Process *process = currentThread->posixData->forkProcess;
process->creationArguments[CREATION_ARGUMENT_ENVIRONMENT] = MakeConstantBuffer((void *) syscall.arguments[2], syscall.arguments[3], process);
process->posixForking = true;
process->permissions = currentProcess->permissions;
EsMountPoint mountPoint = {};
OpenHandleToObject((void *) syscall.arguments[4], KERNEL_OBJECT_NODE, _ES_NODE_DIRECTORY_WRITE);
mountPoint.base = process->handleTable.OpenHandle((void *) syscall.arguments[4], _ES_NODE_DIRECTORY_WRITE, KERNEL_OBJECT_NODE);
mountPoint.prefixBytes = EsStringFormat(mountPoint.prefix, sizeof(mountPoint.prefix), "|POSIX:");
process->creationArguments[CREATION_ARGUMENT_INITIAL_MOUNT_POINTS] = MakeConstantBuffer(&mountPoint, sizeof(EsMountPoint), process);
// Start the process.
if (!process->Start(path, syscall.arguments[1])) {
EsHeapFree(path, 0, K_FIXED);
return -ENOMEM;
}
KSpinlockAcquire(&scheduler.lock);
process->posixForking = false;
if (process->allThreadsTerminated) {
KEventSet(&process->killedEvent, true);
}
KSpinlockRelease(&scheduler.lock);
EsHeapFree(path, 0, K_FIXED);
CloseHandleToObject(process->executableMainThread, KERNEL_OBJECT_THREAD);
// Restore the state of our stack.
#ifdef K_STACK_GROWS_DOWN
EsMemoryCopy((void *) (currentThread->userStackBase + currentThread->userStackReserve - currentThread->posixData->forkStackSize),
currentThread->posixData->forkStack, currentThread->posixData->forkStackSize);
#else
EsMemoryCopy((void *) currentThread->userStackBase, currentThread->posixData->forkStack, currentThread->posixData->forkStackSize);
#endif
EsHeapFree(currentThread->posixData->forkStack, currentThread->posixData->forkStackSize, K_PAGED);
*userStackPointer = currentThread->posixData->forkUSP;
// Fork complete.
currentThread->posixData->forkProcess = nullptr;
currentThread->posixData->forkStack = nullptr;
currentThread->posixData->forkUSP = 0;
if (!process) return -ENOMEM;
EsHandle processHandle = currentProcess->handleTable.OpenHandle(process, 0, KERNEL_OBJECT_PROCESS);
if (!processHandle) CloseHandleToObject(process, KERNEL_OBJECT_PROCESS);
return processHandle;
} break;
case SYS_exit_group: {
EsHandle processHandle = 0;
KMutexAcquire(&forkMutex);
// Are we vforking?
if (currentThread->posixData->forkStack) {
// Restore the state of our stack.
#ifdef K_STACK_GROWS_DOWN
EsMemoryCopy((void *) (currentThread->userStackBase + currentThread->userStackReserve - currentThread->posixData->forkStackSize),
currentThread->posixData->forkStack, currentThread->posixData->forkStackSize);
#else
EsMemoryCopy((void *) currentThread->userStackBase, currentThread->posixData->forkStack, currentThread->posixData->forkStackSize);
#endif
EsHeapFree(currentThread->posixData->forkStack, currentThread->posixData->forkStackSize, K_PAGED);
*userStackPointer = currentThread->posixData->forkUSP;
// Set the exit status.
Process *process = currentThread->posixData->forkProcess;
process->exitStatus = syscall.arguments[0];
process->allThreadsTerminated = true; // Set in case execve() was not attempted.
KEventSet(&process->killedEvent);
processHandle = currentProcess->handleTable.OpenHandle(currentThread->posixData->forkProcess, 0, KERNEL_OBJECT_PROCESS);
if (!processHandle) CloseHandleToObject(currentThread->posixData->forkProcess, KERNEL_OBJECT_PROCESS);
// Close any handles the process owned.
process->handleTable.Destroy();
// Cancel the vfork.
currentThread->posixData->forkProcess = nullptr;
currentThread->posixData->forkStack = nullptr;
currentThread->posixData->forkUSP = 0;
}
KMutexRelease(&forkMutex);
if (processHandle) {
return processHandle;
} else {
scheduler.TerminateProcess(currentProcess, syscall.arguments[0]);
}
} break;
case SYS_sysinfo: {
SYSCALL_BUFFER_POSIX(syscall.arguments[0], sizeof(struct sysinfo), 1, true);
struct sysinfo *buffer = (struct sysinfo *) syscall.arguments[0];
EsMemoryZero(buffer, sizeof(struct sysinfo));
// TODO Incomplete.
buffer->totalram = K_PAGE_SIZE * pmm.commitFixedLimit;
buffer->freeram = K_PAGE_SIZE * (pmm.countZeroedPages + pmm.countFreePages);
buffer->procs = scheduler.allProcesses.count;
buffer->mem_unit = 1;
return 0;
} break;
case SYS_dup2: {
POSIXFile *file;
SYSCALL_HANDLE_POSIX(syscall.arguments[0], file, 1);
// Try to close the newfd.
handleTable->CloseHandle(ConvertStandardInputTo3(syscall.arguments[1]));
// Clone the oldfd as newfd.
OpenHandleToObject(file, KERNEL_OBJECT_POSIX_FD, _object1.flags);
if (!handleTable->OpenHandle(_object1.object, _object1.flags, _object1.type, ConvertStandardInputTo3(syscall.arguments[1]))) {
CloseHandleToObject(file, KERNEL_OBJECT_POSIX_FD, _object1.flags);
return -EBUSY;
} else return 0;
} break;
case SYS_pipe2: {
if (syscall.arguments[1] & ~O_CLOEXEC) {
return -EINVAL;
}
SYSCALL_BUFFER_POSIX(syscall.arguments[0], sizeof(int) * 2, 1, true);
int *fildes = (int *) syscall.arguments[0];
Pipe *pipe = (Pipe *) EsHeapAllocate(sizeof(Pipe), true, K_PAGED);
POSIXFile *reader = (POSIXFile *) EsHeapAllocate(sizeof(POSIXFile), true, K_FIXED);
POSIXFile *writer = (POSIXFile *) EsHeapAllocate(sizeof(POSIXFile), true, K_FIXED);
if (!reader || !writer || !pipe) {
EsHeapFree(pipe, sizeof(Pipe), K_PAGED);
EsHeapFree(reader, 0, K_FIXED);
EsHeapFree(writer, 0, K_FIXED);
return -ENOMEM;
}
KEventSet(&pipe->canWrite);
reader->type = POSIX_FILE_PIPE;
reader->openFlags = PIPE_READER;
reader->handles = 1;
reader->pipe = pipe;
writer->type = POSIX_FILE_PIPE;
writer->openFlags = PIPE_WRITER;
writer->handles = 1;
writer->pipe = pipe;
EsHandle fd0, fd1;
fd0 = handleTable->OpenHandle(reader, (syscall.arguments[1] & O_CLOEXEC) ? FD_CLOEXEC : 0, KERNEL_OBJECT_POSIX_FD);
if (!fd0) {
EsHeapFree(pipe, sizeof(Pipe), K_PAGED);
EsHeapFree(reader, 0, K_FIXED);
EsHeapFree(writer, 0, K_FIXED);
return -EMFILE;
}
pipe->readers = 1;
fd1 = handleTable->OpenHandle(writer, (syscall.arguments[1] & O_CLOEXEC) ? FD_CLOEXEC : 0, KERNEL_OBJECT_POSIX_FD);
if (!fd1) {
handleTable->CloseHandle(ConvertStandardInputTo3(fd0));
EsHeapFree(writer, 0, K_FIXED);
return -EMFILE;
}
pipe->writers = 1;
fildes[0] = fd0, fildes[1] = fd1;
return 0;
} break;
case SYS_getdents64: {
// TODO This is a bit of a hack.
// Especially allocating the entire directory list in the kernel's memory space.
if (syscall.arguments[2] > SYSCALL_BUFFER_LIMIT) return -ENOMEM;
POSIXFile *file;
SYSCALL_HANDLE_POSIX(syscall.arguments[0], file, 1);
SYSCALL_BUFFER_POSIX(syscall.arguments[1], syscall.arguments[2], 3, true);
KMutexAcquire(&file->mutex);
EsDefer(KMutexRelease(&file->mutex));
if (file->type != POSIX_FILE_DIRECTORY) {
return -EACCES;
}
if (!file->offsetIntoFile || !file->directoryBuffer) {
EsHeapFree(file->directoryBuffer, file->directoryBufferLength, K_PAGED);
file->directoryBuffer = nullptr;
file->offsetIntoFile = 0;
file->directoryBufferLength = 0;
uintptr_t bufferSize = file->node->directoryEntry->directoryChildren;
EsDirectoryChild *buffer = (EsDirectoryChild *) EsHeapAllocate(sizeof(EsDirectoryChild) * bufferSize, false, K_FIXED);
if (!buffer) {
return -ENOMEM;
}
size_t count = FSDirectoryEnumerateChildren(file->node, buffer, bufferSize);
if (ES_CHECK_ERROR(count)) {
EsHeapFree(buffer, sizeof(EsDirectoryChild) * bufferSize, K_FIXED);
return -EIO;
}
size_t neededSize = 0;
for (uintptr_t i = 0; i < count; i++) {
neededSize += RoundUp(19 + buffer[i].nameBytes + 1, (size_t) 8);
}
file->directoryBuffer = EsHeapAllocate(neededSize, true, K_PAGED);
if (!file->directoryBuffer) {
EsHeapFree(buffer, bufferSize * sizeof(EsDirectoryChild), K_FIXED);
return -ENOMEM;
}
file->directoryBufferLength = neededSize;
uint8_t *position = (uint8_t *) file->directoryBuffer;
for (uintptr_t i = 0; i < count; i++) {
// EsPrint("%d - %d\n", i, position - (uint8_t *) file->directoryBuffer);
size_t size = RoundUp(19 + buffer[i].nameBytes + 1, (size_t) 8);
EsDirectoryChild *entry = buffer + i;
((uint64_t *) position)[0] = EsRandomU64(); // We don't have a concept of inodes.
((uint64_t *) position)[1] = position - (uint8_t *) file->directoryBuffer + size;
((uint16_t *) position)[8] = size;
((uint8_t *) position)[18] = entry->type == ES_NODE_DIRECTORY ? 4 /* DT_DIR */
: entry->type == ES_NODE_FILE ? 8 /* DT_REG */ : 0 /* DT_UNKNOWN */;
EsMemoryCopy(position + 19, entry->name, entry->nameBytes);
*(position + 19 + entry->nameBytes) = 0;
position += size;
}
EsHeapFree(buffer, bufferSize * sizeof(EsDirectoryChild), K_FIXED);
}
uint64_t offset = file->offsetIntoFile;
size_t bufferSize = syscall.arguments[2];
while (offset + 19 < file->directoryBufferLength) {
uint8_t *position = (uint8_t *) file->directoryBuffer + offset;
uint64_t nextOffset = ((uint64_t *) position)[1];
if (nextOffset > file->directoryBufferLength || nextOffset < offset + 19
|| nextOffset - file->offsetIntoFile >= bufferSize) {
break;
}
offset = nextOffset;
}
size_t bytesToReturn = offset - file->offsetIntoFile;
if (bytesToReturn > bufferSize) {
bytesToReturn = bufferSize;
}
EsMemoryCopy((void *) syscall.arguments[1], (uint8_t *) file->directoryBuffer + file->offsetIntoFile, bytesToReturn);
file->offsetIntoFile += bytesToReturn;
return bytesToReturn;
} break;
case SYS_setpgid: {
Process *process = (Process *) syscall.arguments[0];
process->pgid = syscall.arguments[1];
return 0;
} break;
}
return -1;
}
};
#endif

1423
kernel/scheduler.cpp Normal file

File diff suppressed because it is too large Load Diff

46
kernel/symbols.cpp Normal file
View File

@ -0,0 +1,46 @@
#include <stdint.h>
#include <stddef.h>
extern "C" int EsStringCompareRaw(const char *s1, size_t b1, const char *s2, size_t b2);
void EsPrint(const char *format, ...);
extern "C" void KernelMain();
extern "C" void ProcessorHalt();
struct ExportedKernelFunction {
void *address;
const char *name;
};
ExportedKernelFunction exportedKernelFunctions[] = {
#include <bin/kernel_symbols.h>
};
static bool linked;
void *ResolveKernelSymbol(const char *name, size_t nameBytes) {
// EsPrint("Resolve: '%s'.\n", nameBytes, name);
if (!linked) {
linked = true;
// As we get the function addresses before the kernel is linked (this file needs to be linked with the kernel),
// they are relative to wherever the kernel_all.o's text is placed in the executable's text section.
uintptr_t offset = (uintptr_t) ResolveKernelSymbol("KernelMain", 10) - (uintptr_t) KernelMain;
for (uintptr_t i = 0; i < sizeof(exportedKernelFunctions) / sizeof(exportedKernelFunctions[0]); i++) {
exportedKernelFunctions[i].address = (void *) ((uintptr_t) exportedKernelFunctions[i].address - offset);
}
}
for (uintptr_t i = 0; i < sizeof(exportedKernelFunctions) / sizeof(exportedKernelFunctions[0]); i++) {
if (0 == EsStringCompareRaw(exportedKernelFunctions[i].name, -1, name, nameBytes)) {
return exportedKernelFunctions[i].address;
}
}
EsPrint("ResolveKernelSymbol - Could not find symbol '%s'.\n", nameBytes, name);
return nullptr;
}

862
kernel/synchronisation.cpp Normal file
View File

@ -0,0 +1,862 @@
#ifdef IMPLEMENTATION
#ifdef DEBUG_BUILD
uintptr_t nextMutexID;
#endif
void KSpinlockAcquire(KSpinlock *spinlock) {
if (scheduler.panic) return;
bool _interruptsEnabled = ProcessorAreInterruptsEnabled();
ProcessorDisableInterrupts();
CPULocalStorage *storage = GetLocalStorage();
#ifdef DEBUG_BUILD
if (storage && storage->currentThread && spinlock->owner && spinlock->owner == storage->currentThread) {
KernelPanic("KSpinlock::Acquire - Attempt to acquire a spinlock owned by the current thread (%x/%x, CPU: %d/%d).\nAcquired at %x.\n",
storage->currentThread, spinlock->owner, storage->processorID, spinlock->ownerCPU, spinlock->acquireAddress);
}
#endif
if (storage) {
storage->spinlockCount++;
}
while (__sync_val_compare_and_swap(&spinlock->state, 0, 1));
__sync_synchronize();
spinlock->interruptsEnabled = _interruptsEnabled;
if (storage) {
#ifdef DEBUG_BUILD
spinlock->owner = storage->currentThread;
#endif
spinlock->ownerCPU = storage->processorID;
} else {
// Because spinlocks can be accessed very early on in initialisation there may not be
// a CPULocalStorage available for the current processor. Therefore, just set this field to nullptr.
#ifdef DEBUG_BUILD
spinlock->owner = nullptr;
#endif
}
#ifdef DEBUG_BUILD
spinlock->acquireAddress = (uintptr_t) __builtin_return_address(0);
#endif
}
void KSpinlockRelease(KSpinlock *spinlock, bool force) {
if (scheduler.panic) return;
CPULocalStorage *storage = GetLocalStorage();
if (storage) {
storage->spinlockCount--;
}
if (!force) {
KSpinlockAssertLocked(spinlock);
}
volatile bool wereInterruptsEnabled = spinlock->interruptsEnabled;
#ifdef DEBUG_BUILD
spinlock->owner = nullptr;
#endif
__sync_synchronize();
spinlock->state = 0;
if (wereInterruptsEnabled) ProcessorEnableInterrupts();
#ifdef DEBUG_BUILD
spinlock->releaseAddress = (uintptr_t) __builtin_return_address(0);
#endif
}
void KSpinlockAssertLocked(KSpinlock *spinlock) {
if (scheduler.panic) return;
#ifdef DEBUG_BUILD
CPULocalStorage *storage = GetLocalStorage();
if (!spinlock->state || ProcessorAreInterruptsEnabled()
|| (storage && spinlock->owner != storage->currentThread)) {
#else
if (!spinlock->state || ProcessorAreInterruptsEnabled()) {
#endif
KernelPanic("KSpinlock::AssertLocked - KSpinlock not correctly acquired\n"
"Return address = %x.\n"
"state = %d, ProcessorAreInterruptsEnabled() = %d, this = %x\n",
__builtin_return_address(0), spinlock->state,
ProcessorAreInterruptsEnabled(), spinlock);
}
}
Thread *AttemptMutexAcquisition(KMutex *mutex, Thread *currentThread) {
KSpinlockAcquire(&scheduler.lock);
Thread *old = mutex->owner;
if (!old) {
mutex->owner = currentThread;
}
KSpinlockRelease(&scheduler.lock);
return old;
}
#ifdef DEBUG_BUILD
bool _KMutexAcquire(KMutex *mutex, const char *cMutexString, const char *cFile, int line) {
#else
bool KMutexAcquire(KMutex *mutex) {
#endif
if (scheduler.panic) return false;
Thread *currentThread = GetCurrentThread();
bool hasThread = currentThread;
if (!currentThread) {
currentThread = (Thread *) 1;
} else {
if (currentThread->terminatableState == THREAD_TERMINATABLE) {
KernelPanic("KMutex::Acquire - Thread is terminatable.\n");
}
}
if (hasThread && mutex->owner && mutex->owner == currentThread) {
#ifdef DEBUG_BUILD
KernelPanic("KMutex::Acquire - Attempt to acquire mutex (%x) at %x owned by current thread (%x) acquired at %x.\n",
mutex, __builtin_return_address(0), currentThread, mutex->acquireAddress);
#else
KernelPanic("KMutex::Acquire - Attempt to acquire mutex (%x) at %x owned by current thread (%x).\n",
mutex, __builtin_return_address(0), currentThread);
#endif
}
if (!ProcessorAreInterruptsEnabled()) {
KernelPanic("KMutex::Acquire - Trying to acquire a mutex while interrupts are disabled.\n");
}
while (AttemptMutexAcquisition(mutex, currentThread)) {
__sync_synchronize();
if (GetLocalStorage() && GetLocalStorage()->schedulerReady) {
// Instead of spinning on the lock,
// let's tell the scheduler to not schedule this thread
// until it's released.
scheduler.WaitMutex(mutex);
if (currentThread->terminating && currentThread->terminatableState == THREAD_USER_BLOCK_REQUEST) {
// We didn't acquire the mutex because the thread is terminating.
return false;
}
}
}
__sync_synchronize();
if (mutex->owner != currentThread) {
KernelPanic("KMutex::Acquire - Invalid owner thread (%x, expected %x).\n", mutex->owner, currentThread);
}
#ifdef DEBUG_BUILD
mutex->acquireAddress = (uintptr_t) __builtin_return_address(0);
KMutexAssertLocked(mutex);
if (!mutex->id) {
mutex->id = __sync_fetch_and_add(&nextMutexID, 1);
}
if (currentThread && scheduler.threadEventLog) {
uintptr_t position = __sync_fetch_and_add(&scheduler.threadEventLogPosition, 1);
if (position < scheduler.threadEventLogAllocated) {
EsThreadEventLogEntry *entry = scheduler.threadEventLog + position;
entry->event = ES_THREAD_EVENT_MUTEX_ACQUIRE;
entry->objectID = mutex->id;
entry->threadID = currentThread->id;
entry->line = line;
entry->fileBytes = EsCStringLength(cFile);
if (entry->fileBytes > sizeof(entry->file)) entry->fileBytes = sizeof(entry->file);
entry->expressionBytes = EsCStringLength(cMutexString);
if (entry->expressionBytes > sizeof(entry->expression)) entry->expressionBytes = sizeof(entry->expression);
EsMemoryCopy(entry->file, cFile, entry->fileBytes);
EsMemoryCopy(entry->expression, cMutexString, entry->expressionBytes);
}
}
#endif
return true;
}
#ifdef DEBUG_BUILD
void _KMutexRelease(KMutex *mutex, const char *cMutexString, const char *cFile, int line) {
#else
void KMutexRelease(KMutex *mutex) {
#endif
if (scheduler.panic) return;
KMutexAssertLocked(mutex);
Thread *currentThread = GetCurrentThread();
KSpinlockAcquire(&scheduler.lock);
#ifdef DEBUG_BUILD
// EsPrint("$%x:%x:0\n", owner, id);
#endif
if (currentThread) {
Thread *temp = __sync_val_compare_and_swap(&mutex->owner, currentThread, nullptr);
if (currentThread != temp) KernelPanic("KMutex::Release - Invalid owner thread (%x, expected %x).\n", temp, currentThread);
} else mutex->owner = nullptr;
volatile bool preempt = mutex->blockedThreads.count;
if (scheduler.started) {
// NOTE We unblock all waiting threads, because of how blockedThreadPriorities works.
scheduler.NotifyObject(&mutex->blockedThreads, true, currentThread);
}
KSpinlockRelease(&scheduler.lock);
__sync_synchronize();
#ifdef DEBUG_BUILD
mutex->releaseAddress = (uintptr_t) __builtin_return_address(0);
if (currentThread && scheduler.threadEventLog) {
uintptr_t position = __sync_fetch_and_add(&scheduler.threadEventLogPosition, 1);
if (position < scheduler.threadEventLogAllocated) {
EsThreadEventLogEntry *entry = scheduler.threadEventLog + position;
entry->event = ES_THREAD_EVENT_MUTEX_RELEASE;
entry->objectID = mutex->id;
entry->threadID = currentThread->id;
entry->line = line;
entry->fileBytes = EsCStringLength(cFile);
if (entry->fileBytes > sizeof(entry->file)) entry->fileBytes = sizeof(entry->file);
entry->expressionBytes = EsCStringLength(cMutexString);
if (entry->expressionBytes > sizeof(entry->expression)) entry->expressionBytes = sizeof(entry->expression);
EsMemoryCopy(entry->file, cFile, entry->fileBytes);
EsMemoryCopy(entry->expression, cMutexString, entry->expressionBytes);
}
}
#endif
#ifdef PREEMPT_AFTER_MUTEX_RELEASE
if (preempt) ProcessorFakeTimerInterrupt();
#endif
}
void KMutexAssertLocked(KMutex *mutex) {
Thread *currentThread = GetCurrentThread();
if (!currentThread) {
currentThread = (Thread *) 1;
}
if (mutex->owner != currentThread) {
#ifdef DEBUG_BUILD
KernelPanic("KMutex::AssertLocked - Mutex not correctly acquired\n"
"currentThread = %x, owner = %x\nthis = %x\nReturn %x/%x\nLast used from %x->%x\n",
currentThread, mutex->owner, mutex, __builtin_return_address(0), __builtin_return_address(1),
mutex->acquireAddress, mutex->releaseAddress);
#else
KernelPanic("KMutex::AssertLocked - Mutex not correctly acquired\n"
"currentThread = %x, owner = %x\nthis = %x\nReturn %x\n",
currentThread, mutex->owner, mutex, __builtin_return_address(0));
#endif
}
}
bool KSemaphorePoll(KSemaphore *semaphore) {
bool success = false;
KMutexAcquire(&semaphore->mutex);
if (semaphore->units) { success = true; semaphore->units--; }
if (!semaphore->units && semaphore->available.state) KEventReset(&semaphore->available);
KMutexRelease(&semaphore->mutex);
return success;
}
bool KSemaphoreTake(KSemaphore *semaphore, uintptr_t u, uintptr_t timeoutMs) {
// All-or-nothing approach to prevent deadlocks.
uintptr_t taken = 0;
while (u) {
if (!KEventWait(&semaphore->available, timeoutMs)) {
KSemaphoreReturn(semaphore, taken);
return false;
}
KMutexAcquire(&semaphore->mutex);
if (semaphore->units >= u) { semaphore->units -= u; u = 0; taken += u; }
if (!semaphore->units && semaphore->available.state) KEventReset(&semaphore->available);
KMutexRelease(&semaphore->mutex);
semaphore->lastTaken = (uintptr_t) __builtin_return_address(0);
}
return true;
}
void KSemaphoreReturn(KSemaphore *semaphore, uintptr_t u) {
KMutexAcquire(&semaphore->mutex);
if (!semaphore->available.state) KEventSet(&semaphore->available);
semaphore->units += u;
KMutexRelease(&semaphore->mutex);
}
void KSemaphoreSet(KSemaphore *semaphore, uintptr_t u) {
KMutexAcquire(&semaphore->mutex);
if (!semaphore->available.state && u) KEventSet(&semaphore->available);
else if (semaphore->available.state && !u) KEventReset(&semaphore->available);
semaphore->units = u;
KMutexRelease(&semaphore->mutex);
}
EsError EventSink::Push(EsGeneric data) {
EsError result = ES_SUCCESS;
KSpinlockAssertLocked(&scheduler.lock);
KSpinlockAcquire(&spinlock);
if (queueCount == ES_MAX_EVENT_SINK_BUFFER_SIZE) {
overflow = true;
result = ES_ERROR_EVENT_SINK_OVERFLOW;
KernelLog(LOG_VERBOSE, "Event Sinks", "push overflow", "Push %d into %x.\n", data.i, this);
} else {
bool ignored = false;
if (ignoreDuplicates) {
uintptr_t index = queuePosition;
for (uintptr_t i = 0; i < queueCount; i++) {
if (queue[index] == data) {
ignored = true;
result = ES_ERROR_EVENT_SINK_DUPLICATE;
KernelLog(LOG_VERBOSE, "Event Sinks", "push ignored", "Push %d into %x.\n", data.i, this);
break;
}
index++;
if (index == ES_MAX_EVENT_SINK_BUFFER_SIZE) {
index = 0;
}
}
}
if (!ignored) {
uintptr_t writeIndex = queuePosition + queueCount;
if (writeIndex >= ES_MAX_EVENT_SINK_BUFFER_SIZE) writeIndex -= ES_MAX_EVENT_SINK_BUFFER_SIZE;
if (writeIndex >= ES_MAX_EVENT_SINK_BUFFER_SIZE) KernelPanic("EventSink::Push - Invalid event sink (%x) queue.\n", this);
KernelLog(LOG_VERBOSE, "Event Sinks", "push", "Push %d into %x at %d (%d/%d).\n", data.i, this, writeIndex, queuePosition, queueCount);
queue[writeIndex] = data;
queueCount++;
KEventSet(&available, true, true);
}
}
KSpinlockRelease(&spinlock);
return result;
}
bool KEventSet(KEvent *event, bool schedulerAlreadyLocked, bool maybeAlreadySet) {
if (event->state && !maybeAlreadySet) {
KernelLog(LOG_ERROR, "Synchronisation", "event already set", "KEvent::Set - Attempt to set a event that had already been set\n");
}
if (!schedulerAlreadyLocked) {
KSpinlockAcquire(&scheduler.lock);
} else {
KSpinlockAssertLocked(&scheduler.lock);
}
volatile bool unblockedThreads = false;
if (!event->state) {
if (event->sinkTable) {
for (uintptr_t i = 0; i < ES_MAX_EVENT_FORWARD_COUNT; i++) {
if (!event->sinkTable[i].sink) continue;
event->sinkTable[i].sink->Push(event->sinkTable[i].data);
}
}
event->state = true;
if (scheduler.started) {
if (event->blockedThreads.count) {
unblockedThreads = true;
}
scheduler.NotifyObject(&event->blockedThreads, !event->autoReset /* if this is a manually reset event, unblock all the waiting threads */);
}
}
if (!schedulerAlreadyLocked) {
KSpinlockRelease(&scheduler.lock);
}
return unblockedThreads;
}
void KEventReset(KEvent *event) {
if (event->blockedThreads.firstItem && event->state) {
// TODO Is it possible for this to happen?
KernelLog(LOG_ERROR, "Synchronisation", "reset event with threads blocking",
"KEvent::Reset - Attempt to reset a event while threads are blocking on the event\n");
}
event->state = false;
}
bool KEventPoll(KEvent *event) {
if (event->autoReset) {
return __sync_val_compare_and_swap(&event->state, true, false);
} else {
return event->state;
}
}
bool KEventWait(KEvent *_this, uint64_t timeoutMs) {
KEvent *events[2];
events[0] = _this;
if (timeoutMs == (uint64_t) ES_WAIT_NO_TIMEOUT) {
int index = scheduler.WaitEvents(events, 1);
return index == 0;
} else {
KTimer timer = {};
KTimerSet(&timer, timeoutMs);
events[1] = &timer.event;
int index = scheduler.WaitEvents(events, 2);
KTimerRemove(&timer);
return index == 0;
}
}
void KWriterLockAssertLocked(KWriterLock *lock) {
if (lock->state == 0) {
KernelPanic("KWriterLock::AssertLocked - Unlocked.\n");
}
}
void KWriterLockAssertShared(KWriterLock *lock) {
if (lock->state == 0) {
KernelPanic("KWriterLock::AssertShared - Unlocked.\n");
} else if (lock->state < 0) {
KernelPanic("KWriterLock::AssertShared - In exclusive mode.\n");
}
}
void KWriterLockAssertExclusive(KWriterLock *lock) {
if (lock->state == 0) {
KernelPanic("KWriterLock::AssertExclusive - Unlocked.\n");
} else if (lock->state > 0) {
KernelPanic("KWriterLock::AssertExclusive - In shared mode, with %d readers.\n", lock->state);
}
}
void KWriterLockReturn(KWriterLock *lock, bool write) {
KSpinlockAcquire(&scheduler.lock);
if (lock->state == -1) {
if (!write) {
KernelPanic("KWriterLock::Return - Attempting to return shared access to an exclusively owned lock.\n");
}
lock->state = 0;
} else if (lock->state == 0) {
KernelPanic("KWriterLock::Return - Attempting to return access to an unowned lock.\n");
} else {
if (write) {
KernelPanic("KWriterLock::Return - Attempting to return exclusive access to an shared lock.\n");
}
lock->state--;
}
if (!lock->state) {
scheduler.NotifyObject(&lock->blockedThreads, true);
}
KSpinlockRelease(&scheduler.lock);
}
bool KWriterLockTake(KWriterLock *lock, bool write, bool poll) {
// TODO Preventing exclusive access starvation.
// TODO Do this without taking the scheduler's lock?
bool done = false;
Thread *thread = GetCurrentThread();
if (thread) {
thread->blocking.writerLock = lock;
thread->blocking.writerLockType = write;
}
while (true) {
KSpinlockAcquire(&scheduler.lock);
if (write) {
if (lock->state == 0) {
lock->state = -1;
done = true;
#ifdef DEBUG_BUILD
lock->exclusiveOwner = thread;
#endif
}
} else {
if (lock->state >= 0) {
lock->state++;
done = true;
}
}
KSpinlockRelease(&scheduler.lock);
if (poll || done) {
break;
} else {
if (!thread) {
KernelPanic("KWriterLock::Take - Scheduler not ready yet.\n");
}
thread->state = THREAD_WAITING_WRITER_LOCK;
ProcessorFakeTimerInterrupt();
thread->state = THREAD_ACTIVE;
}
}
return done;
}
void KWriterLockTakeMultiple(KWriterLock **locks, size_t lockCount, bool write) {
uintptr_t i = 0, taken = 0;
while (taken != lockCount) {
if (KWriterLockTake(locks[i], write, taken)) {
taken++, i++;
if (i == lockCount) i = 0;
} else {
intptr_t j = i - 1;
while (taken) {
if (j == -1) j = lockCount - 1;
KWriterLockReturn(locks[j], write);
j--, taken--;
}
}
}
}
void KWriterLockConvertExclusiveToShared(KWriterLock *lock) {
KSpinlockAcquire(&scheduler.lock);
KWriterLockAssertExclusive(lock);
lock->state = 1;
scheduler.NotifyObject(&lock->blockedThreads, true);
KSpinlockRelease(&scheduler.lock);
}
#if 0
volatile int testState;
KWriterLock testWriterLock;
void TestWriterLocksThread1(uintptr_t) {
KEvent wait = {};
testWriterLock.Take(K_LOCK_SHARED);
EsPrint("-->1\n");
testState = 1;
while (testState != 2);
wait.Wait(1000);
EsPrint("-->3\n");
testWriterLock.Return(K_LOCK_SHARED);
testState = 3;
}
void TestWriterLocksThread2(uintptr_t) {
while (testState != 1);
testWriterLock.Take(K_LOCK_SHARED);
EsPrint("-->2\n");
testState = 2;
while (testState != 3);
testWriterLock.Return(K_LOCK_SHARED);
}
void TestWriterLocksThread3(uintptr_t) {
while (testState < 1);
testWriterLock.Take(K_LOCK_EXCLUSIVE);
EsPrint("!!!!!!!!!!!!!!!!!!! %d\n", testState);
testWriterLock.Return(K_LOCK_EXCLUSIVE);
testState = 5;
}
#define TEST_WRITER_LOCK_THREADS (4)
void TestWriterLocksThread4(uintptr_t) {
__sync_fetch_and_add(&testState, 1);
while (testState < 6 + TEST_WRITER_LOCK_THREADS) {
bool type = EsRandomU8() < 0xC0;
testWriterLock.Take(type);
testWriterLock.Return(type);
}
__sync_fetch_and_add(&testState, 1);
}
void TestWriterLocks() {
testState = 0;
EsPrint("TestWriterLocks...\n");
KThreadCreate("Test1", TestWriterLocksThread1);
KThreadCreate("Test2", TestWriterLocksThread2);
KThreadCreate("Test3", TestWriterLocksThread3);
EsPrint("waiting for state 5...\n");
while (testState != 5);
while (true) {
testState = 5;
for (int i = 0; i < TEST_WRITER_LOCK_THREADS; i++) {
KThreadCreate("Test", TestWriterLocksThread4, i);
}
while (testState != TEST_WRITER_LOCK_THREADS + 5);
EsPrint("All threads ready.\n");
KEvent wait = {};
wait.Wait(10000);
testState++;
while (testState != TEST_WRITER_LOCK_THREADS * 2 + 6);
EsPrint("Test complete!\n");
}
}
#endif
void KTimerSet(KTimer *timer, uint64_t triggerInMs, KAsyncTaskCallback _callback, EsGeneric _argument) {
KSpinlockAcquire(&scheduler.lock);
EsDefer(KSpinlockRelease(&scheduler.lock));
// Reset the timer state.
if (timer->item.list) {
scheduler.activeTimers.Remove(&timer->item);
}
KEventReset(&timer->event);
// Set the timer information.
timer->triggerTimeMs = triggerInMs + scheduler.timeMs;
timer->callback = _callback;
timer->argument = _argument;
timer->item.thisItem = timer;
// Add the timer to the list of active timers, keeping the list sorted by trigger time.
LinkedItem<KTimer> *_timer = scheduler.activeTimers.firstItem;
while (_timer) {
KTimer *timer2 = _timer->thisItem;
LinkedItem<KTimer> *next = _timer->nextItem;
if (timer2->triggerTimeMs > timer->triggerTimeMs) {
break; // Insert before this timer.
}
_timer = next;
}
if (_timer) {
scheduler.activeTimers.InsertBefore(&timer->item, _timer);
} else {
scheduler.activeTimers.InsertEnd(&timer->item);
}
}
void KTimerRemove(KTimer *timer) {
KSpinlockAcquire(&scheduler.lock);
EsDefer(KSpinlockRelease(&scheduler.lock));
if (timer->callback) {
KernelPanic("KTimer::Remove - Timers with callbacks cannot be removed.\n");
}
if (timer->item.list) {
scheduler.activeTimers.Remove(&timer->item);
}
}
void Scheduler::WaitMutex(KMutex *mutex) {
Thread *thread = GetCurrentThread();
if (thread->state != THREAD_ACTIVE) {
KernelPanic("Scheduler::WaitMutex - Attempting to wait on a mutex in a non-active thread.\n");
}
KSpinlockAcquire(&lock);
thread->state = THREAD_WAITING_MUTEX;
thread->blocking.mutex = mutex;
// Is the owner of this mutex executing?
// If not, there's no point in spinning on it.
bool spin = mutex && mutex->owner && mutex->owner->executing;
KSpinlockRelease(&lock);
if (!spin && thread->blocking.mutex->owner) {
ProcessorFakeTimerInterrupt();
}
// Early exit if this is a user request to block the thread and the thread is terminating.
while ((!thread->terminating || thread->terminatableState != THREAD_USER_BLOCK_REQUEST) && mutex->owner) {
thread->state = THREAD_WAITING_MUTEX;
}
thread->state = THREAD_ACTIVE;
}
uintptr_t Scheduler::WaitEvents(KEvent **events, size_t count) {
if (count > ES_MAX_WAIT_COUNT) {
KernelPanic("Scheduler::WaitEvents - count (%d) > ES_MAX_WAIT_COUNT (%d)\n", count, ES_MAX_WAIT_COUNT);
} else if (!count) {
KernelPanic("Scheduler::WaitEvents - Count is 0.\n");
} else if (!ProcessorAreInterruptsEnabled()) {
KernelPanic("Scheduler::WaitEvents - Interrupts disabled.\n");
}
Thread *thread = GetCurrentThread();
thread->blocking.eventCount = count;
LinkedItem<Thread> blockedItems[count]; // Max size 16 * 32 = 512.
EsMemoryZero(blockedItems, count * sizeof(LinkedItem<Thread>));
thread->blockedItems = blockedItems;
EsDefer(thread->blockedItems = nullptr);
for (uintptr_t i = 0; i < count; i++) {
thread->blockedItems[i].thisItem = thread;
thread->blocking.events[i] = events[i];
}
while (!thread->terminating || thread->terminatableState != THREAD_USER_BLOCK_REQUEST) {
thread->state = THREAD_WAITING_EVENT;
for (uintptr_t i = 0; i < count; i++) {
if (events[i]->autoReset) {
if (events[i]->state) {
thread->state = THREAD_ACTIVE;
if (__sync_val_compare_and_swap(&events[i]->state, true, false)) {
return i;
}
thread->state = THREAD_WAITING_EVENT;
}
} else {
if (events[i]->state) {
thread->state = THREAD_ACTIVE;
return i;
}
}
}
ProcessorFakeTimerInterrupt();
}
return -1; // Exited from termination.
}
uintptr_t KWaitEvents(KEvent **events, size_t count) {
return scheduler.WaitEvents(events, count);
}
void Scheduler::UnblockThread(Thread *unblockedThread, Thread *previousMutexOwner) {
KSpinlockAssertLocked(&lock);
if (unblockedThread->state == THREAD_WAITING_MUTEX) {
if (unblockedThread->item.list) {
// If we get here from KMutex::Release -> Scheduler::NotifyObject -> Scheduler::UnblockedThread,
// the mutex owner has already been cleared to nullptr, so use the previousMutexOwner parameter.
// But if we get here from Scheduler::TerminateThread, the mutex wasn't released;
// rather, the waiting thread was unblocked as it is in the WAIT system call, but needs to terminate.
if (!previousMutexOwner) {
KMutex *mutex = EsContainerOf(KMutex, blockedThreads, unblockedThread->item.list);
if (&mutex->blockedThreads != unblockedThread->item.list) {
KernelPanic("Scheduler::UnblockThread - Unblocked thread %x was not in a mutex blockedThreads list.\n",
unblockedThread);
}
previousMutexOwner = mutex->owner;
}
if (!previousMutexOwner->blockedThreadPriorities[unblockedThread->priority]) {
KernelPanic("Scheduler::UnblockThread - blockedThreadPriorities was zero (%x/%x).\n",
unblockedThread, previousMutexOwner);
}
previousMutexOwner->blockedThreadPriorities[unblockedThread->priority]--;
MaybeUpdateActiveList(previousMutexOwner);
unblockedThread->item.RemoveFromList();
}
} else if (unblockedThread->state == THREAD_WAITING_EVENT) {
for (uintptr_t i = 0; i < unblockedThread->blocking.eventCount; i++) {
if (unblockedThread->blockedItems[i].list) {
unblockedThread->blockedItems[i].RemoveFromList();
}
}
} else if (unblockedThread->state == THREAD_WAITING_WRITER_LOCK) {
if (unblockedThread->item.list) {
KWriterLock *lock = EsContainerOf(KWriterLock, blockedThreads, unblockedThread->item.list);
if (&lock->blockedThreads != unblockedThread->item.list) {
KernelPanic("Scheduler::UnblockThread - Unblocked thread %x was not in a writer lock blockedThreads list.\n",
unblockedThread);
}
if ((unblockedThread->blocking.writerLockType == K_LOCK_SHARED && lock->state >= 0)
|| (unblockedThread->blocking.writerLockType == K_LOCK_EXCLUSIVE && lock->state == 0)) {
unblockedThread->item.RemoveFromList();
}
}
} else {
KernelPanic("Scheduler::UnblockedThread - Blocked thread in invalid state %d.\n",
unblockedThread->state);
}
unblockedThread->state = THREAD_ACTIVE;
if (!unblockedThread->executing) {
// Put the unblocked thread at the start of the activeThreads list
// so that it is immediately executed when the scheduler yields.
AddActiveThread(unblockedThread, true);
}
// TODO If any processors are idleing, send them a yield IPI.
}
void Scheduler::NotifyObject(LinkedList<Thread> *blockedThreads, bool unblockAll, Thread *previousMutexOwner) {
KSpinlockAssertLocked(&lock);
LinkedItem<Thread> *unblockedItem = blockedThreads->firstItem;
if (!unblockedItem) {
// There weren't any threads blocking on the object.
return;
}
do {
LinkedItem<Thread> *nextUnblockedItem = unblockedItem->nextItem;
Thread *unblockedThread = unblockedItem->thisItem;
UnblockThread(unblockedThread, previousMutexOwner);
unblockedItem = nextUnblockedItem;
} while (unblockAll && unblockedItem);
}
#endif

2095
kernel/syscall.cpp Normal file

File diff suppressed because it is too large Load Diff

431
kernel/terminal.cpp Normal file
View File

@ -0,0 +1,431 @@
// TODO Everything in this file is a hack just so I can debug the kernel.
// Replace all of it!!!
#ifdef IMPLEMENTATION
#include <shared/vga_font.cpp>
#include "x86_64.h"
#define TERMINAL_ADDRESS ((uint16_t *) (LOW_MEMORY_MAP_START + 0xB8000))
#if 1
KSpinlock terminalLock;
KSpinlock printLock;
#else
KMutex terminalLock;
KMutex printLock;
#endif
void DebugWriteCharacter(uintptr_t character);
#ifdef ARCH_X86_COMMON
bool printToDebugger = false;
uintptr_t terminalPosition = 80;
#define EARLY_DEBUGGING
// #define VGA_TEXT_MODE
#ifdef EARLY_DEBUGGING
char kernelLog[262144];
uintptr_t kernelLogPosition;
#endif
static void TerminalCallback(int character, void *) {
if (!character) return;
KSpinlockAcquire(&terminalLock);
EsDefer(KSpinlockRelease(&terminalLock));
#ifdef EARLY_DEBUGGING
{
kernelLog[kernelLogPosition] = character;
kernelLogPosition++;
if (kernelLogPosition == sizeof(kernelLog)) kernelLogPosition = 0;
}
#endif
#ifdef VGA_TEXT_MODE
{
if (character == '\n') {
terminalPosition = terminalPosition - (terminalPosition % 80) + 80;
} else {
TERMINAL_ADDRESS[terminalPosition] = (uint16_t) character | 0x0700;
terminalPosition++;
}
if (terminalPosition >= 80 * 25) {
for (int i = 80; i < 80 * 25; i++) {
TERMINAL_ADDRESS[i - 80] = TERMINAL_ADDRESS[i];
}
for (int i = 80 * 24; i < 80 * 25; i++) {
TERMINAL_ADDRESS[i] = 0x700;
}
terminalPosition -= 80;
// uint64_t start = ProcessorReadTimeStamp();
// uint64_t end = start + 250 * KGetTimeStampTicksPerMs();
// while (ProcessorReadTimeStamp() < end);
}
{
ProcessorOut8(0x3D4, 0x0F);
ProcessorOut8(0x3D5, terminalPosition);
ProcessorOut8(0x3D4, 0x0E);
ProcessorOut8(0x3D5, terminalPosition >> 8);
}
}
#endif
{
ProcessorDebugOutputByte((uint8_t) character);
if (character == '\n') {
ProcessorDebugOutputByte((uint8_t) 13);
}
}
if (printToDebugger) {
DebugWriteCharacter(character);
if (character == '\t') DebugWriteCharacter(' ');
}
}
#endif
size_t debugRows, debugColumns, debugCurrentRow, debugCurrentColumn;
void DebugWriteCharacter(uintptr_t character) {
if (!graphics.target || !graphics.target->debugPutBlock) return;
if (debugCurrentRow == debugRows) {
debugCurrentRow = 0;
// uint64_t start = ProcessorReadTimeStamp();
// uint64_t end = start + 3000 * KGetTimeStampTicksPerMs();
// while (ProcessorReadTimeStamp() < end);
graphics.target->debugClearScreen();
}
uintptr_t row = debugCurrentRow;
uintptr_t column = debugCurrentColumn;
if (character == '\n') {
debugCurrentRow++;
debugCurrentColumn = 0;
return;
}
if (character > 127) character = ' ';
if (row >= debugRows) return;
if (column >= debugColumns) return;
for (int j = 0; j < VGA_FONT_HEIGHT; j++) {
uint8_t byte = ((uint8_t *) vgaFont)[character * 16 + j];
for (int i = 0; i < 8; i++) {
uint8_t bit = byte & (1 << i);
if (bit) graphics.target->debugPutBlock((column + 1) * 9 + i, row * 16 + j, false);
}
}
debugCurrentColumn++;
if (debugCurrentColumn == debugColumns) {
debugCurrentRow++;
debugCurrentColumn = 4;
}
}
void StartDebugOutput() {
if (graphics.target && graphics.target->debugClearScreen && graphics.target->debugPutBlock) {
debugRows = (graphics.height - 1) / VGA_FONT_HEIGHT;
debugColumns = (graphics.width - 1) / VGA_FONT_WIDTH - 2;
debugCurrentRow = debugCurrentColumn = 0;
printToDebugger = true;
graphics.target->debugClearScreen();
}
}
bool debugKeyPressed;
void KDebugKeyPressed() {
if (debugKeyPressed) return;
debugKeyPressed = true;
KernelPanic("Debug key pressed.\n");
}
uintptr_t DebugReadNumber() {
uintptr_t value = 0;
for (uintptr_t i = 0; i < 2 * sizeof(uintptr_t); i++) {
value <<= 4;
while (true) {
int key = KWaitKey();
if (key == ES_SCANCODE_0) { EsPrint("0"); value |= 0; }
else if (key == ES_SCANCODE_1) { EsPrint("1"); value |= 1; }
else if (key == ES_SCANCODE_2) { EsPrint("2"); value |= 2; }
else if (key == ES_SCANCODE_3) { EsPrint("3"); value |= 3; }
else if (key == ES_SCANCODE_4) { EsPrint("4"); value |= 4; }
else if (key == ES_SCANCODE_5) { EsPrint("5"); value |= 5; }
else if (key == ES_SCANCODE_6) { EsPrint("6"); value |= 6; }
else if (key == ES_SCANCODE_7) { EsPrint("7"); value |= 7; }
else if (key == ES_SCANCODE_8) { EsPrint("8"); value |= 8; }
else if (key == ES_SCANCODE_9) { EsPrint("9"); value |= 9; }
else if (key == ES_SCANCODE_A) { EsPrint("A"); value |= 10; }
else if (key == ES_SCANCODE_B) { EsPrint("B"); value |= 11; }
else if (key == ES_SCANCODE_C) { EsPrint("C"); value |= 12; }
else if (key == ES_SCANCODE_D) { EsPrint("D"); value |= 13; }
else if (key == ES_SCANCODE_E) { EsPrint("E"); value |= 14; }
else if (key == ES_SCANCODE_F) { EsPrint("F"); value |= 15; }
else if (key == ES_SCANCODE_ENTER) { value >>= 4; return value; }
else continue;
break;
}
}
return value;
}
void KernelPanic(const char *format, ...) {
ProcessorDisableInterrupts();
ProcessorSendIPI(KERNEL_PANIC_IPI, true);
// Disable synchronisation objects. The panic IPI must be sent before this,
// so other processors don't start getting "mutex not correctly acquired" panics.
scheduler.panic = true;
if (debugKeyPressed) {
DriversDumpState();
}
if (!printToDebugger) StartDebugOutput();
EsPrint("\n--- System Error ---\n>> ");
va_list arguments;
va_start(arguments, format);
_StringFormat(TerminalCallback, (void *) 0x4F00, format, arguments);
va_end(arguments);
EsPrint("Current thread = %x\n", GetCurrentThread());
EsPrint("Trace: %x\n", __builtin_return_address(0));
EsPrint("RSP: %x; RBP: %x\n", ProcessorGetRSP(), ProcessorGetRBP());
// EsPrint("Memory: %x/%x\n", pmm.pagesAllocated, pmm.startPageCount);
{
EsPrint("Threads:\n");
LinkedItem<Thread> *item = scheduler.allThreads.firstItem;
while (item) {
Thread *thread = item->thisItem;
EsPrint("%z %d %x @%x:%x ", (GetCurrentThread() == thread) ? "=>" : " ",
thread->id, thread, thread->interruptContext->rip, thread->interruptContext->rbp);
if (thread->state == THREAD_WAITING_EVENT) {
EsPrint("WaitEvent(Count:%d, %x) ", thread->blocking.eventCount, thread->blocking.events[0]);
} else if (thread->state == THREAD_WAITING_MUTEX) {
EsPrint("WaitMutex(%x, Owner:%d) ", thread->blocking.mutex, thread->blocking.mutex->owner->id);
} else if (thread->state == THREAD_WAITING_WRITER_LOCK) {
EsPrint("WaitWriterLock(%x, %d) ", thread->blocking.writerLock, thread->blocking.writerLockType);
}
Process *process = thread->process;
EsPrint("%z:%z\n", process->cExecutableName, thread->cName);
item = item->nextItem;
}
}
for (uintptr_t i = 0; i < KGetCPUCount(); i++) {
CPULocalStorage *local = KGetCPULocal(i);
if (local->panicContext) {
EsPrint("CPU %d LS %x RIP/RBP %x:%x TID %d\n", local->processorID, local,
local->panicContext->rip, local->panicContext->rbp,
local->currentThread ? local->currentThread->id : 0);
}
}
#ifdef EARLY_DEBUGGING
uintptr_t kernelLogEnd = kernelLogPosition;
EsPrint("Press 'D' to enter debugger.\n");
while (KWaitKey() != ES_SCANCODE_D);
graphics.debuggerActive = true;
while (true) {
#ifdef VGA_TEXT_MODE
for (uintptr_t i = 0; i < 80 * 25; i++) {
TERMINAL_ADDRESS[i] = 0x0700;
}
terminalPosition = 80;
#else
graphics.target->debugClearScreen();
debugCurrentRow = debugCurrentColumn = 0;
#endif
EsPrint("0 - view log\n1 - reset\n2 - view pmem\n3 - view vmem\n4 - stack trace\n");
int key = KWaitKey();
if (key == ES_SCANCODE_0) {
uintptr_t position = 0, nextPosition = 0;
uintptr_t x = 0, y = 0;
#ifdef VGA_TEXT_MODE
for (uintptr_t i = 0; i < 80 * 25; i++) {
TERMINAL_ADDRESS[i] = 0x0700;
}
#else
graphics.target->debugClearScreen();
#endif
while (position < kernelLogEnd) {
char c = kernelLog[position];
if (c != '\n') {
#ifdef VGA_TEXT_MODE
TERMINAL_ADDRESS[x + y * 80] = c | 0x0700;
#else
debugCurrentRow = y, debugCurrentColumn = x;
DebugWriteCharacter(c);
#endif
}
x++;
if (x ==
#ifdef VGA_TEXT_MODE
80
#else
debugColumns
#endif
|| c == '\n') {
x = 0;
y++;
if (y == 1) {
nextPosition = position;
}
}
if (y ==
#ifdef VGA_TEXT_MODE
25
#else
debugRows
#endif
) {
while (true) {
int key = KWaitKey();
if (key == ES_SCANCODE_SPACE || key == ES_SCANCODE_DOWN_ARROW) {
position = nextPosition;
break;
} else if (key == ES_SCANCODE_UP_ARROW) {
position = nextPosition;
if (position < 240) position = 0;
else position -= 240;
break;
}
}
#ifdef VGA_TEXT_MODE
for (uintptr_t i = 0; i < 80 * 25; i++) {
TERMINAL_ADDRESS[i] = 0x0700;
}
#else
graphics.target->debugClearScreen();
#endif
y = 0;
}
position++;
}
KWaitKey();
} else if (key == ES_SCANCODE_1) {
ArchResetCPU();
} else if (key == ES_SCANCODE_2) {
EsPrint("Enter address: ");
uintptr_t address = DebugReadNumber();
uintptr_t offset = address & (K_PAGE_SIZE - 1);
MMArchRemap(kernelMMSpace, pmm.pmManipulationRegion, address - offset);
uintptr_t *data = (uintptr_t *) ((uint8_t *) pmm.pmManipulationRegion + offset);
for (uintptr_t i = 0; i < 8 && (offset + 8 * sizeof(uintptr_t) < K_PAGE_SIZE); i++) {
EsPrint("\n%x - %x\n", address + 8 * sizeof(uintptr_t), data[i]);
}
while (KWaitKey() != ES_SCANCODE_ENTER);
} else if (key == ES_SCANCODE_3) {
EsPrint("Enter address: ");
uintptr_t address = DebugReadNumber();
uintptr_t offset = address & (K_PAGE_SIZE - 1);
uintptr_t *data = (uintptr_t *) address;
for (uintptr_t i = 0; i < 8 && (offset + i * sizeof(uintptr_t) < K_PAGE_SIZE); i++) {
EsPrint("\n%x - %x", address + i * sizeof(uintptr_t), data[i]);
}
while (KWaitKey() != ES_SCANCODE_ENTER);
} else if (key == ES_SCANCODE_4) {
EsPrint("Enter RBP: ");
uintptr_t address = DebugReadNumber();
while (address) {
EsPrint("\n%x", ((uintptr_t *) address)[1]);
address = ((uintptr_t *) address)[0];
}
while (KWaitKey() != ES_SCANCODE_ENTER);
}
}
#endif
ProcessorHalt();
}
void EsPrint(const char *format, ...) {
KSpinlockAcquire(&printLock);
EsDefer(KSpinlockRelease(&printLock));
va_list arguments;
va_start(arguments, format);
_StringFormat(TerminalCallback, (void *) 0x0700, format, arguments);
va_end(arguments);
}
void __KernelLog(const char *format, ...) {
va_list arguments;
va_start(arguments, format);
_StringFormat(TerminalCallback, nullptr, format, arguments);
va_end(arguments);
}
void KernelLog(KLogLevel level, const char *subsystem, const char *event, const char *format, ...) {
if (level == LOG_VERBOSE) return;
(void) event;
KSpinlockAcquire(&printLock);
EsDefer(KSpinlockRelease(&printLock));
__KernelLog("[%z:%z] ", level == LOG_INFO ? "Info" : level == LOG_ERROR ? "**Error**" : level == LOG_VERBOSE ? "Verbose" : "", subsystem);
va_list arguments;
va_start(arguments, format);
_StringFormat(TerminalCallback, nullptr, format, arguments);
va_end(arguments);
}
#endif

1407
kernel/windows.cpp Normal file

File diff suppressed because it is too large Load Diff

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