diff options
Diffstat (limited to '')
75 files changed, 1837 insertions, 976 deletions
diff --git a/src/citra/CMakeLists.txt b/src/citra/CMakeLists.txt index bbb3374f2..713f49193 100644 --- a/src/citra/CMakeLists.txt +++ b/src/citra/CMakeLists.txt @@ -16,20 +16,6 @@ create_directory_groups(${SRCS} ${HEADERS}) add_executable(citra ${SRCS} ${HEADERS}) target_link_libraries(citra core common video_core) target_link_libraries(citra ${GLFW_LIBRARIES} ${OPENGL_gl_LIBRARY} inih) - -if (UNIX) - target_link_libraries(citra -pthread) -endif() - -if (APPLE) - target_link_libraries(citra iconv ${COREFOUNDATION_LIBRARY}) -elseif (WIN32) - target_link_libraries(citra winmm wsock32 ws2_32) - if (MINGW) # GCC does not support codecvt, so use iconv instead - target_link_libraries(citra iconv) - endif() -else() # Unix - target_link_libraries(citra rt) -endif() +target_link_libraries(citra ${PLATFORM_LIBRARIES}) #install(TARGETS citra RUNTIME DESTINATION ${bindir}) diff --git a/src/citra/citra.rc b/src/citra/citra.rc Binary files differindex c28e7dbe5..0165e93da 100644 --- a/src/citra/citra.rc +++ b/src/citra/citra.rc diff --git a/src/citra_qt/CMakeLists.txt b/src/citra_qt/CMakeLists.txt index a0ba252b3..bbc521f8a 100644 --- a/src/citra_qt/CMakeLists.txt +++ b/src/citra_qt/CMakeLists.txt @@ -60,17 +60,6 @@ endif() add_executable(citra-qt ${SRCS} ${HEADERS} ${UI_HDRS}) target_link_libraries(citra-qt core common video_core qhexedit) target_link_libraries(citra-qt ${OPENGL_gl_LIBRARY} ${CITRA_QT_LIBS}) - -if (UNIX) - target_link_libraries(citra-qt -pthread) -endif() - -if (APPLE) - target_link_libraries(citra-qt iconv ${COREFOUNDATION_LIBRARY}) -elseif (WIN32) - target_link_libraries(citra-qt winmm wsock32 ws2_32) -else() # Unix - target_link_libraries(citra-qt rt) -endif() +target_link_libraries(citra-qt ${PLATFORM_LIBRARIES}) #install(TARGETS citra-qt RUNTIME DESTINATION ${bindir}) diff --git a/src/citra_qt/bootmanager.cpp b/src/citra_qt/bootmanager.cpp index 3e24da596..196380105 100644 --- a/src/citra_qt/bootmanager.cpp +++ b/src/citra_qt/bootmanager.cpp @@ -40,18 +40,35 @@ void EmuThread::SetFilename(std::string filename) void EmuThread::run() { stop_run = false; + + // holds whether the cpu was running during the last iteration, + // so that the DebugModeLeft signal can be emitted before the + // next execution step + bool was_active = false; while (!stop_run) { if (cpu_running) { + if (!was_active) + emit DebugModeLeft(); + Core::RunLoop(); + + was_active = cpu_running || exec_cpu_step; + if (!was_active) + emit DebugModeEntered(); } else if (exec_cpu_step) { + if (!was_active) + emit DebugModeLeft(); + exec_cpu_step = false; Core::SingleStep(); - emit CPUStepped(); + emit DebugModeEntered(); yieldCurrentThread(); + + was_active = false; } } render_window->moveContext(); diff --git a/src/citra_qt/bootmanager.h b/src/citra_qt/bootmanager.h index 1c893384c..a55db682a 100644 --- a/src/citra_qt/bootmanager.h +++ b/src/citra_qt/bootmanager.h @@ -81,12 +81,18 @@ private: signals: /** - * Emitted when CPU when we've finished processing a single Gekko instruction + * Emitted when the CPU has halted execution * - * @warning This will only be emitted when the CPU is not running (SetCpuRunning(false)) * @warning When connecting to this signal from other threads, make sure to specify either Qt::QueuedConnection (invoke slot within the destination object's message thread) or even Qt::BlockingQueuedConnection (additionally block source thread until slot returns) */ - void CPUStepped(); + void DebugModeEntered(); + + /** + * Emitted right before the CPU continues execution + * + * @warning When connecting to this signal from other threads, make sure to specify either Qt::QueuedConnection (invoke slot within the destination object's message thread) or even Qt::BlockingQueuedConnection (additionally block source thread until slot returns) + */ + void DebugModeLeft(); }; class GRenderWindow : public QWidget, public EmuWindow diff --git a/src/citra_qt/debugger/callstack.cpp b/src/citra_qt/debugger/callstack.cpp index 274c5cccd..9bb22ca2e 100644 --- a/src/citra_qt/debugger/callstack.cpp +++ b/src/citra_qt/debugger/callstack.cpp @@ -25,7 +25,7 @@ CallstackWidget::CallstackWidget(QWidget* parent): QDockWidget(parent) ui.treeView->setModel(callstack_model); } -void CallstackWidget::OnCPUStepped() +void CallstackWidget::OnDebugModeEntered() { ARM_Disasm* disasm = new ARM_Disasm(); ARM_Interface* app_core = Core::g_app_core; @@ -33,6 +33,8 @@ void CallstackWidget::OnCPUStepped() u32 sp = app_core->GetReg(13); //stack pointer u32 ret_addr, call_addr, func_addr; + Clear(); + int counter = 0; for (u32 addr = 0x10000000; addr >= sp; addr -= 4) { @@ -71,3 +73,17 @@ void CallstackWidget::OnCPUStepped() } } } + +void CallstackWidget::OnDebugModeLeft() +{ + +} + +void CallstackWidget::Clear() +{ + for (int row = 0; row < callstack_model->rowCount(); row++) { + for (int column = 0; column < callstack_model->columnCount(); column++) { + callstack_model->setItem(row, column, new QStandardItem()); + } + } +} diff --git a/src/citra_qt/debugger/callstack.h b/src/citra_qt/debugger/callstack.h index 4f4f74823..1a9b6dc81 100644 --- a/src/citra_qt/debugger/callstack.h +++ b/src/citra_qt/debugger/callstack.h @@ -15,9 +15,13 @@ public: CallstackWidget(QWidget* parent = 0); public slots: - void OnCPUStepped(); + void OnDebugModeEntered(); + void OnDebugModeLeft(); private: Ui::CallStack ui; QStandardItemModel* callstack_model; + + /// Clears the callstack widget while keeping the column widths the same + void Clear(); }; diff --git a/src/citra_qt/debugger/disassembler.cpp b/src/citra_qt/debugger/disassembler.cpp index 8db73752f..c61ace925 100644 --- a/src/citra_qt/debugger/disassembler.cpp +++ b/src/citra_qt/debugger/disassembler.cpp @@ -13,6 +13,7 @@ #include "core/core.h" #include "common/break_points.h" #include "common/symbols.h" +#include "core/arm/arm_interface.h" #include "core/arm/skyeye_common/armdefs.h" #include "core/arm/disassembler/arm_disasm.h" @@ -234,7 +235,7 @@ void DisassemblerWidget::OnToggleStartStop() emu_thread.SetCpuRunning(!emu_thread.IsCpuRunning()); } -void DisassemblerWidget::OnCPUStepped() +void DisassemblerWidget::OnDebugModeEntered() { ARMword next_instr = Core::g_app_core->GetPC(); @@ -251,6 +252,11 @@ void DisassemblerWidget::OnCPUStepped() disasm_ui.treeView->selectionModel()->setCurrentIndex(model_index, QItemSelectionModel::SelectCurrent | QItemSelectionModel::Rows); } +void DisassemblerWidget::OnDebugModeLeft() +{ + +} + int DisassemblerWidget::SelectedRow() { QModelIndex index = disasm_ui.treeView->selectionModel()->currentIndex(); diff --git a/src/citra_qt/debugger/disassembler.h b/src/citra_qt/debugger/disassembler.h index 6d3cef108..0deccc240 100644 --- a/src/citra_qt/debugger/disassembler.h +++ b/src/citra_qt/debugger/disassembler.h @@ -61,7 +61,8 @@ public slots: void OnPause(); void OnToggleStartStop(); - void OnCPUStepped(); + void OnDebugModeEntered(); + void OnDebugModeLeft(); private: // returns -1 if no row is selected diff --git a/src/citra_qt/debugger/graphics_cmdlists.cpp b/src/citra_qt/debugger/graphics_cmdlists.cpp index 4a6159fdf..bd420f24a 100644 --- a/src/citra_qt/debugger/graphics_cmdlists.cpp +++ b/src/citra_qt/debugger/graphics_cmdlists.cpp @@ -76,6 +76,8 @@ TextureInfoDockWidget::TextureInfoDockWidget(const Pica::DebugUtils::TextureInfo format_choice->addItem(tr("IA4")); format_choice->addItem(tr("UNK10")); format_choice->addItem(tr("A4")); + format_choice->addItem(tr("ETC1")); + format_choice->addItem(tr("ETC1A4")); format_choice->setCurrentIndex(static_cast<int>(info.format)); connect(format_choice, SIGNAL(currentIndexChanged(int)), this, SLOT(OnFormatChanged(int))); diff --git a/src/citra_qt/debugger/graphics_framebuffer.cpp b/src/citra_qt/debugger/graphics_framebuffer.cpp index caa6896f9..a9423d6c7 100644 --- a/src/citra_qt/debugger/graphics_framebuffer.cpp +++ b/src/citra_qt/debugger/graphics_framebuffer.cpp @@ -158,17 +158,17 @@ void GraphicsFramebufferWidget::OnFramebufferAddressChanged(qint64 new_value) } } -void GraphicsFramebufferWidget::OnFramebufferWidthChanged(unsigned int new_value) +void GraphicsFramebufferWidget::OnFramebufferWidthChanged(int new_value) { - if (framebuffer_width != new_value) { - framebuffer_width = new_value; + if (framebuffer_width != static_cast<unsigned>(new_value)) { + framebuffer_width = static_cast<unsigned>(new_value); framebuffer_source_list->setCurrentIndex(static_cast<int>(Source::Custom)); emit Update(); } } -void GraphicsFramebufferWidget::OnFramebufferHeightChanged(unsigned int new_value) +void GraphicsFramebufferWidget::OnFramebufferHeightChanged(int new_value) { if (framebuffer_height != new_value) { framebuffer_height = new_value; diff --git a/src/citra_qt/debugger/graphics_framebuffer.h b/src/citra_qt/debugger/graphics_framebuffer.h index 02813525c..56215761e 100644 --- a/src/citra_qt/debugger/graphics_framebuffer.h +++ b/src/citra_qt/debugger/graphics_framebuffer.h @@ -62,8 +62,8 @@ public: public slots: void OnFramebufferSourceChanged(int new_value); void OnFramebufferAddressChanged(qint64 new_value); - void OnFramebufferWidthChanged(unsigned int new_value); - void OnFramebufferHeightChanged(unsigned int new_value); + void OnFramebufferWidthChanged(int new_value); + void OnFramebufferHeightChanged(int new_value); void OnFramebufferFormatChanged(int new_value); void OnUpdate(); diff --git a/src/citra_qt/debugger/registers.cpp b/src/citra_qt/debugger/registers.cpp index e982dfb3f..ab3666156 100644 --- a/src/citra_qt/debugger/registers.cpp +++ b/src/citra_qt/debugger/registers.cpp @@ -41,7 +41,7 @@ RegistersWidget::RegistersWidget(QWidget* parent) : QDockWidget(parent) CSPR->addChild(new QTreeWidgetItem(QStringList("N"))); } -void RegistersWidget::OnCPUStepped() +void RegistersWidget::OnDebugModeEntered() { ARM_Interface* app_core = Core::g_app_core; @@ -65,3 +65,8 @@ void RegistersWidget::OnCPUStepped() CSPR->child(13)->setText(1, QString("%1").arg((app_core->GetCPSR() >> 30) & 0x1)); // Z - Zero CSPR->child(14)->setText(1, QString("%1").arg((app_core->GetCPSR() >> 31) & 0x1)); // N - Negative/Less than } + +void RegistersWidget::OnDebugModeLeft() +{ + +} diff --git a/src/citra_qt/debugger/registers.h b/src/citra_qt/debugger/registers.h index ac8429f2b..bf8955625 100644 --- a/src/citra_qt/debugger/registers.h +++ b/src/citra_qt/debugger/registers.h @@ -17,7 +17,8 @@ public: RegistersWidget(QWidget* parent = NULL); public slots: - void OnCPUStepped(); + void OnDebugModeEntered(); + void OnDebugModeLeft(); private: Ui::ARMRegisters cpu_regs_ui; diff --git a/src/citra_qt/main.cpp b/src/citra_qt/main.cpp index c6671bef1..ece593e5d 100644 --- a/src/citra_qt/main.cpp +++ b/src/citra_qt/main.cpp @@ -124,9 +124,13 @@ GMainWindow::GMainWindow() connect(ui.action_Hotkeys, SIGNAL(triggered()), this, SLOT(OnOpenHotkeysDialog())); // BlockingQueuedConnection is important here, it makes sure we've finished refreshing our views before the CPU continues - connect(&render_window->GetEmuThread(), SIGNAL(CPUStepped()), disasmWidget, SLOT(OnCPUStepped()), Qt::BlockingQueuedConnection); - connect(&render_window->GetEmuThread(), SIGNAL(CPUStepped()), registersWidget, SLOT(OnCPUStepped()), Qt::BlockingQueuedConnection); - connect(&render_window->GetEmuThread(), SIGNAL(CPUStepped()), callstackWidget, SLOT(OnCPUStepped()), Qt::BlockingQueuedConnection); + connect(&render_window->GetEmuThread(), SIGNAL(DebugModeEntered()), disasmWidget, SLOT(OnDebugModeEntered()), Qt::BlockingQueuedConnection); + connect(&render_window->GetEmuThread(), SIGNAL(DebugModeEntered()), registersWidget, SLOT(OnDebugModeEntered()), Qt::BlockingQueuedConnection); + connect(&render_window->GetEmuThread(), SIGNAL(DebugModeEntered()), callstackWidget, SLOT(OnDebugModeEntered()), Qt::BlockingQueuedConnection); + + connect(&render_window->GetEmuThread(), SIGNAL(DebugModeLeft()), disasmWidget, SLOT(OnDebugModeLeft()), Qt::BlockingQueuedConnection); + connect(&render_window->GetEmuThread(), SIGNAL(DebugModeLeft()), registersWidget, SLOT(OnDebugModeLeft()), Qt::BlockingQueuedConnection); + connect(&render_window->GetEmuThread(), SIGNAL(DebugModeLeft()), callstackWidget, SLOT(OnDebugModeLeft()), Qt::BlockingQueuedConnection); // Setup hotkeys RegisterHotkey("Main Window", "Load File", QKeySequence::Open); @@ -167,8 +171,8 @@ void GMainWindow::BootGame(std::string filename) } disasmWidget->Init(); - registersWidget->OnCPUStepped(); - callstackWidget->OnCPUStepped(); + registersWidget->OnDebugModeEntered(); + callstackWidget->OnDebugModeEntered(); render_window->GetEmuThread().SetFilename(filename); render_window->GetEmuThread().start(); diff --git a/src/common/common.h b/src/common/common.h index bf48ae667..3246c7797 100644 --- a/src/common/common.h +++ b/src/common/common.h @@ -11,13 +11,6 @@ #include <cstdio> #include <cstring> -// Force enable logging in the right modes. For some reason, something had changed -// so that debugfast no longer logged. -#if defined(_DEBUG) || defined(DEBUGFAST) -#undef LOGGING -#define LOGGING 1 -#endif - #define STACKALIGN // An inheritable class to disallow the copy constructor and operator= functions diff --git a/src/common/log.h b/src/common/log.h index 667f2fbb9..b397cf14d 100644 --- a/src/common/log.h +++ b/src/common/log.h @@ -14,7 +14,7 @@ #endif #endif -#if _DEBUG +#ifdef _DEBUG #define _dbg_assert_(_t_, _a_) \ if (!(_a_)) {\ LOG_CRITICAL(_t_, "Error...\n\n Line: %d\n File: %s\n Time: %s\n\nIgnore and continue?", \ diff --git a/src/common/logging/log.h b/src/common/logging/log.h index bda3d633a..3d94bf0d9 100644 --- a/src/common/logging/log.h +++ b/src/common/logging/log.h @@ -74,17 +74,6 @@ enum class Class : ClassType { }; /** - * Level below which messages are simply discarded without buffering regardless of the display - * settings. - */ -const Level MINIMUM_LEVEL = -#ifdef _DEBUG - Level::Trace; -#else - Level::Debug; -#endif - -/** * Logs a message to the global logger. This proxy exists to avoid exposing the details of the * Logger class, including the ConcurrentRingBuffer template, to all files that desire to log * messages, reducing unecessary recompilations. @@ -103,13 +92,15 @@ void LogMessage(Class log_class, Level log_level, } // namespace Log #define LOG_GENERIC(log_class, log_level, ...) \ - do { \ - if (::Log::Level::log_level >= ::Log::MINIMUM_LEVEL) \ - ::Log::LogMessage(::Log::Class::log_class, ::Log::Level::log_level, \ - __FILE__, __LINE__, __func__, __VA_ARGS__); \ - } while (0) + ::Log::LogMessage(::Log::Class::log_class, ::Log::Level::log_level, \ + __FILE__, __LINE__, __func__, __VA_ARGS__) +#ifdef _DEBUG #define LOG_TRACE( log_class, ...) LOG_GENERIC(log_class, Trace, __VA_ARGS__) +#else +#define LOG_TRACE( log_class, ...) (void(0)) +#endif + #define LOG_DEBUG( log_class, ...) LOG_GENERIC(log_class, Debug, __VA_ARGS__) #define LOG_INFO( log_class, ...) LOG_GENERIC(log_class, Info, __VA_ARGS__) #define LOG_WARNING( log_class, ...) LOG_GENERIC(log_class, Warning, __VA_ARGS__) diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index d248b454c..0fc8bf318 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -31,17 +31,20 @@ set(SRCS hle/kernel/mutex.cpp hle/kernel/semaphore.cpp hle/kernel/shared_memory.cpp + hle/kernel/timer.cpp hle/kernel/thread.cpp hle/service/ac_u.cpp hle/service/act_u.cpp hle/service/am_app.cpp hle/service/am_net.cpp hle/service/apt_a.cpp + hle/service/apt_s.cpp hle/service/apt_u.cpp hle/service/boss_u.cpp hle/service/cecd_u.cpp hle/service/cfg/cfg.cpp hle/service/cfg/cfg_i.cpp + hle/service/cfg/cfg_s.cpp hle/service/cfg/cfg_u.cpp hle/service/csnd_snd.cpp hle/service/dsp_dsp.cpp @@ -62,6 +65,7 @@ set(SRCS hle/service/nwm_uds.cpp hle/service/pm_app.cpp hle/service/ptm_u.cpp + hle/service/ptm_sysm.cpp hle/service/service.cpp hle/service/soc_u.cpp hle/service/srv.cpp @@ -124,17 +128,20 @@ set(HEADERS hle/kernel/semaphore.h hle/kernel/session.h hle/kernel/shared_memory.h + hle/kernel/timer.h hle/kernel/thread.h hle/service/ac_u.h hle/service/act_u.h hle/service/am_app.h hle/service/am_net.h hle/service/apt_a.h + hle/service/apt_s.h hle/service/apt_u.h hle/service/boss_u.h hle/service/cecd_u.h hle/service/cfg/cfg.h hle/service/cfg/cfg_i.h + hle/service/cfg/cfg_s.h hle/service/cfg/cfg_u.h hle/service/csnd_snd.h hle/service/dsp_dsp.h @@ -155,6 +162,7 @@ set(HEADERS hle/service/nwm_uds.h hle/service/pm_app.h hle/service/ptm_u.h + hle/service/ptm_sysm.h hle/service/service.h hle/service/soc_u.h hle/service/srv.h diff --git a/src/core/arm/arm_interface.h b/src/core/arm/arm_interface.h index d3bd4a9a3..e612f7439 100644 --- a/src/core/arm/arm_interface.h +++ b/src/core/arm/arm_interface.h @@ -7,7 +7,9 @@ #include "common/common.h" #include "common/common_types.h" -#include "core/hle/svc.h" +namespace Core { + struct ThreadContext; +} /// Generic ARM11 CPU interface class ARM_Interface : NonCopyable { @@ -87,13 +89,13 @@ public: * Saves the current CPU context * @param ctx Thread context to save */ - virtual void SaveContext(ThreadContext& ctx) = 0; + virtual void SaveContext(Core::ThreadContext& ctx) = 0; /** * Loads a CPU context * @param ctx Thread context to load */ - virtual void LoadContext(const ThreadContext& ctx) = 0; + virtual void LoadContext(const Core::ThreadContext& ctx) = 0; /// Prepare core for thread reschedule (if needed to correctly handle state) virtual void PrepareReschedule() = 0; diff --git a/src/core/arm/dyncom/arm_dyncom.cpp b/src/core/arm/dyncom/arm_dyncom.cpp index c779e3fd4..9c4cc90f2 100644 --- a/src/core/arm/dyncom/arm_dyncom.cpp +++ b/src/core/arm/dyncom/arm_dyncom.cpp @@ -9,13 +9,14 @@ #include "core/arm/dyncom/arm_dyncom.h" #include "core/arm/dyncom/arm_dyncom_interpreter.h" +#include "core/core.h" #include "core/core_timing.h" const static cpu_config_t s_arm11_cpu_info = { "armv6", "arm11", 0x0007b000, 0x0007f000, NONCACHE }; -ARM_DynCom::ARM_DynCom() : ticks(0) { +ARM_DynCom::ARM_DynCom() { state = std::unique_ptr<ARMul_State>(new ARMul_State); ARMul_EmulateInit(); @@ -74,11 +75,11 @@ void ARM_DynCom::SetCPSR(u32 cpsr) { } u64 ARM_DynCom::GetTicks() const { - return ticks; + // TODO(Subv): Remove ARM_DynCom::GetTicks() and use CoreTiming::GetTicks() directly once ARMemu is gone + return CoreTiming::GetTicks(); } void ARM_DynCom::AddTicks(u64 ticks) { - this->ticks += ticks; down_count -= ticks; if (down_count < 0) CoreTiming::Advance(); @@ -94,7 +95,7 @@ void ARM_DynCom::ExecuteInstructions(int num_instructions) { AddTicks(ticks_executed); } -void ARM_DynCom::SaveContext(ThreadContext& ctx) { +void ARM_DynCom::SaveContext(Core::ThreadContext& ctx) { memcpy(ctx.cpu_registers, state->Reg, sizeof(ctx.cpu_registers)); memcpy(ctx.fpu_registers, state->ExtReg, sizeof(ctx.fpu_registers)); @@ -110,7 +111,7 @@ void ARM_DynCom::SaveContext(ThreadContext& ctx) { ctx.mode = state->NextInstr; } -void ARM_DynCom::LoadContext(const ThreadContext& ctx) { +void ARM_DynCom::LoadContext(const Core::ThreadContext& ctx) { memcpy(state->Reg, ctx.cpu_registers, sizeof(ctx.cpu_registers)); memcpy(state->ExtReg, ctx.fpu_registers, sizeof(ctx.fpu_registers)); diff --git a/src/core/arm/dyncom/arm_dyncom.h b/src/core/arm/dyncom/arm_dyncom.h index 7284dcd07..f16fb070c 100644 --- a/src/core/arm/dyncom/arm_dyncom.h +++ b/src/core/arm/dyncom/arm_dyncom.h @@ -71,13 +71,13 @@ public: * Saves the current CPU context * @param ctx Thread context to save */ - void SaveContext(ThreadContext& ctx) override; + void SaveContext(Core::ThreadContext& ctx) override; /** * Loads a CPU context * @param ctx Thread context to load */ - void LoadContext(const ThreadContext& ctx) override; + void LoadContext(const Core::ThreadContext& ctx) override; /// Prepare core for thread reschedule (if needed to correctly handle state) void PrepareReschedule() override; @@ -89,8 +89,5 @@ public: void ExecuteInstructions(int num_instructions) override; private: - std::unique_ptr<ARMul_State> state; - u64 ticks; - }; diff --git a/src/core/arm/dyncom/arm_dyncom_interpreter.cpp b/src/core/arm/dyncom/arm_dyncom_interpreter.cpp index 7c710ccde..d0347566c 100644 --- a/src/core/arm/dyncom/arm_dyncom_interpreter.cpp +++ b/src/core/arm/dyncom/arm_dyncom_interpreter.cpp @@ -176,13 +176,11 @@ unsigned int DPO(ArithmeticShiftRightByImmediate)(arm_processor *cpu, unsigned i unsigned int shifter_operand; int shift_imm = BITS(sht_oper, 7, 11); if (shift_imm == 0) { - if (BIT(rm, 31)) { + if (BIT(rm, 31) == 0) shifter_operand = 0; - cpu->shifter_carry_out = BIT(rm, 31); - } else { + else shifter_operand = 0xFFFFFFFF; - cpu->shifter_carry_out = BIT(rm, 31); - } + cpu->shifter_carry_out = BIT(rm, 31); } else { shifter_operand = static_cast<int>(rm) >> shift_imm; cpu->shifter_carry_out = BIT(rm, shift_imm - 1); @@ -1728,25 +1726,21 @@ ARM_INST_PTR INTERPRETER_TRANSLATE(ldrb)(unsigned int inst, int index) } ARM_INST_PTR INTERPRETER_TRANSLATE(ldrbt)(unsigned int inst, int index) { - arm_inst *inst_base = (arm_inst *)AllocBuffer(sizeof(arm_inst) + sizeof(ldst_inst)); - ldst_inst *inst_cream = (ldst_inst *)inst_base->component; + arm_inst* inst_base = (arm_inst*)AllocBuffer(sizeof(arm_inst) + sizeof(ldst_inst)); + ldst_inst* inst_cream = (ldst_inst*)inst_base->component; inst_base->cond = BITS(inst, 28, 31); - inst_base->idx = index; - inst_base->br = NON_BRANCH; + inst_base->idx = index; + inst_base->br = NON_BRANCH; inst_cream->inst = inst; - if (I_BIT == 0) { + if (BITS(inst, 25, 27) == 2) { inst_cream->get_addr = LnSWoUB(ImmediatePostIndexed); + } else if (BITS(inst, 25, 27) == 3) { + inst_cream->get_addr = LnSWoUB(ScaledRegisterPostIndexed); } else { DEBUG_MSG; } - #if 0 - inst_cream->get_addr = get_calc_addr_op(inst); - if(inst == 0x54f13001) { - DEBUG_LOG(ARM11, "get_calc_addr_op:%llx\n", inst_cream->get_addr); - } - #endif if (BITS(inst, 12, 15) == 15) { inst_base->br = INDIRECT_BRANCH; @@ -1846,17 +1840,24 @@ ARM_INST_PTR INTERPRETER_TRANSLATE(ldrsh)(unsigned int inst, int index) } ARM_INST_PTR INTERPRETER_TRANSLATE(ldrt)(unsigned int inst, int index) { - arm_inst *inst_base = (arm_inst *)AllocBuffer(sizeof(arm_inst) + sizeof(ldst_inst)); - ldst_inst *inst_cream = (ldst_inst *)inst_base->component; + arm_inst* inst_base = (arm_inst*)AllocBuffer(sizeof(arm_inst) + sizeof(ldst_inst)); + ldst_inst* inst_cream = (ldst_inst*)inst_base->component; inst_base->cond = BITS(inst, 28, 31); - inst_base->idx = index; - inst_base->br = NON_BRANCH; + inst_base->idx = index; + inst_base->br = NON_BRANCH; inst_cream->inst = inst; - if (I_BIT == 0) { + if (BITS(inst, 25, 27) == 2) { inst_cream->get_addr = LnSWoUB(ImmediatePostIndexed); + } else if (BITS(inst, 25, 27) == 3) { + inst_cream->get_addr = LnSWoUB(ScaledRegisterPostIndexed); } else { + // Reaching this would indicate the thumb version + // of this instruction, however the 3DS CPU doesn't + // support this variant (the 3DS CPU is only ARMv6K, + // while this variant is added in ARMv6T2). + // So it's sufficient for citra to not implement this. DEBUG_MSG; } @@ -2714,17 +2715,19 @@ ARM_INST_PTR INTERPRETER_TRANSLATE(strb)(unsigned int inst, int index) } ARM_INST_PTR INTERPRETER_TRANSLATE(strbt)(unsigned int inst, int index) { - arm_inst *inst_base = (arm_inst *)AllocBuffer(sizeof(arm_inst) + sizeof(ldst_inst)); - ldst_inst *inst_cream = (ldst_inst *)inst_base->component; + arm_inst* inst_base = (arm_inst*)AllocBuffer(sizeof(arm_inst) + sizeof(ldst_inst)); + ldst_inst* inst_cream = (ldst_inst*)inst_base->component; inst_base->cond = BITS(inst, 28, 31); - inst_base->idx = index; - inst_base->br = NON_BRANCH; + inst_base->idx = index; + inst_base->br = NON_BRANCH; inst_cream->inst = inst; -// inst_cream->get_addr = get_calc_addr_op(inst); - if (I_BIT == 0) { + + if (BITS(inst, 25, 27) == 2) { inst_cream->get_addr = LnSWoUB(ImmediatePostIndexed); + } else if (BITS(inst, 25, 27) == 3) { + inst_cream->get_addr = LnSWoUB(ScaledRegisterPostIndexed); } else { DEBUG_MSG; } @@ -2796,17 +2799,24 @@ ARM_INST_PTR INTERPRETER_TRANSLATE(strh)(unsigned int inst, int index) } ARM_INST_PTR INTERPRETER_TRANSLATE(strt)(unsigned int inst, int index) { - arm_inst *inst_base = (arm_inst *)AllocBuffer(sizeof(arm_inst) + sizeof(ldst_inst)); - ldst_inst *inst_cream = (ldst_inst *)inst_base->component; + arm_inst* inst_base = (arm_inst*)AllocBuffer(sizeof(arm_inst) + sizeof(ldst_inst)); + ldst_inst* inst_cream = (ldst_inst*)inst_base->component; inst_base->cond = BITS(inst, 28, 31); - inst_base->idx = index; - inst_base->br = NON_BRANCH; + inst_base->idx = index; + inst_base->br = NON_BRANCH; inst_cream->inst = inst; - if (I_BIT == 0) { + if (BITS(inst, 25, 27) == 2) { inst_cream->get_addr = LnSWoUB(ImmediatePostIndexed); + } else if (BITS(inst, 25, 27) == 3) { + inst_cream->get_addr = LnSWoUB(ScaledRegisterPostIndexed); } else { + // Reaching this would indicate the thumb version + // of this instruction, however the 3DS CPU doesn't + // support this variant (the 3DS CPU is only ARMv6K, + // while this variant is added in ARMv6T2). + // So it's sufficient for citra to not implement this. DEBUG_MSG; } @@ -3905,21 +3915,9 @@ unsigned InterpreterMainLoop(ARMul_State* state) { } #endif - #define UPDATE_NFLAG(dst) (cpu->NFlag = BIT(dst, 31) ? 1 : 0) - #define UPDATE_ZFLAG(dst) (cpu->ZFlag = dst ? 0 : 1) - - #define UPDATE_CFLAG(dst, lop, rop) (cpu->CFlag = ((dst < lop) || (dst < rop))) - #define UPDATE_CFLAG_CARRY_FROM_ADD(lop, rop, flag) (cpu->CFlag = (((uint64_t) lop + (uint64_t) rop + (uint64_t) flag) > 0xffffffff) ) - #define UPDATE_CFLAG_NOT_BORROW_FROM_FLAG(lop, rop, flag) (cpu->CFlag = ((uint64_t) lop >= ((uint64_t) rop + (uint64_t) flag))) - #define UPDATE_CFLAG_NOT_BORROW_FROM(lop, rop) (cpu->CFlag = (lop >= rop)) - #define UPDATE_CFLAG_WITH_NOT(dst, lop, rop) (cpu->CFlag = !(dst < lop)) - #define UPDATE_CFLAG_WITH_SC (cpu->CFlag = cpu->shifter_carry_out) - - #define UPDATE_VFLAG(dst, lop, rop) (cpu->VFlag = (((lop < 0) && (rop < 0) && (dst >= 0)) || \ - ((lop >= 0) && (rop) >= 0 && (dst < 0)))) - #define UPDATE_VFLAG_WITH_NOT(dst, lop, rop) (cpu->VFlag = !(((lop < 0) && (rop < 0) && (dst >= 0)) || \ - ((lop >= 0) && (rop) >= 0 && (dst < 0)))) - #define UPDATE_VFLAG_OVERFLOW_FROM(dst, lop, rop) (cpu->VFlag = (((lop ^ rop) & (lop ^ dst)) >> 31)) + #define UPDATE_NFLAG(dst) (cpu->NFlag = BIT(dst, 31) ? 1 : 0) + #define UPDATE_ZFLAG(dst) (cpu->ZFlag = dst ? 0 : 1) + #define UPDATE_CFLAG_WITH_SC (cpu->CFlag = cpu->shifter_carry_out) #define SAVE_NZCVT cpu->Cpsr = (cpu->Cpsr & 0x0fffffdf) | \ (cpu->NFlag << 31) | \ @@ -3967,16 +3965,12 @@ unsigned InterpreterMainLoop(ARMul_State* state) { &&INIT_INST_LENGTH,&&END }; #endif - arm_inst * inst_base; - unsigned int lop, rop, dst; + arm_inst* inst_base; unsigned int addr; unsigned int phys_addr; - unsigned int last_pc = 0; unsigned int num_instrs = 0; - static unsigned int last_physical_base = 0, last_logical_base = 0; int ptr; - bool single_step = (cpu->NumInstrsToExecute == 1); LOAD_NZCVT; DISPATCH: @@ -4003,12 +3997,13 @@ unsigned InterpreterMainLoop(ARMul_State* state) { } ADC_INST: { - adc_inst *inst_cream = (adc_inst *)inst_base->component; - if ((inst_base->cond == 0xe) || CondPassed(cpu, inst_base->cond)) { - lop = RN; - unsigned int sht_op = SHIFTER_OPERAND; - rop = SHIFTER_OPERAND + cpu->CFlag; - RD = dst = lop + rop; + if (inst_base->cond == 0xE || CondPassed(cpu, inst_base->cond)) { + adc_inst* const inst_cream = (adc_inst*)inst_base->component; + + bool carry; + bool overflow; + RD = AddWithCarry(RN, SHIFTER_OPERAND, cpu->CFlag, &carry, &overflow); + if (inst_cream->S && (inst_cream->Rd == 15)) { if (CurrentModeHasSPSR) { cpu->Cpsr = cpu->Spsr_copy; @@ -4016,10 +4011,10 @@ unsigned InterpreterMainLoop(ARMul_State* state) { LOAD_NZCVT; } } else if (inst_cream->S) { - UPDATE_NFLAG(dst); - UPDATE_ZFLAG(dst); - UPDATE_CFLAG_CARRY_FROM_ADD(lop, sht_op, cpu->CFlag); - UPDATE_VFLAG((int)dst, (int)lop, (int)rop); + UPDATE_NFLAG(RD); + UPDATE_ZFLAG(RD); + cpu->CFlag = carry; + cpu->VFlag = overflow; } if (inst_cream->Rd == 15) { INC_PC(sizeof(adc_inst)); @@ -4033,14 +4028,17 @@ unsigned InterpreterMainLoop(ARMul_State* state) { } ADD_INST: { - add_inst *inst_cream = (add_inst *)inst_base->component; - if ((inst_base->cond == 0xe) || CondPassed(cpu, inst_base->cond)) { - lop = RN; - if (inst_cream->Rn == 15) { - lop += 2 * GET_INST_SIZE(cpu); - } - rop = SHIFTER_OPERAND; - RD = dst = lop + rop; + if (inst_base->cond == 0xE || CondPassed(cpu, inst_base->cond)) { + add_inst* const inst_cream = (add_inst*)inst_base->component; + + u32 rn_val = RN; + if (inst_cream->Rn == 15) + rn_val += 2 * GET_INST_SIZE(cpu); + + bool carry; + bool overflow; + RD = AddWithCarry(rn_val, SHIFTER_OPERAND, 0, &carry, &overflow); + if (inst_cream->S && (inst_cream->Rd == 15)) { if (CurrentModeHasSPSR) { cpu->Cpsr = cpu->Spsr_copy; @@ -4048,10 +4046,10 @@ unsigned InterpreterMainLoop(ARMul_State* state) { LOAD_NZCVT; } } else if (inst_cream->S) { - UPDATE_NFLAG(dst); - UPDATE_ZFLAG(dst); - UPDATE_CFLAG(dst, lop, rop); - UPDATE_VFLAG((int)dst, (int)lop, (int)rop); + UPDATE_NFLAG(RD); + UPDATE_ZFLAG(RD); + cpu->CFlag = carry; + cpu->VFlag = overflow; } if (inst_cream->Rd == 15) { INC_PC(sizeof(add_inst)); @@ -4067,9 +4065,9 @@ unsigned InterpreterMainLoop(ARMul_State* state) { { and_inst *inst_cream = (and_inst *)inst_base->component; if ((inst_base->cond == 0xe) || CondPassed(cpu, inst_base->cond)) { - lop = RN; - rop = SHIFTER_OPERAND; - RD = dst = lop & rop; + u32 lop = RN; + u32 rop = SHIFTER_OPERAND; + RD = lop & rop; if (inst_cream->S && (inst_cream->Rd == 15)) { if (CurrentModeHasSPSR) { cpu->Cpsr = cpu->Spsr_copy; @@ -4077,8 +4075,8 @@ unsigned InterpreterMainLoop(ARMul_State* state) { LOAD_NZCVT; } } else if (inst_cream->S) { - UPDATE_NFLAG(dst); - UPDATE_ZFLAG(dst); + UPDATE_NFLAG(RD); + UPDATE_ZFLAG(RD); UPDATE_CFLAG_WITH_SC; } if (inst_cream->Rd == 15) { @@ -4110,12 +4108,12 @@ unsigned InterpreterMainLoop(ARMul_State* state) { { bic_inst *inst_cream = (bic_inst *)inst_base->component; if ((inst_base->cond == 0xe) || CondPassed(cpu, inst_base->cond)) { - lop = RN; + u32 lop = RN; if (inst_cream->Rn == 15) { lop += 2 * GET_INST_SIZE(cpu); } - rop = SHIFTER_OPERAND; - RD = dst = lop & (~rop); + u32 rop = SHIFTER_OPERAND; + RD = lop & (~rop); if ((inst_cream->S) && (inst_cream->Rd == 15)) { if (CurrentModeHasSPSR) { cpu->Cpsr = cpu->Spsr_copy; @@ -4123,8 +4121,8 @@ unsigned InterpreterMainLoop(ARMul_State* state) { LOAD_NZCVT; } } else if (inst_cream->S) { - UPDATE_NFLAG(dst); - UPDATE_ZFLAG(dst); + UPDATE_NFLAG(RD); + UPDATE_ZFLAG(RD); UPDATE_CFLAG_WITH_SC; } if (inst_cream->Rd == 15) { @@ -4230,15 +4228,17 @@ unsigned InterpreterMainLoop(ARMul_State* state) { } CMN_INST: { - cmn_inst *inst_cream = (cmn_inst *)inst_base->component; - if ((inst_base->cond == 0xe) || CondPassed(cpu, inst_base->cond)) { - lop = RN; - rop = SHIFTER_OPERAND; - dst = lop + rop; - UPDATE_NFLAG(dst); - UPDATE_ZFLAG(dst); - UPDATE_CFLAG(dst, lop, rop); - UPDATE_VFLAG((int)dst, (int)lop, (int)rop); + if (inst_base->cond == 0xE || CondPassed(cpu, inst_base->cond)) { + cmn_inst* const inst_cream = (cmn_inst*)inst_base->component; + + bool carry; + bool overflow; + u32 result = AddWithCarry(RN, SHIFTER_OPERAND, 0, &carry, &overflow); + + UPDATE_NFLAG(result); + UPDATE_ZFLAG(result); + cpu->CFlag = carry; + cpu->VFlag = overflow; } cpu->Reg[15] += GET_INST_SIZE(cpu); INC_PC(sizeof(cmn_inst)); @@ -4247,19 +4247,21 @@ unsigned InterpreterMainLoop(ARMul_State* state) { } CMP_INST: { - if ((inst_base->cond == 0xe) || CondPassed(cpu, inst_base->cond)) { - cmp_inst *inst_cream = (cmp_inst *)inst_base->component; - lop = RN; - if (inst_cream->Rn == 15) { - lop += 2 * GET_INST_SIZE(cpu); - } - rop = SHIFTER_OPERAND; - dst = lop - rop; + if (inst_base->cond == 0xE || CondPassed(cpu, inst_base->cond)) { + cmp_inst* const inst_cream = (cmp_inst*)inst_base->component; - UPDATE_NFLAG(dst); - UPDATE_ZFLAG(dst); - UPDATE_CFLAG_NOT_BORROW_FROM(lop, rop); - UPDATE_VFLAG_OVERFLOW_FROM(dst, lop, rop); + u32 rn_val = RN; + if (inst_cream->Rn == 15) + rn_val += 2 * GET_INST_SIZE(cpu); + + bool carry; + bool overflow; + u32 result = AddWithCarry(rn_val, ~SHIFTER_OPERAND, 1, &carry, &overflow); + + UPDATE_NFLAG(result); + UPDATE_ZFLAG(result); + cpu->CFlag = carry; + cpu->VFlag = overflow; } cpu->Reg[15] += GET_INST_SIZE(cpu); INC_PC(sizeof(cmp_inst)); @@ -4317,12 +4319,12 @@ unsigned InterpreterMainLoop(ARMul_State* state) { { eor_inst *inst_cream = (eor_inst *)inst_base->component; if ((inst_base->cond == 0xe) || CondPassed(cpu, inst_base->cond)) { - lop = RN; + u32 lop = RN; if (inst_cream->Rn == 15) { lop += 2 * GET_INST_SIZE(cpu); } - rop = SHIFTER_OPERAND; - RD = dst = lop ^ rop; + u32 rop = SHIFTER_OPERAND; + RD = lop ^ rop; if (inst_cream->S && (inst_cream->Rd == 15)) { if (CurrentModeHasSPSR) { cpu->Cpsr = cpu->Spsr_copy; @@ -4330,8 +4332,8 @@ unsigned InterpreterMainLoop(ARMul_State* state) { LOAD_NZCVT; } } else if (inst_cream->S) { - UPDATE_NFLAG(dst); - UPDATE_ZFLAG(dst); + UPDATE_NFLAG(RD); + UPDATE_ZFLAG(RD); UPDATE_CFLAG_WITH_SC; } if (inst_cream->Rd == 15) { @@ -4848,10 +4850,10 @@ unsigned InterpreterMainLoop(ARMul_State* state) { LOG_ERROR(Core_ARM11, "invalid operands for MLA"); CITRA_IGNORE_EXIT(-1); } - RD = dst = static_cast<uint32_t>((rm * rs + rn) & 0xffffffff); + RD = static_cast<uint32_t>((rm * rs + rn) & 0xffffffff); if (inst_cream->S) { - UPDATE_NFLAG(dst); - UPDATE_ZFLAG(dst); + UPDATE_NFLAG(RD); + UPDATE_ZFLAG(RD); } if (inst_cream->Rd == 15) { INC_PC(sizeof(mla_inst)); @@ -4867,7 +4869,7 @@ unsigned InterpreterMainLoop(ARMul_State* state) { { mov_inst *inst_cream = (mov_inst *)inst_base->component; if ((inst_base->cond == 0xe) || CondPassed(cpu, inst_base->cond)) { - RD = dst = SHIFTER_OPERAND; + RD = SHIFTER_OPERAND; if (inst_cream->S && (inst_cream->Rd == 15)) { if (CurrentModeHasSPSR) { cpu->Cpsr = cpu->Spsr_copy; @@ -4875,8 +4877,8 @@ unsigned InterpreterMainLoop(ARMul_State* state) { LOAD_NZCVT; } } else if (inst_cream->S) { - UPDATE_NFLAG(dst); - UPDATE_ZFLAG(dst); + UPDATE_NFLAG(RD); + UPDATE_ZFLAG(RD); UPDATE_CFLAG_WITH_SC; } if (inst_cream->Rd == 15) { @@ -4964,39 +4966,41 @@ unsigned InterpreterMainLoop(ARMul_State* state) { } MSR_INST: { - msr_inst *inst_cream = (msr_inst *)inst_base->component; - const uint32_t UnallocMask = 0x06f0fc00, UserMask = 0xf80f0200, PrivMask = 0x000001df, StateMask = 0x01000020; - unsigned int inst = inst_cream->inst; - unsigned int operand; + if (inst_base->cond == 0xE || CondPassed(cpu, inst_base->cond)) { + msr_inst *inst_cream = (msr_inst *)inst_base->component; + const uint32_t UnallocMask = 0x06f0fc00, UserMask = 0xf80f0200, PrivMask = 0x000001df, StateMask = 0x01000020; + unsigned int inst = inst_cream->inst; + unsigned int operand; - if (BIT(inst, 25)) { - int rot_imm = BITS(inst, 8, 11) * 2; - operand = ROTATE_RIGHT_32(BITS(inst, 0, 7), rot_imm); - } else { - operand = cpu->Reg[BITS(inst, 0, 3)]; - } - uint32_t byte_mask = (BIT(inst, 16) ? 0xff : 0) | (BIT(inst, 17) ? 0xff00 : 0) - | (BIT(inst, 18) ? 0xff0000 : 0) | (BIT(inst, 19) ? 0xff000000 : 0); - uint32_t mask; - if (!inst_cream->R) { - if (InAPrivilegedMode(cpu)) { - if ((operand & StateMask) != 0) { - /// UNPREDICTABLE - DEBUG_MSG; - } else - mask = byte_mask & (UserMask | PrivMask); + if (BIT(inst, 25)) { + int rot_imm = BITS(inst, 8, 11) * 2; + operand = ROTATE_RIGHT_32(BITS(inst, 0, 7), rot_imm); } else { - mask = byte_mask & UserMask; + operand = cpu->Reg[BITS(inst, 0, 3)]; } - SAVE_NZCVT; + uint32_t byte_mask = (BIT(inst, 16) ? 0xff : 0) | (BIT(inst, 17) ? 0xff00 : 0) + | (BIT(inst, 18) ? 0xff0000 : 0) | (BIT(inst, 19) ? 0xff000000 : 0); + uint32_t mask; + if (!inst_cream->R) { + if (InAPrivilegedMode(cpu)) { + if ((operand & StateMask) != 0) { + /// UNPREDICTABLE + DEBUG_MSG; + } else + mask = byte_mask & (UserMask | PrivMask); + } else { + mask = byte_mask & UserMask; + } + SAVE_NZCVT; - cpu->Cpsr = (cpu->Cpsr & ~mask) | (operand & mask); - switch_mode(cpu, cpu->Cpsr & 0x1f); - LOAD_NZCVT; - } else { - if (CurrentModeHasSPSR) { - mask = byte_mask & (UserMask | PrivMask | StateMask); - cpu->Spsr_copy = (cpu->Spsr_copy & ~mask) | (operand & mask); + cpu->Cpsr = (cpu->Cpsr & ~mask) | (operand & mask); + switch_mode(cpu, cpu->Cpsr & 0x1f); + LOAD_NZCVT; + } else { + if (CurrentModeHasSPSR) { + mask = byte_mask & (UserMask | PrivMask | StateMask); + cpu->Spsr_copy = (cpu->Spsr_copy & ~mask) | (operand & mask); + } } } cpu->Reg[15] += GET_INST_SIZE(cpu); @@ -5010,10 +5014,10 @@ unsigned InterpreterMainLoop(ARMul_State* state) { if ((inst_base->cond == 0xe) || CondPassed(cpu, inst_base->cond)) { uint64_t rm = RM; uint64_t rs = RS; - RD = dst = static_cast<uint32_t>((rm * rs) & 0xffffffff); + RD = static_cast<uint32_t>((rm * rs) & 0xffffffff); if (inst_cream->S) { - UPDATE_NFLAG(dst); - UPDATE_ZFLAG(dst); + UPDATE_NFLAG(RD); + UPDATE_ZFLAG(RD); } if (inst_cream->Rd == 15) { INC_PC(sizeof(mul_inst)); @@ -5027,9 +5031,11 @@ unsigned InterpreterMainLoop(ARMul_State* state) { } MVN_INST: { - mvn_inst *inst_cream = (mvn_inst *)inst_base->component; - if ((inst_base->cond == 0xe) || CondPassed(cpu, inst_base->cond)) { - RD = dst = ~SHIFTER_OPERAND; + if (inst_base->cond == 0xE || CondPassed(cpu, inst_base->cond)) { + mvn_inst* const inst_cream = (mvn_inst*)inst_base->component; + + RD = ~SHIFTER_OPERAND; + if (inst_cream->S && (inst_cream->Rd == 15)) { if (CurrentModeHasSPSR) { cpu->Cpsr = cpu->Spsr_copy; @@ -5037,8 +5043,8 @@ unsigned InterpreterMainLoop(ARMul_State* state) { LOAD_NZCVT; } } else if (inst_cream->S) { - UPDATE_NFLAG(dst); - UPDATE_ZFLAG(dst); + UPDATE_NFLAG(RD); + UPDATE_ZFLAG(RD); UPDATE_CFLAG_WITH_SC; } if (inst_cream->Rd == 15) { @@ -5053,11 +5059,13 @@ unsigned InterpreterMainLoop(ARMul_State* state) { } ORR_INST: { - orr_inst *inst_cream = (orr_inst *)inst_base->component; - if ((inst_base->cond == 0xe) || CondPassed(cpu, inst_base->cond)) { - lop = RN; - rop = SHIFTER_OPERAND; - RD = dst = lop | rop; + if (inst_base->cond == 0xE || CondPassed(cpu, inst_base->cond)) { + orr_inst* const inst_cream = (orr_inst*)inst_base->component; + + u32 lop = RN; + u32 rop = SHIFTER_OPERAND; + RD = lop | rop; + if (inst_cream->S && (inst_cream->Rd == 15)) { if (CurrentModeHasSPSR) { cpu->Cpsr = cpu->Spsr_copy; @@ -5065,8 +5073,8 @@ unsigned InterpreterMainLoop(ARMul_State* state) { LOAD_NZCVT; } } else if (inst_cream->S) { - UPDATE_NFLAG(dst); - UPDATE_ZFLAG(dst); + UPDATE_NFLAG(RD); + UPDATE_ZFLAG(RD); UPDATE_CFLAG_WITH_SC; } if (inst_cream->Rd == 15) { @@ -5286,14 +5294,17 @@ unsigned InterpreterMainLoop(ARMul_State* state) { RFE_INST: RSB_INST: { - rsb_inst *inst_cream = (rsb_inst *)inst_base->component; - if ((inst_base->cond == 0xe) || CondPassed(cpu, inst_base->cond)) { - rop = RN; - lop = SHIFTER_OPERAND; - if (inst_cream->Rn == 15) { - rop += 2 * GET_INST_SIZE(cpu);; - } - RD = dst = lop - rop; + if (inst_base->cond == 0xE || CondPassed(cpu, inst_base->cond)) { + rsb_inst* const inst_cream = (rsb_inst*)inst_base->component; + + u32 rn_val = RN; + if (inst_cream->Rn == 15) + rn_val += 2 * GET_INST_SIZE(cpu); + + bool carry; + bool overflow; + RD = AddWithCarry(~rn_val, SHIFTER_OPERAND, 1, &carry, &overflow); + if (inst_cream->S && (inst_cream->Rd == 15)) { if (CurrentModeHasSPSR) { cpu->Cpsr = cpu->Spsr_copy; @@ -5301,10 +5312,10 @@ unsigned InterpreterMainLoop(ARMul_State* state) { LOAD_NZCVT; } } else if (inst_cream->S) { - UPDATE_NFLAG(dst); - UPDATE_ZFLAG(dst); - UPDATE_CFLAG_NOT_BORROW_FROM(lop, rop); - UPDATE_VFLAG_OVERFLOW_FROM(dst, lop, rop); + UPDATE_NFLAG(RD); + UPDATE_ZFLAG(RD); + cpu->CFlag = carry; + cpu->VFlag = overflow; } if (inst_cream->Rd == 15) { INC_PC(sizeof(rsb_inst)); @@ -5318,11 +5329,13 @@ unsigned InterpreterMainLoop(ARMul_State* state) { } RSC_INST: { - rsc_inst *inst_cream = (rsc_inst *)inst_base->component; - if ((inst_base->cond == 0xe) || CondPassed(cpu, inst_base->cond)) { - lop = RN; - rop = SHIFTER_OPERAND; - RD = dst = rop - lop - !cpu->CFlag; + if (inst_base->cond == 0xE || CondPassed(cpu, inst_base->cond)) { + rsc_inst* const inst_cream = (rsc_inst*)inst_base->component; + + bool carry; + bool overflow; + RD = AddWithCarry(~RN, SHIFTER_OPERAND, cpu->CFlag, &carry, &overflow); + if (inst_cream->S && (inst_cream->Rd == 15)) { if (CurrentModeHasSPSR) { cpu->Cpsr = cpu->Spsr_copy; @@ -5330,10 +5343,10 @@ unsigned InterpreterMainLoop(ARMul_State* state) { LOAD_NZCVT; } } else if (inst_cream->S) { - UPDATE_NFLAG(dst); - UPDATE_ZFLAG(dst); - UPDATE_CFLAG_NOT_BORROW_FROM_FLAG(rop, lop, !cpu->CFlag); - UPDATE_VFLAG_OVERFLOW_FROM((int)dst, (int)rop, (int)lop); + UPDATE_NFLAG(RD); + UPDATE_ZFLAG(RD); + cpu->CFlag = carry; + cpu->VFlag = overflow; } if (inst_cream->Rd == 15) { INC_PC(sizeof(rsc_inst)); @@ -5456,11 +5469,13 @@ unsigned InterpreterMainLoop(ARMul_State* state) { SBC_INST: { - sbc_inst *inst_cream = (sbc_inst *)inst_base->component; - if ((inst_base->cond == 0xe) || CondPassed(cpu, inst_base->cond)) { - lop = SHIFTER_OPERAND + !cpu->CFlag; - rop = RN; - RD = dst = rop - lop; + if (inst_base->cond == 0xE || CondPassed(cpu, inst_base->cond)) { + sbc_inst* const inst_cream = (sbc_inst*)inst_base->component; + + bool carry; + bool overflow; + RD = AddWithCarry(RN, ~SHIFTER_OPERAND, cpu->CFlag, &carry, &overflow); + if (inst_cream->S && (inst_cream->Rd == 15)) { if (CurrentModeHasSPSR) { cpu->Cpsr = cpu->Spsr_copy; @@ -5468,15 +5483,10 @@ unsigned InterpreterMainLoop(ARMul_State* state) { LOAD_NZCVT; } } else if (inst_cream->S) { - UPDATE_NFLAG(dst); - UPDATE_ZFLAG(dst); - - if(rop >= !cpu->CFlag) - UPDATE_CFLAG_NOT_BORROW_FROM(rop - !cpu->CFlag, SHIFTER_OPERAND); - else - UPDATE_CFLAG_NOT_BORROW_FROM(rop, !cpu->CFlag); - - UPDATE_VFLAG_OVERFLOW_FROM(dst, rop, lop); + UPDATE_NFLAG(RD); + UPDATE_ZFLAG(RD); + cpu->CFlag = carry; + cpu->VFlag = overflow; } if (inst_cream->Rd == 15) { INC_PC(sizeof(sbc_inst)); @@ -6254,14 +6264,17 @@ unsigned InterpreterMainLoop(ARMul_State* state) { } SUB_INST: { - sub_inst *inst_cream = (sub_inst *)inst_base->component; - if ((inst_base->cond == 0xe) || CondPassed(cpu, inst_base->cond)) { - lop = RN; - if (inst_cream->Rn == 15) { - lop += 8; - } - rop = SHIFTER_OPERAND; - RD = dst = lop - rop; + if (inst_base->cond == 0xE || CondPassed(cpu, inst_base->cond)) { + sub_inst* const inst_cream = (sub_inst*)inst_base->component; + + u32 rn_val = RN; + if (inst_cream->Rn == 15) + rn_val += 8; + + bool carry; + bool overflow; + RD = AddWithCarry(rn_val, ~SHIFTER_OPERAND, 1, &carry, &overflow); + if (inst_cream->S && (inst_cream->Rd == 15)) { if (CurrentModeHasSPSR) { cpu->Cpsr = cpu->Spsr_copy; @@ -6269,10 +6282,10 @@ unsigned InterpreterMainLoop(ARMul_State* state) { LOAD_NZCVT; } } else if (inst_cream->S) { - UPDATE_NFLAG(dst); - UPDATE_ZFLAG(dst); - UPDATE_CFLAG_NOT_BORROW_FROM(lop, rop); - UPDATE_VFLAG_OVERFLOW_FROM(dst, lop, rop); + UPDATE_NFLAG(RD); + UPDATE_ZFLAG(RD); + cpu->CFlag = carry; + cpu->VFlag = overflow; } if (inst_cream->Rd == 15) { INC_PC(sizeof(sub_inst)); @@ -6400,18 +6413,19 @@ unsigned InterpreterMainLoop(ARMul_State* state) { TEQ_INST: { - if ((inst_base->cond == 0xe) || CondPassed(cpu, inst_base->cond)) { - teq_inst *inst_cream = (teq_inst *)inst_base->component; - lop = RN; + if (inst_base->cond == 0xE || CondPassed(cpu, inst_base->cond)) { + teq_inst* const inst_cream = (teq_inst*)inst_base->component; + + u32 lop = RN; + u32 rop = SHIFTER_OPERAND; if (inst_cream->Rn == 15) lop += GET_INST_SIZE(cpu) * 2; - rop = SHIFTER_OPERAND; - dst = lop ^ rop; + u32 result = lop ^ rop; - UPDATE_NFLAG(dst); - UPDATE_ZFLAG(dst); + UPDATE_NFLAG(result); + UPDATE_ZFLAG(result); UPDATE_CFLAG_WITH_SC; } cpu->Reg[15] += GET_INST_SIZE(cpu); @@ -6421,18 +6435,19 @@ unsigned InterpreterMainLoop(ARMul_State* state) { } TST_INST: { - if ((inst_base->cond == 0xe) || CondPassed(cpu, inst_base->cond)) { - tst_inst *inst_cream = (tst_inst *)inst_base->component; - lop = RN; + if (inst_base->cond == 0xE || CondPassed(cpu, inst_base->cond)) { + tst_inst* const inst_cream = (tst_inst*)inst_base->component; + + u32 lop = RN; + u32 rop = SHIFTER_OPERAND; if (inst_cream->Rn == 15) lop += GET_INST_SIZE(cpu) * 2; - rop = SHIFTER_OPERAND; - dst = lop & rop; + u32 result = lop & rop; - UPDATE_NFLAG(dst); - UPDATE_ZFLAG(dst); + UPDATE_NFLAG(result); + UPDATE_ZFLAG(result); UPDATE_CFLAG_WITH_SC; } cpu->Reg[15] += GET_INST_SIZE(cpu); @@ -6696,10 +6711,10 @@ unsigned InterpreterMainLoop(ARMul_State* state) { { if (inst_base->cond == 0xE || CondPassed(cpu, inst_base->cond)) { umaal_inst* const inst_cream = (umaal_inst*)inst_base->component; - const u32 rm = RM; - const u32 rn = RN; - const u32 rd_lo = RDLO; - const u32 rd_hi = RDHI; + const u64 rm = RM; + const u64 rn = RN; + const u64 rd_lo = RDLO; + const u64 rd_hi = RDHI; const u64 result = (rm * rn) + rd_lo + rd_hi; RDLO = (result & 0xFFFFFFFF); diff --git a/src/core/arm/interpreter/arm_interpreter.cpp b/src/core/arm/interpreter/arm_interpreter.cpp index 80ebc359e..c76d371a2 100644 --- a/src/core/arm/interpreter/arm_interpreter.cpp +++ b/src/core/arm/interpreter/arm_interpreter.cpp @@ -4,6 +4,8 @@ #include "core/arm/interpreter/arm_interpreter.h" +#include "core/core.h" + const static cpu_config_t arm11_cpu_info = { "armv6", "arm11", 0x0007b000, 0x0007f000, NONCACHE }; @@ -75,7 +77,7 @@ void ARM_Interpreter::ExecuteInstructions(int num_instructions) { ARMul_Emulate32(state); } -void ARM_Interpreter::SaveContext(ThreadContext& ctx) { +void ARM_Interpreter::SaveContext(Core::ThreadContext& ctx) { memcpy(ctx.cpu_registers, state->Reg, sizeof(ctx.cpu_registers)); memcpy(ctx.fpu_registers, state->ExtReg, sizeof(ctx.fpu_registers)); @@ -91,7 +93,7 @@ void ARM_Interpreter::SaveContext(ThreadContext& ctx) { ctx.mode = state->NextInstr; } -void ARM_Interpreter::LoadContext(const ThreadContext& ctx) { +void ARM_Interpreter::LoadContext(const Core::ThreadContext& ctx) { memcpy(state->Reg, ctx.cpu_registers, sizeof(ctx.cpu_registers)); memcpy(state->ExtReg, ctx.fpu_registers, sizeof(ctx.fpu_registers)); diff --git a/src/core/arm/interpreter/arm_interpreter.h b/src/core/arm/interpreter/arm_interpreter.h index 019dad5df..e5ecc69c2 100644 --- a/src/core/arm/interpreter/arm_interpreter.h +++ b/src/core/arm/interpreter/arm_interpreter.h @@ -70,13 +70,13 @@ public: * Saves the current CPU context * @param ctx Thread context to save */ - void SaveContext(ThreadContext& ctx) override; + void SaveContext(Core::ThreadContext& ctx) override; /** * Loads a CPU context * @param ctx Thread context to load */ - void LoadContext(const ThreadContext& ctx) override; + void LoadContext(const Core::ThreadContext& ctx) override; /// Prepare core for thread reschedule (if needed to correctly handle state) void PrepareReschedule() override; diff --git a/src/core/arm/interpreter/armsupp.cpp b/src/core/arm/interpreter/armsupp.cpp index 68ac2a0ce..e2626eefb 100644 --- a/src/core/arm/interpreter/armsupp.cpp +++ b/src/core/arm/interpreter/armsupp.cpp @@ -418,6 +418,22 @@ ARMul_NegZero (ARMul_State * state, ARMword result) } } +// Add with carry, indicates if a carry-out or signed overflow occurred. +u32 AddWithCarry(u32 left, u32 right, u32 carry_in, bool* carry_out_occurred, bool* overflow_occurred) +{ + u64 unsigned_sum = (u64)left + (u64)right + (u64)carry_in; + s64 signed_sum = (s64)(s32)left + (s64)(s32)right + (s64)carry_in; + u64 result = (unsigned_sum & 0xFFFFFFFF); + + if (carry_out_occurred) + *carry_out_occurred = (result != unsigned_sum); + + if (overflow_occurred) + *overflow_occurred = ((s64)(s32)result != signed_sum); + + return (u32)result; +} + // Compute whether an addition of A and B, giving RESULT, overflowed. bool AddOverflow(ARMword a, ARMword b, ARMword result) { diff --git a/src/core/arm/skyeye_common/armdefs.h b/src/core/arm/skyeye_common/armdefs.h index 1b2cef451..560b51a9f 100644 --- a/src/core/arm/skyeye_common/armdefs.h +++ b/src/core/arm/skyeye_common/armdefs.h @@ -795,6 +795,7 @@ extern void ARMul_FixSPSR(ARMul_State*, ARMword, ARMword); extern void ARMul_ConsolePrint(ARMul_State*, const char*, ...); extern void ARMul_SelectProcessor(ARMul_State*, unsigned); +extern u32 AddWithCarry(u32, u32, u32, bool*, bool*); extern bool ARMul_AddOverflowQ(ARMword, ARMword); extern u8 ARMul_SignedSaturatedAdd8(u8, u8); diff --git a/src/core/arm/skyeye_common/vfp/vfpsingle.cpp b/src/core/arm/skyeye_common/vfp/vfpsingle.cpp index 08d0d719f..77b528607 100644 --- a/src/core/arm/skyeye_common/vfp/vfpsingle.cpp +++ b/src/core/arm/skyeye_common/vfp/vfpsingle.cpp @@ -959,70 +959,34 @@ vfp_single_multiply(struct vfp_single *vsd, struct vfp_single *vsn, struct vfp_s static u32 vfp_single_multiply_accumulate(ARMul_State* state, int sd, int sn, s32 m, u32 fpscr, u32 negate, const char *func) { - - { - struct vfp_single vsd, vsp, vsn, vsm; - u32 exceptions; - s32 v; - - - - v = vfp_get_float(state, sn); - pr_debug("VFP: s%u = %08x\n", sn, v); - vfp_single_unpack(&vsn, v); - if (vsn.exponent == 0 && vsn.significand) - vfp_single_normalise_denormal(&vsn); - - vfp_single_unpack(&vsm, m); - if (vsm.exponent == 0 && vsm.significand) - vfp_single_normalise_denormal(&vsm); - - exceptions = vfp_single_multiply(&vsp, &vsn, &vsm, fpscr); - - if (negate & NEG_MULTIPLY) - vsp.sign = vfp_sign_negate(vsp.sign); - - v = vfp_get_float(state, sd); - pr_debug("VFP: s%u = %08x\n", sd, v); - vfp_single_unpack(&vsn, v); - if (negate & NEG_SUBTRACT) - vsn.sign = vfp_sign_negate(vsn.sign); - - exceptions |= vfp_single_add(&vsd, &vsn, &vsp, fpscr); - - return vfp_single_normaliseround(state, sd, &vsd, fpscr, exceptions, func); - } - - struct vfp_double vsd, vsp, vsn, vsm; + vfp_single vsd, vsp, vsn, vsm; u32 exceptions; s32 v; - s64 vd; - s64 md; v = vfp_get_float(state, sn); - vd = vfp_single_to_doubleintern(state, v, fpscr); - vfp_double_unpack(&vsn, vd); + pr_debug("VFP: s%u = %08x\n", sn, v); + vfp_single_unpack(&vsn, v); + if (vsn.exponent == 0 && vsn.significand) + vfp_single_normalise_denormal(&vsn); + + vfp_single_unpack(&vsm, m); + if (vsm.exponent == 0 && vsm.significand) + vfp_single_normalise_denormal(&vsm); - md = vfp_single_to_doubleintern(state, m, fpscr); - vfp_double_unpack(&vsm, md); + exceptions = vfp_single_multiply(&vsp, &vsn, &vsm, fpscr); - exceptions = vfp_double_multiply(&vsp, &vsn, &vsm, fpscr); if (negate & NEG_MULTIPLY) vsp.sign = vfp_sign_negate(vsp.sign); v = vfp_get_float(state, sd); - vd = vfp_single_to_doubleintern(state, v, fpscr); - vfp_double_unpack(&vsn, vd); - + pr_debug("VFP: s%u = %08x\n", sd, v); + vfp_single_unpack(&vsn, v); if (negate & NEG_SUBTRACT) vsn.sign = vfp_sign_negate(vsn.sign); - exceptions |= vfp_double_add(&vsd, &vsn, &vsp, fpscr); - - s64 debug = vfp_double_pack(&vsd); - - return vfp_double_fcvtsinterncutting(state, sd, &vsd, fpscr); + exceptions |= vfp_single_add(&vsd, &vsn, &vsp, fpscr); + return vfp_single_normaliseround(state, sd, &vsd, fpscr, exceptions, func); } /* diff --git a/src/core/core.cpp b/src/core/core.cpp index 98f8a7dff..e9e5c35cc 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -8,6 +8,7 @@ #include "core/core_timing.h" #include "core/settings.h" +#include "core/arm/arm_interface.h" #include "core/arm/disassembler/arm_disasm.h" #include "core/arm/interpreter/arm_interpreter.h" #include "core/arm/dyncom/arm_dyncom.h" @@ -17,8 +18,6 @@ namespace Core { -static u64 last_ticks = 0; ///< Last CPU ticks -static ARM_Disasm* disasm = nullptr; ///< ARM disassembler ARM_Interface* g_app_core = nullptr; ///< ARM11 application core ARM_Interface* g_sys_core = nullptr; ///< ARM11 system (OS) core @@ -26,7 +25,7 @@ ARM_Interface* g_sys_core = nullptr; ///< ARM11 system (OS) core void RunLoop(int tight_loop) { // If the current thread is an idle thread, then don't execute instructions, // instead advance to the next event and try to yield to the next thread - if (Kernel::IsIdleThread(Kernel::GetCurrentThreadHandle())) { + if (Kernel::GetCurrentThread()->IsIdle()) { LOG_TRACE(Core_ARM11, "Idling"); CoreTiming::Idle(); CoreTiming::Advance(); @@ -60,7 +59,6 @@ void Stop() { int Init() { LOG_DEBUG(Core, "initialized OK"); - disasm = new ARM_Disasm(); g_sys_core = new ARM_Interpreter(); switch (Settings::values.cpu_core) { @@ -73,13 +71,10 @@ int Init() { break; } - last_ticks = Core::g_app_core->GetTicks(); - return 0; } void Shutdown() { - delete disasm; delete g_app_core; delete g_sys_core; diff --git a/src/core/core.h b/src/core/core.h index ecd58a73a..2f5e8bc6b 100644 --- a/src/core/core.h +++ b/src/core/core.h @@ -4,8 +4,9 @@ #pragma once -#include "core/arm/arm_interface.h" -#include "core/arm/skyeye_common/armdefs.h" +#include "common/common_types.h" + +class ARM_Interface; //////////////////////////////////////////////////////////////////////////////////////////////////// @@ -16,6 +17,21 @@ enum CPUCore { CPU_OldInterpreter, }; +struct ThreadContext { + u32 cpu_registers[13]; + u32 sp; + u32 lr; + u32 pc; + u32 cpsr; + u32 fpu_registers[32]; + u32 fpscr; + u32 fpexc; + + // These are not part of native ThreadContext, but needed by emu + u32 reg_15; + u32 mode; +}; + extern ARM_Interface* g_app_core; ///< ARM11 application core extern ARM_Interface* g_sys_core; ///< ARM11 system (OS) core diff --git a/src/core/core_timing.cpp b/src/core/core_timing.cpp index 833199680..ec9d52a08 100644 --- a/src/core/core_timing.cpp +++ b/src/core/core_timing.cpp @@ -9,6 +9,8 @@ #include "common/chunk_file.h" #include "common/log.h" + +#include "core/arm/arm_interface.h" #include "core/core.h" #include "core/core_timing.h" diff --git a/src/core/hle/function_wrappers.h b/src/core/hle/function_wrappers.h index 0f822f84b..a2f51b41b 100644 --- a/src/core/hle/function_wrappers.h +++ b/src/core/hle/function_wrappers.h @@ -5,6 +5,8 @@ #pragma once #include "common/common_types.h" + +#include "core/arm/arm_interface.h" #include "core/mem_map.h" #include "core/hle/hle.h" @@ -135,6 +137,12 @@ template<s32 func(u32*, u32, u32, u32, u32)> void Wrap() { FuncReturn(retval); } +template<s32 func(u32, s64, s64)> void Wrap() { + s64 param1 = ((u64)PARAM(3) << 32) | PARAM(2); + s64 param2 = ((u64)PARAM(4) << 32) | PARAM(1); + FuncReturn(func(PARAM(0), param1, param2)); +} + //////////////////////////////////////////////////////////////////////////////////////////////////// // Function wrappers that return type u32 diff --git a/src/core/hle/hle.cpp b/src/core/hle/hle.cpp index f76048d14..11570c8b4 100644 --- a/src/core/hle/hle.cpp +++ b/src/core/hle/hle.cpp @@ -4,6 +4,7 @@ #include <vector> +#include "core/arm/arm_interface.h" #include "core/mem_map.h" #include "core/hle/hle.h" #include "core/hle/shared_page.h" diff --git a/src/core/hle/hle.h b/src/core/hle/hle.h index 59b770f02..3f6f9a4b5 100644 --- a/src/core/hle/hle.h +++ b/src/core/hle/hle.h @@ -4,6 +4,8 @@ #pragma once +#include <string> + #include "common/common_types.h" #include "core/core.h" diff --git a/src/core/hle/kernel/address_arbiter.cpp b/src/core/hle/kernel/address_arbiter.cpp index 736bbc36a..b7434aaf2 100644 --- a/src/core/hle/kernel/address_arbiter.cpp +++ b/src/core/hle/kernel/address_arbiter.cpp @@ -29,35 +29,56 @@ public: //////////////////////////////////////////////////////////////////////////////////////////////////// /// Arbitrate an address -ResultCode ArbitrateAddress(Handle handle, ArbitrationType type, u32 address, s32 value) { +ResultCode ArbitrateAddress(Handle handle, ArbitrationType type, u32 address, s32 value, u64 nanoseconds) { + Object* object = Kernel::g_handle_table.GetGeneric(handle).get(); + if (object == nullptr) + return InvalidHandle(ErrorModule::Kernel); + switch (type) { // Signal thread(s) waiting for arbitrate address... case ArbitrationType::Signal: // Negative value means resume all threads if (value < 0) { - ArbitrateAllThreads(handle, address); + ArbitrateAllThreads(object, address); } else { // Resume first N threads for(int i = 0; i < value; i++) - ArbitrateHighestPriorityThread(handle, address); + ArbitrateHighestPriorityThread(object, address); } break; // Wait current thread (acquire the arbiter)... case ArbitrationType::WaitIfLessThan: if ((s32)Memory::Read32(address) <= value) { - Kernel::WaitCurrentThread(WAITTYPE_ARB, handle, address); + Kernel::WaitCurrentThread(WAITTYPE_ARB, object, address); + HLE::Reschedule(__func__); + } + break; + case ArbitrationType::WaitIfLessThanWithTimeout: + if ((s32)Memory::Read32(address) <= value) { + Kernel::WaitCurrentThread(WAITTYPE_ARB, object, address); + Kernel::WakeThreadAfterDelay(GetCurrentThread(), nanoseconds); HLE::Reschedule(__func__); } break; - case ArbitrationType::DecrementAndWaitIfLessThan: { s32 memory_value = Memory::Read32(address) - 1; Memory::Write32(address, memory_value); if (memory_value <= value) { - Kernel::WaitCurrentThread(WAITTYPE_ARB, handle, address); + Kernel::WaitCurrentThread(WAITTYPE_ARB, object, address); + HLE::Reschedule(__func__); + } + break; + } + case ArbitrationType::DecrementAndWaitIfLessThanWithTimeout: + { + s32 memory_value = Memory::Read32(address) - 1; + Memory::Write32(address, memory_value); + if (memory_value <= value) { + Kernel::WaitCurrentThread(WAITTYPE_ARB, object, address); + Kernel::WakeThreadAfterDelay(GetCurrentThread(), nanoseconds); HLE::Reschedule(__func__); } break; diff --git a/src/core/hle/kernel/address_arbiter.h b/src/core/hle/kernel/address_arbiter.h index 030e7ad7b..3ffd465a2 100644 --- a/src/core/hle/kernel/address_arbiter.h +++ b/src/core/hle/kernel/address_arbiter.h @@ -28,7 +28,7 @@ enum class ArbitrationType : u32 { }; /// Arbitrate an address -ResultCode ArbitrateAddress(Handle handle, ArbitrationType type, u32 address, s32 value); +ResultCode ArbitrateAddress(Handle handle, ArbitrationType type, u32 address, s32 value, u64 nanoseconds); /// Create an address arbiter Handle CreateAddressArbiter(const std::string& name = "Unknown"); diff --git a/src/core/hle/kernel/event.cpp b/src/core/hle/kernel/event.cpp index e43c3ee4e..271190dbe 100644 --- a/src/core/hle/kernel/event.cpp +++ b/src/core/hle/kernel/event.cpp @@ -33,11 +33,11 @@ public: ResultVal<bool> WaitSynchronization() override { bool wait = locked; if (locked) { - Handle thread = GetCurrentThreadHandle(); + Handle thread = GetCurrentThread()->GetHandle(); if (std::find(waiting_threads.begin(), waiting_threads.end(), thread) == waiting_threads.end()) { waiting_threads.push_back(thread); } - Kernel::WaitCurrentThread(WAITTYPE_EVENT, GetHandle()); + Kernel::WaitCurrentThread(WAITTYPE_EVENT, this); } if (reset_type != RESETTYPE_STICKY && !permanent_locked) { locked = true; @@ -53,7 +53,7 @@ public: * @return Result of operation, 0 on success, otherwise error code */ ResultCode SetPermanentLock(Handle handle, const bool permanent_locked) { - Event* evt = g_handle_table.Get<Event>(handle); + Event* evt = g_handle_table.Get<Event>(handle).get(); if (evt == nullptr) return InvalidHandle(ErrorModule::Kernel); evt->permanent_locked = permanent_locked; @@ -67,7 +67,7 @@ ResultCode SetPermanentLock(Handle handle, const bool permanent_locked) { * @return Result of operation, 0 on success, otherwise error code */ ResultCode SetEventLocked(const Handle handle, const bool locked) { - Event* evt = g_handle_table.Get<Event>(handle); + Event* evt = g_handle_table.Get<Event>(handle).get(); if (evt == nullptr) return InvalidHandle(ErrorModule::Kernel); if (!evt->permanent_locked) { @@ -82,13 +82,15 @@ ResultCode SetEventLocked(const Handle handle, const bool locked) { * @return Result of operation, 0 on success, otherwise error code */ ResultCode SignalEvent(const Handle handle) { - Event* evt = g_handle_table.Get<Event>(handle); + Event* evt = g_handle_table.Get<Event>(handle).get(); if (evt == nullptr) return InvalidHandle(ErrorModule::Kernel); // Resume threads waiting for event to signal bool event_caught = false; for (size_t i = 0; i < evt->waiting_threads.size(); ++i) { - ResumeThreadFromWait( evt->waiting_threads[i]); + Thread* thread = Kernel::g_handle_table.Get<Thread>(evt->waiting_threads[i]).get(); + if (thread != nullptr) + thread->ResumeFromWait(); // If any thread is signalled awake by this event, assume the event was "caught" and reset // the event. This will result in the next thread waiting on the event to block. Otherwise, @@ -110,7 +112,7 @@ ResultCode SignalEvent(const Handle handle) { * @return Result of operation, 0 on success, otherwise error code */ ResultCode ClearEvent(Handle handle) { - Event* evt = g_handle_table.Get<Event>(handle); + Event* evt = g_handle_table.Get<Event>(handle).get(); if (evt == nullptr) return InvalidHandle(ErrorModule::Kernel); if (!evt->permanent_locked) { diff --git a/src/core/hle/kernel/kernel.cpp b/src/core/hle/kernel/kernel.cpp index ae2c11a1c..d3684896f 100644 --- a/src/core/hle/kernel/kernel.cpp +++ b/src/core/hle/kernel/kernel.cpp @@ -6,13 +6,15 @@ #include "common/common.h" +#include "core/arm/arm_interface.h" #include "core/core.h" #include "core/hle/kernel/kernel.h" #include "core/hle/kernel/thread.h" +#include "core/hle/kernel/timer.h" namespace Kernel { -Handle g_main_thread = 0; +SharedPtr<Thread> g_main_thread = nullptr; HandleTable g_handle_table; u64 g_program_id = 0; @@ -21,7 +23,7 @@ HandleTable::HandleTable() { Clear(); } -ResultVal<Handle> HandleTable::Create(Object* obj) { +ResultVal<Handle> HandleTable::Create(SharedPtr<Object> obj) { _dbg_assert_(Kernel, obj != nullptr); u16 slot = next_free_slot; @@ -37,22 +39,23 @@ ResultVal<Handle> HandleTable::Create(Object* obj) { // CTR-OS doesn't use generation 0, so skip straight to 1. if (next_generation >= (1 << 15)) next_generation = 1; + Handle handle = generation | (slot << 15); + if (obj->handle == INVALID_HANDLE) + obj->handle = handle; + generations[slot] = generation; - intrusive_ptr_add_ref(obj); - objects[slot] = obj; + objects[slot] = std::move(obj); - Handle handle = generation | (slot << 15); - obj->handle = handle; return MakeResult<Handle>(handle); } ResultVal<Handle> HandleTable::Duplicate(Handle handle) { - Object* object = GetGeneric(handle); + SharedPtr<Object> object = GetGeneric(handle); if (object == nullptr) { LOG_ERROR(Kernel, "Tried to duplicate invalid handle: %08X", handle); return ERR_INVALID_HANDLE; } - return Create(object); + return Create(std::move(object)); } ResultCode HandleTable::Close(Handle handle) { @@ -62,7 +65,6 @@ ResultCode HandleTable::Close(Handle handle) { size_t slot = GetSlot(handle); u16 generation = GetGeneration(handle); - intrusive_ptr_release(objects[slot]); objects[slot] = nullptr; generations[generation] = next_free_slot; @@ -77,10 +79,9 @@ bool HandleTable::IsValid(Handle handle) const { return slot < MAX_COUNT && objects[slot] != nullptr && generations[slot] == generation; } -Object* HandleTable::GetGeneric(Handle handle) const { +SharedPtr<Object> HandleTable::GetGeneric(Handle handle) const { if (handle == CurrentThread) { - // TODO(yuriks) Directly return the pointer once this is possible. - handle = GetCurrentThreadHandle(); + return GetCurrentThread(); } else if (handle == CurrentProcess) { LOG_ERROR(Kernel, "Current process (%08X) pseudo-handle not supported", CurrentProcess); return nullptr; @@ -95,8 +96,6 @@ Object* HandleTable::GetGeneric(Handle handle) const { void HandleTable::Clear() { for (size_t i = 0; i < MAX_COUNT; ++i) { generations[i] = i + 1; - if (objects[i] != nullptr) - intrusive_ptr_release(objects[i]); objects[i] = nullptr; } next_free_slot = 0; @@ -105,12 +104,13 @@ void HandleTable::Clear() { /// Initialize the kernel void Init() { Kernel::ThreadingInit(); + Kernel::TimersInit(); } /// Shutdown the kernel void Shutdown() { Kernel::ThreadingShutdown(); - + Kernel::TimersShutdown(); g_handle_table.Clear(); // Free all kernel objects } @@ -123,7 +123,7 @@ bool LoadExec(u32 entry_point) { Core::g_app_core->SetPC(entry_point); // 0x30 is the typical main thread priority I've seen used so far - g_main_thread = Kernel::SetupMainThread(0x30); + g_main_thread = Kernel::SetupMainThread(0x30, Kernel::DEFAULT_STACK_SIZE); // Setup the idle thread Kernel::SetupIdleThread(); diff --git a/src/core/hle/kernel/kernel.h b/src/core/hle/kernel/kernel.h index 7f86fd07d..5e5217b78 100644 --- a/src/core/hle/kernel/kernel.h +++ b/src/core/hle/kernel/kernel.h @@ -4,6 +4,8 @@ #pragma once +#include <boost/intrusive_ptr.hpp> + #include <array> #include <string> #include "common/common.h" @@ -16,6 +18,8 @@ const Handle INVALID_HANDLE = 0; namespace Kernel { +class Thread; + // TODO: Verify code const ResultCode ERR_OUT_OF_HANDLES(ErrorDescription::OutOfMemory, ErrorModule::Kernel, ErrorSummary::OutOfResource, ErrorLevel::Temporary); @@ -39,6 +43,7 @@ enum class HandleType : u32 { Process = 8, AddressArbiter = 9, Semaphore = 10, + Timer = 11 }; enum { @@ -49,7 +54,7 @@ class HandleTable; class Object : NonCopyable { friend class HandleTable; - u32 handle; + u32 handle = INVALID_HANDLE; public: virtual ~Object() {} Handle GetHandle() const { return handle; } @@ -73,7 +78,7 @@ private: unsigned int ref_count = 0; }; -// Special functions that will later be used by boost::instrusive_ptr to do automatic ref-counting +// Special functions used by boost::instrusive_ptr to do automatic ref-counting inline void intrusive_ptr_add_ref(Object* object) { ++object->ref_count; } @@ -84,6 +89,9 @@ inline void intrusive_ptr_release(Object* object) { } } +template <typename T> +using SharedPtr = boost::intrusive_ptr<T>; + /** * This class allows the creation of Handles, which are references to objects that can be tested * for validity and looked up. Here they are used to pass references to kernel objects to/from the @@ -116,7 +124,7 @@ public: * @return The created Handle or one of the following errors: * - `ERR_OUT_OF_HANDLES`: the maximum number of handles has been exceeded. */ - ResultVal<Handle> Create(Object* obj); + ResultVal<Handle> Create(SharedPtr<Object> obj); /** * Returns a new handle that points to the same object as the passed in handle. @@ -140,7 +148,7 @@ public: * Looks up a handle. * @returns Pointer to the looked-up object, or `nullptr` if the handle is not valid. */ - Object* GetGeneric(Handle handle) const; + SharedPtr<Object> GetGeneric(Handle handle) const; /** * Looks up a handle while verifying its type. @@ -148,10 +156,10 @@ public: * type differs from the handle type `T::HANDLE_TYPE`. */ template <class T> - T* Get(Handle handle) const { - Object* object = GetGeneric(handle); + SharedPtr<T> Get(Handle handle) const { + SharedPtr<Object> object = GetGeneric(handle); if (object != nullptr && object->GetHandleType() == T::HANDLE_TYPE) { - return static_cast<T*>(object); + return boost::static_pointer_cast<T>(std::move(object)); } return nullptr; } @@ -170,7 +178,7 @@ private: static u16 GetGeneration(Handle handle) { return handle & 0x7FFF; } /// Stores the Object referenced by the handle or null if the slot is empty. - std::array<Object*, MAX_COUNT> objects; + std::array<SharedPtr<Object>, MAX_COUNT> objects; /** * The value of `next_generation` when the handle was created, used to check for validity. For @@ -189,7 +197,7 @@ private: }; extern HandleTable g_handle_table; -extern Handle g_main_thread; +extern SharedPtr<Thread> g_main_thread; /// The ID code of the currently running game /// TODO(Subv): This variable should not be here, diff --git a/src/core/hle/kernel/mutex.cpp b/src/core/hle/kernel/mutex.cpp index 3dfeffc9b..853a5dd74 100644 --- a/src/core/hle/kernel/mutex.cpp +++ b/src/core/hle/kernel/mutex.cpp @@ -40,14 +40,21 @@ static MutexMap g_mutex_held_locks; * @param mutex Mutex that is to be acquired * @param thread Thread that will acquired */ -void MutexAcquireLock(Mutex* mutex, Handle thread = GetCurrentThreadHandle()) { +void MutexAcquireLock(Mutex* mutex, Handle thread = GetCurrentThread()->GetHandle()) { g_mutex_held_locks.insert(std::make_pair(thread, mutex->GetHandle())); mutex->lock_thread = thread; } -bool ReleaseMutexForThread(Mutex* mutex, Handle thread) { - MutexAcquireLock(mutex, thread); - Kernel::ResumeThreadFromWait(thread); +bool ReleaseMutexForThread(Mutex* mutex, Handle thread_handle) { + MutexAcquireLock(mutex, thread_handle); + + Thread* thread = Kernel::g_handle_table.Get<Thread>(thread_handle).get(); + if (thread == nullptr) { + LOG_ERROR(Kernel, "Called with invalid handle: %08X", thread_handle); + return false; + } + + thread->ResumeFromWait(); return true; } @@ -87,7 +94,7 @@ void ReleaseThreadMutexes(Handle thread) { // Release every mutex that the thread holds, and resume execution on the waiting threads for (MutexMap::iterator iter = locked.first; iter != locked.second; ++iter) { - Mutex* mutex = g_handle_table.Get<Mutex>(iter->second); + Mutex* mutex = g_handle_table.Get<Mutex>(iter->second).get(); ResumeWaitingThread(mutex); } @@ -115,7 +122,7 @@ bool ReleaseMutex(Mutex* mutex) { * @param handle Handle to mutex to release */ ResultCode ReleaseMutex(Handle handle) { - Mutex* mutex = Kernel::g_handle_table.Get<Mutex>(handle); + Mutex* mutex = Kernel::g_handle_table.Get<Mutex>(handle).get(); if (mutex == nullptr) return InvalidHandle(ErrorModule::Kernel); if (!ReleaseMutex(mutex)) { @@ -168,8 +175,8 @@ Handle CreateMutex(bool initial_locked, const std::string& name) { ResultVal<bool> Mutex::WaitSynchronization() { bool wait = locked; if (locked) { - waiting_threads.push_back(GetCurrentThreadHandle()); - Kernel::WaitCurrentThread(WAITTYPE_MUTEX, GetHandle()); + waiting_threads.push_back(GetCurrentThread()->GetHandle()); + Kernel::WaitCurrentThread(WAITTYPE_MUTEX, this); } else { // Lock the mutex when the first thread accesses it locked = true; diff --git a/src/core/hle/kernel/semaphore.cpp b/src/core/hle/kernel/semaphore.cpp index 6bc8066a6..88ec9a104 100644 --- a/src/core/hle/kernel/semaphore.cpp +++ b/src/core/hle/kernel/semaphore.cpp @@ -37,8 +37,8 @@ public: bool wait = !IsAvailable(); if (wait) { - Kernel::WaitCurrentThread(WAITTYPE_SEMA, GetHandle()); - waiting_threads.push(GetCurrentThreadHandle()); + Kernel::WaitCurrentThread(WAITTYPE_SEMA, this); + waiting_threads.push(GetCurrentThread()->GetHandle()); } else { --available_count; } @@ -70,7 +70,7 @@ ResultCode CreateSemaphore(Handle* handle, s32 initial_count, } ResultCode ReleaseSemaphore(s32* count, Handle handle, s32 release_count) { - Semaphore* semaphore = g_handle_table.Get<Semaphore>(handle); + Semaphore* semaphore = g_handle_table.Get<Semaphore>(handle).get(); if (semaphore == nullptr) return InvalidHandle(ErrorModule::Kernel); @@ -84,7 +84,9 @@ ResultCode ReleaseSemaphore(s32* count, Handle handle, s32 release_count) { // Notify some of the threads that the semaphore has been released // stop once the semaphore is full again or there are no more waiting threads while (!semaphore->waiting_threads.empty() && semaphore->IsAvailable()) { - Kernel::ResumeThreadFromWait(semaphore->waiting_threads.front()); + Thread* thread = Kernel::g_handle_table.Get<Thread>(semaphore->waiting_threads.front()).get(); + if (thread != nullptr) + thread->ResumeFromWait(); semaphore->waiting_threads.pop(); --semaphore->available_count; } diff --git a/src/core/hle/kernel/shared_memory.cpp b/src/core/hle/kernel/shared_memory.cpp index cea1f6fa1..5368e4728 100644 --- a/src/core/hle/kernel/shared_memory.cpp +++ b/src/core/hle/kernel/shared_memory.cpp @@ -61,7 +61,7 @@ ResultCode MapSharedMemory(u32 handle, u32 address, MemoryPermission permissions return ResultCode(ErrorDescription::InvalidAddress, ErrorModule::Kernel, ErrorSummary::InvalidArgument, ErrorLevel::Permanent); } - SharedMemory* shared_memory = Kernel::g_handle_table.Get<SharedMemory>(handle); + SharedMemory* shared_memory = Kernel::g_handle_table.Get<SharedMemory>(handle).get(); if (shared_memory == nullptr) return InvalidHandle(ErrorModule::Kernel); shared_memory->base_address = address; @@ -72,7 +72,7 @@ ResultCode MapSharedMemory(u32 handle, u32 address, MemoryPermission permissions } ResultVal<u8*> GetSharedMemoryPointer(Handle handle, u32 offset) { - SharedMemory* shared_memory = Kernel::g_handle_table.Get<SharedMemory>(handle); + SharedMemory* shared_memory = Kernel::g_handle_table.Get<SharedMemory>(handle).get(); if (shared_memory == nullptr) return InvalidHandle(ErrorModule::Kernel); if (0 != shared_memory->base_address) diff --git a/src/core/hle/kernel/thread.cpp b/src/core/hle/kernel/thread.cpp index 58fb62e89..bc86a7c59 100644 --- a/src/core/hle/kernel/thread.cpp +++ b/src/core/hle/kernel/thread.cpp @@ -10,6 +10,7 @@ #include "common/common.h" #include "common/thread_queue_list.h" +#include "core/arm/arm_interface.h" #include "core/core.h" #include "core/core_timing.h" #include "core/hle/hle.h" @@ -21,68 +22,25 @@ namespace Kernel { -class Thread : public Kernel::Object { -public: - - std::string GetName() const override { return name; } - std::string GetTypeName() const override { return "Thread"; } - - static const HandleType HANDLE_TYPE = HandleType::Thread; - HandleType GetHandleType() const override { return HANDLE_TYPE; } - - inline bool IsRunning() const { return (status & THREADSTATUS_RUNNING) != 0; } - inline bool IsStopped() const { return (status & THREADSTATUS_DORMANT) != 0; } - inline bool IsReady() const { return (status & THREADSTATUS_READY) != 0; } - inline bool IsWaiting() const { return (status & THREADSTATUS_WAIT) != 0; } - inline bool IsSuspended() const { return (status & THREADSTATUS_SUSPEND) != 0; } - inline bool IsIdle() const { return idle; } - - ResultVal<bool> WaitSynchronization() override { - const bool wait = status != THREADSTATUS_DORMANT; - if (wait) { - Handle thread = GetCurrentThreadHandle(); - if (std::find(waiting_threads.begin(), waiting_threads.end(), thread) == waiting_threads.end()) { - waiting_threads.push_back(thread); - } - WaitCurrentThread(WAITTYPE_THREADEND, this->GetHandle()); +ResultVal<bool> Thread::WaitSynchronization() { + const bool wait = status != THREADSTATUS_DORMANT; + if (wait) { + Thread* thread = GetCurrentThread(); + if (std::find(waiting_threads.begin(), waiting_threads.end(), thread) == waiting_threads.end()) { + waiting_threads.push_back(thread); } - - return MakeResult<bool>(wait); + WaitCurrentThread(WAITTYPE_THREADEND, this); } - ThreadContext context; - - u32 thread_id; - - u32 status; - u32 entry_point; - u32 stack_top; - u32 stack_size; - - s32 initial_priority; - s32 current_priority; - - s32 processor_id; - - WaitType wait_type; - Handle wait_handle; - VAddr wait_address; - - std::vector<Handle> waiting_threads; - - std::string name; - - /// Whether this thread is intended to never actually be executed, i.e. always idle - bool idle = false; -}; + return MakeResult<bool>(wait); +} // Lists all thread ids that aren't deleted/etc. -static std::vector<Handle> thread_queue; +static std::vector<SharedPtr<Thread>> thread_list; // Lists only ready thread ids. -static Common::ThreadQueueList<Handle, THREADPRIO_LOWEST+1> thread_ready_queue; +static Common::ThreadQueueList<Thread*, THREADPRIO_LOWEST+1> thread_ready_queue; -static Handle current_thread_handle; static Thread* current_thread; static const u32 INITIAL_THREAD_ID = 1; ///< The first available thread id at startup @@ -92,30 +50,9 @@ Thread* GetCurrentThread() { return current_thread; } -/// Gets the current thread handle -Handle GetCurrentThreadHandle() { - return GetCurrentThread()->GetHandle(); -} - -/// Sets the current thread -inline void SetCurrentThread(Thread* t) { - current_thread = t; - current_thread_handle = t->GetHandle(); -} - -/// Saves the current CPU context -void SaveContext(ThreadContext& ctx) { - Core::g_app_core->SaveContext(ctx); -} - -/// Loads a CPU context -void LoadContext(ThreadContext& ctx) { - Core::g_app_core->LoadContext(ctx); -} - /// Resets a thread -void ResetThread(Thread* t, u32 arg, s32 lowest_priority) { - memset(&t->context, 0, sizeof(ThreadContext)); +static void ResetThread(Thread* t, u32 arg, s32 lowest_priority) { + memset(&t->context, 0, sizeof(Core::ThreadContext)); t->context.cpu_registers[0] = arg; t->context.pc = t->context.reg_15 = t->entry_point; @@ -131,22 +68,21 @@ void ResetThread(Thread* t, u32 arg, s32 lowest_priority) { t->current_priority = t->initial_priority; } t->wait_type = WAITTYPE_NONE; - t->wait_handle = 0; + t->wait_object = nullptr; t->wait_address = 0; } /// Change a thread to "ready" state -void ChangeReadyState(Thread* t, bool ready) { - Handle handle = t->GetHandle(); +static void ChangeReadyState(Thread* t, bool ready) { if (t->IsReady()) { if (!ready) { - thread_ready_queue.remove(t->current_priority, handle); + thread_ready_queue.remove(t->current_priority, t); } } else if (ready) { if (t->IsRunning()) { - thread_ready_queue.push_front(t->current_priority, handle); + thread_ready_queue.push_front(t->current_priority, t); } else { - thread_ready_queue.push_back(t->current_priority, handle); + thread_ready_queue.push_back(t->current_priority, t); } t->status = THREADSTATUS_READY; } @@ -158,43 +94,36 @@ static bool CheckWaitType(const Thread* thread, WaitType type) { } /// Check if a thread is blocking on a specified wait type with a specified handle -static bool CheckWaitType(const Thread* thread, WaitType type, Handle wait_handle) { - return CheckWaitType(thread, type) && (wait_handle == thread->wait_handle); +static bool CheckWaitType(const Thread* thread, WaitType type, Object* wait_object) { + return CheckWaitType(thread, type) && wait_object == thread->wait_object; } /// Check if a thread is blocking on a specified wait type with a specified handle and address -static bool CheckWaitType(const Thread* thread, WaitType type, Handle wait_handle, VAddr wait_address) { - return CheckWaitType(thread, type, wait_handle) && (wait_address == thread->wait_address); +static bool CheckWaitType(const Thread* thread, WaitType type, Object* wait_object, VAddr wait_address) { + return CheckWaitType(thread, type, wait_object) && (wait_address == thread->wait_address); } /// Stops the current thread -ResultCode StopThread(Handle handle, const char* reason) { - Thread* thread = g_handle_table.Get<Thread>(handle); - if (thread == nullptr) return InvalidHandle(ErrorModule::Kernel); - +void Thread::Stop(const char* reason) { // Release all the mutexes that this thread holds - ReleaseThreadMutexes(handle); - - ChangeReadyState(thread, false); - thread->status = THREADSTATUS_DORMANT; - for (Handle waiting_handle : thread->waiting_threads) { - Thread* waiting_thread = g_handle_table.Get<Thread>(waiting_handle); + ReleaseThreadMutexes(GetHandle()); - if (CheckWaitType(waiting_thread, WAITTYPE_THREADEND, handle)) - ResumeThreadFromWait(waiting_handle); + ChangeReadyState(this, false); + status = THREADSTATUS_DORMANT; + for (auto& waiting_thread : waiting_threads) { + if (CheckWaitType(waiting_thread.get(), WAITTYPE_THREADEND, this)) + waiting_thread->ResumeFromWait(); } - thread->waiting_threads.clear(); + waiting_threads.clear(); // Stopped threads are never waiting. - thread->wait_type = WAITTYPE_NONE; - thread->wait_handle = 0; - thread->wait_address = 0; - - return RESULT_SUCCESS; + wait_type = WAITTYPE_NONE; + wait_object = nullptr; + wait_address = 0; } /// Changes a threads state -void ChangeThreadState(Thread* t, ThreadStatus new_status) { +static void ChangeThreadState(Thread* t, ThreadStatus new_status) { if (!t || t->status == new_status) { return; } @@ -209,46 +138,44 @@ void ChangeThreadState(Thread* t, ThreadStatus new_status) { } /// Arbitrate the highest priority thread that is waiting -Handle ArbitrateHighestPriorityThread(u32 arbiter, u32 address) { - Handle highest_priority_thread = 0; +Thread* ArbitrateHighestPriorityThread(Object* arbiter, u32 address) { + Thread* highest_priority_thread = nullptr; s32 priority = THREADPRIO_LOWEST; // Iterate through threads, find highest priority thread that is waiting to be arbitrated... - for (Handle handle : thread_queue) { - Thread* thread = g_handle_table.Get<Thread>(handle); - - if (!CheckWaitType(thread, WAITTYPE_ARB, arbiter, address)) + for (auto& thread : thread_list) { + if (!CheckWaitType(thread.get(), WAITTYPE_ARB, arbiter, address)) continue; if (thread == nullptr) continue; // TODO(yuriks): Thread handle will hang around forever. Should clean up. if(thread->current_priority <= priority) { - highest_priority_thread = handle; + highest_priority_thread = thread.get(); priority = thread->current_priority; } } + // If a thread was arbitrated, resume it - if (0 != highest_priority_thread) - ResumeThreadFromWait(highest_priority_thread); + if (nullptr != highest_priority_thread) { + highest_priority_thread->ResumeFromWait(); + } return highest_priority_thread; } /// Arbitrate all threads currently waiting -void ArbitrateAllThreads(u32 arbiter, u32 address) { +void ArbitrateAllThreads(Object* arbiter, u32 address) { // Iterate through threads, find highest priority thread that is waiting to be arbitrated... - for (Handle handle : thread_queue) { - Thread* thread = g_handle_table.Get<Thread>(handle); - - if (CheckWaitType(thread, WAITTYPE_ARB, arbiter, address)) - ResumeThreadFromWait(handle); + for (auto& thread : thread_list) { + if (CheckWaitType(thread.get(), WAITTYPE_ARB, arbiter, address)) + thread->ResumeFromWait(); } } /// Calls a thread by marking it as "ready" (note: will not actually execute until current thread yields) -void CallThread(Thread* t) { +static void CallThread(Thread* t) { // Stop waiting if (t->wait_type != WAITTYPE_NONE) { t->wait_type = WAITTYPE_NONE; @@ -257,12 +184,12 @@ void CallThread(Thread* t) { } /// Switches CPU context to that of the specified thread -void SwitchContext(Thread* t) { +static void SwitchContext(Thread* t) { Thread* cur = GetCurrentThread(); // Save context for current thread if (cur) { - SaveContext(cur->context); + Core::g_app_core->SaveContext(cur->context); if (cur->IsRunning()) { ChangeReadyState(cur, true); @@ -270,19 +197,19 @@ void SwitchContext(Thread* t) { } // Load context of new thread if (t) { - SetCurrentThread(t); + current_thread = t; ChangeReadyState(t, false); t->status = (t->status | THREADSTATUS_RUNNING) & ~THREADSTATUS_READY; t->wait_type = WAITTYPE_NONE; - LoadContext(t->context); + Core::g_app_core->LoadContext(t->context); } else { - SetCurrentThread(nullptr); + current_thread = nullptr; } } /// Gets the next thread that is ready to be run by priority -Thread* NextThread() { - Handle next; +static Thread* NextThread() { + Thread* next; Thread* cur = GetCurrentThread(); if (cur && cur->IsRunning()) { @@ -293,63 +220,111 @@ Thread* NextThread() { if (next == 0) { return nullptr; } - return Kernel::g_handle_table.Get<Thread>(next); + return next; } -void WaitCurrentThread(WaitType wait_type, Handle wait_handle) { +void WaitCurrentThread(WaitType wait_type, Object* wait_object) { Thread* thread = GetCurrentThread(); thread->wait_type = wait_type; - thread->wait_handle = wait_handle; + thread->wait_object = wait_object; ChangeThreadState(thread, ThreadStatus(THREADSTATUS_WAIT | (thread->status & THREADSTATUS_SUSPEND))); } -void WaitCurrentThread(WaitType wait_type, Handle wait_handle, VAddr wait_address) { - WaitCurrentThread(wait_type, wait_handle); +void WaitCurrentThread(WaitType wait_type, Object* wait_object, VAddr wait_address) { + WaitCurrentThread(wait_type, wait_object); GetCurrentThread()->wait_address = wait_address; } +/// Event type for the thread wake up event +static int ThreadWakeupEventType = -1; + +/// Callback that will wake up the thread it was scheduled for +static void ThreadWakeupCallback(u64 parameter, int cycles_late) { + Handle handle = static_cast<Handle>(parameter); + SharedPtr<Thread> thread = Kernel::g_handle_table.Get<Thread>(handle); + if (thread == nullptr) { + LOG_ERROR(Kernel, "Thread doesn't exist %u", handle); + return; + } + + thread->ResumeFromWait(); +} + + +void WakeThreadAfterDelay(Thread* thread, s64 nanoseconds) { + // Don't schedule a wakeup if the thread wants to wait forever + if (nanoseconds == -1) + return; + _dbg_assert_(Kernel, thread != nullptr); + + u64 microseconds = nanoseconds / 1000; + CoreTiming::ScheduleEvent(usToCycles(microseconds), ThreadWakeupEventType, thread->GetHandle()); +} + /// Resumes a thread from waiting by marking it as "ready" -void ResumeThreadFromWait(Handle handle) { - Thread* thread = Kernel::g_handle_table.Get<Thread>(handle); - if (thread) { - thread->status &= ~THREADSTATUS_WAIT; - thread->wait_handle = 0; - thread->wait_type = WAITTYPE_NONE; - if (!(thread->status & (THREADSTATUS_WAITSUSPEND | THREADSTATUS_DORMANT | THREADSTATUS_DEAD))) { - ChangeReadyState(thread, true); - } +void Thread::ResumeFromWait() { + // Cancel any outstanding wakeup events + CoreTiming::UnscheduleEvent(ThreadWakeupEventType, GetHandle()); + + status &= ~THREADSTATUS_WAIT; + wait_object = nullptr; + wait_type = WAITTYPE_NONE; + if (!(status & (THREADSTATUS_WAITSUSPEND | THREADSTATUS_DORMANT | THREADSTATUS_DEAD))) { + ChangeReadyState(this, true); } } /// Prints the thread queue for debugging purposes -void DebugThreadQueue() { +static void DebugThreadQueue() { Thread* thread = GetCurrentThread(); if (!thread) { return; } - LOG_DEBUG(Kernel, "0x%02X 0x%08X (current)", thread->current_priority, GetCurrentThreadHandle()); - for (u32 i = 0; i < thread_queue.size(); i++) { - Handle handle = thread_queue[i]; - s32 priority = thread_ready_queue.contains(handle); + LOG_DEBUG(Kernel, "0x%02X 0x%08X (current)", thread->current_priority, GetCurrentThread()->GetHandle()); + for (auto& t : thread_list) { + s32 priority = thread_ready_queue.contains(t.get()); if (priority != -1) { - LOG_DEBUG(Kernel, "0x%02X 0x%08X", priority, handle); + LOG_DEBUG(Kernel, "0x%02X 0x%08X", priority, t->GetHandle()); } } } -/// Creates a new thread -Thread* CreateThread(Handle& handle, const char* name, u32 entry_point, s32 priority, - s32 processor_id, u32 stack_top, int stack_size) { +ResultVal<SharedPtr<Thread>> Thread::Create(std::string name, VAddr entry_point, s32 priority, + u32 arg, s32 processor_id, VAddr stack_top, u32 stack_size) { + if (stack_size < 0x200) { + LOG_ERROR(Kernel, "(name=%s): invalid stack_size=0x%08X", name.c_str(), stack_size); + // TODO: Verify error + return ResultCode(ErrorDescription::InvalidSize, ErrorModule::Kernel, + ErrorSummary::InvalidArgument, ErrorLevel::Permanent); + } - _assert_msg_(KERNEL, (priority >= THREADPRIO_HIGHEST && priority <= THREADPRIO_LOWEST), - "priority=%d, outside of allowable range!", priority) + if (priority < THREADPRIO_HIGHEST || priority > THREADPRIO_LOWEST) { + s32 new_priority = CLAMP(priority, THREADPRIO_HIGHEST, THREADPRIO_LOWEST); + LOG_WARNING(Kernel_SVC, "(name=%s): invalid priority=%d, clamping to %d", + name.c_str(), priority, new_priority); + // TODO(bunnei): Clamping to a valid priority is not necessarily correct behavior... Confirm + // validity of this + priority = new_priority; + } + + if (!Memory::GetPointer(entry_point)) { + LOG_ERROR(Kernel_SVC, "(name=%s): invalid entry %08x", name.c_str(), entry_point); + // TODO: Verify error + return ResultCode(ErrorDescription::InvalidAddress, ErrorModule::Kernel, + ErrorSummary::InvalidArgument, ErrorLevel::Permanent); + } - Thread* thread = new Thread; + SharedPtr<Thread> thread(new Thread); - // TOOD(yuriks): Fix error reporting - handle = Kernel::g_handle_table.Create(thread).ValueOr(INVALID_HANDLE); + // TODO(yuriks): Thread requires a handle to be inserted into the various scheduling queues for + // the time being. Create a handle here, it will be copied to the handle field in + // the object and use by the rest of the code. This should be removed when other + // code doesn't rely on the handle anymore. + ResultVal<Handle> handle = Kernel::g_handle_table.Create(thread); + if (handle.Failed()) + return handle.Code(); - thread_queue.push_back(handle); + thread_list.push_back(thread); thread_ready_queue.prepare(priority); thread->thread_id = next_thread_id++; @@ -360,69 +335,18 @@ Thread* CreateThread(Handle& handle, const char* name, u32 entry_point, s32 prio thread->initial_priority = thread->current_priority = priority; thread->processor_id = processor_id; thread->wait_type = WAITTYPE_NONE; - thread->wait_handle = 0; + thread->wait_object = nullptr; thread->wait_address = 0; - thread->name = name; - - return thread; -} - -/// Creates a new thread - wrapper for external user -Handle CreateThread(const char* name, u32 entry_point, s32 priority, u32 arg, s32 processor_id, - u32 stack_top, int stack_size) { - - if (name == nullptr) { - LOG_ERROR(Kernel_SVC, "nullptr name"); - return -1; - } - if ((u32)stack_size < 0x200) { - LOG_ERROR(Kernel_SVC, "(name=%s): invalid stack_size=0x%08X", name, - stack_size); - return -1; - } - if (priority < THREADPRIO_HIGHEST || priority > THREADPRIO_LOWEST) { - s32 new_priority = CLAMP(priority, THREADPRIO_HIGHEST, THREADPRIO_LOWEST); - LOG_WARNING(Kernel_SVC, "(name=%s): invalid priority=%d, clamping to %d", - name, priority, new_priority); - // TODO(bunnei): Clamping to a valid priority is not necessarily correct behavior... Confirm - // validity of this - priority = new_priority; - } - if (!Memory::GetPointer(entry_point)) { - LOG_ERROR(Kernel_SVC, "(name=%s): invalid entry %08x", name, entry_point); - return -1; - } - Handle handle; - Thread* thread = CreateThread(handle, name, entry_point, priority, processor_id, stack_top, - stack_size); + thread->name = std::move(name); - ResetThread(thread, arg, 0); - CallThread(thread); + ResetThread(thread.get(), arg, 0); + CallThread(thread.get()); - return handle; -} - -/// Get the priority of the thread specified by handle -ResultVal<u32> GetThreadPriority(const Handle handle) { - Thread* thread = g_handle_table.Get<Thread>(handle); - if (thread == nullptr) return InvalidHandle(ErrorModule::Kernel); - - return MakeResult<u32>(thread->current_priority); + return MakeResult<SharedPtr<Thread>>(std::move(thread)); } /// Set the priority of the thread specified by handle -ResultCode SetThreadPriority(Handle handle, s32 priority) { - Thread* thread = nullptr; - if (!handle) { - thread = GetCurrentThread(); // TODO(bunnei): Is this correct behavior? - } else { - thread = g_handle_table.Get<Thread>(handle); - if (thread == nullptr) { - return InvalidHandle(ErrorModule::Kernel); - } - } - _assert_msg_(KERNEL, (thread != nullptr), "called, but thread is nullptr!"); - +void Thread::SetPriority(s32 priority) { // If priority is invalid, clamp to valid range if (priority < THREADPRIO_HIGHEST || priority > THREADPRIO_LOWEST) { s32 new_priority = CLAMP(priority, THREADPRIO_HIGHEST, THREADPRIO_LOWEST); @@ -433,38 +357,39 @@ ResultCode SetThreadPriority(Handle handle, s32 priority) { } // Change thread priority - s32 old = thread->current_priority; - thread_ready_queue.remove(old, handle); - thread->current_priority = priority; - thread_ready_queue.prepare(thread->current_priority); + s32 old = current_priority; + thread_ready_queue.remove(old, this); + current_priority = priority; + thread_ready_queue.prepare(current_priority); // Change thread status to "ready" and push to ready queue - if (thread->IsRunning()) { - thread->status = (thread->status & ~THREADSTATUS_RUNNING) | THREADSTATUS_READY; + if (IsRunning()) { + status = (status & ~THREADSTATUS_RUNNING) | THREADSTATUS_READY; } - if (thread->IsReady()) { - thread_ready_queue.push_back(thread->current_priority, handle); + if (IsReady()) { + thread_ready_queue.push_back(current_priority, this); } - - return RESULT_SUCCESS; } Handle SetupIdleThread() { - Handle handle; - Thread* thread = CreateThread(handle, "idle", 0, THREADPRIO_LOWEST, THREADPROCESSORID_0, 0, 0); + // We need to pass a few valid values to get around parameter checking in Thread::Create. + auto thread_res = Thread::Create("idle", Memory::KERNEL_MEMORY_VADDR, THREADPRIO_LOWEST, 0, + THREADPROCESSORID_0, 0, Kernel::DEFAULT_STACK_SIZE); + _dbg_assert_(Kernel, thread_res.Succeeded()); + SharedPtr<Thread> thread = std::move(*thread_res); + thread->idle = true; - CallThread(thread); - return handle; + CallThread(thread.get()); + return thread->GetHandle(); } -Handle SetupMainThread(s32 priority, int stack_size) { - Handle handle; - +SharedPtr<Thread> SetupMainThread(s32 priority, u32 stack_size) { // Initialize new "main" thread - Thread* thread = CreateThread(handle, "main", Core::g_app_core->GetPC(), priority, - THREADPROCESSORID_0, Memory::SCRATCHPAD_VADDR_END, stack_size); - - ResetThread(thread, 0, 0); + auto thread_res = Thread::Create("main", Core::g_app_core->GetPC(), priority, 0, + THREADPROCESSORID_0, Memory::SCRATCHPAD_VADDR_END, stack_size); + // TODO(yuriks): Propagate error + _dbg_assert_(Kernel, thread_res.Succeeded()); + SharedPtr<Thread> thread = std::move(*thread_res); // If running another thread already, set it to "ready" state Thread* cur = GetCurrentThread(); @@ -473,11 +398,11 @@ Handle SetupMainThread(s32 priority, int stack_size) { } // Run new "main" thread - SetCurrentThread(thread); + current_thread = thread.get(); thread->status = THREADSTATUS_RUNNING; - LoadContext(thread->context); + Core::g_app_core->LoadContext(thread->context); - return handle; + return thread; } @@ -493,46 +418,19 @@ void Reschedule() { } else { LOG_TRACE(Kernel, "cannot context switch from 0x%08X, no higher priority thread!", prev->GetHandle()); - for (Handle handle : thread_queue) { - Thread* thread = g_handle_table.Get<Thread>(handle); + for (auto& thread : thread_list) { LOG_TRACE(Kernel, "\thandle=0x%08X prio=0x%02X, status=0x%08X wait_type=0x%08X wait_handle=0x%08X", - thread->GetHandle(), thread->current_priority, thread->status, thread->wait_type, thread->wait_handle); + thread->GetHandle(), thread->current_priority, thread->status, thread->wait_type, + (thread->wait_object ? thread->wait_object->GetHandle() : INVALID_HANDLE)); } } - - // TODO(bunnei): Hack - There is no timing mechanism yet to wake up a thread if it has been put - // to sleep. So, we'll just immediately set it to "ready" again after an attempted context - // switch has occurred. This results in the current thread yielding on a sleep once, and then it - // will immediately be placed back in the queue for execution. - - if (CheckWaitType(prev, WAITTYPE_SLEEP)) - ResumeThreadFromWait(prev->GetHandle()); -} - -bool IsIdleThread(Handle handle) { - Thread* thread = g_handle_table.Get<Thread>(handle); - if (!thread) { - LOG_ERROR(Kernel, "Thread not found %u", handle); - return false; - } - return thread->IsIdle(); -} - -ResultCode GetThreadId(u32* thread_id, Handle handle) { - Thread* thread = g_handle_table.Get<Thread>(handle); - if (thread == nullptr) - return ResultCode(ErrorDescription::InvalidHandle, ErrorModule::OS, - ErrorSummary::WrongArgument, ErrorLevel::Permanent); - - *thread_id = thread->thread_id; - - return RESULT_SUCCESS; } //////////////////////////////////////////////////////////////////////////////////////////////////// void ThreadingInit() { next_thread_id = INITIAL_THREAD_ID; + ThreadWakeupEventType = CoreTiming::RegisterEvent("ThreadWakeupCallback", ThreadWakeupCallback); } void ThreadingShutdown() { diff --git a/src/core/hle/kernel/thread.h b/src/core/hle/kernel/thread.h index dfe92d162..284dec400 100644 --- a/src/core/hle/kernel/thread.h +++ b/src/core/hle/kernel/thread.h @@ -4,8 +4,12 @@ #pragma once +#include <string> +#include <vector> + #include "common/common_types.h" +#include "core/core.h" #include "core/mem_map.h" #include "core/hle/kernel/kernel.h" @@ -43,66 +47,107 @@ enum WaitType { WAITTYPE_MUTEX, WAITTYPE_SYNCH, WAITTYPE_ARB, + WAITTYPE_TIMER, }; namespace Kernel { -/// Creates a new thread - wrapper for external user -Handle CreateThread(const char* name, u32 entry_point, s32 priority, u32 arg, s32 processor_id, - u32 stack_top, int stack_size=Kernel::DEFAULT_STACK_SIZE); +class Thread : public Kernel::Object { +public: + static ResultVal<SharedPtr<Thread>> Create(std::string name, VAddr entry_point, s32 priority, + u32 arg, s32 processor_id, VAddr stack_top, u32 stack_size); -/// Sets up the primary application thread -Handle SetupMainThread(s32 priority, int stack_size=Kernel::DEFAULT_STACK_SIZE); + std::string GetName() const override { return name; } + std::string GetTypeName() const override { return "Thread"; } -/// Reschedules to the next available thread (call after current thread is suspended) -void Reschedule(); + static const HandleType HANDLE_TYPE = HandleType::Thread; + HandleType GetHandleType() const override { return HANDLE_TYPE; } -/// Stops the current thread -ResultCode StopThread(Handle thread, const char* reason); + inline bool IsRunning() const { return (status & THREADSTATUS_RUNNING) != 0; } + inline bool IsStopped() const { return (status & THREADSTATUS_DORMANT) != 0; } + inline bool IsReady() const { return (status & THREADSTATUS_READY) != 0; } + inline bool IsWaiting() const { return (status & THREADSTATUS_WAIT) != 0; } + inline bool IsSuspended() const { return (status & THREADSTATUS_SUSPEND) != 0; } + inline bool IsIdle() const { return idle; } -/** - * Retrieves the ID of the specified thread handle - * @param thread_id Will contain the output thread id - * @param handle Handle to the thread we want - * @return Whether the function was successful or not - */ -ResultCode GetThreadId(u32* thread_id, Handle handle); + ResultVal<bool> WaitSynchronization() override; + + s32 GetPriority() const { return current_priority; } + void SetPriority(s32 priority); + + u32 GetThreadId() const { return thread_id; } + + void Stop(const char* reason); + /// Resumes a thread from waiting by marking it as "ready". + void ResumeFromWait(); + + Core::ThreadContext context; + + u32 thread_id; + + u32 status; + u32 entry_point; + u32 stack_top; + u32 stack_size; + + s32 initial_priority; + s32 current_priority; + + s32 processor_id; + + WaitType wait_type; + Object* wait_object; + VAddr wait_address; -/// Resumes a thread from waiting by marking it as "ready" -void ResumeThreadFromWait(Handle handle); + std::vector<SharedPtr<Thread>> waiting_threads; + + std::string name; + + /// Whether this thread is intended to never actually be executed, i.e. always idle + bool idle = false; + +private: + Thread() = default; +}; + +/// Sets up the primary application thread +SharedPtr<Thread> SetupMainThread(s32 priority, u32 stack_size); + +/// Reschedules to the next available thread (call after current thread is suspended) +void Reschedule(); /// Arbitrate the highest priority thread that is waiting -Handle ArbitrateHighestPriorityThread(u32 arbiter, u32 address); +Thread* ArbitrateHighestPriorityThread(Object* arbiter, u32 address); /// Arbitrate all threads currently waiting... -void ArbitrateAllThreads(u32 arbiter, u32 address); +void ArbitrateAllThreads(Object* arbiter, u32 address); -/// Gets the current thread handle -Handle GetCurrentThreadHandle(); +/// Gets the current thread +Thread* GetCurrentThread(); /** * Puts the current thread in the wait state for the given type * @param wait_type Type of wait - * @param wait_handle Handle of Kernel object that we are waiting on, defaults to current thread + * @param wait_object Kernel object that we are waiting on, defaults to current thread + */ +void WaitCurrentThread(WaitType wait_type, Object* wait_object = GetCurrentThread()); + +/** + * Schedules an event to wake up the specified thread after the specified delay. + * @param handle The thread handle. + * @param nanoseconds The time this thread will be allowed to sleep for. */ -void WaitCurrentThread(WaitType wait_type, Handle wait_handle=GetCurrentThreadHandle()); +void WakeThreadAfterDelay(Thread* thread, s64 nanoseconds); /** * Puts the current thread in the wait state for the given type * @param wait_type Type of wait - * @param wait_handle Handle of Kernel object that we are waiting on, defaults to current thread + * @param wait_object Kernel object that we are waiting on * @param wait_address Arbitration address used to resume from wait */ -void WaitCurrentThread(WaitType wait_type, Handle wait_handle, VAddr wait_address); +void WaitCurrentThread(WaitType wait_type, Object* wait_object, VAddr wait_address); -/// Put current thread in a wait state - on WaitSynchronization -void WaitThread_Synchronization(); -/// Get the priority of the thread specified by handle -ResultVal<u32> GetThreadPriority(const Handle handle); - -/// Set the priority of the thread specified by handle -ResultCode SetThreadPriority(Handle handle, s32 priority); /** * Sets up the idle thread, this is a thread that is intended to never execute instructions, @@ -111,10 +156,6 @@ ResultCode SetThreadPriority(Handle handle, s32 priority); * @returns The handle of the idle thread */ Handle SetupIdleThread(); - -/// Whether the current thread is an idle thread -bool IsIdleThread(Handle thread); - /// Initialize threading void ThreadingInit(); diff --git a/src/core/hle/kernel/timer.cpp b/src/core/hle/kernel/timer.cpp new file mode 100644 index 000000000..3b0452d4d --- /dev/null +++ b/src/core/hle/kernel/timer.cpp @@ -0,0 +1,144 @@ +// Copyright 2015 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <set> + +#include "common/common.h" + +#include "core/core_timing.h" +#include "core/hle/kernel/kernel.h" +#include "core/hle/kernel/timer.h" +#include "core/hle/kernel/thread.h" + +namespace Kernel { + +class Timer : public Object { +public: + std::string GetTypeName() const override { return "Timer"; } + std::string GetName() const override { return name; } + + static const HandleType HANDLE_TYPE = HandleType::Timer; + HandleType GetHandleType() const override { return HANDLE_TYPE; } + + ResetType reset_type; ///< The ResetType of this timer + + bool signaled; ///< Whether the timer has been signaled or not + std::set<Handle> waiting_threads; ///< Threads that are waiting for the timer + std::string name; ///< Name of timer (optional) + + u64 initial_delay; ///< The delay until the timer fires for the first time + u64 interval_delay; ///< The delay until the timer fires after the first time + + ResultVal<bool> WaitSynchronization() override { + bool wait = !signaled; + if (wait) { + waiting_threads.insert(GetCurrentThread()->GetHandle()); + Kernel::WaitCurrentThread(WAITTYPE_TIMER, this); + } + return MakeResult<bool>(wait); + } +}; + +/** + * Creates a timer. + * @param handle Reference to handle for the newly created timer + * @param reset_type ResetType describing how to create timer + * @param name Optional name of timer + * @return Newly created Timer object + */ +Timer* CreateTimer(Handle& handle, const ResetType reset_type, const std::string& name) { + Timer* timer = new Timer; + + handle = Kernel::g_handle_table.Create(timer).ValueOr(INVALID_HANDLE); + + timer->reset_type = reset_type; + timer->signaled = false; + timer->name = name; + timer->initial_delay = 0; + timer->interval_delay = 0; + return timer; +} + +ResultCode CreateTimer(Handle* handle, const ResetType reset_type, const std::string& name) { + CreateTimer(*handle, reset_type, name); + return RESULT_SUCCESS; +} + +ResultCode ClearTimer(Handle handle) { + SharedPtr<Timer> timer = Kernel::g_handle_table.Get<Timer>(handle); + + if (timer == nullptr) + return InvalidHandle(ErrorModule::Kernel); + + timer->signaled = false; + return RESULT_SUCCESS; +} + +/// The event type of the generic timer callback event +static int TimerCallbackEventType = -1; + +/// The timer callback event, called when a timer is fired +static void TimerCallback(u64 timer_handle, int cycles_late) { + SharedPtr<Timer> timer = Kernel::g_handle_table.Get<Timer>(timer_handle); + + if (timer == nullptr) { + LOG_CRITICAL(Kernel, "Callback fired for invalid timer %u", timer_handle); + return; + } + + LOG_TRACE(Kernel, "Timer %u fired", timer_handle); + + timer->signaled = true; + + // Resume all waiting threads + for (Handle thread_handle : timer->waiting_threads) { + if (SharedPtr<Thread> thread = Kernel::g_handle_table.Get<Thread>(thread_handle)) + thread->ResumeFromWait(); + } + + timer->waiting_threads.clear(); + + if (timer->reset_type == RESETTYPE_ONESHOT) + timer->signaled = false; + + if (timer->interval_delay != 0) { + // Reschedule the timer with the interval delay + u64 interval_microseconds = timer->interval_delay / 1000; + CoreTiming::ScheduleEvent(usToCycles(interval_microseconds) - cycles_late, + TimerCallbackEventType, timer_handle); + } +} + +ResultCode SetTimer(Handle handle, s64 initial, s64 interval) { + SharedPtr<Timer> timer = Kernel::g_handle_table.Get<Timer>(handle); + + if (timer == nullptr) + return InvalidHandle(ErrorModule::Kernel); + + timer->initial_delay = initial; + timer->interval_delay = interval; + + u64 initial_microseconds = initial / 1000; + CoreTiming::ScheduleEvent(usToCycles(initial_microseconds), TimerCallbackEventType, handle); + return RESULT_SUCCESS; +} + +ResultCode CancelTimer(Handle handle) { + SharedPtr<Timer> timer = Kernel::g_handle_table.Get<Timer>(handle); + + if (timer == nullptr) + return InvalidHandle(ErrorModule::Kernel); + + CoreTiming::UnscheduleEvent(TimerCallbackEventType, handle); + return RESULT_SUCCESS; +} + +void TimersInit() { + TimerCallbackEventType = CoreTiming::RegisterEvent("TimerCallback", TimerCallback); +} + +void TimersShutdown() { +} + +} // namespace diff --git a/src/core/hle/kernel/timer.h b/src/core/hle/kernel/timer.h new file mode 100644 index 000000000..f8aa66b60 --- /dev/null +++ b/src/core/hle/kernel/timer.h @@ -0,0 +1,47 @@ +// Copyright 2015 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "common/common_types.h" + +#include "core/hle/kernel/kernel.h" +#include "core/hle/svc.h" + +namespace Kernel { + +/** + * Cancels a timer + * @param handle Handle of the timer to cancel + */ +ResultCode CancelTimer(Handle handle); + +/** + * Starts a timer with the specified initial delay and interval + * @param handle Handle of the timer to start + * @param initial Delay until the timer is first fired + * @param interval Delay until the timer is fired after the first time + */ +ResultCode SetTimer(Handle handle, s64 initial, s64 interval); + +/** + * Clears a timer + * @param handle Handle of the timer to clear + */ +ResultCode ClearTimer(Handle handle); + +/** + * Creates a timer + * @param Handle to newly created Timer object + * @param reset_type ResetType describing how to create the timer + * @param name Optional name of timer + * @return ResultCode of the error + */ +ResultCode CreateTimer(Handle* handle, const ResetType reset_type, const std::string& name="Unknown"); + +/// Initializes the required variables for timers +void TimersInit(); +/// Tears down the timer variables +void TimersShutdown(); +} // namespace diff --git a/src/core/hle/result.h b/src/core/hle/result.h index 0e9c213e0..82dcf5bba 100644 --- a/src/core/hle/result.h +++ b/src/core/hle/result.h @@ -369,14 +369,14 @@ private: StorageType storage; ResultCode result_code; -#if _DEBUG +#ifdef _DEBUG // The purpose of this pointer is to aid inspecting the type with a debugger, eliminating the // need to cast `storage` to a pointer or pay attention to `result_code`. const T* debug_ptr; #endif void UpdateDebugPtr() { -#if _DEBUG +#ifdef _DEBUG debug_ptr = empty() ? nullptr : static_cast<const T*>(static_cast<const void*>(&storage)); #endif } diff --git a/src/core/hle/service/apt_s.cpp b/src/core/hle/service/apt_s.cpp new file mode 100644 index 000000000..f4599e19d --- /dev/null +++ b/src/core/hle/service/apt_s.cpp @@ -0,0 +1,121 @@ +// Copyright 2015 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + + +#include "common/common.h" +#include "common/file_util.h" + +#include "core/hle/hle.h" +#include "core/hle/kernel/event.h" +#include "core/hle/kernel/mutex.h" +#include "core/hle/kernel/shared_memory.h" +#include "core/hle/service/apt_s.h" + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// Namespace APT_S + +namespace APT_U { + extern void GetLockHandle(Service::Interface* self); + extern void Initialize(Service::Interface* self); + extern void Enable(Service::Interface* self); + extern void InquireNotification(Service::Interface* self); + extern void GetSharedFont(Service::Interface* self); + extern void AppletUtility(Service::Interface* self); + extern void GlanceParameter(Service::Interface* self); + extern void ReceiveParameter(Service::Interface* self); +} + +namespace APT_S { + +const Interface::FunctionInfo FunctionTable[] = { + {0x00010040, APT_U::GetLockHandle, "GetLockHandle"}, + {0x00020080, APT_U::Initialize, "Initialize"}, + {0x00030040, APT_U::Enable, "Enable"}, + {0x00040040, nullptr, "Finalize"}, + {0x00050040, nullptr, "GetAppletManInfo"}, + {0x00060040, nullptr, "GetAppletInfo"}, + {0x00070000, nullptr, "GetLastSignaledAppletId"}, + {0x00080000, nullptr, "CountRegisteredApplet"}, + {0x00090040, nullptr, "IsRegistered"}, + {0x000A0040, nullptr, "GetAttribute"}, + {0x000B0040, APT_U::InquireNotification, "InquireNotification"}, + {0x000C0104, nullptr, "SendParameter"}, + {0x000D0080, APT_U::ReceiveParameter, "ReceiveParameter"}, + {0x000E0080, APT_U::GlanceParameter, "GlanceParameter"}, + {0x000F0100, nullptr, "CancelParameter"}, + {0x001000C2, nullptr, "DebugFunc"}, + {0x001100C0, nullptr, "MapProgramIdForDebug"}, + {0x00120040, nullptr, "SetHomeMenuAppletIdForDebug"}, + {0x00130000, nullptr, "GetPreparationState"}, + {0x00140040, nullptr, "SetPreparationState"}, + {0x00150140, nullptr, "PrepareToStartApplication"}, + {0x00160040, nullptr, "PreloadLibraryApplet"}, + {0x00170040, nullptr, "FinishPreloadingLibraryApplet"}, + {0x00180040, nullptr, "PrepareToStartLibraryApplet"}, + {0x00190040, nullptr, "PrepareToStartSystemApplet"}, + {0x001A0000, nullptr, "PrepareToStartNewestHomeMenu"}, + {0x001B00C4, nullptr, "StartApplication"}, + {0x001C0000, nullptr, "WakeupApplication"}, + {0x001D0000, nullptr, "CancelApplication"}, + {0x001E0084, nullptr, "StartLibraryApplet"}, + {0x001F0084, nullptr, "StartSystemApplet"}, + {0x00200044, nullptr, "StartNewestHomeMenu"}, + {0x00210000, nullptr, "OrderToCloseApplication"}, + {0x00220040, nullptr, "PrepareToCloseApplication"}, + {0x00230040, nullptr, "PrepareToJumpToApplication"}, + {0x00240044, nullptr, "JumpToApplication"}, + {0x002500C0, nullptr, "PrepareToCloseLibraryApplet"}, + {0x00260000, nullptr, "PrepareToCloseSystemApplet"}, + {0x00270044, nullptr, "CloseApplication"}, + {0x00280044, nullptr, "CloseLibraryApplet"}, + {0x00290044, nullptr, "CloseSystemApplet"}, + {0x002A0000, nullptr, "OrderToCloseSystemApplet"}, + {0x002B0000, nullptr, "PrepareToJumpToHomeMenu"}, + {0x002C0044, nullptr, "JumpToHomeMenu"}, + {0x002D0000, nullptr, "PrepareToLeaveHomeMenu"}, + {0x002E0044, nullptr, "LeaveHomeMenu"}, + {0x002F0040, nullptr, "PrepareToLeaveResidentApplet"}, + {0x00300044, nullptr, "LeaveResidentApplet"}, + {0x00310100, nullptr, "PrepareToDoApplicationJump"}, + {0x00320084, nullptr, "DoApplicationJump"}, + {0x00330000, nullptr, "GetProgramIdOnApplicationJump"}, + {0x00340084, nullptr, "SendDeliverArg"}, + {0x00350080, nullptr, "ReceiveDeliverArg"}, + {0x00360040, nullptr, "LoadSysMenuArg"}, + {0x00370042, nullptr, "StoreSysMenuArg"}, + {0x00380040, nullptr, "PreloadResidentApplet"}, + {0x00390040, nullptr, "PrepareToStartResidentApplet"}, + {0x003A0044, nullptr, "StartResidentApplet"}, + {0x003B0040, nullptr, "CancelLibraryApplet"}, + {0x003C0042, nullptr, "SendDspSleep"}, + {0x003D0042, nullptr, "SendDspWakeUp"}, + {0x003E0080, nullptr, "ReplySleepQuery"}, + {0x003F0040, nullptr, "ReplySleepNotificationComplete"}, + {0x00400042, nullptr, "SendCaptureBufferInfo"}, + {0x00410040, nullptr, "ReceiveCaptureBufferInfo"}, + {0x00420080, nullptr, "SleepSystem"}, + {0x00430040, nullptr, "NotifyToWait"}, + {0x00440000, APT_U::GetSharedFont, "GetSharedFont"}, + {0x00450040, nullptr, "GetWirelessRebootInfo"}, + {0x00460104, nullptr, "Wrap"}, + {0x00470104, nullptr, "Unwrap"}, + {0x00480100, nullptr, "GetProgramInfo"}, + {0x00490180, nullptr, "Reboot"}, + {0x004A0040, nullptr, "GetCaptureInfo"}, + {0x004B00C2, APT_U::AppletUtility, "AppletUtility"}, + {0x004C0000, nullptr, "SetFatalErrDispMode"}, + {0x004D0080, nullptr, "GetAppletProgramInfo"}, + {0x004E0000, nullptr, "HardwareResetAsync"}, + {0x004F0080, nullptr, "SetApplicationCpuTimeLimit"}, + {0x00500040, nullptr, "GetApplicationCpuTimeLimit"}, +}; + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// Interface class + +Interface::Interface() { + Register(FunctionTable, ARRAY_SIZE(FunctionTable)); +} + +} // namespace diff --git a/src/core/hle/service/apt_s.h b/src/core/hle/service/apt_s.h new file mode 100644 index 000000000..f097c9747 --- /dev/null +++ b/src/core/hle/service/apt_s.h @@ -0,0 +1,30 @@ +// Copyright 2015 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "core/hle/service/service.h" + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// Namespace APT_S + +namespace APT_S { + +// Application and title launching service. These services handle signaling for home/power button as +// well. Only one session for either APT service can be open at a time, normally processes close the +// service handle immediately once finished using the service. The commands for APT:U and APT:S are +// exactly the same, however certain commands are only accessible with APT:S(NS module will call +// svcBreak when the command isn't accessible). See http://3dbrew.org/wiki/NS#APT_Services. + +/// Interface to "APT:S" service +class Interface : public Service::Interface { +public: + Interface(); + + std::string GetPortName() const override { + return "APT:S"; + } +}; + +} // namespace diff --git a/src/core/hle/service/apt_u.cpp b/src/core/hle/service/apt_u.cpp index d8b261ba7..69a7bcf92 100644 --- a/src/core/hle/service/apt_u.cpp +++ b/src/core/hle/service/apt_u.cpp @@ -10,7 +10,7 @@ #include "core/hle/kernel/event.h" #include "core/hle/kernel/mutex.h" #include "core/hle/kernel/shared_memory.h" -#include "apt_u.h" +#include "core/hle/service/apt_u.h" //////////////////////////////////////////////////////////////////////////////////////////////////// // Namespace APT_U @@ -25,10 +25,12 @@ namespace APT_U { // correctly mapping it in Citra, however we still do not understand how the mapping is determined. static const VAddr SHARED_FONT_VADDR = 0x18000000; -// Handle to shared memory region designated to for shared system font +/// Handle to shared memory region designated to for shared system font static Handle shared_font_mem = 0; static Handle lock_handle = 0; +static Handle notification_event_handle = 0; ///< APT notification event handle +static Handle pause_event_handle = 0; ///< APT pause event handle static std::vector<u8> shared_font; /// Signals used by APT functions @@ -42,18 +44,28 @@ enum class SignalType : u32 { void Initialize(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); - cmd_buff[3] = Kernel::CreateEvent(RESETTYPE_ONESHOT, "APT_U:Menu"); // APT menu event handle - cmd_buff[4] = Kernel::CreateEvent(RESETTYPE_ONESHOT, "APT_U:Pause"); // APT pause event handle + notification_event_handle = Kernel::CreateEvent(RESETTYPE_ONESHOT, "APT_U:Notification"); + pause_event_handle = Kernel::CreateEvent(RESETTYPE_ONESHOT, "APT_U:Pause"); - Kernel::SetEventLocked(cmd_buff[3], true); - Kernel::SetEventLocked(cmd_buff[4], false); // Fire start event + cmd_buff[3] = notification_event_handle; + cmd_buff[4] = pause_event_handle; + + Kernel::SetEventLocked(notification_event_handle, true); + Kernel::SetEventLocked(pause_event_handle, false); // Fire start event _assert_msg_(KERNEL, (0 != lock_handle), "Cannot initialize without lock"); Kernel::ReleaseMutex(lock_handle); cmd_buff[1] = 0; // No error +} - LOG_DEBUG(Service_APT, "called"); +void NotifyToWait(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + u32 app_id = cmd_buff[1]; + // TODO(Subv): Verify this, it seems to get SWKBD and Home Menu further. + Kernel::SignalEvent(pause_event_handle); + LOG_WARNING(Service_APT, "(STUBBED) app_id=%u", app_id); + cmd_buff[1] = 0; } void GetLockHandle(Service::Interface* self) { @@ -86,7 +98,7 @@ void Enable(Service::Interface* self) { void InquireNotification(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); - u32 app_id = cmd_buff[2]; + u32 app_id = cmd_buff[1]; cmd_buff[1] = 0; // No error cmd_buff[2] = static_cast<u32>(SignalType::None); // Signal type LOG_WARNING(Service_APT, "(STUBBED) called app_id=0x%08X", app_id); @@ -194,8 +206,6 @@ void AppletUtility(Service::Interface* self) { * 4 : Handle to shared font memory */ void GetSharedFont(Service::Interface* self) { - LOG_TRACE(Kernel_SVC, "called"); - u32* cmd_buff = Kernel::GetCommandBuffer(); if (!shared_font.empty()) { @@ -281,7 +291,7 @@ const Interface::FunctionInfo FunctionTable[] = { {0x00400042, nullptr, "SendCaptureBufferInfo"}, {0x00410040, nullptr, "ReceiveCaptureBufferInfo"}, {0x00420080, nullptr, "SleepSystem"}, - {0x00430040, nullptr, "NotifyToWait"}, + {0x00430040, NotifyToWait, "NotifyToWait"}, {0x00440000, GetSharedFont, "GetSharedFont"}, {0x00450040, nullptr, "GetWirelessRebootInfo"}, {0x00460104, nullptr, "Wrap"}, diff --git a/src/core/hle/service/cfg/cfg_s.cpp b/src/core/hle/service/cfg/cfg_s.cpp new file mode 100644 index 000000000..cf4e82152 --- /dev/null +++ b/src/core/hle/service/cfg/cfg_s.cpp @@ -0,0 +1,98 @@ +// Copyright 2015 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "common/log.h" +#include "core/hle/hle.h" +#include "core/hle/service/cfg/cfg.h" +#include "core/hle/service/cfg/cfg_s.h" + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// Namespace CFG_S + +namespace CFG_S { + +/** + * CFG_S::GetConfigInfoBlk2 service function + * Inputs: + * 0 : 0x00010082 + * 1 : Size + * 2 : Block ID + * 3 : Descriptor for the output buffer + * 4 : Output buffer pointer + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + */ +static void GetConfigInfoBlk2(Service::Interface* self) { + u32* cmd_buffer = Kernel::GetCommandBuffer(); + u32 size = cmd_buffer[1]; + u32 block_id = cmd_buffer[2]; + u8* data_pointer = Memory::GetPointer(cmd_buffer[4]); + + if (data_pointer == nullptr) { + cmd_buffer[1] = -1; // TODO(Subv): Find the right error code + return; + } + + cmd_buffer[1] = Service::CFG::GetConfigInfoBlock(block_id, size, 0x2, data_pointer).raw; +} + +/** + * CFG_S::GetConfigInfoBlk8 service function + * Inputs: + * 0 : 0x04010082 + * 1 : Size + * 2 : Block ID + * 3 : Descriptor for the output buffer + * 4 : Output buffer pointer + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + */ +static void GetConfigInfoBlk8(Service::Interface* self) { + u32* cmd_buffer = Kernel::GetCommandBuffer(); + u32 size = cmd_buffer[1]; + u32 block_id = cmd_buffer[2]; + u8* data_pointer = Memory::GetPointer(cmd_buffer[4]); + + if (data_pointer == nullptr) { + cmd_buffer[1] = -1; // TODO(Subv): Find the right error code + return; + } + + cmd_buffer[1] = Service::CFG::GetConfigInfoBlock(block_id, size, 0x8, data_pointer).raw; +} + +/** + * CFG_S::UpdateConfigNANDSavegame service function + * Inputs: + * 0 : 0x04030000 + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + */ +static void UpdateConfigNANDSavegame(Service::Interface* self) { + u32* cmd_buffer = Kernel::GetCommandBuffer(); + cmd_buffer[1] = Service::CFG::UpdateConfigNANDSavegame().raw; +} + +const Interface::FunctionInfo FunctionTable[] = { + {0x00010082, GetConfigInfoBlk2, "GetConfigInfoBlk2"}, + {0x00020000, nullptr, "SecureInfoGetRegion"}, + {0x04010082, GetConfigInfoBlk8, "GetConfigInfoBlk8"}, + {0x04020082, nullptr, "SetConfigInfoBlk4"}, + {0x04030000, UpdateConfigNANDSavegame, "UpdateConfigNANDSavegame"}, + {0x04040042, nullptr, "GetLocalFriendCodeSeedData"}, + {0x04050000, nullptr, "GetLocalFriendCodeSeed"}, + {0x04060000, nullptr, "SecureInfoGetRegion"}, + {0x04070000, nullptr, "SecureInfoGetByte101"}, + {0x04080042, nullptr, "SecureInfoGetSerialNo"}, + {0x04090000, nullptr, "UpdateConfigBlk00040003"}, +}; + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// Interface class + +Interface::Interface() { + Register(FunctionTable, ARRAY_SIZE(FunctionTable)); +} + +} // namespace diff --git a/src/core/hle/service/cfg/cfg_s.h b/src/core/hle/service/cfg/cfg_s.h new file mode 100644 index 000000000..d8b67137f --- /dev/null +++ b/src/core/hle/service/cfg/cfg_s.h @@ -0,0 +1,23 @@ +// Copyright 2015 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "core/hle/service/service.h" + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// Namespace CFG_S + +namespace CFG_S { + +class Interface : public Service::Interface { +public: + Interface(); + + std::string GetPortName() const override { + return "cfg:s"; + } +}; + +} // namespace diff --git a/src/core/hle/service/dsp_dsp.cpp b/src/core/hle/service/dsp_dsp.cpp index d4affdfbf..d5e39ea4b 100644 --- a/src/core/hle/service/dsp_dsp.cpp +++ b/src/core/hle/service/dsp_dsp.cpp @@ -23,11 +23,8 @@ void SignalInterrupt() { // that check the DSP interrupt signal event to run. We should figure out the different types of // DSP interrupts, and trigger them at the appropriate times. - if (interrupt_event == 0) { - LOG_WARNING(Service_DSP, "cannot signal interrupt until DSP event has been created!"); - return; - } - Kernel::SignalEvent(interrupt_event); + if (interrupt_event != 0) + Kernel::SignalEvent(interrupt_event); } /** diff --git a/src/core/hle/service/fs/fs_user.cpp b/src/core/hle/service/fs/fs_user.cpp index 7eb32146d..56f3117f4 100644 --- a/src/core/hle/service/fs/fs_user.cpp +++ b/src/core/hle/service/fs/fs_user.cpp @@ -27,8 +27,6 @@ static void Initialize(Service::Interface* self) { // TODO(Link Mauve): check the behavior when cmd_buff[1] isn't 32, as per // http://3dbrew.org/wiki/FS:Initialize#Request cmd_buff[1] = RESULT_SUCCESS.raw; - - LOG_DEBUG(Service_FS, "called"); } /** @@ -104,8 +102,8 @@ static void OpenFileDirectly(Service::Interface* self) { FileSys::Path archive_path(archivename_type, archivename_size, archivename_ptr); FileSys::Path file_path(filename_type, filename_size, filename_ptr); - LOG_DEBUG(Service_FS, "archive_path=%s file_path=%s, mode=%u attributes=%d", - archive_path.DebugStr().c_str(), file_path.DebugStr().c_str(), mode.hex, attributes); + LOG_DEBUG(Service_FS, "archive_id=0x%08X archive_path=%s file_path=%s, mode=%u attributes=%d", + archive_id, archive_path.DebugStr().c_str(), file_path.DebugStr().c_str(), mode.hex, attributes); ResultVal<ArchiveHandle> archive_handle = OpenArchive(archive_id, archive_path); if (archive_handle.Failed()) { @@ -367,7 +365,7 @@ static void OpenArchive(Service::Interface* self) { u32 archivename_ptr = cmd_buff[5]; FileSys::Path archive_path(archivename_type, archivename_size, archivename_ptr); - LOG_DEBUG(Service_FS, "archive_path=%s", archive_path.DebugStr().c_str()); + LOG_DEBUG(Service_FS, "archive_id=0x%08X archive_path=%s", archive_id, archive_path.DebugStr().c_str()); ResultVal<ArchiveHandle> handle = OpenArchive(archive_id, archive_path); cmd_buff[1] = handle.Code().raw; @@ -408,8 +406,6 @@ static void IsSdmcDetected(Service::Interface* self) { cmd_buff[1] = 0; cmd_buff[2] = Settings::values.use_virtual_sd ? 1 : 0; - - LOG_DEBUG(Service_FS, "called"); } /** diff --git a/src/core/hle/service/gsp_gpu.cpp b/src/core/hle/service/gsp_gpu.cpp index 26a43217e..4ca2b9bd0 100644 --- a/src/core/hle/service/gsp_gpu.cpp +++ b/src/core/hle/service/gsp_gpu.cpp @@ -210,14 +210,27 @@ void SignalInterrupt(InterruptId interrupt_id) { } for (int thread_id = 0; thread_id < 0x4; ++thread_id) { InterruptRelayQueue* interrupt_relay_queue = GetInterruptRelayQueue(thread_id); - interrupt_relay_queue->number_interrupts = interrupt_relay_queue->number_interrupts + 1; - u8 next = interrupt_relay_queue->index; next += interrupt_relay_queue->number_interrupts; next = next % 0x34; // 0x34 is the number of interrupt slots + interrupt_relay_queue->number_interrupts += 1; + interrupt_relay_queue->slot[next] = interrupt_id; interrupt_relay_queue->error_code = 0x0; // No error + + // Update framebuffer information if requested + // TODO(yuriks): Confirm where this code should be called. It is definitely updated without + // executing any GSP commands, only waiting on the event. + for (int screen_id = 0; screen_id < 2; ++screen_id) { + FrameBufferUpdate* info = GetFrameBufferInfo(thread_id, screen_id); + + if (info->is_dirty) { + SetBufferSwap(screen_id, info->framebuffer_info[info->index]); + } + + info->is_dirty = false; + } } Kernel::SignalEvent(g_interrupt_event); } @@ -269,8 +282,6 @@ static void ExecuteCommand(const Command& command, u32 thread_id) { WriteGPURegister(GPU_REG_INDEX(memory_fill_config[1].address_end), Memory::VirtualToPhysicalAddress(params.end2) >> 3); WriteGPURegister(GPU_REG_INDEX(memory_fill_config[1].size), params.end2 - params.start2); WriteGPURegister(GPU_REG_INDEX(memory_fill_config[1].value), params.value2); - - SignalInterrupt(InterruptId::PSC0); break; } @@ -283,22 +294,6 @@ static void ExecuteCommand(const Command& command, u32 thread_id) { WriteGPURegister(GPU_REG_INDEX(display_transfer_config.output_size), params.out_buffer_size); WriteGPURegister(GPU_REG_INDEX(display_transfer_config.flags), params.flags); WriteGPURegister(GPU_REG_INDEX(display_transfer_config.trigger), 1); - - // TODO(bunnei): Determine if these interrupts should be signalled here. - SignalInterrupt(InterruptId::PSC1); - SignalInterrupt(InterruptId::PPF); - - // Update framebuffer information if requested - for (int screen_id = 0; screen_id < 2; ++screen_id) { - FrameBufferUpdate* info = GetFrameBufferInfo(thread_id, screen_id); - - if (info->is_dirty) { - SetBufferSwap(screen_id, info->framebuffer_info[info->index]); - info->framebuffer_info->active_fb = info->framebuffer_info->active_fb ^ 1; - } - - info->is_dirty = false; - } break; } @@ -331,9 +326,6 @@ static void ExecuteCommand(const Command& command, u32 thread_id) { /// This triggers handling of the GX command written to the command buffer in shared memory. static void TriggerCmdReqQueue(Service::Interface* self) { - - LOG_TRACE(Service_GSP, "called"); - // Iterate through each thread's command queue... for (unsigned thread_id = 0; thread_id < 0x4; ++thread_id) { CommandBuffer* command_buffer = (CommandBuffer*)GetCommandBuffer(thread_id); diff --git a/src/core/hle/service/gsp_gpu.h b/src/core/hle/service/gsp_gpu.h index 932b6170f..65abb194a 100644 --- a/src/core/hle/service/gsp_gpu.h +++ b/src/core/hle/service/gsp_gpu.h @@ -45,21 +45,16 @@ enum class CommandId : u32 { /// GSP thread interrupt relay queue struct InterruptRelayQueue { - union { - u32 hex; - - // Index of last interrupt in the queue - BitField<0,8,u32> index; - - // Number of interrupts remaining to be processed by the userland code - BitField<8,8,u32> number_interrupts; - - // Error code - zero on success, otherwise an error has occurred - BitField<16,8,u32> error_code; - }; - - u32 unk0; - u32 unk1; + // Index of last interrupt in the queue + u8 index; + // Number of interrupts remaining to be processed by the userland code + u8 number_interrupts; + // Error code - zero on success, otherwise an error has occurred + u8 error_code; + u8 padding1; + + u32 missed_PDC0; + u32 missed_PDC1; InterruptId slot[0x34]; ///< Interrupt ID slots }; diff --git a/src/core/hle/service/hid_user.cpp b/src/core/hle/service/hid_user.cpp index 99b0ea5a0..1403b1de9 100644 --- a/src/core/hle/service/hid_user.cpp +++ b/src/core/hle/service/hid_user.cpp @@ -4,6 +4,7 @@ #include "common/log.h" +#include "core/arm/arm_interface.h" #include "core/hle/hle.h" #include "core/hle/kernel/event.h" #include "core/hle/kernel/shared_memory.h" @@ -162,8 +163,6 @@ static void GetIPCHandles(Service::Interface* self) { cmd_buff[6] = event_accelerometer; cmd_buff[7] = event_gyroscope; cmd_buff[8] = event_debug_pad; - - LOG_TRACE(Service_HID, "called"); } const Interface::FunctionInfo FunctionTable[] = { diff --git a/src/core/hle/service/ptm_sysm.cpp b/src/core/hle/service/ptm_sysm.cpp new file mode 100644 index 000000000..4b5f86a47 --- /dev/null +++ b/src/core/hle/service/ptm_sysm.cpp @@ -0,0 +1,56 @@ +// Copyright 2015 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "common/log.h" +#include "common/make_unique.h" +#include "core/file_sys/archive_extsavedata.h" +#include "core/hle/hle.h" +#include "core/hle/service/ptm_sysm.h" + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// Namespace PTM_SYSM + +namespace PTM_SYSM { + +const Interface::FunctionInfo FunctionTable[] = { + {0x040100C0, nullptr, "SetRtcAlarmEx"}, + {0x04020042, nullptr, "ReplySleepQuery"}, + {0x04030042, nullptr, "NotifySleepPreparationComplete"}, + {0x04040102, nullptr, "SetWakeupTrigger"}, + {0x04050000, nullptr, "GetAwakeReason"}, + {0x04060000, nullptr, "RequestSleep"}, + {0x040700C0, nullptr, "ShutdownAsync"}, + {0x04080000, nullptr, "Awake"}, + {0x04090080, nullptr, "RebootAsync"}, + {0x040A0000, nullptr, "CheckNew3DS"}, + {0x08010640, nullptr, "SetInfoLEDPattern"}, + {0x08020040, nullptr, "SetInfoLEDPatternHeader"}, + {0x08030000, nullptr, "GetInfoLEDStatus"}, + {0x08040040, nullptr, "SetBatteryEmptyLEDPattern"}, + {0x08050000, nullptr, "ClearStepHistory"}, + {0x080600C2, nullptr, "SetStepHistory"}, + {0x08070082, nullptr, "GetPlayHistory"}, + {0x08080000, nullptr, "GetPlayHistoryStart"}, + {0x08090000, nullptr, "GetPlayHistoryLength"}, + {0x080A0000, nullptr, "ClearPlayHistory"}, + {0x080B0080, nullptr, "CalcPlayHistoryStart"}, + {0x080C0080, nullptr, "SetUserTime"}, + {0x080D0000, nullptr, "InvalidateSystemTime"}, + {0x080E0140, nullptr, "NotifyPlayEvent"}, + {0x080F0000, nullptr, "IsLegacyPowerOff"}, + {0x08100000, nullptr, "ClearLegacyPowerOff"}, + {0x08110000, nullptr, "GetShellStatus"}, + {0x08120000, nullptr, "IsShutdownByBatteryEmpty"}, + {0x08130000, nullptr, "FormatSavedata"}, + {0x08140000, nullptr, "GetLegacyJumpProhibitedFlag"} +}; + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// Interface class + +Interface::Interface() { + Register(FunctionTable, ARRAY_SIZE(FunctionTable)); +} + +} // namespace diff --git a/src/core/hle/service/ptm_sysm.h b/src/core/hle/service/ptm_sysm.h new file mode 100644 index 000000000..0f267b214 --- /dev/null +++ b/src/core/hle/service/ptm_sysm.h @@ -0,0 +1,23 @@ +// Copyright 2015 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "core/hle/service/service.h" + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// Namespace PTM_SYSM + +namespace PTM_SYSM { + +class Interface : public Service::Interface { +public: + Interface(); + + std::string GetPortName() const override { + return "ptm:sysm"; + } +}; + +} // namespace diff --git a/src/core/hle/service/ptm_u.cpp b/src/core/hle/service/ptm_u.cpp index fd79cd8ab..753180add 100644 --- a/src/core/hle/service/ptm_u.cpp +++ b/src/core/hle/service/ptm_u.cpp @@ -76,8 +76,6 @@ static void GetShellState(Service::Interface* self) { cmd_buff[1] = 0; cmd_buff[2] = shell_open ? 1 : 0; - - LOG_TRACE(Service_PTM, "PTM_U::GetShellState called"); } /** diff --git a/src/core/hle/service/service.cpp b/src/core/hle/service/service.cpp index 0c5597283..446ed5164 100644 --- a/src/core/hle/service/service.cpp +++ b/src/core/hle/service/service.cpp @@ -11,10 +11,12 @@ #include "core/hle/service/am_app.h" #include "core/hle/service/am_net.h" #include "core/hle/service/apt_a.h" +#include "core/hle/service/apt_s.h" #include "core/hle/service/apt_u.h" #include "core/hle/service/boss_u.h" #include "core/hle/service/cecd_u.h" #include "core/hle/service/cfg/cfg_i.h" +#include "core/hle/service/cfg/cfg_s.h" #include "core/hle/service/cfg/cfg_u.h" #include "core/hle/service/csnd_snd.h" #include "core/hle/service/dsp_dsp.h" @@ -34,6 +36,7 @@ #include "core/hle/service/nwm_uds.h" #include "core/hle/service/pm_app.h" #include "core/hle/service/ptm_u.h" +#include "core/hle/service/ptm_sysm.h" #include "core/hle/service/soc_u.h" #include "core/hle/service/srv.h" #include "core/hle/service/ssl_c.h" @@ -59,7 +62,8 @@ void Manager::DeleteService(const std::string& port_name) { } Interface* Manager::FetchFromHandle(Handle handle) { - return Kernel::g_handle_table.Get<Interface>(handle); + // TODO(yuriks): This function is very suspicious and should probably be exterminated. + return Kernel::g_handle_table.Get<Interface>(handle).get(); } Interface* Manager::FetchFromPortName(const std::string& port_name) { @@ -84,10 +88,12 @@ void Init() { g_manager->AddService(new AM_APP::Interface); g_manager->AddService(new AM_NET::Interface); g_manager->AddService(new APT_A::Interface); + g_manager->AddService(new APT_S::Interface); g_manager->AddService(new APT_U::Interface); g_manager->AddService(new BOSS_U::Interface); g_manager->AddService(new CECD_U::Interface); g_manager->AddService(new CFG_I::Interface); + g_manager->AddService(new CFG_S::Interface); g_manager->AddService(new CFG_U::Interface); g_manager->AddService(new CSND_SND::Interface); g_manager->AddService(new DSP_DSP::Interface); @@ -107,6 +113,7 @@ void Init() { g_manager->AddService(new NWM_UDS::Interface); g_manager->AddService(new PM_APP::Interface); g_manager->AddService(new PTM_U::Interface); + g_manager->AddService(new PTM_SYSM::Interface); g_manager->AddService(new SOC_U::Interface); g_manager->AddService(new SSL_C::Interface); g_manager->AddService(new Y2R_U::Interface); diff --git a/src/core/hle/service/service.h b/src/core/hle/service/service.h index 41ba1e554..e75d5008b 100644 --- a/src/core/hle/service/service.h +++ b/src/core/hle/service/service.h @@ -33,6 +33,22 @@ class Interface : public Kernel::Session { // processes. friend class Manager; + + /** + * Creates a function string for logging, complete with the name (or header code, depending + * on what's passed in) the port name, and all the cmd_buff arguments. + */ + std::string MakeFunctionString(const std::string& name, const std::string& port_name, const u32* cmd_buff) { + // Number of params == bits 0-5 + bits 6-11 + int num_params = (cmd_buff[0] & 0x3F) + ((cmd_buff[0] >> 6) & 0x3F); + + std::string function_string = Common::StringFromFormat("function '%s': port=%s", name.c_str(), port_name.c_str()); + for (int i = 1; i <= num_params; ++i) { + function_string += Common::StringFromFormat(", cmd_buff[%i]=%u", i, cmd_buff[i]); + } + return function_string; + } + public: std::string GetName() const override { return GetPortName(); } @@ -72,21 +88,14 @@ public: auto itr = m_functions.find(cmd_buff[0]); if (itr == m_functions.end() || itr->second.func == nullptr) { - // Number of params == bits 0-5 + bits 6-11 - int num_params = (cmd_buff[0] & 0x3F) + ((cmd_buff[0] >> 6) & 0x3F); - - std::string error = "unknown/unimplemented function '%s': port=%s"; - for (int i = 1; i <= num_params; ++i) { - error += Common::StringFromFormat(", cmd_buff[%i]=%u", i, cmd_buff[i]); - } - - std::string name = (itr == m_functions.end()) ? Common::StringFromFormat("0x%08X", cmd_buff[0]) : itr->second.name; - - LOG_ERROR(Service, error.c_str(), name.c_str(), GetPortName().c_str()); + std::string function_name = (itr == m_functions.end()) ? Common::StringFromFormat("0x%08X", cmd_buff[0]) : itr->second.name; + LOG_ERROR(Service, "%s %s", "unknown/unimplemented", MakeFunctionString(function_name, GetPortName(), cmd_buff).c_str()); // TODO(bunnei): Hack - ignore error cmd_buff[1] = 0; return MakeResult<bool>(false); + } else { + LOG_TRACE(Service, "%s", MakeFunctionString(itr->second.name, GetPortName(), cmd_buff).c_str()); } itr->second.func(this); diff --git a/src/core/hle/service/soc_u.cpp b/src/core/hle/service/soc_u.cpp index f502c6afe..bb8ee86be 100644 --- a/src/core/hle/service/soc_u.cpp +++ b/src/core/hle/service/soc_u.cpp @@ -7,6 +7,19 @@ #if EMU_PLATFORM == PLATFORM_WINDOWS #include <winsock2.h> #include <ws2tcpip.h> + +// MinGW does not define several errno constants +#ifndef _MSC_VER +#define EBADMSG 104 +#define ENODATA 120 +#define ENOMSG 122 +#define ENOSR 124 +#define ENOSTR 125 +#define ETIME 137 +#define EIDRM 2001 +#define ENOLINK 2002 +#endif // _MSC_VER + #else #include <sys/socket.h> #include <netinet/in.h> diff --git a/src/core/hle/service/srv.cpp b/src/core/hle/service/srv.cpp index 912b52adf..ac5f30a28 100644 --- a/src/core/hle/service/srv.cpp +++ b/src/core/hle/service/srv.cpp @@ -14,16 +14,12 @@ namespace SRV { static Handle g_event_handle = 0; static void Initialize(Service::Interface* self) { - LOG_DEBUG(Service_SRV, "called"); - u32* cmd_buff = Kernel::GetCommandBuffer(); cmd_buff[1] = 0; // No error } static void GetProcSemaphore(Service::Interface* self) { - LOG_TRACE(Service_SRV, "called"); - u32* cmd_buff = Kernel::GetCommandBuffer(); // TODO(bunnei): Change to a semaphore once these have been implemented diff --git a/src/core/hle/service/y2r_u.cpp b/src/core/hle/service/y2r_u.cpp index f9e3619dd..b3d873ef0 100644 --- a/src/core/hle/service/y2r_u.cpp +++ b/src/core/hle/service/y2r_u.cpp @@ -12,6 +12,21 @@ namespace Y2R_U { +/** + * Y2R_U::IsBusyConversion service function + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + * 2 : Whether the current conversion is of type busy conversion (?) + */ +static void IsBusyConversion(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + cmd_buff[1] = RESULT_SUCCESS.raw;; + cmd_buff[2] = 0; + + LOG_WARNING(Service, "(STUBBED) called"); +} + const Interface::FunctionInfo FunctionTable[] = { {0x00010040, nullptr, "SetInputFormat"}, {0x00030040, nullptr, "SetOutputFormat"}, @@ -29,7 +44,7 @@ const Interface::FunctionInfo FunctionTable[] = { {0x00220040, nullptr, "SetAlpha"}, {0x00260000, nullptr, "StartConversion"}, {0x00270000, nullptr, "StopConversion"}, - {0x00280000, nullptr, "IsBusyConversion"}, + {0x00280000, IsBusyConversion, "IsBusyConversion"}, {0x002A0000, nullptr, "PingProcess"}, {0x002B0000, nullptr, "DriverInitialize"}, {0x002C0000, nullptr, "DriverFinalize"} diff --git a/src/core/hle/svc.cpp b/src/core/hle/svc.cpp index c25409a9f..a487f757c 100644 --- a/src/core/hle/svc.cpp +++ b/src/core/hle/svc.cpp @@ -7,6 +7,7 @@ #include "common/string_util.h" #include "common/symbols.h" +#include "core/arm/arm_interface.h" #include "core/mem_map.h" #include "core/hle/kernel/address_arbiter.h" @@ -15,6 +16,7 @@ #include "core/hle/kernel/semaphore.h" #include "core/hle/kernel/shared_memory.h" #include "core/hle/kernel/thread.h" +#include "core/hle/kernel/timer.h" #include "core/hle/function_wrappers.h" #include "core/hle/result.h" @@ -23,6 +25,8 @@ //////////////////////////////////////////////////////////////////////////////////////////////////// // Namespace SVC +using Kernel::SharedPtr; + namespace SVC { enum ControlMemoryOperation { @@ -92,7 +96,7 @@ static Result ConnectToPort(Handle* out, const char* port_name) { /// Synchronize to an OS service static Result SendSyncRequest(Handle handle) { - Kernel::Session* session = Kernel::g_handle_table.Get<Kernel::Session>(handle); + SharedPtr<Kernel::Session> session = Kernel::g_handle_table.Get<Kernel::Session>(handle); if (session == nullptr) { return InvalidHandle(ErrorModule::Kernel).raw; } @@ -116,20 +120,19 @@ static Result CloseHandle(Handle handle) { /// Wait for a handle to synchronize, timeout after the specified nanoseconds static Result WaitSynchronization1(Handle handle, s64 nano_seconds) { - // TODO(bunnei): Do something with nano_seconds, currently ignoring this - bool wait_infinite = (nano_seconds == -1); // Used to wait until a thread has terminated - - Kernel::Object* object = Kernel::g_handle_table.GetGeneric(handle); + SharedPtr<Kernel::Object> object = Kernel::g_handle_table.GetGeneric(handle); if (object == nullptr) return InvalidHandle(ErrorModule::Kernel).raw; - LOG_TRACE(Kernel_SVC, "called handle=0x%08X(%s:%s), nanoseconds=%lld", handle, object->GetTypeName().c_str(), - object->GetName().c_str(), nano_seconds); + LOG_TRACE(Kernel_SVC, "called handle=0x%08X(%s:%s), nanoseconds=%lld", handle, + object->GetTypeName().c_str(), object->GetName().c_str(), nano_seconds); ResultVal<bool> wait = object->WaitSynchronization(); // Check for next thread to schedule if (wait.Succeeded() && *wait) { + // Create an event to wake the thread up after the specified nanosecond delay has passed + Kernel::WakeThreadAfterDelay(Kernel::GetCurrentThread(), nano_seconds); HLE::Reschedule(__func__); } @@ -139,6 +142,7 @@ static Result WaitSynchronization1(Handle handle, s64 nano_seconds) { /// Wait for the given handles to synchronize, timeout after the specified nanoseconds static Result WaitSynchronizationN(s32* out, Handle* handles, s32 handle_count, bool wait_all, s64 nano_seconds) { + // TODO(bunnei): Do something with nano_seconds, currently ignoring this bool unlock_all = true; bool wait_infinite = (nano_seconds == -1); // Used to wait until a thread has terminated @@ -148,12 +152,12 @@ static Result WaitSynchronizationN(s32* out, Handle* handles, s32 handle_count, // Iterate through each handle, synchronize kernel object for (s32 i = 0; i < handle_count; i++) { - Kernel::Object* object = Kernel::g_handle_table.GetGeneric(handles[i]); + SharedPtr<Kernel::Object> object = Kernel::g_handle_table.GetGeneric(handles[i]); if (object == nullptr) return InvalidHandle(ErrorModule::Kernel).raw; - LOG_TRACE(Kernel_SVC, "\thandle[%d] = 0x%08X(%s:%s)", i, handles[i], object->GetTypeName().c_str(), - object->GetName().c_str()); + LOG_TRACE(Kernel_SVC, "\thandle[%d] = 0x%08X(%s:%s)", i, handles[i], + object->GetTypeName().c_str(), object->GetName().c_str()); // TODO(yuriks): Verify how the real function behaves when an error happens here ResultVal<bool> wait_result = object->WaitSynchronization(); @@ -180,7 +184,6 @@ static Result WaitSynchronizationN(s32* out, Handle* handles, s32 handle_count, /// Create an address arbiter (to allocate access to shared resources) static Result CreateAddressArbiter(u32* arbiter) { - LOG_TRACE(Kernel_SVC, "called"); Handle handle = Kernel::CreateAddressArbiter(); *arbiter = handle; return 0; @@ -191,7 +194,7 @@ static Result ArbitrateAddress(Handle arbiter, u32 address, u32 type, u32 value, LOG_TRACE(Kernel_SVC, "called handle=0x%08X, address=0x%08X, type=0x%08X, value=0x%08X", arbiter, address, type, value); return Kernel::ArbitrateAddress(arbiter, static_cast<Kernel::ArbitrationType>(type), - address, value).raw; + address, value, nanoseconds).raw; } /// Used to output a message on a debug hardware unit - does nothing on a retail unit @@ -220,6 +223,8 @@ static Result GetResourceLimitCurrentValues(s64* values, Handle resource_limit, /// Creates a new thread static Result CreateThread(u32 priority, u32 entry_point, u32 arg, u32 stack_top, u32 processor_id) { + using Kernel::Thread; + std::string name; if (Symbols::HasSymbol(entry_point)) { TSymbol symbol = Symbols::GetSymbol(entry_point); @@ -228,41 +233,53 @@ static Result CreateThread(u32 priority, u32 entry_point, u32 arg, u32 stack_top name = Common::StringFromFormat("unknown-%08x", entry_point); } - Handle thread = Kernel::CreateThread(name.c_str(), entry_point, priority, arg, processor_id, - stack_top); + ResultVal<SharedPtr<Thread>> thread_res = Kernel::Thread::Create( + name, entry_point, priority, arg, processor_id, stack_top, Kernel::DEFAULT_STACK_SIZE); + if (thread_res.Failed()) + return thread_res.Code().raw; + SharedPtr<Thread> thread = std::move(*thread_res); - Core::g_app_core->SetReg(1, thread); + // TODO(yuriks): Create new handle instead of using built-in + Core::g_app_core->SetReg(1, thread->GetHandle()); LOG_TRACE(Kernel_SVC, "called entrypoint=0x%08X (%s), arg=0x%08X, stacktop=0x%08X, " "threadpriority=0x%08X, processorid=0x%08X : created handle=0x%08X", entry_point, - name.c_str(), arg, stack_top, priority, processor_id, thread); + name.c_str(), arg, stack_top, priority, processor_id, thread->GetHandle()); + + if (THREADPROCESSORID_1 == processor_id) { + LOG_WARNING(Kernel_SVC, + "thread designated for system CPU core (UNIMPLEMENTED) will be run with app core scheduling"); + } return 0; } /// Called when a thread exits -static u32 ExitThread() { - Handle thread = Kernel::GetCurrentThreadHandle(); - - LOG_TRACE(Kernel_SVC, "called, pc=0x%08X", Core::g_app_core->GetPC()); // PC = 0x0010545C +static void ExitThread() { + LOG_TRACE(Kernel_SVC, "called, pc=0x%08X", Core::g_app_core->GetPC()); - Kernel::StopThread(thread, __func__); + Kernel::GetCurrentThread()->Stop(__func__); HLE::Reschedule(__func__); - return 0; } /// Gets the priority for the specified thread static Result GetThreadPriority(s32* priority, Handle handle) { - ResultVal<u32> priority_result = Kernel::GetThreadPriority(handle); - if (priority_result.Succeeded()) { - *priority = *priority_result; - } - return priority_result.Code().raw; + const SharedPtr<Kernel::Thread> thread = Kernel::g_handle_table.Get<Kernel::Thread>(handle); + if (thread == nullptr) + return InvalidHandle(ErrorModule::Kernel).raw; + + *priority = thread->GetPriority(); + return RESULT_SUCCESS.raw; } /// Sets the priority for the specified thread static Result SetThreadPriority(Handle handle, s32 priority) { - return Kernel::SetThreadPriority(handle, priority).raw; + SharedPtr<Kernel::Thread> thread = Kernel::g_handle_table.Get<Kernel::Thread>(handle); + if (thread == nullptr) + return InvalidHandle(ErrorModule::Kernel).raw; + + thread->SetPriority(priority); + return RESULT_SUCCESS.raw; } /// Create a mutex @@ -283,8 +300,13 @@ static Result ReleaseMutex(Handle handle) { /// Get the ID for the specified thread. static Result GetThreadId(u32* thread_id, Handle handle) { LOG_TRACE(Kernel_SVC, "called thread=0x%08X", handle); - ResultCode result = Kernel::GetThreadId(thread_id, handle); - return result.raw; + + const SharedPtr<Kernel::Thread> thread = Kernel::g_handle_table.Get<Kernel::Thread>(handle); + if (thread == nullptr) + return InvalidHandle(ErrorModule::Kernel).raw; + + *thread_id = thread->GetThreadId(); + return RESULT_SUCCESS.raw; } /// Creates a semaphore @@ -338,12 +360,42 @@ static Result ClearEvent(Handle evt) { return Kernel::ClearEvent(evt).raw; } +/// Creates a timer +static Result CreateTimer(Handle* handle, u32 reset_type) { + ResultCode res = Kernel::CreateTimer(handle, static_cast<ResetType>(reset_type)); + LOG_TRACE(Kernel_SVC, "called reset_type=0x%08X : created handle=0x%08X", + reset_type, *handle); + return res.raw; +} + +/// Clears a timer +static Result ClearTimer(Handle handle) { + LOG_TRACE(Kernel_SVC, "called timer=0x%08X", handle); + return Kernel::ClearTimer(handle).raw; +} + +/// Starts a timer +static Result SetTimer(Handle handle, s64 initial, s64 interval) { + LOG_TRACE(Kernel_SVC, "called timer=0x%08X", handle); + return Kernel::SetTimer(handle, initial, interval).raw; +} + +/// Cancels a timer +static Result CancelTimer(Handle handle) { + LOG_TRACE(Kernel_SVC, "called timer=0x%08X", handle); + return Kernel::CancelTimer(handle).raw; +} + /// Sleep the current thread static void SleepThread(s64 nanoseconds) { LOG_TRACE(Kernel_SVC, "called nanoseconds=%lld", nanoseconds); // Sleep current thread and check for next thread to schedule Kernel::WaitCurrentThread(WAITTYPE_SLEEP); + + // Create an event to wake the thread up after the specified nanosecond delay has passed + Kernel::WakeThreadAfterDelay(Kernel::GetCurrentThread(), nanoseconds); + HLE::Reschedule(__func__); } @@ -374,7 +426,7 @@ const HLE::FunctionDef SVC_Table[] = { {0x06, nullptr, "GetProcessIdealProcessor"}, {0x07, nullptr, "SetProcessIdealProcessor"}, {0x08, HLE::Wrap<CreateThread>, "CreateThread"}, - {0x09, HLE::Wrap<ExitThread>, "ExitThread"}, + {0x09, ExitThread, "ExitThread"}, {0x0A, HLE::Wrap<SleepThread>, "SleepThread"}, {0x0B, HLE::Wrap<GetThreadPriority>, "GetThreadPriority"}, {0x0C, HLE::Wrap<SetThreadPriority>, "SetThreadPriority"}, @@ -391,10 +443,10 @@ const HLE::FunctionDef SVC_Table[] = { {0x17, HLE::Wrap<CreateEvent>, "CreateEvent"}, {0x18, HLE::Wrap<SignalEvent>, "SignalEvent"}, {0x19, HLE::Wrap<ClearEvent>, "ClearEvent"}, - {0x1A, nullptr, "CreateTimer"}, - {0x1B, nullptr, "SetTimer"}, - {0x1C, nullptr, "CancelTimer"}, - {0x1D, nullptr, "ClearTimer"}, + {0x1A, HLE::Wrap<CreateTimer>, "CreateTimer"}, + {0x1B, HLE::Wrap<SetTimer>, "SetTimer"}, + {0x1C, HLE::Wrap<CancelTimer>, "CancelTimer"}, + {0x1D, HLE::Wrap<ClearTimer>, "ClearTimer"}, {0x1E, HLE::Wrap<CreateMemoryBlock>, "CreateMemoryBlock"}, {0x1F, HLE::Wrap<MapMemoryBlock>, "MapMemoryBlock"}, {0x20, nullptr, "UnmapMemoryBlock"}, diff --git a/src/core/hle/svc.h b/src/core/hle/svc.h index ad780818e..5d020a5ba 100644 --- a/src/core/hle/svc.h +++ b/src/core/hle/svc.h @@ -20,21 +20,6 @@ struct PageInfo { u32 flags; }; -struct ThreadContext { - u32 cpu_registers[13]; - u32 sp; - u32 lr; - u32 pc; - u32 cpsr; - u32 fpu_registers[32]; - u32 fpscr; - u32 fpexc; - - // These are not part of native ThreadContext, but needed by emu - u32 reg_15; - u32 mode; -}; - enum ResetType { RESETTYPE_ONESHOT, RESETTYPE_STICKY, diff --git a/src/core/hw/gpu.cpp b/src/core/hw/gpu.cpp index e346e0ad6..58eec3005 100644 --- a/src/core/hw/gpu.cpp +++ b/src/core/hw/gpu.cpp @@ -4,9 +4,12 @@ #include "common/common_types.h" +#include "core/arm/arm_interface.h" + #include "core/settings.h" #include "core/core.h" #include "core/mem_map.h" +#include "core/core_timing.h" #include "core/hle/hle.h" #include "core/hle/service/gsp_gpu.h" @@ -22,14 +25,17 @@ namespace GPU { Regs g_regs; -bool g_skip_frame = false; ///< True if the current frame was skipped +/// True if the current frame was skipped +bool g_skip_frame = false; -static u64 frame_ticks = 0; ///< 268MHz / gpu_refresh_rate frames per second -static u64 line_ticks = 0; ///< Number of ticks for a screen line -static u32 cur_line = 0; ///< Current screen line -static u64 last_update_tick = 0; ///< CPU ticl count from last GPU update -static u64 frame_count = 0; ///< Number of frames drawn -static bool last_skip_frame = false; ///< True if the last frame was skipped +/// 268MHz / gpu_refresh_rate frames per second +static u64 frame_ticks; +/// Event id for CoreTiming +static int vblank_event; +/// Total number of frames drawn +static u64 frame_count; +/// True if the last frame was skipped +static bool last_skip_frame = false; template <typename T> inline void Read(T &var, const u32 raw_addr) { @@ -77,6 +83,12 @@ inline void Write(u32 addr, const T data) { *ptr = bswap32(config.value); // TODO: This is just a workaround to missing framebuffer format emulation LOG_TRACE(HW_GPU, "MemoryFill from 0x%08x to 0x%08x", config.GetStartAddress(), config.GetEndAddress()); + + if (!is_second_filler) { + GSP_GPU::SignalInterrupt(GSP_GPU::InterruptId::PSC0); + } else { + GSP_GPU::SignalInterrupt(GSP_GPU::InterruptId::PSC1); + } } break; } @@ -88,22 +100,25 @@ inline void Write(u32 addr, const T data) { u8* source_pointer = Memory::GetPointer(Memory::PhysicalToVirtualAddress(config.GetPhysicalInputAddress())); u8* dest_pointer = Memory::GetPointer(Memory::PhysicalToVirtualAddress(config.GetPhysicalOutputAddress())); + // Cheap emulation of horizontal scaling: Just skip each second pixel of the + // input framebuffer. We keep track of this in the pixel_skip variable. + unsigned pixel_skip = (config.scale_horizontally != 0) ? 2 : 1; + + u32 output_width = config.output_width / pixel_skip; + for (u32 y = 0; y < config.output_height; ++y) { // TODO: Why does the register seem to hold twice the framebuffer width? - for (u32 x = 0; x < config.output_width; ++x) { + + for (u32 x = 0; x < output_width; ++x) { struct { int r, g, b, a; } source_color = { 0, 0, 0, 0 }; - // Cheap emulation of horizontal scaling: Just skip each second pixel of the - // input framebuffer. We keep track of this in the pixel_skip variable. - unsigned pixel_skip = (config.scale_horizontally != 0) ? 2 : 1; - switch (config.input_format) { case Regs::PixelFormat::RGBA8: { // TODO: Most likely got the component order messed up. - u8* srcptr = source_pointer + x * 4 * pixel_skip + y * config.input_width * 4 * pixel_skip; + u8* srcptr = source_pointer + (x * pixel_skip + y * config.input_width) * 4; source_color.r = srcptr[0]; // blue source_color.g = srcptr[1]; // green source_color.b = srcptr[2]; // red @@ -131,7 +146,7 @@ inline void Write(u32 addr, const T data) { case Regs::PixelFormat::RGB8: { // TODO: Most likely got the component order messed up. - u8* dstptr = dest_pointer + x * 3 + y * config.output_width * 3; + u8* dstptr = dest_pointer + (x + y * output_width) * 3; dstptr[0] = source_color.r; // blue dstptr[1] = source_color.g; // green dstptr[2] = source_color.b; // red @@ -146,10 +161,12 @@ inline void Write(u32 addr, const T data) { } LOG_TRACE(HW_GPU, "DisplayTriggerTransfer: 0x%08x bytes from 0x%08x(%ux%u)-> 0x%08x(%ux%u), dst format %x", - config.output_height * config.output_width * 4, + config.output_height * output_width * 4, config.GetPhysicalInputAddress(), (u32)config.input_width, (u32)config.input_height, - config.GetPhysicalOutputAddress(), (u32)config.output_width, (u32)config.output_height, + config.GetPhysicalOutputAddress(), (u32)output_width, (u32)config.output_height, config.output_format.Value()); + + GSP_GPU::SignalInterrupt(GSP_GPU::InterruptId::PPF); } break; } @@ -184,51 +201,39 @@ template void Write<u16>(u32 addr, const u16 data); template void Write<u8>(u32 addr, const u8 data); /// Update hardware -void Update() { +static void VBlankCallback(u64 userdata, int cycles_late) { auto& framebuffer_top = g_regs.framebuffer_config[0]; - // Synchronize GPU on a thread reschedule: Because we cannot accurately predict a vertical - // blank, we need to simulate it. Based on testing, it seems that retail applications work more - // accurately when this is signalled between thread switches. - - if (HLE::g_reschedule) { - u64 current_ticks = Core::g_app_core->GetTicks(); - u32 num_lines = static_cast<u32>((current_ticks - last_update_tick) / line_ticks); - - // Synchronize line... - if (num_lines > 0) { - GSP_GPU::SignalInterrupt(GSP_GPU::InterruptId::PDC0); - cur_line += num_lines; - last_update_tick += (num_lines * line_ticks); - } - - // Synchronize frame... - if (cur_line >= framebuffer_top.height) { - cur_line = 0; - frame_count++; - last_skip_frame = g_skip_frame; - g_skip_frame = (frame_count & Settings::values.frame_skip) != 0; - - // Swap buffers based on the frameskip mode, which is a little bit tricky. When - // a frame is being skipped, nothing is being rendered to the internal framebuffer(s). - // So, we should only swap frames if the last frame was rendered. The rules are: - // - If frameskip == 0 (disabled), always swap buffers - // - If frameskip == 1, swap buffers every other frame (starting from the first frame) - // - If frameskip > 1, swap buffers every frameskip^n frames (starting from the second frame) - if ((((Settings::values.frame_skip != 1) ^ last_skip_frame) && last_skip_frame != g_skip_frame) || - Settings::values.frame_skip == 0) { - VideoCore::g_renderer->SwapBuffers(); - } - - // Signal to GSP that GPU interrupt has occurred - GSP_GPU::SignalInterrupt(GSP_GPU::InterruptId::PDC1); - - // TODO(bunnei): Fake a DSP interrupt on each frame. This does not belong here, but - // until we can emulate DSP interrupts, this is probably the only reasonable place to do - // this. Certain games expect this to be periodically signaled. - DSP_DSP::SignalInterrupt(); - } + frame_count++; + last_skip_frame = g_skip_frame; + g_skip_frame = (frame_count & Settings::values.frame_skip) != 0; + + // Swap buffers based on the frameskip mode, which is a little bit tricky. When + // a frame is being skipped, nothing is being rendered to the internal framebuffer(s). + // So, we should only swap frames if the last frame was rendered. The rules are: + // - If frameskip == 0 (disabled), always swap buffers + // - If frameskip == 1, swap buffers every other frame (starting from the first frame) + // - If frameskip > 1, swap buffers every frameskip^n frames (starting from the second frame) + if ((((Settings::values.frame_skip != 1) ^ last_skip_frame) && last_skip_frame != g_skip_frame) || + Settings::values.frame_skip == 0) { + VideoCore::g_renderer->SwapBuffers(); } + + // Signal to GSP that GPU interrupt has occurred + // TODO(yuriks): hwtest to determine if PDC0 is for the Top screen and PDC1 for the Sub + // screen, or if both use the same interrupts and these two instead determine the + // beginning and end of the VBlank period. If needed, split the interrupt firing into + // two different intervals. + GSP_GPU::SignalInterrupt(GSP_GPU::InterruptId::PDC0); + GSP_GPU::SignalInterrupt(GSP_GPU::InterruptId::PDC1); + + // TODO(bunnei): Fake a DSP interrupt on each frame. This does not belong here, but + // until we can emulate DSP interrupts, this is probably the only reasonable place to do + // this. Certain games expect this to be periodically signaled. + DSP_DSP::SignalInterrupt(); + + // Reschedule recurrent event + CoreTiming::ScheduleEvent(frame_ticks - cycles_late, vblank_event); } /// Initialize hardware @@ -245,8 +250,8 @@ void Init() { framebuffer_top.address_right1 = 0x18273000; framebuffer_top.address_right2 = 0x182B9800; framebuffer_sub.address_left1 = 0x1848F000; - //framebuffer_sub.address_left2 = unknown; - framebuffer_sub.address_right1 = 0x184C7800; + framebuffer_sub.address_left2 = 0x184C7800; + //framebuffer_sub.address_right1 = unknown; //framebuffer_sub.address_right2 = unknown; framebuffer_top.width = 240; @@ -262,12 +267,12 @@ void Init() { framebuffer_sub.active_fb = 0; frame_ticks = 268123480 / Settings::values.gpu_refresh_rate; - line_ticks = (GPU::frame_ticks / framebuffer_top.height); - cur_line = 0; - last_update_tick = Core::g_app_core->GetTicks(); last_skip_frame = false; g_skip_frame = false; + vblank_event = CoreTiming::RegisterEvent("GPU::VBlankCallback", VBlankCallback); + CoreTiming::ScheduleEvent(frame_ticks, vblank_event); + LOG_DEBUG(HW_GPU, "initialized OK"); } diff --git a/src/core/hw/gpu.h b/src/core/hw/gpu.h index 7de055232..7c3a17ee5 100644 --- a/src/core/hw/gpu.h +++ b/src/core/hw/gpu.h @@ -252,9 +252,6 @@ void Read(T &var, const u32 addr); template <typename T> void Write(u32 addr, const T data); -/// Update hardware -void Update(); - /// Initialize hardware void Init(); diff --git a/src/core/hw/hw.cpp b/src/core/hw/hw.cpp index 848ab5348..503200629 100644 --- a/src/core/hw/hw.cpp +++ b/src/core/hw/hw.cpp @@ -75,7 +75,6 @@ template void Write<u8>(u32 addr, const u8 data); /// Update hardware void Update() { - GPU::Update(); } /// Initialize hardware diff --git a/src/core/system.cpp b/src/core/system.cpp index d6188f055..f4c2df1cd 100644 --- a/src/core/system.cpp +++ b/src/core/system.cpp @@ -21,11 +21,11 @@ void UpdateState(State state) { void Init(EmuWindow* emu_window) { Core::Init(); + CoreTiming::Init(); Memory::Init(); HW::Init(); Kernel::Init(); HLE::Init(); - CoreTiming::Init(); VideoCore::Init(emu_window); } @@ -38,11 +38,11 @@ void RunLoopUntil(u64 global_cycles) { void Shutdown() { VideoCore::Shutdown(); - CoreTiming::Shutdown(); HLE::Shutdown(); Kernel::Shutdown(); HW::Shutdown(); Memory::Shutdown(); + CoreTiming::Shutdown(); Core::Shutdown(); } diff --git a/src/video_core/debug_utils/debug_utils.cpp b/src/video_core/debug_utils/debug_utils.cpp index a494465b9..12f0009bd 100644 --- a/src/video_core/debug_utils/debug_utils.cpp +++ b/src/video_core/debug_utils/debug_utils.cpp @@ -18,6 +18,7 @@ #include "common/log.h" #include "common/file_util.h" +#include "common/math_util.h" #include "video_core/color.h" #include "video_core/math.h" @@ -337,25 +338,31 @@ const Math::Vec4<u8> LookupTexture(const u8* source, int x, int y, const Texture i = (i ^ (i << 1)) & 0x1515; // ---2 -1-0 i = (i | (i >> 7)) & 0x3F; - source += coarse_y * info.stride; - const unsigned int offset = coarse_x * block_height + i; + if (info.format != Regs::TextureFormat::ETC1 && + info.format != Regs::TextureFormat::ETC1A4) { + // TODO(neobrain): Fix code design to unify vertical block offsets! + source += coarse_y * info.stride; + } + const unsigned int offset = coarse_x * block_height; + + // TODO: Assert that width/height are multiples of block dimensions switch (info.format) { case Regs::TextureFormat::RGBA8: { - const u8* source_ptr = source + offset * 4; + const u8* source_ptr = source + offset * 4 + i * 4; return { source_ptr[3], source_ptr[2], source_ptr[1], disable_alpha ? (u8)255 : source_ptr[0] }; } case Regs::TextureFormat::RGB8: { - const u8* source_ptr = source + offset * 3; + const u8* source_ptr = source + offset * 3 + i * 3; return { source_ptr[2], source_ptr[1], source_ptr[0], 255 }; } case Regs::TextureFormat::RGBA5551: { - const u16 source_ptr = *(const u16*)(source + offset * 2); + const u16 source_ptr = *(const u16*)(source + offset * 2 + i * 2); u8 r = (source_ptr >> 11) & 0x1F; u8 g = ((source_ptr) >> 6) & 0x1F; u8 b = (source_ptr >> 1) & 0x1F; @@ -366,7 +373,7 @@ const Math::Vec4<u8> LookupTexture(const u8* source, int x, int y, const Texture case Regs::TextureFormat::RGB565: { - const u16 source_ptr = *(const u16*)(source + offset * 2); + const u16 source_ptr = *(const u16*)(source + offset * 2 + i * 2); u8 r = Color::Convert5To8((source_ptr >> 11) & 0x1F); u8 g = Color::Convert6To8(((source_ptr) >> 5) & 0x3F); u8 b = Color::Convert5To8((source_ptr) & 0x1F); @@ -375,7 +382,7 @@ const Math::Vec4<u8> LookupTexture(const u8* source, int x, int y, const Texture case Regs::TextureFormat::RGBA4: { - const u8* source_ptr = source + offset * 2; + const u8* source_ptr = source + offset * 2 + i * 2; u8 r = Color::Convert4To8(source_ptr[1] >> 4); u8 g = Color::Convert4To8(source_ptr[1] & 0xF); u8 b = Color::Convert4To8(source_ptr[0] >> 4); @@ -385,7 +392,7 @@ const Math::Vec4<u8> LookupTexture(const u8* source, int x, int y, const Texture case Regs::TextureFormat::IA8: { - const u8* source_ptr = source + offset * 2; + const u8* source_ptr = source + offset * 2 + i * 2; if (disable_alpha) { // Show intensity as red, alpha as green @@ -397,13 +404,13 @@ const Math::Vec4<u8> LookupTexture(const u8* source, int x, int y, const Texture case Regs::TextureFormat::I8: { - const u8* source_ptr = source + offset; + const u8* source_ptr = source + offset + i; return { *source_ptr, *source_ptr, *source_ptr, 255 }; } case Regs::TextureFormat::A8: { - const u8* source_ptr = source + offset; + const u8* source_ptr = source + offset + i; if (disable_alpha) { return { *source_ptr, *source_ptr, *source_ptr, 255 }; @@ -414,7 +421,7 @@ const Math::Vec4<u8> LookupTexture(const u8* source, int x, int y, const Texture case Regs::TextureFormat::IA4: { - const u8* source_ptr = source + offset; + const u8* source_ptr = source + offset + i; u8 i = Color::Convert4To8(((*source_ptr) & 0xF0) >> 4); u8 a = Color::Convert4To8((*source_ptr) & 0xF); @@ -429,7 +436,7 @@ const Math::Vec4<u8> LookupTexture(const u8* source, int x, int y, const Texture case Regs::TextureFormat::A4: { - const u8* source_ptr = source + offset / 2; + const u8* source_ptr = source + offset / 2 + i / 2; u8 a = (coarse_x % 2) ? ((*source_ptr)&0xF) : (((*source_ptr) & 0xF0) >> 4); a = Color::Convert4To8(a); @@ -441,6 +448,127 @@ const Math::Vec4<u8> LookupTexture(const u8* source, int x, int y, const Texture } } + case Regs::TextureFormat::ETC1: + case Regs::TextureFormat::ETC1A4: + { + bool has_alpha = (info.format == Regs::TextureFormat::ETC1A4); + + // ETC1 further subdivides each 8x8 tile into four 4x4 subtiles + const int subtile_width = 4; + const int subtile_height = 4; + + int subtile_index = ((x / subtile_width) & 1) + 2 * ((y / subtile_height) & 1); + unsigned subtile_bytes = has_alpha ? 2 : 1; // TODO: Name... + + const u64* source_ptr = (const u64*)(source + + coarse_x * subtile_bytes * 4 + + coarse_y * subtile_bytes * 4 * (info.width / 8) + + subtile_index * subtile_bytes * 8); + u64 alpha = 0xFFFFFFFFFFFFFFFF; + if (has_alpha) { + alpha = *source_ptr; + source_ptr++; + } + + union ETC1Tile { + // Each of these two is a collection of 16 bits (one per lookup value) + BitField< 0, 16, u64> table_subindexes; + BitField<16, 16, u64> negation_flags; + + unsigned GetTableSubIndex(unsigned index) const { + return (table_subindexes >> index) & 1; + } + + bool GetNegationFlag(unsigned index) const { + return ((negation_flags >> index) & 1) == 1; + } + + BitField<32, 1, u64> flip; + BitField<33, 1, u64> differential_mode; + + BitField<34, 3, u64> table_index_2; + BitField<37, 3, u64> table_index_1; + + union { + // delta value + base value + BitField<40, 3, s64> db; + BitField<43, 5, u64> b; + + BitField<48, 3, s64> dg; + BitField<51, 5, u64> g; + + BitField<56, 3, s64> dr; + BitField<59, 5, u64> r; + } differential; + + union { + BitField<40, 4, u64> b2; + BitField<44, 4, u64> b1; + + BitField<48, 4, u64> g2; + BitField<52, 4, u64> g1; + + BitField<56, 4, u64> r2; + BitField<60, 4, u64> r1; + } separate; + + const Math::Vec3<u8> GetRGB(int x, int y) const { + int texel = 4 * x + y; + + if (flip) + std::swap(x, y); + + // Lookup base value + Math::Vec3<int> ret; + if (differential_mode) { + ret.r() = differential.r; + ret.g() = differential.g; + ret.b() = differential.b; + if (x >= 2) { + ret.r() += differential.dr; + ret.g() += differential.dg; + ret.b() += differential.db; + } + ret.r() = Color::Convert5To8(ret.r()); + ret.g() = Color::Convert5To8(ret.g()); + ret.b() = Color::Convert5To8(ret.b()); + } else { + if (x < 2) { + ret.r() = Color::Convert4To8(separate.r1); + ret.g() = Color::Convert4To8(separate.g1); + ret.b() = Color::Convert4To8(separate.b1); + } else { + ret.r() = Color::Convert4To8(separate.r2); + ret.g() = Color::Convert4To8(separate.g2); + ret.b() = Color::Convert4To8(separate.b2); + } + } + + // Add modifier + unsigned table_index = (x < 2) ? table_index_2.Value() : table_index_1.Value(); + + static const auto etc1_modifier_table = std::array<std::array<u8, 2>, 8>{{ + { 2, 8 }, { 5, 17 }, { 9, 29 }, { 13, 42 }, + { 18, 60 }, { 24, 80 }, { 33, 106 }, { 47, 183 } + }}; + + int modifier = etc1_modifier_table.at(table_index).at(GetTableSubIndex(texel)); + if (GetNegationFlag(texel)) + modifier *= -1; + + ret.r() = MathUtil::Clamp(ret.r() + modifier, 0, 255); + ret.g() = MathUtil::Clamp(ret.g() + modifier, 0, 255); + ret.b() = MathUtil::Clamp(ret.b() + modifier, 0, 255); + + return ret.Cast<u8>(); + } + } const *etc1_tile = reinterpret_cast<const ETC1Tile*>(source_ptr); + + alpha >>= 4 * ((x & 3) * 4 + (y & 3)); + return Math::MakeVec(etc1_tile->GetRGB(x & 3, y & 3), + disable_alpha ? (u8)255 : Color::Convert4To8(alpha & 0xF)); + } + default: LOG_ERROR(HW_GPU, "Unknown texture format: %x", (u32)info.format); _dbg_assert_(HW_GPU, 0); diff --git a/src/video_core/pica.h b/src/video_core/pica.h index f5771ed84..de1ce05b6 100644 --- a/src/video_core/pica.h +++ b/src/video_core/pica.h @@ -161,8 +161,8 @@ struct Regs { IA4 = 9, A4 = 11, - // TODO: Support for the other formats is not implemented, yet. - // Seems like they are luminance formats and compressed textures. + ETC1 = 12, // compressed + ETC1A4 = 13, // compressed }; static unsigned NibblesPerPixel(TextureFormat format) { diff --git a/src/video_core/renderer_opengl/renderer_opengl.cpp b/src/video_core/renderer_opengl/renderer_opengl.cpp index 29d220e8d..aa47bd616 100644 --- a/src/video_core/renderer_opengl/renderer_opengl.cpp +++ b/src/video_core/renderer_opengl/renderer_opengl.cpp @@ -88,10 +88,8 @@ void RendererOpenGL::SwapBuffers() { void RendererOpenGL::LoadFBToActiveGLTexture(const GPU::Regs::FramebufferConfig& framebuffer, const TextureInfo& texture) { - // TODO: Why are active_fb and the valid framebuffer flipped compared to 3dbrew documentation - // and GSP definitions? const VAddr framebuffer_vaddr = Memory::PhysicalToVirtualAddress( - framebuffer.active_fb == 0 ? framebuffer.address_left2 : framebuffer.address_left1); + framebuffer.active_fb == 0 ? framebuffer.address_left1 : framebuffer.address_left2); LOG_TRACE(Render_OpenGL, "0x%08x bytes from 0x%08x(%dx%d), fmt %x", framebuffer.stride * framebuffer.height, diff --git a/src/video_core/vertex_shader.cpp b/src/video_core/vertex_shader.cpp index ff825e2e1..48977380e 100644 --- a/src/video_core/vertex_shader.cpp +++ b/src/video_core/vertex_shader.cpp @@ -348,13 +348,114 @@ static void ProcessShaderCode(VertexShaderState& state) { break; } + + case Instruction::OpCodeType::MultiplyAdd: + { + if (instr.opcode.EffectiveOpCode() == Instruction::OpCode::MAD) { + const SwizzlePattern& swizzle = *(SwizzlePattern*)&swizzle_data[instr.mad.operand_desc_id]; + + const float24* src1_ = LookupSourceRegister(instr.mad.src1); + const float24* src2_ = LookupSourceRegister(instr.mad.src2); + const float24* src3_ = LookupSourceRegister(instr.mad.src3); + + const bool negate_src1 = ((bool)swizzle.negate_src1 != false); + const bool negate_src2 = ((bool)swizzle.negate_src2 != false); + const bool negate_src3 = ((bool)swizzle.negate_src3 != false); + + float24 src1[4] = { + src1_[(int)swizzle.GetSelectorSrc1(0)], + src1_[(int)swizzle.GetSelectorSrc1(1)], + src1_[(int)swizzle.GetSelectorSrc1(2)], + src1_[(int)swizzle.GetSelectorSrc1(3)], + }; + if (negate_src1) { + src1[0] = src1[0] * float24::FromFloat32(-1); + src1[1] = src1[1] * float24::FromFloat32(-1); + src1[2] = src1[2] * float24::FromFloat32(-1); + src1[3] = src1[3] * float24::FromFloat32(-1); + } + float24 src2[4] = { + src2_[(int)swizzle.GetSelectorSrc2(0)], + src2_[(int)swizzle.GetSelectorSrc2(1)], + src2_[(int)swizzle.GetSelectorSrc2(2)], + src2_[(int)swizzle.GetSelectorSrc2(3)], + }; + if (negate_src2) { + src2[0] = src2[0] * float24::FromFloat32(-1); + src2[1] = src2[1] * float24::FromFloat32(-1); + src2[2] = src2[2] * float24::FromFloat32(-1); + src2[3] = src2[3] * float24::FromFloat32(-1); + } + float24 src3[4] = { + src3_[(int)swizzle.GetSelectorSrc3(0)], + src3_[(int)swizzle.GetSelectorSrc3(1)], + src3_[(int)swizzle.GetSelectorSrc3(2)], + src3_[(int)swizzle.GetSelectorSrc3(3)], + }; + if (negate_src3) { + src3[0] = src3[0] * float24::FromFloat32(-1); + src3[1] = src3[1] * float24::FromFloat32(-1); + src3[2] = src3[2] * float24::FromFloat32(-1); + src3[3] = src3[3] * float24::FromFloat32(-1); + } + + float24* dest = (instr.mad.dest < 0x08) ? state.output_register_table[4*instr.mad.dest.GetIndex()] + : (instr.mad.dest < 0x10) ? dummy_vec4_float24 + : (instr.mad.dest < 0x20) ? &state.temporary_registers[instr.mad.dest.GetIndex()][0] + : dummy_vec4_float24; + + for (int i = 0; i < 4; ++i) { + if (!swizzle.DestComponentEnabled(i)) + continue; + + dest[i] = src1[i] * src2[i] + src3[i]; + } + } else { + LOG_ERROR(HW_GPU, "Unhandled multiply-add instruction: 0x%02x (%s): 0x%08x", + (int)instr.opcode.Value(), instr.opcode.GetInfo().name, instr.hex); + } + break; + } + default: + { + static auto evaluate_condition = [](const VertexShaderState& state, bool refx, bool refy, Instruction::FlowControlType flow_control) { + bool results[2] = { refx == state.conditional_code[0], + refy == state.conditional_code[1] }; + + switch (flow_control.op) { + case flow_control.Or: + return results[0] || results[1]; + + case flow_control.And: + return results[0] && results[1]; + + case flow_control.JustX: + return results[0]; + + case flow_control.JustY: + return results[1]; + } + }; + // Handle each instruction on its own switch (instr.opcode) { case Instruction::OpCode::END: exit_loop = true; break; + case Instruction::OpCode::JMPC: + if (evaluate_condition(state, instr.flow_control.refx, instr.flow_control.refy, instr.flow_control)) { + state.program_counter = &shader_memory[instr.flow_control.dest_offset] - 1; + } + break; + + case Instruction::OpCode::JMPU: + if (shader_uniforms.b[instr.flow_control.bool_uniform_id]) { + state.program_counter = &shader_memory[instr.flow_control.dest_offset] - 1; + } + break; + case Instruction::OpCode::CALL: call(state, instr.flow_control.dest_offset, @@ -362,6 +463,24 @@ static void ProcessShaderCode(VertexShaderState& state) { binary_offset + 1); break; + case Instruction::OpCode::CALLU: + if (shader_uniforms.b[instr.flow_control.bool_uniform_id]) { + call(state, + instr.flow_control.dest_offset, + instr.flow_control.num_instructions, + binary_offset + 1); + } + break; + + case Instruction::OpCode::CALLC: + if (evaluate_condition(state, instr.flow_control.refx, instr.flow_control.refy, instr.flow_control)) { + call(state, + instr.flow_control.dest_offset, + instr.flow_control.num_instructions, + binary_offset + 1); + } + break; + case Instruction::OpCode::NOP: break; @@ -384,29 +503,7 @@ static void ProcessShaderCode(VertexShaderState& state) { { // TODO: Do we need to consider swizzlers here? - auto flow_control = instr.flow_control; - bool results[3] = { (bool)flow_control.refx == state.conditional_code[0], - (bool)flow_control.refy == state.conditional_code[1] }; - - switch (flow_control.op) { - case flow_control.Or: - results[2] = results[0] || results[1]; - break; - - case flow_control.And: - results[2] = results[0] && results[1]; - break; - - case flow_control.JustX: - results[2] = results[0]; - break; - - case flow_control.JustY: - results[2] = results[1]; - break; - } - - if (results[2]) { + if (evaluate_condition(state, instr.flow_control.refx, instr.flow_control.refy, instr.flow_control)) { call(state, binary_offset + 1, instr.flow_control.dest_offset - binary_offset - 1, @@ -429,6 +526,7 @@ static void ProcessShaderCode(VertexShaderState& state) { break; } + } ++state.program_counter; |