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 제외.
109 lines
4 KiB
C
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);
|
|
}
|