/** @file | |
* | |
* Implement virtual EFI RealTimeClock runtime services. | |
* | |
* Coypright (c) 2019, Pete Batard <pete@akeo.ie> | |
* Copyright (c) 2018, Andrei Warkentin <andrey.warkentin@gmail.com> | |
* Copyright (c) 2011-2014, ARM Ltd. All rights reserved. | |
* Copyright (c) 2008-2010, Apple Inc. All rights reserved. | |
* Copyright (c) Microsoft Corporation. All rights reserved. | |
* | |
* SPDX-License-Identifier: BSD-2-Clause-Patent | |
* | |
* Based on ArmPlatformPkg/Library/PL031RealTimeClockLib/PL031RealTimeClockLib.inf | |
* | |
**/ | |
#include <PiDxe.h> | |
#include <Library/BaseLib.h> | |
#include <Library/DebugLib.h> | |
#include <Library/IoLib.h> | |
#include <Library/RealTimeClockLib.h> | |
#include <Library/TimerLib.h> | |
#include <Library/TimeBaseLib.h> | |
#include <Library/UefiRuntimeLib.h> | |
STATIC CONST CHAR16 mEpochVariableName[] = L"RtcEpochSeconds"; | |
STATIC CONST CHAR16 mTimeZoneVariableName[] = L"RtcTimeZone"; | |
STATIC CONST CHAR16 mDaylightVariableName[] = L"RtcDaylight"; | |
/** | |
Returns the current time and date information, and the time-keeping capabilities | |
of the virtual RTC. | |
@param Time A pointer to storage to receive a snapshot of the current time. | |
@param Capabilities An optional pointer to a buffer to receive the real time clock | |
device's capabilities. | |
@retval EFI_SUCCESS The operation completed successfully. | |
@retval EFI_INVALID_PARAMETER Time is NULL. | |
@retval EFI_DEVICE_ERROR The time could not be retrieved due to hardware error. | |
**/ | |
EFI_STATUS | |
EFIAPI | |
LibGetTime ( | |
OUT EFI_TIME *Time, | |
OUT EFI_TIME_CAPABILITIES *Capabilities | |
) | |
{ | |
EFI_STATUS Status; | |
INT16 TimeZone; | |
UINT8 Daylight; | |
UINT64 Freq; | |
UINT64 Counter; | |
UINT64 Remainder; | |
UINTN EpochSeconds; | |
UINTN Size; | |
if (Time == NULL) { | |
return EFI_INVALID_PARAMETER; | |
} | |
// Get the counter frequency | |
Freq = GetPerformanceCounterProperties (NULL, NULL); | |
if (Freq == 0) { | |
return EFI_DEVICE_ERROR; | |
} | |
// Get the epoch time from non-volatile storage | |
Size = sizeof (UINTN); | |
EpochSeconds = 0; | |
Status = EfiGetVariable ( | |
(CHAR16 *)mEpochVariableName, | |
&gEfiCallerIdGuid, | |
NULL, | |
&Size, | |
(VOID *)&EpochSeconds | |
); | |
// Fall back to compilation-time epoch if not set | |
if (EFI_ERROR (Status)) { | |
ASSERT(Status != EFI_INVALID_PARAMETER); | |
ASSERT(Status != EFI_BUFFER_TOO_SMALL); | |
// | |
// The following is intended to produce a compilation error on build | |
// environments where BUILD_EPOCH can not be set from inline shell. | |
// If you are attempting to use this library on such an environment, please | |
// contact the edk2 mailing list, so we can try to add support for it. | |
// | |
EpochSeconds = BUILD_EPOCH; | |
DEBUG (( | |
DEBUG_INFO, | |
"LibGetTime: %s non volatile variable was not found - Using compilation time epoch.\n", | |
mEpochVariableName | |
)); | |
} | |
Counter = GetPerformanceCounter (); | |
EpochSeconds += DivU64x64Remainder (Counter, Freq, &Remainder); | |
// Get the current time zone information from non-volatile storage | |
Size = sizeof (TimeZone); | |
Status = EfiGetVariable ( | |
(CHAR16 *)mTimeZoneVariableName, | |
&gEfiCallerIdGuid, | |
NULL, | |
&Size, | |
(VOID *)&TimeZone | |
); | |
if (EFI_ERROR (Status)) { | |
ASSERT(Status != EFI_INVALID_PARAMETER); | |
ASSERT(Status != EFI_BUFFER_TOO_SMALL); | |
if (Status != EFI_NOT_FOUND) { | |
return Status; | |
} | |
// The time zone variable does not exist in non-volatile storage, so create it. | |
Time->TimeZone = EFI_UNSPECIFIED_TIMEZONE; | |
// Store it | |
Status = EfiSetVariable ( | |
(CHAR16 *)mTimeZoneVariableName, | |
&gEfiCallerIdGuid, | |
EFI_VARIABLE_NON_VOLATILE | EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_RUNTIME_ACCESS, | |
Size, | |
(VOID *)&(Time->TimeZone) | |
); | |
if (EFI_ERROR (Status)) { | |
DEBUG (( | |
DEBUG_ERROR, | |
"LibGetTime: Failed to save %s variable to non-volatile storage, Status = %r\n", | |
mTimeZoneVariableName, | |
Status | |
)); | |
return Status; | |
} | |
} else { | |
// Got the time zone | |
Time->TimeZone = TimeZone; | |
// Check TimeZone bounds: -1440 to 1440 or 2047 | |
if (((Time->TimeZone < -1440) || (Time->TimeZone > 1440)) | |
&& (Time->TimeZone != EFI_UNSPECIFIED_TIMEZONE)) { | |
Time->TimeZone = EFI_UNSPECIFIED_TIMEZONE; | |
} | |
// Adjust for the correct time zone | |
if (Time->TimeZone != EFI_UNSPECIFIED_TIMEZONE) { | |
EpochSeconds += Time->TimeZone * SEC_PER_MIN; | |
} | |
} | |
// Get the current daylight information from non-volatile storage | |
Size = sizeof (Daylight); | |
Status = EfiGetVariable ( | |
(CHAR16 *)mDaylightVariableName, | |
&gEfiCallerIdGuid, | |
NULL, | |
&Size, | |
(VOID *)&Daylight | |
); | |
if (EFI_ERROR (Status)) { | |
ASSERT(Status != EFI_INVALID_PARAMETER); | |
ASSERT(Status != EFI_BUFFER_TOO_SMALL); | |
if (Status != EFI_NOT_FOUND) { | |
return Status; | |
} | |
// The daylight variable does not exist in non-volatile storage, so create it. | |
Time->Daylight = 0; | |
// Store it | |
Status = EfiSetVariable ( | |
(CHAR16 *)mDaylightVariableName, | |
&gEfiCallerIdGuid, | |
EFI_VARIABLE_NON_VOLATILE | EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_RUNTIME_ACCESS, | |
Size, | |
(VOID *)&(Time->Daylight) | |
); | |
if (EFI_ERROR (Status)) { | |
DEBUG (( | |
DEBUG_ERROR, | |
"LibGetTime: Failed to save %s variable to non-volatile storage, Status = %r\n", | |
mDaylightVariableName, | |
Status | |
)); | |
return Status; | |
} | |
} else { | |
// Got the daylight information | |
Time->Daylight = Daylight; | |
// Adjust for the correct period | |
if ((Time->Daylight & EFI_TIME_IN_DAYLIGHT) == EFI_TIME_IN_DAYLIGHT) { | |
// Convert to adjusted time, i.e. spring forwards one hour | |
EpochSeconds += SEC_PER_HOUR; | |
} | |
} | |
EpochToEfiTime (EpochSeconds, Time); | |
// Because we use the performance counter, we can fill the Nanosecond attribute | |
// provided that the remainder doesn't overflow 64-bit during multiplication. | |
if (Remainder <= 18446744073U) { | |
Time->Nanosecond = (UINT32)(MultU64x64 (Remainder, 1000000000U) / Freq); | |
} else { | |
DEBUG ((DEBUG_WARN, "LibGetTime: Nanosecond value not set (64-bit overflow).\n")); | |
} | |
if (Capabilities) { | |
Capabilities->Accuracy = 0; | |
Capabilities->Resolution = 1; | |
Capabilities->SetsToZero = FALSE; | |
} | |
return EFI_SUCCESS; | |
} | |
/** | |
Sets the current local time and date information. | |
@param Time A pointer to the current time. | |
@retval EFI_SUCCESS The operation completed successfully. | |
@retval EFI_INVALID_PARAMETER A time field is out of range. | |
@retval EFI_DEVICE_ERROR The time could not be set due due to hardware error. | |
**/ | |
EFI_STATUS | |
EFIAPI | |
LibSetTime ( | |
IN EFI_TIME *Time | |
) | |
{ | |
EFI_STATUS Status; | |
UINT64 Freq; | |
UINT64 Counter; | |
UINT64 Remainder; | |
UINTN EpochSeconds; | |
if (!IsTimeValid (Time)) { | |
return EFI_INVALID_PARAMETER; | |
} | |
EpochSeconds = EfiTimeToEpoch (Time); | |
// Adjust for the correct time zone, i.e. convert to UTC time zone | |
if ((Time->TimeZone != EFI_UNSPECIFIED_TIMEZONE) | |
&& (EpochSeconds > Time->TimeZone * SEC_PER_MIN)) { | |
EpochSeconds -= Time->TimeZone * SEC_PER_MIN; | |
} | |
// Adjust for the correct period | |
if (((Time->Daylight & EFI_TIME_IN_DAYLIGHT) == EFI_TIME_IN_DAYLIGHT) | |
&& (EpochSeconds > SEC_PER_HOUR)) { | |
// Convert to un-adjusted time, i.e. fall back one hour | |
EpochSeconds -= SEC_PER_HOUR; | |
} | |
// Use the performance counter to subtract the number of seconds | |
// since platform reset. Without this, setting time from the shell | |
// and immediately reading it back would result in a forward time | |
// offset, of the duration during which the platform has been up. | |
Freq = GetPerformanceCounterProperties (NULL, NULL); | |
if (Freq != 0) { | |
Counter = GetPerformanceCounter (); | |
if (EpochSeconds > DivU64x64Remainder (Counter, Freq, &Remainder)) { | |
EpochSeconds -= DivU64x64Remainder (Counter, Freq, &Remainder); | |
} | |
} | |
// Save the current time zone information into non-volatile storage | |
Status = EfiSetVariable ( | |
(CHAR16 *)mTimeZoneVariableName, | |
&gEfiCallerIdGuid, | |
EFI_VARIABLE_NON_VOLATILE | EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_RUNTIME_ACCESS, | |
sizeof (Time->TimeZone), | |
(VOID *)&(Time->TimeZone) | |
); | |
if (EFI_ERROR (Status)) { | |
DEBUG (( | |
DEBUG_ERROR, | |
"LibSetTime: Failed to save %s variable to non-volatile storage, Status = %r\n", | |
mTimeZoneVariableName, | |
Status | |
)); | |
return Status; | |
} | |
// Save the current daylight information into non-volatile storage | |
Status = EfiSetVariable ( | |
(CHAR16 *)mDaylightVariableName, | |
&gEfiCallerIdGuid, | |
EFI_VARIABLE_NON_VOLATILE | EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_RUNTIME_ACCESS, | |
sizeof(Time->Daylight), | |
(VOID *)&(Time->Daylight) | |
); | |
if (EFI_ERROR (Status)) { | |
DEBUG (( | |
DEBUG_ERROR, | |
"LibSetTime: Failed to save %s variable to non-volatile storage, Status = %r\n", | |
mDaylightVariableName, | |
Status | |
)); | |
return Status; | |
} | |
Status = EfiSetVariable ( | |
(CHAR16 *)mEpochVariableName, | |
&gEfiCallerIdGuid, | |
EFI_VARIABLE_NON_VOLATILE | EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_RUNTIME_ACCESS, | |
sizeof (EpochSeconds), | |
&EpochSeconds | |
); | |
if (EFI_ERROR (Status)) { | |
DEBUG (( | |
DEBUG_ERROR, | |
"LibSetTime: Failed to save %s variable to non-volatile storage, Status = %r\n", | |
mDaylightVariableName, | |
Status | |
)); | |
return Status; | |
} | |
return EFI_SUCCESS; | |
} | |
/** | |
Returns the current wakeup alarm clock setting. | |
@param Enabled Indicates if the alarm is currently enabled or disabled. | |
@param Pending Indicates if the alarm signal is pending and requires acknowledgement. | |
@param Time The current alarm setting. | |
@retval EFI_SUCCESS The alarm settings were returned. | |
@retval EFI_INVALID_PARAMETER Any parameter is NULL. | |
@retval EFI_DEVICE_ERROR The wakeup time could not be retrieved due to a hardware error. | |
**/ | |
EFI_STATUS | |
EFIAPI | |
LibGetWakeupTime ( | |
OUT BOOLEAN *Enabled, | |
OUT BOOLEAN *Pending, | |
OUT EFI_TIME *Time | |
) | |
{ | |
return EFI_UNSUPPORTED; | |
} | |
/** | |
Sets the system wakeup alarm clock time. | |
@param Enabled Enable or disable the wakeup alarm. | |
@param Time If Enable is TRUE, the time to set the wakeup alarm for. | |
@retval EFI_SUCCESS If Enable is TRUE, then the wakeup alarm was enabled. If | |
Enable is FALSE, then the wakeup alarm was disabled. | |
@retval EFI_INVALID_PARAMETER A time field is out of range. | |
@retval EFI_DEVICE_ERROR The wakeup time could not be set due to a hardware error. | |
@retval EFI_UNSUPPORTED A wakeup timer is not supported on this platform. | |
**/ | |
EFI_STATUS | |
EFIAPI | |
LibSetWakeupTime ( | |
IN BOOLEAN Enabled, | |
OUT EFI_TIME *Time | |
) | |
{ | |
return EFI_UNSUPPORTED; | |
} | |
/** | |
This is the declaration of an EFI image entry point. This can be the entry point to an application | |
written to this specification, an EFI boot service driver, or an EFI runtime driver. | |
@param ImageHandle Handle that identifies the loaded image. | |
@param SystemTable System Table for this image. | |
@retval EFI_SUCCESS The operation completed successfully. | |
**/ | |
EFI_STATUS | |
EFIAPI | |
LibRtcInitialize ( | |
IN EFI_HANDLE ImageHandle, | |
IN EFI_SYSTEM_TABLE *SystemTable | |
) | |
{ | |
return EFI_SUCCESS; | |
} | |
/** | |
Fixup internal data so that EFI can be call in virtual mode. | |
Call the passed in Child Notify event and convert any pointers in | |
lib to virtual mode. | |
@param[in] Event The Event that is being processed | |
@param[in] Context Event Context | |
**/ | |
VOID | |
EFIAPI | |
LibRtcVirtualNotifyEvent ( | |
IN EFI_EVENT Event, | |
IN VOID *Context | |
) | |
{ | |
return; | |
} |