POSA_LEAKSMS/firmware/common/watchdog.c
유창욱 90f121e14c chore: import codebase with security hardening
SHT30 온습도 모니터링 시스템 전체 소스(서버 PHP, STM32 펌웨어, SQL, 테스트).
전체 코드리뷰에서 도출된 보안 하드닝 10건 반영:
- 요청 서명 HMAC-SHA256 전환(펌웨어 sig.c/서버 config.php/호스트 패리티 동시)
- 재전송 방어 + 기본 API_KEY fail-closed + 디바이스 문자열 정제(api/sensor_data.php)
- 오프라인 SMS 중복 발송 경합 제거(cron_heartbeat.php, 원자적 선점)
- CSV 수식 주입 방지(monthly_report.php), 감사로그 회전 락(retention_cleanup.php)
- 브루트포스 카운터 원자화(login.php), 예시 TOTP 비밀키 무효화, 마이그레이션 멱등화

_backup/(하드코딩 실 비밀값 포함)·config.local.php·런타임 상태는 .gitignore 제외.
2026-06-20 09:37:40 +09:00

109 lines
4 KiB
C

/* =============================================================================
* watchdog.c - 독립 워치독(IWDG) (RPi systemd 자동재시작 대응)
*
* IWDG 는 LSI(저속 내부 RC, STM32F407 기준 약 32kHz) 로 구동된다.
* timeout_ms 동안 watchdog_refresh() 호출이 없으면 MCU 가 하드 리셋된다.
*
* 타임아웃 계산:
* t_IWDG[s] = (reload + 1) * prescaler / f_LSI
* → reload = timeout_s * f_LSI / prescaler - 1
* prescaler 는 4,8,16,32,64,128,256 중 선택, reload 는 12비트(0..4095) 클램프.
*
* 주의: LSI 실주파수는 부품별로 17~47kHz 편차가 있다. 본 구현은 공칭 32kHz 로
* reload 를 계산하므로 실제 타임아웃은 ±오차가 있다. 안전을 위해 메인
* 헬스 루프의 refresh 주기는 설정 타임아웃의 1/2 이하로 둘 것.
* ===========================================================================*/
#include "watchdog.h"
#include "stm32f4xx_hal.h"
#include <stddef.h> /* size_t */
/* LSI 공칭 주파수(Hz). 데이터시트 typ. 32kHz. */
#define WDG_LSI_HZ 32000u
/* IWDG reload 레지스터는 12비트. */
#define WDG_RELOAD_MAX 0x0FFFu /* 4095 */
/* IWDG 핸들(이 모듈 소유). */
static IWDG_HandleTypeDef hiwdg;
/* prescaler 코드 → 분주값 매핑(인덱스 = HAL 의 IWDG_PRESCALER_*). */
typedef struct {
uint32_t code; /* IWDG_PRESCALER_x */
uint32_t div; /* 실제 분주값 */
} wdg_presc_t;
static const wdg_presc_t WDG_PRESC[] = {
{ IWDG_PRESCALER_4, 4u },
{ IWDG_PRESCALER_8, 8u },
{ IWDG_PRESCALER_16, 16u },
{ IWDG_PRESCALER_32, 32u },
{ IWDG_PRESCALER_64, 64u },
{ IWDG_PRESCALER_128, 128u },
{ IWDG_PRESCALER_256, 256u },
};
#define WDG_PRESC_COUNT (sizeof(WDG_PRESC) / sizeof(WDG_PRESC[0]))
void watchdog_init(uint32_t timeout_ms)
{
uint32_t chosen_code = IWDG_PRESCALER_256; /* 안전 기본(최대 타임아웃) */
uint32_t chosen_reload = WDG_RELOAD_MAX;
size_t i;
if (timeout_ms == 0u) {
timeout_ms = 1u; /* 0 방지 */
}
/* 요청 타임아웃을 12비트 reload 안에 담을 수 있는 가장 작은 prescaler 선택
* (분해능을 최대화). 안 되면 가장 큰 prescaler(256)로 최대 범위 사용. */
for (i = 0; i < WDG_PRESC_COUNT; ++i) {
uint32_t div = WDG_PRESC[i].div;
/* reload = timeout_ms * f_LSI / (1000 * div) - 1
* 오버플로 방지를 위해 64비트 중간 계산. */
uint64_t ticks = ((uint64_t)timeout_ms * (uint64_t)WDG_LSI_HZ) / 1000u;
uint64_t reload64;
if (ticks == 0u) {
reload64 = 0u;
} else {
reload64 = (ticks / div);
if (reload64 > 0u) {
reload64 -= 1u; /* reload 는 (값+1) 카운트 */
}
}
if (reload64 <= WDG_RELOAD_MAX) {
chosen_code = WDG_PRESC[i].code;
chosen_reload = (uint32_t)reload64;
break;
}
}
/* 루프가 끝까지 못 맞췄다면(타임아웃이 너무 큼) 256 분주 + 최대 reload 클램프.
* (32kHz, /256, reload=4095 → 약 32.7초가 IWDG 최대 타임아웃) */
if (i >= WDG_PRESC_COUNT) {
chosen_code = IWDG_PRESCALER_256;
chosen_reload = WDG_RELOAD_MAX;
}
if (chosen_reload > WDG_RELOAD_MAX) {
chosen_reload = WDG_RELOAD_MAX;
}
hiwdg.Instance = IWDG;
hiwdg.Init.Prescaler = chosen_code;
hiwdg.Init.Reload = chosen_reload;
/* IWDG_Init 은 카운터를 시작하고 즉시 한 번 refresh 한다. */
if (HAL_IWDG_Init(&hiwdg) != HAL_OK) {
/* 워치독을 켜지 못하면 시스템 보호가 불가하므로 무한 대기 → 외부 관찰 가능.
* (여기서 bsp_fatal 을 부르지 않는 이유: 의존성 최소화.) */
for (;;) {
__NOP();
}
}
}
void watchdog_refresh(void)
{
/* 카운터를 reload 값으로 재적재. 실패해도 다음 주기에 재시도. */
(void)HAL_IWDG_Refresh(&hiwdg);
}