Add BS loader, audio stream thread, refactor workers

This commit is contained in:
spicyjpeg 2024-09-16 20:53:40 +02:00
parent a747dabd76
commit fc95fb5dfc
No known key found for this signature in database
GPG Key ID: 5CC87404C01DF393
38 changed files with 1457 additions and 954 deletions

View File

@ -101,18 +101,19 @@ set(
src/main/app/app.cpp src/main/app/app.cpp
src/main/app/cartactions.cpp src/main/app/cartactions.cpp
src/main/app/cartunlock.cpp src/main/app/cartunlock.cpp
src/main/app/cartworkers.cpp
src/main/app/main.cpp src/main/app/main.cpp
src/main/app/misc.cpp src/main/app/misc.cpp
src/main/app/miscworkers.cpp
src/main/app/modals.cpp src/main/app/modals.cpp
src/main/app/romactions.cpp src/main/app/romactions.cpp
src/main/app/romworkers.cpp
src/main/app/tests.cpp src/main/app/tests.cpp
src/main/app/threads.cpp
src/main/cart/cart.cpp src/main/cart/cart.cpp
src/main/cart/cartdata.cpp src/main/cart/cartdata.cpp
src/main/cart/cartio.cpp src/main/cart/cartio.cpp
src/main/cart/zs01.cpp src/main/cart/zs01.cpp
src/main/workers/cartworkers.cpp
src/main/workers/miscworkers.cpp
src/main/workers/romworkers.cpp
src/main/main.cpp src/main/main.cpp
src/main/uibase.cpp src/main/uibase.cpp
src/main/uicommon.cpp src/main/uicommon.cpp

View File

@ -28,7 +28,7 @@ extern "C" const size_t _resourceArchiveLength;
static char _ptrArg[]{ "resource.ptr=xxxxxxxx\0" }; static char _ptrArg[]{ "resource.ptr=xxxxxxxx\0" };
static char _lengthArg[]{ "resource.length=xxxxxxxx\0" }; static char _lengthArg[]{ "resource.length=xxxxxxxx\0" };
struct [[gnu::packed]] ZIPFileHeader { class [[gnu::packed]] ZIPFileHeader {
public: public:
uint32_t magic; uint32_t magic;
uint16_t version, flags, compType; uint16_t version, flags, compType;

View File

@ -14,6 +14,7 @@
* 573in1. If not, see <https://www.gnu.org/licenses/>. * 573in1. If not, see <https://www.gnu.org/licenses/>.
*/ */
#include <assert.h>
#include <stdarg.h> #include <stdarg.h>
#include <stddef.h> #include <stddef.h>
#include <stdint.h> #include <stdint.h>
@ -22,6 +23,9 @@
#include "common/util/hash.hpp" #include "common/util/hash.hpp"
#include "common/util/templates.hpp" #include "common/util/templates.hpp"
#include "common/gpu.hpp" #include "common/gpu.hpp"
#include "common/mdec.hpp"
#include "common/spu.hpp"
#include "ps1/registers.h"
namespace fs { namespace fs {
@ -63,6 +67,7 @@ size_t Provider::loadData(util::Data &output, const char *path) {
return 0; return 0;
assert(file->size <= SIZE_MAX); assert(file->size <= SIZE_MAX);
if (!output.allocate(size_t(file->size))) { if (!output.allocate(size_t(file->size))) {
file->close(); file->close();
delete file; delete file;
@ -83,6 +88,7 @@ size_t Provider::loadData(void *output, size_t length, const char *path) {
return 0; return 0;
assert(file->size >= length); assert(file->size >= length);
size_t actualLength = file->read(output, length); size_t actualLength = file->read(output, length);
file->close(); file->close();
@ -105,32 +111,71 @@ size_t Provider::saveData(const void *input, size_t length, const char *path) {
size_t Provider::loadTIM(gpu::Image &output, const char *path) { size_t Provider::loadTIM(gpu::Image &output, const char *path) {
util::Data data; util::Data data;
size_t loadLength = 0;
if (!loadData(data, path)) if (!loadData(data, path))
return 0; return 0;
auto header = data.as<const gpu::TIMHeader>(); auto header = data.as<const gpu::TIMHeader>();
auto section = reinterpret_cast<const uint8_t *>(&header[1]);
if (!output.initFromTIMHeader(header)) { if (output.initFromTIMHeader(*header)) {
auto image = header->getImage();
auto clut = header->getCLUT();
if (clut)
loadLength += gpu::upload(clut->vram, clut->getData(), true);
loadLength += gpu::upload(image->vram, image->getData(), true);
}
data.destroy(); data.destroy();
return loadLength;
}
size_t Provider::loadBS(
gpu::Image &output, const gpu::RectWH &rect, const char *path
) {
util::Data data;
size_t loadLength = 0;
if (!loadData(data, path))
return 0; return 0;
size_t bsLength = data.as<const mdec::BSHeader>()->getUncompLength();
mdec::BSDecompressor decompressor;
util::Data buffer;
if (buffer.allocate(bsLength)) {
auto bsPtr = buffer.as<uint32_t>();
size_t sliceLength = (16 * rect.h) * 2;
if (!decompressor.decompress(bsPtr, data.ptr, bsLength)) {
// Reuse the file's buffer to store vertical slices received from
// the MDEC as they are uploaded to VRAM.
data.allocate(sliceLength);
mdec::feedBS(bsPtr, MDEC_CMD_FLAG_FORMAT_16BPP, false);
gpu::RectWH slice;
slice.x = rect.x;
slice.y = rect.y;
slice.w = 16;
slice.h = rect.h;
for (int i = rect.w; i > 0; i -= 16) {
mdec::receive(data.ptr, sliceLength, true);
loadLength += gpu::upload(slice, data.ptr, true);
slice.x += 16;
}
} }
size_t uploadLength = 0; buffer.destroy();
if (header->flags & (1 << 3)) {
auto clut = reinterpret_cast<const gpu::TIMSectionHeader *>(section);
uploadLength += gpu::upload(clut->vram, &clut[1], true);
section += clut->length;
} }
auto image = reinterpret_cast<const gpu::TIMSectionHeader *>(section);
uploadLength += gpu::upload(image->vram, &image[1], true);
data.destroy(); data.destroy();
return uploadLength; return loadLength;
} }
size_t Provider::loadVAG( size_t Provider::loadVAG(
@ -139,26 +184,24 @@ size_t Provider::loadVAG(
// Sounds should be decompressed and uploaded to the SPU one chunk at a // Sounds should be decompressed and uploaded to the SPU one chunk at a
// time, but whatever. // time, but whatever.
util::Data data; util::Data data;
size_t loadLength = 0;
if (!loadData(data, path)) if (!loadData(data, path))
return 0; return 0;
auto header = data.as<const spu::VAGHeader>(); auto header = data.as<const spu::VAGHeader>();
auto body = reinterpret_cast<const uint32_t *>(&header[1]);
if (!output.initFromVAGHeader(header, offset)) { if (output.initFromVAGHeader(*header, offset))
data.destroy(); loadLength = spu::upload(
return 0; offset, header->getData(), data.length - sizeof(spu::VAGHeader),
} true
);
auto uploadLength =
spu::upload(offset, body, data.length - sizeof(spu::VAGHeader), true);
data.destroy(); data.destroy();
return uploadLength; return loadLength;
} }
struct [[gnu::packed]] BMPHeader { class [[gnu::packed]] BMPHeader {
public: public:
uint16_t magic; uint16_t magic;
uint32_t fileLength; uint32_t fileLength;
@ -186,7 +229,7 @@ public:
} }
}; };
size_t Provider::saveVRAMBMP(gpu::RectWH &rect, const char *path) { size_t Provider::saveVRAMBMP(const gpu::RectWH &rect, const char *path) {
auto file = openFile(path, WRITE | ALLOW_CREATE); auto file = openFile(path, WRITE | ALLOW_CREATE);
if (!file) if (!file)
@ -205,11 +248,11 @@ size_t Provider::saveVRAMBMP(gpu::RectWH &rect, const char *path) {
gpu::RectWH slice; gpu::RectWH slice;
slice.x = rect.x; slice.x = rect.x;
slice.y = rect.y + rect.h - 1;
slice.w = rect.w; slice.w = rect.w;
slice.h = 1; slice.h = 1;
for (int y = rect.y + rect.h - 1; y >= rect.y; y--) { for (; slice.y >= rect.y; slice.y--) {
slice.y = y;
auto lineLength = gpu::download(slice, buffer.ptr, true); auto lineLength = gpu::download(slice, buffer.ptr, true);
// BMP stores channels in BGR order as opposed to RGB, so the red // BMP stores channels in BGR order as opposed to RGB, so the red

View File

@ -151,8 +151,9 @@ public:
virtual size_t saveData(const void *input, size_t length, const char *path); virtual size_t saveData(const void *input, size_t length, const char *path);
size_t loadTIM(gpu::Image &output, const char *path); size_t loadTIM(gpu::Image &output, const char *path);
size_t loadBS(gpu::Image &output, const gpu::RectWH &rect, const char *path);
size_t loadVAG(spu::Sound &output, uint32_t offset, const char *path); size_t loadVAG(spu::Sound &output, uint32_t offset, const char *path);
size_t saveVRAMBMP(gpu::RectWH &rect, const char *path); size_t saveVRAMBMP(const gpu::RectWH &rect, const char *path);
}; };
/* String table parser */ /* String table parser */

View File

@ -355,22 +355,17 @@ void Image::initFromVRAMRect(
texpage = gp0_page(rect.x / 64, rect.y / 256, blendMode, colorDepth); texpage = gp0_page(rect.x / 64, rect.y / 256, blendMode, colorDepth);
} }
bool Image::initFromTIMHeader(const TIMHeader *header, BlendMode blendMode) { bool Image::initFromTIMHeader(const TIMHeader &header, BlendMode blendMode) {
if (header->magic != 0x10) if (!header.validateMagic())
return false; return false;
auto ptr = reinterpret_cast<const uint8_t *>(&header[1]); auto image = header.getImage();
auto clut = header.getCLUT();
if (header->flags & (1 << 3)) {
auto clut = reinterpret_cast<const TIMSectionHeader *>(ptr);
if (clut)
palette = gp0_clut(clut->vram.x / 16, clut->vram.y); palette = gp0_clut(clut->vram.x / 16, clut->vram.y);
ptr += clut->length;
}
auto image = reinterpret_cast<const TIMSectionHeader *>(ptr); initFromVRAMRect(image->vram, header.getColorDepth(), blendMode);
initFromVRAMRect(image->vram, ColorDepth(header->flags & 3), blendMode);
return true; return true;
} }

View File

@ -66,8 +66,8 @@ static inline void enableDisplay(bool enable) {
GPU_GP1 = gp1_dispBlank(!enable); GPU_GP1 = gp1_dispBlank(!enable);
} }
size_t upload(const RectWH &rect, const void *data, bool wait); size_t upload(const RectWH &rect, const void *data, bool wait = false);
size_t download(const RectWH &rect, void *data, bool wait); size_t download(const RectWH &rect, void *data, bool wait = false);
/* Rendering context */ /* Rendering context */
@ -181,15 +181,46 @@ public:
/* Image class */ /* Image class */
struct TIMHeader { class TIMSectionHeader {
public:
uint32_t magic, flags;
};
struct TIMSectionHeader {
public: public:
uint32_t length; uint32_t length;
RectWH vram; RectWH vram;
inline const void *getData(void) const {
return this + 1;
}
inline const TIMSectionHeader *getNextSection(void) const {
return reinterpret_cast<const TIMSectionHeader *>(
reinterpret_cast<uintptr_t>(this) + length
);
}
};
class TIMHeader {
public:
uint32_t magic, flags;
inline bool validateMagic(void) const {
return (magic == 0x10) && (getColorDepth() <= GP0_COLOR_16BPP);
}
inline ColorDepth getColorDepth(void) const {
return ColorDepth(flags & 7);
}
inline const TIMSectionHeader *getImage(void) const {
auto image = reinterpret_cast<const TIMSectionHeader *>(this + 1);
if (flags & (1 << 3))
image = image->getNextSection();
return image;
}
inline const TIMSectionHeader *getCLUT(void) const {
if (flags & (1 << 3))
return reinterpret_cast<const TIMSectionHeader *>(this + 1);
return nullptr;
}
}; };
class Image { class Image {
@ -205,7 +236,7 @@ public:
BlendMode blendMode = GP0_BLEND_SEMITRANS BlendMode blendMode = GP0_BLEND_SEMITRANS
); );
bool initFromTIMHeader( bool initFromTIMHeader(
const TIMHeader *header, BlendMode blendMode = GP0_BLEND_SEMITRANS const TIMHeader &header, BlendMode blendMode = GP0_BLEND_SEMITRANS
); );
void drawScaled( void drawScaled(
Context &ctx, int x, int y, int w, int h, bool blend = false Context &ctx, int x, int y, int w, int h, bool blend = false

View File

@ -39,7 +39,7 @@ public:
int8_t baselineOffset; int8_t baselineOffset;
}; };
struct FontMetricsEntry { class FontMetricsEntry {
public: public:
uint32_t codePoint; uint32_t codePoint;
CharacterSize size; CharacterSize size;

View File

@ -20,6 +20,7 @@
#include <stdio.h> #include <stdio.h>
#include "common/util/misc.hpp" #include "common/util/misc.hpp"
#include "common/util/string.hpp" #include "common/util/string.hpp"
#include "common/util/templates.hpp"
#include "common/io.hpp" #include "common/io.hpp"
#include "ps1/registers.h" #include "ps1/registers.h"
#include "ps1/registers573.h" #include "ps1/registers573.h"
@ -35,12 +36,6 @@ static constexpr int _IDE_RESET_ASSERT_DELAY = 5000;
static constexpr int _IDE_RESET_CLEAR_DELAY = 50000; static constexpr int _IDE_RESET_CLEAR_DELAY = 50000;
void init(void) { void init(void) {
// Remapping the base address is required in order for IDE DMA to work
// properly, as the BIU will output it over the address lines during a DMA
// transfer. It does not affect non-DMA access since the BIU will replace
// the bottommost N bits, where N is the number of address lines used, with
// the respective CPU address bits.
BIU_DEV0_ADDR = reinterpret_cast<uint32_t>(SYS573_IDE_CS0_BASE) & 0x1fffffff;
BIU_DEV0_CTRL = 0 BIU_DEV0_CTRL = 0
| (7 << 0) // Write delay | (7 << 0) // Write delay
| (4 << 4) // Read delay | (4 << 4) // Read delay
@ -84,6 +79,65 @@ void resetIDEDevices(void) {
delayMicroseconds(_IDE_RESET_CLEAR_DELAY); delayMicroseconds(_IDE_RESET_CLEAR_DELAY);
} }
/* System bus DMA */
static constexpr int _DMA_TIMEOUT = 100000;
size_t doDMARead(volatile void *source, void *data, size_t length, bool wait) {
length = (length + 3) / 4;
util::assertAligned<uint32_t>(data);
if (!waitForDMATransfer(DMA_PIO, _DMA_TIMEOUT))
return 0;
// The BIU will output the base address set through this register over the
// address lines during a DMA transfer. This does not affect non-DMA access
// as the BIU will realign the address by masking off the bottommost N bits
// (where N is the number of address lines used) and replace them with the
// respective CPU address bits.
BIU_DEV0_ADDR = reinterpret_cast<uint32_t>(source) & 0x1fffffff;
DMA_MADR(DMA_PIO) = reinterpret_cast<uint32_t>(data);
DMA_BCR (DMA_PIO) = length;
DMA_CHCR(DMA_PIO) = 0
| DMA_CHCR_READ
| DMA_CHCR_MODE_BURST
| DMA_CHCR_ENABLE
| DMA_CHCR_TRIGGER;
if (wait)
waitForDMATransfer(DMA_PIO, _DMA_TIMEOUT);
return length * 4;
}
size_t doDMAWrite(
volatile void *dest, const void *data, size_t length, bool wait
) {
length = (length + 3) / 4;
util::assertAligned<uint32_t>(data);
if (!waitForDMATransfer(DMA_PIO, _DMA_TIMEOUT))
return 0;
BIU_DEV0_ADDR = reinterpret_cast<uint32_t>(dest) & 0x1fffffff;
DMA_MADR(DMA_PIO) = reinterpret_cast<uint32_t>(data);
DMA_BCR (DMA_PIO) = length;
DMA_CHCR(DMA_PIO) = 0
| DMA_CHCR_WRITE
| DMA_CHCR_MODE_BURST
| DMA_CHCR_ENABLE
| DMA_CHCR_TRIGGER;
if (wait)
waitForDMATransfer(DMA_PIO, _DMA_TIMEOUT);
return length * 4;
}
/* JAMMA and RTC functions */ /* JAMMA and RTC functions */
uint32_t getJAMMAInputs(void) { uint32_t getJAMMAInputs(void) {

View File

@ -161,6 +161,15 @@ static inline void setMiscOutput(MiscOutputPin pin, bool value) {
void init(void); void init(void);
void resetIDEDevices(void); void resetIDEDevices(void);
/* System bus DMA */
size_t doDMARead(
volatile void *source, void *data, size_t length, bool wait = false
);
size_t doDMAWrite(
volatile void *dest, const void *data, size_t length, bool wait = false
);
/* JAMMA and RTC functions */ /* JAMMA and RTC functions */
uint32_t getJAMMAInputs(void); uint32_t getJAMMAInputs(void);

View File

@ -62,7 +62,8 @@ static const uint8_t _BS_QUANT_TABLE[]{
/* Basic API */ /* Basic API */
static constexpr int _DMA_CHUNK_SIZE = 32; static constexpr int _DMA_CHUNK_SIZE_IN = 32;
static constexpr int _DMA_CHUNK_SIZE_OUT = 32;
static constexpr int _DMA_TIMEOUT = 100000; static constexpr int _DMA_TIMEOUT = 100000;
void init(void) { void init(void) {
@ -82,16 +83,16 @@ size_t feed(const void *data, size_t length, bool wait) {
util::assertAligned<uint32_t>(data); util::assertAligned<uint32_t>(data);
#if 0 #if 0
assert(!(length % _DMA_CHUNK_SIZE)); assert(!(length % _DMA_CHUNK_SIZE_IN));
#else #else
length = (length + _DMA_CHUNK_SIZE - 1) / _DMA_CHUNK_SIZE; length = (length + _DMA_CHUNK_SIZE_IN - 1) / _DMA_CHUNK_SIZE_IN;
#endif #endif
if (!waitForDMATransfer(DMA_MDEC_IN, _DMA_TIMEOUT)) if (!waitForDMATransfer(DMA_MDEC_IN, _DMA_TIMEOUT))
return 0; return 0;
DMA_MADR(DMA_MDEC_IN) = reinterpret_cast<uint32_t>(data); DMA_MADR(DMA_MDEC_IN) = reinterpret_cast<uint32_t>(data);
DMA_BCR (DMA_MDEC_IN) = util::concat4(_DMA_CHUNK_SIZE, length); DMA_BCR (DMA_MDEC_IN) = util::concat4(_DMA_CHUNK_SIZE_IN, length);
DMA_CHCR(DMA_MDEC_IN) = 0 DMA_CHCR(DMA_MDEC_IN) = 0
| DMA_CHCR_WRITE | DMA_CHCR_WRITE
| DMA_CHCR_MODE_SLICE | DMA_CHCR_MODE_SLICE
@ -100,7 +101,33 @@ size_t feed(const void *data, size_t length, bool wait) {
if (wait) if (wait)
waitForDMATransfer(DMA_MDEC_IN, _DMA_TIMEOUT); waitForDMATransfer(DMA_MDEC_IN, _DMA_TIMEOUT);
return length * _DMA_CHUNK_SIZE * 4; return length * _DMA_CHUNK_SIZE_IN * 4;
}
size_t receive(void *data, size_t length, bool wait) {
length /= 4;
util::assertAligned<uint32_t>(data);
#if 0
assert(!(length % _DMA_CHUNK_SIZE_OUT));
#else
length = (length + _DMA_CHUNK_SIZE_OUT - 1) / _DMA_CHUNK_SIZE_OUT;
#endif
if (!waitForDMATransfer(DMA_MDEC_OUT, _DMA_TIMEOUT))
return 0;
DMA_MADR(DMA_MDEC_OUT) = reinterpret_cast<uint32_t>(data);
DMA_BCR (DMA_MDEC_OUT) = util::concat4(_DMA_CHUNK_SIZE_OUT, length);
DMA_CHCR(DMA_MDEC_OUT) = 0
| DMA_CHCR_READ
| DMA_CHCR_MODE_SLICE
| DMA_CHCR_ENABLE;
if (wait)
waitForDMATransfer(DMA_MDEC_OUT, _DMA_TIMEOUT);
return length * _DMA_CHUNK_SIZE_OUT * 4;
} }
/* MDEC bitstream decompressor */ /* MDEC bitstream decompressor */

View File

@ -25,13 +25,16 @@ namespace mdec {
/* Basic API */ /* Basic API */
void init(void); void init(void);
size_t feed(const void *data, size_t length, bool wait); size_t feed(const void *data, size_t length, bool wait = false);
size_t receive(void *data, size_t length, bool wait = false);
static inline bool isIdle(void) { static inline bool isIdle(void) {
return !(MDEC1 & MDEC_STAT_BUSY); return !(MDEC1 & MDEC_STAT_BUSY);
} }
static inline size_t feedBS(const uint32_t *data, uint32_t flags, bool wait) { static inline size_t feedBS(
const uint32_t *data, uint32_t flags, bool wait = false
) {
size_t length = data[0] & MDEC_CMD_FLAG_LENGTH_BITMASK; size_t length = data[0] & MDEC_CMD_FLAG_LENGTH_BITMASK;
MDEC0 = MDEC_CMD_DECODE | length | flags; MDEC0 = MDEC_CMD_DECODE | length | flags;
@ -40,12 +43,19 @@ static inline size_t feedBS(const uint32_t *data, uint32_t flags, bool wait) {
/* MDEC bitstream decompressor */ /* MDEC bitstream decompressor */
struct BSHeader { class BSHeader {
public: public:
uint16_t outputLength; uint16_t outputLength;
uint16_t mdecCommand; uint16_t mdecCommand;
uint16_t quantScale; uint16_t quantScale;
uint16_t version; uint16_t version;
inline size_t getUncompLength(void) const {
// DMA feeds data to the MDEC in 32-word chunks so the uncompressed
// length has to be rounded accordingly. Additionally, the decompressor
// generates a 4-byte header containing the command to send to the MDEC.
return (outputLength + 4 + 127) / 128;
}
}; };
enum BSDecompressorError { enum BSDecompressorError {
@ -55,7 +65,7 @@ enum BSDecompressorError {
}; };
extern "C" BSDecompressorError _bsDecompressorStart( extern "C" BSDecompressorError _bsDecompressorStart(
void *_this, uint32_t *output, size_t outputLength, const uint32_t *input void *_this, uint32_t *output, size_t outputLength, const void *input
); );
extern "C" BSDecompressorError _bsDecompressorResume( extern "C" BSDecompressorError _bsDecompressorResume(
void *_this, uint32_t *output, size_t outputLength void *_this, uint32_t *output, size_t outputLength
@ -63,7 +73,7 @@ extern "C" BSDecompressorError _bsDecompressorResume(
class BSDecompressor { class BSDecompressor {
protected: protected:
const uint32_t *_input; const void *_input;
uint32_t _bits, _nextBits; uint32_t _bits, _nextBits;
size_t _remaining; size_t _remaining;
@ -76,7 +86,7 @@ protected:
public: public:
inline BSDecompressorError decompress( inline BSDecompressorError decompress(
uint32_t *output, const uint32_t *input, size_t outputLength uint32_t *output, const void *input, size_t outputLength
) { ) {
return _bsDecompressorStart(this, output, outputLength, input); return _bsDecompressorStart(this, output, outputLength, input);
} }

View File

@ -59,7 +59,7 @@ void init(void) {
SPU_MASTER_VOL_R = 0; SPU_MASTER_VOL_R = 0;
SPU_REVERB_VOL_L = 0; SPU_REVERB_VOL_L = 0;
SPU_REVERB_VOL_R = 0; SPU_REVERB_VOL_R = 0;
SPU_REVERB_ADDR = 0xfffe; SPU_REVERB_ADDR = SPU_RAM_END / 8;
SPU_FLAG_FM1 = 0; SPU_FLAG_FM1 = 0;
SPU_FLAG_FM2 = 0; SPU_FLAG_FM2 = 0;
@ -88,6 +88,8 @@ void init(void) {
} }
Channel getFreeChannel(void) { Channel getFreeChannel(void) {
util::CriticalSection sec;
#if 0 #if 0
// The status flag gets set when a channel stops or loops for the first // The status flag gets set when a channel stops or loops for the first
// 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
@ -111,6 +113,8 @@ Channel getFreeChannel(void) {
} }
ChannelMask getFreeChannels(int count) { ChannelMask getFreeChannels(int count) {
util::CriticalSection sec;
ChannelMask mask = 0; ChannelMask mask = 0;
for (Channel ch = 0; ch < NUM_CHANNELS; ch++) { for (Channel ch = 0; ch < NUM_CHANNELS; ch++) {
@ -130,6 +134,9 @@ ChannelMask getFreeChannels(int count) {
void stopChannels(ChannelMask mask) { void stopChannels(ChannelMask mask) {
mask &= ALL_CHANNELS; mask &= ALL_CHANNELS;
SPU_FLAG_OFF1 = mask & 0xffff;
SPU_FLAG_OFF2 = mask >> 16;
for (Channel ch = 0; mask; ch++, mask >>= 1) { for (Channel ch = 0; mask; ch++, mask >>= 1) {
if (!(mask & 1)) if (!(mask & 1))
continue; continue;
@ -140,8 +147,6 @@ void stopChannels(ChannelMask mask) {
SPU_CH_ADDR(ch) = DUMMY_BLOCK_OFFSET / 8; SPU_CH_ADDR(ch) = DUMMY_BLOCK_OFFSET / 8;
} }
SPU_FLAG_OFF1 = mask & 0xffff;
SPU_FLAG_OFF2 = mask >> 16;
SPU_FLAG_ON1 = mask & 0xffff; SPU_FLAG_ON1 = mask & 0xffff;
SPU_FLAG_ON2 = mask >> 16; SPU_FLAG_ON2 = mask >> 16;
} }
@ -223,16 +228,13 @@ size_t download(uint32_t offset, void *data, size_t length, bool wait) {
Sound::Sound(void) Sound::Sound(void)
: offset(0), sampleRate(0), length(0) {} : offset(0), sampleRate(0), length(0) {}
bool Sound::initFromVAGHeader(const VAGHeader *header, uint32_t _offset) { bool Sound::initFromVAGHeader(const VAGHeader &header, uint32_t _offset) {
if (header->magic != util::concat4('V', 'A', 'G', 'p')) if (!header.validateMagic())
return false;
if (header->channels > 1)
return false; return false;
offset = _offset; offset = _offset;
sampleRate = (__builtin_bswap32(header->sampleRate) << 12) / 44100; sampleRate = header.getSPUSampleRate();
length = __builtin_bswap32(header->length) / 8; length = header.getSPULength();
return true; return true;
} }
@ -315,21 +317,20 @@ channels(0) {
} }
bool Stream::initFromVAGHeader( bool Stream::initFromVAGHeader(
const VAGHeader *header, uint32_t _offset, size_t _numChunks const VAGHeader &header, uint32_t _offset, size_t _numChunks
) { ) {
if (isPlaying()) if (isPlaying())
return false; return false;
if (header->magic != util::concat4('V', 'A', 'G', 'i')) if (!header.validateInterleavedMagic())
return false;
if (!header->interleave)
return false; return false;
resetBuffer();
offset = _offset; offset = _offset;
interleave = header->interleave; interleave = header.interleave;
numChunks = _numChunks; numChunks = _numChunks;
sampleRate = (__builtin_bswap32(header->sampleRate) << 12) / 44100; sampleRate = header.getSPUSampleRate();
channels = header->channels ? header->channels : 2; channels = header.channels ? header.channels : 2;
return true; return true;
} }
@ -380,12 +381,13 @@ ChannelMask Stream::start(uint16_t left, uint16_t right, ChannelMask mask) {
void Stream::stop(void) { void Stream::stop(void) {
util::CriticalSection sec; util::CriticalSection sec;
if (isPlaying()) { if (!isPlaying())
return;
SPU_CTRL &= ~SPU_CTRL_IRQ_ENABLE; SPU_CTRL &= ~SPU_CTRL_IRQ_ENABLE;
_channelMask = 0;
stopChannels(_channelMask); stopChannels(_channelMask);
} _channelMask = 0;
flushWriteQueue(); flushWriteQueue();
} }
@ -402,30 +404,30 @@ void Stream::handleInterrupt(void) {
_configureIRQ(); _configureIRQ();
} }
size_t Stream::feed(const void *data, size_t count) { size_t Stream::feed(const void *data, size_t length) {
util::CriticalSection sec; util::CriticalSection sec;
auto ptr = reinterpret_cast<uintptr_t>(data); auto ptr = reinterpret_cast<uintptr_t>(data);
auto chunkLength = getChunkLength(); auto chunkLength = getChunkLength();
count = util::min(count, getFreeChunkCount());
for (auto i = count; i; i--) { length = util::min(length, getFreeChunkCount() * chunkLength);
for (int i = length; i >= int(chunkLength); i -= chunkLength) {
upload( upload(
_getChunkOffset(_tail), reinterpret_cast<const void *>(ptr), _getChunkOffset(_tail), reinterpret_cast<const void *>(ptr),
chunkLength, true chunkLength, true
); );
_tail = (_tail + 1) % numChunks;
ptr += chunkLength; ptr += chunkLength;
_tail = (_tail + 1) % numChunks;
_bufferedChunks++;
} }
_bufferedChunks += count;
if (isPlaying()) if (isPlaying())
_configureIRQ(); _configureIRQ();
flushWriteQueue(); flushWriteQueue();
return count; return length;
} }
void Stream::resetBuffer(void) { void Stream::resetBuffer(void) {

View File

@ -18,6 +18,7 @@
#include <stddef.h> #include <stddef.h>
#include <stdint.h> #include <stdint.h>
#include "common/util/templates.hpp"
#include "ps1/registers.h" #include "ps1/registers.h"
namespace spu { namespace spu {
@ -31,8 +32,9 @@ enum LoopFlag : uint8_t {
LOOP_START = 1 << 2 LOOP_START = 1 << 2
}; };
static constexpr uint32_t DUMMY_BLOCK_OFFSET = 0x1000; static constexpr uint32_t DUMMY_BLOCK_OFFSET = 0x01000;
static constexpr uint32_t DUMMY_BLOCK_END = 0x1010; static constexpr uint32_t DUMMY_BLOCK_END = 0x01010;
static constexpr uint32_t SPU_RAM_END = 0x7fff0;
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;
@ -65,18 +67,37 @@ static inline void stopChannel(Channel ch) {
stopChannels(1 << ch); stopChannels(1 << ch);
} }
size_t upload(uint32_t offset, const void *data, size_t length, bool wait); size_t upload(
size_t download(uint32_t offset, void *data, size_t length, bool wait); uint32_t offset, const void *data, size_t length, bool wait = false
);
size_t download(uint32_t offset, void *data, size_t length, bool wait = false);
/* Sound class */ /* Sound class */
static constexpr size_t INTERLEAVED_VAG_BODY_OFFSET = 2048; static constexpr size_t INTERLEAVED_VAG_BODY_OFFSET = 2048;
struct VAGHeader { class VAGHeader {
public: public:
uint32_t magic, version, interleave, length, sampleRate; uint32_t magic, version, interleave, length, sampleRate;
uint16_t _reserved[5], channels; uint16_t _reserved[5], channels;
char name[16]; char name[16];
inline bool validateMagic(void) const {
return (magic == util::concat4('V', 'A', 'G', 'p')) && (channels <= 1);
}
inline bool validateInterleavedMagic(void) const {
return (magic == util::concat4('V', 'A', 'G', 'i')) && interleave;
}
inline uint16_t getSPUSampleRate(void) const {
return (__builtin_bswap32(sampleRate) << 12) / 44100;
}
inline uint16_t getSPULength(void) const {
return __builtin_bswap32(length) / 8;
}
inline const void *getData(void) const {
return this + 1;
}
}; };
class Sound { class Sound {
@ -91,7 +112,7 @@ public:
} }
Sound(void); Sound(void);
bool initFromVAGHeader(const VAGHeader *header, uint32_t _offset); 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;
}; };
@ -125,6 +146,12 @@ public:
return (_channelMask != 0); return (_channelMask != 0);
} }
inline bool isUnderrun(void) const {
__atomic_signal_fence(__ATOMIC_ACQUIRE);
return !_bufferedChunks;
}
inline size_t getChunkLength(void) const { inline size_t getChunkLength(void) const {
return size_t(interleave) * size_t(channels); return size_t(interleave) * size_t(channels);
} }
@ -132,19 +159,19 @@ public:
__atomic_signal_fence(__ATOMIC_ACQUIRE); __atomic_signal_fence(__ATOMIC_ACQUIRE);
// The currently playing chunk cannot be overwritten. // The currently playing chunk cannot be overwritten.
size_t playingChunk = isPlaying() ? 1 : 0; size_t playingChunk = _channelMask ? 1 : 0;
return numChunks - (_bufferedChunks + playingChunk); return numChunks - (_bufferedChunks + playingChunk);
} }
Stream(void); Stream(void);
bool initFromVAGHeader( bool initFromVAGHeader(
const VAGHeader *header, uint32_t _offset, size_t _numChunks const VAGHeader &header, uint32_t _offset, size_t _numChunks
); );
ChannelMask start(uint16_t left, uint16_t right, ChannelMask mask); ChannelMask start(uint16_t left, uint16_t right, ChannelMask mask);
void stop(void); void stop(void);
void handleInterrupt(void); void handleInterrupt(void);
size_t feed(const void *data, size_t count); size_t feed(const void *data, size_t length);
void resetBuffer(void); void resetBuffer(void);
}; };

View File

@ -103,9 +103,9 @@ DeviceError ATADevice::_transfer(
return error; return error;
if (write) if (write)
_writePIO(reinterpret_cast<const void *>(ptr), _SECTOR_LENGTH); _writeData(reinterpret_cast<const void *>(ptr), _SECTOR_LENGTH);
else else
_readPIO(reinterpret_cast<void *>(ptr), _SECTOR_LENGTH); _readData(reinterpret_cast<void *>(ptr), _SECTOR_LENGTH);
} }
lba += chunkLength; lba += chunkLength;
@ -131,7 +131,7 @@ DeviceError ATADevice::enumerate(void) {
if (_waitForDRQ(_DETECT_TIMEOUT)) if (_waitForDRQ(_DETECT_TIMEOUT))
return NO_DRIVE; return NO_DRIVE;
_readPIO(&block, sizeof(IDEIdentifyBlock)); _readData(&block, sizeof(IDEIdentifyBlock));
if (!block.validateChecksum()) if (!block.validateChecksum())
return CHECKSUM_MISMATCH; return CHECKSUM_MISMATCH;

View File

@ -131,7 +131,7 @@ DeviceError ATAPIDevice::_requestSense(void) {
error = _waitForDRQ(_REQ_SENSE_TIMEOUT, true); error = _waitForDRQ(_REQ_SENSE_TIMEOUT, true);
} }
if (!error) { if (!error) {
_writePIO(&packet, _getPacketLength()); _writeData(&packet, _getPacketLength());
error = _waitForDRQ(_REQ_SENSE_TIMEOUT, true); error = _waitForDRQ(_REQ_SENSE_TIMEOUT, true);
} }
@ -141,7 +141,7 @@ DeviceError ATAPIDevice::_requestSense(void) {
if (!error) { if (!error) {
size_t length = _getCylinder(); size_t length = _getCylinder();
_readPIO(&lastSenseData, length); _readData(&lastSenseData, length);
LOG_STORAGE("data ok, length=0x%x", length); LOG_STORAGE("data ok, length=0x%x", length);
} else { } else {
// If the request sense command fails, fall back to reading the sense // If the request sense command fails, fall back to reading the sense
@ -185,7 +185,7 @@ DeviceError ATAPIDevice::_issuePacket(
error = _waitForDRQ(); error = _waitForDRQ();
} }
if (!error) { if (!error) {
_writePIO(&packet, _getPacketLength()); _writeData(&packet, _getPacketLength());
error = dataLength error = dataLength
? _waitForDRQ() ? _waitForDRQ()
@ -228,7 +228,7 @@ DeviceError ATAPIDevice::enumerate(void) {
if (_waitForDRQ(_DETECT_TIMEOUT)) if (_waitForDRQ(_DETECT_TIMEOUT))
return NO_DRIVE; return NO_DRIVE;
_readPIO(&block, sizeof(IDEIdentifyBlock)); _readData(&block, sizeof(IDEIdentifyBlock));
if (!block.validateChecksum()) if (!block.validateChecksum())
return CHECKSUM_MISMATCH; return CHECKSUM_MISMATCH;
@ -298,7 +298,7 @@ DeviceError ATAPIDevice::read(void *data, uint64_t lba, size_t count) {
size_t chunkLength = _getCylinder(); size_t chunkLength = _getCylinder();
_readPIO(reinterpret_cast<void *>(ptr), chunkLength); _readData(reinterpret_cast<void *>(ptr), chunkLength);
ptr += chunkLength; ptr += chunkLength;
} }

View File

@ -20,6 +20,7 @@
#include "common/storage/atapi.hpp" #include "common/storage/atapi.hpp"
#include "common/storage/device.hpp" #include "common/storage/device.hpp"
#include "common/util/log.hpp" #include "common/util/log.hpp"
#include "common/io.hpp"
#include "ps1/registers573.h" #include "ps1/registers573.h"
#include "ps1/system.h" #include "ps1/system.h"
@ -98,11 +99,11 @@ int IDEIdentifyBlock::getHighestPIOMode(void) const {
/* IDE data transfers */ /* IDE data transfers */
static constexpr int _DMA_TIMEOUT = 10000; void IDEDevice::_readData(void *data, size_t length) const {
#if 0
void IDEDevice::_readPIO(void *data, size_t length) const { io::doDMARead(&SYS573_IDE_CS0_BASE[CS0_DATA], data, length);
length++; #else
length /= 2; length = (length + 1) / 2;
util::assertAligned<uint16_t>(data); util::assertAligned<uint16_t>(data);
@ -110,11 +111,14 @@ void IDEDevice::_readPIO(void *data, size_t length) const {
for (; length > 0; length--) for (; length > 0; length--)
*(ptr++) = SYS573_IDE_CS0_BASE[CS0_DATA]; *(ptr++) = SYS573_IDE_CS0_BASE[CS0_DATA];
#endif
} }
void IDEDevice::_writePIO(const void *data, size_t length) const { void IDEDevice::_writeData(const void *data, size_t length) const {
length++; #if 0
length /= 2; io::doDMAWrite(&SYS573_IDE_CS0_BASE[CS0_DATA], data, length);
#else
length = (length + 1) / 2;
util::assertAligned<uint16_t>(data); util::assertAligned<uint16_t>(data);
@ -122,40 +126,7 @@ void IDEDevice::_writePIO(const void *data, size_t length) const {
for (; length > 0; length--) for (; length > 0; length--)
SYS573_IDE_CS0_BASE[CS0_DATA] = *(ptr++); SYS573_IDE_CS0_BASE[CS0_DATA] = *(ptr++);
} #endif
bool IDEDevice::_readDMA(void *data, size_t length) const {
util::assertAligned<uint32_t>(data);
if (!waitForDMATransfer(DMA_PIO, _DMA_TIMEOUT))
return false;
DMA_MADR(DMA_PIO) = reinterpret_cast<uint32_t>(data);
DMA_BCR (DMA_PIO) = (length + 3) / 4;
DMA_CHCR(DMA_PIO) = 0
| DMA_CHCR_READ
| DMA_CHCR_MODE_BURST
| DMA_CHCR_ENABLE
| DMA_CHCR_TRIGGER;
return waitForDMATransfer(DMA_PIO, _DMA_TIMEOUT);
}
bool IDEDevice::_writeDMA(const void *data, size_t length) const {
util::assertAligned<uint32_t>(data);
if (!waitForDMATransfer(DMA_PIO, _DMA_TIMEOUT))
return false;
DMA_MADR(DMA_PIO) = reinterpret_cast<uint32_t>(data);
DMA_BCR (DMA_PIO) = (length + 3) / 4;
DMA_CHCR(DMA_PIO) = 0
| DMA_CHCR_WRITE
| DMA_CHCR_MODE_BURST
| DMA_CHCR_ENABLE
| DMA_CHCR_TRIGGER;
return waitForDMATransfer(DMA_PIO, _DMA_TIMEOUT);
} }
/* IDE status and error polling */ /* IDE status and error polling */

View File

@ -271,10 +271,8 @@ protected:
return util::concat2(_get(CS0_CYLINDER_L), _get(CS0_CYLINDER_H)); return util::concat2(_get(CS0_CYLINDER_L), _get(CS0_CYLINDER_H));
} }
void _readPIO(void *data, size_t length) const; void _readData(void *data, size_t length) const;
void _writePIO(const void *data, size_t length) const; void _writeData(const void *data, size_t length) const;
bool _readDMA(void *data, size_t length) const;
bool _writeDMA(const void *data, size_t length) const;
DeviceError _setup(const IDEIdentifyBlock &block); DeviceError _setup(const IDEIdentifyBlock &block);
DeviceError _waitForIdle( DeviceError _waitForIdle(

View File

@ -82,10 +82,6 @@ template<typename T> static constexpr inline size_t countOf(T &array) {
return sizeof(array) / sizeof(array[0]); return sizeof(array) / sizeof(array[0]);
} }
template<typename T, typename X> static inline T forcedCast(X item) {
return reinterpret_cast<T>(reinterpret_cast<void *>(item));
}
static constexpr inline uint16_t concat2(uint8_t low, uint8_t high) { static constexpr inline uint16_t concat2(uint8_t low, uint8_t high) {
return low | (high << 8); return low | (high << 8);
} }

View File

@ -14,7 +14,9 @@
* 573in1. If not, see <https://www.gnu.org/licenses/>. * 573in1. If not, see <https://www.gnu.org/licenses/>.
*/ */
#include <assert.h>
#include <stddef.h> #include <stddef.h>
#include <stdint.h>
#include <stdio.h> #include <stdio.h>
#include "common/fs/fat.hpp" #include "common/fs/fat.hpp"
#include "common/fs/file.hpp" #include "common/fs/file.hpp"
@ -30,7 +32,9 @@
#include "common/io.hpp" #include "common/io.hpp"
#include "common/spu.hpp" #include "common/spu.hpp"
#include "main/app/app.hpp" #include "main/app/app.hpp"
#include "main/app/threads.hpp"
#include "main/cart/cart.hpp" #include "main/cart/cart.hpp"
#include "main/workers/miscworkers.hpp"
#include "main/uibase.hpp" #include "main/uibase.hpp"
#include "ps1/system.h" #include "ps1/system.h"
@ -201,7 +205,7 @@ void FileIOManager::closeResourceFile(void) {
/* App class */ /* App class */
static constexpr size_t _WORKER_STACK_SIZE = 0x20000; static constexpr size_t _WORKER_STACK_SIZE = 0x8000;
static constexpr int _SPLASH_SCREEN_TIMEOUT = 5; static constexpr int _SPLASH_SCREEN_TIMEOUT = 5;
@ -239,18 +243,42 @@ void App::_unloadCartData(void) {
#endif #endif
} }
void App::_setupInterrupts(void) { void App::_updateOverlays(void) {
setInterruptHandler( // Date and time overlay
util::forcedCast<ArgFunction>(&App::_interruptHandler), this static char dateString[24];
); util::Date date;
IRQ_MASK = 0 io::getRTCTime(date);
| (1 << IRQ_VSYNC) date.toString(dateString);
| (1 << IRQ_SPU)
| (1 << IRQ_PIO); _textOverlay.leftText = dateString;
enableInterrupts();
// Splash screen overlay
int timeout = _ctx.gpuCtx.refreshRate * _SPLASH_SCREEN_TIMEOUT;
__atomic_signal_fence(__ATOMIC_ACQUIRE);
if ((_workerStatus.status != WORKER_BUSY) || (_ctx.time > timeout))
_splashOverlay.hide(_ctx);
#ifdef ENABLE_LOG_BUFFER
// Log overlay
if (
_ctx.buttons.released(ui::BTN_DEBUG) &&
!_ctx.buttons.longReleased(ui::BTN_DEBUG)
)
_logOverlay.toggle(_ctx);
#endif
// Screenshot overlay
if (_ctx.buttons.longPressed(ui::BTN_DEBUG)) {
if (_takeScreenshot())
_screenshotOverlay.animate(_ctx);
}
} }
/* App filesystem functions */
static const char *const _UI_SOUND_PATHS[ui::NUM_UI_SOUNDS]{ static const char *const _UI_SOUND_PATHS[ui::NUM_UI_SOUNDS]{
"assets/sounds/startup.vag", // ui::SOUND_STARTUP "assets/sounds/startup.vag", // ui::SOUND_STARTUP
"assets/sounds/about.vag", // ui::SOUND_ABOUT_SCREEN "assets/sounds/about.vag", // ui::SOUND_ABOUT_SCREEN
@ -275,7 +303,7 @@ void App::_loadResources(void) {
uint32_t spuOffset = spu::DUMMY_BLOCK_END; uint32_t spuOffset = spu::DUMMY_BLOCK_END;
for (int i = 0; i < ui::NUM_UI_SOUNDS; i++) for (int i = 0; i < ui::NUM_UI_SOUNDS; i++)
res.loadVAG(_ctx.sounds[i], spuOffset, _UI_SOUND_PATHS[i]); spuOffset += res.loadVAG(_ctx.sounds[i], spuOffset, _UI_SOUND_PATHS[i]);
} }
bool App::_createDataDirectory(void) { bool App::_createDataDirectory(void) {
@ -334,54 +362,74 @@ bool App::_takeScreenshot(void) {
return true; return true;
} }
void App::_updateOverlays(void) { /* App callbacks */
// Date and time overlay
static char dateString[24];
util::Date date;
io::getRTCTime(date); void _appInterruptHandler(void *arg0, void *arg1) {
date.toString(dateString); auto app = reinterpret_cast<App *>(arg0);
_textOverlay.leftText = dateString; if (acknowledgeInterrupt(IRQ_VSYNC)) {
app->_ctx.tick();
// Splash screen overlay
int timeout = _ctx.gpuCtx.refreshRate * _SPLASH_SCREEN_TIMEOUT;
__atomic_signal_fence(__ATOMIC_ACQUIRE); __atomic_signal_fence(__ATOMIC_ACQUIRE);
if ((_workerStatus.status != WORKER_BUSY) || (_ctx.time > timeout)) if (app->_workerStatus.status != WORKER_REBOOT)
_splashOverlay.hide(_ctx); io::clearWatchdog();
#ifdef ENABLE_LOG_BUFFER
// Log overlay
if ( if (
_ctx.buttons.released(ui::BTN_DEBUG) && gpu::isIdle() &&
!_ctx.buttons.longReleased(ui::BTN_DEBUG) (app->_workerStatus.status != WORKER_BUSY_SUSPEND)
) )
_logOverlay.toggle(_ctx); switchThread(nullptr);
#endif }
// Screenshot overlay if (acknowledgeInterrupt(IRQ_SPU))
if (_ctx.buttons.longPressed(ui::BTN_DEBUG)) { app->_audioStream.handleInterrupt();
if (_takeScreenshot())
_screenshotOverlay.animate(_ctx); if (acknowledgeInterrupt(IRQ_PIO)) {
for (auto dev : app->_fileIO.ideDevices) {
if (dev)
dev->handleInterrupt();
}
} }
} }
void _workerMain(void *arg0, void *arg1) {
auto app = reinterpret_cast<App *>(arg0);
auto func = reinterpret_cast<bool (*)(App &)>(arg1);
if (func)
func(*app);
app->_workerStatus.setStatus(WORKER_DONE);
// Do nothing while waiting for vblank once the task is done.
for (;;)
__asm__ volatile("");
}
void App::_setupInterrupts(void) {
setInterruptHandler(&_appInterruptHandler, this, nullptr);
IRQ_MASK = 0
| (1 << IRQ_VSYNC)
| (1 << IRQ_SPU)
| (1 << IRQ_PIO);
enableInterrupts();
}
void App::_runWorker( void App::_runWorker(
bool (App::*func)(void), ui::Screen &next, bool goBack, bool playSound bool (*func)(App &app), ui::Screen &next, bool goBack, bool playSound
) { ) {
{ {
util::CriticalSection sec; util::CriticalSection sec;
_workerStatus.reset(next, goBack); _workerStatus.reset(next, goBack);
_workerStack.allocate(_WORKER_STACK_SIZE); _workerStack.allocate(_WORKER_STACK_SIZE);
assert(_workerStack.ptr);
_workerFunction = func;
auto stackBottom = _workerStack.as<uint8_t>(); auto stackBottom = _workerStack.as<uint8_t>();
initThread( initThread(
&_workerThread, util::forcedCast<ArgFunction>(&App::_worker), this, &_workerThread, &_workerMain, this, reinterpret_cast<void *>(func),
&stackBottom[(_WORKER_STACK_SIZE - 1) & ~7] &stackBottom[(_WORKER_STACK_SIZE - 1) & ~7]
); );
} }
@ -389,39 +437,7 @@ void App::_runWorker(
_ctx.show(_workerStatusScreen, false, playSound); _ctx.show(_workerStatusScreen, false, playSound);
} }
void App::_worker(void) { /* App main loop */
if (_workerFunction)
(this->*_workerFunction)();
_workerStatus.setStatus(WORKER_DONE);
// Do nothing while waiting for vblank once the task is done.
for (;;)
__asm__ volatile("");
}
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 : _fileIO.ideDevices) {
if (dev)
dev->handleInterrupt();
}
}
}
[[noreturn]] void App::run(const void *resourcePtr, size_t resourceLength) { [[noreturn]] void App::run(const void *resourcePtr, size_t resourceLength) {
#ifdef ENABLE_LOG_BUFFER #ifdef ENABLE_LOG_BUFFER
@ -448,7 +464,8 @@ void App::_interruptHandler(void) {
#endif #endif
_ctx.overlays[2] = &_screenshotOverlay; _ctx.overlays[2] = &_screenshotOverlay;
_runWorker(&App::_startupWorker, _warningScreen); _audioStream.init(&_workerThread);
_runWorker(&startupWorker, _warningScreen);
_setupInterrupts(); _setupInterrupts();
_splashOverlay.show(_ctx); _splashOverlay.show(_ctx);
@ -459,7 +476,7 @@ void App::_interruptHandler(void) {
_updateOverlays(); _updateOverlays();
_ctx.draw(); _ctx.draw();
switchThreadImmediate(&_workerThread); _audioStream.yield();
_ctx.gpuCtx.flip(); _ctx.gpuCtx.flip();
} }
} }

View File

@ -30,6 +30,7 @@
#include "main/app/modals.hpp" #include "main/app/modals.hpp"
#include "main/app/romactions.hpp" #include "main/app/romactions.hpp"
#include "main/app/tests.hpp" #include "main/app/tests.hpp"
#include "main/app/threads.hpp"
#include "main/cart/cart.hpp" #include "main/cart/cart.hpp"
#include "main/cart/cartdata.hpp" #include "main/cart/cartdata.hpp"
#include "main/cart/cartio.hpp" #include "main/cart/cartio.hpp"
@ -101,47 +102,42 @@ public:
/* App class */ /* App class */
class App { class App {
private:
friend void _appInterruptHandler(void *arg0, void *arg1);
friend void _workerMain(void *arg0, void *arg1);
// cartworkers.cpp
friend bool cartDetectWorker(App &app);
friend bool cartUnlockWorker(App &app);
friend bool qrCodeWorker(App &app);
friend bool cartDumpWorker(App &app);
friend bool cartWriteWorker(App &app);
friend bool cartRestoreWorker(App &app);
friend bool cartReflashWorker(App &app);
friend bool cartEraseWorker(App &app);
// romworkers.cpp
friend bool romChecksumWorker(App &app);
friend bool romDumpWorker(App &app);
friend bool romRestoreWorker(App &app);
friend bool romEraseWorker(App &app);
friend bool flashExecutableWriteWorker(App &app);
friend bool flashHeaderWriteWorker(App &app);
// miscworkers.cpp
friend bool startupWorker(App &app);
friend bool fileInitWorker(App &app);
friend bool executableWorker(App &app);
friend bool atapiEjectWorker(App &app);
friend bool rebootWorker(App &app);
// modals.cpp
friend class WorkerStatusScreen; friend class WorkerStatusScreen;
friend class MessageScreen; friend class MessageScreen;
friend class ConfirmScreen; friend class ConfirmScreen;
friend class FilePickerScreen; friend class FilePickerScreen;
friend class FileBrowserScreen; friend class FileBrowserScreen;
friend class WarningScreen;
friend class AutobootScreen;
friend class ButtonMappingScreen;
friend class MainMenuScreen;
friend class CartInfoScreen;
friend class UnlockKeyScreen;
friend class KeyEntryScreen;
friend class CartActionsScreen;
friend class QRCodeScreen;
friend class HexdumpScreen;
friend class ReflashGameScreen;
friend class SystemIDEntryScreen;
friend class StorageInfoScreen;
friend class StorageActionsScreen;
friend class CardSizeScreen;
friend class ChecksumScreen;
friend class TestMenuScreen;
friend class JAMMATestScreen;
friend class AudioTestScreen;
friend class TestPatternScreen;
friend class ColorIntensityScreen;
friend class GeometryScreen;
friend class IDEInfoScreen;
friend class RTCTimeScreen;
friend class LanguageScreen;
friend class ResolutionScreen;
friend class AboutScreen;
private:
// modals.cpp
WorkerStatusScreen _workerStatusScreen; WorkerStatusScreen _workerStatusScreen;
MessageScreen _messageScreen; MessageScreen _messageScreen;
ConfirmScreen _confirmScreen; ConfirmScreen _confirmScreen;
@ -149,17 +145,32 @@ private:
FileBrowserScreen _fileBrowserScreen; FileBrowserScreen _fileBrowserScreen;
// main.cpp // main.cpp
friend class WarningScreen;
friend class AutobootScreen;
friend class ButtonMappingScreen;
friend class MainMenuScreen;
WarningScreen _warningScreen; WarningScreen _warningScreen;
AutobootScreen _autobootScreen; AutobootScreen _autobootScreen;
ButtonMappingScreen _buttonMappingScreen; ButtonMappingScreen _buttonMappingScreen;
MainMenuScreen _mainMenuScreen; MainMenuScreen _mainMenuScreen;
// cartunlock.cpp // cartunlock.cpp
friend class CartInfoScreen;
friend class UnlockKeyScreen;
friend class KeyEntryScreen;
CartInfoScreen _cartInfoScreen; CartInfoScreen _cartInfoScreen;
UnlockKeyScreen _unlockKeyScreen; UnlockKeyScreen _unlockKeyScreen;
KeyEntryScreen _keyEntryScreen; KeyEntryScreen _keyEntryScreen;
// cartactions.cpp // cartactions.cpp
friend class CartActionsScreen;
friend class QRCodeScreen;
friend class HexdumpScreen;
friend class ReflashGameScreen;
friend class SystemIDEntryScreen;
CartActionsScreen _cartActionsScreen; CartActionsScreen _cartActionsScreen;
QRCodeScreen _qrCodeScreen; QRCodeScreen _qrCodeScreen;
HexdumpScreen _hexdumpScreen; HexdumpScreen _hexdumpScreen;
@ -167,12 +178,24 @@ private:
SystemIDEntryScreen _systemIDEntryScreen; SystemIDEntryScreen _systemIDEntryScreen;
// romactions.cpp // romactions.cpp
friend class StorageInfoScreen;
friend class StorageActionsScreen;
friend class CardSizeScreen;
friend class ChecksumScreen;
StorageInfoScreen _storageInfoScreen; StorageInfoScreen _storageInfoScreen;
StorageActionsScreen _storageActionsScreen; StorageActionsScreen _storageActionsScreen;
CardSizeScreen _cardSizeScreen; CardSizeScreen _cardSizeScreen;
ChecksumScreen _checksumScreen; ChecksumScreen _checksumScreen;
// tests.cpp // tests.cpp
friend class TestMenuScreen;
friend class JAMMATestScreen;
friend class AudioTestScreen;
friend class TestPatternScreen;
friend class ColorIntensityScreen;
friend class GeometryScreen;
TestMenuScreen _testMenuScreen; TestMenuScreen _testMenuScreen;
JAMMATestScreen _jammaTestScreen; JAMMATestScreen _jammaTestScreen;
AudioTestScreen _audioTestScreen; AudioTestScreen _audioTestScreen;
@ -180,6 +203,12 @@ private:
GeometryScreen _geometryScreen; GeometryScreen _geometryScreen;
// misc.cpp // misc.cpp
friend class IDEInfoScreen;
friend class RTCTimeScreen;
friend class LanguageScreen;
friend class ResolutionScreen;
friend class AboutScreen;
IDEInfoScreen _ideInfoScreen; IDEInfoScreen _ideInfoScreen;
RTCTimeScreen _rtcTimeScreen; RTCTimeScreen _rtcTimeScreen;
LanguageScreen _languageScreen; LanguageScreen _languageScreen;
@ -198,63 +227,37 @@ private:
ui::Context &_ctx; ui::Context &_ctx;
fs::StringTable _stringTable; fs::StringTable _stringTable;
FileIOManager _fileIO; FileIOManager _fileIO;
AudioStreamManager _audioStream;
Thread _workerThread;
util::Data _workerStack;
WorkerStatus _workerStatus;
cart::CartDump _cartDump; cart::CartDump _cartDump;
cart::ROMHeaderDump _romHeaderDump; cart::ROMHeaderDump _romHeaderDump;
cart::CartDB _cartDB; cart::CartDB _cartDB;
cart::ROMHeaderDB _romHeaderDB; cart::ROMHeaderDB _romHeaderDB;
Thread _workerThread;
util::Data _workerStack;
WorkerStatus _workerStatus;
bool (App::*_workerFunction)(void);
cart::Driver *_cartDriver; cart::Driver *_cartDriver;
cart::CartParser *_cartParser; cart::CartParser *_cartParser;
const cart::CartDBEntry *_identified, *_selectedEntry; const cart::CartDBEntry *_identified, *_selectedEntry;
void _unloadCartData(void); void _unloadCartData(void);
void _setupInterrupts(void); void _updateOverlays(void);
void _loadResources(void); void _loadResources(void);
bool _createDataDirectory(void); bool _createDataDirectory(void);
bool _getNumberedPath( bool _getNumberedPath(
char *output, size_t length, const char *path, int maxIndex = 9999 char *output, size_t length, const char *path, int maxIndex = 9999
); );
bool _takeScreenshot(void); bool _takeScreenshot(void);
void _updateOverlays(void);
void _setupInterrupts(void);
void _runWorker( void _runWorker(
bool (App::*func)(void), ui::Screen &next, bool goBack = false, bool (*func)(App &app), ui::Screen &next, bool goBack = false,
bool playSound = false bool playSound = false
); );
void _worker(void);
void _interruptHandler(void);
// cartworkers.cpp
bool _cartDetectWorker(void);
bool _cartUnlockWorker(void);
bool _qrCodeWorker(void);
bool _cartDumpWorker(void);
bool _cartWriteWorker(void);
bool _cartRestoreWorker(void);
bool _cartReflashWorker(void);
bool _cartEraseWorker(void);
// romworkers.cpp
bool _romChecksumWorker(void);
bool _romDumpWorker(void);
bool _romRestoreWorker(void);
bool _romEraseWorker(void);
bool _flashExecutableWriteWorker(void);
bool _flashHeaderWriteWorker(void);
// miscworkers.cpp
bool _startupWorker(void);
bool _fileInitWorker(void);
bool _executableWorker(void);
bool _atapiEjectWorker(void);
bool _rebootWorker(void);
public: public:
App(ui::Context &ctx); App(ui::Context &ctx);
~App(void); ~App(void);
@ -266,5 +269,5 @@ public:
#define STR(id) (APP->_stringTable.get(id ## _h)) #define STR(id) (APP->_stringTable.get(id ## _h))
#define STRH(id) (APP->_stringTable.get(id)) #define STRH(id) (APP->_stringTable.get(id))
#define WSTR(id) (_stringTable.get(id ## _h)) #define WSTR(id) (app._stringTable.get(id ## _h))
#define WSTRH(id) (_stringTable.get(id)) #define WSTRH(id) (app._stringTable.get(id))

View File

@ -20,6 +20,7 @@
#include "common/util/templates.hpp" #include "common/util/templates.hpp"
#include "main/app/cartactions.hpp" #include "main/app/cartactions.hpp"
#include "main/app/app.hpp" #include "main/app/app.hpp"
#include "main/workers/cartworkers.hpp"
#include "main/uibase.hpp" #include "main/uibase.hpp"
/* Unlocked cartridge screens */ /* Unlocked cartridge screens */
@ -80,7 +81,7 @@ void CartActionsScreen::qrDump(ui::Context &ctx) {
if (APP->_qrCodeScreen.valid) if (APP->_qrCodeScreen.valid)
ctx.show(APP->_qrCodeScreen, false, true); ctx.show(APP->_qrCodeScreen, false, true);
else else
APP->_runWorker(&App::_qrCodeWorker, APP->_qrCodeScreen, false, true); APP->_runWorker(&qrCodeWorker, APP->_qrCodeScreen, false, true);
} }
void CartActionsScreen::hddDump(ui::Context &ctx) { void CartActionsScreen::hddDump(ui::Context &ctx) {
@ -88,7 +89,7 @@ void CartActionsScreen::hddDump(ui::Context &ctx) {
&(APP->_cartInfoScreen); &(APP->_cartInfoScreen);
APP->_messageScreen.previousScreens[MESSAGE_ERROR] = this; APP->_messageScreen.previousScreens[MESSAGE_ERROR] = this;
APP->_runWorker(&App::_cartDumpWorker, APP->_messageScreen, false, true); APP->_runWorker(&cartDumpWorker, APP->_messageScreen, false, true);
} }
void CartActionsScreen::hexdump(ui::Context &ctx) { void CartActionsScreen::hexdump(ui::Context &ctx) {
@ -111,7 +112,7 @@ void CartActionsScreen::hddRestore(ui::Context &ctx) {
&(APP->_fileBrowserScreen); &(APP->_fileBrowserScreen);
APP->_runWorker( APP->_runWorker(
&App::_cartRestoreWorker, APP->_cartInfoScreen, true, true cartRestoreWorker, APP->_cartInfoScreen, true, true
); );
}, },
STR("CartActionsScreen.hddRestore.confirm") STR("CartActionsScreen.hddRestore.confirm")
@ -131,9 +132,7 @@ void CartActionsScreen::erase(ui::Context &ctx) {
APP->_messageScreen.previousScreens[MESSAGE_ERROR] = APP->_messageScreen.previousScreens[MESSAGE_ERROR] =
&(APP->_cartActionsScreen); &(APP->_cartActionsScreen);
APP->_runWorker( APP->_runWorker(&cartEraseWorker, APP->_cartInfoScreen, true, true);
&App::_cartEraseWorker, APP->_cartInfoScreen, true, true
);
}, },
STR("CartActionsScreen.erase.confirm") STR("CartActionsScreen.erase.confirm")
); );
@ -153,7 +152,7 @@ void CartActionsScreen::resetSystemID(ui::Context &ctx) {
&(APP->_cartActionsScreen); &(APP->_cartActionsScreen);
APP->_runWorker( APP->_runWorker(
&App::_cartWriteWorker, APP->_cartInfoScreen, true, true &cartWriteWorker, APP->_cartInfoScreen, true, true
); );
}, },
STR("CartActionsScreen.resetSystemID.confirm") STR("CartActionsScreen.resetSystemID.confirm")
@ -184,7 +183,7 @@ void CartActionsScreen::matchSystemID(ui::Context &ctx) {
&(APP->_cartActionsScreen); &(APP->_cartActionsScreen);
APP->_runWorker( APP->_runWorker(
&App::_cartWriteWorker, APP->_cartInfoScreen, true, true &cartWriteWorker, APP->_cartInfoScreen, true, true
); );
}, },
STR("CartActionsScreen.matchSystemID.confirm") STR("CartActionsScreen.matchSystemID.confirm")
@ -314,8 +313,7 @@ void ReflashGameScreen::update(ui::Context &ctx) {
&(APP->_reflashGameScreen); &(APP->_reflashGameScreen);
APP->_runWorker( APP->_runWorker(
&App::_cartReflashWorker, APP->_cartInfoScreen, true, &cartReflashWorker, APP->_cartInfoScreen, true, true
true
); );
}, },
STR("CartActionsScreen.reflash.confirm") STR("CartActionsScreen.reflash.confirm")
@ -359,8 +357,7 @@ void SystemIDEntryScreen::update(ui::Context &ctx) {
&(APP->_systemIDEntryScreen); &(APP->_systemIDEntryScreen);
APP->_runWorker( APP->_runWorker(
&App::_cartWriteWorker, APP->_cartInfoScreen, true, &cartWriteWorker, APP->_cartInfoScreen, true, true
true
); );
}, },
STR("CartActionsScreen.editSystemID.confirm") STR("CartActionsScreen.editSystemID.confirm")

View File

@ -22,6 +22,7 @@
#include "main/app/app.hpp" #include "main/app/app.hpp"
#include "main/app/cartunlock.hpp" #include "main/app/cartunlock.hpp"
#include "main/cart/cartdata.hpp" #include "main/cart/cartdata.hpp"
#include "main/workers/cartworkers.hpp"
#include "main/uibase.hpp" #include "main/uibase.hpp"
/* Pre-unlock cartridge screens */ /* Pre-unlock cartridge screens */
@ -331,8 +332,7 @@ void UnlockKeyScreen::update(ui::Context &ctx) {
&(APP->_unlockKeyScreen); &(APP->_unlockKeyScreen);
APP->_runWorker( APP->_runWorker(
&App::_cartUnlockWorker, APP->_cartInfoScreen, false, &cartUnlockWorker, APP->_cartInfoScreen, false, true
true
); );
}, },
STRH(_UNLOCK_WARNINGS[dump.chipType]) STRH(_UNLOCK_WARNINGS[dump.chipType])
@ -381,8 +381,7 @@ void KeyEntryScreen::update(ui::Context &ctx) {
&(APP->_keyEntryScreen); &(APP->_keyEntryScreen);
APP->_runWorker( APP->_runWorker(
&App::_cartUnlockWorker, APP->_cartInfoScreen, false, &cartUnlockWorker, APP->_cartInfoScreen, false, true
true
); );
}, },
STRH(_UNLOCK_WARNINGS[dump.chipType]) STRH(_UNLOCK_WARNINGS[dump.chipType])

View File

@ -1,436 +0,0 @@
/*
* 573in1 - Copyright (C) 2022-2024 spicyjpeg
*
* 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.
*
* 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
* 573in1. If not, see <https://www.gnu.org/licenses/>.
*/
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include "common/fs/file.hpp"
#include "common/util/hash.hpp"
#include "common/util/log.hpp"
#include "common/util/templates.hpp"
#include "common/defs.hpp"
#include "common/ioboard.hpp"
#include "main/app/app.hpp"
#include "main/cart/cart.hpp"
#include "main/cart/cartdata.hpp"
#include "main/cart/cartio.hpp"
static const char *const _CARTDB_PATHS[cart::NUM_CHIP_TYPES]{
nullptr,
"data/x76f041.db",
"data/x76f100.db",
"data/zs01.db"
};
bool App::_cartDetectWorker(void) {
_workerStatus.setNextScreen(_cartInfoScreen);
_workerStatus.update(0, 3, WSTR("App.cartDetectWorker.readCart"));
_unloadCartData();
_qrCodeScreen.valid = false;
#ifdef ENABLE_DUMMY_CART_DRIVER
if (!cart::dummyDriverDump.chipType)
_fileIO.resource.loadStruct(cart::dummyDriverDump, "data/test.573");
if (cart::dummyDriverDump.chipType) {
LOG_APP("using dummy cart driver");
_cartDriver = new cart::DummyDriver(_cartDump);
_cartDriver->readSystemID();
} else {
_cartDriver = cart::newCartDriver(_cartDump);
}
#else
_cartDriver = cart::newCartDriver(_cartDump);
#endif
if (_cartDump.chipType) {
auto error = _cartDriver->readCartID();
if (error)
LOG_APP("SID error [%s]", cart::getErrorString(error));
error = _cartDriver->readPublicData();
if (error)
LOG_APP("read error [%s]", cart::getErrorString(error));
else if (!_cartDump.isReadableDataEmpty())
_cartParser = cart::newCartParser(_cartDump);
_workerStatus.update(1, 3, WSTR("App.cartDetectWorker.identifyGame"));
if (!_cartDB.ptr) {
if (!_fileIO.resource.loadData(
_cartDB, _CARTDB_PATHS[_cartDump.chipType])
) {
LOG_APP("%s not found", _CARTDB_PATHS[_cartDump.chipType]);
goto _cartInitDone;
}
}
char code[8], region[8];
if (!_cartParser)
goto _cartInitDone;
if (_cartParser->getCode(code) && _cartParser->getRegion(region))
_identified = _cartDB.lookup(code, region);
if (!_identified)
goto _cartInitDone;
// Force the parser to use correct format for the game (to prevent
// ambiguity between different formats).
delete _cartParser;
_cartParser = cart::newCartParser(
_cartDump, _identified->formatType, _identified->flags
);
}
_cartInitDone:
_workerStatus.update(2, 3, WSTR("App.cartDetectWorker.readDigitalIO"));
if (
#ifdef ENABLE_DUMMY_CART_DRIVER
!(_cartDump.flags & cart::DUMP_SYSTEM_ID_OK) &&
#endif
io::isDigitalIOPresent()
) {
util::Data bitstream;
if (!_fileIO.resource.loadData(bitstream, "data/fpga.bit"))
return true;
bool ready = io::loadDigitalIOBitstream(
bitstream.as<uint8_t>(), bitstream.length
);
bitstream.destroy();
if (!ready)
return true;
io::initDigitalIOFPGA();
auto error = _cartDriver->readSystemID();
if (error)
LOG_APP("XID error [%s]", cart::getErrorString(error));
}
return true;
}
static const util::Hash _UNLOCK_ERRORS[cart::NUM_CHIP_TYPES]{
0,
"App.cartUnlockWorker.x76f041Error"_h,
"App.cartUnlockWorker.x76f100Error"_h,
"App.cartUnlockWorker.zs01Error"_h
};
bool App::_cartUnlockWorker(void) {
_workerStatus.setNextScreen(_cartInfoScreen, true);
_workerStatus.update(0, 2, WSTR("App.cartUnlockWorker.read"));
_qrCodeScreen.valid = false;
auto error = _cartDriver->readPrivateData();
if (error) {
_messageScreen.setMessage(
MESSAGE_ERROR, WSTRH(_UNLOCK_ERRORS[_cartDump.chipType]),
cart::getErrorString(error)
);
_workerStatus.setNextScreen(_messageScreen);
return false;
}
if (_cartParser)
delete _cartParser;
_cartParser = cart::newCartParser(_cartDump);
if (!_cartParser)
return true;
_workerStatus.update(1, 2, WSTR("App.cartUnlockWorker.identifyGame"));
char code[8], region[8];
if (_cartParser->getCode(code) && _cartParser->getRegion(region))
_identified = _cartDB.lookup(code, region);
// If auto-identification failed (e.g. because the format has no game code),
// use the game whose unlocking key was selected as a hint.
if (!_identified) {
if (_selectedEntry) {
LOG_APP("identify failed, using key as hint");
_identified = _selectedEntry;
} else {
return true;
}
}
delete _cartParser;
_cartParser = cart::newCartParser(
_cartDump, _identified->formatType, _identified->flags
);
return true;
}
bool App::_qrCodeWorker(void) {
char qrString[cart::MAX_QR_STRING_LENGTH];
_workerStatus.update(0, 2, WSTR("App.qrCodeWorker.compress"));
_cartDump.toQRString(qrString);
_workerStatus.update(1, 2, WSTR("App.qrCodeWorker.generate"));
_qrCodeScreen.generateCode(qrString);
return true;
}
bool App::_cartDumpWorker(void) {
_workerStatus.update(0, 1, WSTR("App.cartDumpWorker.save"));
char path[fs::MAX_PATH_LENGTH], code[8], region[8];
size_t length = _cartDump.getDumpLength();
if (!_createDataDirectory())
goto _error;
if (
_identified && _cartParser->getCode(code) &&
_cartParser->getRegion(region)
) {
snprintf(
path, sizeof(path), EXTERNAL_DATA_DIR "/%s%s.573", code, region
);
} else {
if (!_getNumberedPath(
path, sizeof(path), EXTERNAL_DATA_DIR "/cart%04d.573"
))
goto _error;
}
LOG_APP("saving %s, length=%d", path, length);
if (_fileIO.vfs.saveData(&_cartDump, length, path) != length)
goto _error;
_messageScreen.setMessage(
MESSAGE_SUCCESS, WSTR("App.cartDumpWorker.success"), path
);
return true;
_error:
_messageScreen.setMessage(
MESSAGE_ERROR, WSTR("App.cartDumpWorker.error"), path
);
return false;
}
bool App::_cartWriteWorker(void) {
_workerStatus.update(0, 1, WSTR("App.cartWriteWorker.write"));
uint8_t key[8];
auto error = _cartDriver->writeData();
if (!error)
_identified->copyKeyTo(key);
_cartDetectWorker();
if (error) {
_messageScreen.setMessage(
MESSAGE_ERROR, WSTR("App.cartWriteWorker.error"),
cart::getErrorString(error)
);
_workerStatus.setNextScreen(_messageScreen);
return false;
}
_cartDump.copyKeyFrom(key);
return _cartUnlockWorker();
}
bool App::_cartRestoreWorker(void) {
_workerStatus.update(0, 3, WSTR("App.cartRestoreWorker.init"));
const char *path = _fileBrowserScreen.selectedPath;
auto file = _fileIO.vfs.openFile(path, fs::READ);
cart::CartDump newDump;
if (file) {
auto length = file->read(&newDump, sizeof(newDump));
file->close();
delete file;
if (length < (sizeof(newDump) - sizeof(newDump.data)))
goto _fileError;
if (!newDump.validateMagic())
goto _fileError;
if (length != newDump.getDumpLength())
goto _fileError;
}
if (_cartDump.chipType != newDump.chipType) {
_messageScreen.setMessage(
MESSAGE_ERROR, WSTR("App.cartRestoreWorker.typeError"), path
);
_workerStatus.setNextScreen(_messageScreen);
return false;
}
{
_workerStatus.update(1, 3, WSTR("App.cartRestoreWorker.setDataKey"));
auto error = _cartDriver->setDataKey(newDump.dataKey);
if (error) {
LOG_APP("key error [%s]", cart::getErrorString(error));
} else {
if (newDump.flags & (
cart::DUMP_PUBLIC_DATA_OK | cart::DUMP_PRIVATE_DATA_OK
))
_cartDump.copyDataFrom(newDump.data);
if (newDump.flags & cart::DUMP_CONFIG_OK)
_cartDump.copyConfigFrom(newDump.config);
_workerStatus.update(2, 3, WSTR("App.cartRestoreWorker.write"));
error = _cartDriver->writeData();
}
_cartDetectWorker();
if (error) {
_messageScreen.setMessage(
MESSAGE_ERROR, WSTR("App.cartRestoreWorker.writeError"),
cart::getErrorString(error)
);
_workerStatus.setNextScreen(_messageScreen);
return false;
}
}
return _cartUnlockWorker();
_fileError:
_messageScreen.setMessage(
MESSAGE_ERROR, WSTR("App.cartRestoreWorker.fileError"), path
);
_workerStatus.setNextScreen(_messageScreen);
return false;
}
bool App::_cartReflashWorker(void) {
// Make sure a valid cart ID is present if required by the new data.
if (
_selectedEntry->requiresCartID() &&
!(_cartDump.flags & cart::DUMP_CART_ID_OK)
) {
_messageScreen.setMessage(
MESSAGE_ERROR, WSTR("App.cartReflashWorker.idError")
);
_workerStatus.setNextScreen(_messageScreen);
return false;
}
_workerStatus.update(0, 3, WSTR("App.cartReflashWorker.init"));
// TODO: preserve 0x81 traceid if possible
#if 0
uint8_t traceID[8];
_cartParser->getIdentifiers()->traceID.copyTo(traceID);
#endif
if (!_cartEraseWorker())
return false;
if (_cartParser)
delete _cartParser;
_cartParser = cart::newCartParser(
_cartDump, _selectedEntry->formatType, _selectedEntry->flags
);
auto pri = _cartParser->getIdentifiers();
auto pub = _cartParser->getPublicIdentifiers();
util::clear(_cartDump.data);
_cartDump.initConfig(
9, _selectedEntry->flags & cart::DATA_HAS_PUBLIC_SECTION
);
if (pri) {
if (_selectedEntry->flags & cart::DATA_HAS_CART_ID)
pri->cartID.copyFrom(_cartDump.cartID.data);
if (_selectedEntry->flags & cart::DATA_HAS_TRACE_ID)
pri->updateTraceID(
_selectedEntry->traceIDType, _selectedEntry->traceIDParam,
&_cartDump.cartID
);
if (_selectedEntry->flags & cart::DATA_HAS_INSTALL_ID) {
// The private installation ID seems to be unused on carts with a
// public data section.
if (pub)
pub->setInstallID(_selectedEntry->installIDPrefix);
else
pri->setInstallID(_selectedEntry->installIDPrefix);
}
}
_cartParser->setCode(_selectedEntry->code);
_cartParser->setRegion(_selectedEntry->region);
_cartParser->setYear(_selectedEntry->year);
_cartParser->flush();
_workerStatus.update(1, 3, WSTR("App.cartReflashWorker.setDataKey"));
auto error = _cartDriver->setDataKey(_selectedEntry->dataKey);
if (error) {
LOG_APP("key error [%s]", cart::getErrorString(error));
} else {
_workerStatus.update(2, 3, WSTR("App.cartReflashWorker.write"));
error = _cartDriver->writeData();
}
_cartDetectWorker();
if (error) {
_messageScreen.setMessage(
MESSAGE_ERROR, WSTR("App.cartReflashWorker.writeError"),
cart::getErrorString(error)
);
_workerStatus.setNextScreen(_messageScreen);
return false;
}
return _cartUnlockWorker();
}
bool App::_cartEraseWorker(void) {
_workerStatus.update(0, 1, WSTR("App.cartEraseWorker.erase"));
auto error = _cartDriver->erase();
_cartDetectWorker();
if (error) {
_messageScreen.setMessage(
MESSAGE_ERROR, WSTR("App.cartEraseWorker.error"),
cart::getErrorString(error)
);
_workerStatus.setNextScreen(_messageScreen);
return false;
}
return _cartUnlockWorker();
}

View File

@ -19,6 +19,8 @@
#include "common/util/templates.hpp" #include "common/util/templates.hpp"
#include "main/app/app.hpp" #include "main/app/app.hpp"
#include "main/app/main.hpp" #include "main/app/main.hpp"
#include "main/workers/cartworkers.hpp"
#include "main/workers/miscworkers.hpp"
#include "main/uibase.hpp" #include "main/uibase.hpp"
/* Main menu screens */ /* Main menu screens */
@ -63,13 +65,7 @@ void WarningScreen::update(ui::Context &ctx) {
_buttons[0] = STR("WarningScreen.ok"); _buttons[0] = STR("WarningScreen.ok");
if (ctx.buttons.pressed(ui::BTN_START)) if (ctx.buttons.pressed(ui::BTN_START))
#ifdef ENABLE_AUTOBOOT
ctx.show(APP->_buttonMappingScreen, false, true); ctx.show(APP->_buttonMappingScreen, false, true);
#else
APP->_runWorker(
&App::_ideInitWorker, APP->_buttonMappingScreen, false, true
);
#endif
} }
void AutobootScreen::show(ui::Context &ctx, bool goBack) { void AutobootScreen::show(ui::Context &ctx, bool goBack) {
@ -102,7 +98,7 @@ void AutobootScreen::update(ui::Context &ctx) {
APP->_messageScreen.previousScreens[MESSAGE_ERROR] = APP->_messageScreen.previousScreens[MESSAGE_ERROR] =
&(APP->_warningScreen); &(APP->_warningScreen);
APP->_runWorker(&App::_executableWorker, APP->_mainMenuScreen, true); APP->_runWorker(&executableWorker, APP->_mainMenuScreen, true);
return; return;
} }
@ -214,9 +210,7 @@ void MainMenuScreen::cartInfo(ui::Context &ctx) {
if (APP->_cartDriver) if (APP->_cartDriver)
ctx.show(APP->_cartInfoScreen, false, true); ctx.show(APP->_cartInfoScreen, false, true);
else else
APP->_runWorker( APP->_runWorker(&cartDetectWorker, APP->_cartInfoScreen, false, true);
&App::_cartDetectWorker, APP->_cartInfoScreen, false, true
);
} }
void MainMenuScreen::storageInfo(ui::Context &ctx) { void MainMenuScreen::storageInfo(ui::Context &ctx) {
@ -236,7 +230,7 @@ void MainMenuScreen::runExecutable(ui::Context &ctx) {
&(APP->_fileBrowserScreen); &(APP->_fileBrowserScreen);
APP->_runWorker( APP->_runWorker(
&App::_executableWorker, APP->_mainMenuScreen, true, true &executableWorker, APP->_mainMenuScreen, true, true
); );
}, },
STR("MainMenuScreen.runExecutable.filePrompt") STR("MainMenuScreen.runExecutable.filePrompt")
@ -269,11 +263,11 @@ void MainMenuScreen::ejectCD(ui::Context &ctx) {
APP->_messageScreen.previousScreens[MESSAGE_SUCCESS] = this; APP->_messageScreen.previousScreens[MESSAGE_SUCCESS] = this;
APP->_messageScreen.previousScreens[MESSAGE_ERROR] = this; APP->_messageScreen.previousScreens[MESSAGE_ERROR] = this;
APP->_runWorker(&App::_atapiEjectWorker, *this, true, true); APP->_runWorker(&atapiEjectWorker, *this, true, true);
} }
void MainMenuScreen::reboot(ui::Context &ctx) { void MainMenuScreen::reboot(ui::Context &ctx) {
APP->_runWorker(&App::_rebootWorker, *this, true, true); APP->_runWorker(&rebootWorker, *this, true, true);
} }
void MainMenuScreen::show(ui::Context &ctx, bool goBack) { void MainMenuScreen::show(ui::Context &ctx, bool goBack) {

View File

@ -25,6 +25,7 @@
#include "common/defs.hpp" #include "common/defs.hpp"
#include "main/app/app.hpp" #include "main/app/app.hpp"
#include "main/app/modals.hpp" #include "main/app/modals.hpp"
#include "main/workers/miscworkers.hpp"
#include "main/uibase.hpp" #include "main/uibase.hpp"
#include "main/uicommon.hpp" #include "main/uicommon.hpp"
@ -206,7 +207,7 @@ void FilePickerScreen::reloadAndShow(ui::Context &ctx) {
APP->_messageScreen.previousScreens[MESSAGE_ERROR] = this; APP->_messageScreen.previousScreens[MESSAGE_ERROR] = this;
APP->_runWorker(&App::_fileInitWorker, *this, false, true); APP->_runWorker(&fileInitWorker, *this, false, true);
return; return;
} }

View File

@ -20,6 +20,8 @@
#include "common/rom.hpp" #include "common/rom.hpp"
#include "main/app/app.hpp" #include "main/app/app.hpp"
#include "main/app/romactions.hpp" #include "main/app/romactions.hpp"
#include "main/workers/miscworkers.hpp"
#include "main/workers/romworkers.hpp"
#include "main/uibase.hpp" #include "main/uibase.hpp"
#include "main/uicommon.hpp" #include "main/uicommon.hpp"
@ -266,7 +268,7 @@ const char *StorageActionsScreen::_getItemName(
void StorageActionsScreen::runExecutable(ui::Context &ctx, size_t length) { void StorageActionsScreen::runExecutable(ui::Context &ctx, size_t length) {
if (selectedRegion->getBootExecutableHeader()) { if (selectedRegion->getBootExecutableHeader()) {
APP->_runWorker(&App::_executableWorker, *this, true, true); APP->_runWorker(&executableWorker, *this, true, true);
} else { } else {
APP->_messageScreen.previousScreens[MESSAGE_ERROR] = this; APP->_messageScreen.previousScreens[MESSAGE_ERROR] = this;
APP->_messageScreen.setMessage( APP->_messageScreen.setMessage(
@ -282,7 +284,7 @@ void StorageActionsScreen::checksum(ui::Context &ctx, size_t length) {
ctx.show(APP->_checksumScreen, false, true); ctx.show(APP->_checksumScreen, false, true);
else else
APP->_runWorker( APP->_runWorker(
&App::_romChecksumWorker, APP->_checksumScreen, false, true &romChecksumWorker, APP->_checksumScreen, false, true
); );
} }
@ -296,7 +298,7 @@ void StorageActionsScreen::dump(ui::Context &ctx, size_t length) {
&(APP->_storageActionsScreen); &(APP->_storageActionsScreen);
APP->_runWorker( APP->_runWorker(
&App::_romDumpWorker, APP->_messageScreen, false, true &romDumpWorker, APP->_messageScreen, false, true
); );
}, },
STR("StorageActionsScreen.dump.confirm") STR("StorageActionsScreen.dump.confirm")
@ -325,7 +327,7 @@ void StorageActionsScreen::restore(ui::Context &ctx, size_t length) {
&(APP->_fileBrowserScreen); &(APP->_fileBrowserScreen);
APP->_runWorker( APP->_runWorker(
&App::_romRestoreWorker, APP->_messageScreen, false, true &romRestoreWorker, APP->_messageScreen, false, true
); );
}, },
STR("StorageActionsScreen.restore.confirm") STR("StorageActionsScreen.restore.confirm")
@ -345,9 +347,7 @@ void StorageActionsScreen::erase(ui::Context &ctx, size_t length) {
APP->_messageScreen.previousScreens[MESSAGE_ERROR] = APP->_messageScreen.previousScreens[MESSAGE_ERROR] =
&(APP->_storageActionsScreen); &(APP->_storageActionsScreen);
APP->_runWorker( APP->_runWorker(&romEraseWorker, APP->_messageScreen, false, true);
&App::_romEraseWorker, APP->_messageScreen, false, true
);
}, },
STR("StorageActionsScreen.erase.confirm") STR("StorageActionsScreen.erase.confirm")
); );
@ -375,8 +375,7 @@ void StorageActionsScreen::installExecutable(ui::Context &ctx, size_t length) {
&(APP->_fileBrowserScreen); &(APP->_fileBrowserScreen);
APP->_runWorker( APP->_runWorker(
&App::_flashExecutableWriteWorker, APP->_messageScreen, false, &flashExecutableWriteWorker, APP->_messageScreen, false, true
true
); );
}, },
STR("StorageActionsScreen.installExecutable.confirm") STR("StorageActionsScreen.installExecutable.confirm")
@ -395,8 +394,7 @@ void StorageActionsScreen::resetFlashHeader(ui::Context &ctx, size_t length) {
&(APP->_storageActionsScreen); &(APP->_storageActionsScreen);
APP->_runWorker( APP->_runWorker(
&App::_flashHeaderWriteWorker, APP->_storageInfoScreen, true, &flashHeaderWriteWorker, APP->_storageInfoScreen, true, true
true
); );
}, },
STR("StorageActionsScreen.resetFlashHeader.confirm") STR("StorageActionsScreen.resetFlashHeader.confirm")

152
src/main/app/threads.cpp Normal file
View File

@ -0,0 +1,152 @@
/*
* 573in1 - Copyright (C) 2022-2024 spicyjpeg
*
* 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.
*
* 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
* 573in1. If not, see <https://www.gnu.org/licenses/>.
*/
#include <stddef.h>
#include "common/fs/file.hpp"
#include "common/util/log.hpp"
#include "common/util/misc.hpp"
#include "common/spu.hpp"
#include "ps1/system.h"
#include "main/app/threads.hpp"
/* Audio stream thread */
static constexpr size_t _STREAM_THREAD_STACK_SIZE = 0x2000;
static constexpr size_t _STREAM_BUFFERED_CHUNKS = 8;
static constexpr size_t _STREAM_MIN_FEED_CHUNKS = 4;
void _streamMain(void *arg0, void *arg1) {
auto obj = reinterpret_cast<AudioStreamManager *>(arg0);
if (obj->_status != AUDIO_STREAM_IDLE) {
auto &stream = obj->_stream;
auto &buffer = obj->_buffer;
auto chunkLength = stream.getChunkLength();
for (;;) {
__atomic_signal_fence(__ATOMIC_ACQUIRE);
if (obj->_status == AUDIO_STREAM_STOP) {
stream.stop();
break;
}
// Keep yielding to the worker thread until the stream's FIFO has
// enough space for the new chunks.
auto numChunks = stream.getFreeChunkCount();
if (numChunks < _STREAM_MIN_FEED_CHUNKS) {
switchThreadImmediate(obj->_yieldTo);
continue;
}
auto length = obj->_file->read(buffer.ptr, chunkLength * numChunks);
if (length >= chunkLength) {
stream.feed(buffer.ptr, length);
if (!stream.isPlaying())
stream.start();
} else if (obj->_status == AUDIO_STREAM_LOOP) {
obj->_file->seek(spu::INTERLEAVED_VAG_BODY_OFFSET);
} else {
// Wait for any leftover data in the FIFO to finish playing,
// then stop playback.
while (!stream.isUnderrun())
switchThreadImmediate(obj->_yieldTo);
break;
}
}
}
// Do nothing and yield until the stream is restarted.
obj->_status = AUDIO_STREAM_IDLE;
flushWriteQueue();
for (;;)
switchThreadImmediate(obj->_yieldTo);
}
void AudioStreamManager::_closeFile(void) {
if (!_file)
return;
_file->close();
delete _file;
_buffer.destroy();
_file = nullptr;
}
void AudioStreamManager::_startThread(
fs::File *file, AudioStreamStatus status
) {
util::CriticalSection sec;
_file = file;
_status = status;
_stack.allocate(_STREAM_THREAD_STACK_SIZE);
assert(_stack.ptr);
auto stackBottom = _stack.as<uint8_t>();
initThread(
&_thread, &_streamMain, this, nullptr,
&stackBottom[(_STREAM_THREAD_STACK_SIZE - 1) & ~7]
);
}
bool AudioStreamManager::play(fs::File *file, bool loop) {
if (isActive())
stop();
spu::VAGHeader header;
if (file->read(&header, sizeof(header)) < sizeof(header))
return false;
if (
file->seek(spu::INTERLEAVED_VAG_BODY_OFFSET)
!= spu::INTERLEAVED_VAG_BODY_OFFSET
)
return false;
auto bufferLength = _stream.getChunkLength() * _STREAM_MIN_FEED_CHUNKS;
if (!_stream.initFromVAGHeader(header, 0x60000, _STREAM_BUFFERED_CHUNKS))
return false;
if (!_buffer.allocate(bufferLength))
return false;
_startThread(file, loop ? AUDIO_STREAM_LOOP : AUDIO_STREAM_PLAY_ONCE);
return true;
}
void AudioStreamManager::stop(void) {
if (!isActive())
return;
_status = AUDIO_STREAM_STOP;
flushWriteQueue();
while (isActive())
yield();
_closeFile();
}

74
src/main/app/threads.hpp Normal file
View File

@ -0,0 +1,74 @@
/*
* 573in1 - Copyright (C) 2022-2024 spicyjpeg
*
* 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.
*
* 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
* 573in1. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include "common/fs/file.hpp"
#include "common/util/templates.hpp"
#include "common/spu.hpp"
#include "ps1/system.h"
/* Audio stream thread */
enum AudioStreamStatus {
AUDIO_STREAM_IDLE = 0,
AUDIO_STREAM_STOP = 1,
AUDIO_STREAM_PLAY_ONCE = 2,
AUDIO_STREAM_LOOP = 3
};
class AudioStreamManager {
friend void _streamMain(void *arg0, void *arg1);
private:
AudioStreamStatus _status;
fs::File *_file;
Thread *_yieldTo;
Thread _thread;
util::Data _stack;
spu::Stream _stream;
util::Data _buffer;
void _closeFile(void);
void _startThread(fs::File *file, AudioStreamStatus status);
public:
inline AudioStreamManager(void)
: _status(AUDIO_STREAM_IDLE), _file(nullptr), _yieldTo(nullptr) {}
inline void init(Thread *yieldTo) {
_yieldTo = yieldTo;
_startThread(nullptr, AUDIO_STREAM_IDLE);
}
inline bool isActive(void) const {
__atomic_signal_fence(__ATOMIC_ACQUIRE);
return (_status != AUDIO_STREAM_IDLE);
}
inline void yield(void) {
switchThreadImmediate(&_thread);
}
inline void handleInterrupt(void) {
_stream.handleInterrupt();
}
bool play(fs::File *file, bool loop = false);
void stop(void);
};

View File

@ -184,7 +184,6 @@ 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

View File

@ -0,0 +1,438 @@
/*
* 573in1 - Copyright (C) 2022-2024 spicyjpeg
*
* 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.
*
* 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
* 573in1. If not, see <https://www.gnu.org/licenses/>.
*/
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include "common/fs/file.hpp"
#include "common/util/hash.hpp"
#include "common/util/log.hpp"
#include "common/util/templates.hpp"
#include "common/defs.hpp"
#include "common/ioboard.hpp"
#include "main/app/app.hpp"
#include "main/cart/cart.hpp"
#include "main/cart/cartdata.hpp"
#include "main/cart/cartio.hpp"
#include "main/workers/cartworkers.hpp"
static const char *const _CARTDB_PATHS[cart::NUM_CHIP_TYPES]{
nullptr,
"data/x76f041.db",
"data/x76f100.db",
"data/zs01.db"
};
bool cartDetectWorker(App &app) {
app._workerStatus.setNextScreen(app._cartInfoScreen);
app._workerStatus.update(0, 3, WSTR("App.cartDetectWorker.readCart"));
app._unloadCartData();
app._qrCodeScreen.valid = false;
#ifdef ENABLE_DUMMY_CART_DRIVER
if (!cart::dummyDriverDump.chipType)
app._fileIO.resource.loadStruct(cart::dummyDriverDump, "data/test.573");
if (cart::dummyDriverDump.chipType) {
LOG_APP("using dummy cart driver");
app._cartDriver = new cart::DummyDriver(app._cartDump);
app._cartDriver->readSystemID();
} else {
app._cartDriver = cart::newCartDriver(app._cartDump);
}
#else
_cartDriver = cart::newCartDriver(_cartDump);
#endif
if (app._cartDump.chipType) {
auto error = app._cartDriver->readCartID();
if (error)
LOG_APP("SID error [%s]", cart::getErrorString(error));
error = app._cartDriver->readPublicData();
if (error)
LOG_APP("read error [%s]", cart::getErrorString(error));
else if (!app._cartDump.isReadableDataEmpty())
app._cartParser = cart::newCartParser(app._cartDump);
app._workerStatus.update(1, 3, WSTR("App.cartDetectWorker.identifyGame"));
if (!app._cartDB.ptr) {
if (!app._fileIO.resource.loadData(
app._cartDB, _CARTDB_PATHS[app._cartDump.chipType])
) {
LOG_APP("%s not found", _CARTDB_PATHS[app._cartDump.chipType]);
goto _cartInitDone;
}
}
char code[8], region[8];
if (!app._cartParser)
goto _cartInitDone;
if (app._cartParser->getCode(code) && app._cartParser->getRegion(region))
app._identified = app._cartDB.lookup(code, region);
if (!app._identified)
goto _cartInitDone;
// Force the parser to use correct format for the game (to prevent
// ambiguity between different formats).
delete app._cartParser;
app._cartParser = cart::newCartParser(
app._cartDump, app._identified->formatType, app._identified->flags
);
}
_cartInitDone:
app._workerStatus.update(2, 3, WSTR("App.cartDetectWorker.readDigitalIO"));
if (
#ifdef ENABLE_DUMMY_CART_DRIVER
!(app._cartDump.flags & cart::DUMP_SYSTEM_ID_OK) &&
#endif
io::isDigitalIOPresent()
) {
util::Data bitstream;
if (!app._fileIO.resource.loadData(bitstream, "data/fpga.bit"))
return true;
bool ready = io::loadDigitalIOBitstream(
bitstream.as<uint8_t>(), bitstream.length
);
bitstream.destroy();
if (!ready)
return true;
io::initDigitalIOFPGA();
auto error = app._cartDriver->readSystemID();
if (error)
LOG_APP("XID error [%s]", cart::getErrorString(error));
}
return true;
}
static const util::Hash _UNLOCK_ERRORS[cart::NUM_CHIP_TYPES]{
0,
"App.cartUnlockWorker.x76f041Error"_h,
"App.cartUnlockWorker.x76f100Error"_h,
"App.cartUnlockWorker.zs01Error"_h
};
bool cartUnlockWorker(App &app) {
app._workerStatus.setNextScreen(app._cartInfoScreen, true);
app._workerStatus.update(0, 2, WSTR("App.cartUnlockWorker.read"));
app._qrCodeScreen.valid = false;
auto error = app._cartDriver->readPrivateData();
if (error) {
app._messageScreen.setMessage(
MESSAGE_ERROR, WSTRH(_UNLOCK_ERRORS[app._cartDump.chipType]),
cart::getErrorString(error)
);
app._workerStatus.setNextScreen(app._messageScreen);
return false;
}
if (app._cartParser)
delete app._cartParser;
app._cartParser = cart::newCartParser(app._cartDump);
if (!app._cartParser)
return true;
app._workerStatus.update(1, 2, WSTR("App.cartUnlockWorker.identifyGame"));
char code[8], region[8];
if (app._cartParser->getCode(code) && app._cartParser->getRegion(region))
app._identified = app._cartDB.lookup(code, region);
// If auto-identification failed (e.g. because the format has no game code),
// use the game whose unlocking key was selected as a hint.
if (!app._identified) {
if (app._selectedEntry) {
LOG_APP("identify failed, using key as hint");
app._identified = app._selectedEntry;
} else {
return true;
}
}
delete app._cartParser;
app._cartParser = cart::newCartParser(
app._cartDump, app._identified->formatType, app._identified->flags
);
return true;
}
bool qrCodeWorker(App &app) {
char qrString[cart::MAX_QR_STRING_LENGTH];
app._workerStatus.update(0, 2, WSTR("App.qrCodeWorker.compress"));
app._cartDump.toQRString(qrString);
app._workerStatus.update(1, 2, WSTR("App.qrCodeWorker.generate"));
app._qrCodeScreen.generateCode(qrString);
return true;
}
bool cartDumpWorker(App &app) {
app._workerStatus.update(0, 1, WSTR("App.cartDumpWorker.save"));
char path[fs::MAX_PATH_LENGTH], code[8], region[8];
size_t length = app._cartDump.getDumpLength();
if (!app._createDataDirectory())
goto _error;
if (
app._identified && app._cartParser->getCode(code) &&
app._cartParser->getRegion(region)
) {
snprintf(
path, sizeof(path), EXTERNAL_DATA_DIR "/%s%s.573", code, region
);
} else {
if (!app._getNumberedPath(
path, sizeof(path), EXTERNAL_DATA_DIR "/cart%04d.573"
))
goto _error;
}
LOG_APP("saving %s, length=%d", path, length);
if (app._fileIO.vfs.saveData(&app._cartDump, length, path) != length)
goto _error;
app._messageScreen.setMessage(
MESSAGE_SUCCESS, WSTR("App.cartDumpWorker.success"), path
);
return true;
_error:
app._messageScreen.setMessage(
MESSAGE_ERROR, WSTR("App.cartDumpWorker.error"), path
);
return false;
}
bool cartWriteWorker(App &app) {
app._workerStatus.update(0, 1, WSTR("App.cartWriteWorker.write"));
uint8_t key[8];
auto error = app._cartDriver->writeData();
if (!error)
app._identified->copyKeyTo(key);
cartDetectWorker(app);
if (error) {
app._messageScreen.setMessage(
MESSAGE_ERROR, WSTR("App.cartWriteWorker.error"),
cart::getErrorString(error)
);
app._workerStatus.setNextScreen(app._messageScreen);
return false;
}
app._cartDump.copyKeyFrom(key);
return cartUnlockWorker(app);
}
bool cartRestoreWorker(App &app) {
app._workerStatus.update(0, 3, WSTR("App.cartRestoreWorker.init"));
const char *path = app._fileBrowserScreen.selectedPath;
auto file = app._fileIO.vfs.openFile(path, fs::READ);
cart::CartDump newDump;
if (file) {
auto length = file->read(&newDump, sizeof(newDump));
file->close();
delete file;
if (length < (sizeof(newDump) - sizeof(newDump.data)))
goto _fileError;
if (!newDump.validateMagic())
goto _fileError;
if (length != newDump.getDumpLength())
goto _fileError;
}
if (app._cartDump.chipType != newDump.chipType) {
app._messageScreen.setMessage(
MESSAGE_ERROR, WSTR("App.cartRestoreWorker.typeError"), path
);
app._workerStatus.setNextScreen(app._messageScreen);
return false;
}
{
app._workerStatus.update(1, 3, WSTR("App.cartRestoreWorker.setDataKey"));
auto error = app._cartDriver->setDataKey(newDump.dataKey);
if (error) {
LOG_APP("key error [%s]", cart::getErrorString(error));
} else {
if (newDump.flags & (
cart::DUMP_PUBLIC_DATA_OK | cart::DUMP_PRIVATE_DATA_OK
))
app._cartDump.copyDataFrom(newDump.data);
if (newDump.flags & cart::DUMP_CONFIG_OK)
app._cartDump.copyConfigFrom(newDump.config);
app._workerStatus.update(2, 3, WSTR("App.cartRestoreWorker.write"));
error = app._cartDriver->writeData();
}
cartDetectWorker(app);
if (error) {
app._messageScreen.setMessage(
MESSAGE_ERROR, WSTR("App.cartRestoreWorker.writeError"),
cart::getErrorString(error)
);
app._workerStatus.setNextScreen(app._messageScreen);
return false;
}
}
return cartUnlockWorker(app);
_fileError:
app._messageScreen.setMessage(
MESSAGE_ERROR, WSTR("App.cartRestoreWorker.fileError"), path
);
app._workerStatus.setNextScreen(app._messageScreen);
return false;
}
bool cartReflashWorker(App &app) {
// Make sure a valid cart ID is present if required by the new data.
if (
app._selectedEntry->requiresCartID() &&
!(app._cartDump.flags & cart::DUMP_CART_ID_OK)
) {
app._messageScreen.setMessage(
MESSAGE_ERROR, WSTR("App.cartReflashWorker.idError")
);
app._workerStatus.setNextScreen(app._messageScreen);
return false;
}
app._workerStatus.update(0, 3, WSTR("App.cartReflashWorker.init"));
// TODO: preserve 0x81 traceid if possible
#if 0
uint8_t traceID[8];
_cartParser->getIdentifiers()->traceID.copyTo(traceID);
#endif
if (!cartEraseWorker(app))
return false;
if (app._cartParser)
delete app._cartParser;
app._cartParser = cart::newCartParser(
app._cartDump, app._selectedEntry->formatType, app._selectedEntry->flags
);
auto pri = app._cartParser->getIdentifiers();
auto pub = app._cartParser->getPublicIdentifiers();
util::clear(app._cartDump.data);
app._cartDump.initConfig(
9, app._selectedEntry->flags & cart::DATA_HAS_PUBLIC_SECTION
);
if (pri) {
if (app._selectedEntry->flags & cart::DATA_HAS_CART_ID)
pri->cartID.copyFrom(app._cartDump.cartID.data);
if (app._selectedEntry->flags & cart::DATA_HAS_TRACE_ID)
pri->updateTraceID(
app._selectedEntry->traceIDType,
app._selectedEntry->traceIDParam, &app._cartDump.cartID
);
if (app._selectedEntry->flags & cart::DATA_HAS_INSTALL_ID) {
// The private installation ID seems to be unused on carts with a
// public data section.
if (pub)
pub->setInstallID(app._selectedEntry->installIDPrefix);
else
pri->setInstallID(app._selectedEntry->installIDPrefix);
}
}
app._cartParser->setCode(app._selectedEntry->code);
app._cartParser->setRegion(app._selectedEntry->region);
app._cartParser->setYear(app._selectedEntry->year);
app._cartParser->flush();
app._workerStatus.update(1, 3, WSTR("App.cartReflashWorker.setDataKey"));
auto error = app._cartDriver->setDataKey(app._selectedEntry->dataKey);
if (error) {
LOG_APP("key error [%s]", cart::getErrorString(error));
} else {
app._workerStatus.update(2, 3, WSTR("App.cartReflashWorker.write"));
error = app._cartDriver->writeData();
}
cartDetectWorker(app);
if (error) {
app._messageScreen.setMessage(
MESSAGE_ERROR, WSTR("App.cartReflashWorker.writeError"),
cart::getErrorString(error)
);
app._workerStatus.setNextScreen(app._messageScreen);
return false;
}
return cartUnlockWorker(app);
}
bool cartEraseWorker(App &app) {
app._workerStatus.update(0, 1, WSTR("App.cartEraseWorker.erase"));
auto error = app._cartDriver->erase();
cartDetectWorker(app);
if (error) {
app._messageScreen.setMessage(
MESSAGE_ERROR, WSTR("App.cartEraseWorker.error"),
cart::getErrorString(error)
);
app._workerStatus.setNextScreen(app._messageScreen);
return false;
}
return cartUnlockWorker(app);
}

View File

@ -0,0 +1,28 @@
/*
* 573in1 - Copyright (C) 2022-2024 spicyjpeg
*
* 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.
*
* 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
* 573in1. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include "main/app/app.hpp"
bool cartDetectWorker(App &app);
bool cartUnlockWorker(App &app);
bool qrCodeWorker(App &app);
bool cartDumpWorker(App &app);
bool cartWriteWorker(App &app);
bool cartRestoreWorker(App &app);
bool cartReflashWorker(App &app);
bool cartEraseWorker(App &app);

View File

@ -25,6 +25,7 @@
#include "common/io.hpp" #include "common/io.hpp"
#include "common/rom.hpp" #include "common/rom.hpp"
#include "main/app/app.hpp" #include "main/app/app.hpp"
#include "main/workers/miscworkers.hpp"
#include "ps1/system.h" #include "ps1/system.h"
static const rom::Region *const _AUTOBOOT_REGIONS[]{ static const rom::Region *const _AUTOBOOT_REGIONS[]{
@ -43,25 +44,25 @@ static const char *const _AUTOBOOT_PATHS[][2]{
{ "hdd:/noboot.txt", "hdd:/psx.exe" } { "hdd:/noboot.txt", "hdd:/psx.exe" }
}; };
bool App::_startupWorker(void) { bool startupWorker(App &app) {
_workerStatus.update(0, 1, WSTR("App.startupWorker.ideInit")); app._workerStatus.update(0, 1, WSTR("App.startupWorker.ideInit"));
_fileIO.initIDE(); app._fileIO.initIDE();
_fileInitWorker(); fileInitWorker(app);
#ifdef ENABLE_AUTOBOOT #ifdef ENABLE_AUTOBOOT
// Only try to autoboot if DIP switch 1 is on. // Only try to autoboot if DIP switch 1 is on.
if (io::getDIPSwitch(0)) { if (io::getDIPSwitch(0)) {
_workerStatus.update(3, 4, WSTR("App.ideInitWorker.autoboot")); app._workerStatus.update(3, 4, WSTR("App.ideInitWorker.autoboot"));
if (io::getDIPSwitch(3)) { if (io::getDIPSwitch(3)) {
for (auto region : _AUTOBOOT_REGIONS) { for (auto region : _AUTOBOOT_REGIONS) {
if (!region->getBootExecutableHeader()) if (!region->getBootExecutableHeader())
continue; continue;
_storageActionsScreen.selectedRegion = region; app._storageActionsScreen.selectedRegion = region;
_workerStatus.setNextScreen(_autobootScreen); app._workerStatus.setNextScreen(app._autobootScreen);
return true; return true;
} }
} }
@ -69,18 +70,18 @@ bool App::_startupWorker(void) {
for (auto path : _AUTOBOOT_PATHS) { for (auto path : _AUTOBOOT_PATHS) {
fs::FileInfo info; fs::FileInfo info;
if (_fileIO.vfs.getFileInfo(info, path[0])) if (app._fileIO.vfs.getFileInfo(info, path[0]))
continue; continue;
if (!_fileIO.vfs.getFileInfo(info, path[1])) if (!app._fileIO.vfs.getFileInfo(info, path[1]))
continue; continue;
_storageActionsScreen.selectedRegion = nullptr; app._storageActionsScreen.selectedRegion = nullptr;
__builtin_strncpy( __builtin_strncpy(
_fileBrowserScreen.selectedPath, path[1], app._fileBrowserScreen.selectedPath, path[1],
sizeof(_fileBrowserScreen.selectedPath) sizeof(app._fileBrowserScreen.selectedPath)
); );
_workerStatus.setNextScreen(_autobootScreen); app._workerStatus.setNextScreen(app._autobootScreen);
return true; return true;
} }
} }
@ -89,17 +90,17 @@ bool App::_startupWorker(void) {
return true; return true;
} }
bool App::_fileInitWorker(void) { bool fileInitWorker(App &app) {
_workerStatus.update(0, 3, WSTR("App.fileInitWorker.unmount")); app._workerStatus.update(0, 3, WSTR("App.fileInitWorker.unmount"));
_fileIO.closeResourceFile(); app._fileIO.closeResourceFile();
_fileIO.unmountIDE(); app._fileIO.unmountIDE();
_workerStatus.update(1, 3, WSTR("App.fileInitWorker.mount")); app._workerStatus.update(1, 3, WSTR("App.fileInitWorker.mount"));
_fileIO.mountIDE(); app._fileIO.mountIDE();
_workerStatus.update(2, 3, WSTR("App.fileInitWorker.loadResources")); app._workerStatus.update(2, 3, WSTR("App.fileInitWorker.loadResources"));
if (_fileIO.loadResourceFile(EXTERNAL_DATA_DIR "/resource.zip")) if (app._fileIO.loadResourceFile(EXTERNAL_DATA_DIR "/resource.zip"))
_loadResources(); app._loadResources();
return true; return true;
} }
@ -132,11 +133,11 @@ static const char *const _DEVICE_TYPES[]{
"atapi" // storage::ATAPI "atapi" // storage::ATAPI
}; };
bool App::_executableWorker(void) { bool executableWorker(App &app) {
_workerStatus.update(0, 2, WSTR("App.executableWorker.init")); app._workerStatus.update(0, 2, WSTR("App.executableWorker.init"));
auto region = _storageActionsScreen.selectedRegion; auto region = app._storageActionsScreen.selectedRegion;
const char *path = _fileBrowserScreen.selectedPath; const char *path = app._fileBrowserScreen.selectedPath;
const char *deviceType; const char *deviceType;
int deviceIndex; int deviceIndex;
@ -151,7 +152,7 @@ bool App::_executableWorker(void) {
} else { } else {
__builtin_memset(header.magic, 0, sizeof(header.magic)); __builtin_memset(header.magic, 0, sizeof(header.magic));
auto file = _fileIO.vfs.openFile(path, fs::READ); auto file = app._fileIO.vfs.openFile(path, fs::READ);
if (file) { if (file) {
file->read(&header, sizeof(header)); file->read(&header, sizeof(header));
@ -160,15 +161,15 @@ bool App::_executableWorker(void) {
} }
if (!header.validateMagic()) { if (!header.validateMagic()) {
_messageScreen.setMessage( app._messageScreen.setMessage(
MESSAGE_ERROR, WSTR("App.executableWorker.fileError"), path MESSAGE_ERROR, WSTR("App.executableWorker.fileError"), path
); );
_workerStatus.setNextScreen(_messageScreen); app._workerStatus.setNextScreen(app._messageScreen);
return false; return false;
} }
deviceIndex = path[3] - '0'; deviceIndex = path[3] - '0';
deviceType = _DEVICE_TYPES[_fileIO.ideDevices[deviceIndex]->type]; deviceType = _DEVICE_TYPES[app._fileIO.ideDevices[deviceIndex]->type];
} }
auto executableEnd = header.textOffset + header.textLength; auto executableEnd = header.textOffset + header.textLength;
@ -197,10 +198,10 @@ bool App::_executableWorker(void) {
// appropriate location. // appropriate location.
util::Data binary; util::Data binary;
if (!_fileIO.resource.loadData(binary, launcher.path)) if (!app._fileIO.resource.loadData(binary, launcher.path))
continue; continue;
_workerStatus.update(1, 2, WSTR("App.executableWorker.load")); app._workerStatus.update(1, 2, WSTR("App.executableWorker.load"));
auto launcherHeader = binary.as<const util::ExecutableHeader>(); auto launcherHeader = binary.as<const util::ExecutableHeader>();
util::ExecutableLoader loader( util::ExecutableLoader loader(
@ -232,7 +233,7 @@ bool App::_executableWorker(void) {
// through the command line. // through the command line.
fs::FileFragmentTable fragments; fs::FileFragmentTable fragments;
_fileIO.vfs.getFileFragments(fragments, path); app._fileIO.vfs.getFileFragments(fragments, path);
auto fragment = fragments.as<const fs::FileFragment>(); auto fragment = fragments.as<const fs::FileFragment>();
auto count = fragments.getNumFragments(); auto count = fragments.getNumFragments();
@ -245,11 +246,11 @@ bool App::_executableWorker(void) {
fragments.destroy(); fragments.destroy();
_messageScreen.setMessage( app._messageScreen.setMessage(
MESSAGE_ERROR, WSTR("App.executableWorker.fragmentError"), MESSAGE_ERROR, WSTR("App.executableWorker.fragmentError"),
path, count, i path, count, i
); );
_workerStatus.setNextScreen(_messageScreen); app._workerStatus.setNextScreen(app._messageScreen);
return false; return false;
} }
@ -258,9 +259,9 @@ bool App::_executableWorker(void) {
// All destructors must be invoked manually as we are not returning to // All destructors must be invoked manually as we are not returning to
// main() before starting the new executable. // main() before starting the new executable.
_unloadCartData(); app._unloadCartData();
_fileIO.closeResourceFile(); app._fileIO.closeResourceFile();
_fileIO.unmountIDE(); app._fileIO.unmountIDE();
LOG_APP("jumping to launcher"); LOG_APP("jumping to launcher");
uninstallExceptionHandler(); uninstallExceptionHandler();
@ -269,18 +270,18 @@ bool App::_executableWorker(void) {
loader.run(); loader.run();
} }
_messageScreen.setMessage( app._messageScreen.setMessage(
MESSAGE_ERROR, WSTR("App.executableWorker.addressError"), MESSAGE_ERROR, WSTR("App.executableWorker.addressError"),
header.textOffset, executableEnd - 1, stackTop header.textOffset, executableEnd - 1, stackTop
); );
_workerStatus.setNextScreen(_messageScreen); app._workerStatus.setNextScreen(app._messageScreen);
return false; return false;
} }
bool App::_atapiEjectWorker(void) { bool atapiEjectWorker(App &app) {
_workerStatus.update(0, 1, WSTR("App.atapiEjectWorker.eject")); app._workerStatus.update(0, 1, WSTR("App.atapiEjectWorker.eject"));
for (auto dev : _fileIO.ideDevices) { for (auto dev : app._fileIO.ideDevices) {
if (!dev) if (!dev)
continue; continue;
@ -294,31 +295,31 @@ bool App::_atapiEjectWorker(void) {
continue; continue;
if (error) { if (error) {
_messageScreen.setMessage( app._messageScreen.setMessage(
MESSAGE_ERROR, WSTR("App.atapiEjectWorker.ejectError"), MESSAGE_ERROR, WSTR("App.atapiEjectWorker.ejectError"),
storage::getErrorString(error) storage::getErrorString(error)
); );
_workerStatus.setNextScreen(_messageScreen); app._workerStatus.setNextScreen(app._messageScreen);
return false; return false;
} }
return true; return true;
} }
_messageScreen.setMessage( app._messageScreen.setMessage(
MESSAGE_ERROR, WSTR("App.atapiEjectWorker.noDrive") MESSAGE_ERROR, WSTR("App.atapiEjectWorker.noDrive")
); );
_workerStatus.setNextScreen(_messageScreen); app._workerStatus.setNextScreen(app._messageScreen);
return false; return false;
} }
bool App::_rebootWorker(void) { bool rebootWorker(App &app) {
_workerStatus.update(0, 1, WSTR("App.rebootWorker.reboot")); app._workerStatus.update(0, 1, WSTR("App.rebootWorker.reboot"));
_unloadCartData(); app._unloadCartData();
_fileIO.closeResourceFile(); app._fileIO.closeResourceFile();
_fileIO.unmountIDE(); app._fileIO.unmountIDE();
_workerStatus.setStatus(WORKER_REBOOT); app._workerStatus.setStatus(WORKER_REBOOT);
// Fall back to a soft reboot if the watchdog fails to reset the system. // Fall back to a soft reboot if the watchdog fails to reset the system.
delayMicroseconds(2000000); delayMicroseconds(2000000);

View File

@ -0,0 +1,25 @@
/*
* 573in1 - Copyright (C) 2022-2024 spicyjpeg
*
* 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.
*
* 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
* 573in1. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include "main/app/app.hpp"
bool startupWorker(App &app);
bool fileInitWorker(App &app);
bool executableWorker(App &app);
bool atapiEjectWorker(App &app);
bool rebootWorker(App &app);

View File

@ -25,6 +25,7 @@
#include "common/romdrivers.hpp" #include "common/romdrivers.hpp"
#include "main/app/app.hpp" #include "main/app/app.hpp"
#include "main/app/romactions.hpp" #include "main/app/romactions.hpp"
#include "main/workers/romworkers.hpp"
struct RegionInfo { struct RegionInfo {
public: public:
@ -73,8 +74,8 @@ static constexpr size_t _DUMP_CHUNKS_PER_CRC = 32; // Save CRC32 every 16 MB
// TODO: all these *really* need a cleanup... // TODO: all these *really* need a cleanup...
bool App::_romChecksumWorker(void) { bool romChecksumWorker(App &app) {
_checksumScreen.valid = false; app._checksumScreen.valid = false;
for (auto &entry : _REGION_INFO) { for (auto &entry : _REGION_INFO) {
if (!entry.region.isPresent()) if (!entry.region.isPresent())
@ -87,7 +88,7 @@ bool App::_romChecksumWorker(void) {
uint32_t offset = 0; uint32_t offset = 0;
uint32_t crc = 0; uint32_t crc = 0;
auto crcPtr = reinterpret_cast<uint32_t *>( auto crcPtr = reinterpret_cast<uint32_t *>(
reinterpret_cast<uintptr_t>(&_checksumScreen.values) + reinterpret_cast<uintptr_t>(&app._checksumScreen.values) +
entry.crcOffset entry.crcOffset
); );
@ -97,7 +98,7 @@ bool App::_romChecksumWorker(void) {
size_t end = util::min(i + _DUMP_CHUNKS_PER_CRC, numChunks); size_t end = util::min(i + _DUMP_CHUNKS_PER_CRC, numChunks);
for (size_t j = i; j < end; j++) { for (size_t j = i; j < end; j++) {
_workerStatus.update(j, numChunks, WSTRH(entry.crcPrompt)); app._workerStatus.update(j, numChunks, WSTRH(entry.crcPrompt));
crc = entry.region.zipCRC32(offset, chunkLength, crc); crc = entry.region.zipCRC32(offset, chunkLength, crc);
offset += chunkLength; offset += chunkLength;
@ -107,24 +108,24 @@ bool App::_romChecksumWorker(void) {
} }
} }
_checksumScreen.valid = true; app._checksumScreen.valid = true;
return true; return true;
} }
bool App::_romDumpWorker(void) { bool romDumpWorker(App &app) {
_workerStatus.update(0, 1, WSTR("App.romDumpWorker.init")); app._workerStatus.update(0, 1, WSTR("App.romDumpWorker.init"));
// Store all dumps in a subdirectory named "dumpNNNN" within the main data // Store all dumps in a subdirectory named "dumpNNNN" within the main data
// folder. // folder.
char dirPath[fs::MAX_PATH_LENGTH], filePath[fs::MAX_PATH_LENGTH]; char dirPath[fs::MAX_PATH_LENGTH], filePath[fs::MAX_PATH_LENGTH];
if (!_createDataDirectory()) if (!app._createDataDirectory())
goto _initError; goto _initError;
if (!_getNumberedPath( if (!app._getNumberedPath(
dirPath, sizeof(dirPath), EXTERNAL_DATA_DIR "/dump%04d" dirPath, sizeof(dirPath), EXTERNAL_DATA_DIR "/dump%04d"
)) ))
goto _initError; goto _initError;
if (!_fileIO.vfs.createDirectory(dirPath)) if (!app._fileIO.vfs.createDirectory(dirPath))
goto _initError; goto _initError;
LOG_APP("saving dumps to %s", dirPath); LOG_APP("saving dumps to %s", dirPath);
@ -145,7 +146,7 @@ bool App::_romDumpWorker(void) {
snprintf(filePath, sizeof(filePath), entry.path, dirPath); snprintf(filePath, sizeof(filePath), entry.path, dirPath);
auto file = _fileIO.vfs.openFile( auto file = app._fileIO.vfs.openFile(
filePath, fs::WRITE | fs::ALLOW_CREATE filePath, fs::WRITE | fs::ALLOW_CREATE
); );
@ -158,7 +159,7 @@ bool App::_romDumpWorker(void) {
buffer.allocate(chunkLength); buffer.allocate(chunkLength);
for (size_t i = 0; i < numChunks; i++) { for (size_t i = 0; i < numChunks; i++) {
_workerStatus.update(i, numChunks, WSTRH(entry.dumpPrompt)); app._workerStatus.update(i, numChunks, WSTRH(entry.dumpPrompt));
entry.region.read(buffer.ptr, offset, chunkLength); entry.region.read(buffer.ptr, offset, chunkLength);
if (file->write(buffer.ptr, chunkLength) < chunkLength) { if (file->write(buffer.ptr, chunkLength) < chunkLength) {
@ -179,42 +180,42 @@ bool App::_romDumpWorker(void) {
LOG_APP("%s saved", filePath); LOG_APP("%s saved", filePath);
} }
_messageScreen.setMessage( app._messageScreen.setMessage(
MESSAGE_SUCCESS, WSTR("App.romDumpWorker.success"), dirPath MESSAGE_SUCCESS, WSTR("App.romDumpWorker.success"), dirPath
); );
return true; return true;
_initError: _initError:
_messageScreen.setMessage( app._messageScreen.setMessage(
MESSAGE_ERROR, WSTR("App.romDumpWorker.initError"), dirPath MESSAGE_ERROR, WSTR("App.romDumpWorker.initError"), dirPath
); );
return false; return false;
_fileError: _fileError:
_messageScreen.setMessage( app._messageScreen.setMessage(
MESSAGE_ERROR, WSTR("App.romDumpWorker.fileError"), filePath MESSAGE_ERROR, WSTR("App.romDumpWorker.fileError"), filePath
); );
return false; return false;
} }
bool App::_romRestoreWorker(void) { bool romRestoreWorker(App &app) {
_workerStatus.update(0, 1, WSTR("App.romRestoreWorker.init")); app._workerStatus.update(0, 1, WSTR("App.romRestoreWorker.init"));
const char *path = _fileBrowserScreen.selectedPath; const char *path = app._fileBrowserScreen.selectedPath;
auto file = _fileIO.vfs.openFile(path, fs::READ); auto file = app._fileIO.vfs.openFile(path, fs::READ);
if (!file) { if (!file) {
_messageScreen.setMessage( app._messageScreen.setMessage(
MESSAGE_ERROR, WSTR("App.romRestoreWorker.fileError"), path MESSAGE_ERROR, WSTR("App.romRestoreWorker.fileError"), path
); );
return false; return false;
} }
if (!_romEraseWorker()) if (!romEraseWorker(app))
return false; return false;
auto region = _storageActionsScreen.selectedRegion; auto region = app._storageActionsScreen.selectedRegion;
auto regionLength = _storageActionsScreen.selectedLength; auto regionLength = app._storageActionsScreen.selectedLength;
auto driver = region->newDriver(); auto driver = region->newDriver();
auto chipLength = driver->getChipSize().chipLength; auto chipLength = driver->getChipSize().chipLength;
@ -232,7 +233,7 @@ bool App::_romRestoreWorker(void) {
// Parallelize writing by buffering a chunk for each chip into RAM, then // Parallelize writing by buffering a chunk for each chip into RAM, then
// writing all chunks to the respective chips at the same time. // writing all chunks to the respective chips at the same time.
for (size_t i = 0; i < chipLength; i += maxChunkLength) { for (size_t i = 0; i < chipLength; i += maxChunkLength) {
_workerStatus.update(i, chipLength, WSTR("App.romRestoreWorker.write")); app._workerStatus.update(i, chipLength, WSTR("App.romRestoreWorker.write"));
auto bufferPtr = buffers.as<uint8_t>(); auto bufferPtr = buffers.as<uint8_t>();
auto lengthPtr = chunkLengths.as<size_t>(); auto lengthPtr = chunkLengths.as<size_t>();
@ -305,7 +306,7 @@ bool App::_romRestoreWorker(void) {
delete file; delete file;
delete driver; delete driver;
_messageScreen.setMessage( app._messageScreen.setMessage(
MESSAGE_ERROR, WSTR("App.romRestoreWorker.flashError"), MESSAGE_ERROR, WSTR("App.romRestoreWorker.flashError"),
rom::getErrorString(error), bytesWritten rom::getErrorString(error), bytesWritten
); );
@ -326,13 +327,13 @@ bool App::_romRestoreWorker(void) {
delete file; delete file;
delete driver; delete driver;
_messageScreen.setMessage(MESSAGE_SUCCESS, WSTRH(message), bytesWritten); app._messageScreen.setMessage(MESSAGE_SUCCESS, WSTRH(message), bytesWritten);
return true; return true;
} }
bool App::_romEraseWorker(void) { bool romEraseWorker(App &app) {
auto region = _storageActionsScreen.selectedRegion; auto region = app._storageActionsScreen.selectedRegion;
auto regionLength = _storageActionsScreen.selectedLength; auto regionLength = app._storageActionsScreen.selectedLength;
auto driver = region->newDriver(); auto driver = region->newDriver();
size_t chipLength = driver->getChipSize().chipLength; size_t chipLength = driver->getChipSize().chipLength;
@ -343,18 +344,18 @@ bool App::_romEraseWorker(void) {
if (!chipLength) { if (!chipLength) {
delete driver; delete driver;
_messageScreen.setMessage( app._messageScreen.setMessage(
MESSAGE_ERROR, WSTR("App.romEraseWorker.unsupported") MESSAGE_ERROR, WSTR("App.romEraseWorker.unsupported")
); );
return false; return false;
} }
_checksumScreen.valid = false; app._checksumScreen.valid = false;
// Parallelize erasing by sending the same sector erase command to all chips // Parallelize erasing by sending the same sector erase command to all chips
// at the same time. // at the same time.
for (size_t i = 0; i < chipLength; i += sectorLength) { for (size_t i = 0; i < chipLength; i += sectorLength) {
_workerStatus.update(i, chipLength, WSTR("App.romEraseWorker.erase")); app._workerStatus.update(i, chipLength, WSTR("App.romEraseWorker.erase"));
for (size_t j = 0; j < regionLength; j += chipLength) for (size_t j = 0; j < regionLength; j += chipLength)
driver->eraseSector(i + j); driver->eraseSector(i + j);
@ -367,7 +368,7 @@ bool App::_romEraseWorker(void) {
delete driver; delete driver;
_messageScreen.setMessage( app._messageScreen.setMessage(
MESSAGE_ERROR, WSTR("App.romEraseWorker.flashError"), MESSAGE_ERROR, WSTR("App.romEraseWorker.flashError"),
rom::getErrorString(error), sectorsErased rom::getErrorString(error), sectorsErased
); );
@ -377,18 +378,18 @@ bool App::_romEraseWorker(void) {
delete driver; delete driver;
_messageScreen.setMessage( app._messageScreen.setMessage(
MESSAGE_SUCCESS, WSTR("App.romEraseWorker.success"), sectorsErased MESSAGE_SUCCESS, WSTR("App.romEraseWorker.success"), sectorsErased
); );
return true; return true;
} }
bool App::_flashExecutableWriteWorker(void) { bool flashExecutableWriteWorker(App &app) {
// TODO: implement // TODO: implement
return false; return false;
} }
bool App::_flashHeaderWriteWorker(void) { bool flashHeaderWriteWorker(App &app) {
auto driver = rom::flash.newDriver(); auto driver = rom::flash.newDriver();
size_t sectorLength = driver->getChipSize().eraseSectorLength; size_t sectorLength = driver->getChipSize().eraseSectorLength;
@ -397,15 +398,15 @@ bool App::_flashHeaderWriteWorker(void) {
if (!sectorLength) { if (!sectorLength) {
delete driver; delete driver;
_messageScreen.setMessage( app._messageScreen.setMessage(
MESSAGE_ERROR, WSTR("App.flashHeaderWriteWorker.unsupported") MESSAGE_ERROR, WSTR("App.flashHeaderWriteWorker.unsupported")
); );
_workerStatus.setNextScreen(_messageScreen); app._workerStatus.setNextScreen(app._messageScreen);
return false; return false;
} }
_checksumScreen.valid = false; app._checksumScreen.valid = false;
_workerStatus.update(0, 2, WSTR("App.flashHeaderWriteWorker.erase")); app._workerStatus.update(0, 2, WSTR("App.flashHeaderWriteWorker.erase"));
// The flash can only be erased with sector granularity, so all data in the // The flash can only be erased with sector granularity, so all data in the
// first sector other than the header must be backed up and rewritten. // first sector other than the header must be backed up and rewritten.
@ -420,11 +421,11 @@ bool App::_flashHeaderWriteWorker(void) {
if (error) if (error)
goto _flashError; goto _flashError;
_workerStatus.update(1, 2, WSTR("App.flashHeaderWriteWorker.write")); app._workerStatus.update(1, 2, WSTR("App.flashHeaderWriteWorker.write"));
// Write the new header (if any). // Write the new header (if any).
if (!_romHeaderDump.isDataEmpty()) { if (!app._romHeaderDump.isDataEmpty()) {
auto ptr = reinterpret_cast<const uint16_t *>(_romHeaderDump.data); auto ptr = reinterpret_cast<const uint16_t *>(app._romHeaderDump.data);
for ( for (
uint32_t offset = rom::FLASH_HEADER_OFFSET; uint32_t offset = rom::FLASH_HEADER_OFFSET;
@ -466,10 +467,10 @@ _flashError:
buffer.destroy(); buffer.destroy();
delete driver; delete driver;
_messageScreen.setMessage( app._messageScreen.setMessage(
MESSAGE_ERROR, WSTR("App.flashHeaderWriteWorker.flashError"), MESSAGE_ERROR, WSTR("App.flashHeaderWriteWorker.flashError"),
rom::getErrorString(error) rom::getErrorString(error)
); );
_workerStatus.setNextScreen(_messageScreen); app._workerStatus.setNextScreen(app._messageScreen);
return false; return false;
} }

View File

@ -0,0 +1,26 @@
/*
* 573in1 - Copyright (C) 2022-2024 spicyjpeg
*
* 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.
*
* 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
* 573in1. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include "main/app/app.hpp"
bool romChecksumWorker(App &app);
bool romDumpWorker(App &app);
bool romRestoreWorker(App &app);
bool romEraseWorker(App &app);
bool flashExecutableWriteWorker(App &app);
bool flashHeaderWriteWorker(App &app);

View File

@ -32,7 +32,8 @@ static uint32_t _savedExceptionVector[4];
static Thread _mainThread; static Thread _mainThread;
ArgFunction interruptHandler = 0; ArgFunction interruptHandler = 0;
void *interruptHandlerArg = 0; void *interruptHandlerArg0 = 0;
void *interruptHandlerArg1 = 0;
Thread *currentThread = &_mainThread; Thread *currentThread = &_mainThread;
Thread *nextThread = &_mainThread; Thread *nextThread = &_mainThread;
@ -89,10 +90,11 @@ void uninstallExceptionHandler(void) {
_flushCache(); _flushCache();
} }
void setInterruptHandler(ArgFunction func, void *arg) { void setInterruptHandler(ArgFunction func, void *arg0, void *arg1) {
disableInterrupts(); disableInterrupts();
interruptHandler = func; interruptHandler = func;
interruptHandlerArg = arg; interruptHandlerArg0 = arg0;
interruptHandlerArg1 = arg1;
flushWriteQueue(); flushWriteQueue();
} }

View File

@ -29,7 +29,7 @@ typedef struct {
} Thread; } Thread;
typedef void (*VoidFunction)(void); typedef void (*VoidFunction)(void);
typedef void (*ArgFunction)(void *arg); typedef void (*ArgFunction)(void *arg0, void *arg1);
#ifdef __cplusplus #ifdef __cplusplus
extern "C" { extern "C" {
@ -92,16 +92,18 @@ __attribute__((always_inline)) static inline void flushWriteQueue(void) {
* *
* @param thread * @param thread
* @param func * @param func
* @param arg Optional argument to entry point * @param arg0 Optional first argument to entry point
* @param arg1 Optional second argument to entry point
* @param stack Pointer to last 8 bytes in the stack * @param stack Pointer to last 8 bytes in the stack
*/ */
__attribute__((always_inline)) static inline void initThread( __attribute__((always_inline)) static inline void initThread(
Thread *thread, ArgFunction func, void *arg, void *stack Thread *thread, ArgFunction func, void *arg0, void *arg1, void *stack
) { ) {
register uint32_t gp __asm__("gp"); register uint32_t gp __asm__("gp");
thread->pc = (uint32_t) func; thread->pc = (uint32_t) func;
thread->a0 = (uint32_t) arg; thread->a0 = (uint32_t) arg0;
thread->a1 = (uint32_t) arg1;
thread->gp = (uint32_t) gp; thread->gp = (uint32_t) gp;
thread->sp = (uint32_t) stack; thread->sp = (uint32_t) stack;
thread->fp = (uint32_t) stack; thread->fp = (uint32_t) stack;
@ -154,9 +156,10 @@ void uninstallExceptionHandler(void);
* a new handler. * a new handler.
* *
* @param func * @param func
* @param arg Optional argument to be passed to handler * @param arg Optional first argument to be passed to handler
* @param arg Optional second argument to be passed to handler
*/ */
void setInterruptHandler(ArgFunction func, void *arg); void setInterruptHandler(ArgFunction func, void *arg0, void *arg1);
/** /**
* @brief Temporarily disables interrupts, then calls the BIOS function to clear * @brief Temporarily disables interrupts, then calls the BIOS function to clear

View File

@ -22,23 +22,25 @@
.set CAUSE, $13 .set CAUSE, $13
.set EPC, $14 .set EPC, $14
.set COP0_CAUSE_EXC_BITMASK, 31 << 2
.set COP0_CAUSE_EXC_SYS, 8 << 2
.section .text._exceptionVector, "ax", @progbits .section .text._exceptionVector, "ax", @progbits
.global _exceptionVector .global _exceptionVector
.type _exceptionVector, @function .type _exceptionVector, @function
_exceptionVector: _exceptionVector:
# This tiny stub is going to be relocated to the address the CPU jumps to # This 16-byte stub is going to be relocated to the address the CPU jumps to
# when an exception occurs (0x80000080) at runtime, overriding the default # when an exception occurs (0x80000080) at runtime, overriding the default
# one installed by the BIOS. We're going to fetch a pointer to the current # one installed by the BIOS. We're going to fetch a pointer to the current
# thread, grab the EPC (i.e. the address of the instruction that was being # thread, grab the EPC (i.e. the address of the instruction that was being
# executed before the exception occurred) and jump to the exception handler. # executed before the exception occurred) and jump to the exception handler.
# NOTE: we can't use any registers other than $k0 and $k1 here, as doing so # NOTE: we can't use any registers other than $k0 and $k1 here, as doing so
# would destroy their contents and corrupt the current thread's state. # would destroy their contents and corrupt the current thread's state.
lui $k0, %hi(currentThread) lw $k0, %gprel(currentThread)($gp)
lw $k0, %lo(currentThread)($k0)
j _exceptionHandler j _exceptionHandler
mfc0 $k1, EPC mfc0 $k1, EPC
nop
.section .text._exceptionHandler, "ax", @progbits .section .text._exceptionHandler, "ax", @progbits
.global _exceptionHandler .global _exceptionHandler
@ -88,15 +90,17 @@ _exceptionHandler:
# exception. If it was caused by a syscall, increment EPC to make sure # exception. If it was caused by a syscall, increment EPC to make sure
# returning to the thread won't trigger another syscall. # returning to the thread won't trigger another syscall.
mfc0 $v0, CAUSE mfc0 $v0, CAUSE
lui $v1, %hi(interruptHandler) lw $v1, %gprel(interruptHandler)($gp)
andi $v0, 0x1f << 2 # if (((CAUSE >> 2) & 0x1f) == 0) goto checkForGTEInst # int code = CAUSE & COP0_CAUSE_EXC_BITMASK;
beqz $v0, .LcheckForGTEInst andi $v0, COP0_CAUSE_EXC_BITMASK
li $at, 8 << 2 # if (((CAUSE >> 2) & 0x1f) == 8) goto applyIncrement beqz $v0, .LisInterrupt
beq $v0, $at, .LapplyIncrement li $at, COP0_CAUSE_EXC_SYS
lw $v1, %lo(interruptHandler)($v1)
.LotherException: beq $v0, $at, .LisSyscall
lw $a0, %gprel(interruptHandlerArg0)($gp)
.LisOtherException: # if ((code != INT) && (code != SYS)) {
# If the exception was not triggered by a syscall nor by an interrupt call # If the exception was not triggered by a syscall nor by an interrupt call
# _unhandledException(), which will then display information about the # _unhandledException(), which will then display information about the
# exception and lock up. # exception and lock up.
@ -107,41 +111,44 @@ _exceptionHandler:
jal _unhandledException jal _unhandledException
addiu $sp, -8 addiu $sp, -8
lw $k0, %gprel(nextThread)($gp)
b .Lreturn b .Lreturn
addiu $sp, 8 addiu $sp, 8
.LcheckForGTEInst: .LisInterrupt: # } else {
# If the exception was caused by an interrupt, check if the interrupted # If the exception was caused by an interrupt, check if the interrupted
# instruction was a GTE opcode and increment EPC to avoid executing it again # instruction was a GTE opcode and increment EPC to avoid executing it again
# if that is the case. This is a workaround for a hardware bug. # if that is the case. This is a workaround for a hardware bug.
lw $v0, 0($k1) # if ((*EPC >> 25) == 0x25) EPC++
# if ((code == INT) && ((*EPC >> 25) == 0x25)) EPC += 4;
lw $v0, 0($k1)
li $at, 0x25 li $at, 0x25
srl $v0, 25 srl $v0, 25
bne $v0, $at, .LskipIncrement bne $v0, $at, .LskipEPCIncrement
lw $v1, %lo(interruptHandler)($v1) lw $a0, %gprel(interruptHandlerArg0)($gp)
.LapplyIncrement: .LisSyscall:
# if (code == SYS) EPC += 4;
addiu $k1, 4 addiu $k1, 4
.LskipIncrement: .LskipEPCIncrement:
# Save the modified EPC and dispatch any pending interrupts. The handler # Save the modified EPC and invoke the interrupt handler, which will
# will temporarily use the current thread's stack. # temporarily use the current thread's stack.
sw $k1, 0x00($k0) sw $k1, 0x00($k0)
lui $a0, %hi(interruptHandlerArg) # interruptHandler(interruptHandlerArg0, interruptHandlerArg1);
lw $a0, %lo(interruptHandlerArg)($a0) lw $a1, %gprel(interruptHandlerArg1)($gp)
jalr $v1 # interruptHandler(interruptHandlerArg) jalr $v1
addiu $sp, -8 addiu $sp, -8
lw $k0, %gprel(nextThread)($gp)
addiu $sp, 8 addiu $sp, 8
.Lreturn: .Lreturn: # }
# Grab a pointer to the next thread to be executed, restore its state and # Grab a pointer to the next thread to be executed and restore its state.
# return.
lui $k0, %hi(nextThread) # currentThread = nextThread;
lw $k0, %lo(nextThread)($k0) sw $k0, %gprel(currentThread)($gp)
lui $at, %hi(currentThread)
sw $k0, %lo(currentThread)($at)
lw $v0, 0x78($k0) lw $v0, 0x78($k0)
lw $v1, 0x7c($k0) lw $v1, 0x7c($k0)
@ -197,7 +204,9 @@ _exceptionHandler:
delayMicroseconds: delayMicroseconds:
# Calculate the approximate number of CPU cycles that need to be burned, # Calculate the approximate number of CPU cycles that need to be burned,
# assuming a 33.8688 MHz clock (1 us = 33.8688 = ~33.875 cycles). # assuming a 33.8688 MHz clock (1 us = 33.8688 = ~33.875 cycles).
sll $a1, $a0, 8 # cycles = ((us * 271) + 4) / 8
# cycles = ((us * 271) + 4) / 8;
sll $a1, $a0, 8
sll $a2, $a0, 4 sll $a2, $a0, 4
addu $a1, $a2 addu $a1, $a2
subu $a1, $a0 subu $a1, $a0
@ -208,9 +217,9 @@ delayMicroseconds:
# loop and returning. # loop and returning.
addiu $a0, -(6 + 1 + 2 + 4 + 2) addiu $a0, -(6 + 1 + 2 + 4 + 2)
# Reset timer 2 to its default setting of counting system clock edges. # TIMER2_CTRL = 0;
lui $v1, %hi(IO_BASE) lui $v1, %hi(IO_BASE)
sh $0, %lo(TIMER2_CTRL)($v1) # TIMER2_CTRL = 0 sh $0, %lo(TIMER2_CTRL)($v1)
# Wait for up to 0xff00 cycles at a time (resetting the timer and waiting # Wait for up to 0xff00 cycles at a time (resetting the timer and waiting
# for it to count up each time), as the counter is only 16 bits wide. We # for it to count up each time), as the counter is only 16 bits wide. We
@ -221,11 +230,13 @@ delayMicroseconds:
beqz $v0, .LshortDelay beqz $v0, .LshortDelay
li $a2, 0xff00 + 3 li $a2, 0xff00 + 3
.LlongDelay: # for (; cycles > 0xff00; cycles -= (0xff00 + 3)) .LlongDelay: # for (; cycles > 0xff00; cycles -= (0xff00 + 3)) {
sh $0, %lo(TIMER2_VALUE)($v1) # TIMER2_VALUE = 0 # TIMER2_VALUE = 0;
sh $0, %lo(TIMER2_VALUE)($v1)
li $v0, 0 li $v0, 0
.LlongDelayLoop: # while (TIMER2_VALUE < 0xff00); .LlongDelayLoop:
# while (TIMER2_VALUE < 0xff00);
nop nop
slt $v0, $v0, $a1 slt $v0, $v0, $a1
bnez $v0, .LlongDelayLoop bnez $v0, .LlongDelayLoop
@ -235,17 +246,21 @@ delayMicroseconds:
bnez $v0, .LlongDelay bnez $v0, .LlongDelay
subu $a0, $a2 subu $a0, $a2
.LshortDelay: .LshortDelay: # }
# Run the last busy loop once less than 0xff00 cycles are remaining. # Run the last busy loop once less than 0xff00 cycles are remaining.
sh $0, %lo(TIMER2_VALUE)($v1) # TIMER2_VALUE = 0
# TIMER2_VALUE = 0;
sh $0, %lo(TIMER2_VALUE)($v1)
li $v0, 0 li $v0, 0
.LshortDelayLoop: # while (TIMER2_VALUE < cycles); .LshortDelayLoop:
# while (TIMER2_VALUE < cycles);
nop nop
slt $v0, $v0, $a0 slt $v0, $v0, $a0
bnez $v0, .LshortDelayLoop bnez $v0, .LshortDelayLoop
lhu $v0, %lo(TIMER2_VALUE)($v1) lhu $v0, %lo(TIMER2_VALUE)($v1)
# return;
jr $ra jr $ra
nop nop
@ -254,9 +269,8 @@ delayMicroseconds:
.type delayMicrosecondsBusy, @function .type delayMicrosecondsBusy, @function
delayMicrosecondsBusy: delayMicrosecondsBusy:
# Calculate the approximate number of CPU cycles that need to be burned, # cycles = ((us * 271) + 4) / 8:
# assuming a 33.8688 MHz clock (1 us = 33.8688 = ~33.875 cycles). sll $a1, $a0, 8
sll $a1, $a0, 8 # cycles = ((us * 271) + 4) / 8
sll $a2, $a0, 4 sll $a2, $a0, 4
addu $a1, $a2 addu $a1, $a2
subu $a1, $a0 subu $a1, $a0
@ -266,9 +280,11 @@ delayMicrosecondsBusy:
# Compensate for the overhead of calculating the cycle count and returning. # Compensate for the overhead of calculating the cycle count and returning.
addiu $a0, -(6 + 1 + 2) addiu $a0, -(6 + 1 + 2)
.Lloop: # while (cycles > 0) cycles -= 2 .Lloop:
# while (cycles > 0) cycles -= 2;
bgtz $a0, .Lloop bgtz $a0, .Lloop
addiu $a0, -2 addiu $a0, -2
# return;
jr $ra jr $ra
nop nop