Netdev List
 help / color / mirror / Atom feed
* [PATCH net V2 2/2] net/mlx5e: Reject unsupported CB Shaper TSA in ETS validation
From: Tariq Toukan @ 2026-07-17  7:51 UTC (permalink / raw)
  To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
	netdev, Paolo Abeni
  Cc: Alexei Lazar, Carolina Jubran, Leon Romanovsky, linux-kernel,
	linux-rdma, Mark Bloch, Pavan Chebbi, Saeed Mahameed,
	Tariq Toukan, Gal Pressman
In-Reply-To: <20260717075125.1244877-1-tariqt@nvidia.com>

From: Alexei Lazar <alazar@nvidia.com>

Credit Based (CB) TSA is not supported by the mlx5 driver, so reject
any configurations that specify it.

Fixes: 08fb1dacdd76 ("net/mlx5e: Support DCBNL IEEE ETS")
Signed-off-by: Alexei Lazar <alazar@nvidia.com>
Reviewed-by: Carolina Jubran <cjubran@nvidia.com>
Signed-off-by: Tariq Toukan <tariqt@nvidia.com>
Reviewed-by: Pavan Chebbi <pavan.chebbi@broadcom.com>
---
 drivers/net/ethernet/mellanox/mlx5/core/en_dcbnl.c | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_dcbnl.c b/drivers/net/ethernet/mellanox/mlx5/core/en_dcbnl.c
index 762f0a46c120..00e706e1ede1 100644
--- a/drivers/net/ethernet/mellanox/mlx5/core/en_dcbnl.c
+++ b/drivers/net/ethernet/mellanox/mlx5/core/en_dcbnl.c
@@ -324,6 +324,14 @@ static int mlx5e_dbcnl_validate_ets(struct net_device *netdev,
 		}
 	}
 
+	for (i = 0; i < IEEE_8021QAZ_MAX_TCS; i++) {
+		if (ets->tc_tsa[i] == IEEE_8021QAZ_TSA_CB_SHAPER) {
+			netdev_err(netdev,
+				   "Failed to validate ETS: CB Shaper is not supported\n");
+			return -EOPNOTSUPP;
+		}
+	}
+
 	/* Validate Bandwidth Sum */
 	for (i = 0; i < IEEE_8021QAZ_MAX_TCS; i++) {
 		if (ets->tc_tsa[i] == IEEE_8021QAZ_TSA_ETS) {
-- 
2.44.0


^ permalink raw reply related

* Re: [PATCH net v2] mac802154: llsec: reject frames shorter than the authentication tag
From: Breno Leitao @ 2026-07-17  8:05 UTC (permalink / raw)
  To: Doruk Tan Ozturk
  Cc: alex.aring, stefan, miquel.raynal, davem, edumazet, kuba, pabeni,
	horms, linux-wpan, netdev, linux-kernel, stable
In-Reply-To: <20260716193106.30607-1-doruk@0sec.ai>

On Thu, Jul 16, 2026 at 09:31:06PM +0200, Doruk Tan Ozturk wrote:
> llsec_do_decrypt_auth() computes the associated-data length for the
> AEAD request as
> 
> 	assoclen += datalen - authlen;
> 
> where datalen is the number of bytes after the MAC header and authlen
> (4, 8 or 16) is the length of the authentication tag. Nothing verifies
> that the frame actually carries at least authlen payload bytes. A
> secured frame whose payload is shorter than the tag makes
> datalen - authlen negative; assoclen is then passed to
> aead_request_set_ad() as an unsigned value close to 4 GiB, so
> crypto_aead_decrypt() walks far off the end of the scatterlist that
> only spans the real frame.
> 
> The frame is fully attacker-controlled and reaches this path from any
> IEEE 802.15.4 peer in radio range. Reject frames whose payload is
> shorter than the authentication tag before the subtraction.
> 
> Dynamically reproduced on a KASAN kernel as a general-protection-fault
> in the AEAD scatterwalk, and the fix confirmed.
> 
> Fixes: 4c14a2fb5d14 ("mac802154: add llsec decryption method")
> Cc: stable@vger.kernel.org
> Assisted-by: 0sec:multi-model
> Reviewed-by: Simon Horman <horms@kernel.org>
> Signed-off-by: Doruk Tan Ozturk <doruk@0sec.ai>

Reviwed-by: Breno Leitao <leitao@debian.org>

^ permalink raw reply

* Re: [PATCH v2 ipsec] xfrm: espintcp: fix UAF during close
From: Breno Leitao @ 2026-07-17  8:06 UTC (permalink / raw)
  To: Sabrina Dubroca
  Cc: netdev, Steffen Klassert, Herbert Xu, stable, zdi-disclosures
In-Reply-To: <a62857f72af228a0781a5c0cc0f8d898d9e0d7be.1784234935.git.sd@queasysnail.net>

On Thu, Jul 16, 2026 at 10:54:59PM +0200, Sabrina Dubroca wrote:
> ZDI reported and analyzed a race condition during close for espintcp
> sockets:
> 
>     espintcp_close() frees emsg->skb via kfree_skb() without holding
>     any socket lock. Concurrently, the xfrm_trans_reinject work queue
>     invokes esp_output_tcp_finish() -> espintcp_push_skb() ->
>     espintcp_push_msgs() -> skb_send_sock_locked(), which reads the
>     same skb as a data source.
> 
> Fix this by adding a synchronize_rcu() call after resetting sk_prot,
> since esp_output_tcp_finish() runs under RCU and won't use a socket
> with sk_prot == &tcp_prot.  Simply taking the socket lock in
> espintcp_close() could lead to leaks, if esp_output_tcp_finish()
> re-adds an skb in the slot we just freed. After this, the existing
> barrier() is no longer needed.
> 
> Cc: stable@vger.kernel.org
> Fixes: e27cca96cd68 ("xfrm: add espintcp (RFC 8229)")
> Reported-by: zdi-disclosures@trendmicro.com
> Signed-off-by: Sabrina Dubroca <sd@queasysnail.net>

Reviewed-by: Breno Leitao <leitao@debian.org>

^ permalink raw reply

* [PATCH] usb: atm: ueagle-atm: reject descriptors that confuse probe and disconnect
From: Diego Fernando Mancera Gomez @ 2026-07-17  8:07 UTC (permalink / raw)
  To: Matthieu CASTET, Stanislaw Gruszka
  Cc: Chas Williams, Greg Kroah-Hartman, Mauricio Faria de Oliveira,
	linux-usb, netdev, linux-atm-general, linux-kernel,
	Diego Fernando Mancera Gomez, syzbot+e62a973f8322b3bbe3ac

uea_probe() distinguishes a pre-firmware device from a post-firmware one
using the USB id (UEA_IS_PREFIRM()), and stores a different object as the
interface data in each case: a 'struct completion' for a pre-firmware
device (to be waited on in .disconnect()), or a 'struct usbatm_data' for a
post-firmware one.

uea_disconnect() instead tells the two apart by the number of interfaces
of the active configuration (a pre-firmware device exposes a single
interface, ADI930 has 2 and eagle has 3), and casts the interface data
accordingly.

Because the two handlers use different criteria, a crafted device that
advertises a pre-firmware id together with a multi-interface descriptor
(or a post-firmware id with a single interface) makes them disagree: the
small 'struct completion' stored by uea_probe() is then passed to
usbatm_usb_disconnect(), which casts it to 'struct usbatm_data' and takes
instance->serialize, reading past the end of the allocation:

  BUG: KASAN: slab-out-of-bounds in __mutex_lock+0x152a/0x1b80
  Read of size 8 at addr ffff8880470e2c60 by task kworker/1:2/982
  ...
   __mutex_lock+0x152a/0x1b80
   usbatm_usb_disconnect+0x70/0x820
   uea_disconnect+0x133/0x2c0
   usb_unbind_interface+0x1dd/0x9e0
  ...
  which belongs to the cache kmalloc-96 of size 96
  The buggy address is located 0 bytes to the right of
   allocated 96-byte region [ffff8880470e2c00, ffff8880470e2c60)

Reject such inconsistent descriptors in uea_probe() so that both handlers
always make the same pre/post-firmware decision.

Reported-by: syzbot+e62a973f8322b3bbe3ac@syzkaller.appspotmail.com
Closes: https://syzkaller.appspot.com/bug?extid=e62a973f8322b3bbe3ac
Fixes: e2674dfbed8a ("usb: atm: ueagle-atm: wait for pre-firmware load in .disconnect()")
Signed-off-by: Diego Fernando Mancera Gomez <diegomancera.dev@gmail.com>
---
 drivers/usb/atm/ueagle-atm.c | 17 +++++++++++++++++
 1 file changed, 17 insertions(+)

diff --git a/drivers/usb/atm/ueagle-atm.c b/drivers/usb/atm/ueagle-atm.c
index 4e71ed679a76..4266a0cb7e3b 100644
--- a/drivers/usb/atm/ueagle-atm.c
+++ b/drivers/usb/atm/ueagle-atm.c
@@ -2549,6 +2549,7 @@ static struct usbatm_driver uea_usbatm_driver = {
 static int uea_probe(struct usb_interface *intf, const struct usb_device_id *id)
 {
 	struct usb_device *usb = interface_to_usbdev(intf);
+	bool single_iface = usb->config->desc.bNumInterfaces == 1;
 	int ret;
 
 	uea_dbg(usb, "ADSL device found with vid (%#X) pid (%#X) Rev (%#X): %s\n",
@@ -2557,6 +2558,22 @@ static int uea_probe(struct usb_interface *intf, const struct usb_device_id *id)
 		le16_to_cpu(usb->descriptor.bcdDevice),
 		chip_name[UEA_CHIP_VERSION(id)]);
 
+	/*
+	 * uea_probe() decides between the pre-firmware and post-firmware case
+	 * from the USB id and stores a different object as interface data in
+	 * each case: a struct completion for a pre-firmware device, a struct
+	 * usbatm_data for a post-firmware one. uea_disconnect() instead tells
+	 * the two apart by the number of interfaces (a pre-firmware device
+	 * exposes a single interface, ADI930 has 2 and eagle has 3). A crafted
+	 * device advertising a pre-firmware id together with a multi-interface
+	 * descriptor (or the other way around) makes the two disagree, so that
+	 * usbatm_usb_disconnect() treats the small completion object as a
+	 * struct usbatm_data and reads out of bounds. Reject such inconsistent
+	 * descriptors so both paths make the same decision.
+	 */
+	if (UEA_IS_PREFIRM(id) != single_iface)
+		return -ENODEV;
+
 	usb_reset_device(usb);
 
 	if (UEA_IS_PREFIRM(id)) {
-- 
2.43.0


^ permalink raw reply related

* [PATCH net 0/2] tcp: validate RST sequence in SYN-RECEIVED
From: Yuxiang Yang @ 2026-07-17  8:14 UTC (permalink / raw)
  To: netdev
  Cc: linux-kernel, linux-kselftest, Eric Dumazet, Neal Cardwell,
	Kuniyuki Iwashima, David S . Miller, Jakub Kicinski, Paolo Abeni,
	Simon Horman, Shuah Khan, Yuxiang Yang, Yizhou Zhao, Ao Wang,
	Xuewei Feng, Qi Li, Ke Xu, yyxroy22

The SYN-RECEIVED request-socket path accepts any in-window RST and
removes the request, even when SEG.SEQ does not exactly match RCV.NXT.
RFC 9293 requires a challenge ACK for a non-exact in-window RST.

Patch 1 applies the RFC 5961 sequence check to request sockets and shares
the per-netns challenge ACK quota with the established-socket path.
Patch 2 adds a compact packetdrill regression test for exact, non-exact,
RST|ACK, and out-of-window cases.

The implementation was tested with a separate raw-socket A/B harness on
IPv4 and IPv6: the unpatched kernel passed 4/12 cases and the patched
kernel passed 12/12.  The packetdrill test fails on the unpatched kernel
and passes on the patched kernel for IPv4, IPv6, and IPv4-mapped IPv6
under QEMU/TCG.

Yuxiang Yang (2):
  tcp: challenge ACK for non-exact RST in SYN-RECEIVED
  selftests/net: packetdrill: cover RST validation in SYN-RECEIVED

 include/net/tcp.h                             |  2 +
 net/ipv4/tcp_input.c                          | 56 +++++++++++++----
 net/ipv4/tcp_minisocks.c                      | 12 +++-
 .../packetdrill/tcp_rfc5961_rst-syn-recv.pkt  | 61 +++++++++++++++++++
 4 files changed, 117 insertions(+), 14 deletions(-)
 create mode 100644 tools/testing/selftests/net/packetdrill/tcp_rfc5961_rst-syn-recv.pkt


base-commit: 3f1f755366687d051174739fb99f7d560202f60b
-- 
2.34.1


^ permalink raw reply

* [PATCH net 1/2] tcp: challenge ACK for non-exact RST in SYN-RECEIVED
From: Yuxiang Yang @ 2026-07-17  8:14 UTC (permalink / raw)
  To: netdev
  Cc: linux-kernel, linux-kselftest, Eric Dumazet, Neal Cardwell,
	Kuniyuki Iwashima, David S . Miller, Jakub Kicinski, Paolo Abeni,
	Simon Horman, Shuah Khan, Yuxiang Yang, Yizhou Zhao, Ao Wang,
	Xuewei Feng, Qi Li, Ke Xu, stable, yyxroy22
In-Reply-To: <20260717081443.809393-1-yangyx22@mails.tsinghua.edu.cn>

The SYN-RECEIVED request-socket path in tcp_check_req() accepts an
in-window RST without requiring SEG.SEQ to exactly match RCV.NXT.  A
non-exact RST therefore removes the request instead of eliciting a
challenge ACK.

RFC 9293 section 3.10.7.4 applies the RFC 5961 reset check in
SYN-RECEIVED: an exact RST resets the connection, while a non-exact
in-window RST must trigger a challenge ACK and be dropped.

Apply that check before the ACK-field validation, following the RFC
sequence-number, RST, then ACK processing order.  Factor the per-netns
challenge ACK quota out of tcp_send_challenge_ack() so request sockets
can share it.  Use the request socket's send_ack() callback and its own
out-of-window ACK timestamp to send and rate-limit the response.

Reported-by: Yuxiang Yang <yangyx22@mails.tsinghua.edu.cn>
Reported-by: Yizhou Zhao <zhaoyz24@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>
Fixes: 282f23c6ee34 ("tcp: implement RFC 5961 3.2")
Cc: stable@vger.kernel.org
Signed-off-by: Yuxiang Yang <yangyx22@mails.tsinghua.edu.cn>
---
 include/net/tcp.h        |  2 ++
 net/ipv4/tcp_input.c     | 56 ++++++++++++++++++++++++++++++----------
 net/ipv4/tcp_minisocks.c | 12 ++++++++-
 3 files changed, 56 insertions(+), 14 deletions(-)

diff --git a/include/net/tcp.h b/include/net/tcp.h
index 6d376ea4d..2c5b88953 100644
--- a/include/net/tcp.h
+++ b/include/net/tcp.h
@@ -1974,6 +1974,8 @@ static inline void tcp_fast_path_check(struct sock *sk)
 
 bool tcp_oow_rate_limited(struct net *net, const struct sk_buff *skb,
 			  int mib_idx, u32 *last_oow_ack_time);
+void tcp_reqsk_send_challenge_ack(struct sock *sk, struct sk_buff *skb,
+				  struct request_sock *req);
 
 static inline void tcp_mib_init(struct net *net)
 {
diff --git a/net/ipv4/tcp_input.c b/net/ipv4/tcp_input.c
index 61045a888..daff93d51 100644
--- a/net/ipv4/tcp_input.c
+++ b/net/ipv4/tcp_input.c
@@ -4038,24 +4038,17 @@ static void tcp_send_ack_reflect_ect(struct sock *sk, bool accecn_reflector)
 	__tcp_send_ack(sk, tp->rcv_nxt, flags);
 }
 
-/* RFC 5961 7 [ACK Throttling] */
-static void tcp_send_challenge_ack(struct sock *sk, bool accecn_reflector)
+/* Consume one slot from the per-netns RFC 5961 challenge ACK quota.
+ * Returns true if a challenge ACK may be sent.
+ */
+static bool tcp_challenge_ack_allowed(struct net *net)
 {
-	struct tcp_sock *tp = tcp_sk(sk);
-	struct net *net = sock_net(sk);
 	u32 count, now, ack_limit;
 
-	/* First check our per-socket dupack rate limit. */
-	if (__tcp_oow_rate_limited(net,
-				   LINUX_MIB_TCPACKSKIPPEDCHALLENGE,
-				   &tp->last_oow_ack_time))
-		return;
-
 	ack_limit = READ_ONCE(net->ipv4.sysctl_tcp_challenge_ack_limit);
 	if (ack_limit == INT_MAX)
-		goto send_ack;
+		return true;
 
-	/* Then check host-wide RFC 5961 rate limit. */
 	now = jiffies / HZ;
 	if (now != READ_ONCE(net->ipv4.tcp_challenge_timestamp)) {
 		u32 half = (ack_limit + 1) >> 1;
@@ -4067,12 +4060,49 @@ static void tcp_send_challenge_ack(struct sock *sk, bool accecn_reflector)
 	count = READ_ONCE(net->ipv4.tcp_challenge_count);
 	if (count > 0) {
 		WRITE_ONCE(net->ipv4.tcp_challenge_count, count - 1);
-send_ack:
+		return true;
+	}
+	return false;
+}
+
+/* RFC 5961 7 [ACK Throttling] */
+static void tcp_send_challenge_ack(struct sock *sk, bool accecn_reflector)
+{
+	struct tcp_sock *tp = tcp_sk(sk);
+	struct net *net = sock_net(sk);
+
+	/* First check our per-socket dupack rate limit. */
+	if (__tcp_oow_rate_limited(net,
+				   LINUX_MIB_TCPACKSKIPPEDCHALLENGE,
+				   &tp->last_oow_ack_time))
+		return;
+
+	/* Then check the per-netns RFC 5961 rate limit. */
+	if (tcp_challenge_ack_allowed(net)) {
 		NET_INC_STATS(net, LINUX_MIB_TCPCHALLENGEACK);
 		tcp_send_ack_reflect_ect(sk, accecn_reflector);
 	}
 }
 
+/* Send a challenge ACK from a SYN-RECEIVED request socket. Uses
+ * __tcp_oow_rate_limited() directly so that an RST carrying payload
+ * cannot bypass the per-request rate limit.
+ */
+void tcp_reqsk_send_challenge_ack(struct sock *sk, struct sk_buff *skb,
+				  struct request_sock *req)
+{
+	struct net *net = sock_net(sk);
+
+	if (__tcp_oow_rate_limited(net, LINUX_MIB_TCPACKSKIPPEDCHALLENGE,
+				   &tcp_rsk(req)->last_oow_ack_time))
+		return;
+
+	if (tcp_challenge_ack_allowed(net)) {
+		NET_INC_STATS(net, LINUX_MIB_TCPCHALLENGEACK);
+		req->rsk_ops->send_ack(sk, skb, req);
+	}
+}
+
 static void tcp_store_ts_recent(struct tcp_sock *tp)
 {
 	tp->rx_opt.ts_recent = tp->rx_opt.rcv_tsval;
diff --git a/net/ipv4/tcp_minisocks.c b/net/ipv4/tcp_minisocks.c
index ddc4b17a8..6ab3e3a0b 100644
--- a/net/ipv4/tcp_minisocks.c
+++ b/net/ipv4/tcp_minisocks.c
@@ -833,7 +833,7 @@ struct sock *tcp_check_req(struct sock *sk, struct sk_buff *skb,
 	 * elsewhere and is checked directly against the child socket rather
 	 * than req because user data may have been sent out.
 	 */
-	if ((flg & TCP_FLAG_ACK) && !fastopen &&
+	if ((flg & TCP_FLAG_ACK) && !(flg & TCP_FLAG_RST) && !fastopen &&
 	    (TCP_SKB_CB(skb)->ack_seq !=
 	     tcp_rsk(req)->snt_isn + 1))
 		return sk;
@@ -872,6 +872,16 @@ struct sock *tcp_check_req(struct sock *sk, struct sk_buff *skb,
 		flg &= ~TCP_FLAG_SYN;
 	}
 
+	/* RFC 5961 section 3.2, as clarified by RFC 9293 section
+	 * 3.10.7.4, requires a challenge ACK for a non-exact
+	 * in-window RST in SYN-RECEIVED.
+	 */
+	if ((flg & TCP_FLAG_RST) &&
+	    TCP_SKB_CB(skb)->seq != tcp_rsk(req)->rcv_nxt) {
+		tcp_reqsk_send_challenge_ack(sk, skb, req);
+		return NULL;
+	}
+
 	/* RFC793: "second check the RST bit" and
 	 *	   "fourth, check the SYN bit"
 	 */
-- 
2.34.1


^ permalink raw reply related

* [PATCH net 2/2] selftests/net: packetdrill: cover RST validation in SYN-RECEIVED
From: Yuxiang Yang @ 2026-07-17  8:14 UTC (permalink / raw)
  To: netdev
  Cc: linux-kernel, linux-kselftest, Eric Dumazet, Neal Cardwell,
	Kuniyuki Iwashima, David S . Miller, Jakub Kicinski, Paolo Abeni,
	Simon Horman, Shuah Khan, Yuxiang Yang, Yizhou Zhao, Ao Wang,
	Xuewei Feng, Qi Li, Ke Xu, yyxroy22
In-Reply-To: <20260717081443.809393-1-yangyx22@mails.tsinghua.edu.cn>

Add packetdrill coverage for the RFC 9293 reset checks on request
sockets in SYN-RECEIVED.  Verify that an exact RST removes the request,
a non-exact in-window RST sends a challenge ACK without removing it,
and an out-of-window RST is silently discarded.

Also cover an RST|ACK with an unacceptable ACK number to ensure RST
sequence validation runs before ACK-field validation.

Signed-off-by: Yuxiang Yang <yangyx22@mails.tsinghua.edu.cn>
---
 .../packetdrill/tcp_rfc5961_rst-syn-recv.pkt  | 61 +++++++++++++++++++
 1 file changed, 61 insertions(+)
 create mode 100644 tools/testing/selftests/net/packetdrill/tcp_rfc5961_rst-syn-recv.pkt

diff --git a/tools/testing/selftests/net/packetdrill/tcp_rfc5961_rst-syn-recv.pkt b/tools/testing/selftests/net/packetdrill/tcp_rfc5961_rst-syn-recv.pkt
new file mode 100644
index 000000000..3fc2de036
--- /dev/null
+++ b/tools/testing/selftests/net/packetdrill/tcp_rfc5961_rst-syn-recv.pkt
@@ -0,0 +1,61 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// RFC 9293 Section 3.10.7.4: in SYN-RECEIVED, an exact RST resets
+// the connection.  A non-exact in-window RST elicits a challenge ACK,
+// while an out-of-window RST is silently discarded.
+
+`./defaults.sh`
+
+// An exact RST removes the request socket.
+   0 socket(..., SOCK_STREAM|SOCK_NONBLOCK, IPPROTO_TCP) = 3
+  +0 setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0
+  +0 bind(3, ..., ...) = 0
+  +0 listen(3, 1) = 0
+  +0 < S 0:0(0) win 1000 <mss 1000,sackOK,nop,nop,nop,wscale 0>
+  +0 > S. 0:0(0) ack 1 <...>
+  +0 < R 1:1(0) win 1000
+ +.1 < . 1:1(0) ack 1 win 1000
+  +0 > R 1:1(0)
+  +0 close(3) = 0
+
+// A non-exact in-window RST gets a challenge ACK and the request survives.
+  +0 socket(..., SOCK_STREAM|SOCK_NONBLOCK, IPPROTO_TCP) = 3
+  +0 setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0
+  +0 bind(3, ..., ...) = 0
+  +0 listen(3, 1) = 0
+  +0 < S 0:0(0) win 1000 <mss 1000,sackOK,nop,nop,nop,wscale 0>
+  +0 > S. 0:0(0) ack 1 <...>
+  +0 < R 2:2(0) win 1000
+  +0 > . 1:1(0) ack 1
+  +0 < . 1:1(0) ack 1 win 1000
+  +0 accept(3, ..., ...) = 4
+  +0 close(4) = 0
+  +0 close(3) = 0
+
+// RST sequence validation precedes ACK validation.  Even an RST|ACK
+// with an unacceptable ACK value gets a challenge ACK.
+  +0 socket(..., SOCK_STREAM|SOCK_NONBLOCK, IPPROTO_TCP) = 3
+  +0 setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0
+  +0 bind(3, ..., ...) = 0
+  +0 listen(3, 1) = 0
+  +0 < S 0:0(0) win 1000 <mss 1000,sackOK,nop,nop,nop,wscale 0>
+  +0 > S. 0:0(0) ack 1 <...>
+  +0 < R. 2:2(0) ack 100 win 1000
+  +0 > . 1:1(0) ack 1
+  +0 < . 1:1(0) ack 1 win 1000
+  +0 accept(3, ..., ...) = 4
+  +0 close(4) = 0
+  +0 close(3) = 0
+
+// An out-of-window RST is silent and does not remove the request.
+  +0 socket(..., SOCK_STREAM|SOCK_NONBLOCK, IPPROTO_TCP) = 3
+  +0 setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0
+  +0 bind(3, ..., ...) = 0
+  +0 listen(3, 1) = 0
+  +0 < S 0:0(0) win 1000 <mss 1000,sackOK,nop,nop,nop,wscale 0>
+  +0 > S. 0:0(0) ack 1 <...>
+  +0 < R 100001:100001(0) win 1000
+ +.1 < . 1:1(0) ack 1 win 1000
+  +0 accept(3, ..., ...) = 4
+  +0 close(4) = 0
+  +0 close(3) = 0
-- 
2.34.1


^ permalink raw reply related

* [PATCH net-next 0/2] Improve wwan qcdm compatibility with user-space tools
From: Daniele Palmas @ 2026-07-17  8:19 UTC (permalink / raw)
  To: Loic Poulain, Sergey Ryazanov, Johannes Berg, Andrew Lunn,
	David Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni
  Cc: netdev, Daniele Palmas

Historically, Qualcomm diagnostic port was a serial device, so user-space
tools were developed using tty specific features. One of the most common
tool is, for example, ModemManager libqcdm, available at
https://gitlab.freedesktop.org/mobile-broadband/ModemManager
which requires tcgetattr/tcsetattr and the possibility to get exclusive
device access to properly work.

The wwan qcdm is not really a tty device, but, besides the lack of tty
functions, it behaves as a standard serial Qualcomm diagnostic port, so,
as c230035c2f2f ("net: wwan: core: implement terminal ioctls for AT port"),
this series add support for terminal ioctls also for qcdm ports and
implement the exclusive open mode feature to improve compatibility with
user-space tools.

This is an incremental change which should not affect the existing tools
using wwan which do not rely on the implemented features.

Daniele Palmas (2):
  net: wwan: add minimalistic IOCTls support also to QCDM port
  net: wwan: add exclusive open mode capability to AT and QCDM ports

 drivers/net/wwan/wwan_core.c | 40 ++++++++++++++++++++++++++++++++++--
 1 file changed, 38 insertions(+), 2 deletions(-)

-- 
2.43.0


^ permalink raw reply

* [PATCH net-next 1/2] net: wwan: add minimalistic IOCTls support also to QCDM port
From: Daniele Palmas @ 2026-07-17  8:19 UTC (permalink / raw)
  To: Loic Poulain, Sergey Ryazanov, Johannes Berg, Andrew Lunn,
	David Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni
  Cc: netdev, Daniele Palmas
In-Reply-To: <20260717081935.2071083-1-dnlplm@gmail.com>

Upstream libqcdm requires IOCTLs support to work, so add the current
AT minimalistic support also to the QCDM port.

Signed-off-by: Daniele Palmas <dnlplm@gmail.com>
---
 drivers/net/wwan/wwan_core.c | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/drivers/net/wwan/wwan_core.c b/drivers/net/wwan/wwan_core.c
index ccce2ad74128..8168239e52c3 100644
--- a/drivers/net/wwan/wwan_core.c
+++ b/drivers/net/wwan/wwan_core.c
@@ -1046,7 +1046,8 @@ static long wwan_port_fops_ioctl(struct file *filp, unsigned int cmd,
 	struct wwan_port *port = filp->private_data;
 	int res;
 
-	if (port->type == WWAN_PORT_AT) {	/* AT port specific IOCTLs */
+	if (port->type == WWAN_PORT_AT || port->type == WWAN_PORT_QCDM) {
+		/* AT and QCDM port specific IOCTLs */
 		res = wwan_port_fops_at_ioctl(port, cmd, arg);
 		if (res != -ENOIOCTLCMD)
 			return res;
-- 
2.43.0


^ permalink raw reply related

* [PATCH net-next 2/2] net: wwan: add exclusive open mode capability to AT and QCDM ports
From: Daniele Palmas @ 2026-07-17  8:19 UTC (permalink / raw)
  To: Loic Poulain, Sergey Ryazanov, Johannes Berg, Andrew Lunn,
	David Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni
  Cc: netdev, Daniele Palmas
In-Reply-To: <20260717081935.2071083-1-dnlplm@gmail.com>

Add exclusive open mode capability to AT and QCDM ports to improve
compatibility with user-space tools using the Qualcomm diagnostic
device (e.g. libqcdm).

Signed-off-by: Daniele Palmas <dnlplm@gmail.com>
---
 drivers/net/wwan/wwan_core.c | 37 +++++++++++++++++++++++++++++++++++-
 1 file changed, 36 insertions(+), 1 deletion(-)

diff --git a/drivers/net/wwan/wwan_core.c b/drivers/net/wwan/wwan_core.c
index 8168239e52c3..3e4491235674 100644
--- a/drivers/net/wwan/wwan_core.c
+++ b/drivers/net/wwan/wwan_core.c
@@ -22,6 +22,7 @@
 #include <linux/termios.h>
 #include <linux/gnss.h>
 #include <linux/wwan.h>
+#include <linux/tty.h>
 #include <net/rtnetlink.h>
 #include <uapi/linux/wwan.h>
 
@@ -79,7 +80,7 @@ struct wwan_device {
  * @data_lock: Port specific data access serialization
  * @headroom_len: SKB reserved headroom size
  * @frag_len: Length to fragment packet
- * @at_data: AT port specific data
+ * @at_data: AT/QCDM port specific data
  * @gnss: Pointer to GNSS device associated with this port
  */
 struct wwan_port {
@@ -98,6 +99,7 @@ struct wwan_port {
 		struct {
 			struct ktermios termios;
 			int mdmbits;
+			unsigned long flags;
 		} at_data;
 		struct gnss_device *gnss;
 	};
@@ -748,6 +750,14 @@ static int wwan_port_op_start(struct wwan_port *port)
 		goto out_unlock;
 	}
 
+	/* Check exclusive open mode for AT and QCDM ports */
+	if ((port->type == WWAN_PORT_AT || port->type == WWAN_PORT_QCDM) &&
+	    test_bit(TTY_EXCLUSIVE, &port->at_data.flags) &&
+	    !capable(CAP_SYS_ADMIN)) {
+		ret = -EBUSY;
+		goto out_unlock;
+	}
+
 	/* If port is already started, don't start again */
 	if (!port->start_count)
 		ret = port->ops->start(port);
@@ -857,6 +867,15 @@ static int wwan_port_fops_release(struct inode *inode, struct file *filp)
 	struct wwan_port *port = filp->private_data;
 
 	wwan_port_op_stop(port);
+
+	/* Clear exclusive flag when last fd is closed */
+	if (port->type == WWAN_PORT_AT || port->type == WWAN_PORT_QCDM) {
+		mutex_lock(&port->ops_lock);
+		if (!port->start_count)
+			clear_bit(TTY_EXCLUSIVE, &port->at_data.flags);
+		mutex_unlock(&port->ops_lock);
+	}
+
 	put_device(&port->dev);
 
 	return 0;
@@ -1031,6 +1050,22 @@ static long wwan_port_fops_at_ioctl(struct wwan_port *port, unsigned int cmd,
 		break;
 	}
 
+	case TIOCEXCL:
+		set_bit(TTY_EXCLUSIVE, &port->at_data.flags);
+		break;
+
+	case TIOCNXCL:
+		clear_bit(TTY_EXCLUSIVE, &port->at_data.flags);
+		break;
+
+	case TIOCGEXCL:
+	{
+		int excl = test_bit(TTY_EXCLUSIVE, &port->at_data.flags);
+
+		ret = put_user(excl, (int __user *)arg);
+		break;
+	}
+
 	default:
 		ret = -ENOIOCTLCMD;
 	}
-- 
2.43.0


^ permalink raw reply related

* Re: [PATCH net-next 5/7] phonet: pep: convert getsockopt to sockopt_t
From: David Laight @ 2026-07-17  8:22 UTC (permalink / raw)
  To: Breno Leitao
  Cc: sdf, David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
	Simon Horman, Alexander Aring, Stefan Schmidt, Miquel Raynal,
	Remi Denis-Courmont, Rémi Denis-Courmont, John Fastabend,
	Sabrina Dubroca, Shuah Khan, netdev, linux-kernel, linux-wpan,
	linux-kselftest, kernel-team
In-Reply-To: <20260716-getsockopt_phase4-v1-5-4f45cb12dce7@debian.org>

On Thu, 16 Jul 2026 06:00:03 -0700
Breno Leitao <leitao@debian.org> wrote:

> Continue converting the proto-layer getsockopt callbacks to the
> sockopt_t interface, splitting pep_getsockopt() into a
> do_pep_getsockopt() helper that takes a sockopt_t.
> 
> The thin pep_getsockopt() wrapper keeps its __user signature for now:
> it builds a user-backed sockopt_t with sockopt_init_user(), calls the
> helper, and writes the returned length back to optlen. The helper uses
> copy_to_iter() instead of copy_to_user(). No functional change.
> 
> Signed-off-by: Breno Leitao <leitao@debian.org>
> ---
>  net/phonet/pep.c | 36 ++++++++++++++++++++++++++----------
>  1 file changed, 26 insertions(+), 10 deletions(-)
> 
> diff --git a/net/phonet/pep.c b/net/phonet/pep.c
> index 60d1a5375725b..c7f4ce894af56 100644
> --- a/net/phonet/pep.c
> +++ b/net/phonet/pep.c
> @@ -1078,17 +1078,11 @@ static int pep_setsockopt(struct sock *sk, int level, int optname,
>  	return err;
>  }
>  
> -static int pep_getsockopt(struct sock *sk, int level, int optname,
> -				char __user *optval, int __user *optlen)
> +static int do_pep_getsockopt(struct sock *sk, int optname, sockopt_t *opt)
>  {
>  	struct pep_sock *pn = pep_sk(sk);
>  	int len, val;
>  
> -	if (level != SOL_PNPIPE)
> -		return -ENOPROTOOPT;
> -	if (get_user(len, optlen))
> -		return -EFAULT;
> -
>  	switch (optname) {
>  	case PNPIPE_ENCAP:
>  		val = pn->ifindex ? PNPIPE_ENCAP_IP : PNPIPE_ENCAP_NONE;
> @@ -1112,11 +1106,33 @@ static int pep_getsockopt(struct sock *sk, int level, int optname,
>  		return -ENOPROTOOPT;
>  	}
>  
> -	len = min_t(unsigned int, sizeof(int), len);
> -	if (put_user(len, optlen))
> +	len = min_t(unsigned int, sizeof(int), opt->optlen);

I'm pretty sure that can be min().
More generally I'm not at all sure about negative lengths.
Historically the user type would have been 'int', but it got replaced
by socklen_t which is probably unsigned.
(IIRC one of the 64bit Unix had started using a 64bit type but I think
it was sun objected to making that change so socklen_t was born.)

Also truncating below 4 bytes makes no sense here on BE systems.
Some code will try to write the significant bytes out, but that
would be better done in the wrapper.

> +	opt->optlen = len;
> +	if (copy_to_iter(&val, len, &opt->iter_out) != len)
>  		return -EFAULT;
> -	if (copy_to_user(optval, &val, len))
> +	return 0;
> +}
> +
> +static int pep_getsockopt(struct sock *sk, int level, int optname,
> +			  char __user *optval, int __user *optlen)
> +{
> +	sockopt_t opt;
> +	int err;
> +
> +	if (level != SOL_PNPIPE)
> +		return -ENOPROTOOPT;
> +
> +	err = sockopt_init_user(&opt, optval, optlen);

Have I not looked at these before?
Why is this here, not in the outer code?

	David

> +	if (err)
> +		return err;
> +
> +	err = do_pep_getsockopt(sk, optname, &opt);
> +	if (err)
> +		return err;
> +
> +	if (put_user(opt.optlen, optlen))
>  		return -EFAULT;
> +
>  	return 0;
>  }
>  
> 


^ permalink raw reply

* [PATCH net v3] sctp: socket: remove unused 'err' parameter from sctp_skb_recv_datagram
From: luoqing @ 2026-07-17  8:20 UTC (permalink / raw)
  To: jedrzej.jagielski
  Cc: davem, edumazet, horms, kuba, linux-kernel, linux-sctp,
	lucien.xin, luoqing, marcelo.leitner, netdev, pabeni
In-Reply-To: <PH0PR11MB5902DBD696C30DB0A2308A49F0C72@PH0PR11MB5902.namprd11.prod.outlook.com>

From: luoqing <luoqing@kylinos.cn>

The 'err' parameter in sctp_skb_recv_datagram() is never used by any
of its callers. Both sctp_recvmsg() and sctp_ulpevent_read_nxtinfo()
pass the address of a local variable but never check its value after
the function returns, rendering the parameter completely useless.

Remove the unused parameter to simplify the function signature and
eliminate dead code.

Signed-off-by: luoqing <luoqing@kylinos.cn>
---
 include/net/sctp/sctp.h |  2 +-
 net/sctp/socket.c       | 10 +++-------
 net/sctp/ulpevent.c     |  3 +--
 3 files changed, 5 insertions(+), 10 deletions(-)

diff --git a/include/net/sctp/sctp.h b/include/net/sctp/sctp.h
index d50c27812504..b86d50d6b146 100644
--- a/include/net/sctp/sctp.h
+++ b/include/net/sctp/sctp.h
@@ -97,7 +97,7 @@ void sctp_sock_rfree(struct sk_buff *skb);
 
 extern struct percpu_counter sctp_sockets_allocated;
 int sctp_asconf_mgmt(struct sctp_sock *, struct sctp_sockaddr_entry *);
-struct sk_buff *sctp_skb_recv_datagram(struct sock *, int, int *);
+struct sk_buff *sctp_skb_recv_datagram(struct sock *sk, int flags);
 
 typedef int (*sctp_callback_t)(struct sctp_endpoint *, struct sctp_transport *, void *);
 void sctp_transport_walk_start(struct rhashtable_iter *iter);
diff --git a/net/sctp/socket.c b/net/sctp/socket.c
index c7b9e325ec1c..3804382d78e0 100644
--- a/net/sctp/socket.c
+++ b/net/sctp/socket.c
@@ -2123,7 +2123,7 @@ static int sctp_recvmsg(struct sock *sk, struct msghdr *msg, size_t len,
 		goto out;
 	}
 
-	skb = sctp_skb_recv_datagram(sk, flags, &err);
+	skb = sctp_skb_recv_datagram(sk, flags);
 	if (!skb)
 		goto out;
 
@@ -9082,7 +9082,7 @@ static int sctp_wait_for_packet(struct sock *sk, int *err, long *timeo_p)
  * Note: This is pretty much the same routine as in core/datagram.c
  * with a few changes to make lksctp work.
  */
-struct sk_buff *sctp_skb_recv_datagram(struct sock *sk, int flags, int *err)
+struct sk_buff *sctp_skb_recv_datagram(struct sock *sk, int flags)
 {
 	int error;
 	struct sk_buff *skb;
@@ -9120,17 +9120,13 @@ struct sk_buff *sctp_skb_recv_datagram(struct sock *sk, int flags, int *err)
 		if (sk->sk_shutdown & RCV_SHUTDOWN)
 			break;
 
-
 		/* User doesn't want to wait.  */
 		error = -EAGAIN;
 		if (!timeo)
 			goto no_packet;
-	} while (sctp_wait_for_packet(sk, err, &timeo) == 0);
-
-	return NULL;
+	} while (sctp_wait_for_packet(sk, &error, &timeo) == 0);
 
 no_packet:
-	*err = error;
 	return NULL;
 }
 
diff --git a/net/sctp/ulpevent.c b/net/sctp/ulpevent.c
index 8920ca92a011..8ed51a15c3a4 100644
--- a/net/sctp/ulpevent.c
+++ b/net/sctp/ulpevent.c
@@ -1061,9 +1061,8 @@ void sctp_ulpevent_read_nxtinfo(const struct sctp_ulpevent *event,
 				struct sock *sk)
 {
 	struct sk_buff *skb;
-	int err;
 
-	skb = sctp_skb_recv_datagram(sk, MSG_PEEK | MSG_DONTWAIT, &err);
+	skb = sctp_skb_recv_datagram(sk, MSG_PEEK | MSG_DONTWAIT);
 	if (skb != NULL) {
 		__sctp_ulpevent_read_nxtinfo(sctp_skb2event(skb),
 					     msghdr, skb);
-- 
2.25.1


^ permalink raw reply related

* Re: [PATCH v8 4/9] nvmem: layouts: Support fixed-layout as the nvmem device node itself
From: Bartosz Golaszewski @ 2026-07-17  8:29 UTC (permalink / raw)
  To: Bartosz Golaszewski
  Cc: linux-mmc, devicetree, linux-kernel, linux-arm-msm, linux-block,
	linux-wireless, ath10k, linux-bluetooth, netdev, daniel,
	Loic Poulain, Ulf Hansson, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Bjorn Andersson, Konrad Dybcio, Jens Axboe,
	Johannes Berg, Jeff Johnson, Marcel Holtmann,
	Luiz Augusto von Dentz, Balakrishna Godavarthi, Rocky Liao,
	David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
	Simon Horman, Srinivas Kandagatla, Andrew Lunn, Heiner Kallweit,
	Russell King, Saravana Kannan, Christian Marangi
In-Reply-To: <CAMRc=MdNehSRQT3MUs+QtbZsHcUCS8YjyZchTgHCVv-HaLQkeA@mail.gmail.com>

On Wed, 15 Jul 2026 14:07:24 +0200, Bartosz Golaszewski <brgl@kernel.org> said:
> On Fri, 3 Jul 2026 15:45:17 +0200, Loic Poulain <loic.poulain@oss.qualcomm.com> said:
>> of_nvmem_layout_get_container() only looks for a child node named
>> "nvmem-layout" to locate the cell definitions. This does not cover
>> providers whose device tree node is itself the fixed-layout container,
>> such as an eMMC boot partition block device whose fwnode points directly
>> at a "fixed-layout" compatible partitions node.
>>
>> When no "nvmem-layout" child is present, fall back to returning the nvmem
>> device node itself if it is compatible with "fixed-layout", so that its
>> cells are parsed by nvmem_add_cells_from_fixed_layout().
>>
>> Signed-off-by: Loic Poulain <loic.poulain@oss.qualcomm.com>
>> ---
>>  drivers/nvmem/layouts.c | 13 ++++++++++++-
>>  1 file changed, 12 insertions(+), 1 deletion(-)
>>
>> diff --git a/drivers/nvmem/layouts.c b/drivers/nvmem/layouts.c
>> index b90584e1b99eab4217cbe7ec48373e18a7caf0dc..efa631ce7283bdd6c8ecda75915911b5e3a33c99 100644
>> --- a/drivers/nvmem/layouts.c
>> +++ b/drivers/nvmem/layouts.c
>> @@ -167,7 +167,18 @@ static int nvmem_layout_bus_populate(struct nvmem_device *nvmem,
>>
>>  struct device_node *of_nvmem_layout_get_container(struct nvmem_device *nvmem)
>>  {
>> -	return of_get_child_by_name(nvmem->dev.of_node, "nvmem-layout");
>> +	struct device_node *np;
>> +
>> +	/* Search for nvmem-layout child */
>> +	np = of_get_child_by_name(nvmem->dev.of_node, "nvmem-layout");
>> +	if (np)
>> +		return np;
>> +
>> +	/* The nvmem of_node is itself a fixed-layout node */
>> +	if (of_device_is_compatible(nvmem->dev.of_node, "fixed-layout"))
>> +		return of_node_get(nvmem->dev.of_node);
>> +
>> +	return NULL;
>>  }
>>  EXPORT_SYMBOL_GPL(of_nvmem_layout_get_container);
>>
>>
>> --
>> 2.34.1
>>
>>
>
> I have it on my TODO list to convert nvmem layouts to be fwnode-agnostic. While
> I'm not sure when I'll have the time to look into it, I think it makes sense
> to not introduce any new OF-specific interfaces. Can you make it into:
>
>   struct fwnode_handle *nvmem_layout_get_container_node(struct nvmem_device *nvmem);
>
> by replacing of_get_child_by_name() with fwnode_get_name_child_node() and
> of_device_is_compatible() with device_is_compatible()? That would mean less
> churn in the future.
>
> Other than that, it looks good.
>
> Bart
>

Scratch that, I didn't realize this isn't a new function but a rework of an
existing one.

Reviewed-by: Bartosz Golaszewski <bartosz.golaszewski@oss.qualcomm.com>

Bart

^ permalink raw reply

* Re: [PATCH net-next] net/sched: sch_cake: skip clearing unused tins during rate adjustment
From: Toke Høiland-Jørgensen @ 2026-07-17  8:31 UTC (permalink / raw)
  To: Jonas Köppeler, Jamal Hadi Salim, Jiri Pirko,
	David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
	Simon Horman
  Cc: cake, netdev, linux-kernel, Jonas Köppeler, Mike Pham
In-Reply-To: <20260716-sch_cake-skip-clearing-tins-v1-1-d9787df20c28@tu-berlin.de>

Jonas Köppeler <j.koeppeler@tu-berlin.de> writes:

> When cake_configure_rates() is called from the dequeue path with
> rate_adjust=true, it only needs to update the rate parameters. The
> loop that clears the unused tins is both unnecessary and harmful in
> this path:
>
>  - cake_clear_tin() overwrites q->cur_tin and q->cur_flow, which are
>    actively used by cake_dequeue(), corrupting the dequeue state.
>  - iterating over the unused tins and their internal queues to purge
>    packets adds needless overhead to the hot path.
>
> Skip the entire loop when rate_adjust is set, as neither
> cake_clear_tin() nor the mtu_time update are needed when only the
> rate changes.
>
> Fixes: 15c2715a5264 ("net/sched: sch_cake: fixup cake_mq rate adjustment for diffserv config")
> Signed-off-by: Jonas Köppeler <j.koeppeler@tu-berlin.de>
> Tested-by: Mike Pham <mikepham4321@gmail.com>

Do you have any performance numbers to show the impact of this?

-Toke

^ permalink raw reply

* Re: [PATCH net-next v2 1/2] dpll: zl3073x: add channel ToD, phase step and TIE operations
From: Petr Oros @ 2026-07-17  8:41 UTC (permalink / raw)
  To: Ivan Vecera, netdev
  Cc: Chris du Quesnay, Arkadiusz Kubalewski, David S. Miller,
	Jakub Kicinski, Jiri Pirko, Michal Schmidt, Paolo Abeni,
	Pasi Vaananen, Prathosh Satish, Richard Cochran, Simon Horman,
	Vadim Fedorenko, linux-kernel
In-Reply-To: <20260713103739.1553284-2-ivecera@redhat.com>


On 7/13/26 12:37, Ivan Vecera wrote:
> 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
>    */

lgtm,

Reviewed-by: Petr Oros <poros@redhat.com>



^ permalink raw reply

* Re: [PATCH net-next v2 2/2] dpll: zl3073x: add PTP clock support
From: Petr Oros @ 2026-07-17  8:43 UTC (permalink / raw)
  To: Ivan Vecera, netdev
  Cc: Chris du Quesnay, Arkadiusz Kubalewski, David S. Miller,
	Jakub Kicinski, Jiri Pirko, Michal Schmidt, Paolo Abeni,
	Pasi Vaananen, Prathosh Satish, Richard Cochran, Simon Horman,
	Vadim Fedorenko, linux-kernel
In-Reply-To: <20260713103739.1553284-3-ivecera@redhat.com>


On 7/13/26 12:37, Ivan Vecera wrote:
> 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);
lgtm,

Reviewed-by: Petr Oros <poros@redhat.com>


^ permalink raw reply

* [PATCH net] octeontx2-pf: tc: fix egress ratelimiting
From: nshettyj @ 2026-07-17  8:43 UTC (permalink / raw)
  To: netdev, linux-kernel
  Cc: Hariprasad Kelam, Nitin Shetty J, Sunil Goutham, Geetha sowjanya,
	Ratheesh Kannoth, Subbaraya Sundeep, Bharat Bhushan, Andrew Lunn,
	David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
	Naveen Mamindlapalli

From: Hariprasad Kelam <hkelam@marvell.com>

The egress rate calculation computes an incorrect mantissa and exponent,
causing up to ~50% deviation from the configured rate at lower speeds.

Rework the computation to follow the hardware rate formula:

	rate = 2 * (1 + mantissa/256) * 2^exp / (1 << div_exp)

Keep div_exp = 0 and derive exp and mantissa from half of the requested
rate. Rates below 2 Mbps are floored to the smallest encodable step
(exp = 0, mantissa = 0).

Fixes: e638a83f167e ("octeontx2-pf: TC_MATCHALL egress ratelimiting offload")
Signed-off-by: Hariprasad Kelam <hkelam@marvell.com>
Signed-off-by: Nitin Shetty J <nshettyj@marvell.com>
---
 .../ethernet/marvell/octeontx2/nic/otx2_tc.c  | 29 ++++++++++---------
 1 file changed, 16 insertions(+), 13 deletions(-)

diff --git a/drivers/net/ethernet/marvell/octeontx2/nic/otx2_tc.c b/drivers/net/ethernet/marvell/octeontx2/nic/otx2_tc.c
index 40162b08014d..0b46ec29e64e 100644
--- a/drivers/net/ethernet/marvell/octeontx2/nic/otx2_tc.c
+++ b/drivers/net/ethernet/marvell/octeontx2/nic/otx2_tc.c
@@ -30,6 +30,7 @@
 #define OTX2_UNSUPP_LSE_DEPTH		GENMASK(6, 4)
 
 #define MCAST_INVALID_GRP		(-1U)
+#define RATE_MANTISSA_BITS		8
 
 static void otx2_get_egress_burst_cfg(struct otx2_nic *nic, u32 burst,
 				      u32 *burst_exp, u32 *burst_mantissa)
@@ -66,28 +67,30 @@ static void otx2_get_egress_burst_cfg(struct otx2_nic *nic, u32 burst,
 static void otx2_get_egress_rate_cfg(u64 maxrate, u32 *exp,
 				     u32 *mantissa, u32 *div_exp)
 {
-	u64 tmp;
-
 	/* Rate calculation by hardware
 	 *
 	 * PIR_ADD = ((256 + mantissa) << exp) / 256
 	 * rate = (2 * PIR_ADD) / ( 1 << div_exp)
 	 * The resultant rate is in Mbps.
+	 *
+	 * Use div_exp = 0 and compute exp/mantissa for maxrate / 2; the
+	 * leading factor of two yields the full rate. Rates below 2 Mbps
+	 * are floored to the smallest step (exp = 0, mantissa = 0).
 	 */
 
-	/* 2Mbps to 100Gbps can be expressed with div_exp = 0.
-	 * Setting this to '0' will ease the calculation of
-	 * exponent and mantissa.
-	 */
 	*div_exp = 0;
-
 	if (maxrate) {
-		*exp = ilog2(maxrate) ? ilog2(maxrate) - 1 : 0;
-		tmp = maxrate - rounddown_pow_of_two(maxrate);
-		if (maxrate < MAX_RATE_MANTISSA)
-			*mantissa = tmp * 2;
-		else
-			*mantissa = tmp / (1ULL << (*exp - 7));
+		maxrate = maxrate / 2;
+		if (!maxrate) {
+			/* Rates below 2 Mbps map to the smallest step */
+			*exp = 0;
+			*mantissa = 0;
+		} else {
+			*exp = ilog2(maxrate);
+			/* Clear MSB and derive fractional bits */
+			maxrate &= ~BIT(*exp);
+			*mantissa = (maxrate << RATE_MANTISSA_BITS) >> *exp;
+		}
 	} else {
 		/* Instead of disabling rate limiting, set all values to max */
 		*exp = MAX_RATE_EXPONENT;
-- 
2.48.1


^ permalink raw reply related

* [PATCH v3 net-next] net: rnpgbe: Delete a null pointer check in rnpgbe_rm_adapter()
From: Markus Elfring @ 2026-07-17  8:38 UTC (permalink / raw)
  To: netdev, Andrew Lunn, David S. Miller, Eric Dumazet,
	Jakub Kicinski, Julia Lawall, MD Danish Anwar, Michael Grzeschik,
	Paolo Abeni, Uwe Kleine-König, Vadim Fedorenko, Yibo Dong
  Cc: LKML, kernel-janitors, Dan Carpenter

From: Markus Elfring <elfring@users.sourceforge.net>
Date: Fri, 17 Jul 2026 10:20:15 +0200

The check for the pointer “mucse” was identified as redundant because
the relevant address information was preserved according to calls of
the functions “pci_set_drvdata” and “pci_get_drvdata”.
Thus remove it.

This issue was detected by using the Coccinelle software.

See also:
Reconsidering pointer dereferences before null pointer checks (with SmPL)
2023-04-09
https://lore.kernel.org/cocci/1a11455f-ab57-dce0-1677-6beb8492a257@web.de/
https://sympa.inria.fr/sympa/arc/cocci/2023-04/msg00021.html

Signed-off-by: Markus Elfring <elfring@users.sourceforge.net>
---

v3:
Andrew Lunn and Julia Lawall requested an extended change description.

v2:
Andrew Lunn requested the removal of a null pointer check.


 drivers/net/ethernet/mucse/rnpgbe/rnpgbe_main.c | 2 --
 1 file changed, 2 deletions(-)

diff --git a/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_main.c b/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_main.c
index 70a2b0082ba8..82a3fbfb3c0a 100644
--- a/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_main.c
+++ b/drivers/net/ethernet/mucse/rnpgbe/rnpgbe_main.c
@@ -249,8 +249,6 @@ static void rnpgbe_rm_adapter(struct pci_dev *pdev)
 	struct net_device *netdev;
 	int err;
 
-	if (!mucse)
-		return;
 	netdev = mucse->netdev;
 	unregister_netdev(netdev);
 	err = rnpgbe_send_notify(hw, false, mucse_fw_powerup);
-- 
2.55.0


^ permalink raw reply related

* [PATCH net-next] ice: rephrase LLDP filter fallback message
From: Thomas Bogendoerfer @ 2026-07-17  8:44 UTC (permalink / raw)
  To: Tony Nguyen, Przemek Kitszel, Andrew Lunn, David S. Miller,
	Eric Dumazet, Jakub Kicinski, Paolo Abeni, intel-wired-lan,
	netdev, linux-kernel

Seeing an error message in a logfile triggers scripts/admins thinking of
problems. The LLDP fallback message itself is not an error, it just
indicates it's using a different filter mechanism. So rephrase to a
less "scary" message.

Signed-off-by: Thomas Bogendoerfer <tbogendoerfer@suse.de>
---
 drivers/net/ethernet/intel/ice/ice_lib.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/net/ethernet/intel/ice/ice_lib.c b/drivers/net/ethernet/intel/ice/ice_lib.c
index 8cdc4fda89e9..59341b5a0c05 100644
--- a/drivers/net/ethernet/intel/ice/ice_lib.c
+++ b/drivers/net/ethernet/intel/ice/ice_lib.c
@@ -2097,7 +2097,7 @@ void ice_vsi_cfg_sw_lldp(struct ice_vsi *vsi, bool tx, bool create)
 				goto report;
 
 			dev_info(dev,
-				 "Failed to add generic LLDP Rx filter on VSI %i error: %d, falling back to specialized AQ control\n",
+				 "Using generic LLDP Rx filter on VSI %i is not possible (%d), falling back to specialized AQ control\n",
 				 vsi->vsi_num, status);
 		}
 
-- 
2.51.0


^ permalink raw reply related

* Re: [PATCH net] mac802154: hold an interface reference across the scan worker
From: Miquel Raynal @ 2026-07-17  8:57 UTC (permalink / raw)
  To: Ibrahim Hashimov
  Cc: Alexander Aring, Stefan Schmidt, David S . Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Simon Horman, linux-wpan, netdev,
	linux-kernel, stable
In-Reply-To: <20260710140927.13228-1-security@auditcode.ai>

Hi Ibrahim,

> +	/* From here on sdata->dev is dereferenced after rcu_read_unlock() and
> +	 * outside the rtnl: in the dev_err()/dev_dbg() traces below, in
> +	 * mac802154_transmit_beacon_req() (skb->dev = sdata->dev) and in the
> +	 * end_scan mac802154_scan_cleanup_locked() call. A concurrent teardown
> +	 * of that interface (NL802154_CMD_DEL_INTERFACE ->
> +	 * ieee802154_if_remove(), or a full PHY removal via
> +	 * ieee802154_unregister_hw()) can unregister the netdev; the actual
> +	 * free then runs asynchronously from netdev_run_todo() with the rtnl
> +	 * already dropped, so neither holding the rtnl nor the per-PHY
> +	 * IEEE802154_IS_SCANNING flag keeps sdata->dev alive here. Pin it with
> +	 * a reference taken while we still hold the RCU read lock (so the
> +	 * netdev cannot be freed before we bump the refcount) and drop it at
> +	 * every exit below. This blocks the teardown's netdev_run_todo() until
> +	 * this worker iteration is done; it cannot self-deadlock because the
> +	 * unregistering task claims the net_todo_list entry under the rtnl, so
> +	 * the blocking netdev_wait_allrefs_any() always runs on that task, not
> +	 * on this single-threaded worker.
> +	 */

The patch seems correct, the fix as well, but can we trim down this huge
comment please?

Thanks,
Miquèl

^ permalink raw reply

* Re: [PATCH net-next v2 1/2] dt-bindings: net: Add Maxio MAE0621A PHY
From: Krzysztof Kozlowski @ 2026-07-17  9:05 UTC (permalink / raw)
  To: Liu Changjie
  Cc: Andrew Lunn, Heiner Kallweit, David S. Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, netdev, Russell King, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Luo Jie, Wolfram Sang,
	devicetree, linux-kernel
In-Reply-To: <MN0PR19MB6091F865EF7FE6AC1EE41C10ACC62@MN0PR19MB6091.namprd19.prod.outlook.com>

On Fri, Jul 17, 2026 at 11:44:03AM +0800, Liu Changjie wrote:
> Document the MAE0621A PHY ID and its optional 125 MHz CLKOUT
> setting. Boards which do not provide the property retain the hardware
> strap or bootloader configuration.
> 
> Only the 125 MHz setting is exposed because it is the only setting
> exercised on hardware.
> 
> Signed-off-by: Liu Changjie <liucj1228@outlook.com>
> ---

How did you address previous feedback?

If you ignore feedback, shall we ignore the patch?

Best regards,
Krzysztof


^ permalink raw reply

* RE: [PATCH net V2] net: phy: motorcomm: read EEE abilities in yt8521_get_features()
From: Clark Wang (OSS) @ 2026-07-17  9:06 UTC (permalink / raw)
  To: Andrew Lunn
  Cc: Frank.Sae@motor-comm.com, leitao@debian.org, hkallweit1@gmail.com,
	linux@armlinux.org.uk, davem@davemloft.net, edumazet@google.com,
	kuba@kernel.org, pabeni@redhat.com, netdev@vger.kernel.org,
	linux-kernel@vger.kernel.org, Clark Wang (OSS),
	imx@lists.linux.dev, Clark Wang
In-Reply-To: <5d55cc0f-4b9f-42df-880a-290faf201cc8@lunn.ch>

> > @@ -2490,7 +2490,8 @@ static int yt8521_get_features(struct phy_device
> *phydev)
> >  		/* add fiber's features to phydev->supported */
> >  		yt8521_prepare_fiber_features(phydev, phydev->supported);
> >  	}
> > -	return ret;
> > +
> > +	return ret ? ret : genphy_c45_read_eee_abilities(phydev);
> 
> Using a ? like this is very uncommon.
> 
> Please change it to the usual
> 
>         if (ret < 0)
>                return ret;
> 
> 	return genphy_c45_read_eee_abilities(phydev);
> 
> It might also be better to put the test after
> yt8521_get_features_paged() call, since that is the only place missing the
> test.

Hi Andrew,

Sorry, I missed your email. Yes, your suggestion is good, it looks more usual that way.
I will send V3.
Thank you very much!

Best Regards,
Clark Wang


^ permalink raw reply

* [PATCH v5 1/4] dmaengine: xilinx_dma: Fix MCDMA descriptor fields based on DMA direction
From: Srinivas Neeli @ 2026-07-17  9:08 UTC (permalink / raw)
  To: Vinod Koul, Radhey Shyam Pandey
  Cc: Frank Li, Michal Simek, Andrew Lunn, David S . Miller,
	Eric Dumazet, Jakub Kicinski, Paolo Abeni, Suraj Gupta,
	Marek Vasut, Tomi Valkeinen, Alex Bereza, Folker Schwesinger,
	dmaengine, netdev, linux-arm-kernel, linux-kernel, git
In-Reply-To: <20260717090824.2364230-1-srinivas.neeli@amd.com>

The MCDMA BD format differs between memory-to-device (MM2S) and
device-to-memory (S2MM) directions, but the driver was using generic
'status' and 'sideband_status' fields for both. This led to incorrect
residue calculations when the hardware updates direction-specific fields.

Refactor the descriptor structure to use unions with direction-specific
field mappings, and update the residue calculation logic to select the
correct status field based on DMA direction.

This matches the hardware descriptor layout and fixes incorrect
residue reporting.

Fixes: 6ccd692bfb7f ("dmaengine: xilinx_dma: Add Xilinx AXI MCDMA Engine driver support")
Reviewed-by: Radhey Shyam Pandey <radhey.shyam.pandey@amd.com>
Signed-off-by: Srinivas Neeli <srinivas.neeli@amd.com>
---
Changes in V5:
 - No change.

Changes in V4:
 - Added Reviewed-by: Radhey Shyam Pandey.

Changes in V3:
 - Renamed subject from "for MM2S vs S2MM" to "based on DMA direction".
 - Reworded commit message for clarity.
 - Added XILINX_MCDMA_BD_HW_SIZE macro and static_assert to verify
   descriptor size at compile time.
 - Refactored residue calculation to separate addition and subtraction
   operations for better readability.

Changes in V2:
 - No change.
---
 drivers/dma/xilinx/xilinx_dma.c | 26 +++++++++++++++++++-------
 1 file changed, 19 insertions(+), 7 deletions(-)

diff --git a/drivers/dma/xilinx/xilinx_dma.c b/drivers/dma/xilinx/xilinx_dma.c
index 98b41b8f8915..ff5b29a808e9 100644
--- a/drivers/dma/xilinx/xilinx_dma.c
+++ b/drivers/dma/xilinx/xilinx_dma.c
@@ -223,6 +223,7 @@
 #define XILINX_MCDMA_IRQ_ERR_MASK		BIT(7)
 #define XILINX_MCDMA_BD_EOP			BIT(30)
 #define XILINX_MCDMA_BD_SOP			BIT(31)
+#define XILINX_MCDMA_BD_HW_SIZE			64
 
 /**
  * struct xilinx_vdma_desc_hw - Hardware Descriptor
@@ -277,8 +278,10 @@ struct xilinx_axidma_desc_hw {
  * @buf_addr_msb: MSB of Buffer address @0x0C
  * @rsvd: Reserved field @0x10
  * @control: Control Information field @0x14
- * @status: Status field @0x18
- * @sideband_status: Status of sideband signals @0x1C
+ * @mm2s_ctrl_sideband: Sideband control info for mm2s @0x18
+ * @s2mm_status: Status field for s2mm @0x18
+ * @mm2s_status: Status field for mm2s @0x1C
+ * @s2mm_sideband_status: Sideband status for s2mm @0x1C
  * @app: APP Fields @0x20 - 0x30
  */
 struct xilinx_aximcdma_desc_hw {
@@ -288,10 +291,17 @@ struct xilinx_aximcdma_desc_hw {
 	u32 buf_addr_msb;
 	u32 rsvd;
 	u32 control;
-	u32 status;
-	u32 sideband_status;
+	union {
+		u32 mm2s_ctrl_sideband;
+		u32 s2mm_status;
+	};
+	union {
+		u32 mm2s_status;
+		u32 s2mm_sideband_status;
+	};
 	u32 app[XILINX_DMA_NUM_APP_WORDS];
 } __aligned(64);
+static_assert(sizeof(struct xilinx_aximcdma_desc_hw) == XILINX_MCDMA_BD_HW_SIZE);
 
 /**
  * struct xilinx_cdma_desc_hw - Hardware Descriptor
@@ -1015,9 +1025,11 @@ static u32 xilinx_dma_get_residue(struct xilinx_dma_chan *chan,
 					   struct xilinx_aximcdma_tx_segment,
 					   node);
 			aximcdma_hw = &aximcdma_seg->hw;
-			residue +=
-				(aximcdma_hw->control & chan->xdev->max_buffer_len) -
-				(aximcdma_hw->status & chan->xdev->max_buffer_len);
+			residue += aximcdma_hw->control & chan->xdev->max_buffer_len;
+			if (chan->direction == DMA_DEV_TO_MEM)
+				residue -= aximcdma_hw->s2mm_status & chan->xdev->max_buffer_len;
+			else
+				residue -= aximcdma_hw->mm2s_status & chan->xdev->max_buffer_len;
 		}
 	}
 
-- 
2.43.0

^ permalink raw reply related

* [PATCH v5 0/4] dmaengine: xilinx_dma: MCDMA descriptor and metadata handling improvements
From: Srinivas Neeli @ 2026-07-17  9:08 UTC (permalink / raw)
  To: Vinod Koul, Radhey Shyam Pandey
  Cc: Frank Li, Michal Simek, Andrew Lunn, David S . Miller,
	Eric Dumazet, Jakub Kicinski, Paolo Abeni, Suraj Gupta,
	Marek Vasut, Tomi Valkeinen, Alex Bereza, Folker Schwesinger,
	dmaengine, netdev, linux-arm-kernel, linux-kernel, git

This series improves the Xilinx AXI DMA and MCDMA driver's descriptor
handling and metadata reporting. It fixes direction-specific descriptor
field usage, ensures completion is based on the hardware completion bit
(important with interrupt coalescing), and extends metadata handling to
expose status and sideband fields alongside APP fields.

The axienet driver is updated to derive RX frame length from the standard
dmaengine residue mechanism rather than descriptor APP fields, making it
work on designs where the AXI4-Stream status/control interface is not
present.

Changes in V5:
 - Patch 4: Take the metadata pointer from the SOP descriptor for TX and
   the EOF descriptor for RX, matching where the hardware reads and writes
   the fields (TX previously used the EOF descriptor). AXI DMA now exposes
   only the APP words (20 bytes) in both directions. AXI MCDMA exposes the
   control sideband, status and APP fields (28 bytes), with the sideband
   position differing between MM2S and S2MM. Reworked the kernel-doc index
   table and commit message accordingly.
 - Patches 1-3: No functional change.

Changes in V4:
 - Patch 1: Added Reviewed-by: Radhey Shyam Pandey.
 - Patch 2: Reworded commit message to reference the AXIDMA fix it mirrors;
   added Reviewed-by: Radhey Shyam Pandey.
 - Patch 3: Renamed subject to "...from residue in dmaengine path";
   condensed commit message; dropped Fixes tag.
 - Patch 4: Restructured get_metadata_ptr() so AXIDMA is the fall-through
   path (no WARN_ON_ONCE); rewrote the kernel-doc as an index table
   covering AXI DMA, MCDMA S2MM and MCDMA MM2S; condensed commit message.

Changes in V3:
 - Patch 1: Renamed subject, added static_assert for descriptor size,
   refactored residue calculation for clarity.
 - Patch 2: Added Fixes tag, expanded commit message explaining interrupt
   coalescing scenario, simplified completion check logic.
 - Patch 3: New patch - axienet now uses result->residue for RX length
   instead of APP metadata, removing dependency on status/control stream.
 - Patch 4: Complete rewrite - metadata pointer now starts at status field
   (index 0) exposing status/sideband to clients; uses EOF descriptor;
   removed 'chan' field from descriptor struct.
 - Dropped V2 patches 4/5 (dt-bindings) and 5/5 (xferred_bytes) as the
   approach changed to use standard residue mechanism.

Changes in V2:
 - Rebased on the AXI DMA binding YAML conversion.
 - Added xlnx,include-stscntrl-strm in the YAML binding.
 - Clarified cover letter to reflect metadata behavior with and without
   APP fields.
https://lore.kernel.org/all/20260309033444.3472359-1-abin.joseph@amd.com/

Srinivas Neeli (3):
  dmaengine: xilinx_dma: Fix MCDMA descriptor fields based on DMA
    direction
  dmaengine: xilinx_dma: Move descriptors to done list based on
    completion bit
  net: xilinx: axienet: Derive RX frame length from residue in dmaengine
    path

Suraj Gupta (1):
  dmaengine: xilinx_dma: Extend metadata handling for AXI DMA and MCDMA

 drivers/dma/xilinx/xilinx_dma.c               | 85 ++++++++++++++++---
 .../net/ethernet/xilinx/xilinx_axienet_main.c | 14 ++-
 2 files changed, 78 insertions(+), 21 deletions(-)

-- 
2.43.0

^ permalink raw reply

* [PATCH v5 2/4] dmaengine: xilinx_dma: Move descriptors to done list based on completion bit
From: Srinivas Neeli @ 2026-07-17  9:08 UTC (permalink / raw)
  To: Vinod Koul, Radhey Shyam Pandey
  Cc: Frank Li, Michal Simek, Andrew Lunn, David S . Miller,
	Eric Dumazet, Jakub Kicinski, Paolo Abeni, Suraj Gupta,
	Marek Vasut, Tomi Valkeinen, Alex Bereza, Folker Schwesinger,
	dmaengine, netdev, linux-arm-kernel, linux-kernel, git
In-Reply-To: <20260717090824.2364230-1-srinivas.neeli@amd.com>

In AXI MCDMA, xilinx_dma_complete_descriptor() walks the channel's
active_list and unconditionally moves every entry to the done_list. The
MCDMA IOC interrupt handler invokes this function on every
interrupt-on-completion, but with interrupt coalescing (IRQThreshold > 1)
an IOC interrupt may fire after only a subset of the queued descriptors
have actually been processed by the hardware. As a result, descriptors
whose completion bit is not yet set in the BD status were being reported
as completed to client drivers.

Add a check for the descriptor completion bit before moving entries from
the active list to the done list, using the appropriate direction-
specific status field (s2mm_status for DMA_DEV_TO_MEM, mm2s_status for
DMA_MEM_TO_DEV).

This mirrors the AXIDMA fix in commit 7bcdaa658102 ("dmaengine:
xilinx_dma: Freeup active list based on descriptor completion bit").

Fixes: 6ccd692bfb7f ("dmaengine: xilinx_dma: Add Xilinx AXI MCDMA Engine driver support")
Reviewed-by: Radhey Shyam Pandey <radhey.shyam.pandey@amd.com>
Signed-off-by: Srinivas Neeli <srinivas.neeli@amd.com>
---
Changes in V5:
 - No change.

Changes in V4:
 - Reworded commit message to reference the AXIDMA fix it mirrors
   (commit 7bcdaa658102).
 - Added Reviewed-by: Radhey Shyam Pandey.

Changes in V3:
 - Added Fixes tag.
 - Expanded commit message to explain the interrupt coalescing scenario
   and why the has_sg guard is omitted for MCDMA.
 - Changed local variable from 'bool completed' to 'u32 status' for
   cleaner status field access.
 - Simplified completion check logic.

Changes in V2:
 - No change.
---
 drivers/dma/xilinx/xilinx_dma.c | 11 +++++++++++
 1 file changed, 11 insertions(+)

diff --git a/drivers/dma/xilinx/xilinx_dma.c b/drivers/dma/xilinx/xilinx_dma.c
index ff5b29a808e9..1b5b00f08c5f 100644
--- a/drivers/dma/xilinx/xilinx_dma.c
+++ b/drivers/dma/xilinx/xilinx_dma.c
@@ -1784,6 +1784,17 @@ static void xilinx_dma_complete_descriptor(struct xilinx_dma_chan *chan)
 					      struct xilinx_axidma_tx_segment, node);
 			if (!(seg->hw.status & XILINX_DMA_BD_COMP_MASK) && chan->has_sg)
 				break;
+		} else if (chan->xdev->dma_config->dmatype == XDMA_TYPE_AXIMCDMA) {
+			struct xilinx_aximcdma_tx_segment *seg;
+			u32 status;
+
+			seg = list_last_entry(&desc->segments,
+					      struct xilinx_aximcdma_tx_segment,
+					      node);
+			status = (chan->direction == DMA_DEV_TO_MEM) ?
+				seg->hw.s2mm_status : seg->hw.mm2s_status;
+			if (!(status & XILINX_DMA_BD_COMP_MASK))
+				break;
 		}
 		if (chan->has_sg && chan->xdev->dma_config->dmatype !=
 		    XDMA_TYPE_VDMA)
-- 
2.43.0

^ permalink raw reply related


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox