Split /dev/di commands from DVDInterface

The various ioctls sometimes have different arguments than the DI command
registers, though they generally overlap.  There are also a bunch of ioctls
that don't even normally go into DVDInterface, just returning various data.
Some of the implemented ioctls are new to Dolphin.
This commit is contained in:
Pokechu22 2019-09-01 20:25:33 -07:00
parent a8ae5fa21a
commit ef2fc5a49b
6 changed files with 918 additions and 315 deletions

View File

@ -187,7 +187,6 @@ void SetLidOpen();
void UpdateInterrupts();
void GenerateDIInterrupt(DIInterruptType _DVDInterrupt);
void WriteImmediate(u32 value, u32 output_address, bool reply_to_ios);
bool ExecuteReadCommand(u64 dvd_offset, u32 output_address, u32 dvd_length, u32 output_length,
const DiscIO::Partition& partition, ReplyType reply_type,
DIInterruptType* interrupt_type);
@ -319,7 +318,7 @@ static void DTKStreamingCallback(const std::vector<u8>& audio_data, s64 cycles_l
else
{
// There's nothing to read, so using DVDThread is unnecessary.
u64 userdata = PackFinishExecutingCommandUserdata(ReplyType::DTK, DIInterruptType::INT_TCINT);
u64 userdata = PackFinishExecutingCommandUserdata(ReplyType::DTK, DIInterruptType::TCINT);
CoreTiming::ScheduleEvent(ticks_to_dtk, s_finish_executing_command, userdata);
}
}
@ -340,7 +339,7 @@ void Init()
s_finish_executing_command =
CoreTiming::RegisterEvent("FinishExecutingCommand", FinishExecutingCommandCallback);
u64 userdata = PackFinishExecutingCommandUserdata(ReplyType::DTK, DIInterruptType::INT_TCINT);
u64 userdata = PackFinishExecutingCommandUserdata(ReplyType::DTK, DIInterruptType::TCINT);
CoreTiming::ScheduleEvent(0, s_finish_executing_command, userdata);
}
@ -500,7 +499,7 @@ void SetLidOpen()
u32 old_value = s_DICVR.CVR;
s_DICVR.CVR = IsDiscInside() ? 0 : 1;
if (s_DICVR.CVR != old_value)
GenerateDIInterrupt(INT_CVRINT);
GenerateDIInterrupt(DIInterruptType::CVRINT);
}
bool UpdateRunningGameMetadata(std::optional<u64> title_id)
@ -576,8 +575,7 @@ void RegisterMMIO(MMIO::Mapping* mmio, u32 base)
s_DICR.Hex = val & 7;
if (s_DICR.TSTART)
{
ExecuteCommand(s_DICMDBUF[0], s_DICMDBUF[1], s_DICMDBUF[2], s_DIMAR,
s_DILENGTH, false);
ExecuteCommand(ReplyType::Interrupt);
}
}));
@ -605,29 +603,59 @@ void GenerateDIInterrupt(DIInterruptType dvd_interrupt)
{
switch (dvd_interrupt)
{
case INT_DEINT:
s_DISR.DEINT = 1;
case DIInterruptType::DEINT:
s_DISR.DEINT = true;
break;
case INT_TCINT:
s_DISR.TCINT = 1;
case DIInterruptType::TCINT:
s_DISR.TCINT = true;
break;
case INT_BRKINT:
s_DISR.BRKINT = 1;
case DIInterruptType::BRKINT:
s_DISR.BRKINT = true;
break;
case INT_CVRINT:
s_DICVR.CVRINT = 1;
case DIInterruptType::CVRINT:
s_DICVR.CVRINT = true;
break;
}
UpdateInterrupts();
}
void WriteImmediate(u32 value, u32 output_address, bool reply_to_ios)
void SetInterruptEnabled(DIInterruptType interrupt, bool enabled)
{
if (reply_to_ios)
Memory::Write_U32(value, output_address);
else
s_DIIMMBUF = value;
switch (interrupt)
{
case DIInterruptType::DEINT:
s_DISR.DEINTMASK = enabled;
break;
case DIInterruptType::TCINT:
s_DISR.TCINTMASK = enabled;
break;
case DIInterruptType::BRKINT:
s_DISR.BRKINTMASK = enabled;
break;
case DIInterruptType::CVRINT:
s_DICVR.CVRINTMASK = enabled;
break;
}
}
void ClearInterrupt(DIInterruptType interrupt)
{
switch (interrupt)
{
case DIInterruptType::DEINT:
s_DISR.DEINT = false;
break;
case DIInterruptType::TCINT:
s_DISR.TCINT = false;
break;
case DIInterruptType::BRKINT:
s_DISR.BRKINT = false;
break;
case DIInterruptType::CVRINT:
s_DICVR.CVRINT = false;
break;
}
}
// Iff false is returned, ScheduleEvent must be used to finish executing the command
@ -639,13 +667,13 @@ bool ExecuteReadCommand(u64 dvd_offset, u32 output_address, u32 dvd_length, u32
{
// Disc read fails
SetHighError(ERROR_NO_DISK_H);
*interrupt_type = INT_DEINT;
*interrupt_type = DIInterruptType::DEINT;
return false;
}
else
{
// Disc read succeeds
*interrupt_type = INT_TCINT;
*interrupt_type = DIInterruptType::TCINT;
}
if (dvd_length > output_length)
@ -662,254 +690,150 @@ bool ExecuteReadCommand(u64 dvd_offset, u32 output_address, u32 dvd_length, u32
// 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 reply_to_ios)
void ExecuteCommand(ReplyType reply_type)
{
ReplyType reply_type = reply_to_ios ? ReplyType::IOS : ReplyType::Interrupt;
DIInterruptType interrupt_type = INT_TCINT;
DIInterruptType interrupt_type = DIInterruptType::TCINT;
bool command_handled_by_thread = false;
// DVDLowRequestError needs access to the error code set by the previous command
if (command_0 >> 24 != DVDLowRequestError)
if (static_cast<DICommand>(s_DICMDBUF[0] >> 24) != DICommand::RequestError)
SetHighError(0);
switch (command_0 >> 24)
switch (static_cast<DICommand>(s_DICMDBUF[0] >> 24))
{
// Seems to be used by both GC and Wii
case DVDLowInquiry:
// Used by both GC and Wii
case DICommand::Inquiry:
// (shuffle2) Taken from my Wii
Memory::Write_U32(0x00000002, output_address);
Memory::Write_U32(0x20060526, output_address + 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, output_address + 8);
Memory::Write_U32(0x00000002, s_DIMAR); // Revision level, device code
Memory::Write_U32(0x20060526, s_DIMAR + 4); // Release date
Memory::Write_U32(0x41000000, s_DIMAR + 8); // Version
INFO_LOG(DVDINTERFACE, "DVDLowInquiry (Buffer 0x%08x, 0x%x)", output_address, output_length);
INFO_LOG(DVDINTERFACE, "DVDLowInquiry (Buffer 0x%08x, 0x%x)", s_DIMAR, s_DILENGTH);
break;
// Only seems to be used from WII_IPC, not through direct access
case DVDLowReadDiskID:
INFO_LOG(DVDINTERFACE, "DVDLowReadDiskID");
command_handled_by_thread =
ExecuteReadCommand(0, output_address, 0x20, output_length, DiscIO::PARTITION_NONE,
reply_type, &interrupt_type);
break;
// Only used from WII_IPC. This is the only read command that decrypts data
case DVDLowRead:
INFO_LOG(DVDINTERFACE, "DVDLowRead: DVDAddr: 0x%09" PRIx64 ", Size: 0x%x", (u64)command_2 << 2,
command_1);
command_handled_by_thread =
ExecuteReadCommand((u64)command_2 << 2, output_address, command_1, output_length,
s_current_partition, reply_type, &interrupt_type);
break;
// Probably only used by Wii
case DVDLowWaitForCoverClose:
INFO_LOG(DVDINTERFACE, "DVDLowWaitForCoverClose");
interrupt_type = (DIInterruptType)4; // ???
break;
// "Set Extension"...not sure what it does. GC only?
case 0x55:
// GC-only patched drive firmware command, used by libogc
case DICommand::Unknown55:
INFO_LOG(DVDINTERFACE, "SetExtension");
break;
// Probably only used though WII_IPC
case DVDLowGetCoverReg:
WriteImmediate(s_DICVR.Hex, output_address, reply_to_ios);
DEBUG_LOG(DVDINTERFACE, "DVDLowGetCoverReg 0x%08x", s_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:
DEBUG_LOG(DVDINTERFACE, "DVDLowClearCoverInterrupt");
s_DICVR.CVRINT = 0;
break;
// Probably only used by Wii
case DVDLowGetCoverStatus:
WriteImmediate(IsDiscInside() ? 2 : 1, output_address, reply_to_ios);
INFO_LOG(DVDINTERFACE, "DVDLowGetCoverStatus: Disc %sInserted", IsDiscInside() ? "" : "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);
// We must make sure it is in a valid area! (#001 check)
// Are these checks correct? They seem to mix 32-bit offsets and 8-bit lengths
// * 0x00000000 - 0x00014000 (limit of older IOS versions)
// * 0x460a0000 - 0x460a0008
// * 0x7ed40000 - 0x7ed40008
if (((command_2 > 0x00000000 && command_2 < 0x00014000) ||
(((command_2 + command_1) > 0x00000000) && (command_2 + command_1) < 0x00014000) ||
(command_2 > 0x460a0000 && command_2 < 0x460a0008) ||
(((command_2 + command_1) > 0x460a0000) && (command_2 + command_1) < 0x460a0008) ||
(command_2 > 0x7ed40000 && command_2 < 0x7ed40008) ||
(((command_2 + command_1) > 0x7ed40000) && (command_2 + command_1) < 0x7ed40008)))
{
command_handled_by_thread =
ExecuteReadCommand((u64)command_2 << 2, output_address, command_1, output_length,
DiscIO::PARTITION_NONE, reply_type, &interrupt_type);
}
else
{
WARN_LOG(DVDINTERFACE, "DVDLowUnencryptedRead: trying to read out of bounds @ %09" PRIx64,
(u64)command_2 << 2);
s_error_code = ERROR_READY | ERROR_BLOCK_OOB;
// Should cause software to call DVDLowRequestError
interrupt_type = INT_BRKINT;
}
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:
// Wii-exclusive
case DICommand::ReportKey:
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
SetHighError(ERROR_INV_CMD);
interrupt_type = INT_BRKINT;
interrupt_type = DIInterruptType::DEINT;
break;
// DMA Read from Disc. Only seems to be used through direct access, not WII_IPC
case 0xA8:
switch (command_0 & 0xFF)
// DMA Read from Disc. Only used through direct access on GC; direct use is prohibited by
// IOS (which uses it internally)
case DICommand::Read:
switch (s_DICMDBUF[0] & 0xFF)
{
case 0x00: // Read Sector
{
u64 iDVDOffset = (u64)command_1 << 2;
u64 iDVDOffset = static_cast<u64>(s_DICMDBUF[1]) << 2;
INFO_LOG(DVDINTERFACE,
"Read: DVDOffset=%08" PRIx64
", DMABuffer = %08x, SrcLength = %08x, DMALength = %08x",
iDVDOffset, output_address, command_2, output_length);
iDVDOffset, s_DIMAR, s_DICMDBUF[2], s_DILENGTH);
command_handled_by_thread =
ExecuteReadCommand(iDVDOffset, output_address, command_2, output_length,
DiscIO::PARTITION_NONE, reply_type, &interrupt_type);
ExecuteReadCommand(iDVDOffset, s_DIMAR, s_DICMDBUF[2], s_DILENGTH, DiscIO::PARTITION_NONE,
reply_type, &interrupt_type);
}
break;
case 0x40: // Read DiscID
INFO_LOG(DVDINTERFACE, "Read DiscID %08x", Memory::Read_U32(output_address));
command_handled_by_thread =
ExecuteReadCommand(0, output_address, 0x20, output_length, DiscIO::PARTITION_NONE,
reply_type, &interrupt_type);
INFO_LOG(DVDINTERFACE, "Read DiscID: buffer %08x", s_DIMAR);
command_handled_by_thread = ExecuteReadCommand(
0, s_DIMAR, 0x20, s_DILENGTH, DiscIO::PARTITION_NONE, reply_type, &interrupt_type);
break;
default:
ERROR_LOG(DVDINTERFACE, "Unknown read subcommand: %08x", command_0);
ERROR_LOG(DVDINTERFACE, "Unknown read subcommand: %08x", s_DICMDBUF[0]);
break;
}
break;
// Seems to be used by both GC and Wii
case DVDLowSeek:
// Used by both GC and Wii
case DICommand::Seek:
// Currently unimplemented
INFO_LOG(DVDINTERFACE, "Seek: offset=%09" PRIx64 " (ignoring)", (u64)command_1 << 2);
INFO_LOG(DVDINTERFACE, "Seek: offset=%09" PRIx64 " (ignoring)",
static_cast<u64>(s_DICMDBUF[1]) << 2);
break;
// Probably only used by Wii
case DVDLowReadDvd:
// Wii-exclusive
case DICommand::ReadDVDMetadata:
switch ((s_DICMDBUF[0] >> 16) & 0xFF)
{
case 0:
ERROR_LOG(DVDINTERFACE, "DVDLowReadDvdPhysical");
break;
case 1:
ERROR_LOG(DVDINTERFACE, "DVDLowReadDvdCopyright");
break;
case 2:
ERROR_LOG(DVDINTERFACE, "DVDLowReadDvdDiscKey");
break;
default:
ERROR_LOG(DVDINTERFACE, "Unknown 0xAD subcommand in %08x", s_DICMDBUF[0]);
break;
}
break;
// Wii-exclusive
case DICommand::ReadDVD:
ERROR_LOG(DVDINTERFACE, "DVDLowReadDvd");
break;
// Probably only used by Wii
case DVDLowReadDvdConfig:
// Wii-exclusive
case DICommand::ReadDVDConfig:
ERROR_LOG(DVDINTERFACE, "DVDLowReadDvdConfig");
break;
// Probably only used by Wii
case DVDLowStopLaser:
// Wii-exclusive
case DICommand::StopLaser:
ERROR_LOG(DVDINTERFACE, "DVDLowStopLaser");
break;
// Probably only used by Wii
case DVDLowOffset:
// Wii-exclusive
case DICommand::Offset:
ERROR_LOG(DVDINTERFACE, "DVDLowOffset");
break;
// Probably only used by Wii
case DVDLowReadDiskBca:
// Wii-exclusive
case DICommand::ReadBCA:
WARN_LOG(DVDINTERFACE, "DVDLowReadDiskBca");
Memory::Write_U32(1, output_address + 0x30);
Memory::Write_U32(1, s_DIMAR + 0x30);
break;
// Probably only used by Wii
case DVDLowRequestDiscStatus:
// Wii-exclusive
case DICommand::RequestDiscStatus:
ERROR_LOG(DVDINTERFACE, "DVDLowRequestDiscStatus");
break;
// Probably only used by Wii
case DVDLowRequestRetryNumber:
// Wii-exclusive
case DICommand::RequestRetryNumber:
ERROR_LOG(DVDINTERFACE, "DVDLowRequestRetryNumber");
break;
// Probably only used by Wii
case DVDLowSetMaximumRotation:
// Wii-exclusive
case DICommand::SetMaximumRotation:
ERROR_LOG(DVDINTERFACE, "DVDLowSetMaximumRotation");
break;
// Probably only used by Wii
case DVDLowSerMeasControl:
// Wii-exclusive
case DICommand::SerMeasControl:
ERROR_LOG(DVDINTERFACE, "DVDLowSerMeasControl");
break;
// Used by both GC and Wii
case DVDLowRequestError:
case DICommand::RequestError:
INFO_LOG(DVDINTERFACE, "Requesting error... (0x%08x)", s_error_code);
WriteImmediate(s_error_code, output_address, reply_to_ios);
s_DIIMMBUF = s_error_code;
SetHighError(0);
break;
// Audio Stream (Immediate). Only seems to be used by some GC games
// Audio Stream (Immediate). Only used by some GC games, but does exist on the Wii
// (command_0 >> 16) & 0xFF = Subcommand
// command_1 << 2 = Offset on disc
// command_2 = Length of the stream
case 0xE1:
case DICommand::AudioStream:
{
u8 cancel_stream = (command_0 >> 16) & 0xFF;
u8 cancel_stream = (s_DICMDBUF[0] >> 16) & 0xFF;
if (cancel_stream)
{
s_stop_at_track_end = false;
@ -917,14 +841,14 @@ void ExecuteCommand(u32 command_0, u32 command_1, u32 command_2, u32 output_addr
}
else
{
if ((command_1 == 0) && (command_2 == 0))
if ((s_DICMDBUF[1] == 0) && (s_DICMDBUF[2] == 0))
{
s_stop_at_track_end = true;
}
else if (!s_stop_at_track_end)
{
s_next_start = static_cast<u64>(command_1) << 2;
s_next_length = command_2;
s_next_start = static_cast<u64>(s_DICMDBUF[1]) << 2;
s_next_length = s_DICMDBUF[2];
if (!s_stream)
{
s_current_start = s_next_start;
@ -936,15 +860,15 @@ void ExecuteCommand(u32 command_0, u32 command_1, u32 command_2, u32 output_addr
}
}
INFO_LOG(DVDINTERFACE, "(Audio) Stream cmd: %08x offset: %08" PRIx64 " length: %08x", command_0,
(u64)command_1 << 2, command_2);
INFO_LOG(DVDINTERFACE, "(Audio) Stream cmd: %08x offset: %08" PRIx64 " length: %08x",
s_DICMDBUF[0], (u64)s_DICMDBUF[1] << 2, s_DICMDBUF[2]);
}
break;
// Request Audio Status (Immediate). Only seems to be used by some GC games
case 0xE2:
// Request Audio Status (Immediate). Only used by some GC games, but does exist on the Wii
case DICommand::RequestAudioStatus:
{
switch (command_0 >> 16 & 0xFF)
switch (s_DICMDBUF[0] >> 16 & 0xFF)
{
case 0x00: // Returns streaming status
INFO_LOG(DVDINTERFACE,
@ -953,38 +877,38 @@ void ExecuteCommand(u32 command_0, u32 command_1, u32 command_2, u32 output_addr
"CurrentStart:%08" PRIx64 " CurrentLength:%08x",
s_audio_position, s_current_start + s_current_length, s_current_start,
s_current_length);
WriteImmediate(s_stream ? 1 : 0, output_address, reply_to_ios);
s_DIIMMBUF = (s_stream ? 1 : 0);
break;
case 0x01: // Returns the current offset
INFO_LOG(DVDINTERFACE, "(Audio): Stream Status: Request Audio status AudioPos:%08" PRIx64,
s_audio_position);
WriteImmediate(static_cast<u32>((s_audio_position & 0xffffffffffff8000ull) >> 2),
output_address, reply_to_ios);
s_DIIMMBUF = static_cast<u32>((s_audio_position & 0xffffffffffff8000ull) >> 2);
break;
case 0x02: // Returns the start offset
INFO_LOG(DVDINTERFACE, "(Audio): Stream Status: Request Audio status CurrentStart:%08" PRIx64,
s_current_start);
WriteImmediate(static_cast<u32>(s_current_start >> 2), output_address, reply_to_ios);
s_DIIMMBUF = static_cast<u32>(s_current_start >> 2);
break;
case 0x03: // Returns the total length
INFO_LOG(DVDINTERFACE, "(Audio): Stream Status: Request Audio status CurrentLength:%08x",
s_current_length);
WriteImmediate(s_current_length, output_address, reply_to_ios);
s_DIIMMBUF = s_current_length;
break;
default:
INFO_LOG(DVDINTERFACE, "(Audio): Subcommand: %02x Request Audio status %s",
command_0 >> 16 & 0xFF, s_stream ? "on" : "off");
s_DICMDBUF[0] >> 16 & 0xFF, s_stream ? "on" : "off");
break;
}
}
break;
case DVDLowStopMotor:
// Used by both GC and Wii
case DICommand::StopMotor:
{
INFO_LOG(DVDINTERFACE, "DVDLowStopMotor %s %s", command_1 ? "eject" : "",
command_2 ? "kill!" : "");
INFO_LOG(DVDINTERFACE, "DVDLowStopMotor %s %s", s_DICMDBUF[1] ? "eject" : "",
s_DICMDBUF[2] ? "kill!" : "");
const bool force_eject = command_1 && !command_2;
const bool force_eject = s_DICMDBUF[1] && !s_DICMDBUF[2];
if (Config::Get(Config::MAIN_AUTO_DISC_CHANGE) && !Movie::IsPlayingInput() &&
DVDThread::IsInsertedDiscRunning() && !s_auto_disc_change_paths.empty())
@ -1001,7 +925,7 @@ void ExecuteCommand(u32 command_0, u32 command_1, u32 command_2, u32 output_addr
}
// DVD Audio Enable/Disable (Immediate). GC uses this, and apparently Wii also does...?
case DVDLowAudioBufferConfig:
case DICommand::AudioBufferConfig:
// The IPL uses this command to enable or disable DTK audio depending on the value of byte 0x8
// in the disc header. See http://www.crazynation.org/GC/GC_DD_TECH/GCTech.htm for more info.
// The link is dead, but you can access the page using the Wayback Machine at archive.org.
@ -1010,35 +934,36 @@ void ExecuteCommand(u32 command_0, u32 command_1, u32 command_2, u32 output_addr
// Should we be failing with an error code when the game tries to use the 0xE1 command?
// (Not that this should matter normally, since games that use DTK set the header byte to 1)
if ((command_0 >> 16) & 0xFF)
if ((s_DICMDBUF[0] >> 16) & 0xFF)
INFO_LOG(DVDINTERFACE, "DTK enabled");
else
INFO_LOG(DVDINTERFACE, "DTK disabled");
break;
// yet another (GC?) command we prolly don't care about
case 0xEE:
// GC-only patched drive firmware command, used by libogc
case DICommand::UnknownEE:
INFO_LOG(DVDINTERFACE, "SetStatus");
break;
// Debug commands; see yagcd. We don't really care
// 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.
// Can probably only be used through direct access
case 0xFE:
ERROR_LOG(DVDINTERFACE, "Unsupported DVD Drive debug command 0x%08x", command_0);
// Can only be used through direct access and only after unlocked.
case DICommand::Debug:
ERROR_LOG(DVDINTERFACE, "Unsupported DVD Drive debug command 0x%08x", s_DICMDBUF[0]);
break;
// Unlock Commands. 1: "MATSHITA" 2: "DVD-GAME"
// Just for fun
// Can probably only be used through direct access
case 0xFF:
// Can only be used through direct access. The unlock command doesn't seem to work on the Wii.
case DICommand::DebugUnlock:
{
if (command_0 == 0xFF014D41 && command_1 == 0x54534849 && command_2 == 0x54410200)
if (s_DICMDBUF[0] == 0xFF014D41 && s_DICMDBUF[1] == 0x54534849 && s_DICMDBUF[2] == 0x54410200)
{
INFO_LOG(DVDINTERFACE, "Unlock test 1 passed");
}
else if (command_0 == 0xFF004456 && command_1 == 0x442D4741 && command_2 == 0x4D450300)
else if (s_DICMDBUF[0] == 0xFF004456 && s_DICMDBUF[1] == 0x442D4741 &&
s_DICMDBUF[2] == 0x4D450300)
{
INFO_LOG(DVDINTERFACE, "Unlock test 2 passed");
}
@ -1050,9 +975,9 @@ void ExecuteCommand(u32 command_0, u32 command_1, u32 command_2, u32 output_addr
break;
default:
ERROR_LOG(DVDINTERFACE, "Unknown command 0x%08x (Buffer 0x%08x, 0x%x)", command_0,
output_address, output_length);
PanicAlertT("Unknown DVD command %08x - fatal error", command_0);
ERROR_LOG(DVDINTERFACE, "Unknown command 0x%08x (Buffer 0x%08x, 0x%x)", s_DICMDBUF[0], s_DIMAR,
s_DILENGTH);
PanicAlertT("Unknown DVD command %08x - fatal error", s_DICMDBUF[0]);
break;
}
@ -1065,6 +990,23 @@ void ExecuteCommand(u32 command_0, u32 command_1, u32 command_2, u32 output_addr
}
}
void PerformDecryptingRead(u32 position, u32 length, u32 output_address, ReplyType reply_type)
{
DIInterruptType interrupt_type = DIInterruptType::TCINT;
const bool command_handled_by_thread =
ExecuteReadCommand(static_cast<u64>(position) << 2, output_address, length, length,
s_current_partition, reply_type, &interrupt_type);
if (!command_handled_by_thread)
{
// TODO: Needs testing to determine if COMMAND_LATENCY_US is accurate for this
CoreTiming::ScheduleEvent(COMMAND_LATENCY_US * (SystemTimers::GetTicksPerSecond() / 1000000),
s_finish_executing_command,
PackFinishExecutingCommandUserdata(reply_type, interrupt_type));
}
}
u64 PackFinishExecutingCommandUserdata(ReplyType reply_type, DIInterruptType interrupt_type)
{
return (static_cast<u64>(reply_type) << 32) + static_cast<u32>(interrupt_type);
@ -1114,9 +1056,7 @@ void FinishExecutingCommand(ReplyType reply_type, DIInterruptType interrupt_type
case ReplyType::IOS:
{
auto di = IOS::HLE::GetIOS()->GetDeviceByName("/dev/di");
if (di)
std::static_pointer_cast<IOS::HLE::Device::DI>(di)->FinishIOCtl(interrupt_type);
IOS::HLE::Device::DI::InterruptFromDVDInterface(interrupt_type);
break;
}

View File

@ -24,38 +24,31 @@ class Mapping;
namespace DVDInterface
{
enum DICommand
enum class DICommand : u8
{
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
Inquiry = 0x12,
ReportKey = 0xa4,
Read = 0xa8,
Seek = 0xab,
ReadDVDMetadata = 0xad,
ReadDVD = 0xd0,
ReadDVDConfig = 0xd1,
StopLaser = 0xd2,
Offset = 0xd9,
ReadBCA = 0xda,
RequestDiscStatus = 0xdb,
RequestRetryNumber = 0xdc,
SetMaximumRotation = 0xdd,
SerMeasControl = 0xdf,
RequestError = 0xe0,
AudioStream = 0xe1,
RequestAudioStatus = 0xe2,
StopMotor = 0xe3,
AudioBufferConfig = 0xe4,
Debug = 0xfe,
DebugUnlock = 0xff,
Unknown55 = 0x55,
UnknownEE = 0xee,
};
// "low" error codes
@ -86,12 +79,12 @@ constexpr u32 ERROR_MEDIUM = 0x062800; // Medium may have changed.
constexpr u32 ERROR_MEDIUM_REQ = 0x0b5a01; // Operator medium removal request.
constexpr u32 HIGH_ERROR_MASK = 0x00ffffff;
enum DIInterruptType : int
enum class DIInterruptType : int
{
INT_DEINT = 0,
INT_TCINT = 1,
INT_BRKINT = 2,
INT_CVRINT = 3,
DEINT = 0,
TCINT = 1,
BRKINT = 2,
CVRINT = 3,
};
enum class ReplyType : u32
@ -99,7 +92,7 @@ enum class ReplyType : u32
NoReply,
Interrupt,
IOS,
DTK
DTK,
};
enum class EjectCause
@ -132,8 +125,8 @@ bool UpdateRunningGameMetadata(std::optional<u64> title_id = {});
// Direct access to DI for IOS HLE (simpler to implement than how real IOS accesses DI,
// and lets us skip encrypting/decrypting in some cases)
void ChangePartition(const DiscIO::Partition& partition);
void ExecuteCommand(u32 command_0, u32 command_1, u32 command_2, u32 output_address,
u32 output_length, bool reply_to_ios);
void ExecuteCommand(ReplyType reply_type);
void PerformDecryptingRead(u32 position, u32 length, u32 output_address, ReplyType reply_type);
void SetLowError(u32 low_error);
void SetHighError(u32 high_error);
@ -142,4 +135,8 @@ void SetHighError(u32 high_error);
void FinishExecutingCommand(ReplyType reply_type, DIInterruptType interrupt_type, s64 cycles_late,
const std::vector<u8>& data = std::vector<u8>());
// Used by IOS HLE
void SetInterruptEnabled(DIInterruptType interrupt, bool enabled);
void ClearInterrupt(DIInterruptType interrupt);
} // end of namespace DVDInterface

View File

@ -354,8 +354,8 @@ static void FinishRead(u64 id, s64 cycles_late)
}
// Notify the emulated software that the command has been executed
DVDInterface::FinishExecutingCommand(request.reply_type, DVDInterface::INT_TCINT, cycles_late,
buffer);
DVDInterface::FinishExecutingCommand(request.reply_type, DVDInterface::DIInterruptType::TCINT,
cycles_late, buffer);
}
static void DVDThread()

View File

@ -13,15 +13,36 @@
#include "Common/CommonTypes.h"
#include "Common/Logging/Log.h"
#include "Common/MsgHandler.h"
#include "Core/CoreTiming.h"
#include "Core/HW/DVD/DVDInterface.h"
#include "Core/HW/DVD/DVDThread.h"
#include "Core/HW/MMIO.h"
#include "Core/HW/Memmap.h"
#include "Core/IOS/ES/ES.h"
#include "Core/IOS/ES/Formats.h"
#include "DiscIO/Volume.h"
template <u32 addr>
class RegisterWrapper
{
public:
operator u32() const { return Memory::mmio_mapping->Read<u32>(addr); }
void operator=(u32 rhs) { Memory::mmio_mapping->Write(addr, rhs); }
};
static RegisterWrapper<0x0D806000> DISR;
static RegisterWrapper<0x0D806004> DICVR;
static RegisterWrapper<0x0D806008> DICMDBUF0;
static RegisterWrapper<0x0D80600C> DICMDBUF1;
static RegisterWrapper<0x0D806010> DICMDBUF2;
static RegisterWrapper<0x0D806014> DIMAR;
static RegisterWrapper<0x0D806018> DILENGTH;
static RegisterWrapper<0x0D80601C> DICR;
static RegisterWrapper<0x0D806020> DIIMMBUF;
namespace IOS::HLE::Device
{
CoreTiming::EventType* DI::s_finish_executing_di_command;
DI::DI(Kernel& ios, const std::string& device_name) : Device(ios, device_name)
{
}
@ -30,40 +51,49 @@ void DI::DoState(PointerWrap& p)
{
DoStateShared(p);
p.Do(m_commands_to_execute);
p.Do(m_executing_command);
p.Do(m_has_initialized);
p.Do(m_last_length);
}
IPCCommandResult DI::Open(const OpenRequest& request)
{
InitializeIfFirstTime();
return Device::Open(request);
}
IPCCommandResult DI::IOCtl(const IOCtlRequest& request)
{
// DI IOCtls are handled in a special way by Dolphin
// compared to other IOS 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.
InitializeIfFirstTime();
bool ready_to_execute = m_commands_to_execute.empty();
// DI IOCtls are handled in a special way by Dolphin compared to other IOS functions.
// Many of them use DVDInterface's ExecuteCommand, which will execute commands more or less
// asynchronously. The rest are also treated as async to match this. Only one command can be
// executed at a time, so commands are queued until DVDInterface is ready to handle them.
const u8 command = Memory::Read_U8(request.buffer_in);
if (request.request != command)
{
WARN_LOG(
IOS_DI,
"IOCtl: Received conflicting commands: ioctl 0x%02x, buffer 0x%02x. Using ioctl command.",
request.request, command);
}
bool ready_to_execute = !m_executing_command.has_value();
m_commands_to_execute.push_back(request.address);
if (ready_to_execute)
StartIOCtl(request);
// DVDInterface handles the timing and will call FinishIOCtl after the command
// has been executed to reply to the request, so we shouldn't reply here.
if (ready_to_execute)
{
ProcessQueuedIOCtl();
}
// FinishIOCtl will be called after the command has been executed
// to reply to the request, so we shouldn't reply here.
return GetNoReply();
}
void DI::StartIOCtl(const IOCtlRequest& request)
{
const u32 command_0 = Memory::Read_U32(request.buffer_in);
const u32 command_1 = Memory::Read_U32(request.buffer_in + 4);
const u32 command_2 = Memory::Read_U32(request.buffer_in + 8);
// 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, request.buffer_out,
request.buffer_out_size, true);
}
void DI::FinishIOCtl(DVDInterface::DIInterruptType interrupt_type)
void DI::ProcessQueuedIOCtl()
{
if (m_commands_to_execute.empty())
{
@ -71,29 +101,512 @@ void DI::FinishIOCtl(DVDInterface::DIInterruptType interrupt_type)
return;
}
// This command has been executed, so it's removed from the queue
u32 command_address = m_commands_to_execute.front();
m_executing_command = {m_commands_to_execute.front()};
m_commands_to_execute.pop_front();
m_ios.EnqueueIPCReply(IOCtlRequest{command_address}, interrupt_type);
IOCtlRequest request{m_executing_command->m_request_address};
auto finished = StartIOCtl(request);
if (finished)
{
CoreTiming::ScheduleEvent(2700 * SystemTimers::TIMER_RATIO, s_finish_executing_di_command,
static_cast<u64>(finished.value()));
return;
}
}
std::optional<DI::DIResult> DI::WriteIfFits(const IOCtlRequest& request, u32 value)
{
if (request.buffer_out_size < 4)
{
WARN_LOG(IOS_DI, "Output buffer is too small to contain result; returning security error");
return DIResult::SecurityError;
}
else
{
Memory::Write_U32(value, request.buffer_out);
return DIResult::Success;
}
}
std::optional<DI::DIResult> DI::StartIOCtl(const IOCtlRequest& request)
{
if (request.buffer_in_size != 0x20)
{
ERROR_LOG(IOS_DI, "IOCtl: Received bad input buffer size 0x%02x, should be 0x20",
request.buffer_in_size);
return DIResult::SecurityError;
}
// DVDInterface's ExecuteCommand handles most of the work for most of these.
// The IOCtl callback is used to generate a reply afterwards.
switch (static_cast<DIIoctl>(request.request))
{
case DIIoctl::DVDLowInquiry:
INFO_LOG(IOS_DI, "DVDLowInquiry");
DICMDBUF0 = 0x12000000;
DICMDBUF1 = 0;
return StartDMATransfer(0x20, request);
case DIIoctl::DVDLowReadDiskID:
INFO_LOG(IOS_DI, "DVDLowReadDiskID");
DICMDBUF0 = 0xA8000040;
DICMDBUF1 = 0;
DICMDBUF2 = 0x20;
return StartDMATransfer(0x20, request);
// TODO: also include the post-read second read
case DIIoctl::DVDLowRead:
{
// TODO. Needs to include decryption.
const u32 length = Memory::Read_U32(request.buffer_in + 4);
const u32 position = Memory::Read_U32(request.buffer_in + 8);
INFO_LOG(IOS_DI, "DVDLowRead: offset 0x%08x (byte 0x%09" PRIx64 "), length 0x%x", position,
static_cast<u64>(position) << 2, length);
if (request.buffer_out_size < length)
{
WARN_LOG(IOS_DI,
"Output buffer is too small for the result of the read (%d bytes given, needed at "
"least %d); returning security error",
request.buffer_out_size, length);
return DIResult::SecurityError;
}
m_last_length = position; // An actual mistake in IOS
DVDInterface::PerformDecryptingRead(position, length, request.buffer_out,
DVDInterface::ReplyType::IOS);
return {};
}
case DIIoctl::DVDLowWaitForCoverClose:
// This is a bit awkward to implement, as it doesn't return for a long time, so just act as if
// the cover was immediately closed
INFO_LOG(IOS_DI, "DVDLowWaitForCoverClose - skipping");
return DIResult::CoverClosed;
case DIIoctl::DVDLowGetCoverRegister:
{
u32 dicvr = DICVR;
DEBUG_LOG(IOS_DI, "DVDLowGetCoverRegister 0x%08x", dicvr);
return WriteIfFits(request, dicvr);
}
case DIIoctl::DVDLowNotifyReset:
INFO_LOG(IOS_DI, "DVDLowNotifyReset");
ResetDIRegisters();
// Should also reset current partition and such
return DIResult::Success;
case DIIoctl::DVDLowSetSpinupFlag:
ERROR_LOG(IOS_DI, "DVDLowSetSpinupFlag - not a valid command, rejecting");
return DIResult::BadArgument;
case DIIoctl::DVDLowReadDvdPhysical:
{
const u8 position = Memory::Read_U8(request.buffer_in + 7);
INFO_LOG(IOS_DI, "DVDLowReadDvdPhysical: position 0x%02x", position);
DICMDBUF0 = 0xAD000000 | (position << 8);
DICMDBUF1 = 0;
DICMDBUF2 = 0;
return StartDMATransfer(0x800, request);
}
case DIIoctl::DVDLowReadDvdCopyright:
{
const u8 position = Memory::Read_U8(request.buffer_in + 7);
INFO_LOG(IOS_DI, "DVDLowReadDvdCopyright: position 0x%02x", position);
DICMDBUF0 = 0xAD010000 | (position << 8);
DICMDBUF1 = 0;
DICMDBUF2 = 0;
return StartImmediateTransfer(request);
}
case DIIoctl::DVDLowReadDvdDiscKey:
{
const u8 position = Memory::Read_U8(request.buffer_in + 7);
INFO_LOG(IOS_DI, "DVDLowReadDvdDiscKey: position 0x%02x", position);
DICMDBUF0 = 0xAD020000 | (position << 8);
DICMDBUF1 = 0;
DICMDBUF2 = 0;
return StartDMATransfer(0x800, request);
}
case DIIoctl::DVDLowGetLength:
INFO_LOG(IOS_DI, "DVDLowGetLength 0x%08x", m_last_length);
return WriteIfFits(request, m_last_length);
case DIIoctl::DVDLowGetImmBuf:
{
u32 diimmbuf = DIIMMBUF;
INFO_LOG(IOS_DI, "DVDLowGetImmBuf 0x%08x", diimmbuf);
return WriteIfFits(request, diimmbuf);
}
case DIIoctl::DVDLowUnmaskCoverInterrupt:
INFO_LOG(IOS_DI, "DVDLowUnmaskCoverInterrupt");
DVDInterface::SetInterruptEnabled(DVDInterface::DIInterruptType::CVRINT, false);
return DIResult::Success;
case DIIoctl::DVDLowClearCoverInterrupt:
DEBUG_LOG(IOS_DI, "DVDLowClearCoverInterrupt");
DVDInterface::ClearInterrupt(DVDInterface::DIInterruptType::CVRINT);
return DIResult::Success;
case DIIoctl::DVDLowGetCoverStatus:
// TODO: handle resetting case
INFO_LOG(IOS_DI, "DVDLowGetCoverStatus: Disc %sInserted",
DVDInterface::IsDiscInside() ? "" : "Not ");
return WriteIfFits(request, DVDInterface::IsDiscInside() ? 2 : 1);
case DIIoctl::DVDLowEnableCoverInterrupt:
INFO_LOG(IOS_DI, "DVDLowEnableCoverInterrupt");
DVDInterface::SetInterruptEnabled(DVDInterface::DIInterruptType::CVRINT, true);
return DIResult::Success;
case DIIoctl::DVDLowReset:
{
const bool spinup = Memory::Read_U32(request.address + 4);
INFO_LOG(IOS_DI, "DVDLowReset %s spinup", spinup ? "with" : "without");
DVDInterface::Reset();
ResetDIRegisters();
// Should also reset current partition and such
return DIResult::Success;
}
case DIIoctl::DVDLowOpenPartition:
ERROR_LOG(IOS_DI, "DVDLowOpenPartition as an ioctl - rejecting");
return DIResult::SecurityError;
case DIIoctl::DVDLowClosePartition:
INFO_LOG(IOS_DI, "DVDLowClosePartition");
DVDInterface::ChangePartition(DiscIO::PARTITION_NONE);
return DIResult::Success;
case DIIoctl::DVDLowUnencryptedRead:
{
const u32 length = Memory::Read_U32(request.buffer_in + 4);
const u32 position = Memory::Read_U32(request.buffer_in + 8);
const u32 end = position + (length >> 2); // a 32-bit offset
INFO_LOG(IOS_DI, "DVDLowUnencryptedRead: offset 0x%08x (byte 0x%09" PRIx64 "), length 0x%x",
position, static_cast<u64>(position) << 2, length);
// (start, end) as 32-bit offsets
// Older IOS versions only accept the first range. Later versions added the extra ranges to
// check how the drive responds to out of bounds reads (an error 001 check).
constexpr std::array<std::pair<u32, u32>, 3> valid_ranges = {
std::make_pair(0, 0x14000), // "System area"
std::make_pair(0x460A0000, 0x460A0008),
std::make_pair(0x7ED40000, 0x7ED40008),
};
for (auto range : valid_ranges)
{
if (range.first <= position && position <= range.second && range.first <= end &&
end <= range.second)
{
DICMDBUF0 = 0xA8000000;
DICMDBUF1 = position;
DICMDBUF2 = length;
return StartDMATransfer(length, request);
}
}
WARN_LOG(IOS_DI, "DVDLowUnencryptedRead: trying to read from an illegal region!");
return DIResult::SecurityError;
}
case DIIoctl::DVDLowEnableDvdVideo:
ERROR_LOG(IOS_DI, "DVDLowEnableDvdVideo - rejecting");
return DIResult::SecurityError;
// There are a bunch of ioctlvs that are also (unintentionally) usable as ioctls; reject these in
// Dolphin as games are unlikely to use them.
case DIIoctl::DVDLowGetNoDiscOpenPartitionParams:
ERROR_LOG(IOS_DI, "DVDLowGetNoDiscOpenPartitionParams as an ioctl - rejecting");
return DIResult::SecurityError;
case DIIoctl::DVDLowNoDiscOpenPartition:
ERROR_LOG(IOS_DI, "DVDLowNoDiscOpenPartition as an ioctl - rejecting");
return DIResult::SecurityError;
case DIIoctl::DVDLowGetNoDiscBufferSizes:
ERROR_LOG(IOS_DI, "DVDLowGetNoDiscBufferSizes as an ioctl - rejecting");
return DIResult::SecurityError;
case DIIoctl::DVDLowOpenPartitionWithTmdAndTicket:
ERROR_LOG(IOS_DI, "DVDLowOpenPartitionWithTmdAndTicket as an ioctl - rejecting");
return DIResult::SecurityError;
case DIIoctl::DVDLowOpenPartitionWithTmdAndTicketView:
ERROR_LOG(IOS_DI, "DVDLowOpenPartitionWithTmdAndTicketView as an ioctl - rejecting");
return DIResult::SecurityError;
case DIIoctl::DVDLowGetStatusRegister:
{
u32 disr = DISR;
INFO_LOG(IOS_DI, "DVDLowGetStatusRegister: 0x%08x", disr);
return WriteIfFits(request, disr);
}
case DIIoctl::DVDLowGetControlRegister:
{
u32 dicr = DICR;
INFO_LOG(IOS_DI, "DVDLowGetControlRegister: 0x%08x", dicr);
return WriteIfFits(request, dicr);
}
case DIIoctl::DVDLowReportKey:
{
const u8 param1 = Memory::Read_U8(request.buffer_in + 7);
const u32 param2 = Memory::Read_U32(request.buffer_in + 8);
INFO_LOG(IOS_DI, "DVDLowReportKey: param1 0x%02x, param2 0x%06x", param1, param2);
DICMDBUF0 = 0xA4000000 | (param1 << 16);
DICMDBUF1 = param2 & 0xFFFFFF;
DICMDBUF2 = 0;
return StartDMATransfer(0x20, request);
}
case DIIoctl::DVDLowSeek:
{
const u32 position = Memory::Read_U32(request.buffer_in + 4); // 32-bit offset
INFO_LOG(IOS_DI, "DVDLowSeek: position 0x%08x, translated to 0x%08x", position,
position); // TODO: do partition translation!
DICMDBUF0 = 0xAB000000;
DICMDBUF1 = position;
return StartImmediateTransfer(request, false);
}
case DIIoctl::DVDLowReadDvd:
{
const u8 flag1 = Memory::Read_U8(request.buffer_in + 7);
const u8 flag2 = Memory::Read_U8(request.buffer_in + 11);
const u32 length = Memory::Read_U32(request.buffer_in + 12);
const u32 position = Memory::Read_U32(request.buffer_in + 16);
INFO_LOG(IOS_DI, "DVDLowReadDvd(%d, %d): position 0x%06x, length 0x%06x", flag1, flag2,
position, length);
DICMDBUF0 = 0xD0000000 | ((flag1 & 1) << 7) | ((flag2 & 1) << 6);
DICMDBUF1 = position & 0xFFFFFF;
DICMDBUF2 = length & 0xFFFFFF;
return StartDMATransfer(0x800 * length, request);
}
case DIIoctl::DVDLowReadDvdConfig:
{
const u8 flag1 = Memory::Read_U8(request.buffer_in + 7);
const u8 param2 = Memory::Read_U8(request.buffer_in + 11);
const u32 position = Memory::Read_U32(request.buffer_in + 12);
INFO_LOG(IOS_DI, "DVDLowReadDvdConfig(%d, %d): position 0x%06x", flag1, param2, position);
DICMDBUF0 = 0xD1000000 | ((flag1 & 1) << 16) | param2;
DICMDBUF1 = position & 0xFFFFFF;
DICMDBUF2 = 0;
return StartImmediateTransfer(request);
}
case DIIoctl::DVDLowStopLaser:
INFO_LOG(IOS_DI, "DVDLowStopLaser");
DICMDBUF0 = 0xD2000000;
return StartImmediateTransfer(request);
case DIIoctl::DVDLowOffset:
{
const u8 flag = Memory::Read_U8(request.buffer_in + 7);
const u32 offset = Memory::Read_U32(request.buffer_in + 8);
INFO_LOG(IOS_DI, "DVDLowOffset(%d): offset 0x%08x", flag, offset);
DICMDBUF0 = 0xD9000000 | ((flag & 1) << 16);
DICMDBUF1 = offset;
return StartImmediateTransfer(request);
}
case DIIoctl::DVDLowReadDiskBca:
INFO_LOG(IOS_DI, "DVDLowReadDiskBca");
DICMDBUF0 = 0xDA000000;
return StartDMATransfer(0x40, request);
case DIIoctl::DVDLowRequestDiscStatus:
INFO_LOG(IOS_DI, "DVDLowRequestDiscStatus");
DICMDBUF0 = 0xDB000000;
return StartImmediateTransfer(request);
case DIIoctl::DVDLowRequestRetryNumber:
INFO_LOG(IOS_DI, "DVDLowRequestRetryNumber");
DICMDBUF0 = 0xDC000000;
return StartImmediateTransfer(request);
case DIIoctl::DVDLowSetMaximumRotation:
{
const u8 speed = Memory::Read_U8(request.buffer_in + 7);
INFO_LOG(IOS_DI, "DVDLowSetMaximumRotation: speed %d", speed);
DICMDBUF0 = 0xDD000000 | ((speed & 3) << 16);
return StartImmediateTransfer(request, false);
}
case DIIoctl::DVDLowSerMeasControl:
{
const u8 flag1 = Memory::Read_U8(request.buffer_in + 7);
const u8 flag2 = Memory::Read_U8(request.buffer_in + 11);
INFO_LOG(IOS_DI, "DVDLowSerMeasControl(%d, %d)", flag1, flag2);
DICMDBUF0 = 0xDF000000 | ((flag1 & 1) << 17) | ((flag2 & 1) << 16);
return StartDMATransfer(0x20, request);
}
case DIIoctl::DVDLowRequestError:
INFO_LOG(IOS_DI, "DVDLowRequestError");
DICMDBUF0 = 0xE0000000;
return StartImmediateTransfer(request);
case DIIoctl::DVDLowAudioStream:
{
const u8 mode = Memory::Read_U8(request.buffer_in + 7);
const u32 length = Memory::Read_U32(request.buffer_in + 8);
const u32 position = Memory::Read_U32(request.buffer_in + 12);
INFO_LOG(IOS_DI, "DVDLowAudioStream(%d): offset 0x%08x (byte 0x%09" PRIx64 "), length 0x%x",
mode, position, static_cast<u64>(position) << 2, length);
DICMDBUF0 = 0xE1000000 | ((mode & 3) << 16);
DICMDBUF1 = position;
DICMDBUF2 = length;
return StartImmediateTransfer(request, false);
}
case DIIoctl::DVDLowRequestAudioStatus:
{
const u8 mode = Memory::Read_U8(request.buffer_in + 7);
INFO_LOG(IOS_DI, "DVDLowRequestAudioStatus(%d)", mode);
DICMDBUF0 = 0xE2000000 | ((mode & 3) << 16);
DICMDBUF1 = 0;
// Note that this command does not copy the value written to DIIMMBUF, which makes it rather
// useless (to actually use it, DVDLowGetImmBuf would need to be used afterwards)
return StartImmediateTransfer(request, false);
}
case DIIoctl::DVDLowStopMotor:
{
const u8 eject = Memory::Read_U8(request.buffer_in + 7);
const u8 kill = Memory::Read_U8(request.buffer_in + 11);
INFO_LOG(IOS_DI, "DVDLowStopMotor(%d, %d)", eject, kill);
DICMDBUF0 = 0xE3000000 | ((eject & 1) << 17) | ((kill & 1) << 20);
DICMDBUF1 = 0;
return StartImmediateTransfer(request);
}
case DIIoctl::DVDLowAudioBufferConfig:
{
const u8 enable = Memory::Read_U8(request.buffer_in + 7);
const u8 buffer_size = Memory::Read_U8(request.buffer_in + 11);
INFO_LOG(IOS_DI, "DVDLowAudioBufferConfig: %s, buffer size %d", enable ? "enabled" : "disabled",
buffer_size);
DICMDBUF0 = 0xE4000000 | ((enable & 1) << 16) | (buffer_size & 0xf);
DICMDBUF1 = 0;
// On the other hand, this command *does* copy DIIMMBUF, but the actual code in the drive never
// writes anything to it, so it just copies over a stale value (e.g. from DVDLowRequestError).
return StartImmediateTransfer(request);
}
default:
ERROR_LOG(IOS_DI, "Unknown ioctl 0x%02x", request.request);
return DIResult::SecurityError;
}
}
std::optional<DI::DIResult> DI::StartDMATransfer(u32 command_length, const IOCtlRequest& request)
{
if (request.buffer_out_size < command_length)
{
// Actual /dev/di will still send a command, but won't write the length or output address,
// causing it to eventually time out after 15 seconds. Just immediately time out here,
// instead.
WARN_LOG(IOS_DI,
"Output buffer is too small for the result of the command (%d bytes given, needed at "
"least %d); returning read timed out (immediately, instead of waiting)",
request.buffer_out_size, command_length);
return DIResult::ReadTimedOut;
}
if ((command_length & 0x1f) != 0 || (request.buffer_out & 0x1f) != 0)
{
// In most cases, the actual DI driver just hangs for unaligned data, but let's be a bit more
// gentle.
WARN_LOG(IOS_DI,
"Output buffer or length is incorrectly aligned (buffer 0x%08x, buffer length %x, "
"command length %x)",
request.buffer_out, request.buffer_out_size, command_length);
return DIResult::BadArgument;
}
DIMAR = request.buffer_out;
m_last_length = command_length;
DILENGTH = command_length;
DVDInterface::ExecuteCommand(DVDInterface::ReplyType::IOS);
// Reply will be posted when done by FinishIOCtl.
return {};
}
std::optional<DI::DIResult> DI::StartImmediateTransfer(const IOCtlRequest& request,
bool write_to_buf)
{
if (write_to_buf && request.buffer_out_size < 4)
{
WARN_LOG(IOS_DI,
"Output buffer size is too small for an immediate transfer (%d bytes, should be at "
"least 4). Performing transfer anyways.",
request.buffer_out_size);
}
m_executing_command->m_copy_diimmbuf = write_to_buf;
DVDInterface::ExecuteCommand(DVDInterface::ReplyType::IOS);
// Reply will be posted when done by FinishIOCtl.
return {};
}
void DI::InterruptFromDVDInterface(DVDInterface::DIInterruptType interrupt_type)
{
DIResult result;
switch (interrupt_type)
{
case DVDInterface::DIInterruptType::TCINT:
result = DIResult::Success;
break;
case DVDInterface::DIInterruptType::DEINT:
result = DIResult::DriveError;
break;
default:
PanicAlert("IOS::HLE::Device::DI: Unexpected DVDInterface interrupt %d!",
static_cast<int>(interrupt_type));
result = DIResult::DriveError;
break;
}
auto di = IOS::HLE::GetIOS()->GetDeviceByName("/dev/di");
if (di)
{
std::static_pointer_cast<DI>(di)->FinishDICommand(result);
}
else
{
PanicAlert("IOS::HLE::Device::DI: Received interrupt from DVDInterface when device wasn't "
"registered!");
}
}
void DI::FinishDICommandCallback(u64 userdata, s64 ticksbehind)
{
const DIResult result = static_cast<DIResult>(userdata);
auto di = IOS::HLE::GetIOS()->GetDeviceByName("/dev/di");
if (di)
std::static_pointer_cast<DI>(di)->FinishDICommand(result);
else
PanicAlert("IOS::HLE::Device::DI: Received interrupt from DI when device wasn't registered!");
}
void DI::FinishDICommand(DIResult result)
{
if (!m_executing_command.has_value())
{
PanicAlert("IOS::HLE::Device::DI: There is no command to finish!");
return;
}
IOCtlRequest request{m_executing_command->m_request_address};
if (m_executing_command->m_copy_diimmbuf)
Memory::Write_U32(DIIMMBUF, request.buffer_out);
m_ios.EnqueueIPCReply(request, static_cast<s32>(result));
m_executing_command.reset();
// 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())
{
IOCtlRequest next_request{m_commands_to_execute.front()};
StartIOCtl(next_request);
ProcessQueuedIOCtl();
}
}
IPCCommandResult DI::IOCtlV(const IOCtlVRequest& request)
{
for (const auto& vector : request.io_vectors)
Memory::Memset(vector.address, 0, vector.size);
s32 return_value = IPC_SUCCESS;
switch (request.request)
// IOCtlVs are not queued since they don't (currently) go into DVDInterface and act
// asynchronously. This does mean that an IOCtlV can be executed while an IOCtl is in progress,
// which isn't ideal.
InitializeIfFirstTime();
if (request.in_vectors[0].size != 0x20)
{
case DVDInterface::DVDLowOpenPartition:
ERROR_LOG(IOS_DI, "IOCtlV: Received bad input buffer size 0x%02x, should be 0x20",
request.in_vectors[0].size);
return GetDefaultReply(static_cast<s32>(DIResult::BadArgument));
}
const u8 command = Memory::Read_U8(request.in_vectors[0].address);
if (request.request != command)
{
WARN_LOG(IOS_DI,
"IOCtlV: Received conflicting commands: ioctl 0x%02x, buffer 0x%02x. Using ioctlv "
"command.",
request.request, command);
}
DIResult return_value = DIResult::BadArgument;
switch (static_cast<DIIoctl>(request.request))
{
case DIIoctl::DVDLowOpenPartition:
{
if (request.in_vectors.size() != 3 || request.io_vectors.size() != 2)
{
ERROR_LOG(IOS_DI, "DVDLowOpenPartition: bad vector count %zu in/%zu out",
request.in_vectors.size(), request.io_vectors.size());
break;
}
DEBUG_ASSERT_MSG(IOS_DI, request.in_vectors[1].address == 0, "DVDLowOpenPartition with ticket");
DEBUG_ASSERT_MSG(IOS_DI, request.in_vectors[2].address == 0,
"DVDLowOpenPartition with cert chain");
@ -103,20 +616,69 @@ IPCCommandResult DI::IOCtlV(const IOCtlVRequest& request)
const DiscIO::Partition partition(partition_offset);
DVDInterface::ChangePartition(partition);
INFO_LOG(IOS_DI, "DVDLowOpenPartition: partition_offset 0x%016" PRIx64, partition_offset);
INFO_LOG(IOS_DI, "DVDLowOpenPartition: partition_offset 0x%09" PRIx64, partition_offset);
// Read TMD to the buffer
const IOS::ES::TMDReader tmd = DVDThread::GetTMD(partition);
const std::vector<u8>& raw_tmd = tmd.GetBytes();
Memory::CopyToEmu(request.io_vectors[0].address, raw_tmd.data(), raw_tmd.size());
m_ios.GetES()->DIVerify(tmd, DVDThread::GetTicket(partition));
return_value = 1;
ReturnCode es_result = m_ios.GetES()->DIVerify(tmd, DVDThread::GetTicket(partition));
Memory::Write_U32(es_result, request.io_vectors[1].address);
return_value = DIResult::Success;
break;
}
case DIIoctl::DVDLowGetNoDiscOpenPartitionParams:
ERROR_LOG(IOS_DI, "DVDLowGetNoDiscOpenPartitionParams - dummied out");
request.DumpUnknown(GetDeviceName(), Common::Log::IOS_DI);
break;
case DIIoctl::DVDLowNoDiscOpenPartition:
ERROR_LOG(IOS_DI, "DVDLowNoDiscOpenPartition - dummied out");
request.DumpUnknown(GetDeviceName(), Common::Log::IOS_DI);
break;
case DIIoctl::DVDLowGetNoDiscBufferSizes:
ERROR_LOG(IOS_DI, "DVDLowGetNoDiscBufferSizes - dummied out");
request.DumpUnknown(GetDeviceName(), Common::Log::IOS_DI);
break;
case DIIoctl::DVDLowOpenPartitionWithTmdAndTicket:
ERROR_LOG(IOS_DI, "DVDLowOpenPartitionWithTmdAndTicket - not implemented");
break;
case DIIoctl::DVDLowOpenPartitionWithTmdAndTicketView:
ERROR_LOG(IOS_DI, "DVDLowOpenPartitionWithTmdAndTicketView - not implemented");
break;
default:
ERROR_LOG(IOS_DI, "Unknown ioctlv 0x%02x", request.request);
request.DumpUnknown(GetDeviceName(), Common::Log::IOS_DI);
}
return GetDefaultReply(return_value);
return GetDefaultReply(static_cast<s32>(return_value));
}
void DI::InitializeIfFirstTime()
{
// Match the behavior of Nintendo's initDvdDriverStage2, which is called the first time
// an open/ioctl/ioctlv occurs. This behavior is observable by directly reading the DI registers,
// so it shouldn't be handled in the constructor.
// Note that ResetDIRegisters also clears the current partition, which actually normally happens
// earlier; however, that is not observable.
// Also, registers are not cleared if syscall_check_di_reset (0x46) returns true (bit 10 of
// HW_RESETS is set), which is not currently emulated.
if (!m_has_initialized)
{
ResetDIRegisters();
m_has_initialized = true;
}
}
void DI::ResetDIRegisters()
{
// Clear transfer complete and error interrupts (normally r/z, but here we just directly write
// zero)
DVDInterface::ClearInterrupt(DVDInterface::DIInterruptType::TCINT);
DVDInterface::ClearInterrupt(DVDInterface::DIInterruptType::DEINT);
// Enable transfer complete and error interrupts, and disable cover interrupt
DVDInterface::SetInterruptEnabled(DVDInterface::DIInterruptType::TCINT, true);
DVDInterface::SetInterruptEnabled(DVDInterface::DIInterruptType::DEINT, true);
DVDInterface::SetInterruptEnabled(DVDInterface::DIInterruptType::CVRINT, false);
}
} // namespace IOS::HLE::Device

View File

@ -5,6 +5,7 @@
#pragma once
#include <deque>
#include <optional>
#include <string>
#include "Common/CommonTypes.h"
@ -15,7 +16,16 @@ class PointerWrap;
namespace DVDInterface
{
enum DIInterruptType : int;
enum class DIInterruptType : int;
}
namespace CoreTiming
{
struct EventType;
}
namespace IOS::HLE
{
void Init();
}
namespace IOS::HLE::Device
@ -25,16 +35,107 @@ class DI : public Device
public:
DI(Kernel& ios, const std::string& device_name);
static void InterruptFromDVDInterface(DVDInterface::DIInterruptType interrupt_type);
void DoState(PointerWrap& p) override;
IPCCommandResult Open(const OpenRequest& request) override;
IPCCommandResult IOCtl(const IOCtlRequest& request) override;
IPCCommandResult IOCtlV(const IOCtlVRequest& request) override;
void FinishIOCtl(DVDInterface::DIInterruptType interrupt_type);
enum class DIIoctl : u32
{
DVDLowInquiry = 0x12,
DVDLowReadDiskID = 0x70,
DVDLowRead = 0x71,
DVDLowWaitForCoverClose = 0x79,
DVDLowGetCoverRegister = 0x7a, // DVDLowPrepareCoverRegister
DVDLowNotifyReset = 0x7e,
DVDLowSetSpinupFlag = 0x7f,
DVDLowReadDvdPhysical = 0x80,
DVDLowReadDvdCopyright = 0x81,
DVDLowReadDvdDiscKey = 0x82,
DVDLowGetLength = 0x83,
DVDLowGetImmBuf = 0x84, // Unconfirmed name
DVDLowUnmaskCoverInterrupt = 0x85,
DVDLowClearCoverInterrupt = 0x86,
// 0x87 is a dummied out command
DVDLowGetCoverStatus = 0x88,
DVDLowEnableCoverInterrupt = 0x89, // Unconfirmed name
DVDLowReset = 0x8a,
DVDLowOpenPartition = 0x8b, // ioctlv only
DVDLowClosePartition = 0x8c,
DVDLowUnencryptedRead = 0x8d,
DVDLowEnableDvdVideo = 0x8e,
DVDLowGetNoDiscOpenPartitionParams = 0x90, // ioctlv, dummied out
DVDLowNoDiscOpenPartition = 0x91, // ioctlv, dummied out
DVDLowGetNoDiscBufferSizes = 0x92, // ioctlv, dummied out
DVDLowOpenPartitionWithTmdAndTicket = 0x93, // ioctlv
DVDLowOpenPartitionWithTmdAndTicketView = 0x94, // ioctlv
DVDLowGetStatusRegister = 0x95, // DVDLowPrepareStatusRegsiter
DVDLowGetControlRegister = 0x96, // DVDLowPrepareControlRegister
DVDLowReportKey = 0xa4,
// 0xa8 is unusable externally
DVDLowSeek = 0xab,
DVDLowReadDvd = 0xd0,
DVDLowReadDvdConfig = 0xd1,
DVDLowStopLaser = 0xd2,
DVDLowOffset = 0xd9,
DVDLowReadDiskBca = 0xda,
DVDLowRequestDiscStatus = 0xdb,
DVDLowRequestRetryNumber = 0xdc,
DVDLowSetMaximumRotation = 0xdd,
DVDLowSerMeasControl = 0xdf,
DVDLowRequestError = 0xe0,
DVDLowAudioStream = 0xe1,
DVDLowRequestAudioStatus = 0xe2,
DVDLowStopMotor = 0xe3,
DVDLowAudioBufferConfig = 0xe4,
};
enum class DIResult : s32
{
Success = 1,
DriveError = 2,
CoverClosed = 4,
ReadTimedOut = 16,
SecurityError = 32,
VerifyError = 64,
BadArgument = 128,
};
private:
void StartIOCtl(const IOCtlRequest& request);
struct ExecutingCommandInfo
{
ExecutingCommandInfo() {}
ExecutingCommandInfo(u32 request_address)
: m_request_address(request_address), m_copy_diimmbuf(false)
{
}
u32 m_request_address;
bool m_copy_diimmbuf;
};
friend void ::IOS::HLE::Init();
void ProcessQueuedIOCtl();
std::optional<DIResult> StartIOCtl(const IOCtlRequest& request);
std::optional<DIResult> WriteIfFits(const IOCtlRequest& request, u32 value);
std::optional<DIResult> StartDMATransfer(u32 command_length, const IOCtlRequest& request);
std::optional<DIResult> StartImmediateTransfer(const IOCtlRequest& request,
bool write_to_buf = true);
void InitializeIfFirstTime();
void ResetDIRegisters();
static void FinishDICommandCallback(u64 userdata, s64 ticksbehind);
void FinishDICommand(DIResult result);
static CoreTiming::EventType* s_finish_executing_di_command;
std::optional<ExecutingCommandInfo> m_executing_command;
std::deque<u32> m_commands_to_execute;
bool m_has_initialized = false;
u32 m_last_length = 0;
};
} // namespace IOS::HLE::Device

View File

@ -782,6 +782,9 @@ void Init()
device->EventNotify();
});
Device::DI::s_finish_executing_di_command =
CoreTiming::RegisterEvent("FinishDICommand", Device::DI::FinishDICommandCallback);
// Start with IOS80 to simulate part of the Wii boot process.
s_ios = std::make_unique<EmulationKernel>(Titles::SYSTEM_MENU_IOS);
// On a Wii, boot2 launches the system menu IOS, which then launches the system menu