diff --git a/libraries/libstratosphere/include/stratosphere/os.hpp b/libraries/libstratosphere/include/stratosphere/os.hpp index 4db896902..0e3c22d36 100644 --- a/libraries/libstratosphere/include/stratosphere/os.hpp +++ b/libraries/libstratosphere/include/stratosphere/os.hpp @@ -29,6 +29,7 @@ #include #include #include +#include #include #include #include diff --git a/libraries/libstratosphere/include/stratosphere/os/os_managed_handle.hpp b/libraries/libstratosphere/include/stratosphere/os/os_managed_handle.hpp index b5e5281d4..737c3c245 100644 --- a/libraries/libstratosphere/include/stratosphere/os/os_managed_handle.hpp +++ b/libraries/libstratosphere/include/stratosphere/os/os_managed_handle.hpp @@ -70,6 +70,11 @@ namespace ams::os { return h; } + void Detach() { + const Handle h = this->Move(); + AMS_UNUSED(h); + } + void Reset(Handle h) { ManagedHandle(h).Swap(*this); } diff --git a/libraries/libstratosphere/include/stratosphere/os/os_transfer_memory.hpp b/libraries/libstratosphere/include/stratosphere/os/os_transfer_memory.hpp new file mode 100644 index 000000000..c06e352bc --- /dev/null +++ b/libraries/libstratosphere/include/stratosphere/os/os_transfer_memory.hpp @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once +#include +#include +#include + +namespace ams::os { + + class TransferMemory { + NON_COPYABLE(TransferMemory); + NON_MOVEABLE(TransferMemory); + private: + TransferMemoryType tmem; + public: + constexpr TransferMemory() : tmem{ .state = TransferMemoryType::State_NotInitialized } { + /* ... */ + } + + TransferMemory(void *address, size_t size, MemoryPermission perm) { + R_ABORT_UNLESS(CreateTransferMemory(std::addressof(this->tmem), address, size, perm)); + } + + TransferMemory(size_t size, Handle handle, bool managed) { + this->Attach(size, handle, managed); + } + + ~TransferMemory() { + if (this->tmem.state == TransferMemoryType::State_NotInitialized) { + return; + } + DestroyTransferMemory(std::addressof(this->tmem)); + } + + void Attach(size_t size, Handle handle, bool managed) { + AttachTransferMemory(std::addressof(this->tmem), size, handle, managed); + } + + Handle Detach() { + return DetachTransferMemory(std::addressof(this->tmem)); + } + + Result Map(void **out, MemoryPermission owner_perm) { + return MapTransferMemory(out, std::addressof(this->tmem), owner_perm); + } + + void Unmap() { + UnmapTransferMemory(std::addressof(this->tmem)); + } + + operator TransferMemoryType &() { + return this->tmem; + } + + operator const TransferMemoryType &() const { + return this->tmem; + } + + TransferMemoryType *GetBase() { + return std::addressof(this->tmem); + } + }; + +} diff --git a/libraries/libstratosphere/include/stratosphere/os/os_transfer_memory_api.hpp b/libraries/libstratosphere/include/stratosphere/os/os_transfer_memory_api.hpp new file mode 100644 index 000000000..2891903f1 --- /dev/null +++ b/libraries/libstratosphere/include/stratosphere/os/os_transfer_memory_api.hpp @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once +#include +#include + +namespace ams::os { + + struct TransferMemoryType; + + Result CreateTransferMemory(TransferMemoryType *tmem, void *address, size_t size, MemoryPermission perm); + + Result AttachTransferMemory(TransferMemoryType *tmem, size_t size, Handle handle, bool managed); + Handle DetachTransferMemory(TransferMemoryType *tmem); + + void DestroyTransferMemory(TransferMemoryType *tmem); + + Result MapTransferMemory(void **out, TransferMemoryType *tmem, MemoryPermission owner_perm); + void UnmapTransferMemory(TransferMemoryType *tmem); + +} diff --git a/libraries/libstratosphere/include/stratosphere/os/os_transfer_memory_types.hpp b/libraries/libstratosphere/include/stratosphere/os/os_transfer_memory_types.hpp new file mode 100644 index 000000000..2b532c0b2 --- /dev/null +++ b/libraries/libstratosphere/include/stratosphere/os/os_transfer_memory_types.hpp @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once +#include +#include + +namespace ams::os { + + struct TransferMemoryType { + enum State { + State_NotInitialized = 0, + State_Created = 1, + State_Mapped = 2, + State_Detached = 3, + }; + + u8 state; + bool handle_managed; + bool allocated; + + void *address; + size_t size; + Handle handle; + + mutable impl::InternalCriticalSectionStorage cs_transfer_memory; + }; + static_assert(std::is_trivial::value); + +} diff --git a/libraries/libstratosphere/include/stratosphere/settings.hpp b/libraries/libstratosphere/include/stratosphere/settings.hpp index d78e42bef..5b995f595 100644 --- a/libraries/libstratosphere/include/stratosphere/settings.hpp +++ b/libraries/libstratosphere/include/stratosphere/settings.hpp @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include diff --git a/libraries/libstratosphere/include/stratosphere/settings/system/settings_platform_region.hpp b/libraries/libstratosphere/include/stratosphere/settings/system/settings_platform_region.hpp new file mode 100644 index 000000000..242358cf0 --- /dev/null +++ b/libraries/libstratosphere/include/stratosphere/settings/system/settings_platform_region.hpp @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once +#include +#include + +namespace ams::settings::system { + + enum PlatformRegion { + PlatformRegion_Invalid = 0, + PlatformRegion_Global = 1, + PlatformRegion_China = 2, + }; + + PlatformRegion GetPlatformRegion(); + +} diff --git a/libraries/libstratosphere/source/os/impl/os_transfer_memory_impl.hpp b/libraries/libstratosphere/source/os/impl/os_transfer_memory_impl.hpp new file mode 100644 index 000000000..39d18a5a6 --- /dev/null +++ b/libraries/libstratosphere/source/os/impl/os_transfer_memory_impl.hpp @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include + +namespace ams::os::impl { + + class TransferMemoryImpl { + public: + static Result Create(Handle *out, void *address, size_t size, MemoryPermission perm); + static void Close(Handle handle); + + static Result Map(void **out, Handle handle, void *address, size_t size, MemoryPermission owner_perm); + static void Unmap(Handle handle, void *address, size_t size); + }; + +} \ No newline at end of file diff --git a/libraries/libstratosphere/source/os/impl/os_transfer_memory_impl.os.horizon.cpp b/libraries/libstratosphere/source/os/impl/os_transfer_memory_impl.os.horizon.cpp new file mode 100644 index 000000000..d67a87b79 --- /dev/null +++ b/libraries/libstratosphere/source/os/impl/os_transfer_memory_impl.os.horizon.cpp @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include +#include "os_transfer_memory_impl.hpp" + +namespace ams::os::impl { + + namespace { + + svc::MemoryPermission ConvertToSvcMemoryPermission(os::MemoryPermission perm) { + switch (perm) { + case os::MemoryPermission_None: return svc::MemoryPermission_None; + case os::MemoryPermission_ReadOnly: return svc::MemoryPermission_Read; + case os::MemoryPermission_WriteOnly: return svc::MemoryPermission_Write; + case os::MemoryPermission_ReadWrite: return svc::MemoryPermission_ReadWrite; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + } + + Result TransferMemoryImpl::Create(Handle *out, void *address, size_t size, MemoryPermission perm) { + /* Convert memory permission. */ + auto svc_perm = ConvertToSvcMemoryPermission(perm); + + /* Create the memory. */ + svc::Handle handle; + R_TRY_CATCH(svc::CreateTransferMemory(std::addressof(handle), reinterpret_cast(address), size, svc_perm)) { + R_CONVERT(svc::ResultOutOfHandles, os::ResultOutOfHandles()) + R_CONVERT(svc::ResultOutOfResource, os::ResultOutOfTransferMemory()) + } R_END_TRY_CATCH_WITH_ABORT_UNLESS; + + *out = handle; + return ResultSuccess(); + } + + void TransferMemoryImpl::Close(Handle handle) { + R_ABORT_UNLESS(svc::CloseHandle(handle)); + } + + Result TransferMemoryImpl::Map(void **out, Handle handle, void *address, size_t size, MemoryPermission owner_perm) { + AMS_ASSERT(address != nullptr); + + /* Convert memory permission. */ + auto svc_owner_perm = ConvertToSvcMemoryPermission(owner_perm); + + /* Map the memory. */ + R_TRY_CATCH(svc::MapTransferMemory(handle, reinterpret_cast(address), size, svc_owner_perm)) { + R_CONVERT(svc::ResultInvalidHandle, os::ResultInvalidHandle()) + R_CONVERT(svc::ResultInvalidSize, os::ResultInvalidTransferMemorySize()) + R_CONVERT(svc::ResultInvalidState, os::ResultInvalidTransferMemoryState()) + R_CONVERT(svc::ResultInvalidCurrentMemory, os::ResultInvalidCurrentMemoryState()) + R_CONVERT(svc::ResultInvalidMemoryRegion, os::ResultInvalidCurrentMemoryState()) + } R_END_TRY_CATCH_WITH_ABORT_UNLESS; + + *out = address; + return ResultSuccess(); + } + + void TransferMemoryImpl::Unmap(Handle handle, void *address, size_t size) { + R_ABORT_UNLESS(svc::UnmapTransferMemory(handle, reinterpret_cast(address), size)); + } + +} diff --git a/libraries/libstratosphere/source/os/os_transfer_memory_api.cpp b/libraries/libstratosphere/source/os/os_transfer_memory_api.cpp new file mode 100644 index 000000000..747e1492e --- /dev/null +++ b/libraries/libstratosphere/source/os/os_transfer_memory_api.cpp @@ -0,0 +1,185 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include +#include "impl/os_thread_manager.hpp" +#include "impl/os_transfer_memory_impl.hpp" + +namespace ams::os { + + namespace { + + Result MapTransferMemoryWithAddressUnsafe(TransferMemoryType *tmem, void *address, os::MemoryPermission owner_perm) { + /* Map the transfer memory. */ + void *mapped_address = nullptr; + R_TRY(impl::TransferMemoryImpl::Map(std::addressof(mapped_address), tmem->handle, address, tmem->size, owner_perm)); + + /* Set fields now that we've mapped. */ + tmem->address = mapped_address; + tmem->state = TransferMemoryType::State_Mapped; + + return ResultSuccess(); + } + + inline void SetupTransferMemoryType(TransferMemoryType *tmem, size_t size, Handle handle, bool managed) { + /* Set members. */ + tmem->handle = handle; + tmem->size = size; + tmem->address = nullptr; + tmem->allocated = false; + + /* Set managed. */ + tmem->handle_managed = managed; + + /* Create the critical section. */ + new (GetPointer(tmem->cs_transfer_memory)) impl::InternalCriticalSection; + } + + } + + Result CreateTransferMemory(TransferMemoryType *tmem, void *address, size_t size, MemoryPermission perm) { + /* Validate pre-conditions. */ + AMS_ASSERT(size > 0); + AMS_ASSERT(util::IsAligned(size, os::MemoryPageSize)); + AMS_ASSERT(address != nullptr); + AMS_ASSERT(util::IsAligned(reinterpret_cast(address), os::MemoryPageSize)); + + /* Create the memory. */ + Handle handle; + R_TRY(impl::TransferMemoryImpl::Create(std::addressof(handle), address, size, perm)); + + /* Setup the object. */ + SetupTransferMemoryType(tmem, size, handle, true); + + return ResultSuccess(); + } + + Result AttachTransferMemory(TransferMemoryType *tmem, size_t size, Handle handle, bool managed) { + AMS_ASSERT(size > 0); + AMS_ASSERT(util::IsAligned(size, os::MemoryPageSize)); + AMS_ASSERT(handle != svc::InvalidHandle); + + /* Setup the object. */ + SetupTransferMemoryType(tmem, size, handle, managed); + + return ResultSuccess(); + } + + Handle DetachTransferMemory(TransferMemoryType *tmem) { + AMS_ASSERT(tmem->state == TransferMemoryType::State_Created); + + /* Set state to detached. */ + tmem->state = TransferMemoryType::State_Detached; + + /* Clear handle. */ + Handle handle = tmem->handle; + + tmem->handle = svc::InvalidHandle; + tmem->handle_managed = false; + + return handle; + } + + void DestroyTransferMemory(TransferMemoryType *tmem) { + /* Unmap the transfer memory, if required. */ + if (tmem->state == TransferMemoryType::State_Mapped) { + UnmapTransferMemory(tmem); + } + + /* Check the state is valid. */ + AMS_ASSERT(tmem->state == TransferMemoryType::State_Created || tmem->state == TransferMemoryType::State_Detached); + + /* Set state to not initialized. */ + tmem->state = TransferMemoryType::State_NotInitialized; + + /* Close the handle, if it's managed. */ + if (tmem->handle_managed) { + impl::TransferMemoryImpl::Close(tmem->handle); + } + tmem->handle_managed = false; + + /* Clear members. */ + tmem->address = nullptr; + tmem->size = 0; + tmem->handle = svc::InvalidHandle; + + /* Destroy the critical section. */ + GetReference(tmem->cs_transfer_memory).~InternalCriticalSection(); + } + + Result MapTransferMemory(void **out, TransferMemoryType *tmem, MemoryPermission owner_perm) { + /* Lock the current thread, and then the transfer memory. */ + std::scoped_lock thread_lk(GetReference(impl::GetCurrentThread()->cs_thread)); + std::scoped_lock lk(GetReference(tmem->cs_transfer_memory)); + + /* Ensure we're in a mappable state. */ + AMS_ASSERT(tmem->state == TransferMemoryType::State_Created); + + /* Try to map up to 64 times. */ + for (int i = 0; i < 64; ++i) { + /* Reserve space to map the memory. */ + /* TODO: os::AslrSpaceManager */ + void *map_address = ::virtmemReserve(tmem->size); + R_UNLESS(map_address != nullptr, os::ResultOutOfAddressSpace()); + + /* Mark allocated. */ + tmem->allocated = true; + auto alloc_guard = SCOPE_GUARD { tmem->allocated = false; }; + + /* Try to map. */ + R_TRY_CATCH(MapTransferMemoryWithAddressUnsafe(tmem, map_address, owner_perm)) { + /* If we failed to map at the address, retry. */ + R_CATCH(os::ResultInvalidCurrentMemoryState) { continue; } + } R_END_TRY_CATCH; + + /* TODO: Check guard space via aslr manager. */ + if (false /* !impl::GetAslrSpaceManager()->CheckGuardSpace(reinterpret_cast(tmem->address), tmem->size) */) { + impl::TransferMemoryImpl::Unmap(tmem->handle, tmem->address, tmem->size); + continue; + } + + /* We mapped successfully. */ + alloc_guard.Cancel(); + *out = tmem->address; + return ResultSuccess(); + } + + /* We failed to map. */ + return os::ResultOutOfAddressSpace(); + } + + void UnmapTransferMemory(TransferMemoryType *tmem) { + /* Lock the memory. */ + std::scoped_lock lk(GetReference(tmem->cs_transfer_memory)); + + /* If the memory isn't mapped, we can't unmap it. */ + if (tmem->state != TransferMemoryType::State_Mapped) { + return; + } + + /* Unmap the memory. */ + impl::TransferMemoryImpl::Unmap(tmem->handle, tmem->address, tmem->size); + + /* Unmapped memory is necessarily not allocated. */ + if (tmem->allocated) { + tmem->allocated = false; + } + + /* Clear the address. */ + tmem->address = nullptr; + tmem->state = TransferMemoryType::State_Created; + } + +} diff --git a/libraries/libstratosphere/source/settings/impl/settings_platform_region_impl.cpp b/libraries/libstratosphere/source/settings/impl/settings_platform_region_impl.cpp new file mode 100644 index 000000000..ce0b62ebe --- /dev/null +++ b/libraries/libstratosphere/source/settings/impl/settings_platform_region_impl.cpp @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include +#include "settings_platform_region_impl.hpp" + +namespace ams::settings::impl { + + Result GetPlatformRegion(s32 *out) { + static_assert(sizeof(*out) == sizeof(::SetSysPlatformRegion)); + return ::setsysGetPlatformRegion(reinterpret_cast<::SetSysPlatformRegion *>(out)); + } + +} diff --git a/libraries/libstratosphere/source/settings/impl/settings_platform_region_impl.hpp b/libraries/libstratosphere/source/settings/impl/settings_platform_region_impl.hpp new file mode 100644 index 000000000..d5b6833ec --- /dev/null +++ b/libraries/libstratosphere/source/settings/impl/settings_platform_region_impl.hpp @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include + +namespace ams::settings::impl { + + Result GetPlatformRegion(s32 *out); + +} diff --git a/libraries/libstratosphere/source/settings/settings_platform_region.cpp b/libraries/libstratosphere/source/settings/settings_platform_region.cpp new file mode 100644 index 000000000..b4c26b8c8 --- /dev/null +++ b/libraries/libstratosphere/source/settings/settings_platform_region.cpp @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include +#include "impl/settings_platform_region_impl.hpp" + +namespace ams::settings::system { + + PlatformRegion GetPlatformRegion() { + if (hos::GetVersion() >= hos::Version_9_0_0) { + s32 region = 0; + R_ABORT_UNLESS(settings::impl::GetPlatformRegion(std::addressof(region))); + return static_cast(region); + } else { + return PlatformRegion_Global; + } + } + +} diff --git a/libraries/libvapours/include/vapours/results/ns_results.hpp b/libraries/libvapours/include/vapours/results/ns_results.hpp index 557b03c82..32f954225 100644 --- a/libraries/libvapours/include/vapours/results/ns_results.hpp +++ b/libraries/libvapours/include/vapours/results/ns_results.hpp @@ -21,7 +21,11 @@ namespace ams::ns { R_DEFINE_NAMESPACE_RESULT_MODULE(16); - R_DEFINE_ERROR_RESULT(Canceled, 90); - R_DEFINE_ERROR_RESULT(OutOfMaxRunningTask, 110); + R_DEFINE_ERROR_RESULT(Canceled, 90); + R_DEFINE_ERROR_RESULT(OutOfMaxRunningTask, 110); + R_DEFINE_ERROR_RESULT(CardUpdateNotSetup, 270); + R_DEFINE_ERROR_RESULT(CardUpdateNotPrepared, 280); + R_DEFINE_ERROR_RESULT(CardUpdateAlreadySetup, 290); + R_DEFINE_ERROR_RESULT(PrepareCardUpdateAlreadyRequested, 460); } diff --git a/libraries/libvapours/include/vapours/results/os_results.hpp b/libraries/libvapours/include/vapours/results/os_results.hpp index 742b12c6d..05479e26d 100644 --- a/libraries/libvapours/include/vapours/results/os_results.hpp +++ b/libraries/libvapours/include/vapours/results/os_results.hpp @@ -21,12 +21,20 @@ namespace ams::os { R_DEFINE_NAMESPACE_RESULT_MODULE(3); - R_DEFINE_ERROR_RESULT(Busy, 4); + R_DEFINE_ERROR_RESULT(Busy, 4); - R_DEFINE_ERROR_RESULT(OutOfMemory, 8); - R_DEFINE_ERROR_RESULT(OutOfResource, 9); + R_DEFINE_ERROR_RESULT(OutOfMemory, 8); + R_DEFINE_ERROR_RESULT(OutOfResource, 9); - R_DEFINE_ERROR_RESULT(OutOfVirtualAddressSpace, 12); - R_DEFINE_ERROR_RESULT(ResourceLimit, 13); + R_DEFINE_ERROR_RESULT(OutOfVirtualAddressSpace, 12); + R_DEFINE_ERROR_RESULT(ResourceLimit, 13); + + R_DEFINE_ERROR_RESULT(OutOfHandles, 500); + R_DEFINE_ERROR_RESULT(InvalidHandle, 501); + R_DEFINE_ERROR_RESULT(InvalidCurrentMemoryState, 502); + R_DEFINE_ERROR_RESULT(InvalidTransferMemoryState, 503); + R_DEFINE_ERROR_RESULT(InvalidTransferMemorySize, 504); + R_DEFINE_ERROR_RESULT(OutOfTransferMemory, 505); + R_DEFINE_ERROR_RESULT(OutOfAddressSpace, 506); } diff --git a/stratosphere/ams_mitm/source/sysupdater/sysupdater_apply_manager.cpp b/stratosphere/ams_mitm/source/sysupdater/sysupdater_apply_manager.cpp new file mode 100644 index 000000000..17591503a --- /dev/null +++ b/stratosphere/ams_mitm/source/sysupdater/sysupdater_apply_manager.cpp @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include +#include "sysupdater_apply_manager.hpp" + +namespace ams::mitm::sysupdater { + + namespace { + + alignas(os::MemoryPageSize) u8 g_boot_image_update_buffer[64_KB]; + + updater::BootImageUpdateType GetBootImageUpdateType() { + int boot_image_update_type; + auto size = settings::fwdbg::GetSettingsItemValue(std::addressof(boot_image_update_type), sizeof(boot_image_update_type), "systeminitializer", "boot_image_update_type"); + if (size != sizeof(boot_image_update_type)) { + return updater::BootImageUpdateType::Erista; + } + return updater::GetBootImageUpdateType(boot_image_update_type); + } + + Result MarkPreCommitForBootImages() { + /* Set verification required for both normal and safe mode. */ + R_TRY(updater::MarkVerifyingRequired(updater::BootModeType::Normal, g_boot_image_update_buffer, sizeof(g_boot_image_update_buffer))); + R_TRY(updater::MarkVerifyingRequired(updater::BootModeType::Safe, g_boot_image_update_buffer, sizeof(g_boot_image_update_buffer))); + + /* Pre-commit is now marked. */ + return ResultSuccess(); + } + + Result UpdateBootImages() { + /* Define a helper to update the images. */ + auto UpdateBootImageImpl = [](updater::BootModeType boot_mode, updater::BootImageUpdateType boot_image_update_type) -> Result { + /* Get the boot image package id. */ + ncm::SystemDataId boot_image_package_id = {}; + R_TRY_CATCH(updater::GetBootImagePackageId(std::addressof(boot_image_package_id), boot_mode, g_boot_image_update_buffer, sizeof(g_boot_image_update_buffer))) { + R_CATCH(updater::ResultBootImagePackageNotFound) { + /* Nintendo simply falls through when the package is not found. */ + } + } R_END_TRY_CATCH; + + + /* Update the boot images. */ + R_TRY_CATCH(updater::UpdateBootImagesFromPackage(boot_image_package_id, boot_mode, g_boot_image_update_buffer, sizeof(g_boot_image_update_buffer), boot_image_update_type)) { + R_CATCH(updater::ResultBootImagePackageNotFound) { + /* Nintendo simply falls through when the package is not found. */ + } + } R_END_TRY_CATCH; + + /* Mark the images verified. */ + R_TRY(updater::MarkVerified(boot_mode, g_boot_image_update_buffer, sizeof(g_boot_image_update_buffer))); + + /* The boot images are updated. */ + return ResultSuccess(); + }; + + /* Get the boot image update type. */ + auto boot_image_update_type = GetBootImageUpdateType(); + + /* Update boot images for safe mode. */ + R_TRY(UpdateBootImageImpl(updater::BootModeType::Safe, boot_image_update_type)); + + /* Update boot images for normal mode. */ + R_TRY(UpdateBootImageImpl(updater::BootModeType::Normal, boot_image_update_type)); + + /* Both sets of images are updated. */ + return ResultSuccess(); + } + + } + + Result SystemUpdateApplyManager::ApplyPackageTask(ncm::PackageSystemDowngradeTask *task) { + /* Lock the apply mutex. */ + std::scoped_lock lk(this->apply_mutex); + + /* NOTE: Here, Nintendo creates a system report for the update. */ + + /* Mark boot images to note that we're updating. */ + R_TRY(MarkPreCommitForBootImages()); + + /* Commit the task. */ + R_TRY(task->Commit()); + + /* Update the boot images. */ + R_TRY(UpdateBootImages()); + + return ResultSuccess(); + } + +} diff --git a/stratosphere/ams_mitm/source/sysupdater/sysupdater_apply_manager.hpp b/stratosphere/ams_mitm/source/sysupdater/sysupdater_apply_manager.hpp new file mode 100644 index 000000000..d05bad8c4 --- /dev/null +++ b/stratosphere/ams_mitm/source/sysupdater/sysupdater_apply_manager.hpp @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include + +namespace ams::mitm::sysupdater { + + class SystemUpdateApplyManager { + private: + os::Mutex apply_mutex; + public: + constexpr SystemUpdateApplyManager() : apply_mutex(false) { /* ... */ } + + Result ApplyPackageTask(ncm::PackageSystemDowngradeTask *task); + }; + +} diff --git a/stratosphere/ams_mitm/source/sysupdater/sysupdater_async_impl.hpp b/stratosphere/ams_mitm/source/sysupdater/sysupdater_async_impl.hpp index 0458e5f8b..e65f9b50a 100644 --- a/stratosphere/ams_mitm/source/sysupdater/sysupdater_async_impl.hpp +++ b/stratosphere/ams_mitm/source/sysupdater/sysupdater_async_impl.hpp @@ -15,6 +15,7 @@ */ #pragma once #include +#include "sysupdater_i_async_result.hpp" #include "sysupdater_thread_allocator.hpp" namespace ams::mitm::sysupdater { @@ -58,18 +59,18 @@ namespace ams::mitm::sysupdater { } }; - class AsyncBase { + class AsyncBase : public IAsyncBase { public: virtual ~AsyncBase() { /* ... */ } - Result Cancel() { + static Result ToAsyncResult(Result result); + + virtual Result Cancel() override final { this->CancelImpl(); return ResultSuccess(); } - static Result ToAsyncResult(Result result); - - virtual Result GetErrorContext(sf::Out out) { + virtual Result GetErrorContext(sf::Out out) override { *out = {}; return ResultSuccess(); } @@ -77,12 +78,27 @@ namespace ams::mitm::sysupdater { virtual void CancelImpl() = 0; }; - class AsyncResultBase : public AsyncBase { + class AsyncResultBase : public IAsyncResult { public: - Result Get() { + virtual ~AsyncResultBase() { /* ... */ } + + static Result ToAsyncResult(Result result) { return AsyncBase::ToAsyncResult(result); } + + virtual Result Cancel() override final { + this->CancelImpl(); + return ResultSuccess(); + } + + virtual Result Get() override final { return ToAsyncResult(this->GetImpl()); } + + virtual Result GetErrorContext(sf::Out out) override { + *out = {}; + return ResultSuccess(); + } private: + virtual void CancelImpl() = 0; virtual Result GetImpl() = 0; }; diff --git a/stratosphere/ams_mitm/source/sysupdater/sysupdater_i_async_result.hpp b/stratosphere/ams_mitm/source/sysupdater/sysupdater_i_async_result.hpp new file mode 100644 index 000000000..77d37447d --- /dev/null +++ b/stratosphere/ams_mitm/source/sysupdater/sysupdater_i_async_result.hpp @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include + +namespace ams::mitm::sysupdater { + + class IAsyncBase : public sf::IServiceObject { + public: + virtual Result Cancel() = 0; + virtual Result GetErrorContext(sf::Out out) = 0; + }; + + class IAsyncResult : public IAsyncBase { + private: + enum class CommandId { + Get = 0, + Cancel = 1, + GetErrorContext = 2, + }; + public: + virtual Result Get() = 0; + public: + DEFINE_SERVICE_DISPATCH_TABLE { + MAKE_SERVICE_COMMAND_META(Get), + MAKE_SERVICE_COMMAND_META(Cancel), + MAKE_SERVICE_COMMAND_META(GetErrorContext), + }; + }; + +} diff --git a/stratosphere/ams_mitm/source/sysupdater/sysupdater_module.cpp b/stratosphere/ams_mitm/source/sysupdater/sysupdater_module.cpp index 1ee370a8e..bd4c35474 100644 --- a/stratosphere/ams_mitm/source/sysupdater/sysupdater_module.cpp +++ b/stratosphere/ams_mitm/source/sysupdater/sysupdater_module.cpp @@ -27,7 +27,7 @@ namespace ams::mitm::sysupdater { constexpr size_t SystemUpdateMaxSessions = 1; constexpr size_t MaxServers = 1; - constexpr size_t MaxSessions = SystemUpdateMaxSessions; + constexpr size_t MaxSessions = SystemUpdateMaxSessions + 3; struct ServerOptions { static constexpr size_t PointerBufferSize = 1_KB; @@ -37,6 +37,8 @@ namespace ams::mitm::sysupdater { sf::hipc::ServerManager g_server_manager; + constinit sysupdater::SystemUpdateService g_system_update_service_object; + } void MitmModule::ThreadFunction(void *arg) { @@ -48,7 +50,7 @@ namespace ams::mitm::sysupdater { ON_SCOPE_EXIT { nim::FinalizeForNetworkInstallManager(); }; /* Register ams:su. */ - R_ABORT_UNLESS((g_server_manager.RegisterServer(SystemUpdateServiceName, SystemUpdateMaxSessions))); + R_ABORT_UNLESS((g_server_manager.RegisterServer(SystemUpdateServiceName, SystemUpdateMaxSessions, sf::ServiceObjectTraits::SharedPointerHelper::GetEmptyDeleteSharedPointer(std::addressof(g_system_update_service_object))))); /* Loop forever, servicing our services. */ g_server_manager.LoopProcess(); diff --git a/stratosphere/ams_mitm/source/sysupdater/sysupdater_service.cpp b/stratosphere/ams_mitm/source/sysupdater/sysupdater_service.cpp index 341044e43..493b49b56 100644 --- a/stratosphere/ams_mitm/source/sysupdater/sysupdater_service.cpp +++ b/stratosphere/ams_mitm/source/sysupdater/sysupdater_service.cpp @@ -15,6 +15,7 @@ */ #include #include "sysupdater_service.hpp" +#include "sysupdater_async_impl.hpp" #include "sysupdater_fs_utils.hpp" namespace ams::mitm::sysupdater { @@ -275,45 +276,6 @@ namespace ams::mitm::sysupdater { return ResultSuccess(); } - Result ActivateSystemUpdateContentMetaDatabase() { - /* TODO: Don't use gamecard db. */ - return ncm::ActivateContentMetaDatabase(ncm::StorageId::GameCard); - } - - void InactivateSystemUpdateContentMetaDatabase() { - /* TODO: Don't use gamecard db. */ - ncm::InactivateContentMetaDatabase(ncm::StorageId::GameCard); - } - - Result OpenSystemUpdateContentMetaDatabase(ncm::ContentMetaDatabase *out) { - /* TODO: Don't use gamecard db. */ - return ncm::OpenContentMetaDatabase(out, ncm::StorageId::GameCard); - } - - Result GetContentInfoOfContentMeta(ncm::ContentInfo *out, ncm::ContentMetaDatabase &db, const ncm::ContentMetaKey &key) { - s32 ofs = 0; - while (true) { - /* List content infos. */ - s32 count; - ncm::ContentInfo info; - R_TRY(db.ListContentInfo(std::addressof(count), std::addressof(info), 1, key, ofs++)); - - /* No content infos left to list. */ - if (count == 0) { - break; - } - - /* Check if the info is for meta content. */ - if (info.GetType() == ncm::ContentType::Meta) { - *out = info; - return ResultSuccess(); - } - } - - /* Not found. */ - return ncm::ResultContentInfoNotFound(); - } - bool IsExFatDriverSupported(const ncm::ContentMetaInfo &info) { return info.version >= MinimumVersionForExFatDriver && ((info.attributes & ncm::ContentMetaAttribute_IncludesExFatDriver) != 0); } @@ -334,6 +296,25 @@ namespace ams::mitm::sysupdater { return ResultSuccess(); } + const char *GetFirmwareVariationSettingName(settings::system::PlatformRegion region) { + switch (region) { + case settings::system::PlatformRegion_Global: return "firmware_variation"; + case settings::system::PlatformRegion_China: return "t_firmware_variation"; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + ncm::FirmwareVariationId GetFirmwareVariationId() { + /* Get the firmware variation setting name. */ + const char * const setting_name = GetFirmwareVariationSettingName(settings::system::GetPlatformRegion()); + + /* Retrieve the firmware variation id. */ + ncm::FirmwareVariationId id = {}; + settings::fwdbg::GetSettingsItemValue(std::addressof(id.value), sizeof(u8), "ns.systemupdate", setting_name); + + return id; + } + } Result SystemUpdateService::GetUpdateInformation(sf::Out out, const ncm::Path &path) { @@ -422,4 +403,115 @@ namespace ams::mitm::sysupdater { return ResultSuccess(); }; + Result SystemUpdateService::SetupUpdate(sf::MoveHandle transfer_memory, u64 transfer_memory_size, const ncm::Path &path, bool exfat) { + return this->SetupUpdateImpl(transfer_memory.GetValue(), transfer_memory_size, path, exfat, GetFirmwareVariationId()); + } + + Result SystemUpdateService::SetupUpdateWithVariation(sf::MoveHandle transfer_memory, u64 transfer_memory_size, const ncm::Path &path, bool exfat, ncm::FirmwareVariationId firmware_variation_id) { + return this->SetupUpdateImpl(transfer_memory.GetValue(), transfer_memory_size, path, exfat, firmware_variation_id); + } + + Result SystemUpdateService::RequestPrepareUpdate(sf::OutCopyHandle out_event_handle, sf::Out> out_async) { + /* Ensure the update is setup but not prepared. */ + R_UNLESS(this->setup_update, ns::ResultCardUpdateNotSetup()); + R_UNLESS(!this->requested_update, ns::ResultPrepareCardUpdateAlreadyRequested()); + + /* Create the async result. */ + auto async_result = std::make_shared(std::addressof(*this->update_task)); + R_UNLESS(async_result != nullptr, ns::ResultOutOfMaxRunningTask()); + + /* Run the task. */ + R_TRY(async_result->Run()); + + /* We prepared the task! */ + this->requested_update = true; + out_event_handle.SetValue(async_result->GetEvent().GetReadableHandle()); + out_async.SetValue(std::move(async_result)); + + return ResultSuccess(); + } + + Result SystemUpdateService::GetPrepareUpdateProgress(sf::Out out) { + /* Ensure the update is setup. */ + R_UNLESS(this->setup_update, ns::ResultCardUpdateNotSetup()); + + /* Get the progress. */ + auto install_progress = this->update_task->GetProgress(); + out.SetValue({ .current_size = install_progress.installed_size, .total_size = install_progress.total_size }); + return ResultSuccess(); + } + + Result SystemUpdateService::HasPreparedUpdate(sf::Out out) { + /* Ensure the update is setup. */ + R_UNLESS(this->setup_update, ns::ResultCardUpdateNotSetup()); + + out.SetValue(this->update_task->GetProgress().state == ncm::InstallProgressState::Downloaded); + return ResultSuccess(); + } + + Result SystemUpdateService::ApplyPreparedUpdate() { + /* Ensure the update is setup. */ + R_UNLESS(this->setup_update, ns::ResultCardUpdateNotSetup()); + + /* Ensure the update is prepared. */ + R_UNLESS(this->update_task->GetProgress().state == ncm::InstallProgressState::Downloaded, ns::ResultCardUpdateNotPrepared()); + + /* Apply the task. */ + R_TRY(this->apply_manager.ApplyPackageTask(std::addressof(*this->update_task))); + + return ResultSuccess(); + } + + Result SystemUpdateService::SetupUpdateImpl(os::ManagedHandle transfer_memory, u64 transfer_memory_size, const ncm::Path &path, bool exfat, ncm::FirmwareVariationId firmware_variation_id) { + /* Ensure we don't already have an update set up. */ + R_UNLESS(!this->setup_update, ns::ResultCardUpdateAlreadySetup()); + + /* Destroy any existing update tasks. */ + nim::SystemUpdateTaskId id; + auto count = nim::ListSystemUpdateTask(std::addressof(id), 1); + if (count > 0) { + R_TRY(nim::DestroySystemUpdateTask(id)); + } + + /* Initialize the update task. */ + R_TRY(InitializeUpdateTask(transfer_memory, transfer_memory_size, path, exfat, firmware_variation_id)); + + /* The update is now set up. */ + this->setup_update = true; + return ResultSuccess(); + } + + Result SystemUpdateService::InitializeUpdateTask(os::ManagedHandle &transfer_memory_handle, u64 transfer_memory_size, const ncm::Path &path, bool exfat, ncm::FirmwareVariationId firmware_variation_id) { + /* Map the transfer memory. */ + const size_t tmem_buffer_size = static_cast(transfer_memory_size); + this->update_transfer_memory.emplace(tmem_buffer_size, transfer_memory_handle.Get(), true); + + void *tmem_buffer; + R_TRY(this->update_transfer_memory->Map(std::addressof(tmem_buffer), os::MemoryPermission_None)); + auto tmem_guard = SCOPE_GUARD { + this->update_transfer_memory->Unmap(); + this->update_transfer_memory = std::nullopt; + }; + + /* Now that the memory is mapped, the input handle is managed and can be released. */ + transfer_memory_handle.Detach(); + + /* Adjust the package root. */ + ncm::Path package_root; + R_TRY(FormatUserPackagePath(std::addressof(package_root), path)); + + /* Ensure that we can create an update context. */ + R_TRY(fs::EnsureDirectoryRecursively("@Sdcard:/atmosphere/update/")); + const char *context_path = "@Sdcard:/atmosphere/update/cup.ctx"; + + /* Create and initialize the update task. */ + this->update_task.emplace(); + R_TRY(this->update_task->Initialize(package_root.str, context_path, tmem_buffer, tmem_buffer_size, exfat, firmware_variation_id)); + + /* We successfully setup the update. */ + tmem_guard.Cancel(); + + return ResultSuccess(); + } + } diff --git a/stratosphere/ams_mitm/source/sysupdater/sysupdater_service.hpp b/stratosphere/ams_mitm/source/sysupdater/sysupdater_service.hpp index 37b6f2ce8..d8a748202 100644 --- a/stratosphere/ams_mitm/source/sysupdater/sysupdater_service.hpp +++ b/stratosphere/ams_mitm/source/sysupdater/sysupdater_service.hpp @@ -15,6 +15,8 @@ */ #pragma once #include +#include "sysupdater_i_async_result.hpp" +#include "sysupdater_apply_manager.hpp" namespace ams::mitm::sysupdater { @@ -32,19 +34,53 @@ namespace ams::mitm::sysupdater { ncm::ContentId invalid_content_id; }; + struct SystemUpdateProgress { + s64 current_size; + s64 total_size; + }; + class SystemUpdateService final : public sf::IServiceObject { private: enum class CommandId { - GetUpdateInformation = 0, - ValidateUpdate = 1, + GetUpdateInformation = 0, + ValidateUpdate = 1, + SetupUpdate = 2, + SetupUpdateWithVariation = 3, + RequestPrepareUpdate = 4, + GetPrepareUpdateProgress = 5, + HasPreparedUpdate = 6, + ApplyPreparedUpdate = 7, }; + private: + SystemUpdateApplyManager apply_manager; + std::optional update_task; + std::optional update_transfer_memory; + bool setup_update; + bool requested_update; + public: + constexpr SystemUpdateService() : apply_manager(), update_task(), update_transfer_memory(), setup_update(false), requested_update(false) { /* ... */ } + private: + Result SetupUpdateImpl(os::ManagedHandle transfer_memory, u64 transfer_memory_size, const ncm::Path &path, bool exfat, ncm::FirmwareVariationId firmware_variation_id); + Result InitializeUpdateTask(os::ManagedHandle &transfer_memory, u64 transfer_memory_size, const ncm::Path &path, bool exfat, ncm::FirmwareVariationId firmware_variation_id); private: Result GetUpdateInformation(sf::Out out, const ncm::Path &path); Result ValidateUpdate(sf::Out out_validate_result, sf::Out out_validate_info, const ncm::Path &path); + Result SetupUpdate(sf::MoveHandle transfer_memory, u64 transfer_memory_size, const ncm::Path &path, bool exfat); + Result SetupUpdateWithVariation(sf::MoveHandle transfer_memory, u64 transfer_memory_size, const ncm::Path &path, bool exfat, ncm::FirmwareVariationId firmware_variation_id); + Result RequestPrepareUpdate(sf::OutCopyHandle out_event_handle, sf::Out> out_async); + Result GetPrepareUpdateProgress(sf::Out out); + Result HasPreparedUpdate(sf::Out out); + Result ApplyPreparedUpdate(); public: DEFINE_SERVICE_DISPATCH_TABLE { MAKE_SERVICE_COMMAND_META(GetUpdateInformation), MAKE_SERVICE_COMMAND_META(ValidateUpdate), + MAKE_SERVICE_COMMAND_META(SetupUpdate), + MAKE_SERVICE_COMMAND_META(SetupUpdateWithVariation), + MAKE_SERVICE_COMMAND_META(RequestPrepareUpdate), + MAKE_SERVICE_COMMAND_META(GetPrepareUpdateProgress), + MAKE_SERVICE_COMMAND_META(HasPreparedUpdate), + MAKE_SERVICE_COMMAND_META(ApplyPreparedUpdate), }; };