diff --git a/CMakeLists.txt b/CMakeLists.txt index 074ab89..ff2fd79 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -21,7 +21,7 @@ SET(PANTHEON_VERSION_MINOR 0) SET(PANTHEON_VERSION_PATCH 1) SET(POISON_MEMORY TRUE) -SET(FSANITIZE TRUE) +SET(FSANITIZE FALSE) CONFIGURE_FILE( "${CMAKE_SOURCE_DIR}/kern.h.in" diff --git a/Common/CMakeLists.txt b/Common/CMakeLists.txt index 45a84b2..dfee9fb 100644 --- a/Common/CMakeLists.txt +++ b/Common/CMakeLists.txt @@ -6,6 +6,7 @@ LIST(APPEND COMMON_HEADERS kern_datatypes.hpp) LIST(APPEND COMMON_HEADERS kern_runtime.hpp) LIST(APPEND COMMON_HEADERS kern_string.hpp) LIST(APPEND COMMON_HEADERS kern_object.hpp) +LIST(APPEND COMMON_HEADERS kern_rand.hpp) LIST(APPEND COMMON_HEADERS Sync/kern_spinlock.hpp) LIST(APPEND COMMON_HEADERS Sync/kern_atomic.hpp) LIST(APPEND COMMON_HEADERS Structures/kern_optional.hpp) @@ -13,8 +14,10 @@ LIST(APPEND COMMON_HEADERS Structures/kern_bitmap.hpp) LIST(APPEND COMMON_HEADERS Structures/kern_rawbitmap.hpp) LIST(APPEND COMMON_HEADERS Structures/kern_slab.hpp) LIST(APPEND COMMON_HEADERS Structures/kern_linkedlist.hpp) +LIST(APPEND COMMON_HEADERS Structures/kern_skiplist.hpp) +LIST(APPEND COMMON_SOURCES kern_rand.cpp) LIST(APPEND COMMON_SOURCES kern_runtime.cpp) LIST(APPEND COMMON_SOURCES kern_basic_malloc.cpp) LIST(APPEND COMMON_SOURCES kern_status.cpp) diff --git a/Common/Structures/kern_allocatable.hpp b/Common/Structures/kern_allocatable.hpp index 0a2e25e..945a662 100644 --- a/Common/Structures/kern_allocatable.hpp +++ b/Common/Structures/kern_allocatable.hpp @@ -18,7 +18,6 @@ class Allocatable static void Init() { AllocSpinlock = pantheon::Spinlock("Allocatable Lock"); - ClearBuffer((CHAR*)Items, sizeof(T) * Count); Allocator = pantheon::mm::SlabCache(Items, Count); } diff --git a/Common/Structures/kern_skiplist.hpp b/Common/Structures/kern_skiplist.hpp new file mode 100644 index 0000000..8b54ff5 --- /dev/null +++ b/Common/Structures/kern_skiplist.hpp @@ -0,0 +1,350 @@ +#include +#include + +#include +#include + +#ifndef _KERN_SKIPLIST_HPP_ +#define _KERN_SKIPLIST_HPP_ + +namespace pantheon +{ + +template +class SkipList +{ +public: + SkipList() + { + this->Head = nullptr; + this->Level = 0; + this->Sz = 0; + } + + ~SkipList() + { + /* NYI */ + } + + [[nodiscard]] UINT64 Size() const + { + return this->Sz; + } + + [[nodiscard]] BOOL Contains(K Key) const + { + return this->Get(Key).GetOkay(); + } + + [[nodiscard]] Optional Get(K Key) const + { + if (this->Head == nullptr) + { + return Optional(); + } + + INT16 Lvl = this->Level; + KVPair *Current = this->Head; + KVPair *Next = Current; + while (Lvl >= 0) + { + Next = Current->Next[Lvl]; + while (Next && (Next->Key <= Key)) + { + Current = Next; + Next = Current->Next[Lvl]; + } + Lvl--; + } + + if (Current->Key == Key) + { + return Optional(Current->Value); + } + return Optional(); + + } + + [[nodiscard]] Optional MinKey() const + { + if (this->Head == nullptr) + { + return Optional(); + } + + KVPair *Current = this->Head->Next[0]; + if (Current && Current->Key != (K() - 1)) + { + return Optional(Current->Key); + } + return Optional(); + } + + [[nodiscard]] Optional MaxKey() const + { + if (this->Head == nullptr) + { + return Optional(); + } + + KVPair *Current = this->Head->Prev[0]; + if (Current && Current->Key != (K() - 1)) + { + return Optional(Current->Key); + } + return Optional(); + } + + VOID Insert(K Key, V Value) + { + this->Setup(); + + KVPair *Update[MaxLvl]; + INT16 Lvl = this->Level; + + KVPair *Current = this->Head; + KVPair *Next = Current; + while (Lvl >= 0) + { + Next = Current->Next[Lvl]; + while (Next && (Next->Key <= Key)) + { + Current = Next; + Next = Current->Next[Lvl]; + } + Update[Lvl] = Current; + Lvl--; + } + + /* This is where we'll need to be: this needs to match the current key. */ + if (Current->Key == Key) + { + Current->Value = Value; + return; + } + + /* Actually make the new entry */ + INT16 NewLvl = this->RandLevel(); + if (NewLvl > this->Level) + { + NewLvl = ++(this->Level); + Update[NewLvl] = this->Head; + } + + KVPair *NewNode = new KVPair(); + NewNode->Key = Key; + NewNode->Value = Value; + NewNode->Level = NewLvl; + for (UINT8 Index = 0; Index < MaxLvl; Index++) + { + NewNode->Next[Index] = nullptr; + NewNode->Prev[Index] = nullptr; + } + + while (NewLvl >= 0) + { + Current = Update[NewLvl]; + + NewNode->Next[NewLvl] = Current->Next[NewLvl]; + Current->Next[NewLvl] = NewNode; + + NewNode->Prev[NewLvl] = Current; + NewNode->Next[NewLvl]->Prev[NewLvl] = NewNode; + NewLvl--; + } + + this->Sz++; + + } + + BOOL Delete(K Key) + { + this->Setup(); + + KVPair *Update[MaxLvl]; + INT16 Lvl = this->Level; + + KVPair *Current = this->Head; + KVPair *Next = Current; + while (Lvl >= 0) + { + Next = Current->Next[Lvl]; + while (Next && (Next->Key <= Key)) + { + Current = Next; + Next = Current->Next[Lvl]; + } + Update[Lvl] = Current; + Lvl--; + } + + /* This is where we'll need to be: this needs to match the current key. */ + if (Current->Key == Key) + { + UINT8 CurLvl = Current->Level; + for (UINT8 Lvl = 0; Lvl <= CurLvl; Lvl++) + { + Current->Prev[Lvl]->Next[Lvl] = Current->Next[Lvl]; + Current->Next[Lvl]->Prev[Lvl] = Current->Prev[Lvl]; + } + + /* If this was top, we might have to fix levels. */ + if (CurLvl == this->Level) + { + while (this->Head->Next[CurLvl] == this->Head && this->Head->Prev[CurLvl] == this->Head) + { + if (CurLvl > 0) + { + CurLvl--; + } else + { + break; + } + + } + this->Level = CurLvl; + } + + this->Sz--; + delete Current; + return TRUE; + } + return FALSE; + } + + Optional Pop(K Key) + { + this->Setup(); + + KVPair *Update[MaxLvl]; + INT16 Lvl = this->Level; + + KVPair *Current = this->Head; + KVPair *Next = Current; + while (Lvl >= 0) + { + Next = Current->Next[Lvl]; + while (Next && (Next->Key <= Key)) + { + Current = Next; + Next = Current->Next[Lvl]; + } + Update[Lvl] = Current; + Lvl--; + } + + /* This is where we'll need to be: this needs to match the current key. */ + if (Current->Key == Key) + { + UINT8 CurLvl = Current->Level; + for (UINT8 Lvl = 0; Lvl <= CurLvl; Lvl++) + { + Current->Prev[Lvl]->Next[Lvl] = Current->Next[Lvl]; + Current->Next[Lvl]->Prev[Lvl] = Current->Prev[Lvl]; + } + + /* If this was top, we might have to fix levels. */ + if (CurLvl == this->Level) + { + while (this->Head->Next[CurLvl] == this->Head && this->Head->Prev[CurLvl] == this->Head) + { + if (CurLvl > 0) + { + CurLvl--; + } else + { + break; + } + + } + this->Level = CurLvl; + } + + this->Sz--; + if (Current->Key == Key) + { + return Optional(Current->Value); + } + return Optional(); + } + return Optional(); + } + + + V& operator[](K Key) + { + this->Setup(); + + INT16 Lvl = this->Level; + KVPair *Current = this->Head; + KVPair *Next = Current; + while (Lvl >= 0) + { + Next = Current->Next[Lvl]; + while (Next && (Next->Key <= Key)) + { + Current = Next; + Next = Current->Next[Lvl]; + } + Lvl--; + } + + if (Current->Key == Key) + { + return Current->Value; + } + static V DefaultItem; + return DefaultItem; + } + +private: + struct KVPair + { + K Key; + V Value; + UINT64 Level; + KVPair *Next[MaxLvl]; + KVPair *Prev[MaxLvl]; + }; + + static_assert(sizeof(KVPair) > 32); + + KVPair *Head; + INT16 Level; + UINT64 Sz; + + UINT8 RandLevel() + { + UINT8 Level = 1; + + /* Rand() generates a 64-bit number. + * The probability of any one bit in particular being 0 or 1 + * is 25% for either case. We only care about two bits, + * so let's use the bottom 2 bits for this. + */ + while ((pantheon::Rand() & 0x3) && Level < MaxLvl) + { + Level++; + } + return Level % MaxLvl; + } + + void Setup() + { + if (this->Head == nullptr) + { + this->Head = new KVPair(); + this->Level = 0; + this->Head->Key = K() - 1; /* HACK: Sets this at a huge value for integers only */ + this->Head->Value = V(); + for (UINT8 Lvl = 0; Lvl < MaxLvl; Lvl++) + { + this->Head->Next[Lvl] = this->Head; + this->Head->Prev[Lvl] = this->Head; + } + } + } +}; + +} +#endif \ No newline at end of file diff --git a/Common/Structures/kern_slab.hpp b/Common/Structures/kern_slab.hpp index 7a97285..7741d7f 100644 --- a/Common/Structures/kern_slab.hpp +++ b/Common/Structures/kern_slab.hpp @@ -21,9 +21,7 @@ template class SlabCache { public: - /* Implicitly assume that sizeof(T) >= sizeof(SlabCache) */ - - constexpr SlabCache() + SlabCache() { this->Size = 0; this->Used = 0; @@ -31,7 +29,7 @@ class SlabCache this->Area = nullptr; } - constexpr SlabCache(VOID *Area, UINT16 Count = 64) + SlabCache(VOID *Area, UINT16 Count = 64) { this->Area = reinterpret_cast(Area); #if POISON_MEMORY @@ -59,10 +57,7 @@ class SlabCache this->FreeList = reinterpret_cast*>(this->Area); } - constexpr ~SlabCache() - { - - } + constexpr ~SlabCache() = default; BOOL InRange(T *Ptr) { @@ -93,6 +88,16 @@ class SlabCache T* NewArea = reinterpret_cast(Current); this->FreeList = Next; this->Used++; +#ifdef POISON_MEMORY + for (UINT64 Address = (UINT64)Current + sizeof(SlabNext); Address < ((UINT64)Current + sizeof(T)); Address++) + { + UINT8 *AsBytes = (UINT8*)Address; + if (*AsBytes != 0xDF) + { + pantheon::StopErrorFmt("FOUND MEMORY CORRUPTION IN ALLOCATOR (%lx, address %lx)\n", this, Address); + } + } +#endif *NewArea = T(); return NewArea; } @@ -115,6 +120,17 @@ class SlabCache T* NewArea = reinterpret_cast(Current); this->FreeList = Next; this->Used++; + +#ifdef POISON_MEMORY + for (UINT64 Address = (UINT64)Current + sizeof(SlabNext); Address < ((UINT64)Current + sizeof(T)); Address++) + { + UINT8 *AsBytes = (UINT8*)Address; + if (*AsBytes != 0xDF) + { + pantheon::StopErrorFmt("FOUND MEMORY CORRUPTION IN ALLOCATOR (%lx, address %lx)\n", this, Address); + } + } +#endif return NewArea; } return nullptr; @@ -134,6 +150,9 @@ class SlabCache UINT64 BasePtr = Base + (Area * sizeof(T)); if (BasePtr == (UINT64)Ptr) { +#ifdef POISON_MEMORY + SetBufferBytes((UINT8*)Ptr, 0xDF, sizeof(T)); +#endif SlabNext *Next = reinterpret_cast*>(Ptr); Next->Next = FreeList; this->FreeList = Next; diff --git a/Common/Sync/kern_atomic.hpp b/Common/Sync/kern_atomic.hpp index 7fe1b0f..4ae85bf 100644 --- a/Common/Sync/kern_atomic.hpp +++ b/Common/Sync/kern_atomic.hpp @@ -69,7 +69,90 @@ class Atomic private: T Content; +}; + +template +class AtomicInteger +{ + +public: + AtomicInteger() + { + this->Content = T(); + } + + AtomicInteger(T Item) + { + this->Store(Item); + } + + ~AtomicInteger() = default; + + [[nodiscard]] + T Load() const + { + T RetVal; + #ifndef ONLY_TESTS + __atomic_load(&(this->Content), &RetVal, __ATOMIC_SEQ_CST); + #else + RetVal = this->Content; + #endif + return RetVal; + } + + void Store(T Item) + { + #ifndef ONLY_TESTS + __atomic_store(&(this->Content), &Item, __ATOMIC_SEQ_CST); + #else + this->Content = Item; + #endif + } + T Add(T Item) + { + #ifndef ONLY_TESTS + return __atomic_fetch_add(&(this->Content), Item, __ATOMIC_SEQ_CST); + #else + this->Content += Item; + return this->Content; + #endif + } + + T Sub(T Item) + { + #ifndef ONLY_TESTS + return __atomic_fetch_sub(&(this->Content), Item, __ATOMIC_SEQ_CST); + #else + this->Content -= Item; + return this->Content; + #endif + } + + [[nodiscard]] + bool operator==(const Atomic &Other) const + { + return this->Load() == Other.Load(); + } + + [[nodiscard]] + bool operator==(const T &Other) const + { + return this->Load() == Other; + } + + void operator=(const T &Thing) + { + this->Store(Thing); + } + + void operator=(T &&Thing) + { + this->Store(Thing); + } + +private: + T Content; }; } diff --git a/Common/Sync/kern_lockable.hpp b/Common/Sync/kern_lockable.hpp index e7d6dc7..db7744f 100644 --- a/Common/Sync/kern_lockable.hpp +++ b/Common/Sync/kern_lockable.hpp @@ -41,6 +41,12 @@ class Lockable this->ObjLock.Release(); } + BOOL TryLock() + { + OBJECT_SELF_ASSERT(); + return this->ObjLock.TryAcquire(); + } + [[nodiscard]] BOOL IsLocked() const { diff --git a/Common/Sync/kern_spinlock.cpp b/Common/Sync/kern_spinlock.cpp index 8def2f4..126c783 100644 --- a/Common/Sync/kern_spinlock.cpp +++ b/Common/Sync/kern_spinlock.cpp @@ -29,15 +29,17 @@ pantheon::Spinlock::~Spinlock() void pantheon::Spinlock::Acquire() { + OBJECT_SELF_ASSERT(); + if (this->DebugName == nullptr) { - StopError("bad spinlock", this); + StopErrorFmt("Bad Spinlock: was nullptr\n"); } pantheon::CPU::PUSHI(); if (this->IsHolding()) { - StopError(this->DebugName, this); + StopErrorFmt("Spinlock: %s (source %p), trying to acquire when already held", this->DebugName, this); } for (;;) @@ -55,16 +57,51 @@ void pantheon::Spinlock::Acquire() __sync_synchronize(); } +BOOL pantheon::Spinlock::TryAcquire() +{ + OBJECT_SELF_ASSERT(); + + if (this->DebugName == nullptr) + { + StopErrorFmt("Bad Spinlock: was nullptr\n"); + } + + pantheon::CPU::PUSHI(); + if (this->IsHolding()) + { + StopErrorFmt("Spinlock: %s (source %p), trying to (try) acquire when already held", this->DebugName, this); + } + + if (__sync_lock_test_and_set(&this->Locked, TRUE) == FALSE) + { + this->CoreNo = pantheon::CPU::GetProcessorNumber(); + __sync_synchronize(); + return TRUE; + } + + while (__atomic_load_n(&this->Locked, __ATOMIC_RELAXED)) + { + pantheon::CPU::PAUSE(); + } + __sync_synchronize(); + pantheon::CPU::POPI(); + return FALSE; +} + void pantheon::Spinlock::Release() { + OBJECT_SELF_ASSERT(); + + __sync_synchronize(); if (!this->IsHolding()) { - pantheon::StopError(this->DebugName, this); + StopErrorFmt("Spinlock: %s (source %p), trying to release when not held (holder is %hd)\n", this->DebugName, this, this->Holder()); } this->CoreNo = -1; __sync_synchronize(); __sync_lock_release(&this->Locked); pantheon::CPU::POPI(); + __sync_synchronize(); } @@ -82,12 +119,12 @@ BOOL pantheon::Spinlock::IsLocked() const /** * \~english @brief Gets the current holder of this lock, if it is held. - * \~english @details Returns 0 is this lock isn't held, otherwise gets the core number. + * \~english @details Returns -1 is this lock isn't held, otherwise gets the core number. * * \~english @author Brian Schnepp */ [[nodiscard]] -UINT8 pantheon::Spinlock::Holder() const +INT16 pantheon::Spinlock::Holder() const { - return (this->Locked ? this->CoreNo : 0); + return (this->Locked ? this->CoreNo : static_cast(-1)); } \ No newline at end of file diff --git a/Common/Sync/kern_spinlock.hpp b/Common/Sync/kern_spinlock.hpp index ba4cb34..f982af6 100644 --- a/Common/Sync/kern_spinlock.hpp +++ b/Common/Sync/kern_spinlock.hpp @@ -15,8 +15,9 @@ class Spinlock void Acquire(); void Release(); + BOOL TryAcquire(); - [[nodiscard]] UINT8 Holder() const; + [[nodiscard]] INT16 Holder() const; [[nodiscard]] BOOL IsLocked() const; void SetDebugName(const char *Name); @@ -25,7 +26,7 @@ class Spinlock private: [[nodiscard]] BOOL IsHolding() const; const char *DebugName; - UINT16 CoreNo; + INT16 CoreNo; BOOL Locked; }; diff --git a/Common/kern_basic_malloc.cpp b/Common/kern_basic_malloc.cpp index 0aa2ebe..e9d5f22 100644 --- a/Common/kern_basic_malloc.cpp +++ b/Common/kern_basic_malloc.cpp @@ -4,6 +4,9 @@ #include #include + +#include + typedef struct FreeList { struct FreeList *Prev; @@ -12,7 +15,32 @@ typedef struct FreeList /* Try to align to 32-bytes, so that allocations can be bitpacked nicely. */ typedef UINT64 BlockHeader; -static constexpr UINT64 HeapSpace = 2ULL * 1024ULL * 1024ULL; + +typedef struct TinyContent +{ + UINT64 Data[4]; +}TinyContent; + +typedef struct SmallContent +{ + UINT64 Data[0x60]; +}SmallContent; + +typedef struct LargeContent +{ + UINT64 Data[0x1000]; +}LargeContent; + +static constexpr UINT64 HeapSpace = 512ULL * 1024ULL; + +alignas(0x1000) static TinyContent TinySlabCache[2 * 1024]; +alignas(0x1000) static SmallContent SmallSlabCache[256]; +alignas(0x1000) static LargeContent LargeSlabCache[8]; + +static pantheon::mm::SlabCache SlabTinyContent((VOID*)TinySlabCache, 2 * 1024); +static pantheon::mm::SlabCache SlabSmallContent((VOID*)SmallSlabCache, 256); +static pantheon::mm::SlabCache SlabLargeContent((VOID*)LargeSlabCache, 8); + static constexpr UINT64 MinBlockSize = sizeof(FreeList) + (2 * sizeof(BlockHeader)); COMPILER_ASSERT(MinBlockSize == 32); @@ -155,6 +183,23 @@ Optional BasicMalloc(UINT64 Amt) } AllocLock.Acquire(); + if (Amt <= sizeof(TinyContent) && !SlabTinyContent.Full()) + { + TinyContent *Content = SlabTinyContent.Allocate(); + AllocLock.Release(); + return Optional(Content); + } else if (Amt <= sizeof(SmallContent) && !SlabSmallContent.Full()) { + SmallContent *Content = SlabSmallContent.Allocate(); + AllocLock.Release(); + return Optional(Content); + } else if (Amt <= sizeof(LargeContent) && !SlabLargeContent.Full()) { + LargeContent *Content = SlabLargeContent.Allocate(); + AllocLock.Release(); + return Optional(Content); + } + + /* Give up and use original explicit free list implementation */ + UINT64 Amount = Max(Align(Amt, (sizeof(BlockHeader))), MinBlockSize); FreeList *Final = nullptr; @@ -200,7 +245,22 @@ void BasicFree(void *Addr) } AllocLock.Acquire(); - + + if (SlabTinyContent.InRange((TinyContent*)Addr)) + { + SlabTinyContent.Deallocate((TinyContent*)Addr); + AllocLock.Release(); + return; + } else if (SlabSmallContent.InRange((SmallContent*)Addr)) { + SlabSmallContent.Deallocate((SmallContent*)Addr); + AllocLock.Release(); + return; + } else if (SlabLargeContent.InRange((LargeContent*)Addr)) { + SlabLargeContent.Deallocate((LargeContent*)Addr); + AllocLock.Release(); + return; + } + /* Ensure the current block is consistent, ie, no double frees */ CHAR *ActualAddr = (CHAR*)Addr; if (GetAlloc(GetHeader((CHAR*)Addr)) == FALSE) @@ -251,5 +311,5 @@ void *operator new(UINT64 Sz) void operator delete(void *Ptr) { - PANTHEON_UNUSED(Ptr); + BasicFree(Ptr); } \ No newline at end of file diff --git a/Common/kern_rand.cpp b/Common/kern_rand.cpp new file mode 100644 index 0000000..4bfe9c1 --- /dev/null +++ b/Common/kern_rand.cpp @@ -0,0 +1,55 @@ +#include + +#include + +#include +#include +#include + +/* Do something about this at some point... */ +static pantheon::Spinlock RNGLock("Random Number Lock"); + +/** + * @brief An implementation of Wichmann-Hill random number generation + * @return A (cryptographically insecure) random number + */ +UINT64 pantheon::Rand() +{ + RNGLock.Acquire(); + + /* Arbitrary seeds. These were randomly generated. */ + static UINT64 Seeds[3] = + { + 0x5648AD4DA64862EB, + 0xF7293DDAD5921465, + 0xC2743D545EDD6E10 + }; + + static const UINT64 Mults[3] = + { + 0x043186A3, + 0xA74CFF4D, + 0xDE834306, + }; + + static const UINT64 Additions[3] = + { + 0xC14A13C7025F69E3, + 0xD398EB8584DBF6D2, + 0x2C1AF76BB6FADB0E, + }; + + /* Use jiffies to help make this more random */ + UINT64 Jiffies = pantheon::CPU::GetJiffies() % 16; + for (UINT64 Counter = 0; Counter < Jiffies; Counter++) + { + for (UINT8 Index = 0; Index < 3; Index++) + { + Seeds[Index] = Jiffies + (Mults[Index] * Seeds[Index] + Additions[Index]); + } + } + + UINT64 Result = Seeds[0] ^ Seeds[1] ^ Seeds[2]; + RNGLock.Release(); + return Result; +} \ No newline at end of file diff --git a/Common/kern_rand.hpp b/Common/kern_rand.hpp new file mode 100644 index 0000000..d0dd139 --- /dev/null +++ b/Common/kern_rand.hpp @@ -0,0 +1,13 @@ +#include + +#ifndef _KERN_RAND_HPP_ +#define _KERN_RAND_HPP_ + +namespace pantheon +{ + +UINT64 Rand(); + +} + +#endif \ No newline at end of file diff --git a/Common/kern_runtime.cpp b/Common/kern_runtime.cpp index 74c3e1d..e647fa8 100644 --- a/Common/kern_runtime.cpp +++ b/Common/kern_runtime.cpp @@ -23,8 +23,8 @@ void SERIAL_LOG_UNSAFE(const char *Fmt, ...) va_end(Args); } -static pantheon::Spinlock PanicMutex; -static pantheon::Spinlock PrintMutex; +static pantheon::Spinlock PanicMutex("Panic Mutex"); +static pantheon::Spinlock PrintMutex("Print Mutex"); void SERIAL_LOG(const char *Fmt, ...) { @@ -50,16 +50,16 @@ void pantheon::StopError(const char *Reason, void *Source) { if (Source) { - SERIAL_LOG_UNSAFE("panic: %s [source: %lx, core %x]\n", Reason, Source, pantheon::CPU::GetProcessorNumber()); + SERIAL_LOG("panic: %s [source: %lx, core %x]\n", Reason, Source, pantheon::CPU::GetProcessorNumber()); } else { - SERIAL_LOG_UNSAFE("panic: %s [core %lx]\n", Reason, pantheon::CPU::GetProcessorNumber()); + SERIAL_LOG("panic: %s [core %lx]\n", Reason, pantheon::CPU::GetProcessorNumber()); } } else { - SERIAL_LOG_UNSAFE("panic: %s [core %lx]\n", "unknown reason", pantheon::CPU::GetProcessorNumber()); + SERIAL_LOG("panic: %s [core %lx]\n", "unknown reason", pantheon::CPU::GetProcessorNumber()); } /* TODO: stop other cores */ @@ -73,7 +73,7 @@ void pantheon::StopError(const char *Reason, void *Source) void pantheon::StopErrorFmt(const char *Fmt, ...) { PanicMutex.Acquire(); - SERIAL_LOG_UNSAFE("panic: "); + SERIAL_LOG("panic: "); va_list Args; va_start(Args, Fmt); vprintf(Fmt, Args); @@ -93,8 +93,6 @@ BOOL pantheon::Panicked() void pantheon::InitBasicRuntime() { - PrintMutex = pantheon::Spinlock("print_mutex"); - PanicMutex = pantheon::Spinlock("panic_mutex"); } void _putchar(char c) diff --git a/Common/kern_runtime.hpp b/Common/kern_runtime.hpp index 1e50ef4..29d638e 100644 --- a/Common/kern_runtime.hpp +++ b/Common/kern_runtime.hpp @@ -219,34 +219,6 @@ static inline void SetBufferBytes(UINT8 *Location, UINT8 Value, UINT32 Amount) } UINT64 Index = 0; - /* Enforce alignment */ - UINT64 ILocation = reinterpret_cast(Location); - if ((ILocation & 0x07) == 0) - { - UINT64 *AsUINT64 = (UINT64*)Location; - UINT64 NValue = ((UINT64)Value << 8*7) | ((UINT64)Value << 8*6) - | ((UINT64)Value << 8*5) | ((UINT64)Value << 8*4) - | ((UINT64)Value << 8*3) | ((UINT64)Value << 8*2) - | ((UINT64)Value << 8) | (UINT64)Value; - - for (Index = 0; Index < Amount; Index += 64) - { - AsUINT64[(Index + 0) / 8] = NValue; - AsUINT64[(Index + 1) / 8] = NValue; - AsUINT64[(Index + 2) / 8] = NValue; - AsUINT64[(Index + 3) / 8] = NValue; - AsUINT64[(Index + 4) / 8] = NValue; - AsUINT64[(Index + 5) / 8] = NValue; - AsUINT64[(Index + 6) / 8] = NValue; - AsUINT64[(Index + 7) / 8] = NValue; - } - - for (; Index < Amount; Index += 8) - { - AsUINT64[Index / 8] = NValue; - } - } - for (; Index < Amount; ++Index) { Location[Index] = Value; @@ -325,5 +297,6 @@ BOOL Panicked(); } #define OBJECT_SELF_ASSERT() if ((this) == nullptr) { StopError("called method was nullptr"); } +#define PANIC_ON(cond, string) if (cond) { pantheon::StopError(string); } #endif \ No newline at end of file diff --git a/System/Exec/kern_initialprograms.cpp b/System/Exec/kern_initialprograms.cpp index 08beded..32c7c8d 100644 --- a/System/Exec/kern_initialprograms.cpp +++ b/System/Exec/kern_initialprograms.cpp @@ -81,7 +81,7 @@ static void RunElf(ELFFileHeader64 Header, const char *ElfLocation, UINT32 Proc, UEntry.SetAccessor(pantheon::vmm::PAGE_MISC_ACCESSED); UEntry.SetMAIREntry(pantheon::vmm::MAIREntry_1); - pantheon::GlobalScheduler::MapPages(Proc, &TargetVAddr, &NewPage, UEntry, 1); + pantheon::Scheduler::MapPages(Proc, &TargetVAddr, &NewPage, UEntry, 1); } } } @@ -100,9 +100,12 @@ static void RunExecutable(void *ElfLocation, const char *Name) } pantheon::vmm::VirtualAddress Base = pantheon::GenerateALSRBase(); - UINT32 PID = pantheon::GlobalScheduler::CreateProcess(Name, (void*)(Base + Header().e_entry)); + UINT32 PID = pantheon::Scheduler::CreateProcess(Name, (void*)(Base + Header().e_entry)); RunElf(Header(), (const char*)ElfLocation, PID, Base); - pantheon::GlobalScheduler::RunProcess(PID); + pantheon::Scheduler::SetState(PID, pantheon::Process::STATE_RUNNING); + + /* Create our initial thread */ + pantheon::Scheduler::CreateThread(PID, (void*)(Base + Header().e_entry), nullptr); } void pantheon::UnpackInitPrograms() diff --git a/System/Exec/kern_proc_alsr.cpp b/System/Exec/kern_proc_alsr.cpp index b33aec4..2bef97a 100644 --- a/System/Exec/kern_proc_alsr.cpp +++ b/System/Exec/kern_proc_alsr.cpp @@ -1,18 +1,10 @@ #include #include +#include #include pantheon::vmm::VirtualAddress pantheon::GenerateALSRBase() { - /* TODO: Grab some real randomness. This is still - * deterministic, but that's probably fine (for now). - */ - static pantheon::vmm::VirtualAddress Seed = pantheon::ALSRMaxAddress; - - /* Arbitrary constants */ - Seed *= 0xBF83525; - Seed += 0x8DD1FC7; - - return (Seed % pantheon::ALSRMaxAddress) & ~(pantheon::ALSRMask - 1); + return (pantheon::Rand() % pantheon::ALSRMaxAddress) & ~(pantheon::ALSRMask - 1); } \ No newline at end of file diff --git a/System/Proc/kern_cpu.cpp b/System/Proc/kern_cpu.cpp index 539089e..4ee4a3a 100644 --- a/System/Proc/kern_cpu.cpp +++ b/System/Proc/kern_cpu.cpp @@ -14,47 +14,39 @@ #include /* Avoid having too high a number of cores to look through. */ +static atomic_uint_fast8_t NoCPUs = 0; static pantheon::CPU::CoreInfo PerCoreInfo[MAX_NUM_CPUS]; -pantheon::CPU::CoreInfo *pantheon::CPU::GetCoreInfo() +void pantheon::CPU::InitCore(UINT8 CoreNo) { - return &(PerCoreInfo[pantheon::CPU::GetProcessorNumber()]); + PerCoreInfo[CoreNo] = {}; + PerCoreInfo[CoreNo].LocalSched = pantheon::LocalScheduler::Create(); + NoCPUs++; + + /* This is needed since kernel constructors happen to be ordered + * in a way that could cause a problem for our initialization order... + */ + PerCoreInfo[CoreNo].LocalSched->Setup(); +} + +UINT8 pantheon::CPU::GetNoCPUs() +{ + return NoCPUs; } -/** - * \~english @brief Initializes a CoreInfo structure. - * \~english @details Prepares a CoreInfo struct by initializing its - * basic variables to an idle state, signalling that it is ready to have - * a scheduler assigned to it to begin processing threads. - * - * \~english @author Brian Schnepp - */ -void pantheon::CPU::InitCoreInfo(UINT8 CoreNo) +pantheon::CPU::CoreInfo *pantheon::CPU::GetCoreInfo() { - static pantheon::Scheduler Scheds[MAX_NUM_CPUS]; - ClearBuffer((CHAR*)&PerCoreInfo[CoreNo], sizeof(pantheon::CPU::CoreInfo)); - PerCoreInfo[CoreNo].CurSched = &Scheds[CoreNo]; - (*PerCoreInfo[CoreNo].CurSched) = pantheon::Scheduler(); + return &(PerCoreInfo[pantheon::CPU::GetProcessorNumber()]); } pantheon::Thread *pantheon::CPU::GetCurThread() { - pantheon::Scheduler *Sched = pantheon::CPU::GetCurSched(); - if (Sched == nullptr) - { - return nullptr; - } - return Sched->MyThread(); + return PerCoreInfo[pantheon::CPU::GetProcessorNumber()].CurThread; } pantheon::Process *pantheon::CPU::GetCurProcess() { - pantheon::Scheduler *Sched = pantheon::CPU::GetCurSched(); - if (Sched == nullptr) - { - return nullptr; - } - pantheon::Thread *CurThread = Sched->MyThread(); + pantheon::Thread *CurThread = pantheon::CPU::GetCurThread(); if (CurThread) { return CurThread->MyProc(); @@ -62,9 +54,9 @@ pantheon::Process *pantheon::CPU::GetCurProcess() return nullptr; } -pantheon::Scheduler *pantheon::CPU::GetCurSched() +UINT64 pantheon::CPU::GetJiffies() { - return pantheon::CPU::GetCoreInfo()->CurSched; + return pantheon::CPU::GetCoreInfo()->LocalJiffies; } pantheon::TrapFrame *pantheon::CPU::GetCurFrame() @@ -72,6 +64,20 @@ pantheon::TrapFrame *pantheon::CPU::GetCurFrame() return pantheon::CPU::GetCoreInfo()->CurFrame; } +pantheon::LocalScheduler *pantheon::CPU::GetLocalSched(UINT8 ProcNo) +{ + if (ProcNo >= pantheon::CPU::GetNoCPUs()) + { + return nullptr; + } + return PerCoreInfo[ProcNo].LocalSched; +} + +pantheon::LocalScheduler *pantheon::CPU::GetMyLocalSched() +{ + return pantheon::CPU::GetLocalSched(pantheon::CPU::GetProcessorNumber()); +} + void *pantheon::CPU::GetStackArea(UINT64 Core) { alignas(4096) static char StackArea[MAX_NUM_CPUS * DEFAULT_STACK_SIZE]; diff --git a/System/Proc/kern_cpu.hpp b/System/Proc/kern_cpu.hpp index 081c019..41eaea0 100644 --- a/System/Proc/kern_cpu.hpp +++ b/System/Proc/kern_cpu.hpp @@ -12,7 +12,7 @@ namespace pantheon class Thread; class Process; -class Scheduler; +class LocalScheduler; namespace CPU { @@ -35,21 +35,31 @@ namespace CPU */ typedef struct CoreInfo { - pantheon::Scheduler *CurSched; + pantheon::Thread *CurThread; + pantheon::Process *CurProcess; pantheon::TrapFrame *CurFrame; + pantheon::LocalScheduler *LocalSched; + + + UINT64 LocalJiffies; UINT64 NOff; BOOL IntStatus; }CoreInfo; -void InitCoreInfo(UINT8 CoreNo); +UINT8 GetNoCPUs(); +void InitCore(UINT8 CoreNo); + CoreInfo *GetCoreInfo(); UINT8 GetProcessorNumber(); pantheon::Thread *GetCurThread(); pantheon::Process *GetCurProcess(); -pantheon::Scheduler *GetCurSched(); pantheon::TrapFrame *GetCurFrame(); +pantheon::LocalScheduler *GetLocalSched(UINT8 ProcNo); +pantheon::LocalScheduler *GetMyLocalSched(); + +UINT64 GetJiffies(); VOID PUSHI(); VOID POPI(); diff --git a/System/Proc/kern_proc.cpp b/System/Proc/kern_proc.cpp index 166352c..dca41f3 100644 --- a/System/Proc/kern_proc.cpp +++ b/System/Proc/kern_proc.cpp @@ -63,7 +63,7 @@ void pantheon::Process::Initialize(const pantheon::ProcessCreateInfo &CreateInfo this->CurState = pantheon::Process::STATE_INIT; this->CurPriority = pantheon::Process::PRIORITY_NORMAL; this->ProcessString = CreateInfo.Name; - this->PID = pantheon::AcquireProcessID(); + this->PID = CreateInfo.ID; this->TTBR0 = pantheon::PageAllocator::Alloc(); this->EntryPoint = CreateInfo.EntryPoint; this->HandTable.Clear(); diff --git a/System/Proc/kern_proc.hpp b/System/Proc/kern_proc.hpp index 2435990..90d5550 100644 --- a/System/Proc/kern_proc.hpp +++ b/System/Proc/kern_proc.hpp @@ -39,6 +39,7 @@ typedef enum ProcessCreateFlags typedef struct ProcessCreateInfo { + UINT32 ID; String Name; pantheon::vmm::VirtualAddress EntryPoint; @@ -95,6 +96,7 @@ class Process : public pantheon::Object, public pantheon::Lockable static const constexpr UINT64 StackPages = 16; static const constexpr pantheon::vmm::VirtualAddress StackAddr = 0xFFFFFFFFF000; + static const constexpr pantheon::vmm::VirtualAddress ThreadLocalBase = 0xFFFFFF000000; /** * @brief Obtains the process ID for this process @@ -129,16 +131,8 @@ class Process : public pantheon::Object, public pantheon::Lockable */ static void Switch(Process *Next) { - if (Next == nullptr) - { - return; - } - - pantheon::Process *CurProc = pantheon::CPU::GetCurProcess(); - if (CurProc && CurProc != Next) - { - pantheon::CPUReg::W_TTBR0_EL1(Next->GetTTBR0()); - } + pantheon::CPUReg::W_TTBR0_EL1(Next->GetTTBR0()); + pantheon::CPU::GetCoreInfo()->CurProcess = Next; } private: diff --git a/System/Proc/kern_sched.cpp b/System/Proc/kern_sched.cpp index cb8993a..8df469f 100644 --- a/System/Proc/kern_sched.cpp +++ b/System/Proc/kern_sched.cpp @@ -15,470 +15,462 @@ #include #include +#include +#include #include -#ifndef ONLY_TESTING -extern "C" CHAR *USER_BEGIN; -extern "C" CHAR *USER_END; -#else -static UINT64 USER_BEGIN = 0; -static UINT64 USER_END = 0; -#endif - /** - * @file Common/Proc/kern_sched.cpp - * \~english @brief Definitions for basic kernel scheduling data structures and - * algorithms. The pantheon kernel implements a basic round-robin style scheduling - * algorithm based on tick counts and a list of threads. + * @file System/Proc/kern_sched.cpp + * \~english @brief Definitions for basic kernel scheduling data structures and algorithms. + * \~english @details Pantheon implements Con Kolivas' MuQSS scheduling algorithm, an efficient algorithm + * which takes priorities into account to provide a fair scheduling system to ensure + * fairness between processes. In contrast to BFS, lock contention is avoided by fairly distributing + * work between individual processors: in this case, threads are the fundamental schedulable unit, + * which is what is assigned virtual deadlines. + * Some details are either simplified or ignored: this implementation ignores nice levels, and ignores cache locality, SMT thread groups, + * and does not use a high precision timer (relying on jiffies wherever the corresponding Linux patch uses niffies), and lacks any runtime tunable parameters. + * @see http://ck.kolivas.org/patches/muqss/sched-MuQSS.txt * \~english @author Brian Schnepp */ -/** - * \~english @brief Initalizes an instance of a per-core scheduler. - * \~english @author Brian Schnepp - */ -pantheon::Scheduler::Scheduler() -{ - this->IdleThread = pantheon::GlobalScheduler::CreateProcessorIdleThread(); - this->CurThread = this->IdleThread; -} -pantheon::Scheduler::~Scheduler() -{ +static pantheon::Spinlock SchedLock("Global Scheduler Lock"); +static pantheon::SkipList ProcessList; +static pantheon::SkipList ThreadList; +pantheon::LocalScheduler::LocalScheduler() : pantheon::Lockable("Scheduler") +{ + /* Because of the ordering required for init_array, the actual + * setup must be done with the Setup() function, not the constructor. + */ + this->IdleThread = nullptr; + this->Ready = FALSE; } +/** + * @private + * @brief Provided by the arch/ subdir. Swaps the current context + */ extern "C" void cpu_switch(pantheon::CpuContext *Old, pantheon::CpuContext *New, UINT64 RegOffset); -/** - * \~english @brief Changes the current thread of this core. - * \~english @details Forcefully changes the current thread by iterating to - * the next thread in the list of threads belonging to this core. The active - * count for the current thread is set to zero, and the new thread is run until - * either the core is forcefully rescheduled, or the timer indicates the current - * thread should quit. - * - * \~english @author Brian Schnepp +/** + * @private + * @brief Provided by the arch/ subdir. Drops a new thread to have a different PSTATE */ -void pantheon::Scheduler::Reschedule() +extern "C" VOID drop_usermode(UINT64 PC, UINT64 PSTATE, UINT64 SP); + +UINT64 pantheon::LocalScheduler::CountThreads(UINT64 PID) { - OBJECT_SELF_ASSERT(); - /* Interrupts must be enabled before we can do anything. */ - if (pantheon::CPU::ICOUNT()) + if (!this->Ready) { - return; + this->Setup(); } - GlobalScheduler::Lock(); - pantheon::Thread *Old = this->CurThread; - pantheon::Thread *New = GlobalScheduler::PopFromReadyList(); - - /* If there is no next, just do the idle thread. */ - if (New == nullptr) + UINT64 Res = 0; + pantheon::ScopedLock _L(this); + for (const auto &Item : this->Threads) { - New = this->IdleThread; + Res += 1ULL * (Item.MyProc()->ProcessID() == PID); } + return Res; +} - /* Don't bother trying to switching threads if we don't have to. */ - New->Lock(); - if (New == Old) +pantheon::Thread *pantheon::LocalScheduler::AcquireThread() +{ + if (!this->Ready) { - New->Unlock(); - pantheon::GlobalScheduler::Unlock(); - return; + this->Setup(); } - Old->Lock(); - /* If it's not currently waiting, definitely don't. */ - if (New->MyState() != pantheon::Thread::STATE_WAITING) + if (!this->IsLocked()) { - New->Unlock(); - Old->Unlock(); - pantheon::GlobalScheduler::Unlock(); - return; + pantheon::StopErrorFmt("Trying to AcquireThread without lock\n"); } - pantheon::ScopedLocalSchedulerLock _L; - Old->SetState(pantheon::Thread::STATE_WAITING); - Old->RefreshTicks(); - + Optional LowestKey = this->LocalRunQueue.MinKey(); + if (!LowestKey.GetOkay()) + { + return nullptr; + } - pantheon::Process *NewProc = New->MyProc(); - pantheon::Process::Switch(NewProc); - this->CurThread = New; - this->CurThread->SetState(pantheon::Thread::STATE_RUNNING); + Optional Lowest = this->LocalRunQueue.Pop(LowestKey.GetValue()); + if (!Lowest.GetOkay()) + { + return nullptr; + } - pantheon::CpuContext *OldContext = Old->GetRegisters(); - pantheon::CpuContext *NewContext = New->GetRegisters(); + pantheon::Thread *Value = Lowest.GetValue(); + Value->Lock(); + return Value; - /* Update the Thread Local Area register */ - pantheon::ipc::SetThreadLocalRegion(New->GetThreadLocalAreaRegister()); +} - /* TODO: Make this better */ - pantheon::GlobalScheduler::AppendIntoReadyList(Old); - pantheon::GlobalScheduler::Unlock(); +/** + * @private + * @brief Obtains the idle thread + */ +pantheon::Thread *pantheon::LocalScheduler::Idle() +{ + if (!this->Ready) + { + this->Setup(); + } - Old->Unlock(); - New->Unlock(); + return this->IdleThread; +} - pantheon::Sync::DSBISH(); - pantheon::Sync::ISB(); - cpu_switch(OldContext, NewContext, CpuIRegOffset); +[[nodiscard]] +UINT64 pantheon::LocalScheduler::GetLowestDeadline() const +{ + Optional Val = this->LocalRunQueue.MinKey(); + return Val.GetOkay() ? Val.GetValue() : -1; } -pantheon::Process *pantheon::Scheduler::MyProc() +static UINT32 AcquireProcessID() { - OBJECT_SELF_ASSERT(); - if (this->CurThread) + UINT32 RetVal = 0; + static UINT32 ProcessID = 1; + + PANIC_ON(!SchedLock.IsLocked(), "Acquire process ID without locking Global Scheduler\n"); + + RetVal = ProcessID++; + while (ProcessList.Contains(RetVal) || RetVal == 0) { - return this->CurThread->MyProc(); + RetVal = ProcessID++; } - return nullptr; + + return RetVal; } -pantheon::Thread *pantheon::Scheduler::MyThread() +static UINT64 AcquireThreadID() { - OBJECT_SELF_ASSERT(); - return this->CurThread; -} + UINT64 RetVal = 0; + static UINT64 ThreadID = 1; -extern "C" VOID drop_usermode(UINT64 PC, UINT64 PSTATE, UINT64 SP); + PANIC_ON(!SchedLock.IsLocked(), "Acquire thread ID without locking Global Scheduler\n"); + + RetVal = ThreadID++; + /* Ban 0 and -1, since these are special values. */ + while (RetVal == 0 || RetVal == ~0ULL || ThreadList.Contains(RetVal)) + { + RetVal = ThreadID++; + } + return RetVal; +} /** - * \~english @brief Creates a process, visible globally, from a name and address. - * \~english @details A process is created, such that it can be run on any - * available processor on the system. The given process name identifies the - * process for debugging purposes and as a user-readable way to identify a - * process, and the start address is a pointer to the first instruction the - * initial threaq should execute when scheduled. - * \~english @param[in] ProcStr A human-readable name for the process - * \~english @param[in] StartAddr The initial value of the program counter - * \~english @return TRUE is the process was sucessfully created, false otherwise. - * \~english @author Brian Schnepp + * @private + * @brief Sets up a local scheduler */ -UINT32 pantheon::GlobalScheduler::CreateProcess(const pantheon::String &ProcStr, void *StartAddr) +void pantheon::LocalScheduler::Setup() { pantheon::ScopedGlobalSchedulerLock _L; - - pantheon::Process *NewProc = Process::Create(); - pantheon::ProcessCreateInfo Info = {}; - Info.Name = ProcStr; - Info.EntryPoint = (pantheon::vmm::VirtualAddress)StartAddr; - if (NewProc == nullptr) - { - return 0; - } + pantheon::Thread *Idle = pantheon::Thread::Create(); + Optional MProc = ProcessList.Get(0); + PANIC_ON(!MProc.GetOkay(), "PID 0 does not exist!\n"); - UINT32 Result = 0; - { - pantheon::ScopedLock _LL(NewProc); - NewProc->Initialize(Info); - Result = NewProc->ProcessID(); - } + Idle->Initialize(AcquireThreadID(), MProc.GetValue(), nullptr, nullptr, pantheon::Thread::PRIORITY_VERYLOW, FALSE); + + Idle->Lock(); + Idle->SetState(pantheon::Thread::STATE_RUNNING); + pantheon::CPU::GetCoreInfo()->CurThread = Idle; + this->IdleThread = Idle; + this->Ready = TRUE; + Idle->Unlock(); +} - GlobalScheduler::ProcessList.PushFront(NewProc); +static UINT64 CalculateDeadline(UINT64 Jiffies, UINT64 PrioRatio, UINT64 RRInterval) +{ + /* We don't necessarilly have a high resolution timer, so let's just use jiffies. */ + UINT64 Deadline = Jiffies + (PrioRatio * RRInterval); - if (NewProc != GlobalScheduler::ProcessList.Front()) + /* However, we have to ban -1, since we're using a SkipList. */ + if ((Deadline & ~0ULL) == ~0ULL) { - StopError("NewProc was not front."); + Deadline = 0; } - return Result; + return Deadline; } -pantheon::Thread *pantheon::GlobalScheduler::CreateUserThreadCommon(pantheon::Process *Proc, void *StartAddr, void *ThreadData, pantheon::Thread::Priority Priority) +void pantheon::LocalScheduler::InsertThread(pantheon::Thread *Thr) { - pantheon::Thread *T = pantheon::Thread::Create(); - T->Initialize(Proc, StartAddr, ThreadData, Priority, TRUE); - GlobalScheduler::ThreadList.PushFront(T); - GlobalScheduler::AppendIntoReadyList(T); - return GlobalScheduler::ThreadList.Front(); -} + pantheon::ScopedLock _L(this); + if (Thr == nullptr) + { + return; + } -pantheon::Thread *pantheon::GlobalScheduler::CreateUserThread(UINT32 PID, void *StartAddr, void *ThreadData, pantheon::Thread::Priority Priority) -{ - pantheon::ScopedGlobalSchedulerLock _L; + PANIC_ON(!Thr->IsLocked(), "Attempting to insert thread which wasn't locked into runqueue\n"); - pantheon::Process *SelProc = nullptr; - for (pantheon::Process &Proc : GlobalScheduler::ProcessList) + if (Thr->MyState() == pantheon::Thread::STATE_WAITING) { - if (Proc.ProcessID() == PID) + UINT64 Jiffies = pantheon::CPU::GetJiffies(); + UINT64 Prio = pantheon::Thread::PRIORITY_MAX - Thr->MyPriority(); + + UINT64 Deadline = CalculateDeadline(Jiffies, Prio, pantheon::Thread::RR_INTERVAL); + while (this->LocalRunQueue.Contains(Deadline)) { - SelProc = &Proc; - break; + Deadline++; } + this->LocalRunQueue.Insert(Deadline, Thr); } - - pantheon::Thread *Result = nullptr; - if (SelProc) - { - Result = GlobalScheduler::CreateUserThreadCommon(SelProc, StartAddr, ThreadData, Priority); - } - return Result; } -pantheon::Thread *pantheon::GlobalScheduler::CreateUserThread(pantheon::Process *Proc, void *StartAddr, void *ThreadData, pantheon::Thread::Priority Priority) -{ - pantheon::ScopedGlobalSchedulerLock _L; - pantheon::Thread *Result = pantheon::GlobalScheduler::CreateUserThreadCommon(Proc, StartAddr, ThreadData, Priority); - return Result; -} -pantheon::Thread *pantheon::GlobalScheduler::CreateThread(pantheon::Process *Proc, void *StartAddr, void *ThreadData, pantheon::Thread::Priority Priority) +/** + * @private + * @brief Sets up a global scheduler + */ +void pantheon::Scheduler::Init() { - /* Assert that AccessSpinlock is locked. */ - if (GlobalScheduler::AccessSpinlock.IsLocked() == FALSE) - { - StopError("CreateThread without AccessSpinlock"); - } + /* This is probably unnecessary, but let's be safe... */ + ScopedGlobalSchedulerLock _L; - pantheon::Thread *T = pantheon::Thread::Create(); - T->Initialize(Proc, StartAddr, ThreadData, Priority, FALSE); - GlobalScheduler::ThreadList.PushFront(T); - GlobalScheduler::AppendIntoReadyList(T); - return GlobalScheduler::ThreadList.Front(); + static pantheon::Process IdleProc; + ProcessList.Insert(0, &IdleProc); } -void pantheon::GlobalScheduler::Lock() +void pantheon::Scheduler::Lock() { - AccessSpinlock.Acquire(); + SchedLock.Acquire(); } -void pantheon::GlobalScheduler::Unlock() +void pantheon::Scheduler::Unlock() { - AccessSpinlock.Release(); + SchedLock.Release(); } -pantheon::Atomic pantheon::GlobalScheduler::Okay; -pantheon::Spinlock pantheon::GlobalScheduler::AccessSpinlock; +UINT32 pantheon::Scheduler::CreateProcess(const pantheon::String &ProcStr, void *StartAddr) +{ + pantheon::ScopedGlobalSchedulerLock _L; -pantheon::LinkedList pantheon::GlobalScheduler::ProcessList; -pantheon::LinkedList pantheon::GlobalScheduler::ThreadList; + UINT32 NewID = AcquireProcessID(); -pantheon::Thread *pantheon::GlobalScheduler::ReadyHead; -pantheon::Thread *pantheon::GlobalScheduler::ReadyTail; + pantheon::Process *Proc = pantheon::Process::Create(); + if (Proc == nullptr) + { + return 0; + } -static pantheon::Process IdleProc; -VOID pantheon::GlobalScheduler::Init() -{ - IdleProc = pantheon::Process(); + pantheon::ScopedLock _SL(Proc); - GlobalScheduler::ReadyHead = nullptr; - GlobalScheduler::ReadyTail = nullptr; + pantheon::ProcessCreateInfo CreateInfo{}; + CreateInfo.EntryPoint = reinterpret_cast(StartAddr); + CreateInfo.Name = ProcStr; + CreateInfo.ID = NewID; + Proc->Initialize(CreateInfo); - GlobalScheduler::ThreadList = LinkedList(); - GlobalScheduler::ProcessList = LinkedList(); + ProcessList.Insert(NewID, Proc); - GlobalScheduler::AccessSpinlock = Spinlock("access_spinlock"); - GlobalScheduler::ProcessList.PushFront(&IdleProc); - GlobalScheduler::Okay.Store(TRUE); + return NewID; } -pantheon::Thread *pantheon::GlobalScheduler::CreateProcessorIdleThread() +UINT64 pantheon::Scheduler::CreateThread(UINT32 PID, void *StartAddr, void *ThreadData, pantheon::Thread::Priority Priority) { - while (!GlobalScheduler::Okay.Load()){} + pantheon::ScopedGlobalSchedulerLock _L; - for (pantheon::Process &Proc : GlobalScheduler::ProcessList) + if(!ProcessList.Contains(PID)) { - if (Proc.ProcessID() == 0) - { - /* Note the idle thread needs to have no meaningful data: it gets smashed on startup. */ - pantheon::Thread *CurThread = Thread::Create(); - CurThread->Initialize(&Proc, nullptr, nullptr, pantheon::Thread::PRIORITY_VERYLOW, FALSE); - return CurThread; - } + /* Huh? You're asking for a PID that doesn't exist. */ + return 0; } - GlobalScheduler::AccessSpinlock.Release(); - return nullptr; + + pantheon::Process *MyProc = ProcessList.Get(PID).GetValue(); + pantheon::Thread *Thr = pantheon::Thread::Create(); + Thr->Initialize(AcquireThreadID(), MyProc, StartAddr, ThreadData, Priority, TRUE); + ThreadList.Insert(Thr->ThreadID(), Thr); + + pantheon::ScopedLock _STL(Thr); + pantheon::CPU::GetMyLocalSched()->InsertThread(Thr); + return Thr->ThreadID(); } -UINT64 pantheon::GlobalScheduler::CountThreads(UINT64 PID) + +UINT64 pantheon::Scheduler::CountThreads(UINT64 PID) { + pantheon::ScopedGlobalSchedulerLock _L; + UINT64 Count = 0; - pantheon::GlobalScheduler::Lock(); - for (const pantheon::Thread &T : GlobalScheduler::ThreadList) + UINT8 MyCPU = pantheon::CPU::GetProcessorNumber(); + UINT8 NoCPUs = pantheon::CPU::GetNoCPUs(); + for (UINT8 Index = 0; Index < NoCPUs; Index++) { - if (T.MyProc()->ProcessID() == PID) - { - Count++; - } + UINT8 LocalIndex = (MyCPU + Index) % NoCPUs; + Count += pantheon::CPU::GetLocalSched(LocalIndex)->CountThreads(PID); } - pantheon::GlobalScheduler::Unlock(); return Count; } -UINT32 pantheon::AcquireProcessID() +static pantheon::Thread *GetNextThread() { - /* TODO: When we run out of IDs, go back and ensure we don't - * reuse an ID already in use! - * - * 0 should be reserved for the generic idle process. - */ - UINT32 RetVal = 0; - static UINT32 ProcessID = 1; - RetVal = ProcessID++; - return RetVal; -} + UINT8 MyCPU = pantheon::CPU::GetProcessorNumber(); + UINT8 NoCPUs = pantheon::CPU::GetNoCPUs(); -UINT64 pantheon::AcquireThreadID() -{ - /* TODO: When we run out of IDs, go back and ensure we don't - * reuse an ID already in use! - */ - UINT32 RetVal = 0; - static UINT64 ThreadID = 0; - /* A copy has to be made since we haven't unlocked the spinlock yet. */ - RetVal = ThreadID++; - return RetVal; -} + UINT64 LowestDeadline = -1; + UINT8 LowestScheduler = MyCPU; -void pantheon::AttemptReschedule() -{ - pantheon::Scheduler *CurSched = pantheon::CPU::GetCurSched(); - pantheon::Thread *CurThread = pantheon::CPU::GetCurThread(); - - if (CurThread) + for (UINT8 Index = 0; Index < NoCPUs; Index++) { - CurThread->Lock(); - CurThread->CountTick(); - UINT64 RemainingTicks = CurThread->TicksLeft(); - - if (RemainingTicks > 0 || CurThread->Preempted()) - { - CurThread->Unlock(); - return; - } - - CurThread->SetTicks(0); - CurThread->Unlock(); - - - if (pantheon::CPU::ICOUNT() != 0) + UINT8 LocalIndex = (MyCPU + Index) % NoCPUs; + pantheon::LocalScheduler *Sched = pantheon::CPU::GetLocalSched(LocalIndex); + if (Sched) { - StopError("Interrupts not allowed for reschedule"); + UINT64 Deadline = Sched->GetLowestDeadline(); + if (Deadline < LowestDeadline) + { + LowestScheduler = LocalIndex; + LowestDeadline = Deadline; + } } - - CurSched->Reschedule(); } -} -extern "C" VOID FinishThread() -{ - pantheon::CPU::GetCurThread()->EnableScheduling(); -} - -BOOL pantheon::GlobalScheduler::MapPages(UINT32 PID, pantheon::vmm::VirtualAddress *VAddresses, pantheon::vmm::PhysicalAddress *PAddresses, const pantheon::vmm::PageTableEntry &PageAttributes, UINT64 NumPages) -{ - BOOL Success = FALSE; - pantheon::GlobalScheduler::Lock(); - for (pantheon::Process &Proc : GlobalScheduler::ProcessList) + for (UINT8 Index = 0; Index < NoCPUs; Index++) { - if (Proc.ProcessID() == PID) + UINT8 LocalIndex = (LowestScheduler + Index) % NoCPUs; + pantheon::LocalScheduler *Sched = pantheon::CPU::GetLocalSched(LocalIndex); + if (Sched && Sched->TryLock()) { - pantheon::ScopedLock L(&Proc); - for (UINT64 Index = 0; Index < NumPages; ++Index) + pantheon::Thread *Thr = Sched->AcquireThread(); + PANIC_ON(Thr && !Thr->Preempted(), "Trying to acquire thread which was preempted\n"); + + Sched->Unlock(); + if (Thr != nullptr) { - Proc.MapAddress(VAddresses[Index], PAddresses[Index], PageAttributes); + return Thr; } - Success = TRUE; - break; } } - pantheon::GlobalScheduler::Unlock(); - return Success; + + return nullptr; } -BOOL pantheon::GlobalScheduler::RunProcess(UINT32 PID) +void pantheon::Scheduler::Reschedule() { - BOOL Success = FALSE; - pantheon::vmm::VirtualAddress Entry = 0x00; - pantheon::GlobalScheduler::Lock(); - for (pantheon::Process &Proc : GlobalScheduler::ProcessList) + /* Interrupts must be enabled before we can do anything. */ + if (pantheon::CPU::ICOUNT()) { - if (Proc.ProcessID() == PID) - { - Proc.Lock(); - Proc.SetState(pantheon::Process::STATE_RUNNING); - Entry = Proc.GetEntryPoint(); - Success = TRUE; - Proc.Unlock(); - break; - } + return; } - pantheon::GlobalScheduler::Unlock(); - if (Success) - { - pantheon::GlobalScheduler::CreateUserThread(PID, (void*)(Entry), nullptr); - } - return Success; -} + pantheon::CPU::GetCurThread()->Lock(); + pantheon::CPU::GetCurThread()->BlockScheduling(); + pantheon::Thread *CurThread = pantheon::CPU::GetCurThread(); -BOOL pantheon::GlobalScheduler::SetState(UINT32 PID, pantheon::Process::State State) -{ - BOOL Success = FALSE; - pantheon::GlobalScheduler::Lock(); - for (pantheon::Process &Proc : GlobalScheduler::ProcessList) + pantheon::Thread *NextThread = GetNextThread(); + if (NextThread == nullptr) { - if (Proc.ProcessID() == PID) + NextThread = pantheon::CPU::GetMyLocalSched()->Idle(); + if (NextThread->IsLocked()) { - Proc.Lock(); - Proc.SetState(State); - Success = TRUE; - Proc.Unlock(); - break; + CurThread->Unlock(); + pantheon::CPU::GetCurThread()->EnableScheduling(); + return; } + NextThread->Lock(); } - pantheon::GlobalScheduler::Unlock(); - return Success; -} -void pantheon::GlobalScheduler::AppendIntoReadyList(pantheon::Thread *Next) -{ - /* Make sure we're locked before doing this... */ - if (GlobalScheduler::AccessSpinlock.IsLocked() == FALSE) + if (NextThread == CurThread) { - StopError("Appending into readylist while not locked"); + CurThread->Unlock(); + pantheon::CPU::GetCurThread()->EnableScheduling(); + return; } - /* If this thread is null for whatever reason, don't bother. */ - if (Next == nullptr) + if (NextThread->MyState() == pantheon::Thread::STATE_RUNNING + || CurThread->MyState() != pantheon::Thread::STATE_RUNNING) { + NextThread->Unlock(); + CurThread->Unlock(); + pantheon::CPU::GetCurThread()->EnableScheduling(); return; } - if (GlobalScheduler::ReadyTail) + pantheon::Thread::Switch(CurThread, NextThread); + pantheon::Process::Switch(NextThread->MyProc()); + + pantheon::CpuContext *OldContext = CurThread->GetRegisters(); + pantheon::CpuContext *NewContext = NextThread->GetRegisters(); + + if (CurThread->MyState() == pantheon::Thread::STATE_WAITING) { - GlobalScheduler::ReadyTail->SetNext(Next); - GlobalScheduler::ReadyTail = Next; + pantheon::CPU::GetMyLocalSched()->InsertThread(CurThread); } - else + CurThread->Unlock(); + + NextThread->Unlock(); + + cpu_switch(OldContext, NewContext, CpuIRegOffset); + pantheon::CPU::GetCurThread()->EnableScheduling(); +} + +void pantheon::Scheduler::AttemptReschedule() +{ + pantheon::Thread *CurThread = pantheon::CPU::GetCurThread(); + INT64 RemainingTicks = CurThread->CountTick(); + + if (RemainingTicks <= 0 && !CurThread->Preempted()) { - /* Only possible if the queue really is empty. */ - GlobalScheduler::ReadyTail = Next; - GlobalScheduler::ReadyHead = Next; + pantheon::Scheduler::Reschedule(); } - GlobalScheduler::ReadyTail->SetNext(nullptr); } -pantheon::Thread *pantheon::GlobalScheduler::PopFromReadyList() +/** + * @private + * @brief Finish routine for jumping to userspace + */ +extern "C" VOID FinishThread() +{ + pantheon::CPU::GetCurThread()->EnableScheduling(); +} + +BOOL pantheon::Scheduler::MapPages(UINT32 PID, const pantheon::vmm::VirtualAddress *VAddresses, const pantheon::vmm::PhysicalAddress *PAddresses, const pantheon::vmm::PageTableEntry &PageAttributes, UINT64 NumPages) { - /* Make sure we're locked before doing this... */ - if (GlobalScheduler::AccessSpinlock.IsLocked() == FALSE) + /* TODO: Move into a more sensible namespace like pantheon::mm or something */ + pantheon::ScopedGlobalSchedulerLock _L; + + if (!ProcessList.Contains(PID)) { - StopError("Poping from readylist while not locked"); + return FALSE; } - pantheon::Thread *Head = GlobalScheduler::ReadyHead; - if (Head) + pantheon::Process *Proc = ProcessList.Get(PID).GetValue(); + pantheon::ScopedLock _LL(Proc); + for (UINT64 Index = 0; Index < NumPages; ++Index) { - GlobalScheduler::ReadyHead = GlobalScheduler::ReadyHead->Next(); + Proc->MapAddress(VAddresses[Index], PAddresses[Index], PageAttributes); } + return TRUE; +} - if (GlobalScheduler::ReadyHead == nullptr) +BOOL pantheon::Scheduler::SetState(UINT32 PID, pantheon::Process::State State) +{ + pantheon::ScopedGlobalSchedulerLock _L; + + if (!ProcessList.Contains(PID)) { - GlobalScheduler::ReadyTail = nullptr; + return FALSE; } - return Head; + + pantheon::Process *Proc = ProcessList.Get(PID).GetValue(); + pantheon::ScopedLock _LL(Proc); + Proc->SetState(State); + return TRUE; +} + +BOOL pantheon::Scheduler::SetThreadState(UINT64 TID, pantheon::Thread::State State) +{ + pantheon::ScopedGlobalSchedulerLock _L; + + if (!ThreadList.Contains(TID)) + { + return FALSE; + } + + pantheon::Thread *Thr = ThreadList[TID]; + pantheon::ScopedLock _LL(Thr); + + Thr->SetState(State); + return TRUE; } \ No newline at end of file diff --git a/System/Proc/kern_sched.hpp b/System/Proc/kern_sched.hpp index 300dc98..2f7ea17 100644 --- a/System/Proc/kern_sched.hpp +++ b/System/Proc/kern_sched.hpp @@ -5,9 +5,13 @@ #include #include +#include #include +#include #include + +#include #include #ifndef _KERN_SCHED_HPP_ @@ -16,81 +20,185 @@ namespace pantheon { -class Scheduler +/** + * \~english @brief An instance of a per-core scheduler, including the local runqueue + * \~english @author Brian Schnepp + */ +class LocalScheduler : public pantheon::Allocatable, public pantheon::Lockable { public: - Scheduler(); - ~Scheduler(); - - void Reschedule(); - Process *MyProc(); - Thread *MyThread(); + /** + * \~english @brief Initalizes an instance of a per-core scheduler. + * \~english @author Brian Schnepp + */ + LocalScheduler(); + ~LocalScheduler() override = default; + + /** + * \~english @brief Counts the number of threads under this scheduler which belong to a given PID. + * \~english @pre This scheduler is not locked + * \~english @return The number of threads under this manager which belong to a given process + */ + UINT64 CountThreads(UINT64 PID); + + /** + * \~english @brief Obtains a thread from the local runqueue which can be run, and removes it from the local runqueue. + * \~english @pre This scheduler is locked + * \~english @return A thread which can be run, if it exists. Nullptr otherwise. + */ + pantheon::Thread *AcquireThread(); + + /** + * \~english @brief Inserts a thread for this local scheduler + * \~english @pre The thread to be inserted is locked before calling + * \~english @pre This scheduler is not locked + * \~english @param Thr The thread object to insert. + */ + void InsertThread(pantheon::Thread *Thr); + + /** + * \~english @brief Gets the lowest virtual deadline that is enqueued + * \~english @pre This scheduler is not locked + * \~english @returns The lowest virtual deadline in this scheduler, or -1 if none exists + */ + [[nodiscard]] UINT64 GetLowestDeadline() const; + + /** @private */ void Setup(); + /** @private */ pantheon::Thread *Idle(); private: - VOID PerformCpuSwitch(pantheon::CpuContext *Old, pantheon::CpuContext *New); - Thread *CurThread; - Thread *IdleThread; + pantheon::SkipList LocalRunQueue; + pantheon::LinkedList Threads; + pantheon::Thread *IdleThread; + BOOL Ready; }; -class GlobalScheduler +/** + * \~english @brief Functions for managing and accessing processes using the global scheduler's state + * \~english @author Brian Schnepp + */ +namespace Scheduler { -public: - static void Init(); - - static UINT32 CreateProcess(const pantheon::String &ProcStr, void *StartAddr); - static pantheon::Thread *CreateThread(pantheon::Process *Proc, void *StartAddr, void *ThreadData, pantheon::Thread::Priority Priority = pantheon::Thread::PRIORITY_NORMAL); - - static pantheon::Thread *CreateUserThread(UINT32 PID, void *StartAddr, void *ThreadData, pantheon::Thread::Priority Priority = pantheon::Thread::PRIORITY_NORMAL); - static pantheon::Thread *CreateUserThread(pantheon::Process *Proc, void *StartAddr, void *ThreadData, pantheon::Thread::Priority Priority = pantheon::Thread::PRIORITY_NORMAL); - - static UINT64 CountThreads(UINT64 PID); - static pantheon::Thread *CreateProcessorIdleThread(); - - static BOOL RunProcess(UINT32 PID); - static BOOL SetState(UINT32 PID, pantheon::Process::State State); - static BOOL MapPages(UINT32 PID, pantheon::vmm::VirtualAddress *VAddresses, pantheon::vmm::PhysicalAddress *PAddresses, const pantheon::vmm::PageTableEntry &PageAttributes, UINT64 NumPages); - - static void AppendIntoReadyList(pantheon::Thread *Next); - static pantheon::Thread *PopFromReadyList(); - - /* TODO: Inherit from Lockable... */ - static void Lock(); - static void Unlock(); - -private: - static Atomic Okay; - static Spinlock AccessSpinlock; - - static LinkedList ProcessList; - static LinkedList ThreadList; - - static Thread *ReadyHead; - static Thread *ReadyTail; + /** @private */ void Init(); + + /** + * \~english @brief Sets the state of a given process + * \~english @pre The global scheduler lock is not held by this processor + * \~english @pre The process lock is not held by this processor + * \~english @param[in] PID The process ID to modify + * \~english @param[in] State The state to set the process to + */ + BOOL SetState(UINT32 PID, pantheon::Process::State State); + + /** + * \~english @brief Sets the state of a given thread + * \~english @pre The global scheduler lock is not held by this processor + * \~english @pre The thread lock is not held by this processor + * \~english @param[in] TID The thread ID to modify + * \~english @param[in] State The state to set the thread to + */ + BOOL SetThreadState(UINT64 TID, pantheon::Thread::State State); + + /** + * \~english @brief Maps some pages into a given process' address space + * \~english @param[in] PID The process ID to map addresses into + * \~english @param[in] VAddresses The set of virtual addresses to map these pages as + * \~english @param[in] PAddresses The set of physical addresses to map these pages from + * \~english @param[in] PageAttributes The set of page attributes (permissions, cachability, etc.) to use for all of these pages + * \~english @param[in] NumPages The number of pages to map + * \~english @return TRUE on success, FALSE otherwise + * \~english @pre Length(VAddresses) == Length(PAddress) + * \~english @pre Length(VAddresses) >= NumPages + * \~english @pre Length(PAddresses) >= NumPages + * \~english @author Brian Schnepp + */ + BOOL MapPages(UINT32 PID, const pantheon::vmm::VirtualAddress *VAddresses, const pantheon::vmm::PhysicalAddress *PAddresses, const pantheon::vmm::PageTableEntry &PageAttributes, UINT64 NumPages); + + /** + * \~english @brief Creates a process, visible globally, from a name and address. + * \~english @details A process is created, such that it can be run on any + * available processor on the system. The given process name identifies the + * process for debugging purposes and as a user-readable way to identify a + * process, and the start address is a pointer to the first instruction the + * initial thread should execute when scheduled. + * \~english @param[in] ProcStr A human-readable name for the process + * \~english @param[in] StartAddr The initial value of the program counter + * \~english @return New PID if the process was sucessfully created, 0 otherwise. + * \~english @author Brian Schnepp + */ + UINT32 CreateProcess(const pantheon::String &ProcStr, void *StartAddr); + + /** + * \~english @brief Creates a process, visible globally, under a given process. + * \~english @details A thread is created, such that it can be run on any + * available processor on the system, but will typically default to the + * current processor where this function is called. StartAddr is a pointer to + * the location of the entrypoint of the new thread, with it's first argument + * being ThreadData. + * \~english @param[in] Proc The process to associate under + * \~english @param[in] StartAddr The entrypoint of the thread + * \~english @param[in] ThreadData The contents of the first argument register when calling StartAddr + * \~english @param[in] Priority The priority of the thread + * \~english @return The newly created ThreadID of the thread if successful, 0 otherwise. + * \~english @author Brian Schnepp + */ + UINT64 CreateThread(UINT32 Proc, void *StartAddr, void *ThreadData, pantheon::Thread::Priority Priority = pantheon::Thread::PRIORITY_NORMAL); + + /** + * \~english @brief Counts the number of threads which belong to a given PID. + * \~english @pre Global scheduler is not locked + * \~english @return The number of threads which belong to a given process + */ + UINT64 CountThreads(UINT64 PID); + + /** + * \~english @brief Locks the global scheduler, preventing operations such as thread or process modification from proceeding on other threads + * @see ScopedGlobalSchedulerLock + */ + void Lock(); + + /** + * \~english @brief Releases the global scheduler, allowing operations such as thread or process modification to proceed on other threads + * @see ScopedGlobalSchedulerLock + */ + void Unlock(); + + /** + * \~english @brief Changes the current thread of this core. + * \~english @details Forcefully changes the current thread by iterating to + * the next thread in the list of threads belonging to this core. The active + * count for the current thread is set to zero, and the new thread is run until + * either the core is forcefully rescheduled, or the timer indicates the current + * thread should quit. + * \~english @pre Interrupts are enabled on this processor (no locks are held) + * \~english @author Brian Schnepp + */ + void Reschedule(); -private: - static Thread *CreateUserThreadCommon(pantheon::Process *Proc, void *StartAddr, void *ThreadData, pantheon::Thread::Priority Priority); + /** + * \~english @brief Checks for the rescheduling condition, and triggers if possible + * \~english @details Checks if the number of jiffies the current thread has been + * allocated have expired, and if so, switches to a different context. The number of + * jiffies a thread has will be determined by the constant RR_INTERVAL in the class + * pantheon::Thread. This will usually be 6, so that threads have a fair amount of + * time, but some threads may be scheduled before others depending on the current + * scheduling policy. + * + * @see pantheon::Thread + * + * \~english @author Brian Schnepp + */ + void AttemptReschedule(); }; class ScopedGlobalSchedulerLock { public: - FORCE_INLINE ScopedGlobalSchedulerLock() { pantheon::GlobalScheduler::Lock(); } - FORCE_INLINE ~ScopedGlobalSchedulerLock() { pantheon::GlobalScheduler::Unlock(); } + FORCE_INLINE ScopedGlobalSchedulerLock() { pantheon::Scheduler::Lock(); } + FORCE_INLINE ~ScopedGlobalSchedulerLock() { pantheon::Scheduler::Unlock(); } }; -class ScopedLocalSchedulerLock -{ -public: - FORCE_INLINE ScopedLocalSchedulerLock() { pantheon::CPU::GetCurThread()->BlockScheduling(); } - FORCE_INLINE ~ScopedLocalSchedulerLock() { pantheon::CPU::GetCurThread()->EnableScheduling(); } -}; - -UINT32 AcquireProcessID(); -UINT64 AcquireThreadID(); - -void AttemptReschedule(); - } #endif \ No newline at end of file diff --git a/System/Proc/kern_thread.cpp b/System/Proc/kern_thread.cpp index 89e3f35..5dd86d6 100644 --- a/System/Proc/kern_thread.cpp +++ b/System/Proc/kern_thread.cpp @@ -64,10 +64,7 @@ pantheon::Thread::Thread(Process *OwningProcess, Priority Pri) : pantheon::Locka this->Registers.Wipe(); this->CurState = pantheon::Thread::STATE_INIT; - this->TID = AcquireThreadID(); - - /* 45 for NORMAL, 30 for LOW, 15 for VERYLOW, etc. */ - this->RefreshTicks(); + this->TID = 0; this->Unlock(); } @@ -137,7 +134,7 @@ pantheon::Process *pantheon::Thread::MyProc() const pantheon::Thread::State pantheon::Thread::MyState() const { OBJECT_SELF_ASSERT(); - UINT64 State = this->CurState; /* ubsan says this is a problem. */ + UINT64 State = this->CurState; if (State > pantheon::Thread::STATE_MAX) { StopErrorFmt("Invalid thread state: got 0x%lx\n", this->CurState); @@ -164,45 +161,29 @@ pantheon::Thread::Priority pantheon::Thread::MyPriority() const /** * \~english @brief Checks if this thread is valid to interrupt at this moment * \~english @author Brian Schnepp - * \~english @return 0 if not doing system work currently, 1 otherwise. + * \~english @return 0 if not doing system work currently, positive integer otherwise. */ [[nodiscard]] -BOOL pantheon::Thread::Preempted() const +INT64 pantheon::Thread::Preempted() const { OBJECT_SELF_ASSERT(); return this->PreemptCount != 0; } -/** - * \~english @brief Gets the number of timer interrupts remaining for the thread - * \~english @author Brian Schnepp - * \~english @return The number of times the processor core will need to be - * interrupted before the thread is preempted and some other work resumes on - * the current processor core. - */ -UINT64 pantheon::Thread::TicksLeft() const -{ - OBJECT_SELF_ASSERT(); - return this->RemainingTicks.Load(); -} - /** * \~english @brief Counts down the number of timer interrupts for this thread * \~english @author Brian Schnepp */ -VOID pantheon::Thread::CountTick() +INT64 pantheon::Thread::CountTick() { OBJECT_SELF_ASSERT(); - if (this->IsLocked() == FALSE) + pantheon::ScopedLock _L(this); + if (this->RemainingTicks.Load() <= 0) { - StopError("CountTicks without lock"); + this->SetTicks(0); + return 0; } - - if (this->RemainingTicks == 0) - { - return; - } - this->RemainingTicks.Store(this->RemainingTicks.Load() - 1); + return this->RemainingTicks.Sub(1); } [[nodiscard]] @@ -212,30 +193,6 @@ UINT64 pantheon::Thread::ThreadID() const return this->TID; } -/** - * \~english @brief Forcefully adds execution time to the current process. - * \~english @author Brian Schnepp - */ -VOID pantheon::Thread::AddTicks(UINT64 TickCount) -{ - OBJECT_SELF_ASSERT(); - if (this->IsLocked() == FALSE) - { - StopError("AddTicks without lock"); - } - this->RemainingTicks.Store(this->RemainingTicks.Load() + TickCount); -} - -VOID pantheon::Thread::RefreshTicks() -{ - OBJECT_SELF_ASSERT(); - if (this->IsLocked() == FALSE) - { - StopError("RefreshTicks without lock"); - } - this->RemainingTicks = static_cast((this->CurPriority + 1)) * 3; -} - /** * \~english @brief Sets the state, such as running, to the current process. * \~english @author Brian Schnepp @@ -245,19 +202,19 @@ VOID pantheon::Thread::SetState(Thread::State St) OBJECT_SELF_ASSERT(); if (this->IsLocked() == FALSE) { - StopError("SetState without lock"); + StopError("SetState without lock", this); } this->CurState = St; } -VOID pantheon::Thread::SetTicks(UINT64 TickCount) +VOID pantheon::Thread::SetTicks(INT64 TickCount) { OBJECT_SELF_ASSERT(); if (this->IsLocked() == FALSE) { - StopError("SetTicks without lock"); + StopError("SetTicks without lock", this); } - this->RemainingTicks = TickCount; + this->RemainingTicks = (TickCount < 0) ? 0 : TickCount; } /** @@ -288,11 +245,6 @@ VOID pantheon::Thread::SetPriority(Thread::Priority Pri) pantheon::CpuContext *pantheon::Thread::GetRegisters() { OBJECT_SELF_ASSERT(); - if (this->IsLocked() == FALSE) - { - StopError("GetRegisters without lock"); - } - /* TODO: Copy the actual registers to the internal representation! */ return &this->Registers; } @@ -340,56 +292,16 @@ pantheon::Thread &pantheon::Thread::operator=(pantheon::Thread &&Other) noexcept return *this; } -void pantheon::Thread::SetKernelStackAddr(UINT64 Addr) -{ - OBJECT_SELF_ASSERT(); - if (this->IsLocked() == FALSE) - { - StopError("SetKernelStackAddr without lock"); - } - - this->Registers.SetSP(Addr); - this->KernelStackSpace = reinterpret_cast((CHAR*)Addr); -} - -void pantheon::Thread::SetUserStackAddr(UINT64 Addr) -{ - OBJECT_SELF_ASSERT(); - if (this->IsLocked() == FALSE) - { - StopError("SetUserStackAddr without lock"); - } - - this->Registers.SetSP(Addr); - this->UserStackSpace = reinterpret_cast((CHAR*)Addr); -} - -void pantheon::Thread::SetProc(pantheon::Process *Proc) -{ - OBJECT_SELF_ASSERT(); - if (this->IsLocked() == FALSE) - { - StopError("SetProc without lock"); - } - this->ParentProcess = Proc; -} - void pantheon::Thread::BlockScheduling() { OBJECT_SELF_ASSERT(); - pantheon::Sync::DSBISH(); - this->PreemptCount.Store(this->PreemptCount.Load() + 1); - pantheon::Sync::DSBISH(); + this->PreemptCount.Add(1LL); } void pantheon::Thread::EnableScheduling() { OBJECT_SELF_ASSERT(); - this->Lock(); - pantheon::Sync::DSBISH(); - this->PreemptCount.Store(this->PreemptCount.Load() - 1); - pantheon::Sync::DSBISH(); - this->Unlock(); + this->PreemptCount.Sub(1LL); } extern "C" VOID drop_usermode(UINT64 PC, UINT64 PSTATE, UINT64 SP); @@ -400,9 +312,14 @@ static void true_drop_process(void *StartAddress, pantheon::vmm::VirtualAddress drop_usermode((UINT64)StartAddress, 0x00, StackAddr); } -void pantheon::Thread::Initialize(pantheon::Process *Proc, void *StartAddr, void *ThreadData, pantheon::Thread::Priority Priority, BOOL UserMode) +void pantheon::Thread::Initialize(UINT64 TID, pantheon::Process *Proc, void *StartAddr, void *ThreadData, pantheon::Thread::Priority Priority, BOOL UserMode) { static constexpr UINT64 InitialThreadStackSize = pantheon::vmm::SmallestPageSize * pantheon::Thread::InitialNumStackPages; + this->TID = TID; + this->CurState = pantheon::Thread::STATE_DEAD; + + this->Lock(); + Optional StackSpace = BasicMalloc(InitialThreadStackSize); if (StackSpace.GetOkay()) { @@ -412,7 +329,6 @@ void pantheon::Thread::Initialize(pantheon::Process *Proc, void *StartAddr, void #endif IStackSpace += InitialThreadStackSize; this->ParentProcess = Proc; - this->Lock(); UINT64 IStartAddr = (UINT64)true_drop_process; UINT64 IThreadData = (UINT64)ThreadData; @@ -425,23 +341,32 @@ void pantheon::Thread::Initialize(pantheon::Process *Proc, void *StartAddr, void } this->SetState(pantheon::Thread::STATE_WAITING); this->SetPriority(Priority); - this->Unlock(); } -} -[[nodiscard]] BOOL pantheon::Thread::End() const -{ - return this->NextThread == nullptr; -} + /* Don't create a TLS for kernel threads */ + if (UserMode) + { + this->LocalRegion = pantheon::Process::ThreadLocalBase + (this->ThreadID() * 0x1000); -pantheon::Thread *pantheon::Thread::Next() -{ - return this->NextThread.Load(); -} + /* Map in the TLR. */ + pantheon::vmm::PageTableEntry Entry; + Entry.SetBlock(TRUE); + Entry.SetMapped(TRUE); + Entry.SetUserNoExecute(TRUE); + Entry.SetKernelNoExecute(TRUE); -void pantheon::Thread::SetNext(pantheon::Thread *Item) -{ - this->NextThread = Item; + /* Yuck. But we need to make page permissions less bad...*/ + UINT64 PagePermission = 0x1 << 6; + Entry.SetPagePermissions(PagePermission); + + Entry.SetSharable(pantheon::vmm::PAGE_SHARABLE_TYPE_INNER); + Entry.SetAccessor(pantheon::vmm::PAGE_MISC_ACCESSED); + Entry.SetMAIREntry(pantheon::vmm::MAIREntry_1); + + pantheon::ScopedLock _L(this->MyProc()); + this->MyProc()->MapAddress(this->LocalRegion, pantheon::PageAllocator::Alloc(), Entry); + } + this->Unlock(); } pantheon::Thread::ThreadLocalRegion *pantheon::Thread::GetThreadLocalArea() @@ -451,4 +376,18 @@ pantheon::Thread::ThreadLocalRegion *pantheon::Thread::GetThreadLocalArea() pantheon::vmm::PhysicalAddress PhyAddr = pantheon::vmm::VirtualToPhysicalAddress(Proc->GetPageTable(), this->LocalRegion); pantheon::vmm::VirtualAddress VirtAddr = pantheon::vmm::PhysicalToVirtualAddress(PhyAddr); return reinterpret_cast(VirtAddr); +} + +/** + * @brief Switches thread context to another thread + */ +void pantheon::Thread::Switch(pantheon::Thread *CurThread, pantheon::Thread *NextThread) +{ + NextThread->SetState(Thread::STATE_RUNNING); + NextThread->SetTicks(Thread::RR_INTERVAL); + + pantheon::ipc::SetThreadLocalRegion(NextThread->LocalRegion); + pantheon::CPU::GetCoreInfo()->CurThread = NextThread; + + CurThread->SetState(pantheon::Thread::STATE_WAITING); } \ No newline at end of file diff --git a/System/Proc/kern_thread.hpp b/System/Proc/kern_thread.hpp index 238d6b1..d0fbdc7 100644 --- a/System/Proc/kern_thread.hpp +++ b/System/Proc/kern_thread.hpp @@ -64,6 +64,7 @@ class Thread : public pantheon::Object, public pantheon::Lockable STATE_INIT, STATE_RUNNING, STATE_WAITING, + STATE_BLOCKED, STATE_TERMINATED, STATE_MAX, }State; @@ -78,6 +79,8 @@ class Thread : public pantheon::Object, public pantheon::Lockable PRIORITY_MAX, }Priority; + static constexpr UINT64 RR_INTERVAL = 6; + public: Thread(); Thread(Process *ParentProcess); @@ -86,46 +89,38 @@ class Thread : public pantheon::Object, public pantheon::Lockable Thread(Thread &&Other) noexcept; ~Thread() override; - void Initialize(pantheon::Process *Proc, void *StartAddr, void *ThreadData, pantheon::Thread::Priority Priority, BOOL UserMode); + void Initialize(UINT64 TID, pantheon::Process *Proc, void *StartAddr, void *ThreadData, pantheon::Thread::Priority Priority, BOOL UserMode); [[nodiscard]] Process *MyProc() const; - [[nodiscard]] Thread::State MyState() const; [[nodiscard]] Thread::Priority MyPriority() const; - - [[nodiscard]] UINT64 TicksLeft() const; [[nodiscard]] UINT64 ThreadID() const; - VOID AddTicks(UINT64 TickCount); - VOID CountTick(); - VOID RefreshTicks(); - VOID SetTicks(UINT64 TickCount); + INT64 CountTick(); + VOID SetTicks(INT64 TickCount); VOID SetState(Thread::State State); VOID SetPriority(Thread::Priority Priority); - VOID SetEntryLocation(UINT64 IP, UINT64 SP, VOID* ThreadData); - CpuContext *GetRegisters(); - void SetUserStackAddr(UINT64 Addr); - void SetKernelStackAddr(UINT64 Addr); + ThreadLocalRegion *GetThreadLocalArea(); [[nodiscard]] pantheon::vmm::VirtualAddress GetThreadLocalAreaRegister() const { return this->LocalRegion; } Thread &operator=(const Thread &Other); Thread &operator=(Thread &&Other) noexcept; - void SetProc(pantheon::Process *Proc); - void BlockScheduling(); void EnableScheduling(); - [[nodiscard]] BOOL Preempted() const; + [[nodiscard]] INT64 Preempted() const; - [[nodiscard]] BOOL End() const; - Thread *Next(); - void SetNext(pantheon::Thread *Item); - ThreadLocalRegion *GetThreadLocalArea(); - + /** + * @brief Switches thread context to another thread + */ + static void Switch(Thread *CurThread, Thread *NextThread); + +private: + VOID SetEntryLocation(UINT64 IP, UINT64 SP, VOID* ThreadData); private: UINT64 TID; @@ -136,8 +131,8 @@ class Thread : public pantheon::Object, public pantheon::Lockable Thread::State CurState; Thread::Priority CurPriority; - pantheon::Atomic PreemptCount; - pantheon::Atomic RemainingTicks; + pantheon::AtomicInteger PreemptCount; + pantheon::AtomicInteger RemainingTicks; void *KernelStackSpace; void *UserStackSpace; @@ -145,8 +140,6 @@ class Thread : public pantheon::Object, public pantheon::Lockable pantheon::vmm::VirtualAddress LocalRegion; static constexpr UINT64 InitialNumStackPages = 4; - - pantheon::Atomic NextThread; }; } diff --git a/System/Syscalls/Syscalls.cpp b/System/Syscalls/Syscalls.cpp index 56d5930..c23ca83 100644 --- a/System/Syscalls/Syscalls.cpp +++ b/System/Syscalls/Syscalls.cpp @@ -24,34 +24,6 @@ static T ReadArgument(UINT64 Val) return reinterpret_cast(Val); } - -static const CHAR *ReadArgumentAsCharPointer(UINT64 Val) -{ - /* Don't allow reading of kernel memory */ - if (Val > pantheon::vmm::HigherHalfAddress) - { - return nullptr; - } - - pantheon::Process *CurProc = pantheon::CPU::GetCurProcess(); - if (CurProc->IsLocked() == FALSE) - { - pantheon::StopErrorFmt("Attempt to read userspace memory without CurProc lock (PID %hhu)\n", CurProc->ProcessID()); - } - - /* Make sure we translate this to something in kernel memory, - * just to be safe. */ - pantheon::vmm::VirtualAddress RawPtr = Val; - const CHAR *Result = nullptr; - - pantheon::vmm::PhysicalAddress PAddr = pantheon::vmm::VirtualToPhysicalAddress(CurProc->GetPageTable(), RawPtr); - pantheon::vmm::VirtualAddress VAddr = pantheon::vmm::PhysicalToVirtualAddress(PAddr); - - Result = reinterpret_cast(VAddr); - - return Result; -} - template static T *ReadArgumentAsPointer(UINT64 Val) { @@ -103,7 +75,7 @@ VOID pantheon::SVCExitProcess(pantheon::TrapFrame *CurFrame) CurThread->SetState(pantheon::Thread::STATE_DEAD); CurThread->Unlock(); - pantheon::CPU::GetCoreInfo()->CurSched->Reschedule(); + pantheon::Scheduler::Reschedule(); } pantheon::Result pantheon::SVCForkProcess(pantheon::TrapFrame *CurFrame) @@ -123,7 +95,7 @@ pantheon::Result pantheon::SVCLogText(pantheon::TrapFrame *CurFrame) const CHAR *Data = nullptr; - Data = ReadArgumentAsCharPointer(CurFrame->GetIntArgument(0)); + Data = ReadArgumentAsPointer(CurFrame->GetIntArgument(0)); if (Data == nullptr) { return pantheon::Result::SYS_FAIL; @@ -180,7 +152,7 @@ pantheon::Result pantheon::SVCCreateNamedEvent(pantheon::TrapFrame *CurFrame) pantheon::ScopedLock ScopeLockProc(Proc); const CHAR *Name = nullptr; - Name = ReadArgumentAsCharPointer(CurFrame->GetIntArgument(0)); + Name = ReadArgumentAsPointer(CurFrame->GetIntArgument(0)); INT32 *ReadHandle = nullptr; ReadHandle = ReadArgument(CurFrame->GetIntArgument(1)); @@ -354,13 +326,11 @@ pantheon::Result pantheon::SVCPollEvent(pantheon::TrapFrame *CurFrame) pantheon::Result pantheon::SVCYield(pantheon::TrapFrame *CurFrame) { PANTHEON_UNUSED(CurFrame); - pantheon::Scheduler *CurSched = pantheon::CPU::GetCurSched(); { pantheon::Thread *CurThread = pantheon::CPU::GetCurThread(); pantheon::ScopedLock ScopeLockThread(CurThread); - CurThread->SetTicks(0); } - CurSched->Reschedule(); + pantheon::Scheduler::Reschedule(); return pantheon::Result::SYS_OK; } @@ -369,18 +339,17 @@ pantheon::Result pantheon::SVCExitThread(pantheon::TrapFrame *CurFrame) PANTHEON_UNUSED(CurFrame); pantheon::Thread *CurThread = pantheon::CPU::GetCurThread(); pantheon::Process *CurProc = CurThread->MyProc(); - pantheon::Scheduler *CurSched = pantheon::CPU::GetCurSched(); CurProc->Lock(); CurThread->Lock(); CurThread->SetState(pantheon::Thread::STATE_TERMINATED); CurThread->Unlock(); - if (pantheon::GlobalScheduler::CountThreads(CurThread->MyProc()->ProcessID()) == 0) + if (pantheon::Scheduler::CountThreads(CurThread->MyProc()->ProcessID()) == 0) { CurProc->SetState(pantheon::Process::STATE_ZOMBIE); } CurProc->Unlock(); - CurSched->Reschedule(); + pantheon::Scheduler::Reschedule(); return pantheon::Result::SYS_OK; } @@ -436,7 +405,7 @@ pantheon::Result pantheon::SVCCreatePort(pantheon::TrapFrame *CurFrame) pantheon::ScopedLock _PL(CurProc); CHAR Buffer[pantheon::ipc::PortNameLength] = {0}; - const CHAR *Location = ReadArgumentAsCharPointer(CurFrame->GetIntArgument(0)); + const CHAR *Location = ReadArgumentAsPointer(CurFrame->GetIntArgument(0)); CopyString(Buffer, Location, pantheon::ipc::PortNameLength); pantheon::ipc::PortName Name; @@ -487,7 +456,7 @@ pantheon::Result pantheon::SVCConnectToNamedPort(pantheon::TrapFrame *CurFrame) pantheon::Process *CurProc = pantheon::CPU::GetCurProcess(); pantheon::ScopedLock _L(CurProc); - const char *Name = ReadArgumentAsCharPointer(CurFrame->GetIntArgument(0)); + const char *Name = ReadArgumentAsPointer(CurFrame->GetIntArgument(0)); if (Name == nullptr) { return pantheon::Result::SYS_FAIL; diff --git a/Userland/SDK/init.cpp b/Userland/SDK/init.cpp index b474492..3ae7ded 100644 --- a/Userland/SDK/init.cpp +++ b/Userland/SDK/init.cpp @@ -7,4 +7,11 @@ extern "C" void _start() main(); svc_ExitProcess(); for (;;) {} +} + +ThreadLocalRegion *GetTLS(void) +{ + ThreadLocalRegion *RetVal = nullptr; + asm volatile ("mrs %0, tpidrro_el0\n" : "=r"(RetVal) ::); + return RetVal; } \ No newline at end of file diff --git a/Userland/SDK/pantheon.h b/Userland/SDK/pantheon.h index 0843163..2479241 100644 --- a/Userland/SDK/pantheon.h +++ b/Userland/SDK/pantheon.h @@ -24,4 +24,26 @@ extern "C" pantheon::Result svc_ReplyAndRecieve(UINT32 ContentSize, UINT32 *Repl extern "C" pantheon::Result svc_CloseHandle(INT32 Handle); extern "C" pantheon::Result svc_SendRequest(INT32 Handle); + + +typedef struct ThreadLocalHeader +{ + UINT16 Meta; + UINT16 Cmd; + UINT32 ClientPID; +}ThreadLocalHeader; + +typedef union ThreadLocalPayload +{ + UINT32 Data[1022]; +}ThreadLocalPayload; + +typedef struct ThreadLocalRegion +{ + ThreadLocalHeader Header; + ThreadLocalPayload Payload; +}ThreadLocalRegion; + +ThreadLocalRegion *GetTLS(void); + #endif \ No newline at end of file diff --git a/Userland/SDK/sys.S b/Userland/SDK/sys.S index 917af0e..cca386c 100644 --- a/Userland/SDK/sys.S +++ b/Userland/SDK/sys.S @@ -131,4 +131,4 @@ SVC_DEF svc_SendRequest mov w8, #19 svc #0 ret -SVC_END \ No newline at end of file +SVC_END diff --git a/Userland/sysm/sysm.cpp b/Userland/sysm/sysm.cpp index 98589e0..c2688bf 100644 --- a/Userland/sysm/sysm.cpp +++ b/Userland/sysm/sysm.cpp @@ -31,7 +31,11 @@ void sysm_Main() Status = svc_AcceptConnection(ServerPortRegistration, &ServerConnection); if (Status == pantheon::Result::SYS_OK) { - /* Reply with nothing, forever. */ + ThreadLocalRegion *Data = GetTLS(); + for (UINT16 Index = 0; Index < 1000; Index++) + { + Data->Payload.Data[Index] = 0xAA; + } svc_ReplyAndRecieve(0, nullptr, &ServerConnection); svc_LogText("[sysm] Replying to session"); } diff --git a/arch/aarch64/ints.cpp b/arch/aarch64/ints.cpp index 7543d09..21036ef 100644 --- a/arch/aarch64/ints.cpp +++ b/arch/aarch64/ints.cpp @@ -14,6 +14,24 @@ static UINT64 TimerClock = 1000; +VOID TraceStack(UINT64 PC, UINT64 X29) +{ + struct Frame + { + Frame *Next; + UINT64 PC; + }; + + UINT64 Counter = 0; + Frame *Cur = reinterpret_cast(X29); + SERIAL_LOG("[Core No %hhu, Frame %lu]: %lx\n", pantheon::CPU::GetProcessorNumber(), Counter++, PC); + while (Cur && Cur->PC) + { + SERIAL_LOG("[Core No %hhu, Frame %lu]: %lx\n", pantheon::CPU::GetProcessorNumber(), Counter++, Cur->PC); + Cur = Cur->Next; + } +} + extern "C" void sync_handler_el1_sp0(pantheon::TrapFrame *Frame) { PANTHEON_UNUSED(Frame); @@ -29,18 +47,17 @@ extern "C" void err_handler_el1_sp0(pantheon::TrapFrame *Frame) extern "C" void fiq_handler_el1_sp0(pantheon::TrapFrame *Frame) { PANTHEON_UNUSED(Frame); - SERIAL_LOG_UNSAFE("%s\n", "ERR: FIQ HANDLER EL1 SP0"); + SERIAL_LOG("%s\n", "ERR: FIQ HANDLER EL1 SP0"); } extern "C" void irq_handler_el1_sp0(pantheon::TrapFrame *Frame) { PANTHEON_UNUSED(Frame); - SERIAL_LOG_UNSAFE("%s\n", "ERR: IRQ HANDLER EL1 SP0"); + SERIAL_LOG("%s\n", "ERR: IRQ HANDLER EL1 SP0"); } extern "C" void sync_handler_el1(pantheon::TrapFrame *Frame) { - PANTHEON_UNUSED(Frame); UINT64 ESR, FAR, ELR, SPSR, SP; asm volatile( "mrs %0, esr_el1\n" @@ -50,36 +67,56 @@ extern "C" void sync_handler_el1(pantheon::TrapFrame *Frame) "mov %4, sp\n" : "=r"(ESR), "=r"(FAR), "=r"(ELR), "=r"(SPSR), "=r"(SP)); + pantheon::Thread *CurThread = pantheon::CPU::GetCurThread(); pantheon::Process *CurProc = pantheon::CPU::GetCurProcess(); + TraceStack(Frame->PC, Frame->Regs[29]); + for (UINT64 Index = 0; Index < 31; Index++) + { + SERIAL_LOG("x%lu: %lx ", Index, Frame->Regs[Index]); + } + SERIAL_LOG("\n"); + + SERIAL_LOG("CurThread Status: preempted: %ld MyState: %ld\n", CurThread->Preempted(), CurThread->MyState()); pantheon::StopErrorFmt( - "ERR: SYNC HANDLER EL1: esr: %lx far: %lx elr: %lx spsr: %lx, sp: %lx pid: %u\n", - ESR, FAR, ELR, SPSR, SP, CurProc->ProcessID()); + "ERR: SYNC HANDLER EL1: esr: %lx far: %lx elr: %lx spsr: %lx, sp: %lx pid: %u, tid: %lu\n", + ESR, FAR, ELR, SPSR, SP, CurProc->ProcessID(), CurThread->ThreadID()); } extern "C" void err_handler_el1(pantheon::TrapFrame *Frame) { - PANTHEON_UNUSED(Frame); + TraceStack(Frame->PC, Frame->Regs[29]); pantheon::StopError("ERR: ERR HANDLER EL1"); } extern "C" void fiq_handler_el1(pantheon::TrapFrame *Frame) { PANTHEON_UNUSED(Frame); - SERIAL_LOG_UNSAFE("%s\n", "ERR: FIQ HANDLER EL1"); + SERIAL_LOG("%s\n", "ERR: FIQ HANDLER EL1"); } +class CurFrameMgr +{ +public: + CurFrameMgr(pantheon::TrapFrame *Frame) { pantheon::CPU::GetCoreInfo()->CurFrame = Frame; } + ~CurFrameMgr() { pantheon::CPU::GetCoreInfo()->CurFrame = nullptr; } +}; + + extern "C" void irq_handler_el1(pantheon::TrapFrame *Frame) { - pantheon::CPU::GetCoreInfo()->CurFrame = Frame; + CurFrameMgr _M(Frame); + UINT32 IAR = pantheon::arm::GICRecvInterrupt(); pantheon::arm::GICAckInterrupt(IAR); if ((IAR & 0x3FF) == 30) { + pantheon::CPU::CoreInfo *CoreInfo = pantheon::CPU::GetCoreInfo(); + CoreInfo->LocalJiffies++; + pantheon::arm::RearmSystemTimer(); - pantheon::AttemptReschedule(); + pantheon::Scheduler::AttemptReschedule(); } - pantheon::CPU::GetCoreInfo()->CurFrame = nullptr; } extern "C" void enable_interrupts(); @@ -87,7 +124,7 @@ extern "C" void disable_interrupts(); extern "C" void sync_handler_el0(pantheon::TrapFrame *Frame) { - pantheon::CPU::GetCoreInfo()->CurFrame = Frame; + CurFrameMgr _M(Frame); UINT64 ESR, FAR, ELR, SPSR; asm volatile( "mrs %0, esr_el1\n" @@ -104,26 +141,28 @@ extern "C" void sync_handler_el0(pantheon::TrapFrame *Frame) pantheon::CallSyscall(SyscallNo, Frame); pantheon::CPU::CLI(); } - else if (ESR == 0x2000000) + else { - SERIAL_LOG_UNSAFE("Bad sync handler el0: esr: 0x%lx far: 0x%lx elr: 0x%lx spsr: 0x%lx\n", ESR, FAR, ELR, SPSR); - SERIAL_LOG_UNSAFE("Process ID was %ld\n", pantheon::CPU::GetCurThread()->MyProc()->ProcessID()); - pantheon::vmm::PageTable *PT = pantheon::CPU::GetCurThread()->MyProc()->GetPageTable(); - pantheon::vmm::PrintPageTablesNoZeroes(PT); - } - pantheon::CPU::GetCoreInfo()->CurFrame = nullptr; + pantheon::Thread *CurThread = pantheon::CPU::GetCurThread(); + SERIAL_LOG("PID %d: Bad sync handler el0: esr: 0x%lx far: 0x%lx elr: 0x%lx spsr: 0x%lx\n", CurThread->MyProc()->ProcessID(), ESR, FAR, ELR, SPSR); + if (ESR == 0x2000000) + { + pantheon::vmm::PageTable *PT = CurThread->MyProc()->GetPageTable(); + pantheon::vmm::PrintPageTablesNoZeroes(PT); + } + } } extern "C" void err_handler_el0(pantheon::TrapFrame *Frame) { PANTHEON_UNUSED(Frame); - SERIAL_LOG_UNSAFE("%s\n", "ERR: ERR HANDLER EL0"); + SERIAL_LOG("%s\n", "ERR: ERR HANDLER EL0"); } extern "C" void fiq_handler_el0(pantheon::TrapFrame *Frame) { PANTHEON_UNUSED(Frame); - SERIAL_LOG_UNSAFE("%s\n", "ERR: FIQ HANDLER EL0"); + SERIAL_LOG("%s\n", "ERR: FIQ HANDLER EL0"); } extern "C" void irq_handler_el0(pantheon::TrapFrame *Frame) @@ -135,25 +174,25 @@ extern "C" void irq_handler_el0(pantheon::TrapFrame *Frame) extern "C" void sync_handler_el0_32(pantheon::TrapFrame *Frame) { PANTHEON_UNUSED(Frame); - SERIAL_LOG_UNSAFE("%s\n", "ERR: SYNC HANDLER EL0_32"); + SERIAL_LOG("%s\n", "ERR: SYNC HANDLER EL0_32"); } extern "C" void err_handler_el0_32(pantheon::TrapFrame *Frame) { PANTHEON_UNUSED(Frame); - SERIAL_LOG_UNSAFE("%s\n", "ERR: ERR HANDLER EL0_32"); + SERIAL_LOG("%s\n", "ERR: ERR HANDLER EL0_32"); } extern "C" void fiq_handler_el0_32(pantheon::TrapFrame *Frame) { PANTHEON_UNUSED(Frame); - SERIAL_LOG_UNSAFE("%s\n", "ERR: FIQ HANDLER EL0_32"); + SERIAL_LOG("%s\n", "ERR: FIQ HANDLER EL0_32"); } extern "C" void irq_handler_el0_32(pantheon::TrapFrame *Frame) { PANTHEON_UNUSED(Frame); - SERIAL_LOG_UNSAFE("%s\n", "ERR: IRQ HANDLER EL0_32"); + SERIAL_LOG("%s\n", "ERR: IRQ HANDLER EL0_32"); } VOID pantheon::arm::LoadInterruptTable(VOID *Table) diff --git a/buildscripts/test-gdb-2smp.sh b/buildscripts/test-gdb-2smp.sh new file mode 100755 index 0000000..683d9d4 --- /dev/null +++ b/buildscripts/test-gdb-2smp.sh @@ -0,0 +1,10 @@ +#!/bin/sh + +rm -rf build +mkdir build +cd build +cmake .. -DCMAKE_TOOLCHAIN_FILE=../cmake/qemu-aarch64.cmake -DCMAKE_C_FLAGS="-g3 -O0" -DCMAKE_CXX_FLAGS="-g3 -O0" +make -j`nproc` +qemu-img create -fqcow2 emmc.img 4G +qemu-system-aarch64 -machine virt,gic-version=2,secure=on -smp 2 -cpu cortex-a72 -m 8G -kernel ./kernel.img -device sdhci-pci -device sd-card,drive=flash -drive id=flash,if=none,format=qcow2,file=emmc.img -s -S -d int + diff --git a/kern_main.cpp b/kern_main.cpp index ea6dfc3..c14d825 100644 --- a/kern_main.cpp +++ b/kern_main.cpp @@ -35,7 +35,8 @@ static void kern_basic_init(InitialBootInfo *InitBootInfo) pantheon::ipc::ServerPort::Init(); pantheon::ipc::ClientPort::Init(); pantheon::ipc::Connection::Init(); - pantheon::GlobalScheduler::Init(); + pantheon::Scheduler::Init(); + pantheon::LocalScheduler::Init(); } static void kern_stage2_init() @@ -44,7 +45,7 @@ static void kern_stage2_init() pantheon::UnpackInitPrograms(); } -extern "C" void kern_init_core() +static void kern_init_core() { UINT8 CpuNo = pantheon::CPU::GetProcessorNumber(); @@ -53,29 +54,22 @@ extern "C" void kern_init_core() /* Loop until core 0 finished essential kernel setup */ } - pantheon::CPU::InitCoreInfo(CpuNo); + pantheon::CPU::InitCore(CpuNo); PerCoreInit(); +} +static void kern_stage2_init_core() +{ + UINT8 CpuNo = pantheon::CPU::GetProcessorNumber(); while (pantheon::GetKernelStatus() < pantheon::KERNEL_STATUS_OK) { /* Loop until core 0 finished kernel setup */ } - SERIAL_LOG("Pantheon booted with core %hhu\n", CpuNo); pantheon::RearmSystemTimer(1000); pantheon::CPU::STI(); - - for (;;) - { - if (CpuNo == 0) - { - /* There's a few more races to deal with before enabling all-core scheduling */ - pantheon::CPU::GetCurSched()->Reschedule(); - } - } } - extern "C" void kern_init(InitialBootInfo *InitBootInfo) { if (pantheon::CPU::GetProcessorNumber() == 0) @@ -84,9 +78,20 @@ extern "C" void kern_init(InitialBootInfo *InitBootInfo) BoardRuntimeInit(); kern_basic_init(InitBootInfo); pantheon::SetKernelStatus(pantheon::KERNEL_STATUS_SECOND_STAGE); + } + + kern_init_core(); + + if (pantheon::CPU::GetProcessorNumber() == 0) + { kern_stage2_init(); pantheon::SetKernelStatus(pantheon::KERNEL_STATUS_OK); } - kern_init_core(); + + kern_stage2_init_core(); + for (;;) + { + pantheon::Scheduler::Reschedule(); + } pantheon::StopError("Broke out of reschedule loop\n"); }