netdev.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* Re: [PATCH net-next 2/3] vsock: convert to getsockopt_iter
From: Bobby Eshleman @ 2026-05-01 17:32 UTC (permalink / raw)
  To: Breno Leitao
  Cc: David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
	Simon Horman, Stefano Garzarella, Shuah Khan, sdf.kernel, netdev,
	linux-kernel, virtualization, linux-kselftest, kernel-team
In-Reply-To: <20260501-getsock_one-v1-2-810ce23ea70e@debian.org>

On Fri, May 01, 2026 at 08:52:52AM -0700, Breno Leitao wrote:
> Convert AF_VSOCK's getsockopt implementation to use the new
> getsockopt_iter callback with sockopt_t. The single
> vsock_connectible_getsockopt() callback is shared by both
> vsock_stream_ops and vsock_seqpacket_ops, so both proto_ops are
> updated to use .getsockopt_iter.
> 
> Key changes:
> - Replace (char __user *optval, int __user *optlen) with sockopt_t *opt
> - Use opt->optlen for buffer length (input) and returned size (output)
> - Use copy_to_iter() instead of put_user()/copy_to_user()
> 
> Signed-off-by: Breno Leitao <leitao@debian.org>
> ---
>  net/vmw_vsock/af_vsock.c | 16 +++++++---------
>  1 file changed, 7 insertions(+), 9 deletions(-)
> 
> diff --git a/net/vmw_vsock/af_vsock.c b/net/vmw_vsock/af_vsock.c
> index 44037b066a5ff..d4a97eeb596e6 100644
> --- a/net/vmw_vsock/af_vsock.c
> +++ b/net/vmw_vsock/af_vsock.c
> @@ -155,6 +155,7 @@
>  #include <linux/random.h>
>  #include <linux/skbuff.h>
>  #include <linux/smp.h>
> +#include <linux/uio.h>
>  #include <linux/socket.h>
>  #include <linux/stddef.h>
>  #include <linux/sysctl.h>
> @@ -2091,8 +2092,7 @@ static int vsock_connectible_setsockopt(struct socket *sock,
>  
>  static int vsock_connectible_getsockopt(struct socket *sock,
>  					int level, int optname,
> -					char __user *optval,
> -					int __user *optlen)
> +					sockopt_t *opt)
>  {
>  	struct sock *sk = sock->sk;
>  	struct vsock_sock *vsk = vsock_sk(sk);
> @@ -2110,8 +2110,7 @@ static int vsock_connectible_getsockopt(struct socket *sock,
>  	if (level != AF_VSOCK)
>  		return -ENOPROTOOPT;
>  
> -	if (get_user(len, optlen))
> -		return -EFAULT;
> +	len = opt->optlen;
>  
>  	memset(&v, 0, sizeof(v));
>  
> @@ -2142,11 +2141,10 @@ static int vsock_connectible_getsockopt(struct socket *sock,
>  		return -EINVAL;
>  	if (len > lv)
>  		len = lv;
> -	if (copy_to_user(optval, &v, len))
> +	if (copy_to_iter(&v, len, &opt->iter_out) != len)
>  		return -EFAULT;
>  
> -	if (put_user(len, optlen))
> -		return -EFAULT;
> +	opt->optlen = len;
>  
>  	return 0;
>  }
> @@ -2631,7 +2629,7 @@ static const struct proto_ops vsock_stream_ops = {
>  	.listen = vsock_listen,
>  	.shutdown = vsock_shutdown,
>  	.setsockopt = vsock_connectible_setsockopt,
> -	.getsockopt = vsock_connectible_getsockopt,
> +	.getsockopt_iter = vsock_connectible_getsockopt,
>  	.sendmsg = vsock_connectible_sendmsg,
>  	.recvmsg = vsock_connectible_recvmsg,
>  	.mmap = sock_no_mmap,
> @@ -2653,7 +2651,7 @@ static const struct proto_ops vsock_seqpacket_ops = {
>  	.listen = vsock_listen,
>  	.shutdown = vsock_shutdown,
>  	.setsockopt = vsock_connectible_setsockopt,
> -	.getsockopt = vsock_connectible_getsockopt,
> +	.getsockopt_iter = vsock_connectible_getsockopt,
>  	.sendmsg = vsock_connectible_sendmsg,
>  	.recvmsg = vsock_connectible_recvmsg,
>  	.mmap = sock_no_mmap,
> 
> -- 
> 2.52.0
> 

Reviewed-by: Bobby Eshleman <bobbyeshleman@meta.com>

^ permalink raw reply

* [PATCH net-next v5 5/5] selftests: net: bridge: add MRC and QQIC field encoding tests
From: Ujjal Roy @ 2026-05-01 17:31 UTC (permalink / raw)
  To: David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
	Simon Horman, Nikolay Aleksandrov, Ido Schimmel, David Ahern,
	Shuah Khan, Andy Roulin, Yong Wang, Petr Machata
  Cc: Ujjal Roy, bridge, netdev, linux-kernel, linux-kselftest
In-Reply-To: <20260501173130.3486-1-royujjal@gmail.com>

Enhance vlmc_query_intvl_test and vlmc_query_response_intvl_test in
bridge_vlan_mcast.sh to validate IGMPv3/MLDv2 protocol compliance for
MRC and QQIC field encoding across both linear and exponential ranges.

TEST: Vlan multicast snooping enable                                [ OK ]
TEST: Vlan mcast_query_interval global option default value         [ OK ]
TEST: Number of tagged IGMPv2 general query                         [ OK ]
TEST: IGMPv3 QQIC linear value 60(s)                                [ OK ]
TEST: MLDv2 QQIC linear value 60(s)                                 [ OK ]
TEST: IGMPv3 QQIC non linear value 160(s)                           [ OK ]
TEST: MLDv2 QQIC non linear value 160(s)                            [ OK ]
TEST: Vlan mcast_query_response_interval global option default value   [ OK ]
TEST: IGMPv3 MRC linear value of 60(x0.1s)                          [ OK ]
TEST: MLDv2 MRC linear value of 24000(ms)                           [ OK ]
TEST: IGMPv3 MRC non linear value of 240(x0.1s)                     [ OK ]
TEST: MLDv2 MRC non linear value of 48000(ms)                       [ OK ]

Reviewed-by: Ido Schimmel <idosch@nvidia.com>
Signed-off-by: Ujjal Roy <royujjal@gmail.com>
---
 .../net/forwarding/bridge_vlan_mcast.sh       | 140 +++++++++++++++++-
 1 file changed, 132 insertions(+), 8 deletions(-)

diff --git a/tools/testing/selftests/net/forwarding/bridge_vlan_mcast.sh b/tools/testing/selftests/net/forwarding/bridge_vlan_mcast.sh
index e8031f68200a..07a377652a59 100755
--- a/tools/testing/selftests/net/forwarding/bridge_vlan_mcast.sh
+++ b/tools/testing/selftests/net/forwarding/bridge_vlan_mcast.sh
@@ -162,14 +162,27 @@ vlmc_query_cnt_setup()
 {
 	local type=$1
 	local dev=$2
+	local match=($3)
 
 	if [[ $type == "igmp" ]]; then
-		tc filter add dev $dev egress pref 10 prot 802.1Q \
+		# This matches: IP Protocol 2 (IGMP)
+		tc filter add dev "$dev" egress pref 10 prot 802.1Q \
 			flower vlan_id 10 vlan_ethtype ipv4 dst_ip 224.0.0.1 ip_proto 2 \
+			action goto chain 1
+		# AND Type 0x11 (Query) at offset 0 of IGMP header
+		# 20 bytes IPv4 header + 4 bytes Router Alert option + IGMP[offset 0]
+		tc filter add dev "$dev" egress pref 20 chain 1 prot 802.1Q u32 \
+			match u8 0x11 0xff at 24 "${match[@]}" \
 			action pass
 	else
-		tc filter add dev $dev egress pref 10 prot 802.1Q \
+		# This matches: ICMPv6
+		tc filter add dev "$dev" egress pref 10 prot 802.1Q \
 			flower vlan_id 10 vlan_ethtype ipv6 dst_ip ff02::1 ip_proto icmpv6 \
+			action goto chain 1
+		# AND Type 0x82 (Query) at offset 0 of MLD header
+		# 40 bytes IPv6 header + 8 bytes Hop-by-hop option + MLD[offset 0]
+		tc filter add dev "$dev" egress pref 20 chain 1 prot 802.1Q u32 \
+			match u8 0x82 0xff at 48 "${match[@]}" \
 			action pass
 	fi
 
@@ -181,7 +194,39 @@ vlmc_query_cnt_cleanup()
 	local dev=$1
 
 	ip link set dev br0 type bridge mcast_stats_enabled 0
-	tc filter del dev $dev egress pref 10
+	tc filter del dev "$dev" egress pref 20 chain 1
+	tc filter del dev "$dev" egress pref 10
+}
+
+vlmc_query_get_intvl_match()
+{
+	local type=$1
+	local version=$2
+	local test=$3
+	local enc_val=$4
+
+	if [ "$test" = "qqic" ]; then
+		# QQIC is 8-bit floating point encoding for IGMPv3 and MLDv2
+		if [ "${type}v${version}" = "igmpv3" ]; then
+			# QQIC is at offset 9 of IGMP header
+			# 20 bytes IPv4 header + 4 bytes Router Alert option + IGMP[offset 9]
+			echo "match u8 $enc_val 0xff at 33"
+		elif [ "${type}v${version}" = "mldv2" ]; then
+			# QQIC is at offset 25 of MLD header
+			# 40 bytes IPv6 header + 8 bytes Hop-by-hop option + MLD[offset 25]
+			echo "match u8 $enc_val 0xff at 73"
+		fi
+	elif [ "$test" = "mrc" ]; then
+		if [ "${type}v${version}" = "igmpv3" ]; then
+			# MRC is 8-bit floating point encoding at offset 1 of IGMP header
+			# 20 bytes IPv4 header + 4 bytes Router Alert option + IGMP[offset 1]
+			echo "match u8 $enc_val 0xff at 25"
+		elif [ "${type}v${version}" = "mldv2" ]; then
+			# MRC is 16-bit floating point encoding at offset 4 of MLD header
+			# 40 bytes IPv6 header + 8 bytes Hop-by-hop option + MLD[offset 4]
+			echo "match u16 $enc_val 0xffff at 52"
+		fi
+	fi
 }
 
 vlmc_check_query()
@@ -191,9 +236,13 @@ vlmc_check_query()
 	local dev=$3
 	local expect=$4
 	local time=$5
+	local test=$6
+	local enc_val=$7
+	local intvl_match=""
 	local ret=0
 
-	vlmc_query_cnt_setup $type $dev
+	intvl_match="$(vlmc_query_get_intvl_match "$type" "$version" "$test" "$enc_val")"
+	vlmc_query_cnt_setup "$type" "$dev" "$intvl_match"
 
 	local pre_tx_xstats=$(vlmc_query_cnt_xstats $type $version $dev)
 	bridge vlan global set vid 10 dev br0 mcast_snooping 1 mcast_querier 1
@@ -201,7 +250,7 @@ vlmc_check_query()
 	if [[ $ret -eq 0 ]]; then
 		sleep $time
 
-		local tcstats=$(tc_rule_stats_get $dev 10 egress)
+		local tcstats=$(tc_rule_stats_get "$dev" 20 egress)
 		local post_tx_xstats=$(vlmc_query_cnt_xstats $type $version $dev)
 
 		if [[ $tcstats != $expect || \
@@ -448,8 +497,46 @@ vlmc_query_intvl_test()
 	# 1 is sent immediately, then 2 more in the next 5 seconds
 	vlmc_check_query igmp 2 $swp1 3 5
 	check_err $? "Wrong number of tagged IGMPv2 general queries sent"
-	log_test "Vlan 10 mcast_query_interval option changed to 200"
+	log_test "Number of tagged IGMPv2 general query"
+
+	RET=0
+	bridge vlan global set vid 10 dev br0 mcast_snooping 1 mcast_igmp_version 3
+	check_err $? "Could not set mcast_igmp_version in vlan 10"
+	bridge vlan global set vid 10 dev br0 mcast_snooping 1 mcast_mld_version 2
+	check_err $? "Could not set mcast_mld_version in vlan 10"
+	bridge vlan global set vid 10 dev br0 mcast_snooping 1 mcast_query_interval 6000
+	check_err $? "Could not set mcast_query_interval in vlan 10"
+	# 1 is sent immediately, IGMPv3 QQIC should match with linear value 60 (0x3c)
+	# which is 8-bit encoded value of 60 [units of seconds]
+	vlmc_check_query igmp 3 $swp1 1 1 qqic 0x3c
+	check_err $? "Wrong QQIC in generated IGMPv3 general queries"
+	log_test "IGMPv3 QQIC linear value 60(s)"
+
+	RET=0
+	# 1 is sent immediately, MLDv2 QQIC should match with linear value 60 (0x3c)
+	# which is 8-bit encoded value of 60 [units of seconds]
+	vlmc_check_query mld 2 $swp1 1 1 qqic 0x3c
+	check_err $? "Wrong QQIC in generated MLDv2 general queries"
+	log_test "MLDv2 QQIC linear value 60(s)"
+
+	RET=0
+	bridge vlan global set vid 10 dev br0 mcast_snooping 1 mcast_query_interval 16000
+	check_err $? "Could not set mcast_query_interval in vlan 10"
+	# 1 is sent immediately, IGMPv3 QQIC should match with non linear value 132 (0x84)
+	# which is 8-bit encoded value of 160 [units of seconds]
+	vlmc_check_query igmp 3 $swp1 1 1 qqic 0x84
+	check_err $? "Wrong QQIC in generated IGMPv3 general queries"
+	log_test "IGMPv3 QQIC non linear value 160(s)"
 
+	RET=0
+	# 1 is sent immediately, MLDv2 QQIC should match with non linear value 132 (0x84)
+	# which is 8-bit encoded value of 160 [units of seconds]
+	vlmc_check_query mld 2 $swp1 1 1 qqic 0x84
+	check_err $? "Wrong QQIC in generated MLDv2 general queries"
+	log_test "MLDv2 QQIC non linear value 160(s)"
+
+	bridge vlan global set vid 10 dev br0 mcast_snooping 1 mcast_igmp_version 2
+	bridge vlan global set vid 10 dev br0 mcast_snooping 1 mcast_mld_version 1
 	bridge vlan global set vid 10 dev br0 mcast_snooping 1 mcast_startup_query_count 2
 	bridge vlan global set vid 10 dev br0 mcast_snooping 1 mcast_query_interval 12500
 }
@@ -469,10 +556,47 @@ vlmc_query_response_intvl_test()
 	log_test "Vlan mcast_query_response_interval global option default value"
 
 	RET=0
-	bridge vlan global set vid 10 dev br0 mcast_snooping 1 mcast_query_response_interval 200
+	bridge vlan global set vid 10 dev br0 mcast_snooping 1 mcast_startup_query_count 0
+	bridge vlan global set vid 10 dev br0 mcast_snooping 1 mcast_igmp_version 3
+	check_err $? "Could not set mcast_igmp_version in vlan 10"
+	bridge vlan global set vid 10 dev br0 mcast_snooping 1 mcast_mld_version 2
+	check_err $? "Could not set mcast_mld_version in vlan 10"
+	bridge vlan global set vid 10 dev br0 mcast_snooping 1 mcast_query_response_interval 600
+	check_err $? "Could not set mcast_query_response_interval in vlan 10"
+	# 1 is sent immediately, IGMPv3 MRC should match with linear value 60 (0x3c)
+	# which is 8-bit encoded value of 60 [units of 0.1s = 6 seconds]
+	vlmc_check_query igmp 3 $swp1 1 1 mrc 0x3c
+	check_err $? "Wrong MRC in generated IGMPv3 general queries"
+	log_test "IGMPv3 MRC linear value of 60(x0.1s)"
+
+	RET=0
+	bridge vlan global set vid 10 dev br0 mcast_snooping 1 mcast_query_response_interval 2400
+	check_err $? "Could not set mcast_query_response_interval in vlan 10"
+	# 1 is sent immediately, MLDv2 MRC should match with linear value 0x5dc0 (24000)
+	# which is 16-bit encoded value of 24000 [units of ms / 24 seconds]
+	vlmc_check_query mld 2 $swp1 1 1 mrc 0x5dc0
+	check_err $? "Wrong MRC in generated MLDv2 general queries"
+	log_test "MLDv2 MRC linear value of 24000(ms)"
+
+	RET=0
+	# 1 is sent immediately, IGMPv3 MRC should match with non linear value 142 (0x8e)
+	# which is 8-bit encoded value of 240 [units of 0.1s = 24 seconds]
+	vlmc_check_query igmp 3 $swp1 1 1 mrc 0x8e
+	check_err $? "Wrong MRC in generated IGMPv3 general queries"
+	log_test "IGMPv3 MRC non linear value of 240(x0.1s)"
+
+	RET=0
+	bridge vlan global set vid 10 dev br0 mcast_snooping 1 mcast_query_response_interval 4800
 	check_err $? "Could not set mcast_query_response_interval in vlan 10"
-	log_test "Vlan 10 mcast_query_response_interval option changed to 200"
+	# 1 is sent immediately, MLDv2 MRC should match with non linear value 0x8770 (34672)
+	# which is 16-bit encoded value of 48000 [units of ms / 48 seconds]
+	vlmc_check_query mld 2 $swp1 1 1 mrc 0x8770
+	check_err $? "Wrong MRC in generated MLDv2 general queries"
+	log_test "MLDv2 MRC non linear value of 48000(ms)"
 
+	bridge vlan global set vid 10 dev br0 mcast_snooping 1 mcast_igmp_version 2
+	bridge vlan global set vid 10 dev br0 mcast_snooping 1 mcast_mld_version 1
+	bridge vlan global set vid 10 dev br0 mcast_snooping 1 mcast_startup_query_count 2
 	bridge vlan global set vid 10 dev br0 mcast_snooping 1 mcast_query_response_interval 1000
 }
 
-- 
2.43.0


^ permalink raw reply related

* [PATCH net-next v5 4/5] ipv6: mld: encode multicast exponential fields
From: Ujjal Roy @ 2026-05-01 17:31 UTC (permalink / raw)
  To: David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
	Simon Horman, Nikolay Aleksandrov, Ido Schimmel, David Ahern,
	Shuah Khan, Andy Roulin, Yong Wang, Petr Machata
  Cc: Ujjal Roy, bridge, netdev, linux-kernel, linux-kselftest
In-Reply-To: <20260501173130.3486-1-royujjal@gmail.com>

In MLD, MRC and QQIC fields are not correctly encoded when
generating query packets. Since the receiver of the query
interprets these fields using the MLDv2 floating-point
decoding logic, any value that exceeds the linear threshold
is incorrectly parsed as an exponential value, leading to
an incorrect interval calculation.

Encode and assign the corresponding protocol fields during
query generation. Introduce the logic to dynamically
calculate the exponent and mantissa using bit-scan (fls).
This ensures MRC (16-bit) and QQIC (8-bit) fields are
properly encoded when transmitting query packets with
intervals that exceed their respective linear thresholds
(32768 for MRD; 128 for QQI).

RFC3810: If Maximum Response Code >= 32768, the Maximum
Response Code field represents a floating-point value as
follows:
     0 1 2 3 4 5 6 7 8 9 A B C D E F
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |1| exp |          mant         |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

RFC3810: If QQIC >= 128, the QQIC field represents a
floating-point value as follows:
     0 1 2 3 4 5 6 7
    +-+-+-+-+-+-+-+-+
    |1| exp | mant  |
    +-+-+-+-+-+-+-+-+

Reviewed-by: Ido Schimmel <idosch@nvidia.com>
Signed-off-by: Ujjal Roy <royujjal@gmail.com>
---
 include/net/mld.h         | 119 ++++++++++++++++++++++++++++++++++++++
 net/bridge/br_multicast.c |   4 +-
 2 files changed, 121 insertions(+), 2 deletions(-)

diff --git a/include/net/mld.h b/include/net/mld.h
index 10e402e0fe08..23255cc47ced 100644
--- a/include/net/mld.h
+++ b/include/net/mld.h
@@ -91,13 +91,132 @@ struct mld2_query {
 
 /* MLDv2 QQIC floating-point exponential field min threshold */
 #define MLD_QQIC_MIN_THRESHOLD	128
+/* MLDv2 QQIC FP max threshold (mant = 0xF, exp = 7) -> 31744 */
+#define MLD_QQIC_MAX_THRESHOLD	31744
 /* MLDv2 MRC floating-point exponential field min threshold */
 #define MLD_MRC_MIN_THRESHOLD	32768UL
+/* MLDv2 MRC FP max threshold (mant = 0xFFF, exp = 7) -> 8387584 */
+#define MLD_MRC_MAX_THRESHOLD	8387584
 #define MLDV1_MRD_MAX_COMPAT	(MLD_MRC_MIN_THRESHOLD - 1)
 
 #define MLD_MAX_QUEUE		8
 #define MLD_MAX_SKBS		32
 
+/* V2 exponential field encoding */
+
+/*
+ * Calculate Maximum Response Code from Maximum Response Delay
+ *
+ * MRC represents the 16-bit encoded form of Maximum Response Delay (MRD);
+ * once decoded, the resulting value is in milliseconds.
+ *
+ * RFC3810, 5.1.3. defines only the decoding formula:
+ *     Maximum Response Delay = (mant | 0x1000) << (exp + 3)
+ *
+ * but does NOT define the encoding procedure. To derive exponent:
+ *
+ * For the 16-bit MRC, the "hidden bit" (0x1000) is left shifted by 12 to
+ * sit above the 12-bit mantissa. The RFC then shifts this entire block
+ * left by (exp + 3) to reconstruct the value. So, 'hidden bit' is the
+ * MSB which is shifted by (12 + exp + 3).
+ *
+ * Total left shift of the hidden bit = 12 + (exp + 3) = exp + 15.
+ * This is the MSB at the 0-based bit position: (exp + 15).
+ * Since fls() is 1-based, fls(value) - 1 = exp + 15.
+ *
+ * Therefore:
+ *     exp  = fls(value) - 16
+ *     mant = (value >> (exp + 3)) & 0x0FFF
+ *
+ * Final encoding formula:
+ *     0x8000 | (exp << 12) | mant
+ *
+ * Example (value = 1311744):
+ *  0               1               2               3
+ *  0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |0 0 0 0 0 0 0 0 0 0 0 1 0 1 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0| 1311744
+ * |                      ^-^--------mant---------^ ^...(exp+3)...^| exp=5
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ *
+ * Encoded:
+ *   0x8000 | (5 << 12) | 0x404 = 0xD404
+ */
+static inline u16 mldv2_mrc(unsigned long mrd)
+{
+	u16 mc_man, mc_exp;
+
+	/* MRC < 32768 is literal */
+	if (mrd < MLD_MRC_MIN_THRESHOLD)
+		return mrd;
+
+	/* Saturate at max representable (mant = 0xFFF, exp = 7) -> 8387584 */
+	if (mrd >= MLD_MRC_MAX_THRESHOLD)
+		return 0xFFFF;
+
+	mc_exp = fls(mrd) - 16;
+	mc_man = (mrd >> (mc_exp + 3)) & 0x0FFF;
+
+	return 0x8000 | (mc_exp << 12) | mc_man;
+}
+
+/*
+ * Calculate Querier's Query Interval Code from Querier's Query Interval
+ *
+ * QQIC represents the 8-bit encoded form of Querier's Query Interval (QQI);
+ * once decoded, the resulting value is in seconds.
+ *
+ * RFC3810, 5.1.9. defines only the decoding formula:
+ *     QQI = (mant | 0x10) << (exp + 3)
+ *
+ * but does NOT define the encoding procedure. To derive exponent:
+ *
+ * For any value of mantissa and exponent, the decoding formula indicates
+ * that the "hidden bit" (0x10) is shifted 4 bits left to sit above the
+ * 4-bit mantissa. The RFC again shifts this entire block left by (exp + 3)
+ * to reconstruct the value. So, 'hidden bit' is the MSB which is shifted
+ * by (4 + exp + 3).
+ *
+ * Total left shift of the 'hidden bit' = 4 + (exp + 3) = exp + 7.
+ * This is the MSB at the 0-based bit position: (exp + 7).
+ * Since fls() is 1-based, fls(value) - 1 = exp + 7.
+ *
+ * Therefore:
+ *     exp  = fls(value) - 8
+ *     mant = (value >> (exp + 3)) & 0x0F
+ *
+ * Final encoding formula:
+ *     0x80 | (exp << 4) | mant
+ *
+ * Example (value = 3200):
+ *  0               1
+ *  0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |0 0 0 0 1 1 0 0 1 0 0 0 0 0 0 0| (value = 3200)
+ * |        ^-^-mant^ ^..(exp+3)..^| exp = 4, mant = 9
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ *
+ * Encoded:
+ *   0x80 | (4 << 4) | 9 = 0xC9
+ */
+static inline u8 mldv2_qqic(unsigned long value)
+{
+	u8 mc_man, mc_exp;
+
+	/* QQIC < 128 is literal */
+	if (value < MLD_QQIC_MIN_THRESHOLD)
+		return value;
+
+	/* Saturate at max representable (mant = 0xF, exp = 7) -> 31744 */
+	if (value >= MLD_QQIC_MAX_THRESHOLD)
+		return 0xFF;
+
+	mc_exp  = fls(value) - 8;
+	mc_man = (value >> (mc_exp + 3)) & 0x0F;
+
+	return 0x80 | (mc_exp << 4) | mc_man;
+}
+
 /* V2 exponential field decoding */
 
 /* Calculate Maximum Response Delay from Maximum Response Code
diff --git a/net/bridge/br_multicast.c b/net/bridge/br_multicast.c
index 27010744d7ae..49ceea3ff974 100644
--- a/net/bridge/br_multicast.c
+++ b/net/bridge/br_multicast.c
@@ -1181,7 +1181,7 @@ static struct sk_buff *br_ip6_multicast_alloc_query(struct net_bridge_mcast *brm
 		break;
 	case 2:
 		mld2q = (struct mld2_query *)icmp6_hdr(skb);
-		mld2q->mld2q_mrc = htons((u16)jiffies_to_msecs(interval));
+		mld2q->mld2q_mrc = htons(mldv2_mrc(jiffies_to_msecs(interval)));
 		mld2q->mld2q_type = ICMPV6_MGM_QUERY;
 		mld2q->mld2q_code = 0;
 		mld2q->mld2q_cksum = 0;
@@ -1190,7 +1190,7 @@ static struct sk_buff *br_ip6_multicast_alloc_query(struct net_bridge_mcast *brm
 		mld2q->mld2q_suppress = sflag;
 		mld2q->mld2q_qrv = 2;
 		mld2q->mld2q_nsrcs = htons(llqt_srcs);
-		mld2q->mld2q_qqic = brmctx->multicast_query_interval / HZ;
+		mld2q->mld2q_qqic = mldv2_qqic(brmctx->multicast_query_interval / HZ);
 		mld2q->mld2q_mca = *group;
 		csum = &mld2q->mld2q_cksum;
 		csum_start = (void *)mld2q;
-- 
2.43.0


^ permalink raw reply related

* [PATCH net-next v5 3/5] ipv4: igmp: encode multicast exponential fields
From: Ujjal Roy @ 2026-05-01 17:31 UTC (permalink / raw)
  To: David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
	Simon Horman, Nikolay Aleksandrov, Ido Schimmel, David Ahern,
	Shuah Khan, Andy Roulin, Yong Wang, Petr Machata
  Cc: Ujjal Roy, bridge, netdev, linux-kernel, linux-kselftest
In-Reply-To: <20260501173130.3486-1-royujjal@gmail.com>

In IGMP, MRC and QQIC fields are not correctly encoded
when generating query packets. Since the receiver of the
query interprets these fields using the IGMPv3 floating-
point decoding logic, any value that exceeds the linear
threshold is incorrectly parsed as an exponential value,
leading to an incorrect interval calculation.

Encode and assign the corresponding protocol fields during
query generation. Introduce the logic to dynamically
calculate the exponent and mantissa using bit-scan (fls).
This ensures MRC and QQIC fields (8-bit) are properly
encoded when transmitting query packets with intervals
that exceed their respective linear threshold value of
128 (for MRT/QQI).

RFC3376: for both MRC and QQIC, values >= 128 represent
the same floating-point encoding as follows:
     0 1 2 3 4 5 6 7
    +-+-+-+-+-+-+-+-+
    |1| exp | mant  |
    +-+-+-+-+-+-+-+-+

Reviewed-by: Ido Schimmel <idosch@nvidia.com>
Signed-off-by: Ujjal Roy <royujjal@gmail.com>
---
 include/linux/igmp.h      | 87 +++++++++++++++++++++++++++++++++++++++
 net/bridge/br_multicast.c | 14 +++----
 2 files changed, 93 insertions(+), 8 deletions(-)

diff --git a/include/linux/igmp.h b/include/linux/igmp.h
index 0e095c9f234e..3a2d35a9f307 100644
--- a/include/linux/igmp.h
+++ b/include/linux/igmp.h
@@ -109,6 +109,93 @@ struct ip_mc_list {
 
 /* IGMPv3 floating-point exponential field min threshold */
 #define IGMPV3_EXP_MIN_THRESHOLD	128
+/* IGMPv3 FP max threshold (mant = 0xF, exp = 7) -> 31744 */
+#define IGMPV3_EXP_MAX_THRESHOLD	31744
+
+/* V3 exponential field encoding */
+
+/* IGMPv3 MRC/QQIC 8-bit exponential field encode
+ *
+ * RFC3376, 4.1.1 & 4.1.7. defines only the decoding formula:
+ *     MRT/QQI = (mant | 0x10) << (exp + 3)
+ *
+ * but does NOT define the encoding procedure. To derive exponent:
+ *
+ * For any value of mantissa and exponent, the decoding formula
+ * indicates that the "hidden bit" (0x10) is shifted 4 bits left
+ * to sit above the 4-bit mantissa. The RFC again shifts this
+ * entire block left by (exp + 3) to reconstruct the value.
+ * So, 'hidden bit' is the MSB which is shifted by (4 + exp + 3).
+ *
+ * Total left shift of the 'hidden bit' = 4 + (exp + 3) = exp + 7.
+ * This is the MSB at the 0-based bit position: (exp + 7).
+ * Since fls() is 1-based, fls(value) - 1 = exp + 7.
+ *
+ * Therefore:
+ *     exp  = fls(value) - 8
+ *     mant = (value >> (exp + 3)) & 0x0F
+ *
+ * Final encoding formula:
+ *     0x80 | (exp << 4) | mant
+ *
+ * Example (value = 3200):
+ *  0               1
+ *  0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |0 0 0 0 1 1 0 0 1 0 0 0 0 0 0 0| (value = 3200)
+ * |        ^-^-mant^ ^..(exp+3)..^| exp = 4, mant = 9
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ *
+ * Encoded:
+ *   0x80 | (4 << 4) | 9 = 0xC9
+ */
+static inline u8 igmpv3_exp_field_encode(unsigned long value)
+{
+	u8 mc_exp, mc_man;
+
+	/* MRC/QQIC < 128 is literal */
+	if (value < IGMPV3_EXP_MIN_THRESHOLD)
+		return value;
+
+	/* Saturate at max representable (mant = 0xF, exp = 7) -> 31744 */
+	if (value >= IGMPV3_EXP_MAX_THRESHOLD)
+		return 0xFF;
+
+	mc_exp  = fls(value) - 8;
+	mc_man = (value >> (mc_exp + 3)) & 0x0F;
+
+	return 0x80 | (mc_exp << 4) | mc_man;
+}
+
+/* Calculate Maximum Response Code from Max Resp Time
+ *
+ * RFC3376, relevant sections:
+ *  - 4.1.1. Maximum Response Code
+ *  - 8.3. Query Response Interval
+ *
+ * MRC represents the encoded form of Max Resp Time (MRT); once
+ * decoded, the resulting value is in units of 0.1 seconds (100 ms).
+ */
+static inline u8 igmpv3_mrc(unsigned long mrt)
+{
+	return igmpv3_exp_field_encode(mrt);
+}
+
+/* Calculate Querier's Query Interval Code from Querier's Query Interval
+ *
+ * RFC3376, relevant sections:
+ *  - 4.1.7. QQIC (Querier's Query Interval Code)
+ *  - 8.2. Query Interval
+ *  - 8.12. Older Version Querier Present Timeout
+ *    (the [Query Interval] in the last Query received)
+ *
+ * QQIC represents the encoded form of Querier's Query Interval (QQI);
+ * once decoded, the resulting value is in units of seconds.
+ */
+static inline u8 igmpv3_qqic(unsigned long qi)
+{
+	return igmpv3_exp_field_encode(qi);
+}
 
 /* V3 exponential field decoding */
 
diff --git a/net/bridge/br_multicast.c b/net/bridge/br_multicast.c
index 1438c023db62..27010744d7ae 100644
--- a/net/bridge/br_multicast.c
+++ b/net/bridge/br_multicast.c
@@ -934,12 +934,12 @@ static struct sk_buff *br_ip4_multicast_alloc_query(struct net_bridge_mcast *brm
 	size_t pkt_size, igmp_hdr_size;
 	unsigned long now = jiffies;
 	struct igmpv3_query *ihv3;
+	unsigned long lmqt, mrt;
 	void *csum_start = NULL;
 	__sum16 *csum = NULL;
 	struct sk_buff *skb;
 	struct igmphdr *ih;
 	struct ethhdr *eth;
-	unsigned long lmqt;
 	struct iphdr *iph;
 	u16 lmqt_srcs = 0;
 
@@ -1004,15 +1004,15 @@ static struct sk_buff *br_ip4_multicast_alloc_query(struct net_bridge_mcast *brm
 	skb_put(skb, 24);
 
 	skb_set_transport_header(skb, skb->len);
+	mrt = group ? brmctx->multicast_last_member_interval :
+		      brmctx->multicast_query_response_interval;
 	*igmp_type = IGMP_HOST_MEMBERSHIP_QUERY;
 
 	switch (brmctx->multicast_igmp_version) {
 	case 2:
 		ih = igmp_hdr(skb);
 		ih->type = IGMP_HOST_MEMBERSHIP_QUERY;
-		ih->code = (group ? brmctx->multicast_last_member_interval :
-				    brmctx->multicast_query_response_interval) /
-			   (HZ / IGMP_TIMER_SCALE);
+		ih->code = mrt / (HZ / IGMP_TIMER_SCALE);
 		ih->group = group;
 		ih->csum = 0;
 		csum = &ih->csum;
@@ -1021,11 +1021,9 @@ static struct sk_buff *br_ip4_multicast_alloc_query(struct net_bridge_mcast *brm
 	case 3:
 		ihv3 = igmpv3_query_hdr(skb);
 		ihv3->type = IGMP_HOST_MEMBERSHIP_QUERY;
-		ihv3->code = (group ? brmctx->multicast_last_member_interval :
-				      brmctx->multicast_query_response_interval) /
-			     (HZ / IGMP_TIMER_SCALE);
+		ihv3->code = igmpv3_mrc(mrt / (HZ / IGMP_TIMER_SCALE));
 		ihv3->group = group;
-		ihv3->qqic = brmctx->multicast_query_interval / HZ;
+		ihv3->qqic = igmpv3_qqic(brmctx->multicast_query_interval / HZ);
 		ihv3->nsrcs = htons(lmqt_srcs);
 		ihv3->resv = 0;
 		ihv3->suppress = sflag;
-- 
2.43.0


^ permalink raw reply related

* [PATCH net-next v5 2/5] ipv6: mld: rename mldv2_mrc() and add mldv2_qqi()
From: Ujjal Roy @ 2026-05-01 17:31 UTC (permalink / raw)
  To: David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
	Simon Horman, Nikolay Aleksandrov, Ido Schimmel, David Ahern,
	Shuah Khan, Andy Roulin, Yong Wang, Petr Machata
  Cc: Ujjal Roy, bridge, netdev, linux-kernel, linux-kselftest
In-Reply-To: <20260501173130.3486-1-royujjal@gmail.com>

Rename mldv2_mrc() to mldv2_mrd() as it is used to calculate
the Maximum Response Delay from the Maximum Response Code.

Introduce a new API mldv2_qqi() to define the existing
calculation logic of QQI from QQIC. This also organizes
the existing mld_update_qi() API.

Reviewed-by: Ido Schimmel <idosch@nvidia.com>
Signed-off-by: Ujjal Roy <royujjal@gmail.com>
---
 include/net/mld.h         | 66 +++++++++++++++++++++++++++++++++------
 net/bridge/br_multicast.c |  2 +-
 net/ipv6/mcast.c          | 19 ++---------
 3 files changed, 61 insertions(+), 26 deletions(-)

diff --git a/include/net/mld.h b/include/net/mld.h
index c07359808493..10e402e0fe08 100644
--- a/include/net/mld.h
+++ b/include/net/mld.h
@@ -89,29 +89,77 @@ struct mld2_query {
 #define MLDV2_QQIC_EXP(value)	(((value) >> 4) & 0x07)
 #define MLDV2_QQIC_MAN(value)	((value) & 0x0f)
 
-#define MLD_EXP_MIN_LIMIT	32768UL
-#define MLDV1_MRD_MAX_COMPAT	(MLD_EXP_MIN_LIMIT - 1)
+/* MLDv2 QQIC floating-point exponential field min threshold */
+#define MLD_QQIC_MIN_THRESHOLD	128
+/* MLDv2 MRC floating-point exponential field min threshold */
+#define MLD_MRC_MIN_THRESHOLD	32768UL
+#define MLDV1_MRD_MAX_COMPAT	(MLD_MRC_MIN_THRESHOLD - 1)
 
 #define MLD_MAX_QUEUE		8
 #define MLD_MAX_SKBS		32
 
-static inline unsigned long mldv2_mrc(const struct mld2_query *mlh2)
+/* V2 exponential field decoding */
+
+/* Calculate Maximum Response Delay from Maximum Response Code
+ *
+ * RFC3810, relevant sections:
+ *  - 5.1.3. Maximum Response Code defines the decoding formula:
+ *      0 1 2 3 4 5 6 7 8 9 A B C D E F
+ *     +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ *     |1| exp |          mant         |
+ *     +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ *    Maximum Response Delay = (mant | 0x1000) << (exp+3)
+ *  - 9.3. Query Response Interval
+ *
+ * After decode, MRC represents the Maximum Response Delay (MRD) in
+ * units of milliseconds.
+ */
+static inline unsigned long mldv2_mrd(const struct mld2_query *mlh2)
 {
-	/* RFC3810, 5.1.3. Maximum Response Code */
-	unsigned long ret, mc_mrc = ntohs(mlh2->mld2q_mrc);
+	unsigned long mc_mrc = ntohs(mlh2->mld2q_mrc);
 
-	if (mc_mrc < MLD_EXP_MIN_LIMIT) {
-		ret = mc_mrc;
+	if (mc_mrc < MLD_MRC_MIN_THRESHOLD) {
+		return mc_mrc;
 	} else {
 		unsigned long mc_man, mc_exp;
 
 		mc_exp = MLDV2_MRC_EXP(mc_mrc);
 		mc_man = MLDV2_MRC_MAN(mc_mrc);
 
-		ret = (mc_man | 0x1000) << (mc_exp + 3);
+		return (mc_man | 0x1000) << (mc_exp + 3);
 	}
+}
 
-	return ret;
+/* Calculate Querier's Query Interval from Querier's Query Interval Code
+ *
+ * RFC3810, relevant sections:
+ *  - 5.1.9. QQIC (Querier's Query Interval Code) defines the decoding formula:
+ *      0 1 2 3 4 5 6 7
+ *     +-+-+-+-+-+-+-+-+
+ *     |1| exp | mant  |
+ *     +-+-+-+-+-+-+-+-+
+ *    QQI = (mant | 0x10) << (exp + 3)
+ *  - 9.2. Query Interval
+ *  - 9.12. Older Version Querier Present Timeout
+ *    (the [Query Interval] in the last Query received)
+ *
+ * After decode, QQIC represents the Querier's Query Interval in units
+ * of seconds.
+ */
+static inline unsigned long mldv2_qqi(const struct mld2_query *mlh2)
+{
+	unsigned long qqic = mlh2->mld2q_qqic;
+
+	if (qqic < MLD_QQIC_MIN_THRESHOLD) {
+		return qqic;
+	} else {
+		unsigned long mc_man, mc_exp;
+
+		mc_exp = MLDV2_QQIC_EXP(qqic);
+		mc_man = MLDV2_QQIC_MAN(qqic);
+
+		return (mc_man | 0x10) << (mc_exp + 3);
+	}
 }
 
 #endif
diff --git a/net/bridge/br_multicast.c b/net/bridge/br_multicast.c
index 9fec76e887bc..1438c023db62 100644
--- a/net/bridge/br_multicast.c
+++ b/net/bridge/br_multicast.c
@@ -3606,7 +3606,7 @@ static int br_ip6_multicast_query(struct net_bridge_mcast *brmctx,
 		    mld2q->mld2q_suppress)
 			goto out;
 
-		max_delay = max(msecs_to_jiffies(mldv2_mrc(mld2q)), 1UL);
+		max_delay = max(msecs_to_jiffies(mldv2_mrd(mld2q)), 1UL);
 	}
 
 	is_general_query = group && ipv6_addr_any(group);
diff --git a/net/ipv6/mcast.c b/net/ipv6/mcast.c
index 3330adcf26db..6ddc18ac59b9 100644
--- a/net/ipv6/mcast.c
+++ b/net/ipv6/mcast.c
@@ -1315,20 +1315,7 @@ static void mld_update_qi(struct inet6_dev *idev,
 	 *  - 9.12. Older Version Querier Present Timeout
 	 *    (the [Query Interval] in the last Query received)
 	 */
-	unsigned long mc_qqi;
-
-	if (mlh2->mld2q_qqic < 128) {
-		mc_qqi = mlh2->mld2q_qqic;
-	} else {
-		unsigned long mc_man, mc_exp;
-
-		mc_exp = MLDV2_QQIC_EXP(mlh2->mld2q_qqic);
-		mc_man = MLDV2_QQIC_MAN(mlh2->mld2q_qqic);
-
-		mc_qqi = (mc_man | 0x10) << (mc_exp + 3);
-	}
-
-	idev->mc_qi = mc_qqi * HZ;
+	idev->mc_qi = mldv2_qqi(mlh2) * HZ;
 }
 
 static void mld_update_qri(struct inet6_dev *idev,
@@ -1338,7 +1325,7 @@ static void mld_update_qri(struct inet6_dev *idev,
 	 *  - 5.1.3. Maximum Response Code
 	 *  - 9.3. Query Response Interval
 	 */
-	idev->mc_qri = msecs_to_jiffies(mldv2_mrc(mlh2));
+	idev->mc_qri = msecs_to_jiffies(mldv2_mrd(mlh2));
 }
 
 static int mld_process_v1(struct inet6_dev *idev, struct mld_msg *mld,
@@ -1390,7 +1377,7 @@ static int mld_process_v1(struct inet6_dev *idev, struct mld_msg *mld,
 static void mld_process_v2(struct inet6_dev *idev, struct mld2_query *mld,
 			   unsigned long *max_delay)
 {
-	*max_delay = max(msecs_to_jiffies(mldv2_mrc(mld)), 1UL);
+	*max_delay = max(msecs_to_jiffies(mldv2_mrd(mld)), 1UL);
 
 	mld_update_qrv(idev, mld);
 	mld_update_qi(idev, mld);
-- 
2.43.0


^ permalink raw reply related

* [PATCH net-next v5 1/5] ipv4: igmp: get rid of IGMPV3_{QQIC,MRC} and simplify calculation
From: Ujjal Roy @ 2026-05-01 17:31 UTC (permalink / raw)
  To: David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
	Simon Horman, Nikolay Aleksandrov, Ido Schimmel, David Ahern,
	Shuah Khan, Andy Roulin, Yong Wang, Petr Machata
  Cc: Ujjal Roy, bridge, netdev, linux-kernel, linux-kselftest
In-Reply-To: <20260501173130.3486-1-royujjal@gmail.com>

Get rid of the IGMPV3_MRC macro and use the igmpv3_mrt() API to
calculate the Max Resp Time from the Maximum Response Code.

Similarly, for IGMPV3_QQIC, use the igmpv3_qqi() API to calculate
the Querier's Query Interval from the QQIC field.

Reviewed-by: Ido Schimmel <idosch@nvidia.com>
Signed-off-by: Ujjal Roy <royujjal@gmail.com>
---
 include/linux/igmp.h      | 80 +++++++++++++++++++++++++++++++++++----
 net/bridge/br_multicast.c |  2 +-
 net/ipv4/igmp.c           |  6 +--
 3 files changed, 76 insertions(+), 12 deletions(-)

diff --git a/include/linux/igmp.h b/include/linux/igmp.h
index 073b30a9b850..0e095c9f234e 100644
--- a/include/linux/igmp.h
+++ b/include/linux/igmp.h
@@ -92,15 +92,79 @@ struct ip_mc_list {
 	struct rcu_head		rcu;
 };
 
+/* RFC3376, relevant sections:
+ *  - 4.1.1. Maximum Response Code
+ *  - 4.1.7. QQIC (Querier's Query Interval Code)
+ *
+ * For both MRC and QQIC, values >= 128 use the same floating-point
+ * encoding as follows:
+ *
+ *  0 1 2 3 4 5 6 7
+ * +-+-+-+-+-+-+-+-+
+ * |1| exp | mant  |
+ * +-+-+-+-+-+-+-+-+
+ */
+#define IGMPV3_FP_EXP(value)		(((value) >> 4) & 0x07)
+#define IGMPV3_FP_MAN(value)		((value) & 0x0f)
+
+/* IGMPv3 floating-point exponential field min threshold */
+#define IGMPV3_EXP_MIN_THRESHOLD	128
+
 /* V3 exponential field decoding */
-#define IGMPV3_MASK(value, nb) ((nb)>=32 ? (value) : ((1<<(nb))-1) & (value))
-#define IGMPV3_EXP(thresh, nbmant, nbexp, value) \
-	((value) < (thresh) ? (value) : \
-        ((IGMPV3_MASK(value, nbmant) | (1<<(nbmant))) << \
-         (IGMPV3_MASK((value) >> (nbmant), nbexp) + (nbexp))))
-
-#define IGMPV3_QQIC(value) IGMPV3_EXP(0x80, 4, 3, value)
-#define IGMPV3_MRC(value) IGMPV3_EXP(0x80, 4, 3, value)
+
+/* IGMPv3 MRC/QQIC 8-bit exponential field decode
+ *
+ * RFC3376, 4.1.1 & 4.1.7. defines the decoding formula:
+ *      0 1 2 3 4 5 6 7
+ *     +-+-+-+-+-+-+-+-+
+ *     |1| exp | mant  |
+ *     +-+-+-+-+-+-+-+-+
+ * Max Resp Time = (mant | 0x10) << (exp + 3)
+ * QQI = (mant | 0x10) << (exp + 3)
+ */
+static inline unsigned long igmpv3_exp_field_decode(const u8 code)
+{
+	if (code < IGMPV3_EXP_MIN_THRESHOLD) {
+		return code;
+	} else {
+		unsigned long mc_man, mc_exp;
+
+		mc_exp = IGMPV3_FP_EXP(code);
+		mc_man = IGMPV3_FP_MAN(code);
+
+		return (mc_man | 0x10) << (mc_exp + 3);
+	}
+}
+
+/* Calculate Max Resp Time from Maximum Response Code
+ *
+ * RFC3376, relevant sections:
+ *  - 4.1.1. Maximum Response Code
+ *  - 8.3. Query Response Interval
+ *
+ * After decode, MRC represents the Maximum Response Time (MRT) in
+ * units of 0.1 seconds (100 ms).
+ */
+static inline unsigned long igmpv3_mrt(const struct igmpv3_query *ih3)
+{
+	return igmpv3_exp_field_decode(ih3->code);
+}
+
+/* Calculate Querier's Query Interval from Querier's Query Interval Code
+ *
+ * RFC3376, relevant sections:
+ *  - 4.1.7. QQIC (Querier's Query Interval Code)
+ *  - 8.2. Query Interval
+ *  - 8.12. Older Version Querier Present Timeout
+ *    (the [Query Interval] in the last Query received)
+ *
+ * After decode, QQIC represents the Querier's Query Interval in units
+ * of seconds.
+ */
+static inline unsigned long igmpv3_qqi(const struct igmpv3_query *ih3)
+{
+	return igmpv3_exp_field_decode(ih3->qqic);
+}
 
 static inline int ip_mc_may_pull(struct sk_buff *skb, unsigned int len)
 {
diff --git a/net/bridge/br_multicast.c b/net/bridge/br_multicast.c
index 881d866d687a..9fec76e887bc 100644
--- a/net/bridge/br_multicast.c
+++ b/net/bridge/br_multicast.c
@@ -3518,7 +3518,7 @@ static void br_ip4_multicast_query(struct net_bridge_mcast *brmctx,
 			goto out;
 
 		max_delay = ih3->code ?
-			    IGMPV3_MRC(ih3->code) * (HZ / IGMP_TIMER_SCALE) : 1;
+			    igmpv3_mrt(ih3) * (HZ / IGMP_TIMER_SCALE) : 1;
 	} else {
 		goto out;
 	}
diff --git a/net/ipv4/igmp.c b/net/ipv4/igmp.c
index a674fb44ec25..d7eff36d98c3 100644
--- a/net/ipv4/igmp.c
+++ b/net/ipv4/igmp.c
@@ -991,7 +991,7 @@ static bool igmp_heard_query(struct in_device *in_dev, struct sk_buff *skb,
 		 * different encoding. We use the v3 encoding as more likely
 		 * to be intended in a v3 query.
 		 */
-		max_delay = IGMPV3_MRC(ih3->code)*(HZ/IGMP_TIMER_SCALE);
+		max_delay = igmpv3_mrt(ih3) * (HZ / IGMP_TIMER_SCALE);
 		if (!max_delay)
 			max_delay = 1;	/* can't mod w/ 0 */
 	} else { /* v3 */
@@ -1006,7 +1006,7 @@ static bool igmp_heard_query(struct in_device *in_dev, struct sk_buff *skb,
 			ih3 = igmpv3_query_hdr(skb);
 		}
 
-		max_delay = IGMPV3_MRC(ih3->code)*(HZ/IGMP_TIMER_SCALE);
+		max_delay = igmpv3_mrt(ih3) * (HZ / IGMP_TIMER_SCALE);
 		if (!max_delay)
 			max_delay = 1;	/* can't mod w/ 0 */
 		WRITE_ONCE(in_dev->mr_maxdelay, max_delay);
@@ -1016,7 +1016,7 @@ static bool igmp_heard_query(struct in_device *in_dev, struct sk_buff *skb,
 		 * configured value.
 		 */
 		in_dev->mr_qrv = ih3->qrv ?: READ_ONCE(net->ipv4.sysctl_igmp_qrv);
-		in_dev->mr_qi = IGMPV3_QQIC(ih3->qqic)*HZ ?: IGMP_QUERY_INTERVAL;
+		in_dev->mr_qi = igmpv3_qqi(ih3) * HZ ? : IGMP_QUERY_INTERVAL;
 
 		/* RFC3376, 8.3. Query Response Interval:
 		 * The number of seconds represented by the [Query Response
-- 
2.43.0


^ permalink raw reply related

* [PATCH net-next v5 0/5] net: bridge: mcast: support exponential field encoding
From: Ujjal Roy @ 2026-05-01 17:31 UTC (permalink / raw)
  To: David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
	Simon Horman, Nikolay Aleksandrov, Ido Schimmel, David Ahern,
	Shuah Khan, Andy Roulin, Yong Wang, Petr Machata
  Cc: Ujjal Roy, bridge, netdev, linux-kernel, linux-kselftest

Description:
This series addresses a mismatch in how multicast query
intervals and response codes are handled across IPv4 (IGMPv3)
and IPv6 (MLDv2). While decoding logic currently exists,
the corresponding encoding logic is missing during query
packet generation. This leads to incorrect intervals being
transmitted when values exceed their linear thresholds.

The patches introduce a unified floating-point encoding
approach based on RFC3376 and RFC3810, ensuring that large
intervals are correctly represented in QQIC and MRC fields
using the exponent-mantissa format.

Key Changes:
* ipv4: igmp: get rid of IGMPV3_{QQIC,MRC} and simplify calculation
  Removes legacy macros in favor of a cleaner, unified
  calculation for retrieving intervals from encoded fields,
  improving code maintainability.

* ipv6: mld: rename mldv2_mrc() and add mldv2_qqi()
  Standardizes MLDv2 terminology by renaming mldv2_mrc()
  to mldv2_mrd() (Maximum Response Delay) and introducing
  a new API mldv2_qqi for QQI calculation, improving code
  readability.

* ipv4: igmp: encode multicast exponential fields
  Introduces the logic to dynamically calculate the exponent
  and mantissa using bit-scan (fls). This ensures QQIC and
  MRC fields (8-bit) are properly encoded when transmitting
  query packets with intervals that exceed their respective
  linear threshold value of 128 (for QQI/MRT).

* ipv6: mld: encode multicast exponential fields
  Applies similar encoding logic for MLDv2. This ensures
  QQIC (8-bit) and MRC (16-bit) fields are properly encoded
  when transmitting query packets with intervals that exceed
  their respective linear thresholds (128 for QQI; 32768
  for MRD).

* selftests: net: bridge: add MRC and QQIC field encoding tests
  Updates bridge selftests to validate both linear and non-linear
  (exponential) encoding for MRC and QQIC fields, ensuring
  protocol compliance across IGMPv3 and MLDv2.

Impact:
These changes ensure that multicast queriers and listeners
stay synchronized on timing intervals, preventing protocol
timeouts or premature group membership expiration caused
by incorrectly formatted packet headers.

Test results:
Giving results only from modified API vlmc_query_intvl_test() and
vlmc_query_response_intvl_test() of the script bridge_vlan_mcast.sh.

Using old Linux code, here is the result:
    - TEST: Vlan multicast snooping enable                                [ OK ]
    - TEST: Vlan mcast_query_interval global option default value         [ OK ]
    - TEST: Number of tagged IGMPv2 general query                         [ OK ]
    - TEST: IGMPv3 QQIC linear value 60(s)                                [ OK ]
    - TEST: MLDv2 QQIC linear value 60(s)                                 [ OK ]
    - TEST: IGMPv3 QQIC non linear value 160(s)                           [FAIL]
    -         Wrong QQIC in generated IGMPv3 general queries
    - TEST: MLDv2 QQIC non linear value 160(s)                            [FAIL]
    -         Wrong QQIC in generated MLDv2 general queries
    - TEST: Vlan mcast_query_response_interval global option default value   [ OK ]
    - TEST: IGMPv3 MRC linear value of 60(x0.1s)                          [ OK ]
    - TEST: MLDv2 MRC linear value of 24000(ms)                           [ OK ]
    - TEST: IGMPv3 MRC non linear value of 240(x0.1s)                     [FAIL]
    -         Wrong MRC in generated IGMPv3 general queries
    - TEST: MLDv2 MRC non linear value of 48000(ms)                       [FAIL]
    -         Wrong MRC in generated MLDv2 general queries

Linux code with this patchset, here is the result:
    * TEST: Vlan multicast snooping enable                                [ OK ]
    * TEST: Vlan mcast_query_interval global option default value         [ OK ]
    * TEST: Number of tagged IGMPv2 general query                         [ OK ]
    * TEST: IGMPv3 QQIC linear value 60(s)                                [ OK ]
    * TEST: MLDv2 QQIC linear value 60(s)                                 [ OK ]
    * TEST: IGMPv3 QQIC non linear value 160(s)                           [ OK ]
    * TEST: MLDv2 QQIC non linear value 160(s)                            [ OK ]
    * TEST: Vlan mcast_query_response_interval global option default value   [ OK ]
    * TEST: IGMPv3 MRC linear value of 60(x0.1s)                          [ OK ]
    * TEST: MLDv2 MRC linear value of 24000(ms)                           [ OK ]
    * TEST: IGMPv3 MRC non linear value of 240(x0.1s)                     [ OK ]
    * TEST: MLDv2 MRC non linear value of 48000(ms)                       [ OK ]

v5:
- Added 'Reviewed-by:' as requested
- Modified script to fix AND logic in classifiers and updated comments
- Passed expected value as argument rather than hard coding in script
- Added minor comments for macros

v4:
- Removed binary application and related codes for selftests
- Decided hard coded match based on extra arguments for selftests
- Ran shellcheck on modified selftests code and fixed
- Updated comments on patch[1,2,3,4] as per review
- Added 'Reviewed-by:' as requested on patch4

v3:
- Updated the series title for better clarity; old one is here
  https://lore.kernel.org/all/20260326150742.50289-1-royujjal@gmail.com/
- Added key changes section for new selftests patch
- Updated netdev style comments and addressed review comments
- Fixed MLDv2 MRC conversion logic during query generation
- Mentioned intervals in units wherever applicable
- Dropped type casting and fixed indentations

v2:
- Retargeted the series to net-next as suggested
- Fixed a compilation warning in the MLD rename change
- Kept reverse xmas tree order in IGMP exponential encoding change
- Added bridge selftests to validate IGMPv3 Query MRC and QQIC handling

Ujjal Roy (5):
  ipv4: igmp: get rid of IGMPV3_{QQIC,MRC} and simplify calculation
  ipv6: mld: rename mldv2_mrc() and add mldv2_qqi()
  ipv4: igmp: encode multicast exponential fields
  ipv6: mld: encode multicast exponential fields
  selftests: net: bridge: add MRC and QQIC field encoding tests

 include/linux/igmp.h                          | 167 +++++++++++++++-
 include/net/mld.h                             | 185 +++++++++++++++++-
 net/bridge/br_multicast.c                     |  22 +--
 net/ipv4/igmp.c                               |   6 +-
 net/ipv6/mcast.c                              |  19 +-
 .../net/forwarding/bridge_vlan_mcast.sh       | 140 ++++++++++++-
 6 files changed, 483 insertions(+), 56 deletions(-)


base-commit: edf4bee4215a173c0534d1851d7523d827149f9e
-- 
2.43.0


^ permalink raw reply

* Re: [PATCH net-next 05/12] net: stmmac: dwxgmac2: Add multi MSI interrupt mode
From: Andrew Lunn @ 2026-05-01 17:21 UTC (permalink / raw)
  To: Alex Elder
  Cc: andrew+netdev, davem, edumazet, kuba, pabeni, maxime.chevallier,
	rmk+kernel, andersson, konradybcio, robh, krzk+dt, conor+dt,
	linusw, brgl, arnd, gregkh, Daniel Thompson, mohd.anwar,
	a0987203069, alexandre.torgue, ast, boon.khai.ng, chenchuangyu,
	chenhuacai, daniel, hawk, hkallweit1, inochiama, john.fastabend,
	julianbraha, livelycarpet87, matthew.gerlach, mcoquelin.stm32, me,
	prabhakar.mahadev-lad.rj, richardcochran, rohan.g.thomas, sdf,
	siyanteng, weishangjuan, wens, netdev, bpf, linux-arm-msm,
	devicetree, linux-gpio, linux-stm32, linux-arm-kernel,
	linux-kernel
In-Reply-To: <20260501155421.3329862-6-elder@riscstar.com>

On Fri, May 01, 2026 at 10:54:13AM -0500, Alex Elder wrote:
> From: Daniel Thompson <daniel@riscstar.com>
> 
> Currently there are no XGMAC platforms integrated using the multi MSI
> interrupt mode. In other words no existing driver sets both
> DWMAC_CORE_XGMAC and STMMAC_FLAG_MULTI_MSI_EN.
> 
> In order to support systems that do enable both options (such as the
> Toshiba TC9564 whose driver is currently being developed) we need to
> add logic to the XGMAC DMA callbacks. Happily we can simply
> replicate similar code from GMAC4. Let's do that!

The word replicate made me think it has been cut/paste, rather than
being refactored into a helper. However,

> +#define XGMAC_INTM_MASK			GENMASK(13, 12)

#define DMA_BUS_MODE_INTM_MASK		GENMASK(17, 16)

Different bits in the register, so the code structure is the same, but
the code cannot be shared in a meaningful way. So this is O.K.

    Andrew

^ permalink raw reply

* Re: [PATCH net-next 3/3] net: selftests: add getsockopt_iter regression tests
From: Bobby Eshleman @ 2026-05-01 17:17 UTC (permalink / raw)
  To: Breno Leitao
  Cc: David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
	Simon Horman, Stefano Garzarella, Shuah Khan, sdf.kernel, netdev,
	linux-kernel, virtualization, linux-kselftest, kernel-team
In-Reply-To: <20260501-getsock_one-v1-3-810ce23ea70e@debian.org>

On Fri, May 01, 2026 at 08:52:53AM -0700, Breno Leitao wrote:
> Add a single kselftest covering the proto_ops getsockopt_iter
> conversions for AF_NETLINK and AF_VSOCK, using one fixture per protocol:
> 
> netlink:
> 
> NETLINK_PKTINFO covers the flag-style int path (exact size, oversize
> clamp, undersize -EINVAL); NETLINK_LIST_MEMBERSHIPS covers the
> size-discovery path that always reports the required buffer length back
> via optlen, even when the user buffer is too small to receive any group
> bits.
> 
> vsock:
> SO_VM_SOCKETS_BUFFER_SIZE covers the u64 path (exact size, oversize
> clamp, undersize -EINVAL).
> 
> Each fixture also exercises an unknown optname and a bogus level so
> the returned-length / errno semantics preserved by the sockopt_t
> conversion are pinned down.
> 
> Signed-off-by: Breno Leitao <leitao@debian.org>

This all looks good to me. My only thought was
SO_VM_SOCKETS_CONNECT_TIMEOUT_NEW/OLD would be a nice add-on for testing
greater than u64, but probably overkill since you already have
NETLINK_LIST_MEMBERSHIPS.

Code all looks reasonable.

Reviewed-by: Bobby Eshleman <bobbyeshleman@meta.com>

> ---
>  tools/testing/selftests/net/Makefile          |   1 +
>  tools/testing/selftests/net/getsockopt_iter.c | 213 ++++++++++++++++++++++++++
>  2 files changed, 214 insertions(+)
> 
> diff --git a/tools/testing/selftests/net/Makefile b/tools/testing/selftests/net/Makefile
> index a275ed5840265..baa30287cf222 100644
> --- a/tools/testing/selftests/net/Makefile
> +++ b/tools/testing/selftests/net/Makefile
> @@ -176,6 +176,7 @@ TEST_GEN_PROGS := \
>  	bind_timewait \
>  	bind_wildcard \
>  	epoll_busy_poll \
> +	getsockopt_iter \
>  	icmp_rfc4884 \
>  	ipv6_fragmentation \
>  	proc_net_pktgen \
> diff --git a/tools/testing/selftests/net/getsockopt_iter.c b/tools/testing/selftests/net/getsockopt_iter.c
> new file mode 100644
> index 0000000000000..179f9e84926fd
> --- /dev/null
> +++ b/tools/testing/selftests/net/getsockopt_iter.c
> @@ -0,0 +1,213 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Quick test for getsockopt{_iter} tests.
> + *
> + * Each fixture targets one converted protocol and pins down the
> + * returned-length / errno semantics across buffer-size variations,
> + * an unknown optname and a bogus level.
> + *
> + * - netlink: NETLINK_PKTINFO covers the flag-style int path; the
> + *   NETLINK_LIST_MEMBERSHIPS cases cover the size-discovery path
> + *   that always reports the required buffer length back via optlen,
> + *   even when the user buffer is too small to receive any group bits.
> + * - vsock:   SO_VM_SOCKETS_BUFFER_SIZE covers the u64 path.
> + *
> + * Author: Breno Leitao <leitao@debian.org>
> + */
> +
> +#include <errno.h>
> +#include <stdint.h>
> +#include <stdio.h>
> +#include <string.h>
> +#include <unistd.h>
> +#include <linux/netlink.h>
> +#include <linux/rtnetlink.h>
> +#include <linux/vm_sockets.h>
> +#include <sys/socket.h>
> +#include "kselftest_harness.h"
> +
> +#ifndef AF_VSOCK
> +#define AF_VSOCK 40
> +#endif
> +
> +/* ---------- netlink ---------- */
> +
> +FIXTURE(netlink)
> +{
> +	int fd;
> +};
> +
> +FIXTURE_SETUP(netlink)
> +{
> +	int group = RTNLGRP_LINK;
> +
> +	self->fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
> +	if (self->fd < 0)
> +		SKIP(return, "AF_NETLINK socket: %s", strerror(errno));
> +
> +	/* Joining a multicast group grows nlk->ngroups so the
> +	 * NETLINK_LIST_MEMBERSHIPS path has a non-zero size to report.
> +	 */
> +	if (setsockopt(self->fd, SOL_NETLINK, NETLINK_ADD_MEMBERSHIP,
> +		       &group, sizeof(group)) < 0)
> +		SKIP(return, "NETLINK_ADD_MEMBERSHIP: %s", strerror(errno));
> +}
> +
> +FIXTURE_TEARDOWN(netlink)
> +{
> +	if (self->fd >= 0)
> +		close(self->fd);
> +}
> +
> +TEST_F(netlink, pktinfo_exact)
> +{
> +	int val = -1;
> +	socklen_t optlen = sizeof(val);
> +
> +	ASSERT_EQ(0, getsockopt(self->fd, SOL_NETLINK, NETLINK_PKTINFO,
> +				&val, &optlen));
> +	ASSERT_EQ(sizeof(int), optlen);
> +	ASSERT_TRUE(val == 0 || val == 1);
> +}
> +
> +TEST_F(netlink, pktinfo_oversize_clamped)
> +{
> +	char buf[16] = {};
> +	socklen_t optlen = sizeof(buf);
> +
> +	ASSERT_EQ(0, getsockopt(self->fd, SOL_NETLINK, NETLINK_PKTINFO,
> +				buf, &optlen));
> +	ASSERT_EQ(sizeof(int), optlen);
> +}
> +
> +TEST_F(netlink, pktinfo_undersize)
> +{
> +	char buf[2] = {};
> +	socklen_t optlen = sizeof(buf);
> +
> +	ASSERT_EQ(-1, getsockopt(self->fd, SOL_NETLINK, NETLINK_PKTINFO,
> +				 buf, &optlen));
> +	ASSERT_EQ(EINVAL, errno);
> +}
> +
> +TEST_F(netlink, list_memberships_size_discovery)
> +{
> +	socklen_t optlen = 0;
> +	char dummy;
> +
> +	ASSERT_EQ(0, getsockopt(self->fd, SOL_NETLINK,
> +				NETLINK_LIST_MEMBERSHIPS,
> +				&dummy, &optlen));
> +	ASSERT_GT(optlen, 0);
> +	ASSERT_EQ(0, optlen % sizeof(__u32));
> +}
> +
> +TEST_F(netlink, list_memberships_full_read)
> +{
> +	__u32 buf[64] = {};
> +	socklen_t optlen = sizeof(buf);
> +
> +	ASSERT_EQ(0, getsockopt(self->fd, SOL_NETLINK,
> +				NETLINK_LIST_MEMBERSHIPS,
> +				buf, &optlen));
> +	ASSERT_GT(optlen, 0);
> +	ASSERT_LE(optlen, sizeof(buf));
> +	ASSERT_EQ(0, optlen % sizeof(__u32));
> +}
> +
> +TEST_F(netlink, bad_level)
> +{
> +	int val;
> +	socklen_t optlen = sizeof(val);
> +
> +	ASSERT_EQ(-1, getsockopt(self->fd, SOL_SOCKET + 1, NETLINK_PKTINFO,
> +				 &val, &optlen));
> +	ASSERT_EQ(ENOPROTOOPT, errno);
> +}
> +
> +TEST_F(netlink, bad_optname)
> +{
> +	int val;
> +	socklen_t optlen = sizeof(val);
> +
> +	ASSERT_EQ(-1, getsockopt(self->fd, SOL_NETLINK, 0x7fff,
> +				 &val, &optlen));
> +	ASSERT_EQ(ENOPROTOOPT, errno);
> +}
> +
> +/* ---------- vsock ---------- */
> +
> +FIXTURE(vsock)
> +{
> +	int fd;
> +};
> +
> +FIXTURE_SETUP(vsock)
> +{
> +	self->fd = socket(AF_VSOCK, SOCK_STREAM, 0);
> +	if (self->fd < 0)
> +		SKIP(return, "AF_VSOCK socket: %s", strerror(errno));
> +}
> +
> +FIXTURE_TEARDOWN(vsock)
> +{
> +	if (self->fd >= 0)
> +		close(self->fd);
> +}
> +
> +TEST_F(vsock, buffer_size_exact)
> +{
> +	uint64_t val = 0;
> +	socklen_t optlen = sizeof(val);
> +
> +	ASSERT_EQ(0, getsockopt(self->fd, AF_VSOCK,
> +				SO_VM_SOCKETS_BUFFER_SIZE,
> +				&val, &optlen));
> +	ASSERT_EQ(sizeof(uint64_t), optlen);
> +	ASSERT_GT(val, 0);
> +}
> +
> +TEST_F(vsock, buffer_size_oversize_clamped)
> +{
> +	char buf[16] = {};
> +	socklen_t optlen = sizeof(buf);
> +
> +	ASSERT_EQ(0, getsockopt(self->fd, AF_VSOCK,
> +				SO_VM_SOCKETS_BUFFER_SIZE,
> +				buf, &optlen));
> +	ASSERT_EQ(sizeof(uint64_t), optlen);
> +}
> +
> +TEST_F(vsock, buffer_size_undersize)
> +{
> +	char buf[4] = {};
> +	socklen_t optlen = sizeof(buf);
> +
> +	ASSERT_EQ(-1, getsockopt(self->fd, AF_VSOCK,
> +				 SO_VM_SOCKETS_BUFFER_SIZE,
> +				 buf, &optlen));
> +	ASSERT_EQ(EINVAL, errno);
> +}
> +
> +TEST_F(vsock, bad_level)
> +{
> +	uint64_t val;
> +	socklen_t optlen = sizeof(val);
> +
> +	ASSERT_EQ(-1, getsockopt(self->fd, SOL_SOCKET + 1,
> +				 SO_VM_SOCKETS_BUFFER_SIZE,
> +				 &val, &optlen));
> +	ASSERT_EQ(ENOPROTOOPT, errno);
> +}
> +
> +TEST_F(vsock, bad_optname)
> +{
> +	uint64_t val;
> +	socklen_t optlen = sizeof(val);
> +
> +	ASSERT_EQ(-1, getsockopt(self->fd, AF_VSOCK, 0x7fff,
> +				 &val, &optlen));
> +	ASSERT_EQ(ENOPROTOOPT, errno);
> +}
> +
> +TEST_HARNESS_MAIN
> 
> -- 
> 2.52.0
> 

^ permalink raw reply

* Re: [PATCH net-next 04/12] net: stmmac: dma: create a separate dma_device pointer
From: Andrew Lunn @ 2026-05-01 17:13 UTC (permalink / raw)
  To: Alex Elder
  Cc: andrew+netdev, davem, edumazet, kuba, pabeni, maxime.chevallier,
	rmk+kernel, andersson, konradybcio, robh, krzk+dt, conor+dt,
	linusw, brgl, arnd, gregkh, daniel, mohd.anwar, a0987203069,
	alexandre.torgue, ast, boon.khai.ng, chenchuangyu, chenhuacai,
	daniel, hawk, hkallweit1, inochiama, john.fastabend, julianbraha,
	livelycarpet87, matthew.gerlach, mcoquelin.stm32, me,
	prabhakar.mahadev-lad.rj, richardcochran, rohan.g.thomas, sdf,
	siyanteng, weishangjuan, wens, netdev, bpf, linux-arm-msm,
	devicetree, linux-gpio, linux-stm32, linux-arm-kernel,
	linux-kernel
In-Reply-To: <20260501155421.3329862-5-elder@riscstar.com>

>  	if (priv->dma_cap.host_dma_width) {
> -		ret = dma_set_mask_and_coherent(device,
> -				DMA_BIT_MASK(priv->dma_cap.host_dma_width));
> +		u64 mask = DMA_BIT_MASK(priv->dma_cap.host_dma_width);
> +
> +		ret = dma_set_mask_and_coherent(priv->dma_device, mask);

I'm nitpicking, but i don't think you need to introduce mask.
DMA_BIT_MASK... is already on a line of its own, and is within the 80
limit. Nothing changes here with s/device/priv->dma_device/

	Andrew

^ permalink raw reply

* Re: [PATCH net-next 03/12] net: pcs: pcs-xpcs: Preserve BMCR_ANENBLE during link up
From: Andrew Lunn @ 2026-05-01 17:06 UTC (permalink / raw)
  To: Alex Elder
  Cc: andrew+netdev, davem, edumazet, kuba, pabeni, maxime.chevallier,
	rmk+kernel, andersson, konradybcio, robh, krzk+dt, conor+dt,
	linusw, brgl, arnd, gregkh, Daniel Thompson, mohd.anwar,
	a0987203069, alexandre.torgue, ast, boon.khai.ng, chenchuangyu,
	chenhuacai, daniel, hawk, hkallweit1, inochiama, john.fastabend,
	julianbraha, livelycarpet87, matthew.gerlach, mcoquelin.stm32, me,
	prabhakar.mahadev-lad.rj, richardcochran, rohan.g.thomas, sdf,
	siyanteng, weishangjuan, wens, netdev, bpf, linux-arm-msm,
	devicetree, linux-gpio, linux-stm32, linux-arm-kernel,
	linux-kernel
In-Reply-To: <20260501155421.3329862-4-elder@riscstar.com>

On Fri, May 01, 2026 at 10:54:11AM -0500, Alex Elder wrote:
> From: Daniel Thompson <daniel@riscstar.com>
> 
> Currently the XCPS found on Toshiba TC9564 (a.k.a. Qualcomm QPS615)
> is unable to operate at 1000base-X and slower with a PHY connected
> using SGMII/2500base-X (in our case a Qualcomm QCA8081). The link
> negotiates speed correctly but the MAC can't get any packets out.

We need to break this down into its components.

I assume you are saying the PHY negotiates the media speed with the
link partner. That is PHY talking to PHY.

But we also have the PCS talking to the PHY. There can be inband
signalling here, for SGMII and 2500BaseX. But not for over clocked
SGMII, which is how some vendors implement 2500BaseX. SGMII signalling
does not work when overclocked to 2.5G.

> This attracted attention to the ANENABLE bit and we observed that the
> bit is currently set during config and cleared during link up.

Here we are talking about the PCS ANEBNABLE, not the PHY ANEBNABLE. So
this is negotiation between the PCS and the PHY.

> Preserving the bit during link up allows the system to work as expected.

>  	int ret;
>  
>  	if (neg_mode == PHYLINK_PCS_NEG_INBAND_ENABLED)
>  		return;

Think about this.

>  	ret = xpcs_write(xpcs, MDIO_MMD_VEND2, MII_BMCR,
> -			 mii_bmcr_encode_fixed(speed, duplex));
> +			 mii_bmcr_encode_fixed(speed, duplex) | an_enable);

And mii_bmcr_encode_fixed().

    Andrew

^ permalink raw reply

* Re: [PATCH] selftests: mptcp: add test for IPv6 subflow SLAB placement
From: Vastargazing @ 2026-05-01 17:01 UTC (permalink / raw)
  To: matttbe; +Cc: martineau, mptcp, netdev, linux-kselftest, shuah, stable, fw
In-Reply-To: <44e564f8-d059-407b-8f5e-a149dd76dea3@kernel.org>

Hi Matt,

Thanks for the review (and for cc'ing Florian - same mistake twice, noted).

You're right on both. The 755 thing is on me, and i'm convinced the
standalone script is too much for what's essentially a one-time init
ordering issue - the fix commit is the safety net here.

Withdrawing the patch.

One question if you don't mind: if something similar came up again and
a test *was* warranted, which existing script would be the natural
home? mptcp_join.sh seemed closest since it exercises the accept path,
but i'm not sure if checking /proc/slabinfo fits there or if there's a
better mechanism for that kind of thing.

Vastargazing

^ permalink raw reply

* Re: [PATCH net] rxrpc: Also unshare DATA/RESPONSE packets when paged frags are present
From: Hyunwoo Kim @ 2026-05-01 16:57 UTC (permalink / raw)
  To: Simon Horman
  Cc: dhowells, marc.dionne, davem, edumazet, kuba, pabeni, linux-afs,
	netdev, imv4bel
In-Reply-To: <20260501155806.222592-3-horms@kernel.org>

On Fri, May 01, 2026 at 04:58:08PM +0100, Simon Horman wrote:
> From: 'Simon Horman' <horms@kernel.org>
> 
> This is an AI-generated review of your patch. The human sending this
> email has considered the AI review valid, or at least plausible.
> Full review at: https://sashiko.dev
> ---
> rxrpc: Also unshare DATA/RESPONSE packets when paged frags are present
> 
> This commit updates the DATA and RESPONSE packet handlers in rxrpc to copy
> the skb into a linear one if there are paged fragments present. This ensures
> the security handler operates on a fully linear skb during decryption.
> 
> > diff --git a/net/rxrpc/call_event.c b/net/rxrpc/call_event.c
> > index fdd683261226..6c924ef55208 100644
> > --- a/net/rxrpc/call_event.c
> > +++ b/net/rxrpc/call_event.c
> > @@ -334,7 +334,7 @@ bool rxrpc_input_call_event(struct rxrpc_call *call)
> >  
> >  			if (sp->hdr.type == RXRPC_PACKET_TYPE_DATA &&
> >  			    sp->hdr.securityIndex != 0 &&
> > -			    skb_cloned(skb)) {
> > +			    (skb_cloned(skb) || skb->data_len)) {
> >  				/* Unshare the packet so that it can be
> >  				 * modified by in-place decryption.
> >  				 */
> 
> Since modern NIC drivers commonly use page pools where packet payloads are
> placed in page fragments, skb->data_len will be greater than zero for almost
> all standard incoming packets.
> 
> By taking this path, the code calls skb_copy() right after this check.
> Will this force a full packet copy for nearly all incoming RxRPC DATA
> packets, bypassing zero-copy receive paths and causing a large performance
> regression?
> 
> Additionally, skb_copy() fully linearizes the packet by allocating a single
> contiguous buffer for the entire packet size. For RxRPC Jumbo packets or UDP
> GRO-aggregated packets (which can reach up to ~64KB), this requires an
> order-4 page allocation.
> 
> Could relying on an order-4 GFP_ATOMIC allocation cause reliable OOM
> failures and packet drops on fragmented systems?
> 
> If the goal is to ensure the skb is writable for in-place decryption, would
> it be safer to use skb_cow_data() instead of forcing a full contiguous
> linearization?
> 
> Simon says:
> 
> I note that this patch included
> Fixes: d0d5c0cd1e71 ("rxrpc: Use skb_unshare() rather than skb_cow_data()")
> And that the suggestion in the last paragraph above would
> reintroduce back to the problem addressed by d0d5c0cd1e71.

Yes. applying the ai's suggestion would reintroduce the bug fixed
by d0d5c0cd1e71.

The performance concern regarding skb_copy() is, however, valid.
That said, I cannot think of a way to keep the zero-copy path while
still handling in-place decryption properly. Suggestions welcome.


Best regards,
Hyunwoo Kim

^ permalink raw reply

* Re: [PATCH net-next 02/12] net: pcs: pcs-xpcs: select operating mode for 10G-baseR capable PCS
From: Andrew Lunn @ 2026-05-01 16:50 UTC (permalink / raw)
  To: Alex Elder
  Cc: andrew+netdev, davem, edumazet, kuba, pabeni, maxime.chevallier,
	rmk+kernel, andersson, konradybcio, robh, krzk+dt, conor+dt,
	linusw, brgl, arnd, gregkh, Daniel Thompson, mohd.anwar,
	a0987203069, alexandre.torgue, ast, boon.khai.ng, chenchuangyu,
	chenhuacai, daniel, hawk, hkallweit1, inochiama, john.fastabend,
	julianbraha, livelycarpet87, matthew.gerlach, mcoquelin.stm32, me,
	prabhakar.mahadev-lad.rj, richardcochran, rohan.g.thomas, sdf,
	siyanteng, weishangjuan, wens, netdev, bpf, linux-arm-msm,
	devicetree, linux-gpio, linux-stm32, linux-arm-kernel,
	linux-kernel
In-Reply-To: <20260501155421.3329862-3-elder@riscstar.com>

> +static int xpcs_config_operating_mode(struct dw_xpcs *xpcs, int an_mode)
> +{
> +	int mdio_stat2, ret;
> +
> +	switch (an_mode) {
> +	case DW_AN_C37_SGMII:
> +	case DW_AN_C37_1000BASEX:
> +	case DW_2500BASEX:
> +		mdio_stat2 = xpcs_read(xpcs, MDIO_MMD_PCS, MDIO_STAT2);
> +		if (mdio_stat2 < 0)
> +			return mdio_stat2;
> +
> +		/*
> +		 * If this XPCS supports 10Gbase-R then it will be the default
> +		 * which prevents 1000base-X and slower from working correctly.

It would be interesting to know if Toshiba messed up the integration
of the PCS, or there is an errata for the licensed IP.

Anybody got access to the databook and erratas?

	Andrew

^ permalink raw reply

* [PATCH net] net: usb: asix: ax88772: re-add usbnet_link_change() in phylink callbacks
From: Markus Baier @ 2026-05-01 16:39 UTC (permalink / raw)
  To: Oleksij Rempel
  Cc: Andrew Lunn, David S . Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, Russell King, Ethan Nelson-Moore, Miaoqian Lin,
	linux-usb, netdev, linux-kernel, Markus Baier

Commit e0bffe3e6894 ("net: asix: ax88772: migrate to phylink") replaced
the asix_adjust_link() PHY callback with phylink's mac_link_up() and
mac_link_down() handlers, but did not carry over the usbnet_link_change()
notification that commit 805206e66fab ("net: asix: fix "can't send until
first packet is send" issue") had added.

As a result, the original symptom returns: when the link comes up,
usbnet is never notified, so the RX URB submission stays dormant until
some other event (e.g. a transmitted packet triggering the status
endpoint interrupt) wakes it up.

This is reproducible with the Apple A1277 USB Ethernet Adapter
(05ac:1402, AX88772A based) on a Banana Pro using a static IPv4
configuration. After bringing the interface up, no incoming packets are
received until the first outgoing frame triggers usbnet's RX path.

Restore the link change notification, gated on a carrier transition so
the call remains idempotent if the status endpoint also reports the
change later.

Fixes: e0bffe3e6894 ("net: asix: ax88772: migrate to phylink")
Assisted-by: Claude:claude-opus-4-7
Signed-off-by: Markus Baier <Markus.Baier@soslab.tu-darmstadt.de>
---

Transparency notice: The English formulation of this bug report was
prepared with AI assistance. The actual system testing and verification
of the issue were performed manually without AI involvement.
The bug was originally located via git bisect between v5.13 (good)
and v5.14 (first bad), pointing to commit e532a096be0e ("net: usb: asix:
ax88772: add phylib support"). Commit 805206e66fab later fixed this in
v5.19, but commit e0bffe3e6894 (phylink migration in v6.x) re-introduced
the same symptom by replacing the asix_adjust_link() callback path
without porting the usbnet_link_change() call to the new phylink
mac_link_up/down handlers.

First Tested on Linux 6.18.18-gentoo with Apple A1277 USB Ethernet Adapter
(05ac:1402, AX88772A based) on a Banana Pro (Allwinner A20).
Later on Linux 7.1.0-rc1 with identical hardware setup.

 drivers/net/usb/asix_devices.c | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/drivers/net/usb/asix_devices.c b/drivers/net/usb/asix_devices.c
index df0bcfedddbc..293ef80c4e30 100644
--- a/drivers/net/usb/asix_devices.c
+++ b/drivers/net/usb/asix_devices.c
@@ -756,6 +756,7 @@ static void ax88772_mac_link_down(struct phylink_config *config,
 	struct usbnet *dev = netdev_priv(to_net_dev(config->dev));
 
 	asix_write_medium_mode(dev, 0, 0);
+	usbnet_link_change(dev, false, false);
 }
 
 static void ax88772_mac_link_up(struct phylink_config *config,
@@ -786,6 +787,7 @@ static void ax88772_mac_link_up(struct phylink_config *config,
 		m |= AX_MEDIUM_RFC;
 
 	asix_write_medium_mode(dev, m, 0);
+	usbnet_link_change(dev, true, false);
 }
 
 static const struct phylink_mac_ops ax88772_phylink_mac_ops = {
-- 
2.52.0


^ permalink raw reply related

* Re: [PATCH 00/11] mlx5 support for VFIO self test
From: Jason Gunthorpe @ 2026-05-01 16:43 UTC (permalink / raw)
  To: David Matlack
  Cc: Alex Williamson, kvm, Leon Romanovsky, linux-kselftest,
	linux-rdma, Mark Bloch, netdev, Saeed Mahameed, Shuah Khan,
	Tariq Toukan, patches, Josh Hilke
In-Reply-To: <CALzav=ci8bi3=sY+F3HJTB5sOQ_pJ8Lm+kz0CDBBWVXry5P98w@mail.gmail.com>

On Fri, May 01, 2026 at 09:11:11AM -0700, David Matlack wrote:
> On Thu, Apr 30, 2026 at 5:08 PM Jason Gunthorpe <jgg@nvidia.com> wrote:
> >
> > Add an mlx5 driver to VFIO self test. This is largely a remix of the
> > existing VFIO mlx5 driver in rdma-core. It uses an RDMA loopback QP
> > to issue RDMA WRITE operations which effectively perform memory
> > copies using DMA. Since mlx5 has a stable programming ABI this
> > should work on devices from CX5 to current HW. The device FW must
> > support the QP loopback configuration.
> 
> > This entire series was coded by Claude Code in about 4 days.
> 
> Very exciting. Josh Hilke from Google is also working on using AI to
> create a selftest driver for Intel IGB NICs so VFIO selftests can run
> in QEMU [1]. So it's encouraging to see you were able to do it with
> mlx5.
> 
> [1] https://www.qemu.org/docs/master/system/devices/igb.html

Yes! I would feed DPDK in as well in this case? Combined with the
kernel driver it should be doable. It is much easier if you understand
how the NIC works, of course. This worked out significantly because I
guided it through sufficiently small steps and knew where to find all
the quality reference material..

> >  - Make it work on a PF too (this is surprisingly hard!).
> 
> Can it work on CX VFs? We're interested in continuously performing
> memory copies across a Live Update using a VF via selftests to
> demonstrate SR-IOV preservation (when we eventually get there).

Yes, I started with VF because it is simpler. The PF support flow
requires a bunch more complicated stuff.

Jason

^ permalink raw reply

* Re: [PATCH net 4/7] net: tls: fix off-by-one in sg_chain entry count for wrapped sk_msg ring
From: Sabrina Dubroca @ 2026-05-01 16:40 UTC (permalink / raw)
  To: Jakub Kicinski
  Cc: davem, netdev, edumazet, pabeni, andrew+netdev, horms, bpf,
	john.fastabend, linux-kselftest, 钱一铭, daniel,
	jonathan.lemon
In-Reply-To: <20260429222944.2139041-5-kuba@kernel.org>

[I'm slowly trying to catch up with the chunk of ktls patches that
were posted in the past 3 days]

2026-04-29, 15:29:41 -0700, Jakub Kicinski wrote:
[...]
> Every other wrapped-ring arithmetic operation in the sk_msg subsystem
> (sk_msg_iter_dist, sk_msg_iter_var_next, sk_msg_iter_var_prev,
> bpf_msg_pull_data) correctly uses NR_MSG_FRAG_IDS as the ring modulus.
> This sg_chain call is the sole remaining use of MAX_SKB_FRAGS for
> ring-modulus arithmetic and was introduced after the ring expansion.

[...]
> diff --git a/net/tls/tls_sw.c b/net/tls/tls_sw.c
> index 906a1998c630..600e13effaab 100644
> --- a/net/tls/tls_sw.c
> +++ b/net/tls/tls_sw.c
> @@ -802,7 +802,7 @@ static int tls_push_record(struct sock *sk, int flags,
>  
>  	if (msg_pl->sg.end < msg_pl->sg.start) {
>  		sg_chain(&msg_pl->sg.data[msg_pl->sg.start],
> -			 MAX_SKB_FRAGS - msg_pl->sg.start + 1,
> +			 NR_MSG_FRAG_IDS - msg_pl->sg.start + 1,
>  			 msg_pl->sg.data);

And get rid of the [start] / NR - start dance to make this code a bit
clearer?

This should maybe even be an skmsg helper to avoid "random" code
making assumptions on the size of the sg.data array. The last
paragraph in your commit message also tends to suggest that.

-- 
Sabrina

^ permalink raw reply

* Re: [PATCH net v2] net: mana: Optimize irq affinity for low vcpu configs
From: Yury Norov @ 2026-05-01 16:22 UTC (permalink / raw)
  To: Shradha Gupta
  Cc: Dexuan Cui, Wei Liu, Haiyang Zhang, K. Y. Srinivasan, Andrew Lunn,
	David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
	Konstantin Taranov, Simon Horman, Erni Sri Satya Vennela,
	Dipayaan Roy, Shiraz Saleem, Michael Kelley, Long Li, Yury Norov,
	linux-hyperv, linux-kernel, netdev, Paul Rosswurm, Shradha Gupta,
	Saurabh Singh Sengar, stable
In-Reply-To: <20260429090640.1790104-1-shradhagupta@linux.microsoft.com>

On Wed, Apr 29, 2026 at 02:06:37AM -0700, Shradha Gupta wrote:
> In mana driver, the number of IRQs allocated is capped by the
> min(num_cpu + 1, queue count). In cases, where the IRQ count is greater
> than the vcpu count, we want to utilize all the vCPUs, irrespective of
> their NUMA/core bindings.
> 
> This is important, especially in the envs where number of vCPUs are so
> few that the softIRQ handling overhead on two IRQs on the same vCPU is
> much more than their overheads if they were spread across sibling vCPUs.
> 
> This behaviour is more evident with dynamic IRQ allocation. Since MANA
> IRQs are assigned at a later stage compared to static allocation, other
> device IRQs may already be affinitized to the vCPUs. As a result, IRQ
> weights become imbalanced, causing multiple MANA IRQs to land on the
> same vCPU, while some vCPUs have none.
> 
> In such cases when many parallel TCP connections are tested, the
> throughput drops significantly.
> 
> Test envs:
> =======================================================
> Case 1: without this patch
> =======================================================
> 4 vcpu(2 cores), 5 MANA IRQs (1 HWC + 4 Queue)
> 
> 	TYPE		effective vCPU aff
> =======================================================
> IRQ0:	HWC		0
> IRQ1:	mana_q1		0
> IRQ2:	mana_q2		2
> IRQ3:	mana_q3		0
> IRQ4:	mana_q4		3
> 
> %soft on each vCPU(mpstat -P ALL 1) on receiver
> vCPU		0	1	2	3
> =======================================================
> pass 1:		38.85	0.03	24.89	24.65
> pass 2:		39.15	0.03	24.57	25.28
> pass 3:		40.36	0.03	23.20	23.17
> 
> =======================================================
> Case 2: with this patch
> =======================================================
> 4 vcpu(2 cores), 5 MANA IRQs (1 HWC + 4 Queue)
> 
>         TYPE            effective vCPU aff
> =======================================================
> IRQ0:   HWC             0
> IRQ1:   mana_q1         0
> IRQ2:   mana_q2         1
> IRQ3:   mana_q3         2
> IRQ4:   mana_q4         3
> 
> %soft on each vCPU(mpstat -P ALL 1) on receiver
> vCPU            0       1       2       3
> =======================================================
> pass 1:         15.42	15.85	14.99	14.51
> pass 2:         15.53	15.94	15.81	15.93
> pass 3:         16.41	16.35	16.40	16.36
> 
> =======================================================
> Throughput Impact(in Gbps, same env)
> =======================================================
> TCP conn	with patch	w/o patch
> 20480		15.65		7.73
> 10240		15.63		8.93
> 8192		15.64		9.69
> 6144		15.64		13.16
> 4096		15.69		15.75
> 2048		15.69		15.83
> 1024		15.71		15.28
> 
> Fixes: 755391121038 ("net: mana: Allocate MSI-X vectors dynamically")
> Cc: stable@vger.kernel.org
> Co-developed-by: Erni Sri Satya Vennela <ernis@linux.microsoft.com>
> Signed-off-by: Erni Sri Satya Vennela <ernis@linux.microsoft.com>
> Signed-off-by: Shradha Gupta <shradhagupta@linux.microsoft.com>
> Reviewed-by: Haiyang Zhang <haiyangz@microsoft.com>
> ---
> Changes in v2
>  * Removed the unused skip_first_cpu variable
>  * fixed exit condition in irq_setup_linear() with len == 0
>  * changed return type of irq_setup_linear() as it will always be 0
>  * removed the unnecessary rcu_read_lock() in irq_setup_linear()
>  * added appropriate comments to indicate expected behaviour when
>    IRQs are more than or equal to num_online_cpus()
> ---
>  .../net/ethernet/microsoft/mana/gdma_main.c   | 47 ++++++++++++++++---
>  1 file changed, 40 insertions(+), 7 deletions(-)
> 
> diff --git a/drivers/net/ethernet/microsoft/mana/gdma_main.c b/drivers/net/ethernet/microsoft/mana/gdma_main.c
> index 098fbda0d128..d740d1dc43da 100644
> --- a/drivers/net/ethernet/microsoft/mana/gdma_main.c
> +++ b/drivers/net/ethernet/microsoft/mana/gdma_main.c
> @@ -167,6 +167,8 @@ static int mana_gd_query_max_resources(struct pci_dev *pdev)
>  	} else {
>  		/* If dynamic allocation is enabled we have already allocated
>  		 * hwc msi
> +		 * Also, we make sure in this case the following is always true
> +		 * (num_msix_usable - 1 HWC) <= num_online_cpus()
>  		 */
>  		gc->num_msix_usable = min(resp.max_msix, num_online_cpus() + 1);
>  	}
> @@ -1672,11 +1674,24 @@ static int irq_setup(unsigned int *irqs, unsigned int len, int node,
>  	return 0;
>  }
>  
> +/* should be called with cpus_read_lock() held */
> +static void irq_setup_linear(unsigned int *irqs, unsigned int len)
> +{
> +	int cpu;
> +
> +	for_each_online_cpu(cpu) {
> +		if (len == 0)
> +			break;
> +
> +		irq_set_affinity_and_hint(*irqs++, cpumask_of(cpu));
> +		len--;
> +	}
> +}
> +
>  static int mana_gd_setup_dyn_irqs(struct pci_dev *pdev, int nvec)
>  {
>  	struct gdma_context *gc = pci_get_drvdata(pdev);
>  	struct gdma_irq_context *gic;
> -	bool skip_first_cpu = false;
>  	int *irqs, irq, err, i;
>  
>  	irqs = kmalloc_objs(int, nvec);

So what about WARN_ON() and nvec adjustment before kmalloc?

> @@ -1722,13 +1737,31 @@ static int mana_gd_setup_dyn_irqs(struct pci_dev *pdev, int nvec)
>  	 * first CPU sibling group since they are already affinitized to HWC IRQ
>  	 */
>  	cpus_read_lock();
> -	if (gc->num_msix_usable <= num_online_cpus())
> -		skip_first_cpu = true;
> +	if (gc->num_msix_usable <= num_online_cpus()) {
> +		err = irq_setup(irqs, nvec, gc->numa_node, true);
> +		if (err) {
> +			cpus_read_unlock();
> +			goto free_irq;

One thing puzzles me: if you skip first CPU with this 'true', and the
gc->num_msix_usable == num_online_cpus(), it's one more than you can
distribute. What do I miss?

> +		}
> +	} else {
> +		/*
> +		 * When num_msix_usable are more than num_online_cpus, we try to
> +		 * make sure we are using all vcpus. In such a case NUMA or
> +		 * CPU core affinity does not matter.

If it doesn't matter, why don't you assign each IRQ to all CPUs then?
In theory, the system would have most of flexibility to balance them.

> +		 * Note: in this case the total mana IRQ should always be
> +		 * num_online_cpus + 1. The first HWC IRQ is already handled
> +		 * in HWC setup calls
> +		 * However, if CPUs went offline since num_msix_usable was
> +		 * computed, nvec count will be more than num_online_cpus().
> +		 * In such cases remaining extra IRQs will retain their default
> +		 * affinity.
> +		 */
> +		if (nvec > num_online_cpus())
> +			dev_dbg(&pdev->dev,
> +				"IRQ count %d exceeds online CPU count %d. Some IRQs will share CPU\n",

I'd better say 'some IRQs will share the default CPU', and in the
perfect world, I'd like to see:

        'The IRQs #4-12 will share the default CPU #0'

type of message.

> +				nvec, num_online_cpus());
                
It's not that straightforward as it should be. In one case 

        nvec > num_online_cpus()

is a problem, while in another - not. It looks already suspicious. So
when you throw a warning, you should mention it, I believe.

In the

        gc->num_msix_usable <= num_online_cpus()

case, when nvec is too big, would'n 'some IRQs share some CPU' just
as well? If so, you again should throw a message.

> -	err = irq_setup(irqs, nvec, gc->numa_node, skip_first_cpu);
> -	if (err) {
> -		cpus_read_unlock();
> -		goto free_irq;
> +		irq_setup_linear(irqs, nvec);
>  	}
>  
>  	cpus_read_unlock();
> 
> base-commit: e728258debd553c95d2e70f9cd97c9fde27c7130
> -- 
> 2.34.1

^ permalink raw reply

* Re: [PATCH 00/11] mlx5 support for VFIO self test
From: David Matlack @ 2026-05-01 16:11 UTC (permalink / raw)
  To: Jason Gunthorpe
  Cc: Alex Williamson, kvm, Leon Romanovsky, linux-kselftest,
	linux-rdma, Mark Bloch, netdev, Saeed Mahameed, Shuah Khan,
	Tariq Toukan, patches, Josh Hilke
In-Reply-To: <0-v1-dc5fa250ca1d+3213-mlx5st_jgg@nvidia.com>

On Thu, Apr 30, 2026 at 5:08 PM Jason Gunthorpe <jgg@nvidia.com> wrote:
>
> Add an mlx5 driver to VFIO self test. This is largely a remix of the
> existing VFIO mlx5 driver in rdma-core. It uses an RDMA loopback QP
> to issue RDMA WRITE operations which effectively perform memory
> copies using DMA. Since mlx5 has a stable programming ABI this
> should work on devices from CX5 to current HW. The device FW must
> support the QP loopback configuration.

> This entire series was coded by Claude Code in about 4 days.

Very exciting. Josh Hilke from Google is also working on using AI to
create a selftest driver for Intel IGB NICs so VFIO selftests can run
in QEMU [1]. So it's encouraging to see you were able to do it with
mlx5.

[1] https://www.qemu.org/docs/master/system/devices/igb.html

> For those interested, the flow I used was broadly a prompt sequence
> sort of like:
>
>  - Hey Claude, go look at the falcon series, VFIO self test, the
>    mlx5 driver, rdma-core and some PDF documentation and make a
>    plan to put mlx5 under the selftest.
>  - Write an rdma-core application using the built-in VFIO provider
>    that can do the required memcpy operations that vfio selftests
>    wants.
>    (This resulted in a 1k loc C file that compiled and ran the
>     first time but had a few bugs related to device programming
>     that the AI resolved.)
>  - Replace the rdma-core components with open-coded versions to
>    create a fully stand-alone program that does the DMA memcpy.
>  - Review and audit the thing.
>  [Pause and de-slop it]
>  - Make it work on a PF too (this is surprisingly hard!).

Can it work on CX VFs? We're interested in continuously performing
memory copies across a Live Update using a VF via selftests to
demonstrate SR-IOV preservation (when we eventually get there).

>  [Move to a kernel tree and copy all the .md files and .c program
>   it made]
>  - Hey Claude, look at all this stuff and make a broad plan to
>    actually build a VFIO self test.
>  - Here is my 1 sentence advice on what each patch should look
>    like, make a detailed plan to make a patch for every one.
>  [Pause and polish the patch plans]
>  - Execute plan X then commit it [pause and de-slop each patch,
>    repeat].
>  [Review and final polish]
>
> It is based on a tree with the falcon series applied.

Thanks for sending, I look forward to reviewing!

^ permalink raw reply

* Re: [PATCH ipsec] esp: Force skb_cow_data() on RX when the skb is non-linear
From: Simon Horman @ 2026-05-01 16:09 UTC (permalink / raw)
  To: imv4bel
  Cc: 'Simon Horman', steffen.klassert, herbert, davem, dsahern,
	edumazet, kuba, pabeni, ilant, sowmini.varadhan, netdev
In-Reply-To: <afLDKSvAvMwGh7Fy@v4bel>

From: 'Simon Horman' <horms@kernel.org>

This is an AI-generated review of your patch. The human sending this
email has considered the AI review valid, or at least plausible.
Full review at: https://sashiko.dev
---
esp: Force skb_cow_data() on RX when the skb is non-linear

This commit modifies esp_input() and esp6_input() to unconditionally call
skb_cow_data() for all non-linear skbs. This removes the fast path that
previously allowed in-place AEAD decryption for skbs carrying paged
fragments without a frag_list.

> diff --git a/net/ipv4/esp4.c b/net/ipv4/esp4.c
> index 6dfc0bcdef65..a6fbdec139dc 100644
> --- a/net/ipv4/esp4.c
> +++ b/net/ipv4/esp4.c
> @@ -868,17 +868,10 @@ static int esp_input(struct xfrm_state *x, struct sk_buff *skb)
>  		assoclen += seqhilen;
>  	}
>  
> -	if (!skb_cloned(skb)) {
> -		if (!skb_is_nonlinear(skb)) {
> -			nfrags = 1;
> -
> -			goto skip_cow;
> -		} else if (!skb_has_frag_list(skb)) {
> -			nfrags = skb_shinfo(skb)->nr_frags;
> -			nfrags++;
> +	if (!skb_cloned(skb) && !skb_is_nonlinear(skb)) {
> +		nfrags = 1;
>  
> -			goto skip_cow;
> -		}
> +		goto skip_cow;
>  	}

By removing this fast path and forcing all non-linear skbs to fall through
to skb_cow_data(), does this break the IPsec GRO/GSO fast path?

Looking at skb_cow_data() in net/core/skbuff.c:

int skb_cow_data(struct sk_buff *skb, int tailbits, struct sk_buff **trailer)
{
	...
	if ((skb_cloned(skb) || skb_shinfo(skb)->nr_frags) &&
	    !__pskb_pull_tail(skb, __skb_pagelen(skb)))
		return -ENOMEM;
	...

For a large GRO or GSO packet (which can be up to 64KB) with paged fragments,
this attempts to pull the entire paged payload into the linear data area
via __pskb_pull_tail().

Since this runs in the RX softirq path, pulling the entire payload requires
a high-order contiguous memory allocation using GFP_ATOMIC. 

Could this lead to widespread packet drops when memory is fragmented and
these high-order atomic allocations fail?

Additionally, even when the allocations succeed, does unconditionally copying
up to 64KB of data per packet introduce a severe performance regression by
entirely negating the software crypto fast path?

[ ... ]

^ permalink raw reply

* Re: [PATCH net] rxrpc: Also unshare DATA/RESPONSE packets when paged frags are present
From: Simon Horman @ 2026-05-01 15:58 UTC (permalink / raw)
  To: imv4bel
  Cc: 'Simon Horman', dhowells, marc.dionne, davem, edumazet,
	kuba, pabeni, linux-afs, netdev
In-Reply-To: <afKV2zGR6rrelPC7@v4bel>

From: 'Simon Horman' <horms@kernel.org>

This is an AI-generated review of your patch. The human sending this
email has considered the AI review valid, or at least plausible.
Full review at: https://sashiko.dev
---
rxrpc: Also unshare DATA/RESPONSE packets when paged frags are present

This commit updates the DATA and RESPONSE packet handlers in rxrpc to copy
the skb into a linear one if there are paged fragments present. This ensures
the security handler operates on a fully linear skb during decryption.

> diff --git a/net/rxrpc/call_event.c b/net/rxrpc/call_event.c
> index fdd683261226..6c924ef55208 100644
> --- a/net/rxrpc/call_event.c
> +++ b/net/rxrpc/call_event.c
> @@ -334,7 +334,7 @@ bool rxrpc_input_call_event(struct rxrpc_call *call)
>  
>  			if (sp->hdr.type == RXRPC_PACKET_TYPE_DATA &&
>  			    sp->hdr.securityIndex != 0 &&
> -			    skb_cloned(skb)) {
> +			    (skb_cloned(skb) || skb->data_len)) {
>  				/* Unshare the packet so that it can be
>  				 * modified by in-place decryption.
>  				 */

Since modern NIC drivers commonly use page pools where packet payloads are
placed in page fragments, skb->data_len will be greater than zero for almost
all standard incoming packets.

By taking this path, the code calls skb_copy() right after this check.
Will this force a full packet copy for nearly all incoming RxRPC DATA
packets, bypassing zero-copy receive paths and causing a large performance
regression?

Additionally, skb_copy() fully linearizes the packet by allocating a single
contiguous buffer for the entire packet size. For RxRPC Jumbo packets or UDP
GRO-aggregated packets (which can reach up to ~64KB), this requires an
order-4 page allocation.

Could relying on an order-4 GFP_ATOMIC allocation cause reliable OOM
failures and packet drops on fragmented systems?

If the goal is to ensure the skb is writable for in-place decryption, would
it be safer to use skb_cow_data() instead of forcing a full contiguous
linearization?

Simon says:

I note that this patch included
Fixes: d0d5c0cd1e71 ("rxrpc: Use skb_unshare() rather than skb_cow_data()")
And that the suggestion in the last paragraph above would
reintroduce back to the problem addressed by d0d5c0cd1e71.

^ permalink raw reply

* [PATCH net-next 12/12] arm64: dts: qcom: qcs6490-rb3gen2: enable TC9564 with a single QCS8081 phy
From: Alex Elder @ 2026-05-01 15:54 UTC (permalink / raw)
  To: andrew+netdev, davem, edumazet, kuba, pabeni, maxime.chevallier,
	rmk+kernel, andersson, konradybcio, robh, krzk+dt, conor+dt,
	linusw, brgl, arnd, gregkh
  Cc: Daniel Thompson, elder, mohd.anwar, a0987203069, alexandre.torgue,
	ast, boon.khai.ng, chenchuangyu, chenhuacai, daniel, hawk,
	hkallweit1, inochiama, john.fastabend, julianbraha,
	livelycarpet87, matthew.gerlach, mcoquelin.stm32, me,
	prabhakar.mahadev-lad.rj, richardcochran, rohan.g.thomas, sdf,
	siyanteng, weishangjuan, wens, netdev, bpf, linux-arm-msm,
	devicetree, linux-gpio, linux-stm32, linux-arm-kernel,
	linux-kernel
In-Reply-To: <20260501155421.3329862-1-elder@riscstar.com>

From: Daniel Thompson <daniel@riscstar.com>

The QCS6490 RB3Gen2 includes a Toshiba TC9564 (a.k.a. Qualcomm QPS615).
TC9564 is an twin Ethernet-AVB/TSN bridge with an integrated PCIe switch.

There are multiple builds of RB3Gen2 with components included/excluded.
That means whether or not there is a phy attached to eMAC0 depends on
the exact board. However all versions include a TC9564 combined with a
single QCS8081 attached to eMAC1.

Add properties to the existing PCI nodes to describe how the TC9564 and
QCS8081 are connected to each other (and to the host SoC).

(Note: "pci1179,0220" is documented in the "net/toshiba,tc956x-dwmac.yaml"
binding, but checkpatch.pl doesn't recognize that.)

Co-developed-by: Alex Elder <elder@riscstar.com>
Signed-off-by: Alex Elder <elder@riscstar.com>
Signed-off-by: Daniel Thompson <daniel@riscstar.com>
---
 arch/arm64/boot/dts/qcom/qcs6490-rb3gen2.dts | 45 +++++++++++++++++++-
 1 file changed, 44 insertions(+), 1 deletion(-)

diff --git a/arch/arm64/boot/dts/qcom/qcs6490-rb3gen2.dts b/arch/arm64/boot/dts/qcom/qcs6490-rb3gen2.dts
index e393ccf1884af..72c89aa7a2e43 100644
--- a/arch/arm64/boot/dts/qcom/qcs6490-rb3gen2.dts
+++ b/arch/arm64/boot/dts/qcom/qcs6490-rb3gen2.dts
@@ -308,6 +308,16 @@ vdd_ntn_1p8: regulator-vdd-ntn-1p8 {
 		regulator-enable-ramp-delay = <10000>;
 	};
 
+	qep_1p8: regulator-qep-1p8 {
+		compatible = "regulator-fixed";
+		regulator-name = "qep_1p8";
+		gpio = <&pm7325_gpios 8 GPIO_ACTIVE_HIGH>;
+		regulator-min-microvolt = <1800000>;
+		regulator-max-microvolt = <1800000>;
+		enable-active-high;
+		regulator-always-on;
+	};
+
 	wcn6750-pmu {
 		compatible = "qcom,wcn6750-pmu";
 		pinctrl-0 = <&bt_en>;
@@ -937,20 +947,46 @@ pcie@3,0 {
 			ranges;
 			bus-range = <0x5 0xff>;
 
-			pci@0,0 {
+			tc956x_emac0: pci@0,0 {
+				compatible = "pci1179,0220";
 				reg = <0x50000 0x0 0x0 0x0 0x0>;
 				#address-cells = <3>;
 				#size-cells = <2>;
 				device_type = "pci";
 				ranges;
+
+				gpio-controller;
+				#gpio-cells = <2>;
 			};
 
 			pci@0,1 {
+				compatible = "pci1179,0220";
 				reg = <0x50100 0x0 0x0 0x0 0x0>;
 				#address-cells = <3>;
 				#size-cells = <2>;
 				device_type = "pci";
 				ranges;
+
+				phy-mode = "sgmii";
+				phy-handle = <&tc956x_emac1_phy>;
+
+				mdio {
+					compatible = "snps,dwmac-mdio";
+					#address-cells = <1>;
+					#size-cells = <0>;
+
+					tc956x_emac1_phy: ethernet-phy@1c {
+						compatible = "ethernet-phy-id004d.d101";
+						reg = <0x1c>;
+						reset-gpios = <&tc956x_emac0 1 GPIO_ACTIVE_LOW>;
+						reset-assert-us = <20>;
+						reset-deassert-us = <20>;
+
+						pinctrl-names = "default";
+						pinctrl-0 = <&qep_irq_pin>;
+						interrupts-extended = <&tlmm 101 IRQ_TYPE_EDGE_FALLING>;
+					};
+				};
 			};
 		};
 	};
@@ -1524,6 +1560,13 @@ usb_hub_reset_state: usb-hub-reset-state {
 		drive-strength = <2>;
 		bias-disable;
 	};
+
+	qep_irq_pin: qep-irq-state {
+		pins = "gpio101";
+		function = "gpio";
+		drive-strength = <2>;
+		bias-disable;
+	};
 };
 
 &lpass_audiocc {
-- 
2.51.0


^ permalink raw reply related

* [PATCH net-next 11/12] misc: tc956x_pci: add TC956x/QPS615 support
From: Alex Elder @ 2026-05-01 15:54 UTC (permalink / raw)
  To: andrew+netdev, davem, edumazet, kuba, pabeni, maxime.chevallier,
	rmk+kernel, andersson, konradybcio, robh, krzk+dt, conor+dt,
	linusw, brgl, arnd, gregkh
  Cc: daniel, elder, mohd.anwar, a0987203069, alexandre.torgue, ast,
	boon.khai.ng, chenchuangyu, chenhuacai, daniel, hawk, hkallweit1,
	inochiama, john.fastabend, julianbraha, livelycarpet87,
	matthew.gerlach, mcoquelin.stm32, me, prabhakar.mahadev-lad.rj,
	richardcochran, rohan.g.thomas, sdf, siyanteng, weishangjuan,
	wens, netdev, bpf, linux-arm-msm, devicetree, linux-gpio,
	linux-stm32, linux-arm-kernel, linux-kernel
In-Reply-To: <20260501155421.3329862-1-elder@riscstar.com>

The Toshiba TC956x is an Ethernet AVB/TSN bridge,  and is
essentially a small and highly-specialized SoC.  It implements
a number of internal functions, including a GPIO controller,
control registers managing internal reset and clock control
signals, a PCIe switch and internal endpoint, and mapping
hardware that translates between PCIe and internal addressing.

The internal PCIe endpoint implements two PCIe functions, each of
which has an attached eMAC.  Each of these eMACs is represented as
two separate devices:  a PCIe function; and the eMAC.  The PCIe
function driver serves as the primary driver, creating other
associated devices using the auxiliary bus.

PCIe function 0 is responsible for managing common features on
the TC956x chip.  It initializes a "chip" data structure that
keeps track of common information, and makes that available via
its platform_data pointer to its PCIe function 1 peer.  It also
configures the address mapping hardware, and sets up an auxiliary
device for the GPIO controller.

As probing concludes, an auxiliary device is created to represent
the eMAC functionality attached to the PCIe function.  A block
of information is set up to be shared with the auxiliary device.
It provides the IRQ to be used by the MAC device, as well as a
some memory-mapped I/O pointers and a few other bits of information
about the chip.  This information is supplied via the auxiliary
device's platform_data pointer.

Co-developed-by: Daniel Thompson <daniel@riscstar.com>
Signed-off-by: Daniel Thompson <daniel@riscstar.com>
Signed-off-by: Alex Elder <elder@riscstar.com>
---
 drivers/misc/Kconfig      |  10 +
 drivers/misc/Makefile     |   1 +
 drivers/misc/tc956x_pci.c | 667 ++++++++++++++++++++++++++++++++++++++
 3 files changed, 678 insertions(+)
 create mode 100644 drivers/misc/tc956x_pci.c

diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
index 00683bf06258f..d021f37129e27 100644
--- a/drivers/misc/Kconfig
+++ b/drivers/misc/Kconfig
@@ -644,6 +644,16 @@ config MCHP_LAN966X_PCI
 	    - lan966x-miim (MDIO_MSCC_MIIM)
 	    - lan966x-switch (LAN966X_SWITCH)
 
+config TOSHIBA_TC956X_PCI
+	tristate "Toshiba TC956X PCI function support"
+	depends on PCI
+	select AUXILIARY_BUS
+	help
+	  This enables support for the two PCI functions implemented by
+	  the embedded PCIe endpoint in the Toshiba TC956X.  This driver
+	  creates auxiliary devices and requires drivers for these devices
+	  to function.
+
 source "drivers/misc/c2port/Kconfig"
 source "drivers/misc/eeprom/Kconfig"
 source "drivers/misc/cb710/Kconfig"
diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
index b32a2597d2467..644d508338382 100644
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -75,3 +75,4 @@ obj-$(CONFIG_MCHP_LAN966X_PCI)	+= lan966x-pci.o
 obj-y				+= keba/
 obj-y				+= amd-sbi/
 obj-$(CONFIG_MISC_RP1)		+= rp1/
+obj-$(CONFIG_TOSHIBA_TC956X_PCI) += tc956x_pci.o
diff --git a/drivers/misc/tc956x_pci.c b/drivers/misc/tc956x_pci.c
new file mode 100644
index 0000000000000..741a0ae0d3afb
--- /dev/null
+++ b/drivers/misc/tc956x_pci.c
@@ -0,0 +1,667 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/*
+ * Copyright (C) 2026 by RISCstar Solutions Corporation.  All rights reserved.
+ */
+
+/*
+ * The Toshiba TC956X implements a PCIe Gen 3 switch that connects an
+ * upstream x4 port to three downstream PCIe ports--two external ones
+ * and an internal one which implements an internal PCIe endpoint.  The
+ * endpoint implements two PCIe functions, each having a Synopsys XGMAC
+ * Ethernet interface.
+ *
+ * The TC956X implements other functionality, including an embedded
+ * MCU, a UART, a GPIO controller, internal resets and clocks, and
+ * interrupt handling.  These features are separate from (and in some
+ * cases used by) both Ethernet XGMACs.  Each Ethernet MAC must be
+ * attached to a working PHY for it to be functional, and for this
+ * reason either of them (or both!) might not be usable/used.
+ *
+ * To support the non-XGMAC functionality on the TC956X regardless of
+ * the presence of either Ethernet PHY, the Ethernet functions are
+ * treated as two parts:  a PCIe function; and a Synopsys XGMAC component.
+ * The PCIe function has access to the BARs used by the XGMAC, and maps
+ * them for use.  Each XGMAP is treated as an auxiliary sub-device of
+ * its (parent) PCIe function, and is probed and bound separate from it.
+ *
+ * This PCI driver binds to the Toshiba TC956X (physical) PCI function
+ * (VID 0x1179, DID 0x0220).  There are two of these present on the
+ * TC956X SoC.  This driver maps the PCI BARs and performs other initial
+ * setup, then creates auxiliary devices.
+ *
+ * Embedded PCI function 0 manages non-MAC functionality.  This includes
+ * creating and registering the GPIO auxiliary device (if necessary), as
+ * well as asserting and deasserting internal reset signals and enabling
+ * and disabling internal clocks.
+ *
+ * Both PCI functions create auxiliary devices to implement an Ethernet
+ * XGMAC.  A block of data (struct tc956x_dwmac_data) is shared using
+ * the auxiliary device's platform data with the stmmac driver that
+ * binds to the XGMAC auxiliary device.  This includes a number of
+ * pointers to memory regions used by the stmmac driver.
+ */
+
+#include <linux/auxiliary_bus.h>
+#include <linux/compiler_types.h>
+#include <linux/device.h>
+#include <linux/dev_printk.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/pci.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
+#include <linux/types.h>
+
+#include <soc/toshiba/tc956x-dwmac.h>
+
+#define DRIVER_NAME			TC956X_PCIE_DRIVER_NAME
+
+#define GPIO_DEVICE_NAME		"tc9564-gpio"
+
+#define PCI_DEVICE_ID_TOSHIBA_TC956X	0x0220
+
+/* PCI BAR assignments */
+#define PCI_BAR_BRIDGE_CONFIG		0	/* For TAMAP */
+#define PCI_BAR_SFR			4	/* For all other features */
+
+/* Chip and revision ID register */
+#define NCID_OFFSET			0x0000
+#define NCID_REV_ID_MASK		GENMASK(7, 0)
+
+/* Reset and clock register offsets.  MAC resets and clocks are controlled
+ * by bits in register 0 for MAC0, register 1 for MAC1.  Other non-MAC
+ * resets and clocks (whose IDs are defined here) are controlled by bits
+ * in register 0.
+ *
+ * These are relative to the base of the clock/reset regmap.
+ */
+#define RSTCTRL0_OFFSET			0x0008
+#define RSTCTRL1_OFFSET			0x0010
+#define CLKCTRL0_OFFSET			0x0004
+#define CLKCTRL1_OFFSET			0x000c
+
+enum reset_id {
+	RESET_MCU		= 0,
+	RESET_MCU1		= 1,
+	RESET_MSIGEN		= 18,
+	RESET_INTC		= 4,
+	RESET_UART0		= 16,
+};
+
+enum clock_id {
+	CLOCK_MCU		= 0,
+	CLOCK_SRAM		= 13,
+	CLOCK_MSIGEN		= 18,
+	CLOCK_PLL		= 24,
+	CLOCK_SGMII		= 25,
+	CLOCK_REFCLK		= 26,
+	CLOCK_INTC		= 4,
+	CLOCK_UART0		= 16,
+};
+
+/*
+ * The TAMAP function has four AXI translation tables each with eight
+ * 4-byte registers.  The Ethernet MAC accesses PCI resources through
+ * addressses based at TC956X_SLV00_SRC_ADDR, and the first translation
+ * table converts those to PCIe address space starting based at 0x0.
+ * We don't use the other three available TAMAC tables.
+ */
+#define ATR_AXI4_SLV0_OFFSET		0x0800
+#define AXI4_TABLE_ENTRY_COUNT		4
+#define AXI4_ENTRY_BASE(id)		((id) * AXI4_TABLE_STRIDE)
+#define AXI4_TABLE_STRIDE               0x20
+
+/* Address translation space parameters used for entry 0 */
+#define SLV00_ATR_SIZE			35	/* 2^36 (64 gigabytes) */
+/* TC956X_SLV00_SRC_ADDR is the source address, defined in the common header */
+#define SLV00_TRSL_ADDR			0x0000000000000000ULL
+
+/* Translation entry registers, fields, and values used */
+#define SRC_ADDR_LO_OFFSET		0x0000
+#define ATR_IMPL			BIT(0)		/* 1 = enabled */
+#define ATR_SIZE_MASK			GENMASK(6, 1)	/* size 2^(ATR + 1) */
+#define SRC_ADDR_HI_OFFSET		0x0004
+#define TRSL_ADDR_LO_OFFSET		0x0008
+#define TRSL_ADDR_HI_OFFSET		0x000c
+#define TRSL_PARAM_OFFSET		0x0010
+#define TRSL_ID_MASK			GENMASK(3, 0)
+#define TRSL_ID_PCIE_TX_RX		0
+#define TRSL_PARAM_MASK			GENMASK(27, 16)
+
+/*
+ * The TC956X implements an "SFR" address space, which provides access
+ * to *all* internal IP block registers, both MAC and non-MAC.  This
+ * space is also accessible via an I2C interface used by the PCI pwrctl
+ * driver (in "pci-pwrctrl-tc9563.c"), though that driver accesses the
+ * range in a very limited way.  For the MAC functions we divide up the
+ * range, providing specific addresses needed by the stmmac driver.
+ */
+#define EMAC_CTL_OFFSET(_mac_id)	((_mac_id) ? 0x1074 : 0x1070)
+#define MSIGEN_OFFSET(_mac_id)		((_mac_id) ? 0xf100 : 0xf000)
+#define DWMAC_OFFSET(_mac_id)		((_mac_id) ? 0x48000 : 0x40000)
+
+/*
+ * struct tc956x_chip - Common information related to the TC956X chip
+ * @dev:		Device structure for function 0
+ * @sfr:		Mapped SFR regions (BAR 4, one per PCI function)
+ * @bridge_config:	Regmap used for bridge configuration (BAR 0)
+ * @reset_clock_regmap:	Regmap used for resets and clocks
+ * @rev_id:		Chip revision ID (for quirks)
+ */
+struct tc956x_chip {
+	struct device *dev;
+	void __iomem *sfr[2];
+	void __iomem *bridge_config;
+	struct regmap *reset_clock_regmap;
+	u8 rev_id;
+};
+
+static const struct regmap_config gpio_regmap_config = {
+	.name		= "tc956x-gpio",
+	.reg_bits	= 32,
+	.reg_stride	= 4,
+	.reg_base	= 0x1200,	/* Register GPIOI0 */
+	.val_bits	= 32,
+	.max_register	= 0x1214,	/* Register GPIOO1 */
+};
+
+static const struct regmap_config reset_clock_regmap_config = {
+	.name		= "tc956x-clk-reset",
+	.reg_bits	= 32,
+	.reg_stride	= 4,
+	.reg_base	= 0x1000,	/* Register NCTLSTS */
+	.val_bits	= 32,
+	.max_register	= 0x1010,	/* Register NRSTCTRL1 */
+};
+
+/* Common clock/reset register update function (also used for MACs) */
+void tc956x_reset_clock_set(const struct tc956x_chip *chip, bool reset,
+			    bool reg0, bool set, u8 bit)
+{
+	u32 mask = BIT(bit);
+	u32 offset;
+
+	if (reset)
+		offset = reg0 ? RSTCTRL0_OFFSET : RSTCTRL1_OFFSET;
+	else
+		offset = reg0 ? CLKCTRL0_OFFSET : CLKCTRL1_OFFSET;
+
+	/* Note: no need to check for errors on read/write for MMIO regmap */
+	(void)regmap_update_bits(chip->reset_clock_regmap, offset, mask,
+				 set ? mask : 0);
+}
+EXPORT_SYMBOL_GPL(tc956x_reset_clock_set);
+
+static inline void chip_reset_assert(const struct tc956x_chip *chip,
+				     enum reset_id id)
+{
+	tc956x_reset_clock_set(chip, true, true, true, (u8)id);
+}
+
+static inline void chip_reset_deassert(const struct tc956x_chip *chip,
+				       enum reset_id id)
+{
+	tc956x_reset_clock_set(chip, true, true, false, (u8)id);
+}
+
+static inline void chip_clock_enable(const struct tc956x_chip *chip,
+				     enum clock_id id)
+{
+	tc956x_reset_clock_set(chip, false, true, true, (u8)id);
+}
+
+static inline void chip_clock_disable(const struct tc956x_chip *chip,
+				      enum clock_id id)
+{
+	tc956x_reset_clock_set(chip, false, true, false, (u8)id);
+}
+
+static void adev_release(struct device *dev)
+{
+	struct auxiliary_device *adev = to_auxiliary_dev(dev);
+
+	of_node_put(adev->dev.of_node);
+	kfree(adev);
+}
+
+static void adev_remove(void *data)
+{
+	struct auxiliary_device *adev = data;
+
+	auxiliary_device_delete(adev);
+	auxiliary_device_uninit(adev);
+}
+
+static int adev_device_add(struct device *dev, const char *name, u32 id,
+			   void *platform_data)
+{
+	struct auxiliary_device *adev;
+	int ret;
+
+	adev = kzalloc_obj(*adev);
+	if (!adev)
+		return -ENOMEM;
+
+	adev->id = id;
+	adev->name = name;
+	adev->dev.parent = dev;
+	adev->dev.platform_data = platform_data;
+	adev->dev.release = adev_release;
+	device_set_of_node_from_dev(&adev->dev, dev);
+
+	ret = auxiliary_device_init(adev);
+	if (ret) {
+		of_node_put(adev->dev.of_node);
+		kfree(adev);
+		return ret;
+	}
+
+	ret = auxiliary_device_add(adev);
+	if (ret) {
+		auxiliary_device_uninit(adev);
+		return ret;
+	}
+
+	return devm_add_action_or_reset(dev, adev_remove, adev);
+}
+
+/* The embedded GPIO controller has an auxiliary device driver */
+static int chip_gpio_adev_add(struct tc956x_chip *chip)
+{
+	struct device *dev = chip->dev;
+	struct regmap *regmap;
+
+	/* If needed, PCIe function 0 implements the GPIO controller. */
+	if (!device_property_present(dev, "gpio-controller"))
+		return 0;
+
+	regmap = devm_regmap_init_mmio(dev, chip->sfr[0], &gpio_regmap_config);
+	if (IS_ERR(regmap))
+		return PTR_ERR(regmap);
+
+	return adev_device_add(dev, GPIO_DEVICE_NAME, 0, regmap);
+}
+
+/* The two embedded XGMAC controllers have an auxiliary device driver */
+static int function_xgmac_adev_add(struct pci_dev *pdev,
+				   struct tc956x_chip *chip,
+				   unsigned int msigen_irq)
+{
+	u8 mac_id = PCI_FUNC(pdev->devfn);
+	struct device *dev = &pdev->dev;
+	struct tc956x_dwmac_data *data;
+	void __iomem *sfr;
+	int ret;
+
+	if (mac_id > 1)
+		return -EINVAL;
+	sfr = chip->sfr[mac_id];
+
+	data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
+	if (!data)
+		return -ENOMEM;
+
+	data->chip = chip;
+	data->msigen = sfr + MSIGEN_OFFSET(mac_id);
+	data->msigen_irq = msigen_irq;
+	data->emac = sfr + DWMAC_OFFSET(mac_id);
+	data->emac_ctl = sfr + EMAC_CTL_OFFSET(mac_id);
+	data->rev_id = chip->rev_id;
+	data->mac_id = mac_id;
+
+	ret = adev_device_add(dev, TC956X_XGMAC_DEV_NAME, mac_id, data);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+static int chip_reset_clock_init(struct tc956x_chip *chip)
+{
+	void __iomem *base = chip->sfr[0];
+	struct device *dev = chip->dev;
+	struct regmap *regmap;
+
+	regmap = devm_regmap_init_mmio(dev, base, &reset_clock_regmap_config);
+	if (IS_ERR(regmap))
+		return PTR_ERR(regmap);
+	chip->reset_clock_regmap = regmap;
+
+	return 0;
+}
+
+static int chip_tamap_init(struct tc956x_chip *chip, struct pci_dev *pdev)
+{
+	void __iomem *base;
+
+	base = pcim_iomap_region(pdev, PCI_BAR_BRIDGE_CONFIG, DRIVER_NAME);
+	if (IS_ERR(base))
+		return PTR_ERR(base);
+
+	chip->bridge_config = base + ATR_AXI4_SLV0_OFFSET;
+
+	return 0;
+}
+
+/**
+ * chip_tamap_config() - Configure the table address map registers
+ * @chip:	The TC956X chip pointer
+ *
+ * Populate the registers used to translate AXI bus accesses to PCI TLPs.
+ * TC956X_SLV00_SRC_ADDR defines the base address of the AXI address range.
+ * AXI addresses are translated to the PCIe address range, whose base address
+ * is defined by SLV00_TRSL_ADDR (which is 0x0).
+ */
+static void chip_tamap_config(struct tc956x_chip *chip)
+{
+	void __iomem *table_base = chip->bridge_config;
+	void __iomem *entry_base;
+	u32 trsl_param_val;
+	u32 atr_size_val;
+	u32 val;
+	u32 i;
+
+	/*
+	 * The lower bits of the source address must be zero, because the
+	 * SRC_ADDR_LO register encodes the address translation space size
+	 * and "implmented" bit there.  The size field defines the size of
+	 * the translation space (2^(ATR_SIZE + 1)).  The minimum size is
+	 * 4096 bytes, so ATR_SIZE value must be 11 or more.
+	 */
+	BUILD_BUG_ON(!!u32_get_bits(lower_32_bits(TC956X_SLV00_SRC_ADDR),
+						  ATR_SIZE_MASK));
+	BUILD_BUG_ON(TC956X_SLV00_SRC_ADDR & ATR_IMPL);
+	BUILD_BUG_ON(SLV00_ATR_SIZE < 11);
+
+	/*
+	 * We only use the first AXI4 slave TAMAC table:
+	 *	EDMA address region:	0x10 0000 0000 - 0x1f ffff ffff
+	 *	is translated to:	0x00 0000 0000 - 0x0f ffff ffff
+	 */
+	entry_base = table_base + AXI4_ENTRY_BASE(0);
+
+	atr_size_val = u32_encode_bits(SLV00_ATR_SIZE, ATR_SIZE_MASK);
+	atr_size_val |= ATR_IMPL;
+	val = lower_32_bits(TC956X_SLV00_SRC_ADDR) | atr_size_val;
+	writel(val, entry_base + SRC_ADDR_LO_OFFSET);
+
+	val = upper_32_bits(TC956X_SLV00_SRC_ADDR);
+	writel(val, entry_base + SRC_ADDR_HI_OFFSET);
+
+	val = lower_32_bits(SLV00_TRSL_ADDR);
+	writel(val, entry_base + TRSL_ADDR_LO_OFFSET);
+
+	val = upper_32_bits(SLV00_TRSL_ADDR);
+	writel(val, entry_base + TRSL_ADDR_HI_OFFSET);
+
+	/* This TRSL_PARAM value is assigned for all four TAMAC tables */
+	trsl_param_val = u32_encode_bits(TRSL_ID_PCIE_TX_RX, TRSL_ID_MASK);
+
+	writel(trsl_param_val, entry_base + TRSL_PARAM_OFFSET);
+
+	/* Set all other unused entries to default values (no translation) */
+	for (i = 1; i < AXI4_TABLE_ENTRY_COUNT; i++) {
+		entry_base = table_base + AXI4_ENTRY_BASE(i);
+
+		writel(0x0, entry_base + SRC_ADDR_LO_OFFSET);
+		writel(0x0, entry_base + SRC_ADDR_HI_OFFSET);
+		writel(0x0, entry_base + TRSL_ADDR_LO_OFFSET);
+		writel(0x0, entry_base + TRSL_ADDR_HI_OFFSET);
+		writel(trsl_param_val, entry_base + TRSL_PARAM_OFFSET);
+	}
+}
+
+static void chip_msigen_enable(struct tc956x_chip *chip)
+{
+	chip_clock_enable(chip, CLOCK_MSIGEN);
+	chip_reset_deassert(chip, RESET_MSIGEN);
+}
+
+static void chip_msigen_disable(struct tc956x_chip *chip)
+{
+	chip_reset_assert(chip, RESET_MSIGEN);
+	chip_clock_disable(chip, CLOCK_MSIGEN);
+}
+
+static void chip_init_state(struct tc956x_chip *chip)
+{
+	/* The only IP block we currently use is MSIGEN */
+	chip_reset_assert(chip, RESET_MCU);
+	chip_reset_assert(chip, RESET_MCU1);
+	chip_reset_assert(chip, RESET_INTC);
+	chip_reset_assert(chip, RESET_UART0);
+	chip_clock_disable(chip, CLOCK_MCU);
+	chip_clock_disable(chip, CLOCK_SRAM);
+	chip_clock_disable(chip, CLOCK_PLL);
+	chip_clock_disable(chip, CLOCK_SGMII);
+	chip_clock_disable(chip, CLOCK_REFCLK);
+	chip_clock_disable(chip, CLOCK_INTC);
+	chip_clock_disable(chip, CLOCK_UART0);
+
+	/* Start with MSIGEN in reset with its clock disabled */
+	chip_msigen_disable(chip);
+}
+
+static void chip_link_del(void *data)
+{
+	struct device_link *link = data;
+
+	device_link_del(link);
+}
+
+/*
+ * Function 0 will allocate the chip structure that is shared by both
+ * functions.  Once it has allocated the structure it assigns it as
+ * the PCI device platform data.  Function 1 can access the shared
+ * chip structure by looking up the function 0 device to use its
+ * platform data..
+ *
+ * Returns a chip structure pointer, or a pointer-coded error.
+ */
+static struct tc956x_chip *chip_get(struct pci_dev *pdev)
+{
+	unsigned int devfn = pdev->devfn;
+	struct device *dev = &pdev->dev;
+	struct tc956x_chip *chip;
+	struct device_link *link;
+	struct pci_dev *peer;
+	int ret;
+
+	/* Function 0 just allocates the chip structure */
+	if (!PCI_FUNC(devfn)) {
+		chip = devm_kzalloc(dev, sizeof(*chip), GFP_KERNEL);
+		if (!chip)
+			return ERR_PTR(-ENOMEM);
+
+		/*
+		 * The function whose device pointer matches the chip's
+		 * device pointer manages common resources (like MSIGEN).
+		 */
+		chip->dev = dev;
+
+		return chip;
+	}
+
+	/* Function 1 has to get the chip structure from function 0 */
+	peer = pci_get_slot(pdev->bus, PCI_DEVFN(PCI_SLOT(devfn), 0));
+	if (!peer)
+		return ERR_PTR(-ENXIO);
+
+	/* If function 0 hasn't set up the chip yet, try again later */
+	chip = dev_get_platdata(&peer->dev);
+	if (!chip)
+		return ERR_PTR(-EPROBE_DEFER);
+
+	/* Mark function 1's device as dependent on function 0 */
+	link = device_link_add(dev, &peer->dev, DL_FLAG_STATELESS);
+	if (!link)
+		return ERR_PTR(-ENODEV);
+
+	ret = devm_add_action_or_reset(&peer->dev, chip_link_del, link);
+	if (ret)
+		return ERR_PTR(ret);
+
+	return chip;
+}
+
+static int chip_init(struct tc956x_chip *chip, struct pci_dev *pdev)
+{
+	u32 id = PCI_FUNC(pdev->devfn) ? 1 : 0;
+	u32 val;
+	int ret;
+
+	/* Both chips need to map their SFR region */
+	chip->sfr[id] = pcim_iomap_region(pdev, PCI_BAR_SFR, DRIVER_NAME);
+	if (IS_ERR(chip->sfr[id]))
+		return PTR_ERR(chip->sfr[id]);
+
+	/* Function 0 handles common initialization */
+	if (id)
+		return 0;
+
+	ret = chip_tamap_init(chip, pdev);
+	if (ret)
+		return ret;
+
+	ret = chip_reset_clock_init(chip);
+	if (ret)
+		return ret;
+
+	chip_init_state(chip);
+
+	ret = chip_gpio_adev_add(chip);
+	if (ret)
+		return ret;
+
+	/* Get the revision ID */
+	val = readl(chip->sfr[0] + NCID_OFFSET);
+	chip->rev_id = u32_get_bits(val, NCID_REV_ID_MASK);
+
+	chip_tamap_config(chip);
+	chip_msigen_enable(chip);
+
+	return 0;
+}
+
+static int
+tc956x_function_probe(struct pci_dev *pdev, const struct pci_device_id *id)
+{
+	struct device *dev = &pdev->dev;
+	struct tc956x_chip *chip;
+	unsigned int msigen_irq;
+	int ret;
+
+	/* Despite being a PCI device, we require devicetree */
+	if (!dev->of_node)
+		return -EINVAL;
+
+	ret = pcim_enable_device(pdev);
+	if (ret)
+		return ret;
+
+	pci_set_master(pdev);
+
+	/* Function 1 gets -EPROBE_DEFER until function 0 sets platform data */
+	chip = chip_get(pdev);
+	if (IS_ERR(chip))
+		return dev_err_probe(dev, PTR_ERR(chip), "failed to get chip\n");
+
+	/* We called pcim_enable_device() so this will be freed automatically */
+	ret = pci_alloc_irq_vectors(pdev, 1, 1, PCI_IRQ_MSI);
+	if (ret < 1)
+		return dev_err_probe(dev, ret ? : -EIO,
+				     "failed to allocate IRQ vectors\n");
+
+	ret = pci_irq_vector(pdev, 0);
+	if (ret < 1)
+		return dev_err_probe(dev, ret ? : -EIO, "failed to get IRQ\n");
+	msigen_irq = ret;
+
+	ret = chip_init(chip, pdev);
+	if (ret)
+		return dev_err_probe(dev, ret, "failed to initialize chip\n");
+
+	/* We're ready; the other function can now probe */
+	dev->platform_data = chip;
+
+	ret = function_xgmac_adev_add(pdev, chip, msigen_irq);
+	if (ret)
+		return dev_err_probe(dev, ret, "failed to add xgmap device\n");
+
+	return 0;
+}
+
+static void tc956x_function_remove(struct pci_dev *pdev)
+{
+	struct tc956x_chip *chip = dev_get_platdata(&pdev->dev);
+
+	pci_clear_master(pdev);
+
+	if (&pdev->dev == chip->dev)
+		chip_msigen_disable(chip);
+}
+
+static const struct pci_device_id tc956x_function_id_table[] = {
+	{ PCI_DEVICE(PCI_VENDOR_ID_TOSHIBA, PCI_DEVICE_ID_TOSHIBA_TC956X), },
+	{ },
+};
+MODULE_DEVICE_TABLE(pci, tc956x_function_id_table);
+
+static int tc956x_chip_suspend_noirq(struct device *dev)
+{
+	struct tc956x_chip *chip = dev_get_platdata(dev);
+	struct pci_dev *pdev = to_pci_dev(dev);
+
+	if (dev == chip->dev)
+		chip_msigen_disable(chip);
+
+	/* It seems most callers ignore the return value here */
+	pci_save_state(pdev);
+	pci_wake_from_d3(pdev, true);
+
+	return 0;
+}
+
+static int tc956x_chip_resume_noirq(struct device *dev)
+{
+	struct tc956x_chip *chip = dev_get_platdata(dev);
+	struct pci_dev *pdev = to_pci_dev(dev);
+
+	pci_wake_from_d3(pdev, false);
+	pci_set_power_state(pdev, PCI_D0);
+	pci_restore_state(pdev);
+
+	if (dev != chip->dev)
+		return 0;
+
+	/* Reconfigure tamap tables following suspend */
+	chip_tamap_config(chip);
+
+	chip_msigen_enable(chip);
+
+	return 0;
+}
+
+static DEFINE_NOIRQ_DEV_PM_OPS(tc956x_chip_pm_ops,
+			       tc956x_chip_suspend_noirq,
+			       tc956x_chip_resume_noirq);
+
+static struct pci_driver tc956x_function_driver = {
+	.name		= DRIVER_NAME,
+	.id_table	= tc956x_function_id_table,
+	.probe		= tc956x_function_probe,
+	.remove		= tc956x_function_remove,
+	.driver		= {
+		.name		= DRIVER_NAME,
+		.owner		= THIS_MODULE,
+		.pm		= pm_sleep_ptr(&tc956x_chip_pm_ops),
+	},
+};
+
+module_pci_driver(tc956x_function_driver);
+
+MODULE_DESCRIPTION("Toshiba TC956X PCIe Embedded Function Driver");
+MODULE_LICENSE("GPL");
-- 
2.51.0


^ permalink raw reply related

* [PATCH net-next 10/12] net: stmmac: tc956x: add TC956x/QPS615 support
From: Alex Elder @ 2026-05-01 15:54 UTC (permalink / raw)
  To: andrew+netdev, davem, edumazet, kuba, pabeni, maxime.chevallier,
	rmk+kernel, andersson, konradybcio, robh, krzk+dt, conor+dt,
	linusw, brgl, arnd, gregkh
  Cc: Daniel Thompson, elder, mohd.anwar, a0987203069, alexandre.torgue,
	ast, boon.khai.ng, chenchuangyu, chenhuacai, daniel, hawk,
	hkallweit1, inochiama, john.fastabend, julianbraha,
	livelycarpet87, matthew.gerlach, mcoquelin.stm32, me,
	prabhakar.mahadev-lad.rj, richardcochran, rohan.g.thomas, sdf,
	siyanteng, weishangjuan, wens, netdev, bpf, linux-arm-msm,
	devicetree, linux-gpio, linux-stm32, linux-arm-kernel,
	linux-kernel
In-Reply-To: <20260501155421.3329862-1-elder@riscstar.com>

From: Daniel Thompson <daniel@riscstar.com>

Toshiba TC956x is an Ethernet AVB/TSN bridge and is essentially a
small and highly-specialized SoC. TC956x includes an "eMAC" subsystem
that can be accessed, along with several other peripherals, via two
PCIe endpoint functions. There is a main driver for the endpoint that
decomposes things and creates auxiliary bus devices to model the SoC.

The eMAC consists of a Designware XGMAC, XPCS and PMA. Each eMAC is
supported by an MSIGEN that bridges TC956x level interrupts to PCIe
MSIs.

Add a driver for the eMAC/MSIGEN combination.

Co-developed-by: Alex Elder <elder@riscstar.com>
Signed-off-by: Alex Elder <elder@riscstar.com>
Signed-off-by: Daniel Thompson <daniel@riscstar.com>
---
 drivers/net/ethernet/stmicro/stmmac/Kconfig   |  13 +
 drivers/net/ethernet/stmicro/stmmac/Makefile  |   2 +
 .../ethernet/stmicro/stmmac/dwmac-tc956x.c    | 791 ++++++++++++++++++
 include/soc/toshiba/tc956x-dwmac.h            |  84 ++
 4 files changed, 890 insertions(+)
 create mode 100644 drivers/net/ethernet/stmicro/stmmac/dwmac-tc956x.c
 create mode 100644 include/soc/toshiba/tc956x-dwmac.h

diff --git a/drivers/net/ethernet/stmicro/stmmac/Kconfig b/drivers/net/ethernet/stmicro/stmmac/Kconfig
index e3dd5adda5aca..66bcfaccbe21f 100644
--- a/drivers/net/ethernet/stmicro/stmmac/Kconfig
+++ b/drivers/net/ethernet/stmicro/stmmac/Kconfig
@@ -404,6 +404,19 @@ config DWMAC_MOTORCOMM
 	  This enables glue driver for Motorcomm DWMAC-based PCI Ethernet
 	  controllers. Currently only YT6801 is supported.
 
+config DWMAC_TC956X
+	tristate "Toshiba TC956X DWMAC support"
+	depends on PCI
+	depends on COMMON_CLK
+	depends on TOSHIBA_TC956X_PCI
+	default m if TOSHIBA_TC956X_PCI
+	help
+	  This selects the Toshiba TC956X (and Qualcomm QPS615) support in the
+	  stmmac driver.
+
+	  This provides support for the ethernet controllers found on these
+	  devices.
+
 config STMMAC_PCI
 	tristate "STMMAC PCI bus support"
 	depends on PCI
diff --git a/drivers/net/ethernet/stmicro/stmmac/Makefile b/drivers/net/ethernet/stmicro/stmmac/Makefile
index a1cea2f57252e..e8e7f95dbe3e8 100644
--- a/drivers/net/ethernet/stmicro/stmmac/Makefile
+++ b/drivers/net/ethernet/stmicro/stmmac/Makefile
@@ -51,4 +51,6 @@ obj-$(CONFIG_STMMAC_PCI)	+= stmmac-pci.o
 obj-$(CONFIG_DWMAC_INTEL)	+= dwmac-intel.o
 obj-$(CONFIG_DWMAC_LOONGSON)	+= dwmac-loongson.o
 obj-$(CONFIG_DWMAC_MOTORCOMM)	+= dwmac-motorcomm.o
+obj-$(CONFIG_TC956X_PCI)	+= tc956x-pci.o
+obj-$(CONFIG_DWMAC_TC956X)	+= dwmac-tc956x.o
 stmmac-pci-objs:= stmmac_pci.o
diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac-tc956x.c b/drivers/net/ethernet/stmicro/stmmac/dwmac-tc956x.c
new file mode 100644
index 0000000000000..27ff83dd3bd9e
--- /dev/null
+++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-tc956x.c
@@ -0,0 +1,791 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/*
+ * Copyright (C) 2026 by RISCstar Solutions Corporation.  All rights reserved.
+ *
+ * Derived from code having the following copyrights:
+ * Copyright (C) 2011-2012  Vayavya Labs Pvt Ltd
+ * Copyright (C) 2025 Toshiba Electronic Devices & Storage Corporation
+ */
+
+#include <linux/auxiliary_bus.h>
+#include <linux/bitops.h>
+#include <linux/iopoll.h>
+#include <linux/irqdomain.h>
+#include <linux/irqchip/chained_irq.h>
+#include <linux/pcs/pcs-xpcs-regmap.h>
+#include <linux/pcs/pcs-xpcs.h>
+#include <linux/phy.h>
+#include <linux/regmap.h>
+#include <linux/stmmac.h>
+#include <linux/types.h>
+#include <linux/units.h>
+
+#include <soc/toshiba/tc956x-dwmac.h>
+
+#include "common.h"
+#include "dwxgmac2.h"
+#include "stmmac.h"
+
+#define DRIVER_NAME			"dwmac-tc956x"
+
+#define TC956X_PTP_CLOCK_RATE		(250 * HZ_PER_MHZ)
+
+#define TC956X_RX_FIFO_KB		46	/* Shared by all RX queues */
+#define TC956X_TX_FIFO_KB		46	/* Shared by all TX queues */
+
+/* Fields and values for the EMACTL registers */
+#define EMAC_SP_SEL_MASK		GENMASK(3, 0)
+#define SP_SEL_SGMII_2500M		4
+#define SP_SEL_SGMII_1000M		5
+#define SP_SEL_SGMII_100M		6
+#define SP_SEL_SGMII_10M		7
+#define EMAC_PHY_INF_SEL_MASK		GENMASK(5, 4)
+#define PCS_CLK_PHY			1	/* Clock from PHY */
+#define EMAC_INV_SGM_SIG_DET		BIT(6)	/* 1 = polarity inverted */
+#define EMAC_LPIHWCLKEN			BIT(8)	/* 1 = low power mode */
+#define EMAC_INIT_DONE			BIT(21)
+
+/* MSIGEN Registers */
+#define MSI_OUT_EN_OFFSET		0x0000
+#define MSI_MASK_CLR_OFFSET		0x000c
+#define MSI_MASK_VALUE			BIT(0)
+#define MSI_INT_STS_OFFSET		0x0010
+
+enum msigen_hwirq {
+	HWIRQ_LPI		= 0,
+	HWIRQ_PMT		= 1,
+	HWIRQ_EVENT		= 2,
+	HWIRQ_TX0		= 3,
+	HWIRQ_RX0		= 11,
+	HWIRQ_XPCS		= 19,
+	HWIRQ_PHY		= 20,
+	HWIRQ_PFMAILBOX		= 21,
+	HWIRQ_MSIREQ_PLS	= 24
+};
+
+#define HWIRQ_COUNT			25
+
+/* Offset to the XPCS memory block, relative to the EMAC address range */
+#define DWMAC_XPCS_OFFSET		0x3a00
+
+/* Offset to the PMATOP memory block, relative to the EMAC address range */
+#define DWMAC_PMATOP_OFFSET			0x4000
+
+#define PMA_CML_GL_PM_CFG0			0x01b8
+
+/*
+ * Five sets three registers must be configured for PMA.  The HWT_REFCLK
+ * registers are each separated by 0x14 bytes.  The Common0 configuration
+ * registers are separated by 0x8 bytes.
+ */
+#define PMA_REG_COUNT				5
+
+#define PMA_HWT_REFCK_R_EN			0x1080
+#define PMA_HWT_REFCK_TERM_EN			0x1090
+#define PMA_HWT_REFCK_STRIDE			0x0014
+
+#define PMA_COMM_CFG_0_1			0x1888
+#define PMA_COMM_CFG_0_1_STRIDE			0x0008
+
+/* PMA_COMM_CFG_0_1 fields (WRITE_MASK is a field name) */
+#define COMM_CFG_WRITE_MASK_MASK		GENMASK(16, 9)
+#define WRITE_MASK_VALUE			0xf7	/* Power-on value */
+#define COMM_CFG_ENABLE				BIT(8)
+#define COMM_CFG_WRITE_DATA_MASK		GENMASK(7, 0)
+#define WRITE_DATA_VALUE			0x04	/* Power-on value */
+
+/**
+ * struct tc956x_data - Toshiba-specific platform data
+ * @dev:		Device pointer
+ * @irq_domain:		MSIGEN IRQ domain
+ * @auxbus_data:	Pointer to data passed from the parent device
+ * @plat:		Pointer to our stmmac platform data
+ * @dma_cfg:		DMA config buffer used by plat_stmmacenet_data
+ * @mdio_bus_data:	MDIO bus data used by plat_stmmacenet_data
+ * @axi:		AXI data used by plat_stmmacenet_data
+ * @desc:		DMA descriptor data used by mac_device_info
+ * @dma:		DMA operations data used by mac_device_info
+ */
+struct tc956x_data {
+	struct device *dev;
+	struct irq_domain *irq_domain;
+	struct tc956x_dwmac_data *auxbus_data;
+	struct plat_stmmacenet_data *plat;
+
+	/* These three fields are used by the plat_stmmacenet_data structure */
+	struct stmmac_dma_cfg dma_cfg;
+	struct stmmac_mdio_bus_data mdio_bus_data;
+	struct stmmac_axi axi;
+
+	/* These two fields are used by the mac_device_info structure */
+	struct stmmac_desc_ops desc;
+	struct stmmac_dma_ops dma;
+};
+
+struct tc956x_mac_speed {
+	phy_interface_t phy_interface;
+	int speed;
+	u32 sp_sel;
+};
+
+static struct tc956x_mac_speed mac_speed[] = {
+	{ PHY_INTERFACE_MODE_2500BASEX,	SPEED_2500,  SP_SEL_SGMII_2500M, },
+	{ PHY_INTERFACE_MODE_SGMII,	SPEED_2500,  SP_SEL_SGMII_2500M, },
+	{ PHY_INTERFACE_MODE_SGMII,	SPEED_1000,  SP_SEL_SGMII_1000M, },
+	{ PHY_INTERFACE_MODE_SGMII,	SPEED_100,   SP_SEL_SGMII_100M, },
+	{ PHY_INTERFACE_MODE_SGMII,	SPEED_10,    SP_SEL_SGMII_10M, },
+};
+
+/* TC956x uses indirect addressing so this need only describe a 1KiB range */
+static const struct regmap_config xpcs_regmap_config = {
+	.reg_bits	= 32,
+	.val_bits	= 32,
+	.reg_base	= 0x00,		/* Minimum XPCS reg offset */
+	.max_register	= 0xff,		/* Register DW_VR_CSR_VIEWPORT */
+	.reg_shift	= REGMAP_UPSHIFT(2),
+};
+
+static void tc956x_msigen_irq_handler(struct irq_desc *desc)
+{
+	struct irq_domain *irq_domain = irq_desc_get_handler_data(desc);
+	struct irq_chip *chip = irq_desc_get_chip(desc);
+	struct irq_chip_generic *gc;
+	unsigned long status;
+	unsigned int hwirq;
+
+	gc = irq_get_domain_generic_chip(irq_domain, 0);
+
+	chained_irq_enter(chip, desc);
+
+	status = irq_reg_readl(gc, MSI_INT_STS_OFFSET);
+	for_each_set_bit(hwirq, &status, HWIRQ_COUNT)
+		generic_handle_domain_irq(irq_domain, hwirq);
+
+	/*
+	 * Clear the MSI flag. Most interrupts within TC956X are level-high
+	 * type. If any interrupts are still asserted then clearing this flag
+	 * will cause the (edge-triggered) MSI to be regenerated.
+	 */
+	irq_reg_writel(gc, MSI_MASK_VALUE, MSI_MASK_CLR_OFFSET);
+
+	chained_irq_exit(chip, desc);
+}
+
+static int tc956x_msigen_irq_chip_init(struct irq_chip_generic *gc)
+{
+	struct tc956x_data *td = gc->domain->host_data;
+
+	gc->reg_base = td->auxbus_data->msigen;
+	gc->chip_types[0].regs.mask = MSI_OUT_EN_OFFSET;
+	gc->chip_types[0].chip.irq_mask = irq_gc_mask_clr_bit;
+	gc->chip_types[0].chip.irq_unmask = irq_gc_mask_set_bit;
+
+	/* Disable all interrupts */
+	irq_reg_writel(gc, 0, MSI_OUT_EN_OFFSET);
+
+	return 0;
+}
+
+static void tc956x_msigen_irq_chip_exit(struct irq_chip_generic *gc)
+{
+	irq_reg_writel(gc, 0, MSI_OUT_EN_OFFSET);
+}
+
+static int tc956x_msigen_irq_domain_init(struct irq_domain *irq_domain)
+{
+	struct tc956x_data *td = irq_domain->host_data;
+
+	irq_set_chained_handler_and_data(td->auxbus_data->msigen_irq,
+					 tc956x_msigen_irq_handler,
+					 irq_domain);
+
+	return 0;
+}
+
+static void tc956x_msigen_irq_domain_exit(struct irq_domain *irq_domain)
+{
+	struct tc956x_data *td = irq_domain->host_data;
+
+	irq_set_chained_handler_and_data(td->auxbus_data->msigen_irq,
+					 NULL, NULL);
+}
+
+/* We have one IRQ chip instance with 25 IRQs in its domain */
+static struct irq_domain *
+tc956x_msigen_irq_domain_instantiate(struct tc956x_data *td)
+{
+	struct irq_domain_chip_generic_info dgc_info;
+	struct irq_domain_info info;
+
+	dgc_info.name = "tc956x-msigen";
+	dgc_info.handler = handle_level_irq;
+	dgc_info.irqs_per_chip = HWIRQ_COUNT;
+	dgc_info.num_ct = 1;
+	dgc_info.init = tc956x_msigen_irq_chip_init;
+	dgc_info.exit = tc956x_msigen_irq_chip_exit;
+
+	info.domain_flags = IRQ_DOMAIN_FLAG_DESTROY_GC;
+	info.size = HWIRQ_COUNT;
+	info.hwirq_max = HWIRQ_COUNT;
+	info.ops = &irq_generic_chip_ops;
+	info.host_data = td;
+	info.dgc_info = &dgc_info;
+	info.init = tc956x_msigen_irq_domain_init;
+	info.exit = tc956x_msigen_irq_domain_exit;
+
+	return devm_irq_domain_instantiate(td->dev, &info);
+}
+
+/**
+ * tc956x_pma_init() - Initialize PMA
+ * @td:	bsp_priv pointer
+ *
+ * Initialize (or re-initialize) the PMA, configure the clocks and wait for the
+ * eMAC to be ready.
+ */
+static void tc956x_pma_init(struct tc956x_data *td)
+{
+	const struct tc956x_chip *chip = td->auxbus_data->chip;
+	void __iomem *emac_ctl = td->auxbus_data->emac_ctl;
+	u32 id = td->auxbus_data->mac_id;
+	void __iomem *pmatop;
+	u32 val;
+	u32 i;
+
+	/*
+	 * When we re-initialize the PMA then the reset will already have
+	 * been deasserted. We must make sure the PMA reset is asserted before
+	 * we change the clock settings.
+	 */
+	tc956x_reset_assert(chip, id, MAC_RESET_PMA);
+
+	pmatop = td->auxbus_data->emac + DWMAC_PMATOP_OFFSET;
+
+	/* Power on CML buffer (0 = normal mode, 1 = power down) */
+	writel(0, pmatop + PMA_CML_GL_PM_CFG0);
+
+	/* This value switches clock from C0_REFCK to CLK_REF_I */
+	val = u32_encode_bits(WRITE_MASK_VALUE, COMM_CFG_WRITE_MASK_MASK);
+	val |= COMM_CFG_ENABLE;
+	val |= u32_encode_bits(WRITE_DATA_VALUE, COMM_CFG_WRITE_DATA_MASK);
+
+	for (i = 0; i < PMA_REG_COUNT; i++) {
+		u32 offset =  i * PMA_HWT_REFCK_STRIDE;
+
+		/* Disable C0_REFCK and 100 ohm termination */
+		writel(0, pmatop + PMA_HWT_REFCK_R_EN + offset);
+		writel(0, pmatop + PMA_HWT_REFCK_TERM_EN + offset);
+
+		/* Switch clock from C0_REFCK to CLK_REF_I */
+		offset =  i * PMA_COMM_CFG_0_1_STRIDE;
+		writel(val, pmatop + PMA_COMM_CFG_0_1 + offset);
+	}
+
+	tc956x_reset_deassert(chip, id, MAC_RESET_PMA);
+
+	WARN_ON(readl_poll_timeout(emac_ctl, val, val & EMAC_INIT_DONE, 50, 1000000));
+}
+
+static int tc956x_mac_speed_select(struct tc956x_data *td, int speed)
+{
+	phy_interface_t phy_interface = td->plat->phy_interface;
+	struct net_device *netdev;
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(mac_speed); i++) {
+		if (mac_speed[i].speed != speed)
+			continue;
+
+		if (mac_speed[i].phy_interface == phy_interface)
+			return mac_speed[i].sp_sel;
+	}
+	netdev = dev_get_drvdata(td->dev);
+	netdev_err(netdev, "%s/%d unsupported\n",
+		   phy_modes(phy_interface), speed);
+
+	return -EOPNOTSUPP;
+}
+
+static int tc956x_mac_configure(struct tc956x_data *td, int speed)
+{
+	void __iomem *emac_ctl = td->auxbus_data->emac_ctl;
+	int sp_sel;
+	u32 val;
+
+	sp_sel = tc956x_mac_speed_select(td, speed);
+	if (sp_sel < 0)
+		return sp_sel;
+
+	val = readl(emac_ctl);
+	val |= EMAC_LPIHWCLKEN;
+	val &= ~EMAC_INV_SGM_SIG_DET;
+	val = u32_replace_bits(val, PCS_CLK_PHY, EMAC_PHY_INF_SEL_MASK);
+	val = u32_replace_bits(val, sp_sel, EMAC_SP_SEL_MASK);
+	writel(val, emac_ctl);
+
+	return 0;
+}
+
+static int tc956x_mac_enable(struct tc956x_data *td)
+{
+	const struct tc956x_chip *chip = td->auxbus_data->chip;
+	struct plat_stmmacenet_data *plat = td->plat;
+	u32 id = td->auxbus_data->mac_id;
+	int ret;
+
+	tc956x_clock_enable(chip, id, MAC_CLOCK_TX);
+	tc956x_clock_enable(chip, id, MAC_CLOCK_RX);
+	tc956x_clock_enable(chip, id, MAC_CLOCK_ALL);
+	if (id)
+		tc956x_clock_enable(chip, id, MAC_CLOCK_RMII);
+
+	/* Set the speed related registers */
+	ret = tc956x_mac_configure(td, plat->max_speed);
+	if (ret)
+		return ret;
+
+	tc956x_reset_deassert(chip, id, MAC_RESET_MAC);
+	tc956x_pma_init(td);
+	tc956x_reset_deassert(chip, id, MAC_RESET_XPCS);
+
+	return 0;
+}
+
+static void tc956x_mac_disable(struct tc956x_data *td)
+{
+	const struct tc956x_chip *chip = td->auxbus_data->chip;
+	u32 id = td->auxbus_data->mac_id;
+
+	tc956x_reset_assert(chip, id, MAC_RESET_MAC);
+	tc956x_reset_assert(chip, id, MAC_RESET_PMA);
+	tc956x_reset_assert(chip, id, MAC_RESET_XPCS);
+
+	tc956x_clock_disable(chip, id, MAC_CLOCK_ALL);
+	tc956x_clock_disable(chip, id, MAC_CLOCK_RX);
+	tc956x_clock_disable(chip, id, MAC_CLOCK_TX);
+	if (id)
+		tc956x_clock_disable(chip, id, MAC_CLOCK_RMII);
+}
+
+static void tc956x_mac_init_state(struct tc956x_data *td)
+{
+	const struct tc956x_chip *chip = td->auxbus_data->chip;
+	u32 id = td->auxbus_data->mac_id;
+
+	tc956x_clock_disable(chip, id, MAC_CLOCK_125M);
+	tc956x_clock_disable(chip, id, MAC_CLOCK_312_5M);
+
+	tc956x_mac_disable(td);
+}
+
+/*
+ * Override method for dwxgmac301_dma_ops->init_rx_chan
+ *
+ * This differs from the dwxgmac301_dma_ops->init_rx_chan by translating the DMA
+ * address for TC956x internal bus. The window that provides DMA access to PCI
+ * is linearly mapped at 0x10_0000_0000.
+ */
+static void tc956x_dma_init_rx_chan(struct stmmac_priv *priv,
+				    void __iomem *ioaddr,
+				    struct stmmac_dma_cfg *dma_cfg,
+				    dma_addr_t phy, u32 chan)
+{
+	dma_addr_t translated = phy + TC956X_SLV00_SRC_ADDR;
+
+	dwxgmac2_dma_init_rx_chan(priv, ioaddr, dma_cfg, phy, chan);
+
+	writel(upper_32_bits(translated),
+	       ioaddr + XGMAC_DMA_CH_RxDESC_HADDR(chan));
+	writel(lower_32_bits(translated),
+	       ioaddr + XGMAC_DMA_CH_RxDESC_LADDR(chan));
+}
+
+/* Override method for dwxgmac301_dma_ops->init_tx_chan */
+static void tc956x_dma_init_tx_chan(struct stmmac_priv *priv,
+				    void __iomem *ioaddr,
+				    struct stmmac_dma_cfg *dma_cfg,
+				    dma_addr_t phy, u32 chan)
+{
+	dma_addr_t translated = phy + TC956X_SLV00_SRC_ADDR;
+
+	dwxgmac2_dma_init_tx_chan(priv, ioaddr, dma_cfg, phy, chan);
+
+	writel(upper_32_bits(translated),
+	       ioaddr + XGMAC_DMA_CH_TxDESC_HADDR(chan));
+	writel(lower_32_bits(translated),
+	       ioaddr + XGMAC_DMA_CH_TxDESC_LADDR(chan));
+}
+
+/* Override method for dwxgmac210_desc_ops->set_addr */
+static void tc956x_desc_set_addr(struct dma_desc *p, dma_addr_t addr)
+{
+	dma_addr_t translated = addr + TC956X_SLV00_SRC_ADDR;
+
+	p->des0 = cpu_to_le32(lower_32_bits(translated));
+	p->des1 = cpu_to_le32(upper_32_bits(translated));
+}
+
+/* Override method for dwxgmac210_desc_ops->set_sec_addr */
+static void tc956x_desc_set_sec_addr(struct dma_desc *p, dma_addr_t addr,
+				     bool is_valid)
+{
+	dma_addr_t translated = addr + TC956X_SLV00_SRC_ADDR;
+
+	p->des2 = cpu_to_le32(lower_32_bits(translated));
+	p->des3 = cpu_to_le32(upper_32_bits(translated));
+}
+
+/*
+ * Use mac_setup to apply the override methods above.
+ *
+ * The memory for the modified ops structures is pre-allocated as part of
+ * struct tc956x_data.
+ */
+static int tc956x_mac_setup(void *apriv, struct mac_device_info *mac)
+{
+	struct stmmac_priv *priv = apriv;
+	struct stmmac_desc_ops *desc;
+	struct stmmac_dma_ops *dma;
+	struct tc956x_data *td;
+
+	td = priv->plat->bsp_priv;
+
+	/* dwxgmac301_dma_ops needs extending to provide DMA address translation */
+	dma = &td->dma;
+	*dma = dwxgmac301_dma_ops;
+	dma->init_rx_chan = tc956x_dma_init_rx_chan;
+	dma->init_tx_chan = tc956x_dma_init_tx_chan;
+	mac->dma = dma;
+
+	/* dwxgmac210_desc_ops also needs extending for the same reason */
+	desc = &td->desc;
+	*desc = dwxgmac210_desc_ops;
+	desc->set_addr = tc956x_desc_set_addr;
+	desc->set_sec_addr = tc956x_desc_set_sec_addr;
+	mac->desc = desc;
+
+	priv->hw = mac;
+
+	return dwxgmac2_setup(priv);
+}
+
+static int tc956x_pcs_init(struct stmmac_priv *priv)
+{
+	struct xpcs_regmap_config xpcs_regmap_cfg;
+	void __iomem *emac = priv->ioaddr;
+	struct regmap *xpcs_regmap;
+	void __iomem *xpcs_addr;
+	struct dw_xpcs *xpcs;
+
+	xpcs_addr = emac + DWMAC_XPCS_OFFSET;
+	xpcs_regmap = devm_regmap_init_mmio(priv->device, xpcs_addr,
+					    &xpcs_regmap_config);
+	if (IS_ERR(xpcs_regmap))
+		return PTR_ERR(xpcs_regmap);
+
+	xpcs_regmap_cfg.regmap = xpcs_regmap;
+	xpcs_regmap_cfg.reg_indir = true;
+
+	xpcs = devm_xpcs_regmap_register(priv->device, &xpcs_regmap_cfg);
+	if (IS_ERR(xpcs))
+		return PTR_ERR(xpcs);
+
+	xpcs_config_eee_mult_fact(xpcs, priv->plat->mult_fact_100ns);
+	priv->hw->phylink_pcs = xpcs_to_phylink_pcs(xpcs);
+
+	return 0;
+}
+
+static struct phylink_pcs *tc956x_select_pcs(struct stmmac_priv *priv,
+					     phy_interface_t interface)
+{
+	return priv->hw->phylink_pcs;
+}
+
+static void tc956x_fix_mac_speed(void *bsp_priv, phy_interface_t interface,
+				 int speed, unsigned int mode)
+{
+	struct tc956x_data *td = bsp_priv;
+
+	tc956x_mac_configure(td, speed);
+	tc956x_pma_init(td);
+}
+
+static int tc956x_dwmac_suspend(struct device *dev, void *bsp_priv)
+{
+	struct tc956x_data *td = bsp_priv;
+
+	tc956x_mac_disable(td);
+
+	return 0;
+}
+
+static int tc956x_dwmac_resume(struct device *dev, void *bsp_priv)
+{
+	struct tc956x_data *td = bsp_priv;
+
+	return tc956x_mac_enable(td);
+}
+
+/* Called by tc956x_dwmac_probe(); return errors with dev_err_probe() */
+static int tc956x_dwmac_parse_dt(struct tc956x_data *td)
+{
+	struct device_node *mdio_node;
+	struct device *dev = td->dev;
+	struct device_node *np;
+
+	np = dev_of_node(dev);
+	if (!np)
+		return dev_err_probe(dev, -EINVAL, "no devicetree node\n");
+
+	/* Find the MDIO bus */
+	for_each_child_of_node(np, mdio_node) {
+		if (of_device_is_compatible(mdio_node,
+					    "snps,dwmac-mdio"))
+			break;
+	}
+
+	/* Pass the MDIO bus (if there is one) to the core driver */
+	if (mdio_node) {
+		td->plat->mdio_node = mdio_node;
+		td->plat->mdio_bus_data->needs_reset = true;
+	}
+
+	return 0;
+}
+
+static int tc956x_lookup_max_speed(phy_interface_t phy_interface)
+{
+	switch (phy_interface) {
+	case PHY_INTERFACE_MODE_SGMII:
+	case PHY_INTERFACE_MODE_2500BASEX:
+		return SPEED_2500;
+
+	default:
+		return -EOPNOTSUPP;
+	}
+}
+
+/* Called by tc956x_dwmac_probe(); return errors with dev_err_probe() */
+static int tc956x_plat_dat_init(struct tc956x_data *td)
+{
+	struct plat_stmmacenet_data *plat;
+	phy_interface_t phy_interface;
+	struct device *dev = td->dev;
+	struct stmmac_axi *axi;
+	u32 speed;
+	int ret;
+	u32 i;
+
+	phy_interface = device_get_phy_mode(dev);
+	if (phy_interface < 0)
+		return -ENODEV;
+
+	/* The platform structure is allocated with devm_kzalloc() */
+	plat = stmmac_plat_dat_alloc(dev);
+	if (!plat)
+		return -ENOMEM;
+
+	ret = tc956x_lookup_max_speed(phy_interface);
+	if (ret < 0)
+		return dev_err_probe(dev, ret, "unsupported phy speed\n");
+	speed = ret;
+
+	plat->core_type = DWMAC_CORE_XGMAC;
+	plat->bus_id = td->auxbus_data->mac_id;
+	plat->phy_interface = phy_interface;
+	plat->mdio_bus_data = &td->mdio_bus_data;
+	/* Parent PCI device is used for DMA */
+	plat->dma_device = dev->parent;
+	plat->dma_cfg = &td->dma_cfg;
+	plat->dma_cfg->pbl = 32;
+	plat->dma_cfg->pblx8 = true;
+
+	/*
+	 * Our MAC clock rate is fixed at 125 MHz.  For XGMAC, clk_csr 0
+	 * represents "divide by 62" and gets the best rate under 2.5 MHz.
+	 */
+	plat->clk_csr = 0;	/* MDC clock = clk_csr_i / 62 */
+	plat->force_sf_dma_mode = 1;
+	plat->max_speed = speed;
+	plat->unicast_filter_entries = 32;
+
+	/*
+	 * TC956x has 8 RX queues but we observe significantly reduced RX
+	 * bandwidth if we don't have at least 8k FIFO space per queue, so
+	 * by default we avoid using all the queues.
+	 */
+	plat->rx_queues_to_use = 4;
+
+	/*
+	 * TX956x has 8 TX queues but only #0 to #3 work for general IP traffic.
+	 * For now we will limit the driver to only these queues.
+	 */
+	plat->tx_queues_to_use = 4;
+
+	/*
+	 * Oversized FIFOs result in reduced performance in bandwidth tests.
+	 * Limit them to 8KiB per queue, or the total available.
+	 */
+	plat->tx_fifo_size =
+		min(TC956X_TX_FIFO_KB, 8 * plat->tx_queues_to_use) * SZ_1K;
+	plat->rx_fifo_size =
+		min(TC956X_RX_FIFO_KB, 8 * plat->rx_queues_to_use) * SZ_1K;
+	plat->host_dma_width = 36;
+
+	plat->rx_sched_algorithm = MTL_RX_ALGORITHM_SP;
+	plat->tx_sched_algorithm = MTL_TX_ALGORITHM_WRR;
+
+	/* Default RX chan is set to queue index (0..rx_queues_to_use-1) */
+	for (i = 0; i < plat->rx_queues_to_use; i++)
+		plat->rx_queues_cfg[i].mode_to_use = MTL_QUEUE_DCB;
+
+	for (i = 0; i < plat->tx_queues_to_use; i++) {
+		plat->tx_queues_cfg[i].weight = 12;
+		plat->tx_queues_cfg[i].mode_to_use = MTL_QUEUE_DCB;
+
+		/* Only queues 5-8 support time-based scheduling on TC956X */
+		if (i >= 5)
+			plat->tx_queues_cfg[i].tbs_en = 1;
+	}
+
+	plat->fix_mac_speed = tc956x_fix_mac_speed;
+	plat->suspend = tc956x_dwmac_suspend;
+	plat->resume = tc956x_dwmac_resume;
+	plat->mac_setup = tc956x_mac_setup;
+	plat->pcs_init = tc956x_pcs_init;
+	plat->select_pcs = tc956x_select_pcs;
+
+	plat->bsp_priv = td;
+	plat->clk_ptp_rate = TC956X_PTP_CLOCK_RATE;
+
+	/* AXI Configuration */
+	axi = &td->axi;
+	axi->axi_lpi_en = 1;
+	axi->axi_wr_osr_lmt = 31;
+	axi->axi_rd_osr_lmt = 31;
+	/* All sizes (2^2..2^8) are supported */
+	axi->axi_blen_regval = DMA_AXI_BLEN_MASK;
+	plat->axi = axi;
+
+	plat->mac_port_sel_speed = speed;
+	plat->flags = STMMAC_FLAG_MULTI_MSI_EN | STMMAC_FLAG_TSO_EN;
+
+	td->plat = plat;
+
+	return 0;
+}
+
+/*
+ * The domain was created with IRQ_DOMAIN_FLAG_DESTROY_GC, so any mapped IRQs
+ * will be disposed when the domain is removed (when the device is destroyed).
+ */
+static int tc956x_stmmac_resources_init(struct tc956x_data *td,
+					struct stmmac_resources *res)
+{
+	struct irq_domain *irq_domain = td->irq_domain;
+	u32 i;
+
+	res->irq = irq_create_mapping(irq_domain, HWIRQ_EVENT);
+	if (!res->irq)
+		return -EINVAL;
+
+	for (i = 0; i < td->plat->tx_queues_to_use; i++) {
+		res->tx_irq[i] = irq_create_mapping(irq_domain, HWIRQ_TX0 + i);
+		if (!res->tx_irq[i])
+			return -EINVAL;
+	}
+
+	for (i = 0; i < td->plat->rx_queues_to_use; i++) {
+		res->rx_irq[i] = irq_create_mapping(irq_domain, HWIRQ_RX0 + i);
+		if (!res->tx_irq[i])
+			return -EINVAL;
+	}
+
+	res->addr = td->auxbus_data->emac;
+
+	return 0;
+}
+
+static int tc956x_dwmac_probe(struct auxiliary_device *adev,
+			      const struct auxiliary_device_id *id)
+{
+	struct stmmac_resources res = { };
+	struct device *dev = &adev->dev;
+	struct tc956x_data *td;
+	int ret;
+
+	td = devm_kzalloc(dev, sizeof(*td), GFP_KERNEL);
+	if (!td)
+		return -ENOMEM;
+
+	td->dev = dev;
+	td->auxbus_data = dev_get_platdata(dev);
+	if (!td->auxbus_data)
+		return dev_err_probe(dev, -EINVAL, "no platform data\n");
+
+	ret = tc956x_plat_dat_init(td);
+	if (ret)
+		return ret;
+
+	ret = tc956x_dwmac_parse_dt(td);
+	if (ret)
+		return ret;
+
+	td->irq_domain = tc956x_msigen_irq_domain_instantiate(td);
+	if (IS_ERR(td->irq_domain))
+		return dev_err_probe(dev, PTR_ERR(td->irq_domain),
+				     "failed to instantiate IRQ domain\n");
+
+	/* Put the MAC in a known initial state */
+	tc956x_mac_init_state(td);
+
+	ret = tc956x_stmmac_resources_init(td, &res);
+	if (ret)
+		return dev_err_probe(dev, ret,
+				     "failed to initialize stmmac resources\n");
+
+	ret = tc956x_mac_enable(td);
+	if (ret)
+		return dev_err_probe(dev, ret, "failed to enable MAC\n");
+
+	ret = stmmac_dvr_probe(dev, td->plat, &res);
+	if (ret)
+		return dev_err_probe(dev, ret, "failed stmmac probe\n");
+
+	return 0;
+}
+
+static void tc956x_dwmac_remove(struct auxiliary_device *adev)
+{
+	struct device *dev = &adev->dev;
+	struct net_device *ndev = dev_get_drvdata(dev);
+	struct stmmac_priv *priv = netdev_priv(ndev);
+	struct tc956x_data *td = priv->plat->bsp_priv;
+
+	stmmac_dvr_remove(dev);
+	tc956x_mac_disable(td);
+}
+
+static const struct auxiliary_device_id tc956x_dwmac_ids[] = {
+	{ .name = TC956X_PCIE_DRIVER_NAME "." TC956X_XGMAC_DEV_NAME, },
+	{ },
+};
+MODULE_DEVICE_TABLE(auxiliary, tc956x_dwmac_ids);
+
+static struct auxiliary_driver tc956x_dwmac_driver = {
+	.name		= DRIVER_NAME,
+	.probe		= tc956x_dwmac_probe,
+	.remove		= tc956x_dwmac_remove,
+	.id_table	= tc956x_dwmac_ids,
+	.driver = {
+		.name	= DRIVER_NAME,
+		.pm	= &stmmac_simple_pm_ops,
+		.owner	= THIS_MODULE,
+	},
+};
+module_auxiliary_driver(tc956x_dwmac_driver);
+
+MODULE_DESCRIPTION("Toshiba TC956x PCIe Ethernet Network Driver");
+MODULE_LICENSE("GPL");
diff --git a/include/soc/toshiba/tc956x-dwmac.h b/include/soc/toshiba/tc956x-dwmac.h
new file mode 100644
index 0000000000000..5ca39cf764be9
--- /dev/null
+++ b/include/soc/toshiba/tc956x-dwmac.h
@@ -0,0 +1,84 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+/*
+ * Copyright (C) 2026 by RISCstar Solutions Corporation.  All rights reserved.
+ */
+
+#ifndef __TOSHIBA_TC956X_DWMAC_H__
+#define __TOSHIBA_TC956X_DWMAC_H__
+
+#include <linux/compiler_types.h>
+#include <linux/types.h>
+
+#define TC956X_PCIE_DRIVER_NAME	"tc956x_pci"
+
+#define TC956X_XGMAC_DEV_NAME	"dwmac-tc956x"
+
+/* Starting address of the space translated by the PCIe endpoint bridge */
+#define TC956X_SLV00_SRC_ADDR	0x0000001000000000ULL
+
+enum tc956x_reset_id {
+	MAC_RESET_MAC		= 7,
+	MAC_RESET_PMA		= 30,
+	MAC_RESET_XPCS		= 31,
+};
+
+enum tc956x_clock_id {
+	MAC_CLOCK_TX		= 7,
+	MAC_CLOCK_RX		= 14,
+	MAC_CLOCK_ALL		= 31,
+	MAC_CLOCK_125M		= 29,
+	MAC_CLOCK_312_5M	= 30,
+	MAC_CLOCK_RMII		= 15,	/* eMAC 1 only */
+};
+
+/**
+ * struct tc956x_dwmac_data - Structure passed to stmmac auxiliary devices.
+ * @chip:		Context pointer needed for reset and clock operations
+ * @emac:		I/O mapped address used by eMAC
+ * @emac_ctl:		I/O mapped address used for eMAC control
+ * @msigen:		I/O mapped address used by MSIGEN
+ * @msigen_irq:		IRQ number used by MSIGEN
+ * @rev_id:		Chip revision ID (for quirks)
+ * @mac_id:		Unique device ID (0 or 1)
+ *
+ * This structure is passed via platform data to the stmmac auxiliary devices.
+ */
+struct tc956x_dwmac_data {
+	const struct tc956x_chip *chip;
+	void __iomem *emac;
+	void __iomem *emac_ctl;
+	void __iomem *msigen;
+	unsigned int msigen_irq;
+	u8 rev_id;
+	u8 mac_id;
+};
+
+extern void tc956x_reset_clock_set(const struct tc956x_chip *chip, bool reset,
+				   bool reg0, bool set, u8 bit);
+
+static inline void tc956x_reset_assert(const struct tc956x_chip *chip,
+				       u8 mac_id, enum tc956x_reset_id id)
+{
+	tc956x_reset_clock_set(chip, true, !mac_id, true, (u8)id);
+}
+
+static inline void tc956x_reset_deassert(const struct tc956x_chip *chip,
+					 u8 mac_id, enum tc956x_reset_id id)
+{
+	tc956x_reset_clock_set(chip, true, !mac_id, false, (u8)id);
+}
+
+static inline void tc956x_clock_enable(const struct tc956x_chip *chip,
+				       u8 mac_id, enum tc956x_clock_id id)
+{
+	tc956x_reset_clock_set(chip, false, !mac_id, true, (u8)id);
+}
+
+static inline void tc956x_clock_disable(const struct tc956x_chip *chip,
+					u8 mac_id, enum tc956x_clock_id id)
+{
+	tc956x_reset_clock_set(chip, false, !mac_id, false, (u8)id);
+}
+
+#endif /* __TOSHIBA_TC956X_DWMAC_H__*/
-- 
2.51.0


^ permalink raw reply related

* [PATCH net-next 09/12] gpio: tc956x: add TC956x/QPS615 support
From: Alex Elder @ 2026-05-01 15:54 UTC (permalink / raw)
  To: andrew+netdev, davem, edumazet, kuba, pabeni, maxime.chevallier,
	rmk+kernel, andersson, konradybcio, robh, krzk+dt, conor+dt,
	linusw, brgl, arnd, gregkh
  Cc: daniel, elder, mohd.anwar, a0987203069, alexandre.torgue, ast,
	boon.khai.ng, chenchuangyu, chenhuacai, daniel, hawk, hkallweit1,
	inochiama, john.fastabend, julianbraha, livelycarpet87,
	matthew.gerlach, mcoquelin.stm32, me, prabhakar.mahadev-lad.rj,
	richardcochran, rohan.g.thomas, sdf, siyanteng, weishangjuan,
	wens, netdev, bpf, linux-arm-msm, devicetree, linux-gpio,
	linux-stm32, linux-arm-kernel, linux-kernel
In-Reply-To: <20260501155421.3329862-1-elder@riscstar.com>

Toshiba TC956x is an Ethernet-AVB/TSN bridge and is essentially
a small and highly-specialized SoC.  TC956x includes a GPIO block that
can be accessed, alongside several other peripherals, via two PCIe
endpoint functions.  The PCIe function driver creates an auxiliary
device for the GPIO block, and that device gets bound to this auxiliary
device driver.

Co-developed-by: Daniel Thompson <daniel@riscstar.com>
Signed-off-by: Daniel Thompson <daniel@riscstar.com>
Signed-off-by: Alex Elder <elder@riscstar.com>
---
 drivers/gpio/Kconfig       |  11 ++
 drivers/gpio/Makefile      |   1 +
 drivers/gpio/gpio-tc956x.c | 209 +++++++++++++++++++++++++++++++++++++
 3 files changed, 221 insertions(+)
 create mode 100644 drivers/gpio/gpio-tc956x.c

diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig
index 020e51e30317a..746cedea7e91d 100644
--- a/drivers/gpio/Kconfig
+++ b/drivers/gpio/Kconfig
@@ -1646,6 +1646,17 @@ config GPIO_TC3589X
 	  This enables support for the GPIOs found on the TC3589X
 	  I/O Expander.
 
+config GPIO_TC956X
+	tristate "Toshiba TC956X GPIO support"
+	depends on TOSHIBA_TC956X_PCI
+	default m if TOSHIBA_TC956X_PCI
+	help
+	  This enables support for the GPIO controller embedded in the Toshiba
+	  TC956X (and Qualcomm QPS615).  This device connects to the host
+	  via PCIe port, which is the upstream port on an internal PCIe
+	  switch.  On some platforms, a few of the GPIO lines are used to
+	  manage external resets.
+
 config GPIO_TIMBERDALE
 	bool "Support for timberdale GPIO IP"
 	depends on MFD_TIMBERDALE
diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile
index b267598b517de..c3584e7cba9b4 100644
--- a/drivers/gpio/Makefile
+++ b/drivers/gpio/Makefile
@@ -178,6 +178,7 @@ obj-$(CONFIG_GPIO_SYSCON)		+= gpio-syscon.o
 obj-$(CONFIG_GPIO_TANGIER)		+= gpio-tangier.o
 obj-$(CONFIG_GPIO_TB10X)		+= gpio-tb10x.o
 obj-$(CONFIG_GPIO_TC3589X)		+= gpio-tc3589x.o
+obj-$(CONFIG_GPIO_TC956X)		+= gpio-tc956x.o
 obj-$(CONFIG_GPIO_TEGRA186)		+= gpio-tegra186.o
 obj-$(CONFIG_GPIO_TEGRA)		+= gpio-tegra.o
 obj-$(CONFIG_GPIO_THUNDERX)		+= gpio-thunderx.o
diff --git a/drivers/gpio/gpio-tc956x.c b/drivers/gpio/gpio-tc956x.c
new file mode 100644
index 0000000000000..12221d8f812d9
--- /dev/null
+++ b/drivers/gpio/gpio-tc956x.c
@@ -0,0 +1,209 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/*
+ * Copyright (C) 2026 by RISCstar Solutions Corporation.  All rights reserved.
+ */
+
+/*
+ * The Toshiba TC956X implements a PCIe Gen 3 switch that connects an
+ * upstream x4 port to two downstream PCIe x2 ports.  It incorporates
+ * an internal endpoint on a internal PCIe port that implements two
+ * Synopsys XGMAC Ethernet interfaces.
+ *
+ * 35 GPIOs are also implemented by an embedded GPIO controller.  Three
+ * registers control the first 32 GPIOs (other than 20 and 21, which are
+ * reserved).  Three other registers control GPIOs 32 through 36. GPIOs
+ * 22-24, 27-28, 31, and 34 are treated as "input only".
+ *
+ * There is a TC956X PCI power controller driver that accesses the
+ * direction and output value registers for GPIOs 2 and 3.  These
+ * GPIOs control the reset signal for the two downstream PCIe ports.
+ * Their values will never change during operation of this driver, and
+ * this driver reserves these two GPIOS.
+ */
+
+#include <linux/auxiliary_bus.h>
+#include <linux/dev_printk.h>
+#include <linux/gpio/driver.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+
+#define DRIVER_NAME		"tc956x-gpio"
+
+#define TC956X_GPIO_COUNT	37	/* Number of GPIOs (20-21 reserved) */
+
+/* The GPIO offsets are relative to 0x1200 in TC956X SFR space */
+#define GPIO_IN0_OFFSET		0x00		/* Input value (0-31) */
+#define GPIO_EN0_OFFSET		0x08		/* 0: out; 1: in (0-31) */
+#define GPIO_OUT0_OFFSET	0x10		/* Output value (0-31) */
+
+#define GPIO_IN1_OFFSET		0x04		/* Input value (32-36) */
+#define GPIO_EN1_OFFSET		0x0c		/* 0: out; 1: in (32-36) */
+#define GPIO_OUT1_OFFSET	0x14		/* Output value (32-36) */
+
+/*
+ * struct tc956x_gpio - Information related to the embedded GPIO controller
+ * @chip:		GPIO chip structure
+ * @regmap:		MMIO register map for SFR GPIO region access
+ * @input_only:		Bitmap indicating which GPIOs are input-only
+ */
+struct tc956x_gpio {
+	struct gpio_chip chip;
+	struct regmap *regmap;
+	DECLARE_BITMAP(input_only, TC956X_GPIO_COUNT);
+};
+
+static int tc956x_gpio_get_direction(struct gpio_chip *gc, unsigned int offset)
+{
+	struct tc956x_gpio *gpio = gpiochip_get_data(gc);
+	u32 reg;
+	u32 val;
+
+	if (test_bit(offset, gpio->input_only))
+		return GPIO_LINE_DIRECTION_IN;
+
+	reg = offset < 32 ? GPIO_EN0_OFFSET : GPIO_EN1_OFFSET;
+
+	regmap_read(gpio->regmap, reg, &val);
+	if (val & BIT(offset % 32))
+		return GPIO_LINE_DIRECTION_IN;
+
+	return GPIO_LINE_DIRECTION_OUT;
+}
+
+static int tc956x_gpio_direction_input(struct gpio_chip *gc,
+				       unsigned int offset)
+{
+	u32 reg = offset < 32 ? GPIO_EN0_OFFSET : GPIO_EN1_OFFSET;
+	struct tc956x_gpio *gpio = gpiochip_get_data(gc);
+	u32 mask = BIT(offset % 32);
+
+	return regmap_update_bits(gpio->regmap, reg, mask, mask);
+}
+
+static int tc956x_gpio_direction_output(struct gpio_chip *gc,
+					unsigned int offset, int value)
+{
+	struct tc956x_gpio *gpio = gpiochip_get_data(gc);
+	u32 vreg;
+	u32 dreg;
+	u32 mask;
+
+	if (test_bit(offset, gpio->input_only))
+		return -EINVAL;
+
+	if (offset < 32) {
+		vreg = GPIO_OUT0_OFFSET;
+		dreg = GPIO_EN0_OFFSET;
+	} else {
+		vreg = GPIO_OUT1_OFFSET;
+		dreg = GPIO_EN1_OFFSET;
+	}
+	mask = BIT(offset % 32);
+
+	/* Set output value first, then direction */
+	regmap_update_bits(gpio->regmap, vreg, mask, value ? mask : 0);
+
+	return regmap_update_bits(gpio->regmap, dreg, mask, 0);
+}
+
+static int tc956x_gpio_get(struct gpio_chip *gc, unsigned int offset)
+{
+	u32 reg = offset < 32 ? GPIO_IN0_OFFSET : GPIO_IN1_OFFSET;
+	struct tc956x_gpio *gpio = gpiochip_get_data(gc);
+	u32 val;
+
+	regmap_read(gpio->regmap, reg, &val);
+
+	return val & BIT(offset % 32) ? 1 : 0;
+}
+
+static int tc956x_gpio_set(struct gpio_chip *gc, unsigned int offset, int value)
+{
+	u32 reg = offset < 32 ? GPIO_OUT0_OFFSET : GPIO_OUT1_OFFSET;
+	struct tc956x_gpio *gpio = gpiochip_get_data(gc);
+	u32 mask = BIT(offset % 32);
+
+	return regmap_update_bits(gpio->regmap, reg, mask, value ? mask : 0);
+}
+
+static int tc956x_gpio_init_valid_mask(struct gpio_chip *gc,
+				       unsigned long *valid_mask,
+				       unsigned int ngpios)
+{
+	/*
+	 * GPIOs 2 and 3 are used by the PCI power control driver, and
+	 * we don't allow them to be used.  GPIOs 20 and 21 are reserved
+	 * (and not usable).
+	 */
+	bitmap_fill(valid_mask, ngpios);
+	bitmap_clear(valid_mask, 2, 2);
+	bitmap_clear(valid_mask, 20, 2);
+
+	return 0;
+}
+
+static int tc956x_gpio_probe(struct auxiliary_device *adev,
+			     const struct auxiliary_device_id *id)
+{
+	struct device *dev = &adev->dev;
+	struct tc956x_gpio *gpio;
+	struct gpio_chip *gc;
+
+	if (!dev->platform_data)
+		return -EINVAL;
+
+	gpio = devm_kzalloc(dev, sizeof(*gpio), GFP_KERNEL);
+	if (!gpio)
+		return -ENOMEM;
+	gpio->regmap = dev->platform_data;
+
+	/* Mark GPIOs 22, 23, 24, 27, 28, 31, and 34 as input only */
+	bitmap_set(gpio->input_only, 22, 3);
+	bitmap_set(gpio->input_only, 27, 2);
+	set_bit(31, gpio->input_only);
+	set_bit(34, gpio->input_only);
+
+	gc = &gpio->chip;
+
+	gc->label = DRIVER_NAME;
+	gc->parent = dev->parent;
+
+	gc->get_direction = tc956x_gpio_get_direction;
+	gc->direction_input = tc956x_gpio_direction_input;
+	gc->direction_output = tc956x_gpio_direction_output;
+	gc->get = tc956x_gpio_get;
+	gc->set = tc956x_gpio_set;
+	gc->init_valid_mask = tc956x_gpio_init_valid_mask;
+
+	gc->base = -1;
+	gc->ngpio = TC956X_GPIO_COUNT;
+	gc->can_sleep = false;
+
+	dev_set_drvdata(dev, gpio);
+
+	return devm_gpiochip_add_data(dev, gc, gpio);
+}
+
+static const struct auxiliary_device_id tc956x_gpio_ids[] = {
+	{ .name = "tc956x_pci.tc9564-gpio", },
+	{ }
+};
+MODULE_DEVICE_TABLE(auxiliary, tc956x_gpio_ids);
+
+static struct auxiliary_driver tc956x_gpio_driver = {
+	.name		= DRIVER_NAME,
+	.probe          = tc956x_gpio_probe,
+	.id_table       = tc956x_gpio_ids,
+	.driver = {
+		.name		= DRIVER_NAME,
+		.owner		= THIS_MODULE,
+		.probe_type	= PROBE_PREFER_ASYNCHRONOUS,
+	},
+};
+module_auxiliary_driver(tc956x_gpio_driver);
+
+MODULE_DESCRIPTION("Toshiba TC956X PCIe GPIO Driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("auxiliary:" DRIVER_NAME);
-- 
2.51.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;
as well as URLs for NNTP newsgroup(s).