Merge branch 'master' into specialized-shaderz

This commit is contained in:
wheremyfoodat 2024-02-28 23:44:33 +02:00
commit 58da6ea8a4
97 changed files with 2168 additions and 501 deletions

View file

@ -21,7 +21,6 @@ CPU::CPU(Memory& mem, Kernel& kernel, Emulator& emu) : mem(mem), emu(emu), sched
void CPU::reset() {
setCPSR(CPSR::UserMode);
setFPSCR(FPSCR::MainThreadDefault);
env.totalTicks = 0;
cp15->reset();
cp15->setTLSBase(VirtualAddrs::TLSBase); // Set cp15 TLS pointer to the main thread's thread-local storage

View file

@ -0,0 +1,52 @@
#include "audio/dsp_core.hpp"
#include "audio/null_core.hpp"
#include "audio/teakra_core.hpp"
#include <algorithm>
#include <cctype>
#include <unordered_map>
std::unique_ptr<Audio::DSPCore> Audio::makeDSPCore(DSPCore::Type type, Memory& mem, Scheduler& scheduler, DSPService& dspService) {
std::unique_ptr<DSPCore> core;
switch (type) {
case DSPCore::Type::Null: core = std::make_unique<NullDSP>(mem, scheduler, dspService); break;
case DSPCore::Type::Teakra: core = std::make_unique<TeakraDSP>(mem, scheduler, dspService); break;
default:
Helpers::warn("Invalid DSP core selected!");
core = std::make_unique<NullDSP>(mem, scheduler, dspService);
break;
}
mem.setDSPMem(core->getDspMemory());
return core;
}
Audio::DSPCore::Type Audio::DSPCore::typeFromString(std::string inString) {
// Transform to lower-case to make the setting case-insensitive
std::transform(inString.begin(), inString.end(), inString.begin(), [](unsigned char c) { return std::tolower(c); });
static const std::unordered_map<std::string, Audio::DSPCore::Type> map = {
{"null", Audio::DSPCore::Type::Null},
{"none", Audio::DSPCore::Type::Null},
{"lle", Audio::DSPCore::Type::Teakra},
{"teakra", Audio::DSPCore::Type::Teakra},
};
if (auto search = map.find(inString); search != map.end()) {
return search->second;
}
printf("Invalid DSP type. Defaulting to null\n");
return Audio::DSPCore::Type::Null;
}
const char* Audio::DSPCore::typeToString(Audio::DSPCore::Type type) {
switch (type) {
case Audio::DSPCore::Type::Null: return "null";
case Audio::DSPCore::Type::Teakra: return "teakra";
default: return "invalid";
}
}

View file

@ -0,0 +1,143 @@
#include "audio/miniaudio_device.hpp"
#include "helpers.hpp"
MiniAudioDevice::MiniAudioDevice() : initialized(false), running(false), samples(nullptr) {}
void MiniAudioDevice::init(Samples& samples, bool safe) {
this->samples = &samples;
running = false;
// Probe for device and available backends and initialize audio
ma_backend backends[ma_backend_null + 1];
uint count = 0;
if (safe) {
backends[0] = ma_backend_null;
count = 1;
} else {
bool found = false;
for (uint i = 0; i <= ma_backend_null; i++) {
ma_backend backend = ma_backend(i);
if (!ma_is_backend_enabled(backend)) {
continue;
}
backends[count++] = backend;
// TODO: Make backend selectable here
found = true;
//count = 1;
//backends[0] = backend;
}
if (!found) {
initialized = false;
Helpers::warn("No valid audio backend found\n");
return;
}
}
if (ma_context_init(backends, count, nullptr, &context) != MA_SUCCESS) {
initialized = false;
Helpers::warn("Unable to initialize audio context");
return;
}
audioDevices.clear();
struct UserContext {
MiniAudioDevice* miniAudio;
ma_device_config& config;
bool found = false;
};
UserContext userContext = {.miniAudio = this, .config = deviceConfig};
ma_context_enumerate_devices(
&context,
[](ma_context* pContext, ma_device_type deviceType, const ma_device_info* pInfo, void* pUserData) -> ma_bool32 {
if (deviceType != ma_device_type_playback) {
return true;
}
UserContext* userContext = reinterpret_cast<UserContext*>(pUserData);
userContext->miniAudio->audioDevices.push_back(pInfo->name);
// TODO: Check if this is the device we want here
userContext->config.playback.pDeviceID = &pInfo->id;
userContext->found = true;
return true;
},
&userContext
);
if (!userContext.found) {
Helpers::warn("MiniAudio: Device not found");
}
deviceConfig = ma_device_config_init(ma_device_type_playback);
// The 3DS outputs s16 stereo audio @ 32768 Hz
deviceConfig.playback.format = ma_format_s16;
deviceConfig.playback.channels = channelCount;
deviceConfig.sampleRate = sampleRate;
//deviceConfig.periodSizeInFrames = 64;
//deviceConfig.periods = 16;
deviceConfig.pUserData = this;
deviceConfig.aaudio.usage = ma_aaudio_usage_game;
deviceConfig.wasapi.noAutoConvertSRC = true;
deviceConfig.dataCallback = [](ma_device* device, void* out, const void* input, ma_uint32 frameCount) {
auto self = reinterpret_cast<MiniAudioDevice*>(device->pUserData);
s16* output = reinterpret_cast<ma_int16*>(out);
// Wait until there's enough samples to pop
while (self->samples->size() < frameCount * channelCount) {
// If audio output is disabled from the emulator thread, make sure that this callback will return and not hang
if (!self->running) {
return;
}
}
self->samples->pop(output, frameCount * channelCount);
};
if (ma_device_init(&context, &deviceConfig, &device) != MA_SUCCESS) {
Helpers::warn("Unable to initialize audio device");
initialized = false;
return;
}
initialized = true;
}
void MiniAudioDevice::start() {
if (!initialized) {
Helpers::warn("MiniAudio device not initialized, won't start");
return;
}
// Ignore the call to start if the device is already running
if (!running) {
if (ma_device_start(&device) == MA_SUCCESS) {
running = true;
} else {
Helpers::warn("Failed to start audio device");
}
}
}
void MiniAudioDevice::stop() {
if (!initialized) {
Helpers::warn("MiniAudio device not initialized, can't start");
return;
}
if (running) {
running = false;
if (ma_device_stop(&device) != MA_SUCCESS) {
Helpers::warn("Failed to stop audio device");
}
}
}

View file

@ -0,0 +1,166 @@
#include "audio/null_core.hpp"
#include "services/dsp.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() {
loaded = false;
for (auto& e : pipeData) {
e.clear();
}
// Note: Reset audio pipe AFTER resetting all pipes, otherwise the new data will be yeeted
resetAudioPipe();
}
void NullDSP::loadComponent(std::vector<u8>& data, u32 programMask, u32 dataMask) {
if (loaded) {
Helpers::warn("Loading DSP component when already loaded");
}
loaded = true;
scheduler.addEvent(Scheduler::EventType::RunDSP, scheduler.currentTimestamp + Audio::cyclesPerFrame);
}
void NullDSP::unloadComponent() {
if (!loaded) {
Helpers::warn("Audio: unloadComponent called without a running program");
}
loaded = false;
scheduler.removeEvent(Scheduler::EventType::RunDSP);
}
void NullDSP::runAudioFrame() {
// Signal audio pipe when an audio frame is done
if (dspState == DSPState::On) [[likely]] {
dspService.triggerPipeEvent(DSPPipeType::Audio);
}
scheduler.addEvent(Scheduler::EventType::RunDSP, scheduler.currentTimestamp + Audio::cyclesPerFrame);
}
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();
dspService.triggerPipeEvent(DSPPipeType::Audio);
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);
// This pipe and interrupt are normally used for requests like AAC decode
dspService.triggerPipeEvent(DSPPipeType::Binary);
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,345 @@
#include "audio/teakra_core.hpp"
#include <algorithm>
#include <chrono>
#include <cstring>
#include <thread>
#include "services/dsp.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, Scheduler& scheduler, DSPService& dspService)
: DSPCore(mem, scheduler, dspService), pipeBaseAddr(0), running(false) {
// Set up callbacks for Teakra
Teakra::AHBMCallback ahbm;
// The AHBM read handlers read from paddrs rather than vaddrs which mem.read8 and the like use
// TODO: When we implement more efficient paddr accesses with a page table or similar, these handlers
// Should be made to properly use it, since this method is hacky and will segfault if given an invalid addr
ahbm.read8 = [&](u32 addr) -> u8 { return mem.getFCRAM()[addr - PhysicalAddrs::FCRAM]; };
ahbm.read16 = [&](u32 addr) -> u16 { return *(u16*)&mem.getFCRAM()[addr - PhysicalAddrs::FCRAM]; };
ahbm.read32 = [&](u32 addr) -> u32 { return *(u32*)&mem.getFCRAM()[addr - PhysicalAddrs::FCRAM]; };
ahbm.write8 = [&](u32 addr, u8 value) { mem.getFCRAM()[addr - PhysicalAddrs::FCRAM] = value; };
ahbm.write16 = [&](u32 addr, u16 value) { *(u16*)&mem.getFCRAM()[addr - PhysicalAddrs::FCRAM] = value; };
ahbm.write32 = [&](u32 addr, u32 value) { *(u32*)&mem.getFCRAM()[addr - PhysicalAddrs::FCRAM] = value; };
teakra.SetAHBMCallback(ahbm);
teakra.SetAudioCallback([](std::array<s16, 2> sample) { /* Do nothing */ });
// Set up event handlers. These handlers forward a hardware interrupt to the DSP service, which is responsible
// For triggering the appropriate DSP kernel events
// Note: It's important not to fire any events if "loaded" is false, ie if we haven't fully loaded a DSP component yet
teakra.SetRecvDataHandler(0, [&]() {
if (loaded) {
dspService.triggerInterrupt0();
}
});
teakra.SetRecvDataHandler(1, [&]() {
if (loaded) {
dspService.triggerInterrupt1();
}
});
auto processPipeEvent = [&](bool dataEvent) {
if (!loaded) {
return;
}
if (dataEvent) {
signalledData = true;
} else {
if ((teakra.GetSemaphore() & 0x8000) == 0) {
return;
}
signalledSemaphore = true;
}
if (signalledSemaphore && signalledData) {
signalledSemaphore = signalledData = false;
u16 slot = teakra.RecvData(2);
u16 side = slot & 1;
u16 pipe = slot / 2;
if (side != static_cast<u16>(PipeDirection::DSPtoCPU)) {
return;
}
if (pipe == 0) {
Helpers::warn("Pipe event for debug pipe: Should be ignored and the data should be flushed");
} else {
dspService.triggerPipeEvent(pipe);
}
}
};
teakra.SetRecvDataHandler(2, [processPipeEvent]() { processPipeEvent(true); });
teakra.SetSemaphoreHandler([processPipeEvent]() { processPipeEvent(false); });
}
void TeakraDSP::reset() {
teakra.Reset();
running = false;
loaded = false;
signalledData = signalledSemaphore = false;
audioFrameIndex = 0;
}
void TeakraDSP::setAudioEnabled(bool enable) {
if (audioEnabled != enable) {
audioEnabled = enable;
// Set the appropriate audio callback for Teakra
if (audioEnabled) {
teakra.SetAudioCallback([this](std::array<s16, 2> sample) {
audioFrame[audioFrameIndex++] = sample[0];
audioFrame[audioFrameIndex++] = sample[1];
// Push our samples at the end of an audio frame
if (audioFrameIndex >= audioFrame.size()) {
audioFrameIndex -= audioFrame.size();
// Wait until we've actually got room to do so
while (sampleBuffer.size() + 2 > sampleBuffer.Capacity()) {
std::this_thread::sleep_for(std::chrono::milliseconds{1});
}
sampleBuffer.push(audioFrame.data(), audioFrame.size());
}
});
} else {
teakra.SetAudioCallback([](std::array<s16, 2> sample) { /* Do nothing */ });
}
}
}
// https://github.com/citra-emu/citra/blob/master/src/audio_core/lle/lle.cpp
void TeakraDSP::writeProcessPipe(u32 channel, u32 size, u32 buffer) {
size &= 0xffff;
PipeStatus status = getPipeStatus(channel, PipeDirection::CPUtoDSP);
bool needUpdate = false; // Do we need to update the pipe status and catch up Teakra?
std::vector<u8> data;
data.reserve(size);
// Read data to write
for (int i = 0; i < size; i++) {
const u8 byte = mem.read8(buffer + i);
data.push_back(byte);
}
u8* dataPointer = data.data();
while (size != 0) {
if (status.isFull()) {
Helpers::warn("Teakra: Writing to full pipe");
}
// Calculate begin/end/size for write
const u16 writeEnd = status.isWrapped() ? (status.readPointer & PipeStatus::pointerMask) : status.byteSize;
const u16 writeBegin = status.writePointer & PipeStatus::pointerMask;
const u16 writeSize = std::min<u16>(u16(size), writeEnd - writeBegin);
if (writeEnd <= writeBegin) [[unlikely]] {
Helpers::warn("Teakra: Writing to pipe but end <= start");
}
// Write data to pipe, increment write and buffer pointers, decrement size
std::memcpy(getDataPointer(status.address * 2 + writeBegin), dataPointer, writeSize);
dataPointer += writeSize;
status.writePointer += writeSize;
size -= writeSize;
if ((status.writePointer & PipeStatus::pointerMask) > status.byteSize) [[unlikely]] {
Helpers::warn("Teakra: Writing to pipe but write > size");
}
if ((status.writePointer & PipeStatus::pointerMask) == status.byteSize) {
status.writePointer &= PipeStatus::wrapBit;
status.writePointer ^= PipeStatus::wrapBit;
}
needUpdate = true;
}
if (needUpdate) {
updatePipeStatus(status);
while (!teakra.SendDataIsEmpty(2)) {
runSlice();
}
teakra.SendData(2, status.slot);
}
}
std::vector<u8> TeakraDSP::readPipe(u32 channel, u32 peer, u32 size, u32 buffer) {
size &= 0xffff;
PipeStatus status = getPipeStatus(channel, PipeDirection::DSPtoCPU);
std::vector<u8> pipeData(size);
u8* dataPointer = pipeData.data();
bool needUpdate = false; // Do we need to update the pipe status and catch up Teakra?
while (size != 0) {
if (status.isEmpty()) [[unlikely]] {
Helpers::warn("Teakra: Reading from empty pipe");
return pipeData;
}
// Read as many bytes as possible
const u16 readEnd = status.isWrapped() ? status.byteSize : (status.writePointer & PipeStatus::pointerMask);
const u16 readBegin = status.readPointer & PipeStatus::pointerMask;
const u16 readSize = std::min<u16>(u16(size), readEnd - readBegin);
// Copy bytes to the output vector, increment the read and vector pointers and decrement the size appropriately
std::memcpy(dataPointer, getDataPointer(status.address * 2 + readBegin), readSize);
dataPointer += readSize;
status.readPointer += readSize;
size -= readSize;
if ((status.readPointer & PipeStatus::pointerMask) > status.byteSize) [[unlikely]] {
Helpers::warn("Teakra: Reading from pipe but read > size");
}
if ((status.readPointer & PipeStatus::pointerMask) == status.byteSize) {
status.readPointer &= PipeStatus::wrapBit;
status.readPointer ^= PipeStatus::wrapBit;
}
needUpdate = true;
}
if (needUpdate) {
updatePipeStatus(status);
while (!teakra.SendDataIsEmpty(2)) {
runSlice();
}
teakra.SendData(2, status.slot);
}
return pipeData;
}
void TeakraDSP::loadComponent(std::vector<u8>& data, u32 programMask, u32 dataMask) {
// TODO: maybe move this to the DSP service
if (loaded) {
Helpers::warn("Loading DSP component when already loaded");
return;
}
teakra.Reset();
running = true;
u8* dspCode = teakra.GetDspMemory().data();
u8* dspData = dspCode + 0x40000;
Dsp1 dsp1;
std::memcpy(&dsp1, data.data(), sizeof(dsp1));
// TODO: verify DSP1 signature
// Load DSP segments to DSP RAM
// TODO: verify hashes
for (uint 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;
}
std::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");
}
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)) {
runSlice();
}
} while (teakra.RecvData(i) != 1);
}
}
// Retrieve the pipe base address
while (!teakra.RecvDataIsReady(2)) {
runSlice();
}
pipeBaseAddr = teakra.RecvData(2);
// Schedule next DSP event
scheduler.addEvent(Scheduler::EventType::RunDSP, scheduler.currentTimestamp + Audio::lleSlice * 2);
loaded = true;
}
void TeakraDSP::unloadComponent() {
if (!loaded) {
Helpers::warn("Audio: unloadComponent called without a running program");
return;
}
loaded = false;
// Stop scheduling DSP events
scheduler.removeEvent(Scheduler::EventType::RunDSP);
// Wait for SEND2 to be ready, then send the shutdown command to the DSP
while (!teakra.SendDataIsEmpty(2)) {
runSlice();
}
teakra.SendData(2, 0x8000);
// Wait for shutdown to be acknowledged
while (!teakra.RecvDataIsReady(2)) {
runSlice();
}
// Read the value and discard it, completing shutdown
teakra.RecvData(2);
running = false;
}

View file

@ -12,9 +12,9 @@ const char* Kernel::resetTypeToString(u32 type) {
}
}
Handle Kernel::makeEvent(ResetType resetType) {
Handle Kernel::makeEvent(ResetType resetType, Event::CallbackType callback) {
Handle ret = makeObject(KernelObjectType::Event);
objects[ret].data = new Event(resetType);
objects[ret].data = new Event(resetType, callback);
return ret;
}
@ -42,8 +42,13 @@ bool Kernel::signalEvent(Handle handle) {
event->fired = false;
}
}
rescheduleThreads();
// Run the callback for events that require a special callback
if (event->callback != Event::CallbackType::None) [[unlikely]] {
runEventCallback(event->callback);
}
return true;
}
@ -230,4 +235,12 @@ void Kernel::waitSynchronizationN() {
} else {
Helpers::panic("WaitSynchronizationN with waitAll");
}
}
void Kernel::runEventCallback(Event::CallbackType callback) {
switch (callback) {
case Event::CallbackType::None: break;
case Event::CallbackType::DSPSemaphore: serviceManager.getDSP().onSemaphoreEventSignal(); break;
default: Helpers::panic("Unimplemented special callback for kernel event!"); break;
}
}

View file

@ -32,7 +32,7 @@ bool NCCH::loadFromHeader(Crypto::AESEngine &aesEngine, IOFile& file, const FSIn
size = u64(*(u32*)&header[0x104]) * mediaUnit; // TODO: Maybe don't type pun because big endian will break
exheaderSize = *(u32*)&header[0x180];
const u64 programID = *(u64*)&header[0x118];
programID = *(u64*)&header[0x118];
// Read NCCH flags
secondaryKeySlot = header[0x188 + 3];

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);
@ -525,4 +524,14 @@ void Memory::copySharedFont(u8* pointer) {
auto fonts = cmrc::ConsoleFonts::get_filesystem();
auto font = fonts.open("CitraSharedFontUSRelocated.bin");
std::memcpy(pointer, font.begin(), font.size());
}
std::optional<u64> Memory::getProgramID() {
auto cxi = getCXI();
if (cxi) {
return cxi->programID;
}
return std::nullopt;
}

View file

@ -84,7 +84,7 @@ void APTService::appletUtility(u32 messagePointer) {
u32 outputSize = mem.read32(messagePointer + 12);
u32 inputPointer = mem.read32(messagePointer + 20);
log("APT::AppletUtility(utility = %d, input size = %x, output size = %x, inputPointer = %08X) (Stubbed)\n", utility, inputSize, outputSize,
log("APT::AppletUtility(utility = %d, input size = %x, output size = %x, inputPointer = %08X)\n", utility, inputSize, outputSize,
inputPointer);
std::vector<u8> out(outputSize);
@ -218,7 +218,7 @@ void APTService::initialize(u32 messagePointer) {
}
void APTService::inquireNotification(u32 messagePointer) {
log("APT::InquireNotification (STUBBED TO RETURN NONE)\n");
log("APT::InquireNotification\n");
mem.write32(messagePointer, IPC::responseHeader(0xB, 2, 0));
mem.write32(messagePointer + 4, Result::Success);

View file

@ -31,13 +31,8 @@ namespace Result {
}
void DSPService::reset() {
for (auto& e : pipeData)
e.clear();
// Note: Reset audio pipe AFTER resetting all pipes, otherwise the new data will be yeeted
resetAudioPipe();
totalEventCount = 0;
dspState = DSPState::Off;
semaphoreMask = 0;
semaphoreEvent = std::nullopt;
interrupt0 = std::nullopt;
@ -48,40 +43,6 @@ void DSPService::reset() {
}
}
void DSPService::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 DSPService::handleSyncRequest(u32 messagePointer) {
const u32 command = mem.read32(messagePointer);
switch (command) {
@ -117,8 +78,16 @@ void DSPService::loadComponent(u32 messagePointer) {
u32 size = mem.read32(messagePointer + 4);
u32 programMask = mem.read32(messagePointer + 8);
u32 dataMask = mem.read32(messagePointer + 12);
u32 buffer = mem.read32(messagePointer + 20);
std::vector<u8> data(size);
for (u32 i = 0; i < size; i++) {
data[i] = mem.read8(buffer + i);
}
log("DSP::LoadComponent (size = %08X, program mask = %X, data mask = %X\n", size, programMask, dataMask);
dsp->loadComponent(data, programMask, dataMask);
mem.write32(messagePointer, IPC::responseHeader(0x11, 2, 2));
mem.write32(messagePointer + 4, Result::Success);
mem.write32(messagePointer + 8, 1); // Component loaded
@ -128,32 +97,12 @@ void DSPService::loadComponent(u32 messagePointer) {
void DSPService::unloadComponent(u32 messagePointer) {
log("DSP::UnloadComponent\n");
dsp->unloadComponent();
mem.write32(messagePointer, IPC::responseHeader(0x12, 1, 0));
mem.write32(messagePointer + 4, Result::Success);
}
std::vector<u8> DSPService::readPipe(u32 pipe, u32 size) {
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, u32(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;
}
void DSPService::readPipeIfPossible(u32 messagePointer) {
u32 channel = mem.read32(messagePointer + 4);
u32 peer = mem.read32(messagePointer + 8);
@ -162,7 +111,7 @@ void DSPService::readPipeIfPossible(u32 messagePointer) {
log("DSP::ReadPipeIfPossible (channel = %d, peer = %d, size = %04X, buffer = %08X)\n", channel, peer, size, buffer);
mem.write32(messagePointer, IPC::responseHeader(0x10, 2, 2));
std::vector<u8> data = readPipe(channel, size);
std::vector<u8> data = dsp->readPipe(channel, peer, size, buffer);
for (uint i = 0; i < data.size(); i++) {
mem.write8(buffer + i, data[i]);
}
@ -176,22 +125,22 @@ void DSPService::recvData(u32 messagePointer) {
log("DSP::RecvData (register = %d)\n", registerIndex);
if (registerIndex != 0) Helpers::panic("Unknown register in DSP::RecvData");
// Return 0 if the DSP is running, otherwise 1
const u16 ret = dspState == DSPState::On ? 0 : 1;
const u16 data = dsp->recvData(registerIndex);
mem.write32(messagePointer, IPC::responseHeader(0x01, 2, 0));
mem.write32(messagePointer + 4, Result::Success);
mem.write16(messagePointer + 8, ret);
mem.write16(messagePointer + 8, data);
}
void DSPService::recvDataIsReady(u32 messagePointer) {
const u32 registerIndex = mem.read32(messagePointer + 4);
log("DSP::RecvDataIsReady (register = %d)\n", registerIndex);
if (registerIndex != 0) Helpers::panic("Unknown register in DSP::RecvDataIsReady");
bool isReady = dsp->recvDataIsReady(registerIndex);
mem.write32(messagePointer, IPC::responseHeader(0x02, 2, 0));
mem.write32(messagePointer + 4, Result::Success);
mem.write32(messagePointer + 8, 1); // Always return that the register is ready for now
mem.write32(messagePointer + 8, isReady ? 1 : 0);
}
DSPService::DSPEvent& DSPService::getEventRef(u32 type, u32 pipe) {
@ -236,7 +185,6 @@ void DSPService::registerInterruptEvents(u32 messagePointer) {
mem.write32(messagePointer + 4, Result::Success);
totalEventCount++;
kernel.signalEvent(eventHandle);
}
}
}
@ -253,7 +201,7 @@ void DSPService::getSemaphoreEventHandle(u32 messagePointer) {
log("DSP::GetSemaphoreEventHandle\n");
if (!semaphoreEvent.has_value()) {
semaphoreEvent = kernel.makeEvent(ResetType::OneShot);
semaphoreEvent = kernel.makeEvent(ResetType::OneShot, Event::CallbackType::DSPSemaphore);
}
mem.write32(messagePointer, IPC::responseHeader(0x16, 1, 2));
@ -267,6 +215,7 @@ void DSPService::setSemaphore(u32 messagePointer) {
const u16 value = mem.read16(messagePointer + 4);
log("DSP::SetSemaphore(value = %04X)\n", value);
dsp->setSemaphore(value);
mem.write32(messagePointer, IPC::responseHeader(0x7, 1, 0));
mem.write32(messagePointer + 4, Result::Success);
}
@ -275,6 +224,9 @@ void DSPService::setSemaphoreMask(u32 messagePointer) {
const u16 mask = mem.read16(messagePointer + 4);
log("DSP::SetSemaphoreMask(mask = %04X)\n", mask);
dsp->setSemaphoreMask(mask);
semaphoreMask = mask;
mem.write32(messagePointer, IPC::responseHeader(0x17, 1, 0));
mem.write32(messagePointer + 4, Result::Success);
}
@ -285,51 +237,7 @@ void DSPService::writeProcessPipe(u32 messagePointer) {
const u32 buffer = mem.read32(messagePointer + 16);
log("DSP::writeProcessPipe (channel = %d, size = %X, buffer = %08X)\n", channel, size, 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("DSP: Wrote to unimplemented pipe %d\n", channel);
break;
}
dsp->writeProcessPipe(channel, size, buffer);
mem.write32(messagePointer, IPC::responseHeader(0xD, 1, 0));
mem.write32(messagePointer + 4, Result::Success);
}
@ -354,12 +262,26 @@ void DSPService::invalidateDCache(u32 messagePointer) {
mem.write32(messagePointer + 4, Result::Success);
}
void DSPService::signalEvents() {
for (const DSPEvent& e : pipeEvents) {
if (e.has_value()) { kernel.signalEvent(e.value()); }
void DSPService::triggerPipeEvent(int index) {
if (index < pipeCount && pipeEvents[index].has_value()) {
kernel.signalEvent(*pipeEvents[index]);
}
}
if (semaphoreEvent.has_value()) { kernel.signalEvent(semaphoreEvent.value()); }
if (interrupt0.has_value()) { kernel.signalEvent(interrupt0.value()); }
if (interrupt1.has_value()) { kernel.signalEvent(interrupt1.value()); }
void DSPService::triggerSemaphoreEvent() {
if (semaphoreEvent.has_value()) {
kernel.signalEvent(*semaphoreEvent);
}
}
void DSPService::triggerInterrupt0() {
if (interrupt0.has_value()) {
kernel.signalEvent(*interrupt0);
}
}
void DSPService::triggerInterrupt1() {
if (interrupt1.has_value()) {
kernel.signalEvent(*interrupt1);
}
}

View file

@ -123,10 +123,6 @@ void GPUService::registerInterruptRelayQueue(u32 messagePointer) {
}
void GPUService::requestInterrupt(GPUInterrupt type) {
// HACK: Signal DSP events on GPU interrupt for now until we have the DSP since games need DSP events
// Maybe there's a better alternative?
kernel.signalDSPEvents();
if (sharedMem == nullptr) [[unlikely]] { // Shared memory hasn't been set up yet
return;
}
@ -289,7 +285,7 @@ void GPUService::setBufferSwap(u32 messagePointer) {
info.displayFb = mem.read32(messagePointer + 28); // Selects either framebuffer A or B
log("GSP::GPU::SetBufferSwap\n");
Helpers::panic("Untested GSP::GPU::SetBufferSwap call");
Helpers::warn("Untested GSP::GPU::SetBufferSwap call");
setBufferSwapImpl(screenId, info);
mem.write32(messagePointer, IPC::responseHeader(0x05, 1, 0));
@ -529,4 +525,4 @@ void GPUService::importDisplayCaptureInfo(u32 messagePointer) {
mem.write32(messagePointer + 28, bottomScreenCapture.rightFramebuffer);
mem.write32(messagePointer + 32, bottomScreenCapture.format);
mem.write32(messagePointer + 36, bottomScreenCapture.stride);
}
}

View file

@ -37,7 +37,8 @@ void NwmUdsService::initializeWithVersion(u32 messagePointer) {
initialized = true;
mem.write32(messagePointer + 4, Result::Success);
// Stubbed to fail temporarily, since some games will break trying to establish networks otherwise
mem.write32(messagePointer + 4, Result::FailurePlaceholder);
mem.write32(messagePointer + 8, 0);
mem.write32(messagePointer + 12, eventHandle.value());
}