// Copyright 2015 Dolphin Emulator Project // Licensed under GPLv2+ // Refer to the license.txt file included. #include #include #include #include #include "Common/ChunkFile.h" #include "Common/CommonTypes.h" #include "Common/Event.h" #include "Common/Flag.h" #include "Common/Logging/Log.h" #include "Common/MsgHandler.h" #include "Common/Thread.h" #include "Common/Timer.h" #include "Core/Core.h" #include "Core/CoreTiming.h" #include "Core/HW/DVDInterface.h" #include "Core/HW/DVDThread.h" #include "Core/HW/Memmap.h" #include "Core/HW/SystemTimers.h" #include "DiscIO/Volume.h" namespace DVDThread { static void DVDThread(); static void FinishRead(u64 userdata, s64 cycles_late); static CoreTiming::EventType* s_finish_read; static std::thread s_dvd_thread; static Common::Event s_dvd_thread_start_working; static Common::Event s_dvd_thread_done_working; static Common::Flag s_dvd_thread_exiting(false); static std::vector s_dvd_buffer; static u64 s_time_read_started; static bool s_dvd_success; static u64 s_dvd_offset; static u32 s_output_address; static u32 s_length; static bool s_decrypt; // This determines which function will be used as a callback. // We can't have a function pointer here, because they can't be in savestates. static bool s_reply_to_ios; // The following time variables are only used for logging static u64 s_realtime_started_us; static u64 s_realtime_done_us; void Start() { s_finish_read = CoreTiming::RegisterEvent("FinishReadDVDThread", FinishRead); _assert_(!s_dvd_thread.joinable()); s_dvd_thread = std::thread(DVDThread); } void Stop() { _assert_(s_dvd_thread.joinable()); // The DVD thread will return if s_DVD_thread_exiting // is set when it starts working s_dvd_thread_exiting.Set(); s_dvd_thread_start_working.Set(); s_dvd_thread.join(); s_dvd_thread_exiting.Clear(); } void DoState(PointerWrap& p) { WaitUntilIdle(); // TODO: Savestates can be smaller if s_DVD_buffer is not saved p.Do(s_dvd_buffer); p.Do(s_time_read_started); p.Do(s_dvd_success); p.Do(s_dvd_offset); p.Do(s_output_address); p.Do(s_length); p.Do(s_decrypt); p.Do(s_reply_to_ios); // s_realtime_started_us and s_realtime_done_us aren't savestated // because they rely on the current system's time. // This means that loading a savestate might cause // incorrect times to be logged once. } void WaitUntilIdle() { _assert_(Core::IsCPUThread()); // Wait until DVD thread isn't working s_dvd_thread_done_working.Wait(); // Set the event again so that we still know that the DVD thread isn't working s_dvd_thread_done_working.Set(); } void StartRead(u64 dvd_offset, u32 output_address, u32 length, bool decrypt, bool reply_to_ios, int ticks_until_completion) { _assert_(Core::IsCPUThread()); s_dvd_thread_done_working.Wait(); s_dvd_offset = dvd_offset; s_output_address = output_address; s_length = length; s_decrypt = decrypt; s_reply_to_ios = reply_to_ios; s_time_read_started = CoreTiming::GetTicks(); s_realtime_started_us = Common::Timer::GetTimeUs(); s_dvd_thread_start_working.Set(); CoreTiming::ScheduleEvent(ticks_until_completion, s_finish_read); } static void FinishRead(u64 userdata, s64 cycles_late) { WaitUntilIdle(); DEBUG_LOG(DVDINTERFACE, "Disc has been read. Real time: %" PRIu64 " us. " "Real time including delay: %" PRIu64 " us. Emulated time including delay: %" PRIu64 " us.", s_realtime_done_us - s_realtime_started_us, Common::Timer::GetTimeUs() - s_realtime_started_us, (CoreTiming::GetTicks() - s_time_read_started) / (SystemTimers::GetTicksPerSecond() / 1000 / 1000)); if (s_dvd_success) Memory::CopyToEmu(s_output_address, s_dvd_buffer.data(), s_length); else PanicAlertT("The disc could not be read (at 0x%" PRIx64 " - 0x%" PRIx64 ").", s_dvd_offset, s_dvd_offset + s_length); // This will make the buffer take less space in savestates. // Reducing the size doesn't change the amount of reserved memory, // so this doesn't lead to extra memory allocations. s_dvd_buffer.resize(0); // Notify the emulated software that the command has been executed DVDInterface::FinishExecutingCommand(s_reply_to_ios, DVDInterface::INT_TCINT); } static void DVDThread() { Common::SetCurrentThreadName("DVD thread"); while (true) { s_dvd_thread_done_working.Set(); s_dvd_thread_start_working.Wait(); if (s_dvd_thread_exiting.IsSet()) return; s_dvd_buffer.resize(s_length); s_dvd_success = DVDInterface::GetVolume().Read(s_dvd_offset, s_length, s_dvd_buffer.data(), s_decrypt); s_realtime_done_us = Common::Timer::GetTimeUs(); } } }