CDROM: Simulate the time it takes to change speeds
This commit is contained in:
parent
db5be6c70c
commit
f4da56efea
|
@ -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(¤t_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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
Loading…
Reference in New Issue