diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index eb8f643a2d..1b44148f4b 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -548,8 +548,6 @@ add_library(core STATIC
     hle/service/es/es.h
     hle/service/eupld/eupld.cpp
     hle/service/eupld/eupld.h
-    hle/service/event.cpp
-    hle/service/event.h
     hle/service/fatal/fatal.cpp
     hle/service/fatal/fatal.h
     hle/service/fatal/fatal_p.cpp
@@ -676,8 +674,6 @@ add_library(core STATIC
     hle/service/mm/mm_u.h
     hle/service/mnpp/mnpp_app.cpp
     hle/service/mnpp/mnpp_app.h
-    hle/service/mutex.cpp
-    hle/service/mutex.h
     hle/service/ncm/ncm.cpp
     hle/service/ncm/ncm.h
     hle/service/nfc/common/amiibo_crypto.cpp
@@ -790,6 +786,15 @@ add_library(core STATIC
     hle/service/nvnflinger/window.h
     hle/service/olsc/olsc.cpp
     hle/service/olsc/olsc.h
+    hle/service/os/event.cpp
+    hle/service/os/event.h
+    hle/service/os/multi_wait_holder.cpp
+    hle/service/os/multi_wait_holder.h
+    hle/service/os/multi_wait_utils.h
+    hle/service/os/multi_wait.cpp
+    hle/service/os/multi_wait.h
+    hle/service/os/mutex.cpp
+    hle/service/os/mutex.h
     hle/service/pcie/pcie.cpp
     hle/service/pcie/pcie.h
     hle/service/pctl/pctl.cpp
diff --git a/src/core/hle/service/am/applet.h b/src/core/hle/service/am/applet.h
index bce6f9050a..b29ecdfed9 100644
--- a/src/core/hle/service/am/applet.h
+++ b/src/core/hle/service/am/applet.h
@@ -9,8 +9,8 @@
 #include "common/math_util.h"
 #include "core/hle/service/apm/apm_controller.h"
 #include "core/hle/service/caps/caps_types.h"
-#include "core/hle/service/event.h"
 #include "core/hle/service/kernel_helpers.h"
+#include "core/hle/service/os/event.h"
 #include "core/hle/service/service.h"
 
 #include "core/hle/service/am/am_types.h"
diff --git a/src/core/hle/service/am/applet_data_broker.h b/src/core/hle/service/am/applet_data_broker.h
index 12326fd042..5a1d43c11a 100644
--- a/src/core/hle/service/am/applet_data_broker.h
+++ b/src/core/hle/service/am/applet_data_broker.h
@@ -7,8 +7,8 @@
 #include <memory>
 #include <mutex>
 
-#include "core/hle/service/event.h"
 #include "core/hle/service/kernel_helpers.h"
+#include "core/hle/service/os/event.h"
 
 union Result;
 
diff --git a/src/core/hle/service/event.cpp b/src/core/hle/service/os/event.cpp
similarity index 93%
rename from src/core/hle/service/event.cpp
rename to src/core/hle/service/os/event.cpp
index 375660d728..ec52c17fd8 100644
--- a/src/core/hle/service/event.cpp
+++ b/src/core/hle/service/os/event.cpp
@@ -2,8 +2,8 @@
 // SPDX-License-Identifier: GPL-2.0-or-later
 
 #include "core/hle/kernel/k_event.h"
-#include "core/hle/service/event.h"
 #include "core/hle/service/kernel_helpers.h"
+#include "core/hle/service/os/event.h"
 
 namespace Service {
 
diff --git a/src/core/hle/service/event.h b/src/core/hle/service/os/event.h
similarity index 100%
rename from src/core/hle/service/event.h
rename to src/core/hle/service/os/event.h
diff --git a/src/core/hle/service/os/multi_wait.cpp b/src/core/hle/service/os/multi_wait.cpp
new file mode 100644
index 0000000000..7b80d28bed
--- /dev/null
+++ b/src/core/hle/service/os/multi_wait.cpp
@@ -0,0 +1,59 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "core/hle/kernel/k_hardware_timer.h"
+#include "core/hle/kernel/k_synchronization_object.h"
+#include "core/hle/kernel/kernel.h"
+#include "core/hle/kernel/svc_common.h"
+#include "core/hle/service/os/multi_wait.h"
+
+namespace Service {
+
+MultiWait::MultiWait() = default;
+MultiWait::~MultiWait() = default;
+
+MultiWaitHolder* MultiWait::WaitAny(Kernel::KernelCore& kernel) {
+    return this->TimedWaitImpl(kernel, -1);
+}
+
+MultiWaitHolder* MultiWait::TryWaitAny(Kernel::KernelCore& kernel) {
+    return this->TimedWaitImpl(kernel, 0);
+}
+
+MultiWaitHolder* MultiWait::TimedWaitAny(Kernel::KernelCore& kernel, s64 timeout_ns) {
+    return this->TimedWaitImpl(kernel, kernel.HardwareTimer().GetTick() + timeout_ns);
+}
+
+MultiWaitHolder* MultiWait::TimedWaitImpl(Kernel::KernelCore& kernel, s64 timeout_tick) {
+    std::array<MultiWaitHolder*, Kernel::Svc::ArgumentHandleCountMax> holders{};
+    std::array<Kernel::KSynchronizationObject*, Kernel::Svc::ArgumentHandleCountMax> objects{};
+
+    s32 out_index = -1;
+    s32 num_objects = 0;
+
+    for (auto it = m_wait_list.begin(); it != m_wait_list.end(); it++) {
+        ASSERT(num_objects < Kernel::Svc::ArgumentHandleCountMax);
+        holders[num_objects] = std::addressof(*it);
+        objects[num_objects] = it->GetNativeHandle();
+        num_objects++;
+    }
+
+    Kernel::KSynchronizationObject::Wait(kernel, std::addressof(out_index), objects.data(),
+                                         num_objects, timeout_tick);
+
+    if (out_index == -1) {
+        return nullptr;
+    } else {
+        return holders[out_index];
+    }
+}
+
+void MultiWait::MoveAll(MultiWait* other) {
+    while (!other->m_wait_list.empty()) {
+        MultiWaitHolder& holder = other->m_wait_list.front();
+        holder.UnlinkFromMultiWait();
+        holder.LinkToMultiWait(this);
+    }
+}
+
+} // namespace Service
diff --git a/src/core/hle/service/os/multi_wait.h b/src/core/hle/service/os/multi_wait.h
new file mode 100644
index 0000000000..340c611b5b
--- /dev/null
+++ b/src/core/hle/service/os/multi_wait.h
@@ -0,0 +1,36 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "core/hle/service/os/multi_wait_holder.h"
+
+namespace Kernel {
+class KernelCore;
+}
+
+namespace Service {
+
+class MultiWait final {
+public:
+    explicit MultiWait();
+    ~MultiWait();
+
+public:
+    MultiWaitHolder* WaitAny(Kernel::KernelCore& kernel);
+    MultiWaitHolder* TryWaitAny(Kernel::KernelCore& kernel);
+    MultiWaitHolder* TimedWaitAny(Kernel::KernelCore& kernel, s64 timeout_ns);
+    // TODO: SdkReplyAndReceive?
+
+    void MoveAll(MultiWait* other);
+
+private:
+    MultiWaitHolder* TimedWaitImpl(Kernel::KernelCore& kernel, s64 timeout_tick);
+
+private:
+    friend class MultiWaitHolder;
+    using ListType = Common::IntrusiveListMemberTraits<&MultiWaitHolder::m_list_node>::ListType;
+    ListType m_wait_list{};
+};
+
+} // namespace Service
diff --git a/src/core/hle/service/os/multi_wait_holder.cpp b/src/core/hle/service/os/multi_wait_holder.cpp
new file mode 100644
index 0000000000..01efa045b5
--- /dev/null
+++ b/src/core/hle/service/os/multi_wait_holder.cpp
@@ -0,0 +1,25 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "core/hle/service/os/multi_wait.h"
+#include "core/hle/service/os/multi_wait_holder.h"
+
+namespace Service {
+
+void MultiWaitHolder::LinkToMultiWait(MultiWait* multi_wait) {
+    if (m_multi_wait != nullptr) {
+        UNREACHABLE();
+    }
+
+    m_multi_wait = multi_wait;
+    m_multi_wait->m_wait_list.push_back(*this);
+}
+
+void MultiWaitHolder::UnlinkFromMultiWait() {
+    if (m_multi_wait) {
+        m_multi_wait->m_wait_list.erase(m_multi_wait->m_wait_list.iterator_to(*this));
+        m_multi_wait = nullptr;
+    }
+}
+
+} // namespace Service
diff --git a/src/core/hle/service/os/multi_wait_holder.h b/src/core/hle/service/os/multi_wait_holder.h
new file mode 100644
index 0000000000..646395a3f3
--- /dev/null
+++ b/src/core/hle/service/os/multi_wait_holder.h
@@ -0,0 +1,44 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "common/intrusive_list.h"
+
+namespace Kernel {
+class KSynchronizationObject;
+} // namespace Kernel
+
+namespace Service {
+
+class MultiWait;
+
+class MultiWaitHolder {
+public:
+    explicit MultiWaitHolder(Kernel::KSynchronizationObject* native_handle)
+        : m_native_handle(native_handle) {}
+
+    void LinkToMultiWait(MultiWait* multi_wait);
+    void UnlinkFromMultiWait();
+
+    void SetUserData(uintptr_t user_data) {
+        m_user_data = user_data;
+    }
+
+    uintptr_t GetUserData() const {
+        return m_user_data;
+    }
+
+    Kernel::KSynchronizationObject* GetNativeHandle() const {
+        return m_native_handle;
+    }
+
+private:
+    friend class MultiWait;
+    Common::IntrusiveListNode m_list_node{};
+    MultiWait* m_multi_wait{};
+    Kernel::KSynchronizationObject* m_native_handle{};
+    uintptr_t m_user_data{};
+};
+
+} // namespace Service
diff --git a/src/core/hle/service/os/multi_wait_utils.h b/src/core/hle/service/os/multi_wait_utils.h
new file mode 100644
index 0000000000..96d3a10f37
--- /dev/null
+++ b/src/core/hle/service/os/multi_wait_utils.h
@@ -0,0 +1,109 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "core/hle/service/os/multi_wait.h"
+
+namespace Service {
+
+namespace impl {
+
+class AutoMultiWaitHolder {
+private:
+    MultiWaitHolder m_holder;
+
+public:
+    template <typename T>
+    explicit AutoMultiWaitHolder(MultiWait* multi_wait, T&& arg) : m_holder(arg) {
+        m_holder.LinkToMultiWait(multi_wait);
+    }
+
+    ~AutoMultiWaitHolder() {
+        m_holder.UnlinkFromMultiWait();
+    }
+
+    std::pair<MultiWaitHolder*, int> ConvertResult(const std::pair<MultiWaitHolder*, int> result,
+                                                   int index) {
+        if (result.first == std::addressof(m_holder)) {
+            return std::make_pair(static_cast<MultiWaitHolder*>(nullptr), index);
+        } else {
+            return result;
+        }
+    }
+};
+
+using WaitAnyFunction = decltype(&MultiWait::WaitAny);
+
+inline std::pair<MultiWaitHolder*, int> WaitAnyImpl(Kernel::KernelCore& kernel,
+                                                    MultiWait* multi_wait, WaitAnyFunction func,
+                                                    int) {
+    return std::pair<MultiWaitHolder*, int>((multi_wait->*func)(kernel), -1);
+}
+
+template <typename T, typename... Args>
+inline std::pair<MultiWaitHolder*, int> WaitAnyImpl(Kernel::KernelCore& kernel,
+                                                    MultiWait* multi_wait, WaitAnyFunction func,
+                                                    int index, T&& x, Args&&... args) {
+    AutoMultiWaitHolder holder(multi_wait, std::forward<T>(x));
+    return holder.ConvertResult(
+        WaitAnyImpl(kernel, multi_wait, func, index + 1, std::forward<Args>(args)...), index);
+}
+
+template <typename... Args>
+inline std::pair<MultiWaitHolder*, int> WaitAnyImpl(Kernel::KernelCore& kernel,
+                                                    MultiWait* multi_wait, WaitAnyFunction func,
+                                                    Args&&... args) {
+    return WaitAnyImpl(kernel, multi_wait, func, 0, std::forward<Args>(args)...);
+}
+
+template <typename... Args>
+inline std::pair<MultiWaitHolder*, int> WaitAnyImpl(Kernel::KernelCore& kernel,
+                                                    WaitAnyFunction func, Args&&... args) {
+    MultiWait temp_multi_wait;
+    return WaitAnyImpl(kernel, std::addressof(temp_multi_wait), func, 0,
+                       std::forward<Args>(args)...);
+}
+
+class NotBoolButInt {
+public:
+    constexpr NotBoolButInt(int v) : m_value(v) {}
+    constexpr operator int() const {
+        return m_value;
+    }
+    explicit operator bool() const = delete;
+
+private:
+    int m_value;
+};
+
+} // namespace impl
+
+template <typename... Args>
+    requires(sizeof...(Args) > 0)
+inline std::pair<MultiWaitHolder*, int> WaitAny(Kernel::KernelCore& kernel, MultiWait* multi_wait,
+                                                Args&&... args) {
+    return impl::WaitAnyImpl(kernel, &MultiWait::WaitAny, multi_wait, std::forward<Args>(args)...);
+}
+
+template <typename... Args>
+    requires(sizeof...(Args) > 0)
+inline int WaitAny(Kernel::KernelCore& kernel, Args&&... args) {
+    return impl::WaitAnyImpl(kernel, &MultiWait::WaitAny, std::forward<Args>(args)...).second;
+}
+
+template <typename... Args>
+    requires(sizeof...(Args) > 0)
+inline std::pair<MultiWaitHolder*, int> TryWaitAny(Kernel::KernelCore& kernel,
+                                                   MultiWait* multi_wait, Args&&... args) {
+    return impl::WaitAnyImpl(kernel, &MultiWait::TryWaitAny, multi_wait,
+                             std::forward<Args>(args)...);
+}
+
+template <typename... Args>
+    requires(sizeof...(Args) > 0)
+inline impl::NotBoolButInt TryWaitAny(Kernel::KernelCore& kernel, Args&&... args) {
+    return impl::WaitAnyImpl(kernel, &MultiWait::TryWaitAny, std::forward<Args>(args)...).second;
+}
+
+} // namespace Service
diff --git a/src/core/hle/service/mutex.cpp b/src/core/hle/service/os/mutex.cpp
similarity index 96%
rename from src/core/hle/service/mutex.cpp
rename to src/core/hle/service/os/mutex.cpp
index b0ff71d1ba..6009f48668 100644
--- a/src/core/hle/service/mutex.cpp
+++ b/src/core/hle/service/os/mutex.cpp
@@ -4,7 +4,7 @@
 #include "core/core.h"
 #include "core/hle/kernel/k_event.h"
 #include "core/hle/kernel/k_synchronization_object.h"
-#include "core/hle/service/mutex.h"
+#include "core/hle/service/os/mutex.h"
 
 namespace Service {
 
diff --git a/src/core/hle/service/mutex.h b/src/core/hle/service/os/mutex.h
similarity index 100%
rename from src/core/hle/service/mutex.h
rename to src/core/hle/service/os/mutex.h
diff --git a/src/core/hle/service/server_manager.h b/src/core/hle/service/server_manager.h
index c4bc07262d..7481c8521d 100644
--- a/src/core/hle/service/server_manager.h
+++ b/src/core/hle/service/server_manager.h
@@ -14,7 +14,7 @@
 #include "common/thread.h"
 #include "core/hle/result.h"
 #include "core/hle/service/hle_ipc.h"
-#include "core/hle/service/mutex.h"
+#include "core/hle/service/os/mutex.h"
 
 namespace Core {
 class System;