Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

rtc: efi: use correct EFI 'epoch'

The rtc-efi driver declares that the EFI 'epoch' is 1/1/1998, but
the UEFI spec does not define it at all. It does define a range of
[1900, 9999] for the 'Year' member of the EFI_TIME struct, so let's
use 1900 as the minimum year and not 1998.
Also, move the validation of the year to the convert_from_efi_time()
routine where all other EFI_TIME fields are validated as well.

This prevents rtc_read_time() failures when the RTC that backs the
EFI time services is set to a date before 1998, e.g., when it has
lost power.

This also optimizes the compute_wday() routine, by replacing the for
loop with a simple arithmetic expression, and by reusing the yearday
value that we need to compute anyway when populating the
rtc_time::tm_yday field.

Cc: Alessandro Zummo <a.zummo@towertech.it>
Cc: Alexandre Belloni <alexandre.belloni@free-electrons.com>
Cc: rtc-linux@googlegroups.com
Signed-off-by: Ard Biesheuvel <ard.biesheuvel@linaro.org>
Signed-off-by: Alexandre Belloni <alexandre.belloni@free-electrons.com>

authored by

Ard Biesheuvel and committed by
Alexandre Belloni
b2bd2370 c86a6c28

+14 -25
+14 -25
drivers/rtc/rtc-efi.c
··· 24 24 #include <linux/efi.h> 25 25 26 26 #define EFI_ISDST (EFI_TIME_ADJUST_DAYLIGHT|EFI_TIME_IN_DAYLIGHT) 27 - /* 28 - * EFI Epoch is 1/1/1998 29 - */ 30 - #define EFI_RTC_EPOCH 1998 31 27 32 28 /* 33 29 * returns day of the year [0-365] ··· 34 38 /* efi_time_t.month is in the [1-12] so, we need -1 */ 35 39 return rtc_year_days(eft->day, eft->month - 1, eft->year); 36 40 } 41 + 37 42 /* 38 43 * returns day of the week [0-6] 0=Sunday 39 - * 40 - * Don't try to provide a year that's before 1998, please ! 41 44 */ 42 45 static int 43 - compute_wday(efi_time_t *eft) 46 + compute_wday(efi_time_t *eft, int yday) 44 47 { 45 - int y; 46 - int ndays = 0; 47 - 48 - if (eft->year < EFI_RTC_EPOCH) { 49 - pr_err("EFI year < " __stringify(EFI_RTC_EPOCH) ", invalid date\n"); 50 - return -1; 51 - } 52 - 53 - for (y = EFI_RTC_EPOCH; y < eft->year; y++) 54 - ndays += 365 + (is_leap_year(y) ? 1 : 0); 55 - 56 - ndays += compute_yday(eft); 48 + int ndays = eft->year * (365 % 7) 49 + + (eft->year - 1) / 4 50 + - (eft->year - 1) / 100 51 + + (eft->year - 1) / 400 52 + + yday; 57 53 58 54 /* 59 - * 4=1/1/1998 was a Thursday 55 + * 1/1/0000 may or may not have been a Sunday (if it ever existed at 56 + * all) but assuming it was makes this calculation work correctly. 60 57 */ 61 - return (ndays + 4) % 7; 58 + return ndays % 7; 62 59 } 63 60 64 61 static void ··· 92 103 if (!eft->month || eft->month > 12) 93 104 return false; 94 105 wtime->tm_mon = eft->month - 1; 95 - wtime->tm_year = eft->year - 1900; 96 106 97 - /* day of the week [0-6], Sunday=0 */ 98 - wtime->tm_wday = compute_wday(eft); 99 - if (wtime->tm_wday < 0) 107 + if (eft->year < 1900 || eft->year > 9999) 100 108 return false; 109 + wtime->tm_year = eft->year - 1900; 101 110 102 111 /* day in the year [1-365]*/ 103 112 wtime->tm_yday = compute_yday(eft); 104 113 114 + /* day of the week [0-6], Sunday=0 */ 115 + wtime->tm_wday = compute_wday(eft, wtime->tm_yday); 105 116 106 117 switch (eft->daylight & EFI_ISDST) { 107 118 case EFI_ISDST: