Compare commits
27 Commits
__refs_pul
...
__refs_pul
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ce46aa4721 | ||
|
|
20e9501b0d | ||
|
|
56d9052b73 | ||
|
|
91d802f68d | ||
|
|
5f8d6bc504 | ||
|
|
e292b2d991 | ||
|
|
6c9b9b72da | ||
|
|
f582a4482d | ||
|
|
1079215871 | ||
|
|
6978093404 | ||
|
|
2f45e999d8 | ||
|
|
d44464829b | ||
|
|
efe50d88ec | ||
|
|
71f62a346d | ||
|
|
c0e45a3c78 | ||
|
|
70482e6b26 | ||
|
|
ca5e843bf6 | ||
|
|
a74fddc98f | ||
|
|
57ebcbf2c4 | ||
|
|
c7019db6f4 | ||
|
|
21f5912ec9 | ||
|
|
8d46c3cc66 | ||
|
|
b504df9d02 | ||
|
|
92b2e92620 | ||
|
|
c9260a75f6 | ||
|
|
1a16d055df | ||
|
|
21742f0096 |
2
externals/dynarmic
vendored
2
externals/dynarmic
vendored
Submodule externals/dynarmic updated: a8cbfd9af4...19a423034e
@@ -10,11 +10,65 @@ PageTable::PageTable() = default;
|
||||
|
||||
PageTable::~PageTable() noexcept = default;
|
||||
|
||||
void PageTable::Resize(size_t address_space_width_in_bits, size_t page_size_in_bits) {
|
||||
const size_t num_page_table_entries{1ULL << (address_space_width_in_bits - page_size_in_bits)};
|
||||
bool PageTable::BeginTraversal(TraversalEntry& out_entry, TraversalContext& out_context,
|
||||
u64 address) const {
|
||||
// Setup invalid defaults.
|
||||
out_entry.phys_addr = 0;
|
||||
out_entry.block_size = page_size;
|
||||
out_context.next_page = 0;
|
||||
|
||||
// Validate that we can read the actual entry.
|
||||
const auto page = address / page_size;
|
||||
if (page >= backing_addr.size()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Validate that the entry is mapped.
|
||||
const auto phys_addr = backing_addr[page];
|
||||
if (phys_addr == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Populate the results.
|
||||
out_entry.phys_addr = phys_addr + address;
|
||||
out_context.next_page = page + 1;
|
||||
out_context.next_offset = address + page_size;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PageTable::ContinueTraversal(TraversalEntry& out_entry, TraversalContext& context) const {
|
||||
// Setup invalid defaults.
|
||||
out_entry.phys_addr = 0;
|
||||
out_entry.block_size = page_size;
|
||||
|
||||
// Validate that we can read the actual entry.
|
||||
const auto page = context.next_page;
|
||||
if (page >= backing_addr.size()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Validate that the entry is mapped.
|
||||
const auto phys_addr = backing_addr[page];
|
||||
if (phys_addr == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Populate the results.
|
||||
out_entry.phys_addr = phys_addr + context.next_offset;
|
||||
context.next_page = page + 1;
|
||||
context.next_offset += page_size;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void PageTable::Resize(std::size_t address_space_width_in_bits, std::size_t page_size_in_bits) {
|
||||
const std::size_t num_page_table_entries{1ULL
|
||||
<< (address_space_width_in_bits - page_size_in_bits)};
|
||||
pointers.resize(num_page_table_entries);
|
||||
backing_addr.resize(num_page_table_entries);
|
||||
current_address_space_width_in_bits = address_space_width_in_bits;
|
||||
page_size = 1ULL << page_size_in_bits;
|
||||
}
|
||||
|
||||
} // namespace Common
|
||||
|
||||
@@ -27,6 +27,16 @@ enum class PageType : u8 {
|
||||
* mimics the way a real CPU page table works.
|
||||
*/
|
||||
struct PageTable {
|
||||
struct TraversalEntry {
|
||||
u64 phys_addr{};
|
||||
std::size_t block_size{};
|
||||
};
|
||||
|
||||
struct TraversalContext {
|
||||
u64 next_page{};
|
||||
u64 next_offset{};
|
||||
};
|
||||
|
||||
/// Number of bits reserved for attribute tagging.
|
||||
/// This can be at most the guaranteed alignment of the pointers in the page table.
|
||||
static constexpr int ATTRIBUTE_BITS = 2;
|
||||
@@ -89,6 +99,10 @@ struct PageTable {
|
||||
PageTable(PageTable&&) noexcept = default;
|
||||
PageTable& operator=(PageTable&&) noexcept = default;
|
||||
|
||||
bool BeginTraversal(TraversalEntry& out_entry, TraversalContext& out_context,
|
||||
u64 address) const;
|
||||
bool ContinueTraversal(TraversalEntry& out_entry, TraversalContext& context) const;
|
||||
|
||||
/**
|
||||
* Resizes the page table to be able to accommodate enough pages within
|
||||
* a given address space.
|
||||
@@ -96,9 +110,9 @@ struct PageTable {
|
||||
* @param address_space_width_in_bits The address size width in bits.
|
||||
* @param page_size_in_bits The page size in bits.
|
||||
*/
|
||||
void Resize(size_t address_space_width_in_bits, size_t page_size_in_bits);
|
||||
void Resize(std::size_t address_space_width_in_bits, std::size_t page_size_in_bits);
|
||||
|
||||
size_t GetAddressSpaceBits() const {
|
||||
std::size_t GetAddressSpaceBits() const {
|
||||
return current_address_space_width_in_bits;
|
||||
}
|
||||
|
||||
@@ -110,9 +124,11 @@ struct PageTable {
|
||||
|
||||
VirtualBuffer<u64> backing_addr;
|
||||
|
||||
size_t current_address_space_width_in_bits;
|
||||
std::size_t current_address_space_width_in_bits{};
|
||||
|
||||
u8* fastmem_arena;
|
||||
u8* fastmem_arena{};
|
||||
|
||||
std::size_t page_size{};
|
||||
};
|
||||
|
||||
} // namespace Common
|
||||
|
||||
@@ -167,6 +167,7 @@ void RestoreGlobalState(bool is_powered_on) {
|
||||
|
||||
// Core
|
||||
values.use_multi_core.SetGlobal(true);
|
||||
values.use_extended_memory_layout.SetGlobal(true);
|
||||
|
||||
// CPU
|
||||
values.cpu_accuracy.SetGlobal(true);
|
||||
|
||||
@@ -466,6 +466,7 @@ struct Values {
|
||||
|
||||
// Core
|
||||
Setting<bool> use_multi_core{true, "use_multi_core"};
|
||||
Setting<bool> use_extended_memory_layout{false, "use_extended_memory_layout"};
|
||||
|
||||
// Cpu
|
||||
RangedSetting<CPUAccuracy> cpu_accuracy{CPUAccuracy::Auto, CPUAccuracy::Auto,
|
||||
|
||||
@@ -28,7 +28,9 @@
|
||||
#include "core/file_sys/vfs_real.h"
|
||||
#include "core/hardware_interrupt_manager.h"
|
||||
#include "core/hid/hid_core.h"
|
||||
#include "core/hle/kernel/k_memory_manager.h"
|
||||
#include "core/hle/kernel/k_process.h"
|
||||
#include "core/hle/kernel/k_resource_limit.h"
|
||||
#include "core/hle/kernel/k_scheduler.h"
|
||||
#include "core/hle/kernel/kernel.h"
|
||||
#include "core/hle/kernel/physical_core.h"
|
||||
@@ -252,9 +254,16 @@ struct System::Impl {
|
||||
}
|
||||
|
||||
telemetry_session->AddInitialInfo(*app_loader, fs_controller, *content_provider);
|
||||
|
||||
// Create a resource limit for the process.
|
||||
const auto physical_memory_size =
|
||||
kernel.MemoryManager().GetSize(Kernel::KMemoryManager::Pool::Application);
|
||||
auto* resource_limit = Kernel::CreateResourceLimitForProcess(system, physical_memory_size);
|
||||
|
||||
// Create the process.
|
||||
auto main_process = Kernel::KProcess::Create(system.Kernel());
|
||||
ASSERT(Kernel::KProcess::Initialize(main_process, system, "main",
|
||||
Kernel::KProcess::ProcessType::Userland)
|
||||
Kernel::KProcess::ProcessType::Userland, resource_limit)
|
||||
.IsSuccess());
|
||||
const auto [load_result, load_parameters] = app_loader->Load(*main_process, system);
|
||||
if (load_result != Loader::ResultStatus::Success) {
|
||||
|
||||
@@ -3,10 +3,13 @@
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "core/device_memory.h"
|
||||
#include "hle/kernel/board/nintendo/nx/k_system_control.h"
|
||||
|
||||
namespace Core {
|
||||
|
||||
DeviceMemory::DeviceMemory() : buffer{DramMemoryMap::Size, 1ULL << 39} {}
|
||||
DeviceMemory::DeviceMemory()
|
||||
: buffer{Kernel::Board::Nintendo::Nx::KSystemControl::Init::GetIntendedMemorySize(),
|
||||
1ULL << 39} {}
|
||||
DeviceMemory::~DeviceMemory() = default;
|
||||
|
||||
} // namespace Core
|
||||
|
||||
@@ -12,12 +12,8 @@ namespace Core {
|
||||
namespace DramMemoryMap {
|
||||
enum : u64 {
|
||||
Base = 0x80000000ULL,
|
||||
Size = 0x100000000ULL,
|
||||
End = Base + Size,
|
||||
KernelReserveBase = Base + 0x60000,
|
||||
SlabHeapBase = KernelReserveBase + 0x85000,
|
||||
SlapHeapSize = 0xa21000,
|
||||
SlabHeapEnd = SlabHeapBase + SlapHeapSize,
|
||||
};
|
||||
}; // namespace DramMemoryMap
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <random>
|
||||
|
||||
#include "common/literals.h"
|
||||
#include "common/settings.h"
|
||||
|
||||
#include "core/hle/kernel/board/nintendo/nx/k_system_control.h"
|
||||
#include "core/hle/kernel/board/nintendo/nx/secure_monitor.h"
|
||||
@@ -28,30 +29,13 @@ namespace {
|
||||
|
||||
using namespace Common::Literals;
|
||||
|
||||
u32 GetMemoryModeForInit() {
|
||||
return 0x01;
|
||||
}
|
||||
|
||||
u32 GetMemorySizeForInit() {
|
||||
return 0;
|
||||
return Settings::values.use_extended_memory_layout ? Smc::MemorySize_6GB : Smc::MemorySize_4GB;
|
||||
}
|
||||
|
||||
Smc::MemoryArrangement GetMemoryArrangeForInit() {
|
||||
switch (GetMemoryModeForInit() & 0x3F) {
|
||||
case 0x01:
|
||||
default:
|
||||
return Smc::MemoryArrangement_4GB;
|
||||
case 0x02:
|
||||
return Smc::MemoryArrangement_4GBForAppletDev;
|
||||
case 0x03:
|
||||
return Smc::MemoryArrangement_4GBForSystemDev;
|
||||
case 0x11:
|
||||
return Smc::MemoryArrangement_6GB;
|
||||
case 0x12:
|
||||
return Smc::MemoryArrangement_6GBForAppletDev;
|
||||
case 0x21:
|
||||
return Smc::MemoryArrangement_8GB;
|
||||
}
|
||||
return Settings::values.use_extended_memory_layout ? Smc::MemoryArrangement_6GB
|
||||
: Smc::MemoryArrangement_4GB;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ KEvent::KEvent(KernelCore& kernel_)
|
||||
|
||||
KEvent::~KEvent() = default;
|
||||
|
||||
void KEvent::Initialize(std::string&& name_) {
|
||||
void KEvent::Initialize(std::string&& name_, KProcess* owner_) {
|
||||
// Increment reference count.
|
||||
// Because reference count is one on creation, this will result
|
||||
// in a reference count of two. Thus, when both readable and
|
||||
@@ -30,10 +30,8 @@ void KEvent::Initialize(std::string&& name_) {
|
||||
writable_event.Initialize(this, name_ + ":Writable");
|
||||
|
||||
// Set our owner process.
|
||||
owner = kernel.CurrentProcess();
|
||||
if (owner) {
|
||||
owner->Open();
|
||||
}
|
||||
owner = owner_;
|
||||
owner->Open();
|
||||
|
||||
// Mark initialized.
|
||||
name = std::move(name_);
|
||||
@@ -47,10 +45,8 @@ void KEvent::Finalize() {
|
||||
void KEvent::PostDestroy(uintptr_t arg) {
|
||||
// Release the event count resource the owner process holds.
|
||||
KProcess* owner = reinterpret_cast<KProcess*>(arg);
|
||||
if (owner) {
|
||||
owner->GetResourceLimit()->Release(LimitableResource::Events, 1);
|
||||
owner->Close();
|
||||
}
|
||||
owner->GetResourceLimit()->Release(LimitableResource::Events, 1);
|
||||
owner->Close();
|
||||
}
|
||||
|
||||
} // namespace Kernel
|
||||
|
||||
@@ -22,7 +22,7 @@ public:
|
||||
explicit KEvent(KernelCore& kernel_);
|
||||
~KEvent() override;
|
||||
|
||||
void Initialize(std::string&& name);
|
||||
void Initialize(std::string&& name, KProcess* owner_);
|
||||
|
||||
void Finalize() override;
|
||||
|
||||
|
||||
@@ -41,24 +41,6 @@ constexpr std::size_t GetAddressSpaceWidthFromType(FileSys::ProgramAddressSpaceT
|
||||
}
|
||||
}
|
||||
|
||||
constexpr u64 GetAddressInRange(const KMemoryInfo& info, VAddr addr) {
|
||||
if (info.GetAddress() < addr) {
|
||||
return addr;
|
||||
}
|
||||
return info.GetAddress();
|
||||
}
|
||||
|
||||
constexpr std::size_t GetSizeInRange(const KMemoryInfo& info, VAddr start, VAddr end) {
|
||||
std::size_t size{info.GetSize()};
|
||||
if (info.GetAddress() < start) {
|
||||
size -= start - info.GetAddress();
|
||||
}
|
||||
if (info.GetEndAddress() > end) {
|
||||
size -= info.GetEndAddress() - end;
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
KPageTable::KPageTable(Core::System& system_)
|
||||
@@ -400,148 +382,471 @@ ResultCode KPageTable::UnmapProcessMemory(VAddr dst_addr, std::size_t size,
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
ResultCode KPageTable::MapPhysicalMemory(VAddr addr, std::size_t size) {
|
||||
ResultCode KPageTable::MapPhysicalMemory(VAddr address, std::size_t size) {
|
||||
// Lock the physical memory lock.
|
||||
KScopedLightLock map_phys_mem_lk(map_physical_memory_lock);
|
||||
|
||||
// Lock the table.
|
||||
KScopedLightLock lk(general_lock);
|
||||
// Calculate the last address for convenience.
|
||||
const VAddr last_address = address + size - 1;
|
||||
|
||||
std::size_t mapped_size{};
|
||||
const VAddr end_addr{addr + size};
|
||||
// Define iteration variables.
|
||||
VAddr cur_address;
|
||||
std::size_t mapped_size;
|
||||
|
||||
block_manager->IterateForRange(addr, end_addr, [&](const KMemoryInfo& info) {
|
||||
if (info.state != KMemoryState::Free) {
|
||||
mapped_size += GetSizeInRange(info, addr, end_addr);
|
||||
}
|
||||
});
|
||||
// The entire mapping process can be retried.
|
||||
while (true) {
|
||||
// Check if the memory is already mapped.
|
||||
{
|
||||
// Lock the table.
|
||||
KScopedLightLock lk(general_lock);
|
||||
|
||||
if (mapped_size == size) {
|
||||
return ResultSuccess;
|
||||
}
|
||||
// Iterate over the memory.
|
||||
cur_address = address;
|
||||
mapped_size = 0;
|
||||
|
||||
const std::size_t remaining_size{size - mapped_size};
|
||||
const std::size_t remaining_pages{remaining_size / PageSize};
|
||||
auto it = block_manager->FindIterator(cur_address);
|
||||
while (true) {
|
||||
// Check that the iterator is valid.
|
||||
ASSERT(it != block_manager->end());
|
||||
|
||||
// Reserve the memory from the process resource limit.
|
||||
KScopedResourceReservation memory_reservation(
|
||||
system.Kernel().CurrentProcess()->GetResourceLimit(), LimitableResource::PhysicalMemory,
|
||||
remaining_size);
|
||||
if (!memory_reservation.Succeeded()) {
|
||||
LOG_ERROR(Kernel, "Could not reserve remaining {:X} bytes", remaining_size);
|
||||
return ResultLimitReached;
|
||||
}
|
||||
// Get the memory info.
|
||||
const KMemoryInfo info = it->GetMemoryInfo();
|
||||
|
||||
KPageLinkedList page_linked_list;
|
||||
// Check if we're done.
|
||||
if (last_address <= info.GetLastAddress()) {
|
||||
if (info.GetState() != KMemoryState::Free) {
|
||||
mapped_size += (last_address + 1 - cur_address);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
CASCADE_CODE(system.Kernel().MemoryManager().Allocate(page_linked_list, remaining_pages,
|
||||
memory_pool, allocation_option));
|
||||
// Track the memory if it's mapped.
|
||||
if (info.GetState() != KMemoryState::Free) {
|
||||
mapped_size += VAddr(info.GetEndAddress()) - cur_address;
|
||||
}
|
||||
|
||||
// We succeeded, so commit the memory reservation.
|
||||
memory_reservation.Commit();
|
||||
|
||||
// Map the memory.
|
||||
auto node{page_linked_list.Nodes().begin()};
|
||||
PAddr map_addr{node->GetAddress()};
|
||||
std::size_t src_num_pages{node->GetNumPages()};
|
||||
block_manager->IterateForRange(addr, end_addr, [&](const KMemoryInfo& info) {
|
||||
if (info.state != KMemoryState::Free) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::size_t dst_num_pages{GetSizeInRange(info, addr, end_addr) / PageSize};
|
||||
VAddr dst_addr{GetAddressInRange(info, addr)};
|
||||
|
||||
while (dst_num_pages) {
|
||||
if (!src_num_pages) {
|
||||
node = std::next(node);
|
||||
map_addr = node->GetAddress();
|
||||
src_num_pages = node->GetNumPages();
|
||||
// Advance.
|
||||
cur_address = info.GetEndAddress();
|
||||
++it;
|
||||
}
|
||||
|
||||
const std::size_t num_pages{std::min(src_num_pages, dst_num_pages)};
|
||||
Operate(dst_addr, num_pages, KMemoryPermission::UserReadWrite, OperationType::Map,
|
||||
map_addr);
|
||||
|
||||
dst_addr += num_pages * PageSize;
|
||||
map_addr += num_pages * PageSize;
|
||||
src_num_pages -= num_pages;
|
||||
dst_num_pages -= num_pages;
|
||||
// If the size mapped is the size requested, we've nothing to do.
|
||||
R_SUCCEED_IF(size == mapped_size);
|
||||
}
|
||||
});
|
||||
|
||||
mapped_physical_memory_size += remaining_size;
|
||||
// Allocate and map the memory.
|
||||
{
|
||||
// Reserve the memory from the process resource limit.
|
||||
KScopedResourceReservation memory_reservation(
|
||||
system.Kernel().CurrentProcess()->GetResourceLimit(),
|
||||
LimitableResource::PhysicalMemory, size - mapped_size);
|
||||
R_UNLESS(memory_reservation.Succeeded(), ResultLimitReached);
|
||||
|
||||
const std::size_t num_pages{size / PageSize};
|
||||
block_manager->Update(addr, num_pages, KMemoryState::Free, KMemoryPermission::None,
|
||||
KMemoryAttribute::None, KMemoryState::Normal,
|
||||
KMemoryPermission::UserReadWrite, KMemoryAttribute::None);
|
||||
// Allocate pages for the new memory.
|
||||
KPageLinkedList page_linked_list;
|
||||
R_TRY(system.Kernel().MemoryManager().Allocate(
|
||||
page_linked_list, (size - mapped_size) / PageSize, memory_pool, allocation_option));
|
||||
|
||||
return ResultSuccess;
|
||||
// Map the memory.
|
||||
{
|
||||
// Lock the table.
|
||||
KScopedLightLock lk(general_lock);
|
||||
|
||||
size_t num_allocator_blocks = 0;
|
||||
|
||||
// Verify that nobody has mapped memory since we first checked.
|
||||
{
|
||||
// Iterate over the memory.
|
||||
size_t checked_mapped_size = 0;
|
||||
cur_address = address;
|
||||
|
||||
auto it = block_manager->FindIterator(cur_address);
|
||||
while (true) {
|
||||
// Check that the iterator is valid.
|
||||
ASSERT(it != block_manager->end());
|
||||
|
||||
// Get the memory info.
|
||||
const KMemoryInfo info = it->GetMemoryInfo();
|
||||
|
||||
const bool is_free = info.GetState() == KMemoryState::Free;
|
||||
if (is_free) {
|
||||
if (info.GetAddress() < address) {
|
||||
++num_allocator_blocks;
|
||||
}
|
||||
if (last_address < info.GetLastAddress()) {
|
||||
++num_allocator_blocks;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if we're done.
|
||||
if (last_address <= info.GetLastAddress()) {
|
||||
if (!is_free) {
|
||||
checked_mapped_size += (last_address + 1 - cur_address);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// Track the memory if it's mapped.
|
||||
if (!is_free) {
|
||||
checked_mapped_size += VAddr(info.GetEndAddress()) - cur_address;
|
||||
}
|
||||
|
||||
// Advance.
|
||||
cur_address = info.GetEndAddress();
|
||||
++it;
|
||||
}
|
||||
|
||||
// If the size now isn't what it was before, somebody mapped or unmapped
|
||||
// concurrently. If this happened, retry.
|
||||
if (mapped_size != checked_mapped_size) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Reset the current tracking address, and make sure we clean up on failure.
|
||||
cur_address = address;
|
||||
auto unmap_guard = detail::ScopeExit([&] {
|
||||
if (cur_address > address) {
|
||||
const VAddr last_unmap_address = cur_address - 1;
|
||||
|
||||
// Iterate, unmapping the pages.
|
||||
cur_address = address;
|
||||
|
||||
auto it = block_manager->FindIterator(cur_address);
|
||||
while (true) {
|
||||
// Check that the iterator is valid.
|
||||
ASSERT(it != block_manager->end());
|
||||
|
||||
// Get the memory info.
|
||||
const KMemoryInfo info = it->GetMemoryInfo();
|
||||
|
||||
// If the memory state is free, we mapped it and need to unmap it.
|
||||
if (info.GetState() == KMemoryState::Free) {
|
||||
// Determine the range to unmap.
|
||||
const size_t cur_pages =
|
||||
std::min(VAddr(info.GetEndAddress()) - cur_address,
|
||||
last_unmap_address + 1 - cur_address) /
|
||||
PageSize;
|
||||
|
||||
// Unmap.
|
||||
ASSERT(Operate(cur_address, cur_pages, KMemoryPermission::None,
|
||||
OperationType::Unmap)
|
||||
.IsSuccess());
|
||||
}
|
||||
|
||||
// Check if we're done.
|
||||
if (last_unmap_address <= info.GetLastAddress()) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Advance.
|
||||
cur_address = info.GetEndAddress();
|
||||
++it;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Iterate over the memory.
|
||||
auto pg_it = page_linked_list.Nodes().begin();
|
||||
PAddr pg_phys_addr = pg_it->GetAddress();
|
||||
size_t pg_pages = pg_it->GetNumPages();
|
||||
|
||||
auto it = block_manager->FindIterator(cur_address);
|
||||
while (true) {
|
||||
// Check that the iterator is valid.
|
||||
ASSERT(it != block_manager->end());
|
||||
|
||||
// Get the memory info.
|
||||
const KMemoryInfo info = it->GetMemoryInfo();
|
||||
|
||||
// If it's unmapped, we need to map it.
|
||||
if (info.GetState() == KMemoryState::Free) {
|
||||
// Determine the range to map.
|
||||
size_t map_pages = std::min(VAddr(info.GetEndAddress()) - cur_address,
|
||||
last_address + 1 - cur_address) /
|
||||
PageSize;
|
||||
|
||||
// While we have pages to map, map them.
|
||||
while (map_pages > 0) {
|
||||
// Check if we're at the end of the physical block.
|
||||
if (pg_pages == 0) {
|
||||
// Ensure there are more pages to map.
|
||||
ASSERT(pg_it != page_linked_list.Nodes().end());
|
||||
|
||||
// Advance our physical block.
|
||||
++pg_it;
|
||||
pg_phys_addr = pg_it->GetAddress();
|
||||
pg_pages = pg_it->GetNumPages();
|
||||
}
|
||||
|
||||
// Map whatever we can.
|
||||
const size_t cur_pages = std::min(pg_pages, map_pages);
|
||||
R_TRY(Operate(cur_address, cur_pages, KMemoryPermission::UserReadWrite,
|
||||
OperationType::Map, pg_phys_addr));
|
||||
|
||||
// Advance.
|
||||
cur_address += cur_pages * PageSize;
|
||||
map_pages -= cur_pages;
|
||||
|
||||
pg_phys_addr += cur_pages * PageSize;
|
||||
pg_pages -= cur_pages;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if we're done.
|
||||
if (last_address <= info.GetLastAddress()) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Advance.
|
||||
cur_address = info.GetEndAddress();
|
||||
++it;
|
||||
}
|
||||
|
||||
// We succeeded, so commit the memory reservation.
|
||||
memory_reservation.Commit();
|
||||
|
||||
// Increase our tracked mapped size.
|
||||
mapped_physical_memory_size += (size - mapped_size);
|
||||
|
||||
// Update the relevant memory blocks.
|
||||
block_manager->Update(address, size / PageSize, KMemoryState::Free,
|
||||
KMemoryPermission::None, KMemoryAttribute::None,
|
||||
KMemoryState::Normal, KMemoryPermission::UserReadWrite,
|
||||
KMemoryAttribute::None);
|
||||
|
||||
// Cancel our guard.
|
||||
unmap_guard.Cancel();
|
||||
|
||||
return ResultSuccess;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ResultCode KPageTable::UnmapPhysicalMemory(VAddr addr, std::size_t size) {
|
||||
ResultCode KPageTable::UnmapPhysicalMemory(VAddr address, std::size_t size) {
|
||||
// Lock the physical memory lock.
|
||||
KScopedLightLock map_phys_mem_lk(map_physical_memory_lock);
|
||||
|
||||
// Lock the table.
|
||||
KScopedLightLock lk(general_lock);
|
||||
|
||||
const VAddr end_addr{addr + size};
|
||||
ResultCode result{ResultSuccess};
|
||||
std::size_t mapped_size{};
|
||||
// Calculate the last address for convenience.
|
||||
const VAddr last_address = address + size - 1;
|
||||
|
||||
// Verify that the region can be unmapped
|
||||
block_manager->IterateForRange(addr, end_addr, [&](const KMemoryInfo& info) {
|
||||
if (info.state == KMemoryState::Normal) {
|
||||
if (info.attribute != KMemoryAttribute::None) {
|
||||
result = ResultInvalidCurrentMemory;
|
||||
return;
|
||||
// Define iteration variables.
|
||||
VAddr cur_address = 0;
|
||||
std::size_t mapped_size = 0;
|
||||
std::size_t num_allocator_blocks = 0;
|
||||
|
||||
// Check if the memory is mapped.
|
||||
{
|
||||
// Iterate over the memory.
|
||||
cur_address = address;
|
||||
mapped_size = 0;
|
||||
|
||||
auto it = block_manager->FindIterator(cur_address);
|
||||
while (true) {
|
||||
// Check that the iterator is valid.
|
||||
ASSERT(it != block_manager->end());
|
||||
|
||||
// Get the memory info.
|
||||
const KMemoryInfo info = it->GetMemoryInfo();
|
||||
|
||||
// Verify the memory's state.
|
||||
const bool is_normal = info.GetState() == KMemoryState::Normal &&
|
||||
info.GetAttribute() == KMemoryAttribute::None;
|
||||
const bool is_free = info.GetState() == KMemoryState::Free;
|
||||
R_UNLESS(is_normal || is_free, ResultInvalidCurrentMemory);
|
||||
|
||||
if (is_normal) {
|
||||
R_UNLESS(info.GetAttribute() == KMemoryAttribute::None, ResultInvalidCurrentMemory);
|
||||
|
||||
if (info.GetAddress() < address) {
|
||||
++num_allocator_blocks;
|
||||
}
|
||||
if (last_address < info.GetLastAddress()) {
|
||||
++num_allocator_blocks;
|
||||
}
|
||||
}
|
||||
mapped_size += GetSizeInRange(info, addr, end_addr);
|
||||
} else if (info.state != KMemoryState::Free) {
|
||||
result = ResultInvalidCurrentMemory;
|
||||
|
||||
// Check if we're done.
|
||||
if (last_address <= info.GetLastAddress()) {
|
||||
if (is_normal) {
|
||||
mapped_size += (last_address + 1 - cur_address);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// Track the memory if it's mapped.
|
||||
if (is_normal) {
|
||||
mapped_size += VAddr(info.GetEndAddress()) - cur_address;
|
||||
}
|
||||
|
||||
// Advance.
|
||||
cur_address = info.GetEndAddress();
|
||||
++it;
|
||||
}
|
||||
});
|
||||
|
||||
if (result.IsError()) {
|
||||
return result;
|
||||
// If there's nothing mapped, we've nothing to do.
|
||||
R_SUCCEED_IF(mapped_size == 0);
|
||||
}
|
||||
|
||||
if (!mapped_size) {
|
||||
return ResultSuccess;
|
||||
// Make a page group for the unmap region.
|
||||
KPageLinkedList pg;
|
||||
{
|
||||
auto& impl = this->PageTableImpl();
|
||||
|
||||
// Begin traversal.
|
||||
Common::PageTable::TraversalContext context;
|
||||
Common::PageTable::TraversalEntry cur_entry = {.phys_addr = 0, .block_size = 0};
|
||||
bool cur_valid = false;
|
||||
Common::PageTable::TraversalEntry next_entry;
|
||||
bool next_valid = false;
|
||||
size_t tot_size = 0;
|
||||
|
||||
cur_address = address;
|
||||
next_valid = impl.BeginTraversal(next_entry, context, cur_address);
|
||||
next_entry.block_size =
|
||||
(next_entry.block_size - (next_entry.phys_addr & (next_entry.block_size - 1)));
|
||||
|
||||
// Iterate, building the group.
|
||||
while (true) {
|
||||
if ((!next_valid && !cur_valid) ||
|
||||
(next_valid && cur_valid &&
|
||||
next_entry.phys_addr == cur_entry.phys_addr + cur_entry.block_size)) {
|
||||
cur_entry.block_size += next_entry.block_size;
|
||||
} else {
|
||||
if (cur_valid) {
|
||||
// ASSERT(IsHeapPhysicalAddress(cur_entry.phys_addr));
|
||||
R_TRY(pg.AddBlock(cur_entry.phys_addr, cur_entry.block_size / PageSize));
|
||||
}
|
||||
|
||||
// Update tracking variables.
|
||||
tot_size += cur_entry.block_size;
|
||||
cur_entry = next_entry;
|
||||
cur_valid = next_valid;
|
||||
}
|
||||
|
||||
if (cur_entry.block_size + tot_size >= size) {
|
||||
break;
|
||||
}
|
||||
|
||||
next_valid = impl.ContinueTraversal(next_entry, context);
|
||||
}
|
||||
|
||||
// Add the last block.
|
||||
if (cur_valid) {
|
||||
// ASSERT(IsHeapPhysicalAddress(cur_entry.phys_addr));
|
||||
R_TRY(pg.AddBlock(cur_entry.phys_addr, (size - tot_size) / PageSize));
|
||||
}
|
||||
}
|
||||
ASSERT(pg.GetNumPages() == mapped_size / PageSize);
|
||||
|
||||
// Unmap each region within the range
|
||||
KPageLinkedList page_linked_list;
|
||||
block_manager->IterateForRange(addr, end_addr, [&](const KMemoryInfo& info) {
|
||||
if (info.state == KMemoryState::Normal) {
|
||||
const std::size_t block_size{GetSizeInRange(info, addr, end_addr)};
|
||||
const std::size_t block_num_pages{block_size / PageSize};
|
||||
const VAddr block_addr{GetAddressInRange(info, addr)};
|
||||
// Reset the current tracking address, and make sure we clean up on failure.
|
||||
cur_address = address;
|
||||
auto remap_guard = detail::ScopeExit([&] {
|
||||
if (cur_address > address) {
|
||||
const VAddr last_map_address = cur_address - 1;
|
||||
cur_address = address;
|
||||
|
||||
AddRegionToPages(block_addr, block_size / PageSize, page_linked_list);
|
||||
// Iterate over the memory we unmapped.
|
||||
auto it = block_manager->FindIterator(cur_address);
|
||||
auto pg_it = pg.Nodes().begin();
|
||||
PAddr pg_phys_addr = pg_it->GetAddress();
|
||||
size_t pg_pages = pg_it->GetNumPages();
|
||||
|
||||
if (result = Operate(block_addr, block_num_pages, KMemoryPermission::None,
|
||||
OperationType::Unmap);
|
||||
result.IsError()) {
|
||||
return;
|
||||
while (true) {
|
||||
// Get the memory info for the pages we unmapped, convert to property.
|
||||
const KMemoryInfo info = it->GetMemoryInfo();
|
||||
|
||||
// If the memory is normal, we unmapped it and need to re-map it.
|
||||
if (info.GetState() == KMemoryState::Normal) {
|
||||
// Determine the range to map.
|
||||
size_t map_pages = std::min(VAddr(info.GetEndAddress()) - cur_address,
|
||||
last_map_address + 1 - cur_address) /
|
||||
PageSize;
|
||||
|
||||
// While we have pages to map, map them.
|
||||
while (map_pages > 0) {
|
||||
// Check if we're at the end of the physical block.
|
||||
if (pg_pages == 0) {
|
||||
// Ensure there are more pages to map.
|
||||
ASSERT(pg_it != pg.Nodes().end());
|
||||
|
||||
// Advance our physical block.
|
||||
++pg_it;
|
||||
pg_phys_addr = pg_it->GetAddress();
|
||||
pg_pages = pg_it->GetNumPages();
|
||||
}
|
||||
|
||||
// Map whatever we can.
|
||||
const size_t cur_pages = std::min(pg_pages, map_pages);
|
||||
ASSERT(this->Operate(cur_address, cur_pages, info.GetPermission(),
|
||||
OperationType::Map, pg_phys_addr) == ResultSuccess);
|
||||
|
||||
// Advance.
|
||||
cur_address += cur_pages * PageSize;
|
||||
map_pages -= cur_pages;
|
||||
|
||||
pg_phys_addr += cur_pages * PageSize;
|
||||
pg_pages -= cur_pages;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if we're done.
|
||||
if (last_map_address <= info.GetLastAddress()) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Advance.
|
||||
++it;
|
||||
}
|
||||
}
|
||||
});
|
||||
if (result.IsError()) {
|
||||
return result;
|
||||
|
||||
// Iterate over the memory, unmapping as we go.
|
||||
auto it = block_manager->FindIterator(cur_address);
|
||||
while (true) {
|
||||
// Check that the iterator is valid.
|
||||
ASSERT(it != block_manager->end());
|
||||
|
||||
// Get the memory info.
|
||||
const KMemoryInfo info = it->GetMemoryInfo();
|
||||
|
||||
// If the memory state is normal, we need to unmap it.
|
||||
if (info.GetState() == KMemoryState::Normal) {
|
||||
// Determine the range to unmap.
|
||||
const size_t cur_pages = std::min(VAddr(info.GetEndAddress()) - cur_address,
|
||||
last_address + 1 - cur_address) /
|
||||
PageSize;
|
||||
|
||||
// Unmap.
|
||||
R_TRY(Operate(cur_address, cur_pages, KMemoryPermission::None, OperationType::Unmap));
|
||||
}
|
||||
|
||||
// Check if we're done.
|
||||
if (last_address <= info.GetLastAddress()) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Advance.
|
||||
cur_address = info.GetEndAddress();
|
||||
++it;
|
||||
}
|
||||
|
||||
const std::size_t num_pages{size / PageSize};
|
||||
system.Kernel().MemoryManager().Free(page_linked_list, num_pages, memory_pool,
|
||||
allocation_option);
|
||||
|
||||
block_manager->Update(addr, num_pages, KMemoryState::Free);
|
||||
|
||||
// Release the memory resource.
|
||||
mapped_physical_memory_size -= mapped_size;
|
||||
auto process{system.Kernel().CurrentProcess()};
|
||||
process->GetResourceLimit()->Release(LimitableResource::PhysicalMemory, mapped_size);
|
||||
mapped_physical_memory_size -= mapped_size;
|
||||
|
||||
// Update memory blocks.
|
||||
system.Kernel().MemoryManager().Free(pg, size / PageSize, memory_pool, allocation_option);
|
||||
block_manager->Update(address, size / PageSize, KMemoryState::Free, KMemoryPermission::None,
|
||||
KMemoryAttribute::None);
|
||||
|
||||
// We succeeded.
|
||||
remap_guard.Cancel();
|
||||
|
||||
return ResultSuccess;
|
||||
}
|
||||
@@ -681,9 +986,8 @@ ResultCode KPageTable::UnmapPages(VAddr addr, const KPageLinkedList& page_linked
|
||||
VAddr cur_addr{addr};
|
||||
|
||||
for (const auto& node : page_linked_list.Nodes()) {
|
||||
const std::size_t num_pages{(addr - cur_addr) / PageSize};
|
||||
if (const auto result{
|
||||
Operate(addr, num_pages, KMemoryPermission::None, OperationType::Unmap)};
|
||||
if (const auto result{Operate(cur_addr, node.GetNumPages(), KMemoryPermission::None,
|
||||
OperationType::Unmap)};
|
||||
result.IsError()) {
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -123,12 +123,11 @@ private:
|
||||
};
|
||||
|
||||
ResultCode KProcess::Initialize(KProcess* process, Core::System& system, std::string process_name,
|
||||
ProcessType type) {
|
||||
ProcessType type, KResourceLimit* res_limit) {
|
||||
auto& kernel = system.Kernel();
|
||||
|
||||
process->name = std::move(process_name);
|
||||
|
||||
process->resource_limit = kernel.GetSystemResourceLimit();
|
||||
process->resource_limit = res_limit;
|
||||
process->status = ProcessStatus::Created;
|
||||
process->program_id = 0;
|
||||
process->process_id = type == ProcessType::KernelInternal ? kernel.CreateNewKernelProcessID()
|
||||
@@ -143,9 +142,6 @@ ResultCode KProcess::Initialize(KProcess* process, Core::System& system, std::st
|
||||
|
||||
kernel.AppendNewProcess(process);
|
||||
|
||||
// Open a reference to the resource limit.
|
||||
process->resource_limit->Open();
|
||||
|
||||
// Clear remaining fields.
|
||||
process->num_running_threads = 0;
|
||||
process->is_signaled = false;
|
||||
@@ -153,6 +149,9 @@ ResultCode KProcess::Initialize(KProcess* process, Core::System& system, std::st
|
||||
process->is_suspended = false;
|
||||
process->schedule_count = 0;
|
||||
|
||||
// Open a reference to the resource limit.
|
||||
process->resource_limit->Open();
|
||||
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
|
||||
@@ -91,7 +91,7 @@ public:
|
||||
static constexpr std::size_t RANDOM_ENTROPY_SIZE = 4;
|
||||
|
||||
static ResultCode Initialize(KProcess* process, Core::System& system, std::string process_name,
|
||||
ProcessType type);
|
||||
ProcessType type, KResourceLimit* res_limit);
|
||||
|
||||
/// Gets a reference to the process' page table.
|
||||
KPageTable& PageTable() {
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "core/core.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/hle/kernel/k_resource_limit.h"
|
||||
#include "core/hle/kernel/svc_results.h"
|
||||
@@ -151,4 +152,22 @@ void KResourceLimit::Release(LimitableResource which, s64 value, s64 hint) {
|
||||
}
|
||||
}
|
||||
|
||||
KResourceLimit* CreateResourceLimitForProcess(Core::System& system, s64 physical_memory_size) {
|
||||
auto* resource_limit = KResourceLimit::Create(system.Kernel());
|
||||
resource_limit->Initialize(&system.CoreTiming());
|
||||
|
||||
// Initialize default resource limit values.
|
||||
// TODO(bunnei): These values are the system defaults, the limits for service processes are
|
||||
// lower. These should use the correct limit values.
|
||||
|
||||
ASSERT(resource_limit->SetLimitValue(LimitableResource::PhysicalMemory, physical_memory_size)
|
||||
.IsSuccess());
|
||||
ASSERT(resource_limit->SetLimitValue(LimitableResource::Threads, 800).IsSuccess());
|
||||
ASSERT(resource_limit->SetLimitValue(LimitableResource::Events, 900).IsSuccess());
|
||||
ASSERT(resource_limit->SetLimitValue(LimitableResource::TransferMemory, 200).IsSuccess());
|
||||
ASSERT(resource_limit->SetLimitValue(LimitableResource::Sessions, 1133).IsSuccess());
|
||||
|
||||
return resource_limit;
|
||||
}
|
||||
|
||||
} // namespace Kernel
|
||||
|
||||
@@ -67,4 +67,7 @@ private:
|
||||
KLightConditionVariable cond_var;
|
||||
const Core::Timing::CoreTiming* core_timing{};
|
||||
};
|
||||
|
||||
KResourceLimit* CreateResourceLimitForProcess(Core::System& system, s64 physical_memory_size);
|
||||
|
||||
} // namespace Kernel
|
||||
|
||||
@@ -240,13 +240,6 @@ struct KernelCore::Impl {
|
||||
constexpr u64 secure_applet_memory_size{4_MiB};
|
||||
ASSERT(system_resource_limit->Reserve(LimitableResource::PhysicalMemory,
|
||||
secure_applet_memory_size));
|
||||
|
||||
// This memory seems to be reserved on hardware, but is not reserved/used by yuzu.
|
||||
// Likely Horizon OS reserved memory
|
||||
// TODO(ameerj): Derive the memory rather than hardcode it.
|
||||
constexpr u64 unknown_reserved_memory{0x2f896000};
|
||||
ASSERT(system_resource_limit->Reserve(LimitableResource::PhysicalMemory,
|
||||
unknown_reserved_memory));
|
||||
}
|
||||
|
||||
void InitializePreemption(KernelCore& kernel) {
|
||||
|
||||
@@ -2332,7 +2332,7 @@ static ResultCode CreateEvent(Core::System& system, Handle* out_write, Handle* o
|
||||
R_UNLESS(event != nullptr, ResultOutOfResource);
|
||||
|
||||
// Initialize the event.
|
||||
event->Initialize("CreateEvent");
|
||||
event->Initialize("CreateEvent", kernel.CurrentProcess());
|
||||
|
||||
// Commit the thread reservation.
|
||||
event_reservation.Commit();
|
||||
|
||||
@@ -618,7 +618,7 @@ void AppletMessageQueue::PushMessage(AppletMessage msg) {
|
||||
AppletMessageQueue::AppletMessage AppletMessageQueue::PopMessage() {
|
||||
if (messages.empty()) {
|
||||
on_new_message->GetWritableEvent().Clear();
|
||||
return AppletMessage::NoMessage;
|
||||
return AppletMessage::None;
|
||||
}
|
||||
auto msg = messages.front();
|
||||
messages.pop();
|
||||
@@ -633,7 +633,7 @@ std::size_t AppletMessageQueue::GetMessageCount() const {
|
||||
}
|
||||
|
||||
void AppletMessageQueue::RequestExit() {
|
||||
PushMessage(AppletMessage::ExitRequested);
|
||||
PushMessage(AppletMessage::Exit);
|
||||
}
|
||||
|
||||
void AppletMessageQueue::FocusStateChanged() {
|
||||
@@ -732,7 +732,7 @@ void ICommonStateGetter::ReceiveMessage(Kernel::HLERequestContext& ctx) {
|
||||
const auto message = msg_queue->PopMessage();
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
|
||||
if (message == AppletMessageQueue::AppletMessage::NoMessage) {
|
||||
if (message == AppletMessageQueue::AppletMessage::None) {
|
||||
LOG_ERROR(Service_AM, "Message queue is empty");
|
||||
rb.Push(ERR_NO_MESSAGES);
|
||||
rb.PushEnum<AppletMessageQueue::AppletMessage>(message);
|
||||
|
||||
@@ -22,6 +22,7 @@ class NVFlinger;
|
||||
|
||||
namespace Service::AM {
|
||||
|
||||
// This is nn::settings::Language
|
||||
enum SystemLanguage {
|
||||
Japanese = 0,
|
||||
English = 1, // en-US
|
||||
@@ -41,16 +42,44 @@ enum SystemLanguage {
|
||||
// 4.0.0+
|
||||
SimplifiedChinese = 15,
|
||||
TraditionalChinese = 16,
|
||||
// 10.1.0+
|
||||
BrazilianPortuguese = 17,
|
||||
};
|
||||
|
||||
class AppletMessageQueue {
|
||||
public:
|
||||
// This is nn::am::AppletMessage
|
||||
enum class AppletMessage : u32 {
|
||||
NoMessage = 0,
|
||||
ExitRequested = 4,
|
||||
None = 0,
|
||||
ChangeIntoForeground = 1,
|
||||
ChangeIntoBackground = 2,
|
||||
Exit = 4,
|
||||
ApplicationExited = 6,
|
||||
FocusStateChanged = 15,
|
||||
Resume = 16,
|
||||
DetectShortPressingHomeButton = 20,
|
||||
DetectLongPressingHomeButton = 21,
|
||||
DetectShortPressingPowerButton = 22,
|
||||
DetectMiddlePressingPowerButton = 23,
|
||||
DetectLongPressingPowerButton = 24,
|
||||
RequestToPrepareSleep = 25,
|
||||
FinishedSleepSequence = 26,
|
||||
SleepRequiredByHighTemperature = 27,
|
||||
SleepRequiredByLowBattery = 28,
|
||||
AutoPowerDown = 29,
|
||||
OperationModeChanged = 30,
|
||||
PerformanceModeChanged = 31,
|
||||
DetectReceivingCecSystemStandby = 32,
|
||||
SdCardRemoved = 33,
|
||||
LaunchApplicationRequested = 50,
|
||||
RequestToDisplay = 51,
|
||||
ShowApplicationLogo = 55,
|
||||
HideApplicationLogo = 56,
|
||||
ForceHideApplicationLogo = 57,
|
||||
FloatingApplicationDetected = 60,
|
||||
DetectShortPressingCaptureButton = 90,
|
||||
AlbumScreenShotTaken = 92,
|
||||
AlbumRecordingSaved = 93,
|
||||
};
|
||||
|
||||
explicit AppletMessageQueue(Core::System& system);
|
||||
@@ -179,11 +208,14 @@ public:
|
||||
~ICommonStateGetter() override;
|
||||
|
||||
private:
|
||||
// This is nn::oe::FocusState
|
||||
enum class FocusState : u8 {
|
||||
InFocus = 1,
|
||||
NotInFocus = 2,
|
||||
Background = 3,
|
||||
};
|
||||
|
||||
// This is nn::oe::OperationMode
|
||||
enum class OperationMode : u8 {
|
||||
Handheld = 0,
|
||||
Docked = 1,
|
||||
|
||||
@@ -17,8 +17,8 @@ constexpr auto DEFAULT_PERFORMANCE_CONFIGURATION = PerformanceConfiguration::Con
|
||||
|
||||
Controller::Controller(Core::Timing::CoreTiming& core_timing_)
|
||||
: core_timing{core_timing_}, configs{
|
||||
{PerformanceMode::Handheld, DEFAULT_PERFORMANCE_CONFIGURATION},
|
||||
{PerformanceMode::Docked, DEFAULT_PERFORMANCE_CONFIGURATION},
|
||||
{PerformanceMode::Normal, DEFAULT_PERFORMANCE_CONFIGURATION},
|
||||
{PerformanceMode::Boost, DEFAULT_PERFORMANCE_CONFIGURATION},
|
||||
} {}
|
||||
|
||||
Controller::~Controller() = default;
|
||||
@@ -63,13 +63,13 @@ void Controller::SetFromCpuBoostMode(CpuBoostMode mode) {
|
||||
PerformanceConfiguration::Config15,
|
||||
}};
|
||||
|
||||
SetPerformanceConfiguration(PerformanceMode::Docked,
|
||||
SetPerformanceConfiguration(PerformanceMode::Boost,
|
||||
BOOST_MODE_TO_CONFIG_MAP.at(static_cast<u32>(mode)));
|
||||
}
|
||||
|
||||
PerformanceMode Controller::GetCurrentPerformanceMode() const {
|
||||
return Settings::values.use_docked_mode.GetValue() ? PerformanceMode::Docked
|
||||
: PerformanceMode::Handheld;
|
||||
return Settings::values.use_docked_mode.GetValue() ? PerformanceMode::Boost
|
||||
: PerformanceMode::Normal;
|
||||
}
|
||||
|
||||
PerformanceConfiguration Controller::GetCurrentPerformanceConfiguration(PerformanceMode mode) {
|
||||
|
||||
@@ -32,15 +32,18 @@ enum class PerformanceConfiguration : u32 {
|
||||
Config16 = 0x9222000C,
|
||||
};
|
||||
|
||||
// This is nn::oe::CpuBoostMode
|
||||
enum class CpuBoostMode : u32 {
|
||||
Disabled = 0,
|
||||
Full = 1, // CPU + GPU -> Config 13, 14, 15, or 16
|
||||
Partial = 2, // GPU Only -> Config 15 or 16
|
||||
Normal = 0, // Boost mode disabled
|
||||
FastLoad = 1, // CPU + GPU -> Config 13, 14, 15, or 16
|
||||
Partial = 2, // GPU Only -> Config 15 or 16
|
||||
};
|
||||
|
||||
enum class PerformanceMode : u8 {
|
||||
Handheld = 0,
|
||||
Docked = 1,
|
||||
// This is nn::oe::PerformanceMode
|
||||
enum class PerformanceMode : s32 {
|
||||
Invalid = -1,
|
||||
Normal = 0,
|
||||
Boost = 1,
|
||||
};
|
||||
|
||||
// Class to manage the state and change of the emulated system performance.
|
||||
|
||||
@@ -3,7 +3,9 @@
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "core/core.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/hle/kernel/k_event.h"
|
||||
#include "core/hle/kernel/k_memory_manager.h"
|
||||
#include "core/hle/kernel/k_process.h"
|
||||
#include "core/hle/kernel/k_readable_event.h"
|
||||
#include "core/hle/kernel/k_resource_limit.h"
|
||||
@@ -15,10 +17,21 @@ namespace Service::KernelHelpers {
|
||||
|
||||
ServiceContext::ServiceContext(Core::System& system_, std::string name_)
|
||||
: kernel(system_.Kernel()) {
|
||||
|
||||
// Create a resource limit for the process.
|
||||
const auto physical_memory_size =
|
||||
kernel.MemoryManager().GetSize(Kernel::KMemoryManager::Pool::System);
|
||||
auto* resource_limit = Kernel::CreateResourceLimitForProcess(system_, physical_memory_size);
|
||||
|
||||
// Create the process.
|
||||
process = Kernel::KProcess::Create(kernel);
|
||||
ASSERT(Kernel::KProcess::Initialize(process, system_, std::move(name_),
|
||||
Kernel::KProcess::ProcessType::Userland)
|
||||
Kernel::KProcess::ProcessType::KernelInternal,
|
||||
resource_limit)
|
||||
.IsSuccess());
|
||||
|
||||
// Close reference to our resource limit, as the process opens one.
|
||||
resource_limit->Close();
|
||||
}
|
||||
|
||||
ServiceContext::~ServiceContext() {
|
||||
@@ -43,7 +56,7 @@ Kernel::KEvent* ServiceContext::CreateEvent(std::string&& name) {
|
||||
}
|
||||
|
||||
// Initialize the event.
|
||||
event->Initialize(std::move(name));
|
||||
event->Initialize(std::move(name), process);
|
||||
|
||||
// Commit the thread reservation.
|
||||
event_reservation.Commit();
|
||||
|
||||
@@ -39,8 +39,7 @@ struct Memory::Impl {
|
||||
void MapMemoryRegion(Common::PageTable& page_table, VAddr base, u64 size, PAddr target) {
|
||||
ASSERT_MSG((size & PAGE_MASK) == 0, "non-page aligned size: {:016X}", size);
|
||||
ASSERT_MSG((base & PAGE_MASK) == 0, "non-page aligned base: {:016X}", base);
|
||||
ASSERT_MSG(target >= DramMemoryMap::Base && target < DramMemoryMap::End,
|
||||
"Out of bounds target: {:016X}", target);
|
||||
ASSERT_MSG(target >= DramMemoryMap::Base, "Out of bounds target: {:016X}", target);
|
||||
MapPages(page_table, base / PAGE_SIZE, size / PAGE_SIZE, target, Common::PageType::Memory);
|
||||
|
||||
if (Settings::IsFastmemEnabled()) {
|
||||
|
||||
@@ -175,22 +175,23 @@ public:
|
||||
return false;
|
||||
}
|
||||
|
||||
BatteryLevel GetBatteryLevel() {
|
||||
Common::Input::BatteryLevel GetBatteryLevel() {
|
||||
const auto level = SDL_JoystickCurrentPowerLevel(sdl_joystick.get());
|
||||
switch (level) {
|
||||
case SDL_JOYSTICK_POWER_EMPTY:
|
||||
return BatteryLevel::Empty;
|
||||
return Common::Input::BatteryLevel::Empty;
|
||||
case SDL_JOYSTICK_POWER_LOW:
|
||||
return BatteryLevel::Low;
|
||||
return Common::Input::BatteryLevel::Low;
|
||||
case SDL_JOYSTICK_POWER_MEDIUM:
|
||||
return BatteryLevel::Medium;
|
||||
return Common::Input::BatteryLevel::Medium;
|
||||
case SDL_JOYSTICK_POWER_FULL:
|
||||
case SDL_JOYSTICK_POWER_MAX:
|
||||
return BatteryLevel::Full;
|
||||
case SDL_JOYSTICK_POWER_UNKNOWN:
|
||||
return Common::Input::BatteryLevel::Full;
|
||||
case SDL_JOYSTICK_POWER_WIRED:
|
||||
return Common::Input::BatteryLevel::Charging;
|
||||
case SDL_JOYSTICK_POWER_UNKNOWN:
|
||||
default:
|
||||
return BatteryLevel::Charging;
|
||||
return Common::Input::BatteryLevel::None;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -351,6 +352,8 @@ void SDLDriver::HandleGameControllerEvent(const SDL_Event& event) {
|
||||
if (const auto joystick = GetSDLJoystickBySDLID(event.jbutton.which)) {
|
||||
const PadIdentifier identifier = joystick->GetPadIdentifier();
|
||||
SetButton(identifier, event.jbutton.button, true);
|
||||
// Battery doesn't trigger an event so just update every button press
|
||||
SetBattery(identifier, joystick->GetBatteryLevel());
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -192,22 +192,22 @@ std::size_t UDPClient::GetClientNumber(std::string_view host, u16 port) const {
|
||||
return MAX_UDP_CLIENTS;
|
||||
}
|
||||
|
||||
BatteryLevel UDPClient::GetBatteryLevel(Response::Battery battery) const {
|
||||
Common::Input::BatteryLevel UDPClient::GetBatteryLevel(Response::Battery battery) const {
|
||||
switch (battery) {
|
||||
case Response::Battery::Dying:
|
||||
return BatteryLevel::Empty;
|
||||
return Common::Input::BatteryLevel::Empty;
|
||||
case Response::Battery::Low:
|
||||
return BatteryLevel::Critical;
|
||||
return Common::Input::BatteryLevel::Critical;
|
||||
case Response::Battery::Medium:
|
||||
return BatteryLevel::Low;
|
||||
return Common::Input::BatteryLevel::Low;
|
||||
case Response::Battery::High:
|
||||
return BatteryLevel::Medium;
|
||||
return Common::Input::BatteryLevel::Medium;
|
||||
case Response::Battery::Full:
|
||||
case Response::Battery::Charged:
|
||||
return BatteryLevel::Full;
|
||||
return Common::Input::BatteryLevel::Full;
|
||||
case Response::Battery::Charging:
|
||||
default:
|
||||
return BatteryLevel::Charging;
|
||||
return Common::Input::BatteryLevel::Charging;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -141,7 +141,7 @@ private:
|
||||
std::size_t GetClientNumber(std::string_view host, u16 port) const;
|
||||
|
||||
// Translates UDP battery level to input engine battery level
|
||||
BatteryLevel GetBatteryLevel(Response::Battery battery) const;
|
||||
Common::Input::BatteryLevel GetBatteryLevel(Response::Battery battery) const;
|
||||
|
||||
void OnVersion(Response::Version);
|
||||
void OnPortInfo(Response::PortInfo);
|
||||
|
||||
@@ -70,7 +70,7 @@ void InputEngine::SetAxis(const PadIdentifier& identifier, int axis, f32 value)
|
||||
TriggerOnAxisChange(identifier, axis, value);
|
||||
}
|
||||
|
||||
void InputEngine::SetBattery(const PadIdentifier& identifier, BatteryLevel value) {
|
||||
void InputEngine::SetBattery(const PadIdentifier& identifier, Common::Input::BatteryLevel value) {
|
||||
{
|
||||
std::lock_guard lock{mutex};
|
||||
ControllerData& controller = controller_list.at(identifier);
|
||||
@@ -143,13 +143,13 @@ f32 InputEngine::GetAxis(const PadIdentifier& identifier, int axis) const {
|
||||
return axis_iter->second;
|
||||
}
|
||||
|
||||
BatteryLevel InputEngine::GetBattery(const PadIdentifier& identifier) const {
|
||||
Common::Input::BatteryLevel InputEngine::GetBattery(const PadIdentifier& identifier) const {
|
||||
std::lock_guard lock{mutex};
|
||||
const auto controller_iter = controller_list.find(identifier);
|
||||
if (controller_iter == controller_list.cend()) {
|
||||
LOG_ERROR(Input, "Invalid identifier guid={}, pad={}, port={}", identifier.guid.RawString(),
|
||||
identifier.pad, identifier.port);
|
||||
return BatteryLevel::Charging;
|
||||
return Common::Input::BatteryLevel::Charging;
|
||||
}
|
||||
const ControllerData& controller = controller_iter->second;
|
||||
return controller.battery;
|
||||
@@ -270,7 +270,7 @@ void InputEngine::TriggerOnAxisChange(const PadIdentifier& identifier, int axis,
|
||||
}
|
||||
|
||||
void InputEngine::TriggerOnBatteryChange(const PadIdentifier& identifier,
|
||||
[[maybe_unused]] BatteryLevel value) {
|
||||
[[maybe_unused]] Common::Input::BatteryLevel value) {
|
||||
std::lock_guard lock{mutex_callback};
|
||||
for (const auto& poller_pair : callback_list) {
|
||||
const InputIdentifier& poller = poller_pair.second;
|
||||
|
||||
@@ -34,16 +34,6 @@ struct BasicMotion {
|
||||
u64 delta_timestamp{};
|
||||
};
|
||||
|
||||
// Stages of a battery charge
|
||||
enum class BatteryLevel {
|
||||
Empty,
|
||||
Critical,
|
||||
Low,
|
||||
Medium,
|
||||
Full,
|
||||
Charging,
|
||||
};
|
||||
|
||||
// Types of input that are stored in the engine
|
||||
enum class EngineInputType {
|
||||
None,
|
||||
@@ -178,7 +168,7 @@ public:
|
||||
bool GetButton(const PadIdentifier& identifier, int button) const;
|
||||
bool GetHatButton(const PadIdentifier& identifier, int button, u8 direction) const;
|
||||
f32 GetAxis(const PadIdentifier& identifier, int axis) const;
|
||||
BatteryLevel GetBattery(const PadIdentifier& identifier) const;
|
||||
Common::Input::BatteryLevel GetBattery(const PadIdentifier& identifier) const;
|
||||
BasicMotion GetMotion(const PadIdentifier& identifier, int motion) const;
|
||||
|
||||
int SetCallback(InputIdentifier input_identifier);
|
||||
@@ -189,7 +179,7 @@ protected:
|
||||
void SetButton(const PadIdentifier& identifier, int button, bool value);
|
||||
void SetHatButton(const PadIdentifier& identifier, int button, u8 value);
|
||||
void SetAxis(const PadIdentifier& identifier, int axis, f32 value);
|
||||
void SetBattery(const PadIdentifier& identifier, BatteryLevel value);
|
||||
void SetBattery(const PadIdentifier& identifier, Common::Input::BatteryLevel value);
|
||||
void SetMotion(const PadIdentifier& identifier, int motion, const BasicMotion& value);
|
||||
|
||||
virtual std::string GetHatButtonName([[maybe_unused]] u8 direction_value) const {
|
||||
@@ -202,13 +192,13 @@ private:
|
||||
std::unordered_map<int, u8> hat_buttons;
|
||||
std::unordered_map<int, float> axes;
|
||||
std::unordered_map<int, BasicMotion> motions;
|
||||
BatteryLevel battery{};
|
||||
Common::Input::BatteryLevel battery{};
|
||||
};
|
||||
|
||||
void TriggerOnButtonChange(const PadIdentifier& identifier, int button, bool value);
|
||||
void TriggerOnHatButtonChange(const PadIdentifier& identifier, int button, u8 value);
|
||||
void TriggerOnAxisChange(const PadIdentifier& identifier, int axis, f32 value);
|
||||
void TriggerOnBatteryChange(const PadIdentifier& identifier, BatteryLevel value);
|
||||
void TriggerOnBatteryChange(const PadIdentifier& identifier, Common::Input::BatteryLevel value);
|
||||
void TriggerOnMotionChange(const PadIdentifier& identifier, int motion,
|
||||
const BasicMotion& value);
|
||||
|
||||
|
||||
@@ -470,7 +470,7 @@ public:
|
||||
}
|
||||
|
||||
Common::Input::BatteryStatus GetStatus() const {
|
||||
return static_cast<Common::Input::BatteryLevel>(input_engine->GetBattery(identifier));
|
||||
return input_engine->GetBattery(identifier);
|
||||
}
|
||||
|
||||
void ForceUpdate() override {
|
||||
|
||||
@@ -184,6 +184,8 @@ inline GLenum VertexFormat(Maxwell::VertexAttribute attrib) {
|
||||
case Maxwell::VertexAttribute::Size::Size_32_32_32:
|
||||
case Maxwell::VertexAttribute::Size::Size_32_32_32_32:
|
||||
return GL_FLOAT;
|
||||
case Maxwell::VertexAttribute::Size::Size_11_11_10:
|
||||
return GL_UNSIGNED_INT_10F_11F_11F_REV;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -495,6 +495,8 @@ VkFormat VertexFormat(Maxwell::VertexAttribute::Type type, Maxwell::VertexAttrib
|
||||
return VK_FORMAT_R32G32B32_SFLOAT;
|
||||
case Maxwell::VertexAttribute::Size::Size_32_32_32_32:
|
||||
return VK_FORMAT_R32G32B32A32_SFLOAT;
|
||||
case Maxwell::VertexAttribute::Size::Size_11_11_10:
|
||||
return VK_FORMAT_B10G11R11_UFLOAT_PACK32;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -100,6 +100,8 @@ VkFormat GetFormat(const Tegra::FramebufferConfig& framebuffer) {
|
||||
return VK_FORMAT_A8B8G8R8_UNORM_PACK32;
|
||||
case Tegra::FramebufferConfig::PixelFormat::RGB565_UNORM:
|
||||
return VK_FORMAT_R5G6B5_UNORM_PACK16;
|
||||
case Tegra::FramebufferConfig::PixelFormat::B8G8R8A8_UNORM:
|
||||
return VK_FORMAT_B8G8R8A8_UNORM;
|
||||
default:
|
||||
UNIMPLEMENTED_MSG("Unknown framebuffer pixel format: {}",
|
||||
static_cast<u32>(framebuffer.pixel_format));
|
||||
|
||||
@@ -231,9 +231,14 @@ std::vector<std::string> GetSupportedExtensions(vk::PhysicalDevice physical) {
|
||||
return supported_extensions;
|
||||
}
|
||||
|
||||
bool IsExtensionSupported(std::span<const std::string> supported_extensions,
|
||||
std::string_view extension) {
|
||||
return std::ranges::find(supported_extensions, extension) != supported_extensions.end();
|
||||
}
|
||||
|
||||
NvidiaArchitecture GetNvidiaArchitecture(vk::PhysicalDevice physical,
|
||||
std::span<const std::string> exts) {
|
||||
if (std::ranges::find(exts, VK_KHR_FRAGMENT_SHADING_RATE_EXTENSION_NAME) != exts.end()) {
|
||||
if (IsExtensionSupported(exts, VK_KHR_FRAGMENT_SHADING_RATE_EXTENSION_NAME)) {
|
||||
VkPhysicalDeviceFragmentShadingRatePropertiesKHR shading_rate_props{};
|
||||
shading_rate_props.sType =
|
||||
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FRAGMENT_SHADING_RATE_PROPERTIES_KHR;
|
||||
@@ -246,7 +251,7 @@ NvidiaArchitecture GetNvidiaArchitecture(vk::PhysicalDevice physical,
|
||||
return NvidiaArchitecture::AmpereOrNewer;
|
||||
}
|
||||
}
|
||||
if (std::ranges::find(exts, VK_NV_SHADING_RATE_IMAGE_EXTENSION_NAME) != exts.end()) {
|
||||
if (IsExtensionSupported(exts, VK_NV_SHADING_RATE_IMAGE_EXTENSION_NAME)) {
|
||||
return NvidiaArchitecture::Turing;
|
||||
}
|
||||
return NvidiaArchitecture::VoltaOrOlder;
|
||||
@@ -611,7 +616,8 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (ext_extended_dynamic_state && driver_id == VK_DRIVER_ID_MESA_RADV) {
|
||||
const bool is_radv = driver_id == VK_DRIVER_ID_MESA_RADV;
|
||||
if (ext_extended_dynamic_state && is_radv) {
|
||||
// Mask driver version variant
|
||||
const u32 version = (properties.driverVersion << 3) >> 3;
|
||||
if (version < VK_MAKE_API_VERSION(0, 21, 2, 0)) {
|
||||
@@ -620,6 +626,17 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
|
||||
ext_extended_dynamic_state = false;
|
||||
}
|
||||
}
|
||||
if (ext_vertex_input_dynamic_state && is_radv) {
|
||||
// TODO(ameerj): Blacklist only offending driver versions
|
||||
// TODO(ameerj): Confirm if RDNA1 is affected
|
||||
const bool is_rdna2 =
|
||||
IsExtensionSupported(supported_extensions, VK_KHR_FRAGMENT_SHADING_RATE_EXTENSION_NAME);
|
||||
if (is_rdna2) {
|
||||
LOG_WARNING(Render_Vulkan,
|
||||
"RADV has broken VK_EXT_vertex_input_dynamic_state on RDNA2 hardware");
|
||||
ext_vertex_input_dynamic_state = false;
|
||||
}
|
||||
}
|
||||
sets_per_pool = 64;
|
||||
|
||||
const bool is_amd =
|
||||
@@ -635,7 +652,7 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
|
||||
has_broken_cube_compatibility = true;
|
||||
}
|
||||
}
|
||||
const bool is_amd_or_radv = is_amd || driver_id == VK_DRIVER_ID_MESA_RADV;
|
||||
const bool is_amd_or_radv = is_amd || is_radv;
|
||||
if (ext_sampler_filter_minmax && is_amd_or_radv) {
|
||||
// Disable ext_sampler_filter_minmax on AMD GCN4 and lower as it is broken.
|
||||
if (!is_float16_supported) {
|
||||
|
||||
@@ -445,6 +445,7 @@ void Config::ReadCoreValues() {
|
||||
qt_config->beginGroup(QStringLiteral("Core"));
|
||||
|
||||
ReadGlobalSetting(Settings::values.use_multi_core);
|
||||
ReadGlobalSetting(Settings::values.use_extended_memory_layout);
|
||||
|
||||
qt_config->endGroup();
|
||||
}
|
||||
@@ -1019,6 +1020,7 @@ void Config::SaveCoreValues() {
|
||||
qt_config->beginGroup(QStringLiteral("Core"));
|
||||
|
||||
WriteGlobalSetting(Settings::values.use_multi_core);
|
||||
WriteGlobalSetting(Settings::values.use_extended_memory_layout);
|
||||
|
||||
qt_config->endGroup();
|
||||
}
|
||||
|
||||
@@ -42,6 +42,9 @@ void ConfigureGeneral::SetConfiguration() {
|
||||
|
||||
ui->use_multi_core->setEnabled(runtime_lock);
|
||||
ui->use_multi_core->setChecked(Settings::values.use_multi_core.GetValue());
|
||||
ui->use_extended_memory_layout->setEnabled(runtime_lock);
|
||||
ui->use_extended_memory_layout->setChecked(
|
||||
Settings::values.use_extended_memory_layout.GetValue());
|
||||
|
||||
ui->toggle_check_exit->setChecked(UISettings::values.confirm_before_closing.GetValue());
|
||||
ui->toggle_user_on_boot->setChecked(UISettings::values.select_user_on_boot.GetValue());
|
||||
@@ -91,6 +94,9 @@ void ConfigureGeneral::ResetDefaults() {
|
||||
void ConfigureGeneral::ApplyConfiguration() {
|
||||
ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_multi_core, ui->use_multi_core,
|
||||
use_multi_core);
|
||||
ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_extended_memory_layout,
|
||||
ui->use_extended_memory_layout,
|
||||
use_extended_memory_layout);
|
||||
|
||||
if (Settings::IsConfiguringGlobal()) {
|
||||
UISettings::values.confirm_before_closing = ui->toggle_check_exit->isChecked();
|
||||
@@ -160,6 +166,9 @@ void ConfigureGeneral::SetupPerGameUI() {
|
||||
Settings::values.use_speed_limit, use_speed_limit);
|
||||
ConfigurationShared::SetColoredTristate(ui->use_multi_core, Settings::values.use_multi_core,
|
||||
use_multi_core);
|
||||
ConfigurationShared::SetColoredTristate(ui->use_extended_memory_layout,
|
||||
Settings::values.use_extended_memory_layout,
|
||||
use_extended_memory_layout);
|
||||
|
||||
connect(ui->toggle_speed_limit, &QCheckBox::clicked, ui->speed_limit, [this]() {
|
||||
ui->speed_limit->setEnabled(ui->toggle_speed_limit->isChecked() &&
|
||||
|
||||
@@ -48,6 +48,7 @@ private:
|
||||
|
||||
ConfigurationShared::CheckState use_speed_limit;
|
||||
ConfigurationShared::CheckState use_multi_core;
|
||||
ConfigurationShared::CheckState use_extended_memory_layout;
|
||||
|
||||
const Core::System& system;
|
||||
};
|
||||
|
||||
@@ -142,6 +142,13 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="use_extended_memory_layout">
|
||||
<property name="text">
|
||||
<string>Extended memory layout (6GB DRAM)</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="toggle_check_exit">
|
||||
<property name="text">
|
||||
|
||||
@@ -2937,6 +2937,7 @@ void GMainWindow::OnLoadAmiibo() {
|
||||
if (nfc_state == Service::NFP::DeviceState::TagFound ||
|
||||
nfc_state == Service::NFP::DeviceState::TagMounted) {
|
||||
nfc->CloseAmiibo();
|
||||
QMessageBox::warning(this, tr("Amiibo"), tr("The current amiibo has been removed"));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -2965,6 +2966,15 @@ void GMainWindow::LoadAmiibo(const QString& filename) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove amiibo if one is connected
|
||||
const auto nfc_state = nfc->GetCurrentState();
|
||||
if (nfc_state == Service::NFP::DeviceState::TagFound ||
|
||||
nfc_state == Service::NFP::DeviceState::TagMounted) {
|
||||
nfc->CloseAmiibo();
|
||||
QMessageBox::warning(this, tr("Amiibo"), tr("The current amiibo has been removed"));
|
||||
return;
|
||||
}
|
||||
|
||||
QFile nfc_file{filename};
|
||||
if (!nfc_file.open(QIODevice::ReadOnly)) {
|
||||
QMessageBox::warning(this, tr("Error opening Amiibo data file"),
|
||||
|
||||
@@ -266,6 +266,7 @@ void Config::ReadValues() {
|
||||
|
||||
// Core
|
||||
ReadSetting("Core", Settings::values.use_multi_core);
|
||||
ReadSetting("Core", Settings::values.use_extended_memory_layout);
|
||||
|
||||
// Cpu
|
||||
ReadSetting("Cpu", Settings::values.cpu_accuracy);
|
||||
|
||||
Reference in New Issue
Block a user