/* * Copyright (c) 2018-2020 Atmosphère-NX * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU General Public License, * version 2, as published by the Free Software Foundation. * * This program is distributed in the hope it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #pragma once #include namespace ams::kern { template concept KPriorityQueueAffinityMask = !std::is_reference::value && requires (T &t) { { t.GetAffinityMask() } -> std::convertible_to; { t.SetAffinityMask(std::declval()) }; { t.GetAffinity(std::declval()) } -> std::same_as; { t.SetAffinity(std::declval(), std::declval()) }; { t.SetAll() }; }; template concept KPriorityQueueMember = !std::is_reference::value && requires (T &t) { { typename T::QueueEntry() }; { (typename T::QueueEntry()).Initialize() }; { (typename T::QueueEntry()).SetPrev(std::addressof(t)) }; { (typename T::QueueEntry()).SetNext(std::addressof(t)) }; { (typename T::QueueEntry()).GetNext() } -> std::same_as; { (typename T::QueueEntry()).GetPrev() } -> std::same_as; { t.GetPriorityQueueEntry(std::declval()) } -> std::same_as; { t.GetAffinityMask() }; { typename std::remove_cvref::type() } -> KPriorityQueueAffinityMask; { t.GetActiveCore() } -> std::convertible_to; { t.GetPriority() } -> std::convertible_to; }; template requires KPriorityQueueMember class KPriorityQueue { public: using AffinityMaskType = typename std::remove_cv().GetAffinityMask())>::type>::type; static_assert(LowestPriority >= 0); static_assert(HighestPriority >= 0); static_assert(LowestPriority >= HighestPriority); static constexpr size_t NumPriority = LowestPriority - HighestPriority + 1; static constexpr size_t NumCores = _NumCores; static constexpr ALWAYS_INLINE bool IsValidCore(s32 core) { return 0 <= core && core < static_cast(NumCores); } static constexpr ALWAYS_INLINE bool IsValidPriority(s32 priority) { return HighestPriority <= priority && priority <= LowestPriority + 1; } private: using Entry = typename Member::QueueEntry; public: class KPerCoreQueue { private: Entry root[NumCores]; public: constexpr ALWAYS_INLINE KPerCoreQueue() : root() { for (size_t i = 0; i < NumCores; i++) { this->root[i].Initialize(); } } constexpr ALWAYS_INLINE bool PushBack(s32 core, Member *member) { /* Get the entry associated with the member. */ Entry &member_entry = member->GetPriorityQueueEntry(core); /* Get the entry associated with the end of the queue. */ Member *tail = this->root[core].GetPrev(); Entry &tail_entry = (tail != nullptr) ? tail->GetPriorityQueueEntry(core) : this->root[core]; /* Link the entries. */ member_entry.SetPrev(tail); member_entry.SetNext(nullptr); tail_entry.SetNext(member); this->root[core].SetPrev(member); return (tail == nullptr); } constexpr ALWAYS_INLINE bool PushFront(s32 core, Member *member) { /* Get the entry associated with the member. */ Entry &member_entry = member->GetPriorityQueueEntry(core); /* Get the entry associated with the front of the queue. */ Member *head = this->root[core].GetNext(); Entry &head_entry = (head != nullptr) ? head->GetPriorityQueueEntry(core) : this->root[core]; /* Link the entries. */ member_entry.SetPrev(nullptr); member_entry.SetNext(head); head_entry.SetPrev(member); this->root[core].SetNext(member); return (head == nullptr); } constexpr ALWAYS_INLINE bool Remove(s32 core, Member *member) { /* Get the entry associated with the member. */ Entry &member_entry = member->GetPriorityQueueEntry(core); /* Get the entries associated with next and prev. */ Member *prev = member_entry.GetPrev(); Member *next = member_entry.GetNext(); Entry &prev_entry = (prev != nullptr) ? prev->GetPriorityQueueEntry(core) : this->root[core]; Entry &next_entry = (next != nullptr) ? next->GetPriorityQueueEntry(core) : this->root[core]; /* Unlink. */ prev_entry.SetNext(next); next_entry.SetPrev(prev); return (this->GetFront(core) == nullptr); } constexpr ALWAYS_INLINE Member *GetFront(s32 core) const { return this->root[core].GetNext(); } }; class KPriorityQueueImpl { private: KPerCoreQueue queues[NumPriority]; util::BitSet64 available_priorities[NumCores]; public: constexpr ALWAYS_INLINE KPriorityQueueImpl() : queues(), available_priorities() { /* ... */ } constexpr ALWAYS_INLINE void PushBack(s32 priority, s32 core, Member *member) { MESOSPHERE_ASSERT(IsValidCore(core)); MESOSPHERE_ASSERT(IsValidPriority(priority)); if (AMS_LIKELY(priority <= LowestPriority)) { if (this->queues[priority].PushBack(core, member)) { this->available_priorities[core].SetBit(priority); } } } constexpr ALWAYS_INLINE void PushFront(s32 priority, s32 core, Member *member) { MESOSPHERE_ASSERT(IsValidCore(core)); MESOSPHERE_ASSERT(IsValidPriority(priority)); if (AMS_LIKELY(priority <= LowestPriority)) { if (this->queues[priority].PushFront(core, member)) { this->available_priorities[core].SetBit(priority); } } } constexpr ALWAYS_INLINE void Remove(s32 priority, s32 core, Member *member) { MESOSPHERE_ASSERT(IsValidCore(core)); MESOSPHERE_ASSERT(IsValidPriority(priority)); if (AMS_LIKELY(priority <= LowestPriority)) { if (this->queues[priority].Remove(core, member)) { this->available_priorities[core].ClearBit(priority); } } } constexpr ALWAYS_INLINE Member *GetFront(s32 core) const { MESOSPHERE_ASSERT(IsValidCore(core)); const s32 priority = this->available_priorities[core].CountLeadingZero(); if (AMS_LIKELY(priority <= LowestPriority)) { return this->queues[priority].GetFront(core); } else { return nullptr; } } constexpr ALWAYS_INLINE Member *GetFront(s32 priority, s32 core) const { MESOSPHERE_ASSERT(IsValidCore(core)); MESOSPHERE_ASSERT(IsValidPriority(priority)); if (AMS_LIKELY(priority <= LowestPriority)) { return this->queues[priority].GetFront(core); } else { return nullptr; } } constexpr ALWAYS_INLINE Member *GetNext(s32 core, const Member *member) const { MESOSPHERE_ASSERT(IsValidCore(core)); Member *next = member->GetPriorityQueueEntry(core).GetNext(); if (next == nullptr) { const s32 priority = this->available_priorities[core].GetNextSet(member->GetPriority()); if (AMS_LIKELY(priority <= LowestPriority)) { next = this->queues[priority].GetFront(core); } } return next; } constexpr ALWAYS_INLINE void MoveToFront(s32 priority, s32 core, Member *member) { MESOSPHERE_ASSERT(IsValidCore(core)); MESOSPHERE_ASSERT(IsValidPriority(priority)); if (AMS_LIKELY(priority <= LowestPriority)) { this->queues[priority].Remove(core, member); this->queues[priority].PushFront(core, member); } } constexpr ALWAYS_INLINE Member *MoveToBack(s32 priority, s32 core, Member *member) { MESOSPHERE_ASSERT(IsValidCore(core)); MESOSPHERE_ASSERT(IsValidPriority(priority)); if (AMS_LIKELY(priority <= LowestPriority)) { this->queues[priority].Remove(core, member); this->queues[priority].PushBack(core, member); return this->queues[priority].GetFront(core); } else { return nullptr; } } }; private: KPriorityQueueImpl scheduled_queue; KPriorityQueueImpl suggested_queue; private: constexpr ALWAYS_INLINE void ClearAffinityBit(u64 &affinity, s32 core) { affinity &= ~(u64(1ul) << core); } constexpr ALWAYS_INLINE s32 GetNextCore(u64 &affinity) { const s32 core = __builtin_ctzll(static_cast(affinity)); ClearAffinityBit(affinity, core); return core; } constexpr ALWAYS_INLINE void PushBack(s32 priority, Member *member) { MESOSPHERE_ASSERT(IsValidPriority(priority)); /* Push onto the scheduled queue for its core, if we can. */ u64 affinity = member->GetAffinityMask().GetAffinityMask(); if (const s32 core = member->GetActiveCore(); core >= 0) { this->scheduled_queue.PushBack(priority, core, member); ClearAffinityBit(affinity, core); } /* And suggest the thread for all other cores. */ while (affinity) { this->suggested_queue.PushBack(priority, GetNextCore(affinity), member); } } constexpr ALWAYS_INLINE void PushFront(s32 priority, Member *member) { MESOSPHERE_ASSERT(IsValidPriority(priority)); /* Push onto the scheduled queue for its core, if we can. */ u64 affinity = member->GetAffinityMask().GetAffinityMask(); if (const s32 core = member->GetActiveCore(); core >= 0) { this->scheduled_queue.PushFront(priority, core, member); ClearAffinityBit(affinity, core); } /* And suggest the thread for all other cores. */ /* Note: Nintendo pushes onto the back of the suggested queue, not the front. */ while (affinity) { this->suggested_queue.PushBack(priority, GetNextCore(affinity), member); } } constexpr ALWAYS_INLINE void Remove(s32 priority, Member *member) { MESOSPHERE_ASSERT(IsValidPriority(priority)); /* Remove from the scheduled queue for its core. */ u64 affinity = member->GetAffinityMask().GetAffinityMask(); if (const s32 core = member->GetActiveCore(); core >= 0) { this->scheduled_queue.Remove(priority, core, member); ClearAffinityBit(affinity, core); } /* Remove from the suggested queue for all other cores. */ while (affinity) { this->suggested_queue.Remove(priority, GetNextCore(affinity), member); } } public: constexpr ALWAYS_INLINE KPriorityQueue() : scheduled_queue(), suggested_queue() { /* ... */ } /* Getters. */ constexpr ALWAYS_INLINE Member *GetScheduledFront(s32 core) const { return this->scheduled_queue.GetFront(core); } constexpr ALWAYS_INLINE Member *GetScheduledFront(s32 core, s32 priority) const { return this->scheduled_queue.GetFront(priority, core); } constexpr ALWAYS_INLINE Member *GetSuggestedFront(s32 core) const { return this->suggested_queue.GetFront(core); } constexpr ALWAYS_INLINE Member *GetSuggestedFront(s32 core, s32 priority) const { return this->suggested_queue.GetFront(priority, core); } constexpr ALWAYS_INLINE Member *GetScheduledNext(s32 core, const Member *member) const { return this->scheduled_queue.GetNext(core, member); } constexpr ALWAYS_INLINE Member *GetSuggestedNext(s32 core, const Member *member) const { return this->suggested_queue.GetNext(core, member); } constexpr ALWAYS_INLINE Member *GetSamePriorityNext(s32 core, const Member *member) const { return member->GetPriorityQueueEntry(core).GetNext(); } /* Mutators. */ constexpr ALWAYS_INLINE void PushBack(Member *member) { this->PushBack(member->GetPriority(), member); } constexpr ALWAYS_INLINE void Remove(Member *member) { this->Remove(member->GetPriority(), member); } constexpr ALWAYS_INLINE void MoveToScheduledFront(Member *member) { this->scheduled_queue.MoveToFront(member->GetPriority(), member->GetActiveCore(), member); } constexpr ALWAYS_INLINE KThread *MoveToScheduledBack(Member *member) { return this->scheduled_queue.MoveToBack(member->GetPriority(), member->GetActiveCore(), member); } /* First class fancy operations. */ constexpr ALWAYS_INLINE void ChangePriority(s32 prev_priority, bool is_running, Member *member) { MESOSPHERE_ASSERT(IsValidPriority(prev_priority)); /* Remove the member from the queues. */ const s32 new_priority = member->GetPriority(); this->Remove(prev_priority, member); /* And enqueue. If the member is running, we want to keep it running. */ if (is_running) { this->PushFront(new_priority, member); } else { this->PushBack(new_priority, member); } } constexpr ALWAYS_INLINE void ChangeAffinityMask(s32 prev_core, const AffinityMaskType &prev_affinity, Member *member) { /* Get the new information. */ const s32 priority = member->GetPriority(); const AffinityMaskType &new_affinity = member->GetAffinityMask(); const s32 new_core = member->GetActiveCore(); /* Remove the member from all queues it was in before. */ for (s32 core = 0; core < static_cast(NumCores); core++) { if (prev_affinity.GetAffinity(core)) { if (core == prev_core) { this->scheduled_queue.Remove(priority, core, member); } else { this->suggested_queue.Remove(priority, core, member); } } } /* And add the member to all queues it should be in now. */ for (s32 core = 0; core < static_cast(NumCores); core++) { if (new_affinity.GetAffinity(core)) { if (core == new_core) { this->scheduled_queue.PushBack(priority, core, member); } else { this->suggested_queue.PushBack(priority, core, member); } } } } constexpr ALWAYS_INLINE void ChangeCore(s32 prev_core, Member *member, bool to_front = false) { /* Get the new information. */ const s32 new_core = member->GetActiveCore(); const s32 priority = member->GetPriority(); /* We don't need to do anything if the core is the same. */ if (prev_core != new_core) { /* Remove from the scheduled queue for the previous core. */ if (prev_core >= 0) { this->scheduled_queue.Remove(priority, prev_core, member); } /* Remove from the suggested queue and add to the scheduled queue for the new core. */ if (new_core >= 0) { this->suggested_queue.Remove(priority, prev_core, member); if (to_front) { this->scheduled_queue.PushFront(priority, new_core, member); } else { this->scheduled_queue.PushBack(priority, new_core, member); } } /* Add to the suggested queue for the previous core. */ if (prev_core >= 0) { this->suggested_queue.PushBack(priority, prev_core, member); } } } }; }