]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
net: usb: lan78xx: restore VLAN and hash filters after link up
authorNicolai Buchwitz <nb@tipi-net.de>
Mon, 22 Jun 2026 10:29:11 +0000 (12:29 +0200)
committerJakub Kicinski <kuba@kernel.org>
Tue, 23 Jun 2026 21:50:06 +0000 (14:50 -0700)
Configured VLANs intermittently stop receiving traffic after a link
down/up cycle, e.g. when the network cable is unplugged and plugged back
in. VLAN filtering stays enabled but all VLAN-tagged frames are dropped
until a VLAN is added or removed again.

The LAN7801 datasheet (DS00002123E) states:

  "A portion of the MAC operates on clocks generated by the Ethernet
   PHY. During a PHY reset event, this portion of the MAC is designed to
   not be taken out of reset until the PHY clocks are operational"
  (section 8.10, MAC Reset Watchdog Timer)

  "After a reset event, the RFE will automatically initialize the
   contents of the VHF to 0h."
  (section 7.1.4, VHF Organization)

Thus a link down/up cycle stops and restarts the PHY clock, resets the
PHY-clocked portion of the MAC, and the RFE clears its VLAN/DA hash
filter (VHF) memory. The VHF holds both the VLAN filter table and the
multicast hash table, but the driver never reprograms either from its
shadow copy once the link is back, so both stay empty.

Reprogram the VLAN filter and multicast hash tables on link up.

Reported-by: Sven Schuchmann <schuchmann@schleissheimer.de>
Closes: https://lore.kernel.org/netdev/BEZP281MB224501E38B30BFDC4BD3D364D9E32@BEZP281MB2245.DEUP281.PROD.OUTLOOK.COM/T/#u
Tested-by: Sven Schuchmann <schuchmann@schleissheimer.de>
Fixes: 55d7de9de6c3 ("Microchip's LAN7800 family USB 2/3 to 10/100/1000 Ethernet device driver")
Signed-off-by: Nicolai Buchwitz <nb@tipi-net.de>
Link: https://patch.msgid.link/20260622102911.484045-1-nb@tipi-net.de
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
drivers/net/usb/lan78xx.c

index bcf293ea1bd38853ff35e8cf85a6ed87d8210ea9..c4cebacabcb5343d3e8c93fc3d4ad310f9728db7 100644 (file)
@@ -1452,6 +1452,15 @@ static inline u32 lan78xx_hash(char addr[ETH_ALEN])
        return (ether_crc(ETH_ALEN, addr) >> 23) & 0x1ff;
 }
 
+static int lan78xx_write_mchash_table(struct lan78xx_net *dev)
+{
+       struct lan78xx_priv *pdata = (struct lan78xx_priv *)(dev->data[0]);
+
+       return lan78xx_dataport_write(dev, DP_SEL_RSEL_VLAN_DA_,
+                                     DP_SEL_VHF_VLAN_LEN,
+                                     DP_SEL_VHF_HASH_LEN, pdata->mchash_table);
+}
+
 static void lan78xx_deferred_multicast_write(struct work_struct *param)
 {
        struct lan78xx_priv *pdata =
@@ -1462,9 +1471,7 @@ static void lan78xx_deferred_multicast_write(struct work_struct *param)
        netif_dbg(dev, drv, dev->net, "deferred multicast write 0x%08x\n",
                  pdata->rfe_ctl);
 
-       ret = lan78xx_dataport_write(dev, DP_SEL_RSEL_VLAN_DA_,
-                                    DP_SEL_VHF_VLAN_LEN,
-                                    DP_SEL_VHF_HASH_LEN, pdata->mchash_table);
+       ret = lan78xx_write_mchash_table(dev);
        if (ret < 0)
                goto multicast_write_done;
 
@@ -1557,6 +1564,7 @@ static void lan78xx_set_multicast(struct net_device *netdev)
 }
 
 static void lan78xx_rx_urb_submit_all(struct lan78xx_net *dev);
+static int lan78xx_write_vlan_table(struct lan78xx_net *dev);
 
 static int lan78xx_mac_reset(struct lan78xx_net *dev)
 {
@@ -2514,6 +2522,17 @@ static void lan78xx_mac_link_up(struct phylink_config *config,
        if (ret < 0)
                goto link_up_fail;
 
+       /* The RFE clears the VLAN/DA hash filter (VHF) on a link down/up
+        * cycle, so reprogram both tables from their shadow copies.
+        */
+       ret = lan78xx_write_vlan_table(dev);
+       if (ret < 0)
+               goto link_up_fail;
+
+       ret = lan78xx_write_mchash_table(dev);
+       if (ret < 0)
+               goto link_up_fail;
+
        netif_start_queue(net);
 
        return;
@@ -3065,14 +3084,20 @@ static int lan78xx_set_features(struct net_device *netdev,
        return lan78xx_write_reg(dev, RFE_CTL, pdata->rfe_ctl);
 }
 
+static int lan78xx_write_vlan_table(struct lan78xx_net *dev)
+{
+       struct lan78xx_priv *pdata = (struct lan78xx_priv *)(dev->data[0]);
+
+       return lan78xx_dataport_write(dev, DP_SEL_RSEL_VLAN_DA_, 0,
+                                     DP_SEL_VHF_VLAN_LEN, pdata->vlan_table);
+}
+
 static void lan78xx_deferred_vlan_write(struct work_struct *param)
 {
        struct lan78xx_priv *pdata =
                        container_of(param, struct lan78xx_priv, set_vlan);
-       struct lan78xx_net *dev = pdata->dev;
 
-       lan78xx_dataport_write(dev, DP_SEL_RSEL_VLAN_DA_, 0,
-                              DP_SEL_VHF_VLAN_LEN, pdata->vlan_table);
+       lan78xx_write_vlan_table(pdata->dev);
 }
 
 static int lan78xx_vlan_rx_add_vid(struct net_device *netdev,