* Re: [PATCH net-next v3 01/15] net: macb: drop "consistent" from alloc/free function names
From: Théo Lebrun @ 2026-07-03 16:32 UTC (permalink / raw)
To: Conor Dooley
Cc: Conor Dooley, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Richard Cochran, Russell King,
netdev, linux-kernel, Nicolas Ferre, Claudiu Beznea,
Paolo Valerio, Nicolai Buchwitz, Vladimir Kondratiev,
Gregory CLEMENT, Benoît Monin, Tawfik Bayouk,
Thomas Petazzoni, Maxime Chevallier
In-Reply-To: <20260703-stucco-broadly-d9393f789868@spud>
Hello Conor,
On Fri Jul 3, 2026 at 1:28 PM CEST, Conor Dooley wrote:
> On Wed, Jul 01, 2026 at 05:59:04PM +0200, Théo Lebrun wrote:
>> Since commit 4df95131ea80 ("net/macb: change RX path for GEM") those
>> functions have not been only allocating or freeing consistent memory
>> mappings.
>>
>> Rename from macb_alloc_consistent() to macb_alloc() and
>> from macb_free_consistent() to macb_free().
>
> What does "consistent" even mean? Is it intended to be analogous to
> coherent?
That's my guess. We used to have pci_alloc_consistent(), being an alias
to dma_alloc_coherent().
This alias has been present since forever (since Git):
git show 1da177e4c3f4:include/asm-generic/pci-dma-compat.h
The alias got removed in commit 7968778914e5 ("PCI: Remove the
deprecated "pci-dma-compat.h" API", 2022-03-09).
Thanks,
--
Théo Lebrun, Bootlin
Embedded Linux and Kernel engineering
https://bootlin.com
^ permalink raw reply
* Re: [PATCH net v2] ppp: defer channel free to an RCU grace period to fix pppol2tp RX UAF
From: Breno Leitao @ 2026-07-03 16:32 UTC (permalink / raw)
To: Qingfang Deng
Cc: Norbert Szetei, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Sebastian Andrzej Siewior, Taegu Ha,
Kees Cook, linux-ppp, linux-kernel, Guillaume Nault, netdev
In-Reply-To: <de2616b3-6edf-4255-ba77-0674e225ab27@linux.dev>
On Fri, Jul 03, 2026 at 03:27:00PM +0800, Qingfang Deng wrote:
> Hi,
>
> On 2026/7/2 2:12, Norbert Szetei wrote:
> > +/* Purge after the grace period: a late ppp_input() may still queue an
> > + * skb on pch->file.rq before the last RCU reader drains.
> > + */
> > +static void ppp_release_channel_free(struct rcu_head *rcu)
> > +{
> > + struct channel *pch = container_of(rcu, struct channel, rcu);
> > +
> > + skb_queue_purge(&pch->file.xq);
> > + skb_queue_purge(&pch->file.rq);
> > + kfree(pch);
> > +}
> > +
> > /*
> > * Drop a reference to a ppp channel and free its memory if the refcount reaches
> > * zero.
> > @@ -3581,9 +3594,7 @@ static void ppp_release_channel(struct channel *pch)
> > pr_err("ppp: destroying undead channel %p !\n", pch);
> > return;
> > }
> > - skb_queue_purge(&pch->file.xq);
> > - skb_queue_purge(&pch->file.rq);
> > - kfree(pch);
> > + call_rcu(&pch->rcu, ppp_release_channel_free);
> > }
> > static void __exit ppp_cleanup(void)
>
> AI-review found an issue: https://sashiko.dev/#/patchset/D9C0245B-608B-4884-8A09-F55BA4A9F948%40doyensec.com
>
> An rcu_barrier() call is needed at the end of ppp_cleanup().
I was initially unclear why rcu_barrier() would be necessary on a kfree path,
but it appears to be required during module unload to ensure that
ppp_release_channel_free() completes before the module's struct rcu_head is
destroyed. Is that the correct understanding?
^ permalink raw reply
* Re: [Intel-wired-lan] [PATCH iwl-net v3 1/2] ice: skip per-VLAN promisc rules when default VSI Rx rule is set
From: Marcin Szycik @ 2026-07-03 16:34 UTC (permalink / raw)
To: Petr Oros, netdev
Cc: Ivan Vecera, Alice Michael, Przemek Kitszel, Eric Dumazet,
linux-kernel, Martyna Szapar-Mudlaw, Andrew Lunn, Tony Nguyen,
Simon Horman, intel-wired-lan, Jacob Keller, Jakub Kicinski,
Paolo Abeni, David S. Miller
In-Reply-To: <20260701133601.2118382-2-poros@redhat.com>
On 01.07.2026 15:36, Petr Oros wrote:
> When an ice port in a vlan-filtering bridge goes promiscuous (typical for
> bond slaves), the driver installs a per-VLAN ICE_SW_LKUP_PROMISC_VLAN rule
> for every VID on top of the broad ICE_SW_LKUP_DFLT VSI Rx rule. Each rule
> consumes one of the ~32K Flow Lookup Unit (FLU) entries the device shares
> across PFs, so a wide trunk (vid 2-4094) over several PFs overruns the
> pool: firmware rejects further Add Switch Rules with ENOSPC (AQ 0x10) and
> the DFLT Rx rule itself fails to install:
>
> ice 0000:5c:00.1: Failed to set VSI 14 as the default forwarding
> VSI, error -5
> ice 0000:5c:00.1 ens1f1: Error -5 setting default VSI 14 Rx rule
>
> Once a switch context is overrun the retries can also come back as ENOENT
> (AQ 0x2), which has misled triage toward a perceived recipe binding defect
> rather than a capacity issue.
>
> The DFLT rule already catches every packet on the port regardless of VLAN
> tag, so the per-VLAN promisc expansion is redundant while it is installed.
> Skip it at the two sites that drive it, ice_set_promisc() and
> ice_vlan_rx_add_vid(), keyed on ice_is_vsi_dflt_vsi() rather than the
> netdev IFF_PROMISC flag so a failed or LAG-suppressed DFLT install still
> falls back to the per-VLAN rules.
>
> IFF_ALLMULTI and IFF_PROMISC can reach ice_vsi_sync_fltr() in separate
> passes (a bridge join sets them through separate calls), so the allmulti
> pass may expand the per-VID rules before the DFLT rule exists. Drop those
> now-redundant rules right after ice_set_dflt_vsi() installs the DFLT rule;
> ice_vsi_exit_dflt_promisc() reinstates them when promisc is cleared.
>
> ice_vsi_sync_fltr() subscribed multicast promiscuity only inside the
> "default VSI not yet in use" branch, so a promiscuous VSI that finds the
> default VSI rule already present (owned by another VSI, or preserved
> across a switchdev session) ended up in unicast promisc with no multicast
> subscription. Issue ice_set_promisc(ICE_MCAST_PROMISC_BITS) whenever the
> netdev is promiscuous; it is idempotent and returns 0 if the rule is
> already present.
>
> Fixes: 1273f89578f2 ("ice: Fix broken IFF_ALLMULTI handling")
> Signed-off-by: Petr Oros <poros@redhat.com>
> ---
> v3:
> - Dropped the two vid=0 ICE_SW_LKUP_PROMISC <-> ICE_SW_LKUP_PROMISC_VLAN
> recipe-swap guards in ice_vlan_rx_add_vid() and ice_vlan_rx_kill_vid();
> each swap is net-zero and guarding the demote stranded the vid=0 rule
> in ICE_SW_LKUP_PROMISC_VLAN when the last VLAN was removed under the
> DFLT rule. Reported by review.
> - Drop the now-redundant per-VID multicast promisc rules right after
> ice_set_dflt_vsi(). A bridge join raises IFF_ALLMULTI and IFF_PROMISC
> in separate sync passes, so the allmulti pass expands the per-VID rules
> before the DFLT rule exists; the cleanup keeps them from lingering and
> exhausting the FLU pool. ice_vsi_exit_dflt_promisc() reinstates them on
> promisc off. Reported by review.
> - Issue ice_set_promisc(ICE_MCAST_PROMISC_BITS) whenever the netdev is
> promiscuous, not only when this VSI installs the default VSI rule, so
> multicast promisc is not lost when the rule is already in use (owned by
> another VSI, or preserved across a switchdev session). Reported by
> review.
> - Hoisted the combined VLAN promisc mask in ice_clear_promisc() into a
> local for alignment. Dropped Aleksandr's Reviewed-by since the code
> changed.
>
> v2: https://lore.kernel.org/all/20260622113428.2565255-2-poros@redhat.com/
> v1: https://lore.kernel.org/all/89efbea9831175e6f57e9fe8557f7a0e48e050b7.1781786935.git.poros@redhat.com/
> ---
> drivers/net/ethernet/intel/ice/ice_main.c | 111 ++++++++++++++++++----
> 1 file changed, 90 insertions(+), 21 deletions(-)
>
> diff --git a/drivers/net/ethernet/intel/ice/ice_main.c b/drivers/net/ethernet/intel/ice/ice_main.c
> index b43d420ece99ca..a84de6cf6eb078 100644
> --- a/drivers/net/ethernet/intel/ice/ice_main.c
> +++ b/drivers/net/ethernet/intel/ice/ice_main.c
> @@ -274,7 +274,8 @@ static int ice_set_promisc(struct ice_vsi *vsi, u8 promisc_m)
> if (vsi->type != ICE_VSI_PF)
> return 0;
>
> - if (ice_vsi_has_non_zero_vlans(vsi)) {
> + /* skip per-VID expansion; the DFLT Rx rule already covers every VID */
> + if (ice_vsi_has_non_zero_vlans(vsi) && !ice_is_vsi_dflt_vsi(vsi)) {
> promisc_m |= (ICE_PROMISC_VLAN_RX | ICE_PROMISC_VLAN_TX);
> status = ice_fltr_set_vlan_vsi_promisc(&vsi->back->hw, vsi,
> promisc_m);
> @@ -304,9 +305,20 @@ static int ice_clear_promisc(struct ice_vsi *vsi, u8 promisc_m)
> return 0;
>
> if (ice_vsi_has_non_zero_vlans(vsi)) {
> - promisc_m |= (ICE_PROMISC_VLAN_RX | ICE_PROMISC_VLAN_TX);
> + u8 vlan_promisc_m = promisc_m | ICE_PROMISC_VLAN_RX |
> + ICE_PROMISC_VLAN_TX;
> + int vid0_status;
> +
> + /* set time used either recipe (per-VID PROMISC_VLAN, or vid=0
I find this sentence hard to understand - did you mean "ice_set_promisc() used
either recipe..."?
> + * PROMISC via the ice_set_promisc() else branch), so clear
> + * both; clearing an absent rule succeeds
What do you mean by this? Both will return -EEXIST if rule is absent. There can
also be other errors.
> + */
> status = ice_fltr_clear_vlan_vsi_promisc(&vsi->back->hw, vsi,
> - promisc_m);
> + vlan_promisc_m);
> + vid0_status = ice_fltr_clear_vsi_promisc(&vsi->back->hw,
> + vsi->idx, promisc_m, 0);
> + if (status == 0)
> + status = vid0_status;
> } else {
> status = ice_fltr_clear_vsi_promisc(&vsi->back->hw, vsi->idx,
> promisc_m, 0);
> @@ -317,6 +329,59 @@ static int ice_clear_promisc(struct ice_vsi *vsi, u8 promisc_m)
> return status;
> }
>
> +/**
> + * ice_vsi_exit_dflt_promisc - drop the default VSI Rx rule on promisc off
> + * @vsi: the VSI leaving promiscuous mode
> + *
> + * For an IFF_ALLMULTI VSI with VLANs the per-VID multicast rules are
> + * reinstated before the default rule is cleared so coverage never lapses;
> + * the then redundant vid=0 rule is dropped best-effort. The callees log
> + * their own failures, so error returns are not re-logged here.
> + *
> + * Return: 0 on success, negative on error with the default rule left in place.
> + */
> +static int ice_vsi_exit_dflt_promisc(struct ice_vsi *vsi)
> +{
> + struct ice_vsi_vlan_ops *vlan_ops = ice_get_compat_vsi_vlan_ops(vsi);
> + struct net_device *netdev = vsi->netdev;
> + struct ice_hw *hw = &vsi->back->hw;
> + bool restore_mc;
> + int err;
> +
> + restore_mc = (vsi->current_netdev_flags & IFF_ALLMULTI) &&
> + ice_vsi_has_non_zero_vlans(vsi);
> +
> + if (restore_mc) {
> + err = ice_fltr_set_vlan_vsi_promisc(hw, vsi,
> + ICE_MCAST_VLAN_PROMISC_BITS);
> + if (err && err != -EEXIST)
> + return err;
> + }
> +
> + err = ice_clear_dflt_vsi(vsi);
> + if (err)
> + return err;
> +
> + if (netdev->features & NETIF_F_HW_VLAN_CTAG_FILTER)
> + vlan_ops->ena_rx_filtering(vsi);
> +
> + if (restore_mc)
> + ice_fltr_clear_vsi_promisc(hw, vsi->idx, ICE_MCAST_PROMISC_BITS,
> + 0);
> +
> + return 0;
> +}
> +
> +/* Drop the per-VID multicast promisc rules, redundant once the default
> + * VSI Rx rule covers every VID. A no-op when the VSI has no VLANs.
> + */
> +static void ice_vsi_clear_vlan_mc_promisc(struct ice_vsi *vsi)
> +{
> + if (ice_vsi_has_non_zero_vlans(vsi))
Nit: could flip condition to decrease indent level.
> + ice_fltr_clear_vlan_vsi_promisc(&vsi->back->hw, vsi,
> + ICE_MCAST_VLAN_PROMISC_BITS);
Error code ignored, not sure if intentionally.
> +}
> +
> /**
> * ice_vsi_sync_fltr - Update the VSI filter list to the HW
> * @vsi: ptr to the VSI
> @@ -429,30 +494,35 @@ static int ice_vsi_sync_fltr(struct ice_vsi *vsi)
> err = 0;
> vlan_ops->dis_rx_filtering(vsi);
>
> - /* promiscuous mode implies allmulticast so
> - * that VSIs that are in promiscuous mode are
> - * subscribed to multicast packets coming to
> - * the port
> + /* DFLT now covers every VID; drop the per-VID
> + * multicast promisc rules a prior IFF_ALLMULTI
> + * pass may have installed (separate passes on a
> + * bridge join) so they do not linger and exhaust
> + * the FLU pool. exit_dflt_promisc() reinstates
Please use the full function name.
> + * them on promisc off.
> */
> - err = ice_set_promisc(vsi,
> - ICE_MCAST_PROMISC_BITS);
> - if (err)
> - goto out_promisc;
> + ice_vsi_clear_vlan_mc_promisc(vsi);
> }
> +
> + /* Promiscuous mode implies allmulticast. Subscribe
> + * the VSI to all multicast even when the default VSI
> + * rule is already in use and the block above is
> + * skipped (it may be owned by another VSI, or
> + * preserved across a switchdev session); the unicast
> + * catch-all does not cover the multicast subscription.
> + */
> + err = ice_set_promisc(vsi, ICE_MCAST_PROMISC_BITS);
> + if (err)
> + goto out_promisc;
> } else {
> /* Clear Rx filter to remove traffic from wire */
> if (ice_is_vsi_dflt_vsi(vsi)) {
> - err = ice_clear_dflt_vsi(vsi);
> + err = ice_vsi_exit_dflt_promisc(vsi);
> if (err) {
> - netdev_err(netdev, "Error %d clearing default VSI %i Rx rule\n",
> - err, vsi->vsi_num);
> vsi->current_netdev_flags |=
> IFF_PROMISC;
> goto out_promisc;
> }
> - if (vsi->netdev->features &
> - NETIF_F_HW_VLAN_CTAG_FILTER)
> - vlan_ops->ena_rx_filtering(vsi);
> }
>
> /* disable allmulti here, but only if allmulti is not
> @@ -3676,10 +3746,9 @@ int ice_vlan_rx_add_vid(struct net_device *netdev, __be16 proto, u16 vid)
> while (test_and_set_bit(ICE_CFG_BUSY, vsi->state))
> usleep_range(1000, 2000);
>
> - /* Add multicast promisc rule for the VLAN ID to be added if
> - * all-multicast is currently enabled.
> - */
> - if (vsi->current_netdev_flags & IFF_ALLMULTI) {
> + /* skip the per-VID rule when the DFLT Rx rule already covers this VID */
> + if ((vsi->current_netdev_flags & IFF_ALLMULTI) &&
> + !ice_is_vsi_dflt_vsi(vsi)) {
> ret = ice_fltr_set_vsi_promisc(&vsi->back->hw, vsi->idx,
> ICE_MCAST_VLAN_PROMISC_BITS,
> vid);
Thanks,
Marcin
^ permalink raw reply
* Re: [PATCH net-next v3 01/15] net: macb: drop "consistent" from alloc/free function names
From: Conor Dooley @ 2026-07-03 16:34 UTC (permalink / raw)
To: Théo Lebrun
Cc: Conor Dooley, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Richard Cochran, Russell King,
netdev, linux-kernel, Nicolas Ferre, Claudiu Beznea,
Paolo Valerio, Nicolai Buchwitz, Vladimir Kondratiev,
Gregory CLEMENT, Benoît Monin, Tawfik Bayouk,
Thomas Petazzoni, Maxime Chevallier
In-Reply-To: <DJP2W90JJK0G.1DBR4RMJKJO2Z@bootlin.com>
[-- Attachment #1: Type: text/plain, Size: 1113 bytes --]
On Fri, Jul 03, 2026 at 06:32:08PM +0200, Théo Lebrun wrote:
> Hello Conor,
>
> On Fri Jul 3, 2026 at 1:28 PM CEST, Conor Dooley wrote:
> > On Wed, Jul 01, 2026 at 05:59:04PM +0200, Théo Lebrun wrote:
> >> Since commit 4df95131ea80 ("net/macb: change RX path for GEM") those
> >> functions have not been only allocating or freeing consistent memory
> >> mappings.
> >>
> >> Rename from macb_alloc_consistent() to macb_alloc() and
> >> from macb_free_consistent() to macb_free().
> >
> > What does "consistent" even mean? Is it intended to be analogous to
> > coherent?
>
> That's my guess. We used to have pci_alloc_consistent(), being an alias
> to dma_alloc_coherent().
>
> This alias has been present since forever (since Git):
>
> git show 1da177e4c3f4:include/asm-generic/pci-dma-compat.h
>
> The alias got removed in commit 7968778914e5 ("PCI: Remove the
> deprecated "pci-dma-compat.h" API", 2022-03-09).
Right, I did figure given the commit you cited as being the end of it
that it was something like that.
Acked-by: Conor Dooley <conor.dooley@microchip.com>
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]
^ permalink raw reply
* Re: [Intel-wired-lan] [PATCH iwl-net v3 2/2] ice: preserve uplink DFLT Rx rule on switchdev release
From: Marcin Szycik @ 2026-07-03 16:36 UTC (permalink / raw)
To: Petr Oros, netdev
Cc: Ivan Vecera, Alice Michael, Przemek Kitszel, Eric Dumazet,
linux-kernel, Martyna Szapar-Mudlaw, Andrew Lunn, Tony Nguyen,
Simon Horman, intel-wired-lan, Jacob Keller, Jakub Kicinski,
Paolo Abeni, David S. Miller
In-Reply-To: <20260701133601.2118382-3-poros@redhat.com>
On 01.07.2026 15:36, Petr Oros wrote:
> When the uplink PF is promiscuous, ice_vsi_sync_fltr() installs an
> ICE_SW_LKUP_DFLT catch-all Rx rule on the uplink VSI. Entering switchdev
> re-affirms it through the idempotent ice_set_dflt_vsi(), but
> ice_eswitch_release_env() removed both the Rx and Tx DFLT rules
> unconditionally on teardown. That clobbered a promisc-owned Rx rule: it
> disappeared while IFF_PROMISC was still set and the sync path was not
> retriggered, leaving the uplink without the catch-all the netdev
> requested.
>
> Skip the Rx DFLT removal when the uplink is promiscuous, both in
> ice_eswitch_release_env() and the err_def_tx unwind of
> ice_eswitch_setup_env(); the Tx leg, owned by switchdev, is still removed.
> Test the live netdev->flags, the same value ena_rx_filtering() ->
> ice_cfg_vlan_pruning() above already keys on, so the preserved rule and
> the pruning state stay consistent, including for a promisc change made
> while switchdev ran (which never reached the gated filter sync).
>
> Fixes: 5c07be96d8b3 ("ice: Avoid setting default Rx VSI twice in switchdev setup")
> Signed-off-by: Petr Oros <poros@redhat.com>
Reviewed-by: Marcin Szycik <marcin.szycik@linux.intel.com>
> ---
> v3:
> - Corrected the Fixes tag from 1a1c40df2e80 ("ice: set and release
> switchdev environment") to 5c07be96d8b3 ("ice: Avoid setting default
> Rx VSI twice in switchdev setup"), the commit that made
> ice_eswitch_setup_env() use the idempotent ice_set_dflt_vsi(); before
> it a pre-existing promisc DFLT rule made setup fail with -EEXIST so the
> release path was never reached. No code change.
>
> v2: https://lore.kernel.org/all/20260622113428.2565255-3-poros@redhat.com/
> v1: https://lore.kernel.org/all/deef5756e534ef06c12d910c5305d3fd205d30a0.1781786935.git.poros@redhat.com/
> ---
> drivers/net/ethernet/intel/ice/ice_eswitch.c | 18 ++++++++++++++----
> 1 file changed, 14 insertions(+), 4 deletions(-)
>
> diff --git a/drivers/net/ethernet/intel/ice/ice_eswitch.c b/drivers/net/ethernet/intel/ice/ice_eswitch.c
> index c30e27bbfe6e25..07e2016fb9481f 100644
> --- a/drivers/net/ethernet/intel/ice/ice_eswitch.c
> +++ b/drivers/net/ethernet/intel/ice/ice_eswitch.c
> @@ -66,8 +66,10 @@ static int ice_eswitch_setup_env(struct ice_pf *pf)
> ice_cfg_dflt_vsi(uplink_vsi->port_info, uplink_vsi->idx, false,
> ICE_FLTR_TX);
> err_def_tx:
> - ice_cfg_dflt_vsi(uplink_vsi->port_info, uplink_vsi->idx, false,
> - ICE_FLTR_RX);
> + /* keep the Rx DFLT rule if the uplink is promiscuous (see release_env) */
> + if (!(uplink_vsi->netdev->flags & IFF_PROMISC))
> + ice_cfg_dflt_vsi(uplink_vsi->port_info, uplink_vsi->idx,
> + false, ICE_FLTR_RX);
> err_def_rx:
> ice_vsi_del_vlan_zero(uplink_vsi);
> err_vlan_zero:
> @@ -276,8 +278,16 @@ static void ice_eswitch_release_env(struct ice_pf *pf)
> vlan_ops->ena_rx_filtering(uplink_vsi);
> ice_cfg_dflt_vsi(uplink_vsi->port_info, uplink_vsi->idx, false,
> ICE_FLTR_TX);
> - ice_cfg_dflt_vsi(uplink_vsi->port_info, uplink_vsi->idx, false,
> - ICE_FLTR_RX);
> +
> + /* Keep the Rx DFLT rule if the uplink is promiscuous; it must outlive
> + * the session. Test the live netdev->flags, the same value
> + * ena_rx_filtering() -> ice_cfg_vlan_pruning() above keys its decision
> + * on, so the preserved DFLT rule and the pruning state stay consistent.
> + */
> + if (!(uplink_vsi->netdev->flags & IFF_PROMISC))
> + ice_cfg_dflt_vsi(uplink_vsi->port_info, uplink_vsi->idx,
> + false, ICE_FLTR_RX);
> +
> ice_fltr_add_mac_and_broadcast(uplink_vsi,
> uplink_vsi->port_info->mac.perm_addr,
> ICE_FWD_TO_VSI);
^ permalink raw reply
* Re: [PATCH ipsec] xfrm: espintcp: fix UAF during close
From: Breno Leitao @ 2026-07-03 16:36 UTC (permalink / raw)
To: Sabrina Dubroca
Cc: netdev, Steffen Klassert, Herbert Xu, stable, zdi-disclosures
In-Reply-To: <50e2ab4348eb8177581058f0152394cfae6a8d27.1783071494.git.sd@queasysnail.net>
Hello Sabrina,
On Fri, Jul 03, 2026 at 04:21:12PM +0200, Sabrina Dubroca wrote:
> diff --git a/net/xfrm/espintcp.c b/net/xfrm/espintcp.c
> index 374e1b964438..f09b5dd85db8 100644
> --- a/net/xfrm/espintcp.c
> +++ b/net/xfrm/espintcp.c
> @@ -517,6 +517,8 @@ static void espintcp_close(struct sock *sk, long timeout)
> sk->sk_prot = &tcp_prot;
> barrier();
>
> + synchronize_rcu();
I've got the impression netdev usually prefers synchronize_net() instead
of synchornize_rcu(). Is there any reason for synchronize_net() not
being used here?
Also, given you have a explicit synchronize_rcu() here, should the
barrier() above be dropped?
^ permalink raw reply
* Re: [PATCH net] net: emac: mal: fix W1C write race in ICINTSTAT clearing
From: Andrew Lunn @ 2026-07-03 16:36 UTC (permalink / raw)
To: David Gibson
Cc: Rosen Penev, netdev, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Jeff Garzik, open list
In-Reply-To: <akcnQEiJYfAtPqN1@zatzit>
On Fri, Jul 03, 2026 at 01:06:40PM +1000, David Gibson wrote:
> On Thu, Jul 02, 2026 at 04:49:23PM -0700, Rosen Penev wrote:
> > The ICINTSTAT register is write-1-to-clear (W1C). The read-modify-write
> > pattern in both mal_txeob() and mal_rxeob() can lose interrupts: if a bit
> > that should not be cleared is already asserted when mfdcri() reads the
> > register, it is included in the read value, retained by the bitwise OR, and
> > then written back as 1 - inadvertently clearing a pending but unhandled
> > interrupt.
> >
> > Fix by writing only the specific bit to clear (ICINTSTAT_ICTX for TXEOB,
> > ICINTSTAT_ICRX for RXEOB). W1C semantics guarantee that writing 0 to the
> > other bits has no effect.
>
> Wow, it's a long time since I thought about the MAL.
>
> > Fixes: 1d3bb996481e ("Device tree aware EMAC driver")
>
> This doesn't appear correct. The lines in question were added by
> fbcc4bacee30c ("ibm_newemac: MAL support for PowerPC 405EZ")
>
> > Assisted-by: opencode:big-pickle
> > Signed-off-by: Rosen Penev <rosenp@gmail.com>
>
> Assuming ICINTSTAT is indeed a W1C register (or "read/clear" as I
> believe they were termed in the 405 documentation) the change looks
> correct. However, I no longer have access to the documentation that
> would let me verify that. I would absolutely not trust an LLM to know
> if that's the case, since it's a fairly arbitrary and specific detail
> of an obscure CPU.
I agree. If this is pure LLM, we need some form of verification.
Andrew
^ permalink raw reply
* Re: [PATCH net] selftests/tc-testing: Add tests that force multiq and taprio to enqueue to child's gso_skb
From: Paolo Abeni @ 2026-07-03 16:37 UTC (permalink / raw)
To: Victor Nogueira
Cc: netdev, hexlabsecurity, pctammela, davem, edumazet, kuba, jhs,
jiri
In-Reply-To: <20260630153651.249752-1-victor@mojatatu.com>
On 6/30/26 5:36 PM, Victor Nogueira wrote:
> Add test cases to reproduce scenarios fixed recently [1] where
> multiqueue and taprio forced their children into enqueueing an skb to
> gso_skb (during peek), but failed to dequeue from gso_skb because they
> called the child's dequeue callback directly. This causes a desync in the
> child's qlen/backlog and results in an eventual null-ptr-deref (with a
> qfq or dualpi2 child).
>
> Test cases are the following:
>
> - Force multiq to dequeue from its child's gso_skb with qfq leaf (fb6c)
> - Force multiq to dequeue from its child's gso_skb with dualpi2 leaf (1922)
> - Force taprio to dequeue from its child's gso_skb with qfq leaf (476f)
> - Force taprio to dequeue from its child's gso_skb with dualpi2 leaf (0235)
>
> [1] https://lore.kernel.org/netdev/20260625-b4-disp-31bcb279-v1-0-85c40b83c529@proton.me/
>
> Signed-off-by: Victor Nogueira <victor@mojatatu.com>
Since the fix has been propagated to net-next, this is net-next
material, right? no need to repost, just ack/nack, thanks!
/P
^ permalink raw reply
* Re: [PATCH net] net: emac: mal: replace devm_request_irq with request_irq to fix probe error race
From: Andrew Lunn @ 2026-07-03 16:38 UTC (permalink / raw)
To: Rosen Penev
Cc: netdev, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, open list
In-Reply-To: <20260702235045.1326384-1-rosenp@gmail.com>
On Thu, Jul 02, 2026 at 04:50:45PM -0700, Rosen Penev wrote:
> devm_request_irq() is a managed resource: the IRQ is not freed until
> devres_release_all() runs after the probe function returns. In the
> probe error path, free_netdev(mal->dummy_dev) and dcr_unmap() execute
> while the IRQ is still live. If the shared IRQ fires during cleanup,
> the handler accesses unmapped DCR registers (crash) or the already-
> freed dummy_dev (use-after-free).
>
> Switch to plain request_irq() with per-IRQ error labels that tear down
> only the IRQs that were successfully registered, and add the matching
> free_irq() calls in mal_remove().
>
> Fixes: 14f59154ff0b ("net: ibm: emac: mal: use devm for request_irq")
> Assisted-by: opencode:big-pickle
> Signed-off-by: Rosen Penev <rosenp@gmail.com>
You seemed to of sent the same patch within 24 hours. Please don't do
that.
https://www.kernel.org/doc/html/latest/process/maintainer-netdev.html
Send a self NACK to the broken version, wait 24 hours, and send v2.
Andrew
---
pw-bot: cr
^ permalink raw reply
* Re: [PATCH net-next 0/2] macvlan: RTNL-less macvlan_fill_info()
From: patchwork-bot+netdevbpf @ 2026-07-03 16:40 UTC (permalink / raw)
To: Eric Dumazet; +Cc: davem, kuba, pabeni, horms, kuniyu, netdev, eric.dumazet
In-Reply-To: <20260701082214.2974946-1-edumazet@google.com>
Hello:
This series was applied to netdev/net-next.git (main)
by Paolo Abeni <pabeni@redhat.com>:
On Wed, 1 Jul 2026 08:22:12 +0000 you wrote:
> This series removes the RTNL lock dependency from macvlan_fill_info(),
> allowing it to run under RCU read lock.
>
> The first patch annotates data races on 'mode' and 'flags' fields which
> are accessed locklessly in the RX/TX paths.
>
> The second patch transitions macvlan_fill_info() to RCU, adding necessary
> annotations for other fields and handling concurrent updates to the MAC
> address list by computing the count dynamically.
>
> [...]
Here is the summary with links:
- [net-next,1/2] macvlan: annotate data-races around vlan->mode and vlan->flags
https://git.kernel.org/netdev/net-next/c/ed37710d6c67
- [net-next,2/2] macvlan: no longer rely on RTNL in macvlan_fill_info()
https://git.kernel.org/netdev/net-next/c/6d728e7e286b
You are awesome, thank you!
--
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/patchwork/pwbot.html
^ permalink raw reply
* Re: [PATCH net-next v7 1/2] dinghai: add ZTE network driver support
From: Paolo Abeni @ 2026-07-03 16:42 UTC (permalink / raw)
To: han.junyang
Cc: andrew+netdev, davem, edumazet, kuba, pabeni, horms, linux-kernel,
netdev, ran.ming, han.chengfei, zhang.yanze
In-Reply-To: <20260630111300830lukBczfNSgWE5wt6qR95k@zte.com.cn>
From: AI Reviewer <ai@example.com>
This is an AI-generated review of your patch. The human sending this
email has considered the AI review valid, or at least plausible.
Full review at: https://sashiko.dev/
---
dinghai: add ZTE network driver support
Add basic framework for ZTE DingHai ethernet PF driver, including
Kconfig/Makefile build support and PCIe device probe/remove skeleton.
> diff --git a/drivers/net/ethernet/zte/dinghai/en_pf.c b/drivers/net/ethernet/zte/dinghai/en_pf.c
> new file mode 100644
> index 0000000000000..b492fd2c261b8
> --- /dev/null
> +++ b/drivers/net/ethernet/zte/dinghai/en_pf.c
[ ... ]
> +static int dh_pf_pci_init(struct dh_core_dev *dev)
> +{
> + struct zxdh_pf_device *pf_dev = dev->priv;
> + int ret;
[ ... ]
> + pf_dev->pci_ioremap_addr[0] =
> + ioremap(pci_resource_start(dev->pdev, 0),
> + pci_resource_len(dev->pdev, 0));
[Severity: Medium]
Should this check if BAR 0 is actually an IORESOURCE_MEM before mapping it?
Calling ioremap() on an I/O port address or an unpopulated BAR could map
arbitrary physical memory.
Would it be safer to use pci_ioremap_bar() or pcim_iomap() which enforce
resource type and length checks?
> + if (!pf_dev->pci_ioremap_addr[0]) {
> + ret = -ENOMEM;
> + dev_err(dev->device, "dh pf pci ioremap failed\n");
> + goto err_pci_save_state;
> + }
[ ... ]
> +static void dh_pf_remove(struct pci_dev *pdev)
> +{
> + struct dh_core_dev *dh_dev = pci_get_drvdata(pdev);
> + struct devlink *devlink = priv_to_devlink(dh_dev);
> + struct zxdh_pf_device *pf_dev = dh_dev->priv;
> +
> + devlink_unregister(devlink);
> + dh_pf_pci_close(dh_dev);
> + mutex_destroy(&pf_dev->irq_lock);
> + mutex_destroy(&dh_dev->lock);
> + dh_core_free_priv(dh_dev);
> + devlink_free(devlink);
> + pci_set_drvdata(pdev, NULL);
> +}
> +
> +static void dh_pf_shutdown(struct pci_dev *pdev)
> +{
> + struct dh_core_dev *dh_dev = pci_get_drvdata(pdev);
> + struct devlink *devlink = priv_to_devlink(dh_dev);
> + struct zxdh_pf_device *pf_dev = dh_dev->priv;
> +
> + devlink_unregister(devlink);
> + dh_pf_pci_close(dh_dev);
> + mutex_destroy(&pf_dev->irq_lock);
> + mutex_destroy(&dh_dev->lock);
> + dh_core_free_priv(dh_dev);
> + devlink_free(devlink);
> + pci_set_drvdata(pdev, NULL);
> +}
[Severity: High]
Does this destructive teardown in the .shutdown callback create a race if
.remove is called afterwards?
If .remove is triggered (for example, during a hot-remove event during
shutdown), pci_get_drvdata() in dh_pf_remove() will return NULL.
The macro priv_to_devlink() will then convert NULL into a bad pointer,
causing a panic in devlink_unregister().
Should .shutdown only quiesce the hardware without destroying software
structures?
--
This is an AI-generated review.
^ permalink raw reply
* Re: [PATCH net-next v7 2/2] dinghai: add hardware register access and PCI capability scanning
From: Paolo Abeni @ 2026-07-03 16:42 UTC (permalink / raw)
To: han.junyang
Cc: andrew+netdev, davem, edumazet, kuba, pabeni, horms, linux-kernel,
netdev, ran.ming, han.chengfei, zhang.yanze
In-Reply-To: <20260630111532964R8a8-5zlzQRMas-VWfCi2@zte.com.cn>
From: AI Reviewer <ai@example.com>
This is an AI-generated review of your patch. The human sending this
email has considered the AI review valid, or at least plausible.
Full review at: https://sashiko.dev/
---
dinghai: add hardware register access and PCI capability scanning
Implement PCI configuration space access, BAR mapping, capability
scanning (common/notify/device), and hardware queue register
definitions for DingHai PF device.
> diff --git a/drivers/net/ethernet/zte/dinghai/en_pf.c b/drivers/net/ethernet/zte/dinghai/en_pf.c
> index b492fd2c261b8..fb80b0baca34d 100644
> --- a/drivers/net/ethernet/zte/dinghai/en_pf.c
> +++ b/drivers/net/ethernet/zte/dinghai/en_pf.c
[ ... ]
> @@ -104,6 +105,443 @@ void dh_pf_pci_close(struct dh_core_dev *dev)
[ ... ]
> +void __iomem *zxdh_pf_map_capability(struct dh_core_dev *dh_dev, int off,
> + size_t minlen, u32 align,
> + u32 start, u32 size,
> + size_t *len, resource_size_t *pa,
> + u32 *bar_off)
> +{
[ ... ]
> + if (length > size)
> + length = size;
> +
> + if (len)
> + *len = length;
> +
> + if (minlen + offset < minlen ||
> + minlen + offset > pci_resource_len(pdev, bar)) {
[Severity: High]
Does this code inflate the length returned in *len?
Because length is not capped by pci_resource_len(pdev, bar) - offset before
being assigned to *len, the returned length can be larger than the actually
mapped memory (which is capped by pci_iomap_range).
Later in zxdh_pf_map_vq_notify(), this inflated notify_len is used for bounds
checking, which could allow out-of-bounds MMIO accesses to pass the check.
[ ... ]
> +int zxdh_pf_device_cfg_init(struct dh_core_dev *dh_dev)
> +{
> + struct zxdh_pf_device *pf_dev = dh_dev->priv;
> + struct pci_dev *pdev = dh_dev->pdev;
> + int device;
> +
> + /* Device capability is only mandatory for
> + * devices that have device-specific configuration.
> + */
> + device = zxdh_pf_pci_find_capability(pdev, ZXDH_PCI_CAP_DEVICE_CFG,
> + IORESOURCE_IO | IORESOURCE_MEM,
> + &pf_dev->modern_bars);
> +
> + /* we don't know how much we should map,
> + * but PAGE_SIZE is more than enough for all existing devices.
> + */
> + if (device) {
> + pf_dev->device = zxdh_pf_map_capability(dh_dev, device, 0,
> + ZXDH_PF_ALIGN4, 0, PAGE_SIZE,
> + &pf_dev->device_len, NULL,
> + &pf_dev->dev_cfg_bar_off);
> + if (!pf_dev->device) {
[Severity: High]
Could pf_dev->device remain NULL if the device capability isn't found?
If the capability is optional and not provided, pf_dev->device will be NULL.
Also, passing minlen = 0 allows undersized mappings.
Later, zxdh_get_mac() and zxdh_set_mac() unconditionally dereference
pf_dev->device, which could trigger a kernel panic due to a NULL pointer
dereference or out-of-bounds access.
[ ... ]
> +void __iomem *zxdh_pf_map_vq_notify(struct dh_core_dev *dh_dev,
> + u16 phy_index, u16 index,
> + resource_size_t *pa)
> +{
> + struct zxdh_pf_device *pf_dev = dh_dev->priv;
> + u16 off;
> +
> + off = zxdh_pf_get_queue_notify_off(dh_dev, phy_index, index);
> +
> + if (pf_dev->notify_base) {
[ ... ]
> + } else {
> + return zxdh_pf_map_capability(dh_dev, pf_dev->notify_map_cap, 2, 2,
> + off * pf_dev->notify_offset_multiplier,
> + 2, NULL, pa, NULL);
> + }
> +}
[Severity: Medium]
Can the offset calculation overflow before being passed?
Since off is u16 and pf_dev->notify_offset_multiplier is u32,
off * pf_dev->notify_offset_multiplier evaluates as a 32-bit unsigned integer.
If the product exceeds 4GB, it wraps around before being passed to
zxdh_pf_map_capability(). A 64-bit cast is needed to prevent mapping incorrect
device registers.
[ ... ]
> +void zxdh_pf_get_vf_mac(struct dh_core_dev *dh_dev, u8 *mac, int vf_id)
> +{
> + struct zxdh_pf_device *pf_dev = dh_dev->priv;
> + u32 DEV_MAC_L;
> + u16 DEV_MAC_H;
> +
> + if (pf_dev->pf_sriov_cap_base) {
> + DEV_MAC_L = ioread32(pf_dev->pf_sriov_cap_base +
> + (pf_dev->sriov_bar_size) * vf_id +
> + pf_dev->dev_cfg_bar_off);
> + mac[0] = DEV_MAC_L & 0xff;
> + mac[1] = (DEV_MAC_L >> 8) & 0xff;
> + mac[2] = (DEV_MAC_L >> 16) & 0xff;
> + mac[3] = (DEV_MAC_L >> 24) & 0xff;
> + DEV_MAC_H = ioread16(pf_dev->pf_sriov_cap_base +
> + (pf_dev->sriov_bar_size) * vf_id +
> + pf_dev->dev_cfg_bar_off +
> + ZXDH_DEV_MAC_HIGH_OFFSET);
> + mac[4] = DEV_MAC_H & 0xff;
> + mac[5] = (DEV_MAC_H >> 8) & 0xff;
> + }
> +}
[Severity: High]
Does this code silently return without initializing the buffer?
If pf_dev->pf_sriov_cap_base is NULL, the function exits without modifying the
output mac buffer or returning an error code.
The caller might proceed using the uninitialized contents of the array, which
could expose uninitialized kernel stack bytes.
[ ... ]
> +void zxdh_set_mac(struct dh_core_dev *dh_dev, u8 *mac)
> +{
> + struct zxdh_pf_device *pf_dev = dh_dev->priv;
> + u32 DEV_MAC_L;
> + u16 DEV_MAC_H;
> +
> + DEV_MAC_L = mac[0] | (mac[1] << 8) | (mac[2] << 16) | (mac[3] << 24);
> + DEV_MAC_H = mac[4] | (mac[5] << 8);
> + iowrite32(DEV_MAC_L, pf_dev->device);
> + iowrite16(DEV_MAC_H, pf_dev->device + ZXDH_DEV_MAC_HIGH_OFFSET);
> +}
> +
> +void zxdh_get_mac(struct dh_core_dev *dh_dev, u8 *mac)
> +{
> + struct zxdh_pf_device *pf_dev = dh_dev->priv;
> + u32 DEV_MAC_L;
> + u16 DEV_MAC_H;
> +
> + DEV_MAC_L = ioread32(pf_dev->device);
> + mac[0] = DEV_MAC_L & 0xff;
> + mac[1] = (DEV_MAC_L >> 8) & 0xff;
> + mac[2] = (DEV_MAC_L >> 16) & 0xff;
> + mac[3] = (DEV_MAC_L >> 24) & 0xff;
> + DEV_MAC_H = ioread16(pf_dev->device + ZXDH_DEV_MAC_HIGH_OFFSET);
> + mac[4] = DEV_MAC_H & 0xff;
> + mac[5] = (DEV_MAC_H >> 8) & 0xff;
> +}
[Severity: High]
Can these functions trigger a NULL pointer dereference?
In zxdh_pf_device_cfg_init(), pf_dev->device can be left as NULL if the device
capability is missing, or it could be undersized.
zxdh_set_mac() and zxdh_get_mac() unconditionally dereference pf_dev->device,
which could trigger a kernel panic.
--
This is an AI-generated review.
^ permalink raw reply
* Re: [PATCH 1/1] net: usb: aqc111: fix set_mac_address return value for bonding
From: Andrew Lunn @ 2026-07-03 16:46 UTC (permalink / raw)
To: Hanson Wang; +Cc: netdev, linux-usb, oneukum
In-Reply-To: <20260703073936.462231-1-hanson.wang@ugreen.com>
On Fri, Jul 03, 2026 at 03:39:36PM +0800, Hanson Wang wrote:
> aqc111_set_mac_addr() returns the result of aqc111_write_cmd() on
> success. That function wraps usb_control_msg(), which returns the
> number of bytes transferred (6 for ETH_ALEN) rather than zero.
>
> Bonding calls ndo_set_mac_address() when enslaving an interface and
> treats any non-zero return value as failure.
It is not just bonding which has problems:
int netif_set_mac_address(struct net_device *dev, struct sockaddr_storage *ss,
struct netlink_ext_ack *extack)
{
const struct net_device_ops *ops = dev->netdev_ops;
int err;
if (!ops->ndo_set_mac_address)
return -EOPNOTSUPP;
if (ss->ss_family != dev->type)
return -EINVAL;
if (!netif_device_present(dev))
return -ENODEV;
err = netif_pre_changeaddr_notify(dev, ss->__data, extack);
if (err)
return err;
if (memcmp(dev->dev_addr, ss->__data, dev->addr_len)) {
err = ops->ndo_set_mac_address(dev, ss);
if (err)
return err;
Could you take a quick look at all USB ethernet drivers, and see if
this bug exists in other drivers. If one driver has it wrong, it could
well be more have it wrong.
Andrew
^ permalink raw reply
* Re: [PATCH] net: qed: Fix spelling typo in qed_dcbx.c comment
From: Andrew Lunn @ 2026-07-03 16:49 UTC (permalink / raw)
To: Praveen Rajendran
Cc: andrew+netdev, davem, edumazet, kuba, pabeni, netdev,
linux-kernel
In-Reply-To: <20260703143130.3685-1-praveenrajendran2009@gmail.com>
On Fri, Jul 03, 2026 at 08:01:30PM +0530, Praveen Rajendran wrote:
> Correct a minor spelling error inside a comment block of the QLogic
> Core module where "successfully" was misspelled as "successfuly".
>
> Signed-off-by: Praveen Rajendran <praveenrajendran2009@gmail.com>
Reviewed-by: Andrew Lunn <andrew@lunn.ch>
Before submitting more patches to netdev, please read:
https://www.kernel.org/doc/html/latest/process/maintainer-netdev.html
Andrew
^ permalink raw reply
* Re: [PATCH net V2 0/3] net/mlx5: LAG bug fixes
From: patchwork-bot+netdevbpf @ 2026-07-03 16:50 UTC (permalink / raw)
To: Tariq Toukan
Cc: andrew+netdev, davem, edumazet, kuba, netdev, pabeni, edwards,
jacob.e.keller, kees, leon, linux-kernel, linux-rdma, msanalla,
mbloch, moshe, ohartoov, rongweil, saeedm, shayd, horms
In-Reply-To: <20260630112917.698313-1-tariqt@nvidia.com>
Hello:
This series was applied to netdev/net.git (main)
by Paolo Abeni <pabeni@redhat.com>:
On Tue, 30 Jun 2026 14:29:14 +0300 you wrote:
> Hi,
>
> Three bug fixes by Shay in the mlx5 LAG subsystem.
>
> Patch 1 fixes an off-by-one in the error rollback path of
> mlx5_lag_create_single_fdb_filter(): the loop started from the
> failed index i, potentially operating on uninitialized state or
> double-tearing-down an entry that had already self-rolled-back.
> The rollback should start from i - 1.
>
> [...]
Here is the summary with links:
- [net,V2,1/3] net/mlx5: LAG, Fix off-by-one in single-FDB error rollback
https://git.kernel.org/netdev/net/c/0f0e4ae6975c
- [net,V2,2/3] net/mlx5: LAG, MPESW, Fix missing complete() on devcom error
https://git.kernel.org/netdev/net/c/d4b85f9a668b
- [net,V2,3/3] net/mlx5e: TC, skip peer flow cleanup when LAG seq is unavailable
https://git.kernel.org/netdev/net/c/7bed4af0ced8
You are awesome, thank you!
--
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/patchwork/pwbot.html
^ permalink raw reply
* Re: [PATCH net-next] macsec: no longer rely on RTNL in macsec_fill_info()
From: patchwork-bot+netdevbpf @ 2026-07-03 16:50 UTC (permalink / raw)
To: Eric Dumazet
Cc: davem, kuba, pabeni, horms, kuniyu, netdev, eric.dumazet, sd,
andrew+netdev
In-Reply-To: <20260701094341.3218199-1-edumazet@google.com>
Hello:
This patch was applied to netdev/net-next.git (main)
by Paolo Abeni <pabeni@redhat.com>:
On Wed, 1 Jul 2026 09:43:41 +0000 you wrote:
> Add READ_ONCE()/WRITE_ONCE() annotations on fields that can be
> changed concurrently in macsec_changelink() and macsec_update_offload():
>
> - secy->key_len
> - secy->xpn
> - tx_sc->encoding_sa
> - tx_sc->encrypt
> - secy->protect_frames
> - tx_sc->send_sci
> - tx_sc->end_station
> - tx_sc->scb
> - secy->replay_protect
> - secy->validate_frames
> - secy->replay_window
> - macsec->offload
>
> [...]
Here is the summary with links:
- [net-next] macsec: no longer rely on RTNL in macsec_fill_info()
https://git.kernel.org/netdev/net-next/c/5a5ebefdab9d
You are awesome, thank you!
--
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/patchwork/pwbot.html
^ permalink raw reply
* [PATCH net-next v4 0/3] net: dsa: motorcomm: Add LED support
From: David Yang @ 2026-07-03 16:51 UTC (permalink / raw)
To: netdev
Cc: David Yang, Andrew Lunn, Vladimir Oltean, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, linux-kernel
v3: https://lore.kernel.org/r/20260701155519.273212-1-mmyangfl@gmail.com
- fix null pointer dereference
- support polarity auto-configuration
v2: https://lore.kernel.org/r/20260629183137.541341-1-mmyangfl@gmail.com
- allocate LED structures only
- eliminate double locking
v1: https://lore.kernel.org/r/20260618202716.2166450-1-mmyangfl@gmail.com
- set up polarity correctly
- do not set up .brightness_get() to prevent dead lock
David Yang (3):
net: dsa: motorcomm: Move to subdirectory
net: dsa: motorcomm: Split SMI module
net: dsa: motorcomm: Add LED support
MAINTAINERS | 2 +-
drivers/net/dsa/Kconfig | 10 +-
drivers/net/dsa/Makefile | 2 +-
drivers/net/dsa/motorcomm/Kconfig | 17 +
drivers/net/dsa/motorcomm/Makefile | 5 +
.../net/dsa/{yt921x.c => motorcomm/chip.c} | 218 +------
.../net/dsa/{yt921x.h => motorcomm/chip.h} | 14 +-
drivers/net/dsa/motorcomm/leds.c | 607 ++++++++++++++++++
drivers/net/dsa/motorcomm/leds.h | 118 ++++
drivers/net/dsa/motorcomm/smi.c | 157 +++++
drivers/net/dsa/motorcomm/smi.h | 88 +++
11 files changed, 1018 insertions(+), 220 deletions(-)
create mode 100644 drivers/net/dsa/motorcomm/Kconfig
create mode 100644 drivers/net/dsa/motorcomm/Makefile
rename drivers/net/dsa/{yt921x.c => motorcomm/chip.c} (96%)
rename drivers/net/dsa/{yt921x.h => motorcomm/chip.h} (99%)
create mode 100644 drivers/net/dsa/motorcomm/leds.c
create mode 100644 drivers/net/dsa/motorcomm/leds.h
create mode 100644 drivers/net/dsa/motorcomm/smi.c
create mode 100644 drivers/net/dsa/motorcomm/smi.h
--
2.53.0
^ permalink raw reply
* [PATCH net-next v4 1/3] net: dsa: motorcomm: Move to subdirectory
From: David Yang @ 2026-07-03 16:51 UTC (permalink / raw)
To: netdev
Cc: David Yang, Andrew Lunn, Vladimir Oltean, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, linux-kernel
In-Reply-To: <20260703165241.542195-1-mmyangfl@gmail.com>
yt921x is already the longest single-file DSA driver, so it's time to
split it into parts.
Signed-off-by: David Yang <mmyangfl@gmail.com>
Reviewed-by: Andrew Lunn <andrew@lunn.ch>
---
MAINTAINERS | 2 +-
drivers/net/dsa/Kconfig | 10 ++--------
drivers/net/dsa/Makefile | 2 +-
drivers/net/dsa/motorcomm/Kconfig | 8 ++++++++
drivers/net/dsa/motorcomm/Makefile | 3 +++
drivers/net/dsa/{yt921x.c => motorcomm/chip.c} | 2 +-
drivers/net/dsa/{yt921x.h => motorcomm/chip.h} | 0
7 files changed, 16 insertions(+), 11 deletions(-)
create mode 100644 drivers/net/dsa/motorcomm/Kconfig
create mode 100644 drivers/net/dsa/motorcomm/Makefile
rename drivers/net/dsa/{yt921x.c => motorcomm/chip.c} (99%)
rename drivers/net/dsa/{yt921x.h => motorcomm/chip.h} (100%)
diff --git a/MAINTAINERS b/MAINTAINERS
index 06df1171f4cf..b007f20b2763 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -18039,7 +18039,7 @@ M: David Yang <mmyangfl@gmail.com>
L: netdev@vger.kernel.org
S: Maintained
F: Documentation/devicetree/bindings/net/dsa/motorcomm,yt921x.yaml
-F: drivers/net/dsa/yt921x.*
+F: drivers/net/dsa/motorcomm/
F: net/dsa/tag_yt921x.c
MOXA SMARTIO/INDUSTIO/INTELLIO SERIAL CARD
diff --git a/drivers/net/dsa/Kconfig b/drivers/net/dsa/Kconfig
index 4ab567c5bbaf..98e9bbe47de7 100644
--- a/drivers/net/dsa/Kconfig
+++ b/drivers/net/dsa/Kconfig
@@ -72,6 +72,8 @@ config NET_DSA_MV88E6060
source "drivers/net/dsa/microchip/Kconfig"
+source "drivers/net/dsa/motorcomm/Kconfig"
+
source "drivers/net/dsa/mv88e6xxx/Kconfig"
source "drivers/net/dsa/mxl862xx/Kconfig"
@@ -158,12 +160,4 @@ config NET_DSA_VITESSE_VSC73XX_PLATFORM
This enables support for the Vitesse VSC7385, VSC7388, VSC7395
and VSC7398 SparX integrated ethernet switches, connected over
a CPU-attached address bus and work in memory-mapped I/O mode.
-
-config NET_DSA_YT921X
- tristate "Motorcomm YT9215 ethernet switch chip support"
- select NET_DSA_TAG_YT921X
- select NET_IEEE8021Q_HELPERS if DCB
- help
- This enables support for the Motorcomm YT9215 ethernet switch
- chip.
endmenu
diff --git a/drivers/net/dsa/Makefile b/drivers/net/dsa/Makefile
index d2975badffc0..138225baa4d5 100644
--- a/drivers/net/dsa/Makefile
+++ b/drivers/net/dsa/Makefile
@@ -14,11 +14,11 @@ obj-$(CONFIG_NET_DSA_SMSC_LAN9303_MDIO) += lan9303_mdio.o
obj-$(CONFIG_NET_DSA_VITESSE_VSC73XX) += vitesse-vsc73xx-core.o
obj-$(CONFIG_NET_DSA_VITESSE_VSC73XX_PLATFORM) += vitesse-vsc73xx-platform.o
obj-$(CONFIG_NET_DSA_VITESSE_VSC73XX_SPI) += vitesse-vsc73xx-spi.o
-obj-$(CONFIG_NET_DSA_YT921X) += yt921x.o
obj-y += b53/
obj-y += hirschmann/
obj-y += lantiq/
obj-y += microchip/
+obj-y += motorcomm/
obj-y += mv88e6xxx/
obj-y += mxl862xx/
obj-y += netc/
diff --git a/drivers/net/dsa/motorcomm/Kconfig b/drivers/net/dsa/motorcomm/Kconfig
new file mode 100644
index 000000000000..1fddd386f866
--- /dev/null
+++ b/drivers/net/dsa/motorcomm/Kconfig
@@ -0,0 +1,8 @@
+# SPDX-License-Identifier: ISC
+config NET_DSA_YT921X
+ tristate "Motorcomm YT9215 ethernet switch chip support"
+ select NET_DSA_TAG_YT921X
+ select NET_IEEE8021Q_HELPERS if DCB
+ help
+ This enables support for the Motorcomm YT9215 ethernet switch
+ chip.
diff --git a/drivers/net/dsa/motorcomm/Makefile b/drivers/net/dsa/motorcomm/Makefile
new file mode 100644
index 000000000000..afd03be9fa35
--- /dev/null
+++ b/drivers/net/dsa/motorcomm/Makefile
@@ -0,0 +1,3 @@
+# SPDX-License-Identifier: ISC
+obj-$(CONFIG_NET_DSA_YT921X) += yt921x.o
+yt921x-objs := chip.o
diff --git a/drivers/net/dsa/yt921x.c b/drivers/net/dsa/motorcomm/chip.c
similarity index 99%
rename from drivers/net/dsa/yt921x.c
rename to drivers/net/dsa/motorcomm/chip.c
index 159b16606f6c..f070732845eb 100644
--- a/drivers/net/dsa/yt921x.c
+++ b/drivers/net/dsa/motorcomm/chip.c
@@ -26,7 +26,7 @@
#include <net/ieee8021q.h>
#include <net/pkt_cls.h>
-#include "yt921x.h"
+#include "chip.h"
struct yt921x_mib_desc {
unsigned int size;
diff --git a/drivers/net/dsa/yt921x.h b/drivers/net/dsa/motorcomm/chip.h
similarity index 100%
rename from drivers/net/dsa/yt921x.h
rename to drivers/net/dsa/motorcomm/chip.h
--
2.53.0
^ permalink raw reply related
* [PATCH net-next v4 2/3] net: dsa: motorcomm: Split SMI module
From: David Yang @ 2026-07-03 16:51 UTC (permalink / raw)
To: netdev
Cc: David Yang, Andrew Lunn, Vladimir Oltean, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, linux-kernel
In-Reply-To: <20260703165241.542195-1-mmyangfl@gmail.com>
SMI operations are going to be used across different modules.
Signed-off-by: David Yang <mmyangfl@gmail.com>
Reviewed-by: Andrew Lunn <andrew@lunn.ch>
---
drivers/net/dsa/motorcomm/Makefile | 1 +
drivers/net/dsa/motorcomm/chip.c | 207 +----------------------------
drivers/net/dsa/motorcomm/smi.c | 157 ++++++++++++++++++++++
drivers/net/dsa/motorcomm/smi.h | 88 ++++++++++++
4 files changed, 247 insertions(+), 206 deletions(-)
create mode 100644 drivers/net/dsa/motorcomm/smi.c
create mode 100644 drivers/net/dsa/motorcomm/smi.h
diff --git a/drivers/net/dsa/motorcomm/Makefile b/drivers/net/dsa/motorcomm/Makefile
index afd03be9fa35..6cea5313a444 100644
--- a/drivers/net/dsa/motorcomm/Makefile
+++ b/drivers/net/dsa/motorcomm/Makefile
@@ -1,3 +1,4 @@
# SPDX-License-Identifier: ISC
obj-$(CONFIG_NET_DSA_YT921X) += yt921x.o
yt921x-objs := chip.o
+yt921x-objs += smi.o
diff --git a/drivers/net/dsa/motorcomm/chip.c b/drivers/net/dsa/motorcomm/chip.c
index f070732845eb..6dee25b6754a 100644
--- a/drivers/net/dsa/motorcomm/chip.c
+++ b/drivers/net/dsa/motorcomm/chip.c
@@ -13,7 +13,6 @@
#include <linux/if_bridge.h>
#include <linux/if_hsr.h>
#include <linux/if_vlan.h>
-#include <linux/iopoll.h>
#include <linux/mdio.h>
#include <linux/module.h>
#include <linux/of.h>
@@ -27,6 +26,7 @@
#include <net/pkt_cls.h>
#include "chip.h"
+#include "smi.h"
struct yt921x_mib_desc {
unsigned int size;
@@ -155,9 +155,6 @@ static const struct yt921x_info yt921x_infos[] = {
#define YT921X_VID_UNWARE 4095
-#define YT921X_POLL_SLEEP_US 10000
-#define YT921X_POLL_TIMEOUT_US 100000
-
/* The interval should be small enough to avoid overflow of 32bit MIBs.
*
* Until we can read MIBs from stats64 call directly (i.e. sleep
@@ -196,208 +193,6 @@ static u32 ethaddr_lo2_to_u32(const unsigned char *addr)
return (addr[4] << 8) | addr[5];
}
-static int yt921x_reg_read(struct yt921x_priv *priv, u32 reg, u32 *valp)
-{
- WARN_ON(!mutex_is_locked(&priv->reg_lock));
-
- return priv->reg_ops->read(priv->reg_ctx, reg, valp);
-}
-
-static int yt921x_reg_write(struct yt921x_priv *priv, u32 reg, u32 val)
-{
- WARN_ON(!mutex_is_locked(&priv->reg_lock));
-
- return priv->reg_ops->write(priv->reg_ctx, reg, val);
-}
-
-static int
-yt921x_reg_wait(struct yt921x_priv *priv, u32 reg, u32 mask, u32 *valp)
-{
- u32 val;
- int res;
- int ret;
-
- ret = read_poll_timeout(yt921x_reg_read, res,
- res || (val & mask) == *valp,
- YT921X_POLL_SLEEP_US, YT921X_POLL_TIMEOUT_US,
- false, priv, reg, &val);
- if (ret)
- return ret;
- if (res)
- return res;
-
- *valp = val;
- return 0;
-}
-
-static int
-yt921x_reg_update_bits(struct yt921x_priv *priv, u32 reg, u32 mask, u32 val)
-{
- int res;
- u32 v;
- u32 u;
-
- res = yt921x_reg_read(priv, reg, &v);
- if (res)
- return res;
-
- u = v;
- u &= ~mask;
- u |= val;
- if (u == v)
- return 0;
-
- return yt921x_reg_write(priv, reg, u);
-}
-
-static int yt921x_reg_set_bits(struct yt921x_priv *priv, u32 reg, u32 mask)
-{
- return yt921x_reg_update_bits(priv, reg, 0, mask);
-}
-
-static int yt921x_reg_clear_bits(struct yt921x_priv *priv, u32 reg, u32 mask)
-{
- return yt921x_reg_update_bits(priv, reg, mask, 0);
-}
-
-static int
-yt921x_reg_toggle_bits(struct yt921x_priv *priv, u32 reg, u32 mask, bool set)
-{
- return yt921x_reg_update_bits(priv, reg, mask, !set ? 0 : mask);
-}
-
-/* Some multi-word registers, like VLANn_CTRL, should be treated as a single
- * long register. More specifically, writes to parts of its words won't become
- * visible, until the last word is written.
- *
- * Here we require full read and write operations over these registers to
- * eliminate potential issues, although partial reads/writes are also possible.
- */
-
-static void update_ctrls_unaligned(u32 *lo, u32 *hi, u64 mask, u64 val)
-{
- *lo &= ~lower_32_bits(mask);
- *hi &= ~upper_32_bits(mask);
- *lo |= lower_32_bits(val);
- *hi |= upper_32_bits(val);
-}
-
-static int
-yt921x_regs_read(struct yt921x_priv *priv, u32 reg, u32 *vals,
- unsigned int num_regs)
-{
- int res;
-
- for (unsigned int i = 0; i < num_regs; i++) {
- res = yt921x_reg_read(priv, reg + 4 * i, &vals[i]);
- if (res)
- return res;
- }
-
- return 0;
-}
-
-static int
-yt921x_regs_write(struct yt921x_priv *priv, u32 reg, const u32 *vals,
- unsigned int num_regs)
-{
- int res;
-
- for (unsigned int i = 0; i < num_regs; i++) {
- res = yt921x_reg_write(priv, reg + 4 * i, vals[i]);
- if (res)
- return res;
- }
-
- return 0;
-}
-
-static int
-yt921x_regs_update_bits(struct yt921x_priv *priv, u32 reg, const u32 *masks,
- const u32 *vals, unsigned int num_regs)
-{
- bool changed = false;
- u32 vs[4];
- int res;
-
- BUILD_BUG_ON(num_regs > ARRAY_SIZE(vs));
-
- res = yt921x_regs_read(priv, reg, vs, num_regs);
- if (res)
- return res;
-
- for (unsigned int i = 0; i < num_regs; i++) {
- u32 u = vs[i];
-
- u &= ~masks[i];
- u |= vals[i];
- if (u != vs[i])
- changed = true;
-
- vs[i] = u;
- }
-
- if (!changed)
- return 0;
-
- return yt921x_regs_write(priv, reg, vs, num_regs);
-}
-
-static int
-yt921x_regs_clear_bits(struct yt921x_priv *priv, u32 reg, const u32 *masks,
- unsigned int num_regs)
-{
- bool changed = false;
- u32 vs[4];
- int res;
-
- BUILD_BUG_ON(num_regs > ARRAY_SIZE(vs));
-
- res = yt921x_regs_read(priv, reg, vs, num_regs);
- if (res)
- return res;
-
- for (unsigned int i = 0; i < num_regs; i++) {
- u32 u = vs[i];
-
- u &= ~masks[i];
- if (u != vs[i])
- changed = true;
-
- vs[i] = u;
- }
-
- if (!changed)
- return 0;
-
- return yt921x_regs_write(priv, reg, vs, num_regs);
-}
-
-static int
-yt921x_reg64_write(struct yt921x_priv *priv, u32 reg, const u32 *vals)
-{
- return yt921x_regs_write(priv, reg, vals, 2);
-}
-
-static int
-yt921x_reg64_update_bits(struct yt921x_priv *priv, u32 reg, const u32 *masks,
- const u32 *vals)
-{
- return yt921x_regs_update_bits(priv, reg, masks, vals, 2);
-}
-
-static int
-yt921x_reg64_clear_bits(struct yt921x_priv *priv, u32 reg, const u32 *masks)
-{
- return yt921x_regs_clear_bits(priv, reg, masks, 2);
-}
-
-static int
-yt921x_reg96_write(struct yt921x_priv *priv, u32 reg, const u32 *vals)
-{
- return yt921x_regs_write(priv, reg, vals, 3);
-}
-
static int yt921x_reg_mdio_read(void *context, u32 reg, u32 *valp)
{
struct yt921x_reg_mdio *mdio = context;
diff --git a/drivers/net/dsa/motorcomm/smi.c b/drivers/net/dsa/motorcomm/smi.c
new file mode 100644
index 000000000000..9054896e4cd1
--- /dev/null
+++ b/drivers/net/dsa/motorcomm/smi.c
@@ -0,0 +1,157 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2026 David Yang
+ */
+
+#include <linux/iopoll.h>
+
+#include "chip.h"
+#include "smi.h"
+
+#define YT921X_POLL_SLEEP_US 10000
+#define YT921X_POLL_TIMEOUT_US 100000
+
+int yt921x_reg_read(struct yt921x_priv *priv, u32 reg, u32 *valp)
+{
+ lockdep_assert_held_once(&priv->reg_lock);
+
+ return priv->reg_ops->read(priv->reg_ctx, reg, valp);
+}
+
+int yt921x_reg_write(struct yt921x_priv *priv, u32 reg, u32 val)
+{
+ lockdep_assert_held_once(&priv->reg_lock);
+
+ return priv->reg_ops->write(priv->reg_ctx, reg, val);
+}
+
+int yt921x_reg_wait(struct yt921x_priv *priv, u32 reg, u32 mask, u32 *valp)
+{
+ u32 val;
+ int res;
+ int ret;
+
+ ret = read_poll_timeout(yt921x_reg_read, res,
+ res || (val & mask) == *valp,
+ YT921X_POLL_SLEEP_US, YT921X_POLL_TIMEOUT_US,
+ false, priv, reg, &val);
+ if (ret)
+ return ret;
+ if (res)
+ return res;
+
+ *valp = val;
+ return 0;
+}
+
+int yt921x_reg_update_bits(struct yt921x_priv *priv, u32 reg, u32 mask, u32 val)
+{
+ int res;
+ u32 v;
+ u32 u;
+
+ res = yt921x_reg_read(priv, reg, &v);
+ if (res)
+ return res;
+
+ u = v;
+ u &= ~mask;
+ u |= val;
+ if (u == v)
+ return 0;
+
+ return yt921x_reg_write(priv, reg, u);
+}
+
+int
+yt921x_regs_read(struct yt921x_priv *priv, u32 reg, u32 *vals,
+ unsigned int num_regs)
+{
+ int res;
+
+ for (unsigned int i = 0; i < num_regs; i++) {
+ res = yt921x_reg_read(priv, reg + 4 * i, &vals[i]);
+ if (res)
+ return res;
+ }
+
+ return 0;
+}
+
+int
+yt921x_regs_write(struct yt921x_priv *priv, u32 reg, const u32 *vals,
+ unsigned int num_regs)
+{
+ int res;
+
+ for (unsigned int i = 0; i < num_regs; i++) {
+ res = yt921x_reg_write(priv, reg + 4 * i, vals[i]);
+ if (res)
+ return res;
+ }
+
+ return 0;
+}
+
+int
+yt921x_regs_update_bits(struct yt921x_priv *priv, u32 reg, const u32 *masks,
+ const u32 *vals, unsigned int num_regs)
+{
+ bool changed = false;
+ u32 vs[4];
+ int res;
+
+ if (WARN_ON_ONCE(num_regs > ARRAY_SIZE(vs)))
+ return -EINVAL;
+
+ res = yt921x_regs_read(priv, reg, vs, num_regs);
+ if (res)
+ return res;
+
+ for (unsigned int i = 0; i < num_regs; i++) {
+ u32 u = vs[i];
+
+ u &= ~masks[i];
+ u |= vals[i];
+ if (u != vs[i])
+ changed = true;
+
+ vs[i] = u;
+ }
+
+ if (!changed)
+ return 0;
+
+ return yt921x_regs_write(priv, reg, vs, num_regs);
+}
+
+int
+yt921x_regs_clear_bits(struct yt921x_priv *priv, u32 reg, const u32 *masks,
+ unsigned int num_regs)
+{
+ bool changed = false;
+ u32 vs[4];
+ int res;
+
+ if (WARN_ON_ONCE(num_regs > ARRAY_SIZE(vs)))
+ return -EINVAL;
+
+ res = yt921x_regs_read(priv, reg, vs, num_regs);
+ if (res)
+ return res;
+
+ for (unsigned int i = 0; i < num_regs; i++) {
+ u32 u = vs[i];
+
+ u &= ~masks[i];
+ if (u != vs[i])
+ changed = true;
+
+ vs[i] = u;
+ }
+
+ if (!changed)
+ return 0;
+
+ return yt921x_regs_write(priv, reg, vs, num_regs);
+}
diff --git a/drivers/net/dsa/motorcomm/smi.h b/drivers/net/dsa/motorcomm/smi.h
new file mode 100644
index 000000000000..2e956065eb90
--- /dev/null
+++ b/drivers/net/dsa/motorcomm/smi.h
@@ -0,0 +1,88 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (c) 2026 David Yang
+ */
+
+#ifndef _YT_SMI_H
+#define _YT_SMI_H
+
+#include <linux/types.h>
+#include <linux/wordpart.h>
+
+struct yt921x_priv;
+
+int yt921x_reg_read(struct yt921x_priv *priv, u32 reg, u32 *valp);
+int yt921x_reg_write(struct yt921x_priv *priv, u32 reg, u32 val);
+int yt921x_reg_wait(struct yt921x_priv *priv, u32 reg, u32 mask, u32 *valp);
+int yt921x_reg_update_bits(struct yt921x_priv *priv, u32 reg, u32 mask,
+ u32 val);
+
+static inline int
+yt921x_reg_set_bits(struct yt921x_priv *priv, u32 reg, u32 mask)
+{
+ return yt921x_reg_update_bits(priv, reg, 0, mask);
+}
+
+static inline int
+yt921x_reg_clear_bits(struct yt921x_priv *priv, u32 reg, u32 mask)
+{
+ return yt921x_reg_update_bits(priv, reg, mask, 0);
+}
+
+static inline int
+yt921x_reg_toggle_bits(struct yt921x_priv *priv, u32 reg, u32 mask, bool set)
+{
+ return yt921x_reg_update_bits(priv, reg, mask, !set ? 0 : mask);
+}
+
+/* Some multi-word registers, like VLANn_CTRL, should be treated as a single
+ * long register. More specifically, writes to parts of its words won't become
+ * visible, until the last word is written.
+ *
+ * Here we require full read and write operations over these registers to
+ * eliminate potential issues, although partial reads/writes are also possible.
+ */
+
+static inline void update_ctrls_unaligned(u32 *lo, u32 *hi, u64 mask, u64 val)
+{
+ *lo &= ~lower_32_bits(mask);
+ *hi &= ~upper_32_bits(mask);
+ *lo |= lower_32_bits(val);
+ *hi |= upper_32_bits(val);
+}
+
+int yt921x_regs_read(struct yt921x_priv *priv, u32 reg, u32 *vals,
+ unsigned int num_regs);
+int yt921x_regs_write(struct yt921x_priv *priv, u32 reg, const u32 *vals,
+ unsigned int num_regs);
+int yt921x_regs_update_bits(struct yt921x_priv *priv, u32 reg, const u32 *masks,
+ const u32 *vals, unsigned int num_regs);
+int yt921x_regs_clear_bits(struct yt921x_priv *priv, u32 reg, const u32 *masks,
+ unsigned int num_regs);
+
+static inline int
+yt921x_reg64_write(struct yt921x_priv *priv, u32 reg, const u32 *vals)
+{
+ return yt921x_regs_write(priv, reg, vals, 2);
+}
+
+static inline int
+yt921x_reg64_update_bits(struct yt921x_priv *priv, u32 reg, const u32 *masks,
+ const u32 *vals)
+{
+ return yt921x_regs_update_bits(priv, reg, masks, vals, 2);
+}
+
+static inline int
+yt921x_reg64_clear_bits(struct yt921x_priv *priv, u32 reg, const u32 *masks)
+{
+ return yt921x_regs_clear_bits(priv, reg, masks, 2);
+}
+
+static inline int
+yt921x_reg96_write(struct yt921x_priv *priv, u32 reg, const u32 *vals)
+{
+ return yt921x_regs_write(priv, reg, vals, 3);
+}
+
+#endif
--
2.53.0
^ permalink raw reply related
* [PATCH net-next v4 3/3] net: dsa: motorcomm: Add LED support
From: David Yang @ 2026-07-03 16:51 UTC (permalink / raw)
To: netdev
Cc: David Yang, Andrew Lunn, Vladimir Oltean, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, linux-kernel
In-Reply-To: <20260703165241.542195-1-mmyangfl@gmail.com>
LEDs can be described in the device tree using the same format as qca8k.
Each port can configure up to 3 LEDs.
Currently, only parallel mode and strict 1:1 mapping are supported.
Signed-off-by: David Yang <mmyangfl@gmail.com>
---
drivers/net/dsa/motorcomm/Kconfig | 9 +
drivers/net/dsa/motorcomm/Makefile | 1 +
drivers/net/dsa/motorcomm/chip.c | 9 +-
drivers/net/dsa/motorcomm/chip.h | 14 +-
drivers/net/dsa/motorcomm/leds.c | 607 +++++++++++++++++++++++++++++
drivers/net/dsa/motorcomm/leds.h | 118 ++++++
6 files changed, 755 insertions(+), 3 deletions(-)
create mode 100644 drivers/net/dsa/motorcomm/leds.c
create mode 100644 drivers/net/dsa/motorcomm/leds.h
diff --git a/drivers/net/dsa/motorcomm/Kconfig b/drivers/net/dsa/motorcomm/Kconfig
index 1fddd386f866..3438e0b14361 100644
--- a/drivers/net/dsa/motorcomm/Kconfig
+++ b/drivers/net/dsa/motorcomm/Kconfig
@@ -6,3 +6,12 @@ config NET_DSA_YT921X
help
This enables support for the Motorcomm YT9215 ethernet switch
chip.
+
+config NET_DSA_YT921X_LEDS
+ bool "LED support for Motorcomm YT9215"
+ default y
+ depends on NET_DSA_YT921X
+ depends on LEDS_CLASS=y || LEDS_CLASS=NET_DSA_YT921X
+ help
+ This enables support for controlling the LEDs attached to the
+ Motorcomm YT9215 switch chips.
diff --git a/drivers/net/dsa/motorcomm/Makefile b/drivers/net/dsa/motorcomm/Makefile
index 6cea5313a444..5a63db0029ff 100644
--- a/drivers/net/dsa/motorcomm/Makefile
+++ b/drivers/net/dsa/motorcomm/Makefile
@@ -1,4 +1,5 @@
# SPDX-License-Identifier: ISC
obj-$(CONFIG_NET_DSA_YT921X) += yt921x.o
yt921x-objs := chip.o
+yt921x-$(CONFIG_NET_DSA_YT921X_LEDS) += leds.o
yt921x-objs += smi.o
diff --git a/drivers/net/dsa/motorcomm/chip.c b/drivers/net/dsa/motorcomm/chip.c
index 6dee25b6754a..a3512136383b 100644
--- a/drivers/net/dsa/motorcomm/chip.c
+++ b/drivers/net/dsa/motorcomm/chip.c
@@ -26,6 +26,7 @@
#include <net/pkt_cls.h>
#include "chip.h"
+#include "leds.h"
#include "smi.h"
struct yt921x_mib_desc {
@@ -151,8 +152,6 @@ static const struct yt921x_info yt921x_infos[] = {
{}
};
-#define YT921X_NAME "yt921x"
-
#define YT921X_VID_UNWARE 4095
/* The interval should be small enough to avoid overflow of 32bit MIBs.
@@ -4581,6 +4580,12 @@ static int yt921x_dsa_setup(struct dsa_switch *ds)
if (res)
return res;
+#if IS_ENABLED(CONFIG_NET_DSA_YT921X_LEDS)
+ res = yt921x_leds_setup(priv);
+ if (res)
+ dev_warn(dev, "Failed to setup LEDs: %d\n", res);
+#endif
+
return 0;
}
diff --git a/drivers/net/dsa/motorcomm/chip.h b/drivers/net/dsa/motorcomm/chip.h
index 555046526669..6570ae5902ad 100644
--- a/drivers/net/dsa/motorcomm/chip.h
+++ b/drivers/net/dsa/motorcomm/chip.h
@@ -850,8 +850,13 @@ enum yt921x_fdb_entry_status {
#define YT921X_ACL_NUM (YT921X_ACL_BLK_NUM * YT921X_ACL_ENT_PER_BLK)
#define YT921X_UDF_NUM 8
+#define YT921X_LED_GROUP_NUM 3
+
/* 8 internal + 2 external + 1 mcu */
-#define YT921X_PORT_NUM 11
+#define YT921X_PORT_NUM 11
+#define YT921X_PORT_MCU 10
+
+#define YT921X_NAME "yt921x"
#define yt921x_port_is_internal(port) ((port) < 8)
#define yt921x_port_is_external(port) (8 <= (port) && (port) < 9)
@@ -938,6 +943,13 @@ struct yt921x_port {
struct yt921x_mib mib;
u64 rx_frames;
u64 tx_frames;
+
+#if IS_ENABLED(CONFIG_NET_DSA_YT921X_LEDS)
+ unsigned char led_duty;
+ unsigned short led_cycle;
+
+ struct yt921x_led *leds[YT921X_LED_GROUP_NUM];
+#endif
};
struct yt921x_reg_ops {
diff --git a/drivers/net/dsa/motorcomm/leds.c b/drivers/net/dsa/motorcomm/leds.c
new file mode 100644
index 000000000000..b066a7e84fe6
--- /dev/null
+++ b/drivers/net/dsa/motorcomm/leds.c
@@ -0,0 +1,607 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2026 David Yang
+ */
+
+#include <linux/uleds.h>
+
+#include "chip.h"
+#include "leds.h"
+#include "smi.h"
+
+#define to_yt921x_led(led_cdev) \
+ container_of_const((led_cdev), struct yt921x_led, cdev)
+#define to_yt921x_port(led) ((led)->port)
+#define to_yt921x_priv(pp) \
+ container_of_const((pp), struct yt921x_priv, ports[(pp)->index])
+#define to_device(priv) ((priv)->ds.dev)
+
+static u32 yt921x_led_regaddr(struct yt921x_priv *priv, int port, int group)
+{
+ switch (group) {
+ case 0:
+ default:
+ return YT921X_LED0_PORTn(port);
+ case 1:
+ return YT921X_LED1_PORTn(port);
+ case 2:
+ return YT921X_LED2_PORTn(port);
+ }
+}
+
+static int
+yt921x_led_force_get(struct yt921x_priv *priv, int port, int group, bool *onp)
+{
+ u32 val;
+ int res;
+
+ res = yt921x_reg_read(priv, YT921X_LED2_PORTn(port), &val);
+ if (res)
+ return res;
+
+ *onp = (val & YT921X_LED2_PORT_FORCEn_M(group)) ==
+ YT921X_LED2_PORT_FORCEn_ON(group);
+ return 0;
+}
+
+static int
+yt921x_led_force_set(struct yt921x_priv *priv, int port, int group, bool on)
+{
+ struct yt921x_port *pp = &priv->ports[port];
+ struct yt921x_led *led = pp->leds[group];
+ u32 ctrl;
+ u32 mask;
+
+ led->use_cycle = false;
+ led->use_duty = false;
+
+ mask = YT921X_LED2_PORT_FORCEn_M(group);
+ ctrl = on ? YT921X_LED2_PORT_FORCEn_ON(group) :
+ YT921X_LED2_PORT_FORCEn_OFF(group);
+ return yt921x_reg_update_bits(priv, YT921X_LED2_PORTn(port), mask,
+ ctrl);
+}
+
+static int
+yt921x_led_blink_select(const struct yt921x_priv *priv, unsigned long on,
+ unsigned long off, unsigned short *cyclep,
+ unsigned char *dutyp)
+{
+ static const unsigned char dutys[] = {
+ YT921X_LED_DUTY(1, 6),
+ YT921X_LED_DUTY(1, 4),
+ YT921X_LED_DUTY(1, 3),
+ YT921X_LED_DUTY(1, 2),
+ };
+ unsigned int cycle_upper;
+ unsigned int cycle_req;
+ unsigned int duty_req;
+ unsigned int cycle;
+ unsigned int duty;
+
+ cycle = YT921X_LED_BLINK_MAX;
+ cycle_upper = cycle * 11585 / 8192 + 1; /* M_SQRT2 * cycle */
+ if (check_add_overflow(on, off, &cycle_req) || cycle_req >= cycle_upper)
+ return -EOPNOTSUPP;
+
+ for (; cycle > YT921X_LED_BLINK_MIN; cycle_upper >>= 1, cycle >>= 1)
+ if (cycle_req >= cycle_upper >> 1)
+ break;
+ *cyclep = cycle;
+
+ duty_req = DIV_ROUND_CLOSEST(YT921X_LED_DUTY_DENOM *
+ (on > off ? off : on), cycle_req);
+ for (unsigned int i = ARRAY_SIZE(dutys) - 1;; i--)
+ if (i == 0 || duty_req >= (dutys[i - 1] + dutys[i]) / 2) {
+ duty = dutys[i];
+ break;
+ }
+ if (on > off)
+ duty = YT921X_LED_DUTY_DENOM - duty;
+ *dutyp = duty;
+
+ return 0;
+}
+
+static int
+yt921x_led_blink_set(struct yt921x_priv *priv, int port, int group,
+ unsigned long *onp, unsigned long *offp)
+{
+ struct yt921x_port *pp = &priv->ports[port];
+ struct yt921x_led *led = pp->leds[group];
+ unsigned short cycle;
+ unsigned char duty;
+ bool use_cycle;
+ u32 ctrl;
+ u32 mask;
+ u32 val;
+ int res;
+
+ if (!*onp && !*offp) {
+ cycle = YT921X_LED_BLINK_DEF;
+ duty = YT921X_LED_DUTY(1, 2);
+ for (unsigned int i = 0; i < YT921X_LED_GROUP_NUM; i++)
+ if (i != group && pp->leds[i] &&
+ pp->leds[i]->use_duty) {
+ duty = pp->led_duty;
+ break;
+ }
+
+ use_cycle = false;
+ } else {
+ bool change_cycle;
+ bool change_duty;
+
+ res = yt921x_led_blink_select(priv, *onp, *offp, &cycle, &duty);
+ if (res)
+ return res;
+
+ use_cycle = cycle < YT921X_LED_BLINK_DEF;
+ change_cycle = use_cycle && cycle != pp->led_cycle;
+ change_duty = duty != pp->led_duty;
+ if (change_cycle || change_duty)
+ for (unsigned int i = 0; i < YT921X_LED_GROUP_NUM;
+ i++) {
+ if (i == group || !pp->leds[i])
+ continue;
+ if ((change_cycle && pp->leds[i]->use_cycle) ||
+ (change_duty && pp->leds[i]->use_duty))
+ return -EOPNOTSUPP;
+ }
+ }
+
+ /* The chip seems to jam a while if changing duty directly */
+ res = yt921x_reg_read(priv, YT921X_LED2_PORTn(port), &val);
+ if (res)
+ return res;
+
+ ctrl = val & ~YT921X_LED2_PORT_FORCEn_M(group);
+ ctrl |= YT921X_LED2_PORT_FORCEn_DONTCARE(group);
+ if (val != ctrl) {
+ res = yt921x_reg_write(priv, YT921X_LED2_PORTn(port), ctrl);
+ if (res)
+ return res;
+ }
+
+ mask = YT921X_LED1_PORT_BLINK_DUTY_M | YT921X_LED1_PORT_BLINK_DUTY_COMP;
+ switch (duty >= YT921X_LED_DUTY(1, 2) ? duty :
+ YT921X_LED_DUTY_DENOM - duty) {
+ default:
+ duty = YT921X_LED_DUTY(1, 2);
+ fallthrough;
+ case YT921X_LED_DUTY(1, 2):
+ ctrl = YT921X_LED1_PORT_BLINK_DUTY_1_2;
+ break;
+ case YT921X_LED_DUTY(2, 3):
+ ctrl = YT921X_LED1_PORT_BLINK_DUTY_2_3;
+ break;
+ case YT921X_LED_DUTY(3, 4):
+ ctrl = YT921X_LED1_PORT_BLINK_DUTY_3_4;
+ break;
+ case YT921X_LED_DUTY(5, 6):
+ ctrl = YT921X_LED1_PORT_BLINK_DUTY_5_6;
+ break;
+ }
+ if (duty < YT921X_LED_DUTY(1, 2))
+ ctrl |= YT921X_LED1_PORT_BLINK_DUTY_COMP;
+ if (use_cycle) {
+ mask |= YT921X_LED1_PORT_OTHER_BLINK_M;
+ ctrl |= YT921X_LED1_PORT_OTHER_BLINK(9 - __fls(cycle));
+ }
+ res = yt921x_reg_update_bits(priv, YT921X_LED1_PORTn(port), mask, ctrl);
+ if (res)
+ return res;
+
+ ctrl = val & ~(YT921X_LED2_PORT_FORCEn_M(group) |
+ YT921X_LED2_PORT_FORCE_BLINKn_M(group));
+ ctrl |= YT921X_LED2_PORT_FORCEn_BLINK(group);
+ if (use_cycle)
+ ctrl |= YT921X_LED2_PORT_FORCE_BLINKn_OTHER(group);
+ else
+ ctrl |= YT921X_LED2_PORT_FORCE_BLINKn(group, __fls(cycle) - 9);
+ res = yt921x_reg_write(priv, YT921X_LED2_PORTn(port), ctrl);
+ if (res)
+ return res;
+
+ led->use_cycle = use_cycle;
+ if (use_cycle)
+ pp->led_cycle = cycle;
+ led->use_duty = true;
+ pp->led_duty = duty;
+
+ *onp = DIV_ROUND_CLOSEST(duty * cycle, YT921X_LED_DUTY_DENOM);
+ *offp = cycle - *onp;
+ return 0;
+}
+
+static const u32 yt921x_led_trigger_maps[__TRIGGER_NETDEV_MAX] = {
+ [TRIGGER_NETDEV_LINK] = YT921X_LEDx_PORT_ACT_DUPLEX_HALF |
+ YT921X_LEDx_PORT_ACT_DUPLEX_FULL,
+ [TRIGGER_NETDEV_LINK_10] = YT921X_LEDx_PORT_ACT_10M,
+ [TRIGGER_NETDEV_LINK_100] = YT921X_LEDx_PORT_ACT_100M,
+ [TRIGGER_NETDEV_LINK_1000] = YT921X_LEDx_PORT_ACT_1000M,
+ [TRIGGER_NETDEV_HALF_DUPLEX] = YT921X_LEDx_PORT_ACT_DUPLEX_HALF,
+ [TRIGGER_NETDEV_FULL_DUPLEX] = YT921X_LEDx_PORT_ACT_DUPLEX_FULL,
+ [TRIGGER_NETDEV_TX] = YT921X_LEDx_PORT_ACT_TX,
+ [TRIGGER_NETDEV_RX] = YT921X_LEDx_PORT_ACT_RX,
+};
+
+static bool
+yt921x_led_trigger_is_supported(const struct yt921x_priv *priv, int port,
+ int group, unsigned long flags)
+{
+ unsigned int i;
+
+ for_each_set_bit(i, &flags, __TRIGGER_NETDEV_MAX)
+ if (!yt921x_led_trigger_maps[i])
+ return false;
+
+ return true;
+}
+
+static int
+yt921x_led_trigger_get(struct yt921x_priv *priv, int port, int group,
+ unsigned long *flagsp)
+{
+ u32 addr;
+ u32 val;
+ int res;
+
+ addr = yt921x_led_regaddr(priv, port, group);
+ res = yt921x_reg_read(priv, addr, &val);
+ if (res)
+ return res;
+
+ *flagsp = 0;
+ for (unsigned int i = 0; i < __TRIGGER_NETDEV_MAX; i++) {
+ u32 mask = yt921x_led_trigger_maps[i];
+
+ if (mask && (val & mask) == mask)
+ *flagsp |= BIT(i);
+ }
+
+ return 0;
+}
+
+static int
+yt921x_led_trigger_set(struct yt921x_priv *priv, int port, int group,
+ unsigned long flags)
+{
+ struct yt921x_port *pp = &priv->ports[port];
+ struct yt921x_led *led = pp->leds[group];
+ unsigned int i;
+ u32 addr;
+ u32 ctrl;
+ u32 mask;
+ int res;
+
+ ctrl = 0;
+ for_each_set_bit(i, &flags, __TRIGGER_NETDEV_MAX) {
+ if (!yt921x_led_trigger_maps[i])
+ return -EOPNOTSUPP;
+
+ ctrl |= yt921x_led_trigger_maps[i];
+ }
+
+ led->use_cycle = false;
+ led->use_duty = false;
+
+ mask = !group ? YT921X_LED0_PORT_ACT_M : YT921X_LEDx_PORT_ACT_M;
+ if (group == 2) {
+ mask |= YT921X_LED2_PORT_FORCEn_M(group);
+ ctrl |= YT921X_LED2_PORT_FORCEn_DONTCARE(group);
+ }
+ addr = yt921x_led_regaddr(priv, port, group);
+ res = yt921x_reg_update_bits(priv, addr, mask, ctrl);
+ if (res)
+ return res;
+
+ if (group != 2) {
+ mask = YT921X_LED2_PORT_FORCEn_M(group);
+ ctrl = YT921X_LED2_PORT_FORCEn_DONTCARE(group);
+ res = yt921x_reg_update_bits(priv, YT921X_LED2_PORTn(port),
+ mask, ctrl);
+ if (res)
+ return res;
+ }
+
+ return 0;
+}
+
+static int
+yt921x_cled_brightness_set_blocking(struct led_classdev *led_cdev,
+ enum led_brightness brightness)
+{
+ struct yt921x_led *led = to_yt921x_led(led_cdev);
+ struct yt921x_port *pp = to_yt921x_port(led);
+ struct yt921x_priv *priv = to_yt921x_priv(pp);
+ int res;
+
+ mutex_lock(&priv->reg_lock);
+ res = yt921x_led_force_set(priv, pp->index, led->group, brightness);
+ mutex_unlock(&priv->reg_lock);
+
+ return res;
+}
+
+static int
+yt921x_cled_blink_set(struct led_classdev *led_cdev, unsigned long *delay_on,
+ unsigned long *delay_off)
+{
+ struct yt921x_led *led = to_yt921x_led(led_cdev);
+ struct yt921x_port *pp = to_yt921x_port(led);
+ struct yt921x_priv *priv = to_yt921x_priv(pp);
+ int res;
+
+ mutex_lock(&priv->reg_lock);
+ res = yt921x_led_blink_set(priv, pp->index, led->group, delay_on,
+ delay_off);
+ mutex_unlock(&priv->reg_lock);
+
+ return res;
+}
+
+static struct device * __maybe_unused
+yt921x_cled_hw_control_get_device(struct led_classdev *led_cdev)
+{
+ struct yt921x_led *led = to_yt921x_led(led_cdev);
+ struct yt921x_port *pp = to_yt921x_port(led);
+ struct yt921x_priv *priv = to_yt921x_priv(pp);
+ struct dsa_port *dp;
+
+ dp = dsa_to_port(&priv->ds, pp->index);
+ if (!dp || !dp->user)
+ return NULL;
+ return &dp->user->dev;
+}
+
+static int __maybe_unused
+yt921x_cled_hw_control_is_supported(struct led_classdev *led_cdev,
+ unsigned long flags)
+{
+ struct yt921x_led *led = to_yt921x_led(led_cdev);
+ struct yt921x_port *pp = to_yt921x_port(led);
+ struct yt921x_priv *priv = to_yt921x_priv(pp);
+
+ if (yt921x_led_trigger_is_supported(priv, pp->index, led->group, flags))
+ return 0;
+ return -EOPNOTSUPP;
+}
+
+static int __maybe_unused
+yt921x_cled_hw_control_get(struct led_classdev *led_cdev, unsigned long *flagsp)
+{
+ struct yt921x_led *led = to_yt921x_led(led_cdev);
+ struct yt921x_port *pp = to_yt921x_port(led);
+ struct yt921x_priv *priv = to_yt921x_priv(pp);
+ int res;
+
+ mutex_lock(&priv->reg_lock);
+ res = yt921x_led_trigger_get(priv, pp->index, led->group, flagsp);
+ mutex_unlock(&priv->reg_lock);
+
+ return res;
+}
+
+static int __maybe_unused
+yt921x_cled_hw_control_set(struct led_classdev *led_cdev, unsigned long flags)
+{
+ struct yt921x_led *led = to_yt921x_led(led_cdev);
+ struct yt921x_port *pp = to_yt921x_port(led);
+ struct yt921x_priv *priv = to_yt921x_priv(pp);
+ int res;
+
+ mutex_lock(&priv->reg_lock);
+ res = yt921x_led_trigger_set(priv, pp->index, led->group, flags);
+ mutex_unlock(&priv->reg_lock);
+
+ return res;
+}
+
+static int
+yt921x_led_setup(struct yt921x_priv *priv, int port,
+ struct fwnode_handle *fwnode, int *polarityp)
+{
+ struct yt921x_port *pp = &priv->ports[port];
+ struct device *dev = to_device(priv);
+ struct led_init_data init_data;
+ struct led_classdev *led_cdev;
+ char name[LED_MAX_NAME_SIZE];
+ enum led_default_state state;
+ struct yt921x_led *led;
+ bool force_high;
+ bool force_low;
+ int polarity;
+ u32 group;
+ bool on;
+ int res;
+
+ if (port == YT921X_PORT_MCU) {
+ dev_err(dev, "No LEDs for port %d\n", port);
+ return -ENODEV;
+ }
+
+ res = fwnode_property_read_u32(fwnode, "reg", &group);
+ if (res)
+ return res;
+ if (group >= YT921X_LED_GROUP_NUM) {
+ dev_err(dev, "Invalid LED reg %u for port %d\n", group, port);
+ return -EINVAL;
+ }
+
+ force_high = fwnode_property_read_bool(fwnode, "active-high");
+ force_low = fwnode_property_read_bool(fwnode, "active-low");
+ if (force_high && force_low) {
+ dev_err(dev, "Duplicate polarities for LED %02d:%02u\n",
+ group, port);
+ return -EINVAL;
+ }
+ polarity = force_high ? 1 : force_low ? -1 : 0;
+
+ led = devm_kzalloc(dev, sizeof(*led), GFP_KERNEL);
+ if (!led)
+ return -ENOMEM;
+ pp->leds[group] = led;
+
+ led->port = pp;
+ led->group = group;
+
+ state = led_init_default_state_get(fwnode);
+ mutex_lock(&priv->reg_lock);
+ switch (state) {
+ case LEDS_DEFSTATE_OFF:
+ case LEDS_DEFSTATE_ON:
+ on = state != LEDS_DEFSTATE_OFF;
+ res = yt921x_led_force_set(priv, port, group, on);
+ break;
+ case LEDS_DEFSTATE_KEEP:
+ res = yt921x_led_force_get(priv, port, group, &on);
+ break;
+ }
+ mutex_unlock(&priv->reg_lock);
+ if (res)
+ goto err;
+
+ led_cdev = &led->cdev;
+ led_cdev->brightness = on;
+ led_cdev->max_brightness = 1;
+ led_cdev->flags = LED_RETAIN_AT_SHUTDOWN;
+ led_cdev->brightness_set_blocking = yt921x_cled_brightness_set_blocking;
+ led_cdev->blink_set = yt921x_cled_blink_set;
+#ifdef CONFIG_LEDS_TRIGGERS
+ led_cdev->hw_control_trigger = "netdev";
+ led_cdev->hw_control_get_device = yt921x_cled_hw_control_get_device;
+ led_cdev->hw_control_is_supported = yt921x_cled_hw_control_is_supported;
+ led_cdev->hw_control_get = yt921x_cled_hw_control_get;
+ led_cdev->hw_control_set = yt921x_cled_hw_control_set;
+#endif
+
+ snprintf(name, sizeof(name), YT921X_NAME "-%u:%02d:%02u",
+ priv->ds.index, port, group);
+ init_data = (typeof(init_data)){
+ .fwnode = fwnode,
+ .devicename = name,
+ .devname_mandatory = true,
+ };
+ res = devm_led_classdev_register_ext(dev, led_cdev, &init_data);
+ if (res)
+ goto err;
+
+ *polarityp = polarity;
+ return 0;
+
+err:
+ pp->leds[group] = NULL;
+ devm_kfree(dev, led);
+ return res;
+}
+
+static void yt921x_leds_teardown(struct yt921x_priv *priv)
+{
+ struct device *dev = to_device(priv);
+ u32 mask;
+ int res;
+
+ for (int port = 0; port < YT921X_PORT_NUM; port++) {
+ struct yt921x_port *pp = &priv->ports[port];
+
+ for (int group = 0; group < YT921X_LED_GROUP_NUM; group++) {
+ struct yt921x_led *led = pp->leds[group];
+
+ if (!led)
+ continue;
+
+ devm_led_classdev_unregister(dev, &led->cdev);
+ pp->leds[group] = NULL;
+ devm_kfree(dev, led);
+ }
+ }
+
+ mutex_lock(&priv->reg_lock);
+
+ res = yt921x_reg_write(priv, YT921X_LED_PAR_PORTS, 0);
+ if (res)
+ goto end;
+
+ mask = YT921X_LED_CTRL_PORT_NUM_M | YT921X_LED_CTRL_EN;
+ res = yt921x_reg_clear_bits(priv, YT921X_LED_CTRL, mask);
+
+end:
+ mutex_unlock(&priv->reg_lock);
+
+ if (res)
+ dev_err(dev, "Failed to teardown LEDs: %d\n", res);
+}
+
+int yt921x_leds_setup(struct yt921x_priv *priv)
+{
+ struct dsa_switch *ds = &priv->ds;
+ struct dsa_port *dp;
+ u32 inv_ctrl = 0;
+ u32 inv_mask = 0;
+ u32 ctrl;
+ u32 mask;
+ int res;
+
+ dsa_switch_for_each_port(dp, ds) {
+ struct device_node *leds_np;
+ int port = dp->index;
+
+ if (!dp->dn)
+ continue;
+
+ leds_np = of_get_child_by_name(dp->dn, "leds");
+ if (!leds_np)
+ continue;
+
+ for_each_child_of_node_scoped(leds_np, led_np) {
+ int polarity;
+
+ res = yt921x_led_setup(priv, port,
+ of_fwnode_handle(led_np),
+ &polarity);
+ if (res) {
+ of_node_put(leds_np);
+ goto err;
+ }
+
+ if (polarity) {
+ inv_mask |= BIT(port);
+ if (polarity > 0)
+ inv_ctrl |= BIT(port);
+ }
+ }
+
+ of_node_put(leds_np);
+ }
+
+ mutex_lock(&priv->reg_lock);
+
+ mask = YT921X_LED_CTRL_MODE_M | YT921X_LED_CTRL_PORT_NUM_M |
+ YT921X_LED_CTRL_EN;
+ ctrl = YT921X_LED_CTRL_MODE_PARALLEL |
+ YT921X_LED_CTRL_PORT_NUM(YT921X_PORT_NUM - 1) |
+ YT921X_LED_CTRL_EN;
+ res = yt921x_reg_update_bits(priv, YT921X_LED_CTRL, mask, ctrl);
+ if (res)
+ goto end;
+
+ /* Inversion is internal - force on will give low logic.
+ * In the rest of the file, treat LEDs as if active-low.
+ */
+ if (inv_mask)
+ res = yt921x_reg_update_bits(priv, YT921X_LED_PAR_INV, inv_mask,
+ inv_ctrl);
+
+end:
+ mutex_unlock(&priv->reg_lock);
+
+ if (res)
+ goto err;
+
+ return 0;
+
+err:
+ yt921x_leds_teardown(priv);
+ return res;
+}
diff --git a/drivers/net/dsa/motorcomm/leds.h b/drivers/net/dsa/motorcomm/leds.h
new file mode 100644
index 000000000000..bbd207e09a8d
--- /dev/null
+++ b/drivers/net/dsa/motorcomm/leds.h
@@ -0,0 +1,118 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (c) 2026 David Yang
+ */
+
+#ifndef _YT_LEDS_H
+#define _YT_LEDS_H
+
+#include <linux/bitfield.h>
+#include <linux/bits.h>
+#include <linux/kconfig.h>
+#include <linux/leds.h>
+
+#define YT921X_LED_CTRL 0xd0000
+#define YT921X_LED_CTRL_EN BIT(21)
+#define YT921X_LED_CTRL_LOOPDETECT_BLINK_M GENMASK(20, 19) /* cycle = 512 * x ms */
+#define YT921X_LED_CTRL_LOOPDETECT_BLINK(x) FIELD_PREP(YT921X_LED_CTRL_LOOPDETECT_BLINK_M, (x))
+#define YT921X_LED_CTRL_PORT_NUM_M GENMASK(16, 13)
+#define YT921X_LED_CTRL_PORT_NUM(x) FIELD_PREP(YT921X_LED_CTRL_PORT_NUM_M, (x))
+#define YT921X_LED_CTRL_MODE_M GENMASK(1, 0)
+#define YT921X_LED_CTRL_MODE(x) FIELD_PREP(YT921X_LED_CTRL_MODE_M, (x))
+#define YT921X_LED_CTRL_MODE_PARALLEL YT921X_LED_CTRL_MODE(0)
+#define YT921X_LED_CTRL_MODE_SERIAL YT921X_LED_CTRL_MODE(2)
+#define YT921X_LED0_PORTn(port) (0xd0004 + 4 * (port))
+#define YT921X_LED0_PORT_ACT_M GENMASK(17, 0)
+#define YT921X_LED0_PORT_ACT_LINK_TRY_DIS BIT(17)
+#define YT921X_LED0_PORT_ACT_COLLISION_BLINK_INDI BIT(16)
+#define YT921X_LED1_PORTn(port) (0xd0040 + 4 * (port))
+#define YT921X_LED1_PORT_OTHER_BLINK_M GENMASK(31, 30) /* cycle = 512 >> x ms */
+#define YT921X_LED1_PORT_OTHER_BLINK(x) FIELD_PREP(YT921X_LED1_PORT_OTHER_BLINK_M, (x))
+#define YT921X_LED1_PORT_EEE_BLINK_M GENMASK(29, 28) /* cycle = 512 >> x ms */
+#define YT921X_LED1_PORT_EEE_BLINK(x) FIELD_PREP(YT921X_LED1_PORT_EEE_BLINK_M, (x))
+#define YT921X_LED1_PORT_BLINK_DUTY_COMP BIT(27)
+#define YT921X_LED1_PORT_BLINK_DUTY_M GENMASK(26, 25)
+#define YT921X_LED1_PORT_BLINK_DUTY(x) FIELD_PREP(YT921X_LED1_PORT_BLINK_DUTY_M, (x))
+#define YT921X_LED1_PORT_BLINK_DUTY_1_2 YT921X_LED1_PORT_BLINK_DUTY(0)
+#define YT921X_LED1_PORT_BLINK_DUTY_2_3 YT921X_LED1_PORT_BLINK_DUTY(1)
+#define YT921X_LED1_PORT_BLINK_DUTY_3_4 YT921X_LED1_PORT_BLINK_DUTY(2)
+#define YT921X_LED1_PORT_BLINK_DUTY_5_6 YT921X_LED1_PORT_BLINK_DUTY(3)
+#define YT921X_LED2_PORTn(port) (0xd0080 + 4 * (port))
+#define YT921X_LED2_PORT_FORCEn_M(grp) GENMASK(4 * (grp) + 19, 4 * (grp) + 18)
+#define YT921X_LED2_PORT_FORCEn(grp, x) ((x) << (4 * (grp) + 18))
+#define YT921X_LED2_PORT_FORCEn_DONTCARE(grp) YT921X_LED2_PORT_FORCEn(grp, 0)
+#define YT921X_LED2_PORT_FORCEn_BLINK(grp) YT921X_LED2_PORT_FORCEn(grp, 1)
+#define YT921X_LED2_PORT_FORCEn_ON(grp) YT921X_LED2_PORT_FORCEn(grp, 2)
+#define YT921X_LED2_PORT_FORCEn_OFF(grp) YT921X_LED2_PORT_FORCEn(grp, 3)
+#define YT921X_LED2_PORT_FORCE_BLINKn_M(grp) GENMASK(4 * (grp) + 17, 4 * (grp) + 16) /* cycle = 512 << x ms */
+#define YT921X_LED2_PORT_FORCE_BLINKn(grp, x) ((x) << (4 * (grp) + 16))
+#define YT921X_LED2_PORT_FORCE_BLINKn_OTHER(grp) YT921X_LED2_PORT_FORCE_BLINKn(grp, 3)
+#define YT921X_LEDx_PORT_ACT_M GENMASK(15, 0)
+#define YT921X_LEDx_PORT_ACT_EEE_BLINK BIT(15)
+#define YT921X_LEDx_PORT_ACT_LOOPDETECT_BLINK BIT(14)
+#define YT921X_LEDx_PORT_ACT_ACTIVE_BLINK BIT(13)
+#define YT921X_LEDx_PORT_ACT_DUPLEX_FULL BIT(12)
+#define YT921X_LEDx_PORT_ACT_DUPLEX_HALF BIT(11)
+#define YT921X_LEDx_PORT_ACT_TX_BLINK BIT(10)
+#define YT921X_LEDx_PORT_ACT_RX_BLINK BIT(9)
+#define YT921X_LEDx_PORT_ACT_TX BIT(8)
+#define YT921X_LEDx_PORT_ACT_RX BIT(7)
+#define YT921X_LEDx_PORT_ACT_1000M BIT(6)
+#define YT921X_LEDx_PORT_ACT_100M BIT(5)
+#define YT921X_LEDx_PORT_ACT_10M BIT(4)
+#define YT921X_LEDx_PORT_ACT_COLLISION_BLINK BIT(3)
+#define YT921X_LEDx_PORT_ACT_1000M_BLINK BIT(2)
+#define YT921X_LEDx_PORT_ACT_100M_BLINK BIT(1)
+#define YT921X_LEDx_PORT_ACT_10M_BLINK BIT(0)
+#define YT921X_LED_SER_CTRL 0xd0100
+#define YT921X_LED_SER_CTRL_UNK GENMASK(25, 24) /* delay? */
+#define YT921X_LED_SER_CTRL_ACTIVE_LOW BIT(4)
+#define YT921X_LED_SER_CTRL_GRP_NUM_M GENMASK(1, 0) /* #grp - 1 */
+#define YT921X_LED_SER_CTRL_GRP_NUM(x) FIELD_PREP(YT921X_LED_SER_CTRL_GRP_NUM_M, (x))
+#define YT921X_LED_SER_MAPnm(grp, port) (0xd0104 + 8 * (2 - (grp)) + 4 * ((port) / 5))
+#define YT921X_LED_SER_MAP_DSTn_PORT_M(port) GENMASK(6 * ((port) % 5) + 5, 6 * ((port) % 5) + 2)
+#define YT921X_LED_SER_MAP_DSTn_PORT(port, x) ((x) << (6 * ((port) % 5) + 2))
+#define YT921X_LED_SER_MAP_DSTn_LED_M(port) GENMASK(6 * ((port) % 5) + 1, 6 * ((port) % 5))
+#define YT921X_LED_SER_MAP_DSTn_LED(port, x) ((x) << (6 * ((port) % 5)))
+#define YT921X_LED_PAR_PORTS 0xd01c4
+#define YT921X_LED_PAR_INV 0xd01c8
+#define YT921X_LED_PAR_INV_INVnm(grp, port) BIT(10 * (grp) + (port))
+#define YT921X_LED_PAR_MAPn(port) (0xd01d0 + 4 * (port))
+#define YT921X_LED_PAR_MAP_DSTn_PORT_M(grp) GENMASK(6 * (grp) + 5, 6 * (grp) + 2)
+#define YT921X_LED_PAR_MAP_DSTn_PORT(grp, x) ((x) << (6 * (grp) + 2))
+#define YT921X_LED_PAR_MAP_DSTn_LED_M(grp) GENMASK(6 * (grp) + 1, 6 * (grp))
+#define YT921X_LED_PAR_MAP_DSTn_LED(grp, x) ((x) << (6 * (grp)))
+
+#define YT921X_LED_BLINK_MIN 64
+#define YT921X_LED_BLINK_DEF 512
+#define YT921X_LED_BLINK_MAX 2048
+
+/* 2 * lcm(2, 3, 4, 6) */
+#define YT921X_LED_DUTY_DENOM 24
+#define YT921X_LED_DUTY(nom, denom) (YT921X_LED_DUTY_DENOM * (nom) / (denom))
+
+struct yt921x_priv;
+
+struct yt921x_led {
+ struct led_classdev cdev;
+ struct yt921x_port *port;
+ unsigned char group;
+
+ bool use_cycle:1;
+ bool use_duty:1;
+};
+
+#if IS_ENABLED(CONFIG_NET_DSA_YT921X_LEDS)
+
+int yt921x_leds_setup(struct yt921x_priv *priv);
+
+#else
+
+static inline int yt921x_leds_setup(struct yt921x_priv *priv)
+{
+ return 0;
+}
+
+#endif
+
+#endif
--
2.53.0
^ permalink raw reply related
* Re: [PATCH net] selftests/tc-testing: Add tests that force multiq and taprio to enqueue to child's gso_skb
From: Victor Nogueira @ 2026-07-03 16:57 UTC (permalink / raw)
To: Paolo Abeni
Cc: netdev, hexlabsecurity, pctammela, davem, edumazet, kuba, jhs,
jiri
In-Reply-To: <e3f99de6-cf19-451f-8175-50fa30012f7f@redhat.com>
On Fri, Jul 3, 2026 at 1:37 PM Paolo Abeni <pabeni@redhat.com> wrote:
>
> On 6/30/26 5:36 PM, Victor Nogueira wrote:
> > Add test cases to reproduce scenarios fixed recently [1] where
> > multiqueue and taprio forced their children into enqueueing an skb to
> > gso_skb (during peek), but failed to dequeue from gso_skb because they
> > called the child's dequeue callback directly. This causes a desync in the
> > child's qlen/backlog and results in an eventual null-ptr-deref (with a
> > qfq or dualpi2 child).
> >
> > Test cases are the following:
> >
> > - Force multiq to dequeue from its child's gso_skb with qfq leaf (fb6c)
> > - Force multiq to dequeue from its child's gso_skb with dualpi2 leaf (1922)
> > - Force taprio to dequeue from its child's gso_skb with qfq leaf (476f)
> > - Force taprio to dequeue from its child's gso_skb with dualpi2 leaf (0235)
> >
> > [1] https://lore.kernel.org/netdev/20260625-b4-disp-31bcb279-v1-0-85c40b83c529@proton.me/
> >
> > Signed-off-by: Victor Nogueira <victor@mojatatu.com>
>
> Since the fix has been propagated to net-next, this is net-next
> material, right? no need to repost, just ack/nack, thanks!
Sorry, I didn't realise the fix had propagated.
Yes, this should go to net-next then.
cheers,
Victor
^ permalink raw reply
* [PATCH ipsec 1/8] xfrm: state: exact mark/mask match for SPI-keyed control-plane SA lookups
From: Antony Antony @ 2026-07-03 17:07 UTC (permalink / raw)
To: Antony Antony, Steffen Klassert, Herbert Xu, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
David Ahern, Jamal Hadi Salim, Shuah Khan
Cc: Sabrina Dubroca, netdev, Yan Yan, Tobias Brunner,
Florian Westphal, linux-kselftest, linux-doc
In-Reply-To: <migrate-state-fixes-v0-0-a69e8637ba3b@secunet.com>
Add __xfrm_state_lookup_exact(), an identity-match like
__xfrm_state_lookup().
Wire it into every SPI-keyed control-plane path: DELSA/GETSA, UPDSA,
GETAE/NEWAE, EXPIRE, MIGRATE_STATE.
xfrm_state_add()'s duplicate-detect keeps the wildcard
__xfrm_state_locate() - unrelated, unchanged.
Fixes: 3d6acfa7641f ("xfrm: SA lookups with mark")
Signed-off-by: Antony Antony <antony.antony@secunet.com>
---
include/net/xfrm.h | 3 ++
net/xfrm/xfrm_state.c | 80 ++++++++++++++++++++++++++++++++++++++++++++-------
net/xfrm/xfrm_user.c | 29 ++++++++++---------
3 files changed, 88 insertions(+), 24 deletions(-)
diff --git a/include/net/xfrm.h b/include/net/xfrm.h
index 519a0156a05c..f6ed590cb2ff 100644
--- a/include/net/xfrm.h
+++ b/include/net/xfrm.h
@@ -1746,6 +1746,9 @@ struct xfrm_state *xfrm_state_lookup_byaddr(struct net *net, u32 mark,
const xfrm_address_t *saddr,
u8 proto,
unsigned short family);
+struct xfrm_state *xfrm_state_lookup_exact(struct net *net, const struct xfrm_mark *mark,
+ const xfrm_address_t *daddr, __be32 spi,
+ u8 proto, unsigned short family);
#ifdef CONFIG_XFRM_SUB_POLICY
void xfrm_tmpl_sort(struct xfrm_tmpl **dst, struct xfrm_tmpl **src, int n,
unsigned short family);
diff --git a/net/xfrm/xfrm_state.c b/net/xfrm/xfrm_state.c
index c58cd024e3c6..df761ce1c290 100644
--- a/net/xfrm/xfrm_state.c
+++ b/net/xfrm/xfrm_state.c
@@ -1172,11 +1172,22 @@ static struct xfrm_state *__xfrm_state_lookup_all(const struct xfrm_hash_state_p
return NULL;
}
-static struct xfrm_state *__xfrm_state_lookup(const struct xfrm_hash_state_ptrs *state_ptrs,
- u32 mark,
- const xfrm_address_t *daddr,
- __be32 spi, u8 proto,
- unsigned short family)
+/* exact=false: data-plane wildcard match against x's mask. exact=true:
+ * control-plane identity match, mark and mask must both match exactly.
+ */
+static bool xfrm_state_mark_matches(const struct xfrm_state *x, u32 mark, u32 mask, bool exact)
+{
+ if (exact)
+ return x->mark.v == mark && x->mark.m == mask;
+ return (mark & x->mark.m) == x->mark.v;
+}
+
+static struct xfrm_state *
+__xfrm_state_lookup(const struct xfrm_hash_state_ptrs *state_ptrs,
+ u32 mark, u32 mask, bool exact,
+ const xfrm_address_t *daddr,
+ __be32 spi, u8 proto,
+ unsigned short family)
{
unsigned int h = __xfrm_spi_hash(daddr, spi, proto, family, state_ptrs->hmask);
struct xfrm_state *x;
@@ -1188,7 +1199,7 @@ static struct xfrm_state *__xfrm_state_lookup(const struct xfrm_hash_state_ptrs
!xfrm_addr_equal(&x->id.daddr, daddr, family))
continue;
- if ((mark & x->mark.m) != x->mark.v)
+ if (!xfrm_state_mark_matches(x, mark, mask, exact))
continue;
if (!xfrm_state_hold_rcu(x))
continue;
@@ -1198,6 +1209,17 @@ static struct xfrm_state *__xfrm_state_lookup(const struct xfrm_hash_state_ptrs
return NULL;
}
+static struct xfrm_state *
+__xfrm_state_lookup_exact(const struct xfrm_hash_state_ptrs *state_ptrs,
+ const struct xfrm_mark *mark,
+ const xfrm_address_t *daddr,
+ __be32 spi, u8 proto,
+ unsigned short family)
+{
+ return __xfrm_state_lookup(state_ptrs, mark->v, mark->m, true,
+ daddr, spi, proto, family);
+}
+
struct xfrm_state *xfrm_input_state_lookup(struct net *net, u32 mark,
const xfrm_address_t *daddr,
__be32 spi, u8 proto,
@@ -1228,7 +1250,7 @@ struct xfrm_state *xfrm_input_state_lookup(struct net *net, u32 mark,
xfrm_hash_ptrs_get(net, &state_ptrs);
- x = __xfrm_state_lookup(&state_ptrs, mark, daddr, spi, proto, family);
+ x = __xfrm_state_lookup(&state_ptrs, mark, 0, false, daddr, spi, proto, family);
if (x) {
spin_lock(&net->xfrm.xfrm_state_lock);
if (x->km.state != XFRM_STATE_VALID) {
@@ -1288,7 +1310,7 @@ __xfrm_state_locate(struct xfrm_state *x, int use_spi, int family)
xfrm_hash_ptrs_get(net, &state_ptrs);
if (use_spi)
- return __xfrm_state_lookup(&state_ptrs, mark, &x->id.daddr,
+ return __xfrm_state_lookup(&state_ptrs, mark, 0, false, &x->id.daddr,
x->id.spi, x->id.proto, family);
else
return __xfrm_state_lookup_byaddr(&state_ptrs, mark,
@@ -1297,6 +1319,27 @@ __xfrm_state_locate(struct xfrm_state *x, int use_spi, int family)
x->id.proto, family);
}
+/* Used by xfrm_state_update() only; xfrm_state_add()'s dup check keeps
+ * using the wildcard __xfrm_state_locate() above.
+ */
+static inline struct xfrm_state *
+__xfrm_state_locate_exact(struct xfrm_state *x, int use_spi, int family)
+{
+ struct xfrm_hash_state_ptrs state_ptrs;
+ struct net *net = xs_net(x);
+
+ xfrm_hash_ptrs_get(net, &state_ptrs);
+
+ if (use_spi)
+ return __xfrm_state_lookup_exact(&state_ptrs, &x->mark, &x->id.daddr,
+ x->id.spi, x->id.proto, family);
+ else
+ return __xfrm_state_lookup_byaddr(&state_ptrs, x->mark.v & x->mark.m,
+ &x->id.daddr,
+ &x->props.saddr,
+ x->id.proto, family);
+}
+
static void xfrm_hash_grow_check(struct net *net, int have_hash_collision)
{
if (have_hash_collision &&
@@ -2229,7 +2272,7 @@ int xfrm_state_update(struct xfrm_state *x)
to_put = NULL;
spin_lock_bh(&net->xfrm.xfrm_state_lock);
- x1 = __xfrm_state_locate(x, use_spi, x->props.family);
+ x1 = __xfrm_state_locate_exact(x, use_spi, x->props.family);
err = -ESRCH;
if (!x1)
@@ -2374,7 +2417,7 @@ xfrm_state_lookup(struct net *net, u32 mark, const xfrm_address_t *daddr, __be32
rcu_read_lock();
xfrm_hash_ptrs_get(net, &state_ptrs);
- x = __xfrm_state_lookup(&state_ptrs, mark, daddr, spi, proto, family);
+ x = __xfrm_state_lookup(&state_ptrs, mark, 0, false, daddr, spi, proto, family);
rcu_read_unlock();
return x;
}
@@ -2398,6 +2441,23 @@ xfrm_state_lookup_byaddr(struct net *net, u32 mark,
}
EXPORT_SYMBOL(xfrm_state_lookup_byaddr);
+struct xfrm_state *
+xfrm_state_lookup_exact(struct net *net, const struct xfrm_mark *mark,
+ const xfrm_address_t *daddr, __be32 spi,
+ u8 proto, unsigned short family)
+{
+ struct xfrm_hash_state_ptrs state_ptrs;
+ struct xfrm_state *x;
+
+ rcu_read_lock();
+ xfrm_hash_ptrs_get(net, &state_ptrs);
+
+ x = __xfrm_state_lookup_exact(&state_ptrs, mark, daddr, spi, proto, family);
+ rcu_read_unlock();
+ return x;
+}
+EXPORT_SYMBOL(xfrm_state_lookup_exact);
+
struct xfrm_state *
xfrm_find_acq(struct net *net, const struct xfrm_mark *mark, u8 mode, u32 reqid,
u32 if_id, u32 pcpu_num, u8 proto, const xfrm_address_t *daddr,
diff --git a/net/xfrm/xfrm_user.c b/net/xfrm/xfrm_user.c
index 6384795ee6b2..b56fca666b89 100644
--- a/net/xfrm/xfrm_user.c
+++ b/net/xfrm/xfrm_user.c
@@ -1089,11 +1089,12 @@ static struct xfrm_state *xfrm_user_state_lookup(struct net *net,
struct xfrm_state *x = NULL;
struct xfrm_mark m;
int err;
- u32 mark = xfrm_mark_get(attrs, &m);
+
+ xfrm_mark_get(attrs, &m);
if (xfrm_id_proto_match(p->proto, IPSEC_PROTO_ANY)) {
err = -ESRCH;
- x = xfrm_state_lookup(net, mark, &p->daddr, p->spi, p->proto, p->family);
+ x = xfrm_state_lookup_exact(net, &m, &p->daddr, p->spi, p->proto, p->family);
} else {
xfrm_address_t *saddr = NULL;
@@ -1104,7 +1105,7 @@ static struct xfrm_state *xfrm_user_state_lookup(struct net *net,
}
err = -ESRCH;
- x = xfrm_state_lookup_byaddr(net, mark,
+ x = xfrm_state_lookup_byaddr(net, m.v & m.m,
&p->daddr, saddr,
p->proto, p->family);
}
@@ -2788,14 +2789,13 @@ static int xfrm_get_ae(struct sk_buff *skb, struct nlmsghdr *nlh,
struct sk_buff *r_skb;
int err;
struct km_event c;
- u32 mark;
struct xfrm_mark m;
struct xfrm_aevent_id *p = nlmsg_data(nlh);
struct xfrm_usersa_id *id = &p->sa_id;
- mark = xfrm_mark_get(attrs, &m);
+ xfrm_mark_get(attrs, &m);
- x = xfrm_state_lookup(net, mark, &id->daddr, id->spi, id->proto, id->family);
+ x = xfrm_state_lookup_exact(net, &m, &id->daddr, id->spi, id->proto, id->family);
if (x == NULL)
return -ESRCH;
@@ -2836,7 +2836,6 @@ static int xfrm_new_ae(struct sk_buff *skb, struct nlmsghdr *nlh,
struct xfrm_state *x;
struct km_event c;
int err = -EINVAL;
- u32 mark = 0;
struct xfrm_mark m;
struct xfrm_aevent_id *p = nlmsg_data(nlh);
struct nlattr *rp = attrs[XFRMA_REPLAY_VAL];
@@ -2856,9 +2855,10 @@ static int xfrm_new_ae(struct sk_buff *skb, struct nlmsghdr *nlh,
return err;
}
- mark = xfrm_mark_get(attrs, &m);
+ xfrm_mark_get(attrs, &m);
- x = xfrm_state_lookup(net, mark, &p->sa_id.daddr, p->sa_id.spi, p->sa_id.proto, p->sa_id.family);
+ x = xfrm_state_lookup_exact(net, &m, &p->sa_id.daddr, p->sa_id.spi,
+ p->sa_id.proto, p->sa_id.family);
if (x == NULL)
return -ESRCH;
@@ -2992,9 +2992,10 @@ static int xfrm_add_sa_expire(struct sk_buff *skb, struct nlmsghdr *nlh,
struct xfrm_user_expire *ue = nlmsg_data(nlh);
struct xfrm_usersa_info *p = &ue->state;
struct xfrm_mark m;
- u32 mark = xfrm_mark_get(attrs, &m);
- x = xfrm_state_lookup(net, mark, &p->id.daddr, p->id.spi, p->id.proto, p->family);
+ xfrm_mark_get(attrs, &m);
+
+ x = xfrm_state_lookup_exact(net, &m, &p->id.daddr, p->id.spi, p->id.proto, p->family);
err = -ENOENT;
if (x == NULL)
@@ -3361,9 +3362,9 @@ static int xfrm_do_migrate_state(struct sk_buff *skb, struct nlmsghdr *nlh,
copy_from_user_migrate_state(&m, um);
- x = xfrm_state_lookup(net, m.old_mark.v & m.old_mark.m,
- &um->id.daddr, um->id.spi,
- um->id.proto, um->id.family);
+ x = xfrm_state_lookup_exact(net, &m.old_mark,
+ &um->id.daddr, um->id.spi,
+ um->id.proto, um->id.family);
if (!x) {
NL_SET_ERR_MSG(extack, "Can not find state");
return -ESRCH;
--
2.47.3
^ permalink raw reply related
* [PATCH ipsec 2/8] xfrm: state: exact mark/mask match for by-address control-plane SA lookups
From: Antony Antony @ 2026-07-03 17:07 UTC (permalink / raw)
To: Antony Antony, Steffen Klassert, Herbert Xu, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
David Ahern, Jamal Hadi Salim, Shuah Khan
Cc: Sabrina Dubroca, netdev, Yan Yan, Tobias Brunner,
Florian Westphal, linux-kselftest, linux-doc
In-Reply-To: <migrate-state-fixes-v0-0-a69e8637ba3b@secunet.com>
Add mark/mask/exact parameters directly to __xfrm_state_lookup_byaddr(),
reusing the SPI-keyed lookup's comparison.
Use __xfrm_state_locate_exact()'s by-address branch UPDSA
and xfrm_user_state_lookup()'s by-address branch
(DELSA/GETSA for non-SPI-keyed lookups).
Fixes: 3d6acfa7641f ("xfrm: SA lookups with mark")
Signed-off-by: Antony Antony <antony.antony@secunet.com>
---
include/net/xfrm.h | 2 +-
net/ipv6/xfrm6_input.c | 2 +-
net/xfrm/xfrm_state.c | 21 +++++++++++----------
net/xfrm/xfrm_user.c | 2 +-
4 files changed, 14 insertions(+), 13 deletions(-)
diff --git a/include/net/xfrm.h b/include/net/xfrm.h
index f6ed590cb2ff..ebe514376254 100644
--- a/include/net/xfrm.h
+++ b/include/net/xfrm.h
@@ -1741,7 +1741,7 @@ struct xfrm_state *xfrm_input_state_lookup(struct net *net, u32 mark,
const xfrm_address_t *daddr,
__be32 spi, u8 proto,
unsigned short family);
-struct xfrm_state *xfrm_state_lookup_byaddr(struct net *net, u32 mark,
+struct xfrm_state *xfrm_state_lookup_byaddr(struct net *net, u32 mark, u32 mask, bool exact,
const xfrm_address_t *daddr,
const xfrm_address_t *saddr,
u8 proto,
diff --git a/net/ipv6/xfrm6_input.c b/net/ipv6/xfrm6_input.c
index 89d0443b5307..2dd347fece52 100644
--- a/net/ipv6/xfrm6_input.c
+++ b/net/ipv6/xfrm6_input.c
@@ -272,7 +272,7 @@ int xfrm6_input_addr(struct sk_buff *skb, xfrm_address_t *daddr,
break;
}
- x = xfrm_state_lookup_byaddr(net, skb->mark, dst, src, proto, AF_INET6);
+ x = xfrm_state_lookup_byaddr(net, skb->mark, 0, false, dst, src, proto, AF_INET6);
if (!x)
continue;
diff --git a/net/xfrm/xfrm_state.c b/net/xfrm/xfrm_state.c
index df761ce1c290..d78cfe481f75 100644
--- a/net/xfrm/xfrm_state.c
+++ b/net/xfrm/xfrm_state.c
@@ -1274,11 +1274,12 @@ struct xfrm_state *xfrm_input_state_lookup(struct net *net, u32 mark,
}
EXPORT_SYMBOL(xfrm_input_state_lookup);
-static struct xfrm_state *__xfrm_state_lookup_byaddr(const struct xfrm_hash_state_ptrs *state_ptrs,
- u32 mark,
- const xfrm_address_t *daddr,
- const xfrm_address_t *saddr,
- u8 proto, unsigned short family)
+static struct xfrm_state *
+__xfrm_state_lookup_byaddr(const struct xfrm_hash_state_ptrs *state_ptrs,
+ u32 mark, u32 mask, bool exact,
+ const xfrm_address_t *daddr,
+ const xfrm_address_t *saddr,
+ u8 proto, unsigned short family)
{
unsigned int h = __xfrm_src_hash(daddr, saddr, family, state_ptrs->hmask);
struct xfrm_state *x;
@@ -1290,7 +1291,7 @@ static struct xfrm_state *__xfrm_state_lookup_byaddr(const struct xfrm_hash_stat
!xfrm_addr_equal(&x->props.saddr, saddr, family))
continue;
- if ((mark & x->mark.m) != x->mark.v)
+ if (!xfrm_state_mark_matches(x, mark, mask, exact))
continue;
if (!xfrm_state_hold_rcu(x))
continue;
@@ -1313,7 +1314,7 @@ __xfrm_state_locate(struct xfrm_state *x, int use_spi, int family)
return __xfrm_state_lookup(&state_ptrs, mark, 0, false, &x->id.daddr,
x->id.spi, x->id.proto, family);
else
- return __xfrm_state_lookup_byaddr(&state_ptrs, mark,
+ return __xfrm_state_lookup_byaddr(&state_ptrs, mark, 0, false,
&x->id.daddr,
&x->props.saddr,
x->id.proto, family);
@@ -1334,7 +1335,7 @@ __xfrm_state_locate_exact(struct xfrm_state *x, int use_spi, int family)
return __xfrm_state_lookup_exact(&state_ptrs, &x->mark, &x->id.daddr,
x->id.spi, x->id.proto, family);
else
- return __xfrm_state_lookup_byaddr(&state_ptrs, x->mark.v & x->mark.m,
+ return __xfrm_state_lookup_byaddr(&state_ptrs, x->mark.v, x->mark.m, true,
&x->id.daddr,
&x->props.saddr,
x->id.proto, family);
@@ -2424,7 +2425,7 @@ xfrm_state_lookup(struct net *net, u32 mark, const xfrm_address_t *daddr, __be32
EXPORT_SYMBOL(xfrm_state_lookup);
struct xfrm_state *
-xfrm_state_lookup_byaddr(struct net *net, u32 mark,
+xfrm_state_lookup_byaddr(struct net *net, u32 mark, u32 mask, bool exact,
const xfrm_address_t *daddr, const xfrm_address_t *saddr,
u8 proto, unsigned short family)
{
@@ -2435,7 +2436,7 @@ xfrm_state_lookup_byaddr(struct net *net, u32 mark,
xfrm_hash_ptrs_get(net, &state_ptrs);
- x = __xfrm_state_lookup_byaddr(&state_ptrs, mark, daddr, saddr, proto, family);
+ x = __xfrm_state_lookup_byaddr(&state_ptrs, mark, mask, exact, daddr, saddr, proto, family);
rcu_read_unlock();
return x;
}
diff --git a/net/xfrm/xfrm_user.c b/net/xfrm/xfrm_user.c
index b56fca666b89..11ec3b14a42f 100644
--- a/net/xfrm/xfrm_user.c
+++ b/net/xfrm/xfrm_user.c
@@ -1105,7 +1105,7 @@ static struct xfrm_state *xfrm_user_state_lookup(struct net *net,
}
err = -ESRCH;
- x = xfrm_state_lookup_byaddr(net, m.v & m.m,
+ x = xfrm_state_lookup_byaddr(net, m.v, m.m, true,
&p->daddr, saddr,
p->proto, p->family);
}
--
2.47.3
^ permalink raw reply related
* [PATCH ipsec 3/8] selftests: net: xfrm_state: add mark shadowing tests for state lookups
From: Antony Antony @ 2026-07-03 17:07 UTC (permalink / raw)
To: Antony Antony, Steffen Klassert, Herbert Xu, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
David Ahern, Jamal Hadi Salim, Shuah Khan
Cc: Sabrina Dubroca, netdev, Yan Yan, Tobias Brunner,
Florian Westphal, linux-kselftest, linux-doc
In-Reply-To: <migrate-state-fixes-v0-0-a69e8637ba3b@secunet.com>
Add SPI and by-address wildcard-mark shadowing tests.
Signed-off-by: Antony Antony <antony.antony@secunet.com>
---
tools/testing/selftests/net/xfrm_state.sh | 130 +++++++++++++++++++++++++++++-
1 file changed, 129 insertions(+), 1 deletion(-)
diff --git a/tools/testing/selftests/net/xfrm_state.sh b/tools/testing/selftests/net/xfrm_state.sh
index f6c54a6496d7..f202073726a9 100755
--- a/tools/testing/selftests/net/xfrm_state.sh
+++ b/tools/testing/selftests/net/xfrm_state.sh
@@ -42,7 +42,11 @@ tests="
mtu_ipv4_r2 IPv4 MTU exceeded from ESP router r2
mtu_ipv6_r2 IPv6 MTU exceeded from ESP router r2
mtu_ipv4_r3 IPv4 MTU exceeded from router r3
- mtu_ipv6_r3 IPv6 MTU exceeded from router r3"
+ mtu_ipv6_r3 IPv6 MTU exceeded from router r3
+ mark_wildcard_shadow mark: wildcard SA in by-spi state get lookup
+ mark_wildcard_delete mark: wildcard SA in by-spi state delete
+ mark_wildcard_get_addr mark: wildcard SA in by-address get lookup
+ mark_wildcard_delete_addr mark: wildcard SA in by-address delete"
prefix4="10.1"
prefix6="fc00"
@@ -101,6 +105,10 @@ run_test() {
mtu_ipv6_r2) test_mtu_ipv6_r2 ;;
mtu_ipv4_r3) test_mtu_ipv4_r3 ;;
mtu_ipv6_r3) test_mtu_ipv6_r3 ;;
+ mark_wildcard_shadow) test_mark_wildcard_shadow ;;
+ mark_wildcard_delete) test_mark_wildcard_delete ;;
+ mark_wildcard_get_addr) test_mark_wildcard_get_addr ;;
+ mark_wildcard_delete_addr) test_mark_wildcard_delete_addr ;;
esac
ret=$?
@@ -167,6 +175,8 @@ setup_namespaces() {
[ -n "${NS_S2}" ] && ns_s2=(ip netns exec "${NS_S2}") && ns_active="${ns_active} $NS_S2"
[ -n "${NS_R3}" ] && ns_r3=(ip netns exec "${NS_R3}") && ns_active="${ns_active} $NS_R3"
[ -n "${NS_B}" ] && ns_active="${ns_active} $NS_B"
+
+ return 0
}
addr_add() {
@@ -295,6 +305,18 @@ setup_ns_set_v6x() {
set_xfrm_params
}
+setup_ns_set_simple() {
+ # Single namespace, no veths/routes.
+ ns_set="a"
+ imax=1
+ src="10.1.1.1"
+ dst="10.1.1.2"
+ src_net="10.1.0.0/24"
+ dst_net="10.2.0.0/24"
+
+ set_xfrm_params
+}
+
setup_network() {
# Create veths and add addresses
local -a ns_cmd
@@ -403,6 +425,7 @@ setup() {
ns_set_v4x) setup_ns_set_v4x ;;
ns_set_v6) setup_ns_set_v6 ;;
ns_set_v6x) setup_ns_set_v6x ;;
+ ns_set_simple) setup_ns_set_simple ;;
namespaces) setup_namespaces ;;
network) setup_network ;;
xfrm) setup_xfrm ;;
@@ -548,6 +571,111 @@ test_mtu_ipv6_r3() {
return "${rc}"
}
+# SA_decoy (mark 0/0, added second) shadows SA_target (mark 1/1) on a
+# wildcard mark lookup. No traffic sent; these only exercise the SAD.
+
+test_mark_wildcard_shadow() {
+ setup ns_set_simple namespaces || return "$ksft_skip"
+ local result=0
+
+ run_cmd "${ns_a[@]}" ip xfrm state add \
+ src "${src}" dst "${dst}" proto esp spi 0x1000 \
+ reqid 100 mode tunnel \
+ aead 'rfc4106(gcm(aes))' 0x1111111111111111111111111111111111111111 96 \
+ mark 1 mask 1
+
+ run_cmd "${ns_a[@]}" ip xfrm state add \
+ src "${src}" dst "${dst}" proto esp spi 0x1000 \
+ reqid 100 mode tunnel \
+ aead 'rfc4106(gcm(aes))' 0x2222222222222222222222222222222222222222 96 \
+ mark 0 mask 0
+
+ run_cmd_err "${ns_a[@]}" ip xfrm state get \
+ dst "${dst}" proto esp spi 0x1000 \
+ mark 1 mask 1
+
+ # Expected: SA_target (mark 0x1/0x1). Actual (bug): SA_decoy (mark 0/0).
+ echo "$out" | grep -q "mark 0x1/0x1" || result=1
+
+ return "${result}"
+}
+
+test_mark_wildcard_delete() {
+ setup ns_set_simple namespaces || return "$ksft_skip"
+ local result=0
+
+ run_cmd "${ns_a[@]}" ip xfrm state add \
+ src "${src}" dst "${dst}" proto esp spi 0x1000 \
+ reqid 100 mode tunnel \
+ aead 'rfc4106(gcm(aes))' 0x1111111111111111111111111111111111111111 96 \
+ mark 1 mask 1
+
+ run_cmd "${ns_a[@]}" ip xfrm state add \
+ src "${src}" dst "${dst}" proto esp spi 0x1000 \
+ reqid 100 mode tunnel \
+ aead 'rfc4106(gcm(aes))' 0x2222222222222222222222222222222222222222 96 \
+ mark 0 mask 0
+
+ run_cmd "${ns_a[@]}" ip xfrm state delete \
+ dst "${dst}" proto esp spi 0x1000 \
+ mark 1 mask 1
+
+ run_cmd_err "${ns_a[@]}" ip xfrm state show
+ echo "$out" | grep -q "mark 0x1/0x1" && result=1
+
+ return "${result}"
+}
+
+# by-address counterpart: proto route2/hao (IPv6 mobility) have no SPI,
+# so xfrm_user_state_lookup() resolves them by address instead.
+
+test_mark_wildcard_get_addr() {
+ setup ns_set_simple namespaces || return "$ksft_skip"
+ local result=0
+ local src6="fc00:9::1"
+ local dst6="fc00:9::2"
+
+ run_cmd "${ns_a[@]}" ip xfrm state add \
+ src "${src6}" dst "${dst6}" proto route2 mode ro coa fc00:9::3 \
+ mark 1 mask 1
+
+ run_cmd "${ns_a[@]}" ip xfrm state add \
+ src "${src6}" dst "${dst6}" proto route2 mode ro coa fc00:9::4 \
+ mark 0 mask 0
+
+ run_cmd_err "${ns_a[@]}" ip xfrm state get \
+ src "${src6}" dst "${dst6}" proto route2 \
+ mark 1 mask 1
+
+ echo "$out" | grep -q "mark 0x1/0x1" || result=1
+
+ return "${result}"
+}
+
+test_mark_wildcard_delete_addr() {
+ setup ns_set_simple namespaces || return "$ksft_skip"
+ local result=0
+ local src6="fc00:9::1"
+ local dst6="fc00:9::2"
+
+ run_cmd "${ns_a[@]}" ip xfrm state add \
+ src "${src6}" dst "${dst6}" proto route2 mode ro coa fc00:9::3 \
+ mark 1 mask 1
+
+ run_cmd "${ns_a[@]}" ip xfrm state add \
+ src "${src6}" dst "${dst6}" proto route2 mode ro coa fc00:9::4 \
+ mark 0 mask 0
+
+ run_cmd "${ns_a[@]}" ip xfrm state delete \
+ src "${src6}" dst "${dst6}" proto route2 \
+ mark 1 mask 1
+
+ run_cmd_err "${ns_a[@]}" ip xfrm state show
+ echo "$out" | grep -q "mark 0x1/0x1" && result=1
+
+ return "${result}"
+}
+
################################################################################
#
usage() {
--
2.47.3
^ permalink raw reply related
* [PATCH ipsec 4/8] xfrm: fix use-after-free of migrated state in xfrm_do_migrate_state()
From: Antony Antony @ 2026-07-03 17:08 UTC (permalink / raw)
To: Antony Antony, Steffen Klassert, Herbert Xu, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
David Ahern, Jamal Hadi Salim, Shuah Khan
Cc: Sabrina Dubroca, netdev, Yan Yan, Tobias Brunner,
Florian Westphal, linux-kselftest, linux-doc, Sashiko
In-Reply-To: <migrate-state-fixes-v0-0-a69e8637ba3b@secunet.com>
A concurrent delete can free the migrated state before
xfrm_do_migrate_state() finishes using it.
Fixes: a9d155ea9b44 ("xfrm: add XFRM_MSG_MIGRATE_STATE for single SA migration")
Reported-by: Sashiko <sashiko-bot@kernel.org>
Signed-off-by: Antony Antony <antony.antony@secunet.com>
---
net/xfrm/xfrm_user.c | 3 +++
1 file changed, 3 insertions(+)
diff --git a/net/xfrm/xfrm_user.c b/net/xfrm/xfrm_user.c
index 11ec3b14a42f..29cbdc836cfc 100644
--- a/net/xfrm/xfrm_user.c
+++ b/net/xfrm/xfrm_user.c
@@ -3468,6 +3468,7 @@ static int xfrm_do_migrate_state(struct sk_buff *skb, struct nlmsghdr *nlh,
__xfrm_state_delete(x);
spin_unlock_bh(&x->lock);
+ xfrm_state_hold(xc);
err = xfrm_state_migrate_install(x, xc, &m, extack);
if (err < 0) {
/*
@@ -3475,6 +3476,7 @@ static int xfrm_do_migrate_state(struct sk_buff *skb, struct nlmsghdr *nlh,
* free under xfrm_cfg_mutex. Both SAs are gone if it does;
* restoring x would risk SN/IV reuse.
*/
+ xfrm_state_put(xc);
goto out;
}
@@ -3493,6 +3495,7 @@ static int xfrm_do_migrate_state(struct sk_buff *skb, struct nlmsghdr *nlh,
err = 0;
}
+ xfrm_state_put(xc);
out:
xfrm_state_put(x);
return err;
--
2.47.3
^ 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