diff --git a/libraries/libstratosphere/include/stratosphere/os.hpp b/libraries/libstratosphere/include/stratosphere/os.hpp index e67cd32a6..4db896902 100644 --- a/libraries/libstratosphere/include/stratosphere/os.hpp +++ b/libraries/libstratosphere/include/stratosphere/os.hpp @@ -33,6 +33,7 @@ #include #include #include +#include #include #include #include diff --git a/libraries/libstratosphere/include/stratosphere/os/os_timer_event.hpp b/libraries/libstratosphere/include/stratosphere/os/os_timer_event.hpp new file mode 100644 index 000000000..aff9beaf2 --- /dev/null +++ b/libraries/libstratosphere/include/stratosphere/os/os_timer_event.hpp @@ -0,0 +1,79 @@ +/* + * 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 +#include +#include + +namespace ams::os { + + class TimerEvent { + NON_COPYABLE(TimerEvent); + NON_MOVEABLE(TimerEvent); + private: + TimerEventType event; + public: + explicit TimerEvent(EventClearMode clear_mode) { + InitializeTimerEvent(std::addressof(this->event), clear_mode); + } + + ~TimerEvent() { + FinalizeTimerEvent(std::addressof(this->event)); + } + + void StartOneShot(TimeSpan first_time) { + return StartOneShotTimerEvent(std::addressof(this->event), first_time); + } + + void StartPeriodic(TimeSpan first_time, TimeSpan interval) { + return StartPeriodicTimerEvent(std::addressof(this->event), first_time, interval); + } + + void Stop() { + return StopTimerEvent(std::addressof(this->event)); + } + + void Wait() { + return WaitTimerEvent(std::addressof(this->event)); + } + + bool TryWait() { + return TryWaitTimerEvent(std::addressof(this->event)); + } + + void Signal() { + return SignalTimerEvent(std::addressof(this->event)); + } + + void Clear() { + return ClearTimerEvent(std::addressof(this->event)); + } + + operator TimerEventType &() { + return this->event; + } + + operator const TimerEventType &() const { + return this->event; + } + + TimerEventType *GetBase() { + return std::addressof(this->event); + } + }; + +} diff --git a/libraries/libstratosphere/include/stratosphere/os/os_timer_event_api.hpp b/libraries/libstratosphere/include/stratosphere/os/os_timer_event_api.hpp new file mode 100644 index 000000000..40d58ef51 --- /dev/null +++ b/libraries/libstratosphere/include/stratosphere/os/os_timer_event_api.hpp @@ -0,0 +1,41 @@ +/* + * 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 +#include + +namespace ams::os { + + struct TimerEventType; + struct WaitableHolderType; + + void InitializeTimerEvent(TimerEventType *event, EventClearMode clear_mode); + void FinalizeTimerEvent(TimerEventType *event); + + void StartOneShotTimerEvent(TimerEventType *event, TimeSpan first_time); + void StartPeriodicTimerEvent(TimerEventType *event, TimeSpan first_time, TimeSpan interval); + void StopTimerEvent(TimerEventType *event); + + void WaitTimerEvent(TimerEventType *event); + bool TryWaitTimerEvent(TimerEventType *event); + + void SignalTimerEvent(TimerEventType *event); + void ClearTimerEvent(TimerEventType *event); + + void InitializeWaitableHolder(WaitableHolderType *waitable_holder, TimerEventType *event); + +} diff --git a/libraries/libstratosphere/include/stratosphere/os/os_timer_event_types.hpp b/libraries/libstratosphere/include/stratosphere/os/os_timer_event_types.hpp new file mode 100644 index 000000000..44e465429 --- /dev/null +++ b/libraries/libstratosphere/include/stratosphere/os/os_timer_event_types.hpp @@ -0,0 +1,63 @@ +/* + * 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 +#include +#include +#include + +namespace ams::os { + + namespace impl { + + class WaitableObjectList; + + } + + struct TimerEventType { + using TimeSpanStorage = TYPED_STORAGE(TimeSpan); + + enum State { + State_NotInitialized = 0, + State_Initialized = 1, + }; + + enum TimerState { + TimerState_Stop = 0, + TimerState_OneShot = 1, + TimerState_Periodic = 2, + }; + + util::TypedStorage waitable_object_list_storage; + + u8 state; + u8 clear_mode; + bool signaled; + u8 timer_state; + u32 broadcast_counter_low; + u32 broadcast_counter_high; + + TimeSpanStorage next_time_to_wakeup; + TimeSpanStorage first; + TimeSpanStorage interval; + + impl::InternalCriticalSectionStorage cs_timer_event; + impl::InternalConditionVariableStorage cv_signaled; + }; + static_assert(std::is_trivial::value); + +} diff --git a/libraries/libstratosphere/source/os/impl/os_timer_event_helper.cpp b/libraries/libstratosphere/source/os/impl/os_timer_event_helper.cpp new file mode 100644 index 000000000..68e3e42bc --- /dev/null +++ b/libraries/libstratosphere/source/os/impl/os_timer_event_helper.cpp @@ -0,0 +1,73 @@ +/* + * 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 +#include "os_timer_event_helper.hpp" + +namespace ams::os::impl { + + TimeSpan SaturatedAdd(TimeSpan t1, TimeSpan t2) { + AMS_ASSERT(t1 >= 0); + AMS_ASSERT(t2 >= 0); + + const u64 u1 = t1.GetNanoSeconds(); + const u64 u2 = t2.GetNanoSeconds(); + + const u64 sum = u1 + u2; + return TimeSpan::FromNanoSeconds(std::min(std::numeric_limits::max(), sum)); + } + + void StopTimerUnsafe(TimerEventType *event) { + GetReference(event->next_time_to_wakeup) = 0; + event->timer_state = TimerEventType::TimerState_Stop; + } + + bool UpdateSignalStateAndRecalculateNextTimeToWakeupUnsafe(TimerEventType *event, TimeSpan cur_time) { + TimeSpan next_time = GetReference(event->next_time_to_wakeup); + + switch (event->timer_state) { + case TimerEventType::TimerState_Stop: + break; + case TimerEventType::TimerState_OneShot: + if (next_time < cur_time) { + event->signaled = true; + StopTimerUnsafe(event); + return true; + } + break; + case TimerEventType::TimerState_Periodic: + if (next_time < cur_time) { + event->signaled = true; + + next_time = SaturatedAdd(next_time, GetReference(event->interval)); + if (next_time < cur_time) { + const u64 elapsed_from_first = (cur_time - GetReference(event->first)).GetNanoSeconds(); + const u64 interval = GetReference(event->interval).GetNanoSeconds(); + + const u64 t = std::min(std::numeric_limits::max(), ((elapsed_from_first / interval) + 1) * interval); + next_time = SaturatedAdd(TimeSpan::FromNanoSeconds(t), GetReference(event->first)); + } + + GetReference(event->next_time_to_wakeup) = next_time; + return true; + } + break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + + return false; + } + +} diff --git a/libraries/libstratosphere/source/os/impl/os_timer_event_helper.hpp b/libraries/libstratosphere/source/os/impl/os_timer_event_helper.hpp new file mode 100644 index 000000000..e23c5fae4 --- /dev/null +++ b/libraries/libstratosphere/source/os/impl/os_timer_event_helper.hpp @@ -0,0 +1,31 @@ +/* + * 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::os { + + struct TimerEventType; + + namespace impl { + + TimeSpan SaturatedAdd(TimeSpan t1, TimeSpan t2); + void StopTimerUnsafe(TimerEventType *event); + bool UpdateSignalStateAndRecalculateNextTimeToWakeupUnsafe(TimerEventType *event, TimeSpan cur_time); + + } + +} \ No newline at end of file diff --git a/libraries/libstratosphere/source/os/impl/os_waitable_holder_impl.hpp b/libraries/libstratosphere/source/os/impl/os_waitable_holder_impl.hpp index 25b3c02a7..90a3e4631 100644 --- a/libraries/libstratosphere/source/os/impl/os_waitable_holder_impl.hpp +++ b/libraries/libstratosphere/source/os/impl/os_waitable_holder_impl.hpp @@ -18,6 +18,7 @@ #include "os_waitable_holder_of_event.hpp" #include "os_waitable_holder_of_inter_process_event.hpp" #include "os_waitable_holder_of_interrupt_event.hpp" +#include "os_waitable_holder_of_timer_event.hpp" #include "os_waitable_holder_of_thread.hpp" #include "os_waitable_holder_of_semaphore.hpp" #include "os_waitable_holder_of_message_queue.hpp" @@ -30,6 +31,7 @@ namespace ams::os::impl { TYPED_STORAGE(WaitableHolderOfEvent) holder_of_event_storage; TYPED_STORAGE(WaitableHolderOfInterProcessEvent) holder_of_inter_process_event_storage; TYPED_STORAGE(WaitableHolderOfInterruptEvent) holder_of_interrupt_event_storage; + TYPED_STORAGE(WaitableHolderOfTimerEvent) holder_of_timer_event_storage; TYPED_STORAGE(WaitableHolderOfThread) holder_of_thread_storage; TYPED_STORAGE(WaitableHolderOfSemaphore) holder_of_semaphore_storage; TYPED_STORAGE(WaitableHolderOfMessageQueueForNotFull) holder_of_mq_for_not_full_storage; @@ -44,6 +46,7 @@ namespace ams::os::impl { CHECK_HOLDER(WaitableHolderOfEvent); CHECK_HOLDER(WaitableHolderOfInterProcessEvent); CHECK_HOLDER(WaitableHolderOfInterruptEvent); + CHECK_HOLDER(WaitableHolderOfTimerEvent); CHECK_HOLDER(WaitableHolderOfThread); CHECK_HOLDER(WaitableHolderOfSemaphore); CHECK_HOLDER(WaitableHolderOfMessageQueueForNotFull); diff --git a/libraries/libstratosphere/source/os/impl/os_waitable_holder_of_timer_event.hpp b/libraries/libstratosphere/source/os/impl/os_waitable_holder_of_timer_event.hpp new file mode 100644 index 000000000..949df7b7c --- /dev/null +++ b/libraries/libstratosphere/source/os/impl/os_waitable_holder_of_timer_event.hpp @@ -0,0 +1,67 @@ +/* + * 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 +#include "os_timer_event_helper.hpp" +#include "os_waitable_holder_base.hpp" +#include "os_waitable_object_list.hpp" + +namespace ams::os::impl { + + class WaitableHolderOfTimerEvent : public WaitableHolderOfUserObject { + private: + TimerEventType *event; + private: + TriBool IsSignaledImpl() const { + TimeSpan cur_time = this->GetManager()->GetCurrentTime(); + UpdateSignalStateAndRecalculateNextTimeToWakeupUnsafe(this->event, cur_time); + return this->event->signaled ? TriBool::True : TriBool::False; + } + public: + explicit WaitableHolderOfTimerEvent(TimerEventType *e) : event(e) { /* ... */ } + + /* IsSignaled, Link, Unlink implemented. */ + virtual TriBool IsSignaled() const override { + std::scoped_lock lk(GetReference(this->event->cs_timer_event)); + return this->IsSignaledImpl(); + } + + virtual TriBool LinkToObjectList() override { + std::scoped_lock lk(GetReference(this->event->cs_timer_event)); + + GetReference(this->event->waitable_object_list_storage).LinkWaitableHolder(*this); + return this->IsSignaledImpl(); + } + + virtual void UnlinkFromObjectList() override { + std::scoped_lock lk(GetReference(this->event->cs_timer_event)); + + GetReference(this->event->waitable_object_list_storage).UnlinkWaitableHolder(*this); + } + + /* Gets the amount of time remaining until this wakes up. */ + virtual TimeSpan GetAbsoluteWakeupTime() const override { + std::scoped_lock lk(GetReference(this->event->cs_timer_event)); + + if (this->event->timer_state == TimerEventType::TimerState_Stop) { + return TimeSpan::FromNanoSeconds(std::numeric_limits::max()); + } + + return GetReference(this->event->next_time_to_wakeup); + } + }; + +} diff --git a/libraries/libstratosphere/source/os/os_timer_event.cpp b/libraries/libstratosphere/source/os/os_timer_event.cpp new file mode 100644 index 000000000..ec1b120e4 --- /dev/null +++ b/libraries/libstratosphere/source/os/os_timer_event.cpp @@ -0,0 +1,263 @@ +/* + * 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 +#include "impl/os_timer_event_helper.hpp" +#include "impl/os_tick_manager.hpp" +#include "impl/os_timeout_helper.hpp" +#include "impl/os_waitable_object_list.hpp" +#include "impl/os_waitable_holder_impl.hpp" + +namespace ams::os { + + namespace { + + ALWAYS_INLINE u64 GetBroadcastCounterUnsafe(TimerEventType *event) { + const u64 upper = event->broadcast_counter_high; + return (upper << BITSIZEOF(event->broadcast_counter_low)) | event->broadcast_counter_low; + } + + ALWAYS_INLINE void IncrementBroadcastCounterUnsafe(TimerEventType *event) { + if ((++event->broadcast_counter_low) == 0) { + ++event->broadcast_counter_high; + } + } + + ALWAYS_INLINE void SignalTimerEventImplUnsafe(TimerEventType *event) { + /* Set signaled. */ + event->signaled = true; + + /* Signal! */ + if (event->clear_mode == EventClearMode_ManualClear) { + /* If we're manual clear, increment counter and wake all. */ + IncrementBroadcastCounterUnsafe(event); + } else { + /* If we're auto clear, signal one thread, which will clear. */ + GetReference(event->cv_signaled).Signal(); + } + + /* Wake up whatever manager, if any. */ + GetReference(event->waitable_object_list_storage).SignalAllThreads(); + } + + } + + void InitializeTimerEvent(TimerEventType *event, EventClearMode clear_mode) { + /* Initialize internal variables. */ + new (GetPointer(event->cs_timer_event)) impl::InternalCriticalSection; + new (GetPointer(event->cv_signaled)) impl::InternalConditionVariable; + + /* Initialize the waitable object list. */ + new (GetPointer(event->waitable_object_list_storage)) impl::WaitableObjectList(); + + /* Initialize member variables. */ + event->clear_mode = static_cast(clear_mode); + event->signaled = false; + event->timer_state = TimerEventType::TimerState_Stop; + event->broadcast_counter_low = 0; + event->broadcast_counter_high = 0; + + GetReference(event->next_time_to_wakeup) = 0; + GetReference(event->first) = 0; + GetReference(event->interval) = 0; + + /* Mark initialized. */ + event->state = TimerEventType::State_Initialized; + } + + void FinalizeTimerEvent(TimerEventType *event) { + + /* Mark uninitialized. */ + event->state = TimerEventType::State_NotInitialized; + + /* Destroy objects. */ + GetReference(event->waitable_object_list_storage).~WaitableObjectList(); + GetReference(event->cv_signaled).~InternalConditionVariable(); + GetReference(event->cs_timer_event).~InternalCriticalSection(); + } + + void StartOneShotTimerEvent(TimerEventType *event, TimeSpan first_time) { + AMS_ASSERT(event->state == TimerEventType::State_Initialized); + AMS_ASSERT(first_time >= 0); + + { + std::scoped_lock lk(GetReference(event->cs_timer_event)); + + /* Get the current time. */ + TimeSpan cur_time = impl::GetCurrentTick().ToTimeSpan(); + + /* Set tracking timespans. */ + GetReference(event->next_time_to_wakeup) = impl::SaturatedAdd(cur_time, first_time); + GetReference(event->first) = first_time; + GetReference(event->interval) = 0; + + /* Set state to OneShot. */ + event->timer_state = TimerEventType::TimerState_OneShot; + + /* Signal. */ + GetReference(event->cv_signaled).Broadcast(); + + /* Wake up whatever manager, if any. */ + GetReference(event->waitable_object_list_storage).SignalAllThreads(); + } + } + + void StartPeriodicTimerEvent(TimerEventType *event, TimeSpan first_time, TimeSpan interval) { + AMS_ASSERT(event->state == TimerEventType::State_Initialized); + AMS_ASSERT(first_time >= 0); + AMS_ASSERT(interval > 0); + + { + std::scoped_lock lk(GetReference(event->cs_timer_event)); + + /* Get the current time. */ + TimeSpan cur_time = impl::GetCurrentTick().ToTimeSpan(); + + /* Set tracking timespans. */ + GetReference(event->next_time_to_wakeup) = impl::SaturatedAdd(cur_time, first_time); + GetReference(event->first) = first_time; + GetReference(event->interval) = interval; + + /* Set state to Periodic. */ + event->timer_state = TimerEventType::TimerState_Periodic; + + /* Signal. */ + GetReference(event->cv_signaled).Broadcast(); + + /* Wake up whatever manager, if any. */ + GetReference(event->waitable_object_list_storage).SignalAllThreads(); + } + } + + void StopTimerEvent(TimerEventType *event) { + AMS_ASSERT(event->state == TimerEventType::State_Initialized); + + { + std::scoped_lock lk(GetReference(event->cs_timer_event)); + + /* Stop the event. */ + impl::StopTimerUnsafe(event); + + /* Signal. */ + GetReference(event->cv_signaled).Broadcast(); + + /* Wake up whatever manager, if any. */ + GetReference(event->waitable_object_list_storage).SignalAllThreads(); + } + } + + void WaitTimerEvent(TimerEventType *event) { + AMS_ASSERT(event->state == TimerEventType::State_Initialized); + + { + std::scoped_lock lk(GetReference(event->cs_timer_event)); + + const auto cur_counter = GetBroadcastCounterUnsafe(event); + while (!event->signaled) { + if (cur_counter != GetBroadcastCounterUnsafe(event)) { + break; + } + + /* Update. */ + auto cur_time = impl::GetCurrentTick().ToTimeSpan(); + if (impl::UpdateSignalStateAndRecalculateNextTimeToWakeupUnsafe(event, cur_time)) { + SignalTimerEventImplUnsafe(event); + break; + } + + /* Check state. */ + if (event->timer_state == TimerEventType::TimerState_Stop) { + GetReference(event->cv_signaled).Wait(GetPointer(event->cs_timer_event)); + } else { + TimeSpan next_time = GetReference(event->next_time_to_wakeup); + impl::TimeoutHelper helper(next_time - cur_time); + GetReference(event->cv_signaled).TimedWait(GetPointer(event->cs_timer_event), helper); + } + } + + if (event->clear_mode == EventClearMode_AutoClear) { + event->signaled = false; + } + } + } + + bool TryWaitTimerEvent(TimerEventType *event) { + AMS_ASSERT(event->state == TimerEventType::State_Initialized); + + { + std::scoped_lock lk(GetReference(event->cs_timer_event)); + + /* Update. */ + auto cur_time = impl::GetCurrentTick().ToTimeSpan(); + if (impl::UpdateSignalStateAndRecalculateNextTimeToWakeupUnsafe(event, cur_time)) { + SignalTimerEventImplUnsafe(event); + } + + bool prev = event->signaled; + + if (event->clear_mode == EventClearMode_AutoClear) { + event->signaled = false; + } + + return prev; + } + } + + void SignalTimerEvent(TimerEventType *event) { + AMS_ASSERT(event->state == TimerEventType::State_Initialized); + + { + std::scoped_lock lk(GetReference(event->cs_timer_event)); + + /* If we're signaled, nothing to do. */ + if (event->signaled) { + return; + } + + /* Update. */ + auto cur_time = impl::GetCurrentTick().ToTimeSpan(); + impl::UpdateSignalStateAndRecalculateNextTimeToWakeupUnsafe(event, cur_time); + + /* Signal. */ + SignalTimerEventImplUnsafe(event); + } + } + + void ClearTimerEvent(TimerEventType *event) { + AMS_ASSERT(event->state == TimerEventType::State_Initialized); + + { + std::scoped_lock lk(GetReference(event->cs_timer_event)); + + /* Update. */ + auto cur_time = impl::GetCurrentTick().ToTimeSpan(); + if (impl::UpdateSignalStateAndRecalculateNextTimeToWakeupUnsafe(event, cur_time)) { + SignalTimerEventImplUnsafe(event); + } + + /* Clear. */ + event->signaled = false; + } + } + + void InitializeWaitableHolder(WaitableHolderType *waitable_holder, TimerEventType *event) { + AMS_ASSERT(event->state == EventType::State_Initialized); + + new (GetPointer(waitable_holder->impl_storage)) impl::WaitableHolderOfTimerEvent(event); + + waitable_holder->user_data = 0; + } + +}