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