* [PATCH net-next] net: dpaa_eth: convert to napi_gro_receive
From: Rosen Penev @ 2026-07-06 3:06 UTC (permalink / raw)
To: netdev
Cc: Madalin Bucur, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, open list
Replace netif_receive_skb() with napi_gro_receive() to improve receive
performance for this driver. It has rx checksum support so routing speed
shouldn't suffer.
Tested on a WatchGuard Firebox M300.
iperf3 bidir speed test:
[ ID][Role] Interval Transfer Bitrate Retr
[ 5][TX-C] 0.00-60.01 sec 5.35 GBytes 766 Mbits/sec 184 sender
[ 5][TX-C] 0.00-60.02 sec 5.35 GBytes 766 Mbits/sec receiver
[ 7][RX-C] 0.00-60.01 sec 5.50 GBytes 787 Mbits/sec 124 sender
[ 7][RX-C] 0.00-60.02 sec 5.49 GBytes 786 Mbits/sec receiver
After:
[ ID][Role] Interval Transfer Bitrate Retr
[ 5][TX-C] 0.00-60.01 sec 6.49 GBytes 929 Mbits/sec 0 sender
[ 5][TX-C] 0.00-60.02 sec 6.49 GBytes 928 Mbits/sec receiver
[ 7][RX-C] 0.00-60.01 sec 6.55 GBytes 938 Mbits/sec 0 sender
[ 7][RX-C] 0.00-60.02 sec 6.55 GBytes 937 Mbits/sec receiver
Assisted-by: Opencode:big-pickle
Signed-off-by: Rosen Penev <rosenp@gmail.com>
---
drivers/net/ethernet/freescale/dpaa/dpaa_eth.c | 5 +----
1 file changed, 1 insertion(+), 4 deletions(-)
diff --git a/drivers/net/ethernet/freescale/dpaa/dpaa_eth.c b/drivers/net/ethernet/freescale/dpaa/dpaa_eth.c
index ad2d8256eb8d..83191f636ec7 100644
--- a/drivers/net/ethernet/freescale/dpaa/dpaa_eth.c
+++ b/drivers/net/ethernet/freescale/dpaa/dpaa_eth.c
@@ -2824,10 +2824,7 @@ static enum qman_cb_dqrr_result rx_default_dqrr(struct qman_portal *portal,
skb_len = skb->len;
- if (unlikely(netif_receive_skb(skb) == NET_RX_DROP)) {
- percpu_stats->rx_dropped++;
- return qman_cb_dqrr_consume;
- }
+ napi_gro_receive(&np->napi, skb);
percpu_stats->rx_packets++;
percpu_stats->rx_bytes += skb_len;
--
2.55.0
^ permalink raw reply related
* RE: [PATCH v3 net-next 12/14] net: enetc: remove redundant num_vsi field from enetc_port_caps
From: Wei Fang (OSS) @ 2026-07-06 2:27 UTC (permalink / raw)
To: sashiko-reviews@lists.linux.dev
Cc: imx@lists.linux.dev, Frank.Li@kernel.org, network dev
In-Reply-To: <20260704104346.D7A561F00A3D@smtp.kernel.org>
> > diff --git a/drivers/net/ethernet/freescale/enetc/enetc4_debugfs.c
> b/drivers/net/ethernet/freescale/enetc/enetc4_debugfs.c
> > index be378bf8f74d1..5029038bf99fa 100644
> > --- a/drivers/net/ethernet/freescale/enetc/enetc4_debugfs.c
> > +++ b/drivers/net/ethernet/freescale/enetc/enetc4_debugfs.c
> [ ... ]
> > @@ -28,17 +28,14 @@ static void enetc_show_si_mac_hash_filter(struct
> seq_file *s, int i)
> >
> > static int enetc_mac_filter_show(struct seq_file *s, void *data)
> > {
> > - struct enetc_si *si = s->private;
> > - struct enetc_hw *hw = &si->hw;
> > + struct enetc_pf *pf = enetc_si_priv(s->private);
> > + struct enetc_hw *hw = &pf->si->hw;
> > + int num_si = pf->total_vfs + 1;
>
> [Severity: Medium]
> Could the unconstrained num_si derived from pf->total_vfs cause undefined
> behavior in the loops further down in this function?
>
> The original code used caps.num_vsi, which was structurally capped by a 4-bit
> hardware register mask. The value total_vfs is read from
> pci_sriov_get_totalvfs(), which returns an unvalidated 16-bit PCIe
> configuration value.
>
> An arbitrarily large total_vfs from a misconfigured or malicious firmware would
> cause PSIPMMR_SI_MAC_MP(i) (which evaluates to BIT(i + 16)) to shift beyond
> the bounds of its type when evaluating:
> str_enabled_disabled(PSIPMMR_SI_MAC_MP(i) & val).
Actually, the value of ECAPR1[NUM_VSI] field is equal to the value of
PCI_SRIOV_TOTAL_VF register. They are all determined by IERB register
EaVFRIDAR[NUM_VF], which is 4-bit field.
When CONFIG_PCI_IOV is disabled, pf->total_vfs is 0, all hardware resources can
be assigned to PF. When CONFIG_PCI_IOV is enabled, pf->pf->total_vfs is actually
obtained from the PCI_SRIOV_TOTAL_VF register, while the ENETC hardware only
supports a maximum of 15 VFs. And for ENETC, PCI_SRIOV_TOTAL_VF is a read
only register.
>
> > struct maft_entry_data maft;
> > struct ntmp_user *user;
> > - struct enetc_pf *pf;
> > u32 val, entry_id;
> > - int i, num_si;
> > int err = 0;
> > -
> > - pf = enetc_si_priv(si);
> > - num_si = pf->caps.num_vsi + 1;
> > + int i;
> [ ... ]
> > diff --git a/drivers/net/ethernet/freescale/enetc/enetc4_pf.c
> b/drivers/net/ethernet/freescale/enetc/enetc4_pf.c
> > index cab79f81d6fe0..8d536a3831e3a 100644
> > --- a/drivers/net/ethernet/freescale/enetc/enetc4_pf.c
> > +++ b/drivers/net/ethernet/freescale/enetc/enetc4_pf.c
> [ ... ]
> > @@ -255,34 +254,35 @@ static void enetc4_default_rings_allocation(struct
> enetc_pf *pf)
> > {
> > struct enetc_hw *hw = &pf->si->hw;
> > u32 num_rx_bdr, num_tx_bdr, val;
> > + int num_vfs = pf->total_vfs;
> > u32 vf_tx_bdr, vf_rx_bdr;
> > int i, rx_rem, tx_rem;
> >
> [ ... ]
> > - for (i = 0; i < pf->caps.num_vsi; i++) {
> > + for (i = 0; i < num_vfs; i++) {
> > vf_tx_bdr = (i < tx_rem) ? num_tx_bdr + 1 : num_tx_bdr;
> > vf_rx_bdr = (i < rx_rem) ? num_rx_bdr + 1 : num_rx_bdr;
> > val = enetc4_psicfgr0_val_construct(true, vf_tx_bdr, vf_rx_bdr);
> > - enetc_port_wr(hw, ENETC4_PSICFGR0(i + 1), val);
> > + enetc_port_wr(hw, ENETC4_PSICFGR0(i + 1), val);
>
> [Severity: Medium]
> Could this result in out-of-bounds MMIO accesses if a misconfigured or malicious
> PCIe device firmware reports a total_vfs value larger than the actual hardware
> capacity (e.g., > 15) in its SR-IOV Extended Capability?
>
> Similar to the debugfs issue above, replacing the hardware-capped
> caps.num_vsi
> with an unvalidated total_vfs causes this unconstrained loop to write past the
> instantiated ENETC4_PSICFGR0 register bounds.
For ENETC, PCI_SRIOV_TOTAL_VF is a read only register. Its value is derived
from IERB register EaVFRIDAR[NUM_VF], which is 4-bit field. So I do not think
the total_vfs will be > 15.
>
> > }
> > }
> [ ... ]
> > @@ -299,26 +299,25 @@ static void enetc4_allocate_si_rings(struct
> enetc_pf *pf)
> > static void enetc4_set_si_msix_num(struct enetc_pf *pf)
> > {
> > struct enetc_hw *hw = &pf->si->hw;
> > - int i, num_msix, total_si;
> > + int num_si = pf->total_vfs + 1;
> > + int i, num_msix;
> > u32 val;
> >
> > - total_si = pf->caps.num_vsi + 1;
> > -
> > - num_msix = pf->caps.num_msix / total_si +
> > - pf->caps.num_msix % total_si - 1;
> > + num_msix = pf->caps.num_msix / num_si +
> > + pf->caps.num_msix % num_si - 1;
> > val = num_msix & PSICFGR2_NUM_MSIX;
> > enetc_port_wr(hw, ENETC4_PSICFGR2(0), val);
>
> [Severity: High]
> Can this cause bitwise truncation and starve the PF of interrupts when SR-IOV
> is disabled?
>
> If CONFIG_PCI_IOV is disabled, pf->total_vfs evaluates to 0, making
> num_si = 1. The driver will then attempt to allocate all available hardware
> MSI-X vectors to the PF (num_msix = pf->caps.num_msix - 1).
>
> If the hardware supports more than 64 MSI-X vectors, num_msix will exceed the
> 6-bit width (max 63) of the hardware register field PSICFGR2_NUM_MSIX.
> The bitwise AND operator & PSICFGR2_NUM_MSIX truncates the value instead
> of
> clamping it (e.g., 64 & 0x3F == 0).
>
> This programs the PF to receive 0 vectors (effectively 1 vector due to the N-1
> encoding), starving it of necessary interrupts. The previous implementation
> divided by pf->caps.num_vsi + 1, which structurally guarded against this
> large allocation.
This is not a problem for current SoCs, because current SoCs support a maximum
of 64 MSI-Xs, so there will be no truncation issue. However, if future SoCs support
more than 64 MSI-Xs, this will indeed be an issue, and I will improve this logic to
better support more capable SoCs in the future.
>
> >
> > - num_msix = pf->caps.num_msix / total_si - 1;
> > + num_msix = pf->caps.num_msix / num_si - 1;
> > val = num_msix & PSICFGR2_NUM_MSIX;
> > - for (i = 0; i < pf->caps.num_vsi; i++)
> > + for (i = 0; i < pf->total_vfs; i++)
> > enetc_port_wr(hw, ENETC4_PSICFGR2(i + 1), val);
> > }
^ permalink raw reply
* RE: [PATCH v3 net-next 10/14] net: enetc: refactor SI VLAN promiscuous mode configuration
From: Wei Fang (OSS) @ 2026-07-06 1:45 UTC (permalink / raw)
To: sashiko-reviews@lists.linux.dev
Cc: Frank.Li@kernel.org, imx@lists.linux.dev, network dev
In-Reply-To: <20260704104346.232751F000E9@smtp.kernel.org>
> > diff --git a/drivers/net/ethernet/freescale/enetc/enetc_hw.h
> b/drivers/net/ethernet/freescale/enetc/enetc_hw.h
> > index 66bfda60da9cb..16da732dc5de8 100644
> > --- a/drivers/net/ethernet/freescale/enetc/enetc_hw.h
> > +++ b/drivers/net/ethernet/freescale/enetc/enetc_hw.h
> > @@ -185,9 +185,8 @@ enum enetc_bdr_type {TX, RX};
> > #define PSIPMMR_SI_MAC_MP(n) BIT((n) + 16)
> >
> > #define ENETC_PSIPVMR 0x001c
> > -#define ENETC_VLAN_PROMISC_MAP_ALL 0x7
> > -#define ENETC_PSIPVMR_SET_VP(simap) ((simap) & 0x7)
> > -#define ENETC_PSIPVMR_SET_VUTA(simap) (((simap) & 0x7) << 16)
> > +#define PSIPVMR_SI_VLAN_P(n) BIT(n) /* n = SI index */
>
> [Severity: High]
> Can this result in a shift-out-of-bounds if n >= 32?
>
No, the hardware has 16 SIs at most.
> > +
> > #define ENETC_PSIPMAR0(n) (0x0100 + (n) * 0x8) /* n = SI index */
> > #define ENETC_PSIPMAR1(n) (0x0104 + (n) * 0x8)
>
> [ ... ]
>
> > diff --git a/drivers/net/ethernet/freescale/enetc/enetc_pf.c
> b/drivers/net/ethernet/freescale/enetc/enetc_pf.c
> > index db2a800a7aaf0..afc02ed62c770 100644
> > --- a/drivers/net/ethernet/freescale/enetc/enetc_pf.c
> > +++ b/drivers/net/ethernet/freescale/enetc/enetc_pf.c
> > @@ -42,24 +42,20 @@ static void enetc_pf_destroy_pcs(struct phylink_pcs
> *pcs)
>
> [ ... ]
>
> > -static void enetc_set_vlan_promisc(struct enetc_hw *hw, char si_map)
> > +static void enetc_set_si_vlan_promisc(struct enetc_si *si, int si_id,
> > + bool promisc)
> > {
> > - u32 val = enetc_port_rd(hw, ENETC_PSIPVMR);
> > + struct enetc_hw *hw = &si->hw;
> > + u32 val;
> >
> > - val &= ~ENETC_PSIPVMR_SET_VP(ENETC_VLAN_PROMISC_MAP_ALL);
> > - enetc_port_wr(hw, ENETC_PSIPVMR, ENETC_PSIPVMR_SET_VP(si_map) |
> val);
> > -}
> > + val = enetc_port_rd(hw, ENETC_PSIPVMR);
> >
> > -static void enetc_enable_si_vlan_promisc(struct enetc_pf *pf, int si_idx)
> > -{
> > - pf->vlan_promisc_simap |= BIT(si_idx);
> > - enetc_set_vlan_promisc(&pf->si->hw, pf->vlan_promisc_simap);
> > -}
> > + if (promisc)
> > + val |= PSIPVMR_SI_VLAN_P(si_id);
> > + else
> > + val &= ~PSIPVMR_SI_VLAN_P(si_id);
>
> [Severity: High]
> If si_id >= 32, will this silently fail or corrupt the VLAN promiscuous state
> for SI 0?
>
si_id won't be >= 32, the hardware can only support a maximum of 16 SIs,
while the current SoCs support a maximum of only 3 SIs.
> Since val is a 32-bit integer, even on 64-bit architectures where BIT() won't
> overflow, the result gets truncated when applied to val here. On 32-bit
> architectures, the shift wraps (e.g. 1 << 32 becomes 1 << 0), which would
> inadvertently modify the state for the PF instead of the target VF.
>
> >
> > -static void enetc_disable_si_vlan_promisc(struct enetc_pf *pf, int si_idx)
> > -{
> > - pf->vlan_promisc_simap &= ~BIT(si_idx);
> > - enetc_set_vlan_promisc(&pf->si->hw, pf->vlan_promisc_simap);
> > + enetc_port_wr(hw, ENETC_PSIPVMR, val);
> > }
>
> [ ... ]
>
> > @@ -441,10 +437,9 @@ static void enetc_configure_port(struct enetc_pf
> *pf)
> >
> > /* split up RFS entries */
> > enetc_port_assign_rfs_entries(pf->si);
> > -
> > /* enforce VLAN promisc mode for all SIs */
> > - pf->vlan_promisc_simap = ENETC_VLAN_PROMISC_MAP_ALL;
> > - enetc_set_vlan_promisc(hw, pf->vlan_promisc_simap);
> > + for (int i = 0; i < pf->total_vfs + 1; i++)
> > + enetc_set_si_vlan_promisc(pf->si, i, true);
>
> [Severity: High]
> Does the driver validate or bound pf->total_vfs anywhere before this loop?
>
> If the hardware or hypervisor advertises a TotalVFs value >= 32, this loop
> will pass an unbounded index to enetc_set_si_vlan_promisc() and trigger the
> shift issues above. The previous implementation safely bounded this by using
> a hardcoded 0x7 mask.
pf->total_vfs is got from pci_sriov_get_totalvfs(), when CONFIG_PCI_IOV is disabled,
pf->total_vfs will be 0. When CONFIG_PCI_IOV is enabled, pf->total_vfs is actually
obtained from the PCI_SRIOV_TOTAL_VF register, while the ENETC hardware only
supports a maximum of 15 VFs.
^ permalink raw reply
* RE: [PATCH v3 net-next 07/14] net: enetc: differentiate phylink capabilities for pseudo-MAC and standalone MAC
From: Wei Fang (OSS) @ 2026-07-06 1:25 UTC (permalink / raw)
To: sashiko-reviews@lists.linux.dev
Cc: imx@lists.linux.dev, Frank.Li@kernel.org, network dev
In-Reply-To: <20260704104345.5B94F1F00A3A@smtp.kernel.org>
> > diff --git a/drivers/net/ethernet/freescale/enetc/enetc_pf_common.c
> b/drivers/net/ethernet/freescale/enetc/enetc_pf_common.c
> > index 3597cb81a7ccb..781b22198ca86 100644
> > --- a/drivers/net/ethernet/freescale/enetc/enetc_pf_common.c
> > +++ b/drivers/net/ethernet/freescale/enetc/enetc_pf_common.c
> > @@ -359,7 +359,8 @@ static bool enetc_port_has_pcs(struct enetc_pf *pf)
> > return (pf->if_mode == PHY_INTERFACE_MODE_SGMII ||
> > pf->if_mode == PHY_INTERFACE_MODE_1000BASEX ||
> > pf->if_mode == PHY_INTERFACE_MODE_2500BASEX ||
> > - pf->if_mode == PHY_INTERFACE_MODE_USXGMII);
> > + pf->if_mode == PHY_INTERFACE_MODE_USXGMII ||
> > + pf->if_mode == PHY_INTERFACE_MODE_10GBASER);
> > }
>
> [Severity: High]
> Will adding PHY_INTERFACE_MODE_10GBASER here cause the ENETC v4 probe
> to fail?
>
> If enetc_port_has_pcs() returns true for 10gbase-r, enetc_mdiobus_create()
> will call enetc_imdio_create(), which does this check:
>
> enetc_imdio_create() {
> ...
> if (!pf->ops->create_pcs) {
> dev_err(dev, "Creating PCS is not supported\n");
> return -EOPNOTSUPP;
> }
> ...
> }
>
> Since enetc4_pf_ops doesn't appear to implement .create_pcs, will device
> initialization now fail entirely with -EOPNOTSUPP when configured with
> the newly supported 10GBASER mode?
This is preparation for the upcoming support of 10GBASE-R mode.
10GBASE-R is not currently supported, so the probe failure is expected.
^ permalink raw reply
* [PATCH nf] netfilter: nf_conntrack_reasm: guard mac_header adjustment after IPv6 defrag
From: Xiang Mei @ 2026-07-05 23:36 UTC (permalink / raw)
To: Pablo Neira Ayuso, Florian Westphal, Phil Sutter
Cc: David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman, netfilter-devel, coreteam, netdev, linux-kernel,
Xiang Mei, Weiming Shi
nf_ct_frag6_reasm() slides the packet head forward to drop the IPv6
fragment header and then unconditionally advances skb->mac_header:
skb->mac_header += sizeof(struct frag_hdr);
On the NF_INET_LOCAL_OUT defrag path the skb has no link-layer header
yet, so skb->mac_header is still the "not set" sentinel (u16)~0U. Adding
sizeof(struct frag_hdr) wraps it to a small value (0xffff + 8 == 7),
after which skb_mac_header_was_set() wrongly reports a MAC header is
present and skb_mac_header() points into the headroom.
The reassembler has done this unconditional add since it was introduced;
it was harmless while mac_header was a bare pointer, but wrong once
mac_header became a u16 offset whose unset state is the ~0U sentinel
tested by skb_mac_header_was_set(). The sibling net/ipv6/reassembly.c
does the same relocation and does guard the adjustment; mirror the
guard here.
Fixes: 9fb9cbb1082d ("[NETFILTER]: Add nf_conntrack subsystem.")
Reported-by: Weiming Shi <bestswngs@gmail.com>
Assisted-by: Claude:claude-opus-4-8
Signed-off-by: Xiang Mei <xmei5@asu.edu>
---
net/ipv6/netfilter/nf_conntrack_reasm.c | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/net/ipv6/netfilter/nf_conntrack_reasm.c b/net/ipv6/netfilter/nf_conntrack_reasm.c
index 64ab23ff559b..3637b20d3fa4 100644
--- a/net/ipv6/netfilter/nf_conntrack_reasm.c
+++ b/net/ipv6/netfilter/nf_conntrack_reasm.c
@@ -348,7 +348,8 @@ static int nf_ct_frag6_reasm(struct frag_queue *fq, struct sk_buff *skb,
skb_network_header(skb)[fq->nhoffset] = skb_transport_header(skb)[0];
memmove(skb->head + sizeof(struct frag_hdr), skb->head,
(skb->data - skb->head) - sizeof(struct frag_hdr));
- skb->mac_header += sizeof(struct frag_hdr);
+ if (skb_mac_header_was_set(skb))
+ skb->mac_header += sizeof(struct frag_hdr);
skb->network_header += sizeof(struct frag_hdr);
skb_reset_transport_header(skb);
--
2.43.0
^ permalink raw reply related
* [PATCH net v3 1/3] net: ethernet: oa_tc6: Protect skb pointer used by two different kernel instances
From: Selvamani Rajagopal via B4 Relay @ 2026-07-05 22:59 UTC (permalink / raw)
To: Parthiban Veerasooran, Andrew Lunn, Piergiorgio Beruto,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni
Cc: netdev, linux-kernel, Andrew Lunn, Parthiban Veerasooran,
Selvamani Rajagopal
In-Reply-To: <20260705-fix-race-condition-and-crash-v3-0-3e51841e4d08@onsemi.com>
From: Selvamani Rajagopal <Selvamani.Rajagopal@onsemi.com>
Threaded IRQ uses waiting_tx_skb. Transmit path also uses
this pointer without any mutual exclusion protection. As a
result, it might leak skb buffer, particularly threaded IRQ
runs in the middle of tranmsmit path, near skb_linearize.
Fixes: b542d13fab0f ("net: ethernet: oa_tc6: Interrupt is active low, level triggered.")
Signed-off-by: Selvamani Rajagopal <Selvamani.Rajagopal@onsemi.com>
---
changes in v3
- Added the missed out spin lock protection for
waiting_tx_skb and disable_traffic flag
changes in v2
- added the missing prefix to the title
---
drivers/net/ethernet/oa_tc6.c | 100 +++++++++++++++++++++++++++++-------------
1 file changed, 70 insertions(+), 30 deletions(-)
diff --git a/drivers/net/ethernet/oa_tc6.c b/drivers/net/ethernet/oa_tc6.c
index 0727d53345a3..5b24cce4f9b5 100644
--- a/drivers/net/ethernet/oa_tc6.c
+++ b/drivers/net/ethernet/oa_tc6.c
@@ -652,6 +652,26 @@ static int oa_tc6_enable_data_transfer(struct oa_tc6 *tc6)
return oa_tc6_write_register(tc6, OA_TC6_REG_CONFIG0, value);
}
+/* Called when a frame that is meant to be transmitted, is dropped. */
+static void oa_tc6_drop_tx_skb(struct oa_tc6 *tc6, struct sk_buff *skb)
+{
+ if (skb) {
+ tc6->netdev->stats.tx_dropped++;
+ dev_kfree_skb_any(skb);
+ }
+}
+
+static struct sk_buff *oa_tc6_detach_waiting_tx_skb(struct oa_tc6 *tc6)
+{
+ struct sk_buff *skb;
+
+ lockdep_assert_held(&tc6->tx_skb_lock);
+ skb = tc6->waiting_tx_skb;
+ tc6->waiting_tx_skb = NULL;
+
+ return skb;
+}
+
static void oa_tc6_cleanup_ongoing_rx_skb(struct oa_tc6 *tc6)
{
if (tc6->rx_skb) {
@@ -663,26 +683,30 @@ static void oa_tc6_cleanup_ongoing_rx_skb(struct oa_tc6 *tc6)
static void oa_tc6_cleanup_ongoing_tx_skb(struct oa_tc6 *tc6)
{
- if (tc6->ongoing_tx_skb) {
- tc6->netdev->stats.tx_dropped++;
- kfree_skb(tc6->ongoing_tx_skb);
- tc6->ongoing_tx_skb = NULL;
- }
+ oa_tc6_drop_tx_skb(tc6, tc6->ongoing_tx_skb);
+ tc6->ongoing_tx_skb = NULL;
}
static void oa_tc6_cleanup_waiting_tx_skb(struct oa_tc6 *tc6)
{
- if (tc6->waiting_tx_skb) {
- tc6->netdev->stats.tx_dropped++;
- kfree_skb(tc6->waiting_tx_skb);
- tc6->waiting_tx_skb = NULL;
- }
+ struct sk_buff *skb;
+
+ spin_lock_bh(&tc6->tx_skb_lock);
+ skb = oa_tc6_detach_waiting_tx_skb(tc6);
+ spin_unlock_bh(&tc6->tx_skb_lock);
+
+ oa_tc6_drop_tx_skb(tc6, skb);
}
-static void oa_tc6_free_pending_skbs(struct oa_tc6 *tc6)
+static void oa_tc6_free_ongoing_skbs(struct oa_tc6 *tc6)
{
oa_tc6_cleanup_ongoing_tx_skb(tc6);
oa_tc6_cleanup_ongoing_rx_skb(tc6);
+}
+
+static void oa_tc6_free_pending_skbs(struct oa_tc6 *tc6)
+{
+ oa_tc6_free_ongoing_skbs(tc6);
oa_tc6_cleanup_waiting_tx_skb(tc6);
}
@@ -693,9 +717,15 @@ static void oa_tc6_free_pending_skbs(struct oa_tc6 *tc6)
static void oa_tc6_disable_traffic(struct oa_tc6 *tc6)
{
u32 regval = INT_MASK0_ALL_INTERRUPTS;
+ struct sk_buff *skb;
+ spin_lock_bh(&tc6->tx_skb_lock);
tc6->disable_traffic = true;
- oa_tc6_free_pending_skbs(tc6);
+ skb = oa_tc6_detach_waiting_tx_skb(tc6);
+ spin_unlock_bh(&tc6->tx_skb_lock);
+
+ oa_tc6_drop_tx_skb(tc6, skb);
+ oa_tc6_free_ongoing_skbs(tc6);
oa_tc6_write_register(tc6, OA_TC6_REG_INT_MASK0, regval);
oa_tc6_read_register(tc6, OA_TC6_REG_STATUS0, ®val);
oa_tc6_write_register(tc6, OA_TC6_REG_STATUS0, regval);
@@ -1136,8 +1166,7 @@ static int oa_tc6_try_spi_transfer(struct oa_tc6 *tc6)
if (ret == -EAGAIN)
continue;
- oa_tc6_cleanup_ongoing_tx_skb(tc6);
- oa_tc6_cleanup_ongoing_rx_skb(tc6);
+ oa_tc6_free_ongoing_skbs(tc6);
netdev_err(tc6->netdev, "Device error: %d\n", ret);
return ret;
}
@@ -1159,15 +1188,20 @@ static irqreturn_t oa_tc6_macphy_threaded_irq(int irq, void *data)
* no need to attempt spi transfer, once it fails. Pending skbs
* are already freed.
*/
- if (!tc6->disable_traffic) {
- while (tc6->int_flag ||
- (tc6->waiting_tx_skb && tc6->tx_credits)) {
- ret = oa_tc6_try_spi_transfer(tc6);
- if (ret) {
- disable_irq_nosync(tc6->spi->irq);
- oa_tc6_disable_traffic(tc6);
- break;
- }
+ spin_lock_bh(&tc6->tx_skb_lock);
+ if (tc6->disable_traffic) {
+ spin_unlock_bh(&tc6->tx_skb_lock);
+ return IRQ_HANDLED;
+ }
+ spin_unlock_bh(&tc6->tx_skb_lock);
+
+ while (tc6->int_flag ||
+ (tc6->waiting_tx_skb && tc6->tx_credits)) {
+ ret = oa_tc6_try_spi_transfer(tc6);
+ if (ret) {
+ disable_irq_nosync(tc6->spi->irq);
+ oa_tc6_disable_traffic(tc6);
+ break;
}
}
@@ -1250,18 +1284,22 @@ EXPORT_SYMBOL_GPL(oa_tc6_zero_align_receive_frame_enable);
*/
netdev_tx_t oa_tc6_start_xmit(struct oa_tc6 *tc6, struct sk_buff *skb)
{
- if (tc6->disable_traffic || tc6->waiting_tx_skb) {
- netif_stop_queue(tc6->netdev);
- return NETDEV_TX_BUSY;
- }
-
if (skb_linearize(skb)) {
- dev_kfree_skb_any(skb);
- tc6->netdev->stats.tx_dropped++;
+ oa_tc6_drop_tx_skb(tc6, skb);
return NETDEV_TX_OK;
}
spin_lock_bh(&tc6->tx_skb_lock);
+ if (tc6->waiting_tx_skb) {
+ netif_stop_queue(tc6->netdev);
+ spin_unlock_bh(&tc6->tx_skb_lock);
+ return NETDEV_TX_BUSY;
+ }
+ if (tc6->disable_traffic) {
+ spin_unlock_bh(&tc6->tx_skb_lock);
+ oa_tc6_drop_tx_skb(tc6, skb);
+ return NETDEV_TX_OK;
+ }
tc6->waiting_tx_skb = skb;
spin_unlock_bh(&tc6->tx_skb_lock);
@@ -1393,7 +1431,9 @@ EXPORT_SYMBOL_GPL(oa_tc6_init);
*/
void oa_tc6_exit(struct oa_tc6 *tc6)
{
+ spin_lock_bh(&tc6->tx_skb_lock);
tc6->disable_traffic = true;
+ spin_unlock_bh(&tc6->tx_skb_lock);
disable_irq(tc6->spi->irq);
oa_tc6_phy_exit(tc6);
oa_tc6_free_pending_skbs(tc6);
--
2.43.0
^ permalink raw reply related
* [PATCH net v3 3/3] net: ethernet: oa_tc6: Carrier off when disable_traffic is set
From: Selvamani Rajagopal via B4 Relay @ 2026-07-05 22:59 UTC (permalink / raw)
To: Parthiban Veerasooran, Andrew Lunn, Piergiorgio Beruto,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni
Cc: netdev, linux-kernel, Andrew Lunn, Parthiban Veerasooran,
Selvamani Rajagopal
In-Reply-To: <20260705-fix-race-condition-and-crash-v3-0-3e51841e4d08@onsemi.com>
From: Selvamani Rajagopal <Selvamani.Rajagopal@onsemi.com>
Previously, TX queue interface was stopped when
disable_traffic flag was set. It is more appropriate to
disable the queue as there is no recovery, once
disable_traffic is set. Carrier is also marked off
Fixes: b542d13fab0f ("net: ethernet: oa_tc6: Interrupt is active low, level triggered.")
Signed-off-by: Selvamani Rajagopal <Selvamani.Rajagopal@onsemi.com>
changes in v3
- New patch. Carrier marked off once disable_traffic is set
---
drivers/net/ethernet/oa_tc6.c | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/drivers/net/ethernet/oa_tc6.c b/drivers/net/ethernet/oa_tc6.c
index a6b8762f6052..1f8564779025 100644
--- a/drivers/net/ethernet/oa_tc6.c
+++ b/drivers/net/ethernet/oa_tc6.c
@@ -730,6 +730,12 @@ static void oa_tc6_disable_traffic(struct oa_tc6 *tc6)
skb = oa_tc6_detach_waiting_tx_skb(tc6);
spin_unlock_bh(&tc6->tx_skb_lock);
+ /* disable_traffic, when set, is a point of no
+ * return to working state. Better to mark
+ * the carrier off.
+ */
+ netif_carrier_off(tc6->netdev);
+ netif_tx_disable(tc6->netdev);
oa_tc6_drop_tx_skb(tc6, skb);
oa_tc6_free_ongoing_skbs(tc6);
oa_tc6_write_register(tc6, OA_TC6_REG_INT_MASK0, regval);
--
2.43.0
^ permalink raw reply related
* [PATCH net v3 2/3] net: ethernet: oa_tc6: Improvement in buffer overflow handling
From: Selvamani Rajagopal via B4 Relay @ 2026-07-05 22:59 UTC (permalink / raw)
To: Parthiban Veerasooran, Andrew Lunn, Piergiorgio Beruto,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni
Cc: netdev, linux-kernel, Andrew Lunn, Parthiban Veerasooran,
Selvamani Rajagopal
In-Reply-To: <20260705-fix-race-condition-and-crash-v3-0-3e51841e4d08@onsemi.com>
From: Selvamani Rajagopal <Selvamani.Rajagopal@onsemi.com>
When oversubscribed traffic causes lot of buffer overflow errors,
probably due to loss of data chunks, driver fails to find a
data chunk with end_valid bit set, before it runs out of sk buffer
space. As a result, assert is seen during skb_put.
Now check is made if tail + len > end, driver abandons the current
data and starts look for a data chunk with start_valid bit,
that is a new frame.
Fixes: d70a0d8f2f2d ("net: ethernet: oa_tc6: implement receive path to receive rx ethernet frames")
Signed-off-by: Selvamani Rajagopal <Selvamani.Rajagopal@onsemi.com>
---
changes in v3
- Continue processing more chunks on error code -EAGAIN. Previously
we were bailing out.
changes in v2
- Check rx_skb pointer before new allocation and NULL before use.
---
drivers/net/ethernet/oa_tc6.c | 113 ++++++++++++++++++++++++++++++------------
1 file changed, 82 insertions(+), 31 deletions(-)
diff --git a/drivers/net/ethernet/oa_tc6.c b/drivers/net/ethernet/oa_tc6.c
index 5b24cce4f9b5..a6b8762f6052 100644
--- a/drivers/net/ethernet/oa_tc6.c
+++ b/drivers/net/ethernet/oa_tc6.c
@@ -710,6 +710,12 @@ static void oa_tc6_free_pending_skbs(struct oa_tc6 *tc6)
oa_tc6_cleanup_waiting_tx_skb(tc6);
}
+static void oa_tc6_look_for_new_frame(struct oa_tc6 *tc6)
+{
+ tc6->rx_buf_overflow = true;
+ oa_tc6_cleanup_ongoing_rx_skb(tc6);
+}
+
/* If the failure is at SPI interface level, masking and clearing
* the interrupt of the device won't work. Since SPI interrupt is
* disabled, it should stop the repeated interrupts.
@@ -753,8 +759,7 @@ static int oa_tc6_process_extended_status(struct oa_tc6 *tc6)
}
if (FIELD_GET(STATUS0_RX_BUFFER_OVERFLOW_ERROR, value)) {
- tc6->rx_buf_overflow = true;
- oa_tc6_cleanup_ongoing_rx_skb(tc6);
+ oa_tc6_look_for_new_frame(tc6);
net_err_ratelimited("%s: Receive buffer overflow error\n",
tc6->netdev->name);
return -EAGAIN;
@@ -780,6 +785,8 @@ static int oa_tc6_process_extended_status(struct oa_tc6 *tc6)
static int oa_tc6_process_rx_chunk_footer(struct oa_tc6 *tc6, u32 footer)
{
+ int ret = 0;
+
/* Process rx chunk footer for the following,
* 1. tx credits
* 2. errors if any from MAC-PHY
@@ -790,9 +797,11 @@ static int oa_tc6_process_rx_chunk_footer(struct oa_tc6 *tc6, u32 footer)
footer);
if (FIELD_GET(OA_TC6_DATA_FOOTER_EXTENDED_STS, footer)) {
- int ret = oa_tc6_process_extended_status(tc6);
-
- if (ret)
+ ret = oa_tc6_process_extended_status(tc6);
+ /* EAGAIN error is recoverable. Move on to check
+ * HEADER and SYNC errors before returning.
+ */
+ if (ret && ret != -EAGAIN)
return ret;
}
@@ -810,7 +819,7 @@ static int oa_tc6_process_rx_chunk_footer(struct oa_tc6 *tc6, u32 footer)
return -ENODEV;
}
- return 0;
+ return ret;
}
static void oa_tc6_submit_rx_skb(struct oa_tc6 *tc6)
@@ -835,13 +844,35 @@ static void oa_tc6_submit_rx_skb(struct oa_tc6 *tc6)
tc6->rx_skb = NULL;
}
-static void oa_tc6_update_rx_skb(struct oa_tc6 *tc6, u8 *payload, u8 length)
+/* On oversubscribed traffic condition, particularly with overwhelming rx
+ * buffer overflow errors, there could be data chunk loss. If tail + length
+ * goes beyond end pointer, that is an indication that the data chunk with
+ * end_valid bit is lost. Time to look for a data chunk with start_valid bit.
+ *
+ * If rx_skb is NULL, it is time to start looking for data chunk with
+ * start_bit.
+ */
+static int oa_tc6_update_rx_skb(struct oa_tc6 *tc6, u8 *payload, u8 length)
{
+ if (!tc6->rx_skb ||
+ (tc6->rx_skb->tail + length) > tc6->rx_skb->end) {
+ oa_tc6_look_for_new_frame(tc6);
+ return -EAGAIN;
+ }
+
memcpy(skb_put(tc6->rx_skb, length), payload, length);
+ return 0;
}
+/* On overwhelming rx buffer overflow errors, due to data chunk loss, it is
+ * possible that we get two data chunks with start_valid bit set, without
+ * end_valid bit set in between. In this case, rx_skb would have a valid
+ * buffer pointer. We should release, if a valid pointer is found before
+ * allocating a new one.
+ */
static int oa_tc6_allocate_rx_skb(struct oa_tc6 *tc6)
{
+ oa_tc6_cleanup_ongoing_rx_skb(tc6);
tc6->rx_skb = netdev_alloc_skb_ip_align(tc6->netdev, tc6->netdev->mtu +
ETH_HLEN + ETH_FCS_LEN);
if (!tc6->rx_skb) {
@@ -861,7 +892,9 @@ static int oa_tc6_prcs_complete_rx_frame(struct oa_tc6 *tc6, u8 *payload,
if (ret)
return ret;
- oa_tc6_update_rx_skb(tc6, payload, size);
+ ret = oa_tc6_update_rx_skb(tc6, payload, size);
+ if (ret)
+ return ret;
oa_tc6_submit_rx_skb(tc6);
@@ -876,22 +909,24 @@ static int oa_tc6_prcs_rx_frame_start(struct oa_tc6 *tc6, u8 *payload, u16 size)
if (ret)
return ret;
- oa_tc6_update_rx_skb(tc6, payload, size);
-
- return 0;
+ return oa_tc6_update_rx_skb(tc6, payload, size);
}
-static void oa_tc6_prcs_rx_frame_end(struct oa_tc6 *tc6, u8 *payload, u16 size)
+static int oa_tc6_prcs_rx_frame_end(struct oa_tc6 *tc6, u8 *payload, u16 size)
{
- oa_tc6_update_rx_skb(tc6, payload, size);
+ int ret;
- oa_tc6_submit_rx_skb(tc6);
+ ret = oa_tc6_update_rx_skb(tc6, payload, size);
+ if (!ret)
+ oa_tc6_submit_rx_skb(tc6);
+ return ret;
}
-static void oa_tc6_prcs_ongoing_rx_frame(struct oa_tc6 *tc6, u8 *payload,
- u32 footer)
+static int oa_tc6_prcs_ongoing_rx_frame(struct oa_tc6 *tc6, u8 *payload,
+ u32 footer)
{
- oa_tc6_update_rx_skb(tc6, payload, OA_TC6_CHUNK_PAYLOAD_SIZE);
+ return oa_tc6_update_rx_skb(tc6, payload,
+ OA_TC6_CHUNK_PAYLOAD_SIZE);
}
static int oa_tc6_prcs_rx_chunk_payload(struct oa_tc6 *tc6, u8 *data,
@@ -931,8 +966,7 @@ static int oa_tc6_prcs_rx_chunk_payload(struct oa_tc6 *tc6, u8 *data,
/* Process the chunk with only rx frame end */
if (end_valid && !start_valid) {
size = end_byte_offset + 1;
- oa_tc6_prcs_rx_frame_end(tc6, data, size);
- return 0;
+ return oa_tc6_prcs_rx_frame_end(tc6, data, size);
}
/* Process the chunk with previous rx frame end and next rx frame
@@ -954,9 +988,7 @@ static int oa_tc6_prcs_rx_chunk_payload(struct oa_tc6 *tc6, u8 *data,
}
/* Process the chunk with ongoing rx frame data */
- oa_tc6_prcs_ongoing_rx_frame(tc6, data, footer);
-
- return 0;
+ return oa_tc6_prcs_ongoing_rx_frame(tc6, data, footer);
}
static u32 oa_tc6_get_rx_chunk_footer(struct oa_tc6 *tc6, u16 footer_offset)
@@ -972,8 +1004,9 @@ static u32 oa_tc6_get_rx_chunk_footer(struct oa_tc6 *tc6, u16 footer_offset)
static int oa_tc6_process_spi_data_rx_buf(struct oa_tc6 *tc6, u16 length)
{
u16 no_of_rx_chunks = length / OA_TC6_CHUNK_SIZE;
+ bool retry = false;
+ int ret = 0;
u32 footer;
- int ret;
/* All the rx chunks in the receive SPI data buffer are examined here */
for (int i = 0; i < no_of_rx_chunks; i++) {
@@ -982,8 +1015,11 @@ static int oa_tc6_process_spi_data_rx_buf(struct oa_tc6 *tc6, u16 length)
OA_TC6_CHUNK_PAYLOAD_SIZE);
ret = oa_tc6_process_rx_chunk_footer(tc6, footer);
- if (ret)
- return ret;
+ if (ret) {
+ if (ret != -EAGAIN)
+ return ret;
+ retry = true;
+ }
/* If there is a data valid chunks then process it for the
* information needed to determine the validity and the location
@@ -995,12 +1031,25 @@ static int oa_tc6_process_spi_data_rx_buf(struct oa_tc6 *tc6, u16 length)
ret = oa_tc6_prcs_rx_chunk_payload(tc6, payload,
footer);
- if (ret)
- return ret;
+ if (ret) {
+ if (ret != -ENOMEM && ret != -EAGAIN)
+ return ret;
+ retry = true;
+ }
}
}
- return 0;
+ /* As a recoverable error, not bailing out on error code
+ * -EAGAIN. If subsequent loop iterations, if any, succeeds,
+ * error code would be overwritten. retry flag helps to
+ * make the caller to continue and retry.
+ */
+ if (retry) {
+ ret = -EAGAIN;
+ oa_tc6_look_for_new_frame(tc6);
+ }
+
+ return ret;
}
static __be32 oa_tc6_prepare_data_header(bool data_valid, bool start_valid,
@@ -1162,10 +1211,12 @@ static int oa_tc6_try_spi_transfer(struct oa_tc6 *tc6)
}
ret = oa_tc6_process_spi_data_rx_buf(tc6, spi_len);
- if (ret) {
- if (ret == -EAGAIN)
- continue;
+ /* Not continuing with the next iteration to give
+ * waiting_tx_skb a chance to get drained, if
+ * needed.
+ */
+ if (ret && ret != -EAGAIN) {
oa_tc6_free_ongoing_skbs(tc6);
netdev_err(tc6->netdev, "Device error: %d\n", ret);
return ret;
--
2.43.0
^ permalink raw reply related
* [PATCH net v3 0/3] Fix to possible skb leak due to race condtion in tx path
From: Selvamani Rajagopal via B4 Relay @ 2026-07-05 22:59 UTC (permalink / raw)
To: Parthiban Veerasooran, Andrew Lunn, Piergiorgio Beruto,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni
Cc: netdev, linux-kernel, Andrew Lunn, Parthiban Veerasooran,
Selvamani Rajagopal
Now the traffic is handled in threaded IRQ, and the
disable_traffic flag is checked before handling the
data, new race condition is exposed, in which
buffer may leak, if threaded IRQ interrupts the
trasmit path midway.
With this change, disable_traffic and waiting_tx_skb
pointer are protected by spin lock/unlock pair.
This is highlighted in Sashiko review
https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260611-level-trigger-v5-0-4533a9e85ce2%40onsemi.com
Also on buffer overrun condition, probably due to loss of
SPI data chunks, receive path doesn't see the expected
data chunk with end_valid bit set. As a result, driver
keeps adding data chunks to the skb before running out
of space and kernel panic is seen.
With this change, before adding data to the skb, if there
is no space, skb is freed and driver starts looking for
new frame by looking for a data chunk with start_valid
bit set.
[ 705.405490] skbuff: skb_over_panic: text:ffffffd2eb72a264 len:1600 put:64 head:ffffff804e5cdc40 data:ffffff804e5cdc80 tail:0x680 end:0x640 dev:eth1
[ 705.405569] ------------[ cut here ]------------
[ 705.405575] kernel BUG at net/core/skbuff.c:214!
[ 705.405589] Internal error: Oops - BUG: 00000000f2000800 [#1] SMP
[ 6703.427690] Call trace:
[ 705.925157] skb_panic+0x58/0x68 (P)
[ 705.928726] skb_put+0x74/0x80
[ 705.931772] oa_tc6_update_rx_skb+0x44/0x98 [oa_tc6_mod]
[ 705.937084] oa_tc6_macphy_threaded_irq+0x3f4/0x900 [oa_tc6_mod]
[ 705.943084] irq_thread_fn+0x34/0xb8
[ 705.946654] irq_thread+0x1a0/0x300
[ 705.950134] kthread+0x138/0x150
[ 705.953356] ret_from_fork+0x10/0x20
Signed-off-by: Selvamani Rajagopal <Selvamani.Rajagopal@onsemi.com>
---
Changes in v3:
- Cover all the instances of disable_traffic flag with
spin lock to serialize the access
- Disabling the tx queue and mark the carrier off when
disable_traffic is set.
- Continue processing received chunks on buffer overflow
error and "out of skb" error.
- Link to v2: https://lore.kernel.org/r/20260626-fix-race-condition-and-crash-v2-0-b6c5c10e604f@onsemi.com
Changes in v2:
- Improvment to how error -EAGAIN is handled. Took care of
couple of use cases where start_bit and end_bit may be missing or
repeated due to lost data chunks.
- Protected handling of waiting_tx_skb pointer with spin lock
- Link to v1: https://lore.kernel.org/r/20260621-fix-race-condition-and-crash-v1-0-87e290d9357f@onsemi.com
---
Selvamani Rajagopal (3):
net: ethernet: oa_tc6: Protect skb pointer used by two different kernel instances
net: ethernet: oa_tc6: Improvement in buffer overflow handling
net: ethernet: oa_tc6: Carrier off when disable_traffic is set
drivers/net/ethernet/oa_tc6.c | 219 ++++++++++++++++++++++++++++++------------
1 file changed, 158 insertions(+), 61 deletions(-)
---
base-commit: d7a8d500d7e42837bd8dce40cb52c97c6e8706a9
change-id: 20260621-fix-race-condition-and-crash-94d055a665c4
Best regards,
--
Selvamani Rajagopal <Selvamani.Rajagopal@onsemi.com>
^ permalink raw reply
* Re: [PATCH net] sctp: validate the body of a STALE_COOKIE error before reading it
From: Xiang Mei @ 2026-07-05 22:29 UTC (permalink / raw)
To: Xin Long
Cc: Marcelo Ricardo Leitner, David S . Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, linux-sctp, netdev,
linux-kernel, bestswngs
In-Reply-To: <CADvbK_cWUT2ygfBSW6qjgx9_y=bvksqG87Lp8edQcFT+3p9fOA@mail.gmail.com>
Thanks for the reminder. I didn't notice that Weiming has patched it.
Sorry about that.
Xiang
On Sun, Jul 5, 2026 at 12:13 PM Xin Long <lucien.xin@gmail.com> wrote:
>
> On Sat, Jul 4, 2026 at 8:31 PM Xiang Mei <xmei5@asu.edu> wrote:
> >
> > sctp_sf_do_5_2_6_stale() reads the 32-bit Measure of Staleness that
> > follows the error header:
> >
> > stale = ntohl(*(__be32 *)((u8 *)err + sizeof(*err)));
> >
> > without checking that the STALE_COOKIE cause actually carries that
> > 4-byte body. sctp_walk_errors() in the caller only requires
> > err->length >= sizeof(struct sctp_errhdr), so a peer can send an 8-byte
> > ERROR chunk whose sole STALE_COOKIE cause has length == 4 and no body.
> > It passes sctp_chunk_length_valid() (>= 8) and the error walk, yet the
> > staleness read reaches past the validated cause.
> >
> > When that is the only chunk in the packet the cause ends exactly at
> > skb_tail (sctp_inq_pop() discards only when chunk_end > skb_tail), so
> > the read stays in-bounds of the skb head slab object but past the packet
> > data. The value is folded into the COOKIE_PRESERVATIVE parameter of the
> > retransmitted INIT and reflected to the peer, leaking adjacent kernel
> > slab bytes.
> >
> > Discard the chunk when the staleness field falls outside the validated
> > chunk data.
> >
> > Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
> > Reported-by: Weiming Shi <bestswngs@gmail.com>
> > Assisted-by: Claude:claude-opus-4-8
> > Signed-off-by: Xiang Mei <xmei5@asu.edu>
> > ---
> > net/sctp/sm_statefuns.c | 3 +++
> > 1 file changed, 3 insertions(+)
> >
> > diff --git a/net/sctp/sm_statefuns.c b/net/sctp/sm_statefuns.c
> > index d23d935e128e..e4b4b63162cf 100644
> > --- a/net/sctp/sm_statefuns.c
> > +++ b/net/sctp/sm_statefuns.c
> > @@ -2592,6 +2592,9 @@ static enum sctp_disposition sctp_sf_do_5_2_6_stale(
> >
> > err = (struct sctp_errhdr *)(chunk->skb->data);
> >
> > + if ((u8 *)err + sizeof(*err) + sizeof(__be32) > chunk->chunk_end)
> > + return sctp_sf_pdiscard(net, ep, asoc, type, arg, commands);
> > +
> > /* When calculating the time extension, an implementation
> > * SHOULD use the RTT information measured based on the
> > * previous COOKIE ECHO / ERROR exchange, and should add no
> > --
> > 2.43.0
> >
>
> I think this is a dup of
> https://lore.kernel.org/netdev/20260704033545.2438373-2-bestswngs@gmail.com/.
>
> Thanks.
^ permalink raw reply
* [PATCH nf] netfilter: ebtables: terminate table name before find_table_lock()
From: Xiang Mei @ 2026-07-05 21:58 UTC (permalink / raw)
To: Pablo Neira Ayuso, Florian Westphal, Phil Sutter,
Nikolay Aleksandrov, Ido Schimmel, David S . Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman
Cc: netfilter-devel, coreteam, bridge, netdev, linux-kernel,
Xiang Mei, Weiming Shi
update_counters() and compat_update_counters() forward a user-supplied
32-byte table name to find_table_lock() without NUL-terminating it. On a
lookup miss, find_inlist_lock() calls try_then_request_module(..., "%s%s",
"ebtable_", name), and vsnprintf() reads past the name field and the
stack object until it hits a zero byte.
BUG: KASAN: stack-out-of-bounds in string (lib/vsprintf.c:648 lib/vsprintf.c:730)
Read of size 1 at addr ffff8880119dfb20 by task exploit/147
Call Trace:
...
string (lib/vsprintf.c:648 lib/vsprintf.c:730)
vsnprintf (lib/vsprintf.c:2945)
__request_module (kernel/module/kmod.c:150)
do_update_counters.isra.0 (net/bridge/netfilter/ebtables.c:371 net/bridge/netfilter/ebtables.c:380)
update_counters (net/bridge/netfilter/ebtables.c:1440)
do_ebt_set_ctl (net/bridge/netfilter/ebtables.c:2573)
nf_setsockopt (net/netfilter/nf_sockopt.c:101)
ip_setsockopt (net/ipv4/ip_sockglue.c:1424)
raw_setsockopt (net/ipv4/raw.c:847)
__sys_setsockopt (net/socket.c:2393)
...
compat_do_replace() shares the same unterminated name via
compat_copy_ebt_replace_from_user(); terminate it there too so all
find_table_lock() callers behave alike. The other callers already
terminate the name after the copy.
Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
Fixes: 81e675c227ec ("netfilter: ebtables: add CONFIG_COMPAT support")
Reported-by: Weiming Shi <bestswngs@gmail.com>
Assisted-by: Claude:claude-opus-4-8
Signed-off-by: Xiang Mei <xmei5@asu.edu>
---
net/bridge/netfilter/ebtables.c | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/net/bridge/netfilter/ebtables.c b/net/bridge/netfilter/ebtables.c
index f20c039e44c8..5b74ff827493 100644
--- a/net/bridge/netfilter/ebtables.c
+++ b/net/bridge/netfilter/ebtables.c
@@ -1434,6 +1434,8 @@ static int update_counters(struct net *net, sockptr_t arg, unsigned int len)
if (copy_from_sockptr(&hlp, arg, sizeof(hlp)))
return -EFAULT;
+ hlp.name[sizeof(hlp.name) - 1] = '\0';
+
if (len != sizeof(hlp) + hlp.num_counters * sizeof(struct ebt_counter))
return -EINVAL;
@@ -2273,6 +2275,8 @@ static int compat_copy_ebt_replace_from_user(struct ebt_replace *repl,
memcpy(repl, &tmp, offsetof(struct ebt_replace, hook_entry));
+ repl->name[sizeof(repl->name) - 1] = '\0';
+
/* starting with hook_entry, 32 vs. 64 bit structures are different */
for (i = 0; i < NF_BR_NUMHOOKS; i++)
repl->hook_entry[i] = compat_ptr(tmp.hook_entry[i]);
@@ -2395,6 +2399,8 @@ static int compat_update_counters(struct net *net, sockptr_t arg,
if (copy_from_sockptr(&hlp, arg, sizeof(hlp)))
return -EFAULT;
+ hlp.name[sizeof(hlp.name) - 1] = '\0';
+
/* try real handler in case userland supplied needed padding */
if (len != sizeof(hlp) + hlp.num_counters * sizeof(struct ebt_counter))
return update_counters(net, arg, len);
--
2.43.0
^ permalink raw reply related
* [PATCH net] selftests: netfilter: conntrack_resize.sh: fix skip exit code
From: Dharmik Parmar @ 2026-07-05 20:42 UTC (permalink / raw)
To: netdev; +Cc: Dharmik Parmar
In-Reply-To: <20260705204252.630729-1-dharmikparmar2004.ref@yahoo.com>
When conntrack sysctls are unavailable, the test prints SKIP but exits
with $KSFT_SKIP. lib.sh defines ksft_skip instead, so the kselftest
runner did not see a proper skip.
Use ksft_skip.
Fixes: d33f889fd80c ("selftests: netfilter: add conntrack stress test")
Signed-off-by: Dharmik Parmar <dharmikparmar2004@yahoo.com>
---
tools/testing/selftests/net/netfilter/conntrack_resize.sh | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/tools/testing/selftests/net/netfilter/conntrack_resize.sh b/tools/testing/selftests/net/netfilter/conntrack_resize.sh
index 615fe3c6f405..51564092cec4 100755
--- a/tools/testing/selftests/net/netfilter/conntrack_resize.sh
+++ b/tools/testing/selftests/net/netfilter/conntrack_resize.sh
@@ -22,7 +22,7 @@ insert_count=2000
modprobe -q nf_conntrack
if ! sysctl -q net.netfilter.nf_conntrack_max >/dev/null;then
echo "SKIP: conntrack sysctls not available"
- exit $KSFT_SKIP
+ exit $ksft_skip
fi
init_net_max=$(sysctl -n net.netfilter.nf_conntrack_max) || exit 1
--
2.43.0
^ permalink raw reply related
* [PATCH ethtool-next 2/2] sfpid: print all compliance codes
From: Aleksander Jan Bajkowski @ 2026-07-05 20:06 UTC (permalink / raw)
To: mkubecek, andrew, danieller, kuba, davem, edumazet, pabeni,
netdev
Cc: Aleksander Jan Bajkowski
In-Reply-To: <20260705200637.2092534-1-olek2@wp.pl>
SFP modules implement multiple compliance codes. This is common for
dual-rate modules. Before the `json` option was introduced, all
compliance codes were displayed. Currently, only the last code is
displayed. This commit fixes that bug. Compliance codes are
represented as array.
Before:
$ ethtool -m sfp-lan
...
Transceiver codes : 0x00 0x00 0x00 0x01 0x20 0x40 0x0c 0x15 0x00
Transceiver type : FC: 100 MBytes/sec</pre>
...
$ ethtool --json -m sfp-wan
[ {
...
"transceiver_codes": [ 0,0,0,1,32,64,12,21,0 ],
"transceiver_type": "FC: 100 MBytes/sec",
...
} ]
After:
$ ethtool -m sfp-wan
...
Transceiver codes : 0x00 0x00 0x00 0x01 0x20 0x40 0x0c 0x15 0x00
Transceiver type : Ethernet: 1000BASE-SX
Transceiver type : FC: intermediate distance (I)
Transceiver type : FC: Shortwave laser w/o OFC (SN)
Transceiver type : FC: Multimode, 62.5um (M6)
Transceiver type : FC: Multimode, 50um (M5)
Transceiver type : FC: 400 MBytes/sec
Transceiver type : FC: 200 MBytes/sec
Transceiver type : FC: 100 MBytes/sec
...
$ ethtool --json -m sfp-wan
[ {
...
"transceiver_codes": [ 0,0,0,1,32,64,12,21,0 ],
"transceiver_type": [ "Ethernet: 1000BASE-SX","FC: intermediate distance (I)","FC: Shortwave laser w/o OFC (SN)","FC: Multimode, 62.5um (M6)","FC: Multimode, 50um (M5)","FC: 400 MBytes/sec","FC: 200 MBytes/sec","FC: 100 MBytes/sec" ],
...
} ]
Fixes: 4071862f58d8 ("sfpid: Add JSON output handling to --module-info in SFF8079 modules")
Signed-off-by: Aleksander Jan Bajkowski <olek2@wp.pl>
---
sfpid.c | 231 +++++++++++++++++++++++++++++++-------------------------
1 file changed, 127 insertions(+), 104 deletions(-)
diff --git a/sfpid.c b/sfpid.c
index d6636ba..72c44cf 100644
--- a/sfpid.c
+++ b/sfpid.c
@@ -50,7 +50,6 @@ static void sff8079_show_connector(const __u8 *id)
static void sff8079_show_transceiver(const __u8 *id)
{
static const char *pfx = "Transceiver type";
- char value[140] = "";
if (is_json_context()) {
open_json_array("transceiver_codes", "");
@@ -70,242 +69,266 @@ static void sff8079_show_transceiver(const __u8 *id)
"Transceiver codes", id[3], id[4], id[5], id[6],
id[7], id[8], id[9], id[10], id[36]);
}
+
+ if (is_json_context())
+ open_json_array("transceiver_type", "");
+
/* 10G Ethernet Compliance Codes */
if (id[3] & (1 << 7))
- sprintf(value, "%s",
+ module_print_array_string(pfx,
"10G Ethernet: 10G Base-ER [SFF-8472 rev10.4 onwards]");
if (id[3] & (1 << 6))
- sprintf(value, "%s", "10G Ethernet: 10G Base-LRM");
+ module_print_array_string(pfx, "10G Ethernet: 10G Base-LRM");
if (id[3] & (1 << 5))
- sprintf(value, "%s", "10G Ethernet: 10G Base-LR");
+ module_print_array_string(pfx, "10G Ethernet: 10G Base-LR");
if (id[3] & (1 << 4))
- sprintf(value, "%s", "10G Ethernet: 10G Base-SR");
+ module_print_array_string(pfx, "10G Ethernet: 10G Base-SR");
/* Infiniband Compliance Codes */
if (id[3] & (1 << 3))
- sprintf(value, "%s", "Infiniband: 1X SX");
+ module_print_array_string(pfx, "Infiniband: 1X SX");
if (id[3] & (1 << 2))
- sprintf(value, "%s", "Infiniband: 1X LX");
+ module_print_array_string(pfx, "Infiniband: 1X LX");
if (id[3] & (1 << 1))
- sprintf(value, "%s", "Infiniband: 1X Copper Active");
+ module_print_array_string(pfx, "Infiniband: 1X Copper Active");
if (id[3] & (1 << 0))
- sprintf(value, "%s", "Infiniband: 1X Copper Passive");
+ module_print_array_string(pfx,
+ "Infiniband: 1X Copper Passive");
/* ESCON Compliance Codes */
if (id[4] & (1 << 7))
- sprintf(value, "%s", "ESCON: ESCON MMF, 1310nm LED");
+ module_print_array_string(pfx, "ESCON: ESCON MMF, 1310nm LED");
if (id[4] & (1 << 6))
- sprintf(value, "%s", "ESCON: ESCON SMF, 1310nm Laser");
+ module_print_array_string(pfx,
+ "ESCON: ESCON SMF, 1310nm Laser");
/* SONET Compliance Codes */
if (id[4] & (1 << 5))
- sprintf(value, "%s", "SONET: OC-192, short reach");
+ module_print_array_string(pfx, "SONET: OC-192, short reach");
if (id[4] & (1 << 4))
- sprintf(value, "%s", "SONET: SONET reach specifier bit 1");
+ module_print_array_string(pfx,
+ "SONET: SONET reach specifier bit 1");
if (id[4] & (1 << 3))
- sprintf(value, "%s", "SONET: SONET reach specifier bit 2");
+ module_print_array_string(pfx,
+ "SONET: SONET reach specifier bit 2");
if (id[4] & (1 << 2))
- sprintf(value, "%s", "SONET: OC-48, long reach");
+ module_print_array_string(pfx, "SONET: OC-48, long reach");
if (id[4] & (1 << 1))
- sprintf(value, "%s", "SONET: OC-48, intermediate reach");
+ module_print_array_string(pfx,
+ "SONET: OC-48, intermediate reach");
if (id[4] & (1 << 0))
- sprintf(value, "%s", "SONET: OC-48, short reach");
+ module_print_array_string(pfx, "SONET: OC-48, short reach");
if (id[5] & (1 << 6))
- sprintf(value, "%s", "SONET: OC-12, single mode, long reach");
+ module_print_array_string(pfx,
+ "SONET: OC-12, single mode, long reach");
if (id[5] & (1 << 5))
- sprintf(value, "%s", "SONET: OC-12, single mode, inter. reach");
+ module_print_array_string(pfx,
+ "SONET: OC-12, single mode, inter. reach");
if (id[5] & (1 << 4))
- sprintf(value, "%s", "SONET: OC-12, short reach");
+ module_print_array_string(pfx, "SONET: OC-12, short reach");
if (id[5] & (1 << 2))
- sprintf(value, "%s", "SONET: OC-3, single mode, long reach");
+ module_print_array_string(pfx,
+ "SONET: OC-3, single mode, long reach");
if (id[5] & (1 << 1))
- sprintf(value, "%s", "SONET: OC-3, single mode, inter. reach");
+ module_print_array_string(pfx,
+ "SONET: OC-3, single mode, inter. reach");
if (id[5] & (1 << 0))
- sprintf(value, "%s", "SONET: OC-3, short reach");
+ module_print_array_string(pfx, "SONET: OC-3, short reach");
/* Ethernet Compliance Codes */
if (id[6] & (1 << 7))
- sprintf(value, "%s", "Ethernet: BASE-PX");
+ module_print_array_string(pfx, "Ethernet: BASE-PX");
if (id[6] & (1 << 6))
- sprintf(value, "%s", "Ethernet: BASE-BX10");
+ module_print_array_string(pfx, "Ethernet: BASE-BX10");
if (id[6] & (1 << 5))
- sprintf(value, "%s", "Ethernet: 100BASE-FX");
+ module_print_array_string(pfx, "Ethernet: 100BASE-FX");
if (id[6] & (1 << 4))
- sprintf(value, "%s", "Ethernet: 100BASE-LX/LX10");
+ module_print_array_string(pfx, "Ethernet: 100BASE-LX/LX10");
if (id[6] & (1 << 3))
- sprintf(value, "%s", "Ethernet: 1000BASE-T");
+ module_print_array_string(pfx, "Ethernet: 1000BASE-T");
if (id[6] & (1 << 2))
- sprintf(value, "%s", "Ethernet: 1000BASE-CX");
+ module_print_array_string(pfx, "Ethernet: 1000BASE-CX");
if (id[6] & (1 << 1))
- sprintf(value, "%s", "Ethernet: 1000BASE-LX");
+ module_print_array_string(pfx, "Ethernet: 1000BASE-LX");
if (id[6] & (1 << 0))
- sprintf(value, "%s", "Ethernet: 1000BASE-SX");
+ module_print_array_string(pfx, "Ethernet: 1000BASE-SX");
/* Fibre Channel link length */
if (id[7] & (1 << 7))
- sprintf(value, "%s", "FC: very long distance (V)");
+ module_print_array_string(pfx, "FC: very long distance (V)");
if (id[7] & (1 << 6))
- sprintf(value, "%s", "FC: short distance (S)");
+ module_print_array_string(pfx, "FC: short distance (S)");
if (id[7] & (1 << 5))
- sprintf(value, "%s", "FC: intermediate distance (I)");
+ module_print_array_string(pfx,
+ "FC: intermediate distance (I)");
if (id[7] & (1 << 4))
- sprintf(value, "%s", "FC: long distance (L)");
+ module_print_array_string(pfx, "FC: long distance (L)");
if (id[7] & (1 << 3))
- sprintf(value, "%s", "FC: medium distance (M)");
+ module_print_array_string(pfx, "FC: medium distance (M)");
/* Fibre Channel transmitter technology */
if (id[7] & (1 << 2))
- sprintf(value, "%s", "FC: Shortwave laser, linear Rx (SA)");
+ module_print_array_string(pfx,
+ "FC: Shortwave laser, linear Rx (SA)");
if (id[7] & (1 << 1))
- sprintf(value, "%s", "FC: Longwave laser (LC)");
+ module_print_array_string(pfx, "FC: Longwave laser (LC)");
if (id[7] & (1 << 0))
- sprintf(value, "%s", "FC: Electrical inter-enclosure (EL)");
+ module_print_array_string(pfx,
+ "FC: Electrical inter-enclosure (EL)");
if (id[8] & (1 << 7))
- sprintf(value, "%s", "FC: Electrical intra-enclosure (EL)");
+ module_print_array_string(pfx,
+ "FC: Electrical intra-enclosure (EL)");
if (id[8] & (1 << 6))
- sprintf(value, "%s", "FC: Shortwave laser w/o OFC (SN)");
+ module_print_array_string(pfx,
+ "FC: Shortwave laser w/o OFC (SN)");
if (id[8] & (1 << 5))
- sprintf(value, "%s", "FC: Shortwave laser with OFC (SL)");
+ module_print_array_string(pfx,
+ "FC: Shortwave laser with OFC (SL)");
if (id[8] & (1 << 4))
- sprintf(value, "%s", "FC: Longwave laser (LL)");
+ module_print_array_string(pfx, "FC: Longwave laser (LL)");
if (id[8] & (1 << 3))
- sprintf(value, "%s", "Active Cable");
+ module_print_array_string(pfx, "Active Cable");
if (id[8] & (1 << 2))
- sprintf(value, "%s", "Passive Cable");
+ module_print_array_string(pfx, "Passive Cable");
if (id[8] & (1 << 1))
- sprintf(value, "%s", "FC: Copper FC-BaseT");
+ module_print_array_string(pfx, "FC: Copper FC-BaseT");
/* Fibre Channel transmission media */
if (id[9] & (1 << 7))
- sprintf(value, "%s", "FC: Twin Axial Pair (TW)");
+ module_print_array_string(pfx, "FC: Twin Axial Pair (TW)");
if (id[9] & (1 << 6))
- sprintf(value, "%s", "FC: Twisted Pair (TP)");
+ module_print_array_string(pfx, "FC: Twisted Pair (TP)");
if (id[9] & (1 << 5))
- sprintf(value, "%s", "FC: Miniature Coax (MI)");
+ module_print_array_string(pfx, "FC: Miniature Coax (MI)");
if (id[9] & (1 << 4))
- sprintf(value, "%s", "FC: Video Coax (TV)");
+ module_print_array_string(pfx, "FC: Video Coax (TV)");
if (id[9] & (1 << 3))
- sprintf(value, "%s", "FC: Multimode, 62.5um (M6)");
+ module_print_array_string(pfx, "FC: Multimode, 62.5um (M6)");
if (id[9] & (1 << 2))
- sprintf(value, "%s", "FC: Multimode, 50um (M5)");
+ module_print_array_string(pfx, "FC: Multimode, 50um (M5)");
if (id[9] & (1 << 0))
- sprintf(value, "%s", "FC: Single Mode (SM)");
+ module_print_array_string(pfx, "FC: Single Mode (SM)");
/* Fibre Channel speed */
if (id[10] & (1 << 7))
- sprintf(value, "%s", "FC: 1200 MBytes/sec");
+ module_print_array_string(pfx, "FC: 1200 MBytes/sec");
if (id[10] & (1 << 6))
- sprintf(value, "%s", "FC: 800 MBytes/sec");
+ module_print_array_string(pfx, "FC: 800 MBytes/sec");
if (id[10] & (1 << 5))
- sprintf(value, "%s", "FC: 1600 MBytes/sec");
+ module_print_array_string(pfx, "FC: 1600 MBytes/sec");
if (id[10] & (1 << 4))
- sprintf(value, "%s", "FC: 400 MBytes/sec");
+ module_print_array_string(pfx, "FC: 400 MBytes/sec");
if (id[10] & (1 << 3))
- sprintf(value, "%s", "FC: 3200 MBytes/sec");
+ module_print_array_string(pfx, "FC: 3200 MBytes/sec");
if (id[10] & (1 << 2))
- sprintf(value, "%s", "FC: 200 MBytes/sec");
+ module_print_array_string(pfx, "FC: 200 MBytes/sec");
if (id[10] & (1 << 0))
- sprintf(value, "%s", "FC: 100 MBytes/sec");
+ module_print_array_string(pfx, "FC: 100 MBytes/sec");
/* Extended Specification Compliance Codes from SFF-8024 */
if (id[36] == 0x1)
- sprintf(value, "%s",
+ module_print_array_string(pfx,
"Extended: 100G AOC or 25GAUI C2M AOC with worst BER of 5x10^(-5)");
if (id[36] == 0x2)
- sprintf(value, "%s", "Extended: 100G Base-SR4 or 25GBase-SR");
+ module_print_array_string(pfx,
+ "Extended: 100G Base-SR4 or 25GBase-SR");
if (id[36] == 0x3)
- sprintf(value, "%s", "Extended: 100G Base-LR4 or 25GBase-LR");
+ module_print_array_string(pfx,
+ "Extended: 100G Base-LR4 or 25GBase-LR");
if (id[36] == 0x4)
- sprintf(value, "%s", "Extended: 100G Base-ER4 or 25GBase-ER");
+ module_print_array_string(pfx,
+ "Extended: 100G Base-ER4 or 25GBase-ER");
if (id[36] == 0x8)
- sprintf(value, "%s",
+ module_print_array_string(pfx,
"Extended: 100G ACC or 25GAUI C2M ACC with worst BER of 5x10^(-5)");
if (id[36] == 0xb)
- sprintf(value, "%s",
+ module_print_array_string(pfx,
"Extended: 100G Base-CR4 or 25G Base-CR CA-L");
if (id[36] == 0xc)
- sprintf(value, "%s", "Extended: 25G Base-CR CA-S");
+ module_print_array_string(pfx, "Extended: 25G Base-CR CA-S");
if (id[36] == 0xd)
- sprintf(value, "%s", "Extended: 25G Base-CR CA-N");
+ module_print_array_string(pfx, "Extended: 25G Base-CR CA-N");
if (id[36] == 0x16)
- sprintf(value, "%s",
+ module_print_array_string(pfx,
"Extended: 10Gbase-T with SFI electrical interface");
if (id[36] == 0x18)
- sprintf(value, "%s",
+ module_print_array_string(pfx,
"Extended: 100G AOC or 25GAUI C2M AOC with worst BER of 10^(-12)");
if (id[36] == 0x19)
- sprintf(value, "%s",
+ module_print_array_string(pfx,
"Extended: 100G ACC or 25GAUI C2M ACC with worst BER of 10^(-12)");
if (id[36] == 0x1a)
- sprintf(value, "%s",
+ module_print_array_string(pfx,
"Extended: 100GE-DWDM2 (DWDM transceiver using 2 wavelengths on a 1550 nm DWDM grid with a reach up to 80 km)");
if (id[36] == 0x1b)
- sprintf(value, "%s",
+ module_print_array_string(pfx,
"Extended: 100G 1550nm WDM (4 wavelengths)");
if (id[36] == 0x1c)
- sprintf(value, "%s", "Extended: 10Gbase-T Short Reach");
+ module_print_array_string(pfx,
+ "Extended: 10Gbase-T Short Reach");
if (id[36] == 0x1d)
- sprintf(value, "%s", "Extended: 5GBASE-T");
+ module_print_array_string(pfx, "Extended: 5GBASE-T");
if (id[36] == 0x1e)
- sprintf(value, "%s", "Extended: 2.5GBASE-T");
+ module_print_array_string(pfx, "Extended: 2.5GBASE-T");
if (id[36] == 0x1f)
- sprintf(value, "%s", "Extended: 40G SWDM4");
+ module_print_array_string(pfx, "Extended: 40G SWDM4");
if (id[36] == 0x20)
- sprintf(value, "%s", "Extended: 100G SWDM4");
+ module_print_array_string(pfx, "Extended: 100G SWDM4");
if (id[36] == 0x21)
- sprintf(value, "%s", "Extended: 100G PAM4 BiDi");
+ module_print_array_string(pfx, "Extended: 100G PAM4 BiDi");
if (id[36] == 0x22)
- sprintf(value, "%s",
+ module_print_array_string(pfx,
"Extended: 4WDM-10 MSA (10km version of 100G CWDM4 with same RS(528,514) FEC in host system)");
if (id[36] == 0x23)
- sprintf(value, "%s",
+ module_print_array_string(pfx,
"Extended: 4WDM-20 MSA (20km version of 100GBASE-LR4 with RS(528,514) FEC in host system)");
if (id[36] == 0x24)
- sprintf(value, "%s",
+ module_print_array_string(pfx,
"Extended: 4WDM-40 MSA (40km reach with APD receiver and RS(528,514) FEC in host system)");
if (id[36] == 0x25)
- sprintf(value, "%s",
+ module_print_array_string(pfx,
"Extended: 100GBASE-DR (clause 140), CAUI-4 (no FEC)");
if (id[36] == 0x26)
- sprintf(value, "%s",
+ module_print_array_string(pfx,
"Extended: 100G-FR or 100GBASE-FR1 (clause 140), CAUI-4 (no FEC)");
if (id[36] == 0x27)
- sprintf(value, "%s",
+ module_print_array_string(pfx,
"Extended: 100G-LR or 100GBASE-LR1 (clause 140), CAUI-4 (no FEC)");
if (id[36] == 0x30)
- sprintf(value, "%s",
+ module_print_array_string(pfx,
"Extended: Active Copper Cable with 50GAUI, 100GAUI-2 or 200GAUI-4 C2M. Providing a worst BER of 10-6 or below");
if (id[36] == 0x31)
- sprintf(value, "%s",
+ module_print_array_string(pfx,
"Extended: Active Optical Cable with 50GAUI, 100GAUI-2 or 200GAUI-4 C2M. Providing a worst BER of 10-6 or below");
if (id[36] == 0x32)
- sprintf(value, "%s",
+ module_print_array_string(pfx,
"Extended: Active Copper Cable with 50GAUI, 100GAUI-2 or 200GAUI-4 C2M. Providing a worst BER of 2.6x10-4 for ACC, 10-5 for AUI, or below");
if (id[36] == 0x33)
- sprintf(value, "%s",
+ module_print_array_string(pfx,
"Extended: Active Optical Cable with 50GAUI, 100GAUI-2 or 200GAUI-4 C2M. Providing a worst BER of 2.6x10-4 for ACC, 10-5 for AUI, or below");
if (id[36] == 0x40)
- sprintf(value, "%s",
+ module_print_array_string(pfx,
"Extended: 50GBASE-CR, 100GBASE-CR2, or 200GBASE-CR4");
if (id[36] == 0x41)
- sprintf(value, "%s",
+ module_print_array_string(pfx,
"Extended: 50GBASE-SR, 100GBASE-SR2, or 200GBASE-SR4");
if (id[36] == 0x42)
- sprintf(value, "%s", "Extended: 50GBASE-FR or 200GBASE-DR4");
+ module_print_array_string(pfx,
+ "Extended: 50GBASE-FR or 200GBASE-DR4");
if (id[36] == 0x43)
- sprintf(value, "%s", "Extended: 200GBASE-FR4");
+ module_print_array_string(pfx, "Extended: 200GBASE-FR4");
if (id[36] == 0x44)
- sprintf(value, "%s", "Extended: 200G 1550 nm PSM4");
+ module_print_array_string(pfx, "Extended: 200G 1550 nm PSM4");
if (id[36] == 0x45)
- sprintf(value, "%s", "Extended: 50GBASE-LR");
+ module_print_array_string(pfx, "Extended: 50GBASE-LR");
if (id[36] == 0x46)
- sprintf(value, "%s", "Extended: 200GBASE-LR4");
+ module_print_array_string(pfx, "Extended: 200GBASE-LR4");
if (id[36] == 0x50)
- sprintf(value, "%s", "Extended: 64GFC EA");
+ module_print_array_string(pfx, "Extended: 64GFC EA");
if (id[36] == 0x51)
- sprintf(value, "%s", "Extended: 64GFC SW");
+ module_print_array_string(pfx, "Extended: 64GFC SW");
if (id[36] == 0x52)
- sprintf(value, "%s", "Extended: 64GFC LW");
+ module_print_array_string(pfx, "Extended: 64GFC LW");
if (id[36] == 0x53)
- sprintf(value, "%s", "Extended: 128GFC EA");
+ module_print_array_string(pfx, "Extended: 128GFC EA");
if (id[36] == 0x54)
- sprintf(value, "%s", "Extended: 128GFC SW");
+ module_print_array_string(pfx, "Extended: 128GFC SW");
if (id[36] == 0x55)
- sprintf(value, "%s", "Extended: 128GFC LW");
+ module_print_array_string(pfx, "Extended: 128GFC LW");
- if (value[0] != '\0')
- module_print_any_string(pfx, value);
+ if (is_json_context())
+ close_json_array("");
}
static void sff8079_show_encoding(const __u8 *id)
--
2.53.0
^ permalink raw reply related
* [PATCH ethtool-next 1/2] sfpid: print all implemented options
From: Aleksander Jan Bajkowski @ 2026-07-05 20:06 UTC (permalink / raw)
To: mkubecek, andrew, danieller, kuba, davem, edumazet, pabeni,
netdev
Cc: Aleksander Jan Bajkowski
SFP modules implement multiple options. Before the “json” option was
instroduced, all options were listed. Currently, only the last option
is listed. This commit fixes this bug. Options are represented as array.
Before:
$ ethtool -m sfp-wan
...
Option values : 0x00 0x32
Option : RATE_SELECT implemented
...
$ ethtool --json -m sfp-wan
[ {
...
"option_values": [ 0,50 ],
"option": "RATE_SELECT implemented",
...
} ]
After:
$ ethtool -m sfp-lan
...
Option values : 0x00 0x32
Option : RX_LOS implemented
Option : TX_DISABLE implemented
Option : RATE_SELECT implemented
...
$ ethtool --json -m sfp-lan
[ {
..
"option_values": [ 0,50 ],
"option": [ "RX_LOS implemented","TX_DISABLE implemented","RATE_SELECT implemented" ],
} ]
..
Fixes: 4071862f58d8 ("sfpid: Add JSON output handling to --module-info in SFF8079 modules")
Signed-off-by: Aleksander Jan Bajkowski <olek2@wp.pl>
---
module-common.c | 8 ++++++++
module-common.h | 1 +
sfpid.c | 39 +++++++++++++++++++++++----------------
3 files changed, 32 insertions(+), 16 deletions(-)
diff --git a/module-common.c b/module-common.c
index 42fccf6..d736c7c 100644
--- a/module-common.c
+++ b/module-common.c
@@ -258,6 +258,14 @@ void module_print_any_bool(const char *fn, char *given_json_fn, bool value,
printf("\t%-41s : %s\n", fn, str_value);
}
+void module_print_array_string(const char *fn, const char *value)
+{
+ if (is_json_context())
+ print_string(PRINT_JSON, NULL, "%s", value);
+ else
+ printf("\t%-41s : %s\n", fn, value);
+}
+
void module_show_value_with_unit(const __u8 *id, unsigned int reg,
const char *name, unsigned int mult,
const char *unit)
diff --git a/module-common.h b/module-common.h
index 4063448..6f9da5f 100644
--- a/module-common.h
+++ b/module-common.h
@@ -281,6 +281,7 @@ void module_print_any_string(const char *fn, const char *value);
void module_print_any_float(const char *fn, float value, const char *unit);
void module_print_any_bool(const char *fn, char *given_json_fn, bool value,
const char *str_value);
+void module_print_array_string(const char *fn, const char *value);
void module_show_value_with_unit(const __u8 *id, unsigned int reg,
const char *name, unsigned int mult,
const char *unit);
diff --git a/sfpid.c b/sfpid.c
index 74a6f51..d6636ba 100644
--- a/sfpid.c
+++ b/sfpid.c
@@ -396,7 +396,6 @@ static void sff8079_show_wavelength_or_copper_compliance(const __u8 *id)
static void sff8079_show_options(const __u8 *id)
{
static const char *pfx = "Option";
- char value[64] = "";
if (is_json_context()) {
open_json_array("option_values", "");
@@ -407,35 +406,43 @@ static void sff8079_show_options(const __u8 *id)
printf("\t%-41s : 0x%02x 0x%02x\n", "Option values", id[64],
id[65]);
}
+
+ if (is_json_context())
+ open_json_array("option", "");
+
if (id[65] & (1 << 1))
- sprintf(value, "%s", "RX_LOS implemented");
+ module_print_array_string(pfx, "RX_LOS implemented");
if (id[65] & (1 << 2))
- sprintf(value, "%s", "RX_LOS implemented, inverted");
+ module_print_array_string(pfx, "RX_LOS implemented, inverted");
if (id[65] & (1 << 3))
- sprintf(value, "%s", "TX_FAULT implemented");
+ module_print_array_string(pfx, "TX_FAULT implemented");
if (id[65] & (1 << 4))
- sprintf(value, "%s", "TX_DISABLE implemented");
+ module_print_array_string(pfx, "TX_DISABLE implemented");
if (id[65] & (1 << 5))
- sprintf(value, "%s", "RATE_SELECT implemented");
+ module_print_array_string(pfx, "RATE_SELECT implemented");
if (id[65] & (1 << 6))
- sprintf(value, "%s", "Tunable transmitter technology");
+ module_print_array_string(pfx,
+ "Tunable transmitter technology");
if (id[65] & (1 << 7))
- sprintf(value, "%s", "Receiver decision threshold implemented");
+ module_print_array_string(pfx,
+ "Receiver decision threshold implemented");
if (id[64] & (1 << 0))
- sprintf(value, "%s", "Linear receiver output implemented");
+ module_print_array_string(pfx,
+ "Linear receiver output implemented");
if (id[64] & (1 << 1))
- sprintf(value, "%s", "Power level 2 requirement");
+ module_print_array_string(pfx, "Power level 2 requirement");
if (id[64] & (1 << 2))
- sprintf(value, "%s", "Cooled transceiver implemented");
+ module_print_array_string(pfx,
+ "Cooled transceiver implemented");
if (id[64] & (1 << 3))
- sprintf(value, "%s", "Retimer or CDR implemented");
+ module_print_array_string(pfx, "Retimer or CDR implemented");
if (id[64] & (1 << 4))
- sprintf(value, "%s", "Paging implemented");
+ module_print_array_string(pfx, "Paging implemented");
if (id[64] & (1 << 5))
- sprintf(value, "%s", "Power level 3 requirement");
+ module_print_array_string(pfx, "Power level 3 requirement");
- if (value[0] != '\0')
- module_print_any_string(pfx, value);
+ if (is_json_context())
+ close_json_array("");
}
static void sff8079_show_all_common(const __u8 *id)
--
2.53.0
^ permalink raw reply related
* Re: [PATCH net] sctp: validate the body of a STALE_COOKIE error before reading it
From: Xin Long @ 2026-07-05 19:13 UTC (permalink / raw)
To: Xiang Mei
Cc: Marcelo Ricardo Leitner, David S . Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, linux-sctp, netdev,
linux-kernel, bestswngs
In-Reply-To: <20260705003013.1134430-1-xmei5@asu.edu>
On Sat, Jul 4, 2026 at 8:31 PM Xiang Mei <xmei5@asu.edu> wrote:
>
> sctp_sf_do_5_2_6_stale() reads the 32-bit Measure of Staleness that
> follows the error header:
>
> stale = ntohl(*(__be32 *)((u8 *)err + sizeof(*err)));
>
> without checking that the STALE_COOKIE cause actually carries that
> 4-byte body. sctp_walk_errors() in the caller only requires
> err->length >= sizeof(struct sctp_errhdr), so a peer can send an 8-byte
> ERROR chunk whose sole STALE_COOKIE cause has length == 4 and no body.
> It passes sctp_chunk_length_valid() (>= 8) and the error walk, yet the
> staleness read reaches past the validated cause.
>
> When that is the only chunk in the packet the cause ends exactly at
> skb_tail (sctp_inq_pop() discards only when chunk_end > skb_tail), so
> the read stays in-bounds of the skb head slab object but past the packet
> data. The value is folded into the COOKIE_PRESERVATIVE parameter of the
> retransmitted INIT and reflected to the peer, leaking adjacent kernel
> slab bytes.
>
> Discard the chunk when the staleness field falls outside the validated
> chunk data.
>
> Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
> Reported-by: Weiming Shi <bestswngs@gmail.com>
> Assisted-by: Claude:claude-opus-4-8
> Signed-off-by: Xiang Mei <xmei5@asu.edu>
> ---
> net/sctp/sm_statefuns.c | 3 +++
> 1 file changed, 3 insertions(+)
>
> diff --git a/net/sctp/sm_statefuns.c b/net/sctp/sm_statefuns.c
> index d23d935e128e..e4b4b63162cf 100644
> --- a/net/sctp/sm_statefuns.c
> +++ b/net/sctp/sm_statefuns.c
> @@ -2592,6 +2592,9 @@ static enum sctp_disposition sctp_sf_do_5_2_6_stale(
>
> err = (struct sctp_errhdr *)(chunk->skb->data);
>
> + if ((u8 *)err + sizeof(*err) + sizeof(__be32) > chunk->chunk_end)
> + return sctp_sf_pdiscard(net, ep, asoc, type, arg, commands);
> +
> /* When calculating the time extension, an implementation
> * SHOULD use the RTT information measured based on the
> * previous COOKIE ECHO / ERROR exchange, and should add no
> --
> 2.43.0
>
I think this is a dup of
https://lore.kernel.org/netdev/20260704033545.2438373-2-bestswngs@gmail.com/.
Thanks.
^ permalink raw reply
* Re: [PATCH net] sctp: validate STALE_COOKIE cause length before reading staleness
From: Xin Long @ 2026-07-05 19:12 UTC (permalink / raw)
To: Weiming Shi
Cc: linux-sctp, Marcelo Ricardo Leitner, David S . Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, netdev, Xiang Mei,
stable
In-Reply-To: <20260704033545.2438373-2-bestswngs@gmail.com>
On Fri, Jul 3, 2026 at 11:35 PM Weiming Shi <bestswngs@gmail.com> wrote:
>
> When an ERROR chunk with a STALE_COOKIE cause is received in the
> COOKIE_ECHOED state, sctp_sf_do_5_2_6_stale() reads the 4-byte Measure
> of Staleness that follows the cause header:
>
> err = (struct sctp_errhdr *)(chunk->skb->data);
> stale = ntohl(*(__be32 *)((u8 *)err + sizeof(*err)));
>
> err is the first cause in the chunk, not the STALE_COOKIE cause that
> caused the dispatch, and nothing guarantees the staleness field is
> present. sctp_walk_errors() only requires a cause to be as long as the
> 4-byte header, so for a STALE_COOKIE cause of length 4 the read runs
> past the cause, and for a minimal ERROR chunk past skb->tail. The value
> is echoed to the peer in the Cookie Preservative of the reply INIT,
> leaking uninitialized memory.
>
> sctp_sf_cookie_echoed_err() already walks to the STALE_COOKIE cause, so
> check its length there and pass it to sctp_sf_do_5_2_6_stale(), which
> reads that cause instead of the first one. A STALE_COOKIE cause too
> short to hold the staleness field is discarded.
>
> The read is reachable by any peer that can drive an association into
> COOKIE_ECHOED, including an unprivileged process using a raw SCTP socket
> in a user and network namespace.
>
> Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
> Reported-by: Xiang Mei <xmei5@asu.edu>
> Assisted-by: Claude:claude-opus-4-8
> Cc: stable@vger.kernel.org
> Signed-off-by: Weiming Shi <bestswngs@gmail.com>
Acked-by: Xin Long <lucien.xin@gmail.com>
^ permalink raw reply
* [PATCH net-next v2 2/2] nfc: s3fwrn5: support the S3NRN4V variant
From: Jorijn van der Graaf @ 2026-07-05 19:06 UTC (permalink / raw)
To: Krzysztof Kozlowski
Cc: Jorijn van der Graaf, David Heidelberg, Andrew Lunn,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Rob Herring, Conor Dooley, oe-linux-nfc, netdev, devicetree,
linux-kernel
In-Reply-To: <20260705190621.128257-1-jorijnvdgraaf@catcrafts.net>
The S3NRN4V (e.g. on the Fairphone 6, SM7635) is an S3FWRN5-family NFC
controller that needs different bring-up, selected with a new
samsung,s3nrn4v compatible:
- It ships with working firmware behind a bootloader protocol this
driver does not implement (GET_BOOTINFO times out), so the firmware
download step is skipped. Its RF registers are (re)loaded with the
proprietary DUAL_OPTION command (the HW and SW register blobs merged
into a single stream) instead of the START/SET/STOP_RFREG sequence.
- Its reference clock speed is configured with the single-byte FW_CFG
form, sent from the ->setup hook (after CORE_RESET, before CORE_INIT).
The selector value (0x11) is taken from the vendor configuration for
this part; its encoding is not documented.
- It gates its XI clock through a CLK_REQ line: the chip drives it high
when it needs the clock, notably to synthesise the 13.56 MHz poll
carrier. Left always-on, the free-running clock never lets the chip's
TX PLL lock on a fresh start and it cannot poll (it falls back to
listen only). Service the handshake when a clk-req GPIO is described,
gating the clock on it; without one the clock stays always-on.
The variant is carried as match data by both the OF and the I2C device
id tables so the two match paths agree, and the OF table is now
referenced unconditionally for its match data, so drop the
of_match_ptr()/__maybe_unused annotations from it.
The error policy differs between the two configuration steps on purpose:
a clock misconfiguration is fatal (a ->setup failure aborts CORE_INIT),
whereas an RF-register update failure is only warned about and bring-up
continues, since the chip falls back to the RF registers programmed in
its flash and NFC may still work.
Unlike the host-endian word read in the legacy rfreg path, the
DUAL_OPTION checksum is accumulated with get_unaligned_le32() and emitted
little-endian explicitly, so it is correct regardless of CPU endianness.
Existing S3FWRN5 / S3FWRN82 setups keep the firmware-download path and
the always-on clock, unchanged.
Assisted-by: Claude:claude-opus-4-8
Assisted-by: Claude:claude-fable-5
Signed-off-by: Jorijn van der Graaf <jorijnvdgraaf@catcrafts.net>
---
Changes in v2:
- Rename the new compatible to samsung,s3nrn4v, matching the binding
change (Requested by: Conor Dooley).
- Close a race in the probe-time CLK_REQ seeding: the GPIO level is
now read under clk_lock (new s3fwrn5_i2c_clk_sync(), used by both
the irq thread and probe), so a level read before the irq fired can
never overwrite the fresher state the irq thread applied (found by
the Sashiko AI review of v1).
- Reject malformed rfreg blobs (word alignment, single-byte section
index bound) up front instead of failing at STOP_UPDATE.
- Handle gpiod_get_value_cansleep() failure instead of gating the
clock off on error.
- Add an s3nrn4v i2c_device_id entry carrying the variant so both
match paths agree.
- Describe the of_match_ptr()/__maybe_unused removal in the commit
message.
v1: https://lore.kernel.org/20260703202601.78563-3-jorijnvdgraaf@catcrafts.net
drivers/nfc/s3fwrn5/core.c | 40 +++++++++-
drivers/nfc/s3fwrn5/i2c.c | 138 +++++++++++++++++++++++++++++++---
drivers/nfc/s3fwrn5/nci.c | 119 ++++++++++++++++++++++++++++-
drivers/nfc/s3fwrn5/nci.h | 32 +++++++-
drivers/nfc/s3fwrn5/s3fwrn5.h | 14 +++-
drivers/nfc/s3fwrn5/uart.c | 2 +-
6 files changed, 330 insertions(+), 15 deletions(-)
diff --git a/drivers/nfc/s3fwrn5/core.c b/drivers/nfc/s3fwrn5/core.c
index af0fa8bd970b..59317eaad7ac 100644
--- a/drivers/nfc/s3fwrn5/core.c
+++ b/drivers/nfc/s3fwrn5/core.c
@@ -122,11 +122,47 @@ static int s3fwrn5_nci_send(struct nci_dev *ndev, struct sk_buff *skb)
return 0;
}
+static int s3fwrn5_nci_setup(struct nci_dev *ndev)
+{
+ struct s3fwrn5_info *info = nci_get_drvdata(ndev);
+
+ /*
+ * Runs after CORE_RESET, before CORE_INIT. The S3NRN4V needs its
+ * reference clock configured here (the downstream stack does it in the
+ * bootloader, before CORE_RESET, but this is the earliest hook the NCI
+ * core offers and the chip accepts it).
+ */
+ if (info->variant == S3FWRN5_VARIANT_S3NRN4V)
+ return s3fwrn5_nci_clk_cfg(info);
+
+ return 0;
+}
+
static int s3fwrn5_nci_post_setup(struct nci_dev *ndev)
{
struct s3fwrn5_info *info = nci_get_drvdata(ndev);
int ret;
+ if (info->variant == S3FWRN5_VARIANT_S3NRN4V) {
+ /*
+ * The S3NRN4V ships with working firmware behind a bootloader
+ * protocol this driver does not implement, so there is no
+ * download step; the NCI core has already done CORE_RESET +
+ * CORE_INIT. Just (re)load the RF registers via DUAL_OPTION.
+ */
+ ret = s3fwrn5_nci_rf_configure_dual(info, "sec_s3nrn4v_hwreg.bin",
+ "sec_s3nrn4v_swreg.bin");
+ /*
+ * Keep going even if the blobs could not be loaded: the chip
+ * still enumerates and falls back to the RF registers programmed
+ * in its flash, so NFC may work anyway.
+ */
+ if (ret < 0)
+ dev_warn(&ndev->nfc_dev->dev,
+ "rfreg configure failed (%d)\n", ret);
+ return 0;
+ }
+
if (s3fwrn5_firmware_init(info)) {
//skip bootloader mode
return 0;
@@ -152,13 +188,14 @@ static const struct nci_ops s3fwrn5_nci_ops = {
.open = s3fwrn5_nci_open,
.close = s3fwrn5_nci_close,
.send = s3fwrn5_nci_send,
+ .setup = s3fwrn5_nci_setup,
.post_setup = s3fwrn5_nci_post_setup,
.prop_ops = s3fwrn5_nci_prop_ops,
.n_prop_ops = ARRAY_SIZE(s3fwrn5_nci_prop_ops),
};
int s3fwrn5_probe(struct nci_dev **ndev, void *phy_id, struct device *pdev,
- const struct s3fwrn5_phy_ops *phy_ops)
+ const struct s3fwrn5_phy_ops *phy_ops, enum s3fwrn5_variant variant)
{
struct s3fwrn5_info *info;
int ret;
@@ -170,6 +207,7 @@ int s3fwrn5_probe(struct nci_dev **ndev, void *phy_id, struct device *pdev,
info->phy_id = phy_id;
info->pdev = pdev;
info->phy_ops = phy_ops;
+ info->variant = variant;
mutex_init(&info->mutex);
s3fwrn5_set_mode(info, S3FWRN5_MODE_COLD);
diff --git a/drivers/nfc/s3fwrn5/i2c.c b/drivers/nfc/s3fwrn5/i2c.c
index e9a34d27a369..7d20e737e402 100644
--- a/drivers/nfc/s3fwrn5/i2c.c
+++ b/drivers/nfc/s3fwrn5/i2c.c
@@ -23,9 +23,76 @@ struct s3fwrn5_i2c_phy {
struct i2c_client *i2c_dev;
struct clk *clk;
+ /*
+ * Optional hardware clock-request handshake. When a CLK_REQ GPIO is
+ * wired, the chip drives it high while it needs its XI clock -- notably
+ * to generate the poll/reader carrier -- and the clock is gated on it
+ * instead of being left always-on (which never lets the chip's TX PLL
+ * lock on a fresh clock start, leaving it unable to poll).
+ */
+ struct gpio_desc *gpio_clk_req;
+ bool clk_on;
+ struct mutex clk_lock; /* serialises clk_on against the CLK_REQ irq */
+
unsigned int irq_skip:1;
};
+static void s3fwrn5_i2c_clk_set_locked(struct s3fwrn5_i2c_phy *phy, bool on)
+{
+ lockdep_assert_held(&phy->clk_lock);
+
+ if (on && !phy->clk_on) {
+ int ret = clk_prepare_enable(phy->clk);
+
+ if (ret == 0)
+ phy->clk_on = true;
+ else
+ dev_warn_once(&phy->i2c_dev->dev,
+ "failed to enable clock (%d); NFC may not poll\n",
+ ret);
+ } else if (!on && phy->clk_on) {
+ clk_disable_unprepare(phy->clk);
+ phy->clk_on = false;
+ }
+}
+
+/*
+ * Apply the current CLK_REQ level. Reading the GPIO under clk_lock makes
+ * concurrent callers (the CLK_REQ irq thread and the probe-time seeding)
+ * safe: whoever runs last applies a level read after the earlier update,
+ * never a stale one.
+ */
+static void s3fwrn5_i2c_clk_sync(struct s3fwrn5_i2c_phy *phy)
+{
+ int level;
+
+ mutex_lock(&phy->clk_lock);
+ level = gpiod_get_value_cansleep(phy->gpio_clk_req);
+ if (level >= 0)
+ s3fwrn5_i2c_clk_set_locked(phy, level > 0);
+ else
+ dev_warn_once(&phy->i2c_dev->dev,
+ "failed to read CLK_REQ (%d); keeping clock state\n",
+ level);
+ mutex_unlock(&phy->clk_lock);
+}
+
+static void s3fwrn5_i2c_clk_disable_action(void *data)
+{
+ struct s3fwrn5_i2c_phy *phy = data;
+
+ mutex_lock(&phy->clk_lock);
+ s3fwrn5_i2c_clk_set_locked(phy, false);
+ mutex_unlock(&phy->clk_lock);
+}
+
+static irqreturn_t s3fwrn5_i2c_clk_req_thread(int irq, void *phy_id)
+{
+ s3fwrn5_i2c_clk_sync(phy_id);
+
+ return IRQ_HANDLED;
+}
+
static void s3fwrn5_i2c_set_mode(void *phy_id, enum s3fwrn5_mode mode)
{
struct s3fwrn5_i2c_phy *phy = phy_id;
@@ -146,6 +213,7 @@ static irqreturn_t s3fwrn5_i2c_irq_thread_fn(int irq, void *phy_id)
static int s3fwrn5_i2c_probe(struct i2c_client *client)
{
+ enum s3fwrn5_variant variant;
struct s3fwrn5_i2c_phy *phy;
int ret;
@@ -172,15 +240,61 @@ static int s3fwrn5_i2c_probe(struct i2c_client *client)
* S3FWRN5 depends on a clock input ("XI" pin) to function properly.
* Depending on the hardware configuration this could be an always-on
* oscillator or some external clock that must be explicitly enabled.
- * Make sure the clock is running before starting S3FWRN5.
+ *
+ * If a CLK_REQ GPIO is wired, the chip gates the clock itself (driving
+ * CLK_REQ high when it needs XI); service that handshake. Otherwise just
+ * make sure the clock is running before starting S3FWRN5.
*/
- phy->clk = devm_clk_get_optional_enabled(&client->dev, NULL);
- if (IS_ERR(phy->clk))
- return dev_err_probe(&client->dev, PTR_ERR(phy->clk),
- "failed to get clock\n");
+ mutex_init(&phy->clk_lock);
+ phy->gpio_clk_req = devm_gpiod_get_optional(&client->dev, "clk-req",
+ GPIOD_IN);
+ if (IS_ERR(phy->gpio_clk_req))
+ return PTR_ERR(phy->gpio_clk_req);
+
+ if (phy->gpio_clk_req) {
+ int clk_req_irq;
+
+ phy->clk = devm_clk_get_optional(&client->dev, NULL);
+ if (IS_ERR(phy->clk))
+ return dev_err_probe(&client->dev, PTR_ERR(phy->clk),
+ "failed to get clock\n");
+
+ /*
+ * Unlike the always-on branch below, this clock is enabled by
+ * hand from the CLK_REQ handler, so devm will not disable it on
+ * unbind. Gate it off explicitly if it is still on at teardown.
+ */
+ ret = devm_add_action_or_reset(&client->dev,
+ s3fwrn5_i2c_clk_disable_action,
+ phy);
+ if (ret)
+ return ret;
+
+ clk_req_irq = gpiod_to_irq(phy->gpio_clk_req);
+ if (clk_req_irq < 0)
+ return clk_req_irq;
+
+ ret = devm_request_threaded_irq(&client->dev, clk_req_irq, NULL,
+ s3fwrn5_i2c_clk_req_thread,
+ IRQF_TRIGGER_RISING |
+ IRQF_TRIGGER_FALLING |
+ IRQF_ONESHOT,
+ "s3fwrn5_clk_req", phy);
+ if (ret)
+ return ret;
+
+ /* Seed the clock state from the current CLK_REQ level. */
+ s3fwrn5_i2c_clk_sync(phy);
+ } else {
+ phy->clk = devm_clk_get_optional_enabled(&client->dev, NULL);
+ if (IS_ERR(phy->clk))
+ return dev_err_probe(&client->dev, PTR_ERR(phy->clk),
+ "failed to get clock\n");
+ }
+ variant = (uintptr_t)i2c_get_match_data(client);
ret = s3fwrn5_probe(&phy->common.ndev, phy, &phy->i2c_dev->dev,
- &i2c_phy_ops);
+ &i2c_phy_ops, variant);
if (ret < 0)
return ret;
@@ -205,13 +319,17 @@ static void s3fwrn5_i2c_remove(struct i2c_client *client)
}
static const struct i2c_device_id s3fwrn5_i2c_id_table[] = {
- { .name = S3FWRN5_I2C_DRIVER_NAME },
+ { .name = S3FWRN5_I2C_DRIVER_NAME, .driver_data = S3FWRN5_VARIANT_FWDL },
+ { .name = "s3nrn4v", .driver_data = S3FWRN5_VARIANT_S3NRN4V },
{ }
};
MODULE_DEVICE_TABLE(i2c, s3fwrn5_i2c_id_table);
-static const struct of_device_id of_s3fwrn5_i2c_match[] __maybe_unused = {
- { .compatible = "samsung,s3fwrn5-i2c", },
+static const struct of_device_id of_s3fwrn5_i2c_match[] = {
+ { .compatible = "samsung,s3fwrn5-i2c",
+ .data = (void *)S3FWRN5_VARIANT_FWDL, },
+ { .compatible = "samsung,s3nrn4v",
+ .data = (void *)S3FWRN5_VARIANT_S3NRN4V, },
{}
};
MODULE_DEVICE_TABLE(of, of_s3fwrn5_i2c_match);
@@ -219,7 +337,7 @@ MODULE_DEVICE_TABLE(of, of_s3fwrn5_i2c_match);
static struct i2c_driver s3fwrn5_i2c_driver = {
.driver = {
.name = S3FWRN5_I2C_DRIVER_NAME,
- .of_match_table = of_match_ptr(of_s3fwrn5_i2c_match),
+ .of_match_table = of_s3fwrn5_i2c_match,
},
.probe = s3fwrn5_i2c_probe,
.remove = s3fwrn5_i2c_remove,
diff --git a/drivers/nfc/s3fwrn5/nci.c b/drivers/nfc/s3fwrn5/nci.c
index 5a9de11bbece..7034fb810e18 100644
--- a/drivers/nfc/s3fwrn5/nci.c
+++ b/drivers/nfc/s3fwrn5/nci.c
@@ -8,6 +8,9 @@
#include <linux/completion.h>
#include <linux/firmware.h>
+#include <linux/minmax.h>
+#include <linux/slab.h>
+#include <linux/unaligned.h>
#include "s3fwrn5.h"
#include "nci.h"
@@ -20,7 +23,7 @@ static int s3fwrn5_nci_prop_rsp(struct nci_dev *ndev, struct sk_buff *skb)
return 0;
}
-const struct nci_driver_ops s3fwrn5_nci_prop_ops[4] = {
+const struct nci_driver_ops s3fwrn5_nci_prop_ops[5] = {
{
.opcode = nci_opcode_pack(NCI_GID_PROPRIETARY,
NCI_PROP_SET_RFREG),
@@ -41,6 +44,11 @@ const struct nci_driver_ops s3fwrn5_nci_prop_ops[4] = {
NCI_PROP_FW_CFG),
.rsp = s3fwrn5_nci_prop_rsp,
},
+ {
+ .opcode = nci_opcode_pack(NCI_GID_PROPRIETARY,
+ NCI_PROP_DUAL_OPTION),
+ .rsp = s3fwrn5_nci_prop_rsp,
+ },
};
#define S3FWRN5_RFREG_SECTION_SIZE 252
@@ -117,3 +125,112 @@ int s3fwrn5_nci_rf_configure(struct s3fwrn5_info *info, const char *fw_name)
release_firmware(fw);
return ret;
}
+
+/*
+ * Configure the reference clock. The S3NRN4V expects the single-byte FW_CFG
+ * form (just the clock-speed selector). The downstream stack sends this in the
+ * bootloader before CORE_RESET; the earliest the mainline NCI core lets us in
+ * is the ->setup hook (after CORE_RESET, before CORE_INIT), which works.
+ */
+int s3fwrn5_nci_clk_cfg(struct s3fwrn5_info *info)
+{
+ u8 clk_speed = NCI_PROP_FW_CFG_CLK_SPEED;
+
+ return nci_prop_cmd(info->ndev, NCI_PROP_FW_CFG, 1, &clk_speed);
+}
+
+/*
+ * S3NRN4V RF register update. The HW and SW register blobs are merged into a
+ * single stream (HW first) and pushed via the DUAL_OPTION command:
+ * START_UPDATE, one SET_OPTION per 252-byte section, then STOP_UPDATE carrying
+ * a 16-bit checksum (running sum of the merged stream as 32-bit words).
+ */
+int s3fwrn5_nci_rf_configure_dual(struct s3fwrn5_info *info,
+ const char *hw_name, const char *sw_name)
+{
+ const struct firmware *hw_fw = NULL, *sw_fw = NULL;
+ struct nci_prop_dual_set_option_cmd set_option;
+ struct device *dev = &info->ndev->nfc_dev->dev;
+ size_t merged_size, i, len;
+ u8 *merged = NULL;
+ u8 stop_cmd[3];
+ u32 checksum;
+ u8 sub_oid;
+ int ret;
+
+ ret = request_firmware(&hw_fw, hw_name, dev);
+ if (ret < 0)
+ return ret;
+ ret = request_firmware(&sw_fw, sw_name, dev);
+ if (ret < 0)
+ goto out_hw;
+
+ merged_size = hw_fw->size + sw_fw->size;
+
+ /*
+ * The stream is checksummed as 32-bit words and pushed in at most 256
+ * sections (the section index is a single byte); reject blobs that
+ * would silently break either.
+ */
+ if (merged_size % 4 ||
+ merged_size > 256 * NCI_PROP_DUAL_SECTION_SIZE) {
+ dev_err(dev, "invalid rfreg blob size (%zu)\n", merged_size);
+ ret = -EINVAL;
+ goto out;
+ }
+
+ merged = kmalloc(merged_size, GFP_KERNEL);
+ if (!merged) {
+ ret = -ENOMEM;
+ goto out;
+ }
+ memcpy(merged, hw_fw->data, hw_fw->size);
+ memcpy(merged + hw_fw->size, sw_fw->data, sw_fw->size);
+
+ /* Running sum of the merged stream as little-endian 32-bit words. */
+ checksum = 0;
+ for (i = 0; i + 4 <= merged_size; i += 4)
+ checksum += get_unaligned_le32(merged + i);
+
+ dev_dbg(dev, "rfreg dual-option update: %s + %s\n", hw_name, sw_name);
+
+ /* START_UPDATE */
+ sub_oid = NCI_PROP_DUAL_SUB_START_UPDATE;
+ ret = nci_prop_cmd(info->ndev, NCI_PROP_DUAL_OPTION, 1, &sub_oid);
+ if (ret < 0) {
+ dev_err(dev, "Unable to start rfreg update\n");
+ goto out;
+ }
+
+ /* SET_OPTION per section */
+ set_option.sub_oid = NCI_PROP_DUAL_SUB_SET_OPTION;
+ set_option.index = 0;
+ for (i = 0; i < merged_size; i += NCI_PROP_DUAL_SECTION_SIZE) {
+ len = min_t(size_t, merged_size - i, NCI_PROP_DUAL_SECTION_SIZE);
+ memcpy(set_option.data, merged + i, len);
+ ret = nci_prop_cmd(info->ndev, NCI_PROP_DUAL_OPTION,
+ len + 2, (__u8 *)&set_option);
+ if (ret < 0) {
+ dev_err(dev, "rfreg update error (code=%d)\n", ret);
+ goto out;
+ }
+ set_option.index++;
+ }
+
+ /* STOP_UPDATE with checksum */
+ stop_cmd[0] = NCI_PROP_DUAL_SUB_STOP_UPDATE;
+ put_unaligned_le16(checksum, &stop_cmd[1]);
+ ret = nci_prop_cmd(info->ndev, NCI_PROP_DUAL_OPTION, 3, stop_cmd);
+ if (ret < 0) {
+ dev_err(dev, "Unable to stop rfreg update\n");
+ goto out;
+ }
+
+ dev_dbg(dev, "rfreg dual-option update: success\n");
+out:
+ kfree(merged);
+ release_firmware(sw_fw);
+out_hw:
+ release_firmware(hw_fw);
+ return ret;
+}
diff --git a/drivers/nfc/s3fwrn5/nci.h b/drivers/nfc/s3fwrn5/nci.h
index bc4bce2bbc4d..23179ba095a1 100644
--- a/drivers/nfc/s3fwrn5/nci.h
+++ b/drivers/nfc/s3fwrn5/nci.h
@@ -40,6 +40,13 @@ struct nci_prop_stop_rfreg_rsp {
#define NCI_PROP_FW_CFG 0x28
+/*
+ * Single-byte FW_CFG payload (clock-speed selector) for the S3NRN4V reference
+ * clock. Taken from the vendor configuration for this part (the encoding is
+ * not documented).
+ */
+#define NCI_PROP_FW_CFG_CLK_SPEED 0x11
+
struct nci_prop_fw_cfg_cmd {
__u8 clk_type;
__u8 clk_speed;
@@ -50,7 +57,30 @@ struct nci_prop_fw_cfg_rsp {
__u8 status;
};
-extern const struct nci_driver_ops s3fwrn5_nci_prop_ops[4];
+/*
+ * The S3NRN4V updates its RF registers through a single "dual option" command
+ * (a sub-OID selects the operation) instead of the START/SET/STOP_RFREG
+ * opcodes above, and expects the HW and SW register blobs merged into one
+ * stream.
+ */
+#define NCI_PROP_DUAL_OPTION 0x2a
+
+#define NCI_PROP_DUAL_SUB_START_UPDATE 0x01
+#define NCI_PROP_DUAL_SUB_SET_OPTION 0x02
+#define NCI_PROP_DUAL_SUB_STOP_UPDATE 0x03
+
+#define NCI_PROP_DUAL_SECTION_SIZE 252
+
+struct nci_prop_dual_set_option_cmd {
+ __u8 sub_oid; /* NCI_PROP_DUAL_SUB_SET_OPTION */
+ __u8 index;
+ __u8 data[NCI_PROP_DUAL_SECTION_SIZE];
+};
+
+extern const struct nci_driver_ops s3fwrn5_nci_prop_ops[5];
int s3fwrn5_nci_rf_configure(struct s3fwrn5_info *info, const char *fw_name);
+int s3fwrn5_nci_rf_configure_dual(struct s3fwrn5_info *info,
+ const char *hw_name, const char *sw_name);
+int s3fwrn5_nci_clk_cfg(struct s3fwrn5_info *info);
#endif /* __LOCAL_S3FWRN5_NCI_H_ */
diff --git a/drivers/nfc/s3fwrn5/s3fwrn5.h b/drivers/nfc/s3fwrn5/s3fwrn5.h
index 2b492236090b..2d8c12091fba 100644
--- a/drivers/nfc/s3fwrn5/s3fwrn5.h
+++ b/drivers/nfc/s3fwrn5/s3fwrn5.h
@@ -21,6 +21,17 @@ enum s3fwrn5_mode {
S3FWRN5_MODE_FW,
};
+enum s3fwrn5_variant {
+ /* S3FWRN5 / S3FWRN82: firmware is downloaded by this driver */
+ S3FWRN5_VARIANT_FWDL,
+ /*
+ * S3NRN4V: ships with working firmware behind a bootloader protocol
+ * this driver does not implement; skip the download, configure the
+ * clock (FW_CFG) and update the RF registers via the DUAL_OPTION cmd.
+ */
+ S3FWRN5_VARIANT_S3NRN4V,
+};
+
struct s3fwrn5_phy_ops {
void (*set_wake)(void *id, bool sleep);
void (*set_mode)(void *id, enum s3fwrn5_mode);
@@ -36,6 +47,7 @@ struct s3fwrn5_info {
const struct s3fwrn5_phy_ops *phy_ops;
struct s3fwrn5_fw_info fw_info;
+ enum s3fwrn5_variant variant;
struct mutex mutex;
};
@@ -78,7 +90,7 @@ static inline int s3fwrn5_write(struct s3fwrn5_info *info, struct sk_buff *skb)
}
int s3fwrn5_probe(struct nci_dev **ndev, void *phy_id, struct device *pdev,
- const struct s3fwrn5_phy_ops *phy_ops);
+ const struct s3fwrn5_phy_ops *phy_ops, enum s3fwrn5_variant variant);
void s3fwrn5_remove(struct nci_dev *ndev);
int s3fwrn5_recv_frame(struct nci_dev *ndev, struct sk_buff *skb,
diff --git a/drivers/nfc/s3fwrn5/uart.c b/drivers/nfc/s3fwrn5/uart.c
index 540a4ddb0b05..47172d739a41 100644
--- a/drivers/nfc/s3fwrn5/uart.c
+++ b/drivers/nfc/s3fwrn5/uart.c
@@ -137,7 +137,7 @@ static int s3fwrn82_uart_probe(struct serdev_device *serdev)
}
ret = s3fwrn5_probe(&phy->common.ndev, phy, &phy->ser_dev->dev,
- &uart_phy_ops);
+ &uart_phy_ops, S3FWRN5_VARIANT_FWDL);
if (ret < 0)
goto err_serdev;
--
2.55.0
^ permalink raw reply related
* [PATCH net-next v2 1/2] dt-bindings: net: nfc: samsung,s3fwrn5: add S3NRN4V and clk-req-gpios
From: Jorijn van der Graaf @ 2026-07-05 19:06 UTC (permalink / raw)
To: Krzysztof Kozlowski
Cc: Jorijn van der Graaf, David Heidelberg, Andrew Lunn,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Rob Herring, Conor Dooley, oe-linux-nfc, netdev, devicetree,
linux-kernel
In-Reply-To: <20260705190621.128257-1-jorijnvdgraaf@catcrafts.net>
The S3NRN4V is an S3FWRN5-family NCI NFC controller found e.g. on the
Fairphone 6 (SM7635). Its host interface is NCI over I2C like the
S3FWRN5, but it is not compatible with any of the existing parts, so no
fallback compatible applies: its bootloader speaks a different protocol
and its RF configuration is loaded through a different proprietary
command set.
Document the optional clk-req-gpios property: the controller's CLK_REQ
output is asserted for as long as the chip needs its external reference
clock, notably while generating the 13.56 MHz poll carrier. The pin is
a level signal reflecting the chip's current clock demand, not a
one-shot event, so it is modelled as a GPIO. No user of the handshake
is known on the already-supported parts, so the property is restricted
to the S3NRN4V (easily relaxed should one appear), and it depends on
clocks, as its purpose is gating an external clock.
Also document the PVDD supply, the externally switched rail powering
the controller (boards feed it from a PMIC LDO). It is required for the
new device; deployed DTs for the existing parts never described a
supply, so for those it stays optional. Add an example for the new
device exercising the new properties.
Assisted-by: Claude:claude-opus-4-8
Assisted-by: Claude:claude-fable-5
Signed-off-by: Jorijn van der Graaf <jorijnvdgraaf@catcrafts.net>
---
Changes in v2:
- Drop the -i2c bus suffix from the new compatible; the parent bus
node already implies the interface (Requested by: Conor Dooley).
- Document the PVDD supply; required for the S3NRN4V, optional for the
existing parts whose deployed DTs never described a supply.
- Add an S3NRN4V example exercising clk-req-gpios, clocks and
pvdd-supply.
- Make clk-req-gpios depend on clocks.
- Describe the CLK_REQ pin in hardware terms (what the pin is, not
what the OS does with it).
- Rework the commit message: justify the GPIO modelling in hardware
terms and explain why no fallback compatible applies.
v1: https://lore.kernel.org/20260703202601.78563-2-jorijnvdgraaf@catcrafts.net
.../bindings/net/nfc/samsung,s3fwrn5.yaml | 65 ++++++++++++++++++-
1 file changed, 64 insertions(+), 1 deletion(-)
diff --git a/Documentation/devicetree/bindings/net/nfc/samsung,s3fwrn5.yaml b/Documentation/devicetree/bindings/net/nfc/samsung,s3fwrn5.yaml
index 12baee45752c..1e784a90d015 100644
--- a/Documentation/devicetree/bindings/net/nfc/samsung,s3fwrn5.yaml
+++ b/Documentation/devicetree/bindings/net/nfc/samsung,s3fwrn5.yaml
@@ -14,12 +14,20 @@ properties:
enum:
- samsung,s3fwrn5-i2c
- samsung,s3fwrn82
+ - samsung,s3nrn4v
en-gpios:
maxItems: 1
description:
Output GPIO pin used for enabling/disabling the chip
+ clk-req-gpios:
+ maxItems: 1
+ description:
+ Input GPIO pin connected to the controller's CLK_REQ output, which the
+ controller asserts for as long as it requires its external reference
+ clock.
+
interrupts:
maxItems: 1
@@ -29,6 +37,9 @@ properties:
clocks:
maxItems: 1
+ pvdd-supply:
+ description: PVDD power supply
+
wake-gpios:
maxItems: 1
description:
@@ -53,17 +64,45 @@ required:
- en-gpios
- wake-gpios
+# The clock-request handshake gates an external clock, so it needs one.
+dependencies:
+ clk-req-gpios: [ clocks ]
+
allOf:
- if:
properties:
compatible:
contains:
- const: samsung,s3fwrn5-i2c
+ enum:
+ - samsung,s3fwrn5-i2c
+ - samsung,s3nrn4v
then:
required:
- interrupts
- reg
+ # No user of the clock-request handshake is known on the other parts.
+ - if:
+ not:
+ properties:
+ compatible:
+ contains:
+ const: samsung,s3nrn4v
+ then:
+ properties:
+ clk-req-gpios: false
+
+ # Deployed DTs for the older parts never described a supply, so PVDD is
+ # only required for the new device.
+ - if:
+ properties:
+ compatible:
+ contains:
+ const: samsung,s3nrn4v
+ then:
+ required:
+ - pvdd-supply
+
examples:
- |
#include <dt-bindings/gpio/gpio.h>
@@ -97,3 +136,27 @@ examples:
};
};
+ # S3NRN4V with a clock-request gated external clock
+ - |
+ #include <dt-bindings/gpio/gpio.h>
+ #include <dt-bindings/interrupt-controller/irq.h>
+
+ i2c {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ nfc@27 {
+ compatible = "samsung,s3nrn4v";
+ reg = <0x27>;
+
+ interrupt-parent = <&tlmm>;
+ interrupts = <31 IRQ_TYPE_EDGE_RISING>;
+
+ en-gpios = <&tlmm 56 GPIO_ACTIVE_HIGH>;
+ wake-gpios = <&tlmm 7 GPIO_ACTIVE_HIGH>;
+ clk-req-gpios = <&tlmm 6 GPIO_ACTIVE_HIGH>;
+
+ clocks = <&rpmhcc 4>;
+ pvdd-supply = <&nfc_pvdd>;
+ };
+ };
--
2.55.0
^ permalink raw reply related
* [PATCH net-next v2 0/2] nfc: s3fwrn5: support the S3NRN4V variant
From: Jorijn van der Graaf @ 2026-07-05 19:06 UTC (permalink / raw)
To: Krzysztof Kozlowski
Cc: Jorijn van der Graaf, David Heidelberg, Andrew Lunn,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Rob Herring, Conor Dooley, oe-linux-nfc, netdev, devicetree,
linux-kernel
This adds support for the Samsung S3NRN4V, an S3FWRN5-family NFC
controller found e.g. on the Fairphone 6 (SM7635), to the s3fwrn5
driver.
The S3NRN4V differs from the already-supported parts in three ways: it
ships with working firmware behind a bootloader protocol the driver
does not implement (so firmware download is skipped), it loads its RF
registers through a different proprietary command (DUAL_OPTION), and it
gates its reference clock through a CLK_REQ line that the driver must
service for the chip to be able to generate the 13.56 MHz poll carrier.
Patch 1 adds the compatible and the clk-req-gpios property to the
binding; patch 2 implements the variant in the driver.
Tested on a Fairphone 6 running a milos-mainline kernel: reader mode polls
and reads ISO 14443-4 tags reliably, both from a fresh boot and across
driver reloads.
Changes in v2:
- Drop the -i2c bus suffix from the new compatible: it is now plain
samsung,s3nrn4v (Requested by: Conor Dooley).
- Close a race in the probe-time CLK_REQ seeding by reading the GPIO
level under clk_lock, so a stale level can never overwrite a fresher
state applied by the irq thread (found by the Sashiko AI review of
v1).
- Binding completeness: document the PVDD supply (required for the
S3NRN4V), add an S3NRN4V example exercising the new properties, make
clk-req-gpios depend on clocks, and describe the CLK_REQ pin in
hardware terms.
- Rework the binding commit message: justify the GPIO modelling in
hardware terms and explain why no fallback compatible applies.
- Add an s3nrn4v i2c_device_id entry carrying the variant so both
match paths agree, and describe the of_match_ptr() removal in the
driver commit message.
- Reject malformed rfreg blobs (word alignment, single-byte section
index bound) up front instead of failing at STOP_UPDATE.
- Handle gpiod_get_value_cansleep() failure in the CLK_REQ sync
instead of treating an error as "clock off".
v1: https://lore.kernel.org/20260703202601.78563-1-jorijnvdgraaf@catcrafts.net
Jorijn van der Graaf (2):
dt-bindings: net: nfc: samsung,s3fwrn5: add S3NRN4V and clk-req-gpios
nfc: s3fwrn5: support the S3NRN4V variant
.../bindings/net/nfc/samsung,s3fwrn5.yaml | 65 ++++++++-
drivers/nfc/s3fwrn5/core.c | 40 ++++-
drivers/nfc/s3fwrn5/i2c.c | 138 ++++++++++++++++--
drivers/nfc/s3fwrn5/nci.c | 119 ++++++++++++++-
drivers/nfc/s3fwrn5/nci.h | 32 +++-
drivers/nfc/s3fwrn5/s3fwrn5.h | 14 +-
drivers/nfc/s3fwrn5/uart.c | 2 +-
7 files changed, 394 insertions(+), 16 deletions(-)
base-commit: 805185b7c7a1069e407b6f7b3bc98e44d415f484
--
2.55.0
^ permalink raw reply
* [PATCH net-next 1/2] net: sfp: allow prefix matching in quirk lookup
From: Martino Dell'Ambrogio @ 2026-07-05 18:54 UTC (permalink / raw)
To: netdev
Cc: Russell King, Andrew Lunn, Heiner Kallweit, David S . Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, linux-kernel,
Martino Dell'Ambrogio
In-Reply-To: <20260705185440.136496-1-tillo@tillo.ch>
Some clone SFP modules (notably XGS-PON ONT sticks) ship malformed
EEPROMs where the vendor PN field is filled with non-printable garbage
past the trailing legitimate characters instead of SFF-8472 mandated
space padding. The current sfp_match() requires an exact full-field
length match: sfp_strlen() returns 16 (no trailing spaces or NULs to
strip), but strlen() of the quirk string is shorter, so the length
comparison rejects the entry before strncmp() is even called and the
quirk silently never applies. The kernel then honors the module's
spurious TX_FAULT signal and the SFP state machine eventually disables
the module.
Add a prefix_match flag to struct sfp_quirk and a SFP_QUIRK_F_PREFIX
macro. When set, sfp_match() compares only strlen() leading bytes of
the quirk string, ignoring trailing field bytes. Existing exact-match
quirks are unaffected (prefix_match defaults to false via zero-init in
the existing SFP_QUIRK macros).
Signed-off-by: Martino Dell'Ambrogio <tillo@tillo.ch>
---
drivers/net/phy/sfp.c | 22 +++++++++++++++++-----
drivers/net/phy/sfp.h | 1 +
2 files changed, 18 insertions(+), 5 deletions(-)
diff --git a/drivers/net/phy/sfp.c b/drivers/net/phy/sfp.c
index f520206..e7ba642 100644
--- a/drivers/net/phy/sfp.c
+++ b/drivers/net/phy/sfp.c
@@ -516,6 +516,13 @@ static void sfp_quirk_ubnt_uf_instant(const struct sfp_eeprom_id *id,
{ .vendor = _v, .part = _p, .support = _s, .fixup = _f, }
#define SFP_QUIRK_S(_v, _p, _s) SFP_QUIRK(_v, _p, _s, NULL)
#define SFP_QUIRK_F(_v, _p, _f) SFP_QUIRK(_v, _p, NULL, _f)
+/* Like SFP_QUIRK_F, but matches as a prefix. Use for clone modules
+ * that fill EEPROM trailing bytes with garbage instead of the
+ * SFF-8472-mandated space padding, so sfp_strlen can't trim the
+ * field down to the legitimate length.
+ */
+#define SFP_QUIRK_F_PREFIX(_v, _p, _f) \
+ { .vendor = _v, .part = _p, .support = NULL, .fixup = _f, .prefix_match = true }
static const struct sfp_quirk sfp_quirks[] = {
// Alcatel Lucent G-010S-P can operate at 2500base-X, but incorrectly
@@ -626,13 +633,16 @@ static size_t sfp_strlen(const char *str, size_t maxlen)
return size;
}
-static bool sfp_match(const char *qs, const char *str, size_t len)
+static bool sfp_match(const char *qs, const char *str, size_t len, bool prefix)
{
+ size_t qs_len;
+
if (!qs)
return true;
- if (strlen(qs) != len)
+ qs_len = strlen(qs);
+ if (prefix ? qs_len > len : qs_len != len)
return false;
- return !strncmp(qs, str, len);
+ return !strncmp(qs, str, qs_len);
}
static const struct sfp_quirk *sfp_lookup_quirk(const struct sfp_eeprom_id *id)
@@ -645,8 +655,10 @@ static const struct sfp_quirk *sfp_lookup_quirk(const struct sfp_eeprom_id *id)
ps = sfp_strlen(id->base.vendor_pn, ARRAY_SIZE(id->base.vendor_pn));
for (i = 0, q = sfp_quirks; i < ARRAY_SIZE(sfp_quirks); i++, q++)
- if (sfp_match(q->vendor, id->base.vendor_name, vs) &&
- sfp_match(q->part, id->base.vendor_pn, ps))
+ if (sfp_match(q->vendor, id->base.vendor_name, vs,
+ q->prefix_match) &&
+ sfp_match(q->part, id->base.vendor_pn, ps,
+ q->prefix_match))
return q;
return NULL;
diff --git a/drivers/net/phy/sfp.h b/drivers/net/phy/sfp.h
index 879dff7..867e45e 100644
--- a/drivers/net/phy/sfp.h
+++ b/drivers/net/phy/sfp.h
@@ -12,6 +12,7 @@ struct sfp_quirk {
void (*support)(const struct sfp_eeprom_id *id,
struct sfp_module_caps *caps);
void (*fixup)(struct sfp *sfp);
+ bool prefix_match;
};
struct sfp_socket_ops {
--
2.47.3
^ permalink raw reply related
* [PATCH net-next 2/2] net: sfp: add quirks for OEM XGSPONST2001 and FS XGS-SFP-ONT-MACI
From: Martino Dell'Ambrogio @ 2026-07-05 18:54 UTC (permalink / raw)
To: netdev
Cc: Russell King, Andrew Lunn, Heiner Kallweit, David S . Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, linux-kernel,
Martino Dell'Ambrogio
In-Reply-To: <20260705185440.136496-1-tillo@tillo.ch>
Cheap XGS-PON ONT sticks identifying as vendor "OEM", PN "XGSPONST2001"
have broken TX_FAULT and LOS indicators (driven by the ONU serial
passthrough wires) and need a longer T_START_UP than the SFF-8472
default. The Fiberstore XGS-SFP-ONT-MACI MAC-mode ONT stick has the
same ONT-class TX_FAULT/LOS wiring and startup behaviour. Apply the
existing sfp_fixup_potron handler to both, which masks both signals
and bumps T_START_UP to T_START_UP_BAD_GPON.
Both modules fail to space-pad the EEPROM vendor PN field past the
legitimate string as SFF-8472 mandates (the XGSPONST2001 fills it with
non-printable garbage), which defeats exact-length matching:
sfp_strlen() cannot trim the field, so a plain SFP_QUIRK_F entry would
silently never apply and the kernel would honor the spurious TX_FAULT
and eventually disable the module. Match both entries as prefixes
using SFP_QUIRK_F_PREFIX.
Signed-off-by: Martino Dell'Ambrogio <tillo@tillo.ch>
---
drivers/net/phy/sfp.c | 15 +++++++++++++++
1 file changed, 15 insertions(+)
diff --git a/drivers/net/phy/sfp.c b/drivers/net/phy/sfp.c
index e7ba642..eae0699 100644
--- a/drivers/net/phy/sfp.c
+++ b/drivers/net/phy/sfp.c
@@ -555,6 +555,13 @@ static const struct sfp_quirk sfp_quirks[] = {
SFP_QUIRK("FS", "GPON-ONU-34-20BI", sfp_quirk_2500basex,
sfp_fixup_ignore_tx_fault),
+ // Fiberstore XGS-SFP-ONT-MACI is a MAC-mode XGS-PON ONT stick with
+ // ONT-class serial-passthrough TX_FAULT/LOS wiring and slow startup;
+ // mask both signals and extend T_START_UP via the potron fixup. The
+ // EEPROM vendor PN field is not space-padded past the legitimate
+ // string, so match it as a prefix.
+ SFP_QUIRK_F_PREFIX("FS", "XGS-SFP-ONT-MACI", sfp_fixup_potron),
+
SFP_QUIRK_F("HALNy", "HL-GSFP", sfp_fixup_halny_gsfp),
SFP_QUIRK_F("H-COM", "SPP425H-GAB4", sfp_fixup_potron),
@@ -612,6 +619,14 @@ static const struct sfp_quirk sfp_quirks[] = {
SFP_QUIRK_S("OEM", "SFP-2.5G-LH20-A", sfp_quirk_2500basex),
SFP_QUIRK_F("OEM", "RTSFP-10", sfp_fixup_rollball_cc),
SFP_QUIRK_F("OEM", "RTSFP-10G", sfp_fixup_rollball_cc),
+
+ // OEM XGSPONST2001 is an XGS-PON ONT stick with broken TX_FAULT and
+ // LOS indicators and slow startup, just like potron. The EEPROM
+ // vendor PN field is filled with non-printable garbage past the
+ // legitimate string instead of space padding, so match it as a
+ // prefix.
+ SFP_QUIRK_F_PREFIX("OEM", "XGSPONST2001", sfp_fixup_potron),
+
SFP_QUIRK_F("Turris", "RTSFP-2.5G", sfp_fixup_rollball),
SFP_QUIRK_F("Turris", "RTSFP-10", sfp_fixup_rollball),
SFP_QUIRK_F("Turris", "RTSFP-10G", sfp_fixup_rollball),
--
2.47.3
^ permalink raw reply related
* [PATCH net-next 0/2] net: sfp: quirk support for XGS-PON ONT sticks with unclean EEPROMs
From: Martino Dell'Ambrogio @ 2026-07-05 18:54 UTC (permalink / raw)
To: netdev
Cc: Russell King, Andrew Lunn, Heiner Kallweit, David S . Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, linux-kernel,
Martino Dell'Ambrogio
Some clone XGS-PON ONT sticks ship EEPROMs where the vendor PN field is
filled with non-printable garbage past the legitimate string instead of
the SFF-8472 mandated space padding. sfp_strlen() then can't trim the
field, the exact-length check in sfp_match() rejects the quirk entry
before the string comparison runs, and the quirk silently never applies
— so the kernel honors the module's spurious TX_FAULT and eventually
disables it.
Patch 1 adds an opt-in prefix-matching flag to the quirk table so such
modules can still be matched; existing exact-match entries behave
exactly as before. Patch 2 adds the two entries that need it: the
"OEM" XGSPONST2001 and the Fiberstore XGS-SFP-ONT-MACI, both wired to
the existing potron fixup.
Both quirks are in production use on a Bananapi BPI-R4 (MT7988A)
router on an XGS-PON uplink, backported onto 6.12.
Martino Dell'Ambrogio (2):
net: sfp: allow prefix matching in quirk lookup
net: sfp: add quirks for OEM XGSPONST2001 and FS XGS-SFP-ONT-MACI
drivers/net/phy/sfp.c | 37 ++++++++++++++++++++++++++++++++-----
drivers/net/phy/sfp.h | 1 +
2 files changed, 33 insertions(+), 5 deletions(-)
--
2.47.3
^ permalink raw reply
* [PATCH v2 net 3/3] ipv4: igmp: Fix potential memory leaks in igmp_mod_timer() and igmp_stop_timer()
From: Eric Dumazet @ 2026-07-05 18:17 UTC (permalink / raw)
To: David S . Miller, Jakub Kicinski, Paolo Abeni
Cc: Simon Horman, Kuniyuki Iwashima, Ido Schimmel, David Ahern,
netdev, eric.dumazet, Eric Dumazet
In-Reply-To: <20260705181756.963063-1-edumazet@google.com>
When a timer is deleted and not re-armed in igmp_mod_timer(), or stopped
in igmp_stop_timer(), the code currently decrements the reference counter
of the multicast list entry @im using refcount_dec(&im->refcnt).
However, both functions can be called from the RCU reader path:
- igmp_mod_timer() via igmp_heard_query() -> for_each_pmc_rcu()
- igmp_stop_timer() via igmp_rcv() -> igmp_heard_report()
If the group im was concurrently removed from the list by ip_mc_dec_group(),
its reference count might have already been decremented to 1.
In this case, timer_delete() succeeds, and refcount_dec() decrements
the refcount from 1 to 0. Since refcount_dec() does not free the object
when it hits 0 (unlike ip_ma_put()), the im structure is leaked.
Fix this by using ip_ma_put(im) instead of refcount_dec(&im->refcnt),
and deferring the put until after the spinlock is released.
Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
Signed-off-by: Eric Dumazet <edumazet@google.com>
---
net/ipv4/igmp.c | 14 ++++++++++++--
1 file changed, 12 insertions(+), 2 deletions(-)
diff --git a/net/ipv4/igmp.c b/net/ipv4/igmp.c
index f5f9763895641bf86bfcf9fd7fd7b06012fa4ece..957013e7f7a25f32d848822caab322147db3dc6e 100644
--- a/net/ipv4/igmp.c
+++ b/net/ipv4/igmp.c
@@ -217,13 +217,18 @@ static void ip_sf_list_clear_all(struct ip_sf_list *psf)
static void igmp_stop_timer(struct ip_mc_list *im)
{
+ bool put = false;
+
spin_lock_bh(&im->lock);
if (timer_delete(&im->timer))
- refcount_dec(&im->refcnt);
+ put = true;
WRITE_ONCE(im->tm_running, 0);
WRITE_ONCE(im->reporter, 0);
im->unsolicit_count = 0;
spin_unlock_bh(&im->lock);
+
+ if (put)
+ ip_ma_put(im);
}
/* It must be called with locked im->lock */
@@ -266,6 +271,8 @@ static void igmp_ifc_start_timer(struct in_device *in_dev, int delay)
static void igmp_mod_timer(struct ip_mc_list *im, int max_delay)
{
+ bool put = false;
+
spin_lock_bh(&im->lock);
im->unsolicit_count = 0;
if (timer_delete(&im->timer)) {
@@ -275,10 +282,13 @@ static void igmp_mod_timer(struct ip_mc_list *im, int max_delay)
spin_unlock_bh(&im->lock);
return;
}
- refcount_dec(&im->refcnt);
+ put = true;
}
igmp_start_timer(im, max_delay);
spin_unlock_bh(&im->lock);
+
+ if (put)
+ ip_ma_put(im);
}
--
2.55.0.rc0.799.gd6f94ed593-goog
^ permalink raw reply related
* [PATCH v2 net 2/3] ipv6: mcast: Fix potential UAF in MLD delayed work
From: Eric Dumazet @ 2026-07-05 18:17 UTC (permalink / raw)
To: David S . Miller, Jakub Kicinski, Paolo Abeni
Cc: Simon Horman, Kuniyuki Iwashima, Ido Schimmel, David Ahern,
netdev, eric.dumazet, Eric Dumazet
In-Reply-To: <20260705181756.963063-1-edumazet@google.com>
A race condition exists between device teardown and incoming MLD query
processing, leading to a Use-After-Free in the MLD delayed work.
During device destruction, the primary reference to inet6_dev is dropped,
which can drop its refcount to 0. The actual freeing of inet6_dev memory
is deferred via RCU.
Concurrently, the packet receive path runs under RCU read lock and obtains
the inet6_dev pointer. Because the memory is RCU-protected, CPU-0 can
safely dereference inet6_dev even if its refcount has hit 0.
However, if CPU-0 calls igmp6_event_query() and schedules delayed work, it
attempts to acquire a reference using in6_dev_hold(). This increments the
refcount from 0 to 1, triggering a "refcount_t: addition on 0" warning.
Since the inet6_dev memory is still scheduled to be freed after the RCU
grace period, the device is freed while the work is still scheduled.
When the work runs, it accesses the freed memory, causing a kernel panic.
Fix this by using refcount_inc_not_zero() (via a new helper
in6_dev_hold_safe()) to prevent acquiring a reference if the device is
already being destroyed. If the refcount is 0, we do not schedule the work.
Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
Signed-off-by: Eric Dumazet <edumazet@google.com>
---
include/net/addrconf.h | 5 +++++
net/ipv6/mcast.c | 40 ++++++++++++++++++++++++++++------------
2 files changed, 33 insertions(+), 12 deletions(-)
diff --git a/include/net/addrconf.h b/include/net/addrconf.h
index 539bbbe54b14e8108ff7304d7a08bc605655cc31..8ced27a8229b6e0580f934be2223676cc123307b 100644
--- a/include/net/addrconf.h
+++ b/include/net/addrconf.h
@@ -446,6 +446,11 @@ static inline void in6_dev_hold(struct inet6_dev *idev)
refcount_inc(&idev->refcnt);
}
+static inline bool in6_dev_hold_safe(struct inet6_dev *idev)
+{
+ return refcount_inc_not_zero(&idev->refcnt);
+}
+
/* called with rcu_read_lock held */
static inline bool ip6_ignore_linkdown(const struct net_device *dev)
{
diff --git a/net/ipv6/mcast.c b/net/ipv6/mcast.c
index 04b811b3be978e36b0fd4ecd8312313d73ed2ed9..4d2b9377ba2de5ad2b6d503bb4b285bfd1307a55 100644
--- a/net/ipv6/mcast.c
+++ b/net/ipv6/mcast.c
@@ -1083,8 +1083,10 @@ static void mld_gq_start_work(struct inet6_dev *idev)
mc_assert_locked(idev);
idev->mc_gq_running = 1;
- if (!mod_delayed_work(mld_wq, &idev->mc_gq_work, tv + 2))
- in6_dev_hold(idev);
+ if (in6_dev_hold_safe(idev)) {
+ if (mod_delayed_work(mld_wq, &idev->mc_gq_work, tv + 2))
+ in6_dev_put(idev);
+ }
}
static void mld_gq_stop_work(struct inet6_dev *idev)
@@ -1102,8 +1104,10 @@ static void mld_ifc_start_work(struct inet6_dev *idev, unsigned long delay)
mc_assert_locked(idev);
- if (!mod_delayed_work(mld_wq, &idev->mc_ifc_work, tv + 2))
- in6_dev_hold(idev);
+ if (in6_dev_hold_safe(idev)) {
+ if (mod_delayed_work(mld_wq, &idev->mc_ifc_work, tv + 2))
+ in6_dev_put(idev);
+ }
}
static void mld_ifc_stop_work(struct inet6_dev *idev)
@@ -1121,8 +1125,10 @@ static void mld_dad_start_work(struct inet6_dev *idev, unsigned long delay)
mc_assert_locked(idev);
- if (!mod_delayed_work(mld_wq, &idev->mc_dad_work, tv + 2))
- in6_dev_hold(idev);
+ if (in6_dev_hold_safe(idev)) {
+ if (mod_delayed_work(mld_wq, &idev->mc_dad_work, tv + 2))
+ in6_dev_put(idev);
+ }
}
static void mld_dad_stop_work(struct inet6_dev *idev)
@@ -1395,18 +1401,23 @@ static void mld_process_v2(struct inet6_dev *idev, struct mld2_query *mld,
void igmp6_event_query(struct sk_buff *skb)
{
struct inet6_dev *idev = __in6_dev_get(skb->dev);
+ bool put = false;
if (!idev || idev->dead)
goto out;
spin_lock_bh(&idev->mc_query_lock);
- if (skb_queue_len(&idev->mc_query_queue) < MLD_MAX_SKBS) {
+ if (skb_queue_len(&idev->mc_query_queue) < MLD_MAX_SKBS &&
+ in6_dev_hold_safe(idev)) {
__skb_queue_tail(&idev->mc_query_queue, skb);
- if (!mod_delayed_work(mld_wq, &idev->mc_query_work, 0))
- in6_dev_hold(idev);
+ if (mod_delayed_work(mld_wq, &idev->mc_query_work, 0))
+ put = true;
skb = NULL;
}
spin_unlock_bh(&idev->mc_query_lock);
+
+ if (put)
+ in6_dev_put(idev);
out:
kfree_skb(skb);
}
@@ -1570,18 +1581,23 @@ static void mld_query_work(struct work_struct *work)
void igmp6_event_report(struct sk_buff *skb)
{
struct inet6_dev *idev = __in6_dev_get(skb->dev);
+ bool put = false;
if (!idev || idev->dead)
goto out;
spin_lock_bh(&idev->mc_report_lock);
- if (skb_queue_len(&idev->mc_report_queue) < MLD_MAX_SKBS) {
+ if (skb_queue_len(&idev->mc_report_queue) < MLD_MAX_SKBS &&
+ in6_dev_hold_safe(idev)) {
__skb_queue_tail(&idev->mc_report_queue, skb);
- if (!mod_delayed_work(mld_wq, &idev->mc_report_work, 0))
- in6_dev_hold(idev);
+ if (mod_delayed_work(mld_wq, &idev->mc_report_work, 0))
+ put = true;
skb = NULL;
}
spin_unlock_bh(&idev->mc_report_lock);
+
+ if (put)
+ in6_dev_put(idev);
out:
kfree_skb(skb);
}
--
2.55.0.rc0.799.gd6f94ed593-goog
^ permalink raw reply related
* [PATCH v2 net 1/3] ipv4: igmp: Fix potential UAF in igmp_gq_start_timer()
From: Eric Dumazet @ 2026-07-05 18:17 UTC (permalink / raw)
To: David S . Miller, Jakub Kicinski, Paolo Abeni
Cc: Simon Horman, Kuniyuki Iwashima, Ido Schimmel, David Ahern,
netdev, eric.dumazet, Eric Dumazet, Zero Day Initiative
In-Reply-To: <20260705181756.963063-1-edumazet@google.com>
A race condition exists between device teardown (inetdev_destroy) and
incoming IGMP query processing (igmp_rcv), leading to a Use-After-Free
in the IGMP timer callback.
During device destruction, inetdev_destroy() drops the primary reference
to in_device, which can drop its refcount to 0. The actual freeing of
in_device memory is deferred via RCU (using call_rcu()).
Concurrently, igmp_rcv() runs under RCU read lock and obtains the
in_device pointer. Because the memory is RCU-protected, CPU-0 can safely
dereference in_device even if its refcount has hit 0.
However, if CPU-0 calls igmp_gq_start_timer() and re-arms the timer, it
attempts to acquire a reference using in_dev_hold(). This increments the
refcount from 0 to 1, triggering a "refcount_t: addition on 0" warning.
Since the in_device memory is still scheduled to be freed after the RCU
grace period (as the free callback does not check the refcount again),
the device is freed while the timer is still armed. When the timer
expires, it accesses the freed memory, causing a kernel panic.
Fix this by using refcount_inc_not_zero() (via a new helper
in_dev_hold_safe()) to prevent acquiring a reference if the device is
already being destroyed. If the refcount is 0, we do not arm the timer.
A similar issue in IPv6 MLD is fixed in a subsequent patch.
Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
Reported-by: Zero Day Initiative <zdi-disclosures@trendmicro.com>
Signed-off-by: Eric Dumazet <edumazet@google.com>
---
include/linux/inetdevice.h | 5 +++++
net/ipv4/igmp.c | 14 +++++++++-----
2 files changed, 14 insertions(+), 5 deletions(-)
diff --git a/include/linux/inetdevice.h b/include/linux/inetdevice.h
index dccbeb25f70141982160c776f3cc727296def2b7..6032eea2539a60d0476d85667bda3bfcad8f1425 100644
--- a/include/linux/inetdevice.h
+++ b/include/linux/inetdevice.h
@@ -293,6 +293,11 @@ static inline void in_dev_put(struct in_device *idev)
#define __in_dev_put(idev) refcount_dec(&(idev)->refcnt)
#define in_dev_hold(idev) refcount_inc(&(idev)->refcnt)
+static inline bool in_dev_hold_safe(struct in_device *idev)
+{
+ return refcount_inc_not_zero(&idev->refcnt);
+}
+
#endif /* __KERNEL__ */
static __inline__ __be32 inet_make_mask(int logmask)
diff --git a/net/ipv4/igmp.c b/net/ipv4/igmp.c
index b6337a47c1418589bf8ef31e00fd98b6187f5271..f5f9763895641bf86bfcf9fd7fd7b06012fa4ece 100644
--- a/net/ipv4/igmp.c
+++ b/net/ipv4/igmp.c
@@ -248,16 +248,20 @@ static void igmp_gq_start_timer(struct in_device *in_dev)
return;
in_dev->mr_gq_running = 1;
- if (!mod_timer(&in_dev->mr_gq_timer, exp))
- in_dev_hold(in_dev);
+ if (in_dev_hold_safe(in_dev)) {
+ if (mod_timer(&in_dev->mr_gq_timer, exp))
+ in_dev_put(in_dev);
+ }
}
static void igmp_ifc_start_timer(struct in_device *in_dev, int delay)
{
- int tv = get_random_u32_below(delay);
+ if (in_dev_hold_safe(in_dev)) {
+ int tv = get_random_u32_below(delay);
- if (!mod_timer(&in_dev->mr_ifc_timer, jiffies+tv+2))
- in_dev_hold(in_dev);
+ if (mod_timer(&in_dev->mr_ifc_timer, jiffies + tv + 2))
+ in_dev_put(in_dev);
+ }
}
static void igmp_mod_timer(struct ip_mc_list *im, int max_delay)
--
2.55.0.rc0.799.gd6f94ed593-goog
^ 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