mirror of
https://github.com/wheremyfoodat/Panda3DS.git
synced 2025-04-06 14:15:41 +12:00
commit
093364f615
24 changed files with 868 additions and 159 deletions
3
.gitmodules
vendored
3
.gitmodules
vendored
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
55
include/audio/dsp_core.hpp
Normal file
55
include/audio/dsp_core.hpp
Normal file
|
@ -0,0 +1,55 @@
|
|||
#pragma once
|
||||
#include <array>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#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<u8> readPipe(u32 channel, u32 peer, u32 size, u32 buffer) = 0;
|
||||
virtual void loadComponent(std::vector<u8>& 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<DSPCore> makeDSPCore(DSPCore::Type type, Memory& mem, Scheduler& scheduler, DSPService& dspService);
|
||||
} // namespace Audio
|
45
include/audio/null_core.hpp
Normal file
45
include/audio/null_core.hpp
Normal file
|
@ -0,0 +1,45 @@
|
|||
#pragma once
|
||||
#include <array>
|
||||
|
||||
#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<std::vector<u8>, pipeCount> pipeData; // The data of each pipe
|
||||
std::array<u8, Memory::DSP_RAM_SIZE> 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<u8> readPipe(u32 channel, u32 peer, u32 size, u32 buffer) override;
|
||||
|
||||
// NOPs for null DSP core
|
||||
void loadComponent(std::vector<u8>& data, u32 programMask, u32 dataMask) override;
|
||||
void unloadComponent() override;
|
||||
void setSemaphore(u16 value) override {}
|
||||
void setSemaphoreMask(u16 value) override {}
|
||||
};
|
||||
|
||||
} // namespace Audio
|
99
include/audio/teakra_core.hpp
Normal file
99
include/audio/teakra_core.hpp
Normal file
|
@ -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<u8> readPipe(u32 channel, u32 peer, u32 size, u32 buffer) override;
|
||||
void loadComponent(std::vector<u8>& data, u32 programMask, u32 dataMask) override;
|
||||
void unloadComponent() override;
|
||||
};
|
||||
} // namespace Audio
|
|
@ -1,6 +1,7 @@
|
|||
#pragma once
|
||||
#include <filesystem>
|
||||
|
||||
#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;
|
||||
|
|
|
@ -2,10 +2,12 @@
|
|||
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <span>
|
||||
|
||||
#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<Audio::DSPCore> 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
|
||||
|
|
|
@ -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();
|
||||
};
|
|
@ -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 {
|
||||
|
|
|
@ -36,6 +36,7 @@ namespace Log {
|
|||
static Logger<false> gpuLogger;
|
||||
static Logger<false> rendererLogger;
|
||||
static Logger<false> shaderJITLogger;
|
||||
static Logger<false> dspLogger;
|
||||
|
||||
// Service loggers
|
||||
static Logger<false> acLogger;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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<usize>(EventType::TotalNumberOfEvents);
|
||||
|
|
|
@ -1,17 +1,12 @@
|
|||
#pragma once
|
||||
#include <array>
|
||||
#include <optional>
|
||||
#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<Handle>;
|
||||
|
@ -36,10 +27,7 @@ class DSPService {
|
|||
DSPEvent interrupt0;
|
||||
DSPEvent interrupt1;
|
||||
std::array<DSPEvent, pipeCount> pipeEvents;
|
||||
std::array<std::vector<u8>, pipeCount> pipeData; // The data of each pipe
|
||||
|
||||
void resetAudioPipe();
|
||||
std::vector<u8> 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();
|
||||
};
|
|
@ -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; }
|
||||
};
|
||||
|
|
|
@ -62,6 +62,16 @@ void EmulatorConfig::load() {
|
|||
}
|
||||
}
|
||||
|
||||
if (data.contains("Audio")) {
|
||||
auto audioResult = toml::expect<toml::value>(data.at("Audio"));
|
||||
if (audioResult.is_ok()) {
|
||||
auto audio = audioResult.unwrap();
|
||||
|
||||
auto dspCoreName = toml::find_or<std::string>(audio, "DSPEmulation", "Null");
|
||||
dspType = Audio::DSPCore::typeFromString(dspCoreName);
|
||||
}
|
||||
}
|
||||
|
||||
if (data.contains("Battery")) {
|
||||
auto batteryResult = toml::expect<toml::value>(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;
|
||||
|
|
52
src/core/audio/dsp_core.cpp
Normal file
52
src/core/audio/dsp_core.cpp
Normal 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";
|
||||
}
|
||||
}
|
166
src/core/audio/null_core.cpp
Normal file
166
src/core/audio/null_core.cpp
Normal 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
|
316
src/core/audio/teakra_core.cpp
Normal file
316
src/core/audio/teakra_core.cpp
Normal file
|
@ -0,0 +1,316 @@
|
|||
#include "audio/teakra_core.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
|
||||
#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) {
|
||||
//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<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;
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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<int>(eventType));
|
||||
|
|
1
third_party/teakra
vendored
Submodule
1
third_party/teakra
vendored
Submodule
|
@ -0,0 +1 @@
|
|||
Subproject commit 01db7cdd00aabcce559a8dddce8798dabb71949b
|
Loading…
Add table
Reference in a new issue