diff --git a/src/core/hle/kernel/hle_ipc.h b/src/core/hle/kernel/hle_ipc.h index ccff81a21..95d9624db 100644 --- a/src/core/hle/kernel/hle_ipc.h +++ b/src/core/hle/kernel/hle_ipc.h @@ -59,7 +59,9 @@ public: /// Empty placeholder structure for services with no per-session data. The session data classes /// in each service must inherit from this. - struct SessionDataBase {}; + struct SessionDataBase { + virtual ~SessionDataBase() = default; + }; protected: /// Creates the storage for the session data of the service. diff --git a/src/core/hle/service/gsp/gsp_gpu.cpp b/src/core/hle/service/gsp/gsp_gpu.cpp index bb3cedbe3..3a919e87e 100644 --- a/src/core/hle/service/gsp/gsp_gpu.cpp +++ b/src/core/hle/service/gsp/gsp_gpu.cpp @@ -9,7 +9,6 @@ #include "core/core.h" #include "core/hle/ipc.h" #include "core/hle/ipc_helpers.h" -#include "core/hle/kernel/event.h" #include "core/hle/kernel/handle_table.h" #include "core/hle/kernel/shared_memory.h" #include "core/hle/result.h" @@ -51,6 +50,20 @@ constexpr ResultCode ERR_REGS_INVALID_SIZE(ErrorDescription::InvalidSize, ErrorM ErrorSummary::InvalidArgument, ErrorLevel::Usage); // 0xE0E02BEC +/// Maximum number of threads that can be registered at the same time in the GSP module. +constexpr u32 MaxGSPThreads = 4; + +/// Thread ids currently in use by the sessions connected to the GSPGPU service. +static std::array used_thread_ids = {false, false, false, false}; + +static u32 GetUnusedThreadId() { + for (u32 id = 0; id < MaxGSPThreads; ++id) { + if (!used_thread_ids[id]) + return id; + } + ASSERT_MSG(false, "All GSP threads are in use"); +} + /// Gets a pointer to a thread command buffer in GSP shared memory static inline u8* GetCommandBuffer(Kernel::SharedPtr shared_memory, u32 thread_id) { @@ -319,12 +332,16 @@ void GSP_GPU::RegisterInterruptRelayQueue(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx, 0x13, 1, 2); u32 flags = rp.Pop(); - interrupt_event = rp.PopObject(); + auto interrupt_event = rp.PopObject(); // TODO(mailwl): return right error code instead assert ASSERT_MSG((interrupt_event != nullptr), "handle is not valid!"); interrupt_event->name = "GSP_GSP_GPU::interrupt_event"; + SessionData* session_data = GetSessionData(ctx.Session()); + session_data->interrupt_event = std::move(interrupt_event); + session_data->registered = true; + IPC::RequestBuilder rb = rp.MakeBuilder(2, 2); if (first_initialization) { @@ -335,25 +352,60 @@ void GSP_GPU::RegisterInterruptRelayQueue(Kernel::HLERequestContext& ctx) { rb.Push(RESULT_SUCCESS); } - rb.Push(thread_id); + rb.Push(session_data->thread_id); rb.PushCopyObjects(shared_memory); - thread_id++; - interrupt_event->Signal(); // TODO(bunnei): Is this correct? - - LOG_WARNING(Service_GSP, "called, flags=0x%08X", flags); + LOG_DEBUG(Service_GSP, "called, flags=0x%08X", flags); } void GSP_GPU::UnregisterInterruptRelayQueue(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx, 0x14, 0, 0); - thread_id = 0; - interrupt_event = nullptr; + SessionData* session_data = GetSessionData(ctx.Session()); + session_data->interrupt_event = nullptr; + session_data->registered = false; IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); rb.Push(RESULT_SUCCESS); - LOG_WARNING(Service_GSP, "(STUBBED) called"); + LOG_DEBUG(Service_GSP, "called"); +} + +void GSP_GPU::SignalInterruptForThread(InterruptId interrupt_id, u32 thread_id) { + SessionData* session_data = FindRegisteredThreadData(thread_id); + if (session_data == nullptr) + return; + + auto interrupt_event = session_data->interrupt_event; + if (interrupt_event == nullptr) { + LOG_WARNING(Service_GSP, "cannot synchronize until GSP event has been created!"); + return; + } + InterruptRelayQueue* interrupt_relay_queue = GetInterruptRelayQueue(shared_memory, thread_id); + u8 next = interrupt_relay_queue->index; + next += interrupt_relay_queue->number_interrupts; + next = next % 0x34; // 0x34 is the number of interrupt slots + + interrupt_relay_queue->number_interrupts += 1; + + interrupt_relay_queue->slot[next] = interrupt_id; + interrupt_relay_queue->error_code = 0x0; // No error + + // Update framebuffer information if requested + // TODO(yuriks): Confirm where this code should be called. It is definitely updated without + // executing any GSP commands, only waiting on the event. + // TODO(Subv): The real GSP module triggers PDC0 after updating both the top and bottom + // screen, it is currently unknown what PDC1 does. + int screen_id = + (interrupt_id == InterruptId::PDC0) ? 0 : (interrupt_id == InterruptId::PDC1) ? 1 : -1; + if (screen_id != -1) { + FrameBufferUpdate* info = GetFrameBufferInfo(thread_id, screen_id); + if (info->is_dirty) { + GSP::SetBufferSwap(screen_id, info->framebuffer_info[info->index]); + info->is_dirty.Assign(false); + } + } + interrupt_event->Signal(); } /** @@ -363,43 +415,26 @@ void GSP_GPU::UnregisterInterruptRelayQueue(Kernel::HLERequestContext& ctx) { * @todo This probably does not belong in the GSP module, instead move to video_core */ void GSP_GPU::SignalInterrupt(InterruptId interrupt_id) { - if (!gpu_right_acquired) { - return; - } - if (nullptr == interrupt_event) { - LOG_WARNING(Service_GSP, "cannot synchronize until GSP event has been created!"); - return; - } if (nullptr == shared_memory) { LOG_WARNING(Service_GSP, "cannot synchronize until GSP shared memory has been created!"); return; } - for (int thread_id = 0; thread_id < 0x4; ++thread_id) { - InterruptRelayQueue* interrupt_relay_queue = - GetInterruptRelayQueue(shared_memory, thread_id); - u8 next = interrupt_relay_queue->index; - next += interrupt_relay_queue->number_interrupts; - next = next % 0x34; // 0x34 is the number of interrupt slots - interrupt_relay_queue->number_interrupts += 1; - - interrupt_relay_queue->slot[next] = interrupt_id; - interrupt_relay_queue->error_code = 0x0; // No error - - // Update framebuffer information if requested - // TODO(yuriks): Confirm where this code should be called. It is definitely updated without - // executing any GSP commands, only waiting on the event. - int screen_id = - (interrupt_id == InterruptId::PDC0) ? 0 : (interrupt_id == InterruptId::PDC1) ? 1 : -1; - if (screen_id != -1) { - FrameBufferUpdate* info = GetFrameBufferInfo(thread_id, screen_id); - if (info->is_dirty) { - GSP::SetBufferSwap(screen_id, info->framebuffer_info[info->index]); - info->is_dirty.Assign(false); - } + // The PDC0 and PDC1 interrupts are fired even if the GPU right hasn't been acquired. + // Normal interrupts are only signaled for the active thread (ie, the thread that has the GPU + // right), but the PDC0/1 interrupts are signaled for every registered thread. + if (interrupt_id == InterruptId::PDC0 || interrupt_id == InterruptId::PDC1) { + for (u32 thread_id = 0; thread_id < MaxGSPThreads; ++thread_id) { + SignalInterruptForThread(interrupt_id, thread_id); } + return; } - interrupt_event->Signal(); + + // For normal interrupts, don't do anything if no process has acquired the GPU right. + if (active_thread_id == -1) + return; + + SignalInterruptForThread(interrupt_id, active_thread_id); } MICROPROFILE_DEFINE(GPU_GSP_DMA, "GPU", "GSP DMA", MP_RGB(100, 0, 255)); @@ -622,18 +657,34 @@ void GSP_GPU::AcquireRight(Kernel::HLERequestContext& ctx) { u32 flag = rp.Pop(); auto process = rp.PopObject(); - gpu_right_acquired = true; + SessionData* session_data = GetSessionData(ctx.Session()); + + LOG_WARNING(Service_GSP, "called flag=%08X process=%u thread_id=%u", flag, process->process_id, + session_data->thread_id); IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); - rb.Push(RESULT_SUCCESS); - LOG_WARNING(Service_GSP, "called flag=%08X process=%u", flag, process->process_id); + if (active_thread_id == session_data->thread_id) { + rb.Push(ResultCode(ErrorDescription::AlreadyDone, ErrorModule::GX, ErrorSummary::Success, + ErrorLevel::Success)); + return; + } + + // TODO(Subv): This case should put the caller thread to sleep until the right is released. + ASSERT_MSG(active_thread_id == -1, "GPU right has already been acquired"); + + active_thread_id = session_data->thread_id; + + rb.Push(RESULT_SUCCESS); } void GSP_GPU::ReleaseRight(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx, 0x17, 0, 0); - gpu_right_acquired = false; + SessionData* session_data = GetSessionData(ctx.Session()); + ASSERT_MSG(active_thread_id == session_data->thread_id, + "Wrong thread tried to release GPU right"); + active_thread_id = -1; IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); rb.Push(RESULT_SUCCESS); @@ -655,6 +706,17 @@ void GSP_GPU::StoreDataCache(Kernel::HLERequestContext& ctx) { size, process->process_id); } +SessionData* GSP_GPU::FindRegisteredThreadData(u32 thread_id) { + for (auto& session_info : connected_sessions) { + SessionData* data = static_cast(session_info.data.get()); + if (!data->registered) + continue; + if (data->thread_id == thread_id) + return data; + } + return nullptr; +} + GSP_GPU::GSP_GPU() : ServiceFramework("gsp::Gpu", 2) { static const FunctionInfo functions[] = { {0x00010082, &GSP_GPU::WriteHWRegs, "WriteHWRegs"}, @@ -691,17 +753,26 @@ GSP_GPU::GSP_GPU() : ServiceFramework("gsp::Gpu", 2) { }; RegisterHandlers(functions); - interrupt_event = nullptr; - using Kernel::MemoryPermission; shared_memory = Kernel::SharedMemory::Create(nullptr, 0x1000, MemoryPermission::ReadWrite, MemoryPermission::ReadWrite, 0, Kernel::MemoryRegion::BASE, "GSP:SharedMemory"); - thread_id = 0; - gpu_right_acquired = false; first_initialization = true; }; +SessionData::SessionData() { + // Assign a new thread id to this session when it connects. Note: In the real GSP service this + // is done through a real thread (svcCreateThread) but we have to simulate it since our HLE + // services don't have threads. + thread_id = GetUnusedThreadId(); + used_thread_ids[thread_id] = true; +} + +SessionData::~SessionData() { + // Free the thread id slot so that other sessions can use it. + used_thread_ids[thread_id] = false; +} + } // namespace GSP } // namespace Service diff --git a/src/core/hle/service/gsp/gsp_gpu.h b/src/core/hle/service/gsp/gsp_gpu.h index 98756a8ff..214d96c0e 100644 --- a/src/core/hle/service/gsp/gsp_gpu.h +++ b/src/core/hle/service/gsp/gsp_gpu.h @@ -8,12 +8,12 @@ #include #include "common/bit_field.h" #include "common/common_types.h" +#include "core/hle/kernel/event.h" #include "core/hle/kernel/hle_ipc.h" #include "core/hle/result.h" #include "core/hle/service/service.h" namespace Kernel { -class Event; class SharedMemory; } // namespace Kernel @@ -179,7 +179,19 @@ struct CommandBuffer { }; static_assert(sizeof(CommandBuffer) == 0x200, "CommandBuffer struct has incorrect size"); -class GSP_GPU final : public ServiceFramework { +struct SessionData : public Kernel::SessionRequestHandler::SessionDataBase { + SessionData(); + ~SessionData(); + + /// Event triggered when GSP interrupt has been signalled + Kernel::SharedPtr interrupt_event; + /// Thread index into interrupt relay queue + u32 thread_id; + /// Whether RegisterInterruptRelayQueue was called for this session + bool registered = false; +}; + +class GSP_GPU final : public ServiceFramework { public: GSP_GPU(); ~GSP_GPU() = default; @@ -201,6 +213,14 @@ public: FrameBufferUpdate* GetFrameBufferInfo(u32 thread_id, u32 screen_index); private: + /** + * Signals that the specified interrupt type has occurred to userland code for the specified GSP + * thread id. + * @param interrupt_id ID of interrupt that is being signalled. + * @param thread_id GSP thread that will receive the interrupt. + */ + void SignalInterruptForThread(InterruptId interrupt_id, u32 thread_id); + /** * GSP_GPU::WriteHWRegs service function * @@ -351,14 +371,15 @@ private: */ void StoreDataCache(Kernel::HLERequestContext& ctx); - /// Event triggered when GSP interrupt has been signalled - Kernel::SharedPtr interrupt_event; - /// GSP shared memoryings - Kernel::SharedPtr shared_memory; - /// Thread index into interrupt relay queue - u32 thread_id = 0; + /// Returns the session data for the specified registered thread id, or nullptr if not found. + SessionData* FindRegisteredThreadData(u32 thread_id); + + /// GSP shared memory + Kernel::SharedPtr shared_memory; + + /// Thread id that currently has GPU rights or -1 if none. + int active_thread_id = -1; - bool gpu_right_acquired = false; bool first_initialization = true; };