diff --git a/core/embed/sys/time/inc/sys/rtc.h b/core/embed/sys/time/inc/sys/rtc.h index 4febcc4785..eb5876a6da 100644 --- a/core/embed/sys/time/inc/sys/rtc.h +++ b/core/embed/sys/time/inc/sys/rtc.h @@ -29,6 +29,18 @@ */ bool rtc_init(void); +/** + * @brief Get the current timestamp from the RTC + * + * Retrieves the current timestamp as a number of seconds since the device got + * powered up for the first time. The timestamp is calculated based on the + * current date and time stored in the RTC. + * + * @param timestamp Pointer to a variable where the timestamp will be stored. + * @return true if the timestamp was successfully retrieved, false otherwise + */ +bool rtc_get_timestamp(uint32_t* timestamp); + /** * @brief Callback invoked when the RTC wakeup event occurs * diff --git a/core/embed/sys/time/stm32u5/rtc.c b/core/embed/sys/time/stm32u5/rtc.c index a9040d67f3..c48a7200a9 100644 --- a/core/embed/sys/time/stm32u5/rtc.c +++ b/core/embed/sys/time/stm32u5/rtc.c @@ -40,6 +40,9 @@ static rtc_driver_t g_rtc_driver = { .initialized = false, }; +static uint32_t rtc_calendar_to_timestamp(const RTC_DateTypeDef* date, + const RTC_TimeTypeDef* time); + bool rtc_init(void) { rtc_driver_t* drv = &g_rtc_driver; @@ -72,6 +75,33 @@ bool rtc_init(void) { return true; } +bool rtc_get_timestamp(uint32_t* timestamp) { + rtc_driver_t* drv = &g_rtc_driver; + + if (!drv->initialized || timestamp == NULL) { + return false; + } + + RTC_DateTypeDef date; + RTC_TimeTypeDef time; + + // Get current time and date, + // Important: GetTime has to be called before the GetDate in order to unlock + // the values in higher-order callendar. + if (HAL_OK != HAL_RTC_GetTime(&drv->hrtc, &time, RTC_FORMAT_BCD)) { + return false; + } + + // Get the current date + if (HAL_OK != HAL_RTC_GetDate(&drv->hrtc, &date, RTC_FORMAT_BCD)) { + return false; + } + + *timestamp = rtc_calendar_to_timestamp(&date, &time); + + return true; +} + bool rtc_wakeup_timer_start(uint32_t seconds, rtc_wakeup_callback_t callback, void* context) { rtc_driver_t* drv = &g_rtc_driver; @@ -139,4 +169,56 @@ void RTC_IRQHandler(void) { IRQ_LOG_EXIT(); } +static const uint8_t days_in_month[] = { + 31, // January + 28, // February (not considering leap years here) + 31, // March + 30, // April + 31, // May + 30, // June + 31, // July + 31, // August + 30, // September + 31, // October + 30, // November + 31 // December +}; + +static uint8_t bcd2bin(uint8_t val) { return (val & 0x0F) + ((val >> 4) * 10); } + +// Check for leap year +static int is_leap_year(int year) { + return ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)); +} + +static uint32_t rtc_calendar_to_timestamp(const RTC_DateTypeDef* date, + const RTC_TimeTypeDef* time) { + uint8_t year = bcd2bin(date->Year); // 0..99 + uint8_t month = bcd2bin(date->Month); // 1..12 + uint8_t day = bcd2bin(date->Date); // 1..31 + uint8_t hour = bcd2bin(time->Hours); + uint8_t min = bcd2bin(time->Minutes); + uint8_t sec = bcd2bin(time->Seconds); + + // STM RTC starts at 2000, so we need to offset the year evey time we + // calculate the leap years. + + uint32_t days = 0; + for (int y = 0; y < year; ++y) { + days += 365; + if (is_leap_year(y + 2000)) days += 1; + } + for (int m = 1; m < month; ++m) { + days += days_in_month[m - 1]; + if (m == 2 && is_leap_year(year + 2000)) days += 1; + } + days += day - 1; + + uint32_t seconds = days * 86400 + hour * 3600 + min * 60 + sec; + + // Unix epoch starts at 1970, STM32 RTC at 2000 + // 946684800 = seconds from 1970-01-01 to 2000-01-01 + return seconds + 946684800; +} + #endif // KERNEL_MODE