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 */