diff --git a/src/core/cdrom.cc b/src/core/cdrom.cc index 204f9f913..456475557 100644 --- a/src/core/cdrom.cc +++ b/src/core/cdrom.cc @@ -1602,8 +1602,12 @@ class CDRomImpl : public PCSX::CDRom { delayedString, m_param[0], m_param[1], m_param[2]); break; case CdlPlay: - PCSX::g_system->log(PCSX::LogClass::CDROM, "%08x [CDROM]%s Command: CdlPlay %i\n", pc, delayedString, - m_param[0]); + if (m_paramC == 0) { + PCSX::g_system->log(PCSX::LogClass::CDROM, "%08x [CDROM]%s Command: CdlPlay\n", pc, delayedString); + } else { + PCSX::g_system->log(PCSX::LogClass::CDROM, "%08x [CDROM]%s Command: CdlPlay %i\n", pc, + delayedString, m_param[0]); + } break; case CdlSetfilter: PCSX::g_system->log(PCSX::LogClass::CDROM, diff --git a/src/mips/common/kernel/threads.h b/src/mips/common/kernel/threads.h new file mode 100644 index 000000000..d2934a07e --- /dev/null +++ b/src/mips/common/kernel/threads.h @@ -0,0 +1,55 @@ +/* + +MIT License + +Copyright (c) 2024 PCSX-Redux authors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +*/ + +#pragma once + +#include + +struct Registers { + union { + struct { + uint32_t r0, at, v0, v1, a0, a1, a2, a3; + uint32_t t0, t1, t2, t3, t4, t5, t6, t7; + uint32_t s0, s1, s2, s3, s4, s5, s6, s7; + uint32_t t8, t9, k0, k1, gp, sp, fp, ra; + } n; + uint32_t r[32]; + } GPR; + uint32_t returnPC; + uint32_t hi, lo; + uint32_t SR; + uint32_t Cause; +}; + +struct Thread { + uint32_t flags, flags2; + struct Registers registers; + uint32_t unknown[9]; +}; + +struct Process { + struct Thread* thread; +}; diff --git a/src/mips/openbios/kernel/threads.h b/src/mips/openbios/kernel/threads.h index 3a8afa7ae..9f9a076fe 100644 --- a/src/mips/openbios/kernel/threads.h +++ b/src/mips/openbios/kernel/threads.h @@ -27,31 +27,7 @@ SOFTWARE. #include -struct Registers { - union { - struct { - uint32_t r0, at, v0, v1, a0, a1, a2, a3; - uint32_t t0, t1, t2, t3, t4, t5, t6, t7; - uint32_t s0, s1, s2, s3, s4, s5, s6, s7; - uint32_t t8, t9, k0, k1, gp, sp, fp, ra; - } n; - uint32_t r[32]; - } GPR; - uint32_t returnPC; - uint32_t hi, lo; - uint32_t SR; - uint32_t Cause; -}; - -struct Thread { - uint32_t flags, flags2; - struct Registers registers; - uint32_t unknown[9]; -}; - -struct Process { - struct Thread* thread; -}; +#include "common/kernel/threads.h" int initThreads(int processCount, int threadCount); diff --git a/src/mips/psyqo/Makefile b/src/mips/psyqo/Makefile index 7c990583d..5cd287850 100644 --- a/src/mips/psyqo/Makefile +++ b/src/mips/psyqo/Makefile @@ -4,7 +4,7 @@ TARGET = psyqo TYPE = library SRCS = \ -../../../third_party/EASTL/source/fixed_pool.cpp \ +$(wildcard ../../../third_party/EASTL/source/*.cpp) \ ../common/crt0/crt0cxx.s \ ../common/crt0/cxxglue.c \ ../common/crt0/memory-c.c \ diff --git a/src/mips/psyqo/cdrom-device.hh b/src/mips/psyqo/cdrom-device.hh index f8fb5794c..a554591e2 100644 --- a/src/mips/psyqo/cdrom-device.hh +++ b/src/mips/psyqo/cdrom-device.hh @@ -26,20 +26,88 @@ SOFTWARE. #pragma once +#include +#include #include +#include +#include +#include + #include "psyqo/cdrom.hh" +#include "psyqo/msf.hh" +#include "psyqo/task.hh" namespace psyqo { +class GPU; + +namespace Concepts { + +template +struct CDRomDeviceStateEnumHasIdle : std::false_type {}; + +template +struct CDRomDeviceStateEnumHasIdle> : std::true_type {}; + +template +concept IsCDRomDeviceStateEnum = + std::is_enum_v && std::is_same_v> && CDRomDeviceStateEnumHasIdle::value; + +} // namespace Concepts + /** * @brief A specialization of the CDRom interface. * * @details This class is a specialization of the CDRom interface, which * provides a way to read from the physical CDRom drive of the console. + * All of the methods in this class are asynchronous, and will call the + * provided callback when the operation is complete. The class also + * provides a blocking variant for some of the methods, which can be + * used to perform the operation synchronously. Note that the blocking + * variants are only provided for methods that are expected to complete + * quickly, and should not be used in performance-critical code, as they + * can still block the system for several milliseconds. The callbacks + * will be called from the main thread, and have a boolean parameter + * that indicates whether the operation was successful. * */ class CDRomDevice final : public CDRom { + public: + typedef eastl::fixed_vector Response; + struct PlaybackLocation { + MSF relative; + MSF absolute; + unsigned track; + unsigned index; + }; + + private: + struct ActionBase { + const char *name() const { return m_name; } + + protected: + ActionBase(const char *const name) : m_name(name) {} + virtual ~ActionBase() = default; + + virtual bool dataReady(const Response &response); + virtual bool complete(const Response &response); + virtual bool acknowledge(const Response &response); + virtual bool end(const Response &response); + + void setCallback(eastl::function &&callback); + void queueCallbackFromISR(bool success); + void setSuccess(bool success); + // This isn't really a great way to do this, but it's the best I can come up with + // that doesn't involve friending a bunch of classes. + PlaybackLocation *getPendingLocationPtr() const; + void queueGetLocationCallback(bool success = true); + + friend class CDRomDevice; + CDRomDevice *m_device = nullptr; + const char *const m_name = nullptr; + }; + public: virtual ~CDRomDevice(); @@ -64,30 +132,226 @@ class CDRomDevice final : public CDRom { */ void reset(eastl::function &&callback); TaskQueue::Task scheduleReset(); + bool resetBlocking(GPU &); + /** + * @brief Reads sectors from the CDRom. + * + * @details This method will read a number of sectors from the CDRom + * drive. The sectors will be read into the provided buffer. Note that + * only one read operation can be active at a time, and that the + * `ISO9660Parser` class will call this method to read the filesystem + * structure, so care must be taken to ensure no other read operation + * is active when the parser is used. + * + * @param sector The sector to start reading from. + * @param count The number of sectors to read. + * @param buffer The buffer to read the sectors into. + * @param callback The callback to call when the read is complete. + * + */ void readSectors(uint32_t sector, uint32_t count, void *buffer, eastl::function &&callback) override; + TaskQueue::Task scheduleReadSectors(uint32_t sector, uint32_t count, void *buffer); + bool readSectorsBlocking(uint32_t sector, uint32_t count, void *buffer, GPU &); + + /** + * @brief Gets the size of the Table of Contents from the CDRom. Note that + * while the blocking variant is available because it is a fairly short + * operation with the CDRom controller, it can still block the system + * for roughly 2ms, which is a long time in the context of a 33MHz CPU. + * + * @param size The pointer to store the size of the TOC. + * @param callback The callback to call when the size is retrieved. + */ + void getTOCSize(unsigned *size, eastl::function &&callback); + TaskQueue::Task scheduleGetTOCSize(unsigned *size); + unsigned getTOCSizeBlocking(GPU &); + + /** + * @brief Reads the Table of Contents from the CDRom. + * + * @details This method will read the Table of Contents from the CDRom + * drive. The TOC will be read into the provided buffer. Note that + * a CD-Rom can have up to 99 tracks, and the TOC will be read into the + * provided buffer starting at index 1 for the first track. Any tracks + * that are not present on the CD will not have their MSF structure + * filled in, so the application should ensure that the buffer is + * initialized to zero before calling this method. The blocking variant + * may take a total of 200ms to complete, depending on the number of + * tracks on the CD. + * + * @param toc The buffer to read the TOC into. + * @param size The size of the buffer. Should be 100 to hold all possible tracks. + * @param callback The callback to call when the read is complete. + */ + void readTOC(MSF *toc, unsigned size, eastl::function &&callback); + TaskQueue::Task scheduleReadTOC(MSF *toc, unsigned size); + bool readTOCBlocking(MSF *toc, unsigned size, GPU &); + + /** + * @brief Mutes the CD audio for both CDDA and CDXA. + * + * @param callback The callback to call when the mute operation is complete. + */ + void mute(eastl::function &&callback); + TaskQueue::Task scheduleMute(); + void muteBlocking(GPU &); + + /** + * @brief Unmutes the CD audio for both CDDA and CDXA. + * + * @param callback The callback to call when the unmute operation is complete. + */ + void unmute(eastl::function &&callback); + TaskQueue::Task scheduleUnmute(); + void unmuteBlocking(GPU &); + + /** + * @brief Begins playing CDDA audio from a given starting point. + * + * @details This method will begin playing CDDA audio from a given + * starting point. The starting point is either a track number or + * an MSF value. Unlike other APIs here, upon success, the callback + * will be called *twice*: once when the playback actually started, + * and once when the playback is complete or paused, which can be + * after the end of the track, at the end of the disc if the last + * track is reached, or if the playback is paused or stopped using + * the `pauseCDDA` or `stopCDDA` methods. The first callback will + * be called with `true` if the playback started successfully, + * and `false` if it failed. In the case of failure, the second + * callback will not be called. The Track variant will stop playback + * at the end of the track, while the Disc variant will stop playback + * at the end of the disc. The resume method can be used to resume + * playback after a pause. Its callback argument will function + * similarly to the callback argument of the `playCDDA` methods. + * + * @param start The starting point for playback. + * @param stopAtEndOfTrack If true, playback will stop at the end of the track. + * @param callback The callback to call when playback is complete. + */ + void playCDDATrack(MSF start, eastl::function &&callback); + void playCDDATrack(unsigned track, eastl::function &&callback); + void playCDDADisc(MSF start, eastl::function &&callback); + void playCDDADisc(unsigned track, eastl::function &&callback); + void resumeCDDA(eastl::function &&callback); + + /** + * @brief Pauses CDDA playback. + * + * @details This method will request a pause of the CDDA playback. + * The callback which was provided to the `playCDDA` method will be + * called with `true` when the playback is paused successfully. + * This method can only be called when the CDDA playback is in + * progress, as indicated by a first successful call of the `playCDDA` + * callback, and will fail if not. Pausing the playback will not + * stop the CDRom drive motor, which means that another `playCDDA` + * call start playing faster than if the motor was stopped. + */ + void pauseCDDA(); + + /** + * @brief Stops CDDA playback. + * + * @details This method will request a stop of the CDDA playback. + * It functions similarly to the `pauseCDDA` method, but will stop + * the CDRom drive motor, which means that another `playCDDA` call + * will take longer to start playing than if the motor was not stopped. + */ + void stopCDDA(); + + /** + * @brief Get the Playback location of the CDDA audio. + * + * @details This method will request the current playback location + * of the CDDA audio. The callback will be called with a pointer to + * a `PlaybackLocation` structure, which will contain the relative + * and absolute MSF values, the current track number, and the current + * index. The callback will be called with a null pointer if the + * location could not be retrieved. + * + * @param location If provided, the location will be stored here. + * @param callback The callback to call when the location is retrieved. + */ + void getPlaybackLocation(PlaybackLocation *location, eastl::function &&callback); + void getPlaybackLocation(eastl::function &&callback); + TaskQueue::Task scheduleGetPlaybackLocation(PlaybackLocation *location); + + /** + * @brief Set the Volume of the CDDA audio. + * + * @details This method will set the volume of the CDDA audio. The + * volume is set using four values, which represent the volume of + * the left channel to the left speaker, the right channel to the + * left speaker, the left channel to the right speaker, and the + * right channel to the right speaker. The given output value + * should be in the range of 0 to 128, where 0 is silence and 128 + * is full volume. The values for a given output speaker will be + * added together, so clipping can occur if the sum of the values + * is greater than 128. The method can be used at any time, unlike + * the mute/unmute methods, which can only be used when the drive + * is idle. The normal volume setting is 0x80, 0x00, 0x00, 0x80. + * + * @param leftToLeft The volume of the left channel to the left speaker. + * @param rightToLeft The volume of the right channel to the left speaker. + * @param leftToRight The volume of the left channel to the right speaker. + * @param rightToRight The volume of the right channel to the right speaker. + */ + void setVolume(uint8_t leftToLeft, uint8_t rightToLeft, uint8_t leftToRight, uint8_t rightToRight); + + /** + * @brief The action base class for the internal state machine. + * + * @details This class is meant to be extended by the various actions + * that the CDRom device can perform. It provides a framework for + * the state machine that is used to perform various operations. + * While it is public, it is not meant to be used directly by the + * application, and should only be used by the CDRomDevice class. + * + */ + template + class Action : public ActionBase { + protected: + Action(const char *const name) : ActionBase(name) {} + void registerMe(CDRomDevice *device) { + device->switchAction(this); + m_device = device; + } + + void setState(S state) { m_device->m_state = static_cast(state); } + S getState() const { return static_cast(m_device->m_state); } + }; private: + void switchAction(ActionBase *action); void irq(); + void actionComplete(); - void dataReady(); - void complete(); - void acknowledge(); - void end(); - void discError(); + friend class ActionBase; eastl::function m_callback; + eastl::function m_locationCallback; + PlaybackLocation m_locationStorage; + PlaybackLocation *m_locationPtr = nullptr; uint32_t m_event = 0; - uint32_t m_count = 0; - uint8_t *m_ptr = nullptr; - enum { - NONE, - RESET, - SETLOC, - SETMODE, - READ, - PAUSE, - } m_action = NONE; + ActionBase *m_action = nullptr; + uint8_t m_state = 0; + bool m_success = false; + bool m_blocking = false; + bool m_pendingGetLocation = false; + + struct BlockingAction { + BlockingAction(CDRomDevice *, GPU &); + ~BlockingAction(); + + private: + CDRomDevice *m_device; + GPU &m_gpu; + }; + + struct MaskedIRQ { + MaskedIRQ(); + ~MaskedIRQ(); + }; }; } // namespace psyqo diff --git a/src/mips/psyqo/examples/cdda/Makefile b/src/mips/psyqo/examples/cdda/Makefile new file mode 100644 index 000000000..801811496 --- /dev/null +++ b/src/mips/psyqo/examples/cdda/Makefile @@ -0,0 +1,12 @@ +TARGET = cdda +TYPE = ps-exe + +SRCS = \ +cdda.cpp \ + +ifeq ($(TEST),true) +CPPFLAGS = -Werror +endif +CXXFLAGS = -std=c++20 + +include ../../psyqo.mk diff --git a/src/mips/psyqo/examples/cdda/cdda.cpp b/src/mips/psyqo/examples/cdda/cdda.cpp new file mode 100644 index 000000000..2fef6e0c6 --- /dev/null +++ b/src/mips/psyqo/examples/cdda/cdda.cpp @@ -0,0 +1,161 @@ +/* + +MIT License + +Copyright (c) 2024 PCSX-Redux authors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +*/ + +#include "common/syscalls/syscalls.h" +#include "psyqo/application.hh" +#include "psyqo/cdrom-device.hh" +#include "psyqo/font.hh" +#include "psyqo/gpu.hh" +#include "psyqo/scene.hh" + +namespace { + +// This is a very simple example of how to use the CD-Rom device +// to play CDDA audio. This example will loop playback of track 2 +// forever, displaying the current track and playback location +// on the screen. This example is not a complete example of how +// to handle CDDA audio in a real application, but it should be +// a good starting point for understanding how to use the CD-Rom +// device to play CDDA audio. Enhancements to this example could +// include handling stop requests, track change requests, and +// retrying playback if it fails, displaying more information +// about the playback, using the pad to control playback, etc. +class CDDA final : public psyqo::Application { + void prepare() override; + void createScene() override; + void loopPlaybackTrack(unsigned track); + + public: + // The font to display our status text + psyqo::Font<> m_font; + // The CD-Rom device object + psyqo::CDRomDevice m_cdrom; + // Storage for the playback location data + psyqo::CDRomDevice::PlaybackLocation m_playbackLocation; + // Keeping internal states kosher + bool m_cddaPlaying = false; + bool m_getlocPending = false; +}; + +class CDDAScene final : public psyqo::Scene { + void frame() override; +}; + +CDDA cdda; +CDDAScene cddaScene; + +} // namespace + +void CDDA::prepare() { + psyqo::GPU::Configuration config; + config.set(psyqo::GPU::Resolution::W320) + .set(psyqo::GPU::VideoMode::AUTO) + .set(psyqo::GPU::ColorMode::C15BITS) + .set(psyqo::GPU::Interlace::PROGRESSIVE); + gpu().initialize(config); + m_cdrom.prepare(); +} + +// This function will loop playback of a given track +// until the end of time. It's a simple example of +// how to use the CD-Rom device to play CDDA audio. +// This function will call itself when the track ends. +// In a real application, we'd want to handle a bit +// more state, such as retrying playback if it fails, +// or handling stop requests or track changes requests, +// which would require a more complex state machine. +void CDDA::loopPlaybackTrack(unsigned track) { + // The callback will be called twice: once when the + // track begins playing, and once when it ends, so + // we need to keep track of the state to know when + // to loop back to the beginning. + m_cdrom.playCDDATrack(track, [this, track](bool success) { + if (!success) { + // If the playback failed, then the state + // machine will just stop running. This is + // a simple example, but in reality, we'd + // want to handle this more gracefully, + // perhaps by retrying the playback periodically. + syscall_puts("CDDA playback failed\n"); + m_cddaPlaying = false; + return; + } + m_cddaPlaying = !m_cddaPlaying; + if (m_cddaPlaying) { + ramsyscall_printf("CDDA playback started at track %d\n", track); + } else { + ramsyscall_printf("CDDA playback looping back to beginning of track %d\n", track); + loopPlaybackTrack(track); + } + }); +} + +void CDDA::createScene() { + m_font.uploadSystemFont(gpu()); + pushScene(&cddaScene); + syscall_puts("CD-Rom device reset...\n"); + m_cdrom.resetBlocking(gpu()); + syscall_puts("CD-Rom device ready, getting TOC size...\n"); + auto tocSize = m_cdrom.getTOCSizeBlocking(gpu()); + ramsyscall_printf("CD-Rom track count: %d\n", tocSize); + // Start playback of track 2, which is a good + // track to loop because games with CDDA will + // have a data track as track 1. We could also + // use the track count from the TOC to retrieve + // the table of contents and play a specific track. + loopPlaybackTrack(2); + using namespace psyqo::timer_literals; + // This timer will check the playback location + // every 20ms to update the display. The 20ms value + // is way overkill for this purpose. + gpu().armPeriodicTimer(20_ms, [this](uint32_t) { + // We can't call getPlaybackLocation while + // the CD-Rom device is busy with another + // getPlaybackLocation call, so we need to + // check if we're already waiting for a + // location before we request another one. + // Also, we can only call getPlaybackLocation + // when the CD-Rom device is playing CDDA. + if (!m_getlocPending && m_cddaPlaying) { + m_getlocPending = true; + m_cdrom.getPlaybackLocation(&m_playbackLocation, [this](auto location) { m_getlocPending = false; }); + } + }); +} + +void CDDAScene::frame() { + psyqo::Color bg{{.r = 0, .g = 64, .b = 91}}; + cdda.gpu().clear(bg); + // The `m_playbackLocation` object will be updated + // by the timer callback in the `CDDA` class, so we + // can just use it here to display the current track + // and playback location. + auto& pos = cdda.m_playbackLocation.relative; + cdda.m_font.printf(cdda.gpu(), {{.x = 4, .y = 20}}, {.r = 0xff, .g = 0xff, .b = 0xff}, "Track %i @%02i:%02i/%02i", + cdda.m_playbackLocation.track, pos.m, pos.s, pos.f); +} + +int main() { return cdda.run(); } diff --git a/src/mips/psyqo/examples/task-demo/task-demo.cpp b/src/mips/psyqo/examples/task-demo/task-demo.cpp index 90d5f7c01..89715448d 100644 --- a/src/mips/psyqo/examples/task-demo/task-demo.cpp +++ b/src/mips/psyqo/examples/task-demo/task-demo.cpp @@ -32,9 +32,9 @@ SOFTWARE. #include "psyqo/font.hh" #include "psyqo/gpu.hh" #include "psyqo/iso9660-parser.hh" +#include "psyqo/msf.hh" #include "psyqo/scene.hh" #include "psyqo/task.hh" -#include "psyqo/xprintf.h" namespace { @@ -51,6 +51,7 @@ class TaskDemo final : public psyqo::Application { eastl::fixed_string m_text; uint8_t m_buffer[2048]; uint32_t m_systemCnfSize; + psyqo::MSF m_toc[100] = {}; bool m_done = false; }; @@ -112,10 +113,21 @@ void TaskDemo::createScene() { .then([this](auto task) { m_text = "Success!"; syscall_puts("Success!\n"); + ramsyscall_printf("Track count: %d\n", m_cdrom.getTOCSizeBlocking(gpu())); m_systemCnfSize = m_request.entry.size; m_done = true; task->resolve(); }) + .then(m_cdrom.scheduleReadTOC(m_toc, 100)) + .then([this](auto task) { + for (unsigned i = 1; i < 100; i++) { + if (m_toc[i].m == 0 && m_toc[i].s == 0 && m_toc[i].f == 0) { + break; + } + ramsyscall_printf("Track %d: %02d:%02d:%02d\n", i, m_toc[i].m, m_toc[i].s, m_toc[i].f); + } + task->resolve(); + }) .butCatch([this](auto queue) { m_text = "Failure, retrying..."; syscall_puts("Failure, retrying...\n"); diff --git a/src/mips/psyqo/hardware/cdrom.hh b/src/mips/psyqo/hardware/cdrom.hh index e875180db..1f5b6967d 100644 --- a/src/mips/psyqo/hardware/cdrom.hh +++ b/src/mips/psyqo/hardware/cdrom.hh @@ -43,7 +43,7 @@ enum class CDL : uint8_t { PAUSE = 9, INIT = 10, MUTE = 11, - DEMUTE = 12, + UNMUTE = 12, SETFILTER = 13, SETMODE = 14, GETMODE = 15, @@ -102,5 +102,10 @@ extern CommandFifo Command; extern Register<0, uint8_t, WriteQueue::Bypass, Access, 0>> DataRequest; extern Register<0, uint8_t, WriteQueue::Bypass, Access, 1>> CauseMask; extern Register<0, uint8_t, WriteQueue::Bypass, Access, 1>> Cause; +extern Register<0, uint8_t, WriteQueue::Bypass, Access, 2>> LeftToLeftVolume; +extern Register<0, uint8_t, WriteQueue::Bypass, Access, 2>> LeftToRightVolume; +extern Register<0, uint8_t, WriteQueue::Bypass, Access, 3>> RightToRightVolume; +extern Register<0, uint8_t, WriteQueue::Bypass, Access, 3>> RightToLeftVolume; +extern Register<0, uint8_t, WriteQueue::Bypass, Access, 3>> VolumeSettings; } // namespace psyqo::Hardware::CDRom diff --git a/src/mips/psyqo/hardware/cpu.hh b/src/mips/psyqo/hardware/cpu.hh index c831408ab..fe66ddedc 100644 --- a/src/mips/psyqo/hardware/cpu.hh +++ b/src/mips/psyqo/hardware/cpu.hh @@ -49,6 +49,7 @@ struct IRQReg : public Register { void set(IRQ irq) { *this |= (static_cast(irq)); } void clear(IRQ irq) { *this &= ~(static_cast(irq)); } void clear() { Register::access() = 0; } + bool isSet(IRQ irq) const { return (*this & static_cast(irq)) != 0; } }; extern IRQReg<0x0070> IReg; @@ -56,4 +57,8 @@ extern IRQReg<0x0074> IMask; extern Register<0x00f0> DPCR; extern Register<0x00f4> DICR; +extern Register<0x0000, uint32_t, WriteQueue::Bypass> WriteQueueFlusher; + +static inline void flushWriteQueue() { WriteQueueFlusher.throwAway(); } + } // namespace psyqo::Hardware::CPU diff --git a/src/mips/psyqo/kernel.hh b/src/mips/psyqo/kernel.hh index b20d7ea7e..4e9799d45 100644 --- a/src/mips/psyqo/kernel.hh +++ b/src/mips/psyqo/kernel.hh @@ -236,6 +236,35 @@ void queueCallback(eastl::function&& lambda); */ void queueCallbackFromISR(eastl::function&& lambda); +/** + * @brief Sets a break handler for a given category. + * + * @details This function is used to set a break handler for a given + * category. The category is technically the upper 10 bits of the break + * code, and the handler is a function that takes the lower 10 bits of + * the break code. The handler should return true if it handled the + * break, and false otherwise. The handler will be called from the + * exception handler, with the same restrictions as for any other + * interrupt handler. Note that the category is actually limited to + * 16 categories by psyqo, from 0 to 15. It is also worth noting that + * category 0 is usually reserved for pcdrv, category 7 is reserved + * by the compiler to emit division by zero checks, and psyqo uses + * category 14 for its own purposes. Only one handler can be set per + * category, and trying to set a handler for a category that already + * has a handler will cause an assertion failure. + * + * @param category The category to handle. + */ + +void setBreakHandler(unsigned category, eastl::function&& handler); + +/** + * @brief Queues a break handler for psyqo's reserved category. + * + * @param handler The handler to call when a break occurs. + */ +void queuePsyqoBreakHandler(eastl::function && handler); + namespace Internal { void pumpCallbacks(); void prepare(Application&); diff --git a/src/mips/psyqo/msf.hh b/src/mips/psyqo/msf.hh index 499c36fa8..cba752c49 100644 --- a/src/mips/psyqo/msf.hh +++ b/src/mips/psyqo/msf.hh @@ -67,6 +67,7 @@ struct MSF { uint8_t pad; }; uint8_t data[4]; + uint32_t full; }; }; diff --git a/src/mips/psyqo/src/cdrom-device-cdda.cpp b/src/mips/psyqo/src/cdrom-device-cdda.cpp new file mode 100644 index 000000000..39d192a85 --- /dev/null +++ b/src/mips/psyqo/src/cdrom-device-cdda.cpp @@ -0,0 +1,280 @@ +/* + +MIT License + +Copyright (c) 2022 PCSX-Redux authors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +*/ + +#include + +#include "psyqo/cdrom-device.hh" +#include "psyqo/hardware/cdrom.hh" +#include "psyqo/kernel.hh" +#include "psyqo/msf.hh" + +namespace { + +enum class PlayCDDAActionState : uint8_t { + IDLE, + GETTD, + SETMODE, + SETLOC, + SEEK, + SEEK_ACK, + PLAY, + // needs to stay unique across all actions, and + // will be hardcoded in the pause command + PLAYING = 100, + STOPPING = 101, + STOPPING_ACK, +}; + +class PlayCDDAAction : public psyqo::CDRomDevice::Action { + public: + PlayCDDAAction() : Action("PlayCDDAAction") {} + void start(psyqo::CDRomDevice *device, unsigned track, bool stopAtEndOfTrack, + eastl::function &&callback) { + psyqo::Kernel::assert(getState() == PlayCDDAActionState::IDLE, + "CDRomDevice::playCDDA() called while another action is in progress"); + registerMe(device); + setCallback(eastl::move(callback)); + setState(PlayCDDAActionState::GETTD); + m_stopAtEndOfTrack = stopAtEndOfTrack; + eastl::atomic_signal_fence(eastl::memory_order_release); + psyqo::Hardware::CDRom::Command.send(psyqo::Hardware::CDRom::CDL::GETTD, psyqo::itob(track)); + } + void start(psyqo::CDRomDevice *device, psyqo::MSF msf, bool stopAtEndOfTrack, + eastl::function &&callback) { + psyqo::Kernel::assert(getState() == PlayCDDAActionState::IDLE, + "CDRomDevice::playCDDA() called while another action is in progress"); + registerMe(device); + setCallback(eastl::move(callback)); + setState(PlayCDDAActionState::SEEK); + m_start = msf; + eastl::atomic_signal_fence(eastl::memory_order_release); + psyqo::Hardware::CDRom::Command.send(psyqo::Hardware::CDRom::CDL::SETMODE, stopAtEndOfTrack ? 3 : 1); + } + void start(psyqo::CDRomDevice *device, eastl::function &&callback) { + psyqo::Kernel::assert(getState() == PlayCDDAActionState::IDLE, + "CDRomDevice::playCDDA() called while another action is in progress"); + registerMe(device); + setCallback(eastl::move(callback)); + setState(PlayCDDAActionState::PLAY); + eastl::atomic_signal_fence(eastl::memory_order_release); + psyqo::Hardware::CDRom::Command.send(psyqo::Hardware::CDRom::CDL::PLAY); + } + bool complete(const psyqo::CDRomDevice::Response &) override { + switch (getState()) { + case PlayCDDAActionState::SEEK_ACK: + setState(PlayCDDAActionState::PLAY); + psyqo::Hardware::CDRom::Command.send(psyqo::Hardware::CDRom::CDL::PLAY); + break; + case PlayCDDAActionState::STOPPING_ACK: + setSuccess(true); + return true; + default: + psyqo::Kernel::abort("PlayCDDAAction got CDROM complete in wrong state"); + break; + } + return false; + } + bool acknowledge(const psyqo::CDRomDevice::Response &response) override { + switch (getState()) { + case PlayCDDAActionState::GETTD: + m_start.m = psyqo::btoi(response[1]); + m_start.s = psyqo::btoi(response[2]); + m_start.f = 0; + setState(PlayCDDAActionState::SETMODE); + psyqo::Hardware::CDRom::Command.send(psyqo::Hardware::CDRom::CDL::SETMODE, + m_stopAtEndOfTrack ? 0x02 : 0); + break; + case PlayCDDAActionState::SETMODE: + setState(PlayCDDAActionState::SETLOC); + psyqo::Hardware::CDRom::Command.send(psyqo::Hardware::CDRom::CDL::SETLOC, psyqo::itob(m_start.m), + psyqo::itob(m_start.s), psyqo::itob(m_start.f)); + break; + case PlayCDDAActionState::SETLOC: + setState(PlayCDDAActionState::SEEK); + psyqo::Hardware::CDRom::Command.send(psyqo::Hardware::CDRom::CDL::SEEKP); + break; + case PlayCDDAActionState::SEEK: + setState(PlayCDDAActionState::SEEK_ACK); + break; + case PlayCDDAActionState::PLAY: + setState(PlayCDDAActionState::PLAYING); + queueCallbackFromISR(true); + break; + case PlayCDDAActionState::PLAYING: { + auto locationPtr = getPendingLocationPtr(); + psyqo::Kernel::assert((response.size() == 8) && locationPtr, + "PlayCDDAAction got unexpected CDROM acknowledge"); + locationPtr->track = psyqo::btoi(response[0]); + locationPtr->index = psyqo::btoi(response[1]); + locationPtr->relative.m = psyqo::btoi(response[2]); + locationPtr->relative.s = psyqo::btoi(response[3]); + locationPtr->relative.f = psyqo::btoi(response[4]); + locationPtr->absolute.m = psyqo::btoi(response[5]); + locationPtr->absolute.s = psyqo::btoi(response[6]); + locationPtr->absolute.f = psyqo::btoi(response[7]); + queueGetLocationCallback(); + } break; + case PlayCDDAActionState::STOPPING: + setState(PlayCDDAActionState::STOPPING_ACK); + break; + default: + psyqo::Kernel::abort("PlayCDDAAction got CDROM acknowledge in wrong state"); + break; + } + return false; + } + bool end(const psyqo::CDRomDevice::Response &) override { + // We got raced to the end of the track and/or disc by the + // pause command, so we should just ignore this. + if (getState() == PlayCDDAActionState::STOPPING) return false; + // We got raced to the end of the track and/or disc by the + // get location command, and we need to signal the callback. + if (getPendingLocationPtr()) { + queueGetLocationCallback(false); + } + psyqo::Kernel::assert(getState() == PlayCDDAActionState::PLAYING, + "PlayCDDAAction got CDROM end in wrong state"); + setSuccess(true); + return true; + } + psyqo::MSF m_start; + bool m_stopAtEndOfTrack = false; +}; + +PlayCDDAAction s_playCDDAAction; + +} // namespace + +void psyqo::CDRomDevice::playCDDATrack(unsigned track, eastl::function &&callback) { + Kernel::assert(m_callback == nullptr, "CDRomDevice::playCDDATrack called with pending action"); + s_playCDDAAction.start(this, track, true, eastl::move(callback)); +} + +void psyqo::CDRomDevice::playCDDATrack(MSF start, eastl::function &&callback) { + Kernel::assert(m_callback == nullptr, "CDRomDevice::playCDDATrack called with pending action"); + s_playCDDAAction.start(this, start, true, eastl::move(callback)); +} + +void psyqo::CDRomDevice::playCDDADisc(unsigned track, eastl::function &&callback) { + Kernel::assert(m_callback == nullptr, "CDRomDevice::playCDDADisc called with pending action"); + s_playCDDAAction.start(this, track, false, eastl::move(callback)); +} + +void psyqo::CDRomDevice::playCDDADisc(MSF start, eastl::function &&callback) { + Kernel::assert(m_callback == nullptr, "CDRomDevice::playCDDADisc called with pending action"); + s_playCDDAAction.start(this, start, false, eastl::move(callback)); +} + +void psyqo::CDRomDevice::resumeCDDA(eastl::function &&callback) { + Kernel::assert(m_callback == nullptr, "CDRomDevice::resumeCDDA called with pending action"); + s_playCDDAAction.start(this, eastl::move(callback)); +} + +void psyqo::CDRomDevice::pauseCDDA() { + MaskedIRQ masked; + // If no action is in progress, it means we got + // raced to the end of the track and/or disc, and + // we should just ignore this. + if (m_action == nullptr) return; + // If we aren't actually playing audio, that's + // a fatal error. + Kernel::assert(m_state == 100, "CDRomDevice::pauseCDDA called while not playing"); + m_state = 101; + eastl::atomic_signal_fence(eastl::memory_order_release); + Hardware::CDRom::Command.send(Hardware::CDRom::CDL::PAUSE); +} + +void psyqo::CDRomDevice::stopCDDA() { + MaskedIRQ masked; + // If no action is in progress, it means we got + // raced to the end of the track and/or disc, and + // we should just ignore this. + if (m_action == nullptr) return; + // If we aren't actually playing audio, that's + // a fatal error. + Kernel::assert(m_state == 100, "CDRomDevice::stopCDDA called while not playing"); + m_state = 101; + eastl::atomic_signal_fence(eastl::memory_order_release); + Hardware::CDRom::Command.send(Hardware::CDRom::CDL::STOP); +} + +void psyqo::CDRomDevice::getPlaybackLocation(eastl::function &&callback) { + MaskedIRQ masked; + Kernel::assert(m_locationCallback == nullptr, "CDRomDevice::getPlaybackLocation while another one is pending"); + if (m_action == nullptr) { + Kernel::queueCallbackFromISR([callback = eastl::move(callback)]() { callback(nullptr); }); + return; + } + m_locationCallback = eastl::move(callback); + m_locationPtr = &m_locationStorage; + m_pendingGetLocation = true; + eastl::atomic_signal_fence(eastl::memory_order_release); + Hardware::CDRom::Command.send(Hardware::CDRom::CDL::GETLOCP); +} + +void psyqo::CDRomDevice::getPlaybackLocation(PlaybackLocation *location, + eastl::function &&callback) { + MaskedIRQ masked; + Kernel::assert(m_locationCallback == nullptr, "CDRomDevice::getPlaybackLocation while another one is pending"); + if (m_action == nullptr) { + Kernel::queueCallbackFromISR([callback = eastl::move(callback)]() { callback(nullptr); }); + return; + } + m_locationCallback = eastl::move(callback); + m_locationPtr = location ? location : &m_locationStorage; + m_pendingGetLocation = true; + eastl::atomic_signal_fence(eastl::memory_order_release); + Hardware::CDRom::Command.send(Hardware::CDRom::CDL::GETLOCP); +} + +psyqo::TaskQueue::Task psyqo::CDRomDevice::scheduleGetPlaybackLocation(PlaybackLocation *location) { + return TaskQueue::Task([this, location](auto task) { + getPlaybackLocation(location, [task](PlaybackLocation *loc) { task->complete(loc != nullptr); }); + }); +} + +psyqo::CDRomDevice::PlaybackLocation *psyqo::CDRomDevice::ActionBase::getPendingLocationPtr() const { + return m_device->m_pendingGetLocation ? m_device->m_locationPtr : nullptr; +} + +void psyqo::CDRomDevice::ActionBase::queueGetLocationCallback(bool success) { + auto device = m_device; + device->m_pendingGetLocation = false; + if (!success) device->m_locationPtr = nullptr; + Kernel::queueCallbackFromISR([device]() { + auto callback = eastl::move(device->m_locationCallback); + callback(device->m_locationPtr); + }); +} + +void psyqo::CDRomDevice::setVolume(uint8_t leftToLeft, uint8_t rightToLeft, uint8_t leftToRight, uint8_t rightToRight) { + MaskedIRQ masked; + Hardware::CDRom::LeftToLeftVolume = leftToLeft; + Hardware::CDRom::RightToLeftVolume = rightToLeft; + Hardware::CDRom::LeftToRightVolume = leftToRight; + Hardware::CDRom::RightToRightVolume = rightToRight; + Hardware::CDRom::VolumeSettings = 0x20; +} diff --git a/src/mips/psyqo/src/cdrom-device-muteunmute.cpp b/src/mips/psyqo/src/cdrom-device-muteunmute.cpp new file mode 100644 index 000000000..539bed24c --- /dev/null +++ b/src/mips/psyqo/src/cdrom-device-muteunmute.cpp @@ -0,0 +1,107 @@ +/* + +MIT License + +Copyright (c) 2022 PCSX-Redux authors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +*/ + +#include + +#include "psyqo/cdrom-device.hh" +#include "psyqo/hardware/cdrom.hh" +#include "psyqo/kernel.hh" + +namespace { + +enum class MuteActionState : uint8_t { + IDLE, + MUTE, +}; + +class MuteAction : public psyqo::CDRomDevice::Action { + public: + MuteAction() : Action("MuteAction") {} + void start(psyqo::CDRomDevice *device, eastl::function &&callback) { + psyqo::Kernel::assert(getState() == MuteActionState::IDLE, + "CDRomDevice::mute() called while another action is in progress"); + registerMe(device); + setCallback(eastl::move(callback)); + setState(MuteActionState::MUTE); + eastl::atomic_signal_fence(eastl::memory_order_release); + psyqo::Hardware::CDRom::Command.send(psyqo::Hardware::CDRom::CDL::MUTE); + } + bool complete(const psyqo::CDRomDevice::Response &) override { + setSuccess(true); + return true; + } +}; + +MuteAction s_muteAction; + +} // namespace + +void psyqo::CDRomDevice::mute(eastl::function &&callback) { + Kernel::assert(m_callback == nullptr, "CDRomDevice::mute called with pending action"); + s_muteAction.start(this, eastl::move(callback)); +} + +psyqo::TaskQueue::Task psyqo::CDRomDevice::scheduleMute() { + return TaskQueue::Task([this](auto task) { mute([task](bool success) { task->complete(success); }); }); +} + +namespace { + +enum class UnmuteActionState : uint8_t { + IDLE, + UNMUTE, +}; + +class UnmuteAction : public psyqo::CDRomDevice::Action { + public: + UnmuteAction() : Action("UnmuteAction") {} + void start(psyqo::CDRomDevice *device, eastl::function &&callback) { + psyqo::Kernel::assert(getState() == UnmuteActionState::IDLE, + "CDRomDevice::unmute() called while another action is in progress"); + registerMe(device); + setCallback(eastl::move(callback)); + setState(UnmuteActionState::UNMUTE); + eastl::atomic_signal_fence(eastl::memory_order_release); + psyqo::Hardware::CDRom::Command.send(psyqo::Hardware::CDRom::CDL::UNMUTE); + } + bool complete(const psyqo::CDRomDevice::Response &) override { + setSuccess(true); + return true; + } +}; + +UnmuteAction s_unmuteAction; + +} // namespace + +void psyqo::CDRomDevice::unmute(eastl::function &&callback) { + Kernel::assert(m_callback == nullptr, "CDRomDevice::unmute called with pending action"); + s_unmuteAction.start(this, eastl::move(callback)); +} + +psyqo::TaskQueue::Task psyqo::CDRomDevice::scheduleUnmute() { + return TaskQueue::Task([this](auto task) { unmute([task](bool success) { task->complete(success); }); }); +} diff --git a/src/mips/psyqo/src/cdrom-device-readsectors.cpp b/src/mips/psyqo/src/cdrom-device-readsectors.cpp new file mode 100644 index 000000000..977d72a3a --- /dev/null +++ b/src/mips/psyqo/src/cdrom-device-readsectors.cpp @@ -0,0 +1,154 @@ +/* + +MIT License + +Copyright (c) 2022 PCSX-Redux authors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +*/ + +#include + +#include "common/hardware/dma.h" +#include "psyqo/cdrom-device.hh" +#include "psyqo/hardware/cdrom.hh" +#include "psyqo/hardware/sbus.hh" +#include "psyqo/kernel.hh" +#include "psyqo/msf.hh" + +namespace { + +enum class ReadSectorsActionState : uint8_t { + IDLE, + SETLOC, + SETMODE, + READ, + READ_ACK, + PAUSE, + PAUSE_ACK, +}; + +class ReadSectorsAction : public psyqo::CDRomDevice::Action { + public: + ReadSectorsAction() : Action("ReadSectorsAction") {} + void start(psyqo::CDRomDevice *device, uint32_t sector, uint32_t count, void *buffer, + eastl::function &&callback) { + psyqo::Kernel::assert(getState() == ReadSectorsActionState::IDLE, + "CDRomDevice::readSectors() called while another action is in progress"); + registerMe(device); + setCallback(eastl::move(callback)); + setState(ReadSectorsActionState::SETLOC); + m_count = count; + m_ptr = reinterpret_cast(buffer); + eastl::atomic_signal_fence(eastl::memory_order_release); + psyqo::MSF msf(sector + 150); + uint8_t bcd[3]; + msf.toBCD(bcd); + psyqo::Hardware::CDRom::Command.send(psyqo::Hardware::CDRom::CDL::SETLOC, bcd[0], bcd[1], bcd[2]); + } + bool dataReady(const psyqo::CDRomDevice::Response &) override { + psyqo::Kernel::assert(getState() == ReadSectorsActionState::READ_ACK, + "ReadSectorsAction got CDROM dataReady in wrong state"); + psyqo::Hardware::CDRom::Ctrl.throwAway(); + psyqo::Hardware::CDRom::DataRequest = 0; + psyqo::Hardware::CDRom::InterruptControl.throwAway(); + psyqo::Hardware::CDRom::DataRequest = 0x80; + psyqo::Hardware::SBus::Dev5Ctrl = 0x20943; + psyqo::Hardware::SBus::ComCtrl = 0x132c; + eastl::atomic_signal_fence(eastl::memory_order_acquire); + DMA_CTRL[DMA_CDROM].MADR = reinterpret_cast(m_ptr); + DMA_CTRL[DMA_CDROM].BCR = 512 | 0x10000; + DMA_CTRL[DMA_CDROM].CHCR = 0x11000000; + m_ptr += 2048; + if (--m_count == 0) { + setState(ReadSectorsActionState::PAUSE); + psyqo::Hardware::CDRom::Command.send(psyqo::Hardware::CDRom::CDL::PAUSE); + } + eastl::atomic_signal_fence(eastl::memory_order_release); + return false; + } + bool complete(const psyqo::CDRomDevice::Response &) override { + psyqo::Kernel::assert(getState() == ReadSectorsActionState::PAUSE_ACK, + "ReadSectorsAction got CDROM complete in wrong state"); + setSuccess(true); + return true; + } + bool acknowledge(const psyqo::CDRomDevice::Response &) override { + switch (getState()) { + case ReadSectorsActionState::SETLOC: + setState(ReadSectorsActionState::SETMODE); + psyqo::Hardware::CDRom::Command.send(psyqo::Hardware::CDRom::CDL::SETMODE, 0x80); + break; + case ReadSectorsActionState::SETMODE: + setState(ReadSectorsActionState::READ); + psyqo::Hardware::CDRom::Command.send(psyqo::Hardware::CDRom::CDL::READN); + break; + case ReadSectorsActionState::READ: + setState(ReadSectorsActionState::READ_ACK); + break; + case ReadSectorsActionState::PAUSE: + setState(ReadSectorsActionState::PAUSE_ACK); + break; + default: + psyqo::Kernel::abort("ReadSectorsAction got CDROM acknowledge in wrong state"); + break; + } + return false; + } + + private: + uint32_t m_count = 0; + uint8_t *m_ptr = nullptr; +}; + +ReadSectorsAction s_readSectorsAction; + +} // namespace + +void psyqo::CDRomDevice::readSectors(uint32_t sector, uint32_t count, void *buffer, + eastl::function &&callback) { + Kernel::assert(m_callback == nullptr, "CDRomDevice::readSectors called with pending action"); + s_readSectorsAction.start(this, sector, count, buffer, eastl::move(callback)); +} + +psyqo::TaskQueue::Task psyqo::CDRomDevice::scheduleReadSectors(uint32_t sector, uint32_t count, void *buffer) { + if (count == 0) { + return TaskQueue::Task([this](auto task) { task->complete(true); }); + } + uint32_t *storage = reinterpret_cast(buffer); + storage[0] = sector; + storage[1] = count; + return TaskQueue::Task([this, buffer](auto task) { + uint32_t *storage = reinterpret_cast(buffer); + uint32_t sector = storage[0]; + uint32_t count = storage[1]; + readSectors(sector, count, buffer, [task](bool success) { task->complete(success); }); + }); +} + +bool psyqo::CDRomDevice::readSectorsBlocking(uint32_t sector, uint32_t count, void *buffer, GPU &gpu) { + Kernel::assert(m_callback == nullptr, "CDRomDevice::readSectorsBlocking called with pending action"); + bool success = false; + { + BlockingAction blocking(this, gpu); + s_readSectorsAction.start(this, sector, count, buffer, [&success](bool success_) { success = success_; }); + } + return success; +} diff --git a/src/mips/psyqo/src/cdrom-device-reset.cpp b/src/mips/psyqo/src/cdrom-device-reset.cpp new file mode 100644 index 000000000..8175311e1 --- /dev/null +++ b/src/mips/psyqo/src/cdrom-device-reset.cpp @@ -0,0 +1,91 @@ +/* + +MIT License + +Copyright (c) 2022 PCSX-Redux authors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +*/ + +#include + +#include "psyqo/cdrom-device.hh" +#include "psyqo/hardware/cdrom.hh" +#include "psyqo/kernel.hh" + +namespace { + +enum class ResetActionState : uint8_t { + IDLE, + RESET, + RESET_ACK, +}; + +class ResetAction : public psyqo::CDRomDevice::Action { + public: + ResetAction() : Action("ResetAction") {} + void start(psyqo::CDRomDevice *device, eastl::function &&callback) { + psyqo::Kernel::assert(getState() == ResetActionState::IDLE, + "CDRomDevice::reset() called while another action is in progress"); + registerMe(device); + setCallback(eastl::move(callback)); + setState(ResetActionState::RESET); + eastl::atomic_signal_fence(eastl::memory_order_release); + psyqo::Hardware::CDRom::Cause = 0x1f; + psyqo::Hardware::CDRom::CauseMask = 0x1f; + psyqo::Hardware::CDRom::Command.send(psyqo::Hardware::CDRom::CDL::INIT); + } + bool complete(const psyqo::CDRomDevice::Response &) override { + psyqo::Kernel::assert(getState() == ResetActionState::RESET_ACK, + "ResetAction got CDROM complete in wrong state"); + setSuccess(true); + return true; + } + bool acknowledge(const psyqo::CDRomDevice::Response &) override { + psyqo::Kernel::assert(getState() == ResetActionState::RESET, + "ResetAction got CDROM acknowledge in wrong state"); + setState(ResetActionState::RESET_ACK); + return false; + } +}; + +ResetAction s_resetAction; + +} // namespace + +void psyqo::CDRomDevice::reset(eastl::function &&callback) { + Kernel::assert(m_callback == nullptr, "CDRomDevice::reset called with pending action"); + s_resetAction.start(this, eastl::move(callback)); +} + +psyqo::TaskQueue::Task psyqo::CDRomDevice::scheduleReset() { + return TaskQueue::Task([this](auto task) { reset([task](bool success) { task->complete(success); }); }); +} + +bool psyqo::CDRomDevice::resetBlocking(GPU &gpu) { + Kernel::assert(m_callback == nullptr, "CDRomDevice::resetBlocking called with pending action"); + unsigned size = 0; + bool success = false; + { + BlockingAction blocking(this, gpu); + s_resetAction.start(this, [&success](bool success_) { success = success_; }); + } + return success; +} diff --git a/src/mips/psyqo/src/cdrom-device-toc.cpp b/src/mips/psyqo/src/cdrom-device-toc.cpp new file mode 100644 index 000000000..d8a5514d4 --- /dev/null +++ b/src/mips/psyqo/src/cdrom-device-toc.cpp @@ -0,0 +1,177 @@ +/* + +MIT License + +Copyright (c) 2022 PCSX-Redux authors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +*/ + +#include + +#include "psyqo/cdrom-device.hh" +#include "psyqo/gpu.hh" +#include "psyqo/hardware/cdrom.hh" +#include "psyqo/kernel.hh" +#include "psyqo/msf.hh" + +namespace { + +enum class GetTNActionEnum : uint8_t { + IDLE, + GETTN, +}; + +class GetTNAction : public psyqo::CDRomDevice::Action { + public: + GetTNAction() : Action("GetTNAction") {} + void start(psyqo::CDRomDevice *device, unsigned *size, eastl::function &&callback) { + psyqo::Kernel::assert(getState() == GetTNActionEnum::IDLE, + "CDRomDevice::getTOCSize() called while another action is in progress"); + registerMe(device); + setCallback(eastl::move(callback)); + setState(GetTNActionEnum::GETTN); + m_size = size; + eastl::atomic_signal_fence(eastl::memory_order_release); + psyqo::Hardware::CDRom::Command.send(psyqo::Hardware::CDRom::CDL::GETTN); + } + bool acknowledge(const psyqo::CDRomDevice::Response &response) override { + *m_size = response[2]; + setSuccess(true); + return true; + } + + private: + unsigned *m_size = nullptr; +}; + +GetTNAction s_getTNAction; + +} // namespace + +void psyqo::CDRomDevice::getTOCSize(unsigned *size, eastl::function &&callback) { + Kernel::assert(m_callback == nullptr, "CDRomDevice::getTOCSize called with pending action"); + s_getTNAction.start(this, size, eastl::move(callback)); +} + +psyqo::TaskQueue::Task psyqo::CDRomDevice::scheduleGetTOCSize(unsigned *size) { + return TaskQueue::Task( + [this, size](auto task) { getTOCSize(size, [task](bool success) { task->complete(success); }); }); +} + +unsigned psyqo::CDRomDevice::getTOCSizeBlocking(GPU &gpu) { + Kernel::assert(m_callback == nullptr, "CDRomDevice::getTOCSizeBlocking called with pending action"); + unsigned size = 0; + bool success = false; + { + BlockingAction blocking(this, gpu); + s_getTNAction.start(this, &size, [&success](bool success_) { success = success_; }); + } + if (!success) return 0; + return size; +} + +namespace { + +enum class ReadTOCActionState : uint8_t { + IDLE, + GETTN, + GETTD, +}; + +class ReadTOCAction : public psyqo::CDRomDevice::Action { + public: + ReadTOCAction() : Action("ReadTOCAction") {} + void start(psyqo::CDRomDevice *device, psyqo::MSF *toc, unsigned size, eastl::function &&callback) { + psyqo::Kernel::assert(getState() == ReadTOCActionState::IDLE, + "CDRomDevice::readTOC() called while another action is in progress"); + registerMe(device); + setCallback(eastl::move(callback)); + setState(ReadTOCActionState::GETTN); + m_toc = toc; + m_size = size; + eastl::atomic_signal_fence(eastl::memory_order_release); + psyqo::Hardware::CDRom::Command.send(psyqo::Hardware::CDRom::CDL::GETTN); + } + bool acknowledge(const psyqo::CDRomDevice::Response &response) override { + switch (getState()) { + case ReadTOCActionState::GETTN: + setState(ReadTOCActionState::GETTD); + m_currentTrack = response[1]; + m_lastTrack = response[2]; + psyqo::Hardware::CDRom::Command.send(psyqo::Hardware::CDRom::CDL::GETTD, psyqo::itob(1)); + break; + case ReadTOCActionState::GETTD: { + psyqo::MSF &msf = m_toc[m_currentTrack]; + msf.m = psyqo::btoi(response[1]); + msf.s = psyqo::btoi(response[2]); + msf.f = 0; + m_currentTrack++; + if ((m_currentTrack <= m_lastTrack) && (m_currentTrack < m_size)) { + psyqo::Hardware::CDRom::Command.send(psyqo::Hardware::CDRom::CDL::GETTD, + psyqo::itob(m_currentTrack)); + } else { + setSuccess(true); + return true; + } + } break; + default: + psyqo::Kernel::abort("ReadTOCAction got CDROM acknowledge in wrong state"); + break; + } + return false; + } + psyqo::MSF *m_toc = nullptr; + unsigned m_size = 0; + uint8_t m_currentTrack = 0; + uint8_t m_lastTrack = 0; +}; + +ReadTOCAction s_readTOCAction; + +} // namespace + +void psyqo::CDRomDevice::readTOC(MSF *toc, unsigned size, eastl::function &&callback) { + Kernel::assert(m_callback == nullptr, "CDRomDevice::readTOC called with pending action"); + s_readTOCAction.start(this, toc, size, eastl::move(callback)); +} + +psyqo::TaskQueue::Task psyqo::CDRomDevice::scheduleReadTOC(MSF *toc, unsigned size) { + if (size == 0) { + return TaskQueue::Task([this](auto task) { task->complete(true); }); + } + size = eastl::min(size, 100u); + toc[0].m = size; + return TaskQueue::Task([this, toc](auto task) { + unsigned size = toc[0].m; + toc[0].m = 0; + readTOC(toc, size, [task](bool success) { task->complete(success); }); + }); +} + +bool psyqo::CDRomDevice::readTOCBlocking(MSF *toc, unsigned size, GPU &gpu) { + Kernel::assert(m_callback == nullptr, "CDRomDevice::readTOCBlocking called with pending action"); + bool success = false; + { + BlockingAction blocking(this, gpu); + readTOC(toc, size, [&success](bool success_) { success = success_; }); + } + return success; +} diff --git a/src/mips/psyqo/src/cdrom-device.cpp b/src/mips/psyqo/src/cdrom-device.cpp index b650e67b7..0a97090eb 100644 --- a/src/mips/psyqo/src/cdrom-device.cpp +++ b/src/mips/psyqo/src/cdrom-device.cpp @@ -28,14 +28,12 @@ SOFTWARE. #include -#include "common/hardware/dma.h" #include "common/kernel/events.h" #include "common/syscalls/syscalls.h" +#include "psyqo/gpu.hh" #include "psyqo/hardware/cdrom.hh" #include "psyqo/hardware/cpu.hh" -#include "psyqo/hardware/sbus.hh" #include "psyqo/kernel.hh" -#include "psyqo/msf.hh" void psyqo::CDRomDevice::prepare() { Hardware::CPU::IMask.set(Hardware::CPU::IRQ::CDRom); @@ -50,41 +48,23 @@ void psyqo::CDRomDevice::prepare() { m_event = Kernel::openEvent(EVENT_CDROM, 0x1000, EVENT_MODE_CALLBACK, eastl::move(callback)); syscall_enableEvent(m_event); } + setVolume(0x80, 0x00, 0x00, 0x80); } psyqo::CDRomDevice::~CDRomDevice() { Kernel::abort("CDRomDevice can't be destroyed (yet)"); } -void psyqo::CDRomDevice::reset(eastl::function &&callback) { - Kernel::assert(m_callback == nullptr, "Only one read allowed at a time"); - Kernel::assert(m_action == NONE, "CDRom state machine is busy"); - m_callback = eastl::move(callback); - m_action = RESET; - eastl::atomic_signal_fence(eastl::memory_order_release); - Hardware::CDRom::Cause = 0x1f; - Hardware::CDRom::CauseMask = 0x1f; - Hardware::CDRom::Command.send(Hardware::CDRom::CDL::INIT); +void psyqo::CDRomDevice::switchAction(ActionBase *action) { + Kernel::assert(m_action == nullptr, "CDRomDevice can only have one action active at a given time"); + m_action = action; } -psyqo::TaskQueue::Task psyqo::CDRomDevice::scheduleReset() { - return TaskQueue::Task([this](auto task) { reset([task](bool success) { task->complete(success); }); }); -} - -void psyqo::CDRomDevice::readSectors(uint32_t sector, uint32_t count, void *buffer, - eastl::function &&callback) { - Kernel::assert(m_callback == nullptr, "Only one action allowed at a time"); - Kernel::assert(m_action == NONE, "CDRom state machine is busy"); - m_callback = eastl::move(callback); - m_action = SETLOC; - m_count = count; - m_ptr = reinterpret_cast(buffer); - eastl::atomic_signal_fence(eastl::memory_order_release); - MSF msf(sector + 150); - uint8_t bcd[3]; - msf.toBCD(bcd); - Hardware::CDRom::Command.send(Hardware::CDRom::CDL::SETLOC, bcd[0], bcd[1], bcd[2]); +void psyqo::CDRomDevice::ActionBase::queueCallbackFromISR(bool success) { + Kernel::assert(!!m_device->m_callback, "CDRomDevice::queueCallbackFromISR() called with no callback"); + Kernel::queueCallbackFromISR([device = m_device, success]() { device->m_callback(success); }); } void psyqo::CDRomDevice::irq() { + Kernel::assert(m_action != nullptr, "CDRomDevice::irq() called with no action - spurious interrupt?"); uint8_t cause = Hardware::CDRom::Cause; if (cause & 7) { @@ -95,94 +75,126 @@ void psyqo::CDRomDevice::irq() { Hardware::CDRom::Cause = 0x18; } + bool callCallback = false; + Response response; + while ((Hardware::CDRom::Ctrl.access() & 0x20) && (response.size() < 16)) { + response.push_back(Hardware::CDRom::Response); + } + +#ifdef DEBUG_CDROM_RESPONSES + if (m_blocking) { + ramsyscall_printf("Got CD-Rom response:"); + for (auto byte : response) { + ramsyscall_printf(" %02x", byte); + } + syscall_puts("\n"); + } else { + Kernel::queueCallbackFromISR([response]() { + ramsyscall_printf("Got CD-Rom response:"); + for (auto byte : response) { + ramsyscall_printf(" %02x", byte); + } + syscall_puts("\n"); + }); + } +#endif + switch (cause & 7) { case 1: - dataReady(); + callCallback = m_action->dataReady(response); break; case 2: - complete(); + callCallback = m_action->complete(response); break; case 3: - acknowledge(); + callCallback = m_action->acknowledge(response); break; case 4: - end(); + callCallback = m_action->end(response); break; - case 5: - discError(); + case 5: { + m_success = false; + callCallback = true; +#ifdef DEBUG_CDROM_ERRORS + m_callback = [callback = eastl::move(m_callback), name = m_action->name(), + response = eastl::move(response)](bool) { + ramsyscall_printf("Got CD-Rom error during action %s:", name); + for (auto byte : response) { + ramsyscall_printf(" %02x", byte); + } + syscall_puts("\n"); + callback(false); + }; +#endif + } break; + default: + Kernel::abort("CDRomDevice::irq() invoked with unknown cause"); break; } -} -void psyqo::CDRomDevice::dataReady() { - uint8_t status = Hardware::CDRom::Response; - Hardware::CDRom::Ctrl.throwAway(); - Hardware::CDRom::DataRequest = 0; - Hardware::CDRom::InterruptControl.throwAway(); - Hardware::CDRom::DataRequest = 0x80; - Hardware::SBus::Dev5Ctrl = 0x20943; - Hardware::SBus::ComCtrl = 0x132c; - eastl::atomic_signal_fence(eastl::memory_order_acquire); - DMA_CTRL[DMA_CDROM].MADR = reinterpret_cast(m_ptr); - DMA_CTRL[DMA_CDROM].BCR = 512 | 0x10000; - DMA_CTRL[DMA_CDROM].CHCR = 0x11000000; - m_ptr += 2048; - if (--m_count == 0) { - m_action = PAUSE; - Hardware::CDRom::Command.send(Hardware::CDRom::CDL::PAUSE); + if (callCallback) { + Kernel::assert(!!m_callback, "Wrong CDRomDevice state"); + m_action = nullptr; + if (m_blocking) { + actionComplete(); + } else { + eastl::atomic_signal_fence(eastl::memory_order_acquire); + Kernel::queueCallbackFromISR([this]() { actionComplete(); }); + } } - eastl::atomic_signal_fence(eastl::memory_order_release); } -void psyqo::CDRomDevice::complete() { - switch (m_action) { - case RESET: - case PAUSE: - eastl::atomic_signal_fence(eastl::memory_order_acquire); - Kernel::assert(!!m_callback, "Wrong CDRomDevice state"); - Kernel::queueCallbackFromISR([this]() { - auto callback = eastl::move(m_callback); - m_action = NONE; - callback(true); - }); - break; - default: - Kernel::abort("CDRomDevice::complete() called in wrong state"); - break; - } +psyqo::CDRomDevice::BlockingAction::BlockingAction(CDRomDevice *device, GPU &gpu) : m_device(device), m_gpu(gpu) { + device->m_blocking = true; + Hardware::CPU::IMask.clear(Hardware::CPU::IRQ::CDRom); + Hardware::CPU::flushWriteQueue(); } -void psyqo::CDRomDevice::acknowledge() { - uint8_t status = Hardware::CDRom::Response; - switch (m_action) { - case RESET: - break; - case SETLOC: - m_action = SETMODE; - Hardware::CDRom::Command.send(Hardware::CDRom::CDL::SETMODE, 0x80); - break; - case SETMODE: - m_action = READ; - Hardware::CDRom::Command.send(Hardware::CDRom::CDL::READN); - break; - case READ: - break; - case PAUSE: - break; - default: - Kernel::abort("Not implemented"); - break; +psyqo::CDRomDevice::BlockingAction::~BlockingAction() { + auto device = m_device; + auto gpu = &m_gpu; + while (device->m_state != 0) { + if (Hardware::CPU::IReg.isSet(Hardware::CPU::IRQ::CDRom)) { + Hardware::CPU::IReg.clear(Hardware::CPU::IRQ::CDRom); + device->irq(); + } + gpu->pumpCallbacks(); } + device->m_blocking = false; + Hardware::CPU::IMask.set(Hardware::CPU::IRQ::CDRom); } -void psyqo::CDRomDevice::end() { Kernel::abort("Not implemented"); } +psyqo::CDRomDevice::MaskedIRQ::MaskedIRQ() { + Hardware::CPU::IMask.clear(Hardware::CPU::IRQ::CDRom); + Hardware::CPU::flushWriteQueue(); +} -void psyqo::CDRomDevice::discError() { - eastl::atomic_signal_fence(eastl::memory_order_acquire); - Kernel::assert(!!m_callback, "Wrong CDRomDevice state"); - Kernel::queueCallbackFromISR([this]() { - auto callback = eastl::move(m_callback); - m_action = NONE; - callback(false); - }); +psyqo::CDRomDevice::MaskedIRQ::~MaskedIRQ() { Hardware::CPU::IMask.set(Hardware::CPU::IRQ::CDRom); } + +void psyqo::CDRomDevice::actionComplete() { + auto callback = eastl::move(m_callback); + m_callback = nullptr; + auto success = m_success; + m_success = false; + m_state = 0; + callback(success); +} + +void psyqo::CDRomDevice::ActionBase::setCallback(eastl::function &&callback) { + auto &deviceCallback = m_device->m_callback; + Kernel::assert(!deviceCallback && m_device->m_state == 0, "Action setup called with pending action"); + m_device->m_callback = eastl::move(callback); +} +void psyqo::CDRomDevice::ActionBase::setSuccess(bool success) { m_device->m_success = success; } +bool psyqo::CDRomDevice::ActionBase::dataReady(const Response &) { + Kernel::abort("Action::dataReady() not implemented - spurious interrupt?"); +} +bool psyqo::CDRomDevice::ActionBase::complete(const Response &) { + Kernel::abort("Action::complete() not implemented - spurious interrupt?"); +} +bool psyqo::CDRomDevice::ActionBase::acknowledge(const Response &) { + Kernel::abort("Action::acknowledge() not implemented - spurious interrupt?"); +} +bool psyqo::CDRomDevice::ActionBase::end(const Response &) { + Kernel::abort("Action::end() not implemented - spurious interrupt?"); } diff --git a/src/mips/psyqo/src/hardware/cdrom.cpp b/src/mips/psyqo/src/hardware/cdrom.cpp index 07873afa6..9e4a132b3 100644 --- a/src/mips/psyqo/src/hardware/cdrom.cpp +++ b/src/mips/psyqo/src/hardware/cdrom.cpp @@ -29,9 +29,32 @@ SOFTWARE. psyqo::Hardware::Register<0x0800, uint8_t, psyqo::Hardware::WriteQueue::Bypass> psyqo::Hardware::CDRom::Ctrl; psyqo::Hardware::Register<0x0801, uint8_t, psyqo::Hardware::WriteQueue::Bypass> psyqo::Hardware::CDRom::Response; psyqo::Hardware::Register<0x0802, uint8_t, psyqo::Hardware::WriteQueue::Bypass> psyqo::Hardware::CDRom::Fifo; -psyqo::Hardware::Register<0x0803, uint8_t, psyqo::Hardware::WriteQueue::Bypass> psyqo::Hardware::CDRom::InterruptControl; +psyqo::Hardware::Register<0x0803, uint8_t, psyqo::Hardware::WriteQueue::Bypass> + psyqo::Hardware::CDRom::InterruptControl; psyqo::Hardware::CDRom::CommandFifo psyqo::Hardware::CDRom::Command; -psyqo::Hardware::Register<0, uint8_t, psyqo::Hardware::WriteQueue::Bypass, psyqo::Hardware::CDRom::Access, 0>> psyqo::Hardware::CDRom::DataRequest; -psyqo::Hardware::Register<0, uint8_t, psyqo::Hardware::WriteQueue::Bypass, psyqo::Hardware::CDRom::Access, 1>> psyqo::Hardware::CDRom::CauseMask; -psyqo::Hardware::Register<0, uint8_t, psyqo::Hardware::WriteQueue::Bypass, psyqo::Hardware::CDRom::Access, 1>> psyqo::Hardware::CDRom::Cause; +psyqo::Hardware::Register<0, uint8_t, psyqo::Hardware::WriteQueue::Bypass, + psyqo::Hardware::CDRom::Access, 0>> + psyqo::Hardware::CDRom::DataRequest; +psyqo::Hardware::Register<0, uint8_t, psyqo::Hardware::WriteQueue::Bypass, + psyqo::Hardware::CDRom::Access, 1>> + psyqo::Hardware::CDRom::CauseMask; +psyqo::Hardware::Register<0, uint8_t, psyqo::Hardware::WriteQueue::Bypass, + psyqo::Hardware::CDRom::Access, 1>> + psyqo::Hardware::CDRom::Cause; + +psyqo::Hardware::Register<0, uint8_t, psyqo::Hardware::WriteQueue::Bypass, + psyqo::Hardware::CDRom::Access, 2>> + psyqo::Hardware::CDRom::LeftToLeftVolume; +psyqo::Hardware::Register<0, uint8_t, psyqo::Hardware::WriteQueue::Bypass, + psyqo::Hardware::CDRom::Access, 2>> + psyqo::Hardware::CDRom::LeftToRightVolume; +psyqo::Hardware::Register<0, uint8_t, psyqo::Hardware::WriteQueue::Bypass, + psyqo::Hardware::CDRom::Access, 3>> + psyqo::Hardware::CDRom::RightToRightVolume; +psyqo::Hardware::Register<0, uint8_t, psyqo::Hardware::WriteQueue::Bypass, + psyqo::Hardware::CDRom::Access, 3>> + psyqo::Hardware::CDRom::RightToLeftVolume; +psyqo::Hardware::Register<0, uint8_t, psyqo::Hardware::WriteQueue::Bypass, + psyqo::Hardware::CDRom::Access, 3>> + psyqo::Hardware::CDRom::VolumeSettings; diff --git a/src/mips/psyqo/src/hardware/cpu.cpp b/src/mips/psyqo/src/hardware/cpu.cpp index 6dd0172b4..ae2a8a7c0 100644 --- a/src/mips/psyqo/src/hardware/cpu.cpp +++ b/src/mips/psyqo/src/hardware/cpu.cpp @@ -30,3 +30,6 @@ psyqo::Hardware::CPU::IRQReg<0x0070> psyqo::Hardware::CPU::IReg; psyqo::Hardware::CPU::IRQReg<0x0074> psyqo::Hardware::CPU::IMask; psyqo::Hardware::Register<0x00f0> psyqo::Hardware::CPU::DPCR; psyqo::Hardware::Register<0x00f4> psyqo::Hardware::CPU::DICR; + +psyqo::Hardware::Register<0x0000, uint32_t, psyqo::Hardware::WriteQueue::Bypass> + psyqo::Hardware::CPU::WriteQueueFlusher; diff --git a/src/mips/psyqo/src/kernel.cpp b/src/mips/psyqo/src/kernel.cpp index fe0767b3a..b370d7862 100644 --- a/src/mips/psyqo/src/kernel.cpp +++ b/src/mips/psyqo/src/kernel.cpp @@ -30,11 +30,13 @@ SOFTWARE. #include #include #include +#include #include #include "common/hardware/dma.h" #include "common/hardware/pcsxhw.h" #include "common/kernel/events.h" +#include "common/kernel/threads.h" #include "common/syscalls/syscalls.h" #include "common/util/encoder.hh" #include "psyqo/application.hh" @@ -84,8 +86,30 @@ int printfStub(const char* fmt, ...) { return r; } +eastl::array, 16> s_breakHandlers; +eastl::fixed_vector, 4> s_psyqoBreakHandlers; + +bool handleBreak(uint32_t code) { + unsigned category = code >> 10; + if (category >= 16) return false; + code &= 0x3ff; + auto& handler = s_breakHandlers[category]; + if (handler) return handler(code); + return false; +} + } // namespace +void psyqo::Kernel::setBreakHandler(unsigned category, eastl::function&& handler) { + Kernel::assert(category < 16, "setBreakHandler: invalid category"); + Kernel::assert(s_breakHandlers[category] == nullptr, "setBreakHandler: category already has a handler"); + s_breakHandlers[category] = eastl::move(handler); +} + +void psyqo::Kernel::queuePsyqoBreakHandler(eastl::function&& handler) { + s_psyqoBreakHandlers.push_back(eastl::move(handler)); +} + bool psyqo::Kernel::isKernelTakenOver() { return s_tookOverKernel; } extern "C" { @@ -99,6 +123,11 @@ void psyqoExceptionHandler(uint32_t ireg) { for (auto& handler : handlers) handler(); } } +void psyqoBreakHandler(uint32_t code) { + if (handleBreak(code)) return; + ramsyscall_printf("Unhandled break: %08x\n", code); + psyqo::Kernel::abort("Unhandled break"); +} void psyqoAssemblyExceptionHandler(); } @@ -296,9 +325,31 @@ void psyqo::Kernel::Internal::prepare(Application& application) { syscall_enqueueRCntIrqs(1); uint32_t event = syscall_openEvent(EVENT_DMA, 0x1000, EVENT_MODE_CALLBACK, dmaIRQ); syscall_enableEvent(event); + event = syscall_openEvent(0xf0000010, 0x1000, EVENT_MODE_CALLBACK, []() { + Process* processes = *reinterpret_cast(0x108); + Thread* currentThread = processes[0].thread; + unsigned exCode = currentThread->registers.Cause & 0x3c; + if (exCode != 0x24) return; + unsigned code = *reinterpret_cast(currentThread->registers.returnPC) >> 6; + if (handleBreak(code)) { + currentThread->registers.returnPC += 4; + syscall_returnFromException(); + } + }); + syscall_enableEvent(event); } else { + // Our exception handler will expect $k0 to be set to 0x1f80, + // in order to have a fast access to the hardware registers. + __asm__ __volatile__("lui $k0, 0x1f80"); queueIRQHandler(IRQ::DMA, dmaIRQ); } + setBreakHandler(14, [](uint32_t code) { + for (auto& handler : s_psyqoBreakHandlers) { + if (handler(code)) return true; + } + ramsyscall_printf("Unhandled psyqo break: %08x\n", code); + return false; + }); Hardware::CPU::IMask.set(Hardware::CPU::IRQ::DMA); dicr = Hardware::CPU::DICR; dicr &= 0xffffff; diff --git a/src/mips/psyqo/src/task.cpp b/src/mips/psyqo/src/task.cpp index 0b1338739..5d1050490 100644 --- a/src/mips/psyqo/src/task.cpp +++ b/src/mips/psyqo/src/task.cpp @@ -26,6 +26,8 @@ SOFTWARE. #include "psyqo/task.hh" +#include "psyqo/gpu.hh" + void psyqo::TaskQueue::reset() { m_queue.clear(); m_catch = nullptr; @@ -95,3 +97,7 @@ void psyqo::TaskQueue::runCatch() { } if (m_parent) m_parent->reject(); } + +psyqo::TaskQueue::Task psyqo::TaskQueue::DelayedTask(uint32_t delay, GPU& gpu) { + return Task([delay, &gpu](auto task) { gpu.armTimer(gpu.now() + delay, [task](auto) { task->resolve(); }); }); +} diff --git a/src/mips/psyqo/src/vector.s b/src/mips/psyqo/src/vector.s index 0df2105ef..926872215 100644 --- a/src/mips/psyqo/src/vector.s +++ b/src/mips/psyqo/src/vector.s @@ -31,6 +31,7 @@ SOFTWARE. .align 2 .global psyqoAssemblyExceptionHandler .global psyqoExceptionHandler + .global psyqoBreakHandler .global psyqoExceptionHandlerAdjustFrameCount .type psyqoAssemblyExceptionHandler, @function @@ -39,16 +40,19 @@ psyqoAssemblyExceptionHandler: sw $v1, 0x108($0) sw $a0, 0x10c($0) - mfc0 $a0, $13 /* $a0 = Cause */ + /* $k0 = hardware registers base, set globally */ + mfc0 $k1, $14 /* $k1 = EPC, will stay there until the end */ - lui $k0, 0x1f80 /* $k0 = hardware registers base, will stay there until the end */ + mfc0 $a0, $13 /* $a0 = Cause */ + li $at, 0x24 /* Prepare for break test in (a) */ lw $v1, 0($k1) /* $v1 = instruction that caused the exception */ - andi $a0, 0x7c /* Test for what kind of exception */ -.Lstop: /* psyqo will only support IRQs, aka 0 */ + andi $a0, 0x3c /* Test for what kind of exception */ + beq $a0, $at, .Lbreak /* (a) */ + li $at, 0x4a /* Prepare for cop2 test in (b) */ +.Lstop: /* Beyond break, psyqo will only support IRQs, aka 0 */ bnez $a0, .Lstop /* Anything else and we just stop - $a0 available again */ - srl $v1, 24 /* \ */ - andi $v1, 0xfe /* | Test if we were in a cop2 operation */ - li $at, 0x4a /* / */ + srl $v1, 24 /* | (b) */ + andi $v1, 0xfe /* |_ Test if we were in a cop2 operation */ lw $a0, 0x1070($k0) /* $a0 = IREG, which we will pass to our C++ handler */ bne $v1, $at, .LnoCOP2adjustmentNeeded nop /* $v1 available again */ @@ -57,7 +61,7 @@ psyqoAssemblyExceptionHandler: andi $v1, $a0, 1 /* Is it VBlank ? */ beqz $v1, .LnotVBlank andi $v1, $a0, 0xfffe - sw $v1, 0x1070($k0) /* ACK VBlank IRQ, $k0, $v1 no longer useful */ + sw $v1, 0x1070($k0) /* ACK VBlank IRQ, $v1 no longer useful */ psyqoExceptionHandlerAdjustFrameCount: /* Basically self modifying code here... */ lui $v1, 0 @@ -72,7 +76,15 @@ psyqoExceptionHandlerAdjustFrameCount: jr $k1 /* Exit the exception handler */ rfe +.Lbreak: + srl $a0, $v1, 6 + la $v1, psyqoBreakHandler + b .LcallCPlusPlus + addiu $k1, 4 + .LnotVBlank: + la $v1, psyqoExceptionHandler +.LcallCPlusPlus: /* We want to call into C++ now, so we need to save the rest of the registers */ sw $v0, 0x104($0) sw $a1, 0x110($0) @@ -91,8 +103,8 @@ psyqoExceptionHandlerAdjustFrameCount: sw $sp, 0x148($0) sw $ra, 0x14c($0) - /* Call the C++ exception handler while adjusting the stack */ - jal psyqoExceptionHandler + /* Call the C++ exception or break handler while adjusting the stack */ + jalr $v1 li $sp, 0x1000 - 16 /* Acknowledge all IRQs */ sw $0, 0x1070($k0) diff --git a/src/mips/psyqo/task.hh b/src/mips/psyqo/task.hh index 91334e09a..5c8c6ab19 100644 --- a/src/mips/psyqo/task.hh +++ b/src/mips/psyqo/task.hh @@ -31,6 +31,8 @@ SOFTWARE. namespace psyqo { +class GPU; + /** * @brief A task queue for processing tasks sequentially. * @@ -192,6 +194,16 @@ class TaskQueue { friend class TaskQueue; }; + /** + * @brief Creates a delayed task. + * + * @details This method is a convenience method to create a task that will + * be executed after a delay. The delay is specified in microseconds. + * + * @param delay The delay in microseconds. + */ + static Task DelayedTask(uint32_t delay, GPU &); + private: void runNext(); void runCatch(); diff --git a/third_party/EASTL/include/EASTL/internal/hashtable.h b/third_party/EASTL/include/EASTL/internal/hashtable.h index 03798633a..a9d3376c9 100644 --- a/third_party/EASTL/include/EASTL/internal/hashtable.h +++ b/third_party/EASTL/include/EASTL/internal/hashtable.h @@ -41,7 +41,7 @@ #include #include #include -#include +//#include EA_DISABLE_ALL_VC_WARNINGS() #include @@ -414,13 +414,13 @@ namespace eastl struct EASTL_API prime_rehash_policy { public: - float mfMaxLoadFactor; - float mfGrowthFactor; + static constexpr unsigned mfMaxLoadFactor = 1; + static constexpr unsigned mfGrowthFactor = 2; mutable uint32_t mnNextResize; public: - prime_rehash_policy(float fMaxLoadFactor = 1.f) - : mfMaxLoadFactor(fMaxLoadFactor), mfGrowthFactor(2.f), mnNextResize(0) { } + prime_rehash_policy() + : mnNextResize(0) { } float GetMaxLoadFactor() const { return mfMaxLoadFactor; } diff --git a/third_party/EASTL/include/EASTL/internal/thread_support.h b/third_party/EASTL/include/EASTL/internal/thread_support.h index 60272a986..682a93b0b 100644 --- a/third_party/EASTL/include/EASTL/internal/thread_support.h +++ b/third_party/EASTL/include/EASTL/internal/thread_support.h @@ -2,6 +2,7 @@ // Copyright (c) Electronic Arts Inc. All rights reserved. ///////////////////////////////////////////////////////////////////////////// +#if 0 #ifndef EASTL_INTERNAL_THREAD_SUPPORT_H #define EASTL_INTERNAL_THREAD_SUPPORT_H @@ -244,3 +245,5 @@ EA_RESTORE_VC_WARNING(); #endif // Header include guard + +#endif diff --git a/third_party/EASTL/source/assert.cpp b/third_party/EASTL/source/assert.cpp index 5c812a06b..4e399d12c 100644 --- a/third_party/EASTL/source/assert.cpp +++ b/third_party/EASTL/source/assert.cpp @@ -2,6 +2,7 @@ // Copyright (c) Electronic Arts Inc. All rights reserved. /////////////////////////////////////////////////////////////////////////////// +#if 0 #include #include @@ -96,17 +97,4 @@ namespace eastl } // namespace eastl - - - - - - - - - - - - - - +#endif diff --git a/third_party/EASTL/source/hashtable.cpp b/third_party/EASTL/source/hashtable.cpp index 8d31663c1..5c425a006 100644 --- a/third_party/EASTL/source/hashtable.cpp +++ b/third_party/EASTL/source/hashtable.cpp @@ -5,7 +5,7 @@ #include #include -#include // Not all compilers support and std::ceilf(), which we need below. +//#include // Not all compilers support and std::ceilf(), which we need below. #include @@ -105,7 +105,7 @@ namespace eastl { const uint32_t nPrime = *(eastl::upper_bound(gPrimeNumberArray, gPrimeNumberArray + kPrimeCount, nBucketCountHint) - 1); - mnNextResize = (uint32_t)ceilf(nPrime * mfMaxLoadFactor); + mnNextResize = nPrime * mfMaxLoadFactor; return nPrime; } @@ -118,7 +118,7 @@ namespace eastl { const uint32_t nPrime = *eastl::lower_bound(gPrimeNumberArray, gPrimeNumberArray + kPrimeCount, nBucketCountHint); - mnNextResize = (uint32_t)ceilf(nPrime * mfMaxLoadFactor); + mnNextResize = nPrime * mfMaxLoadFactor; return nPrime; } @@ -132,7 +132,7 @@ namespace eastl const uint32_t nMinBucketCount = (uint32_t)(nElementCount / mfMaxLoadFactor); const uint32_t nPrime = *eastl::lower_bound(gPrimeNumberArray, gPrimeNumberArray + kPrimeCount, nMinBucketCount); - mnNextResize = (uint32_t)ceilf(nPrime * mfMaxLoadFactor); + mnNextResize = nPrime * mfMaxLoadFactor; return nPrime; } @@ -151,19 +151,19 @@ namespace eastl if(nBucketCount == 1) // We force rehashing to occur if the bucket count is < 2. nBucketCount = 0; - float fMinBucketCount = (nElementCount + nElementAdd) / mfMaxLoadFactor; + auto fMinBucketCount = (nElementCount + nElementAdd) / mfMaxLoadFactor; if(fMinBucketCount > (float)nBucketCount) { fMinBucketCount = eastl::max_alt(fMinBucketCount, mfGrowthFactor * nBucketCount); const uint32_t nPrime = *eastl::lower_bound(gPrimeNumberArray, gPrimeNumberArray + kPrimeCount, (uint32_t)fMinBucketCount); - mnNextResize = (uint32_t)ceilf(nPrime * mfMaxLoadFactor); + mnNextResize = nPrime * mfMaxLoadFactor; return eastl::pair(true, nPrime); } else { - mnNextResize = (uint32_t)ceilf(nBucketCount * mfMaxLoadFactor); + mnNextResize = nBucketCount * mfMaxLoadFactor; return eastl::pair(false, (uint32_t)0); } } diff --git a/third_party/EASTL/source/string.cpp b/third_party/EASTL/source/string.cpp index ad15a109c..2347e104f 100644 --- a/third_party/EASTL/source/string.cpp +++ b/third_party/EASTL/source/string.cpp @@ -6,7 +6,7 @@ #include #include #include -#include +//#include namespace eastl diff --git a/third_party/EASTL/source/thread_support.cpp b/third_party/EASTL/source/thread_support.cpp index 547e58860..f8eeb1031 100644 --- a/third_party/EASTL/source/thread_support.cpp +++ b/third_party/EASTL/source/thread_support.cpp @@ -2,6 +2,7 @@ // Copyright (c) Electronic Arts Inc. All rights reserved. /////////////////////////////////////////////////////////////////////////////// +#if 0 #include #include @@ -109,17 +110,4 @@ namespace eastl } // namespace eastl - - - - - - - - - - - - - - +#endif