diff --git a/.github/workflows/kanso-cd.yml b/.github/workflows/kanso-cd.yml index 940d863..8c82922 100644 --- a/.github/workflows/kanso-cd.yml +++ b/.github/workflows/kanso-cd.yml @@ -15,12 +15,12 @@ jobs: with: platform: ${{ matrix.Platform }} - release-pine64_star64: + release-riscv64_pine64_star64: needs: kernel-build - uses: ./.github/workflows/release-pine64_star64.yml + uses: ./.github/workflows/release-riscv64_pine64_star64.yml secrets: inherit release: - needs: release-pine64_star64 + needs: release-riscv64_pine64_star64 uses: ./.github/workflows/release.yml secrets: inherit diff --git a/.github/workflows/kanso-ci.yml b/.github/workflows/kanso-ci.yml index 4825e24..5344fc3 100644 --- a/.github/workflows/kanso-ci.yml +++ b/.github/workflows/kanso-ci.yml @@ -31,3 +31,16 @@ jobs: with: platform: ${{ matrix.Platform }} + results: + if: ${{ always() }} + runs-on: ubuntu-latest + name: Final Results + needs: [kernel-build] + steps: + - run: | + result="${{ needs.kernel-build.result }}" + if [[ $result == "success" || $result == "skipped" ]]; then + exit 0 + else + exit 1 + fi diff --git a/.github/workflows/release-pine64_star64.yml b/.github/workflows/release-riscv64_pine64_star64.yml similarity index 94% rename from .github/workflows/release-pine64_star64.yml rename to .github/workflows/release-riscv64_pine64_star64.yml index bced922..74f6f8b 100644 --- a/.github/workflows/release-pine64_star64.yml +++ b/.github/workflows/release-riscv64_pine64_star64.yml @@ -1,4 +1,4 @@ -name: Kernel-Build +name: Release-Riscv64_Pine64_Star64 on: workflow_call: @@ -64,6 +64,6 @@ jobs: - name: Upload packaged Star64 image uses: actions/upload-artifact@v4 with: - name: kanso_pine64_star64 + name: kanso_riscv64_pine64_star64 path: | - images/kanso_pine64_star64.img + images/kanso_riscv64_pine64_star64.img diff --git a/.github/workflows/releases/pine64_star64/genimage.cfg b/.github/workflows/releases/pine64_star64/genimage.cfg index 7b14921..c9d1f6e 100644 --- a/.github/workflows/releases/pine64_star64/genimage.cfg +++ b/.github/workflows/releases/pine64_star64/genimage.cfg @@ -1,4 +1,4 @@ -image kanso_pine64_star64.img { +image kanso_riscv64_pine64_star64.img { hdimage { gpt = true } diff --git a/cmake/Toolchains/clang.cmake b/cmake/Toolchains/clang.cmake index 6f62560..d96e18e 100644 --- a/cmake/Toolchains/clang.cmake +++ b/cmake/Toolchains/clang.cmake @@ -21,6 +21,19 @@ if(NOT CMAKE_C_COMPILER OR NOT CMAKE_ASM_COMPILER) message(FATAL_ERROR "Could not find Clang.") endif() + get_filename_component(_LLVM_BIN_DIR "${_CLANG}" DIRECTORY) + + find_program(CMAKE_OBJCOPY # standard CMake cache variable + NAMES llvm-objcopy objcopy + HINTS "${_LLVM_BIN_DIR}" + "${_BREW_LLVM_PREFIX}/bin" + "C:\\Program Files\\LLVM\\bin" + NO_CMAKE_SYSTEM_PATH) + + if(NOT CMAKE_OBJCOPY) + message(FATAL_ERROR "Could not find llvm-objcopy or objcopy in ${_LLVM_BIN_DIR}") + endif() + set(CMAKE_C_COMPILER "${_CLANG}" CACHE FILEPATH "C compiler" FORCE) set(CMAKE_ASM_COMPILER "${_CLANG}" CACHE FILEPATH "ASM compiler" FORCE) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) diff --git a/src/Common/String.c b/src/Common/String.c index 249d514..1f09819 100644 --- a/src/Common/String.c +++ b/src/Common/String.c @@ -31,6 +31,7 @@ void StringFormat(SpanChar* destination, ReadOnlySpanChar message, ...) va_end(vargs); } +// TODO: Refactor this function void StringFormatVargs(SpanChar* destination, ReadOnlySpanChar message, va_list vargs) { uint32_t length = 0; @@ -97,13 +98,43 @@ void StringFormatVargs(SpanChar* destination, ReadOnlySpanChar message, va_list break; } + case 'l': + { + int64_t decimalArgument = va_arg(vargs, int64_t); + + // HACK: We should do a code that compile 32 and 64-bit + int32_t magnitude = (int32_t)decimalArgument; + + if (decimalArgument < 0) + { + destination->Pointer[length++] = '-'; + magnitude = -magnitude; + } + + int32_t divisor = 1; + + while ((magnitude / divisor) > 9) + { + divisor *= 10; + } + + while (divisor > 0) + { + destination->Pointer[length++] = '0' + magnitude / divisor; + + magnitude %= divisor; + divisor /= 10; + } + break; + } + case 'x': { - uint64_t hexaArgument = va_arg(vargs, uint64_t); + uintptr_t hexaArgument = va_arg(vargs, uintptr_t); destination->Pointer[length++] = '0'; destination->Pointer[length++] = 'x'; - for (int64_t i = 15; i >= 0; i--) + for (int32_t i = (sizeof(uintptr_t) * 2) - 1; i >= 0; i--) { unsigned nibble = (hexaArgument >> (i * 4)) & 0xf; destination->Pointer[length++] = "0123456789abcdef"[nibble]; diff --git a/src/Common/Test.h b/src/Common/Test.h index 8436d67..556c54c 100644 --- a/src/Common/Test.h +++ b/src/Common/Test.h @@ -50,17 +50,21 @@ extern SpanChar globalTestLastErrorMessage; do { \ if (!(expr)) { \ TestEntry* testEntry = &globalTests[globalCurrentTestIndex]; \ - testEntry->HasError = true; \ - StringFormat(&globalTestLastErrorMessage, String("%s\n Expected: %s\n Actual: %d %s %d"), __FILE__, #expr, expected, operator, actual); \ - return; \ + if (!testEntry->HasError) \ + { \ + testEntry->HasError = true; \ + StringFormat(&globalTestLastErrorMessage, String("%s\n Expected: %s\n Actual: %d %s %d"), __FILE__, #expr, expected, operator, actual); \ + } \ } \ } while (false) -// BUG: There is a bug in the assert only in 32-bit version, when the assert fail +// BUG: There is a bug in the assert only in 32-bit version, when the assert fail maybe due to 64 bit values used in the comparison like with the time #define TestAssertEquals(expected, actual) TestAssertCore((expected) == (actual), expected, actual, "==") #define TestAssertNotEquals(expected, actual) TestAssertCore((expected) != (actual), expected, actual, "!=") #define TestAssertGreaterThan(expected, actual) TestAssertCore((expected) > (actual), expected, actual, ">") +#define TestAssertIsTrue(actual) TestAssertCore(true == (actual), true, actual, "==") +// TODO: Adapt the macro like the core one #define TestAssertStringEquals(expected, actual) \ do { \ if (finalString.Length != destination.Length) \ @@ -68,7 +72,6 @@ extern SpanChar globalTestLastErrorMessage; TestEntry* testEntry = &globalTests[globalCurrentTestIndex]; \ testEntry->HasError = true; \ StringFormat(&globalTestLastErrorMessage, String("%s\n Expected: (%s.Length) == (%s.Length)\n Actual: %d == %d"), __FILE__, #expected, #actual, expected.Length, actual.Length); \ - return; \ } \ \ if (!StringEquals(expected, actual)) \ @@ -76,7 +79,6 @@ extern SpanChar globalTestLastErrorMessage; TestEntry* testEntry = &globalTests[globalCurrentTestIndex]; \ testEntry->HasError = true; \ StringFormat(&globalTestLastErrorMessage, String("%s\n Expected: (%s) == (%s)\n Actual: \"%s\" == \"%s\""), __FILE__, #expected, #actual, expected.Pointer, actual.Pointer); \ - return; \ } \ } while (false) diff --git a/src/Common/Types.h b/src/Common/Types.h index 75dd029..b939e47 100644 --- a/src/Common/Types.h +++ b/src/Common/Types.h @@ -27,3 +27,11 @@ static_assert(sizeof(uintptr_t) == sizeof(void *), "uintptr_t is not pointer-siz #define va_start __builtin_va_start #define va_end __builtin_va_end #define va_arg __builtin_va_arg + +typedef struct +{ + uint8_t Red; + uint8_t Green; + uint8_t Blue; + uint8_t Alpha; +} Color; diff --git a/src/Kernel/Kernel.c b/src/Kernel/Kernel.c index aa3c0b1..8a1ae46 100644 --- a/src/Kernel/Kernel.c +++ b/src/Kernel/Kernel.c @@ -1,12 +1,12 @@ #include "Kernel.h" #include "KernelConsole.h" +#include "Memory.h" #include "Platform.h" void KernelFailureCore(ReadOnlySpanChar file, uint32_t line, ReadOnlySpanChar message, ...) { - KernelConsolePrint(String("\x1b[31m\n ----------------\n")); - KernelConsolePrint(String("| KERNEL Failure |\n")); - KernelConsolePrint(String(" ----------------\n\n")); + KernelConsoleSetForegroundColor(KernelConsoleColorError); + KernelConsolePrintBoxMessage(String("Kernel Failure")); KernelConsolePrint(String("%s:%d\n"), file, line); va_list vargs; @@ -15,11 +15,12 @@ void KernelFailureCore(ReadOnlySpanChar file, uint32_t line, ReadOnlySpanChar me auto tmp = StackAllocChar(256); StringFormatVargs(&tmp, message, vargs); - KernelConsolePrint(String("%s\n\n\x1b[0m"), tmp); + KernelConsolePrint(String("%s\n\n"), tmp); + KernelConsoleResetStyle(); va_end(vargs); - CpuDisableSupervisorInterrupts(CpuInterruptType_All); + CpuDisableInterrupts(CpuInterruptType_All); while (true) { diff --git a/src/Kernel/KernelConsole.c b/src/Kernel/KernelConsole.c index 5d0ff3e..27eefb0 100644 --- a/src/Kernel/KernelConsole.c +++ b/src/Kernel/KernelConsole.c @@ -1,8 +1,8 @@ #include "KernelConsole.h" +#include "Memory.h" #include "String.h" #include "Platform.h" -// TODO: See https://github.com/riscv-software-src/opensbi/blob/master/lib/sbi/sbi_console.c#L440 for more implementation details void KernelConsolePrint(ReadOnlySpanChar message, ...) { auto output = StackAllocChar(2048); @@ -16,3 +16,76 @@ void KernelConsolePrint(ReadOnlySpanChar message, ...) BiosDebugConsoleWrite(ToReadOnlySpanChar(output)); } + +void KernelConsoleSetForegroundColor(Color color) +{ + KernelConsolePrint(String("\x1b[38;2;%d;%d;%dm"), (int32_t)color.Red, (int32_t)color.Green, (int32_t)color.Blue); +} + +void KernelConsoleResetStyle() +{ + KernelConsolePrint(String("\x1b[0m")); + KernelConsoleSetForegroundColor(KernelConsoleColorNormal); +} + +void FormatBoxedMessage(SpanChar destination, ReadOnlySpanChar message) +{ + auto upLeftCorner = String("┌"); + auto upRightCorner = String("┐"); + auto downLeftCorner = String("└"); + auto downRightCorner = String("┘"); + auto horizontalLine = String("─"); + auto verticalLine = String("│"); + + MemoryCopy(destination, upLeftCorner); + destination = SpanSliceFrom(destination, upLeftCorner.Length); + + for (uint32_t i = 0; i < message.Length + 2; i++) + { + MemoryCopy(destination, horizontalLine); + destination = SpanSliceFrom(destination, horizontalLine.Length); + } + + MemoryCopy(destination, upRightCorner); + destination = SpanSliceFrom(destination, upRightCorner.Length); + + MemoryCopy(destination, String("\n")); + destination = SpanSliceFrom(destination, 1); + + MemoryCopy(destination, verticalLine); + destination = SpanSliceFrom(destination, verticalLine.Length); + + MemoryCopy(destination, String(" ")); + destination = SpanSliceFrom(destination, 1); + + MemoryCopy(destination, message); + destination = SpanSliceFrom(destination, message.Length); + + MemoryCopy(destination, String(" ")); + destination = SpanSliceFrom(destination, 1); + + MemoryCopy(destination, verticalLine); + destination = SpanSliceFrom(destination, verticalLine.Length); + + MemoryCopy(destination, String("\n")); + destination = SpanSliceFrom(destination, 1); + + MemoryCopy(destination, downLeftCorner); + destination = SpanSliceFrom(destination, downLeftCorner.Length); + + for (uint32_t i = 0; i < message.Length + 2; i++) + { + MemoryCopy(destination, horizontalLine); + destination = SpanSliceFrom(destination, horizontalLine.Length); + } + + MemoryCopy(destination, downRightCorner); + destination = SpanSliceFrom(destination, downRightCorner.Length); +} + +void KernelConsolePrintBoxMessage(ReadOnlySpanChar message) +{ + auto boxedMessage = StackAllocChar(512); + FormatBoxedMessage(boxedMessage, message); + KernelConsolePrint(String("\n%s\n"), boxedMessage); +} diff --git a/src/Kernel/KernelConsole.h b/src/Kernel/KernelConsole.h index f9de993..d8ca80a 100644 --- a/src/Kernel/KernelConsole.h +++ b/src/Kernel/KernelConsole.h @@ -4,5 +4,20 @@ #include "String.h" #include "Types.h" +const Color KernelConsoleColorNormal = { 212, 212, 212, 255 }; +const Color KernelConsoleColorHighlight = { 250, 250, 250, 255 }; +const Color KernelConsoleColorAccent = { 79, 193, 255, 255 }; +const Color KernelConsoleColorSuccess = { 106, 153, 85, 255 }; +const Color KernelConsoleColorWarning = { 255, 135, 100, 255 }; +const Color KernelConsoleColorError = { 255, 105, 105, 255 }; +const Color KernelConsoleColorInfo = { 220, 220, 170, 255 }; +const Color KernelConsoleColorAction = { 197, 134, 192, 255 }; +const Color KernelConsoleColorKeyword = { 86, 156, 214, 255 }; +const Color KernelConsoleColorNumeric = { 181, 206, 168, 255 }; + void KernelConsolePrint(ReadOnlySpanChar message, ...); +void KernelConsoleSetForegroundColor(Color color); +void KernelConsoleResetStyle(); + +void KernelConsolePrintBoxMessage(ReadOnlySpanChar message); diff --git a/src/Kernel/KernelMain.c b/src/Kernel/KernelMain.c index 630b31b..452f3fb 100644 --- a/src/Kernel/KernelMain.c +++ b/src/Kernel/KernelMain.c @@ -12,34 +12,83 @@ const char KernelLogo[] = ,'\0' }; -__attribute__((interrupt("supervisor"))) -__attribute__((section(".text.interrupt"))) -void KernelSupervisorTrapHandler(CpuTrapFrame* trapFrame) +void KernelTrapHandler(CpuTrapFrame* trapFrame) { - auto programCounter = CpuTrapFrameGetProgramCounter(trapFrame); + auto trapCause = CpuTrapFrameGetCause(trapFrame); + auto errorName = String("Unknown kernel trap cause"); - CpuClearSupervisorPendingInterrupts(CpuInterruptType_Timer); - KernelConsolePrint(String("Kernel trap handler: %d (PC=%d).\n"), CpuReadTime(), programCounter); + if (trapCause.Type == CpuTrapType_Interrupt) + { + switch (trapCause.InterruptType) + { + case CpuInterruptType_Timer: + CpuClearPendingInterrupts(CpuInterruptType_Timer); + //CpuDisableInterrupts(CpuInterruptType_Timer); + //SbiSetTimer((uint64_t)-1); + BiosSetTimer(CpuReadTime() + 10000000); + + auto programCounter = CpuTrapFrameGetProgramCounter(trapFrame); + KernelConsolePrint(String("Kernel trap handler: %l (PC=%x).\n"), CpuReadTime(), programCounter); - //CpuDisableSupervisorInterrupts(CpuInterruptType_Timer); - //SbiSetTimer((uint64_t)-1); - BiosSetTimer(CpuReadTime() + 10000000); + return; + + default: + errorName = String("Unknown interrupt type"); + } + } + else + { + switch (trapCause.SynchronousType) + { + case CpuTrapSynchronousType_InstructionError: + errorName = String("Instruction error"); + break; + + case CpuTrapSynchronousType_AddressError: + errorName = String("Address error"); + break; + + case CpuTrapSynchronousType_PageError: + errorName = String("Page error"); + break; + + case CpuTrapSynchronousType_IntegrityError: + errorName = String("Integrity error"); + break; + + case CpuTrapSynchronousType_HardwareError: + errorName = String("Hardware error"); + break; + + default: + errorName = String("Unknown synchronous trap type"); + } + } + + CpuLogTrapFrame(trapFrame); + KernelFailure(String("%s. (Code=%x, Extra=%x)"), errorName, trapCause.Code, trapCause.ExtraInformation); } void KernelMain() { auto platformInformation = PlatformGetInformation(); - KernelConsolePrint(String("\n\n\x1b[36m%s\x1b[0m\n"), KernelLogo); + KernelConsoleSetForegroundColor(KernelConsoleColorAccent); + KernelConsolePrint(String("\n\n%s\n"), KernelLogo); + KernelConsoleResetStyle(); + + KernelConsoleSetForegroundColor(KernelConsoleColorHighlight); KernelConsolePrint(String("Kanso OS %s "), KANSO_VERSION_FULL); KernelConsolePrint(String("(%s %d-bit)\n\n"), platformInformation.Name.Pointer, platformInformation.ArchitectureBits); + KernelConsoleResetStyle(); - //CpuSetSupervisorTrapHandler(&KernelSupervisorTrapHandler); + CpuSetTrapHandler(KernelTrapHandler); BiosSetTimer(CpuReadTime() + 10000000); - CpuEnableSupervisorInterrupts(CpuInterruptType_Timer); + CpuEnableInterrupts(CpuInterruptType_Timer); while (true) { + CpuGenerateInvalidInstruction(); CpuWaitForInterrupt(); } } diff --git a/src/Kernel/KernelTest.c b/src/Kernel/KernelTest.c index 07bdedd..8d7ef3d 100644 --- a/src/Kernel/KernelTest.c +++ b/src/Kernel/KernelTest.c @@ -5,33 +5,36 @@ #include "Platform.h" #include "Version.h" -const char* TEST_CONSOLE_RESET = "\x1b[0m"; -const char* TEST_CONSOLE_GREEN = "\x1b[32m"; -const char* TEST_CONSOLE_RED = "\x1b[31m"; - void KernelTestHandler(TestRunState state, ReadOnlySpanChar message, ...) { if (state == TestRunState_Start) { - KernelConsolePrint(String("%s[ RUN ]%s"), TEST_CONSOLE_GREEN, TEST_CONSOLE_RESET); + KernelConsoleSetForegroundColor(KernelConsoleColorSuccess); + KernelConsolePrint(String("[ RUN ]")); } else if (state == TestRunState_OK) { - KernelConsolePrint(String("%s[ OK ]%s"), TEST_CONSOLE_GREEN, TEST_CONSOLE_RESET); + KernelConsoleSetForegroundColor(KernelConsoleColorSuccess); + KernelConsolePrint(String("[ OK ]")); } else if (state == TestRunState_Passed) { - KernelConsolePrint(String("%s[ PASSED ]%s"), TEST_CONSOLE_GREEN, TEST_CONSOLE_RESET); + KernelConsoleSetForegroundColor(KernelConsoleColorSuccess); + KernelConsolePrint(String("[ PASSED ]")); } else if (state == TestRunState_Failed) { - KernelConsolePrint(String("%s[ FAILED ]%s"), TEST_CONSOLE_RED, TEST_CONSOLE_RESET); + KernelConsoleSetForegroundColor(KernelConsoleColorError); + KernelConsolePrint(String("[ FAILED ]")); } else if (state == TestRunState_Separator) { - KernelConsolePrint(String("%s[==========]%s"), TEST_CONSOLE_GREEN, TEST_CONSOLE_RESET); + KernelConsoleSetForegroundColor(KernelConsoleColorSuccess); + KernelConsolePrint(String("[==========]")); } + KernelConsoleResetStyle(); + va_list vargs; va_start(vargs, message); @@ -47,8 +50,10 @@ void KernelMain() { auto platformInformation = PlatformGetInformation(); + KernelConsoleSetForegroundColor(KernelConsoleColorHighlight); KernelConsolePrint(String("\n\nKanso OS Kernel Tests %s "), KANSO_VERSION_FULL); KernelConsolePrint(String("(%s %d-bit)\n\n"), platformInformation.Name.Pointer, platformInformation.ArchitectureBits); + KernelConsoleResetStyle(); TestRun(KernelTestHandler); BiosReset(BiosResetType_Shutdown, BiosResetReason_None); diff --git a/src/Kernel/Platform.h b/src/Kernel/Platform.h index 9e0b438..c445db4 100644 --- a/src/Kernel/Platform.h +++ b/src/Kernel/Platform.h @@ -3,6 +3,10 @@ #include "Memory.h" #include "Types.h" +// -------------------------------------------------------------------------------------- +// General +// -------------------------------------------------------------------------------------- + typedef struct { ReadOnlySpanChar Name; @@ -11,6 +15,11 @@ typedef struct PlatformInformation PlatformGetInformation(); + +// -------------------------------------------------------------------------------------- +// Cpu +// -------------------------------------------------------------------------------------- + typedef enum { CpuInterruptType_None = 0, @@ -20,6 +29,34 @@ typedef enum CpuInterruptType_All = 0xFF, } CpuInterruptType; +typedef enum +{ + CpuTrapType_Unknown, + CpuTrapType_Interrupt, + CpuTrapType_Synchronous +} CpuTrapType; + +typedef enum +{ + CpuTrapSynchronousType_Unknown, + CpuTrapSynchronousType_InstructionError, + CpuTrapSynchronousType_Debug, + CpuTrapSynchronousType_AddressError, + CpuTrapSynchronousType_PageError, + CpuTrapSynchronousType_SystemCall, + CpuTrapSynchronousType_IntegrityError, + CpuTrapSynchronousType_HardwareError +} CpuTrapSynchronousType; + +typedef struct +{ + CpuTrapType Type; + CpuInterruptType InterruptType; + CpuTrapSynchronousType SynchronousType; + uintptr_t Code; + uintptr_t ExtraInformation; +} CpuTrapCause; + struct CpuTrapFrame; typedef struct CpuTrapFrame CpuTrapFrame; @@ -27,14 +64,25 @@ typedef void (*CpuTrapHandler)(struct CpuTrapFrame*); uint64_t CpuReadTime(); uint64_t CpuReadCycle(); -void CpuSetSupervisorTrapHandler(CpuTrapHandler trapHandler); -void CpuEnableSupervisorInterrupts(CpuInterruptType types); -void CpuDisableSupervisorInterrupts(CpuInterruptType types); -void CpuClearSupervisorPendingInterrupts(CpuInterruptType types); + +void CpuGenerateInvalidInstruction(); +uintptr_t CpuComputeNextInstructionAddress(uintptr_t instructionAddress); + +void CpuSetTrapHandler(CpuTrapHandler trapHandler); +void CpuEnableInterrupts(CpuInterruptType types); +void CpuDisableInterrupts(CpuInterruptType types); +void CpuClearPendingInterrupts(CpuInterruptType types); void CpuWaitForInterrupt(); +void CpuLogTrapFrame(const CpuTrapFrame* trapFrame); +CpuTrapCause CpuTrapFrameGetCause(const CpuTrapFrame* trapFrame); uintptr_t CpuTrapFrameGetProgramCounter(const CpuTrapFrame* trapFrame); +void CpuTrapFrameSetProgramCounter(CpuTrapFrame* trapFrame, uintptr_t value); + +// -------------------------------------------------------------------------------------- +// Bios +// -------------------------------------------------------------------------------------- typedef enum { diff --git a/src/Kernel/Platforms/RiscV/AsmCommon.h b/src/Kernel/Platforms/RiscV/AsmCommon.h index f042fc2..5b17560 100644 --- a/src/Kernel/Platforms/RiscV/AsmCommon.h +++ b/src/Kernel/Platforms/RiscV/AsmCommon.h @@ -19,3 +19,92 @@ #endif +.macro save_general_purpose_registers addr + save_pointer x1, 0 * PTR_SIZE(\addr) + save_pointer x2, 1 * PTR_SIZE(\addr) + save_pointer x3, 2 * PTR_SIZE(\addr) + save_pointer x4, 3 * PTR_SIZE(\addr) + save_pointer x5, 4 * PTR_SIZE(\addr) + save_pointer x6, 5 * PTR_SIZE(\addr) + save_pointer x7, 6 * PTR_SIZE(\addr) + save_pointer x8, 7 * PTR_SIZE(\addr) + save_pointer x9, 8 * PTR_SIZE(\addr) + save_pointer x10, 9 * PTR_SIZE(\addr) + save_pointer x11, 10 * PTR_SIZE(\addr) + save_pointer x12, 11 * PTR_SIZE(\addr) + save_pointer x13, 12 * PTR_SIZE(\addr) + save_pointer x14, 13 * PTR_SIZE(\addr) + save_pointer x15, 14 * PTR_SIZE(\addr) + save_pointer x16, 15 * PTR_SIZE(\addr) + save_pointer x17, 16 * PTR_SIZE(\addr) + save_pointer x18, 17 * PTR_SIZE(\addr) + save_pointer x19, 18 * PTR_SIZE(\addr) + save_pointer x20, 19 * PTR_SIZE(\addr) + save_pointer x21, 20 * PTR_SIZE(\addr) + save_pointer x22, 21 * PTR_SIZE(\addr) + save_pointer x23, 22 * PTR_SIZE(\addr) + save_pointer x24, 23 * PTR_SIZE(\addr) + save_pointer x25, 24 * PTR_SIZE(\addr) + save_pointer x26, 25 * PTR_SIZE(\addr) + save_pointer x27, 26 * PTR_SIZE(\addr) + save_pointer x28, 27 * PTR_SIZE(\addr) + save_pointer x29, 28 * PTR_SIZE(\addr) + save_pointer x30, 29 * PTR_SIZE(\addr) + save_pointer x31, 30 * PTR_SIZE(\addr) +.endm + +.macro load_general_purpose_registers addr + load_pointer x1, 0 * PTR_SIZE(\addr) + load_pointer x2, 1 * PTR_SIZE(\addr) + load_pointer x3, 2 * PTR_SIZE(\addr) + load_pointer x4, 3 * PTR_SIZE(\addr) + load_pointer x5, 4 * PTR_SIZE(\addr) + load_pointer x6, 5 * PTR_SIZE(\addr) + load_pointer x7, 6 * PTR_SIZE(\addr) + load_pointer x8, 7 * PTR_SIZE(\addr) + load_pointer x9, 8 * PTR_SIZE(\addr) + load_pointer x10, 9 * PTR_SIZE(\addr) + load_pointer x11, 10 * PTR_SIZE(\addr) + load_pointer x12, 11 * PTR_SIZE(\addr) + load_pointer x13, 12 * PTR_SIZE(\addr) + load_pointer x14, 13 * PTR_SIZE(\addr) + load_pointer x15, 14 * PTR_SIZE(\addr) + load_pointer x16, 15 * PTR_SIZE(\addr) + load_pointer x17, 16 * PTR_SIZE(\addr) + load_pointer x18, 17 * PTR_SIZE(\addr) + load_pointer x19, 18 * PTR_SIZE(\addr) + load_pointer x20, 19 * PTR_SIZE(\addr) + load_pointer x21, 20 * PTR_SIZE(\addr) + load_pointer x22, 21 * PTR_SIZE(\addr) + load_pointer x23, 22 * PTR_SIZE(\addr) + load_pointer x24, 23 * PTR_SIZE(\addr) + load_pointer x25, 24 * PTR_SIZE(\addr) + load_pointer x26, 25 * PTR_SIZE(\addr) + load_pointer x27, 26 * PTR_SIZE(\addr) + load_pointer x28, 27 * PTR_SIZE(\addr) + load_pointer x29, 28 * PTR_SIZE(\addr) + load_pointer x30, 29 * PTR_SIZE(\addr) + load_pointer x31, 30 * PTR_SIZE(\addr) +.endm + +.macro save_supervisor_registers addr + csrr t1, sepc + save_pointer t1, 0 * PTR_SIZE(\addr) + csrr t1, sstatus + save_pointer t1, 1 * PTR_SIZE(\addr) + csrr t1, sscratch + save_pointer t1, 2 * PTR_SIZE(\addr) + csrr t1, scause + save_pointer t1, 3 * PTR_SIZE(\addr) + csrr t1, stval + save_pointer t1, 4 * PTR_SIZE(\addr) +.endm + +.macro load_supervisor_registers addr + load_pointer t1, 0 * PTR_SIZE(\addr) + csrw sepc, t1 + load_pointer t1, 1 * PTR_SIZE(\addr) + csrw sstatus, t1 + load_pointer t1, 2 * PTR_SIZE(\addr) + csrw sscratch, t1 +.endm diff --git a/src/Kernel/Platforms/RiscV/CMakeLists.txt b/src/Kernel/Platforms/RiscV/CMakeLists.txt index 323457a..3f3da4f 100644 --- a/src/Kernel/Platforms/RiscV/CMakeLists.txt +++ b/src/Kernel/Platforms/RiscV/CMakeLists.txt @@ -1,4 +1,4 @@ -add_executable(Kernel UnityBuild.c Boot.S SupervisorTrapEntry.S) +add_executable(Kernel UnityBuild.c Boot.S KernelTrapEntry.S) target_link_libraries(Kernel PRIVATE Common) @@ -12,7 +12,7 @@ set_target_properties(Kernel PROPERTIES ) add_custom_command(TARGET Kernel POST_BUILD - COMMAND llvm-objcopy + COMMAND "${CMAKE_OBJCOPY}" --output-target=binary # -O binary --binary-architecture=riscv:${_MARCH} $ # kernel.elf (source) @@ -21,7 +21,7 @@ add_custom_command(TARGET Kernel POST_BUILD ) -add_executable(KernelTest UnityBuild.c Boot.S SupervisorTrapEntry.S) +add_executable(KernelTest UnityBuild.c Boot.S KernelTrapEntry.S) target_link_libraries(KernelTest PRIVATE Common) diff --git a/src/Kernel/Platforms/RiscV/Cpu.c b/src/Kernel/Platforms/RiscV/Cpu.c index 9e7e03d..c72409a 100644 --- a/src/Kernel/Platforms/RiscV/Cpu.c +++ b/src/Kernel/Platforms/RiscV/Cpu.c @@ -1,12 +1,84 @@ +#include "Memory.h" #include "Types.h" +#include "../../KernelConsole.h" #include "../../Platform.h" +#define RISCV_INTERRUPT_SOFTWARE 1 +#define RISCV_INTERRUPT_TIMER 5 +#define RISCV_INTERRUPT_EXTERNAL 9 + +#define RISCV_TRAP_SYNCHRONOUS_INSTRUCTION_ADDRESS_MISALIGNED 0 +#define RISCV_TRAP_SYNCHRONOUS_INSTRUCTION_ACCESS_FAULT 1 +#define RISCV_TRAP_SYNCHRONOUS_ILLEGAL_INSTRUCTION 2 +#define RISCV_TRAP_SYNCHRONOUS_BREAKPOINT 3 +#define RISCV_TRAP_LOAD_ADDRESS_MISALIGNED 4 +#define RISCV_TRAP_LOAD_ACCESS_FAULT 5 +#define RISCV_TRAP_STORE_AMO_ADDRESS_MISALIGNED 6 +#define RISCV_TRAP_STORE_AMO_ACCESS_FAULT 7 +#define RISCV_TRAP_ECALL_FROM_USER_MODE 8 +#define RISCV_TRAP_ECALL_FROM_SUPERVISOR_MODE 9 +#define RISCV_TRAP_INSTRUCTION_PAGE_FAULT 12 +#define RISCV_TRAP_LOAD_PAGE_FAULT 13 +#define RISCV_TRAP_STORE_AMO_PAGE_FAULT 15 +#define RISCV_TRAP_SOFTWARE_CHECK 18 +#define RISCV_TRAP_HARDWARE_ERROR 19 + +typedef struct +{ + uintptr_t RA; + uintptr_t SP; + uintptr_t GP; + uintptr_t TP; + uintptr_t T0; + uintptr_t T1; + uintptr_t T2; + uintptr_t S0; + uintptr_t S1; + uintptr_t A0; + uintptr_t A1; + uintptr_t A2; + uintptr_t A3; + uintptr_t A4; + uintptr_t A5; + uintptr_t A6; + uintptr_t A7; + uintptr_t S2; + uintptr_t S3; + uintptr_t S4; + uintptr_t S5; + uintptr_t S6; + uintptr_t S7; + uintptr_t S8; + uintptr_t S9; + uintptr_t S10; + uintptr_t S11; + uintptr_t T3; + uintptr_t T4; + uintptr_t T5; + uintptr_t T6; +} GeneralPurposeRegisters; + +typedef struct +{ + uintptr_t Epc; + uintptr_t Status; + uintptr_t Scratch; + uintptr_t Cause; + uintptr_t TrapValue; +} SupervisorRegisters; + struct CpuTrapFrame { - uintptr_t PC; + // TODO: Add a TrapFrameType to specify if it is partial, full, float, vector, etc. + GeneralPurposeRegisters GeneralPurposeRegisters; + SupervisorRegisters SupervisorRegisters; }; -extern void supervisor_trap_entry(); +static_assert(sizeof(CpuTrapFrame) % 16 == 0); + +extern void kernel_trap_entry(); + +CpuTrapHandler globalCpuTrapHandler; uintptr_t ComputeCpuInterruptMask(CpuInterruptType types) { @@ -14,17 +86,17 @@ uintptr_t ComputeCpuInterruptMask(CpuInterruptType types) if (types & CpuInterruptType_Software) { - mask |= (uintptr_t)1 << 1; + mask |= (uintptr_t)1 << RISCV_INTERRUPT_SOFTWARE; } if (types & CpuInterruptType_Timer) { - mask |= (uintptr_t)1 << 5; + mask |= (uintptr_t)1 << RISCV_INTERRUPT_TIMER; } if (types & CpuInterruptType_External) { - mask |= (uintptr_t)1 << 9; + mask |= (uintptr_t)1 << RISCV_INTERRUPT_EXTERNAL; } return mask; @@ -76,16 +148,67 @@ inline uint64_t CpuReadCycle() } #endif -inline void CpuSetSupervisorTrapHandler(CpuTrapHandler trapHandler) +inline void CpuGenerateInvalidInstruction() +{ + __asm__ volatile ("unimp"); +} + +/* Masks and “all-ones” patterns for the three fields we test. */ +#define RV_MASK_LOW2 0x0003u /* bits[1:0] */ +#define RV_MASK_MID3 0x001Cu /* bits[4:2] */ +#define RV_MASK_HIGH2 0x0060u /* bits[6:5] */ + +#define RV_PATTERN_LOW2_32 0x0003u /* 0b11 */ +#define RV_PATTERN_MID3_48 0x001Cu /* 0b111 << 2 */ +#define RV_PATTERN_HIGH2_64 0x0060u /* 0b11 << 5 */ + +/* Resulting instruction sizes in bytes. */ +#define RV_SIZE_16 2u +#define RV_SIZE_32 4u +#define RV_SIZE_48 6u +#define RV_SIZE_64 8u + +uintptr_t CpuComputeNextInstructionAddress(uintptr_t instructionAddress) +{ + /* Text might be in an execute-only segment; cast via unsigned char avoids UB. */ + const uint16_t firstHalfword = + *(const uint16_t *)(const unsigned char *)instructionAddress; + + /* --- 1. 16-bit? -------------------------------------------------------- */ + if ((firstHalfword & RV_MASK_LOW2) != RV_PATTERN_LOW2_32) + return instructionAddress + RV_SIZE_16; + + /* --- 2. 32-bit? -------------------------------------------------------- */ + if ((firstHalfword & RV_MASK_MID3) != RV_PATTERN_MID3_48) + return instructionAddress + RV_SIZE_32; + + /* --- 3. 48-bit? -------------------------------------------------------- */ + if ((firstHalfword & RV_MASK_HIGH2) != RV_PATTERN_HIGH2_64) + return instructionAddress + RV_SIZE_48; + + /* --- 4. Otherwise it must be 64-bit ----------------------------------- */ + return instructionAddress + RV_SIZE_64; +} + +inline void CpuSetTrapHandler(CpuTrapHandler trapHandler) { - __asm__ volatile( - "csrw stvec, %0\n" - "csrsi sstatus, 2" // TODO: Confirm the sstatus flag - : - : "r" (supervisor_trap_entry)); + globalCpuTrapHandler = trapHandler; + + if (trapHandler) + { + __asm__ volatile( + "csrw stvec, %0\n" + "csrsi sstatus, 2" + : + : "r" (kernel_trap_entry)); + } + else + { + __asm__ volatile("csrsi sstatus, 0"); + } } -inline void CpuEnableSupervisorInterrupts(CpuInterruptType types) +inline void CpuEnableInterrupts(CpuInterruptType types) { auto mask = ComputeCpuInterruptMask(types); @@ -97,7 +220,7 @@ inline void CpuEnableSupervisorInterrupts(CpuInterruptType types) ); } -inline void CpuDisableSupervisorInterrupts(CpuInterruptType types) +inline void CpuDisableInterrupts(CpuInterruptType types) { auto mask = ComputeCpuInterruptMask(types); @@ -109,7 +232,7 @@ inline void CpuDisableSupervisorInterrupts(CpuInterruptType types) ); } -inline void CpuClearSupervisorPendingInterrupts(CpuInterruptType types) +inline void CpuClearPendingInterrupts(CpuInterruptType types) { auto mask = ComputeCpuInterruptMask(types); @@ -123,10 +246,193 @@ inline void CpuClearSupervisorPendingInterrupts(CpuInterruptType types) inline void CpuWaitForInterrupt() { - __asm__ __volatile__("wfi"); + __asm__ __volatile__("wfi" ::: "memory"); +} + +void LogRegister(ReadOnlySpanChar name, uintptr_t value, uint8_t padding, bool insertTab) +{ + // TODO: Color the register name with the keyword blue of VScode? + KernelConsoleSetForegroundColor(KernelConsoleColorKeyword); + KernelConsolePrint(String("%s"), name); + KernelConsoleResetStyle(); + + KernelConsolePrint(String(":")); + + for (uint32_t i = 0; i < padding; i++) + { + KernelConsolePrint(String(" ")); + } + + KernelConsoleSetForegroundColor(KernelConsoleColorNumeric); + KernelConsolePrint(String("%x"), value); + KernelConsoleResetStyle(); + + if (insertTab) + { + KernelConsolePrint(String(" ")); + } + else + { + KernelConsolePrint(String("\n")); + } +} + +void LogGeneralPurposeRegisters(const GeneralPurposeRegisters* generalPurposeRegisters) +{ + LogRegister(String("ra"), generalPurposeRegisters->RA, 2, true); + LogRegister(String("sp"), generalPurposeRegisters->SP, 2, true); + LogRegister(String("gp"), generalPurposeRegisters->GP, 2, false); + + LogRegister(String("tp"), generalPurposeRegisters->TP, 2, true); + LogRegister(String("t0"), generalPurposeRegisters->T0, 2, true); + LogRegister(String("t1"), generalPurposeRegisters->T1, 2, false); + + LogRegister(String("t2"), generalPurposeRegisters->T2, 2, true); + LogRegister(String("s0"), generalPurposeRegisters->S0, 2, true); + LogRegister(String("s1"), generalPurposeRegisters->S1, 2, false); + + LogRegister(String("a0"), generalPurposeRegisters->A0, 2, true); + LogRegister(String("a1"), generalPurposeRegisters->A1, 2, true); + LogRegister(String("a2"), generalPurposeRegisters->A2, 2, false); + + LogRegister(String("a3"), generalPurposeRegisters->A3, 2, true); + LogRegister(String("a4"), generalPurposeRegisters->A4, 2, true); + LogRegister(String("a5"), generalPurposeRegisters->A5, 2, false); + + LogRegister(String("a6"), generalPurposeRegisters->A6, 2, true); + LogRegister(String("a7"), generalPurposeRegisters->A7, 2, true); + LogRegister(String("s2"), generalPurposeRegisters->S2, 2, false); + + LogRegister(String("s3"), generalPurposeRegisters->S3, 2, true); + LogRegister(String("s4"), generalPurposeRegisters->S4, 2, true); + LogRegister(String("s5"), generalPurposeRegisters->S5, 2, false); + + LogRegister(String("s6"), generalPurposeRegisters->S6, 2, true); + LogRegister(String("s7"), generalPurposeRegisters->S7, 2, true); + LogRegister(String("s8"), generalPurposeRegisters->S8, 2, false); + + LogRegister(String("s9"), generalPurposeRegisters->S9, 2, true); + LogRegister(String("s10"), generalPurposeRegisters->S10, 1, true); + LogRegister(String("s11"), generalPurposeRegisters->S11, 1, false); + + LogRegister(String("t3"), generalPurposeRegisters->T3, 2, true); + LogRegister(String("t4"), generalPurposeRegisters->T4, 2, true); + LogRegister(String("t5"), generalPurposeRegisters->T5, 2, false); + + LogRegister(String("t6"), generalPurposeRegisters->T6, 2, false); +} + +void LogSupervisorRegisters(const SupervisorRegisters* supervisorRegisters) +{ + LogRegister(String("sepc"), supervisorRegisters->Epc, 3, true); + LogRegister(String("sstatus"), supervisorRegisters->Status, 1, true); + LogRegister(String("sscratch"), supervisorRegisters->Scratch, 1, false); + + LogRegister(String("scause"), supervisorRegisters->Cause, 1, true); + LogRegister(String("stval"), supervisorRegisters->TrapValue, 3, false); +} + +void CpuLogTrapFrame(const CpuTrapFrame* trapFrame) +{ + KernelConsoleSetForegroundColor(KernelConsoleColorInfo); + KernelConsolePrintBoxMessage(String("Trap Frame")); + KernelConsoleResetStyle(); + + KernelConsoleSetForegroundColor(KernelConsoleColorHighlight); + KernelConsolePrint(String("General Purpose Registers:\n")); + KernelConsoleResetStyle(); + LogGeneralPurposeRegisters(&trapFrame->GeneralPurposeRegisters); + + KernelConsoleSetForegroundColor(KernelConsoleColorHighlight); + KernelConsolePrint(String("\nSupervisor Registers:\n")); + KernelConsoleResetStyle(); + LogSupervisorRegisters(&trapFrame->SupervisorRegisters); +} + +CpuTrapCause CpuTrapFrameGetCause(const CpuTrapFrame* trapFrame) +{ + auto isInterrupt = (trapFrame->SupervisorRegisters.Cause >> (sizeof(uintptr_t) * 8 - 1) > 0); + auto causeCode = trapFrame->SupervisorRegisters.Cause & ((1ULL<<((sizeof(uintptr_t)*8)-1))-1); + + auto interruptType = CpuInterruptType_None; + auto synchronousType = CpuTrapSynchronousType_Unknown; + + if (isInterrupt) + { + switch (causeCode) + { + case RISCV_INTERRUPT_SOFTWARE: + interruptType = CpuInterruptType_Software; + break; + + case RISCV_INTERRUPT_TIMER: + interruptType = CpuInterruptType_Timer; + break; + + case RISCV_INTERRUPT_EXTERNAL: + interruptType = CpuInterruptType_External; + break; + } + } + + else + { + switch (causeCode) + { + case RISCV_TRAP_SYNCHRONOUS_INSTRUCTION_ADDRESS_MISALIGNED: + case RISCV_TRAP_SYNCHRONOUS_INSTRUCTION_ACCESS_FAULT: + case RISCV_TRAP_SYNCHRONOUS_ILLEGAL_INSTRUCTION: + synchronousType = CpuTrapSynchronousType_InstructionError; + break; + + case RISCV_TRAP_SYNCHRONOUS_BREAKPOINT: + synchronousType = CpuTrapSynchronousType_Debug; + break; + + case RISCV_TRAP_LOAD_ADDRESS_MISALIGNED: + case RISCV_TRAP_LOAD_ACCESS_FAULT: + case RISCV_TRAP_STORE_AMO_ADDRESS_MISALIGNED: + case RISCV_TRAP_STORE_AMO_ACCESS_FAULT: + synchronousType = CpuTrapSynchronousType_AddressError; + break; + + case RISCV_TRAP_ECALL_FROM_USER_MODE: + case RISCV_TRAP_ECALL_FROM_SUPERVISOR_MODE: + synchronousType = CpuTrapSynchronousType_SystemCall; + break; + + case RISCV_TRAP_INSTRUCTION_PAGE_FAULT: + case RISCV_TRAP_LOAD_PAGE_FAULT: + case RISCV_TRAP_STORE_AMO_PAGE_FAULT: + synchronousType = CpuTrapSynchronousType_PageError; + break; + + case RISCV_TRAP_SOFTWARE_CHECK: + synchronousType = CpuTrapSynchronousType_IntegrityError; + break; + + case RISCV_TRAP_HARDWARE_ERROR: + synchronousType = CpuTrapSynchronousType_HardwareError; + break; + } + } + + return (CpuTrapCause) + { + .Type = isInterrupt ? CpuTrapType_Interrupt : CpuTrapType_Synchronous, + .InterruptType = interruptType, + .SynchronousType = synchronousType, + .Code = trapFrame->SupervisorRegisters.Cause, + .ExtraInformation = trapFrame->SupervisorRegisters.TrapValue + }; } inline uintptr_t CpuTrapFrameGetProgramCounter(const CpuTrapFrame* trapFrame) { - return trapFrame->PC; + return trapFrame->SupervisorRegisters.Epc; +} + +void CpuTrapFrameSetProgramCounter(CpuTrapFrame* trapFrame, uintptr_t value) +{ + trapFrame->SupervisorRegisters.Epc = value; } diff --git a/src/Kernel/Platforms/RiscV/KernelTrapEntry.S b/src/Kernel/Platforms/RiscV/KernelTrapEntry.S new file mode 100644 index 0000000..b718b9f --- /dev/null +++ b/src/Kernel/Platforms/RiscV/KernelTrapEntry.S @@ -0,0 +1,40 @@ +#include "AsmCommon.h" + +.global kernel_trap_entry # TODO: Rename + +.section .text.interrupt + +kernel_trap_entry: + # TODO: We will need to do a switch later when we manage MMU + # to give the trap handler a kernel space stack independant of user mode. + # For now, we just save the current SP that is shared between kernel and user. + csrw sscratch, sp + + # TODO: For now we are saving all the Gprs but it should be revised later + # to optimize + addi sp, sp, -(36 * PTR_SIZE) + save_general_purpose_registers sp + + mv t0, sp + addi t0, t0, 31 * PTR_SIZE + save_supervisor_registers t0 + + la t1, globalCpuTrapHandler + load_pointer t1, (t1) + + mv a0, sp + jalr t1 + + load_general_purpose_registers sp + + mv t0, sp + addi t0, t0, 31 * PTR_SIZE + load_supervisor_registers t0 + + addi sp, sp, (36 * PTR_SIZE) + + # TODO: When we will have a dedicated kernel stack per HART, + # We need to make sure to revert SP before swapping it back + csrr sp, sscratch + + sret diff --git a/src/Kernel/Platforms/RiscV/SupervisorTrapEntry.S b/src/Kernel/Platforms/RiscV/SupervisorTrapEntry.S deleted file mode 100644 index 08548c3..0000000 --- a/src/Kernel/Platforms/RiscV/SupervisorTrapEntry.S +++ /dev/null @@ -1,8 +0,0 @@ -#include "AsmCommon.h" - -.global supervisor_trap_entry - -.section .text.interrupt - -supervisor_trap_entry: - sret diff --git a/tests/Kernel/CpuTests.c b/tests/Kernel/CpuTests.c index a29022e..3b200c4 100644 --- a/tests/Kernel/CpuTests.c +++ b/tests/Kernel/CpuTests.c @@ -35,3 +35,71 @@ Test(Cpu, CpuReadCycle) TestAssertNotEquals(0, cycle2); TestAssertGreaterThan(cycle2, cycle1); } + +volatile bool hasTestTrapHandler_WithTimerInterruptRun = false; + +void TestTrapHandler_WithTimerInterrupt(CpuTrapFrame* trapFrame) +{ + hasTestTrapHandler_WithTimerInterruptRun = true; + + CpuClearPendingInterrupts(CpuInterruptType_Timer); + CpuDisableInterrupts(CpuInterruptType_All); + CpuSetTrapHandler(nullptr); + + auto trapCause = CpuTrapFrameGetCause(trapFrame); + + TestAssertEquals(CpuTrapType_Interrupt, trapCause.Type); + TestAssertEquals(CpuInterruptType_Timer, trapCause.InterruptType); +} + +Test(Cpu, CpuTrapHandler_WithTimerInterrupt_HasCorrectCause) +{ + // Arrange + CpuSetTrapHandler(TestTrapHandler_WithTimerInterrupt); + CpuEnableInterrupts(CpuInterruptType_Timer); + auto timerDeadline = CpuReadTime(); + + // Act + BiosSetTimer(timerDeadline); + + // Assert + const uint32_t maxIterations = 10; + uint32_t iterations = 0; + + while (!hasTestTrapHandler_WithTimerInterruptRun && iterations < maxIterations) + { + iterations++; + } + + TestAssertIsTrue(hasTestTrapHandler_WithTimerInterruptRun); +} + +bool hasTestTrapHandler_WithInvalidInstructionRun = false; + +void TestTrapHandler_WithInvalidInstruction(CpuTrapFrame* trapFrame) +{ + hasTestTrapHandler_WithInvalidInstructionRun = true; + + auto trapCause = CpuTrapFrameGetCause(trapFrame); + + TestAssertEquals(CpuTrapType_Synchronous, trapCause.Type); + TestAssertEquals(CpuTrapSynchronousType_InstructionError, trapCause.SynchronousType); + + auto programCounter = CpuTrapFrameGetProgramCounter(trapFrame); + auto nextInstructionAddress = CpuComputeNextInstructionAddress(programCounter); + CpuTrapFrameSetProgramCounter(trapFrame, nextInstructionAddress); +} + +Test(Cpu, CpuTrapHandler_WithInvalidInstruction_HasCorrectCause) +{ + // Arrange + CpuSetTrapHandler(TestTrapHandler_WithInvalidInstruction); + + // Act + CpuGenerateInvalidInstruction(); + + // Assert + TestAssertEquals(true, hasTestTrapHandler_WithInvalidInstructionRun); + + CpuSetTrapHandler(nullptr); +}