]> git.ipfire.org Git - thirdparty/openwrt.git/commitdiff
realtek: eth: support changing the MTU 24421/head
authorGennaro Cimmino <gcimmino@rayonra.net>
Sun, 26 Jul 2026 16:52:35 +0000 (18:52 +0200)
committerMarkus Stockhausen <markus.stockhausen@gmx.de>
Tue, 28 Jul 2026 14:25:33 +0000 (16:25 +0200)
Add a change_mtu operation, so the length the switch may deliver to the
CPU follows the conduit MTU instead of keeping the value programmed when
the interface came up.

To avoid trouble with the receive buffer, adapt the head of line budget
along with it. Those registers limit how many packets the hardware may
append to a receive ring, and deriving that limit from a frame of
maximum length makes the smallest MTU pay the price of the largest. With
400 byte fragments a 1626 byte frame occupies five of them, which leaves
128 / 5 = 25 packets per ring, while the 1530 byte frame of a tagged
conduit at its idle MTU needs four and fits 32.

The two limits move in opposite directions, so the order matters: on the
way up the budget shrinks before the switch is allowed to hand over
larger frames, which keeps the ring from ever running a budget meant for
smaller ones. On the way down it grows only after the switch has stopped
handing over the larger frames, which narrows that window rather than
closing it, as the ring may still hold frames of the previous size. The
pair runs under the lock the rest of the driver programs those registers
with, as the transmit watchdog is not serialised against an MTU change.
The budget comes from the config, one function per family, and opening
the interface calls it too: RTL83xx has none to resize, as its rings are
free floating, so it writes the ring size of zero that its reset paths
used to write themselves.

Nothing else has to follow, as the receive buffers are of a fixed size.
The largest MTU comes from the config too, one value per family: 10000
bytes on RTL838x and 12288 on the others, less the frame overhead. Those
are the limits the DSA driver already declares in its rmon ranges. A
frame must also fit into a single skb, whose linear part and fragments
hold 400 bytes each, so the operation refuses above an MTU of 7174 on
every family, well below what the switches would take. The fragment
size carries a TODO of its own, and raising it lifts that bound towards
the hardware one.

That remaining window is why every accepted change carries a warning. A
closed interface has neither a ring to protect nor registers worth
touching, as opening it derives both limits from the MTU anyway, so
leave the hardware alone while it is down. That also keeps the warning
out of the boot log, where dsa_conduit_setup() sets the tagging overhead
on a conduit that carries no traffic yet.

Tested on an RTL9303 (Hasivo S1100W-8XGT-SE, RTL8264B PHY, USXGMII).
Both limits follow every MTU as computed, read back from the switch:

  conduit MTU   1504   1000   7174
  packet length 1530   1026   7200
  packets/ring    32     42      7

where the previous static sizing programmed 25 packets per ring at every
MTU. An MTU of 7175 is refused, and one of 9000, which the family
ceiling would allow, reaches the operation and is refused there: no
warning is logged for it, where an accepted change logs one, and the
boot log carries none at all. Raising the MTU to 1600 while the
interface is down leaves the registers at 1530 and the budget at 32
packets and logs nothing, while bringing it up afterwards programs 1626
and 25, so the change is deferred rather than lost. Traffic keeps
flowing across the changes: a 102 byte frame is answered five times out
of five at each step, while a 1046 byte one passes at MTU 1504, stops at
MTU 1000, whose limit of 1026 it exceeds, and passes again once the MTU
is back. Sixty alternating changes between two MTUs complete with
nothing logged, on a build with spinlock debugging enabled.

Assisted-by: Claude:claude-opus-5
Signed-off-by: Gennaro Cimmino <gcimmino@rayonra.net>
Link: https://github.com/openwrt/openwrt/pull/24421
Signed-off-by: Markus Stockhausen <markus.stockhausen@gmx.de>
target/linux/realtek/files-6.18/drivers/net/ethernet/rtl838x_eth.c
target/linux/realtek/files-6.18/drivers/net/ethernet/rtl838x_eth.h

index 05c2d3be9403728f55b3e84fe2a5814841a6c705..ac120acbae729e85abc6ec7ab8f5fafef922659a 100644 (file)
 #define RX_TRUNCATE_EN_83XX            BIT(4)
 #define TX_PAD_EN_838X                 BIT(5)
 
-#define SKB_MTU                                1600
 /* Ethernet header, two stacked VLAN tags (802.1ad QinQ) and FCS */
 #define RTETH_FRAME_OVERHEAD           (ETH_HLEN + 2 * VLAN_HLEN + ETH_FCS_LEN)
+/* Largest frame each family switches, as its datasheet and DSA rmon range have it */
+#define RTETH_838X_MAX_FRAME           10000
+#define RTETH_839X_MAX_FRAME           12288
+#define RTETH_930X_MAX_FRAME           12288
+#define RTETH_931X_MAX_FRAME           12288
 /* TODO: change this to 1568 after fragment handling has been tested in the wild */
 #define SKB_FRAG_SIZE                  400
 #define SKB_PAD                                MAX(32, L1_CACHE_BYTES)
@@ -441,14 +445,17 @@ static void rteth_nic_reset(struct rteth_ctrl *ctrl, int reset_mask)
        msleep(100);
 }
 
-static void rteth_838x_hw_reset(struct rteth_ctrl *ctrl)
+static void rteth_83xx_set_hol(struct rteth_ctrl *ctrl)
 {
-       rteth_nic_reset(ctrl, 0xc);
-
        /* Free floating rings without space tracking */
        regmap_write(ctrl->map, ctrl->r->dma_if_rx_ring_size, 0);
 }
 
+static void rteth_838x_hw_reset(struct rteth_ctrl *ctrl)
+{
+       rteth_nic_reset(ctrl, 0xc);
+}
+
 static void rteth_839x_hw_reset(struct rteth_ctrl *ctrl)
 {
        u32 int_saved, nbuf;
@@ -472,32 +479,36 @@ static void rteth_839x_hw_reset(struct rteth_ctrl *ctrl)
        /* Restore notification settings: on RTL838x these bits are null */
        regmap_update_bits(ctrl->map, ctrl->r->dma_if_intr_msk, 7 << 20, int_saved & (7 << 20));
        regmap_write(ctrl->map, RTL839X_DMA_IF_NBUF_BASE_DESC_ADDR_CTRL, nbuf);
-
-       /* Free floating rings without space tracking */
-       regmap_write(ctrl->map, ctrl->r->dma_if_rx_ring_size, 0);
 }
 
-static void rteth_93xx_hw_reset(struct rteth_ctrl *ctrl)
+static void rteth_93xx_set_hol(struct rteth_ctrl *ctrl)
 {
        /*
         * The counter registers track the number of packets that are allowed to be appended to
         * the ring buffer. On RTL93xx a ring overflow must be avoided at all cost. Defensively
         * calculate the space by anticipating that only fully filled packets are received.
         */
-       int max_frame_size = SKB_MTU + ETH_HLEN + ETH_FCS_LEN + VLAN_HLEN + 4;
+       int max_frame_size = ctrl->dev->mtu + RTETH_FRAME_OVERHEAD;
        int frags_per_pkt = DIV_ROUND_UP(max_frame_size, SKB_FRAG_SIZE);
        int cnt = min(RTETH_RX_RING_SIZE / frags_per_pkt, 0x3ff);
 
-       rteth_nic_reset(ctrl, 0x6);
-
-       /* Setup Head of Line */
        for (int ring = 0; ring < RTETH_RX_RINGS; ring++) {
                int shift = (ring % 3) * 10;
                int reg = (ring / 3) * 4;
-               u32 v;
 
                regmap_update_bits(ctrl->map, ctrl->r->dma_if_rx_ring_size + reg,
                                   0x3ff << shift, cnt << shift);
+       }
+}
+
+static void rteth_93xx_hw_reset(struct rteth_ctrl *ctrl)
+{
+       rteth_nic_reset(ctrl, 0x6);
+
+       for (int ring = 0; ring < RTETH_RX_RINGS; ring++) {
+               int reg = (ring / 3) * 4;
+               u32 v;
+
                /* clear counters by simply writing the current register values back */
                regmap_read(ctrl->map, ctrl->r->dma_if_rx_ring_cntr + reg, &v);
                regmap_write(ctrl->map, ctrl->r->dma_if_rx_ring_cntr + reg, v);
@@ -912,6 +923,7 @@ static int rteth_open(struct net_device *dev)
 
        scoped_guard(spinlock_irqsave, &ctrl->lock) {
                ctrl->r->hw_reset(ctrl);
+               ctrl->r->set_hol(ctrl);
                rteth_setup_cpu_rx_rings(ctrl);
                ret = rteth_setup_ring_buffer(ctrl);
                if (ret)
@@ -1026,6 +1038,41 @@ static int rteth_stop(struct net_device *dev)
        return 0;
 }
 
+static int rteth_change_mtu(struct net_device *dev, int mtu)
+{
+       struct rteth_ctrl *ctrl = netdev_priv(dev);
+       bool grow = mtu > dev->mtu;
+
+       /* A frame of that size must still fit into the linear part and fragments of a single skb */
+       if (mtu + RTETH_FRAME_OVERHEAD > (MAX_SKB_FRAGS + 1) * SKB_FRAG_SIZE)
+               return -EINVAL;
+
+       WRITE_ONCE(dev->mtu, mtu);
+
+       /* Opening the interface programs both limits from the MTU, so leave the hardware alone */
+       if (!netif_running(dev))
+               return 0;
+
+       netdev_warn(dev, "changing the MTU may have unexpected effects under high receive load\n");
+
+       /*
+        * A larger MTU buys a smaller budget, so hand the rings their new limit before the
+        * switch may deliver larger frames, and the other way around when the MTU shrinks.
+        * Hold the lock, so rteth_tx_timeout() cannot reprogram the same registers in between.
+        */
+       scoped_guard(spinlock_irqsave, &ctrl->lock) {
+               if (grow)
+                       ctrl->r->set_hol(ctrl);
+
+               rteth_set_max_packet_length(ctrl);
+
+               if (!grow)
+                       ctrl->r->set_hol(ctrl);
+       }
+
+       return 0;
+}
+
 static void rteth_838x_set_rx_mode(struct net_device *dev)
 {
        struct rteth_ctrl *ctrl = netdev_priv(dev);
@@ -1611,6 +1658,7 @@ static int rteth_setup_tc(struct net_device *dev, enum tc_setup_type type, void
 static const struct net_device_ops rteth_838x_netdev_ops = {
        .ndo_open = rteth_open,
        .ndo_stop = rteth_stop,
+       .ndo_change_mtu = rteth_change_mtu,
        .ndo_start_xmit = rteth_start_xmit,
        .ndo_set_mac_address = rteth_set_mac_address,
        .ndo_validate_addr = eth_validate_addr,
@@ -1623,6 +1671,7 @@ static const struct net_device_ops rteth_838x_netdev_ops = {
 
 static const struct rteth_config rteth_838x_cfg = {
        .cpu_port = RTETH_838X_CPU_PORT,
+       .max_mtu = RTETH_838X_MAX_FRAME - RTETH_FRAME_OVERHEAD,
        .rx_rings = 8,
        .tx_rx_enable = 0xc,
        .tx_trigger_mask = BIT(1),
@@ -1652,6 +1701,7 @@ static const struct rteth_config rteth_838x_cfg = {
        .hw_stop = &rteth_838x_hw_stop,
        .hw_reset = &rteth_838x_hw_reset,
        .init_mac = &rteth_838x_init_mac,
+       .set_hol = rteth_83xx_set_hol,
        .set_max_packet_length = rteth_838x_set_max_packet_length,
        .netdev_ops = &rteth_838x_netdev_ops,
 };
@@ -1659,6 +1709,7 @@ static const struct rteth_config rteth_838x_cfg = {
 static const struct net_device_ops rteth_839x_netdev_ops = {
        .ndo_open = rteth_open,
        .ndo_stop = rteth_stop,
+       .ndo_change_mtu = rteth_change_mtu,
        .ndo_start_xmit = rteth_start_xmit,
        .ndo_set_mac_address = rteth_set_mac_address,
        .ndo_validate_addr = eth_validate_addr,
@@ -1671,6 +1722,7 @@ static const struct net_device_ops rteth_839x_netdev_ops = {
 
 static const struct rteth_config rteth_839x_cfg = {
        .cpu_port = RTETH_839X_CPU_PORT,
+       .max_mtu = RTETH_839X_MAX_FRAME - RTETH_FRAME_OVERHEAD,
        .rx_rings = 8,
        .tx_rx_enable = 0xc,
        .tx_trigger_mask = BIT(1),
@@ -1698,6 +1750,7 @@ static const struct rteth_config rteth_839x_cfg = {
        .hw_stop = &rteth_839x_hw_stop,
        .hw_reset = &rteth_839x_hw_reset,
        .init_mac = &rteth_839x_init_mac,
+       .set_hol = rteth_83xx_set_hol,
        .set_max_packet_length = rteth_839x_set_max_packet_length,
        .setup_notify_ring_buffer = &rteth_839x_setup_notify_ring_buffer,
        .netdev_ops = &rteth_839x_netdev_ops,
@@ -1706,6 +1759,7 @@ static const struct rteth_config rteth_839x_cfg = {
 static const struct net_device_ops rteth_930x_netdev_ops = {
        .ndo_open = rteth_open,
        .ndo_stop = rteth_stop,
+       .ndo_change_mtu = rteth_change_mtu,
        .ndo_start_xmit = rteth_start_xmit,
        .ndo_set_mac_address = rteth_set_mac_address,
        .ndo_validate_addr = eth_validate_addr,
@@ -1718,6 +1772,7 @@ static const struct net_device_ops rteth_930x_netdev_ops = {
 
 static const struct rteth_config rteth_930x_cfg = {
        .cpu_port = RTETH_930X_CPU_PORT,
+       .max_mtu = RTETH_930X_MAX_FRAME - RTETH_FRAME_OVERHEAD,
        .rx_rings = 32,
        .tx_rx_enable = 0x30,
        .tx_trigger_mask = GENMASK(3, 2),
@@ -1746,6 +1801,7 @@ static const struct rteth_config rteth_930x_cfg = {
        .hw_stop = &rteth_930x_hw_stop,
        .hw_reset = &rteth_93xx_hw_reset,
        .init_mac = &rteth_930x_init_mac,
+       .set_hol = rteth_93xx_set_hol,
        .set_max_packet_length = rteth_930x_set_max_packet_length,
        .netdev_ops = &rteth_930x_netdev_ops,
 };
@@ -1753,6 +1809,7 @@ static const struct rteth_config rteth_930x_cfg = {
 static const struct net_device_ops rteth_931x_netdev_ops = {
        .ndo_open = rteth_open,
        .ndo_stop = rteth_stop,
+       .ndo_change_mtu = rteth_change_mtu,
        .ndo_start_xmit = rteth_start_xmit,
        .ndo_set_mac_address = rteth_set_mac_address,
        .ndo_validate_addr = eth_validate_addr,
@@ -1765,6 +1822,7 @@ static const struct net_device_ops rteth_931x_netdev_ops = {
 
 static const struct rteth_config rteth_931x_cfg = {
        .cpu_port = RTETH_931X_CPU_PORT,
+       .max_mtu = RTETH_931X_MAX_FRAME - RTETH_FRAME_OVERHEAD,
        .rx_rings = 32,
        .tx_rx_enable = 0x30,
        .tx_trigger_mask = GENMASK(3, 2),
@@ -1793,6 +1851,7 @@ static const struct rteth_config rteth_931x_cfg = {
        .hw_stop = &rteth_931x_hw_stop,
        .hw_reset = &rteth_93xx_hw_reset,
        .init_mac = &rteth_931x_init_mac,
+       .set_hol = rteth_93xx_set_hol,
        .set_max_packet_length = rteth_931x_set_max_packet_length,
        .netdev_ops = &rteth_931x_netdev_ops,
 };
@@ -1892,7 +1951,7 @@ static int rteth_probe(struct platform_device *pdev)
 
        dev->ethtool_ops = &rteth_ethtool_ops;
        dev->min_mtu = ETH_ZLEN;
-       dev->max_mtu = SKB_MTU;
+       dev->max_mtu = ctrl->r->max_mtu;
        dev->features = NETIF_F_RXCSUM;
        dev->hw_features = NETIF_F_RXCSUM;
        dev->netdev_ops = ctrl->r->netdev_ops;
index ffee2e087fbcfea278de7f36331e782bb0382642..e94735c07499c8f51ca5b6668c248ea3c7c9ae61 100644 (file)
@@ -194,6 +194,7 @@ struct rteth_frag;
 
 struct rteth_config {
        int cpu_port;
+       int max_mtu;
        int rx_rings;
        int tx_rx_enable;
        int tx_trigger_mask;
@@ -222,6 +223,7 @@ struct rteth_config {
        void (*hw_stop)(struct rteth_ctrl *ctrl);
        void (*hw_reset)(struct rteth_ctrl *ctrl);
        int (*init_mac)(struct rteth_ctrl *ctrl);
+       void (*set_hol)(struct rteth_ctrl *ctrl);
        void (*set_max_packet_length)(struct rteth_ctrl *ctrl, int len);
        void (*setup_notify_ring_buffer)(struct rteth_ctrl *ctrl);
        void (*update_counter)(struct rteth_ctrl *ctrl, int ring, int released);