diff options
Diffstat (limited to 'src/core/hle/kernel')
-rw-r--r-- | src/core/hle/kernel/address_arbiter.cpp | 2 | ||||
-rw-r--r-- | src/core/hle/kernel/client_port.cpp | 32 | ||||
-rw-r--r-- | src/core/hle/kernel/client_port.h | 13 | ||||
-rw-r--r-- | src/core/hle/kernel/client_session.cpp | 40 | ||||
-rw-r--r-- | src/core/hle/kernel/client_session.h | 65 | ||||
-rw-r--r-- | src/core/hle/kernel/kernel.cpp | 59 | ||||
-rw-r--r-- | src/core/hle/kernel/kernel.h | 45 | ||||
-rw-r--r-- | src/core/hle/kernel/server_port.cpp | 4 | ||||
-rw-r--r-- | src/core/hle/kernel/server_port.h | 14 | ||||
-rw-r--r-- | src/core/hle/kernel/server_session.cpp | 79 | ||||
-rw-r--r-- | src/core/hle/kernel/server_session.h | 94 | ||||
-rw-r--r-- | src/core/hle/kernel/session.h | 218 | ||||
-rw-r--r-- | src/core/hle/kernel/thread.cpp | 112 | ||||
-rw-r--r-- | src/core/hle/kernel/thread.h | 49 | ||||
-rw-r--r-- | src/core/hle/kernel/timer.cpp | 4 |
15 files changed, 472 insertions, 358 deletions
diff --git a/src/core/hle/kernel/address_arbiter.cpp b/src/core/hle/kernel/address_arbiter.cpp index 37eec4c84..b5a0cc3a3 100644 --- a/src/core/hle/kernel/address_arbiter.cpp +++ b/src/core/hle/kernel/address_arbiter.cpp @@ -79,8 +79,6 @@ ResultCode AddressArbiter::ArbitrateAddress(ArbitrationType type, VAddr address, ErrorSummary::WrongArgument, ErrorLevel::Usage); } - HLE::Reschedule(__func__); - // The calls that use a timeout seem to always return a Timeout error even if they did not put // the thread to sleep if (type == ArbitrationType::WaitIfLessThanWithTimeout || diff --git a/src/core/hle/kernel/client_port.cpp b/src/core/hle/kernel/client_port.cpp index aedc6f989..22645f4ec 100644 --- a/src/core/hle/kernel/client_port.cpp +++ b/src/core/hle/kernel/client_port.cpp @@ -4,12 +4,44 @@ #include "common/assert.h" #include "core/hle/kernel/client_port.h" +#include "core/hle/kernel/client_session.h" #include "core/hle/kernel/kernel.h" #include "core/hle/kernel/server_port.h" +#include "core/hle/kernel/server_session.h" namespace Kernel { ClientPort::ClientPort() {} ClientPort::~ClientPort() {} +ResultVal<SharedPtr<ClientSession>> ClientPort::Connect() { + // Note: Threads do not wait for the server endpoint to call + // AcceptSession before returning from this call. + + if (active_sessions >= max_sessions) { + // TODO(Subv): Return an error code in this situation after session disconnection is + // implemented. + /*return ResultCode(ErrorDescription::MaxConnectionsReached, + ErrorModule::OS, ErrorSummary::WouldBlock, + ErrorLevel::Temporary);*/ + } + active_sessions++; + + // Create a new session pair, let the created sessions inherit the parent port's HLE handler. + auto sessions = + ServerSession::CreateSessionPair(server_port->GetName(), server_port->hle_handler); + auto client_session = std::get<SharedPtr<ClientSession>>(sessions); + auto server_session = std::get<SharedPtr<ServerSession>>(sessions); + + if (server_port->hle_handler) + server_port->hle_handler->ClientConnected(server_session); + + server_port->pending_sessions.push_back(std::move(server_session)); + + // Wake the threads waiting on the ServerPort + server_port->WakeupAllWaitingThreads(); + + return MakeResult<SharedPtr<ClientSession>>(std::move(client_session)); +} + } // namespace diff --git a/src/core/hle/kernel/client_port.h b/src/core/hle/kernel/client_port.h index d28147718..511490c7c 100644 --- a/src/core/hle/kernel/client_port.h +++ b/src/core/hle/kernel/client_port.h @@ -11,8 +11,9 @@ namespace Kernel { class ServerPort; +class ClientSession; -class ClientPort : public Object { +class ClientPort final : public Object { public: friend class ServerPort; std::string GetTypeName() const override { @@ -27,12 +28,20 @@ public: return HANDLE_TYPE; } + /** + * Creates a new Session pair, adds the created ServerSession to the associated ServerPort's + * list of pending sessions, and signals the ServerPort, causing any threads + * waiting on it to awake. + * @returns ClientSession The client endpoint of the created Session pair, or error code. + */ + ResultVal<SharedPtr<ClientSession>> Connect(); + SharedPtr<ServerPort> server_port; ///< ServerPort associated with this client port. u32 max_sessions; ///< Maximum number of simultaneous sessions the port can have u32 active_sessions; ///< Number of currently open sessions to this port std::string name; ///< Name of client port (optional) -protected: +private: ClientPort(); ~ClientPort() override; }; diff --git a/src/core/hle/kernel/client_session.cpp b/src/core/hle/kernel/client_session.cpp new file mode 100644 index 000000000..0331386ec --- /dev/null +++ b/src/core/hle/kernel/client_session.cpp @@ -0,0 +1,40 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "common/assert.h" + +#include "core/hle/kernel/client_session.h" +#include "core/hle/kernel/server_session.h" + +namespace Kernel { + +ClientSession::ClientSession() = default; +ClientSession::~ClientSession() { + // This destructor will be called automatically when the last ClientSession handle is closed by + // the emulated application. + + if (server_session->hle_handler) + server_session->hle_handler->ClientDisconnected(server_session); + + // TODO(Subv): If the session is still open, set the connection status to 2 (Closed by client), + // wake up all the ServerSession's waiting threads and set the WaitSynchronization result to + // 0xC920181A. +} + +ResultVal<SharedPtr<ClientSession>> ClientSession::Create(ServerSession* server_session, + std::string name) { + SharedPtr<ClientSession> client_session(new ClientSession); + + client_session->name = std::move(name); + client_session->server_session = server_session; + client_session->session_status = SessionStatus::Open; + return MakeResult<SharedPtr<ClientSession>>(std::move(client_session)); +} + +ResultCode ClientSession::SendSyncRequest() { + // Signal the server session that new data is available + return server_session->HandleSyncRequest(); +} + +} // namespace diff --git a/src/core/hle/kernel/client_session.h b/src/core/hle/kernel/client_session.h new file mode 100644 index 000000000..ed468dec6 --- /dev/null +++ b/src/core/hle/kernel/client_session.h @@ -0,0 +1,65 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <memory> +#include <string> + +#include "common/common_types.h" + +#include "core/hle/kernel/kernel.h" + +namespace Kernel { + +class ServerSession; + +enum class SessionStatus { + Open = 1, + ClosedByClient = 2, + ClosedBYServer = 3, +}; + +class ClientSession final : public Object { +public: + friend class ServerSession; + + std::string GetTypeName() const override { + return "ClientSession"; + } + + std::string GetName() const override { + return name; + } + + static const HandleType HANDLE_TYPE = HandleType::ClientSession; + HandleType GetHandleType() const override { + return HANDLE_TYPE; + } + + /** + * Sends an SyncRequest from the current emulated thread. + * @return ResultCode of the operation. + */ + ResultCode SendSyncRequest(); + + std::string name; ///< Name of client port (optional) + ServerSession* server_session; ///< The server session associated with this client session. + SessionStatus session_status; ///< The session's current status. + +private: + ClientSession(); + ~ClientSession() override; + + /** + * Creates a client session. + * @param server_session The server session associated with this client session + * @param name Optional name of client session + * @return The created client session + */ + static ResultVal<SharedPtr<ClientSession>> Create(ServerSession* server_session, + std::string name = "Unknown"); +}; + +} // namespace diff --git a/src/core/hle/kernel/kernel.cpp b/src/core/hle/kernel/kernel.cpp index 0c8752670..1db8e102f 100644 --- a/src/core/hle/kernel/kernel.cpp +++ b/src/core/hle/kernel/kernel.cpp @@ -3,6 +3,7 @@ // Refer to the license.txt file included. #include <algorithm> +#include <boost/range/algorithm_ext/erase.hpp> #include "common/assert.h" #include "common/logging/log.h" #include "core/hle/config_mem.h" @@ -31,13 +32,61 @@ void WaitObject::RemoveWaitingThread(Thread* thread) { waiting_threads.erase(itr); } -void WaitObject::WakeupAllWaitingThreads() { - for (auto thread : waiting_threads) - thread->ResumeFromWait(); +SharedPtr<Thread> WaitObject::GetHighestPriorityReadyThread() { + // Remove the threads that are ready or already running from our waitlist + boost::range::remove_erase_if(waiting_threads, [](const SharedPtr<Thread>& thread) { + return thread->status == THREADSTATUS_RUNNING || thread->status == THREADSTATUS_READY || + thread->status == THREADSTATUS_DEAD; + }); + + // TODO(Subv): This call should be performed inside the loop below to check if an object can be + // acquired by a particular thread. This is useful for things like recursive locking of Mutexes. + if (ShouldWait()) + return nullptr; + + Thread* candidate = nullptr; + s32 candidate_priority = THREADPRIO_LOWEST + 1; + + for (const auto& thread : waiting_threads) { + if (thread->current_priority >= candidate_priority) + continue; - waiting_threads.clear(); + bool ready_to_run = + std::none_of(thread->wait_objects.begin(), thread->wait_objects.end(), + [](const SharedPtr<WaitObject>& object) { return object->ShouldWait(); }); + if (ready_to_run) { + candidate = thread.get(); + candidate_priority = thread->current_priority; + } + } + + return candidate; +} - HLE::Reschedule(__func__); +void WaitObject::WakeupAllWaitingThreads() { + while (auto thread = GetHighestPriorityReadyThread()) { + if (!thread->IsSleepingOnWaitAll()) { + Acquire(); + // Set the output index of the WaitSynchronizationN call to the index of this object. + if (thread->wait_set_output) { + thread->SetWaitSynchronizationOutput(thread->GetWaitObjectIndex(this)); + thread->wait_set_output = false; + } + } else { + for (auto& object : thread->wait_objects) { + object->Acquire(); + object->RemoveWaitingThread(thread.get()); + } + // Note: This case doesn't update the output index of WaitSynchronizationN. + // Clear the thread's waitlist + thread->wait_objects.clear(); + } + + thread->SetWaitSynchronizationResult(RESULT_SUCCESS); + thread->ResumeFromWait(); + // Note: Removing the thread from the object's waitlist will be + // done by GetHighestPriorityReadyThread. + } } const std::vector<SharedPtr<Thread>>& WaitObject::GetWaitingThreads() const { diff --git a/src/core/hle/kernel/kernel.h b/src/core/hle/kernel/kernel.h index 231cf7b75..1adcf6c71 100644 --- a/src/core/hle/kernel/kernel.h +++ b/src/core/hle/kernel/kernel.h @@ -31,22 +31,21 @@ enum KernelHandle : Handle { }; enum class HandleType : u32 { - Unknown = 0, - - Session = 2, - Event = 3, - Mutex = 4, - SharedMemory = 5, - Redirection = 6, - Thread = 7, - Process = 8, - AddressArbiter = 9, - Semaphore = 10, - Timer = 11, - ResourceLimit = 12, - CodeSet = 13, - ClientPort = 14, - ServerPort = 15, + Unknown, + Event, + Mutex, + SharedMemory, + Thread, + Process, + AddressArbiter, + Semaphore, + Timer, + ResourceLimit, + CodeSet, + ClientPort, + ServerPort, + ClientSession, + ServerSession, }; enum { @@ -82,23 +81,23 @@ public: */ bool IsWaitable() const { switch (GetHandleType()) { - case HandleType::Session: - case HandleType::ServerPort: case HandleType::Event: case HandleType::Mutex: case HandleType::Thread: case HandleType::Semaphore: case HandleType::Timer: + case HandleType::ServerPort: + case HandleType::ServerSession: return true; case HandleType::Unknown: case HandleType::SharedMemory: - case HandleType::Redirection: case HandleType::Process: case HandleType::AddressArbiter: case HandleType::ResourceLimit: case HandleType::CodeSet: case HandleType::ClientPort: + case HandleType::ClientSession: return false; } } @@ -152,9 +151,15 @@ public: */ void RemoveWaitingThread(Thread* thread); - /// Wake up all threads waiting on this object + /** + * Wake up all threads waiting on this object that can be awoken, in priority order, + * and set the synchronization result and output of the thread. + */ void WakeupAllWaitingThreads(); + /// Obtains the highest priority thread that is ready to run from this object's waiting list. + SharedPtr<Thread> GetHighestPriorityReadyThread(); + /// Get a const reference to the waiting threads list for debug use const std::vector<SharedPtr<Thread>>& GetWaitingThreads() const; diff --git a/src/core/hle/kernel/server_port.cpp b/src/core/hle/kernel/server_port.cpp index 8e3ec8a14..6c19aa7c0 100644 --- a/src/core/hle/kernel/server_port.cpp +++ b/src/core/hle/kernel/server_port.cpp @@ -24,12 +24,14 @@ void ServerPort::Acquire() { } std::tuple<SharedPtr<ServerPort>, SharedPtr<ClientPort>> ServerPort::CreatePortPair( - u32 max_sessions, std::string name) { + u32 max_sessions, std::string name, + std::shared_ptr<Service::SessionRequestHandler> hle_handler) { SharedPtr<ServerPort> server_port(new ServerPort); SharedPtr<ClientPort> client_port(new ClientPort); server_port->name = name + "_Server"; + server_port->hle_handler = std::move(hle_handler); client_port->name = name + "_Client"; client_port->server_port = server_port; client_port->max_sessions = max_sessions; diff --git a/src/core/hle/kernel/server_port.h b/src/core/hle/kernel/server_port.h index fa9448ca0..b0f8df62c 100644 --- a/src/core/hle/kernel/server_port.h +++ b/src/core/hle/kernel/server_port.h @@ -4,11 +4,16 @@ #pragma once +#include <memory> #include <string> #include <tuple> #include "common/common_types.h" #include "core/hle/kernel/kernel.h" +namespace Service { +class SessionRequestHandler; +} + namespace Kernel { class ClientPort; @@ -19,10 +24,13 @@ public: * Creates a pair of ServerPort and an associated ClientPort. * @param max_sessions Maximum number of sessions to the port * @param name Optional name of the ports + * @param hle_handler Optional HLE handler template for the port, + * ServerSessions crated from this port will inherit a reference to this handler. * @return The created port tuple */ static std::tuple<SharedPtr<ServerPort>, SharedPtr<ClientPort>> CreatePortPair( - u32 max_sessions, std::string name = "UnknownPort"); + u32 max_sessions, std::string name = "UnknownPort", + std::shared_ptr<Service::SessionRequestHandler> hle_handler = nullptr); std::string GetTypeName() const override { return "ServerPort"; @@ -41,6 +49,10 @@ public: std::vector<SharedPtr<WaitObject>> pending_sessions; ///< ServerSessions waiting to be accepted by the port + /// This session's HLE request handler template (optional) + /// ServerSessions created from this port inherit a reference to this handler. + std::shared_ptr<Service::SessionRequestHandler> hle_handler; + bool ShouldWait() override; void Acquire() override; diff --git a/src/core/hle/kernel/server_session.cpp b/src/core/hle/kernel/server_session.cpp new file mode 100644 index 000000000..146458c1c --- /dev/null +++ b/src/core/hle/kernel/server_session.cpp @@ -0,0 +1,79 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <tuple> + +#include "core/hle/kernel/client_session.h" +#include "core/hle/kernel/server_session.h" +#include "core/hle/kernel/thread.h" + +namespace Kernel { + +ServerSession::ServerSession() = default; +ServerSession::~ServerSession() { + // This destructor will be called automatically when the last ServerSession handle is closed by + // the emulated application. + // TODO(Subv): Reduce the ClientPort's connection count, + // if the session is still open, set the connection status to 3 (Closed by server), +} + +ResultVal<SharedPtr<ServerSession>> ServerSession::Create( + std::string name, std::shared_ptr<Service::SessionRequestHandler> hle_handler) { + SharedPtr<ServerSession> server_session(new ServerSession); + + server_session->name = std::move(name); + server_session->signaled = false; + server_session->hle_handler = std::move(hle_handler); + + return MakeResult<SharedPtr<ServerSession>>(std::move(server_session)); +} + +bool ServerSession::ShouldWait() { + return !signaled; +} + +void ServerSession::Acquire() { + ASSERT_MSG(!ShouldWait(), "object unavailable!"); + signaled = false; +} + +ResultCode ServerSession::HandleSyncRequest() { + // The ServerSession received a sync request, this means that there's new data available + // from its ClientSession, so wake up any threads that may be waiting on a svcReplyAndReceive or + // similar. + + // If this ServerSession has an associated HLE handler, forward the request to it. + if (hle_handler != nullptr) { + // Attempt to translate the incoming request's command buffer. + ResultCode result = TranslateHLERequest(this); + if (result.IsError()) + return result; + hle_handler->HandleSyncRequest(SharedPtr<ServerSession>(this)); + // TODO(Subv): Translate the response command buffer. + } + + // If this ServerSession does not have an HLE implementation, just wake up the threads waiting + // on it. + signaled = true; + WakeupAllWaitingThreads(); + return RESULT_SUCCESS; +} + +ServerSession::SessionPair ServerSession::CreateSessionPair( + const std::string& name, std::shared_ptr<Service::SessionRequestHandler> hle_handler) { + auto server_session = + ServerSession::Create(name + "_Server", std::move(hle_handler)).MoveFrom(); + // We keep a non-owning pointer to the ServerSession in the ClientSession because we don't want + // to prevent the ServerSession's destructor from being called when the emulated + // application closes the last ServerSession handle. + auto client_session = ClientSession::Create(server_session.get(), name + "_Client").MoveFrom(); + + return std::make_tuple(std::move(server_session), std::move(client_session)); +} + +ResultCode TranslateHLERequest(ServerSession* server_session) { + // TODO(Subv): Implement this function once multiple concurrent processes are supported. + return RESULT_SUCCESS; +} +} diff --git a/src/core/hle/kernel/server_session.h b/src/core/hle/kernel/server_session.h new file mode 100644 index 000000000..458284a5d --- /dev/null +++ b/src/core/hle/kernel/server_session.h @@ -0,0 +1,94 @@ +// Copyright 2014 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <string> +#include "common/assert.h" +#include "common/common_types.h" +#include "core/hle/kernel/kernel.h" +#include "core/hle/kernel/thread.h" +#include "core/hle/result.h" +#include "core/hle/service/service.h" +#include "core/memory.h" + +namespace Kernel { + +class ClientSession; + +/** + * Kernel object representing the server endpoint of an IPC session. Sessions are the basic CTR-OS + * primitive for communication between different processes, and are used to implement service calls + * to the various system services. + * + * To make a service call, the client must write the command header and parameters to the buffer + * located at offset 0x80 of the TLS (Thread-Local Storage) area, then execute a SendSyncRequest + * SVC call with its ClientSession handle. The kernel will read the command header, using it to + * marshall the parameters to the process at the server endpoint of the session. + * After the server replies to the request, the response is marshalled back to the caller's + * TLS buffer and control is transferred back to it. + */ +class ServerSession final : public WaitObject { +public: + std::string GetTypeName() const override { + return "ServerSession"; + } + + static const HandleType HANDLE_TYPE = HandleType::ServerSession; + HandleType GetHandleType() const override { + return HANDLE_TYPE; + } + + using SessionPair = std::tuple<SharedPtr<ServerSession>, SharedPtr<ClientSession>>; + + /** + * Creates a pair of ServerSession and an associated ClientSession. + * @param name Optional name of the ports + * @return The created session tuple + */ + static SessionPair CreateSessionPair( + const std::string& name = "Unknown", + std::shared_ptr<Service::SessionRequestHandler> hle_handler = nullptr); + + /** + * Handle a sync request from the emulated application. + * @returns ResultCode from the operation. + */ + ResultCode HandleSyncRequest(); + + bool ShouldWait() override; + + void Acquire() override; + + std::string name; ///< The name of this session (optional) + bool signaled; ///< Whether there's new data available to this ServerSession + std::shared_ptr<Service::SessionRequestHandler> + hle_handler; ///< This session's HLE request handler (optional) + +private: + ServerSession(); + ~ServerSession() override; + + /** + * Creates a server session. The server session can have an optional HLE handler, + * which will be invoked to handle the IPC requests that this session receives. + * @param name Optional name of the server session. + * @param hle_handler Optional HLE handler for this server session. + * @return The created server session + */ + static ResultVal<SharedPtr<ServerSession>> Create( + std::string name = "Unknown", + std::shared_ptr<Service::SessionRequestHandler> hle_handler = nullptr); +}; + +/** + * Performs command buffer translation for an HLE IPC request. + * The command buffer from the ServerSession thread's TLS is copied into a + * buffer and all descriptors in the buffer are processed. + * TODO(Subv): Implement this function, currently we do not support multiple processes running at + * once, but once that is implemented we'll need to properly translate all descriptors + * in the command buffer. + */ +ResultCode TranslateHLERequest(ServerSession* server_session); +} diff --git a/src/core/hle/kernel/session.h b/src/core/hle/kernel/session.h deleted file mode 100644 index ec025f732..000000000 --- a/src/core/hle/kernel/session.h +++ /dev/null @@ -1,218 +0,0 @@ -// Copyright 2014 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include <string> -#include "common/assert.h" -#include "common/common_types.h" -#include "core/hle/kernel/kernel.h" -#include "core/hle/kernel/thread.h" -#include "core/hle/result.h" -#include "core/memory.h" - -namespace IPC { - -enum DescriptorType : u32 { - // Buffer related desciptors types (mask : 0x0F) - StaticBuffer = 0x02, - PXIBuffer = 0x04, - MappedBuffer = 0x08, - // Handle related descriptors types (mask : 0x30, but need to check for buffer related - // descriptors first ) - CopyHandle = 0x00, - MoveHandle = 0x10, - CallingPid = 0x20, -}; - -/** - * @brief Creates a command header to be used for IPC - * @param command_id ID of the command to create a header for. - * @param normal_params Size of the normal parameters in words. Up to 63. - * @param translate_params_size Size of the translate parameters in words. Up to 63. - * @return The created IPC header. - * - * Normal parameters are sent directly to the process while the translate parameters might go - * through modifications and checks by the kernel. - * The translate parameters are described by headers generated with the IPC::*Desc functions. - * - * @note While #normal_params is equivalent to the number of normal parameters, - * #translate_params_size includes the size occupied by the translate parameters headers. - */ -constexpr u32 MakeHeader(u16 command_id, unsigned int normal_params, - unsigned int translate_params_size) { - return (u32(command_id) << 16) | ((u32(normal_params) & 0x3F) << 6) | - (u32(translate_params_size) & 0x3F); -} - -union Header { - u32 raw; - BitField<0, 6, u32> translate_params_size; - BitField<6, 6, u32> normal_params; - BitField<16, 16, u32> command_id; -}; - -inline Header ParseHeader(u32 header) { - return {header}; -} - -constexpr u32 MoveHandleDesc(u32 num_handles = 1) { - return MoveHandle | ((num_handles - 1) << 26); -} - -constexpr u32 CopyHandleDesc(u32 num_handles = 1) { - return CopyHandle | ((num_handles - 1) << 26); -} - -constexpr u32 CallingPidDesc() { - return CallingPid; -} - -constexpr bool isHandleDescriptor(u32 descriptor) { - return (descriptor & 0xF) == 0x0; -} - -constexpr u32 HandleNumberFromDesc(u32 handle_descriptor) { - return (handle_descriptor >> 26) + 1; -} - -constexpr u32 StaticBufferDesc(u32 size, u8 buffer_id) { - return StaticBuffer | (size << 14) | ((buffer_id & 0xF) << 10); -} - -union StaticBufferDescInfo { - u32 raw; - BitField<10, 4, u32> buffer_id; - BitField<14, 18, u32> size; -}; - -inline StaticBufferDescInfo ParseStaticBufferDesc(const u32 desc) { - return {desc}; -} - -/** - * @brief Creates a header describing a buffer to be sent over PXI. - * @param size Size of the buffer. Max 0x00FFFFFF. - * @param buffer_id The Id of the buffer. Max 0xF. - * @param is_read_only true if the buffer is read-only. If false, the buffer is considered to have - * read-write access. - * @return The created PXI buffer header. - * - * The next value is a phys-address of a table located in the BASE memregion. - */ -inline u32 PXIBufferDesc(u32 size, unsigned buffer_id, bool is_read_only) { - u32 type = PXIBuffer; - if (is_read_only) - type |= 0x2; - return type | (size << 8) | ((buffer_id & 0xF) << 4); -} - -enum MappedBufferPermissions { - R = 1, - W = 2, - RW = R | W, -}; - -constexpr u32 MappedBufferDesc(u32 size, MappedBufferPermissions perms) { - return MappedBuffer | (size << 4) | (u32(perms) << 1); -} - -union MappedBufferDescInfo { - u32 raw; - BitField<4, 28, u32> size; - BitField<1, 2, MappedBufferPermissions> perms; -}; - -inline MappedBufferDescInfo ParseMappedBufferDesc(const u32 desc) { - return {desc}; -} - -inline DescriptorType GetDescriptorType(u32 descriptor) { - // Note: Those checks must be done in this order - if (isHandleDescriptor(descriptor)) - return (DescriptorType)(descriptor & 0x30); - - // handle the fact that the following descriptors can have rights - if (descriptor & MappedBuffer) - return MappedBuffer; - - if (descriptor & PXIBuffer) - return PXIBuffer; - - return StaticBuffer; -} - -} // namespace IPC - -namespace Kernel { - -static const int kCommandHeaderOffset = 0x80; ///< Offset into command buffer of header - -/** - * Returns a pointer to the command buffer in the current thread's TLS - * TODO(Subv): This is not entirely correct, the command buffer should be copied from - * the thread's TLS to an intermediate buffer in kernel memory, and then copied again to - * the service handler process' memory. - * @param offset Optional offset into command buffer - * @return Pointer to command buffer - */ -inline u32* GetCommandBuffer(const int offset = 0) { - return (u32*)Memory::GetPointer(GetCurrentThread()->GetTLSAddress() + kCommandHeaderOffset + - offset); -} - -/** - * Kernel object representing the client endpoint of an IPC session. Sessions are the basic CTR-OS - * primitive for communication between different processes, and are used to implement service calls - * to the various system services. - * - * To make a service call, the client must write the command header and parameters to the buffer - * located at offset 0x80 of the TLS (Thread-Local Storage) area, then execute a SendSyncRequest - * SVC call with its Session handle. The kernel will read the command header, using it to marshall - * the parameters to the process at the server endpoint of the session. After the server replies to - * the request, the response is marshalled back to the caller's TLS buffer and control is - * transferred back to it. - * - * In Citra, only the client endpoint is currently implemented and only HLE calls, where the IPC - * request is answered by C++ code in the emulator, are supported. When SendSyncRequest is called - * with the session handle, this class's SyncRequest method is called, which should read the TLS - * buffer and emulate the call accordingly. Since the code can directly read the emulated memory, - * no parameter marshalling is done. - * - * In the long term, this should be turned into the full-fledged IPC mechanism implemented by - * CTR-OS so that IPC calls can be optionally handled by the real implementations of processes, as - * opposed to HLE simulations. - */ -class Session : public WaitObject { -public: - Session(); - ~Session() override; - - std::string GetTypeName() const override { - return "Session"; - } - - static const HandleType HANDLE_TYPE = HandleType::Session; - HandleType GetHandleType() const override { - return HANDLE_TYPE; - } - - /** - * Handles a synchronous call to this session using HLE emulation. Emulated <-> emulated calls - * aren't supported yet. - */ - virtual ResultVal<bool> SyncRequest() = 0; - - // TODO(bunnei): These functions exist to satisfy a hardware test with a Session object - // passed into WaitSynchronization. Figure out the meaning of them. - - bool ShouldWait() override { - return true; - } - - void Acquire() override { - ASSERT_MSG(!ShouldWait(), "object unavailable!"); - } -}; -} diff --git a/src/core/hle/kernel/thread.cpp b/src/core/hle/kernel/thread.cpp index 84d6d24c6..18b696f72 100644 --- a/src/core/hle/kernel/thread.cpp +++ b/src/core/hle/kernel/thread.cpp @@ -46,7 +46,7 @@ static std::vector<SharedPtr<Thread>> thread_list; // Lists only ready thread ids. static Common::ThreadQueueList<Thread*, THREADPRIO_LOWEST + 1> ready_queue; -static Thread* current_thread; +static SharedPtr<Thread> current_thread; // The first available thread id at startup static u32 next_thread_id; @@ -63,7 +63,7 @@ Thread::Thread() {} Thread::~Thread() {} Thread* GetCurrentThread() { - return current_thread; + return current_thread.get(); } /** @@ -120,8 +120,6 @@ void Thread::Stop() { u32 tls_slot = ((tls_address - Memory::TLS_AREA_VADDR) % Memory::PAGE_SIZE) / Memory::TLS_ENTRY_SIZE; Kernel::g_current_process->tls_slots[tls_page].reset(tls_slot); - - HLE::Reschedule(__func__); } Thread* ArbitrateHighestPriorityThread(u32 address) { @@ -181,50 +179,6 @@ static void PriorityBoostStarvedThreads() { } /** - * Gets the registers for timeout parameter of the next WaitSynchronization call. - * @param thread a pointer to the thread that is ready to call WaitSynchronization - * @returns a tuple of two register pointers to low and high part of the timeout parameter - */ -static std::tuple<u32*, u32*> GetWaitSynchTimeoutParameterRegister(Thread* thread) { - bool thumb_mode = (thread->context.cpsr & TBIT) != 0; - u16 thumb_inst = Memory::Read16(thread->context.pc & 0xFFFFFFFE); - u32 inst = Memory::Read32(thread->context.pc & 0xFFFFFFFC) & 0x0FFFFFFF; - - if ((thumb_mode && thumb_inst == 0xDF24) || (!thumb_mode && inst == 0x0F000024)) { - // svc #0x24 (WaitSynchronization1) - return std::make_tuple(&thread->context.cpu_registers[2], - &thread->context.cpu_registers[3]); - } else if ((thumb_mode && thumb_inst == 0xDF25) || (!thumb_mode && inst == 0x0F000025)) { - // svc #0x25 (WaitSynchronizationN) - return std::make_tuple(&thread->context.cpu_registers[0], - &thread->context.cpu_registers[4]); - } - - UNREACHABLE(); -} - -/** - * Updates the WaitSynchronization timeout parameter according to the difference - * between ticks of the last WaitSynchronization call and the incoming one. - * @param timeout_low a pointer to the register for the low part of the timeout parameter - * @param timeout_high a pointer to the register for the high part of the timeout parameter - * @param last_tick tick of the last WaitSynchronization call - */ -static void UpdateTimeoutParameter(u32* timeout_low, u32* timeout_high, u64 last_tick) { - s64 timeout = ((s64)*timeout_high << 32) | *timeout_low; - - if (timeout != -1) { - timeout -= cyclesToUs(CoreTiming::GetTicks() - last_tick) * 1000; // in nanoseconds - - if (timeout < 0) - timeout = 0; - - *timeout_low = timeout & 0xFFFFFFFF; - *timeout_high = timeout >> 32; - } -} - -/** * Switches the CPU's active thread context to that of the specified thread * @param new_thread The thread to switch to */ @@ -254,32 +208,6 @@ static void SwitchContext(Thread* new_thread) { current_thread = new_thread; - // If the thread was waited by a svcWaitSynch call, step back PC by one instruction to rerun - // the SVC when the thread wakes up. This is necessary to ensure that the thread can acquire - // the requested wait object(s) before continuing. - if (new_thread->waitsynch_waited) { - // CPSR flag indicates CPU mode - bool thumb_mode = (new_thread->context.cpsr & TBIT) != 0; - - // SVC instruction is 2 bytes for THUMB, 4 bytes for ARM - new_thread->context.pc -= thumb_mode ? 2 : 4; - - // Get the register for timeout parameter - u32 *timeout_low, *timeout_high; - std::tie(timeout_low, timeout_high) = GetWaitSynchTimeoutParameterRegister(new_thread); - - // Update the timeout parameter - UpdateTimeoutParameter(timeout_low, timeout_high, new_thread->last_running_ticks); - } - - // Clean up the thread's wait_objects, they'll be restored if needed during - // the svcWaitSynchronization call - for (size_t i = 0; i < new_thread->wait_objects.size(); ++i) { - SharedPtr<WaitObject> object = new_thread->wait_objects[i]; - object->RemoveWaitingThread(new_thread); - } - new_thread->wait_objects.clear(); - ready_queue.remove(new_thread->current_priority, new_thread); new_thread->status = THREADSTATUS_RUNNING; @@ -319,17 +247,13 @@ static Thread* PopNextReadyThread() { void WaitCurrentThread_Sleep() { Thread* thread = GetCurrentThread(); thread->status = THREADSTATUS_WAIT_SLEEP; - - HLE::Reschedule(__func__); } void WaitCurrentThread_WaitSynchronization(std::vector<SharedPtr<WaitObject>> wait_objects, - bool wait_set_output, bool wait_all) { + bool wait_set_output) { Thread* thread = GetCurrentThread(); thread->wait_set_output = wait_set_output; - thread->wait_all = wait_all; thread->wait_objects = std::move(wait_objects); - thread->waitsynch_waited = true; thread->status = THREADSTATUS_WAIT_SYNCH; } @@ -339,6 +263,13 @@ void WaitCurrentThread_ArbitrateAddress(VAddr wait_address) { thread->status = THREADSTATUS_WAIT_ARB; } +void ExitCurrentThread() { + Thread* thread = GetCurrentThread(); + thread->Stop(); + thread_list.erase(std::remove(thread_list.begin(), thread_list.end(), thread), + thread_list.end()); +} + /** * Callback that will wake up the thread it was scheduled for * @param thread_handle The handle of the thread that's been awoken @@ -351,15 +282,15 @@ static void ThreadWakeupCallback(u64 thread_handle, int cycles_late) { return; } - thread->waitsynch_waited = false; - if (thread->status == THREADSTATUS_WAIT_SYNCH || thread->status == THREADSTATUS_WAIT_ARB) { + thread->wait_set_output = false; + // Remove the thread from each of its waiting objects' waitlists + for (auto& object : thread->wait_objects) + object->RemoveWaitingThread(thread.get()); + thread->wait_objects.clear(); thread->SetWaitSynchronizationResult(ResultCode(ErrorDescription::Timeout, ErrorModule::OS, ErrorSummary::StatusChanged, ErrorLevel::Info)); - - if (thread->wait_set_output) - thread->SetWaitSynchronizationOutput(-1); } thread->ResumeFromWait(); @@ -399,6 +330,7 @@ void Thread::ResumeFromWait() { ready_queue.push_back(current_priority, this); status = THREADSTATUS_READY; + HLE::Reschedule(__func__); } /** @@ -494,13 +426,11 @@ ResultVal<SharedPtr<Thread>> Thread::Create(std::string name, VAddr entry_point, thread->last_running_ticks = CoreTiming::GetTicks(); thread->processor_id = processor_id; thread->wait_set_output = false; - thread->wait_all = false; thread->wait_objects.clear(); thread->wait_address = 0; thread->name = std::move(name); thread->callback_handle = wakeup_callback_handle_table.Create(thread).MoveFrom(); thread->owner_process = g_current_process; - thread->waitsynch_waited = false; // Find the next available TLS index, and mark it as used auto& tls_slots = Kernel::g_current_process->tls_slots; @@ -555,8 +485,6 @@ ResultVal<SharedPtr<Thread>> Thread::Create(std::string name, VAddr entry_point, ready_queue.push_back(thread->current_priority, thread.get()); thread->status = THREADSTATUS_READY; - HLE::Reschedule(__func__); - return MakeResult<SharedPtr<Thread>>(std::move(thread)); } @@ -619,14 +547,6 @@ void Reschedule() { HLE::DoneRescheduling(); - // Don't bother switching to the same thread. - // But if the thread was waiting on objects, we still need to switch it - // to perform PC modification, change state to RUNNING, etc. - // This occurs in the case when an object the thread is waiting on immediately wakes up - // the current thread before Reschedule() is called. - if (next == cur && (next == nullptr || next->waitsynch_waited == false)) - return; - if (cur && next) { LOG_TRACE(Kernel, "context switch %u -> %u", cur->GetObjectId(), next->GetObjectId()); } else if (cur) { diff --git a/src/core/hle/kernel/thread.h b/src/core/hle/kernel/thread.h index e0ffcea8a..d4fefc573 100644 --- a/src/core/hle/kernel/thread.h +++ b/src/core/hle/kernel/thread.h @@ -5,7 +5,9 @@ #pragma once #include <string> +#include <unordered_map> #include <vector> +#include <boost/container/flat_map.hpp> #include <boost/container/flat_set.hpp> #include "common/common_types.h" #include "core/core.h" @@ -125,6 +127,16 @@ public: void SetWaitSynchronizationOutput(s32 output); /** + * Retrieves the index that this particular object occupies in the list of objects + * that the thread passed to WaitSynchronizationN. + * It is used to set the output value of WaitSynchronizationN when the thread is awakened. + * @param object Object to query the index of. + */ + s32 GetWaitObjectIndex(const WaitObject* object) const { + return wait_objects_index.at(object->GetObjectId()); + } + + /** * Stops a thread, invalidating it from further use */ void Stop(); @@ -137,6 +149,15 @@ public: return tls_address; } + /** + * Returns whether this thread is waiting for all the objects in + * its wait list to become ready, as a result of a WaitSynchronizationN call + * with wait_all = true, or a ReplyAndReceive call. + */ + bool IsSleepingOnWaitAll() const { + return !wait_objects.empty(); + } + Core::ThreadContext context; u32 thread_id; @@ -154,16 +175,22 @@ public: VAddr tls_address; ///< Virtual address of the Thread Local Storage of the thread - bool waitsynch_waited; ///< Set to true if the last svcWaitSynch call caused the thread to wait - /// Mutexes currently held by this thread, which will be released when it exits. boost::container::flat_set<SharedPtr<Mutex>> held_mutexes; - SharedPtr<Process> owner_process; ///< Process that owns this thread - std::vector<SharedPtr<WaitObject>> wait_objects; ///< Objects that the thread is waiting on - VAddr wait_address; ///< If waiting on an AddressArbiter, this is the arbitration address - bool wait_all; ///< True if the thread is waiting on all objects before resuming - bool wait_set_output; ///< True if the output parameter should be set on thread wakeup + SharedPtr<Process> owner_process; ///< Process that owns this thread + + /// Objects that the thread is waiting on. + /// This is only populated when the thread should wait for all the objects to become ready. + std::vector<SharedPtr<WaitObject>> wait_objects; + + /// Mapping of Object ids to their position in the last waitlist that this object waited on. + boost::container::flat_map<int, s32> wait_objects_index; + + VAddr wait_address; ///< If waiting on an AddressArbiter, this is the arbitration address + + /// True if the WaitSynchronizationN output parameter should be set on thread wakeup. + bool wait_set_output; std::string name; @@ -215,10 +242,9 @@ void WaitCurrentThread_Sleep(); * @param wait_objects Kernel objects that we are waiting on * @param wait_set_output If true, set the output parameter on thread wakeup (for * WaitSynchronizationN only) - * @param wait_all If true, wait on all objects before resuming (for WaitSynchronizationN only) */ void WaitCurrentThread_WaitSynchronization(std::vector<SharedPtr<WaitObject>> wait_objects, - bool wait_set_output, bool wait_all); + bool wait_set_output); /** * Waits the current thread from an ArbitrateAddress call @@ -227,6 +253,11 @@ void WaitCurrentThread_WaitSynchronization(std::vector<SharedPtr<WaitObject>> wa void WaitCurrentThread_ArbitrateAddress(VAddr wait_address); /** + * Stops the current thread and removes it from the thread_list + */ +void ExitCurrentThread(); + +/** * Initialize threading */ void ThreadingInit(); diff --git a/src/core/hle/kernel/timer.cpp b/src/core/hle/kernel/timer.cpp index eac181f4e..b50cf520d 100644 --- a/src/core/hle/kernel/timer.cpp +++ b/src/core/hle/kernel/timer.cpp @@ -60,14 +60,10 @@ void Timer::Set(s64 initial, s64 interval) { u64 initial_microseconds = initial / 1000; CoreTiming::ScheduleEvent(usToCycles(initial_microseconds), timer_callback_event_type, callback_handle); - - HLE::Reschedule(__func__); } void Timer::Cancel() { CoreTiming::UnscheduleEvent(timer_callback_event_type, callback_handle); - - HLE::Reschedule(__func__); } void Timer::Clear() { |