From: Gennaro Cimmino Date: Sun, 26 Jul 2026 16:52:35 +0000 (+0200) Subject: realtek: eth: support changing the MTU X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=c22686329b63652fe7924efcf94855fdb234125e;p=thirdparty%2Fopenwrt.git realtek: eth: support changing the MTU 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 Link: https://github.com/openwrt/openwrt/pull/24421 Signed-off-by: Markus Stockhausen --- diff --git a/target/linux/realtek/files-6.18/drivers/net/ethernet/rtl838x_eth.c b/target/linux/realtek/files-6.18/drivers/net/ethernet/rtl838x_eth.c index 05c2d3be940..ac120acbae7 100644 --- a/target/linux/realtek/files-6.18/drivers/net/ethernet/rtl838x_eth.c +++ b/target/linux/realtek/files-6.18/drivers/net/ethernet/rtl838x_eth.c @@ -43,9 +43,13 @@ #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; diff --git a/target/linux/realtek/files-6.18/drivers/net/ethernet/rtl838x_eth.h b/target/linux/realtek/files-6.18/drivers/net/ethernet/rtl838x_eth.h index ffee2e087fb..e94735c0749 100644 --- a/target/linux/realtek/files-6.18/drivers/net/ethernet/rtl838x_eth.h +++ b/target/linux/realtek/files-6.18/drivers/net/ethernet/rtl838x_eth.h @@ -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);