]> git.proxmox.com Git - mirror_edk2.git/blob - PcAtChipsetPkg/PcatRealTimeClockRuntimeDxe/PcRtc.c
Added 2 new PCDs for minimal and maximal valid year in RTC.
[mirror_edk2.git] / PcAtChipsetPkg / PcatRealTimeClockRuntimeDxe / PcRtc.c
1 /** @file
2 RTC Architectural Protocol GUID as defined in DxeCis 0.96.
3
4 Copyright (c) 2006 - 2015, Intel Corporation. All rights reserved.<BR>
5 This program and the accompanying materials
6 are licensed and made available under the terms and conditions of the BSD License
7 which accompanies this distribution. The full text of the license may be found at
8 http://opensource.org/licenses/bsd-license.php
9
10 THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
11 WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
12
13 **/
14
15 #include "PcRtc.h"
16
17 /**
18 Compare the Hour, Minute and Second of the From time and the To time.
19
20 Only compare H/M/S in EFI_TIME and ignore other fields here.
21
22 @param From the first time
23 @param To the second time
24
25 @return >0 The H/M/S of the From time is later than those of To time
26 @return ==0 The H/M/S of the From time is same as those of To time
27 @return <0 The H/M/S of the From time is earlier than those of To time
28 **/
29 INTN
30 CompareHMS (
31 IN EFI_TIME *From,
32 IN EFI_TIME *To
33 );
34
35 /**
36 To check if second date is later than first date within 24 hours.
37
38 @param From the first date
39 @param To the second date
40
41 @retval TRUE From is previous to To within 24 hours.
42 @retval FALSE From is later, or it is previous to To more than 24 hours.
43 **/
44 BOOLEAN
45 IsWithinOneDay (
46 IN EFI_TIME *From,
47 IN EFI_TIME *To
48 );
49
50 /**
51 Read RTC content through its registers.
52
53 @param Address Address offset of RTC. It is recommended to use macros such as
54 RTC_ADDRESS_SECONDS.
55
56 @return The data of UINT8 type read from RTC.
57 **/
58 UINT8
59 RtcRead (
60 IN UINT8 Address
61 )
62 {
63 IoWrite8 (PCAT_RTC_ADDRESS_REGISTER, (UINT8) (Address | (UINT8) (IoRead8 (PCAT_RTC_ADDRESS_REGISTER) & 0x80)));
64 return IoRead8 (PCAT_RTC_DATA_REGISTER);
65 }
66
67 /**
68 Write RTC through its registers.
69
70 @param Address Address offset of RTC. It is recommended to use macros such as
71 RTC_ADDRESS_SECONDS.
72 @param Data The content you want to write into RTC.
73
74 **/
75 VOID
76 RtcWrite (
77 IN UINT8 Address,
78 IN UINT8 Data
79 )
80 {
81 IoWrite8 (PCAT_RTC_ADDRESS_REGISTER, (UINT8) (Address | (UINT8) (IoRead8 (PCAT_RTC_ADDRESS_REGISTER) & 0x80)));
82 IoWrite8 (PCAT_RTC_DATA_REGISTER, Data);
83 }
84
85 /**
86 Initialize RTC.
87
88 @param Global For global use inside this module.
89
90 @retval EFI_DEVICE_ERROR Initialization failed due to device error.
91 @retval EFI_SUCCESS Initialization successful.
92
93 **/
94 EFI_STATUS
95 PcRtcInit (
96 IN PC_RTC_MODULE_GLOBALS *Global
97 )
98 {
99 EFI_STATUS Status;
100 RTC_REGISTER_A RegisterA;
101 RTC_REGISTER_B RegisterB;
102 RTC_REGISTER_D RegisterD;
103 UINT8 Century;
104 EFI_TIME Time;
105 UINTN DataSize;
106 UINT32 TimerVar;
107 BOOLEAN Enabled;
108 BOOLEAN Pending;
109
110 //
111 // Acquire RTC Lock to make access to RTC atomic
112 //
113 if (!EfiAtRuntime ()) {
114 EfiAcquireLock (&Global->RtcLock);
115 }
116 //
117 // Initialize RTC Register
118 //
119 // Make sure Division Chain is properly configured,
120 // or RTC clock won't "tick" -- time won't increment
121 //
122 RegisterA.Data = RTC_INIT_REGISTER_A;
123 RtcWrite (RTC_ADDRESS_REGISTER_A, RegisterA.Data);
124
125 //
126 // Read Register B
127 //
128 RegisterB.Data = RtcRead (RTC_ADDRESS_REGISTER_B);
129
130 //
131 // Clear RTC flag register
132 //
133 RtcRead (RTC_ADDRESS_REGISTER_C);
134
135 //
136 // Clear RTC register D
137 //
138 RegisterD.Data = RTC_INIT_REGISTER_D;
139 RtcWrite (RTC_ADDRESS_REGISTER_D, RegisterD.Data);
140
141 //
142 // Wait for up to 0.1 seconds for the RTC to be updated
143 //
144 Status = RtcWaitToUpdate (PcdGet32 (PcdRealTimeClockUpdateTimeout));
145 if (EFI_ERROR (Status)) {
146 //
147 // Set the variable with default value if the RTC is functioning incorrectly.
148 //
149 Global->SavedTimeZone = EFI_UNSPECIFIED_TIMEZONE;
150 Global->Daylight = 0;
151 if (!EfiAtRuntime ()) {
152 EfiReleaseLock (&Global->RtcLock);
153 }
154 return EFI_DEVICE_ERROR;
155 }
156 //
157 // Get the Time/Date/Daylight Savings values.
158 //
159 Time.Second = RtcRead (RTC_ADDRESS_SECONDS);
160 Time.Minute = RtcRead (RTC_ADDRESS_MINUTES);
161 Time.Hour = RtcRead (RTC_ADDRESS_HOURS);
162 Time.Day = RtcRead (RTC_ADDRESS_DAY_OF_THE_MONTH);
163 Time.Month = RtcRead (RTC_ADDRESS_MONTH);
164 Time.Year = RtcRead (RTC_ADDRESS_YEAR);
165
166 Century = RtcRead (RTC_ADDRESS_CENTURY);
167
168 //
169 // Set RTC configuration after get original time
170 // The value of bit AIE should be reserved.
171 //
172 RtcWrite (RTC_ADDRESS_REGISTER_B, (UINT8)(RTC_INIT_REGISTER_B | (RegisterB.Data & BIT5)));
173
174 //
175 // Release RTC Lock.
176 //
177 if (!EfiAtRuntime ()) {
178 EfiReleaseLock (&Global->RtcLock);
179 }
180
181 //
182 // Get the data of Daylight saving and time zone, if they have been
183 // stored in NV variable during previous boot.
184 //
185 DataSize = sizeof (UINT32);
186 Status = EfiGetVariable (
187 L"RTC",
188 &gEfiCallerIdGuid,
189 NULL,
190 &DataSize,
191 (VOID *) &TimerVar
192 );
193 if (!EFI_ERROR (Status)) {
194 Time.TimeZone = (INT16) TimerVar;
195 Time.Daylight = (UINT8) (TimerVar >> 16);
196 } else {
197 Time.TimeZone = EFI_UNSPECIFIED_TIMEZONE;
198 Time.Daylight = 0;
199 }
200
201 //
202 // Validate time fields
203 //
204 Status = ConvertRtcTimeToEfiTime (&Time, Century, RegisterB);
205 if (!EFI_ERROR (Status)) {
206 Status = RtcTimeFieldsValid (&Time);
207 }
208 if (EFI_ERROR (Status)) {
209 //
210 // Report Status Code to indicate that the RTC has bad date and time
211 //
212 REPORT_STATUS_CODE (
213 EFI_ERROR_CODE | EFI_ERROR_MINOR,
214 (EFI_SOFTWARE_DXE_RT_DRIVER | EFI_SW_EC_BAD_DATE_TIME)
215 );
216 Time.Second = RTC_INIT_SECOND;
217 Time.Minute = RTC_INIT_MINUTE;
218 Time.Hour = RTC_INIT_HOUR;
219 Time.Day = RTC_INIT_DAY;
220 Time.Month = RTC_INIT_MONTH;
221 Time.Year = RTC_INIT_YEAR;
222 Time.Nanosecond = 0;
223 Time.TimeZone = EFI_UNSPECIFIED_TIMEZONE;
224 Time.Daylight = 0;
225 }
226
227 //
228 // Reset time value according to new RTC configuration
229 //
230 Status = PcRtcSetTime (&Time, Global);
231 if (EFI_ERROR (Status)) {
232 return EFI_DEVICE_ERROR;
233 }
234
235 //
236 // Reset wakeup time value to valid state when wakeup alarm is disabled and wakeup time is invalid.
237 // Global variable has already had valid SavedTimeZone and Daylight,
238 // so we can use them to get and set wakeup time.
239 //
240 Status = PcRtcGetWakeupTime (&Enabled, &Pending, &Time, Global);
241 if ((Enabled) || (!EFI_ERROR (Status))) {
242 return EFI_SUCCESS;
243 }
244
245 //
246 // When wakeup time is disabled and invalid, reset wakeup time register to valid state
247 // but keep wakeup alarm disabled.
248 //
249 Time.Second = RTC_INIT_SECOND;
250 Time.Minute = RTC_INIT_MINUTE;
251 Time.Hour = RTC_INIT_HOUR;
252 Time.Day = RTC_INIT_DAY;
253 Time.Month = RTC_INIT_MONTH;
254 Time.Year = RTC_INIT_YEAR;
255 Time.Nanosecond = 0;
256 Time.TimeZone = Global->SavedTimeZone;
257 Time.Daylight = Global->Daylight;;
258
259 //
260 // Acquire RTC Lock to make access to RTC atomic
261 //
262 if (!EfiAtRuntime ()) {
263 EfiAcquireLock (&Global->RtcLock);
264 }
265 //
266 // Wait for up to 0.1 seconds for the RTC to be updated
267 //
268 Status = RtcWaitToUpdate (PcdGet32 (PcdRealTimeClockUpdateTimeout));
269 if (EFI_ERROR (Status)) {
270 if (!EfiAtRuntime ()) {
271 EfiReleaseLock (&Global->RtcLock);
272 }
273 return EFI_DEVICE_ERROR;
274 }
275
276 ConvertEfiTimeToRtcTime (&Time, RegisterB, &Century);
277
278 //
279 // Set the Y/M/D info to variable as it has no corresponding hw registers.
280 //
281 Status = EfiSetVariable (
282 L"RTCALARM",
283 &gEfiCallerIdGuid,
284 EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_RUNTIME_ACCESS | EFI_VARIABLE_NON_VOLATILE,
285 sizeof (Time),
286 &Time
287 );
288 if (EFI_ERROR (Status)) {
289 if (!EfiAtRuntime ()) {
290 EfiReleaseLock (&Global->RtcLock);
291 }
292 return EFI_DEVICE_ERROR;
293 }
294
295 //
296 // Inhibit updates of the RTC
297 //
298 RegisterB.Bits.Set = 1;
299 RtcWrite (RTC_ADDRESS_REGISTER_B, RegisterB.Data);
300
301 //
302 // Set RTC alarm time registers
303 //
304 RtcWrite (RTC_ADDRESS_SECONDS_ALARM, Time.Second);
305 RtcWrite (RTC_ADDRESS_MINUTES_ALARM, Time.Minute);
306 RtcWrite (RTC_ADDRESS_HOURS_ALARM, Time.Hour);
307
308 //
309 // Allow updates of the RTC registers
310 //
311 RegisterB.Bits.Set = 0;
312 RtcWrite (RTC_ADDRESS_REGISTER_B, RegisterB.Data);
313
314 //
315 // Release RTC Lock.
316 //
317 if (!EfiAtRuntime ()) {
318 EfiReleaseLock (&Global->RtcLock);
319 }
320 return EFI_SUCCESS;
321 }
322
323 /**
324 Returns the current time and date information, and the time-keeping capabilities
325 of the hardware platform.
326
327 @param Time A pointer to storage to receive a snapshot of the current time.
328 @param Capabilities An optional pointer to a buffer to receive the real time clock
329 device's capabilities.
330 @param Global For global use inside this module.
331
332 @retval EFI_SUCCESS The operation completed successfully.
333 @retval EFI_INVALID_PARAMETER Time is NULL.
334 @retval EFI_DEVICE_ERROR The time could not be retrieved due to hardware error.
335
336 **/
337 EFI_STATUS
338 PcRtcGetTime (
339 OUT EFI_TIME *Time,
340 OUT EFI_TIME_CAPABILITIES *Capabilities, OPTIONAL
341 IN PC_RTC_MODULE_GLOBALS *Global
342 )
343 {
344 EFI_STATUS Status;
345 RTC_REGISTER_B RegisterB;
346 UINT8 Century;
347
348 //
349 // Check parameters for null pointer
350 //
351 if (Time == NULL) {
352 return EFI_INVALID_PARAMETER;
353
354 }
355 //
356 // Acquire RTC Lock to make access to RTC atomic
357 //
358 if (!EfiAtRuntime ()) {
359 EfiAcquireLock (&Global->RtcLock);
360 }
361 //
362 // Wait for up to 0.1 seconds for the RTC to be updated
363 //
364 Status = RtcWaitToUpdate (PcdGet32 (PcdRealTimeClockUpdateTimeout));
365 if (EFI_ERROR (Status)) {
366 if (!EfiAtRuntime ()) {
367 EfiReleaseLock (&Global->RtcLock);
368 }
369 return Status;
370 }
371 //
372 // Read Register B
373 //
374 RegisterB.Data = RtcRead (RTC_ADDRESS_REGISTER_B);
375
376 //
377 // Get the Time/Date/Daylight Savings values.
378 //
379 Time->Second = RtcRead (RTC_ADDRESS_SECONDS);
380 Time->Minute = RtcRead (RTC_ADDRESS_MINUTES);
381 Time->Hour = RtcRead (RTC_ADDRESS_HOURS);
382 Time->Day = RtcRead (RTC_ADDRESS_DAY_OF_THE_MONTH);
383 Time->Month = RtcRead (RTC_ADDRESS_MONTH);
384 Time->Year = RtcRead (RTC_ADDRESS_YEAR);
385
386 Century = RtcRead (RTC_ADDRESS_CENTURY);
387
388 //
389 // Release RTC Lock.
390 //
391 if (!EfiAtRuntime ()) {
392 EfiReleaseLock (&Global->RtcLock);
393 }
394
395 //
396 // Get the variable that contains the TimeZone and Daylight fields
397 //
398 Time->TimeZone = Global->SavedTimeZone;
399 Time->Daylight = Global->Daylight;
400
401 //
402 // Make sure all field values are in correct range
403 //
404 Status = ConvertRtcTimeToEfiTime (Time, Century, RegisterB);
405 if (!EFI_ERROR (Status)) {
406 Status = RtcTimeFieldsValid (Time);
407 }
408 if (EFI_ERROR (Status)) {
409 return EFI_DEVICE_ERROR;
410 }
411
412 //
413 // Fill in Capabilities if it was passed in
414 //
415 if (Capabilities != NULL) {
416 Capabilities->Resolution = 1;
417 //
418 // 1 hertz
419 //
420 Capabilities->Accuracy = 50000000;
421 //
422 // 50 ppm
423 //
424 Capabilities->SetsToZero = FALSE;
425 }
426
427 return EFI_SUCCESS;
428 }
429
430 /**
431 Sets the current local time and date information.
432
433 @param Time A pointer to the current time.
434 @param Global For global use inside this module.
435
436 @retval EFI_SUCCESS The operation completed successfully.
437 @retval EFI_INVALID_PARAMETER A time field is out of range.
438 @retval EFI_DEVICE_ERROR The time could not be set due due to hardware error.
439
440 **/
441 EFI_STATUS
442 PcRtcSetTime (
443 IN EFI_TIME *Time,
444 IN PC_RTC_MODULE_GLOBALS *Global
445 )
446 {
447 EFI_STATUS Status;
448 EFI_TIME RtcTime;
449 RTC_REGISTER_B RegisterB;
450 UINT8 Century;
451 UINT32 TimerVar;
452
453 if (Time == NULL) {
454 return EFI_INVALID_PARAMETER;
455 }
456 //
457 // Make sure that the time fields are valid
458 //
459 Status = RtcTimeFieldsValid (Time);
460 if (EFI_ERROR (Status)) {
461 return Status;
462 }
463
464 CopyMem (&RtcTime, Time, sizeof (EFI_TIME));
465
466 //
467 // Acquire RTC Lock to make access to RTC atomic
468 //
469 if (!EfiAtRuntime ()) {
470 EfiAcquireLock (&Global->RtcLock);
471 }
472 //
473 // Wait for up to 0.1 seconds for the RTC to be updated
474 //
475 Status = RtcWaitToUpdate (PcdGet32 (PcdRealTimeClockUpdateTimeout));
476 if (EFI_ERROR (Status)) {
477 if (!EfiAtRuntime ()) {
478 EfiReleaseLock (&Global->RtcLock);
479 }
480 return Status;
481 }
482
483 //
484 // Write timezone and daylight to RTC variable
485 //
486 TimerVar = Time->Daylight;
487 TimerVar = (UINT32) ((TimerVar << 16) | (UINT16)(Time->TimeZone));
488 Status = EfiSetVariable (
489 L"RTC",
490 &gEfiCallerIdGuid,
491 EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_RUNTIME_ACCESS | EFI_VARIABLE_NON_VOLATILE,
492 sizeof (TimerVar),
493 &TimerVar
494 );
495 if (EFI_ERROR (Status)) {
496 if (!EfiAtRuntime ()) {
497 EfiReleaseLock (&Global->RtcLock);
498 }
499 return EFI_DEVICE_ERROR;
500 }
501
502 //
503 // Read Register B, and inhibit updates of the RTC
504 //
505 RegisterB.Data = RtcRead (RTC_ADDRESS_REGISTER_B);
506 RegisterB.Bits.Set = 1;
507 RtcWrite (RTC_ADDRESS_REGISTER_B, RegisterB.Data);
508
509 ConvertEfiTimeToRtcTime (&RtcTime, RegisterB, &Century);
510
511 RtcWrite (RTC_ADDRESS_SECONDS, RtcTime.Second);
512 RtcWrite (RTC_ADDRESS_MINUTES, RtcTime.Minute);
513 RtcWrite (RTC_ADDRESS_HOURS, RtcTime.Hour);
514 RtcWrite (RTC_ADDRESS_DAY_OF_THE_MONTH, RtcTime.Day);
515 RtcWrite (RTC_ADDRESS_MONTH, RtcTime.Month);
516 RtcWrite (RTC_ADDRESS_YEAR, (UINT8) RtcTime.Year);
517 RtcWrite (RTC_ADDRESS_CENTURY, Century);
518
519 //
520 // Allow updates of the RTC registers
521 //
522 RegisterB.Bits.Set = 0;
523 RtcWrite (RTC_ADDRESS_REGISTER_B, RegisterB.Data);
524
525 //
526 // Release RTC Lock.
527 //
528 if (!EfiAtRuntime ()) {
529 EfiReleaseLock (&Global->RtcLock);
530 }
531 //
532 // Set the variable that contains the TimeZone and Daylight fields
533 //
534 Global->SavedTimeZone = Time->TimeZone;
535 Global->Daylight = Time->Daylight;
536
537 return EFI_SUCCESS;
538 }
539
540 /**
541 Returns the current wakeup alarm clock setting.
542
543 @param Enabled Indicates if the alarm is currently enabled or disabled.
544 @param Pending Indicates if the alarm signal is pending and requires acknowledgment.
545 @param Time The current alarm setting.
546 @param Global For global use inside this module.
547
548 @retval EFI_SUCCESS The alarm settings were returned.
549 @retval EFI_INVALID_PARAMETER Enabled is NULL.
550 @retval EFI_INVALID_PARAMETER Pending is NULL.
551 @retval EFI_INVALID_PARAMETER Time is NULL.
552 @retval EFI_DEVICE_ERROR The wakeup time could not be retrieved due to a hardware error.
553 @retval EFI_UNSUPPORTED A wakeup timer is not supported on this platform.
554
555 **/
556 EFI_STATUS
557 PcRtcGetWakeupTime (
558 OUT BOOLEAN *Enabled,
559 OUT BOOLEAN *Pending,
560 OUT EFI_TIME *Time,
561 IN PC_RTC_MODULE_GLOBALS *Global
562 )
563 {
564 EFI_STATUS Status;
565 RTC_REGISTER_B RegisterB;
566 RTC_REGISTER_C RegisterC;
567 UINT8 Century;
568 EFI_TIME RtcTime;
569 UINTN DataSize;
570
571 //
572 // Check parameters for null pointers
573 //
574 if ((Enabled == NULL) || (Pending == NULL) || (Time == NULL)) {
575 return EFI_INVALID_PARAMETER;
576
577 }
578 //
579 // Acquire RTC Lock to make access to RTC atomic
580 //
581 if (!EfiAtRuntime ()) {
582 EfiAcquireLock (&Global->RtcLock);
583 }
584 //
585 // Wait for up to 0.1 seconds for the RTC to be updated
586 //
587 Status = RtcWaitToUpdate (PcdGet32 (PcdRealTimeClockUpdateTimeout));
588 if (EFI_ERROR (Status)) {
589 if (!EfiAtRuntime ()) {
590 EfiReleaseLock (&Global->RtcLock);
591 }
592 return EFI_DEVICE_ERROR;
593 }
594 //
595 // Read Register B and Register C
596 //
597 RegisterB.Data = RtcRead (RTC_ADDRESS_REGISTER_B);
598 RegisterC.Data = RtcRead (RTC_ADDRESS_REGISTER_C);
599
600 //
601 // Get the Time/Date/Daylight Savings values.
602 //
603 *Enabled = RegisterB.Bits.Aie;
604 *Pending = RegisterC.Bits.Af;
605
606 Time->Second = RtcRead (RTC_ADDRESS_SECONDS_ALARM);
607 Time->Minute = RtcRead (RTC_ADDRESS_MINUTES_ALARM);
608 Time->Hour = RtcRead (RTC_ADDRESS_HOURS_ALARM);
609 Time->Day = RtcRead (RTC_ADDRESS_DAY_OF_THE_MONTH);
610 Time->Month = RtcRead (RTC_ADDRESS_MONTH);
611 Time->Year = RtcRead (RTC_ADDRESS_YEAR);
612 Time->TimeZone = Global->SavedTimeZone;
613 Time->Daylight = Global->Daylight;
614
615 Century = RtcRead (RTC_ADDRESS_CENTURY);
616
617 //
618 // Get the alarm info from variable
619 //
620 DataSize = sizeof (EFI_TIME);
621 Status = EfiGetVariable (
622 L"RTCALARM",
623 &gEfiCallerIdGuid,
624 NULL,
625 &DataSize,
626 &RtcTime
627 );
628 if (!EFI_ERROR (Status)) {
629 //
630 // The alarm variable exists. In this case, we read variable to get info.
631 //
632 Time->Day = RtcTime.Day;
633 Time->Month = RtcTime.Month;
634 Time->Year = RtcTime.Year;
635 }
636
637 //
638 // Release RTC Lock.
639 //
640 if (!EfiAtRuntime ()) {
641 EfiReleaseLock (&Global->RtcLock);
642 }
643
644 //
645 // Make sure all field values are in correct range
646 //
647 Status = ConvertRtcTimeToEfiTime (Time, Century, RegisterB);
648 if (!EFI_ERROR (Status)) {
649 Status = RtcTimeFieldsValid (Time);
650 }
651 if (EFI_ERROR (Status)) {
652 return EFI_DEVICE_ERROR;
653 }
654
655 return EFI_SUCCESS;
656 }
657
658 /**
659 Sets the system wakeup alarm clock time.
660
661 @param Enabled Enable or disable the wakeup alarm.
662 @param Time If Enable is TRUE, the time to set the wakeup alarm for.
663 If Enable is FALSE, then this parameter is optional, and may be NULL.
664 @param Global For global use inside this module.
665
666 @retval EFI_SUCCESS If Enable is TRUE, then the wakeup alarm was enabled.
667 If Enable is FALSE, then the wakeup alarm was disabled.
668 @retval EFI_INVALID_PARAMETER A time field is out of range.
669 @retval EFI_DEVICE_ERROR The wakeup time could not be set due to a hardware error.
670 @retval EFI_UNSUPPORTED A wakeup timer is not supported on this platform.
671
672 **/
673 EFI_STATUS
674 PcRtcSetWakeupTime (
675 IN BOOLEAN Enable,
676 IN EFI_TIME *Time, OPTIONAL
677 IN PC_RTC_MODULE_GLOBALS *Global
678 )
679 {
680 EFI_STATUS Status;
681 EFI_TIME RtcTime;
682 RTC_REGISTER_B RegisterB;
683 UINT8 Century;
684 EFI_TIME_CAPABILITIES Capabilities;
685
686 ZeroMem (&RtcTime, sizeof (RtcTime));
687
688 if (Enable) {
689
690 if (Time == NULL) {
691 return EFI_INVALID_PARAMETER;
692 }
693 //
694 // Make sure that the time fields are valid
695 //
696 Status = RtcTimeFieldsValid (Time);
697 if (EFI_ERROR (Status)) {
698 return EFI_INVALID_PARAMETER;
699 }
700 //
701 // Just support set alarm time within 24 hours
702 //
703 PcRtcGetTime (&RtcTime, &Capabilities, Global);
704 Status = RtcTimeFieldsValid (&RtcTime);
705 if (EFI_ERROR (Status)) {
706 return EFI_DEVICE_ERROR;
707 }
708 if (!IsWithinOneDay (&RtcTime, Time)) {
709 return EFI_UNSUPPORTED;
710 }
711 //
712 // Make a local copy of the time and date
713 //
714 CopyMem (&RtcTime, Time, sizeof (EFI_TIME));
715
716 }
717 //
718 // Acquire RTC Lock to make access to RTC atomic
719 //
720 if (!EfiAtRuntime ()) {
721 EfiAcquireLock (&Global->RtcLock);
722 }
723 //
724 // Wait for up to 0.1 seconds for the RTC to be updated
725 //
726 Status = RtcWaitToUpdate (PcdGet32 (PcdRealTimeClockUpdateTimeout));
727 if (EFI_ERROR (Status)) {
728 if (!EfiAtRuntime ()) {
729 EfiReleaseLock (&Global->RtcLock);
730 }
731 return EFI_DEVICE_ERROR;
732 }
733 //
734 // Read Register B
735 //
736 RegisterB.Data = RtcRead (RTC_ADDRESS_REGISTER_B);
737
738 if (Enable) {
739 ConvertEfiTimeToRtcTime (&RtcTime, RegisterB, &Century);
740 } else {
741 //
742 // if the alarm is disable, record the current setting.
743 //
744 RtcTime.Second = RtcRead (RTC_ADDRESS_SECONDS_ALARM);
745 RtcTime.Minute = RtcRead (RTC_ADDRESS_MINUTES_ALARM);
746 RtcTime.Hour = RtcRead (RTC_ADDRESS_HOURS_ALARM);
747 RtcTime.Day = RtcRead (RTC_ADDRESS_DAY_OF_THE_MONTH);
748 RtcTime.Month = RtcRead (RTC_ADDRESS_MONTH);
749 RtcTime.Year = RtcRead (RTC_ADDRESS_YEAR);
750 RtcTime.TimeZone = Global->SavedTimeZone;
751 RtcTime.Daylight = Global->Daylight;
752 }
753
754 //
755 // Set the Y/M/D info to variable as it has no corresponding hw registers.
756 //
757 Status = EfiSetVariable (
758 L"RTCALARM",
759 &gEfiCallerIdGuid,
760 EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_RUNTIME_ACCESS | EFI_VARIABLE_NON_VOLATILE,
761 sizeof (RtcTime),
762 &RtcTime
763 );
764 if (EFI_ERROR (Status)) {
765 if (!EfiAtRuntime ()) {
766 EfiReleaseLock (&Global->RtcLock);
767 }
768 return EFI_DEVICE_ERROR;
769 }
770
771 //
772 // Inhibit updates of the RTC
773 //
774 RegisterB.Bits.Set = 1;
775 RtcWrite (RTC_ADDRESS_REGISTER_B, RegisterB.Data);
776
777 if (Enable) {
778 //
779 // Set RTC alarm time
780 //
781 RtcWrite (RTC_ADDRESS_SECONDS_ALARM, RtcTime.Second);
782 RtcWrite (RTC_ADDRESS_MINUTES_ALARM, RtcTime.Minute);
783 RtcWrite (RTC_ADDRESS_HOURS_ALARM, RtcTime.Hour);
784
785 RegisterB.Bits.Aie = 1;
786
787 } else {
788 RegisterB.Bits.Aie = 0;
789 }
790 //
791 // Allow updates of the RTC registers
792 //
793 RegisterB.Bits.Set = 0;
794 RtcWrite (RTC_ADDRESS_REGISTER_B, RegisterB.Data);
795
796 //
797 // Release RTC Lock.
798 //
799 if (!EfiAtRuntime ()) {
800 EfiReleaseLock (&Global->RtcLock);
801 }
802 return EFI_SUCCESS;
803 }
804
805
806 /**
807 Checks an 8-bit BCD value, and converts to an 8-bit value if valid.
808
809 This function checks the 8-bit BCD value specified by Value.
810 If valid, the function converts it to an 8-bit value and returns it.
811 Otherwise, return 0xff.
812
813 @param Value The 8-bit BCD value to check and convert
814
815 @return The 8-bit value converted. Or 0xff if Value is invalid.
816
817 **/
818 UINT8
819 CheckAndConvertBcd8ToDecimal8 (
820 IN UINT8 Value
821 )
822 {
823 if ((Value < 0xa0) && ((Value & 0xf) < 0xa)) {
824 return BcdToDecimal8 (Value);
825 }
826
827 return 0xff;
828 }
829
830 /**
831 Converts time read from RTC to EFI_TIME format defined by UEFI spec.
832
833 This function converts raw time data read from RTC to the EFI_TIME format
834 defined by UEFI spec.
835 If data mode of RTC is BCD, then converts it to decimal,
836 If RTC is in 12-hour format, then converts it to 24-hour format.
837
838 @param Time On input, the time data read from RTC to convert
839 On output, the time converted to UEFI format
840 @param Century Value of century read from RTC.
841 @param RegisterB Value of Register B of RTC, indicating data mode
842 and hour format.
843
844 @retval EFI_INVALID_PARAMETER Parameters passed in are invalid.
845 @retval EFI_SUCCESS Convert RTC time to EFI time successfully.
846
847 **/
848 EFI_STATUS
849 ConvertRtcTimeToEfiTime (
850 IN OUT EFI_TIME *Time,
851 IN UINT8 Century,
852 IN RTC_REGISTER_B RegisterB
853 )
854 {
855 BOOLEAN IsPM;
856
857 if ((Time->Hour & 0x80) != 0) {
858 IsPM = TRUE;
859 } else {
860 IsPM = FALSE;
861 }
862
863 Time->Hour = (UINT8) (Time->Hour & 0x7f);
864
865 if (RegisterB.Bits.Dm == 0) {
866 Time->Year = CheckAndConvertBcd8ToDecimal8 ((UINT8) Time->Year);
867 Time->Month = CheckAndConvertBcd8ToDecimal8 (Time->Month);
868 Time->Day = CheckAndConvertBcd8ToDecimal8 (Time->Day);
869 Time->Hour = CheckAndConvertBcd8ToDecimal8 (Time->Hour);
870 Time->Minute = CheckAndConvertBcd8ToDecimal8 (Time->Minute);
871 Time->Second = CheckAndConvertBcd8ToDecimal8 (Time->Second);
872 }
873 Century = CheckAndConvertBcd8ToDecimal8 (Century);
874
875 if (Time->Year == 0xff || Time->Month == 0xff || Time->Day == 0xff ||
876 Time->Hour == 0xff || Time->Minute == 0xff || Time->Second == 0xff ||
877 Century == 0xff) {
878 return EFI_INVALID_PARAMETER;
879 }
880
881 Time->Year = (UINT16) (Century * 100 + Time->Year);
882
883 //
884 // If time is in 12 hour format, convert it to 24 hour format
885 //
886 if (RegisterB.Bits.Mil == 0) {
887 if (IsPM && Time->Hour < 12) {
888 Time->Hour = (UINT8) (Time->Hour + 12);
889 }
890
891 if (!IsPM && Time->Hour == 12) {
892 Time->Hour = 0;
893 }
894 }
895
896 Time->Nanosecond = 0;
897
898 return EFI_SUCCESS;
899 }
900
901 /**
902 Wait for a period for the RTC to be ready.
903
904 @param Timeout Tell how long it should take to wait.
905
906 @retval EFI_DEVICE_ERROR RTC device error.
907 @retval EFI_SUCCESS RTC is updated and ready.
908 **/
909 EFI_STATUS
910 RtcWaitToUpdate (
911 UINTN Timeout
912 )
913 {
914 RTC_REGISTER_A RegisterA;
915 RTC_REGISTER_D RegisterD;
916
917 //
918 // See if the RTC is functioning correctly
919 //
920 RegisterD.Data = RtcRead (RTC_ADDRESS_REGISTER_D);
921
922 if (RegisterD.Bits.Vrt == 0) {
923 return EFI_DEVICE_ERROR;
924 }
925 //
926 // Wait for up to 0.1 seconds for the RTC to be ready.
927 //
928 Timeout = (Timeout / 10) + 1;
929 RegisterA.Data = RtcRead (RTC_ADDRESS_REGISTER_A);
930 while (RegisterA.Bits.Uip == 1 && Timeout > 0) {
931 MicroSecondDelay (10);
932 RegisterA.Data = RtcRead (RTC_ADDRESS_REGISTER_A);
933 Timeout--;
934 }
935
936 RegisterD.Data = RtcRead (RTC_ADDRESS_REGISTER_D);
937 if (Timeout == 0 || RegisterD.Bits.Vrt == 0) {
938 return EFI_DEVICE_ERROR;
939 }
940
941 return EFI_SUCCESS;
942 }
943
944 /**
945 See if all fields of a variable of EFI_TIME type is correct.
946
947 @param Time The time to be checked.
948
949 @retval EFI_INVALID_PARAMETER Some fields of Time are not correct.
950 @retval EFI_SUCCESS Time is a valid EFI_TIME variable.
951
952 **/
953 EFI_STATUS
954 RtcTimeFieldsValid (
955 IN EFI_TIME *Time
956 )
957 {
958 if (Time->Year < PcdGet16 (PcdMinimalValidYear) ||
959 Time->Year > PcdGet16 (PcdMaximalValidYear) ||
960 Time->Month < 1 ||
961 Time->Month > 12 ||
962 (!DayValid (Time)) ||
963 Time->Hour > 23 ||
964 Time->Minute > 59 ||
965 Time->Second > 59 ||
966 Time->Nanosecond > 999999999 ||
967 (!(Time->TimeZone == EFI_UNSPECIFIED_TIMEZONE || (Time->TimeZone >= -1440 && Time->TimeZone <= 1440))) ||
968 ((Time->Daylight & (~(EFI_TIME_ADJUST_DAYLIGHT | EFI_TIME_IN_DAYLIGHT))) != 0)) {
969 return EFI_INVALID_PARAMETER;
970 }
971
972 return EFI_SUCCESS;
973 }
974
975 /**
976 See if field Day of an EFI_TIME is correct.
977
978 @param Time Its Day field is to be checked.
979
980 @retval TRUE Day field of Time is correct.
981 @retval FALSE Day field of Time is NOT correct.
982 **/
983 BOOLEAN
984 DayValid (
985 IN EFI_TIME *Time
986 )
987 {
988 INTN DayOfMonth[12];
989
990 DayOfMonth[0] = 31;
991 DayOfMonth[1] = 29;
992 DayOfMonth[2] = 31;
993 DayOfMonth[3] = 30;
994 DayOfMonth[4] = 31;
995 DayOfMonth[5] = 30;
996 DayOfMonth[6] = 31;
997 DayOfMonth[7] = 31;
998 DayOfMonth[8] = 30;
999 DayOfMonth[9] = 31;
1000 DayOfMonth[10] = 30;
1001 DayOfMonth[11] = 31;
1002
1003 //
1004 // The validity of Time->Month field should be checked before
1005 //
1006 ASSERT (Time->Month >=1);
1007 ASSERT (Time->Month <=12);
1008 if (Time->Day < 1 ||
1009 Time->Day > DayOfMonth[Time->Month - 1] ||
1010 (Time->Month == 2 && (!IsLeapYear (Time) && Time->Day > 28))
1011 ) {
1012 return FALSE;
1013 }
1014
1015 return TRUE;
1016 }
1017
1018 /**
1019 Check if it is a leap year.
1020
1021 @param Time The time to be checked.
1022
1023 @retval TRUE It is a leap year.
1024 @retval FALSE It is NOT a leap year.
1025 **/
1026 BOOLEAN
1027 IsLeapYear (
1028 IN EFI_TIME *Time
1029 )
1030 {
1031 if (Time->Year % 4 == 0) {
1032 if (Time->Year % 100 == 0) {
1033 if (Time->Year % 400 == 0) {
1034 return TRUE;
1035 } else {
1036 return FALSE;
1037 }
1038 } else {
1039 return TRUE;
1040 }
1041 } else {
1042 return FALSE;
1043 }
1044 }
1045
1046 /**
1047 Converts time from EFI_TIME format defined by UEFI spec to RTC's.
1048
1049 This function converts time from EFI_TIME format defined by UEFI spec to RTC's.
1050 If data mode of RTC is BCD, then converts EFI_TIME to it.
1051 If RTC is in 12-hour format, then converts EFI_TIME to it.
1052
1053 @param Time On input, the time data read from UEFI to convert
1054 On output, the time converted to RTC format
1055 @param RegisterB Value of Register B of RTC, indicating data mode
1056 @param Century It is set according to EFI_TIME Time.
1057
1058 **/
1059 VOID
1060 ConvertEfiTimeToRtcTime (
1061 IN OUT EFI_TIME *Time,
1062 IN RTC_REGISTER_B RegisterB,
1063 OUT UINT8 *Century
1064 )
1065 {
1066 BOOLEAN IsPM;
1067
1068 IsPM = TRUE;
1069 //
1070 // Adjust hour field if RTC is in 12 hour mode
1071 //
1072 if (RegisterB.Bits.Mil == 0) {
1073 if (Time->Hour < 12) {
1074 IsPM = FALSE;
1075 }
1076
1077 if (Time->Hour >= 13) {
1078 Time->Hour = (UINT8) (Time->Hour - 12);
1079 } else if (Time->Hour == 0) {
1080 Time->Hour = 12;
1081 }
1082 }
1083 //
1084 // Set the Time/Date/Daylight Savings values.
1085 //
1086 *Century = DecimalToBcd8 ((UINT8) (Time->Year / 100));
1087
1088 Time->Year = (UINT16) (Time->Year % 100);
1089
1090 if (RegisterB.Bits.Dm == 0) {
1091 Time->Year = DecimalToBcd8 ((UINT8) Time->Year);
1092 Time->Month = DecimalToBcd8 (Time->Month);
1093 Time->Day = DecimalToBcd8 (Time->Day);
1094 Time->Hour = DecimalToBcd8 (Time->Hour);
1095 Time->Minute = DecimalToBcd8 (Time->Minute);
1096 Time->Second = DecimalToBcd8 (Time->Second);
1097 }
1098 //
1099 // If we are in 12 hour mode and PM is set, then set bit 7 of the Hour field.
1100 //
1101 if (RegisterB.Bits.Mil == 0 && IsPM) {
1102 Time->Hour = (UINT8) (Time->Hour | 0x80);
1103 }
1104 }
1105
1106 /**
1107 Compare the Hour, Minute and Second of the From time and the To time.
1108
1109 Only compare H/M/S in EFI_TIME and ignore other fields here.
1110
1111 @param From the first time
1112 @param To the second time
1113
1114 @return >0 The H/M/S of the From time is later than those of To time
1115 @return ==0 The H/M/S of the From time is same as those of To time
1116 @return <0 The H/M/S of the From time is earlier than those of To time
1117 **/
1118 INTN
1119 CompareHMS (
1120 IN EFI_TIME *From,
1121 IN EFI_TIME *To
1122 )
1123 {
1124 if ((From->Hour > To->Hour) ||
1125 ((From->Hour == To->Hour) && (From->Minute > To->Minute)) ||
1126 ((From->Hour == To->Hour) && (From->Minute == To->Minute) && (From->Second > To->Second))) {
1127 return 1;
1128 } else if ((From->Hour == To->Hour) && (From->Minute == To->Minute) && (From->Second == To->Second)) {
1129 return 0;
1130 } else {
1131 return -1;
1132 }
1133 }
1134
1135 /**
1136 To check if second date is later than first date within 24 hours.
1137
1138 @param From the first date
1139 @param To the second date
1140
1141 @retval TRUE From is previous to To within 24 hours.
1142 @retval FALSE From is later, or it is previous to To more than 24 hours.
1143 **/
1144 BOOLEAN
1145 IsWithinOneDay (
1146 IN EFI_TIME *From,
1147 IN EFI_TIME *To
1148 )
1149 {
1150 UINT8 DayOfMonth[12];
1151 BOOLEAN Adjacent;
1152
1153 DayOfMonth[0] = 31;
1154 DayOfMonth[1] = 29;
1155 DayOfMonth[2] = 31;
1156 DayOfMonth[3] = 30;
1157 DayOfMonth[4] = 31;
1158 DayOfMonth[5] = 30;
1159 DayOfMonth[6] = 31;
1160 DayOfMonth[7] = 31;
1161 DayOfMonth[8] = 30;
1162 DayOfMonth[9] = 31;
1163 DayOfMonth[10] = 30;
1164 DayOfMonth[11] = 31;
1165
1166 Adjacent = FALSE;
1167
1168 //
1169 // The validity of From->Month field should be checked before
1170 //
1171 ASSERT (From->Month >=1);
1172 ASSERT (From->Month <=12);
1173
1174 if (From->Year == To->Year) {
1175 if (From->Month == To->Month) {
1176 if ((From->Day + 1) == To->Day) {
1177 if ((CompareHMS(From, To) >= 0)) {
1178 Adjacent = TRUE;
1179 }
1180 } else if (From->Day == To->Day) {
1181 if ((CompareHMS(From, To) <= 0)) {
1182 Adjacent = TRUE;
1183 }
1184 }
1185 } else if (((From->Month + 1) == To->Month) && (To->Day == 1)) {
1186 if ((From->Month == 2) && !IsLeapYear(From)) {
1187 if (From->Day == 28) {
1188 if ((CompareHMS(From, To) >= 0)) {
1189 Adjacent = TRUE;
1190 }
1191 }
1192 } else if (From->Day == DayOfMonth[From->Month - 1]) {
1193 if ((CompareHMS(From, To) >= 0)) {
1194 Adjacent = TRUE;
1195 }
1196 }
1197 }
1198 } else if (((From->Year + 1) == To->Year) &&
1199 (From->Month == 12) &&
1200 (From->Day == 31) &&
1201 (To->Month == 1) &&
1202 (To->Day == 1)) {
1203 if ((CompareHMS(From, To) >= 0)) {
1204 Adjacent = TRUE;
1205 }
1206 }
1207
1208 return Adjacent;
1209 }
1210