Refactor, add new SPU driver and filesystem mutex

This commit is contained in:
spicyjpeg 2024-07-21 18:15:53 +02:00
parent 5fb99fc08c
commit 065ea8ba45
No known key found for this signature in database
GPG Key ID: 5CC87404C01DF393
14 changed files with 506 additions and 100 deletions

View File

@ -71,9 +71,7 @@ to lowest priority:
new data to the game list (see above) and reverse engineering all flash header new data to the game list (see above) and reverse engineering all flash header
formats used by games. formats used by games.
- Fixing a bunch of stability bugs, such as the executable loader occasionally - Fixing a bunch of stability bugs, such as the executable loader occasionally
crashing or the filesystem drivers not being thread safe (resulting in crashing.
filesystem corruption if e.g. a screenshot is taken while dumping data to the
hard drive).
- Adding support for installing arbitrary executables directly to the internal - Adding support for installing arbitrary executables directly to the internal
flash or a PCMCIA card, allowing the 573 BIOS to boot them automatically even flash or a PCMCIA card, allowing the 573 BIOS to boot them automatically even
with no CD-ROM drive present. with no CD-ROM drive present.

View File

@ -34,17 +34,17 @@ Special thanks:
Copyright (C) 2022-2024 spicyjpeg Copyright (C) 2022-2024 spicyjpeg
This program is free software: you can redistribute it and/or modify it under 573in1 is free software: you can redistribute it and/or modify it under the
the terms of the GNU General Public License as published by the Free Software 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 Foundation, either version 3 of the License, or (at your option) any later
version. 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 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE. See the GNU General Public License for more details. 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 You should have received a copy of the GNU General Public License along with
this program. If not, see <https://www.gnu.org/licenses/>. 573in1. If not, see <https://www.gnu.org/licenses/>.
[ Third-party licenses ] [ Third-party licenses ]

View File

@ -21,6 +21,7 @@
#include "common/ide.hpp" #include "common/ide.hpp"
#include "common/io.hpp" #include "common/io.hpp"
#include "common/util.hpp" #include "common/util.hpp"
#include "ps1/system.h"
#include "vendor/diskio.h" #include "vendor/diskio.h"
#include "vendor/ff.h" #include "vendor/ff.h"
@ -285,6 +286,10 @@ File *FATProvider::openFile(const char *path, uint32_t flags) {
/* FatFs library API glue */ /* FatFs library API glue */
static constexpr int _MUTEX_TIMEOUT = 30000000;
static uint32_t _fatMutex = 0;
extern "C" DSTATUS disk_initialize(uint8_t drive) { extern "C" DSTATUS disk_initialize(uint8_t drive) {
#if 0 #if 0
auto &dev = ide::devices[drive]; auto &dev = ide::devices[drive];
@ -373,4 +378,40 @@ extern "C" uint32_t get_fattime(void) {
return date.toDOSTime(); 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();
}
} }

View File

@ -71,7 +71,6 @@ const char *const DEVICE_ERROR_NAMES[]{
/* Utilities */ /* Utilities */
#ifdef ENABLE_FULL_IDE_DRIVER
static void _copyString(char *output, const uint16_t *input, size_t length) { static void _copyString(char *output, const uint16_t *input, size_t length) {
// The strings in the identification block are byte-swapped and padded with // The strings in the identification block are byte-swapped and padded with
// spaces. To make them printable, any span of consecutive space characters // 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; *(--output) = b;
} }
} }
#endif
static DeviceError _senseDataToError(const SenseData &data) { static DeviceError _senseDataToError(const SenseData &data) {
auto key = data.senseKey & 15; 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) { DeviceError Device::readData(void *data, uint64_t lba, size_t count) {
util::assertAligned<uint32_t>(data); util::assertAligned<uint32_t>(data);
@ -754,6 +756,8 @@ DeviceError Device::writeData(const void *data, uint64_t lba, size_t count) {
return _ataTransfer(reinterpret_cast<uintptr_t>(data), lba, count, true); return _ataTransfer(reinterpret_cast<uintptr_t>(data), lba, count, true);
} }
#ifdef ENABLE_FULL_IDE_DRIVER
DeviceError Device::goIdle(bool standby) { DeviceError Device::goIdle(bool standby) {
if (!(flags & DEVICE_READY)) if (!(flags & DEVICE_READY))
return NO_DRIVE; return NO_DRIVE;
@ -807,4 +811,6 @@ DeviceError Device::flushCache(void) {
return _waitForIdle(); return _waitForIdle();
} }
#endif
} }

View File

@ -308,12 +308,15 @@ public:
Device(uint32_t flags); Device(uint32_t flags);
DeviceError enumerate(void); DeviceError enumerate(void);
DeviceError poll(void); DeviceError poll(void);
void handleInterrupt(void);
DeviceError readData(void *data, uint64_t lba, size_t count); DeviceError readData(void *data, uint64_t lba, size_t count);
DeviceError writeData(const 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 goIdle(bool standby = false);
DeviceError startStopUnit(ATAPIStartStopMode mode); DeviceError startStopUnit(ATAPIStartStopMode mode);
DeviceError flushCache(void); DeviceError flushCache(void);
#endif
}; };
extern const char *const DEVICE_ERROR_NAMES[]; extern const char *const DEVICE_ERROR_NAMES[];

View File

@ -60,6 +60,13 @@ void init(void) {
SPU_REVERB_VOL_R = 0; SPU_REVERB_VOL_R = 0;
SPU_REVERB_ADDR = 0xfffe; 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; SPU_CTRL = SPU_CTRL_ENABLE;
_waitForStatus(0x3f, 0); _waitForStatus(0x3f, 0);
@ -76,7 +83,7 @@ void init(void) {
delayMicroseconds(100); delayMicroseconds(100);
SPU_CTRL = SPU_CTRL_UNMUTE | SPU_CTRL_ENABLE; SPU_CTRL = SPU_CTRL_UNMUTE | SPU_CTRL_ENABLE;
resetAllChannels(); stopChannels(ALL_CHANNELS);
} }
Channel getFreeChannel(void) { Channel getFreeChannel(void) {
@ -85,14 +92,15 @@ Channel getFreeChannel(void) {
// time rather than when it actually goes silent (so it will be set early // 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 // for e.g. short looping samples with a long release envelope, or samples
// looping indefinitely). // 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) { for (Channel ch = 0; mask; ch++, mask >>= 1) {
if (flags & 1) if (mask & 1)
return ch; return ch;
} }
#else #else
for (Channel ch = NUM_CHANNELS - 1; ch >= 0; ch--) { for (Channel ch = 0; ch < NUM_CHANNELS; ch++) {
if (!SPU_CH_ADSR_VOL(ch)) if (!SPU_CH_ADSR_VOL(ch))
return ch; return ch;
} }
@ -101,43 +109,43 @@ Channel getFreeChannel(void) {
return -1; return -1;
} }
void stopChannel(Channel ch) { ChannelMask getFreeChannels(int count) {
if ((ch < 0) || (ch >= NUM_CHANNELS)) ChannelMask mask = 0;
return;
SPU_CH_VOL_L(ch) = 0; for (Channel ch = 0; ch < NUM_CHANNELS; ch++) {
SPU_CH_VOL_R(ch) = 0; if (SPU_CH_ADSR_VOL(ch))
SPU_CH_FREQ(ch) = 1 << 12; continue;
SPU_CH_ADDR(ch) = DUMMY_BLOCK_OFFSET / 8;
if (ch < 16) { mask |= 1 << ch;
SPU_FLAG_OFF1 = 1 << ch; count--;
SPU_FLAG_ON1 = 1 << ch;
} else { if (!count)
SPU_FLAG_OFF2 = 1 << (ch - 16); return mask;
SPU_FLAG_ON2 = 1 << (ch - 16);
} }
return 0;
} }
void resetAllChannels(void) { void stopChannels(ChannelMask mask) {
for (Channel ch = NUM_CHANNELS - 1; ch >= 0; ch--) { mask &= ALL_CHANNELS;
for (Channel ch = 0; mask; ch++, mask >>= 1) {
if (!(mask & 1))
continue;
SPU_CH_VOL_L(ch) = 0; SPU_CH_VOL_L(ch) = 0;
SPU_CH_VOL_R(ch) = 0; SPU_CH_VOL_R(ch) = 0;
SPU_CH_FREQ(ch) = 1 << 12; SPU_CH_FREQ(ch) = 1 << 12;
SPU_CH_ADDR(ch) = DUMMY_BLOCK_OFFSET / 8; SPU_CH_ADDR(ch) = DUMMY_BLOCK_OFFSET / 8;
} }
SPU_FLAG_FM1 = 0; SPU_FLAG_OFF1 = mask & 0xffff;
SPU_FLAG_FM2 = 0; SPU_FLAG_OFF2 = mask >> 16;
SPU_FLAG_NOISE1 = 0; SPU_FLAG_ON1 = mask & 0xffff;
SPU_FLAG_NOISE2 = 0; SPU_FLAG_ON2 = mask >> 16;
SPU_FLAG_REVERB1 = 0;
SPU_FLAG_REVERB2 = 0;
SPU_FLAG_ON1 = 0xffff;
SPU_FLAG_ON2 = 0x00ff;
} }
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; length /= 4;
util::assertAligned<uint32_t>(data); util::assertAligned<uint32_t>(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); _waitForStatus(SPU_CTRL_XFER_BITMASK, 0);
SPU_DMA_CTRL = 4; SPU_DMA_CTRL = 4;
SPU_ADDR = ramOffset / 8; SPU_ADDR = offset / 8;
SPU_CTRL = ctrlReg | SPU_CTRL_XFER_DMA_WRITE; SPU_CTRL = ctrlReg | SPU_CTRL_XFER_DMA_WRITE;
_waitForStatus(SPU_CTRL_XFER_BITMASK, SPU_CTRL_XFER_DMA_WRITE); _waitForStatus(SPU_CTRL_XFER_BITMASK, SPU_CTRL_XFER_DMA_WRITE);
DMA_MADR(DMA_SPU) = reinterpret_cast<uint32_t>(data); DMA_MADR(DMA_SPU) = reinterpret_cast<uint32_t>(data);
DMA_BCR (DMA_SPU) = _DMA_CHUNK_SIZE | (length << 16); 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<uint32_t>(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<uint32_t>(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) if (wait)
waitForDMATransfer(DMA_SPU, _DMA_TIMEOUT); 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 */ /* 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')) if (header->magic != util::concatenate('V', 'A', 'G', 'p'))
return false; return false;
if (header->channels > 1)
return false;
offset = ramOffset / 8; offset = _offset;
sampleRate = (__builtin_bswap32(header->sampleRate) << 12) / 44100; sampleRate = (__builtin_bswap32(header->sampleRate) << 12) / 44100;
length = __builtin_bswap32(header->length); length = __builtin_bswap32(header->length) / 8;
return true; 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_L(ch) = left;
SPU_CH_VOL_R(ch) = right; SPU_CH_VOL_R(ch) = right;
SPU_CH_FREQ (ch) = sampleRate; SPU_CH_FREQ (ch) = sampleRate;
SPU_CH_ADDR (ch) = offset; SPU_CH_ADDR (ch) = offset / 8;
SPU_CH_ADSR1(ch) = 0x00ff; SPU_CH_ADSR1(ch) = 0x00ff;
SPU_CH_ADSR2(ch) = 0x0000; SPU_CH_ADSR2(ch) = 0x0000;
@ -201,4 +250,183 @@ Channel Sound::play(uint16_t left, uint16_t right, Channel ch) const {
return ch; 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<uintptr_t>(data);
auto chunkLength = getChunkLength();
count = util::min(count, getFreeChunkCount());
for (auto i = count; i; i--) {
upload(
_getChunkOffset(_tail), reinterpret_cast<const void *>(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;
}
} }

View File

@ -22,16 +22,30 @@
namespace spu { 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_OFFSET = 0x1000;
static constexpr uint32_t DUMMY_BLOCK_END = 0x1010; static constexpr uint32_t DUMMY_BLOCK_END = 0x1010;
static constexpr int NUM_CHANNELS = 24; static constexpr int NUM_CHANNELS = 24;
static constexpr uint16_t MAX_VOLUME = 0x3fff; static constexpr uint16_t MAX_VOLUME = 0x3fff;
using Channel = int; static constexpr ChannelMask ALL_CHANNELS = (1 << NUM_CHANNELS) - 1;
/* Basic API */ /* 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) { static inline void setMasterVolume(uint16_t master, uint16_t reverb = 0) {
SPU_MASTER_VOL_L = master; SPU_MASTER_VOL_L = master;
SPU_MASTER_VOL_R = 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; SPU_CH_VOL_R(ch) = right;
} }
void init(void); static inline void stopChannel(Channel ch) {
Channel getFreeChannel(void); stopChannels(1 << ch);
void stopChannel(Channel ch); }
void resetAllChannels(void);
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);
size_t download(uint32_t offset, void *data, size_t length, bool wait);
/* Sound class */ /* Sound class */
static constexpr size_t INTERLEAVED_VAG_BODY_OFFSET = 2048;
struct VAGHeader { struct VAGHeader {
public: public:
uint32_t magic, version, interleave, length, sampleRate; uint32_t magic, version, interleave, length, sampleRate;
@ -64,19 +81,71 @@ public:
class Sound { class Sound {
public: public:
uint16_t offset, sampleRate; uint32_t offset;
size_t length; uint16_t sampleRate, length;
inline Sound(void)
: offset(0), length(0) {}
inline Channel play( inline Channel play(
uint16_t left = MAX_VOLUME, uint16_t right = MAX_VOLUME uint16_t left = MAX_VOLUME, uint16_t right = MAX_VOLUME
) const { ) const {
return play(left, right, getFreeChannel()); 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; 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);
};
} }

View File

@ -16,9 +16,11 @@
#pragma once #pragma once
#include <assert.h>
#include <stddef.h> #include <stddef.h>
#include <stdint.h> #include <stdint.h>
#include <stdlib.h> #include <stdlib.h>
#include "ps1/system.h"
namespace util { namespace util {
@ -65,7 +67,7 @@ template<typename T> static inline T roundUpToMultiple(T value, T length) {
} }
template<typename T, typename X> static inline void assertAligned(X *ptr) { template<typename T, typename X> static inline void assertAligned(X *ptr) {
//assert(!(reinterpret_cast<uintptr_t>(ptr) % alignof(T))); assert(!(reinterpret_cast<uintptr_t>(ptr) % alignof(T)));
} }
template<typename T> static inline void clear(T &obj, uint8_t value = 0) { template<typename T> static inline void clear(T &obj, uint8_t value = 0) {
@ -109,6 +111,34 @@ template<typename T> static constexpr inline Hash hash(
Hash hash(const char *str, char terminator = 0); Hash hash(const char *str, char terminator = 0);
Hash hash(const uint8_t *data, size_t length); 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 */ /* Simple "smart" pointer */
class Data { class Data {

View File

@ -41,40 +41,41 @@ void WorkerStatus::reset(ui::Screen &next, bool goBack) {
message = nullptr; message = nullptr;
nextScreen = &next; nextScreen = &next;
nextGoBack = goBack; nextGoBack = goBack;
flushWriteQueue();
} }
void WorkerStatus::update(int part, int total, const char *text) { void WorkerStatus::update(int part, int total, const char *text) {
auto enable = disableInterrupts(); util::CriticalSection sec;
status = WORKER_BUSY; status = WORKER_BUSY;
progress = part; progress = part;
progressTotal = total; progressTotal = total;
if (text) if (text)
message = text; message = text;
if (enable)
enableInterrupts(); flushWriteQueue();
} }
ui::Screen &WorkerStatus::setNextScreen(ui::Screen &next, bool goBack) { ui::Screen &WorkerStatus::setNextScreen(ui::Screen &next, bool goBack) {
auto enable = disableInterrupts(); util::CriticalSection sec;
auto oldNext = nextScreen; auto oldNext = nextScreen;
nextScreen = &next; nextScreen = &next;
nextGoBack = goBack; nextGoBack = goBack;
if (enable) flushWriteQueue();
enableInterrupts();
return *oldNext; return *oldNext;
} }
WorkerStatusType WorkerStatus::setStatus(WorkerStatusType value) { WorkerStatusType WorkerStatus::setStatus(WorkerStatusType value) {
auto enable = disableInterrupts(); util::CriticalSection sec;
auto oldStatus = status; auto oldStatus = status;
status = value; status = value;
if (enable) flushWriteQueue();
enableInterrupts();
return oldStatus; return oldStatus;
} }
@ -219,7 +220,10 @@ void App::_setupInterrupts(void) {
util::forcedCast<ArgFunction>(&App::_interruptHandler), this util::forcedCast<ArgFunction>(&App::_interruptHandler), this
); );
IRQ_MASK = 1 << IRQ_VSYNC; IRQ_MASK = 0
| (1 << IRQ_VSYNC)
| (1 << IRQ_SPU)
| (1 << IRQ_PIO);
enableInterrupts(); enableInterrupts();
} }
@ -319,6 +323,8 @@ void App::_updateOverlays(void) {
// Splash screen overlay // Splash screen overlay
int timeout = _ctx.gpuCtx.refreshRate * _SPLASH_SCREEN_TIMEOUT; int timeout = _ctx.gpuCtx.refreshRate * _SPLASH_SCREEN_TIMEOUT;
__atomic_signal_fence(__ATOMIC_ACQUIRE);
if ((_workerStatus.status == WORKER_DONE) || (_ctx.time > timeout)) if ((_workerStatus.status == WORKER_DONE) || (_ctx.time > timeout))
_splashOverlay.hide(_ctx); _splashOverlay.hide(_ctx);
@ -339,7 +345,8 @@ void App::_updateOverlays(void) {
void App::_runWorker( void App::_runWorker(
bool (App::*func)(void), ui::Screen &next, bool goBack, bool playSound bool (App::*func)(void), ui::Screen &next, bool goBack, bool playSound
) { ) {
auto enable = disableInterrupts(); {
util::CriticalSection sec;
_workerStatus.reset(next, goBack); _workerStatus.reset(next, goBack);
_workerStack.allocate(_WORKER_STACK_SIZE); _workerStack.allocate(_WORKER_STACK_SIZE);
@ -351,8 +358,7 @@ void App::_runWorker(
&_workerThread, util::forcedCast<ArgFunction>(&App::_worker), this, &_workerThread, util::forcedCast<ArgFunction>(&App::_worker), this,
&stackBottom[(_WORKER_STACK_SIZE - 1) & ~7] &stackBottom[(_WORKER_STACK_SIZE - 1) & ~7]
); );
if (enable) }
enableInterrupts();
_ctx.show(_workerStatusScreen, false, playSound); _ctx.show(_workerStatusScreen, false, playSound);
} }
@ -372,11 +378,21 @@ void App::_interruptHandler(void) {
if (acknowledgeInterrupt(IRQ_VSYNC)) { if (acknowledgeInterrupt(IRQ_VSYNC)) {
_ctx.tick(); _ctx.tick();
__atomic_signal_fence(__ATOMIC_ACQUIRE);
if (_workerStatus.status != WORKER_REBOOT) if (_workerStatus.status != WORKER_REBOOT)
io::clearWatchdog(); io::clearWatchdog();
if (gpu::isIdle() && (_workerStatus.status != WORKER_BUSY_SUSPEND)) if (gpu::isIdle() && (_workerStatus.status != WORKER_BUSY_SUSPEND))
switchThread(nullptr); 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) { [[noreturn]] void App::run(const void *resourcePtr, size_t resourceLength) {

View File

@ -48,13 +48,12 @@ enum WorkerStatusType {
// the main thread and the WorkerStatusScreen. // the main thread and the WorkerStatusScreen.
class WorkerStatus { class WorkerStatus {
public: public:
volatile WorkerStatusType status; WorkerStatusType status;
int progress, progressTotal;
volatile int progress, progressTotal; const char *message;
ui::Screen *nextScreen;
const char *volatile message; bool nextGoBack;
ui::Screen *volatile nextScreen;
volatile bool nextGoBack;
void reset(ui::Screen &next, bool goBack = false); void reset(ui::Screen &next, bool goBack = false);
void update(int part, int total, const char *text = nullptr); void update(int part, int total, const char *text = nullptr);

View File

@ -184,6 +184,7 @@ public:
spu::Sound sounds[NUM_UI_SOUNDS]; spu::Sound sounds[NUM_UI_SOUNDS];
ButtonState buttons; ButtonState buttons;
spu::Stream audioStream;
int time; int time;
void *screenData; // Opaque, can be accessed by screens void *screenData; // Opaque, can be accessed by screens
@ -210,8 +211,12 @@ public:
class Layer { class Layer {
protected: protected:
void _newLayer(Context &ctx, int x, int y, int width, int height) const; 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 _setTexturePage(
void _setBlendMode(Context &ctx, gpu::BlendMode blendMode, bool dither = false) const; Context &ctx, uint16_t texpage, bool dither = false
) const;
void _setBlendMode(
Context &ctx, gpu::BlendMode blendMode, bool dither = false
) const;
public: public:
virtual void draw(Context &ctx, bool active = true) const {} virtual void draw(Context &ctx, bool active = true) const {}

View File

@ -14,7 +14,6 @@
* PERFORMANCE OF THIS SOFTWARE. * PERFORMANCE OF THIS SOFTWARE.
*/ */
#include <stdatomic.h>
#include <stdbool.h> #include <stdbool.h>
#include <stdint.h> #include <stdint.h>
#include "ps1/cop0gte.h" #include "ps1/cop0gte.h"
@ -94,10 +93,9 @@ void uninstallExceptionHandler(void) {
void setInterruptHandler(ArgFunction func, void *arg) { void setInterruptHandler(ArgFunction func, void *arg) {
disableInterrupts(); disableInterrupts();
interruptHandler = func; interruptHandler = func;
interruptHandlerArg = arg; interruptHandlerArg = arg;
atomic_signal_fence(memory_order_release); flushWriteQueue();
} }
void flushCache(void) { void flushCache(void) {
@ -154,5 +152,5 @@ void switchThread(Thread *thread) {
thread = &_mainThread; thread = &_mainThread;
nextThread = thread; nextThread = thread;
atomic_signal_fence(memory_order_release); flushWriteQueue();
} }

View File

@ -53,7 +53,7 @@ extern Thread *nextThread;
* were disabled, any callback set using setInterruptHandler() will be invoked * were disabled, any callback set using setInterruptHandler() will be invoked
* immediately. * immediately.
*/ */
static inline void enableInterrupts(void) { __attribute__((always_inline)) static inline void enableInterrupts(void) {
cop0_setSR(cop0_getSR() | COP0_SR_IEc); 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 * @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(); uint32_t sr = cop0_getSR();
cop0_setSR(sr & ~COP0_SR_IEc); cop0_setSR(sr & ~COP0_SR_IEc);
return (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 * @brief Initializes a thread structure with the provided entry point
* (function) and stacktop. The function *must not* return. The stack should be * (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 arg Optional argument to entry point
* @param stack Pointer to last 8 bytes in the stack * @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 Thread *thread, ArgFunction func, void *arg, void *stack
) { ) {
register uint32_t gp __asm__("gp"); 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 * @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); switchThread(thread);
// Execute a syscall to force the switch to happen. // Execute a syscall to force the switch to happen.

View File

@ -53,7 +53,7 @@
#define FF_FS_NOFSINFO 0 #define FF_FS_NOFSINFO 0
#define FF_FS_LOCK 0 #define FF_FS_LOCK 0
#define FF_FS_REENTRANT 0 #define FF_FS_REENTRANT 1
/* miniz configuration */ /* miniz configuration */