diff --git a/src/cdrom/cdrom.cpp b/src/cdrom/cdrom.cpp index cf11f15..c699b14 100644 --- a/src/cdrom/cdrom.cpp +++ b/src/cdrom/cdrom.cpp @@ -3,7 +3,10 @@ MAKE_LOG_FUNCTION(log, cdromLogger) -CDROM::CDROM(Scheduler* scheduler) : scheduler(scheduler) { +CDROM::CDROM(const fs::path& cdPath, Scheduler* scheduler) : scheduler(scheduler) { + std::string pathStr = cdPath.string(); + cd = std::fopen(pathStr.c_str(), "rb"); + statusReg.prmempt = 1; // Parameter fifo empty statusReg.prmwrdy = 1; // Parameter fifo not full @@ -34,6 +37,68 @@ void CDROM::executeCommand(u8 data) { log("SetLoc (loc: %d)\n", seekLoc); break; } + + case CDROMCommands::ReadN: { + response.push(statusCode.raw); + scheduler->push(&int3, scheduler->time + int3Delay, this); + + log("ReadN\n"); + + beginReading(); + scheduler->push(&cdRead, scheduler->time + (!mode.doubleSpeed ? readTime : readTimeDoubleSpeed), this, "cdRead"); + + break; + } + + case CDROMCommands::Pause: { + response.push(statusCode.raw); + scheduler->push(&int3, scheduler->time + int3Delay, this); + + scheduler->push(&int2, scheduler->time + int3Delay + int2Delay, this); + stopReading(); + secondResponse.push(statusCode.raw); + + log("Pause\n"); + break; + } + + case CDROMCommands::Init: { + response.push(statusCode.raw); + secondResponse.push(statusCode.raw); + + scheduler->push(&int3, scheduler->time + int3Delay, this); + scheduler->push(&int2, scheduler->time + int3Delay + int2Delay, this); + + log("Init\n"); + break; + } + + case CDROMCommands::Setmode: { + mode.raw = getParamByte(); + + Helpers::debugAssert(!mode.autoPause, "Enabled CDROM auto pause\n"); + Helpers::debugAssert(!mode.report, "Enabled CDROM report\n"); + Helpers::debugAssert(!mode.xaFilter, "Enabled CDROM xa-filter\n"); + Helpers::debugAssert(!mode.ignoreBit, "Enabled CDROM ignore bit\n"); + Helpers::debugAssert(!mode.sectorSize, "Enabled CDROM 0x924 byte sectors\n"); + Helpers::debugAssert(!mode.xaAdpcm, "Enabled CDROM xa-adpcm\n"); + + response.push(statusCode.raw); + scheduler->push(&int3, scheduler->time + int3Delay, this); + + log("Setmode\n"); + break; + } + + case CDROMCommands::Demute: { + response.push(statusCode.raw); + secondResponse.push(statusCode.raw); + + scheduler->push(&int3, scheduler->time + int3Delay, this); + + log("Demute\n"); + break; + } case CDROMCommands::SeekL: { // TODO: SeekL/P should stop ongoing reads @@ -171,6 +236,51 @@ void CDROM::stopSeeking(void* classptr) { log("Seek ended\n"); } +void CDROM::beginReading() { + statusCode.reading = true; + log("Begin reading\n"); +} + +void CDROM::stopReading() { + statusCode.reading = false; + log("Stop reading\n"); + scheduler->deleteAllEventsOfName("cdRead"); +} + +void CDROM::cdRead(void* classptr) { + CDROM* cdrom = (CDROM*)classptr; + Helpers::debugAssert((cdrom->intFlag & 7) == 0, "[ FATAL ] CDROM INT1 was fired before previous INT%d was acknowledged in interrupt flag\n", cdrom->intFlag & 3); + cdrom->intFlag |= 1; + + cdrom->sector.clear(); + cdrom->sector.resize(sectorSize); + std::fseek(cdrom->cd, (cdrom->seekLoc - 150) * sectorSize, SEEK_SET); + std::fread(cdrom->sector.data(), 1, sectorSize, cdrom->cd); + cdrom->statusReg.drqsts = 1; + + log("Read sector %d\n", cdrom->seekLoc); + cdrom->seekLoc++; + + if (cdrom->mode.sectorSize) + cdrom->sectorCur = 0x0C; + else + cdrom->sectorCur = 0x18; + + cdrom->scheduler->push(&cdrom->cdRead, cdrom->scheduler->time + (!cdrom->mode.doubleSpeed ? readTime : readTimeDoubleSpeed), classptr, "cdRead"); +} + +u32 CDROM::readSectorWord() { + if ((sectorCur + 3) >= sector.size()) Helpers::panic("[ FATAL ] CDROM sector read out of bounds\n"); + + u32 word; + std::memcpy(&word, §or[sectorCur], sizeof(u32)); + sectorCur += sizeof(u32); + + if (sectorCur == sectorSize) statusReg.drqsts = 0; + + return word; +} + void CDROM::pushParam(u8 data) { params.push(data); Helpers::debugAssert(params.size() <= 16, "[ FATAL ] Wrote more than 16 bytes to CDROM parameter fifo"); @@ -187,6 +297,10 @@ void CDROM::writeStatus(u8 data) { statusReg.index = data & 3; } +u8 CDROM::readIE() { + return intEnable; +} + void CDROM::writeIE(u8 data) { intEnable = data; } diff --git a/src/cdrom/cdrom.hpp b/src/cdrom/cdrom.hpp index ff89e86..b122f88 100644 --- a/src/cdrom/cdrom.hpp +++ b/src/cdrom/cdrom.hpp @@ -8,21 +8,25 @@ constexpr u64 cpuSpeed = 33868800; -constexpr u64 readDelay = cpuSpeed / 75; -constexpr u64 readDelayDoubleSpeed = readDelay / 2; +constexpr u64 readTime = cpuSpeed / 75; +constexpr u64 readTimeDoubleSpeed = readTime / 2; // I don't know if these are ok????? -constexpr u64 int3Delay = cpuSpeed / 1000; +constexpr u64 int3Delay = cpuSpeed / 15000; constexpr u64 int2Delay = int3Delay * 2; constexpr u64 getIDDelay = 33868; -constexpr u64 seekTime = 150000; // Currently stubbed seeking time to this for all seeks +constexpr u64 seekTime = 75000; // Currently stubbed seeking time to this for all seeks + +constexpr u64 sectorSize = 0x930; class CDROM { public: - CDROM(Scheduler* scheduler); + CDROM(const fs::path& cdPath, Scheduler* scheduler); Scheduler* scheduler; + FILE* cd; + void executeCommand(u8 data); // INTs @@ -34,11 +38,16 @@ public: static void stopSeeking(void* classptr); + void beginReading(); + void stopReading(); + static void cdRead(void* classptr); + void pushParam(u8 data); u8 readStatus(); void writeStatus(u8 data); + u8 readIE(); void writeIE(u8 data); u8 readIF(); void writeIF(u8 data); @@ -46,7 +55,11 @@ public: u8 getIndex(); u8 getResponseByte(); // This one is public so it can be called from the memory read handlers + u32 readSectorWord(); private: + std::vector sector; + u64 sectorCur = 0; + u8 intEnable = 0; u8 intFlag = 0; @@ -56,6 +69,18 @@ private: u32 seekLoc = 0; u8 bcdToDec(u8 n) { return n - 6 * (n >> 4); } + union { + u8 raw = 0; + BitField<0, 1, u8> cdda; + BitField<1, 1, u8> autoPause; + BitField<2, 1, u8> report; + BitField<3, 1, u8> xaFilter; + BitField<4, 1, u8> ignoreBit; + BitField<5, 1, u8> sectorSize; + BitField<6, 1, u8> xaAdpcm; + BitField<7, 1, u8> doubleSpeed; + } mode; + std::queue response; std::queue secondResponse; @@ -87,6 +112,11 @@ namespace CDROMCommands { enum { GetStat = 0x01, SetLoc = 0x02, + ReadN = 0x06, + Pause = 0x09, + Init = 0x0A, + Setmode = 0x0E, + Demute = 0x0C, SeekL = 0x15, Test = 0x19, GetID = 0x1A diff --git a/src/dma/dma.cpp b/src/dma/dma.cpp index c1b0eba..b993ad3 100644 --- a/src/dma/dma.cpp +++ b/src/dma/dma.cpp @@ -6,6 +6,7 @@ MAKE_LOG_FUNCTION(log, dmaLogger) DMA::DMA() { channels[2].doDMA = &gpuDMA; + channels[3].doDMA = &cdromDMA; channels[6].doDMA = &otcDMA; } @@ -98,6 +99,35 @@ void DMA::gpuDMA(Memory* memory) { log("GPU DMA done\n"); } +void DMA::cdromDMA(Memory* memory) { + const auto& dma = memory->dma; + auto& ch = dma->channels[3]; + log("Start CDROM DMA\n"); + + switch (ch.chcr.syncMode) { + case (u32)SyncMode::Block: { + u32 addr = ch.madr & 0x1ffffc; + u32 bc = ch.bcr.bc; + if (!bc) bc = 0x10000; + while (bc-- > 1) { + memory->write(addr, memory->cdrom->readSectorWord()); + if (ch.chcr.step == (u32)Step::Forward) + addr += 4; + else + addr -= 4; + } + break; + } + default: + Helpers::panic("[DMA] Unimplemented CDROM DMA sync mode %d\n", ch.chcr.syncMode.Value()); + } + + ch.chcr.enable = 0; + ch.chcr.trigger = 0; + // TODO: Update DICR + log("CDROM DMA done\n"); +} + void DMA::otcDMA(Memory* memory) { const auto& dma = memory->dma; auto& ch = dma->channels[6]; diff --git a/src/dma/dma.hpp b/src/dma/dma.hpp index 432178a..b6b5335 100644 --- a/src/dma/dma.hpp +++ b/src/dma/dma.hpp @@ -35,7 +35,7 @@ public: } chcr; bool shouldStartDMA(); - void (*doDMA)(Memory*); + void (*doDMA)(Memory*) = nullptr; }; DMAChannel channels[7]; @@ -59,5 +59,6 @@ public: void doDMA(int channel, Memory* memory); static void gpuDMA(Memory* memory); + static void cdromDMA(Memory* memory); static void otcDMA(Memory* memory); }; \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 9e51cfe..cb6e7a3 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -10,10 +10,10 @@ int main(int argc, char** argv) { printf("ChonkyStation\n"); - PlayStation playstation = PlayStation(argv[1]); + PlayStation playstation = PlayStation(argv[1], argv[2]); - if (argc >= 3) { - playstation.sideloadExecutable(argv[2]); + if (argc >= 4) { + playstation.sideloadExecutable(argv[3]); } // Testing diff --git a/src/memory/memory.cpp b/src/memory/memory.cpp index 36c00f9..ed82a43 100644 --- a/src/memory/memory.cpp +++ b/src/memory/memory.cpp @@ -76,6 +76,7 @@ u8 Memory::read(u32 vaddr) { } else if (paddr == 0x1f801803) { switch (cdrom->getIndex()) { + case 0: return cdrom->readIE(); case 1: return cdrom->readIF(); default: Helpers::panic("[ FATAL ] Unhandled CDROM read8 0x1f801803.%d", cdrom->getIndex()); @@ -113,6 +114,8 @@ u16 Memory::read(u32 vaddr) { else if (Helpers::inRangeSized(paddr, (u32)MemoryBase::SIO, (u32)MemorySize::SIO)) return 0; // SPU else if (Helpers::inRangeSized(paddr, (u32)MemoryBase::SPU, (u32)MemorySize::SPU)) return 0; + // Timers + else if (Helpers::inRangeSized(paddr, (u32)MemoryBase::Timer, (u32)MemorySize::Timer)) return 0; else Helpers::panic("[ FATAL ] Unhandled read16 0x%08x (virtual 0x%08x)\n", paddr, vaddr); } @@ -158,7 +161,10 @@ u32 Memory::read(u32 vaddr) { // Timers else if (Helpers::inRangeSized(paddr, (u32)MemoryBase::Timer, (u32)MemorySize::Timer)) return 0; + else if (paddr == 0x1f802080) return 0; // 1F802080h 4 Redux-Expansion ID "PCSX" (R) + else if (paddr == 0x1f80101C) return 0x00070777; // Expansion 2 Delay/Size (usually 00070777h) (128 bytes, 8bit bus) + else if (paddr == 0x1f801060) return 0x00000b88; // RAM_SIZE (R/W) (usually 00000B88h) (or 00000888h) else Helpers::dump("ramdump.bin", ram, 2_MB); @@ -188,6 +194,9 @@ void Memory::write(u32 vaddr, u8 data) { cdrom->executeCommand(data); break; } + case 1: break; + case 2: break; + case 3: break; default: Helpers::panic("[ FATAL ] Unhandled CDROM write8 0x1f801801.%d <- 0x%02x\n", cdrom->getIndex(), data); } @@ -202,20 +211,35 @@ void Memory::write(u32 vaddr, u8 data) { cdrom->writeIE(data); break; } + case 2: break; + case 3: break; default: Helpers::panic("[ FATAL ] Unhandled CDROM write8 0x1f801802.%d <- 0x%02x\n", cdrom->getIndex(), data); } } else if (paddr == 0x1f801803) { switch (cdrom->getIndex()) { + case 0: { + Helpers::debugAssert(((data >> 5) & 1) == 0, "[ FATAL ] Unhandled CDROM Request Register SMEN bit\n"); + break; + } case 1: { cdrom->writeIF(data); break; } + case 2: break; + case 3: break; default: Helpers::panic("[ FATAL ] Unhandled CDROM write8 0x1f801803.%d <- 0x%02x\n", cdrom->getIndex(), data); } } + + // 1F802080h 1 Redux-Expansion Console putchar (W) + else if (paddr == 0x1f802080) { + std::putc(data, stdout); + return; + } + else if (paddr == 0x1f802041) return; // POST - External 7-segment Display (W) else Helpers::panic("[ FATAL ] Unhandled write8 0x%08x (virtual 0x%08x) <- 0x%02x\n", paddr, vaddr, data); diff --git a/src/playstation.hpp b/src/playstation.hpp index 44726d5..f108980 100644 --- a/src/playstation.hpp +++ b/src/playstation.hpp @@ -12,11 +12,11 @@ class PlayStation { public: - PlayStation(const fs::path& biosPath) : cdrom(&scheduler), interrupt(), gpu(&scheduler), dma(), mem(&interrupt, &dma, &gpu, &cdrom), cpu(&mem) { + PlayStation(const fs::path& biosPath, const fs::path& cdPath) : cdrom(cdPath, &scheduler), interrupt(), gpu(&scheduler), dma(), mem(&interrupt, &dma, &gpu, &cdrom), cpu(&mem) { mem.core = &cpu.core; mem.loadBios(biosPath); - cpu.switchBackend(Cpu::Backend::OldInterpreter); + cpu.switchBackend(Cpu::Backend::Interpreter); // Setup GPU scheduler events scheduler.push(&gpu.scanlineEvent, scheduler.time + GPUConstants::cyclesPerScanline, &gpu); diff --git a/src/scheduler/scheduler.cpp b/src/scheduler/scheduler.cpp index be10512..d5a04a1 100644 --- a/src/scheduler/scheduler.cpp +++ b/src/scheduler/scheduler.cpp @@ -13,8 +13,18 @@ void Scheduler::tick(u64 cycles) { } } -void Scheduler::push(void (*functionPtr)(void*), u64 time, void* data) { +void Scheduler::push(void (*functionPtr)(void*), u64 time, void* data, std::string name) { Helpers::debugAssert(events.size() < schedulerMaxEntries, "[ FATAL ] Queued more than %d scheduler events\n", schedulerMaxEntries); - events.push({ .functionPtr = functionPtr, .data = data, .time = time }); + events.push({ .functionPtr = functionPtr, .data = data, .time = time, .name = name }); +} + +void Scheduler::deleteAllEventsOfName(std::string name) { + auto eventList = events.get_container(); + + for (int i = 0; i < events.size(); i++) { + for (auto& i : eventList) { + if (i.name == name) events.remove(i); + } + } } \ No newline at end of file diff --git a/src/scheduler/scheduler.hpp b/src/scheduler/scheduler.hpp index 833290e..8d598d1 100644 --- a/src/scheduler/scheduler.hpp +++ b/src/scheduler/scheduler.hpp @@ -2,25 +2,61 @@ #include #include +#include constexpr auto schedulerMaxEntries = 64; +// https://stackoverflow.com/questions/19467485/how-to-remove-element-not-at-top-from-priority-queue +template +class pqueue : public std::priority_queue, std::greater> { +public: + std::vector& get_container() { + return this->c; + } + + bool remove(const T& value) { + auto it = std::find(this->c.begin(), this->c.end(), value); + + if (it == this->c.end()) { + return false; + } + + if (it == this->c.begin()) { + // Deque the top element + this->pop(); + } + else { + // Remove element and re-heap + this->c.erase(it); + std::make_heap(this->c.begin(), this->c.end(), this->comp); + } + + return true; + } +}; + class Scheduler { public: u64 time = 0; void tick(u64 cycles); struct Event { - void (*functionPtr)(void*); - void* data; - u64 time; + void (*functionPtr)(void*) = nullptr; + void* data = nullptr; + u64 time = 0; + std::string name = "Default"; + + bool operator==(const Event& other) const { + return (functionPtr == other.functionPtr) && (data == other.data) && (time == other.time) && (name == other.name); + } bool operator>(const Event& other) const { return time > other.time; } }; - std::priority_queue, std::greater> events; - void push(void (*functionPtr)(void*), u64 time, void* data); + pqueue events; + void push(void (*functionPtr)(void*), u64 time, void* data, std::string name = "Default"); + void deleteAllEventsOfName(std::string name); }; \ No newline at end of file diff --git a/test/diff/diff.cpp b/test/diff/diff.cpp index e10ccdc..455bd63 100644 --- a/test/diff/diff.cpp +++ b/test/diff/diff.cpp @@ -3,7 +3,7 @@ using namespace Test; -Diff::Diff(const fs::path& biosPath) : p1(biosPath), p2(biosPath) { +Diff::Diff(const fs::path& biosPath, const fs::path& cdPath) : p1(biosPath, cdPath), p2(biosPath, cdPath) { p1.switchCpuBackend(Cpu::Backend::Interpreter); p2.switchCpuBackend(Cpu::Backend::OldInterpreter); } diff --git a/test/diff/diff.hpp b/test/diff/diff.hpp index 9d77151..754d4b6 100644 --- a/test/diff/diff.hpp +++ b/test/diff/diff.hpp @@ -7,7 +7,7 @@ namespace Test { class Diff { public: - Diff(const fs::path& biosPath); + Diff(const fs::path& biosPath, const fs::path& cdPath); void doTest(); private: PlayStation p1;