#include "dsp.hpp" #include "os.hpp" #include "../platform/dsp.hpp" #include #include #include extern Teakra::Teakra* g_teakra; // TODO: Remove using namespace Platform::CTR::DSP; namespace HLE { namespace OS { // TODO: Use virtual addresses instead // const VAddr dsp_mmio = 0x1ed03000; const PAddr dsp_mmio = 0x10103000; // "Pipes" are data areas in DSP memory shared between this module and the client application. // There are 4 pipes in total, each of which have two SubPipeInfo headers for the two sides of communication (DSP->ARM11 and ARM11->DSP). // The read/write cursors range from 0 to num_bytes and wrap around. The 16th bit can be considered an iteration count. struct SubPipeInfo { struct Cursor { BOOST_HANA_DEFINE_STRUCT(Cursor, (uint16_t, storage) ); auto value() const { return BitField::v3::MakeFieldOn<0, 15>(this); } auto iteration() const { return BitField::v3::MakeFieldOn<15, 1>(this); } }; BOOST_HANA_DEFINE_STRUCT(SubPipeInfo, (uint16_t, pipe_offset_words), // offset to pipe data in 16-bit words (uint16_t, num_bytes), // size of pipe data in bytes (Cursor, read_cursor), // read offset within pipe data (Cursor, write_cursor), // write offset within pipe data (uint8_t, subpipe_index), // 0: Pipe 0 to ARM11, 1: Pipe 0 to DSP, 0: Pipe 1 to ARM11, ... (uint8_t, unknownflags) ); uint16_t GetWritableBytesUntilWrap() const { return num_bytes - write_cursor.value(); } uint16_t GetReadableBytesUntilWrap() const { return std::min(CursorDistance(), num_bytes - read_cursor.value()); } uint16_t CursorDistance() const { auto ret = write_cursor.value() - read_cursor.value(); if (write_cursor.iteration() == read_cursor.iteration()) { if (write_cursor.value() < read_cursor.value()) { throw Mikage::Exceptions::Invalid("Read cursor for DSP pipe overlaps write cursor"); } } else { if (write_cursor.value() > read_cursor.value() || read_cursor.value() - write_cursor.value() > num_bytes) { throw Mikage::Exceptions::Invalid("Write cursor for DSP pipe overlaps read cursor"); } ret += num_bytes; } return ret; } uint16_t GetWritableBytes() const { return num_bytes - CursorDistance(); } // TODO: Use this somewhere void ValidateInvariants() const { (void)CursorDistance(); // Should pass fine if (read_cursor.value() > num_bytes) { throw Mikage::Exceptions::Invalid("Read cursor out of bounds"); } if (write_cursor.value() > num_bytes) { throw Mikage::Exceptions::Invalid("Write cursor out of bounds"); } } void MoveCursor(Cursor& cursor, uint16_t offset) const { uint32_t new_value = uint32_t { cursor.value() } + offset; bool new_iteration = cursor.iteration(); if (uint32_t { cursor.value() } + offset >= num_bytes) { new_value -= num_bytes; new_iteration = !new_iteration; } cursor = cursor.iteration()(new_iteration).value()(new_value); } bool IsEmpty() const { return read_cursor.storage == write_cursor.storage; } bool IsFull() const { return CursorDistance() == num_bytes; } // Index for data written by the DSP for read-out by ARM11 static uint8_t IndexToARM11(uint8_t pipe_index) { return pipe_index * 2; } // Index for data written by ARM11 for read-out by the DSP static uint8_t IndexToDSP(uint8_t pipe_index) { return pipe_index * 2 + 1; } }; FakeDSP::FakeDSP(FakeThread& thread) : os(thread.GetOS()), logger(*thread.GetLogger()) { static_buffer_addr = thread.GetParentProcess().AllocateStaticBuffer(0x20); thread.WriteTLS(0x188, IPC::TranslationDescriptor::MakeStaticBuffer(0, 0x20).raw); thread.WriteTLS(0x18c, static_buffer_addr); pipe_address = thread.GetParentProcess().AllocateStaticBuffer(0x20); thread.WriteTLS(0x180, IPC::TranslationDescriptor::MakeStaticBuffer(0, 0x20).raw); thread.WriteTLS(0x184, pipe_address); // TODO: These should actually both be sticky events Result result; HandleTable::Entry semaphore_event_entry; std::tie(result, semaphore_event_entry) = thread.CallSVC(&OS::SVCCreateEvent, ResetType::OneShot); ValidateContract(result == RESULT_OK); semaphore_event = semaphore_event_entry.first; semaphore_event_entry.second->name = "DSPSemaphoreEvent"; HandleTable::Entry interrupt_event_entry; std::tie(result, interrupt_event_entry) = thread.CallSVC(&OS::SVCCreateEvent, ResetType::OneShot); ValidateContract(result == RESULT_OK); interrupt_event_new = interrupt_event_entry.first; interrupt_event_entry.second->name = "DSPInterruptEvent"; // // TODO: Re-enable std::tie(result) = thread.CallSVC(&OS::SVCBindInterrupt, 0x4a, interrupt_event_entry.second, 4, 1); ValidateContract(result == RESULT_OK); ServiceHelper service; service.Append(ServiceUtil::SetupService(thread, "dsp::DSP", 4)); service.Append(interrupt_event_entry); service.Append(semaphore_event_entry); auto InvokeCommandHandler = [&](FakeThread& thread, uint32_t index) { Platform::IPC::CommandHeader header = { thread.ReadTLS(0x80) }; auto signalled_handle = service.handles[index]; if (signalled_handle == interrupt_event_new) { // TODO: Probably needs to be more sophisticated... fprintf(stderr, "DSP INTERRUPT 0x4a handler\n"); const auto dsp_status_reg = Memory::ReadLegacy(os.setup.mem, dsp_mmio + 0xc); /*const */bool channel2_has_data = (dsp_status_reg & (1 << 12)); if (!channel2_has_data) { fprintf(stderr, "WARNING: DSP interrupt signaled, but channel 2 has no data\n"); // throw Mikage::Exceptions::Invalid("DSP interrupt signaled, but channel 2 has no data"); channel2_has_data = true; } if (channel2_has_data && channel_event != HANDLE_INVALID) { logger.info("{} signalling DSP reply received event {} for channel 2", ThreadPrinter{thread}, HandlePrinter{thread, channel_event}); thread.CallSVC(&OS::SVCSignalEvent, channel_event); } return ServiceHelper::DoNothing; } else if (signalled_handle == semaphore_event) { Memory::WriteLegacy(os.setup.mem, dsp_mmio + 0x10, semaphore_mask); return ServiceHelper::DoNothing; } else { OnIPCRequest(thread, signalled_handle, header); return ServiceHelper::SendReply; } }; service.Run(thread, std::move(InvokeCommandHandler)); } std::tuple FakeDSP::HandleWriteProcessPipe(FakeThread& thread, uint32_t pipe_index, uint32_t num_bytes, IPC::StaticBuffer buffer) { auto buffer_addr = buffer.addr; // TODO: Assert num_bytes is non-zero and smaller/equal than the buffer size // TODO: Move to common code struct { Thread& thread; VAddr addr; void operator()(char* dest, size_t size) { for (auto data_ptr = dest; data_ptr != dest + size; ++data_ptr) { *data_ptr = thread.ReadMemory(addr++); } } } reader { thread, static_cast(pipe_info_base + SubPipeInfo::IndexToDSP(pipe_index) * sizeof(SubPipeInfo)) }; SubPipeInfo pipe = FileFormat::SerializationInterface::Load(reader); logger.info("Pipe {} to DSP with cursors [{:#x}, {:#x}] from {:#x}-{:#x}", pipe_index, pipe.read_cursor.value()(), pipe.write_cursor.value()(), pipe_info_base + 2 * pipe.pipe_offset_words, pipe_info_base + 2 * pipe.pipe_offset_words + pipe.num_bytes); if (pipe.subpipe_index != SubPipeInfo::IndexToDSP(pipe_index)) { throw Mikage::Exceptions::Invalid("Invalid DSP pipe header {}<->{}", pipe.subpipe_index, SubPipeInfo::IndexToDSP(pipe_index)); } while (num_bytes > 0) { if (pipe.IsFull() || num_bytes > pipe.GetWritableBytes()) { throw Mikage::Exceptions::NotImplemented("Cannot flush partial pipe writes"); } uint16_t chunk_size = std::min(num_bytes, pipe.GetWritableBytesUntilWrap()); logger.info(" Writing {:#x} bytes of data at write cursor {:#x} (iteration {})", chunk_size, pipe.write_cursor.value()(), pipe.write_cursor.iteration()()); for (uint32_t offset = 0; offset < chunk_size; ++offset) { // fprintf(stderr, "Wrong target address! Should refer to teakra's DSP memory instead\n"); // throw std::runtime_error("Wrong target address! Should refer to teakra's DSP memory instead"); // thread.WriteMemory( pipe_info_base + 2 * pipe.pipe_offset_words + pipe.write_cursor.value() + offset, // thread.ReadMemory(buffer_addr + offset)); auto val = thread.ReadMemory(buffer_addr + offset); if (num_bytes == 4 && offset >= 2) { // NOTE: Apparently actual DSP does this? Without it, launching titles from HOME Menu fails // TODO: This is not the full workaround, see Citra. val = 0; } // TODO: Write via virtual memory instead g_teakra->GetDspMemory()[/*pipe_info_base*/0x40000 + 2 * pipe.pipe_offset_words + pipe.write_cursor.value() + offset] = val; } num_bytes -= chunk_size; buffer_addr += chunk_size; pipe.MoveCursor(pipe.write_cursor, chunk_size); } // Write back SubPipeInfo to memory // TODO: Only need to write back the read/write cursors... struct Writer { Thread& thread; VAddr addr; void operator()(char* data, size_t size) { for (auto data_ptr = data; data_ptr != data + size; ++data_ptr) { thread.WriteMemory(addr++, *data_ptr); } } } writer { thread, static_cast(pipe_info_base + SubPipeInfo::IndexToDSP(pipe_index) * sizeof(SubPipeInfo)) }; FileFormat::SerializationInterface::Save(pipe, writer); // Notify DSP to process data const auto dsp_status_reg = Memory::ReadLegacy(os.setup.mem, dsp_mmio + 0xc); if ((dsp_status_reg & (1 << 15)) == 1) { throw Mikage::Exceptions::NotImplemented("WriteProcessPipe: Must wait for pending DSP writes"); } // Send subpipe index to DSP channel 2 Memory::WriteLegacy(os.setup.mem, dsp_mmio + 0x30, pipe.subpipe_index); return std::make_tuple(RESULT_OK); } std::tuple FakeDSP::HandleReadPipeIfPossible(FakeThread& thread, uint32_t pipe_index, uint32_t direction, uint32_t num_bytes) { // TODO: wait for dsp status? if (direction != 0) { fprintf(stderr, "ERROR: %d\n", __LINE__); throw Mikage::Exceptions::Invalid("Can't read ARM-side DSP pipe yet"); } // TODO: Move to common code struct { Thread& thread; VAddr addr; void operator()(char* dest, size_t size) { for (auto data_ptr = dest; data_ptr != dest + size; ++data_ptr) { *data_ptr = thread.ReadMemory(addr++); } } } reader { thread, static_cast(pipe_info_base + SubPipeInfo::IndexToARM11(pipe_index) * sizeof(SubPipeInfo)) }; const auto reader2 = reader; SubPipeInfo pipe = FileFormat::SerializationInterface::Load(reader); logger.info("Pipe {} to DSP with cursors [{:#x}, {:#x}] from {:#x}-{:#x}", pipe_index, pipe.read_cursor.value()(), pipe.write_cursor.value()(), pipe_info_base + 2 * pipe.pipe_offset_words, pipe_info_base + 2 * pipe.pipe_offset_words + pipe.num_bytes); if (pipe.subpipe_index != SubPipeInfo::IndexToARM11(pipe_index)) { fprintf(stderr, "ERROR: %d\n", __LINE__); throw Mikage::Exceptions::Invalid("Invalid DSP pipe header {}<->{}", pipe.subpipe_index, SubPipeInfo::IndexToARM11(pipe_index)); } // TODO: Return immediately instead if no data is available? while (pipe.IsEmpty() || pipe.CursorDistance() < num_bytes) { fprintf(stderr, "Only %d bytes available to read, running DSP cycles...\n", pipe.CursorDistance()); // g_teakra->Run(100); g_teakra->Run(0x4000); reader.addr = reader2.addr; pipe = FileFormat::SerializationInterface::Load(reader); } fprintf(stderr, "Available bytes to read: %d\n", pipe.CursorDistance()); // if (pipe.IsEmpty()) { // num_bytes = 0; // } VAddr target_addr = pipe_address; for (uint32_t bytes_left = num_bytes; bytes_left > 0;) { if (bytes_left > pipe.CursorDistance()) { fprintf(stderr, "Partial pipe reads not supported\n"); throw Mikage::Exceptions::NotImplemented("Partial pipe reads not supported"); } uint16_t chunk_size = std::min(bytes_left, pipe.GetReadableBytesUntilWrap()); logger.info(" Reading {:#x} bytes of data at read cursor {:#x} (iteration {})", chunk_size, pipe.read_cursor.value()(), pipe.read_cursor.iteration()()); // logger.info(" Reading {:#x} bytes of data from address {:#x} indicated by read cursor {:#x} (iteration {})", chunk_size, pipe.read_cursor.value()(), pipe.read_cursor.iteration()()); for (uint32_t offset = 0; offset < chunk_size; ++offset) { // TODO: Access this through virtual memory instead... // auto value = thread.ReadMemory(/*pipe_info_base*/0x40000 + 2 * pipe.pipe_offset_words + pipe.read_cursor.value() + offset); auto value = g_teakra->GetDspMemory()[/*pipe_info_base*/0x40000 + 2 * pipe.pipe_offset_words + pipe.read_cursor.value() + offset]; thread.WriteMemory(target_addr + offset, value); } bytes_left -= chunk_size; target_addr += chunk_size; pipe.MoveCursor(pipe.read_cursor, chunk_size); } if (num_bytes > 0) { // Write back SubPipeInfo to memory // TODO: Only need to write back the read/write cursors... struct Writer { Thread& thread; VAddr addr; void operator()(char* data, size_t size) { for (auto data_ptr = data; data_ptr != data + size; ++data_ptr) { thread.WriteMemory(addr++, *data_ptr); } } } writer { thread, static_cast(pipe_info_base + SubPipeInfo::IndexToARM11(pipe_index) * sizeof(SubPipeInfo)) }; FileFormat::SerializationInterface::Save(pipe, writer); // Notify DSP to process data const auto dsp_status_reg = Memory::ReadLegacy(os.setup.mem, dsp_mmio + 0xc); if ((dsp_status_reg & (1 << 15)) == 1) { throw Mikage::Exceptions::NotImplemented("ReadPipeIfPossible: Must wait for pending DSP writes"); } // Send subpipe index to DSP channel 2 Memory::WriteLegacy(os.setup.mem, dsp_mmio + 0x30, pipe.subpipe_index); } return std::make_tuple(RESULT_OK, num_bytes, IPC::StaticBuffer { pipe_address, num_bytes, 0 }); } std::tuple FakeDSP::HandleLoadComponent(FakeThread& thread, uint32_t size, uint32_t program_mask, uint32_t data_mask, const IPC::MappedBuffer component_buffer) { using Header = FileFormat::DSPFirmwareHeader; // NOTE: The upper 16 bits of these may be filled with noise (e.g. Kirby Battle Royale Demo) if ((program_mask & 0xffff) != 0xff || (data_mask & 0xffff) != 0xff) { throw Mikage::Exceptions::Invalid("Unsupported program mask {:#x} and data mask {:#x} for DSP firmware loading", program_mask, data_mask); } if (size < Header::Tags::expected_serialized_size) { throw Mikage::Exceptions::Invalid("Invalid buffer size for DSP firmware header"); } // TODO: Move to common code struct { Thread& thread; VAddr addr; void operator()(char* dest, size_t size) { for (auto data_ptr = dest; data_ptr != dest + size; ++data_ptr) { *data_ptr = thread.ReadMemory(addr++); } } } reader { thread, component_buffer.addr }; auto header = FileFormat::SerializationInterface::Load(reader); if (header.magic[0] != 'D' || header.magic[1] != 'S' || header.magic[2] != 'P' || header.magic[3] != '1') { throw Mikage::Exceptions::Invalid("Invalid DSP firmware identifier"); } if (size < header.size_bytes) { throw Mikage::Exceptions::Invalid("Invalid buffer size for DSP firmware"); } memset(g_teakra->GetDspMemory().data(), 0, g_teakra->GetDspMemory().size()); logger.info("DSP firmware segments:"); for (int index = 0; index < header.num_segments; ++index) { auto& segment = header.segments[index]; bool is_data_memory = (segment.memory_type == 2); auto target_paddr = Memory::DSP::start + (is_data_memory ? Memory::DSP::size / 2 : 0) + segment.target_offset * sizeof(uint16_t); logger.info(" {} at {:#x} -> {:#x}-{:#x}", is_data_memory ? "Data" : segment.memory_type < 2 ? "Program data" : "Unknown data", segment.data_offset, target_paddr, target_paddr + segment.size_bytes); for (uint32_t offset = 0; offset < segment.size_bytes; ++offset) { // NOTE: DSP memory is identity-mapped in virtual memory thread.WriteMemory(target_paddr + offset, thread.ReadMemory(component_buffer.addr + segment.data_offset + offset)); } } // TODO: Should this be done before or after waiting for the DSP replies? if (header.init_flags.load_3d_filters()) { // TODO: At least just write zeroes instead... fprintf(stderr, "TODO DSP: Implement 3d filters\n"); // throw Mikage::Exceptions::NotImplemented("TODO: Implement loading of 3D filters"); } auto& mem = thread.GetOS().setup.mem; // TODO: Re-enable? // // Reset semaphore // Memory::WriteLegacy(mem, dsp_mmio + 0x10, 0); // Memory::WriteLegacy(mem, dsp_mmio + 0x18, 0xffff); Memory::WriteLegacy(mem, dsp_mmio + 0x10, 0); Memory::WriteLegacy(mem, dsp_mmio + 0x18, 0xffff); // Trigger DSP reset const auto dsp_config = Memory::ReadLegacy(mem, dsp_mmio + 0x8); Memory::WriteLegacy(mem, dsp_mmio + 0x8, dsp_config | 1); // TODO: Wait for bit 2 in DSP status to be cleared // Release reset trigger Memory::WriteLegacy(mem, dsp_mmio + 0x8, dsp_config & 0xfe); // Memory::WriteLegacy(mem, dsp_mmio + 0x14, 0xffff); // Wait for channels to be ready if (header.init_flags.wait_for_dsp_reply()) { for (int channel = 0; channel < 3; ++channel) { fprintf(stderr, "Waiting for channel %d to be ready\n", channel); while (true) { g_teakra->Run(100); auto dsp_status = Memory::ReadLegacy(mem, dsp_mmio + 0xc); if (dsp_status & (1 << (10 + channel))) { auto reply = Memory::ReadLegacy(mem, dsp_mmio + 0x24 + 8 * channel); if (reply == 1) { break; } else { throw Mikage::Exceptions::Invalid("Post-init DSP reply has invalid value"); } } } } } else { // Not sure if any other logic in LoadComponent must be skipped in this case throw Mikage::Exceptions::NotImplemented("LoadComponent: Unclear semantics for firmware loading"); } // First reply on channel 2 after initialization is the start offset (in 16-bit words) of the DSP SubPipeInfo array while ((Memory::ReadLegacy(mem, dsp_mmio + 0xc) & (1 << 12)) == 0) { // g_teakra->Run(100); g_teakra->Run(0x4000); } pipe_info_base = Memory::DSP::start + Memory::DSP::size / 2 + sizeof(uint16_t) * Memory::ReadLegacy(mem, dsp_mmio + 0x34); logger.info("DSP pipe headers located at {:#x}", pipe_info_base); if (pipe_info_base >= Memory::DSP::end) { throw Mikage::Exceptions::Invalid("Invalid DSP pipe location"); } // TODO: Don't override OS handlers... instead, subscribe to DSP interrupt! struct ProcessPipeEventHandler { FakeDSP& context; OS& os; bool event_from_data; void operator()() const { // TODO: Assert this is only called from the OS scheduler if (event_from_data) { context.data_signaled = true; } else { if ((g_teakra->GetSemaphore() & 0x8000) == 0) { return; } context.semaphore_signaled = true; } if (context.data_signaled && context.semaphore_signaled) { context.data_signaled = context.semaphore_signaled = false; uint16_t slot = g_teakra->RecvData(2); if ((slot % 2) != 0) { return; } if ((slot / 2) == 0) { fprintf(stderr, "ERROR: PIPE 0\n"); throw std::runtime_error("NOT IMPLEMENTED"); } os.NotifyInterrupt(0x4a); // TODO: Preempt DSP execution } } }; #if 0 /*static */auto process_pipe_event = [os=&thread.GetOS(), this](bool event_from_data) { // TODO: Assert this is only called from the OS scheduler if (event_from_data) { data_signaled = true; } else { if ((g_teakra->GetSemaphore() & 0x8000) == 0) { return; } semaphore_signaled = true; } if (data_signaled && semaphore_signaled) { data_signaled = semaphore_signaled = false; uint16_t slot = g_teakra->RecvData(2); if ((slot % 2) != 0) { return; } if ((slot / 2) == 0) { fprintf(stderr, "ERROR: PIPE 0\n"); throw std::runtime_error("NOT IMPLEMENTED"); } os->NotifyInterrupt(0x4a); // TODO: Preempt DSP execution } }; #endif g_teakra->SetSemaphoreHandler(/*[]() { process_pipe_event(false); }*/ ProcessPipeEventHandler { *this, thread.GetOS(), false }); g_teakra->SetRecvDataHandler(2, /*[]() { process_pipe_event(true); }*/ ProcessPipeEventHandler { *this, thread.GetOS(), true }); return std::make_tuple(RESULT_OK, 1 /* component loaded */, component_buffer); } std::tuple FakeDSP::HandleFlushDataCache(FakeThread& thread, uint32_t start, uint32_t num_bytes, Handle process) { thread.CallSVC(&OS::SVCFlushProcessDataCache, process, start, num_bytes); // Close incoming thread handle thread.CallSVC(&OS::SVCCloseHandle, process); return std::make_tuple(RESULT_OK); } std::tuple FakeDSP::HandleInvalidateDataCache(FakeThread& thread, uint32_t start, uint32_t num_bytes, Handle process) { thread.CallSVC(&OS::SVCInvalidateProcessDataCache, process, start, num_bytes); // Close incoming thread handle thread.CallSVC(&OS::SVCCloseHandle, process); return std::make_tuple(RESULT_OK); } std::tuple FakeDSP::HandleGetSemaphoreEventHandle(FakeThread& thread) { logger.info("{}received GetSemaphoreEventHandle", ThreadPrinter{thread}); return std::make_tuple(RESULT_OK, semaphore_event); } template static auto BindMemFn(Func f, Class* c) { return boost::hana::partial(std::mem_fn(f), c); } static int lol = 0; // TODO: Un-global-ize void FakeDSP::OnIPCRequest(FakeThread& thread, Handle sender, const IPC::CommandHeader& header) { switch (header.command_id) { case 0x1: // RecvData // throw Mikage::Exceptions::NotImplemented("Not implemented: RecvData"); { auto index = thread.ReadTLS(0x84); thread.WriteTLS(0x80, IPC::CommandHeader::Make(1, 2, 0).raw); thread.WriteTLS(0x84, RESULT_OK); // TODO: Applications use RecvData shortly after putting the DSP to // sleep via WriteProcessPipe. However, if we indicate Sleep // status too quickly, a race condition will prevent the // application's main DSP client thread from properly pausing. // Hence, we wait for a bit before reporting the new status, // however this should be done in a cleaner way. // lol++; // thread.WriteTLS(0x88, lol > 10 && state != State::Running); if (!g_teakra->RecvDataIsReady(index)) { fprintf(stderr, "ERROR: DSP data is not ready\n"); throw Mikage::Exceptions::Invalid("DSP data is not ready"); } auto data = g_teakra->RecvData(index); thread.WriteTLS(0x88, data); thread.GetLogger()->info("{}received RecvData with state={}, returning {}", ThreadPrinter{thread}, Meta::to_underlying(state), data); break; } case 0x2: // RecvDataIsReady { auto index = thread.ReadTLS(0x84); thread.WriteTLS(0x80, IPC::CommandHeader::Make(2, 2, 0).raw); thread.WriteTLS(0x84, RESULT_OK); thread.WriteTLS(0x88, g_teakra->RecvDataIsReady(index)); break; } case SetSemaphore::id: { auto value = thread.ReadTLS(0x84); if (value > 0xffff) { throw Mikage::Exceptions::Invalid("Invalid DSP semaphore value"); } Memory::WriteLegacy(os.setup.mem, dsp_mmio + 0x10, value); thread.WriteTLS(0x80, IPC::CommandHeader::Make(SetSemaphore::id, 1, 0).raw); thread.WriteTLS(0x84, RESULT_OK); break; } case 0xc: { // TODO: This is not actually an address but rather an offset (in 16-bit values) VAddr addr = thread.ReadTLS(0x84); thread.GetLogger()->info("{}received ConvertProcessAddressFromDSPRam with address={:#x}", ThreadPrinter{thread}, addr); thread.WriteTLS(0x80, IPC::CommandHeader::Make(0xc, 2, 0).raw); thread.WriteTLS(0x84, RESULT_OK); // TODO: Replace hardcoded constant with symbol for the mid of the physical DSP memory region auto out_addr = 2 * addr + 0x1ff40000; if (out_addr >= 0x1ff80000 || out_addr < 0x1ff40000) { throw Mikage::Exceptions::Invalid("ConvertProcessAddressFromDSPRam tried to query invalid DSP memory address {:#x}", out_addr); } thread.WriteTLS(0x88, out_addr); break; } case 0xd: // WriteProcessPipe IPC::HandleIPCCommand(BindMemFn(&FakeDSP::HandleWriteProcessPipe, this), thread, thread); break; // // Detect DSP state changes, but stub-implement everything else // if (pipe == 2) { // auto value = thread.ReadMemory(buffer_addr); // switch (value) { // case 0: state = State::Running; thread.GetLogger()->info("{}received WriteProcessPipe -> State::Running", ThreadPrinter{thread}); break; // case 1: state = State::Stopped; lol = 0; thread.GetLogger()->info("{}received WriteProcessPipe -> State::Stopped", ThreadPrinter{thread}); break; // case 2: state = State::Running; thread.GetLogger()->info("{}received WriteProcessPipe -> State::Running", ThreadPrinter{thread}); break; // case 3: state = State::Sleeping; lol = 0; thread.GetLogger()->info("{}received WriteProcessPipe -> State::Sleeping", ThreadPrinter{thread}); break; // default: throw Mikage::Exceptions::Invalid("Invalid DSP state"); // } // if (state == State::Stopped || state == State::Sleeping || true) { // read_pipe_if_possible_counter = -1; // } // } // thread.WriteTLS(0x80, IPC::CommandHeader::Make(0xd, 1, 0).raw); // thread.WriteTLS(0x84, RESULT_OK); // break; // ReadPipeIfPossible case 0x10: { IPC::HandleIPCCommand(BindMemFn(&FakeDSP::HandleReadPipeIfPossible, this), thread, thread); break; // uint32_t a = thread.ReadTLS(0x84); // uint32_t b = thread.ReadTLS(0x88); // uint32_t size = std::min(thread.ReadTLS(0x8c) & 0xFFFF, 0x20); // Only considers the lower 16 bits for the size // thread.GetLogger()->info("ReadPipeIfPossible: channel {:#x}, {:#x} bytes", a, size); // if (a != 2) // thread.CallSVC(&OS::SVCBreak, OS::BreakReason::Panic); // for (unsigned i = 0; i < 0x20; ++i) // thread.WriteTLS(0x80 + 4*i, 0xdeadbeef); // thread.WriteTLS(0x80, IPC::CommandHeader::Make(0x10, 2, 2).raw); // thread.WriteTLS(0x84, RESULT_OK); // thread.WriteTLS(0x88, size); // thread.WriteTLS(0x8c, IPC::TranslationDescriptor::MakeStaticBuffer(0, size).raw); // thread.WriteTLS(0x90, pipe_address); // TODO: Return offset to pipe data instead? } case LoadComponent::id: IPC::HandleIPCCommand(BindMemFn(&FakeDSP::HandleLoadComponent, this), thread, thread); break; case 0x12: // UnloadComponent { auto& mem = thread.GetOS().setup.mem; const auto channel = 2; // NOTE: Channel 2 seems to be implicit for this call const auto dsp_config = Memory::ReadLegacy(mem, dsp_mmio + 0x8); Memory::WriteLegacy(mem, dsp_mmio + 0x8, dsp_config & ~0x800); semaphore_mask = Memory::ReadLegacy(mem, dsp_mmio + 0x14); semaphore_mask |= 0x8000; Memory::WriteLegacy(mem, dsp_mmio + 0x14, semaphore_mask); if ((Memory::ReadLegacy(mem, dsp_mmio + 0xc) & (1 << (13 + channel))) != 0) { throw Mikage::Exceptions::NotImplemented("Waiting on pending DSP write not implemented"); } Memory::WriteLegacy(os.setup.mem, dsp_mmio + 0x20 + channel * 8, 0x8000); // Wait until DSP has processed the request while ((Memory::ReadLegacy(mem, dsp_mmio + 0xc) & (1 << 12)) == 0) { g_teakra->Run(0x4000); } auto reply = Memory::ReadLegacy(mem, dsp_mmio + 0x24 + 8 * channel); fprintf(stderr, "DSP SHUTDOWN REPLY: %#x\n", reply); thread.WriteTLS(0x80, IPC::CommandHeader::Make(0x12, 1, 0).raw); thread.WriteTLS(0x84, RESULT_OK); break; } case FlushDataCache::id: IPC::HandleIPCCommand(BindMemFn(&FakeDSP::HandleFlushDataCache, this), thread, thread); break; case 0x14: IPC::HandleIPCCommand(BindMemFn(&FakeDSP::HandleInvalidateDataCache, this), thread, thread); break; case RegisterInterruptEvents::id: { auto interrupt = thread.ReadTLS(0x84); auto pipe = thread.ReadTLS(0x88); if (interrupt != 2 || pipe != 2) { throw Mikage::Exceptions::Invalid("Invalid DSP interrupt/pipe"); } auto new_channel_event = Handle{thread.ReadTLS(0x90)}; if (channel_event != HANDLE_INVALID && new_channel_event != HANDLE_INVALID) { throw Mikage::Exceptions::Invalid("Cannot register more than one DSP client"); } if (new_channel_event != HANDLE_INVALID) { std::shared_ptr object; object = thread.GetProcessHandleTable().FindObject(new_channel_event); object->name = "DSPInterruptEvent"; } else { // TODO: Unregister previously registered event // throw Mikage::Exceptions::NotImplemented("Cannot unregister DSP interrupt event yet"); thread.CallSVC(&OS::SVCCloseHandle, channel_event); } channel_event = new_channel_event; thread.WriteTLS(0x80, IPC::CommandHeader::Make(RegisterInterruptEvents::id, 1, 0).raw); thread.WriteTLS(0x84, RESULT_OK); break; } case GetSemaphoreEventHandle::id: IPC::HandleIPCCommand(BindMemFn(&FakeDSP::HandleGetSemaphoreEventHandle, this), thread, thread); break; case SetSemaphoreMask::id: semaphore_mask = thread.ReadTLS(0x84); thread.WriteTLS(0x80, IPC::CommandHeader::Make(SetSemaphoreMask::id, 1, 0).raw); thread.WriteTLS(0x84, RESULT_OK); break; // GetHeadPhoneStatus case 0x1f: thread.WriteTLS(0x80, IPC::CommandHeader::Make(0x1f, 2, 0).raw); thread.WriteTLS(0x84, RESULT_OK); thread.WriteTLS(0x88, 0); // not inserted break; default: // TODO: Throw and catch IPCError instead throw std::runtime_error(fmt::format("Unknown DSP IPC request {:#x}", header.command_id)); } } } // namespace OS } // namespace HLE #define FORMATS_IMPL_EXPLICIT_FORMAT_INSTANTIATIONS_INTENDED #include namespace FileFormat { template struct SerializationInterface; }