mirror of
https://github.com/spicyjpeg/573in1.git
synced 2025-03-01 07:20:42 +01:00
Refactor, add new SPU driver and filesystem mutex
This commit is contained in:
parent
5fb99fc08c
commit
065ea8ba45
@ -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.
|
||||
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
573in1. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
[ Third-party licenses ]
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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<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);
|
||||
}
|
||||
|
||||
#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
|
||||
|
||||
}
|
||||
|
@ -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[];
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
void resetAllChannels(void) {
|
||||
for (Channel ch = NUM_CHANNELS - 1; ch >= 0; ch--) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
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<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);
|
||||
|
||||
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<uint32_t>(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<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)
|
||||
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<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;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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);
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -16,9 +16,11 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <assert.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include "ps1/system.h"
|
||||
|
||||
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) {
|
||||
//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) {
|
||||
@ -109,6 +111,34 @@ template<typename T> 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 {
|
||||
|
@ -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<ArgFunction>(&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,7 +345,8 @@ 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);
|
||||
@ -351,8 +358,7 @@ void App::_runWorker(
|
||||
&_workerThread, util::forcedCast<ArgFunction>(&App::_worker), this,
|
||||
&stackBottom[(_WORKER_STACK_SIZE - 1) & ~7]
|
||||
);
|
||||
if (enable)
|
||||
enableInterrupts();
|
||||
}
|
||||
|
||||
_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) {
|
||||
|
@ -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);
|
||||
|
@ -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 {}
|
||||
|
@ -14,7 +14,6 @@
|
||||
* PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <stdatomic.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#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();
|
||||
}
|
||||
|
@ -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.
|
||||
|
2
src/vendor/vendorconfig.h
vendored
2
src/vendor/vendorconfig.h
vendored
@ -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 */
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user