Make WII_IPC_HLE_Device_DI call DVDInterface

This commit is contained in:
JosJuice 2014-12-20 13:02:04 +01:00
parent 54f1e3a3c1
commit d1c8a8bd9f
4 changed files with 513 additions and 679 deletions

View File

@ -7,9 +7,7 @@
#include "AudioCommon/AudioCommon.h" #include "AudioCommon/AudioCommon.h"
#include "Common/ChunkFile.h"
#include "Common/CommonTypes.h" #include "Common/CommonTypes.h"
#include "Common/Thread.h"
#include "Core/ConfigManager.h" #include "Core/ConfigManager.h"
#include "Core/CoreTiming.h" #include "Core/CoreTiming.h"
@ -91,16 +89,6 @@ enum
DI_CONFIG_REGISTER = 0x24 DI_CONFIG_REGISTER = 0x24
}; };
// DVD IntteruptTypes
enum DI_InterruptType
{
INT_DEINT = 0,
INT_TCINT = 1,
INT_BRKINT = 2,
INT_CVRINT = 3,
};
// debug commands which may be ORd // debug commands which may be ORd
enum enum
{ {
@ -265,10 +253,13 @@ void EjectDiscCallback(u64 userdata, int cyclesLate);
void InsertDiscCallback(u64 userdata, int cyclesLate); void InsertDiscCallback(u64 userdata, int cyclesLate);
void UpdateInterrupts(); void UpdateInterrupts();
void GenerateDIInterrupt(DI_InterruptType _DVDInterrupt); void GenerateDIInterrupt(DIInterruptType _DVDInterrupt);
void ExecuteCommand();
void FinishExecuteRead(); void WriteImmediate(u32 value, u32 output_address, bool write_to_DIIMMBUF);
u64 SimulateDiscReadTime(); DVDCommandResult ExecuteReadCommand(u64 DVD_offset, u32 output_address,
u32 DVD_length, u32 output_length, bool raw = false);
u64 SimulateDiscReadTime(u64 offset, u32 length);
s64 CalculateRawDiscReadTime(u64 offset, s64 length); s64 CalculateRawDiscReadTime(u64 offset, s64 length);
void DoState(PointerWrap &p) void DoState(PointerWrap &p)
@ -305,8 +296,7 @@ static void TransferComplete(u64 userdata, int cyclesLate)
{ {
m_DICR.TSTART = 0; m_DICR.TSTART = 0;
m_DILENGTH.Length = 0; m_DILENGTH.Length = 0;
GenerateDIInterrupt(INT_TCINT); GenerateDIInterrupt((DIInterruptType)userdata);
g_ErrorCode = 0;
} }
} }
@ -318,8 +308,8 @@ static u32 ProcessDTKSamples(short *tempPCM, u32 num_samples)
if (AudioPos >= CurrentStart + CurrentLength) if (AudioPos >= CurrentStart + CurrentLength)
{ {
DEBUG_LOG(DVDINTERFACE, DEBUG_LOG(DVDINTERFACE,
"ProcessDTKSamples: NextStart=%08x,NextLength=%08x,CurrentStart=%08x,CurrentLength=%08x,AudioPos=%08x", "ProcessDTKSamples: NextStart=%08x,NextLength=%08x,CurrentStart=%08x,CurrentLength=%08x,AudioPos=%08x",
NextStart, NextLength, CurrentStart, CurrentLength, AudioPos); NextStart, NextLength, CurrentStart, CurrentLength, AudioPos);
AudioPos = NextStart; AudioPos = NextStart;
CurrentStart = NextStart; CurrentStart = NextStart;
@ -480,14 +470,12 @@ bool IsLidOpen()
return (m_DICVR.CVR == 1); return (m_DICVR.CVR == 1);
} }
void ClearCoverInterrupt() bool DVDRead(u64 _iDVDOffset, u32 _iRamAddress, u32 _iLength, bool raw)
{ {
m_DICVR.CVRINT = 0; if (raw)
} return VolumeHandler::RAWReadToPtr(Memory::GetPointer(_iRamAddress), _iDVDOffset, _iLength);
else
bool DVDRead(u32 _iDVDOffset, u32 _iRamAddress, u32 _iLength) return VolumeHandler::ReadToPtr(Memory::GetPointer(_iRamAddress), _iDVDOffset, _iLength);
{
return VolumeHandler::ReadToPtr(Memory::GetPointer(_iRamAddress), _iDVDOffset, _iLength);
} }
void RegisterMMIO(MMIO::Mapping* mmio, u32 base) void RegisterMMIO(MMIO::Mapping* mmio, u32 base)
@ -559,7 +547,21 @@ 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)
{ {
ExecuteCommand(); DVDCommandResult result = ExecuteCommand(
m_DICMDBUF[0].Hex, m_DICMDBUF[1].Hex, m_DICMDBUF[2].Hex,
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);
}
} }
}) })
); );
@ -594,7 +596,7 @@ void UpdateInterrupts()
CoreTiming::ForceExceptionCheck(50); CoreTiming::ForceExceptionCheck(50);
} }
void GenerateDIInterrupt(DI_InterruptType _DVDInterrupt) void GenerateDIInterrupt(DIInterruptType _DVDInterrupt)
{ {
switch (_DVDInterrupt) switch (_DVDInterrupt)
{ {
@ -607,115 +609,264 @@ void GenerateDIInterrupt(DI_InterruptType _DVDInterrupt)
UpdateInterrupts(); UpdateInterrupts();
} }
void ExecuteCommand() void WriteImmediate(u32 value, u32 output_address, bool write_to_DIIMMBUF)
{ {
// This variable is used to simulate the time is takes to execute a command. if (write_to_DIIMMBUF)
// 1 / 15000 seconds is just some arbitrary default value. m_DIIMMBUF.Hex = value;
// Commands that implement more precise timing are supposed to overwrite this. else
u64 ticks_until_TC = SystemTimers::GetTicksPerSecond() / 15000; Memory::Write_U32(value, output_address);
}
// _dbg_assert_(DVDINTERFACE, _DICR.RW == 0); // only DVD to Memory DVDCommandResult ExecuteReadCommand(u64 DVD_offset, u32 output_address,
int GCAM = ((SConfig::GetInstance().m_SIDevice[0] == SIDEVICE_AM_BASEBOARD) && u32 DVD_length, u32 output_length, bool raw)
(SConfig::GetInstance().m_EXIDevice[2] == EXIDEVICE_AM_BASEBOARD)) {
? 1 : 0; if (DVD_length > output_length)
{
WARN_LOG(DVDINTERFACE, "Detected attempt to read more data from the DVD than fit inside the out buffer. Clamp.");
DVD_length = output_length;
}
DVDCommandResult result;
result.ticks_until_completion = SimulateDiscReadTime(DVD_offset, DVD_length);
// Is this check needed?
if (output_address == 0)
{
PanicAlert("DVDLowRead : _BufferOut == 0");
result.interrupt_type = INT_DEINT;
return result;
}
if (raw)
{
// We must make sure it is in a valid area! (#001 check)
// * 0x00000000 - 0x00014000 (limit of older IOS versions)
// * 0x460a0000 - 0x460a0008
// * 0x7ed40000 - 0x7ed40008
u32 DVD_offset_32 = (u32)(DVD_offset >> 2);
// Are these checks correct? They seem to mix 32-bit offsets and 8-bit lengths
if (!((DVD_offset_32 > 0x00000000 && DVD_offset_32 < 0x00014000) ||
(((DVD_offset_32 + DVD_length) > 0x00000000) && (DVD_offset_32 + DVD_length) < 0x00014000) ||
(DVD_offset_32 > 0x460a0000 && DVD_offset_32 < 0x460a0008) ||
(((DVD_offset_32 + DVD_length) > 0x460a0000) && (DVD_offset_32 + DVD_length) < 0x460a0008) ||
(DVD_offset_32 > 0x7ed40000 && DVD_offset_32 < 0x7ed40008) ||
(((DVD_offset_32 + DVD_length) > 0x7ed40000) && (DVD_offset_32 + DVD_length) < 0x7ed40008)))
{
WARN_LOG(DVDINTERFACE, "DVDLowUnencryptedRead: trying to read out of bounds @ %09" PRIx64, DVD_offset);
g_ErrorCode = ERROR_READY | ERROR_BLOCK_OOB;
// Should cause software to call DVDLowRequestError
result.interrupt_type = INT_BRKINT;
return result;
}
}
if (!DVDRead(DVD_offset, output_address, DVD_length, raw))
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,
u32 output_address, u32 output_length, bool write_to_DIIMMBUF)
{
DVDCommandResult result;
result.interrupt_type = INT_TCINT;
result.ticks_until_completion = SystemTimers::GetTicksPerSecond() / 15000;
bool GCAM = (SConfig::GetInstance().m_SIDevice[0] == SIDEVICE_AM_BASEBOARD) &&
(SConfig::GetInstance().m_EXIDevice[2] == EXIDEVICE_AM_BASEBOARD);
// DVDLowRequestError needs access to the error code set by the previous command
if (command_0 >> 24 != DVDLowRequestError)
g_ErrorCode = 0;
if (GCAM) if (GCAM)
{ {
ERROR_LOG(DVDINTERFACE, ERROR_LOG(DVDINTERFACE, "DVD: %08x, %08x, %08x, DMA=addr:%08x,len:%08x,ctrl:%08x",
"DVD: %08x, %08x, %08x, DMA=addr:%08x,len:%08x,ctrl:%08x", command_0, command_1, command_2, output_address, output_length, m_DICR.Hex);
m_DICMDBUF[0].Hex, m_DICMDBUF[1].Hex, m_DICMDBUF[2].Hex,
m_DIMAR.Hex, m_DILENGTH.Hex, m_DICR.Hex);
// decrypt command. But we have a zero key, that simplifies things a lot. // decrypt command. But we have a zero key, that simplifies things a lot.
// If you get crazy dvd command errors, make sure 0x80000000 - 0x8000000c is zero'd // If you get crazy dvd command errors, make sure 0x80000000 - 0x8000000c is zero'd
m_DICMDBUF[0].Hex <<= 24; command_0 <<= 24;
} }
switch (command_0 >> 24)
switch (m_DICMDBUF[0].CMDBYTE0)
{ {
// Seems to be used by both GC and Wii
case DVDLowInquiry: case DVDLowInquiry:
if (GCAM) if (GCAM)
{ {
// 0x29484100... // 0x29484100...
// was 21 i'm not entirely sure about this, but it works well. // was 21 i'm not entirely sure about this, but it works well.
m_DIIMMBUF.Hex = 0x21000000; WriteImmediate(0x21000000, output_address, write_to_DIIMMBUF);
} }
else else
{ {
// small safety check, dunno if it's needed // (shuffle2) Taken from my Wii
if ((m_DICMDBUF[1].Hex == 0) && (m_DILENGTH.Length == 0x20)) Memory::Write_U32(0x00000002, output_address);
{ Memory::Write_U32(0x20060526, output_address + 4);
u8* driveInfo = Memory::GetPointer(m_DIMAR.Address); // This was in the oubuf even though this cmd is only supposed to reply with 64bits
// gives the correct output in GCOS - 06 2001/08 (61) // However, this and other tests strongly suggest that the buffer is static, and it's never - or rarely cleared.
// there may be other stuff missing ? Memory::Write_U32(0x41000000, output_address + 8);
driveInfo[4] = 0x20;
driveInfo[5] = 0x01;
driveInfo[6] = 0x06;
driveInfo[7] = 0x08;
driveInfo[8] = 0x61;
// Just for fun INFO_LOG(DVDINTERFACE, "DVDLowInquiry (Buffer 0x%08x, 0x%x)",
INFO_LOG(DVDINTERFACE, "Drive Info: %02x %02x%02x/%02x (%02x)", output_address, output_length);
driveInfo[6], driveInfo[4], driveInfo[5], driveInfo[7], driveInfo[8]);
}
} }
break; break;
// "Set Extension"...not sure what it does // Only seems to be used from WII_IPC, not through direct access
case DVDLowReadDiskID:
INFO_LOG(DVDINTERFACE, "DVDLowReadDiskID");
result = ExecuteReadCommand(0, output_address, command_1, output_length, true);
break;
// Only seems to be used from WII_IPC, not through direct access
case DVDLowRead:
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);
break;
// Probably only used by Wii
case DVDLowWaitForCoverClose:
INFO_LOG(DVDINTERFACE, "DVDLowWaitForCoverClose");
result.interrupt_type = (DIInterruptType)4; // ???
break;
// "Set Extension"...not sure what it does. GC only?
case 0x55: case 0x55:
INFO_LOG(DVDINTERFACE, "SetExtension"); INFO_LOG(DVDINTERFACE, "SetExtension");
break; break;
// DMA Read from Disc // Probably only used though WII_IPC
case DVDLowGetCoverReg:
WriteImmediate(m_DICVR.Hex, output_address, write_to_DIIMMBUF);
INFO_LOG(DVDINTERFACE, "DVDLowGetCoverReg 0x%08x", m_DICVR.Hex);
break;
// Probably only used by Wii
case DVDLowNotifyReset:
ERROR_LOG(DVDINTERFACE, "DVDLowNotifyReset");
PanicAlert("DVDLowNotifyReset");
break;
// Probably only used by Wii
case DVDLowReadDvdPhysical:
ERROR_LOG(DVDINTERFACE, "DVDLowReadDvdPhysical");
PanicAlert("DVDLowReadDvdPhysical");
break;
// Probably only used by Wii
case DVDLowReadDvdCopyright:
ERROR_LOG(DVDINTERFACE, "DVDLowReadDvdCopyright");
PanicAlert("DVDLowReadDvdCopyright");
break;
// Probably only used by Wii
case DVDLowReadDvdDiscKey:
ERROR_LOG(DVDINTERFACE, "DVDLowReadDvdDiscKey");
PanicAlert("DVDLowReadDvdDiscKey");
break;
// Probably only used by Wii
case DVDLowClearCoverInterrupt:
INFO_LOG(DVDINTERFACE, "DVDLowClearCoverInterrupt");
m_DICVR.CVRINT = 0;
// Less than ~1/155th of a second hangs Oregon Trail at "loading wheel".
// More than ~1/140th of a second hangs Resident Evil Archives: Resident Evil Zero.
result.ticks_until_completion = SystemTimers::GetTicksPerSecond() / 146;
break;
// Probably only used by Wii
case DVDLowGetCoverStatus:
WriteImmediate(g_bDiscInside ? 2 : 1, output_address, write_to_DIIMMBUF);
INFO_LOG(DVDINTERFACE, "DVDLowGetCoverStatus: Disc %sInserted", g_bDiscInside ? "" : "Not ");
break;
// Probably only used by Wii
case DVDLowReset:
INFO_LOG(DVDINTERFACE, "DVDLowReset");
break;
// Probably only used by Wii
case DVDLowClosePartition:
INFO_LOG(DVDINTERFACE, "DVDLowClosePartition");
break;
// Probably only used by Wii
case DVDLowUnencryptedRead:
INFO_LOG(DVDINTERFACE, "DVDLowUnencryptedRead: 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);
break;
// Probably only used by Wii
case DVDLowEnableDvdVideo:
ERROR_LOG(DVDINTERFACE, "DVDLowEnableDvdVideo");
break;
// New Super Mario Bros. Wii sends these commands,
// but it seems we don't need to implement anything.
// Probably only used by Wii
case 0x95:
case 0x96:
ERROR_LOG(DVDINTERFACE, "Unimplemented BCA command 0x%08x (Buffer 0x%08x, 0x%x)",
command_0, output_address, output_length);
break;
// Probably only used by Wii
case DVDLowReportKey:
INFO_LOG(DVDINTERFACE, "DVDLowReportKey");
// Does not work on retail discs/drives
// Retail games send this command to see if they are running on real retail hw
g_ErrorCode = ERROR_READY | ERROR_INV_CMD;
result.interrupt_type = INT_BRKINT;
break;
// DMA Read from Disc. Only seems to be used through direct access, not WII_IPC
case 0xA8: case 0xA8:
if (g_bDiscInside) if (g_bDiscInside)
{ {
switch (m_DICMDBUF[0].CMDBYTE3) switch (command_0 & 0xFF)
{ {
case 0x00: // Read Sector case 0x00: // Read Sector
{ {
u32 iDVDOffset = m_DICMDBUF[1].Hex << 2; u64 iDVDOffset = (u64)command_1 << 2;
DEBUG_LOG(DVDINTERFACE, "Read: DVDOffset=%08x, DMABuffer=%08x, SrcLength=%08x, DMALength=%08x", INFO_LOG(DVDINTERFACE, "Read: DVDOffset=%08" PRIx64 ", DMABuffer = %08x, SrcLength = %08x, DMALength = %08x",
iDVDOffset, m_DIMAR.Address, m_DICMDBUF[2].Hex, m_DILENGTH.Length); iDVDOffset, output_address, command_2, output_length);
_dbg_assert_(DVDINTERFACE, m_DICMDBUF[2].Hex == m_DILENGTH.Length);
if (GCAM) if (GCAM)
{ {
if (iDVDOffset & 0x80000000) // read request to hardware buffer if (iDVDOffset & 0x80000000) // read request to hardware buffer
{ {
u32 len = m_DILENGTH.Length / 4;
switch (iDVDOffset) switch (iDVDOffset)
{ {
case 0x80000000: case 0x80000000:
ERROR_LOG(DVDINTERFACE, "GC-AM: READ MEDIA BOARD STATUS (80000000)"); ERROR_LOG(DVDINTERFACE, "GC-AM: READ MEDIA BOARD STATUS (80000000)");
for (u32 i = 0; i < len; i++) for (u32 i = 0; i < output_length; i += 4)
Memory::Write_U32(0, m_DIMAR.Address + i * 4); Memory::Write_U32(0, output_address + i);
break; break;
case 0x80000040: case 0x80000040:
ERROR_LOG(DVDINTERFACE, "GC-AM: READ MEDIA BOARD STATUS (2) (80000040)"); ERROR_LOG(DVDINTERFACE, "GC-AM: READ MEDIA BOARD STATUS (2) (80000040)");
for (u32 i = 0; i < len; i++) for (u32 i = 0; i < output_length; i += 4)
Memory::Write_U32(~0, m_DIMAR.Address + i * 4); Memory::Write_U32(~0, output_address + i);
Memory::Write_U32(0x00000020, m_DIMAR.Address); // DIMM SIZE, LE Memory::Write_U32(0x00000020, output_address); // DIMM SIZE, LE
Memory::Write_U32(0x4743414D, m_DIMAR.Address + 4); // GCAM signature Memory::Write_U32(0x4743414D, output_address + 4); // GCAM signature
break; break;
case 0x80000120: case 0x80000120:
ERROR_LOG(DVDINTERFACE, "GC-AM: READ FIRMWARE STATUS (80000120)"); ERROR_LOG(DVDINTERFACE, "GC-AM: READ FIRMWARE STATUS (80000120)");
for (u32 i = 0; i < len; i++) for (u32 i = 0; i < output_length; i += 4)
Memory::Write_U32(0x01010101, m_DIMAR.Address + i * 4); Memory::Write_U32(0x01010101, output_address + i);
break; break;
case 0x80000140: case 0x80000140:
ERROR_LOG(DVDINTERFACE, "GC-AM: READ FIRMWARE STATUS (80000140)"); ERROR_LOG(DVDINTERFACE, "GC-AM: READ FIRMWARE STATUS (80000140)");
for (u32 i = 0; i < len; i++) for (u32 i = 0; i < output_length; i += 4)
Memory::Write_U32(0x01010101, m_DIMAR.Address + i * 4); Memory::Write_U32(0x01010101, output_address + i);
break; break;
case 0x84000020: case 0x84000020:
ERROR_LOG(DVDINTERFACE, "GC-AM: READ MEDIA BOARD STATUS (1) (84000020)"); ERROR_LOG(DVDINTERFACE, "GC-AM: READ MEDIA BOARD STATUS (1) (84000020)");
for (u32 i = 0; i < len; i++) for (u32 i = 0; i < output_length; i += 4)
Memory::Write_U32(0x00000000, m_DIMAR.Address + i * 4); Memory::Write_U32(0x00000000, output_address + i);
break; break;
default: default:
ERROR_LOG(DVDINTERFACE, "GC-AM: UNKNOWN MEDIA BOARD LOCATION %x", iDVDOffset); ERROR_LOG(DVDINTERFACE, "GC-AM: UNKNOWN MEDIA BOARD LOCATION %" PRIx64, iDVDOffset);
break; break;
} }
break; break;
@ -724,56 +875,43 @@ void ExecuteCommand()
{ {
ERROR_LOG(DVDINTERFACE, "GC-AM: READ MEDIA BOARD COMM AREA (1f900020)"); ERROR_LOG(DVDINTERFACE, "GC-AM: READ MEDIA BOARD COMM AREA (1f900020)");
u8* source = media_buffer + iDVDOffset - 0x1f900000; u8* source = media_buffer + iDVDOffset - 0x1f900000;
Memory::CopyToEmu(m_DIMAR.Address, source, m_DILENGTH.Length); Memory::CopyToEmu(output_address, source, output_length);
for (u32 i = 0; i < m_DILENGTH.Length; i += 4) for (u32 i = 0; i < output_length; i += 4)
ERROR_LOG(DVDINTERFACE, "GC-AM: %08x", Memory::Read_U32(m_DIMAR.Address + i)); ERROR_LOG(DVDINTERFACE, "GC-AM: %08x", Memory::Read_U32(output_address + i));
break; break;
} }
} }
ticks_until_TC = SimulateDiscReadTime(); result = ExecuteReadCommand(iDVDOffset, output_address, command_2, output_length);
// Here is the actual disc reading
if (!DVDRead(iDVDOffset, m_DIMAR.Address, m_DILENGTH.Length))
{
PanicAlertT("Can't read from DVD_Plugin - DVD-Interface: Fatal Error");
}
} }
break; break;
case 0x40: // Read DiscID case 0x40: // Read DiscID
_dbg_assert_(DVDINTERFACE, m_DICMDBUF[1].Hex == 0); INFO_LOG(DVDINTERFACE, "Read DiscID %08x", Memory::Read_U32(output_address));
_dbg_assert_(DVDINTERFACE, m_DICMDBUF[2].Hex == m_DILENGTH.Length); result = ExecuteReadCommand(0, output_address, command_2, output_length);
_dbg_assert_(DVDINTERFACE, m_DILENGTH.Length == 0x20);
if (!DVDRead(m_DICMDBUF[1].Hex, m_DIMAR.Address, m_DILENGTH.Length))
PanicAlertT("Can't read from DVD_Plugin - DVD-Interface: Fatal Error");
WARN_LOG(DVDINTERFACE, "Read DiscID %08x", Memory::Read_U32(m_DIMAR.Address));
break; break;
default: default:
_dbg_assert_msg_(DVDINTERFACE, 0, "Unknown Read Subcommand"); ERROR_LOG(DVDINTERFACE, "Unknown read subcommand: %08x", command_0);
break; break;
} }
} }
else else
{ {
// there is no disc to read // there is no disc to read
m_DICR.TSTART = 0;
m_DILENGTH.Length = 0;
g_ErrorCode = ERROR_NO_DISK | ERROR_COVER_H; g_ErrorCode = ERROR_NO_DISK | ERROR_COVER_H;
GenerateDIInterrupt(INT_DEINT); result.interrupt_type = INT_DEINT;
return;
} }
break; break;
// GC-AM // GC-AM only
case 0xAA: case 0xAA:
if (GCAM) if (GCAM)
{ {
ERROR_LOG(DVDINTERFACE, "GC-AM: 0xAA, DMABuffer=%08x, DMALength=%08x", m_DIMAR.Address, m_DILENGTH.Length); ERROR_LOG(DVDINTERFACE, "GC-AM: 0xAA, DMABuffer=%08x, DMALength=%08x", output_address, output_length);
u32 iDVDOffset = m_DICMDBUF[1].Hex << 2; u64 iDVDOffset = (u64)command_1 << 2;
unsigned int len = m_DILENGTH.Length; u32 len = output_length;
int offset = iDVDOffset - 0x1F900000; s64 offset = iDVDOffset - 0x1F900000;
/* /*
if (iDVDOffset == 0x84800000) if (iDVDOffset == 0x84800000)
{ {
@ -782,7 +920,7 @@ void ExecuteCommand()
else*/ else*/
if ((offset < 0) || ((offset + len) > 0x40) || len > 0x40) if ((offset < 0) || ((offset + len) > 0x40) || len > 0x40)
{ {
u32 addr = m_DIMAR.Address; u32 addr = output_address;
if (iDVDOffset == 0x84800000) if (iDVDOffset == 0x84800000)
{ {
ERROR_LOG(DVDINTERFACE, "FIRMWARE UPLOAD"); ERROR_LOG(DVDINTERFACE, "FIRMWARE UPLOAD");
@ -794,7 +932,7 @@ void ExecuteCommand()
while (len >= 4) while (len >= 4)
{ {
ERROR_LOG(DVDINTERFACE, "GC-AM Media Board WRITE (0xAA): %08x: %08x", iDVDOffset, Memory::Read_U32(addr)); ERROR_LOG(DVDINTERFACE, "GC-AM Media Board WRITE (0xAA): %08" PRIx64 ": %08x", iDVDOffset, Memory::Read_U32(addr));
addr += 4; addr += 4;
len -= 4; len -= 4;
iDVDOffset += 4; iDVDOffset += 4;
@ -806,7 +944,7 @@ void ExecuteCommand()
Memory::CopyFromEmu(media_buffer + offset, addr, len); Memory::CopyFromEmu(media_buffer + offset, addr, len);
while (len >= 4) while (len >= 4)
{ {
ERROR_LOG(DVDINTERFACE, "GC-AM Media Board WRITE (0xAA): %08x: %08x", iDVDOffset, Memory::Read_U32(addr)); ERROR_LOG(DVDINTERFACE, "GC-AM Media Board WRITE (0xAA): %08" PRIx64 ": %08x", iDVDOffset, Memory::Read_U32(addr));
addr += 4; addr += 4;
len -= 4; len -= 4;
iDVDOffset += 4; iDVDOffset += 4;
@ -815,12 +953,12 @@ void ExecuteCommand()
} }
break; break;
// Seek (immediate) // Seems to be used by both GC and Wii
case DVDLowSeek: case DVDLowSeek:
if (!GCAM) if (!GCAM)
{ {
// We don't care :) // Currently unimplemented
DEBUG_LOG(DVDINTERFACE, "Seek: offset=%08x (ignoring)", m_DICMDBUF[1].Hex << 2); INFO_LOG(DVDINTERFACE, "Seek: offset=%09" PRIx64 " (ignoring)", (u64)command_1 << 2);
} }
else else
{ {
@ -893,27 +1031,62 @@ void ExecuteCommand()
break; break;
} }
memset(media_buffer + 0x20, 0, 0x20); memset(media_buffer + 0x20, 0, 0x20);
m_DIIMMBUF.Hex = 0x66556677; // just a random value that works. WriteImmediate(0x66556677, output_address, write_to_DIIMMBUF); // just a random value that works.
} }
break; break;
// Probably only used by Wii
case DVDLowReadDvd:
ERROR_LOG(DVDINTERFACE, "DVDLowReadDvd");
break;
// Probably only used by Wii
case DVDLowReadDvdConfig:
ERROR_LOG(DVDINTERFACE, "DVDLowReadDvdConfig");
break;
// Probably only used by Wii
case DVDLowStopLaser:
ERROR_LOG(DVDINTERFACE, "DVDLowStopLaser");
break;
// Probably only used by Wii
case DVDLowOffset: case DVDLowOffset:
DEBUG_LOG(DVDINTERFACE, "DVDLowOffset: ignoring..."); ERROR_LOG(DVDINTERFACE, "DVDLowOffset");
break;
// Probably only used by Wii
case DVDLowReadDiskBca:
WARN_LOG(DVDINTERFACE, "DVDLowReadDiskBca");
Memory::Write_U32(1, output_address + 0x30);
break;
// Probably only used by Wii
case DVDLowRequestDiscStatus:
ERROR_LOG(DVDINTERFACE, "DVDLowRequestDiscStatus");
break;
// Probably only used by Wii
case DVDLowRequestRetryNumber:
ERROR_LOG(DVDINTERFACE, "DVDLowRequestRetryNumber");
break;
// Probably only used by Wii
case DVDLowSetMaximumRotation:
ERROR_LOG(DVDINTERFACE, "DVDLowSetMaximumRotation");
break;
// Probably only used by Wii
case DVDLowSerMeasControl:
ERROR_LOG(DVDINTERFACE, "DVDLowSerMeasControl");
break; break;
// Request Error Code // Used by both GC and Wii
case DVDLowRequestError: case DVDLowRequestError:
ERROR_LOG(DVDINTERFACE, "Requesting error... (0x%08x)", g_ErrorCode); INFO_LOG(DVDINTERFACE, "Requesting error... (0x%08x)", g_ErrorCode);
m_DIIMMBUF.Hex = g_ErrorCode; WriteImmediate(g_ErrorCode, output_address, write_to_DIIMMBUF);
g_ErrorCode = 0;
break; break;
// Audio Stream (Immediate) // Audio Stream (Immediate). Only seems to be used by some GC games
// m_DICMDBUF[0].CMDBYTE1 = Subcommand // (command_0 >> 16) & 0xFF = Subcommand
// m_DICMDBUF[1].Hex << 2 = Offset on disc // command_1 << 2 = Offset on disc
// m_DICMDBUF[2].Hex = Length of the stream // command_2 = Length of the stream
case 0xE1: case 0xE1:
{ {
u8 cancel_stream = m_DICMDBUF[0].CMDBYTE1; u8 cancel_stream = (command_0 >> 16) & 0xFF;
if (cancel_stream) if (cancel_stream)
{ {
g_bStopAtTrackEnd = false; g_bStopAtTrackEnd = false;
@ -926,17 +1099,16 @@ void ExecuteCommand()
} }
else else
{ {
u32 pos = m_DICMDBUF[1].Hex << 2; if ((command_1 == 0) && (command_2 == 0))
u32 length = m_DICMDBUF[2].Hex;
if ((pos == 0) && (length == 0))
{ {
g_bStopAtTrackEnd = true; g_bStopAtTrackEnd = true;
} }
else if (!g_bStopAtTrackEnd) else if (!g_bStopAtTrackEnd)
{ {
NextStart = pos; // Setting NextStart (a u32) like this discards two bits,
NextLength = length; // but GC games can't be 4 GiB big, so it shouldn't matter
NextStart = command_1 << 2;
NextLength = command_2;
if (!g_bStream) if (!g_bStream)
{ {
CurrentStart = NextStart; CurrentStart = NextStart;
@ -948,50 +1120,59 @@ void ExecuteCommand()
} }
} }
INFO_LOG(DVDINTERFACE, "(Audio) Stream cmd: %08x offset: %08" PRIx64 " length: %08x",
WARN_LOG(DVDINTERFACE, "(Audio) Stream subcmd = %08x offset = %08x length=%08x", command_0, (u64)command_1 << 2, command_2);
m_DICMDBUF[0].Hex, m_DICMDBUF[1].Hex << 2, m_DICMDBUF[2].Hex);
} }
break; break;
// Request Audio Status (Immediate) // Request Audio Status (Immediate). Only seems to be used by some GC games
case 0xE2: case 0xE2:
{ {
switch (m_DICMDBUF[0].CMDBYTE1) switch (command_0 >> 16 & 0xFF)
{ {
case 0x00: // Returns streaming status case 0x00: // Returns streaming status
DEBUG_LOG(DVDINTERFACE, "(Audio): Stream Status: Request Audio status AudioPos:%08x/%08x CurrentStart:%08x CurrentLength:%08x", AudioPos, CurrentStart + CurrentLength, CurrentStart, CurrentLength); INFO_LOG(DVDINTERFACE, "(Audio): Stream Status: Request Audio status AudioPos:%08x/%08x CurrentStart:%08x CurrentLength:%08x", AudioPos, CurrentStart + CurrentLength, CurrentStart, CurrentLength);
m_DIIMMBUF.REGVAL0 = 0; WriteImmediate((g_bStream) ? 1 : 0, output_address, write_to_DIIMMBUF);
m_DIIMMBUF.REGVAL1 = 0;
m_DIIMMBUF.REGVAL2 = 0;
m_DIIMMBUF.REGVAL3 = (g_bStream) ? 1 : 0;
break; break;
case 0x01: // Returns the current offset case 0x01: // Returns the current offset
DEBUG_LOG(DVDINTERFACE, "(Audio): Stream Status: Request Audio status AudioPos:%08x", AudioPos); INFO_LOG(DVDINTERFACE, "(Audio): Stream Status: Request Audio status AudioPos:%08x", AudioPos);
m_DIIMMBUF.Hex = AudioPos >> 2; WriteImmediate(AudioPos >> 2, output_address, write_to_DIIMMBUF);
break; break;
case 0x02: // Returns the start offset case 0x02: // Returns the start offset
DEBUG_LOG(DVDINTERFACE, "(Audio): Stream Status: Request Audio status CurrentStart:%08x", CurrentStart); INFO_LOG(DVDINTERFACE, "(Audio): Stream Status: Request Audio status CurrentStart:%08x", CurrentStart);
m_DIIMMBUF.Hex = CurrentStart >> 2; WriteImmediate(CurrentStart >> 2, output_address, write_to_DIIMMBUF);
break; break;
case 0x03: // Returns the total length case 0x03: // Returns the total length
DEBUG_LOG(DVDINTERFACE, "(Audio): Stream Status: Request Audio status CurrentLength:%08x", CurrentLength); INFO_LOG(DVDINTERFACE, "(Audio): Stream Status: Request Audio status CurrentLength:%08x", CurrentLength);
m_DIIMMBUF.Hex = CurrentLength; WriteImmediate(CurrentLength >> 2, output_address, write_to_DIIMMBUF);
break; break;
default: default:
WARN_LOG(DVDINTERFACE, "(Audio): Subcommand: %02x Request Audio status %s", m_DICMDBUF[0].CMDBYTE1, g_bStream? "on":"off"); WARN_LOG(DVDINTERFACE, "(Audio): Subcommand: %02x Request Audio status %s", command_0 >> 16 & 0xFF, g_bStream ? "on" : "off");
break; break;
} }
} }
break; break;
case DVDLowStopMotor: case DVDLowStopMotor:
DEBUG_LOG(DVDINTERFACE, "Stop motor"); INFO_LOG(DVDINTERFACE, "DVDLowStopMotor %s %s",
command_1 ? "eject" : "", command_2 ? "kill!" : "");
if (command_1)
EjectDiscCallback(0, 0);
break; break;
// DVD Audio Enable/Disable (Immediate) // DVD Audio Enable/Disable (Immediate). GC uses this, and apparently Wii also does...?
case DVDLowAudioBufferConfig: case DVDLowAudioBufferConfig:
if (m_DICMDBUF[0].CMDBYTE1 == 1) // For more information: http://www.crazynation.org/GC/GC_DD_TECH/GCTech.htm (dead link?)
//
// Upon Power up or reset , 2 commands must be issued for proper use of audio streaming:
// DVDReadDiskID A8000040,00000000,00000020
// DVDLowAudioBufferConfig E4xx00yy,00000000,00000020
//
// xx=byte 8 [0 or 1] from the disk header retrieved from DVDReadDiskID
// yy=0 (if xx=0) or 0xA (if xx=1)
if ((command_0 >> 16) & 0xFF)
{ {
// TODO: What is this actually supposed to do? // TODO: What is this actually supposed to do?
g_bStream = true; g_bStream = true;
@ -1005,31 +1186,33 @@ void ExecuteCommand()
} }
break; break;
// yet another command we prolly don't care about // yet another (GC?) command we prolly don't care about
case 0xEE: case 0xEE:
DEBUG_LOG(DVDINTERFACE, "SetStatus - Unimplemented"); INFO_LOG(DVDINTERFACE, "SetStatus");
break; break;
// Debug commands; see yagcd. We don't really care // Debug commands; see yagcd. We don't really care
// NOTE: commands to stream data will send...a raw data stream // NOTE: commands to stream data will send...a raw data stream
// This will appear as unknown commands, unless the check is re-instated to catch such data. // This will appear as unknown commands, unless the check is re-instated to catch such data.
// Can probably only be used through direct access
case 0xFE: case 0xFE:
INFO_LOG(DVDINTERFACE, "Unsupported DVD Drive debug command 0x%08x", m_DICMDBUF[0].Hex); ERROR_LOG(DVDINTERFACE, "Unsupported DVD Drive debug command 0x%08x", command_0);
break; break;
// Unlock Commands. 1: "MATSHITA" 2: "DVD-GAME" // Unlock Commands. 1: "MATSHITA" 2: "DVD-GAME"
// Just for fun // Just for fun
// Can probably only be used through direct access
case 0xFF: case 0xFF:
{ {
if (m_DICMDBUF[0].Hex == 0xFF014D41 && if (command_0 == 0xFF014D41 &&
m_DICMDBUF[1].Hex == 0x54534849 && command_1 == 0x54534849 &&
m_DICMDBUF[2].Hex == 0x54410200) command_2 == 0x54410200)
{ {
INFO_LOG(DVDINTERFACE, "Unlock test 1 passed"); INFO_LOG(DVDINTERFACE, "Unlock test 1 passed");
} }
else if (m_DICMDBUF[0].Hex == 0xFF004456 && else if (command_0 == 0xFF004456 &&
m_DICMDBUF[1].Hex == 0x442D4741 && command_1 == 0x442D4741 &&
m_DICMDBUF[2].Hex == 0x4D450300) command_2 == 0x4D450300)
{ {
INFO_LOG(DVDINTERFACE, "Unlock test 2 passed"); INFO_LOG(DVDINTERFACE, "Unlock test 2 passed");
} }
@ -1041,120 +1224,97 @@ void ExecuteCommand()
break; break;
default: default:
PanicAlertT("Unknown DVD command %08x - fatal error", m_DICMDBUF[0].Hex); ERROR_LOG(DVDINTERFACE, "Unknown command 0x%08x (Buffer 0x%08x, 0x%x)",
_dbg_assert_(DVDINTERFACE, 0); command_0, output_address, output_length);
PanicAlertT("Unknown DVD command %08x - fatal error", command_0);
break; break;
} }
if (ticks_until_TC) return result;
{
// The transfer is finished after a delay
CoreTiming::ScheduleEvent((int)ticks_until_TC, tc);
}
else
{
// transfer is done
m_DICR.TSTART = 0;
m_DILENGTH.Length = 0;
GenerateDIInterrupt(INT_TCINT);
g_ErrorCode = 0;
}
} }
// Simulates the timing aspects of reading data from a disc. // Simulates the timing aspects of reading data from a disc.
// Sets g_last_read_offset and g_last_read_time, and returns ticks_until_TC. // Returns the amount of ticks needed to finish executing the command,
u64 SimulateDiscReadTime() // and sets some state that is used the next time this function runs.
u64 SimulateDiscReadTime(u64 offset, u32 length)
{ {
u64 DVD_offset = (u64)m_DICMDBUF[1].Hex << 2; // The drive buffers 1 MiB (?) of data after every read request;
u64 current_time = CoreTiming::GetTicks(); // if a read request is covered by this buffer (or if it's
u64 ticks_until_TC; // faster to wait for the data to be buffered), the drive
// doesn't seek; it returns buffered data. Data can be
// transferred from the buffer at up to 16 MiB/s.
//
// If the drive has to seek, the time this takes varies a lot.
// A short seek is around 50 ms; a long seek is around 150 ms.
// However, the time isn't purely dependent on the distance; the
// pattern of previous seeks seems to matter in a way I'm
// not sure how to explain.
//
// Metroid Prime is a good example of a game that's sensitive to
// all of these details; if there isn't enough latency in the
// right places, doors open too quickly, and if there's too
// much latency in the wrong places, the video before the
// save-file select screen lags.
//
// For now, just use a very rough approximation: 50 ms seek
// for reads outside 1 MiB, accelerated reads within 1 MiB.
// We can refine this if someone comes up with a more complete
// model for seek times.
if (SConfig::GetInstance().m_LocalCoreStartupParameter.bFastDiscSpeed) u64 current_time = CoreTiming::GetTicks();
u64 ticks_until_completion;
// Number of ticks it takes to seek and read directly from the disk.
u64 disk_read_duration = CalculateRawDiscReadTime(offset, length) +
SystemTimers::GetTicksPerSecond() / 1000 * DISC_ACCESS_TIME_MS;
if (offset + length - g_last_read_offset > 1024 * 1024)
{ {
// Make sure fast disc speed performs "instant" reads; in addition // No buffer; just use the simple seek time + read time.
// to being used to speed up games, fast disc speed is used as a DEBUG_LOG(DVDINTERFACE, "Seeking %" PRId64 " bytes",
// workaround for crashes in certain games, including Star Wars s64(g_last_read_offset) - s64(offset));
// Rogue Leader. ticks_until_completion = disk_read_duration;
ticks_until_TC = 0; g_last_read_time = current_time + ticks_until_completion;
g_last_read_time = current_time;
} }
else else
{ {
// The drive buffers 1 MiB (?) of data after every read request; // Possibly buffered; use the buffer if it saves time.
// if a read request is covered by this buffer (or if it's // It's not proven that the buffer actually behaves like this, but
// faster to wait for the data to be buffered), the drive // it appears to be a decent approximation.
// doesn't seek; it returns buffered data. Data can be
// transferred from the buffer at up to 16 MiB/s.
//
// If the drive has to seek, the time this takes varies a lot.
// A short seek is around 50 ms; a long seek is around 150 ms.
// However, the time isn't purely dependent on the distance; the
// pattern of previous seeks seems to matter in a way I'm
// not sure how to explain.
//
// Metroid Prime is a good example of a game that's sensitive to
// all of these details; if there isn't enough latency in the
// right places, doors open too quickly, and if there's too
// much latency in the wrong places, the video before the
// save-file select screen lags.
//
// For now, just use a very rough approximation: 50 ms seek
// for reads outside 1 MiB, accelerated reads within 1 MiB.
// We can refine this if someone comes up with a more complete
// model for seek times.
// Number of ticks it takes to seek and read directly from the disk. // Time at which the buffer will contain the data we need.
u64 disk_read_duration = CalculateRawDiscReadTime(DVD_offset, m_DILENGTH.Length) + u64 buffer_fill_time = g_last_read_time +
SystemTimers::GetTicksPerSecond() / 1000 * DISC_ACCESS_TIME_MS; CalculateRawDiscReadTime(g_last_read_offset,
offset + length - g_last_read_offset);
// Number of ticks it takes to transfer the data from the buffer to memory.
u64 buffer_read_duration = length *
(SystemTimers::GetTicksPerSecond() / BUFFER_TRANSFER_RATE);
if (DVD_offset + m_DILENGTH.Length - g_last_read_offset > 1024 * 1024) if (current_time > buffer_fill_time)
{ {
// No buffer; just use the simple seek time + read time. DEBUG_LOG(DVDINTERFACE, "Fast buffer read at %" PRIx64, offset);
DEBUG_LOG(DVDINTERFACE, "Seeking %" PRId64 " bytes", ticks_until_completion = buffer_read_duration;
s64(g_last_read_offset) - s64(DVD_offset)); g_last_read_time = buffer_fill_time;
ticks_until_TC = disk_read_duration; }
g_last_read_time = current_time + ticks_until_TC; else if (current_time + disk_read_duration > buffer_fill_time)
{
DEBUG_LOG(DVDINTERFACE, "Slow buffer read at %" PRIx64, offset);
ticks_until_completion = std::max(buffer_fill_time - current_time,
buffer_read_duration);
g_last_read_time = buffer_fill_time;
} }
else else
{ {
// Possibly buffered; use the buffer if it saves time. DEBUG_LOG(DVDINTERFACE, "Short seek %" PRId64 " bytes",
// It's not proven that the buffer actually behaves like this, but s64(g_last_read_offset) - s64(offset));
// it appears to be a decent approximation. ticks_until_completion = disk_read_duration;
g_last_read_time = current_time + ticks_until_completion;
// Time at which the buffer will contain the data we need.
u64 buffer_fill_time = g_last_read_time +
CalculateRawDiscReadTime(g_last_read_offset,
DVD_offset + m_DILENGTH.Length - g_last_read_offset);
// Number of ticks it takes to transfer the data from the buffer to memory.
u64 buffer_read_duration = m_DILENGTH.Length *
(SystemTimers::GetTicksPerSecond() / BUFFER_TRANSFER_RATE);
if (current_time > buffer_fill_time)
{
DEBUG_LOG(DVDINTERFACE, "Fast buffer read at %" PRId64, s64(DVD_offset));
ticks_until_TC = buffer_read_duration;
g_last_read_time = buffer_fill_time;
}
else if (current_time + disk_read_duration > buffer_fill_time)
{
DEBUG_LOG(DVDINTERFACE, "Slow buffer read at %" PRId64, s64(DVD_offset));
ticks_until_TC = std::max(buffer_fill_time - current_time,
buffer_read_duration);
g_last_read_time = buffer_fill_time;
}
else
{
DEBUG_LOG(DVDINTERFACE, "Short seek %" PRId64 " bytes",
s64(g_last_read_offset) - s64(DVD_offset));
ticks_until_TC = disk_read_duration;
g_last_read_time = current_time + ticks_until_TC;
}
} }
} }
g_last_read_offset = (DVD_offset + m_DILENGTH.Length - 2048) & ~2047; g_last_read_offset = (offset + length - 2048) & ~2047;
return ticks_until_TC; return ticks_until_completion;
} }
// Returns the number of ticks it takes to read an amount of // Returns the number of ticks it takes to read an amount of
@ -1199,6 +1359,7 @@ s64 CalculateRawDiscReadTime(u64 offset, s64 length)
speed = std::sqrt(((average_offset - GC_DISC_LOCATION_1_OFFSET) / speed = std::sqrt(((average_offset - GC_DISC_LOCATION_1_OFFSET) /
GC_BYTES_PER_AREA_UNIT + GC_DISC_AREA_UP_TO_LOCATION_1) / PI); GC_BYTES_PER_AREA_UNIT + GC_DISC_AREA_UP_TO_LOCATION_1) / PI);
} }
DEBUG_LOG(DVDINTERFACE, "Disc speed: %f MiB/s", speed / 1024 / 1024);
return (s64)(SystemTimers::GetTicksPerSecond() / speed * length); return (s64)(SystemTimers::GetTicksPerSecond() / speed * length);
} }

View File

@ -13,6 +13,84 @@ namespace MMIO { class Mapping; }
namespace DVDInterface namespace DVDInterface
{ {
// Not sure about endianness here. I'll just name them like this...
enum DIErrorLow
{
ERROR_READY = 0x00000000, // Ready.
ERROR_COVER_L = 0x01000000, // Cover is opened.
ERROR_CHANGE_DISK = 0x02000000, // Disk change.
ERROR_NO_DISK = 0x03000000, // No disk.
ERROR_MOTOR_STOP_L = 0x04000000, // Motor stop.
ERROR_NO_DISKID_L = 0x05000000 // Disk ID not read.
};
enum DIErrorHigh
{
ERROR_NONE = 0x000000, // No error.
ERROR_MOTOR_STOP_H = 0x020400, // Motor stopped.
ERROR_NO_DISKID_H = 0x020401, // Disk ID not read.
ERROR_COVER_H = 0x023a00, // Medium not present / Cover opened.
ERROR_SEEK_NDONE = 0x030200, // No seek complete.
ERROR_READ = 0x031100, // Unrecovered read error.
ERROR_PROTOCOL = 0x040800, // Transfer protocol error.
ERROR_INV_CMD = 0x052000, // Invalid command operation code.
ERROR_AUDIO_BUF = 0x052001, // Audio Buffer not set.
ERROR_BLOCK_OOB = 0x052100, // Logical block address out of bounds.
ERROR_INV_FIELD = 0x052400, // Invalid field in command packet.
ERROR_INV_AUDIO = 0x052401, // Invalid audio command.
ERROR_INV_PERIOD = 0x052402, // Configuration out of permitted period.
ERROR_END_USR_AREA = 0x056300, // End of user area encountered on this track.
ERROR_MEDIUM = 0x062800, // Medium may have changed.
ERROR_MEDIUM_REQ = 0x0b5a01 // Operator medium removal request.
};
enum DICommand
{
DVDLowInquiry = 0x12,
DVDLowReadDiskID = 0x70,
DVDLowRead = 0x71,
DVDLowWaitForCoverClose = 0x79,
DVDLowGetCoverReg = 0x7a, // DVDLowPrepareCoverRegister?
DVDLowNotifyReset = 0x7e,
DVDLowReadDvdPhysical = 0x80,
DVDLowReadDvdCopyright = 0x81,
DVDLowReadDvdDiscKey = 0x82,
DVDLowClearCoverInterrupt = 0x86,
DVDLowGetCoverStatus = 0x88,
DVDLowReset = 0x8a,
DVDLowOpenPartition = 0x8b,
DVDLowClosePartition = 0x8c,
DVDLowUnencryptedRead = 0x8d,
DVDLowEnableDvdVideo = 0x8e,
DVDLowReportKey = 0xa4,
DVDLowSeek = 0xab,
DVDLowReadDvd = 0xd0,
DVDLowReadDvdConfig = 0xd1,
DVDLowStopLaser = 0xd2,
DVDLowOffset = 0xd9,
DVDLowReadDiskBca = 0xda,
DVDLowRequestDiscStatus = 0xdb,
DVDLowRequestRetryNumber = 0xdc,
DVDLowSetMaximumRotation = 0xdd,
DVDLowSerMeasControl = 0xdf,
DVDLowRequestError = 0xe0,
DVDLowStopMotor = 0xe3,
DVDLowAudioBufferConfig = 0xe4
};
enum DIInterruptType
{
INT_DEINT = 0,
INT_TCINT = 1,
INT_BRKINT = 2,
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);
@ -28,75 +106,10 @@ void ChangeDisc(const std::string& fileName);
void SetLidOpen(bool _bOpen = true); void SetLidOpen(bool _bOpen = true);
bool IsLidOpen(); bool IsLidOpen();
// Used as low level control by WII_IPC_HLE_Device_DI
void ClearCoverInterrupt();
// DVD Access Functions // DVD Access Functions
bool DVDRead(u32 _iDVDOffset, u32 _iRamAddress, u32 _iLength); bool DVDRead(u64 _iDVDOffset, u32 _iRamAddress, u32 _iLength, bool raw = false);
extern bool g_bStream; extern bool g_bStream;
DVDCommandResult ExecuteCommand(u32 command_0, u32 command_1, u32 command_2,
// Not sure about endianness here. I'll just name them like this... u32 output_address, u32 output_length, bool write_to_DIIMMBUF);
enum DIErrorLow
{
ERROR_READY = 0x00000000, // Ready.
ERROR_COVER_L = 0x01000000, // Cover is opened.
ERROR_CHANGE_DISK = 0x02000000, // Disk change.
ERROR_NO_DISK = 0x03000000, // No Disk.
ERROR_MOTOR_STOP_L = 0x04000000, // Motor stop.
ERROR_NO_DISKID_L = 0x05000000 // Disk ID not read.
};
enum DIErrorHigh
{
ERROR_NONE = 0x000000, // No error.
ERROR_MOTOR_STOP_H = 0x020400, // Motor stopped.
ERROR_NO_DISKID_H = 0x020401, // Disk ID not read.
ERROR_COVER_H = 0x023a00, // Medium not present / Cover opened.
ERROR_SEEK_NDONE = 0x030200, // No Seek complete.
ERROR_READ = 0x031100, // UnRecoverd read error.
ERROR_PROTOCOL = 0x040800, // Transfer protocol error.
ERROR_INV_CMD = 0x052000, // Invalid command operation code.
ERROR_AUDIO_BUF = 0x052001, // Audio Buffer not set.
ERROR_BLOCK_OOB = 0x052100, // Logical block address out of bounds.
ERROR_INV_FIELD = 0x052400, // Invalid Field in command packet.
ERROR_INV_AUDIO = 0x052401, // Invalid audio command.
ERROR_INV_PERIOD = 0x052402, // Configuration out of permitted period.
ERROR_END_USR_AREA = 0x056300, // End of user area encountered on this track.
ERROR_MEDIUM = 0x062800, // Medium may have changed.
ERROR_MEDIUM_REQ = 0x0b5a01 // Operator medium removal request.
};
enum DICommand
{
DVDLowInquiry = 0x12,
DVDLowReadDiskID = 0x70,
DVDLowRead = 0x71,
DVDLowWaitForCoverClose = 0x79,
DVDLowGetCoverReg = 0x7a, // DVDLowPrepareCoverRegister?
DVDLowNotifyReset = 0x7e,
DVDLowReadDvdPhysical = 0x80,
DVDLowReadDvdCopyright = 0x81,
DVDLowReadDvdDiscKey = 0x82,
DVDLowClearCoverInterrupt = 0x86,
DVDLowGetCoverStatus = 0x88,
DVDLowReset = 0x8a,
DVDLowOpenPartition = 0x8b,
DVDLowClosePartition = 0x8c,
DVDLowUnencryptedRead = 0x8d,
DVDLowEnableDvdVideo = 0x8e,
DVDLowReportKey = 0xa4,
DVDLowSeek = 0xab,
DVDLowReadDvd = 0xd0,
DVDLowReadDvdConfig = 0xd1,
DVDLowStopLaser = 0xd2,
DVDLowOffset = 0xd9,
DVDLowReadDiskBca = 0xda,
DVDLowRequestDiscStatus = 0xdb,
DVDLowRequestRetryNumber = 0xdc,
DVDLowSetMaximumRotation = 0xdd,
DVDLowSerMeasControl = 0xdf,
DVDLowRequestError = 0xe0,
DVDLowStopMotor = 0xe3,
DVDLowAudioBufferConfig = 0xe4
};
} // end of namespace DVDInterface } // end of namespace DVDInterface

View File

@ -7,9 +7,7 @@
#include "Common/CommonTypes.h" #include "Common/CommonTypes.h"
#include "Common/Logging/LogManager.h" #include "Common/Logging/LogManager.h"
#include "Core/Core.h"
#include "Core/VolumeHandler.h" #include "Core/VolumeHandler.h"
#include "Core/HW/CPU.h"
#include "Core/HW/DVDInterface.h" #include "Core/HW/DVDInterface.h"
#include "Core/HW/Memmap.h" #include "Core/HW/Memmap.h"
#include "Core/HW/SystemTimers.h" #include "Core/HW/SystemTimers.h"
@ -17,40 +15,17 @@
#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"
#include "DiscIO/FileMonitor.h"
#include "DiscIO/Filesystem.h"
#include "DiscIO/VolumeCreator.h"
using namespace DVDInterface; using namespace DVDInterface;
#define DI_COVER_REG_INITIALIZED 0 // Should be 4, but doesn't work correctly...
#define DI_COVER_REG_NO_DISC 1
CWII_IPC_HLE_Device_di::CWII_IPC_HLE_Device_di(u32 _DeviceID, const std::string& _rDeviceName ) 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)
, m_pFileSystem(nullptr)
, m_ErrorStatus(0)
, m_CoverStatus(DI_COVER_REG_NO_DISC)
{} {}
CWII_IPC_HLE_Device_di::~CWII_IPC_HLE_Device_di() CWII_IPC_HLE_Device_di::~CWII_IPC_HLE_Device_di()
{ {}
if (m_pFileSystem)
{
delete m_pFileSystem;
m_pFileSystem = nullptr;
}
}
bool CWII_IPC_HLE_Device_di::Open(u32 _CommandAddress, u32 _Mode) bool CWII_IPC_HLE_Device_di::Open(u32 _CommandAddress, u32 _Mode)
{ {
if (VolumeHandler::IsValid())
{
m_pFileSystem = DiscIO::CreateFileSystem(VolumeHandler::GetVolume());
m_CoverStatus |= DI_COVER_REG_INITIALIZED;
m_CoverStatus &= ~DI_COVER_REG_NO_DISC;
}
Memory::Write_U32(GetDeviceID(), _CommandAddress + 4); Memory::Write_U32(GetDeviceID(), _CommandAddress + 4);
m_Active = true; m_Active = true;
return true; return true;
@ -58,12 +33,6 @@ bool CWII_IPC_HLE_Device_di::Open(u32 _CommandAddress, u32 _Mode)
bool CWII_IPC_HLE_Device_di::Close(u32 _CommandAddress, bool _bForce) bool CWII_IPC_HLE_Device_di::Close(u32 _CommandAddress, bool _bForce)
{ {
if (m_pFileSystem)
{
delete m_pFileSystem;
m_pFileSystem = nullptr;
}
m_ErrorStatus = 0;
if (!_bForce) if (!_bForce)
Memory::Write_U32(0, _CommandAddress + 4); Memory::Write_U32(0, _CommandAddress + 4);
m_Active = false; m_Active = false;
@ -72,18 +41,30 @@ bool CWII_IPC_HLE_Device_di::Close(u32 _CommandAddress, bool _bForce)
bool CWII_IPC_HLE_Device_di::IOCtl(u32 _CommandAddress) bool CWII_IPC_HLE_Device_di::IOCtl(u32 _CommandAddress)
{ {
u32 BufferIn = Memory::Read_U32(_CommandAddress + 0x10); u32 BufferIn = Memory::Read_U32(_CommandAddress + 0x10);
u32 BufferInSize = Memory::Read_U32(_CommandAddress + 0x14); u32 BufferInSize = Memory::Read_U32(_CommandAddress + 0x14);
u32 BufferOut = Memory::Read_U32(_CommandAddress + 0x18); u32 BufferOut = Memory::Read_U32(_CommandAddress + 0x18);
u32 BufferOutSize = Memory::Read_U32(_CommandAddress + 0x1C); u32 BufferOutSize = Memory::Read_U32(_CommandAddress + 0x1C);
u32 Command = Memory::Read_U32(BufferIn) >> 24;
u32 command_0 = Memory::Read_U32(BufferIn);
u32 command_1 = Memory::Read_U32(BufferIn + 4);
u32 command_2 = Memory::Read_U32(BufferIn + 8);
DEBUG_LOG(WII_IPC_DVD, "IOCtl Command(0x%08x) BufferIn(0x%08x, 0x%x) BufferOut(0x%08x, 0x%x)", DEBUG_LOG(WII_IPC_DVD, "IOCtl Command(0x%08x) BufferIn(0x%08x, 0x%x) BufferOut(0x%08x, 0x%x)",
Command, BufferIn, BufferInSize, BufferOut, BufferOutSize); command_0, BufferIn, BufferInSize, BufferOut, BufferOutSize);
u32 ReturnValue = ExecuteCommand(BufferIn, BufferInSize, BufferOut, BufferOutSize); // TATSUNOKO VS CAPCOM: Gets here with BufferOut == 0!!!
Memory::Write_U32(ReturnValue, _CommandAddress + 0x4); if (BufferOut != 0)
{
// Set out buffer to zeroes as a safety precaution
// to avoid answering nonsense values
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);
// TODO: Don't discard result.ticks_until_completion
return true; return true;
} }
@ -135,318 +116,6 @@ bool CWII_IPC_HLE_Device_di::IOCtlV(u32 _CommandAddress)
return true; return true;
} }
u32 CWII_IPC_HLE_Device_di::ExecuteCommand(u32 _BufferIn, u32 _BufferInSize, u32 _BufferOut, u32 _BufferOutSize)
{
u32 Command = Memory::Read_U32(_BufferIn) >> 24;
// TATSUNOKO VS CAPCOM: Gets here with _BufferOut == 0!!!
if (_BufferOut != 0)
{
// Set out buffer to zeroes as a safety precaution to avoid answering
// nonsense values
Memory::Memset(_BufferOut, 0, _BufferOutSize);
}
// Initializing a filesystem if it was just loaded
if (!m_pFileSystem && VolumeHandler::IsValid())
{
m_pFileSystem = DiscIO::CreateFileSystem(VolumeHandler::GetVolume());
m_CoverStatus |= DI_COVER_REG_INITIALIZED;
m_CoverStatus &= ~DI_COVER_REG_NO_DISC;
}
// De-initializing a filesystem if the volume was unmounted
if (m_pFileSystem && !VolumeHandler::IsValid())
{
delete m_pFileSystem;
m_pFileSystem = nullptr;
m_CoverStatus |= DI_COVER_REG_NO_DISC;
}
switch (Command)
{
case DVDLowInquiry:
{
// (shuffle2) Taken from my Wii
Memory::Write_U32(0x00000002, _BufferOut);
Memory::Write_U32(0x20060526, _BufferOut + 4);
// This was in the oubuf even though this cmd is only supposed to reply with 64bits
// However, this and other tests strongly suggest that the buffer is static, and it's never - or rarely cleared.
Memory::Write_U32(0x41000000, _BufferOut + 8);
INFO_LOG(WII_IPC_DVD, "DVDLowInquiry (Buffer 0x%08x, 0x%x)",
_BufferOut, _BufferOutSize);
}
break;
case DVDLowReadDiskID:
{
VolumeHandler::RAWReadToPtr(Memory::GetPointer(_BufferOut), 0, _BufferOutSize);
INFO_LOG(WII_IPC_DVD, "DVDLowReadDiskID %s",
ArrayToString(Memory::GetPointer(_BufferOut), _BufferOutSize, _BufferOutSize).c_str());
}
break;
case DVDLowRead:
{
if (_BufferOut == 0)
{
PanicAlert("DVDLowRead : _BufferOut == 0");
return 0;
}
u32 Size = Memory::Read_U32(_BufferIn + 0x04);
u64 DVDAddress = (u64)Memory::Read_U32(_BufferIn + 0x08) << 2;
// Don't do anything if the log is unselected
if (LogManager::GetInstance()->IsEnabled(LogTypes::FILEMON))
{
if (m_pFileSystem)
{
const std::string filename = m_pFileSystem->GetFileName(DVDAddress);
INFO_LOG(WII_IPC_DVD, "DVDLowRead: %s (0x%" PRIx64 ") - (DVDAddr: 0x%" PRIx64 ", Size: 0x%x)",
filename.c_str(), m_pFileSystem->GetFileSize(filename), DVDAddress, Size);
FileMon::CheckFile(filename, (int)m_pFileSystem->GetFileSize(filename));
}
else
{
ERROR_LOG(WII_IPC_DVD, "Filesystem is invalid.");
}
}
if (Size > _BufferOutSize)
{
PanicAlertT("Detected attempt to read more data from the DVD than fit inside the out buffer. Clamp.");
Size = _BufferOutSize;
}
if (!VolumeHandler::ReadToPtr(Memory::GetPointer(_BufferOut), DVDAddress, Size))
{
PanicAlertT("DVDLowRead - Fatal Error: failed to read from volume");
}
}
break;
case DVDLowWaitForCoverClose:
{
INFO_LOG(WII_IPC_DVD, "DVDLowWaitForCoverClose (Buffer 0x%08x, 0x%x)",
_BufferOut, _BufferOutSize);
return 4; // ???
}
break;
case DVDLowGetCoverReg:
Memory::Write_U32(m_CoverStatus, _BufferOut);
INFO_LOG(WII_IPC_DVD, "DVDLowGetCoverReg 0x%08x", Memory::Read_U32(_BufferOut));
break;
case DVDLowNotifyReset:
PanicAlert("DVDLowNotifyReset");
break;
case DVDLowReadDvdPhysical:
PanicAlert("DVDLowReadDvdPhysical");
break;
case DVDLowReadDvdCopyright:
PanicAlert("DVDLowReadDvdCopyright");
break;
case DVDLowReadDvdDiscKey:
PanicAlert("DVDLowReadDvdDiscKey");
break;
case DVDLowClearCoverInterrupt:
// TODO: check (seems to work ok)
INFO_LOG(WII_IPC_DVD, "DVDLowClearCoverInterrupt");
ClearCoverInterrupt();
break;
case DVDLowGetCoverStatus:
Memory::Write_U32(IsDiscInside() ? 2 : 1, _BufferOut);
INFO_LOG(WII_IPC_DVD, "DVDLowGetCoverStatus: Disc %sInserted", IsDiscInside() ? "" : "Not ");
break;
case DVDLowReset:
INFO_LOG(WII_IPC_DVD, "DVDLowReset");
break;
case DVDLowClosePartition:
INFO_LOG(WII_IPC_DVD, "DVDLowClosePartition");
break;
case DVDLowUnencryptedRead:
{
if (_BufferOut == 0)
{
PanicAlert("DVDLowRead : _BufferOut == 0");
return 0;
}
u32 Size = Memory::Read_U32(_BufferIn + 0x04);
// We must make sure it is in a valid area! (#001 check)
// * 0x00000000 - 0x00014000 (limit of older IOS versions)
// * 0x460a0000 - 0x460a0008
// * 0x7ed40000 - 0x7ed40008
u32 DVDAddress32 = Memory::Read_U32(_BufferIn + 0x08);
if (!((DVDAddress32 > 0x00000000 && DVDAddress32 < 0x00014000) ||
(((DVDAddress32 + Size) > 0x00000000) && (DVDAddress32 + Size) < 0x00014000) ||
(DVDAddress32 > 0x460a0000 && DVDAddress32 < 0x460a0008) ||
(((DVDAddress32 + Size) > 0x460a0000) && (DVDAddress32 + Size) < 0x460a0008) ||
(DVDAddress32 > 0x7ed40000 && DVDAddress32 < 0x7ed40008) ||
(((DVDAddress32 + Size) > 0x7ed40000) && (DVDAddress32 + Size) < 0x7ed40008)))
{
WARN_LOG(WII_IPC_DVD, "DVDLowUnencryptedRead: trying to read out of bounds @ %x", DVDAddress32);
m_ErrorStatus = ERROR_READY | ERROR_BLOCK_OOB;
// Should cause software to call DVDLowRequestError
return 2;
}
u64 DVDAddress = (u64)DVDAddress32 << 2;
INFO_LOG(WII_IPC_DVD, "DVDLowUnencryptedRead: DVDAddr: 0x%08" PRIx64 ", Size: 0x%x", DVDAddress, Size);
if (Size > _BufferOutSize)
{
PanicAlertT("Detected attempt to read more data from the DVD than fit inside the out buffer. Clamp.");
Size = _BufferOutSize;
}
if (!VolumeHandler::RAWReadToPtr(Memory::GetPointer(_BufferOut), DVDAddress, Size))
{
PanicAlertT("DVDLowUnencryptedRead - Fatal Error: failed to read from volume");
}
}
break;
case DVDLowEnableDvdVideo:
ERROR_LOG(WII_IPC_DVD, "DVDLowEnableDvdVideo");
break;
case DVDLowReportKey:
INFO_LOG(WII_IPC_DVD, "DVDLowReportKey");
// Does not work on retail discs/drives
// Retail games send this command to see if they are running on real retail hw
m_ErrorStatus = ERROR_READY | ERROR_INV_CMD;
return 2;
break;
case DVDLowSeek:
{
u64 DVDAddress = Memory::Read_U32(_BufferIn + 0x4) << 2;
if (m_pFileSystem)
{
const std::string filename = m_pFileSystem->GetFileName(DVDAddress);
INFO_LOG(WII_IPC_DVD, "DVDLowSeek: %s (0x%" PRIx64 ") - (DVDAddr: 0x%" PRIx64 ")",
filename.c_str(), m_pFileSystem->GetFileSize(filename), DVDAddress);
}
else
{
ERROR_LOG(WII_IPC_DVD, "Filesystem is invalid.");
}
}
break;
case DVDLowReadDvd:
ERROR_LOG(WII_IPC_DVD, "DVDLowReadDvd");
break;
case DVDLowReadDvdConfig:
ERROR_LOG(WII_IPC_DVD, "DVDLowReadDvdConfig");
break;
case DVDLowStopLaser:
ERROR_LOG(WII_IPC_DVD, "DVDLowStopLaser");
break;
case DVDLowOffset:
ERROR_LOG(WII_IPC_DVD, "DVDLowOffset");
break;
case DVDLowReadDiskBca:
WARN_LOG(WII_IPC_DVD, "DVDLowReadDiskBca");
Memory::Write_U32(1, _BufferOut + 0x30);
break;
case DVDLowRequestDiscStatus:
ERROR_LOG(WII_IPC_DVD, "DVDLowRequestDiscStatus");
break;
case DVDLowRequestRetryNumber:
ERROR_LOG(WII_IPC_DVD, "DVDLowRequestRetryNumber");
break;
case DVDLowSetMaximumRotation:
ERROR_LOG(WII_IPC_DVD, "DVDLowSetMaximumRotation");
break;
case DVDLowSerMeasControl:
ERROR_LOG(WII_IPC_DVD, "DVDLowSerMeasControl");
break;
case DVDLowRequestError:
// Identical to the error codes found in yagcd section 5.7.3.5.1 (so far)
WARN_LOG(WII_IPC_DVD, "DVDLowRequestError status = 0x%08x", m_ErrorStatus);
Memory::Write_U32(m_ErrorStatus, _BufferOut);
// When does error status get reset?
break;
// Ex commands are immediate and respond with 4 bytes
case DVDLowStopMotor:
{
u32 eject = Memory::Read_U32(_BufferIn + 4);
// Drive won't do anything till reset is issued. I think it replies like nothing is wrong though?
u32 kill = Memory::Read_U32(_BufferIn + 8);
INFO_LOG(WII_IPC_DVD, "DVDLowStopMotor %s %s",
eject ? "eject" : "", kill ? "kill!" : "");
if (eject)
{
SetLidOpen(true);
SetDiscInside(false);
}
}
break;
case DVDLowAudioBufferConfig:
/*
For more information: http://www.crazynation.org/GC/GC_DD_TECH/GCTech.htm
Upon Power up or reset , 2 commands must be issued for proper use of audio streaming:
DVDReadDiskID A8000040,00000000,00000020
DVDLowAudioBufferConfig E4xx00yy,00000000,00000020
xx=byte 8 [0 or 1] from the disk header retrieved from DVDReadDiskID
yy=0 (if xx=0) or 0xA (if xx=1)
*/
ERROR_LOG(WII_IPC_DVD, "DVDLowAudioBufferConfig");
break;
// New Super Mario Bros.Wii sends these cmds
// but it seems we don't need to implement anything
case 0x95:
case 0x96:
WARN_LOG(WII_IPC_DVD, "Unimplemented command 0x%08x (Buffer 0x%08x, 0x%x)",
Command, _BufferOut, _BufferOutSize);
break;
default:
ERROR_LOG(WII_IPC_DVD, "Unknown command 0x%08x (Buffer 0x%08x, 0x%x)",
Command, _BufferOut, _BufferOutSize);
PanicAlertT("Unknown command 0x%08x", Command);
break;
}
// i dunno but prolly 1 is okay all the time :)
return 1;
}
int CWII_IPC_HLE_Device_di::GetCmdDelay(u32 _CommandAddress) int CWII_IPC_HLE_Device_di::GetCmdDelay(u32 _CommandAddress)
{ {
u32 BufferIn = Memory::Read_U32(_CommandAddress + 0x10); u32 BufferIn = Memory::Read_U32(_CommandAddress + 0x10);

View File

@ -27,13 +27,4 @@ public:
bool IOCtlV(u32 _CommandAddress) override; bool IOCtlV(u32 _CommandAddress) override;
int GetCmdDelay(u32) override; int GetCmdDelay(u32) override;
private:
u32 ExecuteCommand(u32 BufferIn, u32 BufferInSize, u32 _BufferOut, u32 BufferOutSize);
DiscIO::IFileSystem* m_pFileSystem;
u32 m_ErrorStatus;
// This flag seems to only be reset with poweron/off, not sure
u32 m_CoverStatus;
}; };