├── .gitattributes ├── .gitignore ├── .vscode ├── c_cpp_properties.json └── settings.json ├── CHANGELOG.md ├── CMake ├── FindTools.cmake └── pico_sdk_import.cmake ├── CMakeLists.txt ├── Docs ├── Debug │ ├── 0016_open-issues.md │ ├── 0021_sometimes-we-hang-on-input.md │ └── Resolved │ │ ├── 0001_out-of-memory.md │ │ ├── 0002_debug-mutex-in-interrupt.md │ │ ├── 0004_destroy-thread-in-scheduler.md │ │ ├── 0005_interrupts-disabled-in-dummy-thread.md │ │ ├── 0006_infinite-loop-in-crash-report.md │ │ ├── 0007_allocations-in-system-handler.md │ │ ├── 0008_interrupts-disabled.md │ │ ├── 0009_second-syscall-fails.md │ │ ├── 0010_refcount-of-default-thread.md │ │ ├── 0011_ref-ptr-is-broken.md │ │ ├── 0012_blocking-default-thread.md │ │ ├── 0013_deadlock.md │ │ ├── 0014_lock-in-boot.md │ │ ├── 0015_crash-on-new-process.md │ │ ├── 0017_crash-on-system-call.md │ │ ├── 0018_deadlock.md │ │ ├── 0019_page-allocator.md │ │ ├── 0020_assert-on-exit.md │ │ ├── 0022_fault-in-editor.md │ │ └── 0023_memmove-is-buggy.md ├── demo.gif └── demo.mp4 ├── Kernel ├── ABI.cpp ├── ABI.hpp ├── ConsoleDevice.cpp ├── ConsoleDevice.hpp ├── FileSystem │ ├── DeviceFileSystem.cpp │ ├── DeviceFileSystem.hpp │ ├── FileSystem.cpp │ ├── FileSystem.hpp │ ├── FlashFileSystem.cpp │ ├── FlashFileSystem.hpp │ ├── MemoryFileSystem.cpp │ ├── MemoryFileSystem.hpp │ ├── VirtualFileSystem.cpp │ └── VirtualFileSystem.hpp ├── Forward.hpp ├── GlobalMemoryAllocator.cpp ├── GlobalMemoryAllocator.hpp ├── HandlerMode.hpp ├── Interface │ ├── System.hpp │ └── Types.hpp ├── Interrupt │ ├── UART.cpp │ └── UART.hpp ├── KernelMutex.cpp ├── KernelMutex.hpp ├── Loader.cpp ├── Loader.hpp ├── MPU.hpp ├── PageAllocator.cpp ├── PageAllocator.hpp ├── Process.cpp ├── Process.hpp ├── Result.hpp ├── StackWrapper.hpp ├── Synchronization │ ├── AbstractLock.hpp │ ├── HardwareSpinLock.hpp │ ├── LockGuard.hpp │ └── MaskedInterruptGuard.hpp ├── SystemHandler.cpp ├── SystemHandler.hpp ├── Threads │ ├── Scheduler.cpp │ ├── Scheduler.hpp │ ├── Thread.cpp │ └── Thread.hpp ├── cpu.S └── main.cpp ├── LICENSE ├── README.md ├── Std ├── ArmedScopeGuard.hpp ├── Array.hpp ├── CircularQueue.hpp ├── Concepts.hpp ├── Format.cpp ├── Format.hpp ├── Forward.cpp ├── Forward.hpp ├── HashMap.hpp ├── HashTable.hpp ├── Lexer.hpp ├── MemoryAllocator.cpp ├── MemoryAllocator.hpp ├── Optional.hpp ├── OwnPtr.hpp ├── Path.hpp ├── RefPtr.hpp ├── Result.hpp ├── Singleton.hpp ├── SortedSet.hpp ├── Span.hpp ├── String.hpp ├── StringBuilder.hpp ├── StringView.hpp ├── Types.hpp └── Vector.hpp ├── TODO.md ├── Tests ├── .gitignore ├── CMakeLists.txt ├── Std │ ├── TestArmedScopeGuard.cpp │ ├── TestCircularQueue.cpp │ ├── TestFormat.cpp │ ├── TestHashMap.cpp │ ├── TestHashTable.cpp │ ├── TestLexer.cpp │ ├── TestMemoryAllocator.cpp │ ├── TestOptional.cpp │ ├── TestOwnPtr.cpp │ ├── TestPath.cpp │ ├── TestRefPtr.cpp │ ├── TestSingleton.cpp │ ├── TestSortedSet.cpp │ ├── TestSpan.cpp │ ├── TestString.cpp │ ├── TestStringBuilder.cpp │ ├── TestStringView.cpp │ └── TestVector.cpp ├── TestSuite.cpp └── TestSuite.hpp ├── Tools ├── .gitignore ├── .vscode │ └── settings.json ├── CMakeLists.txt ├── ElfEmbed.cpp ├── FileSystem.cpp ├── FileSystem.hpp └── LibElf │ ├── Generator.cpp │ ├── Generator.hpp │ ├── MemoryStream.cpp │ ├── MemoryStream.hpp │ ├── RelocationTable.cpp │ ├── RelocationTable.hpp │ ├── StringTable.cpp │ ├── StringTable.hpp │ ├── SymbolTable.cpp │ └── SymbolTable.hpp ├── Userland ├── .gitignore ├── CMakeLists.txt ├── Editor.c ├── Example.c ├── LibC │ ├── assert.c │ ├── assert.h │ ├── ctype.c │ ├── ctype.h │ ├── dirent.c │ ├── dirent.h │ ├── errno.c │ ├── errno.h │ ├── fcntl.c │ ├── fcntl.h │ ├── malloc.c │ ├── malloc.h │ ├── readline │ │ ├── readline.c │ │ └── readline.h │ ├── spawn.c │ ├── spawn.h │ ├── stdarg.h │ ├── stddef.h │ ├── stdint.h │ ├── stdio.c │ ├── stdio.h │ ├── stdlib.c │ ├── stdlib.h │ ├── string.c │ ├── string.h │ ├── sys │ │ ├── abi.c │ │ ├── abi.h │ │ ├── crt0.S │ │ ├── crt0.c │ │ ├── stat.c │ │ ├── stat.h │ │ ├── system.c │ │ ├── system.h │ │ ├── types.h │ │ ├── wait.c │ │ └── wait.h │ ├── unistd.c │ └── unistd.h ├── Shell.c └── Userland.x └── tasks.py /.gitattributes: -------------------------------------------------------------------------------- 1 | *.mkv filter=lfs diff=lfs merge=lfs -text 2 | *.mp4 filter=lfs diff=lfs merge=lfs -text 3 | *.gif filter=lfs diff=lfs merge=lfs -text 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /Build/ 2 | -------------------------------------------------------------------------------- /.vscode/c_cpp_properties.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": [ 3 | { 4 | "name": "Linux", 5 | "includePath": [ 6 | "${workspaceFolder}/**" 7 | ], 8 | "defines": [], 9 | "compilerPath": "/usr/bin/clang", 10 | "cStandard": "c11", 11 | "cppStandard": "c++20", 12 | "intelliSenseMode": "clang-arm", 13 | "compileCommands": "${workspaceFolder}/Build/compile_commands.json" 14 | } 15 | ], 16 | "version": 4 17 | } 18 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.insertFinalNewline": true, 3 | "files.trimTrailingWhitespace": true, 4 | "editor.tabSize": 4, 5 | "editor.detectIndentation": false, 6 | "editor.insertSpaces": true, 7 | "cmake.buildDirectory": "${workspaceFolder}/Build/IntelliSense", 8 | "files.exclude": { 9 | ".git/": true, 10 | "Build/": true, 11 | }, 12 | "editor.snippetSuggestions": "none", 13 | "C_Cpp.autoAddFileAssociations": false, 14 | "editor.minimap.enabled": false, 15 | "C_Cpp.errorSquiggles": "Disabled", 16 | "cmake.configureOnOpen": false, 17 | } 18 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ### Major 2 | 3 | - Added `AbstractLock` which is used by the new `LockGuard`. 4 | 5 | - Added `HardwareSpinLock` which uses the memory mapped spin locks that the processor provides. 6 | -------------------------------------------------------------------------------- /CMake/FindTools.cmake: -------------------------------------------------------------------------------- 1 | if(NOT DEFINED Tools_FOUND) 2 | set(Tools_FOUND TRUE) 3 | 4 | include(ExternalProject) 5 | ExternalProject_Add(Tools 6 | SOURCE_DIR ${CMAKE_SOURCE_DIR}/Tools 7 | BINARY_DIR ${CMAKE_BINARY_DIR}/Tools 8 | INSTALL_COMMAND "" 9 | BUILD_ALWAYS TRUE) 10 | 11 | set(ELF_EMBED_EXECUTABLE ${CMAKE_BINARY_DIR}/Tools/ElfEmbed) 12 | 13 | add_custom_target(ElfEmbed) 14 | add_dependencies(ElfEmbed Tools) 15 | endif() 16 | -------------------------------------------------------------------------------- /CMake/pico_sdk_import.cmake: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2020 Raspberry Pi (Trading) Ltd. 2 | # 3 | # SPDX-License-Identifier: BSD-3-Clause 4 | # 5 | 6 | # This is a copy of /external/pico_sdk_import.cmake 7 | 8 | # This can be dropped into an external project to help locate this SDK 9 | # It should be include()ed prior to project() 10 | 11 | if (DEFINED ENV{PICO_SDK_PATH} AND (NOT PICO_SDK_PATH)) 12 | set(PICO_SDK_PATH $ENV{PICO_SDK_PATH}) 13 | message("Using PICO_SDK_PATH from environment ('${PICO_SDK_PATH}')") 14 | endif () 15 | 16 | if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT} AND (NOT PICO_SDK_FETCH_FROM_GIT)) 17 | set(PICO_SDK_FETCH_FROM_GIT $ENV{PICO_SDK_FETCH_FROM_GIT}) 18 | message("Using PICO_SDK_FETCH_FROM_GIT from environment ('${PICO_SDK_FETCH_FROM_GIT}')") 19 | endif () 20 | 21 | if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_PATH} AND (NOT PICO_SDK_FETCH_FROM_GIT_PATH)) 22 | set(PICO_SDK_FETCH_FROM_GIT_PATH $ENV{PICO_SDK_FETCH_FROM_GIT_PATH}) 23 | message("Using PICO_SDK_FETCH_FROM_GIT_PATH from environment ('${PICO_SDK_FETCH_FROM_GIT_PATH}')") 24 | endif () 25 | 26 | set(PICO_SDK_PATH "${PICO_SDK_PATH}" CACHE PATH "Path to the Raspberry Pi Pico SDK") 27 | set(PICO_SDK_FETCH_FROM_GIT "${PICO_SDK_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of SDK from git if not otherwise locatable") 28 | set(PICO_SDK_FETCH_FROM_GIT_PATH "${PICO_SDK_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download SDK") 29 | 30 | if (NOT PICO_SDK_PATH) 31 | if (PICO_SDK_FETCH_FROM_GIT) 32 | include(FetchContent) 33 | set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR}) 34 | if (PICO_SDK_FETCH_FROM_GIT_PATH) 35 | get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_SDK_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}") 36 | endif () 37 | FetchContent_Declare( 38 | pico_sdk 39 | GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk 40 | GIT_TAG master 41 | ) 42 | if (NOT pico_sdk) 43 | message("Downloading Raspberry Pi Pico SDK") 44 | FetchContent_Populate(pico_sdk) 45 | set(PICO_SDK_PATH ${pico_sdk_SOURCE_DIR}) 46 | endif () 47 | set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE}) 48 | else () 49 | message(FATAL_ERROR 50 | "SDK location was not specified. Please set PICO_SDK_PATH or set PICO_SDK_FETCH_FROM_GIT to on to fetch from git." 51 | ) 52 | endif () 53 | endif () 54 | 55 | get_filename_component(PICO_SDK_PATH "${PICO_SDK_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}") 56 | if (NOT EXISTS ${PICO_SDK_PATH}) 57 | message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' not found") 58 | endif () 59 | 60 | set(PICO_SDK_INIT_CMAKE_FILE ${PICO_SDK_PATH}/pico_sdk_init.cmake) 61 | if (NOT EXISTS ${PICO_SDK_INIT_CMAKE_FILE}) 62 | message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' does not appear to contain the Raspberry Pi Pico SDK") 63 | endif () 64 | 65 | set(PICO_SDK_PATH ${PICO_SDK_PATH} CACHE PATH "Path to the Raspberry Pi Pico SDK" FORCE) 66 | 67 | include(${PICO_SDK_INIT_CMAKE_FILE}) 68 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.19.5) 2 | 3 | set(CMAKE_EXPORT_COMPILE_COMMANDS ON) 4 | 5 | if (NOT CMAKE_BUILD_TYPE) 6 | set(CMAKE_BUILD_TYPE Debug) 7 | endif() 8 | 9 | set(CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/CMake) 10 | find_package(Tools MODULE) 11 | 12 | include(CMake/pico_sdk_import.cmake) 13 | 14 | project(Pico C CXX ASM) 15 | set(CMAKE_C_STANDARD 11) 16 | set(CMAKE_CXX_STANDARD 20) 17 | 18 | pico_sdk_init() 19 | 20 | # FIXME: This is mostly necessary for the SDK 21 | set(DISABLE_WARNINGS -Wno-unused-parameter -Wno-type-limits) 22 | set(DISABLE_CXX_WARNINGS -Wno-reorder -Wno-ignored-qualifiers) 23 | 24 | add_library(project_options INTERFACE) 25 | target_compile_options(project_options INTERFACE -fdiagnostics-color=always -Wall ${DISABLE_WARNINGS} -Wextra -Werror -O0) 26 | target_compile_options(project_options INTERFACE $<$:-frtti ${DISABLE_CXX_WARNINGS}>) 27 | target_include_directories(project_options INTERFACE ${CMAKE_SOURCE_DIR}) 28 | 29 | add_subdirectory(Userland) 30 | 31 | file(GLOB_RECURSE Kernel_SOURCES CONFIGURE_DEPENDS Kernel/*.cpp Kernel/*.S Std/*.cpp) 32 | 33 | add_executable(Kernel.1 ${Kernel_SOURCES}) 34 | target_link_libraries(Kernel.1 pico_stdlib pico_bootrom hardware_dma project_options LibEmbeddedFiles) 35 | target_compile_definitions(Kernel.1 PRIVATE KERNEL) 36 | pico_add_extra_outputs(Kernel.1) 37 | 38 | add_custom_target(Kernel.elf ALL 39 | COMMAND arm-none-eabi-objcopy 40 | --strip-symbol=__flash_data_2 41 | --strip-symbol=__flash_data_3 42 | --strip-symbol=__flash_data_4 43 | --strip-symbol=__flash_data_5 44 | --strip-symbol=__flash_base 45 | Kernel.1.elf Kernel.elf 46 | DEPENDS Kernel.1) 47 | -------------------------------------------------------------------------------- /Docs/Debug/0016_open-issues.md: -------------------------------------------------------------------------------- 1 | commitid 1c006cdf0ee197ff5c560158fdde5b74cf7a2262 2 | 3 | There are tons of open problems that need to be resolved. 4 | 5 | ### Ideas 6 | 7 | - Verify that we do not allocate in a interrupt handlers. 8 | 9 | - I should add an assertion that checks that if we schedule away from something, it can't be holding any locks. 10 | 11 | - I should use the builtin mutex things from the processor. 12 | 13 | - I really need to cut out the Pico SDK thing entirely. 14 | The whole memory allocation thing is a mess. 15 | 16 | - Essentially, the `KernelMutex` was my attempt at multi-core support, however, they don't really do anything, 17 | other than to cause trouble. 18 | 19 | I need more sophisticated debugging tools to be able to deal with the whole multi-core ordeal. 20 | 21 | - I should verify that my `memmove` implementations are working correctly. 22 | 23 | - I need to implement a proper malloc. 24 | -------------------------------------------------------------------------------- /Docs/Debug/0021_sometimes-we-hang-on-input.md: -------------------------------------------------------------------------------- 1 | commitid 95e46e7f72f7a10eab41443b0a9df522f7dd3f90 2 | 3 | When I type quickly, something can get stuck and we hang. 4 | -------------------------------------------------------------------------------- /Docs/Debug/Resolved/0001_out-of-memory.md: -------------------------------------------------------------------------------- 1 | commitid a0f56d6fd445073e8ce100f2433a555ed0e9b6bf 2 | 3 | After startup, we print out the shell prompt and then run out of memory. 4 | 5 | ### Notes 6 | 7 | - The memory allocation occurs in the `write()` system call in 8 | `String::format`. 9 | 10 | - All allocations are very small, so we are not running out of memory any 11 | time soon. We startup with 16KiB of heap which is a lot for this system. 12 | 13 | - After turning on the debug mode of the memory allocator, we run into an 14 | assertion in the scheduler. This is debugged seperately in 15 | `Docs/Debug/0002_*`. 16 | 17 | - It appears that we are allocating in `dbgln` when calling 18 | `Kernel::syscall`. 19 | 20 | This is not ok, because the implementation is not able to deal with 21 | interrupts. 22 | 23 | - Now we end up in the default thread with interrupts disabled, this is 24 | debugged in `0003`. 25 | 26 | ### Ideas 27 | 28 | - Try running the test suite. 29 | 30 | - Which memory allocation can not be fulfilled? 31 | 32 | - Go through the commit history. 33 | 34 | ### Theories 35 | 36 | - Maybe we allocate from an interrupt handler and this is an syncronization 37 | issue. 38 | 39 | ### Approach 40 | 41 | - We systematically syncronize with `PRIMASK` and a mutex. 42 | 43 | 3480fc7d289248d9fc5359ec5ca4ada12fdcb11b 44 | -------------------------------------------------------------------------------- /Docs/Debug/Resolved/0002_debug-mutex-in-interrupt.md: -------------------------------------------------------------------------------- 1 | commitid 77a87c4077d5c17f94ebeb926655de89c457da18 2 | 3 | We appear to call `Kernel::KernelMutex::unlock` in the PendSV interrupt 4 | handler. 5 | 6 | ### Notes 7 | 8 | - This appears to be caused by the destructor of `Kernel::Thread` which calls 9 | `dbgln`. 10 | 11 | ### Ideas 12 | 13 | - We should not interact with the mutex in the interrupt handler, even if 14 | this messes up the output. We may have to do other syncronization though. 15 | 16 | Maybe we should not write debug messages in interrupt handlers for now. 17 | 18 | ### Changes 19 | 20 | - Ignore debug messages in handler mode and print message at next 21 | oppurtunity. 22 | 23 | 91166f657e0a0643091b8c2af95d621c06807895 24 | -------------------------------------------------------------------------------- /Docs/Debug/Resolved/0004_destroy-thread-in-scheduler.md: -------------------------------------------------------------------------------- 1 | commitid 1e6e5ae5e674aa4032153bd07037ea1226b1e6ba 2 | 3 | ### Notes 4 | 5 | - Somehow, we end up calling `~Thread` in `schedule_next` which seems to cause an infinite loop. 6 | 7 | - When destorying a thread, we seem to release a mutex that causes the thread to be destoryed again. 8 | Essentially, we are increasing the reference count while the destruction logic runs. 9 | 10 | - The symptom was, that we crashed when running `a` in `Editor.elf`. 11 | That is gone now, but we crash before `Editor.elf` can come up. 12 | 13 | ### Conclusions 14 | 15 | - The problem was solved by adding an `m_in_cleanup` member to `RefCounted`. 16 | That way, we ensure that the destructor is only called once. 17 | -------------------------------------------------------------------------------- /Docs/Debug/Resolved/0006_infinite-loop-in-crash-report.md: -------------------------------------------------------------------------------- 1 | commitid a0049d081678b4376cd65d12b9fb8ad3156725bd 2 | 3 | ### Notes 4 | 5 | - I tried disabling the interrupt stuff but that did not help. 6 | 7 | - I was unconditionally calling `delete[]` in `~Vector` even if the inline data was used. 8 | 9 | ### Theories 10 | 11 | - I suspect, that the `restore_interrupts` or the `disable_interrupts` call isn't working properly. 12 | 13 | ### Actions 14 | 15 | - I only call `delete[]` in `~Vector` if a buffer actually exists. 16 | -------------------------------------------------------------------------------- /Docs/Debug/Resolved/0007_allocations-in-system-handler.md: -------------------------------------------------------------------------------- 1 | commitid a0049d081678b4376cd65d12b9fb8ad3156725bd 2 | 3 | ### Notes 4 | 5 | - Currently, the system handler allocates in order to create a `RefPtr` which is used to do the actual work. 6 | 7 | ```none 8 | #0 Std::crash (format=0x100146f0 "VERIFY(%condition)\n%file:%line\n", condition=0x10014710 "Kernel::is_executing_in_thread_mode()", file=0x10014774 "/home/me/dev/pico-os/Kernel/GlobalMemoryAllocator.cpp", line=23) at /home/me/dev/pico-os/Std/Forward.cpp:82 9 | #1 0x10005368 in Kernel::GlobalMemoryAllocator::allocate (this=0x200012a8 ::m_instance>, size=60, debug_override=true, address=0x1000b905 ::construct(Std::String&)+16>) at /home/me/dev/pico-os/Kernel/GlobalMemoryAllocator.cpp:23 10 | #2 0x1000548a in operator new (size=60) at /home/me/dev/pico-os/Kernel/GlobalMemoryAllocator.cpp:64 11 | #3 0x1000b904 in Std::RefCounted::construct () at /home/me/dev/pico-os/Std/RefPtr.hpp:114 12 | #4 0x1000b792 in Kernel::syscall (context=0x20018ef8) at /home/me/dev/pico-os/Kernel/SystemHandler.cpp:29 13 | #5 0x1000f9f4 in isr_svcall () at /home/me/dev/pico-os/Kernel/cpu.S:78 14 | ``` 15 | 16 | - I had some difficulties, convincing the system handler to block until more requests come through. 17 | Currently, I have that code commented out. 18 | 19 | ### Ideas 20 | 21 | - I could add an `m_requested_system_call` flag and then handle that flag in another thread. 22 | -------------------------------------------------------------------------------- /Docs/Debug/Resolved/0008_interrupts-disabled.md: -------------------------------------------------------------------------------- 1 | commitid 9ce5f12a3af19ff09fdc638dafae7ab100c21b87 2 | 3 | Somehow, we end up executing `wfi` with interrupts disabled. 4 | Luckily, I placed an assertion there. 5 | 6 | ### Notes 7 | 8 | - This isn't the first time I am investigating this issue. 9 | 10 | - The `Kernel::are_interrupts_enabled` was implemented the wrong way around. 11 | 12 | ### Ideas 13 | 14 | - Verify the `PRIMASK` implementation. 15 | 16 | ### Theories 17 | 18 | - I suspect, that my implementation of the `PRIMASK` helpers don't work correctly. 19 | 20 | ### Actions 21 | 22 | - Change `return primask` to `return primask == 0`. 23 | -------------------------------------------------------------------------------- /Docs/Debug/Resolved/0011_ref-ptr-is-broken.md: -------------------------------------------------------------------------------- 1 | commitid 6a760d0be77e24aa187303c96624302150efc398 2 | 3 | It seems that my `RefPtr` implementation is broken somehow. 4 | 5 | ### Notes 6 | 7 | - I do not understand which constructor was used by `RefCounted::construct` it makes no sense to me. 8 | 9 | - After some testing, it seems that the `RefPtr` implementation itself is fine. 10 | 11 | - It seems that `String` or `StringView` is broken. 12 | 13 | In particular, it seems that `builder.string().view()` is broken, even if we cache the result elsewhere. 14 | 15 | - It turns out that `ImmutableString` includes the null terminator in it's size, thus the resulting view is broken. 16 | 17 | ### Ideas 18 | 19 | ### Theories 20 | 21 | - I suspect, that the null terminator is included in the `size()` result. 22 | 23 | ### Actions 24 | 25 | - The solution was to subtract one when computing the size of a string, I suspect, that this was already correct before I made 26 | strings immutable. 27 | -------------------------------------------------------------------------------- /Docs/Debug/Resolved/0012_blocking-default-thread.md: -------------------------------------------------------------------------------- 1 | commitid d3d02691235c419173085049b4b51af212a1b974 2 | 3 | It seems that the default thread is blocking on a `KernelMutex`. 4 | 5 | ### Notes 6 | 7 | - When debugging, I saw that `m_holding_thread` was null which makes no sense. 8 | I was messing around with the debugger quite a bit, so this could be a coincidence. 9 | 10 | - It seems we have some sort of deadlock now. 11 | The first two system calls work and the system is running. 12 | 13 | However, when I press a key, the system crashes and I see the second `~Thread` message. 14 | 15 | - After adding the fallback thread, we are now crashing here: 16 | 17 | ```none 18 | #0 Std::crash (format=0x1001782c "VERIFY(%condition)\n%file:%line\n", condition=0x10017af0 "m_enabled", file=0x10017a5c "/home/me/dev/pico-os/Kernel/Threads/Scheduler.cpp", line=104) at /home/me/dev/pico-os/Std/Forward.cpp:82 19 | #1 0x1000c5d8 in Kernel::Scheduler::trigger (this=0x20001328 ::m_instance>) at /home/me/dev/pico-os/Kernel/Threads/Scheduler.cpp:104 20 | #2 0x10004f34 in Kernel::KernelMutex::lock (this=0x200013cc ) at /home/me/dev/pico-os/Kernel/KernelMutex.hpp:47 21 | #3 0x100115b0 in Std::dbgln_raw (str=...) at /home/me/dev/pico-os/Std/Format.cpp:41 22 | #4 0x10007680 in Std::dbgln<>(Std::StringView) (fmtstr=...) at /home/me/dev/pico-os/Std/Format.hpp:268 23 | #5 0x1000738e in Std::dbgln<>(char const*) (fmtstr=0x10017550 "[SystemHandler] Before:") at /home/me/dev/pico-os/Std/Format.hpp:274 24 | #6 0x1000b574 in operator() (__closure=0x2001c7fc) at /home/me/dev/pico-os/Kernel/SystemHandler.cpp:54 25 | #7 0x1000bb10 in operator() (__closure=0x2001c7f8) at /home/me/dev/pico-os/Kernel/Threads/Thread.hpp:70 26 | #8 0x1000c118 in type_erased_member_function_wrapper >(Kernel::SystemHandler::handle_next_waiting_thread()::&&)::, &Kernel::Thread::setup_context >(Kernel::SystemHandler::handle_next_waiting_thread()::&&)::::operator()>(void *) (object=0x2001c7f8) at /home/me/dev/pico-os/Std/Forward.hpp:101 27 | #9 0x00000000 in ?? () 28 | ``` 29 | 30 | My understanding is, that we masked `m_enabled` for something but then we use `Scheduler::trigger()` anyways. 31 | I think that was necessary to print out the state of the scheduler. 32 | 33 | ### Ideas 34 | 35 | ### Theories 36 | 37 | ### Actions 38 | 39 | - I added a fallback thread that can be scheduled if the default thread is blocked. 40 | 41 | - I ensured that `m_enabled` isn't disabled if we could block on a mutex. 42 | -------------------------------------------------------------------------------- /Docs/Debug/Resolved/0014_lock-in-boot.md: -------------------------------------------------------------------------------- 1 | commitid 11615ec31283da624383387370686395068d9e7b 2 | 3 | We aquire a `KernelMutex` during the boot process, that seems dangerous. 4 | 5 | ### Notes 6 | 7 | - We hit an assertion, because interrupts aren't enabled but we aquire a mutex. 8 | 9 | - We enter `boot` with interrupts enabled and in thread mode. 10 | That's not what I expected. 11 | 12 | - Now, we are crashing here, not sure if this is even relevant to this ticket anymore. 13 | 14 | ```none 15 | #0 Std::crash (format=0x10015c58 "VERIFY(%condition)\n%file:%line\n", condition=0x10015c48 "were_enabled", file=0x10015c1c "/home/me/dev/pico-os/Kernel/HandlerMode.hpp", line=83) at /home/me/dev/pico-os/Std/Forward.cpp:82 16 | #1 0x10004dba in Kernel::MaskedInterruptGuard::MaskedInterruptGuard (this=0x20014f10) at /home/me/dev/pico-os/Kernel/HandlerMode.hpp:83 17 | #2 0x10004fea in Kernel::KernelMutex::lock (this=0x20001428 ) at /home/me/dev/pico-os/Kernel/KernelMutex.hpp:40 18 | #3 0x100052dc in Kernel::GlobalMemoryAllocator::allocate (this=0x200012f0 ::m_instance>, size=40, debug_override=true, address=0x10003611 ) at /home/me/dev/pico-os/Kernel/GlobalMemoryAllocator.cpp:31 19 | #4 0x100053ea in operator new (size=40) at /home/me/dev/pico-os/Kernel/GlobalMemoryAllocator.cpp:69 20 | #5 0x10003610 in Kernel::FlashFileSystem::FlashFileSystem (this=0x200012d0 ::m_instance>) at /home/me/dev/pico-os/Kernel/FileSystem/FlashFileSystem.cpp:13 21 | #6 0x100114ee in Std::Singleton::initialize<>() () at /home/me/dev/pico-os/Std/Singleton.hpp:32 22 | #7 0x10010ff2 in Kernel::boot_with_scheduler () at /home/me/dev/pico-os/Kernel/main.cpp:59 23 | #8 0x10011284 in Kernel::Thread::setup_context(void (&)())::{lambda()#1}::operator()() (__closure=0x20014ff8) at /home/me/dev/pico-os/Kernel/Threads/Thread.hpp:68 24 | #9 0x1001163e in type_erased_member_function_wrapper(void (&)())::, &Kernel::Thread::setup_context(void (&)())::::operator()>(void *) (object=0x20014ff8) at /home/me/dev/pico-os/Std/Forward.hpp:101 25 | #10 0x00000000 in ?? () 26 | ``` 27 | 28 | - In `Kernel::boot_with_scheduler`, we do enter with `PRIMASK=0` and `IPSR=0` which means thread mode with interrupts enabled. 29 | That is the expected behaviour. 30 | 31 | - In `GlobalMemoryAllocator::allocate`, we disable interrupts before taking the mutex. 32 | 33 | - For whatever reason, stupid me thought, that I should allocate from interrupts. 34 | That is a bad idea. 35 | 36 | ba6e1db0f80de09ff23d48f8a87ba7dea6957cae 37 | 38 | I hope that there isn't code that does that. 39 | 40 | ### Ideas 41 | 42 | ### Actions 43 | 44 | - We are now creating the `SystemHandler` thread later, when the scheduler is already running. 45 | 46 | - I made it possible to disable a `KernelMutex` during startup. 47 | 48 | - I removed the interrupt guards from the memory allocation logic. 49 | -------------------------------------------------------------------------------- /Docs/Debug/Resolved/0015_crash-on-new-process.md: -------------------------------------------------------------------------------- 1 | commitid 4a09ce9d17e5d8d8cabc7dabea3d8c8cf301215f 2 | 3 | We crash when creating a new process. 4 | 5 | ### Notes 6 | 7 | - Now, we crash when creating a process, because I am calling `add_thread` without masking interrupts. 8 | 9 | ### Actions 10 | 11 | - I added a `MaskedInterruptGuard` thing when creating a new process. 12 | -------------------------------------------------------------------------------- /Docs/Debug/Resolved/0017_crash-on-system-call.md: -------------------------------------------------------------------------------- 1 | commitid c289fc62fcea89008172d7d523b9199cab63e5be 2 | 3 | We crash when making a system call. 4 | 5 | ### Notes 6 | 7 | - ```none 8 | [SystemHandler] Dealing with system call for 'Process: /bin/Shell.elf' 9 | VERIFY(were_enabled) 10 | /home/me/dev/pico-os/Kernel/HandlerMode.hpp:0x00000053 11 | ``` 12 | 13 | ```none 14 | #0 Std::crash (format=0x10015c90 "VERIFY(%condition)\n%file:%line\n", condition=0x10015c80 "were_enabled", file=0x10015c54 "/home/me/dev/pico-os/Kernel/HandlerMode.hpp", line=83) at /home/me/dev/pico-os/Std/Forward.cpp:82 15 | #1 0x10004d9a in Kernel::MaskedInterruptGuard::MaskedInterruptGuard (this=0x20016fc4) at /home/me/dev/pico-os/Kernel/HandlerMode.hpp:83 16 | #2 0x1000ba80 in operator() (__closure=0x20016ffc) at /home/me/dev/pico-os/Kernel/SystemHandler.cpp:86 17 | #3 0x1000c0ac in operator() (__closure=0x20016ff8) at /home/me/dev/pico-os/Kernel/Threads/Thread.hpp:68 18 | #4 0x1000c46a in type_erased_member_function_wrapper >(Kernel::SystemHandler::SystemHandler()::&&)::, &Kernel::Thread::setup_context >(Kernel::SystemHandler::SystemHandler()::&&)::::operator()>(void *) (object=0x20016ff8) at /home/me/dev/pico-os/Std/Forward.hpp:101 19 | ``` 20 | 21 | - In the `SystemHandler`, we were masking interrupts to avoid a lost wakeup problem. 22 | However, we were also using `MaskedInterruptGuard`. 23 | 24 | ### Actions 25 | 26 | - Remove the redundant use of `MaskedInterruptGuard`. 27 | -------------------------------------------------------------------------------- /Docs/Debug/Resolved/0018_deadlock.md: -------------------------------------------------------------------------------- 1 | commitid c289fc62fcea89008172d7d523b9199cab63e5be 2 | 3 | We appear to deadlock after using `sys$write`. 4 | 5 | ### Notes 6 | 7 | - This could be the deadlock issue that I encountered before. 8 | 9 | - We do get the `> ` output, but after that we simply freeze. 10 | Pressing keys doesn't appear to do anything. 11 | 12 | - It seems that the output of `sys$write` and the debug output are interleaved. 13 | That suggests a lack of synchronization: 14 | 15 | ```none 16 | [SystemHandler] Dealing with system call for 'Process: /bin/Shell.elf' 17 | [Thread::~Thread] m_name='Worke r: '/bin/Shell.elf' (PID 0x00000000)' 18 | [Thread::setup_context::lambda] Thread 'Worker: '/bin/Shell.elf' (PID 0x00000000)' is about to die. 19 | ``` 20 | 21 | Notice the `Worke r`. 22 | 23 | - There seems to be some race, another time I was able to get the `> ` without interference, and then, the next system call worked. 24 | 25 | - It seems, we block when cleaning up. 26 | 27 | ```none 28 | [SystemHandler] Dealing with system call for 'Process: /bin/Shell.elf' 29 | Thread::sys$read 30 | [Thread::setup_context::lambda] Thread 'Worker: '/bin/Shell.elf' (PID 0x00000000, SYSCALL 0x00000001)' is about to die. 31 | ``` 32 | 33 | We are stuck with this backtrace: 34 | 35 | ```none 36 | #0 0x1000720c in Kernel::setup_mpu (regions=...) at /home/me/dev/pico-os/Kernel/Loader.cpp:112 37 | #1 0x1000c92e in Kernel::Scheduler::schedule (this=0x20001328 ::m_instance>) at /home/me/dev/pico-os/Kernel/Threads/Scheduler.cpp:100 38 | #2 0x1000c6a0 in Kernel::scheduler_next (context=...) at /home/me/dev/pico-os/Kernel/Threads/Scheduler.cpp:21 39 | #3 0x10010f5c in isr_pendsv () at /home/me/dev/pico-os/Kernel/cpu.S:59 40 | ``` 41 | 42 | Seems there is nothing sensible to schedule. 43 | 44 | - In `ConsoleDevice.cpp`, we loop indefinitively until we are able to read something. 45 | Maybe, we get stuck there? 46 | 47 | - I did manage to reproduce this with `sys$write`, this is clearly a race and probably closely related with `dbgln`. 48 | 49 | ```none 50 | Scheduling old thread again. 51 | Scheduling old thread again. (DONE) 52 | [Thread::setup_context::lambda] Thread 'Worker: '/bin/Shell.elf' (PID 0x00000000, SYSCALL 0x00000002)' is about to die. 53 | ``` 54 | 55 | We never see the `Thread::~Thread` message for that, we block before that happens. 56 | 57 | - Actually, the message is produced from `Thread::die` and not from the default thread like I originally thought. 58 | For some reason, we do not schedule the default thread then. 59 | 60 | ### Ideas 61 | 62 | ### Theories 63 | 64 | - I suspect, that the scheduling logic is broken and that we do not schedule the default thread when we should. 65 | Maybe there is another thread always around that... 66 | 67 | Oops, the thread reading input is always around. 68 | It needs to block, otherwise the default thread might not get a turn if it blocked. 69 | 70 | That would make this a livelock. 71 | 72 | - I suspect, that I am allocating in an interrupt handler somewhere. 73 | 74 | ### Actions 75 | 76 | - Added interrupt guards to `UART`. 77 | 78 | - I removed the `KernelMutex` usage from `dbgln`. 79 | 80 | In the future, I need to figure this out, but for now, I can just mask interrupts and write to UART. 81 | -------------------------------------------------------------------------------- /Docs/Debug/Resolved/0020_assert-on-exit.md: -------------------------------------------------------------------------------- 1 | commitid d64256e80cffded84981a8a3b8980b85eedfc095 2 | 3 | We appear to return from the kernel after `sys$exit`. 4 | 5 | ### Notes 6 | 7 | - We crash here, in userspace: 8 | 9 | ```none 10 | #0 sys$exit (status=) at /home/me/dev/pico-os/Userland/LibC/sys/system.c:42 11 | #1 0x1001b348 in exit (status=-1) at /home/me/dev/pico-os/Userland/LibC/stdlib.c:15 12 | #2 0x1001b66c in ?? () at /home/me/dev/pico-os/Userland/LibC/sys/crt0.S:25 13 | ``` 14 | 15 | This is an assertion I put there to ensure that `sys$exit` doesn't return: 16 | 17 | ```none 18 | void sys$exit(int status) 19 | { 20 | syscall(_SC_exit, status, 0, 0); 21 | asm volatile("bkpt #0"); 22 | printf("sys$exit returned?\n"); 23 | abort(); 24 | } 25 | ``` 26 | 27 | - When I fixed the `SystemHandler` code, I forgot about `exit` and that it needs special treatment. 28 | 29 | ### Actions 30 | 31 | - Do not add the thread back into the scheduler, on `_SC_exit`. 32 | -------------------------------------------------------------------------------- /Docs/Debug/Resolved/0022_fault-in-editor.md: -------------------------------------------------------------------------------- 1 | When we use the `a` command in `Editor.elf`, we hang and then overflow the stack. 2 | 3 | ### Notes 4 | 5 | - This is only the end of the stacktrace, the `memmove` calls continue. 6 | 7 | ```none 8 | #521 0x1001c7c0 in memmove (dest=0x2001001a, src=0x20010024, count=536936440) at /home/me/dev/pico-os/Userland/LibC/string.c:82 9 | #522 0x1001c7c0 in memmove (dest=0x2001001a, src=0x20010024, count=536936440) at /home/me/dev/pico-os/Userland/LibC/string.c:82 10 | #523 0x1001d044 in buffer_append_at_offset (buf=0x200120e0, offset=0, data=0x20010318 "\301\307\001\020\032", data_size=2) at /home/me/dev/pico-os/Userland/Editor.c:23 11 | #524 main (argc=, argv=) at /home/me/dev/pico-os/Userland/Editor.c:211 12 | #525 0x1001c950 in ?? () at /home/me/dev/pico-os/Userland/LibC/sys/crt0.S:19 13 | ``` 14 | 15 | ### Ideas 16 | 17 | ### Theories 18 | 19 | - I suspect, that the compiler recognized my `memmove` implementation and replaced it with a call to itself. 20 | That happened many times before. 21 | 22 | ### Actions 23 | 24 | - I started writing my own `memmove` implementation. 25 | -------------------------------------------------------------------------------- /Docs/Debug/Resolved/0023_memmove-is-buggy.md: -------------------------------------------------------------------------------- 1 | commitid 206427f33510215f5457b5c830c43bb16c0295e2 2 | 3 | My rather quickly implemented `memmove` function doesn't always does what it should. 4 | 5 | ### Notes 6 | 7 | - ```none 8 | % p 9 | Hello, world! 10 | Another line! 11 | % a 12 | yay. 13 | . 14 | % p 15 | Hello, world! 16 | Another line! 17 | yay. 18 | % 0a 19 | x 20 | . 21 | % p 22 | x 23 | Hlo, world! 24 | Another line! 25 | yay. 26 | ``` 27 | 28 | Technically, this could be something else, however, I strongly suspect that my `memmove` implementation 29 | is pretty broken. 30 | 31 | ### Ideas 32 | 33 | ### Actions 34 | 35 | - This was a typo when I was working on `memmove`. 36 | I always wrote to the same address. 37 | -------------------------------------------------------------------------------- /Docs/demo.gif: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:af6c31ad2f76563218a5fa1ca8dc60823cb8e9e57f4825c0012426be724f659b 3 | size 7372948 4 | -------------------------------------------------------------------------------- /Docs/demo.mp4: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:0421325078f13d5b6958aef2bda86d2c6c9d6f58b19ae454dd9be5b281076aa2 3 | size 1771448 4 | -------------------------------------------------------------------------------- /Kernel/ABI.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 Paul Scharnofske 3 | * SPDX-License-Identifier: MIT 4 | */ 5 | 6 | #include 7 | 8 | #include 9 | 10 | #include 11 | 12 | extern "C" 13 | usize strlen(const char *str) 14 | { 15 | usize length = 0; 16 | while (*str++) 17 | length += 1; 18 | 19 | return length; 20 | } 21 | 22 | extern "C" 23 | int strcmp(const char *a, const char *b) 24 | { 25 | while (*a && *b) { 26 | if (*a < *b) 27 | return -1; 28 | 29 | if (*a > *b) 30 | return 1; 31 | 32 | ++a; 33 | ++b; 34 | } 35 | 36 | if (*a) 37 | return 1; 38 | 39 | if (*b) 40 | return -1; 41 | 42 | return 0; 43 | } 44 | 45 | extern "C" 46 | int memcmp(const void *a_, const void *b_, usize n) 47 | { 48 | const u8 *a = (const u8*)a_; 49 | const u8 *b = (const u8*)b_; 50 | 51 | for (usize i = 0; i < n; ++i) { 52 | if (*a < *b) 53 | return -1; 54 | 55 | if (*a > *b) 56 | return 1; 57 | 58 | ++a; 59 | ++b; 60 | } 61 | 62 | return 0; 63 | } 64 | 65 | extern "C" 66 | int __aeabi_idiv0(int return_value) 67 | { 68 | VERIFY_NOT_REACHED(); 69 | } 70 | 71 | extern "C" 72 | long long __aeabi_ldiv0(long long return_value) 73 | { 74 | VERIFY_NOT_REACHED(); 75 | } 76 | 77 | extern "C" 78 | void __cxa_pure_virtual() 79 | { 80 | VERIFY_NOT_REACHED(); 81 | } 82 | 83 | extern "C" 84 | void __cxa_bad_cast() 85 | { 86 | VERIFY_NOT_REACHED(); 87 | } 88 | -------------------------------------------------------------------------------- /Kernel/ABI.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | extern "C" 4 | usize strlen(const char *str); 5 | 6 | extern "C" 7 | int strcmp(const char *a, const char *b); 8 | 9 | extern "C" 10 | int memcmp(const void *a, const void *b, usize n); 11 | 12 | extern "C" 13 | int __aeabi_idiv0(int return_value); 14 | 15 | extern "C" 16 | long long __aeabi_ldiv0(long long return_value); 17 | 18 | extern "C" 19 | void __cxa_pure_virtual(); 20 | 21 | extern "C" 22 | void __cxa_bad_cast(); 23 | -------------------------------------------------------------------------------- /Kernel/ConsoleDevice.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | namespace Kernel 5 | { 6 | ConsoleFile::ConsoleFile() 7 | { 8 | Interrupt::UART::the(); 9 | } 10 | 11 | VirtualFile& ConsoleFileHandle::file() { return ConsoleFile::the(); } 12 | 13 | KernelResult ConsoleFileHandle::read(Bytes bytes) 14 | { 15 | for (;;) { 16 | usize nread = Interrupt::UART::the().read(bytes).must(); 17 | 18 | if (nread > 0) 19 | return nread; 20 | } 21 | } 22 | 23 | KernelResult ConsoleFileHandle::write(ReadonlyBytes bytes) 24 | { 25 | usize nwritten = 0; 26 | while (bytes.size() > nwritten) { 27 | nwritten += Interrupt::UART::the().write(bytes.slice(nwritten)).must(); 28 | } 29 | 30 | return nwritten; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Kernel/ConsoleDevice.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | namespace Kernel { 9 | class ConsoleFileHandle final : public VirtualFileHandle 10 | { 11 | public: 12 | KernelResult read(Bytes bytes) override; 13 | KernelResult write(ReadonlyBytes bytes) override; 14 | 15 | VirtualFile& file() override; 16 | }; 17 | 18 | class ConsoleFile final 19 | : public Singleton 20 | , public VirtualFile 21 | { 22 | public: 23 | VirtualFileHandle& create_handle_impl() override 24 | { 25 | return *new ConsoleFileHandle; 26 | } 27 | 28 | void truncate() override 29 | { 30 | VERIFY_NOT_REACHED(); 31 | } 32 | 33 | private: 34 | friend Singleton; 35 | ConsoleFile(); 36 | }; 37 | } 38 | -------------------------------------------------------------------------------- /Kernel/FileSystem/DeviceFileSystem.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | namespace Kernel 7 | { 8 | DeviceFileSystem::DeviceFileSystem() 9 | { 10 | MemoryFileSystem::the(); 11 | 12 | auto& dev_file = FileSystem::lookup("/dev"); 13 | auto& dev_directory = dynamic_cast(dev_file); 14 | 15 | // FIXME: Move this to ConsoleFile::ConsoleFile 16 | add_device(0x00010001, ConsoleFile::the()); 17 | auto& tty_file = *new MemoryFile; 18 | tty_file.m_mode = ModeFlags::Device; 19 | tty_file.m_device_id = 0x00010001; 20 | dev_directory.m_entries.set("tty", &tty_file); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Kernel/FileSystem/DeviceFileSystem.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | 7 | namespace Kernel 8 | { 9 | class DeviceFile; 10 | class DeviceFileHandle; 11 | class DeviceFileSystem; 12 | 13 | class DeviceFileSystem final 14 | : public Singleton 15 | , public VirtualFileSystem 16 | { 17 | public: 18 | // FIXME: Looks like I choose the wrong abstractions 19 | VirtualFile& root() override { VERIFY_NOT_REACHED(); } 20 | 21 | void add_device(u32 device_id, VirtualFile& file) 22 | { 23 | m_devices.set(device_id, &file); 24 | } 25 | 26 | VirtualFileHandle& create_device_handle(u32 device_id) 27 | { 28 | return m_devices.get_opt(device_id).must()->create_handle(); 29 | } 30 | 31 | private: 32 | HashMap m_devices; 33 | 34 | friend Singleton; 35 | DeviceFileSystem(); 36 | }; 37 | } 38 | -------------------------------------------------------------------------------- /Kernel/FileSystem/FileSystem.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | namespace Kernel::FileSystem 6 | { 7 | VirtualFile& lookup(Path path) 8 | { 9 | VERIFY(path.is_absolute()); 10 | 11 | VirtualFile *file = &MemoryFileSystem::the().root(); 12 | 13 | for (auto& component : path.components()) { 14 | auto *directory = dynamic_cast(file); 15 | ASSERT(directory != nullptr); 16 | 17 | file = directory->m_entries.get_opt(component).must(); 18 | } 19 | 20 | ASSERT(file != nullptr); 21 | return *file; 22 | } 23 | 24 | KernelResult try_lookup(Path path) 25 | { 26 | VERIFY(path.is_absolute()); 27 | 28 | VirtualFile *file = &MemoryFileSystem::the().root(); 29 | 30 | for (auto& component : path.components()) { 31 | auto *directory = dynamic_cast(file); 32 | 33 | if (directory == nullptr) 34 | return ENOTDIR; 35 | 36 | auto file_opt = directory->m_entries.get_opt(component); 37 | 38 | if (!file_opt.is_valid()) 39 | return ENOENT; 40 | 41 | file = file_opt.value(); 42 | } 43 | 44 | ASSERT(file != nullptr); 45 | return file; 46 | } 47 | 48 | static HashMap devices; 49 | 50 | void add_device(u32 device, VirtualFile& file) 51 | { 52 | devices.set(device, &file); 53 | } 54 | 55 | VirtualFileHandle& create_handle_for_device(u32 device) 56 | { 57 | return devices.get_opt(device).must()->create_handle(); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /Kernel/FileSystem/FileSystem.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | 7 | namespace Kernel::FileSystem 8 | { 9 | // FIXME: Remove this function, try_lookup should take this name 10 | VirtualFile& lookup(Path); 11 | 12 | KernelResult try_lookup(Path); 13 | 14 | void add_device(u32 device, VirtualFile&); 15 | VirtualFileHandle& create_handle_for_device(u32 device); 16 | } 17 | -------------------------------------------------------------------------------- /Kernel/FileSystem/FlashFileSystem.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | namespace Kernel 6 | { 7 | extern "C" FileInfo __flash_root; 8 | 9 | VirtualFile& FlashFileSystem::root() { return *m_root; } 10 | 11 | FlashFileSystem::FlashFileSystem() 12 | { 13 | m_root = new FlashDirectory { __flash_root }; 14 | VERIFY(m_root->m_ino == 2); 15 | } 16 | 17 | VirtualFileHandle& FlashFile::create_handle_impl() 18 | { 19 | return *new FlashFileHandle { *this }; 20 | } 21 | 22 | FlashDirectory::FlashDirectory(FileInfo& info) 23 | { 24 | m_filesystem = FileSystemId::Flash; 25 | m_ino = info.st_ino; 26 | m_mode = info.st_mode; 27 | m_size = 0xdead; 28 | 29 | m_entries.set(".", this); 30 | m_entries.set("..", this); 31 | 32 | auto *begin = reinterpret_cast(info.m_data); 33 | auto *end = reinterpret_cast(info.m_data + info.st_size); 34 | 35 | for(auto *entry = begin; entry != end; ++entry) { 36 | if ((entry->m_info->st_mode & Kernel::ModeFlags::Format) == Kernel::ModeFlags::Directory) { 37 | auto& new_directory = *new FlashDirectory { *entry->m_info }; 38 | new_directory.m_entries.set("..", this); 39 | 40 | m_entries.set(entry->m_name, &new_directory); 41 | } else { 42 | VERIFY((entry->m_info->st_mode & Kernel::ModeFlags::Format) == Kernel::ModeFlags::Regular); 43 | m_entries.set(entry->m_name, new FlashFile { *entry->m_info }); 44 | } 45 | } 46 | } 47 | 48 | VirtualFileHandle& FlashDirectory::create_handle_impl() 49 | { 50 | return *new FlashDirectoryHandle { *this }; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Kernel/FileSystem/FlashFileSystem.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | namespace Kernel 10 | { 11 | class FlashFileSystem; 12 | class FlashFile; 13 | class FlashDirectory; 14 | class FlashFileHandle; 15 | 16 | class FlashFileSystem final 17 | : public Singleton 18 | , public VirtualFileSystem 19 | { 20 | public: 21 | VirtualFile& root() override; 22 | 23 | private: 24 | friend Singleton; 25 | FlashFileSystem(); 26 | 27 | FlashDirectory *m_root; 28 | }; 29 | 30 | class FlashFile final : public VirtualFile { 31 | public: 32 | explicit FlashFile(FileInfo& info) 33 | : m_data(info.m_data, info.st_size) 34 | { 35 | m_filesystem = FileSystemId::Flash; 36 | m_ino = info.st_ino; 37 | m_mode = info.st_mode; 38 | m_size = info.st_size; 39 | } 40 | 41 | VirtualFileHandle& create_handle_impl() override; 42 | 43 | void truncate() override 44 | { 45 | VERIFY_NOT_REACHED(); 46 | } 47 | 48 | ReadonlyBytes m_data; 49 | }; 50 | 51 | class FlashFileHandle final : public VirtualFileHandle { 52 | public: 53 | explicit FlashFileHandle(FlashFile& file) 54 | : m_file(file) 55 | , m_offset(0) 56 | { 57 | } 58 | 59 | KernelResult read(Bytes bytes) override 60 | { 61 | usize nread = m_file.m_data.slice(m_offset).copy_trimmed_to(bytes); 62 | m_offset += nread; 63 | return nread; 64 | } 65 | 66 | KernelResult write(ReadonlyBytes bytes) override 67 | { 68 | VERIFY_NOT_REACHED(); 69 | } 70 | 71 | VirtualFile& file() override { return m_file; } 72 | 73 | FlashFile& m_file; 74 | usize m_offset; 75 | }; 76 | 77 | class FlashDirectory final : public VirtualDirectory { 78 | public: 79 | explicit FlashDirectory(FileInfo& info); 80 | 81 | VirtualFileHandle& create_handle_impl() override; 82 | }; 83 | 84 | // FIXME: This is redundant with MemoryDirectoryHandle 85 | class FlashDirectoryHandle final : public VirtualFileHandle { 86 | public: 87 | explicit FlashDirectoryHandle(FlashDirectory& directory) 88 | : m_iterator(directory.m_entries.iter()) 89 | , m_directory(directory) 90 | { 91 | } 92 | 93 | KernelResult read(Bytes bytes) override 94 | { 95 | ASSERT(bytes.size() == sizeof(UserlandDirectoryInfo)); 96 | 97 | if (m_iterator.begin() == m_iterator.end()) 98 | return KernelResult::from_value(0); 99 | 100 | auto& [name, file] = *m_iterator++; 101 | 102 | UserlandDirectoryInfo info; 103 | info.d_ino = file.must()->m_ino; 104 | name.strcpy_to({ info.d_name, sizeof(info.d_name) }); 105 | return bytes_from(info).copy_to(bytes); 106 | } 107 | KernelResult write(ReadonlyBytes bytes) override 108 | { 109 | VERIFY_NOT_REACHED(); 110 | } 111 | 112 | VirtualFile& file() override { return m_directory; } 113 | 114 | 115 | decltype(FlashDirectory::m_entries)::Iterator m_iterator; 116 | FlashDirectory& m_directory; 117 | }; 118 | } 119 | -------------------------------------------------------------------------------- /Kernel/FileSystem/MemoryFileSystem.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | namespace Kernel 7 | { 8 | VirtualFile& MemoryFileSystem::root() { return *m_root; } 9 | 10 | MemoryFileSystem::MemoryFileSystem() 11 | { 12 | m_root = new MemoryDirectory; 13 | ASSERT(m_root->m_ino == 2); 14 | 15 | m_root->m_entries.set(".", m_root); 16 | m_root->m_entries.set("..", m_root); 17 | 18 | auto& dev_directory = *new MemoryDirectory; 19 | dev_directory.m_entries.set("..", m_root); 20 | m_root->m_entries.set("dev", &dev_directory); 21 | 22 | auto& bin_directory = dynamic_cast(FlashFileSystem::the().root()); 23 | bin_directory.m_entries.set("..", m_root); 24 | m_root->m_entries.set("bin", &bin_directory); 25 | } 26 | 27 | VirtualFileHandle& MemoryFile::create_handle_impl() 28 | { 29 | return *new MemoryFileHandle { *this }; 30 | } 31 | 32 | VirtualFileHandle& MemoryDirectory::create_handle_impl() 33 | { 34 | return *new MemoryDirectoryHandle { *this }; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Kernel/FileSystem/VirtualFileSystem.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | namespace Kernel 5 | { 6 | VirtualFileHandle& VirtualFile::create_handle() 7 | { 8 | if ((m_mode & ModeFlags::Format) == ModeFlags::Device) 9 | return DeviceFileSystem::the().create_device_handle(m_device_id); 10 | else 11 | return create_handle_impl(); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Kernel/FileSystem/VirtualFileSystem.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | namespace Kernel 10 | { 11 | class VirtualFile; 12 | class VirtualFileHandle; 13 | class VirtualFileSystem; 14 | 15 | class VirtualFileSystem { 16 | public: 17 | virtual ~VirtualFileSystem() = default; 18 | 19 | virtual VirtualFile& root() = 0; 20 | }; 21 | 22 | class VirtualFile { 23 | public: 24 | virtual ~VirtualFile() = default; 25 | 26 | VirtualFile() 27 | { 28 | m_filesystem = FileSystemId::Invalid; 29 | m_ino = 0; 30 | m_mode = ModeFlags::Invalid; 31 | m_owning_user = 0; 32 | m_owning_group = 0; 33 | m_device_id = 0xdead; 34 | } 35 | 36 | FileSystemId m_filesystem; 37 | u32 m_ino; 38 | ModeFlags m_mode; 39 | u32 m_owning_user; 40 | u32 m_owning_group; 41 | u32 m_size; 42 | u32 m_device_id; 43 | 44 | virtual void truncate() = 0; 45 | 46 | VirtualFileHandle& create_handle(); 47 | virtual VirtualFileHandle& create_handle_impl() = 0; 48 | }; 49 | 50 | class VirtualDirectory : public VirtualFile { 51 | public: 52 | virtual ~VirtualDirectory() = default; 53 | 54 | void truncate() override 55 | { 56 | VERIFY_NOT_REACHED(); 57 | } 58 | 59 | HashMap m_entries; 60 | }; 61 | 62 | class VirtualFileHandle { 63 | public: 64 | virtual VirtualFile& file() = 0; 65 | 66 | virtual KernelResult read(Bytes) = 0; 67 | virtual KernelResult write(ReadonlyBytes) = 0; 68 | }; 69 | } 70 | -------------------------------------------------------------------------------- /Kernel/Forward.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace Kernel 6 | { 7 | using namespace Std; 8 | 9 | class Thread; 10 | } 11 | -------------------------------------------------------------------------------- /Kernel/GlobalMemoryAllocator.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | namespace Kernel 10 | { 11 | class GlobalMemoryAllocator final 12 | : public Singleton 13 | , public MemoryAllocator 14 | { 15 | public: 16 | u8* allocate(usize, bool debug_override = true, void *address = nullptr) override; 17 | void deallocate(u8*, bool debug_override = true, void *address = nullptr) override; 18 | u8* reallocate(u8*, usize, bool debug_override = true, void *address = nullptr) override; 19 | 20 | void set_mutex_enabled(bool enabled); 21 | 22 | private: 23 | friend Singleton; 24 | GlobalMemoryAllocator(); 25 | 26 | Optional m_heap; 27 | 28 | Bytes allocate_heap(); 29 | }; 30 | } 31 | -------------------------------------------------------------------------------- /Kernel/HandlerMode.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | // FIXME: Find a better name for this file 6 | 7 | namespace Kernel 8 | { 9 | inline bool is_executing_in_thread_mode() 10 | { 11 | u32 ipsr; 12 | asm ("mrs %0, ipsr;" 13 | "isb;" 14 | : "=r"(ipsr)); 15 | 16 | return ipsr == 0; 17 | } 18 | inline bool is_executing_in_handler_mode() 19 | { 20 | return !is_executing_in_thread_mode(); 21 | } 22 | 23 | inline bool is_using_main_stack_pointer() 24 | { 25 | u32 control; 26 | asm ("mrs %0, control;" 27 | "isb;" 28 | : "=r"(control)); 29 | 30 | return (control & 2) == 0; 31 | } 32 | 33 | inline bool is_executing_privileged() 34 | { 35 | u32 control; 36 | asm ("mrs %0, control;" 37 | "isb;" 38 | : "=r"(control)); 39 | 40 | return (control & 1) == 0; 41 | } 42 | 43 | inline bool are_interrupts_enabled() 44 | { 45 | u32 primask; 46 | asm volatile("mrs %0, primask;" 47 | "isb;" 48 | : "=r"(primask)); 49 | return primask == 0; 50 | } 51 | inline bool disable_interrupts() 52 | { 53 | if (are_interrupts_enabled()) { 54 | asm volatile("cpsid i;"); 55 | return true; 56 | } 57 | 58 | return false; 59 | } 60 | inline void enable_interrupts() 61 | { 62 | asm volatile("cpsie i;"); 63 | } 64 | inline void restore_interrupts(bool were_enabled) 65 | { 66 | if (were_enabled) 67 | enable_interrupts(); 68 | } 69 | 70 | // FIXME: Some of this code is redundant with the scheduler 71 | 72 | template 73 | void call_member_function(T& object) 74 | { 75 | (object.*Method)(); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /Kernel/Interface/System.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define _SC_read 1 4 | #define _SC_write 2 5 | #define _SC_open 3 6 | #define _SC_close 4 7 | #define _SC_fstat 5 8 | #define _SC_wait 8 9 | #define _SC_exit 10 10 | #define _SC_chdir 11 11 | #define _SC_posix_spawn 12 12 | #define _SC_get_working_directory 13 13 | 14 | #define O_RDONLY (1 << 0) 15 | #define O_WRONLY (2 << 0) 16 | 17 | #define O_DIRECTORY (1 << 4) 18 | #define O_CREAT (1 << 5) 19 | #define O_TRUNC (1 << 6) 20 | 21 | #define STDIN_FILENO 0 22 | #define STDOUT_FILENO 1 23 | 24 | // Remember to update LibC as well 25 | #define ENOTDIR 1 26 | #define EINTR 2 27 | #define ERANGE 3 28 | #define ENOENT 4 29 | #define EACCES 5 30 | #define EISDIR 6 31 | #define EMAX 7 32 | -------------------------------------------------------------------------------- /Kernel/Interrupt/UART.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | namespace Kernel::Interrupt 11 | { 12 | void UART::configure_dma() 13 | { 14 | const u32 channel = 0; 15 | 16 | // FIXME: Claim 17 | 18 | auto config = dma_channel_get_default_config(channel); 19 | channel_config_set_transfer_data_size(&config, DMA_SIZE_8); 20 | 21 | // The read address is the address of the UART data register which is constant 22 | channel_config_set_read_increment(&config, false); 23 | 24 | // Write into a ringbuffer with '2^buffer_power=buffer_size' elements 25 | channel_config_set_write_increment(&config, true); 26 | channel_config_set_ring(&config, true, buffer_power); 27 | 28 | // The UART signals when data is avaliable 29 | channel_config_set_dreq(&config, DREQ_UART0_RX); 30 | 31 | // Transmit '2^32 - 1' symbols, this should suffice for any practical case, 32 | // otherwise, the channel could be triggered again 33 | dma_channel_configure( 34 | channel, 35 | &config, 36 | m_input_buffer->data(), 37 | &uart0_hw->dr, 38 | UINT32_MAX, 39 | true); 40 | } 41 | 42 | void UART::configure_uart() 43 | { 44 | uart_init(uart0, 115200); 45 | 46 | gpio_set_function(PICO_DEFAULT_UART_TX_PIN, GPIO_FUNC_UART); 47 | gpio_set_function(PICO_DEFAULT_UART_RX_PIN, GPIO_FUNC_UART); 48 | 49 | // On my system there is one junk byte on boot 50 | uart_getc(uart0); 51 | } 52 | 53 | UART::UART() 54 | { 55 | m_input_buffer = PageAllocator::the().allocate(buffer_power).must(); 56 | 57 | configure_uart(); 58 | configure_dma(); 59 | } 60 | 61 | KernelResult UART::read(Bytes bytes) 62 | { 63 | MaskedInterruptGuard interrupt_guard; 64 | 65 | usize index; 66 | for (index = 0; index < min(input_buffer_size(), bytes.size()); ++index) { 67 | bytes[index] = m_input_buffer->bytes()[input_buffer_consume_offset()]; 68 | ++m_input_buffer_consume_offset_raw; 69 | } 70 | 71 | return index; 72 | } 73 | 74 | KernelResult UART::write(ReadonlyBytes bytes) 75 | { 76 | MaskedInterruptGuard interrupt_guard; 77 | 78 | for (usize index = 0; index < bytes.size(); ++index) { 79 | if (uart_is_writable(uart0)) { 80 | uart_putc_raw(uart0, static_cast(bytes[index])); 81 | } else { 82 | return index; 83 | } 84 | } 85 | 86 | return bytes.size(); 87 | } 88 | 89 | usize UART::input_buffer_consume_offset() 90 | { 91 | return m_input_buffer_consume_offset_raw % buffer_size; 92 | } 93 | 94 | usize UART::input_buffer_avaliable_offset() 95 | { 96 | VERIFY(dma_channel_hw_addr(input_dma_channel)->write_addr >= uptr(m_input_buffer->data())); 97 | return dma_channel_hw_addr(input_dma_channel)->write_addr - uptr(m_input_buffer->data()); 98 | } 99 | 100 | usize UART::input_buffer_size() 101 | { 102 | FIXME_ASSERT(input_buffer_avaliable_offset() >= input_buffer_consume_offset()); 103 | return input_buffer_avaliable_offset() - input_buffer_consume_offset(); 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /Kernel/Interrupt/UART.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | namespace Kernel::Interrupt 12 | { 13 | constexpr bool debug_uart = false; 14 | 15 | class UART : public Singleton { 16 | public: 17 | void trigger(); 18 | 19 | KernelResult read(Bytes); 20 | KernelResult write(ReadonlyBytes); 21 | 22 | static constexpr usize buffer_size = 1 * KiB; 23 | static constexpr usize buffer_power = power_of_two(buffer_size); 24 | 25 | static constexpr u32 input_dma_channel = 0; 26 | 27 | private: 28 | Optional m_input_buffer; 29 | usize m_input_buffer_consume_offset_raw = 0; 30 | 31 | usize input_buffer_consume_offset(); 32 | usize input_buffer_avaliable_offset(); 33 | usize input_buffer_size(); 34 | 35 | friend Singleton; 36 | UART(); 37 | 38 | void configure_dma(); 39 | void configure_uart(); 40 | }; 41 | } 42 | -------------------------------------------------------------------------------- /Kernel/KernelMutex.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | namespace Kernel 4 | { 5 | KernelMutex dbgln_mutex; 6 | KernelMutex malloc_mutex; 7 | KernelMutex page_allocator_mutex; 8 | } 9 | -------------------------------------------------------------------------------- /Kernel/KernelMutex.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | namespace Kernel 10 | { 11 | // FIXME: Must not be accessed in handler mode 12 | 13 | // FIXME: Public interface, enforce lock guards 14 | 15 | // FIXME: Syncronize lock and unlock themselves 16 | 17 | // FIXME: Fix semantics of Thread::block 18 | 19 | class KernelMutex 20 | { 21 | public: 22 | ~KernelMutex() 23 | { 24 | VERIFY(m_waiting_threads.size() == 0); 25 | } 26 | 27 | // FIXME: This is a bit yanky 28 | // This could be addressed by giving Scheduler a proper constructor and put Scheduler::loop in there 29 | 30 | void lock() 31 | { 32 | if (!m_enabled) 33 | return; 34 | 35 | VERIFY(Kernel::is_executing_in_thread_mode()); 36 | 37 | // We only have to lock if the scheduler is initialized and if threads are already being scheduled. 38 | 39 | if (Scheduler::is_initialized()) { 40 | MaskedInterruptGuard interrupt_guard; 41 | 42 | // We must not hold a strong reference here, otherwise, this thread could not be 43 | // terminated without being rescheduled. Not that this should happen, but... 44 | Thread *active_thread = Scheduler::the().get_active_thread_if_avaliable(); 45 | 46 | if (active_thread != nullptr) { 47 | if (m_holding_thread.is_null()) { 48 | m_holding_thread = *active_thread; 49 | } else { 50 | active_thread->set_masked_from_scheduler(true); 51 | m_waiting_threads.enqueue(*active_thread); 52 | Scheduler::the().trigger(); 53 | } 54 | } 55 | } else { 56 | // Since the Scheduler is not initialized, we do not have to deal with locking 57 | } 58 | } 59 | 60 | void unlock() 61 | { 62 | if (!m_enabled) 63 | return; 64 | 65 | VERIFY(Kernel::is_executing_in_thread_mode()); 66 | 67 | if (Scheduler::is_initialized()) { 68 | MaskedInterruptGuard interrupt_guard; 69 | 70 | m_holding_thread.clear(); 71 | 72 | if (m_waiting_threads.size() > 0) { 73 | RefPtr next_thread = m_waiting_threads.dequeue(); 74 | 75 | FIXME_ASSERT(m_holding_thread.is_null()); 76 | m_holding_thread = next_thread; 77 | 78 | m_holding_thread->wakeup(); 79 | } 80 | } else { 81 | // Since the Scheduler is not initialized, we do not have to deal with locking 82 | 83 | VERIFY(m_holding_thread.is_null()); 84 | } 85 | } 86 | 87 | void set_enabled(bool enabled) 88 | { 89 | m_enabled = enabled; 90 | } 91 | 92 | bool is_locked() 93 | { 94 | MaskedInterruptGuard interrupt_guard; 95 | return !m_holding_thread.is_null(); 96 | } 97 | 98 | private: 99 | // During the boot procedure it's not always possible to aquire a mutex. 100 | // Since the scheduler isn't running at that point, we can safely access the resource without a lock. 101 | volatile bool m_enabled = true; 102 | 103 | RefPtr m_holding_thread; 104 | CircularQueue, 16> m_waiting_threads; 105 | }; 106 | 107 | extern KernelMutex dbgln_mutex; 108 | extern KernelMutex malloc_mutex; 109 | extern KernelMutex page_allocator_mutex; 110 | } 111 | -------------------------------------------------------------------------------- /Kernel/Loader.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | #include 10 | 11 | namespace Kernel 12 | { 13 | constexpr bool debug_loader = false; 14 | 15 | class ElfWrapper { 16 | public: 17 | explicit ElfWrapper(const u8 *base, StringView host_path) 18 | : m_base(base) 19 | , m_host_path(host_path) 20 | { 21 | } 22 | 23 | const u8* base() { return m_base; } 24 | u32 base_as_u32() { return reinterpret_cast(m_base); } 25 | 26 | usize offset() { return m_offset; } 27 | 28 | const Elf32_Ehdr* header() { return reinterpret_cast(m_base); } 29 | const Elf32_Phdr* segments() { return reinterpret_cast(m_base + header()->e_phoff); } 30 | const Elf32_Shdr* sections() { return reinterpret_cast(m_base + header()->e_shoff); } 31 | 32 | const char* section_name_base() { return reinterpret_cast(m_base + sections()[header()->e_shstrndx].sh_offset); } 33 | 34 | void consume(usize size) 35 | { 36 | m_offset += size; 37 | } 38 | 39 | ImmutableString m_host_path; 40 | 41 | private: 42 | const u8 *m_base; 43 | usize m_offset = 0; 44 | }; 45 | 46 | struct LoadedExecutable { 47 | LoadedExecutable() = default; 48 | 49 | LoadedExecutable(const LoadedExecutable&) = delete; 50 | LoadedExecutable(LoadedExecutable&&) = default; 51 | 52 | u32 m_entry; 53 | 54 | u32 m_readonly_base; 55 | u32 m_readonly_size; 56 | 57 | u32 m_writable_base; 58 | u32 m_writable_size; 59 | 60 | u32 m_data_base; 61 | u32 m_text_base; 62 | u32 m_bss_base; 63 | 64 | u32 m_stack_base; 65 | u32 m_stack_size; 66 | 67 | ImmutableString m_host_path; 68 | }; 69 | 70 | LoadedExecutable load_executable_into_memory(ElfWrapper, Thread&); 71 | 72 | void setup_mpu(Vector&); 73 | 74 | void hand_over_to_loaded_executable(const LoadedExecutable&, StackWrapper, Vector&, i32 argc, char **argv, char **envp); 75 | } 76 | -------------------------------------------------------------------------------- /Kernel/MPU.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace Kernel::MPU 6 | { 7 | // FIXME: This API sucks 8 | 9 | union RASR { 10 | struct { 11 | u32 enable : 1; 12 | u32 size : 5; 13 | u32 reserved_4 : 2; 14 | u32 srd : 8; 15 | u32 attrs_b : 1; 16 | u32 attrs_c : 1; 17 | u32 attrs_s : 1; 18 | u32 attrs_tex : 3; 19 | u32 reserved_3 : 2; 20 | u32 attrs_ap : 3; 21 | u32 reserved_2 : 1; 22 | u32 attrs_xn : 1; 23 | u32 reserved_1 : 3; 24 | }; 25 | u32 raw; 26 | }; 27 | static_assert(sizeof(RASR) == 4); 28 | 29 | union CTRL { 30 | struct { 31 | u32 enable : 1; 32 | u32 hfnmiena : 1; 33 | u32 privdefena : 1; 34 | u32 reserved_1 : 29; 35 | }; 36 | u32 raw; 37 | }; 38 | static_assert(sizeof(CTRL) == 4); 39 | 40 | union RBAR { 41 | struct { 42 | u32 region : 4; 43 | u32 valid : 1; 44 | u32 addr : 27; 45 | }; 46 | u32 raw; 47 | }; 48 | static_assert(sizeof(RBAR) == 4); 49 | 50 | // FIXME: Move this into a more sensible location 51 | struct Region { 52 | MPU::RBAR rbar; 53 | MPU::RASR rasr; 54 | }; 55 | 56 | inline CTRL ctrl() 57 | { 58 | return static_cast(mpu_hw->ctrl); 59 | } 60 | inline void set_ctrl(CTRL ctrl) 61 | { 62 | mpu_hw->ctrl = ctrl.raw; 63 | } 64 | 65 | inline RASR rasr() 66 | { 67 | return static_cast(mpu_hw->rasr); 68 | } 69 | inline void set_rasr(RASR rasr) 70 | { 71 | mpu_hw->rasr = rasr.raw; 72 | } 73 | 74 | inline RBAR rbar() 75 | { 76 | return static_cast(mpu_hw->rbar); 77 | } 78 | inline void set_rbar(RBAR rbar) 79 | { 80 | mpu_hw->rbar = rbar.raw; 81 | } 82 | 83 | inline usize compute_size(usize size) 84 | { 85 | VERIFY(__builtin_popcount(size) == 1); 86 | 87 | usize power_of_two = __builtin_ctzl(size); 88 | 89 | VERIFY(power_of_two >= 1); 90 | 91 | return power_of_two - 1; 92 | } 93 | 94 | inline void dump() 95 | { 96 | dbgln("[MPU::dump]"); 97 | dbgln(" CTRL={} RNR={} TYPE={}", ctrl().raw, u32(mpu_hw->rnr), u32(mpu_hw->type)); 98 | 99 | u32 rnr = mpu_hw->rnr; 100 | 101 | for (size_t index = 0; index < 8; ++index) { 102 | mpu_hw->rnr = index; 103 | dbgln(" RBAR={} RASR={}", rbar().raw, rasr().raw); 104 | } 105 | 106 | mpu_hw->rnr = rnr; 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /Kernel/PageAllocator.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | namespace Kernel 11 | { 12 | // This must not be turned on in early boot. 13 | // We need to allocate some memory before we are able to produce output. 14 | volatile inline bool debug_page_allocator = false; 15 | 16 | struct PageRange { 17 | usize m_power; 18 | uptr m_base; 19 | 20 | const u8* data() const { return reinterpret_cast(m_base); } 21 | u8* data() { return reinterpret_cast(m_base); } 22 | 23 | usize size() const { return 1 << m_power; } 24 | 25 | ReadonlyBytes bytes() const { return { data(), size() }; } 26 | Bytes bytes() { return { data(), size() }; } 27 | }; 28 | 29 | class OwnedPageRange { 30 | public: 31 | explicit OwnedPageRange(PageRange range) 32 | : m_range(range) 33 | { 34 | } 35 | OwnedPageRange(const OwnedPageRange&) = delete; 36 | OwnedPageRange(OwnedPageRange&& other) 37 | { 38 | m_range = move(other.m_range); 39 | } 40 | ~OwnedPageRange(); 41 | 42 | OwnedPageRange& operator=(const OwnedPageRange&) = delete; 43 | 44 | OwnedPageRange& operator=(OwnedPageRange&& other) 45 | { 46 | m_range = move(other.m_range); 47 | return *this; 48 | } 49 | 50 | usize size() const { return m_range->size(); } 51 | 52 | const u8* data() const { return m_range->data(); } 53 | u8* data() { return m_range->data(); } 54 | 55 | ReadonlyBytes bytes() const { return m_range->bytes(); } 56 | Bytes bytes() { return m_range->bytes(); } 57 | 58 | Optional m_range; 59 | }; 60 | 61 | class PageAllocator : public Singleton { 62 | public: 63 | static constexpr usize max_power = 19; 64 | static constexpr usize stack_power = power_of_two(0x800); 65 | 66 | Optional allocate(usize power); 67 | void deallocate(OwnedPageRange&); 68 | 69 | // FIXME: Syncronize 70 | void dump() 71 | { 72 | dbgln("[PageAllocator] blocks:"); 73 | for (usize power = 0; power < max_power; ++power) { 74 | dbgln(" [{}]: {}", power, m_blocks[power]); 75 | } 76 | } 77 | 78 | void set_mutex_enabled(bool enabled); 79 | 80 | private: 81 | friend Singleton; 82 | PageAllocator(); 83 | 84 | Optional allocate_locked(usize power); 85 | void deallocate_locked(PageRange); 86 | 87 | // There is quite a bit of trickery going on here: 88 | // 89 | // - The address of this block is encoded indirectly in the address of this object 90 | // 91 | // - The size of this block is encoded indirection in the index used to access m_blocks 92 | struct Block { 93 | Block *m_next; 94 | }; 95 | 96 | Array m_blocks; 97 | }; 98 | } 99 | -------------------------------------------------------------------------------- /Kernel/Process.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | 10 | namespace Kernel 11 | { 12 | class Process : public RefCounted { 13 | public: 14 | struct TerminatedProcess { 15 | i32 m_process_id; 16 | i32 m_status; 17 | }; 18 | 19 | static Process& active(); 20 | 21 | static Process& create(StringView name, ElfWrapper); 22 | static Process& create(StringView name, ElfWrapper, const Vector& arguments, const Vector& variables); 23 | 24 | i32 add_file_handle(VirtualFileHandle& handle) 25 | { 26 | i32 handle_id = m_next_handle_id++; 27 | m_handles.set(handle_id, &handle); 28 | 29 | return handle_id; 30 | } 31 | 32 | VirtualFileHandle& get_file_handle(i32 fd) 33 | { 34 | return *m_handles.get_opt(fd).must(); 35 | } 36 | 37 | Path m_working_directory = "/"; 38 | ImmutableString m_name; 39 | Optional m_executable; 40 | 41 | Process *m_parent = nullptr; 42 | i32 m_process_id; 43 | CircularQueue m_terminated_children; 44 | 45 | private: 46 | static inline i32 m_next_process_id = 0; 47 | 48 | HashMap m_handles; 49 | i32 m_next_handle_id = 0; 50 | 51 | friend RefCounted; 52 | explicit Process(ImmutableString name, Optional executable = {}) 53 | : m_name(move(name)) 54 | , m_executable(move(executable)) 55 | { 56 | m_process_id = m_next_process_id++; 57 | 58 | auto& tty_file = FileSystem::lookup("/dev/tty"); 59 | 60 | i32 stdin_fileno = add_file_handle(tty_file.create_handle()); 61 | VERIFY(stdin_fileno == 0); 62 | 63 | i32 stdout_fileno = add_file_handle(tty_file.create_handle()); 64 | VERIFY(stdout_fileno == 1); 65 | 66 | i32 stderr_fileno = add_file_handle(tty_file.create_handle()); 67 | VERIFY(stderr_fileno == 2); 68 | } 69 | }; 70 | } 71 | -------------------------------------------------------------------------------- /Kernel/Result.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | 7 | namespace Kernel 8 | { 9 | // FIXME: I would like to have errors in an enum, sounds like something for 10 | 11 | template 12 | using KernelResult = Result; 13 | } 14 | -------------------------------------------------------------------------------- /Kernel/StackWrapper.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | 7 | namespace Kernel 8 | { 9 | struct StackWrapper { 10 | explicit StackWrapper(Bytes bytes) 11 | : m_bytes(bytes) 12 | , m_top(bytes.data() + bytes.size()) 13 | { 14 | } 15 | 16 | u8* reserve(usize count) 17 | { 18 | ASSERT(m_bytes.data() + count <= m_top); 19 | return m_top -= count; 20 | } 21 | 22 | u8* push(ReadonlyBytes bytes) 23 | { 24 | u8 *data = reserve(bytes.size()); 25 | bytes.copy_to({ data, bytes.size() }); 26 | return data; 27 | } 28 | 29 | template 30 | T* push_value(const T& value) 31 | { 32 | u8 *data = reserve(sizeof(value)); 33 | return new (data) T { value }; 34 | } 35 | 36 | char* push_cstring(const char *cstring) 37 | { 38 | reserve(__builtin_strlen(cstring) + 1); 39 | 40 | // FIXME: Is this necessary? 41 | char *cstring_on_stack = (char*)align(4); 42 | 43 | __builtin_strcpy(cstring_on_stack, cstring); 44 | return cstring_on_stack; 45 | } 46 | 47 | u8* align(u32 boundary) 48 | { 49 | static_assert(sizeof(u8*) == sizeof(u32)); 50 | if (u32(m_top) % boundary != 0) 51 | reserve(u32(m_top) % boundary); 52 | 53 | return m_top; 54 | } 55 | 56 | u8* top() 57 | { 58 | return m_top; 59 | } 60 | 61 | private: 62 | Bytes m_bytes; 63 | u8 *m_top; 64 | }; 65 | } 66 | -------------------------------------------------------------------------------- /Kernel/Synchronization/AbstractLock.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace Kernel 6 | { 7 | class AbstractLock 8 | { 9 | public: 10 | virtual ~AbstractLock() = default; 11 | 12 | virtual void lock() = 0; 13 | virtual void unlock() = 0; 14 | }; 15 | } 16 | -------------------------------------------------------------------------------- /Kernel/Synchronization/HardwareSpinLock.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | namespace Kernel 9 | { 10 | class HardwareSpinLock final 11 | : public AbstractLock 12 | { 13 | private: 14 | volatile u32 *m_spin_lock_pointer; 15 | 16 | public: 17 | explicit HardwareSpinLock(volatile u32 *spin_lock_pointer) 18 | : m_spin_lock_pointer(spin_lock_pointer) 19 | { 20 | 21 | } 22 | 23 | virtual void lock() override 24 | { 25 | // We only want to synchronize with the other core, not with other threads. 26 | MaskedInterruptGuard interrupt_guard; 27 | 28 | while (true) { 29 | u32 value; 30 | __atomic_load(m_spin_lock_pointer, &value, __ATOMIC_SEQ_CST); 31 | 32 | // We read non-zero, if the lock was aquired sucessfully. 33 | if (value != 0) 34 | continue; 35 | } 36 | } 37 | 38 | virtual void unlock() override 39 | { 40 | // We only want to synchronize with the other core, not with other threads. 41 | MaskedInterruptGuard interrupt_guard; 42 | 43 | // Writing anything will release the lock. 44 | u32 value = 1; 45 | __atomic_store(m_spin_lock_pointer, &value, __ATOMIC_SEQ_CST); 46 | } 47 | }; 48 | } 49 | -------------------------------------------------------------------------------- /Kernel/Synchronization/LockGuard.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace Kernel 6 | { 7 | class LockGuard 8 | { 9 | private: 10 | AbstractLock& m_lock; 11 | 12 | public: 13 | explicit LockGuard(AbstractLock& lock) 14 | : m_lock(lock) 15 | { 16 | m_lock.lock(); 17 | } 18 | 19 | ~LockGuard() 20 | { 21 | m_lock.unlock(); 22 | } 23 | 24 | LockGuard(const LockGuard&) = delete; 25 | LockGuard(LockGuard&&) = delete; 26 | 27 | LockGuard& operator=(const LockGuard&) = delete; 28 | LockGuard& operator=(LockGuard&&) = delete; 29 | }; 30 | } 31 | -------------------------------------------------------------------------------- /Kernel/Synchronization/MaskedInterruptGuard.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace Kernel 4 | { 5 | class MaskedInterruptGuard { 6 | public: 7 | MaskedInterruptGuard() 8 | { 9 | m_should_reenable_interrupts = disable_interrupts(); 10 | } 11 | 12 | ~MaskedInterruptGuard() 13 | { 14 | if (m_should_reenable_interrupts) { 15 | VERIFY(!are_interrupts_enabled()); 16 | enable_interrupts(); 17 | } 18 | } 19 | 20 | MaskedInterruptGuard(const MaskedInterruptGuard&) = delete; 21 | MaskedInterruptGuard(MaskedInterruptGuard&&) = delete; 22 | 23 | void release_early() 24 | { 25 | if (m_should_reenable_interrupts) { 26 | m_should_reenable_interrupts = false; 27 | 28 | VERIFY(!are_interrupts_enabled()); 29 | enable_interrupts(); 30 | } 31 | } 32 | 33 | private: 34 | bool m_should_reenable_interrupts = false; 35 | }; 36 | } 37 | -------------------------------------------------------------------------------- /Kernel/SystemHandler.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | 10 | namespace Kernel 11 | { 12 | constexpr bool debug_system_handler = false; 13 | 14 | class SystemHandler : public Singleton { 15 | public: 16 | void notify_worker_thread(RefPtr thread); 17 | 18 | private: 19 | RefPtr m_thread; 20 | 21 | // In thread mode, we must disable interrupts to interact with this. 22 | // For multi-core support, we would need some sort of mutex here. 23 | CircularQueue, 16> m_waiting_threads; 24 | 25 | friend Singleton; 26 | SystemHandler(); 27 | 28 | void handle_next_waiting_thread(); 29 | }; 30 | 31 | // FIXME: Most of this stuff should go to different places 32 | 33 | struct TypeErasedValue { 34 | template 35 | T value() { return bit_cast(m_storage); } 36 | 37 | template 38 | T* pointer() { return bit_cast(m_storage); } 39 | 40 | u32 syscall() { return value(); } 41 | const char* cstring() { return pointer(); } 42 | i32 fd() { return value(); } 43 | i32 pid() { return value(); } 44 | usize size() { return value(); } 45 | 46 | u32 m_storage; 47 | }; 48 | 49 | struct ExtendedSystemCallArguments { 50 | TypeErasedValue arg3; 51 | TypeErasedValue arg4; 52 | TypeErasedValue arg5; 53 | TypeErasedValue arg6; 54 | }; 55 | 56 | struct ExceptionRegisterContext { 57 | TypeErasedValue r0; 58 | TypeErasedValue r1; 59 | TypeErasedValue r2; 60 | TypeErasedValue r3; 61 | TypeErasedValue ip; 62 | TypeErasedValue lr; 63 | TypeErasedValue pc; 64 | TypeErasedValue xpsr; 65 | }; 66 | 67 | struct FullRegisterContext { 68 | TypeErasedValue r11; 69 | TypeErasedValue r10; 70 | TypeErasedValue r9; 71 | TypeErasedValue r8; 72 | TypeErasedValue r7; 73 | TypeErasedValue r6; 74 | TypeErasedValue r5; 75 | TypeErasedValue r4; 76 | 77 | TypeErasedValue r0; 78 | TypeErasedValue r1; 79 | TypeErasedValue r2; 80 | TypeErasedValue r3; 81 | TypeErasedValue ip; 82 | TypeErasedValue lr; 83 | TypeErasedValue pc; 84 | TypeErasedValue xpsr; 85 | }; 86 | } 87 | 88 | template<> 89 | struct Std::Formatter { 90 | static void format(StringBuilder& builder, const Kernel::ExceptionRegisterContext& context) 91 | { 92 | builder.appendf("r0={} r1={} r2={} r3={}\n", context.r0.m_storage, context.r1.m_storage, context.r2.m_storage, context.r3.m_storage); 93 | builder.appendf("ip={} lr={} pc={} xpsr={}\n", context.ip.m_storage, context.lr.m_storage, context.pc.m_storage, context.xpsr.m_storage); 94 | } 95 | }; 96 | 97 | template<> 98 | struct Std::Formatter { 99 | static void format(StringBuilder& builder, const Kernel::FullRegisterContext& context) 100 | { 101 | builder.appendf("r0={} r1={} r2={} r3={}\n", context.r1.m_storage, context.r2.m_storage, context.r3.m_storage, context.r4.m_storage); 102 | builder.appendf("ip={} lr={} pc={} xpsr={}\n", context.ip.m_storage, context.lr.m_storage, context.pc.m_storage, context.xpsr.m_storage); 103 | builder.appendf("r4={} r5={} r6={} r7={}\n", context.r4.m_storage, context.r5.m_storage, context.r6.m_storage, context.r7.m_storage); 104 | builder.appendf("r8={} sb={} r10={} r11={}\n", context.r8.m_storage, context.r9.m_storage, context.r10.m_storage, context.r11.m_storage); 105 | } 106 | }; 107 | -------------------------------------------------------------------------------- /Kernel/Threads/Scheduler.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | namespace Kernel 14 | { 15 | constexpr bool debug_scheduler = false; 16 | constexpr bool scheduler_slow = false; 17 | 18 | class Scheduler : public Singleton { 19 | public: 20 | Thread* get_active_thread_if_avaliable() 21 | { 22 | VERIFY(is_executing_in_handler_mode() || !are_interrupts_enabled()); 23 | return m_active_thread; 24 | } 25 | 26 | Thread& get_active_thread() 27 | { 28 | VERIFY(is_executing_in_handler_mode() || !are_interrupts_enabled()); 29 | VERIFY(m_active_thread != nullptr); 30 | return *m_active_thread; 31 | } 32 | 33 | void clear_active_thread() 34 | { 35 | VERIFY(is_executing_in_handler_mode() || !are_interrupts_enabled()); 36 | VERIFY(!m_active_thread.is_null()); 37 | m_active_thread.clear(); 38 | } 39 | 40 | Thread& schedule(); 41 | 42 | void add_thread(RefPtr thread) 43 | { 44 | VERIFY(is_executing_in_handler_mode() || !are_interrupts_enabled()); 45 | m_queued_threads.enqueue(thread); 46 | } 47 | 48 | void dump(); 49 | 50 | void loop(); 51 | void trigger(); 52 | 53 | bool m_enabled = false; 54 | 55 | // In thread mode, we must disable interrupts to interact with these. 56 | // For multi-thread support, we should add a mutex here. 57 | CircularQueue, 16> m_queued_threads; 58 | CircularQueue, 16> m_dangling_threads; 59 | RefPtr m_active_thread = nullptr; 60 | 61 | private: 62 | RefPtr m_default_thread; 63 | RefPtr m_fallback_thread; 64 | 65 | friend Singleton; 66 | Scheduler(RefPtr startup_thread); 67 | 68 | RefPtr choose_default_thread(); 69 | }; 70 | } 71 | -------------------------------------------------------------------------------- /Kernel/cpu.S: -------------------------------------------------------------------------------- 1 | .syntax unified 2 | .cpu cortex-m0plus 3 | .thumb 4 | 5 | .global scheduler_next 6 | .global syscall 7 | 8 | // u8* push_callee_saved_registers(u8 *stack) 9 | .thumb_func 10 | push_callee_saved_registers: 11 | 12 | .macro push_register register 13 | mov r1, \register 14 | subs r0, r0, #4 15 | str r1, [r0] 16 | .endm 17 | 18 | push_register r4 19 | push_register r5 20 | push_register r6 21 | push_register r7 22 | push_register r8 23 | push_register r9 24 | push_register r10 25 | push_register r11 26 | 27 | bx lr 28 | 29 | // u8* pop_callee_saved_registers(u8 *stack) 30 | .thumb_func 31 | pop_callee_saved_registers: 32 | 33 | .macro pop_register register 34 | ldr r1, [r0] 35 | adds r0, r0, #4 36 | mov \register, r1 37 | .endm 38 | 39 | pop_register r11 40 | pop_register r10 41 | pop_register r9 42 | pop_register r8 43 | pop_register r7 44 | pop_register r6 45 | pop_register r5 46 | pop_register r4 47 | 48 | bx lr 49 | 50 | .global isr_pendsv 51 | .thumb_func 52 | isr_pendsv: 53 | mrs r0, psp 54 | isb 55 | 56 | bl push_callee_saved_registers 57 | 58 | // FullRegisterContext* scheduler_next(FullRegisterContext*) 59 | bl scheduler_next 60 | 61 | bl pop_callee_saved_registers 62 | 63 | msr psp, r0 64 | isb 65 | 66 | ldr r0, =0xfffffffd 67 | bx r0 68 | 69 | .global isr_svcall 70 | .thumb_func 71 | isr_svcall: 72 | mrs r0, psp 73 | isb 74 | 75 | bl push_callee_saved_registers 76 | 77 | // FullRegisterContext* syscall(FullRegisterContext*) 78 | bl syscall 79 | 80 | bl pop_callee_saved_registers 81 | 82 | msr psp, r0 83 | isb 84 | 85 | ldr r0, =0xfffffffd 86 | bx r0 87 | 88 | // void restore_context_from_thread_mode(FullRegisterContext*) 89 | .global restore_context_from_thread_mode 90 | .thumb_func 91 | restore_context_from_thread_mode: 92 | bl pop_callee_saved_registers 93 | 94 | mov sp, r0 95 | 96 | // FIXME: This is quite a mess, try to simplify? 97 | 98 | ldr r0, [sp, #0x10] 99 | mov ip, r0 100 | 101 | ldr r0, [sp, #0x14] 102 | mov lr, r0 103 | 104 | ldr r0, [sp, #0x1c] 105 | msr apsr_nzcvq, r0 106 | isb 107 | 108 | // FIXME: Do pop/str modify APSR? 109 | 110 | pop {r0, r1, r2, r3} 111 | str r0, [sp, #0] 112 | str r1, [sp, #4] 113 | pop {r0, r1} 114 | pop {pc} 115 | -------------------------------------------------------------------------------- /Kernel/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #include 18 | 19 | #include 20 | 21 | // FIXME: Remove 'Kernel::' prefixes 22 | 23 | extern "C" u8 __end__[]; 24 | extern "C" u8 __HeapLimit[]; 25 | 26 | namespace Kernel 27 | { 28 | // FIXME: Clean this up 29 | void create_shell_process() 30 | { 31 | auto& shell_file = dynamic_cast(Kernel::FileSystem::lookup("/bin/Shell.elf")); 32 | Kernel::ElfWrapper elf { shell_file.m_data.data(), "Userland/Shell.1.elf" }; 33 | Kernel::Process::create("/bin/Shell.elf", move(elf)); 34 | } 35 | 36 | void boot_with_scheduler(); 37 | 38 | // Setup basic systems and run 'boot_with_scheduler' in a new thread 39 | void boot() 40 | { 41 | Kernel::PageAllocator::initialize(); 42 | Kernel::PageAllocator::the().set_mutex_enabled(false); 43 | 44 | Kernel::GlobalMemoryAllocator::initialize(); 45 | Kernel::GlobalMemoryAllocator::the().set_mutex_enabled(false); 46 | 47 | Kernel::Interrupt::UART::initialize(); 48 | Kernel::ConsoleFile::initialize(); 49 | 50 | dbgln("\e[0;1mBOOT\e[0m"); 51 | 52 | auto thread = Kernel::Thread::construct("Kernel (boot_with_scheduler)"); 53 | thread->setup_context(boot_with_scheduler); 54 | thread->m_privileged = true; 55 | 56 | Kernel::Scheduler::initialize(move(thread)); 57 | Kernel::Scheduler::the().loop(); 58 | } 59 | 60 | void boot_with_scheduler() 61 | { 62 | Kernel::FlashFileSystem::initialize(); 63 | Kernel::MemoryFileSystem::initialize(); 64 | Kernel::DeviceFileSystem::initialize(); 65 | 66 | dbgln("__HeapLimit={} __end__={}", __HeapLimit, __end__); 67 | 68 | dbgln("[main] Creating /example.txt"); 69 | auto& example_file = *new Kernel::MemoryFile; 70 | auto& example_handle = example_file.create_handle(); 71 | example_handle.write({ (const u8*)"Hello, world!\n", 14 }); 72 | 73 | auto& root_file = Kernel::FileSystem::lookup("/"); 74 | dynamic_cast(root_file).m_entries.set("example.txt", &example_file); 75 | 76 | Kernel::SystemHandler::initialize(); 77 | 78 | // debug_page_allocator = true; 79 | 80 | create_shell_process(); 81 | } 82 | } 83 | 84 | int main() 85 | { 86 | Kernel::boot(); 87 | } 88 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021-2022 Paul Scharnofske 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Std/ArmedScopeGuard.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace Std 6 | { 7 | template 8 | class ArmedScopeGuard 9 | { 10 | public: 11 | ArmedScopeGuard(Callback&& callback) 12 | : m_callback(move(callback)) 13 | { 14 | } 15 | ArmedScopeGuard(ArmedScopeGuard&& other) 16 | : m_armed(exchange(other.m_armed, false)) 17 | , m_callback(move(other.m_callback)) 18 | { 19 | } 20 | 21 | ~ArmedScopeGuard() 22 | { 23 | if (m_armed) 24 | m_callback(); 25 | } 26 | 27 | ArmedScopeGuard(const ArmedScopeGuard&) = delete; 28 | ArmedScopeGuard& operator=(const ArmedScopeGuard&) = delete; 29 | 30 | ArmedScopeGuard& operator=(ArmedScopeGuard&& other) 31 | { 32 | VERIFY(!m_armed); 33 | 34 | m_armed = exchange(other.m_armed, false); 35 | m_callback = move(other.m_callback); 36 | 37 | return *this; 38 | } 39 | 40 | void disarm() 41 | { 42 | m_armed = false; 43 | } 44 | 45 | private: 46 | Callback m_callback; 47 | bool m_armed = true; 48 | }; 49 | } 50 | -------------------------------------------------------------------------------- /Std/Array.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace Std { 7 | template 8 | class Array { 9 | public: 10 | T __array[Size]; 11 | 12 | const T& operator[](usize index) const { return __array[index]; } 13 | T& operator[](usize index) { return __array[index]; } 14 | 15 | Span span() const { return { __array, Size }; } 16 | Span span() { return { __array, Size }; } 17 | }; 18 | } 19 | -------------------------------------------------------------------------------- /Std/Concepts.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace Std { 6 | template 7 | struct IsSame { 8 | static constexpr bool value = false; 9 | }; 10 | template 11 | struct IsSame { 12 | static constexpr bool value = true; 13 | }; 14 | 15 | template 16 | struct RemoveConst { 17 | using Type = T; 18 | }; 19 | template 20 | struct RemoveConst { 21 | using Type = T; 22 | }; 23 | 24 | template 25 | struct IntegralConstant { 26 | static constexpr T value = Value; 27 | }; 28 | } 29 | 30 | namespace Std::Concepts { 31 | template 32 | concept Same = IsSame::value; 33 | 34 | template 35 | concept Integral = Same 36 | || Same 37 | || Same 38 | || Same 39 | || Same 40 | || Same 41 | || Same 42 | || Same 43 | || Same 44 | || Same; 45 | 46 | template 47 | concept Primitive = Integral 48 | || Same; 49 | 50 | template 51 | concept HasSizeOf = IntegralConstant::value; 52 | } 53 | -------------------------------------------------------------------------------- /Std/Format.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #if defined(TEST) 7 | # include 8 | #elif defined(KERNEL) 9 | # include 10 | # include 11 | # include 12 | #else 13 | # error "Only TEST and KERNEL are supported" 14 | #endif 15 | 16 | namespace Std 17 | { 18 | void dbgln_raw(StringView str) 19 | { 20 | if (Kernel::is_executing_in_handler_mode()) 21 | return; 22 | 23 | #ifdef KERNEL 24 | // FIXME: For multi-core support, we will need a mutex here. 25 | // We would need to mask interrupts and then get the mutex. 26 | // However, that will be quite involved, because of the deadlock risk. 27 | { 28 | Kernel::MaskedInterruptGuard interrupt_guard; 29 | 30 | StringView prefix = "\e[36m"; 31 | StringView suffix = "\e[0m\n"; 32 | 33 | Kernel::ConsoleFileHandle handle; 34 | handle.write(prefix.bytes()); 35 | handle.write(str.bytes()); 36 | handle.write(suffix.bytes()); 37 | } 38 | #else 39 | std::cout << "\e[36m" << std::string_view { str.data(), str.size() } << "\e[0m\n"; 40 | #endif 41 | } 42 | 43 | template 44 | requires Concepts::Integral 45 | void Formatter::format(StringBuilder& builder, T value) 46 | { 47 | if (value < 0) { 48 | builder.append('-'); 49 | return format(builder, -value); 50 | } 51 | 52 | builder.append("0x"); 53 | 54 | char buffer[sizeof(T) * 2]; 55 | for (usize index = 0; index < sizeof(buffer); ++index) { 56 | buffer[index] = "0123456789abcdef"[value % 16]; 57 | value /= 16; 58 | } 59 | 60 | for (usize index = 0; index < sizeof(buffer); ++index) 61 | builder.append(buffer[(sizeof(buffer) - 1) - index]); 62 | } 63 | 64 | void Formatter::format(StringBuilder& builder, StringView value) 65 | { 66 | builder.append(value); 67 | return; 68 | } 69 | 70 | void Formatter::format(StringBuilder& builder, bool value) 71 | { 72 | if (value) 73 | builder.append("true"); 74 | else 75 | builder.append("false"); 76 | } 77 | void Formatter::format(StringBuilder& builder, char value) 78 | { 79 | builder.append(value); 80 | } 81 | 82 | void Formatter::format(StringBuilder& builder, const Path& value) 83 | { 84 | builder.append(value.string()); 85 | } 86 | 87 | 88 | template class Formatter; 89 | template class Formatter; 90 | template class Formatter; 91 | template class Formatter; 92 | template class Formatter; 93 | template class Formatter; 94 | template class Formatter; 95 | template class Formatter; 96 | template class Formatter; 97 | template class Formatter; 98 | } 99 | -------------------------------------------------------------------------------- /Std/Forward.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #if !defined(TEST) && !defined(KERNEL) 6 | # error "Only KERNEL and TEST are supported" 7 | #endif 8 | 9 | #if defined(TEST) 10 | # include 11 | # include 12 | #elif defined(KERNEL) 13 | # include 14 | # include 15 | #endif 16 | 17 | namespace Std 18 | { 19 | static void write_output(StringView value) 20 | { 21 | #if defined(TEST) 22 | std::cerr << std::string_view { value.data(), value.size() }; 23 | #elif defined(KERNEL) 24 | Kernel::ConsoleFileHandle handle; 25 | 26 | // We do not aquire a mutex here, because the system may not work at 27 | // this point. 28 | bool were_interrupts_enabled = Kernel::disable_interrupts(); 29 | handle.write(value.bytes()); 30 | Kernel::restore_interrupts(were_interrupts_enabled); 31 | #endif 32 | } 33 | static void write_output(usize value) 34 | { 35 | #if defined(TEST) 36 | std::cerr << value; 37 | #elif defined(KERNEL) 38 | Kernel::ConsoleFileHandle handle; 39 | 40 | StringBuilder builder; 41 | builder.appendf("{}", value); 42 | 43 | // We do not aquire a mutex here, because the system may not work at 44 | // this point. 45 | bool were_interrupts_enabled = Kernel::disable_interrupts(); 46 | handle.write(builder.bytes()); 47 | Kernel::restore_interrupts(were_interrupts_enabled); 48 | #endif 49 | } 50 | 51 | void crash(const char *format, const char *condition, const char *file, usize line) 52 | { 53 | Std::Lexer lexer { format }; 54 | 55 | while (!lexer.eof()) { 56 | if (lexer.try_consume("%condition")) { 57 | write_output(condition); 58 | continue; 59 | } 60 | 61 | if (lexer.try_consume("%file")) { 62 | write_output(file); 63 | continue; 64 | } 65 | 66 | if (lexer.try_consume("%line")) { 67 | write_output(line); 68 | continue; 69 | } 70 | 71 | write_output(lexer.consume_until('%')); 72 | 73 | if (lexer.peek_or_null() != '%') 74 | ASSERT(lexer.eof()); 75 | } 76 | write_output("\n"); 77 | 78 | #if defined(TEST) 79 | std::abort(); 80 | #else 81 | Kernel::disable_interrupts(); 82 | asm volatile("bkpt #0;"); 83 | 84 | for(;;) 85 | asm volatile("wfi"); 86 | #endif 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /Std/Forward.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | constexpr usize KiB = 1024; 6 | constexpr usize MiB = KiB * KiB; 7 | 8 | #ifndef TEST 9 | constexpr void* operator new(usize count, void* ptr) 10 | { 11 | return ptr; 12 | } 13 | constexpr void* operator new[](usize count, void* ptr) 14 | { 15 | return ptr; 16 | } 17 | void* operator new(usize); 18 | void* operator new[](usize); 19 | void operator delete(void*); 20 | void operator delete[](void*); 21 | #endif 22 | 23 | template 24 | struct RemoveReference { 25 | using Type = T; 26 | }; 27 | template 28 | struct RemoveReference { 29 | using Type = T; 30 | }; 31 | 32 | template 33 | constexpr typename RemoveReference::Type&& move(T&& value) 34 | { 35 | return static_cast::Type&&>(value); 36 | } 37 | 38 | template 39 | constexpr T&& forward(typename RemoveReference::Type& value) 40 | { 41 | return static_cast(value); 42 | } 43 | template 44 | constexpr T&& forward(typename RemoveReference::Type&& value) 45 | { 46 | return static_cast(value); 47 | } 48 | 49 | template 50 | constexpr T exchange(T& obj, U&& new_value) 51 | { 52 | T old_value = move(obj); 53 | obj = forward(new_value); 54 | return move(old_value); 55 | } 56 | 57 | template 58 | constexpr void swap(T& lhs, T& rhs) 59 | { 60 | T value = move(lhs); 61 | lhs = move(rhs); 62 | rhs = move(value); 63 | } 64 | 65 | extern "C" 66 | inline void strlcpy(char *destination, const char *source, usize size) noexcept 67 | { 68 | if (size >= 1) { 69 | __builtin_strncpy(destination, source, size - 1); 70 | destination[size - 1] = 0; 71 | } 72 | } 73 | extern "C" 74 | void* memcpy(void *destination, const void *source, usize count) noexcept; 75 | 76 | template 77 | constexpr T max(T a, T b) 78 | { 79 | return a >= b ? a : b; 80 | } 81 | 82 | template 83 | constexpr T min(T a, T b) 84 | { 85 | return a <= b ? a : b; 86 | } 87 | 88 | inline usize round_to_power_of_two(usize value) 89 | { 90 | return 1 << (32 - __builtin_clz(value)); 91 | } 92 | 93 | constexpr usize power_of_two(usize value) 94 | { 95 | return __builtin_ctzl(value); 96 | } 97 | 98 | template 99 | static void type_erased_member_function_wrapper(void *object) 100 | { 101 | (reinterpret_cast(object)->*Method)(); 102 | } 103 | 104 | namespace Std { 105 | enum class IterationDecision { 106 | Continue, 107 | Break, 108 | }; 109 | 110 | class Path; 111 | 112 | template 113 | void dbgln(const char *fmtstr, const Parameters&...); 114 | 115 | [[noreturn]] 116 | void crash(const char *format, const char *condition, const char *file, usize line); 117 | } 118 | 119 | #define ASSERT(condition) ((condition) ? (void)0 : ::Std::crash("ASSERT(%condition)\n%file:%line\n", #condition, __FILE__, __LINE__)) 120 | #define VERIFY(condition) ((condition) ? (void)0 : ::Std::crash("VERIFY(%condition)\n%file:%line\n", #condition, __FILE__, __LINE__)) 121 | 122 | #define ASSERT_NOT_REACHED() ::Std::crash("ASSERT_NOT_REACHED():\n%file:%line\n", "", __FILE__, __LINE__) 123 | #define VERIFY_NOT_REACHED() ::Std::crash("VERIFY_NOT_REACHED():\n%file:%line\n", "", __FILE__, __LINE__) 124 | 125 | #define FIXME() ::Std::crash("FIXME()\n%file:%line\n", "", __FILE__, __LINE__) 126 | #define FIXME_ASSERT(condition) ((condition) ? (void)0 : ::Std::crash("FIXME(%condition)\n%file:%line\n", #condition, __FILE__, __LINE__)) 127 | -------------------------------------------------------------------------------- /Std/HashMap.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace Std 7 | { 8 | // FIXME: What about things which can't be compared at all? 9 | 10 | template 11 | class HashMap { 12 | public: 13 | void clear() 14 | { 15 | m_hash.clear(); 16 | } 17 | 18 | void set(const Key& key, const Value& value) 19 | { 20 | m_hash.insert({ key, value }); 21 | } 22 | void set(Key&& key, const Value& value) 23 | { 24 | m_hash.insert({ move(key), value }); 25 | } 26 | void set(const Key& key, Value&& value) 27 | { 28 | m_hash.insert({ key, move(value) }); 29 | } 30 | void set(Key&& key, Value&& value) 31 | { 32 | m_hash.insert({ move(key), move(value) }); 33 | } 34 | 35 | Value* get(const Key& key) 36 | { 37 | Node *node = m_hash.search({ key, {} }); 38 | 39 | if (node) 40 | return &node->m_value.value(); 41 | else 42 | return nullptr; 43 | } 44 | const Value* get(const Key& key) const 45 | { 46 | return const_cast(this)->get(key); 47 | } 48 | 49 | Optional get_opt(const Key& key) const 50 | { 51 | const Node *node = m_hash.search({ key, {} }); 52 | 53 | if (node) 54 | return node->m_value; 55 | else 56 | return {}; 57 | } 58 | 59 | void remove(const Key& key) 60 | { 61 | m_hash.remove({ key, {} }); 62 | } 63 | 64 | usize size() const 65 | { 66 | return m_hash.size(); 67 | } 68 | 69 | struct Node { 70 | Key m_key; 71 | Optional m_value; 72 | 73 | u32 hash() const { return Hash::compute(m_key); } 74 | 75 | bool operator<(const Node& other) const 76 | { 77 | return m_key < other.m_key; 78 | } 79 | bool operator>(const Node& other) const 80 | { 81 | return m_key > other.m_key; 82 | } 83 | }; 84 | 85 | using Iterator = HashTable::Iterator; 86 | 87 | Iterator iter() { return m_hash.iter(); } 88 | 89 | private: 90 | HashTable m_hash; 91 | }; 92 | } 93 | -------------------------------------------------------------------------------- /Std/Lexer.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace Std 7 | { 8 | class Lexer { 9 | public: 10 | explicit Lexer(StringView input) 11 | : m_input(input) 12 | { 13 | } 14 | 15 | bool eof() const 16 | { 17 | return m_offset >= m_input.size(); 18 | } 19 | 20 | Optional peek() 21 | { 22 | if (eof()) 23 | return {}; 24 | return m_input[m_offset]; 25 | } 26 | char peek_or_null() 27 | { 28 | return peek().value_or(0); 29 | } 30 | 31 | char consume() 32 | { 33 | ASSERT(!eof()); 34 | return m_input[m_offset++]; 35 | } 36 | bool try_consume(char ch) 37 | { 38 | ASSERT(ch != 0); 39 | 40 | if (peek_or_null() == ch) { 41 | consume(); 42 | return true; 43 | } 44 | return {}; 45 | } 46 | bool try_consume(StringView str) 47 | { 48 | if (str.size() > remaining()) 49 | return false; 50 | 51 | for (usize i = 0; i < str.size(); ++i) { 52 | if (m_input[m_offset + i] != str[i]) 53 | return false; 54 | } 55 | 56 | m_offset += str.size(); 57 | return true; 58 | } 59 | 60 | StringView consume_until(char ch) 61 | { 62 | usize offset = m_offset; 63 | 64 | while(!eof() && peek_or_null() != ch) 65 | consume(); 66 | 67 | 68 | return m_input.substr(offset, m_offset); 69 | } 70 | 71 | usize remaining() const { return m_input.size() - m_offset; } 72 | 73 | private: 74 | StringView m_input; 75 | usize m_offset = 0; 76 | }; 77 | } 78 | -------------------------------------------------------------------------------- /Std/MemoryAllocator.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace Std 6 | { 7 | class MemoryAllocator { 8 | public: 9 | explicit MemoryAllocator(Bytes heap); 10 | 11 | usize heap_size() const { return m_heap.size(); } 12 | 13 | virtual u8* allocate(usize, bool debug_override = true, void *address = nullptr); 14 | virtual void deallocate(u8*, bool debug_override = true, void *address = nullptr); 15 | virtual u8* reallocate(u8*, usize, bool debug_override = true, void *address = nullptr); 16 | 17 | void dump() 18 | { 19 | dbgln("m_freelist:"); 20 | for (auto *entry = m_freelist; entry; entry = entry->m_next) 21 | dbgln(" {} ({} bytes)", entry, entry->m_size); 22 | 23 | auto stats = statistics(); 24 | 25 | dbgln("statistics:"); 26 | dbgln(" m_largest_continous_block {}", stats.m_largest_continous_block); 27 | dbgln(" m_avaliable_memory {}", stats.m_avaliable_memory); 28 | } 29 | 30 | struct Statistics { 31 | usize m_largest_continous_block; 32 | usize m_avaliable_memory; 33 | }; 34 | 35 | Statistics statistics() 36 | { 37 | Statistics stats; 38 | 39 | stats.m_largest_continous_block = 0; 40 | stats.m_avaliable_memory = 0; 41 | 42 | for (auto *entry = m_freelist; entry; entry = entry->m_next) { 43 | stats.m_avaliable_memory += entry->m_size; 44 | 45 | if (entry->m_size > stats.m_largest_continous_block) 46 | stats.m_largest_continous_block = entry->m_size; 47 | } 48 | 49 | return stats; 50 | } 51 | 52 | bool m_debug = false; 53 | 54 | protected: 55 | struct Node { 56 | usize m_size; 57 | Node *m_next; 58 | u8 m_data[]; 59 | }; 60 | static_assert(sizeof(Node) % 4 == 0); 61 | 62 | Bytes m_heap; 63 | 64 | private: 65 | Node *m_freelist; 66 | }; 67 | } 68 | -------------------------------------------------------------------------------- /Std/Optional.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #ifdef TEST 6 | # include 7 | #endif 8 | 9 | namespace Std { 10 | template 11 | class Optional { 12 | public: 13 | Optional() 14 | { 15 | m_is_valid = false; 16 | } 17 | Optional(const T& value) 18 | { 19 | new (m_value) T { value }; 20 | m_is_valid = true; 21 | } 22 | Optional(T&& value) 23 | { 24 | new (m_value) T { move(value) }; 25 | m_is_valid = true; 26 | } 27 | Optional(const Optional& other) 28 | { 29 | *this = other; 30 | } 31 | Optional(Optional&& other) 32 | { 33 | *this = move(other); 34 | } 35 | ~Optional() 36 | { 37 | clear(); 38 | } 39 | 40 | bool is_valid() const { return m_is_valid; } 41 | 42 | const T& value() const & { return *reinterpret_cast(m_value); } 43 | T& value() & { return *reinterpret_cast(m_value); } 44 | T&& value() && { return *reinterpret_cast(m_value); } 45 | 46 | T value_or(T default_) 47 | { 48 | if (is_valid()) 49 | return value(); 50 | else 51 | return default_; 52 | } 53 | T must() && 54 | { 55 | VERIFY(is_valid()); 56 | 57 | T tmp = move(value()); 58 | clear(); 59 | 60 | return move(tmp); 61 | } 62 | T& must() & 63 | { 64 | VERIFY(is_valid()); 65 | return value(); 66 | } 67 | const T& must() const& 68 | { 69 | VERIFY(is_valid()); 70 | return value(); 71 | } 72 | 73 | void clear() 74 | { 75 | if (m_is_valid) { 76 | m_is_valid = false; 77 | value().~T(); 78 | } 79 | } 80 | 81 | Optional& operator=(const T& other) 82 | { 83 | clear(); 84 | 85 | m_is_valid = true; 86 | new (m_value) T { other }; 87 | 88 | return *this; 89 | } 90 | Optional& operator=(T&& other) 91 | { 92 | clear(); 93 | 94 | m_is_valid = true; 95 | new (m_value) T { move(other) }; 96 | 97 | return *this; 98 | } 99 | 100 | Optional& operator=(const Optional& other) 101 | { 102 | clear(); 103 | 104 | if (other.is_valid()) { 105 | m_is_valid = true; 106 | new (m_value) T { other.value() }; 107 | } 108 | 109 | return *this; 110 | } 111 | Optional& operator=(Optional&& other) 112 | { 113 | clear(); 114 | 115 | if (other.is_valid()) { 116 | m_is_valid = true; 117 | new (m_value) T { move(other.value()) }; 118 | other.clear(); 119 | } 120 | 121 | return *this; 122 | } 123 | const T* operator->() const { return &must(); } 124 | T* operator->() { return &must(); } 125 | 126 | private: 127 | alignas(alignof(T)) 128 | u8 m_value[sizeof(T)]; 129 | bool m_is_valid = false; 130 | }; 131 | } 132 | -------------------------------------------------------------------------------- /Std/OwnPtr.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace Std 6 | { 7 | template 8 | class OwnPtr; 9 | 10 | template 11 | class NonnullOwnPtr { 12 | public: 13 | friend OwnPtr; 14 | 15 | NonnullOwnPtr(T *pointer) 16 | : m_pointer(pointer) 17 | { 18 | } 19 | NonnullOwnPtr(NonnullOwnPtr&& other) 20 | { 21 | m_pointer = nullptr; 22 | *this = move(other); 23 | } 24 | ~NonnullOwnPtr() 25 | { 26 | delete m_pointer; 27 | } 28 | 29 | bool is_null() const { return false; } 30 | 31 | const T* ptr() const { return m_pointer; } 32 | T* ptr() { return m_pointer; } 33 | 34 | const T& operator*() const { return *m_pointer; } 35 | T& operator*() { return *m_pointer; } 36 | 37 | const T* operator->() const { return m_pointer; } 38 | T* operator->() { return m_pointer; } 39 | 40 | NonnullOwnPtr& operator=(NonnullOwnPtr&& other) 41 | { 42 | delete m_pointer; 43 | 44 | m_pointer = exchange(other.m_pointer, nullptr); 45 | 46 | return *this; 47 | } 48 | 49 | operator const T&() const { return **this; } 50 | operator T&() { return **this; } 51 | 52 | private: 53 | T *m_pointer; 54 | }; 55 | 56 | template 57 | class OwnPtr { 58 | public: 59 | OwnPtr() 60 | : m_pointer(nullptr) 61 | { 62 | } 63 | OwnPtr(T *pointer) 64 | : m_pointer(pointer) 65 | { 66 | } 67 | OwnPtr(OwnPtr&& other) 68 | { 69 | m_pointer = nullptr; 70 | 71 | *this = move(other); 72 | } 73 | OwnPtr(NonnullOwnPtr&& other) 74 | { 75 | m_pointer = nullptr; 76 | 77 | *this = move(other); 78 | } 79 | ~OwnPtr() 80 | { 81 | clear(); 82 | } 83 | 84 | bool is_null() const { return m_pointer == nullptr; } 85 | 86 | void clear() 87 | { 88 | delete m_pointer; 89 | m_pointer = nullptr; 90 | } 91 | 92 | T* leak() 93 | { 94 | return exchange(m_pointer, nullptr); 95 | } 96 | 97 | const T* ptr() const { return m_pointer; } 98 | T* ptr() { return m_pointer; } 99 | 100 | const T& operator*() const { return *m_pointer; } 101 | T& operator*() { return *m_pointer; } 102 | 103 | const T* operator->() const { return m_pointer; } 104 | T* operator->() { return m_pointer; } 105 | 106 | OwnPtr& operator=(OwnPtr&& other) 107 | { 108 | clear(); 109 | 110 | m_pointer = exchange(other.m_pointer, nullptr); 111 | 112 | return *this; 113 | } 114 | OwnPtr& operator=(NonnullOwnPtr&& other) 115 | { 116 | clear(); 117 | 118 | m_pointer = exchange(other.m_pointer, nullptr); 119 | 120 | return *this; 121 | } 122 | OwnPtr& operator=(T *other) 123 | { 124 | clear(); 125 | 126 | m_pointer = other; 127 | 128 | return *this; 129 | } 130 | 131 | operator const T&() const { return **this; } 132 | operator T&() { return **this; } 133 | 134 | private: 135 | T *m_pointer; 136 | }; 137 | 138 | template 139 | NonnullOwnPtr make(Parameters&&... parameters) 140 | { 141 | return new T { forward(parameters)... }; 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /Std/Path.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | namespace Std 8 | { 9 | class Path { 10 | public: 11 | Path() 12 | { 13 | m_is_absolute = false; 14 | } 15 | Path(StringView path) 16 | { 17 | VERIFY(path.size() >= 1); 18 | 19 | Lexer lexer { path }; 20 | 21 | m_is_absolute = lexer.try_consume('/'); 22 | 23 | while (!lexer.eof()) 24 | { 25 | m_components.append(lexer.consume_until('/')); 26 | 27 | if (!lexer.eof()) 28 | lexer.try_consume('/'); 29 | } 30 | } 31 | Path(const char *path) 32 | : Path(StringView { path }) 33 | { 34 | } 35 | Path(const Path& path) 36 | : m_is_absolute(path.m_is_absolute) 37 | , m_components(path.m_components) 38 | { 39 | } 40 | 41 | bool is_absolute() const { return m_is_absolute; } 42 | auto components() const { return m_components.iter(); } 43 | 44 | Path parent() const 45 | { 46 | VERIFY(m_components.size() >= 1); 47 | 48 | Path parent; 49 | parent.m_is_absolute = m_is_absolute; 50 | 51 | for (usize i = 0; i < m_components.size() - 1; ++i) 52 | parent.m_components.append(m_components[i]); 53 | 54 | return move(parent); 55 | } 56 | 57 | ImmutableString filename() const 58 | { 59 | VERIFY(m_components.size() >= 1); 60 | return m_components[m_components.size() - 1]; 61 | } 62 | 63 | ImmutableString string() const 64 | { 65 | StringBuilder builder; 66 | 67 | if (m_is_absolute && components().size() == 0) { 68 | builder.append('/'); 69 | return builder.string(); 70 | } 71 | 72 | bool put_slash = m_is_absolute; 73 | for (auto& component : components()) 74 | { 75 | if (put_slash) 76 | builder.append('/'); 77 | else 78 | put_slash = true; 79 | 80 | 81 | builder.append(component); 82 | } 83 | 84 | return builder.string(); 85 | } 86 | 87 | Path operator/(const Path& rhs) const 88 | { 89 | ASSERT(!rhs.is_absolute()); 90 | 91 | Path path = *this; 92 | path.m_components.extend(rhs.m_components.span()); 93 | return path; 94 | } 95 | 96 | Path& operator=(const Path& rhs) 97 | { 98 | m_is_absolute = rhs.m_is_absolute; 99 | m_components = rhs.m_components; 100 | 101 | return *this; 102 | } 103 | 104 | private: 105 | bool m_is_absolute; 106 | Vector m_components; 107 | }; 108 | } 109 | -------------------------------------------------------------------------------- /Std/RefPtr.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace Std 6 | { 7 | template 8 | class RefCounted; 9 | 10 | struct DanglingObjectMarker { 11 | }; 12 | 13 | template 14 | class RefPtr { 15 | public: 16 | RefPtr() 17 | : m_pointer(nullptr) 18 | { 19 | } 20 | RefPtr(nullptr_t) 21 | : m_pointer(nullptr) 22 | { 23 | } 24 | RefPtr(T& other) 25 | : m_pointer(&other) 26 | { 27 | m_pointer->ref(); 28 | } 29 | RefPtr(T& other, DanglingObjectMarker) 30 | : m_pointer(&other) 31 | { 32 | // We do not reference here since dangling objects have an initial reference count of one. 33 | VERIFY(m_pointer->refcount() == 1); 34 | } 35 | RefPtr(const RefPtr& other) 36 | { 37 | m_pointer = other.m_pointer; 38 | 39 | if (m_pointer) 40 | m_pointer->ref(); 41 | } 42 | RefPtr(RefPtr&& other) 43 | { 44 | m_pointer = exchange(other.m_pointer, nullptr); 45 | } 46 | ~RefPtr() 47 | { 48 | clear(); 49 | } 50 | 51 | bool is_null() const 52 | { 53 | return m_pointer == nullptr; 54 | } 55 | 56 | void clear() 57 | { 58 | if (m_pointer) 59 | m_pointer->unref(); 60 | 61 | m_pointer = nullptr; 62 | } 63 | 64 | const T* ptr() const { return m_pointer; } 65 | T* ptr() { return m_pointer; } 66 | 67 | RefPtr& operator=(const RefPtr& other) 68 | { 69 | clear(); 70 | 71 | m_pointer = other.m_pointer; 72 | 73 | if (m_pointer) 74 | m_pointer->ref(); 75 | return *this; 76 | } 77 | RefPtr& operator=(RefPtr&& other) 78 | { 79 | clear(); 80 | 81 | m_pointer = exchange(other.m_pointer, nullptr); 82 | return *this; 83 | } 84 | 85 | const T& must() const 86 | { 87 | VERIFY(m_pointer != nullptr); 88 | return *m_pointer; 89 | } 90 | T& must() 91 | { 92 | VERIFY(m_pointer != nullptr); 93 | return *m_pointer; 94 | } 95 | 96 | operator const T*() const { return m_pointer; } 97 | operator T*() { return m_pointer; } 98 | 99 | const T* operator->() const { return m_pointer; } 100 | T* operator->() { return m_pointer; } 101 | 102 | private: 103 | T *m_pointer; 104 | 105 | friend RefCounted; 106 | explicit RefPtr(T *pointer) 107 | : m_pointer(pointer) 108 | { 109 | m_pointer->ref(); 110 | } 111 | }; 112 | 113 | template 114 | class RefCounted { 115 | public: 116 | RefCounted() = default; 117 | RefCounted(const RefCounted&) = delete; 118 | RefCounted(RefCounted&&) = delete; 119 | virtual ~RefCounted() = default; 120 | 121 | template 122 | static RefPtr construct(Parameters&&... parameters) 123 | { 124 | return RefPtr { *new T { forward(parameters)... }, DanglingObjectMarker{} }; 125 | } 126 | 127 | usize refcount() const 128 | { 129 | return m_refcount; 130 | } 131 | 132 | virtual void ref() 133 | { 134 | VERIFY(m_refcount >= 1); 135 | ++m_refcount; 136 | } 137 | 138 | virtual void unref() 139 | { 140 | VERIFY(m_refcount > 0); 141 | --m_refcount; 142 | 143 | if (m_refcount == 0) { 144 | delete static_cast(this); 145 | } 146 | } 147 | 148 | private: 149 | usize m_refcount = 1; 150 | }; 151 | } 152 | -------------------------------------------------------------------------------- /Std/Result.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace Std 4 | { 5 | struct ResultValueTag { 6 | }; 7 | struct ResultErrorTag { 8 | }; 9 | 10 | template 11 | class Result { 12 | public: 13 | Result(const ValueType& value, ResultValueTag = {}) 14 | : m_value(value) 15 | , m_ok(true) 16 | { 17 | } 18 | Result(ValueType&& value, ResultValueTag = {}) 19 | : m_value(move(value)) 20 | , m_ok(true) 21 | { 22 | } 23 | 24 | Result(const ErrorType& error, ResultErrorTag = {}) 25 | : m_error(error) 26 | , m_ok(false) 27 | { 28 | } 29 | Result(ErrorType&& error, ResultErrorTag = {}) 30 | : m_error(move(error)) 31 | , m_ok(false) 32 | { 33 | } 34 | 35 | static Result from_value(const ValueType& value) 36 | { 37 | return Result { value, ResultValueTag{} }; 38 | } 39 | static Result from_value(ValueType&& value) 40 | { 41 | return Result { move(value), ResultValueTag{} }; 42 | } 43 | 44 | static Result from_error(const ErrorType& error) 45 | { 46 | return Result { error, ResultErrorTag{} }; 47 | } 48 | static Result from_error(ErrorType&& error) 49 | { 50 | return Result { move(error), ResultErrorTag{} }; 51 | } 52 | 53 | ~Result() 54 | { 55 | if (m_ok) 56 | m_value.~ValueType(); 57 | else 58 | m_error.~ErrorType(); 59 | } 60 | 61 | const ValueType& value() const 62 | { 63 | ASSERT(m_ok); 64 | return m_value; 65 | } 66 | const ErrorType& error() const 67 | { 68 | ASSERT(!m_ok); 69 | return m_error; 70 | } 71 | 72 | ValueType&& take_value() 73 | { 74 | ASSERT(m_ok); 75 | return move(m_value); 76 | } 77 | ErrorType&& take_error() 78 | { 79 | ASSERT(!m_ok); 80 | return move(m_error); 81 | } 82 | 83 | const ValueType& must() const 84 | { 85 | return value(); 86 | } 87 | 88 | bool ok() const { return m_ok; } 89 | bool is_error() const { return !m_ok; } 90 | 91 | private: 92 | bool m_ok; 93 | 94 | union { 95 | ValueType m_value; 96 | ErrorType m_error; 97 | }; 98 | }; 99 | } 100 | -------------------------------------------------------------------------------- /Std/Singleton.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | // FIXME: This is not safe for multiple cores or even multiple threads 6 | 7 | namespace Std 8 | { 9 | template 10 | struct SingletonContainer 11 | { 12 | static inline bool m_initialized = false; 13 | 14 | alignas(T) 15 | static inline u8 m_instance[sizeof(T)]; 16 | }; 17 | 18 | template 19 | class Singleton { 20 | public: 21 | Singleton(const Singleton&) = delete; 22 | Singleton(Singleton&&) = delete; 23 | 24 | Singleton() = default; 25 | 26 | template 27 | static void initialize(Parameters&&... parameters) 28 | { 29 | VERIFY(!m_initialized); 30 | 31 | m_initialized = true; 32 | new (m_instance) T { forward(parameters)... }; 33 | } 34 | 35 | static bool is_initialized() 36 | { 37 | return m_initialized; 38 | } 39 | 40 | static T& the() 41 | { 42 | VERIFY(m_initialized); 43 | return *m_instance; 44 | } 45 | 46 | private: 47 | static inline bool& m_initialized = SingletonContainer::m_initialized; 48 | 49 | // FIXME: This is the incorrect type 50 | static inline T (&m_instance)[] = reinterpret_cast(SingletonContainer::m_instance); 51 | }; 52 | } 53 | -------------------------------------------------------------------------------- /Std/String.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | -------------------------------------------------------------------------------- /Std/StringBuilder.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | -------------------------------------------------------------------------------- /Std/StringView.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | namespace Std { 9 | 10 | class StringView : public Span { 11 | public: 12 | StringView() 13 | { 14 | } 15 | StringView(Span span) 16 | : Span(span) 17 | { 18 | } 19 | StringView(const char *cstring) 20 | : Span(cstring, __builtin_strlen(cstring)) 21 | { 22 | } 23 | StringView(const char *data, usize size) 24 | : Span(data, size) 25 | { 26 | } 27 | 28 | Optional index_of(char ch) 29 | { 30 | char *pointer = __builtin_strchr(data(), ch); 31 | 32 | if (pointer == nullptr) 33 | return {}; 34 | 35 | return pointer - data(); 36 | } 37 | 38 | StringView substr(usize index) 39 | { 40 | VERIFY(index <= size()); 41 | return { data() + index, size() - index }; 42 | } 43 | StringView substr(usize start, usize end) 44 | { 45 | VERIFY(start <= end); 46 | VERIFY(end <= size()); 47 | return { data() + start, end - start }; 48 | } 49 | 50 | StringView trim(usize size) 51 | { 52 | size = min(this->size(), size); 53 | return { data(), size }; 54 | } 55 | 56 | void strcpy_to(Span other) const 57 | { 58 | VERIFY(other.size() >= size() + 1); 59 | 60 | __builtin_memcpy(other.data(), data(), size()); 61 | other.data()[size()] = 0; 62 | } 63 | 64 | bool starts_with(char ch) const 65 | { 66 | return size() >= 1 && data()[0] == ch; 67 | } 68 | 69 | std::strong_ordering operator<=>(StringView rhs) const 70 | { 71 | if (size() < rhs.size()) 72 | return std::strong_ordering::less; 73 | 74 | if (size() > rhs.size()) 75 | return std::strong_ordering::greater; 76 | 77 | int retval = __builtin_memcmp(data(), rhs.data(), size()); 78 | 79 | if (retval < 0) { 80 | return std::strong_ordering::less; 81 | } else if (retval > 0) { 82 | return std::strong_ordering::greater; 83 | } else { 84 | return std::strong_ordering::equal; 85 | } 86 | } 87 | 88 | bool operator==(StringView rhs) const 89 | { 90 | return operator<=>(rhs) == std::strong_ordering::equal; 91 | } 92 | 93 | ReadonlyBytes bytes() const { return { reinterpret_cast(data()), size() }; } 94 | 95 | private: 96 | void copy_to(Span other) const; 97 | }; 98 | 99 | } 100 | -------------------------------------------------------------------------------- /Std/Types.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | using u8 = unsigned char; 4 | using u16 = unsigned short; 5 | using u32 = unsigned int; 6 | using u64 = unsigned long long; 7 | using i8 = signed char; 8 | using i16 = signed short; 9 | using i32 = signed int; 10 | using i64 = signed long long; 11 | 12 | #if defined(TEST) || defined(HOST) 13 | using usize = long unsigned int; 14 | using isize = i64; 15 | using uptr = u64; 16 | #else 17 | using usize = u32; 18 | using isize = i32; 19 | using uptr = u32; 20 | #endif 21 | 22 | static_assert(sizeof(u8) == 1); 23 | static_assert(sizeof(u16) == 2); 24 | static_assert(sizeof(u32) == 4); 25 | static_assert(sizeof(u64) == 8); 26 | static_assert(sizeof(i8) == 1); 27 | static_assert(sizeof(i16) == 2); 28 | static_assert(sizeof(i32) == 4); 29 | static_assert(sizeof(i64) == 8); 30 | static_assert(sizeof(usize) == sizeof(decltype(sizeof(int)))); 31 | static_assert(sizeof(isize) == sizeof(decltype(sizeof(int)))); 32 | static_assert(sizeof(uptr) == sizeof(void*)); 33 | 34 | using nullptr_t = decltype(nullptr); 35 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | ### TODO 2 | 3 | #### Next Version 4 | 5 | - Add `SoftwareSpinLock` that uses a hardware spin lock but there can be more than actual hardware spin locks. 6 | This is an active locking primitive that must only be used with interrupts disabled. 7 | It should be simple to add deadlock detection here. 8 | 9 | - Add `SoftwareMutex` that uses a `HardwareSpinLock` internally. 10 | This is a passive locking primitive that must only be used with interrupts enabled. 11 | 12 | - Add `WaitingThreadQueue` that keeps a list of threads that are waiting for some resource. 13 | This must only be used with interrupts disabled and uses a `HardwareSpinLock` internally. 14 | 15 | - Verify that all of these locking primitives are functional. 16 | 17 | - Protect all the resources with these locking primitives. 18 | 19 | - Schedule on both cores. 20 | 21 | ### Old 22 | 23 | #### Next Version 24 | 25 | - Group `PageRange`s together in `PageAllocator::deallocate`. 26 | 27 | - Add passive locking primitives 28 | 29 | - Add active locking primitives 30 | 31 | #### Bugs 32 | 33 | - We have a ton of memory leaks in the filesystem, e.g. `VirtualFile::create_handle_impl`. 34 | 35 | - If we do `stat /dev/tty` we get invalid information, because `ConsoleFileHandle` always 36 | returns `ConsoleFile` instead of the actual file. 37 | 38 | - Sometimes we seem to mess something up, this is visible when `ConsoleFileHandle` has an invalid 39 | `this` pointer. I reproduced this by running: 40 | 41 | ~~~none 42 | Example.elf 43 | Example.elf 44 | Example.elf 45 | Example.elf 46 | Example.elf 47 | Example.elf 48 | Example.elf 49 | ~~~ 50 | 51 | #### Future features 52 | 53 | - Keep track of 'used' page ranges. There is an excelent algorithm that can be used to store these bits 54 | in a very compact tree structure. 55 | 56 | - Maybe I could port the Minix filesystem when I add an IDE driver? 57 | 58 | - Keep documentation about interrupt safe functions and which functions can be called in which boot stage 59 | 60 | - Add `MemoryAllocator::allocate_eternal` which doesn't create MTRACE logs 61 | 62 | - Run inside QEMU 63 | 64 | - Write userland applications in Zig 65 | 66 | #### Future tweaks (Userland) 67 | 68 | - Implement a proper malloc 69 | 70 | #### Future tweaks (Kernel) 71 | 72 | - Setup MPU for supervisor mode 73 | 74 | - HardFault in usermode crashes kernel 75 | 76 | - Stack smash protection with MPU 77 | 78 | - Build with `-fstack-protector`? 79 | 80 | #### Future tweaks (Build) 81 | 82 | - Alignment of `.stack`, `.heap` sections is lost in `readelf` 83 | 84 | - C++20 modules 85 | 86 | - Drop SDK entirely 87 | 88 | - Link `libsup++` or add a custom downcast? 89 | 90 | - Meson build 91 | 92 | - Try using LLDB instead of GDB 93 | 94 | - Don't leak includes from newlib libc 95 | 96 | - Use LLVM/LLD for `FileEmbed`; Not sure what I meant with this, but LLVM 97 | surely has all the tools buildin that I need 98 | 99 | - GDB apparently has a secret 'proc' command that makes it possible to debug 100 | multiple processes. This was mentioned in the DragonFlyBSD documentation, 101 | keyword: "inferiour" 102 | -------------------------------------------------------------------------------- /Tests/.gitignore: -------------------------------------------------------------------------------- 1 | /Build/ 2 | -------------------------------------------------------------------------------- /Tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.19.5) 2 | project(Tests CXX) 3 | 4 | enable_testing() 5 | 6 | add_library(project_options INTERFACE) 7 | target_compile_features(project_options INTERFACE cxx_std_20) 8 | target_compile_options(project_options INTERFACE -fdiagnostics-color=always -fsanitize=address -O0 -g -Werror) 9 | target_link_options(project_options INTERFACE -fsanitize=address) 10 | target_compile_definitions(project_options INTERFACE TEST) 11 | target_include_directories(project_options INTERFACE ${CMAKE_SOURCE_DIR}/..) 12 | 13 | file(GLOB_RECURSE Std_SOURCES CONFIGURE_DEPENDS ../Std/*.cpp) 14 | add_library(LibStd ${Std_SOURCES}) 15 | target_link_libraries(LibStd project_options) 16 | 17 | file(GLOB Tests_SOURCES CONFIGURE_DEPENDS *.cpp) 18 | 19 | add_library(LibTests ${Tests_SOURCES}) 20 | target_link_libraries(LibTests LibStd project_options) 21 | 22 | file(GLOB Std_TESTS CONFIGURE_DEPENDS Std/*.cpp) 23 | 24 | foreach(source ${Std_TESTS}) 25 | get_filename_component(name ${source} NAME_WE) 26 | 27 | add_executable(${name} ${source}) 28 | target_link_libraries(${name} LibTests LibStd project_options) 29 | 30 | add_test(NAME ${name} COMMAND ${CMAKE_CURRENT_BINARY_DIR}/${name}) 31 | endforeach() 32 | -------------------------------------------------------------------------------- /Tests/Std/TestArmedScopeGuard.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | TEST_CASE(armedscopeguard) 6 | { 7 | int i = 0; 8 | 9 | { 10 | Std::ArmedScopeGuard guard = [&] { 11 | ++i; 12 | }; 13 | 14 | ASSERT(i == 0); 15 | i = 42; 16 | 17 | guard.disarm(); 18 | } 19 | 20 | ASSERT(i == 42); 21 | i = 13; 22 | 23 | { 24 | Std::ArmedScopeGuard guard = [&] { 25 | ++i; 26 | }; 27 | 28 | ASSERT(i == 13); 29 | } 30 | 31 | ASSERT(i == 14); 32 | } 33 | 34 | TEST_CASE(armedscopeguard_move) 35 | { 36 | int i = 0; 37 | 38 | { 39 | Std::ArmedScopeGuard guard1 = [&] { 40 | ++i; 41 | }; 42 | 43 | ASSERT(i == 0); 44 | 45 | { 46 | Std::ArmedScopeGuard guard2 = move(guard1); 47 | 48 | ASSERT(i == 0); 49 | 50 | Std::ArmedScopeGuard guard3 = move(guard2); 51 | 52 | ASSERT(i == 0); 53 | } 54 | 55 | ASSERT(i == 1); 56 | } 57 | 58 | ASSERT(i == 1); 59 | } 60 | 61 | TEST_MAIN(); 62 | -------------------------------------------------------------------------------- /Tests/Std/TestFormat.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | struct A { 7 | }; 8 | 9 | TEST_CASE(format_has_formatter) 10 | { 11 | ASSERT(Std::HasFormatter::value); 12 | ASSERT(!Std::HasFormatter::value); 13 | } 14 | 15 | TEST_CASE(format) 16 | { 17 | auto test = [](std::string_view expected, std::string_view format, const Parameters&... parameters) { 18 | Std::StringBuilder builder; 19 | builder.appendf(Std::StringView { format.data(), format.size() }, parameters...); 20 | 21 | ASSERT((expected == std::string_view { builder.view().data(), builder.view().size() })); 22 | }; 23 | 24 | test("0x0000002a", "{}", u32(42)); 25 | test("0x01020304", "{}", u32(0x01020304)); 26 | test("-0x00000001", "{}", i32(-1)); 27 | test("0x01020304aabbccdd", "{}", u64(0x01020304aabbccddULL)); 28 | test("0xab", "{}", u8(0xab)); 29 | test("0x12", "{}", i8(0x12)); 30 | 31 | test("foo bar baz", "foo {} baz", "bar"); 32 | test("abc", "a{}c", 'b'); 33 | test(std::string_view { "ax\0yb", 5 }, "a{}b", Std::StringView { "x\0y", 3 }); 34 | 35 | test("a0x00000020a bXYZb c-0x00000004c", "a{}a b{}b c{}c", u32(32), "XYZ", i32(-4)); 36 | } 37 | 38 | TEST_MAIN(); 39 | -------------------------------------------------------------------------------- /Tests/Std/TestHashMap.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | TEST_CASE(hashmap) 6 | { 7 | Std::HashMap map; 8 | 9 | map.set(42, 13); 10 | map.set(13, 51); 11 | map.set(-3, -3); 12 | map.set(42, 7); 13 | 14 | ASSERT(map.size() == 3); 15 | 16 | ASSERT(map.get(13) != nullptr && *map.get(13) == 51); 17 | ASSERT(map.get(16) == nullptr); 18 | ASSERT(map.get(42) != nullptr && *map.get(42) == 7); 19 | 20 | map.remove(42); 21 | 22 | ASSERT(map.size() == 2); 23 | 24 | ASSERT(map.get(-3) != nullptr && *map.get(-3) == -3); 25 | } 26 | 27 | TEST_CASE(hashmap_strings) 28 | { 29 | Std::HashMap map; 30 | 31 | map.set("/redirect/foo", "/etc/foo"); 32 | map.set("/redirect/bar", "/proc/mounts"); 33 | map.set("/redirect/baz", "/bin/Shell.elf"); 34 | 35 | map.set("/redirect/bar", "/proc/mounts2"); 36 | 37 | ASSERT(map.size() == 3); 38 | ASSERT(map.get("/redirect/bar") != nullptr); 39 | ASSERT(*map.get("/redirect/bar") == "/proc/mounts2"); 40 | } 41 | 42 | TEST_CASE(hashmap_iterator) 43 | { 44 | Std::HashMap map; 45 | 46 | map.set(89, 0); 47 | map.set(3, 42); 48 | map.set(7, 6); 49 | map.set(3, 16); 50 | map.set(100, 4); 51 | 52 | bool did_see_pair_1 = false; 53 | bool did_see_pair_3 = false; 54 | bool did_see_pair_4 = false; 55 | bool did_see_pair_5 = false; 56 | 57 | for (auto [key, value] : map.iter()) { 58 | if (key == 89 && value.must() == 0) 59 | ASSERT(exchange(did_see_pair_1, true) == false); 60 | else if (key == 7 && value.must() == 6) 61 | ASSERT(exchange(did_see_pair_3, true) == false); 62 | else if (key == 3 && value.must() == 16) 63 | ASSERT(exchange(did_see_pair_4, true) == false); 64 | else if (key == 100 && value.must() == 4) 65 | ASSERT(exchange(did_see_pair_5, true) == false); 66 | else 67 | ASSERT_NOT_REACHED(); 68 | } 69 | 70 | ASSERT(did_see_pair_1); 71 | ASSERT(did_see_pair_3); 72 | ASSERT(did_see_pair_4); 73 | ASSERT(did_see_pair_5); 74 | } 75 | 76 | TEST_MAIN(); 77 | -------------------------------------------------------------------------------- /Tests/Std/TestHashTable.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | #include 6 | 7 | TEST_CASE(hashtable_int) 8 | { 9 | Std::HashTable hash; 10 | 11 | hash.insert(1); 12 | hash.insert(2); 13 | hash.insert(3); 14 | hash.insert(4); 15 | hash.insert(5); 16 | hash.insert(6); 17 | hash.insert(7); 18 | hash.insert(8); 19 | hash.insert(9); 20 | 21 | hash.insert(6); 22 | 23 | ASSERT(hash.size() == 9); 24 | } 25 | 26 | struct A { 27 | int m_hash; 28 | int m_value; 29 | 30 | bool operator<(const A& other) const 31 | { 32 | return m_value < other.m_value; 33 | } 34 | bool operator>(const A& other) const 35 | { 36 | return m_value > other.m_value; 37 | } 38 | 39 | u32 hash() const 40 | { 41 | return m_hash; 42 | } 43 | }; 44 | 45 | template<> 46 | struct Std::Hash { 47 | static u32 compute(A value) 48 | { 49 | return value.m_hash; 50 | } 51 | }; 52 | 53 | TEST_CASE(hashtable_collisions) 54 | { 55 | Std::HashTable hash; 56 | 57 | hash.insert({ 1, 42 }); 58 | hash.insert({ 2, 13 }); 59 | hash.insert({ 3, -7 }); 60 | hash.insert({ 2, -11 }); 61 | 62 | ASSERT(hash.size() == 4); 63 | 64 | hash.remove({ 2, -11 }); 65 | 66 | ASSERT(hash.size() == 3); 67 | 68 | ASSERT(hash.search({ 2, 13 }) != nullptr); 69 | ASSERT(hash.search({ 2, 13 })->m_value == 13); 70 | 71 | ASSERT(hash.search({ 2, -11 }) == nullptr); 72 | ASSERT(hash.search({ 3, 7 }) == nullptr); 73 | } 74 | 75 | TEST_CASE(hashtable_iterator) 76 | { 77 | Std::HashTable hash; 78 | 79 | hash.insert(14); 80 | hash.insert(72); 81 | hash.insert(3); 82 | hash.insert(0); 83 | 84 | bool did_see_14 = false; 85 | bool did_see_72 = false; 86 | bool did_see_3 = false; 87 | bool did_see_0 = false; 88 | 89 | for (int value : hash.iter()) { 90 | if (value == 14) 91 | ASSERT(std::exchange(did_see_14, true) == false); 92 | else if (value == 72) 93 | ASSERT(std::exchange(did_see_72, true) == false); 94 | else if (value == 3) 95 | ASSERT(std::exchange(did_see_3, true) == false); 96 | else if (value == 0) 97 | ASSERT(std::exchange(did_see_0, true) == false); 98 | else 99 | ASSERT_NOT_REACHED(); 100 | } 101 | 102 | ASSERT(did_see_14); 103 | ASSERT(did_see_72); 104 | ASSERT(did_see_3); 105 | ASSERT(did_see_0); 106 | } 107 | 108 | TEST_MAIN(); 109 | -------------------------------------------------------------------------------- /Tests/Std/TestLexer.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | TEST_CASE(lexer) 6 | { 7 | Std::Lexer lexer { "foo\nbar\nbaz" }; 8 | 9 | ASSERT(!lexer.eof()); 10 | ASSERT(lexer.consume() == 'f'); 11 | ASSERT(!lexer.try_consume('x')); 12 | ASSERT(lexer.consume_until('\n') == "oo"); 13 | 14 | ASSERT(lexer.peek_or_null() == '\n'); 15 | lexer.consume(); 16 | 17 | ASSERT(lexer.consume_until('\n') == "bar"); 18 | ASSERT(lexer.try_consume('\n')); 19 | 20 | ASSERT(lexer.consume_until('\n') == "baz"); 21 | ASSERT(lexer.eof()); 22 | ASSERT(!lexer.try_consume('\n')); 23 | } 24 | 25 | TEST_MAIN(); 26 | -------------------------------------------------------------------------------- /Tests/Std/TestMemoryAllocator.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | // We are making some assumptions here about the nature of the implementation 12 | 13 | TEST_CASE(memoryallocator) 14 | { 15 | std::array heap; 16 | 17 | Std::MemoryAllocator mem { { heap.data(), heap.size() } }; 18 | 19 | u8 *pointer1 = mem.allocate(32); 20 | u8 *pointer2 = mem.allocate(64); 21 | u8 *pointer3 = mem.allocate(32); 22 | 23 | mem.deallocate(pointer2); 24 | 25 | u8 *pointer4 = mem.allocate(64); 26 | 27 | std::memset(pointer4, 0x33, 64); 28 | 29 | std::memset(pointer1, 0xff, 32); 30 | std::memset(pointer3, 0xff, 32); 31 | 32 | for (usize i = 0; i < 64; ++i) 33 | ASSERT(pointer4[i] == 0x33); 34 | } 35 | 36 | TEST_CASE(memoryallocator_death_by_a_thousand_cuts) 37 | { 38 | std::array heap; 39 | 40 | constexpr usize max_allocations = (heap.size() / 20) - 1; 41 | 42 | Std::MemoryAllocator mem { { heap.data(), heap.size() } }; 43 | 44 | auto stats_before = mem.statistics(); 45 | 46 | ASSERT(stats_before.m_largest_continous_block > heap.size() / 2); 47 | 48 | std::vector allocations; 49 | for (usize i = 0; i < max_allocations; ++i) 50 | allocations.push_back(mem.allocate(4)); 51 | 52 | auto stats_middle = mem.statistics(); 53 | ASSERT(stats_middle.m_largest_continous_block < heap.size() / 4); 54 | 55 | // We want to deallocate in a random, but reproducible order 56 | std::mt19937 prng { 3040088493306752707 }; 57 | std::shuffle(allocations.begin(), allocations.end(), prng); 58 | 59 | for (u8 *allocation : allocations) 60 | mem.deallocate(allocation); 61 | 62 | auto stats_after = mem.statistics(); 63 | 64 | ASSERT(stats_before.m_largest_continous_block == stats_after.m_largest_continous_block); 65 | } 66 | 67 | TEST_MAIN(); 68 | -------------------------------------------------------------------------------- /Tests/Std/TestOptional.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | TEST_CASE(optional) 6 | { 7 | Std::Optional opt; 8 | 9 | ASSERT(opt.is_valid() == false); 10 | ASSERT(opt.value_or(42) == 42); 11 | 12 | opt = 13; 13 | 14 | ASSERT(opt.is_valid() == true); 15 | ASSERT(opt.value_or(42) == 13); 16 | ASSERT(opt.value() == 13); 17 | ASSERT(opt.must() == 13); 18 | } 19 | 20 | TEST_CASE(optional_destructors) 21 | { 22 | Tests::Tracker::clear(); 23 | 24 | { 25 | Std::Optional opt; 26 | 27 | Tests::Tracker::assert(0, 0, 0, 0); 28 | 29 | opt = Tests::Tracker { 42 }; 30 | 31 | Tests::Tracker::assert(1, 1, 0, 1); 32 | } 33 | 34 | Tests::Tracker::assert(1, 1, 0, 2); 35 | } 36 | 37 | TEST_CASE(optional_moving) 38 | { 39 | Tests::Tracker::clear(); 40 | 41 | { 42 | Std::Optional opt1; 43 | 44 | Tests::Tracker::assert(0, 0, 0, 0); 45 | 46 | { 47 | Std::Optional opt2 { 42 }; 48 | 49 | Tests::Tracker::assert(1, 1, 0, 1); 50 | 51 | { 52 | Std::Optional opt3 { 13 }; 53 | 54 | Tests::Tracker::assert(2, 2, 0, 2); 55 | 56 | opt1 = opt3; 57 | 58 | Tests::Tracker::assert(2, 2, 1, 2); 59 | 60 | ASSERT(opt1.must() == 13); 61 | 62 | Tests::Tracker::assert(3, 2, 1, 3); 63 | } 64 | 65 | Tests::Tracker::assert(3, 2, 1, 4); 66 | 67 | opt2 = 7; 68 | 69 | Tests::Tracker::assert(4, 3, 1, 6); 70 | } 71 | 72 | Tests::Tracker::assert(4, 3, 1, 7); 73 | 74 | ASSERT(opt1.must() == 13); 75 | 76 | Tests::Tracker::assert(5, 3, 1, 8); 77 | 78 | opt1.clear(); 79 | 80 | Tests::Tracker::assert(5, 3, 1, 9); 81 | } 82 | 83 | Tests::Tracker::assert(5, 3, 1, 9); 84 | } 85 | 86 | TEST_CASE(optional_move_clears) 87 | { 88 | Std::Optional opt1 = 42; 89 | 90 | Std::Optional opt2 = move(opt1); 91 | 92 | ASSERT(!opt1.is_valid()); 93 | } 94 | 95 | TEST_CASE(optional_rvalue_must_clears) 96 | { 97 | Std::Optional opt1 = 42; 98 | 99 | Std::Optional opt2 = move(opt1).must(); 100 | 101 | ASSERT(!opt1.is_valid()); 102 | } 103 | 104 | TEST_CASE(optional_does_not_return_dangling_reference) 105 | { 106 | struct A { 107 | bool m_initialized = false; 108 | 109 | A() : m_initialized(true) { } 110 | ~A() 111 | { 112 | m_initialized = false; 113 | } 114 | }; 115 | 116 | Std::Optional opt2; 117 | 118 | { 119 | Std::Optional opt1 { A{} }; 120 | opt2 = move(opt1).must(); 121 | 122 | ASSERT(!opt1.is_valid()); 123 | } 124 | 125 | ASSERT(opt2.is_valid()); 126 | ASSERT(opt2->m_initialized); 127 | } 128 | 129 | TEST_MAIN(); 130 | -------------------------------------------------------------------------------- /Tests/Std/TestOwnPtr.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | #include 6 | 7 | static_assert(!std::is_copy_constructible_v>); 8 | static_assert(!std::is_copy_assignable_v>); 9 | 10 | TEST_CASE(ownptr) 11 | { 12 | Std::OwnPtr pointer1; 13 | 14 | ASSERT(pointer1.ptr() == nullptr); 15 | 16 | pointer1 = new int { 42 }; 17 | 18 | ASSERT(pointer1.ptr() != nullptr); 19 | ASSERT(*pointer1 == 42); 20 | } 21 | 22 | TEST_CASE(ownptr_cons) 23 | { 24 | Tests::Tracker::clear(); 25 | 26 | { 27 | Std::OwnPtr pointer1; 28 | 29 | Tests::Tracker::assert(0, 0, 0, 0); 30 | 31 | { 32 | auto pointer2 = Std::make(); 33 | 34 | Tests::Tracker::assert(1, 0, 0, 0); 35 | 36 | ASSERT(pointer2.ptr() != nullptr); 37 | pointer1 = move(pointer2); 38 | ASSERT(pointer2.ptr() == nullptr); 39 | 40 | Tests::Tracker::assert(1, 0, 0, 0); 41 | } 42 | 43 | Tests::Tracker::assert(1, 0, 0, 0); 44 | } 45 | 46 | Tests::Tracker::assert(1, 0, 0, 1); 47 | } 48 | 49 | TEST_CASE(ownptr_leak) 50 | { 51 | Tests::Tracker::clear(); 52 | 53 | Tests::Tracker *raw_pointer = nullptr; 54 | 55 | { 56 | Std::OwnPtr pointer = Std::make(); 57 | 58 | Tests::Tracker::assert(1, 0, 0, 0); 59 | 60 | raw_pointer = pointer.leak(); 61 | } 62 | 63 | Tests::Tracker::assert(1, 0, 0, 0); 64 | 65 | delete raw_pointer; 66 | } 67 | 68 | TEST_MAIN(); 69 | -------------------------------------------------------------------------------- /Tests/Std/TestPath.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | TEST_CASE(path) 6 | { 7 | Std::Path path { "/foo/bar/baz" }; 8 | ASSERT(path.is_absolute()); 9 | ASSERT(path.components().size() == 3); 10 | ASSERT(path.components()[0] == "foo"); 11 | ASSERT(path.components()[1] == "bar"); 12 | ASSERT(path.components()[2] == "baz"); 13 | ASSERT(path.components()[0] != "xyz"); 14 | 15 | ASSERT(path.string() == "/foo/bar/baz"); 16 | 17 | // FIXME: Using the '/' operator seems to free the underlying strings when the new object goes out of scope? 18 | ASSERT((path / "xyz").string() == "/foo/bar/baz/xyz"); 19 | 20 | ASSERT(path.string() == "/foo/bar/baz"); 21 | 22 | ASSERT((path / "x").is_absolute()); 23 | 24 | ASSERT(!Std::Path { "x" }.is_absolute()); 25 | } 26 | 27 | TEST_CASE(path_parent_1) 28 | { 29 | Std::Path path1 { "x/y/z/w" }; 30 | 31 | ASSERT(path1.string() == "x/y/z/w"); 32 | ASSERT(path1.is_absolute() == false); 33 | 34 | Std::Path path2 = path1.parent(); 35 | ASSERT(path2.string() == "x/y/z"); 36 | ASSERT(path2.is_absolute() == false); 37 | 38 | Std::Path path3 = path2.parent(); 39 | ASSERT(path3.string() == "x/y"); 40 | ASSERT(path3.is_absolute() == false); 41 | 42 | Std::Path path4 = path3.parent(); 43 | ASSERT(path4.string() == "x"); 44 | ASSERT(path4.is_absolute() == false); 45 | } 46 | 47 | TEST_CASE(path_parent_2) 48 | { 49 | Std::Path path1 { "/x/y" }; 50 | 51 | ASSERT(path1.string() == "/x/y"); 52 | ASSERT(path1.is_absolute() == true); 53 | 54 | Std::Path path2 = path1.parent(); 55 | ASSERT(path2.string() == "/x"); 56 | ASSERT(path2.is_absolute() == true); 57 | 58 | path1 = path1 / "z"; 59 | 60 | ASSERT(path1.string() == "/x/y/z"); 61 | ASSERT(path1.is_absolute() == true); 62 | 63 | ASSERT(path2.string() == "/x"); 64 | ASSERT(path2.string() != "/y"); 65 | ASSERT(path2.is_absolute() == true); 66 | } 67 | 68 | TEST_CASE(path_filename) 69 | { 70 | Std::Path path1 { "/x/y" }; 71 | 72 | ASSERT(path1.filename() == "y"); 73 | ASSERT(path1.parent().filename() == "x"); 74 | } 75 | 76 | TEST_CASE(path_absolute_regression) 77 | { 78 | Std::Path path1 { "/foo.txt" }; 79 | 80 | ASSERT(path1.is_absolute() == true); 81 | 82 | Std::Path path2 = path1.parent(); 83 | ASSERT(path2.string() == "/"); 84 | ASSERT(path2.is_absolute() == true); 85 | } 86 | 87 | TEST_MAIN(); 88 | -------------------------------------------------------------------------------- /Tests/Std/TestRefPtr.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | struct A : Std::RefCounted { 6 | }; 7 | 8 | TEST_CASE(refptr) 9 | { 10 | Std::RefPtr refptr = A::construct(); 11 | ASSERT(refptr->refcount() == 1); 12 | } 13 | 14 | struct B : Tests::Tracker, Std::RefCounted { 15 | B() : Tests::Tracker() { } 16 | }; 17 | 18 | TEST_CASE(refptr_destructor_called) 19 | { 20 | Tests::Tracker::clear(); 21 | 22 | { 23 | auto refptr = B::construct(); 24 | 25 | Tests::Tracker::assert(1, 0, 0, 0); 26 | } 27 | 28 | Tests::Tracker::assert(1, 0, 0, 1); 29 | } 30 | 31 | TEST_CASE(refptr_rawptr) 32 | { 33 | Tests::Tracker::clear(); 34 | 35 | B *rawptr; 36 | { 37 | auto refptr = B::construct(); 38 | 39 | rawptr = refptr; 40 | rawptr->ref(); 41 | 42 | Tests::Tracker::assert(1, 0, 0, 0); 43 | } 44 | 45 | Tests::Tracker::assert(1, 0, 0, 0); 46 | 47 | rawptr->unref(); 48 | 49 | Tests::Tracker::assert(1, 0, 0, 1); 50 | } 51 | 52 | TEST_CASE(refptr_multiple) 53 | { 54 | Tests::Tracker::clear(); 55 | 56 | { 57 | auto refptr1 = B::construct(); 58 | 59 | { 60 | Std::RefPtr refptr2 = refptr1; 61 | 62 | Tests::Tracker::assert(1, 0, 0, 0); 63 | } 64 | 65 | Tests::Tracker::assert(1, 0, 0, 0); 66 | } 67 | 68 | Tests::Tracker::assert(1, 0, 0, 1); 69 | } 70 | 71 | TEST_CASE(refptr_nullptr) 72 | { 73 | Tests::Tracker::clear(); 74 | 75 | { 76 | Std::RefPtr refptr; 77 | } 78 | 79 | Tests::Tracker::assert(0, 0, 0, 0); 80 | } 81 | 82 | TEST_CASE(refptr_move) 83 | { 84 | Tests::Tracker::clear(); 85 | 86 | auto refptr1 = B::construct(); 87 | auto refptr2 = move(refptr1); 88 | 89 | VERIFY(refptr1 == nullptr); 90 | VERIFY(refptr2->refcount() == 1); 91 | } 92 | 93 | TEST_CASE(refptr_assign) 94 | { 95 | Tests::Tracker::clear(); 96 | 97 | auto refptr1 = B::construct(); 98 | auto refptr2 = B::construct(); 99 | 100 | Tests::Tracker::assert(2, 0, 0, 0); 101 | 102 | refptr2 = refptr1; 103 | 104 | Tests::Tracker::assert(2, 0, 0, 1); 105 | } 106 | 107 | static void foo(Std::RefPtr) { 108 | // Let parameter go out of scope. 109 | } 110 | 111 | TEST_CASE(refptr_move_into_function) 112 | { 113 | Tests::Tracker::clear(); 114 | 115 | { 116 | auto refptr_1 = B::construct(); 117 | 118 | Tests::Tracker::assert(1, 0, 0, 0); 119 | 120 | foo(refptr_1); 121 | 122 | Tests::Tracker::assert(1, 0, 0, 0); 123 | 124 | foo(std::move(refptr_1)); 125 | 126 | Tests::Tracker::assert(1, 0, 0, 1); 127 | } 128 | 129 | Tests::Tracker::assert(1, 0, 0, 1); 130 | } 131 | 132 | static Std::RefPtr bar() { 133 | return B::construct(); 134 | } 135 | 136 | TEST_CASE(refptr_return_from_function) 137 | { 138 | Tests::Tracker::clear(); 139 | 140 | { 141 | auto refptr_1 = bar(); 142 | 143 | Tests::Tracker::assert(1, 0, 0, 0); 144 | } 145 | 146 | Tests::Tracker::assert(1, 0, 0, 1); 147 | 148 | { 149 | bar(); 150 | 151 | Tests::Tracker::assert(2, 0, 0, 2); 152 | } 153 | } 154 | 155 | TEST_MAIN(); 156 | -------------------------------------------------------------------------------- /Tests/Std/TestSingleton.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | class A : public Std::Singleton { 6 | public: 7 | A(const A&) 8 | { 9 | ASSERT_NOT_REACHED(); 10 | } 11 | A(A&&) 12 | { 13 | ASSERT_NOT_REACHED(); 14 | } 15 | ~A() 16 | { 17 | ASSERT_NOT_REACHED(); 18 | } 19 | 20 | private: 21 | friend Singleton; 22 | A() 23 | { 24 | static bool initialized = false; 25 | 26 | ASSERT(!initialized); 27 | initialized = true; 28 | } 29 | }; 30 | 31 | TEST_CASE(singleton) 32 | { 33 | A::initialize(); 34 | A::the(); 35 | } 36 | 37 | TEST_MAIN(); 38 | -------------------------------------------------------------------------------- /Tests/Std/TestSpan.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | #include 6 | 7 | TEST_CASE(span_bytes_from) 8 | { 9 | int value = 42; 10 | 11 | auto bytes = Std::bytes_from(value); 12 | 13 | ASSERT(bytes.size() == 4); 14 | ASSERT(bytes.data() == reinterpret_cast(&value)); 15 | } 16 | 17 | TEST_CASE(span_copy_to_exact) 18 | { 19 | int value1 = 42; 20 | auto bytes1 = Std::bytes_from(value1); 21 | 22 | int value2 = 0; 23 | auto bytes2 = Std::bytes_from(value2); 24 | 25 | bytes1.copy_to(bytes2); 26 | ASSERT(value1 == 42 && value2 == 42); 27 | 28 | value2 = 13; 29 | bytes2.copy_trimmed_to(bytes1); 30 | ASSERT(value1 == 13 && value2 == 13); 31 | } 32 | 33 | TEST_CASE(span_slice) 34 | { 35 | std::array buffer; 36 | auto bytes = Std::Bytes { buffer.data(), buffer.size() }; 37 | 38 | bytes.slice(3); 39 | ASSERT(bytes.data() == buffer.data()); 40 | ASSERT(bytes.size() == buffer.size()); 41 | 42 | ASSERT(bytes.slice(3).data() == buffer.data() + 3); 43 | ASSERT(bytes.slice(5).size() == buffer.size() - 5); 44 | 45 | ASSERT(bytes.slice(buffer.size()).data() == buffer.data() + buffer.size()); 46 | ASSERT(bytes.slice(buffer.size()).size() == 0); 47 | } 48 | 49 | TEST_CASE(span_iterator) 50 | { 51 | std::array buffer { 3, 0, 1, 8, 1, 2, 4, 10 }; 52 | auto span = Std::Span { buffer.data(), buffer.size() }; 53 | 54 | auto iter = span.iter(); 55 | 56 | ASSERT(*iter == 3); 57 | iter++; 58 | ASSERT(*iter == 0); 59 | 60 | ASSERT(iter.size() == 7); 61 | ASSERT(iter.data() == buffer.data() + 1); 62 | ASSERT((iter.end() == Std::Span { buffer.data() + buffer.size(), 0 })); 63 | 64 | iter = span.iter(); 65 | for (size_t index = 0; index < buffer.size(); ++index) 66 | ASSERT(*iter++ == buffer[index]); 67 | } 68 | 69 | TEST_MAIN(); 70 | -------------------------------------------------------------------------------- /Tests/Std/TestString.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | TEST_CASE(string_compare) 6 | { 7 | Std::ImmutableString string_1{ "abc" }; 8 | Std::ImmutableString string_2{ "abc" }; 9 | Std::ImmutableString string_3{ "abcd" }; 10 | Std::ImmutableString string_4{ "ab" }; 11 | 12 | ASSERT(string_1 == string_2); 13 | ASSERT(string_1 != string_3); 14 | ASSERT(string_1 != string_4); 15 | ASSERT(string_3 != string_4); 16 | } 17 | 18 | TEST_CASE(string_copy) 19 | { 20 | Std::ImmutableString string_1{ "abc" }; 21 | Std::ImmutableString string_2 = string_1; 22 | Std::ImmutableString string_3{ "cba" }; 23 | 24 | ASSERT(string_1 == string_2); 25 | ASSERT(string_1 != string_3); 26 | 27 | string_1 = string_3; 28 | 29 | ASSERT(string_1 != string_2); 30 | 31 | { 32 | Std::ImmutableString string_4 = string_1; 33 | } 34 | 35 | ASSERT(string_1 != string_2); 36 | ASSERT(string_1 == string_3); 37 | } 38 | 39 | TEST_MAIN(); 40 | -------------------------------------------------------------------------------- /Tests/Std/TestStringBuilder.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | template<> 6 | struct Std::Formatter { 7 | static void format(StringBuilder& builder, std::strong_ordering value) 8 | { 9 | if (value == std::strong_ordering::equal) { 10 | builder.append("equal"); 11 | } else if (value == std::strong_ordering::less) { 12 | builder.append("less"); 13 | } else if (value == std::strong_ordering::greater) { 14 | builder.append("greater"); 15 | } else if (value == std::strong_ordering::equivalent) { 16 | builder.append("equivalent"); 17 | } else { 18 | VERIFY_NOT_REACHED(); 19 | } 20 | } 21 | }; 22 | 23 | TEST_CASE(stringbuilder) 24 | { 25 | Std::StringBuilder builder; 26 | builder.append("foo"); 27 | builder.append(' '); 28 | builder.append("bar"); 29 | builder.append(' '); 30 | builder.appendf("b{}z", "a"); 31 | 32 | ASSERT(string_1.size() == string_1.view().size()); 33 | 34 | ASSERT(builder.view() == "foo bar baz"); 35 | ASSERT(builder.string().view() == "foo bar baz"); 36 | } 37 | 38 | TEST_CASE(stringbuilder_exceed_inline_capacity) 39 | { 40 | Std::StringBuilder builder; 41 | 42 | for (usize i = 0; i < 513; ++i) 43 | builder.append('a' + i % 26); 44 | 45 | auto string = builder.string(); 46 | ASSERT(string.size() == 513); 47 | 48 | for (usize i = 0; i < 513; ++i) 49 | ASSERT(string.data()[i] == 'a' + i % 26); 50 | } 51 | 52 | TEST_MAIN(); 53 | -------------------------------------------------------------------------------- /Tests/Std/TestStringView.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | TEST_CASE(stringview_trivial) 9 | { 10 | ASSERT(Std::StringView{ "abc" } == Std::StringView{ "abc" }); 11 | ASSERT(Std::StringView{ "abc" } != Std::StringView{ "abcd" }); 12 | } 13 | 14 | TEST_CASE(stringview_cstring) 15 | { 16 | Std::StringView sv { "Hello, world!" }; 17 | 18 | ASSERT((std::string_view { "Hello, world!" } == std::string_view { sv.data(), sv.size() })); 19 | 20 | sv = "foo"; 21 | 22 | ASSERT((std::string_view { "foo" } == std::string_view { sv.data(), sv.size() })); 23 | } 24 | 25 | TEST_CASE(stringview_index_of) 26 | { 27 | Std::StringView sv = "0123456"; 28 | 29 | ASSERT(sv.index_of('0').must() == 0); 30 | ASSERT(sv.index_of('3').must() == 3); 31 | ASSERT(sv.index_of('7').is_valid() == false); 32 | } 33 | 34 | TEST_CASE(stringview_equal) 35 | { 36 | Std::StringView sv1 = "foo"; 37 | Std::StringView sv2 = "bar"; 38 | Std::StringView sv3 = sv2; 39 | Std::StringView sv4 = "foobar" + 3; 40 | 41 | ASSERT(sv1 != sv2); 42 | ASSERT(sv2 == sv3); 43 | ASSERT(sv3 == sv4); 44 | } 45 | 46 | TEST_CASE(stringview_substr) 47 | { 48 | Std::StringView sv = "foobarbaz"; 49 | ASSERT(sv.substr(0) == "foobarbaz"); 50 | ASSERT(sv.substr(3) == "barbaz"); 51 | ASSERT(sv.substr(3, 6) == "bar"); 52 | } 53 | 54 | TEST_CASE(stringview_trim) 55 | { 56 | Std::StringView sv = "xy"; 57 | ASSERT(sv.trim(3).size() == 2); 58 | ASSERT(sv.trim(1) == "x"); 59 | } 60 | 61 | TEST_CASE(stringview_strcpy_1) 62 | { 63 | Std::StringView sv = "Hello, world!"; 64 | std::array buffer; 65 | 66 | sv.strcpy_to({ buffer.data(), buffer.size() }); 67 | 68 | auto actual = std::span { buffer }.subspan(0, 14); 69 | std::array expected = { 72, 101, 108, 108, 111, 44, 32, 119, 111, 114, 108, 100, 33, 0 }; 70 | ASSERT(std::equal(actual.begin(), actual.end(), expected.begin(), expected.end())); 71 | } 72 | 73 | TEST_CASE(stringview_strcpy_2) 74 | { 75 | Std::StringView sv = { "foo\0bar", 7 }; 76 | std::array buffer; 77 | 78 | sv.strcpy_to({ buffer.data(), buffer.size() }); 79 | 80 | auto actual = std::span { buffer }.subspan(0, 8); 81 | std::array expected = { 102, 111, 111, 0, 98, 97, 114, 0 }; 82 | ASSERT(std::equal(actual.begin(), actual.end(), expected.begin(), expected.end())); 83 | } 84 | 85 | TEST_MAIN(); 86 | -------------------------------------------------------------------------------- /Tests/Std/TestVector.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | TEST_CASE(vector_default) 6 | { 7 | Std::Vector vec; 8 | 9 | ASSERT(vec.size() == 0); 10 | 11 | vec.append(42); 12 | 13 | ASSERT(vec.size() == 1); 14 | ASSERT(vec[0] == 42); 15 | } 16 | 17 | TEST_CASE(vector_inline_no_move) 18 | { 19 | Tests::Tracker::clear(); 20 | 21 | Std::Vector vec; 22 | vec.append({}); 23 | vec.append({}); 24 | vec.append({}); 25 | vec.append({}); 26 | 27 | Tests::Tracker::assert(4, 4, 0, 4); 28 | } 29 | 30 | TEST_CASE(vector_ensure_capacity_no_move) 31 | { 32 | Tests::Tracker::clear(); 33 | 34 | Std::Vector vec; 35 | vec.ensure_capacity(3); 36 | 37 | vec.append({}); 38 | vec.append({}); 39 | vec.append({}); 40 | 41 | Tests::Tracker::assert(3, 3, 0, 3); 42 | } 43 | 44 | TEST_CASE(vector_extend_no_move) 45 | { 46 | Std::Vector vec1; 47 | Std::Vector vec2; 48 | 49 | vec1.append({}); 50 | vec1.append({}); 51 | 52 | Tests::Tracker::clear(); 53 | 54 | vec2.extend(vec1.span()); 55 | 56 | Tests::Tracker::assert(0, 0, 2, 0); 57 | } 58 | 59 | TEST_CASE(vector_inline_no_create) 60 | { 61 | Tests::Tracker::clear(); 62 | 63 | Std::Vector vec; 64 | 65 | Tests::Tracker::assert(0, {}, {}, {}); 66 | } 67 | 68 | TEST_CASE(vector_copy) 69 | { 70 | Std::Vector vec1; 71 | vec1.append(1); 72 | 73 | Std::Vector vec2 { vec1 }; 74 | vec2.append(2); 75 | vec2.append(3); 76 | 77 | ASSERT(vec1.size() == 1); 78 | ASSERT(vec2.size() == 3); 79 | 80 | vec1.append(1); 81 | 82 | ASSERT(vec1.size() == 2); 83 | ASSERT(vec2.size() == 3); 84 | } 85 | 86 | TEST_CASE(vector_move) 87 | { 88 | Std::Vector vec1; 89 | vec1.append(1); 90 | 91 | Std::Vector vec2 { std::move(vec1) }; 92 | vec2.append(2); 93 | 94 | ASSERT(vec1.size() == 0); 95 | ASSERT(vec2.size() == 2); 96 | } 97 | 98 | TEST_CASE(vector_clear) 99 | { 100 | Std::Vector vec; 101 | 102 | vec.append({}); 103 | vec.append({}); 104 | vec.append({}); 105 | 106 | ASSERT(vec.size() == 3); 107 | usize capacity = vec.capacity(); 108 | 109 | Tests::Tracker::clear(); 110 | 111 | vec.clear(); 112 | 113 | Tests::Tracker::assert(0, 0, 0, 3); 114 | 115 | ASSERT(vec.size() == 0); 116 | ASSERT(vec.capacity() == capacity); 117 | } 118 | 119 | TEST_CASE(vector_exceed_inline_capacity) 120 | { 121 | Std::Vector vec; 122 | 123 | for (usize i = 0; i < 130; ++i) 124 | vec.append(i % 13); 125 | 126 | ASSERT(vec.size() == 130); 127 | 128 | for (usize i = 0; i < 130; ++i) 129 | ASSERT(vec.data()[i] == i % 13); 130 | } 131 | 132 | TEST_MAIN(); 133 | -------------------------------------------------------------------------------- /Tests/TestSuite.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | namespace Tests 4 | { 5 | size_t Tracker::m_create_count = 0; 6 | size_t Tracker::m_copy_count = 0; 7 | size_t Tracker::m_move_count = 0; 8 | size_t Tracker::m_destroy_count = 0; 9 | } 10 | -------------------------------------------------------------------------------- /Tools/.gitignore: -------------------------------------------------------------------------------- 1 | /build/ 2 | /Build/ 3 | -------------------------------------------------------------------------------- /Tools/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "C_Cpp.autoAddFileAssociations": false, 3 | } -------------------------------------------------------------------------------- /Tools/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.19.5) 2 | project(Tools CXX) 3 | 4 | find_package(fmt) 5 | 6 | add_library(project_options INTERFACE) 7 | target_compile_features(project_options INTERFACE cxx_std_20) 8 | target_compile_options(project_options INTERFACE -fdiagnostics-color=always -g) 9 | target_include_directories(project_options INTERFACE ${CMAKE_SOURCE_DIR} ${CMAKE_SOURCE_DIR}/..) 10 | target_compile_definitions(project_options INTERFACE HOST) 11 | 12 | file(GLOB LibElf_SOURCES LibElf/*.cpp) 13 | add_library(LibElf ${LibElf_SOURCES}) 14 | target_link_libraries(LibElf project_options fmt::fmt) 15 | 16 | file(GLOB ElfEmbed_SOURCES *.cpp) 17 | add_executable(ElfEmbed ${ElfEmbed_SOURCES}) 18 | target_link_libraries(ElfEmbed project_options LibElf bsd) 19 | -------------------------------------------------------------------------------- /Tools/ElfEmbed.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | #include "FileSystem.hpp" 15 | 16 | static void write_output_file(std::filesystem::path path, Elf::MemoryStream& stream) 17 | { 18 | fmt::print("Writing output file {}\n", path.string()); 19 | 20 | int fd = creat(path.c_str(), S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); 21 | assert(fd >= 0); 22 | 23 | stream.copy_to_raw_fd(fd); 24 | 25 | int retval = close(fd); 26 | assert(retval == 0); 27 | } 28 | 29 | int main(int argc, char **argv) 30 | { 31 | // FIXME: Parse command line arguments 32 | 33 | Elf::Generator generator; 34 | 35 | FileSystem fs { generator }; 36 | 37 | std::map bin_files; 38 | bin_files["Shell.elf"] = fs.add_host_file("Shell.elf", Kernel::ModeFlags::Regular | Kernel::ModeFlags::DefaultExecutablePermissions); 39 | bin_files["Example.elf"] = fs.add_host_file("Example.elf", Kernel::ModeFlags::Regular | Kernel::ModeFlags::DefaultExecutablePermissions); 40 | bin_files["Editor.elf"] = fs.add_host_file("Editor.elf", Kernel::ModeFlags::Regular | Kernel::ModeFlags::DefaultExecutablePermissions); 41 | 42 | fs.add_root_directory(bin_files); 43 | 44 | fs.finalize(); 45 | 46 | auto stream = generator.finalize(); 47 | write_output_file("FileSystem.elf", stream); 48 | } 49 | -------------------------------------------------------------------------------- /Tools/FileSystem.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | #include 11 | 12 | class FileSystem { 13 | public: 14 | explicit FileSystem(Elf::Generator& generator); 15 | ~FileSystem(); 16 | 17 | uint32_t add_file( 18 | Elf::MemoryStream&, 19 | Kernel::ModeFlags mode = Kernel::ModeFlags::Regular | Kernel::ModeFlags::DefaultPermissions, 20 | uint32_t inode_number = 0, 21 | size_t *load_offset = nullptr); 22 | 23 | uint32_t add_host_file( 24 | std::string_view path, 25 | Kernel::ModeFlags mode = Kernel::ModeFlags::Regular | Kernel::ModeFlags::DefaultPermissions); 26 | 27 | uint32_t add_directory(std::map& files, uint32_t inode_number = 0); 28 | uint32_t add_root_directory(std::map& files); 29 | 30 | void finalize(); 31 | 32 | private: 33 | Elf::Generator& m_generator; 34 | bool m_finalized = false; 35 | 36 | std::optional m_data_relocs; 37 | std::optional m_data_index; 38 | std::optional m_base_symbol; 39 | 40 | Elf::MemoryStream m_data_stream; 41 | 42 | std::map m_inode_to_offset; 43 | 44 | size_t m_next_inode = 3; 45 | }; 46 | -------------------------------------------------------------------------------- /Tools/LibElf/Generator.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | #include "MemoryStream.hpp" 9 | #include "StringTable.hpp" 10 | #include "SymbolTable.hpp" 11 | 12 | namespace Elf 13 | { 14 | class Generator { 15 | public: 16 | Generator(); 17 | ~Generator(); 18 | 19 | Elf32_Shdr& section(size_t index) { return m_sections[index]; } 20 | SymbolTable& symtab() { return m_symtab.value(); } 21 | 22 | size_t append_section(std::string_view name, MemoryStream& stream, Elf32_Word type, Elf32_Word flags); 23 | size_t create_section(std::string_view name, Elf32_Word type, Elf32_Word flags); 24 | void write_section(size_t section_index, MemoryStream&); 25 | 26 | MemoryStream finalize(); 27 | 28 | private: 29 | void create_undefined_section(); 30 | 31 | void encode_sections(size_t& section_offset, size_t& shstrtab_section_index); 32 | void encode_header(size_t section_offset, size_t shstrtab_section_index); 33 | 34 | MemoryStream m_stream; 35 | 36 | bool m_finalized = false; 37 | std::vector m_sections; 38 | 39 | std::optional m_shstrtab; 40 | std::optional m_symtab; 41 | }; 42 | } 43 | -------------------------------------------------------------------------------- /Tools/LibElf/MemoryStream.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "MemoryStream.hpp" 10 | 11 | namespace Elf 12 | { 13 | std::span mmap_file(std::filesystem::path path) 14 | { 15 | int fd = open(path.c_str(), O_RDONLY); 16 | assert(fd >= 0); 17 | 18 | struct stat statbuf; 19 | 20 | int retval = fstat(fd, &statbuf); 21 | assert(retval == 0); 22 | 23 | void *pointer = mmap(nullptr, statbuf.st_size, PROT_READ, MAP_PRIVATE, fd, 0); 24 | assert(pointer != MAP_FAILED); 25 | 26 | return { (const uint8_t*)pointer, (size_t)statbuf.st_size }; 27 | } 28 | MemoryStream::MemoryStream() 29 | { 30 | int fd = memfd_create("MemoryStream", 0); 31 | assert(fd >= 0); 32 | 33 | m_file = fdopen(fd, "w"); 34 | assert(m_file != nullptr); 35 | } 36 | MemoryStream::~MemoryStream() 37 | { 38 | if (m_file) 39 | fclose(m_file); 40 | } 41 | MemoryStream::MemoryStream(MemoryStream&& other) 42 | { 43 | m_file = std::exchange(other.m_file, nullptr); 44 | } 45 | size_t MemoryStream::write_bytes(std::span bytes) 46 | { 47 | size_t offset = this->offset(); 48 | 49 | size_t retval = fwrite(bytes.data(), 1, bytes.size(), m_file); 50 | assert(retval == bytes.size_bytes()); 51 | 52 | return offset; 53 | } 54 | size_t MemoryStream::write_bytes(MemoryStream& other) 55 | { 56 | size_t base_offset = this->offset(); 57 | 58 | size_t offset = other.offset(); 59 | 60 | other.seek(0); 61 | 62 | char buffer[0x1000]; 63 | while (!feof(other.m_file)) { 64 | size_t nread = fread(buffer, 1, sizeof(buffer), other.m_file); 65 | size_t nwritten = fwrite(buffer, 1, nread, m_file); 66 | assert(nread == nwritten); 67 | } 68 | 69 | other.seek(offset); 70 | 71 | return base_offset; 72 | } 73 | size_t MemoryStream::offset() 74 | { 75 | long offset = ftell(m_file); 76 | assert(offset >= 0); 77 | return (size_t)offset; 78 | } 79 | size_t MemoryStream::size() 80 | { 81 | int retval; 82 | 83 | size_t offset = this->offset(); 84 | 85 | retval = fseek(m_file, 0, SEEK_END); 86 | assert(retval == 0); 87 | 88 | size_t size = this->offset(); 89 | 90 | retval = fseek(m_file, offset, SEEK_SET); 91 | assert(retval == 0); 92 | 93 | return size; 94 | } 95 | void MemoryStream::seek(size_t offset) 96 | { 97 | int retval = fseek(m_file, offset, SEEK_SET); 98 | assert(retval == 0); 99 | } 100 | void MemoryStream::seek_relative(ssize_t offset) 101 | { 102 | int retval = fseek(m_file, offset, SEEK_CUR); 103 | assert(retval == 0); 104 | } 105 | void MemoryStream::copy_to_raw_fd(int fd) 106 | { 107 | off_t input_offset = 0; 108 | 109 | ssize_t retval = copy_file_range(fileno(m_file), &input_offset, fd, nullptr, size(), 0); 110 | assert(retval == size()); 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /Tools/LibElf/MemoryStream.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | namespace Elf 9 | { 10 | std::span mmap_file(std::filesystem::path); 11 | 12 | class MemoryStream { 13 | public: 14 | MemoryStream(); 15 | ~MemoryStream(); 16 | MemoryStream(MemoryStream&&); 17 | 18 | size_t write_bytes(std::span); 19 | size_t write_bytes(MemoryStream&); 20 | 21 | template 22 | size_t write_object(const T& value) 23 | { 24 | return write_bytes({ (const uint8_t*)&value, sizeof(value) }); 25 | } 26 | 27 | size_t offset(); 28 | size_t size(); 29 | 30 | void seek(size_t); 31 | void seek_relative(ssize_t); 32 | 33 | void copy_to_raw_fd(int fd); 34 | 35 | private: 36 | FILE *m_file; 37 | }; 38 | } 39 | -------------------------------------------------------------------------------- /Tools/LibElf/RelocationTable.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | #include "Generator.hpp" 6 | #include "RelocationTable.hpp" 7 | 8 | namespace Elf 9 | { 10 | RelocationTable::RelocationTable(Generator& generator, std::string_view name_suffix, size_t symtab_index, size_t target_index) 11 | : m_generator(generator) 12 | , m_name_suffix(name_suffix) 13 | , m_symtab_index(symtab_index) 14 | , m_target_index(target_index) 15 | { 16 | m_rel_index = m_generator.create_section(fmt::format(".rel{}", m_name_suffix), SHT_REL, 0); 17 | auto& rel_section = m_generator.section(*m_rel_index); 18 | rel_section.sh_entsize = sizeof(Elf32_Rel); 19 | rel_section.sh_link = m_symtab_index; 20 | rel_section.sh_info = m_target_index; 21 | 22 | m_rela_index = m_generator.create_section(fmt::format(".rela{}", m_name_suffix), SHT_RELA, 0); 23 | auto& rela_section = generator.section(*m_rela_index); 24 | rela_section.sh_entsize = sizeof(Elf32_Rela); 25 | rela_section.sh_link = m_symtab_index; 26 | rela_section.sh_info = m_target_index; 27 | } 28 | RelocationTable::~RelocationTable() 29 | { 30 | assert(m_finalized); 31 | } 32 | void RelocationTable::add_entry(Elf32_Rel relocation) 33 | { 34 | m_rel_stream.write_object(relocation); 35 | } 36 | void RelocationTable::add_entry(Elf32_Rela relocation) 37 | { 38 | m_rela_stream.write_object(relocation); 39 | } 40 | void RelocationTable::finalize() 41 | { 42 | assert(!m_finalized); 43 | m_finalized = true; 44 | 45 | m_generator.write_section(m_rel_index.value(), m_rel_stream); 46 | m_generator.write_section(m_rela_index.value(), m_rela_stream); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Tools/LibElf/RelocationTable.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | #include "MemoryStream.hpp" 9 | 10 | namespace Elf 11 | { 12 | class Generator; 13 | 14 | class RelocationTable { 15 | public: 16 | RelocationTable(Generator&, std::string_view name_suffix, size_t symtab_index, size_t target_index); 17 | ~RelocationTable(); 18 | 19 | void add_entry(Elf32_Rel); 20 | void add_entry(Elf32_Rela); 21 | 22 | void finalize(); 23 | 24 | size_t symtab_index() { return m_symtab_index; } 25 | size_t target_index() { return m_target_index; } 26 | size_t rel_index() { return m_rel_index.value(); } 27 | size_t rela_index() { return m_rela_index.value(); } 28 | 29 | private: 30 | Generator& m_generator; 31 | bool m_finalized = false; 32 | 33 | MemoryStream m_rel_stream; 34 | MemoryStream m_rela_stream; 35 | 36 | std::string_view m_name_suffix; 37 | 38 | size_t m_symtab_index; 39 | size_t m_target_index; 40 | 41 | std::optional m_rel_index; 42 | std::optional m_rela_index; 43 | }; 44 | } 45 | -------------------------------------------------------------------------------- /Tools/LibElf/StringTable.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "StringTable.hpp" 5 | #include "Generator.hpp" 6 | 7 | namespace Elf 8 | { 9 | StringTable::StringTable(Generator& generator, std::string_view name) 10 | : m_generator(generator) 11 | { 12 | create_undefined_entry(); 13 | 14 | m_strtab_index = m_generator.create_section(name, SHT_STRTAB, 0); 15 | } 16 | StringTable::~StringTable() 17 | { 18 | assert(m_finalized); 19 | } 20 | void StringTable::create_undefined_entry() 21 | { 22 | m_strtab_stream.write_object(0); 23 | } 24 | size_t StringTable::add_entry(std::string_view name) 25 | { 26 | size_t offset = m_strtab_stream.write_bytes({ (const uint8_t*)name.data(), name.size() }); 27 | m_strtab_stream.write_object(0); 28 | return offset; 29 | } 30 | void StringTable::finalize() 31 | { 32 | assert(!m_finalized); 33 | m_finalized = true; 34 | 35 | m_generator.write_section(m_strtab_index.value(), m_strtab_stream); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Tools/LibElf/StringTable.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "MemoryStream.hpp" 7 | 8 | namespace Elf 9 | { 10 | class Generator; 11 | 12 | class StringTable { 13 | public: 14 | explicit StringTable(Generator&, std::string_view name); 15 | ~StringTable(); 16 | 17 | size_t add_entry(std::string_view); 18 | 19 | void finalize(); 20 | 21 | size_t strtab_index() const { return m_strtab_index.value(); } 22 | 23 | private: 24 | void create_undefined_entry(); 25 | 26 | Generator& m_generator; 27 | bool m_finalized = false; 28 | 29 | MemoryStream m_strtab_stream; 30 | 31 | std::optional m_strtab_index; 32 | }; 33 | } 34 | -------------------------------------------------------------------------------- /Tools/LibElf/SymbolTable.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | #include "SymbolTable.hpp" 6 | #include "Generator.hpp" 7 | 8 | namespace Elf 9 | { 10 | SymbolTable::SymbolTable(Generator& generator, std::string_view name_suffix) 11 | : m_generator(generator) 12 | , m_name_suffix(name_suffix) 13 | , m_string_table(generator, fmt::format(".strtab{}", name_suffix)) 14 | { 15 | create_undefined_symbol(); 16 | 17 | m_symtab_index = generator.create_section(fmt::format(".symtab{}", m_name_suffix), SHT_SYMTAB, 0); 18 | auto& symtab_section = m_generator.section(*m_symtab_index); 19 | symtab_section.sh_entsize = sizeof(Elf32_Sym); 20 | symtab_section.sh_link = m_string_table.strtab_index(); 21 | } 22 | SymbolTable::~SymbolTable() 23 | { 24 | assert(m_finalized); 25 | } 26 | void SymbolTable::create_undefined_symbol() 27 | { 28 | Elf32_Sym undefined_symbol; 29 | undefined_symbol.st_info = 0; 30 | undefined_symbol.st_other = 0; 31 | undefined_symbol.st_shndx = SHN_UNDEF; 32 | undefined_symbol.st_size = 0; 33 | undefined_symbol.st_value = 0; 34 | undefined_symbol.st_name = 0; 35 | m_symtab_stream.write_object(undefined_symbol); 36 | 37 | ++m_next_index; 38 | } 39 | size_t SymbolTable::add_symbol(std::string_view name, Elf32_Sym symbol) 40 | { 41 | // We don't support local symbols. 42 | assert(symbol.st_info & ELF32_ST_BIND(0xf) == ELF32_ST_BIND(STB_GLOBAL)); 43 | 44 | symbol.st_name = m_string_table.add_entry(name); 45 | m_symtab_stream.write_object(symbol); 46 | return m_next_index++; 47 | } 48 | size_t SymbolTable::add_undefined_symbol(std::string_view name, Elf32_Sym symbol) 49 | { 50 | // We don't support local symbols. 51 | assert(symbol.st_info & ELF32_ST_BIND(0xf) == ELF32_ST_BIND(STB_GLOBAL)); 52 | 53 | symbol.st_shndx = 0; 54 | symbol.st_name = m_string_table.add_entry(name); 55 | m_symtab_stream.write_object(symbol); 56 | return m_next_index++; 57 | } 58 | void SymbolTable::finalize() 59 | { 60 | assert(!m_finalized); 61 | m_finalized = true; 62 | 63 | m_string_table.finalize(); 64 | 65 | m_generator.write_section(*m_symtab_index, m_symtab_stream); 66 | auto& symtab_section = m_generator.section(m_symtab_index.value()); 67 | symtab_section.sh_info = 1; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /Tools/LibElf/SymbolTable.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | #include "StringTable.hpp" 9 | 10 | namespace Elf 11 | { 12 | class Generator; 13 | 14 | class SymbolTable { 15 | public: 16 | SymbolTable(Generator&, std::string_view name_suffix); 17 | ~SymbolTable(); 18 | 19 | size_t add_symbol(std::string_view name, Elf32_Sym); 20 | size_t add_undefined_symbol(std::string_view name, Elf32_Sym); 21 | 22 | void finalize(); 23 | 24 | size_t symtab_index() { return m_symtab_index.value(); } 25 | 26 | private: 27 | void create_undefined_symbol(); 28 | 29 | Generator& m_generator; 30 | bool m_finalized = false; 31 | 32 | StringTable m_string_table; 33 | MemoryStream m_symtab_stream; 34 | 35 | std::string_view m_name_suffix; 36 | size_t m_next_index = 0; 37 | 38 | std::optional m_symtab_index; 39 | }; 40 | } 41 | -------------------------------------------------------------------------------- /Userland/.gitignore: -------------------------------------------------------------------------------- 1 | /Shell 2 | /Example 3 | -------------------------------------------------------------------------------- /Userland/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | file(GLOB_RECURSE LIBC_SOURCES CONFIGURE_DEPENDS LibC/*.c LibC/*.S) 2 | file(GLOB_RECURSE Example_SOURCES CONFIGURE_depends LibC/*.c LibC/*.S) 3 | 4 | function(userland_executable name_) 5 | add_custom_target(${name_}.1.elf ALL 6 | COMMAND clang --target=arm-none-eabi -mcpu=cortex-m0plus 7 | -std=gnu11 -Og -g -static -nostdlib -fcolor-diagnostics 8 | -fropi -frwpi 9 | -DUSERLAND 10 | -I ${CMAKE_CURRENT_SOURCE_DIR}/LibC 11 | -I ${CMAKE_SOURCE_DIR} 12 | -T ${CMAKE_CURRENT_SOURCE_DIR}/Userland.x 13 | -Xlinker --nmagic 14 | --sysroot=/usr/local/arm-none-eabi 15 | ${LIBC_SOURCES} 16 | ${CMAKE_CURRENT_SOURCE_DIR}/${name_}.c 17 | -o ${CMAKE_CURRENT_BINARY_DIR}/${name_}.1.elf) 18 | 19 | add_custom_target(${name_}.elf ALL 20 | COMMAND arm-none-eabi-objcopy 21 | --strip-unneeded 22 | ${CMAKE_CURRENT_BINARY_DIR}/${name_}.1.elf 23 | ${CMAKE_CURRENT_BINARY_DIR}/${name_}.elf 24 | DEPENDS ${name_}.1.elf) 25 | 26 | list(APPEND USERLAND_EXECUTABLES ${name_}.elf) 27 | endfunction() 28 | 29 | userland_executable(Shell) 30 | userland_executable(Example) 31 | userland_executable(Editor) 32 | 33 | add_custom_target(FileSystem.elf ALL 34 | COMMAND ${ELF_EMBED_EXECUTABLE} 35 | DEPENDS ${USERLAND_EXECUTABLES} ElfEmbed) 36 | 37 | add_library(LibEmbeddedFiles OBJECT IMPORTED GLOBAL) 38 | set_target_properties(LibEmbeddedFiles 39 | PROPERTIES 40 | IMPORTED_OBJECTS ${CMAKE_CURRENT_BINARY_DIR}/FileSystem.elf) 41 | add_dependencies(LibEmbeddedFiles FileSystem.elf) 42 | -------------------------------------------------------------------------------- /Userland/Example.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | int main(int argc, char **argv, char **envp) 6 | { 7 | printf("This output is created by '/bin/Example.elf'\n"); 8 | 9 | char *pwd = get_current_dir_name(); 10 | printf("We are currently executing in '%s'\n", pwd); 11 | free(pwd); 12 | 13 | printf("Got argc=%i argv=%p envp=%p\n", argc, argv, envp); 14 | 15 | printf("Arguments:\n"); 16 | while (*argv != NULL) 17 | printf(" '%s'\n", *argv++); 18 | 19 | printf("Environment:\n"); 20 | while (*envp != NULL) 21 | printf(" '%s'\n", *envp++); 22 | } 23 | -------------------------------------------------------------------------------- /Userland/LibC/assert.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | void breakpoint(const char *filename, size_t line) 5 | { 6 | // The filename and line can be obtained from the backtrace, provided, 7 | // that the compiler did not optimize them away 8 | volatile const char *filename_ = filename; 9 | volatile size_t line_ = line; 10 | 11 | const char *message = "USERLAND ASSERTION FAILED\n"; 12 | sys$write(STDOUT_FILENO, message, __builtin_strlen(message)); 13 | 14 | asm volatile("bkpt #0"); 15 | } 16 | -------------------------------------------------------------------------------- /Userland/LibC/assert.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | __attribute__((noinline)) 6 | void breakpoint(const char *filename, size_t line); 7 | 8 | #define assert(condition) ((condition) ? (void)0 : breakpoint(__FILE__, __LINE__)) 9 | -------------------------------------------------------------------------------- /Userland/LibC/ctype.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int isdigit(int ch) 4 | { 5 | return ch >= '0' && ch <= '9'; 6 | } 7 | -------------------------------------------------------------------------------- /Userland/LibC/ctype.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | int isdigit(int ch); 4 | -------------------------------------------------------------------------------- /Userland/LibC/dirent.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | DIR* opendir(const char *path) 10 | { 11 | int fd = sys$open(path, O_DIRECTORY, 0); 12 | 13 | if (fd < 0) { 14 | errno = -fd; 15 | return NULL; 16 | } 17 | 18 | DIR *dirp = malloc(sizeof(DIR)); 19 | dirp->fd = fd; 20 | 21 | return dirp; 22 | } 23 | 24 | struct dirent* readdir(DIR *dirp) 25 | { 26 | ssize_t retval = read(dirp->fd, &dirp->entry, sizeof(struct dirent)); 27 | 28 | if (retval < 0) { 29 | errno = -retval; 30 | return NULL; 31 | } 32 | 33 | if (retval == 0) 34 | return NULL; 35 | 36 | assert(retval == sizeof(struct dirent)); 37 | 38 | return &dirp->entry; 39 | } 40 | 41 | int closedir(DIR *dirp) 42 | { 43 | int retval = close(dirp->fd); 44 | libc_check_errno(retval); 45 | return 0; 46 | } 47 | -------------------------------------------------------------------------------- /Userland/LibC/dirent.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | typedef struct { 7 | int fd; 8 | 9 | // We return a pointer to this in each readdir call 10 | struct dirent entry; 11 | } DIR; 12 | 13 | DIR* opendir(const char *path); 14 | struct dirent* readdir(DIR *dirp); 15 | int closedir(DIR *dirp); 16 | -------------------------------------------------------------------------------- /Userland/LibC/errno.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | int errno; 10 | 11 | // Remember to update the kernel aswell. 12 | const char *const strerror_lookup[] = { 13 | "Success", 14 | [ENOTDIR] = "Not a directory", 15 | [EINTR] = "Interrupted system call", 16 | [ERANGE] = "Numerical result out of range", 17 | [ENOENT] = "No such file or directory", 18 | [EACCES] = "Permission denied", 19 | [EISDIR] = "Is a directory", 20 | }; 21 | 22 | uint32_t _pc_base(); 23 | 24 | char* strerror(int error) 25 | { 26 | assert(error >= 0 && error < EMAX); 27 | 28 | // FIXME: The compiler "forgets" to add the program counter when computing the 29 | // address. I was not able to reproduce this with a smaller program, it 30 | // appears, the environment influences this hick-up 31 | const char *error_pointer_thingy = strerror_lookup[error] + _pc_base(); 32 | 33 | return (char*)strdup(error_pointer_thingy); 34 | } 35 | -------------------------------------------------------------------------------- /Userland/LibC/errno.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | extern int errno; 4 | 5 | char* strerror(int error); 6 | 7 | #define libc_check_errno(variable) \ 8 | do { \ 9 | if (variable < 0) { \ 10 | errno = -variable; \ 11 | return -1; \ 12 | } \ 13 | } while(0) 14 | -------------------------------------------------------------------------------- /Userland/LibC/fcntl.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | int close(int fd) 8 | { 9 | int retval = sys$close(fd); 10 | libc_check_errno(retval); 11 | return 0; 12 | } 13 | 14 | int open(const char *path, int flags, ...) 15 | { 16 | if ((flags & O_CREAT)) 17 | { 18 | va_list ap; 19 | 20 | va_start(ap, flags); 21 | int mode = va_arg(ap, int); 22 | va_end(ap); 23 | 24 | int retval = sys$open(path, flags, mode); 25 | libc_check_errno(retval); 26 | return retval; 27 | } 28 | 29 | int retval = sys$open(path, flags, 0); 30 | libc_check_errno(retval); 31 | return retval; 32 | } 33 | 34 | ssize_t read(int fd, void *buffer, size_t count) 35 | { 36 | ssize_t retval = sys$read(fd, buffer, count); 37 | libc_check_errno(retval); 38 | return retval; 39 | } 40 | 41 | ssize_t write(int fd, const void *buffer, size_t count) 42 | { 43 | ssize_t retval = sys$write(fd, buffer, count); 44 | libc_check_errno(retval); 45 | return retval; 46 | } 47 | -------------------------------------------------------------------------------- /Userland/LibC/fcntl.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | int close(int fd); 8 | 9 | int creat(const char *path, mode_t mode); 10 | 11 | int open(const char *path, int flags, ...); 12 | 13 | ssize_t read(int fd, void *buffer, size_t count); 14 | ssize_t write(int fd, const void *buffer, size_t count); 15 | -------------------------------------------------------------------------------- /Userland/LibC/malloc.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | extern char __heap_start__[]; 7 | extern char __heap_end__[]; 8 | 9 | // FIXME: Implement a proper malloc. 10 | 11 | void free(void *pointer) 12 | { 13 | } 14 | 15 | static char *heap; 16 | 17 | static size_t round_to_word(size_t size) 18 | { 19 | if (size % 4 != 0) 20 | size = size + 4 - size % 4; 21 | return size; 22 | } 23 | 24 | void* malloc(size_t size) 25 | { 26 | if (heap == NULL) 27 | heap = __heap_start__; 28 | 29 | size = round_to_word(size); 30 | 31 | heap += size; 32 | 33 | char *pointer = heap - size; 34 | assert(pointer <= __heap_end__); 35 | return pointer; 36 | } 37 | 38 | void* realloc(void *pointer, size_t size) 39 | { 40 | return pointer; 41 | } 42 | -------------------------------------------------------------------------------- /Userland/LibC/malloc.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | void free(void* pointer); 6 | void* malloc(size_t size); 7 | void* realloc(void *pointer, size_t size); 8 | -------------------------------------------------------------------------------- /Userland/LibC/readline/readline.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | char* readline(const char *prompt) 10 | { 11 | printf("%s", prompt); 12 | 13 | size_t buffer_size = 256; 14 | char *buffer = malloc(buffer_size); 15 | 16 | size_t index = 0; 17 | while (index < buffer_size) 18 | { 19 | int retval = sys$read(STDIN_FILENO, &buffer[index], 1); 20 | assert(retval == 1); 21 | 22 | char ch = buffer[index]; 23 | 24 | if (ch == 0x7f) { 25 | if (index == 0) 26 | continue; 27 | 28 | printf("\e[1D \e[1D"); 29 | buffer[--index] = 0; 30 | continue; 31 | } 32 | 33 | ++index; 34 | putchar(ch); 35 | 36 | if (ch == '\n') { 37 | assert(index < buffer_size); 38 | assert(index > 0); 39 | buffer[index - 1] = 0; 40 | 41 | return buffer; 42 | } 43 | } 44 | 45 | abort(); 46 | } 47 | -------------------------------------------------------------------------------- /Userland/LibC/readline/readline.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | char* readline(const char *prompt); 4 | -------------------------------------------------------------------------------- /Userland/LibC/spawn.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | int posix_spawn( 6 | pid_t *pid, 7 | const char *pathname, 8 | const posix_spawn_file_actions_t *file_actions, 9 | const posix_spawnattr_t *attrp, 10 | char **argv, 11 | char **envp) 12 | { 13 | int retval = sys$posix_spawn(pid, pathname, file_actions, attrp, argv, envp); 14 | libc_check_errno(retval); 15 | return 0; 16 | } 17 | -------------------------------------------------------------------------------- /Userland/LibC/spawn.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | int posix_spawn( 7 | pid_t *pid, 8 | const char *pathname, 9 | const posix_spawn_file_actions_t *file_actions, 10 | const posix_spawnattr_t *attrp, 11 | char **argv, 12 | char **envp); 13 | -------------------------------------------------------------------------------- /Userland/LibC/stdarg.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | typedef __builtin_va_list va_list; 4 | 5 | #define va_start(ap, last) __builtin_va_start(ap, last) 6 | #define va_arg(ap, type) __builtin_va_arg(ap, type) 7 | #define va_end(ap) __builtin_va_end(ap) 8 | -------------------------------------------------------------------------------- /Userland/LibC/stddef.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define NULL ((void*)0) 4 | 5 | typedef unsigned int size_t; 6 | typedef int ssize_t; 7 | -------------------------------------------------------------------------------- /Userland/LibC/stdint.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | typedef unsigned int uint32_t; 4 | typedef int int32_t; 5 | typedef unsigned short uint16_t; 6 | typedef unsigned int size_t; 7 | typedef unsigned char uint8_t; 8 | typedef unsigned int uintptr_t; 9 | -------------------------------------------------------------------------------- /Userland/LibC/stdio.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | int printf(const char *format, ...); 4 | int putchar(int ch); 5 | int puts(const char *str); 6 | -------------------------------------------------------------------------------- /Userland/LibC/stdlib.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | _Noreturn void abort(void) 8 | { 9 | for(;;) 10 | asm volatile("bkpt #0"); 11 | } 12 | 13 | _Noreturn void exit(int status) 14 | { 15 | sys$exit(status); 16 | printf("How did we get here?\n"); 17 | } 18 | 19 | static char __env_PATH[] = "/bin"; 20 | 21 | char* getenv(const char *name) 22 | { 23 | assert(strcmp(name, "PATH") == 0); 24 | return __env_PATH; 25 | } 26 | -------------------------------------------------------------------------------- /Userland/LibC/stdlib.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | _Noreturn void abort(void); 6 | _Noreturn void exit(int status); 7 | 8 | char* getenv(const char *name); 9 | -------------------------------------------------------------------------------- /Userland/LibC/string.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | char* strchr(const char *str, int ch) 7 | { 8 | for (;;) { 9 | if (*str == 0) 10 | return NULL; 11 | 12 | if (*str == ch) 13 | return (char*)str; 14 | 15 | ++str; 16 | } 17 | } 18 | 19 | char* strtok_r(char *str, const char *delim, char **saveptr) 20 | { 21 | if (str == NULL) { 22 | // Skip all leading delimiters. 23 | while (strchr(delim, **saveptr)) 24 | ++(*saveptr); 25 | 26 | char *begin = *saveptr; 27 | 28 | // Find the next delimiter. 29 | for(;;) { 30 | if (**saveptr == 0) { 31 | if (begin == *saveptr) 32 | return NULL; 33 | 34 | return begin; 35 | } 36 | 37 | if (strchr(delim, **saveptr)) { 38 | **saveptr = 0; 39 | ++(*saveptr); 40 | 41 | return begin; 42 | } 43 | 44 | ++(*saveptr); 45 | } 46 | 47 | abort(); 48 | } else { 49 | *saveptr = str; 50 | return strtok_r(NULL, delim, saveptr); 51 | } 52 | } 53 | 54 | int strcmp(const char *lhs, const char *rhs) 55 | { 56 | while(*lhs && *lhs == *rhs) { 57 | ++lhs; 58 | ++rhs; 59 | } 60 | 61 | return *lhs - *rhs; 62 | } 63 | 64 | void* memset(void *dest, int ch, size_t count) 65 | { 66 | __aeabi_memset(dest, count, ch); 67 | return dest; 68 | } 69 | 70 | void* memcpy(void *dest, const void *src, size_t count) 71 | { 72 | __aeabi_memcpy(dest, src, count); 73 | return dest; 74 | } 75 | 76 | void* memmove(void *dest, const void *src, size_t count) 77 | { 78 | __aeabi_memmove(dest, src, count); 79 | return dest; 80 | } 81 | 82 | size_t strlen(const char *str) 83 | { 84 | size_t length = 0; 85 | while (*str++) 86 | ++length; 87 | 88 | return length; 89 | } 90 | 91 | char* strdup(const char *str) 92 | { 93 | char *copy = malloc(strlen(str) + 1); 94 | return strcpy(copy, str); 95 | } 96 | 97 | char* strcpy(char *dest, const char *src) 98 | { 99 | return memcpy(dest, src, strlen(src) + 1); 100 | } 101 | 102 | char* strcat(char *dest, const char *src) 103 | { 104 | strcpy(dest + strlen(dest), src); 105 | return dest; 106 | } 107 | -------------------------------------------------------------------------------- /Userland/LibC/string.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | char* strtok_r(char *str, const char *delim, char **saveptr); 6 | int strcmp(const char *lhs, const char *rhs); 7 | void* memset(void *dest, int ch, size_t count); 8 | void* memcpy(void *dest, const void *src, size_t count); 9 | void* memmove(void *dest, const void *src, size_t count); 10 | char* strchr(const char *str, int ch); 11 | size_t strlen(const char *str); 12 | char* strdup(const char *str); 13 | char* strcpy(char *dest, const char *src); 14 | char* strcat(char *dest, const char *src); 15 | -------------------------------------------------------------------------------- /Userland/LibC/sys/abi.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #define rom_table_code(code1, code2) ((code2) << 8 | (code1)) 7 | 8 | #define ROM_MEMCPY 0 9 | #define ROM_MEMSET 1 10 | #define ROM_MAX 2 11 | 12 | static uint32_t rom_functions[] = { 13 | [ROM_MEMCPY] = rom_table_code('M', 'C'), 14 | [ROM_MEMSET] = rom_table_code('M', 'S'), 15 | }; 16 | 17 | void rom_functions_init() 18 | { 19 | typedef uint32_t (*fn_table_lookup)(uint16_t *table, uint32_t code); 20 | 21 | uint16_t *table_lookup_ptr = (uint16_t*)0x00000018; 22 | fn_table_lookup table_lookup = (fn_table_lookup)(uint32_t)*table_lookup_ptr; 23 | 24 | uint16_t *func_table_ptr = (uint16_t*)0x00000014; 25 | uint16_t *func_table = (uint16_t*)(uint32_t)*func_table_ptr; 26 | 27 | for (size_t i = 0; i < ROM_MAX; ++i) { 28 | uint32_t rom_function_code = rom_functions[i]; 29 | uint32_t value = rom_functions[i] = table_lookup(func_table, rom_functions[i]); 30 | if (value == 0) 31 | printf("Could not find ROM function %u (i=%zu)\n", rom_function_code, i); 32 | assert(value); 33 | } 34 | } 35 | 36 | void __aeabi_memcpy(void *dest, const void *src, size_t n) 37 | { 38 | typedef uint8_t* (*fn_memcpy)(void*, const void*, size_t); 39 | ((fn_memcpy)rom_functions[ROM_MEMCPY])(dest, src, n); 40 | } 41 | 42 | void __aeabi_memset(void *dest, size_t n, int c) 43 | { 44 | typedef uint8_t* (*fn_memset)(uint8_t *ptr, uint8_t c, uint32_t n); 45 | ((fn_memset)rom_functions[ROM_MEMSET])(dest, (uint8_t)c, n); 46 | } 47 | 48 | void __aeabi_memclr(void *dest, size_t n) 49 | { 50 | __aeabi_memset(dest, n, 0); 51 | } 52 | 53 | // FIXME: This is completely untested. 54 | 55 | static uint8_t unaligned_read_u8(uintptr_t source) 56 | { 57 | // Address of the word in which this byte is contained. 58 | uint32_t *source_aligned = (uint32_t*)(source - source % 4); 59 | 60 | // Read the entire word. 61 | uint32_t value = *source_aligned; 62 | 63 | // This is a little endian system. 64 | // If we think of the decimal representation, the first bytes we read are on the right. 65 | // We shift to the right by eight bits for each byte we want to skip. 66 | value = value >> (8 * (source % 4)); 67 | 68 | // The value is in the lowest byte, return that. 69 | return value & 0xff; 70 | } 71 | 72 | static void unaligned_write_u8(uintptr_t destination, uint8_t new_value) 73 | { 74 | // Address of the word in which this byte is contained. 75 | uint32_t *destination_aligned = (uint32_t*)(destination - destination % 4); 76 | 77 | // Read the entire word. 78 | uint32_t value = *destination_aligned; 79 | 80 | // Modify the byte we want to change. 81 | if (destination % 4 == 0) { 82 | value = value & 0xffffff00 | ((uint32_t)new_value << 0); 83 | } else if (destination % 4 == 1) { 84 | value = value & 0xffff00ff | ((uint32_t)new_value << 8); 85 | } else if (destination % 4 == 2) { 86 | value = value & 0xff00ffff | ((uint32_t)new_value << 16); 87 | } else if (destination % 4 == 3) { 88 | value = value & 0x00ffffff | ((uint32_t)new_value << 24); 89 | } 90 | 91 | // Write the entire word back. 92 | *destination_aligned = value; 93 | } 94 | 95 | void __aeabi_memmove(void *destination, void *source, size_t count) 96 | { 97 | // The danger is, that we overwride some of the source, before we copy it to the destination. 98 | 99 | uintptr_t destination_address = (uintptr_t)destination; 100 | uintptr_t source_address = (uintptr_t)source; 101 | 102 | if (destination_address <= source_address) { 103 | // We can safely copy from left-to-right. 104 | 105 | for (int i = 0; i < count; ++i) { 106 | unaligned_write_u8(destination_address + i, unaligned_read_u8(source_address + i)); 107 | } 108 | } else { 109 | // We can safely copy from right-to-left. 110 | 111 | for (int i = 0; i < count; ++i) { 112 | unaligned_write_u8(destination_address + count - i - 1, unaligned_read_u8(source_address + count - i - 1)); 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /Userland/LibC/sys/abi.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | void __aeabi_memcpy(void *dest, const void *src, size_t n); 6 | void __aeabi_memset(void *dest, size_t n, int c); 7 | void __aeabi_memclr(void *dest, size_t n); 8 | void __aeabi_memmove(void *dest, const void *src, size_t n); 9 | -------------------------------------------------------------------------------- /Userland/LibC/sys/crt0.S: -------------------------------------------------------------------------------- 1 | .syntax unified 2 | .cpu cortex-m0plus 3 | .thumb 4 | 5 | // void _init(void) 6 | .extern _init 7 | 8 | // void _fini(void) 9 | .extern _fini 10 | 11 | // [[noreturn]] 12 | // void _start(u32 stack_top) 13 | .global _start 14 | .thumb_func 15 | _start: 16 | push {r0, r1, r2} 17 | bl _init 18 | pop {r0, r1, r2} 19 | bl main 20 | 21 | push {r0} 22 | bl _fini 23 | pop {r0} 24 | 25 | bl exit 26 | 27 | // void _syscall(u32 syscall, u32 arg1, u32 arg2, u32 arg3) 28 | .global _syscall 29 | .thumb_func 30 | _syscall: 31 | svc #0 32 | bx lr 33 | 34 | // u32 _static_base() 35 | .global _static_base 36 | .thumb_func 37 | _static_base: 38 | mov r0, sb 39 | bx lr 40 | 41 | // u32 _pc_base() 42 | .global _pc_base 43 | .thumb_func 44 | _pc_base: 45 | mov r0, pc 46 | subs r0, r0, #3 47 | 48 | ldr r1, =_pc_base 49 | 50 | subs r0, r0, r1 51 | 52 | bx lr 53 | -------------------------------------------------------------------------------- /Userland/LibC/sys/crt0.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | extern uint8_t __bss_start__[]; 5 | extern uint8_t __bss_end__[]; 6 | 7 | void rom_functions_init(); 8 | 9 | void _init() 10 | { 11 | rom_functions_init(); 12 | 13 | memset(__bss_start__, 0, __bss_end__ - __bss_start__); 14 | 15 | // FIXME: Call preinit array 16 | 17 | // FIXME: Call init array 18 | } 19 | 20 | void _fini() 21 | { 22 | // FIXME: Call fini array 23 | } 24 | -------------------------------------------------------------------------------- /Userland/LibC/sys/stat.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | int fstat(int fd, struct stat *statbuf) 10 | { 11 | int retval = sys$fstat(fd, statbuf); 12 | libc_check_errno(retval); 13 | return 0; 14 | } 15 | 16 | int stat(const char *pathname, struct stat *statbuf) 17 | { 18 | int fd = sys$open(pathname, O_RDONLY, 0); 19 | 20 | if (fd < 0) { 21 | errno = ENOENT; 22 | return -1; 23 | } 24 | 25 | int retval = sys$fstat(fd, statbuf); 26 | 27 | sys$close(fd); 28 | 29 | libc_check_errno(retval); 30 | return 0; 31 | } 32 | -------------------------------------------------------------------------------- /Userland/LibC/sys/stat.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | int fstat(int fd, struct stat *statbuf); 6 | int stat(const char *pathname, struct stat *statbuf); 7 | -------------------------------------------------------------------------------- /Userland/LibC/sys/system.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | int32_t _syscall(uint32_t syscall, uint32_t arg1, uint32_t arg2, uint32_t arg3); 7 | #define syscall(syscall, arg1, arg2, arg3) _syscall((uint32_t)(syscall), (uint32_t)(arg1), (uint32_t)(arg2), (uint32_t)(arg3)) 8 | 9 | ssize_t sys$write(int fd, const void *buffer, size_t count) 10 | { 11 | return syscall(_SC_write, fd, buffer, count); 12 | } 13 | 14 | ssize_t sys$read(int fd, void *buffer, size_t count) 15 | { 16 | return syscall(_SC_read, fd, buffer, count); 17 | } 18 | 19 | int sys$open(const char *pathname, int flags, int mode) 20 | { 21 | return syscall(_SC_open, pathname, flags, mode); 22 | } 23 | 24 | int sys$close(int fd) 25 | { 26 | return syscall(_SC_close, fd, 0, 0); 27 | } 28 | 29 | int sys$fstat(int fd, struct stat *statbuf) 30 | { 31 | return syscall(_SC_fstat, fd, statbuf, 0); 32 | } 33 | 34 | int sys$wait(int *wstatus) 35 | { 36 | return syscall(_SC_wait, wstatus, 0, 0); 37 | } 38 | 39 | void sys$exit(int status) 40 | { 41 | syscall(_SC_exit, status, 0, 0); 42 | asm volatile("bkpt #0"); 43 | printf("sys$exit returned?\n"); 44 | abort(); 45 | } 46 | 47 | int sys$chdir(const char *pathname) 48 | { 49 | return syscall(_SC_chdir, pathname, 0, 0); 50 | } 51 | 52 | int sys$posix_spawn( 53 | pid_t *pid, 54 | const char *pathname, 55 | const posix_spawn_file_actions_t *file_actions, 56 | const posix_spawnattr_t *attrp, 57 | char **argv, 58 | char **envp) 59 | { 60 | struct extended_system_call_arguments extended_arguments; 61 | extended_arguments.arg3 = (uint32_t)file_actions; 62 | extended_arguments.arg4 = (uint32_t)attrp; 63 | extended_arguments.arg5 = (uint32_t)argv; 64 | extended_arguments.arg6 = (uint32_t)envp; 65 | 66 | return syscall(_SC_posix_spawn, pid, pathname, &extended_arguments); 67 | } 68 | 69 | int sys$get_working_directory(void *buffer, size_t *buffer_size) 70 | { 71 | return syscall(_SC_get_working_directory, buffer, buffer_size, 0); 72 | } 73 | -------------------------------------------------------------------------------- /Userland/LibC/sys/system.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | ssize_t sys$write(int fd, const void *buffer, size_t count); 10 | ssize_t sys$read(int fd, void *buffer, size_t count); 11 | int sys$open(const char *pathname, int flags, int mode); 12 | int sys$close(int fd); 13 | int sys$fstat(int fd, struct stat *statbuf); 14 | int sys$wait(int *wstatus); 15 | int sys$chdir(const char *pathname); 16 | int sys$posix_spawn( 17 | pid_t *pid, 18 | const char *pathname, 19 | const posix_spawn_file_actions_t *file_actions, 20 | const posix_spawnattr_t *attrp, 21 | char **argv, 22 | char **envp); 23 | int sys$get_working_directory(void *buffer, size_t *buffer_size); 24 | 25 | _Noreturn 26 | void sys$exit(int status); 27 | -------------------------------------------------------------------------------- /Userland/LibC/sys/types.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | typedef unsigned int size_t; 4 | typedef int ssize_t; 5 | typedef int pid_t; 6 | -------------------------------------------------------------------------------- /Userland/LibC/sys/wait.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | pid_t wait(int *status) 7 | { 8 | pid_t retval; 9 | 10 | while ((retval = sys$wait(status)) == -EINTR) 11 | ; 12 | 13 | libc_check_errno(retval); 14 | return 0; 15 | } 16 | -------------------------------------------------------------------------------- /Userland/LibC/sys/wait.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | pid_t wait(int *status); 6 | -------------------------------------------------------------------------------- /Userland/LibC/unistd.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | int chdir(const char *pathname) 12 | { 13 | int retval = sys$chdir(pathname); 14 | libc_check_errno(retval); 15 | return 0; 16 | } 17 | 18 | // FIXME: Do this properly 19 | int geteuid(void) 20 | { 21 | return 0; 22 | } 23 | int getegid(void) 24 | { 25 | return 0; 26 | } 27 | 28 | int access(const char *pathname, int mode) 29 | { 30 | assert(mode == X_OK); 31 | 32 | struct stat statbuf; 33 | int retval = stat(pathname, &statbuf); 34 | libc_check_errno(retval); 35 | 36 | if (statbuf.st_uid == geteuid() && (statbuf.st_mode & S_IXUSR)) 37 | return 0; 38 | if (statbuf.st_gid == getegid() && (statbuf.st_mode & S_IXGRP)) 39 | return 0; 40 | 41 | errno = EACCES; 42 | return -1; 43 | } 44 | 45 | char* get_current_dir_name(void) 46 | { 47 | int retval; 48 | 49 | size_t buffer_size = 0; 50 | retval = sys$get_working_directory(NULL, &buffer_size); 51 | assert(retval == -ERANGE); 52 | 53 | char *buffer = malloc(buffer_size); 54 | retval = sys$get_working_directory(buffer, &buffer_size); 55 | assert(retval == 0); 56 | 57 | return buffer; 58 | } 59 | -------------------------------------------------------------------------------- /Userland/LibC/unistd.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #define STDIN_FILENO 0 6 | #define STDOUT_FILENO 1 7 | 8 | #define X_OK 1 9 | 10 | int chdir(const char *pathname); 11 | 12 | int access(const char *pathname, int mode); 13 | 14 | int geteuid(void); 15 | int getegid(void); 16 | 17 | char* get_current_dir_name(void); 18 | -------------------------------------------------------------------------------- /Userland/Userland.x: -------------------------------------------------------------------------------- 1 | ENTRY(_start); 2 | 3 | /* FIXME: Keep debugging information! */ 4 | 5 | PHDRS { 6 | text PT_LOAD; 7 | data PT_LOAD; 8 | } 9 | 10 | SECTIONS 11 | { 12 | . = ALIGN(4); 13 | .text : { 14 | *(.init) 15 | *(*.text*) 16 | *(.fini) 17 | *(.rodata*) 18 | 19 | __preinit_array_start = .; 20 | KEEP(*(.preinit_array*)) 21 | __preinit_array_end = .; 22 | 23 | __init_array_start = .; 24 | KEEP(*(.init_array*)) 25 | __init_array_end = .; 26 | 27 | __fini_array_start = .; 28 | KEEP(*(.fini_array*)) 29 | __fini_array_end = .; 30 | } :text 31 | . = ALIGN(4); 32 | .data : { 33 | *(.data*) 34 | } :data 35 | . = ALIGN(4); 36 | .bss (NOLOAD) : { 37 | __bss_start__ = .; 38 | *(.bss*) 39 | __bss_end__ = .; 40 | } :data 41 | . = ALIGN(4); 42 | .heap (NOLOAD) : { 43 | __heap_start__ = .; 44 | . += 0x1000; 45 | __heap_end__ = .; 46 | } :data 47 | . = ALIGN(8); 48 | .stack (NOLOAD) : { 49 | . += 0x1100; 50 | } :data 51 | } 52 | -------------------------------------------------------------------------------- /tasks.py: -------------------------------------------------------------------------------- 1 | import invoke 2 | import os 3 | import tempfile 4 | 5 | @invoke.task 6 | def probe(c, debug=False): 7 | if debug: 8 | debug_flags = "--debug=3" 9 | else: 10 | debug_flags = "" 11 | 12 | c.sudo(f"openocd {debug_flags} -f interface/picoprobe.cfg -f target/rp2040.cfg", pty=True) 13 | 14 | @invoke.task 15 | def dbg(c, gdb="arm-none-eabi-gdb", port=3333): 16 | init_script = tempfile.NamedTemporaryFile(suffix=".gdb") 17 | 18 | # FIXME: This is really ugly. 19 | init_script.write(f"""\ 20 | target extended-remote localhost:{port} 21 | file Kernel.elf 22 | 23 | define dis_here 24 | x/20i ($pc -20) 25 | end 26 | 27 | define si_and_dis 28 | si 29 | dis_here 30 | end 31 | 32 | define rebuild 33 | shell ninja 34 | load 35 | monitor reset init 36 | end 37 | 38 | set confirm off 39 | 40 | set history save on 41 | set history size unlimited 42 | set history remove-duplicates 1 43 | """.encode()) 44 | init_script.flush() 45 | 46 | c.run(f"{gdb} -q -x {init_script.name}", pty=True) 47 | 48 | @invoke.task 49 | def tty(c): 50 | if not os.path.exists("/dev/ttyACM0"): 51 | print("Can not find serial device '/dev/ttyACM0'.") 52 | exit(1) 53 | 54 | c.sudo("stty -F /dev/ttyACM0 115200 igncr") 55 | c.sudo("tio /dev/ttyACM0", pty=True) 56 | 57 | @invoke.task 58 | def backup(c): 59 | c.run("~/dev/scripts/backup.rb --name 'pico-os' --url 'git@github.com:asynts/os' --upload 's3://backup.asynts.com/git/pico-os'") 60 | --------------------------------------------------------------------------------