Merge branch 'master' of github.com:citra-emu/citra into ips-patches

This commit is contained in:
Zak Kurka 2018-11-15 07:11:18 -06:00
commit 1ded48f5a3
209 changed files with 17211 additions and 9498 deletions

View file

@ -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;

View file

@ -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)) {}

View file

@ -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)

View file

@ -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();

View file

@ -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);
}

View file

@ -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();
};

View 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;
});
}

View 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);

View file

@ -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;
}

View file

@ -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;
};

View file

@ -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();

View file

@ -21,6 +21,7 @@ public:
~ConfigureGeneral();
void PopulateHotkeyList(const HotkeyRegistry& registry);
void ResetDefaults();
void applyConfiguration();
void retranslateUi();

View file

@ -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>

View file

@ -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. "

View file

@ -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;
};

View file

@ -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">

View file

@ -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(

View file

@ -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;

View file

@ -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) {

View file

@ -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;

View file

@ -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;
};

View file

@ -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;

View 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;
}

View 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;
};

View file

@ -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()) {

View file

@ -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();

View file

@ -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/>

View file

@ -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)

View file

@ -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 {

View file

@ -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"

View file

@ -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>

View file

@ -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
}

View file

@ -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;
}

View file

@ -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
View 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

View file

@ -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)

View file

@ -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();
}

View file

@ -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();

View file

@ -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();
}

View file

@ -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;

View file

@ -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;

View file

@ -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()) {

View file

@ -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;

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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));
}

View 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

View file

@ -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

View file

@ -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());
}
}

View file

@ -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;
}

View file

@ -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());

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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.

View file

@ -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);

View file

@ -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

View file

@ -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));

View file

@ -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

View file

@ -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.

View file

@ -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;
};

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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;

View file

@ -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

View file

@ -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)) {

View file

@ -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

View file

@ -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;
}

View file

@ -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;

View file

@ -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:

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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;
}

View file

@ -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;
};
/**

View file

@ -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 {

View file

@ -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.

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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";

View file

@ -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

View file

@ -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);

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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) {

View file

@ -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;

View file

@ -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) {

View file

@ -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(&current_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;
}

View file

@ -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

View file

@ -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