Merge branch 'net-stmmac-fix-vlan-handling-when-interface-is-down'

Ovidiu Panait says:

====================
net: stmmac: Fix VLAN handling when interface is down

VLAN register accesses on the MAC side require the PHY RX clock to be
active. When the network interface is down, the PHY is suspended and
the RX clock is unavailable, causing VLAN operations to fail with
timeouts.

The VLAN core automatically removes VID 0 after the interface goes down
and re-adds it when it comes back up, so these timeouts happen during
normal interface down/up:

    # ip link set end1 down
    renesas-gbeth 15c40000.ethernet end1: Timeout accessing MAC_VLAN_Tag_Filter
    renesas-gbeth 15c40000.ethernet end1: failed to kill vid 0081/0

Adding VLANs while the interface is down also fails:

    # ip link add link end1 name end1.10 type vlan id 10
    renesas-gbeth 15c40000.ethernet end1: Timeout accessing MAC_VLAN_Tag_Filter
    RTNETLINK answers: Device or resource busy

Patch 4 fixes this by adding checks in the VLAN paths for netif_running(),
and skipping register accesses if the interface is down. Only the software
state is updated in this case. When the interface is brought up, the VLAN
state is restored to hardware.

Patches 1-3 fix some issues in the existing VLAN implementation.
====================

Link: https://patch.msgid.link/20260303145828.7845-1-ovidiu.panait.rb@renesas.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
Jakub Kicinski 2026-03-04 18:48:51 -08:00
commit ae779bcb18
3 changed files with 79 additions and 35 deletions

View file

@ -323,6 +323,7 @@ struct stmmac_priv {
void __iomem *ptpaddr;
void __iomem *estaddr;
unsigned long active_vlans[BITS_TO_LONGS(VLAN_N_VID)];
unsigned int num_double_vlans;
int sfty_irq;
int sfty_ce_irq;
int sfty_ue_irq;

View file

@ -156,6 +156,7 @@ static void stmmac_tx_timer_arm(struct stmmac_priv *priv, u32 queue);
static void stmmac_flush_tx_descriptors(struct stmmac_priv *priv, int queue);
static void stmmac_set_dma_operation_mode(struct stmmac_priv *priv, u32 txmode,
u32 rxmode, u32 chan);
static int stmmac_vlan_restore(struct stmmac_priv *priv);
#ifdef CONFIG_DEBUG_FS
static const struct net_device_ops stmmac_netdev_ops;
@ -4107,6 +4108,8 @@ static int __stmmac_open(struct net_device *dev,
phylink_start(priv->phylink);
stmmac_vlan_restore(priv);
ret = stmmac_request_irq(dev);
if (ret)
goto irq_error;
@ -6766,6 +6769,9 @@ static int stmmac_vlan_update(struct stmmac_priv *priv, bool is_double)
hash = 0;
}
if (!netif_running(priv->dev))
return 0;
return stmmac_update_vlan_hash(priv, priv->hw, hash, pmatch, is_double);
}
@ -6775,6 +6781,7 @@ static int stmmac_vlan_update(struct stmmac_priv *priv, bool is_double)
static int stmmac_vlan_rx_add_vid(struct net_device *ndev, __be16 proto, u16 vid)
{
struct stmmac_priv *priv = netdev_priv(ndev);
unsigned int num_double_vlans;
bool is_double = false;
int ret;
@ -6786,7 +6793,8 @@ static int stmmac_vlan_rx_add_vid(struct net_device *ndev, __be16 proto, u16 vid
is_double = true;
set_bit(vid, priv->active_vlans);
ret = stmmac_vlan_update(priv, is_double);
num_double_vlans = priv->num_double_vlans + is_double;
ret = stmmac_vlan_update(priv, num_double_vlans);
if (ret) {
clear_bit(vid, priv->active_vlans);
goto err_pm_put;
@ -6794,9 +6802,15 @@ static int stmmac_vlan_rx_add_vid(struct net_device *ndev, __be16 proto, u16 vid
if (priv->hw->num_vlan) {
ret = stmmac_add_hw_vlan_rx_fltr(priv, ndev, priv->hw, proto, vid);
if (ret)
if (ret) {
clear_bit(vid, priv->active_vlans);
stmmac_vlan_update(priv, priv->num_double_vlans);
goto err_pm_put;
}
}
priv->num_double_vlans = num_double_vlans;
err_pm_put:
pm_runtime_put(priv->device);
@ -6809,6 +6823,7 @@ err_pm_put:
static int stmmac_vlan_rx_kill_vid(struct net_device *ndev, __be16 proto, u16 vid)
{
struct stmmac_priv *priv = netdev_priv(ndev);
unsigned int num_double_vlans;
bool is_double = false;
int ret;
@ -6820,14 +6835,23 @@ static int stmmac_vlan_rx_kill_vid(struct net_device *ndev, __be16 proto, u16 vi
is_double = true;
clear_bit(vid, priv->active_vlans);
num_double_vlans = priv->num_double_vlans - is_double;
ret = stmmac_vlan_update(priv, num_double_vlans);
if (ret) {
set_bit(vid, priv->active_vlans);
goto del_vlan_error;
}
if (priv->hw->num_vlan) {
ret = stmmac_del_hw_vlan_rx_fltr(priv, ndev, priv->hw, proto, vid);
if (ret)
if (ret) {
set_bit(vid, priv->active_vlans);
stmmac_vlan_update(priv, priv->num_double_vlans);
goto del_vlan_error;
}
}
ret = stmmac_vlan_update(priv, is_double);
priv->num_double_vlans = num_double_vlans;
del_vlan_error:
pm_runtime_put(priv->device);
@ -6835,6 +6859,23 @@ del_vlan_error:
return ret;
}
static int stmmac_vlan_restore(struct stmmac_priv *priv)
{
int ret;
if (!(priv->dev->features & NETIF_F_VLAN_FEATURES))
return 0;
if (priv->hw->num_vlan)
stmmac_restore_hw_vlan_rx_fltr(priv, priv->dev, priv->hw);
ret = stmmac_vlan_update(priv, priv->num_double_vlans);
if (ret)
netdev_err(priv->dev, "Failed to restore VLANs\n");
return ret;
}
static int stmmac_bpf(struct net_device *dev, struct netdev_bpf *bpf)
{
struct stmmac_priv *priv = netdev_priv(dev);
@ -8259,10 +8300,10 @@ int stmmac_resume(struct device *dev)
stmmac_init_coalesce(priv);
phylink_rx_clk_stop_block(priv->phylink);
stmmac_set_rx_mode(ndev);
stmmac_restore_hw_vlan_rx_fltr(priv, ndev, priv->hw);
phylink_rx_clk_stop_unblock(priv->phylink);
stmmac_vlan_restore(priv);
stmmac_enable_all_queues(priv);
stmmac_enable_all_dma_irq(priv);

View file

@ -76,7 +76,9 @@ static int vlan_add_hw_rx_fltr(struct net_device *dev,
}
hw->vlan_filter[0] = vid;
vlan_write_single(dev, vid);
if (netif_running(dev))
vlan_write_single(dev, vid);
return 0;
}
@ -97,12 +99,15 @@ static int vlan_add_hw_rx_fltr(struct net_device *dev,
return -EPERM;
}
ret = vlan_write_filter(dev, hw, index, val);
if (netif_running(dev)) {
ret = vlan_write_filter(dev, hw, index, val);
if (ret)
return ret;
}
if (!ret)
hw->vlan_filter[index] = val;
hw->vlan_filter[index] = val;
return ret;
return 0;
}
static int vlan_del_hw_rx_fltr(struct net_device *dev,
@ -115,7 +120,9 @@ static int vlan_del_hw_rx_fltr(struct net_device *dev,
if (hw->num_vlan == 1) {
if ((hw->vlan_filter[0] & VLAN_TAG_VID) == vid) {
hw->vlan_filter[0] = 0;
vlan_write_single(dev, 0);
if (netif_running(dev))
vlan_write_single(dev, 0);
}
return 0;
}
@ -124,25 +131,23 @@ static int vlan_del_hw_rx_fltr(struct net_device *dev,
for (i = 0; i < hw->num_vlan; i++) {
if ((hw->vlan_filter[i] & VLAN_TAG_DATA_VEN) &&
((hw->vlan_filter[i] & VLAN_TAG_DATA_VID) == vid)) {
ret = vlan_write_filter(dev, hw, i, 0);
if (!ret)
hw->vlan_filter[i] = 0;
else
return ret;
if (netif_running(dev)) {
ret = vlan_write_filter(dev, hw, i, 0);
if (ret)
return ret;
}
hw->vlan_filter[i] = 0;
}
}
return ret;
return 0;
}
static void vlan_restore_hw_rx_fltr(struct net_device *dev,
struct mac_device_info *hw)
{
void __iomem *ioaddr = hw->pcsr;
u32 value;
u32 hash;
u32 val;
int i;
/* Single Rx VLAN Filter */
@ -152,19 +157,8 @@ static void vlan_restore_hw_rx_fltr(struct net_device *dev,
}
/* Extended Rx VLAN Filter Enable */
for (i = 0; i < hw->num_vlan; i++) {
if (hw->vlan_filter[i] & VLAN_TAG_DATA_VEN) {
val = hw->vlan_filter[i];
vlan_write_filter(dev, hw, i, val);
}
}
hash = readl(ioaddr + VLAN_HASH_TABLE);
if (hash & VLAN_VLHT) {
value = readl(ioaddr + VLAN_TAG);
value |= VLAN_VTHM;
writel(value, ioaddr + VLAN_TAG);
}
for (i = 0; i < hw->num_vlan; i++)
vlan_write_filter(dev, hw, i, hw->vlan_filter[i]);
}
static void vlan_update_hash(struct mac_device_info *hw, u32 hash,
@ -183,6 +177,10 @@ static void vlan_update_hash(struct mac_device_info *hw, u32 hash,
value |= VLAN_EDVLP;
value |= VLAN_ESVL;
value |= VLAN_DOVLTC;
} else {
value &= ~VLAN_EDVLP;
value &= ~VLAN_ESVL;
value &= ~VLAN_DOVLTC;
}
writel(value, ioaddr + VLAN_TAG);
@ -193,6 +191,10 @@ static void vlan_update_hash(struct mac_device_info *hw, u32 hash,
value |= VLAN_EDVLP;
value |= VLAN_ESVL;
value |= VLAN_DOVLTC;
} else {
value &= ~VLAN_EDVLP;
value &= ~VLAN_ESVL;
value &= ~VLAN_DOVLTC;
}
writel(value | perfect_match, ioaddr + VLAN_TAG);