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 제외.
446 lines
18 KiB
C
446 lines
18 KiB
C
/* =============================================================================
|
|
* ethernetif.c - LwIP <-> STM32 HAL_ETH 글루 (STM32F407 + LAN8720 RMII)
|
|
*
|
|
* ST 공식 LwIP "ethernetif.c" 템플릿(STM32F4 + LAN8720)을 RTOS(NO_SYS=0)용으로
|
|
* 적응. 핵심 구성:
|
|
* - low_level_init(): HAL_ETH 핸들/MAC(STM32 UID 유도)/DMA 디스크립터/버퍼
|
|
* 설정 후 ETH 시작.
|
|
* - low_level_output(): pbuf 체인 -> HAL_ETH_Transmit (Tx 디스크립터).
|
|
* - ethernetif_input task: RX 인터럽트 -> 세마포어 -> 태스크가 프레임을 꺼내
|
|
* pbuf 로 복사 후 netif->input(=tcpip_input) 호출.
|
|
* - HAL_ETH_MspInit(): RMII GPIO/클럭/NVIC 설정.
|
|
* - PHY 링크 폴링 태스크: LAN8720 BSR 를 읽어 링크/속도/듀플렉스 통지.
|
|
*
|
|
* 디스크립터/버퍼는 일반 SRAM(0x2000xxxx) 에 두며 4바이트 정렬. ETH DMA 는
|
|
* CCM(0x10000000) 에 접근할 수 없으므로 CCM 배치 금지.
|
|
*
|
|
* 참고 핀맵(RMII, app/하드웨어 명세):
|
|
* REF_CLK PA1, MDIO PA2, MDC PC1, CRS_DV PA7,
|
|
* RXD0 PC4, RXD1 PC5, TXEN PB11, TXD0 PB12, TXD1 PB13
|
|
* LAN8720 PHY 주소 = 0 (TODO(hw) 보드에서 확인).
|
|
* ===========================================================================*/
|
|
#include "lwip/opt.h"
|
|
#include "lwip/timeouts.h"
|
|
#include "lwip/netif.h"
|
|
#include "lwip/netifapi.h" /* netifapi_netif_set_link_up/down: 코어락 직렬화(tcpip 스레드 외부에서 호출) */
|
|
#include "lwip/pbuf.h"
|
|
#include "lwip/stats.h"
|
|
#include "lwip/snmp.h"
|
|
#include "lwip/ethip6.h"
|
|
#include "netif/etharp.h"
|
|
#include "netif/ethernet.h"
|
|
|
|
#include "app_config.h"
|
|
#include "applog.h"
|
|
|
|
#include "stm32f4xx_hal.h"
|
|
|
|
#include "FreeRTOS.h"
|
|
#include "task.h"
|
|
#include "semphr.h"
|
|
|
|
#include <string.h>
|
|
|
|
/* ── 컴파일타임 상수 ────────────────────────────────────────────────────── */
|
|
|
|
/* LAN8720 PHY 주소(RMII 스트랩). 기본 0. TODO(hw): 보드 스트랩 핀으로 확정. */
|
|
#ifndef LAN8720_PHY_ADDRESS
|
|
#define LAN8720_PHY_ADDRESS 0x00U
|
|
#endif
|
|
|
|
/* DMA 디스크립터/버퍼 개수. RAM 절약 + 1 HTTPS 커넥션 처리에 충분.
|
|
* 각 버퍼는 ETH_RX_BUF_SIZE(=ETH_MAX_PACKET_SIZE, 1524) 바이트. */
|
|
#define ETH_RXBUFNB 4U
|
|
#define ETH_TXBUFNB 4U
|
|
|
|
/* 이더넷 프레임 처리 정의가 헤더(stm32f4xx_hal_eth.h)에 없을 수 있어 보강. */
|
|
#ifndef ETH_MAX_PACKET_SIZE
|
|
#define ETH_MAX_PACKET_SIZE 1524U
|
|
#endif
|
|
#ifndef ETH_RX_BUF_SIZE
|
|
#define ETH_RX_BUF_SIZE ETH_MAX_PACKET_SIZE
|
|
#endif
|
|
#ifndef ETH_TX_BUF_SIZE
|
|
#define ETH_TX_BUF_SIZE ETH_MAX_PACKET_SIZE
|
|
#endif
|
|
|
|
/* netif 식별자. */
|
|
#define IFNAME0 'e'
|
|
#define IFNAME1 'n'
|
|
|
|
/* PHY 링크 폴링 주기. */
|
|
#define PHY_LINK_POLL_MS 500U
|
|
|
|
/* ── DMA 디스크립터/버퍼 (4바이트 정렬, 일반 SRAM) ──────────────────────── */
|
|
/* ETH DMA 는 CCM 접근 불가 -> 링커가 .bss(일반 SRAM)에 두도록 한다.
|
|
* 일부 프로젝트는 별도 섹션(.RxDecripSection 등)에 배치하지만, 기본 .bss 면 충분. */
|
|
__attribute__((aligned(4))) static ETH_DMADescTypeDef DMARxDscrTab[ETH_RXBUFNB];
|
|
__attribute__((aligned(4))) static ETH_DMADescTypeDef DMATxDscrTab[ETH_TXBUFNB];
|
|
__attribute__((aligned(4))) static uint8_t Rx_Buff[ETH_RXBUFNB][ETH_RX_BUF_SIZE];
|
|
__attribute__((aligned(4))) static uint8_t Tx_Buff[ETH_TXBUFNB][ETH_TX_BUF_SIZE];
|
|
|
|
/* ── HAL ETH 핸들 + RTOS 동기 객체 ──────────────────────────────────────── */
|
|
|
|
static ETH_HandleTypeDef s_heth;
|
|
static SemaphoreHandle_t s_rx_sem; /* RX IRQ -> input 태스크 통지 */
|
|
static struct netif *s_netif; /* 콜백/태스크에서 참조 */
|
|
|
|
/* ── 전방 선언 ──────────────────────────────────────────────────────────── */
|
|
static void ethernetif_input_task(void *arg);
|
|
static void phy_link_task(void *arg);
|
|
static struct pbuf *low_level_input(struct netif *netif);
|
|
|
|
/* =============================================================================
|
|
* HAL MSP: RMII GPIO / 클럭 / NVIC
|
|
* HAL_ETH_Init() 내부에서 HAL 이 자동 호출한다.
|
|
* ===========================================================================*/
|
|
void HAL_ETH_MspInit(ETH_HandleTypeDef *heth)
|
|
{
|
|
GPIO_InitTypeDef gpio = {0};
|
|
|
|
if (heth->Instance != ETH) {
|
|
return;
|
|
}
|
|
|
|
/* GPIO 포트 클럭. */
|
|
__HAL_RCC_GPIOA_CLK_ENABLE();
|
|
__HAL_RCC_GPIOB_CLK_ENABLE();
|
|
__HAL_RCC_GPIOC_CLK_ENABLE();
|
|
|
|
/* SYSCFG 는 RMII/MII 선택에 필요. ETH 클럭(MAC + Tx + Rx)을 모두 켠다.
|
|
* STM32F4 HAL 의 표준 ETH 클럭 매크로명. */
|
|
__HAL_RCC_SYSCFG_CLK_ENABLE();
|
|
__HAL_RCC_ETHMAC_CLK_ENABLE();
|
|
__HAL_RCC_ETHMACTX_CLK_ENABLE();
|
|
__HAL_RCC_ETHMACRX_CLK_ENABLE();
|
|
|
|
/* RMII 인터페이스 선택(반드시 ETH 클럭 활성화 후, MAC reset 전).
|
|
* HAL_ETH_Init 의 MediaInterface=RMII 가 SYSCFG 를 설정하지만 명시적으로 보장. */
|
|
SYSCFG->PMC |= SYSCFG_PMC_MII_RMII_SEL; /* 1 = RMII */
|
|
|
|
/* 공통 RMII AF 설정 헬퍼. 모든 핀은 AF11_ETH, push-pull, very-high speed. */
|
|
gpio.Mode = GPIO_MODE_AF_PP;
|
|
gpio.Pull = GPIO_NOPULL;
|
|
gpio.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
|
|
gpio.Alternate = GPIO_AF11_ETH;
|
|
|
|
/* PORTA: PA1(REF_CLK), PA2(MDIO), PA7(CRS_DV) */
|
|
gpio.Pin = GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_7;
|
|
HAL_GPIO_Init(GPIOA, &gpio);
|
|
|
|
/* PORTB: PB11(TX_EN), PB12(TXD0), PB13(TXD1) */
|
|
gpio.Pin = GPIO_PIN_11 | GPIO_PIN_12 | GPIO_PIN_13;
|
|
HAL_GPIO_Init(GPIOB, &gpio);
|
|
|
|
/* PORTC: PC1(MDC), PC4(RXD0), PC5(RXD1) */
|
|
gpio.Pin = GPIO_PIN_1 | GPIO_PIN_4 | GPIO_PIN_5;
|
|
HAL_GPIO_Init(GPIOC, &gpio);
|
|
|
|
/* ETH 전역 인터럽트 활성화(RX 완료 시 input 태스크 깨움). */
|
|
HAL_NVIC_SetPriority(ETH_IRQn, 7, 0); /* FreeRTOS 안전 우선순위 범위 내 */
|
|
HAL_NVIC_EnableIRQ(ETH_IRQn);
|
|
}
|
|
|
|
/* =============================================================================
|
|
* ETH 인터럽트: HAL 디스패치 -> RxCpltCallback 에서 세마포어 give
|
|
* ===========================================================================*/
|
|
void ETH_IRQHandler(void)
|
|
{
|
|
HAL_ETH_IRQHandler(&s_heth);
|
|
}
|
|
|
|
void HAL_ETH_RxCpltCallback(ETH_HandleTypeDef *heth)
|
|
{
|
|
(void)heth;
|
|
BaseType_t hp_woken = pdFALSE;
|
|
if (s_rx_sem != NULL) {
|
|
xSemaphoreGiveFromISR(s_rx_sem, &hp_woken);
|
|
}
|
|
portYIELD_FROM_ISR(hp_woken);
|
|
}
|
|
|
|
/* =============================================================================
|
|
* low_level_init: HAL_ETH 초기화 + MAC 주소 + DMA 디스크립터 + 시작
|
|
* ===========================================================================*/
|
|
static void mac_from_uid(uint8_t mac[6])
|
|
{
|
|
/* STM32 96bit 고유 ID 에서 MAC 유도. 로컬 관리(LAA) 비트 set, 멀티캐스트 clear. */
|
|
uint32_t u0 = HAL_GetUIDw0();
|
|
uint32_t u1 = HAL_GetUIDw1();
|
|
uint32_t u2 = HAL_GetUIDw2();
|
|
uint32_t mix = u0 ^ u1 ^ u2;
|
|
|
|
mac[0] = 0x02; /* locally administered, unicast */
|
|
mac[1] = (uint8_t)(u2 & 0xFF);
|
|
mac[2] = (uint8_t)((u1 >> 8) & 0xFF);
|
|
mac[3] = (uint8_t)((mix >> 16) & 0xFF);
|
|
mac[4] = (uint8_t)((mix >> 8) & 0xFF);
|
|
mac[5] = (uint8_t)(mix & 0xFF);
|
|
}
|
|
|
|
static void low_level_init(struct netif *netif)
|
|
{
|
|
static uint8_t mac[6];
|
|
mac_from_uid(mac);
|
|
|
|
s_heth.Instance = ETH;
|
|
s_heth.Init.MACAddr = mac;
|
|
s_heth.Init.AutoNegotiation= ETH_AUTONEGOTIATION_ENABLE;
|
|
s_heth.Init.Speed = ETH_SPEED_100M; /* AutoNeg 시 무시 */
|
|
s_heth.Init.DuplexMode = ETH_MODE_FULLDUPLEX; /* AutoNeg 시 무시 */
|
|
s_heth.Init.MediaInterface = ETH_MEDIA_INTERFACE_RMII;
|
|
s_heth.Init.RxMode = ETH_RXINTERRUPT_MODE; /* 인터럽트 기반 RX */
|
|
s_heth.Init.ChecksumMode = ETH_CHECKSUM_BY_SOFTWARE; /* lwipopts SW 체크섬과 일치 */
|
|
s_heth.Init.PhyAddress = LAN8720_PHY_ADDRESS;
|
|
|
|
if (HAL_ETH_Init(&s_heth) != HAL_OK) {
|
|
/* AutoNeg 실패(케이블 없음 등)도 HAL_TIMEOUT 을 줄 수 있음 -> 계속 진행하고
|
|
* PHY 폴링 태스크가 이후 링크를 처리하게 한다. */
|
|
LOGW("eth: HAL_ETH_Init 경고(링크 없음 가능)");
|
|
}
|
|
|
|
/* MAC 길이/타입. */
|
|
netif->hwaddr_len = ETH_HWADDR_LEN; /* = 6 */
|
|
memcpy(netif->hwaddr, mac, 6);
|
|
netif->mtu = 1500;
|
|
|
|
netif->flags = NETIF_FLAG_BROADCAST | NETIF_FLAG_ETHARP;
|
|
/* 링크 업/다운은 PHY 폴링 태스크가 netif_set_link_up/down 으로 통지하므로
|
|
* 여기서 NETIF_FLAG_LINK_UP 플래그를 미리 세우지 않는다. */
|
|
|
|
/* DMA 디스크립터 체인 초기화(Rx/Tx). */
|
|
HAL_ETH_DMATxDescListInit(&s_heth, DMATxDscrTab, &Tx_Buff[0][0], ETH_TXBUFNB);
|
|
HAL_ETH_DMARxDescListInit(&s_heth, DMARxDscrTab, &Rx_Buff[0][0], ETH_RXBUFNB);
|
|
|
|
/* MAC/DMA 시작(Tx/Rx enable). */
|
|
if (HAL_ETH_Start(&s_heth) != HAL_OK) {
|
|
LOGE("eth: HAL_ETH_Start 실패");
|
|
}
|
|
|
|
LOGI("eth: MAC=%02X:%02X:%02X:%02X:%02X:%02X",
|
|
mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
|
|
}
|
|
|
|
/* =============================================================================
|
|
* low_level_output: pbuf 체인 -> HAL_ETH_Transmit
|
|
* ===========================================================================*/
|
|
static err_t low_level_output(struct netif *netif, struct pbuf *p)
|
|
{
|
|
(void)netif;
|
|
|
|
/* 현재 Tx 디스크립터의 버퍼로 pbuf 체인을 선형 복사.
|
|
* (lwipopts: LWIP_NETIF_TX_SINGLE_PBUF=1 이라 보통 단일 조각이지만 안전하게 순회) */
|
|
uint8_t *buf = (uint8_t *)(s_heth.TxDesc->Buffer1Addr);
|
|
if (buf == NULL) {
|
|
return ERR_BUF;
|
|
}
|
|
|
|
uint32_t framelen = 0;
|
|
for (struct pbuf *q = p; q != NULL; q = q->next) {
|
|
if (framelen + q->len > ETH_TX_BUF_SIZE) {
|
|
LOGW("eth: TX 프레임 과대(%lu)", (unsigned long)(framelen + q->len));
|
|
return ERR_BUF;
|
|
}
|
|
memcpy(&buf[framelen], q->payload, q->len);
|
|
framelen += q->len;
|
|
}
|
|
|
|
if (HAL_ETH_TransmitFrame(&s_heth, framelen) != HAL_OK) {
|
|
/* 디스크립터가 CPU 소유가 아니면(Tx underflow) 복구: 디스크립터 리셋. */
|
|
if ((s_heth.DMASR & ETH_DMASR_TUS) != (uint32_t)RESET) {
|
|
s_heth.Instance->DMASR = ETH_DMASR_TUS;
|
|
s_heth.Instance->DMATPDR = 0; /* Transmit Poll Demand 로 재개 */
|
|
}
|
|
LINK_STATS_INC(link.drop);
|
|
return ERR_IF;
|
|
}
|
|
|
|
LINK_STATS_INC(link.xmit);
|
|
return ERR_OK;
|
|
}
|
|
|
|
/* =============================================================================
|
|
* low_level_input: HAL_ETH RX 디스크립터 -> 새 pbuf
|
|
* ===========================================================================*/
|
|
static struct pbuf *low_level_input(struct netif *netif)
|
|
{
|
|
(void)netif;
|
|
struct pbuf *p = NULL;
|
|
|
|
/* 수신 프레임 1개 가져오기. 없으면 NULL. */
|
|
if (HAL_ETH_GetReceivedFrame_IT(&s_heth) != HAL_OK) {
|
|
return NULL;
|
|
}
|
|
|
|
uint32_t len = s_heth.RxFrameInfos.length;
|
|
uint8_t *src = (uint8_t *)s_heth.RxFrameInfos.buffer;
|
|
|
|
if (len > 0 && len <= ETH_RX_BUF_SIZE) {
|
|
p = pbuf_alloc(PBUF_RAW, (u16_t)len, PBUF_POOL);
|
|
if (p != NULL) {
|
|
/* pbuf 체인 각 조각으로 복사(POOL 버퍼가 분할될 수 있음). */
|
|
uint32_t copied = 0;
|
|
for (struct pbuf *q = p; q != NULL && copied < len; q = q->next) {
|
|
uint32_t n = q->len;
|
|
if (copied + n > len) n = len - copied;
|
|
memcpy(q->payload, &src[copied], n);
|
|
copied += n;
|
|
}
|
|
LINK_STATS_INC(link.recv);
|
|
} else {
|
|
LINK_STATS_INC(link.memerr);
|
|
LINK_STATS_INC(link.drop);
|
|
}
|
|
} else {
|
|
LINK_STATS_INC(link.lenerr);
|
|
LINK_STATS_INC(link.drop);
|
|
}
|
|
|
|
/* DMA 디스크립터를 다시 DMA 소유로 반환(여러 디스크립터에 걸친 프레임 포함). */
|
|
{
|
|
__IO ETH_DMADescTypeDef *dmarxdesc = s_heth.RxFrameInfos.FSRxDesc;
|
|
for (uint32_t i = 0; i < s_heth.RxFrameInfos.SegCount; i++) {
|
|
dmarxdesc->Status |= ETH_DMARXDESC_OWN;
|
|
dmarxdesc = (ETH_DMADescTypeDef *)(dmarxdesc->Buffer2NextDescAddr);
|
|
}
|
|
s_heth.RxFrameInfos.SegCount = 0;
|
|
}
|
|
|
|
/* RX 버퍼 언어베일러블(RBUS) 비트가 섰으면 RX 재개. */
|
|
if ((s_heth.Instance->DMASR & ETH_DMASR_RBUS) != (uint32_t)RESET) {
|
|
s_heth.Instance->DMASR = ETH_DMASR_RBUS;
|
|
s_heth.Instance->DMARPDR = 0; /* Receive Poll Demand */
|
|
}
|
|
|
|
return p;
|
|
}
|
|
|
|
/* =============================================================================
|
|
* ethernetif_input_task: RX 세마포어 대기 -> 가용 프레임 모두 input 으로 전달
|
|
* ===========================================================================*/
|
|
static void ethernetif_input_task(void *arg)
|
|
{
|
|
struct netif *netif = (struct netif *)arg;
|
|
|
|
for (;;) {
|
|
/* RX IRQ 통지 대기. 타임아웃을 둬서 IRQ 누락 시에도 주기적으로
|
|
* 디스크립터를 비워(폴링) 록업을 방지한다. 반환값과 무관하게 매 주기
|
|
* 가용 프레임을 모두 처리한다. */
|
|
(void)xSemaphoreTake(s_rx_sem, pdMS_TO_TICKS(100));
|
|
|
|
struct pbuf *p;
|
|
while ((p = low_level_input(netif)) != NULL) {
|
|
/* netif->input 은 netif_add 등록상 tcpip_input(=ethernet_input 경유). */
|
|
if (netif->input(p, netif) != ERR_OK) {
|
|
pbuf_free(p);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* =============================================================================
|
|
* phy_link_task: LAN8720 BSR 폴링 -> 링크 업/다운 + 속도/듀플렉스 반영
|
|
* ===========================================================================*/
|
|
static void phy_link_task(void *arg)
|
|
{
|
|
struct netif *netif = (struct netif *)arg;
|
|
int prev_up = -1;
|
|
|
|
for (;;) {
|
|
uint32_t bsr = 0;
|
|
int link_up = 0;
|
|
|
|
if (HAL_ETH_ReadPHYRegister(&s_heth, PHY_BSR, &bsr) == HAL_OK) {
|
|
link_up = (bsr & PHY_LINKED_STATUS) ? 1 : 0;
|
|
}
|
|
|
|
if (link_up != prev_up) {
|
|
if (link_up) {
|
|
/* 자동협상 결과로 MAC 속도/듀플렉스 갱신 후 ETH 재시작. */
|
|
uint32_t sr = 0;
|
|
/* LAN8720 special control/status 레지스터(0x1F)에서 속도 추출.
|
|
* 비표준 매크로 회피 위해 BCR/BSR 기반 보수적 처리. */
|
|
if (HAL_ETH_ReadPHYRegister(&s_heth, PHY_SR, &sr) == HAL_OK) {
|
|
/* LAN8720 PHY_SR(0x1F): bit4=duplex(1=full), bit2..3=speed
|
|
* (10=10M,100=100M). 보드 PHY 별 상이 가능 -> TODO(hw) 확인. */
|
|
uint32_t speed = ((sr >> 2) & 0x03); /* 01=10,10=100 (LAN8720) */
|
|
uint32_t duplex = (sr >> 4) & 0x01;
|
|
|
|
s_heth.Init.DuplexMode = duplex ? ETH_MODE_FULLDUPLEX
|
|
: ETH_MODE_HALFDUPLEX;
|
|
s_heth.Init.Speed = (speed == 0x02) ? ETH_SPEED_100M
|
|
: ETH_SPEED_10M;
|
|
}
|
|
HAL_ETH_ConfigMAC(&s_heth, NULL);
|
|
HAL_ETH_Start(&s_heth);
|
|
|
|
/* LWIP_TCPIP_CORE_LOCKING=1: 이 태스크는 tcpip 스레드가 아니므로
|
|
* raw netif_set_link_up 을 직접 부르면 코어 상태 레이스가 난다.
|
|
* netifapi_* 로 호출하면 tcpip 스레드 문맥(코어락 보유)에서 실행되어
|
|
* 안전하다. (링크 콜백 net_link_cb 도 그 문맥에서 raw netif_set_up
|
|
* 을 호출하므로 중첩 락 데드락이 없다.) */
|
|
netifapi_netif_set_link_up(netif);
|
|
LOGI("eth: PHY 링크 업 (speed=%s)",
|
|
(s_heth.Init.Speed == ETH_SPEED_100M) ? "100M" : "10M");
|
|
} else {
|
|
HAL_ETH_Stop(&s_heth);
|
|
netifapi_netif_set_link_down(netif);
|
|
LOGW("eth: PHY 링크 다운");
|
|
}
|
|
prev_up = link_up;
|
|
}
|
|
|
|
vTaskDelay(pdMS_TO_TICKS(PHY_LINK_POLL_MS));
|
|
}
|
|
}
|
|
|
|
/* =============================================================================
|
|
* ethernetif_init: LwIP netif_add 콜백 (net.c 가 등록)
|
|
* ===========================================================================*/
|
|
err_t ethernetif_init(struct netif *netif)
|
|
{
|
|
LWIP_ASSERT("netif != NULL", (netif != NULL));
|
|
|
|
s_netif = netif;
|
|
|
|
#if LWIP_NETIF_HOSTNAME
|
|
netif->hostname = BOARD_DEVICE_ID;
|
|
#endif
|
|
|
|
netif->name[0] = IFNAME0;
|
|
netif->name[1] = IFNAME1;
|
|
|
|
/* IPv4 출력 = etharp_output, 링크 출력 = low_level_output.
|
|
* netif->input 은 netif_add 의 마지막 인자(tcpip_input)로 설정됨. */
|
|
netif->output = etharp_output;
|
|
netif->linkoutput = low_level_output;
|
|
|
|
MIB2_INIT_NETIF(netif, snmp_ifType_ethernet_csmacd, 100000000);
|
|
|
|
/* RX 통지 세마포어. */
|
|
s_rx_sem = xSemaphoreCreateBinary();
|
|
if (s_rx_sem == NULL) {
|
|
LOGE("eth: RX 세마포어 생성 실패");
|
|
return ERR_MEM;
|
|
}
|
|
|
|
/* HW 초기화(MAC/PHY/DMA/시작). */
|
|
low_level_init(netif);
|
|
|
|
/* RX 처리 태스크 + PHY 링크 폴링 태스크 기동.
|
|
* 우선순위/스택은 lwipopts.h 의 ETHIF_RX_* 사용. */
|
|
if (xTaskCreate(ethernetif_input_task, ETHIF_RX_THREAD_NAME,
|
|
ETHIF_RX_THREAD_STACKSIZE, netif,
|
|
ETHIF_RX_THREAD_PRIO, NULL) != pdPASS) {
|
|
LOGE("eth: RX 태스크 생성 실패");
|
|
return ERR_MEM;
|
|
}
|
|
if (xTaskCreate(phy_link_task, "eth_phy",
|
|
256, netif,
|
|
DEFAULT_THREAD_PRIO, NULL) != pdPASS) {
|
|
LOGE("eth: PHY 태스크 생성 실패");
|
|
return ERR_MEM;
|
|
}
|
|
|
|
return ERR_OK;
|
|
}
|