* [PATCH net-next v13 00/10] net: stmmac: qcom-ethqos: add support for SCMI power domains
From: Bartosz Golaszewski @ 2026-07-13 13:20 UTC (permalink / raw)
To: Bjorn Andersson, Konrad Dybcio, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Maxime Coquelin, Alexandre Torgue,
Vinod Koul, Giuseppe Cavallaro, Chen-Yu Tsai, Jernej Skrabec,
Neil Armstrong, Kevin Hilman, Jerome Brunet, Shawn Guo,
Fabio Estevam, Jan Petrous, s32, Mohd Ayaan Anwar, Romain Gantois,
Geert Uytterhoeven, Magnus Damm, Maxime Ripard,
Christophe Roullier, Bartosz Golaszewski, Radu Rendec
Cc: linux-arm-msm, devicetree, linux-kernel, netdev, linux-stm32,
linux-arm-kernel, Drew Fustini, linux-sunxi, linux-amlogic,
linux-mips, imx, linux-renesas-soc, linux-rockchip, sophgo,
linux-riscv, brgl, Bartosz Golaszewski, Bartosz Golaszewski,
stable, Krzysztof Kozlowski, Martin Blumenstingl, Lad Prabhakar
Add support for the firmware-managed variant of the DesignWare MAC on
the sa8255p platform. This series contains new DT bindings and driver
changes required to support the MAC in the STMMAC driver.
It also reorganizes the ethqos code quite a bit to make the introduction
of power domains into the driver a bit easier on the eye.
The DTS changes will go in separately.
Signed-off-by: Bartosz Golaszewski <bartosz.golaszewski@linaro.org>
Signed-off-by: Bartosz Golaszewski <bartosz.golaszewski@oss.qualcomm.com>
---
Changes in v13:
- Reapply OPP on speed change in the PHY driver
- Use linux/device-id/of.h instead of mod_devicetable.h
- Fix the "invalid PTP clock rate" warning on sa8255p
- Add commits necessary to allow correct link speed change with SCMI
- Link to v12: https://patch.msgid.link/20260706-qcom-sa8255p-emac-v12-0-e3ab1ecf2901@oss.qualcomm.com
Changes in v12:
- Drop the power-domain-names from the SGMII PHY DT bindings
- Add missing Kconfig dependency on PM for the new SGMII PHY driver
- Link to v11: https://patch.msgid.link/20260629-qcom-sa8255p-emac-v11-0-1b7fb95b51f9@oss.qualcomm.com
Changes in v11:
- Take a new approach: add a dedicated driver for the firmware-managed
SGMII PHY and simplify changes made to the MAC driver
- Link to v10: https://patch.msgid.link/20260323-qcom-sa8255p-emac-v10-0-79302b238a16@oss.qualcomm.com
Changes in v10:
- Fix unit address in DT example
- Link to v9: https://patch.msgid.link/20260316-qcom-sa8255p-emac-v9-0-c58934e76ff2@oss.qualcomm.com
Changes in v9:
- Rebase on top of current linux-next again
- Link to v8: https://patch.msgid.link/20260311-qcom-sa8255p-emac-v8-0-58227bcf1018@oss.qualcomm.com
Changes in v8:
- Rebase on top of recent changes in linux-next which required an
extensive rework
- Drop partial R-b tags
- Link to v7: https://patch.msgid.link/20260306-qcom-sa8255p-emac-v7-0-d6a3013094b7@oss.qualcomm.com
Changes in v7:
- Restored the correct authorship after learning git uses .mailmap for
the --author switch
- Rebased on top of changes from Russell
- Fixed resource management issues in error paths
- Link to v6: https://lore.kernel.org/r/20260112-qcom-sa8255p-emac-v6-0-86a3d4b2ad83@oss.qualcomm.com
Changes in v6:
- Fix $id value in the bindings
- Drop patch 3/8 from the series
- Update init/exit callback signatures
- Link to v5: https://lore.kernel.org/r/20251107-qcom-sa8255p-emac-v5-0-01d3e3aaf388@linaro.org
- Link to v6: https://lore.kernel.org/r/20251219-qcom-sa8255p-emac-v6-0-487f1082461e@oss.qualcomm.com
Changes in v5:
- Name the DT binding document after the new compatbile
- Add missing space
- Make the power-domains limits stricter
- Link to v4: https://lore.kernel.org/r/20251104-qcom-sa8255p-emac-v4-0-f76660087cea@linaro.org
Changes in v4:
- Remove the phys property from the SCMI bindings
- Mark the power-domain-names property as required
- Set maxItems for power-domains to 1 for all existing bindings to
maintain the current requirements after modifying the value in the
top-level document
- Link to v3: https://lore.kernel.org/r/20251027-qcom-sa8255p-emac-v3-0-75767b9230ab@linaro.org
Changes in v3:
- Drop 'power' and 'perf' prefixes from power domain names
- Rebase on top of Russell's changes to dwmac
- Rebase on top of even more changes from Russell that are not yet
in next (E1vB6ld-0000000BIPy-2Qi4@rmk-PC.armlinux.org.uk)
- Link to v2: https://lore.kernel.org/all/20251008-qcom-sa8255p-emac-v2-0-92bc29309fce@linaro.org/
Changes in v2:
- Fix the power-domains property in DT bindings
- Rework the DT bindings example
- Drop the DTS patch, it will go upstream separately
- Link to v1: https://lore.kernel.org/r/20250910-qcom-sa8255p-emac-v1-0-32a79cf1e668@linaro.org
---
Bartosz Golaszewski (10):
net: phy: aquantia: fix system interface type not updated in forced mode
dt-bindings: phy: document the serdes PHY on sa8255p
phy: qcom: add the SGMII SerDes PHY driver for SCMI systems
dt-bindings: net: qcom: document the ethqos device for SCMI-based systems
net: stmmac: qcom-ethqos: set serdes mode before powerup
net: stmmac: qcom-ethqos: update phy_mode to the resolved interface in mac_finish()
net: stmmac: qcom-ethqos: fix SGMII loopback not set on resume after speed change
net: stmmac: qcom-ethqos: reuse the address of ethqos_emac_driver_data
net: stmmac: qcom-ethqos: factor out linux-level setup into a separate function
net: stmmac: qcom-ethqos: add support for sa8255p
.../bindings/net/allwinner,sun7i-a20-gmac.yaml | 3 +
.../bindings/net/altr,socfpga-stmmac.yaml | 3 +
.../bindings/net/amlogic,meson-dwmac.yaml | 3 +
.../devicetree/bindings/net/eswin,eic7700-eth.yaml | 3 +
.../devicetree/bindings/net/intel,dwmac-plat.yaml | 3 +
.../bindings/net/loongson,ls1b-gmac.yaml | 3 +
.../bindings/net/loongson,ls1c-emac.yaml | 3 +
.../devicetree/bindings/net/nxp,dwmac-imx.yaml | 3 +
.../devicetree/bindings/net/nxp,lpc1850-dwmac.yaml | 3 +
.../devicetree/bindings/net/nxp,s32-dwmac.yaml | 3 +
.../devicetree/bindings/net/qcom,ethqos.yaml | 3 +
.../bindings/net/qcom,sa8255p-ethqos.yaml | 107 +++++++++
.../devicetree/bindings/net/renesas,rzn1-gmac.yaml | 3 +
.../bindings/net/renesas,rzv2h-gbeth.yaml | 3 +
.../devicetree/bindings/net/rockchip-dwmac.yaml | 3 +
.../devicetree/bindings/net/snps,dwmac.yaml | 5 +-
.../bindings/net/sophgo,cv1800b-dwmac.yaml | 3 +
.../bindings/net/sophgo,sg2044-dwmac.yaml | 3 +
.../bindings/net/starfive,jh7110-dwmac.yaml | 3 +
.../devicetree/bindings/net/stm32-dwmac.yaml | 3 +
.../devicetree/bindings/net/tesla,fsd-ethqos.yaml | 3 +
.../devicetree/bindings/net/thead,th1520-gmac.yaml | 3 +
.../bindings/net/toshiba,visconti-dwmac.yaml | 3 +
.../bindings/phy/qcom,sa8255p-dwmac-sgmii-phy.yaml | 50 +++++
MAINTAINERS | 1 +
.../ethernet/stmicro/stmmac/dwmac-qcom-ethqos.c | 246 ++++++++++++++++-----
drivers/net/phy/aquantia/aquantia_main.c | 2 +-
drivers/phy/qualcomm/Kconfig | 11 +
drivers/phy/qualcomm/Makefile | 1 +
drivers/phy/qualcomm/phy-qcom-sgmii-eth-scmi.c | 165 ++++++++++++++
30 files changed, 593 insertions(+), 58 deletions(-)
---
base-commit: 141ee96ad5c69b9d33b2b50ddd49315671023726
change-id: 20250704-qcom-sa8255p-emac-8460235ac512
Best regards,
--
Bartosz Golaszewski <bartosz.golaszewski@oss.qualcomm.com>
^ permalink raw reply
* [PATCH] virtio_net: fix infinite loop in virtnet_poll_cleantx when device is broken
From: Jinqian Yang @ 2026-07-13 13:20 UTC (permalink / raw)
To: mst, jasowang, xuanzhuo, eperezma, andrew+netdev, davem, edumazet,
kuba, pabeni
Cc: netdev, virtualization, linux-kernel, liuyonglong, wangzhou1,
linuxarm, Jinqian Yang
virtnet_poll_cleantx() contains a do-while loop that cleans up
transmitted TX buffers and calls virtqueue_enable_cb_delayed() to check
whether more buffers need processing. When the virtio backend stops
responding during guest reboot, used->idx is never updated, so
virtqueue_enable_cb_delayed() always returns false and the loop never
terminates. Then it will block reboot process, and the guest will hang.
The problem occurs during guest reboot under network traffic:
1. kernel_restart() -> device_shutdown() traverses the device list
2. virtio_dev_shutdown() calls virtio_break_device() which sets
vq->broken = true
3. virtio_dev_shutdown() then calls virtio_synchronize_cbs() to wait
for in-flight callbacks to complete
4. A virtio interrupt fires, softirq is deferred to ksoftirqd which
calls net_rx_action() -> virtnet_poll() -> virtnet_poll_cleantx()
5. virtnet_poll_cleantx() enters the do-while loop and never exits
because the QEMU backend has stopped updating used->idx, despite
vq->broken having been set to true in step 2.
Since the loop runs inside ksoftirqd (a SCHED_OTHER kthread), it is
visible to the scheduler and does not trigger a hard lockup. However,
the kthread never leaves the loop, so RCU detects it as a CPU stall
and reports it periodically. Meanwhile, the reboot process remains
blocked in device_shutdown() because virtio_dev_shutdown() cannot
complete its synchronization step, and the guest hangs permanently.
This can be reproduced on a guest with a virtio-net device: run iperf3
traffic in the guest, then trigger reboot. The reboot occasionally hangs
permanently with RCU stall on ksoftirqd.
Observed on ARM64 KVM guest:
CPU#1 RCU stall (ksoftirqd/1), repeated periodically:
virtqueue_enable_cb_delayed_split <- virtnet_poll <- __napi_poll <-
net_rx_action <- handle_softirqs <- run_ksoftirqd <-
smpboot_thread_fn <- kthread
Fix by adding a virtqueue_is_broken() check to the loop condition, so
that the loop exits immediately when the device is broken, allowing
the device shutdown to proceed.
Signed-off-by: Jinqian Yang <yangjinqian1@huawei.com>
---
drivers/net/virtio_net.c | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/drivers/net/virtio_net.c b/drivers/net/virtio_net.c
index 7d2eeb9b1226..c8d2d420c31d 100644
--- a/drivers/net/virtio_net.c
+++ b/drivers/net/virtio_net.c
@@ -2970,7 +2970,8 @@ static void virtnet_poll_cleantx(struct receive_queue *rq, int budget)
do {
virtqueue_disable_cb(sq->vq);
free_old_xmit(sq, txq, !!budget);
- } while (unlikely(!virtqueue_enable_cb_delayed(sq->vq)));
+ } while (!virtqueue_is_broken(sq->vq) &&
+ unlikely(!virtqueue_enable_cb_delayed(sq->vq)));
if (sq->vq->num_free >= MAX_SKB_FRAGS + 2)
virtnet_tx_wake_queue(vi, sq);
--
2.33.0
^ permalink raw reply related
* Re: [PATCHv2 2/4] net: smc91x: do not use readw()/writew() on ColdFire platforms
From: Greg Ungerer @ 2026-07-13 13:19 UTC (permalink / raw)
To: nico; +Cc: linux-kernel, arnd, netdev
In-Reply-To: <20260609142139.1563360-4-gerg@linux-m68k.org>
Gentle ping.
This patch is independent and can be applied as-is.
On 10/6/26 00:12, Greg Ungerer wrote:
> Modify the access macros and functions used to access the smsc hardware
> registers when used on ColdFire SoC platforms so they do not use readw()
> or writew(), or derived functions like ioread16be() and iowrite16be().
>
> The current set of readX()/writeX() access methods for ColdFire have
> historically been non-standard, in that they mostly access memory
> big-endian instead of the expected little-endian. Before fixing the
> ColdFire readX() and writeX() supporting code to properly work with
> little-endian data existing driver uses need to be fixed. Convert the
> smsc driver ColdFire uses of these to use the raw access macros - which
> are well defined to be (native) big-endian on ColdFire. This change
> requires some byte swapping at time of access to retain existing correct
> behavior.
>
> Signed-off-by: Greg Ungerer <gerg@linux-m68k.org>
> ---
> v2: changed from RFC to PATCH
>
> drivers/net/ethernet/smsc/smc91x.h | 12 ++++++++----
> 1 file changed, 8 insertions(+), 4 deletions(-)
>
> diff --git a/drivers/net/ethernet/smsc/smc91x.h b/drivers/net/ethernet/smsc/smc91x.h
> index 38aa4374e813..1290335629c1 100644
> --- a/drivers/net/ethernet/smsc/smc91x.h
> +++ b/drivers/net/ethernet/smsc/smc91x.h
> @@ -142,22 +142,26 @@ static inline void _SMC_outw_align4(u16 val, void __iomem *ioaddr, int reg,
> #define SMC_CAN_USE_32BIT 0
> #define SMC_NOWAIT 1
>
> +/*
> + * Access SMSC device registers using raw IO access primitives. Byte
> + * swap as required for device registers, but not data.
> + */
> static inline void mcf_insw(void __iomem *a, unsigned char *p, int l)
> {
> u16 *wp = (u16 *) p;
> while (l-- > 0)
> - *wp++ = readw(a);
> + *wp++ = __raw_readw(a);
> }
>
> static inline void mcf_outsw(void __iomem *a, unsigned char *p, int l)
> {
> u16 *wp = (u16 *) p;
> while (l-- > 0)
> - writew(*wp++, a);
> + __raw_writew(*wp++, a);
> }
>
> -#define SMC_inw(a, r) ioread16be((a) + (r))
> -#define SMC_outw(lp, v, a, r) iowrite16be(v, (a) + (r))
> +#define SMC_inw(a, r) swab16(__raw_readw((a) + (r)))
> +#define SMC_outw(lp, v, a, r) __raw_writew(swab16(v), (a) + (r))
> #define SMC_insw(a, r, p, l) mcf_insw(a + r, p, l)
> #define SMC_outsw(a, r, p, l) mcf_outsw(a + r, p, l)
>
^ permalink raw reply
* Re: [PATCHv2 1/4] net: fec: do not use readl()/writel() for ColdFire
From: Greg Ungerer @ 2026-07-13 13:15 UTC (permalink / raw)
To: Wei Fang; +Cc: linux-kernel, arnd, frank.li, shenwei.wang, imx, netdev
In-Reply-To: <20260609142139.1563360-3-gerg@linux-m68k.org>
Gentle ping.
This patch is independant and can be applied as-is.
On 10/6/26 00:12, Greg Ungerer wrote:
> Modify the FEC driver to not directly use readl() and writel() to access
> hardware registers but instead local fec_readl() and fec_writel() methods.
> This allows for different architecture users of this driver to have
> different underlying access functions - to support both little and big
> endian hardware.
>
> The FEC hardware block in ColdFire SoC parts is accessed big-endian.
> The usual kernel readl()/writel() IO memory access methods are defined to
> access little endian data. Change access for ColdFire to use __raw_readl()
> and __raw_writel() access methods - which do not modify or swap bytes
> on access.
>
> The FEC driver works today because the m68k architecture io.h has a
> kludge in the definitions of the readl() and writel() functions for
> ColdFire that allow big-endian access if the address of the register to
> access is within the SoC's internal peripheral registers. This is being
> fixed in the near future to define readl() and writel() correctly - with
> no byte swapping. Thus the motivation for this fix here.
>
> __raw_readl()/__raw_writel() access methods are used instead of the more
> commonly used ioread32be()/iowrite32be() here because those are broken too,
> because of the current readl()/writel() kludge. They are implemented in
> asm-generic/io.h in terms of readl()/writel().
>
> Note that even when readl() and writel() are fixed on ColdFire they will
> not be the right thing to use within the FEC driver on ColdFire hardware.
>
> Signed-off-by: Greg Ungerer <gerg@linux-m68k.org>
> ---
> v2: changed from RFC to PATCH
> minor reordering of local variables for preferred layout
>
> drivers/net/ethernet/freescale/fec.h | 15 ++
> drivers/net/ethernet/freescale/fec_main.c | 257 +++++++++++-----------
> drivers/net/ethernet/freescale/fec_ptp.c | 78 +++----
> 3 files changed, 183 insertions(+), 167 deletions(-)
>
> diff --git a/drivers/net/ethernet/freescale/fec.h b/drivers/net/ethernet/freescale/fec.h
> index 7176803146f3..af31d946a638 100644
> --- a/drivers/net/ethernet/freescale/fec.h
> +++ b/drivers/net/ethernet/freescale/fec.h
> @@ -701,5 +701,20 @@ int fec_ptp_set(struct net_device *ndev, struct kernel_hwtstamp_config *config,
> struct netlink_ext_ack *extack);
> void fec_ptp_get(struct net_device *ndev, struct kernel_hwtstamp_config *config);
>
> +/*
> + * ColdFire SoC peripheral blocks are big-endian, so use the raw IO access
> + * functions for them.
> + */
> +#ifdef CONFIG_COLDFIRE
> +#define fec_readl __raw_readl
> +#define fec_writel __raw_writel
> +#define fec_readl_poll_timeout_atomic(addr, val, cond, delay_us, timeout_us) \
> + readx_poll_timeout_atomic(__raw_readl, addr, val, cond, delay_us, timeout_us)
> +#else
> +#define fec_readl readl
> +#define fec_writel writel
> +#define fec_readl_poll_timeout_atomic readl_poll_timeout_atomic
> +#endif
> +
> /****************************************************************************/
> #endif /* FEC_H */
> diff --git a/drivers/net/ethernet/freescale/fec_main.c b/drivers/net/ethernet/freescale/fec_main.c
> index 6ebde65d7f1b..023fde9cfa3b 100644
> --- a/drivers/net/ethernet/freescale/fec_main.c
> +++ b/drivers/net/ethernet/freescale/fec_main.c
> @@ -497,11 +497,11 @@ static void fec_txq_trigger_xmit(struct fec_enet_private *fep,
> struct fec_enet_priv_tx_q *txq)
> {
> if (!(fep->quirks & FEC_QUIRK_ERR007885) ||
> - !readl(txq->bd.reg_desc_active) ||
> - !readl(txq->bd.reg_desc_active) ||
> - !readl(txq->bd.reg_desc_active) ||
> - !readl(txq->bd.reg_desc_active))
> - writel(0, txq->bd.reg_desc_active);
> + !fec_readl(txq->bd.reg_desc_active) ||
> + !fec_readl(txq->bd.reg_desc_active) ||
> + !fec_readl(txq->bd.reg_desc_active) ||
> + !fec_readl(txq->bd.reg_desc_active))
> + fec_writel(0, txq->bd.reg_desc_active);
> }
>
> static struct bufdesc *
> @@ -1069,7 +1069,7 @@ static void fec_enet_active_rxring(struct net_device *ndev)
> int i;
>
> for (i = 0; i < fep->num_rx_queues; i++)
> - writel(0, fep->rx_queue[i]->bd.reg_desc_active);
> + fec_writel(0, fep->rx_queue[i]->bd.reg_desc_active);
> }
>
> static void fec_enet_enable_ring(struct net_device *ndev)
> @@ -1081,23 +1081,23 @@ static void fec_enet_enable_ring(struct net_device *ndev)
>
> for (i = 0; i < fep->num_rx_queues; i++) {
> rxq = fep->rx_queue[i];
> - writel(rxq->bd.dma, fep->hwp + FEC_R_DES_START(i));
> - writel(fep->max_buf_size, fep->hwp + FEC_R_BUFF_SIZE(i));
> + fec_writel(rxq->bd.dma, fep->hwp + FEC_R_DES_START(i));
> + fec_writel(fep->max_buf_size, fep->hwp + FEC_R_BUFF_SIZE(i));
>
> /* enable DMA1/2 */
> if (i)
> - writel(RCMR_MATCHEN | RCMR_CMP(i),
> - fep->hwp + FEC_RCMR(i));
> + fec_writel(RCMR_MATCHEN | RCMR_CMP(i),
> + fep->hwp + FEC_RCMR(i));
> }
>
> for (i = 0; i < fep->num_tx_queues; i++) {
> txq = fep->tx_queue[i];
> - writel(txq->bd.dma, fep->hwp + FEC_X_DES_START(i));
> + fec_writel(txq->bd.dma, fep->hwp + FEC_X_DES_START(i));
>
> /* enable DMA1/2 */
> if (i)
> - writel(DMA_CLASS_EN | IDLE_SLOPE(i),
> - fep->hwp + FEC_DMA_CFG(i));
> + fec_writel(DMA_CLASS_EN | IDLE_SLOPE(i),
> + fep->hwp + FEC_DMA_CFG(i));
> }
> }
>
> @@ -1112,15 +1112,15 @@ static void fec_ctrl_reset(struct fec_enet_private *fep, bool allow_wol)
> if (!allow_wol || !(fep->wol_flag & FEC_WOL_FLAG_SLEEP_ON)) {
> if (fep->quirks & FEC_QUIRK_HAS_MULTI_QUEUES ||
> ((fep->quirks & FEC_QUIRK_NO_HARD_RESET) && fep->link)) {
> - writel(0, fep->hwp + FEC_ECNTRL);
> + fec_writel(0, fep->hwp + FEC_ECNTRL);
> } else {
> - writel(FEC_ECR_RESET, fep->hwp + FEC_ECNTRL);
> + fec_writel(FEC_ECR_RESET, fep->hwp + FEC_ECNTRL);
> udelay(10);
> }
> } else {
> - val = readl(fep->hwp + FEC_ECNTRL);
> + val = fec_readl(fep->hwp + FEC_ECNTRL);
> val |= (FEC_ECR_MAGICEN | FEC_ECR_SLEEP);
> - writel(val, fep->hwp + FEC_ECNTRL);
> + fec_writel(val, fep->hwp + FEC_ECNTRL);
> }
> }
>
> @@ -1128,11 +1128,11 @@ static void fec_set_hw_mac_addr(struct net_device *ndev)
> {
> struct fec_enet_private *fep = netdev_priv(ndev);
>
> - writel(ndev->dev_addr[3] | (ndev->dev_addr[2] << 8) |
> - (ndev->dev_addr[1] << 16) | (ndev->dev_addr[0] << 24),
> - fep->hwp + FEC_ADDR_LOW);
> - writel((ndev->dev_addr[5] << 16) | (ndev->dev_addr[4] << 24),
> - fep->hwp + FEC_ADDR_HIGH);
> + fec_writel(ndev->dev_addr[3] | (ndev->dev_addr[2] << 8) |
> + (ndev->dev_addr[1] << 16) | (ndev->dev_addr[0] << 24),
> + fep->hwp + FEC_ADDR_LOW);
> + fec_writel((ndev->dev_addr[5] << 16) | (ndev->dev_addr[4] << 24),
> + fep->hwp + FEC_ADDR_HIGH);
> }
>
> /*
> @@ -1162,7 +1162,7 @@ fec_restart(struct net_device *ndev)
> fec_set_hw_mac_addr(ndev);
>
> /* Clear any outstanding interrupt, except MDIO. */
> - writel((0xffffffff & ~FEC_ENET_MII), fep->hwp + FEC_IEVENT);
> + fec_writel((0xffffffff & ~FEC_ENET_MII), fep->hwp + FEC_IEVENT);
>
> fec_enet_bd_init(ndev);
>
> @@ -1171,19 +1171,19 @@ fec_restart(struct net_device *ndev)
> /* Enable MII mode */
> if (fep->full_duplex == DUPLEX_FULL) {
> /* FD enable */
> - writel(0x04, fep->hwp + FEC_X_CNTRL);
> + fec_writel(0x04, fep->hwp + FEC_X_CNTRL);
> } else {
> /* No Rcv on Xmit */
> rcntl |= FEC_RCR_DRT;
> - writel(0x0, fep->hwp + FEC_X_CNTRL);
> + fec_writel(0x0, fep->hwp + FEC_X_CNTRL);
> }
>
> /* Set MII speed */
> - writel(fep->phy_speed, fep->hwp + FEC_MII_SPEED);
> + fec_writel(fep->phy_speed, fep->hwp + FEC_MII_SPEED);
>
> #if !defined(CONFIG_M5272)
> if (fep->quirks & FEC_QUIRK_HAS_RACC) {
> - u32 val = readl(fep->hwp + FEC_RACC);
> + u32 val = fec_readl(fep->hwp + FEC_RACC);
>
> /* align IP header */
> val |= FEC_RACC_SHIFT16;
> @@ -1192,8 +1192,8 @@ fec_restart(struct net_device *ndev)
> val |= FEC_RACC_OPTIONS;
> else
> val &= ~FEC_RACC_OPTIONS;
> - writel(val, fep->hwp + FEC_RACC);
> - writel(min(fep->rx_frame_size, fep->max_buf_size), fep->hwp + FEC_FTRL);
> + fec_writel(val, fep->hwp + FEC_RACC);
> + fec_writel(min(fep->rx_frame_size, fep->max_buf_size), fep->hwp + FEC_FTRL);
> }
> #endif
>
> @@ -1227,8 +1227,8 @@ fec_restart(struct net_device *ndev)
> if (fep->quirks & FEC_QUIRK_USE_GASKET) {
> u32 cfgr;
> /* disable the gasket and wait */
> - writel(0, fep->hwp + FEC_MIIGSK_ENR);
> - while (readl(fep->hwp + FEC_MIIGSK_ENR) & 4)
> + fec_writel(0, fep->hwp + FEC_MIIGSK_ENR);
> + while (fec_readl(fep->hwp + FEC_MIIGSK_ENR) & 4)
> udelay(1);
>
> /*
> @@ -1240,10 +1240,10 @@ fec_restart(struct net_device *ndev)
> ? BM_MIIGSK_CFGR_RMII : BM_MIIGSK_CFGR_MII;
> if (ndev->phydev && ndev->phydev->speed == SPEED_10)
> cfgr |= BM_MIIGSK_CFGR_FRCONT_10M;
> - writel(cfgr, fep->hwp + FEC_MIIGSK_CFGR);
> + fec_writel(cfgr, fep->hwp + FEC_MIIGSK_CFGR);
>
> /* re-enable the gasket */
> - writel(2, fep->hwp + FEC_MIIGSK_ENR);
> + fec_writel(2, fep->hwp + FEC_MIIGSK_ENR);
> }
> #endif
> }
> @@ -1256,25 +1256,25 @@ fec_restart(struct net_device *ndev)
> rcntl |= FEC_RCR_FLOWCTL;
>
> /* set FIFO threshold parameter to reduce overrun */
> - writel(FEC_ENET_RSEM_V, fep->hwp + FEC_R_FIFO_RSEM);
> - writel(FEC_ENET_RSFL_V, fep->hwp + FEC_R_FIFO_RSFL);
> - writel(FEC_ENET_RAEM_V, fep->hwp + FEC_R_FIFO_RAEM);
> - writel(FEC_ENET_RAFL_V, fep->hwp + FEC_R_FIFO_RAFL);
> + fec_writel(FEC_ENET_RSEM_V, fep->hwp + FEC_R_FIFO_RSEM);
> + fec_writel(FEC_ENET_RSFL_V, fep->hwp + FEC_R_FIFO_RSFL);
> + fec_writel(FEC_ENET_RAEM_V, fep->hwp + FEC_R_FIFO_RAEM);
> + fec_writel(FEC_ENET_RAFL_V, fep->hwp + FEC_R_FIFO_RAFL);
>
> /* OPD */
> - writel(FEC_ENET_OPD_V, fep->hwp + FEC_OPD);
> + fec_writel(FEC_ENET_OPD_V, fep->hwp + FEC_OPD);
> } else {
> rcntl &= ~FEC_RCR_FLOWCTL;
> }
> #endif /* !defined(CONFIG_M5272) */
>
> - writel(rcntl, fep->hwp + FEC_R_CNTRL);
> + fec_writel(rcntl, fep->hwp + FEC_R_CNTRL);
>
> /* Setup multicast filter. */
> set_multicast_list(ndev);
> #ifndef CONFIG_M5272
> - writel(0, fep->hwp + FEC_HASH_TABLE_HIGH);
> - writel(0, fep->hwp + FEC_HASH_TABLE_LOW);
> + fec_writel(0, fep->hwp + FEC_HASH_TABLE_HIGH);
> + fec_writel(0, fep->hwp + FEC_HASH_TABLE_LOW);
> #endif
>
> if (fep->quirks & FEC_QUIRK_ENET_MAC) {
> @@ -1290,9 +1290,9 @@ fec_restart(struct net_device *ndev)
> */
> if ((fep->quirks & FEC_QUIRK_JUMBO_FRAME) &&
> (ndev->mtu > (PKT_MAXBUF_SIZE - VLAN_ETH_HLEN - ETH_FCS_LEN)))
> - writel(0xF, fep->hwp + FEC_X_WMRK);
> + fec_writel(0xF, fep->hwp + FEC_X_WMRK);
> else
> - writel(FEC_TXWMRK_STRFWD, fep->hwp + FEC_X_WMRK);
> + fec_writel(FEC_TXWMRK_STRFWD, fep->hwp + FEC_X_WMRK);
> }
>
> if (fep->bufdesc_ex)
> @@ -1307,11 +1307,11 @@ fec_restart(struct net_device *ndev)
>
> #ifndef CONFIG_M5272
> /* Enable the MIB statistic event counters */
> - writel(0 << 31, fep->hwp + FEC_MIB_CTRLSTAT);
> + fec_writel(0 << 31, fep->hwp + FEC_MIB_CTRLSTAT);
> #endif
>
> /* And last, enable the transmit and receive processing */
> - writel(ecntl, fep->hwp + FEC_ECNTRL);
> + fec_writel(ecntl, fep->hwp + FEC_ECNTRL);
> fec_enet_active_rxring(ndev);
>
> if (fep->bufdesc_ex) {
> @@ -1321,9 +1321,9 @@ fec_restart(struct net_device *ndev)
>
> /* Enable interrupts we wish to service */
> if (fep->link)
> - writel(FEC_DEFAULT_IMASK, fep->hwp + FEC_IMASK);
> + fec_writel(FEC_DEFAULT_IMASK, fep->hwp + FEC_IMASK);
> else
> - writel(0, fep->hwp + FEC_IMASK);
> + fec_writel(0, fep->hwp + FEC_IMASK);
>
> /* Init the interrupt coalescing */
> if (fep->quirks & FEC_QUIRK_HAS_COALESCE)
> @@ -1384,29 +1384,30 @@ static void fec_irqs_disable(struct net_device *ndev)
> {
> struct fec_enet_private *fep = netdev_priv(ndev);
>
> - writel(0, fep->hwp + FEC_IMASK);
> + fec_writel(0, fep->hwp + FEC_IMASK);
> }
>
> static void fec_irqs_disable_except_wakeup(struct net_device *ndev)
> {
> struct fec_enet_private *fep = netdev_priv(ndev);
>
> - writel(0, fep->hwp + FEC_IMASK);
> - writel(FEC_ENET_WAKEUP, fep->hwp + FEC_IMASK);
> + fec_writel(0, fep->hwp + FEC_IMASK);
> + fec_writel(FEC_ENET_WAKEUP, fep->hwp + FEC_IMASK);
> }
>
> static void
> fec_stop(struct net_device *ndev)
> {
> struct fec_enet_private *fep = netdev_priv(ndev);
> - u32 rmii_mode = readl(fep->hwp + FEC_R_CNTRL) & FEC_RCR_RMII;
> - u32 val;
> + u32 rmii_mode, val;
> +
> + rmii_mode = fec_readl(fep->hwp + FEC_R_CNTRL) & FEC_RCR_RMII;
>
> /* We cannot expect a graceful transmit stop without link !!! */
> if (fep->link) {
> - writel(1, fep->hwp + FEC_X_CNTRL); /* Graceful transmit stop */
> + fec_writel(1, fep->hwp + FEC_X_CNTRL); /* Graceful transmit stop */
> udelay(10);
> - if (!(readl(fep->hwp + FEC_IEVENT) & FEC_ENET_GRA))
> + if (!(fec_readl(fep->hwp + FEC_IEVENT) & FEC_ENET_GRA))
> netdev_err(ndev, "Graceful transmit stop did not complete!\n");
> }
>
> @@ -1414,20 +1415,20 @@ fec_stop(struct net_device *ndev)
> fec_ptp_save_state(fep);
>
> fec_ctrl_reset(fep, true);
> - writel(fep->phy_speed, fep->hwp + FEC_MII_SPEED);
> - writel(FEC_DEFAULT_IMASK, fep->hwp + FEC_IMASK);
> + fec_writel(fep->phy_speed, fep->hwp + FEC_MII_SPEED);
> + fec_writel(FEC_DEFAULT_IMASK, fep->hwp + FEC_IMASK);
>
> /* We have to keep ENET enabled to have MII interrupt stay working */
> if (fep->quirks & FEC_QUIRK_ENET_MAC &&
> !(fep->wol_flag & FEC_WOL_FLAG_SLEEP_ON)) {
> - writel(FEC_ECR_ETHEREN, fep->hwp + FEC_ECNTRL);
> - writel(rmii_mode, fep->hwp + FEC_R_CNTRL);
> + fec_writel(FEC_ECR_ETHEREN, fep->hwp + FEC_ECNTRL);
> + fec_writel(rmii_mode, fep->hwp + FEC_R_CNTRL);
> }
>
> if (fep->bufdesc_ex) {
> - val = readl(fep->hwp + FEC_ECNTRL);
> + val = fec_readl(fep->hwp + FEC_ECNTRL);
> val |= FEC_ECR_EN1588;
> - writel(val, fep->hwp + FEC_ECNTRL);
> + fec_writel(val, fep->hwp + FEC_ECNTRL);
>
> fec_ptp_start_cyclecounter(ndev);
> fec_ptp_restore_state(fep);
> @@ -1713,8 +1714,8 @@ static int fec_enet_tx_queue(struct fec_enet_private *fep,
>
> /* ERR006358: Keep the transmitter going */
> if (bdp != txq->bd.cur &&
> - readl(txq->bd.reg_desc_active) == 0)
> - writel(0, txq->bd.reg_desc_active);
> + fec_readl(txq->bd.reg_desc_active) == 0)
> + fec_writel(0, txq->bd.reg_desc_active);
>
> if (txq->xsk_pool) {
> struct xsk_buff_pool *pool = txq->xsk_pool;
> @@ -1923,7 +1924,7 @@ static int fec_enet_rx_queue(struct fec_enet_private *fep,
> break;
> pkt_received++;
>
> - writel(FEC_ENET_RXF_GET(queue), fep->hwp + FEC_IEVENT);
> + fec_writel(FEC_ENET_RXF_GET(queue), fep->hwp + FEC_IEVENT);
>
> /* Check for errors. */
> status ^= BD_ENET_RX_LAST;
> @@ -1991,7 +1992,7 @@ static int fec_enet_rx_queue(struct fec_enet_private *fep,
> * incoming frames. On a heavily loaded network, we should be
> * able to keep up at the expense of system resources.
> */
> - writel(0, rxq->bd.reg_desc_active);
> + fec_writel(0, rxq->bd.reg_desc_active);
> }
> rxq->bd.cur = bdp;
>
> @@ -2053,7 +2054,7 @@ static int fec_enet_rx_queue_xdp(struct fec_enet_private *fep, int queue,
> break;
> pkt_received++;
>
> - writel(FEC_ENET_RXF_GET(queue), fep->hwp + FEC_IEVENT);
> + fec_writel(FEC_ENET_RXF_GET(queue), fep->hwp + FEC_IEVENT);
>
> /* Check for errors. */
> status ^= BD_ENET_RX_LAST;
> @@ -2166,7 +2167,7 @@ static int fec_enet_rx_queue_xdp(struct fec_enet_private *fep, int queue,
> * incoming frames. On a heavily loaded network, we should be
> * able to keep up at the expense of system resources.
> */
> - writel(0, rxq->bd.reg_desc_active);
> + fec_writel(0, rxq->bd.reg_desc_active);
> }
>
> rxq->bd.cur = bdp;
> @@ -2296,7 +2297,7 @@ static int fec_enet_rx_queue_xsk(struct fec_enet_private *fep, int queue,
> if (unlikely(pkt_received >= budget))
> break;
>
> - writel(FEC_ENET_RXF_GET(queue), fep->hwp + FEC_IEVENT);
> + fec_writel(FEC_ENET_RXF_GET(queue), fep->hwp + FEC_IEVENT);
>
> index = fec_enet_get_bd_index(bdp, &rxq->bd);
> xsk = rxq->rx_buf[index].xdp;
> @@ -2428,7 +2429,7 @@ static int fec_enet_rx_queue_xsk(struct fec_enet_private *fep, int queue,
> * incoming frames. On a heavily loaded network, we should be
> * able to keep up at the expense of system resources.
> */
> - writel(0, rxq->bd.reg_desc_active);
> + fec_writel(0, rxq->bd.reg_desc_active);
> }
>
> rxq->bd.cur = bdp;
> @@ -2475,12 +2476,12 @@ static bool fec_enet_collect_events(struct fec_enet_private *fep)
> {
> uint int_events;
>
> - int_events = readl(fep->hwp + FEC_IEVENT);
> + int_events = fec_readl(fep->hwp + FEC_IEVENT);
>
> /* Don't clear MDIO events, we poll for those */
> int_events &= ~FEC_ENET_MII;
>
> - writel(int_events, fep->hwp + FEC_IEVENT);
> + fec_writel(int_events, fep->hwp + FEC_IEVENT);
>
> return int_events != 0;
> }
> @@ -2497,7 +2498,7 @@ fec_enet_interrupt(int irq, void *dev_id)
>
> if (napi_schedule_prep(&fep->napi)) {
> /* Disable interrupts */
> - writel(0, fep->hwp + FEC_IMASK);
> + fec_writel(0, fep->hwp + FEC_IMASK);
> __napi_schedule(&fep->napi);
> }
> }
> @@ -2520,7 +2521,7 @@ static int fec_enet_rx_napi(struct napi_struct *napi, int budget)
>
> if (max_done < budget) {
> napi_complete_done(napi, max_done);
> - writel(FEC_DEFAULT_IMASK, fep->hwp + FEC_IMASK);
> + fec_writel(FEC_DEFAULT_IMASK, fep->hwp + FEC_IMASK);
> return max_done;
> }
>
> @@ -2576,9 +2577,9 @@ static int fec_get_mac(struct net_device *ndev)
> */
> if (!is_valid_ether_addr(iap)) {
> *((__be32 *) &tmpaddr[0]) =
> - cpu_to_be32(readl(fep->hwp + FEC_ADDR_LOW));
> + cpu_to_be32(fec_readl(fep->hwp + FEC_ADDR_LOW));
> *((__be16 *) &tmpaddr[4]) =
> - cpu_to_be16(readl(fep->hwp + FEC_ADDR_HIGH) >> 16);
> + cpu_to_be16(fec_readl(fep->hwp + FEC_ADDR_HIGH) >> 16);
> iap = &tmpaddr[0];
> }
>
> @@ -2630,8 +2631,8 @@ static int fec_enet_eee_mode_set(struct net_device *ndev, u32 lpi_timer,
> wake_cycle = 0;
> }
>
> - writel(sleep_cycle, fep->hwp + FEC_LPI_SLEEP);
> - writel(wake_cycle, fep->hwp + FEC_LPI_WAKE);
> + fec_writel(sleep_cycle, fep->hwp + FEC_LPI_SLEEP);
> + fec_writel(wake_cycle, fep->hwp + FEC_LPI_WAKE);
>
> return 0;
> }
> @@ -2701,11 +2702,11 @@ static int fec_enet_mdio_wait(struct fec_enet_private *fep)
> uint ievent;
> int ret;
>
> - ret = readl_poll_timeout_atomic(fep->hwp + FEC_IEVENT, ievent,
> - ievent & FEC_ENET_MII, 2, 30000);
> + ret = fec_readl_poll_timeout_atomic(fep->hwp + FEC_IEVENT, ievent,
> + ievent & FEC_ENET_MII, 2, 30000);
>
> if (!ret)
> - writel(FEC_ENET_MII, fep->hwp + FEC_IEVENT);
> + fec_writel(FEC_ENET_MII, fep->hwp + FEC_IEVENT);
>
> return ret;
> }
> @@ -2726,9 +2727,9 @@ static int fec_enet_mdio_read_c22(struct mii_bus *bus, int mii_id, int regnum)
> frame_addr = regnum;
>
> /* start a read op */
> - writel(frame_start | frame_op |
> - FEC_MMFR_PA(mii_id) | FEC_MMFR_RA(frame_addr) |
> - FEC_MMFR_TA, fep->hwp + FEC_MII_DATA);
> + fec_writel(frame_start | frame_op |
> + FEC_MMFR_PA(mii_id) | FEC_MMFR_RA(frame_addr) |
> + FEC_MMFR_TA, fep->hwp + FEC_MII_DATA);
>
> /* wait for end of transfer */
> ret = fec_enet_mdio_wait(fep);
> @@ -2737,7 +2738,7 @@ static int fec_enet_mdio_read_c22(struct mii_bus *bus, int mii_id, int regnum)
> goto out;
> }
>
> - ret = FEC_MMFR_DATA(readl(fep->hwp + FEC_MII_DATA));
> + ret = FEC_MMFR_DATA(fec_readl(fep->hwp + FEC_MII_DATA));
>
> out:
> pm_runtime_put_autosuspend(dev);
> @@ -2759,10 +2760,10 @@ static int fec_enet_mdio_read_c45(struct mii_bus *bus, int mii_id,
> frame_start = FEC_MMFR_ST_C45;
>
> /* write address */
> - writel(frame_start | FEC_MMFR_OP_ADDR_WRITE |
> - FEC_MMFR_PA(mii_id) | FEC_MMFR_RA(devad) |
> - FEC_MMFR_TA | (regnum & 0xFFFF),
> - fep->hwp + FEC_MII_DATA);
> + fec_writel(frame_start | FEC_MMFR_OP_ADDR_WRITE |
> + FEC_MMFR_PA(mii_id) | FEC_MMFR_RA(devad) |
> + FEC_MMFR_TA | (regnum & 0xFFFF),
> + fep->hwp + FEC_MII_DATA);
>
> /* wait for end of transfer */
> ret = fec_enet_mdio_wait(fep);
> @@ -2774,9 +2775,9 @@ static int fec_enet_mdio_read_c45(struct mii_bus *bus, int mii_id,
> frame_op = FEC_MMFR_OP_READ_C45;
>
> /* start a read op */
> - writel(frame_start | frame_op |
> - FEC_MMFR_PA(mii_id) | FEC_MMFR_RA(devad) |
> - FEC_MMFR_TA, fep->hwp + FEC_MII_DATA);
> + fec_writel(frame_start | frame_op |
> + FEC_MMFR_PA(mii_id) | FEC_MMFR_RA(devad) |
> + FEC_MMFR_TA, fep->hwp + FEC_MII_DATA);
>
> /* wait for end of transfer */
> ret = fec_enet_mdio_wait(fep);
> @@ -2785,7 +2786,7 @@ static int fec_enet_mdio_read_c45(struct mii_bus *bus, int mii_id,
> goto out;
> }
>
> - ret = FEC_MMFR_DATA(readl(fep->hwp + FEC_MII_DATA));
> + ret = FEC_MMFR_DATA(fec_readl(fep->hwp + FEC_MII_DATA));
>
> out:
> pm_runtime_put_autosuspend(dev);
> @@ -2809,10 +2810,10 @@ static int fec_enet_mdio_write_c22(struct mii_bus *bus, int mii_id, int regnum,
> frame_addr = regnum;
>
> /* start a write op */
> - writel(frame_start | FEC_MMFR_OP_WRITE |
> - FEC_MMFR_PA(mii_id) | FEC_MMFR_RA(frame_addr) |
> - FEC_MMFR_TA | FEC_MMFR_DATA(value),
> - fep->hwp + FEC_MII_DATA);
> + fec_writel(frame_start | FEC_MMFR_OP_WRITE |
> + FEC_MMFR_PA(mii_id) | FEC_MMFR_RA(frame_addr) |
> + FEC_MMFR_TA | FEC_MMFR_DATA(value),
> + fep->hwp + FEC_MII_DATA);
>
> /* wait for end of transfer */
> ret = fec_enet_mdio_wait(fep);
> @@ -2838,10 +2839,10 @@ static int fec_enet_mdio_write_c45(struct mii_bus *bus, int mii_id,
> frame_start = FEC_MMFR_ST_C45;
>
> /* write address */
> - writel(frame_start | FEC_MMFR_OP_ADDR_WRITE |
> - FEC_MMFR_PA(mii_id) | FEC_MMFR_RA(devad) |
> - FEC_MMFR_TA | (regnum & 0xFFFF),
> - fep->hwp + FEC_MII_DATA);
> + fec_writel(frame_start | FEC_MMFR_OP_ADDR_WRITE |
> + FEC_MMFR_PA(mii_id) | FEC_MMFR_RA(devad) |
> + FEC_MMFR_TA | (regnum & 0xFFFF),
> + fep->hwp + FEC_MII_DATA);
>
> /* wait for end of transfer */
> ret = fec_enet_mdio_wait(fep);
> @@ -2851,10 +2852,10 @@ static int fec_enet_mdio_write_c45(struct mii_bus *bus, int mii_id,
> }
>
> /* start a write op */
> - writel(frame_start | FEC_MMFR_OP_WRITE |
> - FEC_MMFR_PA(mii_id) | FEC_MMFR_RA(devad) |
> - FEC_MMFR_TA | FEC_MMFR_DATA(value),
> - fep->hwp + FEC_MII_DATA);
> + fec_writel(frame_start | FEC_MMFR_OP_WRITE |
> + FEC_MMFR_PA(mii_id) | FEC_MMFR_RA(devad) |
> + FEC_MMFR_TA | FEC_MMFR_DATA(value),
> + fep->hwp + FEC_MII_DATA);
>
> /* wait for end of transfer */
> ret = fec_enet_mdio_wait(fep);
> @@ -3132,13 +3133,13 @@ static int fec_enet_mii_init(struct platform_device *pdev)
> * - writing MMFR:
> * - mscr[7:0]_not_zero
> */
> - writel(0, fep->hwp + FEC_MII_DATA);
> + fec_writel(0, fep->hwp + FEC_MII_DATA);
> }
>
> - writel(fep->phy_speed, fep->hwp + FEC_MII_SPEED);
> + fec_writel(fep->phy_speed, fep->hwp + FEC_MII_SPEED);
>
> /* Clear any pending transaction complete indication */
> - writel(FEC_ENET_MII, fep->hwp + FEC_IEVENT);
> + fec_writel(FEC_ENET_MII, fep->hwp + FEC_IEVENT);
>
> fep->mii_bus = mdiobus_alloc();
> if (fep->mii_bus == NULL) {
> @@ -3320,7 +3321,7 @@ static void fec_enet_get_regs(struct net_device *ndev,
> continue;
>
> off >>= 2;
> - buf[off] = readl(&theregs[off]);
> + buf[off] = fec_readl(&theregs[off]);
> }
>
> pm_runtime_put_autosuspend(dev);
> @@ -3487,7 +3488,7 @@ static void fec_enet_update_ethtool_stats(struct net_device *dev)
> int i;
>
> for (i = 0; i < ARRAY_SIZE(fec_stats); i++)
> - fep->ethtool_stats[i] = readl(fep->hwp + fec_stats[i].offset);
> + fep->ethtool_stats[i] = fec_readl(fep->hwp + fec_stats[i].offset);
> }
>
> static void fec_enet_get_xdp_stats(struct fec_enet_private *fep, u64 *data)
> @@ -3588,10 +3589,10 @@ static void fec_enet_clear_ethtool_stats(struct net_device *dev)
> int i, j;
>
> /* Disable MIB statistics counters */
> - writel(FEC_MIB_CTRLSTAT_DISABLE, fep->hwp + FEC_MIB_CTRLSTAT);
> + fec_writel(FEC_MIB_CTRLSTAT_DISABLE, fep->hwp + FEC_MIB_CTRLSTAT);
>
> for (i = 0; i < ARRAY_SIZE(fec_stats); i++)
> - writel(0, fep->hwp + fec_stats[i].offset);
> + fec_writel(0, fep->hwp + fec_stats[i].offset);
>
> for (i = fep->num_rx_queues - 1; i >= 0; i--) {
> rxq = fep->rx_queue[i];
> @@ -3600,7 +3601,7 @@ static void fec_enet_clear_ethtool_stats(struct net_device *dev)
> }
>
> /* Don't disable MIB statistics counters */
> - writel(0, fep->hwp + FEC_MIB_CTRLSTAT);
> + fec_writel(0, fep->hwp + FEC_MIB_CTRLSTAT);
> }
>
> #else /* !defined(CONFIG_M5272) */
> @@ -3649,13 +3650,13 @@ static void fec_enet_itr_coal_set(struct net_device *ndev)
> tx_itr |= FEC_ITR_ICTT(tx_ictt);
> }
>
> - writel(tx_itr, fep->hwp + FEC_TXIC0);
> - writel(rx_itr, fep->hwp + FEC_RXIC0);
> + fec_writel(tx_itr, fep->hwp + FEC_TXIC0);
> + fec_writel(rx_itr, fep->hwp + FEC_RXIC0);
> if (fep->quirks & FEC_QUIRK_HAS_MULTI_QUEUES) {
> - writel(tx_itr, fep->hwp + FEC_TXIC1);
> - writel(rx_itr, fep->hwp + FEC_RXIC1);
> - writel(tx_itr, fep->hwp + FEC_TXIC2);
> - writel(rx_itr, fep->hwp + FEC_RXIC2);
> + fec_writel(tx_itr, fep->hwp + FEC_TXIC1);
> + fec_writel(rx_itr, fep->hwp + FEC_RXIC1);
> + fec_writel(tx_itr, fep->hwp + FEC_TXIC2);
> + fec_writel(rx_itr, fep->hwp + FEC_RXIC2);
> }
> }
>
> @@ -4281,22 +4282,22 @@ static void set_multicast_list(struct net_device *ndev)
> unsigned int hash_high = 0, hash_low = 0;
>
> if (ndev->flags & IFF_PROMISC) {
> - tmp = readl(fep->hwp + FEC_R_CNTRL);
> + tmp = fec_readl(fep->hwp + FEC_R_CNTRL);
> tmp |= 0x8;
> - writel(tmp, fep->hwp + FEC_R_CNTRL);
> + fec_writel(tmp, fep->hwp + FEC_R_CNTRL);
> return;
> }
>
> - tmp = readl(fep->hwp + FEC_R_CNTRL);
> + tmp = fec_readl(fep->hwp + FEC_R_CNTRL);
> tmp &= ~0x8;
> - writel(tmp, fep->hwp + FEC_R_CNTRL);
> + fec_writel(tmp, fep->hwp + FEC_R_CNTRL);
>
> if (ndev->flags & IFF_ALLMULTI) {
> /* Catch all multicast addresses, so set the
> * filter to all 1's
> */
> - writel(0xffffffff, fep->hwp + FEC_GRP_HASH_TABLE_HIGH);
> - writel(0xffffffff, fep->hwp + FEC_GRP_HASH_TABLE_LOW);
> + fec_writel(0xffffffff, fep->hwp + FEC_GRP_HASH_TABLE_HIGH);
> + fec_writel(0xffffffff, fep->hwp + FEC_GRP_HASH_TABLE_LOW);
>
> return;
> }
> @@ -4317,8 +4318,8 @@ static void set_multicast_list(struct net_device *ndev)
> hash_low |= 1 << hash;
> }
>
> - writel(hash_high, fep->hwp + FEC_GRP_HASH_TABLE_HIGH);
> - writel(hash_low, fep->hwp + FEC_GRP_HASH_TABLE_LOW);
> + fec_writel(hash_high, fep->hwp + FEC_GRP_HASH_TABLE_HIGH);
> + fec_writel(hash_low, fep->hwp + FEC_GRP_HASH_TABLE_LOW);
> }
>
> /* Set a MAC change in hardware. */
> @@ -5003,7 +5004,7 @@ static int fec_enet_init(struct net_device *ndev)
> ndev->netdev_ops = &fec_netdev_ops;
> ndev->ethtool_ops = &fec_enet_ethtool_ops;
>
> - writel(FEC_RX_DISABLED_IMASK, fep->hwp + FEC_IMASK);
> + fec_writel(FEC_RX_DISABLED_IMASK, fep->hwp + FEC_IMASK);
> netif_napi_add(ndev, &fep->napi, fec_enet_rx_napi);
>
> if (fep->quirks & FEC_QUIRK_HAS_VLAN)
> @@ -5607,9 +5608,9 @@ static int fec_resume(struct device *dev)
> enable_irq(fep->wake_irq);
> }
>
> - val = readl(fep->hwp + FEC_ECNTRL);
> + val = fec_readl(fep->hwp + FEC_ECNTRL);
> val &= ~(FEC_ECR_MAGICEN | FEC_ECR_SLEEP);
> - writel(val, fep->hwp + FEC_ECNTRL);
> + fec_writel(val, fep->hwp + FEC_ECNTRL);
> fep->wol_flag &= ~FEC_WOL_FLAG_SLEEP_ON;
> }
> fec_restart(ndev);
> diff --git a/drivers/net/ethernet/freescale/fec_ptp.c b/drivers/net/ethernet/freescale/fec_ptp.c
> index 56801c2009d5..07062599b522 100644
> --- a/drivers/net/ethernet/freescale/fec_ptp.c
> +++ b/drivers/net/ethernet/freescale/fec_ptp.c
> @@ -102,14 +102,14 @@ static u64 fec_ptp_read(struct cyclecounter *cc)
> container_of(cc, struct fec_enet_private, cc);
> u32 tempval;
>
> - tempval = readl(fep->hwp + FEC_ATIME_CTRL);
> + tempval = fec_readl(fep->hwp + FEC_ATIME_CTRL);
> tempval |= FEC_T_CTRL_CAPTURE;
> - writel(tempval, fep->hwp + FEC_ATIME_CTRL);
> + fec_writel(tempval, fep->hwp + FEC_ATIME_CTRL);
>
> if (fep->quirks & FEC_QUIRK_BUG_CAPTURE)
> udelay(1);
>
> - return readl(fep->hwp + FEC_ATIME);
> + return fec_readl(fep->hwp + FEC_ATIME);
> }
>
> /**
> @@ -142,17 +142,17 @@ static int fec_ptp_enable_pps(struct fec_enet_private *fep, uint enable)
> if (enable) {
> /* clear capture or output compare interrupt status if have.
> */
> - writel(FEC_T_TF_MASK, fep->hwp + FEC_TCSR(fep->pps_channel));
> + fec_writel(FEC_T_TF_MASK, fep->hwp + FEC_TCSR(fep->pps_channel));
>
> /* It is recommended to double check the TMODE field in the
> * TCSR register to be cleared before the first compare counter
> * is written into TCCR register. Just add a double check.
> */
> - val = readl(fep->hwp + FEC_TCSR(fep->pps_channel));
> + val = fec_readl(fep->hwp + FEC_TCSR(fep->pps_channel));
> do {
> val &= ~(FEC_T_TMODE_MASK);
> - writel(val, fep->hwp + FEC_TCSR(fep->pps_channel));
> - val = readl(fep->hwp + FEC_TCSR(fep->pps_channel));
> + fec_writel(val, fep->hwp + FEC_TCSR(fep->pps_channel));
> + val = fec_readl(fep->hwp + FEC_TCSR(fep->pps_channel));
> } while (val & FEC_T_TMODE_MASK);
>
> /* Dummy read counter to update the counter */
> @@ -194,31 +194,31 @@ static int fec_ptp_enable_pps(struct fec_enet_private *fep, uint enable)
> * is bigger than fep->cc.mask would be a error.
> */
> val &= fep->cc.mask;
> - writel(val, fep->hwp + FEC_TCCR(fep->pps_channel));
> + fec_writel(val, fep->hwp + FEC_TCCR(fep->pps_channel));
>
> /* Calculate the second the compare event timestamp */
> fep->next_counter = (val + fep->reload_period) & fep->cc.mask;
>
> /* * Enable compare event when overflow */
> - val = readl(fep->hwp + FEC_ATIME_CTRL);
> + val = fec_readl(fep->hwp + FEC_ATIME_CTRL);
> val |= FEC_T_CTRL_PINPER;
> - writel(val, fep->hwp + FEC_ATIME_CTRL);
> + fec_writel(val, fep->hwp + FEC_ATIME_CTRL);
>
> /* Compare channel setting. */
> - val = readl(fep->hwp + FEC_TCSR(fep->pps_channel));
> + val = fec_readl(fep->hwp + FEC_TCSR(fep->pps_channel));
> val |= (1 << FEC_T_TF_OFFSET | 1 << FEC_T_TIE_OFFSET);
> val &= ~(1 << FEC_T_TDRE_OFFSET);
> val &= ~(FEC_T_TMODE_MASK);
> val |= (FEC_HIGH_PULSE << FEC_T_TMODE_OFFSET);
> - writel(val, fep->hwp + FEC_TCSR(fep->pps_channel));
> + fec_writel(val, fep->hwp + FEC_TCSR(fep->pps_channel));
>
> /* Write the second compare event timestamp and calculate
> * the third timestamp. Refer the TCCR register detail in the spec.
> */
> - writel(fep->next_counter, fep->hwp + FEC_TCCR(fep->pps_channel));
> + fec_writel(fep->next_counter, fep->hwp + FEC_TCCR(fep->pps_channel));
> fep->next_counter = (fep->next_counter + fep->reload_period) & fep->cc.mask;
> } else {
> - writel(0, fep->hwp + FEC_TCSR(fep->pps_channel));
> + fec_writel(0, fep->hwp + FEC_TCSR(fep->pps_channel));
> }
>
> fep->pps_enable = enable;
> @@ -258,26 +258,26 @@ static int fec_ptp_pps_perout(struct fec_enet_private *fep)
> compare_val = fep->perout_stime - curr_time + ptp_hc;
> compare_val &= fep->cc.mask;
>
> - writel(compare_val, fep->hwp + FEC_TCCR(fep->pps_channel));
> + fec_writel(compare_val, fep->hwp + FEC_TCCR(fep->pps_channel));
> fep->next_counter = (compare_val + fep->reload_period) & fep->cc.mask;
>
> /* Enable compare event when overflow */
> - temp_val = readl(fep->hwp + FEC_ATIME_CTRL);
> + temp_val = fec_readl(fep->hwp + FEC_ATIME_CTRL);
> temp_val |= FEC_T_CTRL_PINPER;
> - writel(temp_val, fep->hwp + FEC_ATIME_CTRL);
> + fec_writel(temp_val, fep->hwp + FEC_ATIME_CTRL);
>
> /* Compare channel setting. */
> - temp_val = readl(fep->hwp + FEC_TCSR(fep->pps_channel));
> + temp_val = fec_readl(fep->hwp + FEC_TCSR(fep->pps_channel));
> temp_val |= (1 << FEC_T_TF_OFFSET | 1 << FEC_T_TIE_OFFSET);
> temp_val &= ~(1 << FEC_T_TDRE_OFFSET);
> temp_val &= ~(FEC_T_TMODE_MASK);
> temp_val |= (FEC_TMODE_TOGGLE << FEC_T_TMODE_OFFSET);
> - writel(temp_val, fep->hwp + FEC_TCSR(fep->pps_channel));
> + fec_writel(temp_val, fep->hwp + FEC_TCSR(fep->pps_channel));
>
> /* Write the second compare event timestamp and calculate
> * the third timestamp. Refer the TCCR register detail in the spec.
> */
> - writel(fep->next_counter, fep->hwp + FEC_TCCR(fep->pps_channel));
> + fec_writel(fep->next_counter, fep->hwp + FEC_TCCR(fep->pps_channel));
> fep->next_counter = (fep->next_counter + fep->reload_period) & fep->cc.mask;
> spin_unlock_irqrestore(&fep->tmreg_lock, flags);
>
> @@ -314,13 +314,13 @@ void fec_ptp_start_cyclecounter(struct net_device *ndev)
> spin_lock_irqsave(&fep->tmreg_lock, flags);
>
> /* 1ns counter */
> - writel(inc << FEC_T_INC_OFFSET, fep->hwp + FEC_ATIME_INC);
> + fec_writel(inc << FEC_T_INC_OFFSET, fep->hwp + FEC_ATIME_INC);
>
> /* use 31-bit timer counter */
> - writel(FEC_COUNTER_PERIOD, fep->hwp + FEC_ATIME_EVT_PERIOD);
> + fec_writel(FEC_COUNTER_PERIOD, fep->hwp + FEC_ATIME_EVT_PERIOD);
>
> - writel(FEC_T_CTRL_ENABLE | FEC_T_CTRL_PERIOD_RST,
> - fep->hwp + FEC_ATIME_CTRL);
> + fec_writel(FEC_T_CTRL_ENABLE | FEC_T_CTRL_PERIOD_RST,
> + fep->hwp + FEC_ATIME_CTRL);
>
> memset(&fep->cc, 0, sizeof(fep->cc));
> fep->cc.read = fec_ptp_read;
> @@ -397,11 +397,11 @@ static int fec_ptp_adjfine(struct ptp_clock_info *ptp, long scaled_ppm)
>
> spin_lock_irqsave(&fep->tmreg_lock, flags);
>
> - tmp = readl(fep->hwp + FEC_ATIME_INC) & FEC_T_INC_MASK;
> + tmp = fec_readl(fep->hwp + FEC_ATIME_INC) & FEC_T_INC_MASK;
> tmp |= corr_ns << FEC_T_INC_CORR_OFFSET;
> - writel(tmp, fep->hwp + FEC_ATIME_INC);
> + fec_writel(tmp, fep->hwp + FEC_ATIME_INC);
> corr_period = corr_period > 1 ? corr_period - 1 : corr_period;
> - writel(corr_period, fep->hwp + FEC_ATIME_CORR);
> + fec_writel(corr_period, fep->hwp + FEC_ATIME_CORR);
> /* dummy read to update the timer. */
> timecounter_read(&fep->tc);
>
> @@ -493,7 +493,7 @@ static int fec_ptp_settime(struct ptp_clock_info *ptp,
> counter = ns & fep->cc.mask;
>
> spin_lock_irqsave(&fep->tmreg_lock, flags);
> - writel(counter, fep->hwp + FEC_ATIME);
> + fec_writel(counter, fep->hwp + FEC_ATIME);
> timecounter_init(&fep->tc, &fep->cc, ns);
> spin_unlock_irqrestore(&fep->tmreg_lock, flags);
> mutex_unlock(&fep->ptp_clk_mutex);
> @@ -508,7 +508,7 @@ static int fec_ptp_pps_disable(struct fec_enet_private *fep, uint channel)
>
> spin_lock_irqsave(&fep->tmreg_lock, flags);
> fep->perout_enable = false;
> - writel(0, fep->hwp + FEC_TCSR(channel));
> + fec_writel(0, fep->hwp + FEC_TCSR(channel));
> spin_unlock_irqrestore(&fep->tmreg_lock, flags);
>
> return 0;
> @@ -701,15 +701,15 @@ static irqreturn_t fec_pps_interrupt(int irq, void *dev_id)
> u8 channel = fep->pps_channel;
> struct ptp_clock_event event;
>
> - val = readl(fep->hwp + FEC_TCSR(channel));
> + val = fec_readl(fep->hwp + FEC_TCSR(channel));
> if (val & FEC_T_TF_MASK) {
> /* Write the next next compare(not the next according the spec)
> * value to the register
> */
> - writel(fep->next_counter, fep->hwp + FEC_TCCR(channel));
> + fec_writel(fep->next_counter, fep->hwp + FEC_TCCR(channel));
> do {
> - writel(val, fep->hwp + FEC_TCSR(channel));
> - } while (readl(fep->hwp + FEC_TCSR(channel)) & FEC_T_TF_MASK);
> + fec_writel(val, fep->hwp + FEC_TCSR(channel));
> + } while (fec_readl(fep->hwp + FEC_TCSR(channel)) & FEC_T_TF_MASK);
>
> /* Update the counter; */
> fep->next_counter = (fep->next_counter + fep->reload_period) &
> @@ -813,8 +813,8 @@ void fec_ptp_save_state(struct fec_enet_private *fep)
> fep->ptp_saved_state.ns_phc = timecounter_read(&fep->tc);
> fep->ptp_saved_state.ns_sys = ktime_get_ns();
>
> - fep->ptp_saved_state.at_corr = readl(fep->hwp + FEC_ATIME_CORR);
> - atime_inc_corr = readl(fep->hwp + FEC_ATIME_INC) & FEC_T_INC_CORR_MASK;
> + fep->ptp_saved_state.at_corr = fec_readl(fep->hwp + FEC_ATIME_CORR);
> + atime_inc_corr = fec_readl(fep->hwp + FEC_ATIME_INC) & FEC_T_INC_CORR_MASK;
> fep->ptp_saved_state.at_inc_corr = (u8)(atime_inc_corr >> FEC_T_INC_CORR_OFFSET);
>
> spin_unlock_irqrestore(&fep->tmreg_lock, flags);
> @@ -823,7 +823,7 @@ void fec_ptp_save_state(struct fec_enet_private *fep)
> /* Restore PTP functionality after a reset */
> void fec_ptp_restore_state(struct fec_enet_private *fep)
> {
> - u32 atime_inc = readl(fep->hwp + FEC_ATIME_INC) & FEC_T_INC_MASK;
> + u32 atime_inc = fec_readl(fep->hwp + FEC_ATIME_INC) & FEC_T_INC_MASK;
> unsigned long flags;
> u32 counter;
> u64 ns;
> @@ -833,13 +833,13 @@ void fec_ptp_restore_state(struct fec_enet_private *fep)
> /* Reset turned it off, so adjust our status flag */
> fep->pps_enable = 0;
>
> - writel(fep->ptp_saved_state.at_corr, fep->hwp + FEC_ATIME_CORR);
> + fec_writel(fep->ptp_saved_state.at_corr, fep->hwp + FEC_ATIME_CORR);
> atime_inc |= ((u32)fep->ptp_saved_state.at_inc_corr) << FEC_T_INC_CORR_OFFSET;
> - writel(atime_inc, fep->hwp + FEC_ATIME_INC);
> + fec_writel(atime_inc, fep->hwp + FEC_ATIME_INC);
>
> ns = ktime_get_ns() - fep->ptp_saved_state.ns_sys + fep->ptp_saved_state.ns_phc;
> counter = ns & fep->cc.mask;
> - writel(counter, fep->hwp + FEC_ATIME);
> + fec_writel(counter, fep->hwp + FEC_ATIME);
> timecounter_init(&fep->tc, &fep->cc, ns);
>
> spin_unlock_irqrestore(&fep->tmreg_lock, flags);
^ permalink raw reply
* [PATCH iproute2-next v6 2/2] rdma: display resource limits in curr/max format
From: Tao Cui @ 2026-07-13 13:12 UTC (permalink / raw)
To: dsahern, leonro; +Cc: linux-rdma, netdev, Tao Cui
In-Reply-To: <20260713131238.955962-1-cui.tao@linux.dev>
From: Tao Cui <cuitao@kylinos.cn>
Parse the new RDMA_NLDEV_ATTR_RES_SUMMARY_ENTRY_MAX netlink attribute
to show resource limits alongside current counts in curr/max format:
Before: 0: mlx5_0: qp 123 cq 45 mr 200 pd 10
After: 0: mlx5_0: qp 123/131072 cq 45/65536 mr 200/1000000 pd 10/32768
JSON output provides both current and max fields per resource type
(e.g. "qp": 123, "qp-max": 131072). Backward compatible: no output
change when kernel lacks the new attribute.
Signed-off-by: Tao Cui <cuitao@kylinos.cn>
Link: https://lore.kernel.org/all/20260615003646.168704-1-cui.tao@linux.dev/
---
rdma/res.c | 21 ++++++++++++++++++++-
rdma/utils.c | 1 +
2 files changed, 21 insertions(+), 1 deletion(-)
diff --git a/rdma/res.c b/rdma/res.c
index 062f0007..046935e2 100644
--- a/rdma/res.c
+++ b/rdma/res.c
@@ -55,7 +55,26 @@ static int res_print_summary(struct nlattr **tb)
name = mnl_attr_get_str(nla_line[RDMA_NLDEV_ATTR_RES_SUMMARY_ENTRY_NAME]);
curr = mnl_attr_get_u64(nla_line[RDMA_NLDEV_ATTR_RES_SUMMARY_ENTRY_CURR]);
- res_print_u64(name, curr, nla_line[RDMA_NLDEV_ATTR_RES_SUMMARY_ENTRY_CURR]);
+ if (nla_line[RDMA_NLDEV_ATTR_RES_SUMMARY_ENTRY_MAX]) {
+ uint64_t max;
+ char max_name[64];
+
+ max = mnl_attr_get_u64(
+ nla_line[RDMA_NLDEV_ATTR_RES_SUMMARY_ENTRY_MAX]);
+ snprintf(max_name, sizeof(max_name), "%s-max", name);
+ print_u64(PRINT_JSON, name, NULL, curr);
+ print_u64(PRINT_JSON, max_name, NULL, max);
+ if (!is_json_context()) {
+ char buf[64];
+
+ snprintf(buf, sizeof(buf), "%s %" PRIu64 "/%" PRIu64 " ",
+ name, curr, max);
+ pr_out("%s", buf);
+ }
+ } else {
+ res_print_u64(name, curr,
+ nla_line[RDMA_NLDEV_ATTR_RES_SUMMARY_ENTRY_CURR]);
+ }
}
return 0;
}
diff --git a/rdma/utils.c b/rdma/utils.c
index 87003b2c..90ea1c55 100644
--- a/rdma/utils.c
+++ b/rdma/utils.c
@@ -480,6 +480,7 @@ static const enum mnl_attr_data_type nldev_policy[RDMA_NLDEV_ATTR_MAX] = {
[RDMA_NLDEV_ATTR_EVENT_TYPE] = MNL_TYPE_U8,
[RDMA_NLDEV_SYS_ATTR_MONITOR_MODE] = MNL_TYPE_U8,
[RDMA_NLDEV_ATTR_STAT_OPCOUNTER_ENABLED] = MNL_TYPE_U8,
+ [RDMA_NLDEV_ATTR_RES_SUMMARY_ENTRY_MAX] = MNL_TYPE_U64,
};
static int rd_attr_check(const struct nlattr *attr, int *typep)
--
2.43.0
^ permalink raw reply related
* [PATCH iproute2-next v6 1/2] rdma: update uapi headers
From: Tao Cui @ 2026-07-13 13:12 UTC (permalink / raw)
To: dsahern, leonro; +Cc: linux-rdma, netdev, Tao Cui
In-Reply-To: <20260713131238.955962-1-cui.tao@linux.dev>
From: Tao Cui <cuitao@kylinos.cn>
Update rdma_netlink.h file upto kernel commit 5911f6d6e7cc
("RDMA/nldev: Add resource summary max values for usage display")
Signed-off-by: Tao Cui <cuitao@kylinos.cn>
---
rdma/include/uapi/rdma/rdma_netlink.h | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/rdma/include/uapi/rdma/rdma_netlink.h b/rdma/include/uapi/rdma/rdma_netlink.h
index 4356ec4a..e5b8b065 100644
--- a/rdma/include/uapi/rdma/rdma_netlink.h
+++ b/rdma/include/uapi/rdma/rdma_netlink.h
@@ -604,6 +604,11 @@ enum rdma_nldev_attr {
RDMA_NLDEV_ATTR_FRMR_POOL_PINNED_HANDLES, /* u32 */
RDMA_NLDEV_ATTR_FRMR_POOL_KEY_KERNEL_VENDOR_KEY, /* u64 */
+ /*
+ * Resource summary entry maximum value.
+ */
+ RDMA_NLDEV_ATTR_RES_SUMMARY_ENTRY_MAX, /* u64 */
+
/*
* Always the end
*/
--
2.43.0
^ permalink raw reply related
* [PATCH iproute2-next v6 0/2] rdma: display resource limits in curr/max format
From: Tao Cui @ 2026-07-13 13:12 UTC (permalink / raw)
To: dsahern, leonro; +Cc: linux-rdma, netdev, Tao Cui
From: Tao Cui <cuitao@kylinos.cn>
This series adds support for displaying RDMA device resource limits in
curr/max format in the rdma tool, building on the kernel uapi attribute
RDMA_NLDEV_ATTR_RES_SUMMARY_ENTRY_MAX which has landed in linux-next
(kernel commit 5911f6d6e7cc [1]).
Patch 1 syncs the rdma_netlink.h uapi header from the kernel.
Patch 2 updates the rdma tool to parse the new attribute and display
resource usage in curr/max format:
$ rdma resource show
0: mlx5_0: qp 123/131072 cq 45/65536 mr 200/1000000 pd 10/32768
[1]:
Link: https://lore.kernel.org/all/20260615003646.168704-1-cui.tao@linux.dev/
Tao Cui (2):
rdma: update uapi headers
rdma: display resource limits in curr/max format
rdma/include/uapi/rdma/rdma_netlink.h | 5 +++++
rdma/res.c | 21 ++++++++++++++++++++-
rdma/utils.c | 1 +
3 files changed, 26 insertions(+), 1 deletion(-)
---
Changes in v6:
- Fix the incorrect kernel commit SHA referenced in the uapi headers
sync message (5911f6d6e7cc, not 5911f6d6e7ce).
Changes in v5:
- Split the rdma_netlink.h update into its own commit following the
uapi headers sync format ("rdma: update uapi headers").
Changes in v4:
- Add a Link: reference to the kernel patch; re-send now that the
kernel side has landed in linux-next.
--
2.43.0
^ permalink raw reply
* [PATCH net-next 2/2] net: phy: add DAPU Telecom DAP8210R(I) Gigabit Ethernet PHY driver
From: Artem Shimko @ 2026-07-13 13:12 UTC (permalink / raw)
To: netdev, Andrew Lunn, Heiner Kallweit, Russell King,
David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Rob Herring, Krzysztof Kozlowski, Conor Dooley
Cc: Artem Shimko, linux-kernel, devicetree
In-Reply-To: <20260713131223.279555-1-a.shimko.dev@gmail.com>
Add a new PHY driver for the DAPU Telecom DAP8211R(I) Gigabit
Ethernet PHY, which is commonly used in enterprise and industrial
networking applications.
The driver implements extended register access via indirect addressing
through corresponding registers, and provides comprehensive device tree
support for RGMII delay configuration. The rx-internal-delay-ps and
tx-internal-delay-ps properties allow precise tuning of clock delays in
150 ps steps from 0 to 2250 ps, with automatic selection of the nearest
supported value if an exact match is not specified. Additionally,
the optional tx-inverted-clk flag enables 180-degree TX clock phase shift
for boards where signal integrity or MAC requirements necessitate clock
inversion. The driver also exports a debugfs interface for diagnostics,
exposing PHY status registers and providing runtime control over the
internal packet generator, CRC corruption, and loopback functionality.
Signed-off-by: Artem Shimko <a.shimko.dev@gmail.com>
---
drivers/net/phy/Kconfig | 10 +
drivers/net/phy/Makefile | 1 +
drivers/net/phy/dap8211r.c | 544 +++++++++++++++++++++++++++++++++++++
3 files changed, 555 insertions(+)
create mode 100644 drivers/net/phy/dap8211r.c
diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig
index 099f25dceabb..4576f707ac94 100644
--- a/drivers/net/phy/Kconfig
+++ b/drivers/net/phy/Kconfig
@@ -237,6 +237,16 @@ config DAVICOM_PHY
help
Currently supports dm9161e and dm9131
+config DAP8211R_PHY
+ tristate "DAPU Telecom DAP8211R(I) Gigabit Ethernet PHY"
+ depends on OF
+ help
+ Support for the DAPU Telecom DAP8211R(I) Gigabit Ethernet PHY.
+ This PHY is designed for enterprise and industrial networking
+ applications, supporting 10/100/1000 Mbps operation.
+ RGMII with: configurable TX/RX clock delays, optional flag to enable
+ 180-degree TX clock phase shift and internal packet generator.
+
config ICPLUS_PHY
tristate "ICPlus PHYs"
help
diff --git a/drivers/net/phy/Makefile b/drivers/net/phy/Makefile
index de660ae94945..ad35733eb4bb 100644
--- a/drivers/net/phy/Makefile
+++ b/drivers/net/phy/Makefile
@@ -53,6 +53,7 @@ obj-$(CONFIG_BROADCOM_PHY) += broadcom.o
obj-$(CONFIG_CICADA_PHY) += cicada.o
obj-$(CONFIG_CORTINA_PHY) += cortina.o
obj-$(CONFIG_DAVICOM_PHY) += davicom.o
+obj-$(CONFIG_DAP8211R_PHY) += dap8211r.o
obj-$(CONFIG_DP83640_PHY) += dp83640.o
obj-$(CONFIG_DP83822_PHY) += dp83822.o
obj-$(CONFIG_DP83848_PHY) += dp83848.o
diff --git a/drivers/net/phy/dap8211r.c b/drivers/net/phy/dap8211r.c
new file mode 100644
index 000000000000..bd695bc4953a
--- /dev/null
+++ b/drivers/net/phy/dap8211r.c
@@ -0,0 +1,544 @@
+// SPDX-License-Identifier: GPL
+/*
+ * Driver for the DAPU Telecom DAP8211R(I) Gigabit Ethernet PHY.
+ *
+ * Specifications:
+ * - IEEE 802.3 10BASE-Te, 100BASE-TX, 1000BASE-T
+ * - IEEE 802.3az-2010 Energy Efficient Ethernet
+ * - IEEE 1588 SyncE support
+ * - RGMII
+ * - Package Generator for diagnostics
+ *
+ * Author: Artem Shimko <a.shimko.dev@gmail.com>
+ */
+
+#include <linux/bitfield.h>
+#include <linux/debugfs.h>
+#include <linux/ethtool.h>
+#include <linux/kernel.h>
+#include <linux/mii.h>
+#include <linux/module.h>
+#include <linux/netdevice.h>
+#include <linux/of.h>
+#include <linux/phy.h>
+
+#define DAP8211R_PHY_ID 0x0008011B
+#define DAP8211R_PHY_ID_MASK 0xFFFFFFFF
+
+#define DAP8211R_EXT_ADD 0x1E
+#define DAP8211R_EXT_DATA 0x1F
+
+#define DAP8211R_PKGC5 0xA0
+#define DAP8211R_PKG_PL_MASK GENMASK(1, 0)
+#define DAP8211R_PKG_PL_5AA5 BIT(1)
+#define DAP8211R_PKG_COR_CRC BIT(2)
+#define DAP8211R_PKG_GEN_EN BIT(12)
+#define DAP8211R_PKG_GEN_MODE BIT(13)
+#define DAP8211R_PKG_GATE_EN BIT(14)
+#define DAP8211R_PKG_CHK_EN BIT(15)
+#define DAP8211R_PKG_GEN_MASK GENMASK(15, 12)
+
+#define DAP8211R_PHY_CON 0xA001
+#define DAP8211R_PHY_LDO_EN BIT(6)
+#define DAP8211R_PHY_RX_DLY BIT(8)
+#define DAP8211R_PHY_GATE_RX_CLK BIT(9)
+#define DAP8211R_PHY_SW_RST BIT(15)
+
+#define DAP8211R_RGMII_CON 0xA003
+#define DAP8211R_RGMII_TX_DEL_MASK GENMASK(3, 0)
+#define DAP8211R_RGMII_RX_DEL_MASK GENMASK(13, 10)
+#define DAP8211R_RGMII_CLK_INVERT BIT(14)
+
+/* Default RGMII delay (1.95ns == 13 * 150)ps) */
+#define DAP8211R_DEFAULT_DELAY_PS 1950
+
+struct dap8211r_delay_config {
+ u32 ps;
+ u8 sel;
+};
+
+static const struct dap8211r_delay_config delay_config[] = {
+ { 0, 0},
+ { 150, 1},
+ { 300, 2},
+ { 450, 3},
+ { 600, 4},
+ { 750, 5},
+ { 900, 6},
+ {1050, 7},
+ {1200, 8},
+ {1350, 9},
+ {1500, 10},
+ {1650, 11},
+ {1800, 12},
+ {1950, 13},
+ {2100, 14},
+ {2250, 15},
+};
+
+#define DAP8211R_DELAY_COUNT ARRAY_SIZE(delay_config)
+
+struct dap8211r_priv {
+ struct device *dev;
+#if IS_ENABLED(CONFIG_DEBUG_FS)
+ struct dentry *root_dir;
+ bool crc_cor;
+ bool loopback;
+#endif
+};
+
+/**
+ * dap8211r_delay_ps_to_sel() - Convert picoseconds to register value
+ * @ps: Delay in picoseconds (0-2250)
+ *
+ * Converts a delay value in picoseconds to the corresponding register value
+ * for RGMII delay configuration. The PHY supports 150ps steps from 0 to 2250ps.
+ *
+ * Return: Register value (0-15)
+ */
+static u16 dap8211r_delay_ps_to_sel(struct phy_device *phydev, u32 ps)
+{
+ int i, best_idx = 0;
+ u32 best_diff = UINT_MAX, diff = 0;
+
+ if (!ps)
+ return 0;
+
+ for (i = 0; i < DAP8211R_DELAY_COUNT; i++) {
+ diff = abs(ps - delay_config[i].ps);
+
+ if (diff < best_diff) {
+ best_diff = diff;
+ best_idx = i;
+ }
+
+ if (!diff)
+ break;
+ }
+
+ if (best_diff)
+ phydev_warn(phydev, "Delay %u ps not found, using closest %u ps\n", ps,
+ delay_config[best_idx].ps);
+
+ phydev_dbg(phydev, "Delay ps idx: %u\n", delay_config[best_idx].sel);
+
+ return delay_config[best_idx].sel;
+}
+
+/**
+ * dap8211r_read_ext() - Read extended register
+ * @phydev: PHY device structure
+ * @reg: Extended register address
+ *
+ * Reads a PHY extended register using the indirect access method.
+ * The caller must hold the MDIO bus lock.
+ *
+ * Return: Register value on success, or negative error code
+ */
+static int dap8211r_read_ext(struct phy_device *phydev, u16 reg)
+{
+ int ret;
+
+ phy_lock_mdio_bus(phydev);
+ ret = __phy_write(phydev, DAP8211R_EXT_ADD, reg);
+ if (ret < 0)
+ goto out;
+
+ ret = __phy_read(phydev, DAP8211R_EXT_DATA);
+out:
+ phy_unlock_mdio_bus(phydev);
+ return ret;
+}
+
+/**
+ * dap8211r_modify_ext() - Modify extended register bits
+ * @phydev: PHY device structure
+ * @reg: Extended register address
+ * @mask: Bit mask of bits to clear
+ * @set: Bit mask of bits to set
+ *
+ * Modifies a PHY extended register using the indirect access method.
+ * New value = (old value & ~mask) | set.
+ * The caller must hold the MDIO bus lock.
+ *
+ * Return: 0 on success, or negative error code
+ */
+static int dap8211r_modify_ext(struct phy_device *phydev, u16 reg, u16 mask, u16 set)
+{
+ int ret;
+
+ phy_lock_mdio_bus(phydev);
+ ret = __phy_write(phydev, DAP8211R_EXT_ADD, reg);
+ if (ret < 0)
+ goto out;
+
+ ret = __phy_modify(phydev, DAP8211R_EXT_DATA, mask, set);
+out:
+ phy_unlock_mdio_bus(phydev);
+ return ret;
+}
+
+/**
+ * dap8211r_get_rgmii_delay() - Get RGMII delay from device tree
+ * @phydev: PHY device structure
+ * @prop_name: Device tree property name
+ *
+ * Reads the RGMII delay from the device tree. If the property is not
+ * specified, the default delay (1950ps) is used.
+ *
+ * Return: Register value (0-15) or default if property not found
+ */
+static u32 dap8211r_get_rgmii_delay(struct phy_device *phydev, const char *prop_name)
+{
+ struct device_node *np = phydev->mdio.dev.of_node;
+ int ret;
+ u32 ps = 0;
+
+ ret = of_property_read_u32(np, prop_name, &ps);
+ if (ret) {
+ phydev_dbg(phydev, "Using default delay (%ups)\n", DAP8211R_DEFAULT_DELAY_PS);
+ ps = DAP8211R_DEFAULT_DELAY_PS;
+ }
+
+ return dap8211r_delay_ps_to_sel(phydev, ps);
+}
+
+/**
+ * dap8211r_config_init() - Initialize PHY
+ * @phydev: PHY device structure
+ *
+ * Configures the PHY during initialization:
+ * - RGMII delays based on interface mode
+ * - TX clock invertion
+ * - Software reset to apply settings
+ *
+ * Return: 0 on success, or negative error code
+ */
+static int dap8211r_config_init(struct phy_device *phydev)
+{
+ struct device_node *phydev_node = phydev->mdio.dev.of_node;
+ u16 mask = 0, set = 0;
+ int ret;
+
+ switch (phydev->interface) {
+ case PHY_INTERFACE_MODE_RGMII:
+ break;
+ case PHY_INTERFACE_MODE_RGMII_RXID:
+ set = FIELD_PREP(DAP8211R_RGMII_RX_DEL_MASK,
+ dap8211r_get_rgmii_delay(phydev, "rx-internal-delay-ps"));
+ mask = DAP8211R_RGMII_RX_DEL_MASK;
+ break;
+ case PHY_INTERFACE_MODE_RGMII_ID:
+ set = FIELD_PREP(DAP8211R_RGMII_RX_DEL_MASK,
+ dap8211r_get_rgmii_delay(phydev, "rx-internal-delay-ps"));
+ mask = DAP8211R_RGMII_RX_DEL_MASK;
+ fallthrough;
+ case PHY_INTERFACE_MODE_RGMII_TXID:
+ set |= FIELD_PREP(DAP8211R_RGMII_TX_DEL_MASK,
+ dap8211r_get_rgmii_delay(phydev, "tx-internal-delay-ps"));
+ mask |= DAP8211R_RGMII_TX_DEL_MASK;
+ break;
+ default:
+ phydev_err(phydev, "Unsupported interface: %d\n",
+ phydev->interface);
+ return -EINVAL;
+ }
+
+ if (of_property_read_bool(phydev_node, "tx-use-inverted-clk"))
+ set |= DAP8211R_RGMII_CLK_INVERT;
+
+ mask |= DAP8211R_RGMII_CLK_INVERT;
+
+ ret = dap8211r_modify_ext(phydev, DAP8211R_PHY_CON, DAP8211R_PHY_SW_RST, 0);
+ if (ret)
+ return ret;
+
+ /* Wait for reset self-clear */
+ fsleep(200);
+
+ ret = dap8211r_modify_ext(phydev, DAP8211R_RGMII_CON, mask, set);
+ if (ret)
+ return ret;
+
+ phydev_dbg(phydev, "RGMII configured: interface=%d, mask=0x%04x, set=0x%04x\n",
+ phydev->interface, mask, set);
+
+ return 0;
+}
+
+#if IS_ENABLED(CONFIG_DEBUG_FS)
+
+/**
+ * info_show() - Show PHY status information
+ * @sf: Sequence file structure
+ * @data: Private data (struct dap8211r_priv *)
+ *
+ * Displays PHY configuration and status registers for debugging.
+ *
+ * Return: 0 on success, or negative error code
+ */
+static int info_show(struct seq_file *sf, void *data)
+{
+ struct dap8211r_priv *priv = sf->private;
+ struct phy_device *phydev = to_phy_device(priv->dev);
+ int val;
+
+ val = dap8211r_read_ext(phydev, DAP8211R_PHY_CON);
+ if (val < 0)
+ return val;
+
+ seq_printf(sf, "PHY_CON: 0x%04x\n", val);
+ seq_printf(sf, " LDO enabled: %s\n", FIELD_GET(DAP8211R_PHY_LDO_EN, val) ?
+ "yes" : "no");
+ seq_printf(sf, " RX dly en: %s\n", FIELD_GET(DAP8211R_PHY_RX_DLY, val) ?
+ "yes" : "no");
+ seq_printf(sf, " RX ckl gating: %s\n", FIELD_GET(DAP8211R_PHY_GATE_RX_CLK, val) ?
+ "yes" : "no");
+
+ val = dap8211r_read_ext(phydev, DAP8211R_RGMII_CON);
+ if (val < 0)
+ return val;
+
+ seq_printf(sf, "RGMII_CON: 0x%04x\n", val);
+ seq_printf(sf, " TX delay idx: %lx\n", FIELD_GET(DAP8211R_RGMII_TX_DEL_MASK, val));
+ seq_printf(sf, " RX delay idx: %lx\n", FIELD_GET(DAP8211R_RGMII_RX_DEL_MASK, val));
+ seq_printf(sf, " CLK invert: %s\n", FIELD_GET(DAP8211R_RGMII_CLK_INVERT, val) ?
+ "yes" : "no");
+
+ val = phy_read(phydev, MII_BMSR);
+ if (val < 0)
+ return val;
+
+ seq_printf(sf, "BMSR: 0x%04x\n", val);
+ seq_printf(sf, " Link: %s\n", FIELD_GET(BMSR_LSTATUS, val) ?
+ "up" : "down");
+ seq_printf(sf, " AN complete: %s\n", FIELD_GET(BMSR_ANEGCOMPLETE, val) ?
+ "yes" : "no");
+
+ val = dap8211r_read_ext(phydev, DAP8211R_PKGC5);
+ if (val < 0)
+ return val;
+
+ if (FIELD_GET(DAP8211R_PKG_GEN_EN, val) &&
+ FIELD_GET(DAP8211R_PKG_CHK_EN, val))
+ seq_puts(sf, "WARNING: Package Generating enabled\n");
+
+ return 0;
+}
+DEFINE_SHOW_ATTRIBUTE(info);
+
+/**
+ * pkg_gen_show() - Show packet generator status
+ * @sf: Sequence file structure
+ * @data: Private data (struct dap8211r_priv *)
+ *
+ * Displays the current status of the internal packet generator,
+ * including CRC corruption and loopback settings.
+ *
+ * Return: 0 on success, or negative error code
+ */
+static int pkg_gen_show(struct seq_file *sf, void *data)
+{
+ struct dap8211r_priv *priv = sf->private;
+ struct phy_device *phydev = to_phy_device(priv->dev);
+ int val;
+
+ val = dap8211r_read_ext(phydev, DAP8211R_PKGC5);
+ if (val < 0)
+ return val;
+
+ seq_puts(sf, "Package Generating: ");
+ if (FIELD_GET(DAP8211R_PKG_GEN_EN, val) &&
+ FIELD_GET(DAP8211R_PKG_CHK_EN, val))
+ seq_puts(sf, "enabled\n");
+ else
+ seq_puts(sf, "disabled\n");
+
+ seq_printf(sf, "CRC corruption en: %s\n", FIELD_GET(DAP8211R_PKG_COR_CRC, val) ?
+ "enabled" : "disabled");
+
+ val = phy_read(phydev, MII_BMCR);
+ if (val < 0)
+ return val;
+
+ seq_printf(sf, "Loopback en: %s\n", FIELD_GET(BMCR_LOOPBACK, val) ?
+ "enabled" : "disabled");
+ return 0;
+}
+
+/**
+ * pkg_gen_write() - Enable/disable packet generator
+ * @file: File structure
+ * @user_buf: User space buffer
+ * @count: Buffer size
+ * @ppos: File position
+ *
+ * Enables or disables the internal packet generator.
+ * Also controls loopback and CRC corruption via debugfs flags.
+ *
+ * NOTE: We intentionally do not use genphy_loopback() here.
+ * genphy_loopback() does a full BMCR overwrite (mask = ~0) and
+ * waits up to 500 ms for link in loopback mode. This is too
+ * heavy for a diagnostic packet generator:
+ *
+ * - It destroys all other BMCR state (speed, duplex, AN config).
+ * - The 500 ms link poll is unnecessary: we already have a link
+ * or are deliberately setting up a test condition.
+ * - We want to minimize changes to BMCR during debugging.
+ *
+ * Return: Number of bytes written on success, or negative error code
+ */
+static ssize_t pkg_gen_write(struct file *file,
+ const char __user *user_buf,
+ size_t count, loff_t *ppos)
+{
+ struct seq_file *sf = file->private_data;
+ struct dap8211r_priv *priv = sf->private;
+ struct phy_device *phydev = to_phy_device(priv->dev);
+ bool en;
+ u16 mask, set, set_bmcr;
+ int ret;
+
+ ret = kstrtobool_from_user(user_buf, count, &en);
+ if (ret)
+ return ret;
+
+ if (en) {
+ set = DAP8211R_PKG_GEN_EN | DAP8211R_PKG_CHK_EN | DAP8211R_PKG_PL_5AA5;
+
+ if (priv->crc_cor)
+ set |= DAP8211R_PKG_COR_CRC;
+
+ if (priv->loopback)
+ set_bmcr = BMCR_LOOPBACK;
+ else
+ set_bmcr = BMCR_ANENABLE;
+ } else {
+ set = DAP8211R_PKG_GEN_MODE | DAP8211R_PKG_GATE_EN;
+ set_bmcr = BMCR_ANENABLE;
+ }
+
+ ret = phy_modify(phydev, MII_BMCR, BMCR_LOOPBACK | BMCR_ANENABLE, set_bmcr);
+ if (ret < 0)
+ return ret;
+
+ mask = DAP8211R_PKG_GEN_MASK | DAP8211R_PKG_PL_MASK | DAP8211R_PKG_COR_CRC;
+ ret = dap8211r_modify_ext(phydev, DAP8211R_PKGC5, mask, set);
+ if (ret < 0) {
+ phy_modify(phydev, MII_BMCR, BMCR_LOOPBACK, 0);
+ return ret;
+ }
+
+ return count;
+}
+DEFINE_SHOW_STORE_ATTRIBUTE(pkg_gen);
+
+/**
+ * dap8211r_debug_init() - Initialize debugfs entries
+ * @priv: Private driver data
+ *
+ * Creates debugfs directory and files for PHY debugging.
+ * - info: PHY status information
+ * - pkg_gen: Enable/disable generator (1/0)
+ * - pkg_gen_crc_cor: Enable CRC corruption (Y/N)
+ * - pkg_gen_loopback: Enable loopback (Y/N)
+ *
+ * Debugfs is only available when CONFIG_DEBUG_FS is enabled.
+ */
+static void dap8211r_debug_init(struct dap8211r_priv *priv)
+{
+ priv->root_dir = debugfs_create_dir(dev_name(priv->dev), NULL);
+ if (IS_ERR(priv->root_dir))
+ return;
+
+ debugfs_create_file("info", 0444, priv->root_dir, priv,
+ &info_fops);
+ debugfs_create_file("pkg_gen", 0644, priv->root_dir, priv,
+ &pkg_gen_fops);
+ debugfs_create_bool("pkg_gen_crc_cor", 0644, priv->root_dir,
+ &priv->crc_cor);
+ debugfs_create_bool("pkg_gen_loopback", 0644, priv->root_dir,
+ &priv->loopback);
+}
+
+/**
+ * dap8211r_debug_remove() - Remove debugfs entries
+ * @priv: Private driver data
+ *
+ * Recursively removes all debugfs files and directories created
+ * by dap8211r_debug_init().
+ */
+static void dap8211r_debug_remove(struct dap8211r_priv *priv)
+{
+ debugfs_remove_recursive(priv->root_dir);
+}
+
+#else /* !CONFIG_DEBUG_FS */
+static void dap8211r_debug_init(struct dap8211r_priv *priv) {}
+static void dap8211r_debug_remove(struct dap8211r_priv *priv) {}
+#endif /* CONFIG_DEBUG_FS */
+
+/**
+ * dap8211r_probe() - PHY probe callback
+ * @phydev: PHY device structure
+ *
+ * Called when the PHY is discovered. Allocates and initializes
+ * private driver data and debugfs entries.
+ *
+ * Return: 0 on success, or negative error code
+ */
+static int dap8211r_probe(struct phy_device *phydev)
+{
+ struct device *dev = &phydev->mdio.dev;
+ struct dap8211r_priv *priv;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->dev = dev;
+ phydev->priv = priv;
+
+ dap8211r_debug_init(priv);
+ phydev_dbg(phydev, "DAP8211R PHY probed\n");
+
+ return 0;
+}
+
+/**
+ * dap8211r_remove() - PHY remove callback
+ * @phydev: PHY device structure
+ *
+ * Called when the PHY is removed. Cleans up private driver data
+ * and debugfs entries.
+ */
+static void dap8211r_remove(struct phy_device *phydev)
+{
+ dap8211r_debug_remove(phydev->priv);
+}
+
+static struct phy_driver dap8211r_driver[] = {
+ {
+ PHY_ID_MATCH_EXACT(DAP8211R_PHY_ID),
+ .name = "DAP8211R Gigabit Ethernet",
+ .probe = dap8211r_probe,
+ .remove = dap8211r_remove,
+ .config_init = dap8211r_config_init,
+ .read_status = genphy_read_status,
+ .set_loopback = genphy_loopback,
+ .config_aneg = genphy_config_aneg,
+ .suspend = genphy_suspend,
+ .resume = genphy_resume,
+ },
+};
+module_phy_driver(dap8211r_driver);
+
+MODULE_DESCRIPTION("DAP8211R Gigabit Ethernet PHY driver");
+MODULE_AUTHOR("Artem Shimko <a.shimko.dev@gmail.com>");
+MODULE_LICENSE("GPL");
+
+static const struct mdio_device_id __maybe_unused dap8211r_tb[] = {
+ { DAP8211R_PHY_ID, DAP8211R_PHY_ID_MASK },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(mdio, dap8211r_tb);
--
2.43.0
^ permalink raw reply related
* [PATCH net-next 1/2] dt-bindings: net: add DAPU Telecom DAP8211R(I) PHY binding
From: Artem Shimko @ 2026-07-13 13:12 UTC (permalink / raw)
To: netdev, Andrew Lunn, Heiner Kallweit, Russell King,
David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Rob Herring, Krzysztof Kozlowski, Conor Dooley
Cc: Artem Shimko, linux-kernel, devicetree
In-Reply-To: <20260713131223.279555-1-a.shimko.dev@gmail.com>
Add device tree binding documentation for the DAPU Telecom DAP8211R(I)
Gigabit Ethernet PHY.
The PHY supports TX and RX clock delays in 150 ps steps from 0 to 2250 ps,
with a default of 1950 ps if not specified. The tx-inverted-clk flag
provides a vendor-specific extension for boards where PCB trace length or
MAC requirements necessitate 180-degree clock phase shift.
Signed-off-by: Artem Shimko <a.shimko.dev@gmail.com>
---
.../bindings/net/dapu,dap8211r.yaml | 78 +++++++++++++++++++
1 file changed, 78 insertions(+)
create mode 100644 Documentation/devicetree/bindings/net/dapu,dap8211r.yaml
diff --git a/Documentation/devicetree/bindings/net/dapu,dap8211r.yaml b/Documentation/devicetree/bindings/net/dapu,dap8211r.yaml
new file mode 100644
index 000000000000..208a82f779d6
--- /dev/null
+++ b/Documentation/devicetree/bindings/net/dapu,dap8211r.yaml
@@ -0,0 +1,78 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/net/dapu,dap8211r.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: DAPU Telecom DAP8211R(I) Gigabit Ethernet PHY
+
+maintainers:
+ - Artem Shimko <a.shimko.dev@gmail.com>
+
+description: |
+ The DAP8211R(I) is a Gigabit Ethernet PHY with RGMII interface,
+ supporting IEEE 802.3az Energy Efficient Ethernet, IEEE 1588 SyncE,
+ and an internal packet generator for diagnostics.
+
+ Specifications:
+ - 10BASE-Te, 100BASE-TX, 1000BASE-T
+ - RGMII with configurable TX/RX clock delays (150 ps steps, 0-2250 ps)
+ - IEEE 802.3az-2010 Energy Efficient Ethernet
+ - IEEE 1588 SyncE support
+ - Internal packet generator and checker for link diagnostics
+
+allOf:
+ - $ref: ethernet-phy.yaml#
+
+properties:
+ compatible:
+ const: ethernet-phy-id0008.011b
+
+ reg:
+ maxItems: 1
+
+ rx-internal-delay-ps:
+ description:
+ RGMII RX clock delay in picoseconds. The PHY supports 150 ps steps
+ from 0 to 2250 ps. If not specified, defaults to 1950 ps. If the
+ requested value does not exactly match a supported step, the driver
+ selects the nearest supported value and issues a warning.
+ enum: [0, 150, 300, 450, 600, 750, 900, 1050, 1200, 1350, 1500,
+ 1650, 1800, 1950, 2100, 2250]
+ default: 1950
+
+ tx-internal-delay-ps:
+ description:
+ RGMII TX clock delay in picoseconds. The PHY supports 150 ps steps
+ from 0 to 2250 ps. If not specified, defaults to 1950 ps. If the
+ requested value does not exactly match a supported step, the driver
+ selects the nearest supported value and issues a warning.
+ enum: [0, 150, 300, 450, 600, 750, 900, 1050, 1200, 1350, 1500,
+ 1650, 1800, 1950, 2100, 2250]
+ default: 1950
+
+ tx-inverted-clk:
+ $ref: /schemas/types.yaml#/definitions/flag
+ description:
+ If present, the RGMII TX clock to the MAC is inverted (180 degree
+ phase shift relative to the data lines). This is a vendor-specific
+ extension for boards where PCB trace length or MAC requirements
+ necessitate clock inversion. Only use this property after hardware
+ signal integrity validation.
+
+unevaluatedProperties: false
+
+examples:
+ - |
+ mdio {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ ethernet-phy@1 {
+ compatible = "ethernet-phy-ieee802.3-c22";
+ reg = <1>;
+ rx-internal-delay-ps = <1050>;
+ tx-internal-delay-ps = <1150>;
+ tx-inverted-clk;
+ };
+ };
\ No newline at end of file
--
2.43.0
^ permalink raw reply related
* [PATCH net-next 0/2] Add DAPU Telecom DAP8211R(I) Gigabit Ethernet PHY driver
From: Artem Shimko @ 2026-07-13 13:12 UTC (permalink / raw)
To: netdev, Andrew Lunn, Heiner Kallweit, Russell King,
David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Rob Herring, Krzysztof Kozlowski, Conor Dooley
Cc: Artem Shimko, linux-kernel, devicetree
Hello,
This series adds support for the DAPU Telecom DAP8211R(I) Gigabit
Ethernet PHY, commonly used in enterprise and industrial networking
applications. The PHY supports 10/100/1000 Mbps operation with RGMII
interface and includes features such as IEEE 802.3az Energy Efficient
Ethernet, IEEE 1588 SyncE, and an internal packet generator for
diagnostics.
The driver implements extended register access via indirect addressing
(registers 0x1E/0x1F) and provides comprehensive device tree support
for RGMII delay configuration. The rx-internal-delay-ps and
tx-internal-delay-ps properties allow precise tuning of clock delays
in 150 ps steps from 0 to 2250 ps, with automatic selection of the
nearest supported value if an exact match is not specified. The
optional tx-inverted-clk flag enables 180-degree TX clock phase shift
for boards where signal integrity or MAC requirements necessitate
clock inversion.
The driver also includes a debugfs interface for diagnostics, exposing
PHY status registers (info) and providing runtime control over the
internal packet generator (pkg_gen), CRC corruption, and loopback
functionality. This interface is particularly useful for development
and field diagnostics without requiring external test equipment.
This PHY is used on the NDA platform with 1G Ethernet tile and has
been tested on that hardware with successful link establishment and
RGMII delay tuning.
Due to the specific PCB layout of the platform and FPGA configuration,
the default RGMII timing configuration was insufficient, causing packet
loss during normal operation. Tuning the TX/RX line delays and enabling
clock inversion restored proper signal timing, resulting in zero packet
loss and stable link performance.
$ make dt_binding_check
SCHEMA Documentation/devicetree/bindings/processed-schema.json
...
DTEX Documentation/devicetree/bindings/net/dapu,dap8211r.example.dts
DTC [C] Documentation/devicetree/bindings/net/dapu,dap8211r.example.dtb
...
grep -i "dap8211r" Documentation/devicetree/bindings/processed-schema.json
chema.json
"http://devicetree.org/schemas/net/dapu,dap8211r.yaml": {
"$filename": ".../devicetree/bindings/net/dapu,dap8211r.yaml",
"$id": "http://devicetree.org/schemas/net/dapu,dap8211r.yaml#",
"title": "DAPU Telecom DAP8211R(I) Gigabit Ethernet PHY",
Working with xgmac.
Board side not pkc_gen mode (normal):
$ arping -I eth0 192.168.5.100
ARPING 192.168.5.1 from 192.168.5.100 eth0
Unicast reply from 192.168.5.1 [board.mac.addr] 8.543ms
Unicast reply from 192.168.5.1 [board.mac.addr] 3.295ms
Unicast reply from 192.168.5.1 [board.mac.addr] 4.301ms
Unicast reply from 192.168.5.1 [board.mac.addr] 4.096ms
Unicast reply from 192.168.5.1 [board.mac.addr] 2.872ms
...
$ Unfortunately, there is a dependence on the axibus speed here
$ iperf3 -c 192.168.5.1
Connecting to host 192.168.5.1, port 5201
[ 5] local 192.168.5.100 port 58936 connected to 192.168.5.1 port 5201
[ ID] Interval Transfer Bitrate Retr Cwnd
[ 5] 0.00-1.00 sec 7.88 MBytes 65.8 Mbits/sec 0 150 KBytes
[ 5] 1.00-2.00 sec 8.50 MBytes 71.4 Mbits/sec 0 165 KBytes
[ 5] 2.00-3.00 sec 8.25 MBytes 69.2 Mbits/sec 0 165 KBytes
[ 5] 3.00-4.01 sec 8.50 MBytes 71.1 Mbits/sec 0 165 KBytes
[ 5] 4.01-5.00 sec 8.38 MBytes 70.3 Mbits/sec 0 165 KBytes
[ 5] 5.00-6.00 sec 8.50 MBytes 71.5 Mbits/sec 0 165 KBytes
[ 5] 6.00-7.01 sec 8.62 MBytes 72.0 Mbits/sec 0 174 KBytes
[ 5] 7.01-8.00 sec 8.62 MBytes 72.8 Mbits/sec 0 174 KBytes
[ 5] 8.00-9.00 sec 8.62 MBytes 72.2 Mbits/sec 0 174 KBytes
[ 5] 9.00-10.04 sec 8.62 MBytes 69.9 Mbits/sec 0 174 KBytes
- - - - - - - - - - - - - - - - - - - - - - - - -
[ ID] Interval Transfer Bitrate Retr
[ 5] 0.00-10.04 sec 84.6 MBytes 70.7 Mbits/sec 0 sender
[ 5] 0.00-10.12 sec 84.8 MBytes 70.3 Mbits/sec receiveriperf Done.
$ ethtool -t eth0
...
The test extra info:
1. MAC Loopback 0
2. MAC Loopback (diff. queues) 0
3. PHY Loopback 0
...
ELP side not pkc_gen mode (normal):
...
17:29:11.974973 ARP, Reply ELP is-at elp.mac.addr(oui Unknown), length 28
17:29:12.975199 ARP, Request who-has ELP (elp.mac.addr(oui Unknown)) tell 192.168.5.100, length 46
17:29:12.975217 ARP, Reply ELP is-at elp.mac.addr(oui Unknown), length 28
17:29:13.975022 ARP, Request who-has ELP (elp.mac.addr(oui Unknown)) tell 192.168.5.100, length 46
17:29:13.975035 ARP, Reply ELP is-at elp.mac.addr(oui Unknown), length 28
17:29:14.974837 ARP, Request who-has ELP (elp.mac.addr(oui Unknown)) tell 192.168.5.100, length 46
17:29:14.974849 ARP, Reply ELP is-at elp.mac.addr(oui Unknown), length 28
17:29:15.975026 ARP, Request who-has ELP (elp.mac.addr(oui Unknown)) tell 192.168.5.100, length 46
...
Accepted connection from 192.168.5.100, port 58932
[ 5] local 192.168.5.1 port 5201 connected to 192.168.5.100 port 58936
[ ID] Interval Transfer Bitrate
[ 5] 0.00-1.00 sec 7.12 MBytes 59.7 Mbits/sec
[ 5] 1.00-2.00 sec 8.50 MBytes 71.3 Mbits/sec
[ 5] 2.00-3.00 sec 8.50 MBytes 71.3 Mbits/sec
[ 5] 3.00-4.00 sec 8.38 MBytes 70.3 Mbits/sec
[ 5] 4.00-5.00 sec 8.50 MBytes 71.3 Mbits/sec
[ 5] 5.00-6.00 sec 8.38 MBytes 70.3 Mbits/sec
[ 5] 6.00-7.00 sec 8.62 MBytes 72.4 Mbits/sec
[ 5] 7.00-8.00 sec 8.62 MBytes 72.3 Mbits/sec
[ 5] 8.00-9.00 sec 8.62 MBytes 72.4 Mbits/sec
[ 5] 9.00-10.00 sec 8.62 MBytes 72.4 Mbits/sec
[ 5] 10.00-10.12 sec 896 KBytes 62.3 Mbits/sec
- - - - - - - - - - - - - - - - - - - - - - - - -
[ ID] Interval Transfer Bitrate
[ 5] 0.00-10.12 sec 84.8 MBytes 70.3 Mbits/sec receiver
Board side pkc_gen mode:
To send pck_gen patern frame to ELP
$ echo N > pkg_gen_loopback
$ echo 1 > pkg_gen
To send pck_gen patern frame to MAC
$ echo Y > pkg_gen_loopback
$ echo 1 > pkg_gen
ELP side pkc_gen mode:
$ Ther is expected patern
$ tcpdump -i ens16
17:27:18.993518 5a:a5:5a:a5:5a:a5 (oui Unknown) > 5a:a5:5a:a5:5a:a5, ethertype Unknown (0x5aa5), length 60:
0x0000: 5aa5 5aa5 5aa5 5aa5 5aa5 5aa5 5aa5 5aa5 Z.Z.Z.Z.Z.Z.Z.Z.
0x0010: 5aa5 5aa5 5aa5 5aa5 5aa5 5aa5 5aa5 5aa5 Z.Z.Z.Z.Z.Z.Z.Z.
0x0020: 5aa5 5aa5 5aa5 5aa5 5aa5 5aa5 5aa5 Z.Z.Z.Z.Z.Z.Z.
17:27:18.995522 5a:a5:5a:a5:5a:a5 (oui Unknown) > 5a:a5:5a:a5:5a:a5, ethertype Unknown (0x5aa5), length 60:
0x0000: 5aa5 5aa5 5aa5 5aa5 5aa5 5aa5 5aa5 5aa5 Z.Z.Z.Z.Z.Z.Z.Z.
0x0010: 5aa5 5aa5 5aa5 5aa5 5aa5 5aa5 5aa5 5aa5 Z.Z.Z.Z.Z.Z.Z.Z.
0x0020: 5aa5 5aa5 5aa5 5aa5 5aa5 5aa5 5aa5 Z.Z.Z.Z.Z.Z.Z.
...
DTS node example:
ðernet_1g_tile {
...
phy-mode = "rgmii-rxid";
phy-handle = <&phy1>;
...
mdio: mdio {
phy1: ethernet-phy@1 {
...
compatible = "ethernet-phy-ieee802.3-c22";
rx-internal-delay-ps = <1050>;
tx-use-inverted-clk;
...
};
};
};
--
Best regards,
Artem Shimko
Artem Shimko (2):
dt-bindings: net: add DAPU Telecom DAP8211R(I) PHY binding
net: phy: add DAPU Telecom DAP8210R(I) Gigabit Ethernet PHY driver
.../bindings/net/dapu,dap8211r.yaml | 78 +++
drivers/net/phy/Kconfig | 10 +
drivers/net/phy/Makefile | 1 +
drivers/net/phy/dap8211r.c | 544 ++++++++++++++++++
4 files changed, 633 insertions(+)
create mode 100644 Documentation/devicetree/bindings/net/dapu,dap8211r.yaml
create mode 100644 drivers/net/phy/dap8211r.c
--
2.43.0
^ permalink raw reply
* Re: [PATCH net] selftests/net: Fix tun IPv6 test addresses to avoid 6to4 range
From: Simon Horman @ 2026-07-13 13:12 UTC (permalink / raw)
To: Ricardo B. Marlière
Cc: David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Shuah Khan, Xu Du, netdev, linux-kselftest, linux-kernel
In-Reply-To: <20260706-b4-net_tun_addr-v1-1-3d3cb2473560@suse.com>
On Mon, Jul 06, 2026 at 01:24:04PM -0300, Ricardo B. Marlière wrote:
> The IPv6 addresses used for the tun_vnet_udptnl fixture currently fall in
> the 2002::/16 prefix, which is reserved for the 6to4 transition mechanism
> (RFC 3056).
>
> On systems where the sit module is loaded, the kernel automatically claims
> 2002::/16 as a 6to4 tunnel prefix. When the test assigns a 2002:: address
> to a TUN interface, sit registers a competing local route for the same
> address. This ambiguity breaks the GENEVE decapsulation path: packets
> injected via the TUN fd are not delivered to the test socket, causing the
> IPv6-outer gtgso send_gso_packet variants to fail.
>
> Replace all four IPv6 test addresses with addresses from the fd00:db8::/32
> range, which is part of the ULA space (fc00::/7, RFC 4193) and carries no
> special kernel semantics.
>
> Fixes: 24e59f26eef2 ("selftest: tun: Add helpers for GSO over UDP tunnel")
> Signed-off-by: Ricardo B. Marlière <rbm@suse.com>
Reviewed-by: Simon Horman <horms@kernel.org>
^ permalink raw reply
* Re: [PATCH nf] netfilter: ipset: skip extension destroy on hash resize replay
From: Jozsef Kadlecsik @ 2026-07-13 12:59 UTC (permalink / raw)
To: Weiming Shi
Cc: Pablo Neira Ayuso, Jozsef Kadlecsik, netfilter-devel, coreteam,
netdev, linux-kernel, Xiang Mei
In-Reply-To: <20260704062234.2625208-1-bestswngs@gmail.com>
Hi,
On Fri, 3 Jul 2026, Weiming Shi wrote:
> During a hash set resize, mtype_resize() copies each element into the
> new table with memcpy(), so the new-table element shares the old-table
> element's comment extension. An xt_SET delete on the old table during
> the resize destroys that shared comment via ip_set_ext_destroy() and
> queues a replayed delete on h->ad. After the table swap mtype_resize()
> replays it with mtype_del() on the new table, whose copy still points at
> the freed comment, so ip_set_ext_destroy() frees it a second time:
>
> ODEBUG: activate active (active state 1) object: ... object type: rcu_head
> WARNING: CPU: 3 PID: 5311 at lib/debugobjects.c:514 debug_print_object
> Call Trace:
> <IRQ>
> kvfree_call_rcu (kernel/rcu/tree.c:3825)
> ip_set_comment_free (net/netfilter/ipset/ip_set_core.c:397)
> hash_ip4_del (net/netfilter/ipset/ip_set_hash_gen.h:1098)
> hash_ip4_kadt (net/netfilter/ipset/ip_set_hash_ip.c:96)
> ip_set_del (net/netfilter/ipset/ip_set_core.c:813)
> set_target_v3 (net/netfilter/xt_set.c:412)
> ipt_do_table (net/ipv4/netfilter/ip_tables.c:346)
> __ip_local_out (net/ipv4/ip_output.c:119)
> icmp_push_reply (net/ipv4/icmp.c:397)
> __icmp_send (net/ipv4/icmp.c:804)
> __udp4_lib_rcv (net/ipv4/udp.c:2521)
> ip_local_deliver (net/ipv4/ip_input.c:254)
> ip_rcv (net/ipv4/ip_input.c:569)
> </IRQ>
>
> The replay passes a NULL ext (the kernel-side delete that queued it
> already destroyed the extensions), so skip ip_set_ext_destroy() when ext
> is NULL. This also avoids the NULL ext->target dereference that was only
> kept safe by the new table's ref being zero.
>
> Reachable from an unprivileged user namespace.
>
> Fixes: f66ee0410b1c ("netfilter: ipset: Fix \"INFO: rcu detected stall in hash_xxx\" reports")
> Reported-by: Xiang Mei <xmei5@asu.edu>
> Assisted-by: Claude:claude-opus-4-8
> Signed-off-by: Weiming Shi <bestswngs@gmail.com>
> ---
> net/netfilter/ipset/ip_set_hash_gen.h | 6 ++++--
> 1 file changed, 4 insertions(+), 2 deletions(-)
>
> diff --git a/net/netfilter/ipset/ip_set_hash_gen.h b/net/netfilter/ipset/ip_set_hash_gen.h
> index 5e4453e9e..bc909ae2d 100644
> --- a/net/netfilter/ipset/ip_set_hash_gen.h
> +++ b/net/netfilter/ipset/ip_set_hash_gen.h
> @@ -1080,9 +1080,11 @@ mtype_del(struct ip_set *set, void *value, const struct ip_set_ext *ext,
> mtype_del_cidr(set, h,
> NCIDR_PUT(DCIDR_GET(d->cidr, j)), j);
> #endif
> - ip_set_ext_destroy(set, data);
> + /* On a resize replay the extensions were already destroyed. */
> + if (ext)
> + ip_set_ext_destroy(set, data);
>
> - if (atomic_read(&t->ref) && ext->target) {
> + if (ext && atomic_read(&t->ref) && ext->target) {
> /* Resize is in process and kernel side del,
> * save values
> */
Please rebase your patch against the nf-next tree
(git://git.kernel.org/pub/scm/linux/kernel/git/netfilter/nf-next.git): the
second chunk of your patch is not needed as it has been fixed.
Thank you and best regards,
Jozsef
^ permalink raw reply
* Re: [PATCH net-next v3 07/15] net: macb: introduce macb_context struct for buffer management
From: Théo Lebrun @ 2026-07-13 12:53 UTC (permalink / raw)
To: Paolo Abeni
Cc: conor.dooley, andrew+netdev, davem, edumazet, kuba,
richardcochran, linux, netdev, linux-kernel, nicolas.ferre,
claudiu.beznea, pvalerio, nb, vladimir.kondratiev,
gregory.clement, benoit.monin, tawfik.bayouk, thomas.petazzoni,
maxime.chevallier
In-Reply-To: <20260708102716.169767-1-pabeni@redhat.com>
Hello Paolo's LLM,
On Wed Jul 8, 2026 at 12:27 PM CEST, Paolo Abeni wrote:
> From: AI Reviewer <ai@example.com>
>
> This is an AI-generated review of your patch. The human sending this
> email has considered the AI review valid, or at least plausible.
> Full review at: https://netdev-ai.bots.linux.dev/sashiko/
> ---
> net: macb: introduce macb_context struct for buffer management
>
> This patch moves per-queue ring buffers and ring-size parameters out of
> struct macb / struct macb_queue into a new struct macb_context that is
> allocated on open and freed on close, so future patches can swap in a
> new context while the interface is running.
>
>> diff --git a/drivers/net/ethernet/cadence/macb_main.c b/drivers/net/ethernet/cadence/macb_main.c
>> index 951a7f080225..dfb1d49d841c 100644
>> --- a/drivers/net/ethernet/cadence/macb_main.c
>> +++ b/drivers/net/ethernet/cadence/macb_main.c
>
> [ ... ]
>
>> @@ -1257,6 +1288,7 @@ static void macb_tx_error_task(struct work_struct *work)
>> struct macb_queue *queue = container_of(work, struct macb_queue,
>> tx_error_task);
>> unsigned int q = queue - queue->bp->queues;
>> + struct macb_txq *txq = macb_txq(queue);
>> struct macb *bp = queue->bp;
>
> [High]
> Can macb_tx_error_task() dereference a freed context?
>
> macb_tx_error_task is scheduled from macb_interrupt():
>
> schedule_work(&queue->tx_error_task);
>
> After this patch, the very first thing the worker does is:
>
> struct macb_txq *txq = macb_txq(queue);
>
> which expands to &bp->ctx->txq[q] and dereferences bp->ctx.
>
> Looking at macb_close():
>
> macb_reset_hw(bp);
> ...
> macb_free(bp);
> kfree(bp->ctx);
> bp->ctx = NULL;
>
> There is no cancel_work_sync(&queue->tx_error_task), and macb_reset_hw()
> does not drain the workqueue. If an interrupt scheduled the work just
> before close, or the work was already pending, the worker can run after
> kfree(bp->ctx) and hit a NULL dereference (and a use-after-free if
> macb_open() re-allocates ctx concurrently via ethtool ring reconfig).
>
> The same reasoning applies to bp->hresp_err_bh_work, which is queued
> from macb_interrupt() and whose handler calls
> mog_init_rings()/macb_init_buffers() that read bp->ctx->tx_ring_size and
> bp->ctx->{tx,rx}q[q]. cancel_work_sync(&bp->hresp_err_bh_work) is only
> called from macb_remove(), not macb_close().
All of this is a valid concern.
> Pre-patch these fields lived inside the embedded struct macb_queue so a
> late worker would touch stale-but-addressable memory; post-patch the
> first indirection through bp->ctx faults.
This pre-series analysis is wrong however. Current upstream would crash:
macb_tx_error_task() restarts the HW and it even assigns
queue->tx_ring_dma which still contains the freed DMA address
(macb_free()/macb_close() don't reset that value).
Summary: this patch turns potential memory corruption into a NULL
pointer dereference.
> Would adding cancel_work_sync(&queue->tx_error_task) per queue and
> cancel_work_sync(&bp->hresp_err_bh_work) before kfree(bp->ctx) in
> macb_close() close this window?
Yes that is the way forward. It will probably be separate and target
net/main. Not specifically because I want it backported (it'd be nice
but issue is super unlikely and no one reported it) but mostly because
this series is already 15 patches long.
>> @@ -3690,8 +3778,8 @@ static int macb_set_ringparam(struct net_device *netdev,
>> macb_close(bp->netdev);
>> }
>>
>> - bp->rx_ring_size = new_rx_size;
>> - bp->tx_ring_size = new_tx_size;
>> + bp->configured_rx_ring_size = new_rx_size;
>> + bp->configured_tx_ring_size = new_tx_size;
>>
>> if (reset)
>> macb_open(bp->netdev);
>
> [Medium]
> Should the return value of macb_open() be checked here?
>
> This patch adds a new failure point inside macb_open():
>
> bp->ctx = kzalloc_obj(*bp->ctx);
> if (!bp->ctx) {
> err = -ENOMEM;
> goto pm_exit;
> }
>
> On failure, bp->ctx remains NULL and the interface stays down, but
> macb_set_ringparam() ignores the return value and reports success to
> userspace. The commit message says the motivation is to improve
> reliability under memory pressure, but this caller silently swallows the
> new allocation failure.
No need, this series ends with context swapping and the
macb_context_swap_end() operation, which replaces the macb_open()
highlighted here, cannot fail.
>> @@ -5039,6 +5125,12 @@ static int at91ether_open(struct net_device *netdev)
>> if (ret < 0)
>> return ret;
>>
>> + bp->ctx = kzalloc_obj(*bp->ctx);
>> + if (!bp->ctx) {
>> + ret = -ENOMEM;
>> + goto pm_exit;
>> + }
>> +
>> /* Clear internal statistics */
>> ctl = macb_readl(bp, NCR);
>> macb_writel(bp, NCR, ctl | MACB_BIT(CLRSTAT));
>
> [Low]
> Should bp->ctx->rx_ring_size, tx_ring_size and rx_buffer_size be
> initialized on this path?
>
> macb_open() sets them from configured_{rx,tx}_ring_size and calls
> macb_init_rx_buffer_size(), but at91ether_open() leaves them at zero
> after kzalloc_obj().
>
> Helpers such as macb_rx_ring_wrap() compute:
>
> return index & (bp->ctx->rx_ring_size - 1);
>
> which becomes index & 0xFFFFFFFF when rx_ring_size is 0. It happens to
> be harmless today because the at91ether path constrains indices to
> [0, AT91ETHER_MAX_RX_DESCR-1] independently and macb_get_regs() reports
> zeroed head/tail for the tx-less at91ether, but pre-patch macb_init_dflt()
> unconditionally set bp->rx_ring_size = DEFAULT_RX_RING_SIZE.
>
> Any future shared helper that used tx_ring_size as a stride or divisor
> would misbehave on at91ether.
Yes. Null impact (but it would be better in theory).
>> @@ -5076,6 +5171,9 @@ static int at91ether_close(struct net_device *netdev)
>>
>> at91ether_stop(bp);
>>
>> + kfree(bp->ctx);
>> + bp->ctx = NULL;
>> +
>> pm_runtime_put(&bp->pdev->dev);
>>
>> return 0;
>
> [High]
> Is there IRQ synchronization missing before freeing bp->ctx on this
> path?
>
> at91ether_stop() disables MAC RX/TX and writes IDR to mask further
> interrupts, but does not synchronize with a handler already running or
> already dispatched on another CPU. at91ether_interrupt() calls
> at91ether_rx(), which after this patch does:
>
> struct macb_rxq *rxq = &bp->ctx->rxq[0];
> ...
> desc = macb_rx_desc(queue, rxq->tail);
> ...
> p_recv = rxq->buffers + rxq->tail * AT91ETHER_MAX_RBUFF_SZ;
>
> If the handler is in flight when kfree(bp->ctx); bp->ctx = NULL runs,
> the next access through bp->ctx faults. The macb_open()/macb_close()
> path is somewhat protected by napi_disable() before macb_free(), but
> at91ether does not use NAPI and has no equivalent barrier.
>
> Would a synchronize_irq(bp->netdev->irq) between at91ether_stop() and
> kfree(bp->ctx) close this?
Ah at91ether_interrupt() doesn't grab bp->lock as macb_interrupt() does.
Indeed synchronize_irq() would solve this.
Thanks,
--
Théo Lebrun, Bootlin
Embedded Linux and Kernel engineering
https://bootlin.com
^ permalink raw reply
* [PATCH nf v2] selftests: netfilter: nft_flowtable.sh: fix offload counter verification for tunnel tests
From: Lorenzo Bianconi @ 2026-07-13 12:53 UTC (permalink / raw)
To: Pablo Neira Ayuso, Florian Westphal, Phil Sutter, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
Shuah Khan, Lorenzo Bianconi
Cc: netfilter-devel, coreteam, netdev, linux-kselftest
The IPIP and IP6IP6 tunnel tests call check_counters() to verify
flowtable offloading occurred, but the flow-add rule only matches
meta oif "veth1". When traffic is routed through a tunnel device,
oif is the tunnel interface (tun0, tun6, etc.), not veth1, so
the flow-add rule never fires, no flowtable entry is created,
and counters stay at zero — producing a silent false pass.
Fix by adding tunnel-specific flow-add rules for each tunnel
interface. These match TCP dport 12345 traffic before the bare
accept rule, set ct mark, add the flow to the flowtable, and
increment routed_orig. The existing routed_repl rule on veth0
already handles the reply direction since decapsulated reply
packets exit through the physical interface.
Also add check_counters() for the IP6IP6 non-VLAN and
IP6IP6-over-VLAN tests which previously used a bare PASS message.
Fixes: fe8313316eaf ("selftests: netfilter: nft_flowtable.sh: Add IPIP flowtable selftest")
Fixes: 5e5180352193 ("selftests: netfilter: nft_flowtable.sh: Add IP6IP6 flowtable selftest")
Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
---
Changes in v2:
- Add missing tunnel-specific flow-add rules for each tunnel interface.
- Link to v1: https://lore.kernel.org/r/20260711-flowtable-selftest-ip6ip6-fix-v1-1-60e9e7384df7@kernel.org
---
tools/testing/selftests/net/netfilter/nft_flowtable.sh | 14 ++++++++++++--
1 file changed, 12 insertions(+), 2 deletions(-)
diff --git a/tools/testing/selftests/net/netfilter/nft_flowtable.sh b/tools/testing/selftests/net/netfilter/nft_flowtable.sh
index 08ad07500e8a..d9a21ca8ed2c 100755
--- a/tools/testing/selftests/net/netfilter/nft_flowtable.sh
+++ b/tools/testing/selftests/net/netfilter/nft_flowtable.sh
@@ -617,7 +617,11 @@ ip -6 -net "$nsr2" route add default via fee1:3::1
ip -net "$ns2" route add default via 10.0.2.1
ip -6 -net "$ns2" route add default via dead:2::1
+ip netns exec "$nsr1" nft -a insert rule inet filter forward \
+ 'meta oif tun0 tcp dport 12345 ct mark set 1 flow add @f1 counter name routed_orig accept'
ip netns exec "$nsr1" nft -a insert rule inet filter forward 'meta oif tun0 accept'
+ip netns exec "$nsr1" nft -a insert rule inet filter forward \
+ 'meta oif tun6 tcp dport 12345 ct mark set 1 flow add @f1 counter name routed_orig accept'
ip netns exec "$nsr1" nft -a insert rule inet filter forward 'meta oif tun6 accept'
ip netns exec "$nsr1" nft -a insert rule inet filter forward \
'meta oif "veth0" tcp sport 12345 ct mark set 1 flow add @f1 counter name routed_repl accept'
@@ -629,7 +633,7 @@ if ! test_tcp_forwarding_nat "$ns1" "$ns2" 1 "IPIP tunnel"; then
fi
if test_tcp_forwarding "$ns1" "$ns2" 1 6 "[dead:2::99]" 12345; then
- echo "PASS: flow offload for ns1/ns2 IP6IP6 tunnel"
+ check_counters "flow offload for ns1/ns2 IP6IP6 tunnel"
else
echo "FAIL: flow offload for ns1/ns2 with IP6IP6 tunnel" 1>&2
ip netns exec "$nsr1" nft list ruleset
@@ -642,6 +646,8 @@ ip -net "$nsr1" link set veth1.10 up
ip -net "$nsr1" addr add 192.168.20.1/24 dev veth1.10
ip -net "$nsr1" addr add fee1:4::1/64 dev veth1.10 nodad
ip netns exec "$nsr1" sysctl net.ipv4.conf.veth1/10.forwarding=1 > /dev/null
+ip netns exec "$nsr1" nft -a insert rule inet filter forward \
+ 'meta oif veth1.10 tcp dport 12345 ct mark set 1 flow add @f1 counter name routed_orig accept'
ip netns exec "$nsr1" nft -a insert rule inet filter forward 'meta oif veth1.10 accept'
ip -net "$nsr1" link add name tun0.10 type ipip local 192.168.20.1 remote 192.168.20.2
@@ -649,6 +655,8 @@ ip -net "$nsr1" link set tun0.10 up
ip -net "$nsr1" addr add 192.168.200.1/24 dev tun0.10
ip -net "$nsr1" route change default via 192.168.200.2
ip netns exec "$nsr1" sysctl net.ipv4.conf.tun0/10.forwarding=1 > /dev/null
+ip netns exec "$nsr1" nft -a insert rule inet filter forward \
+ 'meta oif tun0.10 tcp dport 12345 ct mark set 1 flow add @f1 counter name routed_orig accept'
ip netns exec "$nsr1" nft -a insert rule inet filter forward 'meta oif tun0.10 accept'
ip -net "$nsr1" link add name tun6.10 type ip6tnl local fee1:4::1 remote fee1:4::2 encaplimit none
@@ -656,6 +664,8 @@ ip -net "$nsr1" link set tun6.10 up
ip -net "$nsr1" addr add fee1:5::1/64 dev tun6.10 nodad
ip -6 -net "$nsr1" route delete default
ip -6 -net "$nsr1" route add default via fee1:5::2
+ip netns exec "$nsr1" nft -a insert rule inet filter forward \
+ 'meta oif tun6.10 tcp dport 12345 ct mark set 1 flow add @f1 counter name routed_orig accept'
ip netns exec "$nsr1" nft -a insert rule inet filter forward 'meta oif tun6.10 accept'
ip -net "$nsr2" link add link veth0 name veth0.10 type vlan id 10
@@ -683,7 +693,7 @@ if ! test_tcp_forwarding_nat "$ns1" "$ns2" 1 "IPIP tunnel over vlan"; then
fi
if test_tcp_forwarding "$ns1" "$ns2" 1 6 "[dead:2::99]" 12345; then
- echo "PASS: flow offload for ns1/ns2 IP6IP6 tunnel over vlan"
+ check_counters "flow offload for ns1/ns2 IP6IP6 tunnel over vlan"
else
echo "FAIL: flow offload for ns1/ns2 with IP6IP6 tunnel over vlan" 1>&2
ip netns exec "$nsr1" nft list ruleset
---
base-commit: 3f1f755366687d051174739fb99f7d560202f60b
change-id: 20260711-flowtable-selftest-ip6ip6-fix-4d8a623247a3
Best regards,
--
Lorenzo Bianconi <lorenzo@kernel.org>
^ permalink raw reply related
* [PATCH nf-next v6 6/6] selftests: netfilter: nft_flowtable.sh: add SIT flowtable selftest
From: Lorenzo Bianconi @ 2026-07-13 12:46 UTC (permalink / raw)
To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Felix Fietkau, Matthias Brugger,
AngeloGioacchino Del Regno, Simon Horman, David Ahern,
Ido Schimmel, Pablo Neira Ayuso, Florian Westphal, Phil Sutter,
Shuah Khan, Lorenzo Bianconi
Cc: linux-arm-kernel, linux-mediatek, netdev, netfilter-devel,
coreteam, linux-kselftest
In-Reply-To: <20260713-b4-flowtable-sw-accel-ip6ip-v6-0-33f0155fc658@kernel.org>
Similar to IPIP, IP6IP6 and IPv4 over IPv6, introduce specific selftest
for SIT flowtable sw acceleration in nft_flowtable.sh.
Also add tunnel-specific flow-add rules for sit1 and sit1.10 so that
check_counters() properly verifies offload occurred, and restore IPv6
default routes at cleanup to prevent tunnel routes leaking to subsequent
tests (e.g. IPsec).
Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
---
tools/testing/selftests/net/netfilter/config | 1 +
.../selftests/net/netfilter/nft_flowtable.sh | 53 ++++++++++++++++++++++
2 files changed, 54 insertions(+)
diff --git a/tools/testing/selftests/net/netfilter/config b/tools/testing/selftests/net/netfilter/config
index c3c121b6f300..a212b9edbcd9 100644
--- a/tools/testing/selftests/net/netfilter/config
+++ b/tools/testing/selftests/net/netfilter/config
@@ -35,6 +35,7 @@ CONFIG_IP_SCTP=m
CONFIG_IPV6=y
CONFIG_IPV6_MULTIPLE_TABLES=y
CONFIG_IPV6_TUNNEL=m
+CONFIG_IPV6_SIT=m
CONFIG_IP_VS=m
CONFIG_IP_VS_PROTO_TCP=y
CONFIG_IP_VS_RR=m
diff --git a/tools/testing/selftests/net/netfilter/nft_flowtable.sh b/tools/testing/selftests/net/netfilter/nft_flowtable.sh
index 1cb5c65351b7..cda68070cd00 100755
--- a/tools/testing/selftests/net/netfilter/nft_flowtable.sh
+++ b/tools/testing/selftests/net/netfilter/nft_flowtable.sh
@@ -597,6 +597,10 @@ ip -net "$nsr1" addr add 192.168.210.1/24 dev tun6
ip -net "$nsr1" addr add fee1:3::1/64 dev tun6 nodad
ip netns exec "$nsr1" sysctl net.ipv4.conf.tun6.forwarding=1 > /dev/null
+ip -net "$nsr1" link add name sit1 type sit local 192.168.10.1 remote 192.168.10.2 ttl 255
+ip -net "$nsr1" link set sit1 up
+ip -net "$nsr1" addr add fe01:3::1/64 dev sit1 nodad
+
ip -net "$nsr2" link add name tun0 type ipip local 192.168.10.2 remote 192.168.10.1
ip -net "$nsr2" link set tun0 up
ip -net "$nsr2" addr add 192.168.100.2/24 dev tun0
@@ -608,6 +612,10 @@ ip -net "$nsr2" addr add 192.168.210.2/24 dev tun6
ip -net "$nsr2" addr add fee1:3::2/64 dev tun6 nodad
ip netns exec "$nsr2" sysctl net.ipv4.conf.tun6.forwarding=1 > /dev/null
+ip -net "$nsr2" link add name sit1 type sit local 192.168.10.2 remote 192.168.10.1 ttl 255
+ip -net "$nsr2" link set sit1 up
+ip -net "$nsr2" addr add fe01:3::2/64 dev sit1 nodad
+
ip -net "$nsr1" route change default via 192.168.100.2
ip -net "$nsr2" route change default via 192.168.100.1
@@ -626,6 +634,9 @@ ip netns exec "$nsr1" nft -a insert rule inet filter forward 'meta oif tun0 acce
ip netns exec "$nsr1" nft -a insert rule inet filter forward \
'meta oif tun6 tcp dport 12345 ct mark set 1 flow add @f1 counter name routed_orig accept'
ip netns exec "$nsr1" nft -a insert rule inet filter forward 'meta oif tun6 accept'
+ip netns exec "$nsr1" nft -a insert rule inet filter forward \
+ 'meta oif sit1 tcp dport 12345 ct mark set 1 flow add @f1 counter name routed_orig accept'
+ip netns exec "$nsr1" nft -a insert rule inet filter forward 'meta oif sit1 accept'
ip netns exec "$nsr1" nft -a insert rule inet filter forward \
'meta oif "veth0" tcp sport 12345 ct mark set 1 flow add @f1 counter name routed_repl accept'
@@ -652,6 +663,19 @@ if ! test_tcp_forwarding_nat "$ns1" "$ns2" 1 "IP4IP6 tunnel"; then
ret=1
fi
+ip -6 -net "$nsr1" route delete default
+ip -6 -net "$nsr1" route add default via fe01:3::2
+ip -6 -net "$nsr2" route delete default
+ip -6 -net "$nsr2" route add default via fe01:3::1
+
+if test_tcp_forwarding "$ns1" "$ns2" 1 6 "[dead:2::99]" 12345; then
+ check_counters "flow offload for ns1/ns2 SIT tunnel"
+else
+ echo "FAIL: flow offload for ns1/ns2 with SIT tunnel" 1>&2
+ ip netns exec "$nsr1" nft list ruleset
+ ret=1
+fi
+
# Create vlan tagged devices for IPIP traffic.
ip -net "$nsr1" link add link veth1 name veth1.10 type vlan id 10
ip -net "$nsr1" link set veth1.10 up
@@ -682,6 +706,13 @@ ip netns exec "$nsr1" nft -a insert rule inet filter forward \
'meta oif tun6.10 tcp dport 12345 ct mark set 1 flow add @f1 counter name routed_orig accept'
ip netns exec "$nsr1" nft -a insert rule inet filter forward 'meta oif tun6.10 accept'
+ip -net "$nsr1" link add name sit1.10 type sit local 192.168.20.1 remote 192.168.20.2 ttl 255
+ip -net "$nsr1" link set sit1.10 up
+ip -net "$nsr1" addr add fe01:5::1/64 dev sit1.10 nodad
+ip netns exec "$nsr1" nft -a insert rule inet filter forward \
+ 'meta oif sit1.10 tcp dport 12345 ct mark set 1 flow add @f1 counter name routed_orig accept'
+ip netns exec "$nsr1" nft -a insert rule inet filter forward 'meta oif sit1.10 accept'
+
ip -net "$nsr2" link add link veth0 name veth0.10 type vlan id 10
ip -net "$nsr2" link set veth0.10 up
ip -net "$nsr2" addr add 192.168.20.2/24 dev veth0.10
@@ -699,6 +730,11 @@ ip -net "$nsr2" link set tun6.10 up
ip -net "$nsr2" addr add 192.168.220.2/24 dev tun6.10
ip -net "$nsr2" addr add fee1:5::2/64 dev tun6.10 nodad
ip netns exec "$nsr2" sysctl net.ipv4.conf.tun6/10.forwarding=1 > /dev/null
+
+ip -net "$nsr2" link add name sit1.10 type sit local 192.168.20.2 remote 192.168.20.1 ttl 255
+ip -net "$nsr2" link set sit1.10 up
+ip -net "$nsr2" addr add fe01:5::2/64 dev sit1.10 nodad
+
ip -6 -net "$nsr2" route delete default
ip -6 -net "$nsr2" route add default via fee1:5::1
@@ -725,11 +761,28 @@ if ! test_tcp_forwarding_nat "$ns1" "$ns2" 1 "IP4IP6 tunnel over vlan"; then
ret=1
fi
+ip -6 -net "$nsr1" route delete default
+ip -6 -net "$nsr1" route add default via fe01:5::2
+ip -6 -net "$nsr2" route delete default
+ip -6 -net "$nsr2" route add default via fe01:5::1
+
+if test_tcp_forwarding "$ns1" "$ns2" 1 6 "[dead:2::99]" 12345; then
+ check_counters "flow offload for ns1/ns2 SIT tunnel over vlan"
+else
+ echo "FAIL: flow offload for ns1/ns2 with SIT tunnel over vlan" 1>&2
+ ip netns exec "$nsr1" nft list ruleset
+ ret=1
+fi
+
# Restore the previous configuration
ip -net "$nsr1" route change default via 192.168.10.2
ip -net "$nsr2" route change default via 192.168.10.1
ip -net "$ns2" route del default via 10.0.2.1
ip -6 -net "$ns2" route del default via dead:2::1
+ip -6 -net "$nsr1" route delete default
+ip -6 -net "$nsr1" route add default via fee1:2::2
+ip -6 -net "$nsr2" route delete default
+ip -6 -net "$nsr2" route add default via fee1:2::1
}
# Another test:
--
2.55.0
^ permalink raw reply related
* [PATCH nf-next v6 5/6] net: netfilter: add SIT tunnel flowtable acceleration
From: Lorenzo Bianconi @ 2026-07-13 12:46 UTC (permalink / raw)
To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Felix Fietkau, Matthias Brugger,
AngeloGioacchino Del Regno, Simon Horman, David Ahern,
Ido Schimmel, Pablo Neira Ayuso, Florian Westphal, Phil Sutter,
Shuah Khan, Lorenzo Bianconi
Cc: linux-arm-kernel, linux-mediatek, netdev, netfilter-devel,
coreteam, linux-kselftest
In-Reply-To: <20260713-b4-flowtable-sw-accel-ip6ip-v6-0-33f0155fc658@kernel.org>
Introduce sw flowtable acceleration for the TX/RX paths of
SIT tunnels, relying on the netfilter flowtable infrastructure.
The feature can be tested with a forwarding scenario between two
NICs (eth0 and eth1), where a SIT tunnel is used to reach a remote
site via eth1 as the underlay device:
ETH0 -- TUN0 <==> ETH1 -- [IP network] -- TUN1 (192.168.2.2)
[IP configuration]
6: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
link/ether 00:00:22:33:11:55 brd ff:ff:ff:ff:ff:ff
inet6 2001:db8:1::2/64 scope global nodad
valid_lft forever preferred_lft forever
7: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
link/ether 00:11:22:33:11:55 brd ff:ff:ff:ff:ff:ff
inet 192.168.2.1/24 scope global eth1
valid_lft forever preferred_lft forever
8: tun0@NONE: <POINTOPOINT,NOARP,UP,LOWER_UP> mtu 1480 qdisc noqueue state UNKNOWN group default qlen 1000
link/sit 192.168.2.1 peer 192.168.2.2
inet6 2001:db8:200::1/64 scope global nodad
valid_lft forever preferred_lft forever
$ ip route show
192.168.2.0/24 dev eth1 proto kernel scope link src 192.168.2.1
$ ip -6 route show
2001:db8:1::/64 dev eth0 proto kernel metric 256 pref medium
2001:db8:200::/64 dev tun0 proto kernel metric 256 pref medium
default via 2001:db8:200::2 dev tun0 metric 1024 pref medium
$ nft list ruleset
table inet filter {
flowtable ft {
hook ingress priority filter
devices = { eth0, eth1 }
}
chain forward {
type filter hook forward priority filter; policy accept;
meta l4proto { tcp, udp } flow add @ft
}
}
When reproducing this scenario using veth interfaces, the following
results were observed:
- TCP stream received from SIT tunnel:
- net-next (baseline): ~118 Gbps
- net-next + SIT flowtable support: ~148 Gbps
- TCP stream transmitted to SIT tunnel:
- net-next (baseline): ~131 Gbps
- net-next + SIT flowtable support: ~147 Gbps
Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
---
net/ipv6/sit.c | 60 ++++++++
net/netfilter/nf_flow_table_ip.c | 286 +++++++++++++++++++++------------------
2 files changed, 218 insertions(+), 128 deletions(-)
diff --git a/net/ipv6/sit.c b/net/ipv6/sit.c
index a38b24fb8384..47150df789e7 100644
--- a/net/ipv6/sit.c
+++ b/net/ipv6/sit.c
@@ -29,6 +29,7 @@
#include <linux/icmp.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
+#include <linux/inetdevice.h>
#include <linux/init.h>
#include <linux/netfilter_ipv4.h>
#include <linux/if_ether.h>
@@ -1365,6 +1366,64 @@ ipip6_tunnel_ctl(struct net_device *dev, struct ip_tunnel_parm_kern *p,
}
}
+static int ipip6_tunnel_fill_forward_path(struct net_device_path_ctx *ctx,
+ struct net_device_path *path)
+{
+ struct ip_tunnel *tunnel = netdev_priv(ctx->dev);
+ const struct iphdr *tiph = &tunnel->parms.iph;
+ struct rtable *rt;
+
+ /* NBMA tunnels (ISATAP, 6to4, 6rd) resolve the outer destination
+ * per-packet from the inner IPv6 address; not offloadable.
+ */
+ if (ctx->dev->priv_flags & IFF_ISATAP)
+ return -EOPNOTSUPP;
+
+ if (!tiph->daddr)
+ return -EOPNOTSUPP;
+
+ /* FOU/GUE encapsulation is handled in the slow path by
+ * ip_tunnel_encap(); the fast path only pushes a plain
+ * IPv4 header and would skip the UDP encapsulation.
+ */
+ if (tunnel->encap.type != TUNNEL_ENCAP_NONE)
+ return -EOPNOTSUPP;
+
+ rt = ip_route_output(dev_net(ctx->dev), tiph->daddr, tiph->saddr,
+ inet_dsfield_to_dscp(tiph->tos),
+ tunnel->parms.link, RT_SCOPE_UNIVERSE);
+ if (IS_ERR(rt))
+ return PTR_ERR(rt);
+
+ path->type = DEV_PATH_TUN;
+ if (tiph->saddr)
+ path->tun.src_v4.s_addr = tiph->saddr;
+ else
+ path->tun.src_v4.s_addr = inet_select_addr(rt->dst.dev,
+ tiph->daddr,
+ RT_SCOPE_UNIVERSE);
+ path->tun.dst_v4.s_addr = tiph->daddr;
+ path->tun.encap_proto = AF_INET;
+ path->dev = ctx->dev;
+
+ switch (ctx->ether_type) {
+ case cpu_to_be16(ETH_P_IP):
+ path->tun.l3_proto = IPPROTO_IPIP;
+ break;
+ case cpu_to_be16(ETH_P_IPV6):
+ path->tun.l3_proto = IPPROTO_IPV6;
+ break;
+ default:
+ ip_rt_put(rt);
+ return -EOPNOTSUPP;
+ }
+
+ ctx->dev = rt->dst.dev;
+ ip_rt_put(rt);
+
+ return 0;
+}
+
static int
ipip6_tunnel_siocdevprivate(struct net_device *dev, struct ifreq *ifr,
void __user *data, int cmd)
@@ -1401,6 +1460,7 @@ static const struct net_device_ops ipip6_netdev_ops = {
.ndo_siocdevprivate = ipip6_tunnel_siocdevprivate,
.ndo_get_iflink = ip_tunnel_get_iflink,
.ndo_tunnel_ctl = ipip6_tunnel_ctl,
+ .ndo_fill_forward_path = ipip6_tunnel_fill_forward_path,
};
static void ipip6_dev_free(struct net_device *dev)
diff --git a/net/netfilter/nf_flow_table_ip.c b/net/netfilter/nf_flow_table_ip.c
index 9432aa140657..03e399380937 100644
--- a/net/netfilter/nf_flow_table_ip.c
+++ b/net/netfilter/nf_flow_table_ip.c
@@ -337,7 +337,7 @@ static bool nf_flow_ip4_tunnel_proto(struct nf_flowtable_ctx *ctx,
if (iph->ttl <= 1)
return false;
- if (iph->protocol == IPPROTO_IPIP) {
+ if (iph->protocol == IPPROTO_IPIP || iph->protocol == IPPROTO_IPV6) {
ctx->tun.proto = iph->protocol;
ctx->tun.hdr_size = size;
ctx->offset += ctx->tun.hdr_size;
@@ -463,21 +463,6 @@ static void nf_flow_encap_pop(struct nf_flowtable_ctx *ctx,
nf_flow_ip_tunnel_pop(ctx, skb);
}
-static struct flow_offload_tuple_rhash *
-nf_flow_offload_lookup(struct nf_flowtable_ctx *ctx,
- struct nf_flowtable *flow_table, struct sk_buff *skb)
-{
- struct flow_offload_tuple tuple = {};
-
- if (!nf_flow_skb_encap_protocol(ctx, skb, htons(ETH_P_IP)))
- return NULL;
-
- if (nf_flow_tuple_ip(ctx, skb, &tuple) < 0)
- return NULL;
-
- return flow_offload_lookup(flow_table, &tuple);
-}
-
static int nf_flow_offload_forward(struct nf_flowtable_ctx *ctx,
struct nf_flowtable *flow_table,
struct flow_offload_tuple_rhash *tuplehash,
@@ -601,23 +586,38 @@ static int nf_flow_pppoe_push(struct sk_buff *skb, u16 id,
return 0;
}
-static int nf_flow_tunnel_ipip_push(struct net *net, struct sk_buff *skb,
- struct flow_offload_tuple *tuple,
- struct dst_entry *dst, __be32 *ip_daddr)
+static int nf_flow_tunnel_ip_push(struct net *net, struct sk_buff *skb,
+ struct flow_offload_tuple *tuple,
+ struct dst_entry *dst, __be32 *ip_daddr)
{
- struct iphdr *iph = (struct iphdr *)skb_network_header(skb);
struct rtable *rt = dst_rtable(dst);
- u8 tos = iph->tos, ttl = iph->ttl;
- __be16 frag_off = iph->frag_off;
- u32 headroom = sizeof(*iph);
+ __be16 frag_off = 0;
+ struct iphdr *iph;
+ u8 tos = 0, ttl;
+ u32 headroom;
int err;
+ if (tuple->tun.l3_proto == IPPROTO_IPV6) {
+ struct ipv6hdr *ip6h;
+
+ ip6h = (struct ipv6hdr *)skb_network_header(skb);
+ tos = ipv6_get_dsfield(ip6h);
+ ttl = ip6h->hop_limit;
+ frag_off = htons(IP_DF);
+ } else {
+ iph = (struct iphdr *)skb_network_header(skb);
+ frag_off = iph->frag_off;
+ tos = iph->tos;
+ ttl = iph->ttl;
+ }
+
err = iptunnel_handle_offloads(skb, SKB_GSO_IPXIP4);
if (err)
return err;
- skb_set_inner_ipproto(skb, IPPROTO_IPIP);
- headroom += LL_RESERVED_SPACE(rt->dst.dev) + rt->dst.header_len;
+ skb_set_inner_ipproto(skb, tuple->tun.l3_proto);
+ headroom = sizeof(*iph) + LL_RESERVED_SPACE(rt->dst.dev) +
+ rt->dst.header_len;
err = skb_cow_head(skb, headroom);
if (err)
return err;
@@ -716,8 +716,7 @@ static int nf_flow_tunnel_push(struct net *net, struct sk_buff *skb,
{
switch (tuple->tun.encap_proto) {
case AF_INET:
- return nf_flow_tunnel_ipip_push(net, skb, tuple, dst,
- ip_daddr);
+ return nf_flow_tunnel_ip_push(net, skb, tuple, dst, ip_daddr);
case AF_INET6:
return nf_flow_tunnel_ip6_push(net, skb, tuple, dst,
ip6_daddr);
@@ -823,106 +822,6 @@ static unsigned int nf_flow_queue_xmit(struct net *net, struct sk_buff *skb,
return NF_STOLEN;
}
-unsigned int
-nf_flow_offload_ip_hook(void *priv, struct sk_buff *skb,
- const struct nf_hook_state *state)
-{
- struct flow_offload_tuple_rhash *tuplehash;
- struct nf_flowtable *flow_table = priv;
- struct flow_offload_tuple *other_tuple;
- enum flow_offload_tuple_dir dir;
- struct nf_flowtable_ctx ctx = {
- .in = state->in,
- };
- struct nf_flow_xmit xmit = {};
- struct in6_addr *ip6_daddr;
- struct flow_offload *flow;
- struct neighbour *neigh;
- struct rtable *rt;
- __be32 ip_daddr;
- int ret;
-
- tuplehash = nf_flow_offload_lookup(&ctx, flow_table, skb);
- if (!tuplehash)
- return NF_ACCEPT;
-
- ret = nf_flow_offload_forward(&ctx, flow_table, tuplehash, skb);
- if (ret < 0)
- return NF_DROP;
- else if (ret == 0)
- return NF_ACCEPT;
-
- if (unlikely(tuplehash->tuple.xmit_type == FLOW_OFFLOAD_XMIT_XFRM)) {
- rt = dst_rtable(tuplehash->tuple.dst_cache);
- memset(skb->cb, 0, sizeof(struct inet_skb_parm));
- IPCB(skb)->iif = skb->dev->ifindex;
- IPCB(skb)->flags = IPSKB_FORWARDED;
- return nf_flow_xmit_xfrm(skb, state, &rt->dst);
- }
-
- dir = tuplehash->tuple.dir;
- flow = container_of(tuplehash, struct flow_offload, tuplehash[dir]);
- other_tuple = &flow->tuplehash[!dir].tuple;
- ip_daddr = other_tuple->src_v4.s_addr;
- ip6_daddr = &other_tuple->src_v6;
-
- if (nf_flow_tunnel_push(state->net, skb, other_tuple,
- tuplehash->tuple.dst_cache,
- &ip_daddr, &ip6_daddr) < 0)
- return NF_DROP;
-
- switch (tuplehash->tuple.xmit_type) {
- case FLOW_OFFLOAD_XMIT_NEIGH: {
- struct dst_entry *dst;
-
- xmit.outdev = dev_get_by_index_rcu(state->net, tuplehash->tuple.ifidx);
- if (!xmit.outdev) {
- flow_offload_teardown(flow);
- return NF_DROP;
- }
- if (other_tuple->tun.encap_proto ?
- other_tuple->tun.encap_proto == AF_INET6 :
- ctx.tun.proto == IPPROTO_IPV6) {
- struct rt6_info *rt6;
-
- rt6 = dst_rt6_info(tuplehash->tuple.dst_cache);
- neigh = ip_neigh_gw6(rt6->dst.dev,
- rt6_nexthop(rt6, ip6_daddr));
- dst = &rt6->dst;
- } else {
- rt = dst_rtable(tuplehash->tuple.dst_cache);
- neigh = ip_neigh_gw4(rt->dst.dev,
- rt_nexthop(rt, ip_daddr));
- dst = &rt->dst;
- }
- if (IS_ERR(neigh)) {
- flow_offload_teardown(flow);
- return NF_DROP;
- }
- xmit.dest = neigh->ha;
- skb_dst_set_noref(skb, dst);
- break;
- }
- case FLOW_OFFLOAD_XMIT_DIRECT:
- xmit.outdev = dev_get_by_index_rcu(state->net, tuplehash->tuple.out.ifidx);
- if (!xmit.outdev) {
- flow_offload_teardown(flow);
- return NF_DROP;
- }
- xmit.dest = tuplehash->tuple.out.h_dest;
- xmit.source = tuplehash->tuple.out.h_source;
- break;
- default:
- WARN_ON_ONCE(1);
- return NF_DROP;
- }
- xmit.tuple = other_tuple;
- xmit.needs_gso_segment = tuplehash->tuple.needs_gso_segment;
-
- return nf_flow_queue_xmit(state->net, skb, &xmit);
-}
-EXPORT_SYMBOL_GPL(nf_flow_offload_ip_hook);
-
static void nf_flow_nat_ipv6_tcp(struct sk_buff *skb, unsigned int thoff,
struct in6_addr *addr,
struct in6_addr *new_addr,
@@ -1107,8 +1006,16 @@ static int nf_flow_offload_ipv6_forward(struct nf_flowtable_ctx *ctx,
flow = container_of(tuplehash, struct flow_offload, tuplehash[dir]);
mtu = flow->tuplehash[dir].tuple.mtu + ctx->offset;
- if (flow->tuplehash[!dir].tuple.tun_num)
+ switch (flow->tuplehash[!dir].tuple.tun.encap_proto) {
+ case AF_INET:
+ mtu -= sizeof(struct iphdr);
+ break;
+ case AF_INET6:
mtu -= sizeof(*ip6h);
+ break;
+ default:
+ break;
+ }
if (unlikely(nf_flow_exceeds_mtu(skb, mtu)))
return 0;
@@ -1142,6 +1049,25 @@ static int nf_flow_offload_ipv6_forward(struct nf_flowtable_ctx *ctx,
return 1;
}
+static struct flow_offload_tuple_rhash *
+nf_flow_offload_lookup(struct nf_flowtable_ctx *ctx,
+ struct nf_flowtable *flow_table, struct sk_buff *skb)
+{
+ struct flow_offload_tuple tuple = {};
+
+ if (!nf_flow_skb_encap_protocol(ctx, skb, htons(ETH_P_IP)))
+ return NULL;
+
+ if (ctx->tun.proto == IPPROTO_IPV6) {
+ if (nf_flow_tuple_ipv6(ctx, skb, &tuple) < 0)
+ return NULL;
+ } else if (nf_flow_tuple_ip(ctx, skb, &tuple) < 0) {
+ return NULL;
+ }
+
+ return flow_offload_lookup(flow_table, &tuple);
+}
+
static struct flow_offload_tuple_rhash *
nf_flow_offload_ipv6_lookup(struct nf_flowtable_ctx *ctx,
struct nf_flowtable *flow_table,
@@ -1162,6 +1088,110 @@ nf_flow_offload_ipv6_lookup(struct nf_flowtable_ctx *ctx,
return flow_offload_lookup(flow_table, &tuple);
}
+unsigned int
+nf_flow_offload_ip_hook(void *priv, struct sk_buff *skb,
+ const struct nf_hook_state *state)
+{
+ struct flow_offload_tuple_rhash *tuplehash;
+ struct nf_flowtable *flow_table = priv;
+ struct flow_offload_tuple *other_tuple;
+ enum flow_offload_tuple_dir dir;
+ struct nf_flowtable_ctx ctx = {
+ .in = state->in,
+ };
+ struct nf_flow_xmit xmit = {};
+ struct in6_addr *ip6_daddr;
+ struct flow_offload *flow;
+ struct neighbour *neigh;
+ struct rtable *rt;
+ __be32 ip_daddr;
+ int ret;
+
+ tuplehash = nf_flow_offload_lookup(&ctx, flow_table, skb);
+ if (!tuplehash)
+ return NF_ACCEPT;
+
+ if (ctx.tun.proto == IPPROTO_IPV6)
+ ret = nf_flow_offload_ipv6_forward(&ctx, flow_table, tuplehash,
+ skb);
+ else
+ ret = nf_flow_offload_forward(&ctx, flow_table, tuplehash, skb);
+ if (ret < 0)
+ return NF_DROP;
+ else if (ret == 0)
+ return NF_ACCEPT;
+
+ if (unlikely(tuplehash->tuple.xmit_type == FLOW_OFFLOAD_XMIT_XFRM)) {
+ rt = dst_rtable(tuplehash->tuple.dst_cache);
+ memset(skb->cb, 0, sizeof(struct inet_skb_parm));
+ IPCB(skb)->iif = skb->dev->ifindex;
+ IPCB(skb)->flags = IPSKB_FORWARDED;
+ return nf_flow_xmit_xfrm(skb, state, &rt->dst);
+ }
+
+ dir = tuplehash->tuple.dir;
+ flow = container_of(tuplehash, struct flow_offload, tuplehash[dir]);
+ other_tuple = &flow->tuplehash[!dir].tuple;
+ ip_daddr = other_tuple->src_v4.s_addr;
+ ip6_daddr = &other_tuple->src_v6;
+
+ if (nf_flow_tunnel_push(state->net, skb, other_tuple,
+ tuplehash->tuple.dst_cache,
+ &ip_daddr, &ip6_daddr) < 0)
+ return NF_DROP;
+
+ switch (tuplehash->tuple.xmit_type) {
+ case FLOW_OFFLOAD_XMIT_NEIGH: {
+ struct dst_entry *dst;
+
+ xmit.outdev = dev_get_by_index_rcu(state->net, tuplehash->tuple.ifidx);
+ if (!xmit.outdev) {
+ flow_offload_teardown(flow);
+ return NF_DROP;
+ }
+ if (other_tuple->tun.encap_proto ?
+ other_tuple->tun.encap_proto == AF_INET6 :
+ ctx.tun.proto == IPPROTO_IPV6) {
+ struct rt6_info *rt6;
+
+ rt6 = dst_rt6_info(tuplehash->tuple.dst_cache);
+ neigh = ip_neigh_gw6(rt6->dst.dev,
+ rt6_nexthop(rt6, ip6_daddr));
+ dst = &rt6->dst;
+ } else {
+ rt = dst_rtable(tuplehash->tuple.dst_cache);
+ neigh = ip_neigh_gw4(rt->dst.dev,
+ rt_nexthop(rt, ip_daddr));
+ dst = &rt->dst;
+ }
+ if (IS_ERR(neigh)) {
+ flow_offload_teardown(flow);
+ return NF_DROP;
+ }
+ xmit.dest = neigh->ha;
+ skb_dst_set_noref(skb, dst);
+ break;
+ }
+ case FLOW_OFFLOAD_XMIT_DIRECT:
+ xmit.outdev = dev_get_by_index_rcu(state->net, tuplehash->tuple.out.ifidx);
+ if (!xmit.outdev) {
+ flow_offload_teardown(flow);
+ return NF_DROP;
+ }
+ xmit.dest = tuplehash->tuple.out.h_dest;
+ xmit.source = tuplehash->tuple.out.h_source;
+ break;
+ default:
+ WARN_ON_ONCE(1);
+ return NF_DROP;
+ }
+ xmit.tuple = other_tuple;
+ xmit.needs_gso_segment = tuplehash->tuple.needs_gso_segment;
+
+ return nf_flow_queue_xmit(state->net, skb, &xmit);
+}
+EXPORT_SYMBOL_GPL(nf_flow_offload_ip_hook);
+
unsigned int
nf_flow_offload_ipv6_hook(void *priv, struct sk_buff *skb,
const struct nf_hook_state *state)
--
2.55.0
^ permalink raw reply related
* [PATCH nf-next v6 4/6] selftests: netfilter: nft_flowtable.sh: add IPv4 over IPv6 flowtable selftest
From: Lorenzo Bianconi @ 2026-07-13 12:46 UTC (permalink / raw)
To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Felix Fietkau, Matthias Brugger,
AngeloGioacchino Del Regno, Simon Horman, David Ahern,
Ido Schimmel, Pablo Neira Ayuso, Florian Westphal, Phil Sutter,
Shuah Khan, Lorenzo Bianconi
Cc: linux-arm-kernel, linux-mediatek, netdev, netfilter-devel,
coreteam, linux-kselftest
In-Reply-To: <20260713-b4-flowtable-sw-accel-ip6ip-v6-0-33f0155fc658@kernel.org>
Similar to IPIP and IP6IP6, introduce specific selftest for IPv4 over IPv6
flowtable sw acceleration in nft_flowtable.sh
Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
---
.../selftests/net/netfilter/nft_flowtable.sh | 33 +++++++++++++++++++---
1 file changed, 29 insertions(+), 4 deletions(-)
diff --git a/tools/testing/selftests/net/netfilter/nft_flowtable.sh b/tools/testing/selftests/net/netfilter/nft_flowtable.sh
index d9a21ca8ed2c..1cb5c65351b7 100755
--- a/tools/testing/selftests/net/netfilter/nft_flowtable.sh
+++ b/tools/testing/selftests/net/netfilter/nft_flowtable.sh
@@ -579,9 +579,8 @@ if ! test_tcp_forwarding_nat "$ns1" "$ns2" 1 ""; then
ip netns exec "$nsr1" nft list ruleset
fi
-# IPIP tunnel test:
-# Add IPIP tunnel interfaces and check flowtable acceleration.
-test_ipip() {
+# IP tunnel tests:
+test_ip_tnls() {
if ! ip -net "$nsr1" link add name tun0 type ipip \
local 192.168.10.1 remote 192.168.10.2 >/dev/null;then
echo "SKIP: could not add ipip tunnel"
@@ -594,7 +593,9 @@ ip netns exec "$nsr1" sysctl net.ipv4.conf.tun0.forwarding=1 > /dev/null
ip -net "$nsr1" link add name tun6 type ip6tnl local fee1:2::1 remote fee1:2::2 encaplimit none
ip -net "$nsr1" link set tun6 up
+ip -net "$nsr1" addr add 192.168.210.1/24 dev tun6
ip -net "$nsr1" addr add fee1:3::1/64 dev tun6 nodad
+ip netns exec "$nsr1" sysctl net.ipv4.conf.tun6.forwarding=1 > /dev/null
ip -net "$nsr2" link add name tun0 type ipip local 192.168.10.2 remote 192.168.10.1
ip -net "$nsr2" link set tun0 up
@@ -603,7 +604,9 @@ ip netns exec "$nsr2" sysctl net.ipv4.conf.tun0.forwarding=1 > /dev/null
ip -net "$nsr2" link add name tun6 type ip6tnl local fee1:2::2 remote fee1:2::1 encaplimit none || ret=1
ip -net "$nsr2" link set tun6 up
+ip -net "$nsr2" addr add 192.168.210.2/24 dev tun6
ip -net "$nsr2" addr add fee1:3::2/64 dev tun6 nodad
+ip netns exec "$nsr2" sysctl net.ipv4.conf.tun6.forwarding=1 > /dev/null
ip -net "$nsr1" route change default via 192.168.100.2
ip -net "$nsr2" route change default via 192.168.100.1
@@ -640,6 +643,15 @@ else
ret=1
fi
+ip -net "$nsr1" route change default via 192.168.210.2
+ip -net "$nsr2" route change default via 192.168.210.1
+
+if ! test_tcp_forwarding_nat "$ns1" "$ns2" 1 "IP4IP6 tunnel"; then
+ echo "FAIL: flow offload for ns1/ns2 with IP4IP6 tunnel" 1>&2
+ ip netns exec "$nsr1" nft list ruleset
+ ret=1
+fi
+
# Create vlan tagged devices for IPIP traffic.
ip -net "$nsr1" link add link veth1 name veth1.10 type vlan id 10
ip -net "$nsr1" link set veth1.10 up
@@ -661,7 +673,9 @@ ip netns exec "$nsr1" nft -a insert rule inet filter forward 'meta oif tun0.10 a
ip -net "$nsr1" link add name tun6.10 type ip6tnl local fee1:4::1 remote fee1:4::2 encaplimit none
ip -net "$nsr1" link set tun6.10 up
+ip -net "$nsr1" addr add 192.168.220.1/24 dev tun6.10
ip -net "$nsr1" addr add fee1:5::1/64 dev tun6.10 nodad
+ip netns exec "$nsr1" sysctl net.ipv4.conf.tun6/10.forwarding=1 > /dev/null
ip -6 -net "$nsr1" route delete default
ip -6 -net "$nsr1" route add default via fee1:5::2
ip netns exec "$nsr1" nft -a insert rule inet filter forward \
@@ -682,7 +696,9 @@ ip netns exec "$nsr2" sysctl net.ipv4.conf.tun0/10.forwarding=1 > /dev/null
ip -net "$nsr2" link add name tun6.10 type ip6tnl local fee1:4::2 remote fee1:4::1 encaplimit none || ret=1
ip -net "$nsr2" link set tun6.10 up
+ip -net "$nsr2" addr add 192.168.220.2/24 dev tun6.10
ip -net "$nsr2" addr add fee1:5::2/64 dev tun6.10 nodad
+ip netns exec "$nsr2" sysctl net.ipv4.conf.tun6/10.forwarding=1 > /dev/null
ip -6 -net "$nsr2" route delete default
ip -6 -net "$nsr2" route add default via fee1:5::1
@@ -700,6 +716,15 @@ else
ret=1
fi
+ip -net "$nsr1" route change default via 192.168.220.2
+ip -net "$nsr2" route change default via 192.168.220.1
+
+if ! test_tcp_forwarding_nat "$ns1" "$ns2" 1 "IP4IP6 tunnel over vlan"; then
+ echo "FAIL: flow offload for ns1/ns2 with IP4IP6 tunnel over vlan" 1>&2
+ ip netns exec "$nsr1" nft list ruleset
+ ret=1
+fi
+
# Restore the previous configuration
ip -net "$nsr1" route change default via 192.168.10.2
ip -net "$nsr2" route change default via 192.168.10.1
@@ -792,7 +817,7 @@ ip -net "$nsr1" addr add dead:1::1/64 dev veth0 nodad
ip -net "$nsr1" link set up dev veth0
}
-test_ipip
+test_ip_tnls
test_bridge
--
2.55.0
^ permalink raw reply related
* [PATCH nf-next v6 3/6] net: netfilter: add IPv4 over IPv6 tunnel flowtable acceleration
From: Lorenzo Bianconi @ 2026-07-13 12:46 UTC (permalink / raw)
To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Felix Fietkau, Matthias Brugger,
AngeloGioacchino Del Regno, Simon Horman, David Ahern,
Ido Schimmel, Pablo Neira Ayuso, Florian Westphal, Phil Sutter,
Shuah Khan, Lorenzo Bianconi
Cc: linux-arm-kernel, linux-mediatek, netdev, netfilter-devel,
coreteam, linux-kselftest
In-Reply-To: <20260713-b4-flowtable-sw-accel-ip6ip-v6-0-33f0155fc658@kernel.org>
Introduce sw flowtable acceleration for the TX/RX paths of
IPv4 over IPv6 tunnels, relying on the netfilter flowtable
infrastructure.
The feature can be tested with a forwarding scenario between two
NICs (eth0 and eth1), where an IPv4 over IPv6 tunnel is used to
reach a remote site via eth1 as the underlay device:
ETH0 -- TUN0 <==> ETH1 -- [IP network] -- TUN1 (2001:db8:2::2)
[IP configuration]
6: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
link/ether 00:00:22:33:11:55 brd ff:ff:ff:ff:ff:ff
inet 192.168.0.2/24 scope global eth0
valid_lft forever preferred_lft forever
7: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
link/ether 00:11:22:33:11:55 brd ff:ff:ff:ff:ff:ff
inet6 2001:db8:2::1/64 scope global nodad
valid_lft forever preferred_lft forever
8: tun0@NONE: <POINTOPOINT,NOARP,UP,LOWER_UP> mtu 1480 qdisc noqueue state UNKNOWN group default qlen 1000
link/tunnel6 2001:db8:2::1 peer 2001:db8:2::2 permaddr ce9c:2940:7dcc::
inet 192.168.100.1/24 scope global tun0
valid_lft forever preferred_lft forever
$ ip route show
default via 192.168.100.2 dev tun0
192.168.0.0/24 dev eth0 proto kernel scope link src 192.168.0.2
192.168.100.0/24 dev tun0 proto kernel scope link src 192.168.100.1
$ ip -6 route show
2001:db8:2::/64 dev eth1 proto kernel metric 256 pref medium
$ nft list ruleset
table inet filter {
flowtable ft {
hook ingress priority filter
devices = { eth0, eth1 }
}
chain forward {
type filter hook forward priority filter; policy accept;
meta l4proto { tcp, udp } flow add @ft
}
}
When reproducing this scenario using veth interfaces, the following
results were observed:
- TCP stream received from IPv4 over IPv6 tunnel:
- net-next (baseline): ~126 Gbps
- net-next + IP6IP flowtable support: ~138 Gbps
- TCP stream transmitted to IPv4 over IPv6 tunnel:
- net-next (baseline): ~127 Gbps
- net-next + IP6IP flowtable support: ~140 Gbps
Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
---
net/netfilter/nf_flow_table_core.c | 19 +++-
net/netfilter/nf_flow_table_ip.c | 190 +++++++++++++++++++++++++------------
net/netfilter/nf_flow_table_path.c | 10 +-
3 files changed, 147 insertions(+), 72 deletions(-)
diff --git a/net/netfilter/nf_flow_table_core.c b/net/netfilter/nf_flow_table_core.c
index 2a829b5e8240..56e58926fd31 100644
--- a/net/netfilter/nf_flow_table_core.c
+++ b/net/netfilter/nf_flow_table_core.c
@@ -76,9 +76,14 @@ struct flow_offload *flow_offload_alloc(struct nf_conn *ct)
}
EXPORT_SYMBOL_GPL(flow_offload_alloc);
-static u32 flow_offload_dst_cookie(struct flow_offload_tuple *flow_tuple)
+static u32 flow_offload_dst_cookie(struct flow_offload_tuple *flow_tuple,
+ u8 tun_encap_proto)
{
- if (flow_tuple->l3proto == NFPROTO_IPV6)
+ bool dst_v6;
+
+ dst_v6 = tun_encap_proto ? tun_encap_proto == NFPROTO_IPV6
+ : flow_tuple->l3proto == NFPROTO_IPV6;
+ if (dst_v6)
return rt6_get_cookie(dst_rt6_info(flow_tuple->dst_cache));
return 0;
@@ -99,10 +104,12 @@ static int flow_offload_fill_route(struct flow_offload *flow,
enum flow_offload_tuple_dir dir)
{
struct flow_offload_tuple *flow_tuple = &flow->tuplehash[dir].tuple;
+ u8 l3proto, encap_proto = route->tuple[!dir].in.tun.encap_proto;
struct dst_entry *dst = nft_route_dst_fetch(route, dir);
int i, j = 0;
- switch (flow_tuple->l3proto) {
+ l3proto = encap_proto ? encap_proto : flow_tuple->l3proto;
+ switch (l3proto) {
case NFPROTO_IPV4:
flow_tuple->mtu = ip_dst_mtu_maybe_forward(dst, true);
break;
@@ -130,7 +137,8 @@ static int flow_offload_fill_route(struct flow_offload *flow,
if (flow_tuple->tun_num) {
flow_tuple->dst_cache = dst;
flow_tuple->dst_cookie =
- flow_offload_dst_cookie(flow_tuple);
+ flow_offload_dst_cookie(flow_tuple,
+ encap_proto);
}
memcpy(flow_tuple->out.h_dest, route->tuple[dir].out.h_dest,
ETH_ALEN);
@@ -144,7 +152,8 @@ static int flow_offload_fill_route(struct flow_offload *flow,
case FLOW_OFFLOAD_XMIT_NEIGH:
flow_tuple->ifidx = route->tuple[dir].out.ifindex;
flow_tuple->dst_cache = dst;
- flow_tuple->dst_cookie = flow_offload_dst_cookie(flow_tuple);
+ flow_tuple->dst_cookie = flow_offload_dst_cookie(flow_tuple,
+ encap_proto);
break;
default:
WARN_ON_ONCE(1);
diff --git a/net/netfilter/nf_flow_table_ip.c b/net/netfilter/nf_flow_table_ip.c
index f29c69c362f5..9432aa140657 100644
--- a/net/netfilter/nf_flow_table_ip.c
+++ b/net/netfilter/nf_flow_table_ip.c
@@ -191,27 +191,27 @@ static void nf_flow_tuple_encap(struct nf_flowtable_ctx *ctx,
break;
}
- switch (inner_proto) {
- case htons(ETH_P_IP):
- iph = (struct iphdr *)(skb_network_header(skb) + offset);
- if (ctx->tun.proto == IPPROTO_IPIP) {
+ if (ctx->tun.proto == IPPROTO_IPIP || ctx->tun.proto == IPPROTO_IPV6) {
+ switch (inner_proto) {
+ case htons(ETH_P_IP):
+ iph = (struct iphdr *)(skb_network_header(skb) +
+ offset);
tuple->tun.dst_v4.s_addr = iph->daddr;
tuple->tun.src_v4.s_addr = iph->saddr;
- tuple->tun.l3_proto = IPPROTO_IPIP;
+ tuple->tun.l3_proto = ctx->tun.proto;
tuple->tun.encap_proto = AF_INET;
- }
- break;
- case htons(ETH_P_IPV6):
- ip6h = (struct ipv6hdr *)(skb_network_header(skb) + offset);
- if (ctx->tun.proto == IPPROTO_IPV6) {
+ break;
+ case htons(ETH_P_IPV6):
+ ip6h = (struct ipv6hdr *)(skb_network_header(skb) +
+ offset);
tuple->tun.dst_v6 = ip6h->daddr;
tuple->tun.src_v6 = ip6h->saddr;
- tuple->tun.l3_proto = IPPROTO_IPV6;
+ tuple->tun.l3_proto = ctx->tun.proto;
tuple->tun.encap_proto = AF_INET6;
+ break;
+ default:
+ break;
}
- break;
- default:
- break;
}
}
@@ -362,7 +362,7 @@ static bool nf_flow_ip6_tunnel_proto(struct nf_flowtable_ctx *ctx,
if (ipv6_ext_hdr(ip6h->nexthdr))
return false;
- if (ip6h->nexthdr == IPPROTO_IPV6) {
+ if (ip6h->nexthdr == IPPROTO_IPIP || ip6h->nexthdr == IPPROTO_IPV6) {
ctx->tun.proto = ip6h->nexthdr;
ctx->tun.hdr_size = sizeof(*ip6h);
ctx->offset += ctx->tun.hdr_size;
@@ -383,6 +383,10 @@ static void nf_flow_ip_tunnel_pop(struct nf_flowtable_ctx *ctx,
skb_pull(skb, ctx->tun.hdr_size);
skb_reset_network_header(skb);
+ if (ctx->tun.proto == IPPROTO_IPIP)
+ skb->protocol = htons(ETH_P_IP);
+ else
+ skb->protocol = htons(ETH_P_IPV6);
}
static bool nf_flow_skb_encap_protocol(struct nf_flowtable_ctx *ctx,
@@ -488,8 +492,16 @@ static int nf_flow_offload_forward(struct nf_flowtable_ctx *ctx,
flow = container_of(tuplehash, struct flow_offload, tuplehash[dir]);
mtu = flow->tuplehash[dir].tuple.mtu + ctx->offset;
- if (flow->tuplehash[!dir].tuple.tun_num)
+ switch (flow->tuplehash[!dir].tuple.tun.encap_proto) {
+ case AF_INET:
mtu -= sizeof(*iph);
+ break;
+ case AF_INET6:
+ mtu -= sizeof(struct ipv6hdr);
+ break;
+ default:
+ break;
+ }
if (unlikely(nf_flow_exceeds_mtu(skb, mtu)))
return 0;
@@ -616,6 +628,7 @@ static int nf_flow_tunnel_ipip_push(struct net *net, struct sk_buff *skb,
/* Push down and install the IP header. */
skb_push(skb, sizeof(*iph));
skb_reset_network_header(skb);
+ skb->protocol = htons(ETH_P_IP);
iph = ip_hdr(skb);
iph->version = 4;
@@ -635,57 +648,57 @@ static int nf_flow_tunnel_ipip_push(struct net *net, struct sk_buff *skb,
return 0;
}
-static int nf_flow_tunnel_v4_push(struct net *net, struct sk_buff *skb,
- struct flow_offload_tuple *tuple,
- struct dst_entry *dst, __be32 *ip_daddr)
+static int nf_flow_tunnel_ip6_push(struct net *net, struct sk_buff *skb,
+ struct flow_offload_tuple *tuple,
+ struct dst_entry *dst,
+ struct in6_addr **ip6_daddr)
{
- if (tuple->tun_num)
- return nf_flow_tunnel_ipip_push(net, skb, tuple, dst, ip_daddr);
-
- return 0;
-}
-
-static int nf_flow_tunnel_ip6ip6_push(struct net *net, struct sk_buff *skb,
- struct flow_offload_tuple *tuple,
- struct dst_entry *dst,
- struct in6_addr **ip6_daddr)
-{
- struct ipv6hdr *ip6h = (struct ipv6hdr *)skb_network_header(skb);
- __u8 dsfield = ipv6_get_dsfield(ip6h);
- struct rtable *rt = dst_rtable(dst);
struct flowi6 fl6 = {
.daddr = tuple->tun.src_v6,
.saddr = tuple->tun.dst_v6,
- .flowi6_proto = IPPROTO_IPV6,
+ .flowi6_proto = tuple->tun.l3_proto,
};
- u8 hop_limit = ip6h->hop_limit;
+ u8 hop_limit, dsfield;
+ struct ipv6hdr *ip6h;
int err, mtu;
u32 headroom;
+ if (tuple->tun.l3_proto == IPPROTO_IPIP) {
+ struct iphdr *iph = (struct iphdr *)skb_network_header(skb);
+
+ dsfield = ipv4_get_dsfield(iph);
+ hop_limit = iph->ttl;
+ } else {
+ ip6h = (struct ipv6hdr *)skb_network_header(skb);
+ dsfield = ipv6_get_dsfield(ip6h);
+ hop_limit = ip6h->hop_limit;
+ }
+
err = iptunnel_handle_offloads(skb, SKB_GSO_IPXIP6);
if (err)
return err;
- skb_set_inner_ipproto(skb, IPPROTO_IPV6);
- headroom = sizeof(*ip6h) + LL_RESERVED_SPACE(rt->dst.dev) +
- rt->dst.header_len;
+ skb_set_inner_ipproto(skb, tuple->tun.l3_proto);
+ headroom = sizeof(*ip6h) + LL_RESERVED_SPACE(dst->dev) +
+ dst->header_len;
err = skb_cow_head(skb, headroom);
if (err)
return err;
skb_scrub_packet(skb, true);
- mtu = dst_mtu(&rt->dst) - sizeof(*ip6h);
+ mtu = dst_mtu(dst) - sizeof(*ip6h);
mtu = max(mtu, IPV6_MIN_MTU);
skb_dst_update_pmtu_no_confirm(skb, mtu);
skb_push(skb, sizeof(*ip6h));
skb_reset_network_header(skb);
+ skb->protocol = htons(ETH_P_IPV6);
ip6h = ipv6_hdr(skb);
ip6_flow_hdr(ip6h, dsfield,
ip6_make_flowlabel(net, skb, fl6.flowlabel, true, &fl6));
ip6h->hop_limit = hop_limit;
- ip6h->nexthdr = IPPROTO_IPV6;
+ ip6h->nexthdr = tuple->tun.l3_proto;
ip6h->daddr = tuple->tun.src_v6;
ip6h->saddr = tuple->tun.dst_v6;
ipv6_hdr(skb)->payload_len = htons(skb->len - sizeof(*ip6h));
@@ -696,15 +709,21 @@ static int nf_flow_tunnel_ip6ip6_push(struct net *net, struct sk_buff *skb,
return 0;
}
-static int nf_flow_tunnel_v6_push(struct net *net, struct sk_buff *skb,
- struct flow_offload_tuple *tuple,
- struct dst_entry *dst,
- struct in6_addr **ip6_daddr)
+static int nf_flow_tunnel_push(struct net *net, struct sk_buff *skb,
+ struct flow_offload_tuple *tuple,
+ struct dst_entry *dst, __be32 *ip_daddr,
+ struct in6_addr **ip6_daddr)
{
- if (tuple->tun_num)
- return nf_flow_tunnel_ip6ip6_push(net, skb, tuple, dst, ip6_daddr);
-
- return 0;
+ switch (tuple->tun.encap_proto) {
+ case AF_INET:
+ return nf_flow_tunnel_ipip_push(net, skb, tuple, dst,
+ ip_daddr);
+ case AF_INET6:
+ return nf_flow_tunnel_ip6_push(net, skb, tuple, dst,
+ ip6_daddr);
+ default:
+ return 0;
+ }
}
static int nf_flow_encap_push(struct sk_buff *skb,
@@ -816,6 +835,7 @@ nf_flow_offload_ip_hook(void *priv, struct sk_buff *skb,
.in = state->in,
};
struct nf_flow_xmit xmit = {};
+ struct in6_addr *ip6_daddr;
struct flow_offload *flow;
struct neighbour *neigh;
struct rtable *rt;
@@ -844,27 +864,45 @@ nf_flow_offload_ip_hook(void *priv, struct sk_buff *skb,
flow = container_of(tuplehash, struct flow_offload, tuplehash[dir]);
other_tuple = &flow->tuplehash[!dir].tuple;
ip_daddr = other_tuple->src_v4.s_addr;
+ ip6_daddr = &other_tuple->src_v6;
- if (nf_flow_tunnel_v4_push(state->net, skb, other_tuple,
- tuplehash->tuple.dst_cache, &ip_daddr) < 0)
+ if (nf_flow_tunnel_push(state->net, skb, other_tuple,
+ tuplehash->tuple.dst_cache,
+ &ip_daddr, &ip6_daddr) < 0)
return NF_DROP;
switch (tuplehash->tuple.xmit_type) {
- case FLOW_OFFLOAD_XMIT_NEIGH:
- rt = dst_rtable(tuplehash->tuple.dst_cache);
+ case FLOW_OFFLOAD_XMIT_NEIGH: {
+ struct dst_entry *dst;
+
xmit.outdev = dev_get_by_index_rcu(state->net, tuplehash->tuple.ifidx);
if (!xmit.outdev) {
flow_offload_teardown(flow);
return NF_DROP;
}
- neigh = ip_neigh_gw4(rt->dst.dev, rt_nexthop(rt, ip_daddr));
+ if (other_tuple->tun.encap_proto ?
+ other_tuple->tun.encap_proto == AF_INET6 :
+ ctx.tun.proto == IPPROTO_IPV6) {
+ struct rt6_info *rt6;
+
+ rt6 = dst_rt6_info(tuplehash->tuple.dst_cache);
+ neigh = ip_neigh_gw6(rt6->dst.dev,
+ rt6_nexthop(rt6, ip6_daddr));
+ dst = &rt6->dst;
+ } else {
+ rt = dst_rtable(tuplehash->tuple.dst_cache);
+ neigh = ip_neigh_gw4(rt->dst.dev,
+ rt_nexthop(rt, ip_daddr));
+ dst = &rt->dst;
+ }
if (IS_ERR(neigh)) {
flow_offload_teardown(flow);
return NF_DROP;
}
xmit.dest = neigh->ha;
- skb_dst_set_noref(skb, &rt->dst);
+ skb_dst_set_noref(skb, dst);
break;
+ }
case FLOW_OFFLOAD_XMIT_DIRECT:
xmit.outdev = dev_get_by_index_rcu(state->net, tuplehash->tuple.out.ifidx);
if (!xmit.outdev) {
@@ -1114,8 +1152,12 @@ nf_flow_offload_ipv6_lookup(struct nf_flowtable_ctx *ctx,
if (!nf_flow_skb_encap_protocol(ctx, skb, htons(ETH_P_IPV6)))
return NULL;
- if (nf_flow_tuple_ipv6(ctx, skb, &tuple) < 0)
+ if (ctx->tun.proto == IPPROTO_IPIP) {
+ if (nf_flow_tuple_ip(ctx, skb, &tuple) < 0)
+ return NULL;
+ } else if (nf_flow_tuple_ipv6(ctx, skb, &tuple) < 0) {
return NULL;
+ }
return flow_offload_lookup(flow_table, &tuple);
}
@@ -1136,13 +1178,18 @@ nf_flow_offload_ipv6_hook(void *priv, struct sk_buff *skb,
struct flow_offload *flow;
struct neighbour *neigh;
struct rt6_info *rt;
+ __be32 ip_daddr;
int ret;
tuplehash = nf_flow_offload_ipv6_lookup(&ctx, flow_table, skb);
if (tuplehash == NULL)
return NF_ACCEPT;
- ret = nf_flow_offload_ipv6_forward(&ctx, flow_table, tuplehash, skb);
+ if (ctx.tun.proto == IPPROTO_IPIP)
+ ret = nf_flow_offload_forward(&ctx, flow_table, tuplehash, skb);
+ else
+ ret = nf_flow_offload_ipv6_forward(&ctx, flow_table, tuplehash,
+ skb);
if (ret < 0)
return NF_DROP;
else if (ret == 0)
@@ -1159,29 +1206,46 @@ nf_flow_offload_ipv6_hook(void *priv, struct sk_buff *skb,
dir = tuplehash->tuple.dir;
flow = container_of(tuplehash, struct flow_offload, tuplehash[dir]);
other_tuple = &flow->tuplehash[!dir].tuple;
+ ip_daddr = other_tuple->src_v4.s_addr;
ip6_daddr = &other_tuple->src_v6;
- if (nf_flow_tunnel_v6_push(state->net, skb, other_tuple,
- tuplehash->tuple.dst_cache,
- &ip6_daddr) < 0)
+ if (nf_flow_tunnel_push(state->net, skb, other_tuple,
+ tuplehash->tuple.dst_cache,
+ &ip_daddr, &ip6_daddr) < 0)
return NF_DROP;
switch (tuplehash->tuple.xmit_type) {
- case FLOW_OFFLOAD_XMIT_NEIGH:
- rt = dst_rt6_info(tuplehash->tuple.dst_cache);
+ case FLOW_OFFLOAD_XMIT_NEIGH: {
+ struct dst_entry *dst;
+
xmit.outdev = dev_get_by_index_rcu(state->net, tuplehash->tuple.ifidx);
if (!xmit.outdev) {
flow_offload_teardown(flow);
return NF_DROP;
}
- neigh = ip_neigh_gw6(rt->dst.dev, rt6_nexthop(rt, ip6_daddr));
+ if (other_tuple->tun.encap_proto ?
+ other_tuple->tun.encap_proto == AF_INET :
+ ctx.tun.proto == IPPROTO_IPIP) {
+ struct rtable *rt4;
+
+ rt4 = dst_rtable(tuplehash->tuple.dst_cache);
+ neigh = ip_neigh_gw4(rt4->dst.dev,
+ rt_nexthop(rt4, ip_daddr));
+ dst = &rt4->dst;
+ } else {
+ rt = dst_rt6_info(tuplehash->tuple.dst_cache);
+ neigh = ip_neigh_gw6(rt->dst.dev,
+ rt6_nexthop(rt, ip6_daddr));
+ dst = &rt->dst;
+ }
if (IS_ERR(neigh)) {
flow_offload_teardown(flow);
return NF_DROP;
}
xmit.dest = neigh->ha;
- skb_dst_set_noref(skb, &rt->dst);
+ skb_dst_set_noref(skb, dst);
break;
+ }
case FLOW_OFFLOAD_XMIT_DIRECT:
xmit.outdev = dev_get_by_index_rcu(state->net, tuplehash->tuple.out.ifidx);
if (!xmit.outdev) {
diff --git a/net/netfilter/nf_flow_table_path.c b/net/netfilter/nf_flow_table_path.c
index caaf48c5fd2a..5e84b7f18a26 100644
--- a/net/netfilter/nf_flow_table_path.c
+++ b/net/netfilter/nf_flow_table_path.c
@@ -216,12 +216,13 @@ static int nft_flow_tunnel_update_route(const struct nft_pktinfo *pkt,
struct dst_entry *tun_dst = NULL;
struct flowi fl = {};
- switch (nft_pf(pkt)) {
+ switch (tun->encap_proto) {
case NFPROTO_IPV4:
fl.u.ip4.daddr = tun->dst_v4.s_addr;
fl.u.ip4.saddr = tun->src_v4.s_addr;
fl.u.ip4.flowi4_iif = nft_in(pkt)->ifindex;
- fl.u.ip4.flowi4_dscp = ip4h_dscp(ip_hdr(pkt->skb));
+ if (nft_pf(pkt) == NFPROTO_IPV4)
+ fl.u.ip4.flowi4_dscp = ip4h_dscp(ip_hdr(pkt->skb));
fl.u.ip4.flowi4_mark = pkt->skb->mark;
fl.u.ip4.flowi4_flags = FLOWI_FLAG_ANYSRC;
break;
@@ -229,13 +230,14 @@ static int nft_flow_tunnel_update_route(const struct nft_pktinfo *pkt,
fl.u.ip6.daddr = tun->dst_v6;
fl.u.ip6.saddr = tun->src_v6;
fl.u.ip6.flowi6_iif = nft_in(pkt)->ifindex;
- fl.u.ip6.flowlabel = ip6_flowinfo(ipv6_hdr(pkt->skb));
+ if (nft_pf(pkt) == NFPROTO_IPV6)
+ fl.u.ip6.flowlabel = ip6_flowinfo(ipv6_hdr(pkt->skb));
fl.u.ip6.flowi6_mark = pkt->skb->mark;
fl.u.ip6.flowi6_flags = FLOWI_FLAG_ANYSRC;
break;
}
- nf_route(nft_net(pkt), &tun_dst, &fl, false, nft_pf(pkt));
+ nf_route(nft_net(pkt), &tun_dst, &fl, false, tun->encap_proto);
if (!tun_dst)
return -ENOENT;
--
2.55.0
^ permalink raw reply related
* [PATCH nf-next v6 2/6] net: netfilter: add encap_proto to flow_offload_tunnel
From: Lorenzo Bianconi @ 2026-07-13 12:46 UTC (permalink / raw)
To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Felix Fietkau, Matthias Brugger,
AngeloGioacchino Del Regno, Simon Horman, David Ahern,
Ido Schimmel, Pablo Neira Ayuso, Florian Westphal, Phil Sutter,
Shuah Khan, Lorenzo Bianconi
Cc: linux-arm-kernel, linux-mediatek, netdev, netfilter-devel,
coreteam, linux-kselftest
In-Reply-To: <20260713-b4-flowtable-sw-accel-ip6ip-v6-0-33f0155fc658@kernel.org>
Add encap_proto (AF_INET or AF_INET6) to struct flow_offload_tunnel
to allow its use as part of the hash table key during flowtable entry
lookup.
This is a preliminary change to support IPv4 over IPv6 tunneling via
the flowtable infrastructure for software acceleration.
Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
---
include/linux/netdevice.h | 1 +
include/net/netfilter/nf_flow_table.h | 1 +
net/ipv4/ipip.c | 1 +
net/ipv6/ip6_tunnel.c | 1 +
net/netfilter/nf_flow_table_ip.c | 2 ++
net/netfilter/nf_flow_table_path.c | 2 ++
6 files changed, 8 insertions(+)
diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h
index 3ed53f390606..0630c07e543e 100644
--- a/include/linux/netdevice.h
+++ b/include/linux/netdevice.h
@@ -902,6 +902,7 @@ struct net_device_path {
};
u8 l3_proto;
+ u8 encap_proto;
} tun;
struct {
enum {
diff --git a/include/net/netfilter/nf_flow_table.h b/include/net/netfilter/nf_flow_table.h
index ce414118962f..bae0f55743e9 100644
--- a/include/net/netfilter/nf_flow_table.h
+++ b/include/net/netfilter/nf_flow_table.h
@@ -118,6 +118,7 @@ struct flow_offload_tunnel {
};
u8 l3_proto;
+ u8 encap_proto;
};
struct flow_offload_tuple {
diff --git a/net/ipv4/ipip.c b/net/ipv4/ipip.c
index d1aa048a6099..4d2195b4ae3e 100644
--- a/net/ipv4/ipip.c
+++ b/net/ipv4/ipip.c
@@ -370,6 +370,7 @@ static int ipip_fill_forward_path(struct net_device_path_ctx *ctx,
path->tun.src_v4.s_addr = tiph->saddr;
path->tun.dst_v4.s_addr = tiph->daddr;
path->tun.l3_proto = IPPROTO_IPIP;
+ path->tun.encap_proto = AF_INET;
path->dev = ctx->dev;
ctx->dev = rt->dst.dev;
diff --git a/net/ipv6/ip6_tunnel.c b/net/ipv6/ip6_tunnel.c
index 38da07101601..a121f715afd2 100644
--- a/net/ipv6/ip6_tunnel.c
+++ b/net/ipv6/ip6_tunnel.c
@@ -1863,6 +1863,7 @@ static int ip6_tnl_fill_forward_path(struct net_device_path_ctx *ctx,
path->type = DEV_PATH_TUN;
path->tun.src_v6 = t->parms.laddr;
path->tun.dst_v6 = t->parms.raddr;
+ path->tun.encap_proto = AF_INET6;
if (ctx->ether_type == cpu_to_be16(ETH_P_IP))
path->tun.l3_proto = IPPROTO_IPIP;
else
diff --git a/net/netfilter/nf_flow_table_ip.c b/net/netfilter/nf_flow_table_ip.c
index 0b78decce8a9..f29c69c362f5 100644
--- a/net/netfilter/nf_flow_table_ip.c
+++ b/net/netfilter/nf_flow_table_ip.c
@@ -198,6 +198,7 @@ static void nf_flow_tuple_encap(struct nf_flowtable_ctx *ctx,
tuple->tun.dst_v4.s_addr = iph->daddr;
tuple->tun.src_v4.s_addr = iph->saddr;
tuple->tun.l3_proto = IPPROTO_IPIP;
+ tuple->tun.encap_proto = AF_INET;
}
break;
case htons(ETH_P_IPV6):
@@ -206,6 +207,7 @@ static void nf_flow_tuple_encap(struct nf_flowtable_ctx *ctx,
tuple->tun.dst_v6 = ip6h->daddr;
tuple->tun.src_v6 = ip6h->saddr;
tuple->tun.l3_proto = IPPROTO_IPV6;
+ tuple->tun.encap_proto = AF_INET6;
}
break;
default:
diff --git a/net/netfilter/nf_flow_table_path.c b/net/netfilter/nf_flow_table_path.c
index c8011ec36532..caaf48c5fd2a 100644
--- a/net/netfilter/nf_flow_table_path.c
+++ b/net/netfilter/nf_flow_table_path.c
@@ -129,6 +129,7 @@ static int nft_dev_path_info(const struct net_device_path_stack *stack,
info->tun.src_v6 = path->tun.src_v6;
info->tun.dst_v6 = path->tun.dst_v6;
info->tun.l3_proto = path->tun.l3_proto;
+ info->tun.encap_proto = path->tun.encap_proto;
info->num_tuns++;
} else {
if (info->num_encaps >= NF_FLOW_TABLE_ENCAP_MAX)
@@ -278,6 +279,7 @@ static int nft_dev_forward_path(const struct nft_pktinfo *pkt,
route->tuple[!dir].in.tun.src_v6 = info.tun.dst_v6;
route->tuple[!dir].in.tun.dst_v6 = info.tun.src_v6;
route->tuple[!dir].in.tun.l3_proto = info.tun.l3_proto;
+ route->tuple[!dir].in.tun.encap_proto = info.tun.encap_proto;
route->tuple[!dir].in.num_tuns = info.num_tuns;
}
--
2.55.0
^ permalink raw reply related
* [PATCH nf-next v6 1/6] net: netfilter: add ether_type to net_device_path_ctx
From: Lorenzo Bianconi @ 2026-07-13 12:46 UTC (permalink / raw)
To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Felix Fietkau, Matthias Brugger,
AngeloGioacchino Del Regno, Simon Horman, David Ahern,
Ido Schimmel, Pablo Neira Ayuso, Florian Westphal, Phil Sutter,
Shuah Khan, Lorenzo Bianconi
Cc: linux-arm-kernel, linux-mediatek, netdev, netfilter-devel,
coreteam, linux-kselftest
In-Reply-To: <20260713-b4-flowtable-sw-accel-ip6ip-v6-0-33f0155fc658@kernel.org>
Add an ether_type field to struct net_device_path_ctx to allow IPv6
tunnel drivers to select the appropriate L3 protocol based on the
encapsulated traffic.
Update the airoha and mtk Ethernet drivers to use the new
dev_fill_forward_path() signature.
This is a preliminary patch to enable sw flowtable acceleration for
IPv4 over IPv6 tunnels.
Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
---
drivers/net/ethernet/airoha/airoha_ppe.c | 13 ++++++++-----
drivers/net/ethernet/mediatek/mtk_ppe_offload.c | 13 ++++++++-----
include/linux/netdevice.h | 4 +++-
net/core/dev.c | 6 ++++--
net/ipv6/ip6_tunnel.c | 5 ++++-
net/netfilter/nf_flow_table_path.c | 8 +++++---
6 files changed, 32 insertions(+), 17 deletions(-)
diff --git a/drivers/net/ethernet/airoha/airoha_ppe.c b/drivers/net/ethernet/airoha/airoha_ppe.c
index e7c78293002a..06d128c67c8c 100644
--- a/drivers/net/ethernet/airoha/airoha_ppe.c
+++ b/drivers/net/ethernet/airoha/airoha_ppe.c
@@ -276,7 +276,8 @@ static int airoha_ppe_flow_mangle_ipv4(const struct flow_action_entry *act,
return 0;
}
-static int airoha_ppe_get_wdma_info(struct net_device *dev, const u8 *addr,
+static int airoha_ppe_get_wdma_info(struct net_device *dev,
+ const u8 *addr, __be16 ether_type,
struct airoha_wdma_info *info)
{
struct net_device_path_stack stack;
@@ -287,7 +288,7 @@ static int airoha_ppe_get_wdma_info(struct net_device *dev, const u8 *addr,
return -ENODEV;
rcu_read_lock();
- err = dev_fill_forward_path(dev, addr, &stack);
+ err = dev_fill_forward_path(dev, addr, ether_type, &stack);
rcu_read_unlock();
if (err)
return err;
@@ -331,7 +332,7 @@ static int airoha_ppe_foe_entry_prepare(struct airoha_eth *eth,
struct airoha_foe_entry *hwe,
struct net_device *netdev, int type,
struct airoha_flow_data *data,
- int l4proto)
+ __be16 ether_type, int l4proto)
{
u32 qdata = FIELD_PREP(AIROHA_FOE_SHAPER_ID, 0x7f), ports_pad, val;
int wlan_etype = -EINVAL, dsa_port = airoha_get_dsa_port(&netdev);
@@ -354,7 +355,7 @@ static int airoha_ppe_foe_entry_prepare(struct airoha_eth *eth,
struct airoha_wdma_info info = {};
if (!airoha_ppe_get_wdma_info(netdev, data->eth.h_dest,
- &info)) {
+ ether_type, &info)) {
val |= FIELD_PREP(AIROHA_FOE_IB2_NBQ, info.idx) |
FIELD_PREP(AIROHA_FOE_IB2_PSE_PORT,
FE_PSE_PORT_CDM4);
@@ -1081,6 +1082,7 @@ static int airoha_ppe_flow_offload_replace(struct airoha_eth *eth,
struct flow_action_entry *act;
struct airoha_foe_entry hwe;
int err, i, offload_type;
+ __be16 ether_type = 0;
u16 addr_type = 0;
u8 l4proto = 0;
@@ -1107,6 +1109,7 @@ static int airoha_ppe_flow_offload_replace(struct airoha_eth *eth,
struct flow_match_basic match;
flow_rule_match_basic(rule, &match);
+ ether_type = match.key->n_proto;
l4proto = match.key->ip_proto;
} else {
return -EOPNOTSUPP;
@@ -1177,7 +1180,7 @@ static int airoha_ppe_flow_offload_replace(struct airoha_eth *eth,
return -EINVAL;
err = airoha_ppe_foe_entry_prepare(eth, &hwe, odev, offload_type,
- &data, l4proto);
+ &data, ether_type, l4proto);
if (err)
return err;
diff --git a/drivers/net/ethernet/mediatek/mtk_ppe_offload.c b/drivers/net/ethernet/mediatek/mtk_ppe_offload.c
index cc8c4ef8038f..2601c17b29c8 100644
--- a/drivers/net/ethernet/mediatek/mtk_ppe_offload.c
+++ b/drivers/net/ethernet/mediatek/mtk_ppe_offload.c
@@ -89,7 +89,8 @@ mtk_flow_offload_mangle_eth(const struct flow_action_entry *act, void *eth)
}
static int
-mtk_flow_get_wdma_info(struct net_device *dev, const u8 *addr, struct mtk_wdma_info *info)
+mtk_flow_get_wdma_info(struct net_device *dev, const u8 *addr,
+ __be16 ether_type, struct mtk_wdma_info *info)
{
struct net_device_path_stack stack;
struct net_device_path *path;
@@ -102,7 +103,7 @@ mtk_flow_get_wdma_info(struct net_device *dev, const u8 *addr, struct mtk_wdma_i
return -1;
rcu_read_lock();
- err = dev_fill_forward_path(dev, addr, &stack);
+ err = dev_fill_forward_path(dev, addr, ether_type, &stack);
rcu_read_unlock();
if (err)
return err;
@@ -190,12 +191,12 @@ mtk_flow_get_dsa_port(struct net_device **dev)
static int
mtk_flow_set_output_device(struct mtk_eth *eth, struct mtk_foe_entry *foe,
struct net_device *dev, const u8 *dest_mac,
- int *wed_index)
+ __be16 ether_type, int *wed_index)
{
struct mtk_wdma_info info = {};
int pse_port, dsa_port, queue;
- if (mtk_flow_get_wdma_info(dev, dest_mac, &info) == 0) {
+ if (mtk_flow_get_wdma_info(dev, dest_mac, ether_type, &info) == 0) {
mtk_foe_entry_set_wdma(eth, foe, info.wdma_idx, info.queue,
info.bss, info.wcid, info.amsdu);
if (mtk_is_netsys_v2_or_greater(eth)) {
@@ -273,6 +274,7 @@ mtk_flow_offload_replace(struct mtk_eth *eth, struct flow_cls_offload *f,
struct mtk_flow_data data = {};
struct mtk_foe_entry foe;
struct mtk_flow_entry *entry;
+ __be16 ether_type = 0;
int offload_type = 0;
int wed_index = -1;
u16 addr_type = 0;
@@ -319,6 +321,7 @@ mtk_flow_offload_replace(struct mtk_eth *eth, struct flow_cls_offload *f,
struct flow_match_basic match;
flow_rule_match_basic(rule, &match);
+ ether_type = match.key->n_proto;
l4proto = match.key->ip_proto;
} else {
return -EOPNOTSUPP;
@@ -481,7 +484,7 @@ mtk_flow_offload_replace(struct mtk_eth *eth, struct flow_cls_offload *f,
mtk_foe_entry_set_pppoe(eth, &foe, data.pppoe.sid);
err = mtk_flow_set_output_device(eth, &foe, odev, data.eth.h_dest,
- &wed_index);
+ ether_type, &wed_index);
if (err)
return err;
diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h
index 8db25b79573e..3ed53f390606 100644
--- a/include/linux/netdevice.h
+++ b/include/linux/netdevice.h
@@ -938,6 +938,7 @@ struct net_device_path_stack {
struct net_device_path_ctx {
const struct net_device *dev;
u8 daddr[ETH_ALEN];
+ __be16 ether_type;
int num_vlans;
struct {
@@ -3425,7 +3426,8 @@ void dev_remove_offload(struct packet_offload *po);
int dev_get_iflink(const struct net_device *dev);
int dev_fill_metadata_dst(struct net_device *dev, struct sk_buff *skb);
-int dev_fill_forward_path(const struct net_device *dev, const u8 *daddr,
+int dev_fill_forward_path(const struct net_device *dev,
+ const u8 *daddr, __be16 ether_type,
struct net_device_path_stack *stack);
struct net_device *dev_get_by_name(struct net *net, const char *name);
struct net_device *dev_get_by_name_rcu(struct net *net, const char *name);
diff --git a/net/core/dev.c b/net/core/dev.c
index 7c21bc0a1e34..e66447ccf0ea 100644
--- a/net/core/dev.c
+++ b/net/core/dev.c
@@ -750,12 +750,14 @@ static struct net_device_path *dev_fwd_path(struct net_device_path_stack *stack)
return &stack->path[k];
}
-int dev_fill_forward_path(const struct net_device *dev, const u8 *daddr,
+int dev_fill_forward_path(const struct net_device *dev,
+ const u8 *daddr, __be16 ether_type,
struct net_device_path_stack *stack)
{
const struct net_device *last_dev;
struct net_device_path_ctx ctx = {
- .dev = dev,
+ .dev = dev,
+ .ether_type = ether_type,
};
struct net_device_path *path;
int ret = 0;
diff --git a/net/ipv6/ip6_tunnel.c b/net/ipv6/ip6_tunnel.c
index bf8e40af60b0..38da07101601 100644
--- a/net/ipv6/ip6_tunnel.c
+++ b/net/ipv6/ip6_tunnel.c
@@ -1863,7 +1863,10 @@ static int ip6_tnl_fill_forward_path(struct net_device_path_ctx *ctx,
path->type = DEV_PATH_TUN;
path->tun.src_v6 = t->parms.laddr;
path->tun.dst_v6 = t->parms.raddr;
- path->tun.l3_proto = IPPROTO_IPV6;
+ if (ctx->ether_type == cpu_to_be16(ETH_P_IP))
+ path->tun.l3_proto = IPPROTO_IPIP;
+ else
+ path->tun.l3_proto = IPPROTO_IPV6;
path->dev = ctx->dev;
ctx->dev = dst->dev;
}
diff --git a/net/netfilter/nf_flow_table_path.c b/net/netfilter/nf_flow_table_path.c
index 98c03b487f52..c8011ec36532 100644
--- a/net/netfilter/nf_flow_table_path.c
+++ b/net/netfilter/nf_flow_table_path.c
@@ -45,7 +45,8 @@ static bool nft_is_valid_ether_device(const struct net_device *dev)
static int nft_dev_fill_forward_path(const struct nf_flow_route *route,
const struct dst_entry *dst_cache,
const struct nf_conn *ct,
- enum ip_conntrack_dir dir, u8 *ha,
+ enum ip_conntrack_dir dir,
+ u8 *ha, __be16 ether_type,
struct net_device_path_stack *stack)
{
const void *daddr = &ct->tuplehash[!dir].tuple.src.u3;
@@ -72,7 +73,7 @@ static int nft_dev_fill_forward_path(const struct nf_flow_route *route,
return -1;
out:
- return dev_fill_forward_path(dev, ha, stack);
+ return dev_fill_forward_path(dev, ha, ether_type, stack);
}
struct nft_forward_info {
@@ -255,7 +256,8 @@ static int nft_dev_forward_path(const struct nft_pktinfo *pkt,
unsigned char ha[ETH_ALEN];
int i;
- if (nft_dev_fill_forward_path(route, dst, ct, dir, ha, &stack) < 0 ||
+ if (nft_dev_fill_forward_path(route, dst, ct, dir, ha, pkt->ethertype,
+ &stack) < 0 ||
nft_dev_path_info(&stack, &info, ha, &ft->data) < 0)
return -ENOENT;
--
2.55.0
^ permalink raw reply related
* [PATCH nf-next v6 0/6] Add IPv4 over IPv6 and SIT flowtable SW acceleration
From: Lorenzo Bianconi @ 2026-07-13 12:46 UTC (permalink / raw)
To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Felix Fietkau, Matthias Brugger,
AngeloGioacchino Del Regno, Simon Horman, David Ahern,
Ido Schimmel, Pablo Neira Ayuso, Florian Westphal, Phil Sutter,
Shuah Khan, Lorenzo Bianconi
Cc: linux-arm-kernel, linux-mediatek, netdev, netfilter-devel,
coreteam, linux-kselftest
Similar to IPIP and IP6I6 tunnels, introduce sw acceleration for IPv4 over
IPv6 and SIT tunnels in the netfilter flowtable infrastructure.
---
Changes in v6:
- Rebase on top of nf-next and fixed conflicts.
- Fix the case when both ingress and egress interfaces are IP tunnels.
- Link to v5: https://lore.kernel.org/r/20260709-b4-flowtable-sw-accel-ip6ip-v5-0-828ceaf85bab@kernel.org
Changes in v5:
- Fix ipip6_tunnel_fill_forward_path() to take into account not only
IPv6 packet since SIT tunnels can encapsulate even IPv4/MPLS traffic.
- Return an error in ipip6_tunnel_fill_forward_path() if tunnel daddr is
not set since NBMA tunnels are not supported yet.
- Return an error in ipip6_tunnel_fill_forward_path() if encap.type is
not TUNNEL_ENCAP_NONE
- cosmetics
- Link to v4: https://lore.kernel.org/r/20260703-b4-flowtable-sw-accel-ip6ip-v4-0-00398cd12382@kernel.org
Changes in v4:
- Rebase on top of nf-next and fixed conflicts.
- Link to v3: https://lore.kernel.org/r/20260531-b4-flowtable-sw-accel-ip6ip-v3-0-56a2826f3279@kernel.org
Changes in v3:
- Drop nf_flow_tunnel_v4_push and nf_flow_tunnel_v6_push routines
- Rebase on top of net-next tree.
- Link to v2: https://lore.kernel.org/r/20260506-b4-flowtable-sw-accel-ip6ip-v2-0-439fd427726e@kernel.org
Changes in v2:
- Fix MTU check in nf_flow_offload_forward() and in
nf_flow_offload_ipv6_forward()
- Add SIT sw acceleration support
- Link to v1: https://lore.kernel.org/r/20260505-b4-flowtable-sw-accel-ip6ip-v1-0-9ac39ccc9ea9@kernel.org
---
Lorenzo Bianconi (6):
net: netfilter: add ether_type to net_device_path_ctx
net: netfilter: add encap_proto to flow_offload_tunnel
net: netfilter: add IPv4 over IPv6 tunnel flowtable acceleration
selftests: netfilter: nft_flowtable.sh: add IPv4 over IPv6 flowtable selftest
net: netfilter: add SIT tunnel flowtable acceleration
selftests: netfilter: nft_flowtable.sh: add SIT flowtable selftest
drivers/net/ethernet/airoha/airoha_ppe.c | 13 +-
drivers/net/ethernet/mediatek/mtk_ppe_offload.c | 13 +-
include/linux/netdevice.h | 5 +-
include/net/netfilter/nf_flow_table.h | 1 +
net/core/dev.c | 6 +-
net/ipv4/ipip.c | 1 +
net/ipv6/ip6_tunnel.c | 6 +-
net/ipv6/sit.c | 60 +++
net/netfilter/nf_flow_table_core.c | 19 +-
net/netfilter/nf_flow_table_ip.c | 424 +++++++++++++--------
net/netfilter/nf_flow_table_path.c | 20 +-
tools/testing/selftests/net/netfilter/config | 1 +
.../selftests/net/netfilter/nft_flowtable.sh | 86 ++++-
13 files changed, 461 insertions(+), 194 deletions(-)
---
base-commit: 0150128b9506b5869d8d2000c6e71bef0719d6ce
change-id: 20260505-b4-flowtable-sw-accel-ip6ip-7101034cd147
Best regards,
--
Lorenzo Bianconi <lorenzo@kernel.org>
^ permalink raw reply
* [RFC PATCH net-next 2/2] selftests/net: selftest for AF_LLC jumbo frames
From: David 'equinox' Lamparter @ 2026-07-13 12:43 UTC (permalink / raw)
To: netdev
Cc: David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman, David 'equinox' Lamparter
In-Reply-To: <20260713124545.462790-1-equinox@diac24.net>
This selftest just checks that AF_LLC correctly in- & output frames
below and above the jumbo frame threshold (1500 bytes). It more or less
also doubles as basic AF_LLC test.
Unfortunately AF_LLC can't be used in a netns, so this test must run as
root in the initial netns.
Signed-off-by: David 'equinox' Lamparter <equinox@diac24.net>
---
MAINTAINERS | 1 +
tools/testing/selftests/net/Makefile | 1 +
tools/testing/selftests/net/llc_jumbo.py | 337 +++++++++++++++++++++++
3 files changed, 339 insertions(+)
create mode 100644 tools/testing/selftests/net/llc_jumbo.py
diff --git a/MAINTAINERS b/MAINTAINERS
index f3218abefd0c..8ca5afa39734 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -15057,6 +15057,7 @@ F: include/linux/llc.h
F: include/net/llc*
F: include/uapi/linux/llc.h
F: net/llc/
+F: tools/testing/selftests/net/llc_jumbo.py
LM73 HARDWARE MONITOR DRIVER
M: Guillaume Ligneul <guillaume.ligneul@gmail.com>
diff --git a/tools/testing/selftests/net/Makefile b/tools/testing/selftests/net/Makefile
index 708d960ae07d..4b3d5491909b 100644
--- a/tools/testing/selftests/net/Makefile
+++ b/tools/testing/selftests/net/Makefile
@@ -57,6 +57,7 @@ TEST_PROGS := \
l2_tos_ttl_inherit.sh \
l2tp.sh \
link_netns.py \
+ llc_jumbo.py \
lwt_dst_cache_ref_loop.sh \
macvlan_mcast_shared_mac.sh \
msg_zerocopy.sh \
diff --git a/tools/testing/selftests/net/llc_jumbo.py b/tools/testing/selftests/net/llc_jumbo.py
new file mode 100644
index 000000000000..51c23ea6f812
--- /dev/null
+++ b/tools/testing/selftests/net/llc_jumbo.py
@@ -0,0 +1,337 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0
+"""
+Copyright (C) 2026 David 'equinox' Lamparter
+
+Linux kernel self-test for AF_LLC sockets, specifically jumbo frame (802.1AC)
+support.
+"""
+
+from __future__ import annotations
+
+import os
+import signal
+import struct
+import binascii
+import functools
+import errno
+import ctypes
+from typing import Callable
+
+from socket import socket, htons, SOCK_DGRAM, SOCK_RAW, AF_PACKET, ETH_P_ALL
+
+from lib.py import ksft_run, ksft_exit, ksft_eq, KsftSkipEx
+from lib.py import ip
+
+# from lib.py import NetNS, NetNSEnter
+
+
+ETH_P_8021AC = 0x8870
+AF_LLC = 26
+
+
+class ContextSkip:
+ """
+ convert exception from context manager into KsftSkipEx with message
+ """
+
+ def __init__(self, ctx, message):
+ self._ctx = ctx
+ self._message = message
+
+ def __enter__(self):
+ try:
+ return self._ctx.__enter__()
+ except Exception as e:
+ raise KsftSkipEx(self._message + "[" + repr(e) + "]") from e
+
+ def __exit__(self, exc_type, exc_value, traceback):
+ self._ctx.__exit__(exc_type, exc_value, traceback)
+
+
+# Python's socket.bind() has no clue about AF_LLC
+c_macaddr = ctypes.c_ubyte * 6
+
+
+class sockaddr_llc(ctypes.Structure):
+ _fields_ = [
+ ("sllc_family", ctypes.c_ushort),
+ ("sllc_arphrd", ctypes.c_ushort),
+ ("sllc_test", ctypes.c_ubyte),
+ ("sllc_xid", ctypes.c_ubyte),
+ ("sllc_ua", ctypes.c_ubyte),
+ ("sllc_sap", ctypes.c_ubyte),
+ ("sllc_mac", c_macaddr),
+ ("pad", ctypes.c_ubyte * 2),
+ ]
+
+ @classmethod
+ def make(
+ cls,
+ sllc_family=AF_LLC,
+ sllc_arphrd=0,
+ sllc_test=0,
+ sllc_xid=0,
+ sllc_ua=0,
+ sllc_sap=0,
+ sllc_mac: None | str | bytes = None,
+ ) -> sockaddr_llc:
+ if sllc_mac is None:
+ _sllc_mac = c_macaddr(0, 0, 0, 0, 0, 0)
+ elif isinstance(sllc_mac, str):
+ _sllc_mac = c_macaddr(*(int(b, 16) for b in sllc_mac.split(":")))
+ else:
+ _sllc_mac = c_macaddr(*(b for b in sllc_mac))
+ return cls(
+ sllc_family, sllc_arphrd, sllc_test, sllc_xid, sllc_ua, sllc_sap, _sllc_mac
+ )
+
+
+assert len(bytes(sockaddr_llc())) == 16
+
+
+libc = ctypes.CDLL(None, use_errno=True)
+libc.bind.argtypes = (ctypes.c_int, ctypes.POINTER(sockaddr_llc), ctypes.c_int)
+libc.bind.restype = ctypes.c_int
+libc.sendto.argtypes = (
+ ctypes.c_int,
+ ctypes.c_voidp,
+ ctypes.c_size_t,
+ ctypes.c_int,
+ ctypes.POINTER(sockaddr_llc),
+ ctypes.c_int,
+)
+libc.sendto.restype = ctypes.c_int
+
+
+def llc_bind(fd: socket, addr: sockaddr_llc) -> None:
+ ret = libc.bind(fd.fileno(), addr, len(bytes(addr)))
+ if ret:
+ err = ctypes.get_errno()
+ raise OSError(err, errno.errorcode.get(err, str(err)))
+
+
+def llc_sendto(fd: socket, addr: sockaddr_llc, data: bytes, flags=0) -> None:
+ ret = libc.sendto(fd.fileno(), data, len(data), flags, addr, len(bytes(addr)))
+ if ret < 0:
+ err = ctypes.get_errno()
+ raise OSError(err, errno.errorcode.get(err, str(err)))
+ return ret
+
+
+def wrap_common_setup(testfn: Callable[[socket, socket], None]) -> Callable[[], None]:
+ """
+ common setup for all LLC tests
+
+ (create netns, create AF_LLC + AF_PACKET sockets)
+ """
+
+ def inner1() -> None:
+ if os.path.exists("/sys/class/net/testveth0"):
+ raise KsftSkipEx("already have a testveth0 netdev")
+ if os.path.exists("/sys/class/net/testveth1"):
+ raise KsftSkipEx("already have a testveth1 netdev")
+
+ ip(
+ "link add name testveth0 address 02:00:00:00:00:00 "
+ + "type veth peer name testveth1 address 02:11:11:11:11:11"
+ )
+ with open(
+ "/proc/sys/net/ipv6/conf/testveth0/disable_ipv6", "w", encoding="ASCII"
+ ) as fd:
+ fd.write("1\n")
+ with open(
+ "/proc/sys/net/ipv6/conf/testveth1/disable_ipv6", "w", encoding="ASCII"
+ ) as fd:
+ fd.write("1\n")
+ ip("link set testveth0 mtu 9000 up")
+ ip("link set testveth1 mtu 9000 up")
+
+ try:
+ with ContextSkip(
+ socket(AF_LLC, SOCK_DGRAM, 0), "AF_LLC not enabled in kernel?"
+ ) as sock0_llc:
+ llc_bind(
+ sock0_llc,
+ sockaddr_llc.make(sllc_sap=0xFE, sllc_mac="02:00:00:00:00:00"),
+ )
+
+ with ContextSkip(
+ socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL)),
+ "AF_PACKET not enabled in kernel?",
+ ) as sock1_pkt:
+ sock1_pkt.bind(("testveth1", ETH_P_ALL))
+
+ signal.alarm(10)
+ testfn(sock0_llc, sock1_pkt)
+
+ finally:
+ signal.alarm(0)
+ ip("link del testveth0")
+
+ @functools.wraps(testfn)
+ def inner() -> None:
+ if os.getuid() == 0:
+ inner1()
+ else:
+ raise KsftSkipEx(
+ "this test requires root since AF_LLC has no netns support (yet?)"
+ )
+ # with NetNS() as ns1:
+ # with NetNSEnter(str(ns1)):
+ # inner1()
+
+ return inner
+
+
+def pkt_check(fd: socket, expectlen: int, data: None | bytes = None):
+ # ignore non-LLC packets
+ # (since we can't run in netns, random junk may show up)
+ proto_len = -1
+ while not (0 <= proto_len < 1536 or proto_len == 0x8870):
+ pkt = fd.recv(4096)
+ proto_len = struct.unpack(">H", pkt[12:14])[0]
+
+ assert binascii.b2a_hex(pkt[0:6]) == b"021111111111"
+ assert binascii.b2a_hex(pkt[6:12]) == b"020000000000"
+
+ if expectlen <= 1497:
+ ksft_eq(proto_len, expectlen + 3)
+ else:
+ ksft_eq(proto_len, ETH_P_8021AC, "802.1AC ethertype (0x8870)")
+
+ if data is None:
+ data = b"\00" * expectlen
+ else:
+ data = (data + b"\00" * expectlen)[:expectlen]
+
+ ksft_eq(binascii.b2a_hex(pkt[14:17]).decode("ASCII").upper(), "FEFE03")
+ ksft_eq(pkt[17:], data)
+
+
+def test_llc_tx(sock0_llc: socket, sock1_pkt: socket) -> None:
+ llc_sendto(
+ sock0_llc,
+ sockaddr_llc.make(sllc_sap=0xFE, sllc_mac="02:11:11:11:11:11"),
+ b"\x00" * 1400,
+ )
+ pkt_check(sock1_pkt, 1400)
+
+ llc_sendto(
+ sock0_llc,
+ sockaddr_llc.make(sllc_sap=0xFE, sllc_mac="02:11:11:11:11:11"),
+ b"\x00" * 1497,
+ )
+ pkt_check(sock1_pkt, 1497)
+
+
+def test_llc_tx_jumbo(sock0_llc: socket, sock1_pkt: socket) -> None:
+ llc_sendto(
+ sock0_llc,
+ sockaddr_llc.make(sllc_sap=0xFE, sllc_mac="02:11:11:11:11:11"),
+ b"\x00" * 1498,
+ )
+ pkt_check(sock1_pkt, 1498)
+
+ llc_sendto(
+ sock0_llc,
+ sockaddr_llc.make(sllc_sap=0xFE, sllc_mac="02:11:11:11:11:11"),
+ b"\x00" * 4000,
+ )
+ pkt_check(sock1_pkt, 4000)
+
+
+machdr = binascii.a2b_hex("020000000000" + "021111111111")
+
+
+def test_llc_rx(sock0_llc: socket, sock1_pkt: socket) -> None:
+ sock1_pkt.send(
+ machdr + struct.pack(">H", 1403) + binascii.a2b_hex("FEFE03") + b"\x00" * 1400
+ )
+ rxdata = sock0_llc.recv(4096)
+ ksft_eq(len(rxdata), 1400)
+ ksft_eq(rxdata, b"\x00" * 1400)
+
+ sock1_pkt.send(
+ machdr + struct.pack(">H", 1500) + binascii.a2b_hex("FEFE03") + b"\x00" * 1497
+ )
+ rxdata = sock0_llc.recv(4096)
+ ksft_eq(len(rxdata), 1497)
+ ksft_eq(rxdata, b"\x00" * 1497)
+
+
+def test_llc_rx_jumbo(sock0_llc: socket, sock1_pkt: socket) -> None:
+ sock1_pkt.send(
+ machdr
+ + struct.pack(">H", ETH_P_8021AC)
+ + binascii.a2b_hex("FEFE03")
+ + b"\x00" * 1498
+ )
+ rxdata = sock0_llc.recv(4096)
+ ksft_eq(len(rxdata), 1498)
+ ksft_eq(rxdata, b"\x00" * 1498)
+
+ sock1_pkt.send(
+ machdr
+ + struct.pack(">H", ETH_P_8021AC)
+ + binascii.a2b_hex("FEFE03")
+ + b"\x00" * 4000
+ )
+ rxdata = sock0_llc.recv(4096)
+ ksft_eq(len(rxdata), 4000)
+ ksft_eq(rxdata, b"\x00" * 4000)
+
+
+def test_llc_rx_reject_smuggling(sock0_llc: socket, sock1_pkt: socket) -> None:
+ """
+ make sure smaller packets can't be smuggled in using 0x8870 ethertype
+ """
+ # these tests use a 2nd packet to check first one was dropped, relying on ordering
+
+ sock1_pkt.send(
+ machdr
+ + struct.pack(">H", ETH_P_8021AC)
+ + binascii.a2b_hex("FEFE03")
+ + b"\x00" * 1497
+ )
+ sock1_pkt.send(
+ machdr + struct.pack(">H", 131) + binascii.a2b_hex("FEFE03") + b"\x00" * 128
+ )
+ rxdata = sock0_llc.recv(4096)
+ ksft_eq(len(rxdata), 128)
+ ksft_eq(rxdata, b"\x00" * 128)
+
+ sock1_pkt.send(
+ machdr
+ + struct.pack(">H", ETH_P_8021AC)
+ + binascii.a2b_hex("FEFE03")
+ + b"\x00" * 250
+ )
+ sock1_pkt.send(
+ machdr + struct.pack(">H", 131) + binascii.a2b_hex("FEFE03") + b"\x00" * 128
+ )
+ rxdata = sock0_llc.recv(4096)
+ ksft_eq(len(rxdata), 128)
+ ksft_eq(rxdata, b"\x00" * 128)
+
+
+def main() -> None:
+ ksft_run(
+ [
+ wrap_common_setup(test_llc_tx),
+ wrap_common_setup(test_llc_tx_jumbo),
+ wrap_common_setup(test_llc_rx),
+ wrap_common_setup(test_llc_rx_jumbo),
+ wrap_common_setup(test_llc_rx_reject_smuggling),
+ ]
+ )
+ ksft_exit()
+
+
+def sigalrm(sig, frame):
+ raise TimeoutError("SIGALRM")
+
+
+if __name__ == "__main__":
+ signal.signal(signal.SIGALRM, sigalrm)
+ main()
--
2.53.0
^ permalink raw reply related
* [RFC PATCH net-next 1/2] llc: add 802.1AC (jumbo frames) support
From: David 'equinox' Lamparter @ 2026-07-13 12:43 UTC (permalink / raw)
To: netdev
Cc: David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman, David 'equinox' Lamparter
In-Reply-To: <20260713124545.462790-1-equinox@diac24.net>
Length values in the ethertype field only go up to 1500 (with 1501 to
1535 undefined). 802.1AC (2016 Cor1-2018) defines 0x8870 to use as
ethertype for LLC frames with a length > 1500.
Easy enough to implement. Frames 1500 bytes or shorter are explicitly
rejected when received with ethertype 0x8870 since that could be abused
to smuggle packets past filters. (It could also cause compatibility
issues when mixing in other devices that don't understand that value.)
A kernel self-test is coming up separately.
(background: the IS-IS routing protocol uses LLC/CLNS. FRRouting
currently uses AF_PACKET ETH_P_ALL sockets for that, which is a bit
overkill. Using AF_LLC instead was exploratory, but this fell out as a
side effect. Two AF_PACKET sockets, ETH_P_802_2 + ETH_P_8021AC, is
probably a better option though.)
Signed-off-by: David 'equinox' Lamparter <equinox@diac24.net>
---
net/llc/llc_core.c | 7 +++++++
net/llc/llc_input.c | 11 +++++++++++
net/llc/llc_output.c | 6 ++++--
3 files changed, 22 insertions(+), 2 deletions(-)
diff --git a/net/llc/llc_core.c b/net/llc/llc_core.c
index 5b0f1986bddc..81cd04a8e7e3 100644
--- a/net/llc/llc_core.c
+++ b/net/llc/llc_core.c
@@ -129,14 +129,21 @@ static struct packet_type llc_packet_type __read_mostly = {
.func = llc_rcv,
};
+static struct packet_type llc_packet_type_8021ac __read_mostly = {
+ .type = cpu_to_be16(ETH_P_8021AC),
+ .func = llc_rcv,
+};
+
static int __init llc_init(void)
{
dev_add_pack(&llc_packet_type);
+ dev_add_pack(&llc_packet_type_8021ac);
return 0;
}
static void __exit llc_exit(void)
{
+ dev_remove_pack(&llc_packet_type_8021ac);
dev_remove_pack(&llc_packet_type);
}
diff --git a/net/llc/llc_input.c b/net/llc/llc_input.c
index 8eb3d73c39d1..9409da626b9c 100644
--- a/net/llc/llc_input.c
+++ b/net/llc/llc_input.c
@@ -120,6 +120,11 @@ static inline int llc_fixup_skb(struct sk_buff *skb)
skb_pull(skb, llc_len);
skb_reset_transport_header(skb);
+
+ /* trimming the checksum is not necessary for 802.1AC since the
+ * frames are required to be larger than 1500 bytes, thus have no
+ * ethernet padding
+ */
if (skb->protocol == htons(ETH_P_802_2)) {
__be16 pdulen;
s32 data_size;
@@ -135,6 +140,12 @@ static inline int llc_fixup_skb(struct sk_buff *skb)
return 0;
if (unlikely(pskb_trim_rcsum(skb, data_size)))
return 0;
+ } else if (skb->protocol == htons(ETH_P_8021AC)) {
+ /* don't accept non-jumbo 802.1AC frames, it could be used to
+ * bypass filters on 802.2. Minimum 1497 + 1 byte.
+ */
+ if (!pskb_may_pull(skb, 1497 + 1))
+ return 0;
}
return 1;
}
diff --git a/net/llc/llc_output.c b/net/llc/llc_output.c
index 5a6466fc626a..16efe273188f 100644
--- a/net/llc/llc_output.c
+++ b/net/llc/llc_output.c
@@ -26,12 +26,14 @@ int llc_mac_hdr_init(struct sk_buff *skb,
const unsigned char *sa, const unsigned char *da)
{
int rc = -EINVAL;
+ unsigned short proto;
switch (skb->dev->type) {
case ARPHRD_ETHER:
case ARPHRD_LOOPBACK:
- rc = dev_hard_header(skb, skb->dev, ETH_P_802_2, da, sa,
- skb->len);
+ proto = skb->len > 1500 ? ETH_P_8021AC : ETH_P_802_2;
+ skb->protocol = htons(proto);
+ rc = dev_hard_header(skb, skb->dev, proto, da, sa, skb->len);
if (rc > 0)
rc = 0;
break;
--
2.53.0
^ permalink raw reply related
* [RFC net-next] llc: jumbo frame support
From: David 'equinox' Lamparter @ 2026-07-13 12:43 UTC (permalink / raw)
To: netdev
Cc: David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman
Hi all,
this is a somewhat random stab at 802.1AC / jumbo frame support for LLC.
It kinda fell out as a side effect from poking at FRRouting's IS-IS
socket use. Unfortunately even if this gets picked up right now, it'll
be quite a bit of time before we can even consider using it there. (And
it's not clear that it'll go that way.)
Regardless, it's a tiny change & my messing around with things
effectively got me the selftest too.
The fact that AF_LLC can't be used in a network namespace is a bit of a
problem though (haven't looked at how much work that would be, unlike
the jumbo frame support this didn't fall out "for free"), and so is the
fact that AF_LLC is on limited maintenance...
It's an RFC. Comments appreciated.
Cheers,
equi (David)
(waves from netdevconf in Rome)
^ permalink raw reply
* Re: [PATCH net-next v2 1/3] net: ti: icssm-prueth: Add HSR and PRP HW offload mode support for AM57xx, AM437x and AM335x
From: Parvathi Pudi @ 2026-07-13 12:29 UTC (permalink / raw)
To: Paolo Abeni
Cc: parvathi, andrew+netdev, davem, edumazet, kuba, danishanwar,
rogerq, pmohan, afd, basharath, arnd, linux-kernel, netdev,
linux-arm-kernel, pratheesh, j-rameshbabu, Vignesh Raghavendra,
praneeth, srk, rogerq, m-malladi, krishna, mohan
In-Reply-To: <20260706091314.134721-1-pabeni@redhat.com>
Hi,
> From: AI Reviewer <ai@example.com>
>
> This is an AI-generated review of your patch. The human sending this
> email has considered the AI review valid, or at least plausible.
> Full review at: https://sashiko.dev/
> ---
> Subject: net: ti: icssm-prueth: Add HSR and PRP HW offload mode support for
> AM57xx, AM437x and AM335x
>
> This commit introduces hardware offload support for the HSR and PRP redundancy
> protocols on TI PRU-ICSS Ethernet MACs. It enables firmware-based frame
> duplicate detection and L2 forwarding, treating PRU Ethernet ports as Link
> Redundancy Entity (LRE) slaves. The driver can now dynamically switch between
> dual-EMAC, switch, and HSR/PRP modes.
>
>> diff --git a/drivers/net/ethernet/ti/icssm/icssm_prueth.c
>> b/drivers/net/ethernet/ti/icssm/icssm_prueth.c
> [ ... ]
>> @@ -564,7 +592,7 @@ static int icssm_prueth_tx_enqueue(struct prueth_emac *emac,
> [ ... ]
>> /* update first buffer descriptor */
>> wr_buf_desc = (pktlen << PRUETH_BD_LENGTH_SHIFT) &
>> PRUETH_BD_LENGTH_MASK;
>> + if (PRUETH_IS_HSR(prueth))
>> + wr_buf_desc |= BIT(PRUETH_BD_HSR_FRAME_SHIFT);
>
> [Severity: High]
> Does padding small packets with eth_skb_pad() in icssm_emac_ndo_start_xmit()
> interfere with PRP trailer detection here?
>
> When a short PRP frame is padded to 60 bytes in icssm_emac_ndo_start_xmit(),
> the zero-padding is appended after the RCT. Later, in
> icssm_prueth_tx_enqueue(), the driver attempts to read the RCT like this:
>
> struct prp_txopt_rct *rct =
> (struct prp_txopt_rct *)(skb_tail_pointer(skb) - ICSSM_LRE_TAG_SIZE);
>
> Won't this calculation point into the trailing zero-padding instead of the
> actual RCT? This would cause small PRP frames to bypass redundancy
> duplication.
No, eth_skb_pad() doesn't interfere with PRP trailer detection.
The kernel's HSR/PRP layer always pads a short frame to 60 bytes first, and only
then appends the 6-byte RCT trailer on top. So the trailer is always the last 6
bytes, and by the time this driver sees the frame it's already past 60 bytes.
This padding call never fires and the RCT read is always correct. Not an issue.
>
> [ ... ]
>> @@ -912,6 +963,25 @@ static int icssm_emac_request_irqs(struct prueth_emac
>> *emac)
>> return ret;
>> }
>>
>> +static int icssm_emac_sanitize_feature_flags(struct prueth_emac *emac)
>> +{
>> + netdev_features_t request_lre;
>> +
>> + request_lre = emac->ndev->features & NETIF_PRUETH_LRE_OFFLOAD_FEATURES;
>> +
>> + if (prueth_is_lre(emac->prueth) && !request_lre) {
>> + netdev_err(emac->ndev, "Error: Turn ON HSR offload\n");
>> + return -EINVAL;
>> + }
>
> [Severity: Medium]
> Is this check sufficient to handle partial feature toggling?
>
> If a user disables NETIF_F_HW_HSR_TAG_RM via ethtool while leaving
> NETIF_F_HW_HSR_FWD active, request_lre will still evaluate to non-zero and
> bypass this validation. Since the firmware unconditionally strips the tag,
> could this mismatched configuration cause the software stack to misinterpret
> the inner IP header as an HSR tag?
We will address this in the next version.
>
> [ ... ]
>> @@ -1079,6 +1162,9 @@ static int icssm_emac_ndo_stop(struct net_device *ndev)
>> else
>> rproc_shutdown(emac->pru);
>>
>> + if (prueth_is_lre(prueth))
>> + icssm_prueth_lre_cleanup(prueth);
>
> [Severity: High]
> Could this unconditionally cancel the global HSR/PRP duplicate table check
> timer while the other LRE port is still running?
>
> If eth0 is brought down, the timer is canceled here, but if eth1 remains
> active, the duplication tables could silently fill up and drop legitimate
> frames because the check timer is never restarted.
We will address this in the next version.
>
> [ ... ]
>> @@ -1122,7 +1208,8 @@ static int icssm_prueth_change_mode(struct prueth *prueth,
>> }
>> }
>>
>> - if (mode == PRUSS_ETHTYPE_EMAC || mode == PRUSS_ETHTYPE_SWITCH) {
>> + if (mode == PRUSS_ETHTYPE_EMAC || mode == PRUSS_ETHTYPE_SWITCH ||
>> + mode == PRUSS_ETHTYPE_HSR || mode == PRUSS_ETHTYPE_PRP) {
>
> [Severity: High]
> This is a pre-existing issue, but can the direct invocation of ndo_open() and
> ndo_stop() inside icssm_prueth_change_mode() cause a deadlock?
>
> If ndev->netdev_ops->ndo_open(ndev) fails and returns an error, the function
> aborts without restoring the NAPI state. Later, when the interface is brought
> down, the kernel naturally invokes ndo_stop() again. A second napi_disable()
> call on an already disabled NAPI might trigger an infinite sleep waiting for
> NAPI_STATE_SCHED.
Yes, this is causing a deadlock. icssm_prueth_change_mode() already existed before
this series and this patch series only adds two more mode values to an existing check,
it doesn't touch the open/stop logic itself. We will fix it as its own separate
patch later, instead of mixing it into this HSR/PRP series.
>
> [ ... ]
>> @@ -1360,6 +1465,8 @@ static void icssm_emac_ndo_set_rx_mode(struct net_device
>> *ndev)
> [ ... ]
>> /* It is a shared table. So lock the access */
>> - spin_lock_irqsave(&emac->addr_lock, flags);
>> + spin_lock_irqsave(mc_filter_tbl_lock, flags);
>>
>> /* Disable and reset multicast filter, allows allmulti */
>> icssm_emac_mc_filter_ctrl(emac, false);
>
> [Severity: High]
> Does this function corrupt the shared multicast hardware filter table in LRE
> mode?
>
> By calling icssm_emac_mc_filter_reset() the shared table in DRAM1 is cleared.
> The code then repopulates the table by iterating only over the current
> net_device's multicast addresses. Could this silently drop multicast traffic
> for the other active slave port since its addresses are wiped out?
No, this is safe in LRE mode. Although icssm_emac_mc_filter_reset() clears the
shared multicast filter table in DRAM1, it is rebuilt right away from the current
net_device's multicast list.
The HSR/PRP layer keeps the multicast lists of both slave ports in sync by copying
the master's list whenever it changes, so both ports always have the same multicast
addresses. That means rebuilding the shared table from either port's list restores
all the required entries and does not drop multicast addresses needed by the other port.
>
> [ ... ]
>> +static int icssm_prueth_hsr_port_link(struct net_device *ndev,
>> + struct net_device *hsr_ndev)
>> +{
> [ ... ]
>> + spin_lock_irqsave(&emac->addr_lock, flags);
>> +
>> + if (!prueth->hsr_members) {
>
> [Severity: Low]
> Is the per-port emac->addr_lock the correct lock to use when modifying the
> device-wide shared state in prueth->hsr_members and prueth->hsr_dev?
>
> While the networking core's global rtnl_lock might serialize this path during
> NETDEV_CHANGEUPPER, using a local instance lock to protect global state could
> be a maintainability hazard.
We will address this in the next version.
>
> [ ... ]
>> +free_hsr:
>> + spin_lock_irqsave(&emac->addr_lock, flags);
>> +
>> + prueth->hsr_dev = NULL;
>> + prueth->hsr_members &= ~BIT(emac->port_id);
>> +
>> + spin_unlock_irqrestore(&emac->addr_lock, flags);
>> + return ret;
>> +}
>
> [Severity: High]
> If adding the second HSR/PRP port fails (e.g., during
> icssm_prueth_change_mode), does this error path corrupt the bridge state?
>
> Unconditionally clearing the shared prueth->hsr_dev pointer while the first
> port is still present in prueth->hsr_members appears to permanently break
> subsequent link attempts for the second port, because future links will fail
> on the earlier condition:
>
> if (prueth->hsr_dev != hsr_ndev)
> --
We will address this in the next version.
Thanks and Regards,
Parvathi.
^ permalink raw reply
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox