Files
templates/c/rtc-service/rtc_service_core.c

337 lines
11 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* Единое представление календарного времени и логика RTC с резервированием. Проверка полей
* календаря отделена от регистров часов и backup-памяти; аппаратные операции выполняются через
* контракт rtc_service_port.
*/
/**
****************************************************************************
* @file rtc_service_core.c
* @brief Ядро RTC Service. Портируемая логика и валидация.
****************************************************************************
*/
#include "rtc_service.h"
#include "rtc_service_port.h"
#include <stddef.h>
#include <string.h>
#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(&current);
if (pres == RTC_SERVICE_PORT_OK) {
status->initialized = rtc_service_is_datetime_valid(&current);
}
}
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";
}
}