| // Copyright 2021 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 "src/virtualization/bin/vmm/arch/x64/rtc_mc146818.h" |
| |
| #include <errno.h> |
| #include <lib/syslog/cpp/macros.h> |
| |
| #include <ctime> |
| |
| namespace { |
| enum RegisterBFlags : uint8_t { |
| kRegisterBDaylightSavingsEnable = 1 << 0, // DSE |
| kRegisterB24HourFormat = 1 << 1, // 24/12 |
| kRegisterBBinaryMode = 1 << 2, // DM |
| kRegisterBSquareWaveEnable = 1 << 3, // SQWE |
| kRegisterBUpdateInteruptEnable = 1 << 4, // UIE |
| kRegisterBAlarmInteruptEnable = 1 << 5, // AIE |
| kRegisterBPeriodicInteruptEnable = 1 << 6, // PIE |
| kRegisterBStopTicks = 1 << 7, // SET |
| }; |
| // Alternate/extra RTC modes are unsupported by this emulated RTC, so we |
| // make them unwriteable |
| constexpr uint8_t kRegisterBUnwritableMask = kRegisterBDaylightSavingsEnable | |
| kRegisterB24HourFormat | kRegisterBBinaryMode | |
| kRegisterBSquareWaveEnable; |
| |
| enum RegisterCFlags : uint8_t { |
| kRegisterCUpdateFlag = 1 << 4, // UF |
| kRegisterCAlarmFlag = 1 << 5, // AF |
| kRegisterCPeriodicFlag = 1 << 6, // PF |
| kRegisterCIRQFlag = 1 << 7, // IRQF |
| }; |
| |
| // Linux expects the RTC to be in BCD mode regardless of the binary mode flag |
| // on x86, so we have to convert registers back and forth |
| constexpr uint8_t ToBcd(uint8_t binary) { |
| return static_cast<uint8_t>(((binary / 10 % 10) << 4) | (binary % 10)); |
| } |
| constexpr uint8_t FromBcd(uint8_t bcd) { |
| return ((bcd & 0xf0) >> 4) * 10 + (bcd & 0x0f); |
| } |
| } // namespace |
| |
| RtcMc146818::RtcMc146818() { |
| registers_[Register::kA] = 0b00100000; // Tick rate: 1 second per second |
| registers_[Register::kB] = kRegisterB24HourFormat; |
| registers_[Register::kC] = 0; |
| registers_[Register::kSecondsAlarm] = 0; |
| registers_[Register::kMinutesAlarm] = 0; |
| registers_[Register::kHoursAlarm] = 0; |
| UpdateTime(); |
| } |
| |
| zx_status_t RtcMc146818::ReadRegister(Register reg, uint8_t* value) { |
| std::lock_guard<std::mutex> lock(mutex_); |
| if (registers_.count(reg) == 0) { |
| FX_LOGS(ERROR) << "Read from unsupported RTC register (0x" << std::hex |
| << static_cast<uint32_t>(reg) << ")"; |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| UpdateTime(); |
| *value = registers_[reg]; |
| if (reg == Register::kC) { |
| // Register C is cleared on read |
| registers_[Register::kC] = 0; |
| } |
| return ZX_OK; |
| } |
| |
| zx_status_t RtcMc146818::WriteRegister(Register reg, uint8_t value) { |
| std::lock_guard<std::mutex> lock(mutex_); |
| UpdateTime(); |
| switch (reg) { |
| case Register::kSeconds: |
| case Register::kMinutes: |
| case Register::kHours: |
| case Register::kDayOfWeek: |
| case Register::kDayOfMonth: |
| case Register::kMonth: |
| case Register::kYear: |
| case Register::kCentury: |
| registers_[reg] = value; |
| offset_ = GetOffset(); |
| break; |
| |
| case Register::kSecondsAlarm: |
| case Register::kMinutesAlarm: |
| case Register::kHoursAlarm: |
| // TODO: Implement alarms |
| return ZX_ERR_NOT_SUPPORTED; |
| |
| case Register::kA: |
| // Changing the RTC speed is unsupported |
| FX_LOGS(DEBUG) << "Ignoring write to adjust RTC speed (0x" << std::hex |
| << static_cast<uint32_t>(value) << ")"; |
| break; |
| |
| case Register::kB: |
| if (value & (kRegisterBUpdateInteruptEnable | kRegisterBAlarmInteruptEnable | |
| kRegisterBPeriodicInteruptEnable)) { |
| // Update, alarm, and periodic interrupts are unimplemented |
| // TODO: Implement alarms |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| registers_[Register::kB] = (value & ~kRegisterBUnwritableMask) | |
| (registers_[Register::kB] & kRegisterBUnwritableMask); |
| if (registers_[Register::kB] != value) { |
| FX_LOGS(INFO) << "Partially ignoring write to RTC operating mode (0x" << std::hex |
| << static_cast<uint32_t>(value) << ")"; |
| } |
| break; |
| |
| case Register::kC: |
| FX_LOGS(INFO) << "Ignoring write to read-only RTC flags (0x" << std::hex |
| << static_cast<uint32_t>(value) << ")"; |
| break; |
| |
| default: |
| FX_LOGS(ERROR) << "Write to unsupported RTC register (0x" << std::hex |
| << static_cast<uint32_t>(reg) << ", 0x" << static_cast<uint32_t>(value) << ")"; |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| return ZX_OK; |
| } |
| |
| void RtcMc146818::UpdateTime() { |
| if (registers_[Register::kB] & kRegisterBStopTicks) |
| return; |
| |
| time_t now = std::chrono::system_clock::to_time_t(Now() + offset_); |
| tm datetime; |
| gmtime_r(&now, &datetime); |
| |
| registers_[Register::kSeconds] = ToBcd(datetime.tm_sec); |
| registers_[Register::kMinutes] = ToBcd(datetime.tm_min); |
| registers_[Register::kHours] = ToBcd(datetime.tm_hour); |
| registers_[Register::kDayOfWeek] = ToBcd(datetime.tm_wday + 1); |
| registers_[Register::kDayOfMonth] = ToBcd(datetime.tm_mday); |
| registers_[Register::kMonth] = ToBcd(datetime.tm_mon + 1); |
| int year = datetime.tm_year + 1900; |
| registers_[Register::kYear] = ToBcd(year % 100); |
| registers_[Register::kCentury] = ToBcd(year / 100); |
| } |
| |
| std::chrono::seconds RtcMc146818::GetOffset() { |
| // Note: timegm() handles out-of-range values by just overflowing them to the |
| // next higher unit (e.g., 70 seconds turns into 1 minute and 10 seconds). |
| // Since the registers physically only allow for years 0000 to 9999, timegm() |
| // will never return its only error EOVERFLOW since they all fit into a |
| // 64-bit time_t |
| tm datetime{ |
| .tm_sec = FromBcd(registers_[Register::kSeconds]), |
| .tm_min = FromBcd(registers_[Register::kMinutes]), |
| .tm_hour = FromBcd(registers_[Register::kHours]), |
| .tm_mday = FromBcd(registers_[Register::kDayOfMonth]), |
| .tm_mon = FromBcd(registers_[Register::kMonth]) - 1, |
| .tm_year = FromBcd(registers_[Register::kYear]) + 100 * FromBcd(registers_[Register::kCentury]) - 1900, |
| }; |
| auto offset = std::chrono::system_clock::from_time_t(timegm(&datetime)) - Now(); |
| return std::chrono::duration_cast<std::chrono::seconds>(offset); |
| } |