#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 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(); } } 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: reset\r\n"); ch390_hardware_reset(); SEGGER_RTT_WriteString(0, "ETH init: probe\r\n"); g_ch390_ready = ch390_runtime_probe_identity(); 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: default\r\n"); ch390_default_config(); SEGGER_RTT_WriteString(0, "ETH init: mac\r\n"); if (ch390_mac_address_valid(mac)) { ch390_set_mac_address((uint8_t *)mac); } 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; ch390_software_reset(); ch390_delay_us(5000u); ch390_default_config(); ch390_runtime_prepare_netif(netif); ch390_runtime_sync_mac(netif); g_ch390_irq_pending = 0u; ch390_runtime_refresh_diag(); g_ch390_ready = g_diag.id_valid; 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; }