Duckstation/src/core/cdrom.h

348 lines
9.8 KiB
C
Raw Normal View History

2019-09-17 09:22:39 +00:00
#pragma once
#include "cdrom_async_reader.h"
2019-09-17 09:22:39 +00:00
#include "common/bitfield.h"
#include "common/cd_image.h"
2019-10-15 07:27:35 +00:00
#include "common/cd_xa.h"
2019-10-05 06:07:15 +00:00
#include "common/fifo_queue.h"
#include "common/heap_array.h"
2019-09-21 15:12:16 +00:00
#include "types.h"
2019-10-15 07:27:35 +00:00
#include <array>
#include <string>
#include <tuple>
#include <vector>
2019-09-17 09:22:39 +00:00
class StateWrapper;
class TimingEvent;
2019-09-17 09:22:39 +00:00
JIT optimizations and refactoring (#675) * CPU/Recompiler: Use rel32 call where possible for no-args * JitCodeBuffer: Support using preallocated buffer * CPU/Recompiler/AArch64: Use bl instead of blr for short branches * CPU/CodeCache: Allocate recompiler buffer in program space This means we don't need 64-bit moves for every call out of the recompiler. * GTE: Don't store as u16 and load as u32 * CPU/Recompiler: Add methods to emit global load/stores * GTE: Convert class to namespace * CPU/Recompiler: Call GTE functions directly * Settings: Turn into a global variable * GPU: Replace local pointers with global * InterruptController: Turn into a global pointer * System: Replace local pointers with global * Timers: Turn into a global instance * DMA: Turn into a global instance * SPU: Turn into a global instance * CDROM: Turn into a global instance * MDEC: Turn into a global instance * Pad: Turn into a global instance * SIO: Turn into a global instance * CDROM: Move audio FIFO to the heap * CPU/Recompiler: Drop ASMFunctions No longer needed since we have code in the same 4GB window. * CPUCodeCache: Turn class into namespace * Bus: Local pointer -> global pointers * CPU: Turn class into namespace * Bus: Turn into namespace * GTE: Store registers in CPU state struct Allows relative addressing on ARM. * CPU/Recompiler: Align code storage to page size * CPU/Recompiler: Fix relative branches on A64 * HostInterface: Local references to global * System: Turn into a namespace, move events out * Add guard pages * Android: Fix build
2020-07-31 07:09:18 +00:00
class CDROM final
2019-09-17 09:22:39 +00:00
{
public:
CDROM();
~CDROM();
JIT optimizations and refactoring (#675) * CPU/Recompiler: Use rel32 call where possible for no-args * JitCodeBuffer: Support using preallocated buffer * CPU/Recompiler/AArch64: Use bl instead of blr for short branches * CPU/CodeCache: Allocate recompiler buffer in program space This means we don't need 64-bit moves for every call out of the recompiler. * GTE: Don't store as u16 and load as u32 * CPU/Recompiler: Add methods to emit global load/stores * GTE: Convert class to namespace * CPU/Recompiler: Call GTE functions directly * Settings: Turn into a global variable * GPU: Replace local pointers with global * InterruptController: Turn into a global pointer * System: Replace local pointers with global * Timers: Turn into a global instance * DMA: Turn into a global instance * SPU: Turn into a global instance * CDROM: Turn into a global instance * MDEC: Turn into a global instance * Pad: Turn into a global instance * SIO: Turn into a global instance * CDROM: Move audio FIFO to the heap * CPU/Recompiler: Drop ASMFunctions No longer needed since we have code in the same 4GB window. * CPUCodeCache: Turn class into namespace * Bus: Local pointer -> global pointers * CPU: Turn class into namespace * Bus: Turn into namespace * GTE: Store registers in CPU state struct Allows relative addressing on ARM. * CPU/Recompiler: Align code storage to page size * CPU/Recompiler: Fix relative branches on A64 * HostInterface: Local references to global * System: Turn into a namespace, move events out * Add guard pages * Android: Fix build
2020-07-31 07:09:18 +00:00
void Initialize();
void Shutdown();
2019-09-17 09:22:39 +00:00
void Reset();
bool DoState(StateWrapper& sw);
bool HasMedia() const { return m_reader.HasMedia(); }
2020-07-22 16:36:05 +00:00
const std::string& GetMediaFileName() const { return m_reader.GetMediaFileName(); }
void InsertMedia(std::unique_ptr<CDImage> media);
std::unique_ptr<CDImage> RemoveMedia(bool force = false);
2019-09-20 10:14:00 +00:00
void CPUClockChanged();
2019-09-17 09:22:39 +00:00
// I/O
u8 ReadRegister(u32 offset);
void WriteRegister(u32 offset, u8 value);
void DMARead(u32* words, u32 word_count);
2019-09-17 09:22:39 +00:00
2019-10-17 13:54:51 +00:00
// Render statistics debug window.
void DrawDebugWindow();
void SetUseReadThread(bool enabled);
/// Reads a frame from the audio FIFO, used by the SPU.
ALWAYS_INLINE std::tuple<s16, s16> GetAudioFrame()
{
const u32 frame = m_audio_fifo.IsEmpty() ? 0u : m_audio_fifo.Pop();
return std::tuple<s16, s16>(static_cast<s16>(frame), static_cast<s16>(frame >> 16));
}
2019-09-17 09:22:39 +00:00
private:
enum : u32
{
RAW_SECTOR_OUTPUT_SIZE = CDImage::RAW_SECTOR_SIZE - CDImage::SECTOR_SYNC_SIZE,
DATA_SECTOR_OUTPUT_SIZE = CDImage::DATA_SECTOR_SIZE,
SECTOR_SYNC_SIZE = CDImage::SECTOR_SYNC_SIZE,
SECTOR_HEADER_SIZE = CDImage::SECTOR_HEADER_SIZE,
XA_RESAMPLE_RING_BUFFER_SIZE = 32,
XA_RESAMPLE_ZIGZAG_TABLE_SIZE = 29,
XA_RESAMPLE_NUM_ZIGZAG_TABLES = 7,
PARAM_FIFO_SIZE = 16,
RESPONSE_FIFO_SIZE = 16,
DATA_FIFO_SIZE = RAW_SECTOR_OUTPUT_SIZE,
NUM_SECTOR_BUFFERS = 8,
AUDIO_FIFO_SIZE = 44100 * 2,
AUDIO_FIFO_LOW_WATERMARK = 5,
BASE_RESET_TICKS = 400000,
};
2019-09-17 09:22:39 +00:00
static constexpr u8 INTERRUPT_REGISTER_MASK = 0x1F;
enum class Interrupt : u8
{
2020-03-07 05:10:19 +00:00
DataReady = 0x01,
Complete = 0x02,
2019-09-24 14:41:09 +00:00
ACK = 0x03,
2020-03-07 05:10:19 +00:00
DataEnd = 0x04,
Error = 0x05
};
enum class Command : u16
{
Sync = 0x00,
Getstat = 0x01,
Setloc = 0x02,
Play = 0x03,
Forward = 0x04,
Backward = 0x05,
ReadN = 0x06,
MotorOn = 0x07,
Stop = 0x08,
Pause = 0x09,
Reset = 0x0A,
Mute = 0x0B,
Demute = 0x0C,
Setfilter = 0x0D,
Setmode = 0x0E,
Getparam = 0x0F,
GetlocL = 0x10,
GetlocP = 0x11,
SetSession = 0x12,
GetTN = 0x13,
GetTD = 0x14,
SeekL = 0x15,
SeekP = 0x16,
SetClock = 0x17,
GetClock = 0x18,
Test = 0x19,
GetID = 0x1A,
ReadS = 0x1B,
Init = 0x1C,
GetQ = 0x1D,
ReadTOC = 0x1E,
VideoCD = 0x1F,
None = 0xFFFF
};
enum class DriveState : u8
2019-09-17 09:22:39 +00:00
{
2019-09-21 15:12:16 +00:00
Idle,
ShellOpening,
Resetting,
SeekingPhysical,
SeekingLogical,
ReadingID,
2019-11-08 14:21:11 +00:00
ReadingTOC,
Reading,
Playing,
2019-10-28 07:19:29 +00:00
Pausing,
2020-03-07 05:10:19 +00:00
Stopping,
ChangingSession
2019-09-17 09:22:39 +00:00
};
2019-09-21 15:12:16 +00:00
union StatusRegister
2019-09-17 09:22:39 +00:00
{
u8 bits;
BitField<u8, u8, 0, 2> index;
BitField<u8, bool, 2, 1> ADPBUSY;
BitField<u8, bool, 3, 1> PRMEMPTY;
BitField<u8, bool, 4, 1> PRMWRDY;
BitField<u8, bool, 5, 1> RSLRRDY;
BitField<u8, bool, 6, 1> DRQSTS;
BitField<u8, bool, 7, 1> BUSYSTS;
2019-09-21 15:12:16 +00:00
};
2019-09-17 09:22:39 +00:00
enum StatBits : u8
{
STAT_ERROR = (1 << 0),
STAT_MOTOR_ON = (1 << 1),
STAT_SEEK_ERROR = (1 << 2),
STAT_ID_ERROR = (1 << 3),
STAT_SHELL_OPEN = (1 << 4),
STAT_READING = (1 << 5),
STAT_SEEKING = (1 << 6),
STAT_PLAYING_CDDA = (1 << 7)
};
enum ErrorReason : u8
{
ERROR_REASON_INVALID_ARGUMENT = 0x10,
ERROR_REASON_INCORRECT_NUMBER_OF_PARAMETERS = 0x20,
ERROR_REASON_INVALID_COMMAND = 0x40,
ERROR_REASON_NOT_READY = 0x80
};
2019-09-21 15:12:16 +00:00
union SecondaryStatusRegister
{
u8 bits;
BitField<u8, bool, 0, 1> error;
BitField<u8, bool, 1, 1> motor_on;
BitField<u8, bool, 2, 1> seek_error;
BitField<u8, bool, 3, 1> id_error;
BitField<u8, bool, 4, 1> shell_open;
BitField<u8, bool, 5, 1> reading;
BitField<u8, bool, 6, 1> seeking;
BitField<u8, bool, 7, 1> playing_cdda;
2019-10-20 05:55:23 +00:00
/// Clears the CDDA/seeking bits.
ALWAYS_INLINE void ClearActiveBits() { bits &= ~(STAT_SEEKING | STAT_READING | STAT_PLAYING_CDDA); }
2019-09-21 15:12:16 +00:00
};
union ModeRegister
{
u8 bits;
BitField<u8, bool, 0, 1> cdda;
BitField<u8, bool, 1, 1> auto_pause;
BitField<u8, bool, 2, 1> report_audio;
BitField<u8, bool, 3, 1> xa_filter;
BitField<u8, bool, 4, 1> ignore_bit;
BitField<u8, bool, 5, 1> read_raw_sector;
2019-09-30 10:01:41 +00:00
BitField<u8, bool, 6, 1> xa_enable;
2019-09-21 15:12:16 +00:00
BitField<u8, bool, 7, 1> double_speed;
};
union RequestRegister
{
u8 bits;
BitField<u8, bool, 5, 1> SMEN;
BitField<u8, bool, 6, 1> BFWR;
BitField<u8, bool, 7, 1> BFRD;
};
2019-10-05 06:07:15 +00:00
void SoftReset();
ALWAYS_INLINE bool IsDriveIdle() const { return m_drive_state == DriveState::Idle; }
ALWAYS_INLINE bool IsSeeking() const
{
return (m_drive_state == DriveState::SeekingLogical || m_drive_state == DriveState::SeekingPhysical ||
m_drive_state == DriveState::Resetting);
}
ALWAYS_INLINE bool IsReadingOrPlaying() const
{
return (m_drive_state == DriveState::Reading || m_drive_state == DriveState::Playing);
}
ALWAYS_INLINE bool CanReadMedia() const { return (m_drive_state != DriveState::ShellOpening && m_reader.HasMedia()); }
ALWAYS_INLINE bool HasPendingCommand() const { return m_command != Command::None; }
ALWAYS_INLINE bool HasPendingInterrupt() const { return m_interrupt_flag_register != 0; }
ALWAYS_INLINE bool HasPendingAsyncInterrupt() const { return m_pending_async_interrupt != 0; }
ALWAYS_INLINE void AddCDAudioFrame(s16 left, s16 right)
{
m_audio_fifo.Push(ZeroExtend32(static_cast<u16>(left)) | (ZeroExtend32(static_cast<u16>(right)) << 16));
}
2019-09-21 15:12:16 +00:00
void SetInterrupt(Interrupt interrupt);
void SetAsyncInterrupt(Interrupt interrupt);
void ClearAsyncInterrupt();
void DeliverAsyncInterrupt();
void SendACKAndStat();
void SendErrorResponse(u8 stat_bits = STAT_ERROR, u8 reason = 0x80);
void SendAsyncErrorResponse(u8 stat_bits = STAT_ERROR, u8 reason = 0x80);
2019-09-21 15:12:16 +00:00
void UpdateStatusRegister();
void UpdateInterruptRequest();
2019-09-21 15:12:16 +00:00
TickCount GetAckDelayForCommand(Command command);
TickCount GetTicksForRead();
TickCount GetTicksForSeek(CDImage::LBA new_lba);
TickCount GetTicksForStop(bool motor_was_on);
CDImage::LBA GetNextSectorToBeRead();
2019-09-21 15:12:16 +00:00
void BeginCommand(Command command); // also update status register
2019-11-10 13:03:52 +00:00
void EndCommand(); // also updates status register
void AbortCommand();
2019-09-21 15:12:16 +00:00
void ExecuteCommand();
void ExecuteTestCommand(u8 subcommand);
void UpdateCommandEvent();
void ExecuteDrive(TickCount ticks_late);
void BeginReading(TickCount ticks_late = 0, bool after_seek = false);
void BeginPlaying(u8 track_bcd, TickCount ticks_late = 0, bool after_seek = false);
void DoShellOpenComplete(TickCount ticks_late);
void DoResetComplete(TickCount ticks_late);
void DoSeekComplete(TickCount ticks_late);
void DoPauseComplete();
2019-10-28 07:19:29 +00:00
void DoStopComplete();
2020-03-07 05:10:19 +00:00
void DoChangeSessionComplete();
void DoIDRead();
2019-11-08 14:21:11 +00:00
void DoTOCRead();
2019-09-21 15:12:16 +00:00
void DoSectorRead();
void ProcessDataSectorHeader(const u8* raw_sector);
void ProcessDataSector(const u8* raw_sector, const CDImage::SubChannelQ& subq);
void ProcessXAADPCMSector(const u8* raw_sector, const CDImage::SubChannelQ& subq);
void ProcessCDDASector(const u8* raw_sector, const CDImage::SubChannelQ& subq);
void StopReadingWithDataEnd();
void BeginSeeking(bool logical, bool read_after_seek, bool play_after_seek);
void UpdatePositionWhileSeeking();
void ResetCurrentXAFile();
void ResetAudioDecoder();
2019-09-30 10:01:41 +00:00
void LoadDataFIFO();
void ClearSectorBuffers();
2019-09-21 15:12:16 +00:00
template<bool STEREO, bool SAMPLE_RATE>
void ResampleXAADPCM(const s16* frames_in, u32 num_frames_in);
std::unique_ptr<TimingEvent> m_command_event;
std::unique_ptr<TimingEvent> m_drive_event;
2019-09-21 15:12:16 +00:00
Command m_command = Command::None;
DriveState m_drive_state = DriveState::Idle;
DiscRegion m_disc_region = DiscRegion::Other;
2019-09-21 15:12:16 +00:00
StatusRegister m_status = {};
SecondaryStatusRegister m_secondary_status = {};
ModeRegister m_mode = {};
bool m_current_double_speed = false;
2019-10-15 07:27:35 +00:00
u8 m_interrupt_enable_register = INTERRUPT_REGISTER_MASK;
u8 m_interrupt_flag_register = 0;
u8 m_pending_async_interrupt = 0;
2019-10-15 07:27:35 +00:00
CDImage::Position m_setloc_position = {};
CDImage::LBA m_current_lba{};
CDImage::LBA m_seek_start_lba{};
CDImage::LBA m_seek_end_lba{};
bool m_setloc_pending = false;
bool m_read_after_seek = false;
bool m_play_after_seek = false;
bool m_muted = false;
bool m_adpcm_muted = false;
2019-10-15 07:27:35 +00:00
u8 m_xa_filter_file_number = 0;
u8 m_xa_filter_channel_number = 0;
u8 m_xa_current_file_number = 0;
u8 m_xa_current_channel_number = 0;
u8 m_xa_current_set = false;
2019-10-15 07:27:35 +00:00
2019-11-10 12:45:48 +00:00
CDImage::SectorHeader m_last_sector_header{};
CDXA::XASubHeader m_last_sector_subheader{};
bool m_last_sector_header_valid = false;
2019-11-10 12:45:48 +00:00
CDImage::SubChannelQ m_last_subq{};
2019-11-10 13:03:52 +00:00
u8 m_last_cdda_report_frame_nibble = 0xFF;
u8 m_play_track_number_bcd = 0xFF;
2020-03-07 05:10:19 +00:00
u8 m_async_command_parameter = 0x00;
2019-10-15 07:27:35 +00:00
std::array<std::array<u8, 2>, 2> m_cd_audio_volume_matrix{};
std::array<std::array<u8, 2>, 2> m_next_cd_audio_volume_matrix{};
std::array<s32, 4> m_xa_last_samples{};
std::array<std::array<s16, XA_RESAMPLE_RING_BUFFER_SIZE>, 2> m_xa_resample_ring_buffer{};
u8 m_xa_resample_p = 0;
u8 m_xa_resample_sixstep = 6;
2019-09-17 09:22:39 +00:00
InlineFIFOQueue<u8, PARAM_FIFO_SIZE> m_param_fifo;
InlineFIFOQueue<u8, RESPONSE_FIFO_SIZE> m_response_fifo;
InlineFIFOQueue<u8, RESPONSE_FIFO_SIZE> m_async_response_fifo;
2019-09-17 09:22:39 +00:00
HeapFIFOQueue<u8, DATA_FIFO_SIZE> m_data_fifo;
struct SectorBuffer
{
HeapArray<u8, RAW_SECTOR_OUTPUT_SIZE> data;
u32 size;
};
u32 m_current_read_sector_buffer = 0;
u32 m_current_write_sector_buffer = 0;
std::array<SectorBuffer, NUM_SECTOR_BUFFERS> m_sector_buffers;
CDROMAsyncReader m_reader;
// two 16-bit samples packed in 32-bits
JIT optimizations and refactoring (#675) * CPU/Recompiler: Use rel32 call where possible for no-args * JitCodeBuffer: Support using preallocated buffer * CPU/Recompiler/AArch64: Use bl instead of blr for short branches * CPU/CodeCache: Allocate recompiler buffer in program space This means we don't need 64-bit moves for every call out of the recompiler. * GTE: Don't store as u16 and load as u32 * CPU/Recompiler: Add methods to emit global load/stores * GTE: Convert class to namespace * CPU/Recompiler: Call GTE functions directly * Settings: Turn into a global variable * GPU: Replace local pointers with global * InterruptController: Turn into a global pointer * System: Replace local pointers with global * Timers: Turn into a global instance * DMA: Turn into a global instance * SPU: Turn into a global instance * CDROM: Turn into a global instance * MDEC: Turn into a global instance * Pad: Turn into a global instance * SIO: Turn into a global instance * CDROM: Move audio FIFO to the heap * CPU/Recompiler: Drop ASMFunctions No longer needed since we have code in the same 4GB window. * CPUCodeCache: Turn class into namespace * Bus: Local pointer -> global pointers * CPU: Turn class into namespace * Bus: Turn into namespace * GTE: Store registers in CPU state struct Allows relative addressing on ARM. * CPU/Recompiler: Align code storage to page size * CPU/Recompiler: Fix relative branches on A64 * HostInterface: Local references to global * System: Turn into a namespace, move events out * Add guard pages * Android: Fix build
2020-07-31 07:09:18 +00:00
HeapFIFOQueue<u32, AUDIO_FIFO_SIZE> m_audio_fifo;
2019-09-21 15:12:16 +00:00
};
JIT optimizations and refactoring (#675) * CPU/Recompiler: Use rel32 call where possible for no-args * JitCodeBuffer: Support using preallocated buffer * CPU/Recompiler/AArch64: Use bl instead of blr for short branches * CPU/CodeCache: Allocate recompiler buffer in program space This means we don't need 64-bit moves for every call out of the recompiler. * GTE: Don't store as u16 and load as u32 * CPU/Recompiler: Add methods to emit global load/stores * GTE: Convert class to namespace * CPU/Recompiler: Call GTE functions directly * Settings: Turn into a global variable * GPU: Replace local pointers with global * InterruptController: Turn into a global pointer * System: Replace local pointers with global * Timers: Turn into a global instance * DMA: Turn into a global instance * SPU: Turn into a global instance * CDROM: Turn into a global instance * MDEC: Turn into a global instance * Pad: Turn into a global instance * SIO: Turn into a global instance * CDROM: Move audio FIFO to the heap * CPU/Recompiler: Drop ASMFunctions No longer needed since we have code in the same 4GB window. * CPUCodeCache: Turn class into namespace * Bus: Local pointer -> global pointers * CPU: Turn class into namespace * Bus: Turn into namespace * GTE: Store registers in CPU state struct Allows relative addressing on ARM. * CPU/Recompiler: Align code storage to page size * CPU/Recompiler: Fix relative branches on A64 * HostInterface: Local references to global * System: Turn into a namespace, move events out * Add guard pages * Android: Fix build
2020-07-31 07:09:18 +00:00
extern CDROM g_cdrom;