243 lines
9.3 KiB
C++
Executable File
243 lines
9.3 KiB
C++
Executable File
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
#include <chrono>
|
|
|
|
#include "core/core.h"
|
|
#include "core/core_timing.h"
|
|
|
|
#include "core/file_sys/vfs.h"
|
|
#include "core/hle/kernel/svc.h"
|
|
#include "core/hle/service/glue/time/manager.h"
|
|
#include "core/hle/service/glue/time/time_zone_binary.h"
|
|
#include "core/hle/service/psc/time/service_manager.h"
|
|
#include "core/hle/service/psc/time/static.h"
|
|
#include "core/hle/service/psc/time/system_clock.h"
|
|
#include "core/hle/service/psc/time/time_zone_service.h"
|
|
#include "core/hle/service/set/system_settings_server.h"
|
|
#include "core/hle/service/sm/sm.h"
|
|
|
|
namespace Service::Glue::Time {
|
|
namespace {
|
|
|
|
template <typename T>
|
|
T GetSettingsItemValue(std::shared_ptr<Service::Set::ISystemSettingsServer>& set_sys,
|
|
const char* category, const char* name) {
|
|
std::vector<u8> interval_buf;
|
|
auto res = set_sys->GetSettingsItemValue(interval_buf, category, name);
|
|
ASSERT(res == ResultSuccess);
|
|
|
|
T v{};
|
|
std::memcpy(&v, interval_buf.data(), sizeof(T));
|
|
return v;
|
|
}
|
|
|
|
s64 CalendarTimeToEpoch(Service::PSC::Time::CalendarTime calendar) {
|
|
constexpr auto is_leap = [](s32 year) -> bool {
|
|
return (((year) % 4) == 0 && (((year) % 100) != 0 || ((year) % 400) == 0));
|
|
};
|
|
constexpr std::array<s32, 12> MonthStartDayOfYear{
|
|
0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334,
|
|
};
|
|
|
|
s16 month_s16{calendar.month};
|
|
s8 month{static_cast<s8>(((month_s16 * 43) & ~std::numeric_limits<s16>::max()) +
|
|
((month_s16 * 43) >> 9))};
|
|
s8 month_index{static_cast<s8>(calendar.month - 12 * month)};
|
|
if (month_index == 0) {
|
|
month_index = 12;
|
|
}
|
|
s32 year{(month + calendar.year) - !month_index};
|
|
s32 v8{year >= 0 ? year : year + 3};
|
|
|
|
s64 days_since_epoch = calendar.day + MonthStartDayOfYear[month_index - 1];
|
|
days_since_epoch += (year * 365) + (v8 / 4) - (year / 100) + (year / 400) - 365;
|
|
|
|
if (month_index <= 2 && is_leap(year)) {
|
|
days_since_epoch--;
|
|
}
|
|
auto epoch_s{((24ll * days_since_epoch + calendar.hour) * 60ll + calendar.minute) * 60ll +
|
|
calendar.second};
|
|
return epoch_s - 62135683200ll;
|
|
}
|
|
|
|
s64 GetEpochTimeFromInitialYear(std::shared_ptr<Service::Set::ISystemSettingsServer>& set_sys) {
|
|
Service::PSC::Time::CalendarTime calendar{
|
|
.year = GetSettingsItemValue<s16>(set_sys, "time", "standard_user_clock_initial_year"),
|
|
.month = 1,
|
|
.day = 1,
|
|
.hour = 0,
|
|
.minute = 0,
|
|
.second = 0,
|
|
};
|
|
return CalendarTimeToEpoch(calendar);
|
|
}
|
|
} // namespace
|
|
|
|
TimeManager::TimeManager(Core::System& system)
|
|
: m_steady_clock_resource{system}, m_worker{system, m_steady_clock_resource,
|
|
m_file_timestamp_worker} {
|
|
m_time_m =
|
|
system.ServiceManager().GetService<Service::PSC::Time::ServiceManager>("time:m", true);
|
|
|
|
auto res = m_time_m->GetStaticServiceAsServiceManager(m_time_sm);
|
|
ASSERT(res == ResultSuccess);
|
|
|
|
m_set_sys =
|
|
system.ServiceManager().GetService<Service::Set::ISystemSettingsServer>("set:sys", true);
|
|
|
|
ResetTimeZoneBinary();
|
|
res = MountTimeZoneBinary(system);
|
|
ASSERT(res == ResultSuccess);
|
|
|
|
m_worker.Initialize(m_time_sm, m_set_sys);
|
|
|
|
res = m_time_sm->GetStandardUserSystemClock(m_file_timestamp_worker.m_system_clock);
|
|
ASSERT(res == ResultSuccess);
|
|
|
|
res = m_time_sm->GetTimeZoneService(m_file_timestamp_worker.m_time_zone);
|
|
ASSERT(res == ResultSuccess);
|
|
|
|
res = SetupStandardSteadyClockCore();
|
|
ASSERT(res == ResultSuccess);
|
|
|
|
Service::PSC::Time::SystemClockContext user_clock_context{};
|
|
res = m_set_sys->GetUserSystemClockContext(user_clock_context);
|
|
ASSERT(res == ResultSuccess);
|
|
|
|
// TODO this clock should initialise with this epoch time, and be updated somewhere else on
|
|
// first boot, but I haven't been able to find that point (likely via ntc's auto correct as it's
|
|
// defaulted to be enabled), and to get correct times we need to initialise with the current
|
|
// time instead.
|
|
auto epoch_time{GetEpochTimeFromInitialYear(m_set_sys)};
|
|
if (user_clock_context == Service::PSC::Time::SystemClockContext{}) {
|
|
m_steady_clock_resource.GetRtcTimeInSeconds(epoch_time);
|
|
}
|
|
|
|
res = m_time_m->SetupStandardLocalSystemClockCore(user_clock_context, epoch_time);
|
|
ASSERT(res == ResultSuccess);
|
|
|
|
Service::PSC::Time::SystemClockContext network_clock_context{};
|
|
res = m_set_sys->GetNetworkSystemClockContext(network_clock_context);
|
|
ASSERT(res == ResultSuccess);
|
|
|
|
auto network_accuracy_m{GetSettingsItemValue<s32>(
|
|
m_set_sys, "time", "standard_network_clock_sufficient_accuracy_minutes")};
|
|
auto one_minute_ns{
|
|
std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::minutes(1)).count()};
|
|
s64 network_accuracy_ns{network_accuracy_m * one_minute_ns};
|
|
|
|
res = m_time_m->SetupStandardNetworkSystemClockCore(network_clock_context, network_accuracy_ns);
|
|
ASSERT(res == ResultSuccess);
|
|
|
|
bool is_automatic_correction_enabled{};
|
|
res = m_set_sys->IsUserSystemClockAutomaticCorrectionEnabled(is_automatic_correction_enabled);
|
|
ASSERT(res == ResultSuccess);
|
|
|
|
Service::PSC::Time::SteadyClockTimePoint automatic_correction_time_point{};
|
|
res = m_set_sys->GetUserSystemClockAutomaticCorrectionUpdatedTime(
|
|
automatic_correction_time_point);
|
|
ASSERT(res == ResultSuccess);
|
|
|
|
res = m_time_m->SetupStandardUserSystemClockCore(automatic_correction_time_point,
|
|
is_automatic_correction_enabled);
|
|
ASSERT(res == ResultSuccess);
|
|
|
|
res = m_time_m->SetupEphemeralNetworkSystemClockCore();
|
|
ASSERT(res == ResultSuccess);
|
|
|
|
res = SetupTimeZoneServiceCore();
|
|
ASSERT(res == ResultSuccess);
|
|
|
|
s64 rtc_time_s{};
|
|
res = m_steady_clock_resource.GetRtcTimeInSeconds(rtc_time_s);
|
|
ASSERT(res == ResultSuccess);
|
|
|
|
// TODO system report "launch"
|
|
// "rtc_reset" = m_steady_clock_resource.m_rtc_reset
|
|
// "rtc_value" = rtc_time_s
|
|
|
|
m_worker.StartThread();
|
|
|
|
m_file_timestamp_worker.m_initialized = true;
|
|
|
|
s64 system_clock_time{};
|
|
if (m_file_timestamp_worker.m_system_clock->GetCurrentTime(system_clock_time) ==
|
|
ResultSuccess) {
|
|
Service::PSC::Time::CalendarTime calendar_time{};
|
|
Service::PSC::Time::CalendarAdditionalInfo calendar_additional{};
|
|
if (m_file_timestamp_worker.m_time_zone->ToCalendarTimeWithMyRule(
|
|
calendar_time, calendar_additional, system_clock_time) == ResultSuccess) {
|
|
// TODO IFileSystemProxy::SetCurrentPosixTime(system_clock_time,
|
|
// calendar_additional.ut_offset)
|
|
}
|
|
}
|
|
}
|
|
|
|
Result TimeManager::SetupStandardSteadyClockCore() {
|
|
Common::UUID external_clock_source_id{};
|
|
auto res = m_set_sys->GetExternalSteadyClockSourceId(external_clock_source_id);
|
|
ASSERT(res == ResultSuccess);
|
|
|
|
s64 external_steady_clock_internal_offset_s{};
|
|
res = m_set_sys->GetExternalSteadyClockInternalOffset(external_steady_clock_internal_offset_s);
|
|
ASSERT(res == ResultSuccess);
|
|
|
|
auto one_second_ns{
|
|
std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::seconds(1)).count()};
|
|
s64 external_steady_clock_internal_offset_ns{external_steady_clock_internal_offset_s *
|
|
one_second_ns};
|
|
|
|
s32 standard_steady_clock_test_offset_m{
|
|
GetSettingsItemValue<s32>(m_set_sys, "time", "standard_steady_clock_test_offset_minutes")};
|
|
auto one_minute_ns{
|
|
std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::minutes(1)).count()};
|
|
s64 standard_steady_clock_test_offset_ns{standard_steady_clock_test_offset_m * one_minute_ns};
|
|
|
|
auto reset_detected = m_steady_clock_resource.GetResetDetected();
|
|
if (reset_detected) {
|
|
external_clock_source_id = {};
|
|
}
|
|
|
|
Common::UUID clock_source_id{};
|
|
m_steady_clock_resource.Initialize(&clock_source_id, &external_clock_source_id);
|
|
|
|
if (clock_source_id != external_clock_source_id) {
|
|
m_set_sys->SetExternalSteadyClockSourceId(clock_source_id);
|
|
}
|
|
|
|
res = m_time_m->SetupStandardSteadyClockCore(clock_source_id, m_steady_clock_resource.GetTime(),
|
|
external_steady_clock_internal_offset_ns,
|
|
standard_steady_clock_test_offset_ns,
|
|
reset_detected);
|
|
ASSERT(res == ResultSuccess);
|
|
R_SUCCEED();
|
|
}
|
|
|
|
Result TimeManager::SetupTimeZoneServiceCore() {
|
|
Service::PSC::Time::LocationName name{};
|
|
auto res = m_set_sys->GetDeviceTimeZoneLocationName(name);
|
|
ASSERT(res == ResultSuccess);
|
|
|
|
Service::PSC::Time::SteadyClockTimePoint time_point{};
|
|
res = m_set_sys->GetDeviceTimeZoneLocationUpdatedTime(time_point);
|
|
ASSERT(res == ResultSuccess);
|
|
|
|
auto location_count = GetTimeZoneCount();
|
|
Service::PSC::Time::RuleVersion rule_version{};
|
|
GetTimeZoneVersion(rule_version);
|
|
|
|
std::span<const u8> rule_buffer{};
|
|
size_t rule_size{};
|
|
res = GetTimeZoneRule(rule_buffer, rule_size, name);
|
|
ASSERT(res == ResultSuccess);
|
|
|
|
res = m_time_m->SetupTimeZoneServiceCore(name, time_point, rule_version, location_count,
|
|
rule_buffer);
|
|
ASSERT(res == ResultSuccess);
|
|
|
|
R_SUCCEED();
|
|
}
|
|
|
|
} // namespace Service::Glue::Time
|