1 /** @file
2   Implement EFI RealTimeClock runtime services via RTC Lib.
3 
4   Copyright (c) 2008 - 2010, Apple Inc. All rights reserved.<BR>
5   Copyright (c) 2011 - 2014, ARM Ltd. All rights reserved.<BR>
6   Copyright (c) 2019, Linaro Ltd. All rights reserved.<BR>
7 
8   SPDX-License-Identifier: BSD-2-Clause-Patent
9 
10 **/
11 
12 #include <PiDxe.h>
13 
14 #include <Guid/EventGroup.h>
15 #include <Guid/GlobalVariable.h>
16 
17 #include <Library/BaseLib.h>
18 #include <Library/DebugLib.h>
19 #include <Library/DxeServicesTableLib.h>
20 #include <Library/IoLib.h>
21 #include <Library/MemoryAllocationLib.h>
22 #include <Library/PcdLib.h>
23 #include <Library/RealTimeClockLib.h>
24 #include <Library/TimeBaseLib.h>
25 #include <Library/UefiBootServicesTableLib.h>
26 #include <Library/UefiLib.h>
27 #include <Library/UefiRuntimeServicesTableLib.h>
28 #include <Library/UefiRuntimeLib.h>
29 
30 #include <Protocol/RealTimeClock.h>
31 
32 #include "PL031RealTimeClock.h"
33 
34 STATIC BOOLEAN                mPL031Initialized = FALSE;
35 STATIC EFI_EVENT              mRtcVirtualAddrChangeEvent;
36 STATIC UINTN                  mPL031RtcBase;
37 
38 EFI_STATUS
IdentifyPL031(VOID)39 IdentifyPL031 (
40   VOID
41   )
42 {
43   EFI_STATUS    Status;
44 
45   // Check if this is a PrimeCell Peripheral
46   if (  (MmioRead8 (mPL031RtcBase + PL031_RTC_PCELL_ID0) != 0x0D)
47       || (MmioRead8 (mPL031RtcBase + PL031_RTC_PCELL_ID1) != 0xF0)
48       || (MmioRead8 (mPL031RtcBase + PL031_RTC_PCELL_ID2) != 0x05)
49       || (MmioRead8 (mPL031RtcBase + PL031_RTC_PCELL_ID3) != 0xB1)) {
50     Status = EFI_NOT_FOUND;
51     goto EXIT;
52   }
53 
54   // Check if this PrimeCell Peripheral is the PL031 Real Time Clock
55   if (  (MmioRead8 (mPL031RtcBase + PL031_RTC_PERIPH_ID0) != 0x31)
56       || (MmioRead8 (mPL031RtcBase + PL031_RTC_PERIPH_ID1) != 0x10)
57       || ((MmioRead8 (mPL031RtcBase + PL031_RTC_PERIPH_ID2) & 0xF) != 0x04)
58       || (MmioRead8 (mPL031RtcBase + PL031_RTC_PERIPH_ID3) != 0x00)) {
59     Status = EFI_NOT_FOUND;
60     goto EXIT;
61   }
62 
63   Status = EFI_SUCCESS;
64 
65   EXIT:
66   return Status;
67 }
68 
69 EFI_STATUS
InitializePL031(VOID)70 InitializePL031 (
71   VOID
72   )
73 {
74   EFI_STATUS    Status;
75 
76   // Prepare the hardware
77   Status = IdentifyPL031();
78   if (EFI_ERROR (Status)) {
79     goto EXIT;
80   }
81 
82   // Ensure interrupts are masked. We do not want RTC interrupts in UEFI
83   if ((MmioRead32 (mPL031RtcBase + PL031_RTC_IMSC_IRQ_MASK_SET_CLEAR_REGISTER) & PL031_SET_IRQ_MASK) != 0) {
84     MmioWrite32 (mPL031RtcBase + PL031_RTC_IMSC_IRQ_MASK_SET_CLEAR_REGISTER, 0);
85   }
86 
87   // Clear any existing interrupts
88   if ((MmioRead32 (mPL031RtcBase + PL031_RTC_RIS_RAW_IRQ_STATUS_REGISTER) & PL031_IRQ_TRIGGERED) == PL031_IRQ_TRIGGERED) {
89     MmioOr32 (mPL031RtcBase + PL031_RTC_ICR_IRQ_CLEAR_REGISTER, PL031_CLEAR_IRQ);
90   }
91 
92   // Start the clock counter
93   if ((MmioRead32 (mPL031RtcBase + PL031_RTC_CR_CONTROL_REGISTER) & PL031_RTC_ENABLED) != PL031_RTC_ENABLED) {
94     MmioOr32 (mPL031RtcBase + PL031_RTC_CR_CONTROL_REGISTER, PL031_RTC_ENABLED);
95   }
96 
97   mPL031Initialized = TRUE;
98 
99   EXIT:
100   return Status;
101 }
102 
103 /**
104   Returns the current time and date information, and the time-keeping capabilities
105   of the hardware platform.
106 
107   @param  Time                   A pointer to storage to receive a snapshot of the current time.
108   @param  Capabilities           An optional pointer to a buffer to receive the real time clock
109                                  device's capabilities.
110 
111   @retval EFI_SUCCESS            The operation completed successfully.
112   @retval EFI_INVALID_PARAMETER  Time is NULL.
113   @retval EFI_DEVICE_ERROR       The time could not be retrieved due to hardware error.
114   @retval EFI_SECURITY_VIOLATION The time could not be retrieved due to an authentication failure.
115 
116 **/
117 EFI_STATUS
118 EFIAPI
LibGetTime(OUT EFI_TIME * Time,OUT EFI_TIME_CAPABILITIES * Capabilities)119 LibGetTime (
120   OUT EFI_TIME                *Time,
121   OUT EFI_TIME_CAPABILITIES   *Capabilities
122   )
123 {
124   EFI_STATUS  Status = EFI_SUCCESS;
125   UINT32      EpochSeconds;
126 
127   // Ensure Time is a valid pointer
128   if (Time == NULL) {
129     return EFI_INVALID_PARAMETER;
130   }
131 
132   // Initialize the hardware if not already done
133   if (!mPL031Initialized) {
134     Status = InitializePL031 ();
135     if (EFI_ERROR (Status)) {
136       return Status;
137     }
138   }
139 
140   EpochSeconds = MmioRead32 (mPL031RtcBase + PL031_RTC_DR_DATA_REGISTER);
141 
142   // Adjust for the correct time zone
143   // The timezone setting also reflects the DST setting of the clock
144   if (Time->TimeZone != EFI_UNSPECIFIED_TIMEZONE) {
145     EpochSeconds += Time->TimeZone * SEC_PER_MIN;
146   } else if ((Time->Daylight & EFI_TIME_IN_DAYLIGHT) == EFI_TIME_IN_DAYLIGHT) {
147     // Convert to adjusted time, i.e. spring forwards one hour
148     EpochSeconds += SEC_PER_HOUR;
149   }
150 
151   // Convert from internal 32-bit time to UEFI time
152   EpochToEfiTime (EpochSeconds, Time);
153 
154   // Update the Capabilities info
155   if (Capabilities != NULL) {
156     // PL031 runs at frequency 1Hz
157     Capabilities->Resolution  = PL031_COUNTS_PER_SECOND;
158     // Accuracy in ppm multiplied by 1,000,000, e.g. for 50ppm set 50,000,000
159     Capabilities->Accuracy    = (UINT32)PcdGet32 (PcdPL031RtcPpmAccuracy);
160     // FALSE: Setting the time does not clear the values below the resolution level
161     Capabilities->SetsToZero  = FALSE;
162   }
163 
164   return EFI_SUCCESS;
165 }
166 
167 
168 /**
169   Sets the current local time and date information.
170 
171   @param  Time                  A pointer to the current time.
172 
173   @retval EFI_SUCCESS           The operation completed successfully.
174   @retval EFI_INVALID_PARAMETER A time field is out of range.
175   @retval EFI_DEVICE_ERROR      The time could not be set due due to hardware error.
176 
177 **/
178 EFI_STATUS
179 EFIAPI
LibSetTime(IN EFI_TIME * Time)180 LibSetTime (
181   IN  EFI_TIME                *Time
182   )
183 {
184   EFI_STATUS  Status;
185   UINT32      EpochSeconds;
186 
187   // Because the PL031 is a 32-bit counter counting seconds,
188   // the maximum time span is just over 136 years.
189   // Time is stored in Unix Epoch format, so it starts in 1970,
190   // Therefore it can not exceed the year 2106.
191   if ((Time->Year < 1970) || (Time->Year >= 2106)) {
192     return EFI_UNSUPPORTED;
193   }
194 
195   // Initialize the hardware if not already done
196   if (!mPL031Initialized) {
197     Status = InitializePL031 ();
198     if (EFI_ERROR (Status)) {
199       return Status;
200     }
201   }
202 
203   EpochSeconds = EfiTimeToEpoch (Time);
204 
205   // Adjust for the correct time zone, i.e. convert to UTC time zone
206   // The timezone setting also reflects the DST setting of the clock
207   if (Time->TimeZone != EFI_UNSPECIFIED_TIMEZONE) {
208     EpochSeconds -= Time->TimeZone * SEC_PER_MIN;
209   } else if ((Time->Daylight & EFI_TIME_IN_DAYLIGHT) == EFI_TIME_IN_DAYLIGHT) {
210     // Convert to un-adjusted time, i.e. fall back one hour
211     EpochSeconds -= SEC_PER_HOUR;
212   }
213 
214   // Set the PL031
215   MmioWrite32 (mPL031RtcBase + PL031_RTC_LR_LOAD_REGISTER, EpochSeconds);
216 
217   return EFI_SUCCESS;
218 }
219 
220 
221 /**
222   Returns the current wakeup alarm clock setting.
223 
224   @param  Enabled               Indicates if the alarm is currently enabled or disabled.
225   @param  Pending               Indicates if the alarm signal is pending and requires acknowledgement.
226   @param  Time                  The current alarm setting.
227 
228   @retval EFI_SUCCESS           The alarm settings were returned.
229   @retval EFI_INVALID_PARAMETER Any parameter is NULL.
230   @retval EFI_DEVICE_ERROR      The wakeup time could not be retrieved due to a hardware error.
231 
232 **/
233 EFI_STATUS
234 EFIAPI
LibGetWakeupTime(OUT BOOLEAN * Enabled,OUT BOOLEAN * Pending,OUT EFI_TIME * Time)235 LibGetWakeupTime (
236   OUT BOOLEAN     *Enabled,
237   OUT BOOLEAN     *Pending,
238   OUT EFI_TIME    *Time
239   )
240 {
241   // Not a required feature
242   return EFI_UNSUPPORTED;
243 }
244 
245 
246 /**
247   Sets the system wakeup alarm clock time.
248 
249   @param  Enabled               Enable or disable the wakeup alarm.
250   @param  Time                  If Enable is TRUE, the time to set the wakeup alarm for.
251 
252   @retval EFI_SUCCESS           If Enable is TRUE, then the wakeup alarm was enabled. If
253                                 Enable is FALSE, then the wakeup alarm was disabled.
254   @retval EFI_INVALID_PARAMETER A time field is out of range.
255   @retval EFI_DEVICE_ERROR      The wakeup time could not be set due to a hardware error.
256   @retval EFI_UNSUPPORTED       A wakeup timer is not supported on this platform.
257 
258 **/
259 EFI_STATUS
260 EFIAPI
LibSetWakeupTime(IN BOOLEAN Enabled,OUT EFI_TIME * Time)261 LibSetWakeupTime (
262   IN BOOLEAN      Enabled,
263   OUT EFI_TIME    *Time
264   )
265 {
266   // Not a required feature
267   return EFI_UNSUPPORTED;
268 }
269 
270 /**
271   Fixup internal data so that EFI can be call in virtual mode.
272   Call the passed in Child Notify event and convert any pointers in
273   lib to virtual mode.
274 
275   @param[in]    Event   The Event that is being processed
276   @param[in]    Context Event Context
277 **/
278 VOID
279 EFIAPI
LibRtcVirtualNotifyEvent(IN EFI_EVENT Event,IN VOID * Context)280 LibRtcVirtualNotifyEvent (
281   IN EFI_EVENT        Event,
282   IN VOID             *Context
283   )
284 {
285   //
286   // Only needed if you are going to support the OS calling RTC functions in virtual mode.
287   // You will need to call EfiConvertPointer (). To convert any stored physical addresses
288   // to virtual address. After the OS transitions to calling in virtual mode, all future
289   // runtime calls will be made in virtual mode.
290   //
291   EfiConvertPointer (0x0, (VOID**)&mPL031RtcBase);
292   return;
293 }
294 
295 /**
296   This is the declaration of an EFI image entry point. This can be the entry point to an application
297   written to this specification, an EFI boot service driver, or an EFI runtime driver.
298 
299   @param  ImageHandle           Handle that identifies the loaded image.
300   @param  SystemTable           System Table for this image.
301 
302   @retval EFI_SUCCESS           The operation completed successfully.
303 
304 **/
305 EFI_STATUS
306 EFIAPI
LibRtcInitialize(IN EFI_HANDLE ImageHandle,IN EFI_SYSTEM_TABLE * SystemTable)307 LibRtcInitialize (
308   IN EFI_HANDLE                            ImageHandle,
309   IN EFI_SYSTEM_TABLE                      *SystemTable
310   )
311 {
312   EFI_STATUS    Status;
313   EFI_HANDLE    Handle;
314 
315   // Initialize RTC Base Address
316   mPL031RtcBase = PcdGet32 (PcdPL031RtcBase);
317 
318   // Declare the controller as EFI_MEMORY_RUNTIME
319   Status = gDS->AddMemorySpace (
320                   EfiGcdMemoryTypeMemoryMappedIo,
321                   mPL031RtcBase, SIZE_4KB,
322                   EFI_MEMORY_UC | EFI_MEMORY_RUNTIME
323                   );
324   if (EFI_ERROR (Status)) {
325     return Status;
326   }
327 
328   Status = gDS->SetMemorySpaceAttributes (mPL031RtcBase, SIZE_4KB, EFI_MEMORY_UC | EFI_MEMORY_RUNTIME);
329   if (EFI_ERROR (Status)) {
330     return Status;
331   }
332 
333   // Install the protocol
334   Handle = NULL;
335   Status = gBS->InstallMultipleProtocolInterfaces (
336                   &Handle,
337                   &gEfiRealTimeClockArchProtocolGuid,  NULL,
338                   NULL
339                  );
340   ASSERT_EFI_ERROR (Status);
341 
342   //
343   // Register for the virtual address change event
344   //
345   Status = gBS->CreateEventEx (
346                   EVT_NOTIFY_SIGNAL,
347                   TPL_NOTIFY,
348                   LibRtcVirtualNotifyEvent,
349                   NULL,
350                   &gEfiEventVirtualAddressChangeGuid,
351                   &mRtcVirtualAddrChangeEvent
352                   );
353   ASSERT_EFI_ERROR (Status);
354 
355   return Status;
356 }
357