|
|
|
@ -157,12 +157,12 @@ static u32 s_current_length;
|
|
|
|
|
static u64 s_next_start;
|
|
|
|
|
static u32 s_next_length;
|
|
|
|
|
static u32 s_pending_samples;
|
|
|
|
|
static bool s_can_configure_dtk = true;
|
|
|
|
|
static bool s_enable_dtk = false;
|
|
|
|
|
static u8 s_dtk_buffer_length = 0; // TODO: figure out how this affects the regular buffer
|
|
|
|
|
|
|
|
|
|
// Disc drive state
|
|
|
|
|
static u32 s_error_code = 0;
|
|
|
|
|
static DriveState s_drive_state;
|
|
|
|
|
static DriveError s_error_code;
|
|
|
|
|
|
|
|
|
|
// Disc drive timing
|
|
|
|
|
static u64 s_read_buffer_start_time;
|
|
|
|
@ -186,19 +186,19 @@ static void EjectDiscCallback(u64 userdata, s64 cyclesLate);
|
|
|
|
|
static void InsertDiscCallback(u64 userdata, s64 cyclesLate);
|
|
|
|
|
static void FinishExecutingCommandCallback(u64 userdata, s64 cycles_late);
|
|
|
|
|
|
|
|
|
|
void SetLidOpen();
|
|
|
|
|
static void SetLidOpen();
|
|
|
|
|
|
|
|
|
|
void UpdateInterrupts();
|
|
|
|
|
void GenerateDIInterrupt(DIInterruptType _DVDInterrupt);
|
|
|
|
|
static void UpdateInterrupts();
|
|
|
|
|
static void GenerateDIInterrupt(DIInterruptType dvd_interrupt);
|
|
|
|
|
|
|
|
|
|
bool ExecuteReadCommand(u64 dvd_offset, u32 output_address, u32 dvd_length, u32 output_length,
|
|
|
|
|
const DiscIO::Partition& partition, ReplyType reply_type,
|
|
|
|
|
DIInterruptType* interrupt_type);
|
|
|
|
|
static bool ExecuteReadCommand(u64 dvd_offset, u32 output_address, u32 dvd_length,
|
|
|
|
|
u32 output_length, const DiscIO::Partition& partition,
|
|
|
|
|
ReplyType reply_type, DIInterruptType* interrupt_type);
|
|
|
|
|
|
|
|
|
|
u64 PackFinishExecutingCommandUserdata(ReplyType reply_type, DIInterruptType interrupt_type);
|
|
|
|
|
static u64 PackFinishExecutingCommandUserdata(ReplyType reply_type, DIInterruptType interrupt_type);
|
|
|
|
|
|
|
|
|
|
void ScheduleReads(u64 offset, u32 length, const DiscIO::Partition& partition, u32 output_address,
|
|
|
|
|
ReplyType reply_type);
|
|
|
|
|
static void ScheduleReads(u64 offset, u32 length, const DiscIO::Partition& partition,
|
|
|
|
|
u32 output_address, ReplyType reply_type);
|
|
|
|
|
|
|
|
|
|
void DoState(PointerWrap& p)
|
|
|
|
|
{
|
|
|
|
@ -219,10 +219,10 @@ void DoState(PointerWrap& p)
|
|
|
|
|
p.Do(s_next_start);
|
|
|
|
|
p.Do(s_next_length);
|
|
|
|
|
p.Do(s_pending_samples);
|
|
|
|
|
p.Do(s_can_configure_dtk);
|
|
|
|
|
p.Do(s_enable_dtk);
|
|
|
|
|
p.Do(s_dtk_buffer_length);
|
|
|
|
|
|
|
|
|
|
p.Do(s_drive_state);
|
|
|
|
|
p.Do(s_error_code);
|
|
|
|
|
|
|
|
|
|
p.Do(s_read_buffer_start_time);
|
|
|
|
@ -345,8 +345,19 @@ void Init()
|
|
|
|
|
|
|
|
|
|
DVDThread::Start();
|
|
|
|
|
|
|
|
|
|
Reset();
|
|
|
|
|
s_DISR.Hex = 0;
|
|
|
|
|
s_DICVR.Hex = 1; // Disc Channel relies on cover being open when no disc is inserted
|
|
|
|
|
s_DICMDBUF[0] = 0;
|
|
|
|
|
s_DICMDBUF[1] = 0;
|
|
|
|
|
s_DICMDBUF[2] = 0;
|
|
|
|
|
s_DIMAR = 0;
|
|
|
|
|
s_DILENGTH = 0;
|
|
|
|
|
s_DICR.Hex = 0;
|
|
|
|
|
s_DIIMMBUF = 0;
|
|
|
|
|
s_DICFG.Hex = 0;
|
|
|
|
|
s_DICFG.CONFIG = 1; // Disable bootrom descrambler
|
|
|
|
|
|
|
|
|
|
ResetDrive(false);
|
|
|
|
|
|
|
|
|
|
s_auto_change_disc = CoreTiming::RegisterEvent("AutoChangeDisc", AutoChangeDiscCallback);
|
|
|
|
|
s_eject_disc = CoreTiming::RegisterEvent("EjectDisc", EjectDiscCallback);
|
|
|
|
@ -359,22 +370,10 @@ void Init()
|
|
|
|
|
CoreTiming::ScheduleEvent(0, s_finish_executing_command, userdata);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// This doesn't reset any inserted disc or the cover state.
|
|
|
|
|
void Reset(bool spinup)
|
|
|
|
|
// Resets state on the MN102 chip in the drive itself, but not the DI registers exposed on the
|
|
|
|
|
// emulated device, or any inserted disc.
|
|
|
|
|
void ResetDrive(bool spinup)
|
|
|
|
|
{
|
|
|
|
|
INFO_LOG(DVDINTERFACE, "Reset %s spinup", spinup ? "with" : "without");
|
|
|
|
|
|
|
|
|
|
s_DISR.Hex = 0;
|
|
|
|
|
s_DICMDBUF[0] = 0;
|
|
|
|
|
s_DICMDBUF[1] = 0;
|
|
|
|
|
s_DICMDBUF[2] = 0;
|
|
|
|
|
s_DIMAR = 0;
|
|
|
|
|
s_DILENGTH = 0;
|
|
|
|
|
s_DICR.Hex = 0;
|
|
|
|
|
s_DIIMMBUF = 0;
|
|
|
|
|
s_DICFG.Hex = 0;
|
|
|
|
|
s_DICFG.CONFIG = 1; // Disable bootrom descrambler
|
|
|
|
|
|
|
|
|
|
s_stream = false;
|
|
|
|
|
s_stop_at_track_end = false;
|
|
|
|
|
s_audio_position = 0;
|
|
|
|
@ -383,31 +382,30 @@ void Reset(bool spinup)
|
|
|
|
|
s_current_start = 0;
|
|
|
|
|
s_current_length = 0;
|
|
|
|
|
s_pending_samples = 0;
|
|
|
|
|
s_can_configure_dtk = true;
|
|
|
|
|
s_enable_dtk = false;
|
|
|
|
|
s_dtk_buffer_length = 0;
|
|
|
|
|
|
|
|
|
|
if (!IsDiscInside())
|
|
|
|
|
{
|
|
|
|
|
// ERROR_COVER is used when the cover is open;
|
|
|
|
|
// ERROR_NO_DISK_L is used when the cover is closed but there is no disc.
|
|
|
|
|
// CoverOpened is used when the cover is open;
|
|
|
|
|
// NoMediumPresent is used when the cover is closed but there is no disc.
|
|
|
|
|
// On the Wii, this can only happen if something other than a DVD is inserted into the disc
|
|
|
|
|
// drive (for instance, an audio CD) and only after it attempts to read it. Otherwise, it will
|
|
|
|
|
// report the cover as opened.
|
|
|
|
|
SetLowError(ERROR_COVER);
|
|
|
|
|
SetDriveState(DriveState::CoverOpened);
|
|
|
|
|
}
|
|
|
|
|
else if (!spinup)
|
|
|
|
|
{
|
|
|
|
|
// Wii hardware tests indicate that this is used when ejecting and inserting a new disc, or
|
|
|
|
|
// performing a reset without spinup.
|
|
|
|
|
SetLowError(ERROR_CHANGE_DISK);
|
|
|
|
|
SetDriveState(DriveState::DiscChangeDetected);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
SetLowError(ERROR_NO_DISKID_L);
|
|
|
|
|
SetDriveState(DriveState::DiscIdNotRead);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SetHighError(ERROR_NONE);
|
|
|
|
|
SetDriveError(DriveError::None);
|
|
|
|
|
|
|
|
|
|
// The buffer is empty at start
|
|
|
|
|
s_read_buffer_start_offset = 0;
|
|
|
|
@ -454,7 +452,7 @@ void SetDisc(std::unique_ptr<DiscIO::VolumeDisc> disc,
|
|
|
|
|
DVDThread::SetDisc(std::move(disc));
|
|
|
|
|
SetLidOpen();
|
|
|
|
|
|
|
|
|
|
Reset(false);
|
|
|
|
|
ResetDrive(false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool IsDiscInside()
|
|
|
|
@ -544,7 +542,7 @@ bool AutoChangeDisc()
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SetLidOpen()
|
|
|
|
|
static void SetLidOpen()
|
|
|
|
|
{
|
|
|
|
|
u32 old_value = s_DICVR.CVR;
|
|
|
|
|
s_DICVR.CVR = IsDiscInside() ? 0 : 1;
|
|
|
|
@ -633,7 +631,7 @@ void RegisterMMIO(MMIO::Mapping* mmio, u32 base)
|
|
|
|
|
MMIO::InvalidWrite<u32>());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void UpdateInterrupts()
|
|
|
|
|
static void UpdateInterrupts()
|
|
|
|
|
{
|
|
|
|
|
const bool set_mask = (s_DISR.DEINT & s_DISR.DEINTMASK) || (s_DISR.TCINT & s_DISR.TCINTMASK) ||
|
|
|
|
|
(s_DISR.BRKINT & s_DISR.BRKINTMASK) ||
|
|
|
|
@ -645,7 +643,7 @@ void UpdateInterrupts()
|
|
|
|
|
CoreTiming::ForceExceptionCheck(50);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void GenerateDIInterrupt(DIInterruptType dvd_interrupt)
|
|
|
|
|
static void GenerateDIInterrupt(DIInterruptType dvd_interrupt)
|
|
|
|
|
{
|
|
|
|
|
switch (dvd_interrupt)
|
|
|
|
|
{
|
|
|
|
@ -705,41 +703,41 @@ void ClearInterrupt(DIInterruptType interrupt)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Checks the drive state to make sure a read-like command can be performed.
|
|
|
|
|
// If false is returned, SetHighError will have been called, and the caller
|
|
|
|
|
// If false is returned, SetDriveError will have been called, and the caller
|
|
|
|
|
// should issue a DEINT interrupt.
|
|
|
|
|
static bool CheckReadPreconditions()
|
|
|
|
|
{
|
|
|
|
|
if (!IsDiscInside()) // Implies ERROR_COVER or ERROR_NO_DISK
|
|
|
|
|
if (!IsDiscInside()) // Implies CoverOpened or NoMediumPresent
|
|
|
|
|
{
|
|
|
|
|
ERROR_LOG(DVDINTERFACE, "No disc inside.");
|
|
|
|
|
SetHighError(ERROR_NO_DISK_H);
|
|
|
|
|
SetDriveError(DriveError::MediumNotPresent);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
if ((s_error_code & LOW_ERROR_MASK) == ERROR_CHANGE_DISK)
|
|
|
|
|
if (s_drive_state == DriveState::DiscChangeDetected)
|
|
|
|
|
{
|
|
|
|
|
ERROR_LOG(DVDINTERFACE, "Disc changed (motor stopped).");
|
|
|
|
|
SetHighError(ERROR_MEDIUM);
|
|
|
|
|
SetDriveError(DriveError::MediumChanged);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
if ((s_error_code & LOW_ERROR_MASK) == ERROR_MOTOR_STOP_L)
|
|
|
|
|
if (s_drive_state == DriveState::MotorStopped)
|
|
|
|
|
{
|
|
|
|
|
ERROR_LOG(DVDINTERFACE, "Motor stopped.");
|
|
|
|
|
SetHighError(ERROR_MOTOR_STOP_H);
|
|
|
|
|
SetDriveError(DriveError::MotorStopped);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
if ((s_error_code & LOW_ERROR_MASK) == ERROR_NO_DISKID_L)
|
|
|
|
|
if (s_drive_state == DriveState::DiscIdNotRead)
|
|
|
|
|
{
|
|
|
|
|
ERROR_LOG(DVDINTERFACE, "Disc id not read.");
|
|
|
|
|
SetHighError(ERROR_NO_DISKID_H);
|
|
|
|
|
SetDriveError(DriveError::NoDiscID);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Iff false is returned, ScheduleEvent must be used to finish executing the command
|
|
|
|
|
bool ExecuteReadCommand(u64 dvd_offset, u32 output_address, u32 dvd_length, u32 output_length,
|
|
|
|
|
const DiscIO::Partition& partition, ReplyType reply_type,
|
|
|
|
|
DIInterruptType* interrupt_type)
|
|
|
|
|
static bool ExecuteReadCommand(u64 dvd_offset, u32 output_address, u32 dvd_length,
|
|
|
|
|
u32 output_length, const DiscIO::Partition& partition,
|
|
|
|
|
ReplyType reply_type, DIInterruptType* interrupt_type)
|
|
|
|
|
{
|
|
|
|
|
if (!CheckReadPreconditions())
|
|
|
|
|
{
|
|
|
|
@ -775,7 +773,7 @@ bool ExecuteReadCommand(u64 dvd_offset, u32 output_address, u32 dvd_length, u32
|
|
|
|
|
if (reply_type == ReplyType::IOS && partition == DiscIO::PARTITION_NONE &&
|
|
|
|
|
dvd_offset + dvd_length > 0x50000)
|
|
|
|
|
{
|
|
|
|
|
SetHighError(DVDInterface::ERROR_BLOCK_OOB);
|
|
|
|
|
SetDriveError(DriveError::BlockOOB);
|
|
|
|
|
*interrupt_type = DIInterruptType::DEINT;
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
@ -794,7 +792,7 @@ void ExecuteCommand(ReplyType reply_type)
|
|
|
|
|
|
|
|
|
|
// DVDLowRequestError needs access to the error code set by the previous command
|
|
|
|
|
if (static_cast<DICommand>(s_DICMDBUF[0] >> 24) != DICommand::RequestError)
|
|
|
|
|
SetHighError(0);
|
|
|
|
|
SetDriveError(DriveError::None);
|
|
|
|
|
|
|
|
|
|
switch (static_cast<DICommand>(s_DICMDBUF[0] >> 24))
|
|
|
|
|
{
|
|
|
|
@ -811,7 +809,7 @@ void ExecuteCommand(ReplyType reply_type)
|
|
|
|
|
// GC-only patched drive firmware command, used by libogc
|
|
|
|
|
case DICommand::Unknown55:
|
|
|
|
|
INFO_LOG(DVDINTERFACE, "SetExtension");
|
|
|
|
|
SetHighError(ERROR_INV_CMD);
|
|
|
|
|
SetDriveError(DriveError::InvalidCommand);
|
|
|
|
|
interrupt_type = DIInterruptType::DEINT;
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
@ -820,7 +818,7 @@ void ExecuteCommand(ReplyType reply_type)
|
|
|
|
|
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);
|
|
|
|
|
SetDriveError(DriveError::InvalidCommand);
|
|
|
|
|
interrupt_type = DIInterruptType::DEINT;
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
@ -838,7 +836,9 @@ void ExecuteCommand(ReplyType reply_type)
|
|
|
|
|
", DMABuffer = %08x, SrcLength = %08x, DMALength = %08x",
|
|
|
|
|
iDVDOffset, s_DIMAR, s_DICMDBUF[2], s_DILENGTH);
|
|
|
|
|
|
|
|
|
|
s_can_configure_dtk = false;
|
|
|
|
|
if (s_drive_state == DriveState::ReadyNoReadsMade)
|
|
|
|
|
SetDriveState(DriveState::Ready);
|
|
|
|
|
|
|
|
|
|
command_handled_by_thread =
|
|
|
|
|
ExecuteReadCommand(iDVDOffset, s_DIMAR, s_DICMDBUF[2], s_DILENGTH, DiscIO::PARTITION_NONE,
|
|
|
|
|
reply_type, &interrupt_type);
|
|
|
|
@ -847,20 +847,16 @@ void ExecuteCommand(ReplyType reply_type)
|
|
|
|
|
|
|
|
|
|
case 0x40: // Read DiscID
|
|
|
|
|
INFO_LOG(DVDINTERFACE, "Read DiscID: buffer %08x", s_DIMAR);
|
|
|
|
|
// TODO: It doesn't make sense to include ERROR_CHANGE_DISK here, as it implies that the drive
|
|
|
|
|
// is not spinning and reading the disc ID shouldn't change it. However, the Wii Menu breaks
|
|
|
|
|
// without it.
|
|
|
|
|
if ((s_error_code & LOW_ERROR_MASK) == ERROR_NO_DISKID_L ||
|
|
|
|
|
(s_error_code & LOW_ERROR_MASK) == ERROR_CHANGE_DISK)
|
|
|
|
|
if (s_drive_state == DriveState::DiscIdNotRead)
|
|
|
|
|
{
|
|
|
|
|
SetLowError(ERROR_READY);
|
|
|
|
|
SetDriveState(DriveState::ReadyNoReadsMade);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
else if (s_drive_state == DriveState::ReadyNoReadsMade)
|
|
|
|
|
{
|
|
|
|
|
// The first disc ID reading is required before DTK can be configured.
|
|
|
|
|
// If the disc ID is read again (or any other read occurs), it no longer can
|
|
|
|
|
// be configured.
|
|
|
|
|
s_can_configure_dtk = false;
|
|
|
|
|
SetDriveState(DriveState::Ready);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
command_handled_by_thread = ExecuteReadCommand(
|
|
|
|
@ -897,33 +893,33 @@ void ExecuteCommand(ReplyType reply_type)
|
|
|
|
|
ERROR_LOG(DVDINTERFACE, "Unknown 0xAD subcommand in %08x", s_DICMDBUF[0]);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
SetHighError(ERROR_INV_CMD);
|
|
|
|
|
SetDriveError(DriveError::InvalidCommand);
|
|
|
|
|
interrupt_type = DIInterruptType::DEINT;
|
|
|
|
|
break;
|
|
|
|
|
// Wii-exclusive
|
|
|
|
|
case DICommand::ReadDVD:
|
|
|
|
|
ERROR_LOG(DVDINTERFACE, "DVDLowReadDvd");
|
|
|
|
|
SetHighError(ERROR_INV_CMD);
|
|
|
|
|
SetDriveError(DriveError::InvalidCommand);
|
|
|
|
|
interrupt_type = DIInterruptType::DEINT;
|
|
|
|
|
break;
|
|
|
|
|
// Wii-exclusive
|
|
|
|
|
case DICommand::ReadDVDConfig:
|
|
|
|
|
ERROR_LOG(DVDINTERFACE, "DVDLowReadDvdConfig");
|
|
|
|
|
SetHighError(ERROR_INV_CMD);
|
|
|
|
|
SetDriveError(DriveError::InvalidCommand);
|
|
|
|
|
interrupt_type = DIInterruptType::DEINT;
|
|
|
|
|
break;
|
|
|
|
|
// Wii-exclusive
|
|
|
|
|
case DICommand::StopLaser:
|
|
|
|
|
ERROR_LOG(DVDINTERFACE, "DVDLowStopLaser");
|
|
|
|
|
DolphinAnalytics::Instance().ReportGameQuirk(GameQuirk::USES_DVD_LOW_STOP_LASER);
|
|
|
|
|
SetHighError(ERROR_INV_CMD);
|
|
|
|
|
SetDriveError(DriveError::InvalidCommand);
|
|
|
|
|
interrupt_type = DIInterruptType::DEINT;
|
|
|
|
|
break;
|
|
|
|
|
// Wii-exclusive
|
|
|
|
|
case DICommand::Offset:
|
|
|
|
|
ERROR_LOG(DVDINTERFACE, "DVDLowOffset");
|
|
|
|
|
DolphinAnalytics::Instance().ReportGameQuirk(GameQuirk::USES_DVD_LOW_OFFSET);
|
|
|
|
|
SetHighError(ERROR_INV_CMD);
|
|
|
|
|
SetDriveError(DriveError::InvalidCommand);
|
|
|
|
|
interrupt_type = DIInterruptType::DEINT;
|
|
|
|
|
break;
|
|
|
|
|
// Wii-exclusive
|
|
|
|
@ -943,36 +939,45 @@ void ExecuteCommand(ReplyType reply_type)
|
|
|
|
|
case DICommand::RequestDiscStatus:
|
|
|
|
|
ERROR_LOG(DVDINTERFACE, "DVDLowRequestDiscStatus");
|
|
|
|
|
DolphinAnalytics::Instance().ReportGameQuirk(GameQuirk::USES_DVD_LOW_REQUEST_DISC_STATUS);
|
|
|
|
|
SetHighError(ERROR_INV_CMD);
|
|
|
|
|
SetDriveError(DriveError::InvalidCommand);
|
|
|
|
|
interrupt_type = DIInterruptType::DEINT;
|
|
|
|
|
break;
|
|
|
|
|
// Wii-exclusive
|
|
|
|
|
case DICommand::RequestRetryNumber:
|
|
|
|
|
ERROR_LOG(DVDINTERFACE, "DVDLowRequestRetryNumber");
|
|
|
|
|
DolphinAnalytics::Instance().ReportGameQuirk(GameQuirk::USES_DVD_LOW_REQUEST_RETRY_NUMBER);
|
|
|
|
|
SetHighError(ERROR_INV_CMD);
|
|
|
|
|
SetDriveError(DriveError::InvalidCommand);
|
|
|
|
|
interrupt_type = DIInterruptType::DEINT;
|
|
|
|
|
break;
|
|
|
|
|
// Wii-exclusive
|
|
|
|
|
case DICommand::SetMaximumRotation:
|
|
|
|
|
ERROR_LOG(DVDINTERFACE, "DVDLowSetMaximumRotation");
|
|
|
|
|
SetHighError(ERROR_INV_CMD);
|
|
|
|
|
SetDriveError(DriveError::InvalidCommand);
|
|
|
|
|
interrupt_type = DIInterruptType::DEINT;
|
|
|
|
|
break;
|
|
|
|
|
// Wii-exclusive
|
|
|
|
|
case DICommand::SerMeasControl:
|
|
|
|
|
ERROR_LOG(DVDINTERFACE, "DVDLowSerMeasControl");
|
|
|
|
|
DolphinAnalytics::Instance().ReportGameQuirk(GameQuirk::USES_DVD_LOW_SER_MEAS_CONTROL);
|
|
|
|
|
SetHighError(ERROR_INV_CMD);
|
|
|
|
|
SetDriveError(DriveError::InvalidCommand);
|
|
|
|
|
interrupt_type = DIInterruptType::DEINT;
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
// Used by both GC and Wii
|
|
|
|
|
case DICommand::RequestError:
|
|
|
|
|
INFO_LOG(DVDINTERFACE, "Requesting error... (0x%08x)", s_error_code);
|
|
|
|
|
s_DIIMMBUF = s_error_code;
|
|
|
|
|
SetHighError(0);
|
|
|
|
|
{
|
|
|
|
|
u32 drive_state;
|
|
|
|
|
if (s_drive_state == DriveState::Ready)
|
|
|
|
|
drive_state = 0;
|
|
|
|
|
else
|
|
|
|
|
drive_state = static_cast<u32>(s_drive_state) - 1;
|
|
|
|
|
|
|
|
|
|
const u32 result = (drive_state << 24) | static_cast<u32>(s_error_code);
|
|
|
|
|
INFO_LOG(DVDINTERFACE, "Requesting error... (0x%08x)", result);
|
|
|
|
|
s_DIIMMBUF = result;
|
|
|
|
|
SetDriveError(DriveError::None);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Audio Stream (Immediate). Only used by some GC games, but does exist on the Wii
|
|
|
|
|
// (command_0 >> 16) & 0xFF = Subcommand
|
|
|
|
@ -983,7 +988,6 @@ void ExecuteCommand(ReplyType reply_type)
|
|
|
|
|
if (!CheckReadPreconditions())
|
|
|
|
|
{
|
|
|
|
|
ERROR_LOG(DVDINTERFACE, "Cannot play audio (command %08x)", s_DICMDBUF[0]);
|
|
|
|
|
SetHighError(ERROR_AUDIO_BUF);
|
|
|
|
|
interrupt_type = DIInterruptType::DEINT;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
@ -992,12 +996,13 @@ void ExecuteCommand(ReplyType reply_type)
|
|
|
|
|
ERROR_LOG(DVDINTERFACE,
|
|
|
|
|
"Attempted to change playing audio while audio is disabled! (%08x %08x %08x)",
|
|
|
|
|
s_DICMDBUF[0], s_DICMDBUF[1], s_DICMDBUF[2]);
|
|
|
|
|
SetHighError(ERROR_AUDIO_BUF);
|
|
|
|
|
SetDriveError(DriveError::NoAudioBuf);
|
|
|
|
|
interrupt_type = DIInterruptType::DEINT;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
s_can_configure_dtk = false;
|
|
|
|
|
if (s_drive_state == DriveState::ReadyNoReadsMade)
|
|
|
|
|
SetDriveState(DriveState::Ready);
|
|
|
|
|
|
|
|
|
|
switch ((s_DICMDBUF[0] >> 16) & 0xFF)
|
|
|
|
|
{
|
|
|
|
@ -1035,7 +1040,7 @@ void ExecuteCommand(ReplyType reply_type)
|
|
|
|
|
default:
|
|
|
|
|
ERROR_LOG(DVDINTERFACE, "Invalid audio command! (%08x %08x %08x)", s_DICMDBUF[0],
|
|
|
|
|
s_DICMDBUF[1], s_DICMDBUF[2]);
|
|
|
|
|
SetHighError(ERROR_INV_AUDIO);
|
|
|
|
|
SetDriveError(DriveError::InvalidAudioCommand);
|
|
|
|
|
interrupt_type = DIInterruptType::DEINT;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
@ -1045,10 +1050,17 @@ void ExecuteCommand(ReplyType reply_type)
|
|
|
|
|
// Request Audio Status (Immediate). Only used by some GC games, but does exist on the Wii
|
|
|
|
|
case DICommand::RequestAudioStatus:
|
|
|
|
|
{
|
|
|
|
|
if (!CheckReadPreconditions())
|
|
|
|
|
{
|
|
|
|
|
ERROR_LOG(DVDINTERFACE, "Attempted to request audio status in an invalid state!");
|
|
|
|
|
interrupt_type = DIInterruptType::DEINT;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!s_enable_dtk)
|
|
|
|
|
{
|
|
|
|
|
ERROR_LOG(DVDINTERFACE, "Attempted to request audio status while audio is disabled!");
|
|
|
|
|
SetHighError(ERROR_AUDIO_BUF);
|
|
|
|
|
SetDriveError(DriveError::NoAudioBuf);
|
|
|
|
|
interrupt_type = DIInterruptType::DEINT;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
@ -1082,7 +1094,7 @@ void ExecuteCommand(ReplyType reply_type)
|
|
|
|
|
default:
|
|
|
|
|
ERROR_LOG(DVDINTERFACE, "Invalid audio status command! (%08x %08x %08x)", s_DICMDBUF[0],
|
|
|
|
|
s_DICMDBUF[1], s_DICMDBUF[2]);
|
|
|
|
|
SetHighError(ERROR_INV_AUDIO);
|
|
|
|
|
SetDriveError(DriveError::InvalidAudioCommand);
|
|
|
|
|
interrupt_type = DIInterruptType::DEINT;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
@ -1096,7 +1108,12 @@ void ExecuteCommand(ReplyType reply_type)
|
|
|
|
|
const bool kill = (s_DICMDBUF[0] & (1 << 20));
|
|
|
|
|
INFO_LOG(DVDINTERFACE, "DVDLowStopMotor%s%s", eject ? " eject" : "", kill ? " kill!" : "");
|
|
|
|
|
|
|
|
|
|
SetLowError(ERROR_MOTOR_STOP_L);
|
|
|
|
|
if (s_drive_state == DriveState::Ready || s_drive_state == DriveState::ReadyNoReadsMade ||
|
|
|
|
|
s_drive_state == DriveState::DiscIdNotRead)
|
|
|
|
|
{
|
|
|
|
|
SetDriveState(DriveState::MotorStopped);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const bool force_eject = eject && !kill;
|
|
|
|
|
|
|
|
|
|
if (Config::Get(Config::MAIN_AUTO_DISC_CHANGE) && !Movie::IsPlayingInput() &&
|
|
|
|
@ -1121,22 +1138,31 @@ void ExecuteCommand(ReplyType reply_type)
|
|
|
|
|
// The link is dead, but you can access the page using the Wayback Machine at archive.org.
|
|
|
|
|
|
|
|
|
|
// This command can only be used immediately after reading the disc ID, before any other
|
|
|
|
|
// reads. Too early, and you get ERROR_NO_DISKID. Too late, and you get ERROR_INV_PERIOD.
|
|
|
|
|
if (!s_can_configure_dtk)
|
|
|
|
|
// reads. Too early, and you get NoDiscID. Too late, and you get InvalidPeriod.
|
|
|
|
|
if (!CheckReadPreconditions())
|
|
|
|
|
{
|
|
|
|
|
ERROR_LOG(DVDINTERFACE, "Attempted to change DTK configuration after a read has been made!");
|
|
|
|
|
SetHighError(ERROR_INV_PERIOD);
|
|
|
|
|
ERROR_LOG(DVDINTERFACE, "Attempted to change DTK configuration in an invalid state!");
|
|
|
|
|
interrupt_type = DIInterruptType::DEINT;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (s_drive_state == DriveState::Ready)
|
|
|
|
|
{
|
|
|
|
|
ERROR_LOG(DVDINTERFACE, "Attempted to change DTK configuration after a read has been made!");
|
|
|
|
|
SetDriveError(DriveError::InvalidPeriod);
|
|
|
|
|
interrupt_type = DIInterruptType::DEINT;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Note that this can be called multiple times, as long as the drive is in the ReadyNoReadsMade
|
|
|
|
|
// state. Calling it does not exit that state.
|
|
|
|
|
AudioBufferConfig((s_DICMDBUF[0] >> 16) & 1, s_DICMDBUF[0] & 0xf);
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
// GC-only patched drive firmware command, used by libogc
|
|
|
|
|
case DICommand::UnknownEE:
|
|
|
|
|
INFO_LOG(DVDINTERFACE, "SetStatus");
|
|
|
|
|
SetHighError(ERROR_INV_CMD);
|
|
|
|
|
SetDriveError(DriveError::InvalidCommand);
|
|
|
|
|
interrupt_type = DIInterruptType::DEINT;
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
@ -1146,7 +1172,7 @@ void ExecuteCommand(ReplyType reply_type)
|
|
|
|
|
// 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]);
|
|
|
|
|
SetHighError(ERROR_INV_CMD);
|
|
|
|
|
SetDriveError(DriveError::InvalidCommand);
|
|
|
|
|
interrupt_type = DIInterruptType::DEINT;
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
@ -1175,7 +1201,7 @@ void ExecuteCommand(ReplyType reply_type)
|
|
|
|
|
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]);
|
|
|
|
|
SetHighError(ERROR_INV_CMD);
|
|
|
|
|
SetDriveError(DriveError::InvalidCommand);
|
|
|
|
|
interrupt_type = DIInterruptType::DEINT;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
@ -1193,7 +1219,7 @@ void PerformDecryptingRead(u32 position, u32 length, u32 output_address,
|
|
|
|
|
const DiscIO::Partition& partition, ReplyType reply_type)
|
|
|
|
|
{
|
|
|
|
|
DIInterruptType interrupt_type = DIInterruptType::TCINT;
|
|
|
|
|
s_can_configure_dtk = false;
|
|
|
|
|
SetDriveState(DriveState::Ready);
|
|
|
|
|
|
|
|
|
|
const bool command_handled_by_thread =
|
|
|
|
|
ExecuteReadCommand(static_cast<u64>(position) << 2, output_address, length, length, partition,
|
|
|
|
@ -1218,7 +1244,7 @@ void AudioBufferConfig(bool enable_dtk, u8 dtk_buffer_length)
|
|
|
|
|
INFO_LOG(DVDINTERFACE, "DTK disabled");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
u64 PackFinishExecutingCommandUserdata(ReplyType reply_type, DIInterruptType interrupt_type)
|
|
|
|
|
static u64 PackFinishExecutingCommandUserdata(ReplyType reply_type, DIInterruptType interrupt_type)
|
|
|
|
|
{
|
|
|
|
|
return (static_cast<u64>(reply_type) << 32) + static_cast<u32>(interrupt_type);
|
|
|
|
|
}
|
|
|
|
@ -1230,16 +1256,14 @@ void FinishExecutingCommandCallback(u64 userdata, s64 cycles_late)
|
|
|
|
|
FinishExecutingCommand(reply_type, interrupt_type, cycles_late);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SetLowError(u32 low_error)
|
|
|
|
|
void SetDriveState(DriveState state)
|
|
|
|
|
{
|
|
|
|
|
DEBUG_ASSERT((low_error & HIGH_ERROR_MASK) == 0);
|
|
|
|
|
s_error_code = (s_error_code & HIGH_ERROR_MASK) | (low_error & LOW_ERROR_MASK);
|
|
|
|
|
s_drive_state = state;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SetHighError(u32 high_error)
|
|
|
|
|
void SetDriveError(DriveError error)
|
|
|
|
|
{
|
|
|
|
|
DEBUG_ASSERT((high_error & LOW_ERROR_MASK) == 0);
|
|
|
|
|
s_error_code = (s_error_code & LOW_ERROR_MASK) | (high_error & HIGH_ERROR_MASK);
|
|
|
|
|
s_error_code = error;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void FinishExecutingCommand(ReplyType reply_type, DIInterruptType interrupt_type, s64 cycles_late,
|
|
|
|
@ -1254,8 +1278,11 @@ void FinishExecutingCommand(ReplyType reply_type, DIInterruptType interrupt_type
|
|
|
|
|
else if (reply_type == ReplyType::Interrupt || reply_type == ReplyType::IOS)
|
|
|
|
|
transfer_size = s_DILENGTH;
|
|
|
|
|
|
|
|
|
|
s_DIMAR += transfer_size;
|
|
|
|
|
s_DILENGTH -= transfer_size;
|
|
|
|
|
if (interrupt_type == DIInterruptType::TCINT)
|
|
|
|
|
{
|
|
|
|
|
s_DIMAR += transfer_size;
|
|
|
|
|
s_DILENGTH -= transfer_size;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
switch (reply_type)
|
|
|
|
|
{
|
|
|
|
@ -1290,8 +1317,8 @@ void FinishExecutingCommand(ReplyType reply_type, DIInterruptType interrupt_type
|
|
|
|
|
|
|
|
|
|
// Determines from a given read request how much of the request is buffered,
|
|
|
|
|
// and how much is required to be read from disc.
|
|
|
|
|
void ScheduleReads(u64 offset, u32 length, const DiscIO::Partition& partition, u32 output_address,
|
|
|
|
|
ReplyType reply_type)
|
|
|
|
|
static void ScheduleReads(u64 offset, u32 length, const DiscIO::Partition& partition,
|
|
|
|
|
u32 output_address, ReplyType reply_type)
|
|
|
|
|
{
|
|
|
|
|
// The drive continues to read 1 MiB beyond the last read position when idle.
|
|
|
|
|
// If a future read falls within this window, part of the read may be returned
|
|
|
|
|