From: "Nhi Pham" <nhi@os.amperecomputing.com>
To: devel@edk2.groups.io
Cc: Vu Nguyen <vunguyen@os.amperecomputing.com>
Subject: [edk2-platforms][PATCH 05/34] JadePkg: Implement RealTimeClockLib for PCF85063
Date: Wed, 9 Dec 2020 16:25:02 +0700 [thread overview]
Message-ID: <20201209092531.30867-6-nhi@os.amperecomputing.com> (raw)
In-Reply-To: <20201209092531.30867-1-nhi@os.amperecomputing.com>
From: Vu Nguyen <vunguyen@os.amperecomputing.com>
This library supports to retrieve and update system datetime over real
RTC PCF85063 device on Jade board instead of using emulator RTC.
Also, timezone is set to UTC as default.
Signed-off-by: Vu Nguyen <vunguyen@os.amperecomputing.com>
---
Silicon/Ampere/AmpereAltraPkg/Ac01Pkg.dsc.inc | 4 +-
Platform/Ampere/JadePkg/Jade.dsc | 6 +
Platform/Ampere/JadePkg/Library/PCF85063RealTimeClockLib/PCF85063.inf | 55 ++
Platform/Ampere/JadePkg/Library/PCF85063RealTimeClockLib/PCF85063.h | 86 +++
Platform/Ampere/JadePkg/Library/PCF85063RealTimeClockLib/PCF85063.c | 288 ++++++++++
Platform/Ampere/JadePkg/Library/PCF85063RealTimeClockLib/RtcSystemLib.c | 603 ++++++++++++++++++++
6 files changed, 1040 insertions(+), 2 deletions(-)
diff --git a/Silicon/Ampere/AmpereAltraPkg/Ac01Pkg.dsc.inc b/Silicon/Ampere/AmpereAltraPkg/Ac01Pkg.dsc.inc
index f2422156d1c3..4dbc7dc35bc7 100755
--- a/Silicon/Ampere/AmpereAltraPkg/Ac01Pkg.dsc.inc
+++ b/Silicon/Ampere/AmpereAltraPkg/Ac01Pkg.dsc.inc
@@ -88,10 +88,10 @@ [LibraryClasses.common]
PMProLib|Silicon/Ampere/AmpereAltraPkg/Library/PMProLib/PMProLib.inf
AmpereCpuLib|Silicon/Ampere/AmpereAltraPkg/Library/AmpereCpuLib/AmpereCpuLib.inf
TimeBaseLib|EmbeddedPkg/Library/TimeBaseLib/TimeBaseLib.inf
+ I2CLib|Silicon/Ampere/AmpereAltraPkg/Library/DWI2CLib/I2CLib.inf
+ DwapbGpioLib|Silicon/Ampere/AmpereAltraPkg/Library/DwapbGpioLib/DwapbGpioLib.inf
MmCommunicationLib|Silicon/Ampere/AmpereAltraPkg/Library/MmCommunicationLib/MmCommunicationLib.inf
- RealTimeClockLib|EmbeddedPkg/Library/TemplateRealTimeClockLib/TemplateRealTimeClockLib.inf
-
# ARM PL011 UART Driver
PL011UartLib|ArmPlatformPkg/Library/PL011UartLib/PL011UartLib.inf
SerialPortLib|ArmPlatformPkg/Library/PL011SerialPortLib/PL011SerialPortLib.inf
diff --git a/Platform/Ampere/JadePkg/Jade.dsc b/Platform/Ampere/JadePkg/Jade.dsc
index 56c6d53e3629..02157cb52f6e 100755
--- a/Platform/Ampere/JadePkg/Jade.dsc
+++ b/Platform/Ampere/JadePkg/Jade.dsc
@@ -49,6 +49,12 @@ [Defines]
#
################################################################################
[LibraryClasses]
+
+ #
+ # RTC Library: Common RTC
+ #
+ RealTimeClockLib|Platform/Ampere/JadePkg/Library/PCF85063RealTimeClockLib/PCF85063.inf
+
#
# Library for FailSafe support
#
diff --git a/Platform/Ampere/JadePkg/Library/PCF85063RealTimeClockLib/PCF85063.inf b/Platform/Ampere/JadePkg/Library/PCF85063RealTimeClockLib/PCF85063.inf
new file mode 100644
index 000000000000..7666f313c31d
--- /dev/null
+++ b/Platform/Ampere/JadePkg/Library/PCF85063RealTimeClockLib/PCF85063.inf
@@ -0,0 +1,55 @@
+## @file
+#
+# Copyright (c) 2020, Ampere Computing LLC. All rights reserved.<BR>
+#
+# SPDX-License-Identifier: BSD-2-Clause-Patent
+#
+##
+
+[Defines]
+ INF_VERSION = 0x0001001B
+ MODULE_TYPE = BASE
+ BASE_NAME = RealTimeClockLib
+ FILE_GUID = 271569F6-5522-4006-9FF5-F07A59473AAC
+ LIBRARY_CLASS = RealTimeClockLib
+ VERSION_STRING = 1.0
+
+[Sources.common]
+ PCF85063.c
+ RtcSystemLib.c
+ PCF85063.h
+
+[Packages]
+ ArmPkg/ArmPkg.dec
+ ArmPlatformPkg/ArmPlatformPkg.dec
+ EmbeddedPkg/EmbeddedPkg.dec
+ MdePkg/MdePkg.dec
+ Silicon/Ampere/AmperePkg.dec
+ Silicon/Ampere/AmpereAltraPkg/Ac01Pkg.dec
+
+[LibraryClasses]
+ BaseLib
+ UefiRuntimeLib
+ UefiLib
+ DebugLib
+ TimerLib
+ DxeServicesTableLib
+ ArmLib
+ SMProLib
+ ArmGenericTimerCounterLib
+ I2CLib
+ DwapbGpioLib
+
+[Guids]
+ gEfiEventVirtualAddressChangeGuid
+
+[FixedPcd]
+ gArmPlatformTokenSpaceGuid.PcdCoreCount
+ gArmPlatformTokenSpaceGuid.PcdClusterCount
+
+ gAmpereTokenSpaceGuid.PcdTurboDefaultFreq
+ gAmpereTokenSpaceGuid.PcdSmproDb
+ gAmpereTokenSpaceGuid.PcdSmproDbBaseReg
+ gAmpereTokenSpaceGuid.PcdSmproEfuseShadow0
+ gAmpereTokenSpaceGuid.PcdSmproI2cBmcBusAddr
+ gAmpereTokenSpaceGuid.PcdSmproNsMailboxIndex
diff --git a/Platform/Ampere/JadePkg/Library/PCF85063RealTimeClockLib/PCF85063.h b/Platform/Ampere/JadePkg/Library/PCF85063RealTimeClockLib/PCF85063.h
new file mode 100644
index 000000000000..0779bc229188
--- /dev/null
+++ b/Platform/Ampere/JadePkg/Library/PCF85063RealTimeClockLib/PCF85063.h
@@ -0,0 +1,86 @@
+/** @file
+
+ Copyright (c) 2020, Ampere Computing LLC. All rights reserved.<BR>
+
+ SPDX-License-Identifier: BSD-2-Clause-Patent
+
+**/
+
+#ifndef _COMMON_H_
+#define _COMMON_H_
+
+#include <Uefi.h>
+#include <Library/BaseLib.h>
+#include <Library/RealTimeClockLib.h>
+
+//
+// I2C bus address that RTC connected to
+//
+#define I2C_RTC_BUS_ADDRESS 1
+
+//
+// I2C RTC bus speed
+//
+#define I2C_RTC_BUS_SPEED 100000
+
+//
+// I2C chip address that RTC connected to
+//
+#define I2C_RTC_CHIP_ADDRESS 0x51
+
+//
+// The GPI PIN that tell if RTC can be access
+//
+#define I2C_RTC_ACCESS_GPIO_PIN 28
+
+/**
+ * Returns the current time and date information of the hardware platform.
+ *
+ * @param Time A pointer to storage to receive a snapshot of the current time.
+ *
+ *
+ * @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
+PlatformGetTime (
+ OUT EFI_TIME *Time
+ );
+
+/**
+ * Set the time and date information to the hardware platform.
+ *
+ * @param Time A pointer to storage to set the current time to hardware platform.
+ *
+ *
+ * @retval EFI_SUCCESS The operation completed successfully.
+ * @retval EFI_INVALID_PARAMETER Time is NULL.
+ * @retval EFI_DEVICE_ERROR The time could not be set due due to hardware error.
+ **/
+EFI_STATUS
+EFIAPI
+PlatformSetTime (
+ IN EFI_TIME *Time
+ );
+
+/**
+ * Callback function for hardware platform to convert data pointers to virtual address
+ */
+VOID
+EFIAPI
+PlatformVirtualAddressChangeEvent (VOID);
+
+/**
+ * Callback function for hardware platform to initialize private data
+ *
+ *
+ * @retval EFI_SUCCESS The operation completed successfully.
+ * @retval Others The error status indicates the error
+ */
+EFI_STATUS
+EFIAPI
+PlatformInitialize (VOID);
+
+#endif /* _COMMON_H_ */
diff --git a/Platform/Ampere/JadePkg/Library/PCF85063RealTimeClockLib/PCF85063.c b/Platform/Ampere/JadePkg/Library/PCF85063RealTimeClockLib/PCF85063.c
new file mode 100755
index 000000000000..f1f01ee7b566
--- /dev/null
+++ b/Platform/Ampere/JadePkg/Library/PCF85063RealTimeClockLib/PCF85063.c
@@ -0,0 +1,288 @@
+/** @file
+
+ Copyright (c) 2020, Ampere Computing LLC. All rights reserved.<BR>
+
+ SPDX-License-Identifier: BSD-2-Clause-Patent
+
+**/
+
+#include <Uefi.h>
+#include <Library/BaseLib.h>
+#include <Library/DebugLib.h>
+#include <Library/UefiLib.h>
+#include <Library/UefiBootServicesTableLib.h>
+#include <Library/UefiRuntimeLib.h>
+#include <Library/UefiRuntimeServicesTableLib.h>
+#include <Library/MemoryAllocationLib.h>
+#include <Library/BaseMemoryLib.h>
+#include <Library/PcdLib.h>
+#include <Library/I2CLib.h>
+#include <Library/DwapbGpioLib.h>
+#include <Library/TimerLib.h>
+#include <Library/SMProInterface.h>
+#include <Platform/Ac01.h>
+#include "PCF85063.h"
+
+#define RTC_TIMEOUT_WAIT_ACCESS 100000 /* 100 miliseconds */
+#define RTC_DEFAULT_MIN_YEAR 2000
+#define RTC_DEFAULT_MAX_YEAR 2099
+
+/* Runtime needs to be 64K alignment */
+#define RUNTIME_ADDRESS_MASK (~(SIZE_64KB - 1))
+#define RUNTIME_ADDRESS_LENGTH SIZE_64KB
+
+#define RTC_ADDR 0x4
+#define RTC_DATA_BUF_LEN 8
+
+/**
+ * PCF85063 register offsets
+ */
+#define PCF85063_OFFSET_SEC 0x0
+#define PCF85063_OFFSET_MIN 0x1
+#define PCF85063_OFFSET_HR 0x2
+#define PCF85063_OFFSET_DAY 0x3
+#define PCF85063_OFFSET_WKD 0x4
+#define PCF85063_OFFSET_MON 0x5
+#define PCF85063_OFFSET_YEA 0x6
+
+/**
+ * PCF85063 encoding macros
+ */
+#define PCF85063_SEC_ENC(s) (((((s) / 10) & 0x7) << 4) | (((s) % 10) & 0xf))
+#define PCF85063_MIN_ENC(m) (((((m) / 10) & 0x7) << 4) | (((m) % 10) & 0xf))
+#define PCF85063_HR_ENC(h) (((((h) / 10) & 0x3) << 4) | (((h) % 10) & 0xf))
+#define PCF85063_DAY_ENC(d) (((((d) / 10) & 0x3) << 4) | (((d) % 10) & 0xf))
+#define PCF85063_WKD_ENC(w) ((w) & 0x7)
+#define PCF85063_MON_ENC(m) (((((m) / 10) & 0x1) << 4) | (((m) % 10) & 0xf))
+#define PCF85063_YEA_ENC(y) (((((y) / 10) & 0xf) << 4) | (((y) % 10) & 0xf))
+
+/**
+ * PCF85063 decoding macros
+ */
+#define PCF85063_SEC_DEC(s) (((((s) & 0x70) >> 4) * 10) + ((s) & 0xf))
+#define PCF85063_MIN_DEC(m) (((((m) & 0x70) >> 4) * 10) + ((m) & 0xf))
+#define PCF85063_HR_DEC(h) (((((h) & 0x30) >> 4) * 10) + ((h) & 0xf))
+#define PCF85063_DAY_DEC(d) (((((d) & 0x30) >> 4)* 10) + ((d) & 0xf))
+#define PCF85063_WKD_DEC(w) ((w) & 0x7)
+#define PCF85063_MON_DEC(m) (((((m) & 0x10) >> 4) * 10) + ((m) & 0xf))
+#define PCF85063_YEA_DEC(y) (((((y) & 0xf0) >> 4) * 10) + ((y) & 0xf))
+
+/* Buffer pointers to convert Vir2Phys and Phy2Vir */
+STATIC volatile UINT64 RtcBufVir;
+STATIC volatile UINT64 RtcBufPhy;
+
+STATIC volatile UINT64 DBAddr = (UINT64) SMPRO_DB_BASE_REG;
+
+STATIC
+EFI_STATUS
+RtcI2CWaitAccess (VOID)
+{
+ INTN Timeout = RTC_TIMEOUT_WAIT_ACCESS;
+
+ while ((DwapbGpioReadBit (I2C_RTC_ACCESS_GPIO_PIN) != 0) && (Timeout > 0)) {
+ MicroSecondDelay (100);
+ Timeout -= 100;
+ }
+
+ if (Timeout <= 0) {
+ DEBUG ((DEBUG_ERROR, "%a: Timeout while waiting access RTC\n", __FUNCTION__));
+ return EFI_TIMEOUT;
+ }
+
+ return EFI_SUCCESS;
+}
+
+STATIC
+EFI_STATUS
+RtcI2CRead (
+ IN UINT8 Addr,
+ IN OUT UINT64 Data,
+ IN UINT32 DataLen
+)
+{
+ EFI_STATUS Status;
+ UINT32 TmpLen;
+
+ if (EFI_ERROR (RtcI2CWaitAccess ())) {
+ return EFI_DEVICE_ERROR;
+ }
+
+ Status = I2CProbe (I2C_RTC_BUS_ADDRESS, I2C_RTC_BUS_SPEED);
+ if (EFI_ERROR (Status)) {
+ return EFI_DEVICE_ERROR;
+ }
+
+ /* The first byte is the address */
+ TmpLen = 1;
+ Status = I2CWrite (I2C_RTC_BUS_ADDRESS, I2C_RTC_CHIP_ADDRESS, (UINT8 *) &Addr, &TmpLen);
+ if (EFI_ERROR (Status)) {
+ return EFI_DEVICE_ERROR;
+ }
+
+ /* Read back the date */
+ Status = I2CRead (I2C_RTC_BUS_ADDRESS, I2C_RTC_CHIP_ADDRESS, NULL, 0, (UINT8 *) Data, &DataLen);
+ if (EFI_ERROR (Status)) {
+ return EFI_DEVICE_ERROR;
+ }
+
+ return EFI_SUCCESS;
+}
+
+EFI_STATUS
+RtcI2CWrite (
+ IN UINT8 Addr,
+ IN UINT64 Data,
+ IN UINT32 DataLen
+ )
+{
+ EFI_STATUS Status;
+ UINT8 TmpBuf[RTC_DATA_BUF_LEN + 1];
+ UINT32 TmpLen;
+
+ if (EFI_ERROR (RtcI2CWaitAccess ())) {
+ return EFI_DEVICE_ERROR;
+ }
+
+ if (DataLen > sizeof (TmpBuf) - 1) {
+ return EFI_INVALID_PARAMETER;
+ }
+
+ Status = I2CProbe (I2C_RTC_BUS_ADDRESS, I2C_RTC_BUS_SPEED);
+ if (EFI_ERROR (Status)) {
+ return EFI_DEVICE_ERROR;
+ }
+
+ /* The first byte is the address */
+ TmpBuf[0] = Addr;
+ TmpLen = DataLen + 1;
+ CopyMem ((VOID *) (TmpBuf + 1), (VOID *) Data, DataLen);
+
+ Status = I2CWrite (I2C_RTC_BUS_ADDRESS, I2C_RTC_CHIP_ADDRESS, TmpBuf, &TmpLen);
+ if (EFI_ERROR (Status)) {
+ return EFI_DEVICE_ERROR;
+ }
+
+ return EFI_SUCCESS;
+}
+
+/**
+ * Returns the current time and date information of the hardware platform.
+ *
+ * @param Time A pointer to storage to receive a snapshot of the current time.
+ *
+ *
+ * @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
+PlatformGetTime (
+ OUT EFI_TIME *Time
+ )
+{
+ EFI_STATUS Status;
+ UINT8 *Data = (UINT8 *) RtcBufVir;
+
+ if (Time == NULL) {
+ return EFI_INVALID_PARAMETER;
+ }
+
+ Status = RtcI2CRead (RTC_ADDR, RtcBufVir, RTC_DATA_BUF_LEN);
+
+ if (Status == EFI_SUCCESS) {
+ Time->Second = PCF85063_SEC_DEC (Data[PCF85063_OFFSET_SEC]);
+ Time->Minute = PCF85063_MIN_DEC (Data[PCF85063_OFFSET_MIN]);
+ Time->Hour = PCF85063_HR_DEC (Data[PCF85063_OFFSET_HR]);
+ Time->Day = PCF85063_DAY_DEC (Data[PCF85063_OFFSET_DAY]);
+ Time->Month = PCF85063_MON_DEC (Data[PCF85063_OFFSET_MON]);
+ Time->Year = PCF85063_YEA_DEC (Data[PCF85063_OFFSET_YEA]);
+ Time->Year += RTC_DEFAULT_MIN_YEAR;
+ if (Time->Year > RTC_DEFAULT_MAX_YEAR) {
+ Time->Year = RTC_DEFAULT_MAX_YEAR;
+ }
+ if (Time->Year < RTC_DEFAULT_MIN_YEAR) {
+ Time->Year = RTC_DEFAULT_MIN_YEAR;
+ }
+ }
+
+ return Status;
+}
+
+/**
+ * Set the time and date information to the hardware platform.
+ *
+ * @param Time A pointer to storage to set the current time to hardware platform.
+ *
+ *
+ * @retval EFI_SUCCESS The operation completed successfully.
+ * @retval EFI_INVALID_PARAMETER Time is NULL.
+ * @retval EFI_DEVICE_ERROR The time could not be set due due to hardware error.
+ **/
+EFI_STATUS
+EFIAPI
+PlatformSetTime (
+ IN EFI_TIME *Time
+ )
+{
+ UINT8 *Data = (UINT8 *) RtcBufVir;
+
+ if (Time == NULL) {
+ return EFI_INVALID_PARAMETER;
+ }
+
+ if (Time->Year < RTC_DEFAULT_MIN_YEAR ||
+ Time->Year > RTC_DEFAULT_MAX_YEAR) {
+ return EFI_INVALID_PARAMETER;
+ }
+
+ Data[PCF85063_OFFSET_SEC] = PCF85063_SEC_ENC (Time->Second);
+ Data[PCF85063_OFFSET_MIN] = PCF85063_MIN_ENC (Time->Minute);
+ Data[PCF85063_OFFSET_HR] = PCF85063_HR_ENC (Time->Hour);
+ Data[PCF85063_OFFSET_DAY] = PCF85063_DAY_ENC (Time->Day);
+ Data[PCF85063_OFFSET_MON] = PCF85063_MON_ENC (Time->Month);
+ Data[PCF85063_OFFSET_YEA] = PCF85063_YEA_ENC (Time->Year - RTC_DEFAULT_MIN_YEAR);
+
+ return RtcI2CWrite (RTC_ADDR, RtcBufVir, RTC_DATA_BUF_LEN);
+}
+
+/**
+ * Callback function for hardware platform to convert data pointers to virtual address
+ */
+VOID
+EFIAPI
+PlatformVirtualAddressChangeEvent (VOID)
+{
+ EfiConvertPointer (0x0, (VOID **) &RtcBufVir);
+ EfiConvertPointer (0x0, (VOID **) &DBAddr);
+}
+
+/**
+ * Callback function for hardware platform to initialize private data
+ *
+ *
+ * @retval EFI_SUCCESS The operation completed successfully.
+ * @retval Others The error status indicates the error
+ */
+EFI_STATUS
+EFIAPI
+PlatformInitialize (VOID)
+{
+ EFI_STATUS Status;
+
+ /*
+ * Allocate the buffer for RTC data
+ * The buffer can be accessible after ExitBootServices
+ */
+ RtcBufVir = (UINT64) AllocateRuntimeZeroPool (RTC_DATA_BUF_LEN);
+ ASSERT_EFI_ERROR (RtcBufVir);
+ RtcBufPhy = (UINT64) RtcBufVir;
+
+ Status = I2CSetupRuntime (I2C_RTC_BUS_ADDRESS);
+ ASSERT_EFI_ERROR (Status);
+
+ Status = DwapbGPIOSetupRuntime (I2C_RTC_ACCESS_GPIO_PIN);
+ ASSERT_EFI_ERROR (Status);
+
+ return Status;
+}
diff --git a/Platform/Ampere/JadePkg/Library/PCF85063RealTimeClockLib/RtcSystemLib.c b/Platform/Ampere/JadePkg/Library/PCF85063RealTimeClockLib/RtcSystemLib.c
new file mode 100755
index 000000000000..cfcd6ec24f3a
--- /dev/null
+++ b/Platform/Ampere/JadePkg/Library/PCF85063RealTimeClockLib/RtcSystemLib.c
@@ -0,0 +1,603 @@
+/** @file
+
+ Copyright (c) 2020, Ampere Computing LLC. All rights reserved.<BR>
+
+ SPDX-License-Identifier: BSD-2-Clause-Patent
+
+**/
+
+#include <PiDxe.h>
+#include <Uefi.h>
+#include <Library/BaseLib.h>
+#include <Library/DebugLib.h>
+#include <Library/RealTimeClockLib.h>
+#include <Library/UefiLib.h>
+#include <Library/UefiBootServicesTableLib.h>
+#include <Library/UefiRuntimeLib.h>
+#include <Library/UefiRuntimeServicesTableLib.h>
+#include <Library/MemoryAllocationLib.h>
+#include <Library/DxeServicesTableLib.h>
+#include <Protocol/RealTimeClock.h>
+#include <Library/ArmGenericTimerCounterLib.h>
+#include <Library/ArmLib.h>
+#include <Guid/EventGroup.h>
+#include "PCF85063.h"
+
+#define TICKS_PER_SEC (ArmGenericTimerGetTimerFreq ())
+
+#define TIMEZONE_0 0
+
+/**
+ * Define EPOCH (1970-JANUARY-01) in the Julian Date representation
+ */
+#define EPOCH_JULIAN_DATE 2440588
+
+/**
+ * Seconds per unit
+ */
+#define SEC_PER_MIN ((UINTN) 60)
+#define SEC_PER_HOUR ((UINTN) 3600)
+#define SEC_PER_DAY ((UINTN) 86400)
+
+#define SEC_PER_MONTH ((UINTN) 2,592,000)
+#define SEC_PER_YEAR ((UINTN) 31,536,000)
+
+STATIC EFI_RUNTIME_SERVICES *mRT;
+STATIC EFI_EVENT mVirtualAddressChangeEvent = NULL;
+
+STATIC UINT64 mLastSavedSystemCount = 0;
+STATIC UINT64 mLastSavedTimeEpoch = 0;
+STATIC CONST CHAR16 mTimeZoneVariableName[] = L"RtcTimeZone";
+STATIC CONST CHAR16 mDaylightVariableName[] = L"RtcDaylight";
+
+STATIC
+BOOLEAN
+IsLeapYear (
+ IN EFI_TIME *Time
+ )
+{
+ if (Time->Year % 4 == 0) {
+ if (Time->Year % 100 == 0) {
+ if (Time->Year % 400 == 0) {
+ return TRUE;
+ } else {
+ return FALSE;
+ }
+ } else {
+ return TRUE;
+ }
+ } else {
+ return FALSE;
+ }
+}
+
+STATIC
+BOOLEAN
+DayValid (
+ IN EFI_TIME *Time
+ )
+{
+ INTN DayOfMonth[12];
+
+ DayOfMonth[0] = 31;
+ DayOfMonth[1] = 29;
+ DayOfMonth[2] = 31;
+ DayOfMonth[3] = 30;
+ DayOfMonth[4] = 31;
+ DayOfMonth[5] = 30;
+ DayOfMonth[6] = 31;
+ DayOfMonth[7] = 31;
+ DayOfMonth[8] = 30;
+ DayOfMonth[9] = 31;
+ DayOfMonth[10] = 30;
+ DayOfMonth[11] = 31;
+
+ if (Time->Day < 1 ||
+ Time->Day > DayOfMonth[Time->Month - 1] ||
+ (Time->Month == 2 && (!IsLeapYear (Time) && Time->Day > 28))
+ ) {
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+STATIC
+EFI_STATUS
+RtcTimeFieldsValid (
+ IN EFI_TIME *Time
+ )
+{
+ if (Time->Month < 1 ||
+ Time->Month > 12 ||
+ (!DayValid (Time)) ||
+ Time->Hour > 23 ||
+ Time->Minute > 59 ||
+ Time->Second > 59 ||
+ Time->Nanosecond > 999999999 ||
+ (!(Time->TimeZone == EFI_UNSPECIFIED_TIMEZONE || (Time->TimeZone >= -1440 && Time->TimeZone <= 1440))) ||
+ ((Time->Daylight & (~(EFI_TIME_ADJUST_DAYLIGHT | EFI_TIME_IN_DAYLIGHT))) != 0)) {
+ return EFI_INVALID_PARAMETER;
+ }
+
+ return EFI_SUCCESS;
+}
+
+UINT8
+Bin2Bcd (
+ UINT32 Val
+ )
+{
+ return (((Val / 10) << 4) | (Val % 10));
+}
+
+/**
+ * Converts EFI_TIME to Epoch seconds (elapsed since 1970 JANUARY 01, 00:00:00 UTC)
+ */
+STATIC UINTN
+EfiTimeToEpoch (
+ IN EFI_TIME *Time
+ )
+{
+ UINTN a;
+ UINTN y;
+ UINTN m;
+ UINTN JulianDate;
+ UINTN EpochDays;
+ UINTN EpochSeconds;
+
+ a = (14 - Time->Month) / 12 ;
+ y = Time->Year + 4800 - a;
+ m = Time->Month + (12*a) - 3;
+
+ JulianDate = Time->Day + ((153*m + 2)/5) + (365*y) + (y/4) - (y/100) + (y/400) - 32045;
+
+ ASSERT (JulianDate > EPOCH_JULIAN_DATE);
+ EpochDays = JulianDate - EPOCH_JULIAN_DATE;
+
+ EpochSeconds = (EpochDays * SEC_PER_DAY) + ((UINTN)Time->Hour * SEC_PER_HOUR) + (Time->Minute * SEC_PER_MIN) + Time->Second;
+
+ return EpochSeconds;
+}
+
+/**
+ * Converts Epoch seconds (elapsed since 1970 JANUARY 01, 00:00:00 UTC) to EFI_TIME
+ */
+STATIC
+VOID
+EpochToEfiTime (
+ IN UINTN EpochSeconds,
+ OUT EFI_TIME *Time
+ )
+{
+ INTN a;
+ INTN b;
+ INTN c;
+ INTN d;
+ INTN g;
+ INTN j;
+ INTN m;
+ INTN y;
+ INTN da;
+ INTN db;
+ INTN dc;
+ INTN dg;
+ INTN hh;
+ INTN mm;
+ INTN ss;
+ INTN J;
+
+ J = (EpochSeconds / 86400) + 2440588;
+ j = J + 32044;
+ g = j / 146097;
+ dg = j % 146097;
+ c = (((dg / 36524) + 1) * 3) / 4;
+ dc = dg - (c * 36524);
+ b = dc / 1461;
+ db = dc % 1461;
+ a = (((db / 365) + 1) * 3) / 4;
+ da = db - (a * 365);
+ y = (g * 400) + (c * 100) + (b * 4) + a;
+ m = (((da * 5) + 308) / 153) - 2;
+ d = da - (((m + 4) * 153) / 5) + 122;
+
+ Time->Year = y - 4800 + ((m + 2) / 12);
+ Time->Month = ((m + 2) % 12) + 1;
+ Time->Day = d + 1;
+
+ ss = EpochSeconds % 60;
+ a = (EpochSeconds - ss) / 60;
+ mm = a % 60;
+ b = (a - mm) / 60;
+ hh = b % 24;
+
+ Time->Hour = hh;
+ Time->Minute = mm;
+ Time->Second = ss;
+ Time->Nanosecond = 0;
+}
+
+/**
+ * Returns the current time and date information, and the time-keeping capabilities
+ * of the hardware platform.
+ *
+ * @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;
+ UINT64 CurrentSystemCount;
+ UINT64 TimeElapsed;
+ INT16 TimeZone;
+ UINT8 Daylight;
+ UINTN Size;
+ UINTN EpochSeconds;
+
+ if (Time == NULL) {
+ return EFI_INVALID_PARAMETER;
+ }
+
+ if ((mLastSavedTimeEpoch == 0) || EfiAtRuntime ()) {
+ /* SMPro requires physical address for message communication */
+ Status = PlatformGetTime (Time);
+ if (EFI_ERROR (Status)) {
+ /* Failed to read platform RTC so create fake time */
+ Time->Second = 0;
+ Time->Minute = 0;
+ Time->Hour = 10;
+ Time->Day = 1;
+ Time->Month = 1;
+ Time->Year = 2017;
+ }
+ if (!EfiAtRuntime ()) {
+ mLastSavedTimeEpoch = EfiTimeToEpoch (Time);
+ mLastSavedSystemCount = ArmGenericTimerGetSystemCount ();
+ }
+ EpochSeconds = EfiTimeToEpoch (Time);
+ } else {
+ CurrentSystemCount = ArmGenericTimerGetSystemCount ();
+ if (CurrentSystemCount >= mLastSavedSystemCount) {
+ TimeElapsed = (CurrentSystemCount - mLastSavedSystemCount) / MultU64x32 (1, TICKS_PER_SEC);
+ EpochSeconds = mLastSavedTimeEpoch + TimeElapsed;
+ } else {
+ /* System counter overflow 64 bits */
+ /* Call GetTime again to read the date from RTC HW, not using generic timer system counter */
+ mLastSavedTimeEpoch = 0;
+ return LibGetTime (Time, Capabilities);
+ }
+ }
+
+ /* Get the current time zone information from non-volatile storage */
+ Size = sizeof (TimeZone);
+ Status = mRT->GetVariable (
+ (CHAR16 *) mTimeZoneVariableName,
+ &gEfiCallerIdGuid,
+ NULL,
+ &Size,
+ (VOID *) &TimeZone
+ );
+ if (EFI_ERROR (Status)) {
+ /* The time zone variable does not exist in non-volatile storage, so create it. */
+ Time->TimeZone = TIMEZONE_0;
+ /* Store it */
+ Status = mRT->SetVariable (
+ (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,
+ "%a: Failed to save %s variable to non-volatile storage, Status = %r\n",
+ __FUNCTION__,
+ 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 = mRT->GetVariable (
+ (CHAR16 *) mDaylightVariableName,
+ &gEfiCallerIdGuid,
+ NULL,
+ &Size,
+ (VOID *)&Daylight
+ );
+
+ if (EFI_ERROR (Status)) {
+ /* The daylight variable does not exist in non-volatile storage, so create it. */
+ Time->Daylight = 0;
+ /* Store it */
+ Status = mRT->SetVariable (
+ (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,
+ "%a: Failed to save %s variable to non-volatile storage, Status = %r\n",
+ __FUNCTION__,
+ 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);
+
+ 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;
+ UINTN EpochSeconds;
+
+ if ((Time == NULL) || (RtcTimeFieldsValid (Time) != EFI_SUCCESS)) {
+ return EFI_INVALID_PARAMETER;
+ }
+
+ /* Always default to UTC time if unspecified */
+ if (Time->TimeZone == EFI_UNSPECIFIED_TIMEZONE) {
+ Time->TimeZone = TIMEZONE_0;
+ }
+
+ EpochSeconds = EfiTimeToEpoch (Time);
+
+ /* Adjust for the correct time zone, convert to UTC time zone */
+ 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;
+ }
+
+ Status = PlatformSetTime (Time);
+ if (EFI_ERROR (Status)) {
+ return Status;
+ }
+
+ /* Save the current time zone information into non-volatile storage */
+ Status = mRT->SetVariable (
+ (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,
+ "%a: Failed to save %s variable to non-volatile storage, Status = %r\n",
+ __FUNCTION__,
+ mTimeZoneVariableName,
+ Status
+ ));
+ return Status;
+ }
+
+ /* Save the current daylight information into non-volatile storage */
+ Status = mRT->SetVariable (
+ (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,
+ "%a: Failed to save %s variable to non-volatile storage, Status = %r\n",
+ __FUNCTION__,
+ mDaylightVariableName,
+ Status
+ ));
+ return Status;
+ }
+
+ EpochToEfiTime (EpochSeconds, Time);
+
+ Status = PlatformSetTime (Time);
+ if (EFI_ERROR (Status)) {
+ return Status;
+ }
+
+ EpochToEfiTime (EpochSeconds, Time);
+
+ if (!EfiAtRuntime ()) {
+ mLastSavedTimeEpoch = EpochSeconds;
+ mLastSavedSystemCount = ArmGenericTimerGetSystemCount ();
+ }
+
+ 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;
+}
+
+/**
+ * Notification function of EVT_SIGNAL_VIRTUAL_ADDRESS_CHANGE.
+ *
+ * This is a notification function registered on EVT_SIGNAL_VIRTUAL_ADDRESS_CHANGE event.
+ * It convers pointer to new virtual address.
+ *
+ * @param Event Event whose notification function is being invoked.
+ * @param Context Pointer to the notification function's context.
+ */
+STATIC
+VOID
+EFIAPI
+VirtualAddressChangeEvent (
+ IN EFI_EVENT Event,
+ IN VOID *Context
+ )
+{
+ EfiConvertPointer (0x0, (VOID**) &mRT);
+ PlatformVirtualAddressChangeEvent ();
+}
+
+/**
+ * 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
+ )
+{
+ EFI_STATUS Status;
+ EFI_HANDLE Handle;
+
+ Status = PlatformInitialize ();
+ if (EFI_ERROR (Status)) {
+ return Status;
+ }
+
+ /*
+ * Register for the virtual address change event
+ */
+ Status = gBS->CreateEventEx (
+ EVT_NOTIFY_SIGNAL,
+ TPL_NOTIFY,
+ VirtualAddressChangeEvent,
+ NULL,
+ &gEfiEventVirtualAddressChangeGuid,
+ &mVirtualAddressChangeEvent
+ );
+ ASSERT_EFI_ERROR (Status);
+
+ /*
+ * Setup the setters and getters
+ */
+ mRT = gRT;
+ mRT->GetTime = LibGetTime;
+ mRT->SetTime = LibSetTime;
+ mRT->GetWakeupTime = LibGetWakeupTime;
+ mRT->SetWakeupTime = LibSetWakeupTime;
+
+ /*
+ * Install the protocol
+ */
+ Handle = NULL;
+ Status = gBS->InstallMultipleProtocolInterfaces (
+ &Handle,
+ &gEfiRealTimeClockArchProtocolGuid, NULL,
+ NULL
+ );
+ ASSERT_EFI_ERROR (Status);
+
+ return EFI_SUCCESS;
+}
--
2.17.1
next prev parent reply other threads:[~2020-12-09 9:24 UTC|newest]
Thread overview: 40+ messages / expand[flat|nested] mbox.gz Atom feed top
2020-12-09 9:24 [edk2-platforms][PATCH 00/34] Add new Ampere Mt. Jade platform Nhi Pham
2020-12-09 9:24 ` [edk2-platforms][PATCH 01/34] Initial support for Ampere Altra and " Nhi Pham
2021-01-07 23:57 ` [edk2-devel] " Leif Lindholm
2021-01-15 4:59 ` Vu Nguyen
2020-12-09 9:24 ` [edk2-platforms][PATCH 02/34] Platform/Ampere: Implement FailSafe library Nhi Pham
2020-12-09 9:25 ` [edk2-platforms][PATCH 03/34] Platform/Ampere: Add FailSafe and WDT support Nhi Pham
2020-12-09 9:25 ` [edk2-platforms][PATCH 04/34] AmpereAltraPkg: Implement GpioLib and I2cLib modules Nhi Pham
2020-12-09 9:25 ` Nhi Pham [this message]
2020-12-09 9:25 ` [edk2-platforms][PATCH 06/34] Platform/Ampere: Add AcpiPccLib to support ACPI PCCT Table Nhi Pham
2020-12-09 9:25 ` [edk2-platforms][PATCH 07/34] Platform/Ampere: Add AcpiHelperLib to update ACPI DSDT table Nhi Pham
2020-12-09 9:25 ` [edk2-platforms][PATCH 08/34] JadePkg: Initial support for static ACPI tables Nhi Pham
2020-12-09 9:25 ` [edk2-platforms][PATCH 09/34] JadePkg: Install some ACPI tables at runtime Nhi Pham
2020-12-09 9:25 ` [edk2-platforms][PATCH 10/34] Silicon/Ampere: Support Non Volatile storage for Variable service Nhi Pham
2020-12-09 9:25 ` [edk2-platforms][PATCH 11/34] Silicon/Ampere: Support PlatformManagerUiLib Nhi Pham
2020-12-09 9:25 ` [edk2-platforms][PATCH 12/34] AmpereAltraPkg: Add PcieCore Library Nhi Pham
2020-12-09 9:25 ` [edk2-platforms][PATCH 13/34] AmpereAltraPkg: Add PciHostBridge driver Nhi Pham
2020-12-09 9:25 ` [edk2-platforms][PATCH 14/34] JadePkg: Add implementation for PcieBoardLib Nhi Pham
2020-12-09 9:25 ` [edk2-platforms][PATCH 15/34] JadePkg: Enable PCIe support Nhi Pham
2020-12-09 9:25 ` [edk2-platforms][PATCH 16/34] JadePkg: Add ASpeed GOP driver Nhi Pham
2020-12-09 9:25 ` [edk2-platforms][PATCH 17/34] Silicon/Ampere: Add Random Number Generator Support Nhi Pham
2020-12-09 9:25 ` [edk2-platforms][PATCH 18/34] Silicon/Ampere: Fixup runtime memory attribute Nhi Pham
2020-12-09 9:25 ` [edk2-platforms][PATCH 19/34] JadePkg: Add SMBIOS tables support Nhi Pham
2020-12-09 9:25 ` [edk2-platforms][PATCH 20/34] AmpereAltraPkg: Add DebugInfoPei module Nhi Pham
2020-12-09 9:25 ` [edk2-platforms][PATCH 21/34] Silicon/Ampere: Add platform info screen Nhi Pham
2020-12-09 9:25 ` [edk2-platforms][PATCH 22/34] Silicon/Ampere: Add Memory " Nhi Pham
2020-12-09 9:25 ` [edk2-platforms][PATCH 23/34] AmpereAltraPkg: Add CPU Configuration for SubNUMA Nhi Pham
2020-12-09 9:25 ` [edk2-platforms][PATCH 24/34] AmpereAltraPkg: Add ACPI configuration screen Nhi Pham
2020-12-09 9:25 ` [edk2-platforms][PATCH 25/34] AmpereAltraPkg: Implement PlatformFlashAccessLib instance Nhi Pham
2020-12-09 9:25 ` [edk2-platforms][PATCH 26/34] JadePkg: Add implementation for UEFI Capsule Update Nhi Pham
2020-12-09 9:25 ` [edk2-platforms][PATCH 27/34] JadePkg: Add Capsule Update support Nhi Pham
2020-12-09 9:25 ` [edk2-platforms][PATCH 28/34] Silicon/Ampere: Implement PlatformBootManagerLib for LinuxBoot Nhi Pham
2020-12-09 9:25 ` [edk2-platforms][PATCH 29/34] Platform/Ampere: Add LinuxBoot image Nhi Pham
2020-12-10 12:40 ` [edk2-devel] " Leif Lindholm
2020-12-11 2:38 ` Nhi Pham
2020-12-11 11:15 ` Leif Lindholm
2020-12-09 9:25 ` [edk2-platforms][PATCH 30/34] JadePkg: Support LinuxBoot DSC/FDF build for Jade platform Nhi Pham
2020-12-09 9:25 ` [edk2-platforms][PATCH 31/34] AmpereAltraPkg: Add BootProgress support Nhi Pham
2020-12-09 9:25 ` [edk2-platforms][PATCH 32/34] JadePkg: Add ACPI/APEI tables Nhi Pham
2020-12-09 9:25 ` [edk2-platforms][PATCH 33/34] Platform/Ampere: Add AcpiApeiLib Nhi Pham
2020-12-09 9:25 ` [edk2-platforms][PATCH 34/34] AmpereAltraPkg, JadePkg: Add RAS setting screen Nhi Pham
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-list from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20201209092531.30867-6-nhi@os.amperecomputing.com \
--to=devel@edk2.groups.io \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox