diff --git a/libraries/libstratosphere/include/stratosphere/os.hpp b/libraries/libstratosphere/include/stratosphere/os.hpp index d23174b52..c24591d7e 100644 --- a/libraries/libstratosphere/include/stratosphere/os.hpp +++ b/libraries/libstratosphere/include/stratosphere/os.hpp @@ -44,7 +44,7 @@ #include #include #include -//#include +#include //#include //#include #include diff --git a/libraries/libstratosphere/include/stratosphere/os/impl/os_internal_light_event.hpp b/libraries/libstratosphere/include/stratosphere/os/impl/os_internal_light_event.hpp new file mode 100644 index 000000000..3cfb45d3b --- /dev/null +++ b/libraries/libstratosphere/include/stratosphere/os/impl/os_internal_light_event.hpp @@ -0,0 +1,51 @@ +/* + * 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 + +#if defined(ATMOSPHERE_OS_HORIZON) + #include +#else + #error "Unknown OS for ams::os::impl::InternalLightEventImpl" +#endif + +namespace ams::os::impl { + + class InternalLightEvent { + private: + InternalLightEventImpl m_impl; + public: + explicit InternalLightEvent(bool signaled) : m_impl(signaled) { /* ... */ } + + ALWAYS_INLINE void SignalWithAutoClear() { return m_impl.SignalWithAutoClear(); } + ALWAYS_INLINE void SignalWithManualClear() { return m_impl.SignalWithManualClear(); } + + ALWAYS_INLINE void Clear() { return m_impl.Clear(); } + + ALWAYS_INLINE void WaitWithAutoClear() { return m_impl.WaitWithAutoClear(); } + ALWAYS_INLINE void WaitWithManualClear() { return m_impl.WaitWithManualClear(); } + + ALWAYS_INLINE bool TryWaitWithAutoClear() { return m_impl.TryWaitWithAutoClear(); } + ALWAYS_INLINE bool TryWaitWithManualClear() { return m_impl.TryWaitWithManualClear(); } + + ALWAYS_INLINE bool TimedWaitWithAutoClear(const TimeoutHelper &timeout_helper) { return m_impl.TimedWaitWithAutoClear(timeout_helper); } + ALWAYS_INLINE bool TimedWaitWithManualClear(const TimeoutHelper &timeout_helper) { return m_impl.TimedWaitWithManualClear(timeout_helper); } + }; + + using InternalLightEventStorage = util::TypedStorage; + +} diff --git a/libraries/libstratosphere/include/stratosphere/os/impl/os_internal_light_event_impl.os.horizon.hpp b/libraries/libstratosphere/include/stratosphere/os/impl/os_internal_light_event_impl.os.horizon.hpp new file mode 100644 index 000000000..c4854d54c --- /dev/null +++ b/libraries/libstratosphere/include/stratosphere/os/impl/os_internal_light_event_impl.os.horizon.hpp @@ -0,0 +1,49 @@ +/* + * 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::impl { + + class TimeoutHelper; + + class InternalLightEventImpl { + private: + std::atomic m_state; + u32 m_padding; + public: + explicit InternalLightEventImpl(bool signaled) { this->Initialize(signaled); } + ~InternalLightEventImpl() { this->Finalize(); } + + void Initialize(bool signaled); + void Finalize(); + + void SignalWithAutoClear(); + void SignalWithManualClear(); + + void Clear(); + + void WaitWithAutoClear(); + void WaitWithManualClear(); + + bool TryWaitWithAutoClear(); + bool TryWaitWithManualClear(); + + bool TimedWaitWithAutoClear(const TimeoutHelper &timeout_helper); + bool TimedWaitWithManualClear(const TimeoutHelper &timeout_helper); + }; + +} diff --git a/libraries/libstratosphere/include/stratosphere/os/os_light_event.hpp b/libraries/libstratosphere/include/stratosphere/os/os_light_event.hpp new file mode 100644 index 000000000..43516f4c9 --- /dev/null +++ b/libraries/libstratosphere/include/stratosphere/os/os_light_event.hpp @@ -0,0 +1,72 @@ +/* + * 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 { + + class LightEvent { + NON_COPYABLE(LightEvent); + NON_MOVEABLE(LightEvent); + private: + LightEventType m_event; + public: + explicit LightEvent(EventClearMode clear_mode) { + InitializeLightEvent(std::addressof(m_event), false, clear_mode); + } + + ~LightEvent() { + FinalizeLightEvent(std::addressof(m_event)); + } + + void Wait() { + return WaitLightEvent(std::addressof(m_event)); + } + + bool TryWait() { + return TryWaitLightEvent(std::addressof(m_event)); + } + + bool TimedWait(TimeSpan timeout) { + return TimedWaitLightEvent(std::addressof(m_event), timeout); + } + + void Signal() { + return SignalLightEvent(std::addressof(m_event)); + } + + void Clear() { + return ClearLightEvent(std::addressof(m_event)); + } + + operator LightEventType &() { + return m_event; + } + + operator const LightEventType &() const { + return m_event; + } + + LightEventType *GetBase() { + return std::addressof(m_event); + } + }; + +} diff --git a/libraries/libstratosphere/include/stratosphere/os/os_light_event_api.hpp b/libraries/libstratosphere/include/stratosphere/os/os_light_event_api.hpp new file mode 100644 index 000000000..73e753c5f --- /dev/null +++ b/libraries/libstratosphere/include/stratosphere/os/os_light_event_api.hpp @@ -0,0 +1,34 @@ +/* + * 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 LightEventType; + + void InitializeLightEvent(LightEventType *event, bool signaled, EventClearMode clear_mode); + void FinalizeLightEvent(LightEventType *event); + + void SignalLightEvent(LightEventType *event); + void WaitLightEvent(LightEventType *event); + bool TryWaitLightEvent(LightEventType *event); + bool TimedWaitLightEvent(LightEventType *event, TimeSpan timeout); + void ClearLightEvent(LightEventType *event); + +} diff --git a/libraries/libstratosphere/include/stratosphere/os/os_light_event_types.hpp b/libraries/libstratosphere/include/stratosphere/os/os_light_event_types.hpp new file mode 100644 index 000000000..5e60774d6 --- /dev/null +++ b/libraries/libstratosphere/include/stratosphere/os/os_light_event_types.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 +#include + +namespace ams::os { + + struct LightEventType { + bool is_auto_clear; + bool is_initialized; + + impl::InternalLightEventStorage storage; + }; + static_assert(std::is_trivial::value); + +} diff --git a/libraries/libstratosphere/source/os/impl/os_internal_light_event_impl.os.horizon.hpp b/libraries/libstratosphere/source/os/impl/os_internal_light_event_impl.os.horizon.hpp new file mode 100644 index 000000000..f2d19bdc0 --- /dev/null +++ b/libraries/libstratosphere/source/os/impl/os_internal_light_event_impl.os.horizon.hpp @@ -0,0 +1,299 @@ +/* + * 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::impl { + + namespace { + + constexpr inline s32 LightEventState_NotSignaledAndNoWaiter = 0; + constexpr inline s32 LightEventState_NotSignaledAndWaiter = 1; + constexpr inline s32 LightEventState_Signaled = 2; + + ALWAYS_INLINE uintptr_t GetAddressOfAtomicInteger(std::atomic *ptr) { + static_assert(sizeof(*ptr) == sizeof(s32)); + static_assert(alignof(*ptr) == alignof(s32)); + static_assert(std::atomic::is_always_lock_free); + + return reinterpret_cast(ptr); + } + + ALWAYS_INLINE bool SvcSignalToAddressForAutoClear(std::atomic *state, s32 expected) { + /* Signal. */ + R_TRY_CATCH(svc::SignalToAddress(GetAddressOfAtomicInteger(state), svc::SignalType_SignalAndModifyByWaitingCountIfEqual, expected, 1)) { + R_CATCH(svc::ResultInvalidState) { return false; } + } R_END_TRY_CATCH_WITH_ABORT_UNLESS; + + return true; + } + + ALWAYS_INLINE bool SvcSignalToAddressForManualClear(std::atomic *state, s32 expected) { + /* Signal. */ + R_TRY_CATCH(svc::SignalToAddress(GetAddressOfAtomicInteger(state), svc::SignalType_SignalAndIncrementIfEqual, expected, -1)) { + R_CATCH(svc::ResultInvalidState) { return false; } + } R_END_TRY_CATCH_WITH_ABORT_UNLESS; + + return true; + } + + ALWAYS_INLINE bool SvcWaitForAddressIfEqual(std::atomic *state) { + /* Wait. */ + R_TRY_CATCH(svc::WaitForAddress(GetAddressOfAtomicInteger(state), svc::ArbitrationType_WaitIfEqual, LightEventState_NotSignaledAndWaiter, -1ll)) { + R_CATCH(svc::ResultInvalidState) { return false; } + } R_END_TRY_CATCH_WITH_ABORT_UNLESS; + + return true; + } + + ALWAYS_INLINE bool SvcWaitForAddressIfEqual(bool *out_timed_out, std::atomic *state, s64 timeout) { + /* Default to not timed out. */ + *out_timed_out = false; + + /* Wait. */ + R_TRY_CATCH(svc::WaitForAddress(GetAddressOfAtomicInteger(state), svc::ArbitrationType_WaitIfEqual, LightEventState_NotSignaledAndWaiter, timeout)) { + R_CATCH(svc::ResultInvalidState) { return false; } + R_CATCH(svc::ResultTimedOut) { *out_timed_out = true; return false; } + } R_END_TRY_CATCH_WITH_ABORT_UNLESS; + + return true; + } + + ALWAYS_INLINE bool CompareAndSwap(std::atomic *state, s32 expected, s32 desired) { + return state->compare_exchange_strong(expected, desired); + } + + } + + void InternalLightEventImpl::Initialize(bool signaled) { + /* Set initial state. */ + m_state.store(signaled ? LightEventState_Signaled : LightEventState_NotSignaledAndNoWaiter, std::memory_order_relaxed); + } + + void InternalLightEventImpl::Finalize() { + /* ... */ + } + + void InternalLightEventImpl::SignalWithAutoClear() { + /* Loop until signaled */ + while (true) { + /* Fence memory. */ + std::atomic_thread_fence(std::memory_order_seq_cst); + + /* Get the current state. */ + const auto state = m_state.load(std::memory_order_relaxed); + switch (state) { + case LightEventState_NotSignaledAndNoWaiter: + /* Try to signal. */ + if (CompareAndSwap(std::addressof(m_state), state, LightEventState_Signaled)) { + return; + } + break; + case LightEventState_NotSignaledAndWaiter: + /* Try to signal. */ + if (SvcSignalToAddressForAutoClear(std::addressof(m_state), state)) { + return; + } + break; + case LightEventState_Signaled: + /* If we're already signaled, we're done. */ + return; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + } + + void InternalLightEventImpl::SignalWithManualClear() { + /* Loop until signaled */ + while (true) { + /* Fence memory. */ + std::atomic_thread_fence(std::memory_order_seq_cst); + + /* Get the current state. */ + const auto state = m_state.load(std::memory_order_relaxed); + switch (state) { + case LightEventState_NotSignaledAndNoWaiter: + /* Try to signal. */ + if (CompareAndSwap(std::addressof(m_state), state, LightEventState_Signaled)) { + return; + } + break; + case LightEventState_NotSignaledAndWaiter: + /* Try to signal. */ + if (SvcSignalToAddressForManualClear(std::addressof(m_state), state)) { + return; + } + break; + case LightEventState_Signaled: + /* If we're already signaled, we're done. */ + return; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + } + + void InternalLightEventImpl::Clear() { + /* Fence memory. */ + std::atomic_thread_fence(std::memory_order_acquire); + ON_SCOPE_EXIT { std::atomic_thread_fence(std::memory_order_release); }; + + /* Change state from signaled to not-signaled. */ + CompareAndSwap(std::addressof(m_state), LightEventState_Signaled, LightEventState_NotSignaledAndNoWaiter); + } + + void InternalLightEventImpl::WaitWithAutoClear() { + /* When we're done, fence memory. */ + ON_SCOPE_EXIT { std::atomic_thread_fence(std::memory_order_seq_cst); }; + + /* Loop waiting. */ + while (true) { + /* Get the current state. */ + const auto state = m_state.load(std::memory_order_acquire); + switch (state) { + case LightEventState_NotSignaledAndNoWaiter: + /* Change state to have waiter. */ + if (!CompareAndSwap(std::addressof(m_state), state, LightEventState_NotSignaledAndWaiter)) { + break; + } + [[fallthrough]]; + case LightEventState_NotSignaledAndWaiter: + /* Wait for the address. */ + if (SvcWaitForAddressIfEqual(std::addressof(m_state))) { + return; + } + break; + case LightEventState_Signaled: + /* Change state to no-waiters. */ + if (CompareAndSwap(std::addressof(m_state), state, LightEventState_NotSignaledAndNoWaiter)) { + return; + } + break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + } + + void InternalLightEventImpl::WaitWithManualClear() { + /* When we're done, fence memory. */ + ON_SCOPE_EXIT { std::atomic_thread_fence(std::memory_order_seq_cst); }; + + /* Loop waiting. */ + while (true) { + /* Get the current state. */ + const auto state = m_state.load(std::memory_order_acquire); + switch (state) { + case LightEventState_NotSignaledAndNoWaiter: + /* Change state to have waiter. */ + if (!CompareAndSwap(std::addressof(m_state), state, LightEventState_NotSignaledAndWaiter)) { + break; + } + [[fallthrough]]; + case LightEventState_NotSignaledAndWaiter: + /* Wait for the address. */ + SvcWaitForAddressIfEqual(std::addressof(m_state)); + return; + case LightEventState_Signaled: + /* We're signaled, so return. */ + return; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + } + + bool InternalLightEventImpl::TryWaitWithAutoClear() { + /* When we're done, fence memory. */ + ON_SCOPE_EXIT { std::atomic_thread_fence(std::memory_order_seq_cst); }; + + return CompareAndSwap(std::addressof(m_state), LightEventState_Signaled, LightEventState_NotSignaledAndNoWaiter); + } + + bool InternalLightEventImpl::TryWaitWithManualClear() { + /* When we're done, fence memory. */ + ON_SCOPE_EXIT { std::atomic_thread_fence(std::memory_order_seq_cst); }; + + return m_state.load(std::memory_order_acquire) == LightEventState_Signaled; + } + + bool InternalLightEventImpl::TimedWaitWithAutoClear(const TimeoutHelper &timeout_helper) { + /* When we're done, fence memory. */ + ON_SCOPE_EXIT { std::atomic_thread_fence(std::memory_order_seq_cst); }; + + /* Loop waiting. */ + while (true) { + /* Get the current state. */ + const auto state = m_state.load(std::memory_order_acquire); + switch (state) { + case LightEventState_NotSignaledAndNoWaiter: + /* Change state to have waiter. */ + if (!CompareAndSwap(std::addressof(m_state), state, LightEventState_NotSignaledAndWaiter)) { + break; + } + [[fallthrough]]; + case LightEventState_NotSignaledAndWaiter: + /* Wait for the address, checking timeout. */ + { + bool timed_out; + if (SvcWaitForAddressIfEqual(std::addressof(timed_out), std::addressof(m_state), timeout_helper.GetTimeLeftOnTarget().GetNanoSeconds())) { + return true; + } + if (timed_out) { + return false; + } + } + break; + case LightEventState_Signaled: + /* Try to clear. */ + if (CompareAndSwap(std::addressof(m_state), LightEventState_Signaled, LightEventState_NotSignaledAndNoWaiter)) { + return true; + } + break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + } + + bool InternalLightEventImpl::TimedWaitWithManualClear(const TimeoutHelper &timeout_helper) { + /* When we're done, fence memory. */ + ON_SCOPE_EXIT { std::atomic_thread_fence(std::memory_order_seq_cst); }; + + /* Loop waiting. */ + while (true) { + /* Get the current state. */ + const auto state = m_state.load(std::memory_order_acquire); + switch (state) { + case LightEventState_NotSignaledAndNoWaiter: + /* Change state to have waiter. */ + if (!CompareAndSwap(std::addressof(m_state), state, LightEventState_NotSignaledAndWaiter)) { + break; + } + [[fallthrough]]; + case LightEventState_NotSignaledAndWaiter: + /* Wait and check for timeout. */ + { + bool timed_out; + SvcWaitForAddressIfEqual(std::addressof(timed_out), std::addressof(m_state), timeout_helper.GetTimeLeftOnTarget().GetNanoSeconds()); + return !timed_out; + } + break; + case LightEventState_Signaled: + /* We're signaled, so return. */ + return true; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + } + +} diff --git a/libraries/libstratosphere/source/os/os_light_event.cpp b/libraries/libstratosphere/source/os/os_light_event.cpp new file mode 100644 index 000000000..909cdb564 --- /dev/null +++ b/libraries/libstratosphere/source/os/os_light_event.cpp @@ -0,0 +1,107 @@ +/* + * 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_timeout_helper.hpp" + +#if defined(ATMOSPHERE_OS_HORIZON) + #include "impl/os_internal_light_event_impl.os.horizon.hpp" +#else + #error "Unknown OS for ams::os::impl::InternalLightEventImpl" +#endif + +namespace ams::os { + bool is_auto_clear; + bool is_initialized; + + impl::InternalLightEventStorage storage; + + void InitializeLightEvent(LightEventType *event, bool signaled, EventClearMode clear_mode) { + /* Set member variables. */ + event->is_auto_clear = clear_mode == EventClearMode_AutoClear; + event->is_initialized = true; + + /* Create object. */ + util::ConstructAt(event->storage, signaled); + } + + void FinalizeLightEvent(LightEventType *event) { + /* Set not-initialized. */ + event->is_initialized = false; + + /* Destroy object. */ + util::DestroyAt(event->storage); + } + + void SignalLightEvent(LightEventType *event) { + /* Check pre-conditions. */ + AMS_ASSERT(event->is_initialized); + + /* Signal. */ + if (event->is_auto_clear) { + return util::GetReference(event->storage).SignalWithAutoClear(); + } else { + return util::GetReference(event->storage).SignalWithManualClear(); + } + } + + void WaitLightEvent(LightEventType *event) { + /* Check pre-conditions. */ + AMS_ASSERT(event->is_initialized); + + /* Wait. */ + if (event->is_auto_clear) { + return util::GetReference(event->storage).WaitWithAutoClear(); + } else { + return util::GetReference(event->storage).WaitWithManualClear(); + } + } + + bool TryWaitLightEvent(LightEventType *event) { + /* Check pre-conditions. */ + AMS_ASSERT(event->is_initialized); + + /* Wait. */ + if (event->is_auto_clear) { + return util::GetReference(event->storage).TryWaitWithAutoClear(); + } else { + return util::GetReference(event->storage).TryWaitWithManualClear(); + } + } + + bool TimedWaitLightEvent(LightEventType *event, TimeSpan timeout) { + /* Check pre-conditions. */ + AMS_ASSERT(event->is_initialized); + AMS_ASSERT(timeout.GetNanoSeconds() >= 0); + + /* Create timeout helper. */ + impl::TimeoutHelper timeout_helper(timeout); + + /* Wait. */ + if (event->is_auto_clear) { + return util::GetReference(event->storage).TimedWaitWithAutoClear(timeout_helper); + } else { + return util::GetReference(event->storage).TimedWaitWithManualClear(timeout_helper); + } + } + + void ClearLightEvent(LightEventType *event) { + /* Check pre-conditions. */ + AMS_ASSERT(event->is_initialized); + + return util::GetReference(event->storage).Clear(); + } + +}