mirror of
				https://github.com/PabloMK7/citra.git
				synced 2025-10-30 21:30:04 +00:00 
			
		
		
		
	Merge branch 'master' of github.com:citra-emu/citra into ips-patches
This commit is contained in:
		
						commit
						1ded48f5a3
					
				
					 209 changed files with 17211 additions and 9498 deletions
				
			
		|  | @ -56,7 +56,8 @@ CubebSink::CubebSink(std::string target_device_name) : impl(std::make_unique<Imp | |||
|             const auto collection_end{collection.device + collection.count}; | ||||
|             const auto device{ | ||||
|                 std::find_if(collection.device, collection_end, [&](const cubeb_device_info& info) { | ||||
|                     return target_device_name == info.friendly_name; | ||||
|                     return info.friendly_name != nullptr && | ||||
|                            target_device_name == info.friendly_name; | ||||
|                 })}; | ||||
|             if (device != collection_end) { | ||||
|                 output_device = device->devid; | ||||
|  |  | |||
|  | @ -12,6 +12,7 @@ | |||
| #include "common/assert.h" | ||||
| #include "common/common_types.h" | ||||
| #include "common/logging/log.h" | ||||
| #include "core/core.h" | ||||
| #include "core/core_timing.h" | ||||
| 
 | ||||
| using InterruptType = Service::DSP::DSP_DSP::InterruptType; | ||||
|  | @ -63,7 +64,7 @@ private: | |||
|     HLE::Mixers mixers; | ||||
| 
 | ||||
|     DspHle& parent; | ||||
|     CoreTiming::EventType* tick_event; | ||||
|     Core::TimingEventType* tick_event; | ||||
| 
 | ||||
|     std::weak_ptr<DSP_DSP> dsp_dsp; | ||||
| }; | ||||
|  | @ -71,15 +72,17 @@ private: | |||
| DspHle::Impl::Impl(DspHle& parent_) : parent(parent_) { | ||||
|     dsp_memory.raw_memory.fill(0); | ||||
| 
 | ||||
|     Core::Timing& timing = Core::System::GetInstance().CoreTiming(); | ||||
|     tick_event = | ||||
|         CoreTiming::RegisterEvent("AudioCore::DspHle::tick_event", [this](u64, s64 cycles_late) { | ||||
|         timing.RegisterEvent("AudioCore::DspHle::tick_event", [this](u64, s64 cycles_late) { | ||||
|             this->AudioTickCallback(cycles_late); | ||||
|         }); | ||||
|     CoreTiming::ScheduleEvent(audio_frame_ticks, tick_event); | ||||
|     timing.ScheduleEvent(audio_frame_ticks, tick_event); | ||||
| } | ||||
| 
 | ||||
| DspHle::Impl::~Impl() { | ||||
|     CoreTiming::UnscheduleEvent(tick_event, 0); | ||||
|     Core::Timing& timing = Core::System::GetInstance().CoreTiming(); | ||||
|     timing.UnscheduleEvent(tick_event, 0); | ||||
| } | ||||
| 
 | ||||
| DspState DspHle::Impl::GetDspState() const { | ||||
|  | @ -328,7 +331,8 @@ void DspHle::Impl::AudioTickCallback(s64 cycles_late) { | |||
|     } | ||||
| 
 | ||||
|     // Reschedule recurrent event
 | ||||
|     CoreTiming::ScheduleEvent(audio_frame_ticks - cycles_late, tick_event); | ||||
|     Core::Timing& timing = Core::System::GetInstance().CoreTiming(); | ||||
|     timing.ScheduleEvent(audio_frame_ticks - cycles_late, tick_event); | ||||
| } | ||||
| 
 | ||||
| DspHle::DspHle() : impl(std::make_unique<Impl>(*this)) {} | ||||
|  |  | |||
|  | @ -14,6 +14,8 @@ add_executable(citra-qt | |||
|     applets/swkbd.h | ||||
|     bootmanager.cpp | ||||
|     bootmanager.h | ||||
|     compatibility_list.cpp | ||||
|     compatibility_list.h | ||||
|     camera/camera_util.cpp | ||||
|     camera/camera_util.h | ||||
|     camera/still_image_camera.cpp | ||||
|  | @ -76,6 +78,8 @@ add_executable(citra-qt | |||
|     game_list.cpp | ||||
|     game_list.h | ||||
|     game_list_p.h | ||||
|     game_list_worker.cpp | ||||
|     game_list_worker.h | ||||
|     hotkeys.cpp | ||||
|     hotkeys.h | ||||
|     main.cpp | ||||
|  | @ -136,15 +140,15 @@ set(UIS | |||
| ) | ||||
| 
 | ||||
| file(GLOB COMPAT_LIST | ||||
|      ${CMAKE_BINARY_DIR}/dist/compatibility_list/compatibility_list.qrc | ||||
|      ${CMAKE_BINARY_DIR}/dist/compatibility_list/compatibility_list.json) | ||||
| file(GLOB_RECURSE ICONS ${CMAKE_SOURCE_DIR}/dist/icons/*) | ||||
| file(GLOB_RECURSE THEMES ${CMAKE_SOURCE_DIR}/dist/qt_themes/*) | ||||
|      ${PROJECT_BINARY_DIR}/dist/compatibility_list/compatibility_list.qrc | ||||
|      ${PROJECT_BINARY_DIR}/dist/compatibility_list/compatibility_list.json) | ||||
| file(GLOB_RECURSE ICONS ${PROJECT_SOURCE_DIR}/dist/icons/*) | ||||
| file(GLOB_RECURSE THEMES ${PROJECT_SOURCE_DIR}/dist/qt_themes/*) | ||||
| 
 | ||||
| qt5_wrap_ui(UI_HDRS ${UIS}) | ||||
| 
 | ||||
| if (ENABLE_QT_TRANSLATION) | ||||
|     set(CITRA_QT_LANGUAGES "${CMAKE_SOURCE_DIR}/dist/languages" CACHE PATH "Path to the translation bundle for the Qt frontend") | ||||
|     set(CITRA_QT_LANGUAGES "${PROJECT_SOURCE_DIR}/dist/languages" CACHE PATH "Path to the translation bundle for the Qt frontend") | ||||
|     option(GENERATE_QT_TRANSLATION "Generate en.ts as the translation source file" OFF) | ||||
| 
 | ||||
|     # Update source TS file if enabled | ||||
|  | @ -209,7 +213,7 @@ target_link_libraries(citra-qt PRIVATE Boost::boost glad nihstro-headers Qt5::Op | |||
| target_link_libraries(citra-qt PRIVATE ${PLATFORM_LIBRARIES} Threads::Threads) | ||||
| 
 | ||||
| if (CITRA_ENABLE_COMPATIBILITY_REPORTING) | ||||
|     add_definitions(-DCITRA_ENABLE_COMPATIBILITY_REPORTING) | ||||
|     target_compile_definitions(citra-qt PRIVATE -DCITRA_ENABLE_COMPATIBILITY_REPORTING) | ||||
| endif() | ||||
| 
 | ||||
| if (USE_DISCORD_PRESENCE) | ||||
|  |  | |||
|  | @ -109,9 +109,8 @@ private: | |||
| GRenderWindow::GRenderWindow(QWidget* parent, EmuThread* emu_thread) | ||||
|     : QWidget(parent), child(nullptr), emu_thread(emu_thread) { | ||||
| 
 | ||||
|     std::string window_title = fmt::format("Citra {} | {}-{}", Common::g_build_name, | ||||
|                                            Common::g_scm_branch, Common::g_scm_desc); | ||||
|     setWindowTitle(QString::fromStdString(window_title)); | ||||
|     setWindowTitle(QStringLiteral("Citra %1 | %2-%3") | ||||
|                        .arg(Common::g_build_name, Common::g_scm_branch, Common::g_scm_desc)); | ||||
|     setAttribute(Qt::WA_AcceptTouchEvents); | ||||
| 
 | ||||
|     InputCommon::Init(); | ||||
|  |  | |||
|  | @ -5,6 +5,7 @@ | |||
| #include <QButtonGroup> | ||||
| #include <QMessageBox> | ||||
| #include <QPushButton> | ||||
| #include <QtConcurrent/qtconcurrentrun.h> | ||||
| #include "citra_qt/compatdb.h" | ||||
| #include "common/telemetry.h" | ||||
| #include "core/core.h" | ||||
|  | @ -21,6 +22,8 @@ CompatDB::CompatDB(QWidget* parent) | |||
|     connect(ui->radioButton_IntroMenu, &QRadioButton::clicked, this, &CompatDB::EnableNext); | ||||
|     connect(ui->radioButton_WontBoot, &QRadioButton::clicked, this, &CompatDB::EnableNext); | ||||
|     connect(button(NextButton), &QPushButton::clicked, this, &CompatDB::Submit); | ||||
|     connect(&testcase_watcher, &QFutureWatcher<bool>::finished, this, | ||||
|             &CompatDB::OnTestcaseSubmitted); | ||||
| } | ||||
| 
 | ||||
| CompatDB::~CompatDB() = default; | ||||
|  | @ -46,18 +49,38 @@ void CompatDB::Submit() { | |||
|         } | ||||
|         break; | ||||
|     case CompatDBPage::Final: | ||||
|         back(); | ||||
|         LOG_DEBUG(Frontend, "Compatibility Rating: {}", compatibility->checkedId()); | ||||
|         Core::Telemetry().AddField(Telemetry::FieldType::UserFeedback, "Compatibility", | ||||
|                                    compatibility->checkedId()); | ||||
|         // older versions of QT don't support the "NoCancelButtonOnLastPage" option, this is a
 | ||||
|         // workaround
 | ||||
| 
 | ||||
|         button(NextButton)->setEnabled(false); | ||||
|         button(NextButton)->setText(tr("Submitting")); | ||||
|         button(QWizard::CancelButton)->setVisible(false); | ||||
| 
 | ||||
|         testcase_watcher.setFuture(QtConcurrent::run( | ||||
|             [this]() { return Core::System::GetInstance().TelemetrySession().SubmitTestcase(); })); | ||||
|         break; | ||||
|     default: | ||||
|         LOG_ERROR(Frontend, "Unexpected page: {}", currentId()); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void CompatDB::OnTestcaseSubmitted() { | ||||
|     if (!testcase_watcher.result()) { | ||||
|         QMessageBox::critical(this, tr("Communication error"), | ||||
|                               tr("An error occured while sending the Testcase")); | ||||
|         button(NextButton)->setEnabled(true); | ||||
|         button(NextButton)->setText(tr("Next")); | ||||
|         button(QWizard::CancelButton)->setVisible(true); | ||||
|     } else { | ||||
|         next(); | ||||
|         // older versions of QT don't support the "NoCancelButtonOnLastPage" option, this is a
 | ||||
|         // workaround
 | ||||
|         button(QWizard::CancelButton)->setVisible(false); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void CompatDB::EnableNext() { | ||||
|     button(NextButton)->setEnabled(true); | ||||
| } | ||||
|  |  | |||
|  | @ -5,6 +5,7 @@ | |||
| #pragma once | ||||
| 
 | ||||
| #include <memory> | ||||
| #include <QFutureWatcher> | ||||
| #include <QWizard> | ||||
| 
 | ||||
| namespace Ui { | ||||
|  | @ -19,8 +20,11 @@ public: | |||
|     ~CompatDB(); | ||||
| 
 | ||||
| private: | ||||
|     QFutureWatcher<bool> testcase_watcher; | ||||
| 
 | ||||
|     std::unique_ptr<Ui::CompatDB> ui; | ||||
| 
 | ||||
|     void Submit(); | ||||
|     void OnTestcaseSubmitted(); | ||||
|     void EnableNext(); | ||||
| }; | ||||
|  |  | |||
							
								
								
									
										16
									
								
								src/citra_qt/compatibility_list.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								src/citra_qt/compatibility_list.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,16 @@ | |||
| // Copyright 2018 yuzu Emulator Project
 | ||||
| // Licensed under GPLv2 or any later version
 | ||||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| #include <algorithm> | ||||
| #include <fmt/format.h> | ||||
| #include "citra_qt/compatibility_list.h" | ||||
| 
 | ||||
| CompatibilityList::const_iterator FindMatchingCompatibilityEntry( | ||||
|     const CompatibilityList& compatibility_list, u64 program_id) { | ||||
|     return std::find_if(compatibility_list.begin(), compatibility_list.end(), | ||||
|                         [program_id](const auto& element) { | ||||
|                             std::string pid = fmt::format("{:016X}", program_id); | ||||
|                             return element.first == pid; | ||||
|                         }); | ||||
| } | ||||
							
								
								
									
										15
									
								
								src/citra_qt/compatibility_list.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								src/citra_qt/compatibility_list.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,15 @@ | |||
| // Copyright 2018 yuzu Emulator Project
 | ||||
| // Licensed under GPLv2 or any later version
 | ||||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| #pragma once | ||||
| 
 | ||||
| #include <string> | ||||
| #include <unordered_map> | ||||
| #include <QString> | ||||
| #include "common/common_types.h" | ||||
| 
 | ||||
| using CompatibilityList = std::unordered_map<std::string, std::pair<QString, QString>>; | ||||
| 
 | ||||
| CompatibilityList::const_iterator FindMatchingCompatibilityEntry( | ||||
|     const CompatibilityList& compatibility_list, u64 program_id); | ||||
|  | @ -16,11 +16,16 @@ Config::Config() { | |||
|     // TODO: Don't hardcode the path; let the frontend decide where to put the config files.
 | ||||
|     qt_config_loc = FileUtil::GetUserPath(FileUtil::UserPath::ConfigDir) + "qt-config.ini"; | ||||
|     FileUtil::CreateFullPath(qt_config_loc); | ||||
|     qt_config = new QSettings(QString::fromStdString(qt_config_loc), QSettings::IniFormat); | ||||
|     qt_config = | ||||
|         std::make_unique<QSettings>(QString::fromStdString(qt_config_loc), QSettings::IniFormat); | ||||
| 
 | ||||
|     Reload(); | ||||
| } | ||||
| 
 | ||||
| Config::~Config() { | ||||
|     Save(); | ||||
| } | ||||
| 
 | ||||
| const std::array<int, Settings::NativeButton::NumButtons> Config::default_buttons = { | ||||
|     Qt::Key_A, Qt::Key_S, Qt::Key_Z, Qt::Key_X, Qt::Key_T, Qt::Key_G, Qt::Key_F, Qt::Key_H, | ||||
|     Qt::Key_Q, Qt::Key_W, Qt::Key_M, Qt::Key_N, Qt::Key_1, Qt::Key_2, Qt::Key_B, | ||||
|  | @ -561,9 +566,3 @@ void Config::Reload() { | |||
| void Config::Save() { | ||||
|     SaveValues(); | ||||
| } | ||||
| 
 | ||||
| Config::~Config() { | ||||
|     Save(); | ||||
| 
 | ||||
|     delete qt_config; | ||||
| } | ||||
|  |  | |||
|  | @ -5,6 +5,7 @@ | |||
| #pragma once | ||||
| 
 | ||||
| #include <array> | ||||
| #include <memory> | ||||
| #include <string> | ||||
| #include <QVariant> | ||||
| #include "core/settings.h" | ||||
|  | @ -12,16 +13,6 @@ | |||
| class QSettings; | ||||
| 
 | ||||
| class Config { | ||||
|     QSettings* qt_config; | ||||
|     std::string qt_config_loc; | ||||
| 
 | ||||
|     void ReadValues(); | ||||
|     void SaveValues(); | ||||
|     QVariant ReadSetting(const QString& name); | ||||
|     QVariant ReadSetting(const QString& name, const QVariant& default_value); | ||||
|     void WriteSetting(const QString& name, const QVariant& value); | ||||
|     void WriteSetting(const QString& name, const QVariant& value, const QVariant& default_value); | ||||
| 
 | ||||
| public: | ||||
|     Config(); | ||||
|     ~Config(); | ||||
|  | @ -31,4 +22,15 @@ public: | |||
| 
 | ||||
|     static const std::array<int, Settings::NativeButton::NumButtons> default_buttons; | ||||
|     static const std::array<std::array<int, 5>, Settings::NativeAnalog::NumAnalogs> default_analogs; | ||||
| 
 | ||||
| private: | ||||
|     void ReadValues(); | ||||
|     void SaveValues(); | ||||
|     QVariant ReadSetting(const QString& name); | ||||
|     QVariant ReadSetting(const QString& name, const QVariant& default_value); | ||||
|     void WriteSetting(const QString& name, const QVariant& value); | ||||
|     void WriteSetting(const QString& name, const QVariant& value, const QVariant& default_value); | ||||
| 
 | ||||
|     std::unique_ptr<QSettings> qt_config; | ||||
|     std::string qt_config_loc; | ||||
| }; | ||||
|  |  | |||
|  | @ -2,6 +2,7 @@ | |||
| // Licensed under GPLv2 or any later version
 | ||||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| #include <QMessageBox> | ||||
| #include "citra_qt/configuration/configure_general.h" | ||||
| #include "citra_qt/ui_settings.h" | ||||
| #include "core/core.h" | ||||
|  | @ -15,6 +16,8 @@ ConfigureGeneral::ConfigureGeneral(QWidget* parent) | |||
|     this->setConfiguration(); | ||||
| 
 | ||||
|     ui->updateBox->setVisible(UISettings::values.updater_found); | ||||
|     connect(ui->button_reset_defaults, &QPushButton::clicked, this, | ||||
|             &ConfigureGeneral::ResetDefaults); | ||||
| } | ||||
| 
 | ||||
| ConfigureGeneral::~ConfigureGeneral() = default; | ||||
|  | @ -33,6 +36,19 @@ void ConfigureGeneral::PopulateHotkeyList(const HotkeyRegistry& registry) { | |||
|     ui->hotkeysDialog->Populate(registry); | ||||
| } | ||||
| 
 | ||||
| void ConfigureGeneral::ResetDefaults() { | ||||
|     QMessageBox::StandardButton answer = QMessageBox::question( | ||||
|         this, tr("Citra"), | ||||
|         tr("Are you sure you want to <b>reset your settings</b> and close Citra?"), | ||||
|         QMessageBox::Yes | QMessageBox::No, QMessageBox::No); | ||||
| 
 | ||||
|     if (answer == QMessageBox::No) | ||||
|         return; | ||||
| 
 | ||||
|     FileUtil::Delete(FileUtil::GetUserPath(FileUtil::UserPath::ConfigDir) + "qt-config.ini"); | ||||
|     std::exit(0); | ||||
| } | ||||
| 
 | ||||
| void ConfigureGeneral::applyConfiguration() { | ||||
|     UISettings::values.confirm_before_closing = ui->toggle_check_exit->isChecked(); | ||||
| 
 | ||||
|  |  | |||
|  | @ -21,6 +21,7 @@ public: | |||
|     ~ConfigureGeneral(); | ||||
| 
 | ||||
|     void PopulateHotkeyList(const HotkeyRegistry& registry); | ||||
|     void ResetDefaults(); | ||||
|     void applyConfiguration(); | ||||
|     void retranslateUi(); | ||||
| 
 | ||||
|  |  | |||
|  | @ -147,6 +147,13 @@ | |||
|        </layout> | ||||
|       </widget> | ||||
|      </item> | ||||
|      <item alignment="Qt::AlignRight"> | ||||
|       <widget class="QPushButton" name="button_reset_defaults"> | ||||
|        <property name="text"> | ||||
|         <string>Reset All Settings</string> | ||||
|        </property> | ||||
|       </widget> | ||||
|      </item> | ||||
|     </layout> | ||||
|    </item> | ||||
|   </layout> | ||||
|  |  | |||
|  | @ -220,12 +220,12 @@ ConfigureSystem::ConfigureSystem(QWidget* parent) : QWidget(parent), ui(new Ui:: | |||
|     ui->setupUi(this); | ||||
|     connect(ui->combo_birthmonth, | ||||
|             static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, | ||||
|             &ConfigureSystem::updateBirthdayComboBox); | ||||
|             &ConfigureSystem::UpdateBirthdayComboBox); | ||||
|     connect(ui->combo_init_clock, | ||||
|             static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, | ||||
|             &ConfigureSystem::updateInitTime); | ||||
|             &ConfigureSystem::UpdateInitTime); | ||||
|     connect(ui->button_regenerate_console_id, &QPushButton::clicked, this, | ||||
|             &ConfigureSystem::refreshConsoleID); | ||||
|             &ConfigureSystem::RefreshConsoleID); | ||||
|     for (u8 i = 0; i < country_names.size(); i++) { | ||||
|         if (country_names.at(i) != "") { | ||||
|             ui->combo_country->addItem(tr(country_names.at(i)), i); | ||||
|  | @ -270,7 +270,7 @@ void ConfigureSystem::ReadSystemSettings() { | |||
|     // set birthday
 | ||||
|     std::tie(birthmonth, birthday) = cfg->GetBirthday(); | ||||
|     ui->combo_birthmonth->setCurrentIndex(birthmonth - 1); | ||||
|     updateBirthdayComboBox( | ||||
|     UpdateBirthdayComboBox( | ||||
|         birthmonth - | ||||
|         1); // explicitly update it because the signal from setCurrentIndex is not reliable
 | ||||
|     ui->combo_birthday->setCurrentIndex(birthday - 1); | ||||
|  | @ -358,7 +358,7 @@ void ConfigureSystem::applyConfiguration() { | |||
|     Settings::Apply(); | ||||
| } | ||||
| 
 | ||||
| void ConfigureSystem::updateBirthdayComboBox(int birthmonth_index) { | ||||
| void ConfigureSystem::UpdateBirthdayComboBox(int birthmonth_index) { | ||||
|     if (birthmonth_index < 0 || birthmonth_index >= 12) | ||||
|         return; | ||||
| 
 | ||||
|  | @ -391,17 +391,17 @@ void ConfigureSystem::ConfigureTime() { | |||
| 
 | ||||
|     this->setConfiguration(); | ||||
| 
 | ||||
|     updateInitTime(ui->combo_init_clock->currentIndex()); | ||||
|     UpdateInitTime(ui->combo_init_clock->currentIndex()); | ||||
| } | ||||
| 
 | ||||
| void ConfigureSystem::updateInitTime(int init_clock) { | ||||
| void ConfigureSystem::UpdateInitTime(int init_clock) { | ||||
|     const bool is_fixed_time = | ||||
|         static_cast<Settings::InitClock>(init_clock) == Settings::InitClock::FixedTime; | ||||
|     ui->label_init_time->setVisible(is_fixed_time); | ||||
|     ui->edit_init_time->setVisible(is_fixed_time); | ||||
| } | ||||
| 
 | ||||
| void ConfigureSystem::refreshConsoleID() { | ||||
| void ConfigureSystem::RefreshConsoleID() { | ||||
|     QMessageBox::StandardButton reply; | ||||
|     QString warning_text = tr("This will replace your current virtual 3DS with a new one. " | ||||
|                               "Your current virtual 3DS will not be recoverable. " | ||||
|  |  | |||
|  | @ -23,29 +23,29 @@ class ConfigureSystem : public QWidget { | |||
| 
 | ||||
| public: | ||||
|     explicit ConfigureSystem(QWidget* parent = nullptr); | ||||
|     ~ConfigureSystem(); | ||||
|     ~ConfigureSystem() override; | ||||
| 
 | ||||
|     void applyConfiguration(); | ||||
|     void setConfiguration(); | ||||
|     void retranslateUi(); | ||||
| 
 | ||||
| public slots: | ||||
|     void updateBirthdayComboBox(int birthmonth_index); | ||||
|     void updateInitTime(int init_clock); | ||||
|     void refreshConsoleID(); | ||||
| 
 | ||||
| private: | ||||
|     void ReadSystemSettings(); | ||||
|     void ConfigureTime(); | ||||
| 
 | ||||
|     void UpdateBirthdayComboBox(int birthmonth_index); | ||||
|     void UpdateInitTime(int init_clock); | ||||
|     void RefreshConsoleID(); | ||||
| 
 | ||||
|     std::unique_ptr<Ui::ConfigureSystem> ui; | ||||
|     bool enabled; | ||||
|     bool enabled = false; | ||||
| 
 | ||||
|     std::shared_ptr<Service::CFG::Module> cfg; | ||||
|     std::u16string username; | ||||
|     int birthmonth, birthday; | ||||
|     int language_index; | ||||
|     int sound_index; | ||||
|     int birthmonth = 0; | ||||
|     int birthday = 0; | ||||
|     int language_index = 0; | ||||
|     int sound_index = 0; | ||||
|     u8 country_code; | ||||
|     u16 play_coin; | ||||
| }; | ||||
|  |  | |||
|  | @ -261,6 +261,9 @@ | |||
|         </item> | ||||
|         <item row="6" column="1"> | ||||
|          <widget class="QDateTimeEdit" name="edit_init_time"> | ||||
|           <property name="displayFormat"> | ||||
|            <string>yyyy-MM-ddTHH:mm:ss</string> | ||||
|           </property> | ||||
|          </widget> | ||||
|         </item> | ||||
|         <item row="7" column="0"> | ||||
|  |  | |||
|  | @ -30,23 +30,8 @@ QVariant BreakPointModel::data(const QModelIndex& index, int role) const { | |||
|     switch (role) { | ||||
|     case Qt::DisplayRole: { | ||||
|         if (index.column() == 0) { | ||||
|             static const std::map<Pica::DebugContext::Event, QString> map = { | ||||
|                 {Pica::DebugContext::Event::PicaCommandLoaded, tr("Pica command loaded")}, | ||||
|                 {Pica::DebugContext::Event::PicaCommandProcessed, tr("Pica command processed")}, | ||||
|                 {Pica::DebugContext::Event::IncomingPrimitiveBatch, tr("Incoming primitive batch")}, | ||||
|                 {Pica::DebugContext::Event::FinishedPrimitiveBatch, tr("Finished primitive batch")}, | ||||
|                 {Pica::DebugContext::Event::VertexShaderInvocation, tr("Vertex shader invocation")}, | ||||
|                 {Pica::DebugContext::Event::IncomingDisplayTransfer, | ||||
|                  tr("Incoming display transfer")}, | ||||
|                 {Pica::DebugContext::Event::GSPCommandProcessed, tr("GSP command processed")}, | ||||
|                 {Pica::DebugContext::Event::BufferSwapped, tr("Buffers swapped")}, | ||||
|             }; | ||||
| 
 | ||||
|             DEBUG_ASSERT(map.size() == | ||||
|                          static_cast<std::size_t>(Pica::DebugContext::Event::NumEvents)); | ||||
|             return (map.find(event) != map.end()) ? map.at(event) : QString(); | ||||
|             return DebugContextEventToString(event); | ||||
|         } | ||||
| 
 | ||||
|         break; | ||||
|     } | ||||
| 
 | ||||
|  | @ -128,6 +113,30 @@ void BreakPointModel::OnResumed() { | |||
|     active_breakpoint = context->active_breakpoint; | ||||
| } | ||||
| 
 | ||||
| QString BreakPointModel::DebugContextEventToString(Pica::DebugContext::Event event) { | ||||
|     switch (event) { | ||||
|     case Pica::DebugContext::Event::PicaCommandLoaded: | ||||
|         return tr("Pica command loaded"); | ||||
|     case Pica::DebugContext::Event::PicaCommandProcessed: | ||||
|         return tr("Pica command processed"); | ||||
|     case Pica::DebugContext::Event::IncomingPrimitiveBatch: | ||||
|         return tr("Incoming primitive batch"); | ||||
|     case Pica::DebugContext::Event::FinishedPrimitiveBatch: | ||||
|         return tr("Finished primitive batch"); | ||||
|     case Pica::DebugContext::Event::VertexShaderInvocation: | ||||
|         return tr("Vertex shader invocation"); | ||||
|     case Pica::DebugContext::Event::IncomingDisplayTransfer: | ||||
|         return tr("Incoming display transfer"); | ||||
|     case Pica::DebugContext::Event::GSPCommandProcessed: | ||||
|         return tr("GSP command processed"); | ||||
|     case Pica::DebugContext::Event::BufferSwapped: | ||||
|         return tr("Buffers swapped"); | ||||
|     case Pica::DebugContext::Event::NumEvents: | ||||
|         break; | ||||
|     } | ||||
|     return tr("Unknown debug context event"); | ||||
| } | ||||
| 
 | ||||
| GraphicsBreakPointsWidget::GraphicsBreakPointsWidget( | ||||
|     std::shared_ptr<Pica::DebugContext> debug_context, QWidget* parent) | ||||
|     : QDockWidget(tr("Pica Breakpoints"), parent), Pica::DebugContext::BreakPointObserver( | ||||
|  |  | |||
|  | @ -29,6 +29,8 @@ public: | |||
|     void OnResumed(); | ||||
| 
 | ||||
| private: | ||||
|     static QString DebugContextEventToString(Pica::DebugContext::Event event); | ||||
| 
 | ||||
|     std::weak_ptr<Pica::DebugContext> context_weak; | ||||
|     bool at_breakpoint; | ||||
|     Pica::DebugContext::Event active_breakpoint; | ||||
|  |  | |||
|  | @ -51,7 +51,7 @@ std::size_t WaitTreeItem::Row() const { | |||
| } | ||||
| 
 | ||||
| std::vector<std::unique_ptr<WaitTreeThread>> WaitTreeItem::MakeThreadItemList() { | ||||
|     const auto& threads = Kernel::GetThreadList(); | ||||
|     const auto& threads = Core::System::GetInstance().Kernel().GetThreadManager().GetThreadList(); | ||||
|     std::vector<std::unique_ptr<WaitTreeThread>> item_list; | ||||
|     item_list.reserve(threads.size()); | ||||
|     for (std::size_t i = 0; i < threads.size(); ++i) { | ||||
|  |  | |||
|  | @ -21,8 +21,10 @@ | |||
| #include <QToolButton> | ||||
| #include <QTreeView> | ||||
| #include <fmt/format.h> | ||||
| #include "citra_qt/compatibility_list.h" | ||||
| #include "citra_qt/game_list.h" | ||||
| #include "citra_qt/game_list_p.h" | ||||
| #include "citra_qt/game_list_worker.h" | ||||
| #include "citra_qt/main.h" | ||||
| #include "citra_qt/ui_settings.h" | ||||
| #include "common/common_paths.h" | ||||
|  | @ -30,7 +32,6 @@ | |||
| #include "core/file_sys/archive_extsavedata.h" | ||||
| #include "core/file_sys/archive_source_sd_savedata.h" | ||||
| #include "core/hle/service/fs/archive.h" | ||||
| #include "core/loader/loader.h" | ||||
| 
 | ||||
| GameListSearchField::KeyReleaseEater::KeyReleaseEater(GameList* gamelist) : gamelist{gamelist} {} | ||||
| 
 | ||||
|  | @ -288,11 +289,11 @@ GameList::GameList(GMainWindow* parent) : QWidget{parent} { | |||
|     tree_view->setContextMenuPolicy(Qt::CustomContextMenu); | ||||
| 
 | ||||
|     item_model->insertColumns(0, COLUMN_COUNT); | ||||
|     item_model->setHeaderData(COLUMN_NAME, Qt::Horizontal, "Name"); | ||||
|     item_model->setHeaderData(COLUMN_COMPATIBILITY, Qt::Horizontal, "Compatibility"); | ||||
|     item_model->setHeaderData(COLUMN_REGION, Qt::Horizontal, "Region"); | ||||
|     item_model->setHeaderData(COLUMN_FILE_TYPE, Qt::Horizontal, "File type"); | ||||
|     item_model->setHeaderData(COLUMN_SIZE, Qt::Horizontal, "Size"); | ||||
|     item_model->setHeaderData(COLUMN_NAME, Qt::Horizontal, tr("Name")); | ||||
|     item_model->setHeaderData(COLUMN_COMPATIBILITY, Qt::Horizontal, tr("Compatibility")); | ||||
|     item_model->setHeaderData(COLUMN_REGION, Qt::Horizontal, tr("Region")); | ||||
|     item_model->setHeaderData(COLUMN_FILE_TYPE, Qt::Horizontal, tr("File type")); | ||||
|     item_model->setHeaderData(COLUMN_SIZE, Qt::Horizontal, tr("Size")); | ||||
|     item_model->setSortRole(GameListItemPath::TitleRole); | ||||
| 
 | ||||
|     connect(main_window, &GMainWindow::UpdateThemedIcons, this, &GameList::onUpdateThemedIcons); | ||||
|  | @ -648,11 +649,6 @@ void GameList::LoadInterfaceLayout() { | |||
| const QStringList GameList::supported_file_extensions = {"3ds", "3dsx", "elf", "axf", | ||||
|                                                          "cci", "cxi",  "app"}; | ||||
| 
 | ||||
| static bool HasSupportedFileExtension(const std::string& file_name) { | ||||
|     QFileInfo file = QFileInfo(QString::fromStdString(file_name)); | ||||
|     return GameList::supported_file_extensions.contains(file.suffix(), Qt::CaseInsensitive); | ||||
| } | ||||
| 
 | ||||
| void GameList::RefreshGameDirectory() { | ||||
|     if (!UISettings::values.game_dirs.isEmpty() && current_worker != nullptr) { | ||||
|         LOG_INFO(Frontend, "Change detected in the games directory. Reloading game list."); | ||||
|  | @ -678,123 +674,6 @@ QString GameList::FindGameByProgramID(QStandardItem* current_item, u64 program_i | |||
|     return ""; | ||||
| } | ||||
| 
 | ||||
| void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsigned int recursion, | ||||
|                                              GameListDir* parent_dir) { | ||||
|     const auto callback = [this, recursion, parent_dir](u64* num_entries_out, | ||||
|                                                         const std::string& directory, | ||||
|                                                         const std::string& virtual_name) -> bool { | ||||
|         std::string physical_name = directory + DIR_SEP + virtual_name; | ||||
| 
 | ||||
|         if (stop_processing) | ||||
|             return false; // Breaks the callback loop.
 | ||||
| 
 | ||||
|         bool is_dir = FileUtil::IsDirectory(physical_name); | ||||
|         if (!is_dir && HasSupportedFileExtension(physical_name)) { | ||||
|             std::unique_ptr<Loader::AppLoader> loader = Loader::GetLoader(physical_name); | ||||
|             if (!loader) | ||||
|                 return true; | ||||
| 
 | ||||
|             u64 program_id = 0; | ||||
|             loader->ReadProgramId(program_id); | ||||
| 
 | ||||
|             u64 extdata_id = 0; | ||||
|             loader->ReadExtdataId(extdata_id); | ||||
| 
 | ||||
|             std::vector<u8> smdh = [program_id, &loader]() -> std::vector<u8> { | ||||
|                 std::vector<u8> original_smdh; | ||||
|                 loader->ReadIcon(original_smdh); | ||||
| 
 | ||||
|                 if (program_id < 0x0004000000000000 || program_id > 0x00040000FFFFFFFF) | ||||
|                     return original_smdh; | ||||
| 
 | ||||
|                 std::string update_path = Service::AM::GetTitleContentPath( | ||||
|                     Service::FS::MediaType::SDMC, program_id + 0x0000000E00000000); | ||||
| 
 | ||||
|                 if (!FileUtil::Exists(update_path)) | ||||
|                     return original_smdh; | ||||
| 
 | ||||
|                 std::unique_ptr<Loader::AppLoader> update_loader = Loader::GetLoader(update_path); | ||||
| 
 | ||||
|                 if (!update_loader) | ||||
|                     return original_smdh; | ||||
| 
 | ||||
|                 std::vector<u8> update_smdh; | ||||
|                 update_loader->ReadIcon(update_smdh); | ||||
|                 return update_smdh; | ||||
|             }(); | ||||
| 
 | ||||
|             if (!Loader::IsValidSMDH(smdh) && UISettings::values.game_list_hide_no_icon) { | ||||
|                 // Skip this invalid entry
 | ||||
|                 return true; | ||||
|             } | ||||
| 
 | ||||
|             auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id); | ||||
| 
 | ||||
|             // The game list uses this as compatibility number for untested games
 | ||||
|             QString compatibility("99"); | ||||
|             if (it != compatibility_list.end()) | ||||
|                 compatibility = it->second.first; | ||||
| 
 | ||||
|             emit EntryReady( | ||||
|                 { | ||||
|                     new GameListItemPath(QString::fromStdString(physical_name), smdh, program_id, | ||||
|                                          extdata_id), | ||||
|                     new GameListItemCompat(compatibility), | ||||
|                     new GameListItemRegion(smdh), | ||||
|                     new GameListItem( | ||||
|                         QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType()))), | ||||
|                     new GameListItemSize(FileUtil::GetSize(physical_name)), | ||||
|                 }, | ||||
|                 parent_dir); | ||||
| 
 | ||||
|         } else if (is_dir && recursion > 0) { | ||||
|             watch_list.append(QString::fromStdString(physical_name)); | ||||
|             AddFstEntriesToGameList(physical_name, recursion - 1, parent_dir); | ||||
|         } | ||||
| 
 | ||||
|         return true; | ||||
|     }; | ||||
| 
 | ||||
|     FileUtil::ForeachDirectoryEntry(nullptr, dir_path, callback); | ||||
| } | ||||
| 
 | ||||
| void GameListWorker::run() { | ||||
|     stop_processing = false; | ||||
|     for (UISettings::GameDir& game_dir : game_dirs) { | ||||
|         if (game_dir.path == "INSTALLED") { | ||||
|             QString path = | ||||
|                 QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir)) + | ||||
|                 "Nintendo " | ||||
|                 "3DS/00000000000000000000000000000000/" | ||||
|                 "00000000000000000000000000000000/title/00040000"; | ||||
|             watch_list.append(path); | ||||
|             GameListDir* game_list_dir = new GameListDir(game_dir, GameListItemType::InstalledDir); | ||||
|             emit DirEntryReady({game_list_dir}); | ||||
|             AddFstEntriesToGameList(path.toStdString(), 2, game_list_dir); | ||||
|         } else if (game_dir.path == "SYSTEM") { | ||||
|             QString path = | ||||
|                 QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir)) + | ||||
|                 "00000000000000000000000000000000/title/00040010"; | ||||
|             watch_list.append(path); | ||||
|             GameListDir* game_list_dir = new GameListDir(game_dir, GameListItemType::SystemDir); | ||||
|             emit DirEntryReady({game_list_dir}); | ||||
|             AddFstEntriesToGameList(path.toStdString(), 2, game_list_dir); | ||||
|         } else { | ||||
|             watch_list.append(game_dir.path); | ||||
|             GameListDir* game_list_dir = new GameListDir(game_dir); | ||||
|             emit DirEntryReady({game_list_dir}); | ||||
|             AddFstEntriesToGameList(game_dir.path.toStdString(), game_dir.deep_scan ? 256 : 0, | ||||
|                                     game_list_dir); | ||||
|         } | ||||
|     }; | ||||
|     emit Finished(watch_list); | ||||
| } | ||||
| 
 | ||||
| void GameListWorker::Cancel() { | ||||
|     this->disconnect(); | ||||
|     stop_processing = true; | ||||
| } | ||||
| 
 | ||||
| GameListPlaceholder::GameListPlaceholder(GMainWindow* parent) : QWidget{parent} { | ||||
|     this->main_window = parent; | ||||
| 
 | ||||
|  |  | |||
|  | @ -4,9 +4,10 @@ | |||
| 
 | ||||
| #pragma once | ||||
| 
 | ||||
| #include <unordered_map> | ||||
| #include <QMenu> | ||||
| #include <QString> | ||||
| #include <QWidget> | ||||
| #include "citra_qt/compatibility_list.h" | ||||
| #include "common/common_types.h" | ||||
| #include "ui_settings.h" | ||||
| 
 | ||||
|  | @ -70,9 +71,8 @@ signals: | |||
|     void GameChosen(QString game_path); | ||||
|     void ShouldCancelWorker(); | ||||
|     void OpenFolderRequested(u64 program_id, GameListOpenTarget target); | ||||
|     void NavigateToGamedbEntryRequested( | ||||
|         u64 program_id, | ||||
|         std::unordered_map<std::string, std::pair<QString, QString>>& compatibility_list); | ||||
|     void NavigateToGamedbEntryRequested(u64 program_id, | ||||
|                                         const CompatibilityList& compatibility_list); | ||||
|     void OpenDirectory(QString directory); | ||||
|     void AddDirectory(); | ||||
|     void ShowList(bool show); | ||||
|  | @ -103,7 +103,7 @@ private: | |||
|     QStandardItemModel* item_model = nullptr; | ||||
|     GameListWorker* current_worker = nullptr; | ||||
|     QFileSystemWatcher* watcher = nullptr; | ||||
|     std::unordered_map<std::string, std::pair<QString, QString>> compatibility_list; | ||||
|     CompatibilityList compatibility_list; | ||||
| 
 | ||||
|     friend class GameListSearchField; | ||||
| }; | ||||
|  |  | |||
|  | @ -4,7 +4,6 @@ | |||
| 
 | ||||
| #pragma once | ||||
| 
 | ||||
| #include <atomic> | ||||
| #include <map> | ||||
| #include <unordered_map> | ||||
| #include <utility> | ||||
|  | @ -59,17 +58,6 @@ static QPixmap GetDefaultIcon(bool large) { | |||
|     return icon; | ||||
| } | ||||
| 
 | ||||
| static auto FindMatchingCompatibilityEntry( | ||||
|     const std::unordered_map<std::string, std::pair<QString, QString>>& compatibility_list, | ||||
|     u64 program_id) { | ||||
|     return std::find_if( | ||||
|         compatibility_list.begin(), compatibility_list.end(), | ||||
|         [program_id](const std::pair<std::string, std::pair<QString, QString>>& element) { | ||||
|             std::string pid = fmt::format("{:016X}", program_id); | ||||
|             return element.first == pid; | ||||
|         }); | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * Gets the short game title from SMDH data. | ||||
|  * @param smdh SMDH data | ||||
|  | @ -216,7 +204,7 @@ class GameListItemCompat : public GameListItem { | |||
| public: | ||||
|     static const int CompatNumberRole = SortRole; | ||||
|     GameListItemCompat() = default; | ||||
|     explicit GameListItemCompat(const QString& compatiblity) { | ||||
|     explicit GameListItemCompat(const QString& compatibility) { | ||||
|         setData(type(), TypeRole); | ||||
| 
 | ||||
|         struct CompatStatus { | ||||
|  | @ -235,13 +223,13 @@ public: | |||
|         {"99", {"#000000", QT_TR_NOOP("Not Tested"), QT_TR_NOOP("The game has not yet been tested.")}}}; | ||||
|         // clang-format on
 | ||||
| 
 | ||||
|         auto iterator = status_data.find(compatiblity); | ||||
|         auto iterator = status_data.find(compatibility); | ||||
|         if (iterator == status_data.end()) { | ||||
|             LOG_WARNING(Frontend, "Invalid compatibility number {}", compatiblity.toStdString()); | ||||
|             LOG_WARNING(Frontend, "Invalid compatibility number {}", compatibility.toStdString()); | ||||
|             return; | ||||
|         } | ||||
|         CompatStatus status = iterator->second; | ||||
|         setData(compatiblity, CompatNumberRole); | ||||
|         const CompatStatus& status = iterator->second; | ||||
|         setData(compatibility, CompatNumberRole); | ||||
|         setText(QObject::tr(status.text)); | ||||
|         setToolTip(QObject::tr(status.tooltip)); | ||||
|         setData(CreateCirclePixmapFromColor(status.color), Qt::DecorationRole); | ||||
|  | @ -373,51 +361,6 @@ public: | |||
|     } | ||||
| }; | ||||
| 
 | ||||
| /**
 | ||||
|  * Asynchronous worker object for populating the game list. | ||||
|  * Communicates with other threads through Qt's signal/slot system. | ||||
|  */ | ||||
| class GameListWorker : public QObject, public QRunnable { | ||||
|     Q_OBJECT | ||||
| 
 | ||||
| public: | ||||
|     explicit GameListWorker( | ||||
|         QList<UISettings::GameDir>& game_dirs, | ||||
|         const std::unordered_map<std::string, std::pair<QString, QString>>& compatibility_list) | ||||
|         : game_dirs(game_dirs), compatibility_list(compatibility_list) {} | ||||
| 
 | ||||
| public slots: | ||||
|     /// Starts the processing of directory tree information.
 | ||||
|     void run() override; | ||||
|     /// Tells the worker that it should no longer continue processing. Thread-safe.
 | ||||
|     void Cancel(); | ||||
| 
 | ||||
| signals: | ||||
|     /**
 | ||||
|      * The `EntryReady` signal is emitted once an entry has been prepared and is ready | ||||
|      * to be added to the game list. | ||||
|      * @param entry_items a list with `QStandardItem`s that make up the columns of the new | ||||
|      * entry. | ||||
|      */ | ||||
|     void DirEntryReady(GameListDir* entry_items); | ||||
|     void EntryReady(QList<QStandardItem*> entry_items, GameListDir* parent_dir); | ||||
| 
 | ||||
|     /**
 | ||||
|      * After the worker has traversed the game directory looking for entries, this signal is | ||||
|      * emitted with a list of folders that should be watched for changes as well. | ||||
|      */ | ||||
|     void Finished(QStringList watch_list); | ||||
| 
 | ||||
| private: | ||||
|     QStringList watch_list; | ||||
|     const std::unordered_map<std::string, std::pair<QString, QString>>& compatibility_list; | ||||
|     QList<UISettings::GameDir>& game_dirs; | ||||
|     std::atomic_bool stop_processing; | ||||
| 
 | ||||
|     void AddFstEntriesToGameList(const std::string& dir_path, unsigned int recursion, | ||||
|                                  GameListDir* parent_dir); | ||||
| }; | ||||
| 
 | ||||
| class GameList; | ||||
| class QHBoxLayout; | ||||
| class QTreeView; | ||||
|  |  | |||
							
								
								
									
										150
									
								
								src/citra_qt/game_list_worker.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										150
									
								
								src/citra_qt/game_list_worker.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,150 @@ | |||
| // Copyright 2018 yuzu emulator team
 | ||||
| // Licensed under GPLv2 or any later version
 | ||||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| #include <memory> | ||||
| #include <string> | ||||
| #include <utility> | ||||
| #include <vector> | ||||
| #include <QDir> | ||||
| #include <QFileInfo> | ||||
| #include "citra_qt/compatibility_list.h" | ||||
| #include "citra_qt/game_list.h" | ||||
| #include "citra_qt/game_list_p.h" | ||||
| #include "citra_qt/game_list_worker.h" | ||||
| #include "citra_qt/ui_settings.h" | ||||
| #include "common/common_paths.h" | ||||
| #include "common/file_util.h" | ||||
| #include "core/hle/service/am/am.h" | ||||
| #include "core/hle/service/fs/archive.h" | ||||
| #include "core/loader/loader.h" | ||||
| 
 | ||||
| namespace { | ||||
| bool HasSupportedFileExtension(const std::string& file_name) { | ||||
|     const QFileInfo file = QFileInfo(QString::fromStdString(file_name)); | ||||
|     return GameList::supported_file_extensions.contains(file.suffix(), Qt::CaseInsensitive); | ||||
| } | ||||
| } // Anonymous namespace
 | ||||
| 
 | ||||
| GameListWorker::GameListWorker(QList<UISettings::GameDir>& game_dirs, | ||||
|                                const CompatibilityList& compatibility_list) | ||||
|     : game_dirs(game_dirs), compatibility_list(compatibility_list) {} | ||||
| 
 | ||||
| GameListWorker::~GameListWorker() = default; | ||||
| 
 | ||||
| void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsigned int recursion, | ||||
|                                              GameListDir* parent_dir) { | ||||
|     const auto callback = [this, recursion, parent_dir](u64* num_entries_out, | ||||
|                                                         const std::string& directory, | ||||
|                                                         const std::string& virtual_name) -> bool { | ||||
|         std::string physical_name = directory + DIR_SEP + virtual_name; | ||||
| 
 | ||||
|         if (stop_processing) | ||||
|             return false; // Breaks the callback loop.
 | ||||
| 
 | ||||
|         bool is_dir = FileUtil::IsDirectory(physical_name); | ||||
|         if (!is_dir && HasSupportedFileExtension(physical_name)) { | ||||
|             std::unique_ptr<Loader::AppLoader> loader = Loader::GetLoader(physical_name); | ||||
|             if (!loader) | ||||
|                 return true; | ||||
| 
 | ||||
|             u64 program_id = 0; | ||||
|             loader->ReadProgramId(program_id); | ||||
| 
 | ||||
|             u64 extdata_id = 0; | ||||
|             loader->ReadExtdataId(extdata_id); | ||||
| 
 | ||||
|             std::vector<u8> smdh = [program_id, &loader]() -> std::vector<u8> { | ||||
|                 std::vector<u8> original_smdh; | ||||
|                 loader->ReadIcon(original_smdh); | ||||
| 
 | ||||
|                 if (program_id < 0x0004000000000000 || program_id > 0x00040000FFFFFFFF) | ||||
|                     return original_smdh; | ||||
| 
 | ||||
|                 std::string update_path = Service::AM::GetTitleContentPath( | ||||
|                     Service::FS::MediaType::SDMC, program_id + 0x0000000E00000000); | ||||
| 
 | ||||
|                 if (!FileUtil::Exists(update_path)) | ||||
|                     return original_smdh; | ||||
| 
 | ||||
|                 std::unique_ptr<Loader::AppLoader> update_loader = Loader::GetLoader(update_path); | ||||
| 
 | ||||
|                 if (!update_loader) | ||||
|                     return original_smdh; | ||||
| 
 | ||||
|                 std::vector<u8> update_smdh; | ||||
|                 update_loader->ReadIcon(update_smdh); | ||||
|                 return update_smdh; | ||||
|             }(); | ||||
| 
 | ||||
|             if (!Loader::IsValidSMDH(smdh) && UISettings::values.game_list_hide_no_icon) { | ||||
|                 // Skip this invalid entry
 | ||||
|                 return true; | ||||
|             } | ||||
| 
 | ||||
|             auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id); | ||||
| 
 | ||||
|             // The game list uses this as compatibility number for untested games
 | ||||
|             QString compatibility("99"); | ||||
|             if (it != compatibility_list.end()) | ||||
|                 compatibility = it->second.first; | ||||
| 
 | ||||
|             emit EntryReady( | ||||
|                 { | ||||
|                     new GameListItemPath(QString::fromStdString(physical_name), smdh, program_id, | ||||
|                                          extdata_id), | ||||
|                     new GameListItemCompat(compatibility), | ||||
|                     new GameListItemRegion(smdh), | ||||
|                     new GameListItem( | ||||
|                         QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType()))), | ||||
|                     new GameListItemSize(FileUtil::GetSize(physical_name)), | ||||
|                 }, | ||||
|                 parent_dir); | ||||
| 
 | ||||
|         } else if (is_dir && recursion > 0) { | ||||
|             watch_list.append(QString::fromStdString(physical_name)); | ||||
|             AddFstEntriesToGameList(physical_name, recursion - 1, parent_dir); | ||||
|         } | ||||
| 
 | ||||
|         return true; | ||||
|     }; | ||||
| 
 | ||||
|     FileUtil::ForeachDirectoryEntry(nullptr, dir_path, callback); | ||||
| } | ||||
| 
 | ||||
| void GameListWorker::run() { | ||||
|     stop_processing = false; | ||||
|     for (UISettings::GameDir& game_dir : game_dirs) { | ||||
|         if (game_dir.path == "INSTALLED") { | ||||
|             QString path = | ||||
|                 QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir)) + | ||||
|                 "Nintendo " | ||||
|                 "3DS/00000000000000000000000000000000/" | ||||
|                 "00000000000000000000000000000000/title/00040000"; | ||||
|             watch_list.append(path); | ||||
|             GameListDir* game_list_dir = new GameListDir(game_dir, GameListItemType::InstalledDir); | ||||
|             emit DirEntryReady({game_list_dir}); | ||||
|             AddFstEntriesToGameList(path.toStdString(), 2, game_list_dir); | ||||
|         } else if (game_dir.path == "SYSTEM") { | ||||
|             QString path = | ||||
|                 QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir)) + | ||||
|                 "00000000000000000000000000000000/title/00040010"; | ||||
|             watch_list.append(path); | ||||
|             GameListDir* game_list_dir = new GameListDir(game_dir, GameListItemType::SystemDir); | ||||
|             emit DirEntryReady({game_list_dir}); | ||||
|             AddFstEntriesToGameList(path.toStdString(), 2, game_list_dir); | ||||
|         } else { | ||||
|             watch_list.append(game_dir.path); | ||||
|             GameListDir* game_list_dir = new GameListDir(game_dir); | ||||
|             emit DirEntryReady({game_list_dir}); | ||||
|             AddFstEntriesToGameList(game_dir.path.toStdString(), game_dir.deep_scan ? 256 : 0, | ||||
|                                     game_list_dir); | ||||
|         } | ||||
|     }; | ||||
|     emit Finished(watch_list); | ||||
| } | ||||
| 
 | ||||
| void GameListWorker::Cancel() { | ||||
|     this->disconnect(); | ||||
|     stop_processing = true; | ||||
| } | ||||
							
								
								
									
										62
									
								
								src/citra_qt/game_list_worker.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								src/citra_qt/game_list_worker.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,62 @@ | |||
| // Copyright 2018 yuzu emulator team
 | ||||
| // Licensed under GPLv2 or any later version
 | ||||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| #pragma once | ||||
| 
 | ||||
| #include <atomic> | ||||
| #include <map> | ||||
| #include <memory> | ||||
| #include <string> | ||||
| #include <unordered_map> | ||||
| #include <QList> | ||||
| #include <QObject> | ||||
| #include <QRunnable> | ||||
| #include <QString> | ||||
| #include "citra_qt/compatibility_list.h" | ||||
| #include "common/common_types.h" | ||||
| 
 | ||||
| class QStandardItem; | ||||
| 
 | ||||
| /**
 | ||||
|  * Asynchronous worker object for populating the game list. | ||||
|  * Communicates with other threads through Qt's signal/slot system. | ||||
|  */ | ||||
| class GameListWorker : public QObject, public QRunnable { | ||||
|     Q_OBJECT | ||||
| 
 | ||||
| public: | ||||
|     GameListWorker(QList<UISettings::GameDir>& game_dirs, | ||||
|                    const CompatibilityList& compatibility_list); | ||||
|     ~GameListWorker() override; | ||||
| 
 | ||||
|     /// Starts the processing of directory tree information.
 | ||||
|     void run() override; | ||||
| 
 | ||||
|     /// Tells the worker that it should no longer continue processing. Thread-safe.
 | ||||
|     void Cancel(); | ||||
| 
 | ||||
| signals: | ||||
|     /**
 | ||||
|      * The `EntryReady` signal is emitted once an entry has been prepared and is ready | ||||
|      * to be added to the game list. | ||||
|      * @param entry_items a list with `QStandardItem`s that make up the columns of the new entry. | ||||
|      */ | ||||
|     void DirEntryReady(GameListDir* entry_items); | ||||
|     void EntryReady(QList<QStandardItem*> entry_items, GameListDir* parent_dir); | ||||
| 
 | ||||
|     /**
 | ||||
|      * After the worker has traversed the game directory looking for entries, this signal is emitted | ||||
|      * with a list of folders that should be watched for changes as well. | ||||
|      */ | ||||
|     void Finished(QStringList watch_list); | ||||
| 
 | ||||
| private: | ||||
|     void AddFstEntriesToGameList(const std::string& dir_path, unsigned int recursion, | ||||
|                                  GameListDir* parent_dir); | ||||
| 
 | ||||
|     QStringList watch_list; | ||||
|     const CompatibilityList& compatibility_list; | ||||
|     QList<UISettings::GameDir>& game_dirs; | ||||
|     std::atomic_bool stop_processing; | ||||
| }; | ||||
|  | @ -21,6 +21,7 @@ | |||
| #include "citra_qt/camera/qt_multimedia_camera.h" | ||||
| #include "citra_qt/camera/still_image_camera.h" | ||||
| #include "citra_qt/compatdb.h" | ||||
| #include "citra_qt/compatibility_list.h" | ||||
| #include "citra_qt/configuration/config.h" | ||||
| #include "citra_qt/configuration/configure_dialog.h" | ||||
| #include "citra_qt/debugger/console.h" | ||||
|  | @ -44,6 +45,7 @@ | |||
| #include "citra_qt/util/clickable_label.h" | ||||
| #include "common/common_paths.h" | ||||
| #include "common/detached_tasks.h" | ||||
| #include "common/file_util.h" | ||||
| #include "common/logging/backend.h" | ||||
| #include "common/logging/filter.h" | ||||
| #include "common/logging/log.h" | ||||
|  | @ -57,6 +59,7 @@ | |||
| #include "core/frontend/applets/default_applets.h" | ||||
| #include "core/gdbstub/gdbstub.h" | ||||
| #include "core/hle/service/fs/archive.h" | ||||
| #include "core/hle/service/nfc/nfc.h" | ||||
| #include "core/loader/loader.h" | ||||
| #include "core/movie.h" | ||||
| #include "core/settings.h" | ||||
|  | @ -341,8 +344,9 @@ void GMainWindow::InitializeHotkeys() { | |||
|     hotkey_registry.RegisterHotkey("Main Window", "Start Emulation"); | ||||
|     hotkey_registry.RegisterHotkey("Main Window", "Continue/Pause", QKeySequence(Qt::Key_F4)); | ||||
|     hotkey_registry.RegisterHotkey("Main Window", "Restart", QKeySequence(Qt::Key_F5)); | ||||
|     hotkey_registry.RegisterHotkey("Main Window", "Swap Screens", QKeySequence(tr("F9"))); | ||||
|     hotkey_registry.RegisterHotkey("Main Window", "Toggle Screen Layout", QKeySequence(tr("F10"))); | ||||
|     hotkey_registry.RegisterHotkey("Main Window", "Swap Screens", QKeySequence(Qt::Key_F9)); | ||||
|     hotkey_registry.RegisterHotkey("Main Window", "Toggle Screen Layout", | ||||
|                                    QKeySequence(Qt::Key_F10)); | ||||
|     hotkey_registry.RegisterHotkey("Main Window", "Fullscreen", QKeySequence::FullScreen); | ||||
|     hotkey_registry.RegisterHotkey("Main Window", "Exit Fullscreen", QKeySequence(Qt::Key_Escape), | ||||
|                                    Qt::ApplicationShortcut); | ||||
|  | @ -356,6 +360,11 @@ void GMainWindow::InitializeHotkeys() { | |||
|                                    Qt::ApplicationShortcut); | ||||
|     hotkey_registry.RegisterHotkey("Main Window", "Advance Frame", QKeySequence(Qt::Key_Backslash), | ||||
|                                    Qt::ApplicationShortcut); | ||||
|     hotkey_registry.RegisterHotkey("Main Window", "Load Amiibo", QKeySequence(Qt::Key_F2), | ||||
|                                    Qt::ApplicationShortcut); | ||||
|     hotkey_registry.RegisterHotkey("Main Window", "Remove Amiibo", QKeySequence(Qt::Key_F3), | ||||
|                                    Qt::ApplicationShortcut); | ||||
| 
 | ||||
|     hotkey_registry.LoadHotkeys(); | ||||
| 
 | ||||
|     connect(hotkey_registry.GetHotkey("Main Window", "Load File", this), &QShortcut::activated, | ||||
|  | @ -417,6 +426,18 @@ void GMainWindow::InitializeHotkeys() { | |||
|             &QShortcut::activated, ui.action_Enable_Frame_Advancing, &QAction::trigger); | ||||
|     connect(hotkey_registry.GetHotkey("Main Window", "Advance Frame", this), &QShortcut::activated, | ||||
|             ui.action_Advance_Frame, &QAction::trigger); | ||||
|     connect(hotkey_registry.GetHotkey("Main Window", "Load Amiibo", this), &QShortcut::activated, | ||||
|             this, [&] { | ||||
|                 if (ui.action_Load_Amiibo->isEnabled()) { | ||||
|                     OnLoadAmiibo(); | ||||
|                 } | ||||
|             }); | ||||
|     connect(hotkey_registry.GetHotkey("Main Window", "Remove Amiibo", this), &QShortcut::activated, | ||||
|             this, [&] { | ||||
|                 if (ui.action_Remove_Amiibo->isEnabled()) { | ||||
|                     OnRemoveAmiibo(); | ||||
|                 } | ||||
|             }); | ||||
| } | ||||
| 
 | ||||
| void GMainWindow::ShowUpdaterWidgets() { | ||||
|  | @ -495,6 +516,8 @@ void GMainWindow::ConnectMenuEvents() { | |||
|     connect(ui.action_Load_File, &QAction::triggered, this, &GMainWindow::OnMenuLoadFile); | ||||
|     connect(ui.action_Install_CIA, &QAction::triggered, this, &GMainWindow::OnMenuInstallCIA); | ||||
|     connect(ui.action_Exit, &QAction::triggered, this, &QMainWindow::close); | ||||
|     connect(ui.action_Load_Amiibo, &QAction::triggered, this, &GMainWindow::OnLoadAmiibo); | ||||
|     connect(ui.action_Remove_Amiibo, &QAction::triggered, this, &GMainWindow::OnRemoveAmiibo); | ||||
| 
 | ||||
|     // Emulation
 | ||||
|     connect(ui.action_Start, &QAction::triggered, this, &GMainWindow::OnStartGame); | ||||
|  | @ -667,12 +690,12 @@ bool GMainWindow::LoadROM(const QString& filename) { | |||
|     render_window->InitRenderTarget(); | ||||
|     render_window->MakeCurrent(); | ||||
| 
 | ||||
|     const char* below_gl33_title = "OpenGL 3.3 Unsupported"; | ||||
|     const char* below_gl33_message = "Your GPU may not support OpenGL 3.3, or you do not " | ||||
|                                      "have the latest graphics driver."; | ||||
|     const QString below_gl33_title = tr("OpenGL 3.3 Unsupported"); | ||||
|     const QString below_gl33_message = tr("Your GPU may not support OpenGL 3.3, or you do not " | ||||
|                                           "have the latest graphics driver."); | ||||
| 
 | ||||
|     if (!gladLoadGL()) { | ||||
|         QMessageBox::critical(this, tr(below_gl33_title), tr(below_gl33_message)); | ||||
|         QMessageBox::critical(this, below_gl33_title, below_gl33_message); | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|  | @ -743,7 +766,7 @@ bool GMainWindow::LoadROM(const QString& filename) { | |||
|             break; | ||||
| 
 | ||||
|         case Core::System::ResultStatus::ErrorVideoCore_ErrorBelowGL33: | ||||
|             QMessageBox::critical(this, tr(below_gl33_title), tr(below_gl33_message)); | ||||
|             QMessageBox::critical(this, below_gl33_title, below_gl33_message); | ||||
|             break; | ||||
| 
 | ||||
|         default: | ||||
|  | @ -847,6 +870,8 @@ void GMainWindow::ShutdownGame() { | |||
|     ui.action_Pause->setEnabled(false); | ||||
|     ui.action_Stop->setEnabled(false); | ||||
|     ui.action_Restart->setEnabled(false); | ||||
|     ui.action_Load_Amiibo->setEnabled(false); | ||||
|     ui.action_Remove_Amiibo->setEnabled(false); | ||||
|     ui.action_Report_Compatibility->setEnabled(false); | ||||
|     ui.action_Enable_Frame_Advancing->setEnabled(false); | ||||
|     ui.action_Enable_Frame_Advancing->setChecked(false); | ||||
|  | @ -960,14 +985,11 @@ void GMainWindow::OnGameListOpenFolder(u64 data_id, GameListOpenTarget target) { | |||
|     QDesktopServices::openUrl(QUrl::fromLocalFile(qpath)); | ||||
| } | ||||
| 
 | ||||
| void GMainWindow::OnGameListNavigateToGamedbEntry( | ||||
|     u64 program_id, | ||||
|     std::unordered_map<std::string, std::pair<QString, QString>>& compatibility_list) { | ||||
| 
 | ||||
| void GMainWindow::OnGameListNavigateToGamedbEntry(u64 program_id, | ||||
|                                                   const CompatibilityList& compatibility_list) { | ||||
|     auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id); | ||||
| 
 | ||||
|     QString directory; | ||||
| 
 | ||||
|     if (it != compatibility_list.end()) | ||||
|         directory = it->second.second; | ||||
| 
 | ||||
|  | @ -1137,6 +1159,7 @@ void GMainWindow::OnStartGame() { | |||
|     ui.action_Pause->setEnabled(true); | ||||
|     ui.action_Stop->setEnabled(true); | ||||
|     ui.action_Restart->setEnabled(true); | ||||
|     ui.action_Load_Amiibo->setEnabled(true); | ||||
|     ui.action_Report_Compatibility->setEnabled(true); | ||||
|     ui.action_Enable_Frame_Advancing->setEnabled(true); | ||||
| 
 | ||||
|  | @ -1291,6 +1314,57 @@ void GMainWindow::OnConfigure() { | |||
|     } | ||||
| } | ||||
| 
 | ||||
| void GMainWindow::OnLoadAmiibo() { | ||||
|     const QString extensions{"*.bin"}; | ||||
|     const QString file_filter = tr("Amiibo File (%1);; All Files (*.*)").arg(extensions); | ||||
|     const QString filename = QFileDialog::getOpenFileName(this, tr("Load Amiibo"), "", file_filter); | ||||
| 
 | ||||
|     if (filename.isEmpty()) { | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     Core::System& system{Core::System::GetInstance()}; | ||||
|     Service::SM::ServiceManager& sm = system.ServiceManager(); | ||||
|     auto nfc = sm.GetService<Service::NFC::Module::Interface>("nfc:u"); | ||||
|     if (nfc == nullptr) { | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     QFile nfc_file{filename}; | ||||
|     if (!nfc_file.open(QIODevice::ReadOnly)) { | ||||
|         QMessageBox::warning(this, tr("Error opening Amiibo data file"), | ||||
|                              tr("Unable to open Amiibo file \"%1\" for reading.").arg(filename)); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     Service::NFC::AmiiboData amiibo_data{}; | ||||
|     const u64 read_size = | ||||
|         nfc_file.read(reinterpret_cast<char*>(&amiibo_data), sizeof(Service::NFC::AmiiboData)); | ||||
|     if (read_size != sizeof(Service::NFC::AmiiboData)) { | ||||
|         QMessageBox::warning(this, tr("Error reading Amiibo data file"), | ||||
|                              tr("Unable to fully read Amiibo data. Expected to read %1 bytes, but " | ||||
|                                 "was only able to read %2 bytes.") | ||||
|                                  .arg(sizeof(Service::NFC::AmiiboData)) | ||||
|                                  .arg(read_size)); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     nfc->LoadAmiibo(amiibo_data); | ||||
|     ui.action_Remove_Amiibo->setEnabled(true); | ||||
| } | ||||
| 
 | ||||
| void GMainWindow::OnRemoveAmiibo() { | ||||
|     Core::System& system{Core::System::GetInstance()}; | ||||
|     Service::SM::ServiceManager& sm = system.ServiceManager(); | ||||
|     auto nfc = sm.GetService<Service::NFC::Module::Interface>("nfc:u"); | ||||
|     if (nfc == nullptr) { | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     nfc->RemoveAmiibo(); | ||||
|     ui.action_Remove_Amiibo->setEnabled(false); | ||||
| } | ||||
| 
 | ||||
| void GMainWindow::OnToggleFilterBar() { | ||||
|     game_list->setFilterVisible(ui.action_Show_Filter_Bar->isChecked()); | ||||
|     if (ui.action_Show_Filter_Bar->isChecked()) { | ||||
|  |  | |||
|  | @ -9,6 +9,7 @@ | |||
| #include <QMainWindow> | ||||
| #include <QTimer> | ||||
| #include <QTranslator> | ||||
| #include "citra_qt/compatibility_list.h" | ||||
| #include "citra_qt/hotkeys.h" | ||||
| #include "common/announce_multiplayer_room.h" | ||||
| #include "core/core.h" | ||||
|  | @ -153,9 +154,8 @@ private slots: | |||
|     /// Called whenever a user selects a game in the game list widget.
 | ||||
|     void OnGameListLoadFile(QString game_path); | ||||
|     void OnGameListOpenFolder(u64 program_id, GameListOpenTarget target); | ||||
|     void OnGameListNavigateToGamedbEntry( | ||||
|         u64 program_id, | ||||
|         std::unordered_map<std::string, std::pair<QString, QString>>& compatibility_list); | ||||
|     void OnGameListNavigateToGamedbEntry(u64 program_id, | ||||
|                                          const CompatibilityList& compatibility_list); | ||||
|     void OnGameListOpenDirectory(QString path); | ||||
|     void OnGameListAddDirectory(); | ||||
|     void OnGameListShowList(bool show); | ||||
|  | @ -166,6 +166,8 @@ private slots: | |||
|     void OnCIAInstallFinished(); | ||||
|     void OnMenuRecentFile(); | ||||
|     void OnConfigure(); | ||||
|     void OnLoadAmiibo(); | ||||
|     void OnRemoveAmiibo(); | ||||
|     void OnToggleFilterBar(); | ||||
|     void OnDisplayTitleBars(bool); | ||||
|     void ToggleFullscreen(); | ||||
|  |  | |||
|  | @ -57,11 +57,20 @@ | |||
|       <string>Recent Files</string> | ||||
|      </property> | ||||
|     </widget> | ||||
|     <widget class="QMenu" name="menu_Amiibo"> | ||||
|      <property name="title"> | ||||
|       <string>Amiibo</string> | ||||
|      </property> | ||||
|      <addaction name="action_Load_Amiibo"/> | ||||
|      <addaction name="action_Remove_Amiibo"/> | ||||
|     </widget> | ||||
|     <addaction name="action_Load_File"/> | ||||
|     <addaction name="action_Install_CIA"/> | ||||
|     <addaction name="separator"/> | ||||
|     <addaction name="menu_recent_files"/> | ||||
|     <addaction name="separator"/> | ||||
|     <addaction name="menu_Amiibo"/> | ||||
|     <addaction name="separator"/> | ||||
|     <addaction name="action_Exit"/> | ||||
|    </widget> | ||||
|    <widget class="QMenu" name="menu_Emulation"> | ||||
|  | @ -73,6 +82,8 @@ | |||
|     <addaction name="action_Stop"/> | ||||
|     <addaction name="action_Restart"/> | ||||
|     <addaction name="separator"/> | ||||
|     <addaction name="action_Report_Compatibility"/> | ||||
|     <addaction name="separator"/> | ||||
|     <addaction name="action_Configure"/> | ||||
|    </widget> | ||||
|    <widget class="QMenu" name="menu_View"> | ||||
|  | @ -139,8 +150,6 @@ | |||
|     <addaction name="action_Check_For_Updates"/> | ||||
|     <addaction name="action_Open_Maintenance_Tool"/> | ||||
|     <addaction name="separator"/> | ||||
|     <addaction name="action_Report_Compatibility"/> | ||||
|     <addaction name="separator"/> | ||||
|     <addaction name="action_FAQ"/> | ||||
|     <addaction name="action_About"/> | ||||
|    </widget> | ||||
|  | @ -415,6 +424,22 @@ | |||
|     <string>Restart</string> | ||||
|    </property> | ||||
|   </action> | ||||
|   <action name="action_Load_Amiibo"> | ||||
|    <property name="enabled"> | ||||
|     <bool>false</bool> | ||||
|    </property> | ||||
|    <property name="text"> | ||||
|     <string>Load...</string> | ||||
|    </property> | ||||
|   </action> | ||||
|   <action name="action_Remove_Amiibo"> | ||||
|    <property name="enabled"> | ||||
|     <bool>false</bool> | ||||
|    </property> | ||||
|    <property name="text"> | ||||
|     <string>Remove</string> | ||||
|    </property> | ||||
|   </action> | ||||
|  </widget> | ||||
|  <resources/> | ||||
|  <connections/> | ||||
|  |  | |||
|  | @ -65,8 +65,6 @@ add_library(common STATIC | |||
|     logging/text_formatter.cpp | ||||
|     logging/text_formatter.h | ||||
|     math_util.h | ||||
|     memory_util.cpp | ||||
|     memory_util.h | ||||
|     microprofile.cpp | ||||
|     microprofile.h | ||||
|     microprofileui.h | ||||
|  | @ -90,6 +88,7 @@ add_library(common STATIC | |||
|     timer.cpp | ||||
|     timer.h | ||||
|     vector_math.h | ||||
|     web_result.h | ||||
| ) | ||||
| 
 | ||||
| if(ARCHITECTURE_x86_64) | ||||
|  |  | |||
|  | @ -9,23 +9,7 @@ | |||
| #include <string> | ||||
| #include <vector> | ||||
| #include "common/common_types.h" | ||||
| 
 | ||||
| namespace Common { | ||||
| struct WebResult { | ||||
|     enum class Code : u32 { | ||||
|         Success, | ||||
|         InvalidURL, | ||||
|         CredentialsMissing, | ||||
|         LibError, | ||||
|         HttpError, | ||||
|         WrongContent, | ||||
|         NoWebservice, | ||||
|     }; | ||||
|     Code result_code; | ||||
|     std::string result_string; | ||||
|     std::string returned_data; | ||||
| }; | ||||
| } // namespace Common
 | ||||
| #include "common/web_result.h" | ||||
| 
 | ||||
| namespace AnnounceMultiplayerRoom { | ||||
| 
 | ||||
|  |  | |||
|  | @ -51,3 +51,4 @@ | |||
| #define SHARED_FONT "shared_font.bin" | ||||
| #define AES_KEYS "aes_keys.txt" | ||||
| #define BOOTROM9 "boot9.bin" | ||||
| #define SECRET_SECTOR "sector0x96.bin" | ||||
|  |  | |||
|  | @ -14,21 +14,24 @@ | |||
| #ifdef _WIN32 | ||||
| #include <windows.h> | ||||
| // windows.h needs to be included before other windows headers
 | ||||
| #include <commdlg.h> // for GetSaveFileName
 | ||||
| #include <direct.h>  // getcwd
 | ||||
| #include <direct.h> // getcwd
 | ||||
| #include <io.h> | ||||
| #include <shellapi.h> | ||||
| #include <shlobj.h> // for SHGetFolderPath
 | ||||
| #include <tchar.h> | ||||
| #include "common/string_util.h" | ||||
| 
 | ||||
| // 64 bit offsets for windows
 | ||||
| #ifdef _MSC_VER | ||||
| // 64 bit offsets for MSVC
 | ||||
| #define fseeko _fseeki64 | ||||
| #define ftello _ftelli64 | ||||
| #define atoll _atoi64 | ||||
| #define fileno _fileno | ||||
| #endif | ||||
| 
 | ||||
| // 64 bit offsets for MSVC and MinGW. MinGW also needs this for using _wstat64
 | ||||
| #define stat _stat64 | ||||
| #define fstat _fstat64 | ||||
| #define fileno _fileno | ||||
| 
 | ||||
| #else | ||||
| #ifdef __APPLE__ | ||||
| #include <sys/param.h> | ||||
|  |  | |||
|  | @ -1,178 +0,0 @@ | |||
| // Copyright 2013 Dolphin Emulator Project / 2014 Citra Emulator Project
 | ||||
| // Licensed under GPLv2 or any later version
 | ||||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| #include <fmt/format.h> | ||||
| #include "common/logging/log.h" | ||||
| #include "common/memory_util.h" | ||||
| 
 | ||||
| #ifdef _WIN32 | ||||
| #include <windows.h> | ||||
| // Windows.h needs to be included before psapi.h
 | ||||
| #include <psapi.h> | ||||
| #include "common/common_funcs.h" | ||||
| #include "common/string_util.h" | ||||
| #else | ||||
| #include <cstdlib> | ||||
| #include <sys/mman.h> | ||||
| #endif | ||||
| 
 | ||||
| #if !defined(_WIN32) && defined(ARCHITECTURE_x86_64) && !defined(MAP_32BIT) | ||||
| #include <unistd.h> | ||||
| #define PAGE_MASK (getpagesize() - 1) | ||||
| #define round_page(x) ((((unsigned long)(x)) + PAGE_MASK) & ~(PAGE_MASK)) | ||||
| #endif | ||||
| 
 | ||||
| // This is purposely not a full wrapper for virtualalloc/mmap, but it
 | ||||
| // provides exactly the primitive operations that Dolphin needs.
 | ||||
| 
 | ||||
| void* AllocateExecutableMemory(std::size_t size, bool low) { | ||||
| #if defined(_WIN32) | ||||
|     void* ptr = VirtualAlloc(nullptr, size, MEM_COMMIT, PAGE_EXECUTE_READWRITE); | ||||
| #else | ||||
|     static char* map_hint = nullptr; | ||||
| #if defined(ARCHITECTURE_x86_64) && !defined(MAP_32BIT) | ||||
|     // This OS has no flag to enforce allocation below the 4 GB boundary,
 | ||||
|     // but if we hint that we want a low address it is very likely we will
 | ||||
|     // get one.
 | ||||
|     // An older version of this code used MAP_FIXED, but that has the side
 | ||||
|     // effect of discarding already mapped pages that happen to be in the
 | ||||
|     // requested virtual memory range (such as the emulated RAM, sometimes).
 | ||||
|     if (low && (!map_hint)) | ||||
|         map_hint = (char*)round_page(512 * 1024 * 1024); /* 0.5 GB rounded up to the next page */ | ||||
| #endif | ||||
|     void* ptr = mmap(map_hint, size, PROT_READ | PROT_WRITE | PROT_EXEC, | ||||
|                      MAP_ANON | MAP_PRIVATE | ||||
| #if defined(ARCHITECTURE_x86_64) && defined(MAP_32BIT) | ||||
|                          | (low ? MAP_32BIT : 0) | ||||
| #endif | ||||
|                          , | ||||
|                      -1, 0); | ||||
| #endif /* defined(_WIN32) */ | ||||
| 
 | ||||
| #ifdef _WIN32 | ||||
|     if (ptr == nullptr) { | ||||
| #else | ||||
|     if (ptr == MAP_FAILED) { | ||||
|         ptr = nullptr; | ||||
| #endif | ||||
|         LOG_ERROR(Common_Memory, "Failed to allocate executable memory"); | ||||
|     } | ||||
| #if !defined(_WIN32) && defined(ARCHITECTURE_x86_64) && !defined(MAP_32BIT) | ||||
|     else { | ||||
|         if (low) { | ||||
|             map_hint += size; | ||||
|             map_hint = (char*)round_page(map_hint); /* round up to the next page */ | ||||
|         } | ||||
|     } | ||||
| #endif | ||||
| 
 | ||||
| #if EMU_ARCH_BITS == 64 | ||||
|     if ((u64)ptr >= 0x80000000 && low == true) | ||||
|         LOG_ERROR(Common_Memory, "Executable memory ended up above 2GB!"); | ||||
| #endif | ||||
| 
 | ||||
|     return ptr; | ||||
| } | ||||
| 
 | ||||
| void* AllocateMemoryPages(std::size_t size) { | ||||
| #ifdef _WIN32 | ||||
|     void* ptr = VirtualAlloc(nullptr, size, MEM_COMMIT, PAGE_READWRITE); | ||||
| #else | ||||
|     void* ptr = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0); | ||||
| 
 | ||||
|     if (ptr == MAP_FAILED) | ||||
|         ptr = nullptr; | ||||
| #endif | ||||
| 
 | ||||
|     if (ptr == nullptr) | ||||
|         LOG_ERROR(Common_Memory, "Failed to allocate raw memory"); | ||||
| 
 | ||||
|     return ptr; | ||||
| } | ||||
| 
 | ||||
| void* AllocateAlignedMemory(std::size_t size, std::size_t alignment) { | ||||
| #ifdef _WIN32 | ||||
|     void* ptr = _aligned_malloc(size, alignment); | ||||
| #else | ||||
|     void* ptr = nullptr; | ||||
| #ifdef ANDROID | ||||
|     ptr = memalign(alignment, size); | ||||
| #else | ||||
|     if (posix_memalign(&ptr, alignment, size) != 0) | ||||
|         LOG_ERROR(Common_Memory, "Failed to allocate aligned memory"); | ||||
| #endif | ||||
| #endif | ||||
| 
 | ||||
|     if (ptr == nullptr) | ||||
|         LOG_ERROR(Common_Memory, "Failed to allocate aligned memory"); | ||||
| 
 | ||||
|     return ptr; | ||||
| } | ||||
| 
 | ||||
| void FreeMemoryPages(void* ptr, std::size_t size) { | ||||
|     if (ptr) { | ||||
| #ifdef _WIN32 | ||||
|         if (!VirtualFree(ptr, 0, MEM_RELEASE)) | ||||
|             LOG_ERROR(Common_Memory, "FreeMemoryPages failed!\n{}", GetLastErrorMsg()); | ||||
| #else | ||||
|         munmap(ptr, size); | ||||
| #endif | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void FreeAlignedMemory(void* ptr) { | ||||
|     if (ptr) { | ||||
| #ifdef _WIN32 | ||||
|         _aligned_free(ptr); | ||||
| #else | ||||
|         free(ptr); | ||||
| #endif | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void WriteProtectMemory(void* ptr, std::size_t size, bool allowExecute) { | ||||
| #ifdef _WIN32 | ||||
|     DWORD oldValue; | ||||
|     if (!VirtualProtect(ptr, size, allowExecute ? PAGE_EXECUTE_READ : PAGE_READONLY, &oldValue)) | ||||
|         LOG_ERROR(Common_Memory, "WriteProtectMemory failed!\n{}", GetLastErrorMsg()); | ||||
| #else | ||||
|     mprotect(ptr, size, allowExecute ? (PROT_READ | PROT_EXEC) : PROT_READ); | ||||
| #endif | ||||
| } | ||||
| 
 | ||||
| void UnWriteProtectMemory(void* ptr, std::size_t size, bool allowExecute) { | ||||
| #ifdef _WIN32 | ||||
|     DWORD oldValue; | ||||
|     if (!VirtualProtect(ptr, size, allowExecute ? PAGE_EXECUTE_READWRITE : PAGE_READWRITE, | ||||
|                         &oldValue)) | ||||
|         LOG_ERROR(Common_Memory, "UnWriteProtectMemory failed!\n{}", GetLastErrorMsg()); | ||||
| #else | ||||
|     mprotect(ptr, size, | ||||
|              allowExecute ? (PROT_READ | PROT_WRITE | PROT_EXEC) : PROT_WRITE | PROT_READ); | ||||
| #endif | ||||
| } | ||||
| 
 | ||||
| std::string MemUsage() { | ||||
| #ifdef _WIN32 | ||||
| #pragma comment(lib, "psapi") | ||||
|     DWORD processID = GetCurrentProcessId(); | ||||
|     HANDLE hProcess; | ||||
|     PROCESS_MEMORY_COUNTERS pmc; | ||||
|     std::string Ret; | ||||
| 
 | ||||
|     // Print information about the memory usage of the process.
 | ||||
| 
 | ||||
|     hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, processID); | ||||
|     if (nullptr == hProcess) | ||||
|         return "MemUsage Error"; | ||||
| 
 | ||||
|     if (GetProcessMemoryInfo(hProcess, &pmc, sizeof(pmc))) | ||||
|         Ret = fmt::format("{} K", Common::ThousandSeparate(pmc.WorkingSetSize / 1024, 7)); | ||||
| 
 | ||||
|     CloseHandle(hProcess); | ||||
|     return Ret; | ||||
| #else | ||||
|     return ""; | ||||
| #endif | ||||
| } | ||||
|  | @ -1,21 +0,0 @@ | |||
| // Copyright 2013 Dolphin Emulator Project / 2014 Citra Emulator Project
 | ||||
| // Licensed under GPLv2 or any later version
 | ||||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| #pragma once | ||||
| 
 | ||||
| #include <cstddef> | ||||
| #include <string> | ||||
| 
 | ||||
| void* AllocateExecutableMemory(std::size_t size, bool low = true); | ||||
| void* AllocateMemoryPages(std::size_t size); | ||||
| void FreeMemoryPages(void* ptr, std::size_t size); | ||||
| void* AllocateAlignedMemory(std::size_t size, std::size_t alignment); | ||||
| void FreeAlignedMemory(void* ptr); | ||||
| void WriteProtectMemory(void* ptr, std::size_t size, bool executable = false); | ||||
| void UnWriteProtectMemory(void* ptr, std::size_t size, bool allowExecute = false); | ||||
| std::string MemUsage(); | ||||
| 
 | ||||
| inline int GetPageSize() { | ||||
|     return 4096; | ||||
| } | ||||
|  | @ -153,6 +153,7 @@ struct VisitorInterface : NonCopyable { | |||
| 
 | ||||
|     /// Completion method, called once all fields have been visited
 | ||||
|     virtual void Complete() = 0; | ||||
|     virtual bool SubmitTestcase() = 0; | ||||
| }; | ||||
| 
 | ||||
| /**
 | ||||
|  | @ -178,6 +179,9 @@ struct NullVisitor : public VisitorInterface { | |||
|     void Visit(const Field<std::chrono::microseconds>& /*field*/) override {} | ||||
| 
 | ||||
|     void Complete() override {} | ||||
|     bool SubmitTestcase() override { | ||||
|         return false; | ||||
|     } | ||||
| }; | ||||
| 
 | ||||
| } // namespace Telemetry
 | ||||
|  |  | |||
							
								
								
									
										25
									
								
								src/common/web_result.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								src/common/web_result.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,25 @@ | |||
| // Copyright 2018 Citra Emulator Project
 | ||||
| // Licensed under GPLv2 or any later version
 | ||||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| #pragma once | ||||
| 
 | ||||
| #include <string> | ||||
| #include "common/common_types.h" | ||||
| 
 | ||||
| namespace Common { | ||||
| struct WebResult { | ||||
|     enum class Code : u32 { | ||||
|         Success, | ||||
|         InvalidURL, | ||||
|         CredentialsMissing, | ||||
|         LibError, | ||||
|         HttpError, | ||||
|         WrongContent, | ||||
|         NoWebservice, | ||||
|     }; | ||||
|     Code result_code; | ||||
|     std::string result_string; | ||||
|     std::string returned_data; | ||||
| }; | ||||
| } // namespace Common
 | ||||
|  | @ -58,6 +58,7 @@ add_library(core STATIC | |||
|     file_sys/disk_archive.h | ||||
|     file_sys/errors.h | ||||
|     file_sys/file_backend.h | ||||
|     file_sys/delay_generator.cpp | ||||
|     file_sys/delay_generator.h | ||||
|     file_sys/ivfc_archive.cpp | ||||
|     file_sys/ivfc_archive.h | ||||
|  | @ -102,8 +103,6 @@ add_library(core STATIC | |||
|     hle/applets/mint.h | ||||
|     hle/applets/swkbd.cpp | ||||
|     hle/applets/swkbd.h | ||||
|     hle/config_mem.cpp | ||||
|     hle/config_mem.h | ||||
|     hle/function_wrappers.h | ||||
|     hle/ipc.h | ||||
|     hle/ipc_helpers.h | ||||
|  | @ -113,6 +112,8 @@ add_library(core STATIC | |||
|     hle/kernel/client_port.h | ||||
|     hle/kernel/client_session.cpp | ||||
|     hle/kernel/client_session.h | ||||
|     hle/kernel/config_mem.cpp | ||||
|     hle/kernel/config_mem.h | ||||
|     hle/kernel/errors.h | ||||
|     hle/kernel/event.cpp | ||||
|     hle/kernel/event.h | ||||
|  | @ -143,6 +144,8 @@ add_library(core STATIC | |||
|     hle/kernel/session.h | ||||
|     hle/kernel/shared_memory.cpp | ||||
|     hle/kernel/shared_memory.h | ||||
|     hle/kernel/shared_page.cpp | ||||
|     hle/kernel/shared_page.h | ||||
|     hle/kernel/svc.cpp | ||||
|     hle/kernel/svc.h | ||||
|     hle/kernel/thread.cpp | ||||
|  | @ -385,8 +388,6 @@ add_library(core STATIC | |||
|     hle/service/ssl_c.h | ||||
|     hle/service/y2r_u.cpp | ||||
|     hle/service/y2r_u.h | ||||
|     hle/shared_page.cpp | ||||
|     hle/shared_page.h | ||||
|     hw/aes/arithmetic128.cpp | ||||
|     hw/aes/arithmetic128.h | ||||
|     hw/aes/ccm.cpp | ||||
|  | @ -446,7 +447,7 @@ target_link_libraries(core PUBLIC common PRIVATE audio_core network video_core) | |||
| target_link_libraries(core PUBLIC Boost::boost PRIVATE cryptopp fmt open_source_archives) | ||||
| if (ENABLE_WEB_SERVICE) | ||||
|     target_compile_definitions(core PRIVATE -DENABLE_WEB_SERVICE) | ||||
|     target_link_libraries(core PRIVATE json-headers web_service) | ||||
|     target_link_libraries(core PRIVATE web_service) | ||||
| endif() | ||||
| 
 | ||||
| if (ARCHITECTURE_x86_64) | ||||
|  |  | |||
|  | @ -71,7 +71,8 @@ private: | |||
| 
 | ||||
| class DynarmicUserCallbacks final : public Dynarmic::A32::UserCallbacks { | ||||
| public: | ||||
|     explicit DynarmicUserCallbacks(ARM_Dynarmic& parent) : parent(parent) {} | ||||
|     explicit DynarmicUserCallbacks(ARM_Dynarmic& parent) | ||||
|         : parent(parent), timing(parent.system.CoreTiming()) {} | ||||
|     ~DynarmicUserCallbacks() = default; | ||||
| 
 | ||||
|     std::uint8_t MemoryRead8(VAddr vaddr) override { | ||||
|  | @ -134,7 +135,8 @@ public: | |||
|             if (GDBStub::IsConnected()) { | ||||
|                 parent.jit->HaltExecution(); | ||||
|                 parent.SetPC(pc); | ||||
|                 Kernel::Thread* thread = Kernel::GetCurrentThread(); | ||||
|                 Kernel::Thread* thread = | ||||
|                     Core::System::GetInstance().Kernel().GetThreadManager().GetCurrentThread(); | ||||
|                 parent.SaveContext(thread->context); | ||||
|                 GDBStub::Break(); | ||||
|                 GDBStub::SendTrap(thread, 5); | ||||
|  | @ -147,18 +149,19 @@ public: | |||
|     } | ||||
| 
 | ||||
|     void AddTicks(std::uint64_t ticks) override { | ||||
|         CoreTiming::AddTicks(ticks); | ||||
|         timing.AddTicks(ticks); | ||||
|     } | ||||
|     std::uint64_t GetTicksRemaining() override { | ||||
|         s64 ticks = CoreTiming::GetDowncount(); | ||||
|         s64 ticks = timing.GetDowncount(); | ||||
|         return static_cast<u64>(ticks <= 0 ? 0 : ticks); | ||||
|     } | ||||
| 
 | ||||
|     ARM_Dynarmic& parent; | ||||
|     Core::Timing& timing; | ||||
| }; | ||||
| 
 | ||||
| ARM_Dynarmic::ARM_Dynarmic(PrivilegeMode initial_mode) | ||||
|     : cb(std::make_unique<DynarmicUserCallbacks>(*this)) { | ||||
| ARM_Dynarmic::ARM_Dynarmic(Core::System& system, PrivilegeMode initial_mode) | ||||
|     : system(system), cb(std::make_unique<DynarmicUserCallbacks>(*this)) { | ||||
|     interpreter_state = std::make_shared<ARMul_State>(initial_mode); | ||||
|     PageTableChanged(); | ||||
| } | ||||
|  |  | |||
|  | @ -15,11 +15,15 @@ namespace Memory { | |||
| struct PageTable; | ||||
| } // namespace Memory
 | ||||
| 
 | ||||
| namespace Core { | ||||
| struct System; | ||||
| } | ||||
| 
 | ||||
| class DynarmicUserCallbacks; | ||||
| 
 | ||||
| class ARM_Dynarmic final : public ARM_Interface { | ||||
| public: | ||||
|     explicit ARM_Dynarmic(PrivilegeMode initial_mode); | ||||
|     ARM_Dynarmic(Core::System& system, PrivilegeMode initial_mode); | ||||
|     ~ARM_Dynarmic(); | ||||
| 
 | ||||
|     void Run() override; | ||||
|  | @ -50,6 +54,7 @@ public: | |||
| 
 | ||||
| private: | ||||
|     friend class DynarmicUserCallbacks; | ||||
|     Core::System& system; | ||||
|     std::unique_ptr<DynarmicUserCallbacks> cb; | ||||
|     std::unique_ptr<Dynarmic::A32::Jit> MakeJit(); | ||||
| 
 | ||||
|  |  | |||
|  | @ -75,7 +75,7 @@ ARM_DynCom::ARM_DynCom(PrivilegeMode initial_mode) { | |||
| ARM_DynCom::~ARM_DynCom() {} | ||||
| 
 | ||||
| void ARM_DynCom::Run() { | ||||
|     ExecuteInstructions(std::max<s64>(CoreTiming::GetDowncount(), 0)); | ||||
|     ExecuteInstructions(std::max<s64>(Core::System::GetInstance().CoreTiming().GetDowncount(), 0)); | ||||
| } | ||||
| 
 | ||||
| void ARM_DynCom::Step() { | ||||
|  | @ -146,7 +146,7 @@ void ARM_DynCom::SetCP15Register(CP15Register reg, u32 value) { | |||
| void ARM_DynCom::ExecuteInstructions(u64 num_instructions) { | ||||
|     state->NumInstrsToExecute = num_instructions; | ||||
|     unsigned ticks_executed = InterpreterMainLoop(state.get()); | ||||
|     CoreTiming::AddTicks(ticks_executed); | ||||
|     Core::System::GetInstance().CoreTiming().AddTicks(ticks_executed); | ||||
|     state->ServeBreak(); | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -18,6 +18,7 @@ | |||
| #include "core/arm/skyeye_common/armstate.h" | ||||
| #include "core/arm/skyeye_common/armsupp.h" | ||||
| #include "core/arm/skyeye_common/vfp/vfp.h" | ||||
| #include "core/core.h" | ||||
| #include "core/core_timing.h" | ||||
| #include "core/gdbstub/gdbstub.h" | ||||
| #include "core/hle/kernel/svc.h" | ||||
|  | @ -3859,7 +3860,7 @@ SUB_INST : { | |||
| SWI_INST : { | ||||
|     if (inst_base->cond == ConditionCode::AL || CondPassed(cpu, inst_base->cond)) { | ||||
|         swi_inst* const inst_cream = (swi_inst*)inst_base->component; | ||||
|         CoreTiming::AddTicks(num_instrs); | ||||
|         Core::System::GetInstance().CoreTiming().AddTicks(num_instrs); | ||||
|         cpu->NumInstrsToExecute = | ||||
|             num_instrs >= cpu->NumInstrsToExecute ? 0 : cpu->NumInstrsToExecute - num_instrs; | ||||
|         num_instrs = 0; | ||||
|  |  | |||
|  | @ -604,7 +604,8 @@ void ARMul_State::ServeBreak() { | |||
|     if (last_bkpt_hit) { | ||||
|         Reg[15] = last_bkpt.address; | ||||
|     } | ||||
|     Kernel::Thread* thread = Kernel::GetCurrentThread(); | ||||
|     Kernel::Thread* thread = | ||||
|         Core::System::GetInstance().Kernel().GetThreadManager().GetCurrentThread(); | ||||
|     Core::CPU().SaveContext(thread->context); | ||||
|     if (last_bkpt_hit || GDBStub::GetCpuStepFlag()) { | ||||
|         last_bkpt_hit = false; | ||||
|  |  | |||
|  | @ -59,13 +59,13 @@ System::ResultStatus System::RunLoop(bool tight_loop) { | |||
| 
 | ||||
|     // If we don't have a currently active thread then don't execute instructions,
 | ||||
|     // instead advance to the next event and try to yield to the next thread
 | ||||
|     if (Kernel::GetCurrentThread() == nullptr) { | ||||
|     if (kernel->GetThreadManager().GetCurrentThread() == nullptr) { | ||||
|         LOG_TRACE(Core_ARM11, "Idling"); | ||||
|         CoreTiming::Idle(); | ||||
|         CoreTiming::Advance(); | ||||
|         timing->Idle(); | ||||
|         timing->Advance(); | ||||
|         PrepareReschedule(); | ||||
|     } else { | ||||
|         CoreTiming::Advance(); | ||||
|         timing->Advance(); | ||||
|         if (tight_loop) { | ||||
|             cpu_core->Run(); | ||||
|         } else { | ||||
|  | @ -126,7 +126,9 @@ System::ResultStatus System::Load(EmuWindow& emu_window, const std::string& file | |||
|         return init_result; | ||||
|     } | ||||
| 
 | ||||
|     const Loader::ResultStatus load_result{app_loader->Load(Kernel::g_current_process)}; | ||||
|     Kernel::SharedPtr<Kernel::Process> process; | ||||
|     const Loader::ResultStatus load_result{app_loader->Load(process)}; | ||||
|     kernel->SetCurrentProcess(process); | ||||
|     if (Loader::ResultStatus::Success != load_result) { | ||||
|         LOG_CRITICAL(Core, "Failed to load ROM (Error {})!", static_cast<u32>(load_result)); | ||||
|         System::Shutdown(); | ||||
|  | @ -140,7 +142,7 @@ System::ResultStatus System::Load(EmuWindow& emu_window, const std::string& file | |||
|             return ResultStatus::ErrorLoader; | ||||
|         } | ||||
|     } | ||||
|     Memory::SetCurrentPageTable(&Kernel::g_current_process->vm_manager.page_table); | ||||
|     Memory::SetCurrentPageTable(&kernel->GetCurrentProcess()->vm_manager.page_table); | ||||
|     status = ResultStatus::Success; | ||||
|     m_emu_window = &emu_window; | ||||
|     m_filepath = filepath; | ||||
|  | @ -153,7 +155,7 @@ void System::PrepareReschedule() { | |||
| } | ||||
| 
 | ||||
| PerfStats::Results System::GetAndResetPerfStats() { | ||||
|     return perf_stats.GetAndResetStats(CoreTiming::GetGlobalTimeUs()); | ||||
|     return perf_stats.GetAndResetStats(timing->GetGlobalTimeUs()); | ||||
| } | ||||
| 
 | ||||
| void System::Reschedule() { | ||||
|  | @ -162,17 +164,17 @@ void System::Reschedule() { | |||
|     } | ||||
| 
 | ||||
|     reschedule_pending = false; | ||||
|     Kernel::Reschedule(); | ||||
|     kernel->GetThreadManager().Reschedule(); | ||||
| } | ||||
| 
 | ||||
| System::ResultStatus System::Init(EmuWindow& emu_window, u32 system_mode) { | ||||
|     LOG_DEBUG(HW_Memory, "initialized OK"); | ||||
| 
 | ||||
|     CoreTiming::Init(); | ||||
|     timing = std::make_unique<Timing>(); | ||||
| 
 | ||||
|     if (Settings::values.use_cpu_jit) { | ||||
| #ifdef ARCHITECTURE_x86_64 | ||||
|         cpu_core = std::make_unique<ARM_Dynarmic>(USER32MODE); | ||||
|         cpu_core = std::make_unique<ARM_Dynarmic>(*this, USER32MODE); | ||||
| #else | ||||
|         cpu_core = std::make_unique<ARM_DynCom>(USER32MODE); | ||||
|         LOG_WARNING(Core, "CPU JIT requested, but Dynarmic not available"); | ||||
|  | @ -191,13 +193,12 @@ System::ResultStatus System::Init(EmuWindow& emu_window, u32 system_mode) { | |||
|     rpc_server = std::make_unique<RPC::RPCServer>(); | ||||
| #endif | ||||
| 
 | ||||
|     service_manager = std::make_shared<Service::SM::ServiceManager>(); | ||||
|     shared_page_handler = std::make_shared<SharedPage::Handler>(); | ||||
|     archive_manager = std::make_unique<Service::FS::ArchiveManager>(); | ||||
|     service_manager = std::make_shared<Service::SM::ServiceManager>(*this); | ||||
|     archive_manager = std::make_unique<Service::FS::ArchiveManager>(*this); | ||||
| 
 | ||||
|     HW::Init(); | ||||
|     Kernel::Init(system_mode); | ||||
|     Service::Init(*this, service_manager); | ||||
|     kernel = std::make_unique<Kernel::KernelSystem>(system_mode); | ||||
|     Service::Init(*this); | ||||
|     GDBStub::Init(); | ||||
| 
 | ||||
|     ResultStatus result = VideoCore::Init(emu_window); | ||||
|  | @ -230,6 +231,22 @@ const Service::FS::ArchiveManager& System::ArchiveManager() const { | |||
|     return *archive_manager; | ||||
| } | ||||
| 
 | ||||
| Kernel::KernelSystem& System::Kernel() { | ||||
|     return *kernel; | ||||
| } | ||||
| 
 | ||||
| const Kernel::KernelSystem& System::Kernel() const { | ||||
|     return *kernel; | ||||
| } | ||||
| 
 | ||||
| Timing& System::CoreTiming() { | ||||
|     return *timing; | ||||
| } | ||||
| 
 | ||||
| const Timing& System::CoreTiming() const { | ||||
|     return *timing; | ||||
| } | ||||
| 
 | ||||
| void System::RegisterSoftwareKeyboard(std::shared_ptr<Frontend::SoftwareKeyboard> swkbd) { | ||||
|     registered_swkbd = std::move(swkbd); | ||||
| } | ||||
|  | @ -247,8 +264,7 @@ void System::Shutdown() { | |||
|     // Shutdown emulation session
 | ||||
|     GDBStub::Shutdown(); | ||||
|     VideoCore::Shutdown(); | ||||
|     Service::Shutdown(); | ||||
|     Kernel::Shutdown(); | ||||
|     kernel.reset(); | ||||
|     HW::Shutdown(); | ||||
|     telemetry_session.reset(); | ||||
| #ifdef ENABLE_SCRIPTING | ||||
|  | @ -257,7 +273,7 @@ void System::Shutdown() { | |||
|     service_manager.reset(); | ||||
|     dsp_core.reset(); | ||||
|     cpu_core.reset(); | ||||
|     CoreTiming::Shutdown(); | ||||
|     timing.reset(); | ||||
|     app_loader.reset(); | ||||
| 
 | ||||
|     if (auto room_member = Network::GetRoomMember().lock()) { | ||||
|  |  | |||
|  | @ -8,7 +8,6 @@ | |||
| #include <string> | ||||
| #include "common/common_types.h" | ||||
| #include "core/frontend/applets/swkbd.h" | ||||
| #include "core/hle/shared_page.h" | ||||
| #include "core/loader/loader.h" | ||||
| #include "core/memory.h" | ||||
| #include "core/perf_stats.h" | ||||
|  | @ -36,8 +35,14 @@ class ArchiveManager; | |||
| } | ||||
| } // namespace Service
 | ||||
| 
 | ||||
| namespace Kernel { | ||||
| class KernelSystem; | ||||
| } | ||||
| 
 | ||||
| namespace Core { | ||||
| 
 | ||||
| class Timing; | ||||
| 
 | ||||
| class System { | ||||
| public: | ||||
|     /**
 | ||||
|  | @ -167,6 +172,18 @@ public: | |||
|     /// Gets a const reference to the archive manager
 | ||||
|     const Service::FS::ArchiveManager& ArchiveManager() const; | ||||
| 
 | ||||
|     /// Gets a reference to the kernel
 | ||||
|     Kernel::KernelSystem& Kernel(); | ||||
| 
 | ||||
|     /// Gets a const reference to the kernel
 | ||||
|     const Kernel::KernelSystem& Kernel() const; | ||||
| 
 | ||||
|     /// Gets a reference to the timing system
 | ||||
|     Timing& CoreTiming(); | ||||
| 
 | ||||
|     /// Gets a const reference to the timing system
 | ||||
|     const Timing& CoreTiming() const; | ||||
| 
 | ||||
|     PerfStats perf_stats; | ||||
|     FrameLimiter frame_limiter; | ||||
| 
 | ||||
|  | @ -193,10 +210,6 @@ public: | |||
|         return registered_swkbd; | ||||
|     } | ||||
| 
 | ||||
|     std::shared_ptr<SharedPage::Handler> GetSharedPageHandler() const { | ||||
|         return shared_page_handler; | ||||
|     } | ||||
| 
 | ||||
| private: | ||||
|     /**
 | ||||
|      * Initialize the emulated system. | ||||
|  | @ -236,11 +249,14 @@ private: | |||
|     std::unique_ptr<RPC::RPCServer> rpc_server; | ||||
| #endif | ||||
| 
 | ||||
|     /// Shared Page
 | ||||
|     std::shared_ptr<SharedPage::Handler> shared_page_handler; | ||||
| 
 | ||||
|     std::unique_ptr<Service::FS::ArchiveManager> archive_manager; | ||||
| 
 | ||||
| public: // HACK: this is temporary exposed for tests,
 | ||||
|         // due to WIP kernel refactor causing desync state in memory
 | ||||
|     std::unique_ptr<Kernel::KernelSystem> kernel; | ||||
|     std::unique_ptr<Timing> timing; | ||||
| 
 | ||||
| private: | ||||
|     static System s_instance; | ||||
| 
 | ||||
|     ResultStatus status = ResultStatus::Success; | ||||
|  |  | |||
|  | @ -2,75 +2,25 @@ | |||
| // Licensed under GPLv2+
 | ||||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| #include "core/core_timing.h" | ||||
| 
 | ||||
| #include <algorithm> | ||||
| #include <cinttypes> | ||||
| #include <mutex> | ||||
| #include <string> | ||||
| #include <tuple> | ||||
| #include <unordered_map> | ||||
| #include <vector> | ||||
| #include "common/assert.h" | ||||
| #include "common/logging/log.h" | ||||
| #include "common/thread.h" | ||||
| #include "common/threadsafe_queue.h" | ||||
| #include "core/core_timing.h" | ||||
| 
 | ||||
| namespace CoreTiming { | ||||
| 
 | ||||
| static s64 global_timer; | ||||
| static s64 slice_length; | ||||
| static s64 downcount; | ||||
| 
 | ||||
| struct EventType { | ||||
|     TimedCallback callback; | ||||
|     const std::string* name; | ||||
| }; | ||||
| 
 | ||||
| struct Event { | ||||
|     s64 time; | ||||
|     u64 fifo_order; | ||||
|     u64 userdata; | ||||
|     const EventType* type; | ||||
| }; | ||||
| namespace Core { | ||||
| 
 | ||||
| // Sort by time, unless the times are the same, in which case sort by the order added to the queue
 | ||||
| static bool operator>(const Event& left, const Event& right) { | ||||
|     return std::tie(left.time, left.fifo_order) > std::tie(right.time, right.fifo_order); | ||||
| bool Timing::Event::operator>(const Event& right) const { | ||||
|     return std::tie(time, fifo_order) > std::tie(right.time, right.fifo_order); | ||||
| } | ||||
| 
 | ||||
| static bool operator<(const Event& left, const Event& right) { | ||||
|     return std::tie(left.time, left.fifo_order) < std::tie(right.time, right.fifo_order); | ||||
| bool Timing::Event::operator<(const Event& right) const { | ||||
|     return std::tie(time, fifo_order) < std::tie(right.time, right.fifo_order); | ||||
| } | ||||
| 
 | ||||
| // unordered_map stores each element separately as a linked list node so pointers to elements
 | ||||
| // remain stable regardless of rehashes/resizing.
 | ||||
| static std::unordered_map<std::string, EventType> event_types; | ||||
| 
 | ||||
| // The queue is a min-heap using std::make_heap/push_heap/pop_heap.
 | ||||
| // We don't use std::priority_queue because we need to be able to serialize, unserialize and
 | ||||
| // erase arbitrary events (RemoveEvent()) regardless of the queue order. These aren't accomodated
 | ||||
| // by the standard adaptor class.
 | ||||
| static std::vector<Event> event_queue; | ||||
| static u64 event_fifo_id; | ||||
| // the queue for storing the events from other threads threadsafe until they will be added
 | ||||
| // to the event_queue by the emu thread
 | ||||
| static Common::MPSCQueue<Event, false> ts_queue; | ||||
| 
 | ||||
| static constexpr int MAX_SLICE_LENGTH = 20000; | ||||
| 
 | ||||
| static s64 idled_cycles; | ||||
| 
 | ||||
| // Are we in a function that has been called from Advance()
 | ||||
| // If events are sheduled from a function that gets called from Advance(),
 | ||||
| // don't change slice_length and downcount.
 | ||||
| static bool is_global_timer_sane; | ||||
| 
 | ||||
| static EventType* ev_lost = nullptr; | ||||
| 
 | ||||
| static void EmptyTimedCallback(u64 userdata, s64 cyclesLate) {} | ||||
| 
 | ||||
| EventType* RegisterEvent(const std::string& name, TimedCallback callback) { | ||||
| TimingEventType* Timing::RegisterEvent(const std::string& name, TimedCallback callback) { | ||||
|     // check for existing type with same name.
 | ||||
|     // we want event type names to remain unique so that we can use them for serialization.
 | ||||
|     ASSERT_MSG(event_types.find(name) == event_types.end(), | ||||
|  | @ -78,42 +28,17 @@ EventType* RegisterEvent(const std::string& name, TimedCallback callback) { | |||
|                "during Init to avoid breaking save states.", | ||||
|                name); | ||||
| 
 | ||||
|     auto info = event_types.emplace(name, EventType{callback, nullptr}); | ||||
|     EventType* event_type = &info.first->second; | ||||
|     auto info = event_types.emplace(name, TimingEventType{callback, nullptr}); | ||||
|     TimingEventType* event_type = &info.first->second; | ||||
|     event_type->name = &info.first->first; | ||||
|     return event_type; | ||||
| } | ||||
| 
 | ||||
| void UnregisterAllEvents() { | ||||
|     ASSERT_MSG(event_queue.empty(), "Cannot unregister events with events pending"); | ||||
|     event_types.clear(); | ||||
| } | ||||
| 
 | ||||
| void Init() { | ||||
|     downcount = MAX_SLICE_LENGTH; | ||||
|     slice_length = MAX_SLICE_LENGTH; | ||||
|     global_timer = 0; | ||||
|     idled_cycles = 0; | ||||
| 
 | ||||
|     // The time between CoreTiming being intialized and the first call to Advance() is considered
 | ||||
|     // the slice boundary between slice -1 and slice 0. Dispatcher loops must call Advance() before
 | ||||
|     // executing the first cycle of each slice to prepare the slice length and downcount for
 | ||||
|     // that slice.
 | ||||
|     is_global_timer_sane = true; | ||||
| 
 | ||||
|     event_fifo_id = 0; | ||||
|     ev_lost = RegisterEvent("_lost_event", &EmptyTimedCallback); | ||||
| } | ||||
| 
 | ||||
| void Shutdown() { | ||||
| Timing::~Timing() { | ||||
|     MoveEvents(); | ||||
|     ClearPendingEvents(); | ||||
|     UnregisterAllEvents(); | ||||
| } | ||||
| 
 | ||||
| // This should only be called from the CPU thread. If you are calling
 | ||||
| // it from any other thread, you are doing something evil
 | ||||
| u64 GetTicks() { | ||||
| u64 Timing::GetTicks() const { | ||||
|     u64 ticks = static_cast<u64>(global_timer); | ||||
|     if (!is_global_timer_sane) { | ||||
|         ticks += slice_length - downcount; | ||||
|  | @ -121,19 +46,16 @@ u64 GetTicks() { | |||
|     return ticks; | ||||
| } | ||||
| 
 | ||||
| void AddTicks(u64 ticks) { | ||||
| void Timing::AddTicks(u64 ticks) { | ||||
|     downcount -= ticks; | ||||
| } | ||||
| 
 | ||||
| u64 GetIdleTicks() { | ||||
| u64 Timing::GetIdleTicks() const { | ||||
|     return static_cast<u64>(idled_cycles); | ||||
| } | ||||
| 
 | ||||
| void ClearPendingEvents() { | ||||
|     event_queue.clear(); | ||||
| } | ||||
| 
 | ||||
| void ScheduleEvent(s64 cycles_into_future, const EventType* event_type, u64 userdata) { | ||||
| void Timing::ScheduleEvent(s64 cycles_into_future, const TimingEventType* event_type, | ||||
|                            u64 userdata) { | ||||
|     ASSERT(event_type != nullptr); | ||||
|     s64 timeout = GetTicks() + cycles_into_future; | ||||
| 
 | ||||
|  | @ -145,11 +67,12 @@ void ScheduleEvent(s64 cycles_into_future, const EventType* event_type, u64 user | |||
|     std::push_heap(event_queue.begin(), event_queue.end(), std::greater<>()); | ||||
| } | ||||
| 
 | ||||
| void ScheduleEventThreadsafe(s64 cycles_into_future, const EventType* event_type, u64 userdata) { | ||||
| void Timing::ScheduleEventThreadsafe(s64 cycles_into_future, const TimingEventType* event_type, | ||||
|                                      u64 userdata) { | ||||
|     ts_queue.Push(Event{global_timer + cycles_into_future, 0, userdata, event_type}); | ||||
| } | ||||
| 
 | ||||
| void UnscheduleEvent(const EventType* event_type, u64 userdata) { | ||||
| void Timing::UnscheduleEvent(const TimingEventType* event_type, u64 userdata) { | ||||
|     auto itr = std::remove_if(event_queue.begin(), event_queue.end(), [&](const Event& e) { | ||||
|         return e.type == event_type && e.userdata == userdata; | ||||
|     }); | ||||
|  | @ -161,7 +84,7 @@ void UnscheduleEvent(const EventType* event_type, u64 userdata) { | |||
|     } | ||||
| } | ||||
| 
 | ||||
| void RemoveEvent(const EventType* event_type) { | ||||
| void Timing::RemoveEvent(const TimingEventType* event_type) { | ||||
|     auto itr = std::remove_if(event_queue.begin(), event_queue.end(), | ||||
|                               [&](const Event& e) { return e.type == event_type; }); | ||||
| 
 | ||||
|  | @ -172,12 +95,12 @@ void RemoveEvent(const EventType* event_type) { | |||
|     } | ||||
| } | ||||
| 
 | ||||
| void RemoveNormalAndThreadsafeEvent(const EventType* event_type) { | ||||
| void Timing::RemoveNormalAndThreadsafeEvent(const TimingEventType* event_type) { | ||||
|     MoveEvents(); | ||||
|     RemoveEvent(event_type); | ||||
| } | ||||
| 
 | ||||
| void ForceExceptionCheck(s64 cycles) { | ||||
| void Timing::ForceExceptionCheck(s64 cycles) { | ||||
|     cycles = std::max<s64>(0, cycles); | ||||
|     if (downcount > cycles) { | ||||
|         slice_length -= downcount - cycles; | ||||
|  | @ -185,7 +108,7 @@ void ForceExceptionCheck(s64 cycles) { | |||
|     } | ||||
| } | ||||
| 
 | ||||
| void MoveEvents() { | ||||
| void Timing::MoveEvents() { | ||||
|     for (Event ev; ts_queue.Pop(ev);) { | ||||
|         ev.fifo_order = event_fifo_id++; | ||||
|         event_queue.emplace_back(std::move(ev)); | ||||
|  | @ -193,7 +116,7 @@ void MoveEvents() { | |||
|     } | ||||
| } | ||||
| 
 | ||||
| void Advance() { | ||||
| void Timing::Advance() { | ||||
|     MoveEvents(); | ||||
| 
 | ||||
|     s64 cycles_executed = slice_length - downcount; | ||||
|  | @ -220,17 +143,17 @@ void Advance() { | |||
|     downcount = slice_length; | ||||
| } | ||||
| 
 | ||||
| void Idle() { | ||||
| void Timing::Idle() { | ||||
|     idled_cycles += downcount; | ||||
|     downcount = 0; | ||||
| } | ||||
| 
 | ||||
| std::chrono::microseconds GetGlobalTimeUs() { | ||||
| std::chrono::microseconds Timing::GetGlobalTimeUs() const { | ||||
|     return std::chrono::microseconds{GetTicks() * 1000000 / BASE_CLOCK_RATE_ARM11}; | ||||
| } | ||||
| 
 | ||||
| s64 GetDowncount() { | ||||
| s64 Timing::GetDowncount() const { | ||||
|     return downcount; | ||||
| } | ||||
| 
 | ||||
| } // namespace CoreTiming
 | ||||
| } // namespace Core
 | ||||
|  |  | |||
|  | @ -21,8 +21,11 @@ | |||
| #include <functional> | ||||
| #include <limits> | ||||
| #include <string> | ||||
| #include <unordered_map> | ||||
| #include <vector> | ||||
| #include "common/common_types.h" | ||||
| #include "common/logging/log.h" | ||||
| #include "common/threadsafe_queue.h" | ||||
| 
 | ||||
| // The timing we get from the assembly is 268,111,855.956 Hz
 | ||||
| // It is possible that this number isn't just an integer because the compiler could have
 | ||||
|  | @ -120,73 +123,112 @@ inline u64 cyclesToMs(s64 cycles) { | |||
|     return cycles * 1000 / BASE_CLOCK_RATE_ARM11; | ||||
| } | ||||
| 
 | ||||
| namespace CoreTiming { | ||||
| 
 | ||||
| struct EventType; | ||||
| namespace Core { | ||||
| 
 | ||||
| using TimedCallback = std::function<void(u64 userdata, int cycles_late)>; | ||||
| 
 | ||||
| /**
 | ||||
|  * CoreTiming begins at the boundary of timing slice -1. An initial call to Advance() is | ||||
|  * required to end slice -1 and start slice 0 before the first cycle of code is executed. | ||||
|  */ | ||||
| void Init(); | ||||
| void Shutdown(); | ||||
| struct TimingEventType { | ||||
|     TimedCallback callback; | ||||
|     const std::string* name; | ||||
| }; | ||||
| 
 | ||||
| /**
 | ||||
|  * This should only be called from the emu thread, if you are calling it any other thread, you are | ||||
|  * doing something evil | ||||
|  */ | ||||
| u64 GetTicks(); | ||||
| u64 GetIdleTicks(); | ||||
| void AddTicks(u64 ticks); | ||||
| class Timing { | ||||
| public: | ||||
|     ~Timing(); | ||||
| 
 | ||||
| /**
 | ||||
|  * Returns the event_type identifier. if name is not unique, it will assert. | ||||
|  */ | ||||
| EventType* RegisterEvent(const std::string& name, TimedCallback callback); | ||||
| void UnregisterAllEvents(); | ||||
|     /**
 | ||||
|      * This should only be called from the emu thread, if you are calling it any other thread, you | ||||
|      * are doing something evil | ||||
|      */ | ||||
|     u64 GetTicks() const; | ||||
|     u64 GetIdleTicks() const; | ||||
|     void AddTicks(u64 ticks); | ||||
| 
 | ||||
| /**
 | ||||
|  * After the first Advance, the slice lengths and the downcount will be reduced whenever an event | ||||
|  * is scheduled earlier than the current values. | ||||
|  * Scheduling from a callback will not update the downcount until the Advance() completes. | ||||
|  */ | ||||
| void ScheduleEvent(s64 cycles_into_future, const EventType* event_type, u64 userdata = 0); | ||||
|     /**
 | ||||
|      * Returns the event_type identifier. if name is not unique, it will assert. | ||||
|      */ | ||||
|     TimingEventType* RegisterEvent(const std::string& name, TimedCallback callback); | ||||
| 
 | ||||
| /**
 | ||||
|  * This is to be called when outside of hle threads, such as the graphics thread, wants to | ||||
|  * schedule things to be executed on the main thread. | ||||
|  * Not that this doesn't change slice_length and thus events scheduled by this might be called | ||||
|  * with a delay of up to MAX_SLICE_LENGTH | ||||
|  */ | ||||
| void ScheduleEventThreadsafe(s64 cycles_into_future, const EventType* event_type, u64 userdata); | ||||
|     /**
 | ||||
|      * After the first Advance, the slice lengths and the downcount will be reduced whenever an | ||||
|      * event is scheduled earlier than the current values. Scheduling from a callback will not | ||||
|      * update the downcount until the Advance() completes. | ||||
|      */ | ||||
|     void ScheduleEvent(s64 cycles_into_future, const TimingEventType* event_type, u64 userdata = 0); | ||||
| 
 | ||||
| void UnscheduleEvent(const EventType* event_type, u64 userdata); | ||||
|     /**
 | ||||
|      * This is to be called when outside of hle threads, such as the graphics thread, wants to | ||||
|      * schedule things to be executed on the main thread. | ||||
|      * Not that this doesn't change slice_length and thus events scheduled by this might be called | ||||
|      * with a delay of up to MAX_SLICE_LENGTH | ||||
|      */ | ||||
|     void ScheduleEventThreadsafe(s64 cycles_into_future, const TimingEventType* event_type, | ||||
|                                  u64 userdata); | ||||
| 
 | ||||
| /// We only permit one event of each type in the queue at a time.
 | ||||
| void RemoveEvent(const EventType* event_type); | ||||
| void RemoveNormalAndThreadsafeEvent(const EventType* event_type); | ||||
|     void UnscheduleEvent(const TimingEventType* event_type, u64 userdata); | ||||
| 
 | ||||
| /** Advance must be called at the beginning of dispatcher loops, not the end. Advance() ends
 | ||||
|  * the previous timing slice and begins the next one, you must Advance from the previous | ||||
|  * slice to the current one before executing any cycles. CoreTiming starts in slice -1 so an | ||||
|  * Advance() is required to initialize the slice length before the first cycle of emulated | ||||
|  * instructions is executed. | ||||
|  */ | ||||
| void Advance(); | ||||
| void MoveEvents(); | ||||
|     /// We only permit one event of each type in the queue at a time.
 | ||||
|     void RemoveEvent(const TimingEventType* event_type); | ||||
|     void RemoveNormalAndThreadsafeEvent(const TimingEventType* event_type); | ||||
| 
 | ||||
| /// Pretend that the main CPU has executed enough cycles to reach the next event.
 | ||||
| void Idle(); | ||||
|     /** Advance must be called at the beginning of dispatcher loops, not the end. Advance() ends
 | ||||
|      * the previous timing slice and begins the next one, you must Advance from the previous | ||||
|      * slice to the current one before executing any cycles. CoreTiming starts in slice -1 so an | ||||
|      * Advance() is required to initialize the slice length before the first cycle of emulated | ||||
|      * instructions is executed. | ||||
|      */ | ||||
|     void Advance(); | ||||
|     void MoveEvents(); | ||||
| 
 | ||||
| /// Clear all pending events. This should ONLY be done on exit.
 | ||||
| void ClearPendingEvents(); | ||||
|     /// Pretend that the main CPU has executed enough cycles to reach the next event.
 | ||||
|     void Idle(); | ||||
| 
 | ||||
| void ForceExceptionCheck(s64 cycles); | ||||
|     void ForceExceptionCheck(s64 cycles); | ||||
| 
 | ||||
| std::chrono::microseconds GetGlobalTimeUs(); | ||||
|     std::chrono::microseconds GetGlobalTimeUs() const; | ||||
| 
 | ||||
| s64 GetDowncount(); | ||||
|     s64 GetDowncount() const; | ||||
| 
 | ||||
| } // namespace CoreTiming
 | ||||
| private: | ||||
|     struct Event { | ||||
|         s64 time; | ||||
|         u64 fifo_order; | ||||
|         u64 userdata; | ||||
|         const TimingEventType* type; | ||||
| 
 | ||||
|         bool operator>(const Event& right) const; | ||||
|         bool operator<(const Event& right) const; | ||||
|     }; | ||||
| 
 | ||||
|     static constexpr int MAX_SLICE_LENGTH = 20000; | ||||
| 
 | ||||
|     s64 global_timer = 0; | ||||
|     s64 slice_length = MAX_SLICE_LENGTH; | ||||
|     s64 downcount = MAX_SLICE_LENGTH; | ||||
| 
 | ||||
|     // unordered_map stores each element separately as a linked list node so pointers to
 | ||||
|     // elements remain stable regardless of rehashes/resizing.
 | ||||
|     std::unordered_map<std::string, TimingEventType> event_types; | ||||
| 
 | ||||
|     // The queue is a min-heap using std::make_heap/push_heap/pop_heap.
 | ||||
|     // We don't use std::priority_queue because we need to be able to serialize, unserialize and
 | ||||
|     // erase arbitrary events (RemoveEvent()) regardless of the queue order. These aren't
 | ||||
|     // accomodated by the standard adaptor class.
 | ||||
|     std::vector<Event> event_queue; | ||||
|     u64 event_fifo_id = 0; | ||||
|     // the queue for storing the events from other threads threadsafe until they will be added
 | ||||
|     // to the event_queue by the emu thread
 | ||||
|     Common::MPSCQueue<Event, false> ts_queue; | ||||
|     s64 idled_cycles = 0; | ||||
| 
 | ||||
|     // Are we in a function that has been called from Advance()
 | ||||
|     // If events are sheduled from a function that gets called from Advance(),
 | ||||
|     // don't change slice_length and downcount.
 | ||||
|     // The time between CoreTiming being intialized and the first call to Advance() is considered
 | ||||
|     // the slice boundary between slice -1 and slice 0. Dispatcher loops must call Advance() before
 | ||||
|     // executing the first cycle of each slice to prepare the slice length and downcount for
 | ||||
|     // that slice.
 | ||||
|     bool is_global_timer_sane = true; | ||||
| }; | ||||
| 
 | ||||
| } // namespace Core
 | ||||
|  |  | |||
|  | @ -3,6 +3,7 @@ | |||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| #include <utility> | ||||
| #include "core/core.h" | ||||
| #include "core/file_sys/archive_savedata.h" | ||||
| #include "core/hle/kernel/process.h" | ||||
| 
 | ||||
|  | @ -16,16 +17,19 @@ ArchiveFactory_SaveData::ArchiveFactory_SaveData( | |||
|     : sd_savedata_source(std::move(sd_savedata)) {} | ||||
| 
 | ||||
| ResultVal<std::unique_ptr<ArchiveBackend>> ArchiveFactory_SaveData::Open(const Path& path) { | ||||
|     return sd_savedata_source->Open(Kernel::g_current_process->codeset->program_id); | ||||
|     return sd_savedata_source->Open( | ||||
|         Core::System::GetInstance().Kernel().GetCurrentProcess()->codeset->program_id); | ||||
| } | ||||
| 
 | ||||
| ResultCode ArchiveFactory_SaveData::Format(const Path& path, | ||||
|                                            const FileSys::ArchiveFormatInfo& format_info) { | ||||
|     return sd_savedata_source->Format(Kernel::g_current_process->codeset->program_id, format_info); | ||||
|     return sd_savedata_source->Format( | ||||
|         Core::System::GetInstance().Kernel().GetCurrentProcess()->codeset->program_id, format_info); | ||||
| } | ||||
| 
 | ||||
| ResultVal<ArchiveFormatInfo> ArchiveFactory_SaveData::GetFormatInfo(const Path& path) const { | ||||
|     return sd_savedata_source->GetFormatInfo(Kernel::g_current_process->codeset->program_id); | ||||
|     return sd_savedata_source->GetFormatInfo( | ||||
|         Core::System::GetInstance().Kernel().GetCurrentProcess()->codeset->program_id); | ||||
| } | ||||
| 
 | ||||
| } // namespace FileSys
 | ||||
|  |  | |||
|  | @ -7,6 +7,7 @@ | |||
| #include "common/common_types.h" | ||||
| #include "common/logging/log.h" | ||||
| #include "common/swap.h" | ||||
| #include "core/core.h" | ||||
| #include "core/file_sys/archive_selfncch.h" | ||||
| #include "core/file_sys/errors.h" | ||||
| #include "core/file_sys/ivfc_archive.h" | ||||
|  | @ -279,7 +280,7 @@ void ArchiveFactory_SelfNCCH::Register(Loader::AppLoader& app_loader) { | |||
| 
 | ||||
| ResultVal<std::unique_ptr<ArchiveBackend>> ArchiveFactory_SelfNCCH::Open(const Path& path) { | ||||
|     auto archive = std::make_unique<SelfNCCHArchive>( | ||||
|         ncch_data[Kernel::g_current_process->codeset->program_id]); | ||||
|         ncch_data[Core::System::GetInstance().Kernel().GetCurrentProcess()->codeset->program_id]); | ||||
|     return MakeResult<std::unique_ptr<ArchiveBackend>>(std::move(archive)); | ||||
| } | ||||
| 
 | ||||
|  |  | |||
							
								
								
									
										22
									
								
								src/core/file_sys/delay_generator.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								src/core/file_sys/delay_generator.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,22 @@ | |||
| // Copyright 2018 Citra Emulator Project
 | ||||
| // Licensed under GPLv2 or any later version
 | ||||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| #include <algorithm> | ||||
| #include "core/file_sys/delay_generator.h" | ||||
| 
 | ||||
| namespace FileSys { | ||||
| 
 | ||||
| DelayGenerator::~DelayGenerator() = default; | ||||
| 
 | ||||
| u64 DefaultDelayGenerator::GetReadDelayNs(std::size_t length) { | ||||
|     // This is the delay measured for a romfs read.
 | ||||
|     // For now we will take that as a default
 | ||||
|     static constexpr u64 slope(94); | ||||
|     static constexpr u64 offset(582778); | ||||
|     static constexpr u64 minimum(663124); | ||||
|     u64 IPCDelayNanoseconds = std::max<u64>(static_cast<u64>(length) * slope + offset, minimum); | ||||
|     return IPCDelayNanoseconds; | ||||
| } | ||||
| 
 | ||||
| } // namespace FileSys
 | ||||
|  | @ -4,10 +4,14 @@ | |||
| 
 | ||||
| #pragma once | ||||
| 
 | ||||
| #include <cstddef> | ||||
| #include "common/common_types.h" | ||||
| 
 | ||||
| namespace FileSys { | ||||
| 
 | ||||
| class DelayGenerator { | ||||
| public: | ||||
|     virtual ~DelayGenerator(); | ||||
|     virtual u64 GetReadDelayNs(std::size_t length) = 0; | ||||
| 
 | ||||
|     // TODO (B3N30): Add getter for all other file/directory io operations
 | ||||
|  | @ -15,15 +19,7 @@ public: | |||
| 
 | ||||
| class DefaultDelayGenerator : public DelayGenerator { | ||||
| public: | ||||
|     u64 GetReadDelayNs(std::size_t length) override { | ||||
|         // This is the delay measured for a romfs read.
 | ||||
|         // For now we will take that as a default
 | ||||
|         static constexpr u64 slope(94); | ||||
|         static constexpr u64 offset(582778); | ||||
|         static constexpr u64 minimum(663124); | ||||
|         u64 IPCDelayNanoseconds = std::max<u64>(static_cast<u64>(length) * slope + offset, minimum); | ||||
|         return IPCDelayNanoseconds; | ||||
|     } | ||||
|     u64 GetReadDelayNs(std::size_t length) override; | ||||
| }; | ||||
| 
 | ||||
| } // namespace FileSys
 | ||||
|  |  | |||
|  | @ -226,7 +226,9 @@ Loader::ResultStatus NCCHContainer::Load() { | |||
|                         std::memcpy(input.data(), key_y_primary.data(), key_y_primary.size()); | ||||
|                         std::memcpy(input.data() + key_y_primary.size(), seed.data(), seed.size()); | ||||
|                         CryptoPP::SHA256 sha; | ||||
|                         sha.CalculateDigest(key_y_secondary.data(), input.data(), input.size()); | ||||
|                         std::array<u8, CryptoPP::SHA256::DIGESTSIZE> hash; | ||||
|                         sha.CalculateDigest(hash.data(), input.data(), input.size()); | ||||
|                         std::memcpy(key_y_secondary.data(), hash.data(), key_y_secondary.size()); | ||||
|                     } | ||||
|                 } | ||||
| 
 | ||||
|  |  | |||
|  | @ -34,7 +34,7 @@ bool SeedDB::Load() { | |||
|         LOG_ERROR(Service_FS, "Failed to read seed database count fully"); | ||||
|         return false; | ||||
|     } | ||||
|     if (!file.Seek(file.Tell() + SEEDDB_PADDING_BYTES, SEEK_SET)) { | ||||
|     if (!file.Seek(SEEDDB_PADDING_BYTES, SEEK_CUR)) { | ||||
|         LOG_ERROR(Service_FS, "Failed to skip seed database padding"); | ||||
|         return false; | ||||
|     } | ||||
|  |  | |||
|  | @ -160,7 +160,7 @@ BreakpointMap breakpoints_write; | |||
| } // Anonymous namespace
 | ||||
| 
 | ||||
| static Kernel::Thread* FindThreadById(int id) { | ||||
|     const auto& threads = Kernel::GetThreadList(); | ||||
|     const auto& threads = Core::System::GetInstance().Kernel().GetThreadManager().GetThreadList(); | ||||
|     for (auto& thread : threads) { | ||||
|         if (thread->GetThreadId() == static_cast<u32>(id)) { | ||||
|             return thread.get(); | ||||
|  | @ -535,7 +535,8 @@ static void HandleQuery() { | |||
|         SendReply(target_xml); | ||||
|     } else if (strncmp(query, "fThreadInfo", strlen("fThreadInfo")) == 0) { | ||||
|         std::string val = "m"; | ||||
|         const auto& threads = Kernel::GetThreadList(); | ||||
|         const auto& threads = | ||||
|             Core::System::GetInstance().Kernel().GetThreadManager().GetThreadList(); | ||||
|         for (const auto& thread : threads) { | ||||
|             val += fmt::format("{:x},", thread->GetThreadId()); | ||||
|         } | ||||
|  | @ -547,7 +548,8 @@ static void HandleQuery() { | |||
|         std::string buffer; | ||||
|         buffer += "l<?xml version=\"1.0\"?>"; | ||||
|         buffer += "<threads>"; | ||||
|         const auto& threads = Kernel::GetThreadList(); | ||||
|         const auto& threads = | ||||
|             Core::System::GetInstance().Kernel().GetThreadManager().GetThreadList(); | ||||
|         for (const auto& thread : threads) { | ||||
|             buffer += fmt::format(R"*(<thread id="{:x}" name="Thread {:x}"></thread>)*", | ||||
|                                   thread->GetThreadId(), thread->GetThreadId()); | ||||
|  |  | |||
|  | @ -8,6 +8,7 @@ | |||
| #include <unordered_map> | ||||
| #include "common/assert.h" | ||||
| #include "common/common_types.h" | ||||
| #include "core/core.h" | ||||
| #include "core/core_timing.h" | ||||
| #include "core/hle/applets/applet.h" | ||||
| #include "core/hle/applets/erreula.h" | ||||
|  | @ -38,7 +39,7 @@ namespace Applets { | |||
| 
 | ||||
| static std::unordered_map<Service::APT::AppletId, std::shared_ptr<Applet>> applets; | ||||
| /// The CoreTiming event identifier for the Applet update callback.
 | ||||
| static CoreTiming::EventType* applet_update_event = nullptr; | ||||
| static Core::TimingEventType* applet_update_event = nullptr; | ||||
| /// The interval at which the Applet update callback will be called, 16.6ms
 | ||||
| static const u64 applet_update_interval_us = 16666; | ||||
| 
 | ||||
|  | @ -88,8 +89,8 @@ static void AppletUpdateEvent(u64 applet_id, s64 cycles_late) { | |||
| 
 | ||||
|     // If the applet is still running after the last update, reschedule the event
 | ||||
|     if (applet->IsRunning()) { | ||||
|         CoreTiming::ScheduleEvent(usToCycles(applet_update_interval_us) - cycles_late, | ||||
|                                   applet_update_event, applet_id); | ||||
|         Core::System::GetInstance().CoreTiming().ScheduleEvent( | ||||
|             usToCycles(applet_update_interval_us) - cycles_late, applet_update_event, applet_id); | ||||
|     } else { | ||||
|         // Otherwise the applet has terminated, in which case we should clean it up
 | ||||
|         applets[id] = nullptr; | ||||
|  | @ -101,8 +102,8 @@ ResultCode Applet::Start(const Service::APT::AppletStartupParameter& parameter) | |||
|     if (result.IsError()) | ||||
|         return result; | ||||
|     // Schedule the update event
 | ||||
|     CoreTiming::ScheduleEvent(usToCycles(applet_update_interval_us), applet_update_event, | ||||
|                               static_cast<u64>(id)); | ||||
|     Core::System::GetInstance().CoreTiming().ScheduleEvent( | ||||
|         usToCycles(applet_update_interval_us), applet_update_event, static_cast<u64>(id)); | ||||
|     return result; | ||||
| } | ||||
| 
 | ||||
|  | @ -128,11 +129,12 @@ bool IsLibraryAppletRunning() { | |||
| 
 | ||||
| void Init() { | ||||
|     // Register the applet update callback
 | ||||
|     applet_update_event = CoreTiming::RegisterEvent("HLE Applet Update Event", AppletUpdateEvent); | ||||
|     applet_update_event = Core::System::GetInstance().CoreTiming().RegisterEvent( | ||||
|         "HLE Applet Update Event", AppletUpdateEvent); | ||||
| } | ||||
| 
 | ||||
| void Shutdown() { | ||||
|     CoreTiming::RemoveEvent(applet_update_event); | ||||
|     Core::System::GetInstance().CoreTiming().RemoveEvent(applet_update_event); | ||||
| } | ||||
| } // namespace Applets
 | ||||
| } // namespace HLE
 | ||||
|  |  | |||
|  | @ -3,6 +3,7 @@ | |||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| #include "common/string_util.h" | ||||
| #include "core/core.h" | ||||
| #include "core/hle/applets/erreula.h" | ||||
| #include "core/hle/service/apt/apt.h" | ||||
| 
 | ||||
|  | @ -27,11 +28,9 @@ ResultCode ErrEula::ReceiveParameter(const Service::APT::MessageParameter& param | |||
| 
 | ||||
|     // TODO: allocated memory never released
 | ||||
|     using Kernel::MemoryPermission; | ||||
|     // Allocate a heap block of the required size for this applet.
 | ||||
|     heap_memory = std::make_shared<std::vector<u8>>(capture_info.size); | ||||
|     // Create a SharedMemory that directly points to this heap block.
 | ||||
|     framebuffer_memory = Kernel::SharedMemory::CreateForApplet( | ||||
|         heap_memory, 0, capture_info.size, MemoryPermission::ReadWrite, MemoryPermission::ReadWrite, | ||||
|     framebuffer_memory = Core::System::GetInstance().Kernel().CreateSharedMemoryForApplet( | ||||
|         0, capture_info.size, MemoryPermission::ReadWrite, MemoryPermission::ReadWrite, | ||||
|         "ErrEula Memory"); | ||||
| 
 | ||||
|     // Send the response message with the newly created SharedMemory
 | ||||
|  |  | |||
|  | @ -7,6 +7,7 @@ | |||
| #include "common/assert.h" | ||||
| #include "common/logging/log.h" | ||||
| #include "common/string_util.h" | ||||
| #include "core/core.h" | ||||
| #include "core/hle/applets/mii_selector.h" | ||||
| #include "core/hle/kernel/kernel.h" | ||||
| #include "core/hle/kernel/shared_memory.h" | ||||
|  | @ -34,11 +35,9 @@ ResultCode MiiSelector::ReceiveParameter(const Service::APT::MessageParameter& p | |||
|     memcpy(&capture_info, parameter.buffer.data(), sizeof(capture_info)); | ||||
| 
 | ||||
|     using Kernel::MemoryPermission; | ||||
|     // Allocate a heap block of the required size for this applet.
 | ||||
|     heap_memory = std::make_shared<std::vector<u8>>(capture_info.size); | ||||
|     // Create a SharedMemory that directly points to this heap block.
 | ||||
|     framebuffer_memory = Kernel::SharedMemory::CreateForApplet( | ||||
|         heap_memory, 0, capture_info.size, MemoryPermission::ReadWrite, MemoryPermission::ReadWrite, | ||||
|     framebuffer_memory = Core::System::GetInstance().Kernel().CreateSharedMemoryForApplet( | ||||
|         0, capture_info.size, MemoryPermission::ReadWrite, MemoryPermission::ReadWrite, | ||||
|         "MiiSelector Memory"); | ||||
| 
 | ||||
|     // Send the response message with the newly created SharedMemory
 | ||||
|  |  | |||
|  | @ -3,6 +3,7 @@ | |||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| #include "common/string_util.h" | ||||
| #include "core/core.h" | ||||
| #include "core/hle/applets/mint.h" | ||||
| #include "core/hle/service/apt/apt.h" | ||||
| 
 | ||||
|  | @ -27,11 +28,9 @@ ResultCode Mint::ReceiveParameter(const Service::APT::MessageParameter& paramete | |||
| 
 | ||||
|     // TODO: allocated memory never released
 | ||||
|     using Kernel::MemoryPermission; | ||||
|     // Allocate a heap block of the required size for this applet.
 | ||||
|     heap_memory = std::make_shared<std::vector<u8>>(capture_info.size); | ||||
|     // Create a SharedMemory that directly points to this heap block.
 | ||||
|     framebuffer_memory = Kernel::SharedMemory::CreateForApplet( | ||||
|         heap_memory, 0, capture_info.size, MemoryPermission::ReadWrite, MemoryPermission::ReadWrite, | ||||
|     framebuffer_memory = Core::System::GetInstance().Kernel().CreateSharedMemoryForApplet( | ||||
|         0, capture_info.size, MemoryPermission::ReadWrite, MemoryPermission::ReadWrite, | ||||
|         "Mint Memory"); | ||||
| 
 | ||||
|     // Send the response message with the newly created SharedMemory
 | ||||
|  |  | |||
|  | @ -8,6 +8,7 @@ | |||
| #include "common/assert.h" | ||||
| #include "common/logging/log.h" | ||||
| #include "common/string_util.h" | ||||
| #include "core/core.h" | ||||
| #include "core/hle/applets/swkbd.h" | ||||
| #include "core/hle/kernel/kernel.h" | ||||
| #include "core/hle/kernel/shared_memory.h" | ||||
|  | @ -38,11 +39,9 @@ ResultCode SoftwareKeyboard::ReceiveParameter(Service::APT::MessageParameter con | |||
|     memcpy(&capture_info, parameter.buffer.data(), sizeof(capture_info)); | ||||
| 
 | ||||
|     using Kernel::MemoryPermission; | ||||
|     // Allocate a heap block of the required size for this applet.
 | ||||
|     heap_memory = std::make_shared<std::vector<u8>>(capture_info.size); | ||||
|     // Create a SharedMemory that directly points to this heap block.
 | ||||
|     framebuffer_memory = Kernel::SharedMemory::CreateForApplet( | ||||
|         heap_memory, 0, capture_info.size, MemoryPermission::ReadWrite, MemoryPermission::ReadWrite, | ||||
|     framebuffer_memory = Core::System::GetInstance().Kernel().CreateSharedMemoryForApplet( | ||||
|         0, capture_info.size, MemoryPermission::ReadWrite, MemoryPermission::ReadWrite, | ||||
|         "SoftwareKeyboard Memory"); | ||||
| 
 | ||||
|     // Send the response message with the newly created SharedMemory
 | ||||
|  |  | |||
|  | @ -9,27 +9,6 @@ | |||
| #include "core/hle/kernel/thread.h" | ||||
| #include "core/memory.h" | ||||
| 
 | ||||
| namespace Kernel { | ||||
| 
 | ||||
| /// Offset into command buffer of header
 | ||||
| static const int kCommandHeaderOffset = 0x80; | ||||
| 
 | ||||
| /**
 | ||||
|  * 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 | ||||
|  * @param offset Optional offset into command buffer (in bytes) | ||||
|  * @return Pointer to command buffer | ||||
|  */ | ||||
| inline u32* GetCommandBuffer(const int offset = 0) { | ||||
|     return (u32*)Memory::GetPointer(GetCurrentThread()->GetTLSAddress() + kCommandHeaderOffset + | ||||
|                                     offset); | ||||
| } | ||||
| 
 | ||||
| } // namespace Kernel
 | ||||
| 
 | ||||
| namespace IPC { | ||||
| 
 | ||||
| /// Size of the command buffer area, in 32-bit words.
 | ||||
|  |  | |||
|  | @ -7,6 +7,7 @@ | |||
| #include "common/logging/log.h" | ||||
| #include "core/hle/kernel/address_arbiter.h" | ||||
| #include "core/hle/kernel/errors.h" | ||||
| #include "core/hle/kernel/kernel.h" | ||||
| #include "core/hle/kernel/thread.h" | ||||
| #include "core/memory.h" | ||||
| 
 | ||||
|  | @ -64,11 +65,11 @@ SharedPtr<Thread> AddressArbiter::ResumeHighestPriorityThread(VAddr address) { | |||
|     return thread; | ||||
| } | ||||
| 
 | ||||
| AddressArbiter::AddressArbiter() {} | ||||
| AddressArbiter::AddressArbiter(KernelSystem& kernel) : Object(kernel) {} | ||||
| AddressArbiter::~AddressArbiter() {} | ||||
| 
 | ||||
| SharedPtr<AddressArbiter> AddressArbiter::Create(std::string name) { | ||||
|     SharedPtr<AddressArbiter> address_arbiter(new AddressArbiter); | ||||
| SharedPtr<AddressArbiter> KernelSystem::CreateAddressArbiter(std::string name) { | ||||
|     SharedPtr<AddressArbiter> address_arbiter(new AddressArbiter(*this)); | ||||
| 
 | ||||
|     address_arbiter->name = std::move(name); | ||||
| 
 | ||||
|  |  | |||
|  | @ -31,14 +31,6 @@ enum class ArbitrationType : u32 { | |||
| 
 | ||||
| class AddressArbiter final : public Object { | ||||
| public: | ||||
|     /**
 | ||||
|      * Creates an address arbiter. | ||||
|      * | ||||
|      * @param name Optional name used for debugging. | ||||
|      * @returns The created AddressArbiter. | ||||
|      */ | ||||
|     static SharedPtr<AddressArbiter> Create(std::string name = "Unknown"); | ||||
| 
 | ||||
|     std::string GetTypeName() const override { | ||||
|         return "Arbiter"; | ||||
|     } | ||||
|  | @ -57,7 +49,7 @@ public: | |||
|                                 s32 value, u64 nanoseconds); | ||||
| 
 | ||||
| private: | ||||
|     AddressArbiter(); | ||||
|     explicit AddressArbiter(KernelSystem& kernel); | ||||
|     ~AddressArbiter() override; | ||||
| 
 | ||||
|     /// Puts the thread to wait on the specified arbitration address under this address arbiter.
 | ||||
|  | @ -72,6 +64,8 @@ private: | |||
| 
 | ||||
|     /// Threads waiting for the address arbiter to be signaled.
 | ||||
|     std::vector<SharedPtr<Thread>> waiting_threads; | ||||
| 
 | ||||
|     friend class KernelSystem; | ||||
| }; | ||||
| 
 | ||||
| } // namespace Kernel
 | ||||
|  |  | |||
|  | @ -13,7 +13,7 @@ | |||
| 
 | ||||
| namespace Kernel { | ||||
| 
 | ||||
| ClientPort::ClientPort() = default; | ||||
| ClientPort::ClientPort(KernelSystem& kernel) : kernel(kernel), Object(kernel) {} | ||||
| ClientPort::~ClientPort() = default; | ||||
| 
 | ||||
| ResultVal<SharedPtr<ClientSession>> ClientPort::Connect() { | ||||
|  | @ -26,7 +26,7 @@ ResultVal<SharedPtr<ClientSession>> ClientPort::Connect() { | |||
|     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(), this); | ||||
|     auto sessions = kernel.CreateSessionPair(server_port->GetName(), this); | ||||
| 
 | ||||
|     if (server_port->hle_handler) | ||||
|         server_port->hle_handler->ClientConnected(std::get<SharedPtr<ServerSession>>(sessions)); | ||||
|  |  | |||
|  | @ -48,13 +48,16 @@ public: | |||
|     void ConnectionClosed(); | ||||
| 
 | ||||
| private: | ||||
|     ClientPort(); | ||||
|     explicit ClientPort(KernelSystem& kernel); | ||||
|     ~ClientPort() override; | ||||
| 
 | ||||
|     KernelSystem& kernel; | ||||
|     SharedPtr<ServerPort> server_port; ///< ServerPort associated with this client port.
 | ||||
|     u32 max_sessions = 0;    ///< Maximum number of simultaneous sessions the port can have
 | ||||
|     u32 active_sessions = 0; ///< Number of currently open sessions to this port
 | ||||
|     std::string name;        ///< Name of client port (optional)
 | ||||
| 
 | ||||
|     friend class KernelSystem; | ||||
| }; | ||||
| 
 | ||||
| } // namespace Kernel
 | ||||
|  |  | |||
|  | @ -13,7 +13,7 @@ | |||
| 
 | ||||
| namespace Kernel { | ||||
| 
 | ||||
| ClientSession::ClientSession() = default; | ||||
| ClientSession::ClientSession(KernelSystem& kernel) : Object(kernel) {} | ||||
| ClientSession::~ClientSession() { | ||||
|     // This destructor will be called automatically when the last ClientSession handle is closed by
 | ||||
|     // the emulated application.
 | ||||
|  |  | |||
|  | @ -12,13 +12,12 @@ | |||
| 
 | ||||
| namespace Kernel { | ||||
| 
 | ||||
| class ServerSession; | ||||
| class Session; | ||||
| class Thread; | ||||
| 
 | ||||
| class ClientSession final : public Object { | ||||
| public: | ||||
|     friend class ServerSession; | ||||
|     friend class KernelSystem; | ||||
| 
 | ||||
|     std::string GetTypeName() const override { | ||||
|         return "ClientSession"; | ||||
|  | @ -46,7 +45,7 @@ public: | |||
|     std::shared_ptr<Session> parent; | ||||
| 
 | ||||
| private: | ||||
|     ClientSession(); | ||||
|     explicit ClientSession(KernelSystem& kernel); | ||||
|     ~ClientSession() override; | ||||
| }; | ||||
| 
 | ||||
|  |  | |||
|  | @ -3,15 +3,13 @@ | |||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| #include <cstring> | ||||
| #include "core/hle/config_mem.h" | ||||
| #include "core/hle/kernel/config_mem.h" | ||||
| 
 | ||||
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | ||||
| 
 | ||||
| namespace ConfigMem { | ||||
| 
 | ||||
| ConfigMemDef config_mem; | ||||
| 
 | ||||
| void Init() { | ||||
| Handler::Handler() { | ||||
|     std::memset(&config_mem, 0, sizeof(config_mem)); | ||||
| 
 | ||||
|     // Values extracted from firmware 11.2.0-35E
 | ||||
|  | @ -28,4 +26,8 @@ void Init() { | |||
|     config_mem.firm_ctr_sdk_ver = 0x0000F297; | ||||
| } | ||||
| 
 | ||||
| ConfigMemDef& Handler::GetConfigMem() { | ||||
|     return config_mem; | ||||
| } | ||||
| 
 | ||||
| } // namespace ConfigMem
 | ||||
|  | @ -49,8 +49,13 @@ struct ConfigMemDef { | |||
| static_assert(sizeof(ConfigMemDef) == Memory::CONFIG_MEMORY_SIZE, | ||||
|               "Config Memory structure size is wrong"); | ||||
| 
 | ||||
| extern ConfigMemDef config_mem; | ||||
| class Handler { | ||||
| public: | ||||
|     Handler(); | ||||
|     ConfigMemDef& GetConfigMem(); | ||||
| 
 | ||||
| void Init(); | ||||
| private: | ||||
|     ConfigMemDef config_mem; | ||||
| }; | ||||
| 
 | ||||
| } // namespace ConfigMem
 | ||||
|  | @ -67,6 +67,10 @@ constexpr ResultCode ERR_MISALIGNED_SIZE(ErrorDescription::MisalignedSize, Error | |||
| constexpr ResultCode ERR_OUT_OF_MEMORY(ErrorDescription::OutOfMemory, ErrorModule::Kernel, | ||||
|                                        ErrorSummary::OutOfResource, | ||||
|                                        ErrorLevel::Permanent); // 0xD86007F3
 | ||||
| /// Returned when out of heap or linear heap memory when allocating
 | ||||
| constexpr ResultCode ERR_OUT_OF_HEAP_MEMORY(ErrorDescription::OutOfMemory, ErrorModule::OS, | ||||
|                                             ErrorSummary::OutOfResource, | ||||
|                                             ErrorLevel::Status); // 0xC860180A
 | ||||
| constexpr ResultCode ERR_NOT_IMPLEMENTED(ErrorDescription::NotImplemented, ErrorModule::OS, | ||||
|                                          ErrorSummary::InvalidArgument, | ||||
|                                          ErrorLevel::Usage); // 0xE0E01BF4
 | ||||
|  |  | |||
|  | @ -7,16 +7,16 @@ | |||
| #include <vector> | ||||
| #include "common/assert.h" | ||||
| #include "core/hle/kernel/event.h" | ||||
| #include "core/hle/kernel/object.h" | ||||
| #include "core/hle/kernel/kernel.h" | ||||
| #include "core/hle/kernel/thread.h" | ||||
| 
 | ||||
| namespace Kernel { | ||||
| 
 | ||||
| Event::Event() {} | ||||
| Event::Event(KernelSystem& kernel) : WaitObject(kernel) {} | ||||
| Event::~Event() {} | ||||
| 
 | ||||
| SharedPtr<Event> Event::Create(ResetType reset_type, std::string name) { | ||||
|     SharedPtr<Event> evt(new Event); | ||||
| SharedPtr<Event> KernelSystem::CreateEvent(ResetType reset_type, std::string name) { | ||||
|     SharedPtr<Event> evt(new Event(*this)); | ||||
| 
 | ||||
|     evt->signaled = false; | ||||
|     evt->reset_type = reset_type; | ||||
|  |  | |||
|  | @ -12,13 +12,6 @@ namespace Kernel { | |||
| 
 | ||||
| class Event final : public WaitObject { | ||||
| public: | ||||
|     /**
 | ||||
|      * Creates an event | ||||
|      * @param reset_type ResetType describing how to create event | ||||
|      * @param name Optional name of event | ||||
|      */ | ||||
|     static SharedPtr<Event> Create(ResetType reset_type, std::string name = "Unknown"); | ||||
| 
 | ||||
|     std::string GetTypeName() const override { | ||||
|         return "Event"; | ||||
|     } | ||||
|  | @ -47,13 +40,15 @@ public: | |||
|     void Clear(); | ||||
| 
 | ||||
| private: | ||||
|     Event(); | ||||
|     explicit Event(KernelSystem& kernel); | ||||
|     ~Event() override; | ||||
| 
 | ||||
|     ResetType reset_type; ///< Current ResetType
 | ||||
| 
 | ||||
|     bool signaled;    ///< Whether the event has already been signaled
 | ||||
|     std::string name; ///< Name of event (optional)
 | ||||
| 
 | ||||
|     friend class KernelSystem; | ||||
| }; | ||||
| 
 | ||||
| } // namespace Kernel
 | ||||
|  |  | |||
|  | @ -12,9 +12,7 @@ | |||
| 
 | ||||
| namespace Kernel { | ||||
| 
 | ||||
| HandleTable g_handle_table; | ||||
| 
 | ||||
| HandleTable::HandleTable() { | ||||
| HandleTable::HandleTable(KernelSystem& kernel) : kernel(kernel) { | ||||
|     next_generation = 1; | ||||
|     Clear(); | ||||
| } | ||||
|  | @ -74,9 +72,9 @@ bool HandleTable::IsValid(Handle handle) const { | |||
| 
 | ||||
| SharedPtr<Object> HandleTable::GetGeneric(Handle handle) const { | ||||
|     if (handle == CurrentThread) { | ||||
|         return GetCurrentThread(); | ||||
|         return kernel.GetThreadManager().GetCurrentThread(); | ||||
|     } else if (handle == CurrentProcess) { | ||||
|         return g_current_process; | ||||
|         return kernel.GetCurrentProcess(); | ||||
|     } | ||||
| 
 | ||||
|     if (!IsValid(handle)) { | ||||
|  |  | |||
|  | @ -42,7 +42,7 @@ enum KernelHandle : Handle { | |||
|  */ | ||||
| class HandleTable final : NonCopyable { | ||||
| public: | ||||
|     HandleTable(); | ||||
|     explicit HandleTable(KernelSystem& kernel); | ||||
| 
 | ||||
|     /**
 | ||||
|      * Allocates a handle for the given object. | ||||
|  | @ -119,8 +119,8 @@ private: | |||
| 
 | ||||
|     /// Head of the free slots linked list.
 | ||||
|     u16 next_free_slot; | ||||
| 
 | ||||
|     KernelSystem& kernel; | ||||
| }; | ||||
| 
 | ||||
| extern HandleTable g_handle_table; | ||||
| 
 | ||||
| } // namespace Kernel
 | ||||
|  |  | |||
|  | @ -6,6 +6,7 @@ | |||
| #include <vector> | ||||
| #include "common/assert.h" | ||||
| #include "common/common_types.h" | ||||
| #include "core/core.h" | ||||
| #include "core/hle/kernel/event.h" | ||||
| #include "core/hle/kernel/handle_table.h" | ||||
| #include "core/hle/kernel/hle_ipc.h" | ||||
|  | @ -49,13 +50,14 @@ SharedPtr<Event> HLERequestContext::SleepClientThread(SharedPtr<Thread> thread, | |||
|         std::array<u32_le, IPC::COMMAND_BUFFER_LENGTH + 2 * IPC::MAX_STATIC_BUFFERS> cmd_buff; | ||||
|         Memory::ReadBlock(*process, thread->GetCommandBufferAddress(), cmd_buff.data(), | ||||
|                           cmd_buff.size() * sizeof(u32)); | ||||
|         context.WriteToOutgoingCommandBuffer(cmd_buff.data(), *process, Kernel::g_handle_table); | ||||
|         context.WriteToOutgoingCommandBuffer(cmd_buff.data(), *process); | ||||
|         // Copy the translated command buffer back into the thread's command buffer area.
 | ||||
|         Memory::WriteBlock(*process, thread->GetCommandBufferAddress(), cmd_buff.data(), | ||||
|                            cmd_buff.size() * sizeof(u32)); | ||||
|     }; | ||||
| 
 | ||||
|     auto event = Kernel::Event::Create(Kernel::ResetType::OneShot, "HLE Pause Event: " + reason); | ||||
|     auto event = Core::System::GetInstance().Kernel().CreateEvent(Kernel::ResetType::OneShot, | ||||
|                                                                   "HLE Pause Event: " + reason); | ||||
|     thread->status = ThreadStatus::WaitHleEvent; | ||||
|     thread->wait_objects = {event}; | ||||
|     event->AddWaitingThread(thread); | ||||
|  | @ -96,8 +98,7 @@ void HLERequestContext::AddStaticBuffer(u8 buffer_id, std::vector<u8> data) { | |||
| } | ||||
| 
 | ||||
| ResultCode HLERequestContext::PopulateFromIncomingCommandBuffer(const u32_le* src_cmdbuf, | ||||
|                                                                 Process& src_process, | ||||
|                                                                 HandleTable& src_table) { | ||||
|                                                                 Process& src_process) { | ||||
|     IPC::Header header{src_cmdbuf[0]}; | ||||
| 
 | ||||
|     std::size_t untranslated_size = 1u + header.normal_params_size; | ||||
|  | @ -120,10 +121,10 @@ ResultCode HLERequestContext::PopulateFromIncomingCommandBuffer(const u32_le* sr | |||
|                 Handle handle = src_cmdbuf[i]; | ||||
|                 SharedPtr<Object> object = nullptr; | ||||
|                 if (handle != 0) { | ||||
|                     object = src_table.GetGeneric(handle); | ||||
|                     object = src_process.handle_table.GetGeneric(handle); | ||||
|                     ASSERT(object != nullptr); // TODO(yuriks): Return error
 | ||||
|                     if (descriptor == IPC::DescriptorType::MoveHandle) { | ||||
|                         src_table.Close(handle); | ||||
|                         src_process.handle_table.Close(handle); | ||||
|                     } | ||||
|                 } | ||||
| 
 | ||||
|  | @ -161,8 +162,8 @@ ResultCode HLERequestContext::PopulateFromIncomingCommandBuffer(const u32_le* sr | |||
|     return RESULT_SUCCESS; | ||||
| } | ||||
| 
 | ||||
| ResultCode HLERequestContext::WriteToOutgoingCommandBuffer(u32_le* dst_cmdbuf, Process& dst_process, | ||||
|                                                            HandleTable& dst_table) const { | ||||
| ResultCode HLERequestContext::WriteToOutgoingCommandBuffer(u32_le* dst_cmdbuf, | ||||
|                                                            Process& dst_process) const { | ||||
|     IPC::Header header{cmd_buf[0]}; | ||||
| 
 | ||||
|     std::size_t untranslated_size = 1u + header.normal_params_size; | ||||
|  | @ -187,7 +188,7 @@ ResultCode HLERequestContext::WriteToOutgoingCommandBuffer(u32_le* dst_cmdbuf, P | |||
|                 Handle handle = 0; | ||||
|                 if (object != nullptr) { | ||||
|                     // TODO(yuriks): Figure out the proper error handling for if this fails
 | ||||
|                     handle = dst_table.Create(object).Unwrap(); | ||||
|                     handle = dst_process.handle_table.Create(object).Unwrap(); | ||||
|                 } | ||||
|                 dst_cmdbuf[i++] = handle; | ||||
|             } | ||||
|  |  | |||
|  | @ -124,8 +124,7 @@ private: | |||
| 
 | ||||
| /**
 | ||||
|  * Class containing information about an in-flight IPC request being handled by an HLE service | ||||
|  * implementation. Services should avoid using old global APIs (e.g. Kernel::GetCommandBuffer()) and | ||||
|  * when possible use the APIs in this class to service the request. | ||||
|  * implementation. | ||||
|  * | ||||
|  * HLE handle protocol | ||||
|  * =================== | ||||
|  | @ -226,11 +225,9 @@ public: | |||
|     MappedBuffer& GetMappedBuffer(u32 id_from_cmdbuf); | ||||
| 
 | ||||
|     /// Populates this context with data from the requesting process/thread.
 | ||||
|     ResultCode PopulateFromIncomingCommandBuffer(const u32_le* src_cmdbuf, Process& src_process, | ||||
|                                                  HandleTable& src_table); | ||||
|     ResultCode PopulateFromIncomingCommandBuffer(const u32_le* src_cmdbuf, Process& src_process); | ||||
|     /// Writes data from this context back to the requesting process/thread.
 | ||||
|     ResultCode WriteToOutgoingCommandBuffer(u32_le* dst_cmdbuf, Process& dst_process, | ||||
|                                             HandleTable& dst_table) const; | ||||
|     ResultCode WriteToOutgoingCommandBuffer(u32_le* dst_cmdbuf, Process& dst_process) const; | ||||
| 
 | ||||
| private: | ||||
|     std::array<u32, IPC::COMMAND_BUFFER_LENGTH> cmd_buf; | ||||
|  |  | |||
|  | @ -14,6 +14,68 @@ | |||
| 
 | ||||
| namespace Kernel { | ||||
| 
 | ||||
| void ScanForAndUnmapBuffer(std::array<u32, IPC::COMMAND_BUFFER_LENGTH>& dst_cmd_buf, | ||||
|                            const std::size_t dst_command_size, std::size_t& target_index, | ||||
|                            SharedPtr<Process> src_process, SharedPtr<Process> dst_process, | ||||
|                            const VAddr source_address, const VAddr page_start, const u32 num_pages, | ||||
|                            const u32 size, const IPC::MappedBufferPermissions permissions) { | ||||
|     while (target_index < dst_command_size) { | ||||
|         u32 desc = dst_cmd_buf[target_index++]; | ||||
| 
 | ||||
|         if (IPC::GetDescriptorType(desc) == IPC::DescriptorType::CopyHandle || | ||||
|             IPC::GetDescriptorType(desc) == IPC::DescriptorType::MoveHandle) { | ||||
|             u32 num_handles = IPC::HandleNumberFromDesc(desc); | ||||
|             for (u32 j = 0; j < num_handles; ++j) { | ||||
|                 target_index += 1; | ||||
|             } | ||||
|             continue; | ||||
|         } | ||||
| 
 | ||||
|         if (IPC::GetDescriptorType(desc) == IPC::DescriptorType::CallingPid || | ||||
|             IPC::GetDescriptorType(desc) == IPC::DescriptorType::StaticBuffer) { | ||||
|             target_index += 1; | ||||
|             continue; | ||||
|         } | ||||
| 
 | ||||
|         if (IPC::GetDescriptorType(desc) == IPC::DescriptorType::MappedBuffer) { | ||||
|             VAddr dest_address = dst_cmd_buf[target_index]; | ||||
|             IPC::MappedBufferDescInfo dest_descInfo{desc}; | ||||
|             u32 dest_size = static_cast<u32>(dest_descInfo.size); | ||||
|             IPC::MappedBufferPermissions dest_permissions = dest_descInfo.perms; | ||||
| 
 | ||||
|             if (dest_size == 0) { | ||||
|                 target_index += 1; | ||||
|                 continue; | ||||
|             } | ||||
| 
 | ||||
|             ASSERT(permissions == dest_permissions && size == dest_size); | ||||
|             // Readonly buffers do not need to be copied over to the target
 | ||||
|             // process again because they were (presumably) not modified. This
 | ||||
|             // behavior is consistent with the real kernel.
 | ||||
|             if (permissions != IPC::MappedBufferPermissions::R) { | ||||
|                 // Copy the modified buffer back into the target process
 | ||||
|                 Memory::CopyBlock(*src_process, *dst_process, source_address, dest_address, size); | ||||
|             } | ||||
| 
 | ||||
|             VAddr prev_reserve = page_start - Memory::PAGE_SIZE; | ||||
|             VAddr next_reserve = page_start + num_pages * Memory::PAGE_SIZE; | ||||
| 
 | ||||
|             auto& prev_vma = src_process->vm_manager.FindVMA(prev_reserve)->second; | ||||
|             auto& next_vma = src_process->vm_manager.FindVMA(next_reserve)->second; | ||||
|             ASSERT(prev_vma.meminfo_state == MemoryState::Reserved && | ||||
|                    next_vma.meminfo_state == MemoryState::Reserved); | ||||
| 
 | ||||
|             // Unmap the buffer and guard pages from the source process
 | ||||
|             ResultCode result = src_process->vm_manager.UnmapRange( | ||||
|                 page_start - Memory::PAGE_SIZE, (num_pages + 2) * Memory::PAGE_SIZE); | ||||
|             ASSERT(result == RESULT_SUCCESS); | ||||
| 
 | ||||
|             target_index += 1; | ||||
|             break; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| ResultCode TranslateCommandBuffer(SharedPtr<Thread> src_thread, SharedPtr<Thread> dst_thread, | ||||
|                                   VAddr src_address, VAddr dst_address, bool reply) { | ||||
| 
 | ||||
|  | @ -33,6 +95,18 @@ ResultCode TranslateCommandBuffer(SharedPtr<Thread> src_thread, SharedPtr<Thread | |||
|     std::array<u32, IPC::COMMAND_BUFFER_LENGTH> cmd_buf; | ||||
|     Memory::ReadBlock(*src_process, src_address, cmd_buf.data(), command_size * sizeof(u32)); | ||||
| 
 | ||||
|     // Create a copy of the target's command buffer
 | ||||
|     IPC::Header dst_header; | ||||
|     Memory::ReadBlock(*dst_process, dst_address, &dst_header.raw, sizeof(dst_header.raw)); | ||||
| 
 | ||||
|     std::size_t dst_untranslated_size = 1u + dst_header.normal_params_size; | ||||
|     std::size_t dst_command_size = dst_untranslated_size + dst_header.translate_params_size; | ||||
|     std::size_t target_index = dst_untranslated_size; | ||||
| 
 | ||||
|     std::array<u32, IPC::COMMAND_BUFFER_LENGTH> dst_cmd_buf; | ||||
|     Memory::ReadBlock(*dst_process, dst_address, dst_cmd_buf.data(), | ||||
|                       dst_command_size * sizeof(u32)); | ||||
| 
 | ||||
|     std::size_t i = untranslated_size; | ||||
|     while (i < command_size) { | ||||
|         u32 descriptor = cmd_buf[i]; | ||||
|  | @ -60,9 +134,9 @@ ResultCode TranslateCommandBuffer(SharedPtr<Thread> src_thread, SharedPtr<Thread | |||
|                 } else if (handle == CurrentProcess) { | ||||
|                     object = src_process; | ||||
|                 } else if (handle != 0) { | ||||
|                     object = g_handle_table.GetGeneric(handle); | ||||
|                     object = src_process->handle_table.GetGeneric(handle); | ||||
|                     if (descriptor == IPC::DescriptorType::MoveHandle) { | ||||
|                         g_handle_table.Close(handle); | ||||
|                         src_process->handle_table.Close(handle); | ||||
|                     } | ||||
|                 } | ||||
| 
 | ||||
|  | @ -73,7 +147,7 @@ ResultCode TranslateCommandBuffer(SharedPtr<Thread> src_thread, SharedPtr<Thread | |||
|                     continue; | ||||
|                 } | ||||
| 
 | ||||
|                 auto result = g_handle_table.Create(std::move(object)); | ||||
|                 auto result = dst_process->handle_table.Create(std::move(object)); | ||||
|                 cmd_buf[i++] = result.ValueOr(0); | ||||
|             } | ||||
|             break; | ||||
|  | @ -128,76 +202,50 @@ ResultCode TranslateCommandBuffer(SharedPtr<Thread> src_thread, SharedPtr<Thread | |||
|             u32 num_pages = | ||||
|                 Common::AlignUp(page_offset + size, Memory::PAGE_SIZE) >> Memory::PAGE_BITS; | ||||
| 
 | ||||
|             // Skip when the size is zero and num_pages == 0
 | ||||
|             if (size == 0) { | ||||
|                 cmd_buf[i++] = 0; | ||||
|                 break; | ||||
|             } | ||||
|             ASSERT(num_pages >= 1); | ||||
| 
 | ||||
|             if (reply) { | ||||
|                 // TODO(Subv): Scan the target's command buffer to make sure that there was a
 | ||||
|                 // MappedBuffer descriptor in the original request. The real kernel panics if you
 | ||||
|                 // try to reply with an unsolicited MappedBuffer.
 | ||||
|                 // Scan the target's command buffer for the matching mapped buffer.
 | ||||
|                 // The real kernel panics if you try to reply with an unsolicited MappedBuffer.
 | ||||
|                 ScanForAndUnmapBuffer(dst_cmd_buf, dst_command_size, target_index, src_process, | ||||
|                                       dst_process, source_address, page_start, num_pages, size, | ||||
|                                       permissions); | ||||
| 
 | ||||
|                 // Unmap the buffers. Readonly buffers do not need to be copied over to the target
 | ||||
|                 // process again because they were (presumably) not modified. This behavior is
 | ||||
|                 // consistent with the real kernel.
 | ||||
|                 if (permissions == IPC::MappedBufferPermissions::R) { | ||||
|                     ResultCode result = src_process->vm_manager.UnmapRange( | ||||
|                         page_start, num_pages * Memory::PAGE_SIZE); | ||||
|                     ASSERT(result == RESULT_SUCCESS); | ||||
|                 } | ||||
| 
 | ||||
|                 ASSERT_MSG(permissions == IPC::MappedBufferPermissions::R, | ||||
|                            "Unmapping Write MappedBuffers is unimplemented"); | ||||
|                 i += 1; | ||||
|                 break; | ||||
|             } | ||||
| 
 | ||||
|             VAddr target_address = 0; | ||||
| 
 | ||||
|             auto IsPageAligned = [](VAddr address) -> bool { | ||||
|                 return (address & Memory::PAGE_MASK) == 0; | ||||
|             }; | ||||
| 
 | ||||
|             // TODO(Subv): Support more than 1 page and aligned page mappings
 | ||||
|             ASSERT_MSG( | ||||
|                 num_pages == 1 && | ||||
|                     (!IsPageAligned(source_address) || !IsPageAligned(source_address + size)), | ||||
|                 "MappedBuffers of more than one page or aligned transfers are not implemented"); | ||||
| 
 | ||||
|             // TODO(Subv): Perform permission checks.
 | ||||
| 
 | ||||
|             // TODO(Subv): Leave a page of Reserved memory before the first page and after the last
 | ||||
|             // page.
 | ||||
|             // Reserve a page of memory before the mapped buffer
 | ||||
|             auto reserve_buffer = std::make_shared<std::vector<u8>>(Memory::PAGE_SIZE); | ||||
|             dst_process->vm_manager.MapMemoryBlockToBase( | ||||
|                 Memory::IPC_MAPPING_VADDR, Memory::IPC_MAPPING_SIZE, reserve_buffer, 0, | ||||
|                 static_cast<u32>(reserve_buffer->size()), Kernel::MemoryState::Reserved); | ||||
| 
 | ||||
|             if (!IsPageAligned(source_address) || | ||||
|                 (num_pages == 1 && !IsPageAligned(source_address + size))) { | ||||
|                 // If the address of the source buffer is not page-aligned or if the buffer doesn't
 | ||||
|                 // fill an entire page, then we have to allocate a page of memory in the target
 | ||||
|                 // process and copy over the data from the input buffer. This allocated buffer will
 | ||||
|                 // be copied back to the source process and deallocated when the server replies to
 | ||||
|                 // the request via ReplyAndReceive.
 | ||||
|             auto buffer = std::make_shared<std::vector<u8>>(num_pages * Memory::PAGE_SIZE); | ||||
|             Memory::ReadBlock(*src_process, source_address, buffer->data() + page_offset, size); | ||||
| 
 | ||||
|                 auto buffer = std::make_shared<std::vector<u8>>(Memory::PAGE_SIZE); | ||||
| 
 | ||||
|                 // Number of bytes until the next page.
 | ||||
|                 std::size_t difference_to_page = | ||||
|                     Common::AlignUp(source_address, Memory::PAGE_SIZE) - source_address; | ||||
|                 // If the data fits in one page we can just copy the required size instead of the
 | ||||
|                 // entire page.
 | ||||
|                 std::size_t read_size = | ||||
|                     num_pages == 1 ? static_cast<std::size_t>(size) : difference_to_page; | ||||
| 
 | ||||
|                 Memory::ReadBlock(*src_process, source_address, buffer->data() + page_offset, | ||||
|                                   read_size); | ||||
| 
 | ||||
|                 // Map the page into the target process' address space.
 | ||||
|                 target_address = | ||||
|                     dst_process->vm_manager | ||||
|                         .MapMemoryBlockToBase(Memory::IPC_MAPPING_VADDR, Memory::IPC_MAPPING_SIZE, | ||||
|                                               buffer, 0, static_cast<u32>(buffer->size()), | ||||
|                                               Kernel::MemoryState::Shared) | ||||
|                         .Unwrap(); | ||||
|             } | ||||
|             // Map the page(s) into the target process' address space.
 | ||||
|             target_address = dst_process->vm_manager | ||||
|                                  .MapMemoryBlockToBase( | ||||
|                                      Memory::IPC_MAPPING_VADDR, Memory::IPC_MAPPING_SIZE, buffer, 0, | ||||
|                                      static_cast<u32>(buffer->size()), Kernel::MemoryState::Shared) | ||||
|                                  .Unwrap(); | ||||
| 
 | ||||
|             cmd_buf[i++] = target_address + page_offset; | ||||
| 
 | ||||
|             // Reserve a page of memory after the mapped buffer
 | ||||
|             dst_process->vm_manager.MapMemoryBlockToBase( | ||||
|                 Memory::IPC_MAPPING_VADDR, Memory::IPC_MAPPING_SIZE, reserve_buffer, 0, | ||||
|                 static_cast<u32>(reserve_buffer->size()), Kernel::MemoryState::Reserved); | ||||
|             break; | ||||
|         } | ||||
|         default: | ||||
|  |  | |||
|  | @ -2,46 +2,77 @@ | |||
| // Licensed under GPLv2 or any later version
 | ||||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| #include "core/hle/config_mem.h" | ||||
| #include "core/hle/kernel/client_port.h" | ||||
| #include "core/hle/kernel/config_mem.h" | ||||
| #include "core/hle/kernel/handle_table.h" | ||||
| #include "core/hle/kernel/kernel.h" | ||||
| #include "core/hle/kernel/memory.h" | ||||
| #include "core/hle/kernel/process.h" | ||||
| #include "core/hle/kernel/resource_limit.h" | ||||
| #include "core/hle/kernel/shared_page.h" | ||||
| #include "core/hle/kernel/thread.h" | ||||
| #include "core/hle/kernel/timer.h" | ||||
| #include "core/hle/shared_page.h" | ||||
| 
 | ||||
| namespace Kernel { | ||||
| 
 | ||||
| std::atomic<u32> Object::next_object_id{0}; | ||||
| 
 | ||||
| /// Initialize the kernel
 | ||||
| void Init(u32 system_mode) { | ||||
|     ConfigMem::Init(); | ||||
| KernelSystem::KernelSystem(u32 system_mode) { | ||||
|     MemoryInit(system_mode); | ||||
| 
 | ||||
|     Kernel::MemoryInit(system_mode); | ||||
| 
 | ||||
|     Kernel::ResourceLimitsInit(); | ||||
|     Kernel::ThreadingInit(); | ||||
|     Kernel::TimersInit(); | ||||
| 
 | ||||
|     Object::next_object_id = 0; | ||||
|     // TODO(Subv): Start the process ids from 10 for now, as lower PIDs are
 | ||||
|     // reserved for low-level services
 | ||||
|     Process::next_process_id = 10; | ||||
|     resource_limits = std::make_unique<ResourceLimitList>(*this); | ||||
|     thread_manager = std::make_unique<ThreadManager>(); | ||||
|     timer_manager = std::make_unique<TimerManager>(); | ||||
| } | ||||
| 
 | ||||
| /// Shutdown the kernel
 | ||||
| void Shutdown() { | ||||
|     g_handle_table.Clear(); // Free all kernel objects
 | ||||
| KernelSystem::~KernelSystem() = default; | ||||
| 
 | ||||
|     Kernel::ThreadingShutdown(); | ||||
|     g_current_process = nullptr; | ||||
| ResourceLimitList& KernelSystem::ResourceLimit() { | ||||
|     return *resource_limits; | ||||
| } | ||||
| 
 | ||||
|     Kernel::TimersShutdown(); | ||||
|     Kernel::ResourceLimitsShutdown(); | ||||
|     Kernel::MemoryShutdown(); | ||||
| const ResourceLimitList& KernelSystem::ResourceLimit() const { | ||||
|     return *resource_limits; | ||||
| } | ||||
| 
 | ||||
| u32 KernelSystem::GenerateObjectID() { | ||||
|     return next_object_id++; | ||||
| } | ||||
| 
 | ||||
| SharedPtr<Process> KernelSystem::GetCurrentProcess() const { | ||||
|     return current_process; | ||||
| } | ||||
| 
 | ||||
| void KernelSystem::SetCurrentProcess(SharedPtr<Process> process) { | ||||
|     current_process = std::move(process); | ||||
| } | ||||
| 
 | ||||
| ThreadManager& KernelSystem::GetThreadManager() { | ||||
|     return *thread_manager; | ||||
| } | ||||
| 
 | ||||
| const ThreadManager& KernelSystem::GetThreadManager() const { | ||||
|     return *thread_manager; | ||||
| } | ||||
| 
 | ||||
| TimerManager& KernelSystem::GetTimerManager() { | ||||
|     return *timer_manager; | ||||
| } | ||||
| 
 | ||||
| const TimerManager& KernelSystem::GetTimerManager() const { | ||||
|     return *timer_manager; | ||||
| } | ||||
| 
 | ||||
| SharedPage::Handler& KernelSystem::GetSharedPageHandler() { | ||||
|     return *shared_page_handler; | ||||
| } | ||||
| 
 | ||||
| const SharedPage::Handler& KernelSystem::GetSharedPageHandler() const { | ||||
|     return *shared_page_handler; | ||||
| } | ||||
| 
 | ||||
| void KernelSystem::AddNamedPort(std::string name, SharedPtr<ClientPort> port) { | ||||
|     named_ports.emplace(std::move(name), std::move(port)); | ||||
| } | ||||
| 
 | ||||
| } // namespace Kernel
 | ||||
|  |  | |||
|  | @ -4,14 +4,250 @@ | |||
| 
 | ||||
| #pragma once | ||||
| 
 | ||||
| #include <array> | ||||
| #include <atomic> | ||||
| #include <memory> | ||||
| #include <string> | ||||
| #include <unordered_map> | ||||
| #include <vector> | ||||
| #include <boost/smart_ptr/intrusive_ptr.hpp> | ||||
| #include "common/common_types.h" | ||||
| #include "core/hle/kernel/memory.h" | ||||
| #include "core/hle/result.h" | ||||
| 
 | ||||
| namespace ConfigMem { | ||||
| class Handler; | ||||
| } | ||||
| 
 | ||||
| namespace SharedPage { | ||||
| class Handler; | ||||
| } | ||||
| 
 | ||||
| namespace Kernel { | ||||
| 
 | ||||
| /// Initialize the kernel with the specified system mode.
 | ||||
| void Init(u32 system_mode); | ||||
| class AddressArbiter; | ||||
| class Event; | ||||
| class Mutex; | ||||
| class CodeSet; | ||||
| class Process; | ||||
| class Thread; | ||||
| class Semaphore; | ||||
| class Timer; | ||||
| class ClientPort; | ||||
| class ServerPort; | ||||
| class ClientSession; | ||||
| class ServerSession; | ||||
| class ResourceLimitList; | ||||
| class SharedMemory; | ||||
| class ThreadManager; | ||||
| class TimerManager; | ||||
| class VMManager; | ||||
| 
 | ||||
| /// Shutdown the kernel
 | ||||
| void Shutdown(); | ||||
| enum class ResetType { | ||||
|     OneShot, | ||||
|     Sticky, | ||||
|     Pulse, | ||||
| }; | ||||
| 
 | ||||
| /// Permissions for mapped shared memory blocks
 | ||||
| enum class MemoryPermission : u32 { | ||||
|     None = 0, | ||||
|     Read = (1u << 0), | ||||
|     Write = (1u << 1), | ||||
|     ReadWrite = (Read | Write), | ||||
|     Execute = (1u << 2), | ||||
|     ReadExecute = (Read | Execute), | ||||
|     WriteExecute = (Write | Execute), | ||||
|     ReadWriteExecute = (Read | Write | Execute), | ||||
|     DontCare = (1u << 28) | ||||
| }; | ||||
| 
 | ||||
| enum class MemoryRegion : u16 { | ||||
|     APPLICATION = 1, | ||||
|     SYSTEM = 2, | ||||
|     BASE = 3, | ||||
| }; | ||||
| 
 | ||||
| template <typename T> | ||||
| using SharedPtr = boost::intrusive_ptr<T>; | ||||
| 
 | ||||
| class KernelSystem { | ||||
| public: | ||||
|     explicit KernelSystem(u32 system_mode); | ||||
|     ~KernelSystem(); | ||||
| 
 | ||||
|     /**
 | ||||
|      * Creates an address arbiter. | ||||
|      * | ||||
|      * @param name Optional name used for debugging. | ||||
|      * @returns The created AddressArbiter. | ||||
|      */ | ||||
|     SharedPtr<AddressArbiter> CreateAddressArbiter(std::string name = "Unknown"); | ||||
| 
 | ||||
|     /**
 | ||||
|      * Creates an event | ||||
|      * @param reset_type ResetType describing how to create event | ||||
|      * @param name Optional name of event | ||||
|      */ | ||||
|     SharedPtr<Event> CreateEvent(ResetType reset_type, std::string name = "Unknown"); | ||||
| 
 | ||||
|     /**
 | ||||
|      * Creates a mutex. | ||||
|      * @param initial_locked Specifies if the mutex should be locked initially | ||||
|      * @param name Optional name of mutex | ||||
|      * @return Pointer to new Mutex object | ||||
|      */ | ||||
|     SharedPtr<Mutex> CreateMutex(bool initial_locked, std::string name = "Unknown"); | ||||
| 
 | ||||
|     SharedPtr<CodeSet> CreateCodeSet(std::string name, u64 program_id); | ||||
| 
 | ||||
|     SharedPtr<Process> CreateProcess(SharedPtr<CodeSet> code_set); | ||||
| 
 | ||||
|     /**
 | ||||
|      * Creates and returns a new thread. The new thread is immediately scheduled | ||||
|      * @param name The friendly name desired for the thread | ||||
|      * @param entry_point The address at which the thread should start execution | ||||
|      * @param priority The thread's priority | ||||
|      * @param arg User data to pass to the thread | ||||
|      * @param processor_id The ID(s) of the processors on which the thread is desired to be run | ||||
|      * @param stack_top The address of the thread's stack top | ||||
|      * @param owner_process The parent process for the thread | ||||
|      * @return A shared pointer to the newly created thread | ||||
|      */ | ||||
|     ResultVal<SharedPtr<Thread>> CreateThread(std::string name, VAddr entry_point, u32 priority, | ||||
|                                               u32 arg, s32 processor_id, VAddr stack_top, | ||||
|                                               Process& owner_process); | ||||
| 
 | ||||
|     /**
 | ||||
|      * Creates a semaphore. | ||||
|      * @param initial_count Number of slots reserved for other threads | ||||
|      * @param max_count Maximum number of slots the semaphore can have | ||||
|      * @param name Optional name of semaphore | ||||
|      * @return The created semaphore | ||||
|      */ | ||||
|     ResultVal<SharedPtr<Semaphore>> CreateSemaphore(s32 initial_count, s32 max_count, | ||||
|                                                     std::string name = "Unknown"); | ||||
| 
 | ||||
|     /**
 | ||||
|      * Creates a timer | ||||
|      * @param reset_type ResetType describing how to create the timer | ||||
|      * @param name Optional name of timer | ||||
|      * @return The created Timer | ||||
|      */ | ||||
|     SharedPtr<Timer> CreateTimer(ResetType reset_type, std::string name = "Unknown"); | ||||
| 
 | ||||
|     /**
 | ||||
|      * 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 | ||||
|      * @return The created port tuple | ||||
|      */ | ||||
|     std::tuple<SharedPtr<ServerPort>, SharedPtr<ClientPort>> CreatePortPair( | ||||
|         u32 max_sessions, std::string name = "UnknownPort"); | ||||
| 
 | ||||
|     /**
 | ||||
|      * Creates a pair of ServerSession and an associated ClientSession. | ||||
|      * @param name        Optional name of the ports. | ||||
|      * @param client_port Optional The ClientPort that spawned this session. | ||||
|      * @return The created session tuple | ||||
|      */ | ||||
|     std::tuple<SharedPtr<ServerSession>, SharedPtr<ClientSession>> CreateSessionPair( | ||||
|         const std::string& name = "Unknown", SharedPtr<ClientPort> client_port = nullptr); | ||||
| 
 | ||||
|     ResourceLimitList& ResourceLimit(); | ||||
|     const ResourceLimitList& ResourceLimit() const; | ||||
| 
 | ||||
|     /**
 | ||||
|      * Creates a shared memory object. | ||||
|      * @param owner_process Process that created this shared memory object. | ||||
|      * @param size Size of the memory block. Must be page-aligned. | ||||
|      * @param permissions Permission restrictions applied to the process which created the block. | ||||
|      * @param other_permissions Permission restrictions applied to other processes mapping the | ||||
|      * block. | ||||
|      * @param address The address from which to map the Shared Memory. | ||||
|      * @param region If the address is 0, the shared memory will be allocated in this region of the | ||||
|      * linear heap. | ||||
|      * @param name Optional object name, used for debugging purposes. | ||||
|      */ | ||||
|     SharedPtr<SharedMemory> CreateSharedMemory(Process* owner_process, u32 size, | ||||
|                                                MemoryPermission permissions, | ||||
|                                                MemoryPermission other_permissions, | ||||
|                                                VAddr address = 0, | ||||
|                                                MemoryRegion region = MemoryRegion::BASE, | ||||
|                                                std::string name = "Unknown"); | ||||
| 
 | ||||
|     /**
 | ||||
|      * Creates a shared memory object from a block of memory managed by an HLE applet. | ||||
|      * @param offset The offset into the heap block that the SharedMemory will map. | ||||
|      * @param size Size of the memory block. Must be page-aligned. | ||||
|      * @param permissions Permission restrictions applied to the process which created the block. | ||||
|      * @param other_permissions Permission restrictions applied to other processes mapping the | ||||
|      * block. | ||||
|      * @param name Optional object name, used for debugging purposes. | ||||
|      */ | ||||
|     SharedPtr<SharedMemory> CreateSharedMemoryForApplet(u32 offset, u32 size, | ||||
|                                                         MemoryPermission permissions, | ||||
|                                                         MemoryPermission other_permissions, | ||||
|                                                         std::string name = "Unknown Applet"); | ||||
| 
 | ||||
|     u32 GenerateObjectID(); | ||||
| 
 | ||||
|     /// Retrieves a process from the current list of processes.
 | ||||
|     SharedPtr<Process> GetProcessById(u32 process_id) const; | ||||
| 
 | ||||
|     SharedPtr<Process> GetCurrentProcess() const; | ||||
|     void SetCurrentProcess(SharedPtr<Process> process); | ||||
| 
 | ||||
|     ThreadManager& GetThreadManager(); | ||||
|     const ThreadManager& GetThreadManager() const; | ||||
| 
 | ||||
|     TimerManager& GetTimerManager(); | ||||
|     const TimerManager& GetTimerManager() const; | ||||
| 
 | ||||
|     void MapSharedPages(VMManager& address_space); | ||||
| 
 | ||||
|     SharedPage::Handler& GetSharedPageHandler(); | ||||
|     const SharedPage::Handler& GetSharedPageHandler() const; | ||||
| 
 | ||||
|     MemoryRegionInfo* GetMemoryRegion(MemoryRegion region); | ||||
| 
 | ||||
|     std::array<MemoryRegionInfo, 3> memory_regions; | ||||
| 
 | ||||
|     /// Adds a port to the named port table
 | ||||
|     void AddNamedPort(std::string name, SharedPtr<ClientPort> port); | ||||
| 
 | ||||
|     /// Map of named ports managed by the kernel, which can be retrieved using the ConnectToPort
 | ||||
|     std::unordered_map<std::string, SharedPtr<ClientPort>> named_ports; | ||||
| 
 | ||||
| private: | ||||
|     void MemoryInit(u32 mem_type); | ||||
| 
 | ||||
|     std::unique_ptr<ResourceLimitList> resource_limits; | ||||
|     std::atomic<u32> next_object_id{0}; | ||||
| 
 | ||||
|     // Note: keep the member order below in order to perform correct destruction.
 | ||||
|     // Thread manager is destructed before process list in order to Stop threads and clear thread
 | ||||
|     // info from their parent processes first. Timer manager is destructed after process list
 | ||||
|     // because timers are destructed along with process list and they need to clear info from the
 | ||||
|     // timer manager.
 | ||||
|     // TODO (wwylele): refactor the cleanup sequence to make this less complicated and sensitive.
 | ||||
| 
 | ||||
|     std::unique_ptr<TimerManager> timer_manager; | ||||
| 
 | ||||
|     // TODO(Subv): Start the process ids from 10 for now, as lower PIDs are
 | ||||
|     // reserved for low-level services
 | ||||
|     u32 next_process_id = 10; | ||||
| 
 | ||||
|     // Lists all processes that exist in the current session.
 | ||||
|     std::vector<SharedPtr<Process>> process_list; | ||||
| 
 | ||||
|     SharedPtr<Process> current_process; | ||||
| 
 | ||||
|     std::unique_ptr<ThreadManager> thread_manager; | ||||
| 
 | ||||
|     std::unique_ptr<ConfigMem::Handler> config_mem_handler; | ||||
|     std::unique_ptr<SharedPage::Handler> shared_page_handler; | ||||
| }; | ||||
| 
 | ||||
| } // namespace Kernel
 | ||||
|  |  | |||
|  | @ -12,8 +12,10 @@ | |||
| #include "common/common_types.h" | ||||
| #include "common/logging/log.h" | ||||
| #include "core/core.h" | ||||
| #include "core/hle/config_mem.h" | ||||
| #include "core/hle/kernel/config_mem.h" | ||||
| #include "core/hle/kernel/memory.h" | ||||
| #include "core/hle/kernel/process.h" | ||||
| #include "core/hle/kernel/shared_page.h" | ||||
| #include "core/hle/kernel/vm_manager.h" | ||||
| #include "core/hle/result.h" | ||||
| #include "core/memory.h" | ||||
|  | @ -23,8 +25,6 @@ | |||
| 
 | ||||
| namespace Kernel { | ||||
| 
 | ||||
| MemoryRegionInfo memory_regions[3]; | ||||
| 
 | ||||
| /// Size of the APPLICATION, SYSTEM and BASE memory regions (respectively) for each system
 | ||||
| /// memory configuration type.
 | ||||
| static const u32 memory_region_sizes[8][3] = { | ||||
|  | @ -41,7 +41,7 @@ static const u32 memory_region_sizes[8][3] = { | |||
|     {0x0B200000, 0x02E00000, 0x02000000}, // 7
 | ||||
| }; | ||||
| 
 | ||||
| void MemoryInit(u32 mem_type) { | ||||
| void KernelSystem::MemoryInit(u32 mem_type) { | ||||
|     // TODO(yuriks): On the n3DS, all o3DS configurations (<=5) are forced to 6 instead.
 | ||||
|     ASSERT_MSG(mem_type <= 5, "New 3DS memory configuration aren't supported yet!"); | ||||
|     ASSERT(mem_type != 1); | ||||
|  | @ -50,13 +50,7 @@ void MemoryInit(u32 mem_type) { | |||
|     // the sizes specified in the memory_region_sizes table.
 | ||||
|     VAddr base = 0; | ||||
|     for (int i = 0; i < 3; ++i) { | ||||
|         memory_regions[i].base = base; | ||||
|         memory_regions[i].size = memory_region_sizes[mem_type][i]; | ||||
|         memory_regions[i].used = 0; | ||||
|         memory_regions[i].linear_heap_memory = std::make_shared<std::vector<u8>>(); | ||||
|         // Reserve enough space for this region of FCRAM.
 | ||||
|         // We do not want this block of memory to be relocated when allocating from it.
 | ||||
|         memory_regions[i].linear_heap_memory->reserve(memory_regions[i].size); | ||||
|         memory_regions[i].Reset(base, memory_region_sizes[mem_type][i]); | ||||
| 
 | ||||
|         base += memory_regions[i].size; | ||||
|     } | ||||
|  | @ -64,25 +58,19 @@ void MemoryInit(u32 mem_type) { | |||
|     // We must've allocated the entire FCRAM by the end
 | ||||
|     ASSERT(base == Memory::FCRAM_SIZE); | ||||
| 
 | ||||
|     using ConfigMem::config_mem; | ||||
|     config_mem_handler = std::make_unique<ConfigMem::Handler>(); | ||||
|     auto& config_mem = config_mem_handler->GetConfigMem(); | ||||
|     config_mem.app_mem_type = mem_type; | ||||
|     // app_mem_malloc does not always match the configured size for memory_region[0]: in case the
 | ||||
|     // n3DS type override is in effect it reports the size the game expects, not the real one.
 | ||||
|     config_mem.app_mem_alloc = memory_region_sizes[mem_type][0]; | ||||
|     config_mem.sys_mem_alloc = memory_regions[1].size; | ||||
|     config_mem.base_mem_alloc = memory_regions[2].size; | ||||
| 
 | ||||
|     shared_page_handler = std::make_unique<SharedPage::Handler>(); | ||||
| } | ||||
| 
 | ||||
| void MemoryShutdown() { | ||||
|     for (auto& region : memory_regions) { | ||||
|         region.base = 0; | ||||
|         region.size = 0; | ||||
|         region.used = 0; | ||||
|         region.linear_heap_memory = nullptr; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| MemoryRegionInfo* GetMemoryRegion(MemoryRegion region) { | ||||
| MemoryRegionInfo* KernelSystem::GetMemoryRegion(MemoryRegion region) { | ||||
|     switch (region) { | ||||
|     case MemoryRegion::APPLICATION: | ||||
|         return &memory_regions[0]; | ||||
|  | @ -152,23 +140,93 @@ void HandleSpecialMapping(VMManager& address_space, const AddressMapping& mappin | |||
|                             mapping.read_only ? VMAPermission::Read : VMAPermission::ReadWrite); | ||||
| } | ||||
| 
 | ||||
| void MapSharedPages(VMManager& address_space) { | ||||
|     auto cfg_mem_vma = address_space | ||||
|                            .MapBackingMemory(Memory::CONFIG_MEMORY_VADDR, | ||||
|                                              reinterpret_cast<u8*>(&ConfigMem::config_mem), | ||||
|                                              Memory::CONFIG_MEMORY_SIZE, MemoryState::Shared) | ||||
|                            .Unwrap(); | ||||
| void KernelSystem::MapSharedPages(VMManager& address_space) { | ||||
|     auto cfg_mem_vma = | ||||
|         address_space | ||||
|             .MapBackingMemory(Memory::CONFIG_MEMORY_VADDR, | ||||
|                               reinterpret_cast<u8*>(&config_mem_handler->GetConfigMem()), | ||||
|                               Memory::CONFIG_MEMORY_SIZE, MemoryState::Shared) | ||||
|             .Unwrap(); | ||||
|     address_space.Reprotect(cfg_mem_vma, VMAPermission::Read); | ||||
| 
 | ||||
|     auto shared_page_vma = | ||||
|         address_space | ||||
|             .MapBackingMemory( | ||||
|                 Memory::SHARED_PAGE_VADDR, | ||||
|                 reinterpret_cast<u8*>( | ||||
|                     &Core::System::GetInstance().GetSharedPageHandler()->GetSharedPage()), | ||||
|                 Memory::SHARED_PAGE_SIZE, MemoryState::Shared) | ||||
|             .MapBackingMemory(Memory::SHARED_PAGE_VADDR, | ||||
|                               reinterpret_cast<u8*>(&shared_page_handler->GetSharedPage()), | ||||
|                               Memory::SHARED_PAGE_SIZE, MemoryState::Shared) | ||||
|             .Unwrap(); | ||||
|     address_space.Reprotect(shared_page_vma, VMAPermission::Read); | ||||
| } | ||||
| 
 | ||||
| void MemoryRegionInfo::Reset(u32 base, u32 size) { | ||||
|     this->base = base; | ||||
|     this->size = size; | ||||
|     used = 0; | ||||
|     free_blocks.clear(); | ||||
| 
 | ||||
|     // mark the entire region as free
 | ||||
|     free_blocks.insert(Interval::right_open(base, base + size)); | ||||
| } | ||||
| 
 | ||||
| MemoryRegionInfo::IntervalSet MemoryRegionInfo::HeapAllocate(u32 size) { | ||||
|     IntervalSet result; | ||||
|     u32 rest = size; | ||||
| 
 | ||||
|     // Try allocating from the higher address
 | ||||
|     for (auto iter = free_blocks.rbegin(); iter != free_blocks.rend(); ++iter) { | ||||
|         ASSERT(iter->bounds() == boost::icl::interval_bounds::right_open()); | ||||
|         if (iter->upper() - iter->lower() >= rest) { | ||||
|             // Requested size is fulfilled with this block
 | ||||
|             result += Interval(iter->upper() - rest, iter->upper()); | ||||
|             rest = 0; | ||||
|             break; | ||||
|         } | ||||
|         result += *iter; | ||||
|         rest -= iter->upper() - iter->lower(); | ||||
|     } | ||||
| 
 | ||||
|     if (rest != 0) { | ||||
|         // There is no enough free space
 | ||||
|         return {}; | ||||
|     } | ||||
| 
 | ||||
|     free_blocks -= result; | ||||
|     used += size; | ||||
|     return result; | ||||
| } | ||||
| 
 | ||||
| bool MemoryRegionInfo::LinearAllocate(u32 offset, u32 size) { | ||||
|     Interval interval(offset, offset + size); | ||||
|     if (!boost::icl::contains(free_blocks, interval)) { | ||||
|         // The requested range is already allocated
 | ||||
|         return false; | ||||
|     } | ||||
|     free_blocks -= interval; | ||||
|     used += size; | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| std::optional<u32> MemoryRegionInfo::LinearAllocate(u32 size) { | ||||
|     // Find the first sufficient continuous block from the lower address
 | ||||
|     for (const auto& interval : free_blocks) { | ||||
|         ASSERT(interval.bounds() == boost::icl::interval_bounds::right_open()); | ||||
|         if (interval.upper() - interval.lower() >= size) { | ||||
|             Interval allocated(interval.lower(), interval.lower() + size); | ||||
|             free_blocks -= allocated; | ||||
|             used += size; | ||||
|             return allocated.lower(); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     // No sufficient block found
 | ||||
|     return {}; | ||||
| } | ||||
| 
 | ||||
| void MemoryRegionInfo::Free(u32 offset, u32 size) { | ||||
|     Interval interval(offset, offset + size); | ||||
|     ASSERT(!boost::icl::intersects(free_blocks, interval)); // must be allocated blocks
 | ||||
|     free_blocks += interval; | ||||
|     used -= size; | ||||
| } | ||||
| 
 | ||||
| } // namespace Kernel
 | ||||
|  |  | |||
|  | @ -4,12 +4,13 @@ | |||
| 
 | ||||
| #pragma once | ||||
| 
 | ||||
| #include <memory> | ||||
| #include <optional> | ||||
| #include <boost/icl/interval_set.hpp> | ||||
| #include "common/common_types.h" | ||||
| #include "core/hle/kernel/process.h" | ||||
| 
 | ||||
| namespace Kernel { | ||||
| 
 | ||||
| struct AddressMapping; | ||||
| class VMManager; | ||||
| 
 | ||||
| struct MemoryRegionInfo { | ||||
|  | @ -17,15 +18,50 @@ struct MemoryRegionInfo { | |||
|     u32 size; | ||||
|     u32 used; | ||||
| 
 | ||||
|     std::shared_ptr<std::vector<u8>> linear_heap_memory; | ||||
|     // The domain of the interval_set are offsets from start of FCRAM
 | ||||
|     using IntervalSet = boost::icl::interval_set<u32>; | ||||
|     using Interval = IntervalSet::interval_type; | ||||
| 
 | ||||
|     IntervalSet free_blocks; | ||||
| 
 | ||||
|     /**
 | ||||
|      * Reset the allocator state | ||||
|      * @param base The base offset the beginning of FCRAM. | ||||
|      * @param size The region size this allocator manages | ||||
|      */ | ||||
|     void Reset(u32 base, u32 size); | ||||
| 
 | ||||
|     /**
 | ||||
|      * Allocates memory from the heap. | ||||
|      * @param size The size of memory to allocate. | ||||
|      * @returns The set of blocks that make up the allocation request. Empty set if there is no | ||||
|      *     enough space. | ||||
|      */ | ||||
|     IntervalSet HeapAllocate(u32 size); | ||||
| 
 | ||||
|     /**
 | ||||
|      * Allocates memory from the linear heap with specific address and size. | ||||
|      * @param offset the address offset to the beginning of FCRAM. | ||||
|      * @param size size of the memory to allocate. | ||||
|      * @returns true if the allocation is successful. false if the requested region is not free. | ||||
|      */ | ||||
|     bool LinearAllocate(u32 offset, u32 size); | ||||
| 
 | ||||
|     /**
 | ||||
|      * Allocates memory from the linear heap with only size specified. | ||||
|      * @param size size of the memory to allocate. | ||||
|      * @returns the address offset to the beginning of FCRAM; null if there is no enough space | ||||
|      */ | ||||
|     std::optional<u32> LinearAllocate(u32 size); | ||||
| 
 | ||||
|     /**
 | ||||
|      * Frees one segment of memory. The memory must have been allocated as heap or linear heap. | ||||
|      * @param offset the region address offset to the beginning of FCRAM. | ||||
|      * @param size the size of the region to free. | ||||
|      */ | ||||
|     void Free(u32 offset, u32 size); | ||||
| }; | ||||
| 
 | ||||
| void MemoryInit(u32 mem_type); | ||||
| void MemoryShutdown(); | ||||
| MemoryRegionInfo* GetMemoryRegion(MemoryRegion region); | ||||
| 
 | ||||
| void HandleSpecialMapping(VMManager& address_space, const AddressMapping& mapping); | ||||
| void MapSharedPages(VMManager& address_space); | ||||
| 
 | ||||
| extern MemoryRegionInfo memory_regions[3]; | ||||
| } // namespace Kernel
 | ||||
|  |  | |||
|  | @ -24,11 +24,11 @@ void ReleaseThreadMutexes(Thread* thread) { | |||
|     thread->held_mutexes.clear(); | ||||
| } | ||||
| 
 | ||||
| Mutex::Mutex() {} | ||||
| Mutex::Mutex(KernelSystem& kernel) : WaitObject(kernel) {} | ||||
| Mutex::~Mutex() {} | ||||
| 
 | ||||
| SharedPtr<Mutex> Mutex::Create(bool initial_locked, std::string name) { | ||||
|     SharedPtr<Mutex> mutex(new Mutex); | ||||
| SharedPtr<Mutex> KernelSystem::CreateMutex(bool initial_locked, std::string name) { | ||||
|     SharedPtr<Mutex> mutex(new Mutex(*this)); | ||||
| 
 | ||||
|     mutex->lock_count = 0; | ||||
|     mutex->name = std::move(name); | ||||
|  | @ -36,7 +36,7 @@ SharedPtr<Mutex> Mutex::Create(bool initial_locked, std::string name) { | |||
| 
 | ||||
|     // Acquire mutex with current thread if initialized as locked
 | ||||
|     if (initial_locked) | ||||
|         mutex->Acquire(GetCurrentThread()); | ||||
|         mutex->Acquire(thread_manager->GetCurrentThread()); | ||||
| 
 | ||||
|     return mutex; | ||||
| } | ||||
|  |  | |||
|  | @ -16,14 +16,6 @@ class Thread; | |||
| 
 | ||||
| class Mutex final : public WaitObject { | ||||
| public: | ||||
|     /**
 | ||||
|      * Creates a mutex. | ||||
|      * @param initial_locked Specifies if the mutex should be locked initially | ||||
|      * @param name Optional name of mutex | ||||
|      * @return Pointer to new Mutex object | ||||
|      */ | ||||
|     static SharedPtr<Mutex> Create(bool initial_locked, std::string name = "Unknown"); | ||||
| 
 | ||||
|     std::string GetTypeName() const override { | ||||
|         return "Mutex"; | ||||
|     } | ||||
|  | @ -61,8 +53,10 @@ public: | |||
|     ResultCode Release(Thread* thread); | ||||
| 
 | ||||
| private: | ||||
|     Mutex(); | ||||
|     explicit Mutex(KernelSystem& kernel); | ||||
|     ~Mutex() override; | ||||
| 
 | ||||
|     friend class KernelSystem; | ||||
| }; | ||||
| 
 | ||||
| /**
 | ||||
|  |  | |||
|  | @ -3,10 +3,13 @@ | |||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| #include "common/assert.h" | ||||
| #include "core/hle/kernel/kernel.h" | ||||
| #include "core/hle/kernel/object.h" | ||||
| 
 | ||||
| namespace Kernel { | ||||
| 
 | ||||
| Object::Object(KernelSystem& kernel) : object_id{kernel.GenerateObjectID()} {} | ||||
| 
 | ||||
| Object::~Object() = default; | ||||
| 
 | ||||
| bool Object::IsWaitable() const { | ||||
|  |  | |||
|  | @ -6,13 +6,13 @@ | |||
| 
 | ||||
| #include <atomic> | ||||
| #include <string> | ||||
| 
 | ||||
| #include <boost/smart_ptr/intrusive_ptr.hpp> | ||||
| 
 | ||||
| #include "common/common_types.h" | ||||
| #include "core/hle/kernel/kernel.h" | ||||
| 
 | ||||
| namespace Kernel { | ||||
| 
 | ||||
| class KernelSystem; | ||||
| 
 | ||||
| using Handle = u32; | ||||
| 
 | ||||
| enum class HandleType : u32 { | ||||
|  | @ -37,14 +37,9 @@ enum { | |||
|     DEFAULT_STACK_SIZE = 0x4000, | ||||
| }; | ||||
| 
 | ||||
| enum class ResetType { | ||||
|     OneShot, | ||||
|     Sticky, | ||||
|     Pulse, | ||||
| }; | ||||
| 
 | ||||
| class Object : NonCopyable { | ||||
| public: | ||||
|     explicit Object(KernelSystem& kernel); | ||||
|     virtual ~Object(); | ||||
| 
 | ||||
|     /// Returns a unique identifier for the object. For debugging purposes only.
 | ||||
|  | @ -66,15 +61,12 @@ public: | |||
|      */ | ||||
|     bool IsWaitable() const; | ||||
| 
 | ||||
| public: | ||||
|     static std::atomic<u32> next_object_id; | ||||
| 
 | ||||
| private: | ||||
|     friend void intrusive_ptr_add_ref(Object*); | ||||
|     friend void intrusive_ptr_release(Object*); | ||||
| 
 | ||||
|     std::atomic<u32> ref_count{0}; | ||||
|     std::atomic<u32> object_id{next_object_id++}; | ||||
|     std::atomic<u32> object_id; | ||||
| }; | ||||
| 
 | ||||
| // Special functions used by boost::instrusive_ptr to do automatic ref-counting
 | ||||
|  | @ -88,9 +80,6 @@ inline void intrusive_ptr_release(Object* object) { | |||
|     } | ||||
| } | ||||
| 
 | ||||
| template <typename T> | ||||
| using SharedPtr = boost::intrusive_ptr<T>; | ||||
| 
 | ||||
| /**
 | ||||
|  * Attempts to downcast the given Object pointer to a pointer to T. | ||||
|  * @return Derived pointer to the object, or `nullptr` if `object` isn't of type T. | ||||
|  |  | |||
|  | @ -17,11 +17,8 @@ | |||
| 
 | ||||
| namespace Kernel { | ||||
| 
 | ||||
| // Lists all processes that exist in the current session.
 | ||||
| static std::vector<SharedPtr<Process>> process_list; | ||||
| 
 | ||||
| SharedPtr<CodeSet> CodeSet::Create(std::string name, u64 program_id) { | ||||
|     SharedPtr<CodeSet> codeset(new CodeSet); | ||||
| SharedPtr<CodeSet> KernelSystem::CreateCodeSet(std::string name, u64 program_id) { | ||||
|     SharedPtr<CodeSet> codeset(new CodeSet(*this)); | ||||
| 
 | ||||
|     codeset->name = std::move(name); | ||||
|     codeset->program_id = program_id; | ||||
|  | @ -29,18 +26,17 @@ SharedPtr<CodeSet> CodeSet::Create(std::string name, u64 program_id) { | |||
|     return codeset; | ||||
| } | ||||
| 
 | ||||
| CodeSet::CodeSet() {} | ||||
| CodeSet::CodeSet(KernelSystem& kernel) : Object(kernel) {} | ||||
| CodeSet::~CodeSet() {} | ||||
| 
 | ||||
| u32 Process::next_process_id; | ||||
| 
 | ||||
| SharedPtr<Process> Process::Create(SharedPtr<CodeSet> code_set) { | ||||
|     SharedPtr<Process> process(new Process); | ||||
| SharedPtr<Process> KernelSystem::CreateProcess(SharedPtr<CodeSet> code_set) { | ||||
|     SharedPtr<Process> process(new Process(*this)); | ||||
| 
 | ||||
|     process->codeset = std::move(code_set); | ||||
|     process->flags.raw = 0; | ||||
|     process->flags.memory_region.Assign(MemoryRegion::APPLICATION); | ||||
|     process->status = ProcessStatus::Created; | ||||
|     process->process_id = ++next_process_id; | ||||
| 
 | ||||
|     process_list.push_back(process); | ||||
|     return process; | ||||
|  | @ -119,17 +115,13 @@ void Process::ParseKernelCaps(const u32* kernel_caps, std::size_t len) { | |||
| } | ||||
| 
 | ||||
| void Process::Run(s32 main_thread_priority, u32 stack_size) { | ||||
|     memory_region = GetMemoryRegion(flags.memory_region); | ||||
|     memory_region = kernel.GetMemoryRegion(flags.memory_region); | ||||
| 
 | ||||
|     auto MapSegment = [&](CodeSet::Segment& segment, VMAPermission permissions, | ||||
|                           MemoryState memory_state) { | ||||
|         auto vma = vm_manager | ||||
|                        .MapMemoryBlock(segment.addr, codeset->memory, segment.offset, segment.size, | ||||
|                                        memory_state) | ||||
|                        .Unwrap(); | ||||
|         vm_manager.Reprotect(vma, permissions); | ||||
|         misc_memory_used += segment.size; | ||||
|         memory_region->used += segment.size; | ||||
|         HeapAllocate(segment.addr, segment.size, permissions, memory_state, true); | ||||
|         Memory::WriteBlock(*this, segment.addr, codeset->memory->data() + segment.offset, | ||||
|                            segment.size); | ||||
|     }; | ||||
| 
 | ||||
|     // Map CodeSet segments
 | ||||
|  | @ -138,16 +130,11 @@ void Process::Run(s32 main_thread_priority, u32 stack_size) { | |||
|     MapSegment(codeset->DataSegment(), VMAPermission::ReadWrite, MemoryState::Private); | ||||
| 
 | ||||
|     // Allocate and map stack
 | ||||
|     vm_manager | ||||
|         .MapMemoryBlock(Memory::HEAP_VADDR_END - stack_size, | ||||
|                         std::make_shared<std::vector<u8>>(stack_size, 0), 0, stack_size, | ||||
|                         MemoryState::Locked) | ||||
|         .Unwrap(); | ||||
|     misc_memory_used += stack_size; | ||||
|     memory_region->used += stack_size; | ||||
|     HeapAllocate(Memory::HEAP_VADDR_END - stack_size, stack_size, VMAPermission::ReadWrite, | ||||
|                  MemoryState::Locked, true); | ||||
| 
 | ||||
|     // Map special address mappings
 | ||||
|     MapSharedPages(vm_manager); | ||||
|     kernel.MapSharedPages(vm_manager); | ||||
|     for (const auto& mapping : address_mappings) { | ||||
|         HandleSpecialMapping(vm_manager, mapping); | ||||
|     } | ||||
|  | @ -155,7 +142,7 @@ void Process::Run(s32 main_thread_priority, u32 stack_size) { | |||
|     status = ProcessStatus::Running; | ||||
| 
 | ||||
|     vm_manager.LogLayout(Log::Level::Debug); | ||||
|     Kernel::SetupMainThread(codeset->entrypoint, main_thread_priority, this); | ||||
|     Kernel::SetupMainThread(kernel, codeset->entrypoint, main_thread_priority, this); | ||||
| } | ||||
| 
 | ||||
| VAddr Process::GetLinearHeapAreaAddress() const { | ||||
|  | @ -172,44 +159,55 @@ VAddr Process::GetLinearHeapLimit() const { | |||
|     return GetLinearHeapBase() + memory_region->size; | ||||
| } | ||||
| 
 | ||||
| ResultVal<VAddr> Process::HeapAllocate(VAddr target, u32 size, VMAPermission perms) { | ||||
| ResultVal<VAddr> Process::HeapAllocate(VAddr target, u32 size, VMAPermission perms, | ||||
|                                        MemoryState memory_state, bool skip_range_check) { | ||||
|     LOG_DEBUG(Kernel, "Allocate heap target={:08X}, size={:08X}", target, size); | ||||
|     if (target < Memory::HEAP_VADDR || target + size > Memory::HEAP_VADDR_END || | ||||
|         target + size < target) { | ||||
|         return ERR_INVALID_ADDRESS; | ||||
|         if (!skip_range_check) { | ||||
|             LOG_ERROR(Kernel, "Invalid heap address"); | ||||
|             return ERR_INVALID_ADDRESS; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     if (heap_memory == nullptr) { | ||||
|         // Initialize heap
 | ||||
|         heap_memory = std::make_shared<std::vector<u8>>(); | ||||
|         heap_start = heap_end = target; | ||||
|     auto vma = vm_manager.FindVMA(target); | ||||
|     if (vma->second.type != VMAType::Free || vma->second.base + vma->second.size < target + size) { | ||||
|         LOG_ERROR(Kernel, "Trying to allocate already allocated memory"); | ||||
|         return ERR_INVALID_ADDRESS_STATE; | ||||
|     } | ||||
| 
 | ||||
|     // If necessary, expand backing vector to cover new heap extents.
 | ||||
|     if (target < heap_start) { | ||||
|         heap_memory->insert(begin(*heap_memory), heap_start - target, 0); | ||||
|         heap_start = target; | ||||
|         vm_manager.RefreshMemoryBlockMappings(heap_memory.get()); | ||||
|     auto allocated_fcram = memory_region->HeapAllocate(size); | ||||
|     if (allocated_fcram.empty()) { | ||||
|         LOG_ERROR(Kernel, "Not enough space"); | ||||
|         return ERR_OUT_OF_HEAP_MEMORY; | ||||
|     } | ||||
|     if (target + size > heap_end) { | ||||
|         heap_memory->insert(end(*heap_memory), (target + size) - heap_end, 0); | ||||
|         heap_end = target + size; | ||||
|         vm_manager.RefreshMemoryBlockMappings(heap_memory.get()); | ||||
| 
 | ||||
|     // Maps heap block by block
 | ||||
|     VAddr interval_target = target; | ||||
|     for (const auto& interval : allocated_fcram) { | ||||
|         u32 interval_size = interval.upper() - interval.lower(); | ||||
|         LOG_DEBUG(Kernel, "Allocated FCRAM region lower={:08X}, upper={:08X}", interval.lower(), | ||||
|                   interval.upper()); | ||||
|         std::fill(Memory::fcram.begin() + interval.lower(), | ||||
|                   Memory::fcram.begin() + interval.upper(), 0); | ||||
|         auto vma = vm_manager.MapBackingMemory( | ||||
|             interval_target, Memory::fcram.data() + interval.lower(), interval_size, memory_state); | ||||
|         ASSERT(vma.Succeeded()); | ||||
|         vm_manager.Reprotect(vma.Unwrap(), perms); | ||||
|         interval_target += interval_size; | ||||
|     } | ||||
|     ASSERT(heap_end - heap_start == heap_memory->size()); | ||||
| 
 | ||||
|     CASCADE_RESULT(auto vma, vm_manager.MapMemoryBlock(target, heap_memory, target - heap_start, | ||||
|                                                        size, MemoryState::Private)); | ||||
|     vm_manager.Reprotect(vma, perms); | ||||
|     memory_used += size; | ||||
|     resource_limit->current_commit += size; | ||||
| 
 | ||||
|     heap_used += size; | ||||
|     memory_region->used += size; | ||||
| 
 | ||||
|     return MakeResult<VAddr>(heap_end - size); | ||||
|     return MakeResult<VAddr>(target); | ||||
| } | ||||
| 
 | ||||
| ResultCode Process::HeapFree(VAddr target, u32 size) { | ||||
|     LOG_DEBUG(Kernel, "Free heap target={:08X}, size={:08X}", target, size); | ||||
|     if (target < Memory::HEAP_VADDR || target + size > Memory::HEAP_VADDR_END || | ||||
|         target + size < target) { | ||||
|         LOG_ERROR(Kernel, "Invalid heap address"); | ||||
|         return ERR_INVALID_ADDRESS; | ||||
|     } | ||||
| 
 | ||||
|  | @ -217,59 +215,72 @@ ResultCode Process::HeapFree(VAddr target, u32 size) { | |||
|         return RESULT_SUCCESS; | ||||
|     } | ||||
| 
 | ||||
|     ResultCode result = vm_manager.UnmapRange(target, size); | ||||
|     if (result.IsError()) | ||||
|         return result; | ||||
|     // Free heaps block by block
 | ||||
|     CASCADE_RESULT(auto backing_blocks, vm_manager.GetBackingBlocksForRange(target, size)); | ||||
|     for (const auto [backing_memory, block_size] : backing_blocks) { | ||||
|         memory_region->Free(Memory::GetFCRAMOffset(backing_memory), block_size); | ||||
|     } | ||||
| 
 | ||||
|     heap_used -= size; | ||||
|     memory_region->used -= size; | ||||
|     ResultCode result = vm_manager.UnmapRange(target, size); | ||||
|     ASSERT(result.IsSuccess()); | ||||
| 
 | ||||
|     memory_used -= size; | ||||
|     resource_limit->current_commit -= size; | ||||
| 
 | ||||
|     return RESULT_SUCCESS; | ||||
| } | ||||
| 
 | ||||
| ResultVal<VAddr> Process::LinearAllocate(VAddr target, u32 size, VMAPermission perms) { | ||||
|     auto& linheap_memory = memory_region->linear_heap_memory; | ||||
| 
 | ||||
|     VAddr heap_end = GetLinearHeapBase() + (u32)linheap_memory->size(); | ||||
|     // Games and homebrew only ever seem to pass 0 here (which lets the kernel decide the address),
 | ||||
|     // but explicit addresses are also accepted and respected.
 | ||||
|     LOG_DEBUG(Kernel, "Allocate linear heap target={:08X}, size={:08X}", target, size); | ||||
|     u32 physical_offset; | ||||
|     if (target == 0) { | ||||
|         target = heap_end; | ||||
|         auto offset = memory_region->LinearAllocate(size); | ||||
|         if (!offset) { | ||||
|             LOG_ERROR(Kernel, "Not enough space"); | ||||
|             return ERR_OUT_OF_HEAP_MEMORY; | ||||
|         } | ||||
|         physical_offset = *offset; | ||||
|         target = physical_offset + GetLinearHeapAreaAddress(); | ||||
|     } else { | ||||
|         if (target < GetLinearHeapBase() || target + size > GetLinearHeapLimit() || | ||||
|             target + size < target) { | ||||
|             LOG_ERROR(Kernel, "Invalid linear heap address"); | ||||
|             return ERR_INVALID_ADDRESS; | ||||
|         } | ||||
| 
 | ||||
|         // Kernel would crash/return error when target doesn't meet some requirement.
 | ||||
|         // It seems that target is required to follow immediately after the allocated linear heap,
 | ||||
|         // or cover the entire hole if there is any.
 | ||||
|         // Right now we just ignore these checks because they are still unclear. Further more,
 | ||||
|         // games and homebrew only ever seem to pass target = 0 here (which lets the kernel decide
 | ||||
|         // the address), so this not important.
 | ||||
| 
 | ||||
|         physical_offset = target - GetLinearHeapAreaAddress(); // relative to FCRAM
 | ||||
|         if (!memory_region->LinearAllocate(physical_offset, size)) { | ||||
|             LOG_ERROR(Kernel, "Trying to allocate already allocated memory"); | ||||
|             return ERR_INVALID_ADDRESS_STATE; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     if (target < GetLinearHeapBase() || target + size > GetLinearHeapLimit() || target > heap_end || | ||||
|         target + size < target) { | ||||
|     u8* backing_memory = Memory::fcram.data() + physical_offset; | ||||
| 
 | ||||
|         return ERR_INVALID_ADDRESS; | ||||
|     } | ||||
|     std::fill(backing_memory, backing_memory + size, 0); | ||||
|     auto vma = vm_manager.MapBackingMemory(target, backing_memory, size, MemoryState::Continuous); | ||||
|     ASSERT(vma.Succeeded()); | ||||
|     vm_manager.Reprotect(vma.Unwrap(), perms); | ||||
| 
 | ||||
|     // Expansion of the linear heap is only allowed if you do an allocation immediately at its
 | ||||
|     // end. It's possible to free gaps in the middle of the heap and then reallocate them later,
 | ||||
|     // but expansions are only allowed at the end.
 | ||||
|     if (target == heap_end) { | ||||
|         linheap_memory->insert(linheap_memory->end(), size, 0); | ||||
|         vm_manager.RefreshMemoryBlockMappings(linheap_memory.get()); | ||||
|     } | ||||
| 
 | ||||
|     // TODO(yuriks): As is, this lets processes map memory allocated by other processes from the
 | ||||
|     // same region. It is unknown if or how the 3DS kernel checks against this.
 | ||||
|     std::size_t offset = target - GetLinearHeapBase(); | ||||
|     CASCADE_RESULT(auto vma, vm_manager.MapMemoryBlock(target, linheap_memory, offset, size, | ||||
|                                                        MemoryState::Continuous)); | ||||
|     vm_manager.Reprotect(vma, perms); | ||||
| 
 | ||||
|     linear_heap_used += size; | ||||
|     memory_region->used += size; | ||||
|     memory_used += size; | ||||
|     resource_limit->current_commit += size; | ||||
| 
 | ||||
|     LOG_DEBUG(Kernel, "Allocated at target={:08X}", target); | ||||
|     return MakeResult<VAddr>(target); | ||||
| } | ||||
| 
 | ||||
| ResultCode Process::LinearFree(VAddr target, u32 size) { | ||||
|     auto& linheap_memory = memory_region->linear_heap_memory; | ||||
| 
 | ||||
|     LOG_DEBUG(Kernel, "Free linear heap target={:08X}, size={:08X}", target, size); | ||||
|     if (target < GetLinearHeapBase() || target + size > GetLinearHeapLimit() || | ||||
|         target + size < target) { | ||||
| 
 | ||||
|         LOG_ERROR(Kernel, "Invalid linear heap address"); | ||||
|         return ERR_INVALID_ADDRESS; | ||||
|     } | ||||
| 
 | ||||
|  | @ -277,41 +288,81 @@ ResultCode Process::LinearFree(VAddr target, u32 size) { | |||
|         return RESULT_SUCCESS; | ||||
|     } | ||||
| 
 | ||||
|     VAddr heap_end = GetLinearHeapBase() + (u32)linheap_memory->size(); | ||||
|     if (target + size > heap_end) { | ||||
|         return ERR_INVALID_ADDRESS_STATE; | ||||
|     } | ||||
| 
 | ||||
|     ResultCode result = vm_manager.UnmapRange(target, size); | ||||
|     if (result.IsError()) | ||||
|     if (result.IsError()) { | ||||
|         LOG_ERROR(Kernel, "Trying to free already freed memory"); | ||||
|         return result; | ||||
| 
 | ||||
|     linear_heap_used -= size; | ||||
|     memory_region->used -= size; | ||||
| 
 | ||||
|     if (target + size == heap_end) { | ||||
|         // End of linear heap has been freed, so check what's the last allocated block in it and
 | ||||
|         // reduce the size.
 | ||||
|         auto vma = vm_manager.FindVMA(target); | ||||
|         ASSERT(vma != vm_manager.vma_map.end()); | ||||
|         ASSERT(vma->second.type == VMAType::Free); | ||||
|         VAddr new_end = vma->second.base; | ||||
|         if (new_end >= GetLinearHeapBase()) { | ||||
|             linheap_memory->resize(new_end - GetLinearHeapBase()); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     memory_used -= size; | ||||
|     resource_limit->current_commit -= size; | ||||
| 
 | ||||
|     u32 physical_offset = target - GetLinearHeapAreaAddress(); // relative to FCRAM
 | ||||
|     memory_region->Free(physical_offset, size); | ||||
| 
 | ||||
|     return RESULT_SUCCESS; | ||||
| } | ||||
| 
 | ||||
| Kernel::Process::Process() {} | ||||
| Kernel::Process::~Process() {} | ||||
| ResultCode Process::Map(VAddr target, VAddr source, u32 size, VMAPermission perms) { | ||||
|     LOG_DEBUG(Kernel, "Map memory target={:08X}, source={:08X}, size={:08X}, perms={:08X}", target, | ||||
|               source, size, static_cast<u8>(perms)); | ||||
|     if (source < Memory::HEAP_VADDR || source + size > Memory::HEAP_VADDR_END || | ||||
|         source + size < source) { | ||||
|         LOG_ERROR(Kernel, "Invalid source address"); | ||||
|         return ERR_INVALID_ADDRESS; | ||||
|     } | ||||
| 
 | ||||
| void ClearProcessList() { | ||||
|     process_list.clear(); | ||||
|     // TODO(wwylele): check target address range. Is it also restricted to heap region?
 | ||||
| 
 | ||||
|     auto vma = vm_manager.FindVMA(target); | ||||
|     if (vma->second.type != VMAType::Free || vma->second.base + vma->second.size < target + size) { | ||||
|         LOG_ERROR(Kernel, "Trying to map to already allocated memory"); | ||||
|         return ERR_INVALID_ADDRESS_STATE; | ||||
|     } | ||||
| 
 | ||||
|     // Mark source region as Aliased
 | ||||
|     CASCADE_CODE(vm_manager.ChangeMemoryState(source, size, MemoryState::Private, | ||||
|                                               VMAPermission::ReadWrite, MemoryState::Aliased, | ||||
|                                               VMAPermission::ReadWrite)); | ||||
| 
 | ||||
|     CASCADE_RESULT(auto backing_blocks, vm_manager.GetBackingBlocksForRange(source, size)); | ||||
|     VAddr interval_target = target; | ||||
|     for (const auto [backing_memory, block_size] : backing_blocks) { | ||||
|         auto target_vma = vm_manager.MapBackingMemory(interval_target, backing_memory, block_size, | ||||
|                                                       MemoryState::Alias); | ||||
|         interval_target += block_size; | ||||
|     } | ||||
| 
 | ||||
|     return RESULT_SUCCESS; | ||||
| } | ||||
| ResultCode Process::Unmap(VAddr target, VAddr source, u32 size, VMAPermission perms) { | ||||
|     LOG_DEBUG(Kernel, "Unmap memory target={:08X}, source={:08X}, size={:08X}, perms={:08X}", | ||||
|               target, source, size, static_cast<u8>(perms)); | ||||
|     if (source < Memory::HEAP_VADDR || source + size > Memory::HEAP_VADDR_END || | ||||
|         source + size < source) { | ||||
|         LOG_ERROR(Kernel, "Invalid source address"); | ||||
|         return ERR_INVALID_ADDRESS; | ||||
|     } | ||||
| 
 | ||||
|     // TODO(wwylele): check target address range. Is it also restricted to heap region?
 | ||||
| 
 | ||||
|     // TODO(wwylele): check that the source and the target are actually a pair created by Map
 | ||||
|     // Should return error 0xD8E007F5 in this case
 | ||||
| 
 | ||||
|     CASCADE_CODE(vm_manager.UnmapRange(target, size)); | ||||
| 
 | ||||
|     // Change back source region state. Note that the permission is reprotected according to param
 | ||||
|     CASCADE_CODE(vm_manager.ChangeMemoryState(source, size, MemoryState::Aliased, | ||||
|                                               VMAPermission::None, MemoryState::Private, perms)); | ||||
| 
 | ||||
|     return RESULT_SUCCESS; | ||||
| } | ||||
| 
 | ||||
| SharedPtr<Process> GetProcessById(u32 process_id) { | ||||
| Kernel::Process::Process(KernelSystem& kernel) | ||||
|     : Object(kernel), handle_table(kernel), kernel(kernel) {} | ||||
| Kernel::Process::~Process() {} | ||||
| 
 | ||||
| SharedPtr<Process> KernelSystem::GetProcessById(u32 process_id) const { | ||||
|     auto itr = std::find_if( | ||||
|         process_list.begin(), process_list.end(), | ||||
|         [&](const SharedPtr<Process>& process) { return process->process_id == process_id; }); | ||||
|  | @ -321,6 +372,4 @@ SharedPtr<Process> GetProcessById(u32 process_id) { | |||
| 
 | ||||
|     return *itr; | ||||
| } | ||||
| 
 | ||||
| SharedPtr<Process> g_current_process; | ||||
| } // namespace Kernel
 | ||||
|  |  | |||
|  | @ -13,6 +13,7 @@ | |||
| #include <boost/container/static_vector.hpp> | ||||
| #include "common/bit_field.h" | ||||
| #include "common/common_types.h" | ||||
| #include "core/hle/kernel/handle_table.h" | ||||
| #include "core/hle/kernel/object.h" | ||||
| #include "core/hle/kernel/vm_manager.h" | ||||
| 
 | ||||
|  | @ -26,12 +27,6 @@ struct AddressMapping { | |||
|     bool unk_flag; | ||||
| }; | ||||
| 
 | ||||
| enum class MemoryRegion : u16 { | ||||
|     APPLICATION = 1, | ||||
|     SYSTEM = 2, | ||||
|     BASE = 3, | ||||
| }; | ||||
| 
 | ||||
| union ProcessFlags { | ||||
|     u16 raw; | ||||
| 
 | ||||
|  | @ -55,15 +50,14 @@ enum class ProcessStatus { Created, Running, Exited }; | |||
| class ResourceLimit; | ||||
| struct MemoryRegionInfo; | ||||
| 
 | ||||
| struct CodeSet final : public Object { | ||||
| class CodeSet final : public Object { | ||||
| public: | ||||
|     struct Segment { | ||||
|         std::size_t offset = 0; | ||||
|         VAddr addr = 0; | ||||
|         u32 size = 0; | ||||
|     }; | ||||
| 
 | ||||
|     static SharedPtr<CodeSet> Create(std::string name, u64 program_id); | ||||
| 
 | ||||
|     std::string GetTypeName() const override { | ||||
|         return "CodeSet"; | ||||
|     } | ||||
|  | @ -111,14 +105,14 @@ struct CodeSet final : public Object { | |||
|     u64 program_id; | ||||
| 
 | ||||
| private: | ||||
|     CodeSet(); | ||||
|     explicit CodeSet(KernelSystem& kernel); | ||||
|     ~CodeSet() override; | ||||
| 
 | ||||
|     friend class KernelSystem; | ||||
| }; | ||||
| 
 | ||||
| class Process final : public Object { | ||||
| public: | ||||
|     static SharedPtr<Process> Create(SharedPtr<CodeSet> code_set); | ||||
| 
 | ||||
|     std::string GetTypeName() const override { | ||||
|         return "Process"; | ||||
|     } | ||||
|  | @ -131,7 +125,7 @@ public: | |||
|         return HANDLE_TYPE; | ||||
|     } | ||||
| 
 | ||||
|     static u32 next_process_id; | ||||
|     HandleTable handle_table; | ||||
| 
 | ||||
|     SharedPtr<CodeSet> codeset; | ||||
|     /// Resource limit descriptor for this process
 | ||||
|  | @ -153,7 +147,7 @@ public: | |||
|     ProcessStatus status; | ||||
| 
 | ||||
|     /// The id of this process
 | ||||
|     u32 process_id = next_process_id++; | ||||
|     u32 process_id; | ||||
| 
 | ||||
|     /**
 | ||||
|      * Parses a list of kernel capability descriptors (as found in the ExHeader) and applies them | ||||
|  | @ -171,15 +165,7 @@ public: | |||
| 
 | ||||
|     VMManager vm_manager; | ||||
| 
 | ||||
|     // Memory used to back the allocations in the regular heap. A single vector is used to cover
 | ||||
|     // the entire virtual address space extents that bound the allocations, including any holes.
 | ||||
|     // This makes deallocation and reallocation of holes fast and keeps process memory contiguous
 | ||||
|     // in the emulator address space, allowing Memory::GetPointer to be reasonably safe.
 | ||||
|     std::shared_ptr<std::vector<u8>> heap_memory; | ||||
|     // The left/right bounds of the address space covered by heap_memory.
 | ||||
|     VAddr heap_start = 0, heap_end = 0; | ||||
| 
 | ||||
|     u32 heap_used = 0, linear_heap_used = 0, misc_memory_used = 0; | ||||
|     u32 memory_used = 0; | ||||
| 
 | ||||
|     MemoryRegionInfo* memory_region = nullptr; | ||||
| 
 | ||||
|  | @ -194,21 +180,22 @@ public: | |||
|     VAddr GetLinearHeapBase() const; | ||||
|     VAddr GetLinearHeapLimit() const; | ||||
| 
 | ||||
|     ResultVal<VAddr> HeapAllocate(VAddr target, u32 size, VMAPermission perms); | ||||
|     ResultVal<VAddr> HeapAllocate(VAddr target, u32 size, VMAPermission perms, | ||||
|                                   MemoryState memory_state = MemoryState::Private, | ||||
|                                   bool skip_range_check = false); | ||||
|     ResultCode HeapFree(VAddr target, u32 size); | ||||
| 
 | ||||
|     ResultVal<VAddr> LinearAllocate(VAddr target, u32 size, VMAPermission perms); | ||||
|     ResultCode LinearFree(VAddr target, u32 size); | ||||
| 
 | ||||
|     ResultCode Map(VAddr target, VAddr source, u32 size, VMAPermission perms); | ||||
|     ResultCode Unmap(VAddr target, VAddr source, u32 size, VMAPermission perms); | ||||
| 
 | ||||
| private: | ||||
|     Process(); | ||||
|     explicit Process(Kernel::KernelSystem& kernel); | ||||
|     ~Process() override; | ||||
| 
 | ||||
|     friend class KernelSystem; | ||||
|     KernelSystem& kernel; | ||||
| }; | ||||
| 
 | ||||
| void ClearProcessList(); | ||||
| 
 | ||||
| /// Retrieves a process from the current list of processes.
 | ||||
| SharedPtr<Process> GetProcessById(u32 process_id); | ||||
| 
 | ||||
| extern SharedPtr<Process> g_current_process; | ||||
| } // namespace Kernel
 | ||||
|  |  | |||
|  | @ -9,19 +9,17 @@ | |||
| 
 | ||||
| namespace Kernel { | ||||
| 
 | ||||
| static SharedPtr<ResourceLimit> resource_limits[4]; | ||||
| 
 | ||||
| ResourceLimit::ResourceLimit() {} | ||||
| ResourceLimit::ResourceLimit(KernelSystem& kernel) : Object(kernel) {} | ||||
| ResourceLimit::~ResourceLimit() {} | ||||
| 
 | ||||
| SharedPtr<ResourceLimit> ResourceLimit::Create(std::string name) { | ||||
|     SharedPtr<ResourceLimit> resource_limit(new ResourceLimit); | ||||
| SharedPtr<ResourceLimit> ResourceLimit::Create(KernelSystem& kernel, std::string name) { | ||||
|     SharedPtr<ResourceLimit> resource_limit(new ResourceLimit(kernel)); | ||||
| 
 | ||||
|     resource_limit->name = std::move(name); | ||||
|     return resource_limit; | ||||
| } | ||||
| 
 | ||||
| SharedPtr<ResourceLimit> ResourceLimit::GetForCategory(ResourceLimitCategory category) { | ||||
| SharedPtr<ResourceLimit> ResourceLimitList::GetForCategory(ResourceLimitCategory category) { | ||||
|     switch (category) { | ||||
|     case ResourceLimitCategory::APPLICATION: | ||||
|     case ResourceLimitCategory::SYS_APPLET: | ||||
|  | @ -90,10 +88,10 @@ u32 ResourceLimit::GetMaxResourceValue(u32 resource) const { | |||
|     } | ||||
| } | ||||
| 
 | ||||
| void ResourceLimitsInit() { | ||||
| ResourceLimitList::ResourceLimitList(KernelSystem& kernel) { | ||||
|     // Create the four resource limits that the system uses
 | ||||
|     // Create the APPLICATION resource limit
 | ||||
|     SharedPtr<ResourceLimit> resource_limit = ResourceLimit::Create("Applications"); | ||||
|     SharedPtr<ResourceLimit> resource_limit = ResourceLimit::Create(kernel, "Applications"); | ||||
|     resource_limit->max_priority = 0x18; | ||||
|     resource_limit->max_commit = 0x4000000; | ||||
|     resource_limit->max_threads = 0x20; | ||||
|  | @ -107,7 +105,7 @@ void ResourceLimitsInit() { | |||
|     resource_limits[static_cast<u8>(ResourceLimitCategory::APPLICATION)] = resource_limit; | ||||
| 
 | ||||
|     // Create the SYS_APPLET resource limit
 | ||||
|     resource_limit = ResourceLimit::Create("System Applets"); | ||||
|     resource_limit = ResourceLimit::Create(kernel, "System Applets"); | ||||
|     resource_limit->max_priority = 0x4; | ||||
|     resource_limit->max_commit = 0x5E00000; | ||||
|     resource_limit->max_threads = 0x1D; | ||||
|  | @ -121,7 +119,7 @@ void ResourceLimitsInit() { | |||
|     resource_limits[static_cast<u8>(ResourceLimitCategory::SYS_APPLET)] = resource_limit; | ||||
| 
 | ||||
|     // Create the LIB_APPLET resource limit
 | ||||
|     resource_limit = ResourceLimit::Create("Library Applets"); | ||||
|     resource_limit = ResourceLimit::Create(kernel, "Library Applets"); | ||||
|     resource_limit->max_priority = 0x4; | ||||
|     resource_limit->max_commit = 0x600000; | ||||
|     resource_limit->max_threads = 0xE; | ||||
|  | @ -135,7 +133,7 @@ void ResourceLimitsInit() { | |||
|     resource_limits[static_cast<u8>(ResourceLimitCategory::LIB_APPLET)] = resource_limit; | ||||
| 
 | ||||
|     // Create the OTHER resource limit
 | ||||
|     resource_limit = ResourceLimit::Create("Others"); | ||||
|     resource_limit = ResourceLimit::Create(kernel, "Others"); | ||||
|     resource_limit->max_priority = 0x4; | ||||
|     resource_limit->max_commit = 0x2180000; | ||||
|     resource_limit->max_threads = 0xE1; | ||||
|  | @ -149,6 +147,6 @@ void ResourceLimitsInit() { | |||
|     resource_limits[static_cast<u8>(ResourceLimitCategory::OTHER)] = resource_limit; | ||||
| } | ||||
| 
 | ||||
| void ResourceLimitsShutdown() {} | ||||
| ResourceLimitList::~ResourceLimitList() = default; | ||||
| 
 | ||||
| } // namespace Kernel
 | ||||
|  |  | |||
|  | @ -4,6 +4,7 @@ | |||
| 
 | ||||
| #pragma once | ||||
| 
 | ||||
| #include <array> | ||||
| #include "common/common_types.h" | ||||
| #include "core/hle/kernel/object.h" | ||||
| 
 | ||||
|  | @ -34,14 +35,7 @@ public: | |||
|     /**
 | ||||
|      * Creates a resource limit object. | ||||
|      */ | ||||
|     static SharedPtr<ResourceLimit> Create(std::string name = "Unknown"); | ||||
| 
 | ||||
|     /**
 | ||||
|      * Retrieves the resource limit associated with the specified resource limit category. | ||||
|      * @param category The resource limit category | ||||
|      * @returns The resource limit associated with the category | ||||
|      */ | ||||
|     static SharedPtr<ResourceLimit> GetForCategory(ResourceLimitCategory category); | ||||
|     static SharedPtr<ResourceLimit> Create(KernelSystem& kernel, std::string name = "Unknown"); | ||||
| 
 | ||||
|     std::string GetTypeName() const override { | ||||
|         return "ResourceLimit"; | ||||
|  | @ -113,14 +107,24 @@ public: | |||
|     s32 current_cpu_time = 0; | ||||
| 
 | ||||
| private: | ||||
|     ResourceLimit(); | ||||
|     explicit ResourceLimit(KernelSystem& kernel); | ||||
|     ~ResourceLimit() override; | ||||
| }; | ||||
| 
 | ||||
| /// Initializes the resource limits
 | ||||
| void ResourceLimitsInit(); | ||||
| class ResourceLimitList { | ||||
| public: | ||||
|     explicit ResourceLimitList(KernelSystem& kernel); | ||||
|     ~ResourceLimitList(); | ||||
| 
 | ||||
| // Destroys the resource limits
 | ||||
| void ResourceLimitsShutdown(); | ||||
|     /**
 | ||||
|      * Retrieves the resource limit associated with the specified resource limit category. | ||||
|      * @param category The resource limit category | ||||
|      * @returns The resource limit associated with the category | ||||
|      */ | ||||
|     SharedPtr<ResourceLimit> GetForCategory(ResourceLimitCategory category); | ||||
| 
 | ||||
| private: | ||||
|     std::array<SharedPtr<ResourceLimit>, 4> resource_limits; | ||||
| }; | ||||
| 
 | ||||
| } // namespace Kernel
 | ||||
|  |  | |||
|  | @ -10,16 +10,16 @@ | |||
| 
 | ||||
| namespace Kernel { | ||||
| 
 | ||||
| Semaphore::Semaphore() {} | ||||
| Semaphore::Semaphore(KernelSystem& kernel) : WaitObject(kernel) {} | ||||
| Semaphore::~Semaphore() {} | ||||
| 
 | ||||
| ResultVal<SharedPtr<Semaphore>> Semaphore::Create(s32 initial_count, s32 max_count, | ||||
|                                                   std::string name) { | ||||
| ResultVal<SharedPtr<Semaphore>> KernelSystem::CreateSemaphore(s32 initial_count, s32 max_count, | ||||
|                                                               std::string name) { | ||||
| 
 | ||||
|     if (initial_count > max_count) | ||||
|         return ERR_INVALID_COMBINATION_KERNEL; | ||||
| 
 | ||||
|     SharedPtr<Semaphore> semaphore(new Semaphore); | ||||
|     SharedPtr<Semaphore> semaphore(new Semaphore(*this)); | ||||
| 
 | ||||
|     // When the semaphore is created, some slots are reserved for other threads,
 | ||||
|     // and the rest is reserved for the caller thread
 | ||||
|  |  | |||
|  | @ -15,16 +15,6 @@ namespace Kernel { | |||
| 
 | ||||
| class Semaphore final : public WaitObject { | ||||
| public: | ||||
|     /**
 | ||||
|      * Creates a semaphore. | ||||
|      * @param initial_count Number of slots reserved for other threads | ||||
|      * @param max_count Maximum number of slots the semaphore can have | ||||
|      * @param name Optional name of semaphore | ||||
|      * @return The created semaphore | ||||
|      */ | ||||
|     static ResultVal<SharedPtr<Semaphore>> Create(s32 initial_count, s32 max_count, | ||||
|                                                   std::string name = "Unknown"); | ||||
| 
 | ||||
|     std::string GetTypeName() const override { | ||||
|         return "Semaphore"; | ||||
|     } | ||||
|  | @ -52,8 +42,10 @@ public: | |||
|     ResultVal<s32> Release(s32 release_count); | ||||
| 
 | ||||
| private: | ||||
|     Semaphore(); | ||||
|     explicit Semaphore(KernelSystem& kernel); | ||||
|     ~Semaphore() override; | ||||
| 
 | ||||
|     friend class KernelSystem; | ||||
| }; | ||||
| 
 | ||||
| } // namespace Kernel
 | ||||
|  |  | |||
|  | @ -13,7 +13,7 @@ | |||
| 
 | ||||
| namespace Kernel { | ||||
| 
 | ||||
| ServerPort::ServerPort() {} | ||||
| ServerPort::ServerPort(KernelSystem& kernel) : WaitObject(kernel) {} | ||||
| ServerPort::~ServerPort() {} | ||||
| 
 | ||||
| ResultVal<SharedPtr<ServerSession>> ServerPort::Accept() { | ||||
|  | @ -35,11 +35,11 @@ void ServerPort::Acquire(Thread* thread) { | |||
|     ASSERT_MSG(!ShouldWait(thread), "object unavailable!"); | ||||
| } | ||||
| 
 | ||||
| std::tuple<SharedPtr<ServerPort>, SharedPtr<ClientPort>> ServerPort::CreatePortPair( | ||||
| std::tuple<SharedPtr<ServerPort>, SharedPtr<ClientPort>> KernelSystem::CreatePortPair( | ||||
|     u32 max_sessions, std::string name) { | ||||
| 
 | ||||
|     SharedPtr<ServerPort> server_port(new ServerPort); | ||||
|     SharedPtr<ClientPort> client_port(new ClientPort); | ||||
|     SharedPtr<ServerPort> server_port(new ServerPort(*this)); | ||||
|     SharedPtr<ClientPort> client_port(new ClientPort(*this)); | ||||
| 
 | ||||
|     server_port->name = name + "_Server"; | ||||
|     client_port->name = name + "_Client"; | ||||
|  |  | |||
|  | @ -20,16 +20,6 @@ class SessionRequestHandler; | |||
| 
 | ||||
| class ServerPort final : public WaitObject { | ||||
| 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 | ||||
|      * @return The created port tuple | ||||
|      */ | ||||
|     static std::tuple<SharedPtr<ServerPort>, SharedPtr<ClientPort>> CreatePortPair( | ||||
|         u32 max_sessions, std::string name = "UnknownPort"); | ||||
| 
 | ||||
|     std::string GetTypeName() const override { | ||||
|         return "ServerPort"; | ||||
|     } | ||||
|  | @ -69,8 +59,10 @@ public: | |||
|     void Acquire(Thread* thread) override; | ||||
| 
 | ||||
| private: | ||||
|     ServerPort(); | ||||
|     explicit ServerPort(KernelSystem& kernel); | ||||
|     ~ServerPort() override; | ||||
| 
 | ||||
|     friend class KernelSystem; | ||||
| }; | ||||
| 
 | ||||
| } // namespace Kernel
 | ||||
|  |  | |||
|  | @ -13,7 +13,7 @@ | |||
| 
 | ||||
| namespace Kernel { | ||||
| 
 | ||||
| ServerSession::ServerSession() = default; | ||||
| ServerSession::ServerSession(KernelSystem& kernel) : WaitObject(kernel) {} | ||||
| ServerSession::~ServerSession() { | ||||
|     // This destructor will be called automatically when the last ServerSession handle is closed by
 | ||||
|     // the emulated application.
 | ||||
|  | @ -28,8 +28,8 @@ ServerSession::~ServerSession() { | |||
|     parent->server = nullptr; | ||||
| } | ||||
| 
 | ||||
| ResultVal<SharedPtr<ServerSession>> ServerSession::Create(std::string name) { | ||||
|     SharedPtr<ServerSession> server_session(new ServerSession); | ||||
| ResultVal<SharedPtr<ServerSession>> ServerSession::Create(KernelSystem& kernel, std::string name) { | ||||
|     SharedPtr<ServerSession> server_session(new ServerSession(kernel)); | ||||
| 
 | ||||
|     server_session->name = std::move(name); | ||||
|     server_session->parent = nullptr; | ||||
|  | @ -100,10 +100,10 @@ ResultCode ServerSession::HandleSyncRequest(SharedPtr<Thread> thread) { | |||
|     return RESULT_SUCCESS; | ||||
| } | ||||
| 
 | ||||
| ServerSession::SessionPair ServerSession::CreateSessionPair(const std::string& name, | ||||
|                                                             SharedPtr<ClientPort> port) { | ||||
|     auto server_session = ServerSession::Create(name + "_Server").Unwrap(); | ||||
|     SharedPtr<ClientSession> client_session(new ClientSession); | ||||
| std::tuple<SharedPtr<ServerSession>, SharedPtr<ClientSession>> KernelSystem::CreateSessionPair( | ||||
|     const std::string& name, SharedPtr<ClientPort> port) { | ||||
|     auto server_session = ServerSession::Create(*this, name + "_Server").Unwrap(); | ||||
|     SharedPtr<ClientSession> client_session(new ClientSession(*this)); | ||||
|     client_session->name = name + "_Client"; | ||||
| 
 | ||||
|     std::shared_ptr<Session> parent(new Session); | ||||
|  |  | |||
|  | @ -48,17 +48,6 @@ public: | |||
|         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. | ||||
|      * @param client_port Optional The ClientPort that spawned this session. | ||||
|      * @return The created session tuple | ||||
|      */ | ||||
|     static SessionPair CreateSessionPair(const std::string& name = "Unknown", | ||||
|                                          SharedPtr<ClientPort> client_port = nullptr); | ||||
| 
 | ||||
|     /**
 | ||||
|      * Sets the HLE handler for the session. This handler will be called to service IPC requests | ||||
|      * instead of the regular IPC machinery. (The regular IPC machinery is currently not | ||||
|  | @ -95,16 +84,20 @@ public: | |||
|     SharedPtr<Thread> currently_handling; | ||||
| 
 | ||||
| private: | ||||
|     ServerSession(); | ||||
|     explicit ServerSession(KernelSystem& kernel); | ||||
|     ~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 kernel The kernel instance to create the server session on | ||||
|      * @param name Optional name of the server session. | ||||
|      * @return The created server session | ||||
|      */ | ||||
|     static ResultVal<SharedPtr<ServerSession>> Create(std::string name = "Unknown"); | ||||
|     static ResultVal<SharedPtr<ServerSession>> Create(KernelSystem& kernel, | ||||
|                                                       std::string name = "Unknown"); | ||||
| 
 | ||||
|     friend class KernelSystem; | ||||
| }; | ||||
| 
 | ||||
| } // namespace Kernel
 | ||||
|  |  | |||
|  | @ -11,14 +11,20 @@ | |||
| 
 | ||||
| namespace Kernel { | ||||
| 
 | ||||
| SharedMemory::SharedMemory() {} | ||||
| SharedMemory::~SharedMemory() {} | ||||
| SharedMemory::SharedMemory(KernelSystem& kernel) : Object(kernel), kernel(kernel) {} | ||||
| SharedMemory::~SharedMemory() { | ||||
|     for (const auto& interval : holding_memory) { | ||||
|         kernel.GetMemoryRegion(MemoryRegion::SYSTEM) | ||||
|             ->Free(interval.lower(), interval.upper() - interval.lower()); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| SharedPtr<SharedMemory> SharedMemory::Create(SharedPtr<Process> owner_process, u32 size, | ||||
|                                              MemoryPermission permissions, | ||||
|                                              MemoryPermission other_permissions, VAddr address, | ||||
|                                              MemoryRegion region, std::string name) { | ||||
|     SharedPtr<SharedMemory> shared_memory(new SharedMemory); | ||||
| SharedPtr<SharedMemory> KernelSystem::CreateSharedMemory(Process* owner_process, u32 size, | ||||
|                                                          MemoryPermission permissions, | ||||
|                                                          MemoryPermission other_permissions, | ||||
|                                                          VAddr address, MemoryRegion region, | ||||
|                                                          std::string name) { | ||||
|     SharedPtr<SharedMemory> shared_memory(new SharedMemory(*this)); | ||||
| 
 | ||||
|     shared_memory->owner_process = owner_process; | ||||
|     shared_memory->name = std::move(name); | ||||
|  | @ -30,64 +36,53 @@ SharedPtr<SharedMemory> SharedMemory::Create(SharedPtr<Process> owner_process, u | |||
|         // We need to allocate a block from the Linear Heap ourselves.
 | ||||
|         // We'll manually allocate some memory from the linear heap in the specified region.
 | ||||
|         MemoryRegionInfo* memory_region = GetMemoryRegion(region); | ||||
|         auto& linheap_memory = memory_region->linear_heap_memory; | ||||
|         auto offset = memory_region->LinearAllocate(size); | ||||
| 
 | ||||
|         ASSERT_MSG(linheap_memory->size() + size <= memory_region->size, | ||||
|                    "Not enough space in region to allocate shared memory!"); | ||||
|         ASSERT_MSG(offset, "Not enough space in region to allocate shared memory!"); | ||||
| 
 | ||||
|         shared_memory->backing_block = linheap_memory; | ||||
|         shared_memory->backing_block_offset = linheap_memory->size(); | ||||
|         // Allocate some memory from the end of the linear heap for this region.
 | ||||
|         linheap_memory->insert(linheap_memory->end(), size, 0); | ||||
|         memory_region->used += size; | ||||
| 
 | ||||
|         shared_memory->linear_heap_phys_address = | ||||
|             Memory::FCRAM_PADDR + memory_region->base + | ||||
|             static_cast<PAddr>(shared_memory->backing_block_offset); | ||||
|         std::fill(Memory::fcram.data() + *offset, Memory::fcram.data() + *offset + size, 0); | ||||
|         shared_memory->backing_blocks = {{Memory::fcram.data() + *offset, size}}; | ||||
|         shared_memory->holding_memory += MemoryRegionInfo::Interval(*offset, *offset + size); | ||||
|         shared_memory->linear_heap_phys_offset = *offset; | ||||
| 
 | ||||
|         // Increase the amount of used linear heap memory for the owner process.
 | ||||
|         if (shared_memory->owner_process != nullptr) { | ||||
|             shared_memory->owner_process->linear_heap_used += size; | ||||
|         } | ||||
| 
 | ||||
|         // Refresh the address mappings for the current process.
 | ||||
|         if (Kernel::g_current_process != nullptr) { | ||||
|             Kernel::g_current_process->vm_manager.RefreshMemoryBlockMappings(linheap_memory.get()); | ||||
|             shared_memory->owner_process->memory_used += size; | ||||
|         } | ||||
|     } else { | ||||
|         auto& vm_manager = shared_memory->owner_process->vm_manager; | ||||
|         // The memory is already available and mapped in the owner process.
 | ||||
|         auto vma = vm_manager.FindVMA(address); | ||||
|         ASSERT_MSG(vma != vm_manager.vma_map.end(), "Invalid memory address"); | ||||
|         ASSERT_MSG(vma->second.backing_block, "Backing block doesn't exist for address"); | ||||
| 
 | ||||
|         // The returned VMA might be a bigger one encompassing the desired address.
 | ||||
|         auto vma_offset = address - vma->first; | ||||
|         ASSERT_MSG(vma_offset + size <= vma->second.size, | ||||
|                    "Shared memory exceeds bounds of mapped block"); | ||||
| 
 | ||||
|         shared_memory->backing_block = vma->second.backing_block; | ||||
|         shared_memory->backing_block_offset = vma->second.offset + vma_offset; | ||||
|         auto backing_blocks = vm_manager.GetBackingBlocksForRange(address, size); | ||||
|         ASSERT_MSG(backing_blocks.Succeeded(), "Trying to share freed memory"); | ||||
|         shared_memory->backing_blocks = std::move(backing_blocks).Unwrap(); | ||||
|     } | ||||
| 
 | ||||
|     shared_memory->base_address = address; | ||||
|     return shared_memory; | ||||
| } | ||||
| 
 | ||||
| SharedPtr<SharedMemory> SharedMemory::CreateForApplet(std::shared_ptr<std::vector<u8>> heap_block, | ||||
|                                                       u32 offset, u32 size, | ||||
|                                                       MemoryPermission permissions, | ||||
|                                                       MemoryPermission other_permissions, | ||||
|                                                       std::string name) { | ||||
|     SharedPtr<SharedMemory> shared_memory(new SharedMemory); | ||||
| SharedPtr<SharedMemory> KernelSystem::CreateSharedMemoryForApplet( | ||||
|     u32 offset, u32 size, MemoryPermission permissions, MemoryPermission other_permissions, | ||||
|     std::string name) { | ||||
|     SharedPtr<SharedMemory> shared_memory(new SharedMemory(*this)); | ||||
| 
 | ||||
|     // Allocate memory in heap
 | ||||
|     MemoryRegionInfo* memory_region = GetMemoryRegion(MemoryRegion::SYSTEM); | ||||
|     auto backing_blocks = memory_region->HeapAllocate(size); | ||||
|     ASSERT_MSG(!backing_blocks.empty(), "Not enough space in region to allocate shared memory!"); | ||||
|     shared_memory->holding_memory = backing_blocks; | ||||
|     shared_memory->owner_process = nullptr; | ||||
|     shared_memory->name = std::move(name); | ||||
|     shared_memory->size = size; | ||||
|     shared_memory->permissions = permissions; | ||||
|     shared_memory->other_permissions = other_permissions; | ||||
|     shared_memory->backing_block = heap_block; | ||||
|     shared_memory->backing_block_offset = offset; | ||||
|     for (const auto& interval : backing_blocks) { | ||||
|         shared_memory->backing_blocks.push_back( | ||||
|             {Memory::fcram.data() + interval.lower(), interval.upper() - interval.lower()}); | ||||
|         std::fill(Memory::fcram.data() + interval.lower(), Memory::fcram.data() + interval.upper(), | ||||
|                   0); | ||||
|     } | ||||
|     shared_memory->base_address = Memory::HEAP_VADDR + offset; | ||||
| 
 | ||||
|     return shared_memory; | ||||
|  | @ -147,24 +142,32 @@ ResultCode SharedMemory::Map(Process* target_process, VAddr address, MemoryPermi | |||
| 
 | ||||
|     if (base_address == 0 && target_address == 0) { | ||||
|         // Calculate the address at which to map the memory block.
 | ||||
|         auto maybe_vaddr = Memory::PhysicalToVirtualAddress(linear_heap_phys_address); | ||||
|         ASSERT(maybe_vaddr); | ||||
|         target_address = *maybe_vaddr; | ||||
|         // Note: even on new firmware versions, the target address is still in the old linear heap
 | ||||
|         // region. This exception is made to keep the shared font compatibility. See
 | ||||
|         // APT:GetSharedFont for detail.
 | ||||
|         target_address = linear_heap_phys_offset + Memory::LINEAR_HEAP_VADDR; | ||||
|     } | ||||
| 
 | ||||
|     auto vma = target_process->vm_manager.FindVMA(target_address); | ||||
|     if (vma->second.type != VMAType::Free || | ||||
|         vma->second.base + vma->second.size < target_address + size) { | ||||
|         LOG_ERROR(Kernel, | ||||
|                   "cannot map id={}, address=0x{:08X} name={}, mapping to already allocated memory", | ||||
|                   GetObjectId(), address, name); | ||||
|         return ERR_INVALID_ADDRESS_STATE; | ||||
|     } | ||||
| 
 | ||||
|     // Map the memory block into the target process
 | ||||
|     auto result = target_process->vm_manager.MapMemoryBlock( | ||||
|         target_address, backing_block, backing_block_offset, size, MemoryState::Shared); | ||||
|     if (result.Failed()) { | ||||
|         LOG_ERROR( | ||||
|             Kernel, | ||||
|             "cannot map id={}, target_address=0x{:08X} name={}, error mapping to virtual memory", | ||||
|             GetObjectId(), target_address, name); | ||||
|         return result.Code(); | ||||
|     VAddr interval_target = target_address; | ||||
|     for (const auto& interval : backing_blocks) { | ||||
|         auto vma = target_process->vm_manager.MapBackingMemory( | ||||
|             interval_target, interval.first, interval.second, MemoryState::Shared); | ||||
|         ASSERT(vma.Succeeded()); | ||||
|         target_process->vm_manager.Reprotect(vma.Unwrap(), ConvertPermissions(permissions)); | ||||
|         interval_target += interval.second; | ||||
|     } | ||||
| 
 | ||||
|     return target_process->vm_manager.ReprotectRange(target_address, size, | ||||
|                                                      ConvertPermissions(permissions)); | ||||
|     return RESULT_SUCCESS; | ||||
| } | ||||
| 
 | ||||
| ResultCode SharedMemory::Unmap(Process* target_process, VAddr address) { | ||||
|  | @ -180,7 +183,10 @@ VMAPermission SharedMemory::ConvertPermissions(MemoryPermission permission) { | |||
| }; | ||||
| 
 | ||||
| u8* SharedMemory::GetPointer(u32 offset) { | ||||
|     return backing_block->data() + backing_block_offset + offset; | ||||
|     if (backing_blocks.size() != 1) { | ||||
|         LOG_WARNING(Kernel, "Unsafe GetPointer on discontinuous SharedMemory"); | ||||
|     } | ||||
|     return backing_blocks[0].first + offset; | ||||
| } | ||||
| 
 | ||||
| } // namespace Kernel
 | ||||
|  |  | |||
|  | @ -12,55 +12,8 @@ | |||
| 
 | ||||
| namespace Kernel { | ||||
| 
 | ||||
| /// Permissions for mapped shared memory blocks
 | ||||
| enum class MemoryPermission : u32 { | ||||
|     None = 0, | ||||
|     Read = (1u << 0), | ||||
|     Write = (1u << 1), | ||||
|     ReadWrite = (Read | Write), | ||||
|     Execute = (1u << 2), | ||||
|     ReadExecute = (Read | Execute), | ||||
|     WriteExecute = (Write | Execute), | ||||
|     ReadWriteExecute = (Read | Write | Execute), | ||||
|     DontCare = (1u << 28) | ||||
| }; | ||||
| 
 | ||||
| class SharedMemory final : public Object { | ||||
| public: | ||||
|     /**
 | ||||
|      * Creates a shared memory object. | ||||
|      * @param owner_process Process that created this shared memory object. | ||||
|      * @param size Size of the memory block. Must be page-aligned. | ||||
|      * @param permissions Permission restrictions applied to the process which created the block. | ||||
|      * @param other_permissions Permission restrictions applied to other processes mapping the | ||||
|      * block. | ||||
|      * @param address The address from which to map the Shared Memory. | ||||
|      * @param region If the address is 0, the shared memory will be allocated in this region of the | ||||
|      * linear heap. | ||||
|      * @param name Optional object name, used for debugging purposes. | ||||
|      */ | ||||
|     static SharedPtr<SharedMemory> Create(SharedPtr<Process> owner_process, u32 size, | ||||
|                                           MemoryPermission permissions, | ||||
|                                           MemoryPermission other_permissions, VAddr address = 0, | ||||
|                                           MemoryRegion region = MemoryRegion::BASE, | ||||
|                                           std::string name = "Unknown"); | ||||
| 
 | ||||
|     /**
 | ||||
|      * Creates a shared memory object from a block of memory managed by an HLE applet. | ||||
|      * @param heap_block Heap block of the HLE applet. | ||||
|      * @param offset The offset into the heap block that the SharedMemory will map. | ||||
|      * @param size Size of the memory block. Must be page-aligned. | ||||
|      * @param permissions Permission restrictions applied to the process which created the block. | ||||
|      * @param other_permissions Permission restrictions applied to other processes mapping the | ||||
|      * block. | ||||
|      * @param name Optional object name, used for debugging purposes. | ||||
|      */ | ||||
|     static SharedPtr<SharedMemory> CreateForApplet(std::shared_ptr<std::vector<u8>> heap_block, | ||||
|                                                    u32 offset, u32 size, | ||||
|                                                    MemoryPermission permissions, | ||||
|                                                    MemoryPermission other_permissions, | ||||
|                                                    std::string name = "Unknown Applet"); | ||||
| 
 | ||||
|     std::string GetTypeName() const override { | ||||
|         return "SharedMemory"; | ||||
|     } | ||||
|  | @ -105,16 +58,14 @@ public: | |||
|     u8* GetPointer(u32 offset = 0); | ||||
| 
 | ||||
|     /// Process that created this shared memory block.
 | ||||
|     SharedPtr<Process> owner_process; | ||||
|     Process* owner_process; | ||||
|     /// Address of shared memory block in the owner process if specified.
 | ||||
|     VAddr base_address; | ||||
|     /// Physical address of the shared memory block in the linear heap if no address was specified
 | ||||
|     /// Offset in FCRAM of the shared memory block in the linear heap if no address was specified
 | ||||
|     /// during creation.
 | ||||
|     PAddr linear_heap_phys_address; | ||||
|     PAddr linear_heap_phys_offset; | ||||
|     /// Backing memory for this shared memory block.
 | ||||
|     std::shared_ptr<std::vector<u8>> backing_block; | ||||
|     /// Offset into the backing block for this shared memory.
 | ||||
|     std::size_t backing_block_offset; | ||||
|     std::vector<std::pair<u8*, u32>> backing_blocks; | ||||
|     /// Size of the memory block. Page-aligned.
 | ||||
|     u32 size; | ||||
|     /// Permission restrictions applied to the process which created the block.
 | ||||
|  | @ -124,9 +75,14 @@ public: | |||
|     /// Name of shared memory object.
 | ||||
|     std::string name; | ||||
| 
 | ||||
|     MemoryRegionInfo::IntervalSet holding_memory; | ||||
| 
 | ||||
| private: | ||||
|     SharedMemory(); | ||||
|     explicit SharedMemory(KernelSystem& kernel); | ||||
|     ~SharedMemory() override; | ||||
| 
 | ||||
|     friend class KernelSystem; | ||||
|     KernelSystem& kernel; | ||||
| }; | ||||
| 
 | ||||
| } // namespace Kernel
 | ||||
|  |  | |||
|  | @ -4,9 +4,10 @@ | |||
| 
 | ||||
| #include <chrono> | ||||
| #include <cstring> | ||||
| #include "core/core.h" | ||||
| #include "core/core_timing.h" | ||||
| #include "core/hle/kernel/shared_page.h" | ||||
| #include "core/hle/service/ptm/ptm.h" | ||||
| #include "core/hle/shared_page.h" | ||||
| #include "core/movie.h" | ||||
| #include "core/settings.h" | ||||
| 
 | ||||
|  | @ -53,9 +54,9 @@ Handler::Handler() { | |||
|     init_time = GetInitTime(); | ||||
| 
 | ||||
|     using namespace std::placeholders; | ||||
|     update_time_event = CoreTiming::RegisterEvent( | ||||
|     update_time_event = Core::System::GetInstance().CoreTiming().RegisterEvent( | ||||
|         "SharedPage::UpdateTimeCallback", std::bind(&Handler::UpdateTimeCallback, this, _1, _2)); | ||||
|     CoreTiming::ScheduleEvent(0, update_time_event); | ||||
|     Core::System::GetInstance().CoreTiming().ScheduleEvent(0, update_time_event); | ||||
| 
 | ||||
|     float slidestate = | ||||
|         Settings::values.toggle_3d ? (float_le)Settings::values.factor_3d / 100 : 0.0f; | ||||
|  | @ -65,8 +66,8 @@ Handler::Handler() { | |||
| /// Gets system time in 3DS format. The epoch is Jan 1900, and the unit is millisecond.
 | ||||
| u64 Handler::GetSystemTime() const { | ||||
|     std::chrono::milliseconds now = | ||||
|         init_time + | ||||
|         std::chrono::duration_cast<std::chrono::milliseconds>(CoreTiming::GetGlobalTimeUs()); | ||||
|         init_time + std::chrono::duration_cast<std::chrono::milliseconds>( | ||||
|                         Core::System::GetInstance().CoreTiming().GetGlobalTimeUs()); | ||||
| 
 | ||||
|     // 3DS system does't allow user to set a time before Jan 1 2000,
 | ||||
|     // so we use it as an auxiliary epoch to calculate the console time.
 | ||||
|  | @ -97,14 +98,15 @@ void Handler::UpdateTimeCallback(u64 userdata, int cycles_late) { | |||
|         shared_page.date_time_counter % 2 ? shared_page.date_time_0 : shared_page.date_time_1; | ||||
| 
 | ||||
|     date_time.date_time = GetSystemTime(); | ||||
|     date_time.update_tick = CoreTiming::GetTicks(); | ||||
|     date_time.update_tick = Core::System::GetInstance().CoreTiming().GetTicks(); | ||||
|     date_time.tick_to_second_coefficient = BASE_CLOCK_RATE_ARM11; | ||||
|     date_time.tick_offset = 0; | ||||
| 
 | ||||
|     ++shared_page.date_time_counter; | ||||
| 
 | ||||
|     // system time is updated hourly
 | ||||
|     CoreTiming::ScheduleEvent(msToCycles(60 * 60 * 1000) - cycles_late, update_time_event); | ||||
|     Core::System::GetInstance().CoreTiming().ScheduleEvent(msToCycles(60 * 60 * 1000) - cycles_late, | ||||
|                                                            update_time_event); | ||||
| } | ||||
| 
 | ||||
| void Handler::SetMacAddress(const MacAddress& addr) { | ||||
|  | @ -21,8 +21,8 @@ | |||
| 
 | ||||
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | ||||
| 
 | ||||
| namespace CoreTiming { | ||||
| struct EventType; | ||||
| namespace Core { | ||||
| struct TimingEventType; | ||||
| } | ||||
| 
 | ||||
| namespace SharedPage { | ||||
|  | @ -96,7 +96,7 @@ public: | |||
| private: | ||||
|     u64 GetSystemTime() const; | ||||
|     void UpdateTimeCallback(u64 userdata, int cycles_late); | ||||
|     CoreTiming::EventType* update_time_event; | ||||
|     Core::TimingEventType* update_time_event; | ||||
|     std::chrono::seconds init_time; | ||||
| 
 | ||||
|     SharedPageDef shared_page; | ||||
|  | @ -83,7 +83,7 @@ static ResultCode ControlMemory(u32* out_addr, u32 operation, u32 addr0, u32 add | |||
|     } | ||||
|     VMAPermission vma_permissions = (VMAPermission)permissions; | ||||
| 
 | ||||
|     auto& process = *g_current_process; | ||||
|     auto& process = *Core::System::GetInstance().Kernel().GetCurrentProcess(); | ||||
| 
 | ||||
|     switch (operation & MEMOP_OPERATION_MASK) { | ||||
|     case MEMOP_FREE: { | ||||
|  | @ -114,16 +114,12 @@ static ResultCode ControlMemory(u32* out_addr, u32 operation, u32 addr0, u32 add | |||
|     } | ||||
| 
 | ||||
|     case MEMOP_MAP: { | ||||
|         // TODO: This is just a hack to avoid regressions until memory aliasing is implemented
 | ||||
|         CASCADE_RESULT(*out_addr, process.HeapAllocate(addr0, size, vma_permissions)); | ||||
|         CASCADE_CODE(process.Map(addr0, addr1, size, vma_permissions)); | ||||
|         break; | ||||
|     } | ||||
| 
 | ||||
|     case MEMOP_UNMAP: { | ||||
|         // TODO: This is just a hack to avoid regressions until memory aliasing is implemented
 | ||||
|         ResultCode result = process.HeapFree(addr0, size); | ||||
|         if (result.IsError()) | ||||
|             return result; | ||||
|         CASCADE_CODE(process.Unmap(addr0, addr1, size, vma_permissions)); | ||||
|         break; | ||||
|     } | ||||
| 
 | ||||
|  | @ -145,19 +141,21 @@ static ResultCode ControlMemory(u32* out_addr, u32 operation, u32 addr0, u32 add | |||
| } | ||||
| 
 | ||||
| static void ExitProcess() { | ||||
|     LOG_INFO(Kernel_SVC, "Process {} exiting", g_current_process->process_id); | ||||
|     KernelSystem& kernel = Core::System::GetInstance().Kernel(); | ||||
|     SharedPtr<Process> current_process = kernel.GetCurrentProcess(); | ||||
|     LOG_INFO(Kernel_SVC, "Process {} exiting", current_process->process_id); | ||||
| 
 | ||||
|     ASSERT_MSG(g_current_process->status == ProcessStatus::Running, "Process has already exited"); | ||||
|     ASSERT_MSG(current_process->status == ProcessStatus::Running, "Process has already exited"); | ||||
| 
 | ||||
|     g_current_process->status = ProcessStatus::Exited; | ||||
|     current_process->status = ProcessStatus::Exited; | ||||
| 
 | ||||
|     // Stop all the process threads that are currently waiting for objects.
 | ||||
|     auto& thread_list = GetThreadList(); | ||||
|     auto& thread_list = kernel.GetThreadManager().GetThreadList(); | ||||
|     for (auto& thread : thread_list) { | ||||
|         if (thread->owner_process != g_current_process) | ||||
|         if (thread->owner_process != current_process) | ||||
|             continue; | ||||
| 
 | ||||
|         if (thread == GetCurrentThread()) | ||||
|         if (thread == kernel.GetThreadManager().GetCurrentThread()) | ||||
|             continue; | ||||
| 
 | ||||
|         // TODO(Subv): When are the other running/ready threads terminated?
 | ||||
|  | @ -169,7 +167,7 @@ static void ExitProcess() { | |||
|     } | ||||
| 
 | ||||
|     // Kill the current thread
 | ||||
|     GetCurrentThread()->Stop(); | ||||
|     kernel.GetThreadManager().GetCurrentThread()->Stop(); | ||||
| 
 | ||||
|     Core::System::GetInstance().PrepareReschedule(); | ||||
| } | ||||
|  | @ -181,7 +179,9 @@ static ResultCode MapMemoryBlock(Handle handle, u32 addr, u32 permissions, u32 o | |||
|               "otherpermission={}", | ||||
|               handle, addr, permissions, other_permissions); | ||||
| 
 | ||||
|     SharedPtr<SharedMemory> shared_memory = g_handle_table.Get<SharedMemory>(handle); | ||||
|     SharedPtr<SharedMemory> shared_memory = | ||||
|         Core::System::GetInstance().Kernel().GetCurrentProcess()->handle_table.Get<SharedMemory>( | ||||
|             handle); | ||||
|     if (shared_memory == nullptr) | ||||
|         return ERR_INVALID_HANDLE; | ||||
| 
 | ||||
|  | @ -195,7 +195,8 @@ static ResultCode MapMemoryBlock(Handle handle, u32 addr, u32 permissions, u32 o | |||
|     case MemoryPermission::WriteExecute: | ||||
|     case MemoryPermission::ReadWriteExecute: | ||||
|     case MemoryPermission::DontCare: | ||||
|         return shared_memory->Map(g_current_process.get(), addr, permissions_type, | ||||
|         return shared_memory->Map(Core::System::GetInstance().Kernel().GetCurrentProcess().get(), | ||||
|                                   addr, permissions_type, | ||||
|                                   static_cast<MemoryPermission>(other_permissions)); | ||||
|     default: | ||||
|         LOG_ERROR(Kernel_SVC, "unknown permissions=0x{:08X}", permissions); | ||||
|  | @ -209,11 +210,12 @@ static ResultCode UnmapMemoryBlock(Handle handle, u32 addr) { | |||
| 
 | ||||
|     // TODO(Subv): Return E0A01BF5 if the address is not in the application's heap
 | ||||
| 
 | ||||
|     SharedPtr<SharedMemory> shared_memory = g_handle_table.Get<SharedMemory>(handle); | ||||
|     SharedPtr<Process> current_process = Core::System::GetInstance().Kernel().GetCurrentProcess(); | ||||
|     SharedPtr<SharedMemory> shared_memory = current_process->handle_table.Get<SharedMemory>(handle); | ||||
|     if (shared_memory == nullptr) | ||||
|         return ERR_INVALID_HANDLE; | ||||
| 
 | ||||
|     return shared_memory->Unmap(g_current_process.get(), addr); | ||||
|     return shared_memory->Unmap(current_process.get(), addr); | ||||
| } | ||||
| 
 | ||||
| /// Connect to an OS service given the port name, returns the handle to the port to out
 | ||||
|  | @ -229,8 +231,10 @@ static ResultCode ConnectToPort(Handle* out_handle, VAddr port_name_address) { | |||
| 
 | ||||
|     LOG_TRACE(Kernel_SVC, "called port_name={}", port_name); | ||||
| 
 | ||||
|     auto it = Service::g_kernel_named_ports.find(port_name); | ||||
|     if (it == Service::g_kernel_named_ports.end()) { | ||||
|     KernelSystem& kernel = Core::System::GetInstance().Kernel(); | ||||
| 
 | ||||
|     auto it = kernel.named_ports.find(port_name); | ||||
|     if (it == kernel.named_ports.end()) { | ||||
|         LOG_WARNING(Kernel_SVC, "tried to connect to unknown port: {}", port_name); | ||||
|         return ERR_NOT_FOUND; | ||||
|     } | ||||
|  | @ -241,13 +245,15 @@ static ResultCode ConnectToPort(Handle* out_handle, VAddr port_name_address) { | |||
|     CASCADE_RESULT(client_session, client_port->Connect()); | ||||
| 
 | ||||
|     // Return the client session
 | ||||
|     CASCADE_RESULT(*out_handle, g_handle_table.Create(client_session)); | ||||
|     CASCADE_RESULT(*out_handle, kernel.GetCurrentProcess()->handle_table.Create(client_session)); | ||||
|     return RESULT_SUCCESS; | ||||
| } | ||||
| 
 | ||||
| /// Makes a blocking IPC call to an OS service.
 | ||||
| static ResultCode SendSyncRequest(Handle handle) { | ||||
|     SharedPtr<ClientSession> session = g_handle_table.Get<ClientSession>(handle); | ||||
|     KernelSystem& kernel = Core::System::GetInstance().Kernel(); | ||||
|     SharedPtr<ClientSession> session = | ||||
|         kernel.GetCurrentProcess()->handle_table.Get<ClientSession>(handle); | ||||
|     if (session == nullptr) { | ||||
|         return ERR_INVALID_HANDLE; | ||||
|     } | ||||
|  | @ -256,19 +262,20 @@ static ResultCode SendSyncRequest(Handle handle) { | |||
| 
 | ||||
|     Core::System::GetInstance().PrepareReschedule(); | ||||
| 
 | ||||
|     return session->SendSyncRequest(GetCurrentThread()); | ||||
|     return session->SendSyncRequest(kernel.GetThreadManager().GetCurrentThread()); | ||||
| } | ||||
| 
 | ||||
| /// Close a handle
 | ||||
| static ResultCode CloseHandle(Handle handle) { | ||||
|     LOG_TRACE(Kernel_SVC, "Closing handle 0x{:08X}", handle); | ||||
|     return g_handle_table.Close(handle); | ||||
|     return Core::System::GetInstance().Kernel().GetCurrentProcess()->handle_table.Close(handle); | ||||
| } | ||||
| 
 | ||||
| /// Wait for a handle to synchronize, timeout after the specified nanoseconds
 | ||||
| static ResultCode WaitSynchronization1(Handle handle, s64 nano_seconds) { | ||||
|     auto object = g_handle_table.Get<WaitObject>(handle); | ||||
|     Thread* thread = GetCurrentThread(); | ||||
|     KernelSystem& kernel = Core::System::GetInstance().Kernel(); | ||||
|     auto object = kernel.GetCurrentProcess()->handle_table.Get<WaitObject>(handle); | ||||
|     Thread* thread = kernel.GetThreadManager().GetCurrentThread(); | ||||
| 
 | ||||
|     if (object == nullptr) | ||||
|         return ERR_INVALID_HANDLE; | ||||
|  | @ -320,7 +327,8 @@ 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, VAddr handles_address, s32 handle_count, | ||||
|                                        bool wait_all, s64 nano_seconds) { | ||||
|     Thread* thread = GetCurrentThread(); | ||||
|     KernelSystem& kernel = Core::System::GetInstance().Kernel(); | ||||
|     Thread* thread = kernel.GetThreadManager().GetCurrentThread(); | ||||
| 
 | ||||
|     if (!Memory::IsValidVirtualAddress(handles_address)) | ||||
|         return ERR_INVALID_POINTER; | ||||
|  | @ -338,7 +346,7 @@ static ResultCode WaitSynchronizationN(s32* out, VAddr handles_address, s32 hand | |||
| 
 | ||||
|     for (int i = 0; i < handle_count; ++i) { | ||||
|         Handle handle = Memory::Read32(handles_address + i * sizeof(Handle)); | ||||
|         auto object = g_handle_table.Get<WaitObject>(handle); | ||||
|         auto object = kernel.GetCurrentProcess()->handle_table.Get<WaitObject>(handle); | ||||
|         if (object == nullptr) | ||||
|             return ERR_INVALID_HANDLE; | ||||
|         objects[i] = object; | ||||
|  | @ -502,9 +510,12 @@ static ResultCode ReplyAndReceive(s32* index, VAddr handles_address, s32 handle_ | |||
|     using ObjectPtr = SharedPtr<WaitObject>; | ||||
|     std::vector<ObjectPtr> objects(handle_count); | ||||
| 
 | ||||
|     KernelSystem& kernel = Core::System::GetInstance().Kernel(); | ||||
|     SharedPtr<Process> current_process = kernel.GetCurrentProcess(); | ||||
| 
 | ||||
|     for (int i = 0; i < handle_count; ++i) { | ||||
|         Handle handle = Memory::Read32(handles_address + i * sizeof(Handle)); | ||||
|         auto object = g_handle_table.Get<WaitObject>(handle); | ||||
|         auto object = current_process->handle_table.Get<WaitObject>(handle); | ||||
|         if (object == nullptr) | ||||
|             return ERR_INVALID_HANDLE; | ||||
|         objects[i] = object; | ||||
|  | @ -512,10 +523,11 @@ static ResultCode ReplyAndReceive(s32* index, VAddr handles_address, s32 handle_ | |||
| 
 | ||||
|     // We are also sending a command reply.
 | ||||
|     // Do not send a reply if the command id in the command buffer is 0xFFFF.
 | ||||
|     u32* cmd_buff = GetCommandBuffer(); | ||||
|     IPC::Header header{cmd_buff[0]}; | ||||
|     Thread* thread = kernel.GetThreadManager().GetCurrentThread(); | ||||
|     u32 cmd_buff_header = Memory::Read32(thread->GetCommandBufferAddress()); | ||||
|     IPC::Header header{cmd_buff_header}; | ||||
|     if (reply_target != 0 && header.command_id != 0xFFFF) { | ||||
|         auto session = g_handle_table.Get<ServerSession>(reply_target); | ||||
|         auto session = current_process->handle_table.Get<ServerSession>(reply_target); | ||||
|         if (session == nullptr) | ||||
|             return ERR_INVALID_HANDLE; | ||||
| 
 | ||||
|  | @ -531,11 +543,11 @@ static ResultCode ReplyAndReceive(s32* index, VAddr handles_address, s32 handle_ | |||
|             return ERR_SESSION_CLOSED_BY_REMOTE; | ||||
|         } | ||||
| 
 | ||||
|         VAddr source_address = GetCurrentThread()->GetCommandBufferAddress(); | ||||
|         VAddr source_address = thread->GetCommandBufferAddress(); | ||||
|         VAddr target_address = request_thread->GetCommandBufferAddress(); | ||||
| 
 | ||||
|         ResultCode translation_result = TranslateCommandBuffer( | ||||
|             Kernel::GetCurrentThread(), request_thread, source_address, target_address, true); | ||||
|         ResultCode translation_result = | ||||
|             TranslateCommandBuffer(thread, request_thread, source_address, target_address, true); | ||||
| 
 | ||||
|         // Note: The real kernel seems to always panic if the Server->Client buffer translation
 | ||||
|         // fails for whatever reason.
 | ||||
|  | @ -555,8 +567,6 @@ static ResultCode ReplyAndReceive(s32* index, VAddr handles_address, s32 handle_ | |||
|         return RESULT_SUCCESS; | ||||
|     } | ||||
| 
 | ||||
|     auto thread = GetCurrentThread(); | ||||
| 
 | ||||
|     // Find the first object that is acquirable in the provided list of objects
 | ||||
|     auto itr = std::find_if(objects.begin(), objects.end(), [thread](const ObjectPtr& object) { | ||||
|         return !object->ShouldWait(thread); | ||||
|  | @ -572,7 +582,7 @@ static ResultCode ReplyAndReceive(s32* index, VAddr handles_address, s32 handle_ | |||
|             return RESULT_SUCCESS; | ||||
| 
 | ||||
|         auto server_session = static_cast<ServerSession*>(object); | ||||
|         return ReceiveIPCRequest(server_session, GetCurrentThread()); | ||||
|         return ReceiveIPCRequest(server_session, thread); | ||||
|     } | ||||
| 
 | ||||
|     // No objects were ready to be acquired, prepare to suspend the thread.
 | ||||
|  | @ -615,8 +625,10 @@ static ResultCode ReplyAndReceive(s32* index, VAddr handles_address, s32 handle_ | |||
| 
 | ||||
| /// Create an address arbiter (to allocate access to shared resources)
 | ||||
| static ResultCode CreateAddressArbiter(Handle* out_handle) { | ||||
|     SharedPtr<AddressArbiter> arbiter = AddressArbiter::Create(); | ||||
|     CASCADE_RESULT(*out_handle, g_handle_table.Create(std::move(arbiter))); | ||||
|     KernelSystem& kernel = Core::System::GetInstance().Kernel(); | ||||
|     SharedPtr<AddressArbiter> arbiter = kernel.CreateAddressArbiter(); | ||||
|     CASCADE_RESULT(*out_handle, | ||||
|                    kernel.GetCurrentProcess()->handle_table.Create(std::move(arbiter))); | ||||
|     LOG_TRACE(Kernel_SVC, "returned handle=0x{:08X}", *out_handle); | ||||
|     return RESULT_SUCCESS; | ||||
| } | ||||
|  | @ -627,12 +639,16 @@ static ResultCode ArbitrateAddress(Handle handle, u32 address, u32 type, u32 val | |||
|     LOG_TRACE(Kernel_SVC, "called handle=0x{:08X}, address=0x{:08X}, type=0x{:08X}, value=0x{:08X}", | ||||
|               handle, address, type, value); | ||||
| 
 | ||||
|     SharedPtr<AddressArbiter> arbiter = g_handle_table.Get<AddressArbiter>(handle); | ||||
|     KernelSystem& kernel = Core::System::GetInstance().Kernel(); | ||||
| 
 | ||||
|     SharedPtr<AddressArbiter> arbiter = | ||||
|         kernel.GetCurrentProcess()->handle_table.Get<AddressArbiter>(handle); | ||||
|     if (arbiter == nullptr) | ||||
|         return ERR_INVALID_HANDLE; | ||||
| 
 | ||||
|     auto res = arbiter->ArbitrateAddress(GetCurrentThread(), static_cast<ArbitrationType>(type), | ||||
|                                          address, value, nanoseconds); | ||||
|     auto res = | ||||
|         arbiter->ArbitrateAddress(kernel.GetThreadManager().GetCurrentThread(), | ||||
|                                   static_cast<ArbitrationType>(type), address, value, nanoseconds); | ||||
| 
 | ||||
|     // TODO(Subv): Identify in which specific cases this call should cause a reschedule.
 | ||||
|     Core::System::GetInstance().PrepareReschedule(); | ||||
|  | @ -675,11 +691,12 @@ static void OutputDebugString(VAddr address, int len) { | |||
| static ResultCode GetResourceLimit(Handle* resource_limit, Handle process_handle) { | ||||
|     LOG_TRACE(Kernel_SVC, "called process=0x{:08X}", process_handle); | ||||
| 
 | ||||
|     SharedPtr<Process> process = g_handle_table.Get<Process>(process_handle); | ||||
|     SharedPtr<Process> current_process = Core::System::GetInstance().Kernel().GetCurrentProcess(); | ||||
|     SharedPtr<Process> process = current_process->handle_table.Get<Process>(process_handle); | ||||
|     if (process == nullptr) | ||||
|         return ERR_INVALID_HANDLE; | ||||
| 
 | ||||
|     CASCADE_RESULT(*resource_limit, g_handle_table.Create(process->resource_limit)); | ||||
|     CASCADE_RESULT(*resource_limit, current_process->handle_table.Create(process->resource_limit)); | ||||
| 
 | ||||
|     return RESULT_SUCCESS; | ||||
| } | ||||
|  | @ -691,7 +708,8 @@ static ResultCode GetResourceLimitCurrentValues(VAddr values, Handle resource_li | |||
|               resource_limit_handle, names, name_count); | ||||
| 
 | ||||
|     SharedPtr<ResourceLimit> resource_limit = | ||||
|         g_handle_table.Get<ResourceLimit>(resource_limit_handle); | ||||
|         Core::System::GetInstance().Kernel().GetCurrentProcess()->handle_table.Get<ResourceLimit>( | ||||
|             resource_limit_handle); | ||||
|     if (resource_limit == nullptr) | ||||
|         return ERR_INVALID_HANDLE; | ||||
| 
 | ||||
|  | @ -711,7 +729,8 @@ static ResultCode GetResourceLimitLimitValues(VAddr values, Handle resource_limi | |||
|               resource_limit_handle, names, name_count); | ||||
| 
 | ||||
|     SharedPtr<ResourceLimit> resource_limit = | ||||
|         g_handle_table.Get<ResourceLimit>(resource_limit_handle); | ||||
|         Core::System::GetInstance().Kernel().GetCurrentProcess()->handle_table.Get<ResourceLimit>( | ||||
|             resource_limit_handle); | ||||
|     if (resource_limit == nullptr) | ||||
|         return ERR_INVALID_HANDLE; | ||||
| 
 | ||||
|  | @ -733,14 +752,16 @@ static ResultCode CreateThread(Handle* out_handle, u32 priority, u32 entry_point | |||
|         return ERR_OUT_OF_RANGE; | ||||
|     } | ||||
| 
 | ||||
|     SharedPtr<ResourceLimit>& resource_limit = g_current_process->resource_limit; | ||||
|     SharedPtr<Process> current_process = Core::System::GetInstance().Kernel().GetCurrentProcess(); | ||||
| 
 | ||||
|     SharedPtr<ResourceLimit>& resource_limit = current_process->resource_limit; | ||||
|     if (resource_limit->GetMaxResourceValue(ResourceTypes::PRIORITY) > priority) { | ||||
|         return ERR_NOT_AUTHORIZED; | ||||
|     } | ||||
| 
 | ||||
|     if (processor_id == ThreadProcessorIdDefault) { | ||||
|         // Set the target CPU to the one specified in the process' exheader.
 | ||||
|         processor_id = g_current_process->ideal_processor; | ||||
|         processor_id = current_process->ideal_processor; | ||||
|         ASSERT(processor_id != ThreadProcessorIdDefault); | ||||
|     } | ||||
| 
 | ||||
|  | @ -761,14 +782,14 @@ static ResultCode CreateThread(Handle* out_handle, u32 priority, u32 entry_point | |||
|         break; | ||||
|     } | ||||
| 
 | ||||
|     CASCADE_RESULT(SharedPtr<Thread> thread, | ||||
|                    Thread::Create(name, entry_point, priority, arg, processor_id, stack_top, | ||||
|                                   g_current_process)); | ||||
|     CASCADE_RESULT(SharedPtr<Thread> thread, Core::System::GetInstance().Kernel().CreateThread( | ||||
|                                                  name, entry_point, priority, arg, processor_id, | ||||
|                                                  stack_top, *current_process)); | ||||
| 
 | ||||
|     thread->context->SetFpscr(FPSCR_DEFAULT_NAN | FPSCR_FLUSH_TO_ZERO | | ||||
|                               FPSCR_ROUND_TOZERO); // 0x03C00000
 | ||||
| 
 | ||||
|     CASCADE_RESULT(*out_handle, g_handle_table.Create(std::move(thread))); | ||||
|     CASCADE_RESULT(*out_handle, current_process->handle_table.Create(std::move(thread))); | ||||
| 
 | ||||
|     Core::System::GetInstance().PrepareReschedule(); | ||||
| 
 | ||||
|  | @ -784,13 +805,14 @@ static ResultCode CreateThread(Handle* out_handle, u32 priority, u32 entry_point | |||
| static void ExitThread() { | ||||
|     LOG_TRACE(Kernel_SVC, "called, pc=0x{:08X}", Core::CPU().GetPC()); | ||||
| 
 | ||||
|     ExitCurrentThread(); | ||||
|     Core::System::GetInstance().Kernel().GetThreadManager().ExitCurrentThread(); | ||||
|     Core::System::GetInstance().PrepareReschedule(); | ||||
| } | ||||
| 
 | ||||
| /// Gets the priority for the specified thread
 | ||||
| static ResultCode GetThreadPriority(u32* priority, Handle handle) { | ||||
|     const SharedPtr<Thread> thread = g_handle_table.Get<Thread>(handle); | ||||
|     const SharedPtr<Thread> thread = | ||||
|         Core::System::GetInstance().Kernel().GetCurrentProcess()->handle_table.Get<Thread>(handle); | ||||
|     if (thread == nullptr) | ||||
|         return ERR_INVALID_HANDLE; | ||||
| 
 | ||||
|  | @ -804,13 +826,15 @@ static ResultCode SetThreadPriority(Handle handle, u32 priority) { | |||
|         return ERR_OUT_OF_RANGE; | ||||
|     } | ||||
| 
 | ||||
|     SharedPtr<Thread> thread = g_handle_table.Get<Thread>(handle); | ||||
|     SharedPtr<Thread> thread = | ||||
|         Core::System::GetInstance().Kernel().GetCurrentProcess()->handle_table.Get<Thread>(handle); | ||||
|     if (thread == nullptr) | ||||
|         return ERR_INVALID_HANDLE; | ||||
| 
 | ||||
|     // Note: The kernel uses the current process's resource limit instead of
 | ||||
|     // the one from the thread owner's resource limit.
 | ||||
|     SharedPtr<ResourceLimit>& resource_limit = g_current_process->resource_limit; | ||||
|     SharedPtr<ResourceLimit>& resource_limit = | ||||
|         Core::System::GetInstance().Kernel().GetCurrentProcess()->resource_limit; | ||||
|     if (resource_limit->GetMaxResourceValue(ResourceTypes::PRIORITY) > priority) { | ||||
|         return ERR_NOT_AUTHORIZED; | ||||
|     } | ||||
|  | @ -828,9 +852,10 @@ static ResultCode SetThreadPriority(Handle handle, u32 priority) { | |||
| 
 | ||||
| /// Create a mutex
 | ||||
| static ResultCode CreateMutex(Handle* out_handle, u32 initial_locked) { | ||||
|     SharedPtr<Mutex> mutex = Mutex::Create(initial_locked != 0); | ||||
|     KernelSystem& kernel = Core::System::GetInstance().Kernel(); | ||||
|     SharedPtr<Mutex> mutex = kernel.CreateMutex(initial_locked != 0); | ||||
|     mutex->name = fmt::format("mutex-{:08x}", Core::CPU().GetReg(14)); | ||||
|     CASCADE_RESULT(*out_handle, g_handle_table.Create(std::move(mutex))); | ||||
|     CASCADE_RESULT(*out_handle, kernel.GetCurrentProcess()->handle_table.Create(std::move(mutex))); | ||||
| 
 | ||||
|     LOG_TRACE(Kernel_SVC, "called initial_locked={} : created handle=0x{:08X}", | ||||
|               initial_locked ? "true" : "false", *out_handle); | ||||
|  | @ -842,18 +867,22 @@ static ResultCode CreateMutex(Handle* out_handle, u32 initial_locked) { | |||
| static ResultCode ReleaseMutex(Handle handle) { | ||||
|     LOG_TRACE(Kernel_SVC, "called handle=0x{:08X}", handle); | ||||
| 
 | ||||
|     SharedPtr<Mutex> mutex = g_handle_table.Get<Mutex>(handle); | ||||
|     KernelSystem& kernel = Core::System::GetInstance().Kernel(); | ||||
| 
 | ||||
|     SharedPtr<Mutex> mutex = kernel.GetCurrentProcess()->handle_table.Get<Mutex>(handle); | ||||
|     if (mutex == nullptr) | ||||
|         return ERR_INVALID_HANDLE; | ||||
| 
 | ||||
|     return mutex->Release(GetCurrentThread()); | ||||
|     return mutex->Release(kernel.GetThreadManager().GetCurrentThread()); | ||||
| } | ||||
| 
 | ||||
| /// Get the ID of the specified process
 | ||||
| static ResultCode GetProcessId(u32* process_id, Handle process_handle) { | ||||
|     LOG_TRACE(Kernel_SVC, "called process=0x{:08X}", process_handle); | ||||
| 
 | ||||
|     const SharedPtr<Process> process = g_handle_table.Get<Process>(process_handle); | ||||
|     const SharedPtr<Process> process = | ||||
|         Core::System::GetInstance().Kernel().GetCurrentProcess()->handle_table.Get<Process>( | ||||
|             process_handle); | ||||
|     if (process == nullptr) | ||||
|         return ERR_INVALID_HANDLE; | ||||
| 
 | ||||
|  | @ -865,7 +894,9 @@ static ResultCode GetProcessId(u32* process_id, Handle process_handle) { | |||
| static ResultCode GetProcessIdOfThread(u32* process_id, Handle thread_handle) { | ||||
|     LOG_TRACE(Kernel_SVC, "called thread=0x{:08X}", thread_handle); | ||||
| 
 | ||||
|     const SharedPtr<Thread> thread = g_handle_table.Get<Thread>(thread_handle); | ||||
|     const SharedPtr<Thread> thread = | ||||
|         Core::System::GetInstance().Kernel().GetCurrentProcess()->handle_table.Get<Thread>( | ||||
|             thread_handle); | ||||
|     if (thread == nullptr) | ||||
|         return ERR_INVALID_HANDLE; | ||||
| 
 | ||||
|  | @ -881,7 +912,8 @@ static ResultCode GetProcessIdOfThread(u32* process_id, Handle thread_handle) { | |||
| static ResultCode GetThreadId(u32* thread_id, Handle handle) { | ||||
|     LOG_TRACE(Kernel_SVC, "called thread=0x{:08X}", handle); | ||||
| 
 | ||||
|     const SharedPtr<Thread> thread = g_handle_table.Get<Thread>(handle); | ||||
|     const SharedPtr<Thread> thread = | ||||
|         Core::System::GetInstance().Kernel().GetCurrentProcess()->handle_table.Get<Thread>(handle); | ||||
|     if (thread == nullptr) | ||||
|         return ERR_INVALID_HANDLE; | ||||
| 
 | ||||
|  | @ -891,9 +923,12 @@ static ResultCode GetThreadId(u32* thread_id, Handle handle) { | |||
| 
 | ||||
| /// Creates a semaphore
 | ||||
| static ResultCode CreateSemaphore(Handle* out_handle, s32 initial_count, s32 max_count) { | ||||
|     CASCADE_RESULT(SharedPtr<Semaphore> semaphore, Semaphore::Create(initial_count, max_count)); | ||||
|     KernelSystem& kernel = Core::System::GetInstance().Kernel(); | ||||
|     CASCADE_RESULT(SharedPtr<Semaphore> semaphore, | ||||
|                    kernel.CreateSemaphore(initial_count, max_count)); | ||||
|     semaphore->name = fmt::format("semaphore-{:08x}", Core::CPU().GetReg(14)); | ||||
|     CASCADE_RESULT(*out_handle, g_handle_table.Create(std::move(semaphore))); | ||||
|     CASCADE_RESULT(*out_handle, | ||||
|                    kernel.GetCurrentProcess()->handle_table.Create(std::move(semaphore))); | ||||
| 
 | ||||
|     LOG_TRACE(Kernel_SVC, "called initial_count={}, max_count={}, created handle=0x{:08X}", | ||||
|               initial_count, max_count, *out_handle); | ||||
|  | @ -904,7 +939,9 @@ static ResultCode CreateSemaphore(Handle* out_handle, s32 initial_count, s32 max | |||
| static ResultCode ReleaseSemaphore(s32* count, Handle handle, s32 release_count) { | ||||
|     LOG_TRACE(Kernel_SVC, "called release_count={}, handle=0x{:08X}", release_count, handle); | ||||
| 
 | ||||
|     SharedPtr<Semaphore> semaphore = g_handle_table.Get<Semaphore>(handle); | ||||
|     SharedPtr<Semaphore> semaphore = | ||||
|         Core::System::GetInstance().Kernel().GetCurrentProcess()->handle_table.Get<Semaphore>( | ||||
|             handle); | ||||
|     if (semaphore == nullptr) | ||||
|         return ERR_INVALID_HANDLE; | ||||
| 
 | ||||
|  | @ -916,13 +953,15 @@ static ResultCode ReleaseSemaphore(s32* count, Handle handle, s32 release_count) | |||
| /// Query process memory
 | ||||
| static ResultCode QueryProcessMemory(MemoryInfo* memory_info, PageInfo* page_info, | ||||
|                                      Handle process_handle, u32 addr) { | ||||
|     SharedPtr<Process> process = g_handle_table.Get<Process>(process_handle); | ||||
|     SharedPtr<Process> process = | ||||
|         Core::System::GetInstance().Kernel().GetCurrentProcess()->handle_table.Get<Process>( | ||||
|             process_handle); | ||||
|     if (process == nullptr) | ||||
|         return ERR_INVALID_HANDLE; | ||||
| 
 | ||||
|     auto vma = process->vm_manager.FindVMA(addr); | ||||
| 
 | ||||
|     if (vma == g_current_process->vm_manager.vma_map.end()) | ||||
|     if (vma == process->vm_manager.vma_map.end()) | ||||
|         return ERR_INVALID_ADDRESS; | ||||
| 
 | ||||
|     memory_info->base_address = vma->second.base; | ||||
|  | @ -942,9 +981,10 @@ static ResultCode QueryMemory(MemoryInfo* memory_info, PageInfo* page_info, u32 | |||
| 
 | ||||
| /// Create an event
 | ||||
| static ResultCode CreateEvent(Handle* out_handle, u32 reset_type) { | ||||
|     SharedPtr<Event> evt = Event::Create(static_cast<ResetType>(reset_type), | ||||
|                                          fmt::format("event-{:08x}", Core::CPU().GetReg(14))); | ||||
|     CASCADE_RESULT(*out_handle, g_handle_table.Create(std::move(evt))); | ||||
|     KernelSystem& kernel = Core::System::GetInstance().Kernel(); | ||||
|     SharedPtr<Event> evt = kernel.CreateEvent(static_cast<ResetType>(reset_type), | ||||
|                                               fmt::format("event-{:08x}", Core::CPU().GetReg(14))); | ||||
|     CASCADE_RESULT(*out_handle, kernel.GetCurrentProcess()->handle_table.Create(std::move(evt))); | ||||
| 
 | ||||
|     LOG_TRACE(Kernel_SVC, "called reset_type=0x{:08X} : created handle=0x{:08X}", reset_type, | ||||
|               *out_handle); | ||||
|  | @ -953,7 +993,9 @@ static ResultCode CreateEvent(Handle* out_handle, u32 reset_type) { | |||
| 
 | ||||
| /// Duplicates a kernel handle
 | ||||
| static ResultCode DuplicateHandle(Handle* out, Handle handle) { | ||||
|     CASCADE_RESULT(*out, g_handle_table.Duplicate(handle)); | ||||
|     CASCADE_RESULT( | ||||
|         *out, | ||||
|         Core::System::GetInstance().Kernel().GetCurrentProcess()->handle_table.Duplicate(handle)); | ||||
|     LOG_TRACE(Kernel_SVC, "duplicated 0x{:08X} to 0x{:08X}", handle, *out); | ||||
|     return RESULT_SUCCESS; | ||||
| } | ||||
|  | @ -962,7 +1004,8 @@ static ResultCode DuplicateHandle(Handle* out, Handle handle) { | |||
| static ResultCode SignalEvent(Handle handle) { | ||||
|     LOG_TRACE(Kernel_SVC, "called event=0x{:08X}", handle); | ||||
| 
 | ||||
|     SharedPtr<Event> evt = g_handle_table.Get<Event>(handle); | ||||
|     SharedPtr<Event> evt = | ||||
|         Core::System::GetInstance().Kernel().GetCurrentProcess()->handle_table.Get<Event>(handle); | ||||
|     if (evt == nullptr) | ||||
|         return ERR_INVALID_HANDLE; | ||||
| 
 | ||||
|  | @ -975,7 +1018,8 @@ static ResultCode SignalEvent(Handle handle) { | |||
| static ResultCode ClearEvent(Handle handle) { | ||||
|     LOG_TRACE(Kernel_SVC, "called event=0x{:08X}", handle); | ||||
| 
 | ||||
|     SharedPtr<Event> evt = g_handle_table.Get<Event>(handle); | ||||
|     SharedPtr<Event> evt = | ||||
|         Core::System::GetInstance().Kernel().GetCurrentProcess()->handle_table.Get<Event>(handle); | ||||
|     if (evt == nullptr) | ||||
|         return ERR_INVALID_HANDLE; | ||||
| 
 | ||||
|  | @ -985,9 +1029,10 @@ static ResultCode ClearEvent(Handle handle) { | |||
| 
 | ||||
| /// Creates a timer
 | ||||
| static ResultCode CreateTimer(Handle* out_handle, u32 reset_type) { | ||||
|     SharedPtr<Timer> timer = Timer::Create(static_cast<ResetType>(reset_type), | ||||
|                                            fmt ::format("timer-{:08x}", Core::CPU().GetReg(14))); | ||||
|     CASCADE_RESULT(*out_handle, g_handle_table.Create(std::move(timer))); | ||||
|     KernelSystem& kernel = Core::System::GetInstance().Kernel(); | ||||
|     SharedPtr<Timer> timer = kernel.CreateTimer( | ||||
|         static_cast<ResetType>(reset_type), fmt ::format("timer-{:08x}", Core::CPU().GetReg(14))); | ||||
|     CASCADE_RESULT(*out_handle, kernel.GetCurrentProcess()->handle_table.Create(std::move(timer))); | ||||
| 
 | ||||
|     LOG_TRACE(Kernel_SVC, "called reset_type=0x{:08X} : created handle=0x{:08X}", reset_type, | ||||
|               *out_handle); | ||||
|  | @ -998,7 +1043,8 @@ static ResultCode CreateTimer(Handle* out_handle, u32 reset_type) { | |||
| static ResultCode ClearTimer(Handle handle) { | ||||
|     LOG_TRACE(Kernel_SVC, "called timer=0x{:08X}", handle); | ||||
| 
 | ||||
|     SharedPtr<Timer> timer = g_handle_table.Get<Timer>(handle); | ||||
|     SharedPtr<Timer> timer = | ||||
|         Core::System::GetInstance().Kernel().GetCurrentProcess()->handle_table.Get<Timer>(handle); | ||||
|     if (timer == nullptr) | ||||
|         return ERR_INVALID_HANDLE; | ||||
| 
 | ||||
|  | @ -1014,7 +1060,8 @@ static ResultCode SetTimer(Handle handle, s64 initial, s64 interval) { | |||
|         return ERR_OUT_OF_RANGE_KERNEL; | ||||
|     } | ||||
| 
 | ||||
|     SharedPtr<Timer> timer = g_handle_table.Get<Timer>(handle); | ||||
|     SharedPtr<Timer> timer = | ||||
|         Core::System::GetInstance().Kernel().GetCurrentProcess()->handle_table.Get<Timer>(handle); | ||||
|     if (timer == nullptr) | ||||
|         return ERR_INVALID_HANDLE; | ||||
| 
 | ||||
|  | @ -1027,7 +1074,8 @@ static ResultCode SetTimer(Handle handle, s64 initial, s64 interval) { | |||
| static ResultCode CancelTimer(Handle handle) { | ||||
|     LOG_TRACE(Kernel_SVC, "called timer=0x{:08X}", handle); | ||||
| 
 | ||||
|     SharedPtr<Timer> timer = g_handle_table.Get<Timer>(handle); | ||||
|     SharedPtr<Timer> timer = | ||||
|         Core::System::GetInstance().Kernel().GetCurrentProcess()->handle_table.Get<Timer>(handle); | ||||
|     if (timer == nullptr) | ||||
|         return ERR_INVALID_HANDLE; | ||||
| 
 | ||||
|  | @ -1040,25 +1088,29 @@ static ResultCode CancelTimer(Handle handle) { | |||
| static void SleepThread(s64 nanoseconds) { | ||||
|     LOG_TRACE(Kernel_SVC, "called nanoseconds={}", nanoseconds); | ||||
| 
 | ||||
|     KernelSystem& kernel = Core::System::GetInstance().Kernel(); | ||||
|     ThreadManager& thread_manager = kernel.GetThreadManager(); | ||||
| 
 | ||||
|     // Don't attempt to yield execution if there are no available threads to run,
 | ||||
|     // this way we avoid a useless reschedule to the idle thread.
 | ||||
|     if (nanoseconds == 0 && !HaveReadyThreads()) | ||||
|     if (nanoseconds == 0 && !thread_manager.HaveReadyThreads()) | ||||
|         return; | ||||
| 
 | ||||
|     // Sleep current thread and check for next thread to schedule
 | ||||
|     WaitCurrentThread_Sleep(); | ||||
|     thread_manager.WaitCurrentThread_Sleep(); | ||||
| 
 | ||||
|     // Create an event to wake the thread up after the specified nanosecond delay has passed
 | ||||
|     GetCurrentThread()->WakeAfterDelay(nanoseconds); | ||||
|     thread_manager.GetCurrentThread()->WakeAfterDelay(nanoseconds); | ||||
| 
 | ||||
|     Core::System::GetInstance().PrepareReschedule(); | ||||
| } | ||||
| 
 | ||||
| /// This returns the total CPU ticks elapsed since the CPU was powered-on
 | ||||
| static s64 GetSystemTick() { | ||||
|     s64 result = CoreTiming::GetTicks(); | ||||
|     s64 result = Core::System::GetInstance().CoreTiming().GetTicks(); | ||||
|     // Advance time to defeat dumb games (like Cubic Ninja) that busy-wait for the frame to end.
 | ||||
|     CoreTiming::AddTicks(150); // Measured time between two calls on a 9.2 o3DS with Ninjhax 1.1b
 | ||||
|     // Measured time between two calls on a 9.2 o3DS with Ninjhax 1.1b
 | ||||
|     Core::System::GetInstance().CoreTiming().AddTicks(150); | ||||
|     return result; | ||||
| } | ||||
| 
 | ||||
|  | @ -1096,18 +1148,20 @@ static ResultCode CreateMemoryBlock(Handle* out_handle, u32 addr, u32 size, u32 | |||
|         return ERR_INVALID_ADDRESS; | ||||
|     } | ||||
| 
 | ||||
|     SharedPtr<Process> current_process = Core::System::GetInstance().Kernel().GetCurrentProcess(); | ||||
| 
 | ||||
|     // When trying to create a memory block with address = 0,
 | ||||
|     // if the process has the Shared Device Memory flag in the exheader,
 | ||||
|     // then we have to allocate from the same region as the caller process instead of the BASE
 | ||||
|     // region.
 | ||||
|     MemoryRegion region = MemoryRegion::BASE; | ||||
|     if (addr == 0 && g_current_process->flags.shared_device_mem) | ||||
|         region = g_current_process->flags.memory_region; | ||||
|     if (addr == 0 && current_process->flags.shared_device_mem) | ||||
|         region = current_process->flags.memory_region; | ||||
| 
 | ||||
|     shared_memory = | ||||
|         SharedMemory::Create(g_current_process, size, static_cast<MemoryPermission>(my_permission), | ||||
|                              static_cast<MemoryPermission>(other_permission), addr, region); | ||||
|     CASCADE_RESULT(*out_handle, g_handle_table.Create(std::move(shared_memory))); | ||||
|     shared_memory = Core::System::GetInstance().Kernel().CreateSharedMemory( | ||||
|         current_process.get(), size, static_cast<MemoryPermission>(my_permission), | ||||
|         static_cast<MemoryPermission>(other_permission), addr, region); | ||||
|     CASCADE_RESULT(*out_handle, current_process->handle_table.Create(std::move(shared_memory))); | ||||
| 
 | ||||
|     LOG_WARNING(Kernel_SVC, "called addr=0x{:08X}", addr); | ||||
|     return RESULT_SUCCESS; | ||||
|  | @ -1118,70 +1172,82 @@ static ResultCode CreatePort(Handle* server_port, Handle* client_port, VAddr nam | |||
|     // TODO(Subv): Implement named ports.
 | ||||
|     ASSERT_MSG(name_address == 0, "Named ports are currently unimplemented"); | ||||
| 
 | ||||
|     auto ports = ServerPort::CreatePortPair(max_sessions); | ||||
|     CASCADE_RESULT(*client_port, | ||||
|                    g_handle_table.Create(std::move(std::get<SharedPtr<ClientPort>>(ports)))); | ||||
|     KernelSystem& kernel = Core::System::GetInstance().Kernel(); | ||||
|     SharedPtr<Process> current_process = kernel.GetCurrentProcess(); | ||||
| 
 | ||||
|     auto ports = kernel.CreatePortPair(max_sessions); | ||||
|     CASCADE_RESULT(*client_port, current_process->handle_table.Create( | ||||
|                                      std::move(std::get<SharedPtr<ClientPort>>(ports)))); | ||||
|     // Note: The 3DS kernel also leaks the client port handle if the server port handle fails to be
 | ||||
|     // created.
 | ||||
|     CASCADE_RESULT(*server_port, | ||||
|                    g_handle_table.Create(std::move(std::get<SharedPtr<ServerPort>>(ports)))); | ||||
|     CASCADE_RESULT(*server_port, current_process->handle_table.Create( | ||||
|                                      std::move(std::get<SharedPtr<ServerPort>>(ports)))); | ||||
| 
 | ||||
|     LOG_TRACE(Kernel_SVC, "called max_sessions={}", max_sessions); | ||||
|     return RESULT_SUCCESS; | ||||
| } | ||||
| 
 | ||||
| static ResultCode CreateSessionToPort(Handle* out_client_session, Handle client_port_handle) { | ||||
|     SharedPtr<ClientPort> client_port = g_handle_table.Get<ClientPort>(client_port_handle); | ||||
|     SharedPtr<Process> current_process = Core::System::GetInstance().Kernel().GetCurrentProcess(); | ||||
|     SharedPtr<ClientPort> client_port = | ||||
|         current_process->handle_table.Get<ClientPort>(client_port_handle); | ||||
|     if (client_port == nullptr) | ||||
|         return ERR_INVALID_HANDLE; | ||||
| 
 | ||||
|     CASCADE_RESULT(auto session, client_port->Connect()); | ||||
|     CASCADE_RESULT(*out_client_session, g_handle_table.Create(std::move(session))); | ||||
|     CASCADE_RESULT(*out_client_session, current_process->handle_table.Create(std::move(session))); | ||||
|     return RESULT_SUCCESS; | ||||
| } | ||||
| 
 | ||||
| static ResultCode CreateSession(Handle* server_session, Handle* client_session) { | ||||
|     auto sessions = ServerSession::CreateSessionPair(); | ||||
|     KernelSystem& kernel = Core::System::GetInstance().Kernel(); | ||||
|     auto sessions = kernel.CreateSessionPair(); | ||||
| 
 | ||||
|     SharedPtr<Process> current_process = kernel.GetCurrentProcess(); | ||||
| 
 | ||||
|     auto& server = std::get<SharedPtr<ServerSession>>(sessions); | ||||
|     CASCADE_RESULT(*server_session, g_handle_table.Create(std::move(server))); | ||||
|     CASCADE_RESULT(*server_session, current_process->handle_table.Create(std::move(server))); | ||||
| 
 | ||||
|     auto& client = std::get<SharedPtr<ClientSession>>(sessions); | ||||
|     CASCADE_RESULT(*client_session, g_handle_table.Create(std::move(client))); | ||||
|     CASCADE_RESULT(*client_session, current_process->handle_table.Create(std::move(client))); | ||||
| 
 | ||||
|     LOG_TRACE(Kernel_SVC, "called"); | ||||
|     return RESULT_SUCCESS; | ||||
| } | ||||
| 
 | ||||
| static ResultCode AcceptSession(Handle* out_server_session, Handle server_port_handle) { | ||||
|     SharedPtr<ServerPort> server_port = g_handle_table.Get<ServerPort>(server_port_handle); | ||||
|     SharedPtr<Process> current_process = Core::System::GetInstance().Kernel().GetCurrentProcess(); | ||||
|     SharedPtr<ServerPort> server_port = | ||||
|         current_process->handle_table.Get<ServerPort>(server_port_handle); | ||||
|     if (server_port == nullptr) | ||||
|         return ERR_INVALID_HANDLE; | ||||
| 
 | ||||
|     CASCADE_RESULT(auto session, server_port->Accept()); | ||||
|     CASCADE_RESULT(*out_server_session, g_handle_table.Create(std::move(session))); | ||||
|     CASCADE_RESULT(*out_server_session, current_process->handle_table.Create(std::move(session))); | ||||
|     return RESULT_SUCCESS; | ||||
| } | ||||
| 
 | ||||
| static ResultCode GetSystemInfo(s64* out, u32 type, s32 param) { | ||||
|     LOG_TRACE(Kernel_SVC, "called type={} param={}", type, param); | ||||
| 
 | ||||
|     KernelSystem& kernel = Core::System::GetInstance().Kernel(); | ||||
| 
 | ||||
|     switch ((SystemInfoType)type) { | ||||
|     case SystemInfoType::REGION_MEMORY_USAGE: | ||||
|         switch ((SystemInfoMemUsageRegion)param) { | ||||
|         case SystemInfoMemUsageRegion::ALL: | ||||
|             *out = GetMemoryRegion(MemoryRegion::APPLICATION)->used + | ||||
|                    GetMemoryRegion(MemoryRegion::SYSTEM)->used + | ||||
|                    GetMemoryRegion(MemoryRegion::BASE)->used; | ||||
|             *out = kernel.GetMemoryRegion(MemoryRegion::APPLICATION)->used + | ||||
|                    kernel.GetMemoryRegion(MemoryRegion::SYSTEM)->used + | ||||
|                    kernel.GetMemoryRegion(MemoryRegion::BASE)->used; | ||||
|             break; | ||||
|         case SystemInfoMemUsageRegion::APPLICATION: | ||||
|             *out = GetMemoryRegion(MemoryRegion::APPLICATION)->used; | ||||
|             *out = kernel.GetMemoryRegion(MemoryRegion::APPLICATION)->used; | ||||
|             break; | ||||
|         case SystemInfoMemUsageRegion::SYSTEM: | ||||
|             *out = GetMemoryRegion(MemoryRegion::SYSTEM)->used; | ||||
|             *out = kernel.GetMemoryRegion(MemoryRegion::SYSTEM)->used; | ||||
|             break; | ||||
|         case SystemInfoMemUsageRegion::BASE: | ||||
|             *out = GetMemoryRegion(MemoryRegion::BASE)->used; | ||||
|             *out = kernel.GetMemoryRegion(MemoryRegion::BASE)->used; | ||||
|             break; | ||||
|         default: | ||||
|             LOG_ERROR(Kernel_SVC, "unknown GetSystemInfo type=0 region: param={}", param); | ||||
|  | @ -1209,7 +1275,9 @@ static ResultCode GetSystemInfo(s64* out, u32 type, s32 param) { | |||
| static ResultCode GetProcessInfo(s64* out, Handle process_handle, u32 type) { | ||||
|     LOG_TRACE(Kernel_SVC, "called process=0x{:08X} type={}", process_handle, type); | ||||
| 
 | ||||
|     SharedPtr<Process> process = g_handle_table.Get<Process>(process_handle); | ||||
|     SharedPtr<Process> process = | ||||
|         Core::System::GetInstance().Kernel().GetCurrentProcess()->handle_table.Get<Process>( | ||||
|             process_handle); | ||||
|     if (process == nullptr) | ||||
|         return ERR_INVALID_HANDLE; | ||||
| 
 | ||||
|  | @ -1218,7 +1286,7 @@ static ResultCode GetProcessInfo(s64* out, Handle process_handle, u32 type) { | |||
|     case 2: | ||||
|         // TODO(yuriks): Type 0 returns a slightly higher number than type 2, but I'm not sure
 | ||||
|         // what's the difference between them.
 | ||||
|         *out = process->heap_used + process->linear_heap_used + process->misc_memory_used; | ||||
|         *out = process->memory_used; | ||||
|         if (*out % Memory::PAGE_SIZE != 0) { | ||||
|             LOG_ERROR(Kernel_SVC, "called, memory size not page-aligned"); | ||||
|             return ERR_MISALIGNED_SIZE; | ||||
|  | @ -1406,8 +1474,9 @@ void CallSVC(u32 immediate) { | |||
|     // Lock the global kernel mutex when we enter the kernel HLE.
 | ||||
|     std::lock_guard<std::recursive_mutex> lock(HLE::g_hle_lock); | ||||
| 
 | ||||
|     ASSERT_MSG(g_current_process->status == ProcessStatus::Running, | ||||
|                "Running threads from exiting processes is unimplemented"); | ||||
|     DEBUG_ASSERT_MSG(Core::System::GetInstance().Kernel().GetCurrentProcess()->status == | ||||
|                          ProcessStatus::Running, | ||||
|                      "Running threads from exiting processes is unimplemented"); | ||||
| 
 | ||||
|     const FunctionDef* info = GetSVCInfo(immediate); | ||||
|     if (info) { | ||||
|  |  | |||
|  | @ -4,16 +4,15 @@ | |||
| 
 | ||||
| #include <algorithm> | ||||
| #include <list> | ||||
| #include <unordered_map> | ||||
| #include <vector> | ||||
| #include "common/assert.h" | ||||
| #include "common/common_types.h" | ||||
| #include "common/logging/log.h" | ||||
| #include "common/math_util.h" | ||||
| #include "common/thread_queue_list.h" | ||||
| #include "core/arm/arm_interface.h" | ||||
| #include "core/arm/skyeye_common/armstate.h" | ||||
| #include "core/core.h" | ||||
| #include "core/core_timing.h" | ||||
| #include "core/hle/kernel/errors.h" | ||||
| #include "core/hle/kernel/handle_table.h" | ||||
| #include "core/hle/kernel/kernel.h" | ||||
|  | @ -26,9 +25,6 @@ | |||
| 
 | ||||
| namespace Kernel { | ||||
| 
 | ||||
| /// Event type for the thread wake up event
 | ||||
| static CoreTiming::EventType* ThreadWakeupEventType = nullptr; | ||||
| 
 | ||||
| bool Thread::ShouldWait(Thread* thread) const { | ||||
|     return status != ThreadStatus::Dead; | ||||
| } | ||||
|  | @ -37,46 +33,29 @@ void Thread::Acquire(Thread* thread) { | |||
|     ASSERT_MSG(!ShouldWait(thread), "object unavailable!"); | ||||
| } | ||||
| 
 | ||||
| // TODO(yuriks): This can be removed if Thread objects are explicitly pooled in the future, allowing
 | ||||
| //               us to simply use a pool index or similar.
 | ||||
| static Kernel::HandleTable wakeup_callback_handle_table; | ||||
| 
 | ||||
| // Lists all thread ids that aren't deleted/etc.
 | ||||
| static std::vector<SharedPtr<Thread>> thread_list; | ||||
| 
 | ||||
| // Lists only ready thread ids.
 | ||||
| static Common::ThreadQueueList<Thread*, ThreadPrioLowest + 1> ready_queue; | ||||
| 
 | ||||
| static SharedPtr<Thread> current_thread; | ||||
| 
 | ||||
| // The first available thread id at startup
 | ||||
| static u32 next_thread_id; | ||||
| 
 | ||||
| /**
 | ||||
|  * Creates a new thread ID | ||||
|  * @return The new thread ID | ||||
|  */ | ||||
| inline static u32 const NewThreadId() { | ||||
| u32 ThreadManager::NewThreadId() { | ||||
|     return next_thread_id++; | ||||
| } | ||||
| 
 | ||||
| Thread::Thread() : context(Core::CPU().NewContext()) {} | ||||
| Thread::Thread(KernelSystem& kernel) | ||||
|     : WaitObject(kernel), context(Core::CPU().NewContext()), | ||||
|       thread_manager(kernel.GetThreadManager()) {} | ||||
| Thread::~Thread() {} | ||||
| 
 | ||||
| Thread* GetCurrentThread() { | ||||
| Thread* ThreadManager::GetCurrentThread() const { | ||||
|     return current_thread.get(); | ||||
| } | ||||
| 
 | ||||
| void Thread::Stop() { | ||||
|     // Cancel any outstanding wakeup events for this thread
 | ||||
|     CoreTiming::UnscheduleEvent(ThreadWakeupEventType, callback_handle); | ||||
|     wakeup_callback_handle_table.Close(callback_handle); | ||||
|     callback_handle = 0; | ||||
|     Core::System::GetInstance().CoreTiming().UnscheduleEvent(thread_manager.ThreadWakeupEventType, | ||||
|                                                              thread_id); | ||||
|     thread_manager.wakeup_callback_table.erase(thread_id); | ||||
| 
 | ||||
|     // Clean up thread from ready queue
 | ||||
|     // This is only needed when the thread is termintated forcefully (SVC TerminateProcess)
 | ||||
|     if (status == ThreadStatus::Ready) { | ||||
|         ready_queue.remove(current_priority, this); | ||||
|         thread_manager.ready_queue.remove(current_priority, this); | ||||
|     } | ||||
| 
 | ||||
|     status = ThreadStatus::Dead; | ||||
|  | @ -96,19 +75,17 @@ void Thread::Stop() { | |||
|     u32 tls_page = (tls_address - Memory::TLS_AREA_VADDR) / Memory::PAGE_SIZE; | ||||
|     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); | ||||
|     owner_process->tls_slots[tls_page].reset(tls_slot); | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * Switches the CPU's active thread context to that of the specified thread | ||||
|  * @param new_thread The thread to switch to | ||||
|  */ | ||||
| static void SwitchContext(Thread* new_thread) { | ||||
| void ThreadManager::SwitchContext(Thread* new_thread) { | ||||
|     Thread* previous_thread = GetCurrentThread(); | ||||
| 
 | ||||
|     Core::Timing& timing = Core::System::GetInstance().CoreTiming(); | ||||
| 
 | ||||
|     // Save context for previous thread
 | ||||
|     if (previous_thread) { | ||||
|         previous_thread->last_running_ticks = CoreTiming::GetTicks(); | ||||
|         previous_thread->last_running_ticks = timing.GetTicks(); | ||||
|         Core::CPU().SaveContext(previous_thread->context); | ||||
| 
 | ||||
|         if (previous_thread->status == ThreadStatus::Running) { | ||||
|  | @ -125,9 +102,9 @@ static void SwitchContext(Thread* new_thread) { | |||
|                    "Thread must be ready to become running."); | ||||
| 
 | ||||
|         // Cancel any outstanding wakeup events for this thread
 | ||||
|         CoreTiming::UnscheduleEvent(ThreadWakeupEventType, new_thread->callback_handle); | ||||
|         timing.UnscheduleEvent(ThreadWakeupEventType, new_thread->thread_id); | ||||
| 
 | ||||
|         auto previous_process = Kernel::g_current_process; | ||||
|         auto previous_process = Core::System::GetInstance().Kernel().GetCurrentProcess(); | ||||
| 
 | ||||
|         current_thread = new_thread; | ||||
| 
 | ||||
|  | @ -135,8 +112,8 @@ static void SwitchContext(Thread* new_thread) { | |||
|         new_thread->status = ThreadStatus::Running; | ||||
| 
 | ||||
|         if (previous_process != current_thread->owner_process) { | ||||
|             Kernel::g_current_process = current_thread->owner_process; | ||||
|             SetCurrentPageTable(&Kernel::g_current_process->vm_manager.page_table); | ||||
|             Core::System::GetInstance().Kernel().SetCurrentProcess(current_thread->owner_process); | ||||
|             SetCurrentPageTable(¤t_thread->owner_process->vm_manager.page_table); | ||||
|         } | ||||
| 
 | ||||
|         Core::CPU().LoadContext(new_thread->context); | ||||
|  | @ -148,11 +125,7 @@ static void SwitchContext(Thread* new_thread) { | |||
|     } | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * Pops and returns the next thread from the thread queue | ||||
|  * @return A pointer to the next ready thread | ||||
|  */ | ||||
| static Thread* PopNextReadyThread() { | ||||
| Thread* ThreadManager::PopNextReadyThread() { | ||||
|     Thread* next; | ||||
|     Thread* thread = GetCurrentThread(); | ||||
| 
 | ||||
|  | @ -171,27 +144,22 @@ static Thread* PopNextReadyThread() { | |||
|     return next; | ||||
| } | ||||
| 
 | ||||
| void WaitCurrentThread_Sleep() { | ||||
| void ThreadManager::WaitCurrentThread_Sleep() { | ||||
|     Thread* thread = GetCurrentThread(); | ||||
|     thread->status = ThreadStatus::WaitSleep; | ||||
| } | ||||
| 
 | ||||
| void ExitCurrentThread() { | ||||
| void ThreadManager::ExitCurrentThread() { | ||||
|     Thread* thread = GetCurrentThread(); | ||||
|     thread->Stop(); | ||||
|     thread_list.erase(std::remove(thread_list.begin(), thread_list.end(), thread), | ||||
|                       thread_list.end()); | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * Callback that will wake up the thread it was scheduled for | ||||
|  * @param thread_handle The handle of the thread that's been awoken | ||||
|  * @param cycles_late The number of CPU cycles that have passed since the desired wakeup time | ||||
|  */ | ||||
| static void ThreadWakeupCallback(u64 thread_handle, s64 cycles_late) { | ||||
|     SharedPtr<Thread> thread = wakeup_callback_handle_table.Get<Thread>((Handle)thread_handle); | ||||
| void ThreadManager::ThreadWakeupCallback(u64 thread_id, s64 cycles_late) { | ||||
|     SharedPtr<Thread> thread = wakeup_callback_table.at(thread_id); | ||||
|     if (thread == nullptr) { | ||||
|         LOG_CRITICAL(Kernel, "Callback fired for invalid thread {:08X}", (Handle)thread_handle); | ||||
|         LOG_CRITICAL(Kernel, "Callback fired for invalid thread {:08X}", thread_id); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|  | @ -217,7 +185,8 @@ void Thread::WakeAfterDelay(s64 nanoseconds) { | |||
|     if (nanoseconds == -1) | ||||
|         return; | ||||
| 
 | ||||
|     CoreTiming::ScheduleEvent(nsToCycles(nanoseconds), ThreadWakeupEventType, callback_handle); | ||||
|     Core::System::GetInstance().CoreTiming().ScheduleEvent( | ||||
|         nsToCycles(nanoseconds), thread_manager.ThreadWakeupEventType, thread_id); | ||||
| } | ||||
| 
 | ||||
| void Thread::ResumeFromWait() { | ||||
|  | @ -253,15 +222,12 @@ void Thread::ResumeFromWait() { | |||
| 
 | ||||
|     wakeup_callback = nullptr; | ||||
| 
 | ||||
|     ready_queue.push_back(current_priority, this); | ||||
|     thread_manager.ready_queue.push_back(current_priority, this); | ||||
|     status = ThreadStatus::Ready; | ||||
|     Core::System::GetInstance().PrepareReschedule(); | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * Prints the thread queue for debugging purposes | ||||
|  */ | ||||
| static void DebugThreadQueue() { | ||||
| void ThreadManager::DebugThreadQueue() { | ||||
|     Thread* thread = GetCurrentThread(); | ||||
|     if (!thread) { | ||||
|         LOG_DEBUG(Kernel, "Current: NO CURRENT THREAD"); | ||||
|  | @ -320,9 +286,9 @@ static void ResetThreadContext(const std::unique_ptr<ARM_Interface::ThreadContex | |||
|     context->SetCpsr(USER32MODE | ((entry_point & 1) << 5)); // Usermode and THUMB mode
 | ||||
| } | ||||
| 
 | ||||
| ResultVal<SharedPtr<Thread>> Thread::Create(std::string name, VAddr entry_point, u32 priority, | ||||
|                                             u32 arg, s32 processor_id, VAddr stack_top, | ||||
|                                             SharedPtr<Process> owner_process) { | ||||
| ResultVal<SharedPtr<Thread>> KernelSystem::CreateThread(std::string name, VAddr entry_point, | ||||
|                                                         u32 priority, u32 arg, s32 processor_id, | ||||
|                                                         VAddr stack_top, Process& owner_process) { | ||||
|     // Check if priority is in ranged. Lowest priority -> highest priority id.
 | ||||
|     if (priority > ThreadPrioLowest) { | ||||
|         LOG_ERROR(Kernel_SVC, "Invalid thread priority: {}", priority); | ||||
|  | @ -336,33 +302,33 @@ ResultVal<SharedPtr<Thread>> Thread::Create(std::string name, VAddr entry_point, | |||
| 
 | ||||
|     // TODO(yuriks): Other checks, returning 0xD9001BEA
 | ||||
| 
 | ||||
|     if (!Memory::IsValidVirtualAddress(*owner_process, entry_point)) { | ||||
|     if (!Memory::IsValidVirtualAddress(owner_process, entry_point)) { | ||||
|         LOG_ERROR(Kernel_SVC, "(name={}): invalid entry {:08x}", name, entry_point); | ||||
|         // TODO: Verify error
 | ||||
|         return ResultCode(ErrorDescription::InvalidAddress, ErrorModule::Kernel, | ||||
|                           ErrorSummary::InvalidArgument, ErrorLevel::Permanent); | ||||
|     } | ||||
| 
 | ||||
|     SharedPtr<Thread> thread(new Thread); | ||||
|     SharedPtr<Thread> thread(new Thread(*this)); | ||||
| 
 | ||||
|     thread_list.push_back(thread); | ||||
|     ready_queue.prepare(priority); | ||||
|     thread_manager->thread_list.push_back(thread); | ||||
|     thread_manager->ready_queue.prepare(priority); | ||||
| 
 | ||||
|     thread->thread_id = NewThreadId(); | ||||
|     thread->thread_id = thread_manager->NewThreadId(); | ||||
|     thread->status = ThreadStatus::Dormant; | ||||
|     thread->entry_point = entry_point; | ||||
|     thread->stack_top = stack_top; | ||||
|     thread->nominal_priority = thread->current_priority = priority; | ||||
|     thread->last_running_ticks = CoreTiming::GetTicks(); | ||||
|     thread->last_running_ticks = Core::System::GetInstance().CoreTiming().GetTicks(); | ||||
|     thread->processor_id = processor_id; | ||||
|     thread->wait_objects.clear(); | ||||
|     thread->wait_address = 0; | ||||
|     thread->name = std::move(name); | ||||
|     thread->callback_handle = wakeup_callback_handle_table.Create(thread).Unwrap(); | ||||
|     thread->owner_process = owner_process; | ||||
|     thread_manager->wakeup_callback_table[thread->thread_id] = thread.get(); | ||||
|     thread->owner_process = &owner_process; | ||||
| 
 | ||||
|     // Find the next available TLS index, and mark it as used
 | ||||
|     auto& tls_slots = owner_process->tls_slots; | ||||
|     auto& tls_slots = owner_process.tls_slots; | ||||
| 
 | ||||
|     auto [available_page, available_slot, needs_allocation] = GetFreeThreadLocalSlot(tls_slots); | ||||
| 
 | ||||
|  | @ -370,32 +336,26 @@ ResultVal<SharedPtr<Thread>> Thread::Create(std::string name, VAddr entry_point, | |||
|         // There are no already-allocated pages with free slots, lets allocate a new one.
 | ||||
|         // TLS pages are allocated from the BASE region in the linear heap.
 | ||||
|         MemoryRegionInfo* memory_region = GetMemoryRegion(MemoryRegion::BASE); | ||||
|         auto& linheap_memory = memory_region->linear_heap_memory; | ||||
| 
 | ||||
|         if (linheap_memory->size() + Memory::PAGE_SIZE > memory_region->size) { | ||||
|         // Allocate some memory from the end of the linear heap for this region.
 | ||||
|         auto offset = memory_region->LinearAllocate(Memory::PAGE_SIZE); | ||||
|         if (!offset) { | ||||
|             LOG_ERROR(Kernel_SVC, | ||||
|                       "Not enough space in region to allocate a new TLS page for thread"); | ||||
|             return ERR_OUT_OF_MEMORY; | ||||
|         } | ||||
| 
 | ||||
|         std::size_t offset = linheap_memory->size(); | ||||
| 
 | ||||
|         // Allocate some memory from the end of the linear heap for this region.
 | ||||
|         linheap_memory->insert(linheap_memory->end(), Memory::PAGE_SIZE, 0); | ||||
|         memory_region->used += Memory::PAGE_SIZE; | ||||
|         owner_process->linear_heap_used += Memory::PAGE_SIZE; | ||||
|         owner_process.memory_used += Memory::PAGE_SIZE; | ||||
| 
 | ||||
|         tls_slots.emplace_back(0); // The page is completely available at the start
 | ||||
|         available_page = tls_slots.size() - 1; | ||||
|         available_slot = 0; // Use the first slot in the new page
 | ||||
| 
 | ||||
|         auto& vm_manager = owner_process->vm_manager; | ||||
|         vm_manager.RefreshMemoryBlockMappings(linheap_memory.get()); | ||||
|         auto& vm_manager = owner_process.vm_manager; | ||||
| 
 | ||||
|         // Map the page to the current process' address space.
 | ||||
|         // TODO(Subv): Find the correct MemoryState for this region.
 | ||||
|         vm_manager.MapMemoryBlock(Memory::TLS_AREA_VADDR + available_page * Memory::PAGE_SIZE, | ||||
|                                   linheap_memory, offset, Memory::PAGE_SIZE, MemoryState::Private); | ||||
|         vm_manager.MapBackingMemory(Memory::TLS_AREA_VADDR + available_page * Memory::PAGE_SIZE, | ||||
|                                     Memory::fcram.data() + *offset, Memory::PAGE_SIZE, | ||||
|                                     MemoryState::Locked); | ||||
|     } | ||||
| 
 | ||||
|     // Mark the slot as used
 | ||||
|  | @ -403,11 +363,13 @@ ResultVal<SharedPtr<Thread>> Thread::Create(std::string name, VAddr entry_point, | |||
|     thread->tls_address = Memory::TLS_AREA_VADDR + available_page * Memory::PAGE_SIZE + | ||||
|                           available_slot * Memory::TLS_ENTRY_SIZE; | ||||
| 
 | ||||
|     Memory::ZeroBlock(owner_process, thread->tls_address, Memory::TLS_ENTRY_SIZE); | ||||
| 
 | ||||
|     // TODO(peachum): move to ScheduleThread() when scheduler is added so selected core is used
 | ||||
|     // to initialize the context
 | ||||
|     ResetThreadContext(thread->context, stack_top, entry_point, arg); | ||||
| 
 | ||||
|     ready_queue.push_back(thread->current_priority, thread.get()); | ||||
|     thread_manager->ready_queue.push_back(thread->current_priority, thread.get()); | ||||
|     thread->status = ThreadStatus::Ready; | ||||
| 
 | ||||
|     return MakeResult<SharedPtr<Thread>>(std::move(thread)); | ||||
|  | @ -418,9 +380,9 @@ void Thread::SetPriority(u32 priority) { | |||
|                "Invalid priority value."); | ||||
|     // If thread was ready, adjust queues
 | ||||
|     if (status == ThreadStatus::Ready) | ||||
|         ready_queue.move(this, current_priority, priority); | ||||
|         thread_manager.ready_queue.move(this, current_priority, priority); | ||||
|     else | ||||
|         ready_queue.prepare(priority); | ||||
|         thread_manager.ready_queue.prepare(priority); | ||||
| 
 | ||||
|     nominal_priority = current_priority = priority; | ||||
| } | ||||
|  | @ -437,17 +399,18 @@ void Thread::UpdatePriority() { | |||
| void Thread::BoostPriority(u32 priority) { | ||||
|     // If thread was ready, adjust queues
 | ||||
|     if (status == ThreadStatus::Ready) | ||||
|         ready_queue.move(this, current_priority, priority); | ||||
|         thread_manager.ready_queue.move(this, current_priority, priority); | ||||
|     else | ||||
|         ready_queue.prepare(priority); | ||||
|         thread_manager.ready_queue.prepare(priority); | ||||
|     current_priority = priority; | ||||
| } | ||||
| 
 | ||||
| SharedPtr<Thread> SetupMainThread(u32 entry_point, u32 priority, SharedPtr<Process> owner_process) { | ||||
| SharedPtr<Thread> SetupMainThread(KernelSystem& kernel, u32 entry_point, u32 priority, | ||||
|                                   SharedPtr<Process> owner_process) { | ||||
|     // Initialize new "main" thread
 | ||||
|     auto thread_res = | ||||
|         Thread::Create("main", entry_point, priority, 0, owner_process->ideal_processor, | ||||
|                        Memory::HEAP_VADDR_END, owner_process); | ||||
|         kernel.CreateThread("main", entry_point, priority, 0, owner_process->ideal_processor, | ||||
|                             Memory::HEAP_VADDR_END, *owner_process); | ||||
| 
 | ||||
|     SharedPtr<Thread> thread = std::move(thread_res).Unwrap(); | ||||
| 
 | ||||
|  | @ -458,11 +421,11 @@ SharedPtr<Thread> SetupMainThread(u32 entry_point, u32 priority, SharedPtr<Proce | |||
|     return thread; | ||||
| } | ||||
| 
 | ||||
| bool HaveReadyThreads() { | ||||
| bool ThreadManager::HaveReadyThreads() { | ||||
|     return ready_queue.get_first() != nullptr; | ||||
| } | ||||
| 
 | ||||
| void Reschedule() { | ||||
| void ThreadManager::Reschedule() { | ||||
|     Thread* cur = GetCurrentThread(); | ||||
|     Thread* next = PopNextReadyThread(); | ||||
| 
 | ||||
|  | @ -497,27 +460,19 @@ VAddr Thread::GetCommandBufferAddress() const { | |||
|     return GetTLSAddress() + CommandHeaderOffset; | ||||
| } | ||||
| 
 | ||||
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | ||||
| 
 | ||||
| void ThreadingInit() { | ||||
|     ThreadWakeupEventType = CoreTiming::RegisterEvent("ThreadWakeupCallback", ThreadWakeupCallback); | ||||
| 
 | ||||
|     current_thread = nullptr; | ||||
|     next_thread_id = 1; | ||||
| ThreadManager::ThreadManager() { | ||||
|     ThreadWakeupEventType = Core::System::GetInstance().CoreTiming().RegisterEvent( | ||||
|         "ThreadWakeupCallback", | ||||
|         [this](u64 thread_id, s64 cycle_late) { ThreadWakeupCallback(thread_id, cycle_late); }); | ||||
| } | ||||
| 
 | ||||
| void ThreadingShutdown() { | ||||
|     current_thread = nullptr; | ||||
| 
 | ||||
| ThreadManager::~ThreadManager() { | ||||
|     for (auto& t : thread_list) { | ||||
|         t->Stop(); | ||||
|     } | ||||
|     thread_list.clear(); | ||||
|     ready_queue.clear(); | ||||
|     ClearProcessList(); | ||||
| } | ||||
| 
 | ||||
| const std::vector<SharedPtr<Thread>>& GetThreadList() { | ||||
| const std::vector<SharedPtr<Thread>>& ThreadManager::GetThreadList() { | ||||
|     return thread_list; | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -10,7 +10,9 @@ | |||
| #include <boost/container/flat_map.hpp> | ||||
| #include <boost/container/flat_set.hpp> | ||||
| #include "common/common_types.h" | ||||
| #include "common/thread_queue_list.h" | ||||
| #include "core/arm/arm_interface.h" | ||||
| #include "core/core_timing.h" | ||||
| #include "core/hle/kernel/object.h" | ||||
| #include "core/hle/kernel/wait_object.h" | ||||
| #include "core/hle/result.h" | ||||
|  | @ -53,23 +55,89 @@ enum class ThreadWakeupReason { | |||
|     Timeout // The thread was woken up due to a wait timeout.
 | ||||
| }; | ||||
| 
 | ||||
| class ThreadManager { | ||||
| public: | ||||
|     ThreadManager(); | ||||
|     ~ThreadManager(); | ||||
| 
 | ||||
|     /**
 | ||||
|      * Creates a new thread ID | ||||
|      * @return The new thread ID | ||||
|      */ | ||||
|     u32 NewThreadId(); | ||||
| 
 | ||||
|     /**
 | ||||
|      * Gets the current thread | ||||
|      */ | ||||
|     Thread* GetCurrentThread() const; | ||||
| 
 | ||||
|     /**
 | ||||
|      * Reschedules to the next available thread (call after current thread is suspended) | ||||
|      */ | ||||
|     void Reschedule(); | ||||
| 
 | ||||
|     /**
 | ||||
|      * Prints the thread queue for debugging purposes | ||||
|      */ | ||||
|     void DebugThreadQueue(); | ||||
| 
 | ||||
|     /**
 | ||||
|      * Returns whether there are any threads that are ready to run. | ||||
|      */ | ||||
|     bool HaveReadyThreads(); | ||||
| 
 | ||||
|     /**
 | ||||
|      * Waits the current thread on a sleep | ||||
|      */ | ||||
|     void WaitCurrentThread_Sleep(); | ||||
| 
 | ||||
|     /**
 | ||||
|      * Stops the current thread and removes it from the thread_list | ||||
|      */ | ||||
|     void ExitCurrentThread(); | ||||
| 
 | ||||
|     /**
 | ||||
|      * Get a const reference to the thread list for debug use | ||||
|      */ | ||||
|     const std::vector<SharedPtr<Thread>>& GetThreadList(); | ||||
| 
 | ||||
| private: | ||||
|     /**
 | ||||
|      * Switches the CPU's active thread context to that of the specified thread | ||||
|      * @param new_thread The thread to switch to | ||||
|      */ | ||||
|     void SwitchContext(Thread* new_thread); | ||||
| 
 | ||||
|     /**
 | ||||
|      * Pops and returns the next thread from the thread queue | ||||
|      * @return A pointer to the next ready thread | ||||
|      */ | ||||
|     Thread* PopNextReadyThread(); | ||||
| 
 | ||||
|     /**
 | ||||
|      * Callback that will wake up the thread it was scheduled for | ||||
|      * @param thread_id The ID of the thread that's been awoken | ||||
|      * @param cycles_late The number of CPU cycles that have passed since the desired wakeup time | ||||
|      */ | ||||
|     void ThreadWakeupCallback(u64 thread_id, s64 cycles_late); | ||||
| 
 | ||||
|     u32 next_thread_id = 1; | ||||
|     SharedPtr<Thread> current_thread; | ||||
|     Common::ThreadQueueList<Thread*, ThreadPrioLowest + 1> ready_queue; | ||||
|     std::unordered_map<u64, Thread*> wakeup_callback_table; | ||||
| 
 | ||||
|     /// Event type for the thread wake up event
 | ||||
|     Core::TimingEventType* ThreadWakeupEventType = nullptr; | ||||
| 
 | ||||
|     // Lists all threadsthat aren't deleted.
 | ||||
|     std::vector<SharedPtr<Thread>> thread_list; | ||||
| 
 | ||||
|     friend class Thread; | ||||
|     friend class KernelSystem; | ||||
| }; | ||||
| 
 | ||||
| class Thread final : public WaitObject { | ||||
| public: | ||||
|     /**
 | ||||
|      * Creates and returns a new thread. The new thread is immediately scheduled | ||||
|      * @param name The friendly name desired for the thread | ||||
|      * @param entry_point The address at which the thread should start execution | ||||
|      * @param priority The thread's priority | ||||
|      * @param arg User data to pass to the thread | ||||
|      * @param processor_id The ID(s) of the processors on which the thread is desired to be run | ||||
|      * @param stack_top The address of the thread's stack top | ||||
|      * @param owner_process The parent process for the thread | ||||
|      * @return A shared pointer to the newly created thread | ||||
|      */ | ||||
|     static ResultVal<SharedPtr<Thread>> Create(std::string name, VAddr entry_point, u32 priority, | ||||
|                                                u32 arg, s32 processor_id, VAddr stack_top, | ||||
|                                                SharedPtr<Process> owner_process); | ||||
| 
 | ||||
|     std::string GetName() const override { | ||||
|         return name; | ||||
|     } | ||||
|  | @ -204,7 +272,7 @@ public: | |||
|     /// Mutexes that this thread is currently waiting for.
 | ||||
|     boost::container::flat_set<SharedPtr<Mutex>> pending_mutexes; | ||||
| 
 | ||||
|     SharedPtr<Process> owner_process; ///< Process that owns this thread
 | ||||
|     Process* owner_process; ///< Process that owns this thread
 | ||||
| 
 | ||||
|     /// Objects that the thread is waiting on, in the same order as they were
 | ||||
|     // passed to WaitSynchronization1/N.
 | ||||
|  | @ -214,9 +282,6 @@ public: | |||
| 
 | ||||
|     std::string name; | ||||
| 
 | ||||
|     /// Handle used as userdata to reference this object when inserting into the CoreTiming queue.
 | ||||
|     Handle callback_handle; | ||||
| 
 | ||||
|     using WakeupCallback = void(ThreadWakeupReason reason, SharedPtr<Thread> thread, | ||||
|                                 SharedPtr<WaitObject> object); | ||||
|     // Callback that will be invoked when the thread is resumed from a waiting state. If the thread
 | ||||
|  | @ -225,69 +290,23 @@ public: | |||
|     std::function<WakeupCallback> wakeup_callback; | ||||
| 
 | ||||
| private: | ||||
|     Thread(); | ||||
|     explicit Thread(KernelSystem&); | ||||
|     ~Thread() override; | ||||
| 
 | ||||
|     ThreadManager& thread_manager; | ||||
| 
 | ||||
|     friend class KernelSystem; | ||||
| }; | ||||
| 
 | ||||
| /**
 | ||||
|  * Sets up the primary application thread | ||||
|  * @param kernel The kernel instance on which the thread is created | ||||
|  * @param entry_point The address at which the thread should start execution | ||||
|  * @param priority The priority to give the main thread | ||||
|  * @param owner_process The parent process for the main thread | ||||
|  * @return A shared pointer to the main thread | ||||
|  */ | ||||
| SharedPtr<Thread> SetupMainThread(u32 entry_point, u32 priority, SharedPtr<Process> owner_process); | ||||
| 
 | ||||
| /**
 | ||||
|  * Returns whether there are any threads that are ready to run. | ||||
|  */ | ||||
| bool HaveReadyThreads(); | ||||
| 
 | ||||
| /**
 | ||||
|  * Reschedules to the next available thread (call after current thread is suspended) | ||||
|  */ | ||||
| void Reschedule(); | ||||
| 
 | ||||
| /**
 | ||||
|  * Arbitrate the highest priority thread that is waiting | ||||
|  * @param address The address for which waiting threads should be arbitrated | ||||
|  */ | ||||
| Thread* ArbitrateHighestPriorityThread(u32 address); | ||||
| 
 | ||||
| /**
 | ||||
|  * Arbitrate all threads currently waiting. | ||||
|  * @param address The address for which waiting threads should be arbitrated | ||||
|  */ | ||||
| void ArbitrateAllThreads(u32 address); | ||||
| 
 | ||||
| /**
 | ||||
|  * Gets the current thread | ||||
|  */ | ||||
| Thread* GetCurrentThread(); | ||||
| 
 | ||||
| /**
 | ||||
|  * Waits the current thread on a sleep | ||||
|  */ | ||||
| void WaitCurrentThread_Sleep(); | ||||
| 
 | ||||
| /**
 | ||||
|  * Stops the current thread and removes it from the thread_list | ||||
|  */ | ||||
| void ExitCurrentThread(); | ||||
| 
 | ||||
| /**
 | ||||
|  * Initialize threading | ||||
|  */ | ||||
| void ThreadingInit(); | ||||
| 
 | ||||
| /**
 | ||||
|  * Shutdown threading | ||||
|  */ | ||||
| void ThreadingShutdown(); | ||||
| 
 | ||||
| /**
 | ||||
|  * Get a const reference to the thread list for debug use | ||||
|  */ | ||||
| const std::vector<SharedPtr<Thread>>& GetThreadList(); | ||||
| SharedPtr<Thread> SetupMainThread(KernelSystem& kernel, u32 entry_point, u32 priority, | ||||
|                                   SharedPtr<Process> owner_process); | ||||
| 
 | ||||
| } // namespace Kernel
 | ||||
|  |  | |||
|  | @ -3,9 +3,10 @@ | |||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| #include <cinttypes> | ||||
| #include <unordered_map> | ||||
| #include "common/assert.h" | ||||
| #include "common/logging/log.h" | ||||
| #include "core/core_timing.h" | ||||
| #include "core/core.h" | ||||
| #include "core/hle/kernel/handle_table.h" | ||||
| #include "core/hle/kernel/object.h" | ||||
| #include "core/hle/kernel/thread.h" | ||||
|  | @ -13,24 +14,22 @@ | |||
| 
 | ||||
| namespace Kernel { | ||||
| 
 | ||||
| /// The event type of the generic timer callback event
 | ||||
| static CoreTiming::EventType* timer_callback_event_type = nullptr; | ||||
| // TODO(yuriks): This can be removed if Timer objects are explicitly pooled in the future, allowing
 | ||||
| //               us to simply use a pool index or similar.
 | ||||
| static Kernel::HandleTable timer_callback_handle_table; | ||||
| Timer::Timer(KernelSystem& kernel) : WaitObject(kernel), timer_manager(kernel.GetTimerManager()) {} | ||||
| Timer::~Timer() { | ||||
|     Cancel(); | ||||
|     timer_manager.timer_callback_table.erase(callback_id); | ||||
| } | ||||
| 
 | ||||
| Timer::Timer() {} | ||||
| Timer::~Timer() {} | ||||
| 
 | ||||
| SharedPtr<Timer> Timer::Create(ResetType reset_type, std::string name) { | ||||
|     SharedPtr<Timer> timer(new Timer); | ||||
| SharedPtr<Timer> KernelSystem::CreateTimer(ResetType reset_type, std::string name) { | ||||
|     SharedPtr<Timer> timer(new Timer(*this)); | ||||
| 
 | ||||
|     timer->reset_type = reset_type; | ||||
|     timer->signaled = false; | ||||
|     timer->name = std::move(name); | ||||
|     timer->initial_delay = 0; | ||||
|     timer->interval_delay = 0; | ||||
|     timer->callback_handle = timer_callback_handle_table.Create(timer).Unwrap(); | ||||
|     timer->callback_id = ++timer_manager->next_timer_callback_id; | ||||
|     timer_manager->timer_callback_table[timer->callback_id] = timer.get(); | ||||
| 
 | ||||
|     return timer; | ||||
| } | ||||
|  | @ -57,12 +56,14 @@ void Timer::Set(s64 initial, s64 interval) { | |||
|         // Immediately invoke the callback
 | ||||
|         Signal(0); | ||||
|     } else { | ||||
|         CoreTiming::ScheduleEvent(nsToCycles(initial), timer_callback_event_type, callback_handle); | ||||
|         Core::System::GetInstance().CoreTiming().ScheduleEvent( | ||||
|             nsToCycles(initial), timer_manager.timer_callback_event_type, callback_id); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void Timer::Cancel() { | ||||
|     CoreTiming::UnscheduleEvent(timer_callback_event_type, callback_handle); | ||||
|     Core::System::GetInstance().CoreTiming().UnscheduleEvent( | ||||
|         timer_manager.timer_callback_event_type, callback_id); | ||||
| } | ||||
| 
 | ||||
| void Timer::Clear() { | ||||
|  | @ -86,29 +87,28 @@ void Timer::Signal(s64 cycles_late) { | |||
| 
 | ||||
|     if (interval_delay != 0) { | ||||
|         // Reschedule the timer with the interval delay
 | ||||
|         CoreTiming::ScheduleEvent(nsToCycles(interval_delay) - cycles_late, | ||||
|                                   timer_callback_event_type, callback_handle); | ||||
|         Core::System::GetInstance().CoreTiming().ScheduleEvent( | ||||
|             nsToCycles(interval_delay) - cycles_late, timer_manager.timer_callback_event_type, | ||||
|             callback_id); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /// The timer callback event, called when a timer is fired
 | ||||
| static void TimerCallback(u64 timer_handle, s64 cycles_late) { | ||||
|     SharedPtr<Timer> timer = | ||||
|         timer_callback_handle_table.Get<Timer>(static_cast<Handle>(timer_handle)); | ||||
| void TimerManager::TimerCallback(u64 callback_id, s64 cycles_late) { | ||||
|     SharedPtr<Timer> timer = timer_callback_table.at(callback_id); | ||||
| 
 | ||||
|     if (timer == nullptr) { | ||||
|         LOG_CRITICAL(Kernel, "Callback fired for invalid timer {:08x}", timer_handle); | ||||
|         LOG_CRITICAL(Kernel, "Callback fired for invalid timer {:016x}", callback_id); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     timer->Signal(cycles_late); | ||||
| } | ||||
| 
 | ||||
| void TimersInit() { | ||||
|     timer_callback_handle_table.Clear(); | ||||
|     timer_callback_event_type = CoreTiming::RegisterEvent("TimerCallback", TimerCallback); | ||||
| TimerManager::TimerManager() { | ||||
|     timer_callback_event_type = Core::System::GetInstance().CoreTiming().RegisterEvent( | ||||
|         "TimerCallback", | ||||
|         [this](u64 thread_id, s64 cycle_late) { TimerCallback(thread_id, cycle_late); }); | ||||
| } | ||||
| 
 | ||||
| void TimersShutdown() {} | ||||
| 
 | ||||
| } // namespace Kernel
 | ||||
|  |  | |||
Some files were not shown because too many files have changed in this diff Show more
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue