Add DSP emulation interface

Co-Authored-By: PSISP <12768103+psi-rockin@users.noreply.github.com>
This commit is contained in:
wheremyfoodat 2024-02-17 00:39:55 +02:00
parent 3c25be4c63
commit 0a51a80d91
14 changed files with 426 additions and 6 deletions

View file

@ -0,0 +1,21 @@
#include "audio/dsp_core.hpp"
#include "audio/null_core.hpp"
#include "audio/teakra_core.hpp"
std::unique_ptr<Audio::DSPCore> Audio::makeDSPCore(DSPCore::Type type, Memory& mem) {
std::unique_ptr<DSPCore> core;
switch (type) {
case DSPCore::Type::Null: core = std::make_unique<NullDSP>(mem); break;
case DSPCore::Type::Teakra: core = std::make_unique<TeakraDSP>(mem); break;
default:
Helpers::warn("Invalid DSP core selected!");
core = std::make_unique<NullDSP>(mem);
break;
}
mem.setDSPMem(core->getDspMemory());
return core;
}

View file

@ -0,0 +1,129 @@
#include "audio/null_core.hpp"
namespace Audio {
namespace DSPPipeType {
enum : u32 {
Debug = 0,
DMA = 1,
Audio = 2,
Binary = 3,
};
}
void NullDSP::resetAudioPipe() {
// Hardcoded responses for now
// These are DSP DRAM offsets for various variables
// https://www.3dbrew.org/wiki/DSP_Memory_Region
static constexpr std::array<u16, 16> responses = {
0x000F, // Number of responses
0xBFFF, // Frame counter
0x9E92, // Source configs
0x8680, // Source statuses
0xA792, // ADPCM coefficients
0x9430, // DSP configs
0x8400, // DSP status
0x8540, // Final samples
0x9492, // Intermediate mix samples
0x8710, // Compressor
0x8410, // Debug
0xA912, // ??
0xAA12, // ??
0xAAD2, // ??
0xAC52, // Surround sound biquad filter 1
0xAC5C // Surround sound biquad filter 2
};
std::vector<u8>& audioPipe = pipeData[DSPPipeType::Audio];
audioPipe.resize(responses.size() * sizeof(u16));
// Push back every response to the audio pipe
size_t index = 0;
for (auto e : responses) {
audioPipe[index++] = e & 0xff;
audioPipe[index++] = e >> 8;
}
}
void NullDSP::reset() {
for (auto& e : pipeData) e.clear();
// Note: Reset audio pipe AFTER resetting all pipes, otherwise the new data will be yeeted
resetAudioPipe();
}
u16 NullDSP::recvData(u32 regId) {
if (regId != 0) {
Helpers::panic("Audio: invalid register in null frontend");
}
return dspState == DSPState::On;
}
void NullDSP::writeProcessPipe(u32 channel, u32 size, u32 buffer) {
enum class StateChange : u8 {
Initialize = 0,
Shutdown = 1,
Wakeup = 2,
Sleep = 3,
};
switch (channel) {
case DSPPipeType::Audio: {
if (size != 4) {
printf("Invalid size written to DSP Audio Pipe\n");
break;
}
// Get new state
const u8 state = mem.read8(buffer);
if (state > 3) {
log("WriteProcessPipe::Audio: Unknown state change type");
} else {
switch (static_cast<StateChange>(state)) {
case StateChange::Initialize:
// TODO: Other initialization stuff here
dspState = DSPState::On;
resetAudioPipe();
break;
case StateChange::Shutdown:
dspState = DSPState::Off;
break;
default: Helpers::panic("Unimplemented DSP audio pipe state change %d", state);
}
}
break;
}
case DSPPipeType::Binary:
Helpers::warn("Unimplemented write to binary pipe! Size: %d\n", size);
break;
default: log("Audio::NullDSP: Wrote to unimplemented pipe %d\n", channel); break;
}
}
std::vector<u8> NullDSP::readPipe(u32 pipe, u32 peer, u32 size, u32 buffer) {
if (size & 1) Helpers::panic("Tried to read odd amount of bytes from DSP pipe");
if (pipe >= pipeCount || size > 0xffff) {
return {};
}
if (pipe != DSPPipeType::Audio) {
log("Reading from non-audio pipe! This might be broken, might need to check what pipe is being read from and implement writing to it\n");
}
std::vector<u8>& data = pipeData[pipe];
size = std::min<u32>(size, data.size()); // Clamp size to the maximum available data size
if (size == 0) {
return {};
}
// Return "size" bytes from the audio pipe and erase them
std::vector<u8> out(data.begin(), data.begin() + size);
data.erase(data.begin(), data.begin() + size);
return out;
}
} // namespace Audio

View file

@ -0,0 +1,150 @@
#include "audio/teakra_core.hpp"
using namespace Audio;
struct Dsp1 {
// All sizes are in bytes unless otherwise specified
u8 signature[0x100];
u8 magic[4];
u32 size;
u8 codeMemLayout;
u8 dataMemLayout;
u8 pad[3];
u8 specialType;
u8 segmentCount;
u8 flags;
u32 specialStart;
u32 specialSize;
u64 zeroBits;
struct Segment {
u32 offs; // Offset of the segment data
u32 dspAddr; // Start of the segment in 16-bit units
u32 size;
u8 pad[3];
u8 type;
u8 hash[0x20];
};
Segment segments[10];
};
TeakraDSP::TeakraDSP(Memory& mem) : DSPCore(mem), pipeBaseAddr(0), running(false) {
teakra.Reset();
// Set up callbacks for Teakra
Teakra::AHBMCallback ahbm;
ahbm.read8 = [&](u32 addr) -> u8 { return mem.read8(addr); };
ahbm.read16 = [&](u32 addr) -> u16 { return mem.read16(addr); };
ahbm.read32 = [&](u32 addr) -> u32 { return mem.read32(addr); };
ahbm.write8 = [&](u32 addr, u8 value) { mem.write8(addr, value); };
ahbm.write16 = [&](u32 addr, u16 value) { mem.write16(addr, value); };
ahbm.write32 = [&](u32 addr, u32 value) { mem.write32(addr, value); };
teakra.SetAHBMCallback(ahbm);
teakra.SetAudioCallback([=](std::array<s16, 2> sample) {
// NOP for now
});
}
void TeakraDSP::reset() {
teakra.Reset();
running = false;
}
void TeakraDSP::runAudioFrame() {
if (running) {
teakra.Run(16384);
}
}
void TeakraDSP::writeProcessPipe(u32 channel, u32 size, u32 buffer) {
// TODO
}
std::vector<u8> TeakraDSP::readPipe(u32 channel, u32 peer, u32 size, u32 buffer) {
// TODO
return std::vector<u8>();
}
void TeakraDSP::loadComponent(std::vector<u8>& data, u32 programMask, u32 dataMask) {
// TODO: maybe move this to the DSP service
u8* dspCode = teakra.GetDspMemory().data();
u8* dspData = dspCode + 0x40000;
Dsp1 dsp1;
memcpy(&dsp1, data.data(), sizeof(dsp1));
// TODO: verify DSP1 signature
// Load DSP segments to DSP RAM
// TODO: verify hashes
for (unsigned int i = 0; i < dsp1.segmentCount; i++) {
auto& segment = dsp1.segments[i];
u32 addr = segment.dspAddr << 1;
u8* src = data.data() + segment.offs;
u8* dst = nullptr;
switch (segment.type) {
case 0:
case 1: dst = dspCode + addr; break;
default: dst = dspData + addr; break;
}
memcpy(dst, src, segment.size);
}
bool syncWithDsp = dsp1.flags & 0x1;
bool loadSpecialSegment = (dsp1.flags >> 1) & 0x1;
// TODO: how does the special segment work?
if (loadSpecialSegment) {
log("LoadComponent: special segment not supported");
}
running = true;
if (syncWithDsp) {
// Wait for the DSP to reply with 1s in all RECV registers
for (int i = 0; i < 3; i++) {
do {
while (!teakra.RecvDataIsReady(i)) {
runAudioFrame();
}
} while (teakra.RecvData(i) != 1);
}
}
// Retrieve the pipe base address
while (!teakra.RecvDataIsReady(2)) {
runAudioFrame();
}
pipeBaseAddr = teakra.RecvData(2);
}
void TeakraDSP::unloadComponent() {
if (!running) {
Helpers::panic("Audio: unloadComponent called without a running program");
}
// Wait for SEND2 to be ready, then send the shutdown command to the DSP
while (!teakra.SendDataIsEmpty(2)) {
runAudioFrame();
}
teakra.SendData(2, 0x8000);
// Wait for shutdown to be acknowledged
while (!teakra.RecvDataIsReady(2)) {
runAudioFrame();
}
// Read the value and discard it, completing shutdown
teakra.RecvData(2);
running = false;
}

View file

@ -15,7 +15,6 @@ using namespace KernelMemoryTypes;
Memory::Memory(u64& cpuTicks, const EmulatorConfig& config) : cpuTicks(cpuTicks), config(config) {
fcram = new uint8_t[FCRAM_SIZE]();
dspRam = new uint8_t[DSP_RAM_SIZE]();
readTable.resize(totalPageCount, 0);
writeTable.resize(totalPageCount, 0);