From d9b5815d797260ca8c8af1f8cca19e85c8c32a09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mario=20B=C4=83l=C4=83nic=C4=83?= Date: Fri, 29 Dec 2023 23:29:47 +0200 Subject: [PATCH 09/16] Platform/RPi5: Add real-time clock support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Working get/set time and wake up alarm within UEFI. Signed-off-by: Mario Bălănică --- .../Drivers/RpiFirmwareDxe/RpiFirmwareDxe.c | 110 ++++++- .../Include/IndustryStandard/RpiMbox.h | 2 + .../Include/Protocol/RpiFirmware.h | 28 ++ .../RaspberryPi/Library/RpiRtcLib/RpiRtcLib.c | 305 ++++++++++++++++++ .../Library/RpiRtcLib/RpiRtcLib.inf | 42 +++ Platform/RaspberryPi/RPi5/RPi5.dsc | 2 +- 6 files changed, 487 insertions(+), 2 deletions(-) create mode 100644 Platform/RaspberryPi/Library/RpiRtcLib/RpiRtcLib.c create mode 100644 Platform/RaspberryPi/Library/RpiRtcLib/RpiRtcLib.inf diff --git a/Platform/RaspberryPi/Drivers/RpiFirmwareDxe/RpiFirmwareDxe.c b/Platform/RaspberryPi/Drivers/RpiFirmwareDxe/RpiFirmwareDxe.c index 9077f077..b432e828 100644 --- a/Platform/RaspberryPi/Drivers/RpiFirmwareDxe/RpiFirmwareDxe.c +++ b/Platform/RaspberryPi/Drivers/RpiFirmwareDxe/RpiFirmwareDxe.c @@ -1,5 +1,6 @@ /** @file * + * Copyright (c) 2023, Mario Bălănică * Copyright (c) 2020, Pete Batard * Copyright (c) 2019, ARM Limited. All rights reserved. * Copyright (c) 2017-2020, Andrei Warkentin @@ -1332,6 +1333,111 @@ RpiFirmwareNotifyGpioSetCfg ( return Status; } + +#pragma pack() +typedef struct { + UINT32 Register; + UINT32 Value; +} RPI_FW_RTC_TAG; + +typedef struct { + RPI_FW_BUFFER_HEAD BufferHead; + RPI_FW_TAG_HEAD TagHead; + RPI_FW_RTC_TAG TagBody; + UINT32 EndTag; +} RPI_FW_RTC_CMD; +#pragma pack() + +STATIC +EFI_STATUS +EFIAPI +RpiFirmwareGetRtc ( + IN RASPBERRY_PI_RTC_REGISTER Register, + OUT UINT32 *Value + ) +{ + RPI_FW_RTC_CMD *Cmd; + EFI_STATUS Status; + UINT32 Result; + + if (!AcquireSpinLockOrFail (&mMailboxLock)) { + DEBUG ((DEBUG_ERROR, "%a: failed to acquire spinlock\n", __FUNCTION__)); + return EFI_DEVICE_ERROR; + } + + Cmd = mDmaBuffer; + ZeroMem (Cmd, sizeof (*Cmd)); + + Cmd->BufferHead.BufferSize = sizeof (*Cmd); + Cmd->BufferHead.Response = 0; + Cmd->TagHead.TagId = RPI_MBOX_GET_RTC_REG; + Cmd->TagHead.TagSize = sizeof (Cmd->TagBody); + Cmd->TagHead.TagValueSize = 0; + Cmd->TagBody.Register = Register; + Cmd->TagBody.Value = 0; + Cmd->EndTag = 0; + + Status = MailboxTransaction (Cmd->BufferHead.BufferSize, RPI_MBOX_VC_CHANNEL, &Result); + + if (EFI_ERROR (Status) || + Cmd->BufferHead.Response != RPI_MBOX_RESP_SUCCESS) { + DEBUG ((DEBUG_ERROR, + "%a: mailbox transaction error: Status == %r, Response == 0x%x\n", + __FUNCTION__, Status, Cmd->BufferHead.Response)); + Status = EFI_DEVICE_ERROR; + } else { + *Value = Cmd->TagBody.Value; + } + + ReleaseSpinLock (&mMailboxLock); + + return Status; +} + +STATIC +EFI_STATUS +EFIAPI +RpiFirmwareSetRtc ( + IN RASPBERRY_PI_RTC_REGISTER Register, + IN UINT32 Value + ) +{ + RPI_FW_RTC_CMD *Cmd; + EFI_STATUS Status; + UINT32 Result; + + if (!AcquireSpinLockOrFail (&mMailboxLock)) { + DEBUG ((DEBUG_ERROR, "%a: failed to acquire spinlock\n", __FUNCTION__)); + return EFI_DEVICE_ERROR; + } + + Cmd = mDmaBuffer; + ZeroMem (Cmd, sizeof (*Cmd)); + + Cmd->BufferHead.BufferSize = sizeof (*Cmd); + Cmd->BufferHead.Response = 0; + Cmd->TagHead.TagId = RPI_MBOX_SET_RTC_REG; + Cmd->TagHead.TagSize = sizeof (Cmd->TagBody); + Cmd->TagHead.TagValueSize = 0; + Cmd->TagBody.Register = Register; + Cmd->TagBody.Value = Value; + Cmd->EndTag = 0; + + Status = MailboxTransaction (Cmd->BufferHead.BufferSize, RPI_MBOX_VC_CHANNEL, &Result); + + if (EFI_ERROR (Status) || + Cmd->BufferHead.Response != RPI_MBOX_RESP_SUCCESS) { + DEBUG ((DEBUG_ERROR, + "%a: mailbox transaction error: Status == %r, Response == 0x%x\n", + __FUNCTION__, Status, Cmd->BufferHead.Response)); + Status = EFI_DEVICE_ERROR; + } + + ReleaseSpinLock (&mMailboxLock); + + return Status; +} + STATIC RASPBERRY_PI_FIRMWARE_PROTOCOL mRpiFirmwareProtocol = { RpiFirmwareSetPowerState, RpiFirmwareGetMacAddress, @@ -1352,7 +1458,9 @@ STATIC RASPBERRY_PI_FIRMWARE_PROTOCOL mRpiFirmwareProtocol = { RpiFirmwareNotifyXhciReset, RpiFirmwareGetCurrentClockState, RpiFirmwareSetClockState, - RpiFirmwareNotifyGpioSetCfg + RpiFirmwareNotifyGpioSetCfg, + RpiFirmwareGetRtc, + RpiFirmwareSetRtc, }; /** diff --git a/Platform/RaspberryPi/Include/IndustryStandard/RpiMbox.h b/Platform/RaspberryPi/Include/IndustryStandard/RpiMbox.h index 551c2b82..916b7442 100644 --- a/Platform/RaspberryPi/Include/IndustryStandard/RpiMbox.h +++ b/Platform/RaspberryPi/Include/IndustryStandard/RpiMbox.h @@ -93,6 +93,7 @@ #define RPI_MBOX_GET_POE_HAT_VAL 0x00030049 #define RPI_MBOX_SET_POE_HAT_VAL 0x00030050 #define RPI_MBOX_NOTIFY_XHCI_RESET 0x00030058 +#define RPI_MBOX_GET_RTC_REG 0x00030087 #define RPI_MBOX_SET_CLOCK_STATE 0x00038001 #define RPI_MBOX_SET_CLOCK_RATE 0x00038002 @@ -104,6 +105,7 @@ #define RPI_MBOX_SET_SDHOST_CLOCK 0x00038042 #define RPI_MBOX_SET_GPIO_CONFIG 0x00038043 #define RPI_MBOX_SET_PERIPH_REG 0x00038045 +#define RPI_MBOX_SET_RTC_REG 0x00038087 #define RPI_MBOX_ALLOC_FB 0x00040001 #define RPI_MBOX_FB_BLANK 0x00040002 diff --git a/Platform/RaspberryPi/Include/Protocol/RpiFirmware.h b/Platform/RaspberryPi/Include/Protocol/RpiFirmware.h index 86bf1687..92ab7f84 100644 --- a/Platform/RaspberryPi/Include/Protocol/RpiFirmware.h +++ b/Platform/RaspberryPi/Include/Protocol/RpiFirmware.h @@ -1,5 +1,6 @@ /** @file * + * Copyright (c) 2023, Mario Bălănică * Copyright (c) 2019, ARM Limited. All rights reserved. * Copyright (c) 2017 - 2020, Andrei Warkentin * Copyright (c) 2016, Linaro Limited. All rights reserved. @@ -14,6 +15,17 @@ #define RASPBERRY_PI_FIRMWARE_PROTOL_GUID \ { 0x0ACA9535, 0x7AD0, 0x4286, { 0xB0, 0x2E, 0x87, 0xFA, 0x7E, 0x2A, 0x57, 0x11 } } +typedef enum { + RpiRtcTime = 0, + RpiRtcAlarm, + RpiRtcAlarmPending, + RpiRtcAlarmEnable, + RpiRtcBatteryChargeVoltage, + RpiRtcBatteryChargeVoltageMin, + RpiRtcBatteryChargeVoltageMax, + RpiRtcBatteryVoltage, +} RASPBERRY_PI_RTC_REGISTER; + typedef EFI_STATUS (EFIAPI *SET_POWER_STATE) ( @@ -141,6 +153,20 @@ EFI_STATUS UINTN State ); +typedef +EFI_STATUS +(EFIAPI *GET_RTC) ( + IN RASPBERRY_PI_RTC_REGISTER Register, + OUT UINT32 *Value + ); + +typedef +EFI_STATUS +(EFIAPI *SET_RTC) ( + IN RASPBERRY_PI_RTC_REGISTER Register, + IN UINT32 Value + ); + typedef struct { SET_POWER_STATE SetPowerState; GET_MAC_ADDRESS GetMacAddress; @@ -162,6 +188,8 @@ typedef struct { GET_CLOCK_STATE GetClockState; SET_CLOCK_STATE SetClockState; GPIO_SET_CFG SetGpioConfig; + GET_RTC GetRtc; + SET_RTC SetRtc; } RASPBERRY_PI_FIRMWARE_PROTOCOL; extern EFI_GUID gRaspberryPiFirmwareProtocolGuid; diff --git a/Platform/RaspberryPi/Library/RpiRtcLib/RpiRtcLib.c b/Platform/RaspberryPi/Library/RpiRtcLib/RpiRtcLib.c new file mode 100644 index 00000000..298b3678 --- /dev/null +++ b/Platform/RaspberryPi/Library/RpiRtcLib/RpiRtcLib.c @@ -0,0 +1,305 @@ +/** @file + * + * Copyright (c) 2023, Mario Bălănică + * + * SPDX-License-Identifier: BSD-2-Clause-Patent + * + **/ + +#include + +#include +#include +#include +#include +#include + +#include + +STATIC RASPBERRY_PI_FIRMWARE_PROTOCOL *mFwProtocol; + +STATIC +VOID +EFIAPI +OffsetTimeZoneEpoch ( + IN EFI_TIME *Time, + IN OUT UINT32 *EpochSeconds, + IN BOOLEAN Add + ) +{ + // + // Adjust for the correct time zone + // The timezone setting also reflects the DST setting of the clock + // + if (Time->TimeZone != EFI_UNSPECIFIED_TIMEZONE) { + *EpochSeconds += (Add ? 1 : -1) * Time->TimeZone * SEC_PER_MIN; + } else if ((Time->Daylight & EFI_TIME_IN_DAYLIGHT) == EFI_TIME_IN_DAYLIGHT) { + // Convert to adjusted time, i.e. spring forwards one hour + *EpochSeconds += (Add ? 1 : -1) * SEC_PER_HOUR; + } +} + +/** + 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. + @retval EFI_SECURITY_VIOLATION The time could not be retrieved due to an authentication failure. + +**/ +EFI_STATUS +EFIAPI +LibGetTime ( + OUT EFI_TIME *Time, + OUT EFI_TIME_CAPABILITIES *Capabilities + ) +{ + EFI_STATUS Status; + UINT32 EpochSeconds; + + if (Time == NULL) { + return EFI_INVALID_PARAMETER; + } + + Status = mFwProtocol->GetRtc (RpiRtcTime, &EpochSeconds); + if (EFI_ERROR (Status)) { + return Status; + } + + OffsetTimeZoneEpoch (Time, &EpochSeconds, TRUE); + + 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 to hardware error. + +**/ +EFI_STATUS +EFIAPI +LibSetTime ( + IN EFI_TIME *Time + ) +{ + EFI_STATUS Status; + UINT32 EpochSeconds; + + if (Time == NULL || !IsTimeValid (Time)) { + return EFI_UNSUPPORTED; + } + + EpochSeconds = (UINT32)EfiTimeToEpoch (Time); + + OffsetTimeZoneEpoch (Time, &EpochSeconds, FALSE); + + Status = mFwProtocol->SetRtc (RpiRtcTime, EpochSeconds); + if (EFI_ERROR (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 + ) +{ + EFI_STATUS Status; + UINT32 EpochSeconds; + UINT32 EnableVal; + UINT32 PendingVal; + + if (Time == NULL || Enabled == NULL || Pending == NULL) { + return EFI_INVALID_PARAMETER; + } + + Status = mFwProtocol->GetRtc (RpiRtcAlarmEnable, &EnableVal); + if (EFI_ERROR (Status)) { + return Status; + } + + Status = mFwProtocol->GetRtc (RpiRtcAlarmPending, &PendingVal); + if (EFI_ERROR (Status)) { + return Status; + } + + *Enabled = EnableVal; + *Pending = PendingVal; + + if (*Pending) { + // Acknowledge alarm + Status = mFwProtocol->SetRtc (RpiRtcAlarmPending, TRUE); + if (EFI_ERROR (Status)) { + return Status; + } + } + + Status = mFwProtocol->GetRtc (RpiRtcAlarm, &EpochSeconds); + if (EFI_ERROR (Status)) { + return Status; + } + + OffsetTimeZoneEpoch (Time, &EpochSeconds, TRUE); + + EpochToEfiTime (EpochSeconds, Time); + + return EFI_SUCCESS; +} + +/** + 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 + ) +{ + EFI_STATUS Status; + UINT32 EpochSeconds; + + if (Enabled) { + if (Time == NULL || !IsTimeValid (Time)) { + return EFI_INVALID_PARAMETER; + } + + EpochSeconds = (UINT32)EfiTimeToEpoch (Time); + + OffsetTimeZoneEpoch (Time, &EpochSeconds, FALSE); + + Status = mFwProtocol->SetRtc (RpiRtcAlarm, EpochSeconds); + if (EFI_ERROR (Status)) { + return Status; + } + } + + Status = mFwProtocol->SetRtc (RpiRtcAlarmEnable, Enabled); + if (EFI_ERROR (Status)) { + return Status; + } + + 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 + +**/ +STATIC +VOID +EFIAPI +VirtualAddressChangeNotify ( + IN EFI_EVENT Event, + IN VOID *Context + ) +{ + EfiConvertPointer (0x0, (VOID **)&mFwProtocol); +} + +/** + 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_TIME Time; + EFI_EVENT VirtualAddressChangeEvent; + + Status = gBS->LocateProtocol ( + &gRaspberryPiFirmwareProtocolGuid, + NULL, + (VOID **)&mFwProtocol); + ASSERT_EFI_ERROR (Status); + if (EFI_ERROR (Status)) { + return Status; + } + + Status = gBS->CreateEventEx ( + EVT_NOTIFY_SIGNAL, + TPL_NOTIFY, + VirtualAddressChangeNotify, + NULL, + &gEfiEventVirtualAddressChangeGuid, + &VirtualAddressChangeEvent); + ASSERT_EFI_ERROR (Status); + if (EFI_ERROR (Status)) { + return Status; + } + + // + // Initial RTC time starts off at Epoch = 0, which is out + // of UEFI's bounds. Update it to the firmware build time. + // + Status = LibGetTime (&Time, NULL); + if (EFI_ERROR(Status)) { + return Status; + } + + if (!IsTimeValid (&Time)) { + EpochToEfiTime (BUILD_EPOCH, &Time); + Status = LibSetTime (&Time); + if (EFI_ERROR(Status)) { + return Status; + } + } + + return EFI_SUCCESS; +} diff --git a/Platform/RaspberryPi/Library/RpiRtcLib/RpiRtcLib.inf b/Platform/RaspberryPi/Library/RpiRtcLib/RpiRtcLib.inf new file mode 100644 index 00000000..f271f39c --- /dev/null +++ b/Platform/RaspberryPi/Library/RpiRtcLib/RpiRtcLib.inf @@ -0,0 +1,42 @@ +#/** @file +# +# Copyright (c) 2023, Mario Bălănică +# +# SPDX-License-Identifier: BSD-2-Clause-Patent +# +#**/ + +[Defines] + INF_VERSION = 0x0001001A + BASE_NAME = RpiRtcLib + FILE_GUID = 2c823916-13b7-48f3-bb6d-a8cf438b91fd + MODULE_TYPE = BASE + VERSION_STRING = 1.0 + LIBRARY_CLASS = RealTimeClockLib + +[Sources.common] + RpiRtcLib.c + +[Packages] + EmbeddedPkg/EmbeddedPkg.dec + MdePkg/MdePkg.dec + Platform/RaspberryPi/RaspberryPi.dec + +[LibraryClasses] + DebugLib + TimeBaseLib + UefiBootServicesTableLib + UefiRuntimeLib + +[Guids] + gEfiEventVirtualAddressChangeGuid + +[Protocols] + gRaspberryPiFirmwareProtocolGuid ## CONSUMES + +[Depex] + gRaspberryPiFirmwareProtocolGuid + +# Current usage of this library expects GCC in a UNIX-like shell environment with the date command +[BuildOptions] + GCC:*_*_*_CC_FLAGS = -DBUILD_EPOCH=`date +%s` diff --git a/Platform/RaspberryPi/RPi5/RPi5.dsc b/Platform/RaspberryPi/RPi5/RPi5.dsc index f8e08de2..f2a1992e 100644 --- a/Platform/RaspberryPi/RPi5/RPi5.dsc +++ b/Platform/RaspberryPi/RPi5/RPi5.dsc @@ -547,7 +547,7 @@ EmbeddedPkg/ResetRuntimeDxe/ResetRuntimeDxe.inf EmbeddedPkg/RealTimeClockRuntimeDxe/RealTimeClockRuntimeDxe.inf { - RealTimeClockLib|EmbeddedPkg/Library/VirtualRealTimeClockLib/VirtualRealTimeClockLib.inf + RealTimeClockLib|Platform/RaspberryPi/Library/RpiRtcLib/RpiRtcLib.inf } EmbeddedPkg/MetronomeDxe/MetronomeDxe.inf -- 2.51.2