diff --git a/.gitmodules b/.gitmodules index f1e8f469..7d234ac6 100644 --- a/.gitmodules +++ b/.gitmodules @@ -55,3 +55,6 @@ [submodule "third_party/libuv"] path = third_party/libuv url = https://github.com/libuv/libuv +[submodule "third_party/teakra"] + path = third_party/teakra + url = https://github.com/wwylele/teakra diff --git a/CMakeLists.txt b/CMakeLists.txt index 456d6513..2c0ec255 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -89,6 +89,7 @@ include_directories(third_party/toml11) include_directories(third_party/glm) add_subdirectory(third_party/cmrc) +add_subdirectory(third_party/teakra EXCLUDE_FROM_ALL) set(BOOST_ROOT "${CMAKE_SOURCE_DIR}/third_party/boost") set(Boost_INCLUDE_DIR "${CMAKE_SOURCE_DIR}/third_party/boost") @@ -191,6 +192,7 @@ set(FS_SOURCE_FILES src/core/fs/archive_self_ncch.cpp src/core/fs/archive_save_d set(APPLET_SOURCE_FILES src/core/applets/applet.cpp src/core/applets/mii_selector.cpp src/core/applets/software_keyboard.cpp src/core/applets/applet_manager.cpp src/core/applets/error_applet.cpp ) +set(AUDIO_SOURCE_FILES src/core/audio/dsp_core.cpp src/core/audio/null_core.cpp src/core/audio/teakra_core.cpp) set(RENDERER_SW_SOURCE_FILES src/core/renderer_sw/renderer_sw.cpp) # Frontend source files @@ -247,6 +249,7 @@ set(HEADER_FILES include/emulator.hpp include/helpers.hpp include/termcolor.hpp include/services/amiibo_device.hpp include/services/nfc_types.hpp include/swap.hpp include/services/csnd.hpp include/services/nwm_uds.hpp include/fs/archive_system_save_data.hpp include/lua_manager.hpp include/memory_mapped_file.hpp include/hydra_icon.hpp include/PICA/dynapica/shader_rec_emitter_arm64.hpp include/scheduler.hpp include/applets/error_applet.hpp + include/audio/dsp_core.hpp include/audio/null_core.hpp include/audio/teakra_core.hpp ) cmrc_add_resource_library( @@ -299,6 +302,7 @@ source_group("Source Files\\Core\\Loader" FILES ${LOADER_SOURCE_FILES}) source_group("Source Files\\Core\\Services" FILES ${SERVICE_SOURCE_FILES}) source_group("Source Files\\Core\\Applets" FILES ${APPLET_SOURCE_FILES}) source_group("Source Files\\Core\\PICA" FILES ${PICA_SOURCE_FILES}) +source_group("Source Files\\Core\\Audio" FILES ${AUDIO_SOURCE_FILES}) source_group("Source Files\\Core\\Software Renderer" FILES ${RENDERER_SW_SOURCE_FILES}) source_group("Source Files\\Third Party" FILES ${THIRD_PARTY_SOURCE_FILES}) @@ -397,7 +401,7 @@ endif() source_group("Header Files\\Core" FILES ${HEADER_FILES}) set(ALL_SOURCES ${SOURCE_FILES} ${FRONTEND_SOURCE_FILES} ${FS_SOURCE_FILES} ${CRYPTO_SOURCE_FILES} ${KERNEL_SOURCE_FILES} ${LOADER_SOURCE_FILES} ${SERVICE_SOURCE_FILES} ${APPLET_SOURCE_FILES} ${RENDERER_SW_SOURCE_FILES} ${PICA_SOURCE_FILES} ${THIRD_PARTY_SOURCE_FILES} - ${HEADER_FILES} ${FRONTEND_HEADER_FILES}) + ${AUDIO_SOURCE_FILES} ${HEADER_FILES} ${FRONTEND_HEADER_FILES}) if(ENABLE_OPENGL) # Add the OpenGL source files to ALL_SOURCES @@ -429,7 +433,7 @@ if(ENABLE_LTO OR ENABLE_USER_BUILD) set_target_properties(Alber PROPERTIES INTERPROCEDURAL_OPTIMIZATION TRUE) endif() -target_link_libraries(Alber PRIVATE dynarmic cryptopp glad resources_console_fonts) +target_link_libraries(Alber PRIVATE dynarmic cryptopp glad resources_console_fonts teakra) if(NOT ANDROID) target_link_libraries(Alber PRIVATE SDL2-static) diff --git a/include/audio/dsp_core.hpp b/include/audio/dsp_core.hpp new file mode 100644 index 00000000..3f1768ff --- /dev/null +++ b/include/audio/dsp_core.hpp @@ -0,0 +1,55 @@ +#pragma once +#include +#include +#include +#include +#include +#include + +#include "helpers.hpp" +#include "logger.hpp" +#include "scheduler.hpp" + +// The DSP core must have access to the DSP service to be able to trigger interrupts properly +class DSPService; +class Memory; + +namespace Audio { + // There are 160 stereo samples in 1 audio frame, so 320 samples total + static constexpr u64 samplesInFrame = 160; + // 1 frame = 4096 DSP cycles = 8192 ARM11 cycles + static constexpr u64 cyclesPerFrame = samplesInFrame * 8192; + // For LLE DSP cores, we run the DSP for N cycles at a time, every N*2 arm11 cycles since the ARM11 runs twice as fast + static constexpr u64 lleSlice = 16384; + + class DSPCore { + protected: + Memory& mem; + Scheduler& scheduler; + DSPService& dspService; + + MAKE_LOG_FUNCTION(log, dspLogger) + + public: + enum class Type { Null, Teakra }; + DSPCore(Memory& mem, Scheduler& scheduler, DSPService& dspService) : mem(mem), scheduler(scheduler), dspService(dspService) {} + + virtual void reset() = 0; + virtual void runAudioFrame() = 0; + virtual u8* getDspMemory() = 0; + + virtual u16 recvData(u32 regId) = 0; + virtual bool recvDataIsReady(u32 regId) = 0; + virtual void setSemaphore(u16 value) = 0; + virtual void writeProcessPipe(u32 channel, u32 size, u32 buffer) = 0; + virtual std::vector readPipe(u32 channel, u32 peer, u32 size, u32 buffer) = 0; + virtual void loadComponent(std::vector& data, u32 programMask, u32 dataMask) = 0; + virtual void unloadComponent() = 0; + virtual void setSemaphoreMask(u16 value) = 0; + + static Audio::DSPCore::Type typeFromString(std::string inString); + static const char* typeToString(Audio::DSPCore::Type type); + }; + + std::unique_ptr makeDSPCore(DSPCore::Type type, Memory& mem, Scheduler& scheduler, DSPService& dspService); +} // namespace Audio \ No newline at end of file diff --git a/include/audio/null_core.hpp b/include/audio/null_core.hpp new file mode 100644 index 00000000..136a76ac --- /dev/null +++ b/include/audio/null_core.hpp @@ -0,0 +1,45 @@ +#pragma once +#include + +#include "audio/dsp_core.hpp" +#include "memory.hpp" + +namespace Audio { + class NullDSP : public DSPCore { + enum class DSPState : u32 { + Off, + On, + Slep, + }; + + // Number of DSP pipes + static constexpr size_t pipeCount = 8; + DSPState dspState; + + std::array, pipeCount> pipeData; // The data of each pipe + std::array dspRam; + + void resetAudioPipe(); + bool loaded = false; // Have we loaded a component? + + public: + NullDSP(Memory& mem, Scheduler& scheduler, DSPService& dspService) : DSPCore(mem, scheduler, dspService) {} + + void reset() override; + void runAudioFrame() override; + + u8* getDspMemory() override { return dspRam.data(); } + + u16 recvData(u32 regId) override; + bool recvDataIsReady(u32 regId) override { return true; } // Treat data as always ready + void writeProcessPipe(u32 channel, u32 size, u32 buffer) override; + std::vector readPipe(u32 channel, u32 peer, u32 size, u32 buffer) override; + + // NOPs for null DSP core + void loadComponent(std::vector& data, u32 programMask, u32 dataMask) override; + void unloadComponent() override; + void setSemaphore(u16 value) override {} + void setSemaphoreMask(u16 value) override {} + }; + +} // namespace Audio \ No newline at end of file diff --git a/include/audio/teakra_core.hpp b/include/audio/teakra_core.hpp new file mode 100644 index 00000000..57db0e4a --- /dev/null +++ b/include/audio/teakra_core.hpp @@ -0,0 +1,99 @@ +#pragma once +#include "audio/dsp_core.hpp" +#include "memory.hpp" +#include "swap.hpp" +#include "teakra/teakra.h" + +namespace Audio { + class TeakraDSP : public DSPCore { + Teakra::Teakra teakra; + u32 pipeBaseAddr; + bool running; // Is the DSP running? + bool loaded; // Have we finished loading a binary with LoadComponent? + + // Get a pointer to a data memory address + u8* getDataPointer(u32 address) { return getDspMemory() + Memory::DSP_DATA_MEMORY_OFFSET + address; } + + enum class PipeDirection { + DSPtoCPU = 0, + CPUtoDSP = 1, + }; + + // A lot of Teakra integration code, especially pipe stuff is based on Citra's integration here: + // https://github.com/citra-emu/citra/blob/master/src/audio_core/lle/lle.cpp + struct PipeStatus { + // All addresses and sizes here refer to byte values, NOT 16-bit values. + u16_le address; + u16_le byteSize; + u16_le readPointer; + u16_le writePointer; + u8 slot; + u8 flags; + + static constexpr u16 wrapBit = 0x8000; + static constexpr u16 pointerMask = 0x7FFF; + + bool isFull() const { return (readPointer ^ writePointer) == wrapBit; } + bool isEmpty() const { return (readPointer ^ writePointer) == 0; } + + // isWrapped: Are read and write pointers in different memory passes. + // true: xxxx]----[xxxx (data is wrapping around the end of memory) + // false: ----[xxxx]---- + bool isWrapped() const { return (readPointer ^ writePointer) >= wrapBit; } + }; + static_assert(sizeof(PipeStatus) == 10, "Teakra: Pipe Status size is wrong"); + static constexpr u8 pipeToSlotIndex(u8 pipe, PipeDirection direction) { return (pipe * 2) + u8(direction); } + + PipeStatus getPipeStatus(u8 pipe, PipeDirection direction) { + PipeStatus ret; + const u8 index = pipeToSlotIndex(pipe, direction); + + std::memcpy(&ret, getDataPointer(pipeBaseAddr * 2 + index * sizeof(PipeStatus)), sizeof(PipeStatus)); + return ret; + } + + void updatePipeStatus(const PipeStatus& status) { + u8 slot = status.slot; + u8* statusAddress = getDataPointer(pipeBaseAddr * 2 + slot * sizeof(PipeStatus)); + + if (slot % 2 == 0) { + std::memcpy(statusAddress + 4, &status.readPointer, sizeof(u16)); + } else { + std::memcpy(statusAddress + 6, &status.writePointer, sizeof(u16)); + } + } + + bool signalledData; + bool signalledSemaphore; + + // Run 1 slice of DSP instructions + void runSlice() { + if (running) { + teakra.Run(Audio::lleSlice); + } + } + + public: + TeakraDSP(Memory& mem, Scheduler& scheduler, DSPService& dspService); + + void reset() override; + + // Run 1 slice of DSP instructions and schedule the next audio frame + void runAudioFrame() override { + runSlice(); + scheduler.addEvent(Scheduler::EventType::RunDSP, scheduler.currentTimestamp + Audio::lleSlice * 2); + } + + u8* getDspMemory() override { return teakra.GetDspMemory().data(); } + + u16 recvData(u32 regId) override { return teakra.RecvData(regId); } + bool recvDataIsReady(u32 regId) override { return teakra.RecvDataIsReady(regId); } + void setSemaphore(u16 value) override { teakra.SetSemaphore(value); } + void setSemaphoreMask(u16 value) override { teakra.MaskSemaphore(value); } + + void writeProcessPipe(u32 channel, u32 size, u32 buffer) override; + std::vector readPipe(u32 channel, u32 peer, u32 size, u32 buffer) override; + void loadComponent(std::vector& data, u32 programMask, u32 dataMask) override; + void unloadComponent() override; + }; +} // namespace Audio diff --git a/include/config.hpp b/include/config.hpp index 155f5961..e5c10f4b 100644 --- a/include/config.hpp +++ b/include/config.hpp @@ -1,6 +1,7 @@ #pragma once #include +#include "audio/dsp_core.hpp" #include "renderer.hpp" // Remember to initialize every field here to its default value otherwise bad things will happen @@ -15,6 +16,7 @@ struct EmulatorConfig { bool shaderJitEnabled = shaderJitDefault; bool discordRpcEnabled = false; RendererType rendererType = RendererType::OpenGL; + Audio::DSPCore::Type dspType = Audio::DSPCore::Type::Null; bool sdCardInserted = true; bool sdWriteProtected = false; diff --git a/include/emulator.hpp b/include/emulator.hpp index d3377f6c..207bef46 100644 --- a/include/emulator.hpp +++ b/include/emulator.hpp @@ -2,10 +2,12 @@ #include #include +#include #include #include #include "PICA/gpu.hpp" +#include "audio/dsp_core.hpp" #include "cheats.hpp" #include "config.hpp" #include "cpu.hpp" @@ -41,9 +43,11 @@ class Emulator { GPU gpu; Memory memory; Kernel kernel; + std::unique_ptr dsp; + Scheduler scheduler; + Crypto::AESEngine aesEngine; Cheats cheats; - Scheduler scheduler; // Variables to keep track of whether the user is controlling the 3DS analog stick with their keyboard // This is done so when a gamepad is connected, we won't automatically override the 3DS analog stick settings with the gamepad's state diff --git a/include/kernel/kernel.hpp b/include/kernel/kernel.hpp index e78a588a..fc7fe3f3 100644 --- a/include/kernel/kernel.hpp +++ b/include/kernel/kernel.hpp @@ -66,15 +66,20 @@ class Kernel { Handle makeMemoryBlock(u32 addr, u32 size, u32 myPermission, u32 otherPermission); public: - Handle makeEvent(ResetType resetType); // Needs to be public to be accessible to the APT/HID services - Handle makeMutex(bool locked = false); // Needs to be public to be accessible to the APT/DSP services - Handle makeSemaphore(u32 initialCount, u32 maximumCount); // Needs to be public to be accessible to the service manager port + // Needs to be public to be accessible to the APT/HID services + Handle makeEvent(ResetType resetType, Event::CallbackType callback = Event::CallbackType::None); + // Needs to be public to be accessible to the APT/DSP services + Handle makeMutex(bool locked = false); + // Needs to be public to be accessible to the service manager port + Handle makeSemaphore(u32 initialCount, u32 maximumCount); Handle makeTimer(ResetType resetType); void pollTimers(); // Signals an event, returns true on success or false if the event does not exist bool signalEvent(Handle e); - + // Run the callback for "special" events that have callbacks + void runEventCallback(Event::CallbackType callback); + void clearEvent(Handle e) { KernelObject* object = getObject(e, KernelObjectType::Event); if (object != nullptr) { @@ -240,6 +245,5 @@ public: ServiceManager& getServiceManager() { return serviceManager; } void sendGPUInterrupt(GPUInterrupt type) { serviceManager.sendGPUInterrupt(type); } - void signalDSPEvents() { serviceManager.signalDSPEvents(); } void clearInstructionCache(); }; \ No newline at end of file diff --git a/include/kernel/kernel_types.hpp b/include/kernel/kernel_types.hpp index 01af4bd9..a68ef8d5 100644 --- a/include/kernel/kernel_types.hpp +++ b/include/kernel/kernel_types.hpp @@ -62,11 +62,19 @@ struct Process { }; struct Event { + // Some events (for now, only the DSP semaphore events) need to execute a callback when signalled + // This enum stores what kind of callback they should execute + enum class CallbackType : u32 { + None, DSPSemaphore, + }; + u64 waitlist; // A bitfield where each bit symbolizes if the thread with thread with the corresponding index is waiting on the event ResetType resetType = ResetType::OneShot; + CallbackType callback = CallbackType::None; bool fired = false; Event(ResetType resetType) : resetType(resetType), waitlist(0) {} + Event(ResetType resetType, CallbackType cb) : resetType(resetType), waitlist(0), callback(cb) {} }; struct Port { diff --git a/include/logger.hpp b/include/logger.hpp index e021a685..4fc521b6 100644 --- a/include/logger.hpp +++ b/include/logger.hpp @@ -36,6 +36,7 @@ namespace Log { static Logger gpuLogger; static Logger rendererLogger; static Logger shaderJITLogger; + static Logger dspLogger; // Service loggers static Logger acLogger; diff --git a/include/memory.hpp b/include/memory.hpp index 640ae5f0..e2716e2a 100644 --- a/include/memory.hpp +++ b/include/memory.hpp @@ -100,8 +100,8 @@ namespace KernelMemoryTypes { class Memory { u8* fcram; - u8* dspRam; - u8* vram; // Provided to the memory class by the GPU class + u8* dspRam; // Provided to us by Audio + u8* vram; // Provided to the memory class by the GPU class u64& cpuTicks; // Reference to the CPU tick counter using SharedMemoryBlock = KernelMemoryTypes::SharedMemoryBlock; @@ -281,6 +281,8 @@ private: u32 getUsedUserMem() { return usedUserMemory; } void setVRAM(u8* pointer) { vram = pointer; } + void setDSPMem(u8* pointer) { dspRam = pointer; } + bool allocateMainThreadStack(u32 size); Regions getConsoleRegion(); void copySharedFont(u8* ptr); diff --git a/include/scheduler.hpp b/include/scheduler.hpp index 5645f47d..97c50afc 100644 --- a/include/scheduler.hpp +++ b/include/scheduler.hpp @@ -10,7 +10,8 @@ struct Scheduler { enum class EventType { VBlank = 0, // End of frame event UpdateTimers = 1, // Update kernel timer objects - Panic = 2, // Dummy event that is always pending and should never be triggered (Timestamp = UINT64_MAX) + RunDSP = 2, // Make the emulated DSP run for one audio frame + Panic = 3, // Dummy event that is always pending and should never be triggered (Timestamp = UINT64_MAX) TotalNumberOfEvents // How many event types do we have in total? }; static constexpr usize totalNumberOfEvents = static_cast(EventType::TotalNumberOfEvents); diff --git a/include/services/dsp.hpp b/include/services/dsp.hpp index ab9fb106..bc27377d 100644 --- a/include/services/dsp.hpp +++ b/include/services/dsp.hpp @@ -1,17 +1,12 @@ #pragma once #include #include +#include "audio/dsp_core.hpp" #include "helpers.hpp" #include "logger.hpp" #include "memory.hpp" #include "result/result.hpp" -namespace DSPPipeType { - enum : u32 { - Debug = 0, DMA = 1, Audio = 2, Binary = 3 - }; -} - // Circular dependencies! class Kernel; @@ -19,15 +14,11 @@ class DSPService { Handle handle = KernelHandles::DSP; Memory& mem; Kernel& kernel; + Audio::DSPCore* dsp = nullptr; MAKE_LOG_FUNCTION(log, dspServiceLogger) - enum class DSPState : u32 { - Off, On, Slep - }; - // Number of DSP pipes static constexpr size_t pipeCount = 8; - DSPState dspState; // DSP service event handles using DSPEvent = std::optional; @@ -36,10 +27,7 @@ class DSPService { DSPEvent interrupt0; DSPEvent interrupt1; std::array pipeEvents; - std::array, pipeCount> pipeData; // The data of each pipe - - void resetAudioPipe(); - std::vector readPipe(u32 pipe, u32 size); + u16 semaphoreMask = 0; DSPEvent& getEventRef(u32 type, u32 pipe); static constexpr size_t maxEventCount = 6; @@ -67,6 +55,10 @@ public: DSPService(Memory& mem, Kernel& kernel) : mem(mem), kernel(kernel) {} void reset(); void handleSyncRequest(u32 messagePointer); + void setDSPCore(Audio::DSPCore* pointer) { dsp = pointer; } + + // Special callback that's ran when the semaphore event is signalled + void onSemaphoreEventSignal() { dsp->setSemaphore(semaphoreMask); } enum class SoundOutputMode : u8 { Mono = 0, @@ -74,5 +66,8 @@ public: Surround = 2 }; - void signalEvents(); + void triggerPipeEvent(int index); + void triggerSemaphoreEvent(); + void triggerInterrupt0(); + void triggerInterrupt1(); }; \ No newline at end of file diff --git a/include/services/service_manager.hpp b/include/services/service_manager.hpp index 93700498..8d1cf381 100644 --- a/include/services/service_manager.hpp +++ b/include/services/service_manager.hpp @@ -105,9 +105,8 @@ class ServiceManager { void setHIDSharedMem(u8* ptr) { hid.setSharedMem(ptr); } void setCSNDSharedMem(u8* ptr) { csnd.setSharedMemory(ptr); } - void signalDSPEvents() { dsp.signalEvents(); } - // Input function wrappers HIDService& getHID() { return hid; } NFCService& getNFC() { return nfc; } + DSPService& getDSP() { return dsp; } }; diff --git a/src/config.cpp b/src/config.cpp index cd4e1f79..12b112dc 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -62,6 +62,16 @@ void EmulatorConfig::load() { } } + if (data.contains("Audio")) { + auto audioResult = toml::expect(data.at("Audio")); + if (audioResult.is_ok()) { + auto audio = audioResult.unwrap(); + + auto dspCoreName = toml::find_or(audio, "DSPEmulation", "Null"); + dspType = Audio::DSPCore::typeFromString(dspCoreName); + } + } + if (data.contains("Battery")) { auto batteryResult = toml::expect(data.at("Battery")); if (batteryResult.is_ok()) { @@ -109,6 +119,7 @@ void EmulatorConfig::save() { data["General"]["UsePortableBuild"] = usePortableBuild; data["GPU"]["EnableShaderJIT"] = shaderJitEnabled; data["GPU"]["Renderer"] = std::string(Renderer::typeToString(rendererType)); + data["Audio"]["DSPEmulation"] = std::string(Audio::DSPCore::typeToString(dspType)); data["Battery"]["ChargerPlugged"] = chargerPlugged; data["Battery"]["BatteryPercentage"] = batteryPercentage; diff --git a/src/core/audio/dsp_core.cpp b/src/core/audio/dsp_core.cpp new file mode 100644 index 00000000..e4162e93 --- /dev/null +++ b/src/core/audio/dsp_core.cpp @@ -0,0 +1,52 @@ +#include "audio/dsp_core.hpp" + +#include "audio/null_core.hpp" +#include "audio/teakra_core.hpp" + +#include +#include +#include + +std::unique_ptr Audio::makeDSPCore(DSPCore::Type type, Memory& mem, Scheduler& scheduler, DSPService& dspService) { + std::unique_ptr core; + + switch (type) { + case DSPCore::Type::Null: core = std::make_unique(mem, scheduler, dspService); break; + case DSPCore::Type::Teakra: core = std::make_unique(mem, scheduler, dspService); break; + + default: + Helpers::warn("Invalid DSP core selected!"); + core = std::make_unique(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 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"; + } +} diff --git a/src/core/audio/null_core.cpp b/src/core/audio/null_core.cpp new file mode 100644 index 00000000..ec073ae7 --- /dev/null +++ b/src/core/audio/null_core.cpp @@ -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 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& 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& 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(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 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& data = pipeData[pipe]; + size = std::min(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 out(data.begin(), data.begin() + size); + data.erase(data.begin(), data.begin() + size); + return out; + } +} // namespace Audio diff --git a/src/core/audio/teakra_core.cpp b/src/core/audio/teakra_core.cpp new file mode 100644 index 00000000..1f609187 --- /dev/null +++ b/src/core/audio/teakra_core.cpp @@ -0,0 +1,316 @@ +#include "audio/teakra_core.hpp" + +#include +#include + +#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 sample) { + //printf("%d %d\n", sample[0], sample[1]); + // NOP for now + }); + + // 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(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; +} + +// 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 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(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 TeakraDSP::readPipe(u32 channel, u32 peer, u32 size, u32 buffer) { + size &= 0xffff; + + PipeStatus status = getPipeStatus(channel, PipeDirection::DSPtoCPU); + + std::vector 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(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& 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; +} \ No newline at end of file diff --git a/src/core/kernel/events.cpp b/src/core/kernel/events.cpp index b2f89fbf..7c0d3047 100644 --- a/src/core/kernel/events.cpp +++ b/src/core/kernel/events.cpp @@ -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; + } } \ No newline at end of file diff --git a/src/core/memory.cpp b/src/core/memory.cpp index 83248ef5..fdc8c475 100644 --- a/src/core/memory.cpp +++ b/src/core/memory.cpp @@ -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); diff --git a/src/core/services/dsp.cpp b/src/core/services/dsp.cpp index 69eb9fb3..33c1703d 100644 --- a/src/core/services/dsp.cpp +++ b/src/core/services/dsp.cpp @@ -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 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& 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 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 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& data = pipeData[pipe]; - size = std::min(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 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 data = readPipe(channel, size); + std::vector 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(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); + } } \ No newline at end of file diff --git a/src/core/services/gsp_gpu.cpp b/src/core/services/gsp_gpu.cpp index d77f936e..2e1ce2d3 100644 --- a/src/core/services/gsp_gpu.cpp +++ b/src/core/services/gsp_gpu.cpp @@ -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; } diff --git a/src/emulator.cpp b/src/emulator.cpp index c567cbc7..dbbb0c37 100644 --- a/src/emulator.cpp +++ b/src/emulator.cpp @@ -24,6 +24,11 @@ Emulator::Emulator() httpServer(this) #endif { + DSPService& dspService = kernel.getServiceManager().getDSP(); + + dsp = Audio::makeDSPCore(config.dspType, memory, scheduler, dspService); + dspService.setDSPCore(dsp.get()); + #ifdef PANDA3DS_ENABLE_DISCORD_RPC if (config.discordRpcEnabled) { discordRpc.init(); @@ -46,6 +51,8 @@ void Emulator::reset(ReloadOption reload) { cpu.reset(); gpu.reset(); memory.reset(); + dsp->reset(); + // Reset scheduler and add a VBlank event scheduler.reset(); @@ -136,13 +143,17 @@ void Emulator::pollScheduler() { ServiceManager& srv = kernel.getServiceManager(); srv.sendGPUInterrupt(GPUInterrupt::VBlank0); srv.sendGPUInterrupt(GPUInterrupt::VBlank1); - + // Queue next VBlank event scheduler.addEvent(Scheduler::EventType::VBlank, time + CPU::ticksPerSec / 60); break; } case Scheduler::EventType::UpdateTimers: kernel.pollTimers(); break; + case Scheduler::EventType::RunDSP: { + dsp->runAudioFrame(); + break; + } default: { Helpers::panic("Scheduler: Unimplemented event type received: %d\n", static_cast(eventType)); diff --git a/third_party/teakra b/third_party/teakra new file mode 160000 index 00000000..01db7cdd --- /dev/null +++ b/third_party/teakra @@ -0,0 +1 @@ +Subproject commit 01db7cdd00aabcce559a8dddce8798dabb71949b