* Re: [PATCH net-next v2 1/3] net: ti: icssm-prueth: Add HSR and PRP HW offload mode support for AM57xx, AM437x and AM335x
From: Parvathi Pudi @ 2026-07-13 12:29 UTC (permalink / raw)
To: Paolo Abeni
Cc: parvathi, andrew+netdev, davem, edumazet, kuba, danishanwar,
rogerq, pmohan, afd, basharath, arnd, linux-kernel, netdev,
linux-arm-kernel, pratheesh, j-rameshbabu, Vignesh Raghavendra,
praneeth, srk, rogerq, m-malladi, krishna, mohan
In-Reply-To: <20260706091314.134721-1-pabeni@redhat.com>
Hi,
> From: AI Reviewer <ai@example.com>
>
> This is an AI-generated review of your patch. The human sending this
> email has considered the AI review valid, or at least plausible.
> Full review at: https://sashiko.dev/
> ---
> Subject: net: ti: icssm-prueth: Add HSR and PRP HW offload mode support for
> AM57xx, AM437x and AM335x
>
> This commit introduces hardware offload support for the HSR and PRP redundancy
> protocols on TI PRU-ICSS Ethernet MACs. It enables firmware-based frame
> duplicate detection and L2 forwarding, treating PRU Ethernet ports as Link
> Redundancy Entity (LRE) slaves. The driver can now dynamically switch between
> dual-EMAC, switch, and HSR/PRP modes.
>
>> diff --git a/drivers/net/ethernet/ti/icssm/icssm_prueth.c
>> b/drivers/net/ethernet/ti/icssm/icssm_prueth.c
> [ ... ]
>> @@ -564,7 +592,7 @@ static int icssm_prueth_tx_enqueue(struct prueth_emac *emac,
> [ ... ]
>> /* update first buffer descriptor */
>> wr_buf_desc = (pktlen << PRUETH_BD_LENGTH_SHIFT) &
>> PRUETH_BD_LENGTH_MASK;
>> + if (PRUETH_IS_HSR(prueth))
>> + wr_buf_desc |= BIT(PRUETH_BD_HSR_FRAME_SHIFT);
>
> [Severity: High]
> Does padding small packets with eth_skb_pad() in icssm_emac_ndo_start_xmit()
> interfere with PRP trailer detection here?
>
> When a short PRP frame is padded to 60 bytes in icssm_emac_ndo_start_xmit(),
> the zero-padding is appended after the RCT. Later, in
> icssm_prueth_tx_enqueue(), the driver attempts to read the RCT like this:
>
> struct prp_txopt_rct *rct =
> (struct prp_txopt_rct *)(skb_tail_pointer(skb) - ICSSM_LRE_TAG_SIZE);
>
> Won't this calculation point into the trailing zero-padding instead of the
> actual RCT? This would cause small PRP frames to bypass redundancy
> duplication.
No, eth_skb_pad() doesn't interfere with PRP trailer detection.
The kernel's HSR/PRP layer always pads a short frame to 60 bytes first, and only
then appends the 6-byte RCT trailer on top. So the trailer is always the last 6
bytes, and by the time this driver sees the frame it's already past 60 bytes.
This padding call never fires and the RCT read is always correct. Not an issue.
>
> [ ... ]
>> @@ -912,6 +963,25 @@ static int icssm_emac_request_irqs(struct prueth_emac
>> *emac)
>> return ret;
>> }
>>
>> +static int icssm_emac_sanitize_feature_flags(struct prueth_emac *emac)
>> +{
>> + netdev_features_t request_lre;
>> +
>> + request_lre = emac->ndev->features & NETIF_PRUETH_LRE_OFFLOAD_FEATURES;
>> +
>> + if (prueth_is_lre(emac->prueth) && !request_lre) {
>> + netdev_err(emac->ndev, "Error: Turn ON HSR offload\n");
>> + return -EINVAL;
>> + }
>
> [Severity: Medium]
> Is this check sufficient to handle partial feature toggling?
>
> If a user disables NETIF_F_HW_HSR_TAG_RM via ethtool while leaving
> NETIF_F_HW_HSR_FWD active, request_lre will still evaluate to non-zero and
> bypass this validation. Since the firmware unconditionally strips the tag,
> could this mismatched configuration cause the software stack to misinterpret
> the inner IP header as an HSR tag?
We will address this in the next version.
>
> [ ... ]
>> @@ -1079,6 +1162,9 @@ static int icssm_emac_ndo_stop(struct net_device *ndev)
>> else
>> rproc_shutdown(emac->pru);
>>
>> + if (prueth_is_lre(prueth))
>> + icssm_prueth_lre_cleanup(prueth);
>
> [Severity: High]
> Could this unconditionally cancel the global HSR/PRP duplicate table check
> timer while the other LRE port is still running?
>
> If eth0 is brought down, the timer is canceled here, but if eth1 remains
> active, the duplication tables could silently fill up and drop legitimate
> frames because the check timer is never restarted.
We will address this in the next version.
>
> [ ... ]
>> @@ -1122,7 +1208,8 @@ static int icssm_prueth_change_mode(struct prueth *prueth,
>> }
>> }
>>
>> - if (mode == PRUSS_ETHTYPE_EMAC || mode == PRUSS_ETHTYPE_SWITCH) {
>> + if (mode == PRUSS_ETHTYPE_EMAC || mode == PRUSS_ETHTYPE_SWITCH ||
>> + mode == PRUSS_ETHTYPE_HSR || mode == PRUSS_ETHTYPE_PRP) {
>
> [Severity: High]
> This is a pre-existing issue, but can the direct invocation of ndo_open() and
> ndo_stop() inside icssm_prueth_change_mode() cause a deadlock?
>
> If ndev->netdev_ops->ndo_open(ndev) fails and returns an error, the function
> aborts without restoring the NAPI state. Later, when the interface is brought
> down, the kernel naturally invokes ndo_stop() again. A second napi_disable()
> call on an already disabled NAPI might trigger an infinite sleep waiting for
> NAPI_STATE_SCHED.
Yes, this is causing a deadlock. icssm_prueth_change_mode() already existed before
this series and this patch series only adds two more mode values to an existing check,
it doesn't touch the open/stop logic itself. We will fix it as its own separate
patch later, instead of mixing it into this HSR/PRP series.
>
> [ ... ]
>> @@ -1360,6 +1465,8 @@ static void icssm_emac_ndo_set_rx_mode(struct net_device
>> *ndev)
> [ ... ]
>> /* It is a shared table. So lock the access */
>> - spin_lock_irqsave(&emac->addr_lock, flags);
>> + spin_lock_irqsave(mc_filter_tbl_lock, flags);
>>
>> /* Disable and reset multicast filter, allows allmulti */
>> icssm_emac_mc_filter_ctrl(emac, false);
>
> [Severity: High]
> Does this function corrupt the shared multicast hardware filter table in LRE
> mode?
>
> By calling icssm_emac_mc_filter_reset() the shared table in DRAM1 is cleared.
> The code then repopulates the table by iterating only over the current
> net_device's multicast addresses. Could this silently drop multicast traffic
> for the other active slave port since its addresses are wiped out?
No, this is safe in LRE mode. Although icssm_emac_mc_filter_reset() clears the
shared multicast filter table in DRAM1, it is rebuilt right away from the current
net_device's multicast list.
The HSR/PRP layer keeps the multicast lists of both slave ports in sync by copying
the master's list whenever it changes, so both ports always have the same multicast
addresses. That means rebuilding the shared table from either port's list restores
all the required entries and does not drop multicast addresses needed by the other port.
>
> [ ... ]
>> +static int icssm_prueth_hsr_port_link(struct net_device *ndev,
>> + struct net_device *hsr_ndev)
>> +{
> [ ... ]
>> + spin_lock_irqsave(&emac->addr_lock, flags);
>> +
>> + if (!prueth->hsr_members) {
>
> [Severity: Low]
> Is the per-port emac->addr_lock the correct lock to use when modifying the
> device-wide shared state in prueth->hsr_members and prueth->hsr_dev?
>
> While the networking core's global rtnl_lock might serialize this path during
> NETDEV_CHANGEUPPER, using a local instance lock to protect global state could
> be a maintainability hazard.
We will address this in the next version.
>
> [ ... ]
>> +free_hsr:
>> + spin_lock_irqsave(&emac->addr_lock, flags);
>> +
>> + prueth->hsr_dev = NULL;
>> + prueth->hsr_members &= ~BIT(emac->port_id);
>> +
>> + spin_unlock_irqrestore(&emac->addr_lock, flags);
>> + return ret;
>> +}
>
> [Severity: High]
> If adding the second HSR/PRP port fails (e.g., during
> icssm_prueth_change_mode), does this error path corrupt the bridge state?
>
> Unconditionally clearing the shared prueth->hsr_dev pointer while the first
> port is still present in prueth->hsr_members appears to permanently break
> subsequent link attempts for the second port, because future links will fail
> on the earlier condition:
>
> if (prueth->hsr_dev != hsr_ndev)
> --
We will address this in the next version.
Thanks and Regards,
Parvathi.
^ permalink raw reply
* Re: [PATCH net-next v2] ipv6: honor per-interface proxy_ndp in forward and NA paths
From: Chenguang Zhao @ 2026-07-13 12:27 UTC (permalink / raw)
To: Jakub Kicinski, Chenguang Zhao
Cc: Nicolas Dichtel, dsahern, idosch, davem, edumazet, pabeni, horms,
netdev, chenguang.zhao
In-Reply-To: <20260629154649.1a7b7f2a@kernel.org>
Thanks for the question.
Yes, I do find this useful; it is not only about cleaning up an
inconsistency found by tooling.
The common deployment for proxy NDP is to enable it only on the
upstream/WAN interface, not globally, for example:
# host with eth0 (WAN) and eth1 (LAN / containers / VPN)
sysctl -w net.ipv6.conf.eth0.proxy_ndp=1
ip -6 neigh add proxy 2001:db8::10 dev eth0
This is what Documentation and most guides describe (ndppd, Docker
IPv6, VPN/VPS setups that need to answer NS for addresses that live
behind another interface). Operators usually avoid
net.ipv6.conf.all.proxy_ndp=1 so proxying is not enabled on every
interface.
With that setup today:
- ndisc_recv_ns() already honors idev->cnf.proxy_ndp, so NS replies
work as expected;
- ip6_forward() / ndisc_recv_na() only look at
net->ipv6.devconf_all->proxy_ndp, so the forward/local-delivery
and NA handling for proxied addresses still require the global
knob.
So per-interface proxy_ndp is only half-effective unless
all.proxy_ndp is also set. The XXX comments look like that gap was
known when proxy_ndp was introduced; idev is available on these paths
now, so I want the remaining paths to match the NS path and the
documented per-interface usage.
I did use code review assistance while looking at IPv6 NDP, but the
motivation for sending the patch is the incomplete per-interface
behavior above, not just "make the checks look the same".
Applying this patch only adds per-interface control for IPv6 proxy NDP
to enable finer-grained configuration, and it should have negligible
impact on existing systems with the global IPv6 proxy NDP enabled.
Chenguang
Thanks
在 2026/6/30 06:46, Jakub Kicinski 写道:
> On Mon, 29 Jun 2026 15:59:00 +0200 Nicolas Dichtel wrote:
>> Le 29/06/2026 à 08:18, Chenguang Zhao a écrit :
>>> ndisc_recv_ns() has always checked both devconf_all and idev->cnf for
>>> proxy_ndp, but ip6_forward() and ndisc_recv_na() only looked at the
>>> global setting. The original commit left XXX comments in these paths
>>> likely because idev was not available there at the time; ip6_forward()
>>> now obtains idev from IP6CB(skb)->iif.
>>>
>>> Honor per-interface proxy_ndp in both places to match the NS path and
>>> allow setups that only enable proxy_ndp on specific interfaces.
>>>
>>> In ip6_forward(), idev is looked up via the ingress interface (iif) while
>>> pneigh_lookup() uses skb->dev. For ND packets this is correct because
>>> vrf_ip6_rcv() does not modify skb->dev for neighbour discovery frames,
>>> so both refer to the ingress interface.
>>>
>>> Signed-off-by: Chenguang Zhao <zhaochenguang@kylinos.cn>
>>> ---
>>> v2:
>>> Per Ido's review, the following changes were made in v2:
>>> - Target net-next instead of net
>>> - Drop Fixes tag
>>> - Expand commit message: XXX comment history, idev vs skb->dev for ND packets
>>> - Fix subject prefix
>>>
>>> v1:
>>> - https://lore.kernel.org/all/20260623085600.396401-1-zhaochenguang@kylinos.cn/
>>>
>>> net/ipv6/ip6_output.c | 4 ++--
>>> net/ipv6/ndisc.c | 4 ++--
>>> 2 files changed, 4 insertions(+), 4 deletions(-)
>>>
>>> diff --git a/net/ipv6/ip6_output.c b/net/ipv6/ip6_output.c
>>> index 368e4fa3b43c..c4ca4a813479 100644
>>> --- a/net/ipv6/ip6_output.c
>>> +++ b/net/ipv6/ip6_output.c
>>> @@ -579,8 +579,8 @@ int ip6_forward(struct sk_buff *skb)
>>> return -ETIMEDOUT;
>>> }
>>>
>>> - /* XXX: idev->cnf.proxy_ndp? */
>>> - if (READ_ONCE(net->ipv6.devconf_all->proxy_ndp) &&
>>> + if ((READ_ONCE(net->ipv6.devconf_all->proxy_ndp) ||
>>> + (idev && READ_ONCE(idev->cnf.proxy_ndp))) &&
>> As stated by Ido, this changes a 20 years old user-visible behavior. It suddenly
>> may enable proxy NDP on a system.
>> I was thinking that this kind of change was prohibited.
> Chenguang Zhao, is this a change you find useful or are you just
> acting on an AI-generated port about inconsistency in behavior?
> Could you please explain your use case / deployment scenario?
^ permalink raw reply
* Re: [PATCH iproute2-next v5 1/2] rdma: update uapi headers
From: Tao Cui @ 2026-07-13 12:26 UTC (permalink / raw)
To: Leon Romanovsky; +Cc: cui.tao, dsahern, linux-rdma, netdev, cuitao
In-Reply-To: <20260712090325.GF33197@unreal>
在 2026/7/12 17:03, Leon Romanovsky 写道:
> On Fri, Jul 10, 2026 at 09:17:58AM +0800, Tao Cui wrote:
>> From: Tao Cui <cuitao@kylinos.cn>
>>
>> Update rdma_netlink.h file upto kernel commit 5911f6d6e7ce
>> ("RDMA/nldev: Add resource summary max values for usage display")
>
> This SHA-1 is incorrect.
> 5911f6d6e7cc ("RDMA/nldev: Add resource summary max values for usage display")
>
Mhm... sorry for that
> Thanks
^ permalink raw reply
* Re: [PATCH net-next 0/3] net: nexthop: per-nexthop UDP dst port for fdb (VXLAN) nexthops
From: Ido Schimmel @ 2026-07-13 12:23 UTC (permalink / raw)
To: Jack Ma
Cc: netdev, David Ahern, David S . Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, Shuah Khan,
linux-kselftest, linux-kernel
In-Reply-To: <20260712191218.236-1-jack4it@gmail.com>
On Sun, Jul 12, 2026 at 07:12:15PM +0000, Jack Ma wrote:
> FDB nexthops let a VXLAN fdb entry point at a group of remote VTEPs, with the
> kernel flow-hashing across the group (commit 1274e1cc4226 ("vxlan: ecmp support
> for mac fdb entries")). Each leg carries its own remote IP, but the UDP
> destination port is always taken from the VXLAN device (vxlan->cfg.dst_port)
> and cannot be set per leg.
>
> Some deployments pack several receivers behind a single underlay IP and tell
> them apart by UDP destination port. To spread flows across such receivers they
I don't understand the "tell them apart" phrasing. Aren't all of these
receivers interchangeable given you are load balancing between them?
> need a nexthop group whose legs share the remote IP but differ in UDP port,
> which is not currently expressible.
I think you will need to provide more details about the use case given
you are asking us to accept new uAPI which we will need to maintain
forever. The FDB nexthop groups make sense when you want to spread
overlay traffic between several distinct VTEPs. In the EVPN-MH use case
these VTEPs are different TOR switches. In your use case, why can't the
load balancing happen at the target host (e.g., using XDP / TC /
SO_REUSEPORT / flow classification)?
To be clear, I'm not strictly against the idea (we can talk about the
implementation later), but I would like to understand why your use case
can't be addressed using existing mechanisms (or why it's undesirable).
Thanks
^ permalink raw reply
* [PATCH v3 net] octeontx2-af: Block VFs from clobbering special CGX PKIND state
From: Ratheesh Kannoth @ 2026-07-13 12:19 UTC (permalink / raw)
To: davem, gakula, linux-kernel, netdev, sgoutham
Cc: andrew+netdev, edumazet, kuba, pabeni, Hariprasad Kelam,
Ratheesh Kannoth
From: Hariprasad Kelam <hkelam@marvell.com>
PF and VF NIX LFs that share a CGX LMAC reuse the same hardware PKIND
programming. When HiGig2 or EDSA parsing is enabled, a VF NIX LF alloc must
not reset the LMAC RX PKIND or default TX parse config over the PF setup.
Add cgx_get_pkind() and rvu_cgx_is_pkind_config_permitted() so VFs skip
cgx_set_pkind(), rvu_npc_set_pkind(), and NIX_AF_LFX_TX_PARSE_CFG updates
when the LMAC is using NPC_RX_HIGIG_PKIND or NPC_RX_EDSA_PKIND.
Fixes: 94d942c5fb97 ("octeontx2-af: Config pkind for CGX mapped PFs")
Cc: Geetha sowjanya <gakula@marvell.com>
Signed-off-by: Hariprasad Kelam <hkelam@marvell.com>
Signed-off-by: Ratheesh Kannoth <rkannoth@marvell.com>
---
v2 -> v3: Addressed simon comments
https://lore.kernel.org/netdev/20260709122648.1552103-2-horms@kernel.org/
v1 -> v2: Addressed simon comments
https://lore.kernel.org/netdev/20260619041002.1773822-1-rkannoth@marvell.com/
---
.../net/ethernet/marvell/octeontx2/af/cgx.c | 12 +++++++
.../net/ethernet/marvell/octeontx2/af/cgx.h | 1 +
.../net/ethernet/marvell/octeontx2/af/rvu.h | 1 +
.../ethernet/marvell/octeontx2/af/rvu_cgx.c | 32 +++++++++++++++++++
.../ethernet/marvell/octeontx2/af/rvu_nix.c | 18 ++++++++---
.../ethernet/marvell/octeontx2/af/rvu_npc.c | 11 +++++--
6 files changed, 69 insertions(+), 6 deletions(-)
diff --git a/drivers/net/ethernet/marvell/octeontx2/af/cgx.c b/drivers/net/ethernet/marvell/octeontx2/af/cgx.c
index 2e94d5105016..f5fd6138c352 100644
--- a/drivers/net/ethernet/marvell/octeontx2/af/cgx.c
+++ b/drivers/net/ethernet/marvell/octeontx2/af/cgx.c
@@ -518,6 +518,18 @@ int cgx_set_pkind(void *cgxd, u8 lmac_id, int pkind)
return 0;
}
+int cgx_get_pkind(void *cgxd, u8 lmac_id, int *pkind)
+{
+ struct cgx *cgx = cgxd;
+
+ if (!is_lmac_valid(cgx, lmac_id))
+ return -ENODEV;
+
+ *pkind = cgx_read(cgx, lmac_id, cgx->mac_ops->rxid_map_offset);
+ *pkind = *pkind & 0x3F;
+ return 0;
+}
+
static u8 cgx_get_lmac_type(void *cgxd, int lmac_id)
{
struct cgx *cgx = cgxd;
diff --git a/drivers/net/ethernet/marvell/octeontx2/af/cgx.h b/drivers/net/ethernet/marvell/octeontx2/af/cgx.h
index 92ccf343dfe0..8411a75dd723 100644
--- a/drivers/net/ethernet/marvell/octeontx2/af/cgx.h
+++ b/drivers/net/ethernet/marvell/octeontx2/af/cgx.h
@@ -141,6 +141,7 @@ int cgx_get_cgxid(void *cgxd);
int cgx_get_lmac_cnt(void *cgxd);
void *cgx_get_pdata(int cgx_id);
int cgx_set_pkind(void *cgxd, u8 lmac_id, int pkind);
+int cgx_get_pkind(void *cgxd, u8 lmac_id, int *pkind);
int cgx_lmac_evh_register(struct cgx_event_cb *cb, void *cgxd, int lmac_id);
int cgx_lmac_evh_unregister(void *cgxd, int lmac_id);
int cgx_get_tx_stats(void *cgxd, int lmac_id, int idx, u64 *tx_stat);
diff --git a/drivers/net/ethernet/marvell/octeontx2/af/rvu.h b/drivers/net/ethernet/marvell/octeontx2/af/rvu.h
index 7f3505ae6860..bb671e2150aa 100644
--- a/drivers/net/ethernet/marvell/octeontx2/af/rvu.h
+++ b/drivers/net/ethernet/marvell/octeontx2/af/rvu.h
@@ -1115,6 +1115,7 @@ void npc_read_mcam_entry(struct rvu *rvu, struct npc_mcam *mcam,
u8 *intf, u8 *ena);
int npc_config_cntr_default_entries(struct rvu *rvu, bool enable);
bool is_cgx_config_permitted(struct rvu *rvu, u16 pcifunc);
+bool rvu_cgx_is_pkind_config_permitted(struct rvu *rvu, u16 pcifunc);
bool is_mac_feature_supported(struct rvu *rvu, int pf, int feature);
u32 rvu_cgx_get_fifolen(struct rvu *rvu);
void *rvu_first_cgx_pdata(struct rvu *rvu);
diff --git a/drivers/net/ethernet/marvell/octeontx2/af/rvu_cgx.c b/drivers/net/ethernet/marvell/octeontx2/af/rvu_cgx.c
index 4ff3935ed3fe..2be1da3476ac 100644
--- a/drivers/net/ethernet/marvell/octeontx2/af/rvu_cgx.c
+++ b/drivers/net/ethernet/marvell/octeontx2/af/rvu_cgx.c
@@ -1355,3 +1355,35 @@ void rvu_mac_reset(struct rvu *rvu, u16 pcifunc)
if (mac_ops->mac_reset(cgxd, lmac, !is_vf(pcifunc)))
dev_err(rvu->dev, "Failed to reset MAC\n");
}
+
+/* Do not allow CGX-mapped VFs to overwrite PKIND when special parse kinds
+ * (HiGig, EDSA, etc.) are in use on the shared LMAC.
+ */
+bool rvu_cgx_is_pkind_config_permitted(struct rvu *rvu, u16 pcifunc)
+{
+ int pf, err, rxpkind;
+ u8 cgx_id, lmac_id;
+ void *cgxd;
+
+ pf = rvu_get_pf(rvu->pdev, pcifunc);
+
+ if (!(pcifunc & RVU_PFVF_FUNC_MASK))
+ return true;
+
+ if (!is_pf_cgxmapped(rvu, pf))
+ return true;
+
+ rvu_get_cgx_lmac_id(rvu->pf2cgxlmac_map[pf], &cgx_id, &lmac_id);
+ cgxd = rvu_cgx_pdata(cgx_id, rvu);
+ err = cgx_get_pkind(cgxd, lmac_id, &rxpkind);
+ if (err)
+ return false;
+
+ switch (rxpkind) {
+ case NPC_RX_HIGIG_PKIND:
+ case NPC_RX_EDSA_PKIND:
+ return false;
+ default:
+ return true;
+ }
+}
diff --git a/drivers/net/ethernet/marvell/octeontx2/af/rvu_nix.c b/drivers/net/ethernet/marvell/octeontx2/af/rvu_nix.c
index 6a0ce2665031..040eb4c9b8d1 100644
--- a/drivers/net/ethernet/marvell/octeontx2/af/rvu_nix.c
+++ b/drivers/net/ethernet/marvell/octeontx2/af/rvu_nix.c
@@ -338,6 +338,7 @@ static int nix_interface_init(struct rvu *rvu, u16 pcifunc, int type, int nixlf,
struct sdp_node_info *sdp_info;
int pkind, pf, vf, lbkid, vfid;
u8 cgx_id, lmac_id;
+ struct cgx *cgxd;
bool from_vf;
int err;
@@ -363,8 +364,15 @@ static int nix_interface_init(struct rvu *rvu, u16 pcifunc, int type, int nixlf,
pfvf->tx_chan_cnt = 1;
rsp->tx_link = cgx_id * hw->lmac_per_cgx + lmac_id;
- cgx_set_pkind(rvu_cgx_pdata(cgx_id, rvu), lmac_id, pkind);
- rvu_npc_set_pkind(rvu, pkind, pfvf);
+ cgxd = rvu_cgx_pdata(cgx_id, rvu);
+
+ mutex_lock(&cgxd->lock);
+ if (rvu_cgx_is_pkind_config_permitted(rvu, pcifunc)) {
+ cgx_set_pkind(rvu_cgx_pdata(cgx_id, rvu), lmac_id,
+ pkind);
+ rvu_npc_set_pkind(rvu, pkind, pfvf);
+ }
+ mutex_unlock(&cgxd->lock);
break;
case NIX_INTF_TYPE_LBK:
vf = (pcifunc & RVU_PFVF_FUNC_MASK) - 1;
@@ -1685,8 +1693,10 @@ int rvu_mbox_handler_nix_lf_alloc(struct rvu *rvu,
rvu_write64(rvu, blkaddr, NIX_AF_LFX_RX_CFG(nixlf), req->rx_cfg);
/* Configure pkind for TX parse config */
- cfg = NPC_TX_DEF_PKIND;
- rvu_write64(rvu, blkaddr, NIX_AF_LFX_TX_PARSE_CFG(nixlf), cfg);
+ if (rvu_cgx_is_pkind_config_permitted(rvu, pcifunc)) {
+ cfg = NPC_TX_DEF_PKIND;
+ rvu_write64(rvu, blkaddr, NIX_AF_LFX_TX_PARSE_CFG(nixlf), cfg);
+ }
if (is_rep_dev(rvu, pcifunc)) {
pfvf->tx_chan_base = RVU_SWITCH_LBK_CHAN;
diff --git a/drivers/net/ethernet/marvell/octeontx2/af/rvu_npc.c b/drivers/net/ethernet/marvell/octeontx2/af/rvu_npc.c
index c7bc0b3a29b9..1297c6681a01 100644
--- a/drivers/net/ethernet/marvell/octeontx2/af/rvu_npc.c
+++ b/drivers/net/ethernet/marvell/octeontx2/af/rvu_npc.c
@@ -17,6 +17,7 @@
#include "npc_profile.h"
#include "rvu_npc_hash.h"
#include "cn20k/npc.h"
+#include "lmac_common.h"
#include "rvu_npc.h"
#include "cn20k/reg.h"
@@ -4204,6 +4205,7 @@ int rvu_npc_set_parse_mode(struct rvu *rvu, u16 pcifunc, u64 mode, u8 dir,
int pf = rvu_get_pf(rvu->pdev, pcifunc);
u64 rxpkind, txpkind;
u8 cgx_id, lmac_id;
+ struct cgx *cgxd;
/* use default pkind to disable edsa/higig */
rxpkind = rvu_npc_get_pkind(rvu, pf);
@@ -4228,9 +4230,11 @@ int rvu_npc_set_parse_mode(struct rvu *rvu, u16 pcifunc, u64 mode, u8 dir,
if (!is_cgx_config_permitted(rvu, pcifunc))
return 0;
rvu_get_cgx_lmac_id(rvu->pf2cgxlmac_map[pf], &cgx_id, &lmac_id);
+ cgxd = rvu_cgx_pdata(cgx_id, rvu);
- rc = cgx_set_pkind(rvu_cgx_pdata(cgx_id, rvu), lmac_id,
- rxpkind);
+ mutex_lock(&cgxd->lock);
+ rc = cgx_set_pkind(cgxd, lmac_id, rxpkind);
+ mutex_unlock(&cgxd->lock);
if (rc)
return rc;
}
@@ -4241,6 +4245,9 @@ int rvu_npc_set_parse_mode(struct rvu *rvu, u16 pcifunc, u64 mode, u8 dir,
if (rc)
return rc;
+ if (!rvu_cgx_is_pkind_config_permitted(rvu, pcifunc))
+ return -EINVAL;
+
rvu_write64(rvu, blkaddr, NIX_AF_LFX_TX_PARSE_CFG(nixlf),
txpkind);
}
--
2.43.0
^ permalink raw reply related
* Re: [PATCH] dpaa2-eth: put MAC endpoint device on disconnect
From: Ioana Ciornei @ 2026-07-13 12:10 UTC (permalink / raw)
To: Guangshuo Li
Cc: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, netdev, linux-kernel
In-Reply-To: <20260708111738.750391-1-lgs201920130244@gmail.com>
On Wed, Jul 08, 2026 at 07:17:37PM +0800, Guangshuo Li wrote:
> fsl_mc_get_endpoint() returns the MAC endpoint device with a reference
> taken through device_find_child(). The Ethernet connect path stores that
> device in mac->mc_dev and keeps it for the lifetime of the connected MAC
> object.
>
> However, the disconnect path only disconnects and closes the MAC before
> freeing the dpaa2_mac object. It does not drop the endpoint device
> reference stored in mac->mc_dev, so every successful connect leaks that
> device reference when the MAC is later disconnected.
>
> Drop the endpoint device reference after closing the MAC and before
> freeing the dpaa2_mac object.
>
> Fixes: 719479230893 ("dpaa2-eth: add MAC/PHY support through phylink")
> Signed-off-by: Guangshuo Li <lgs201920130244@gmail.com>
Reviewed-by: Ioana Ciornei <ioana.ciornei@nxp.com>
^ permalink raw reply
* Re: [PATCH] dpaa2-eth: put MAC endpoint device on disconnect
From: Ioana Ciornei @ 2026-07-13 12:09 UTC (permalink / raw)
To: Guangshuo Li
Cc: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, netdev, linux-kernel
In-Reply-To: <20260708111738.750391-1-lgs201920130244@gmail.com>
On Wed, Jul 08, 2026 at 07:17:37PM +0800, Guangshuo Li wrote:
> fsl_mc_get_endpoint() returns the MAC endpoint device with a reference
> taken through device_find_child(). The Ethernet connect path stores that
> device in mac->mc_dev and keeps it for the lifetime of the connected MAC
> object.
>
> However, the disconnect path only disconnects and closes the MAC before
> freeing the dpaa2_mac object. It does not drop the endpoint device
> reference stored in mac->mc_dev, so every successful connect leaks that
> device reference when the MAC is later disconnected.
>
> Drop the endpoint device reference after closing the MAC and before
> freeing the dpaa2_mac object.
>
> Fixes: 719479230893 ("dpaa2-eth: add MAC/PHY support through phylink")
> Signed-off-by: Guangshuo Li <lgs201920130244@gmail.com>
Reviewed-by: Ioana Ciornei <ioana.ciornei@nxp.com>
Next time, please mention the targeted tree (net-next vs net).
^ permalink raw reply
* Re: [PATCH v8 07/10] rust: configfs: use `LocalModule` for `THIS_MODULE`
From: Gary Guo @ 2026-07-13 12:07 UTC (permalink / raw)
To: Alvin Sun, Miguel Ojeda, Boqun Feng, Gary Guo,
Björn Roy Baron, Benno Lossin, Andreas Hindborg, Alice Ryhl,
Trevor Gross, Danilo Krummrich, Luis Chamberlain, Petr Pavlu,
Daniel Gomez, Sami Tolvanen, Aaron Tomlin, Greg Kroah-Hartman,
Rafael J. Wysocki, David Airlie, Simona Vetter, Daniel Almeida,
Arnd Bergmann, Brendan Higgins, David Gow, Rae Moar, Breno Leitao,
Jens Axboe, Dave Ertman, Leon Romanovsky, Igor Korotin,
FUJITA Tomonori, Bjorn Helgaas, Krzysztof Wilczyński,
Arve Hjønnevåg, Todd Kjos, Christian Brauner,
Carlos Llamas
Cc: rust-for-linux, linux-modules, driver-core, dri-devel, nova-gpu,
linux-kselftest, kunit-dev, linux-block, linux-kernel, netdev,
linux-pci
In-Reply-To: <20260713-fix-fops-owner-v8-7-2495cfa82d47@linux.dev>
On Mon Jul 13, 2026 at 7:45 AM BST, Alvin Sun wrote:
> Replace the `THIS_MODULE` static reference in the `configfs_attrs!`
> macro with `this_module::<LocalModule>()`, and update
> rnull to import `LocalModule` instead of `THIS_MODULE`, consistent
> with the move of `THIS_MODULE` into the `ModuleMetadata` trait.
>
> Assisted-by: opencode:glm-5.2
> Reviewed-by: Andreas Hindborg <a.hindborg@kernel.org>
> Acked-by: Danilo Krummrich <dakr@kernel.org>
> Signed-off-by: Alvin Sun <alvin.sun@linux.dev>
Reviewed-by: Gary Guo <gary@garyguo.net>
> ---
> drivers/block/rnull/configfs.rs | 5 +----
> rust/kernel/configfs.rs | 9 ++++++---
> 2 files changed, 7 insertions(+), 7 deletions(-)
^ permalink raw reply
* RE: [PATCH net v4] tipc: serialize udp bearer replicast list updates
From: Tung Quang Nguyen @ 2026-07-13 12:06 UTC (permalink / raw)
To: Weiming Shi
Cc: netdev@vger.kernel.org, tipc-discussion@lists.sourceforge.net,
linux-kernel@vger.kernel.org, xmei5@asu.edu, Jon Maloy,
David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman
In-Reply-To: <20260710174718.1969789-2-bestswngs@gmail.com>
>Subject: [PATCH net v4] tipc: serialize udp bearer replicast list updates
>
>tipc_udp_rcast_add() and cleanup_bearer() both update ub->rcast.list with
>list_add_rcu() / list_del_rcu(), but nothing serializes them. The add runs from
>the encap receive softirq (via tipc_udp_rcast_disc()) without rtnl_lock(), so it
>can race the cleanup delete and corrupt the list:
>
> list_del corruption. prev->next should be ffff8880298d7ab8,
> but was ffff88802449ad38. (prev=ffff888027e3ec98)
> kernel BUG at lib/list_debug.c:62!
> RIP: __list_del_entry_valid_or_report+0x17a/0x200
> Workqueue: events cleanup_bearer
> Call Trace:
> cleanup_bearer (net/tipc/udp_media.c:811)
> process_one_work (kernel/workqueue.c:3302)
> worker_thread (kernel/workqueue.c:3466)
>
>The bearer can be enabled from an unprivileged user namespace, as the
>TIPCv2 generic-netlink ops carry no GENL_ADMIN_PERM.
>
>Add a spinlock to struct udp_bearer and take it around the list_add_rcu() in
>tipc_udp_rcast_add() and the list_del_rcu() loop in cleanup_bearer() so the
>two writers can no longer corrupt the list.
>
>Reject a duplicate peer under the same lock before allocating, and remove
>tipc_udp_is_known_peer(). The old lockless pre-check in
>tipc_udp_rcast_disc() was racy: two softirqs discovering the same peer could
>both find it absent and add it twice. Doing the check under rcast_lock in
>tipc_udp_rcast_add(), before the allocation, makes it the single point of truth
>for both the discovery and the netlink add paths and keeps a flood of the same
>address from churning the allocator.
>
>Fixes: ef20cd4dd163 ("tipc: introduce UDP replicast")
>Reported-by: Xiang Mei <xmei5@asu.edu>
>Suggested-by: Tung Nguyen <tung.quang.nguyen@est.tech>
>Signed-off-by: Weiming Shi <bestswngs@gmail.com>
>---
>v4: (per Tung's review)
> - Reject a duplicate under rcast_lock before allocating rcast, so a
> flood of the same address can't churn the allocator.
>v3:
> - Check for a duplicate peer in tipc_udp_rcast_add() under rcast_lock
> and remove tipc_udp_is_known_peer().
>v2:
> - Narrow the lock to the list mutation.
>
> net/tipc/udp_media.c | 53 ++++++++++++++++++++------------------------
> 1 file changed, 24 insertions(+), 29 deletions(-)
>
>diff --git a/net/tipc/udp_media.c b/net/tipc/udp_media.c index
>62ae7f5b5..3f0d75d72 100644
>--- a/net/tipc/udp_media.c
>+++ b/net/tipc/udp_media.c
>@@ -94,6 +94,7 @@ struct udp_replicast {
> * @ifindex: local address scope
> * @work: used to schedule deferred work on a bearer
> * @rcast: associated udp_replicast container
>+ * @rcast_lock: serialize updates to @rcast.list against concurrent
>updaters
> */
> struct udp_bearer {
> struct tipc_bearer __rcu *bearer;
>@@ -101,6 +102,7 @@ struct udp_bearer {
> u32 ifindex;
> struct work_struct work;
> struct udp_replicast rcast;
>+ spinlock_t rcast_lock; /* protects rcast.list */
> };
>
> static int tipc_udp_is_mcast_addr(struct udp_media_addr *addr) @@ -278,26
>+280,6 @@ static int tipc_udp_send_msg(struct net *net, struct sk_buff *skb,
> return err;
> }
>
>-static bool tipc_udp_is_known_peer(struct tipc_bearer *b,
>- struct udp_media_addr *addr)
>-{
>- struct udp_replicast *rcast, *tmp;
>- struct udp_bearer *ub;
>-
>- ub = rcu_dereference_rtnl(b->media_ptr);
>- if (!ub) {
>- pr_err_ratelimited("UDP bearer instance not found\n");
>- return false;
>- }
>-
>- list_for_each_entry_safe(rcast, tmp, &ub->rcast.list, list) {
>- if (!memcmp(&rcast->addr, addr, sizeof(struct
>udp_media_addr)))
>- return true;
>- }
>-
>- return false;
>-}
>-
> static int tipc_udp_rcast_add(struct tipc_bearer *b,
> struct udp_media_addr *addr)
> {
>@@ -308,16 +290,34 @@ static int tipc_udp_rcast_add(struct tipc_bearer *b,
> if (!ub)
> return -ENODEV;
>
>+ /* Serialize with other updaters and reject a duplicate before
>+ * allocating, so a flood of the same address can't churn the
>+ * allocator.
>+ */
>+ spin_lock_bh(&ub->rcast_lock);
>+ list_for_each_entry(rcast, &ub->rcast.list, list) {
>+ if (!memcmp(&rcast->addr, addr, sizeof(*addr))) {
>+ spin_unlock_bh(&ub->rcast_lock);
>+ return 0;
>+ }
>+ }
>+
> rcast = kmalloc_obj(*rcast, GFP_ATOMIC);
>- if (!rcast)
>+ if (!rcast) {
>+ spin_unlock_bh(&ub->rcast_lock);
> return -ENOMEM;
>+ }
>
> if (dst_cache_init(&rcast->dst_cache, GFP_ATOMIC)) {
>+ spin_unlock_bh(&ub->rcast_lock);
> kfree(rcast);
> return -ENOMEM;
> }
>
> memcpy(&rcast->addr, addr, sizeof(struct udp_media_addr));
>+ list_add_rcu(&rcast->list, &ub->rcast.list);
>+ b->bcast_addr.broadcast = TIPC_REPLICAST_SUPPORT;
>+ spin_unlock_bh(&ub->rcast_lock);
sashiko reports potential memory leak when tipc_udp_nl_bearer_add() adds an element to the list right after cleanup_bearer() frees all element in the list.
I think we need to add more code to handle this case as below:
--- a/net/tipc/udp_media.c
+++ b/net/tipc/udp_media.c
@@ -95,6 +95,7 @@ struct udp_replicast {
* @work: used to schedule deferred work on a bearer
* @rcast: associated udp_replicast container
* @rcast_lock: serialize updates to @rcast.list against concurrent updaters
+ * @bearer_disabled: flag to not add rcast to rcast.list if bearer was disabled
*/
struct udp_bearer {
struct tipc_bearer __rcu *bearer;
@@ -103,6 +104,7 @@ struct udp_bearer {
struct work_struct work;
struct udp_replicast rcast;
spinlock_t rcast_lock; /* protects rcast.list */
+ bool bearer_disabled;
};
static int tipc_udp_is_mcast_addr(struct udp_media_addr *addr)
@@ -295,6 +297,10 @@ static int tipc_udp_rcast_add(struct tipc_bearer *b,
* allocator.
*/
spin_lock_bh(&ub->rcast_lock);
+ if (ub->bearer_disabled) {
+ spin_unlock_bh(&ub->rcast_lock);
+ return 0;
+ }
list_for_each_entry(rcast, &ub->rcast.list, list) {
if (!memcmp(&rcast->addr, addr, sizeof(*addr))) {
spin_unlock_bh(&ub->rcast_lock);
@@ -817,6 +823,7 @@ static void cleanup_bearer(struct work_struct *work)
list_del_rcu(&rcast->list);
call_rcu_hurry(&rcast->rcu, rcast_free_rcu);
}
+ ub->bearer_disabled = true;
spin_unlock_bh(&ub->rcast_lock);
tn = tipc_net(sock_net(ub->sk));
>
> if (ntohs(addr->proto) == ETH_P_IP)
> pr_info("New replicast peer: %pI4\n", &rcast->addr.ipv4); @@
>-325,8 +325,6 @@ static int tipc_udp_rcast_add(struct tipc_bearer *b,
> else if (ntohs(addr->proto) == ETH_P_IPV6)
> pr_info("New replicast peer: %pI6\n", &rcast->addr.ipv6);
>#endif
>- b->bcast_addr.broadcast = TIPC_REPLICAST_SUPPORT;
>- list_add_rcu(&rcast->list, &ub->rcast.list);
> return 0;
> }
>
>@@ -361,9 +359,6 @@ static int tipc_udp_rcast_disc(struct tipc_bearer *b,
>struct sk_buff *skb)
> return 0;
> }
>
>- if (likely(tipc_udp_is_known_peer(b, &src)))
>- return 0;
>-
> return tipc_udp_rcast_add(b, &src);
> }
>
>@@ -644,9 +639,6 @@ int tipc_udp_nl_bearer_add(struct tipc_bearer *b,
>struct nlattr *attr)
> return -EINVAL;
> }
>
>- if (tipc_udp_is_known_peer(b, &addr))
>- return 0;
>-
> return tipc_udp_rcast_add(b, &addr);
> }
>
>@@ -679,6 +671,7 @@ static int tipc_udp_enable(struct net *net, struct
>tipc_bearer *b,
> return -ENOMEM;
>
> INIT_LIST_HEAD(&ub->rcast.list);
>+ spin_lock_init(&ub->rcast_lock);
>
> if (!attrs[TIPC_NLA_BEARER_UDP_OPTS])
> goto err;
>@@ -819,10 +812,12 @@ static void cleanup_bearer(struct work_struct
>*work)
> struct udp_replicast *rcast, *tmp;
> struct tipc_net *tn;
>
>+ spin_lock_bh(&ub->rcast_lock);
> list_for_each_entry_safe(rcast, tmp, &ub->rcast.list, list) {
> list_del_rcu(&rcast->list);
> call_rcu_hurry(&rcast->rcu, rcast_free_rcu);
> }
>+ spin_unlock_bh(&ub->rcast_lock);
>
> tn = tipc_net(sock_net(ub->sk));
>
>--
>2.43.0
^ permalink raw reply
* Re: [PATCH net-next v3 06/15] net: macb: allocate tieoff descriptor once across device lifetime
From: Théo Lebrun @ 2026-07-13 12:00 UTC (permalink / raw)
To: Nicolai Buchwitz
Cc: Conor Dooley, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Richard Cochran, Russell King,
netdev, linux-kernel, Nicolas Ferre, Claudiu Beznea,
Paolo Valerio, Vladimir Kondratiev, Gregory CLEMENT,
Benoît Monin, Tawfik Bayouk, Thomas Petazzoni,
Maxime Chevallier
In-Reply-To: <6e449ee7d40ec885e8a53fa18fe70e86@tipi-net.de>
On Thu Jul 2, 2026 at 12:54 PM CEST, Nicolai Buchwitz wrote:
> On 1.7.2026 17:59, Théo Lebrun wrote:
>> The tieoff descriptor is a RX DMA descriptor ring of size one. It gets
>> configured onto queues for Wake-on-LAN during system-wide suspend when
>> hardware does not support disabling individual queues
>> (MACB_CAPS_QUEUE_DISABLE).
>>
>> MACB/GEM driver allocates it alongside the main RX ring
>> inside macb_alloc() at open. Free is done by macb_free() at close.
>>
>> Change to allocate once at probe and free on probe failure or device
>> removal. This makes the tieoff descriptor lifetime much longer,
>> avoiding repeating coherent buffer allocation on each open/close cycle.
>>
>> Main benefit: we dissociate its lifetime from the main ring's lifetime.
>> That way there is less work to be doing on resources (re)alloc. This
>> currently happens on close/open, but will soon also happen on context
>> swap operations (set_ringparam, change_mtu, set_channels, etc).
>>
>> Signed-off-by: Théo Lebrun <theo.lebrun@bootlin.com>
>> ---
>> drivers/net/ethernet/cadence/macb_main.c | 75
>> +++++++++++++++++---------------
>> 1 file changed, 41 insertions(+), 34 deletions(-)
>>
>> diff --git a/drivers/net/ethernet/cadence/macb_main.c
>> b/drivers/net/ethernet/cadence/macb_main.c
>> index 8b52122bc134..951a7f080225 100644
>> --- a/drivers/net/ethernet/cadence/macb_main.c
>> +++ b/drivers/net/ethernet/cadence/macb_main.c
>
>> [...]
>
>> static void macb_init_rings(struct macb *bp)
>> @@ -2832,8 +2801,6 @@ static void macb_init_rings(struct macb *bp)
>> bp->queues[0].tx_head = 0;
>> bp->queues[0].tx_tail = 0;
>> desc->ctrl |= MACB_BIT(TX_WRAP);
>> -
>> - macb_init_tieoff(bp);
>> }
>>
>> static void macb_reset_hw(struct macb *bp)
>> @@ -5518,6 +5485,38 @@ static int eyeq5_init(struct platform_device
>> *pdev)
>> return ret;
>> }
>>
>> +static int macb_alloc_tieoff(struct macb *bp)
>> +{
>> + /* Tieoff is a workaround in case HW cannot disable queues, for PM.
>> */
>> + if (bp->caps & MACB_CAPS_QUEUE_DISABLE)
>> + return 0;
>
> Before, the tieoff was allocated in macb_alloc(), which the at91ether
> path
> never called. Now it's allocated from macb_probe() for all variants,
> gated only
> on MACB_CAPS_QUEUE_DISABLE, so EMAC gets a coherent descriptor it never
> uses.
>
> Add MACB_CAPS_MACB_IS_EMAC to the if statement?
Clearly. That EMAC distinction keeps being annoying.
Thanks,
--
Théo Lebrun, Bootlin
Embedded Linux and Kernel engineering
https://bootlin.com
^ permalink raw reply
* Re: [PATCH net 4/7] selftests: ovpn: increase timeout
From: Antonio Quartulli @ 2026-07-13 11:44 UTC (permalink / raw)
To: Matthieu Baerts (NGI0), David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, Shuah Khan
Cc: netdev, linux-kselftest, linux-kernel, Sabrina Dubroca
In-Reply-To: <20260710-net-sft-fix-containers-v1-4-a2915c294ef5@kernel.org>
Hi all,
On 10/07/2026 20:04, Matthieu Baerts (NGI0) wrote:
> The default timeout is 45 seconds, that's too low for a few ovpn tests.
>
> Indeed, these tests can take up to 50 seconds with some debug kernel
> config on NIPA. Set a timeout to 90 seconds, just to be on the safe
> side.
>
> Note that the Fixes tag here points to the introduction of the ovpn
> tests because I don't know when they started to take more than 45
> seconds. That's OK because a timeout of 1.5 minutes is not exaggerated.
>
> Fixes: 959bc330a439 ("testing/selftests: add test tool and scripts for ovpn module")
> Signed-off-by: Matthieu Baerts (NGI0) <matttbe@kernel.org>
we didn't really bother tweaking this value because AFAIK NIPA uses a
much higher timeout value.
This said, I agree that when running locally 45 seconds may not be
always enough.
Acked-by: Antonio Quartulli <antonio@openvpn.net>
Jakub can pull this one directly without going through my tree.
--
Antonio Quartulli
OpenVPN Inc.
^ permalink raw reply
* Re: [PATCH net 3/7] selftests: ovpn: add IPV6 and VETH configs
From: Antonio Quartulli @ 2026-07-13 11:42 UTC (permalink / raw)
To: Matthieu Baerts (NGI0), David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, Shuah Khan
Cc: netdev, linux-kselftest, linux-kernel, Sabrina Dubroca
In-Reply-To: <20260710-net-sft-fix-containers-v1-3-a2915c294ef5@kernel.org>
Hi all,
On 10/07/2026 20:04, Matthieu Baerts (NGI0) wrote:
> They are required to run the selftests:
>
> - Tests are executed in v4 and v6.
>
> - Virtual Ethernet are used between the different netns.
>
> This has not been seen on NIPA before, because the 'ovpn' tests are
> executed with the 'tcp_ao' ones, merging their config files. These two
> kernel config are present in tools/testing/selftests/net/tcp_ao/config.
>
> This issue is visible when only the ovpn config is used on top of the
> default one. This is the recommended way to execute selftest targets.
>
> Fixes: 959bc330a439 ("testing/selftests: add test tool and scripts for ovpn module")
> Signed-off-by: Matthieu Baerts (NGI0) <matttbe@kernel.org>
Acked-by: Antonio Quartulli <antonio@openvpn.net>
Jakub can pull this one directly without going through my tree.
--
Antonio Quartulli
OpenVPN Inc.
^ permalink raw reply
* [PATCH v4 1/1] macvlan: allow source mode devices along with passthru
From: Thomas Martitz @ 2026-07-13 11:24 UTC (permalink / raw)
To: Simon Horman, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, open list:NETWORKING DRIVERS,
open list
Cc: Thomas Martitz, open list:NETWORKING DRIVERS, open list
In-Reply-To: <20260713112414.2842803-1-t.martitz@fritz.com>
This allows for configurations where there are a few
known senders in the system (e.g. multiple SoCs on the same
board) along with unlimited external senders.
The source mode devices represent the known senders while
all external senders terminate on passthru device.
Although you can still receive packets on the lower device
without the need for the passthru vlan device, there
are use cases where you need additional packet processing
in the pipeline that hooks via rx_handler. With this the
rx_handler can be attached to the passthru device while
macvlan itself remains attached to the lower device.
We use this to use the same physical link for inter-SoC
networking and external networking. Some of our chips
have no other viable link for inter-SoC traffic.
Signed-off-by: Thomas Martitz <t.martitz@fritz.com>
---
drivers/net/macvlan.c | 118 ++++++++++++++++++++++++++++--------------
1 file changed, 79 insertions(+), 39 deletions(-)
diff --git a/drivers/net/macvlan.c b/drivers/net/macvlan.c
index 9a4bc99dbf53b..405f2cc0e5363 100644
--- a/drivers/net/macvlan.c
+++ b/drivers/net/macvlan.c
@@ -83,6 +83,11 @@ static inline void macvlan_set_passthru(struct macvlan_port *port)
port->flags |= MACVLAN_F_PASSTHRU;
}
+static inline void macvlan_clear_passthru(struct macvlan_port *port)
+{
+ port->flags &= ~MACVLAN_F_PASSTHRU;
+}
+
static inline bool macvlan_addr_change(const struct macvlan_port *port)
{
return port->flags & MACVLAN_F_ADDRCHANGE;
@@ -637,7 +642,7 @@ static int macvlan_open(struct net_device *dev)
struct net_device *lowerdev = vlan->lowerdev;
int err;
- if (macvlan_passthru(vlan->port)) {
+ if (vlan->mode == MACVLAN_MODE_PASSTHRU) {
if (!(vlan->flags & MACVLAN_FLAG_NOPROMISC)) {
err = dev_set_promiscuity(lowerdev, 1);
if (err < 0)
@@ -712,7 +717,7 @@ static int macvlan_stop(struct net_device *dev)
dev_uc_unsync(lowerdev, dev);
dev_mc_unsync(lowerdev, dev);
- if (macvlan_passthru(vlan->port)) {
+ if (vlan->mode == MACVLAN_MODE_PASSTHRU) {
if (!(vlan->flags & MACVLAN_FLAG_NOPROMISC))
dev_set_promiscuity(lowerdev, -1);
goto hash_del;
@@ -968,6 +973,21 @@ static int macvlan_init(struct net_device *dev)
return 0;
}
+static void macvlan_restore_mac(struct macvlan_port *port)
+{
+ /* If the lower device address has been changed by passthru
+ * macvlan, put it back.
+ */
+ if (macvlan_passthru(port) &&
+ !ether_addr_equal(port->dev->dev_addr, port->perm_addr)) {
+ struct sockaddr_storage ss;
+
+ ss.ss_family = port->dev->type;
+ memcpy(&ss.__data, port->perm_addr, port->dev->addr_len);
+ dev_set_mac_address(port->dev, &ss, NULL);
+ }
+}
+
static void macvlan_uninit(struct net_device *dev)
{
struct macvlan_dev *vlan = netdev_priv(dev);
@@ -977,8 +997,19 @@ static void macvlan_uninit(struct net_device *dev)
macvlan_flush_sources(port, vlan);
port->count -= 1;
- if (!port->count)
- macvlan_port_destroy(port->dev);
+ if (port->count) {
+ /* In case of remaining source interfaces undo
+ * passthru-specific properties.
+ */
+ if (vlan->mode == MACVLAN_MODE_PASSTHRU) {
+ /* Order is important, do not trigger macvlan_device_event(). */
+ macvlan_clear_passthru(port);
+ macvlan_restore_mac(port);
+ }
+ return;
+ }
+
+ macvlan_port_destroy(port->dev);
}
static void macvlan_dev_get_stats64(struct net_device *dev,
@@ -1052,7 +1083,7 @@ static int macvlan_fdb_add(struct ndmsg *ndm, struct nlattr *tb[],
/* Support unicast filter only on passthru devices.
* Multicast filter should be allowed on all devices.
*/
- if (!macvlan_passthru(vlan->port) && is_unicast_ether_addr(addr))
+ if (vlan->mode != MACVLAN_MODE_PASSTHRU && is_unicast_ether_addr(addr))
return -EOPNOTSUPP;
if (flags & NLM_F_REPLACE)
@@ -1077,7 +1108,7 @@ static int macvlan_fdb_del(struct ndmsg *ndm, struct nlattr *tb[],
/* Support unicast filter only on passthru devices.
* Multicast filter should be allowed on all devices.
*/
- if (!macvlan_passthru(vlan->port) && is_unicast_ether_addr(addr))
+ if (vlan->mode != MACVLAN_MODE_PASSTHRU && is_unicast_ether_addr(addr))
return -EOPNOTSUPP;
if (is_unicast_ether_addr(addr))
@@ -1308,17 +1339,7 @@ static void macvlan_port_destroy(struct net_device *dev)
kfree_skb(skb);
}
- /* If the lower device address has been changed by passthru
- * macvlan, put it back.
- */
- if (macvlan_passthru(port) &&
- !ether_addr_equal(port->dev->dev_addr, port->perm_addr)) {
- struct sockaddr_storage ss;
-
- ss.ss_family = port->dev->type;
- memcpy(&ss.__data, port->perm_addr, port->dev->addr_len);
- dev_set_mac_address(port->dev, &ss, NULL);
- }
+ macvlan_restore_mac(port);
kfree(port);
}
@@ -1506,15 +1527,6 @@ int macvlan_common_newlink(struct net_device *dev,
}
port = macvlan_port_get_rtnl(lowerdev);
- /* Only 1 macvlan device can be created in passthru mode */
- if (macvlan_passthru(port)) {
- /* The macvlan port must be not created this time,
- * still goto destroy_macvlan_port for readability.
- */
- err = -EINVAL;
- goto destroy_macvlan_port;
- }
-
vlan->lowerdev = lowerdev;
vlan->dev = dev;
vlan->port = port;
@@ -1527,12 +1539,31 @@ int macvlan_common_newlink(struct net_device *dev,
if (data && data[IFLA_MACVLAN_FLAGS])
vlan->flags = nla_get_u16(data[IFLA_MACVLAN_FLAGS]);
+ /* Only 1 macvlan device can be created in passthru mode. There may be
+ * additional source mode devices but nothing else at the moment.
+ *
+ * First check if adding a source mode device to an existing passthru vlan.
+ */
+ if (macvlan_passthru(port) && vlan->mode != MACVLAN_MODE_SOURCE) {
+ /* The macvlan port must be not created this time,
+ * still goto destroy_macvlan_port for readability.
+ */
+ err = -EINVAL;
+ goto destroy_macvlan_port;
+ }
+
+ /* Now check if adding a passthru device to an existing set of source mode
+ * devices.
+ */
if (vlan->mode == MACVLAN_MODE_PASSTHRU) {
- if (port->count) {
- err = -EINVAL;
- goto destroy_macvlan_port;
+ struct macvlan_dev *p;
+
+ list_for_each_entry(p, &port->vlans, list) {
+ if (p->mode != MACVLAN_MODE_SOURCE) {
+ err = -EINVAL;
+ goto destroy_macvlan_port;
+ }
}
- macvlan_set_passthru(port);
eth_hw_addr_inherit(dev, lowerdev);
}
@@ -1564,7 +1595,12 @@ int macvlan_common_newlink(struct net_device *dev,
if (err)
goto unregister_netdev;
- list_add_tail_rcu(&vlan->list, &port->vlans);
+ /* macvlan_handle_frame expects the (one and only) passthru device first. */
+ if (vlan->mode == MACVLAN_MODE_PASSTHRU) {
+ macvlan_set_passthru(port);
+ list_add_rcu(&vlan->list, &port->vlans);
+ } else
+ list_add_tail_rcu(&vlan->list, &port->vlans);
update_port_bc_queue_len(vlan->port);
netif_stacked_transfer_operstate(lowerdev, dev);
linkwatch_fire_event(dev);
@@ -1627,19 +1663,23 @@ static int macvlan_changelink(struct net_device *dev,
if (data && data[IFLA_MACVLAN_MODE]) {
set_mode = true;
mode = nla_get_u32(data[IFLA_MACVLAN_MODE]);
- /* Passthrough mode can't be set or cleared dynamically */
- if ((mode == MACVLAN_MODE_PASSTHRU) !=
- (vlan->mode == MACVLAN_MODE_PASSTHRU))
- return -EINVAL;
- if (vlan->mode == MACVLAN_MODE_SOURCE &&
- vlan->mode != mode)
- macvlan_flush_sources(vlan->port, vlan);
+ if (mode != vlan->mode) {
+ /* Passthrough mode can't be set or cleared dynamically,
+ * regardless of existing source interfaces. Furthermore, source
+ * interfaces can't switch modes within a passhtrough port.
+ */
+ if (vlan->mode == MACVLAN_MODE_PASSTHRU ||
+ macvlan_passthru(vlan->port))
+ return -EINVAL;
+ if (vlan->mode == MACVLAN_MODE_SOURCE)
+ macvlan_flush_sources(vlan->port, vlan);
+ }
}
if (data && data[IFLA_MACVLAN_FLAGS]) {
__u16 flags = nla_get_u16(data[IFLA_MACVLAN_FLAGS]);
bool promisc = (flags ^ vlan->flags) & MACVLAN_FLAG_NOPROMISC;
- if (macvlan_passthru(vlan->port) && promisc) {
+ if (vlan->mode == MACVLAN_MODE_PASSTHRU && promisc) {
int err;
if (flags & MACVLAN_FLAG_NOPROMISC)
--
2.54.0
^ permalink raw reply related
* [PATCH v4 0/1] macvlan: allow source mode devices along with passthru
From: Thomas Martitz @ 2026-07-13 11:24 UTC (permalink / raw)
To: Simon Horman, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, open list:NETWORKING DRIVERS,
open list
Cc: Thomas Martitz, open list:NETWORKING DRIVERS, open list
Hello,
we're trying to solve a use case on our devices where two SoC are
connected on the same board, using the only available high-speed interface.
One SoC runs the main Linux system including the full routing stack
(FRITZ!OS) and the other SoC implements most of the GPON ONT side.
The high-speed interface is of course also used for the user traffic.
Therefore we must tell the inter-SoC traffic apart from the user traffic.
We achieve this by matching the well-known MAC address of the ONT SoC.
The user traffic passes through the ONT SoC without modifying MAC headers.
Now we would like to use macvlan (with source mode devices) on the main
SoC side for this but our routing stack requires the rx_handler to be
available. Therefore macvlan is currently not an option.
With this patch macvlan becomes an option because the current limitation
of either "one passthru device" or "any other configuration" is relaxed
for the combination of passthru and any number of source mode devices.
This allows us to configure a source mode device for the other SoC and
register an rx_handler for further processing on the passthru device.
Thanks in advance!
---
Changes in v4
- Prevent macvlan_restore_mac() from changing the MAC address
of remaining source mode interfaces.
- Set MACVLAN_F_PASSTHRU on the macvlan_port only after adding
the passthru interface truly succeeds.
- Changing existing interfaces to passthru mode shouldn't become
allowed.
Changes in v3
- fix passthru port removal caused by passing the wrong
device to macvlan_port_release_mac(). This was also
detected by syzbot.
- macvlan_port_release_mac() is now named macvlan_restore_mac()
and gets passed a "struct macvlan_port" directly.
- Link to v2: https://lore.kernel.org/netdev/20260709100512.1383421-1-t.martitz@fritz.com/
Changes in v2:
- changed several port-wide checks (macvlan_passthru()) to
per-interface checks (vlan->mode == vlan->mode == MACVLAN_MODE_PASSTHRU)
- correctly handle removing the passthru interface when there are still
source interfaces
- Link to initial posting: https://lore.kernel.org/netdev/20260612092345.2352255-1-t.martitz@fritz.com/
---
Thomas Martitz (1):
macvlan: allow source mode devices along with passthru
drivers/net/macvlan.c | 107 ++++++++++++++++++++++++++++--------------
1 file changed, 73 insertions(+), 34 deletions(-)
--
2.54.0
^ permalink raw reply
* [PATCH RFC net-next v3 1/3] net: enforce net sysctl registration
From: Joel Granados @ 2026-07-13 11:07 UTC (permalink / raw)
To: David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman, David Ahern, Ido Schimmel, Pablo Neira Ayuso,
Florian Westphal, Phil Sutter, Marcelo Ricardo Leitner, Xin Long,
Steffen Klassert, Herbert Xu, D. Wythe, Dust Li, Sidraya Jayagond,
Wenjia Zhang, Mahanta Jambigi, Tony Lu, Wen Gu, Kuniyuki Iwashima,
Stefano Garzarella
Cc: netdev, linux-kernel, netfilter-devel, coreteam, linux-sctp,
linux-rdma, linux-s390, virtualization, Joel Granados
In-Reply-To: <20260713-jag-net_const_qualify-v3-0-7289fe9eaea6@kernel.org>
Replace the warning and file permission change with an error when an
"unsafe" net sysctl registration is detected.
One of the barriers preventing the const qualification of the ctl_tables
in the net directory is the permission (->mode) change in
ensure_safe_net_sysctl. This prep commit removes that barrier and
ensures that the received ctl_table pointer to the net ctl_table
register function is const.
Signed-off-by: Joel Granados <joel.granados@kernel.org>
---
include/net/net_namespace.h | 4 ++--
net/sysctl_net.c | 24 ++++++++++++------------
2 files changed, 14 insertions(+), 14 deletions(-)
diff --git a/include/net/net_namespace.h b/include/net/net_namespace.h
index 80de5e98a66d6c9273aa7c5b9d489b22cef8559a..dca0ec809483bec604f4ca3d99dfea32834af8fa 100644
--- a/include/net/net_namespace.h
+++ b/include/net/net_namespace.h
@@ -522,12 +522,12 @@ struct ctl_table;
#ifdef CONFIG_SYSCTL
int net_sysctl_init(void);
struct ctl_table_header *register_net_sysctl_sz(struct net *net, const char *path,
- struct ctl_table *table, size_t table_size);
+ const struct ctl_table *table, size_t table_size);
void unregister_net_sysctl_table(struct ctl_table_header *header);
#else
static inline int net_sysctl_init(void) { return 0; }
static inline struct ctl_table_header *register_net_sysctl_sz(struct net *net,
- const char *path, struct ctl_table *table, size_t table_size)
+ const char *path, const struct ctl_table *table, size_t table_size)
{
return NULL;
}
diff --git a/net/sysctl_net.c b/net/sysctl_net.c
index 19e8048241bacb18de853d3b904d0f97fd2fe78a..4714887113d90a191c300c9c49a6317d5609efeb 100644
--- a/net/sysctl_net.c
+++ b/net/sysctl_net.c
@@ -114,16 +114,16 @@ __init int net_sysctl_init(void)
goto out;
}
-/* Verify that sysctls for non-init netns are safe by either:
+/* Return error when sysctls for non-init netns are unsafe by verifying:
* 1) being read-only, or
* 2) having a data pointer which points outside of the global kernel/module
* data segment, and rather into the heap where a per-net object was
* allocated.
*/
-static void ensure_safe_net_sysctl(struct net *net, const char *path,
- struct ctl_table *table, size_t table_size)
+static int ensure_safe_net_sysctl(struct net *net, const char *path,
+ const struct ctl_table *table, size_t table_size)
{
- struct ctl_table *ent;
+ const struct ctl_table *ent;
pr_debug("Registering net sysctl (net %p): %s\n", net, path);
ent = table;
@@ -149,24 +149,24 @@ static void ensure_safe_net_sysctl(struct net *net, const char *path,
else
continue;
- /* If it is writable and points to kernel/module global
- * data, then it's probably a netns leak.
- */
+ /* Warn on netns leak. */
WARN(1, "sysctl %s/%s: data points to %s global data: %ps\n",
- path, ent->procname, where, ent->data);
+ path, ent->procname, where, ent->data);
- /* Make it "safe" by dropping writable perms */
- ent->mode &= ~0222;
+ return -EACCES;
}
+
+ return 0;
}
struct ctl_table_header *register_net_sysctl_sz(struct net *net,
const char *path,
- struct ctl_table *table,
+ const struct ctl_table *table,
size_t table_size)
{
if (!net_eq(net, &init_net))
- ensure_safe_net_sysctl(net, path, table, table_size);
+ if (ensure_safe_net_sysctl(net, path, table, table_size))
+ return NULL;
return __register_sysctl_table(&net->sysctls, path, table, table_size);
}
--
2.50.1
^ permalink raw reply related
* [PATCH RFC net-next v3 2/3] net: Const qualify ctl_tables that kmemdup unconditionally
From: Joel Granados @ 2026-07-13 11:07 UTC (permalink / raw)
To: David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman, David Ahern, Ido Schimmel, Pablo Neira Ayuso,
Florian Westphal, Phil Sutter, Marcelo Ricardo Leitner, Xin Long,
Steffen Klassert, Herbert Xu, D. Wythe, Dust Li, Sidraya Jayagond,
Wenjia Zhang, Mahanta Jambigi, Tony Lu, Wen Gu, Kuniyuki Iwashima,
Stefano Garzarella
Cc: netdev, linux-kernel, netfilter-devel, coreteam, linux-sctp,
linux-rdma, linux-s390, virtualization, Joel Granados
In-Reply-To: <20260713-jag-net_const_qualify-v3-0-7289fe9eaea6@kernel.org>
Const qualify clt_table arrays in the net directory that always pass a
memory duplicate to sysctl register. The template would then be in
.rodata and the kmemdup'ed array would be outside.
Signed-off-by: Joel Granados <joel.granados@kernel.org>
---
net/ipv4/devinet.c | 2 +-
net/ipv6/icmp.c | 2 +-
net/ipv6/route.c | 2 +-
net/ipv6/sysctl_net_ipv6.c | 2 +-
net/netfilter/nf_conntrack_standalone.c | 2 +-
net/sctp/sysctl.c | 2 +-
net/xfrm/xfrm_sysctl.c | 2 +-
7 files changed, 7 insertions(+), 7 deletions(-)
diff --git a/net/ipv4/devinet.c b/net/ipv4/devinet.c
index a35b72662e431661da1672f428cae6bb3110480b..19edc08ae20c4f16d3bcf479dc25022d55cbb5af 100644
--- a/net/ipv4/devinet.c
+++ b/net/ipv4/devinet.c
@@ -2798,7 +2798,7 @@ static void devinet_sysctl_unregister(struct in_device *idev)
neigh_sysctl_unregister(idev->arp_parms);
}
-static struct ctl_table ctl_forward_entry[] = {
+static const struct ctl_table ctl_forward_entry[] = {
{
.procname = "ip_forward",
.data = &ipv4_devconf.data[
diff --git a/net/ipv6/icmp.c b/net/ipv6/icmp.c
index efb23807a0262e8d68aa1afc8d96ee94eab89d50..a95b0351824f3237815e43bf8448110070955884 100644
--- a/net/ipv6/icmp.c
+++ b/net/ipv6/icmp.c
@@ -1374,7 +1374,7 @@ EXPORT_SYMBOL(icmpv6_err_convert);
static u32 icmpv6_errors_extension_mask_all =
GENMASK_U8(ICMP_ERR_EXT_COUNT - 1, 0);
-static struct ctl_table ipv6_icmp_table_template[] = {
+static const struct ctl_table ipv6_icmp_table_template[] = {
{
.procname = "ratelimit",
.data = &init_net.ipv6.sysctl.icmpv6_time,
diff --git a/net/ipv6/route.c b/net/ipv6/route.c
index a1301334da48c0f911da06ce448a76ecfb0d25cf..96b37c102a634c6715a5fbd1d39ca415302ff859 100644
--- a/net/ipv6/route.c
+++ b/net/ipv6/route.c
@@ -6555,7 +6555,7 @@ static int ipv6_sysctl_rtcache_flush(const struct ctl_table *ctl, int write,
return 0;
}
-static struct ctl_table ipv6_route_table_template[] = {
+static const struct ctl_table ipv6_route_table_template[] = {
{
.procname = "max_size",
.data = &init_net.ipv6.sysctl.ip6_rt_max_size,
diff --git a/net/ipv6/sysctl_net_ipv6.c b/net/ipv6/sysctl_net_ipv6.c
index d2cd33e2698d5c88df4718c9622dba2d574fa309..1a0a36dcdabc1be961d0ab69e5c93b05c53f46a8 100644
--- a/net/ipv6/sysctl_net_ipv6.c
+++ b/net/ipv6/sysctl_net_ipv6.c
@@ -61,7 +61,7 @@ proc_rt6_multipath_hash_fields(const struct ctl_table *table, int write, void *b
return ret;
}
-static struct ctl_table ipv6_table_template[] = {
+static const struct ctl_table ipv6_table_template[] = {
{
.procname = "bindv6only",
.data = &init_net.ipv6.sysctl.bindv6only,
diff --git a/net/netfilter/nf_conntrack_standalone.c b/net/netfilter/nf_conntrack_standalone.c
index be2953c7d702e92031d4bcf7e707741abed0f49c..f4f2d82192d54ed9831b9677743f1139820e5a2e 100644
--- a/net/netfilter/nf_conntrack_standalone.c
+++ b/net/netfilter/nf_conntrack_standalone.c
@@ -639,7 +639,7 @@ enum nf_ct_sysctl_index {
NF_SYSCTL_CT_LAST_SYSCTL,
};
-static struct ctl_table nf_ct_sysctl_table[] = {
+static const struct ctl_table nf_ct_sysctl_table[] = {
[NF_SYSCTL_CT_MAX] = {
.procname = "nf_conntrack_max",
.data = &nf_conntrack_max,
diff --git a/net/sctp/sysctl.c b/net/sctp/sysctl.c
index 15e7db9a3ab2e325f3951ac20c067a973a049618..331f45af9c4990d78a10a5c2c4efbcbca21813dc 100644
--- a/net/sctp/sysctl.c
+++ b/net/sctp/sysctl.c
@@ -92,7 +92,7 @@ static struct ctl_table sctp_table[] = {
#define SCTP_PF_RETRANS_IDX 2
#define SCTP_PS_RETRANS_IDX 3
-static struct ctl_table sctp_net_table[] = {
+static const struct ctl_table sctp_net_table[] = {
[SCTP_RTO_MIN_IDX] = {
.procname = "rto_min",
.data = &init_net.sctp.rto_min,
diff --git a/net/xfrm/xfrm_sysctl.c b/net/xfrm/xfrm_sysctl.c
index ca003e8a03760cd8dbb9e9f7cd5a9738eeeb7e71..357152a50faf10e5c33468c034dd1777e0bed079 100644
--- a/net/xfrm/xfrm_sysctl.c
+++ b/net/xfrm/xfrm_sysctl.c
@@ -13,7 +13,7 @@ static void __net_init __xfrm_sysctl_init(struct net *net)
}
#ifdef CONFIG_SYSCTL
-static struct ctl_table xfrm_table[] = {
+static const struct ctl_table xfrm_table[] = {
{
.procname = "xfrm_aevent_etime",
.maxlen = sizeof(u32),
--
2.50.1
^ permalink raw reply related
* [PATCH RFC net-next v3 3/3] net: Const qualify network templated ctl_tables Arrays
From: Joel Granados @ 2026-07-13 11:07 UTC (permalink / raw)
To: David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman, David Ahern, Ido Schimmel, Pablo Neira Ayuso,
Florian Westphal, Phil Sutter, Marcelo Ricardo Leitner, Xin Long,
Steffen Klassert, Herbert Xu, D. Wythe, Dust Li, Sidraya Jayagond,
Wenjia Zhang, Mahanta Jambigi, Tony Lu, Wen Gu, Kuniyuki Iwashima,
Stefano Garzarella
Cc: netdev, linux-kernel, netfilter-devel, coreteam, linux-sctp,
linux-rdma, linux-s390, virtualization, Joel Granados
In-Reply-To: <20260713-jag-net_const_qualify-v3-0-7289fe9eaea6@kernel.org>
Add duplication helpers in the cases where the ctl_table array elements
are modified after duplication. Helpers return a ctl_table as const
pointer allowing the const qualification of the static global ctl_table
array.
Signed-off-by: Joel Granados <joel.granados@kernel.org>
---
net/core/sysctl_net_core.c | 38 +++++++++++++++++----------
net/ipv4/sysctl_net_ipv4.c | 54 +++++++++++++++++++++++----------------
net/ipv4/xfrm4_policy.c | 22 ++++++++++++----
net/ipv6/xfrm6_policy.c | 22 ++++++++++++----
net/netfilter/nf_hooks_lwtunnel.c | 4 +--
net/smc/smc_sysctl.c | 26 ++++++++++++++-----
net/unix/sysctl_net_unix.c | 21 +++++++++++----
net/vmw_vsock/af_vsock.c | 25 +++++++++++++-----
8 files changed, 146 insertions(+), 66 deletions(-)
diff --git a/net/core/sysctl_net_core.c b/net/core/sysctl_net_core.c
index b508618bfc12393ba926ebf5a2dd4ea73ef03ee8..eb35da3556f4aa00cecd4582ab94e339d2518506 100644
--- a/net/core/sysctl_net_core.c
+++ b/net/core/sysctl_net_core.c
@@ -678,7 +678,7 @@ static struct ctl_table net_core_table[] = {
},
};
-static struct ctl_table netns_core_table[] = {
+static const struct ctl_table netns_core_table[] = {
#if IS_ENABLED(CONFIG_RPS)
{
.procname = "rps_default_mask",
@@ -787,26 +787,38 @@ static int __init fb_tunnels_only_for_init_net_sysctl_setup(char *str)
}
__setup("fb_tunnels=", fb_tunnels_only_for_init_net_sysctl_setup);
-static __net_init int sysctl_core_net_init(struct net *net)
+static const struct ctl_table *netns_core_table_dup(struct net *net)
{
size_t table_size = ARRAY_SIZE(netns_core_table);
struct ctl_table *tbl;
+ int i;
+
+ tbl = kmemdup(netns_core_table, sizeof(netns_core_table), GFP_KERNEL);
+ if (!tbl)
+ return NULL;
+
+ for (i = 0; i < table_size; ++i) {
+ if (tbl[i].data == &sysctl_wmem_max)
+ break;
+
+ tbl[i].data += (char *)net - (char *)&init_net;
+ }
+ for (; i < table_size; ++i)
+ tbl[i].mode &= ~0222;
+
+ return tbl;
+}
+
+static __net_init int sysctl_core_net_init(struct net *net)
+{
+ size_t table_size = ARRAY_SIZE(netns_core_table);
+ const struct ctl_table *tbl;
tbl = netns_core_table;
if (!net_eq(net, &init_net)) {
- int i;
- tbl = kmemdup(tbl, sizeof(netns_core_table), GFP_KERNEL);
+ tbl = netns_core_table_dup(net);
if (tbl == NULL)
goto err_dup;
-
- for (i = 0; i < table_size; ++i) {
- if (tbl[i].data == &sysctl_wmem_max)
- break;
-
- tbl[i].data += (char *)net - (char *)&init_net;
- }
- for (; i < table_size; ++i)
- tbl[i].mode &= ~0222;
}
net->core.sysctl_hdr = register_net_sysctl_sz(net, "net/core", tbl, table_size);
diff --git a/net/ipv4/sysctl_net_ipv4.c b/net/ipv4/sysctl_net_ipv4.c
index ca1180dba1dea9ce72028ba49b7f953da343336b..2f0363bca2a88d68276670cfce6fb04398f82bc5 100644
--- a/net/ipv4/sysctl_net_ipv4.c
+++ b/net/ipv4/sysctl_net_ipv4.c
@@ -624,7 +624,7 @@ static struct ctl_table ipv4_table[] = {
},
};
-static struct ctl_table ipv4_net_table[] = {
+static const struct ctl_table ipv4_net_table[] = {
{
.procname = "tcp_max_tw_buckets",
.data = &init_net.ipv4.tcp_death_row.sysctl_max_tw_buckets,
@@ -1654,35 +1654,45 @@ static struct ctl_table ipv4_net_table[] = {
},
};
-static __net_init int ipv4_sysctl_init_net(struct net *net)
+static const struct ctl_table *ipv4_net_table_dup(struct net *net)
{
size_t table_size = ARRAY_SIZE(ipv4_net_table);
struct ctl_table *table;
+ int i;
+
+ table = kmemdup(ipv4_net_table, sizeof(ipv4_net_table), GFP_KERNEL);
+ if (!table)
+ return NULL;
+
+ for (i = 0; i < table_size; i++) {
+ if (table[i].data) {
+ /* Update the variables to point into
+ * the current struct net
+ */
+ table[i].data += (void *)net - (void *)&init_net;
+ } else {
+ /* Entries without data pointer are global;
+ * Make them read-only in non-init_net ns
+ */
+ table[i].mode &= ~0222;
+ }
+ if (table[i].extra2 >= (void *)&init_net.ipv4 &&
+ table[i].extra2 < (void *)(&init_net.ipv4 + 1))
+ table[i].extra2 += (void *)net - (void *)&init_net;
+ }
+ return table;
+}
+
+static __net_init int ipv4_sysctl_init_net(struct net *net)
+{
+ size_t table_size = ARRAY_SIZE(ipv4_net_table);
+ const struct ctl_table *table;
table = ipv4_net_table;
if (!net_eq(net, &init_net)) {
- int i;
-
- table = kmemdup(table, sizeof(ipv4_net_table), GFP_KERNEL);
+ table = ipv4_net_table_dup(net);
if (!table)
goto err_alloc;
-
- for (i = 0; i < table_size; i++) {
- if (table[i].data) {
- /* Update the variables to point into
- * the current struct net
- */
- table[i].data += (void *)net - (void *)&init_net;
- } else {
- /* Entries without data pointer are global;
- * Make them read-only in non-init_net ns
- */
- table[i].mode &= ~0222;
- }
- if (table[i].extra2 >= (void *)&init_net.ipv4 &&
- table[i].extra2 < (void *)(&init_net.ipv4 + 1))
- table[i].extra2 += (void *)net - (void *)&init_net;
- }
}
net->ipv4.ipv4_hdr = register_net_sysctl_sz(net, "net/ipv4", table,
diff --git a/net/ipv4/xfrm4_policy.c b/net/ipv4/xfrm4_policy.c
index 58faf1ddd2b151e4569bb6351029718dac37521b..ab7a01029d490416d36482f7a3189f83d6670f42 100644
--- a/net/ipv4/xfrm4_policy.c
+++ b/net/ipv4/xfrm4_policy.c
@@ -141,7 +141,7 @@ static const struct xfrm_policy_afinfo xfrm4_policy_afinfo = {
};
#ifdef CONFIG_SYSCTL
-static struct ctl_table xfrm4_policy_table[] = {
+static const struct ctl_table xfrm4_policy_table[] = {
{
.procname = "xfrm4_gc_thresh",
.data = &init_net.xfrm.xfrm4_dst_ops.gc_thresh,
@@ -151,18 +151,30 @@ static struct ctl_table xfrm4_policy_table[] = {
},
};
-static __net_init int xfrm4_net_sysctl_init(struct net *net)
+static const struct ctl_table *xfrm4_policy_table_dup(struct net *net)
{
struct ctl_table *table;
+
+ table = kmemdup(xfrm4_policy_table, sizeof(xfrm4_policy_table),
+ GFP_KERNEL);
+ if (!table)
+ return NULL;
+
+ table[0].data = &net->xfrm.xfrm4_dst_ops.gc_thresh;
+
+ return table;
+}
+
+static __net_init int xfrm4_net_sysctl_init(struct net *net)
+{
+ const struct ctl_table *table;
struct ctl_table_header *hdr;
table = xfrm4_policy_table;
if (!net_eq(net, &init_net)) {
- table = kmemdup(table, sizeof(xfrm4_policy_table), GFP_KERNEL);
+ table = xfrm4_policy_table_dup(net);
if (!table)
goto err_alloc;
-
- table[0].data = &net->xfrm.xfrm4_dst_ops.gc_thresh;
}
hdr = register_net_sysctl_sz(net, "net/ipv4", table,
diff --git a/net/ipv6/xfrm6_policy.c b/net/ipv6/xfrm6_policy.c
index 125ea9a5b8a082052380b7fd7ed7123f5247d7cc..1e0385b62cde3f6d23382f92bbad5d7fdd09f1ef 100644
--- a/net/ipv6/xfrm6_policy.c
+++ b/net/ipv6/xfrm6_policy.c
@@ -186,7 +186,7 @@ static void xfrm6_policy_fini(void)
}
#ifdef CONFIG_SYSCTL
-static struct ctl_table xfrm6_policy_table[] = {
+static const struct ctl_table xfrm6_policy_table[] = {
{
.procname = "xfrm6_gc_thresh",
.data = &init_net.xfrm.xfrm6_dst_ops.gc_thresh,
@@ -196,18 +196,30 @@ static struct ctl_table xfrm6_policy_table[] = {
},
};
-static int __net_init xfrm6_net_sysctl_init(struct net *net)
+static const struct ctl_table *xfrm6_policy_table_dup(struct net *net)
{
struct ctl_table *table;
+
+ table = kmemdup(xfrm6_policy_table, sizeof(xfrm6_policy_table),
+ GFP_KERNEL);
+ if (!table)
+ return NULL;
+
+ table[0].data = &net->xfrm.xfrm6_dst_ops.gc_thresh;
+
+ return table;
+}
+
+static int __net_init xfrm6_net_sysctl_init(struct net *net)
+{
+ const struct ctl_table *table;
struct ctl_table_header *hdr;
table = xfrm6_policy_table;
if (!net_eq(net, &init_net)) {
- table = kmemdup(table, sizeof(xfrm6_policy_table), GFP_KERNEL);
+ table = xfrm6_policy_table_dup(net);
if (!table)
goto err_alloc;
-
- table[0].data = &net->xfrm.xfrm6_dst_ops.gc_thresh;
}
hdr = register_net_sysctl_sz(net, "net/ipv6", table,
diff --git a/net/netfilter/nf_hooks_lwtunnel.c b/net/netfilter/nf_hooks_lwtunnel.c
index 2d890dd04ff89041e6aec3741f24cdd7bc47d1fe..4e1eef1ba0f1559ca35f024723af551c6c9e7d35 100644
--- a/net/netfilter/nf_hooks_lwtunnel.c
+++ b/net/netfilter/nf_hooks_lwtunnel.c
@@ -54,7 +54,7 @@ int nf_hooks_lwtunnel_sysctl_handler(const struct ctl_table *table, int write,
}
EXPORT_SYMBOL_GPL(nf_hooks_lwtunnel_sysctl_handler);
-static struct ctl_table nf_lwtunnel_sysctl_table[] = {
+static const struct ctl_table nf_lwtunnel_sysctl_table[] = {
{
.procname = "nf_hooks_lwtunnel",
.data = NULL,
@@ -66,8 +66,8 @@ static struct ctl_table nf_lwtunnel_sysctl_table[] = {
static int __net_init nf_lwtunnel_net_init(struct net *net)
{
+ const struct ctl_table *table;
struct ctl_table_header *hdr;
- struct ctl_table *table;
table = nf_lwtunnel_sysctl_table;
if (!net_eq(net, &init_net)) {
diff --git a/net/smc/smc_sysctl.c b/net/smc/smc_sysctl.c
index b1efed5462435b1a6f2f59584a4cf47f5f6e1981..09dad48337f6164f5765fa793412bdebf47e61ca 100644
--- a/net/smc/smc_sysctl.c
+++ b/net/smc/smc_sysctl.c
@@ -97,7 +97,7 @@ static int proc_smc_hs_ctrl(const struct ctl_table *ctl, int write,
}
#endif /* CONFIG_SMC_HS_CTRL_BPF */
-static struct ctl_table smc_table[] = {
+static const struct ctl_table smc_table[] = {
{
.procname = "autocorking_size",
.data = &init_net.smc.sysctl_autocorking_size,
@@ -195,14 +195,29 @@ static struct ctl_table smc_table[] = {
#endif /* CONFIG_SMC_HS_CTRL_BPF */
};
-int __net_init smc_sysctl_net_init(struct net *net)
+static const struct ctl_table *smc_table_dup(struct net *net)
{
size_t table_size = ARRAY_SIZE(smc_table);
struct ctl_table *table;
+ int i;
+
+ table = kmemdup(smc_table, sizeof(smc_table), GFP_KERNEL);
+ if (!table)
+ return NULL;
+
+ for (i = 0; i < table_size; i++)
+ table[i].data += (void *)net - (void *)&init_net;
+
+ return table;
+}
+
+int __net_init smc_sysctl_net_init(struct net *net)
+{
+ size_t table_size = ARRAY_SIZE(smc_table);
+ const struct ctl_table *table;
table = smc_table;
if (!net_eq(net, &init_net)) {
- int i;
#if IS_ENABLED(CONFIG_SMC_HS_CTRL_BPF)
struct smc_hs_ctrl *ctrl;
@@ -214,12 +229,9 @@ int __net_init smc_sysctl_net_init(struct net *net)
rcu_read_unlock();
#endif /* CONFIG_SMC_HS_CTRL_BPF */
- table = kmemdup(table, sizeof(smc_table), GFP_KERNEL);
+ table = smc_table_dup(net);
if (!table)
goto err_alloc;
-
- for (i = 0; i < table_size; i++)
- table[i].data += (void *)net - (void *)&init_net;
}
net->smc.smc_hdr = register_net_sysctl_sz(net, "net/smc", table,
diff --git a/net/unix/sysctl_net_unix.c b/net/unix/sysctl_net_unix.c
index e02ed6e3955c06b60cf4afb02656df8956f075ba..47660d5726bbd7d812762f4feffa9a0a42499d7d 100644
--- a/net/unix/sysctl_net_unix.c
+++ b/net/unix/sysctl_net_unix.c
@@ -13,7 +13,7 @@
#include "af_unix.h"
-static struct ctl_table unix_table[] = {
+static const struct ctl_table unix_table[] = {
{
.procname = "max_dgram_qlen",
.data = &init_net.unx.sysctl_max_dgram_qlen,
@@ -23,18 +23,29 @@ static struct ctl_table unix_table[] = {
},
};
-int __net_init unix_sysctl_register(struct net *net)
+static const struct ctl_table *unix_table_dup(struct net *net)
{
struct ctl_table *table;
+ table = kmemdup(unix_table, sizeof(unix_table), GFP_KERNEL);
+ if (!table)
+ return NULL;
+
+ table[0].data = &net->unx.sysctl_max_dgram_qlen;
+
+ return table;
+}
+
+int __net_init unix_sysctl_register(struct net *net)
+{
+ const struct ctl_table *table;
+
if (net_eq(net, &init_net)) {
table = unix_table;
} else {
- table = kmemdup(unix_table, sizeof(unix_table), GFP_KERNEL);
+ table = unix_table_dup(net);
if (!table)
goto err_alloc;
-
- table[0].data = &net->unx.sysctl_max_dgram_qlen;
}
net->unx.ctl = register_net_sysctl_sz(net, "net/unix", table,
diff --git a/net/vmw_vsock/af_vsock.c b/net/vmw_vsock/af_vsock.c
index 622dbd0467994428f1a590f559b78d8c17f6ba60..caebef73ea58d2b6043ca3fe3b6872f92fbe9fa6 100644
--- a/net/vmw_vsock/af_vsock.c
+++ b/net/vmw_vsock/af_vsock.c
@@ -2899,7 +2899,7 @@ static int vsock_net_child_mode_string(const struct ctl_table *table, int write,
return 0;
}
-static struct ctl_table vsock_table[] = {
+static const struct ctl_table vsock_table[] = {
{
.procname = "ns_mode",
.data = &init_net.vsock.mode,
@@ -2925,20 +2925,31 @@ static struct ctl_table vsock_table[] = {
},
};
-static int __net_init vsock_sysctl_register(struct net *net)
+static const struct ctl_table *vsock_table_dup(struct net *net)
{
struct ctl_table *table;
+ table = kmemdup(vsock_table, sizeof(vsock_table), GFP_KERNEL);
+ if (!table)
+ return NULL;
+
+ table[0].data = &net->vsock.mode;
+ table[1].data = &net->vsock.child_ns_mode;
+ table[2].data = &net->vsock.g2h_fallback;
+
+ return table;
+}
+
+static int __net_init vsock_sysctl_register(struct net *net)
+{
+ const struct ctl_table *table;
+
if (net_eq(net, &init_net)) {
table = vsock_table;
} else {
- table = kmemdup(vsock_table, sizeof(vsock_table), GFP_KERNEL);
+ table = vsock_table_dup(net);
if (!table)
goto err_alloc;
-
- table[0].data = &net->vsock.mode;
- table[1].data = &net->vsock.child_ns_mode;
- table[2].data = &net->vsock.g2h_fallback;
}
net->vsock.sysctl_hdr = register_net_sysctl_sz(net, "net/vsock", table,
--
2.50.1
^ permalink raw reply related
* [PATCH RFC net-next v3 0/3] net: sysctl: Const Qualify sysctl ctl_table arrays
From: Joel Granados @ 2026-07-13 11:07 UTC (permalink / raw)
To: David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman, David Ahern, Ido Schimmel, Pablo Neira Ayuso,
Florian Westphal, Phil Sutter, Marcelo Ricardo Leitner, Xin Long,
Steffen Klassert, Herbert Xu, D. Wythe, Dust Li, Sidraya Jayagond,
Wenjia Zhang, Mahanta Jambigi, Tony Lu, Wen Gu, Kuniyuki Iwashima,
Stefano Garzarella
Cc: netdev, linux-kernel, netfilter-devel, coreteam, linux-sctp,
linux-rdma, linux-s390, virtualization, Joel Granados
What?
=====
We do two things:
1. Reject netns-unsafe: Replace warning and file permission change with
an error (reject registration) when an "unsafe" net sysctl
registration is detected.
2. Const qualify: Const qualify network templated ctl_table arrays and
unconditional kmemdup'ed ctl_table arrays.
Why?
====
The main motivation for this is to continue with the const qualification
of the ctl_table arrays [1]. The permission change inside
ensure_safe_net_sysctl disallows cons qualifiaction as it basically
modifies the entries before running the sysctl registration.
ent->mode &= ~0222;
On reject netns-unsafe?
=======================
* I believe that there is currently now way that the permission change
gets executed [2]
* I found one case where the warning message was posted to lore
(vsock_sysctl_register) [3], but it made its to mainline as part of
the second case in [2].
* We should error anyway because writing to the global sysctl value
through a child netns is indicative of a bug [4].
On Const qualification?
=======================
We can separate the places where network registers sysctl tables into
three groups:
1. Static global: The unchanged global static arrays are passed along to
sysctl register.
2. Always kmemdup: The global static arrays are always kmemdup'ed before
passing them along to sysctl register.
3. Dynamic global: The global static array is changed in place before
passing it along to sysctl register.
This series handles case 1 and 2. It leaves 3 for a later point as
const qualifying those global ctl_tables is more involved.
RFC
===
Keeping the RFC tag for now in hope of any preliminary feedback. I would
be very thankful if you point me to anything that I have missed in my
analysis that shows that this cannot/shouldn't be done.
Changes in v3:
- Const qualified 2 of the 3 cases within the net directory ctl_table
register sites.
- Link to v2: https://lore.kernel.org/r/20260707-jag-net_const_qualify-v2-1-5a5c52031ead@kernel.org
Changes in v2:
- Rebased on top of net-next
- Updated subject to "RFC net-next"
- Link to v1: https://lore.kernel.org/r/20260629-jag-net_const_qualify-v1-1-ee98b8fc400c@kernel.org
Best
[1]
https://git.kernel.org/pub/scm/linux/kernel/git/sysctl/sysctl.git/commit/?h=constfy-sysctl-6.14-rc1&id=1751f872cc97f992ed5c4c72c55588db1f0021e1
[2]
I have identified 4 contexts relevant to the ensure_safe_net_sysctl call
inside the network sysctl registration.
1. When the (struct net) == &init_net (like in iw_cm_init): In this case
ensure_safe_net_sysctl is not executed and permission modification
never happens.
2. When the ctl_table data (->data) gets "manually" assigned to
something other init_net (like in vsock_sysctl_register): In this
case ensure_safe_net_sysctl *is* executed but the data that is passed
is neither a module address (!is_module_address) nor a kernel core
address (!is_kernel_core_data); so the permission modification never
happens.
3. When the permissions are explicitly changed on a kmemdup'ed ctl_table
array (like in sysctl_core_net_init): in this case
ensure_safe_net_sysctl *is* executed but the permission modification
never happens as the mode is not writable.
4. When ctl have custom proc_handlers (like in nf_lwtunnel_net_init): In
this case ->data is NULL so it is not a module address
(!is_module_address) nor a kernel core address
(!is_kernel_core_data), so permission modification never happens.
It seems like there is no way of executing the permission change in
ensure_safe_net_sysctl. Please correct me if this is inaccurate and help
me find the case that I missed.
[3]
https://lore.kernel.org/all/20260302194926.90378-1-graf@amazon.com/
[4]
The ensure_safe_net_sysctl function was introduced in Commit:
31c4d2f160eb7b17cbead24dc6efed06505a3fee ("net: Ensure net namespace
isolation of sysctls") which states that it is trying to prevent a
leak (indicative of a bug).
---
Signed-off-by: Joel Granados <joel.granados@kernel.org>
---
Joel Granados (3):
net: enforce net sysctl registration
net: Const qualify ctl_tables that kmemdup unconditionally
net: Const qualify network templated ctl_tables Arrays
include/net/net_namespace.h | 4 +--
net/core/sysctl_net_core.c | 38 +++++++++++++++--------
net/ipv4/devinet.c | 2 +-
net/ipv4/sysctl_net_ipv4.c | 54 +++++++++++++++++++--------------
net/ipv4/xfrm4_policy.c | 22 +++++++++++---
net/ipv6/icmp.c | 2 +-
net/ipv6/route.c | 2 +-
net/ipv6/sysctl_net_ipv6.c | 2 +-
net/ipv6/xfrm6_policy.c | 22 +++++++++++---
net/netfilter/nf_conntrack_standalone.c | 2 +-
net/netfilter/nf_hooks_lwtunnel.c | 4 +--
net/sctp/sysctl.c | 2 +-
net/smc/smc_sysctl.c | 26 +++++++++++-----
net/sysctl_net.c | 24 +++++++--------
net/unix/sysctl_net_unix.c | 21 ++++++++++---
net/vmw_vsock/af_vsock.c | 25 ++++++++++-----
net/xfrm/xfrm_sysctl.c | 2 +-
17 files changed, 167 insertions(+), 87 deletions(-)
---
base-commit: 474cff6868129755cf889edf40d7f491729fc588
change-id: 20260629-jag-net_const_qualify-f4e09759dac7
Best regards,
--
Joel Granados <joel.granados@kernel.org>
^ permalink raw reply
* Re: [PATCH net v4 1/3] octeon_ep: fix skb frags overflow in the RX path
From: Simon Horman @ 2026-07-13 11:06 UTC (permalink / raw)
To: Maoyi Xie
Cc: Veerasenareddy Burru, Sathesh Edara, Satananda Burla,
Shinas Rasheed, Andrew Lunn, David S . Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Maciej Fijalkowski, Guangshuo Li,
David Carlier, netdev, linux-kernel
In-Reply-To: <20260706150208.2944898-2-maoyixie.tju@gmail.com>
On Mon, Jul 06, 2026 at 11:02:06PM +0800, Maoyi Xie wrote:
> __octep_oq_process_rx() builds an skb for a multi-buffer packet by adding
> one fragment per buffer_size chunk:
>
> data_len = buff_info->len - oq->max_single_buffer_size;
> while (data_len) {
> ...
> skb_add_rx_frag(skb, shinfo->nr_frags, buff_info->page, 0,
> buff_info->len, buff_info->len);
> ...
> }
>
> buff_info->len comes from the device response header
> (be64_to_cpu(resp_hw->length)). Nothing bounds the fragment count against
> MAX_SKB_FRAGS. data_len can be close to 65535. buffer_size defaults to
> about 3776 on 4K pages, so a full packet yields about 18 fragments. That
> is one more than the default MAX_SKB_FRAGS of 17, so skb_add_rx_frag()
> writes past shinfo->frags[].
>
> The fragment count is now checked before build_skb(). A packet that needs
> more fragments than the skb can hold is dropped. octep_oq_drop_rx()
> consumes its descriptors like the build_skb failure path. The same class
> was fixed in other RX paths, including commit 5ffcb7b890f6 ("net: atlantic:
> fix fragment overflow handling in RX path") and commit f0813bcd2d9d ("net:
> wwan: t7xx: fix potential skb->frags overflow in RX path").
>
> Fixes: 37d79d059606 ("octeon_ep: add Tx/Rx processing and interrupt support")
> Co-developed-by: Kaixuan Li <kaixuan.li@ntu.edu.sg>
> Signed-off-by: Kaixuan Li <kaixuan.li@ntu.edu.sg>
> Signed-off-by: Maoyi Xie <maoyixie.tju@gmail.com>
> Reviewed-by: Maciej Fijalkowski <maciej.fijalkowski@intel.com>
> ---
> drivers/net/ethernet/marvell/octeon_ep/octep_rx.c | 9 +++++++++
> 1 file changed, 9 insertions(+)
>
> diff --git a/drivers/net/ethernet/marvell/octeon_ep/octep_rx.c b/drivers/net/ethernet/marvell/octeon_ep/octep_rx.c
> index e6ebc7e44a..bdbed58c7b 100644
> --- a/drivers/net/ethernet/marvell/octeon_ep/octep_rx.c
> +++ b/drivers/net/ethernet/marvell/octeon_ep/octep_rx.c
> @@ -453,6 +453,15 @@ static int __octep_oq_process_rx(struct octep_device *oct,
>
> octep_oq_next_pkt(oq, buff_info, &read_idx, &desc_used);
>
> + if (buff_info->len > oq->max_single_buffer_size) {
> + u16 data_len = buff_info->len - oq->max_single_buffer_size;
> +
> + if (DIV_ROUND_UP(data_len, oq->buffer_size) > MAX_SKB_FRAGS) {
> + octep_oq_drop_rx(oq, buff_info, &read_idx, &desc_used);
> + continue;
> + }
> + }
The AI-generated review of this patch on sashiko.dev flags that
this cannot occur for 4K pages. I believe this can be
addressed by changing the type of data_len to u32.
I also believe that a similar problem exists in patch 3/3.
"Does truncating the data_len calculation to u16 introduce a regression
by bypassing the MAX_SKB_FRAGS check?
If buff_info->len is unexpectedly large, the u16 truncation of data_len
could wrap around to a small value. This would bypass the check,
leaving unconsumed fragments in the ring that might be incorrectly
parsed as new packet headers."
> +
> skb = build_skb((void *)resp_hw, PAGE_SIZE);
> if (!skb) {
> octep_oq_drop_rx(oq, buff_info,
> --
> 2.34.1
>
^ permalink raw reply
* Re: [PATCH net] tcp: initialize standalone IPv4 ACK options
From: Yizhou Zhao @ 2026-07-13 10:59 UTC (permalink / raw)
To: Eric Dumazet
Cc: netdev, Neal Cardwell, Kuniyuki Iwashima, David S. Miller,
Jakub Kicinski, Paolo Abeni, Simon Horman, linux-kernel,
Yuxiang Yang, Ao Wang, Xuewei Feng, Qi Li, Ke Xu, stable
In-Reply-To: <CANn89i+jA5kPcZrjXfsY1ic_LjeEwPHi-U54kYmZdkBKHB+vTA@mail.gmail.com>
Thanks for your review.
> On Jul 13, 2026, at 16:48, Eric Dumazet <edumazet@google.com> wrote:
>
> On Mon, Jul 13, 2026 at 10:18 AM Yizhou Zhao
> <zhaoyz24@mails.tsinghua.edu.cn> wrote:
>>
>> tcp_v4_send_ack() constructs standalone IPv4 TCP ACK replies on the stack
>> for SYN-RECV and TIME-WAIT paths. It currently zeroes only the TCP
>> header, not the accompanying option buffer.
>>
>> TCP-AO options may have actual lengths that are not 4-byte aligned, while
>> the transmitted TCP header length is correctly rounded up to a 4-byte
>> boundary. tcp_ao_hash_hdr() writes only the MAC bytes, leaving the
>> TCP-AO option alignment padding in rep.opt uninitialized. With stack
>> auto-initialization disabled, those padding bytes can be copied into the
>> network packet and sent to the peer.
>>
>> Zero the whole reply structure before writing options, so the alignment
>> padding bytes are initialized.
>
> Please fix TCP-AO instead of slowing down TCP (almost no TCP flow is using AO)
We have posted a v2 patch following your suggestions:
https://lore.kernel.org/netdev/20260713105631.8616-1-zhaoyz24@mails.tsinghua.edu.cn/
Regards,
Yizhou
^ permalink raw reply
* [PATCH net v2] tcp: initialize standalone TCP-AO response padding
From: Yizhou Zhao @ 2026-07-13 10:56 UTC (permalink / raw)
To: netdev
Cc: Yizhou Zhao, Eric Dumazet, Neal Cardwell, Kuniyuki Iwashima,
David S. Miller, Jakub Kicinski, Paolo Abeni, Simon Horman,
linux-kernel, Yuxiang Yang, Ao Wang, Xuewei Feng, Qi Li, Ke Xu,
stable
tcp_v4_send_ack() and tcp_v6_send_response() construct standalone TCP
responses with TCP-AO options. The option length carries the actual MAC
length, but the TCP header length includes the option rounded up to a
four-byte boundary.
tcp_ao_hash_hdr() writes the MAC only. Thus, when the MAC length is not
four-byte aligned, the one to three bytes after the MAC are left
uninitialized and may be transmitted. For the normal TCP-AO hashing
mode, those bytes also have to be initialized before computing the MAC.
Initialize only the alignment padding in the TCP-AO branches, before
hashing the header. Use TCPOPT_NOP, as in the normal TCP-AO output path.
This avoids adding work to non-AO TCP responses while preserving a valid
authenticated header.
Fixes: decde2586b34 ("net/tcp: Add TCP-AO sign to twsk")
Fixes: da7dfaa6d6f7 ("net/tcp: Consistently align TCP-AO option in the header")
Cc: stable@vger.kernel.org
Reported-by: Yizhou Zhao <zhaoyz24@mails.tsinghua.edu.cn>
Reported-by: Yuxiang Yang <yangyx22@mails.tsinghua.edu.cn>
Reported-by: Ao Wang <wangao@seu.edu.cn>
Reported-by: Xuewei Feng <fengxw06@126.com>
Reported-by: Qi Li <qli01@tsinghua.edu.cn>
Reported-by: Ke Xu <xuke@tsinghua.edu.cn>
Assisted-by: Claude-Code:GLM-5.2-special
Suggested-by: Eric Dumazet <edumazet@google.com>
Signed-off-by: Yizhou Zhao <zhaoyz24@mails.tsinghua.edu.cn>
---
Changes in v2:
- Fix TCP-AO path only to avoid slowing down other TCP paths, suggested
by Eric.
- Fix the IPv6 path either.
- Link to v1: https://lore.kernel.org/netdev/20260713081842.3119-1-zhaoyz24@mails.tsinghua.edu.cn/
---
net/ipv4/tcp_ipv4.c | 3 +++
net/ipv6/tcp_ipv6.c | 2 ++
2 files changed, 5 insertions(+)
diff --git a/net/ipv4/tcp_ipv4.c b/net/ipv4/tcp_ipv4.c
index 209ef7522508..2f6ff630a0e5 100644
--- a/net/ipv4/tcp_ipv4.c
+++ b/net/ipv4/tcp_ipv4.c
@@ -971,6 +971,9 @@ static void tcp_v4_send_ack(const struct sock *sk,
key->rcv_next);
arg.iov[0].iov_len += tcp_ao_len_aligned(key->ao_key);
rep.th.doff = arg.iov[0].iov_len / 4;
+ memset((u8 *)&rep.opt[offset] + tcp_ao_maclen(key->ao_key),
+ TCPOPT_NOP, tcp_ao_len_aligned(key->ao_key) -
+ tcp_ao_len(key->ao_key));
tcp_ao_hash_hdr(AF_INET, (char *)&rep.opt[offset],
key->ao_key, key->traffic_key,
diff --git a/net/ipv6/tcp_ipv6.c b/net/ipv6/tcp_ipv6.c
index ebe161d72fbd..0bc89014653d 100644
--- a/net/ipv6/tcp_ipv6.c
+++ b/net/ipv6/tcp_ipv6.c
@@ -923,6 +923,8 @@ static void tcp_v6_send_response(const struct sock *sk, struct sk_buff *skb, u32
(tcp_ao_len(key->ao_key) << 16) |
(key->ao_key->sndid << 8) |
(key->rcv_next));
+ memset((u8 *)topt + tcp_ao_maclen(key->ao_key), TCPOPT_NOP,
+ tcp_ao_len_aligned(key->ao_key) - tcp_ao_len(key->ao_key));
tcp_ao_hash_hdr(AF_INET6, (char *)topt, key->ao_key,
key->traffic_key,
--
2.47.3
^ permalink raw reply related
* [PATCH net] net/x25: fix use-after-free in x25_kill_by_neigh()
From: David Lee @ 2026-07-13 10:47 UTC (permalink / raw)
To: Martin Schiller
Cc: David Lee, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Simon Horman,
Dominik 'Disconnect3d' Czarnota, linux-x25, netdev,
linux-kernel, stable
x25_kill_by_neigh() walks the global X.25 socket list looking for sockets
attached to a terminating neighbour. x25_list_lock protects list membership
while the lookup is in progress, but it does not pin a socket's lifetime
after the lock is dropped.
The function currently drops x25_list_lock before calling lock_sock(s). A
concurrent close can run x25_release(), remove the same socket from
x25_list, and drop the last socket reference in that window. The neighbour
teardown path can then lock or inspect a freed struct sock/struct x25_sock.
Take sock_hold(s) while x25_list_lock still proves that the list entry is
live, then drop the temporary reference after the socket has been locked,
rechecked, and released. Recheck x25_sk(s)->neighbour after lock_sock(),
because another path may have disconnected the socket before this path
acquired the socket lock. Restart the list walk after each disconnect
because the list lock was dropped and the previous iterator state may no
longer be valid.
A QEMU/KASAN run against origin/master reproduced a slab-use-after-free in
x25_kill_by_neigh().
Fixes: 7781607938c8 ("net/x25: Fix null-ptr-deref caused by x25_disconnect")
Cc: stable@vger.kernel.org
Signed-off-by: David Lee <david.lee@trailofbits.com>
Assisted-by: Codex:gpt-5.5
---
Trail of Bits has a reproducer that triggers kernel panic demonstrating the bug, and can share it if needed.
net/x25/af_x25.c | 8 ++++++--
1 file changed, 6 insertions(+), 2 deletions(-)
diff --git a/net/x25/af_x25.c b/net/x25/af_x25.c
index c31d2af5dd22..8aae9273b7c1 100644
--- a/net/x25/af_x25.c
+++ b/net/x25/af_x25.c
@@ -1768,15 +1768,19 @@ void x25_kill_by_neigh(struct x25_neigh *nb)
{
struct sock *s;
+again:
write_lock_bh(&x25_list_lock);
sk_for_each(s, &x25_list) {
if (x25_sk(s)->neighbour == nb) {
+ sock_hold(s);
write_unlock_bh(&x25_list_lock);
lock_sock(s);
- x25_disconnect(s, ENETUNREACH, 0, 0);
+ if (x25_sk(s)->neighbour == nb)
+ x25_disconnect(s, ENETUNREACH, 0, 0);
release_sock(s);
- write_lock_bh(&x25_list_lock);
+ sock_put(s);
+ goto again;
}
}
write_unlock_bh(&x25_list_lock);
^ permalink raw reply related
* Re: [PATCH net 7/7] selftests: netconsole: ignore busywait errors
From: Breno Leitao @ 2026-07-13 10:41 UTC (permalink / raw)
To: Matthieu Baerts (NGI0)
Cc: David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman, Shuah Khan, netdev, linux-kselftest, linux-kernel,
Andrew Lunn, Andre Carvalho
In-Reply-To: <20260710-net-sft-fix-containers-v1-7-a2915c294ef5@kernel.org>
On Fri, Jul 10, 2026 at 08:04:47PM +0200, Matthieu Baerts (NGI0) wrote:
> In these netconsole tests, bash is used with errexit (set -e). It means
> that if the busywait timeout, the tests finish without printing an error
> message.
>
> It is fine to ignore these errors, because the following validate_xxx
> helpers will check the content of the output file, and exit with an
> appropriated error message, e.g. FAIL: File was not generated.
Agreed, the validate functions will catch this and report it more
appropriately.
> Signed-off-by: Matthieu Baerts (NGI0) <matttbe@kernel.org>
Reviewed-by: Breno Leitao <leitao@debian.org>
^ permalink raw reply
* [PATCH net-next v2 1/2] dpll: zl3073x: add channel ToD, phase step and TIE operations
From: Ivan Vecera @ 2026-07-13 10:37 UTC (permalink / raw)
To: netdev
Cc: Chris du Quesnay, Arkadiusz Kubalewski, David S. Miller,
Jakub Kicinski, Jiri Pirko, Michal Schmidt, Paolo Abeni,
Pasi Vaananen, Petr Oros, Prathosh Satish, Richard Cochran,
Simon Horman, Vadim Fedorenko, linux-kernel
In-Reply-To: <20260713103739.1553284-1-ivecera@redhat.com>
Add low-level DPLL channel operations for ToD read/write/adjust,
output phase step, delta frequency offset write and TIE (Time
Interval Error) write. These serve as building blocks for the PTP
clock callbacks added in the next patch.
ToD operations use a wait-before-write pattern to avoid blocking
after each operation. tod_adjust additionally waits for completion
since callers may follow with phase step operations.
The tod_ready_wait helper selects the poll timeout based on the
current ToD command - write operations use a longer timeout (1000 ms)
than reads (30 ms).
The ToD read captures system timestamps (ptp_system_timestamp) around
the HW command and completion poll to support cross-timestamping.
The TIE write operation provides sub-picosecond resolution phase
adjustment for modes where the DPLL is tracking a reference
(AUTO and REFLOCK).
Add output step-time mask invariant to zl3073x_chan and
zl3073x_chan_is_out_stepped() helper to check if an output
participates in step-time operations.
Tested-by: Chris du Quesnay <Chris.duQuesnay@microchip.com>
Signed-off-by: Ivan Vecera <ivecera@redhat.com>
---
drivers/dpll/zl3073x/chan.c | 307 ++++++++++++++++++++++++++++++++++++
drivers/dpll/zl3073x/chan.h | 48 ++++++
drivers/dpll/zl3073x/core.c | 8 +
drivers/dpll/zl3073x/core.h | 8 +
drivers/dpll/zl3073x/regs.h | 56 +++++++
5 files changed, 427 insertions(+)
diff --git a/drivers/dpll/zl3073x/chan.c b/drivers/dpll/zl3073x/chan.c
index 4ec2cf53dad46..53032e782fd4a 100644
--- a/drivers/dpll/zl3073x/chan.c
+++ b/drivers/dpll/zl3073x/chan.c
@@ -3,6 +3,7 @@
#include <linux/cleanup.h>
#include <linux/delay.h>
#include <linux/dev_printk.h>
+#include <linux/ptp_clock_kernel.h>
#include <linux/string.h>
#include <linux/types.h>
@@ -202,6 +203,11 @@ int zl3073x_chan_state_fetch(struct zl3073x_dev *zldev, u8 index)
zl3073x_chan_refsel_state_get(chan),
zl3073x_chan_refsel_ref_get(chan));
+ rc = zl3073x_read_u16(zldev, ZL_REG_OUTPUT_STEP_TIME_MASK,
+ &chan->out_step_time_mask);
+ if (rc)
+ return rc;
+
guard(mutex)(&zldev->multiop_lock);
/* Read DPLL configuration from mailbox */
@@ -234,6 +240,307 @@ const struct zl3073x_chan *zl3073x_chan_state_get(struct zl3073x_dev *zldev,
return &zldev->chan[index];
}
+/**
+ * zl3073x_chan_tod_ready_wait - wait for ToD semaphore to clear
+ * @zldev: pointer to zl3073x device
+ * @ch: DPLL channel index
+ *
+ * Polls the ToD control register until the semaphore bit is cleared,
+ * indicating the device has completed the previous ToD operation.
+ *
+ * Return: 0 on success, -EBUSY if semaphore not cleared, <0 on error
+ */
+static int zl3073x_chan_tod_ready_wait(struct zl3073x_dev *zldev, u8 ch)
+{
+ unsigned int timeout;
+ u8 tod_ctrl;
+ int rc;
+
+ rc = zl3073x_read_u8(zldev, ZL_REG_DPLL_TOD_CTRL(ch), &tod_ctrl);
+ if (rc)
+ return rc;
+
+ switch (FIELD_GET(ZL_DPLL_TOD_CTRL_CMD, tod_ctrl)) {
+ case ZL_DPLL_TOD_CTRL_CMD_WR_NEXT_1HZ:
+ timeout = ZL_POLL_TOD_WR_TIMEOUT_US;
+ break;
+ default:
+ timeout = ZL_POLL_TOD_RD_TIMEOUT_US;
+ break;
+ }
+
+ rc = zl3073x_poll_zero_u8(zldev, ZL_REG_DPLL_TOD_CTRL(ch),
+ ZL_DPLL_TOD_CTRL_SEM, timeout);
+
+ return rc == -ETIMEDOUT ? -EBUSY : rc;
+}
+
+/**
+ * zl3073x_chan_tod_ctrl - issue ToD command
+ * @zldev: pointer to zl3073x device
+ * @ch: DPLL channel index
+ * @cmd: ToD command to execute
+ *
+ * Writes the semaphore and command to dpll_tod_ctrl. The caller must
+ * ensure the device is ready (semaphore clear) before calling and
+ * must wait for completion if needed.
+ *
+ * Return: 0 on success, <0 on error
+ */
+static int zl3073x_chan_tod_ctrl(struct zl3073x_dev *zldev, u8 ch, u8 cmd)
+{
+ return zl3073x_write_u8(zldev, ZL_REG_DPLL_TOD_CTRL(ch),
+ ZL_DPLL_TOD_CTRL_SEM | cmd);
+}
+
+/**
+ * zl3073x_chan_tod_read - read ToD registers after issuing a command
+ * @zldev: pointer to zl3073x device
+ * @ch: DPLL channel index
+ * @next_hz: if true, read predicted ToD at next 1 Hz; otherwise read current
+ * @ts: timespec to store the result
+ * @sts: optional system timestamp pair for cross-timestamping
+ *
+ * Context: Caller must serialize all zl3073x_chan_tod_* calls externally.
+ * Return: 0 on success, <0 on error
+ */
+int zl3073x_chan_tod_read(struct zl3073x_dev *zldev, u8 ch,
+ bool next_hz, struct timespec64 *ts,
+ struct ptp_system_timestamp *sts)
+{
+ u32 nsec;
+ u64 sec;
+ u8 cmd;
+ int rc;
+
+ if (next_hz)
+ cmd = ZL_DPLL_TOD_CTRL_CMD_RD_NEXT_1HZ;
+ else
+ cmd = ZL_DPLL_TOD_CTRL_CMD_RD_CURRENT;
+
+ /* Wait for any previous ToD operation to complete */
+ rc = zl3073x_chan_tod_ready_wait(zldev, ch);
+ if (rc)
+ return rc;
+
+ ptp_read_system_prets(sts);
+ rc = zl3073x_chan_tod_ctrl(zldev, ch, cmd);
+ if (rc)
+ return rc;
+
+ rc = zl3073x_chan_tod_ready_wait(zldev, ch);
+ if (rc)
+ return rc;
+ ptp_read_system_postts(sts);
+
+ rc = zl3073x_read_u48(zldev, ZL_REG_DPLL_TOD_SEC(ch), &sec);
+ if (rc)
+ return rc;
+
+ /* HW nanoseconds are always in [0, NSEC_PER_SEC) range */
+ rc = zl3073x_read_u32(zldev, ZL_REG_DPLL_TOD_NS(ch), &nsec);
+ if (rc)
+ return rc;
+
+ ts->tv_sec = sec;
+ ts->tv_nsec = nsec;
+
+ return 0;
+}
+
+/**
+ * zl3073x_chan_tod_write - write ToD registers and trigger 1 Hz update
+ * @zldev: pointer to zl3073x device
+ * @ch: DPLL channel index
+ * @ts: time to set
+ *
+ * Context: Caller must serialize all zl3073x_chan_tod_* calls externally.
+ * Return: 0 on success, <0 on error
+ */
+int zl3073x_chan_tod_write(struct zl3073x_dev *zldev, u8 ch,
+ struct timespec64 ts)
+{
+ int rc;
+
+ /* Wait for any previous ToD operation to complete */
+ rc = zl3073x_chan_tod_ready_wait(zldev, ch);
+ if (rc)
+ return rc;
+
+ rc = zl3073x_write_u48(zldev, ZL_REG_DPLL_TOD_SEC(ch), ts.tv_sec);
+ if (rc)
+ return rc;
+
+ rc = zl3073x_write_u32(zldev, ZL_REG_DPLL_TOD_NS(ch), ts.tv_nsec);
+ if (rc)
+ return rc;
+
+ return zl3073x_chan_tod_ctrl(zldev, ch,
+ ZL_DPLL_TOD_CTRL_CMD_WR_NEXT_1HZ);
+}
+
+/**
+ * zl3073x_chan_tod_adjust - atomic ToD read-modify-write with rollover guard
+ * @zldev: pointer to zl3073x device
+ * @ch: DPLL channel index
+ * @delta: time adjustment to apply
+ *
+ * Reads the next-Hz ToD and current ToD, then checks whether enough time
+ * remains before the next 1 Hz rollover to safely complete the write.
+ * If less than 20 ms remains, waits for the rollover and increments the
+ * next-Hz seconds by one. Applies @delta and writes the result back.
+ *
+ * Context: Caller must serialize all zl3073x_chan_tod_* calls externally.
+ * Return: 0 on success, <0 on error
+ */
+int zl3073x_chan_tod_adjust(struct zl3073x_dev *zldev, u8 ch,
+ struct timespec64 delta)
+{
+ static const long threshold_ns = 20 * NSEC_PER_MSEC;
+ struct timespec64 ts_next, ts_cur, diff;
+ int rc;
+
+ /* Read predicted ToD at next 1 Hz tick */
+ rc = zl3073x_chan_tod_read(zldev, ch, true, &ts_next, NULL);
+ if (rc)
+ return rc;
+
+ /* Read current ToD to determine remaining margin */
+ rc = zl3073x_chan_tod_read(zldev, ch, false, &ts_cur, NULL);
+ if (rc)
+ return rc;
+
+ /* If too close to (or past) the next rollover, wait it out */
+ diff = timespec64_sub(ts_next, ts_cur);
+ if (diff.tv_sec < 0 || (!diff.tv_sec && diff.tv_nsec < threshold_ns)) {
+ if (!diff.tv_sec && diff.tv_nsec)
+ fsleep((unsigned long)diff.tv_nsec / NSEC_PER_USEC + 1);
+ ts_next.tv_sec++;
+ }
+
+ /* Apply delta to the next-Hz ToD */
+ ts_next = timespec64_add(ts_next, delta);
+ if (!timespec64_valid(&ts_next))
+ return -EINVAL;
+
+ /* Write adjusted ToD back and wait for completion */
+ rc = zl3073x_chan_tod_write(zldev, ch, ts_next);
+ if (rc)
+ return rc;
+
+ return zl3073x_chan_tod_ready_wait(zldev, ch);
+}
+
+/**
+ * zl3073x_chan_df_offset_set - write delta frequency offset to hardware
+ * @zldev: pointer to zl3073x device
+ * @ch: DPLL channel index
+ * @offset: frequency offset in 2^-48 steps
+ *
+ * Return: 0 on success, <0 on error
+ */
+int zl3073x_chan_df_offset_set(struct zl3073x_dev *zldev, u8 ch, s64 offset)
+{
+ int rc;
+
+ rc = zl3073x_write_u48(zldev, ZL_REG_DPLL_DF_OFFSET(ch), offset);
+ if (!rc)
+ zldev->chan[ch].df_offset = offset;
+
+ return rc;
+}
+
+/**
+ * zl3073x_chan_tie_write - adjust DPLL phase using TIE write
+ * @zldev: pointer to zl3073x device
+ * @ch: DPLL channel index
+ * @delta_ns: phase adjustment in nanoseconds (must be within +-1s)
+ *
+ * Converts nanoseconds to TIE units (0.01 ps) and writes TIE data
+ * to the specified channel.
+ *
+ * Return: 0 on success, <0 on error
+ */
+int zl3073x_chan_tie_write(struct zl3073x_dev *zldev, u8 ch, s64 delta_ns)
+{
+ s64 tie_data;
+ int rc;
+
+ guard(mutex)(&zldev->tie_lock);
+
+ /* Wait for any previous TIE operation to complete */
+ rc = zl3073x_poll_zero_u8(zldev, ZL_REG_DPLL_TIE_CTRL,
+ ZL_DPLL_TIE_CTRL_OP,
+ ZL_POLL_TIE_WR_TIMEOUT_US);
+ if (rc)
+ return rc;
+
+ /* Convert ns to TIE units (0.01 ps = 10^-14 s) */
+ tie_data = delta_ns * 100000LL;
+
+ rc = zl3073x_write_u48(zldev, ZL_REG_DPLL_TIE_DATA(ch), tie_data);
+ if (rc)
+ return rc;
+
+ rc = zl3073x_write_u8(zldev, ZL_REG_DPLL_TIE_CTRL_MASK, BIT(ch));
+ if (rc)
+ return rc;
+
+ return zl3073x_write_u8(zldev, ZL_REG_DPLL_TIE_CTRL,
+ ZL_DPLL_TIE_CTRL_OP_WR);
+}
+
+/**
+ * zl3073x_chan_phase_step - execute one output phase step operation
+ * @zldev: pointer to zl3073x device
+ * @ch: DPLL channel index
+ * @out_mask: bitmask of outputs to step
+ * @step_cycles: phase step in synthesizer clock cycles
+ * @tod_step: also step the ToD counter
+ *
+ * All masked outputs must use synthesizers of the same frequency since
+ * the step value is in synthesizer clock cycles.
+ *
+ * Return: 0 on success, <0 on error
+ */
+int zl3073x_chan_phase_step(struct zl3073x_dev *zldev, u8 ch,
+ u16 out_mask, s32 step_cycles,
+ bool tod_step)
+{
+ u8 ctrl;
+ int rc;
+
+ guard(mutex)(&zldev->phase_step_lock);
+
+ /* Wait for any previous phase step operation to complete */
+ rc = zl3073x_poll_zero_u8(zldev, ZL_REG_OUTPUT_PHASE_STEP_CTRL,
+ ZL_OUTPUT_PHASE_STEP_CTRL_OP,
+ ZL_POLL_PHASE_STEP_TIMEOUT_US);
+ if (rc)
+ return rc;
+
+ rc = zl3073x_write_u32(zldev, ZL_REG_OUTPUT_PHASE_STEP_DATA,
+ step_cycles);
+ if (rc)
+ return rc;
+
+ rc = zl3073x_write_u16(zldev, ZL_REG_OUTPUT_PHASE_STEP_MASK, out_mask);
+ if (rc)
+ return rc;
+
+ rc = zl3073x_write_u8(zldev, ZL_REG_OUTPUT_PHASE_STEP_NUMBER, 1);
+ if (rc)
+ return rc;
+
+ ctrl = FIELD_PREP(ZL_OUTPUT_PHASE_STEP_CTRL_DPLL, ch) |
+ FIELD_PREP(ZL_OUTPUT_PHASE_STEP_CTRL_OP,
+ ZL_OUTPUT_PHASE_STEP_CTRL_OP_WRITE);
+ if (tod_step)
+ ctrl |= ZL_OUTPUT_PHASE_STEP_CTRL_TOD_STEP;
+
+ return zl3073x_write_u8(zldev, ZL_REG_OUTPUT_PHASE_STEP_CTRL, ctrl);
+}
+
/**
* zl3073x_chan_state_set - commit DPLL channel state changes to hardware
* @zldev: pointer to zl3073x_dev structure
diff --git a/drivers/dpll/zl3073x/chan.h b/drivers/dpll/zl3073x/chan.h
index dc9c6d95bdee7..9dc5a3f1991f1 100644
--- a/drivers/dpll/zl3073x/chan.h
+++ b/drivers/dpll/zl3073x/chan.h
@@ -5,10 +5,12 @@
#include <linux/bitfield.h>
#include <linux/stddef.h>
+#include <linux/time64.h>
#include <linux/types.h>
#include "regs.h"
+struct ptp_system_timestamp;
struct zl3073x_dev;
/**
@@ -16,6 +18,7 @@ struct zl3073x_dev;
* @ctrl: DPLL control register value
* @mode_refsel: mode and reference selection register value
* @ref_prio: reference priority registers (4 bits per ref, P/N packed)
+ * @out_step_time_mask: output step-time mask
* @mon_status: monitor status register value
* @refsel_status: reference selection status register value
* @df_offset: frequency offset vs tracked reference in 2^-48 steps
@@ -26,6 +29,9 @@ struct zl3073x_chan {
u8 mode_refsel;
u8 ref_prio[ZL3073X_NUM_REFS / 2];
);
+ struct_group(inv, /* Invariants */
+ u16 out_step_time_mask;
+ );
struct_group(stat,
u8 mon_status;
u8 refsel_status;
@@ -42,6 +48,20 @@ int zl3073x_chan_state_set(struct zl3073x_dev *zldev, u8 index,
int zl3073x_chan_state_update(struct zl3073x_dev *zldev, u8 index);
int zl3073x_chan_nco_mode_set(struct zl3073x_dev *zldev, u8 index);
+int zl3073x_chan_tod_read(struct zl3073x_dev *zldev, u8 ch,
+ bool next_hz, struct timespec64 *ts,
+ struct ptp_system_timestamp *sts);
+int zl3073x_chan_tod_write(struct zl3073x_dev *zldev, u8 ch,
+ struct timespec64 ts);
+int zl3073x_chan_tod_adjust(struct zl3073x_dev *zldev, u8 ch,
+ struct timespec64 delta);
+int zl3073x_chan_phase_step(struct zl3073x_dev *zldev, u8 ch,
+ u16 out_mask, s32 step_cycles, bool tod_step);
+
+int zl3073x_chan_df_offset_set(struct zl3073x_dev *zldev, u8 ch, s64 offset);
+
+int zl3073x_chan_tie_write(struct zl3073x_dev *zldev, u8 ch, s64 delta_ns);
+
/**
* zl3073x_chan_df_offset_get - get cached df_offset vs tracked reference
* @chan: pointer to channel state
@@ -200,6 +220,21 @@ static inline bool zl3073x_chan_mode_is_reflock(const struct zl3073x_chan *chan)
return zl3073x_chan_mode_get(chan) == ZL_DPLL_MODE_REFSEL_MODE_REFLOCK;
}
+/**
+ * zl3073x_chan_mode_supports_tie - check if channel mode supports TIE write
+ * @chan: pointer to channel state
+ *
+ * TIE write is supported in AUTO and REFLOCK modes regardless of lock state.
+ *
+ * Return: true if TIE write is supported, false otherwise
+ */
+static inline bool
+zl3073x_chan_mode_supports_tie(const struct zl3073x_chan *chan)
+{
+ return zl3073x_chan_mode_is_auto(chan) ||
+ zl3073x_chan_mode_is_reflock(chan);
+}
+
/**
* zl3073x_chan_is_ho_ready - check if holdover is ready
* @chan: pointer to channel state
@@ -233,4 +268,17 @@ static inline u8 zl3073x_chan_refsel_ref_get(const struct zl3073x_chan *chan)
return FIELD_GET(ZL_DPLL_REFSEL_STATUS_REFSEL, chan->refsel_status);
}
+/**
+ * zl3073x_chan_is_out_stepped - check if output is in step-time mask
+ * @chan: pointer to channel state
+ * @out: output index
+ *
+ * Return: true if output is affected by step-time operations
+ */
+static inline bool
+zl3073x_chan_is_out_stepped(const struct zl3073x_chan *chan, u8 out)
+{
+ return !!(chan->out_step_time_mask & BIT(out));
+}
+
#endif /* _ZL3073X_CHAN_H */
diff --git a/drivers/dpll/zl3073x/core.c b/drivers/dpll/zl3073x/core.c
index 7f5afaaae6342..b2f9fc97fd1b6 100644
--- a/drivers/dpll/zl3073x/core.c
+++ b/drivers/dpll/zl3073x/core.c
@@ -1033,6 +1033,14 @@ int zl3073x_dev_probe(struct zl3073x_dev *zldev)
* and/or polls are required to be done atomically.
*/
rc = devm_mutex_init(zldev->dev, &zldev->multiop_lock);
+ if (rc)
+ return dev_err_probe(zldev->dev, rc,
+ "Failed to initialize mutex\n");
+ rc = devm_mutex_init(zldev->dev, &zldev->phase_step_lock);
+ if (rc)
+ return dev_err_probe(zldev->dev, rc,
+ "Failed to initialize mutex\n");
+ rc = devm_mutex_init(zldev->dev, &zldev->tie_lock);
if (rc)
return dev_err_probe(zldev->dev, rc,
"Failed to initialize mutex\n");
diff --git a/drivers/dpll/zl3073x/core.h b/drivers/dpll/zl3073x/core.h
index 78dc208f3eea2..2dc836d0c8f6b 100644
--- a/drivers/dpll/zl3073x/core.h
+++ b/drivers/dpll/zl3073x/core.h
@@ -26,6 +26,10 @@ struct zl3073x_dpll;
#define ZL_POLL_HWREG_TIMEOUT_US (50 * USEC_PER_MSEC)
#define ZL_POLL_MB_TIMEOUT_US (30 * USEC_PER_MSEC)
#define ZL_POLL_PHASE_ERR_TIMEOUT_US (50 * USEC_PER_MSEC)
+#define ZL_POLL_PHASE_STEP_TIMEOUT_US (3000 * USEC_PER_MSEC)
+#define ZL_POLL_TIE_WR_TIMEOUT_US (1000 * USEC_PER_MSEC)
+#define ZL_POLL_TOD_RD_TIMEOUT_US (30 * USEC_PER_MSEC)
+#define ZL_POLL_TOD_WR_TIMEOUT_US (1000 * USEC_PER_MSEC)
enum zl3073x_flags {
ZL3073X_FLAG_REF_PHASE_COMP_32_BIT,
@@ -55,6 +59,8 @@ struct zl3073x_chip_info {
* @regmap: regmap to access device registers
* @info: detected chip info
* @multiop_lock: to serialize multiple register operations
+ * @tie_lock: to serialize TIE write operations
+ * @phase_step_lock: to serialize output phase step operations
* @ref: array of input references' invariants
* @out: array of outs' invariants
* @synth: array of synths' invariants
@@ -71,6 +77,8 @@ struct zl3073x_dev {
struct regmap *regmap;
const struct zl3073x_chip_info *info;
struct mutex multiop_lock;
+ struct mutex tie_lock;
+ struct mutex phase_step_lock;
/* Invariants */
struct zl3073x_ref ref[ZL3073X_NUM_REFS];
diff --git a/drivers/dpll/zl3073x/regs.h b/drivers/dpll/zl3073x/regs.h
index b70ead7d4495b..447757e99845d 100644
--- a/drivers/dpll/zl3073x/regs.h
+++ b/drivers/dpll/zl3073x/regs.h
@@ -179,6 +179,20 @@
#define ZL_DPLL_DF_READ_CMD GENMASK(2, 0)
#define ZL_DPLL_DF_READ_CMD_ACC_I 4
+#define ZL_REG_DPLL_TIE_CTRL ZL_REG(5, 0x30, 1)
+#define ZL_DPLL_TIE_CTRL_OP GENMASK(2, 0)
+#define ZL_DPLL_TIE_CTRL_OP_WR 4
+
+#define ZL_REG_DPLL_TIE_CTRL_MASK ZL_REG(5, 0x31, 1)
+
+#define ZL_REG_DPLL_TOD_CTRL(_idx) \
+ ZL_REG_IDX(_idx, 5, 0x38, 1, 8, 1)
+#define ZL_DPLL_TOD_CTRL_SEM BIT(4)
+#define ZL_DPLL_TOD_CTRL_CMD GENMASK(3, 0)
+#define ZL_DPLL_TOD_CTRL_CMD_WR_NEXT_1HZ 1
+#define ZL_DPLL_TOD_CTRL_CMD_RD_CURRENT 8
+#define ZL_DPLL_TOD_CTRL_CMD_RD_NEXT_1HZ 9
+
#define ZL_REG_DPLL_MEAS_CTRL ZL_REG(5, 0x50, 1)
#define ZL_DPLL_MEAS_CTRL_EN BIT(0)
#define ZL_DPLL_MEAS_CTRL_AVG_FACTOR GENMASK(7, 4)
@@ -193,6 +207,9 @@
/*******************************
* Register Pages 6-7, DPLL Data
+ *
+ * Per-channel registers with stride 0x20. Channels 0-3 reside on page 6,
+ * channel 4 on page 7.
*******************************/
#define ZL_REG_DPLL_DF_OFFSET_03(_idx) \
@@ -202,6 +219,24 @@
((_idx) < 4 ? ZL_REG_DPLL_DF_OFFSET_03(_idx) : ZL_REG_DPLL_DF_OFFSET_4)
#define ZL_DPLL_DF_OFFSET_UNKNOWN S64_MIN
+#define ZL_REG_DPLL_TIE_DATA_03(_idx) \
+ ZL_REG_IDX(_idx, 6, 0x0C, 6, 4, 0x20)
+#define ZL_REG_DPLL_TIE_DATA_4 ZL_REG(7, 0x0C, 6)
+#define ZL_REG_DPLL_TIE_DATA(_idx) \
+ ((_idx) < 4 ? ZL_REG_DPLL_TIE_DATA_03(_idx) : ZL_REG_DPLL_TIE_DATA_4)
+
+#define ZL_REG_DPLL_TOD_SEC_03(_idx) \
+ ZL_REG_IDX(_idx, 6, 0x12, 6, 4, 0x20)
+#define ZL_REG_DPLL_TOD_SEC_4 ZL_REG(7, 0x12, 6)
+#define ZL_REG_DPLL_TOD_SEC(_idx) \
+ ((_idx) < 4 ? ZL_REG_DPLL_TOD_SEC_03(_idx) : ZL_REG_DPLL_TOD_SEC_4)
+
+#define ZL_REG_DPLL_TOD_NS_03(_idx) \
+ ZL_REG_IDX(_idx, 6, 0x18, 4, 4, 0x20)
+#define ZL_REG_DPLL_TOD_NS_4 ZL_REG(7, 0x18, 4)
+#define ZL_REG_DPLL_TOD_NS(_idx) \
+ ((_idx) < 4 ? ZL_REG_DPLL_TOD_NS_03(_idx) : ZL_REG_DPLL_TOD_NS_4)
+
/***********************************
* Register Page 9, Synth and Output
***********************************/
@@ -221,6 +256,8 @@
#define ZL_OUTPUT_CTRL_EN BIT(0)
#define ZL_OUTPUT_CTRL_SYNTH_SEL GENMASK(6, 4)
+#define ZL_REG_OUTPUT_STEP_TIME_MASK ZL_REG(9, 0x36, 2)
+
/*******************************
* Register Page 10, Ref Mailbox
*******************************/
@@ -316,6 +353,25 @@
#define ZL_REG_OUTPUT_ESYNC_WIDTH ZL_REG(14, 0x18, 4)
#define ZL_REG_OUTPUT_PHASE_COMP ZL_REG(14, 0x20, 4)
+/***********************************
+ * Register Page 9, Output Phase Step
+ ***********************************/
+
+#define ZL_REG_OUTPUT_PHASE_STEP_CTRL ZL_REG(9, 0x38, 1)
+#define ZL_OUTPUT_PHASE_STEP_CTRL_DPLL GENMASK(6, 4)
+#define ZL_OUTPUT_PHASE_STEP_CTRL_TOD_STEP BIT(3)
+#define ZL_OUTPUT_PHASE_STEP_CTRL_OP GENMASK(1, 0)
+#define ZL_OUTPUT_PHASE_STEP_CTRL_OP_NONE 0
+#define ZL_OUTPUT_PHASE_STEP_CTRL_OP_RESET 1
+#define ZL_OUTPUT_PHASE_STEP_CTRL_OP_READ 2
+#define ZL_OUTPUT_PHASE_STEP_CTRL_OP_WRITE 3
+
+#define ZL_REG_OUTPUT_PHASE_STEP_NUMBER ZL_REG(9, 0x39, 1)
+
+#define ZL_REG_OUTPUT_PHASE_STEP_MASK ZL_REG(9, 0x3a, 2)
+
+#define ZL_REG_OUTPUT_PHASE_STEP_DATA ZL_REG(9, 0x3c, 4)
+
/*
* Register Page 255 - HW registers access
*/
--
2.53.0
^ permalink raw reply related
* [PATCH net-next v2 2/2] dpll: zl3073x: add PTP clock support
From: Ivan Vecera @ 2026-07-13 10:37 UTC (permalink / raw)
To: netdev
Cc: Chris du Quesnay, Arkadiusz Kubalewski, David S. Miller,
Jakub Kicinski, Jiri Pirko, Michal Schmidt, Paolo Abeni,
Pasi Vaananen, Petr Oros, Prathosh Satish, Richard Cochran,
Simon Horman, Vadim Fedorenko, linux-kernel
In-Reply-To: <20260713103739.1553284-1-ivecera@redhat.com>
Add PTP clock support for the ZL3073x DPLL driver. A PTP clock device
is registered for each DPLL channel regardless of the initial channel
state, providing gettimex64, settime64, adjtime, adjfine, adjphase,
getmaxphase and perout callbacks.
Callback availability depends on the current channel state:
- adjfine: when NCO pin is connected (returns -EOPNOTSUPP otherwise)
- adjphase: available when tracking a reference, uses TIE write
- adjtime: always available and uses
* phase step for sub-second deltas when NCO pin is connected
* TIE write when tracking a reference
* plain ToD read-modify-write otherwise
- gettime/settime: always available
The adjtime callback splits multi-second adjustments into a ToD
read-modify-write for the seconds part and a sub-second mechanism
(phase step or TIE write) for the remainder. On partial failure
where seconds were already committed, success is returned to
prevent the PTP servo from retrying and applying seconds again.
Output pins eligible for periodic output (single-ended, marked in
output_step_time_mask, supporting 1 Hz) are registered as PTP perout
channels. Enabling perout sets the output frequency to 1 Hz via the
existing DPLL frequency_set path.
All PTP callbacks are serialized by the existing per-DPLL zldpll->lock
mutex, which is also used by DPLL pin and device callbacks.
Tested-by: Chris du Quesnay <Chris.duQuesnay@microchip.com>
Signed-off-by: Ivan Vecera <ivecera@redhat.com>
---
drivers/dpll/zl3073x/Kconfig | 6 +-
drivers/dpll/zl3073x/dpll.c | 575 +++++++++++++++++++++++++++++++++--
drivers/dpll/zl3073x/dpll.h | 6 +
3 files changed, 551 insertions(+), 36 deletions(-)
diff --git a/drivers/dpll/zl3073x/Kconfig b/drivers/dpll/zl3073x/Kconfig
index 5bbca14005813..d0574ad571d3f 100644
--- a/drivers/dpll/zl3073x/Kconfig
+++ b/drivers/dpll/zl3073x/Kconfig
@@ -2,7 +2,7 @@
config ZL3073X
tristate "Microchip Azurite DPLL/PTP/SyncE devices" if COMPILE_TEST
- depends on NET
+ depends on NET && PTP_1588_CLOCK_OPTIONAL
select DPLL
select NET_DEVLINK
select REGMAP
@@ -16,7 +16,7 @@ config ZL3073X
config ZL3073X_I2C
tristate "I2C bus implementation for Microchip Azurite devices"
- depends on I2C && NET
+ depends on I2C && NET && PTP_1588_CLOCK_OPTIONAL
select REGMAP_I2C
select ZL3073X
help
@@ -28,7 +28,7 @@ config ZL3073X_I2C
config ZL3073X_SPI
tristate "SPI bus implementation for Microchip Azurite devices"
- depends on NET && SPI
+ depends on NET && SPI && PTP_1588_CLOCK_OPTIONAL
select REGMAP_SPI
select ZL3073X
help
diff --git a/drivers/dpll/zl3073x/dpll.c b/drivers/dpll/zl3073x/dpll.c
index d91f52b58eae3..7c11de35821ae 100644
--- a/drivers/dpll/zl3073x/dpll.c
+++ b/drivers/dpll/zl3073x/dpll.c
@@ -2,6 +2,7 @@
#include <linux/bits.h>
#include <linux/bitfield.h>
+#include <linux/cleanup.h>
#include <linux/bug.h>
#include <linux/container_of.h>
#include <linux/dev_printk.h>
@@ -14,6 +15,7 @@
#include <linux/netlink.h>
#include <linux/platform_device.h>
#include <linux/property.h>
+#include <linux/ptp_clock_kernel.h>
#include <linux/slab.h>
#include <linux/sprintf.h>
@@ -42,6 +44,7 @@
* @phase_offset: last saved pin phase offset
* @freq_offset: last saved fractional frequency offset
* @measured_freq: last saved measured frequency
+ * @perout_idx: PTP perout channel index, or -1 if not perout-eligible
*/
struct zl3073x_dpll_pin {
struct list_head list;
@@ -59,6 +62,7 @@ struct zl3073x_dpll_pin {
s64 phase_offset;
s64 freq_offset;
u32 measured_freq;
+ s8 perout_idx;
};
/*
@@ -936,21 +940,17 @@ zl3073x_dpll_output_pin_frequency_get(const struct dpll_pin *dpll_pin,
}
static int
-zl3073x_dpll_output_pin_frequency_set(const struct dpll_pin *dpll_pin,
- void *pin_priv,
- const struct dpll_device *dpll,
- void *dpll_priv, u64 frequency,
- struct netlink_ext_ack *extack)
+__zl3073x_dpll_output_pin_frequency_set(struct zl3073x_dpll *zldpll,
+ struct zl3073x_dpll_pin *pin,
+ u64 frequency)
{
- struct zl3073x_dpll *zldpll = dpll_priv;
struct zl3073x_dev *zldev = zldpll->dev;
- struct zl3073x_dpll_pin *pin = pin_priv;
const struct zl3073x_synth *synth;
u32 new_div, synth_freq;
struct zl3073x_out out;
u8 out_id;
- guard(mutex)(&zldpll->lock);
+ lockdep_assert_held(&zldpll->lock);
out_id = zl3073x_output_pin_out_get(pin->id);
out = *zl3073x_out_state_get(zldev, out_id);
@@ -1011,6 +1011,21 @@ zl3073x_dpll_output_pin_frequency_set(const struct dpll_pin *dpll_pin,
return zl3073x_out_state_set(zldev, out_id, &out);
}
+static int
+zl3073x_dpll_output_pin_frequency_set(const struct dpll_pin *dpll_pin,
+ void *pin_priv,
+ const struct dpll_device *dpll,
+ void *dpll_priv, u64 frequency,
+ struct netlink_ext_ack *extack)
+{
+ struct zl3073x_dpll *zldpll = dpll_priv;
+
+ guard(mutex)(&zldpll->lock);
+
+ return __zl3073x_dpll_output_pin_frequency_set(zldpll, pin_priv,
+ frequency);
+}
+
static int
zl3073x_dpll_output_pin_phase_adjust_get(const struct dpll_pin *dpll_pin,
void *pin_priv,
@@ -1608,6 +1623,7 @@ zl3073x_dpll_pin_alloc(struct zl3073x_dpll *zldpll, enum dpll_pin_direction dir,
pin->dpll = zldpll;
pin->dir = dir;
pin->id = id;
+ pin->perout_idx = -1;
return pin;
}
@@ -1626,6 +1642,44 @@ zl3073x_dpll_pin_free(struct zl3073x_dpll_pin *pin)
kfree(pin);
}
+/**
+ * zl3073x_dpll_pin_is_perout - check if output pin is perout-eligible
+ * @pin: DPLL pin to check
+ * @props: pin properties (already fetched by caller)
+ *
+ * An output pin is eligible for PTP periodic output if it is single-ended
+ * (not differential) and supports 1 Hz in its frequency list.
+ *
+ * Return: true if eligible, false otherwise
+ */
+static bool
+zl3073x_dpll_pin_is_perout(struct zl3073x_dpll_pin *pin,
+ const struct zl3073x_pin_props *props)
+{
+ struct zl3073x_dpll *zldpll = pin->dpll;
+ const struct zl3073x_chan *chan;
+ u8 out_id;
+ int i;
+
+ if (zl3073x_dpll_is_input_pin(pin))
+ return false;
+
+ out_id = zl3073x_output_pin_out_get(pin->id);
+ if (zl3073x_dev_out_is_diff(zldpll->dev, out_id))
+ return false;
+
+ chan = zl3073x_chan_state_get(zldpll->dev, zldpll->id);
+ if (!zl3073x_chan_is_out_stepped(chan, out_id))
+ return false;
+
+ for (i = 0; i < props->dpll_props.freq_supported_num; i++) {
+ if (props->dpll_props.freq_supported[i].min == 1)
+ return true;
+ }
+
+ return false;
+}
+
/**
* zl3073x_dpll_pin_register - register DPLL pin
* @pin: pointer to DPLL pin
@@ -1667,6 +1721,8 @@ zl3073x_dpll_pin_register(struct zl3073x_dpll_pin *pin, u32 index)
if (pin->prio == ZL_DPLL_REF_PRIO_NONE)
/* Clamp prio to max value */
pin->prio = ZL_DPLL_REF_PRIO_MAX;
+ } else if (zl3073x_dpll_pin_is_perout(pin, props)) {
+ pin->perout_idx = zldpll->ptp_info.n_per_out++;
}
/* Create or get existing DPLL pin */
@@ -1897,6 +1953,8 @@ zl3073x_dpll_pins_register(struct zl3073x_dpll *zldpll)
u8 id, index;
int rc;
+ zldpll->ptp_info.n_per_out = 0;
+
/* Process input pins */
for (index = 0; index < ZL3073X_NUM_PINS; index++) {
/* First input pins and then output pins */
@@ -2280,45 +2338,447 @@ zl3073x_dpll_init_fine_phase_adjust(struct zl3073x_dev *zldev)
return rc;
}
+/* Maximum frequency adjustment: +-1% of nominal in ppb */
+#define ZL3073X_DPLL_PTP_MAX_ADJ 10000000
+
/**
- * zl3073x_dpll_alloc - allocate DPLL device
- * @zldev: pointer to zl3073x device
- * @ch: DPLL channel number
+ * zl3073x_dpll_ptp_gettimex64 - read current time from ToD counters
+ * @info: PTP clock info
+ * @ts: timespec to store current time
+ * @sts: optional system timestamp pair for cross-timestamping
*
- * Allocates DPLL device structure for given DPLL channel.
+ * Return: 0 on success, <0 on error
+ */
+static int zl3073x_dpll_ptp_gettimex64(struct ptp_clock_info *info,
+ struct timespec64 *ts,
+ struct ptp_system_timestamp *sts)
+{
+ struct zl3073x_dpll *zldpll = container_of(info, struct zl3073x_dpll,
+ ptp_info);
+
+ guard(mutex)(&zldpll->lock);
+
+ return zl3073x_chan_tod_read(zldpll->dev, zldpll->id, false, ts, sts);
+}
+
+/**
+ * zl3073x_dpll_ptp_settime64 - set ToD counters to given time
+ * @info: PTP clock info
+ * @ts: timespec with time to set
*
- * Return: pointer to DPLL device on success, error pointer on error
+ * Return: 0 on success, <0 on error
*/
-struct zl3073x_dpll *
-zl3073x_dpll_alloc(struct zl3073x_dev *zldev, u8 ch)
+static int zl3073x_dpll_ptp_settime64(struct ptp_clock_info *info,
+ const struct timespec64 *ts)
{
- struct zl3073x_dpll *zldpll;
+ struct zl3073x_dpll *zldpll = container_of(info, struct zl3073x_dpll,
+ ptp_info);
- zldpll = kzalloc_obj(*zldpll);
- if (!zldpll)
- return ERR_PTR(-ENOMEM);
+ guard(mutex)(&zldpll->lock);
- zldpll->dev = zldev;
- zldpll->id = ch;
- mutex_init(&zldpll->lock);
- INIT_LIST_HEAD(&zldpll->pins);
+ return zl3073x_chan_tod_write(zldpll->dev, zldpll->id, *ts);
+}
- return zldpll;
+/**
+ * zl3073x_dpll_ptp_adjtime_phase_step - adjust sub-second time via phase step
+ * @zldpll: DPLL channel
+ * @delta: time adjustment in nanoseconds (must be within (-NSEC_PER_SEC,
+ * NSEC_PER_SEC))
+ *
+ * Uses the output phase step mechanism with tod_step=1 to adjust both
+ * the output clock phase and the ToD counter simultaneously. This keeps
+ * outputs and ToD coherent. Only valid for NCO.
+ *
+ * Outputs are grouped by synthesizer since the phase step value is in
+ * synthesizer clock cycles. The first synth group with enabled outputs
+ * uses tod_step to adjust both outputs and the ToD counter. Remaining
+ * groups step outputs only. If no synth has enabled outputs, the ToD
+ * counter is stepped alone using an empty output mask (the FW uses the
+ * lowest-ID synth's period for the conversion).
+ *
+ * Return: 0 on success, -EOPNOTSUPP if no synths available, <0 on error
+ */
+static int zl3073x_dpll_ptp_adjtime_phase_step(struct zl3073x_dpll *zldpll,
+ s64 delta)
+{
+ u16 synth_mask[ZL3073X_NUM_SYNTHS] = {};
+ struct zl3073x_dev *zldev = zldpll->dev;
+ const struct zl3073x_synth *synth;
+ const struct zl3073x_chan *chan;
+ struct zl3073x_dpll_pin *pin;
+ u32 first_synth_freq = 0;
+ bool tod_stepped = false;
+ s32 step_cycles;
+ u32 synth_freq;
+ int rc;
+ u8 i;
+
+ chan = zl3073x_chan_state_get(zldev, zldpll->id);
+
+ /* Build per-synth output masks from registered output pins */
+ list_for_each_entry(pin, &zldpll->pins, list) {
+ u8 out_id, synth_id;
+
+ if (zl3073x_dpll_is_input_pin(pin))
+ continue;
+
+ out_id = zl3073x_output_pin_out_get(pin->id);
+
+ if (!zl3073x_chan_is_out_stepped(chan, out_id))
+ continue;
+
+ synth_id = zl3073x_dev_out_synth_get(zldev, out_id);
+ if (synth_id >= ZL3073X_NUM_SYNTHS) {
+ dev_warn(zldev->dev, "Unexpected synth id for OUT%u\n",
+ out_id);
+ continue;
+ }
+ synth_mask[synth_id] |= BIT(out_id);
+ }
+
+ /* Process each synth group */
+ for (i = 0; i < ZL3073X_NUM_SYNTHS; i++) {
+ synth = zl3073x_synth_state_get(zldev, i);
+ if (!zl3073x_synth_is_enabled(synth) ||
+ zl3073x_synth_dpll_get(synth) != zldpll->id)
+ continue;
+
+ synth_freq = zl3073x_synth_freq_get(synth);
+
+ /* Remember lowest-ID synth freq for ToD-only fallback */
+ if (!first_synth_freq)
+ first_synth_freq = synth_freq;
+
+ if (!synth_mask[i])
+ continue;
+
+ /* Safe for s32: max synth freq is 750 MHz */
+ step_cycles = div_s64(delta * synth_freq, NSEC_PER_SEC);
+
+ rc = zl3073x_chan_phase_step(zldev, zldpll->id,
+ synth_mask[i], step_cycles,
+ !tod_stepped);
+ if (rc) {
+ if (tod_stepped) {
+ dev_warn(zldev->dev,
+ "Partial phase step failure\n");
+ return 0;
+ }
+ return rc;
+ }
+ tod_stepped = true;
+ }
+
+ if (!first_synth_freq)
+ return -EOPNOTSUPP;
+
+ /* No enabled outputs found; step ToD counter only using the
+ * lowest-ID synth's period (empty output mask).
+ */
+ if (!tod_stepped) {
+ step_cycles = div_s64(delta * first_synth_freq, NSEC_PER_SEC);
+ return zl3073x_chan_phase_step(zldev, zldpll->id, 0,
+ step_cycles, true);
+ }
+
+ return 0;
}
/**
- * zl3073x_dpll_free - free DPLL device
- * @zldpll: pointer to zl3073x_dpll structure
+ * zl3073x_dpll_ptp_adjtime - adjust PTP clock time
+ * @info: PTP clock info
+ * @delta: time adjustment in nanoseconds
*
- * Deallocates given DPLL device previously allocated by @zl3073x_dpll_alloc.
+ * For NCO, large deltas (>= 1 second) are split into a ToD
+ * read-modify-write for the seconds part and an output phase step for
+ * the sub-second remainder. Sub-second deltas use phase step directly,
+ * falling back to ToD read-modify-write if phase step is unavailable.
+ * In AUTO/REFLOCK modes, large deltas are split into ToD
+ * read-modify-write for seconds and TIE write for the sub-second
+ * remainder. Sub-second deltas use TIE write directly.
+ *
+ * Return: 0 on success, <0 on error
*/
-void
-zl3073x_dpll_free(struct zl3073x_dpll *zldpll)
+static int zl3073x_dpll_ptp_adjtime(struct ptp_clock_info *info, s64 delta)
{
- WARN(zldpll->dpll_dev, "DPLL device is still registered\n");
+ struct zl3073x_dpll *zldpll = container_of(info, struct zl3073x_dpll,
+ ptp_info);
+ struct zl3073x_dev *zldev = zldpll->dev;
+ const struct zl3073x_chan *chan;
+ bool sec_adjusted = false;
+ struct timespec64 ts;
+ int rc;
- mutex_destroy(&zldpll->lock);
- kfree(zldpll);
+ if (!delta)
+ return 0;
+
+ guard(mutex)(&zldpll->lock);
+
+ /* Modes without phase step or TIE use plain ToD adjust */
+ chan = zl3073x_chan_state_get(zldev, zldpll->id);
+ if (!zl3073x_chan_mode_is_nco(chan) &&
+ !zl3073x_chan_mode_supports_tie(chan))
+ return zl3073x_chan_tod_adjust(zldev, zldpll->id,
+ ns_to_timespec64(delta));
+
+ /* Split off seconds via ToD read-modify-write so the sub-second
+ * remainder can be applied through the output-coherent mechanism
+ * (phase step or TIE write).
+ */
+ if (abs(delta) >= NSEC_PER_SEC) {
+ s32 remainder;
+
+ ts.tv_sec = div_s64_rem(delta, NSEC_PER_SEC, &remainder);
+ ts.tv_nsec = 0;
+ delta = remainder;
+
+ rc = zl3073x_chan_tod_adjust(zldev, zldpll->id, ts);
+ if (rc)
+ return rc;
+
+ sec_adjusted = true;
+
+ /* No sub-second remainder, done */
+ if (!delta)
+ return 0;
+ }
+
+ /* Apply sub-second delta via phase step (NCO) or TIE write */
+ if (zl3073x_chan_mode_is_nco(chan)) {
+ rc = zl3073x_dpll_ptp_adjtime_phase_step(zldpll, delta);
+ if (!rc)
+ return 0;
+ } else {
+ rc = zl3073x_chan_tie_write(zldev, zldpll->id, delta);
+ if (!rc)
+ return 0;
+ }
+
+ /* Phase step or TIE write failed, fall back to ToD adjust */
+ rc = zl3073x_chan_tod_adjust(zldev, zldpll->id,
+ ns_to_timespec64(delta));
+
+ /* In the unlikely event that both phase step/TIE write and fallback
+ * ToD adjust fail after seconds were already committed, return
+ * success to prevent the PTP servo from retrying the full delta and
+ * applying seconds again. The sub-second residual will self-correct
+ * in the next servo cycle.
+ */
+ if (rc && sec_adjusted) {
+ dev_warn(zldev->dev,
+ "Sub-second adjustment failed after seconds applied\n");
+ return 0;
+ }
+
+ return rc;
+}
+
+/**
+ * zl3073x_dpll_ptp_adjfine - adjust PTP clock frequency
+ * @info: PTP clock info
+ * @scaled_ppm: frequency adjustment in scaled ppm (ppm * 2^16)
+ *
+ * Only supported for NCO. Writes the delta frequency offset register.
+ *
+ * Return: 0 on success, -EOPNOTSUPP if NCO pin is not connected, <0 on error
+ */
+static int
+zl3073x_dpll_ptp_adjfine(struct ptp_clock_info *info, long scaled_ppm)
+{
+ struct zl3073x_dpll *zldpll = container_of(info, struct zl3073x_dpll,
+ ptp_info);
+ const struct zl3073x_chan *chan;
+ s64 offset;
+
+ /* Convert scaled_ppm to df_offset in 2^-48 steps:
+ * df_offset = -(scaled_ppm * 2^32) / 10^6
+ *
+ * Simplify to avoid overflow:
+ * df_offset = -(scaled_ppm * 2^26) / 5^6
+ * df_offset = -(scaled_ppm * 67108864) / 15625
+ */
+ offset = -div_s64((s64)scaled_ppm * 67108864LL, 15625);
+
+ guard(mutex)(&zldpll->lock);
+
+ chan = zl3073x_chan_state_get(zldpll->dev, zldpll->id);
+ if (!zl3073x_chan_mode_is_nco(chan))
+ return scaled_ppm ? -EOPNOTSUPP : 0;
+ if (offset == chan->df_offset)
+ return 0;
+
+ return zl3073x_chan_df_offset_set(zldpll->dev, zldpll->id, offset);
+}
+
+/**
+ * zl3073x_dpll_ptp_adjphase - adjust PTP clock phase
+ * @info: PTP clock info
+ * @delta: phase adjustment in nanoseconds
+ *
+ * Only supported in AUTO and REFLOCK modes. Uses TIE write for
+ * nanosecond resolution phase adjustment.
+ *
+ * Return: 0 on success, -EOPNOTSUPP if mode doesn't support TIE, <0 on error
+ */
+static int zl3073x_dpll_ptp_adjphase(struct ptp_clock_info *info, s32 delta)
+{
+ struct zl3073x_dpll *zldpll = container_of(info, struct zl3073x_dpll,
+ ptp_info);
+ struct zl3073x_dev *zldev = zldpll->dev;
+ const struct zl3073x_chan *chan;
+
+ if (!delta)
+ return 0;
+
+ guard(mutex)(&zldpll->lock);
+
+ chan = zl3073x_chan_state_get(zldev, zldpll->id);
+
+ if (!zl3073x_chan_mode_supports_tie(chan))
+ return -EOPNOTSUPP;
+
+ return zl3073x_chan_tie_write(zldev, zldpll->id, delta);
+}
+
+static s32
+zl3073x_dpll_ptp_getmaxphase(struct ptp_clock_info *info __always_unused)
+{
+ /* HW limits TIE write to +-1 second. Return the constant HW
+ * limit and let adjphase handle mode-specific checks.
+ */
+ return NSEC_PER_SEC - 1;
+}
+
+/**
+ * zl3073x_dpll_ptp_perout_find_pin - find pin by perout channel index
+ * @zldpll: DPLL channel
+ * @idx: perout channel index to find
+ *
+ * Return: pointer to the pin, or NULL if not found
+ */
+static struct zl3073x_dpll_pin *
+zl3073x_dpll_ptp_perout_find_pin(struct zl3073x_dpll *zldpll, int idx)
+{
+ struct zl3073x_dpll_pin *pin;
+
+ list_for_each_entry(pin, &zldpll->pins, list) {
+ if (pin->perout_idx == idx)
+ return pin;
+ }
+
+ return NULL;
+}
+
+/**
+ * zl3073x_dpll_ptp_enable - enable/disable PTP clock functions
+ * @info: PTP clock info
+ * @rq: the requested clock function and parameters
+ * @on: true to enable, false to disable
+ *
+ * Handles PTP_CLK_REQ_PEROUT requests. Only 1PPS (period = 1s) is supported.
+ * On enable, configures the output divider for 1 Hz. On disable, does nothing
+ * as the signal is not disabled.
+ *
+ * Return: 0 on success, <0 on error
+ */
+static int zl3073x_dpll_ptp_enable(struct ptp_clock_info *info,
+ struct ptp_clock_request *rq, int on)
+{
+ struct zl3073x_dpll *zldpll = container_of(info, struct zl3073x_dpll,
+ ptp_info);
+ struct zl3073x_dpll_pin *pin;
+ unsigned int idx;
+ int rc;
+
+ if (rq->type != PTP_CLK_REQ_PEROUT)
+ return -EOPNOTSUPP;
+
+ idx = rq->perout.index;
+ if (idx >= info->n_per_out)
+ return -EINVAL;
+
+ /* Toggling output pins can produce runt pulses, so disable
+ * is a no-op.
+ */
+ if (!on)
+ return 0;
+
+ /* Only accept exactly 1PPS (period.sec == 1, nsec == 0) */
+ if (rq->perout.period.sec != 1 || rq->perout.period.nsec != 0)
+ return -EINVAL;
+
+ mutex_lock(&zldpll->lock);
+
+ pin = zl3073x_dpll_ptp_perout_find_pin(zldpll, idx);
+ if (!pin) {
+ mutex_unlock(&zldpll->lock);
+ return -EINVAL;
+ }
+
+ /* Already at 1 Hz, nothing to do */
+ if (zl3073x_dev_output_pin_freq_get(zldpll->dev, pin->id) == 1) {
+ mutex_unlock(&zldpll->lock);
+ return 0;
+ }
+
+ rc = __zl3073x_dpll_output_pin_frequency_set(zldpll, pin, 1);
+
+ mutex_unlock(&zldpll->lock);
+
+ if (!rc)
+ dpll_pin_change_ntf(pin->dpll_pin);
+
+ return rc;
+}
+
+static const struct ptp_clock_info zl3073x_dpll_ptp_clock_info = {
+ .owner = THIS_MODULE,
+ .max_adj = ZL3073X_DPLL_PTP_MAX_ADJ,
+ .gettimex64 = zl3073x_dpll_ptp_gettimex64,
+ .settime64 = zl3073x_dpll_ptp_settime64,
+ .adjtime = zl3073x_dpll_ptp_adjtime,
+ .adjfine = zl3073x_dpll_ptp_adjfine,
+ .adjphase = zl3073x_dpll_ptp_adjphase,
+ .getmaxphase = zl3073x_dpll_ptp_getmaxphase,
+ .enable = zl3073x_dpll_ptp_enable,
+};
+
+/**
+ * zl3073x_dpll_ptp_register - register PTP clock for a DPLL channel
+ * @zldpll: DPLL channel to register PTP clock for
+ *
+ * Return: 0 on success, <0 on error
+ */
+static int zl3073x_dpll_ptp_register(struct zl3073x_dpll *zldpll)
+{
+ struct zl3073x_dev *zldev = zldpll->dev;
+ struct ptp_clock *ptp_clock;
+
+ snprintf(zldpll->ptp_info.name, sizeof(zldpll->ptp_info.name),
+ "zl3073x-dpll%u", zldpll->id);
+
+ ptp_clock = ptp_clock_register(&zldpll->ptp_info, zldev->dev);
+ if (IS_ERR(ptp_clock)) {
+ dev_err(zldev->dev, "Failed to register PTP clock for DPLL%u\n",
+ zldpll->id);
+ return PTR_ERR(ptp_clock);
+ }
+
+ zldpll->ptp_clock = ptp_clock;
+
+ return 0;
+}
+
+/**
+ * zl3073x_dpll_ptp_unregister - unregister PTP clock for a DPLL channel
+ * @zldpll: DPLL channel to unregister PTP clock for
+ */
+static void zl3073x_dpll_ptp_unregister(struct zl3073x_dpll *zldpll)
+{
+ if (!IS_ERR_OR_NULL(zldpll->ptp_clock)) {
+ ptp_clock_unregister(zldpll->ptp_clock);
+ zldpll->ptp_clock = NULL;
+ }
}
/**
@@ -2403,6 +2863,48 @@ zl3073x_dpll_ref_sync_pairs_register(struct zl3073x_dpll *zldpll)
return 0;
}
+/**
+ * zl3073x_dpll_alloc - allocate DPLL device
+ * @zldev: pointer to zl3073x device
+ * @ch: DPLL channel number
+ *
+ * Allocates DPLL device structure for given DPLL channel.
+ *
+ * Return: pointer to DPLL device on success, error pointer on error
+ */
+struct zl3073x_dpll *
+zl3073x_dpll_alloc(struct zl3073x_dev *zldev, u8 ch)
+{
+ struct zl3073x_dpll *zldpll;
+
+ zldpll = kzalloc_obj(*zldpll);
+ if (!zldpll)
+ return ERR_PTR(-ENOMEM);
+
+ zldpll->dev = zldev;
+ zldpll->id = ch;
+ zldpll->ptp_info = zl3073x_dpll_ptp_clock_info;
+ mutex_init(&zldpll->lock);
+ INIT_LIST_HEAD(&zldpll->pins);
+
+ return zldpll;
+}
+
+/**
+ * zl3073x_dpll_free - free DPLL device
+ * @zldpll: pointer to zl3073x_dpll structure
+ *
+ * Deallocates given DPLL device previously allocated by @zl3073x_dpll_alloc.
+ */
+void
+zl3073x_dpll_free(struct zl3073x_dpll *zldpll)
+{
+ WARN(zldpll->dpll_dev, "DPLL device is still registered\n");
+
+ mutex_destroy(&zldpll->lock);
+ kfree(zldpll);
+}
+
/**
* zl3073x_dpll_register - register DPLL device and all its pins
* @zldpll: pointer to zl3073x_dpll structure
@@ -2433,6 +2935,13 @@ zl3073x_dpll_register(struct zl3073x_dpll *zldpll)
return rc;
}
+ rc = zl3073x_dpll_ptp_register(zldpll);
+ if (rc) {
+ zl3073x_dpll_pins_unregister(zldpll);
+ zl3073x_dpll_device_unregister(zldpll);
+ return rc;
+ }
+
return 0;
}
@@ -2446,7 +2955,7 @@ zl3073x_dpll_register(struct zl3073x_dpll *zldpll)
void
zl3073x_dpll_unregister(struct zl3073x_dpll *zldpll)
{
- /* Unregister all pins and dpll */
+ zl3073x_dpll_ptp_unregister(zldpll);
zl3073x_dpll_pins_unregister(zldpll);
zl3073x_dpll_device_unregister(zldpll);
}
diff --git a/drivers/dpll/zl3073x/dpll.h b/drivers/dpll/zl3073x/dpll.h
index faebc402ba1b7..9b5f72c33849c 100644
--- a/drivers/dpll/zl3073x/dpll.h
+++ b/drivers/dpll/zl3073x/dpll.h
@@ -5,6 +5,8 @@
#include <linux/dpll.h>
#include <linux/list.h>
+#include <linux/mutex.h>
+#include <linux/ptp_clock_kernel.h>
#include "core.h"
@@ -22,6 +24,8 @@
* @type: DPLL type (PPS or EEC)
* @lock_status: last saved DPLL lock status
* @pins: list of pins
+ * @ptp_info: PTP clock info
+ * @ptp_clock: registered PTP clock (or NULL)
*/
struct zl3073x_dpll {
struct list_head list;
@@ -36,6 +40,8 @@ struct zl3073x_dpll {
enum dpll_type type;
enum dpll_lock_status lock_status;
struct list_head pins;
+ struct ptp_clock_info ptp_info;
+ struct ptp_clock *ptp_clock;
};
struct zl3073x_dpll *zl3073x_dpll_alloc(struct zl3073x_dev *zldev, u8 ch);
--
2.53.0
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox