/* * 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 . */ #include namespace ams::kern { namespace { class KDpcTask { private: static inline KLightLock s_lock; static inline KLightConditionVariable s_cond_var; static inline u64 s_core_mask; static inline KDpcTask *s_task; private: static bool HasRequest(s32 core_id) { return (s_core_mask & (1ull << core_id)) != 0; } static void SetRequest(s32 core_id) { s_core_mask |= (1ull << core_id); } static void ClearRequest(s32 core_id) { s_core_mask &= ~(1ull << core_id); } public: virtual void DoTask() { /* ... */ } static void WaitForRequest() { /* Wait for a request to come in. */ const auto core_id = GetCurrentCoreId(); KScopedLightLock lk(s_lock); while (!HasRequest(core_id)) { s_cond_var.Wait(&s_lock, -1ll); } } static bool TimedWaitForRequest(s64 timeout) { /* Wait for a request to come in. */ const auto core_id = GetCurrentCoreId(); KScopedLightLock lk(s_lock); while (!HasRequest(core_id)) { s_cond_var.Wait(&s_lock, timeout); if (KHardwareTimer::GetTick() >= timeout) { return false; } } return true; } static void HandleRequest() { /* Perform the request. */ s_task->DoTask(); /* Clear the request. */ const auto core_id = GetCurrentCoreId(); KScopedLightLock lk(s_lock); ClearRequest(core_id); if (s_core_mask == 0) { s_cond_var.Broadcast(); } } }; /* Convenience definitions. */ constexpr s32 DpcManagerThreadPriority = 3; constexpr s64 DpcManagerTimeout = ams::svc::Tick(TimeSpan::FromMilliSeconds(10)); /* Globals. */ s64 g_preemption_priorities[cpu::NumCores]; /* Manager thread functions. */ void DpcManagerNormalThreadFunction(uintptr_t arg) { /* Input argument goes unused. */ MESOSPHERE_UNUSED(arg); /* Forever wait and service requests. */ while (true) { KDpcTask::WaitForRequest(); KDpcTask::HandleRequest(); } } void DpcManagerPreemptionThreadFunction(uintptr_t arg) { /* Input argument goes unused. */ MESOSPHERE_UNUSED(arg); /* Forever wait and service requests, rotating the scheduled queue every 10 ms. */ s64 timeout = KHardwareTimer::GetTick() + DpcManagerTimeout; while (true) { if (KDpcTask::TimedWaitForRequest(timeout)) { KDpcTask::HandleRequest(); } else { /* Rotate the scheduler queue for each core. */ KScopedSchedulerLock lk; for (size_t core_id = 0; core_id < cpu::NumCores; core_id++) { if (const s32 priority = g_preemption_priorities[core_id]; priority > DpcManagerThreadPriority) { KScheduler::RotateScheduledQueue(static_cast(core_id), priority); } } /* Update our next timeout. */ timeout = KHardwareTimer::GetTick() + DpcManagerTimeout; } } } } void KDpcManager::Initialize(s32 core_id, s32 priority) { /* Reserve a thread from the system limit. */ MESOSPHERE_ABORT_UNLESS(Kernel::GetSystemResourceLimit().Reserve(ams::svc::LimitableResource_ThreadCountMax, 1)); /* Create a new thread. */ KThread *new_thread = KThread::Create(); MESOSPHERE_ABORT_UNLESS(new_thread != nullptr); /* Launch the new thread. */ g_preemption_priorities[core_id] = priority; if (core_id == cpu::NumCores - 1) { MESOSPHERE_R_ABORT_UNLESS(KThread::InitializeKernelThread(new_thread, DpcManagerPreemptionThreadFunction, 0, DpcManagerThreadPriority, core_id)); } else { MESOSPHERE_R_ABORT_UNLESS(KThread::InitializeKernelThread(new_thread, DpcManagerNormalThreadFunction, 0, DpcManagerThreadPriority, core_id)); } /* Register the new thread. */ KThread::Register(new_thread); /* Run the thread. */ new_thread->Run(); } void KDpcManager::HandleDpc() { /* The only deferred procedure supported by Horizon is thread termination. */ /* Check if we need to terminate the current thread. */ KThread *cur_thread = GetCurrentThreadPointer(); if (cur_thread->IsTerminationRequested()) { KScopedInterruptEnable ei; cur_thread->Exit(); } } }