jabyengine/src/Library/src/CD/cd.cpp

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;
}
}
}
}