/* =============================================================================
 * 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;
}
