diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index 8801f90b..08fd883f 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -125,10 +125,15 @@ add_library(skyline SHARED ${source_DIR}/skyline/services/hid/IHidServer.cpp ${source_DIR}/skyline/services/hid/IAppletResource.cpp ${source_DIR}/skyline/services/hid/IActiveVibrationDeviceList.cpp + ${source_DIR}/skyline/services/timesrv/common.cpp + ${source_DIR}/skyline/services/timesrv/core.cpp + ${source_DIR}/skyline/services/timesrv/time_shared_memory.cpp + ${source_DIR}/skyline/services/timesrv/time_manager_server.cpp ${source_DIR}/skyline/services/timesrv/IStaticService.cpp ${source_DIR}/skyline/services/timesrv/ISystemClock.cpp ${source_DIR}/skyline/services/timesrv/ISteadyClock.cpp ${source_DIR}/skyline/services/timesrv/ITimeZoneService.cpp + ${source_DIR}/skyline/services/glue/IStaticService.cpp ${source_DIR}/skyline/services/fssrv/IFileSystemProxy.cpp ${source_DIR}/skyline/services/fssrv/IFileSystem.cpp ${source_DIR}/skyline/services/fssrv/IFile.cpp diff --git a/app/src/main/cpp/skyline/common.h b/app/src/main/cpp/skyline/common.h index 9328e4a1..100bd25f 100644 --- a/app/src/main/cpp/skyline/common.h +++ b/app/src/main/cpp/skyline/common.h @@ -126,6 +126,7 @@ namespace skyline { constexpr u16 DockedResolutionH{1080}; //!< The height component of the docked resolution // Time constexpr u64 NsInSecond{1000000000}; //!< The amount of nanoseconds in a second + constexpr u64 NsInDay{86400000000000UL}; //!< The amount of nanoseconds in a day } namespace util { diff --git a/app/src/main/cpp/skyline/services/glue/IStaticService.cpp b/app/src/main/cpp/skyline/services/glue/IStaticService.cpp new file mode 100644 index 00000000..68e8fbf0 --- /dev/null +++ b/app/src/main/cpp/skyline/services/glue/IStaticService.cpp @@ -0,0 +1,95 @@ +// SPDX-License-Identifier: MPL-2.0 +// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/) + +#include +#include +#include +#include "IStaticService.h" + +namespace skyline::service::glue { + IStaticService::IStaticService(const DeviceState &state, ServiceManager &manager, std::shared_ptr core, timesrv::StaticServicePermissions permissions) : BaseService(state, manager), core(std::move(core)), permissions(permissions) {} + + Result IStaticService::GetStandardUserSystemClock(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { + return core->GetStandardUserSystemClock(session, request, response); + } + + Result IStaticService::GetStandardNetworkSystemClock(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { + return core->GetStandardNetworkSystemClock(session, request, response); + } + + Result IStaticService::GetStandardSteadyClock(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { + return core->GetStandardSteadyClock(session, request, response); + } + Result IStaticService::GetTimeZoneService(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { + // STUFFF + return core->GetTimeZoneService(session, request, response); + } + + Result IStaticService::GetStandardLocalSystemClock(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { + return core->GetStandardLocalSystemClock(session, request, response); + } + + Result IStaticService::GetEphemeralNetworkSystemClock(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { + return core->GetEphemeralNetworkSystemClock(session, request, response); + } + + Result IStaticService::GetSharedMemoryNativeHandle(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { + return core->GetSharedMemoryNativeHandle(session, request, response); + } + + Result IStaticService::SetStandardSteadyClockInternalOffset(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { + if (!permissions.writeSteadyClock) + return timesrv::result::PermissionDenied; + + // HOS would write the offset between the RTC and the epoch here, however as we emulate an RTC with no offset we can ignore this + return {}; + } + + Result IStaticService::GetStandardSteadyClockRtcValue(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { + // std::time is effectively our RTC + response.Push(std::time(nullptr)); + return {}; + } + + Result IStaticService::IsStandardUserSystemClockAutomaticCorrectionEnabled(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { + return core->IsStandardUserSystemClockAutomaticCorrectionEnabled(session, request, response); + } + + Result IStaticService::SetStandardUserSystemClockAutomaticCorrectionEnabled(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { + return core->SetStandardUserSystemClockAutomaticCorrectionEnabled(session, request, response); + } + + Result IStaticService::GetStandardUserSystemClockInitialYear(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { + constexpr i32 standardUserSystemClockInitialYear{2019}; //!< https://switchbrew.org/wiki/System_Settings#time + response.Push(standardUserSystemClockInitialYear); + return {}; + } + + Result IStaticService::IsStandardNetworkSystemClockAccuracySufficient(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { + return core->IsStandardNetworkSystemClockAccuracySufficient(session, request, response); + } + + Result IStaticService::GetStandardUserSystemClockAutomaticCorrectionUpdatedTime(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { + return core->GetStandardUserSystemClockAutomaticCorrectionUpdatedTime(session, request, response); + } + + Result IStaticService::CalculateMonotonicSystemClockBaseTimePoint(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { + return core->CalculateMonotonicSystemClockBaseTimePoint(session, request, response); + } + + Result IStaticService::GetClockSnapshot(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { + return core->GetClockSnapshot(session, request, response); + } + + Result IStaticService::GetClockSnapshotFromSystemClockContext(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { + return core->GetClockSnapshotFromSystemClockContext(session, request, response); + } + + Result IStaticService::CalculateStandardUserSystemClockDifferenceByUser(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { + return core->CalculateStandardUserSystemClockDifferenceByUser(session, request, response); + } + + Result IStaticService::CalculateSpanBetween(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { + return core->CalculateSpanBetween(session, request, response); + } +} diff --git a/app/src/main/cpp/skyline/services/glue/IStaticService.h b/app/src/main/cpp/skyline/services/glue/IStaticService.h new file mode 100644 index 00000000..ef0cd4d5 --- /dev/null +++ b/app/src/main/cpp/skyline/services/glue/IStaticService.h @@ -0,0 +1,82 @@ +// SPDX-License-Identifier: MPL-2.0 +// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/) + +#pragma once + +#include +#include + +namespace skyline::service::glue { + /** + * @brief IStaticService (covers time:a, time:r, time:u) is glue's version of pcv::IStaticService, it adds some more functions and provides the user variant + * @url https://switchbrew.org/wiki/PSC_services#time:su.2C_time:s + */ + class IStaticService : public BaseService { + private: + std::shared_ptr core; + timesrv::StaticServicePermissions permissions; + + public: + IStaticService(const DeviceState &state, ServiceManager &manager, std::shared_ptr core, timesrv::StaticServicePermissions permissions); + + Result GetStandardUserSystemClock(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response); + + Result GetStandardNetworkSystemClock(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response); + + Result GetStandardSteadyClock(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response); + + Result GetTimeZoneService(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response); + + Result GetStandardLocalSystemClock(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response); + + Result GetEphemeralNetworkSystemClock(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response); + + Result GetSharedMemoryNativeHandle(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response); + + Result SetStandardSteadyClockInternalOffset(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response); + + Result GetStandardSteadyClockRtcValue(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response); + + Result IsStandardUserSystemClockAutomaticCorrectionEnabled(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response); + + Result SetStandardUserSystemClockAutomaticCorrectionEnabled(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response); + + Result GetStandardUserSystemClockInitialYear(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response); + + Result IsStandardNetworkSystemClockAccuracySufficient(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response); + + Result GetStandardUserSystemClockAutomaticCorrectionUpdatedTime(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response); + + Result CalculateMonotonicSystemClockBaseTimePoint(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response); + + Result GetClockSnapshot(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response); + + Result GetClockSnapshotFromSystemClockContext(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response); + + Result CalculateStandardUserSystemClockDifferenceByUser(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response); + + Result CalculateSpanBetween(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response); + + SERVICE_DECL( + SFUNC(0x0, IStaticService, GetStandardUserSystemClock), + SFUNC(0x1, IStaticService, GetStandardNetworkSystemClock), + SFUNC(0x2, IStaticService, GetStandardSteadyClock), + SFUNC(0x3, IStaticService, GetTimeZoneService), + SFUNC(0x4, IStaticService, GetStandardLocalSystemClock), + SFUNC(0x5, IStaticService, GetEphemeralNetworkSystemClock), + SFUNC(0x14, IStaticService, GetSharedMemoryNativeHandle), + SFUNC(0x32, IStaticService, SetStandardSteadyClockInternalOffset), + SFUNC(0x33, IStaticService, GetStandardSteadyClockRtcValue), + SFUNC(0x64, IStaticService, IsStandardUserSystemClockAutomaticCorrectionEnabled), + SFUNC(0x65, IStaticService, SetStandardUserSystemClockAutomaticCorrectionEnabled), + SFUNC(0x66, IStaticService, GetStandardUserSystemClockInitialYear), + SFUNC(0xC8, IStaticService, IsStandardNetworkSystemClockAccuracySufficient), + SFUNC(0xC9, IStaticService, GetStandardUserSystemClockAutomaticCorrectionUpdatedTime), + SFUNC(0x12C, IStaticService, CalculateMonotonicSystemClockBaseTimePoint), + SFUNC(0x190, IStaticService, GetClockSnapshot), + SFUNC(0x191, IStaticService, GetClockSnapshotFromSystemClockContext), + SFUNC(0x1F4, IStaticService, CalculateStandardUserSystemClockDifferenceByUser), + SFUNC(0x1F5, IStaticService, CalculateSpanBetween), + ) + }; +} \ No newline at end of file diff --git a/app/src/main/cpp/skyline/services/serviceman.cpp b/app/src/main/cpp/skyline/services/serviceman.cpp index 8fca286d..219c5a01 100644 --- a/app/src/main/cpp/skyline/services/serviceman.cpp +++ b/app/src/main/cpp/skyline/services/serviceman.cpp @@ -13,6 +13,8 @@ #include "fatalsrv/IService.h" #include "hid/IHidServer.h" #include "timesrv/IStaticService.h" +#include "glue/IStaticService.h" +#include "services/timesrv/core.h" #include "fssrv/IFileSystemProxy.h" #include "services/nvdrv/INvDrvServices.h" #include "visrv/IManagerRootService.h" @@ -29,15 +31,21 @@ #include "prepo/IPrepoService.h" #include "serviceman.h" -#define SERVICE_CASE(class, name) \ +#define SERVICE_CASE(class, name, ...) \ case util::MakeMagic(name): { \ - std::shared_ptr serviceObject = std::make_shared(state, *this); \ + std::shared_ptr serviceObject = std::make_shared(state, *this __VA_OPT__(,) __VA_ARGS__); \ serviceMap[util::MakeMagic(name)] = serviceObject; \ return serviceObject; \ } namespace skyline::service { - ServiceManager::ServiceManager(const DeviceState &state) : state(state), smUserInterface(std::make_shared(state, *this)) {} + struct GlobalServiceState { + timesrv::core::TimeServiceObject timesrv; + + explicit GlobalServiceState(const DeviceState &state) : timesrv(state) {} + }; + + ServiceManager::ServiceManager(const DeviceState &state) : state(state), smUserInterface(std::make_shared(state, *this)), globalServiceState(std::make_shared(state)) {} std::shared_ptr ServiceManager::CreateService(ServiceName name) { auto serviceIter{serviceMap.find(name)}; @@ -54,9 +62,11 @@ namespace skyline::service { SERVICE_CASE(audio::IAudioOutManager, "audout:u") SERVICE_CASE(audio::IAudioRendererManager, "audren:u") SERVICE_CASE(hid::IHidServer, "hid") - SERVICE_CASE(timesrv::IStaticService, "time:s") - SERVICE_CASE(timesrv::IStaticService, "time:a") - SERVICE_CASE(timesrv::IStaticService, "time:u") + SERVICE_CASE(timesrv::IStaticService, "time:s", globalServiceState->timesrv, timesrv::constant::StaticServiceSystemPermissions) // Both of these would be registered after TimeServiceManager::Setup normally but we call that in the GlobalServiceState constructor so can just list them here directly + SERVICE_CASE(timesrv::IStaticService, "time:su", globalServiceState->timesrv, timesrv::constant::StaticServiceSystemUpdatePermissions) + SERVICE_CASE(glue::IStaticService, "time:a", globalServiceState->timesrv.managerServer.GetAdminStaticService(state, *this), timesrv::constant::StaticServiceAdminPermissions) + SERVICE_CASE(glue::IStaticService, "time:r", globalServiceState->timesrv.managerServer.GetRepairStaticService(state, *this), timesrv::constant::StaticServiceRepairPermissions) + SERVICE_CASE(glue::IStaticService, "time:u", globalServiceState->timesrv.managerServer.GetUserStaticService(state, *this), timesrv::constant::StaticServiceUserPermissions) SERVICE_CASE(fssrv::IFileSystemProxy, "fsp-srv") SERVICE_CASE(nvdrv::INvDrvServices, "nvdrv") SERVICE_CASE(nvdrv::INvDrvServices, "nvdrv:a") diff --git a/app/src/main/cpp/skyline/services/serviceman.h b/app/src/main/cpp/skyline/services/serviceman.h index f983434d..f6e74c54 100644 --- a/app/src/main/cpp/skyline/services/serviceman.h +++ b/app/src/main/cpp/skyline/services/serviceman.h @@ -7,6 +7,11 @@ #include "base_service.h" namespace skyline::service { + /** + * @brief Holds global service state for service data that persists across sessions + */ + struct GlobalServiceState; + /** * @brief The ServiceManager class manages passing IPC requests to the right Service and running event loops of Services */ @@ -25,6 +30,7 @@ namespace skyline::service { public: std::shared_ptr smUserInterface; //!< Used by applications to open connections to services + std::shared_ptr globalServiceState; ServiceManager(const DeviceState &state); diff --git a/app/src/main/cpp/skyline/services/timesrv/IStaticService.cpp b/app/src/main/cpp/skyline/services/timesrv/IStaticService.cpp index a77b4ccb..1a830ca5 100644 --- a/app/src/main/cpp/skyline/services/timesrv/IStaticService.cpp +++ b/app/src/main/cpp/skyline/services/timesrv/IStaticService.cpp @@ -1,26 +1,29 @@ // SPDX-License-Identifier: MPL-2.0 // Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/) +#include +#include "results.h" +#include "core.h" #include "ISteadyClock.h" -#include "ISystemClock.h" #include "ITimeZoneService.h" +#include "ISystemClock.h" #include "IStaticService.h" namespace skyline::service::timesrv { - IStaticService::IStaticService(const DeviceState &state, ServiceManager &manager) : BaseService(state, manager) {} + IStaticService::IStaticService(const DeviceState &state, ServiceManager &manager, core::TimeServiceObject &core, StaticServicePermissions permissions) : BaseService(state, manager), core(core), permissions(permissions) {} Result IStaticService::GetStandardUserSystemClock(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { - manager.RegisterService(std::make_shared(SystemClockType::User, state, manager), session, response); + manager.RegisterService(std::make_shared(state, manager, core.userSystemClock, permissions.writeUserSystemClock, permissions.ignoreUninitializedChecks), session, response); return {}; } Result IStaticService::GetStandardNetworkSystemClock(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { - manager.RegisterService(std::make_shared(SystemClockType::Network, state, manager), session, response); + manager.RegisterService(std::make_shared(state, manager, core.networkSystemClock, permissions.writeNetworkSystemClock, permissions.ignoreUninitializedChecks), session, response); return {}; } Result IStaticService::GetStandardSteadyClock(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { - manager.RegisterService(std::make_shared(state, manager), session, response); + manager.RegisterService(std::make_shared(state, manager, core.standardSteadyClock, permissions.writeSteadyClock, permissions.ignoreUninitializedChecks), session, response); return {}; } @@ -30,7 +33,187 @@ namespace skyline::service::timesrv { } Result IStaticService::GetStandardLocalSystemClock(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { - manager.RegisterService(std::make_shared(SystemClockType::Local, state, manager), session, response); + manager.RegisterService(std::make_shared(state, manager, core.localSystemClock, permissions.writeLocalSystemClock, permissions.ignoreUninitializedChecks), session, response); return {}; } + + Result IStaticService::GetEphemeralNetworkSystemClock(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { + manager.RegisterService(std::make_shared(state, manager, core.networkSystemClock, permissions.writeNetworkSystemClock, permissions.ignoreUninitializedChecks), session, response); + return {}; + } + + Result IStaticService::GetSharedMemoryNativeHandle(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { + auto sharedMemory{core.timeSharedMemory.GetSharedMemory()}; + auto handle{state.process->InsertItem(sharedMemory)}; + response.copyHandles.push_back(handle); + return {}; + } + + Result IStaticService::SetStandardSteadyClockInternalOffset(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { + if (permissions.writeSteadyClock) + return result::Unimplemented; + else + return result::PermissionDenied; + } + + Result IStaticService::GetStandardSteadyClockRtcValue(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { + return result::Unimplemented; + } + + Result IStaticService::IsStandardUserSystemClockAutomaticCorrectionEnabled(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { + if (!core.userSystemClock.IsClockInitialized()) + return result::ClockUninitialized; + + response.Push(core.userSystemClock.IsAutomaticCorrectionEnabled()); + return {}; + } + + Result IStaticService::SetStandardUserSystemClockAutomaticCorrectionEnabled(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { + if (!core.userSystemClock.IsClockInitialized() || !core.standardSteadyClock.IsClockInitialized()) + return result::ClockUninitialized; + + if (!permissions.writeUserSystemClock) + return result::PermissionDenied; + + return core.userSystemClock.UpdateAutomaticCorrectionState(request.Pop()); + } + + Result IStaticService::GetStandardUserSystemClockInitialYear(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { + return result::Unimplemented; + } + + Result IStaticService::IsStandardNetworkSystemClockAccuracySufficient(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { + response.Push(core.networkSystemClock.IsAccuracySufficient()); + return {}; + } + + Result IStaticService::GetStandardUserSystemClockAutomaticCorrectionUpdatedTime(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { + if (!core.userSystemClock.IsClockInitialized()) + return result::ClockUninitialized; + + response.Push(core.userSystemClock.GetAutomaticCorrectionUpdatedTime()); + return {}; + } + + Result IStaticService::CalculateMonotonicSystemClockBaseTimePoint(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { + if (!core.standardSteadyClock.IsClockInitialized()) + return result::ClockUninitialized; + + auto timePoint{core.standardSteadyClock.GetCurrentTimePoint()}; + if (!timePoint) + return timePoint; + + auto clockContext{request.Pop()}; + if (clockContext.timestamp.clockSourceId != timePoint->clockSourceId) + return result::ClockSourceIdMismatch; + + i64 baseTimePoint{timePoint->timePoint + clockContext.offset - TimeSpanType::FromNanoseconds(util::GetTimeNs()).Seconds()}; + response.Push(baseTimePoint); + return {}; + } + + ResultValue IStaticService::GetClockSnapshotFromSystemClockContextImpl(const SystemClockContext &userContext, const SystemClockContext &networkContext, u8 unk) { + ClockSnapshot out{}; + + out.userContext = userContext; + out.networkContext = networkContext; + auto timePoint{core.standardSteadyClock.GetCurrentTimePoint()}; + if (!timePoint) + return timePoint; + + out.steadyClockTimePoint = *timePoint; + out.automaticCorrectionEnabled = core.userSystemClock.IsAutomaticCorrectionEnabled(); + // TODO GetDeviceLocationName + auto userPosixTime{ClockSnapshot::GetCurrentTime(out.steadyClockTimePoint, out.userContext)}; + if (!userPosixTime) + return userPosixTime; + + out.userPosixTime = *userPosixTime; + + // TODO CalendarTimeWithMyRule + + // Not necessarily a fatal error if this fails + auto networkPosixTime{ClockSnapshot::GetCurrentTime(out.steadyClockTimePoint, out.networkContext)}; + if (networkPosixTime) + out.networkPosixTime = *networkPosixTime; + else + out.networkPosixTime = 0; + + // TODO CalendarTimeWithMyRule + + out._unk_ = unk; + out.version = 0; + + return out; + } + + Result IStaticService::GetClockSnapshot(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { + auto unk{request.Pop()}; + + auto userContext{core.userSystemClock.GetClockContext()}; + if (!userContext) + return userContext; + + auto networkContext{core.networkSystemClock.GetClockContext()}; + if (!networkContext) + return networkContext; + + auto snapshot{GetClockSnapshotFromSystemClockContextImpl(*userContext, *networkContext, unk)}; + if (!snapshot) + return snapshot; + + request.outputBuf.at(0).as() = *snapshot; + + return {}; + } + + Result IStaticService::GetClockSnapshotFromSystemClockContext(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { + auto unk{request.Pop()}; + request.Skip>(); + auto userContext{request.Pop()}; + auto networkContext{request.Pop()}; + + auto snapshot{GetClockSnapshotFromSystemClockContextImpl(userContext, networkContext, unk)}; + if (!snapshot) + return snapshot; + + request.outputBuf.at(0).as() = *snapshot; + return {}; + } + + Result IStaticService::CalculateStandardUserSystemClockDifferenceByUser(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { + auto snapshotA{request.inputBuf.at(0).as()}; + auto snapshotB{request.inputBuf.at(1).as()}; + + TimeSpanType difference{TimeSpanType::FromSeconds(snapshotB.userContext.offset - snapshotA.userContext.offset)}; + + if (snapshotA.userContext.timestamp.clockSourceId != snapshotB.userContext.timestamp.clockSourceId) { + difference = 0; + } else if (snapshotA.automaticCorrectionEnabled && snapshotB.automaticCorrectionEnabled) { + if (snapshotA.networkContext.timestamp.clockSourceId != snapshotA.steadyClockTimePoint.clockSourceId || snapshotB.networkContext.timestamp.clockSourceId != snapshotB.steadyClockTimePoint.clockSourceId) + difference = 0; + } + + response.Push(difference.Nanoseconds()); + return {}; + } + + Result IStaticService::CalculateSpanBetween(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { + auto snapshotA{request.inputBuf.at(0).as()}; + auto snapshotB{request.inputBuf.at(1).as()}; + + auto difference{GetSpanBetween(snapshotA.steadyClockTimePoint, snapshotB.steadyClockTimePoint)}; + if (difference) { + response.Push(TimeSpanType::FromSeconds(*difference).Nanoseconds()); + return difference; + } + + // If GetSpanBetween fails then fall back to comparing POSIX timepoints + if (snapshotA.networkPosixTime && snapshotB.networkPosixTime) { + response.Push(TimeSpanType::FromSeconds(snapshotB.networkPosixTime - snapshotA.networkPosixTime).Nanoseconds()); + return {}; + } else { + return result::InvalidComparison; + } + } } diff --git a/app/src/main/cpp/skyline/services/timesrv/IStaticService.h b/app/src/main/cpp/skyline/services/timesrv/IStaticService.h index e9422e10..eaf22b82 100644 --- a/app/src/main/cpp/skyline/services/timesrv/IStaticService.h +++ b/app/src/main/cpp/skyline/services/timesrv/IStaticService.h @@ -4,47 +4,139 @@ #pragma once #include +#include namespace skyline::service::timesrv { /** - * @brief IStaticService (covers time:u, time:a and time:s) is responsible for providing handles to various clock services + * @brief Holds permissions for an instance of IStaticService + */ + struct StaticServicePermissions { + bool writeLocalSystemClock; + bool writeUserSystemClock; + bool writeNetworkSystemClock; + bool writeTimezone; + bool writeSteadyClock; + bool ignoreUninitializedChecks; + }; + + namespace constant { + constexpr StaticServicePermissions StaticServiceUserPermissions{}; + constexpr StaticServicePermissions StaticServiceAdminPermissions{ + .writeLocalSystemClock = true, + .writeUserSystemClock = true, + .writeTimezone = true, + }; + constexpr StaticServicePermissions StaticServiceRepairPermissions{ + .writeSteadyClock = true, + }; + constexpr StaticServicePermissions StaticServiceManagerPermissions{ + .writeLocalSystemClock = true, + .writeUserSystemClock = true, + .writeNetworkSystemClock = true, + .writeTimezone = true, + .writeSteadyClock = true, + .ignoreUninitializedChecks = false, + }; + constexpr StaticServicePermissions StaticServiceSystemPermissions{ + .writeNetworkSystemClock = true, + }; + constexpr StaticServicePermissions StaticServiceSystemUpdatePermissions{ + .ignoreUninitializedChecks = true, + }; + + } + + namespace core { + struct TimeServiceObject; + } + + /** + * @brief IStaticService (covers time:su, time:s) is responsible for providing the system access to various clocks * @url https://switchbrew.org/wiki/PSC_services#time:su.2C_time:s */ class IStaticService : public BaseService { - public: - IStaticService(const DeviceState &state, ServiceManager &manager); + private: + core::TimeServiceObject &core; + StaticServicePermissions permissions; //!< What this instance is allowed to do + + ResultValue GetClockSnapshotFromSystemClockContextImpl(const SystemClockContext &userContext, const SystemClockContext &networkContext, u8 unk); + + public: + IStaticService(const DeviceState &state, ServiceManager &manager, core::TimeServiceObject &core, StaticServicePermissions permissions); - /** - * @brief Returns a handle to a ISystemClock for user time - */ Result GetStandardUserSystemClock(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response); - /** - * @brief Returns a handle to a ISystemClock for network time - */ Result GetStandardNetworkSystemClock(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response); - /** - * @brief Returns a handle to a ISteadyClock for a steady timepoint - */ Result GetStandardSteadyClock(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response); - /** - * @brief Returns a handle to a ITimeZoneService for reading time zone information - */ Result GetTimeZoneService(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response); - /** - * @brief Returns a handle to a ISystemClock for local time - */ Result GetStandardLocalSystemClock(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response); + Result GetEphemeralNetworkSystemClock(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response); + + Result GetSharedMemoryNativeHandle(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response); + + Result SetStandardSteadyClockInternalOffset(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response); + + Result GetStandardSteadyClockRtcValue(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response); + + Result IsStandardUserSystemClockAutomaticCorrectionEnabled(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response); + + Result SetStandardUserSystemClockAutomaticCorrectionEnabled(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response); + + Result GetStandardUserSystemClockInitialYear(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response); + + Result IsStandardNetworkSystemClockAccuracySufficient(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response); + + Result GetStandardUserSystemClockAutomaticCorrectionUpdatedTime(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response); + + /** + * @brief Generates an appropriate base timepoint from the supplied context + */ + Result CalculateMonotonicSystemClockBaseTimePoint(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response); + + /** + * @brief Generates a snapshot of all clocks in the system using the current contexts + */ + Result GetClockSnapshot(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response); + + /** + * @brief Generates a shapshot of all clocks using the supplied contexts + */ + Result GetClockSnapshotFromSystemClockContext(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response); + + /** + * @brief Takes two snapshots and compares the user time between the them + */ + Result CalculateStandardUserSystemClockDifferenceByUser(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response); + + /** + * @brief Calculates the timespan between the two given clock snapshots + */ + Result CalculateSpanBetween(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response); + SERVICE_DECL( SFUNC(0x0, IStaticService, GetStandardUserSystemClock), SFUNC(0x1, IStaticService, GetStandardNetworkSystemClock), SFUNC(0x2, IStaticService, GetStandardSteadyClock), SFUNC(0x3, IStaticService, GetTimeZoneService), - SFUNC(0x4, IStaticService, GetStandardLocalSystemClock) + SFUNC(0x4, IStaticService, GetStandardLocalSystemClock), + SFUNC(0x5, IStaticService, GetEphemeralNetworkSystemClock), + SFUNC(0x14, IStaticService, GetSharedMemoryNativeHandle), + SFUNC(0x32, IStaticService, SetStandardSteadyClockInternalOffset), + SFUNC(0x33, IStaticService, GetStandardSteadyClockRtcValue), + SFUNC(0x64, IStaticService, IsStandardUserSystemClockAutomaticCorrectionEnabled), + SFUNC(0x65, IStaticService, SetStandardUserSystemClockAutomaticCorrectionEnabled), + SFUNC(0x66, IStaticService, GetStandardUserSystemClockInitialYear), + SFUNC(0xC8, IStaticService, IsStandardNetworkSystemClockAccuracySufficient), + SFUNC(0xC9, IStaticService, GetStandardUserSystemClockAutomaticCorrectionUpdatedTime), + SFUNC(0x12C, IStaticService, CalculateMonotonicSystemClockBaseTimePoint), + SFUNC(0x190, IStaticService, GetClockSnapshot), + SFUNC(0x191, IStaticService, GetClockSnapshotFromSystemClockContext), + SFUNC(0x1F4, IStaticService, CalculateStandardUserSystemClockDifferenceByUser), + SFUNC(0x1F5, IStaticService, CalculateSpanBetween), ) }; -} +} \ No newline at end of file diff --git a/app/src/main/cpp/skyline/services/timesrv/ISteadyClock.cpp b/app/src/main/cpp/skyline/services/timesrv/ISteadyClock.cpp index c5c0528e..242e6103 100644 --- a/app/src/main/cpp/skyline/services/timesrv/ISteadyClock.cpp +++ b/app/src/main/cpp/skyline/services/timesrv/ISteadyClock.cpp @@ -1,13 +1,73 @@ // SPDX-License-Identifier: MPL-2.0 // Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/) +#include "core.h" #include "ISteadyClock.h" namespace skyline::service::timesrv { - ISteadyClock::ISteadyClock(const DeviceState &state, ServiceManager &manager) : BaseService(state, manager) {} + ISteadyClock::ISteadyClock(const DeviceState &state, ServiceManager &manager, core::SteadyClockCore &core, bool writeable, bool ignoreUninitializedChecks) : BaseService(state, manager), core(core), writeable(writeable), ignoreUninitializedChecks(ignoreUninitializedChecks) {} Result ISteadyClock::GetCurrentTimePoint(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { - response.Push(SteadyClockTimePoint{static_cast(std::time(nullptr))}); + // When a clock is uninitialized it still ticks however the offsets aren't configured + if (!ignoreUninitializedChecks && !core.IsClockInitialized()) + return result::ClockUninitialized; + + auto timePoint{core.GetCurrentTimePoint()}; + if (timePoint) + response.Push(*timePoint); + + return timePoint; + } + + Result ISteadyClock::GetTestOffset(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { + if (!ignoreUninitializedChecks && !core.IsClockInitialized()) + return result::ClockUninitialized; + + response.Push(core.GetTestOffset()); + return {}; + } + + Result ISteadyClock::SetTestOffset(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { + if (!ignoreUninitializedChecks && !core.IsClockInitialized()) + return result::ClockUninitialized; + + auto testOffset{request.Pop()}; + core.SetTestOffset(testOffset); + return {}; + } + + Result ISteadyClock::GetRtcValue(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { + if (!ignoreUninitializedChecks && !core.IsClockInitialized()) + return result::ClockUninitialized; + + auto rtcValue{core.GetRtcValue()}; + if (rtcValue) + response.Push(*rtcValue); + + return rtcValue; + } + + Result ISteadyClock::IsRtcResetDetected(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { + if (!ignoreUninitializedChecks && !core.IsClockInitialized()) + return result::ClockUninitialized; + + response.Push(core.IsRtcResetDetected()); + return {}; + } + + Result ISteadyClock::GetSetupResultValue(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { + if (!ignoreUninitializedChecks && !core.IsClockInitialized()) + return result::ClockUninitialized; + + response.Push(core.GetSetupResult()); + return {}; + } + + Result ISteadyClock::GetInternalOffset(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { + if (!ignoreUninitializedChecks && !core.IsClockInitialized()) + return result::ClockUninitialized; + + response.Push(core.GetInternalOffset()); return {}; } } diff --git a/app/src/main/cpp/skyline/services/timesrv/ISteadyClock.h b/app/src/main/cpp/skyline/services/timesrv/ISteadyClock.h index cb20757f..015af36b 100644 --- a/app/src/main/cpp/skyline/services/timesrv/ISteadyClock.h +++ b/app/src/main/cpp/skyline/services/timesrv/ISteadyClock.h @@ -6,29 +6,45 @@ #include namespace skyline::service::timesrv { - /** - * @url https://switchbrew.org/wiki/PSC_services#SteadyClockTimePoint - */ - struct SteadyClockTimePoint { - u64 timepoint; //!< The point in time of this timepoint - u8 id[0x10]; //!< The ID of the source clock - }; + namespace core { + class SteadyClockCore; + } /** - * @brief ISteadyClock is used to retrieve a steady time that increments uniformly for the lifetime on an application + * @brief ISteadyClock is used to interface with timesrv steady clocks * @url https://switchbrew.org/wiki/PSC_services#ISteadyClock */ class ISteadyClock : public BaseService { - public: - ISteadyClock(const DeviceState &state, ServiceManager &manager); + private: + core::SteadyClockCore &core; + bool writeable; + bool ignoreUninitializedChecks; + + public: + ISteadyClock(const DeviceState &state, ServiceManager &manager, core::SteadyClockCore &core, bool writeable, bool ignoreUninitializedChecks); - /** - * @brief Returns the current value of the steady clock - */ Result GetCurrentTimePoint(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response); + Result GetTestOffset(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response); + + Result SetTestOffset(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response); + + Result GetRtcValue(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response); + + Result IsRtcResetDetected(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response); + + Result GetSetupResultValue(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response); + + Result GetInternalOffset(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response); + SERVICE_DECL( - SFUNC(0x0, ISteadyClock, GetCurrentTimePoint) + SFUNC(0x0, ISteadyClock, GetCurrentTimePoint), + SFUNC(0x2, ISteadyClock, GetTestOffset), + SFUNC(0x3, ISteadyClock, SetTestOffset), + SFUNC(0x64, ISteadyClock, GetRtcValue), + SFUNC(0x65, ISteadyClock, IsRtcResetDetected), + SFUNC(0x66, ISteadyClock, GetSetupResultValue), + SFUNC(0xC8, ISteadyClock, GetInternalOffset), ) }; } diff --git a/app/src/main/cpp/skyline/services/timesrv/ISystemClock.cpp b/app/src/main/cpp/skyline/services/timesrv/ISystemClock.cpp index d8d7aced..71292a79 100644 --- a/app/src/main/cpp/skyline/services/timesrv/ISystemClock.cpp +++ b/app/src/main/cpp/skyline/services/timesrv/ISystemClock.cpp @@ -1,20 +1,66 @@ // SPDX-License-Identifier: MPL-2.0 // Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/) -#include "ISteadyClock.h" +#include +#include "results.h" +#include "core.h" #include "ISystemClock.h" namespace skyline::service::timesrv { - ISystemClock::ISystemClock(const SystemClockType clockType, const DeviceState &state, ServiceManager &manager) : type(clockType), BaseService(state, manager) {} + ISystemClock::ISystemClock(const DeviceState &state, ServiceManager &manager, core::SystemClockCore &core, bool writeClock, bool ignoreUninitializedChecks) : BaseService(state, manager), core(core), writeClock(writeClock), ignoreUninitializedChecks(ignoreUninitializedChecks) {} Result ISystemClock::GetCurrentTime(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { - response.Push(static_cast(std::time(nullptr))); - return {}; + if (!ignoreUninitializedChecks && !core.IsClockInitialized()) + return result::ClockUninitialized; + + auto posixTime{core.GetCurrentTime()}; + if (posixTime) + response.Push(*posixTime); + + return posixTime; + } + + Result ISystemClock::SetCurrentTime(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { + if (!writeClock) + return result::PermissionDenied; + + if (!ignoreUninitializedChecks && !core.IsClockInitialized()) + return result::ClockUninitialized; + + return core.SetCurrentTime(request.Pop()); } Result ISystemClock::GetSystemClockContext(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { - response.Push(static_cast(std::time(nullptr))); - response.Push(SteadyClockTimePoint{static_cast(std::time(nullptr))}); + if (!ignoreUninitializedChecks && !core.IsClockInitialized()) + return result::ClockUninitialized; + + auto context{core.GetClockContext()}; + if (context) + response.Push(*context); + + return context; + } + + Result ISystemClock::SetSystemClockContext(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { + if (!writeClock) + return result::PermissionDenied; + + if (!ignoreUninitializedChecks && !core.IsClockInitialized()) + return result::ClockUninitialized; + + return core.SetClockContext(request.Pop()); + } + + Result ISystemClock::GetOperationEventReadableHandle(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { + if (!operationEvent) { + operationEvent = std::make_shared(state, false); + core.AddOperationEvent(operationEvent); + } + + auto handle{state.process->InsertItem(operationEvent)}; + state.logger->Debug("ISystemClock Operation Event Handle: 0x{:X}", handle); + response.copyHandles.push_back(handle); return {}; } + } diff --git a/app/src/main/cpp/skyline/services/timesrv/ISystemClock.h b/app/src/main/cpp/skyline/services/timesrv/ISystemClock.h index 4275f2db..be9ee2e9 100644 --- a/app/src/main/cpp/skyline/services/timesrv/ISystemClock.h +++ b/app/src/main/cpp/skyline/services/timesrv/ISystemClock.h @@ -3,41 +3,45 @@ #pragma once +#include #include namespace skyline::service::timesrv { - /** - * @brief The type of a #SystemClockType - */ - enum class SystemClockType { - User, //!< Use time provided by user - Network, //!< Use network time - Local, //!< Use local time - }; + namespace core { + class SystemClockCore; + } /** - * @brief ISystemClock is used to retrieve and set time + * @brief ISystemClock is used to interface with timesrv system clocks * @url https://switchbrew.org/wiki/PSC_services#ISystemClock */ class ISystemClock : public BaseService { + private: + core::SystemClockCore &core; + bool writeClock; + bool ignoreUninitializedChecks; + + std::shared_ptr operationEvent{}; + public: - const SystemClockType type; + ISystemClock(const DeviceState &state, ServiceManager &manager, core::SystemClockCore &core, bool writeClock, bool ignoreUninitializedChecks); - ISystemClock(const SystemClockType clockType, const DeviceState &state, ServiceManager &manager); - - /** - * @brief Returns the amount of seconds since epoch - */ Result GetCurrentTime(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response); - /** - * @brief Returns the system clock context - */ + Result SetCurrentTime(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response); + Result GetSystemClockContext(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response); + Result SetSystemClockContext(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response); + + Result GetOperationEventReadableHandle(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response); + SERVICE_DECL( SFUNC(0x0, ISystemClock, GetCurrentTime), - SFUNC(0x2, ISystemClock, GetSystemClockContext) + SFUNC(0x1, ISystemClock, SetCurrentTime), + SFUNC(0x2, ISystemClock, GetSystemClockContext), + SFUNC(0x3, ISystemClock, SetSystemClockContext), + SFUNC(0x4, ISystemClock, GetOperationEventReadableHandle), ) }; } diff --git a/app/src/main/cpp/skyline/services/timesrv/ITimeZoneService.cpp b/app/src/main/cpp/skyline/services/timesrv/ITimeZoneService.cpp index 9b7d2a3e..9027bb21 100644 --- a/app/src/main/cpp/skyline/services/timesrv/ITimeZoneService.cpp +++ b/app/src/main/cpp/skyline/services/timesrv/ITimeZoneService.cpp @@ -1,14 +1,15 @@ // SPDX-License-Identifier: MPL-2.0 // Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/) +#include "common.h" #include "ITimeZoneService.h" namespace skyline::service::timesrv { ITimeZoneService::ITimeZoneService(const DeviceState &state, ServiceManager &manager) : BaseService(state, manager) {} Result ITimeZoneService::ToCalendarTimeWithMyRule(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { - auto &time{request.Pop()}; - auto calender{*std::gmtime(reinterpret_cast(&time))}; + auto time{static_cast(request.Pop())}; + auto calender{*std::gmtime(&time)}; CalendarTime calendarTime{ .year = static_cast(calender.tm_year), @@ -20,11 +21,11 @@ namespace skyline::service::timesrv { response.Push(calendarTime); CalendarAdditionalInfo calendarInfo{ - .dayWeek = static_cast(calender.tm_wday), - .dayMonth = static_cast(calender.tm_mday), - .tzName = *reinterpret_cast(calender.tm_zone), - .dst = static_cast(calender.tm_isdst), - .utcRel = static_cast(calender.tm_gmtoff), + .dayOfWeek = static_cast(calender.tm_wday), + .dayOfYear = static_cast(calender.tm_yday), + .timezoneName = "GMT", + .dst = static_cast(calender.tm_isdst), + .gmtOffset = static_cast(calender.tm_gmtoff), }; response.Push(calendarInfo); return {}; diff --git a/app/src/main/cpp/skyline/services/timesrv/ITimeZoneService.h b/app/src/main/cpp/skyline/services/timesrv/ITimeZoneService.h index 7fa28320..49bc3a01 100644 --- a/app/src/main/cpp/skyline/services/timesrv/ITimeZoneService.h +++ b/app/src/main/cpp/skyline/services/timesrv/ITimeZoneService.h @@ -11,33 +11,6 @@ namespace skyline::service::timesrv { * @url https://switchbrew.org/wiki/PSC_services#ITimeZoneService */ class ITimeZoneService : public BaseService { - private: - /** - * @brief A particular time point in Nintendo's calendar format - */ - struct CalendarTime { - u16 year; //!< Amount of years that have passed since 1900 - u8 month; //!< Month of the year (1-12) [POSIX time uses 0-11] - u8 day; //!< Day of the month (1-31) - u8 hour; //!< Hour of the day (0-23) - u8 minute; //!< Minute of the hour (0-59) - u8 second; //!< Second of the minute (0-60) - u8 _pad_; - }; - static_assert(sizeof(CalendarTime) == 0x8); - - /** - * @brief Additional metadata about the time alongside CalendarTime - */ - struct CalendarAdditionalInfo { - u32 dayWeek; //!< Amount of days since Sunday - u32 dayMonth; //!< Amount of days since the start of the month - u64 tzName; //!< The name of the time zone - i32 dst; //!< If DST is in effect or not - u32 utcRel; //!< Offset of the time from GMT in seconds - }; - static_assert(sizeof(CalendarAdditionalInfo) == 0x18); - public: ITimeZoneService(const DeviceState &state, ServiceManager &manager); diff --git a/app/src/main/cpp/skyline/services/timesrv/common.cpp b/app/src/main/cpp/skyline/services/timesrv/common.cpp new file mode 100644 index 00000000..f4b7d7ba --- /dev/null +++ b/app/src/main/cpp/skyline/services/timesrv/common.cpp @@ -0,0 +1,24 @@ +#include +#include "results.h" +#include "common.h" + +namespace skyline::service::timesrv { + ResultValue ClockSnapshot::GetCurrentTime(const SteadyClockTimePoint &timePoint, const SystemClockContext &context) { + if (context.timestamp.clockSourceId != timePoint.clockSourceId) + return result::ClockSourceIdMismatch; + + return context.offset + timePoint.timePoint; + } + + ResultValue GetSpanBetween(const SteadyClockTimePoint &start, const SteadyClockTimePoint &end) { + // We can't compare between different clocks as they don't necessarily operate from the same origin + if (start.clockSourceId != end.clockSourceId) + return result::InvalidComparison; + + if (((start.timePoint > 0) && (end.timePoint < std::numeric_limits::min() + start.timePoint)) || + ((start.timePoint < 0) && (end.timePoint > std::numeric_limits::max() + start.timePoint))) + return result::CompareOverflow; + + return end.timePoint - start.timePoint; + } +} \ No newline at end of file diff --git a/app/src/main/cpp/skyline/services/timesrv/common.h b/app/src/main/cpp/skyline/services/timesrv/common.h new file mode 100644 index 00000000..3a89a25b --- /dev/null +++ b/app/src/main/cpp/skyline/services/timesrv/common.h @@ -0,0 +1,135 @@ +#pragma once + +#include +#include + +namespace skyline::service::timesrv { + using PosixTime = i64; //!< Unit for time in seconds since the epoch + + /** + * @brief Stores a quantity of time with nanosecond accuracy and provides helper functions to convert it to other units + */ + class TimeSpanType { + private: + i64 ns{}; //!< Timepoint of the timespan in nanoseconds + + public: + constexpr TimeSpanType() {} + + constexpr TimeSpanType(i64 ns) : ns(ns) {} + + static constexpr TimeSpanType FromNanoseconds(i64 ns) { + return {ns}; + } + + static constexpr TimeSpanType FromSeconds(i64 s) { + return {s * static_cast(skyline::constant::NsInSecond)}; + } + + static constexpr TimeSpanType FromDays(i64 d) { + return {d * static_cast(skyline::constant::NsInDay)}; + } + + constexpr i64 Nanoseconds() const { + return ns; + } + + constexpr i64 Seconds() const { + return ns / static_cast(skyline::constant::NsInSecond); + } + + constexpr friend bool operator>(const TimeSpanType &lhs, const TimeSpanType &rhs) { + return lhs.ns > rhs.ns; + } + + constexpr friend bool operator<(const TimeSpanType &lhs, const TimeSpanType &rhs) { + return lhs.ns < rhs.ns; + } + + constexpr friend TimeSpanType operator+(const TimeSpanType &lhs, const TimeSpanType &rhs) { + return FromNanoseconds(lhs.ns + rhs.ns); + } + + constexpr friend TimeSpanType operator-(const TimeSpanType &lhs, const TimeSpanType &rhs) { + return FromNanoseconds(lhs.ns - rhs.ns); + } + }; + + /** + * @brief Holds details about a point in time sourced from a steady clock (e.g. RTC) + */ + struct __attribute__((packed)) SteadyClockTimePoint { + i64 timePoint; //!< Time in seconds + UUID clockSourceId; //!< The UUID of the steady clock this timepoint comes from + + auto operator<=>(const SteadyClockTimePoint &) const = default; + }; + static_assert(sizeof(SteadyClockTimePoint) == 0x18); + + /** + * @brief Describes a system clocks offset from its associated steady clock + */ + struct __attribute__((packed)) SystemClockContext { + i64 offset; // Offset between the steady timepoint and the epoch + SteadyClockTimePoint timestamp; //!< The steady timepoint this context was calibrated from + + auto operator<=>(const SystemClockContext &) const = default; + }; + static_assert(sizeof(SystemClockContext) == 0x20); + + /** + * @brief A particular time point in Nintendo's calendar format + */ + struct CalendarTime { + u16 year; //!< The current year minus 1900 + u8 month; //!< 1-12 (POSIX time uses 0-11) + u8 day; //!< 1-31 + u8 hour; //!< 0-23 + u8 minute; //!< 0-59 + u8 second; //!< 0-60 + u8 _pad_; + }; + static_assert(sizeof(CalendarTime) == 0x8); + + /** + * @brief Additional metadata about the time alongside CalendarTime + */ + struct CalendarAdditionalInfo { + u32 dayOfWeek; //!< 0-6 + u32 dayOfYear; //!< 0-365 + std::array timezoneName; + u32 dst; //!< If DST is in effect or not + i32 gmtOffset; //!< Offset of the time from GMT in seconds + }; + static_assert(sizeof(CalendarAdditionalInfo) == 0x18); + + /** + * @brief A snapshot of all clocks in the system + */ + struct ClockSnapshot { + SystemClockContext userContext; + SystemClockContext networkContext; + PosixTime userPosixTime; + PosixTime networkPosixTime; + CalendarTime userCalendarTime; + CalendarTime networkCalendarTime; + CalendarAdditionalInfo userCalendarAdditionalInfo; + CalendarAdditionalInfo networkCalendarAdditionalInfo; + SteadyClockTimePoint steadyClockTimePoint; + std::array locationName; + u8 automaticCorrectionEnabled; + u8 _unk_; + u16 version; + + /** + * @brief Gets the current time based off of the supplied timepoint and context + */ + static ResultValue GetCurrentTime(const SteadyClockTimePoint &timePoint, const SystemClockContext &context); + }; + static_assert(sizeof(ClockSnapshot) == 0xD0); + + /** + * @brief Gets the time between a pair of steady clock timepoints + */ + ResultValue GetSpanBetween(const SteadyClockTimePoint &start, const SteadyClockTimePoint &end); +} \ No newline at end of file diff --git a/app/src/main/cpp/skyline/services/timesrv/core.cpp b/app/src/main/cpp/skyline/services/timesrv/core.cpp new file mode 100644 index 00000000..b1dd6b0b --- /dev/null +++ b/app/src/main/cpp/skyline/services/timesrv/core.cpp @@ -0,0 +1,260 @@ +// SPDX-License-Identifier: MPL-2.0 +// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/) + +#include "core.h" +#include "time_manager_server.h" + +namespace skyline::service::timesrv::core { + TimeSpanType SteadyClockCore::GetRawTimePoint() { + auto timePoint{GetTimePoint()}; + + if (timePoint) + return TimeSpanType::FromSeconds(timePoint->timePoint); + else + throw exception("Error reading timepoint"); + } + + ResultValue SteadyClockCore::GetCurrentTimePoint() { + auto timePoint{GetTimePoint()}; + if (timePoint) + timePoint->timePoint += (GetTestOffset() + GetInternalOffset()).Seconds(); + + return timePoint; + } + + TimeSpanType SteadyClockCore::GetCurrentRawTimePoint() { + return GetRawTimePoint() + GetTestOffset() + GetInternalOffset(); + } + + void StandardSteadyClockCore::Setup(UUID pRtcId, TimeSpanType pRtcOffset, TimeSpanType pInternalOffset, TimeSpanType pTestOffset, bool rtcResetDetected) { + rtcId = pRtcId; + rtcOffset = pRtcOffset; + internalOffset = pInternalOffset; + testOffset = pTestOffset; + + if (rtcResetDetected) + SetRtcReset(); + + MarkInitialized(); + } + + ResultValue StandardSteadyClockCore::GetTimePoint() { + SteadyClockTimePoint timePoint{ + .timePoint = GetRawTimePoint().Seconds(), + .clockSourceId = rtcId, + }; + + return timePoint; + } + + TimeSpanType StandardSteadyClockCore::GetRawTimePoint() { + std::lock_guard lock(mutex); + + auto timePoint{TimeSpanType::FromNanoseconds(util::GetTimeNs()) + rtcOffset}; + + if (timePoint > cachedValue) + cachedValue = timePoint; + + return timePoint; + } + + ResultValue TickBasedSteadyClockCore::GetTimePoint() { + SteadyClockTimePoint timePoint{ + .timePoint = TimeSpanType::FromNanoseconds(util::GetTimeNs()).Seconds(), + .clockSourceId = id, + }; + + return timePoint; + } + + bool SystemClockCore::IsClockSetup() { + if (GetClockContext()) { + auto timePoint{steadyClock.GetCurrentTimePoint()}; + if (timePoint) + return timePoint->clockSourceId.Valid(); + } + + return false; + } + + Result SystemClockCore::UpdateClockContext(const SystemClockContext &newContext) { + auto ret{SetClockContext(newContext)}; + if (ret) + return ret; + + // Writes new state to shared memory etc + if (updateCallback) { + return updateCallback->UpdateContext(newContext); + } else { + return {}; + } + } + + Result SystemClockCore::SetCurrentTime(PosixTime posixTimePoint) { + auto timePoint{steadyClock.GetCurrentTimePoint()}; + if (!timePoint) + return timePoint; + + // Set new context with an offset relative to the given POSIX time + SystemClockContext newContext{ + .timestamp = *timePoint, + .offset = posixTimePoint - timePoint->timePoint, + }; + + UpdateClockContext(newContext); + + return {}; + } + + ResultValue SystemClockCore::GetCurrentTime() { + auto timePoint{steadyClock.GetCurrentTimePoint()}; + if (!timePoint) + return timePoint; + + auto clockContext{GetClockContext()}; + if (!clockContext) + return clockContext; + + if (clockContext->timestamp.clockSourceId != timePoint->clockSourceId) + return result::ClockSourceIdMismatch; + + return clockContext->offset + timePoint->timePoint; + } + + void StandardLocalSystemClockCore::Setup(const SystemClockContext &context, PosixTime posixTime) { + auto timePoint{steadyClock.GetCurrentTimePoint()}; + + Result ret{}; + + // If the new context comes from the same clock as what we currently have we don't need to set any offset as they share the same base + if (timePoint && timePoint->clockSourceId == context.timestamp.clockSourceId) + ret = UpdateClockContext(context); + else + ret = SetCurrentTime(posixTime); + + if (ret) + throw exception("Failed to setup StandardLocalSystemClockCore"); + + MarkInitialized(); + } + + void StandardNetworkSystemClockCore::Setup(const SystemClockContext &context, TimeSpanType newSufficientAccuracy) { + if (UpdateClockContext(context)) + throw exception("Failed to set up StandardNetworkSystemClockCore"); + + sufficientAccuracy = newSufficientAccuracy; + MarkInitialized(); + } + + bool StandardNetworkSystemClockCore::IsAccuracySufficient() { + if (!IsClockInitialized()) + return false; + + auto timePoint{steadyClock.GetCurrentTimePoint()}; + if (!timePoint) + return false; + + auto spanBetween{GetSpanBetween(context.timestamp, *timePoint)}; + return spanBetween && *spanBetween < sufficientAccuracy.Seconds(); + } + + Result StandardUserSystemClockCore::SetAutomaticCorrectionEnabled(bool enable) { + // Resync with network clock before any state transitions + if (enable != automaticCorrectionEnabled && networkSystemClock.IsClockSetup()) { + auto ctx{networkSystemClock.GetClockContext()}; + if (!ctx) + return ctx; + + auto ret{localSystemClock.SetClockContext(*ctx)}; + if (ret) + return ret; + } + + automaticCorrectionEnabled = enable; + return {}; + } + + void StandardUserSystemClockCore::SetAutomaticCorrectionUpdatedTime(const SteadyClockTimePoint &timePoint) { + automaticCorrectionUpdatedTime = timePoint; + automaticCorrectionUpdatedEvent->Signal(); + } + + Result StandardUserSystemClockCore::UpdateAutomaticCorrectionState(bool enable) { + auto ret{SetAutomaticCorrectionEnabled(enable)}; + if (!ret) { + timeSharedMemory.SetStandardUserSystemClockAutomaticCorrectionEnabled(enable); + + auto timePoint{steadyClock.GetCurrentTimePoint()}; + if (timePoint) + SetAutomaticCorrectionUpdatedTime(*timePoint); + else + return timePoint; + } + + return ret; + } + + void StandardUserSystemClockCore::Setup(bool enableAutomaticCorrection, const SteadyClockTimePoint &automaticCorrectionUpdateTime) { + if (SetAutomaticCorrectionEnabled(enableAutomaticCorrection)) + throw exception("Failed to set up SetupStandardUserSystemClock: failed to set automatic correction state!"); + + SetAutomaticCorrectionUpdatedTime(automaticCorrectionUpdateTime); + + MarkInitialized(); + + timeSharedMemory.SetStandardUserSystemClockAutomaticCorrectionEnabled(enableAutomaticCorrection); + } + + ResultValue StandardUserSystemClockCore::GetClockContext() { + if (automaticCorrectionEnabled && networkSystemClock.IsClockSetup()) { + auto ctx{networkSystemClock.GetClockContext()}; + if (!ctx) + return ctx; + + auto ret{localSystemClock.SetClockContext(*ctx)}; + if (ret) + return ret; + } + + return localSystemClock.GetClockContext(); + } + + TimeServiceObject::TimeServiceObject(const DeviceState &state) : timeSharedMemory(state), localSystemClockContextWriter(timeSharedMemory), networkSystemClockContextWriter(timeSharedMemory), localSystemClock(standardSteadyClock), networkSystemClock(standardSteadyClock), userSystemClock(state, standardSteadyClock, localSystemClock, networkSystemClock, timeSharedMemory), empheralSystemClock(tickBasedSteadyClock), managerServer(*this) { + + // Setup time service: + // A new rtc UUID is generated every time glue inits time + auto rtcId{UUID::GenerateUuidV4()}; + auto rtcOffset{TimeSpanType::FromSeconds(std::time(nullptr)) - TimeSpanType::FromNanoseconds(util::GetTimeNs())}; + + // On the switch the RTC may not always start from the epoch so it is compensated with the internal offset. + // We however emulate RTC to start from the epoch so we can set it to zero, if we wanted to add an option for a system time offset we would change this. + TimeSpanType internalOffset{}; + + // Setup the standard steady clock from which everything in the system counts + managerServer.SetupStandardSteadyClock(rtcId, rtcOffset, internalOffset, {}, false); + + SystemClockContext localSystemClockContext{ + .timestamp { + .timePoint = 0, + .clockSourceId = rtcId, + }, + .offset = 0 //!< Zero offset as the RTC is calibrated already + }; + // Don't supply a POSIX time as the offset will be taken from the above context instead. + // Normally the POSIX time would be the initial year for the clock to reset to if the context got wiped. + managerServer.SetupStandardLocalSystemClock(localSystemClockContext, 0); + + // Use the context just created in local clock for the network clock, HOS gets this from settings + auto context{localSystemClock.GetClockContext()}; + if (!context) + throw exception("Failed to get local system clock context!"); + + constexpr TimeSpanType sufficientAccuracy{TimeSpanType::FromDays(30)}; //!< https://switchbrew.org/wiki/System_Settings#time + + managerServer.SetupStandardNetworkSystemClock(*context, sufficientAccuracy); + + // Initialise the user system clock with automatic correction disabled as we don't emulate the automatic correction thread + managerServer.SetupStandardUserSystemClock(false, SteadyClockTimePoint{.clockSourceId = UUID::GenerateUuidV4()}); + managerServer.SetupEphemeralSystemClock(); + } +} \ No newline at end of file diff --git a/app/src/main/cpp/skyline/services/timesrv/core.h b/app/src/main/cpp/skyline/services/timesrv/core.h new file mode 100644 index 00000000..50241da6 --- /dev/null +++ b/app/src/main/cpp/skyline/services/timesrv/core.h @@ -0,0 +1,318 @@ +// SPDX-License-Identifier: MPL-2.0 +// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/) + +#pragma once + +#include +#include +#include +#include +#include +#include "time_shared_memory.h" +#include "time_manager_server.h" +#include "results.h" + +namespace skyline::service::timesrv::core { + /** + * @brief A steady clock provides a monotonically increasing timepoint calibrated from a specific base + */ + class SteadyClockCore { + bool rtcResetDetected{}; //!< True if the RTC this clock is based off has reset before this boot. + bool initialized{}; //!< If this clock is calibrated with offsets etc and ready for use by applications + + protected: + void SetRtcReset() { + rtcResetDetected = true; + } + + void MarkInitialized() { + initialized = true; + } + + public: + bool IsRtcResetDetected() { + return rtcResetDetected; + } + + bool IsClockInitialized() { + return initialized; + } + + /** + * @brief Returns the current timepoint of the clock including offsets in a SteadyClockTimePoint struct with a source UUID + */ + ResultValue GetCurrentTimePoint(); + + /** + * @brief Returns the current raw timepoint of the clock including offsets but without any UUID, this may have higher accuracy + */ + TimeSpanType GetCurrentRawTimePoint(); + + /** + * @brief Returns the base timepoint of the clock without any offsets applied in a SteadyClockTimePoint struct with a source UUID + */ + virtual ResultValue GetTimePoint() = 0; + + /** + * @brief Returns the current raw timepoint of the clock without any offsets applied without any UUID, this may have higher accuracy + */ + virtual TimeSpanType GetRawTimePoint(); + + /** + * @brief A test offset is used to alter the base timepoint of the steady clock without it being visible to applications + */ + virtual TimeSpanType GetTestOffset() { + return {}; + } + + virtual void SetTestOffset(TimeSpanType offset) {} + + /** + * @brief The internal offset is the offset between the raw steady clock time and the target time of this steady clock + */ + virtual TimeSpanType GetInternalOffset() { + return {}; + } + + virtual void SetInternalOffset(TimeSpanType offset) { + return; + } + + /** + * @brief Returns the current value of the RTC that backs this clock + */ + virtual ResultValue GetRtcValue() { + return result::Unimplemented; + } + + virtual Result GetSetupResult() { + return {}; + } + }; + + /** + * @brief The standard steady clock is calibrated against system RTC time and is used as a base for all clocks aside from alarms and ephemeral + */ + class StandardSteadyClockCore : public SteadyClockCore { + std::mutex mutex; //!< Protects accesses to cachedValue + TimeSpanType testOffset{}; + TimeSpanType internalOffset{}; + TimeSpanType rtcOffset{}; //!< The offset between the RTC timepoint and the raw timepoints of this clock + TimeSpanType cachedValue{}; //!< Stores the cached time value, used to prevent time ever decreasing + UUID rtcId{}; //!< UUID of the RTC this is calibrated against + + public: + void Setup(UUID rtcId, TimeSpanType pRtcOffset, TimeSpanType pInternalOffset, TimeSpanType pTestOffset, bool rtcResetDetected); + + void SetRtcOffset(TimeSpanType offset) { + rtcOffset = offset; + } + + ResultValue GetTimePoint() override; + + TimeSpanType GetRawTimePoint() override; + + TimeSpanType GetTestOffset() override { + return testOffset; + } + + void SetTestOffset(TimeSpanType offset) override { + testOffset = offset; + } + + TimeSpanType GetInternalOffset() override { + return internalOffset; + } + + void SetInternalOffset(TimeSpanType offset) override { + internalOffset = offset; + } + }; + + /** + * @brief The tick-based steady clock provides a monotonically increasing steady clock that is based off system boot + */ + class TickBasedSteadyClockCore : public SteadyClockCore { + private: + UUID id{UUID::GenerateUuidV4()}; + + public: + ResultValue GetTimePoint() override; + }; + + class SystemClockContextUpdateCallback; + + /** + * @brief System clocks make use of the steady clock in order to provide an adjusted POSIX timepoint that is synchronised with the network or adapted to user time optionss + */ + class SystemClockCore { + private: + bool initialized{}; //!< True if the clock is safe to be used by applications and in a defined state + SystemClockContextUpdateCallback *updateCallback; //!< Called when the context of the clock is updated + + protected: + SteadyClockCore &steadyClock; //!< Clock that backs this system clock + SystemClockContext context{}; //!< Holds the currently in-use context of the clock + + void MarkInitialized() { + initialized = true; + } + + public: + SystemClockCore(SteadyClockCore &steadyClock) : steadyClock(steadyClock) {} + + void AddOperationEvent(const std::shared_ptr &event) { + updateCallback->AddOperationEvent(event); + } + + void SetUpdateCallback(SystemClockContextUpdateCallback *callback) { + updateCallback = callback; + } + + bool IsClockInitialized() { + return initialized; + } + + /** + * @brief Checks if this system clock can produce a valid timepoint + */ + bool IsClockSetup(); + + /** + * @brief Updates the clock to use the given context and calls the update callback + */ + Result UpdateClockContext(const SystemClockContext &newContext); + + /** + * @brief Sets the current clock offsets as if posixTimePoint is the current time, this updates the clock comtext so will call the callback + */ + Result SetCurrentTime(PosixTime posixTimePoint); + + /** + * @brief Returns the current POSIX time for this system clock + */ + ResultValue GetCurrentTime(); + + virtual ResultValue GetClockContext() { + return context; + } + + virtual Result SetClockContext(const SystemClockContext &newContext) { + context = newContext; + return {}; + } + }; + + /** + * @brief The local system clock is a user configurable system clock based off of the system steady clock + */ + class StandardLocalSystemClockCore : public SystemClockCore { + public: + StandardLocalSystemClockCore(SteadyClockCore &steadyClock) : SystemClockCore(steadyClock) {} + + void Setup(const SystemClockContext &context, PosixTime posixTime); + }; + + /** + * @brief The network system clock is a network based system clock that is inconfigurable by the user in HOS + */ + class StandardNetworkSystemClockCore : public SystemClockCore { + private: + TimeSpanType sufficientAccuracy{TimeSpanType::FromDays(10)}; //!< Maxiumum drift between the current steady time and the timestamp of the context currently in use + + public: + StandardNetworkSystemClockCore(SteadyClockCore &steadyClock) : SystemClockCore(steadyClock) {} + + void Setup(const SystemClockContext &context, TimeSpanType newSufficientAccuracy); + + /** + * @brief Checks if the clock accuracy is less than sufficientAccuracy + */ + bool IsAccuracySufficient(); + }; + + /** + * @brief The standard user system clock provides an automatically corrected clock based on both local and network time, it is what should be used in most cases for time measurement + */ + class StandardUserSystemClockCore : public SystemClockCore { + private: + StandardLocalSystemClockCore &localSystemClock; //!< The StandardLocalSystemClockCore this clock uses for correction + StandardNetworkSystemClockCore &networkSystemClock; //!< The StandardNetworkSystemClockCore this clock uses for correction + bool automaticCorrectionEnabled{}; //!< If automatic correction with the network clock should be enabled + SteadyClockTimePoint automaticCorrectionUpdatedTime; //!< When automatic correction was last enabled + TimeSharedMemory &timeSharedMemory; //!< Shmem reference for automatic correction state updating + + /** + * @brief Sets automatic correction state and resyncs with network clock on changes + */ + Result SetAutomaticCorrectionEnabled(bool enable); + + void SetAutomaticCorrectionUpdatedTime(const SteadyClockTimePoint &timePoint); + + public: + std::shared_ptr automaticCorrectionUpdatedEvent; + + StandardUserSystemClockCore(const DeviceState &state, StandardSteadyClockCore &standardSteadyClock, StandardLocalSystemClockCore &localSystemClock, StandardNetworkSystemClockCore &networkSystemClock, TimeSharedMemory &timeSharedMemory) : SystemClockCore(standardSteadyClock), localSystemClock(localSystemClock), networkSystemClock(networkSystemClock), automaticCorrectionUpdatedEvent(std::make_shared(state, false)), timeSharedMemory(timeSharedMemory) {} + + void Setup(bool enableAutomaticCorrection, const SteadyClockTimePoint &automaticCorrectionUpdateTime); + + bool IsAutomaticCorrectionEnabled() { + return automaticCorrectionEnabled; + } + + SteadyClockTimePoint GetAutomaticCorrectionUpdatedTime() { + return automaticCorrectionUpdatedTime; + } + + /** + * @brief Updates the automatic correction state in shared memory and this clock + */ + Result UpdateAutomaticCorrectionState(bool enable); + + ResultValue GetClockContext() override; + + /** + * @brief Context is not directly settable here as it is derived from network and local clocks + */ + Result SetClockContext(const SystemClockContext &pContext) override { + return result::Unimplemented; + } + }; + + /** + * @brief The ephemeral system clock provides a per-boot timepoint + */ + class EphemeralNetworkSystemClockCore : public SystemClockCore { + public: + EphemeralNetworkSystemClockCore(SteadyClockCore &steadyClock) : SystemClockCore(steadyClock) {} + + void Setup() { + MarkInitialized(); + } + }; + + /** + * @brief Stores the global state of timesrv and exposes a manager interface for use by IPC + */ + struct TimeServiceObject { + TimeSharedMemory timeSharedMemory; + + LocalSystemClockUpdateCallback localSystemClockContextWriter; + NetworkSystemClockUpdateCallback networkSystemClockContextWriter; + EphemeralNetworkSystemClockUpdateCallback ephemeralNetworkSystemClockContextWriter; + + StandardSteadyClockCore standardSteadyClock; + TickBasedSteadyClockCore tickBasedSteadyClock; + StandardLocalSystemClockCore localSystemClock; + StandardNetworkSystemClockCore networkSystemClock; + StandardUserSystemClockCore userSystemClock; + EphemeralNetworkSystemClockCore empheralSystemClock; + + TimeManagerServer managerServer; + + /** + * @brief Sets up all clocks with offsets based off of the current time + */ + TimeServiceObject(const DeviceState &state); + }; +} diff --git a/app/src/main/cpp/skyline/services/timesrv/results.h b/app/src/main/cpp/skyline/services/timesrv/results.h new file mode 100644 index 00000000..6de56df2 --- /dev/null +++ b/app/src/main/cpp/skyline/services/timesrv/results.h @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: MPL-2.0 +// Copyright © 2021 Skyline Team and Contributors (https://github.com/skyline-emu/) + +#pragma once + +#include + +namespace skyline::service::timesrv::result { + constexpr Result PermissionDenied(116, 1); + constexpr Result ClockSourceIdMismatch(116, 102); + constexpr Result ClockUninitialized(116, 103); + constexpr Result InvalidComparison(116, 200); + constexpr Result CompareOverflow(116, 201); + constexpr Result Unimplemented(116, 990); +} \ No newline at end of file diff --git a/app/src/main/cpp/skyline/services/timesrv/time_manager_server.cpp b/app/src/main/cpp/skyline/services/timesrv/time_manager_server.cpp new file mode 100644 index 00000000..397d6c5f --- /dev/null +++ b/app/src/main/cpp/skyline/services/timesrv/time_manager_server.cpp @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: MPL-2.0 +// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/) + +#include +#include +#include "core.h" +#include "time_manager_server.h" + +namespace skyline::service::timesrv { + TimeManagerServer::TimeManagerServer(core::TimeServiceObject &core) : core(core) {} + + std::shared_ptr TimeManagerServer::GetUserStaticService(const DeviceState &state, ServiceManager &manager) { + return std::make_shared(state, manager, core, constant::StaticServiceUserPermissions); + } + + std::shared_ptr TimeManagerServer::GetAdminStaticService(const DeviceState &state, ServiceManager &manager) { + return std::make_shared(state, manager, core, constant::StaticServiceAdminPermissions); + } + + std::shared_ptr TimeManagerServer::GetRepairStaticService(const DeviceState &state, ServiceManager &manager) { + return std::make_shared(state, manager, core, constant::StaticServiceRepairPermissions); + } + + std::shared_ptr TimeManagerServer::GetManagerStaticService(const DeviceState &state, ServiceManager &manager) { + return std::make_shared(state, manager, core, constant::StaticServiceManagerPermissions); + } + + Result TimeManagerServer::SetupStandardSteadyClock(UUID rtcId, TimeSpanType rtcOffset, TimeSpanType internalOffset, TimeSpanType testOffset, bool rtcResetDetected) { + core.standardSteadyClock.Setup(rtcId, rtcOffset, internalOffset, testOffset, rtcResetDetected); + + auto timePoint{core.standardSteadyClock.GetCurrentRawTimePoint()}; + core.timeSharedMemory.SetupStandardSteadyClock(rtcId, timePoint); + return {}; + } + + Result TimeManagerServer::SetupStandardLocalSystemClock(const SystemClockContext &context, PosixTime posixTime) { + core.localSystemClock.SetUpdateCallback(&core.localSystemClockContextWriter); + core.localSystemClock.Setup(context, posixTime); + return {}; + } + + Result TimeManagerServer::SetupStandardNetworkSystemClock(const SystemClockContext &context, TimeSpanType sufficientAccuracy) { + core.networkSystemClock.SetUpdateCallback(&core.networkSystemClockContextWriter); + core.networkSystemClock.Setup(context, sufficientAccuracy); + return {}; + } + + Result TimeManagerServer::SetupStandardUserSystemClock(bool enableAutomaticCorrection, const SteadyClockTimePoint &automaticCorrectionUpdateTime) { + core.userSystemClock.Setup(enableAutomaticCorrection, automaticCorrectionUpdateTime); + return {}; + } + + Result TimeManagerServer::SetupEphemeralSystemClock() { + core.empheralSystemClock.SetUpdateCallback(&core.ephemeralNetworkSystemClockContextWriter); + core.empheralSystemClock.Setup(); + return {}; + } + + std::shared_ptr TimeManagerServer::GetStandardUserSystemClockAutomaticCorrectionEvent() { + return core.userSystemClock.automaticCorrectionUpdatedEvent; + } + + Result TimeManagerServer::SetStandardSteadyClockRtcOffset(TimeSpanType rtcOffset) { + core.standardSteadyClock.SetRtcOffset(rtcOffset); + core.timeSharedMemory.SetSteadyClockRawTimePoint(core.standardSteadyClock.GetCurrentRawTimePoint()); + + return {}; + } +} diff --git a/app/src/main/cpp/skyline/services/timesrv/time_manager_server.h b/app/src/main/cpp/skyline/services/timesrv/time_manager_server.h new file mode 100644 index 00000000..792d3d53 --- /dev/null +++ b/app/src/main/cpp/skyline/services/timesrv/time_manager_server.h @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: MPL-2.0 +// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/) + +#pragma once + +#include +#include +#include +#include "common.h" +#include "IStaticService.h" + +namespace skyline::service::timesrv { + namespace core { + struct TimeServiceObject; + } + + class TimeManagerServer { + private: + core::TimeServiceObject &core; + + public: + TimeManagerServer(core::TimeServiceObject &core); + + std::shared_ptr GetUserStaticService(const DeviceState &state, ServiceManager &manager); + + std::shared_ptr GetAdminStaticService(const DeviceState &state, ServiceManager &manager); + + std::shared_ptr GetRepairStaticService(const DeviceState &state, ServiceManager &manager); + + std::shared_ptr GetManagerStaticService(const DeviceState &state, ServiceManager &manager); + + Result SetupStandardSteadyClock(UUID rtcId, TimeSpanType rtcOffset, TimeSpanType internalOffset, TimeSpanType testOffset, bool rtcResetDetected); + + Result SetupStandardLocalSystemClock(const SystemClockContext &context, PosixTime posixTime); + + Result SetupStandardNetworkSystemClock(const SystemClockContext &context, TimeSpanType sufficientAccuracy); + + Result SetupStandardUserSystemClock(bool enableAutomaticCorrection, const SteadyClockTimePoint &automaticCorrectionUpdateTime); + + Result SetupEphemeralSystemClock(); + + std::shared_ptr GetStandardUserSystemClockAutomaticCorrectionEvent(); + + Result SetStandardSteadyClockRtcOffset(TimeSpanType rtcOffset); + + /* + Result SetupTimeZoneManager + */ + + + }; +} diff --git a/app/src/main/cpp/skyline/services/timesrv/time_shared_memory.cpp b/app/src/main/cpp/skyline/services/timesrv/time_shared_memory.cpp new file mode 100644 index 00000000..59e71338 --- /dev/null +++ b/app/src/main/cpp/skyline/services/timesrv/time_shared_memory.cpp @@ -0,0 +1,146 @@ +// SPDX-License-Identifier: MPL-2.0 +// Copyright © 2021 Skyline Team and Contributors (https://github.com/skyline-emu/) + +#include "core.h" +#include "time_manager_server.h" + +namespace skyline::service::timesrv::core { + struct __attribute__((packed)) TimeSharedMemoryLayout { + template + struct ClockContextEntry { + u32 updateCount; + u32 _pad_; + std::array context; + }; + + ClockContextEntry standardSteadyClockContextEntry; + ClockContextEntry localSystemClockContextEntry; + ClockContextEntry networkSystemClockContextEntry; + struct __attribute__((packed)) { + u32 updateCount; + std::array enabled; + } standardUserSystemClockAutomaticCorrectionEnabledEntry; + }; + static_assert(offsetof(TimeSharedMemoryLayout, localSystemClockContextEntry) == 0x38); + static_assert(offsetof(TimeSharedMemoryLayout, networkSystemClockContextEntry) == 0x80); + static_assert(offsetof(TimeSharedMemoryLayout, standardUserSystemClockAutomaticCorrectionEnabledEntry) == 0xC8); + + /** + * @brief Time Shared Memory uses a double buffered format that alternates writes context data, this is a helper to simplify that + */ + template + static void UpdateTimeSharedMemoryItem(u32 &updateCount, std::array &item, const T &newValue) { + u32 newCount{updateCount + 1}; + item[newCount & 1] = newValue; + asm volatile("DMB ISHST"); // 0xA + updateCount = newCount; + } + + /** + * @brief Waits for Time Shared Memory to settle then returns the latest version of the requested value + */ + template + static T ReadTimeSharedMemoryItem(u32 &updateCount, std::array &item) { + u32 checkUpdateCount{}; + T out{}; + + do { + checkUpdateCount = updateCount; + out = item[updateCount & 1]; + asm volatile("DMB ISHLD"); // 0x9 + } while (checkUpdateCount != updateCount); + + return out; + } + + namespace constant { + constexpr size_t TimeSharedMemorySize{0x1000}; //!< The size of the time shared memory region + } + + TimeSharedMemory::TimeSharedMemory(const DeviceState &state) : kTimeSharedMemory(std::make_shared(state, constant::TimeSharedMemorySize)), timeSharedMemory(reinterpret_cast(kTimeSharedMemory->kernel.ptr)) {} + + void TimeSharedMemory::SetupStandardSteadyClock(UUID rtcId, TimeSpanType baseTimePoint) { + SteadyClockTimePoint context{ + .timePoint = baseTimePoint.Nanoseconds() - static_cast(util::GetTimeNs()), + .clockSourceId = rtcId + }; + + UpdateTimeSharedMemoryItem(timeSharedMemory->standardSteadyClockContextEntry.updateCount, timeSharedMemory->standardSteadyClockContextEntry.context, context); + } + + void TimeSharedMemory::SetSteadyClockRawTimePoint(TimeSpanType timePoint) { + auto context{ReadTimeSharedMemoryItem(timeSharedMemory->standardSteadyClockContextEntry.updateCount, timeSharedMemory->standardSteadyClockContextEntry.context)}; + context.timePoint = timePoint.Nanoseconds() - static_cast(util::GetTimeNs()); + + UpdateTimeSharedMemoryItem(timeSharedMemory->standardSteadyClockContextEntry.updateCount, timeSharedMemory->standardSteadyClockContextEntry.context, context); + } + + void TimeSharedMemory::UpdateLocalSystemClockContext(const SystemClockContext &context) { + UpdateTimeSharedMemoryItem(timeSharedMemory->localSystemClockContextEntry.updateCount, timeSharedMemory->localSystemClockContextEntry.context, context); + } + + void TimeSharedMemory::UpdateNetworkSystemClockContext(const SystemClockContext &context) { + UpdateTimeSharedMemoryItem(timeSharedMemory->networkSystemClockContextEntry.updateCount, timeSharedMemory->networkSystemClockContextEntry.context, context); + } + + void TimeSharedMemory::SetStandardUserSystemClockAutomaticCorrectionEnabled(bool enabled) { + UpdateTimeSharedMemoryItem(timeSharedMemory->standardUserSystemClockAutomaticCorrectionEnabledEntry.updateCount, timeSharedMemory->standardUserSystemClockAutomaticCorrectionEnabledEntry.enabled, static_cast(enabled)); + } + + bool SystemClockContextUpdateCallback::UpdateBaseContext(const SystemClockContext &newContext) { + if (context && context == newContext) + return false; + + context = newContext; + return true; + + } + + void SystemClockContextUpdateCallback::SignalOperationEvent() { + std::lock_guard lock(mutex); + + for (const auto &event : operationEventList) + event->Signal(); + } + + void SystemClockContextUpdateCallback::AddOperationEvent(const std::shared_ptr &event) { + std::lock_guard lock(mutex); + + operationEventList.push_back(event); + } + + LocalSystemClockUpdateCallback::LocalSystemClockUpdateCallback(TimeSharedMemory &timeSharedMemory) : timeSharedMemory(timeSharedMemory) {} + + Result LocalSystemClockUpdateCallback::UpdateContext(const SystemClockContext &newContext) { + // No need to update shmem state redundantly + if (!UpdateBaseContext(newContext)) + return {}; + + timeSharedMemory.UpdateLocalSystemClockContext(newContext); + + SignalOperationEvent(); + return {}; + } + + NetworkSystemClockUpdateCallback::NetworkSystemClockUpdateCallback(TimeSharedMemory &timeSharedMemory) : timeSharedMemory(timeSharedMemory) {} + + Result NetworkSystemClockUpdateCallback::UpdateContext(const SystemClockContext &newContext) { + // No need to update shmem state redundantly + if (!UpdateBaseContext(newContext)) + return {}; + + timeSharedMemory.UpdateNetworkSystemClockContext(newContext); + + SignalOperationEvent(); + return {}; + } + + Result EphemeralNetworkSystemClockUpdateCallback::UpdateContext(const SystemClockContext &newContext) { + // Avoid signalling the event when there is no change in context + if (!UpdateBaseContext(newContext)) + return {}; + + SignalOperationEvent(); + return {}; + } +} \ No newline at end of file diff --git a/app/src/main/cpp/skyline/services/timesrv/time_shared_memory.h b/app/src/main/cpp/skyline/services/timesrv/time_shared_memory.h new file mode 100644 index 00000000..0680058f --- /dev/null +++ b/app/src/main/cpp/skyline/services/timesrv/time_shared_memory.h @@ -0,0 +1,110 @@ +// SPDX-License-Identifier: MPL-2.0 +// Copyright © 2021 Skyline Team and Contributors (https://github.com/skyline-emu/) + +#pragma once + +#include +#include +#include +#include +#include + +namespace skyline::service::timesrv::core { + struct TimeSharedMemoryLayout; + + /** + * @brief TimeSharedMemory holds context data about clocks in a double buffered format + */ + class TimeSharedMemory { + private: + std::shared_ptr kTimeSharedMemory; + TimeSharedMemoryLayout *timeSharedMemory; + + public: + TimeSharedMemory(const DeviceState &state); + + std::shared_ptr GetSharedMemory() { + return kTimeSharedMemory; + } + + /** + * @brief Fills in the steady clock section of shmem, the current time is subtracted from baseTimePoint to workout the offset + */ + void SetupStandardSteadyClock(UUID rtcId, TimeSpanType baseTimePoint); + + void SetSteadyClockRawTimePoint(TimeSpanType timePoint); + + void UpdateLocalSystemClockContext(const SystemClockContext &context); + + void UpdateNetworkSystemClockContext(const SystemClockContext &context); + + void SetStandardUserSystemClockAutomaticCorrectionEnabled(bool enabled); + }; + + /** + * @brief Base class for callbacks that run after a system clock context is updated + */ + class SystemClockContextUpdateCallback { + private: + std::list> operationEventList; //!< List of KEvents to be signalled when this callback is called + std::mutex mutex; //!< Protects access to operationEventList + std::optional context; //!< The context that used when this callback was last called + + protected: + /** + * @brief Updates the base callback context with the one supplied as an argument + * @return true if the context was updated + */ + bool UpdateBaseContext(const SystemClockContext &newContext); + + /** + * @brief Signals all events in the operation event list + */ + void SignalOperationEvent(); + + public: + /** + * @brief Adds an operation event to be siignalled on context updates + */ + void AddOperationEvent(const std::shared_ptr &event); + + /** + * @brief Repllaces the current context with the supplied one and signals events if the context differs from the last used one + */ + virtual Result UpdateContext(const SystemClockContext &newContext) = 0; + }; + + /** + * @brief Update callback for the local system clock, handles writing data to shmem + */ + class LocalSystemClockUpdateCallback : public SystemClockContextUpdateCallback { + private: + TimeSharedMemory &timeSharedMemory; + + public: + LocalSystemClockUpdateCallback(TimeSharedMemory &timeSharedMemory); + + Result UpdateContext(const SystemClockContext &newContext) override; + }; + + /** + * @brief Update callback for the network system clock, handles writing data to shmem + */ + class NetworkSystemClockUpdateCallback : public SystemClockContextUpdateCallback { + private: + TimeSharedMemory &timeSharedMemory; + + public: + NetworkSystemClockUpdateCallback(TimeSharedMemory &timeSharedMemory); + + Result UpdateContext(const SystemClockContext &newContext) override; + }; + + /** + * @brief Update callback for the ephemeral network system clock, only handles signalling the event as there is no shmem entry for ephemeral + */ + class EphemeralNetworkSystemClockUpdateCallback : public SystemClockContextUpdateCallback { + public: + Result UpdateContext(const SystemClockContext &newContext) override; + }; +} \ No newline at end of file