#include "ch390_runtime.h"

#include "CH390.h"
#include "CH390_Interface.h"
#include "SEGGER_RTT.h"
#include "ethernetif.h"
#include "stm32f1xx_hal.h"
#include "lwip/etharp.h"
#include "lwip/pbuf.h"
#include "lwip/stats.h"

#include <string.h>

static void ch390_runtime_dispatch_frame(struct netif *netif, struct pbuf *p)
{
    if ((p != NULL) && (netif->input(p, netif) != ERR_OK)) {
        pbuf_free(p);
    }
}

static uint8_t ch390_runtime_drain_rx(struct netif *netif, uint8_t max_frames)
{
    struct pbuf *p;
    uint8_t drained = 0u;
    uint8_t rx_ready;

    while (drained < max_frames) {
        p = ch390_runtime_input_frame(netif);
        if (p == NULL) {
            ch390_read_reg(CH390_MRCMDX);
            rx_ready = ch390_read_reg(CH390_MRCMDX);
            if ((rx_ready & CH390_PKT_RDY) == 0u) {
                break;
            }
            drained++;
            continue;
        }
        ch390_runtime_dispatch_frame(netif, p);
        drained++;
    }

    return drained;
}

static volatile uint8_t g_ch390_irq_pending;
static uint8_t g_ch390_ready;
static ch390_diag_t g_diag;
static uint8_t g_tx_consecutive_timeout;
static uint8_t g_chip_reset_count;
static uint8_t g_link_restart_pending;

#define TX_BUSY_WAIT_TIMEOUT_MS       10u
#define TX_TIMEOUT_RESET_THRESHOLD    6u
#define HEALTH_FAIL_THRESHOLD         3u
#define RESTART_PENDING_FLAG          0x01u
#define HEALTH_FAIL_SHIFT             4u
#define HEALTH_FAIL_MASK              0xF0u

#define CH390_RX_FILTER_ENABLE             1u
#define CH390_RX_PREFIX_LEN                38u
#define CH390_ETH_HEADER_LEN               14u
#define CH390_IPV4_MIN_HEADER_LEN          20u
#define CH390_ETH_TYPE_IPV4                0x0800u
#define CH390_ETH_TYPE_ARP                 0x0806u
#define CH390_ETH_TYPE_VLAN                0x8100u
#define CH390_ETH_TYPE_IPV6                0x86DDu
#define CH390_ETH_TYPE_QINQ                0x88A8u
#define CH390_ETH_TYPE_LLDP                0x88CCu
#define CH390_IP_PROTO_ICMP                1u
#define CH390_IP_PROTO_IGMP                2u
#define CH390_IP_PROTO_TCP                 6u
#define CH390_IP_PROTO_UDP                 17u

typedef enum {
    CH390_RX_ACCEPT = 0,
    CH390_RX_DROP_MALFORMED,
    CH390_RX_DROP_IPV6,
    CH390_RX_DROP_LLDP,
    CH390_RX_DROP_UDP,
    CH390_RX_DROP_IGMP,
    CH390_RX_DROP_OTHER_ETH,
    CH390_RX_DROP_OTHER_IPV4
} ch390_rx_filter_result_t;

static bool ch390_mac_address_valid(const uint8_t *mac);
static ch390_rx_filter_result_t ch390_runtime_filter_frame(const uint8_t *prefix, uint16_t frame_len, uint16_t prefix_len);
static void ch390_runtime_count_filtered_frame(ch390_rx_filter_result_t result);
static void ch390_runtime_copy_prefix_to_pbuf(struct pbuf *p, const uint8_t *prefix, uint16_t prefix_len);
static void ch390_runtime_read_remaining_to_pbuf(struct pbuf *p, uint16_t offset);

static ch390_rx_filter_result_t ch390_runtime_filter_frame(const uint8_t *prefix, uint16_t frame_len, uint16_t prefix_len)
{
    uint16_t eth_type;
    uint16_t l2_header_len = CH390_ETH_HEADER_LEN;
    uint16_t ip_offset;
    uint8_t ip_version;
    uint8_t ip_ihl;
    uint8_t ip_proto;

    if ((prefix == NULL) || (frame_len < CH390_ETH_HEADER_LEN) || (prefix_len < CH390_ETH_HEADER_LEN)) {
        return CH390_RX_DROP_MALFORMED;
    }

    eth_type = (uint16_t)(((uint16_t)prefix[12] << 8) | prefix[13]);
    g_diag.last_eth_type = eth_type;

    if ((eth_type == CH390_ETH_TYPE_VLAN) || (eth_type == CH390_ETH_TYPE_QINQ)) {
        if ((frame_len < (CH390_ETH_HEADER_LEN + 4u)) || (prefix_len < (CH390_ETH_HEADER_LEN + 4u))) {
            return CH390_RX_DROP_MALFORMED;
        }
        l2_header_len = (uint16_t)(CH390_ETH_HEADER_LEN + 4u);
        eth_type = (uint16_t)(((uint16_t)prefix[16] << 8) | prefix[17]);
        g_diag.last_eth_type = eth_type;
    }

    if (eth_type == CH390_ETH_TYPE_ARP) {
        g_diag.rx_arp_frames++;
        return CH390_RX_ACCEPT;
    }

    if (eth_type == CH390_ETH_TYPE_IPV6) {
        return CH390_RX_DROP_IPV6;
    }

    if (eth_type == CH390_ETH_TYPE_LLDP) {
        return CH390_RX_DROP_LLDP;
    }

    if (eth_type != CH390_ETH_TYPE_IPV4) {
        return CH390_RX_DROP_OTHER_ETH;
    }

    ip_offset = l2_header_len;
    if ((frame_len < (uint16_t)(ip_offset + CH390_IPV4_MIN_HEADER_LEN)) ||
        (prefix_len < (uint16_t)(ip_offset + CH390_IPV4_MIN_HEADER_LEN))) {
        return CH390_RX_DROP_MALFORMED;
    }

    ip_version = (uint8_t)(prefix[ip_offset] >> 4);
    ip_ihl = (uint8_t)(prefix[ip_offset] & 0x0Fu);
    if ((ip_version != 4u) || (ip_ihl < 5u)) {
        return CH390_RX_DROP_MALFORMED;
    }

    ip_proto = prefix[ip_offset + 9u];
    g_diag.rx_ip_frames++;

    if (ip_proto == CH390_IP_PROTO_ICMP) {
        g_diag.rx_ipv4_icmp_frames++;
        return CH390_RX_ACCEPT;
    }

    if (ip_proto == CH390_IP_PROTO_TCP) {
        g_diag.rx_ipv4_tcp_frames++;
        return CH390_RX_ACCEPT;
    }

    if (ip_proto == CH390_IP_PROTO_UDP) {
        g_diag.rx_ipv4_udp_frames++;
        return CH390_RX_DROP_UDP;
    }

    if (ip_proto == CH390_IP_PROTO_IGMP) {
        g_diag.rx_filtered_igmp++;
        return CH390_RX_DROP_IGMP;
    }

    return CH390_RX_DROP_OTHER_IPV4;
}

static void ch390_runtime_count_filtered_frame(ch390_rx_filter_result_t result)
{
    g_diag.rx_filtered_frames++;
    switch (result) {
    case CH390_RX_DROP_MALFORMED:
        g_diag.rx_filtered_malformed++;
        break;
    case CH390_RX_DROP_IPV6:
        g_diag.rx_filtered_ipv6++;
        break;
    case CH390_RX_DROP_LLDP:
        g_diag.rx_filtered_lldp++;
        break;
    case CH390_RX_DROP_UDP:
        g_diag.rx_filtered_udp++;
        break;
    case CH390_RX_DROP_IGMP:
        break;
    case CH390_RX_DROP_OTHER_ETH:
        g_diag.rx_filtered_other_eth++;
        break;
    case CH390_RX_DROP_OTHER_IPV4:
        g_diag.rx_filtered_other_ipv4++;
        break;
    case CH390_RX_ACCEPT:
    default:
        break;
    }
}

static void ch390_runtime_copy_prefix_to_pbuf(struct pbuf *p, const uint8_t *prefix, uint16_t prefix_len)
{
    struct pbuf *q;
    uint16_t copied = 0u;

    for (q = p; (q != NULL) && (copied < prefix_len); q = q->next) {
        uint16_t chunk = (uint16_t)(prefix_len - copied);
        if (chunk > q->len) {
            chunk = q->len;
        }
        memcpy(q->payload, &prefix[copied], chunk);
        copied = (uint16_t)(copied + chunk);
    }
}

static void ch390_runtime_read_remaining_to_pbuf(struct pbuf *p, uint16_t offset)
{
    struct pbuf *q;
    uint16_t skipped = 0u;

    for (q = p; q != NULL; q = q->next) {
        if (skipped >= offset) {
            ch390_read_mem((uint8_t *)q->payload, q->len);
        } else if ((uint16_t)(skipped + q->len) > offset) {
            uint16_t in_chunk_offset = (uint16_t)(offset - skipped);
            uint16_t read_len = (uint16_t)(q->len - in_chunk_offset);
            ch390_read_mem(&((uint8_t *)q->payload)[in_chunk_offset], read_len);
        }
        skipped = (uint16_t)(skipped + q->len);
    }
}

static uint8_t ch390_runtime_is_restart_pending(void)
{
    return (uint8_t)(g_link_restart_pending & RESTART_PENDING_FLAG);
}

static void ch390_runtime_set_restart_pending(void)
{
    g_link_restart_pending = (uint8_t)(g_link_restart_pending | RESTART_PENDING_FLAG);
}

static void ch390_runtime_clear_restart_pending(void)
{
    g_link_restart_pending = (uint8_t)(g_link_restart_pending & (uint8_t)(~RESTART_PENDING_FLAG));
}

static uint8_t ch390_runtime_get_health_fail_count(void)
{
    return (uint8_t)((g_link_restart_pending & HEALTH_FAIL_MASK) >> HEALTH_FAIL_SHIFT);
}

static void ch390_runtime_set_health_fail_count(uint8_t count)
{
    g_link_restart_pending = (uint8_t)((g_link_restart_pending & (uint8_t)(~HEALTH_FAIL_MASK)) |
                                       (uint8_t)((count << HEALTH_FAIL_SHIFT) & HEALTH_FAIL_MASK));
}

static uint8_t ch390_runtime_probe_identity(void)
{
    g_diag.vendor_id = ch390_get_vendor_id();
    g_diag.product_id = ch390_get_product_id();
    g_diag.revision = ch390_get_revision();
    g_diag.phy_bmcr = ch390_read_phy(CH390_PHY_BMCR);
    g_diag.phy_bmsr = ch390_read_phy(CH390_PHY_BMSR);
    g_diag.phy_id1 = ch390_read_phy(CH390_PHY_PHYID1);
    g_diag.phy_id2 = ch390_read_phy(CH390_PHY_PHYID2);
    g_diag.phy_anar = ch390_read_phy(CH390_PHY_ANAR);
    g_diag.phy_anlpar = ch390_read_phy(CH390_PHY_ANLPAR);
    g_diag.phy_aner = ch390_read_phy(CH390_PHY_ANER);
    g_diag.nsr = ch390_read_reg(CH390_NSR);
    g_diag.ncr = ch390_read_reg(CH390_NCR);
    g_diag.rcr = ch390_read_reg(CH390_RCR);
    g_diag.imr = ch390_read_reg(CH390_IMR);
    g_diag.intcr = ch390_read_reg(CH390_INTCR);
    g_diag.gpr = ch390_read_reg(CH390_GPR);
    g_diag.isr = ch390_read_reg(CH390_ISR);
    g_diag.phy_speed_10m = 0u;
    g_diag.phy_full_duplex = 0u;
    g_diag.link_up = (uint8_t)0u;
    g_diag.id_valid = (uint8_t)((g_diag.vendor_id != 0x0000u) &&
                                (g_diag.vendor_id != 0xFFFFu) &&
                                (g_diag.product_id != 0x0000u) &&
                                (g_diag.product_id != 0xFFFFu));
    return g_diag.id_valid;
}

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

    if (netif == NULL) {
        return;
    }

    netif->hwaddr_len = ETHARP_HWADDR_LEN;
    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 ch390_runtime_sync_mac(struct netif *netif)
{
    if (netif == NULL) {
        return;
    }

    if (ch390_mac_address_valid(netif->hwaddr)) {
        ch390_set_mac_address(netif->hwaddr);
    }

    ch390_get_mac(netif->hwaddr);
}

static void ch390_runtime_refresh_diag(void)
{
    uint8_t id_valid = ch390_runtime_probe_identity();
    g_diag.int_pin = (uint8_t)ch390_get_int_pin();

    if (id_valid != 0u) {
        g_diag.phy_speed_10m = (uint8_t)ch390_get_phy_speed();
        g_diag.phy_full_duplex = (uint8_t)ch390_get_duplex_mode();
        g_diag.link_up = (uint8_t)ch390_get_link_status();
    }
}

static bool ch390_runtime_recover_chip(struct netif *netif, const uint8_t *mac)
{
    g_ch390_ready = 0u;
    g_ch390_irq_pending = 0u;

    ch390_hardware_reset();
    if (ch390_runtime_probe_identity() == 0u) {
        return false;
    }

    ch390_software_reset();
    if (ch390_runtime_probe_identity() == 0u) {
        return false;
    }

    ch390_phy_power_cycle();
    ch390_default_config();

    if (ch390_mac_address_valid(mac)) {
        ch390_set_mac_address((uint8_t *)mac);
    }

    ch390_runtime_prepare_netif(netif);
    ch390_runtime_sync_mac(netif);
    ch390_runtime_refresh_diag();
    g_ch390_ready = g_diag.id_valid;
    g_ch390_irq_pending = 0u;

    return g_ch390_ready != 0u;
}

struct pbuf *ch390_runtime_input_frame(struct netif *netif)
{
    struct ethernetif *ethernetif = (struct ethernetif *)netif->state;
    struct pbuf *p = NULL;
    uint16_t len;
    uint16_t frame_len;
    uint16_t prefix_len;
    uint8_t rcr;
    uint8_t rx_ready;
    uint8_t rx_header[4];
    uint8_t frame_prefix[CH390_RX_PREFIX_LEN];
    ch390_rx_filter_result_t filter_result;
    ch390_read_reg(CH390_MRCMDX);
    rx_ready = ch390_read_reg(CH390_MRCMDX);

    if (rx_ready & CH390_PKT_ERR) {
        rcr = ch390_read_reg(CH390_RCR);
        ch390_write_reg(CH390_RCR, (uint8_t)(rcr & (uint8_t)(~RCR_RXEN)));
        ch390_write_reg(CH390_MPTRCR, 0x01u);
        ch390_write_reg(CH390_MRRH, 0x0Cu);
        ch390_delay_us(1000u);
        ch390_write_reg(CH390_RCR, (uint8_t)(rcr | RCR_RXEN));
        ethernetif->rx_len = 0u;
        LINK_STATS_INC(link.drop);
        g_diag.rx_packets_drop++;
        return NULL;
    }

    if ((rx_ready & CH390_PKT_RDY) == 0u) {
        ethernetif->rx_len = 0u;
        return NULL;
    }

    g_diag.rx_ready_hits++;

    ch390_read_mem(rx_header, 4);
    ethernetif->rx_status = rx_header[1];
    frame_len = (uint16_t)((uint16_t)rx_header[2] | ((uint16_t)rx_header[3] << 8));

    if ((ethernetif->rx_status & 0x3Fu) != 0u || frame_len == 0u || frame_len > CH390_PKT_MAX) {
        ethernetif->rx_len = 0u;
        ch390_drop_packet(frame_len);
        LINK_STATS_INC(link.drop);
        g_diag.rx_packets_drop++;
        return NULL;
    }

    ethernetif->rx_len = frame_len;
    prefix_len = frame_len;
    if (prefix_len > CH390_RX_PREFIX_LEN) {
        prefix_len = CH390_RX_PREFIX_LEN;
    }
    ch390_read_mem(frame_prefix, prefix_len);

#if CH390_RX_FILTER_ENABLE
    filter_result = ch390_runtime_filter_frame(frame_prefix, frame_len, prefix_len);
    if (filter_result != CH390_RX_ACCEPT) {
        ch390_drop_packet((uint16_t)(frame_len - prefix_len));
        LINK_STATS_INC(link.drop);
        g_diag.rx_packets_drop++;
        ch390_runtime_count_filtered_frame(filter_result);
        return NULL;
    }
#endif

    len = ethernetif->rx_len;
#if ETH_PAD_SIZE
    len += ETH_PAD_SIZE;
#endif

    p = pbuf_alloc(PBUF_RAW, len, PBUF_POOL);
    if (p != NULL) {
#if ETH_PAD_SIZE
        pbuf_remove_header(p, ETH_PAD_SIZE);
#endif
        ch390_runtime_copy_prefix_to_pbuf(p, frame_prefix, prefix_len);
        ch390_runtime_read_remaining_to_pbuf(p, prefix_len);
#if ETH_PAD_SIZE
        pbuf_add_header(p, ETH_PAD_SIZE);
#endif
        LINK_STATS_INC(link.recv);
        g_diag.rx_packets_ok++;

        g_diag.last_frame_len = frame_len;
        g_diag.last_payload_len = p->tot_len;
    } else {
        ch390_drop_packet((uint16_t)(frame_len - prefix_len));
        LINK_STATS_INC(link.memerr);
        LINK_STATS_INC(link.drop);
        g_diag.rx_packets_drop++;
        g_diag.rx_pbuf_alloc_failed++;
    }

    return p;
}

static bool ch390_mac_address_valid(const uint8_t *mac)
{
    if (mac == NULL) {
        return false;
    }
    for (uint8_t i = 0; i < ETHARP_HWADDR_LEN; ++i) {
        if (mac[i] == 0u) {
            return false;
        }
    }
    return true;
}

void ch390_runtime_init(struct netif *netif, const uint8_t *mac)
{
    SEGGER_RTT_WriteString(0, "ETH init: gpio\r\n");
    ch390_gpio_init();
    SEGGER_RTT_WriteString(0, "ETH init: spi\r\n");
    ch390_spi_init();
    SEGGER_RTT_WriteString(0, "ETH init: deep reset\r\n");
    g_ch390_ready = (uint8_t)ch390_runtime_recover_chip(netif, mac);
    if (g_ch390_ready == 0u) {
        ch390_runtime_prepare_netif(netif);
        netif_set_link_down(netif);
        SEGGER_RTT_WriteString(0, "ETH init: invalid chip id\r\n");
        return;
    }

    SEGGER_RTT_WriteString(0, "ETH init: mac\r\n");
    if (ch390_mac_address_valid(mac)) {
        ch390_runtime_sync_mac(netif);
    }
    else {
        if (mac != NULL) {
            ch390_get_mac((uint8_t *)mac);
            SEGGER_RTT_printf(0, "ETH init: invalid MAC in config, using hardware MAC: %02X:%02X:%02X:%02X:%02X:%02X\r\n",
                                  mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
        }
        else {
            SEGGER_RTT_WriteString(0, "ETH init: no MAC in config\r\n");
        }
    }

    SEGGER_RTT_WriteString(0, "ETH init: getmac\r\n");
    ch390_runtime_prepare_netif(netif);
    ch390_get_mac(netif->hwaddr);

    ch390_runtime_refresh_diag();
    g_ch390_ready = g_diag.id_valid;

    SEGGER_RTT_WriteString(0, "ETH init: irq\r\n");
    ch390_interrupt_init();
    SEGGER_RTT_WriteString(0, "ETH init: done\r\n");
}

void ch390_runtime_set_irq_pending(void)
{
    g_ch390_irq_pending = 1u;
}

uint8_t ch390_runtime_is_irq_pending(void)
{
    return g_ch390_irq_pending;
}

void ch390_runtime_poll(struct netif *netif)
{
    uint8_t int_status;
    uint8_t rx_ready;
    uint8_t rx_budget;
    uint8_t rx_hint;

    if (!g_ch390_ready) {
        return;
    }

    g_diag.rx_poll_calls++;

    rx_budget = 1u;
    rx_hint = 0u;

    if ((g_ch390_irq_pending != 0u) || (ch390_get_int_pin() == GPIO_PIN_RESET)) {
        g_ch390_irq_pending = 0u;
        int_status = ch390_get_int_status();

        if ((int_status & ISR_LNKCHG) != 0u) {
            ch390_runtime_check_link(netif);
        }

        if ((int_status & ISR_ROS) != 0u) {
            LINK_STATS_INC(link.err);
        }

        if ((int_status & (ISR_PR | ISR_ROS | ISR_ROO)) != 0u) {
            rx_hint = 1u;
            rx_budget = 8u;
        }
    }

    ch390_read_reg(CH390_MRCMDX);
    rx_ready = ch390_read_reg(CH390_MRCMDX);
    if ((rx_ready & CH390_PKT_RDY) != 0u) {
        rx_hint = 1u;
        if (rx_budget < 4u) {
            rx_budget = 4u;
        }
    }

    if (rx_hint != 0u) {
        (void)ch390_runtime_drain_rx(netif, rx_budget);
    }
}

void ch390_runtime_check_link(struct netif *netif)
{
    uint8_t link_up;
    static uint8_t s_last_reported = 0xFFu;

    if (!g_ch390_ready) {
        netif_set_link_down(netif);
        return;
    }

    if (ch390_runtime_is_restart_pending() != 0u) {
        netif_set_link_down(netif);
        ch390_runtime_clear_restart_pending();
        SEGGER_RTT_WriteString(0, "ETH restart pending: hold link down for app recycle\r\n");
        return;
    }

    ch390_runtime_refresh_diag();
    link_up = (uint8_t)ch390_get_link_status();

    if (link_up != s_last_reported) {
        SEGGER_RTT_printf(0,
                          "ETH link %s nsr=0x%02X bmsr=0x%04X anlpar=0x%04X\r\n",
                          link_up ? "up" : "down",
                          g_diag.nsr,
                          g_diag.phy_bmsr,
                          g_diag.phy_anlpar);
        s_last_reported = link_up;
    }

    if (link_up) {
        if (!netif_is_link_up(netif)) {
            netif_set_link_up(netif);
        }
    } else if (netif_is_link_up(netif)) {
        netif_set_link_down(netif);
    }
}

err_t ch390_runtime_output(struct netif *netif, struct pbuf *p)
{
    struct pbuf *q;
    uint32_t start_tick;

    if (!g_ch390_ready) {
        LINK_STATS_INC(link.drop);
        return ERR_IF;
    }

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

    start_tick = HAL_GetTick();
    while (ch390_read_reg(CH390_TCR) & TCR_TXREQ) {
        if ((HAL_GetTick() - start_tick) > TX_BUSY_WAIT_TIMEOUT_MS) {
#if ETH_PAD_SIZE
            pbuf_add_header(p, ETH_PAD_SIZE);
#endif
            LINK_STATS_INC(link.drop);
            g_diag.tx_packets_timeout++;
            if (g_tx_consecutive_timeout < 0xFFu) {
                g_tx_consecutive_timeout++;
            }
            if (g_tx_consecutive_timeout >= TX_TIMEOUT_RESET_THRESHOLD) {
                (void)ch390_runtime_emergency_reset(netif);
            }
            return ERR_TIMEOUT;
        }
    }

    g_tx_consecutive_timeout = 0u;

    for (q = p; q != NULL; q = q->next) {
        ch390_write_mem((uint8_t *)q->payload, q->len);
    }

    ch390_write_reg(CH390_TXPLL, p->tot_len & 0xFFu);
    ch390_write_reg(CH390_TXPLH, (p->tot_len >> 8) & 0xFFu);
    ch390_send_request();

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

    LINK_STATS_INC(link.xmit);
    g_diag.tx_packets_ok++;
    return ERR_OK;
}

void ch390_runtime_get_diag(ch390_diag_t *diag)
{
    if (diag != NULL) {
        ch390_runtime_refresh_diag();
        *diag = g_diag;
    }
}

bool ch390_runtime_is_ready(void)
{
    return g_ch390_ready != 0u;
}

bool ch390_runtime_emergency_reset(struct netif *netif)
{
    SEGGER_RTT_printf(0, "ETH emergency reset (tx_timeout=%u resets=%u)\r\n",
                      g_tx_consecutive_timeout, g_chip_reset_count);

    if (netif != NULL) {
        netif_set_link_down(netif);
    }

    if (g_chip_reset_count < 0xFFu) {
        g_chip_reset_count++;
    }
    g_tx_consecutive_timeout = 0u;

    (void)ch390_runtime_recover_chip(netif, (netif != NULL) ? netif->hwaddr : NULL);

    if (g_ch390_ready == 0u) {
        SEGGER_RTT_WriteString(0, "ETH emergency reset: chip not responding\r\n");
        return false;
    }

    ch390_runtime_set_health_fail_count(0u);
    ch390_runtime_set_restart_pending();
    SEGGER_RTT_WriteString(0, "ETH emergency reset: OK\r\n");
    return true;
}

void ch390_runtime_health_check(struct netif *netif)
{
    uint16_t vid;
    uint8_t fail_count;

    if (!g_ch390_ready) {
        SEGGER_RTT_WriteString(0, "ETH health: chip not ready, attempting reset\r\n");
        (void)ch390_runtime_emergency_reset(netif);
        return;
    }

    /* Verify chip is still responding by reading vendor ID */
    vid = ch390_get_vendor_id();
    if (vid == 0x0000u || vid == 0xFFFFu) {
        fail_count = ch390_runtime_get_health_fail_count();
        if (fail_count < 0x0Fu) {
            fail_count++;
        }
        ch390_runtime_set_health_fail_count(fail_count);
        if (fail_count >= HEALTH_FAIL_THRESHOLD) {
            SEGGER_RTT_printf(0, "ETH health: invalid VID=0x%04X streak=%u, attempting reset\r\n",
                              vid,
                              fail_count);
            ch390_runtime_set_health_fail_count(0u);
            (void)ch390_runtime_emergency_reset(netif);
        }
    } else {
        ch390_runtime_set_health_fail_count(0u);
    }
}

uint8_t ch390_runtime_get_reset_count(void)
{
    return g_chip_reset_count;
}
