diff --git a/Source/Core/Core/HW/DVDInterface.cpp b/Source/Core/Core/HW/DVDInterface.cpp index f9bab08896..1058dcd25e 100644 --- a/Source/Core/Core/HW/DVDInterface.cpp +++ b/Source/Core/Core/HW/DVDInterface.cpp @@ -236,7 +236,7 @@ static u32 g_ErrorCode = 0; static bool g_bDiscInside = false; bool g_bStream = false; static bool g_bStopAtTrackEnd = false; -static int tc = 0; +static int finish_execute_command = 0; static int dtk = 0; static u64 g_last_read_offset; @@ -291,7 +291,7 @@ void DoState(PointerWrap &p) p.Do(g_bStopAtTrackEnd); } -static void TransferComplete(u64 userdata, int cyclesLate) +static void FinishExecuteCommand(u64 userdata, int cyclesLate) { if (m_DICR.TSTART) { @@ -393,7 +393,7 @@ void Init() ejectDisc = CoreTiming::RegisterEvent("EjectDisc", EjectDiscCallback); insertDisc = CoreTiming::RegisterEvent("InsertDisc", InsertDiscCallback); - tc = CoreTiming::RegisterEvent("TransferComplete", TransferComplete); + finish_execute_command = CoreTiming::RegisterEvent("FinishExecuteCommand", FinishExecuteCommand); dtk = CoreTiming::RegisterEvent("StreamingTimer", DTKStreamingCallback); CoreTiming::ScheduleEvent(0, dtk); @@ -541,12 +541,8 @@ void RegisterMMIO(MMIO::Mapping* mmio, u32 base) m_DICR.Hex = val & 7; if (m_DICR.TSTART) { - DVDCommandResult result = ExecuteCommand( - m_DICMDBUF[0].Hex, m_DICMDBUF[1].Hex, m_DICMDBUF[2].Hex, - m_DIMAR.Hex, m_DILENGTH.Hex, true); - - // The transfer is finished after a delay - CoreTiming::ScheduleEvent((int)result.ticks_until_completion, tc, result.interrupt_type); + ExecuteCommand(m_DICMDBUF[0].Hex, m_DICMDBUF[1].Hex, m_DICMDBUF[2].Hex, + m_DIMAR.Hex, m_DILENGTH.Hex, true, finish_execute_command); } }) ); @@ -629,8 +625,11 @@ void ExecuteReadCommand(u64 DVD_offset, u32 output_address, u32 DVD_length, result->interrupt_type = INT_TCINT; } -DVDCommandResult ExecuteCommand(u32 command_0, u32 command_1, u32 command_2, - u32 output_address, u32 output_length, bool write_to_DIIMMBUF) +// When the command has finished executing, callback_event_type +// will be called using CoreTiming::ScheduleEvent, +// with the userdata set to the interrupt type. +void ExecuteCommand(u32 command_0, u32 command_1, u32 command_2, u32 output_address, u32 output_length, + bool write_to_DIIMMBUF, int callback_event_type) { DVDCommandResult result; result.interrupt_type = INT_TCINT; @@ -1201,7 +1200,9 @@ DVDCommandResult ExecuteCommand(u32 command_0, u32 command_1, u32 command_2, break; } - return result; + // The command will finish executing after a delay, + // to simulate the speed of a real disc drive + CoreTiming::ScheduleEvent((int)result.ticks_until_completion, callback_event_type, result.interrupt_type); } // Simulates the timing aspects of reading data from a disc. diff --git a/Source/Core/Core/HW/DVDInterface.h b/Source/Core/Core/HW/DVDInterface.h index 5b8a249fee..094896dd36 100644 --- a/Source/Core/Core/HW/DVDInterface.h +++ b/Source/Core/Core/HW/DVDInterface.h @@ -105,7 +105,7 @@ void ChangeDisc(const std::string& fileName); // DVD Access Functions bool DVDRead(u64 _iDVDOffset, u32 _iRamAddress, u32 _iLength, bool decrypt); extern bool g_bStream; -DVDCommandResult ExecuteCommand(u32 command_0, u32 command_1, u32 command_2, - u32 output_address, u32 output_length, bool write_to_DIIMMBUF); +void ExecuteCommand(u32 command_0, u32 command_1, u32 command_2, u32 output_address, u32 output_length, + bool write_to_DIIMMBUF, int callback_event_type); } // end of namespace DVDInterface diff --git a/Source/Core/Core/IPC_HLE/WII_IPC_HLE.cpp b/Source/Core/Core/IPC_HLE/WII_IPC_HLE.cpp index 2f5de7e188..08b5ed2ed9 100644 --- a/Source/Core/Core/IPC_HLE/WII_IPC_HLE.cpp +++ b/Source/Core/Core/IPC_HLE/WII_IPC_HLE.cpp @@ -85,7 +85,7 @@ static u64 last_reply_time; static const u64 ENQUEUE_REQUEST_FLAG = 0x100000000ULL; static const u64 ENQUEUE_ACKNOWLEDGEMENT_FLAG = 0x200000000ULL; -static void EnqueueEventCallback(u64 userdata, int) +static void EnqueueEvent(u64 userdata, int cycles_late = 0) { if (userdata & ENQUEUE_ACKNOWLEDGEMENT_FLAG) { @@ -144,7 +144,7 @@ void Init() g_DeviceMap[i] = new CWII_IPC_HLE_Device_stub(i, "/dev/usb/oh1"); i++; g_DeviceMap[i] = new IWII_IPC_HLE_Device(i, "_Unimplemented_Device_"); i++; - event_enqueue = CoreTiming::RegisterEvent("IPCEvent", EnqueueEventCallback); + event_enqueue = CoreTiming::RegisterEvent("IPCEvent", EnqueueEvent); } void Reset(bool _bHard) @@ -563,6 +563,11 @@ void EnqueueReply_Threadsafe(u32 address, int cycles_in_future) CoreTiming::ScheduleEvent_Threadsafe(cycles_in_future, event_enqueue, address); } +void EnqueueReply_Immediate(u32 address) +{ + EnqueueEvent(address); +} + void EnqueueCommandAcknowledgement(u32 address, int cycles_in_future) { CoreTiming::ScheduleEvent(cycles_in_future, event_enqueue, diff --git a/Source/Core/Core/IPC_HLE/WII_IPC_HLE.h b/Source/Core/Core/IPC_HLE/WII_IPC_HLE.h index 1175462dc3..3383642b74 100644 --- a/Source/Core/Core/IPC_HLE/WII_IPC_HLE.h +++ b/Source/Core/Core/IPC_HLE/WII_IPC_HLE.h @@ -80,6 +80,7 @@ void ExecuteCommand(u32 _Address); void EnqueueRequest(u32 address); void EnqueueReply(u32 address, int cycles_in_future = 0); void EnqueueReply_Threadsafe(u32 address, int cycles_in_future = 0); +void EnqueueReply_Immediate(u32 address); void EnqueueCommandAcknowledgement(u32 _Address, int cycles_in_future = 0); } // end of namespace WII_IPC_HLE_Interface diff --git a/Source/Core/Core/IPC_HLE/WII_IPC_HLE_Device_DI.cpp b/Source/Core/Core/IPC_HLE/WII_IPC_HLE_Device_DI.cpp index 6592b54881..07a22b79a2 100644 --- a/Source/Core/Core/IPC_HLE/WII_IPC_HLE_Device_DI.cpp +++ b/Source/Core/Core/IPC_HLE/WII_IPC_HLE_Device_DI.cpp @@ -9,6 +9,7 @@ #include "Common/Logging/LogManager.h" #include "Core/ConfigManager.h" +#include "Core/CoreTiming.h" #include "Core/VolumeHandler.h" #include "Core/HW/DVDInterface.h" #include "Core/HW/Memmap.h" @@ -17,14 +18,38 @@ #include "Core/IPC_HLE/WII_IPC_HLE.h" #include "Core/IPC_HLE/WII_IPC_HLE_Device_DI.h" -using namespace DVDInterface; +static CWII_IPC_HLE_Device_di* g_di_pointer; +static int ioctl_callback; -CWII_IPC_HLE_Device_di::CWII_IPC_HLE_Device_di(u32 _DeviceID, const std::string& _rDeviceName ) +static void IOCtlCallback(u64 userdata, int cycles_late) +{ + if (g_di_pointer != nullptr) + g_di_pointer->FinishIOCtl((DVDInterface::DIInterruptType)userdata); + + // If g_di_pointer == nullptr, IOS was probably shut down, + // so the command shouldn't be completed +} + +CWII_IPC_HLE_Device_di::CWII_IPC_HLE_Device_di(u32 _DeviceID, const std::string& _rDeviceName) : IWII_IPC_HLE_Device(_DeviceID, _rDeviceName) -{} +{ + if (g_di_pointer == nullptr) + ERROR_LOG(WII_IPC_DVD, "Trying to run two DI devices at once. IOCtl may not behave as expected."); + + g_di_pointer = this; + ioctl_callback = CoreTiming::RegisterEvent("IOCtlCallbackDI", IOCtlCallback); +} CWII_IPC_HLE_Device_di::~CWII_IPC_HLE_Device_di() -{} +{ + g_di_pointer = nullptr; +} + +void CWII_IPC_HLE_Device_di::DoState(PointerWrap& p) +{ + DoStateShared(p); + p.Do(m_commands_to_execute); +} IPCCommandResult CWII_IPC_HLE_Device_di::Open(u32 _CommandAddress, u32 _Mode) { @@ -43,10 +68,29 @@ IPCCommandResult CWII_IPC_HLE_Device_di::Close(u32 _CommandAddress, bool _bForce IPCCommandResult CWII_IPC_HLE_Device_di::IOCtl(u32 _CommandAddress) { - u32 BufferIn = Memory::Read_U32(_CommandAddress + 0x10); - u32 BufferInSize = Memory::Read_U32(_CommandAddress + 0x14); - u32 BufferOut = Memory::Read_U32(_CommandAddress + 0x18); - u32 BufferOutSize = Memory::Read_U32(_CommandAddress + 0x1C); + // DI IOCtls are handled in a special way by Dolphin + // compared to other WII_IPC_HLE functions. + // This is a wrapper around DVDInterface's ExecuteCommand, + // which will execute commands more or less asynchronously. + // Only one command can be executed at a time, so commands + // are queued until DVDInterface is ready to handle them. + + bool ready_to_execute = m_commands_to_execute.empty(); + m_commands_to_execute.push_back(_CommandAddress); + if (ready_to_execute) + StartIOCtl(_CommandAddress); + + // DVDInterface handles the timing, and we handle the reply, + // so WII_IPC_HLE shouldn't do any of that. + return IPC_NO_REPLY; +} + +void CWII_IPC_HLE_Device_di::StartIOCtl(u32 command_address) +{ + u32 BufferIn = Memory::Read_U32(command_address + 0x10); + u32 BufferInSize = Memory::Read_U32(command_address + 0x14); + u32 BufferOut = Memory::Read_U32(command_address + 0x18); + u32 BufferOutSize = Memory::Read_U32(command_address + 0x1C); u32 command_0 = Memory::Read_U32(BufferIn); u32 command_1 = Memory::Read_U32(BufferIn + 4); @@ -63,11 +107,38 @@ IPCCommandResult CWII_IPC_HLE_Device_di::IOCtl(u32 _CommandAddress) Memory::Memset(BufferOut, 0, BufferOutSize); } - DVDCommandResult result = ExecuteCommand(command_0, command_1, command_2, - BufferOut, BufferOutSize, false); - Memory::Write_U32(result.interrupt_type, _CommandAddress + 0x4); + // DVDInterface's ExecuteCommand handles most of the work. + // The IOCtl callback is used to generate a reply afterwards. + DVDInterface::ExecuteCommand(command_0, command_1, command_2, BufferOut, BufferOutSize, + false, ioctl_callback); +} - return { true, result.ticks_until_completion }; +void CWII_IPC_HLE_Device_di::FinishIOCtl(DVDInterface::DIInterruptType interrupt_type) +{ + if (m_commands_to_execute.empty()) + { + PanicAlertT("WII_IPC_HLE_Device_DI tried to reply to non-existing command"); + return; + } + + // This command has been executed, so it's removed from the queue + u32 command_address = m_commands_to_execute.front(); + m_commands_to_execute.pop_front(); + + // The DI interrupt type is used as a return value + Memory::Write_U32(interrupt_type, command_address + 4); + + // The original hardware overwrites the command type with the async reply type. + Memory::Write_U32(IPC_REP_ASYNC, command_address); + // IOS also seems to write back the command that was responded to in the FD field. + Memory::Write_U32(Memory::Read_U32(command_address), command_address + 8); + // Generate a reply to the IPC command + WII_IPC_HLE_Interface::EnqueueReply_Immediate(command_address); + + // DVDInterface is now ready to execute another command, + // so we start executing a command from the queue if there is one + if (!m_commands_to_execute.empty()) + StartIOCtl(m_commands_to_execute.front()); } IPCCommandResult CWII_IPC_HLE_Device_di::IOCtlV(u32 _CommandAddress) @@ -85,7 +156,7 @@ IPCCommandResult CWII_IPC_HLE_Device_di::IOCtlV(u32 _CommandAddress) u32 ReturnValue = 0; switch (CommandBuffer.Parameter) { - case DVDLowOpenPartition: + case DVDInterface::DVDLowOpenPartition: { _dbg_assert_msg_(WII_IPC_DVD, CommandBuffer.InBuffer[1].m_Address == 0, "DVDLowOpenPartition with ticket"); _dbg_assert_msg_(WII_IPC_DVD, CommandBuffer.InBuffer[2].m_Address == 0, "DVDLowOpenPartition with cert chain"); diff --git a/Source/Core/Core/IPC_HLE/WII_IPC_HLE_Device_DI.h b/Source/Core/Core/IPC_HLE/WII_IPC_HLE_Device_DI.h index 6c10e8f258..4ca874ed1d 100644 --- a/Source/Core/Core/IPC_HLE/WII_IPC_HLE_Device_DI.h +++ b/Source/Core/Core/IPC_HLE/WII_IPC_HLE_Device_DI.h @@ -4,14 +4,10 @@ #pragma once +#include +#include "Core/HW/DVDInterface.h" #include "Core/IPC_HLE/WII_IPC_HLE_Device.h" -namespace DiscIO -{ - class IVolume; - class IFileSystem; -} - class CWII_IPC_HLE_Device_di : public IWII_IPC_HLE_Device { public: @@ -20,9 +16,18 @@ public: virtual ~CWII_IPC_HLE_Device_di(); + void DoState(PointerWrap& p) override; + IPCCommandResult Open(u32 _CommandAddress, u32 _Mode) override; IPCCommandResult Close(u32 _CommandAddress, bool _bForce) override; IPCCommandResult IOCtl(u32 _CommandAddress) override; IPCCommandResult IOCtlV(u32 _CommandAddress) override; + + void FinishIOCtl(DVDInterface::DIInterruptType interrupt_type); +private: + + void StartIOCtl(u32 command_address); + + std::deque m_commands_to_execute; };