CDROM: Simulate the time it takes to change speeds

This commit is contained in:
Connor McLaughlin 2021-06-14 14:55:37 +10:00
parent db5be6c70c
commit f4da56efea
2 changed files with 116 additions and 35 deletions

View File

@ -18,6 +18,11 @@ Log_SetChannel(CDROM);
#include <emmintrin.h> #include <emmintrin.h>
#endif #endif
static constexpr std::array<const char*, 15> s_drive_state_names = {
{"Idle", "Opening Shell", "Resetting", "Seeking (Physical)", "Seeking (Logical)", "Reading ID", "Reading TOC",
"Reading", "Playing", "Pausing", "Stopping", "Changing Session", "Spinning Up", "Seeking (Implicit)",
"Changing Speed/Implicit TOC Read"}};
struct CommandInfo struct CommandInfo
{ {
const char* name; const char* name;
@ -120,7 +125,6 @@ void CDROM::Reset()
m_secondary_status.shell_open = !CanReadMedia(); m_secondary_status.shell_open = !CanReadMedia();
m_mode.bits = 0; m_mode.bits = 0;
m_mode.read_raw_sector = true; m_mode.read_raw_sector = true;
m_current_double_speed = false;
m_interrupt_enable_register = INTERRUPT_REGISTER_MASK; m_interrupt_enable_register = INTERRUPT_REGISTER_MASK;
m_interrupt_flag_register = 0; m_interrupt_flag_register = 0;
m_pending_async_interrupt = 0; m_pending_async_interrupt = 0;
@ -168,8 +172,10 @@ void CDROM::Reset()
SetHoldPosition(0, true); SetHoldPosition(0, true);
} }
void CDROM::SoftReset() void CDROM::SoftReset(TickCount ticks_late)
{ {
const bool was_double_speed = m_mode.double_speed;
ClearCommandSecondResponse(); ClearCommandSecondResponse();
ClearDriveState(); ClearDriveState();
m_secondary_status.bits = 0; m_secondary_status.bits = 0;
@ -203,15 +209,38 @@ void CDROM::SoftReset()
UpdateStatusRegister(); UpdateStatusRegister();
if (HasMedia())
{
const TickCount toc_read_ticks = GetTicksForTOCRead();
const TickCount speed_change_ticks = was_double_speed ? GetTicksForSpeedChange() : 0;
const TickCount seek_ticks = (m_current_lba != 0) ? GetTicksForSeek(0) : 0;
const TickCount total_ticks = toc_read_ticks + speed_change_ticks + seek_ticks - ticks_late;
if (was_double_speed)
{
Log_DevPrintf("CDROM was double speed on reset, switching to single speed in %d ticks, reading TOC in %d ticks, "
"seeking in %d ticks",
speed_change_ticks, toc_read_ticks, seek_ticks);
}
else
{
Log_DevPrintf("CDROM reading TOC on reset in %d ticks and seeking in %d ticks", toc_read_ticks, seek_ticks);
}
if (m_current_lba != 0) if (m_current_lba != 0)
{ {
const TickCount seek_ticks = GetTicksForSeek(0);
m_drive_state = DriveState::SeekingImplicit; m_drive_state = DriveState::SeekingImplicit;
m_drive_event->SetIntervalAndSchedule(seek_ticks); m_drive_event->SetIntervalAndSchedule(total_ticks);
m_reader.QueueReadSector(0); m_reader.QueueReadSector(0);
m_seek_start_lba = m_current_lba; m_seek_start_lba = m_current_lba;
m_seek_end_lba = 0; m_seek_end_lba = 0;
} }
else
{
m_drive_state = DriveState::ChangingSpeedOrTOCRead;
m_drive_event->Schedule(total_ticks);
}
}
} }
bool CDROM::DoState(StateWrapper& sw) bool CDROM::DoState(StateWrapper& sw)
@ -222,7 +251,10 @@ bool CDROM::DoState(StateWrapper& sw)
sw.Do(&m_status.bits); sw.Do(&m_status.bits);
sw.Do(&m_secondary_status.bits); sw.Do(&m_secondary_status.bits);
sw.Do(&m_mode.bits); sw.Do(&m_mode.bits);
sw.Do(&m_current_double_speed);
bool current_double_speed = m_mode.double_speed;
sw.Do(&current_double_speed);
sw.Do(&m_interrupt_enable_register); sw.Do(&m_interrupt_enable_register);
sw.Do(&m_interrupt_flag_register); sw.Do(&m_interrupt_flag_register);
sw.Do(&m_pending_async_interrupt); sw.Do(&m_pending_async_interrupt);
@ -734,7 +766,7 @@ TickCount CDROM::GetTicksForRead()
return m_mode.double_speed ? (tps / 150) : (tps / 75); return m_mode.double_speed ? (tps / 150) : (tps / 75);
} }
TickCount CDROM::GetTicksForSeek(CDImage::LBA new_lba) TickCount CDROM::GetTicksForSeek(CDImage::LBA new_lba, bool ignore_speed_change)
{ {
static constexpr TickCount MIN_TICKS = 20000; static constexpr TickCount MIN_TICKS = 20000;
@ -747,7 +779,7 @@ TickCount CDROM::GetTicksForSeek(CDImage::LBA new_lba)
else else
UpdatePhysicalPosition(); UpdatePhysicalPosition();
const TickCount tps = System::GetTicksPerSecond(); const TickCount tps = System::MASTER_CLOCK;
const CDImage::LBA current_lba = m_secondary_status.motor_on ? (IsSeeking() ? m_seek_end_lba : m_physical_lba) : 0; const CDImage::LBA current_lba = m_secondary_status.motor_on ? (IsSeeking() ? m_seek_end_lba : m_physical_lba) : 0;
const u32 lba_diff = static_cast<u32>((new_lba > current_lba) ? (new_lba - current_lba) : (current_lba - new_lba)); const u32 lba_diff = static_cast<u32>((new_lba > current_lba) ? (new_lba - current_lba) : (current_lba - new_lba));
@ -762,7 +794,7 @@ TickCount CDROM::GetTicksForSeek(CDImage::LBA new_lba)
if (lba_diff < 32) if (lba_diff < 32)
{ {
ticks += static_cast<u32>(GetTicksForRead()) * std::min<u32>(BASE_SECTORS_PER_TRACK, lba_diff); ticks += static_cast<u32>(GetTicksForRead()) * std::min<u32>(BASE_SECTORS_PER_TRACK, lba_diff) * 2;
} }
else else
{ {
@ -780,26 +812,44 @@ TickCount CDROM::GetTicksForSeek(CDImage::LBA new_lba)
ticks += static_cast<u32>((u64(tps) * 300) / 1000); ticks += static_cast<u32>((u64(tps) * 300) / 1000);
} }
if (m_mode.double_speed != m_current_double_speed) if (m_drive_state == DriveState::ChangingSpeedOrTOCRead && !ignore_speed_change)
{ {
Log_DevPrintf("Switched from %s to %s speed", m_current_double_speed ? "double" : "single", // we're still reading the TOC, so add that time in
m_mode.double_speed ? "double" : "single"); const TickCount remaining_change_ticks = m_drive_event->GetTicksUntilNextExecution();
m_current_double_speed = m_mode.double_speed; ticks += remaining_change_ticks;
// Approximate time for the motor to change speed? Log_DevPrintf("Seek time for %u LBAs: %d (%d for speed change/implicit TOC read)", lba_diff, ticks,
ticks += static_cast<u32>(static_cast<double>(tps) * 0.1); remaining_change_ticks);
}
else
{
Log_DevPrintf("Seek time for %u LBAs: %d", lba_diff, ticks);
} }
if (g_settings.cdrom_seek_speedup > 1) if (g_settings.cdrom_seek_speedup > 1)
ticks = std::min<u32>(ticks / g_settings.cdrom_seek_speedup, MIN_TICKS); ticks = std::min<u32>(ticks / g_settings.cdrom_seek_speedup, MIN_TICKS);
Log_DevPrintf("Seek time for %u LBAs: %u", lba_diff, ticks); return System::ScaleTicksToOverclock(static_cast<TickCount>(ticks));
return static_cast<u32>(ticks);
} }
TickCount CDROM::GetTicksForStop(bool motor_was_on) TickCount CDROM::GetTicksForStop(bool motor_was_on)
{ {
return motor_was_on ? (m_mode.double_speed ? 25000000 : 13000000) : 7000; return System::ScaleTicksToOverclock(motor_was_on ? (m_mode.double_speed ? 25000000 : 13000000) : 7000);
}
TickCount CDROM::GetTicksForSpeedChange()
{
static constexpr u32 ticks_single_to_double = static_cast<u32>(0.8 * static_cast<double>(System::MASTER_CLOCK));
static constexpr u32 ticks_double_to_single = static_cast<u32>(1.0 * static_cast<double>(System::MASTER_CLOCK));
return System::ScaleTicksToOverclock(m_mode.double_speed ? ticks_single_to_double : ticks_double_to_single);
}
TickCount CDROM::GetTicksForTOCRead()
{
if (!HasMedia())
return 0;
return System::GetTicksPerSecond();
} }
CDImage::LBA CDROM::GetNextSectorToBeRead() CDImage::LBA CDROM::GetNextSectorToBeRead()
@ -940,7 +990,7 @@ void CDROM::ExecuteCommand(TickCount ticks_late)
{ {
SendACKAndStat(); SendACKAndStat();
SetHoldPosition(0, true); SetHoldPosition(0, true);
QueueCommandSecondResponse(Command::ReadTOC, System::GetTicksPerSecond() / 2); // half a second QueueCommandSecondResponse(Command::ReadTOC, GetTicksForTOCRead());
} }
EndCommand(); EndCommand();
@ -963,11 +1013,32 @@ void CDROM::ExecuteCommand(TickCount ticks_late)
case Command::Setmode: case Command::Setmode:
{ {
const u8 mode = m_param_fifo.Peek(0); const u8 mode = m_param_fifo.Peek(0);
Log_DebugPrintf("CDROM setmode command 0x%02X", ZeroExtend32(mode)); const bool speed_change = (mode & 0x80) != (m_mode.bits & 0x80);
Log_DevPrintf("CDROM setmode command 0x%02X", ZeroExtend32(mode));
m_mode.bits = mode; m_mode.bits = mode;
SendACKAndStat(); SendACKAndStat();
EndCommand(); EndCommand();
if (speed_change)
{
// if we're seeking or reading, we need to add time to the current seek/read
const TickCount change_ticks = GetTicksForSpeedChange();
if (m_drive_state != DriveState::Idle)
{
Log_DevPrintf("Drive is %s, delaying event by %d ticks for speed change to %s-speed",
s_drive_state_names[static_cast<u8>(m_drive_state)], change_ticks,
m_mode.double_speed ? "double" : "single");
m_drive_event->Delay(change_ticks);
}
else
{
Log_DevPrintf("Drive is idle, speed change takes %d ticks", change_ticks);
m_drive_state = DriveState::ChangingSpeedOrTOCRead;
m_drive_event->Schedule(change_ticks);
}
}
return; return;
} }
@ -1204,7 +1275,7 @@ void CDROM::ExecuteCommand(TickCount ticks_late)
if (IsSeeking()) if (IsSeeking())
UpdatePositionWhileSeeking(); UpdatePositionWhileSeeking();
SoftReset(); SoftReset(ticks_late);
QueueCommandSecondResponse(Command::Reset, RESET_TICKS); QueueCommandSecondResponse(Command::Reset, RESET_TICKS);
return; return;
@ -1574,6 +1645,10 @@ void CDROM::ExecuteDrive(TickCount ticks_late)
DoSpinUpComplete(); DoSpinUpComplete();
break; break;
case DriveState::ChangingSpeedOrTOCRead:
DoSpeedChangeOrImplicitTOCReadComplete();
break;
// old states, no longer used, but kept for save state compatibility // old states, no longer used, but kept for save state compatibility
case DriveState::UNUSED_ReadingID: case DriveState::UNUSED_ReadingID:
{ {
@ -1717,7 +1792,7 @@ void CDROM::BeginSeeking(bool logical, bool read_after_seek, bool play_after_see
m_setloc_position.frame, m_setloc_position.ToLBA(), logical ? "logical" : "physical"); m_setloc_position.frame, m_setloc_position.ToLBA(), logical ? "logical" : "physical");
const CDImage::LBA seek_lba = m_setloc_position.ToLBA(); const CDImage::LBA seek_lba = m_setloc_position.ToLBA();
const TickCount seek_time = GetTicksForSeek(seek_lba); const TickCount seek_time = GetTicksForSeek(seek_lba, play_after_seek);
m_secondary_status.ClearActiveBits(); m_secondary_status.ClearActiveBits();
m_secondary_status.motor_on = true; m_secondary_status.motor_on = true;
@ -1982,6 +2057,13 @@ void CDROM::DoSpinUpComplete()
m_secondary_status.motor_on = true; m_secondary_status.motor_on = true;
} }
void CDROM::DoSpeedChangeOrImplicitTOCReadComplete()
{
Log_DebugPrintf("Speed change/implicit TOC read complete");
m_drive_state = DriveState::Idle;
m_drive_event->Deactivate();
}
void CDROM::DoIDRead() void CDROM::DoIDRead()
{ {
Log_DebugPrintf("ID read complete"); Log_DebugPrintf("ID read complete");
@ -2593,10 +2675,6 @@ void CDROM::DrawDebugWindow()
if (ImGui::CollapsingHeader("Status/Mode", ImGuiTreeNodeFlags_DefaultOpen)) if (ImGui::CollapsingHeader("Status/Mode", ImGuiTreeNodeFlags_DefaultOpen))
{ {
static constexpr std::array<const char*, 14> drive_state_names = {
{"Idle", "Opening Shell", "Resetting", "Seeking (Physical)", "Seeking (Logical)", "Reading ID", "Reading TOC",
"Reading", "Playing", "Pausing", "Stopping", "Changing Session", "Spinning Up", "Seeking (Implicit)"}};
ImGui::Columns(3); ImGui::Columns(3);
ImGui::Text("Status"); ImGui::Text("Status");
@ -2701,7 +2779,7 @@ void CDROM::DrawDebugWindow()
else else
{ {
ImGui::TextColored(active_color, "Drive: %s (%d ticks remaining)", ImGui::TextColored(active_color, "Drive: %s (%d ticks remaining)",
drive_state_names[static_cast<u8>(m_drive_state)], s_drive_state_names[static_cast<u8>(m_drive_state)],
m_drive_event->IsActive() ? m_drive_event->GetTicksUntilNextExecution() : 0); m_drive_event->IsActive() ? m_drive_event->GetTicksUntilNextExecution() : 0);
} }

View File

@ -153,7 +153,8 @@ private:
UNUSED_Stopping, UNUSED_Stopping,
ChangingSession, ChangingSession,
SpinningUp, SpinningUp,
SeekingImplicit SeekingImplicit,
ChangingSpeedOrTOCRead
}; };
union StatusRegister union StatusRegister
@ -225,7 +226,7 @@ private:
BitField<u8, bool, 7, 1> BFRD; BitField<u8, bool, 7, 1> BFRD;
}; };
void SoftReset(); void SoftReset(TickCount ticks_late);
ALWAYS_INLINE bool IsDriveIdle() const { return m_drive_state == DriveState::Idle; } ALWAYS_INLINE bool IsDriveIdle() const { return m_drive_state == DriveState::Idle; }
ALWAYS_INLINE bool IsMotorOn() const { return m_secondary_status.motor_on; } ALWAYS_INLINE bool IsMotorOn() const { return m_secondary_status.motor_on; }
@ -271,8 +272,10 @@ private:
TickCount GetTicksForSpinUp(); TickCount GetTicksForSpinUp();
TickCount GetTicksForIDRead(); TickCount GetTicksForIDRead();
TickCount GetTicksForRead(); TickCount GetTicksForRead();
TickCount GetTicksForSeek(CDImage::LBA new_lba); TickCount GetTicksForSeek(CDImage::LBA new_lba, bool ignore_speed_change = false);
TickCount GetTicksForStop(bool motor_was_on); TickCount GetTicksForStop(bool motor_was_on);
TickCount GetTicksForSpeedChange();
TickCount GetTicksForTOCRead();
CDImage::LBA GetNextSectorToBeRead(); CDImage::LBA GetNextSectorToBeRead();
bool CompleteSeek(); bool CompleteSeek();
@ -294,6 +297,7 @@ private:
void DoStatSecondResponse(); void DoStatSecondResponse();
void DoChangeSessionComplete(); void DoChangeSessionComplete();
void DoSpinUpComplete(); void DoSpinUpComplete();
void DoSpeedChangeOrImplicitTOCReadComplete();
void DoIDRead(); void DoIDRead();
void DoSectorRead(); void DoSectorRead();
void ProcessDataSectorHeader(const u8* raw_sector); void ProcessDataSectorHeader(const u8* raw_sector);
@ -327,7 +331,6 @@ private:
StatusRegister m_status = {}; StatusRegister m_status = {};
SecondaryStatusRegister m_secondary_status = {}; SecondaryStatusRegister m_secondary_status = {};
ModeRegister m_mode = {}; ModeRegister m_mode = {};
bool m_current_double_speed = false;
u8 m_interrupt_enable_register = INTERRUPT_REGISTER_MASK; u8 m_interrupt_enable_register = INTERRUPT_REGISTER_MASK;
u8 m_interrupt_flag_register = 0; u8 m_interrupt_flag_register = 0;