POSA_LEAKSMS/firmware/common/bsp.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

250 lines
10 KiB
C

/* =============================================================================
* bsp.c - 보드 지원 패키지 (클럭/HAL/공통 주변장치 초기화) [STM32F407VGT6]
*
* 책임:
* - HAL_Init() 및 SystemClock_Config(): HSE 8MHz -> PLL -> SYSCLK 168MHz,
* AHB 168MHz, APB1 42MHz, APB2 84MHz, Flash 5WS, PWR/VOS Scale1.
* - 공통 GPIO RCC 클럭 게이팅 + 상태 LED.
* - bsp_led_set/toggle, bsp_fatal(IRQ 차단 + 고속 점멸 + 워치독 미갱신 → 리셋).
* - HAL 타임베이스를 SysTick 이 아닌 TIM6 으로 둬서 FreeRTOS 가 SysTick 을
* 단독으로 소유하게 한다(HAL_InitTick / TIM6_DAC_IRQHandler → HAL_IncTick).
*
* 참고: SystemClock_Config 값은 STM32F407 + 8MHz HSE(예: STM32F4-Discovery,
* 또는 LAN8720 RMII 보드의 25MHz 크리스털을 8MHz 로 분주 공급하는 구성)
* 기준의 표준 168MHz 설정이다. HSE 주파수가 다르면 PLLM 을 조정한다.
* ===========================================================================*/
#include "bsp.h"
#include "app_config.h"
#include "stm32f4xx_hal.h"
/* ── 상태 LED 핀 ───────────────────────────────────────────────────────────
* TODO(hw): 보드에 맞게 조정. 기본값은 STM32F4-Discovery 의 녹색 LED(PD12).
* - Discovery 계열: PD12(녹), PD13(주황), PD14(빨), PD15(파)
* - Nucleo-F4 계열 : PA5 (LD2) 로 바꿀 것
* 여기서는 단일 "상태 LED" 만 운영 가시성 용도로 사용한다.
*/
#define BSP_LED_PORT GPIOD
#define BSP_LED_PIN GPIO_PIN_12
#define BSP_LED_GPIO_CLK_EN() __HAL_RCC_GPIOD_CLK_ENABLE()
/* bsp_fatal() 고속 점멸 주기(루프 카운트). 정확한 타이밍은 불필요(곧 리셋됨). */
#define BSP_FATAL_BLINK_BUSYLOOP 400000u
/* ── HAL 타임베이스 전용 타이머 핸들 (TIM6) ───────────────────────────────── */
static TIM_HandleTypeDef htim6;
/* =============================================================================
* SystemClock_Config — HSE 8MHz -> 168MHz
* PLL: VCO_in = HSE/PLLM = 8/8 = 1MHz
* VCO_out = 1MHz * PLLN(336) = 336MHz
* SYSCLK = VCO_out / PLLP(2) = 168MHz
* PLLQ(7) -> 48MHz (USB/SDIO/RNG 클럭)
* AHB=/1(168), APB1=/4(42), APB2=/2(84), Flash latency 5WS.
* ===========================================================================*/
static void SystemClock_Config(void)
{
RCC_OscInitTypeDef osc = {0};
RCC_ClkInitTypeDef clk = {0};
/* 전압 레귤레이터 클럭 게이팅 + Scale1 (168MHz 동작에 필수). */
__HAL_RCC_PWR_CLK_ENABLE();
__HAL_PWR_VOLTAGE_SCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);
/* HSE -> PLL */
osc.OscillatorType = RCC_OSCILLATORTYPE_HSE;
osc.HSEState = RCC_HSE_ON;
osc.PLL.PLLState = RCC_PLL_ON;
osc.PLL.PLLSource = RCC_PLLSOURCE_HSE;
osc.PLL.PLLM = 8; /* 8MHz HSE -> 1MHz VCO 입력 */
osc.PLL.PLLN = 336; /* -> 336MHz VCO 출력 */
osc.PLL.PLLP = RCC_PLLP_DIV2; /* -> 168MHz SYSCLK */
osc.PLL.PLLQ = 7; /* -> 48MHz (RNG/USB) */
if (HAL_RCC_OscConfig(&osc) != HAL_OK) {
bsp_fatal("RCC_OscConfig");
}
/* 버스 클럭: SYSCLK=PLL, AHB=/1, APB1=/4, APB2=/2. Flash 5WS. */
clk.ClockType = RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_HCLK |
RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2;
clk.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
clk.AHBCLKDivider = RCC_SYSCLK_DIV1; /* HCLK = 168MHz */
clk.APB1CLKDivider = RCC_HCLK_DIV4; /* PCLK1 = 42MHz */
clk.APB2CLKDivider = RCC_HCLK_DIV2; /* PCLK2 = 84MHz */
if (HAL_RCC_ClockConfig(&clk, FLASH_LATENCY_5) != HAL_OK) {
bsp_fatal("RCC_ClockConfig");
}
}
/* ── 공통 GPIO 클럭 게이팅 ──────────────────────────────────────────────────
* RMII(LAN8720)/I2C/UART 등 다른 모듈이 자기 핀을 켜기 전에 흔히 쓰는 포트들을
* 미리 enable 해 둔다(중복 enable 은 무해). 각 드라이버가 자기 MspInit 에서
* 추가로 켜도 된다.
*/
static void bsp_gpio_clocks_init(void)
{
__HAL_RCC_GPIOA_CLK_ENABLE(); /* RMII: PA1/PA2/PA7 */
__HAL_RCC_GPIOB_CLK_ENABLE(); /* RMII: PB11/PB12/PB13 · SHT30 I2C1: PB6/PB7 */
__HAL_RCC_GPIOC_CLK_ENABLE(); /* RMII: PC1/PC4/PC5 */
__HAL_RCC_GPIOD_CLK_ENABLE(); /* 로그 UART USART3: PD8/PD9 · 상태 LED: PD12 */
}
/* ── 상태 LED 초기화 ────────────────────────────────────────────────────── */
static void bsp_led_init(void)
{
GPIO_InitTypeDef g = {0};
BSP_LED_GPIO_CLK_EN();
g.Pin = BSP_LED_PIN;
g.Mode = GPIO_MODE_OUTPUT_PP;
g.Pull = GPIO_NOPULL;
g.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(BSP_LED_PORT, &g);
HAL_GPIO_WritePin(BSP_LED_PORT, BSP_LED_PIN, GPIO_PIN_RESET);
}
/* =============================================================================
* 공개 API
* ===========================================================================*/
void bsp_init(void)
{
HAL_Init(); /* NVIC 그룹/Flash prefetch/기본 타임베이스 설정 */
SystemClock_Config(); /* 168MHz */
bsp_gpio_clocks_init();
bsp_led_init();
}
void bsp_led_set(int on)
{
HAL_GPIO_WritePin(BSP_LED_PORT, BSP_LED_PIN,
on ? GPIO_PIN_SET : GPIO_PIN_RESET);
}
void bsp_led_toggle(void)
{
HAL_GPIO_TogglePin(BSP_LED_PORT, BSP_LED_PIN);
}
void bsp_fatal(const char *reason)
{
(void)reason; /* 로그는 호출측 책임. 여기선 안전 정지만 한다. */
/* 인터럽트 전면 차단: 더 이상 어떤 태스크/ISR 도 실행되지 않게 한다.
* 이 함수는 워치독을 갱신하지 않으므로 IWDG 가 곧 MCU 를 리셋한다. */
__disable_irq();
for (;;) {
bsp_led_toggle();
for (volatile uint32_t i = 0; i < BSP_FATAL_BLINK_BUSYLOOP; ++i) {
__NOP();
}
}
}
/* =============================================================================
* bsp_rand32 — 하드웨어 RNG 기반 32비트 난수 (강한 심볼)
*
* lwipopts.h 의 LWIP_RAND() 백엔드. net.c 의 약한(weak) 예측 가능 xorshift 대체
* 구현을 이 강한 심볼이 덮어쓴다. TCP 초기 시퀀스 번호 / DHCP·DNS 트랜잭션 ID 의
* 예측 가능성을 줄인다.
*
* 주의: RNG 주변장치는 TLS(tls_mbedtls.c)도 사용한다. 서로 다른 HAL 핸들이지만
* 둘 다 RNG->DR 을 읽기만 하므로 공존 가능하다(드물게 상관된 값이 나올 수 있으나
* lwip 용도에는 충분). RNG 클럭 소스는 PLLQ=48MHz (SystemClock_Config 참조).
* RNG 불가 시 마지막 수단으로 틱 혼합값을 반환한다(예측 가능 — 정상 동작에선 미사용).
* ===========================================================================*/
static RNG_HandleTypeDef s_bsp_rng;
static int s_bsp_rng_ready = 0;
uint32_t bsp_rand32(void)
{
if (!s_bsp_rng_ready) {
__HAL_RCC_RNG_CLK_ENABLE();
s_bsp_rng.Instance = RNG;
if (HAL_RNG_Init(&s_bsp_rng) != HAL_OK) {
return 0x9E3779B9u ^ (uint32_t)HAL_GetTick(); /* RNG 불가: 폴백 */
}
s_bsp_rng_ready = 1;
}
uint32_t v = 0;
if (HAL_RNG_GenerateRandomNumber(&s_bsp_rng, &v) != HAL_OK) {
return 0x9E3779B9u ^ (uint32_t)HAL_GetTick(); /* 일시 오류: 폴백 */
}
return v;
}
/* =============================================================================
* HAL 타임베이스 오버라이드 (TIM6)
*
* 기본 HAL 은 SysTick 을 타임베이스로 쓴다. 그러나 FreeRTOS 도 SysTick 을
* 스케줄러 틱으로 사용하므로 충돌한다. 아래 약(weak) 함수를 재정의하여 HAL
* 타임베이스를 TIM6(기본 우선순위) 으로 옮긴다. FreeRTOS 는 SysTick 을 단독
* 소유하고, HAL_GetTick()/HAL_Delay() 는 TIM6 인터럽트로 동작한다.
*
* HAL_Init() 내부에서 TICK_INT_PRIORITY 로 HAL_InitTick() 이 호출된다.
* ===========================================================================*/
HAL_StatusTypeDef HAL_InitTick(uint32_t TickPriority)
{
RCC_ClkInitTypeDef clkcfg;
uint32_t flashLatency;
uint32_t pclk1;
uint32_t tim_clk;
uint32_t prescaler;
/* TIM6 클럭 = APB1 타이머 클럭. APB1 분주(>1)면 타이머 클럭은 PCLK1 x2. */
HAL_RCC_GetClockConfig(&clkcfg, &flashLatency);
pclk1 = HAL_RCC_GetPCLK1Freq();
if (clkcfg.APB1CLKDivider == RCC_HCLK_DIV1) {
tim_clk = pclk1;
} else {
tim_clk = pclk1 * 2u; /* APB1 prescaler != 1 → 타이머 클럭 2배 (84MHz) */
}
/* 1MHz(=1us) 카운팅이 되도록 프리스케일러 계산 → 1ms 마다 업데이트 IRQ. */
prescaler = (tim_clk / 1000000u) - 1u;
__HAL_RCC_TIM6_CLK_ENABLE();
htim6.Instance = TIM6;
htim6.Init.Prescaler = prescaler; /* → 1MHz */
htim6.Init.CounterMode = TIM_COUNTERMODE_UP;
htim6.Init.Period = 1000u - 1u; /* 1000 카운트 = 1ms */
htim6.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim6.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
if (HAL_TIM_Base_Init(&htim6) != HAL_OK) {
return HAL_ERROR;
}
/* TIM6 업데이트 인터럽트 활성화. uwTickPrio 는 HAL 내부에서 참조됨. */
if (TickPriority < (1uL << __NVIC_PRIO_BITS)) {
HAL_NVIC_SetPriority(TIM6_DAC_IRQn, TickPriority, 0);
uwTickPrio = TickPriority;
} else {
return HAL_ERROR;
}
HAL_NVIC_EnableIRQ(TIM6_DAC_IRQn);
return HAL_TIM_Base_Start_IT(&htim6);
}
/* HAL_SuspendTick/ResumeTick 도 TIM6 기준으로 재정의(HAL_Delay 등 일관성). */
void HAL_SuspendTick(void)
{
__HAL_TIM_DISABLE_IT(&htim6, TIM_IT_UPDATE);
}
void HAL_ResumeTick(void)
{
__HAL_TIM_ENABLE_IT(&htim6, TIM_IT_UPDATE);
}
/* TIM6 업데이트 인터럽트 → HAL 틱 증가.
* (startup_stm32f407xx.s 의 벡터 이름과 일치해야 한다: TIM6_DAC_IRQHandler) */
void TIM6_DAC_IRQHandler(void)
{
if (__HAL_TIM_GET_FLAG(&htim6, TIM_FLAG_UPDATE) != RESET &&
__HAL_TIM_GET_IT_SOURCE(&htim6, TIM_IT_UPDATE) != RESET) {
__HAL_TIM_CLEAR_IT(&htim6, TIM_IT_UPDATE);
HAL_IncTick();
}
}