Files
TCP2UART/Drivers/CH390/ch390_runtime.c
T

719 lines
20 KiB
C

#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();
}
}
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;
}