/** **************************************************************************** * @file rtc_service_core.c * @brief Ядро RTC Service. Портируемая логика и валидация. **************************************************************************** */ #include "rtc_service.h" #include "rtc_service_port.h" #include #include #define RTC_SERVICE_MAGIC 0x52544353UL /* "RTCS" */ #define RTC_SERVICE_RECORD_VERSION 0x01UL #define RTC_SERVICE_BACKUP_WORDS 16UL #define RTC_SERVICE_BACKUP_DATA_WORDS 11UL typedef struct { uint32_t data[RTC_SERVICE_BACKUP_DATA_WORDS]; } RtcService_BackupPayload; typedef union { uint32_t raw[RTC_SERVICE_BACKUP_WORDS]; struct { uint32_t magic; uint32_t version; uint32_t crc32; uint32_t payload[RTC_SERVICE_BACKUP_DATA_WORDS]; } f; } RtcService_BackupRecord; static uint32_t s_last_backup_seq = 0U; static uint8_t is_leap_year(uint16_t year) { /* year in range [0..99] interpreted as 2000..2099 */ uint16_t full_year = (uint16_t)(2000U + year); if ((full_year % 4U) != 0U) return 0U; if ((full_year % 100U) == 0U) return (full_year % 400U) == 0U; return 1U; } static uint8_t days_in_month(uint16_t year, uint16_t month) { static const uint8_t dim[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; if (month < 1U || month > 12U) return 0U; if (month != 2U) return dim[month - 1U]; return is_leap_year(year) ? 29U : 28U; } static uint8_t rtc_service_is_datetime_valid(const RtcService_DateTime* dt) { if (dt == NULL) return 0U; if (dt->year > 99U || dt->month < 1U || dt->month > 12U) return 0U; { uint8_t dim = days_in_month(dt->year, dt->month); if (dt->day == 0U || dt->day > dim) return 0U; } if (dt->hour > 23U || dt->minute > 59U || dt->second > 59U) return 0U; if (dt->weekday < 1U || dt->weekday > 7U) return 0U; return 1U; } static uint32_t crc32_fletcher(const uint32_t* data, size_t words) { uint32_t a = 0xFFFFU; uint32_t b = 0xFFFFU; for (size_t i = 0U; i < words; i++) { uint32_t w = data[i]; a = (a + ((w >> 0U) & 0xFFU)) & 0xFFFFU; b = (b + a) & 0xFFFFU; a = (a + ((w >> 8U) & 0xFFU)) & 0xFFFFU; b = (b + a) & 0xFFFFU; a = (a + ((w >> 16U) & 0xFFU)) & 0xFFFFU; b = (b + a) & 0xFFFFU; a = (a + ((w >> 24U) & 0xFFU)) & 0xFFFFU; b = (b + a) & 0xFFFFU; } return (b << 16U) | a; } static void rtc_service_pack_payload(const RtcService_DateTime* dt, uint32_t seq, RtcService_BackupPayload* payload) { memset(payload, 0, sizeof(*payload)); payload->data[0U] = RTC_SERVICE_MAGIC; payload->data[1U] = seq; payload->data[2U] = RTC_SERVICE_RECORD_VERSION; payload->data[3U] = (uint32_t)dt->year; payload->data[4U] = ((uint32_t)dt->month << 16U) | dt->day; payload->data[5U] = ((uint32_t)dt->hour << 16U) | dt->minute; payload->data[6U] = ((uint32_t)dt->weekday << 16U) | dt->second; } static void rtc_service_unpack_payload(const RtcService_BackupPayload* payload, RtcService_DateTime* dt, uint32_t* seq) { if (dt != NULL) { dt->year = (uint16_t)(payload->data[3U] & 0xFFFFU); dt->month = (uint16_t)((payload->data[4U] >> 16U) & 0xFFFFU); dt->day = (uint16_t)(payload->data[4U] & 0xFFFFU); dt->hour = (uint16_t)((payload->data[5U] >> 16U) & 0xFFFFU); dt->minute = (uint16_t)(payload->data[5U] & 0xFFFFU); dt->weekday = (uint16_t)((payload->data[6U] >> 16U) & 0xFFFFU); dt->second = (uint16_t)(payload->data[6U] & 0xFFFFU); } if (seq != NULL) *seq = payload->data[1U]; } static RtcService_Result rtc_service_load_backup(RtcService_BackupPayload* payload, uint32_t* seq) { RtcService_BackupRecord rec; if (payload == NULL) return RTC_SERVICE_RESULT_INVALID_ARGUMENT; if (RtcService_Port_ReadBackup(rec.raw, RTC_SERVICE_BACKUP_WORDS) != RTC_SERVICE_PORT_OK) return RTC_SERVICE_RESULT_BACKUP_UNAVAILABLE; if (rec.f.magic != RTC_SERVICE_MAGIC) return RTC_SERVICE_RESULT_BACKUP_CORRUPT; if (rec.f.version != RTC_SERVICE_RECORD_VERSION) return RTC_SERVICE_RESULT_BACKUP_CORRUPT; { uint32_t calc_crc = crc32_fletcher(rec.f.payload, RTC_SERVICE_BACKUP_DATA_WORDS); if (calc_crc != rec.f.crc32) return RTC_SERVICE_RESULT_BACKUP_CORRUPT; } memcpy(payload->data, rec.f.payload, sizeof(rec.f.payload)); if (seq != NULL) *seq = payload->data[1U]; return RTC_SERVICE_RESULT_OK; } static RtcService_Result rtc_service_store_backup(const RtcService_DateTime* dt, uint32_t seq) { RtcService_BackupRecord rec; RtcService_BackupPayload payload; memset(&rec, 0, sizeof(rec)); rtc_service_pack_payload(dt, seq, &payload); rec.f.magic = RTC_SERVICE_MAGIC; rec.f.version = RTC_SERVICE_RECORD_VERSION; rec.f.crc32 = crc32_fletcher(payload.data, RTC_SERVICE_BACKUP_DATA_WORDS); memcpy(rec.f.payload, payload.data, sizeof(payload.data)); if (RtcService_Port_EraseBackup() != RTC_SERVICE_PORT_OK) return RTC_SERVICE_RESULT_BACKUP_UNAVAILABLE; if (RtcService_Port_WriteBackup(rec.raw, RTC_SERVICE_BACKUP_WORDS) != RTC_SERVICE_PORT_OK) return RTC_SERVICE_RESULT_BACKUP_UNAVAILABLE; return RTC_SERVICE_RESULT_OK; } static RtcService_Result rtc_service_sanitize_weekday(RtcService_DateTime* dt) { if (dt == NULL) return RTC_SERVICE_RESULT_INVALID_ARGUMENT; if (!rtc_service_is_datetime_valid(dt)) return RTC_SERVICE_RESULT_INVALID_DATETIME; /* Если weekday некорректен, корректно пересчитываем по Zeller (для 2000..2099). */ if (dt->weekday < 1U || dt->weekday > 7U) { int16_t q = (int16_t)dt->day; int16_t m = (int16_t)dt->month; int16_t Y = 2000 + (int16_t)dt->year; if (m < 3) { m += 12; Y -= 1; } { int16_t K = Y % 100; int16_t J = Y / 100; int16_t h = (q + (13 * (m + 1)) / 5 + K + K / 4 + J / 4 + 5 * J) % 7; dt->weekday = (uint16_t)((h + 5U) % 7U + 1U); } } return RTC_SERVICE_RESULT_OK; } RtcService_Result RtcService_Init(const RtcService_InitConfig* cfg, RtcService_Status* status) { if (cfg == NULL || status == NULL) return RTC_SERVICE_RESULT_INVALID_ARGUMENT; memset(status, 0, sizeof(*status)); s_last_backup_seq = 0U; { RtcService_PortResult pres = RtcService_Port_InitClock(cfg->preferred_clock_source, &status->active_clock_source); if (pres != RTC_SERVICE_PORT_OK) { if (pres == RTC_SERVICE_PORT_RESULT_UNSUPPORTED_CLOCK) return RTC_SERVICE_RESULT_HW_NOT_SUPPORTED; return RTC_SERVICE_RESULT_FAILED; } } RtcService_Port_EnableRtcClock(); { RtcService_DateTime current = {0}; RtcService_PortResult pres = RtcService_Port_ReadRtc(¤t); if (pres == RTC_SERVICE_PORT_OK) { status->initialized = rtc_service_is_datetime_valid(¤t); } } status->valid_reset = RtcService_Port_IsPowerOnReset(); RtcService_BackupPayload payload; memset(&payload, 0, sizeof(payload)); RtcService_DateTime backup_dt = {0}; uint32_t seq = 0U; RtcService_Result bres = rtc_service_load_backup(&payload, &seq); uint8_t has_valid_backup = 0U; if ((bres == RTC_SERVICE_RESULT_OK)) { RtcService_DateTime unpacked = {0}; rtc_service_unpack_payload(&payload, &unpacked, NULL); if (rtc_service_is_datetime_valid(&unpacked)) { backup_dt = unpacked; has_valid_backup = 1U; } } s_last_backup_seq = seq; if (has_valid_backup != 0U) { if ((status->valid_reset != 0U) || (status->initialized == 0U)) { status->restored_from_backup = 1U; if (RtcService_SetDateTime(&backup_dt) != RTC_SERVICE_RESULT_OK) return RTC_SERVICE_RESULT_FAILED; return RTC_SERVICE_RESULT_OK; } return RTC_SERVICE_RESULT_OK; } if (RtcService_SetDateTime(&cfg->fallback_datetime) != RTC_SERVICE_RESULT_OK) return RTC_SERVICE_RESULT_INVALID_DATETIME; if (rtc_service_store_backup(&cfg->fallback_datetime, s_last_backup_seq + 1U) != RTC_SERVICE_RESULT_OK) { return RTC_SERVICE_RESULT_BACKUP_UNAVAILABLE; } s_last_backup_seq += 1U; status->initialized = 1U; return RTC_SERVICE_RESULT_OK; } RtcService_Result RtcService_GetDateTime(RtcService_DateTime* dt) { if (dt == NULL) return RTC_SERVICE_RESULT_INVALID_ARGUMENT; if (RtcService_Port_ReadRtc(dt) != RTC_SERVICE_PORT_OK) return RTC_SERVICE_RESULT_FAILED; if (!rtc_service_is_datetime_valid(dt)) return RTC_SERVICE_RESULT_INVALID_DATETIME; return RTC_SERVICE_RESULT_OK; } RtcService_Result RtcService_SetDateTime(const RtcService_DateTime* dt) { RtcService_Result res; RtcService_DateTime normalized = {0}; if (dt == NULL) return RTC_SERVICE_RESULT_INVALID_ARGUMENT; memcpy(&normalized, dt, sizeof(normalized)); res = rtc_service_sanitize_weekday(&normalized); if (res != RTC_SERVICE_RESULT_OK) return res; if (!rtc_service_is_datetime_valid(&normalized)) return RTC_SERVICE_RESULT_INVALID_DATETIME; if (RtcService_Port_WriteRtc(&normalized) != RTC_SERVICE_PORT_OK) return RTC_SERVICE_RESULT_FAILED; if (rtc_service_store_backup(&normalized, s_last_backup_seq + 1U) == RTC_SERVICE_RESULT_OK) { s_last_backup_seq += 1U; } return RTC_SERVICE_RESULT_OK; } const char* RtcService_ResultToText(RtcService_Result res) { switch (res) { case RTC_SERVICE_RESULT_OK: return "OK"; case RTC_SERVICE_RESULT_INVALID_ARGUMENT: return "INVALID_ARGUMENT"; case RTC_SERVICE_RESULT_INVALID_DATETIME: return "INVALID_DATETIME"; case RTC_SERVICE_RESULT_HW_NOT_SUPPORTED: return "HW_NOT_SUPPORTED"; case RTC_SERVICE_RESULT_BACKUP_CORRUPT: return "BACKUP_CORRUPT"; case RTC_SERVICE_RESULT_BACKUP_UNAVAILABLE: return "BACKUP_UNAVAILABLE"; default: return "FAILED"; } }