240 lines
11 KiB
C++
240 lines
11 KiB
C++
#include "../../internal-include/CD/cd_internal.hpp"
|
|
#include "../../internal-include/System/callbacks_internal.hpp"
|
|
#include <PSX/System/IOPorts/dma_io.hpp>
|
|
#include <PSX/System/IOPorts/interrupt_io.hpp>
|
|
#include <PSX/System/syscalls.hpp>
|
|
#include <stdio.hpp>
|
|
|
|
// TODO: Outsource the interrupt handler to new source file?
|
|
// TODO: Do not spawn a new thread for handling the CD interrupt but use that thread for loading files or something?
|
|
// TODO: Can you use the GPU IO Port while also using DMA?
|
|
namespace JabyEngine {
|
|
namespace CDDA {
|
|
extern CD::internal::BCDTimeStamp playing_track;
|
|
}
|
|
|
|
namespace CDXA {
|
|
CD::internal::State interrupt_handler(uint8_t irq);
|
|
}
|
|
|
|
namespace CD {
|
|
extern volatile uint32_t zero;
|
|
|
|
namespace internal {
|
|
struct File {
|
|
uint32_t cur_lba;
|
|
uint32_t dst_lba;
|
|
|
|
void set_from(const AutoLBAEntry& file_info) {
|
|
this->cur_lba = file_info.get_lba();
|
|
this->dst_lba = this->cur_lba + file_info.get_size_in_sectors();
|
|
}
|
|
|
|
bool done_processing() {
|
|
this->cur_lba++;
|
|
return this->cur_lba == this->dst_lba;
|
|
}
|
|
};
|
|
|
|
static constexpr auto AudioSectorMode = CD_IO::Mode::from(CD_IO::Mode::SingleSpeed, CD_IO::Mode::AutoPauseTrack, CD_IO::Mode::CDDA);
|
|
static constexpr auto DataSectorMode = CD_IO::Mode::from(CD_IO::Mode::DoubleSpeed, CD_IO::Mode::DataSector);
|
|
static constexpr auto XAAudioSectorMode = CD_IO::Mode::from(CD_IO::Mode::SingleSpeed, CD_IO::Mode::XADPCM, CD_IO::Mode::WholeSector, CD_IO::Mode::UseXAFilter);
|
|
|
|
namespace IRQ {
|
|
static SysCall::InterruptVerifierResult verifier();
|
|
static void handler(uint32_t);
|
|
}
|
|
|
|
static SectorBufferAllocator sector_allocator;
|
|
static File cur_file;
|
|
|
|
State current_state = State::Ready;
|
|
uint8_t irq_bit_pending = CD_IO::Interrupt::None;
|
|
|
|
auto irq_callback = SysCall::InterruptCallback::from(IRQ::verifier, IRQ::handler);
|
|
|
|
static BCDTimeStamp send_read_cmd0(uint32_t lba, CD_IO::Command::Desc cmd) {
|
|
const auto loc = BCDTimeStamp::from(lba);
|
|
|
|
Command::send(CD_IO::Command::SetLoc, loc.min, loc.sec, loc.sector);
|
|
Command::send(cmd);
|
|
return loc;
|
|
}
|
|
|
|
static void send_read_n0(uint32_t lba) {
|
|
send_read_cmd0(lba, CD_IO::Command::ReadN);
|
|
current_state = State::Reading;
|
|
}
|
|
|
|
namespace IRQ {
|
|
static void read_sector_dma0(uint32_t* dst, size_t bytes) {
|
|
static const auto WaitSectorReady = []() {
|
|
while(!CD_IO::IndexStatus.read().is_set(CD_IO::IndexStatus::HasDataFifoData));
|
|
};
|
|
|
|
static const auto ReadSector = [](uint32_t* dst, size_t bytes) {
|
|
DMA_IO::CDROM.set_adr(reinterpret_cast<uintptr_t>(dst));
|
|
DMA_IO::CDROM.block_ctrl.write(DMA_IO::BCR::SyncMode0::for_cd(bytes >> 2));
|
|
DMA_IO::CDROM.channel_ctrl.write(DMA_IO::CHCHR::StartCDROM());
|
|
|
|
DMA_IO::CDROM.wait();
|
|
CD_IO::PortIndex0::change_to();
|
|
|
|
CD_IO::PortIndex0::Request.write(CD_IO::Request::reset());
|
|
};
|
|
|
|
WaitSectorReady();
|
|
ReadSector(dst, bytes);
|
|
}
|
|
|
|
void read_sector_to0(uint32_t* dst, size_t bytes) {
|
|
// We only support DMA rn
|
|
read_sector_dma0(dst, bytes);
|
|
|
|
// Do we ever want to support reading via IO Port?
|
|
// Doesn't seem to important when we can use DMA
|
|
}
|
|
|
|
void resume_at0(const BCDTimeStamp& cd_time) {
|
|
Command::send(CD_IO::Command::SetLoc, cd_time.min, cd_time.sec, cd_time.sector);
|
|
}
|
|
|
|
//######################################################################################################################
|
|
|
|
static SysCall::InterruptVerifierResult verifier() {
|
|
if(Interrupt::is_irq(Interrupt::CDROM)) {
|
|
Interrupt::ack_irq(Interrupt::CDROM);
|
|
return SysCall::InterruptVerifierResult::ExecuteHandler;
|
|
}
|
|
|
|
else {
|
|
return SysCall::InterruptVerifierResult::SkipHandler;
|
|
}
|
|
}
|
|
|
|
static void handler(uint32_t x) {
|
|
const auto old_status = CD_IO::IndexStatus.read();
|
|
|
|
CD_IO::PortIndex1::change_to();
|
|
const auto cur_irq = CD_IO::Interrupt::get_type(CD_IO::PortIndex1::InterruptFlag);
|
|
CD_IO::PortIndex1::change_to();
|
|
CD_IO::Interrupt::ack_extended(CD_IO::PortIndex1::InterruptFlag);
|
|
|
|
irq_bit_pending = bit::clear(irq_bit_pending, cur_irq);
|
|
|
|
CD_IO::PortIndex0::change_to();
|
|
if(cur_irq == CD_IO::Interrupt::DataReady) {
|
|
CD_IO::PortIndex0::Request.write(CD_IO::Request::want_data());
|
|
}
|
|
|
|
// No masking required because we can only write bit 0 - 2
|
|
CD_IO::IndexStatus.write(old_status);
|
|
Callback::internal::CD::execute(cur_irq);
|
|
}
|
|
|
|
void process(uint32_t irq) {
|
|
if(current_state != State::XAMode) {
|
|
switch(irq) {
|
|
case CD_IO::Interrupt::DataReady: {
|
|
// Obtain sector content here
|
|
auto* sector = sector_allocator.allocate_sector();
|
|
if(sector) {
|
|
//Now obtain sector
|
|
read_sector_to0(sector->data, CD_IO::DataSector::SizeBytes);
|
|
|
|
if(cur_file.done_processing()) {
|
|
current_state = State::Done;
|
|
Command::send(CD_IO::Command::Pause);
|
|
}
|
|
}
|
|
|
|
else {
|
|
current_state = State::BufferFull;
|
|
Command::send(CD_IO::Command::Pause);
|
|
}
|
|
} break;
|
|
|
|
case CD_IO::Interrupt::DataEnd: {
|
|
resume_at0(CDDA::playing_track);
|
|
Command::send(CD_IO::Command::Play);
|
|
} break;
|
|
|
|
case CD_IO::Interrupt::DiskError: {
|
|
current_state = State::Error;
|
|
} break;
|
|
}
|
|
}
|
|
|
|
else {
|
|
current_state = CDXA::interrupt_handler(irq);
|
|
}
|
|
}
|
|
}
|
|
|
|
void read_file(AutoLBAEntry file_info, const SectorBufferAllocator& buffer_allocator) {
|
|
cur_file.set_from(file_info);
|
|
sector_allocator = buffer_allocator;
|
|
|
|
enable_CD();
|
|
send_read_n0(cur_file.cur_lba);
|
|
}
|
|
|
|
void end_read_file() {
|
|
sector_allocator = SectorBufferAllocator::invalid();
|
|
}
|
|
|
|
void continue_reading() {
|
|
if(current_state == State::BufferFull) {
|
|
CD_IO::PortIndex0::change_to();
|
|
send_read_n0(cur_file.cur_lba);
|
|
}
|
|
}
|
|
|
|
BCDTimeStamp get_loc() {
|
|
Command::send_wait_response(CD_IO::Command::GetLocP);
|
|
|
|
// XEBRA does not support the mirror register so we go for the original...
|
|
CD_IO::PortIndex1::change_to();
|
|
const auto track = CD_IO::PortIndex1::ResponseFifo.read().raw; // track number (AAh=Lead-out area) (FFh=unknown, toc, none?)
|
|
const auto index = CD_IO::PortIndex1::ResponseFifo.read().raw; // index number (Usually 01h)
|
|
const auto mm = CD_IO::PortIndex1::ResponseFifo.read().raw; // minute number within track (00h and up)
|
|
const auto ss = CD_IO::PortIndex1::ResponseFifo.read().raw; // second number within track (00h to 59h)
|
|
const auto sect = CD_IO::PortIndex1::ResponseFifo.read().raw; // sector number within track (00h to 74h)
|
|
|
|
const auto min = CD_IO::PortIndex1::ResponseFifo.read().raw; // minute number on entire disk (00h and up)
|
|
const auto sec = CD_IO::PortIndex1::ResponseFifo.read().raw; // second number on entire disk (00h to 59h)
|
|
const auto sectors = CD_IO::PortIndex1::ResponseFifo.read().raw; // sector number on entire disk (00h to 74h)
|
|
|
|
return BCDTimeStamp{min, sec, sectors};
|
|
}
|
|
|
|
BCDTimeStamp get_locL() {
|
|
Command::send_wait_response(CD_IO::Command::GetLocL);
|
|
|
|
const auto min = CD_IO::PortIndex0::ResponseFifo.read().raw;
|
|
const auto sec = CD_IO::PortIndex0::ResponseFifo.read().raw;
|
|
const auto sector = CD_IO::PortIndex0::ResponseFifo.read().raw;
|
|
|
|
return BCDTimeStamp{min, sec, sector};
|
|
}
|
|
|
|
void enable_CD() {
|
|
Command::send(CD_IO::Command::SetMode, DataSectorMode);
|
|
}
|
|
|
|
void enable_CDDA() {
|
|
Command::send(CD_IO::Command::SetMode, AudioSectorMode);
|
|
}
|
|
|
|
void enable_CDXA(bool double_speed) {
|
|
static constexpr uint8_t SingleSpeedBit = 0x0;
|
|
static constexpr uint8_t DoubleSpeedBit = static_cast<uint8_t>(CD_IO::Mode::DoubleSpeed);
|
|
|
|
const uint8_t mode = XAAudioSectorMode.raw | (double_speed ? DoubleSpeedBit : SingleSpeedBit);
|
|
|
|
Command::send(CD_IO::Command::SetMode, mode);
|
|
current_state = State::XAMode;
|
|
}
|
|
}
|
|
}
|
|
} |