2026-04-29

TF-A 의 eMMC 초기화 실패 시 panic 대신 SoC 리셋으로 자가 복구하기

STM32MP153D 보드를 eMMC 부팅으로 양산하다 보면 가끔 부팅 중 멈춰 있는 보드를 만난다. 시리얼 콘솔에는 이게 마지막 메시지다.

SDMMC2 init failed
PANIC at PC : ...

그리고 그대로 영원히 정지. 외부 전원을 끊어 다시 넣어주면 멀쩡히 살아나는 경우가 대부분인데, 이건 일과성 실패 — 전원 레일 ramp 타이밍, eMMC fast-boot 모드 진입 직후의 버스 라인 노이즈, RCC 클록 도메인 안정화 jitter 같은, cold boot 한 번이면 사라지는 부류의 문제다. 그런데 TF-A BL2 는 그 한 번의 cold boot 기회를 스스로 만들지 못하고 panic() 에 갇혀 사람이 와서 전원을 뽑아주기를 기다린다. 양산 환경에서는 이게 사고가 된다.

왜 panic 보다 reset 인가

일과성 실패의 복구 경로는 결국 cold boot 한 번이다. 그러면 BL2 가 panic() 으로 SoC 를 정지시키는 대신 stm32mp_system_reset() 으로 SoC 자체를 reset 시켜버리는 편이 자연스럽다. RCC 시스템 리셋이 들어가면 BootROM 이 다시 실행되어 BL2 도 처음부터 다시 unpack/실행되니, 일시적 잡음으로 인한 init 실패는 다음 부팅 cycle 에서 자동으로 해소된다. 사람이 안 와도 된다.

패치 내용

변경 위치는 한 곳이다 — plat/st/common/bl2_io_storage.cboot_mmc(). stm32_sdmmc2_mmc_init() 가 실패한 직후 panic() 직전에 reset 호출 한 줄과 짧은 주석을 끼워 넣는다.

변경 전

params.device_info = &mmc_info;
if (stm32_sdmmc2_mmc_init(&params) != 0) {
    ERROR("SDMMC%u init failed\n", boot_interface_instance);
    panic();
}

변경 후

params.device_info = &mmc_info;
if (stm32_sdmmc2_mmc_init(&params) != 0) {
    ERROR("SDMMC%u init failed - resetting system\n",
          boot_interface_instance);
    /*
     * eMMC init failures here are usually transient (rail-ramp
     * timing, bus-line noise on fast-boot entry, RCC clock-domain
     * settling jitter). panic() leaves the SoC frozen and forces
     * an external power cycle; a system reset lets BootROM re-run
     * the entire boot path, which most transient failures survive.
     * stm32mp_system_reset() is __dead2, so panic() below is a
     * defensive fallback if the reset circuit is itself wedged,
     * and a no-return marker for analyzers.
     */
    stm32mp_system_reset();
    panic();
}

그리고 reset API 헤더 한 줄 추가.

 #include <drivers/st/stm32_sdmmc2.h>
+#include <drivers/st/stm32mp_reset.h>
 #include <drivers/usb_device.h>

전체 diff 는 한 파일에서 14줄 추가, 1줄 삭제. 정말 한 줄 변경이라고 봐도 무방하다.

panic() 도 그대로 두는 이유

stm32mp_system_reset() 은 헤더에 __dead2 (no-return) attribute 가 달려 있어, 호출 즉시 SoC 가 리셋되고 그 다음 줄의 panic() 에는 정상 동작에서는 절대 도달하지 않는다. 그런데 일부러 panic() 호출을 지우지 않고 그대로 둔 데는 두 가지 이유가 있다.

  1. 방어적 fallback. 리셋 회로나 전원 시퀀서에 결함이 있어 stm32mp_system_reset() 이 실제로는 리셋을 못 일으키는 만일의 상황을 가정해보자. 이때는 그래도 panic() 루프에 머물러 있는 편이, 실패 지점 너머로 실행을 계속하는 것보다 훨씬 안전하다. 절대 정지하면 안 되는 곳에서 정지하는 것보다, 절대 진행하면 안 되는 곳에서 진행하는 게 더 위험하다.
  2. 정적 분석기와 후속 수정자에 대한 명시적 표식. 이 분기는 "절대 그 이상 진행해서는 안 된다" 는 의미를 코드에 박아두는 것이다. 누군가 나중에 코드를 손보면서 reset 호출만 지우고 정책을 슬그머니 되돌리는 일을 막는다.

이런 방어선은 비용이 거의 없으면서 (panic() 한 줄) 코드 의도를 명확히 남긴다는 점에서 가성비가 좋다.

다운스트림 v2.4 → mainline v2.10 포팅 노트

본 변경은 사실 STM32MP153D 보드에서 TF-A v2.4 백포트로 1년 넘게 양산 운영해온 정책이다. v2.4 다운스트림 패치 (0005-mmc-init-reset.patch) 에는 함께 들어있는 두 가지가 더 있는데, mainline 송신본에서는 의도적으로 뺐다.

  • MMC_DEFAULT_MAX_RETRIES 를 5에서 50으로 늘리는 변경. 무조건 reset 으로 떨어지는 fallback 이 있는 이상 retry 횟수를 더 늘릴 동기가 약하고, 한 부팅 안에서 50번 재시도하느니 한 번 reset 하고 cold boot 부터 깔끔히 다시 가는 편이 결과적으로 빠르다.
  • "SDMMC init1 start / done" NOTICE 디버그 로그. production BL2 콘솔을 가능한 한 조용하게 유지하기 위해 뺐다. 디버깅이 필요하면 별도 NOTICE_LEVEL 로 켜는 편이 깔끔.

또 v2.4 에는 stm32image 포맷용 별도 파일 plat/st/common/bl2_stm32_io_storage.c 에도 동일한 panic() 위치가 있었는데, v2.10 에서는 이 파일이 bl2_io_storage.c 로 통합되어 패치 대상이 한 곳뿐이다. 통합된 김에 한 hunk 로 두 boot flow 를 모두 커버한다.

upstream 송신 결과

2026-04-29 03:21 UTC, TF-A 메일링 리스트로 송신했다.

  • Subject: [PATCH] fix(st): reset SoC instead of panic() on MMC init failure
  • Message-Id: <20260429032109.789181-1-happycpu@gmail.com>
  • To: tf-a@lists.trustedfirmware.org, yann.gautier@foss.st.com (ST plat maintainer)
  • Cc: madhukar.pappireddy@arm.com, manish.badarkhe@arm.com, olivier.deprez@arm.com (TF-A maintainers)
  • Archive: lists.trustedfirmware.org/archives/list/tf-a@lists.trustedfirmware.org/

TF-A 는 lore.kernel.org 가 아닌 hyperkitty 에 메일링 아카이브가 인덱싱되고, 실제 merge 채널은 메일링이 아니라 review.trustedfirmware.org Gerrit 이다. 메일링은 정책 RFC 토론 용도. ST plat maintainer ack 받으면 같은 commit 을 Gerrit 에 push 해서 정식 review 트랙으로 넘길 예정이다.

마무리

한 줄짜리 변경이지만 운영 입장에서 의미가 크다. "보드를 손으로 가서 전원을 뽑아야 살아나는 사고" 가 "다음 cold boot 한 번이면 자동 복구되는 일과성 잡음" 으로 바뀐다. 그동안 같은 경험을 모은 보드가 한두 대가 아니라면 이 변경은 사고 수를 통째로 한 자리수씩 깎아준다.

비슷한 위치에서 panic() 으로 정지하는 부트로더/펌웨어 코드는 가능한 한 reset 으로 떨어뜨려두는 정책이 양산 환경에 맞다. __dead2 함수 호출 + panic() fallback 의 두 줄짜리 패턴은 어디서 써도 가성비가 좋다.

댓글 없음:

댓글 쓰기

Linux gpio-74x164 에 lines-initial-states 지원 추가 — 74HC595 chain 의 부팅 글리치 잡기 (개정판)

74HC595 / 74LVC594 같은 직렬-입력 병렬-출력 시프트 레지스터를 SPI 로 cascade 해서 GPIO 32개를 만들어 쓰는 보드를 다뤄본 적이 있으면 부팅 직후 짧게 깜빡이는 LED 한 줄을 본 기억이 있을 것이다. active-low...