diff --git a/src/core/hle/service/gsp/gsp_gpu.cpp b/src/core/hle/service/gsp/gsp_gpu.cpp index bb3cedbe..11f9f4ae 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,9 @@ 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; + /// 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 +321,20 @@ 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"; + u32 thread_id = next_thread_id++; + ASSERT_MSG(thread_id < MaxGSPThreads, "GSP thread id overflow"); + + SessionData* session_data = GetSessionData(ctx.Session()); + session_data->thread_id = thread_id; + session_data->interrupt_event = std::move(interrupt_event); + session_data->registered = true; + IPC::RequestBuilder rb = rp.MakeBuilder(2, 2); if (first_initialization) { @@ -338,22 +348,23 @@ void GSP_GPU::RegisterInterruptRelayQueue(Kernel::HLERequestContext& ctx) { rb.Push(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->thread_id = 0; + session_data->interrupt_event = nullptr; + session_data->registered = false; + + // TODO(Subv): Reset next_thread_id so that it doesn't go past the maximum of 4. IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); rb.Push(RESULT_SUCCESS); - LOG_WARNING(Service_GSP, "(STUBBED) called"); + LOG_DEBUG(Service_GSP, "called"); } /** @@ -363,18 +374,32 @@ 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!"); + // Don't do anything if no process has acquired the GPU right. + if (active_thread_id == -1) 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) { + + // 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. + for (int thread_id = 0; thread_id < MaxGSPThreads; ++thread_id) { + if (interrupt_id != InterruptId::PDC0 && interrupt_id != InterruptId::PDC1) { + // Ignore threads that aren't the current active thread + if (thread_id != active_thread_id) + continue; + } + SessionData* session_data = FindRegisteredThreadData(thread_id); + if (session_data == nullptr) + continue; + + auto interrupt_event = session_data->interrupt_event; + if (interrupt_event == nullptr) { + LOG_WARNING(Service_GSP, "cannot synchronize until GSP event has been created!"); + continue; + } InterruptRelayQueue* interrupt_relay_queue = GetInterruptRelayQueue(shared_memory, thread_id); u8 next = interrupt_relay_queue->index; @@ -389,6 +414,8 @@ void GSP_GPU::SignalInterrupt(InterruptId interrupt_id) { // 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) { @@ -398,8 +425,8 @@ void GSP_GPU::SignalInterrupt(InterruptId interrupt_id) { info->is_dirty.Assign(false); } } + interrupt_event->Signal(); } - interrupt_event->Signal(); } MICROPROFILE_DEFINE(GPU_GSP_DMA, "GPU", "GSP DMA", MP_RGB(100, 0, 255)); @@ -622,18 +649,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 +698,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 +745,12 @@ 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; }; - } // 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 98756a8f..01bab865 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,16 @@ struct CommandBuffer { }; static_assert(sizeof(CommandBuffer) == 0x200, "CommandBuffer struct has incorrect size"); -class GSP_GPU final : public ServiceFramework { +struct SessionData : public Kernel::SessionRequestHandler::SessionDataBase { + /// Event triggered when GSP interrupt has been signalled + Kernel::SharedPtr interrupt_event; + /// Thread index into interrupt relay queue + u32 thread_id = 0; + /// Whether RegisterInterruptRelayQueue was called for this session + bool registered = false; +}; + +class GSP_GPU final : public ServiceFramework { public: GSP_GPU(); ~GSP_GPU() = default; @@ -351,14 +360,18 @@ class GSP_GPU final : public ServiceFramework { */ void StoreDataCache(Kernel::HLERequestContext& ctx); - /// Event triggered when GSP interrupt has been signalled - Kernel::SharedPtr interrupt_event; - /// GSP shared memoryings + /// Returns the session data for the specified registered thread id, or nullptr if not found. + SessionData* FindRegisteredThreadData(u32 thread_id); + + /// Next threadid value to use when RegisterInterruptRelayQueue is called. + u32 next_thread_id = 0; + + /// GSP shared memory Kernel::SharedPtr shared_memory; - /// Thread index into interrupt relay queue - u32 thread_id = 0; - bool gpu_right_acquired = false; + /// Thread id that currently has GPU rights or -1 if none. + int active_thread_id = -1; + bool first_initialization = true; };