| // Copyright 2018 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "include/librtc.h" |
| |
| #include <ddk/driver.h> |
| #include <zircon/device/rtc.h> |
| #include <zircon/syscalls.h> |
| |
| // Leading 0 allows using the 1-indexed month values from rtc. |
| static const uint64_t days_in_month[] = { |
| 0, |
| 31, // January |
| 28, // February (not leap year) |
| 31, // March |
| 30, // April |
| 31, // May |
| 30, // June |
| 31, // July |
| 31, // August |
| 30, // September |
| 31, // October |
| 30, // November |
| 31, // December |
| }; |
| |
| // Start with seconds from the Unix epoch to 2016/1/1T00:00:00. |
| static const uint64_t local_epoc = 1451606400; |
| static const uint16_t local_epoc_year = 2016; |
| |
| static bool is_leap_year(uint16_t year) { |
| return ((year % 4) == 0 && (year % 100) != 0) || ((year % 400) == 0); |
| } |
| |
| // This is run on boot (after validation of the RTC) and whenever the |
| // RTC is adjusted. |
| zx_status_t set_utc_offset(const rtc_t* rtc) { |
| // First add all of the prior years |
| uint64_t days_since_local_epoc = 0; |
| for (uint16_t year = local_epoc_year; year < rtc->year; year++) { |
| days_since_local_epoc += is_leap_year(year) ? 366 : 365; |
| } |
| |
| // Next add all the prior complete months this year. |
| for (size_t month = 1; month < rtc->month; month++) { |
| days_since_local_epoc += days_in_month[month]; |
| } |
| if (rtc->month > 2 && is_leap_year(rtc->year)) { |
| days_since_local_epoc++; |
| } |
| |
| // Add all the prior complete days. |
| days_since_local_epoc += rtc->day - 1; |
| |
| // Hours, minutes, and seconds are 0 indexed. |
| uint64_t hours_since_local_epoc = (days_since_local_epoc * 24) + rtc->hours; |
| uint64_t minutes_since_local_epoc = (hours_since_local_epoc * 60) + rtc->minutes; |
| uint64_t seconds_since_local_epoc = (minutes_since_local_epoc * 60) + rtc->seconds; |
| |
| uint64_t rtc_seconds = local_epoc + seconds_since_local_epoc; |
| uint64_t rtc_nanoseconds = rtc_seconds * 1000000000; |
| |
| uint64_t monotonic_nanoseconds = zx_clock_get_monotonic(); |
| int64_t offset = rtc_nanoseconds - monotonic_nanoseconds; |
| |
| zx_status_t status = zx_clock_adjust(get_root_resource(), ZX_CLOCK_UTC, offset); |
| return status; |
| } |
| |
| uint8_t to_bcd(uint8_t binary) { |
| return ((binary / 10) << 4) | (binary % 10); |
| } |
| |
| uint8_t from_bcd(uint8_t bcd) { |
| return ((bcd >> 4) * 10) + (bcd & 0xf); |
| } |
| |
| bool rtc_is_invalid(const rtc_t* rtc) { |
| return rtc->seconds > 59 || |
| rtc->minutes > 59 || |
| rtc->hours > 23 || |
| rtc->day > 31 || |
| rtc->month > 12 || |
| rtc->year < 2000 || |
| rtc->year > 2099; |
| } |
| |
| // Validate that the RTC is set to a valid time, and to a relatively |
| // sane one. Report the validated or reset time back via rtc. |
| void sanitize_rtc(void* ctx, zx_protocol_device_t* dev, rtc_t* rtc) { |
| // January 1, 2016 00:00:00 |
| static const rtc_t default_rtc = { |
| .day = 1, |
| .month = 1, |
| .year = 2016, |
| }; |
| size_t out_actual; |
| dev->ioctl(ctx, IOCTL_RTC_GET, NULL, 0, rtc, sizeof *rtc, &out_actual); |
| if (rtc_is_invalid(rtc) || rtc->year < local_epoc_year) { |
| dev->ioctl(ctx, IOCTL_RTC_SET, &default_rtc, sizeof default_rtc, NULL, 0, NULL); |
| *rtc = default_rtc; |
| } |
| } |