Merge pull request #1854 from JosJuice/read-disc-after-delay

DVDInterface: Read disc after delay, not before
This commit is contained in:
Pierre Bourdon 2015-02-18 20:35:07 +00:00
commit 8cc6e5cff9
9 changed files with 236 additions and 88 deletions

View File

@ -234,8 +234,14 @@ void ScheduleEvent_Threadsafe(int cyclesIntoFuture, int event_type, u64 userdata
tsQueue.Push(ne); tsQueue.Push(ne);
} }
// Executes an event immediately, then returns.
void ScheduleEvent_Immediate(int event_type, u64 userdata)
{
event_types[event_type].callback(userdata, 0);
}
// Same as ScheduleEvent_Threadsafe(0, ...) EXCEPT if we are already on the CPU thread // Same as ScheduleEvent_Threadsafe(0, ...) EXCEPT if we are already on the CPU thread
// in which case the event will get handled immediately, before returning. // in which case this is the same as ScheduleEvent_Immediate.
void ScheduleEvent_Threadsafe_Immediate(int event_type, u64 userdata) void ScheduleEvent_Threadsafe_Immediate(int event_type, u64 userdata)
{ {
if (Core::IsCPUThread()) if (Core::IsCPUThread())

View File

@ -43,11 +43,11 @@ void DoState(PointerWrap &p);
int RegisterEvent(const std::string& name, TimedCallback callback); int RegisterEvent(const std::string& name, TimedCallback callback);
void UnregisterAllEvents(); void UnregisterAllEvents();
// userdata MAY NOT CONTAIN POINTERS. userdata might get written and reloaded from disk, // userdata MAY NOT CONTAIN POINTERS. userdata might get written and reloaded from savestates.
// when we implement state saves. void ScheduleEvent(int cyclesIntoFuture, int event_type, u64 userdata = 0);
void ScheduleEvent(int cyclesIntoFuture, int event_type, u64 userdata=0); void ScheduleEvent_Immediate(int event_type, u64 userdata = 0);
void ScheduleEvent_Threadsafe(int cyclesIntoFuture, int event_type, u64 userdata=0); void ScheduleEvent_Threadsafe(int cyclesIntoFuture, int event_type, u64 userdata = 0);
void ScheduleEvent_Threadsafe_Immediate(int event_type, u64 userdata=0); void ScheduleEvent_Threadsafe_Immediate(int event_type, u64 userdata = 0);
// We only permit one event of each type in the queue at a time. // We only permit one event of each type in the queue at a time.
void RemoveEvent(int event_type); void RemoveEvent(int event_type);

View File

@ -213,6 +213,22 @@ union UDICFG
UDICFG(u32 _hex) {Hex = _hex;} UDICFG(u32 _hex) {Hex = _hex;}
}; };
struct DVDReadCommand
{
bool is_valid;
u64 DVD_offset;
u32 output_address;
u32 length;
bool decrypt;
DIInterruptType interrupt_type;
// Used to notify emulated software after executing command.
// Pointers don't work with savestates, so CoreTiming events are used instead
int callback_event_type;
};
// STATE_TO_SAVE // STATE_TO_SAVE
// hardware registers // hardware registers
@ -225,6 +241,8 @@ static UDICR m_DICR;
static UDIIMMBUF m_DIIMMBUF; static UDIIMMBUF m_DIIMMBUF;
static UDICFG m_DICFG; static UDICFG m_DICFG;
static DVDReadCommand current_read_command;
static u32 AudioPos; static u32 AudioPos;
static u32 CurrentStart; static u32 CurrentStart;
static u32 CurrentLength; static u32 CurrentLength;
@ -236,7 +254,8 @@ static u32 g_ErrorCode = 0;
static bool g_bDiscInside = false; static bool g_bDiscInside = false;
bool g_bStream = false; bool g_bStream = false;
static bool g_bStopAtTrackEnd = false; static bool g_bStopAtTrackEnd = false;
static int tc = 0; static int finish_execute_command = 0;
static int finish_execute_read_command = 0;
static int dtk = 0; static int dtk = 0;
static u64 g_last_read_offset; static u64 g_last_read_offset;
@ -257,8 +276,8 @@ void UpdateInterrupts();
void GenerateDIInterrupt(DIInterruptType _DVDInterrupt); void GenerateDIInterrupt(DIInterruptType _DVDInterrupt);
void WriteImmediate(u32 value, u32 output_address, bool write_to_DIIMMBUF); void WriteImmediate(u32 value, u32 output_address, bool write_to_DIIMMBUF);
DVDCommandResult ExecuteReadCommand(u64 DVD_offset, u32 output_address, DVDReadCommand ExecuteReadCommand(u64 DVD_offset, u32 output_address, u32 DVD_length, u32 output_length,
u32 DVD_length, u32 output_length, bool decrypt); bool decrypt, DIInterruptType* interrupt_type, u64* ticks_until_completion);
u64 SimulateDiscReadTime(u64 offset, u32 length); u64 SimulateDiscReadTime(u64 offset, u32 length);
s64 CalculateRawDiscReadTime(u64 offset, s64 length); s64 CalculateRawDiscReadTime(u64 offset, s64 length);
@ -274,6 +293,8 @@ void DoState(PointerWrap &p)
p.Do(m_DIIMMBUF); p.Do(m_DIIMMBUF);
p.DoPOD(m_DICFG); p.DoPOD(m_DICFG);
p.Do(current_read_command);
p.Do(NextStart); p.Do(NextStart);
p.Do(AudioPos); p.Do(AudioPos);
p.Do(NextLength); p.Do(NextLength);
@ -291,7 +312,7 @@ void DoState(PointerWrap &p)
p.Do(g_bStopAtTrackEnd); p.Do(g_bStopAtTrackEnd);
} }
static void TransferComplete(u64 userdata, int cyclesLate) static void FinishExecuteCommand(u64 userdata, int cyclesLate)
{ {
if (m_DICR.TSTART) if (m_DICR.TSTART)
{ {
@ -301,6 +322,30 @@ static void TransferComplete(u64 userdata, int cyclesLate)
} }
} }
static void FinishExecuteReadCommand(u64 userdata, int cyclesLate)
{
if (!current_read_command.is_valid)
{
PanicAlertT("DVDInterface tried to execute non-existing command");
}
else
{
// Here is the actual disc reading
if (!DVDRead(current_read_command.DVD_offset, current_read_command.output_address,
current_read_command.length, current_read_command.decrypt))
{
PanicAlertT("Can't read from DVD_Plugin - DVD-Interface: Fatal Error");
}
}
// The command is marked as invalid because it shouldn't be used again
current_read_command.is_valid = false;
// The final step is to notify the emulated software that the command has been executed
CoreTiming::ScheduleEvent_Immediate(current_read_command.callback_event_type,
current_read_command.interrupt_type);
}
static u32 ProcessDTKSamples(short *tempPCM, u32 num_samples) static u32 ProcessDTKSamples(short *tempPCM, u32 num_samples)
{ {
u32 samples_processed = 0; u32 samples_processed = 0;
@ -376,6 +421,8 @@ void Init()
m_DICFG.Hex = 0; m_DICFG.Hex = 0;
m_DICFG.CONFIG = 1; // Disable bootrom descrambler m_DICFG.CONFIG = 1; // Disable bootrom descrambler
current_read_command.is_valid = false;
AudioPos = 0; AudioPos = 0;
NextStart = 0; NextStart = 0;
NextLength = 0; NextLength = 0;
@ -393,7 +440,8 @@ void Init()
ejectDisc = CoreTiming::RegisterEvent("EjectDisc", EjectDiscCallback); ejectDisc = CoreTiming::RegisterEvent("EjectDisc", EjectDiscCallback);
insertDisc = CoreTiming::RegisterEvent("InsertDisc", InsertDiscCallback); insertDisc = CoreTiming::RegisterEvent("InsertDisc", InsertDiscCallback);
tc = CoreTiming::RegisterEvent("TransferComplete", TransferComplete); finish_execute_command = CoreTiming::RegisterEvent("FinishExecuteCommand", FinishExecuteCommand);
finish_execute_read_command = CoreTiming::RegisterEvent("FinishExecuteReadCommand", FinishExecuteReadCommand);
dtk = CoreTiming::RegisterEvent("StreamingTimer", DTKStreamingCallback); dtk = CoreTiming::RegisterEvent("StreamingTimer", DTKStreamingCallback);
CoreTiming::ScheduleEvent(0, dtk); CoreTiming::ScheduleEvent(0, dtk);
@ -542,21 +590,8 @@ void RegisterMMIO(MMIO::Mapping* mmio, u32 base)
m_DICR.Hex = val & 7; m_DICR.Hex = val & 7;
if (m_DICR.TSTART) if (m_DICR.TSTART)
{ {
DVDCommandResult result = ExecuteCommand( ExecuteCommand(m_DICMDBUF[0].Hex, m_DICMDBUF[1].Hex, m_DICMDBUF[2].Hex,
m_DICMDBUF[0].Hex, m_DICMDBUF[1].Hex, m_DICMDBUF[2].Hex, m_DIMAR.Hex, m_DILENGTH.Hex, true, finish_execute_command);
m_DIMAR.Hex, m_DILENGTH.Hex, true);
if (SConfig::GetInstance().m_LocalCoreStartupParameter.bFastDiscSpeed)
{
// Make sure fast disc speed performs "instant" reads; in addition
// to being used to speed up games, fast disc speed is used as a
// workaround for crashes in Star Wars Rogue Leader.
TransferComplete(result.interrupt_type, 0);
}
else
{
// The transfer is finished after a delay
CoreTiming::ScheduleEvent((int)result.ticks_until_completion, tc, result.interrupt_type);
}
} }
}) })
); );
@ -612,38 +647,51 @@ void WriteImmediate(u32 value, u32 output_address, bool write_to_DIIMMBUF)
Memory::Write_U32(value, output_address); Memory::Write_U32(value, output_address);
} }
DVDCommandResult ExecuteReadCommand(u64 DVD_offset, u32 output_address, // If the returned DVDReadCommand has is_valid set to true,
u32 DVD_length, u32 output_length, bool decrypt) // FinishExecuteReadCommand must be used to finish executing it
DVDReadCommand ExecuteReadCommand(u64 DVD_offset, u32 output_address, u32 DVD_length, u32 output_length,
bool decrypt, DIInterruptType* interrupt_type, u64* ticks_until_completion)
{ {
DVDReadCommand command;
if (!g_bDiscInside)
{
g_ErrorCode = ERROR_NO_DISK | ERROR_COVER_H;
*interrupt_type = INT_DEINT;
command.is_valid = false;
return command;
}
if (DVD_length > output_length) if (DVD_length > output_length)
{ {
WARN_LOG(DVDINTERFACE, "Detected attempt to read more data from the DVD than fit inside the out buffer. Clamp."); WARN_LOG(DVDINTERFACE, "Detected attempt to read more data from the DVD than fit inside the out buffer. Clamp.");
DVD_length = output_length; DVD_length = output_length;
} }
DVDCommandResult result; if (SConfig::GetInstance().m_LocalCoreStartupParameter.bFastDiscSpeed)
result.ticks_until_completion = SimulateDiscReadTime(DVD_offset, DVD_length); *ticks_until_completion = 0; // An optional hack to speed up loading times
else
*ticks_until_completion = SimulateDiscReadTime(DVD_offset, DVD_length);
if (!g_bDiscInside) *interrupt_type = INT_TCINT;
{ command.is_valid = true;
g_ErrorCode = ERROR_NO_DISK | ERROR_COVER_H; command.DVD_offset = DVD_offset;
result.interrupt_type = INT_DEINT; command.output_address = output_address;
return result; command.length = DVD_length;
} command.decrypt = decrypt;
return command;
if (!DVDRead(DVD_offset, output_address, DVD_length, decrypt))
PanicAlertT("Can't read from DVD_Plugin - DVD-Interface: Fatal Error");
result.interrupt_type = INT_TCINT;
return result;
} }
DVDCommandResult ExecuteCommand(u32 command_0, u32 command_1, u32 command_2, // When the command has finished executing, callback_event_type
u32 output_address, u32 output_length, bool write_to_DIIMMBUF) // 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; DIInterruptType interrupt_type = INT_TCINT;
result.interrupt_type = INT_TCINT; u64 ticks_until_completion = SystemTimers::GetTicksPerSecond() / 15000;
result.ticks_until_completion = SystemTimers::GetTicksPerSecond() / 15000; DVDReadCommand read_command;
read_command.is_valid = false;
bool GCAM = (SConfig::GetInstance().m_SIDevice[0] == SIDEVICE_AM_BASEBOARD) && bool GCAM = (SConfig::GetInstance().m_SIDevice[0] == SIDEVICE_AM_BASEBOARD) &&
(SConfig::GetInstance().m_EXIDevice[2] == EXIDEVICE_AM_BASEBOARD); (SConfig::GetInstance().m_EXIDevice[2] == EXIDEVICE_AM_BASEBOARD);
@ -688,19 +736,21 @@ DVDCommandResult ExecuteCommand(u32 command_0, u32 command_1, u32 command_2,
// Only seems to be used from WII_IPC, not through direct access // Only seems to be used from WII_IPC, not through direct access
case DVDLowReadDiskID: case DVDLowReadDiskID:
INFO_LOG(DVDINTERFACE, "DVDLowReadDiskID"); INFO_LOG(DVDINTERFACE, "DVDLowReadDiskID");
result = ExecuteReadCommand(0, output_address, 0x20, output_length, false); read_command = ExecuteReadCommand(0, output_address, 0x20, output_length,
false, &interrupt_type, &ticks_until_completion);
break; break;
// Only used from WII_IPC. This is the only read command that decrypts data // Only used from WII_IPC. This is the only read command that decrypts data
case DVDLowRead: case DVDLowRead:
INFO_LOG(DVDINTERFACE, "DVDLowRead: DVDAddr: 0x%09" PRIx64 ", Size: 0x%x", (u64)command_2 << 2, command_1); INFO_LOG(DVDINTERFACE, "DVDLowRead: DVDAddr: 0x%09" PRIx64 ", Size: 0x%x", (u64)command_2 << 2, command_1);
result = ExecuteReadCommand((u64)command_2 << 2, output_address, command_1, output_length, true); read_command = ExecuteReadCommand((u64)command_2 << 2, output_address, command_1, output_length,
true, &interrupt_type, &ticks_until_completion);
break; break;
// Probably only used by Wii // Probably only used by Wii
case DVDLowWaitForCoverClose: case DVDLowWaitForCoverClose:
INFO_LOG(DVDINTERFACE, "DVDLowWaitForCoverClose"); INFO_LOG(DVDINTERFACE, "DVDLowWaitForCoverClose");
result.interrupt_type = (DIInterruptType)4; // ??? interrupt_type = (DIInterruptType)4; // ???
break; break;
// "Set Extension"...not sure what it does. GC only? // "Set Extension"...not sure what it does. GC only?
@ -773,14 +823,15 @@ DVDCommandResult ExecuteCommand(u32 command_0, u32 command_1, u32 command_2,
(command_2 > 0x7ed40000 && command_2 < 0x7ed40008) || (command_2 > 0x7ed40000 && command_2 < 0x7ed40008) ||
(((command_2 + command_1) > 0x7ed40000) && (command_2 + command_1) < 0x7ed40008))) (((command_2 + command_1) > 0x7ed40000) && (command_2 + command_1) < 0x7ed40008)))
{ {
result = ExecuteReadCommand((u64)command_2 << 2, output_address, command_1, output_length, false); read_command = ExecuteReadCommand((u64)command_2 << 2, output_address, command_1, output_length,
false, &interrupt_type, &ticks_until_completion);
} }
else else
{ {
WARN_LOG(DVDINTERFACE, "DVDLowUnencryptedRead: trying to read out of bounds @ %09" PRIx64, (u64)command_2 << 2); WARN_LOG(DVDINTERFACE, "DVDLowUnencryptedRead: trying to read out of bounds @ %09" PRIx64, (u64)command_2 << 2);
g_ErrorCode = ERROR_READY | ERROR_BLOCK_OOB; g_ErrorCode = ERROR_READY | ERROR_BLOCK_OOB;
// Should cause software to call DVDLowRequestError // Should cause software to call DVDLowRequestError
result.interrupt_type = INT_BRKINT; interrupt_type = INT_BRKINT;
} }
break; break;
@ -805,7 +856,7 @@ DVDCommandResult ExecuteCommand(u32 command_0, u32 command_1, u32 command_2,
// Does not work on retail discs/drives // Does not work on retail discs/drives
// Retail games send this command to see if they are running on real retail hw // Retail games send this command to see if they are running on real retail hw
g_ErrorCode = ERROR_READY | ERROR_INV_CMD; g_ErrorCode = ERROR_READY | ERROR_INV_CMD;
result.interrupt_type = INT_BRKINT; interrupt_type = INT_BRKINT;
break; break;
// DMA Read from Disc. Only seems to be used through direct access, not WII_IPC // DMA Read from Disc. Only seems to be used through direct access, not WII_IPC
@ -869,13 +920,15 @@ DVDCommandResult ExecuteCommand(u32 command_0, u32 command_1, u32 command_2,
} }
} }
result = ExecuteReadCommand(iDVDOffset, output_address, command_2, output_length, false); read_command = ExecuteReadCommand(iDVDOffset, output_address, command_2, output_length,
false, &interrupt_type, &ticks_until_completion);
} }
break; break;
case 0x40: // Read DiscID case 0x40: // Read DiscID
INFO_LOG(DVDINTERFACE, "Read DiscID %08x", Memory::Read_U32(output_address)); INFO_LOG(DVDINTERFACE, "Read DiscID %08x", Memory::Read_U32(output_address));
result = ExecuteReadCommand(0, output_address, 0x20, output_length, false); read_command = ExecuteReadCommand(0, output_address, 0x20, output_length,
false, &interrupt_type, &ticks_until_completion);
break; break;
default: default:
@ -1210,7 +1263,23 @@ DVDCommandResult ExecuteCommand(u32 command_0, u32 command_1, u32 command_2,
break; break;
} }
return result; // The command will finish executing after a delay,
// to simulate the speed of a real disc drive
if (read_command.is_valid)
{
// We schedule a FinishExecuteReadCommand (which will call the actual callback
// once it's done) so that the data transfer isn't completed too early.
// Most games don't care about it, but if it's done wrong, Resident Evil 3
// plays some extra noise when playing the menu selection sound effect.
read_command.callback_event_type = callback_event_type;
read_command.interrupt_type = interrupt_type;
current_read_command = read_command;
CoreTiming::ScheduleEvent((int)ticks_until_completion, finish_execute_read_command);
}
else
{
CoreTiming::ScheduleEvent((int)ticks_until_completion, callback_event_type, interrupt_type);
}
} }
// Simulates the timing aspects of reading data from a disc. // Simulates the timing aspects of reading data from a disc.

View File

@ -85,12 +85,6 @@ enum DIInterruptType
INT_CVRINT = 3, INT_CVRINT = 3,
}; };
struct DVDCommandResult
{
DIInterruptType interrupt_type;
u64 ticks_until_completion;
};
void Init(); void Init();
void Shutdown(); void Shutdown();
void DoState(PointerWrap &p); void DoState(PointerWrap &p);
@ -105,7 +99,7 @@ void ChangeDisc(const std::string& fileName);
// DVD Access Functions // DVD Access Functions
bool DVDRead(u64 _iDVDOffset, u32 _iRamAddress, u32 _iLength, bool decrypt); bool DVDRead(u64 _iDVDOffset, u32 _iRamAddress, u32 _iLength, bool decrypt);
extern bool g_bStream; extern bool g_bStream;
DVDCommandResult ExecuteCommand(u32 command_0, u32 command_1, u32 command_2, void ExecuteCommand(u32 command_0, u32 command_1, u32 command_2, u32 output_address, u32 output_length,
u32 output_address, u32 output_length, bool write_to_DIIMMBUF); bool write_to_DIIMMBUF, int callback_event_type);
} // end of namespace DVDInterface } // end of namespace DVDInterface

View File

@ -85,7 +85,7 @@ static u64 last_reply_time;
static const u64 ENQUEUE_REQUEST_FLAG = 0x100000000ULL; static const u64 ENQUEUE_REQUEST_FLAG = 0x100000000ULL;
static const u64 ENQUEUE_ACKNOWLEDGEMENT_FLAG = 0x200000000ULL; 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) 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 CWII_IPC_HLE_Device_stub(i, "/dev/usb/oh1"); i++;
g_DeviceMap[i] = new IWII_IPC_HLE_Device(i, "_Unimplemented_Device_"); 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) 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); CoreTiming::ScheduleEvent_Threadsafe(cycles_in_future, event_enqueue, address);
} }
void EnqueueReply_Immediate(u32 address)
{
EnqueueEvent(address);
}
void EnqueueCommandAcknowledgement(u32 address, int cycles_in_future) void EnqueueCommandAcknowledgement(u32 address, int cycles_in_future)
{ {
CoreTiming::ScheduleEvent(cycles_in_future, event_enqueue, CoreTiming::ScheduleEvent(cycles_in_future, event_enqueue,

View File

@ -80,6 +80,7 @@ void ExecuteCommand(u32 _Address);
void EnqueueRequest(u32 address); void EnqueueRequest(u32 address);
void EnqueueReply(u32 address, int cycles_in_future = 0); void EnqueueReply(u32 address, int cycles_in_future = 0);
void EnqueueReply_Threadsafe(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); void EnqueueCommandAcknowledgement(u32 _Address, int cycles_in_future = 0);
} // end of namespace WII_IPC_HLE_Interface } // end of namespace WII_IPC_HLE_Interface

View File

@ -9,6 +9,7 @@
#include "Common/Logging/LogManager.h" #include "Common/Logging/LogManager.h"
#include "Core/ConfigManager.h" #include "Core/ConfigManager.h"
#include "Core/CoreTiming.h"
#include "Core/VolumeHandler.h" #include "Core/VolumeHandler.h"
#include "Core/HW/DVDInterface.h" #include "Core/HW/DVDInterface.h"
#include "Core/HW/Memmap.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.h"
#include "Core/IPC_HLE/WII_IPC_HLE_Device_DI.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) : 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() 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) 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) IPCCommandResult CWII_IPC_HLE_Device_di::IOCtl(u32 _CommandAddress)
{ {
u32 BufferIn = Memory::Read_U32(_CommandAddress + 0x10); // DI IOCtls are handled in a special way by Dolphin
u32 BufferInSize = Memory::Read_U32(_CommandAddress + 0x14); // compared to other WII_IPC_HLE functions.
u32 BufferOut = Memory::Read_U32(_CommandAddress + 0x18); // This is a wrapper around DVDInterface's ExecuteCommand,
u32 BufferOutSize = Memory::Read_U32(_CommandAddress + 0x1C); // 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_0 = Memory::Read_U32(BufferIn);
u32 command_1 = Memory::Read_U32(BufferIn + 4); u32 command_1 = Memory::Read_U32(BufferIn + 4);
@ -63,14 +107,38 @@ IPCCommandResult CWII_IPC_HLE_Device_di::IOCtl(u32 _CommandAddress)
Memory::Memset(BufferOut, 0, BufferOutSize); Memory::Memset(BufferOut, 0, BufferOutSize);
} }
DVDCommandResult result = ExecuteCommand(command_0, command_1, command_2, // DVDInterface's ExecuteCommand handles most of the work.
BufferOut, BufferOutSize, false); // The IOCtl callback is used to generate a reply afterwards.
Memory::Write_U32(result.interrupt_type, _CommandAddress + 0x4); DVDInterface::ExecuteCommand(command_0, command_1, command_2, BufferOut, BufferOutSize,
false, ioctl_callback);
}
if (SConfig::GetInstance().m_LocalCoreStartupParameter.bFastDiscSpeed) void CWII_IPC_HLE_Device_di::FinishIOCtl(DVDInterface::DIInterruptType interrupt_type)
result.ticks_until_completion = 0; // An optional hack to speed up loading times {
if (m_commands_to_execute.empty())
{
PanicAlertT("WII_IPC_HLE_Device_DI tried to reply to non-existing command");
return;
}
return { true, result.ticks_until_completion }; // 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) IPCCommandResult CWII_IPC_HLE_Device_di::IOCtlV(u32 _CommandAddress)
@ -88,7 +156,7 @@ IPCCommandResult CWII_IPC_HLE_Device_di::IOCtlV(u32 _CommandAddress)
u32 ReturnValue = 0; u32 ReturnValue = 0;
switch (CommandBuffer.Parameter) 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[1].m_Address == 0, "DVDLowOpenPartition with ticket");
_dbg_assert_msg_(WII_IPC_DVD, CommandBuffer.InBuffer[2].m_Address == 0, "DVDLowOpenPartition with cert chain"); _dbg_assert_msg_(WII_IPC_DVD, CommandBuffer.InBuffer[2].m_Address == 0, "DVDLowOpenPartition with cert chain");

View File

@ -4,14 +4,10 @@
#pragma once #pragma once
#include <deque>
#include "Core/HW/DVDInterface.h"
#include "Core/IPC_HLE/WII_IPC_HLE_Device.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 class CWII_IPC_HLE_Device_di : public IWII_IPC_HLE_Device
{ {
public: public:
@ -20,9 +16,18 @@ public:
virtual ~CWII_IPC_HLE_Device_di(); virtual ~CWII_IPC_HLE_Device_di();
void DoState(PointerWrap& p) override;
IPCCommandResult Open(u32 _CommandAddress, u32 _Mode) override; IPCCommandResult Open(u32 _CommandAddress, u32 _Mode) override;
IPCCommandResult Close(u32 _CommandAddress, bool _bForce) override; IPCCommandResult Close(u32 _CommandAddress, bool _bForce) override;
IPCCommandResult IOCtl(u32 _CommandAddress) override; IPCCommandResult IOCtl(u32 _CommandAddress) override;
IPCCommandResult IOCtlV(u32 _CommandAddress) override; IPCCommandResult IOCtlV(u32 _CommandAddress) override;
void FinishIOCtl(DVDInterface::DIInterruptType interrupt_type);
private:
void StartIOCtl(u32 command_address);
std::deque<u32> m_commands_to_execute;
}; };

View File

@ -64,7 +64,7 @@ static Common::Event g_compressAndDumpStateSyncEvent;
static std::thread g_save_thread; static std::thread g_save_thread;
// Don't forget to increase this after doing changes on the savestate system // Don't forget to increase this after doing changes on the savestate system
static const u32 STATE_VERSION = 39; static const u32 STATE_VERSION = 40;
enum enum
{ {