diff options
105 files changed, 1655 insertions, 730 deletions
diff --git a/.gitmodules b/.gitmodules index f498a60de..dbb1b0dd3 100644 --- a/.gitmodules +++ b/.gitmodules @@ -16,3 +16,6 @@ [submodule "dynarmic"] path = externals/dynarmic url = https://github.com/MerryMage/dynarmic.git +[submodule "xbyak"] + path = externals/xbyak + url = https://github.com/herumi/xbyak.git diff --git a/CMakeLists.txt b/CMakeLists.txt index c7255a998..52a1fd492 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -184,8 +184,10 @@ IF (APPLE) FIND_LIBRARY(COCOA_LIBRARY Cocoa) # Umbrella framework for everything GUI-related set(PLATFORM_LIBRARIES ${COCOA_LIBRARY} ${IOKIT_LIBRARY} ${COREVIDEO_LIBRARY}) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++") - set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -stdlib=libc++") + if (CMAKE_CXX_COMPILER_ID STREQUAL Clang) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -stdlib=libc++") + endif() ELSEIF (WIN32) # WSAPoll and SHGetKnownFolderPath (AppData/Roaming) didn't exist before WinNT 6.x (Vista) add_definitions(-D_WIN32_WINNT=0x0600 -DWINVER=0x0600) @@ -255,6 +257,8 @@ set(INI_PREFIX "${CMAKE_CURRENT_SOURCE_DIR}/externals/inih") include_directories(${INI_PREFIX}) add_subdirectory(${INI_PREFIX}) +add_subdirectory(externals) + option(DYNARMIC_TESTS OFF) add_subdirectory(externals/dynarmic) diff --git a/externals/CMakeLists.txt b/externals/CMakeLists.txt new file mode 100644 index 000000000..7e4b05ffc --- /dev/null +++ b/externals/CMakeLists.txt @@ -0,0 +1,8 @@ +# Xbyak +if (ARCHITECTURE_x86_64) + add_library(xbyak INTERFACE) + target_include_directories(xbyak INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/xbyak/xbyak) + if (NOT MSVC) + target_compile_options(xbyak INTERFACE -fno-operator-names) + endif() +endif() diff --git a/externals/dynarmic b/externals/dynarmic -Subproject 54d051977f7a6af9c7596ba6a4e6eb467bd1e0b +Subproject 34e19f135c0dd2feac4f77660f51aa4ea28a738 diff --git a/externals/xbyak b/externals/xbyak new file mode 160000 +Subproject fe4765d2fed4e990ea5e9661b6bc5fc9bf48ec1 diff --git a/src/audio_core/time_stretch.h b/src/audio_core/time_stretch.h index fa81718ed..e3e4dc353 100644 --- a/src/audio_core/time_stretch.h +++ b/src/audio_core/time_stretch.h @@ -2,6 +2,8 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#pragma once + #include <cstddef> #include <memory> #include <vector> diff --git a/src/citra/citra.cpp b/src/citra/citra.cpp index f9387e61c..3114a71db 100644 --- a/src/citra/citra.cpp +++ b/src/citra/citra.cpp @@ -18,7 +18,7 @@ #endif #ifdef _WIN32 -#include <Windows.h> +#include <windows.h> #endif #include "citra/config.h" diff --git a/src/citra/citra.rc b/src/citra/citra.rc index b0edb2e6b..fea603004 100644 --- a/src/citra/citra.rc +++ b/src/citra/citra.rc @@ -5,5 +5,5 @@ // Icon with lowest ID value placed first to ensure application icon // remains consistent on all systems. -GLFW_ICON ICON "..\\..\\dist\\citra.ico" +CITRA_ICON ICON "../../dist/citra.ico" diff --git a/src/citra_qt/bootmanager.h b/src/citra_qt/bootmanager.h index 67228b94f..af52f369b 100644 --- a/src/citra_qt/bootmanager.h +++ b/src/citra_qt/bootmanager.h @@ -2,6 +2,8 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#pragma once + #include <atomic> #include <condition_variable> #include <mutex> @@ -21,7 +23,7 @@ class EmuThread : public QThread { Q_OBJECT public: - EmuThread(GRenderWindow* render_window); + explicit EmuThread(GRenderWindow* render_window); /** * Start emulation (on new thread) diff --git a/src/citra_qt/citra-qt.rc b/src/citra_qt/citra-qt.rc index 3c7239853..fea603004 100644 --- a/src/citra_qt/citra-qt.rc +++ b/src/citra_qt/citra-qt.rc @@ -5,5 +5,5 @@ // Icon with lowest ID value placed first to ensure application icon // remains consistent on all systems. -IDI_ICON1 ICON "..\\..\\dist\\citra.ico" +CITRA_ICON ICON "../../dist/citra.ico" diff --git a/src/citra_qt/debugger/callstack.h b/src/citra_qt/debugger/callstack.h index 765757986..f04ab9c7e 100644 --- a/src/citra_qt/debugger/callstack.h +++ b/src/citra_qt/debugger/callstack.h @@ -2,6 +2,8 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#pragma once + #include <QDockWidget> #include "ui_callstack.h" @@ -11,7 +13,7 @@ class CallstackWidget : public QDockWidget { Q_OBJECT public: - CallstackWidget(QWidget* parent = nullptr); + explicit CallstackWidget(QWidget* parent = nullptr); public slots: void OnDebugModeEntered(); diff --git a/src/citra_qt/debugger/disassembler.h b/src/citra_qt/debugger/disassembler.h index 2ca6c2bd4..895f6ac89 100644 --- a/src/citra_qt/debugger/disassembler.h +++ b/src/citra_qt/debugger/disassembler.h @@ -17,7 +17,7 @@ class DisassemblerModel : public QAbstractListModel { Q_OBJECT public: - DisassemblerModel(QObject* parent); + explicit DisassemblerModel(QObject* parent); int columnCount(const QModelIndex& parent = QModelIndex()) const override; int rowCount(const QModelIndex& parent = QModelIndex()) const override; diff --git a/src/citra_qt/debugger/graphics.h b/src/citra_qt/debugger/graphics.h index bedf3e596..8837fb792 100644 --- a/src/citra_qt/debugger/graphics.h +++ b/src/citra_qt/debugger/graphics.h @@ -13,7 +13,7 @@ class GPUCommandStreamItemModel : public QAbstractListModel, Q_OBJECT public: - GPUCommandStreamItemModel(QObject* parent); + explicit GPUCommandStreamItemModel(QObject* parent); int rowCount(const QModelIndex& parent = QModelIndex()) const override; QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; diff --git a/src/citra_qt/debugger/graphics_breakpoints.h b/src/citra_qt/debugger/graphics_breakpoints.h index 5fc40c916..bec72a2db 100644 --- a/src/citra_qt/debugger/graphics_breakpoints.h +++ b/src/citra_qt/debugger/graphics_breakpoints.h @@ -20,8 +20,8 @@ class GraphicsBreakPointsWidget : public QDockWidget, Pica::DebugContext::BreakP using Event = Pica::DebugContext::Event; public: - GraphicsBreakPointsWidget(std::shared_ptr<Pica::DebugContext> debug_context, - QWidget* parent = nullptr); + explicit GraphicsBreakPointsWidget(std::shared_ptr<Pica::DebugContext> debug_context, + QWidget* parent = nullptr); void OnPicaBreakPointHit(Pica::DebugContext::Event event, void* data) override; void OnPicaResume() override; diff --git a/src/citra_qt/debugger/graphics_cmdlists.h b/src/citra_qt/debugger/graphics_cmdlists.h index fa2b9122b..8f40b94c5 100644 --- a/src/citra_qt/debugger/graphics_cmdlists.h +++ b/src/citra_qt/debugger/graphics_cmdlists.h @@ -20,7 +20,7 @@ public: CommandIdRole = Qt::UserRole, }; - GPUCommandListModel(QObject* parent); + explicit GPUCommandListModel(QObject* parent); int columnCount(const QModelIndex& parent = QModelIndex()) const override; int rowCount(const QModelIndex& parent = QModelIndex()) const override; @@ -39,7 +39,7 @@ class GPUCommandListWidget : public QDockWidget { Q_OBJECT public: - GPUCommandListWidget(QWidget* parent = nullptr); + explicit GPUCommandListWidget(QWidget* parent = nullptr); public slots: void OnToggleTracing(); diff --git a/src/citra_qt/debugger/graphics_surface.h b/src/citra_qt/debugger/graphics_surface.h index 21e6b5b8b..19ffb55fb 100644 --- a/src/citra_qt/debugger/graphics_surface.h +++ b/src/citra_qt/debugger/graphics_surface.h @@ -18,7 +18,8 @@ class SurfacePicture : public QLabel { Q_OBJECT public: - SurfacePicture(QWidget* parent = 0, GraphicsSurfaceWidget* surface_widget = nullptr); + explicit SurfacePicture(QWidget* parent = nullptr, + GraphicsSurfaceWidget* surface_widget = nullptr); ~SurfacePicture(); protected slots: @@ -71,8 +72,8 @@ class GraphicsSurfaceWidget : public BreakPointObserverDock { static unsigned int NibblesPerPixel(Format format); public: - GraphicsSurfaceWidget(std::shared_ptr<Pica::DebugContext> debug_context, - QWidget* parent = nullptr); + explicit GraphicsSurfaceWidget(std::shared_ptr<Pica::DebugContext> debug_context, + QWidget* parent = nullptr); void Pick(int x, int y); public slots: diff --git a/src/citra_qt/debugger/graphics_tracing.h b/src/citra_qt/debugger/graphics_tracing.h index e04a3dac3..20acc91e7 100644 --- a/src/citra_qt/debugger/graphics_tracing.h +++ b/src/citra_qt/debugger/graphics_tracing.h @@ -12,8 +12,8 @@ class GraphicsTracingWidget : public BreakPointObserverDock { Q_OBJECT public: - GraphicsTracingWidget(std::shared_ptr<Pica::DebugContext> debug_context, - QWidget* parent = nullptr); + explicit GraphicsTracingWidget(std::shared_ptr<Pica::DebugContext> debug_context, + QWidget* parent = nullptr); private slots: void StartRecording(); diff --git a/src/citra_qt/debugger/graphics_vertex_shader.h b/src/citra_qt/debugger/graphics_vertex_shader.h index ec42f24bb..6e62fbb9b 100644 --- a/src/citra_qt/debugger/graphics_vertex_shader.h +++ b/src/citra_qt/debugger/graphics_vertex_shader.h @@ -19,7 +19,7 @@ class GraphicsVertexShaderModel : public QAbstractTableModel { Q_OBJECT public: - GraphicsVertexShaderModel(GraphicsVertexShaderWidget* parent); + explicit GraphicsVertexShaderModel(GraphicsVertexShaderWidget* parent); int columnCount(const QModelIndex& parent = QModelIndex()) const override; int rowCount(const QModelIndex& parent = QModelIndex()) const override; diff --git a/src/citra_qt/debugger/profiler.h b/src/citra_qt/debugger/profiler.h index d8c6487aa..c8912fd5a 100644 --- a/src/citra_qt/debugger/profiler.h +++ b/src/citra_qt/debugger/profiler.h @@ -15,7 +15,7 @@ class ProfilerModel : public QAbstractItemModel { Q_OBJECT public: - ProfilerModel(QObject* parent); + explicit ProfilerModel(QObject* parent); QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; @@ -37,7 +37,7 @@ class ProfilerWidget : public QDockWidget { Q_OBJECT public: - ProfilerWidget(QWidget* parent = nullptr); + explicit ProfilerWidget(QWidget* parent = nullptr); private slots: void setProfilingInfoUpdateEnabled(bool enable); @@ -53,7 +53,7 @@ class MicroProfileDialog : public QWidget { Q_OBJECT public: - MicroProfileDialog(QWidget* parent = nullptr); + explicit MicroProfileDialog(QWidget* parent = nullptr); /// Returns a QAction that can be used to toggle visibility of this dialog. QAction* toggleViewAction(); diff --git a/src/citra_qt/debugger/ramview.h b/src/citra_qt/debugger/ramview.h index 8043c59e8..d01cea93b 100644 --- a/src/citra_qt/debugger/ramview.h +++ b/src/citra_qt/debugger/ramview.h @@ -2,13 +2,15 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#pragma once + #include "qhexedit.h" class GRamView : public QHexEdit { Q_OBJECT public: - GRamView(QWidget* parent = nullptr); + explicit GRamView(QWidget* parent = nullptr); public slots: void OnCPUStepped(); diff --git a/src/citra_qt/debugger/registers.h b/src/citra_qt/debugger/registers.h index 54c9a8155..55bda5b59 100644 --- a/src/citra_qt/debugger/registers.h +++ b/src/citra_qt/debugger/registers.h @@ -2,6 +2,8 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#pragma once + #include <QDockWidget> #include "ui_registers.h" @@ -13,7 +15,7 @@ class RegistersWidget : public QDockWidget { Q_OBJECT public: - RegistersWidget(QWidget* parent = nullptr); + explicit RegistersWidget(QWidget* parent = nullptr); public slots: void OnDebugModeEntered(); diff --git a/src/citra_qt/debugger/wait_tree.cpp b/src/citra_qt/debugger/wait_tree.cpp index be5a51e52..5a308bf7f 100644 --- a/src/citra_qt/debugger/wait_tree.cpp +++ b/src/citra_qt/debugger/wait_tree.cpp @@ -8,7 +8,6 @@ #include "core/hle/kernel/event.h" #include "core/hle/kernel/mutex.h" #include "core/hle/kernel/semaphore.h" -#include "core/hle/kernel/session.h" #include "core/hle/kernel/thread.h" #include "core/hle/kernel/timer.h" @@ -230,7 +229,8 @@ std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeThread::GetChildren() const { list.push_back(std::make_unique<WaitTreeMutexList>(thread.held_mutexes)); } if (thread.status == THREADSTATUS_WAIT_SYNCH) { - list.push_back(std::make_unique<WaitTreeObjectList>(thread.wait_objects, thread.wait_all)); + list.push_back(std::make_unique<WaitTreeObjectList>(thread.wait_objects, + thread.IsSleepingOnWaitAll())); } return list; diff --git a/src/citra_qt/debugger/wait_tree.h b/src/citra_qt/debugger/wait_tree.h index 5d1d964d1..ee9708fc1 100644 --- a/src/citra_qt/debugger/wait_tree.h +++ b/src/citra_qt/debugger/wait_tree.h @@ -2,6 +2,8 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#pragma once + #include <boost/container/flat_set.hpp> #include <QAbstractItemModel> @@ -49,7 +51,7 @@ private: class WaitTreeText : public WaitTreeItem { Q_OBJECT public: - WaitTreeText(const QString& text); + explicit WaitTreeText(const QString& text); QString GetText() const override; private: @@ -65,7 +67,7 @@ public: class WaitTreeWaitObject : public WaitTreeExpandableItem { Q_OBJECT public: - WaitTreeWaitObject(const Kernel::WaitObject& object); + explicit WaitTreeWaitObject(const Kernel::WaitObject& object); static std::unique_ptr<WaitTreeWaitObject> make(const Kernel::WaitObject& object); QString GetText() const override; std::vector<std::unique_ptr<WaitTreeItem>> GetChildren() const override; @@ -92,7 +94,7 @@ private: class WaitTreeThread : public WaitTreeWaitObject { Q_OBJECT public: - WaitTreeThread(const Kernel::Thread& thread); + explicit WaitTreeThread(const Kernel::Thread& thread); QString GetText() const override; QColor GetColor() const override; std::vector<std::unique_ptr<WaitTreeItem>> GetChildren() const override; @@ -101,35 +103,37 @@ public: class WaitTreeEvent : public WaitTreeWaitObject { Q_OBJECT public: - WaitTreeEvent(const Kernel::Event& object); + explicit WaitTreeEvent(const Kernel::Event& object); std::vector<std::unique_ptr<WaitTreeItem>> GetChildren() const override; }; class WaitTreeMutex : public WaitTreeWaitObject { Q_OBJECT public: - WaitTreeMutex(const Kernel::Mutex& object); + explicit WaitTreeMutex(const Kernel::Mutex& object); std::vector<std::unique_ptr<WaitTreeItem>> GetChildren() const override; }; class WaitTreeSemaphore : public WaitTreeWaitObject { Q_OBJECT public: - WaitTreeSemaphore(const Kernel::Semaphore& object); + explicit WaitTreeSemaphore(const Kernel::Semaphore& object); std::vector<std::unique_ptr<WaitTreeItem>> GetChildren() const override; }; class WaitTreeTimer : public WaitTreeWaitObject { Q_OBJECT public: - WaitTreeTimer(const Kernel::Timer& object); + explicit WaitTreeTimer(const Kernel::Timer& object); std::vector<std::unique_ptr<WaitTreeItem>> GetChildren() const override; }; class WaitTreeMutexList : public WaitTreeExpandableItem { Q_OBJECT public: - WaitTreeMutexList(const boost::container::flat_set<Kernel::SharedPtr<Kernel::Mutex>>& list); + explicit WaitTreeMutexList( + const boost::container::flat_set<Kernel::SharedPtr<Kernel::Mutex>>& list); + QString GetText() const override; std::vector<std::unique_ptr<WaitTreeItem>> GetChildren() const override; @@ -140,7 +144,7 @@ private: class WaitTreeThreadList : public WaitTreeExpandableItem { Q_OBJECT public: - WaitTreeThreadList(const std::vector<Kernel::SharedPtr<Kernel::Thread>>& list); + explicit WaitTreeThreadList(const std::vector<Kernel::SharedPtr<Kernel::Thread>>& list); QString GetText() const override; std::vector<std::unique_ptr<WaitTreeItem>> GetChildren() const override; @@ -152,7 +156,7 @@ class WaitTreeModel : public QAbstractItemModel { Q_OBJECT public: - WaitTreeModel(QObject* parent = nullptr); + explicit WaitTreeModel(QObject* parent = nullptr); QVariant data(const QModelIndex& index, int role) const override; QModelIndex index(int row, int column, const QModelIndex& parent) const override; @@ -171,7 +175,7 @@ class WaitTreeWidget : public QDockWidget { Q_OBJECT public: - WaitTreeWidget(QWidget* parent = nullptr); + explicit WaitTreeWidget(QWidget* parent = nullptr); public slots: void OnDebugModeEntered(); diff --git a/src/citra_qt/game_list.cpp b/src/citra_qt/game_list.cpp index 07bc35308..09469f3c5 100644 --- a/src/citra_qt/game_list.cpp +++ b/src/citra_qt/game_list.cpp @@ -3,6 +3,7 @@ // Refer to the license.txt file included. #include <QHeaderView> +#include <QMenu> #include <QThreadPool> #include <QVBoxLayout> #include "common/common_paths.h" @@ -13,7 +14,7 @@ #include "game_list_p.h" #include "ui_settings.h" -GameList::GameList(QWidget* parent) { +GameList::GameList(QWidget* parent) : QWidget{parent} { QVBoxLayout* layout = new QVBoxLayout; tree_view = new QTreeView; @@ -28,18 +29,18 @@ GameList::GameList(QWidget* parent) { tree_view->setSortingEnabled(true); tree_view->setEditTriggers(QHeaderView::NoEditTriggers); tree_view->setUniformRowHeights(true); + tree_view->setContextMenuPolicy(Qt::CustomContextMenu); item_model->insertColumns(0, COLUMN_COUNT); item_model->setHeaderData(COLUMN_NAME, Qt::Horizontal, "Name"); item_model->setHeaderData(COLUMN_FILE_TYPE, Qt::Horizontal, "File type"); item_model->setHeaderData(COLUMN_SIZE, Qt::Horizontal, "Size"); - connect(tree_view, SIGNAL(activated(const QModelIndex&)), this, - SLOT(ValidateEntry(const QModelIndex&))); + connect(tree_view, &QTreeView::activated, this, &GameList::ValidateEntry); + connect(tree_view, &QTreeView::customContextMenuRequested, this, &GameList::PopupContextMenu); // We must register all custom types with the Qt Automoc system so that we are able to use it - // with - // signals/slots. In this case, QList falls under the umbrells of custom types. + // with signals/slots. In this case, QList falls under the umbrells of custom types. qRegisterMetaType<QList<QStandardItem*>>("QList<QStandardItem*>"); layout->addWidget(tree_view); @@ -50,7 +51,7 @@ GameList::~GameList() { emit ShouldCancelWorker(); } -void GameList::AddEntry(QList<QStandardItem*> entry_items) { +void GameList::AddEntry(const QList<QStandardItem*>& entry_items) { item_model->invisibleRootItem()->appendRow(entry_items); } @@ -72,6 +73,23 @@ void GameList::DonePopulating() { tree_view->setEnabled(true); } +void GameList::PopupContextMenu(const QPoint& menu_location) { + QModelIndex item = tree_view->indexAt(menu_location); + if (!item.isValid()) + return; + + int row = item_model->itemFromIndex(item)->row(); + QStandardItem* child_file = item_model->invisibleRootItem()->child(row, COLUMN_NAME); + u64 program_id = child_file->data(GameListItemPath::ProgramIdRole).toULongLong(); + + QMenu context_menu; + QAction* open_save_location = context_menu.addAction(tr("Open Save Data Location")); + open_save_location->setEnabled(program_id != 0); + connect(open_save_location, &QAction::triggered, + [&]() { emit OpenSaveFolderRequested(program_id); }); + context_menu.exec(tree_view->viewport()->mapToGlobal(menu_location)); +} + void GameList::PopulateAsync(const QString& dir_path, bool deep_scan) { if (!FileUtil::Exists(dir_path.toStdString()) || !FileUtil::IsDirectory(dir_path.toStdString())) { @@ -86,12 +104,13 @@ void GameList::PopulateAsync(const QString& dir_path, bool deep_scan) { emit ShouldCancelWorker(); GameListWorker* worker = new GameListWorker(dir_path, deep_scan); - connect(worker, SIGNAL(EntryReady(QList<QStandardItem*>)), this, - SLOT(AddEntry(QList<QStandardItem*>)), Qt::QueuedConnection); - connect(worker, SIGNAL(Finished()), this, SLOT(DonePopulating()), Qt::QueuedConnection); + connect(worker, &GameListWorker::EntryReady, this, &GameList::AddEntry, Qt::QueuedConnection); + connect(worker, &GameListWorker::Finished, this, &GameList::DonePopulating, + Qt::QueuedConnection); // Use DirectConnection here because worker->Cancel() is thread-safe and we want it to cancel // without delay. - connect(this, SIGNAL(ShouldCancelWorker()), worker, SLOT(Cancel()), Qt::DirectConnection); + connect(this, &GameList::ShouldCancelWorker, worker, &GameListWorker::Cancel, + Qt::DirectConnection); QThreadPool::globalInstance()->start(worker); current_worker = std::move(worker); @@ -128,8 +147,11 @@ void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsign std::vector<u8> smdh; loader->ReadIcon(smdh); + u64 program_id = 0; + loader->ReadProgramId(program_id); + emit EntryReady({ - new GameListItemPath(QString::fromStdString(physical_name), smdh), + new GameListItemPath(QString::fromStdString(physical_name), smdh, program_id), new GameListItem( QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType()))), new GameListItemSize(FileUtil::GetSize(physical_name)), @@ -151,6 +173,6 @@ void GameListWorker::run() { } void GameListWorker::Cancel() { - disconnect(this, 0, 0, 0); + disconnect(this, nullptr, nullptr, nullptr); stop_processing = true; } diff --git a/src/citra_qt/game_list.h b/src/citra_qt/game_list.h index a22e9bc60..1abf10051 100644 --- a/src/citra_qt/game_list.h +++ b/src/citra_qt/game_list.h @@ -25,7 +25,7 @@ public: COLUMN_COUNT, // Number of columns }; - GameList(QWidget* parent = nullptr); + explicit GameList(QWidget* parent = nullptr); ~GameList() override; void PopulateAsync(const QString& dir_path, bool deep_scan); @@ -33,18 +33,18 @@ public: void SaveInterfaceLayout(); void LoadInterfaceLayout(); -public slots: - void AddEntry(QList<QStandardItem*> entry_items); - -private slots: - void ValidateEntry(const QModelIndex& item); - void DonePopulating(); - signals: void GameChosen(QString game_path); void ShouldCancelWorker(); + void OpenSaveFolderRequested(u64 program_id); private: + void AddEntry(const QList<QStandardItem*>& entry_items); + void ValidateEntry(const QModelIndex& item); + void DonePopulating(); + + void PopupContextMenu(const QPoint& menu_location); + QTreeView* tree_view = nullptr; QStandardItemModel* item_model = nullptr; GameListWorker* current_worker = nullptr; diff --git a/src/citra_qt/game_list_p.h b/src/citra_qt/game_list_p.h index 5ca3fe991..a15f06c5f 100644 --- a/src/citra_qt/game_list_p.h +++ b/src/citra_qt/game_list_p.h @@ -71,10 +71,13 @@ class GameListItemPath : public GameListItem { public: static const int FullPathRole = Qt::UserRole + 1; static const int TitleRole = Qt::UserRole + 2; + static const int ProgramIdRole = Qt::UserRole + 3; GameListItemPath() : GameListItem() {} - GameListItemPath(const QString& game_path, const std::vector<u8>& smdh_data) : GameListItem() { + GameListItemPath(const QString& game_path, const std::vector<u8>& smdh_data, u64 program_id) + : GameListItem() { setData(game_path, FullPathRole); + setData(qulonglong(program_id), ProgramIdRole); if (!Loader::IsValidSMDH(smdh_data)) { // SMDH is not valid, set a default icon diff --git a/src/citra_qt/hotkeys.h b/src/citra_qt/hotkeys.h index 350103c6f..46f48c2d8 100644 --- a/src/citra_qt/hotkeys.h +++ b/src/citra_qt/hotkeys.h @@ -55,7 +55,7 @@ class GHotkeysDialog : public QWidget { Q_OBJECT public: - GHotkeysDialog(QWidget* parent = nullptr); + explicit GHotkeysDialog(QWidget* parent = nullptr); private: Ui::hotkeys ui; diff --git a/src/citra_qt/main.cpp b/src/citra_qt/main.cpp index a3887f9ab..ad6221739 100644 --- a/src/citra_qt/main.cpp +++ b/src/citra_qt/main.cpp @@ -2,6 +2,7 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include <cinttypes> #include <clocale> #include <memory> #include <thread> @@ -41,6 +42,7 @@ #include "common/string_util.h" #include "core/arm/disassembler/load_symbol_map.h" #include "core/core.h" +#include "core/file_sys/archive_source_sd_savedata.h" #include "core/gdbstub/gdbstub.h" #include "core/loader/loader.h" #include "core/settings.h" @@ -171,6 +173,8 @@ GMainWindow::GMainWindow() : config(new Config()), emu_thread(nullptr) { // Setup connections connect(game_list, SIGNAL(GameChosen(QString)), this, SLOT(OnGameListLoadFile(QString)), Qt::DirectConnection); + connect(game_list, SIGNAL(OpenSaveFolderRequested(u64)), this, + SLOT(OnGameListOpenSaveFolder(u64)), Qt::DirectConnection); connect(ui.action_Configure, SIGNAL(triggered()), this, SLOT(OnConfigure())); connect(ui.action_Load_File, SIGNAL(triggered()), this, SLOT(OnMenuLoadFile()), Qt::DirectConnection); @@ -460,6 +464,21 @@ void GMainWindow::OnGameListLoadFile(QString game_path) { BootGame(game_path.toStdString()); } +void GMainWindow::OnGameListOpenSaveFolder(u64 program_id) { + std::string sdmc_dir = FileUtil::GetUserPath(D_SDMC_IDX); + std::string path = FileSys::ArchiveSource_SDSaveData::GetSaveDataPathFor(sdmc_dir, program_id); + QString qpath = QString::fromStdString(path); + + QDir dir(qpath); + if (!dir.exists()) { + QMessageBox::critical(this, tr("Error Opening Save Folder"), tr("Folder does not exist!")); + return; + } + + LOG_INFO(Frontend, "Opening save data path for program_id=%" PRIu64, program_id); + QDesktopServices::openUrl(QUrl::fromLocalFile(qpath)); +} + void GMainWindow::OnMenuLoadFile() { QString filename = QFileDialog::getOpenFileName(this, tr("Load File"), UISettings::values.roms_path, diff --git a/src/citra_qt/main.h b/src/citra_qt/main.h index f87178227..035b68a35 100644 --- a/src/citra_qt/main.h +++ b/src/citra_qt/main.h @@ -105,6 +105,7 @@ private slots: void OnStopGame(); /// Called whenever a user selects a game in the game list widget. void OnGameListLoadFile(QString game_path); + void OnGameListOpenSaveFolder(u64 program_id); void OnMenuLoadFile(); void OnMenuLoadSymbolMap(); /// Called whenever a user selects the "File->Select Game List Root" menu item diff --git a/src/citra_qt/util/spinbox.h b/src/citra_qt/util/spinbox.h index a57355cd6..2fa1db3a4 100644 --- a/src/citra_qt/util/spinbox.h +++ b/src/citra_qt/util/spinbox.h @@ -42,7 +42,7 @@ class CSpinBox : public QAbstractSpinBox { Q_OBJECT public: - CSpinBox(QWidget* parent = nullptr); + explicit CSpinBox(QWidget* parent = nullptr); void stepBy(int steps) override; StepEnabled stepEnabled() const override; diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index 74a271f08..e6c2ce335 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -71,9 +71,15 @@ if(ARCHITECTURE_x86_64) set(HEADERS ${HEADERS} x64/abi.h x64/cpu_detect.h - x64/emitter.h) + x64/emitter.h + x64/xbyak_abi.h + x64/xbyak_util.h + ) endif() create_directory_groups(${SRCS} ${HEADERS}) add_library(common STATIC ${SRCS} ${HEADERS}) +if (ARCHITECTURE_x86_64) + target_link_libraries(common xbyak) +endif() diff --git a/src/common/bit_set.h b/src/common/bit_set.h index c48b3b769..3059d0cb0 100644 --- a/src/common/bit_set.h +++ b/src/common/bit_set.h @@ -16,7 +16,7 @@ namespace Common { // Helper functions: -#ifdef _WIN32 +#ifdef _MSC_VER template <typename T> static inline int CountSetBits(T v) { // from https://graphics.stanford.edu/~seander/bithacks.html diff --git a/src/common/file_util.cpp b/src/common/file_util.cpp index b6161f2dc..c618495f7 100644 --- a/src/common/file_util.cpp +++ b/src/common/file_util.cpp @@ -40,9 +40,20 @@ #endif #if defined(__APPLE__) +// CFURL contains __attribute__ directives that gcc does not know how to parse, so we need to just +// ignore them if we're not using clang. The macro is only used to prevent linking against +// functions that don't exist on older versions of macOS, and the worst case scenario is a linker +// error, so this is perfectly safe, just inconvenient. +#ifndef __clang__ +#define availability(...) +#endif #include <CoreFoundation/CFBundle.h> #include <CoreFoundation/CFString.h> #include <CoreFoundation/CFURL.h> +#ifdef availability +#undef availability +#endif + #endif #include <algorithm> diff --git a/src/common/logging/text_formatter.cpp b/src/common/logging/text_formatter.cpp index d61c1696b..9d423766f 100644 --- a/src/common/logging/text_formatter.cpp +++ b/src/common/logging/text_formatter.cpp @@ -7,7 +7,7 @@ #ifdef _WIN32 #define WIN32_LEAN_AND_MEAN -#include <Windows.h> +#include <windows.h> #endif #include "common/assert.h" diff --git a/src/common/string_util.cpp b/src/common/string_util.cpp index df1008180..bad311793 100644 --- a/src/common/string_util.cpp +++ b/src/common/string_util.cpp @@ -14,7 +14,7 @@ #ifdef _WIN32 #include <codecvt> -#include <Windows.h> +#include <windows.h> #include "common/common_funcs.h" #else #include <iconv.h> diff --git a/src/common/thread.cpp b/src/common/thread.cpp index 9bb2f4e1d..9e207118f 100644 --- a/src/common/thread.cpp +++ b/src/common/thread.cpp @@ -6,7 +6,7 @@ #ifdef __APPLE__ #include <mach/mach.h> #elif defined(_WIN32) -#include <Windows.h> +#include <windows.h> #else #if defined(__Bitrig__) || defined(__DragonFly__) || defined(__FreeBSD__) || defined(__OpenBSD__) #include <pthread_np.h> diff --git a/src/common/timer.cpp b/src/common/timer.cpp index e843cbd9c..c9803109e 100644 --- a/src/common/timer.cpp +++ b/src/common/timer.cpp @@ -4,7 +4,8 @@ #include <time.h> #ifdef _WIN32 -#include <Windows.h> +#include <windows.h> +// windows.h needs to be included before other windows headers #include <mmsystem.h> #include <sys/timeb.h> #else diff --git a/src/common/x64/xbyak_abi.h b/src/common/x64/xbyak_abi.h new file mode 100644 index 000000000..6090d93e1 --- /dev/null +++ b/src/common/x64/xbyak_abi.h @@ -0,0 +1,178 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <initializer_list> +#include <xbyak.h> +#include "common/assert.h" +#include "common/bit_set.h" + +namespace Common { +namespace X64 { + +int RegToIndex(const Xbyak::Reg& reg) { + using Kind = Xbyak::Reg::Kind; + ASSERT_MSG((reg.getKind() & (Kind::REG | Kind::XMM)) != 0, + "RegSet only support GPRs and XMM registers."); + ASSERT_MSG(reg.getIdx() < 16, "RegSet only supports XXM0-15."); + return reg.getIdx() + (reg.getKind() == Kind::REG ? 0 : 16); +} + +inline Xbyak::Reg64 IndexToReg64(int reg_index) { + ASSERT(reg_index < 16); + return Xbyak::Reg64(reg_index); +} + +inline Xbyak::Xmm IndexToXmm(int reg_index) { + ASSERT(reg_index >= 16 && reg_index < 32); + return Xbyak::Xmm(reg_index - 16); +} + +inline Xbyak::Reg IndexToReg(int reg_index) { + if (reg_index < 16) { + return IndexToReg64(reg_index); + } else { + return IndexToXmm(reg_index); + } +} + +inline BitSet32 BuildRegSet(std::initializer_list<Xbyak::Reg> regs) { + BitSet32 bits; + for (const Xbyak::Reg& reg : regs) { + bits[RegToIndex(reg)] = true; + } + return bits; +} + +const BitSet32 ABI_ALL_GPRS(0x0000FFFF); +const BitSet32 ABI_ALL_XMMS(0xFFFF0000); + +#ifdef _WIN32 + +// Microsoft x64 ABI +const Xbyak::Reg ABI_RETURN = Xbyak::util::rax; +const Xbyak::Reg ABI_PARAM1 = Xbyak::util::rcx; +const Xbyak::Reg ABI_PARAM2 = Xbyak::util::rdx; +const Xbyak::Reg ABI_PARAM3 = Xbyak::util::r8; +const Xbyak::Reg ABI_PARAM4 = Xbyak::util::r9; + +const BitSet32 ABI_ALL_CALLER_SAVED = BuildRegSet({ + // GPRs + Xbyak::util::rcx, Xbyak::util::rdx, Xbyak::util::r8, Xbyak::util::r9, Xbyak::util::r10, + Xbyak::util::r11, + // XMMs + Xbyak::util::xmm0, Xbyak::util::xmm1, Xbyak::util::xmm2, Xbyak::util::xmm3, Xbyak::util::xmm4, + Xbyak::util::xmm5, +}); + +const BitSet32 ABI_ALL_CALLEE_SAVED = BuildRegSet({ + // GPRs + Xbyak::util::rbx, Xbyak::util::rsi, Xbyak::util::rdi, Xbyak::util::rbp, Xbyak::util::r12, + Xbyak::util::r13, Xbyak::util::r14, Xbyak::util::r15, + // XMMs + Xbyak::util::xmm6, Xbyak::util::xmm7, Xbyak::util::xmm8, Xbyak::util::xmm9, Xbyak::util::xmm10, + Xbyak::util::xmm11, Xbyak::util::xmm12, Xbyak::util::xmm13, Xbyak::util::xmm14, + Xbyak::util::xmm15, +}); + +constexpr size_t ABI_SHADOW_SPACE = 0x20; + +#else + +// System V x86-64 ABI +const Xbyak::Reg ABI_RETURN = Xbyak::util::rax; +const Xbyak::Reg ABI_PARAM1 = Xbyak::util::rdi; +const Xbyak::Reg ABI_PARAM2 = Xbyak::util::rsi; +const Xbyak::Reg ABI_PARAM3 = Xbyak::util::rdx; +const Xbyak::Reg ABI_PARAM4 = Xbyak::util::rcx; + +const BitSet32 ABI_ALL_CALLER_SAVED = BuildRegSet({ + // GPRs + Xbyak::util::rcx, Xbyak::util::rdx, Xbyak::util::rdi, Xbyak::util::rsi, Xbyak::util::r8, + Xbyak::util::r9, Xbyak::util::r10, Xbyak::util::r11, + // XMMs + Xbyak::util::xmm0, Xbyak::util::xmm1, Xbyak::util::xmm2, Xbyak::util::xmm3, Xbyak::util::xmm4, + Xbyak::util::xmm5, Xbyak::util::xmm6, Xbyak::util::xmm7, Xbyak::util::xmm8, Xbyak::util::xmm9, + Xbyak::util::xmm10, Xbyak::util::xmm11, Xbyak::util::xmm12, Xbyak::util::xmm13, + Xbyak::util::xmm14, Xbyak::util::xmm15, +}); + +const BitSet32 ABI_ALL_CALLEE_SAVED = BuildRegSet({ + // GPRs + Xbyak::util::rbx, Xbyak::util::rbp, Xbyak::util::r12, Xbyak::util::r13, Xbyak::util::r14, + Xbyak::util::r15, +}); + +constexpr size_t ABI_SHADOW_SPACE = 0; + +#endif + +void ABI_CalculateFrameSize(BitSet32 regs, size_t rsp_alignment, size_t needed_frame_size, + s32* out_subtraction, s32* out_xmm_offset) { + int count = (regs & ABI_ALL_GPRS).Count(); + rsp_alignment -= count * 8; + size_t subtraction = 0; + int xmm_count = (regs & ABI_ALL_XMMS).Count(); + if (xmm_count) { + // If we have any XMMs to save, we must align the stack here. + subtraction = rsp_alignment & 0xF; + } + subtraction += 0x10 * xmm_count; + size_t xmm_base_subtraction = subtraction; + subtraction += needed_frame_size; + subtraction += ABI_SHADOW_SPACE; + // Final alignment. + rsp_alignment -= subtraction; + subtraction += rsp_alignment & 0xF; + + *out_subtraction = (s32)subtraction; + *out_xmm_offset = (s32)(subtraction - xmm_base_subtraction); +} + +size_t ABI_PushRegistersAndAdjustStack(Xbyak::CodeGenerator& code, BitSet32 regs, + size_t rsp_alignment, size_t needed_frame_size = 0) { + s32 subtraction, xmm_offset; + ABI_CalculateFrameSize(regs, rsp_alignment, needed_frame_size, &subtraction, &xmm_offset); + + for (int reg_index : (regs & ABI_ALL_GPRS)) { + code.push(IndexToReg64(reg_index)); + } + + if (subtraction != 0) { + code.sub(code.rsp, subtraction); + } + + for (int reg_index : (regs & ABI_ALL_XMMS)) { + code.movaps(code.xword[code.rsp + xmm_offset], IndexToXmm(reg_index)); + xmm_offset += 0x10; + } + + return ABI_SHADOW_SPACE; +} + +void ABI_PopRegistersAndAdjustStack(Xbyak::CodeGenerator& code, BitSet32 regs, size_t rsp_alignment, + size_t needed_frame_size = 0) { + s32 subtraction, xmm_offset; + ABI_CalculateFrameSize(regs, rsp_alignment, needed_frame_size, &subtraction, &xmm_offset); + + for (int reg_index : (regs & ABI_ALL_XMMS)) { + code.movaps(IndexToXmm(reg_index), code.xword[code.rsp + xmm_offset]); + xmm_offset += 0x10; + } + + if (subtraction != 0) { + code.add(code.rsp, subtraction); + } + + // GPRs need to be popped in reverse order + for (int reg_index = 15; reg_index >= 0; reg_index--) { + if (regs[reg_index]) { + code.pop(IndexToReg64(reg_index)); + } + } +} + +} // namespace X64 +} // namespace Common diff --git a/src/common/x64/xbyak_util.h b/src/common/x64/xbyak_util.h new file mode 100644 index 000000000..0f52f704b --- /dev/null +++ b/src/common/x64/xbyak_util.h @@ -0,0 +1,49 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <type_traits> +#include <xbyak.h> +#include "common/x64/xbyak_abi.h" + +namespace Common { +namespace X64 { + +// Constants for use with cmpps/cmpss +enum { + CMP_EQ = 0, + CMP_LT = 1, + CMP_LE = 2, + CMP_UNORD = 3, + CMP_NEQ = 4, + CMP_NLT = 5, + CMP_NLE = 6, + CMP_ORD = 7, +}; + +inline bool IsWithin2G(uintptr_t ref, uintptr_t target) { + u64 distance = target - (ref + 5); + return !(distance >= 0x8000'0000ULL && distance <= ~0x8000'0000ULL); +} + +inline bool IsWithin2G(const Xbyak::CodeGenerator& code, uintptr_t target) { + return IsWithin2G(reinterpret_cast<uintptr_t>(code.getCurr()), target); +} + +template <typename T> +inline void CallFarFunction(Xbyak::CodeGenerator& code, const T f) { + static_assert(std::is_pointer<T>(), "Argument must be a (function) pointer."); + size_t addr = reinterpret_cast<size_t>(f); + if (IsWithin2G(code, addr)) { + code.call(f); + } else { + // ABI_RETURN is a safe temp register to use before a call + code.mov(ABI_RETURN, addr); + code.call(ABI_RETURN); + } +} + +} // namespace X64 +} // namespace Common diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index e26677079..af224166a 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -38,6 +38,7 @@ set(SRCS hle/applets/swkbd.cpp hle/kernel/address_arbiter.cpp hle/kernel/client_port.cpp + hle/kernel/client_session.cpp hle/kernel/event.cpp hle/kernel/kernel.cpp hle/kernel/memory.cpp @@ -46,14 +47,15 @@ set(SRCS hle/kernel/resource_limit.cpp hle/kernel/semaphore.cpp hle/kernel/server_port.cpp - hle/kernel/session.cpp + hle/kernel/server_session.cpp hle/kernel/shared_memory.cpp hle/kernel/thread.cpp hle/kernel/timer.cpp hle/kernel/vm_manager.cpp hle/service/ac_u.cpp - hle/service/act_a.cpp - hle/service/act_u.cpp + hle/service/act/act.cpp + hle/service/act/act_a.cpp + hle/service/act/act_u.cpp hle/service/am/am.cpp hle/service/am/am_app.cpp hle/service/am/am_net.cpp @@ -73,6 +75,7 @@ set(SRCS hle/service/cam/cam_s.cpp hle/service/cam/cam_u.cpp hle/service/cecd/cecd.cpp + hle/service/cecd/cecd_ndm.cpp hle/service/cecd/cecd_s.cpp hle/service/cecd/cecd_u.cpp hle/service/cfg/cfg.cpp @@ -194,12 +197,14 @@ set(HEADERS hle/config_mem.h hle/function_wrappers.h hle/hle.h + hle/ipc.h hle/applets/applet.h hle/applets/erreula.h hle/applets/mii_selector.h hle/applets/swkbd.h hle/kernel/address_arbiter.h hle/kernel/client_port.h + hle/kernel/client_session.h hle/kernel/event.h hle/kernel/kernel.h hle/kernel/memory.h @@ -208,15 +213,16 @@ set(HEADERS hle/kernel/resource_limit.h hle/kernel/semaphore.h hle/kernel/server_port.h - hle/kernel/session.h + hle/kernel/server_session.h hle/kernel/shared_memory.h hle/kernel/thread.h hle/kernel/timer.h hle/kernel/vm_manager.h hle/result.h hle/service/ac_u.h - hle/service/act_a.h - hle/service/act_u.h + hle/service/act/act.h + hle/service/act/act_a.h + hle/service/act/act_u.h hle/service/am/am.h hle/service/am/am_app.h hle/service/am/am_net.h @@ -236,6 +242,7 @@ set(HEADERS hle/service/cam/cam_s.h hle/service/cam/cam_u.h hle/service/cecd/cecd.h + hle/service/cecd/cecd_ndm.h hle/service/cecd/cecd_s.h hle/service/cecd/cecd_u.h hle/service/cfg/cfg.h diff --git a/src/core/arm/dyncom/arm_dyncom_interpreter.cpp b/src/core/arm/dyncom/arm_dyncom_interpreter.cpp index 7b8616702..67c45640a 100644 --- a/src/core/arm/dyncom/arm_dyncom_interpreter.cpp +++ b/src/core/arm/dyncom/arm_dyncom_interpreter.cpp @@ -953,7 +953,7 @@ unsigned InterpreterMainLoop(ARMul_State* cpu) { #define GDB_BP_CHECK \ cpu->Cpsr &= ~(1 << 5); \ cpu->Cpsr |= cpu->TFlag << 5; \ - if (GDBStub::g_server_enabled) { \ + if (GDBStub::IsServerEnabled()) { \ if (GDBStub::IsMemoryBreak() || (breakpoint_data.type != GDBStub::BreakpointType::None && \ PC == breakpoint_data.address)) { \ GDBStub::Break(); \ @@ -1649,7 +1649,7 @@ DISPATCH : { } // Find breakpoint if one exists within the block - if (GDBStub::g_server_enabled && GDBStub::IsConnected()) { + if (GDBStub::IsConnected()) { breakpoint_data = GDBStub::GetNextBreakpointFromAddress(cpu->Reg[15], GDBStub::BreakpointType::Execute); } diff --git a/src/core/arm/dyncom/arm_dyncom_trans.h b/src/core/arm/dyncom/arm_dyncom_trans.h index b1ec90662..632ff2cd6 100644 --- a/src/core/arm/dyncom/arm_dyncom_trans.h +++ b/src/core/arm/dyncom/arm_dyncom_trans.h @@ -1,3 +1,5 @@ +#pragma once + #include <cstddef> #include "common/common_types.h" diff --git a/src/core/arm/skyeye_common/armstate.cpp b/src/core/arm/skyeye_common/armstate.cpp index 1465b074e..c36b0208f 100644 --- a/src/core/arm/skyeye_common/armstate.cpp +++ b/src/core/arm/skyeye_common/armstate.cpp @@ -182,7 +182,7 @@ void ARMul_State::ResetMPCoreCP15Registers() { } static void CheckMemoryBreakpoint(u32 address, GDBStub::BreakpointType type) { - if (GDBStub::g_server_enabled && GDBStub::CheckBreakpoint(address, type)) { + if (GDBStub::IsServerEnabled() && GDBStub::CheckBreakpoint(address, type)) { LOG_DEBUG(Debug, "Found memory breakpoint @ %08x", address); GDBStub::Break(true); } diff --git a/src/core/core.cpp b/src/core/core.cpp index 49ac8be6e..6efa18159 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -22,7 +22,7 @@ std::unique_ptr<ARM_Interface> g_sys_core; ///< ARM11 system (OS) core /// Run the core CPU loop void RunLoop(int tight_loop) { - if (GDBStub::g_server_enabled) { + if (GDBStub::IsServerEnabled()) { GDBStub::HandlePacket(); // If the loop is halted and we want to step, use a tiny (1) number of instructions to diff --git a/src/core/file_sys/archive_source_sd_savedata.cpp b/src/core/file_sys/archive_source_sd_savedata.cpp index 2d8a950a3..287322d3e 100644 --- a/src/core/file_sys/archive_source_sd_savedata.cpp +++ b/src/core/file_sys/archive_source_sd_savedata.cpp @@ -90,4 +90,9 @@ ResultVal<ArchiveFormatInfo> ArchiveSource_SDSaveData::GetFormatInfo(u64 program return MakeResult<ArchiveFormatInfo>(info); } +std::string ArchiveSource_SDSaveData::GetSaveDataPathFor(const std::string& mount_point, + u64 program_id) { + return GetSaveDataPath(GetSaveDataContainerPath(mount_point), program_id); +} + } // namespace FileSys diff --git a/src/core/file_sys/archive_source_sd_savedata.h b/src/core/file_sys/archive_source_sd_savedata.h index b33126c31..b5fe43cc1 100644 --- a/src/core/file_sys/archive_source_sd_savedata.h +++ b/src/core/file_sys/archive_source_sd_savedata.h @@ -23,6 +23,8 @@ public: ResultCode Format(u64 program_id, const FileSys::ArchiveFormatInfo& format_info); ResultVal<ArchiveFormatInfo> GetFormatInfo(u64 program_id) const; + static std::string GetSaveDataPathFor(const std::string& mount_point, u64 program_id); + private: std::string mount_point; }; diff --git a/src/core/gdbstub/gdbstub.cpp b/src/core/gdbstub/gdbstub.cpp index aea43e92b..f96cbde64 100644 --- a/src/core/gdbstub/gdbstub.cpp +++ b/src/core/gdbstub/gdbstub.cpp @@ -5,6 +5,7 @@ // Originally written by Sven Peter <sven@fail0verflow.com> for anergistic. #include <algorithm> +#include <atomic> #include <climits> #include <csignal> #include <cstdarg> @@ -15,8 +16,8 @@ #include <fcntl.h> #ifdef _WIN32 -#include <WinSock2.h> -#include <common/x64/abi.h> +#include <winsock2.h> +// winsock2.h needs to be included first to prevent winsock.h being included by other includes #include <io.h> #include <iphlpapi.h> #include <ws2tcpip.h> @@ -130,7 +131,10 @@ static u16 gdbstub_port = 24689; static bool halt_loop = true; static bool step_loop = false; -std::atomic<bool> g_server_enabled(false); + +// If set to false, the server will never be started and no +// gdbstub-related functions will be executed. +static std::atomic<bool> server_enabled(false); #ifdef _WIN32 WSADATA InitData; @@ -181,11 +185,10 @@ static u8 NibbleToHex(u8 n) { /** * Converts input hex string characters into an array of equivalent of u8 bytes. * -* @param dest Pointer to buffer to store u8 bytes. * @param src Pointer to array of output hex string characters. * @param len Length of src array. */ -static u32 HexToInt(u8* src, u32 len) { +static u32 HexToInt(const u8* src, size_t len) { u32 output = 0; while (len-- > 0) { output = (output << 4) | HexCharToValue(src[0]); @@ -201,7 +204,7 @@ static u32 HexToInt(u8* src, u32 len) { * @param src Pointer to array of u8 bytes. * @param len Length of src array. */ -static void MemToGdbHex(u8* dest, u8* src, u32 len) { +static void MemToGdbHex(u8* dest, const u8* src, size_t len) { while (len-- > 0) { u8 tmp = *src++; *dest++ = NibbleToHex(tmp >> 4); @@ -216,7 +219,7 @@ static void MemToGdbHex(u8* dest, u8* src, u32 len) { * @param src Pointer to array of output hex string characters. * @param len Length of src array. */ -static void GdbHexToMem(u8* dest, u8* src, u32 len) { +static void GdbHexToMem(u8* dest, const u8* src, size_t len) { while (len-- > 0) { *dest++ = (HexCharToValue(src[0]) << 4) | HexCharToValue(src[1]); src += 2; @@ -240,7 +243,7 @@ static void IntToGdbHex(u8* dest, u32 v) { * * @param src Pointer to hex string. */ -static u32 GdbHexToInt(u8* src) { +static u32 GdbHexToInt(const u8* src) { u32 output = 0; for (int i = 0; i < 8; i += 2) { @@ -264,7 +267,7 @@ static u8 ReadByte() { } /// Calculate the checksum of the current command buffer. -static u8 CalculateChecksum(u8* buffer, u32 length) { +static u8 CalculateChecksum(const u8* buffer, size_t length) { return static_cast<u8>(std::accumulate(buffer, buffer + length, 0, std::plus<u8>())); } @@ -582,7 +585,7 @@ static void ReadRegisters() { /// Modify data of register specified by gdb client. static void WriteRegister() { - u8* buffer_ptr = command_buffer + 3; + const u8* buffer_ptr = command_buffer + 3; u32 id = HexCharToValue(command_buffer[1]); if (command_buffer[2] != '=') { @@ -608,7 +611,7 @@ static void WriteRegister() { /// Modify all registers with data received from the client. static void WriteRegisters() { - u8* buffer_ptr = command_buffer + 1; + const u8* buffer_ptr = command_buffer + 1; if (command_buffer[0] != 'G') return SendReply("E01"); @@ -653,7 +656,7 @@ static void ReadMemory() { SendReply("E01"); } - u8* data = Memory::GetPointer(addr); + const u8* data = Memory::GetPointer(addr); if (!data) { return SendReply("E00"); } @@ -902,7 +905,7 @@ void SetServerPort(u16 port) { void ToggleServer(bool status) { if (status) { - g_server_enabled = status; + server_enabled = status; // Start server if (!IsConnected() && Core::g_sys_core != nullptr) { @@ -914,12 +917,12 @@ void ToggleServer(bool status) { Shutdown(); } - g_server_enabled = status; + server_enabled = status; } } static void Init(u16 port) { - if (!g_server_enabled) { + if (!server_enabled) { // Set the halt loop to false in case the user enabled the gdbstub mid-execution. // This way the CPU can still execute normally. halt_loop = false; @@ -998,7 +1001,7 @@ void Init() { } void Shutdown() { - if (!g_server_enabled) { + if (!server_enabled) { return; } @@ -1015,8 +1018,12 @@ void Shutdown() { LOG_INFO(Debug_GDBStub, "GDB stopped."); } +bool IsServerEnabled() { + return server_enabled; +} + bool IsConnected() { - return g_server_enabled && gdbserver_socket != -1; + return IsServerEnabled() && gdbserver_socket != -1; } bool GetCpuHaltFlag() { diff --git a/src/core/gdbstub/gdbstub.h b/src/core/gdbstub/gdbstub.h index a7483bb10..38177e32c 100644 --- a/src/core/gdbstub/gdbstub.h +++ b/src/core/gdbstub/gdbstub.h @@ -5,7 +5,7 @@ // Originally written by Sven Peter <sven@fail0verflow.com> for anergistic. #pragma once -#include <atomic> + #include "common/common_types.h" namespace GDBStub { @@ -24,10 +24,6 @@ struct BreakpointAddress { BreakpointType type; }; -/// If set to false, the server will never be started and no gdbstub-related functions will be -/// executed. -extern std::atomic<bool> g_server_enabled; - /** * Set the port the gdbstub should use to listen for connections. * @@ -36,7 +32,7 @@ extern std::atomic<bool> g_server_enabled; void SetServerPort(u16 port); /** - * Set the g_server_enabled flag and start or stop the server if possible. + * Starts or stops the server if possible. * * @param status Set the server to enabled or disabled. */ @@ -48,6 +44,9 @@ void Init(); /// Stop gdbstub server. void Shutdown(); +/// Checks if the gdbstub server is enabled. +bool IsServerEnabled(); + /// Returns true if there is an active socket connection. bool IsConnected(); diff --git a/src/core/hle/kernel/session.h b/src/core/hle/ipc.h index ec025f732..4e094faa7 100644 --- a/src/core/hle/kernel/session.h +++ b/src/core/hle/ipc.h @@ -1,17 +1,31 @@ -// Copyright 2014 Citra Emulator Project +// Copyright 2016 Citra Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. #pragma once -#include <string> -#include "common/assert.h" #include "common/common_types.h" -#include "core/hle/kernel/kernel.h" #include "core/hle/kernel/thread.h" -#include "core/hle/result.h" #include "core/memory.h" +namespace Kernel { + +static const int kCommandHeaderOffset = 0x80; ///< Offset into command buffer of header + +/** + * Returns a pointer to the command buffer in the current thread's TLS + * TODO(Subv): This is not entirely correct, the command buffer should be copied from + * the thread's TLS to an intermediate buffer in kernel memory, and then copied again to + * the service handler process' memory. + * @param offset Optional offset into command buffer + * @return Pointer to command buffer + */ +inline u32* GetCommandBuffer(const int offset = 0) { + return (u32*)Memory::GetPointer(GetCurrentThread()->GetTLSAddress() + kCommandHeaderOffset + + offset); +} +} + namespace IPC { enum DescriptorType : u32 { @@ -144,75 +158,3 @@ inline DescriptorType GetDescriptorType(u32 descriptor) { } } // namespace IPC - -namespace Kernel { - -static const int kCommandHeaderOffset = 0x80; ///< Offset into command buffer of header - -/** - * Returns a pointer to the command buffer in the current thread's TLS - * TODO(Subv): This is not entirely correct, the command buffer should be copied from - * the thread's TLS to an intermediate buffer in kernel memory, and then copied again to - * the service handler process' memory. - * @param offset Optional offset into command buffer - * @return Pointer to command buffer - */ -inline u32* GetCommandBuffer(const int offset = 0) { - return (u32*)Memory::GetPointer(GetCurrentThread()->GetTLSAddress() + kCommandHeaderOffset + - offset); -} - -/** - * Kernel object representing the client endpoint of an IPC session. Sessions are the basic CTR-OS - * primitive for communication between different processes, and are used to implement service calls - * to the various system services. - * - * To make a service call, the client must write the command header and parameters to the buffer - * located at offset 0x80 of the TLS (Thread-Local Storage) area, then execute a SendSyncRequest - * SVC call with its Session handle. The kernel will read the command header, using it to marshall - * the parameters to the process at the server endpoint of the session. After the server replies to - * the request, the response is marshalled back to the caller's TLS buffer and control is - * transferred back to it. - * - * In Citra, only the client endpoint is currently implemented and only HLE calls, where the IPC - * request is answered by C++ code in the emulator, are supported. When SendSyncRequest is called - * with the session handle, this class's SyncRequest method is called, which should read the TLS - * buffer and emulate the call accordingly. Since the code can directly read the emulated memory, - * no parameter marshalling is done. - * - * In the long term, this should be turned into the full-fledged IPC mechanism implemented by - * CTR-OS so that IPC calls can be optionally handled by the real implementations of processes, as - * opposed to HLE simulations. - */ -class Session : public WaitObject { -public: - Session(); - ~Session() override; - - std::string GetTypeName() const override { - return "Session"; - } - - static const HandleType HANDLE_TYPE = HandleType::Session; - HandleType GetHandleType() const override { - return HANDLE_TYPE; - } - - /** - * Handles a synchronous call to this session using HLE emulation. Emulated <-> emulated calls - * aren't supported yet. - */ - virtual ResultVal<bool> SyncRequest() = 0; - - // TODO(bunnei): These functions exist to satisfy a hardware test with a Session object - // passed into WaitSynchronization. Figure out the meaning of them. - - bool ShouldWait() override { - return true; - } - - void Acquire() override { - ASSERT_MSG(!ShouldWait(), "object unavailable!"); - } -}; -} diff --git a/src/core/hle/kernel/address_arbiter.cpp b/src/core/hle/kernel/address_arbiter.cpp index 37eec4c84..b5a0cc3a3 100644 --- a/src/core/hle/kernel/address_arbiter.cpp +++ b/src/core/hle/kernel/address_arbiter.cpp @@ -79,8 +79,6 @@ ResultCode AddressArbiter::ArbitrateAddress(ArbitrationType type, VAddr address, ErrorSummary::WrongArgument, ErrorLevel::Usage); } - HLE::Reschedule(__func__); - // The calls that use a timeout seem to always return a Timeout error even if they did not put // the thread to sleep if (type == ArbitrationType::WaitIfLessThanWithTimeout || diff --git a/src/core/hle/kernel/client_port.cpp b/src/core/hle/kernel/client_port.cpp index aedc6f989..22645f4ec 100644 --- a/src/core/hle/kernel/client_port.cpp +++ b/src/core/hle/kernel/client_port.cpp @@ -4,12 +4,44 @@ #include "common/assert.h" #include "core/hle/kernel/client_port.h" +#include "core/hle/kernel/client_session.h" #include "core/hle/kernel/kernel.h" #include "core/hle/kernel/server_port.h" +#include "core/hle/kernel/server_session.h" namespace Kernel { ClientPort::ClientPort() {} ClientPort::~ClientPort() {} +ResultVal<SharedPtr<ClientSession>> ClientPort::Connect() { + // Note: Threads do not wait for the server endpoint to call + // AcceptSession before returning from this call. + + if (active_sessions >= max_sessions) { + // TODO(Subv): Return an error code in this situation after session disconnection is + // implemented. + /*return ResultCode(ErrorDescription::MaxConnectionsReached, + ErrorModule::OS, ErrorSummary::WouldBlock, + ErrorLevel::Temporary);*/ + } + active_sessions++; + + // Create a new session pair, let the created sessions inherit the parent port's HLE handler. + auto sessions = + ServerSession::CreateSessionPair(server_port->GetName(), server_port->hle_handler); + auto client_session = std::get<SharedPtr<ClientSession>>(sessions); + auto server_session = std::get<SharedPtr<ServerSession>>(sessions); + + if (server_port->hle_handler) + server_port->hle_handler->ClientConnected(server_session); + + server_port->pending_sessions.push_back(std::move(server_session)); + + // Wake the threads waiting on the ServerPort + server_port->WakeupAllWaitingThreads(); + + return MakeResult<SharedPtr<ClientSession>>(std::move(client_session)); +} + } // namespace diff --git a/src/core/hle/kernel/client_port.h b/src/core/hle/kernel/client_port.h index d28147718..511490c7c 100644 --- a/src/core/hle/kernel/client_port.h +++ b/src/core/hle/kernel/client_port.h @@ -11,8 +11,9 @@ namespace Kernel { class ServerPort; +class ClientSession; -class ClientPort : public Object { +class ClientPort final : public Object { public: friend class ServerPort; std::string GetTypeName() const override { @@ -27,12 +28,20 @@ public: return HANDLE_TYPE; } + /** + * Creates a new Session pair, adds the created ServerSession to the associated ServerPort's + * list of pending sessions, and signals the ServerPort, causing any threads + * waiting on it to awake. + * @returns ClientSession The client endpoint of the created Session pair, or error code. + */ + ResultVal<SharedPtr<ClientSession>> Connect(); + SharedPtr<ServerPort> server_port; ///< ServerPort associated with this client port. u32 max_sessions; ///< Maximum number of simultaneous sessions the port can have u32 active_sessions; ///< Number of currently open sessions to this port std::string name; ///< Name of client port (optional) -protected: +private: ClientPort(); ~ClientPort() override; }; diff --git a/src/core/hle/kernel/client_session.cpp b/src/core/hle/kernel/client_session.cpp new file mode 100644 index 000000000..0331386ec --- /dev/null +++ b/src/core/hle/kernel/client_session.cpp @@ -0,0 +1,40 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "common/assert.h" + +#include "core/hle/kernel/client_session.h" +#include "core/hle/kernel/server_session.h" + +namespace Kernel { + +ClientSession::ClientSession() = default; +ClientSession::~ClientSession() { + // This destructor will be called automatically when the last ClientSession handle is closed by + // the emulated application. + + if (server_session->hle_handler) + server_session->hle_handler->ClientDisconnected(server_session); + + // TODO(Subv): If the session is still open, set the connection status to 2 (Closed by client), + // wake up all the ServerSession's waiting threads and set the WaitSynchronization result to + // 0xC920181A. +} + +ResultVal<SharedPtr<ClientSession>> ClientSession::Create(ServerSession* server_session, + std::string name) { + SharedPtr<ClientSession> client_session(new ClientSession); + + client_session->name = std::move(name); + client_session->server_session = server_session; + client_session->session_status = SessionStatus::Open; + return MakeResult<SharedPtr<ClientSession>>(std::move(client_session)); +} + +ResultCode ClientSession::SendSyncRequest() { + // Signal the server session that new data is available + return server_session->HandleSyncRequest(); +} + +} // namespace diff --git a/src/core/hle/kernel/client_session.h b/src/core/hle/kernel/client_session.h new file mode 100644 index 000000000..ed468dec6 --- /dev/null +++ b/src/core/hle/kernel/client_session.h @@ -0,0 +1,65 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <memory> +#include <string> + +#include "common/common_types.h" + +#include "core/hle/kernel/kernel.h" + +namespace Kernel { + +class ServerSession; + +enum class SessionStatus { + Open = 1, + ClosedByClient = 2, + ClosedBYServer = 3, +}; + +class ClientSession final : public Object { +public: + friend class ServerSession; + + std::string GetTypeName() const override { + return "ClientSession"; + } + + std::string GetName() const override { + return name; + } + + static const HandleType HANDLE_TYPE = HandleType::ClientSession; + HandleType GetHandleType() const override { + return HANDLE_TYPE; + } + + /** + * Sends an SyncRequest from the current emulated thread. + * @return ResultCode of the operation. + */ + ResultCode SendSyncRequest(); + + std::string name; ///< Name of client port (optional) + ServerSession* server_session; ///< The server session associated with this client session. + SessionStatus session_status; ///< The session's current status. + +private: + ClientSession(); + ~ClientSession() override; + + /** + * Creates a client session. + * @param server_session The server session associated with this client session + * @param name Optional name of client session + * @return The created client session + */ + static ResultVal<SharedPtr<ClientSession>> Create(ServerSession* server_session, + std::string name = "Unknown"); +}; + +} // namespace diff --git a/src/core/hle/kernel/kernel.cpp b/src/core/hle/kernel/kernel.cpp index 0c8752670..1db8e102f 100644 --- a/src/core/hle/kernel/kernel.cpp +++ b/src/core/hle/kernel/kernel.cpp @@ -3,6 +3,7 @@ // Refer to the license.txt file included. #include <algorithm> +#include <boost/range/algorithm_ext/erase.hpp> #include "common/assert.h" #include "common/logging/log.h" #include "core/hle/config_mem.h" @@ -31,13 +32,61 @@ void WaitObject::RemoveWaitingThread(Thread* thread) { waiting_threads.erase(itr); } -void WaitObject::WakeupAllWaitingThreads() { - for (auto thread : waiting_threads) - thread->ResumeFromWait(); +SharedPtr<Thread> WaitObject::GetHighestPriorityReadyThread() { + // Remove the threads that are ready or already running from our waitlist + boost::range::remove_erase_if(waiting_threads, [](const SharedPtr<Thread>& thread) { + return thread->status == THREADSTATUS_RUNNING || thread->status == THREADSTATUS_READY || + thread->status == THREADSTATUS_DEAD; + }); + + // TODO(Subv): This call should be performed inside the loop below to check if an object can be + // acquired by a particular thread. This is useful for things like recursive locking of Mutexes. + if (ShouldWait()) + return nullptr; + + Thread* candidate = nullptr; + s32 candidate_priority = THREADPRIO_LOWEST + 1; + + for (const auto& thread : waiting_threads) { + if (thread->current_priority >= candidate_priority) + continue; - waiting_threads.clear(); + bool ready_to_run = + std::none_of(thread->wait_objects.begin(), thread->wait_objects.end(), + [](const SharedPtr<WaitObject>& object) { return object->ShouldWait(); }); + if (ready_to_run) { + candidate = thread.get(); + candidate_priority = thread->current_priority; + } + } + + return candidate; +} - HLE::Reschedule(__func__); +void WaitObject::WakeupAllWaitingThreads() { + while (auto thread = GetHighestPriorityReadyThread()) { + if (!thread->IsSleepingOnWaitAll()) { + Acquire(); + // Set the output index of the WaitSynchronizationN call to the index of this object. + if (thread->wait_set_output) { + thread->SetWaitSynchronizationOutput(thread->GetWaitObjectIndex(this)); + thread->wait_set_output = false; + } + } else { + for (auto& object : thread->wait_objects) { + object->Acquire(); + object->RemoveWaitingThread(thread.get()); + } + // Note: This case doesn't update the output index of WaitSynchronizationN. + // Clear the thread's waitlist + thread->wait_objects.clear(); + } + + thread->SetWaitSynchronizationResult(RESULT_SUCCESS); + thread->ResumeFromWait(); + // Note: Removing the thread from the object's waitlist will be + // done by GetHighestPriorityReadyThread. + } } const std::vector<SharedPtr<Thread>>& WaitObject::GetWaitingThreads() const { diff --git a/src/core/hle/kernel/kernel.h b/src/core/hle/kernel/kernel.h index 231cf7b75..1adcf6c71 100644 --- a/src/core/hle/kernel/kernel.h +++ b/src/core/hle/kernel/kernel.h @@ -31,22 +31,21 @@ enum KernelHandle : Handle { }; enum class HandleType : u32 { - Unknown = 0, - - Session = 2, - Event = 3, - Mutex = 4, - SharedMemory = 5, - Redirection = 6, - Thread = 7, - Process = 8, - AddressArbiter = 9, - Semaphore = 10, - Timer = 11, - ResourceLimit = 12, - CodeSet = 13, - ClientPort = 14, - ServerPort = 15, + Unknown, + Event, + Mutex, + SharedMemory, + Thread, + Process, + AddressArbiter, + Semaphore, + Timer, + ResourceLimit, + CodeSet, + ClientPort, + ServerPort, + ClientSession, + ServerSession, }; enum { @@ -82,23 +81,23 @@ public: */ bool IsWaitable() const { switch (GetHandleType()) { - case HandleType::Session: - case HandleType::ServerPort: case HandleType::Event: case HandleType::Mutex: case HandleType::Thread: case HandleType::Semaphore: case HandleType::Timer: + case HandleType::ServerPort: + case HandleType::ServerSession: return true; case HandleType::Unknown: case HandleType::SharedMemory: - case HandleType::Redirection: case HandleType::Process: case HandleType::AddressArbiter: case HandleType::ResourceLimit: case HandleType::CodeSet: case HandleType::ClientPort: + case HandleType::ClientSession: return false; } } @@ -152,9 +151,15 @@ public: */ void RemoveWaitingThread(Thread* thread); - /// Wake up all threads waiting on this object + /** + * Wake up all threads waiting on this object that can be awoken, in priority order, + * and set the synchronization result and output of the thread. + */ void WakeupAllWaitingThreads(); + /// Obtains the highest priority thread that is ready to run from this object's waiting list. + SharedPtr<Thread> GetHighestPriorityReadyThread(); + /// Get a const reference to the waiting threads list for debug use const std::vector<SharedPtr<Thread>>& GetWaitingThreads() const; diff --git a/src/core/hle/kernel/server_port.cpp b/src/core/hle/kernel/server_port.cpp index 8e3ec8a14..6c19aa7c0 100644 --- a/src/core/hle/kernel/server_port.cpp +++ b/src/core/hle/kernel/server_port.cpp @@ -24,12 +24,14 @@ void ServerPort::Acquire() { } std::tuple<SharedPtr<ServerPort>, SharedPtr<ClientPort>> ServerPort::CreatePortPair( - u32 max_sessions, std::string name) { + u32 max_sessions, std::string name, + std::shared_ptr<Service::SessionRequestHandler> hle_handler) { SharedPtr<ServerPort> server_port(new ServerPort); SharedPtr<ClientPort> client_port(new ClientPort); server_port->name = name + "_Server"; + server_port->hle_handler = std::move(hle_handler); client_port->name = name + "_Client"; client_port->server_port = server_port; client_port->max_sessions = max_sessions; diff --git a/src/core/hle/kernel/server_port.h b/src/core/hle/kernel/server_port.h index fa9448ca0..b0f8df62c 100644 --- a/src/core/hle/kernel/server_port.h +++ b/src/core/hle/kernel/server_port.h @@ -4,11 +4,16 @@ #pragma once +#include <memory> #include <string> #include <tuple> #include "common/common_types.h" #include "core/hle/kernel/kernel.h" +namespace Service { +class SessionRequestHandler; +} + namespace Kernel { class ClientPort; @@ -19,10 +24,13 @@ public: * Creates a pair of ServerPort and an associated ClientPort. * @param max_sessions Maximum number of sessions to the port * @param name Optional name of the ports + * @param hle_handler Optional HLE handler template for the port, + * ServerSessions crated from this port will inherit a reference to this handler. * @return The created port tuple */ static std::tuple<SharedPtr<ServerPort>, SharedPtr<ClientPort>> CreatePortPair( - u32 max_sessions, std::string name = "UnknownPort"); + u32 max_sessions, std::string name = "UnknownPort", + std::shared_ptr<Service::SessionRequestHandler> hle_handler = nullptr); std::string GetTypeName() const override { return "ServerPort"; @@ -41,6 +49,10 @@ public: std::vector<SharedPtr<WaitObject>> pending_sessions; ///< ServerSessions waiting to be accepted by the port + /// This session's HLE request handler template (optional) + /// ServerSessions created from this port inherit a reference to this handler. + std::shared_ptr<Service::SessionRequestHandler> hle_handler; + bool ShouldWait() override; void Acquire() override; diff --git a/src/core/hle/kernel/server_session.cpp b/src/core/hle/kernel/server_session.cpp new file mode 100644 index 000000000..146458c1c --- /dev/null +++ b/src/core/hle/kernel/server_session.cpp @@ -0,0 +1,79 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <tuple> + +#include "core/hle/kernel/client_session.h" +#include "core/hle/kernel/server_session.h" +#include "core/hle/kernel/thread.h" + +namespace Kernel { + +ServerSession::ServerSession() = default; +ServerSession::~ServerSession() { + // This destructor will be called automatically when the last ServerSession handle is closed by + // the emulated application. + // TODO(Subv): Reduce the ClientPort's connection count, + // if the session is still open, set the connection status to 3 (Closed by server), +} + +ResultVal<SharedPtr<ServerSession>> ServerSession::Create( + std::string name, std::shared_ptr<Service::SessionRequestHandler> hle_handler) { + SharedPtr<ServerSession> server_session(new ServerSession); + + server_session->name = std::move(name); + server_session->signaled = false; + server_session->hle_handler = std::move(hle_handler); + + return MakeResult<SharedPtr<ServerSession>>(std::move(server_session)); +} + +bool ServerSession::ShouldWait() { + return !signaled; +} + +void ServerSession::Acquire() { + ASSERT_MSG(!ShouldWait(), "object unavailable!"); + signaled = false; +} + +ResultCode ServerSession::HandleSyncRequest() { + // The ServerSession received a sync request, this means that there's new data available + // from its ClientSession, so wake up any threads that may be waiting on a svcReplyAndReceive or + // similar. + + // If this ServerSession has an associated HLE handler, forward the request to it. + if (hle_handler != nullptr) { + // Attempt to translate the incoming request's command buffer. + ResultCode result = TranslateHLERequest(this); + if (result.IsError()) + return result; + hle_handler->HandleSyncRequest(SharedPtr<ServerSession>(this)); + // TODO(Subv): Translate the response command buffer. + } + + // If this ServerSession does not have an HLE implementation, just wake up the threads waiting + // on it. + signaled = true; + WakeupAllWaitingThreads(); + return RESULT_SUCCESS; +} + +ServerSession::SessionPair ServerSession::CreateSessionPair( + const std::string& name, std::shared_ptr<Service::SessionRequestHandler> hle_handler) { + auto server_session = + ServerSession::Create(name + "_Server", std::move(hle_handler)).MoveFrom(); + // We keep a non-owning pointer to the ServerSession in the ClientSession because we don't want + // to prevent the ServerSession's destructor from being called when the emulated + // application closes the last ServerSession handle. + auto client_session = ClientSession::Create(server_session.get(), name + "_Client").MoveFrom(); + + return std::make_tuple(std::move(server_session), std::move(client_session)); +} + +ResultCode TranslateHLERequest(ServerSession* server_session) { + // TODO(Subv): Implement this function once multiple concurrent processes are supported. + return RESULT_SUCCESS; +} +} diff --git a/src/core/hle/kernel/server_session.h b/src/core/hle/kernel/server_session.h new file mode 100644 index 000000000..458284a5d --- /dev/null +++ b/src/core/hle/kernel/server_session.h @@ -0,0 +1,94 @@ +// Copyright 2014 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <string> +#include "common/assert.h" +#include "common/common_types.h" +#include "core/hle/kernel/kernel.h" +#include "core/hle/kernel/thread.h" +#include "core/hle/result.h" +#include "core/hle/service/service.h" +#include "core/memory.h" + +namespace Kernel { + +class ClientSession; + +/** + * Kernel object representing the server endpoint of an IPC session. Sessions are the basic CTR-OS + * primitive for communication between different processes, and are used to implement service calls + * to the various system services. + * + * To make a service call, the client must write the command header and parameters to the buffer + * located at offset 0x80 of the TLS (Thread-Local Storage) area, then execute a SendSyncRequest + * SVC call with its ClientSession handle. The kernel will read the command header, using it to + * marshall the parameters to the process at the server endpoint of the session. + * After the server replies to the request, the response is marshalled back to the caller's + * TLS buffer and control is transferred back to it. + */ +class ServerSession final : public WaitObject { +public: + std::string GetTypeName() const override { + return "ServerSession"; + } + + static const HandleType HANDLE_TYPE = HandleType::ServerSession; + HandleType GetHandleType() const override { + return HANDLE_TYPE; + } + + using SessionPair = std::tuple<SharedPtr<ServerSession>, SharedPtr<ClientSession>>; + + /** + * Creates a pair of ServerSession and an associated ClientSession. + * @param name Optional name of the ports + * @return The created session tuple + */ + static SessionPair CreateSessionPair( + const std::string& name = "Unknown", + std::shared_ptr<Service::SessionRequestHandler> hle_handler = nullptr); + + /** + * Handle a sync request from the emulated application. + * @returns ResultCode from the operation. + */ + ResultCode HandleSyncRequest(); + + bool ShouldWait() override; + + void Acquire() override; + + std::string name; ///< The name of this session (optional) + bool signaled; ///< Whether there's new data available to this ServerSession + std::shared_ptr<Service::SessionRequestHandler> + hle_handler; ///< This session's HLE request handler (optional) + +private: + ServerSession(); + ~ServerSession() override; + + /** + * Creates a server session. The server session can have an optional HLE handler, + * which will be invoked to handle the IPC requests that this session receives. + * @param name Optional name of the server session. + * @param hle_handler Optional HLE handler for this server session. + * @return The created server session + */ + static ResultVal<SharedPtr<ServerSession>> Create( + std::string name = "Unknown", + std::shared_ptr<Service::SessionRequestHandler> hle_handler = nullptr); +}; + +/** + * Performs command buffer translation for an HLE IPC request. + * The command buffer from the ServerSession thread's TLS is copied into a + * buffer and all descriptors in the buffer are processed. + * TODO(Subv): Implement this function, currently we do not support multiple processes running at + * once, but once that is implemented we'll need to properly translate all descriptors + * in the command buffer. + */ +ResultCode TranslateHLERequest(ServerSession* server_session); +} diff --git a/src/core/hle/kernel/thread.cpp b/src/core/hle/kernel/thread.cpp index 84d6d24c6..4bbc08516 100644 --- a/src/core/hle/kernel/thread.cpp +++ b/src/core/hle/kernel/thread.cpp @@ -120,8 +120,6 @@ void Thread::Stop() { u32 tls_slot = ((tls_address - Memory::TLS_AREA_VADDR) % Memory::PAGE_SIZE) / Memory::TLS_ENTRY_SIZE; Kernel::g_current_process->tls_slots[tls_page].reset(tls_slot); - - HLE::Reschedule(__func__); } Thread* ArbitrateHighestPriorityThread(u32 address) { @@ -181,50 +179,6 @@ static void PriorityBoostStarvedThreads() { } /** - * Gets the registers for timeout parameter of the next WaitSynchronization call. - * @param thread a pointer to the thread that is ready to call WaitSynchronization - * @returns a tuple of two register pointers to low and high part of the timeout parameter - */ -static std::tuple<u32*, u32*> GetWaitSynchTimeoutParameterRegister(Thread* thread) { - bool thumb_mode = (thread->context.cpsr & TBIT) != 0; - u16 thumb_inst = Memory::Read16(thread->context.pc & 0xFFFFFFFE); - u32 inst = Memory::Read32(thread->context.pc & 0xFFFFFFFC) & 0x0FFFFFFF; - - if ((thumb_mode && thumb_inst == 0xDF24) || (!thumb_mode && inst == 0x0F000024)) { - // svc #0x24 (WaitSynchronization1) - return std::make_tuple(&thread->context.cpu_registers[2], - &thread->context.cpu_registers[3]); - } else if ((thumb_mode && thumb_inst == 0xDF25) || (!thumb_mode && inst == 0x0F000025)) { - // svc #0x25 (WaitSynchronizationN) - return std::make_tuple(&thread->context.cpu_registers[0], - &thread->context.cpu_registers[4]); - } - - UNREACHABLE(); -} - -/** - * Updates the WaitSynchronization timeout parameter according to the difference - * between ticks of the last WaitSynchronization call and the incoming one. - * @param timeout_low a pointer to the register for the low part of the timeout parameter - * @param timeout_high a pointer to the register for the high part of the timeout parameter - * @param last_tick tick of the last WaitSynchronization call - */ -static void UpdateTimeoutParameter(u32* timeout_low, u32* timeout_high, u64 last_tick) { - s64 timeout = ((s64)*timeout_high << 32) | *timeout_low; - - if (timeout != -1) { - timeout -= cyclesToUs(CoreTiming::GetTicks() - last_tick) * 1000; // in nanoseconds - - if (timeout < 0) - timeout = 0; - - *timeout_low = timeout & 0xFFFFFFFF; - *timeout_high = timeout >> 32; - } -} - -/** * Switches the CPU's active thread context to that of the specified thread * @param new_thread The thread to switch to */ @@ -254,32 +208,6 @@ static void SwitchContext(Thread* new_thread) { current_thread = new_thread; - // If the thread was waited by a svcWaitSynch call, step back PC by one instruction to rerun - // the SVC when the thread wakes up. This is necessary to ensure that the thread can acquire - // the requested wait object(s) before continuing. - if (new_thread->waitsynch_waited) { - // CPSR flag indicates CPU mode - bool thumb_mode = (new_thread->context.cpsr & TBIT) != 0; - - // SVC instruction is 2 bytes for THUMB, 4 bytes for ARM - new_thread->context.pc -= thumb_mode ? 2 : 4; - - // Get the register for timeout parameter - u32 *timeout_low, *timeout_high; - std::tie(timeout_low, timeout_high) = GetWaitSynchTimeoutParameterRegister(new_thread); - - // Update the timeout parameter - UpdateTimeoutParameter(timeout_low, timeout_high, new_thread->last_running_ticks); - } - - // Clean up the thread's wait_objects, they'll be restored if needed during - // the svcWaitSynchronization call - for (size_t i = 0; i < new_thread->wait_objects.size(); ++i) { - SharedPtr<WaitObject> object = new_thread->wait_objects[i]; - object->RemoveWaitingThread(new_thread); - } - new_thread->wait_objects.clear(); - ready_queue.remove(new_thread->current_priority, new_thread); new_thread->status = THREADSTATUS_RUNNING; @@ -319,17 +247,13 @@ static Thread* PopNextReadyThread() { void WaitCurrentThread_Sleep() { Thread* thread = GetCurrentThread(); thread->status = THREADSTATUS_WAIT_SLEEP; - - HLE::Reschedule(__func__); } void WaitCurrentThread_WaitSynchronization(std::vector<SharedPtr<WaitObject>> wait_objects, - bool wait_set_output, bool wait_all) { + bool wait_set_output) { Thread* thread = GetCurrentThread(); thread->wait_set_output = wait_set_output; - thread->wait_all = wait_all; thread->wait_objects = std::move(wait_objects); - thread->waitsynch_waited = true; thread->status = THREADSTATUS_WAIT_SYNCH; } @@ -351,15 +275,15 @@ static void ThreadWakeupCallback(u64 thread_handle, int cycles_late) { return; } - thread->waitsynch_waited = false; - if (thread->status == THREADSTATUS_WAIT_SYNCH || thread->status == THREADSTATUS_WAIT_ARB) { + thread->wait_set_output = false; + // Remove the thread from each of its waiting objects' waitlists + for (auto& object : thread->wait_objects) + object->RemoveWaitingThread(thread.get()); + thread->wait_objects.clear(); thread->SetWaitSynchronizationResult(ResultCode(ErrorDescription::Timeout, ErrorModule::OS, ErrorSummary::StatusChanged, ErrorLevel::Info)); - - if (thread->wait_set_output) - thread->SetWaitSynchronizationOutput(-1); } thread->ResumeFromWait(); @@ -399,6 +323,7 @@ void Thread::ResumeFromWait() { ready_queue.push_back(current_priority, this); status = THREADSTATUS_READY; + HLE::Reschedule(__func__); } /** @@ -494,13 +419,11 @@ ResultVal<SharedPtr<Thread>> Thread::Create(std::string name, VAddr entry_point, thread->last_running_ticks = CoreTiming::GetTicks(); thread->processor_id = processor_id; thread->wait_set_output = false; - thread->wait_all = false; thread->wait_objects.clear(); thread->wait_address = 0; thread->name = std::move(name); thread->callback_handle = wakeup_callback_handle_table.Create(thread).MoveFrom(); thread->owner_process = g_current_process; - thread->waitsynch_waited = false; // Find the next available TLS index, and mark it as used auto& tls_slots = Kernel::g_current_process->tls_slots; @@ -555,8 +478,6 @@ ResultVal<SharedPtr<Thread>> Thread::Create(std::string name, VAddr entry_point, ready_queue.push_back(thread->current_priority, thread.get()); thread->status = THREADSTATUS_READY; - HLE::Reschedule(__func__); - return MakeResult<SharedPtr<Thread>>(std::move(thread)); } @@ -619,14 +540,6 @@ void Reschedule() { HLE::DoneRescheduling(); - // Don't bother switching to the same thread. - // But if the thread was waiting on objects, we still need to switch it - // to perform PC modification, change state to RUNNING, etc. - // This occurs in the case when an object the thread is waiting on immediately wakes up - // the current thread before Reschedule() is called. - if (next == cur && (next == nullptr || next->waitsynch_waited == false)) - return; - if (cur && next) { LOG_TRACE(Kernel, "context switch %u -> %u", cur->GetObjectId(), next->GetObjectId()); } else if (cur) { diff --git a/src/core/hle/kernel/thread.h b/src/core/hle/kernel/thread.h index e0ffcea8a..238359fc5 100644 --- a/src/core/hle/kernel/thread.h +++ b/src/core/hle/kernel/thread.h @@ -5,7 +5,9 @@ #pragma once #include <string> +#include <unordered_map> #include <vector> +#include <boost/container/flat_map.hpp> #include <boost/container/flat_set.hpp> #include "common/common_types.h" #include "core/core.h" @@ -125,6 +127,16 @@ public: void SetWaitSynchronizationOutput(s32 output); /** + * Retrieves the index that this particular object occupies in the list of objects + * that the thread passed to WaitSynchronizationN. + * It is used to set the output value of WaitSynchronizationN when the thread is awakened. + * @param object Object to query the index of. + */ + s32 GetWaitObjectIndex(const WaitObject* object) const { + return wait_objects_index.at(object->GetObjectId()); + } + + /** * Stops a thread, invalidating it from further use */ void Stop(); @@ -137,6 +149,15 @@ public: return tls_address; } + /** + * Returns whether this thread is waiting for all the objects in + * its wait list to become ready, as a result of a WaitSynchronizationN call + * with wait_all = true, or a ReplyAndReceive call. + */ + bool IsSleepingOnWaitAll() const { + return !wait_objects.empty(); + } + Core::ThreadContext context; u32 thread_id; @@ -154,16 +175,22 @@ public: VAddr tls_address; ///< Virtual address of the Thread Local Storage of the thread - bool waitsynch_waited; ///< Set to true if the last svcWaitSynch call caused the thread to wait - /// Mutexes currently held by this thread, which will be released when it exits. boost::container::flat_set<SharedPtr<Mutex>> held_mutexes; - SharedPtr<Process> owner_process; ///< Process that owns this thread - std::vector<SharedPtr<WaitObject>> wait_objects; ///< Objects that the thread is waiting on - VAddr wait_address; ///< If waiting on an AddressArbiter, this is the arbitration address - bool wait_all; ///< True if the thread is waiting on all objects before resuming - bool wait_set_output; ///< True if the output parameter should be set on thread wakeup + SharedPtr<Process> owner_process; ///< Process that owns this thread + + /// Objects that the thread is waiting on. + /// This is only populated when the thread should wait for all the objects to become ready. + std::vector<SharedPtr<WaitObject>> wait_objects; + + /// Mapping of Object ids to their position in the last waitlist that this object waited on. + boost::container::flat_map<int, s32> wait_objects_index; + + VAddr wait_address; ///< If waiting on an AddressArbiter, this is the arbitration address + + /// True if the WaitSynchronizationN output parameter should be set on thread wakeup. + bool wait_set_output; std::string name; @@ -215,10 +242,9 @@ void WaitCurrentThread_Sleep(); * @param wait_objects Kernel objects that we are waiting on * @param wait_set_output If true, set the output parameter on thread wakeup (for * WaitSynchronizationN only) - * @param wait_all If true, wait on all objects before resuming (for WaitSynchronizationN only) */ void WaitCurrentThread_WaitSynchronization(std::vector<SharedPtr<WaitObject>> wait_objects, - bool wait_set_output, bool wait_all); + bool wait_set_output); /** * Waits the current thread from an ArbitrateAddress call diff --git a/src/core/hle/kernel/timer.cpp b/src/core/hle/kernel/timer.cpp index eac181f4e..b50cf520d 100644 --- a/src/core/hle/kernel/timer.cpp +++ b/src/core/hle/kernel/timer.cpp @@ -60,14 +60,10 @@ void Timer::Set(s64 initial, s64 interval) { u64 initial_microseconds = initial / 1000; CoreTiming::ScheduleEvent(usToCycles(initial_microseconds), timer_callback_event_type, callback_handle); - - HLE::Reschedule(__func__); } void Timer::Cancel() { CoreTiming::UnscheduleEvent(timer_callback_event_type, callback_handle); - - HLE::Reschedule(__func__); } void Timer::Clear() { diff --git a/src/core/hle/result.h b/src/core/hle/result.h index 8d29117a8..53864a3a7 100644 --- a/src/core/hle/result.h +++ b/src/core/hle/result.h @@ -18,6 +18,7 @@ enum class ErrorDescription : u32 { Success = 0, WrongPermission = 46, OS_InvalidBufferDescriptor = 48, + MaxConnectionsReached = 52, WrongAddress = 53, FS_ArchiveNotMounted = 101, FS_FileNotFound = 112, diff --git a/src/core/hle/service/act/act.cpp b/src/core/hle/service/act/act.cpp new file mode 100644 index 000000000..9600036c0 --- /dev/null +++ b/src/core/hle/service/act/act.cpp @@ -0,0 +1,18 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "core/hle/service/act/act.h" +#include "core/hle/service/act/act_a.h" +#include "core/hle/service/act/act_u.h" + +namespace Service { +namespace ACT { + +void Init() { + AddService(new ACT_A); + AddService(new ACT_U); +} + +} // namespace ACT +} // namespace Service diff --git a/src/core/hle/service/act/act.h b/src/core/hle/service/act/act.h new file mode 100644 index 000000000..1425291aa --- /dev/null +++ b/src/core/hle/service/act/act.h @@ -0,0 +1,14 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +namespace Service { +namespace ACT { + +/// Initializes all ACT services +void Init(); + +} // namespace ACT +} // namespace Service diff --git a/src/core/hle/service/act_a.cpp b/src/core/hle/service/act/act_a.cpp index 9880aafff..5c523368f 100644 --- a/src/core/hle/service/act_a.cpp +++ b/src/core/hle/service/act/act_a.cpp @@ -2,7 +2,8 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -#include "core/hle/service/act_a.h" +#include "core/hle/service/act/act.h" +#include "core/hle/service/act/act_a.h" namespace Service { namespace ACT { diff --git a/src/core/hle/service/act_a.h b/src/core/hle/service/act/act_a.h index e3adb03e5..e3adb03e5 100644 --- a/src/core/hle/service/act_a.h +++ b/src/core/hle/service/act/act_a.h diff --git a/src/core/hle/service/act_u.cpp b/src/core/hle/service/act/act_u.cpp index b4f69c57d..cf98aa1d6 100644 --- a/src/core/hle/service/act_u.cpp +++ b/src/core/hle/service/act/act_u.cpp @@ -2,7 +2,8 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -#include "core/hle/service/act_u.h" +#include "core/hle/service/act/act.h" +#include "core/hle/service/act/act_u.h" namespace Service { namespace ACT { diff --git a/src/core/hle/service/act_u.h b/src/core/hle/service/act/act_u.h index 9d8538fbf..9d8538fbf 100644 --- a/src/core/hle/service/act_u.h +++ b/src/core/hle/service/act/act_u.h diff --git a/src/core/hle/service/apt/apt.cpp b/src/core/hle/service/apt/apt.cpp index 31e5e07b2..615fe31ea 100644 --- a/src/core/hle/service/apt/apt.cpp +++ b/src/core/hle/service/apt/apt.cpp @@ -458,12 +458,16 @@ void GetStartupArgument(Service::Interface* self) { return; } - LOG_WARNING(Service_APT, "(stubbed) called startup_argument_type=%u , parameter_size=0x%08x , " - "parameter_value=0x%08x", - startup_argument_type, parameter_size, Memory::Read32(cmd_buff[41])); + u32 addr = cmd_buff[65]; + if (addr && parameter_size) { + Memory::ZeroBlock(addr, parameter_size); + } + + LOG_WARNING(Service_APT, "(stubbed) called startup_argument_type=%u , parameter_size=0x%08x", + startup_argument_type, parameter_size); cmd_buff[1] = RESULT_SUCCESS.raw; - cmd_buff[2] = (parameter_size > 0) ? 1 : 0; + cmd_buff[2] = 0; } void CheckNew3DSApp(Service::Interface* self) { diff --git a/src/core/hle/service/apt/apt.h b/src/core/hle/service/apt/apt.h index 44dbd8757..80325361f 100644 --- a/src/core/hle/service/apt/apt.h +++ b/src/core/hle/service/apt/apt.h @@ -14,6 +14,9 @@ class Interface; namespace APT { +/// Each APT service can only have up to 2 sessions connected at the same time. +static const u32 MaxAPTSessions = 2; + /// Holds information about the parameters used in Send/Glance/ReceiveParameter struct MessageParameter { u32 sender_id = 0; @@ -407,9 +410,11 @@ void CancelLibraryApplet(Service::Interface* self); * Inputs: * 1 : Parameter Size (capped to 0x300) * 2 : StartupArgumentType + * 65 : Output buffer for startup argument * Outputs: * 0 : Return header - * 1 : u8, Exists (0 = does not exist, 1 = exists) + * 1 : Result of function, 0 on success, otherwise error code + * 2 : u8, Exists (0 = does not exist, 1 = exists) */ void GetStartupArgument(Service::Interface* self); diff --git a/src/core/hle/service/apt/apt_a.cpp b/src/core/hle/service/apt/apt_a.cpp index 6e35e1d29..62dc2d61d 100644 --- a/src/core/hle/service/apt/apt_a.cpp +++ b/src/core/hle/service/apt/apt_a.cpp @@ -102,7 +102,7 @@ const Interface::FunctionInfo FunctionTable[] = { {0x01050100, nullptr, "IsTitleAllowed"}, }; -APT_A_Interface::APT_A_Interface() { +APT_A_Interface::APT_A_Interface() : Interface(MaxAPTSessions) { Register(FunctionTable); } diff --git a/src/core/hle/service/apt/apt_s.cpp b/src/core/hle/service/apt/apt_s.cpp index 84019e6e5..effd23dce 100644 --- a/src/core/hle/service/apt/apt_s.cpp +++ b/src/core/hle/service/apt/apt_s.cpp @@ -102,7 +102,7 @@ const Interface::FunctionInfo FunctionTable[] = { {0x01050100, nullptr, "IsTitleAllowed"}, }; -APT_S_Interface::APT_S_Interface() { +APT_S_Interface::APT_S_Interface() : Interface(MaxAPTSessions) { Register(FunctionTable); } diff --git a/src/core/hle/service/apt/apt_u.cpp b/src/core/hle/service/apt/apt_u.cpp index a731c39f6..e06084a1e 100644 --- a/src/core/hle/service/apt/apt_u.cpp +++ b/src/core/hle/service/apt/apt_u.cpp @@ -99,7 +99,7 @@ const Interface::FunctionInfo FunctionTable[] = { {0x01020000, CheckNew3DS, "CheckNew3DS"}, }; -APT_U_Interface::APT_U_Interface() { +APT_U_Interface::APT_U_Interface() : Interface(MaxAPTSessions) { Register(FunctionTable); } diff --git a/src/core/hle/service/cecd/cecd.cpp b/src/core/hle/service/cecd/cecd.cpp index 515b344e6..eb04273db 100644 --- a/src/core/hle/service/cecd/cecd.cpp +++ b/src/core/hle/service/cecd/cecd.cpp @@ -5,6 +5,7 @@ #include "common/logging/log.h" #include "core/hle/kernel/event.h" #include "core/hle/service/cecd/cecd.h" +#include "core/hle/service/cecd/cecd_ndm.h" #include "core/hle/service/cecd/cecd_s.h" #include "core/hle/service/cecd/cecd_u.h" #include "core/hle/service/service.h" @@ -43,12 +44,13 @@ void GetChangeStateEventHandle(Service::Interface* self) { } void Init() { - AddService(new CECD_S_Interface); - AddService(new CECD_U_Interface); + AddService(new CECD_NDM); + AddService(new CECD_S); + AddService(new CECD_U); - cecinfo_event = Kernel::Event::Create(Kernel::ResetType::OneShot, "CECD_U::cecinfo_event"); + cecinfo_event = Kernel::Event::Create(Kernel::ResetType::OneShot, "CECD::cecinfo_event"); change_state_event = - Kernel::Event::Create(Kernel::ResetType::OneShot, "CECD_U::change_state_event"); + Kernel::Event::Create(Kernel::ResetType::OneShot, "CECD::change_state_event"); } void Shutdown() { diff --git a/src/core/hle/service/cecd/cecd_ndm.cpp b/src/core/hle/service/cecd/cecd_ndm.cpp new file mode 100644 index 000000000..7baf93750 --- /dev/null +++ b/src/core/hle/service/cecd/cecd_ndm.cpp @@ -0,0 +1,23 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "core/hle/service/cecd/cecd.h" +#include "core/hle/service/cecd/cecd_ndm.h" + +namespace Service { +namespace CECD { + +static const Interface::FunctionInfo FunctionTable[] = { + {0x00010000, nullptr, "Initialize"}, + {0x00020000, nullptr, "Deinitialize"}, + {0x00030000, nullptr, "ResumeDaemon"}, + {0x00040040, nullptr, "SuspendDaemon"}, +}; + +CECD_NDM::CECD_NDM() { + Register(FunctionTable); +} + +} // namespace CECD +} // namespace Service diff --git a/src/core/hle/service/cecd/cecd_ndm.h b/src/core/hle/service/cecd/cecd_ndm.h new file mode 100644 index 000000000..2e2e50ada --- /dev/null +++ b/src/core/hle/service/cecd/cecd_ndm.h @@ -0,0 +1,22 @@ +// Copyright 2016 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 Service { +namespace CECD { + +class CECD_NDM : public Interface { +public: + CECD_NDM(); + + std::string GetPortName() const override { + return "cecd:ndm"; + } +}; + +} // namespace CECD +} // namespace Service diff --git a/src/core/hle/service/cecd/cecd_s.cpp b/src/core/hle/service/cecd/cecd_s.cpp index 7477b9320..eacda7d41 100644 --- a/src/core/hle/service/cecd/cecd_s.cpp +++ b/src/core/hle/service/cecd/cecd_s.cpp @@ -2,16 +2,34 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include "core/hle/service/cecd/cecd.h" #include "core/hle/service/cecd/cecd_s.h" namespace Service { namespace CECD { -// Empty arrays are illegal -- commented out until an entry is added. -// const Interface::FunctionInfo FunctionTable[] = { }; +static const Interface::FunctionInfo FunctionTable[] = { + // cecd:u shared commands + {0x000100C2, nullptr, "OpenRawFile"}, + {0x00020042, nullptr, "ReadRawFile"}, + {0x00030104, nullptr, "ReadMessage"}, + {0x00040106, nullptr, "ReadMessageWithHMAC"}, + {0x00050042, nullptr, "WriteRawFile"}, + {0x00060104, nullptr, "WriteMessage"}, + {0x00070106, nullptr, "WriteMessageWithHMAC"}, + {0x00080102, nullptr, "Delete"}, + {0x000A00C4, nullptr, "GetSystemInfo"}, + {0x000B0040, nullptr, "RunCommand"}, + {0x000C0040, nullptr, "RunCommandAlt"}, + {0x000E0000, GetCecStateAbbreviated, "GetCecStateAbbreviated"}, + {0x000F0000, GetCecInfoEventHandle, "GetCecInfoEventHandle"}, + {0x00100000, GetChangeStateEventHandle, "GetChangeStateEventHandle"}, + {0x00110104, nullptr, "OpenAndWrite"}, + {0x00120104, nullptr, "OpenAndRead"}, +}; -CECD_S_Interface::CECD_S_Interface() { - // Register(FunctionTable); +CECD_S::CECD_S() { + Register(FunctionTable); } } // namespace CECD diff --git a/src/core/hle/service/cecd/cecd_s.h b/src/core/hle/service/cecd/cecd_s.h index df5c01849..ab6c6789a 100644 --- a/src/core/hle/service/cecd/cecd_s.h +++ b/src/core/hle/service/cecd/cecd_s.h @@ -9,9 +9,9 @@ namespace Service { namespace CECD { -class CECD_S_Interface : public Interface { +class CECD_S : public Interface { public: - CECD_S_Interface(); + CECD_S(); std::string GetPortName() const override { return "cecd:s"; diff --git a/src/core/hle/service/cecd/cecd_u.cpp b/src/core/hle/service/cecd/cecd_u.cpp index 7d98ba6e9..3ed864f0b 100644 --- a/src/core/hle/service/cecd/cecd_u.cpp +++ b/src/core/hle/service/cecd/cecd_u.cpp @@ -9,6 +9,7 @@ namespace Service { namespace CECD { static const Interface::FunctionInfo FunctionTable[] = { + // cecd:u shared commands {0x000100C2, nullptr, "OpenRawFile"}, {0x00020042, nullptr, "ReadRawFile"}, {0x00030104, nullptr, "ReadMessage"}, @@ -27,7 +28,7 @@ static const Interface::FunctionInfo FunctionTable[] = { {0x00120104, nullptr, "OpenAndRead"}, }; -CECD_U_Interface::CECD_U_Interface() { +CECD_U::CECD_U() { Register(FunctionTable); } diff --git a/src/core/hle/service/cecd/cecd_u.h b/src/core/hle/service/cecd/cecd_u.h index 394030ffc..16e874ff5 100644 --- a/src/core/hle/service/cecd/cecd_u.h +++ b/src/core/hle/service/cecd/cecd_u.h @@ -9,9 +9,9 @@ namespace Service { namespace CECD { -class CECD_U_Interface : public Interface { +class CECD_U : public Interface { public: - CECD_U_Interface(); + CECD_U(); std::string GetPortName() const override { return "cecd:u"; diff --git a/src/core/hle/service/dlp/dlp.h b/src/core/hle/service/dlp/dlp.h index ec2fe46e8..3185fe322 100644 --- a/src/core/hle/service/dlp/dlp.h +++ b/src/core/hle/service/dlp/dlp.h @@ -2,6 +2,8 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#pragma once + namespace Service { namespace DLP { diff --git a/src/core/hle/service/fs/archive.cpp b/src/core/hle/service/fs/archive.cpp index bef75f5df..09205e4b2 100644 --- a/src/core/hle/service/fs/archive.cpp +++ b/src/core/hle/service/fs/archive.cpp @@ -24,6 +24,7 @@ #include "core/file_sys/directory_backend.h" #include "core/file_sys/file_backend.h" #include "core/hle/hle.h" +#include "core/hle/kernel/client_session.h" #include "core/hle/result.h" #include "core/hle/service/fs/archive.h" #include "core/hle/service/fs/fs_user.h" @@ -93,7 +94,7 @@ File::File(std::unique_ptr<FileSys::FileBackend>&& backend, const FileSys::Path& File::~File() {} -ResultVal<bool> File::SyncRequest() { +void File::HandleSyncRequest(Kernel::SharedPtr<Kernel::ServerSession> server_session) { u32* cmd_buff = Kernel::GetCommandBuffer(); FileCommand cmd = static_cast<FileCommand>(cmd_buff[0]); switch (cmd) { @@ -103,8 +104,8 @@ ResultVal<bool> File::SyncRequest() { u64 offset = cmd_buff[1] | ((u64)cmd_buff[2]) << 32; u32 length = cmd_buff[3]; u32 address = cmd_buff[5]; - LOG_TRACE(Service_FS, "Read %s %s: offset=0x%llx length=%d address=0x%x", - GetTypeName().c_str(), GetName().c_str(), offset, length, address); + LOG_TRACE(Service_FS, "Read %s: offset=0x%llx length=%d address=0x%x", GetName().c_str(), + offset, length, address); if (offset + length > backend->GetSize()) { LOG_ERROR(Service_FS, @@ -116,7 +117,7 @@ ResultVal<bool> File::SyncRequest() { ResultVal<size_t> read = backend->Read(offset, data.size(), data.data()); if (read.Failed()) { cmd_buff[1] = read.Code().raw; - return read.Code(); + return; } Memory::WriteBlock(address, data.data(), *read); cmd_buff[2] = static_cast<u32>(*read); @@ -129,22 +130,22 @@ ResultVal<bool> File::SyncRequest() { u32 length = cmd_buff[3]; u32 flush = cmd_buff[4]; u32 address = cmd_buff[6]; - LOG_TRACE(Service_FS, "Write %s %s: offset=0x%llx length=%d address=0x%x, flush=0x%x", - GetTypeName().c_str(), GetName().c_str(), offset, length, address, flush); + LOG_TRACE(Service_FS, "Write %s: offset=0x%llx length=%d address=0x%x, flush=0x%x", + GetName().c_str(), offset, length, address, flush); std::vector<u8> data(length); Memory::ReadBlock(address, data.data(), data.size()); ResultVal<size_t> written = backend->Write(offset, data.size(), flush != 0, data.data()); if (written.Failed()) { cmd_buff[1] = written.Code().raw; - return written.Code(); + return; } cmd_buff[2] = static_cast<u32>(*written); break; } case FileCommand::GetSize: { - LOG_TRACE(Service_FS, "GetSize %s %s", GetTypeName().c_str(), GetName().c_str()); + LOG_TRACE(Service_FS, "GetSize %s", GetName().c_str()); u64 size = backend->GetSize(); cmd_buff[2] = (u32)size; cmd_buff[3] = size >> 32; @@ -153,14 +154,13 @@ ResultVal<bool> File::SyncRequest() { case FileCommand::SetSize: { u64 size = cmd_buff[1] | ((u64)cmd_buff[2] << 32); - LOG_TRACE(Service_FS, "SetSize %s %s size=%llu", GetTypeName().c_str(), GetName().c_str(), - size); + LOG_TRACE(Service_FS, "SetSize %s size=%llu", GetName().c_str(), size); backend->SetSize(size); break; } case FileCommand::Close: { - LOG_TRACE(Service_FS, "Close %s %s", GetTypeName().c_str(), GetName().c_str()); + LOG_TRACE(Service_FS, "Close %s", GetName().c_str()); backend->Close(); break; } @@ -173,7 +173,11 @@ ResultVal<bool> File::SyncRequest() { case FileCommand::OpenLinkFile: { LOG_WARNING(Service_FS, "(STUBBED) File command OpenLinkFile %s", GetName().c_str()); - cmd_buff[3] = Kernel::g_handle_table.Create(this).ValueOr(INVALID_HANDLE); + auto sessions = Kernel::ServerSession::CreateSessionPair(GetName(), shared_from_this()); + ClientConnected(std::get<Kernel::SharedPtr<Kernel::ServerSession>>(sessions)); + cmd_buff[3] = Kernel::g_handle_table + .Create(std::get<Kernel::SharedPtr<Kernel::ClientSession>>(sessions)) + .ValueOr(INVALID_HANDLE); break; } @@ -194,10 +198,9 @@ ResultVal<bool> File::SyncRequest() { LOG_ERROR(Service_FS, "Unknown command=0x%08X!", cmd); ResultCode error = UnimplementedFunction(ErrorModule::FS); cmd_buff[1] = error.raw; // TODO(Link Mauve): use the correct error code for that. - return error; + return; } cmd_buff[1] = RESULT_SUCCESS.raw; // No error - return MakeResult<bool>(false); } Directory::Directory(std::unique_ptr<FileSys::DirectoryBackend>&& backend, @@ -206,18 +209,16 @@ Directory::Directory(std::unique_ptr<FileSys::DirectoryBackend>&& backend, Directory::~Directory() {} -ResultVal<bool> Directory::SyncRequest() { +void Directory::HandleSyncRequest(Kernel::SharedPtr<Kernel::ServerSession> server_session) { u32* cmd_buff = Kernel::GetCommandBuffer(); DirectoryCommand cmd = static_cast<DirectoryCommand>(cmd_buff[0]); switch (cmd) { - // Read from directory... case DirectoryCommand::Read: { u32 count = cmd_buff[1]; u32 address = cmd_buff[3]; std::vector<FileSys::Entry> entries(count); - LOG_TRACE(Service_FS, "Read %s %s: count=%d", GetTypeName().c_str(), GetName().c_str(), - count); + LOG_TRACE(Service_FS, "Read %s: count=%d", GetName().c_str(), count); // Number of entries actually read u32 read = backend->Read(entries.size(), entries.data()); @@ -227,7 +228,7 @@ ResultVal<bool> Directory::SyncRequest() { } case DirectoryCommand::Close: { - LOG_TRACE(Service_FS, "Close %s %s", GetTypeName().c_str(), GetName().c_str()); + LOG_TRACE(Service_FS, "Close %s", GetName().c_str()); backend->Close(); break; } @@ -237,10 +238,9 @@ ResultVal<bool> Directory::SyncRequest() { LOG_ERROR(Service_FS, "Unknown command=0x%08X!", cmd); ResultCode error = UnimplementedFunction(ErrorModule::FS); cmd_buff[1] = error.raw; // TODO(Link Mauve): use the correct error code for that. - return MakeResult<bool>(false); + return; } cmd_buff[1] = RESULT_SUCCESS.raw; // No error - return MakeResult<bool>(false); } //////////////////////////////////////////////////////////////////////////////////////////////////// @@ -307,9 +307,9 @@ ResultCode RegisterArchiveType(std::unique_ptr<FileSys::ArchiveFactory>&& factor return RESULT_SUCCESS; } -ResultVal<Kernel::SharedPtr<File>> OpenFileFromArchive(ArchiveHandle archive_handle, - const FileSys::Path& path, - const FileSys::Mode mode) { +ResultVal<std::shared_ptr<File>> OpenFileFromArchive(ArchiveHandle archive_handle, + const FileSys::Path& path, + const FileSys::Mode mode) { ArchiveBackend* archive = GetArchive(archive_handle); if (archive == nullptr) return ERR_INVALID_ARCHIVE_HANDLE; @@ -318,8 +318,8 @@ ResultVal<Kernel::SharedPtr<File>> OpenFileFromArchive(ArchiveHandle archive_han if (backend.Failed()) return backend.Code(); - auto file = Kernel::SharedPtr<File>(new File(backend.MoveFrom(), path)); - return MakeResult<Kernel::SharedPtr<File>>(std::move(file)); + auto file = std::shared_ptr<File>(new File(backend.MoveFrom(), path)); + return MakeResult<std::shared_ptr<File>>(std::move(file)); } ResultCode DeleteFileFromArchive(ArchiveHandle archive_handle, const FileSys::Path& path) { @@ -398,8 +398,8 @@ ResultCode RenameDirectoryBetweenArchives(ArchiveHandle src_archive_handle, } } -ResultVal<Kernel::SharedPtr<Directory>> OpenDirectoryFromArchive(ArchiveHandle archive_handle, - const FileSys::Path& path) { +ResultVal<std::shared_ptr<Directory>> OpenDirectoryFromArchive(ArchiveHandle archive_handle, + const FileSys::Path& path) { ArchiveBackend* archive = GetArchive(archive_handle); if (archive == nullptr) return ERR_INVALID_ARCHIVE_HANDLE; @@ -408,8 +408,8 @@ ResultVal<Kernel::SharedPtr<Directory>> OpenDirectoryFromArchive(ArchiveHandle a if (backend.Failed()) return backend.Code(); - auto directory = Kernel::SharedPtr<Directory>(new Directory(backend.MoveFrom(), path)); - return MakeResult<Kernel::SharedPtr<Directory>>(std::move(directory)); + auto directory = std::shared_ptr<Directory>(new Directory(backend.MoveFrom(), path)); + return MakeResult<std::shared_ptr<Directory>>(std::move(directory)); } ResultVal<u64> GetFreeBytesInArchive(ArchiveHandle archive_handle) { diff --git a/src/core/hle/service/fs/archive.h b/src/core/hle/service/fs/archive.h index 87089bd92..7ba62ede0 100644 --- a/src/core/hle/service/fs/archive.h +++ b/src/core/hle/service/fs/archive.h @@ -8,7 +8,7 @@ #include <string> #include "common/common_types.h" #include "core/file_sys/archive_backend.h" -#include "core/hle/kernel/session.h" +#include "core/hle/kernel/server_session.h" #include "core/hle/result.h" namespace FileSys { @@ -43,33 +43,37 @@ enum class MediaType : u32 { NAND = 0, SDMC = 1, GameCard = 2 }; typedef u64 ArchiveHandle; -class File : public Kernel::Session { +class File final : public SessionRequestHandler, public std::enable_shared_from_this<File> { public: File(std::unique_ptr<FileSys::FileBackend>&& backend, const FileSys::Path& path); ~File(); - std::string GetName() const override { + std::string GetName() const { return "Path: " + path.DebugStr(); } - ResultVal<bool> SyncRequest() override; FileSys::Path path; ///< Path of the file u32 priority; ///< Priority of the file. TODO(Subv): Find out what this means std::unique_ptr<FileSys::FileBackend> backend; ///< File backend interface + +protected: + void HandleSyncRequest(Kernel::SharedPtr<Kernel::ServerSession> server_session) override; }; -class Directory : public Kernel::Session { +class Directory final : public SessionRequestHandler { public: Directory(std::unique_ptr<FileSys::DirectoryBackend>&& backend, const FileSys::Path& path); ~Directory(); - std::string GetName() const override { + std::string GetName() const { return "Directory: " + path.DebugStr(); } - ResultVal<bool> SyncRequest() override; FileSys::Path path; ///< Path of the directory std::unique_ptr<FileSys::DirectoryBackend> backend; ///< File backend interface + +protected: + void HandleSyncRequest(Kernel::SharedPtr<Kernel::ServerSession> server_session) override; }; /** @@ -99,11 +103,11 @@ ResultCode RegisterArchiveType(std::unique_ptr<FileSys::ArchiveFactory>&& factor * @param archive_handle Handle to an open Archive object * @param path Path to the File inside of the Archive * @param mode Mode under which to open the File - * @return The opened File object as a Session + * @return The opened File object */ -ResultVal<Kernel::SharedPtr<File>> OpenFileFromArchive(ArchiveHandle archive_handle, - const FileSys::Path& path, - const FileSys::Mode mode); +ResultVal<std::shared_ptr<File>> OpenFileFromArchive(ArchiveHandle archive_handle, + const FileSys::Path& path, + const FileSys::Mode mode); /** * Delete a File from an Archive @@ -178,10 +182,10 @@ ResultCode RenameDirectoryBetweenArchives(ArchiveHandle src_archive_handle, * Open a Directory from an Archive * @param archive_handle Handle to an open Archive object * @param path Path to the Directory inside of the Archive - * @return The opened Directory object as a Session + * @return The opened Directory object */ -ResultVal<Kernel::SharedPtr<Directory>> OpenDirectoryFromArchive(ArchiveHandle archive_handle, - const FileSys::Path& path); +ResultVal<std::shared_ptr<Directory>> OpenDirectoryFromArchive(ArchiveHandle archive_handle, + const FileSys::Path& path); /** * Get the free space in an Archive diff --git a/src/core/hle/service/fs/fs_user.cpp b/src/core/hle/service/fs/fs_user.cpp index d6ab5b065..337da1387 100644 --- a/src/core/hle/service/fs/fs_user.cpp +++ b/src/core/hle/service/fs/fs_user.cpp @@ -8,6 +8,7 @@ #include "common/logging/log.h" #include "common/scope_exit.h" #include "common/string_util.h" +#include "core/hle/kernel/client_session.h" #include "core/hle/result.h" #include "core/hle/service/fs/archive.h" #include "core/hle/service/fs/fs_user.h" @@ -17,7 +18,7 @@ // Namespace FS_User using Kernel::SharedPtr; -using Kernel::Session; +using Kernel::ServerSession; namespace Service { namespace FS { @@ -67,10 +68,16 @@ static void OpenFile(Service::Interface* self) { LOG_DEBUG(Service_FS, "path=%s, mode=%u attrs=%u", file_path.DebugStr().c_str(), mode.hex, attributes); - ResultVal<SharedPtr<File>> file_res = OpenFileFromArchive(archive_handle, file_path, mode); + ResultVal<std::shared_ptr<File>> file_res = + OpenFileFromArchive(archive_handle, file_path, mode); cmd_buff[1] = file_res.Code().raw; if (file_res.Succeeded()) { - cmd_buff[3] = Kernel::g_handle_table.Create(*file_res).MoveFrom(); + std::shared_ptr<File> file = *file_res; + auto sessions = ServerSession::CreateSessionPair(file->GetName(), file); + file->ClientConnected(std::get<Kernel::SharedPtr<Kernel::ServerSession>>(sessions)); + cmd_buff[3] = Kernel::g_handle_table + .Create(std::get<Kernel::SharedPtr<Kernel::ClientSession>>(sessions)) + .MoveFrom(); } else { cmd_buff[3] = 0; LOG_ERROR(Service_FS, "failed to get a handle for file %s", file_path.DebugStr().c_str()); @@ -127,10 +134,16 @@ static void OpenFileDirectly(Service::Interface* self) { } SCOPE_EXIT({ CloseArchive(*archive_handle); }); - ResultVal<SharedPtr<File>> file_res = OpenFileFromArchive(*archive_handle, file_path, mode); + ResultVal<std::shared_ptr<File>> file_res = + OpenFileFromArchive(*archive_handle, file_path, mode); cmd_buff[1] = file_res.Code().raw; if (file_res.Succeeded()) { - cmd_buff[3] = Kernel::g_handle_table.Create(*file_res).MoveFrom(); + std::shared_ptr<File> file = *file_res; + auto sessions = ServerSession::CreateSessionPair(file->GetName(), file); + file->ClientConnected(std::get<Kernel::SharedPtr<Kernel::ServerSession>>(sessions)); + cmd_buff[3] = Kernel::g_handle_table + .Create(std::get<Kernel::SharedPtr<Kernel::ClientSession>>(sessions)) + .MoveFrom(); } else { cmd_buff[3] = 0; LOG_ERROR(Service_FS, "failed to get a handle for file %s mode=%u attributes=%u", @@ -388,10 +401,16 @@ static void OpenDirectory(Service::Interface* self) { LOG_DEBUG(Service_FS, "type=%u size=%u data=%s", static_cast<u32>(dirname_type), dirname_size, dir_path.DebugStr().c_str()); - ResultVal<SharedPtr<Directory>> dir_res = OpenDirectoryFromArchive(archive_handle, dir_path); + ResultVal<std::shared_ptr<Directory>> dir_res = + OpenDirectoryFromArchive(archive_handle, dir_path); cmd_buff[1] = dir_res.Code().raw; if (dir_res.Succeeded()) { - cmd_buff[3] = Kernel::g_handle_table.Create(*dir_res).MoveFrom(); + std::shared_ptr<Directory> directory = *dir_res; + auto sessions = ServerSession::CreateSessionPair(directory->GetName(), directory); + directory->ClientConnected(std::get<Kernel::SharedPtr<Kernel::ServerSession>>(sessions)); + cmd_buff[3] = Kernel::g_handle_table + .Create(std::get<Kernel::SharedPtr<Kernel::ClientSession>>(sessions)) + .MoveFrom(); } else { LOG_ERROR(Service_FS, "failed to get a handle for directory type=%d size=%d data=%s", dirname_type, dirname_size, dir_path.DebugStr().c_str()); diff --git a/src/core/hle/service/gsp_gpu.cpp b/src/core/hle/service/gsp_gpu.cpp index 49f4836b4..947958703 100644 --- a/src/core/hle/service/gsp_gpu.cpp +++ b/src/core/hle/service/gsp_gpu.cpp @@ -636,6 +636,7 @@ static void TriggerCmdReqQueue(Interface* self) { * Inputs: * 0: Header 0x00180000 * Outputs: + * 0: Header Code[0x00180240] * 1: Result code * 2: Left framebuffer virtual address for the main screen * 3: Right framebuffer virtual address for the main screen @@ -658,18 +659,19 @@ static void ImportDisplayCaptureInfo(Interface* self) { FrameBufferUpdate* top_screen = GetFrameBufferInfo(thread_id, 0); FrameBufferUpdate* bottom_screen = GetFrameBufferInfo(thread_id, 1); + cmd_buff[0] = IPC::MakeHeader(0x18, 0x9, 0); + cmd_buff[1] = RESULT_SUCCESS.raw; + // Top Screen cmd_buff[2] = top_screen->framebuffer_info[top_screen->index].address_left; cmd_buff[3] = top_screen->framebuffer_info[top_screen->index].address_right; cmd_buff[4] = top_screen->framebuffer_info[top_screen->index].format; cmd_buff[5] = top_screen->framebuffer_info[top_screen->index].stride; - + // Bottom Screen cmd_buff[6] = bottom_screen->framebuffer_info[bottom_screen->index].address_left; cmd_buff[7] = bottom_screen->framebuffer_info[bottom_screen->index].address_right; cmd_buff[8] = bottom_screen->framebuffer_info[bottom_screen->index].format; cmd_buff[9] = bottom_screen->framebuffer_info[bottom_screen->index].stride; - cmd_buff[1] = RESULT_SUCCESS.raw; - LOG_WARNING(Service_GSP, "called"); } diff --git a/src/core/hle/service/hid/hid.cpp b/src/core/hle/service/hid/hid.cpp index 99baded11..18a1b6a16 100644 --- a/src/core/hle/service/hid/hid.cpp +++ b/src/core/hle/service/hid/hid.cpp @@ -37,7 +37,8 @@ static int enable_gyroscope_count = 0; // positive means enabled static PadState GetCirclePadDirectionState(s16 circle_pad_x, s16 circle_pad_y) { // 30 degree and 60 degree are angular thresholds for directions - constexpr float TAN30 = 0.577350269, TAN60 = 1 / TAN30; + constexpr float TAN30 = 0.577350269f; + constexpr float TAN60 = 1 / TAN30; // a circle pad radius greater than 40 will trigger circle pad direction constexpr int CIRCLE_PAD_THRESHOLD_SQUARE = 40 * 40; PadState state; diff --git a/src/core/hle/service/service.cpp b/src/core/hle/service/service.cpp index effecc043..25a7aeea8 100644 --- a/src/core/hle/service/service.cpp +++ b/src/core/hle/service/service.cpp @@ -2,11 +2,14 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include <boost/range/algorithm_ext/erase.hpp> + #include "common/logging/log.h" #include "common/string_util.h" + +#include "core/hle/kernel/server_port.h" #include "core/hle/service/ac_u.h" -#include "core/hle/service/act_a.h" -#include "core/hle/service/act_u.h" +#include "core/hle/service/act/act.h" #include "core/hle/service/am/am.h" #include "core/hle/service/apt/apt.h" #include "core/hle/service/boss/boss.h" @@ -44,8 +47,8 @@ namespace Service { -std::unordered_map<std::string, Kernel::SharedPtr<Interface>> g_kernel_named_ports; -std::unordered_map<std::string, Kernel::SharedPtr<Interface>> g_srv_services; +std::unordered_map<std::string, Kernel::SharedPtr<Kernel::ClientPort>> g_kernel_named_ports; +std::unordered_map<std::string, Kernel::SharedPtr<Kernel::ClientPort>> g_srv_services; /** * Creates a function string for logging, complete with the name (or header code, depending @@ -64,7 +67,23 @@ static std::string MakeFunctionString(const char* name, const char* port_name, return function_string; } -ResultVal<bool> Interface::SyncRequest() { +void SessionRequestHandler::ClientConnected( + Kernel::SharedPtr<Kernel::ServerSession> server_session) { + connected_sessions.push_back(server_session); +} + +void SessionRequestHandler::ClientDisconnected( + Kernel::SharedPtr<Kernel::ServerSession> server_session) { + boost::range::remove_erase(connected_sessions, server_session); +} + +Interface::Interface(u32 max_sessions) : max_sessions(max_sessions) {} +Interface::~Interface() = default; + +void Interface::HandleSyncRequest(Kernel::SharedPtr<Kernel::ServerSession> server_session) { + // TODO(Subv): Make use of the server_session in the HLE service handlers to distinguish which + // session triggered each command. + u32* cmd_buff = Kernel::GetCommandBuffer(); auto itr = m_functions.find(cmd_buff[0]); @@ -78,14 +97,12 @@ ResultVal<bool> Interface::SyncRequest() { // TODO(bunnei): Hack - ignore error cmd_buff[1] = 0; - return MakeResult<bool>(false); + return; } LOG_TRACE(Service, "%s", MakeFunctionString(itr->second.name, GetPortName().c_str(), cmd_buff).c_str()); itr->second.func(this); - - return MakeResult<bool>(false); // TODO: Implement return from actual function } void Interface::Register(const FunctionInfo* functions, size_t n) { @@ -100,11 +117,19 @@ void Interface::Register(const FunctionInfo* functions, size_t n) { // Module interface static void AddNamedPort(Interface* interface_) { - g_kernel_named_ports.emplace(interface_->GetPortName(), interface_); + auto ports = + Kernel::ServerPort::CreatePortPair(interface_->GetMaxSessions(), interface_->GetPortName(), + std::shared_ptr<Interface>(interface_)); + auto client_port = std::get<Kernel::SharedPtr<Kernel::ClientPort>>(ports); + g_kernel_named_ports.emplace(interface_->GetPortName(), std::move(client_port)); } void AddService(Interface* interface_) { - g_srv_services.emplace(interface_->GetPortName(), interface_); + auto ports = + Kernel::ServerPort::CreatePortPair(interface_->GetMaxSessions(), interface_->GetPortName(), + std::shared_ptr<Interface>(interface_)); + auto client_port = std::get<Kernel::SharedPtr<Kernel::ClientPort>>(ports); + g_srv_services.emplace(interface_->GetPortName(), std::move(client_port)); } /// Initialize ServiceManager @@ -113,6 +138,7 @@ void Init() { AddNamedPort(new ERR::ERR_F); FS::ArchiveInit(); + ACT::Init(); AM::Init(); APT::Init(); BOSS::Init(); @@ -132,8 +158,6 @@ void Init() { QTM::Init(); AddService(new AC::AC_U); - AddService(new ACT::ACT_A); - AddService(new ACT::ACT_U); AddService(new CSND::CSND_SND); AddService(new DSP_DSP::Interface); AddService(new GSP::GSP_GPU); diff --git a/src/core/hle/service/service.h b/src/core/hle/service/service.h index 29daacfc4..a7ba7688f 100644 --- a/src/core/hle/service/service.h +++ b/src/core/hle/service/service.h @@ -9,8 +9,15 @@ #include <unordered_map> #include <boost/container/flat_map.hpp> #include "common/common_types.h" -#include "core/hle/kernel/session.h" +#include "core/hle/ipc.h" +#include "core/hle/kernel/client_port.h" +#include "core/hle/kernel/thread.h" #include "core/hle/result.h" +#include "core/memory.h" + +namespace Kernel { +class ServerSession; +} //////////////////////////////////////////////////////////////////////////////////////////////////// // Namespace Service @@ -18,14 +25,63 @@ namespace Service { static const int kMaxPortSize = 8; ///< Maximum size of a port name (8 characters) +/// Arbitrary default number of maximum connections to an HLE service. +static const u32 DefaultMaxSessions = 10; + +/** + * Interface implemented by HLE Session handlers. + * This can be provided to a ServerSession in order to hook into several relevant events + * (such as a new connection or a SyncRequest) so they can be implemented in the emulator. + */ +class SessionRequestHandler { +public: + /** + * Handles a sync request from the emulated application. + * @param server_session The ServerSession that was triggered for this sync request, + * it should be used to differentiate which client (As in ClientSession) we're answering to. + * TODO(Subv): Use a wrapper structure to hold all the information relevant to + * this request (ServerSession, Originator thread, Translated command buffer, etc). + * @returns ResultCode the result code of the translate operation. + */ + virtual void HandleSyncRequest(Kernel::SharedPtr<Kernel::ServerSession> server_session) = 0; + + /** + * Signals that a client has just connected to this HLE handler and keeps the + * associated ServerSession alive for the duration of the connection. + * @param server_session Owning pointer to the ServerSession associated with the connection. + */ + void ClientConnected(Kernel::SharedPtr<Kernel::ServerSession> server_session); + + /** + * Signals that a client has just disconnected from this HLE handler and releases the + * associated ServerSession. + * @param server_session ServerSession associated with the connection. + */ + void ClientDisconnected(Kernel::SharedPtr<Kernel::ServerSession> server_session); -/// Interface to a CTROS service -class Interface : public Kernel::Session { - // TODO(yuriks): An "Interface" being a Kernel::Object is mostly non-sense. Interface should be - // just something that encapsulates a session and acts as a helper to implement service - // processes. +protected: + /// List of sessions that are connected to this handler. + /// A ServerSession whose server endpoint is an HLE implementation is kept alive by this list + // for the duration of the connection. + std::vector<Kernel::SharedPtr<Kernel::ServerSession>> connected_sessions; +}; + +/** + * Framework for implementing HLE service handlers which dispatch incoming SyncRequests based on a + * table mapping header ids to handler functions. + */ +class Interface : public SessionRequestHandler { public: - std::string GetName() const override { + /** + * Creates an HLE interface with the specified max sessions. + * @param max_sessions Maximum number of sessions that can be + * connected to this service at the same time. + */ + Interface(u32 max_sessions = DefaultMaxSessions); + + virtual ~Interface(); + + std::string GetName() const { return GetPortName(); } @@ -33,6 +89,15 @@ public: version.raw = raw_version; } + /** + * Gets the maximum allowed number of sessions that can be connected to this service + * at the same time. + * @returns The maximum number of connections allowed. + */ + u32 GetMaxSessions() const { + return max_sessions; + } + typedef void (*Function)(Interface*); struct FunctionInfo { @@ -49,9 +114,9 @@ public: return "[UNKNOWN SERVICE PORT]"; } - ResultVal<bool> SyncRequest() override; - protected: + void HandleSyncRequest(Kernel::SharedPtr<Kernel::ServerSession> server_session) override; + /** * Registers the functions in the service */ @@ -71,6 +136,7 @@ protected: } version = {}; private: + u32 max_sessions; ///< Maximum number of concurrent sessions that this service can handle. boost::container::flat_map<u32, FunctionInfo> m_functions; }; @@ -81,9 +147,9 @@ void Init(); void Shutdown(); /// Map of named ports managed by the kernel, which can be retrieved using the ConnectToPort SVC. -extern std::unordered_map<std::string, Kernel::SharedPtr<Interface>> g_kernel_named_ports; +extern std::unordered_map<std::string, Kernel::SharedPtr<Kernel::ClientPort>> g_kernel_named_ports; /// Map of services registered with the "srv:" service, retrieved using GetServiceHandle. -extern std::unordered_map<std::string, Kernel::SharedPtr<Interface>> g_srv_services; +extern std::unordered_map<std::string, Kernel::SharedPtr<Kernel::ClientPort>> g_srv_services; /// Adds a service to the services table void AddService(Interface* interface_); diff --git a/src/core/hle/service/soc_u.cpp b/src/core/hle/service/soc_u.cpp index fd251fc0a..c3918cdd0 100644 --- a/src/core/hle/service/soc_u.cpp +++ b/src/core/hle/service/soc_u.cpp @@ -11,7 +11,7 @@ #include "common/common_types.h" #include "common/logging/log.h" #include "common/scope_exit.h" -#include "core/hle/kernel/session.h" +#include "core/hle/kernel/server_session.h" #include "core/hle/result.h" #include "core/hle/service/soc_u.h" #include "core/memory.h" diff --git a/src/core/hle/service/srv.cpp b/src/core/hle/service/srv.cpp index f8df38c42..3bd787147 100644 --- a/src/core/hle/service/srv.cpp +++ b/src/core/hle/service/srv.cpp @@ -2,9 +2,13 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include <tuple> + #include "common/common_types.h" #include "common/logging/log.h" +#include "core/hle/kernel/client_session.h" #include "core/hle/kernel/event.h" +#include "core/hle/kernel/server_session.h" #include "core/hle/service/srv.h" namespace Service { @@ -79,7 +83,15 @@ static void GetServiceHandle(Interface* self) { auto it = Service::g_srv_services.find(port_name); if (it != Service::g_srv_services.end()) { - cmd_buff[3] = Kernel::g_handle_table.Create(it->second).MoveFrom(); + auto client_port = it->second; + + auto client_session = client_port->Connect(); + res = client_session.Code(); + + if (client_session.Succeeded()) { + // Return the client session + cmd_buff[3] = Kernel::g_handle_table.Create(*client_session).MoveFrom(); + } LOG_TRACE(Service_SRV, "called port=%s, handle=0x%08X", port_name.c_str(), cmd_buff[3]); } else { LOG_ERROR(Service_SRV, "(UNIMPLEMENTED) called port=%s", port_name.c_str()); diff --git a/src/core/hle/service/srv.h b/src/core/hle/service/srv.h index 6041ca42d..d3a9de879 100644 --- a/src/core/hle/service/srv.h +++ b/src/core/hle/service/srv.h @@ -2,6 +2,8 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#pragma once + #include "core/hle/service/service.h" namespace Service { diff --git a/src/core/hle/svc.cpp b/src/core/hle/svc.cpp index c6b80dc50..ef25acc4a 100644 --- a/src/core/hle/svc.cpp +++ b/src/core/hle/svc.cpp @@ -13,6 +13,7 @@ #include "core/hle/function_wrappers.h" #include "core/hle/kernel/address_arbiter.h" #include "core/hle/kernel/client_port.h" +#include "core/hle/kernel/client_session.h" #include "core/hle/kernel/event.h" #include "core/hle/kernel/memory.h" #include "core/hle/kernel/mutex.h" @@ -20,6 +21,7 @@ #include "core/hle/kernel/resource_limit.h" #include "core/hle/kernel/semaphore.h" #include "core/hle/kernel/server_port.h" +#include "core/hle/kernel/server_session.h" #include "core/hle/kernel/shared_memory.h" #include "core/hle/kernel/thread.h" #include "core/hle/kernel/timer.h" @@ -41,6 +43,9 @@ const ResultCode ERR_PORT_NAME_TOO_LONG(ErrorDescription(30), ErrorModule::OS, ErrorSummary::InvalidArgument, ErrorLevel::Usage); // 0xE0E0181E +const ResultCode ERR_SYNC_TIMEOUT(ErrorDescription::Timeout, ErrorModule::OS, + ErrorSummary::StatusChanged, ErrorLevel::Info); + const ResultCode ERR_MISALIGNED_ADDRESS{// 0xE0E01BF1 ErrorDescription::MisalignedAddress, ErrorModule::OS, ErrorSummary::InvalidArgument, ErrorLevel::Usage}; @@ -222,20 +227,29 @@ static ResultCode ConnectToPort(Handle* out_handle, const char* port_name) { return ERR_NOT_FOUND; } - CASCADE_RESULT(*out_handle, Kernel::g_handle_table.Create(it->second)); + auto client_port = it->second; + + SharedPtr<Kernel::ClientSession> client_session; + CASCADE_RESULT(client_session, client_port->Connect()); + + // Return the client session + CASCADE_RESULT(*out_handle, Kernel::g_handle_table.Create(client_session)); return RESULT_SUCCESS; } -/// Synchronize to an OS service +/// Makes a blocking IPC call to an OS service. static ResultCode SendSyncRequest(Handle handle) { - SharedPtr<Kernel::Session> session = Kernel::g_handle_table.Get<Kernel::Session>(handle); + SharedPtr<Kernel::ClientSession> session = + Kernel::g_handle_table.Get<Kernel::ClientSession>(handle); if (session == nullptr) { return ERR_INVALID_HANDLE; } LOG_TRACE(Kernel_SVC, "called handle=0x%08X(%s)", handle, session->GetName().c_str()); - return session->SyncRequest().Code(); + // TODO(Subv): svcSendSyncRequest should put the caller thread to sleep while the server + // responds and cause a reschedule. + return session->SendSyncRequest(); } /// Close a handle @@ -249,27 +263,30 @@ static ResultCode WaitSynchronization1(Handle handle, s64 nano_seconds) { auto object = Kernel::g_handle_table.GetWaitObject(handle); Kernel::Thread* thread = Kernel::GetCurrentThread(); - thread->waitsynch_waited = false; - if (object == nullptr) return ERR_INVALID_HANDLE; LOG_TRACE(Kernel_SVC, "called handle=0x%08X(%s:%s), nanoseconds=%lld", handle, object->GetTypeName().c_str(), object->GetName().c_str(), nano_seconds); - HLE::Reschedule(__func__); - - // Check for next thread to schedule if (object->ShouldWait()) { + if (nano_seconds == 0) + return ERR_SYNC_TIMEOUT; + object->AddWaitingThread(thread); - Kernel::WaitCurrentThread_WaitSynchronization({object}, false, false); + // TODO(Subv): Perform things like update the mutex lock owner's priority to + // prevent priority inversion. Currently this is done in Mutex::ShouldWait, + // but it should be moved to a function that is called from here. + thread->status = THREADSTATUS_WAIT_SYNCH; // Create an event to wake the thread up after the specified nanosecond delay has passed thread->WakeAfterDelay(nano_seconds); - // NOTE: output of this SVC will be set later depending on how the thread resumes - return HLE::RESULT_INVALID; + // Note: The output of this SVC will be set to RESULT_SUCCESS if the thread + // resumes due to a signal in its wait objects. + // Otherwise we retain the default value of timeout. + return ERR_SYNC_TIMEOUT; } object->Acquire(); @@ -280,11 +297,7 @@ static ResultCode WaitSynchronization1(Handle handle, s64 nano_seconds) { /// Wait for the given handles to synchronize, timeout after the specified nanoseconds static ResultCode WaitSynchronizationN(s32* out, Handle* handles, s32 handle_count, bool wait_all, s64 nano_seconds) { - bool wait_thread = !wait_all; - int handle_index = 0; Kernel::Thread* thread = Kernel::GetCurrentThread(); - bool was_waiting = thread->waitsynch_waited; - thread->waitsynch_waited = false; // Check if 'handles' is invalid if (handles == nullptr) @@ -300,90 +313,113 @@ static ResultCode WaitSynchronizationN(s32* out, Handle* handles, s32 handle_cou return ResultCode(ErrorDescription::OutOfRange, ErrorModule::OS, ErrorSummary::InvalidArgument, ErrorLevel::Usage); - // If 'handle_count' is non-zero, iterate through each handle and wait the current thread if - // necessary - if (handle_count != 0) { - bool selected = false; // True once an object has been selected - - Kernel::SharedPtr<Kernel::WaitObject> wait_object; - - for (int i = 0; i < handle_count; ++i) { - auto object = Kernel::g_handle_table.GetWaitObject(handles[i]); - if (object == nullptr) - return ERR_INVALID_HANDLE; - - // Check if the current thread should wait on this object... - if (object->ShouldWait()) { - - // Check we are waiting on all objects... - if (wait_all) - // Wait the thread - wait_thread = true; - } else { - // Do not wait on this object, check if this object should be selected... - if (!wait_all && (!selected || (wait_object == object && was_waiting))) { - // Do not wait the thread - wait_thread = false; - handle_index = i; - wait_object = object; - selected = true; - } - } - } - } else { - // If no handles were passed in, put the thread to sleep only when 'wait_all' is false - // NOTE: This should deadlock the current thread if no timeout was specified - if (!wait_all) { - wait_thread = true; - } + using ObjectPtr = Kernel::SharedPtr<Kernel::WaitObject>; + std::vector<ObjectPtr> objects(handle_count); + + for (int i = 0; i < handle_count; ++i) { + auto object = Kernel::g_handle_table.GetWaitObject(handles[i]); + if (object == nullptr) + return ERR_INVALID_HANDLE; + objects[i] = object; } - SCOPE_EXIT({ - HLE::Reschedule("WaitSynchronizationN"); - }); // Reschedule after putting the threads to sleep. + // Clear the mapping of wait object indices. + // We don't want any lingering state in this map. + // It will be repopulated later in the wait_all = false case. + thread->wait_objects_index.clear(); + + if (wait_all) { + bool all_available = + std::all_of(objects.begin(), objects.end(), + [](const ObjectPtr& object) { return !object->ShouldWait(); }); + if (all_available) { + // We can acquire all objects right now, do so. + for (auto& object : objects) + object->Acquire(); + // Note: In this case, the `out` parameter is not set, + // and retains whatever value it had before. + return RESULT_SUCCESS; + } + + // Not all objects were available right now, prepare to suspend the thread. - // If thread should wait, then set its state to waiting - if (wait_thread) { + // If a timeout value of 0 was provided, just return the Timeout error code instead of + // suspending the thread. + if (nano_seconds == 0) + return ERR_SYNC_TIMEOUT; - // Actually wait the current thread on each object if we decided to wait... - std::vector<SharedPtr<Kernel::WaitObject>> wait_objects; - wait_objects.reserve(handle_count); + // Put the thread to sleep + thread->status = THREADSTATUS_WAIT_SYNCH; - for (int i = 0; i < handle_count; ++i) { - auto object = Kernel::g_handle_table.GetWaitObject(handles[i]); - object->AddWaitingThread(Kernel::GetCurrentThread()); - wait_objects.push_back(object); + // Add the thread to each of the objects' waiting threads. + for (auto& object : objects) { + object->AddWaitingThread(thread); + // TODO(Subv): Perform things like update the mutex lock owner's priority to + // prevent priority inversion. Currently this is done in Mutex::ShouldWait, + // but it should be moved to a function that is called from here. } - Kernel::WaitCurrentThread_WaitSynchronization(std::move(wait_objects), true, wait_all); + // Set the thread's waitlist to the list of objects passed to WaitSynchronizationN + thread->wait_objects = std::move(objects); // Create an event to wake the thread up after the specified nanosecond delay has passed - Kernel::GetCurrentThread()->WakeAfterDelay(nano_seconds); - - // NOTE: output of this SVC will be set later depending on how the thread resumes - return HLE::RESULT_INVALID; - } + thread->WakeAfterDelay(nano_seconds); - // Acquire objects if we did not wait... - for (int i = 0; i < handle_count; ++i) { - auto object = Kernel::g_handle_table.GetWaitObject(handles[i]); + // This value gets set to -1 by default in this case, it is not modified after this. + *out = -1; + // Note: The output of this SVC will be set to RESULT_SUCCESS if the thread resumes due to + // a signal in one of its wait objects. + return ERR_SYNC_TIMEOUT; + } else { + // Find the first object that is acquirable in the provided list of objects + auto itr = std::find_if(objects.begin(), objects.end(), + [](const ObjectPtr& object) { return !object->ShouldWait(); }); - // Acquire the object if it is not waiting... - if (!object->ShouldWait()) { + if (itr != objects.end()) { + // We found a ready object, acquire it and set the result value + Kernel::WaitObject* object = itr->get(); object->Acquire(); + *out = std::distance(objects.begin(), itr); + return RESULT_SUCCESS; + } + + // No objects were ready to be acquired, prepare to suspend the thread. + + // If a timeout value of 0 was provided, just return the Timeout error code instead of + // suspending the thread. + if (nano_seconds == 0) + return ERR_SYNC_TIMEOUT; + + // Put the thread to sleep + thread->status = THREADSTATUS_WAIT_SYNCH; - // If this was the first non-waiting object and 'wait_all' is false, don't acquire - // any other objects - if (!wait_all) - break; + // Clear the thread's waitlist, we won't use it for wait_all = false + thread->wait_objects.clear(); + + // Add the thread to each of the objects' waiting threads. + for (size_t i = 0; i < objects.size(); ++i) { + Kernel::WaitObject* object = objects[i].get(); + // Set the index of this object in the mapping of Objects -> index for this thread. + thread->wait_objects_index[object->GetObjectId()] = static_cast<int>(i); + object->AddWaitingThread(thread); + // TODO(Subv): Perform things like update the mutex lock owner's priority to + // prevent priority inversion. Currently this is done in Mutex::ShouldWait, + // but it should be moved to a function that is called from here. } - } - // TODO(bunnei): If 'wait_all' is true, this is probably wrong. However, real hardware does - // not seem to set it to any meaningful value. - *out = handle_count != 0 ? (wait_all ? -1 : handle_index) : 0; + // Note: If no handles and no timeout were given, then the thread will deadlock, this is + // consistent with hardware behavior. - return RESULT_SUCCESS; + // Create an event to wake the thread up after the specified nanosecond delay has passed + thread->WakeAfterDelay(nano_seconds); + + // Note: The output of this SVC will be set to RESULT_SUCCESS if the thread resumes due to a + // signal in one of its wait objects. + // Otherwise we retain the default value of timeout, and -1 in the out parameter + thread->wait_set_output = true; + *out = -1; + return ERR_SYNC_TIMEOUT; + } } /// Create an address arbiter (to allocate access to shared resources) @@ -1148,6 +1184,8 @@ void CallSVC(u32 immediate) { if (info) { if (info->func) { info->func(); + // TODO(Subv): Not all service functions should cause a reschedule in all cases. + HLE::Reschedule(__func__); } else { LOG_ERROR(Kernel_SVC, "unimplemented SVC function %s(..)", info->name); } diff --git a/src/core/loader/loader.h b/src/core/loader/loader.h index 5e3d46638..a6c2a745f 100644 --- a/src/core/loader/loader.h +++ b/src/core/loader/loader.h @@ -144,6 +144,15 @@ public: } /** + * Get the program id of the application + * @param out_program_id Reference to store program id into + * @return ResultStatus result of function + */ + virtual ResultStatus ReadProgramId(u64& out_program_id) { + return ResultStatus::ErrorNotImplemented; + } + + /** * Get the RomFS of the application * Since the RomFS can be huge, we return a file reference instead of copying to a buffer * @param romfs_file The file containing the RomFS diff --git a/src/core/loader/ncch.cpp b/src/core/loader/ncch.cpp index d4be61e0e..6f2164428 100644 --- a/src/core/loader/ncch.cpp +++ b/src/core/loader/ncch.cpp @@ -344,6 +344,18 @@ ResultStatus AppLoader_NCCH::ReadLogo(std::vector<u8>& buffer) { return LoadSectionExeFS("logo", buffer); } +ResultStatus AppLoader_NCCH::ReadProgramId(u64& out_program_id) { + if (!file.IsOpen()) + return ResultStatus::Error; + + ResultStatus result = LoadExeFS(); + if (result != ResultStatus::Success) + return result; + + out_program_id = ncch_header.program_id; + return ResultStatus::Success; +} + ResultStatus AppLoader_NCCH::ReadRomFS(std::shared_ptr<FileUtil::IOFile>& romfs_file, u64& offset, u64& size) { if (!file.IsOpen()) diff --git a/src/core/loader/ncch.h b/src/core/loader/ncch.h index bcf3ae6e3..6c93d46d8 100644 --- a/src/core/loader/ncch.h +++ b/src/core/loader/ncch.h @@ -220,6 +220,13 @@ public: ResultStatus ReadLogo(std::vector<u8>& buffer) override; /** + * Get the program id of the application + * @param out_program_id Reference to store program id into + * @return ResultStatus result of function + */ + ResultStatus ReadProgramId(u64& out_program_id) override; + + /** * Get the RomFS of the application * @param romfs_file Reference to buffer to store data * @param offset Offset in the file to the RomFS diff --git a/src/core/memory.cpp b/src/core/memory.cpp index 65e4bba85..d058dc844 100644 --- a/src/core/memory.cpp +++ b/src/core/memory.cpp @@ -357,14 +357,24 @@ void RasterizerMarkRegionCached(PAddr start, u32 size, int count_delta) { } } +static void RoundToPages(PAddr& start, u32& size) { + PAddr start_rounded_down = start & ~PAGE_MASK; + PAddr end_rounded_up = ((start + size) + PAGE_MASK) & ~PAGE_MASK; + + start = start_rounded_down; + size = end_rounded_up - start_rounded_down; +} + void RasterizerFlushRegion(PAddr start, u32 size) { if (VideoCore::g_renderer != nullptr) { + RoundToPages(start, size); VideoCore::g_renderer->Rasterizer()->FlushRegion(start, size); } } void RasterizerFlushAndInvalidateRegion(PAddr start, u32 size) { if (VideoCore::g_renderer != nullptr) { + RoundToPages(start, size); VideoCore::g_renderer->Rasterizer()->FlushAndInvalidateRegion(start, size); } } diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt index 89237e477..b47156ca4 100644 --- a/src/tests/CMakeLists.txt +++ b/src/tests/CMakeLists.txt @@ -13,6 +13,6 @@ include_directories(../../externals/catch/single_include/) add_executable(tests ${SRCS} ${HEADERS}) target_link_libraries(tests core video_core audio_core common) -target_link_libraries(tests ${PLATFORM_LIBRARIES}) +target_link_libraries(tests ${PLATFORM_LIBRARIES} Threads::Threads) add_test(NAME tests COMMAND $<TARGET_FILE:tests>) diff --git a/src/video_core/CMakeLists.txt b/src/video_core/CMakeLists.txt index 581a37897..9aa446a8f 100644 --- a/src/video_core/CMakeLists.txt +++ b/src/video_core/CMakeLists.txt @@ -59,6 +59,9 @@ create_directory_groups(${SRCS} ${HEADERS}) add_library(video_core STATIC ${SRCS} ${HEADERS}) target_link_libraries(video_core glad) +if (ARCHITECTURE_x86_64) + target_link_libraries(video_core xbyak) +endif() if (PNG_FOUND) target_link_libraries(video_core ${PNG_LIBRARIES}) diff --git a/src/video_core/command_processor.cpp b/src/video_core/command_processor.cpp index 0495a9fac..8a5d8533c 100644 --- a/src/video_core/command_processor.cpp +++ b/src/video_core/command_processor.cpp @@ -251,7 +251,6 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) { ASSERT(vertex != -1); bool vertex_cache_hit = false; - Shader::OutputRegisters output_registers; if (is_indexed) { if (g_debug_context && Pica::g_debug_context->recorder) { @@ -279,10 +278,9 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) { g_debug_context->OnEvent(DebugContext::Event::VertexShaderInvocation, (void*)&input); g_state.vs.Run(shader_unit, input, loader.GetNumTotalAttributes()); - output_registers = shader_unit.output_registers; // Retrieve vertex from register data - output_vertex = output_registers.ToVertex(regs.vs); + output_vertex = shader_unit.output_registers.ToVertex(regs.vs); if (is_indexed) { vertex_cache[vertex_cache_pos] = output_vertex; diff --git a/src/video_core/shader/shader.cpp b/src/video_core/shader/shader.cpp index 3febe739c..c7f23dab9 100644 --- a/src/video_core/shader/shader.cpp +++ b/src/video_core/shader/shader.cpp @@ -25,7 +25,7 @@ namespace Pica { namespace Shader { -OutputVertex OutputRegisters::ToVertex(const Regs::ShaderConfig& config) { +OutputVertex OutputRegisters::ToVertex(const Regs::ShaderConfig& config) const { // Setup output data OutputVertex ret; // TODO(neobrain): Under some circumstances, up to 16 attributes may be output. We need to diff --git a/src/video_core/shader/shader.h b/src/video_core/shader/shader.h index 8858d67f8..0111d8c0f 100644 --- a/src/video_core/shader/shader.h +++ b/src/video_core/shader/shader.h @@ -85,7 +85,7 @@ struct OutputRegisters { alignas(16) Math::Vec4<float24> value[16]; - OutputVertex ToVertex(const Regs::ShaderConfig& config); + OutputVertex ToVertex(const Regs::ShaderConfig& config) const; }; static_assert(std::is_pod<OutputRegisters>::value, "Structure is not POD"); diff --git a/src/video_core/shader/shader_jit_x64.cpp b/src/video_core/shader/shader_jit_x64.cpp index c96110bb2..3ba31d474 100644 --- a/src/video_core/shader/shader_jit_x64.cpp +++ b/src/video_core/shader/shader_jit_x64.cpp @@ -6,24 +6,30 @@ #include <cmath> #include <cstdint> #include <nihstro/shader_bytecode.h> +#include <smmintrin.h> #include <xmmintrin.h> #include "common/assert.h" #include "common/logging/log.h" #include "common/vector_math.h" -#include "common/x64/abi.h" #include "common/x64/cpu_detect.h" -#include "common/x64/emitter.h" -#include "shader.h" -#include "shader_jit_x64.h" +#include "common/x64/xbyak_abi.h" +#include "common/x64/xbyak_util.h" #include "video_core/pica_state.h" #include "video_core/pica_types.h" +#include "video_core/shader/shader.h" +#include "video_core/shader/shader_jit_x64.h" + +using namespace Common::X64; +using namespace Xbyak::util; +using Xbyak::Label; +using Xbyak::Reg32; +using Xbyak::Reg64; +using Xbyak::Xmm; namespace Pica { namespace Shader { -using namespace Gen; - typedef void (JitShader::*JitFunction)(Instruction instr); const JitFunction instr_table[64] = { @@ -98,44 +104,47 @@ const JitFunction instr_table[64] = { // purposes, as documented below: /// Pointer to the uniform memory -static const X64Reg SETUP = R9; +static const Reg64 SETUP = r9; /// The two 32-bit VS address offset registers set by the MOVA instruction -static const X64Reg ADDROFFS_REG_0 = R10; -static const X64Reg ADDROFFS_REG_1 = R11; +static const Reg64 ADDROFFS_REG_0 = r10; +static const Reg64 ADDROFFS_REG_1 = r11; /// VS loop count register (Multiplied by 16) -static const X64Reg LOOPCOUNT_REG = R12; +static const Reg32 LOOPCOUNT_REG = r12d; /// Current VS loop iteration number (we could probably use LOOPCOUNT_REG, but this quicker) -static const X64Reg LOOPCOUNT = RSI; +static const Reg32 LOOPCOUNT = esi; /// Number to increment LOOPCOUNT_REG by on each loop iteration (Multiplied by 16) -static const X64Reg LOOPINC = RDI; +static const Reg32 LOOPINC = edi; /// Result of the previous CMP instruction for the X-component comparison -static const X64Reg COND0 = R13; +static const Reg64 COND0 = r13; /// Result of the previous CMP instruction for the Y-component comparison -static const X64Reg COND1 = R14; +static const Reg64 COND1 = r14; /// Pointer to the UnitState instance for the current VS unit -static const X64Reg STATE = R15; +static const Reg64 STATE = r15; /// SIMD scratch register -static const X64Reg SCRATCH = XMM0; +static const Xmm SCRATCH = xmm0; /// Loaded with the first swizzled source register, otherwise can be used as a scratch register -static const X64Reg SRC1 = XMM1; +static const Xmm SRC1 = xmm1; /// Loaded with the second swizzled source register, otherwise can be used as a scratch register -static const X64Reg SRC2 = XMM2; +static const Xmm SRC2 = xmm2; /// Loaded with the third swizzled source register, otherwise can be used as a scratch register -static const X64Reg SRC3 = XMM3; +static const Xmm SRC3 = xmm3; /// Additional scratch register -static const X64Reg SCRATCH2 = XMM4; +static const Xmm SCRATCH2 = xmm4; /// Constant vector of [1.0f, 1.0f, 1.0f, 1.0f], used to efficiently set a vector to one -static const X64Reg ONE = XMM14; +static const Xmm ONE = xmm14; /// Constant vector of [-0.f, -0.f, -0.f, -0.f], used to efficiently negate a vector with XOR -static const X64Reg NEGBIT = XMM15; +static const Xmm NEGBIT = xmm15; // State registers that must not be modified by external functions calls // Scratch registers, e.g., SRC1 and SCRATCH, have to be saved on the side if needed -static const BitSet32 persistent_regs = { - SETUP, STATE, // Pointers to register blocks - ADDROFFS_REG_0, ADDROFFS_REG_1, LOOPCOUNT_REG, COND0, COND1, // Cached registers - ONE + 16, NEGBIT + 16, // Constants -}; +static const BitSet32 persistent_regs = BuildRegSet({ + // Pointers to register blocks + SETUP, STATE, + // Cached registers + ADDROFFS_REG_0, ADDROFFS_REG_1, LOOPCOUNT_REG, COND0, COND1, + // Constants + ONE, NEGBIT, +}); /// Raw constant for the source register selector that indicates no swizzling is performed static const u8 NO_SRC_REG_SWIZZLE = 0x1b; @@ -157,7 +166,8 @@ static void LogCritical(const char* msg) { void JitShader::Compile_Assert(bool condition, const char* msg) { if (!condition) { - ABI_CallFunctionP(reinterpret_cast<const void*>(LogCritical), const_cast<char*>(msg)); + mov(ABI_PARAM1, reinterpret_cast<size_t>(msg)); + CallFarFunction(*this, LogCritical); } } @@ -169,8 +179,8 @@ void JitShader::Compile_Assert(bool condition, const char* msg) { * @param dest Destination XMM register to store the loaded, swizzled source register */ void JitShader::Compile_SwizzleSrc(Instruction instr, unsigned src_num, SourceRegister src_reg, - X64Reg dest) { - X64Reg src_ptr; + Xmm dest) { + Reg64 src_ptr; size_t src_offset; if (src_reg.GetRegisterType() == RegisterType::FloatUniform) { @@ -206,13 +216,13 @@ void JitShader::Compile_SwizzleSrc(Instruction instr, unsigned src_num, SourceRe if (src_num == offset_src && address_register_index != 0) { switch (address_register_index) { case 1: // address offset 1 - MOVAPS(dest, MComplex(src_ptr, ADDROFFS_REG_0, SCALE_1, src_offset_disp)); + movaps(dest, xword[src_ptr + ADDROFFS_REG_0 + src_offset_disp]); break; case 2: // address offset 2 - MOVAPS(dest, MComplex(src_ptr, ADDROFFS_REG_1, SCALE_1, src_offset_disp)); + movaps(dest, xword[src_ptr + ADDROFFS_REG_1 + src_offset_disp]); break; case 3: // address offset 3 - MOVAPS(dest, MComplex(src_ptr, LOOPCOUNT_REG, SCALE_1, src_offset_disp)); + movaps(dest, xword[src_ptr + LOOPCOUNT_REG.cvt64() + src_offset_disp]); break; default: UNREACHABLE(); @@ -220,7 +230,7 @@ void JitShader::Compile_SwizzleSrc(Instruction instr, unsigned src_num, SourceRe } } else { // Load the source - MOVAPS(dest, MDisp(src_ptr, src_offset_disp)); + movaps(dest, xword[src_ptr + src_offset_disp]); } SwizzlePattern swiz = {g_state.vs.swizzle_data[operand_desc_id]}; @@ -232,17 +242,17 @@ void JitShader::Compile_SwizzleSrc(Instruction instr, unsigned src_num, SourceRe sel = ((sel & 0xc0) >> 6) | ((sel & 3) << 6) | ((sel & 0xc) << 2) | ((sel & 0x30) >> 2); // Shuffle inputs for swizzle - SHUFPS(dest, R(dest), sel); + shufps(dest, dest, sel); } // If the source register should be negated, flip the negative bit using XOR const bool negate[] = {swiz.negate_src1, swiz.negate_src2, swiz.negate_src3}; if (negate[src_num - 1]) { - XORPS(dest, R(NEGBIT)); + xorps(dest, NEGBIT); } } -void JitShader::Compile_DestEnable(Instruction instr, X64Reg src) { +void JitShader::Compile_DestEnable(Instruction instr, Xmm src) { DestRegister dest; unsigned operand_desc_id; if (instr.opcode.Value().EffectiveOpCode() == OpCode::Id::MAD || @@ -263,21 +273,21 @@ void JitShader::Compile_DestEnable(Instruction instr, X64Reg src) { // If all components are enabled, write the result to the destination register if (swiz.dest_mask == NO_DEST_REG_MASK) { // Store dest back to memory - MOVAPS(MDisp(STATE, dest_offset_disp), src); + movaps(xword[STATE + dest_offset_disp], src); } else { // Not all components are enabled, so mask the result when storing to the destination // register... - MOVAPS(SCRATCH, MDisp(STATE, dest_offset_disp)); + movaps(SCRATCH, xword[STATE + dest_offset_disp]); if (Common::GetCPUCaps().sse4_1) { u8 mask = ((swiz.dest_mask & 1) << 3) | ((swiz.dest_mask & 8) >> 3) | ((swiz.dest_mask & 2) << 1) | ((swiz.dest_mask & 4) >> 1); - BLENDPS(SCRATCH, R(src), mask); + blendps(SCRATCH, src, mask); } else { - MOVAPS(SCRATCH2, R(src)); - UNPCKHPS(SCRATCH2, R(SCRATCH)); // Unpack X/Y components of source and destination - UNPCKLPS(SCRATCH, R(src)); // Unpack Z/W components of source and destination + movaps(SCRATCH2, src); + unpckhps(SCRATCH2, SCRATCH); // Unpack X/Y components of source and destination + unpcklps(SCRATCH, src); // Unpack Z/W components of source and destination // Compute selector to selectively copy source components to destination for SHUFPS // instruction @@ -285,62 +295,62 @@ void JitShader::Compile_DestEnable(Instruction instr, X64Reg src) { ((swiz.DestComponentEnabled(1) ? 3 : 2) << 2) | ((swiz.DestComponentEnabled(2) ? 0 : 1) << 4) | ((swiz.DestComponentEnabled(3) ? 2 : 3) << 6); - SHUFPS(SCRATCH, R(SCRATCH2), sel); + shufps(SCRATCH, SCRATCH2, sel); } // Store dest back to memory - MOVAPS(MDisp(STATE, dest_offset_disp), SCRATCH); + movaps(xword[STATE + dest_offset_disp], SCRATCH); } } -void JitShader::Compile_SanitizedMul(Gen::X64Reg src1, Gen::X64Reg src2, Gen::X64Reg scratch) { - MOVAPS(scratch, R(src1)); - CMPPS(scratch, R(src2), CMP_ORD); +void JitShader::Compile_SanitizedMul(Xmm src1, Xmm src2, Xmm scratch) { + movaps(scratch, src1); + cmpordps(scratch, src2); - MULPS(src1, R(src2)); + mulps(src1, src2); - MOVAPS(src2, R(src1)); - CMPPS(src2, R(src2), CMP_UNORD); + movaps(src2, src1); + cmpunordps(src2, src2); - XORPS(scratch, R(src2)); - ANDPS(src1, R(scratch)); + xorps(scratch, src2); + andps(src1, scratch); } void JitShader::Compile_EvaluateCondition(Instruction instr) { // Note: NXOR is used below to check for equality switch (instr.flow_control.op) { case Instruction::FlowControlType::Or: - MOV(32, R(RAX), R(COND0)); - MOV(32, R(RBX), R(COND1)); - XOR(32, R(RAX), Imm32(instr.flow_control.refx.Value() ^ 1)); - XOR(32, R(RBX), Imm32(instr.flow_control.refy.Value() ^ 1)); - OR(32, R(RAX), R(RBX)); + mov(eax, COND0); + mov(ebx, COND1); + xor(eax, (instr.flow_control.refx.Value() ^ 1)); + xor(ebx, (instr.flow_control.refy.Value() ^ 1)); + or (eax, ebx); break; case Instruction::FlowControlType::And: - MOV(32, R(RAX), R(COND0)); - MOV(32, R(RBX), R(COND1)); - XOR(32, R(RAX), Imm32(instr.flow_control.refx.Value() ^ 1)); - XOR(32, R(RBX), Imm32(instr.flow_control.refy.Value() ^ 1)); - AND(32, R(RAX), R(RBX)); + mov(eax, COND0); + mov(ebx, COND1); + xor(eax, (instr.flow_control.refx.Value() ^ 1)); + xor(ebx, (instr.flow_control.refy.Value() ^ 1)); + and(eax, ebx); break; case Instruction::FlowControlType::JustX: - MOV(32, R(RAX), R(COND0)); - XOR(32, R(RAX), Imm32(instr.flow_control.refx.Value() ^ 1)); + mov(eax, COND0); + xor(eax, (instr.flow_control.refx.Value() ^ 1)); break; case Instruction::FlowControlType::JustY: - MOV(32, R(RAX), R(COND1)); - XOR(32, R(RAX), Imm32(instr.flow_control.refy.Value() ^ 1)); + mov(eax, COND1); + xor(eax, (instr.flow_control.refy.Value() ^ 1)); break; } } void JitShader::Compile_UniformCondition(Instruction instr) { - int offset = + size_t offset = ShaderSetup::UniformOffset(RegisterType::BoolUniform, instr.flow_control.bool_uniform_id); - CMP(sizeof(bool) * 8, MDisp(SETUP, offset), Imm8(0)); + cmp(byte[SETUP + offset], 0); } BitSet32 JitShader::PersistentCallerSavedRegs() { @@ -350,7 +360,7 @@ BitSet32 JitShader::PersistentCallerSavedRegs() { void JitShader::Compile_ADD(Instruction instr) { Compile_SwizzleSrc(instr, 1, instr.common.src1, SRC1); Compile_SwizzleSrc(instr, 2, instr.common.src2, SRC2); - ADDPS(SRC1, R(SRC2)); + addps(SRC1, SRC2); Compile_DestEnable(instr, SRC1); } @@ -360,15 +370,15 @@ void JitShader::Compile_DP3(Instruction instr) { Compile_SanitizedMul(SRC1, SRC2, SCRATCH); - MOVAPS(SRC2, R(SRC1)); - SHUFPS(SRC2, R(SRC2), _MM_SHUFFLE(1, 1, 1, 1)); + movaps(SRC2, SRC1); + shufps(SRC2, SRC2, _MM_SHUFFLE(1, 1, 1, 1)); - MOVAPS(SRC3, R(SRC1)); - SHUFPS(SRC3, R(SRC3), _MM_SHUFFLE(2, 2, 2, 2)); + movaps(SRC3, SRC1); + shufps(SRC3, SRC3, _MM_SHUFFLE(2, 2, 2, 2)); - SHUFPS(SRC1, R(SRC1), _MM_SHUFFLE(0, 0, 0, 0)); - ADDPS(SRC1, R(SRC2)); - ADDPS(SRC1, R(SRC3)); + shufps(SRC1, SRC1, _MM_SHUFFLE(0, 0, 0, 0)); + addps(SRC1, SRC2); + addps(SRC1, SRC3); Compile_DestEnable(instr, SRC1); } @@ -379,13 +389,13 @@ void JitShader::Compile_DP4(Instruction instr) { Compile_SanitizedMul(SRC1, SRC2, SCRATCH); - MOVAPS(SRC2, R(SRC1)); - SHUFPS(SRC1, R(SRC1), _MM_SHUFFLE(2, 3, 0, 1)); // XYZW -> ZWXY - ADDPS(SRC1, R(SRC2)); + movaps(SRC2, SRC1); + shufps(SRC1, SRC1, _MM_SHUFFLE(2, 3, 0, 1)); // XYZW -> ZWXY + addps(SRC1, SRC2); - MOVAPS(SRC2, R(SRC1)); - SHUFPS(SRC1, R(SRC1), _MM_SHUFFLE(0, 1, 2, 3)); // XYZW -> WZYX - ADDPS(SRC1, R(SRC2)); + movaps(SRC2, SRC1); + shufps(SRC1, SRC1, _MM_SHUFFLE(0, 1, 2, 3)); // XYZW -> WZYX + addps(SRC1, SRC2); Compile_DestEnable(instr, SRC1); } @@ -401,50 +411,50 @@ void JitShader::Compile_DPH(Instruction instr) { if (Common::GetCPUCaps().sse4_1) { // Set 4th component to 1.0 - BLENDPS(SRC1, R(ONE), 0x8); // 0b1000 + blendps(SRC1, ONE, 0b1000); } else { // Set 4th component to 1.0 - MOVAPS(SCRATCH, R(SRC1)); - UNPCKHPS(SCRATCH, R(ONE)); // XYZW, 1111 -> Z1__ - UNPCKLPD(SRC1, R(SCRATCH)); // XYZW, Z1__ -> XYZ1 + movaps(SCRATCH, SRC1); + unpckhps(SCRATCH, ONE); // XYZW, 1111 -> Z1__ + unpcklpd(SRC1, SCRATCH); // XYZW, Z1__ -> XYZ1 } Compile_SanitizedMul(SRC1, SRC2, SCRATCH); - MOVAPS(SRC2, R(SRC1)); - SHUFPS(SRC1, R(SRC1), _MM_SHUFFLE(2, 3, 0, 1)); // XYZW -> ZWXY - ADDPS(SRC1, R(SRC2)); + movaps(SRC2, SRC1); + shufps(SRC1, SRC1, _MM_SHUFFLE(2, 3, 0, 1)); // XYZW -> ZWXY + addps(SRC1, SRC2); - MOVAPS(SRC2, R(SRC1)); - SHUFPS(SRC1, R(SRC1), _MM_SHUFFLE(0, 1, 2, 3)); // XYZW -> WZYX - ADDPS(SRC1, R(SRC2)); + movaps(SRC2, SRC1); + shufps(SRC1, SRC1, _MM_SHUFFLE(0, 1, 2, 3)); // XYZW -> WZYX + addps(SRC1, SRC2); Compile_DestEnable(instr, SRC1); } void JitShader::Compile_EX2(Instruction instr) { Compile_SwizzleSrc(instr, 1, instr.common.src1, SRC1); - MOVSS(XMM0, R(SRC1)); + movss(xmm0, SRC1); // ABI_PARAM1 - ABI_PushRegistersAndAdjustStack(PersistentCallerSavedRegs(), 0); - ABI_CallFunction(reinterpret_cast<const void*>(exp2f)); - ABI_PopRegistersAndAdjustStack(PersistentCallerSavedRegs(), 0); + ABI_PushRegistersAndAdjustStack(*this, PersistentCallerSavedRegs(), 0); + CallFarFunction(*this, exp2f); + ABI_PopRegistersAndAdjustStack(*this, PersistentCallerSavedRegs(), 0); - SHUFPS(XMM0, R(XMM0), _MM_SHUFFLE(0, 0, 0, 0)); - MOVAPS(SRC1, R(XMM0)); + shufps(xmm0, xmm0, _MM_SHUFFLE(0, 0, 0, 0)); // ABI_RETURN + movaps(SRC1, xmm0); Compile_DestEnable(instr, SRC1); } void JitShader::Compile_LG2(Instruction instr) { Compile_SwizzleSrc(instr, 1, instr.common.src1, SRC1); - MOVSS(XMM0, R(SRC1)); + movss(xmm0, SRC1); // ABI_PARAM1 - ABI_PushRegistersAndAdjustStack(PersistentCallerSavedRegs(), 0); - ABI_CallFunction(reinterpret_cast<const void*>(log2f)); - ABI_PopRegistersAndAdjustStack(PersistentCallerSavedRegs(), 0); + ABI_PushRegistersAndAdjustStack(*this, PersistentCallerSavedRegs(), 0); + CallFarFunction(*this, log2f); + ABI_PopRegistersAndAdjustStack(*this, PersistentCallerSavedRegs(), 0); - SHUFPS(XMM0, R(XMM0), _MM_SHUFFLE(0, 0, 0, 0)); - MOVAPS(SRC1, R(XMM0)); + shufps(xmm0, xmm0, _MM_SHUFFLE(0, 0, 0, 0)); // ABI_RETURN + movaps(SRC1, xmm0); Compile_DestEnable(instr, SRC1); } @@ -464,8 +474,8 @@ void JitShader::Compile_SGE(Instruction instr) { Compile_SwizzleSrc(instr, 2, instr.common.src2, SRC2); } - CMPPS(SRC2, R(SRC1), CMP_LE); - ANDPS(SRC2, R(ONE)); + cmpleps(SRC2, SRC1); + andps(SRC2, ONE); Compile_DestEnable(instr, SRC2); } @@ -479,8 +489,8 @@ void JitShader::Compile_SLT(Instruction instr) { Compile_SwizzleSrc(instr, 2, instr.common.src2, SRC2); } - CMPPS(SRC1, R(SRC2), CMP_LT); - ANDPS(SRC1, R(ONE)); + cmpltps(SRC1, SRC2); + andps(SRC1, ONE); Compile_DestEnable(instr, SRC1); } @@ -489,10 +499,10 @@ void JitShader::Compile_FLR(Instruction instr) { Compile_SwizzleSrc(instr, 1, instr.common.src1, SRC1); if (Common::GetCPUCaps().sse4_1) { - ROUNDFLOORPS(SRC1, R(SRC1)); + roundps(SRC1, SRC1, _MM_FROUND_FLOOR); } else { - CVTTPS2DQ(SRC1, R(SRC1)); - CVTDQ2PS(SRC1, R(SRC1)); + cvttps2dq(SRC1, SRC1); + cvtdq2ps(SRC1, SRC1); } Compile_DestEnable(instr, SRC1); @@ -502,7 +512,7 @@ void JitShader::Compile_MAX(Instruction instr) { Compile_SwizzleSrc(instr, 1, instr.common.src1, SRC1); Compile_SwizzleSrc(instr, 2, instr.common.src2, SRC2); // SSE semantics match PICA200 ones: In case of NaN, SRC2 is returned. - MAXPS(SRC1, R(SRC2)); + maxps(SRC1, SRC2); Compile_DestEnable(instr, SRC1); } @@ -510,7 +520,7 @@ void JitShader::Compile_MIN(Instruction instr) { Compile_SwizzleSrc(instr, 1, instr.common.src1, SRC1); Compile_SwizzleSrc(instr, 2, instr.common.src2, SRC2); // SSE semantics match PICA200 ones: In case of NaN, SRC2 is returned. - MINPS(SRC1, R(SRC2)); + minps(SRC1, SRC2); Compile_DestEnable(instr, SRC1); } @@ -524,37 +534,37 @@ void JitShader::Compile_MOVA(Instruction instr) { Compile_SwizzleSrc(instr, 1, instr.common.src1, SRC1); // Convert floats to integers using truncation (only care about X and Y components) - CVTTPS2DQ(SRC1, R(SRC1)); + cvttps2dq(SRC1, SRC1); // Get result - MOVQ_xmm(R(RAX), SRC1); + movq(rax, SRC1); // Handle destination enable if (swiz.DestComponentEnabled(0) && swiz.DestComponentEnabled(1)) { // Move and sign-extend low 32 bits - MOVSX(64, 32, ADDROFFS_REG_0, R(RAX)); + movsxd(ADDROFFS_REG_0, eax); // Move and sign-extend high 32 bits - SHR(64, R(RAX), Imm8(32)); - MOVSX(64, 32, ADDROFFS_REG_1, R(RAX)); + shr(rax, 32); + movsxd(ADDROFFS_REG_1, eax); // Multiply by 16 to be used as an offset later - SHL(64, R(ADDROFFS_REG_0), Imm8(4)); - SHL(64, R(ADDROFFS_REG_1), Imm8(4)); + shl(ADDROFFS_REG_0, 4); + shl(ADDROFFS_REG_1, 4); } else { if (swiz.DestComponentEnabled(0)) { // Move and sign-extend low 32 bits - MOVSX(64, 32, ADDROFFS_REG_0, R(RAX)); + movsxd(ADDROFFS_REG_0, eax); // Multiply by 16 to be used as an offset later - SHL(64, R(ADDROFFS_REG_0), Imm8(4)); + shl(ADDROFFS_REG_0, 4); } else if (swiz.DestComponentEnabled(1)) { // Move and sign-extend high 32 bits - SHR(64, R(RAX), Imm8(32)); - MOVSX(64, 32, ADDROFFS_REG_1, R(RAX)); + shr(rax, 32); + movsxd(ADDROFFS_REG_1, eax); // Multiply by 16 to be used as an offset later - SHL(64, R(ADDROFFS_REG_1), Imm8(4)); + shl(ADDROFFS_REG_1, 4); } } } @@ -569,8 +579,8 @@ void JitShader::Compile_RCP(Instruction instr) { // TODO(bunnei): RCPSS is a pretty rough approximation, this might cause problems if Pica // performs this operation more accurately. This should be checked on hardware. - RCPSS(SRC1, R(SRC1)); - SHUFPS(SRC1, R(SRC1), _MM_SHUFFLE(0, 0, 0, 0)); // XYWZ -> XXXX + rcpss(SRC1, SRC1); + shufps(SRC1, SRC1, _MM_SHUFFLE(0, 0, 0, 0)); // XYWZ -> XXXX Compile_DestEnable(instr, SRC1); } @@ -580,8 +590,8 @@ void JitShader::Compile_RSQ(Instruction instr) { // TODO(bunnei): RSQRTSS is a pretty rough approximation, this might cause problems if Pica // performs this operation more accurately. This should be checked on hardware. - RSQRTSS(SRC1, R(SRC1)); - SHUFPS(SRC1, R(SRC1), _MM_SHUFFLE(0, 0, 0, 0)); // XYWZ -> XXXX + rsqrtss(SRC1, SRC1); + shufps(SRC1, SRC1, _MM_SHUFFLE(0, 0, 0, 0)); // XYWZ -> XXXX Compile_DestEnable(instr, SRC1); } @@ -589,34 +599,35 @@ void JitShader::Compile_RSQ(Instruction instr) { void JitShader::Compile_NOP(Instruction instr) {} void JitShader::Compile_END(Instruction instr) { - ABI_PopRegistersAndAdjustStack(ABI_ALL_CALLEE_SAVED, 8); - RET(); + ABI_PopRegistersAndAdjustStack(*this, ABI_ALL_CALLEE_SAVED, 8); + ret(); } void JitShader::Compile_CALL(Instruction instr) { // Push offset of the return - PUSH(64, Imm32(instr.flow_control.dest_offset + instr.flow_control.num_instructions)); + push(qword, (instr.flow_control.dest_offset + instr.flow_control.num_instructions)); // Call the subroutine - FixupBranch b = CALL(); - fixup_branches.push_back({b, instr.flow_control.dest_offset}); + call(instruction_labels[instr.flow_control.dest_offset]); // Skip over the return offset that's on the stack - ADD(64, R(RSP), Imm32(8)); + add(rsp, 8); } void JitShader::Compile_CALLC(Instruction instr) { Compile_EvaluateCondition(instr); - FixupBranch b = J_CC(CC_Z, true); + Label b; + jz(b); Compile_CALL(instr); - SetJumpTarget(b); + L(b); } void JitShader::Compile_CALLU(Instruction instr) { Compile_UniformCondition(instr); - FixupBranch b = J_CC(CC_Z, true); + Label b; + jz(b); Compile_CALL(instr); - SetJumpTarget(b); + L(b); } void JitShader::Compile_CMP(Instruction instr) { @@ -633,33 +644,33 @@ void JitShader::Compile_CMP(Instruction instr) { static const u8 cmp[] = {CMP_EQ, CMP_NEQ, CMP_LT, CMP_LE, CMP_LT, CMP_LE}; bool invert_op_x = (op_x == Op::GreaterThan || op_x == Op::GreaterEqual); - Gen::X64Reg lhs_x = invert_op_x ? SRC2 : SRC1; - Gen::X64Reg rhs_x = invert_op_x ? SRC1 : SRC2; + Xmm lhs_x = invert_op_x ? SRC2 : SRC1; + Xmm rhs_x = invert_op_x ? SRC1 : SRC2; if (op_x == op_y) { // Compare X-component and Y-component together - CMPPS(lhs_x, R(rhs_x), cmp[op_x]); - MOVQ_xmm(R(COND0), lhs_x); + cmpps(lhs_x, rhs_x, cmp[op_x]); + movq(COND0, lhs_x); - MOV(64, R(COND1), R(COND0)); + mov(COND1, COND0); } else { bool invert_op_y = (op_y == Op::GreaterThan || op_y == Op::GreaterEqual); - Gen::X64Reg lhs_y = invert_op_y ? SRC2 : SRC1; - Gen::X64Reg rhs_y = invert_op_y ? SRC1 : SRC2; + Xmm lhs_y = invert_op_y ? SRC2 : SRC1; + Xmm rhs_y = invert_op_y ? SRC1 : SRC2; // Compare X-component - MOVAPS(SCRATCH, R(lhs_x)); - CMPSS(SCRATCH, R(rhs_x), cmp[op_x]); + movaps(SCRATCH, lhs_x); + cmpss(SCRATCH, rhs_x, cmp[op_x]); // Compare Y-component - CMPPS(lhs_y, R(rhs_y), cmp[op_y]); + cmpps(lhs_y, rhs_y, cmp[op_y]); - MOVQ_xmm(R(COND0), SCRATCH); - MOVQ_xmm(R(COND1), lhs_y); + movq(COND0, SCRATCH); + movq(COND1, lhs_y); } - SHR(32, R(COND0), Imm8(31)); - SHR(64, R(COND1), Imm8(63)); + shr(COND0.cvt32(), 31); // ignores upper 32 bits in source + shr(COND1, 63); } void JitShader::Compile_MAD(Instruction instr) { @@ -674,7 +685,7 @@ void JitShader::Compile_MAD(Instruction instr) { } Compile_SanitizedMul(SRC1, SRC2, SCRATCH); - ADDPS(SRC1, R(SRC3)); + addps(SRC1, SRC3); Compile_DestEnable(instr, SRC1); } @@ -682,6 +693,7 @@ void JitShader::Compile_MAD(Instruction instr) { void JitShader::Compile_IF(Instruction instr) { Compile_Assert(instr.flow_control.dest_offset >= program_counter, "Backwards if-statements not supported"); + Label l_else, l_endif; // Evaluate the "IF" condition if (instr.opcode.Value() == OpCode::Id::IFU) { @@ -689,26 +701,25 @@ void JitShader::Compile_IF(Instruction instr) { } else if (instr.opcode.Value() == OpCode::Id::IFC) { Compile_EvaluateCondition(instr); } - FixupBranch b = J_CC(CC_Z, true); + jz(l_else, T_NEAR); // Compile the code that corresponds to the condition evaluating as true Compile_Block(instr.flow_control.dest_offset); // If there isn't an "ELSE" condition, we are done here if (instr.flow_control.num_instructions == 0) { - SetJumpTarget(b); + L(l_else); return; } - FixupBranch b2 = J(true); - - SetJumpTarget(b); + jmp(l_endif, T_NEAR); + L(l_else); // This code corresponds to the "ELSE" condition // Comple the code that corresponds to the condition evaluating as false Compile_Block(instr.flow_control.dest_offset + instr.flow_control.num_instructions); - SetJumpTarget(b2); + L(l_endif); } void JitShader::Compile_LOOP(Instruction instr) { @@ -721,25 +732,26 @@ void JitShader::Compile_LOOP(Instruction instr) { // This decodes the fields from the integer uniform at index instr.flow_control.int_uniform_id. // The Y (LOOPCOUNT_REG) and Z (LOOPINC) component are kept multiplied by 16 (Left shifted by // 4 bits) to be used as an offset into the 16-byte vector registers later - int offset = + size_t offset = ShaderSetup::UniformOffset(RegisterType::IntUniform, instr.flow_control.int_uniform_id); - MOV(32, R(LOOPCOUNT), MDisp(SETUP, offset)); - MOV(32, R(LOOPCOUNT_REG), R(LOOPCOUNT)); - SHR(32, R(LOOPCOUNT_REG), Imm8(4)); - AND(32, R(LOOPCOUNT_REG), Imm32(0xFF0)); // Y-component is the start - MOV(32, R(LOOPINC), R(LOOPCOUNT)); - SHR(32, R(LOOPINC), Imm8(12)); - AND(32, R(LOOPINC), Imm32(0xFF0)); // Z-component is the incrementer - MOVZX(32, 8, LOOPCOUNT, R(LOOPCOUNT)); // X-component is iteration count - ADD(32, R(LOOPCOUNT), Imm8(1)); // Iteration count is X-component + 1 - - auto loop_start = GetCodePtr(); + mov(LOOPCOUNT, dword[SETUP + offset]); + mov(LOOPCOUNT_REG, LOOPCOUNT); + shr(LOOPCOUNT_REG, 4); + and(LOOPCOUNT_REG, 0xFF0); // Y-component is the start + mov(LOOPINC, LOOPCOUNT); + shr(LOOPINC, 12); + and(LOOPINC, 0xFF0); // Z-component is the incrementer + movzx(LOOPCOUNT, LOOPCOUNT.cvt8()); // X-component is iteration count + add(LOOPCOUNT, 1); // Iteration count is X-component + 1 + + Label l_loop_start; + L(l_loop_start); Compile_Block(instr.flow_control.dest_offset + 1); - ADD(32, R(LOOPCOUNT_REG), R(LOOPINC)); // Increment LOOPCOUNT_REG by Z-component - SUB(32, R(LOOPCOUNT), Imm8(1)); // Increment loop count by 1 - J_CC(CC_NZ, loop_start); // Loop if not equal + add(LOOPCOUNT_REG, LOOPINC); // Increment LOOPCOUNT_REG by Z-component + sub(LOOPCOUNT, 1); // Increment loop count by 1 + jnz(l_loop_start); // Loop if not equal looping = false; } @@ -755,8 +767,12 @@ void JitShader::Compile_JMP(Instruction instr) { bool inverted_condition = (instr.opcode.Value() == OpCode::Id::JMPU) && (instr.flow_control.num_instructions & 1); - FixupBranch b = J_CC(inverted_condition ? CC_Z : CC_NZ, true); - fixup_branches.push_back({b, instr.flow_control.dest_offset}); + Label& b = instruction_labels[instr.flow_control.dest_offset]; + if (inverted_condition) { + jz(b, T_NEAR); + } else { + jnz(b, T_NEAR); + } } void JitShader::Compile_Block(unsigned end) { @@ -767,13 +783,14 @@ void JitShader::Compile_Block(unsigned end) { void JitShader::Compile_Return() { // Peek return offset on the stack and check if we're at that offset - MOV(64, R(RAX), MDisp(RSP, 8)); - CMP(32, R(RAX), Imm32(program_counter)); + mov(rax, qword[rsp + 8]); + cmp(eax, (program_counter)); // If so, jump back to before CALL - FixupBranch b = J_CC(CC_NZ, true); - RET(); - SetJumpTarget(b); + Label b; + jnz(b); + ret(); + L(b); } void JitShader::Compile_NextInstr() { @@ -781,9 +798,7 @@ void JitShader::Compile_NextInstr() { Compile_Return(); } - ASSERT_MSG(code_ptr[program_counter] == nullptr, - "Tried to compile already compiled shader location!"); - code_ptr[program_counter] = GetCodePtr(); + L(instruction_labels[program_counter]); Instruction instr = GetVertexShaderInstruction(program_counter++); @@ -824,64 +839,53 @@ void JitShader::FindReturnOffsets() { void JitShader::Compile() { // Reset flow control state - program = (CompiledShader*)GetCodePtr(); + program = (CompiledShader*)getCurr(); program_counter = 0; looping = false; - code_ptr.fill(nullptr); - fixup_branches.clear(); + instruction_labels.fill(Xbyak::Label()); // Find all `CALL` instructions and identify return locations FindReturnOffsets(); // The stack pointer is 8 modulo 16 at the entry of a procedure - ABI_PushRegistersAndAdjustStack(ABI_ALL_CALLEE_SAVED, 8); + ABI_PushRegistersAndAdjustStack(*this, ABI_ALL_CALLEE_SAVED, 8); - MOV(PTRBITS, R(SETUP), R(ABI_PARAM1)); - MOV(PTRBITS, R(STATE), R(ABI_PARAM2)); + mov(SETUP, ABI_PARAM1); + mov(STATE, ABI_PARAM2); // Zero address/loop registers - XOR(64, R(ADDROFFS_REG_0), R(ADDROFFS_REG_0)); - XOR(64, R(ADDROFFS_REG_1), R(ADDROFFS_REG_1)); - XOR(64, R(LOOPCOUNT_REG), R(LOOPCOUNT_REG)); + xor(ADDROFFS_REG_0.cvt32(), ADDROFFS_REG_0.cvt32()); + xor(ADDROFFS_REG_1.cvt32(), ADDROFFS_REG_1.cvt32()); + xor(LOOPCOUNT_REG, LOOPCOUNT_REG); // Used to set a register to one static const __m128 one = {1.f, 1.f, 1.f, 1.f}; - MOV(PTRBITS, R(RAX), ImmPtr(&one)); - MOVAPS(ONE, MatR(RAX)); + mov(rax, reinterpret_cast<size_t>(&one)); + movaps(ONE, xword[rax]); // Used to negate registers static const __m128 neg = {-0.f, -0.f, -0.f, -0.f}; - MOV(PTRBITS, R(RAX), ImmPtr(&neg)); - MOVAPS(NEGBIT, MatR(RAX)); + mov(rax, reinterpret_cast<size_t>(&neg)); + movaps(NEGBIT, xword[rax]); // Jump to start of the shader program - JMPptr(R(ABI_PARAM3)); + jmp(ABI_PARAM3); // Compile entire program Compile_Block(static_cast<unsigned>(g_state.vs.program_code.size())); - // Set the target for any incomplete branches now that the entire shader program has been - // emitted - for (const auto& branch : fixup_branches) { - SetJumpTarget(branch.first, code_ptr[branch.second]); - } - // Free memory that's no longer needed return_offsets.clear(); return_offsets.shrink_to_fit(); - fixup_branches.clear(); - fixup_branches.shrink_to_fit(); - uintptr_t size = - reinterpret_cast<uintptr_t>(GetCodePtr()) - reinterpret_cast<uintptr_t>(program); - ASSERT_MSG(size <= MAX_SHADER_SIZE, "Compiled a shader that exceeds the allocated size!"); + ready(); + uintptr_t size = reinterpret_cast<uintptr_t>(getCurr()) - reinterpret_cast<uintptr_t>(program); + ASSERT_MSG(size <= MAX_SHADER_SIZE, "Compiled a shader that exceeds the allocated size!"); LOG_DEBUG(HW_GPU, "Compiled shader size=%lu", size); } -JitShader::JitShader() { - AllocCodeSpace(MAX_SHADER_SIZE); -} +JitShader::JitShader() : Xbyak::CodeGenerator(MAX_SHADER_SIZE) {} } // namespace Shader diff --git a/src/video_core/shader/shader_jit_x64.h b/src/video_core/shader/shader_jit_x64.h index 98de5ecef..e0ecde3f2 100644 --- a/src/video_core/shader/shader_jit_x64.h +++ b/src/video_core/shader/shader_jit_x64.h @@ -9,6 +9,7 @@ #include <utility> #include <vector> #include <nihstro/shader_bytecode.h> +#include <xbyak.h> #include "common/bit_set.h" #include "common/common_types.h" #include "common/x64/emitter.h" @@ -29,12 +30,12 @@ constexpr size_t MAX_SHADER_SIZE = 1024 * 64; * This class implements the shader JIT compiler. It recompiles a Pica shader program into x86_64 * code that can be executed on the host machine directly. */ -class JitShader : public Gen::XCodeBlock { +class JitShader : public Xbyak::CodeGenerator { public: JitShader(); void Run(const ShaderSetup& setup, UnitState<false>& state, unsigned offset) const { - program(&setup, &state, code_ptr[offset]); + program(&setup, &state, instruction_labels[offset].getAddress()); } void Compile(); @@ -71,14 +72,14 @@ private: void Compile_NextInstr(); void Compile_SwizzleSrc(Instruction instr, unsigned src_num, SourceRegister src_reg, - Gen::X64Reg dest); - void Compile_DestEnable(Instruction instr, Gen::X64Reg dest); + Xbyak::Xmm dest); + void Compile_DestEnable(Instruction instr, Xbyak::Xmm dest); /** * Compiles a `MUL src1, src2` operation, properly handling the PICA semantics when multiplying * zero by inf. Clobbers `src2` and `scratch`. */ - void Compile_SanitizedMul(Gen::X64Reg src1, Gen::X64Reg src2, Gen::X64Reg scratch); + void Compile_SanitizedMul(Xbyak::Xmm src1, Xbyak::Xmm src2, Xbyak::Xmm scratch); void Compile_EvaluateCondition(Instruction instr); void Compile_UniformCondition(Instruction instr); @@ -103,7 +104,7 @@ private: void FindReturnOffsets(); /// Mapping of Pica VS instructions to pointers in the emitted code - std::array<const u8*, 1024> code_ptr; + std::array<Xbyak::Label, 1024> instruction_labels; /// Offsets in code where a return needs to be inserted std::vector<unsigned> return_offsets; @@ -111,9 +112,6 @@ private: unsigned program_counter = 0; ///< Offset of the next instruction to decode bool looping = false; ///< True if compiling a loop, used to check for nested loops - /// Branches that need to be fixed up once the entire shader program is compiled - std::vector<std::pair<Gen::FixupBranch, unsigned>> fixup_branches; - using CompiledShader = void(const void* setup, void* state, const u8* start_addr); CompiledShader* program = nullptr; }; |