#include "cam.hpp" #include "fake_process.hpp" #include "os.hpp" #include namespace HLE { namespace OS { // TODO: Move to hw/y2r struct CAMOutputFormat { uint32_t raw; static constexpr std::array format_map = {{ GenericImageFormat::RGBA8, GenericImageFormat::RGB8, GenericImageFormat::RGBA5551, GenericImageFormat::RGB565, }}; }; struct FakeCAM { // Staging buffer for output data // TODO: On actual hardware, the DMA engines are likely used to stream data directly to/from the Y2R register block VAddr output_vaddr = 0; VAddr input_y_vaddr = 0; uint32_t input_size = 0; GenericImageFormat output_format = GenericImageFormat::Unknown; HandleTable::Entry transfer_end_event; FakeCAM(FakeThread& thread); }; static OS::ResultAnd<> Y2RSetReceiving(FakeCAM& context, FakeThread& thread, VAddr dest_vaddr, uint32_t dest_size, uint32_t transfer_unit, uint32_t transfer_stride, Handle dest_process_handle) { auto dest_process = thread.GetProcessHandleTable().FindObject(dest_process_handle); thread.GetLogger()->info("SetReceiving: Stub conversion to {:#x}-{:#x} in {}", dest_vaddr, dest_vaddr + dest_size, ProcessPrinter { *dest_process }); // TODO: Below logic should be done in StartConversion instead // TODO: Set up Y2R hardware to perform the actual conversion. DMAConfig config { .channel = 0xff, .value_size = 0, // uint8_t .flags = {}, // ?? .dest = DMAConfig::SubConfig::Default(), .source = DMAConfig::SubConfig::Default(), }; // Copy raw data to staging buffer so we get *some* feedback auto [result, dma_handle] = thread.CallSVC(&OS::SVCStartInterprocessDMA, *dest_process, thread.GetParentProcess(), context.input_y_vaddr, context.output_vaddr, std::min(context.input_size, dest_size), config); std::tie(result, dma_handle) = thread.CallSVC(&OS::SVCStartInterprocessDMA, thread.GetParentProcess(), *dest_process, context.output_vaddr, dest_vaddr, dest_size, config); // TODO: Link dma_handle to transfer_end_event thread.CallSVC(&OS::SVCCloseHandle, dest_process_handle); return { result }; } static auto Y2RCommandHandler(FakeCAM& context, FakeThread& thread, Platform::IPC::CommandHeader header) try { using SetReceiving = IPC::IPCCommand<0x18>::add_uint32::add_uint32::add_uint32::add_uint32::add_handle::response; switch (header.command_id) { // SetInputFormat case 0x1: thread.WriteTLS(0x80, IPC::CommandHeader::Make(0x1, 1, 0).raw); thread.WriteTLS(0x84, RESULT_OK); break; // SetOutputFormat case 0x3: context.output_format = ToGenericFormat(CAMOutputFormat { thread.ReadTLS(0x84) }); thread.WriteTLS(0x80, IPC::CommandHeader::Make(0x3, 1, 0).raw); thread.WriteTLS(0x84, RESULT_OK); break; case 0x4: // GetOutputFormat thread.WriteTLS(0x80, IPC::CommandHeader::Make(0x3, 2, 0).raw); thread.WriteTLS(0x84, RESULT_OK); thread.WriteTLS(0x88, FromGenericFormat(context.output_format).raw); break; // SetRotation case 0x5: thread.WriteTLS(0x80, IPC::CommandHeader::Make(0x5, 1, 0).raw); thread.WriteTLS(0x84, RESULT_OK); break; // SetBlockAlignment case 0x7: thread.WriteTLS(0x80, IPC::CommandHeader::Make(0x7, 1, 0).raw); thread.WriteTLS(0x84, RESULT_OK); break; // SetSpatialDithering case 0x9: thread.WriteTLS(0x80, IPC::CommandHeader::Make(0x9, 1, 0).raw); thread.WriteTLS(0x84, RESULT_OK); break; // SetTemporalDithering case 0xb: thread.WriteTLS(0x80, IPC::CommandHeader::Make(0xb, 1, 0).raw); thread.WriteTLS(0x84, RESULT_OK); break; // SetTransferEndInterrupt case 0xd: thread.WriteTLS(0x80, IPC::CommandHeader::Make(0xd, 1, 0).raw); thread.WriteTLS(0x84, RESULT_OK); break; // GetTransferEndEvent case 0xf: thread.WriteTLS(0x80, IPC::CommandHeader::Make(0xf, 1, 2).raw); thread.WriteTLS(0x84, RESULT_OK); thread.WriteTLS(0x88, IPC::TranslationDescriptor::MakeHandles(1, false).raw); thread.WriteTLS(0x8c, context.transfer_end_event.first.value); break; // SetSendingY case 0x10: context.input_y_vaddr = thread.ReadTLS(0x84); context.input_size = thread.ReadTLS(0x88); thread.WriteTLS(0x80, IPC::CommandHeader::Make(0x10, 1, 0).raw); thread.WriteTLS(0x84, RESULT_OK); break; // SetSendingU case 0x11: thread.WriteTLS(0x80, IPC::CommandHeader::Make(0x11, 1, 0).raw); thread.WriteTLS(0x84, RESULT_OK); break; // SetSendingV case 0x12: thread.WriteTLS(0x80, IPC::CommandHeader::Make(0x12, 1, 0).raw); thread.WriteTLS(0x84, RESULT_OK); break; // SetSendingYuv case 0x13: context.input_y_vaddr = thread.ReadTLS(0x84); context.input_size = thread.ReadTLS(0x88); thread.WriteTLS(0x80, IPC::CommandHeader::Make(0x13, 1, 0).raw); thread.WriteTLS(0x84, RESULT_OK); break; case SetReceiving::id: IPC::HandleIPCCommand(Y2RSetReceiving, thread, context, thread); break; // SetInputLineWidth case 0x1a: thread.WriteTLS(0x80, IPC::CommandHeader::Make(0x1a, 1, 0).raw); thread.WriteTLS(0x84, RESULT_OK); break; // SetInputLines case 0x1c: thread.WriteTLS(0x80, IPC::CommandHeader::Make(0x1c, 1, 0).raw); thread.WriteTLS(0x84, RESULT_OK); break; // SetCoefficientParams case 0x1e: thread.WriteTLS(0x80, IPC::CommandHeader::Make(0x1e, 1, 0).raw); thread.WriteTLS(0x84, RESULT_OK); break; // SetStandardCoefficient case 0x20: thread.WriteTLS(0x80, IPC::CommandHeader::Make(0x20, 1, 0).raw); thread.WriteTLS(0x84, RESULT_OK); break; // SetAlpha case 0x22: thread.WriteTLS(0x80, IPC::CommandHeader::Make(0x22, 1, 0).raw); thread.WriteTLS(0x84, RESULT_OK); break; // SetDitheringWeightParams case 0x24: thread.WriteTLS(0x80, IPC::CommandHeader::Make(0x24, 1, 0).raw); thread.WriteTLS(0x84, RESULT_OK); break; // StartConversion case 0x26: // Immediately signal completion thread.CallSVC(&OS::SVCSignalEvent, context.transfer_end_event.first); thread.WriteTLS(0x80, IPC::CommandHeader::Make(0x26, 1, 0).raw); thread.WriteTLS(0x84, RESULT_OK); break; // SetPackageParameter case 0x29: thread.WriteTLS(0x80, IPC::CommandHeader::Make(0x29, 1, 0).raw); thread.WriteTLS(0x84, RESULT_OK); break; // PingProcess case 0x2a: thread.WriteTLS(0x80, IPC::CommandHeader::Make(0x2a, 2, 0).raw); thread.WriteTLS(0x84, RESULT_OK); thread.WriteTLS(0x88, 0); break; // DriverInitialize case 0x2b: thread.CallSVC(&OS::SVCClearEvent, context.transfer_end_event.first); thread.WriteTLS(0x80, IPC::CommandHeader::Make(0x2b, 1, 0).raw); thread.WriteTLS(0x84, RESULT_OK); break; // DriverFinalize case 0x2c: thread.WriteTLS(0x80, IPC::CommandHeader::Make(0x2c, 1, 0).raw); thread.WriteTLS(0x84, RESULT_OK); break; // StopConversion case 0x27: thread.WriteTLS(0x80, IPC::CommandHeader::Make(0x27, 1, 0).raw); thread.WriteTLS(0x84, RESULT_OK); break; // IsBusyConversion case 0x28: thread.WriteTLS(0x80, IPC::CommandHeader::Make(0x28, 2, 0).raw); thread.WriteTLS(0x84, RESULT_OK); thread.WriteTLS(0x88, 0); // Not busy break; default: throw IPC::IPCError{header.raw, 0xdeadbeef}; } return ServiceHelper::SendReply; } catch (const IPC::IPCError& err) { throw std::runtime_error(fmt::format("Unknown y2r:u service command with header {:#010x}", err.header)); } template static void IPCThread(FakeCAM& context, FakeThread& thread, const char* service_name, Handler&& handler) { // TODO: Only a single connection should be accepted on any service ServiceHelper service; service.Append(ServiceUtil::SetupService(thread, service_name, 3)); auto InvokeCommandHandler = [&context,handler=std::move(handler)](FakeThread& thread, uint32_t) { Platform::IPC::CommandHeader header = { thread.ReadTLS(0x80) }; return handler(context, thread, header); }; service.Run(thread, std::move(InvokeCommandHandler)); } FakeCAM::FakeCAM(FakeThread& thread) { thread.name = "y2r:uThread"; Result result; std::tie(result, output_vaddr) = thread.CallSVC(&OS::SVCControlMemory, 0, 0, 0x47000, 3/*ALLOC*/, 0x3/*RW*/); if (result != RESULT_OK) { throw std::runtime_error("Failed to allocate y2r:u output buffer"); } std::tie(result, transfer_end_event) = thread.CallSVC(&OS::SVCCreateEvent, ResetType::OneShot); if (result != RESULT_OK) thread.CallSVC(&OS::SVCBreak, OS::BreakReason::Panic); transfer_end_event.second->name = "Y2RTransferEndEvent"; IPCThread(*this, thread, "y2r:u", Y2RCommandHandler); } template<> std::shared_ptr CreateFakeProcessViaContext(OS& os, Interpreter::Setup& setup, uint32_t pid, const std::string& name) { return WrappedFakeProcess::CreateWithContext(os, setup, pid, name); } } // namespace OS } // namespace HLE