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 의 두 줄짜리 패턴은 어디서 써도 가성비가 좋다.

2025-06-02

gitea 설치

깃 서버

wget -O gitea https://dl.gitea.com/gitea/1.22.0/gitea-1.22.0-linux-amd64
chmod a+x gitea
./gitea web

github 에서 미러링시에, access token을 넣어야하는데,
access token생성시  repo 항목 모두 채크하고 만들어야 땡겨짐.


2025-05-25

와이파이핫스팟 인터넷 활성화.wifi hotsopt internet enable

우분투22.04에서 와이파이핫스팟을 했는데. 연결된 클라이언트들이 인터넷이 안된다.
이유를 gpt에 물어봤더니. 보안상 masquerade와 forwarding을 drop시켰다.
이것들을 동작시 활성화 하도록 스크립트를 추가한다.

sudo mkdir -p /etc/NetworkManager/dispatcher.d

sudo vi /etc/NetworkManager/dispatcher.d/90-hotspot-nat.sh
#!/bin/bash

IFACE="$1"
STATUS="$2"

# 핫스팟 인터페이스명(수정 필요할 수도 있음)
HOTSPOT_IF="wlp0s20f3"

# 유선인터페이스 자동 탐색
eth_if=$(ip -o link show | awk -F': ' '{print $2}' | grep -E '^en' | grep -v 'br' | grep -v 'docker' | grep -v 'veth' | grep -v 'lo' | head -n1)

if [ "$IFACE" = "$HOTSPOT_IF" ] && [ "$STATUS" = "up" ]; then
    # NAT 추가 (중복 X)
    if ! iptables -t nat -C POSTROUTING -s 10.42.0.0/24 -o $eth_if -j MASQUERADE 2>/dev/null; then
        iptables -t nat -A POSTROUTING -s 10.42.0.0/24 -o $eth_if -j MASQUERADE
    fi
    # FORWARD 체인 정책 허용
    iptables -P FORWARD ACCEPT
fi

nmcli con modify Hotspot wifi-sec.key-mgmt wpa-psk
nmcli con modify Hotspot wifi-sec.pmf default
#nmcli con down Hotspot && nmcli con up Hotspot

sudo chmod +x /etc/NetworkManager/dispatcher.d/90-hotspot-nat.sh

2025-04-25

[siwg917] 공유기 mac 얻기, arp table에서 확인.



1. arp 요청함수.

#include "lwip/netif.h"
#include "lwip/ip4_addr.h"
#include "lwip/etharp.h"

#define ARP_RETRY_COUNT 10
#define ARP_WAIT_MS 100

void request_and_print_gateway_mac(const sl_net_wifi_client_profile_t *profile, struct netif *netif)
{
ip4_addr_t gateway_ip;
IP4_ADDR(&gateway_ip,
profile->ip.ip.v4.gateway.bytes[0],
profile->ip.ip.v4.gateway.bytes[1],
profile->ip.ip.v4.gateway.bytes[2],
profile->ip.ip.v4.gateway.bytes[3]);

// 1. ARP 요청 전송
if (etharp_request(netif, &gateway_ip) != ERR_OK) {
printf("Failed to send ARP request to gateway\r\n");
return;
}

// 2. ARP 응답 대기 및 MAC 주소 찾기
struct eth_addr *eth_ret;
const ip4_addr_t *resolved_ip;

for (int i = 0; i < ARP_RETRY_COUNT; i++) {
if (etharp_find_addr(netif, &gateway_ip, &eth_ret, &resolved_ip) >= 0) {
printf("Gateway MAC: %02X:%02X:%02X:%02X:%02X:%02X\r\n",
eth_ret->addr[0], eth_ret->addr[1], eth_ret->addr[2],
eth_ret->addr[3], eth_ret->addr[4], eth_ret->addr[5]);
return;
}
osDelay(100); // FreeRTOS 기반 대기
}

printf("Gateway MAC: [Not found in ARP cache after ARP request]\r\n");
}


2. 업데이트된 arp테이블에서 mac 보기. (함수일부)
void display_client_network_info(sl_net_wifi_client_profile_t profile)
{

printf("\r\n=== Wi-Fi Client Network Information ===\r\n");
printf("SSID: %s\r\n", profile.config.ssid.value);
printf("Security: %d\r\n", profile.config.security);

// IP 정보 표시
printf("\r\n--- IP Configuration ---\r\n");
printf("IP Management Mode: %s\r\n",
profile.ip.mode == SL_IP_MANAGEMENT_DHCP ? "DHCP" :
profile.ip.mode == SL_IP_MANAGEMENT_STATIC_IP ? "Static IP" : "Link Local");

if (profile.ip.type & SL_IPV4) {
printf("IPv4 Address: %d.%d.%d.%d\r\n",
profile.ip.ip.v4.ip_address.bytes[0],
profile.ip.ip.v4.ip_address.bytes[1],
profile.ip.ip.v4.ip_address.bytes[2],
profile.ip.ip.v4.ip_address.bytes[3]);

printf("Subnet Mask: %d.%d.%d.%d\r\n",
profile.ip.ip.v4.netmask.bytes[0],
profile.ip.ip.v4.netmask.bytes[1],
profile.ip.ip.v4.netmask.bytes[2],
profile.ip.ip.v4.netmask.bytes[3]);

printf("Gateway: %d.%d.%d.%d\r\n",
profile.ip.ip.v4.gateway.bytes[0],
profile.ip.ip.v4.gateway.bytes[1],
profile.ip.ip.v4.gateway.bytes[2],
profile.ip.ip.v4.gateway.bytes[3]);

// Gateway MAC 주소 표시
struct netif *netif = netif_default; // 또는 Wi-Fi에 해당하는 netif 지정
ip4_addr_t gateway_ip;
struct eth_addr *eth_ret;
const ip4_addr_t *resolved_ip;

IP4_ADDR(&gateway_ip,
profile.ip.ip.v4.gateway.bytes[0],
profile.ip.ip.v4.gateway.bytes[1],
profile.ip.ip.v4.gateway.bytes[2],
profile.ip.ip.v4.gateway.bytes[3]);

if (etharp_find_addr(netif, &gateway_ip, &eth_ret, &resolved_ip) >= 0) {
printf("Gateway MAC: %02X:%02X:%02X:%02X:%02X:%02X\r\n",
eth_ret->addr[0], eth_ret->addr[1], eth_ret->addr[2],
eth_ret->addr[3], eth_ret->addr[4], eth_ret->addr[5]);
} else {
printf("Gateway MAC: [Not found in ARP cache]\r\n");
}
}
}

3. 실행위치
static void application_start(void *argument)
{
UNUSED_PARAMETER(argument);
sl_status_t status;
sl_ip_address_t ip_address = { 0 };
sl_net_wifi_client_profile_t profile = { 0 };
sl_wifi_performance_profile_t performance_profile = { .profile = ASSOCIATED_POWER_SAVE_LOW_LATENCY };
status = sl_net_init(SL_NET_WIFI_CLIENT_INTERFACE, &client_configuration, &wifi_client_context, NULL);
if (status != SL_STATUS_OK) {
printf("Failed to start Wi-Fi Client interface: 0x%lx\r\n", status);
return;
}
printf("Wi-Fi Client interface success\r\n");

#ifdef SLI_SI91X_MCU_INTERFACE
uint8_t xtal_enable = 1;
status = sl_si91x_m4_ta_secure_handshake(SL_SI91X_ENABLE_XTAL, 1, &xtal_enable, 0, NULL);
if (status != SL_STATUS_OK) {
printf("Failed to bring m4_ta_secure_handshake: 0x%lx\r\n", status);
return;
}
printf("m4_ta_secure_handshake Success\r\n");

2025-02-02

[qemu] 실행

DISTRO=nodistro MACHINE=qemuarm source layers/meta-st/scripts/envsetup.sh

runqemu qemuarm

다음에는 간단한 distro를 만들어서 해봐야겠다.

2024-10-10

구글드라이브 wget 다운받는 명령어

구글드라이브의 파일을 링크가있는사람 무조건받기로 해서 공유링크를 복사하여 아래의 "구글드라이브공유링크"에 붙여넣는다. 원하는파일명은적절히 써준다. 

주의: 실행속성은 추가로 변경해주어야한다. chmod a+x 파일명

GDRIVE_LINK="구글드라이브공유링크" && FILE_ID=$(echo $GDRIVE_LINK | sed -n 's#.*d/\([^/]*\)/.*#\1#p') && wget --no-check-certificate "https://drive.google.com/uc?export=download&id=${FILE_ID}" -O 원하는파일명


2024-08-24

암호화 + base64 하고 쉘스크립트.

 #!/bin/bash


# IV와 UserKey를 설정합니다 (아스키 문자열을 그대로 사용).

IV="0123456789abcdef" 

UserKey="0123456789abcdef"


# IV와 UserKey를 16진수로 변환

IV_HEX=$(echo -n "$IV" | hexdump -ve '1/1 "%.2x"')

UserKey_HEX=$(echo -n "$UserKey" | hexdump -ve '1/1 "%.2x"')


# 암호화할 문자열을 설정합니다.

input='to_encryption_plainText'


# 입력 문자열을 임시 파일에 저장합니다.

echo -n "$input" > input.txt


# AES-128-CBC 모드로 암호화하여 결과를 바이너리 파일로 저장

openssl enc -aes-128-cbc -K "$UserKey_HEX" -iv "$IV_HEX" -in input.txt -out encrypted.bin


# 암호화된 파일을 Base64로 인코딩하면서 개행 문자 없이 출력

openssl enc -base64 -A -in encrypted.bin


2024-08-15

2024-04-26

network metric priority



1. 변경할 인터페이스
2. 그것의 gateway와 metric값 취득
3. 변경.(nmcli변경후 재부팅 또는 ip명령으로 기존값 제거)

nmcli con mod <con> ipv4.route-metric <new value>
ip route del default via <gateway ip> metric <old value>

nmcli con mod eth0 ipv4.route-metric 102
ip route del default via 192.168.222.1 metric 100

Sharing /dev/ttyUSB0 between Claude Code and a Human via tmux

Sharing /dev/ttyUSB0 between Claude Code and a Human via tmux When you debug an embedded board with Claude Code, sooner or later you want t...