* [PATCH net-next v3 00/12] net: airoha: Support multiple net_devices connected to the same GDM port
From: Lorenzo Bianconi @ 2026-04-06 10:34 UTC (permalink / raw)
To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Lorenzo Bianconi
Cc: Christian Marangi, Benjamin Larsson, linux-arm-kernel,
linux-mediatek, netdev, devicetree, Xuegang Lu
EN7581 or AN7583 SoCs support connecting multiple external SerDes (e.g.
Ethernet or USB SerDes) to GDM3 or GDM4 ports via a hw arbiter that
manages the traffic in a TDM manner. As a result multiple net_devices can
connect to the same GDM{3,4} port and there is a theoretical "1:n"
relation between GDM ports and net_devices.
┌─────────────────────────────────┐
│ │ ┌──────┐
│ P1 GDM1 ├────►MT7530│
│ │ └──────┘
│ │ ETH0 (DSA conduit)
│ │
│ PSE/FE │
│ │
│ │
│ │ ┌─────┐
│ P0 CDM1 ├────►QDMA0│
│ P4 P9 GDM4 │ └─────┘
└──┬─────────────────────────┬────┘
│ │
┌──▼──┐ ┌────▼────┐
│ PPE │ │ ARB │
└─────┘ └─┬─────┬─┘
│ │
┌──▼──┐┌─▼───┐
│ ETH ││ USB │
└─────┘└─────┘
ETH1 ETH2
This series introduces support for multiple net_devices connected to the
same Frame Engine (FE) GDM port (GDM3 or GDM4) via an external hw
arbiter. Please note GDM1 or GDM2 does not support the connection with
the external arbiter.
---
Changes in v3:
- Fix MTU and VIP configuration when the GDM port is shared between
multiple net_devices.
- Add sanity check for nbq parameter.
- Add missing of_node_get() for net_device np node.
- Check if GDM port is shared before decresing device MTU.
- Move port forward configuration in airoha_dev_stop() before
configuring DMA tx/rx engine.
- Introduce PRIV_FLAG_WAN parameter.
- Link to v2: https://lore.kernel.org/r/20260401-airoha-eth-multi-serdes-v2-0-ac427ae4beeb@kernel.org
Changes in v2:
- Rename multiplexer in arbiter in the commit logs.
- Rebase on top of net-next main branch.
- Add missing PPE cpu port configuration for GDM2 when loopback is
enabled.
- Link to v1: https://lore.kernel.org/r/20260329-airoha-eth-multi-serdes-v1-0-00f52dc360ca@kernel.org
---
Lorenzo Bianconi (12):
dt-bindings: net: airoha: Add EN7581 ethernet-ports properties
net: airoha: Set PPE cpu port for GDM2 if loopback is enabled
net: airoha: Rely on net_device pointer in airoha_dev_setup_tc_block signature
net: airoha: Rely on net_device pointer in HTB callbacks
net: airoha: Rely on net_device pointer in ETS callbacks
net: airoha: Introduce airoha_gdm_dev struct
net: airoha: Move airoha_qdma pointer in airoha_gdm_dev struct
net: airoha: Rely on airoha_gdm_dev pointer in airhoa_is_lan_gdm_port()
net: airoha: Support multiple net_devices for a single FE GDM port
net: airoha: Do not stop GDM port if it is shared
net: airoha: Introduce WAN device flag
net: airoha: Rename get_src_port_id callback in get_sport
.../devicetree/bindings/net/airoha,en7581-eth.yaml | 44 +-
drivers/net/ethernet/airoha/airoha_eth.c | 725 ++++++++++++++-------
drivers/net/ethernet/airoha/airoha_eth.h | 39 +-
drivers/net/ethernet/airoha/airoha_ppe.c | 45 +-
4 files changed, 585 insertions(+), 268 deletions(-)
---
base-commit: 3741f8fa004bf598cd5032b0ff240984332d6f05
change-id: 20260324-airoha-eth-multi-serdes-fb4b556ee756
Best regards,
--
Lorenzo Bianconi <lorenzo@kernel.org>
^ permalink raw reply
* Re: [PATCH net 1/1] net: l3mdev: Ignore non-L3 uppers in l3mdev_fib_table_rcu
From: Ido Schimmel @ 2026-04-06 10:33 UTC (permalink / raw)
To: Ao Zhou
Cc: netdev, David Ahern, David S . Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, Yifan Wu, Juefei Pu,
Yuan Tan, Xin Liu, Haoze Xie
In-Reply-To: <b3b88cddc7e79d4b43756b26ae5db965678f3ba9.1775062214.git.royenheart@gmail.com>
On Sat, Apr 04, 2026 at 07:52:03PM +0800, Ao Zhou wrote:
> From: Haoze Xie <royenheart@gmail.com>
>
> l3mdev_fib_table_rcu() assumes that any upper device observed for
> an IFF_L3MDEV_SLAVE device is an L3 master and dereferences
> master->l3mdev_ops unconditionally.
>
> VRF slave setup sets IFF_L3MDEV_SLAVE before the upper link is fully
> switched, so readers can transiently observe a non-L3 upper such as a
> bridge and follow a NULL l3mdev_ops pointer. Require the current upper
> to still be an L3 master before consulting its FIB table.
Do you have a reproducer? I don't see how that can happen.
do_set_master() ensures that the device doesn't have a master when
ndo_add_slave() is called. Meaning, if netif_is_l3_slave() is true and
netdev_master_upper_dev_get_rcu() resolved a master device, it's
guaranteed to be a VRF.
>
> Fixes: fee6d4c777a1 ("net: Add netif_is_l3_slave")
> Reported-by: Yifan Wu <yifanwucs@gmail.com>
> Reported-by: Juefei Pu <tomapufckgml@gmail.com>
> Co-developed-by: Yuan Tan <yuantan098@gmail.com>
> Signed-off-by: Yuan Tan <yuantan098@gmail.com>
> Suggested-by: Xin Liu <bird@lzu.edu.cn>
> Signed-off-by: Haoze Xie <royenheart@gmail.com>
> Signed-off-by: Ao Zhou <n05ec@lzu.edu.cn>
> ---
> net/l3mdev/l3mdev.c | 2 +-
> 1 file changed, 1 insertion(+), 1 deletion(-)
>
> diff --git a/net/l3mdev/l3mdev.c b/net/l3mdev/l3mdev.c
> index 5432a5f2dfc8..b8a3030cb2c4 100644
> --- a/net/l3mdev/l3mdev.c
> +++ b/net/l3mdev/l3mdev.c
> @@ -177,7 +177,7 @@ u32 l3mdev_fib_table_rcu(const struct net_device *dev)
> const struct net_device *master;
>
> master = netdev_master_upper_dev_get_rcu(_dev);
> - if (master &&
> + if (master && netif_is_l3_master(master) &&
> master->l3mdev_ops->l3mdev_fib_table)
> tb_id = master->l3mdev_ops->l3mdev_fib_table(master);
> }
> --
> 2.53.0
>
^ permalink raw reply
* Re: [PATCH net-next v2 1/3] net: pse-pd: prevent regulator cleanup from disabling unclaimed PSE PIs
From: Oleksij Rempel @ 2026-04-06 10:22 UTC (permalink / raw)
To: Carlo Szelinsky
Cc: Kory Maincent, Andrew Lunn, Heiner Kallweit, Russell King,
Jakub Kicinski, David S . Miller, Eric Dumazet, Paolo Abeni,
Simon Horman, netdev, linux-kernel
In-Reply-To: <20260330132952.2950531-2-github@szelinsky.de>
On Mon, Mar 30, 2026 at 03:29:50PM +0200, Carlo Szelinsky wrote:
> When a PSE controller driver is loaded as a module, its PI regulators
> are registered before any consumer (PHY) acquires the corresponding PSE
> control via of_pse_control_get(). The regulator framework's
> regulator_late_cleanup then calls pse_pi_is_enabled(), which queries
> hardware and sees the PI is enabled. Since no consumer holds it
> (use_count == 0), regulator_late_cleanup disables it, killing PoE.
Let's address the deferred probing issues first. The system shuts the
current for the ports, because ports are not tracked. And ports are not
tracked/claimed, because the first patch breaks it. Which introduces
additional safety, diagnostic and power budget allocation issues.
--
Pengutronix e.K. | |
Steuerwalder Str. 21 | http://www.pengutronix.de/ |
31137 Hildesheim, Germany | Phone: +49-5121-206917-0 |
Amtsgericht Hildesheim, HRA 2686 | Fax: +49-5121-206917-5555 |
^ permalink raw reply
* Re: [PATCH v2] stmmac: cleanup dead dependencies on STMMAC_PLATFORM and STMMAC_ETH in Kconfig
From: Geert Uytterhoeven @ 2026-04-06 9:40 UTC (permalink / raw)
To: Julian Braha
Cc: davem, peppe.cavallaro, alexandre.torgue, mcoquelin.stm32, linux,
kuba, netdev, linux-arm-kernel, linux-kernel,
Russell King (Oracle)
In-Reply-To: <f914fb50-ffca-49dd-901d-0b0176e5ee93@gmail.com>
Hi Julian,
On Mon, 6 Apr 2026 at 11:27, Julian Braha <julianbraha@gmail.com> wrote:
> With the patch applied, I've attempted to configure the kernel
> with STMMAC_PLATFORM=y while STMMAC_ETH=m but menuconfig gives this
> error message:
> "This feature depends on another which has been configured as a module.
> As a result, this feature will be built as a module."
>
> So it looks to me like this patch hasn't actually changed any kconfig
> behavior, but let me know if I'm missing something.
OK, thanks for checking!
I thought there was still some difference between "if" and "depends on"
when the symbol is a module, but apparently I was wrong...
Gr{oetje,eeting}s,
Geert
--
Geert Uytterhoeven -- There's lots of Linux beyond ia32 -- geert@linux-m68k.org
In personal conversations with technical people, I call myself a hacker. But
when I'm talking to journalists I just say "programmer" or something like that.
-- Linus Torvalds
^ permalink raw reply
* Re: [PATCH net-next v2 3/3] net: mdio: treat PSE EPROBE_DEFER as non-fatal during PHY registration
From: Oleksij Rempel @ 2026-04-06 9:30 UTC (permalink / raw)
To: Carlo Szelinsky
Cc: Andrew Lunn, Kory Maincent, Andrew Lunn, Heiner Kallweit,
Russell King, Jakub Kicinski, David S . Miller, Eric Dumazet,
Paolo Abeni, Simon Horman, netdev, linux-kernel
In-Reply-To: <20260405185730.3937952-1-github@szelinsky.de>
On Sun, Apr 05, 2026 at 08:57:30PM +0200, Carlo Szelinsky wrote:
> Hi Andrew,
>
> So I went and looked at whether we can just let EPROBE_DEFER do its
> thing here, like you suggested.
>
> From what I can tell, the issue is where it happens.
> fwnode_mdiobus_register_phy() gets called during the MDIO bus scan in
> __of_mdiobus_parse_phys(), and if any PHY returns -EPROBE_DEFER there,
> the whole scan bails out - none of the PHYs on that bus get registered.
> So you'd lose all networking on that bus just because one PHY's PSE
> controller isn't ready yet.
>
> I also dug into the timing question you raised. Correct me if I'm
> wrong, but from what I see the deferred probe timeout is 10s and
> regulator_late_cleanup fires at 30s, so the ordering would actually
> work out - the consumer would get to claim the regulator before
> cleanup kills it. It's more the bus level collateral damage that
> seemed like the real problem to me.
>
> That's basically why I ended up treating EPROBE_DEFER as non-fatal
> for PSE during PHY registration and doing lazy resolution instead.
> The admin_state_synced flag then covers the window between PSE
> controller probe and whenever the lazy resolution actually happens.
>
> But I might be looking at this the wrong way - would you rather we
> defer the whole bus and accept that trade-off? Or does the lazy
> approach seem reasonable for this case? Happy to hear if you have
> a different idea entirely.
The core question, do we need PSE for PHY functionality? And what it
will cost to decouple it?
We can make a step back and re-evaluate - what functionality and what
order is actually required to find potentially better implementation.
We have a lot of current flowing over wires, budget and port
prioritization issues, things which may damage HW if done not correctly.
With other word, if we do not have properly operational environment
providing system specific policies, it is better to run safe
configuration - all ports/regulators are off.
This means:
- PSE controller driver should be registered as early as possible,
without caring about existence of PHYs, ports or network interfaces.
And configure ports in to default safe operation - off. Accept we
have some controller/firmware which would care about safety.
- as soon as we have all needed components, we can start provide
controllable interfaces to serve external consumers.
If we decouple PSE and PHY registration (and we probably will need to do
it some day), we would need to have own implementation of deferred
probing in the PSE core. Event driven or by polling - which sounds not
like very good idea. Pick your poison...
Probably there is no quick and easy fix for now.
Best Regards,
Oleksij
--
Pengutronix e.K. | |
Steuerwalder Str. 21 | http://www.pengutronix.de/ |
31137 Hildesheim, Germany | Phone: +49-5121-206917-0 |
Amtsgericht Hildesheim, HRA 2686 | Fax: +49-5121-206917-5555 |
^ permalink raw reply
* Re: [PATCH v2] stmmac: cleanup dead dependencies on STMMAC_PLATFORM and STMMAC_ETH in Kconfig
From: Julian Braha @ 2026-04-06 9:27 UTC (permalink / raw)
To: Geert Uytterhoeven
Cc: davem, peppe.cavallaro, alexandre.torgue, mcoquelin.stm32, linux,
kuba, netdev, linux-arm-kernel, linux-kernel,
Russell King (Oracle)
In-Reply-To: <CAMuHMdUfzVSQpadJYpEqPJ_UOBAgswnGzD_bp_U3U6jt2dy0dg@mail.gmail.com>
Hi Geert,
With the patch applied, I've attempted to configure the kernel
with STMMAC_PLATFORM=y while STMMAC_ETH=m but menuconfig gives this
error message:
"This feature depends on another which has been configured as a module.
As a result, this feature will be built as a module."
So it looks to me like this patch hasn't actually changed any kconfig
behavior, but let me know if I'm missing something.
Best regards,
Julian Braha
^ permalink raw reply
* Re: [net-next,PATCH v5 3/3] net: phy: realtek: Add property to enable SSC
From: Aleksander Jan Bajkowski @ 2026-04-06 9:06 UTC (permalink / raw)
To: Marek Vasut, netdev
Cc: David S. Miller, Andrew Lunn, Conor Dooley, Eric Dumazet,
Florian Fainelli, Heiner Kallweit, Ivan Galkin, Jakub Kicinski,
Krzysztof Kozlowski, Michael Klein, Paolo Abeni, Rob Herring,
Russell King, Vladimir Oltean, devicetree
In-Reply-To: <22564dd5-c2b5-46ec-b083-6239a3ff5a2f@mailbox.org>
Hi Marek,
On 05/04/2026 23:59, Marek Vasut wrote:
> On 4/5/26 10:23 PM, Aleksander Jan Bajkowski wrote:
>
> Hi,
>
>>> +static int rtl8211f_config_clkout_ssc(struct phy_device *phydev)
>>> +{
>>> + struct rtl821x_priv *priv = phydev->priv;
>>> + struct device *dev = &phydev->mdio.dev;
>>> + int ret;
>>> +
>>> + /* The value is preserved if the device tree property is absent */
>>> + if (!priv->enable_clkout_ssc)
>>> + return 0;
>>> +
>>> + /* RTL8211FVD has PHYCR2 register, but configuration of CLKOUT SSC
>>> + * is not currently supported by this driver due to different bit
>>> + * layout.
>>> + */
>>> + if (phydev->drv->phy_id == RTL_8211FVD_PHYID)
>>> + return 0;
>>> +
>>> + /* Unnamed registers from EMI improvement parameters
>>> application note 1.2 */
>>> + ret = phy_write_paged(phydev, 0xd09, 0x10, 0xcf00);
>>> + if (ret < 0) {
>>> + dev_err(dev, "CLKOUT SSC initialization failed: %pe\n",
>>> ERR_PTR(ret));
>>> + return ret;
>>> + }
>>> +
>>> + ret = phy_write(phydev, RTL8211F_SSC_CLKOUT, 0x38c3);
>>
>> Only registers 0x10–0x17 require paged operations. The remaining
>> registers
>> are mapped directly into the PHY address space. This is mentioned in
>> commit
>> 650e55f224a575cdb18c984b95036109519502d1. Paged and direct access return
>> the same results. With this in mind, I believe that
>> RTL8211F_SSC_CLKOUT is
>> an alias for RTL8211F_PHYCR2 and is described on page 45 of the
>> datasheet[1].
>>
>> 1. RTL8211F(I)-CG/RTL8211FD(I)-CG Datasheet
> This is a good point indeed, I think I can simply set PHYCR2 bits
> 7,12,13 to enable the CLKOUT SSC ?
Sounds correct.
Regards,
Aleksander
^ permalink raw reply
* Re: [PATCH net-next v2 3/3] net: mdio: treat PSE EPROBE_DEFER as non-fatal during PHY registration
From: Oleksij Rempel @ 2026-04-06 8:42 UTC (permalink / raw)
To: Kory Maincent
Cc: Carlo Szelinsky, Andrew Lunn, Andrew Lunn, Heiner Kallweit,
Russell King, Jakub Kicinski, David S . Miller, Eric Dumazet,
Paolo Abeni, Simon Horman, netdev, linux-kernel
In-Reply-To: <20260403153849.2ed7d342@kmaincent-XPS-13-7390>
Hi,
On Fri, Apr 03, 2026 at 03:38:49PM +0200, Kory Maincent wrote:
> On Fri, 3 Apr 2026 15:31:11 +0200
> Carlo Szelinsky <github@szelinsky.de> wrote:
>
> > Hi Andrew,
> >
> > Thx for helping me!
> > I thought of keeping the eager path so we still catch broken DT bindings
> > early at boot instead of silently failing later on first ethtool access.
> > But you're right, dropping it would simplify things quite a bit. Do you
> > think that trade-off is worth it? I will follow your lead.
>
> On my side I thinks that's a good idea, and I don't see any issue with that for
> now. Oleksij you introduced it here in the first place, is it ok for you?
If I see it correctly - this patch kills all notifications originated
from the PSE core to the users space, until some one calls get/set path
from user space. Means, kernel update may break UAPI behavior of
existing devices.
On other hand, I agree that PSE is not a strickt requirement for PHY
functionality in most cases. At the early stage, as initial PSE support
was introduced, PHYs was kind of representation of the port and related
ethernet interface (needed for LLDP). Are there better methods to solve
it now?
Best Regards,
Oleksij
--
Pengutronix e.K. | |
Steuerwalder Str. 21 | http://www.pengutronix.de/ |
31137 Hildesheim, Germany | Phone: +49-5121-206917-0 |
Amtsgericht Hildesheim, HRA 2686 | Fax: +49-5121-206917-5555 |
^ permalink raw reply
* Re: [PATCH net-next v8 00/11] net: tunnel: introduce noref xmit flows for tunnels
From: Marek Mietus @ 2026-04-06 7:58 UTC (permalink / raw)
To: Paolo Abeni, netdev, sd, kuba; +Cc: Jason
In-Reply-To: <726e1d40-6a29-4e6f-964c-1a4d0cf7d4eb@redhat.com>
W dniu 17.03.2026 o 12:37, Paolo Abeni pisze:
> On 3/12/26 4:56 PM, Marek Mietus wrote:
>> Currently, tunnel xmit flows always take a reference on the dst_entry
>> for each xmitted packet. These atomic operations are redundant in some
>> flows.
>>
>> This patchset introduces the infrastructure required for converting
>> the tunnel xmit flows to noref, and converts them where possible.
>>
>> These changes improve tunnel performance, since less atomic operations
>> are used.
>>
>> There are already noref optimizations in both ipv4 and ip6.
>> (See __ip_queue_xmit, inet6_csk_xmit)
>> This patchset implements similar optimizations in ip and udp tunnels.
>>
>> Benchmarks:
>> I used a vxlan tunnel over a pair of veth peers and measured the average
>> throughput over multiple samples.
>>
>> I ran 100 samples on a clean build, and another 100 on a patched
>> build. Each sample ran for 120 seconds. These were my results:
>>
>> clean: 72.52 gb/sec, stddev = 1.39
>> patched: 75.39 gb/sec, stddev = 0.94
>>
>> TL;DR - This patchset results in a 4% improvement in throughput for
>> vxlan. It's safe to assume that we might see similar results when testing
>> other tunnels.
>
> Sabrina noted I wrongly replied on an old revision. Reporting my
> statements here for completeness.
>
> IMHO this performance delta is not enough to justify this amount of changes.
>
> Additionally, the measured impact of removing the dst_hold/dst_release
> does not fit with my direct experience on the same matter: it should be
> below noise level in practice, as dst are per-cpu and and no
> contention/false sharing is expected in a good setup.
>
> I think you are observing larger impact because in the veth test
> dst_release can happen on a remote CPU. Note that this setup (vxlan over
> veth) is not very relevant in practice.
>
> I'm sorry I'm not applying this series.
>
> Side note: if you are interested into improving (UDP) tunnel
> performances have a look to big TCP support work from Alice Mikityanska:
>
> https://lore.kernel.org/netdev/20260226201600.222044-1-alice.kernel@fastmail.im/
>
> /P
>
>
Hi Paolo,
Could you clarify what would be a more realistic test setup? I'm assuming using physical interfaces would yield more realistic results.
Also, what performance benefit would be worth this kind of "code churn"? What kind of margins were you looking for?
I got mixed signals from you and Sabrina, so these clarifications would go a long way.
P.S - I'll take a closer look at Alice Mikityanska's "big TCP" works.
^ permalink raw reply
* [PATCH net-next v6 7/7] net: bcmgenet: reject MTU changes incompatible with XDP
From: Nicolai Buchwitz @ 2026-04-06 8:35 UTC (permalink / raw)
To: netdev
Cc: Justin Chen, Simon Horman, Mohsin Bashir, Doug Berger,
Florian Fainelli, Broadcom internal kernel review list,
Andrew Lunn, Eric Dumazet, Paolo Abeni, Nicolai Buchwitz,
David S. Miller, Jakub Kicinski, Alexei Starovoitov,
Daniel Borkmann, Jesper Dangaard Brouer, John Fastabend,
Stanislav Fomichev, linux-kernel, bpf
In-Reply-To: <20260406083536.839517-1-nb@tipi-net.de>
Add a minimal ndo_change_mtu that rejects MTU values too large for
single-page XDP buffers when an XDP program is attached. Without this,
users could change the MTU at runtime and break the XDP buffer layout.
When no XDP program is attached, any MTU change is accepted, matching
the existing behavior without ndo_change_mtu.
Signed-off-by: Nicolai Buchwitz <nb@tipi-net.de>
---
drivers/net/ethernet/broadcom/genet/bcmgenet.c | 15 +++++++++++++++
1 file changed, 15 insertions(+)
diff --git a/drivers/net/ethernet/broadcom/genet/bcmgenet.c b/drivers/net/ethernet/broadcom/genet/bcmgenet.c
index 7d0c9a848331..46998703560b 100644
--- a/drivers/net/ethernet/broadcom/genet/bcmgenet.c
+++ b/drivers/net/ethernet/broadcom/genet/bcmgenet.c
@@ -4092,6 +4092,20 @@ static int bcmgenet_xdp_xmit(struct net_device *dev, int num_frames,
return sent;
}
+static int bcmgenet_change_mtu(struct net_device *dev, int new_mtu)
+{
+ struct bcmgenet_priv *priv = netdev_priv(dev);
+
+ if (priv->xdp_prog && new_mtu > PAGE_SIZE - GENET_RX_HEADROOM -
+ SKB_DATA_ALIGN(sizeof(struct skb_shared_info))) {
+ netdev_warn(dev, "MTU too large for single-page XDP buffer\n");
+ return -EINVAL;
+ }
+
+ WRITE_ONCE(dev->mtu, new_mtu);
+ return 0;
+}
+
static const struct net_device_ops bcmgenet_netdev_ops = {
.ndo_open = bcmgenet_open,
.ndo_stop = bcmgenet_close,
@@ -4102,6 +4116,7 @@ static const struct net_device_ops bcmgenet_netdev_ops = {
.ndo_eth_ioctl = phy_do_ioctl_running,
.ndo_set_features = bcmgenet_set_features,
.ndo_get_stats64 = bcmgenet_get_stats64,
+ .ndo_change_mtu = bcmgenet_change_mtu,
.ndo_change_carrier = bcmgenet_change_carrier,
.ndo_bpf = bcmgenet_xdp,
.ndo_xdp_xmit = bcmgenet_xdp_xmit,
--
2.51.0
^ permalink raw reply related
* [PATCH net-next v6 6/7] net: bcmgenet: add XDP statistics counters
From: Nicolai Buchwitz @ 2026-04-06 8:35 UTC (permalink / raw)
To: netdev
Cc: Justin Chen, Simon Horman, Mohsin Bashir, Doug Berger,
Florian Fainelli, Broadcom internal kernel review list,
Andrew Lunn, Eric Dumazet, Paolo Abeni, Nicolai Buchwitz,
David S. Miller, Jakub Kicinski, Alexei Starovoitov,
Daniel Borkmann, Jesper Dangaard Brouer, John Fastabend,
Stanislav Fomichev, linux-kernel, bpf
In-Reply-To: <20260406083536.839517-1-nb@tipi-net.de>
Expose per-action XDP counters via ethtool -S: xdp_pass, xdp_drop,
xdp_tx, xdp_tx_err, xdp_redirect, and xdp_redirect_err.
These use the existing soft MIB infrastructure and are incremented in
bcmgenet_run_xdp() alongside the existing driver statistics.
Signed-off-by: Nicolai Buchwitz <nb@tipi-net.de>
---
drivers/net/ethernet/broadcom/genet/bcmgenet.c | 15 +++++++++++++++
drivers/net/ethernet/broadcom/genet/bcmgenet.h | 6 ++++++
2 files changed, 21 insertions(+)
diff --git a/drivers/net/ethernet/broadcom/genet/bcmgenet.c b/drivers/net/ethernet/broadcom/genet/bcmgenet.c
index b725ffa9c746..7d0c9a848331 100644
--- a/drivers/net/ethernet/broadcom/genet/bcmgenet.c
+++ b/drivers/net/ethernet/broadcom/genet/bcmgenet.c
@@ -1170,6 +1170,13 @@ static const struct bcmgenet_stats bcmgenet_gstrings_stats[] = {
STAT_GENET_SOFT_MIB("tx_realloc_tsb", mib.tx_realloc_tsb),
STAT_GENET_SOFT_MIB("tx_realloc_tsb_failed",
mib.tx_realloc_tsb_failed),
+ /* XDP counters */
+ STAT_GENET_SOFT_MIB("xdp_pass", mib.xdp_pass),
+ STAT_GENET_SOFT_MIB("xdp_drop", mib.xdp_drop),
+ STAT_GENET_SOFT_MIB("xdp_tx", mib.xdp_tx),
+ STAT_GENET_SOFT_MIB("xdp_tx_err", mib.xdp_tx_err),
+ STAT_GENET_SOFT_MIB("xdp_redirect", mib.xdp_redirect),
+ STAT_GENET_SOFT_MIB("xdp_redirect_err", mib.xdp_redirect_err),
/* Per TX queues */
STAT_GENET_Q(0),
STAT_GENET_Q(1),
@@ -2424,6 +2431,7 @@ static unsigned int bcmgenet_run_xdp(struct bcmgenet_rx_ring *ring,
switch (act) {
case XDP_PASS:
+ priv->mib.xdp_pass++;
return XDP_PASS;
case XDP_TX:
/* Prepend a zeroed TSB (Transmit Status Block). The GENET
@@ -2436,6 +2444,7 @@ static unsigned int bcmgenet_run_xdp(struct bcmgenet_rx_ring *ring,
sizeof(struct status_64) + sizeof(struct xdp_frame)) {
page_pool_put_full_page(ring->page_pool, rx_page,
true);
+ priv->mib.xdp_tx_err++;
return XDP_DROP;
}
xdp->data -= sizeof(struct status_64);
@@ -2455,19 +2464,24 @@ static unsigned int bcmgenet_run_xdp(struct bcmgenet_rx_ring *ring,
xdpf, false))) {
spin_unlock(&tx_ring->lock);
xdp_return_frame_rx_napi(xdpf);
+ priv->mib.xdp_tx_err++;
return XDP_DROP;
}
bcmgenet_xdp_ring_doorbell(priv, tx_ring);
spin_unlock(&tx_ring->lock);
+ priv->mib.xdp_tx++;
return XDP_TX;
case XDP_REDIRECT:
if (unlikely(xdp_do_redirect(priv->dev, xdp, prog))) {
+ priv->mib.xdp_redirect_err++;
page_pool_put_full_page(ring->page_pool, rx_page,
true);
return XDP_DROP;
}
+ priv->mib.xdp_redirect++;
return XDP_REDIRECT;
case XDP_DROP:
+ priv->mib.xdp_drop++;
page_pool_put_full_page(ring->page_pool, rx_page, true);
return XDP_DROP;
default:
@@ -2475,6 +2489,7 @@ static unsigned int bcmgenet_run_xdp(struct bcmgenet_rx_ring *ring,
fallthrough;
case XDP_ABORTED:
trace_xdp_exception(priv->dev, prog, act);
+ priv->mib.xdp_drop++;
page_pool_put_full_page(ring->page_pool, rx_page, true);
return XDP_ABORTED;
}
diff --git a/drivers/net/ethernet/broadcom/genet/bcmgenet.h b/drivers/net/ethernet/broadcom/genet/bcmgenet.h
index 8966d32efe2f..c4e85c185702 100644
--- a/drivers/net/ethernet/broadcom/genet/bcmgenet.h
+++ b/drivers/net/ethernet/broadcom/genet/bcmgenet.h
@@ -156,6 +156,12 @@ struct bcmgenet_mib_counters {
u32 tx_dma_failed;
u32 tx_realloc_tsb;
u32 tx_realloc_tsb_failed;
+ u32 xdp_pass;
+ u32 xdp_drop;
+ u32 xdp_tx;
+ u32 xdp_tx_err;
+ u32 xdp_redirect;
+ u32 xdp_redirect_err;
};
struct bcmgenet_tx_stats64 {
--
2.51.0
^ permalink raw reply related
* [PATCH net-next v6 5/7] net: bcmgenet: add XDP_REDIRECT and ndo_xdp_xmit support
From: Nicolai Buchwitz @ 2026-04-06 8:35 UTC (permalink / raw)
To: netdev
Cc: Justin Chen, Simon Horman, Mohsin Bashir, Doug Berger,
Florian Fainelli, Broadcom internal kernel review list,
Andrew Lunn, Eric Dumazet, Paolo Abeni, Nicolai Buchwitz,
David S. Miller, Jakub Kicinski, Alexei Starovoitov,
Daniel Borkmann, Jesper Dangaard Brouer, John Fastabend,
Stanislav Fomichev, linux-kernel, bpf
In-Reply-To: <20260406083536.839517-1-nb@tipi-net.de>
Add XDP_REDIRECT support and implement ndo_xdp_xmit for receiving
redirected frames from other devices.
XDP_REDIRECT calls xdp_do_redirect() in the RX path with
xdp_do_flush() once per NAPI poll cycle. ndo_xdp_xmit batches frames
into ring 16 under a single spinlock acquisition.
Advertise NETDEV_XDP_ACT_REDIRECT and NETDEV_XDP_ACT_NDO_XMIT in
xdp_features.
Signed-off-by: Nicolai Buchwitz <nb@tipi-net.de>
---
.../net/ethernet/broadcom/genet/bcmgenet.c | 87 ++++++++++++++++---
1 file changed, 73 insertions(+), 14 deletions(-)
diff --git a/drivers/net/ethernet/broadcom/genet/bcmgenet.c b/drivers/net/ethernet/broadcom/genet/bcmgenet.c
index afaa991ff23a..b725ffa9c746 100644
--- a/drivers/net/ethernet/broadcom/genet/bcmgenet.c
+++ b/drivers/net/ethernet/broadcom/genet/bcmgenet.c
@@ -2327,22 +2327,22 @@ static struct sk_buff *bcmgenet_xdp_build_skb(struct bcmgenet_rx_ring *ring,
return skb;
}
+/* Submit a single XDP frame to the TX ring. Caller must hold ring->lock.
+ * Returns true on success. Does not ring the doorbell - caller must
+ * write TDMA_PROD_INDEX after batching.
+ */
static bool bcmgenet_xdp_xmit_frame(struct bcmgenet_priv *priv,
+ struct bcmgenet_tx_ring *ring,
struct xdp_frame *xdpf, bool dma_map)
{
- struct bcmgenet_tx_ring *ring = &priv->xdp_tx_ring;
struct device *kdev = &priv->pdev->dev;
struct enet_cb *tx_cb_ptr;
dma_addr_t mapping;
unsigned int dma_len;
u32 len_stat;
- spin_lock(&ring->lock);
-
- if (ring->free_bds < 1) {
- spin_unlock(&ring->lock);
+ if (ring->free_bds < 1)
return false;
- }
tx_cb_ptr = bcmgenet_get_txcb(priv, ring);
@@ -2356,7 +2356,6 @@ static bool bcmgenet_xdp_xmit_frame(struct bcmgenet_priv *priv,
*/
if (unlikely(xdpf->headroom < sizeof(struct status_64))) {
bcmgenet_put_txcb(priv, ring);
- spin_unlock(&ring->lock);
return false;
}
@@ -2370,7 +2369,6 @@ static bool bcmgenet_xdp_xmit_frame(struct bcmgenet_priv *priv,
tx_cb_ptr->skb = NULL;
tx_cb_ptr->xdpf = NULL;
bcmgenet_put_txcb(priv, ring);
- spin_unlock(&ring->lock);
return false;
}
} else {
@@ -2402,12 +2400,14 @@ static bool bcmgenet_xdp_xmit_frame(struct bcmgenet_priv *priv,
ring->prod_index++;
ring->prod_index &= DMA_P_INDEX_MASK;
+ return true;
+}
+
+static void bcmgenet_xdp_ring_doorbell(struct bcmgenet_priv *priv,
+ struct bcmgenet_tx_ring *ring)
+{
bcmgenet_tdma_ring_writel(priv, ring->index, ring->prod_index,
TDMA_PROD_INDEX);
-
- spin_unlock(&ring->lock);
-
- return true;
}
static unsigned int bcmgenet_run_xdp(struct bcmgenet_rx_ring *ring,
@@ -2416,6 +2416,7 @@ static unsigned int bcmgenet_run_xdp(struct bcmgenet_rx_ring *ring,
struct page *rx_page)
{
struct bcmgenet_priv *priv = ring->priv;
+ struct bcmgenet_tx_ring *tx_ring;
struct xdp_frame *xdpf;
unsigned int act;
@@ -2447,11 +2448,25 @@ static unsigned int bcmgenet_run_xdp(struct bcmgenet_rx_ring *ring,
true);
return XDP_DROP;
}
- if (unlikely(!bcmgenet_xdp_xmit_frame(priv, xdpf, false))) {
+
+ tx_ring = &priv->xdp_tx_ring;
+ spin_lock(&tx_ring->lock);
+ if (unlikely(!bcmgenet_xdp_xmit_frame(priv, tx_ring,
+ xdpf, false))) {
+ spin_unlock(&tx_ring->lock);
xdp_return_frame_rx_napi(xdpf);
return XDP_DROP;
}
+ bcmgenet_xdp_ring_doorbell(priv, tx_ring);
+ spin_unlock(&tx_ring->lock);
return XDP_TX;
+ case XDP_REDIRECT:
+ if (unlikely(xdp_do_redirect(priv->dev, xdp, prog))) {
+ page_pool_put_full_page(ring->page_pool, rx_page,
+ true);
+ return XDP_DROP;
+ }
+ return XDP_REDIRECT;
case XDP_DROP:
page_pool_put_full_page(ring->page_pool, rx_page, true);
return XDP_DROP;
@@ -2475,6 +2490,7 @@ static unsigned int bcmgenet_desc_rx(struct bcmgenet_rx_ring *ring,
struct bcmgenet_priv *priv = ring->priv;
struct net_device *dev = priv->dev;
struct bpf_prog *xdp_prog;
+ bool xdp_flush = false;
struct enet_cb *cb;
struct sk_buff *skb;
u32 dma_length_status;
@@ -2620,6 +2636,8 @@ static unsigned int bcmgenet_desc_rx(struct bcmgenet_rx_ring *ring,
if (xdp_prog) {
xdp_act = bcmgenet_run_xdp(ring, xdp_prog,
&xdp, rx_page);
+ if (xdp_act == XDP_REDIRECT)
+ xdp_flush = true;
if (xdp_act != XDP_PASS)
goto next;
}
@@ -2673,6 +2691,9 @@ static unsigned int bcmgenet_desc_rx(struct bcmgenet_rx_ring *ring,
bcmgenet_rdma_ring_writel(priv, ring->index, ring->c_index, RDMA_CONS_INDEX);
}
+ if (xdp_flush)
+ xdp_do_flush();
+
ring->dim.bytes = bytes_processed;
ring->dim.packets = rxpktprocessed;
@@ -4003,10 +4024,16 @@ static int bcmgenet_xdp_setup(struct net_device *dev,
return -EOPNOTSUPP;
}
+ if (!prog)
+ xdp_features_clear_redirect_target(dev);
+
old_prog = xchg(&priv->xdp_prog, prog);
if (old_prog)
bpf_prog_put(old_prog);
+ if (prog)
+ xdp_features_set_redirect_target(dev, false);
+
return 0;
}
@@ -4020,6 +4047,36 @@ static int bcmgenet_xdp(struct net_device *dev, struct netdev_bpf *xdp)
}
}
+static int bcmgenet_xdp_xmit(struct net_device *dev, int num_frames,
+ struct xdp_frame **frames, u32 flags)
+{
+ struct bcmgenet_priv *priv = netdev_priv(dev);
+ struct bcmgenet_tx_ring *ring = &priv->xdp_tx_ring;
+ int sent = 0;
+ int i;
+
+ if (unlikely(flags & ~XDP_XMIT_FLAGS_MASK))
+ return -EINVAL;
+
+ if (unlikely(!netif_running(dev)))
+ return -ENETDOWN;
+
+ spin_lock(&ring->lock);
+
+ for (i = 0; i < num_frames; i++) {
+ if (!bcmgenet_xdp_xmit_frame(priv, ring, frames[i], true))
+ break;
+ sent++;
+ }
+
+ if (sent)
+ bcmgenet_xdp_ring_doorbell(priv, ring);
+
+ spin_unlock(&ring->lock);
+
+ return sent;
+}
+
static const struct net_device_ops bcmgenet_netdev_ops = {
.ndo_open = bcmgenet_open,
.ndo_stop = bcmgenet_close,
@@ -4032,6 +4089,7 @@ static const struct net_device_ops bcmgenet_netdev_ops = {
.ndo_get_stats64 = bcmgenet_get_stats64,
.ndo_change_carrier = bcmgenet_change_carrier,
.ndo_bpf = bcmgenet_xdp,
+ .ndo_xdp_xmit = bcmgenet_xdp_xmit,
};
/* GENET hardware parameters/characteristics */
@@ -4334,7 +4392,8 @@ static int bcmgenet_probe(struct platform_device *pdev)
NETIF_F_RXCSUM;
dev->hw_features |= dev->features;
dev->vlan_features |= dev->features;
- dev->xdp_features = NETDEV_XDP_ACT_BASIC;
+ dev->xdp_features = NETDEV_XDP_ACT_BASIC | NETDEV_XDP_ACT_REDIRECT |
+ NETDEV_XDP_ACT_NDO_XMIT;
netdev_sw_irq_coalesce_default_on(dev);
--
2.51.0
^ permalink raw reply related
* [PATCH net-next v6 4/7] net: bcmgenet: add XDP_TX support
From: Nicolai Buchwitz @ 2026-04-06 8:35 UTC (permalink / raw)
To: netdev
Cc: Justin Chen, Simon Horman, Mohsin Bashir, Doug Berger,
Florian Fainelli, Broadcom internal kernel review list,
Andrew Lunn, Eric Dumazet, Paolo Abeni, Nicolai Buchwitz,
David S. Miller, Jakub Kicinski, Alexei Starovoitov,
Daniel Borkmann, Jesper Dangaard Brouer, John Fastabend,
Stanislav Fomichev, linux-kernel, bpf
In-Reply-To: <20260406083536.839517-1-nb@tipi-net.de>
Implement XDP_TX using ring 16 (DESC_INDEX), the hardware default
descriptor ring, dedicated to XDP TX for isolation from SKB TX queues.
Ring 16 gets 32 BDs carved from ring 0's allocation. TX completion is
piggybacked on RX NAPI poll since ring 16's INTRL2_1 bit collides with
RX ring 0, similar to how bnxt, ice, and other XDP drivers handle TX
completion within the RX poll path.
The GENET MAC has TBUF_64B_EN set globally, requiring every TX buffer
to start with a 64-byte struct status_64 (TSB). For local XDP_TX, the
TSB is prepended by backing xdp->data into the RSB area (unused after
BPF execution) and zeroing it. For foreign frames redirected from other
devices, the TSB is written into the xdp_frame headroom.
The page_pool DMA direction is changed from DMA_FROM_DEVICE to
DMA_BIDIRECTIONAL to allow TX reuse of the existing DMA mapping.
Signed-off-by: Nicolai Buchwitz <nb@tipi-net.de>
---
.../net/ethernet/broadcom/genet/bcmgenet.c | 224 ++++++++++++++++--
.../net/ethernet/broadcom/genet/bcmgenet.h | 3 +
2 files changed, 205 insertions(+), 22 deletions(-)
diff --git a/drivers/net/ethernet/broadcom/genet/bcmgenet.c b/drivers/net/ethernet/broadcom/genet/bcmgenet.c
index 187fa11ad5c2..afaa991ff23a 100644
--- a/drivers/net/ethernet/broadcom/genet/bcmgenet.c
+++ b/drivers/net/ethernet/broadcom/genet/bcmgenet.c
@@ -48,8 +48,10 @@
#define GENET_Q0_RX_BD_CNT \
(TOTAL_DESC - priv->hw_params->rx_queues * priv->hw_params->rx_bds_per_q)
+#define GENET_Q16_TX_BD_CNT 32
#define GENET_Q0_TX_BD_CNT \
- (TOTAL_DESC - priv->hw_params->tx_queues * priv->hw_params->tx_bds_per_q)
+ (TOTAL_DESC - priv->hw_params->tx_queues * priv->hw_params->tx_bds_per_q \
+ - GENET_Q16_TX_BD_CNT)
#define RX_BUF_LENGTH 2048
#define SKB_ALIGNMENT 32
@@ -1893,6 +1895,14 @@ static struct sk_buff *bcmgenet_free_tx_cb(struct device *dev,
if (cb == GENET_CB(skb)->last_cb)
return skb;
+ } else if (cb->xdpf) {
+ if (cb->xdp_dma_map)
+ dma_unmap_single(dev, dma_unmap_addr(cb, dma_addr),
+ dma_unmap_len(cb, dma_len),
+ DMA_TO_DEVICE);
+ dma_unmap_addr_set(cb, dma_addr, 0);
+ xdp_return_frame(cb->xdpf);
+ cb->xdpf = NULL;
} else if (dma_unmap_addr(cb, dma_addr)) {
dma_unmap_page(dev,
dma_unmap_addr(cb, dma_addr),
@@ -1925,10 +1935,16 @@ static unsigned int __bcmgenet_tx_reclaim(struct net_device *dev,
unsigned int pkts_compl = 0;
unsigned int txbds_ready;
unsigned int c_index;
+ struct enet_cb *tx_cb;
struct sk_buff *skb;
- /* Clear status before servicing to reduce spurious interrupts */
- bcmgenet_intrl2_1_writel(priv, (1 << ring->index), INTRL2_CPU_CLEAR);
+ /* Clear status before servicing to reduce spurious interrupts.
+ * Ring DESC_INDEX (XDP TX) has no interrupt; skip the clear to
+ * avoid clobbering RX ring 0's bit at the same position.
+ */
+ if (ring->index != DESC_INDEX)
+ bcmgenet_intrl2_1_writel(priv, BIT(ring->index),
+ INTRL2_CPU_CLEAR);
/* Compute how many buffers are transmitted since last xmit call */
c_index = bcmgenet_tdma_ring_readl(priv, ring->index, TDMA_CONS_INDEX)
@@ -1941,8 +1957,15 @@ static unsigned int __bcmgenet_tx_reclaim(struct net_device *dev,
/* Reclaim transmitted buffers */
while (txbds_processed < txbds_ready) {
- skb = bcmgenet_free_tx_cb(&priv->pdev->dev,
- &priv->tx_cbs[ring->clean_ptr]);
+ tx_cb = &priv->tx_cbs[ring->clean_ptr];
+ if (tx_cb->xdpf) {
+ pkts_compl++;
+ bytes_compl += tx_cb->xdp_dma_map
+ ? tx_cb->xdpf->len
+ : tx_cb->xdpf->len -
+ sizeof(struct status_64);
+ }
+ skb = bcmgenet_free_tx_cb(&priv->pdev->dev, tx_cb);
if (skb) {
pkts_compl++;
bytes_compl += GENET_CB(skb)->bytes_sent;
@@ -1964,8 +1987,11 @@ static unsigned int __bcmgenet_tx_reclaim(struct net_device *dev,
u64_stats_add(&stats->bytes, bytes_compl);
u64_stats_update_end(&stats->syncp);
- netdev_tx_completed_queue(netdev_get_tx_queue(dev, ring->index),
- pkts_compl, bytes_compl);
+ /* Ring DESC_INDEX (XDP TX) has no netdev TX queue; skip BQL */
+ if (ring->index != DESC_INDEX)
+ netdev_tx_completed_queue(netdev_get_tx_queue(dev,
+ ring->index),
+ pkts_compl, bytes_compl);
return txbds_processed;
}
@@ -2042,6 +2068,9 @@ static void bcmgenet_tx_reclaim_all(struct net_device *dev)
do {
bcmgenet_tx_reclaim(dev, &priv->tx_rings[i++], true);
} while (i <= priv->hw_params->tx_queues && netif_is_multiqueue(dev));
+
+ /* Also reclaim XDP TX ring */
+ bcmgenet_tx_reclaim(dev, &priv->xdp_tx_ring, true);
}
/* Reallocate the SKB to put enough headroom in front of it and insert
@@ -2298,11 +2327,96 @@ static struct sk_buff *bcmgenet_xdp_build_skb(struct bcmgenet_rx_ring *ring,
return skb;
}
+static bool bcmgenet_xdp_xmit_frame(struct bcmgenet_priv *priv,
+ struct xdp_frame *xdpf, bool dma_map)
+{
+ struct bcmgenet_tx_ring *ring = &priv->xdp_tx_ring;
+ struct device *kdev = &priv->pdev->dev;
+ struct enet_cb *tx_cb_ptr;
+ dma_addr_t mapping;
+ unsigned int dma_len;
+ u32 len_stat;
+
+ spin_lock(&ring->lock);
+
+ if (ring->free_bds < 1) {
+ spin_unlock(&ring->lock);
+ return false;
+ }
+
+ tx_cb_ptr = bcmgenet_get_txcb(priv, ring);
+
+ if (dma_map) {
+ void *tsb_start;
+
+ /* The GENET MAC has TBUF_64B_EN set globally, so hardware
+ * expects a 64-byte TSB prefix on every TX buffer. For
+ * redirected frames (ndo_xdp_xmit) we prepend a zeroed TSB
+ * using the frame's headroom.
+ */
+ if (unlikely(xdpf->headroom < sizeof(struct status_64))) {
+ bcmgenet_put_txcb(priv, ring);
+ spin_unlock(&ring->lock);
+ return false;
+ }
+
+ tsb_start = xdpf->data - sizeof(struct status_64);
+ memset(tsb_start, 0, sizeof(struct status_64));
+
+ dma_len = xdpf->len + sizeof(struct status_64);
+ mapping = dma_map_single(kdev, tsb_start, dma_len,
+ DMA_TO_DEVICE);
+ if (dma_mapping_error(kdev, mapping)) {
+ tx_cb_ptr->skb = NULL;
+ tx_cb_ptr->xdpf = NULL;
+ bcmgenet_put_txcb(priv, ring);
+ spin_unlock(&ring->lock);
+ return false;
+ }
+ } else {
+ struct page *page = virt_to_page(xdpf->data);
+
+ /* For local XDP_TX the caller already prepended the TSB
+ * into xdpf->data/len, so dma_len == xdpf->len.
+ */
+ dma_len = xdpf->len;
+ mapping = page_pool_get_dma_addr(page) +
+ sizeof(*xdpf) + xdpf->headroom;
+ dma_sync_single_for_device(kdev, mapping, dma_len,
+ DMA_BIDIRECTIONAL);
+ }
+
+ dma_unmap_addr_set(tx_cb_ptr, dma_addr, mapping);
+ dma_unmap_len_set(tx_cb_ptr, dma_len, dma_len);
+ tx_cb_ptr->skb = NULL;
+ tx_cb_ptr->xdpf = xdpf;
+ tx_cb_ptr->xdp_dma_map = dma_map;
+
+ len_stat = (dma_len << DMA_BUFLENGTH_SHIFT) |
+ (priv->hw_params->qtag_mask << DMA_TX_QTAG_SHIFT) |
+ DMA_TX_APPEND_CRC | DMA_SOP | DMA_EOP;
+
+ dmadesc_set(priv, tx_cb_ptr->bd_addr, mapping, len_stat);
+
+ ring->free_bds--;
+ ring->prod_index++;
+ ring->prod_index &= DMA_P_INDEX_MASK;
+
+ bcmgenet_tdma_ring_writel(priv, ring->index, ring->prod_index,
+ TDMA_PROD_INDEX);
+
+ spin_unlock(&ring->lock);
+
+ return true;
+}
+
static unsigned int bcmgenet_run_xdp(struct bcmgenet_rx_ring *ring,
struct bpf_prog *prog,
struct xdp_buff *xdp,
struct page *rx_page)
{
+ struct bcmgenet_priv *priv = ring->priv;
+ struct xdp_frame *xdpf;
unsigned int act;
act = bpf_prog_run_xdp(prog, xdp);
@@ -2310,14 +2424,42 @@ static unsigned int bcmgenet_run_xdp(struct bcmgenet_rx_ring *ring,
switch (act) {
case XDP_PASS:
return XDP_PASS;
+ case XDP_TX:
+ /* Prepend a zeroed TSB (Transmit Status Block). The GENET
+ * MAC has TBUF_64B_EN set globally, so hardware expects every
+ * TX buffer to begin with a 64-byte struct status_64. Back
+ * up xdp->data into the RSB area (which is no longer needed
+ * after the BPF program ran) and zero it.
+ */
+ if (xdp->data - xdp->data_hard_start <
+ sizeof(struct status_64) + sizeof(struct xdp_frame)) {
+ page_pool_put_full_page(ring->page_pool, rx_page,
+ true);
+ return XDP_DROP;
+ }
+ xdp->data -= sizeof(struct status_64);
+ xdp->data_meta -= sizeof(struct status_64);
+ memset(xdp->data, 0, sizeof(struct status_64));
+
+ xdpf = xdp_convert_buff_to_frame(xdp);
+ if (unlikely(!xdpf)) {
+ page_pool_put_full_page(ring->page_pool, rx_page,
+ true);
+ return XDP_DROP;
+ }
+ if (unlikely(!bcmgenet_xdp_xmit_frame(priv, xdpf, false))) {
+ xdp_return_frame_rx_napi(xdpf);
+ return XDP_DROP;
+ }
+ return XDP_TX;
case XDP_DROP:
page_pool_put_full_page(ring->page_pool, rx_page, true);
return XDP_DROP;
default:
- bpf_warn_invalid_xdp_action(ring->priv->dev, prog, act);
+ bpf_warn_invalid_xdp_action(priv->dev, prog, act);
fallthrough;
case XDP_ABORTED:
- trace_xdp_exception(ring->priv->dev, prog, act);
+ trace_xdp_exception(priv->dev, prog, act);
page_pool_put_full_page(ring->page_pool, rx_page, true);
return XDP_ABORTED;
}
@@ -2542,9 +2684,15 @@ static int bcmgenet_rx_poll(struct napi_struct *napi, int budget)
{
struct bcmgenet_rx_ring *ring = container_of(napi,
struct bcmgenet_rx_ring, napi);
+ struct bcmgenet_priv *priv = ring->priv;
struct dim_sample dim_sample = {};
unsigned int work_done;
+ /* Reclaim completed XDP TX frames (ring 16 has no interrupt) */
+ if (priv->xdp_tx_ring.free_bds < priv->xdp_tx_ring.size)
+ bcmgenet_tx_reclaim(priv->dev,
+ &priv->xdp_tx_ring, false);
+
work_done = bcmgenet_desc_rx(ring, budget);
if (work_done < budget && napi_complete_done(napi, work_done))
@@ -2775,10 +2923,11 @@ static void bcmgenet_init_rx_coalesce(struct bcmgenet_rx_ring *ring)
/* Initialize a Tx ring along with corresponding hardware registers */
static void bcmgenet_init_tx_ring(struct bcmgenet_priv *priv,
+ struct bcmgenet_tx_ring *ring,
unsigned int index, unsigned int size,
- unsigned int start_ptr, unsigned int end_ptr)
+ unsigned int start_ptr,
+ unsigned int end_ptr)
{
- struct bcmgenet_tx_ring *ring = &priv->tx_rings[index];
u32 words_per_bd = WORDS_PER_BD(priv);
u32 flow_period_val = 0;
@@ -2819,8 +2968,11 @@ static void bcmgenet_init_tx_ring(struct bcmgenet_priv *priv,
bcmgenet_tdma_ring_writel(priv, index, end_ptr * words_per_bd - 1,
DMA_END_ADDR);
- /* Initialize Tx NAPI */
- netif_napi_add_tx(priv->dev, &ring->napi, bcmgenet_tx_poll);
+ /* Initialize Tx NAPI for priority queues only; ring DESC_INDEX
+ * (XDP TX) has its completions handled inline in RX NAPI.
+ */
+ if (index != DESC_INDEX)
+ netif_napi_add_tx(priv->dev, &ring->napi, bcmgenet_tx_poll);
}
static int bcmgenet_rx_ring_create_pool(struct bcmgenet_priv *priv,
@@ -2832,7 +2984,7 @@ static int bcmgenet_rx_ring_create_pool(struct bcmgenet_priv *priv,
.pool_size = ring->size,
.nid = NUMA_NO_NODE,
.dev = &priv->pdev->dev,
- .dma_dir = DMA_FROM_DEVICE,
+ .dma_dir = DMA_BIDIRECTIONAL,
.offset = GENET_XDP_HEADROOM,
.max_len = RX_BUF_LENGTH,
};
@@ -2966,6 +3118,7 @@ static int bcmgenet_tdma_disable(struct bcmgenet_priv *priv)
reg = bcmgenet_tdma_readl(priv, DMA_CTRL);
mask = (1 << (priv->hw_params->tx_queues + 1)) - 1;
+ mask |= BIT(DESC_INDEX);
mask = (mask << DMA_RING_BUF_EN_SHIFT) | DMA_EN;
reg &= ~mask;
bcmgenet_tdma_writel(priv, reg, DMA_CTRL);
@@ -3011,14 +3164,18 @@ static int bcmgenet_rdma_disable(struct bcmgenet_priv *priv)
* with queue 1 being the highest priority queue.
*
* Queue 0 is the default Tx queue with
- * GENET_Q0_TX_BD_CNT = 256 - 4 * 32 = 128 descriptors.
+ * GENET_Q0_TX_BD_CNT = 256 - 4 * 32 - 32 = 96 descriptors.
+ *
+ * Ring 16 (DESC_INDEX) is used for XDP TX with
+ * GENET_Q16_TX_BD_CNT = 32 descriptors.
*
* The transmit control block pool is then partitioned as follows:
- * - Tx queue 0 uses tx_cbs[0..127]
- * - Tx queue 1 uses tx_cbs[128..159]
- * - Tx queue 2 uses tx_cbs[160..191]
- * - Tx queue 3 uses tx_cbs[192..223]
- * - Tx queue 4 uses tx_cbs[224..255]
+ * - Tx queue 0 uses tx_cbs[0..95]
+ * - Tx queue 1 uses tx_cbs[96..127]
+ * - Tx queue 2 uses tx_cbs[128..159]
+ * - Tx queue 3 uses tx_cbs[160..191]
+ * - Tx queue 4 uses tx_cbs[192..223]
+ * - Tx queue 16 uses tx_cbs[224..255]
*/
static void bcmgenet_init_tx_queues(struct net_device *dev)
{
@@ -3031,7 +3188,8 @@ static void bcmgenet_init_tx_queues(struct net_device *dev)
/* Initialize Tx priority queues */
for (i = 0; i <= priv->hw_params->tx_queues; i++) {
- bcmgenet_init_tx_ring(priv, i, end - start, start, end);
+ bcmgenet_init_tx_ring(priv, &priv->tx_rings[i],
+ i, end - start, start, end);
start = end;
end += priv->hw_params->tx_bds_per_q;
dma_priority[DMA_PRIO_REG_INDEX(i)] |=
@@ -3039,13 +3197,19 @@ static void bcmgenet_init_tx_queues(struct net_device *dev)
<< DMA_PRIO_REG_SHIFT(i);
}
+ /* Initialize ring 16 (descriptor ring) for XDP TX */
+ bcmgenet_init_tx_ring(priv, &priv->xdp_tx_ring,
+ DESC_INDEX, GENET_Q16_TX_BD_CNT,
+ TOTAL_DESC - GENET_Q16_TX_BD_CNT, TOTAL_DESC);
+
/* Set Tx queue priorities */
bcmgenet_tdma_writel(priv, dma_priority[0], DMA_PRIORITY_0);
bcmgenet_tdma_writel(priv, dma_priority[1], DMA_PRIORITY_1);
bcmgenet_tdma_writel(priv, dma_priority[2], DMA_PRIORITY_2);
- /* Configure Tx queues as descriptor rings */
+ /* Configure Tx queues as descriptor rings, including ring 16 */
ring_mask = (1 << (priv->hw_params->tx_queues + 1)) - 1;
+ ring_mask |= BIT(DESC_INDEX);
bcmgenet_tdma_writel(priv, ring_mask, DMA_RING_CFG);
/* Enable Tx rings */
@@ -3759,6 +3923,21 @@ static void bcmgenet_get_stats64(struct net_device *dev,
stats->tx_dropped += tx_dropped;
}
+ /* Include XDP TX ring (DESC_INDEX) stats */
+ tx_stats = &priv->xdp_tx_ring.stats64;
+ do {
+ start = u64_stats_fetch_begin(&tx_stats->syncp);
+ tx_bytes = u64_stats_read(&tx_stats->bytes);
+ tx_packets = u64_stats_read(&tx_stats->packets);
+ tx_errors = u64_stats_read(&tx_stats->errors);
+ tx_dropped = u64_stats_read(&tx_stats->dropped);
+ } while (u64_stats_fetch_retry(&tx_stats->syncp, start));
+
+ stats->tx_bytes += tx_bytes;
+ stats->tx_packets += tx_packets;
+ stats->tx_errors += tx_errors;
+ stats->tx_dropped += tx_dropped;
+
for (q = 0; q <= priv->hw_params->rx_queues; q++) {
rx_stats = &priv->rx_rings[q].stats64;
do {
@@ -4262,6 +4441,7 @@ static int bcmgenet_probe(struct platform_device *pdev)
u64_stats_init(&priv->rx_rings[i].stats64.syncp);
for (i = 0; i <= priv->hw_params->tx_queues; i++)
u64_stats_init(&priv->tx_rings[i].stats64.syncp);
+ u64_stats_init(&priv->xdp_tx_ring.stats64.syncp);
/* libphy will determine the link state */
netif_carrier_off(dev);
diff --git a/drivers/net/ethernet/broadcom/genet/bcmgenet.h b/drivers/net/ethernet/broadcom/genet/bcmgenet.h
index 1459473ac1b0..8966d32efe2f 100644
--- a/drivers/net/ethernet/broadcom/genet/bcmgenet.h
+++ b/drivers/net/ethernet/broadcom/genet/bcmgenet.h
@@ -472,6 +472,8 @@ struct bcmgenet_rx_stats64 {
struct enet_cb {
struct sk_buff *skb;
+ struct xdp_frame *xdpf;
+ bool xdp_dma_map;
struct page *rx_page;
unsigned int rx_page_offset;
void __iomem *bd_addr;
@@ -611,6 +613,7 @@ struct bcmgenet_priv {
unsigned int num_tx_bds;
struct bcmgenet_tx_ring tx_rings[GENET_MAX_MQ_CNT + 1];
+ struct bcmgenet_tx_ring xdp_tx_ring;
/* receive variables */
void __iomem *rx_bds;
--
2.51.0
^ permalink raw reply related
* [PATCH net-next v6 3/7] net: bcmgenet: add basic XDP support (PASS/DROP)
From: Nicolai Buchwitz @ 2026-04-06 8:35 UTC (permalink / raw)
To: netdev
Cc: Justin Chen, Simon Horman, Mohsin Bashir, Doug Berger,
Florian Fainelli, Broadcom internal kernel review list,
Andrew Lunn, Eric Dumazet, Paolo Abeni, Nicolai Buchwitz,
David S. Miller, Jakub Kicinski, Alexei Starovoitov,
Daniel Borkmann, Jesper Dangaard Brouer, John Fastabend,
Stanislav Fomichev, linux-kernel, bpf
In-Reply-To: <20260406083536.839517-1-nb@tipi-net.de>
Add XDP program attachment via ndo_bpf and execute XDP programs in the
RX path. XDP_PASS builds an SKB from the xdp_buff (handling
xdp_adjust_head/tail), XDP_DROP returns the page to page_pool without
SKB allocation.
XDP_TX and XDP_REDIRECT are not yet supported and return XDP_ABORTED.
Advertise NETDEV_XDP_ACT_BASIC in xdp_features.
Signed-off-by: Nicolai Buchwitz <nb@tipi-net.de>
---
.../net/ethernet/broadcom/genet/bcmgenet.c | 139 +++++++++++++++---
.../net/ethernet/broadcom/genet/bcmgenet.h | 4 +
2 files changed, 123 insertions(+), 20 deletions(-)
diff --git a/drivers/net/ethernet/broadcom/genet/bcmgenet.c b/drivers/net/ethernet/broadcom/genet/bcmgenet.c
index 6b66a95c2f08..187fa11ad5c2 100644
--- a/drivers/net/ethernet/broadcom/genet/bcmgenet.c
+++ b/drivers/net/ethernet/broadcom/genet/bcmgenet.c
@@ -35,6 +35,8 @@
#include <linux/ip.h>
#include <linux/ipv6.h>
#include <linux/phy.h>
+#include <linux/bpf_trace.h>
+#include <linux/filter.h>
#include <linux/unaligned.h>
@@ -2274,6 +2276,53 @@ static int bcmgenet_rx_refill(struct bcmgenet_rx_ring *ring,
return 0;
}
+static struct sk_buff *bcmgenet_xdp_build_skb(struct bcmgenet_rx_ring *ring,
+ struct xdp_buff *xdp)
+{
+ unsigned int metasize;
+ struct sk_buff *skb;
+
+ skb = napi_build_skb(xdp->data_hard_start, PAGE_SIZE);
+ if (unlikely(!skb))
+ return NULL;
+
+ skb_mark_for_recycle(skb);
+
+ metasize = xdp->data - xdp->data_meta;
+ skb_reserve(skb, xdp->data - xdp->data_hard_start);
+ __skb_put(skb, xdp->data_end - xdp->data);
+
+ if (metasize)
+ skb_metadata_set(skb, metasize);
+
+ return skb;
+}
+
+static unsigned int bcmgenet_run_xdp(struct bcmgenet_rx_ring *ring,
+ struct bpf_prog *prog,
+ struct xdp_buff *xdp,
+ struct page *rx_page)
+{
+ unsigned int act;
+
+ act = bpf_prog_run_xdp(prog, xdp);
+
+ switch (act) {
+ case XDP_PASS:
+ return XDP_PASS;
+ case XDP_DROP:
+ page_pool_put_full_page(ring->page_pool, rx_page, true);
+ return XDP_DROP;
+ default:
+ bpf_warn_invalid_xdp_action(ring->priv->dev, prog, act);
+ fallthrough;
+ case XDP_ABORTED:
+ trace_xdp_exception(ring->priv->dev, prog, act);
+ page_pool_put_full_page(ring->page_pool, rx_page, true);
+ return XDP_ABORTED;
+ }
+}
+
/* bcmgenet_desc_rx - descriptor based rx process.
* this could be called from bottom half, or from NAPI polling method.
*/
@@ -2283,6 +2332,7 @@ static unsigned int bcmgenet_desc_rx(struct bcmgenet_rx_ring *ring,
struct bcmgenet_rx_stats64 *stats = &ring->stats64;
struct bcmgenet_priv *priv = ring->priv;
struct net_device *dev = priv->dev;
+ struct bpf_prog *xdp_prog;
struct enet_cb *cb;
struct sk_buff *skb;
u32 dma_length_status;
@@ -2293,6 +2343,8 @@ static unsigned int bcmgenet_desc_rx(struct bcmgenet_rx_ring *ring,
unsigned int p_index, mask;
unsigned int discards;
+ xdp_prog = READ_ONCE(priv->xdp_prog);
+
/* Clear status before servicing to reduce spurious interrupts */
mask = 1 << (UMAC_IRQ1_RX_INTR_SHIFT + ring->index);
bcmgenet_intrl2_1_writel(priv, mask, INTRL2_CPU_CLEAR);
@@ -2325,7 +2377,7 @@ static unsigned int bcmgenet_desc_rx(struct bcmgenet_rx_ring *ring,
struct status_64 *status;
struct page *rx_page;
unsigned int rx_off;
- __be16 rx_csum;
+ __be16 rx_csum = 0;
void *hard_start;
cb = &priv->rx_cbs[ring->read_ptr];
@@ -2403,30 +2455,44 @@ static unsigned int bcmgenet_desc_rx(struct bcmgenet_rx_ring *ring,
goto next;
} /* error packet */
- /* Build SKB from the page - data starts at hard_start,
- * frame begins after RSB(64) + pad(2) = 66 bytes.
- */
- skb = napi_build_skb(hard_start, PAGE_SIZE - GENET_XDP_HEADROOM);
- if (unlikely(!skb)) {
- BCMGENET_STATS64_INC(stats, dropped);
- page_pool_put_full_page(ring->page_pool, rx_page,
- true);
- goto next;
- }
-
- skb_mark_for_recycle(skb);
-
- /* Reserve the RSB + pad, then set the data length */
- skb_reserve(skb, GENET_RSB_PAD);
- __skb_put(skb, len - GENET_RSB_PAD);
+ {
+ struct xdp_buff xdp;
+ unsigned int xdp_act;
+ int pkt_len;
+
+ pkt_len = len - GENET_RSB_PAD;
+ if (priv->crc_fwd_en)
+ pkt_len -= ETH_FCS_LEN;
+
+ /* Save rx_csum before XDP runs - an XDP program
+ * could overwrite the RSB via bpf_xdp_adjust_head.
+ */
+ if (dev->features & NETIF_F_RXCSUM)
+ rx_csum = (__force __be16)(status->rx_csum
+ & 0xffff);
+
+ xdp_init_buff(&xdp, PAGE_SIZE, &ring->xdp_rxq);
+ xdp_prepare_buff(&xdp, page_address(rx_page),
+ GENET_RX_HEADROOM, pkt_len, true);
+
+ if (xdp_prog) {
+ xdp_act = bcmgenet_run_xdp(ring, xdp_prog,
+ &xdp, rx_page);
+ if (xdp_act != XDP_PASS)
+ goto next;
+ }
- if (priv->crc_fwd_en) {
- skb_trim(skb, skb->len - ETH_FCS_LEN);
+ skb = bcmgenet_xdp_build_skb(ring, &xdp);
+ if (unlikely(!skb)) {
+ BCMGENET_STATS64_INC(stats, dropped);
+ page_pool_put_full_page(ring->page_pool,
+ rx_page, true);
+ goto next;
+ }
}
/* Set up checksum offload */
if (dev->features & NETIF_F_RXCSUM) {
- rx_csum = (__force __be16)(status->rx_csum & 0xffff);
if (rx_csum) {
skb->csum = (__force __wsum)ntohs(rx_csum);
skb->ip_summed = CHECKSUM_COMPLETE;
@@ -3744,6 +3810,37 @@ static int bcmgenet_change_carrier(struct net_device *dev, bool new_carrier)
return 0;
}
+static int bcmgenet_xdp_setup(struct net_device *dev,
+ struct netdev_bpf *xdp)
+{
+ struct bcmgenet_priv *priv = netdev_priv(dev);
+ struct bpf_prog *old_prog;
+ struct bpf_prog *prog = xdp->prog;
+
+ if (prog && dev->mtu > PAGE_SIZE - GENET_RX_HEADROOM -
+ SKB_DATA_ALIGN(sizeof(struct skb_shared_info))) {
+ NL_SET_ERR_MSG_MOD(xdp->extack,
+ "MTU too large for single-page XDP buffer");
+ return -EOPNOTSUPP;
+ }
+
+ old_prog = xchg(&priv->xdp_prog, prog);
+ if (old_prog)
+ bpf_prog_put(old_prog);
+
+ return 0;
+}
+
+static int bcmgenet_xdp(struct net_device *dev, struct netdev_bpf *xdp)
+{
+ switch (xdp->command) {
+ case XDP_SETUP_PROG:
+ return bcmgenet_xdp_setup(dev, xdp);
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
static const struct net_device_ops bcmgenet_netdev_ops = {
.ndo_open = bcmgenet_open,
.ndo_stop = bcmgenet_close,
@@ -3755,6 +3852,7 @@ static const struct net_device_ops bcmgenet_netdev_ops = {
.ndo_set_features = bcmgenet_set_features,
.ndo_get_stats64 = bcmgenet_get_stats64,
.ndo_change_carrier = bcmgenet_change_carrier,
+ .ndo_bpf = bcmgenet_xdp,
};
/* GENET hardware parameters/characteristics */
@@ -4057,6 +4155,7 @@ static int bcmgenet_probe(struct platform_device *pdev)
NETIF_F_RXCSUM;
dev->hw_features |= dev->features;
dev->vlan_features |= dev->features;
+ dev->xdp_features = NETDEV_XDP_ACT_BASIC;
netdev_sw_irq_coalesce_default_on(dev);
diff --git a/drivers/net/ethernet/broadcom/genet/bcmgenet.h b/drivers/net/ethernet/broadcom/genet/bcmgenet.h
index 82a6d29f481d..1459473ac1b0 100644
--- a/drivers/net/ethernet/broadcom/genet/bcmgenet.h
+++ b/drivers/net/ethernet/broadcom/genet/bcmgenet.h
@@ -16,6 +16,7 @@
#include <linux/dim.h>
#include <linux/ethtool.h>
#include <net/page_pool/helpers.h>
+#include <linux/bpf.h>
#include <net/xdp.h>
#include "../unimac.h"
@@ -671,6 +672,9 @@ struct bcmgenet_priv {
u8 sopass[SOPASS_MAX];
struct bcmgenet_mib_counters mib;
+
+ /* XDP */
+ struct bpf_prog *xdp_prog;
};
static inline bool bcmgenet_has_40bits(struct bcmgenet_priv *priv)
--
2.51.0
^ permalink raw reply related
* [PATCH net-next v6 2/7] net: bcmgenet: register xdp_rxq_info for each RX ring
From: Nicolai Buchwitz @ 2026-04-06 8:35 UTC (permalink / raw)
To: netdev
Cc: Justin Chen, Simon Horman, Mohsin Bashir, Doug Berger,
Florian Fainelli, Broadcom internal kernel review list,
Andrew Lunn, Eric Dumazet, Paolo Abeni, Nicolai Buchwitz,
David S. Miller, Jakub Kicinski, Alexei Starovoitov,
Daniel Borkmann, Jesper Dangaard Brouer, John Fastabend,
Stanislav Fomichev, linux-kernel, bpf
In-Reply-To: <20260406083536.839517-1-nb@tipi-net.de>
Register an xdp_rxq_info per RX ring and associate it with the ring's
page_pool via MEM_TYPE_PAGE_POOL. This is required infrastructure for
XDP program execution: the XDP framework needs to know the memory model
backing each RX queue for correct page lifecycle management.
No functional change - XDP programs are not yet attached or executed.
Signed-off-by: Nicolai Buchwitz <nb@tipi-net.de>
---
drivers/net/ethernet/broadcom/genet/bcmgenet.c | 18 ++++++++++++++++++
drivers/net/ethernet/broadcom/genet/bcmgenet.h | 2 ++
2 files changed, 20 insertions(+)
diff --git a/drivers/net/ethernet/broadcom/genet/bcmgenet.c b/drivers/net/ethernet/broadcom/genet/bcmgenet.c
index 22a1a8b59268..6b66a95c2f08 100644
--- a/drivers/net/ethernet/broadcom/genet/bcmgenet.c
+++ b/drivers/net/ethernet/broadcom/genet/bcmgenet.c
@@ -2779,7 +2779,23 @@ static int bcmgenet_rx_ring_create_pool(struct bcmgenet_priv *priv,
return err;
}
+ err = xdp_rxq_info_reg(&ring->xdp_rxq, priv->dev, ring->index, 0);
+ if (err)
+ goto err_free_pp;
+
+ err = xdp_rxq_info_reg_mem_model(&ring->xdp_rxq, MEM_TYPE_PAGE_POOL,
+ ring->page_pool);
+ if (err)
+ goto err_unreg_rxq;
+
return 0;
+
+err_unreg_rxq:
+ xdp_rxq_info_unreg(&ring->xdp_rxq);
+err_free_pp:
+ page_pool_destroy(ring->page_pool);
+ ring->page_pool = NULL;
+ return err;
}
/* Initialize a RDMA ring */
@@ -2808,6 +2824,7 @@ static int bcmgenet_init_rx_ring(struct bcmgenet_priv *priv,
if (ret) {
for (i = 0; i < ring->size; i++)
bcmgenet_free_rx_cb(ring->cbs + i, ring->page_pool);
+ xdp_rxq_info_unreg(&ring->xdp_rxq);
page_pool_destroy(ring->page_pool);
ring->page_pool = NULL;
return ret;
@@ -3013,6 +3030,7 @@ static void bcmgenet_destroy_rx_page_pools(struct bcmgenet_priv *priv)
for (i = 0; i <= priv->hw_params->rx_queues; ++i) {
ring = &priv->rx_rings[i];
if (ring->page_pool) {
+ xdp_rxq_info_unreg(&ring->xdp_rxq);
page_pool_destroy(ring->page_pool);
ring->page_pool = NULL;
}
diff --git a/drivers/net/ethernet/broadcom/genet/bcmgenet.h b/drivers/net/ethernet/broadcom/genet/bcmgenet.h
index 11a0ec563a89..82a6d29f481d 100644
--- a/drivers/net/ethernet/broadcom/genet/bcmgenet.h
+++ b/drivers/net/ethernet/broadcom/genet/bcmgenet.h
@@ -16,6 +16,7 @@
#include <linux/dim.h>
#include <linux/ethtool.h>
#include <net/page_pool/helpers.h>
+#include <net/xdp.h>
#include "../unimac.h"
@@ -579,6 +580,7 @@ struct bcmgenet_rx_ring {
u32 rx_max_coalesced_frames;
u32 rx_coalesce_usecs;
struct page_pool *page_pool;
+ struct xdp_rxq_info xdp_rxq;
struct bcmgenet_priv *priv;
};
--
2.51.0
^ permalink raw reply related
* [PATCH net-next v6 1/7] net: bcmgenet: convert RX path to page_pool
From: Nicolai Buchwitz @ 2026-04-06 8:35 UTC (permalink / raw)
To: netdev
Cc: Justin Chen, Simon Horman, Mohsin Bashir, Doug Berger,
Florian Fainelli, Broadcom internal kernel review list,
Andrew Lunn, Eric Dumazet, Paolo Abeni, Nicolai Buchwitz,
David S. Miller, Jakub Kicinski, Vikas Gupta, Bhargava Marreddy,
Rajashekar Hudumula, Arnd Bergmann, Fernando Fernandez Mancera,
Markus Blöchl, linux-kernel
In-Reply-To: <20260406083536.839517-1-nb@tipi-net.de>
Replace the per-packet __netdev_alloc_skb() + dma_map_single() in the
RX path with page_pool, which provides efficient page recycling and
DMA mapping management. This is a prerequisite for XDP support (which
requires stable page-backed buffers rather than SKB linear data).
Key changes:
- Create a page_pool per RX ring (PP_FLAG_DMA_MAP | PP_FLAG_DMA_SYNC_DEV)
- bcmgenet_rx_refill() allocates pages via page_pool_alloc_pages()
- bcmgenet_desc_rx() builds SKBs from pages via napi_build_skb() with
skb_mark_for_recycle() for automatic page_pool return
- Buffer layout reserves XDP_PACKET_HEADROOM (256 bytes) before the HW
RSB (64 bytes) + alignment pad (2 bytes) for future XDP headroom
Signed-off-by: Nicolai Buchwitz <nb@tipi-net.de>
---
drivers/net/ethernet/broadcom/Kconfig | 1 +
.../net/ethernet/broadcom/genet/bcmgenet.c | 218 +++++++++++-------
.../net/ethernet/broadcom/genet/bcmgenet.h | 4 +
3 files changed, 144 insertions(+), 79 deletions(-)
diff --git a/drivers/net/ethernet/broadcom/Kconfig b/drivers/net/ethernet/broadcom/Kconfig
index dd164acafd01..a6c388dacba6 100644
--- a/drivers/net/ethernet/broadcom/Kconfig
+++ b/drivers/net/ethernet/broadcom/Kconfig
@@ -78,6 +78,7 @@ config BCMGENET
select BCM7XXX_PHY
select MDIO_BCM_UNIMAC
select DIMLIB
+ select PAGE_POOL
select BROADCOM_PHY if ARCH_BCM2835
help
This driver supports the built-in Ethernet MACs found in the
diff --git a/drivers/net/ethernet/broadcom/genet/bcmgenet.c b/drivers/net/ethernet/broadcom/genet/bcmgenet.c
index 482a31e7b72b..22a1a8b59268 100644
--- a/drivers/net/ethernet/broadcom/genet/bcmgenet.c
+++ b/drivers/net/ethernet/broadcom/genet/bcmgenet.c
@@ -52,6 +52,14 @@
#define RX_BUF_LENGTH 2048
#define SKB_ALIGNMENT 32
+/* Page pool RX buffer layout:
+ * XDP_PACKET_HEADROOM | RSB(64) + pad(2) | frame data | skb_shared_info
+ * The HW writes the 64B RSB + 2B alignment padding before the frame.
+ */
+#define GENET_XDP_HEADROOM XDP_PACKET_HEADROOM
+#define GENET_RSB_PAD (sizeof(struct status_64) + 2)
+#define GENET_RX_HEADROOM (GENET_XDP_HEADROOM + GENET_RSB_PAD)
+
/* Tx/Rx DMA register offset, skip 256 descriptors */
#define WORDS_PER_BD(p) (p->hw_params->words_per_bd)
#define DMA_DESC_SIZE (WORDS_PER_BD(priv) * sizeof(u32))
@@ -1895,21 +1903,13 @@ static struct sk_buff *bcmgenet_free_tx_cb(struct device *dev,
}
/* Simple helper to free a receive control block's resources */
-static struct sk_buff *bcmgenet_free_rx_cb(struct device *dev,
- struct enet_cb *cb)
+static void bcmgenet_free_rx_cb(struct enet_cb *cb,
+ struct page_pool *pool)
{
- struct sk_buff *skb;
-
- skb = cb->skb;
- cb->skb = NULL;
-
- if (dma_unmap_addr(cb, dma_addr)) {
- dma_unmap_single(dev, dma_unmap_addr(cb, dma_addr),
- dma_unmap_len(cb, dma_len), DMA_FROM_DEVICE);
- dma_unmap_addr_set(cb, dma_addr, 0);
+ if (cb->rx_page) {
+ page_pool_put_full_page(pool, cb->rx_page, false);
+ cb->rx_page = NULL;
}
-
- return skb;
}
/* Unlocked version of the reclaim routine */
@@ -2248,46 +2248,30 @@ static netdev_tx_t bcmgenet_xmit(struct sk_buff *skb, struct net_device *dev)
goto out;
}
-static struct sk_buff *bcmgenet_rx_refill(struct bcmgenet_priv *priv,
- struct enet_cb *cb)
+static int bcmgenet_rx_refill(struct bcmgenet_rx_ring *ring,
+ struct enet_cb *cb)
{
- struct device *kdev = &priv->pdev->dev;
- struct sk_buff *skb;
- struct sk_buff *rx_skb;
+ struct bcmgenet_priv *priv = ring->priv;
dma_addr_t mapping;
+ struct page *page;
- /* Allocate a new Rx skb */
- skb = __netdev_alloc_skb(priv->dev, priv->rx_buf_len + SKB_ALIGNMENT,
- GFP_ATOMIC | __GFP_NOWARN);
- if (!skb) {
+ page = page_pool_alloc_pages(ring->page_pool,
+ GFP_ATOMIC | __GFP_NOWARN);
+ if (!page) {
priv->mib.alloc_rx_buff_failed++;
netif_err(priv, rx_err, priv->dev,
- "%s: Rx skb allocation failed\n", __func__);
- return NULL;
- }
-
- /* DMA-map the new Rx skb */
- mapping = dma_map_single(kdev, skb->data, priv->rx_buf_len,
- DMA_FROM_DEVICE);
- if (dma_mapping_error(kdev, mapping)) {
- priv->mib.rx_dma_failed++;
- dev_kfree_skb_any(skb);
- netif_err(priv, rx_err, priv->dev,
- "%s: Rx skb DMA mapping failed\n", __func__);
- return NULL;
+ "%s: Rx page allocation failed\n", __func__);
+ return -ENOMEM;
}
- /* Grab the current Rx skb from the ring and DMA-unmap it */
- rx_skb = bcmgenet_free_rx_cb(kdev, cb);
+ /* page_pool handles DMA mapping via PP_FLAG_DMA_MAP */
+ mapping = page_pool_get_dma_addr(page) + GENET_XDP_HEADROOM;
- /* Put the new Rx skb on the ring */
- cb->skb = skb;
- dma_unmap_addr_set(cb, dma_addr, mapping);
- dma_unmap_len_set(cb, dma_len, priv->rx_buf_len);
+ cb->rx_page = page;
+ cb->rx_page_offset = GENET_XDP_HEADROOM;
dmadesc_set_addr(priv, cb->bd_addr, mapping);
- /* Return the current Rx skb to caller */
- return rx_skb;
+ return 0;
}
/* bcmgenet_desc_rx - descriptor based rx process.
@@ -2339,25 +2323,28 @@ static unsigned int bcmgenet_desc_rx(struct bcmgenet_rx_ring *ring,
while ((rxpktprocessed < rxpkttoprocess) &&
(rxpktprocessed < budget)) {
struct status_64 *status;
+ struct page *rx_page;
+ unsigned int rx_off;
__be16 rx_csum;
+ void *hard_start;
cb = &priv->rx_cbs[ring->read_ptr];
- skb = bcmgenet_rx_refill(priv, cb);
- if (unlikely(!skb)) {
+ /* Save the received page before refilling */
+ rx_page = cb->rx_page;
+ rx_off = cb->rx_page_offset;
+
+ if (bcmgenet_rx_refill(ring, cb)) {
BCMGENET_STATS64_INC(stats, dropped);
goto next;
}
- status = (struct status_64 *)skb->data;
+ page_pool_dma_sync_for_cpu(ring->page_pool, rx_page, 0,
+ RX_BUF_LENGTH);
+
+ hard_start = page_address(rx_page) + rx_off;
+ status = (struct status_64 *)hard_start;
dma_length_status = status->length_status;
- if (dev->features & NETIF_F_RXCSUM) {
- rx_csum = (__force __be16)(status->rx_csum & 0xffff);
- if (rx_csum) {
- skb->csum = (__force __wsum)ntohs(rx_csum);
- skb->ip_summed = CHECKSUM_COMPLETE;
- }
- }
/* DMA flags and length are still valid no matter how
* we got the Receive Status Vector (64B RSB or register)
@@ -2373,7 +2360,8 @@ static unsigned int bcmgenet_desc_rx(struct bcmgenet_rx_ring *ring,
if (unlikely(len > RX_BUF_LENGTH)) {
netif_err(priv, rx_status, dev, "oversized packet\n");
BCMGENET_STATS64_INC(stats, length_errors);
- dev_kfree_skb_any(skb);
+ page_pool_put_full_page(ring->page_pool, rx_page,
+ true);
goto next;
}
@@ -2381,7 +2369,8 @@ static unsigned int bcmgenet_desc_rx(struct bcmgenet_rx_ring *ring,
netif_err(priv, rx_status, dev,
"dropping fragmented packet!\n");
BCMGENET_STATS64_INC(stats, fragmented_errors);
- dev_kfree_skb_any(skb);
+ page_pool_put_full_page(ring->page_pool, rx_page,
+ true);
goto next;
}
@@ -2409,24 +2398,47 @@ static unsigned int bcmgenet_desc_rx(struct bcmgenet_rx_ring *ring,
DMA_RX_RXER)) == DMA_RX_RXER)
u64_stats_inc(&stats->errors);
u64_stats_update_end(&stats->syncp);
- dev_kfree_skb_any(skb);
+ page_pool_put_full_page(ring->page_pool, rx_page,
+ true);
goto next;
} /* error packet */
- skb_put(skb, len);
+ /* Build SKB from the page - data starts at hard_start,
+ * frame begins after RSB(64) + pad(2) = 66 bytes.
+ */
+ skb = napi_build_skb(hard_start, PAGE_SIZE - GENET_XDP_HEADROOM);
+ if (unlikely(!skb)) {
+ BCMGENET_STATS64_INC(stats, dropped);
+ page_pool_put_full_page(ring->page_pool, rx_page,
+ true);
+ goto next;
+ }
- /* remove RSB and hardware 2bytes added for IP alignment */
- skb_pull(skb, 66);
- len -= 66;
+ skb_mark_for_recycle(skb);
+
+ /* Reserve the RSB + pad, then set the data length */
+ skb_reserve(skb, GENET_RSB_PAD);
+ __skb_put(skb, len - GENET_RSB_PAD);
if (priv->crc_fwd_en) {
- skb_trim(skb, len - ETH_FCS_LEN);
- len -= ETH_FCS_LEN;
+ skb_trim(skb, skb->len - ETH_FCS_LEN);
}
+ /* Set up checksum offload */
+ if (dev->features & NETIF_F_RXCSUM) {
+ rx_csum = (__force __be16)(status->rx_csum & 0xffff);
+ if (rx_csum) {
+ skb->csum = (__force __wsum)ntohs(rx_csum);
+ skb->ip_summed = CHECKSUM_COMPLETE;
+ }
+ }
+
+ len = skb->len;
bytes_processed += len;
- /*Finish setting up the received SKB and send it to the kernel*/
+ /* Finish setting up the received SKB and send it to the
+ * kernel.
+ */
skb->protocol = eth_type_trans(skb, priv->dev);
u64_stats_update_begin(&stats->syncp);
@@ -2495,12 +2507,11 @@ static void bcmgenet_dim_work(struct work_struct *work)
dim->state = DIM_START_MEASURE;
}
-/* Assign skb to RX DMA descriptor. */
+/* Assign page_pool pages to RX DMA descriptors. */
static int bcmgenet_alloc_rx_buffers(struct bcmgenet_priv *priv,
struct bcmgenet_rx_ring *ring)
{
struct enet_cb *cb;
- struct sk_buff *skb;
int i;
netif_dbg(priv, hw, priv->dev, "%s\n", __func__);
@@ -2508,10 +2519,7 @@ static int bcmgenet_alloc_rx_buffers(struct bcmgenet_priv *priv,
/* loop here for each buffer needing assign */
for (i = 0; i < ring->size; i++) {
cb = ring->cbs + i;
- skb = bcmgenet_rx_refill(priv, cb);
- if (skb)
- dev_consume_skb_any(skb);
- if (!cb->skb)
+ if (bcmgenet_rx_refill(ring, cb))
return -ENOMEM;
}
@@ -2520,16 +2528,18 @@ static int bcmgenet_alloc_rx_buffers(struct bcmgenet_priv *priv,
static void bcmgenet_free_rx_buffers(struct bcmgenet_priv *priv)
{
- struct sk_buff *skb;
+ struct bcmgenet_rx_ring *ring;
struct enet_cb *cb;
- int i;
+ int q, i;
- for (i = 0; i < priv->num_rx_bds; i++) {
- cb = &priv->rx_cbs[i];
-
- skb = bcmgenet_free_rx_cb(&priv->pdev->dev, cb);
- if (skb)
- dev_consume_skb_any(skb);
+ for (q = 0; q <= priv->hw_params->rx_queues; q++) {
+ ring = &priv->rx_rings[q];
+ if (!ring->page_pool)
+ continue;
+ for (i = 0; i < ring->size; i++) {
+ cb = ring->cbs + i;
+ bcmgenet_free_rx_cb(cb, ring->page_pool);
+ }
}
}
@@ -2747,6 +2757,31 @@ static void bcmgenet_init_tx_ring(struct bcmgenet_priv *priv,
netif_napi_add_tx(priv->dev, &ring->napi, bcmgenet_tx_poll);
}
+static int bcmgenet_rx_ring_create_pool(struct bcmgenet_priv *priv,
+ struct bcmgenet_rx_ring *ring)
+{
+ struct page_pool_params pp_params = {
+ .order = 0,
+ .flags = PP_FLAG_DMA_MAP | PP_FLAG_DMA_SYNC_DEV,
+ .pool_size = ring->size,
+ .nid = NUMA_NO_NODE,
+ .dev = &priv->pdev->dev,
+ .dma_dir = DMA_FROM_DEVICE,
+ .offset = GENET_XDP_HEADROOM,
+ .max_len = RX_BUF_LENGTH,
+ };
+ int err;
+
+ ring->page_pool = page_pool_create(&pp_params);
+ if (IS_ERR(ring->page_pool)) {
+ err = PTR_ERR(ring->page_pool);
+ ring->page_pool = NULL;
+ return err;
+ }
+
+ return 0;
+}
+
/* Initialize a RDMA ring */
static int bcmgenet_init_rx_ring(struct bcmgenet_priv *priv,
unsigned int index, unsigned int size,
@@ -2754,7 +2789,7 @@ static int bcmgenet_init_rx_ring(struct bcmgenet_priv *priv,
{
struct bcmgenet_rx_ring *ring = &priv->rx_rings[index];
u32 words_per_bd = WORDS_PER_BD(priv);
- int ret;
+ int ret, i;
ring->priv = priv;
ring->index = index;
@@ -2765,10 +2800,19 @@ static int bcmgenet_init_rx_ring(struct bcmgenet_priv *priv,
ring->cb_ptr = start_ptr;
ring->end_ptr = end_ptr - 1;
- ret = bcmgenet_alloc_rx_buffers(priv, ring);
+ ret = bcmgenet_rx_ring_create_pool(priv, ring);
if (ret)
return ret;
+ ret = bcmgenet_alloc_rx_buffers(priv, ring);
+ if (ret) {
+ for (i = 0; i < ring->size; i++)
+ bcmgenet_free_rx_cb(ring->cbs + i, ring->page_pool);
+ page_pool_destroy(ring->page_pool);
+ ring->page_pool = NULL;
+ return ret;
+ }
+
bcmgenet_init_dim(ring, bcmgenet_dim_work);
bcmgenet_init_rx_coalesce(ring);
@@ -2961,6 +3005,20 @@ static void bcmgenet_fini_rx_napi(struct bcmgenet_priv *priv)
}
}
+static void bcmgenet_destroy_rx_page_pools(struct bcmgenet_priv *priv)
+{
+ struct bcmgenet_rx_ring *ring;
+ unsigned int i;
+
+ for (i = 0; i <= priv->hw_params->rx_queues; ++i) {
+ ring = &priv->rx_rings[i];
+ if (ring->page_pool) {
+ page_pool_destroy(ring->page_pool);
+ ring->page_pool = NULL;
+ }
+ }
+}
+
/* Initialize Rx queues
*
* Queues 0-15 are priority queues. Hardware Filtering Block (HFB) can be
@@ -3032,6 +3090,7 @@ static void bcmgenet_fini_dma(struct bcmgenet_priv *priv)
}
bcmgenet_free_rx_buffers(priv);
+ bcmgenet_destroy_rx_page_pools(priv);
kfree(priv->rx_cbs);
kfree(priv->tx_cbs);
}
@@ -3108,6 +3167,7 @@ static int bcmgenet_init_dma(struct bcmgenet_priv *priv, bool flush_rx)
if (ret) {
netdev_err(priv->dev, "failed to initialize Rx queues\n");
bcmgenet_free_rx_buffers(priv);
+ bcmgenet_destroy_rx_page_pools(priv);
kfree(priv->rx_cbs);
kfree(priv->tx_cbs);
return ret;
diff --git a/drivers/net/ethernet/broadcom/genet/bcmgenet.h b/drivers/net/ethernet/broadcom/genet/bcmgenet.h
index 9e4110c7fdf6..11a0ec563a89 100644
--- a/drivers/net/ethernet/broadcom/genet/bcmgenet.h
+++ b/drivers/net/ethernet/broadcom/genet/bcmgenet.h
@@ -15,6 +15,7 @@
#include <linux/phy.h>
#include <linux/dim.h>
#include <linux/ethtool.h>
+#include <net/page_pool/helpers.h>
#include "../unimac.h"
@@ -469,6 +470,8 @@ struct bcmgenet_rx_stats64 {
struct enet_cb {
struct sk_buff *skb;
+ struct page *rx_page;
+ unsigned int rx_page_offset;
void __iomem *bd_addr;
DEFINE_DMA_UNMAP_ADDR(dma_addr);
DEFINE_DMA_UNMAP_LEN(dma_len);
@@ -575,6 +578,7 @@ struct bcmgenet_rx_ring {
struct bcmgenet_net_dim dim;
u32 rx_max_coalesced_frames;
u32 rx_coalesce_usecs;
+ struct page_pool *page_pool;
struct bcmgenet_priv *priv;
};
--
2.51.0
^ permalink raw reply related
* [PATCH net-next v6 0/7] net: bcmgenet: add XDP support
From: Nicolai Buchwitz @ 2026-04-06 8:35 UTC (permalink / raw)
To: netdev
Cc: Justin Chen, Simon Horman, Mohsin Bashir, Doug Berger,
Florian Fainelli, Broadcom internal kernel review list,
Andrew Lunn, Eric Dumazet, Paolo Abeni, Nicolai Buchwitz,
Alexei Starovoitov, Daniel Borkmann, David S. Miller,
Jakub Kicinski, Jesper Dangaard Brouer, John Fastabend,
Stanislav Fomichev, bpf
Add XDP support to the bcmgenet driver, covering XDP_PASS, XDP_DROP,
XDP_TX, XDP_REDIRECT, and ndo_xdp_xmit.
The first patch converts the RX path from the existing kmalloc-based
allocation to page_pool, which is a prerequisite for XDP. The remaining
patches incrementally add XDP functionality and per-action statistics.
Tested on Raspberry Pi CM4 (BCM2711, bcmgenet, 1Gbps link):
- XDP_PASS: 943 Mbit/s TX, 935 Mbit/s RX (no regression vs baseline)
- XDP_PASS latency: 0.164ms avg, 0% packet loss
- XDP_DROP: all inbound traffic blocked as expected
- XDP_TX: TX counter increments (packet reflection working)
- Link flap with XDP attached: no errors
- Program swap under iperf3 load: no errors
- Upstream XDP selftests (xdp.py): pass_sb, drop_sb, tx_sb passing
- XDP-based EtherCAT master (~37 kHz cycle rate, all packet processing
in BPF/XDP), stable over multiple days
Previous versions:
v5: https://lore.kernel.org/netdev/20260328230513.415790-1-nb@tipi-net.de/
v4: https://lore.kernel.org/netdev/20260323120539.136029-1-nb@tipi-net.de/
v3: https://lore.kernel.org/netdev/20260319115402.353509-1-nb@tipi-net.de/
v2: https://lore.kernel.org/netdev/20260315214914.1555777-1-nb@tipi-net.de/
v1: https://lore.kernel.org/netdev/20260313092101.1344954-1-nb@tipi-net.de/
Changes since v5:
- Refactored desc_rx: always prepare xdp_buff and use
bcmgenet_xdp_build_skb for both XDP and non-XDP paths, treating
no-prog as XDP_PASS. (Jakub Kicinski)
- Removed synchronize_net() before bpf_prog_put(), RCU handles
the grace period. (Jakub Kicinski)
- Save status->rx_csum before running XDP program to prevent
bpf_xdp_adjust_head from corrupting the RSB checksum.
(Jakub Kicinski)
- Tightened TSB headroom check to include sizeof(struct xdp_frame).
(Jakub Kicinski)
- Fixed reclaim gating: check for pending frames on the XDP TX ring
instead of priv->xdp_prog, so in-flight frames are still reclaimed
after XDP program detach. (Jakub Kicinski)
- Removed dead len -= ETH_FCS_LEN in patch 1. (Mohsin Bashir)
- Added patch 7: minimal ndo_change_mtu that rejects MTU values
incompatible with XDP when a program is attached. (Mohsin Bashir,
Florian Fainelli)
Changes since v4:
- Fixed unused variable warning: moved tx_ring declaration from
patch 4 to patch 5 where it is first used. (Jakub Kicinski)
Changes since v3:
- Fixed xdp_prepare_buff() called with meta_valid=false, causing
bcmgenet_xdp_build_skb() to compute metasize=UINT_MAX and corrupt
skb meta_len. Now passes true. (Simon Horman)
- Removed bcmgenet_dump_tx_queue() for ring 16 in bcmgenet_timeout().
Ring 16 has no netdev TX queue, so netdev_get_tx_queue(dev, 16)
accessed beyond the allocated _tx array. (Simon Horman)
- Fixed checkpatch alignment warnings in patches 4 and 5.
Changes since v2:
- Fixed page leak on partial bcmgenet_alloc_rx_buffers() failure:
free already-allocated rx_cbs before destroying page pool.
(Simon Horman)
- Fixed GENET_Q16_TX_BD_CNT defined as 64 instead of 32.
(Simon Horman)
- Moved XDP TX ring to a separate struct member (xdp_tx_ring)
instead of expanding tx_rings[] to DESC_INDEX+1. (Justin Chen)
- Added synchronize_net() before bpf_prog_put() in XDP prog swap.
- Removed goto drop_page inside switch; inlined page_pool_put
calls in each failure path. (Justin Chen)
- Removed unnecessary curly braces around case XDP_TX. (Justin Chen)
- Moved int err hoisting from patch 2 to patch 1. (Justin Chen)
- Kept return type on same line as function name, per driver
convention. (Justin Chen)
- XDP TX packets/bytes now counted in TX reclaim for standard
network statistics.
Changes since v1:
- Fixed tx_rings[DESC_INDEX] out-of-bounds access. Expanded array
to DESC_INDEX+1 and initialized ring 16 with dedicated BDs.
- Use ring 16 (hardware default descriptor ring) for XDP TX,
isolating from normal SKB TX queues.
- Piggyback ring 16 TX completion on RX NAPI poll (INTRL2_1 bit
collision with RX ring 0).
- Fixed ring 16 TX reclaim: skip INTRL2_1 clear, skip BQL
completion, use non-destructive reclaim in RX poll path.
- Prepend zeroed TSB before XDP TX frame data (TBUF_64B_EN requires
64-byte struct status_64 prefix on all TX buffers).
- Tested with upstream XDP selftests (xdp.py): pass_sb, drop_sb,
tx_sb all passing. The multi-buffer tests (pass_mb, drop_mb,
tx_mb) fail because bcmgenet does not support jumbo frames /
MTU changes; I plan to add ndo_change_mtu support in a follow-up
series.
Nicolai Buchwitz (7):
net: bcmgenet: convert RX path to page_pool
net: bcmgenet: register xdp_rxq_info for each RX ring
net: bcmgenet: add basic XDP support (PASS/DROP)
net: bcmgenet: add XDP_TX support
net: bcmgenet: add XDP_REDIRECT and ndo_xdp_xmit support
net: bcmgenet: add XDP statistics counters
net: bcmgenet: reject MTU changes incompatible with XDP
drivers/net/ethernet/broadcom/Kconfig | 1 +
.../net/ethernet/broadcom/genet/bcmgenet.c | 643 +++++++++++++++---
.../net/ethernet/broadcom/genet/bcmgenet.h | 19 +
3 files changed, 565 insertions(+), 98 deletions(-)
--
2.51.0
^ permalink raw reply
* RE: [PATCH net 1/1] tipc: validate Gap ACK blocks in STATE message
From: Tung Quang Nguyen @ 2026-04-06 8:29 UTC (permalink / raw)
To: Ren Wei
Cc: jmaloy@redhat.com, davem@davemloft.net, edumazet@google.com,
kuba@kernel.org, pabeni@redhat.com, horms@kernel.org,
ying.xue@windriver.com, yifanwucs@gmail.com,
tomapufckgml@gmail.com, yuantan098@gmail.com, bird@lzu.edu.cn,
enjou1224z@gmail.com, caoruide123@gmail.com,
netdev@vger.kernel.org
In-Reply-To: <8b07adfdbda1c0fbcf10a114f3272b848d4ea3a0.1775269941.git.caoruide123@gmail.com>
>Subject: [PATCH net 1/1] tipc: validate Gap ACK blocks in STATE message
>
>From: Ruide Cao <caoruide123@gmail.com>
>
>tipc_get_gap_ack_blks() reads len, ugack_cnt and bgack_cnt directly from
>msg_data(hdr) before verifying that a STATE message actually contains the
>fixed Gap ACK block header in its logical data area.
>
>A peer that negotiates TIPC_GAP_ACK_BLOCK can send a short STATE message
>with a declared TIPC payload shorter than struct tipc_gap_ack_blks and still
>append a few physical bytes after the header. The helper then trusts those
>bytes as Gap ACK metadata, and the forged bgack_cnt/len values can drive the
>broadcast receive path into kmemdup() beyond the skb boundary.
>
>Fix this by rejecting Gap ACK parsing unless the logical STATE payload is large
>enough to cover the fixed header, and by rejecting declared Gap ACK lengths
>that are smaller than the fixed header or larger than the logical payload.
>Return 0 for invalid lengths so callers do not treat malformed Gap ACK data as
>monitor payload offset, while preserving the declared size for valid but unused
>Gap ACK records. This keeps malformed Gap ACK data ignored without
>misaligning monitor payload parsing in unicast STATE messages.
>
>Fixes: d7626b5acff9 ("tipc: introduce Gap ACK blocks for broadcast link")
>Reported-by: Yifan Wu <yifanwucs@gmail.com>
>Reported-by: Juefei Pu <tomapufckgml@gmail.com>
>Co-developed-by: Yuan Tan <yuantan098@gmail.com>
>Signed-off-by: Yuan Tan <yuantan098@gmail.com>
>Suggested-by: Xin Liu <bird@lzu.edu.cn>
>Tested-by: Ren Wei <enjou1224z@gmail.com>
>Signed-off-by: Ruide Cao <caoruide123@gmail.com>
>Signed-off-by: Ren Wei <n05ec@lzu.edu.cn>
>---
> net/tipc/link.c | 12 +++++++++++-
> 1 file changed, 11 insertions(+), 1 deletion(-)
>
>diff --git a/net/tipc/link.c b/net/tipc/link.c index 49dfc098d89b..a364822c1cd8
>100644
>--- a/net/tipc/link.c
>+++ b/net/tipc/link.c
>@@ -1415,12 +1415,20 @@ u16 tipc_get_gap_ack_blks(struct
>tipc_gap_ack_blks **ga, struct tipc_link *l,
> struct tipc_msg *hdr, bool uc)
> {
> struct tipc_gap_ack_blks *p;
>- u16 sz = 0;
>+ u16 sz = 0, dlen = msg_data_sz(hdr);
>
> /* Does peer support the Gap ACK blocks feature? */
> if (l->peer_caps & TIPC_GAP_ACK_BLOCK) {
>+ if (dlen < sizeof(*p))
Your patch is wrong. sizeof(*p) is equivalent to sizeof(NULL) which is 4 bytes. I guess you did not really test this code to see if it is ever hit.
>+ goto ignore;
>+
> p = (struct tipc_gap_ack_blks *)msg_data(hdr);
> sz = ntohs(p->len);
>+ if (sz < sizeof(*p) || sz > dlen) {
sizeof(*p) cannot be used because tipc_gap_ack_blks contains flexible array. struct_size() must be used in stead.
The above 2 comparisons are redundant because they are already done in below "Sanity check" and in tipc_link_proto_rcv():
tipc_link_proto_rcv()
{
...
/* Validate Gap ACK blocks, drop if invalid */
glen = tipc_get_gap_ack_blks(&ga, l, hdr, true);
if (glen > dlen)
break;
...
}
>+ sz = 0;
>+ goto ignore;
>+ }
>+
> /* Sanity check */
> if (sz == struct_size(p, gacks, size_add(p->ugack_cnt, p-
>>bgack_cnt))) {
> /* Good, check if the desired type exists */ @@ -
>1434,6 +1442,8 @@ u16 tipc_get_gap_ack_blks(struct tipc_gap_ack_blks **ga,
>struct tipc_link *l,
> }
> }
> }
>+
>+ignore:
> /* Other cases: ignore! */
> p = NULL;
>
>--
>2.34.1
>
^ permalink raw reply
* Re: [PATCH v2] stmmac: cleanup dead dependencies on STMMAC_PLATFORM and STMMAC_ETH in Kconfig
From: Geert Uytterhoeven @ 2026-04-06 8:23 UTC (permalink / raw)
To: Julian Braha
Cc: davem, peppe.cavallaro, alexandre.torgue, mcoquelin.stm32, linux,
kuba, netdev, linux-arm-kernel, linux-kernel,
Russell King (Oracle)
In-Reply-To: <20260402145858.240231-1-julianbraha@gmail.com>
Hi Julian,
On Thu, 2 Apr 2026 at 17:07, Julian Braha <julianbraha@gmail.com> wrote:
> There are already 'if STMMAC_ETH' and 'STMMAC_PLATFORM'
> conditions wrapping these config options, making the
> 'depends on' statements duplicate dependencies (dead code).
>
> I propose leaving the outer 'if STMMAC_PLATFORM...endif' and
> 'if STMMAC_ETH...endif' conditions, and removing the
> individual 'depends on' statements.
>
> This dead code was found by kconfirm, a static analysis tool for Kconfig.
>
> Signed-off-by: Julian Braha <julianbraha@gmail.com>
> Reviewed-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
> ---
> v2: add back default STMMAC_PLATFORM for DWMAC_GENERIC
> Link to v1: https://lore.kernel.org/all/20260331125817.117091-1-julianbraha@gmail.com/
Thanks for your patch, which is now commit e2f152c822cf5d37 ("stmmac:
cleanup dead dependencies on STMMAC_PLATFORM and STMMAC_ETH in
Kconfig") in net-next.
> --- a/drivers/net/ethernet/stmicro/stmmac/Kconfig
> +++ b/drivers/net/ethernet/stmicro/stmmac/Kconfig
> @@ -20,7 +20,6 @@ if STMMAC_ETH
> config STMMAC_SELFTESTS
> bool "Support for STMMAC Selftests"
> depends on INET
> - depends on STMMAC_ETH
> default n
> help
> This adds support for STMMAC Selftests using ethtool. Enable this
> @@ -29,7 +28,6 @@ config STMMAC_SELFTESTS
>
> config STMMAC_PLATFORM
> tristate "STMMAC Platform bus support"
> - depends on STMMAC_ETH
> select MFD_SYSCON
> default y
This now lets us have STMMAC_PLATFORM=y and STMMAC_ETH=m.
Does that actually link?
> help
Gr{oetje,eeting}s,
Geert
--
Geert Uytterhoeven -- There's lots of Linux beyond ia32 -- geert@linux-m68k.org
In personal conversations with technical people, I call myself a hacker. But
when I'm talking to journalists I just say "programmer" or something like that.
-- Linus Torvalds
^ permalink raw reply
* I need your help
From: Jisun Lee @ 2026-04-06 8:08 UTC (permalink / raw)
To: netdev
Dear Beloved
My ex Husband Song Lee refuse to pay our divorce settlement
funds. I need your help to recover Assets from the bank for
healthcare and child support
purposes, diagnosed with (bladder cancer).
I need your advise and support
Thank You
Jisun Lee
^ permalink raw reply
* [PATCH net] net: stmmac: dwmac-motorcomm: fix eFUSE MAC address read failure
From: Johan Alvarado @ 2026-04-06 7:44 UTC (permalink / raw)
To: me, davem, kuba, pabeni, edumazet
Cc: ggo, andrew+netdev, netdev, linux-kernel
In-Reply-To: <24cfefff-1233-4745-8c47-812b502d5d19@tuxedocomputers.com>
This patch fixes an issue where reading the MAC address from the eFUSE
fails due to a race condition.
The root cause was identified by comparing the driver's behavior with a
custom U-Boot port. In U-Boot, the MAC address was read successfully
every time because the driver was loaded later in the boot process, giving
the hardware ample time to initialize. In Linux, reading the eFUSE
immediately returns all zeros, resulting in a fallback to a random MAC address.
Hardware cold-boot testing revealed that the eFUSE controller requires a
short settling time to load its internal data. Adding a 2000-5000us
delay after the reset ensures the hardware is fully ready, allowing the
native MAC address to be read consistently.
Fixes: 02ff155ea281 ("net: stmmac: Add glue driver for Motorcomm YT6801 ethernet controller")
Reported-by: Georg Gottleuber <ggo@tuxedocomputers.com>
Closes: https://lore.kernel.org/all/24cfefff-1233-4745-8c47-812b502d5d19@tuxedocomputers.com/
Signed-off-by: Johan Alvarado <contact@c127.dev>
---
drivers/net/ethernet/stmicro/stmmac/dwmac-motorcomm.c | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac-motorcomm.c b/drivers/net/ethernet/stmicro/stmmac/dwmac-motorcomm.c
index 8b45b9cf7202..663d87ccfa0f 100644
--- a/drivers/net/ethernet/stmicro/stmmac/dwmac-motorcomm.c
+++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-motorcomm.c
@@ -6,6 +6,7 @@
*/
#include <linux/bits.h>
+#include <linux/delay.h>
#include <linux/dev_printk.h>
#include <linux/io.h>
#include <linux/iopoll.h>
@@ -334,6 +335,13 @@ static int motorcomm_probe(struct pci_dev *pdev, const struct pci_device_id *id)
motorcomm_reset(priv);
+ /*
+ * After system reset, the eFuse controller needs time to load
+ * its internal data. Without this delay, eFuse reads return
+ * all zeros, causing MAC address detection to fail.
+ */
+ usleep_range(2000, 5000);
+
ret = motorcomm_efuse_read_mac(&pdev->dev, priv, res.mac);
if (ret == -ENOENT) {
dev_warn(&pdev->dev, "eFuse contains no valid MAC address\n");
--
2.53.0
^ permalink raw reply related
* [PATCH] MAINTAINERS: Add Prashanth as additional maintainer for amd-xgbe driver
From: Raju Rangoju @ 2026-04-06 7:38 UTC (permalink / raw)
To: linux-kernel
Cc: pabeni, kuba, edumazet, davem, andrew+netdev, netdev,
Raju Rangoju, Prashanth Kumar K R
Add Prashanth as an additional maintainer for the amd-xgbe Ethernet
driver to help with ongoing development and maintenance.
Cc: Prashanth Kumar K R <PrashanthKumar.K.R@amd.com>
Signed-off-by: Raju Rangoju <Raju.Rangoju@amd.com>
---
MAINTAINERS | 1 +
1 file changed, 1 insertion(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index 4601903a8f9b..dc935838e516 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1291,6 +1291,7 @@ F: include/uapi/drm/amdxdna_accel.h
AMD XGBE DRIVER
M: Raju Rangoju <Raju.Rangoju@amd.com>
+M: Prashanth Kumar K R <PrashanthKumar.K.R@amd.com>
L: netdev@vger.kernel.org
S: Maintained
F: arch/arm64/boot/dts/amd/amd-seattle-xgbe*.dtsi
--
2.34.1
^ permalink raw reply related
* [PATCH net-next v2] selftests/drivers/net: Add an xdp test to xdp.py
From: Leon Hwang @ 2026-04-06 7:26 UTC (permalink / raw)
To: netdev
Cc: Andrew Lunn, David S . Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Shuah Khan, Alexei Starovoitov, Daniel Borkmann,
Jesper Dangaard Brouer, John Fastabend, Stanislav Fomichev,
linux-kselftest, linux-kernel, bpf, Leon Hwang, Leon Hwang
In "bpf: Disallow freplace on XDP with mismatched xdp_has_frags values" [1],
this XDP test is suggested to add to xdp.py.
1. Verify the failure of updating frag-capable prog with non-frag-capable
prog, when the frag-capable prog attaches to mtu=9k driver.
The test has been verified against Mellanox CX6 and Intel 82599ES NICs.
With dropping other tests, here is the test log.
# ethtool -i eth0
driver: mlx5_core
version: 6.19.0-061900-generic
# NETIF=eth0 python3 xdp.py
TAP version 13
1..1
ok 1 xdp.test_xdp_native_update_mb_to_sb
# Totals: pass:1 fail:0 xfail:0 xpass:0 skip:0 error:0
# ethtool -i eth0
driver: ixgbe
version: 6.19.0-061900-generic
# NETIF=eth0 python3 xdp.py
TAP version 13
1..1
# CMD: ip link set dev eth0 xdpdrv obj /path/to/tools/testing/selftests/net/lib/xdp_dummy.bpf.o sec xdp.frags
# EXIT: 2
# STDERR: RTNETLINK answers: Invalid argument
ok 1 xdp.test_xdp_native_update_mb_to_sb # SKIP device does not support multi-buffer XDP
# Totals: pass:0 fail:0 xfail:0 xpass:0 skip:1 error:0
Links:
[1] https://lore.kernel.org/bpf/20260326124205.1a3bb825@kernel.org/
Cc: Leon Hwang <leon.hwang@linux.dev>
Signed-off-by: Leon Hwang <leon.huangfu@shopee.com>
---
v1 -> v2:
- Update the category in subject from selftests/net to selftests/drivers/net.
- Drop _exec_cmd helper.
- Recover the original MTU using cfg.dev['mtu'] (per Kuba).
- Drop the unnecessary shell=True arg (per Kuba).
- Print error using ksft_pr (per Kuba).
- Drop the sb_to_mb case (per Kuba).
- Skip test when device supports sb XDP with mtu 9000 (per Kuba,Gemini/sashiko).
v1: https://lore.kernel.org/netdev/20260401052746.314667-1-leon.huangfu@shopee.com/
---
tools/testing/selftests/drivers/net/xdp.py | 28 +++++++++++++++++++++-
1 file changed, 27 insertions(+), 1 deletion(-)
diff --git a/tools/testing/selftests/drivers/net/xdp.py b/tools/testing/selftests/drivers/net/xdp.py
index d86446569f89..864ca4097b87 100755
--- a/tools/testing/selftests/drivers/net/xdp.py
+++ b/tools/testing/selftests/drivers/net/xdp.py
@@ -13,7 +13,7 @@ from enum import Enum
from lib.py import ksft_run, ksft_exit, ksft_eq, ksft_ge, ksft_ne, ksft_pr
from lib.py import KsftNamedVariant, ksft_variants
-from lib.py import KsftFailEx, NetDrvEpEnv
+from lib.py import KsftFailEx, KsftSkipEx, NetDrvEpEnv
from lib.py import EthtoolFamily, NetdevFamily, NlError
from lib.py import bkg, cmd, rand_port, wait_port_listen
from lib.py import ip, defer
@@ -693,6 +693,31 @@ def test_xdp_native_qstats(cfg, act):
ksft_ge(after['tx-packets'], before['tx-packets'])
+def test_xdp_native_update_mb_to_sb(cfg):
+ obj = cfg.net_lib_dir / "xdp_dummy.bpf.o"
+ mtu = 9000
+
+ ip(f"link set dev {cfg.ifname} mtu {mtu}")
+ defer(ip, f"link set dev {cfg.ifname} mtu {cfg.dev['mtu']} xdpdrv off")
+
+ attach = cmd(f"ip link set dev {cfg.ifname} xdpdrv obj {obj} sec xdp", fail=False)
+ if attach.ret == 0:
+ raise KsftSkipEx(f"device supports single-buffer XDP with mtu {mtu}")
+
+ attach = cmd(f"ip link set dev {cfg.ifname} xdpdrv obj {obj} sec xdp.frags", fail=False)
+ if attach.ret != 0:
+ ksft_pr(attach)
+ raise KsftSkipEx("device does not support multi-buffer XDP")
+
+ # Verify updating mb -> mb program works.
+ cmd(f"ip -force link set dev {cfg.ifname} xdpdrv obj {obj} sec xdp.frags")
+
+ # Verify updating mb -> sb program does not work.
+ update = cmd(f"ip -force link set dev {cfg.ifname} xdpdrv obj {obj} sec xdp", fail=False)
+ if update.ret == 0:
+ raise KsftFailEx("device unexpectedly updates non-multi-buffer XDP")
+
+
def main():
"""
Main function to execute the XDP tests.
@@ -718,6 +743,7 @@ def main():
test_xdp_native_adjst_head_grow_data,
test_xdp_native_adjst_head_shrnk_data,
test_xdp_native_qstats,
+ test_xdp_native_update_mb_to_sb,
],
args=(cfg,))
ksft_exit()
--
2.53.0
^ permalink raw reply related
* [PATCH net-next 3/3] net: phy: realtek: convert RTL8211F to .disable_autonomous_eee
From: Nicolai Buchwitz @ 2026-04-06 7:13 UTC (permalink / raw)
To: Andrew Lunn, Heiner Kallweit, Russell King, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Florian Fainelli,
Broadcom internal kernel review list
Cc: netdev, linux-kernel, Nicolai Buchwitz
In-Reply-To: <20260406-devel-autonomous-eee-v1-0-b335e7143711@tipi-net.de>
The RTL8211F previously unconditionally disabled PHY-mode EEE in
config_init. Convert this to use the new .disable_autonomous_eee
callback so it is only disabled when the MAC indicates EEE support
via phy_support_eee().
This preserves PHY-autonomous EEE for MACs that do not support EEE,
while still disabling it when the MAC manages LPI.
Signed-off-by: Nicolai Buchwitz <nb@tipi-net.de>
---
drivers/net/phy/realtek/realtek_main.c | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/drivers/net/phy/realtek/realtek_main.c b/drivers/net/phy/realtek/realtek_main.c
index 023e47ad605bde57287d3da8b80c47d97d898b96..b7c96b020baf6f2ae8c5fe71d1fc3cbf7c6e2bc7 100644
--- a/drivers/net/phy/realtek/realtek_main.c
+++ b/drivers/net/phy/realtek/realtek_main.c
@@ -700,9 +700,8 @@ static int rtl8211f_config_aldps(struct phy_device *phydev)
return phy_modify(phydev, RTL8211F_PHYCR1, mask, mask);
}
-static int rtl8211f_config_phy_eee(struct phy_device *phydev)
+static int rtl8211f_disable_autonomous_eee(struct phy_device *phydev)
{
- /* Disable PHY-mode EEE so LPI is passed to the MAC */
return phy_modify(phydev, RTL8211F_PHYCR2,
RTL8211F_PHYCR2_PHY_EEE_ENABLE, 0);
}
@@ -730,7 +729,7 @@ static int rtl8211f_config_init(struct phy_device *phydev)
return ret;
}
- return rtl8211f_config_phy_eee(phydev);
+ return 0;
}
static int rtl821x_suspend(struct phy_device *phydev)
@@ -2324,6 +2323,7 @@ static struct phy_driver realtek_drvs[] = {
.led_hw_is_supported = rtl8211x_led_hw_is_supported,
.led_hw_control_get = rtl8211f_led_hw_control_get,
.led_hw_control_set = rtl8211f_led_hw_control_set,
+ .disable_autonomous_eee = rtl8211f_disable_autonomous_eee,
}, {
PHY_ID_MATCH_EXACT(RTL_8211FVD_PHYID),
.name = "RTL8211F-VD Gigabit Ethernet",
@@ -2340,6 +2340,7 @@ static struct phy_driver realtek_drvs[] = {
.led_hw_is_supported = rtl8211x_led_hw_is_supported,
.led_hw_control_get = rtl8211f_led_hw_control_get,
.led_hw_control_set = rtl8211f_led_hw_control_set,
+ .disable_autonomous_eee = rtl8211f_disable_autonomous_eee,
}, {
.name = "Generic FE-GE Realtek PHY",
.match_phy_device = rtlgen_match_phy_device,
--
2.51.0
^ permalink raw reply related
* [PATCH net-next 1/3] net: phy: add support for disabling PHY-autonomous EEE
From: Nicolai Buchwitz @ 2026-04-06 7:13 UTC (permalink / raw)
To: Andrew Lunn, Heiner Kallweit, Russell King, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Florian Fainelli,
Broadcom internal kernel review list
Cc: netdev, linux-kernel, Nicolai Buchwitz
In-Reply-To: <20260406-devel-autonomous-eee-v1-0-b335e7143711@tipi-net.de>
Some PHYs (e.g. Broadcom BCM54xx, Realtek RTL8211F) implement
autonomous EEE where the PHY manages LPI signaling without forwarding
it to the MAC. This conflicts with MAC drivers that implement their own
LPI control.
Add a .disable_autonomous_eee callback to struct phy_driver and call it
from phy_support_eee(). When a MAC driver indicates it supports EEE via
phy_support_eee(), the PHY's autonomous EEE is automatically disabled so
the MAC can manage LPI entry/exit.
Signed-off-by: Nicolai Buchwitz <nb@tipi-net.de>
---
drivers/net/phy/phy_device.c | 22 ++++++++++++++++++++++
include/linux/phy.h | 14 ++++++++++++++
2 files changed, 36 insertions(+)
diff --git a/drivers/net/phy/phy_device.c b/drivers/net/phy/phy_device.c
index 0edff47478c20dd8537d4b94806be13b2409c711..cda4abf4e68cddb25b4ac69c35c8e095fac3e31c 100644
--- a/drivers/net/phy/phy_device.c
+++ b/drivers/net/phy/phy_device.c
@@ -1375,6 +1375,14 @@ int phy_init_hw(struct phy_device *phydev)
return ret;
}
+ /* Re-apply autonomous EEE disable after soft reset */
+ if (phydev->autonomous_eee_disabled &&
+ phydev->drv->disable_autonomous_eee) {
+ ret = phydev->drv->disable_autonomous_eee(phydev);
+ if (ret)
+ return ret;
+ }
+
return 0;
}
EXPORT_SYMBOL(phy_init_hw);
@@ -2898,6 +2906,20 @@ void phy_support_eee(struct phy_device *phydev)
linkmode_copy(phydev->advertising_eee, phydev->supported_eee);
phydev->eee_cfg.tx_lpi_enabled = true;
phydev->eee_cfg.eee_enabled = true;
+
+ /* If the PHY supports autonomous EEE, disable it so the MAC can
+ * manage LPI signaling instead. The flag is stored so it can be
+ * re-applied after a PHY soft reset (e.g. suspend/resume).
+ */
+ if (phydev->drv && phydev->drv->disable_autonomous_eee) {
+ int ret = phydev->drv->disable_autonomous_eee(phydev);
+
+ if (ret)
+ phydev_warn(phydev, "Failed to disable autonomous EEE: %pe\n",
+ ERR_PTR(ret));
+ else
+ phydev->autonomous_eee_disabled = true;
+ }
}
EXPORT_SYMBOL(phy_support_eee);
diff --git a/include/linux/phy.h b/include/linux/phy.h
index 5de4b172cd0ba0048039d73d049b0ff24e8b2e44..199a7aaa341bfd9b13731e996a8de7e1aea586ff 100644
--- a/include/linux/phy.h
+++ b/include/linux/phy.h
@@ -612,6 +612,8 @@ struct phy_oatc14_sqi_capability {
* @advertising_eee: Currently advertised EEE linkmodes
* @enable_tx_lpi: When True, MAC should transmit LPI to PHY
* @eee_active: phylib private state, indicating that EEE has been negotiated
+ * @autonomous_eee_disabled: Set when autonomous EEE has been disabled,
+ * used to re-apply after PHY soft reset
* @eee_cfg: User configuration of EEE
* @lp_advertising: Current link partner advertised linkmodes
* @host_interfaces: PHY interface modes supported by host
@@ -739,6 +741,7 @@ struct phy_device {
__ETHTOOL_DECLARE_LINK_MODE_MASK(eee_disabled_modes);
bool enable_tx_lpi;
bool eee_active;
+ bool autonomous_eee_disabled;
struct eee_config eee_cfg;
/* Host supported PHY interface types. Should be ignored if empty. */
@@ -1359,6 +1362,17 @@ struct phy_driver {
void (*get_stats)(struct phy_device *dev,
struct ethtool_stats *stats, u64 *data);
+ /**
+ * @disable_autonomous_eee: Disable PHY-autonomous EEE
+ *
+ * Some PHYs manage EEE autonomously, preventing the MAC from
+ * controlling LPI signaling. This callback disables autonomous
+ * EEE at the PHY.
+ *
+ * Return: 0 on success, negative errno on failure.
+ */
+ int (*disable_autonomous_eee)(struct phy_device *dev);
+
/* Get and Set PHY tunables */
/** @get_tunable: Return the value of a tunable */
int (*get_tunable)(struct phy_device *dev,
--
2.51.0
^ permalink raw reply related
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