Merge pull request #8571 from Pokechu22/di-interrupts

Fix gamecube games not noticing disc changes
This commit is contained in:
JosJuice 2020-08-11 20:03:41 +02:00 committed by GitHub
commit 07a0d44b36
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 187 additions and 140 deletions

View File

@ -250,8 +250,9 @@ bool CBoot::DVDReadDiscID(const DiscIO::VolumeDisc& disc, u32 output_address)
if (!disc.Read(0, buffer.size(), buffer.data(), DiscIO::PARTITION_NONE))
return false;
Memory::CopyToEmu(output_address, buffer.data(), buffer.size());
// Clear ERROR_NO_DISKID_L, probably should check if that's currently set
DVDInterface::SetLowError(DVDInterface::ERROR_READY);
// Transition out of the DiscIdNotRead state (which the drive should be in at this point,
// on the assumption that this is only used for the first read)
DVDInterface::SetDriveState(DVDInterface::DriveState::ReadyNoReadsMade);
return true;
}

View File

@ -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

View File

@ -51,33 +51,41 @@ enum class DICommand : u8
UnknownEE = 0xee,
};
// "low" error codes
constexpr u32 ERROR_READY = 0x0000000; // Ready.
constexpr u32 ERROR_COVER = 0x01000000; // Cover is opened.
constexpr u32 ERROR_CHANGE_DISK = 0x02000000; // Disk change.
constexpr u32 ERROR_NO_DISK_L = 0x03000000; // No disk.
constexpr u32 ERROR_MOTOR_STOP_L = 0x04000000; // Motor stop.
constexpr u32 ERROR_NO_DISKID_L = 0x05000000; // Disk ID not read.
constexpr u32 LOW_ERROR_MASK = 0xff000000;
// Disc drive state.
// Reported in error codes as 0 for Ready, and value-1 for the rest
// (i.e. Ready and ReadyNoReadsMade are both reported as 0)
enum class DriveState : u8
{
Ready = 0,
ReadyNoReadsMade = 1,
CoverOpened = 2,
DiscChangeDetected = 3,
NoMediumPresent = 4,
MotorStopped = 5,
DiscIdNotRead = 6
};
// "high" error codes
constexpr u32 ERROR_NONE = 0x000000; // No error.
constexpr u32 ERROR_MOTOR_STOP_H = 0x020400; // Motor stopped.
constexpr u32 ERROR_NO_DISKID_H = 0x020401; // Disk ID not read.
constexpr u32 ERROR_NO_DISK_H = 0x023a00; // Medium not present / Cover opened.
constexpr u32 ERROR_SEEK_NDONE = 0x030200; // No seek complete.
constexpr u32 ERROR_READ = 0x031100; // Unrecovered read error.
constexpr u32 ERROR_PROTOCOL = 0x040800; // Transfer protocol error.
constexpr u32 ERROR_INV_CMD = 0x052000; // Invalid command operation code.
constexpr u32 ERROR_AUDIO_BUF = 0x052001; // Audio Buffer not set.
constexpr u32 ERROR_BLOCK_OOB = 0x052100; // Logical block address out of bounds.
constexpr u32 ERROR_INV_FIELD = 0x052400; // Invalid field in command packet.
constexpr u32 ERROR_INV_AUDIO = 0x052401; // Invalid audio command.
constexpr u32 ERROR_INV_PERIOD = 0x052402; // Configuration out of permitted period.
constexpr u32 ERROR_END_USR_AREA = 0x056300; // End of user area encountered on this track.
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;
// Actual drive error codes, which fill the remaining 3 bytes
// Numbers more or less match a SCSI sense key (1 nybble) followed by SCSI ASC/ASCQ (2 bytes).
enum class DriveError : u32
{
None = 0x00000, // No error.
MotorStopped = 0x20400, // Motor stopped.
NoDiscID = 0x20401, // Disk ID not read.
MediumNotPresent = 0x23a00, // Medium not present / Cover opened.
SeekNotDone = 0x30200, // No seek complete.
ReadError = 0x31100, // Unrecovered read error.
ProtocolError = 0x40800, // Transfer protocol error.
InvalidCommand = 0x52000, // Invalid command operation code.
NoAudioBuf = 0x52001, // Audio Buffer not set.
BlockOOB = 0x52100, // Logical block address out of bounds.
InvalidField = 0x52400, // Invalid field in command packet.
InvalidAudioCommand = 0x52401, // Invalid audio command.
InvalidPeriod = 0x52402, // Configuration out of permitted period.
EndOfUserArea = 0x56300, // End of user area encountered on this track.
MediumChanged = 0x62800, // Medium may have changed.
MediumRemovalRequest = 0xb5a01, // Operator medium removal request.
};
enum class DIInterruptType : int
{
@ -102,7 +110,7 @@ enum class EjectCause
};
void Init();
void Reset(bool spinup = true);
void ResetDrive(bool spinup);
void Shutdown();
void DoState(PointerWrap& p);
@ -130,8 +138,8 @@ void PerformDecryptingRead(u32 position, u32 length, u32 output_address,
// Exposed for use by emulated BS2; does not perform any checks on drive state
void AudioBufferConfig(bool enable_dtk, u8 dtk_buffer_length);
void SetLowError(u32 low_error);
void SetHighError(u32 high_error);
void SetDriveState(DriveState state);
void SetDriveError(DriveError error);
// Used by DVDThread
void FinishExecutingCommand(ReplyType reply_type, DIInterruptType interrupt_type, s64 cycles_late,

View File

@ -348,7 +348,7 @@ static void FinishRead(u64 id, s64 cycles_late)
PanicAlertT("The disc could not be read (at 0x%" PRIx64 " - 0x%" PRIx64 ").",
request.dvd_offset, request.dvd_offset + request.length);
DVDInterface::SetHighError(DVDInterface::ERROR_BLOCK_OOB);
DVDInterface::SetDriveError(DVDInterface::DriveError::BlockOOB);
interrupt = DVDInterface::DIInterruptType::DEINT;
}
else

View File

@ -11,6 +11,7 @@
#include "Common/CommonTypes.h"
#include "Core/Core.h"
#include "Core/CoreTiming.h"
#include "Core/HW/DVD/DVDInterface.h"
#include "Core/HW/MMIO.h"
#include "Core/HW/SystemTimers.h"
#include "Core/IOS/IOS.h"
@ -111,8 +112,18 @@ void RegisterMMIO(MMIO::Mapping* mmio, u32 base)
MMIO::ComplexWrite<u32>(
[](u32, u32 val) { WARN_LOG(PROCESSORINTERFACE, "Fifo reset (%08x)", val); }));
mmio->Register(base | PI_RESET_CODE, MMIO::DirectRead<u32>(&m_ResetCode),
MMIO::DirectWrite<u32>(&m_ResetCode));
mmio->Register(base | PI_RESET_CODE, MMIO::ComplexRead<u32>([](u32) {
DEBUG_LOG(PROCESSORINTERFACE, "Read PI_RESET_CODE: %08x", m_ResetCode);
return m_ResetCode;
}),
MMIO::ComplexWrite<u32>([](u32, u32 val) {
m_ResetCode = val;
INFO_LOG(PROCESSORINTERFACE, "Wrote PI_RESET_CODE: %08x", m_ResetCode);
if (~m_ResetCode & 0x4)
{
DVDInterface::ResetDrive(true);
}
}));
mmio->Register(base | PI_FLIPPER_REV, MMIO::DirectRead<u32>(&m_FlipperRev),
MMIO::InvalidWrite<u32>());

View File

@ -266,9 +266,9 @@ std::optional<DI::DIResult> DI::StartIOCtl(const IOCtlRequest& request)
return DIResult::Success;
case DIIoctl::DVDLowReset:
{
const bool spinup = Memory::Read_U32(request.address + 4);
const bool spinup = Memory::Read_U32(request.buffer_in + 4);
INFO_LOG(IOS_DI, "DVDLowReset %s spinup", spinup ? "with" : "without");
DVDInterface::Reset(spinup);
DVDInterface::ResetDrive(spinup);
ResetDIRegisters();
return DIResult::Success;
}

View File

@ -37,7 +37,7 @@ static void ReinitHardware()
// HACK However, resetting DI will reset the DTK config, which is set by the system menu
// (and not by MIOS), causing games that use DTK to break. Perhaps MIOS doesn't actually
// reset DI fully, in such a way that the DTK config isn't cleared?
// DVDInterface::Reset();
// DVDInterface::ResetDrive(true);
PowerPC::Reset();
Wiimote::ResetAllWiimotes();
// Note: this is specific to Dolphin and is required because we initialised it in Wii mode.

View File

@ -74,7 +74,7 @@ static Common::Event g_compressAndDumpStateSyncEvent;
static std::thread g_save_thread;
// Don't forget to increase this after doing changes on the savestate system
constexpr u32 STATE_VERSION = 121; // Last changed in PR 8988
constexpr u32 STATE_VERSION = 122; // Last changed in PR 8571
// Maps savestate versions to Dolphin versions.
// Versions after 42 don't need to be added to this list,