Implement the entirety of time services

This reimplements our time backend to be significantly more accurate to
the real PSC and provides complete implementations for every time IPC
allowing many newer games to work properly.

Time is unique in its use of glue services, the core sysmodule is fully
isolated and doesn't interface with any other services. Glue is instead
used where that is needed (e.g. for fetching settings), this distinction
is also present in our implementation.

Another unique feature of time is its global state, as time is
calibrated from the start of the service its state cannot be lost as
that would result in the application offsetting time incorrectly
whenever it closed a session.

A large proportion of this is based off of Thog's 9.0.0 PSC reversing.
This commit is contained in:
Billy Laws 2021-02-20 14:27:50 +00:00 committed by ◱ Mark
parent cb9f5f144e
commit 34cb0b49e8
23 changed files with 1810 additions and 107 deletions

View File

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

View File

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

View File

@ -0,0 +1,95 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
#include <kernel/types/KProcess.h>
#include <services/timesrv/IStaticService.h>
#include <services/timesrv/results.h>
#include "IStaticService.h"
namespace skyline::service::glue {
IStaticService::IStaticService(const DeviceState &state, ServiceManager &manager, std::shared_ptr<timesrv::IStaticService> 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<timesrv::PosixTime>(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);
}
}

View File

@ -0,0 +1,82 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
#pragma once
#include <services/serviceman.h>
#include <services/timesrv/IStaticService.h>
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<timesrv::IStaticService> core;
timesrv::StaticServicePermissions permissions;
public:
IStaticService(const DeviceState &state, ServiceManager &manager, std::shared_ptr<timesrv::IStaticService> 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),
)
};
}

View File

@ -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<ServiceName>(name): { \
std::shared_ptr<BaseService> serviceObject = std::make_shared<class>(state, *this); \
std::shared_ptr<BaseService> serviceObject = std::make_shared<class>(state, *this __VA_OPT__(,) __VA_ARGS__); \
serviceMap[util::MakeMagic<ServiceName>(name)] = serviceObject; \
return serviceObject; \
}
namespace skyline::service {
ServiceManager::ServiceManager(const DeviceState &state) : state(state), smUserInterface(std::make_shared<sm::IUserInterface>(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<sm::IUserInterface>(state, *this)), globalServiceState(std::make_shared<GlobalServiceState>(state)) {}
std::shared_ptr<BaseService> 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")

View File

@ -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<BaseService> smUserInterface; //!< Used by applications to open connections to services
std::shared_ptr<GlobalServiceState> globalServiceState;
ServiceManager(const DeviceState &state);

View File

@ -1,26 +1,29 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
#include <kernel/types/KProcess.h>
#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<ISystemClock>(SystemClockType::User, state, manager), session, response);
manager.RegisterService(std::make_shared<ISystemClock>(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<ISystemClock>(SystemClockType::Network, state, manager), session, response);
manager.RegisterService(std::make_shared<ISystemClock>(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<ISteadyClock>(state, manager), session, response);
manager.RegisterService(std::make_shared<ISteadyClock>(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<ISystemClock>(SystemClockType::Local, state, manager), session, response);
manager.RegisterService(std::make_shared<ISystemClock>(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<ISystemClock>(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<type::KSharedMemory>(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<u8>(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<u8>());
}
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<u8>(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<SystemClockContext>()};
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<ClockSnapshot> 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<u8>()};
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<ClockSnapshot>() = *snapshot;
return {};
}
Result IStaticService::GetClockSnapshotFromSystemClockContext(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
auto unk{request.Pop<u8>()};
request.Skip<std::array<u8, 7>>();
auto userContext{request.Pop<SystemClockContext>()};
auto networkContext{request.Pop<SystemClockContext>()};
auto snapshot{GetClockSnapshotFromSystemClockContextImpl(userContext, networkContext, unk)};
if (!snapshot)
return snapshot;
request.outputBuf.at(0).as<ClockSnapshot>() = *snapshot;
return {};
}
Result IStaticService::CalculateStandardUserSystemClockDifferenceByUser(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
auto snapshotA{request.inputBuf.at(0).as<ClockSnapshot>()};
auto snapshotB{request.inputBuf.at(1).as<ClockSnapshot>()};
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<i64>(difference.Nanoseconds());
return {};
}
Result IStaticService::CalculateSpanBetween(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
auto snapshotA{request.inputBuf.at(0).as<ClockSnapshot>()};
auto snapshotB{request.inputBuf.at(1).as<ClockSnapshot>()};
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;
}
}
}

View File

@ -4,47 +4,139 @@
#pragma once
#include <services/serviceman.h>
#include <services/timesrv/common.h>
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<ClockSnapshot> 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),
)
};
}
}

View File

@ -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<u64>(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<TimeSpanType>()};
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<u8>(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 {};
}
}

View File

@ -6,29 +6,45 @@
#include <services/serviceman.h>
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),
)
};
}

View File

@ -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 <kernel/types/KProcess.h>
#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<u64>(static_cast<u64>(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<PosixTime>());
}
Result ISystemClock::GetSystemClockContext(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
response.Push<u64>(static_cast<u64>(std::time(nullptr)));
response.Push(SteadyClockTimePoint{static_cast<u64>(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<SystemClockContext>());
}
Result ISystemClock::GetOperationEventReadableHandle(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
if (!operationEvent) {
operationEvent = std::make_shared<kernel::type::KEvent>(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 {};
}
}

View File

@ -3,41 +3,45 @@
#pragma once
#include <kernel/types/KEvent.h>
#include <services/serviceman.h>
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<kernel::type::KEvent> 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),
)
};
}

View File

@ -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<u64>()};
auto calender{*std::gmtime(reinterpret_cast<const time_t *>(&time))};
auto time{static_cast<time_t>(request.Pop<PosixTime>())};
auto calender{*std::gmtime(&time)};
CalendarTime calendarTime{
.year = static_cast<u16>(calender.tm_year),
@ -20,11 +21,11 @@ namespace skyline::service::timesrv {
response.Push(calendarTime);
CalendarAdditionalInfo calendarInfo{
.dayWeek = static_cast<u32>(calender.tm_wday),
.dayMonth = static_cast<u32>(calender.tm_mday),
.tzName = *reinterpret_cast<const u64 *>(calender.tm_zone),
.dst = static_cast<i32>(calender.tm_isdst),
.utcRel = static_cast<u32>(calender.tm_gmtoff),
.dayOfWeek = static_cast<u32>(calender.tm_wday),
.dayOfYear = static_cast<u32>(calender.tm_yday),
.timezoneName = "GMT",
.dst = static_cast<u32>(calender.tm_isdst),
.gmtOffset = static_cast<i32>(calender.tm_gmtoff),
};
response.Push(calendarInfo);
return {};

View File

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

View File

@ -0,0 +1,24 @@
#include <common.h>
#include "results.h"
#include "common.h"
namespace skyline::service::timesrv {
ResultValue<i64> ClockSnapshot::GetCurrentTime(const SteadyClockTimePoint &timePoint, const SystemClockContext &context) {
if (context.timestamp.clockSourceId != timePoint.clockSourceId)
return result::ClockSourceIdMismatch;
return context.offset + timePoint.timePoint;
}
ResultValue<i64> 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<i64>::min() + start.timePoint)) ||
((start.timePoint < 0) && (end.timePoint > std::numeric_limits<i64>::max() + start.timePoint)))
return result::CompareOverflow;
return end.timePoint - start.timePoint;
}
}

View File

@ -0,0 +1,135 @@
#pragma once
#include <common.h>
#include <common/uuid.h>
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<i64>(skyline::constant::NsInSecond)};
}
static constexpr TimeSpanType FromDays(i64 d) {
return {d * static_cast<i64>(skyline::constant::NsInDay)};
}
constexpr i64 Nanoseconds() const {
return ns;
}
constexpr i64 Seconds() const {
return ns / static_cast<i64>(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<u8, 8> 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<u8, 36> locationName;
u8 automaticCorrectionEnabled;
u8 _unk_;
u16 version;
/**
* @brief Gets the current time based off of the supplied timepoint and context
*/
static ResultValue<PosixTime> GetCurrentTime(const SteadyClockTimePoint &timePoint, const SystemClockContext &context);
};
static_assert(sizeof(ClockSnapshot) == 0xD0);
/**
* @brief Gets the time between a pair of steady clock timepoints
*/
ResultValue<i64> GetSpanBetween(const SteadyClockTimePoint &start, const SteadyClockTimePoint &end);
}

View File

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

View File

@ -0,0 +1,318 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
#pragma once
#include <common.h>
#include <common/uuid.h>
#include <kernel/types/KEvent.h>
#include <kernel/types/KSharedMemory.h>
#include <services/timesrv/common.h>
#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<SteadyClockTimePoint> 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<SteadyClockTimePoint> 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<PosixTime> 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<SteadyClockTimePoint> 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<SteadyClockTimePoint> 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<kernel::type::KEvent> &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<PosixTime> GetCurrentTime();
virtual ResultValue<SystemClockContext> 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<kernel::type::KEvent> automaticCorrectionUpdatedEvent;
StandardUserSystemClockCore(const DeviceState &state, StandardSteadyClockCore &standardSteadyClock, StandardLocalSystemClockCore &localSystemClock, StandardNetworkSystemClockCore &networkSystemClock, TimeSharedMemory &timeSharedMemory) : SystemClockCore(standardSteadyClock), localSystemClock(localSystemClock), networkSystemClock(networkSystemClock), automaticCorrectionUpdatedEvent(std::make_shared<kernel::type::KEvent>(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<SystemClockContext> 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);
};
}

View File

@ -0,0 +1,15 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright © 2021 Skyline Team and Contributors (https://github.com/skyline-emu/)
#pragma once
#include <common.h>
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);
}

View File

@ -0,0 +1,69 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
#include <common.h>
#include <services/serviceman.h>
#include "core.h"
#include "time_manager_server.h"
namespace skyline::service::timesrv {
TimeManagerServer::TimeManagerServer(core::TimeServiceObject &core) : core(core) {}
std::shared_ptr<IStaticService> TimeManagerServer::GetUserStaticService(const DeviceState &state, ServiceManager &manager) {
return std::make_shared<IStaticService>(state, manager, core, constant::StaticServiceUserPermissions);
}
std::shared_ptr<IStaticService> TimeManagerServer::GetAdminStaticService(const DeviceState &state, ServiceManager &manager) {
return std::make_shared<IStaticService>(state, manager, core, constant::StaticServiceAdminPermissions);
}
std::shared_ptr<IStaticService> TimeManagerServer::GetRepairStaticService(const DeviceState &state, ServiceManager &manager) {
return std::make_shared<IStaticService>(state, manager, core, constant::StaticServiceRepairPermissions);
}
std::shared_ptr<IStaticService> TimeManagerServer::GetManagerStaticService(const DeviceState &state, ServiceManager &manager) {
return std::make_shared<IStaticService>(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<kernel::type::KEvent> TimeManagerServer::GetStandardUserSystemClockAutomaticCorrectionEvent() {
return core.userSystemClock.automaticCorrectionUpdatedEvent;
}
Result TimeManagerServer::SetStandardSteadyClockRtcOffset(TimeSpanType rtcOffset) {
core.standardSteadyClock.SetRtcOffset(rtcOffset);
core.timeSharedMemory.SetSteadyClockRawTimePoint(core.standardSteadyClock.GetCurrentRawTimePoint());
return {};
}
}

View File

@ -0,0 +1,52 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
#pragma once
#include <common.h>
#include <common/uuid.h>
#include <kernel/types/KEvent.h>
#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<IStaticService> GetUserStaticService(const DeviceState &state, ServiceManager &manager);
std::shared_ptr<IStaticService> GetAdminStaticService(const DeviceState &state, ServiceManager &manager);
std::shared_ptr<IStaticService> GetRepairStaticService(const DeviceState &state, ServiceManager &manager);
std::shared_ptr<IStaticService> 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<kernel::type::KEvent> GetStandardUserSystemClockAutomaticCorrectionEvent();
Result SetStandardSteadyClockRtcOffset(TimeSpanType rtcOffset);
/*
Result SetupTimeZoneManager
*/
};
}

View File

@ -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<typename T>
struct ClockContextEntry {
u32 updateCount;
u32 _pad_;
std::array<T, 2> context;
};
ClockContextEntry<SteadyClockTimePoint> standardSteadyClockContextEntry;
ClockContextEntry<SystemClockContext> localSystemClockContextEntry;
ClockContextEntry<SystemClockContext> networkSystemClockContextEntry;
struct __attribute__((packed)) {
u32 updateCount;
std::array<u8, 2> 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<typename T>
static void UpdateTimeSharedMemoryItem(u32 &updateCount, std::array<T, 2> &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<typename T>
static T ReadTimeSharedMemoryItem(u32 &updateCount, std::array<T, 2> &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<kernel::type::KSharedMemory>(state, constant::TimeSharedMemorySize)), timeSharedMemory(reinterpret_cast<TimeSharedMemoryLayout *>(kTimeSharedMemory->kernel.ptr)) {}
void TimeSharedMemory::SetupStandardSteadyClock(UUID rtcId, TimeSpanType baseTimePoint) {
SteadyClockTimePoint context{
.timePoint = baseTimePoint.Nanoseconds() - static_cast<i64>(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<i64>(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<u8>(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<kernel::type::KEvent> &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 {};
}
}

View File

@ -0,0 +1,110 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright © 2021 Skyline Team and Contributors (https://github.com/skyline-emu/)
#pragma once
#include <common.h>
#include <common/uuid.h>
#include <kernel/types/KEvent.h>
#include <kernel/types/KSharedMemory.h>
#include <services/timesrv/common.h>
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<kernel::type::KSharedMemory> kTimeSharedMemory;
TimeSharedMemoryLayout *timeSharedMemory;
public:
TimeSharedMemory(const DeviceState &state);
std::shared_ptr<kernel::type::KSharedMemory> 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<std::shared_ptr<kernel::type::KEvent>> operationEventList; //!< List of KEvents to be signalled when this callback is called
std::mutex mutex; //!< Protects access to operationEventList
std::optional<SystemClockContext> 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<kernel::type::KEvent> &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;
};
}