From nobody Sun Feb 8 19:20:49 2026 Received: from metis.whiteo.stw.pengutronix.de (metis.whiteo.stw.pengutronix.de [185.203.201.7]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id BA5563385A6 for ; Fri, 23 Jan 2026 09:07:52 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=185.203.201.7 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1769159275; cv=none; b=V1AFQHpq3+jZtVOgFqdi/1/2iTKkg3CIPez6Aj5ov2e+4iGa0S+b2zPq57pk/fOndsD2+8lYi1U5yH3ZwtxJJzb2wTaP4JnyzUwQ9R8+H4kVcR0ZWpahih7Swh1qxtfrJqXP7Mwo8wpp/cpQRIbF/6rWeAdZ83h/NRtkVapPB4g= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1769159275; c=relaxed/simple; bh=mCKUo3TPfcyOzZ8ABSgqCmFLcXF+ttKCZnIc/7ffrv8=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=pS+f+zVbbGvwE8TVkBinef7dAR4PJWrBGN0jzqQHyJuMDi/BQzBMLyRaZl4ffDYdd1T+BbJRq7E4H0COU/dL73m+qoD9wBVL8FTN7gA3+jbRJYfSKrhXHmkaLO6EBrq9fJLEujUE6h4Sf+rMPExS55kRqb2sNvZyqWYakEJ90dY= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=pengutronix.de; spf=pass smtp.mailfrom=pengutronix.de; arc=none smtp.client-ip=185.203.201.7 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=pengutronix.de Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=pengutronix.de Received: from drehscheibe.grey.stw.pengutronix.de ([2a0a:edc0:0:c01:1d::a2]) by metis.whiteo.stw.pengutronix.de with esmtps (TLS1.3:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.92) (envelope-from ) id 1vjD8q-00027G-59; Fri, 23 Jan 2026 10:07:44 +0100 Received: from dude04.red.stw.pengutronix.de ([2a0a:edc0:0:1101:1d::ac] helo=dude04) by drehscheibe.grey.stw.pengutronix.de with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.96) (envelope-from ) id 1vjD8p-00244x-0V; Fri, 23 Jan 2026 10:07:42 +0100 Received: from ore by dude04 with local (Exim 4.98.2) (envelope-from ) id 1vjD8o-00000006ZWi-1hdQ; Fri, 23 Jan 2026 10:07:42 +0100 From: Oleksij Rempel To: "David S. Miller" , Eric Dumazet , Jakub Kicinski , Paolo Abeni , Andrew Lunn , Thangaraj Samynathan , Rengarajan Sundararajan Cc: Oleksij Rempel , kernel@pengutronix.de, linux-kernel@vger.kernel.org, netdev@vger.kernel.org, UNGLinuxDriver@microchip.com Subject: [RFC PATCH 1/4] net: lan78xx: Add devlink health support for diagnostics Date: Fri, 23 Jan 2026 10:07:37 +0100 Message-ID: <20260123090741.1566469-2-o.rempel@pengutronix.de> X-Mailer: git-send-email 2.47.3 In-Reply-To: <20260123090741.1566469-1-o.rempel@pengutronix.de> References: <20260123090741.1566469-1-o.rempel@pengutronix.de> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-SA-Exim-Connect-IP: 2a0a:edc0:0:c01:1d::a2 X-SA-Exim-Mail-From: ore@pengutronix.de X-SA-Exim-Scanned: No (on metis.whiteo.stw.pengutronix.de); SAEximRunCond expanded to false X-PTX-Original-Recipient: linux-kernel@vger.kernel.org Content-Type: text/plain; charset="utf-8" Add devlink health support for diagnostics Signed-off-by: Oleksij Rempel --- drivers/net/usb/lan78xx.c | 388 +++++++++++++++++++++++++++++++++++++- 1 file changed, 387 insertions(+), 1 deletion(-) diff --git a/drivers/net/usb/lan78xx.c b/drivers/net/usb/lan78xx.c index ad620b56443b..221be42e06f4 100644 --- a/drivers/net/usb/lan78xx.c +++ b/drivers/net/usb/lan78xx.c @@ -27,6 +27,7 @@ #include #include #include +#include #include #include #include @@ -41,6 +42,11 @@ #define THROTTLE_JIFFIES (HZ / 8) #define UNLINK_TIMEOUT_MS 3 =20 +#define LAN78XX_STALL_PAUSE_THRESH 100 +#define LAN78XX_LIVELOCK_DROP_THRESH 10000 +#define LAN78XX_LIVELOCK_DROP_RATIO 10 +#define LAN78XX_TX_TIMEOUT_DROP_THRESH 1000 + #define RX_MAX_QUEUE_MEMORY (60 * 1518) =20 #define SS_USB_PKT_SIZE (1024) @@ -373,6 +379,10 @@ struct lan78xx_priv { u32 wol; }; =20 +struct lan78xx_devlink_priv { + struct lan78xx_net *dev; +}; + enum skb_state { illegal =3D 0, tx_start, @@ -411,6 +421,19 @@ struct statstage { struct lan78xx_statstage64 curr_stat; }; =20 +struct lan78xx_stat_snapshot { + ktime_t time; + + u64 tx_pause_total; + u64 tx_unicast_total; + u64 rx_total_frames; + u64 rx_hw_drop_total; + u64 rx_sw_packets_total; + + u32 last_delta_pause; + u32 last_delta_drops; +}; + struct irq_domain_data { struct irq_domain *irqdomain; unsigned int phyirq; @@ -477,6 +500,35 @@ struct lan78xx_net { =20 struct phylink *phylink; struct phylink_config phylink_config; + + struct devlink *devlink; + struct devlink_health_reporter *fifo_reporter; + struct devlink_health_reporter *internal_err_reporter; + struct lan78xx_stat_snapshot snapshot; +}; + +struct lan78xx_dump_ctx { + const char *msg; + ktime_t ts; /* Timestamp of detection */ + + union { + struct { + u64 delta_pause; + u64 delta_rx; + u64 delta_hw_drop; + u64 delta_sw_rx; + } fifo; + struct { + u32 int_sts; /* The ISR's view of INT_STS */ + u32 int_enp; /* The ISR's view of INT_ENP_CTL */ + } err; + }; +}; + +/* Register Dump Map Structure */ +struct lan78xx_reg_map { + u32 reg; + const char *name; }; =20 /* use ethtool to change the level for any given device */ @@ -484,6 +536,87 @@ static int msg_level =3D -1; module_param(msg_level, int, 0); MODULE_PARM_DESC(msg_level, "Override default message level"); =20 +/* Helper macro to map register to name string */ +#define LAN78XX_DUMP_REG(reg) { reg, #reg } + +static const struct lan78xx_reg_map lan78xx_fifo_regs[] =3D { + /* --- FIFO Control & Status --- + * specific enable/reset bits. + * used_bytes tells us if the bottleneck is USB (TX high) or MAC + * (RX high). + */ + LAN78XX_DUMP_REG(FCT_TX_CTL), + LAN78XX_DUMP_REG(FCT_RX_CTL), + + /* --- Data Path Usage --- + * Capture total buffer usage including USB endpoint overhead. + * If DP_STOR is high but FCT_USED is low, data is stuck in the USB + * layer. + */ + LAN78XX_DUMP_REG(TX_DP_STOR), + LAN78XX_DUMP_REG(RX_DP_STOR), + + /* --- FIFO Boundaries --- + * verify if the FIFO partitioning has been corrupted or misconfigured. + */ + LAN78XX_DUMP_REG(FCT_TX_FIFO_END), + LAN78XX_DUMP_REG(FCT_RX_FIFO_END), + + /* --- Flow Control --- + * Critical for "Pause Storm" debugging. + * Check if thresholds are set correctly and if Pause frames are enabled. + */ + LAN78XX_DUMP_REG(FCT_FLOW), + LAN78XX_DUMP_REG(FLOW), + + /* --- Configuration & Speed --- + * Mismatches between MAC speed (1G) and USB speed (HighSpeed) + * are the #1 cause of buffer overflows. + */ + LAN78XX_DUMP_REG(MAC_CR), /* MAC Speed/Duplex */ + LAN78XX_DUMP_REG(USB_CFG0), /* USB Speed/Burst Cap Enable */ + LAN78XX_DUMP_REG(BURST_CAP), /* USB Burst Size Limit */ + LAN78XX_DUMP_REG(BULK_IN_DLY), /* Inter-packet delay settings */ + + /* --- Debug Pointers --- + * Internal read/write pointers for the FIFO RAM. + * Helps detect if the hardware pointer logic has wrapped or frozen. + */ + LAN78XX_DUMP_REG(DP_SEL), + LAN78XX_DUMP_REG(DP_CMD), +}; + +static const struct lan78xx_reg_map lan78xx_err_regs[] =3D { + /* --- Interrupt Status --- + * The "Smoking Gun". Reveals if the error was triggered by: + * - MAC_ERR_INT: Internal logic overflow/underflow. + * - PHY_INT: Link loss or signal degradation. + * - TDFO/RDFO: FIFO Overflows (redundant but explicit). + */ + LAN78XX_DUMP_REG(INT_STS), + LAN78XX_DUMP_REG(INT_EP_CTL), + + /* --- System Health --- + * Check for invalid power states (D3 while active) or stuck resets. + * HW_CFG also contains the "Soft Reset" status bit. + */ + LAN78XX_DUMP_REG(HW_CFG), + LAN78XX_DUMP_REG(PMT_CTL), + + /* --- Bus Integrity --- + * USB_CFG1 contains Low Power Mode (LPM) and Suspend guards. + */ + LAN78XX_DUMP_REG(USB_CFG0), + LAN78XX_DUMP_REG(USB_CFG1), + + /* --- MAC Status --- + * Verify if the receiver is actually enabled (RXEN) and if + * filtering (Promiscuous/Multicast) is set as expected. + */ + LAN78XX_DUMP_REG(MAC_CR), + LAN78XX_DUMP_REG(MAC_RX), +}; + static struct sk_buff *lan78xx_get_buf(struct sk_buff_head *buf_pool) { if (skb_queue_empty(buf_pool)) @@ -831,12 +964,67 @@ static void lan78xx_check_stat_rollover(struct lan78x= x_net *dev, memcpy(&dev->stats.saved, stats, sizeof(struct lan78xx_statstage)); } =20 +static void lan78xx_check_stat_anomalies(struct lan78xx_net *dev) +{ + u64 delta_pause, delta_rx, delta_hw_drop, delta_sw_rx; + struct lan78xx_dump_ctx ctx =3D {0}; + struct lan78xx_stat_snapshot now; + const char *anomaly_msg =3D NULL; + + /* 1. Capture "Now" (Atomic-ish collection) */ + now.time =3D ktime_get_real(); + + mutex_lock(&dev->stats.access_lock); + now.tx_pause_total =3D dev->stats.curr_stat.tx_pause_frames; + now.rx_total_frames =3D dev->stats.curr_stat.rx_unicast_frames + + dev->stats.curr_stat.rx_broadcast_frames + + dev->stats.curr_stat.rx_multicast_frames; + now.rx_hw_drop_total =3D dev->stats.curr_stat.rx_dropped_frames; + now.tx_unicast_total =3D dev->stats.curr_stat.tx_unicast_frames; + mutex_unlock(&dev->stats.access_lock); + + now.rx_sw_packets_total =3D dev->net->stats.rx_packets; + + delta_pause =3D now.tx_pause_total - dev->snapshot.tx_pause_total; + delta_rx =3D now.rx_total_frames - dev->snapshot.rx_total_frames; + delta_hw_drop =3D now.rx_hw_drop_total - dev->snapshot.rx_hw_drop_total; + delta_sw_rx =3D now.rx_sw_packets_total - dev->snapshot.rx_sw_packets_tot= al; + + now.last_delta_pause =3D (u32)delta_pause; + now.last_delta_drops =3D (u32)delta_hw_drop; + + dev->snapshot =3D now; + + if (delta_pause > LAN78XX_STALL_PAUSE_THRESH && delta_rx =3D=3D 0) { + anomaly_msg =3D "Stall: Pause Storm & No RX"; + } else if (delta_hw_drop > LAN78XX_LIVELOCK_DROP_THRESH && + delta_hw_drop > (delta_sw_rx * LAN78XX_LIVELOCK_DROP_RATIO)) { + anomaly_msg =3D "Stall: RX Livelock Detected (Excessive Drop Ratio)"; + } + + if (!anomaly_msg) + return; + + /* 5. Reporting */ + ctx.msg =3D anomaly_msg; + ctx.ts =3D now.time; + ctx.fifo.delta_pause =3D delta_pause; + ctx.fifo.delta_rx =3D delta_rx; + ctx.fifo.delta_hw_drop =3D delta_hw_drop; + ctx.fifo.delta_sw_rx =3D delta_sw_rx; + + netdev_warn(dev->net, "%s (HW Drops: +%llu, SW RX: +%llu)\n", + ctx.msg, delta_hw_drop, delta_sw_rx); + + devlink_health_report(dev->fifo_reporter, ctx.msg, &ctx); +} + static void lan78xx_update_stats(struct lan78xx_net *dev) { + struct lan78xx_statstage lan78xx_stats; u32 *p, *count, *max; u64 *data; int i; - struct lan78xx_statstage lan78xx_stats; =20 if (usb_autopm_get_interface(dev->intf) < 0) return; @@ -856,6 +1044,8 @@ static void lan78xx_update_stats(struct lan78xx_net *d= ev) =20 mutex_unlock(&dev->stats.access_lock); =20 + lan78xx_check_stat_anomalies(dev); + usb_autopm_put_interface(dev->intf); } =20 @@ -1625,6 +1815,18 @@ static void lan78xx_status(struct lan78xx_net *dev, = struct urb *urb) =20 if (dev->domain_data.phyirq > 0) generic_handle_irq_safe(dev->domain_data.phyirq); + } else if (intdata & (INT_ENP_TDFO_INT | INT_ENP_TDFU_INT | + INT_ENP_RDFO_INT | INT_ENP_MAC_ERR_INT)) { + struct lan78xx_dump_ctx ctx =3D {0}; + + ctx.msg =3D "HW Interrupt Error"; + ctx.ts =3D ktime_get_real(); + ctx.err.int_sts =3D intdata; + + netdev_warn(dev->net, "HW Error detected: 0x%08x, triggering health repo= rt\n", + intdata); + + devlink_health_report(dev->internal_err_reporter, ctx.msg, &ctx); } else { netdev_warn(dev->net, "unexpected interrupt: 0x%08x\n", intdata); @@ -4705,6 +4907,148 @@ static void intr_complete(struct urb *urb) } } =20 +static int lan78xx_dump_regs(struct lan78xx_net *dev, struct devlink_fmsg = *fmsg, + const struct lan78xx_reg_map *map, size_t count) +{ + int ret, i; + u32 val; + + for (i =3D 0; i < count; i++) { + ret =3D lan78xx_read_reg(dev, map[i].reg, &val); + if (ret) + return ret; + devlink_fmsg_u32_pair_put(fmsg, map[i].name, val); + } + return 0; +} + +static int lan78xx_fifo_dump(struct devlink_health_reporter *reporter, + struct devlink_fmsg *fmsg, void *priv_ctx, + struct netlink_ext_ack *extack) +{ + struct lan78xx_net *dev =3D devlink_health_reporter_priv(reporter); + struct lan78xx_dump_ctx *ctx =3D priv_ctx; + + /* 1. Context Snapshot: + * Dump the specific counters that triggered the threshold. + * Registers may have changed since the decision was made. + */ + if (ctx) { + devlink_fmsg_string_pair_put(fmsg, "trigger_reason", ctx->msg); + devlink_fmsg_u64_pair_put(fmsg, "timestamp_ns", + ktime_to_ns(ctx->ts)); + + devlink_fmsg_obj_nest_start(fmsg); + devlink_fmsg_u64_pair_put(fmsg, "trigger_delta_pause", + ctx->fifo.delta_pause); + devlink_fmsg_u64_pair_put(fmsg, "trigger_delta_rx", + ctx->fifo.delta_rx); + devlink_fmsg_u64_pair_put(fmsg, "trigger_delta_hw_drop", + ctx->fifo.delta_hw_drop); + devlink_fmsg_u64_pair_put(fmsg, "trigger_delta_sw_rx", + ctx->fifo.delta_sw_rx); + devlink_fmsg_obj_nest_end(fmsg); + } + + /* USB Speed is critical for interpreting throughput/stall issues */ + devlink_fmsg_u8_pair_put(fmsg, "usb_speed_enum", dev->udev->speed); + + /* 2. Live Register Dump */ + return lan78xx_dump_regs(dev, fmsg, lan78xx_fifo_regs, + ARRAY_SIZE(lan78xx_fifo_regs)); +} + +static int lan78xx_internal_err_dump(struct devlink_health_reporter *repor= ter, + struct devlink_fmsg *fmsg, void *priv_ctx, + struct netlink_ext_ack *extack) +{ + struct lan78xx_net *dev =3D devlink_health_reporter_priv(reporter); + struct lan78xx_dump_ctx *ctx =3D priv_ctx; + + /* Interrupt status is "write-1-to-clear" or cleared on read. + * We must dump the value seen by the ISR, not the current register + * value. + */ + if (ctx) { + devlink_fmsg_string_pair_put(fmsg, "trigger_reason", ctx->msg); + devlink_fmsg_u64_pair_put(fmsg, "timestamp_ns", + ktime_to_ns(ctx->ts)); + + devlink_fmsg_u32_pair_put(fmsg, "trigger_int_sts", + ctx->err.int_sts); + devlink_fmsg_u32_pair_put(fmsg, "trigger_int_enp", + ctx->err.int_enp); + } + + return lan78xx_dump_regs(dev, fmsg, lan78xx_err_regs, + ARRAY_SIZE(lan78xx_err_regs)); +} + +static const struct devlink_health_reporter_ops lan78xx_fifo_ops =3D { + .name =3D "fifo", + .dump =3D lan78xx_fifo_dump, +}; + +static const struct devlink_health_reporter_ops lan78xx_internal_err_ops = =3D { + .name =3D "internal_err", + .dump =3D lan78xx_internal_err_dump, +}; + +static int lan78xx_health_init(struct lan78xx_net *dev) +{ + dev->fifo_reporter =3D devlink_health_reporter_create(dev->devlink, + &lan78xx_fifo_ops, + dev); + if (IS_ERR(dev->fifo_reporter)) { + netdev_warn(dev->net, "Failed to create fifo reporter\n"); + + return PTR_ERR(dev->fifo_reporter); + } + + dev->internal_err_reporter =3D + devlink_health_reporter_create(dev->devlink, + &lan78xx_internal_err_ops, dev); + if (IS_ERR(dev->internal_err_reporter)) { + netdev_warn(dev->net, "Failed to create internal_err reporter\n"); + devlink_health_reporter_destroy(dev->fifo_reporter); + + return PTR_ERR(dev->internal_err_reporter); + } + + return 0; +} + +static void lan78xx_health_cleanup(struct lan78xx_net *dev) +{ + devlink_health_reporter_destroy(dev->fifo_reporter); + devlink_health_reporter_destroy(dev->internal_err_reporter); +} + +static int lan78xx_devlink_info_get(struct devlink *devlink, + struct devlink_info_req *req, + struct netlink_ext_ack *extack) +{ + struct lan78xx_devlink_priv *dl_priv =3D devlink_priv(devlink); + struct lan78xx_net *dev =3D dl_priv->dev; + char buf[16]; + + snprintf(buf, sizeof(buf), "0x%04X", dev->chipid); + devlink_info_version_fixed_put(req, + DEVLINK_INFO_VERSION_GENERIC_ASIC_ID, + buf); + + snprintf(buf, sizeof(buf), "0x%04X", dev->chiprev); + devlink_info_version_fixed_put(req, + DEVLINK_INFO_VERSION_GENERIC_ASIC_REV, + buf); + + return 0; +} + +static const struct devlink_ops lan78xx_devlink_ops =3D { + .info_get =3D lan78xx_devlink_info_get, +}; + static void lan78xx_disconnect(struct usb_interface *intf) { struct lan78xx_net *dev; @@ -4719,6 +5063,13 @@ static void lan78xx_disconnect(struct usb_interface = *intf) udev =3D interface_to_usbdev(intf); net =3D dev->net; =20 + lan78xx_health_cleanup(dev); + if (dev->devlink) { + devlink_unregister(dev->devlink); + devlink_free(dev->devlink); + dev->devlink =3D NULL; + } + rtnl_lock(); phylink_stop(dev->phylink); phylink_disconnect_phy(dev->phylink); @@ -4749,6 +5100,30 @@ static void lan78xx_disconnect(struct usb_interface = *intf) static void lan78xx_tx_timeout(struct net_device *net, unsigned int txqueu= e) { struct lan78xx_net *dev =3D netdev_priv(net); + struct lan78xx_dump_ctx ctx =3D {0}; + s64 diff_ms; + + /* Calculate time since last health check */ + ctx.ts =3D ktime_get_real(); + diff_ms =3D ktime_ms_delta(ctx.ts, dev->snapshot.time); + + /* We rely on the trend data captured during the last valid stat update + * to infer the system state before the crash. + */ + if (dev->snapshot.last_delta_pause > LAN78XX_STALL_PAUSE_THRESH) + ctx.msg =3D "TX Timeout (Flow Control Storm?)"; + else if (dev->snapshot.last_delta_drops > LAN78XX_TX_TIMEOUT_DROP_THRESH) + ctx.msg =3D "TX Timeout (FIFO Drop Storm?)"; + else + ctx.msg =3D "TX Timeout"; + + ctx.fifo.delta_pause =3D dev->snapshot.last_delta_pause; + ctx.fifo.delta_hw_drop =3D dev->snapshot.last_delta_drops; + + netdev_warn(dev->net, "%s (Last stat update: %lld ms ago)\n", + ctx.msg, diff_ms); + + devlink_health_report(dev->fifo_reporter, ctx.msg, &ctx); =20 unlink_urbs(dev, &dev->txq); napi_schedule(&dev->napi); @@ -5157,6 +5532,17 @@ static int lan78xx_probe(struct usb_interface *intf, pm_runtime_set_autosuspend_delay(&udev->dev, DEFAULT_AUTOSUSPEND_DELAY); =20 + dev->devlink =3D devlink_alloc(&lan78xx_devlink_ops, + sizeof(struct lan78xx_devlink_priv), + &udev->dev); + if (dev->devlink) { + struct lan78xx_devlink_priv *dl_priv =3D devlink_priv(dev->devlink); + + dl_priv->dev =3D dev; + devlink_register(dev->devlink); + lan78xx_health_init(dev); + } + return 0; =20 phy_uninit: --=20 2.47.3 From nobody Sun Feb 8 19:20:49 2026 Received: from metis.whiteo.stw.pengutronix.de (metis.whiteo.stw.pengutronix.de [185.203.201.7]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id B54BF1B0F19 for ; Fri, 23 Jan 2026 09:07:50 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=185.203.201.7 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1769159272; cv=none; b=Gm4sBYWeVqbuUejfy8BSePkEA+NJlhMG+XDS0InicRR0grawo8AQojxfo8V9jJiBztxaJ6EFc4Yh7MfLaOCMVATtNzAhaWX6I768qBzEb56OyG6oBlIajy0Yu0eoMbX0aSVDyQeNLzQlwVdGxfRDkyHUSeV+YcGfBlsT0m7sHf4= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1769159272; c=relaxed/simple; bh=9id+dPf4V4aFGOwRtmPbZkC/ulviMk3AceF/lZjHGPY=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=lo6ymSBKFYW7wz66Vo0mFlpfBMKwbO8Gz7EsfHCs3i9HeZUehjhbZn7y9N2MX4oXHOKRhHAZmC5E4giluu8yGgEDq7BhJzVNmT3K33e6e7VLnJqPkWqRHwGG6DWbZ2kfIbcOhEZ9R4zwTQuTA4cX1cVwK0bP3vWlp1bS0BdKAoo= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=pengutronix.de; spf=pass smtp.mailfrom=pengutronix.de; arc=none smtp.client-ip=185.203.201.7 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=pengutronix.de Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=pengutronix.de Received: from drehscheibe.grey.stw.pengutronix.de ([2a0a:edc0:0:c01:1d::a2]) by metis.whiteo.stw.pengutronix.de with esmtps (TLS1.3:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.92) (envelope-from ) id 1vjD8q-00027H-59; Fri, 23 Jan 2026 10:07:44 +0100 Received: from dude04.red.stw.pengutronix.de ([2a0a:edc0:0:1101:1d::ac] helo=dude04) by drehscheibe.grey.stw.pengutronix.de with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.96) (envelope-from ) id 1vjD8p-00244y-0Y; Fri, 23 Jan 2026 10:07:42 +0100 Received: from ore by dude04 with local (Exim 4.98.2) (envelope-from ) id 1vjD8o-00000006ZWs-1oaF; Fri, 23 Jan 2026 10:07:42 +0100 From: Oleksij Rempel To: "David S. Miller" , Eric Dumazet , Jakub Kicinski , Paolo Abeni , Andrew Lunn , Thangaraj Samynathan , Rengarajan Sundararajan Cc: Oleksij Rempel , kernel@pengutronix.de, linux-kernel@vger.kernel.org, netdev@vger.kernel.org, UNGLinuxDriver@microchip.com Subject: [RFC PATCH 2/4] net: lan78xx: Implement FIFO stall recovery via lite reset Date: Fri, 23 Jan 2026 10:07:38 +0100 Message-ID: <20260123090741.1566469-3-o.rempel@pengutronix.de> X-Mailer: git-send-email 2.47.3 In-Reply-To: <20260123090741.1566469-1-o.rempel@pengutronix.de> References: <20260123090741.1566469-1-o.rempel@pengutronix.de> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-SA-Exim-Connect-IP: 2a0a:edc0:0:c01:1d::a2 X-SA-Exim-Mail-From: ore@pengutronix.de X-SA-Exim-Scanned: No (on metis.whiteo.stw.pengutronix.de); SAEximRunCond expanded to false X-PTX-Original-Recipient: linux-kernel@vger.kernel.org Content-Type: text/plain; charset="utf-8" Implement the recovery logic for the devlink FIFO health reporter. The recovery callback triggers a full hardware Lite Reset using lan78xx_reset(). This is a fast, reliable way to restore traffic in unattended embedded deployments when a FIFO stall is detected. Signed-off-by: Oleksij Rempel --- drivers/net/usb/lan78xx.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/drivers/net/usb/lan78xx.c b/drivers/net/usb/lan78xx.c index 221be42e06f4..9dadca4101bc 100644 --- a/drivers/net/usb/lan78xx.c +++ b/drivers/net/usb/lan78xx.c @@ -4984,8 +4984,18 @@ static int lan78xx_internal_err_dump(struct devlink_= health_reporter *reporter, ARRAY_SIZE(lan78xx_err_regs)); } =20 +static int lan78xx_fifo_recover(struct devlink_health_reporter *reporter, + void *priv_ctx, struct netlink_ext_ack *extack) +{ + struct lan78xx_net *dev =3D devlink_health_reporter_priv(reporter); + + netdev_warn(dev->net, "Recovering from FIFO stall via Lite Reset\n"); + return lan78xx_reset(dev); +} + static const struct devlink_health_reporter_ops lan78xx_fifo_ops =3D { .name =3D "fifo", + .recover =3D lan78xx_fifo_recover, .dump =3D lan78xx_fifo_dump, }; =20 --=20 2.47.3 From nobody Sun Feb 8 19:20:49 2026 Received: from metis.whiteo.stw.pengutronix.de (metis.whiteo.stw.pengutronix.de [185.203.201.7]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 2D0F92E7F2C for ; Fri, 23 Jan 2026 09:07:51 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=185.203.201.7 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1769159272; cv=none; b=RZAbPLnWeFWOCZ3orWkUrVWlW4/eaklmqbazX2ZlieipC3/8rxuPsI7IA5SHmqDKjeNdW2RejA5Jz2R9Q+ZEjWNxFK5s0GgmMJqBhZyAfW7aCkzvgQecqgENk/CM94uRbQcdPWudluhnflVvx2+2vjrAF4gh7fRDDUIfoyiXH+w= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1769159272; c=relaxed/simple; bh=r5XEHOJhn1h9gr3DcBd/TQpuCeRA/qz6bGYE0Gsm3I0=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=Y9uyGmuxh/kCnGftB7ZfMC4sA8riON2vqnUtCMQcAt4AvCmUJVlxYlNYuKApLIzDkN0oT6Xf+BMCnYeGDSLW1y4sMYQB8KQ3RjpEpuR1TxS6jdIyp3KxwpHfgZ/D1TFei6mRoE/V7vKfGVeG1GXZaiZMGM8L6ttH5dUlQyUk3As= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=pengutronix.de; spf=pass smtp.mailfrom=pengutronix.de; arc=none smtp.client-ip=185.203.201.7 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=pengutronix.de Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=pengutronix.de Received: from drehscheibe.grey.stw.pengutronix.de ([2a0a:edc0:0:c01:1d::a2]) by metis.whiteo.stw.pengutronix.de with esmtps (TLS1.3:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.92) (envelope-from ) id 1vjD8q-00027J-59; Fri, 23 Jan 2026 10:07:44 +0100 Received: from dude04.red.stw.pengutronix.de ([2a0a:edc0:0:1101:1d::ac] helo=dude04) by drehscheibe.grey.stw.pengutronix.de with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.96) (envelope-from ) id 1vjD8p-00244z-0l; Fri, 23 Jan 2026 10:07:42 +0100 Received: from ore by dude04 with local (Exim 4.98.2) (envelope-from ) id 1vjD8o-00000006ZX2-1uae; Fri, 23 Jan 2026 10:07:42 +0100 From: Oleksij Rempel To: "David S. Miller" , Eric Dumazet , Jakub Kicinski , Paolo Abeni , Andrew Lunn , Thangaraj Samynathan , Rengarajan Sundararajan Cc: Oleksij Rempel , kernel@pengutronix.de, linux-kernel@vger.kernel.org, netdev@vger.kernel.org, UNGLinuxDriver@microchip.com Subject: [RFC PATCH 3/4] net: lan78xx: Enhance health reporting with workqueue and detailed flow control stats Date: Fri, 23 Jan 2026 10:07:39 +0100 Message-ID: <20260123090741.1566469-4-o.rempel@pengutronix.de> X-Mailer: git-send-email 2.47.3 In-Reply-To: <20260123090741.1566469-1-o.rempel@pengutronix.de> References: <20260123090741.1566469-1-o.rempel@pengutronix.de> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-SA-Exim-Connect-IP: 2a0a:edc0:0:c01:1d::a2 X-SA-Exim-Mail-From: ore@pengutronix.de X-SA-Exim-Scanned: No (on metis.whiteo.stw.pengutronix.de); SAEximRunCond expanded to false X-PTX-Original-Recipient: linux-kernel@vger.kernel.org Content-Type: text/plain; charset="utf-8" Refactor the health reporting to: 1. Introduce a dedicated workqueue for TX timeouts. This prevents calling devlink_health_report (which may sleep) from an atomic context (netdev tx_timeout). 2. Update statistics tracking and reporting context to separate TX Pause and RX Pause frames, allowing finer-grained stall analysis (local vs. link partner induced flow control storm). 3. Change the devlink recovery function to call phylink_mac_change(false). This leverages the newly robust link_down path which performs the necessary locking and conditional Lite Reset. Signed-off-by: Oleksij Rempel --- drivers/net/usb/lan78xx.c | 133 +++++++++++++++++++++++++------------- 1 file changed, 87 insertions(+), 46 deletions(-) diff --git a/drivers/net/usb/lan78xx.c b/drivers/net/usb/lan78xx.c index 9dadca4101bc..316a3a8d0534 100644 --- a/drivers/net/usb/lan78xx.c +++ b/drivers/net/usb/lan78xx.c @@ -425,15 +425,36 @@ struct lan78xx_stat_snapshot { ktime_t time; =20 u64 tx_pause_total; + u64 rx_pause_total; u64 tx_unicast_total; u64 rx_total_frames; u64 rx_hw_drop_total; u64 rx_sw_packets_total; =20 - u32 last_delta_pause; + u32 last_delta_rx_pause; + u32 last_delta_tx_pause; u32 last_delta_drops; }; =20 +struct lan78xx_dump_ctx { + const char *msg; + ktime_t ts; /* Timestamp of detection */ + + union { + struct { + u64 delta_tx_pause; + u64 delta_rx_pause; + u64 delta_rx; + u64 delta_hw_drop; + u64 delta_sw_rx; + } fifo; + struct { + u32 int_sts; /* The ISR's view of INT_STS */ + u32 int_enp; /* The ISR's view of INT_ENP_CTL */ + } err; + }; +}; + struct irq_domain_data { struct irq_domain *irqdomain; unsigned int phyirq; @@ -505,27 +526,10 @@ struct lan78xx_net { struct devlink_health_reporter *fifo_reporter; struct devlink_health_reporter *internal_err_reporter; struct lan78xx_stat_snapshot snapshot; + struct work_struct tx_timeout_work; + struct lan78xx_dump_ctx timeout_ctx; }; =20 -struct lan78xx_dump_ctx { - const char *msg; - ktime_t ts; /* Timestamp of detection */ - - union { - struct { - u64 delta_pause; - u64 delta_rx; - u64 delta_hw_drop; - u64 delta_sw_rx; - } fifo; - struct { - u32 int_sts; /* The ISR's view of INT_STS */ - u32 int_enp; /* The ISR's view of INT_ENP_CTL */ - } err; - }; -}; - -/* Register Dump Map Structure */ struct lan78xx_reg_map { u32 reg; const char *name; @@ -966,7 +970,7 @@ static void lan78xx_check_stat_rollover(struct lan78xx_= net *dev, =20 static void lan78xx_check_stat_anomalies(struct lan78xx_net *dev) { - u64 delta_pause, delta_rx, delta_hw_drop, delta_sw_rx; + u64 delta_tx_pause, delta_rx_pause, delta_rx, delta_hw_drop, delta_sw_rx; struct lan78xx_dump_ctx ctx =3D {0}; struct lan78xx_stat_snapshot now; const char *anomaly_msg =3D NULL; @@ -976,6 +980,7 @@ static void lan78xx_check_stat_anomalies(struct lan78xx= _net *dev) =20 mutex_lock(&dev->stats.access_lock); now.tx_pause_total =3D dev->stats.curr_stat.tx_pause_frames; + now.rx_pause_total =3D dev->stats.curr_stat.rx_pause_frames; now.rx_total_frames =3D dev->stats.curr_stat.rx_unicast_frames + dev->stats.curr_stat.rx_broadcast_frames + dev->stats.curr_stat.rx_multicast_frames; @@ -985,17 +990,19 @@ static void lan78xx_check_stat_anomalies(struct lan78= xx_net *dev) =20 now.rx_sw_packets_total =3D dev->net->stats.rx_packets; =20 - delta_pause =3D now.tx_pause_total - dev->snapshot.tx_pause_total; + delta_tx_pause =3D now.tx_pause_total - dev->snapshot.tx_pause_total; + delta_rx_pause =3D now.rx_pause_total - dev->snapshot.rx_pause_total; delta_rx =3D now.rx_total_frames - dev->snapshot.rx_total_frames; delta_hw_drop =3D now.rx_hw_drop_total - dev->snapshot.rx_hw_drop_total; delta_sw_rx =3D now.rx_sw_packets_total - dev->snapshot.rx_sw_packets_tot= al; =20 - now.last_delta_pause =3D (u32)delta_pause; + now.last_delta_tx_pause =3D (u32)delta_tx_pause; + now.last_delta_rx_pause =3D (u32)delta_rx_pause; now.last_delta_drops =3D (u32)delta_hw_drop; =20 dev->snapshot =3D now; =20 - if (delta_pause > LAN78XX_STALL_PAUSE_THRESH && delta_rx =3D=3D 0) { + if (delta_tx_pause > LAN78XX_STALL_PAUSE_THRESH && delta_rx =3D=3D 0) { anomaly_msg =3D "Stall: Pause Storm & No RX"; } else if (delta_hw_drop > LAN78XX_LIVELOCK_DROP_THRESH && delta_hw_drop > (delta_sw_rx * LAN78XX_LIVELOCK_DROP_RATIO)) { @@ -1008,10 +1015,11 @@ static void lan78xx_check_stat_anomalies(struct lan= 78xx_net *dev) /* 5. Reporting */ ctx.msg =3D anomaly_msg; ctx.ts =3D now.time; - ctx.fifo.delta_pause =3D delta_pause; - ctx.fifo.delta_rx =3D delta_rx; + ctx.fifo.delta_tx_pause =3D delta_tx_pause; + ctx.fifo.delta_rx_pause =3D delta_rx_pause; + ctx.fifo.delta_rx =3D delta_rx; ctx.fifo.delta_hw_drop =3D delta_hw_drop; - ctx.fifo.delta_sw_rx =3D delta_sw_rx; + ctx.fifo.delta_sw_rx =3D delta_sw_rx; =20 netdev_warn(dev->net, "%s (HW Drops: +%llu, SW RX: +%llu)\n", ctx.msg, delta_hw_drop, delta_sw_rx); @@ -2495,6 +2503,24 @@ static void lan78xx_mac_config(struct phylink_config= *config, unsigned int mode, ERR_PTR(ret)); } =20 +static int lan78xx_configure_flowcontrol(struct lan78xx_net *dev, + bool tx_pause, bool rx_pause); +static int lan78xx_reset(struct lan78xx_net *dev); + +static void lan78xx_dump_status(struct lan78xx_net *dev, const char *msg) +{ + u32 int_sts, mac_tx, fct_tx_ctl, mac_rx, fct_rx_ctl; + + lan78xx_read_reg(dev, INT_STS, &int_sts); + lan78xx_read_reg(dev, MAC_TX, &mac_tx); + lan78xx_read_reg(dev, FCT_TX_CTL, &fct_tx_ctl); + lan78xx_read_reg(dev, MAC_RX, &mac_rx); + lan78xx_read_reg(dev, FCT_RX_CTL, &fct_rx_ctl); + + netdev_info(dev->net, "[%s] INT_STS: 0x%08x, MAC_TX: 0x%08x, FCT_TX: 0x%0= 8x, MAC_RX: 0x%08x, FCT_RX: 0x%08x\n", + msg, int_sts, mac_tx, fct_tx_ctl, mac_rx, fct_rx_ctl); +} + static void lan78xx_mac_link_down(struct phylink_config *config, unsigned int mode, phy_interface_t interface) { @@ -4939,8 +4965,10 @@ static int lan78xx_fifo_dump(struct devlink_health_r= eporter *reporter, ktime_to_ns(ctx->ts)); =20 devlink_fmsg_obj_nest_start(fmsg); - devlink_fmsg_u64_pair_put(fmsg, "trigger_delta_pause", - ctx->fifo.delta_pause); + devlink_fmsg_u64_pair_put(fmsg, "trigger_delta_tx_pause", + ctx->fifo.delta_tx_pause); + devlink_fmsg_u64_pair_put(fmsg, "trigger_delta_rx_pause", + ctx->fifo.delta_rx_pause); devlink_fmsg_u64_pair_put(fmsg, "trigger_delta_rx", ctx->fifo.delta_rx); devlink_fmsg_u64_pair_put(fmsg, "trigger_delta_hw_drop", @@ -4989,8 +5017,9 @@ static int lan78xx_fifo_recover(struct devlink_health= _reporter *reporter, { struct lan78xx_net *dev =3D devlink_health_reporter_priv(reporter); =20 - netdev_warn(dev->net, "Recovering from FIFO stall via Lite Reset\n"); - return lan78xx_reset(dev); + netdev_warn(dev->net, "Recovering via Lite Reset\n"); + phylink_mac_change(dev->phylink, false); + return 0; } =20 static const struct devlink_health_reporter_ops lan78xx_fifo_ops =3D { @@ -5075,6 +5104,7 @@ static void lan78xx_disconnect(struct usb_interface *= intf) =20 lan78xx_health_cleanup(dev); if (dev->devlink) { + cancel_work_sync(&dev->tx_timeout_work); devlink_unregister(dev->devlink); devlink_free(dev->devlink); dev->devlink =3D NULL; @@ -5107,36 +5137,45 @@ static void lan78xx_disconnect(struct usb_interface= *intf) usb_put_dev(udev); } =20 +static void lan78xx_tx_timeout_work(struct work_struct *work) +{ + struct lan78xx_net *dev =3D container_of(work, struct lan78xx_net, + tx_timeout_work); + + devlink_health_report(dev->fifo_reporter, dev->timeout_ctx.msg, + &dev->timeout_ctx); +} + static void lan78xx_tx_timeout(struct net_device *net, unsigned int txqueu= e) { struct lan78xx_net *dev =3D netdev_priv(net); - struct lan78xx_dump_ctx ctx =3D {0}; - s64 diff_ms; + s64 diff_ms =3D 0; =20 /* Calculate time since last health check */ - ctx.ts =3D ktime_get_real(); - diff_ms =3D ktime_ms_delta(ctx.ts, dev->snapshot.time); + dev->timeout_ctx.ts =3D ktime_get_real(); + diff_ms =3D ktime_ms_delta(dev->timeout_ctx.ts, dev->snapshot.time); =20 /* We rely on the trend data captured during the last valid stat update * to infer the system state before the crash. */ - if (dev->snapshot.last_delta_pause > LAN78XX_STALL_PAUSE_THRESH) - ctx.msg =3D "TX Timeout (Flow Control Storm?)"; + if (dev->snapshot.last_delta_rx_pause > LAN78XX_STALL_PAUSE_THRESH) + dev->timeout_ctx.msg =3D "TX Timeout (Link Partner Pause Storm?)"; + else if (dev->snapshot.last_delta_tx_pause > LAN78XX_STALL_PAUSE_THRESH) + dev->timeout_ctx.msg =3D "TX Timeout (Local Flow Control Storm?)"; else if (dev->snapshot.last_delta_drops > LAN78XX_TX_TIMEOUT_DROP_THRESH) - ctx.msg =3D "TX Timeout (FIFO Drop Storm?)"; + dev->timeout_ctx.msg =3D "TX Timeout (FIFO Drop Storm?)"; else - ctx.msg =3D "TX Timeout"; + dev->timeout_ctx.msg =3D "TX Timeout"; =20 - ctx.fifo.delta_pause =3D dev->snapshot.last_delta_pause; - ctx.fifo.delta_hw_drop =3D dev->snapshot.last_delta_drops; + dev->timeout_ctx.fifo.delta_rx_pause =3D dev->snapshot.last_delta_rx_paus= e; + dev->timeout_ctx.fifo.delta_tx_pause =3D dev->snapshot.last_delta_tx_paus= e; + dev->timeout_ctx.fifo.delta_hw_drop =3D dev->snapshot.last_delta_drops; =20 netdev_warn(dev->net, "%s (Last stat update: %lld ms ago)\n", - ctx.msg, diff_ms); + dev->timeout_ctx.msg, diff_ms); =20 - devlink_health_report(dev->fifo_reporter, ctx.msg, &ctx); - - unlink_urbs(dev, &dev->txq); - napi_schedule(&dev->napi); + /* Defer report to worker to avoid sleeping in atomic context */ + schedule_work(&dev->tx_timeout_work); } =20 static netdev_features_t lan78xx_features_check(struct sk_buff *skb, @@ -5542,6 +5581,8 @@ static int lan78xx_probe(struct usb_interface *intf, pm_runtime_set_autosuspend_delay(&udev->dev, DEFAULT_AUTOSUSPEND_DELAY); =20 + INIT_WORK(&dev->tx_timeout_work, lan78xx_tx_timeout_work); + dev->devlink =3D devlink_alloc(&lan78xx_devlink_ops, sizeof(struct lan78xx_devlink_priv), &udev->dev); --=20 2.47.3 From nobody Sun Feb 8 19:20:49 2026 Received: from metis.whiteo.stw.pengutronix.de (metis.whiteo.stw.pengutronix.de [185.203.201.7]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 4906A30DD30 for ; Fri, 23 Jan 2026 09:07:50 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=185.203.201.7 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1769159272; cv=none; b=N/S1bMdWKGxfUI2z95rbxokgoS3h3PRTLQ/bMS+KXhHQlc2FK7leYMBiwj5LEa3re2lKACTj7WCJ+bRY17L9aH633YImC1awqQbLbxlKXv8f7U7xAJw8ggwWyS+H0h/M5RmCWK5Bq84TaWfvE22pjyRzyozlIaqDLep7H+WlCMg= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1769159272; c=relaxed/simple; bh=/0cL2fuHdGpIi6bdL6UK4t+rDuKh3T0YQNJtD/9d/c0=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=jpks14i+beXpwJjZuthb50SYClRutY7fkazWQ+Fi5IfA2CAa8FKdqybIfeae7QEVwfesQwRUQzrgn0U58ZMNgLfVBf1+BF1iDyNsDWSl8LMb5gvvPsyxXmJYeAR+ayk8KrlGeHjnVi6Ltvg97nUzSEXLkuAB4zZ/aOXCmbp45Sw= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=pengutronix.de; spf=pass smtp.mailfrom=pengutronix.de; arc=none smtp.client-ip=185.203.201.7 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=pengutronix.de Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=pengutronix.de Received: from drehscheibe.grey.stw.pengutronix.de ([2a0a:edc0:0:c01:1d::a2]) by metis.whiteo.stw.pengutronix.de with esmtps (TLS1.3:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.92) (envelope-from ) id 1vjD8q-00027I-58; Fri, 23 Jan 2026 10:07:44 +0100 Received: from dude04.red.stw.pengutronix.de ([2a0a:edc0:0:1101:1d::ac] helo=dude04) by drehscheibe.grey.stw.pengutronix.de with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.96) (envelope-from ) id 1vjD8p-002451-0k; Fri, 23 Jan 2026 10:07:42 +0100 Received: from ore by dude04 with local (Exim 4.98.2) (envelope-from ) id 1vjD8o-00000006ZXC-20KC; Fri, 23 Jan 2026 10:07:42 +0100 From: Oleksij Rempel To: "David S. Miller" , Eric Dumazet , Jakub Kicinski , Paolo Abeni , Andrew Lunn , Thangaraj Samynathan , Rengarajan Sundararajan Cc: Oleksij Rempel , kernel@pengutronix.de, linux-kernel@vger.kernel.org, netdev@vger.kernel.org, UNGLinuxDriver@microchip.com Subject: [RFC PATCH 4/4] net: lan78xx: Add debugfs file for error injection testing Date: Fri, 23 Jan 2026 10:07:40 +0100 Message-ID: <20260123090741.1566469-5-o.rempel@pengutronix.de> X-Mailer: git-send-email 2.47.3 In-Reply-To: <20260123090741.1566469-1-o.rempel@pengutronix.de> References: <20260123090741.1566469-1-o.rempel@pengutronix.de> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-SA-Exim-Connect-IP: 2a0a:edc0:0:c01:1d::a2 X-SA-Exim-Mail-From: ore@pengutronix.de X-SA-Exim-Scanned: No (on metis.whiteo.stw.pengutronix.de); SAEximRunCond expanded to false X-PTX-Original-Recipient: linux-kernel@vger.kernel.org Content-Type: text/plain; charset="utf-8" Add a debugfs file (inject\_error) to allow users to trigger specific hardware errors (e.g., Burst Cap Violation, RX FIFO Overflow, USB PHY destabilization) for testing the newly introduced health and recovery mechanisms. Signed-off-by: Oleksij Rempel --- drivers/net/usb/lan78xx.c | 71 +++++++++++++++++++++++++++++++++++++++ drivers/net/usb/lan78xx.h | 4 +++ 2 files changed, 75 insertions(+) diff --git a/drivers/net/usb/lan78xx.c b/drivers/net/usb/lan78xx.c index 316a3a8d0534..ae721025cf3d 100644 --- a/drivers/net/usb/lan78xx.c +++ b/drivers/net/usb/lan78xx.c @@ -3,6 +3,7 @@ * Copyright (C) 2015 Microchip Technology */ #include +#include #include #include #include @@ -519,6 +520,8 @@ struct lan78xx_net { =20 struct irq_domain_data domain_data; =20 + struct dentry *debugfs_pdev; + struct phylink *phylink; struct phylink_config phylink_config; =20 @@ -5088,6 +5091,68 @@ static const struct devlink_ops lan78xx_devlink_ops = =3D { .info_get =3D lan78xx_devlink_info_get, }; =20 +static ssize_t lan78xx_inject_write(struct file *file, const char __user *= user_buf, + size_t count, loff_t *ppos) +{ + struct lan78xx_net *dev =3D file->private_data; + char buf[32]; + int val, ret; + u32 reg_val; + + if (count >=3D sizeof(buf)) + return -EINVAL; + + if (copy_from_user(buf, user_buf, count)) + return -EFAULT; + buf[count] =3D 0; + + if (kstrtoint(buf, 0, &val)) + return -EINVAL; + + switch (val) { + case 1: /* Trigger Burst Cap Violation (Hang UTX) */ + /* Enable Burst Cap Enforcement */ + ret =3D lan78xx_read_reg(dev, USB_CFG0, ®_val); + if (ret < 0) + return ret; + reg_val |=3D USB_CFG_BCE_; + lan78xx_write_reg(dev, USB_CFG0, reg_val); + + /* Set illegal Burst Cap size (512 bytes < Max Frame) */ + lan78xx_write_reg(dev, BURST_CAP, 0x01); + break; + + case 2: /* Trigger RX FIFO Overflow (Hold UTX in Reset) */ + ret =3D lan78xx_read_reg(dev, USB_CFG0, ®_val); + if (ret < 0) + return ret; + reg_val |=3D USB_CFG0_UTX_RESET_; + lan78xx_write_reg(dev, USB_CFG0, reg_val); + break; + + case 3: /* Destabilize USB PHY (Invalid HS State) */ + ret =3D lan78xx_read_reg(dev, LAN78XX_USB2_TEST_REG, ®_val); + if (ret < 0) + return ret; + /* Set bits 15:14 to '10' (Binary) - Defined as "Invalid combination" */ + reg_val &=3D ~(0x3 << 14); + reg_val |=3D (0x2 << 14); + lan78xx_write_reg(dev, LAN78XX_USB2_TEST_REG, reg_val); + break; + + default: + return -EINVAL; + } + + return count; +} + +static const struct file_operations lan78xx_inject_fops =3D { + .open =3D simple_open, + .write =3D lan78xx_inject_write, + .llseek =3D default_llseek, +}; + static void lan78xx_disconnect(struct usb_interface *intf) { struct lan78xx_net *dev; @@ -5102,6 +5167,8 @@ static void lan78xx_disconnect(struct usb_interface *= intf) udev =3D interface_to_usbdev(intf); net =3D dev->net; =20 + debugfs_remove_recursive(dev->debugfs_pdev); + lan78xx_health_cleanup(dev); if (dev->devlink) { cancel_work_sync(&dev->tx_timeout_work); @@ -5594,6 +5661,10 @@ static int lan78xx_probe(struct usb_interface *intf, lan78xx_health_init(dev); } =20 + dev->debugfs_pdev =3D debugfs_create_dir(netdev_name(netdev), NULL); + debugfs_create_file("inject_error", 0200, dev->debugfs_pdev, dev, + &lan78xx_inject_fops); + return 0; =20 phy_uninit: diff --git a/drivers/net/usb/lan78xx.h b/drivers/net/usb/lan78xx.h index 968e5e5faee0..16666a998441 100644 --- a/drivers/net/usb/lan78xx.h +++ b/drivers/net/usb/lan78xx.h @@ -366,6 +366,7 @@ #define USB_CFG_MAX_DEV_SPEED_SS_ (0x00008000) #define USB_CFG_MAX_DEV_SPEED_HS_ (0x00000000) #define USB_CFG_MAX_DEV_SPEED_FS_ (0x00002000) +#define USB_CFG0_UTX_RESET_ (0x00000400) #define USB_CFG_PHY_BOOST_MASK_ (0x00000180) #define USB_CFG_PHY_BOOST_PLUS_12_ (0x00000180) #define USB_CFG_PHY_BOOST_PLUS_8_ (0x00000100) @@ -876,4 +877,7 @@ #define OTP_TPVSR_VAL (OTP_BASE_ADDR + 4 * 0x3A) #define OTP_TPVHR_VAL (OTP_BASE_ADDR + 4 * 0x3B) #define OTP_TPVSA_VAL (OTP_BASE_ADDR + 4 * 0x3C) + +#define LAN78XX_USB2_TEST_REG (0x12C4) + #endif /* _LAN78XX_H */ --=20 2.47.3