1 // Copyright 2018 The Fuchsia Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "include/librtc.h"
6 
7 #include <ddk/driver.h>
8 #include <zircon/syscalls.h>
9 
10 // Leading 0 allows using the 1-indexed month values from rtc.
11 static const uint64_t days_in_month[] = {
12     0,
13     31, // January
14     28, // February (not leap year)
15     31, // March
16     30, // April
17     31, // May
18     30, // June
19     31, // July
20     31, // August
21     30, // September
22     31, // October
23     30, // November
24     31, // December
25 };
26 
27 // Start with seconds from the Unix epoch to 2000/1/1T00:00:00.
28 static const uint64_t local_epoch = 946684800;
29 static const uint16_t local_epoch_year = 2000;
30 static const uint16_t default_year = 2018;
31 
is_leap_year(uint16_t year)32 static bool is_leap_year(uint16_t year) {
33     return ((year % 4) == 0 && (year % 100) != 0) || ((year % 400) == 0);
34 }
35 
seconds_since_epoch(const fuchsia_hardware_rtc_Time * rtc)36 uint64_t seconds_since_epoch(const fuchsia_hardware_rtc_Time* rtc) {
37     // First add all of the prior years.
38     uint64_t days_since_local_epoch = 0;
39     for (uint16_t year = local_epoch_year; year < rtc->year; year++) {
40         days_since_local_epoch += is_leap_year(year) ? 366 : 365;
41     }
42 
43     // Next add all the prior complete months this year.
44     for (size_t month = 1; month < rtc->month; month++) {
45         days_since_local_epoch += days_in_month[month];
46     }
47     if (rtc->month > 2 && is_leap_year(rtc->year)) {
48         days_since_local_epoch++;
49     }
50 
51     // Add all the prior complete days.
52     days_since_local_epoch += rtc->day - 1;
53 
54     // Hours, minutes, and seconds are 0 indexed.
55     uint64_t hours_since_local_epoch = (days_since_local_epoch * 24) + rtc->hours;
56     uint64_t minutes_since_local_epoch = (hours_since_local_epoch * 60) + rtc->minutes;
57     uint64_t seconds_since_local_epoch = (minutes_since_local_epoch * 60) + rtc->seconds;
58 
59     uint64_t rtc_seconds = local_epoch + seconds_since_local_epoch;
60     return rtc_seconds;
61 }
62 
seconds_to_rtc(uint64_t seconds,fuchsia_hardware_rtc_Time * rtc)63 void seconds_to_rtc(uint64_t seconds, fuchsia_hardware_rtc_Time* rtc) {
64     // subtract local epoch offset to get to rtc time
65     uint64_t epoch = seconds - local_epoch;
66 
67     rtc->seconds = epoch % 60; epoch /= 60;
68     rtc->minutes = epoch % 60; epoch /= 60;
69     rtc->hours = epoch % 24; epoch /= 24;
70 
71     for (rtc->year = local_epoch_year;; rtc->year++) {
72         uint32_t days_per_year = 365;
73         if (is_leap_year(rtc->year)) {
74             days_per_year++;
75         }
76 
77         if (epoch < days_per_year) {
78             break;
79         }
80 
81         epoch -= days_per_year;
82     }
83 
84     for (rtc->month = 1;; rtc->month++) {
85         uint32_t days_per_month = days_in_month[rtc->month];
86         if ((rtc->month == 2) && is_leap_year(rtc->year)) {
87             days_per_month++;
88         }
89 
90         if (epoch < days_per_month) {
91             break;
92         }
93 
94         epoch -= days_per_month;
95     }
96 
97     // remaining epoch is a whole number of days so just make it one-indexed
98     rtc->day = epoch + 1;
99 }
100 
to_bcd(uint8_t binary)101 uint8_t to_bcd(uint8_t binary) {
102     return ((binary / 10) << 4) | (binary % 10);
103 }
104 
from_bcd(uint8_t bcd)105 uint8_t from_bcd(uint8_t bcd) {
106     return ((bcd >> 4) * 10) + (bcd & 0xf);
107 }
108 
rtc_is_invalid(const fuchsia_hardware_rtc_Time * rtc)109 bool rtc_is_invalid(const fuchsia_hardware_rtc_Time* rtc) {
110     return rtc->seconds > 59 ||
111         rtc->minutes > 59 ||
112         rtc->hours > 23 ||
113         rtc->day > 31 ||
114         rtc->month > 12 ||
115         rtc->year < local_epoch_year ||
116         rtc->year > 2099;
117 }
118 
119 // Validate that the RTC is set to a valid time, and to a relatively
120 // sane one. Report the validated or reset time back via rtc.
sanitize_rtc(void * ctx,fuchsia_hardware_rtc_Time * rtc,zx_status_t (* rtc_get)(void *,fuchsia_hardware_rtc_Time *),zx_status_t (* rtc_set)(void *,const fuchsia_hardware_rtc_Time *))121 void sanitize_rtc(void* ctx, fuchsia_hardware_rtc_Time* rtc,
122                   zx_status_t (*rtc_get)(void*, fuchsia_hardware_rtc_Time*),
123                   zx_status_t (*rtc_set)(void*, const fuchsia_hardware_rtc_Time*)) {
124     // January 1, 2016 00:00:00
125     static const fuchsia_hardware_rtc_Time default_rtc = {
126         .day = 1,
127         .month = 1,
128         .year = default_year,
129     };
130     rtc_get(ctx, rtc);
131     if (rtc_is_invalid(rtc) || rtc->year < default_year) {
132         rtc_set(ctx, &default_rtc);
133         *rtc = default_rtc;
134     }
135 }
136