mirror of
https://github.com/spicyjpeg/573in1.git
synced 2025-03-01 15:30:31 +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
|
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.
|
||||||
|
@ -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 ]
|
||||||
|
|
||||||
|
@ -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();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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[];
|
||||||
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void resetAllChannels(void) {
|
return 0;
|
||||||
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_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;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
||||||
|
@ -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) {
|
||||||
|
@ -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);
|
||||||
|
@ -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 {}
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
|
@ -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.
|
||||||
|
2
src/vendor/vendorconfig.h
vendored
2
src/vendor/vendorconfig.h
vendored
@ -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 */
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user