diff --git a/README.md b/README.md index 572f630..24c6873 100644 --- a/README.md +++ b/README.md @@ -71,9 +71,7 @@ to lowest priority: new data to the game list (see above) and reverse engineering all flash header formats used by games. - Fixing a bunch of stability bugs, such as the executable loader occasionally - crashing or the filesystem drivers not being thread safe (resulting in - filesystem corruption if e.g. a screenshot is taken while dumping data to the - hard drive). + crashing. - Adding support for installing arbitrary executables directly to the internal flash or a PCMCIA card, allowing the 573 BIOS to boot them automatically even with no CD-ROM drive present. diff --git a/assets/about.txt b/assets/about.txt index 50c956b..3bdf733 100644 --- a/assets/about.txt +++ b/assets/about.txt @@ -34,17 +34,17 @@ Special thanks: Copyright (C) 2022-2024 spicyjpeg -This program is free software: you can redistribute it and/or modify it under -the terms of the GNU General Public License as published by the Free Software +573in1 is free software: you can redistribute it and/or modify it under the +terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. -This program is distributed in the hope that it will be useful, but WITHOUT ANY +573in1 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with -this program. If not, see . +573in1. If not, see . [ Third-party licenses ] diff --git a/src/common/file/fat.cpp b/src/common/file/fat.cpp index ff78f98..691e498 100644 --- a/src/common/file/fat.cpp +++ b/src/common/file/fat.cpp @@ -21,6 +21,7 @@ #include "common/ide.hpp" #include "common/io.hpp" #include "common/util.hpp" +#include "ps1/system.h" #include "vendor/diskio.h" #include "vendor/ff.h" @@ -285,6 +286,10 @@ File *FATProvider::openFile(const char *path, uint32_t flags) { /* FatFs library API glue */ +static constexpr int _MUTEX_TIMEOUT = 30000000; + +static uint32_t _fatMutex = 0; + extern "C" DSTATUS disk_initialize(uint8_t drive) { #if 0 auto &dev = ide::devices[drive]; @@ -373,4 +378,40 @@ extern "C" uint32_t get_fattime(void) { return date.toDOSTime(); } +extern "C" int ff_mutex_create(int id) { + return true; +} + +extern "C" void ff_mutex_delete(int id) {} + +extern "C" int ff_mutex_take(int id) { + uint32_t mask = 1 << id; + + for (int timeout = _MUTEX_TIMEOUT; timeout > 0; timeout -= 10) { + { + util::ThreadCriticalSection sec; + __atomic_signal_fence(__ATOMIC_ACQUIRE); + + auto locked = _fatMutex & mask; + + if (!locked) { + _fatMutex |= mask; + flushWriteQueue(); + return true; + } + } + + delayMicroseconds(10); + } + + return false; +} + +extern "C" void ff_mutex_give(int id) { + util::CriticalSection sec; + + _fatMutex &= ~(1 << id); + flushWriteQueue(); +} + } diff --git a/src/common/ide.cpp b/src/common/ide.cpp index 8474f0f..7ae853c 100644 --- a/src/common/ide.cpp +++ b/src/common/ide.cpp @@ -71,7 +71,6 @@ const char *const DEVICE_ERROR_NAMES[]{ /* Utilities */ -#ifdef ENABLE_FULL_IDE_DRIVER static void _copyString(char *output, const uint16_t *input, size_t length) { // The strings in the identification block are byte-swapped and padded with // spaces. To make them printable, any span of consecutive space characters @@ -99,7 +98,6 @@ static void _copyString(char *output, const uint16_t *input, size_t length) { *(--output) = b; } } -#endif static DeviceError _senseDataToError(const SenseData &data) { auto key = data.senseKey & 15; @@ -727,6 +725,10 @@ DeviceError Device::poll(void) { } } +void Device::handleInterrupt(void) { + // TODO: use interrupts to yield instead of busy waiting +} + DeviceError Device::readData(void *data, uint64_t lba, size_t count) { util::assertAligned(data); @@ -754,6 +756,8 @@ DeviceError Device::writeData(const void *data, uint64_t lba, size_t count) { return _ataTransfer(reinterpret_cast(data), lba, count, true); } +#ifdef ENABLE_FULL_IDE_DRIVER + DeviceError Device::goIdle(bool standby) { if (!(flags & DEVICE_READY)) return NO_DRIVE; @@ -807,4 +811,6 @@ DeviceError Device::flushCache(void) { return _waitForIdle(); } +#endif + } diff --git a/src/common/ide.hpp b/src/common/ide.hpp index d65be46..89c1018 100644 --- a/src/common/ide.hpp +++ b/src/common/ide.hpp @@ -308,12 +308,15 @@ public: Device(uint32_t flags); DeviceError enumerate(void); DeviceError poll(void); + void handleInterrupt(void); DeviceError readData(void *data, uint64_t lba, size_t count); DeviceError writeData(const void *data, uint64_t lba, size_t count); +#ifdef ENABLE_FULL_IDE_DRIVER DeviceError goIdle(bool standby = false); DeviceError startStopUnit(ATAPIStartStopMode mode); DeviceError flushCache(void); +#endif }; extern const char *const DEVICE_ERROR_NAMES[]; diff --git a/src/common/spu.cpp b/src/common/spu.cpp index 1af3ab7..960bf41 100644 --- a/src/common/spu.cpp +++ b/src/common/spu.cpp @@ -60,6 +60,13 @@ void init(void) { SPU_REVERB_VOL_R = 0; SPU_REVERB_ADDR = 0xfffe; + SPU_FLAG_FM1 = 0; + SPU_FLAG_FM2 = 0; + SPU_FLAG_NOISE1 = 0; + SPU_FLAG_NOISE2 = 0; + SPU_FLAG_REVERB1 = 0; + SPU_FLAG_REVERB2 = 0; + SPU_CTRL = SPU_CTRL_ENABLE; _waitForStatus(0x3f, 0); @@ -76,7 +83,7 @@ void init(void) { delayMicroseconds(100); SPU_CTRL = SPU_CTRL_UNMUTE | SPU_CTRL_ENABLE; - resetAllChannels(); + stopChannels(ALL_CHANNELS); } Channel getFreeChannel(void) { @@ -85,14 +92,15 @@ Channel getFreeChannel(void) { // time rather than when it actually goes silent (so it will be set early // for e.g. short looping samples with a long release envelope, or samples // looping indefinitely). - uint32_t flags = (SPU_FLAG_STATUS1 | (SPU_FLAG_STATUS2 << 16)) & 0xffffff; + ChannelMask mask = + (SPU_FLAG_STATUS1 | (SPU_FLAG_STATUS2 << 16)) & ALL_CHANNELS; - for (Channel ch = 0; flags; ch++, flags >>= 1) { - if (flags & 1) + for (Channel ch = 0; mask; ch++, mask >>= 1) { + if (mask & 1) return ch; } #else - for (Channel ch = NUM_CHANNELS - 1; ch >= 0; ch--) { + for (Channel ch = 0; ch < NUM_CHANNELS; ch++) { if (!SPU_CH_ADSR_VOL(ch)) return ch; } @@ -101,43 +109,43 @@ Channel getFreeChannel(void) { return -1; } -void stopChannel(Channel ch) { - if ((ch < 0) || (ch >= NUM_CHANNELS)) - return; +ChannelMask getFreeChannels(int count) { + ChannelMask mask = 0; - SPU_CH_VOL_L(ch) = 0; - SPU_CH_VOL_R(ch) = 0; - SPU_CH_FREQ(ch) = 1 << 12; - SPU_CH_ADDR(ch) = DUMMY_BLOCK_OFFSET / 8; + for (Channel ch = 0; ch < NUM_CHANNELS; ch++) { + if (SPU_CH_ADSR_VOL(ch)) + continue; - if (ch < 16) { - SPU_FLAG_OFF1 = 1 << ch; - SPU_FLAG_ON1 = 1 << ch; - } else { - SPU_FLAG_OFF2 = 1 << (ch - 16); - SPU_FLAG_ON2 = 1 << (ch - 16); + mask |= 1 << ch; + count--; + + if (!count) + return mask; } + + return 0; } -void resetAllChannels(void) { - for (Channel ch = NUM_CHANNELS - 1; ch >= 0; ch--) { +void stopChannels(ChannelMask mask) { + mask &= ALL_CHANNELS; + + for (Channel ch = 0; mask; ch++, mask >>= 1) { + if (!(mask & 1)) + continue; + SPU_CH_VOL_L(ch) = 0; SPU_CH_VOL_R(ch) = 0; SPU_CH_FREQ(ch) = 1 << 12; SPU_CH_ADDR(ch) = DUMMY_BLOCK_OFFSET / 8; } - SPU_FLAG_FM1 = 0; - SPU_FLAG_FM2 = 0; - SPU_FLAG_NOISE1 = 0; - SPU_FLAG_NOISE2 = 0; - SPU_FLAG_REVERB1 = 0; - SPU_FLAG_REVERB2 = 0; - SPU_FLAG_ON1 = 0xffff; - SPU_FLAG_ON2 = 0x00ff; + SPU_FLAG_OFF1 = mask & 0xffff; + SPU_FLAG_OFF2 = mask >> 16; + SPU_FLAG_ON1 = mask & 0xffff; + SPU_FLAG_ON2 = mask >> 16; } -size_t upload(uint32_t ramOffset, const void *data, size_t length, bool wait) { +size_t upload(uint32_t offset, const void *data, size_t length, bool wait) { length /= 4; util::assertAligned(data); @@ -153,13 +161,49 @@ size_t upload(uint32_t ramOffset, const void *data, size_t length, bool wait) { _waitForStatus(SPU_CTRL_XFER_BITMASK, 0); SPU_DMA_CTRL = 4; - SPU_ADDR = ramOffset / 8; + SPU_ADDR = offset / 8; SPU_CTRL = ctrlReg | SPU_CTRL_XFER_DMA_WRITE; _waitForStatus(SPU_CTRL_XFER_BITMASK, SPU_CTRL_XFER_DMA_WRITE); DMA_MADR(DMA_SPU) = reinterpret_cast(data); DMA_BCR (DMA_SPU) = _DMA_CHUNK_SIZE | (length << 16); - DMA_CHCR(DMA_SPU) = DMA_CHCR_WRITE | DMA_CHCR_MODE_SLICE | DMA_CHCR_ENABLE; + DMA_CHCR(DMA_SPU) = 0 + | DMA_CHCR_WRITE + | DMA_CHCR_MODE_SLICE + | DMA_CHCR_ENABLE; + + if (wait) + waitForDMATransfer(DMA_SPU, _DMA_TIMEOUT); + + return length * _DMA_CHUNK_SIZE * 4; +} + +size_t download(uint32_t offset, void *data, size_t length, bool wait) { + length /= 4; + + util::assertAligned(data); + //assert(!(length % _DMA_CHUNK_SIZE)); + length = (length + _DMA_CHUNK_SIZE - 1) / _DMA_CHUNK_SIZE; + + if (!waitForDMATransfer(DMA_SPU, _DMA_TIMEOUT)) + return 0; + + uint16_t ctrlReg = SPU_CTRL & ~SPU_CTRL_XFER_BITMASK; + + SPU_CTRL = ctrlReg; + _waitForStatus(SPU_CTRL_XFER_BITMASK, 0); + + SPU_DMA_CTRL = 4; + SPU_ADDR = offset / 8; + SPU_CTRL = ctrlReg | SPU_CTRL_XFER_DMA_READ; + _waitForStatus(SPU_CTRL_XFER_BITMASK, SPU_CTRL_XFER_DMA_READ); + + DMA_MADR(DMA_SPU) = reinterpret_cast(data); + DMA_BCR (DMA_SPU) = _DMA_CHUNK_SIZE | (length << 16); + DMA_CHCR(DMA_SPU) = 0 + | DMA_CHCR_READ + | DMA_CHCR_MODE_SLICE + | DMA_CHCR_ENABLE; if (wait) waitForDMATransfer(DMA_SPU, _DMA_TIMEOUT); @@ -169,13 +213,18 @@ size_t upload(uint32_t ramOffset, const void *data, size_t length, bool wait) { /* Sound class */ -bool Sound::initFromVAGHeader(const VAGHeader *header, uint32_t ramOffset) { +Sound::Sound(void) +: offset(0), sampleRate(0), length(0) {} + +bool Sound::initFromVAGHeader(const VAGHeader *header, uint32_t _offset) { if (header->magic != util::concatenate('V', 'A', 'G', 'p')) return false; + if (header->channels > 1) + return false; - offset = ramOffset / 8; + offset = _offset; sampleRate = (__builtin_bswap32(header->sampleRate) << 12) / 44100; - length = __builtin_bswap32(header->length); + length = __builtin_bswap32(header->length) / 8; return true; } @@ -189,7 +238,7 @@ Channel Sound::play(uint16_t left, uint16_t right, Channel ch) const { SPU_CH_VOL_L(ch) = left; SPU_CH_VOL_R(ch) = right; SPU_CH_FREQ (ch) = sampleRate; - SPU_CH_ADDR (ch) = offset; + SPU_CH_ADDR (ch) = offset / 8; SPU_CH_ADSR1(ch) = 0x00ff; SPU_CH_ADSR2(ch) = 0x0000; @@ -201,4 +250,183 @@ Channel Sound::play(uint16_t left, uint16_t right, Channel ch) const { return ch; } +/* Stream class */ + +/* + * The stream driver lays out a ring buffer of interleaved audio chunks in SPU + * RAM as follows: + * + * +---------------------------------+---------------------------------+----- + * | Chunk | Chunk | + * | +------------+------------+ | +------------+------------+ | + * | | Ch0 data | Ch1 data | ... | | Ch0 data | Ch1 data | ... | ... + * | +------------+------------+ | +------------+------------+ | + * +-^------------^------------------+-^------------^------------------+----- + * | Ch0 start | Ch1 start | Ch0 loop | Ch1 loop + * | IRQ address + * + * The length of each chunk is given by the interleave size multiplied by the + * channel count. Each data block must be terminated with the loop end and + * sustain flags set in order to make the channels "jump" to the next chunk's + * blocks. + */ + +void Stream::_configureIRQ(void) const { + uint16_t ctrlReg = SPU_CTRL; + + // Disable the IRQ if an underrun occurs. + // TODO: handle this in a slightly better way + if (!_bufferedChunks) { + SPU_CTRL = ctrlReg & ~SPU_CTRL_IRQ_ENABLE; + return; + } + + // Exit if the IRQ has been set up before and not yet acknowledged by + // handleInterrupt(). + if (ctrlReg & SPU_CTRL_IRQ_ENABLE) + return; + + auto tempMask = _channelMask; + auto chunkOffset = _getChunkOffset(_head); + + SPU_IRQ_ADDR = chunkOffset / 8; + SPU_CTRL = ctrlReg | SPU_CTRL_IRQ_ENABLE; + + for (Channel ch = 0; tempMask; ch++, tempMask >>= 1) { + if (!(tempMask & 1)) + continue; + + SPU_CH_LOOP_ADDR(ch) = chunkOffset / 8; + chunkOffset += interleave; + } +} + +Stream::Stream(void) +: _channelMask(0), offset(0), interleave(0), numChunks(0), sampleRate(0), +channels(0) { + resetBuffer(); +} + +bool Stream::initFromVAGHeader( + const VAGHeader *header, uint32_t _offset, size_t _numChunks +) { + if (isPlaying()) + return false; + if (header->magic != util::concatenate('V', 'A', 'G', 'i')) + return false; + if (!header->interleave) + return false; + + offset = _offset; + interleave = header->interleave; + numChunks = _numChunks; + sampleRate = (__builtin_bswap32(header->sampleRate) << 12) / 44100; + channels = header->channels ? header->channels : 2; + + return true; +} + +ChannelMask Stream::start(uint16_t left, uint16_t right, ChannelMask mask) { + if (isPlaying() || !_bufferedChunks) + return 0; + + mask &= ALL_CHANNELS; + + auto tempMask = mask; + auto chunkOffset = _getChunkOffset(_head); + int isRightCh = 0; + + for (Channel ch = 0; tempMask; ch++, tempMask >>= 1) { + if (!(tempMask & 1)) + continue; + + // Assume each pair of channels is a stereo pair. If the channel count + // is odd, assume the last channel is mono. + if (isRightCh) { + SPU_CH_VOL_L(ch) = 0; + SPU_CH_VOL_R(ch) = right; + } else if (tempMask != 1) { + SPU_CH_VOL_L(ch) = left; + SPU_CH_VOL_R(ch) = 0; + } else { + SPU_CH_VOL_L(ch) = left; + SPU_CH_VOL_R(ch) = right; + } + + SPU_CH_FREQ(ch) = sampleRate; + SPU_CH_ADDR(ch) = chunkOffset / 8; + SPU_CH_ADSR1(ch) = 0x00ff; + SPU_CH_ADSR2(ch) = 0x0000; + + chunkOffset += interleave; + isRightCh ^= 1; + } + + //assert(streamCh == channels); + + _channelMask = mask; + SPU_FLAG_ON1 = mask & 0xffff; + SPU_FLAG_ON2 = mask >> 16; + + handleInterrupt(); + return mask; +} + +void Stream::stop(void) { + util::CriticalSection sec; + + if (isPlaying()) { + SPU_CTRL &= ~SPU_CTRL_IRQ_ENABLE; + _channelMask = 0; + + stopChannels(_channelMask); + } + + flushWriteQueue(); +} + +void Stream::handleInterrupt(void) { + if (!isPlaying()) + return; + + // Disabling the IRQ is always required in order to acknowledge it. + SPU_CTRL &= ~SPU_CTRL_IRQ_ENABLE; + + _head = (_head + 1) % numChunks; + _bufferedChunks--; + _configureIRQ(); +} + +size_t Stream::feed(const void *data, size_t count) { + util::CriticalSection sec; + + auto ptr = reinterpret_cast(data); + auto chunkLength = getChunkLength(); + count = util::min(count, getFreeChunkCount()); + + for (auto i = count; i; i--) { + upload( + _getChunkOffset(_tail), reinterpret_cast(ptr), + chunkLength, true + ); + + _tail = (_tail + 1) % numChunks; + ptr += chunkLength; + } + + _bufferedChunks += count; + + if (isPlaying()) + _configureIRQ(); + + flushWriteQueue(); + return count; +} + +void Stream::resetBuffer(void) { + _head = 0; + _tail = 0; + _bufferedChunks = 0; +} + } diff --git a/src/common/spu.hpp b/src/common/spu.hpp index bf735f8..f821e3d 100644 --- a/src/common/spu.hpp +++ b/src/common/spu.hpp @@ -22,16 +22,30 @@ namespace spu { +using Channel = int; +using ChannelMask = uint32_t; + +enum LoopFlag : uint8_t { + LOOP_END = 1 << 0, + LOOP_SUSTAIN = 1 << 1, + LOOP_START = 1 << 2 +}; + static constexpr uint32_t DUMMY_BLOCK_OFFSET = 0x1000; static constexpr uint32_t DUMMY_BLOCK_END = 0x1010; static constexpr int NUM_CHANNELS = 24; static constexpr uint16_t MAX_VOLUME = 0x3fff; -using Channel = int; +static constexpr ChannelMask ALL_CHANNELS = (1 << NUM_CHANNELS) - 1; /* Basic API */ +void init(void); +Channel getFreeChannel(void); +ChannelMask getFreeChannels(int count = NUM_CHANNELS); +void stopChannels(ChannelMask mask); + static inline void setMasterVolume(uint16_t master, uint16_t reverb = 0) { SPU_MASTER_VOL_L = master; SPU_MASTER_VOL_R = master; @@ -47,14 +61,17 @@ static inline void setChannelVolume(Channel ch, uint16_t left, uint16_t right) { SPU_CH_VOL_R(ch) = right; } -void init(void); -Channel getFreeChannel(void); -void stopChannel(Channel ch); -void resetAllChannels(void); -size_t upload(uint32_t ramOffset, const void *data, size_t length, bool wait); +static inline void stopChannel(Channel ch) { + stopChannels(1 << ch); +} + +size_t upload(uint32_t offset, const void *data, size_t length, bool wait); +size_t download(uint32_t offset, void *data, size_t length, bool wait); /* Sound class */ +static constexpr size_t INTERLEAVED_VAG_BODY_OFFSET = 2048; + struct VAGHeader { public: uint32_t magic, version, interleave, length, sampleRate; @@ -64,19 +81,71 @@ public: class Sound { public: - uint16_t offset, sampleRate; - size_t length; + uint32_t offset; + uint16_t sampleRate, length; - inline Sound(void) - : offset(0), length(0) {} inline Channel play( uint16_t left = MAX_VOLUME, uint16_t right = MAX_VOLUME ) const { return play(left, right, getFreeChannel()); } - bool initFromVAGHeader(const VAGHeader *header, uint32_t ramOffset); + Sound(void); + bool initFromVAGHeader(const VAGHeader *header, uint32_t _offset); Channel play(uint16_t left, uint16_t right, Channel ch) const; }; +/* Stream class */ + +class Stream { +private: + ChannelMask _channelMask; + uint16_t _head, _tail, _bufferedChunks; + + inline uint32_t _getChunkOffset(size_t chunk) const { + return offset + getChunkLength() * chunk; + } + + void _configureIRQ(void) const; + +public: + uint32_t offset; + uint16_t interleave, numChunks, sampleRate, channels; + + inline ~Stream(void) { + stop(); + } + inline ChannelMask start( + uint16_t left = MAX_VOLUME, uint16_t right = MAX_VOLUME + ) { + return start(left, right, getFreeChannels(channels)); + } + inline bool isPlaying(void) const { + __atomic_signal_fence(__ATOMIC_ACQUIRE); + + return (_channelMask != 0); + } + inline size_t getChunkLength(void) const { + return size_t(interleave) * size_t(channels); + } + inline size_t getFreeChunkCount(void) const { + __atomic_signal_fence(__ATOMIC_ACQUIRE); + + // The currently playing chunk cannot be overwritten. + size_t playingChunk = isPlaying() ? 1 : 0; + return numChunks - (_bufferedChunks + playingChunk); + } + + Stream(void); + bool initFromVAGHeader( + const VAGHeader *header, uint32_t _offset, size_t _numChunks + ); + ChannelMask start(uint16_t left, uint16_t right, ChannelMask mask); + void stop(void); + void handleInterrupt(void); + + size_t feed(const void *data, size_t count); + void resetBuffer(void); +}; + } diff --git a/src/common/util.hpp b/src/common/util.hpp index b1440d7..e495a31 100644 --- a/src/common/util.hpp +++ b/src/common/util.hpp @@ -16,9 +16,11 @@ #pragma once +#include #include #include #include +#include "ps1/system.h" namespace util { @@ -65,7 +67,7 @@ template static inline T roundUpToMultiple(T value, T length) { } template static inline void assertAligned(X *ptr) { - //assert(!(reinterpret_cast(ptr) % alignof(T))); + assert(!(reinterpret_cast(ptr) % alignof(T))); } template static inline void clear(T &obj, uint8_t value = 0) { @@ -109,6 +111,34 @@ template static constexpr inline Hash hash( Hash hash(const char *str, char terminator = 0); Hash hash(const uint8_t *data, size_t length); +/* Critical section helper */ + +class CriticalSection { +private: + bool _enable; + +public: + inline CriticalSection(void) { + _enable = disableInterrupts(); + } + inline ~CriticalSection(void) { + if (_enable) + enableInterrupts(); + } +}; + +class ThreadCriticalSection { +public: + inline ThreadCriticalSection(void) { + bool enable = disableInterrupts(); + + assert(enable); + } + inline ~ThreadCriticalSection(void) { + enableInterrupts(); + } +}; + /* Simple "smart" pointer */ class Data { diff --git a/src/main/app/app.cpp b/src/main/app/app.cpp index 582d1c9..3660713 100644 --- a/src/main/app/app.cpp +++ b/src/main/app/app.cpp @@ -41,40 +41,41 @@ void WorkerStatus::reset(ui::Screen &next, bool goBack) { message = nullptr; nextScreen = &next; nextGoBack = goBack; + + flushWriteQueue(); } void WorkerStatus::update(int part, int total, const char *text) { - auto enable = disableInterrupts(); + util::CriticalSection sec; + status = WORKER_BUSY; progress = part; progressTotal = total; if (text) message = text; - if (enable) - enableInterrupts(); + + flushWriteQueue(); } ui::Screen &WorkerStatus::setNextScreen(ui::Screen &next, bool goBack) { - auto enable = disableInterrupts(); + util::CriticalSection sec; + auto oldNext = nextScreen; nextScreen = &next; nextGoBack = goBack; - if (enable) - enableInterrupts(); - + flushWriteQueue(); return *oldNext; } WorkerStatusType WorkerStatus::setStatus(WorkerStatusType value) { - auto enable = disableInterrupts(); + util::CriticalSection sec; + auto oldStatus = status; status = value; - if (enable) - enableInterrupts(); - + flushWriteQueue(); return oldStatus; } @@ -219,7 +220,10 @@ void App::_setupInterrupts(void) { util::forcedCast(&App::_interruptHandler), this ); - IRQ_MASK = 1 << IRQ_VSYNC; + IRQ_MASK = 0 + | (1 << IRQ_VSYNC) + | (1 << IRQ_SPU) + | (1 << IRQ_PIO); enableInterrupts(); } @@ -319,6 +323,8 @@ void App::_updateOverlays(void) { // Splash screen overlay int timeout = _ctx.gpuCtx.refreshRate * _SPLASH_SCREEN_TIMEOUT; + __atomic_signal_fence(__ATOMIC_ACQUIRE); + if ((_workerStatus.status == WORKER_DONE) || (_ctx.time > timeout)) _splashOverlay.hide(_ctx); @@ -339,20 +345,20 @@ void App::_updateOverlays(void) { void App::_runWorker( bool (App::*func)(void), ui::Screen &next, bool goBack, bool playSound ) { - auto enable = disableInterrupts(); + { + util::CriticalSection sec; - _workerStatus.reset(next, goBack); - _workerStack.allocate(_WORKER_STACK_SIZE); + _workerStatus.reset(next, goBack); + _workerStack.allocate(_WORKER_STACK_SIZE); - _workerFunction = func; - auto stackBottom = _workerStack.as(); + _workerFunction = func; + auto stackBottom = _workerStack.as(); - initThread( - &_workerThread, util::forcedCast(&App::_worker), this, - &stackBottom[(_WORKER_STACK_SIZE - 1) & ~7] - ); - if (enable) - enableInterrupts(); + initThread( + &_workerThread, util::forcedCast(&App::_worker), this, + &stackBottom[(_WORKER_STACK_SIZE - 1) & ~7] + ); + } _ctx.show(_workerStatusScreen, false, playSound); } @@ -372,11 +378,21 @@ void App::_interruptHandler(void) { if (acknowledgeInterrupt(IRQ_VSYNC)) { _ctx.tick(); + __atomic_signal_fence(__ATOMIC_ACQUIRE); + if (_workerStatus.status != WORKER_REBOOT) io::clearWatchdog(); if (gpu::isIdle() && (_workerStatus.status != WORKER_BUSY_SUSPEND)) switchThread(nullptr); } + + if (acknowledgeInterrupt(IRQ_SPU)) + _ctx.audioStream.handleInterrupt(); + + if (acknowledgeInterrupt(IRQ_PIO)) { + for (auto &dev : ide::devices) + dev.handleInterrupt(); + } } [[noreturn]] void App::run(const void *resourcePtr, size_t resourceLength) { diff --git a/src/main/app/app.hpp b/src/main/app/app.hpp index 69359ac..af3b9b0 100644 --- a/src/main/app/app.hpp +++ b/src/main/app/app.hpp @@ -48,13 +48,12 @@ enum WorkerStatusType { // the main thread and the WorkerStatusScreen. class WorkerStatus { public: - volatile WorkerStatusType status; + WorkerStatusType status; + int progress, progressTotal; - volatile int progress, progressTotal; - - const char *volatile message; - ui::Screen *volatile nextScreen; - volatile bool nextGoBack; + const char *message; + ui::Screen *nextScreen; + bool nextGoBack; void reset(ui::Screen &next, bool goBack = false); void update(int part, int total, const char *text = nullptr); diff --git a/src/main/uibase.hpp b/src/main/uibase.hpp index 16cb4ae..61c39da 100644 --- a/src/main/uibase.hpp +++ b/src/main/uibase.hpp @@ -124,10 +124,10 @@ private: ButtonMap _buttonMap; uint32_t _mappings[NUM_BUTTONS]; - uint8_t _held, _prevHeld; - uint8_t _longHeld, _prevLongHeld; - uint8_t _pressed, _released; - uint8_t _longPressed, _longReleased; + uint8_t _held, _prevHeld; + uint8_t _longHeld, _prevLongHeld; + uint8_t _pressed, _released; + uint8_t _longPressed, _longReleased; int _repeatTimer; @@ -184,6 +184,7 @@ public: spu::Sound sounds[NUM_UI_SOUNDS]; ButtonState buttons; + spu::Stream audioStream; int time; void *screenData; // Opaque, can be accessed by screens @@ -210,8 +211,12 @@ public: class Layer { protected: void _newLayer(Context &ctx, int x, int y, int width, int height) const; - void _setTexturePage(Context &ctx, uint16_t texpage, bool dither = false) const; - void _setBlendMode(Context &ctx, gpu::BlendMode blendMode, bool dither = false) const; + void _setTexturePage( + Context &ctx, uint16_t texpage, bool dither = false + ) const; + void _setBlendMode( + Context &ctx, gpu::BlendMode blendMode, bool dither = false + ) const; public: virtual void draw(Context &ctx, bool active = true) const {} diff --git a/src/ps1/system.c b/src/ps1/system.c index 29df71a..d7d412c 100644 --- a/src/ps1/system.c +++ b/src/ps1/system.c @@ -14,7 +14,6 @@ * PERFORMANCE OF THIS SOFTWARE. */ -#include #include #include #include "ps1/cop0gte.h" @@ -94,10 +93,9 @@ void uninstallExceptionHandler(void) { void setInterruptHandler(ArgFunction func, void *arg) { disableInterrupts(); - interruptHandler = func; interruptHandlerArg = arg; - atomic_signal_fence(memory_order_release); + flushWriteQueue(); } void flushCache(void) { @@ -154,5 +152,5 @@ void switchThread(Thread *thread) { thread = &_mainThread; nextThread = thread; - atomic_signal_fence(memory_order_release); + flushWriteQueue(); } diff --git a/src/ps1/system.h b/src/ps1/system.h index d48e41b..b5b2a5c 100644 --- a/src/ps1/system.h +++ b/src/ps1/system.h @@ -53,7 +53,7 @@ extern Thread *nextThread; * were disabled, any callback set using setInterruptHandler() will be invoked * immediately. */ -static inline void enableInterrupts(void) { +__attribute__((always_inline)) static inline void enableInterrupts(void) { cop0_setSR(cop0_getSR() | COP0_SR_IEc); } @@ -65,13 +65,24 @@ static inline void enableInterrupts(void) { * * @return True if interrupts were previously enabled, false otherwise */ -static inline bool disableInterrupts(void) { +__attribute__((always_inline)) static inline bool disableInterrupts(void) { uint32_t sr = cop0_getSR(); cop0_setSR(sr & ~COP0_SR_IEc); return (sr & COP0_SR_IEc); } +/** + * @brief Forces all pending memory writes to complete and stalls until the + * write queue is empty. Calling this function is not necessary when accessing + * memory or hardware registers through KSEG1 as the write queue is only enabled + * when using KUSEG or KSEG0. + */ +__attribute__((always_inline)) static inline void flushWriteQueue(void) { + __atomic_signal_fence(__ATOMIC_RELEASE); + *((volatile uint8_t *) 0xbfc00000); +} + /** * @brief Initializes a thread structure with the provided entry point * (function) and stacktop. The function *must not* return. The stack should be @@ -82,7 +93,7 @@ static inline bool disableInterrupts(void) { * @param arg Optional argument to entry point * @param stack Pointer to last 8 bytes in the stack */ -static inline void initThread( +__attribute__((always_inline)) static inline void initThread( Thread *thread, ArgFunction func, void *arg, void *stack ) { register uint32_t gp __asm__("gp"); @@ -220,7 +231,9 @@ void switchThread(Thread *thread); * * @param thread Pointer to new thread or NULL for main thread */ -static inline void switchThreadImmediate(Thread *thread) { +__attribute__((always_inline)) static inline void switchThreadImmediate( + Thread *thread +) { switchThread(thread); // Execute a syscall to force the switch to happen. diff --git a/src/vendor/vendorconfig.h b/src/vendor/vendorconfig.h index e4a524a..428bbcb 100644 --- a/src/vendor/vendorconfig.h +++ b/src/vendor/vendorconfig.h @@ -53,7 +53,7 @@ #define FF_FS_NOFSINFO 0 #define FF_FS_LOCK 0 -#define FF_FS_REENTRANT 0 +#define FF_FS_REENTRANT 1 /* miniz configuration */