/* ============================================================================= * net.c - 이더넷/LwIP 네트워크 (net.h 구현) * * 부트 흐름: * net_init(): * 1) tcpip_init() -> LwIP tcpip 스레드 기동(FreeRTOS). * 2) netif_add(ethernetif_init, tcpip_input) 으로 ETH netif 등록. * 3) default netif 지정 + status/link 콜백 등록. * 4) APP_NET_USE_DHCP 면 dhcp_start, 아니면 static 적용. * net_wait_up(): 링크 업 + IP 확보까지 블록(타임아웃). DHCP 타임아웃 시 * static 으로 폴백. * * tcpip 스레드 외부(애플리케이션 태스크)에서 LwIP 코어를 만지므로, * netifapi_* / tcpip_callback 으로 직렬화한다(LWIP_TCPIP_CORE_LOCKING=1). * ===========================================================================*/ #include "net.h" #include "app_config.h" #include "applog.h" #include "lwip/tcpip.h" #include "lwip/netif.h" #include "lwip/netifapi.h" #include "lwip/dhcp.h" #include "lwip/dns.h" #include "lwip/ip_addr.h" #include "lwip/init.h" #include "FreeRTOS.h" #include "task.h" #include #include /* ethernetif.c 가 제공하는 LwIP netif 초기화 콜백. */ extern err_t ethernetif_init(struct netif *netif); /* ── 내부 상태 ──────────────────────────────────────────────────────────── */ static struct netif s_netif; /* 링크 업 + IP 확보 여부(콜백/태스크 경합 -> volatile). */ static volatile int s_link_up = 0; static volatile int s_addr_set = 0; /* 0.0.0.0 가 아닌 IP 가 할당됨 */ static int s_dhcp_mode = 0; /* 현재 DHCP 로 시도 중인지 */ /* ── 콜백 ───────────────────────────────────────────────────────────────── */ /* IP 주소가 바뀌면(특히 0->유효) 호출됨. */ static void net_status_cb(struct netif *netif) { if (netif_is_up(netif) && !ip4_addr_isany_val(*netif_ip4_addr(netif))) { s_addr_set = 1; char ip[16]; ip4addr_ntoa_r(netif_ip4_addr(netif), ip, sizeof(ip)); LOGI("net: IP 할당 %s", ip); } else { s_addr_set = 0; } } /* 물리 링크(케이블) 상태 변경 시 호출됨(ethernetif 가 PHY 폴링으로 통지). */ static void net_link_cb(struct netif *netif) { if (netif_is_link_up(netif)) { s_link_up = 1; LOGI("net: 링크 업"); /* 링크가 올라오면 인터페이스 up + 주소 절차 시작. * 이 콜백은 ethernetif 의 netifapi_netif_set_link_up 경유로 tcpip 코어락을 * 이미 보유한 문맥에서 호출된다. 따라서 여기서 netifapi_*(다시 코어락 시도) * 를 부르면 비재귀 뮤텍스 자기-데드락이 난다. raw netif_set_up 을 쓴다. */ netif_set_up(netif); } else { s_link_up = 0; s_addr_set = 0; LOGW("net: 링크 다운"); } } /* ── 주소 적용 헬퍼 ─────────────────────────────────────────────────────── */ static void net_apply_static(void) { ip4_addr_t ip, mask, gw, dns; if (!ip4addr_aton(APP_NET_STATIC_IP, &ip)) IP4_ADDR(&ip, 192,168,0,50); if (!ip4addr_aton(APP_NET_STATIC_NETMASK,&mask))IP4_ADDR(&mask, 255,255,255,0); if (!ip4addr_aton(APP_NET_STATIC_GW, &gw)) IP4_ADDR(&gw, 192,168,0,1); /* DHCP 가 돌고 있었다면 멈춘다(폴백 경로). */ if (s_dhcp_mode) { netifapi_dhcp_stop(&s_netif); s_dhcp_mode = 0; } netifapi_netif_set_addr(&s_netif, &ip, &mask, &gw); /* DNS 서버 설정. dns_setserver 는 raw 코어 API 이고, net_apply_static 은 * net_wait_up(앱 태스크, tcpip 스레드 아님)에서도 호출되므로 코어락으로 * 직렬화한다. (netifapi_* 호출과 중첩되지 않게 이 호출만 짧게 감싼다.) */ if (ip4addr_aton(APP_NET_STATIC_DNS, &dns)) { ip_addr_t d; ip_addr_copy_from_ip4(d, dns); LOCK_TCPIP_CORE(); dns_setserver(0, &d); UNLOCK_TCPIP_CORE(); } netifapi_netif_set_up(&s_netif); s_addr_set = 1; LOGI("net: static 적용 ip=%s gw=%s dns=%s", APP_NET_STATIC_IP, APP_NET_STATIC_GW, APP_NET_STATIC_DNS); } /* ── 공개 API ───────────────────────────────────────────────────────────── */ void net_init(void) { ip4_addr_t ip0, mask0, gw0; /* tcpip 스레드 기동. NULL 콜백 = 동기 완료 대기 불필요. */ tcpip_init(NULL, NULL); /* netif 초기 주소: DHCP 면 0.0.0.0 으로 시작, static 이면 곧바로 채운다. * (정적 값은 아래 분기에서 다시 set_addr 로 적용) */ ip4_addr_set_zero(&ip0); ip4_addr_set_zero(&mask0); ip4_addr_set_zero(&gw0); /* ethernetif_init 가 MAC/PHY/DMA 를 세팅하고, 수신은 tcpip_input 으로 전달. */ if (netifapi_netif_add(&s_netif, &ip0, &mask0, &gw0, NULL, ethernetif_init, tcpip_input) != ERR_OK) { LOGE("net: netif_add 실패"); return; } netifapi_netif_set_default(&s_netif); #if LWIP_NETIF_HOSTNAME s_netif.hostname = BOARD_DEVICE_ID; /* DHCP 호스트명(옵션 12) */ #endif /* 콜백 등록(코어 컨텍스트 안전: netif 핸들 직접 접근은 set 함수로). */ netif_set_status_callback(&s_netif, net_status_cb); netif_set_link_callback(&s_netif, net_link_cb); /* 인터페이스 administratively up. 실제 링크 업은 PHY 폴링이 통지. */ netifapi_netif_set_up(&s_netif); #if APP_NET_USE_DHCP s_dhcp_mode = 1; if (netifapi_dhcp_start(&s_netif) != ERR_OK) { LOGE("net: dhcp_start 실패 -> static 폴백"); net_apply_static(); } else { LOGI("net: DHCP 시작(타임아웃 %ums 후 static 폴백)", APP_NET_DHCP_TIMEOUT_MS); } #else net_apply_static(); #endif } int net_wait_up(uint32_t timeout_ms) { const uint32_t step_ms = 100; uint32_t waited = 0; /* 1단계: 물리 링크 업 대기(케이블 연결). */ while (!s_link_up) { if (timeout_ms != 0 && waited >= timeout_ms) { LOGW("net: 링크 업 타임아웃"); return -1; } vTaskDelay(pdMS_TO_TICKS(step_ms)); waited += step_ms; } /* 2단계: IP 주소 확보 대기. */ #if APP_NET_USE_DHCP uint32_t dhcp_waited = 0; while (!s_addr_set) { /* DHCP 타임아웃 -> static 폴백(1회). */ if (s_dhcp_mode && dhcp_waited >= APP_NET_DHCP_TIMEOUT_MS) { LOGW("net: DHCP 타임아웃 -> static 폴백"); net_apply_static(); } if (timeout_ms != 0 && waited >= timeout_ms) { LOGW("net: IP 확보 타임아웃"); return -1; } vTaskDelay(pdMS_TO_TICKS(step_ms)); waited += step_ms; dhcp_waited += step_ms; } #else while (!s_addr_set) { if (timeout_ms != 0 && waited >= timeout_ms) { LOGW("net: IP 확보 타임아웃"); return -1; } vTaskDelay(pdMS_TO_TICKS(step_ms)); waited += step_ms; } #endif char ip[16]; net_ip_str(ip, sizeof(ip)); LOGI("net: 준비 완료 ip=%s", ip); return 0; } net_status_t net_status(void) { return (s_link_up && s_addr_set && netif_is_up(&s_netif)) ? NET_UP : NET_DOWN; } void net_ip_str(char *buf, uint32_t buflen) { if (buf == NULL || buflen == 0) { return; } if (s_addr_set && !ip4_addr_isany_val(*netif_ip4_addr(&s_netif))) { char tmp[16]; ip4addr_ntoa_r(netif_ip4_addr(&s_netif), tmp, sizeof(tmp)); /* 안전 복사(널 종료 보장). */ size_t n = strlen(tmp); if (n >= buflen) n = buflen - 1; memcpy(buf, tmp, n); buf[n] = '\0'; } else { const char *z = "0.0.0.0"; size_t n = strlen(z); if (n >= buflen) n = buflen - 1; memcpy(buf, z, n); buf[n] = '\0'; } } /* ── LWIP_RAND() 백엔드 ───────────────────────────────────────────────────── * lwipopts.h 의 LWIP_RAND() 가 bsp_rand32() 를 호출한다. 하드웨어 RNG 가 * 정식 구현(rng.c/bsp.c)될 때까지의 약한 대체 구현. RNG 페리페럴이 준비되면 * 그쪽 strong 심볼이 우선하도록 weak 로 둔다. * TODO(hw): bsp/rng 모듈에서 HW RNG(RNG->DR) 기반 강한 bsp_rand32() 제공. */ __attribute__((weak)) uint32_t bsp_rand32(void) { /* xorshift32 + FreeRTOS 틱 시드(예측 가능 — TLS/DHCP 트랜잭션 ID 보안엔 부적합). * 운영에서는 반드시 HW RNG 구현으로 대체. */ static uint32_t s = 0x1234abcdU; s ^= (uint32_t)xTaskGetTickCount(); s ^= s << 13; s ^= s >> 17; s ^= s << 5; if (s == 0) s = 0xa5a5a5a5U; return s; }