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