/**
 * @file ethernetif.c
 * @brief Ethernet interface implementation for CH390 + LwIP + FreeRTOS
 */

#include "lwip/opt.h"
#include "lwip/def.h"
#include "lwip/mem.h"
#include "lwip/pbuf.h"
#include "lwip/stats.h"
#include "lwip/snmp.h"
#include "lwip/etharp.h"
#include "lwip/netifapi.h"
#include "lwip/tcpip.h"
#include "lwip/ip4_addr.h"
#include "netif/ethernet.h"
#include "ethernetif.h"

#include "CH390.h"
#include "CH390_Interface.h"

#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h"

#include "app_runtime.h"
#include "debug_log.h"
#include "config.h"

#include <string.h>

/* Interface name */
#define IFNAME0 'e'
#define IFNAME1 'n'

/* Global network interface */
struct netif ch390_netif;

/* Mutex for SPI access protection */
static SemaphoreHandle_t spi_mutex = NULL;
static uint8_t s_rx_buffer[CH390_PKT_MAX];
static uint8_t s_tx_buffer[CH390_PKT_MAX];
static uint8_t s_garp_sent = 0u;
static uint8_t s_effective_mac[ETHARP_HWADDR_LEN];
static uint8_t s_effective_mac_valid = 0u;

#define CH390_TX_TIMEOUT_RESTART_THRESHOLD  6u
#define CH390_EXPECTED_VENDOR_ID            0x1C00u
#define CH390_EXPECTED_PRODUCT_ID           0x9151u
#define ETH_RX_LOG_INITIAL_COUNT            3u
#define ETH_RX_LOG_EVERY_COUNT              64u

#define ETH_RECOVERY_NONE 0u
#define ETH_RECOVERY_RX   1u
#define ETH_RECOVERY_FULL 2u

struct ethernetif_recovery_state
{
    uint8_t pending;
    uint8_t in_progress;
    uint8_t tx_timeout_streak;
    uint32_t tx_timeout_count;
    uint32_t rx_overflow_count;
    uint32_t rx_bad_count;
    uint32_t rx_recovery_count;
    uint32_t full_recovery_count;
};

static struct ethernetif_recovery_state s_recovery = {0};

/* Forward declarations */
static err_t low_level_init(struct netif *netif);
static err_t low_level_output(struct netif *netif, struct pbuf *p);
static struct pbuf *low_level_input(struct netif *netif);
static void ethernetif_update_link(uint8_t link_status);
static uint32_t ethernetif_record_counter(uint32_t *counter);
static uint32_t ethernetif_record_event_and_request(uint32_t *counter, uint8_t recovery_type);
static uint8_t ethernetif_claim_recovery(void);
static void ethernetif_finish_recovery(void);
static uint8_t ethernetif_run_pending_recovery(void);
static uint8_t ethernetif_should_log_counter(uint32_t count);
static void ethernetif_clear_tx_timeout_streak(void);
static uint32_t ethernetif_note_tx_timeout_streak(void);
static uint8_t ethernetif_mac_is_all_zero(const uint8_t *mac);
static void ethernetif_store_effective_mac(const uint8_t *mac);
static void ethernetif_apply_configured_or_internal_mac_locked(const uint8_t *configured_mac);
static void ethernetif_prepare_runtime_netif_locked(struct netif *netif);
static void ethernetif_apply_runtime_netif_config_locked(const ip4_addr_t *ipaddr,
                                                         const ip4_addr_t *netmask,
                                                         const ip4_addr_t *gw,
                                                         const uint8_t *mac);
static void ethernetif_perform_full_recovery(const ip4_addr_t *ipaddr,
                                             const ip4_addr_t *netmask,
                                             const ip4_addr_t *gw,
                                             const uint8_t *mac,
                                             uint8_t *link_status);

/*---------------------------------------------------------------------------
 * Low Level Hardware Functions
 *---------------------------------------------------------------------------*/

/**
 * @brief Initialize the CH390 hardware
 * @param netif Network interface structure
 */
static err_t low_level_init(struct netif *netif)
{
    const device_config_t *cfg = config_get();
    
    /* Create SPI mutex */
    if (spi_mutex == NULL)
    {
        spi_mutex = xSemaphoreCreateMutex();
        if (spi_mutex == NULL)
        {
            return ERR_MEM;
        }
    }
    
    /* Initialize CH390 GPIO and SPI */
    ch390_gpio_init();
    ch390_spi_init();
    
    /* Hardware reset CH390 */
    ch390_hardware_reset();
    
    /* Configure CH390 with default settings */
    ch390_default_config();
    debug_log_printf("[ETH] cfg imr=0x%02X intcr=0x%02X rcr=0x%02X\r\n",
                     ch390_read_reg(CH390_IMR),
                     ch390_read_reg(CH390_INTCR),
                     ch390_read_reg(CH390_RCR));
    debug_log_printf("[ETH] bc bcastcr=0x%02X mar7=0x%02X\r\n",
                     ch390_read_reg(CH390_BCASTCR),
                     ch390_read_reg(CH390_MAR + 7));

    ethernetif_apply_configured_or_internal_mac_locked(cfg->net.mac);
    
    ethernetif_prepare_runtime_netif_locked(netif);
    
    /* Enable CH390 interrupt */
    ch390_interrupt_init();

    return ERR_OK;
}

static uint32_t ethernetif_record_counter(uint32_t *counter)
{
    uint32_t value;

    taskENTER_CRITICAL();
    ++(*counter);
    value = *counter;
    taskEXIT_CRITICAL();

    return value;
}

static uint32_t ethernetif_record_event_and_request(uint32_t *counter, uint8_t recovery_type)
{
    uint32_t value;

    taskENTER_CRITICAL();
    ++(*counter);
    if (recovery_type == ETH_RECOVERY_FULL)
    {
        app_request_network_restart();
    }
    else if (recovery_type > s_recovery.pending)
    {
        s_recovery.pending = recovery_type;
    }
    value = *counter;
    taskEXIT_CRITICAL();

    return value;
}

static void ethernetif_clear_tx_timeout_streak(void)
{
    taskENTER_CRITICAL();
    s_recovery.tx_timeout_streak = 0u;
    taskEXIT_CRITICAL();
}

static uint32_t ethernetif_note_tx_timeout_streak(void)
{
    uint32_t streak;

    taskENTER_CRITICAL();
    ++s_recovery.tx_timeout_count;
    if (s_recovery.tx_timeout_streak < 0xFFu)
    {
        ++s_recovery.tx_timeout_streak;
    }
    streak = s_recovery.tx_timeout_streak;
    taskEXIT_CRITICAL();

    return streak;
}

static uint8_t ethernetif_claim_recovery(void)
{
    uint8_t recovery_type = ETH_RECOVERY_NONE;

    taskENTER_CRITICAL();
    if ((s_recovery.in_progress == 0u) && (s_recovery.pending != ETH_RECOVERY_NONE))
    {
        recovery_type = s_recovery.pending;
        s_recovery.pending = ETH_RECOVERY_NONE;
        s_recovery.in_progress = 1u;
    }
    taskEXIT_CRITICAL();

    return recovery_type;
}

static void ethernetif_finish_recovery(void)
{
    taskENTER_CRITICAL();
    s_recovery.in_progress = 0u;
    taskEXIT_CRITICAL();
}

static uint8_t ethernetif_should_log_counter(uint32_t count)
{
    if (count <= ETH_RX_LOG_INITIAL_COUNT)
    {
        return 1u;
    }

    return ((count % ETH_RX_LOG_EVERY_COUNT) == 0u) ? 1u : 0u;
}

static uint8_t ethernetif_run_pending_recovery(void)
{
    ip4_addr_t ipaddr;
    ip4_addr_t netmask;
    ip4_addr_t gateway;
    uint8_t recovery_type;
    uint8_t link_status = 0u;
    uint32_t count;

    recovery_type = ethernetif_claim_recovery();
    if (recovery_type == ETH_RECOVERY_NONE)
    {
        return ETH_RECOVERY_NONE;
    }

    if (recovery_type == ETH_RECOVERY_FULL)
    {
        const device_config_t *cfg = config_get();

        IP4_ADDR(&ipaddr, cfg->net.ip[0], cfg->net.ip[1], cfg->net.ip[2], cfg->net.ip[3]);
        IP4_ADDR(&netmask, cfg->net.mask[0], cfg->net.mask[1], cfg->net.mask[2], cfg->net.mask[3]);
        IP4_ADDR(&gateway, cfg->net.gw[0], cfg->net.gw[1], cfg->net.gw[2], cfg->net.gw[3]);
        ethernetif_clear_tx_timeout_streak();
        ethernetif_perform_full_recovery(&ipaddr, &netmask, &gateway, cfg->net.mac, &link_status);
    }
    else
    {
        if (spi_mutex != NULL)
        {
            xSemaphoreTake(spi_mutex, portMAX_DELAY);
        }
        ch390_rx_reset();
        if (spi_mutex != NULL)
        {
            xSemaphoreGive(spi_mutex);
        }
    }

    if (recovery_type == ETH_RECOVERY_FULL)
    {
        count = ethernetif_record_counter(&s_recovery.full_recovery_count);
        debug_log_printf("[ETH] rec full n=%lu link=%u\r\n",
                         (unsigned long)count,
                         (unsigned int)link_status);
    }
    else
    {
        count = ethernetif_record_counter(&s_recovery.rx_recovery_count);
        if (ethernetif_should_log_counter(count) != 0u)
        {
            debug_log_printf("[ETH] rec rx n=%lu\r\n", (unsigned long)count);
        }
    }

    ethernetif_finish_recovery();
    return recovery_type;
}

static void ethernetif_perform_full_recovery(const ip4_addr_t *ipaddr,
                                              const ip4_addr_t *netmask,
                                              const ip4_addr_t *gw,
                                              const uint8_t *mac,
                                              uint8_t *link_status)
{
    uint8_t local_link_status = 0u;

    if (spi_mutex != NULL)
    {
        xSemaphoreTake(spi_mutex, portMAX_DELAY);
    }

    ch390_spi_init();
    ch390_hardware_reset();
    ch390_default_config();
    ethernetif_apply_configured_or_internal_mac_locked(mac);
    ch390_set_phy_mode(CH390_AUTO);
    ch390_rx_enable(1);
    ethernetif_prepare_runtime_netif_locked(&ch390_netif);
    ethernetif_apply_runtime_netif_config_locked(ipaddr,
                                                 netmask,
                                                 gw,
                                                 (s_effective_mac_valid != 0u) ? s_effective_mac : NULL);
    local_link_status = (uint8_t)ch390_get_link_status();

    if (spi_mutex != NULL)
    {
        xSemaphoreGive(spi_mutex);
    }

    ch390_interrupt_init();

    if (link_status != NULL)
    {
        *link_status = local_link_status;
    }

    ethernetif_update_link(local_link_status);
}

static uint8_t ethernetif_mac_is_all_zero(const uint8_t *mac)
{
    uint32_t i;

    if (mac == NULL)
    {
        return 1u;
    }

    for (i = 0u; i < ETHARP_HWADDR_LEN; ++i)
    {
        if (mac[i] != 0u)
        {
            return 0u;
        }
    }

    return 1u;
}

static void ethernetif_store_effective_mac(const uint8_t *mac)
{
    if (mac == NULL)
    {
        s_effective_mac_valid = 0u;
        return;
    }

    MEMCPY(s_effective_mac, mac, ETHARP_HWADDR_LEN);
    s_effective_mac_valid = 1u;
}

static void ethernetif_apply_configured_or_internal_mac_locked(const uint8_t *configured_mac)
{
    uint8_t runtime_mac[ETHARP_HWADDR_LEN];

    if (ethernetif_mac_is_all_zero(configured_mac) == 0u)
    {
        ch390_set_mac_address((uint8_t *)configured_mac);
        ethernetif_store_effective_mac(configured_mac);
        return;
    }

    ch390_get_mac(runtime_mac);
    ethernetif_store_effective_mac(runtime_mac);
    debug_log_printf("[ETH] use internal mac=%02X:%02X:%02X:%02X:%02X:%02X\r\n",
                     (unsigned int)runtime_mac[0],
                     (unsigned int)runtime_mac[1],
                     (unsigned int)runtime_mac[2],
                     (unsigned int)runtime_mac[3],
                     (unsigned int)runtime_mac[4],
                     (unsigned int)runtime_mac[5]);
}

static void ethernetif_prepare_runtime_netif_locked(struct netif *netif)
{
    struct ethernetif *ethernetif;

    if (netif == NULL)
    {
        return;
    }

    netif->hwaddr_len = ETHARP_HWADDR_LEN;
    ch390_get_mac(netif->hwaddr);
    ethernetif_store_effective_mac(netif->hwaddr);
    netif->mtu = 1500;
    netif->flags = NETIF_FLAG_BROADCAST | NETIF_FLAG_ETHARP | NETIF_FLAG_ETHERNET;

    ethernetif = (struct ethernetif *)netif->state;
    if (ethernetif != NULL)
    {
        ethernetif->rx_len = 0u;
        ethernetif->rx_status = 0u;
    }
}

static void ethernetif_apply_runtime_netif_config_locked(const ip4_addr_t *ipaddr,
                                                         const ip4_addr_t *netmask,
                                                         const ip4_addr_t *gw,
                                                         const uint8_t *mac)
{
    err_t err;

    if (mac != NULL)
    {
        MEMCPY(ch390_netif.hwaddr, mac, ETHARP_HWADDR_LEN);
        s_garp_sent = 0u;
    }

    if (ipaddr != NULL && netmask != NULL && gw != NULL)
    {
        err = netifapi_netif_set_addr(&ch390_netif, ipaddr, netmask, gw);
        if (err != ERR_OK)
        {
            debug_log_printf("[ETH] set-addr fail err=%d\r\n", (int)err);
        }
    }

    err = netifapi_netif_set_default(&ch390_netif);
    if (err != ERR_OK)
    {
        debug_log_printf("[ETH] resync-default fail err=%d\r\n", (int)err);
    }

    err = netifapi_netif_set_up(&ch390_netif);
    if (err != ERR_OK)
    {
        debug_log_printf("[ETH] resync-up fail err=%d\r\n", (int)err);
    }
}

void ethernetif_apply_runtime_netif_config(const ip4_addr_t *ipaddr,
                                           const ip4_addr_t *netmask,
                                           const ip4_addr_t *gw,
                                           const uint8_t *mac)
{
    ethernetif_apply_runtime_netif_config_locked(ipaddr, netmask, gw, mac);
}

void ethernetif_force_full_recovery(const ip4_addr_t *ipaddr,
                                    const ip4_addr_t *netmask,
                                    const ip4_addr_t *gw,
                                    const uint8_t *mac)
{
    uint8_t link_status = 0u;
    uint32_t count;

    ethernetif_clear_tx_timeout_streak();
    ethernetif_perform_full_recovery(ipaddr, netmask, gw, mac, &link_status);
    count = ethernetif_record_counter(&s_recovery.full_recovery_count);
    debug_log_printf("[ETH] rec full n=%lu link=%u\r\n",
                     (unsigned long)count,
                     (unsigned int)link_status);
}

void ethernetif_force_link_down(void)
{
    ethernetif_update_link(0u);
}

uint8_t ethernetif_ch390_health_ok(void)
{
    return ((ethernetif_ch390_get_vendor_id() == CH390_EXPECTED_VENDOR_ID) &&
            (ethernetif_ch390_get_product_id() == CH390_EXPECTED_PRODUCT_ID)) ? 1u : 0u;
}

uint8_t ethernetif_get_effective_mac(uint8_t *mac)
{
    if ((mac == NULL) || (s_effective_mac_valid == 0u))
    {
        return 0u;
    }

    MEMCPY(mac, s_effective_mac, ETHARP_HWADDR_LEN);
    return 1u;
}

uint16_t ethernetif_ch390_get_vendor_id(void)
{
    uint16_t vendor_id;

    if (spi_mutex != NULL)
    {
        xSemaphoreTake(spi_mutex, portMAX_DELAY);
    }

    vendor_id = ch390_get_vendor_id();

    if (spi_mutex != NULL)
    {
        xSemaphoreGive(spi_mutex);
    }

    return vendor_id;
}

uint16_t ethernetif_ch390_get_product_id(void)
{
    uint16_t product_id;

    if (spi_mutex != NULL)
    {
        xSemaphoreTake(spi_mutex, portMAX_DELAY);
    }

    product_id = ch390_get_product_id();

    if (spi_mutex != NULL)
    {
        xSemaphoreGive(spi_mutex);
    }

    return product_id;
}

uint8_t ethernetif_ch390_get_revision(void)
{
    uint8_t revision;

    if (spi_mutex != NULL)
    {
        xSemaphoreTake(spi_mutex, portMAX_DELAY);
    }

    revision = ch390_get_revision();

    if (spi_mutex != NULL)
    {
        xSemaphoreGive(spi_mutex);
    }

    return revision;
}

/**
 * @brief Transmit a packet via CH390
 * @param netif Network interface structure
 * @param p Packet buffer to transmit
 * @return ERR_OK on success
 */
static err_t low_level_output(struct netif *netif, struct pbuf *p)
{
    struct pbuf *q;
    uint16_t offset;
    uint16_t tx_len;
    int tx_rc;
    uint32_t tx_timeout_streak;
    (void)netif;
    
    /* Take SPI mutex */
    if (spi_mutex != NULL)
    {
        xSemaphoreTake(spi_mutex, portMAX_DELAY);
    }
    
#if ETH_PAD_SIZE
    pbuf_remove_header(p, ETH_PAD_SIZE);
#endif
    
    offset = 0u;

    /* Copy data to contiguous staging buffer */
    for (q = p; q != NULL; q = q->next)
    {
        if ((uint32_t)offset + (uint32_t)q->len > (uint32_t)sizeof(s_tx_buffer))
        {
            if (spi_mutex != NULL)
            {
                xSemaphoreGive(spi_mutex);
            }

#if ETH_PAD_SIZE
            pbuf_add_header(p, ETH_PAD_SIZE);
#endif

            LINK_STATS_INC(link.drop);
            return ERR_BUF;
        }

        MEMCPY(&s_tx_buffer[offset], q->payload, q->len);
        offset = (uint16_t)(offset + q->len);
    }

    tx_len = p->tot_len;

    tx_rc = ch390_runtime_send_packet(s_tx_buffer, tx_len);
    
    /* Release SPI mutex */
    if (spi_mutex != NULL)
    {
        xSemaphoreGive(spi_mutex);
    }
    
#if ETH_PAD_SIZE
    pbuf_add_header(p, ETH_PAD_SIZE);
#endif

    if (tx_rc != 0)
    {
        LINK_STATS_INC(link.drop);
        LINK_STATS_INC(link.err);
        tx_timeout_streak = ethernetif_note_tx_timeout_streak();
        debug_log_printf("[ETH] tx timeout n=%lu streak=%lu\r\n",
                         (unsigned long)s_recovery.tx_timeout_count,
                         (unsigned long)tx_timeout_streak);
        if (tx_timeout_streak >= CH390_TX_TIMEOUT_RESTART_THRESHOLD)
        {
            app_request_network_restart();
        }
        return ERR_TIMEOUT;
    }

    ethernetif_clear_tx_timeout_streak();
    LINK_STATS_INC(link.xmit);
    
    return ERR_OK;
}

/**
 * @brief Receive a packet from CH390
 * @param netif Network interface structure
 * @return Packet buffer containing received data, or NULL if no packet
 */
static struct pbuf *low_level_input(struct netif *netif)
{
    struct ethernetif *ethernetif = netif->state;
    struct pbuf *p = NULL;
    struct pbuf *q;
    uint16_t offset;
    uint16_t len;
    uint32_t rx_len;
    uint8_t rx_status;
    
    /* Take SPI mutex */
    if (spi_mutex != NULL)
    {
        xSemaphoreTake(spi_mutex, portMAX_DELAY);
    }
    
    rx_len = ch390_runtime_receive_packet(s_rx_buffer, &rx_status);
    ethernetif->rx_status = rx_status;
    ethernetif->rx_len = (uint16_t)rx_len;

    if (rx_len == 0u)
    {
        if (spi_mutex != NULL)
        {
            xSemaphoreGive(spi_mutex);
        }
        return NULL;
    }

    len = (uint16_t)rx_len;

#if ETH_PAD_SIZE
    len += ETH_PAD_SIZE;
#endif
    
    /* Allocate pbuf chain */
    p = pbuf_alloc(PBUF_RAW, len, PBUF_POOL);
    
    if (p != NULL)
    {
        offset = 0u;
#if ETH_PAD_SIZE
        pbuf_remove_header(p, ETH_PAD_SIZE);
#endif
        
        /* Copy packet data into pbuf chain */
        for (q = p; q != NULL; q = q->next)
        {
            MEMCPY(q->payload, &s_rx_buffer[offset], q->len);
            offset = (uint16_t)(offset + q->len);
        }

        if ((ethernetif->rx_status & 0x3Fu) != 0u)
        {
            pbuf_free(p);
            p = NULL;
            LINK_STATS_INC(link.drop);
        }
        else
        {
            LINK_STATS_INC(link.recv);
        }
        
#if ETH_PAD_SIZE
        if (p != NULL)
        {
            pbuf_add_header(p, ETH_PAD_SIZE);
        }
#endif
    }
    else
    {
        /* No memory - drop packet */
        LINK_STATS_INC(link.memerr);
        LINK_STATS_INC(link.drop);
    }
    
    /* Release SPI mutex */
    if (spi_mutex != NULL)
    {
        xSemaphoreGive(spi_mutex);
    }
    
    return p;
}

/*---------------------------------------------------------------------------
 * Public Interface Functions
 *---------------------------------------------------------------------------*/

/**
 * @brief Process received ethernet packets
 * @param netif Network interface structure
 */
void ethernetif_input(struct netif *netif)
{
    struct pbuf *p;
    err_t input_err;
    
    /* Get received packet */
    p = low_level_input(netif);
    
    if (p != NULL)
    {
        /* Pass to LwIP stack */
        input_err = netif->input(p, netif);
        if (input_err != ERR_OK)
        {
            LWIP_DEBUGF(NETIF_DEBUG, ("ethernetif_input: IP input error\n"));
            pbuf_free(p);
        }
    }
}

/**
 * @brief Initialize the ethernet interface
 * @param netif Network interface structure
 * @return ERR_OK on success
 */
err_t ethernetif_init(struct netif *netif)
{
    struct ethernetif *ethernetif;
    
    LWIP_ASSERT("netif != NULL", (netif != NULL));
    
    /* Allocate ethernetif state structure */
    ethernetif = mem_malloc(sizeof(struct ethernetif));
    if (ethernetif == NULL)
    {
        LWIP_DEBUGF(NETIF_DEBUG, ("ethernetif_init: out of memory\n"));
        return ERR_MEM;
    }
    
    memset(ethernetif, 0, sizeof(struct ethernetif));
    
#if LWIP_NETIF_HOSTNAME
    netif->hostname = "tcp2uart";
#endif
    
    /* Initialize SNMP variables */
    MIB2_INIT_NETIF(netif, snmp_ifType_ethernet_csmacd, 100000000);
    
    netif->state = ethernetif;
    netif->name[0] = IFNAME0;
    netif->name[1] = IFNAME1;
    
    /* Set output functions */
#if LWIP_IPV4
    netif->output = etharp_output;
#endif
    netif->linkoutput = low_level_output;
    
    /* Initialize hardware */
    if (low_level_init(netif) != ERR_OK)
    {
        mem_free(ethernetif);
        netif->state = NULL;
        return ERR_MEM;
    }
    
    return ERR_OK;
}

/**
 * @brief Initialize LwIP network interface
 * @param ipaddr IP address
 * @param netmask Network mask
 * @param gw Gateway address
 */
void lwip_netif_init(const ip4_addr_t *ipaddr, const ip4_addr_t *netmask, const ip4_addr_t *gw)
{
    err_t err;

    g_netif_init_ok = 0;
    g_netif_add_err = 0x7FFFFFFF;
    g_netif_set_default_err = 0x7FFFFFFF;
    g_netif_set_link_down_err = 0x7FFFFFFF;
    g_netif_set_up_err = 0x7FFFFFFF;

    g_netif_phase = 2u;
    /* Add network interface */
    g_netif_phase = 3u;
    err = netifapi_netif_add(&ch390_netif, ipaddr, netmask, gw, NULL,
                             &ethernetif_init, &tcpip_input);
    g_netif_add_err = (int32_t)err;
    if (err != ERR_OK) {
        g_netif_init_ok = -1;
        debug_log_printf("[ETH] netif-add fail err=%d\r\n", (int)err);
        return;
    }
    g_netif_phase = 4u;
    
    /* Set as default interface */
    err = netifapi_netif_set_default(&ch390_netif);
    g_netif_set_default_err = (int32_t)err;
    if (err != ERR_OK) {
        g_netif_init_ok = -1;
        debug_log_printf("[ETH] set-default fail err=%d\r\n", (int)err);
        return;
    }
    
    /* Set interface down initially */
    err = netifapi_netif_set_link_down(&ch390_netif);
    g_netif_set_link_down_err = (int32_t)err;
    if (err != ERR_OK) {
        g_netif_init_ok = -1;
        debug_log_printf("[ETH] set-link-down fail err=%d\r\n", (int)err);
        return;
    }
    err = netifapi_netif_set_up(&ch390_netif);
    g_netif_set_up_err = (int32_t)err;
    if (err != ERR_OK) {
        g_netif_init_ok = -1;
        debug_log_printf("[ETH] set-up fail err=%d\r\n", (int)err);
        return;
    }
    g_netif_phase = 5u;
    g_netif_phase = 6u;
    g_netif_init_ok = 1;
}

void ethernetif_diag_ch390_init(void)
{
    const device_config_t *cfg = config_get();

    if (spi_mutex == NULL)
    {
        spi_mutex = xSemaphoreCreateMutex();
    }

    ch390_gpio_init();
    ch390_spi_init();
    ch390_hardware_reset();
    ch390_default_config();
    ethernetif_apply_configured_or_internal_mac_locked(cfg->net.mac);
    ch390_interrupt_init();

    g_netif_init_ok = 1;
}

void ethernetif_diag_poll_status(void)
{
    struct ch390_runtime_status runtime_status;

    if (spi_mutex != NULL)
    {
        xSemaphoreTake(spi_mutex, portMAX_DELAY);
    }

    (void)memset(&runtime_status, 0, sizeof(runtime_status));
    ch390_runtime_poll(&runtime_status);

    if (spi_mutex != NULL)
    {
        xSemaphoreGive(spi_mutex);
    }
}

/**
 * @brief Check and handle CH390 link status
 */
static void ethernetif_update_link(uint8_t link_status)
{
    uint8_t old_link_status;

    LOCK_TCPIP_CORE();

    old_link_status = netif_is_link_up(&ch390_netif) ? 1u : 0u;

    if (link_status)
    {
        if (!netif_is_link_up(&ch390_netif))
        {
            netif_set_link_up(&ch390_netif);
            if ((s_garp_sent == 0u) && netif_is_up(&ch390_netif))
            {
                etharp_gratuitous(&ch390_netif);
                s_garp_sent = 1u;
            }
        }
    }
    else
    {
        if (netif_is_link_up(&ch390_netif))
        {
            netif_set_link_down(&ch390_netif);
            s_garp_sent = 0u;
        }
    }

    UNLOCK_TCPIP_CORE();

    if (old_link_status != ((link_status != 0u) ? 1u : 0u))
    {
        debug_log_printf("[ETH] link %s ip=%u.%u.%u.%u mac=%02X:%02X:%02X:%02X:%02X:%02X\r\n",
                         (link_status != 0u) ? "up" : "down",
                         (unsigned int)ip4_addr1_16(netif_ip4_addr(&ch390_netif)),
                         (unsigned int)ip4_addr2_16(netif_ip4_addr(&ch390_netif)),
                         (unsigned int)ip4_addr3_16(netif_ip4_addr(&ch390_netif)),
                         (unsigned int)ip4_addr4_16(netif_ip4_addr(&ch390_netif)),
                         (unsigned int)ch390_netif.hwaddr[0],
                         (unsigned int)ch390_netif.hwaddr[1],
                         (unsigned int)ch390_netif.hwaddr[2],
                         (unsigned int)ch390_netif.hwaddr[3],
                         (unsigned int)ch390_netif.hwaddr[4],
                         (unsigned int)ch390_netif.hwaddr[5]);
    }
}

/**
 * @brief Process all pending RX packets
 */
void ethernetif_poll(void)
{
    struct ch390_runtime_status runtime_status;
    struct pbuf *p;
    err_t input_err;
    uint32_t rx_overflow_count;
    uint8_t recovery_type;
    uint8_t rx_budget = 4u;
    
    /* Take SPI mutex */
    if (spi_mutex != NULL)
    {
        xSemaphoreTake(spi_mutex, portMAX_DELAY);
    }
    
    (void)memset(&runtime_status, 0, sizeof(runtime_status));
    ch390_runtime_poll(&runtime_status);
    
    /* Release SPI mutex */
    if (spi_mutex != NULL)
    {
        xSemaphoreGive(spi_mutex);
    }

    /* Handle RX overflow */
    if ((runtime_status.int_status & (ISR_ROS | ISR_ROO)) != 0u)
    {
        LINK_STATS_INC(link.err);
        rx_overflow_count = ethernetif_record_event_and_request(&s_recovery.rx_overflow_count, ETH_RECOVERY_RX);
        if (ethernetif_should_log_counter(rx_overflow_count) != 0u)
        {
            debug_log_printf("[ETH] rx ovf n=%lu\r\n", (unsigned long)rx_overflow_count);
        }
    }

    recovery_type = ethernetif_run_pending_recovery();

    /* Handle link change unless a full recovery already resynchronized link state. */
    if ((recovery_type != ETH_RECOVERY_FULL) && ((runtime_status.int_status & ISR_LNKCHG) != 0u))
    {
        ethernetif_update_link((uint8_t)ch390_runtime_link_up_from_status(&runtime_status));
    }
    
    /* Always attempt to drain RX FIFO so packet handling does not depend only on ISR_PR timing. */
    while (rx_budget > 0u)
    {
        p = low_level_input(&ch390_netif);
        if (p == NULL)
        {
            break;
        }
        
        input_err = ch390_netif.input(p, &ch390_netif);
        if (input_err != ERR_OK)
        {
            pbuf_free(p);
        }

        --rx_budget;
    }

    (void)ethernetif_run_pending_recovery();

    if (rx_budget == 0u)
    {
        taskYIELD();
    }
}

void ethernetif_check_link(void)
{
    uint8_t link_status;

    if (spi_mutex != NULL)
    {
        xSemaphoreTake(spi_mutex, portMAX_DELAY);
    }

    link_status = (uint8_t)ch390_get_link_status();

    if (spi_mutex != NULL)
    {
        xSemaphoreGive(spi_mutex);
    }

    ethernetif_update_link(link_status);
}

uint8_t ethernetif_link_is_up(void)
{
    return netif_is_link_up(&ch390_netif) ? 1u : 0u;
}
