summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSerge Semin <fancer.lancer@gmail.com>2025-08-29 00:23:23 +0300
committerSerge Semin <fancer.lancer@gmail.com>2026-08-13 20:33:46 +0300
commit271532680957849a92548f0fb4a57487a5dc6051 (patch)
tree7278aca81c15544d7e789e3861580ced932c4e82
parent7f4824d2c5cec98097720165b996936234233a38 (diff)
downloadlinux-271532680957849a92548f0fb4a57487a5dc6051.tar.gz
linux-271532680957849a92548f0fb4a57487a5dc6051.zip
net: stmmac: Fix xmit frames corruption due to out of bounds MTU
Tx/Rx COE and TSO features apply additional constraint to DMA-mode and MTU. First of all DMA Store-and-forward mode implies that the frames length mustn't exceed the Tx/Rx FIFO depth. Secondly all DW GMAC, DW QoS Ether and DW XGMAC/XLGMAC demands to apply additional constraint on the Tx frames size if Tx COE is active [1, 2, 3]: "You must make sure that the Transmit FIFO is deep enough to store a complete frame before that frame is transferred to the MAC transmitter. The reason being that when space is not available to accept the programmed burst length of data, then the MTL Tx FIFO starts reading to avoid dead-lock. When reading starts, the COE fails and consequently all succeeding frames may get corrupted because of improper recovery. Therefore, you must enable the checksum insertion only in the frames that are less than the following number of bytes in size (even in the store-and-forward mode): TXFIFO_SIZE - ((PBL + N)*(DATAWIDTH/8))" Thirdly similar but less strict constraint exist if TSO feature is enabled on DW QoS Ether or DW XGMAC/XLGMAC [2, 3]: "The header length plus the MSS size (which is equal to the size of each TCP segment) must not exceed 16383 bytes, otherwise, the MAC transmitter truncates the packet after 16383 bytes causing a CRC error. The header length plus MSS size plus programmed PBL value in register must be lesser than the programmed Tx Queue size." It was surprising to realize that almost none of these constraints are taken into account in the driver, except MTU being less than Tx FIFO size. In the meantime exceeding any of them will cause frames full corruption. That's what happens in case if Jumbo/Giant frames activated. Let's fix the denoted problem by adding the comprehensive procedure to support the net_device::max_mtu field in the correct state. For that the driver now performs the max MTU constraint calculation on: 1. device probe stage so to start device using with a correct constraing. 2. on each change of the active Tx/Rx queues since it causes Tx/Rx FIFO size change on the modern DW network controllers. 3. on each Tx COE feature activation/de-activation due to the feature-specify frame size requirement. Note on each action causing net_device::max_mtu change the driver now has to make sure the current MTU doesn't exceed the new constraint otherwise the action won't be accepted. As a nice consequence of this change the net_device_ops::ndo_change_mtu() callback won't need to have FIFO-specific MTU sanity checks. The respective constraints are now reflected in net_device::max_mtu. [1] DesignWare Cores Ethernet MAC Universal Databook, Revision 3.73a, October 2013, p. 185. [2] DesignWare Cores Ethernet Quality-of-Service Databook, Revision 5.20a, April 2020, p. 364/380. [3] DesignWare Cores XGMAC - 10G Ethernet MAC Databook, Revision 3.20a, October 2022, p. 249/257. Fixes: eaf4fac47807 ("net: stmmac: Do not accept invalid MTU values") Fixes: d6ddfacd95c7 ("net: stmmac: Add DMA related callbacks for XGMAC2") Fixes: f748be531d70 ("stmmac: support new GMAC4") Signed-off-by: Serge Semin <fancer.lancer@gmail.com>
-rw-r--r--drivers/net/ethernet/stmicro/stmmac/stmmac_main.c135
1 files changed, 126 insertions, 9 deletions
diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
index 7fbaa490149b7..02ba1e965a5ba 100644
--- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
+++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
@@ -1552,6 +1552,30 @@ static void stmmac_display_rings(struct stmmac_priv *priv,
}
/**
+ * stmmac_get_txpbl - retrieve Tx DMA PBL value
+ * @priv: driver private structure
+ * Description: calculate the Tx DMA PBL in accordance with the
+ * DW controller specifics.
+ * Return: Tx DMA PBL in beats.
+ */
+static unsigned int stmmac_get_txpbl(struct stmmac_priv *priv)
+{
+ struct stmmac_dma_cfg *dma_cfg = priv->plat->dma_cfg;
+ unsigned int factor;
+
+ /* DMA PBL multiplier had been x4 prior DW GMAC v3.50a */
+ if ((priv->plat->core_type == DWMAC_CORE_GMAC &&
+ priv->synopsys_id >= DWMAC_CORE_3_50) ||
+ dwmac_is_xmac(priv->plat->core_type)) {
+ factor = dma_cfg->pblx8 ? 8 : 1;
+ } else {
+ factor = dma_cfg->pblx8 ? 4 : 1;
+ }
+
+ return (dma_cfg->txpbl ?: dma_cfg->pbl) * factor;
+}
+
+/**
* stmmac_get_txfifosz - retrieve Tx MTL FIFO size
* @priv: driver private structure
* Description: calculate the Tx MTL FIFO depth in accordance with
@@ -1602,13 +1626,17 @@ static unsigned int stmmac_get_min_mtu(struct stmmac_priv *priv)
/**
* stmmac_get_max_mtu - retrieve maximal supported MTU
* @priv: driver private structure
+ * @tx_coe: Tx COE feature state
+ * @tx_cnt: Number of active Tx queues
+ * @rx_cnt: Number of active Rx queues
* Description: calculate the maximum MTU supported by DW controller in
* accordance with the device active features.
* Return: maximum MTU.
*/
-static unsigned int stmmac_get_max_mtu(struct stmmac_priv *priv)
+static unsigned int stmmac_get_max_mtu(struct stmmac_priv *priv, bool tx_coe,
+ u32 tx_cnt, u32 rx_cnt)
{
- unsigned int max_mtu;
+ unsigned int max_mtu, tx_mtu, rx_mtu, txfifosz, rxfifosz, addend;
/* MTU constraint caused by the MAC implementation */
if (dwmac_is_xmac(priv->plat->core_type))
@@ -1623,7 +1651,38 @@ static unsigned int stmmac_get_max_mtu(struct stmmac_priv *priv)
priv->plat->maxmtu < max_mtu)
max_mtu = priv->plat->maxmtu;
- return max_mtu;
+ /* MTU constraint caused by DMA SF/CT mode and active Tx COE */
+ if (dwmac_is_xmac(priv->plat->core_type)) {
+ addend = priv->plat->data_width > 4 ? 5 : 7;
+ txfifosz = priv->plat->tx_fifo_size / tx_cnt;
+ rxfifosz = priv->plat->rx_fifo_size / rx_cnt;
+ } else {
+ addend = 3;
+ txfifosz = priv->plat->tx_fifo_size;
+ rxfifosz = priv->plat->rx_fifo_size;
+ }
+
+ /* DMA Cut-through mode doesn't apply any Rx FIFO-depth constraint */
+ if (priv->plat->force_thresh_dma_mode)
+ rx_mtu = max_mtu;
+ else
+ rx_mtu = rxfifosz - ETH_HLEN - 2 * VLAN_HLEN - ETH_FCS_LEN;
+
+ /* DMA Store-and-Forward mode requires frames to be <= FIFO-depth */
+ if (priv->plat->force_sf_dma_mode || priv->plat->tx_coe)
+ tx_mtu = txfifosz - ETH_HLEN - 2 * VLAN_HLEN - ETH_FCS_LEN;
+ else
+ tx_mtu = max_mtu;
+
+ /* Tx COE and TSO further reduce the frame length to avoid dead-lock */
+ if (tx_coe) {
+ tx_mtu -= (stmmac_get_txpbl(priv) + addend) * priv->plat->data_width;
+ /* Subtract two more bytes since MTU must be strictly lesser */
+ tx_mtu -= 2;
+ }
+
+ /* Zero Tx/Rx FIFO size lead to standard max MTU due to subtractions */
+ return min3(max_mtu, tx_mtu, rx_mtu);
}
/**
@@ -6282,6 +6341,47 @@ static void stmmac_set_rx_mode(struct net_device *dev)
}
/**
+ * stmmac_verify_max_mtu - verify net-device maximum MTU constraint
+ * @priv: driver private structure
+ * @tx_coe: Tx COE feature state
+ * @tx_cnt: Number of active Tx queues
+ * @rx_cnt: Number of active Rx queues
+ * Description: calculate the maximum MTU constraint corresponding to the
+ * device capability and passed Tx/Rx parameters, and check whether it can
+ * be applied so the current MTU wouldn't get out of bounds.
+ * Return: 0 on success or errno if current MTU gets out of the new max MTU.
+ */
+static int stmmac_verify_max_mtu(struct stmmac_priv *priv, bool tx_coe,
+ u32 tx_cnt, u32 rx_cnt)
+{
+ unsigned int mtu, max_mtu;
+
+ mtu = READ_ONCE(priv->dev->mtu);
+ max_mtu = stmmac_get_max_mtu(priv, tx_coe, tx_cnt, rx_cnt);
+
+ if (mtu > max_mtu)
+ return -EINVAL;
+
+ return 0;
+}
+
+/**
+ * stmmac_set_max_mtu - set net-device maximum MTU constraint
+ * @priv: driver private structure
+ * @tx_coe: Tx COE feature state
+ * @tx_cnt: Number of active Tx queues
+ * @rx_cnt: Number of active Rx queues
+ * Description: calculate and apply the maximum MTU constraint corresponding to the
+ * device capability and passed Tx/Rx parameters.
+ * Return: 0 on success or errno if current MTU gets out of the new max MTU.
+ */
+static void stmmac_set_max_mtu(struct stmmac_priv *priv, bool tx_coe,
+ u32 tx_cnt, u32 rx_cnt)
+{
+ priv->dev->max_mtu = stmmac_get_max_mtu(priv, tx_coe, tx_cnt, rx_cnt);
+}
+
+/**
* stmmac_change_mtu - entry point to change MTU size for the device.
* @dev : device pointer.
* @new_mtu : the new MTU size for the device.
@@ -6295,7 +6395,6 @@ static void stmmac_set_rx_mode(struct net_device *dev)
static int stmmac_change_mtu(struct net_device *dev, int new_mtu)
{
struct stmmac_priv *priv = netdev_priv(dev);
- int txfifosz = stmmac_get_txfifosz(priv);
struct stmmac_dma_conf *dma_conf;
int ret;
@@ -6304,9 +6403,6 @@ static int stmmac_change_mtu(struct net_device *dev, int new_mtu)
return -EINVAL;
}
- if (new_mtu > txfifosz)
- return -EINVAL;
-
if (netif_running(dev)) {
netdev_dbg(priv->dev, "restarting interface to change its MTU\n");
/* Try to allocate the new DMA conf with the new mtu */
@@ -6354,6 +6450,12 @@ static netdev_features_t stmmac_fix_features(struct net_device *dev,
if (priv->plat->bugged_jumbo && (dev->mtu > ETH_DATA_LEN))
features &= ~NETIF_F_CSUM_MASK;
+ /* MTU constraint depends on Tx COE/TSO features */
+ if (stmmac_verify_max_mtu(priv, features & NETIF_F_CSUM_MASK,
+ priv->plat->tx_queues_to_use,
+ priv->plat->rx_queues_to_use))
+ features &= ~NETIF_F_CSUM_MASK;
+
/* SPH feature depends on Rx COE */
if (priv->sph_active)
features |= NETIF_F_RXCSUM;
@@ -6371,6 +6473,10 @@ static int stmmac_set_features(struct net_device *netdev,
struct stmmac_priv *priv = netdev_priv(netdev);
u32 chan;
+ stmmac_set_max_mtu(priv, features & NETIF_F_CSUM_MASK,
+ priv->plat->tx_queues_to_use,
+ priv->plat->rx_queues_to_use);
+
stmmac_rx_fcs(priv, priv->hw, features & NETIF_F_RXFCS);
stmmac_rx_ipc(priv, priv->hw, features & NETIF_F_RXCSUM);
@@ -8277,7 +8383,12 @@ static void stmmac_napi_del(struct net_device *dev)
int stmmac_reinit_queues(struct net_device *dev, u32 rx_cnt, u32 tx_cnt)
{
struct stmmac_priv *priv = netdev_priv(dev);
- int ret = 0, i;
+ int ret, i;
+
+ ret = stmmac_verify_max_mtu(priv, dev->features & NETIF_F_CSUM_MASK,
+ tx_cnt, rx_cnt);
+ if (ret)
+ return ret;
if (netif_running(dev))
stmmac_release(dev);
@@ -8286,6 +8397,10 @@ int stmmac_reinit_queues(struct net_device *dev, u32 rx_cnt, u32 tx_cnt)
priv->plat->rx_queues_to_use = rx_cnt;
priv->plat->tx_queues_to_use = tx_cnt;
+
+ stmmac_set_max_mtu(priv, dev->features & NETIF_F_CSUM_MASK,
+ tx_cnt, rx_cnt);
+
if (!netif_is_rxfh_configured(dev))
for (i = 0; i < ARRAY_SIZE(priv->rss.table); i++)
priv->rss.table[i] = ethtool_rxfh_indir_default(i,
@@ -8685,7 +8800,9 @@ static int __stmmac_dvr_probe(struct device *device,
ndev->vlan_features |= ndev->features;
ndev->min_mtu = stmmac_get_min_mtu(priv);
- ndev->max_mtu = stmmac_get_max_mtu(priv);
+ ndev->max_mtu = stmmac_get_max_mtu(priv, ndev->features & NETIF_F_CSUM_MASK,
+ priv->plat->tx_queues_to_use,
+ priv->plat->rx_queues_to_use);
ndev->priv_flags |= IFF_LIVE_ADDR_CHANGE;