* Re: [PATCH v8 10/10] rust: module: update MAINTAINERS to cover module.rs
From: Alice Ryhl @ 2026-07-13 8:57 UTC (permalink / raw)
To: Alvin Sun
Cc: Miguel Ojeda, Boqun Feng, Gary Guo, Björn Roy Baron,
Benno Lossin, Andreas Hindborg, Trevor Gross, Danilo Krummrich,
Luis Chamberlain, Petr Pavlu, Daniel Gomez, Sami Tolvanen,
Aaron Tomlin, Greg Kroah-Hartman, Rafael J. Wysocki, David Airlie,
Simona Vetter, Daniel Almeida, Arnd Bergmann, Brendan Higgins,
David Gow, Rae Moar, Breno Leitao, Jens Axboe, Dave Ertman,
Leon Romanovsky, Igor Korotin, FUJITA Tomonori, Bjorn Helgaas,
Krzysztof Wilczyński, Arve Hjønnevåg, Todd Kjos,
Christian Brauner, Carlos Llamas, rust-for-linux, linux-modules,
driver-core, dri-devel, nova-gpu, linux-kselftest, kunit-dev,
linux-block, linux-kernel, netdev, linux-pci
In-Reply-To: <d7c1607f-ea74-442a-9700-7fc4f0c64b76@linux.dev>
On Mon, Jul 13, 2026 at 10:48 AM Alvin Sun <alvin.sun@linux.dev> wrote:
>
>
> On 7/13/26 16:16, Alice Ryhl wrote:
> > On Mon, Jul 13, 2026 at 02:45:31PM +0800, Alvin Sun wrote:
> >> Module types now live in `rust/kernel/module.rs` alongside
> >> `rust/kernel/module_param.rs`. Update the MODULE SUPPORT file pattern
> >> from `rust/kernel/module_param.rs` to `rust/kernel/module*.rs` so both
> >> files are covered.
> >>
> >> Cc: Petr Pavlu <petr.pavlu@suse.com>
> >> Assisted-by: opencode:glm-5.2
> >> Link: https://lore.kernel.org/rust-for-linux/8ea21b29-9baf-4926-a16f-7d21c5a1a1b8@suse.com
> >> Signed-off-by: Alvin Sun <alvin.sun@linux.dev>
> >> ---
> >> MAINTAINERS | 2 +-
> >> 1 file changed, 1 insertion(+), 1 deletion(-)
> >>
> >> diff --git a/MAINTAINERS b/MAINTAINERS
> >> index e035a3be797c4..74733de3e41ee 100644
> >> --- a/MAINTAINERS
> >> +++ b/MAINTAINERS
> >> @@ -17984,7 +17984,7 @@ F: include/linux/module*.h
> >> F: kernel/module/
> >> F: lib/test_kmod.c
> >> F: lib/tests/module/
> >> -F: rust/kernel/module_param.rs
> >> +F: rust/kernel/module*.rs
> >> F: rust/macros/module.rs
> >> F: scripts/module*
> >> F: tools/testing/selftests/kmod/
> > Is there a reason why this is a separate commit from the commit that
> > introduces rust/kernel/module.rs file?
>
> Gary asked the same on v6; `git log` shows MAINTAINERS changes are
> conventionally
> standalone commits with a `MAINTAINERS: ` prefix, so I kept it separate.
> Gary agreed [1].
>
> [1]:
> https://lore.kernel.org/rust-for-linux/DJJ26195JBS9.D3DNLZPRJYO4@garyguo.net/
Ok, that's fine then.
Alice
^ permalink raw reply
* Re: [PATCH rdma-next 13/13] RDMA/selftests: Add rxe_netns_names test
From: Jiri Pirko @ 2026-07-13 8:58 UTC (permalink / raw)
To: Zhu Yanjun
Cc: linux-rdma, cgroups, netdev, linux-s390, linux-kselftest, jgg,
leon, parav, mbloch, cmeiohas, roman.gushchin, bvanassche,
zyjzyj2000, shuah, tj, mkoutny, hannes, alibuda, dust.li, sidraya,
wenjia
In-Reply-To: <ebb4125e-6d35-44c9-b1b8-267dc3226b81@linux.dev>
Fri, Jul 10, 2026 at 06:24:25AM +0200, yanjun.zhu@linux.dev wrote:
>
>在 2026/7/9 2:55, Jiri Pirko 写道:
>> From: Jiri Pirko <jiri@nvidia.com>
>>
>> Add a kselftest script that exercises per-netns RDMA device naming
>> with RXE. Cover duplicate names across namespaces, move conflict
>> handling, move-with-rename, and same-namespace rename requests.
>
>This is a very comprehensive test suite for the per-netns RDMA device naming
>infra.
>
>I especially appreciate the inclusion of the 'TEST_TEARDOWN_RETURN'
>case—ensuring
>
>that the kernel's automatic renaming and fallback mechanics work seamlessly
>during
>
>netns deletion is crucial for long-term stability.
>
>
>One minor thing to clean up before pushing to the tree: the script declares
>'ktap_set_plan 7'
>
>but actually defines 6 distinct test cases in the execution block. I will fix
>this plan count to 6
Correct. Leftover, will fix.
>
>to avoid any "bad plan" warnings in automated CI frameworks (like KernelCI).
>
>
>Aside from that, the cleanup paths and setup tracking are solid.
>
>Thanks for adding this.
>
>Reviewed-by: Zhu Yanjun <yanjun.zhu@linux.dev>
Thanks!
>
>Zhu Yanjun
>
>>
>> Signed-off-by: Jiri Pirko <jiri@nvidia.com>
>> ---
>> tools/testing/selftests/rdma/Makefile | 3 +-
>> tools/testing/selftests/rdma/config | 2 +
>> .../testing/selftests/rdma/rxe_netns_names.sh | 282 ++++++++++++++++++
>> 3 files changed, 286 insertions(+), 1 deletion(-)
>> create mode 100755 tools/testing/selftests/rdma/rxe_netns_names.sh
>>
>> diff --git a/tools/testing/selftests/rdma/Makefile b/tools/testing/selftests/rdma/Makefile
>> index 07af7f15c1bf..a91c14c45006 100644
>> --- a/tools/testing/selftests/rdma/Makefile
>> +++ b/tools/testing/selftests/rdma/Makefile
>> @@ -3,6 +3,7 @@ TEST_PROGS := rxe_rping_between_netns.sh \
>> rxe_ipv6.sh \
>> rxe_socket_with_netns.sh \
>> rxe_test_NETDEV_UNREGISTER.sh \
>> - rxe_sent_rcvd_bytes.sh
>> + rxe_sent_rcvd_bytes.sh \
>> + rxe_netns_names.sh
>> include ../lib.mk
>> diff --git a/tools/testing/selftests/rdma/config b/tools/testing/selftests/rdma/config
>> index 4ffb814e253b..e1ff54ec0f57 100644
>> --- a/tools/testing/selftests/rdma/config
>> +++ b/tools/testing/selftests/rdma/config
>> @@ -1,3 +1,5 @@
>> CONFIG_TUN
>> CONFIG_VETH
>> +CONFIG_DUMMY
>> +CONFIG_NET_NS
>> CONFIG_RDMA_RXE
>> diff --git a/tools/testing/selftests/rdma/rxe_netns_names.sh b/tools/testing/selftests/rdma/rxe_netns_names.sh
>> new file mode 100755
>> index 000000000000..a7e57706fdff
>> --- /dev/null
>> +++ b/tools/testing/selftests/rdma/rxe_netns_names.sh
>> @@ -0,0 +1,282 @@
>> +#!/bin/bash
>> +# SPDX-License-Identifier: GPL-2.0
>> +#
>> +# Exercise RDMA device name handling across network namespaces.
>> +
>> +source "$(dirname "$0")/../kselftest/ktap_helpers.sh"
>> +
>> +NAME_PREFIX="rxe_netns_names_$$"
>> +NETDEV_PREFIX="rxn$$"
>> +NS1="${NAME_PREFIX}ns1"
>> +NS2="${NAME_PREFIX}ns2"
>> +RXE_A="${NAME_PREFIX}rxe_a"
>> +RXE_B="${NAME_PREFIX}rxe_b"
>> +RXE_SAME="${NAME_PREFIX}rxe_same"
>> +RXE_NEW="${NAME_PREFIX}rxe_new"
>> +DUMMY_A="${NETDEV_PREFIX}a"
>> +DUMMY_B="${NETDEV_PREFIX}b"
>> +OLD_MODE=""
>> +MODE_CHANGED=0
>> +MODS=("dummy" "rdma_rxe")
>> +TEST_SAME_NAMES="same RDMA device name can exist in two net namespaces"
>> +TEST_MOVE_CONFLICT="move without rename fails on destination name conflict"
>> +TEST_MOVE_RENAME="move then rename succeeds"
>> +TEST_COMBINED_MOVE_RENAME="move with requested destination name succeeds"
>> +TEST_SAME_NETNS_DUP_RENAME="same-netns rename rejects duplicate name"
>> +TEST_TEARDOWN_RETURN="netns delete returns device to init_net and renames on conflict"
>> +
>> +ksft_skip()
>> +{
>> + ktap_skip_all "$*"
>> + exit "$KSFT_SKIP"
>> +}
>> +
>> +fail()
>> +{
>> + ktap_exit_fail_msg "$*"
>> +}
>> +
>> +need_cmd()
>> +{
>> + command -v "$1" >/dev/null 2>&1 || ksft_skip "missing command: $1"
>> +}
>> +
>> +rdma_ns()
>> +{
>> + local ns=$1
>> +
>> + shift
>> + ip netns exec "$ns" rdma "$@"
>> +}
>> +
>> +rdma_dev_exists()
>> +{
>> + local ns=$1
>> + local dev=$2
>> +
>> + if [ -n "$ns" ]; then
>> + rdma_ns "$ns" dev show "$dev" >/dev/null 2>&1
>> + else
>> + rdma dev show "$dev" >/dev/null 2>&1
>> + fi
>> +}
>> +
>> +add_dummy()
>> +{
>> + local netdev=$1
>> +
>> + ip link add "$netdev" type dummy || return 1
>> + ip link set "$netdev" up || return 1
>> +}
>> +
>> +add_rxe()
>> +{
>> + local dev=$1
>> + local netdev=$2
>> +
>> + rdma link add "$dev" type rxe netdev "$netdev"
>> +}
>> +
>> +rdma_dev_on_netdev()
>> +{
>> + local netdev=$1
>> +
>> + rdma link show 2>/dev/null | awk -v want="$netdev" '
>> + {
>> + for (i = 1; i < NF; i++)
>> + if ($i == "netdev" && $(i + 1) == want) {
>> + dev = $2
>> + sub(/\/.*/, "", dev)
>> + print dev
>> + exit
>> + }
>> + }'
>> +}
>> +
>> +wait_rdma_dev_on_netdev()
>> +{
>> + local netdev=$1
>> + local dev
>> + local i
>> +
>> + for i in $(seq 1 50); do
>> + dev=$(rdma_dev_on_netdev "$netdev")
>> + if [ -n "$dev" ]; then
>> + echo "$dev"
>> + return 0
>> + fi
>> + sleep 0.1
>> + done
>> +
>> + return 1
>> +}
>> +
>> +setup_devs()
>> +{
>> + cleanup_devs
>> +
>> + add_dummy "$DUMMY_A" || return 1
>> + add_dummy "$DUMMY_B" || return 1
>> +
>> + add_rxe "$RXE_A" "$DUMMY_A" || return 1
>> + add_rxe "$RXE_B" "$DUMMY_B" || return 1
>> +}
>> +
>> +cleanup_devs()
>> +{
>> + ip link del "$DUMMY_A" 2>/dev/null
>> + ip link del "$DUMMY_B" 2>/dev/null
>> +}
>> +
>> +setup()
>> +{
>> + OLD_MODE=$(rdma system show 2>/dev/null |
>> + sed -n 's/.*netns \([^ ]*\).*/\1/p')
>> + [ -n "$OLD_MODE" ] || ksft_skip "failed to read RDMA netns mode"
>> +
>> + rdma system set netns exclusive >/dev/null 2>&1 ||
>> + ksft_skip "rdma netns exclusive mode is not supported"
>> + MODE_CHANGED=1
>> +
>> + ip netns add "$NS1" || return 1
>> + ip netns add "$NS2" || return 1
>> +}
>> +
>> +cleanup()
>> +{
>> + cleanup_devs
>> +
>> + ip netns del "$NS1" 2>/dev/null
>> + ip netns del "$NS2" 2>/dev/null
>> +
>> + if [ "$MODE_CHANGED" -eq 1 ]; then
>> + rdma system set netns "$OLD_MODE" 2>/dev/null
>> + fi
>> +
>> + for m in "${MODS[@]}"; do
>> + modprobe -r "$m" 2>/dev/null
>> + done
>> +}
>> +
>> +rdma_supports_combined_move_rename()
>> +{
>> + rdma dev help 2>&1 | grep -Eq 'netns .*name|name .*netns'
>> +}
>> +
>> +[ "$(id -u)" -eq 0 ] || ksft_skip "must be run as root"
>> +need_cmd ip
>> +need_cmd rdma
>> +need_cmd modprobe
>> +
>> +trap cleanup EXIT
>> +
>> +for m in "${MODS[@]}"; do
>> + modinfo "$m" >/dev/null 2>&1 || ksft_skip "module $m not found"
>> + modprobe "$m" || fail "failed to load $m"
>> +done
>> +
>> +setup || fail "failed to create net namespaces"
>> +
>> +ktap_print_header
>> +ktap_set_plan 7
>> +
>> +if setup_devs &&
>> + rdma dev set "$RXE_A" netns "$NS1" &&
>> + rdma_ns "$NS1" dev set "$RXE_A" name "$RXE_SAME" &&
>> + rdma dev set "$RXE_B" netns "$NS2" &&
>> + rdma_ns "$NS2" dev set "$RXE_B" name "$RXE_SAME" &&
>> + rdma_dev_exists "$NS1" "$RXE_SAME" &&
>> + rdma_dev_exists "$NS2" "$RXE_SAME"; then
>> + ktap_test_pass "$TEST_SAME_NAMES"
>> +else
>> + ktap_test_fail "$TEST_SAME_NAMES"
>> +fi
>> +cleanup_devs
>> +
>> +if ! setup_devs ||
>> + ! rdma dev set "$RXE_A" netns "$NS1" ||
>> + ! rdma_ns "$NS1" dev set "$RXE_A" name "$RXE_SAME" ||
>> + ! rdma dev set "$RXE_B" netns "$NS2" ||
>> + ! rdma_ns "$NS2" dev set "$RXE_B" name "$RXE_SAME"; then
>> + ktap_test_fail "$TEST_MOVE_CONFLICT"
>> +elif rdma_ns "$NS1" dev set "$RXE_SAME" netns "$NS2" >/dev/null 2>&1; then
>> + ktap_test_fail "$TEST_MOVE_CONFLICT"
>> +elif rdma_dev_exists "$NS1" "$RXE_SAME" &&
>> + rdma_dev_exists "$NS2" "$RXE_SAME"; then
>> + ktap_test_pass "$TEST_MOVE_CONFLICT"
>> +else
>> + ktap_test_fail "$TEST_MOVE_CONFLICT"
>> +fi
>> +cleanup_devs
>> +
>> +if ! setup_devs; then
>> + ktap_test_fail "$TEST_MOVE_RENAME"
>> +elif rdma dev set "$RXE_A" netns "$NS2" &&
>> + rdma_ns "$NS2" dev set "$RXE_A" name "$RXE_NEW"; then
>> + if rdma_dev_exists "$NS2" "$RXE_NEW" &&
>> + ! rdma_dev_exists "" "$RXE_A"; then
>> + ktap_test_pass "$TEST_MOVE_RENAME"
>> + else
>> + ktap_test_fail "$TEST_MOVE_RENAME"
>> + fi
>> +else
>> + ktap_test_fail "$TEST_MOVE_RENAME"
>> +fi
>> +cleanup_devs
>> +
>> +if ! rdma_supports_combined_move_rename; then
>> + ktap_test_skip "$TEST_COMBINED_MOVE_RENAME"
>> +elif ! setup_devs; then
>> + ktap_test_fail "$TEST_COMBINED_MOVE_RENAME"
>> +elif rdma dev set "$RXE_A" netns "$NS2" name "$RXE_NEW"; then
>> + if rdma_dev_exists "$NS2" "$RXE_NEW" &&
>> + ! rdma_dev_exists "" "$RXE_A"; then
>> + ktap_test_pass "$TEST_COMBINED_MOVE_RENAME"
>> + else
>> + ktap_test_fail "$TEST_COMBINED_MOVE_RENAME"
>> + fi
>> +else
>> + ktap_test_fail "$TEST_COMBINED_MOVE_RENAME"
>> +fi
>> +cleanup_devs
>> +
>> +if ! setup_devs; then
>> + ktap_test_fail "$TEST_SAME_NETNS_DUP_RENAME"
>> +elif rdma dev set "$RXE_A" name "$RXE_SAME" &&
>> + rdma dev set "$RXE_B" name "$RXE_NEW"; then
>> + if rdma dev set "$RXE_A" name "$RXE_NEW" >/dev/null 2>&1; then
>> + ktap_test_fail "$TEST_SAME_NETNS_DUP_RENAME"
>> + elif rdma_dev_exists "" "$RXE_SAME" &&
>> + rdma_dev_exists "" "$RXE_NEW"; then
>> + ktap_test_pass "$TEST_SAME_NETNS_DUP_RENAME"
>> + else
>> + ktap_test_fail "$TEST_SAME_NETNS_DUP_RENAME"
>> + fi
>> +else
>> + ktap_test_fail "$TEST_SAME_NETNS_DUP_RENAME"
>> +fi
>> +cleanup_devs
>> +
>> +if ! setup_devs; then
>> + ktap_test_fail "$TEST_TEARDOWN_RETURN"
>> +elif ! rdma dev set "$RXE_A" name "$RXE_SAME" ||
>> + ! rdma dev set "$RXE_B" netns "$NS2" ||
>> + ! rdma_ns "$NS2" dev set "$RXE_B" name "$RXE_SAME" ||
>> + ! rdma_dev_exists "$NS2" "$RXE_SAME"; then
>> + ktap_test_fail "$TEST_TEARDOWN_RETURN"
>> +else
>> + ip netns del "$NS2"
>> + returned=$(wait_rdma_dev_on_netdev "$DUMMY_B")
>> + ktap_print_msg "device returned to init_net as '${returned:-<missing>}'"
>> + if rdma_dev_exists "" "$RXE_SAME" &&
>> + [ -n "$returned" ] &&
>> + [ "$returned" != "$RXE_SAME" ] &&
>> + [ "${returned#ibdev}" != "$returned" ]; then
>> + ktap_test_pass "$TEST_TEARDOWN_RETURN"
>> + else
>> + ktap_test_fail "$TEST_TEARDOWN_RETURN"
>> + fi
>> +fi
>> +cleanup_devs
>> +
>> +ktap_finished
>
>--
>Best Regards,
>Yanjun.Zhu
>
^ permalink raw reply
* Re: [PATCH rdma-next 13/13] RDMA/selftests: Add rxe_netns_names test
From: Jiri Pirko @ 2026-07-13 9:02 UTC (permalink / raw)
To: yanjun.zhu
Cc: linux-rdma, cgroups, netdev, linux-s390, linux-kselftest, jgg,
leon, parav, mbloch, cmeiohas, roman.gushchin, bvanassche,
zyjzyj2000, shuah, tj, mkoutny, hannes, alibuda, dust.li, sidraya,
wenjia
In-Reply-To: <2aec2d14-0000-4595-aa2a-73aa5ae41060@linux.dev>
Sat, Jul 11, 2026 at 01:51:45AM +0200, yanjun.zhu@linux.dev wrote:
>On 7/9/26 2:55 AM, Jiri Pirko wrote:
>> From: Jiri Pirko <jiri@nvidia.com>
>>
>> Add a kselftest script that exercises per-netns RDMA device naming
>> with RXE. Cover duplicate names across namespaces, move conflict
>> handling, move-with-rename, and same-namespace rename requests.
>
># timeout set to 45
># selftests: rdma: rxe_netns_names.sh
># TAP version 13
># 1..6
># ok 1 same RDMA device name can exist in two net namespaces
># ok 2 move without rename fails on destination name conflict
># ok 3 move then rename succeeds
># ok 4 move with requested destination name succeeds # SKIP < --- This
>testcase skip
>
># ok 5 same-netns rename rejects duplicate name
># # device returned to init_net as 'ibdev35'
># ok 6 netns delete returns device to init_net and renames on conflict
># # 1 skipped test(s) detected. Consider enabling relevant config options to
>improve coverage.
># # Totals: pass:5 fail:0 xfail:0 xpass:0 skip:1 error:0
>ok 6 selftests: rdma: rxe_netns_names.sh
>
>The above are my test results. But one testcase is skipped.
Yep, you need patched iproute2:
https://github.com/jpirko/iproute2_mlxsw/commit/cfb61f715fb16fc2b6d007408e048786ef3ed3f6
Plan to send this one after this patchset is merged.
>
>Zhu Yanjun
>
>>
>> Signed-off-by: Jiri Pirko <jiri@nvidia.com>
>> ---
>> tools/testing/selftests/rdma/Makefile | 3 +-
>> tools/testing/selftests/rdma/config | 2 +
>> .../testing/selftests/rdma/rxe_netns_names.sh | 282 ++++++++++++++++++
>> 3 files changed, 286 insertions(+), 1 deletion(-)
>> create mode 100755 tools/testing/selftests/rdma/rxe_netns_names.sh
>>
>> diff --git a/tools/testing/selftests/rdma/Makefile b/tools/testing/selftests/rdma/Makefile
>> index 07af7f15c1bf..a91c14c45006 100644
>> --- a/tools/testing/selftests/rdma/Makefile
>> +++ b/tools/testing/selftests/rdma/Makefile
>> @@ -3,6 +3,7 @@ TEST_PROGS := rxe_rping_between_netns.sh \
>> rxe_ipv6.sh \
>> rxe_socket_with_netns.sh \
>> rxe_test_NETDEV_UNREGISTER.sh \
>> - rxe_sent_rcvd_bytes.sh
>> + rxe_sent_rcvd_bytes.sh \
>> + rxe_netns_names.sh
>> include ../lib.mk
>> diff --git a/tools/testing/selftests/rdma/config b/tools/testing/selftests/rdma/config
>> index 4ffb814e253b..e1ff54ec0f57 100644
>> --- a/tools/testing/selftests/rdma/config
>> +++ b/tools/testing/selftests/rdma/config
>> @@ -1,3 +1,5 @@
>> CONFIG_TUN
>> CONFIG_VETH
>> +CONFIG_DUMMY
>> +CONFIG_NET_NS
>> CONFIG_RDMA_RXE
>> diff --git a/tools/testing/selftests/rdma/rxe_netns_names.sh b/tools/testing/selftests/rdma/rxe_netns_names.sh
>> new file mode 100755
>> index 000000000000..a7e57706fdff
>> --- /dev/null
>> +++ b/tools/testing/selftests/rdma/rxe_netns_names.sh
>> @@ -0,0 +1,282 @@
>> +#!/bin/bash
>> +# SPDX-License-Identifier: GPL-2.0
>> +#
>> +# Exercise RDMA device name handling across network namespaces.
>> +
>> +source "$(dirname "$0")/../kselftest/ktap_helpers.sh"
>> +
>> +NAME_PREFIX="rxe_netns_names_$$"
>> +NETDEV_PREFIX="rxn$$"
>> +NS1="${NAME_PREFIX}ns1"
>> +NS2="${NAME_PREFIX}ns2"
>> +RXE_A="${NAME_PREFIX}rxe_a"
>> +RXE_B="${NAME_PREFIX}rxe_b"
>> +RXE_SAME="${NAME_PREFIX}rxe_same"
>> +RXE_NEW="${NAME_PREFIX}rxe_new"
>> +DUMMY_A="${NETDEV_PREFIX}a"
>> +DUMMY_B="${NETDEV_PREFIX}b"
>> +OLD_MODE=""
>> +MODE_CHANGED=0
>> +MODS=("dummy" "rdma_rxe")
>> +TEST_SAME_NAMES="same RDMA device name can exist in two net namespaces"
>> +TEST_MOVE_CONFLICT="move without rename fails on destination name conflict"
>> +TEST_MOVE_RENAME="move then rename succeeds"
>> +TEST_COMBINED_MOVE_RENAME="move with requested destination name succeeds"
>> +TEST_SAME_NETNS_DUP_RENAME="same-netns rename rejects duplicate name"
>> +TEST_TEARDOWN_RETURN="netns delete returns device to init_net and renames on conflict"
>> +
>> +ksft_skip()
>> +{
>> + ktap_skip_all "$*"
>> + exit "$KSFT_SKIP"
>> +}
>> +
>> +fail()
>> +{
>> + ktap_exit_fail_msg "$*"
>> +}
>> +
>> +need_cmd()
>> +{
>> + command -v "$1" >/dev/null 2>&1 || ksft_skip "missing command: $1"
>> +}
>> +
>> +rdma_ns()
>> +{
>> + local ns=$1
>> +
>> + shift
>> + ip netns exec "$ns" rdma "$@"
>> +}
>> +
>> +rdma_dev_exists()
>> +{
>> + local ns=$1
>> + local dev=$2
>> +
>> + if [ -n "$ns" ]; then
>> + rdma_ns "$ns" dev show "$dev" >/dev/null 2>&1
>> + else
>> + rdma dev show "$dev" >/dev/null 2>&1
>> + fi
>> +}
>> +
>> +add_dummy()
>> +{
>> + local netdev=$1
>> +
>> + ip link add "$netdev" type dummy || return 1
>> + ip link set "$netdev" up || return 1
>> +}
>> +
>> +add_rxe()
>> +{
>> + local dev=$1
>> + local netdev=$2
>> +
>> + rdma link add "$dev" type rxe netdev "$netdev"
>> +}
>> +
>> +rdma_dev_on_netdev()
>> +{
>> + local netdev=$1
>> +
>> + rdma link show 2>/dev/null | awk -v want="$netdev" '
>> + {
>> + for (i = 1; i < NF; i++)
>> + if ($i == "netdev" && $(i + 1) == want) {
>> + dev = $2
>> + sub(/\/.*/, "", dev)
>> + print dev
>> + exit
>> + }
>> + }'
>> +}
>> +
>> +wait_rdma_dev_on_netdev()
>> +{
>> + local netdev=$1
>> + local dev
>> + local i
>> +
>> + for i in $(seq 1 50); do
>> + dev=$(rdma_dev_on_netdev "$netdev")
>> + if [ -n "$dev" ]; then
>> + echo "$dev"
>> + return 0
>> + fi
>> + sleep 0.1
>> + done
>> +
>> + return 1
>> +}
>> +
>> +setup_devs()
>> +{
>> + cleanup_devs
>> +
>> + add_dummy "$DUMMY_A" || return 1
>> + add_dummy "$DUMMY_B" || return 1
>> +
>> + add_rxe "$RXE_A" "$DUMMY_A" || return 1
>> + add_rxe "$RXE_B" "$DUMMY_B" || return 1
>> +}
>> +
>> +cleanup_devs()
>> +{
>> + ip link del "$DUMMY_A" 2>/dev/null
>> + ip link del "$DUMMY_B" 2>/dev/null
>> +}
>> +
>> +setup()
>> +{
>> + OLD_MODE=$(rdma system show 2>/dev/null |
>> + sed -n 's/.*netns \([^ ]*\).*/\1/p')
>> + [ -n "$OLD_MODE" ] || ksft_skip "failed to read RDMA netns mode"
>> +
>> + rdma system set netns exclusive >/dev/null 2>&1 ||
>> + ksft_skip "rdma netns exclusive mode is not supported"
>> + MODE_CHANGED=1
>> +
>> + ip netns add "$NS1" || return 1
>> + ip netns add "$NS2" || return 1
>> +}
>> +
>> +cleanup()
>> +{
>> + cleanup_devs
>> +
>> + ip netns del "$NS1" 2>/dev/null
>> + ip netns del "$NS2" 2>/dev/null
>> +
>> + if [ "$MODE_CHANGED" -eq 1 ]; then
>> + rdma system set netns "$OLD_MODE" 2>/dev/null
>> + fi
>> +
>> + for m in "${MODS[@]}"; do
>> + modprobe -r "$m" 2>/dev/null
>> + done
>> +}
>> +
>> +rdma_supports_combined_move_rename()
>> +{
>> + rdma dev help 2>&1 | grep -Eq 'netns .*name|name .*netns'
>> +}
>> +
>> +[ "$(id -u)" -eq 0 ] || ksft_skip "must be run as root"
>> +need_cmd ip
>> +need_cmd rdma
>> +need_cmd modprobe
>> +
>> +trap cleanup EXIT
>> +
>> +for m in "${MODS[@]}"; do
>> + modinfo "$m" >/dev/null 2>&1 || ksft_skip "module $m not found"
>> + modprobe "$m" || fail "failed to load $m"
>> +done
>> +
>> +setup || fail "failed to create net namespaces"
>> +
>> +ktap_print_header
>> +ktap_set_plan 7
>> +
>> +if setup_devs &&
>> + rdma dev set "$RXE_A" netns "$NS1" &&
>> + rdma_ns "$NS1" dev set "$RXE_A" name "$RXE_SAME" &&
>> + rdma dev set "$RXE_B" netns "$NS2" &&
>> + rdma_ns "$NS2" dev set "$RXE_B" name "$RXE_SAME" &&
>> + rdma_dev_exists "$NS1" "$RXE_SAME" &&
>> + rdma_dev_exists "$NS2" "$RXE_SAME"; then
>> + ktap_test_pass "$TEST_SAME_NAMES"
>> +else
>> + ktap_test_fail "$TEST_SAME_NAMES"
>> +fi
>> +cleanup_devs
>> +
>> +if ! setup_devs ||
>> + ! rdma dev set "$RXE_A" netns "$NS1" ||
>> + ! rdma_ns "$NS1" dev set "$RXE_A" name "$RXE_SAME" ||
>> + ! rdma dev set "$RXE_B" netns "$NS2" ||
>> + ! rdma_ns "$NS2" dev set "$RXE_B" name "$RXE_SAME"; then
>> + ktap_test_fail "$TEST_MOVE_CONFLICT"
>> +elif rdma_ns "$NS1" dev set "$RXE_SAME" netns "$NS2" >/dev/null 2>&1; then
>> + ktap_test_fail "$TEST_MOVE_CONFLICT"
>> +elif rdma_dev_exists "$NS1" "$RXE_SAME" &&
>> + rdma_dev_exists "$NS2" "$RXE_SAME"; then
>> + ktap_test_pass "$TEST_MOVE_CONFLICT"
>> +else
>> + ktap_test_fail "$TEST_MOVE_CONFLICT"
>> +fi
>> +cleanup_devs
>> +
>> +if ! setup_devs; then
>> + ktap_test_fail "$TEST_MOVE_RENAME"
>> +elif rdma dev set "$RXE_A" netns "$NS2" &&
>> + rdma_ns "$NS2" dev set "$RXE_A" name "$RXE_NEW"; then
>> + if rdma_dev_exists "$NS2" "$RXE_NEW" &&
>> + ! rdma_dev_exists "" "$RXE_A"; then
>> + ktap_test_pass "$TEST_MOVE_RENAME"
>> + else
>> + ktap_test_fail "$TEST_MOVE_RENAME"
>> + fi
>> +else
>> + ktap_test_fail "$TEST_MOVE_RENAME"
>> +fi
>> +cleanup_devs
>> +
>> +if ! rdma_supports_combined_move_rename; then
>> + ktap_test_skip "$TEST_COMBINED_MOVE_RENAME"
>> +elif ! setup_devs; then
>> + ktap_test_fail "$TEST_COMBINED_MOVE_RENAME"
>> +elif rdma dev set "$RXE_A" netns "$NS2" name "$RXE_NEW"; then
>> + if rdma_dev_exists "$NS2" "$RXE_NEW" &&
>> + ! rdma_dev_exists "" "$RXE_A"; then
>> + ktap_test_pass "$TEST_COMBINED_MOVE_RENAME"
>> + else
>> + ktap_test_fail "$TEST_COMBINED_MOVE_RENAME"
>> + fi
>> +else
>> + ktap_test_fail "$TEST_COMBINED_MOVE_RENAME"
>> +fi
>> +cleanup_devs
>> +
>> +if ! setup_devs; then
>> + ktap_test_fail "$TEST_SAME_NETNS_DUP_RENAME"
>> +elif rdma dev set "$RXE_A" name "$RXE_SAME" &&
>> + rdma dev set "$RXE_B" name "$RXE_NEW"; then
>> + if rdma dev set "$RXE_A" name "$RXE_NEW" >/dev/null 2>&1; then
>> + ktap_test_fail "$TEST_SAME_NETNS_DUP_RENAME"
>> + elif rdma_dev_exists "" "$RXE_SAME" &&
>> + rdma_dev_exists "" "$RXE_NEW"; then
>> + ktap_test_pass "$TEST_SAME_NETNS_DUP_RENAME"
>> + else
>> + ktap_test_fail "$TEST_SAME_NETNS_DUP_RENAME"
>> + fi
>> +else
>> + ktap_test_fail "$TEST_SAME_NETNS_DUP_RENAME"
>> +fi
>> +cleanup_devs
>> +
>> +if ! setup_devs; then
>> + ktap_test_fail "$TEST_TEARDOWN_RETURN"
>> +elif ! rdma dev set "$RXE_A" name "$RXE_SAME" ||
>> + ! rdma dev set "$RXE_B" netns "$NS2" ||
>> + ! rdma_ns "$NS2" dev set "$RXE_B" name "$RXE_SAME" ||
>> + ! rdma_dev_exists "$NS2" "$RXE_SAME"; then
>> + ktap_test_fail "$TEST_TEARDOWN_RETURN"
>> +else
>> + ip netns del "$NS2"
>> + returned=$(wait_rdma_dev_on_netdev "$DUMMY_B")
>> + ktap_print_msg "device returned to init_net as '${returned:-<missing>}'"
>> + if rdma_dev_exists "" "$RXE_SAME" &&
>> + [ -n "$returned" ] &&
>> + [ "$returned" != "$RXE_SAME" ] &&
>> + [ "${returned#ibdev}" != "$returned" ]; then
>> + ktap_test_pass "$TEST_TEARDOWN_RETURN"
>> + else
>> + ktap_test_fail "$TEST_TEARDOWN_RETURN"
>> + fi
>> +fi
>> +cleanup_devs
>> +
>> +ktap_finished
>
^ permalink raw reply
* Re: [PATCH] [RFC] net-ipv6: ipv4 emulation support
From: Maciej Żenczykowski @ 2026-07-13 9:05 UTC (permalink / raw)
To: Maciej Żenczykowski
Cc: Linux Network Development Mailing List,
Fernando Fernandez Mancera
In-Reply-To: <20260713081606.950892-1-maze@google.com>
Note that this is absolutely ancient - it's a patch from March 2012
against 3.18 ;-)
But maybe something like this could be pursued?
From what I (vaguely) remember I was able to boot my linux workstation
to a working GUI with this.
On Mon, Jul 13, 2026 at 10:16 AM Maciej Żenczykowski <maze@google.com> wrote:
>
> Change-Id: Ibab1a59f4730f3fd0a7d14fffc5100a02a170ac6
> ---
> gconfigs/Gconfig.net-configs.ipv6 | 2 +
> include/linux/ipv6.h | 6 +-
> include/linux/socket.h | 5 +-
> include/net/compat.h | 2 +-
> net/compat.c | 6 +-
> net/core/iovec.c | 4 +-
> net/ipv6/Kconfig | 19 +++
> net/ipv6/af_inet6.c | 5 +
> net/ipv6/ipv6_sockglue.c | 8 +-
> net/ipv6/sysctl_net_ipv6.c | 26 ++++
> net/socket.c | 211 +++++++++++++++++++++++++++---
> 11 files changed, 260 insertions(+), 34 deletions(-)
> create mode 100644 gconfigs/Gconfig.net-configs.ipv6
>
> diff --git a/gconfigs/Gconfig.net-configs.ipv6 b/gconfigs/Gconfig.net-configs.ipv6
> new file mode 100644
> index 000000000000..9eb5694bae8e
> --- /dev/null
> +++ b/gconfigs/Gconfig.net-configs.ipv6
> @@ -0,0 +1,2 @@
> +CONFIG_IPV6_IPV4_EMULATE=y
> +CONFIG_IPV6_IPV4_EMULATE_SYSCTL_DEFAULT=y
> diff --git a/include/linux/ipv6.h b/include/linux/ipv6.h
> index ff560537dd61..8bc5306094b5 100644
> --- a/include/linux/ipv6.h
> +++ b/include/linux/ipv6.h
> @@ -157,11 +157,13 @@ struct ipv6_pinfo {
> #if defined(__BIG_ENDIAN_BITFIELD)
> /* Packed in 16bits. */
> __s16 mcast_hops:9;
> - __u16 __unused_2:6,
> + __u16 __unused_2:5,
> + ipv4emu:1,
> mc_loop:1;
> #else
> __u16 mc_loop:1,
> - __unused_2:6;
> + ipv4emu:1,
> + __unused_2:5;
> __s16 mcast_hops:9;
> #endif
> int ucast_oif;
> diff --git a/include/linux/socket.h b/include/linux/socket.h
> index bb9b83640070..4ae959a3443b 100644
> --- a/include/linux/socket.h
> +++ b/include/linux/socket.h
> @@ -8,6 +8,7 @@
> #include <linux/types.h> /* pid_t */
> #include <linux/compiler.h> /* __user */
> #include <uapi/linux/socket.h>
> +struct sock; /* from net/sock.h */
>
> struct pid;
> struct cred;
> @@ -312,8 +313,8 @@ extern int csum_partial_copy_fromiovecend(unsigned char *kdata,
> extern unsigned long iov_pages(const struct iovec *iov, int offset,
> unsigned long nr_segs);
>
> -extern int verify_iovec(struct msghdr *m, struct iovec *iov, struct sockaddr_storage *address, int mode);
> -extern int move_addr_to_kernel(void __user *uaddr, int ulen, struct sockaddr_storage *kaddr);
> +extern int verify_iovec(struct sock *sk, struct msghdr *m, struct iovec *iov, struct sockaddr_storage *address, int mode);
> +extern int move_addr_to_kernel(struct sock *sk, void __user *uaddr, int *ulen, struct sockaddr_storage *kaddr);
> extern int put_cmsg(struct msghdr*, int level, int type, int len, void *data);
>
> struct timespec;
> diff --git a/include/net/compat.h b/include/net/compat.h
> index 3b603b199c01..b9ee7aa5a31f 100644
> --- a/include/net/compat.h
> +++ b/include/net/compat.h
> @@ -41,7 +41,7 @@ int compat_sock_get_timestampns(struct sock *, struct timespec __user *);
> #endif /* defined(CONFIG_COMPAT) */
>
> int get_compat_msghdr(struct msghdr *, struct compat_msghdr __user *);
> -int verify_compat_iovec(struct msghdr *, struct iovec *,
> +int verify_compat_iovec(struct sock *, struct msghdr *, struct iovec *,
> struct sockaddr_storage *, int);
> asmlinkage long compat_sys_sendmsg(int, struct compat_msghdr __user *,
> unsigned int);
> diff --git a/net/compat.c b/net/compat.c
> index bc8aeefddf3f..f66bd6552204 100644
> --- a/net/compat.c
> +++ b/net/compat.c
> @@ -80,15 +80,15 @@ int get_compat_msghdr(struct msghdr *kmsg, struct compat_msghdr __user *umsg)
> }
>
> /* I've named the args so it is easy to tell whose space the pointers are in. */
> -int verify_compat_iovec(struct msghdr *kern_msg, struct iovec *kern_iov,
> +int verify_compat_iovec(struct sock *sk, struct msghdr *kern_msg, struct iovec *kern_iov,
> struct sockaddr_storage *kern_address, int mode)
> {
> int tot_len;
>
> if (kern_msg->msg_name && kern_msg->msg_namelen) {
> if (mode == VERIFY_READ) {
> - int err = move_addr_to_kernel(kern_msg->msg_name,
> - kern_msg->msg_namelen,
> + int err = move_addr_to_kernel(sk, kern_msg->msg_name,
> + &kern_msg->msg_namelen,
> kern_address);
> if (err < 0)
> return err;
> diff --git a/net/core/iovec.c b/net/core/iovec.c
> index e1ec45ab1e63..a7a393cae2a7 100644
> --- a/net/core/iovec.c
> +++ b/net/core/iovec.c
> @@ -35,7 +35,7 @@
> * in any case.
> */
>
> -int verify_iovec(struct msghdr *m, struct iovec *iov, struct sockaddr_storage *address, int mode)
> +int verify_iovec(struct sock *sk, struct msghdr *m, struct iovec *iov, struct sockaddr_storage *address, int mode)
> {
> int size, ct, err;
>
> @@ -43,7 +43,7 @@ int verify_iovec(struct msghdr *m, struct iovec *iov, struct sockaddr_storage *a
> if (mode == VERIFY_READ) {
> void __user *namep;
> namep = (void __user __force *) m->msg_name;
> - err = move_addr_to_kernel(namep, m->msg_namelen,
> + err = move_addr_to_kernel(sk, namep, &m->msg_namelen,
> address);
> if (err < 0)
> return err;
> diff --git a/net/ipv6/Kconfig b/net/ipv6/Kconfig
> index 438a73aa777c..69924a6c2257 100644
> --- a/net/ipv6/Kconfig
> +++ b/net/ipv6/Kconfig
> @@ -21,6 +21,25 @@ menuconfig IPV6
>
> if IPV6
>
> +config IPV6_IPV4_EMULATE
> + bool "IPv6: IPv4 socket interface emulation support"
> + default n
> + depends on IPV6=y
> + ---help---
> + Whether to include support code which allows using a kernel IPv6
> + socket from userspace as if it was an IPv4 socket.
> +
> + ie. this is a userspace-kernel IP address translation shim layer.
> +
> +config IPV6_IPV4_EMULATE_SYSCTL_DEFAULT
> + bool "IPv6: IPv4 socket emulation - default sysctl setting"
> + default n
> + depends on IPV6_IPV4_EMULATE
> + ---help---
> + Default value for /proc/sys/net/ipv6/ipv4_emulation
> + The content of this sysctl file determines whether userspace asking
> + for IPv4 sockets gets IPv6 sockets with IPv4 emulation turned on.
> +
> config IPV6_ROUTER_PREF
> bool "IPv6: Router Preference (RFC 4191) support"
> ---help---
> diff --git a/net/ipv6/af_inet6.c b/net/ipv6/af_inet6.c
> index e8c4400f23e9..4f08abdf06e0 100644
> --- a/net/ipv6/af_inet6.c
> +++ b/net/ipv6/af_inet6.c
> @@ -476,6 +476,11 @@ int inet6_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg)
> struct sock *sk = sock->sk;
> struct net *net = sock_net(sk);
>
> +#if defined(CONFIG_IPV6_IPV4_EMULATE)
> + if (inet6_sk(sk)->ipv4emu)
> + return inet_ioctl(sock, cmd, arg);
> +#endif
> +
> switch (cmd) {
> case SIOCGSTAMP:
> return sock_get_timestamp(sk, (struct timeval __user *)arg);
> diff --git a/net/ipv6/ipv6_sockglue.c b/net/ipv6/ipv6_sockglue.c
> index e1a9583bb419..2e97c6d55b62 100644
> --- a/net/ipv6/ipv6_sockglue.c
> +++ b/net/ipv6/ipv6_sockglue.c
> @@ -857,7 +857,7 @@ int ipv6_setsockopt(struct sock *sk, int level, int optname,
> if (level == SOL_IP && sk->sk_type != SOCK_RAW)
> return udp_prot.setsockopt(sk, level, optname, optval, optlen);
>
> - if (level != SOL_IPV6)
> + if ((level != SOL_IPV6) || (inet6_sk(sk)->ipv4emu))
> return -ENOPROTOOPT;
>
> err = do_ipv6_setsockopt(sk, level, optname, optval, optlen);
> @@ -888,7 +888,7 @@ int compat_ipv6_setsockopt(struct sock *sk, int level, int optname,
> return udp_prot.setsockopt(sk, level, optname, optval, optlen);
> }
>
> - if (level != SOL_IPV6)
> + if ((level != SOL_IPV6) || (inet6_sk(sk)->ipv4emu))
> return -ENOPROTOOPT;
>
> if (optname >= MCAST_JOIN_GROUP && optname <= MCAST_MSFILTER)
> @@ -1297,7 +1297,7 @@ int ipv6_getsockopt(struct sock *sk, int level, int optname,
> if (level == SOL_IP && sk->sk_type != SOCK_RAW)
> return udp_prot.getsockopt(sk, level, optname, optval, optlen);
>
> - if (level != SOL_IPV6)
> + if ((level != SOL_IPV6) || (inet6_sk(sk)->ipv4emu))
> return -ENOPROTOOPT;
>
> err = do_ipv6_getsockopt(sk, level, optname, optval, optlen, 0);
> @@ -1334,7 +1334,7 @@ int compat_ipv6_getsockopt(struct sock *sk, int level, int optname,
> return udp_prot.getsockopt(sk, level, optname, optval, optlen);
> }
>
> - if (level != SOL_IPV6)
> + if ((level != SOL_IPV6) || (inet6_sk(sk)->ipv4emu))
> return -ENOPROTOOPT;
>
> if (optname == MCAST_MSFILTER)
> diff --git a/net/ipv6/sysctl_net_ipv6.c b/net/ipv6/sysctl_net_ipv6.c
> index c5c10fafcfe2..0ab6a8d5aa57 100644
> --- a/net/ipv6/sysctl_net_ipv6.c
> +++ b/net/ipv6/sysctl_net_ipv6.c
> @@ -6,6 +6,7 @@
> */
>
> #include <linux/mm.h>
> +#include <linux/module.h>
> #include <linux/sysctl.h>
> #include <linux/in6.h>
> #include <linux/ipv6.h>
> @@ -18,6 +19,20 @@
>
> static int one = 1;
>
> +#if defined(CONFIG_IPV6_IPV4_EMULATE)
> +static int zero;
> +
> +# if defined(CONFIG_IPV6_IPV4_EMULATE_SYSCTL_DEFAULT)
> +bool sysctl_ipv4_emulation = true;
> +# else
> +bool sysctl_ipv4_emulation = false;
> +# endif
> +
> +module_param_named(ipv4_emulation, sysctl_ipv4_emulation, bool, S_IRUGO|S_IWUSR);
> +MODULE_PARM_DESC(ipv4_emulation,
> + "Auto promote PF_INET sockets into PF_INET6 sockets, with IPv4 API emulation.");
> +#endif
> +
> static struct ctl_table ipv6_table_template[] = {
> {
> .procname = "bindv6only",
> @@ -73,6 +88,17 @@ static struct ctl_table ipv6_rotable[] = {
> .proc_handler = proc_dointvec_minmax,
> .extra1 = &one
> },
> +#if defined(CONFIG_IPV6_IPV4_EMULATE)
> + {
> + .procname = "ipv4_emulation",
> + .data = &sysctl_ipv4_emulation,
> + .maxlen = sizeof(bool),
> + .mode = 0644,
> + .proc_handler = proc_dointvec_minmax,
> + .extra1 = &zero,
> + .extra2 = &one,
> + },
> +#endif
> { }
> };
>
> diff --git a/net/socket.c b/net/socket.c
> index fe20c319a0bb..e7d2f5281ff3 100644
> --- a/net/socket.c
> +++ b/net/socket.c
> @@ -101,6 +101,7 @@
> #include <linux/netfilter.h>
>
> #include <linux/if_tun.h>
> +#include <linux/ipv6.h>
> #include <linux/ipv6_route.h>
> #include <linux/route.h>
> #include <linux/sockios.h>
> @@ -178,30 +179,171 @@ static DEFINE_PER_CPU(int, sockets_in_use);
> * divide and look after the messy bits.
> */
>
> +static int ipv6_promote_ipv4_address(struct sock *sk,
> + struct sockaddr_storage * kaddr, int * addr_len)
> +{
> +#if defined(CONFIG_IPV6_IPV4_EMULATE)
> + if (!sk) {
> + pr_warning("ipv6_promote_ipv4_address: no sk\n");
> + return 0;
> + }
> +
> + if (sk->sk_family != PF_INET6) {
> + //pr_warning("ipv6_promote_ipv4_address: sk_family == %d\n", sk->sk_family);
> + return 0;
> + }
> +
> + if (!inet6_sk(sk)->ipv4emu) {
> + //pr_warning("ipv6_promote_ipv4_address: not ipv4emu\n");
> + return 0;
> + }
> +
> + if (kaddr->ss_family == AF_UNSPEC) {
> + //pr_warning("ipv6_promote_ipv4_address: unspec\n");
> + return 0;
> + }
> +
> + if (kaddr->ss_family != AF_INET) {
> + pr_warning("ipv6_promote_ipv4_address: addr is %d\n", kaddr->ss_family);
> + return -EAFNOSUPPORT;
> + }
> +
> + if (*addr_len < sizeof(struct sockaddr_in)) {
> + pr_warning("ipv6_promote_ipv4_address: addr_len == %d\n", *addr_len);
> + return -EINVAL;
> + }
> +
> + {
> + const struct sockaddr_in * kaddr4 = (const struct sockaddr_in *)kaddr;
> + struct sockaddr_in6 * kaddr6 = (struct sockaddr_in6 *)kaddr;
> + __be16 port = kaddr4->sin_port;
> + __be32 ipv4 = kaddr4->sin_addr.s_addr;
> +
> + //*addr_len = sizeof(struct sockaddr_in6);
> + *addr_len = 24; //SIN6_LEN_RFC2133; /* 24 = 2+2+4+16, ie. without scope id */
> +// Why not fill in scope_id with 0 ?
> +// One way to avoid the magic 24 constant is to use offsetof(struct sockaddr_in6, scope_id)
> + kaddr6->sin6_family = AF_INET6;
> + kaddr6->sin6_port = port;
> + kaddr6->sin6_flowinfo = htonl(0);
> + kaddr6->sin6_addr.s6_addr32[0] = htonl(0);
> + kaddr6->sin6_addr.s6_addr32[1] = htonl(0);
> + kaddr6->sin6_addr.s6_addr32[2] = ntohl(ipv4) ? htonl(0xFFFF) : htonl(0);
> + kaddr6->sin6_addr.s6_addr32[3] = ipv4;
> + kaddr6->sin6_scope_id = 0;
> +
> + pr_debug("ipv6_promote_ipv4_address: ok [%08X:%d]\n", ntohl(ipv4), ntohs(port));
> + }
> +#endif
> + return 0;
> +}
> +
> +static int ipv6_demote_ipv6_address(struct sock *sk, struct sockaddr_storage * kaddr, int * addr_len) {
> +#if defined(CONFIG_IPV6_IPV4_EMULATE)
> + if (!sk) {
> + pr_warning("ipv6_demote_ipv6_address: no sk\n");
> + return 0;
> + }
> +
> + if (sk->sk_family != PF_INET6) {
> + //pr_warning("ipv6_demote_ipv6_address: sk_family == %d\n", sk->sk_family);
> + return 0;
> + }
> +
> + if (!inet6_sk(sk)->ipv4emu) {
> + //pr_warning("ipv6_demote_ipv6_address: not ipv4emu\n");
> + return 0;
> + }
> +
> + if (kaddr->ss_family != AF_INET6) {
> + pr_warning("ipv6_demote_ipv6_address: addr is %d\n", kaddr->ss_family);
> + return -EAFNOSUPPORT;
> + }
> +
> + if (*addr_len < sizeof(struct sockaddr_in6)) {
> + pr_warning("ipv6_demote_ipv6_address: addr_len == %d\n", *addr_len);
> + return -EINVAL;
> + }
> +
> + {
> + struct sockaddr_in6 const * kaddr6 = (struct sockaddr_in6 const *)kaddr;
> + struct sockaddr_in * kaddr4 = (struct sockaddr_in *)kaddr;
> + __be16 port = kaddr6->sin6_port;
> + __be32 ipv6a = kaddr6->sin6_addr.s6_addr32[0];
> + __be32 ipv6b = kaddr6->sin6_addr.s6_addr32[1];
> + __be32 ipv6c = kaddr6->sin6_addr.s6_addr32[2];
> + __be32 ipv4 = kaddr6->sin6_addr.s6_addr32[3];
> +
> +// please use ipv6_addr_v4mapped()
> +
> + if (ipv6a) {
> + pr_warning("ipv6_demote_ipv6_address: not mapped [1]\n");
> + return -EINVAL;
> + }
> +
> + if (ipv6b) {
> + pr_warning("ipv6_demote_ipv6_address: not mapped [2]\n");
> + return -EINVAL;
> + }
> +
> + if ((ipv6c != htonl(0)) && (ipv6c != htonl(0xFFFF))) {
> + pr_warning("ipv6_demote_ipv6_address: not mapped [3]\n");
> + return -EINVAL;
> + }
> +
> + if ((ipv6c == htonl(0)) && (ipv4 != htonl(0))) {
> + pr_warning("ipv6_demote_ipv6_address: not mapped [4]\n");
> + return -EINVAL;
> + }
> +
> +// Really avoid those magic values...
> +// Since I doubt this is performance critical, just do the whole struct clear at the beginning:
> +// memset(uaddr4, 0, sizeof(*uaddr4));
> +
> + *addr_len = sizeof(struct sockaddr_in); // 2 + 2 + 4 + 8 padding
> + kaddr4->sin_family = AF_INET;
> + kaddr4->sin_port = port;
> + kaddr4->sin_addr.s_addr = ipv4;
> + memset(kaddr4->sin_zero, 0, 8);
> +
> + pr_debug("ipv6_demote_ipv6_address: ok [%08X:%d]\n", ntohl(ipv4), ntohs(port));
> + }
> +#endif
> + return 0;
> +}
> +
> /**
> * move_addr_to_kernel - copy a socket address into kernel space
> + * @sk: socket which is receiving the address (or NULL)
> * @uaddr: Address in user space
> * @kaddr: Address in kernel space
> - * @ulen: Length in user space
> + * @ulen: pointer to user space length
> *
> * The address is copied into kernel space. If the provided address is
> * too long an error code of -EINVAL is returned. If the copy gives
> * invalid addresses -EFAULT is returned. On a success 0 is returned.
> */
>
> -int move_addr_to_kernel(void __user *uaddr, int ulen, struct sockaddr_storage *kaddr)
> +int move_addr_to_kernel(struct sock *sk, void __user *uaddr, int *ulen,
> + struct sockaddr_storage *kaddr)
> {
> - if (ulen < 0 || ulen > sizeof(struct sockaddr_storage))
> + int err;
> +
> + if (*ulen < 0 || *ulen > sizeof(struct sockaddr_storage))
> return -EINVAL;
> - if (ulen == 0)
> + if (*ulen == 0)
> return 0;
> - if (copy_from_user(kaddr, uaddr, ulen))
> + if (copy_from_user(kaddr, uaddr, *ulen))
> return -EFAULT;
> - return audit_sockaddr(ulen, kaddr);
> + err = audit_sockaddr(*ulen, kaddr);
> + if (err)
> + return err;
> + return ipv6_promote_ipv4_address(sk, kaddr, ulen);
> }
>
> /**
> * move_addr_to_user - copy an address to user space
> + * @sk: socket which is generating the address (or NULL)
> * @kaddr: kernel space address
> * @klen: length of address in kernel
> * @uaddr: user space address
> @@ -217,7 +359,7 @@ int move_addr_to_kernel(void __user *uaddr, int ulen, struct sockaddr_storage *k
> * specified. Zero is returned for a success.
> */
>
> -static int move_addr_to_user(struct sockaddr_storage *kaddr, int klen,
> +static int move_addr_to_user(struct sock *sk, struct sockaddr_storage *kaddr, int klen,
> void __user *uaddr, int __user *ulen)
> {
> int err;
> @@ -225,6 +367,9 @@ static int move_addr_to_user(struct sockaddr_storage *kaddr, int klen,
>
> BUG_ON(klen > sizeof(struct sockaddr_storage));
> err = get_user(len, ulen);
> + if (err)
> + return err;
> + err = ipv6_demote_ipv6_address(sk, kaddr, &klen);
> if (err)
> return err;
> if (len > klen)
> @@ -1249,12 +1394,19 @@ call_kill:
> }
> EXPORT_SYMBOL(sock_wake_async);
>
> +#if defined(CONFIG_IPV6_IPV4_EMULATE)
> +extern bool sysctl_ipv4_emulation;
> +#endif
> +
> int __sock_create(struct net *net, int family, int type, int protocol,
> struct socket **res, int kern)
> {
> int err;
> struct socket *sock;
> const struct net_proto_family *pf;
> +#if defined(CONFIG_IPV6_IPV4_EMULATE)
> + bool ipv4emu = false;
> +#endif
>
> /*
> * Check protocol is in range
> @@ -1283,6 +1435,13 @@ int __sock_create(struct net *net, int family, int type, int protocol,
> if (err)
> return err;
>
> +#if defined(CONFIG_IPV6_IPV4_EMULATE)
> + if (sysctl_ipv4_emulation && !kern && family == PF_INET) {
> + family = PF_INET6;
> + ipv4emu = true;
> + }
> +#endif
> +
> /*
> * Allocate the socket and allow the family to set things up. if
> * the protocol is 0, the family is instructed to select an appropriate
> @@ -1328,6 +1487,14 @@ int __sock_create(struct net *net, int family, int type, int protocol,
> if (err < 0)
> goto out_module_put;
>
> +#if defined(CONFIG_IPV6_IPV4_EMULATE)
> + if (ipv4emu) {
> + inet6_sk(sock->sk)->ipv4emu = 1;
> + /* we need to clear ipv6only, since it could have defaulted to true via sysctl */
> + sock->sk->sk_ipv6only = 0;
> + }
> +#endif
> +
> /*
> * Now to bump the refcnt of the [loadable] module that owns this
> * socket at sock_release time we decrement its refcnt.
> @@ -1532,7 +1699,7 @@ SYSCALL_DEFINE3(bind, int, fd, struct sockaddr __user *, umyaddr, int, addrlen)
>
> sock = sockfd_lookup_light(fd, &err, &fput_needed);
> if (sock) {
> - err = move_addr_to_kernel(umyaddr, addrlen, &address);
> + err = move_addr_to_kernel(sock->sk, umyaddr, &addrlen, &address);
> if (err >= 0) {
> err = security_socket_bind(sock,
> (struct sockaddr *)&address,
> @@ -1646,7 +1813,7 @@ SYSCALL_DEFINE4(accept4, int, fd, struct sockaddr __user *, upeer_sockaddr,
> err = -ECONNABORTED;
> goto out_fd;
> }
> - err = move_addr_to_user(&address,
> + err = move_addr_to_user(newsock->sk, &address,
> len, upeer_sockaddr, upeer_addrlen);
> if (err < 0)
> goto out_fd;
> @@ -1695,7 +1862,7 @@ SYSCALL_DEFINE3(connect, int, fd, struct sockaddr __user *, uservaddr,
> sock = sockfd_lookup_light(fd, &err, &fput_needed);
> if (!sock)
> goto out;
> - err = move_addr_to_kernel(uservaddr, addrlen, &address);
> + err = move_addr_to_kernel(sock->sk, uservaddr, &addrlen, &address);
> if (err < 0)
> goto out_put;
>
> @@ -1735,7 +1902,7 @@ SYSCALL_DEFINE3(getsockname, int, fd, struct sockaddr __user *, usockaddr,
> err = sock->ops->getname(sock, (struct sockaddr *)&address, &len, 0);
> if (err)
> goto out_put;
> - err = move_addr_to_user(&address, len, usockaddr, usockaddr_len);
> + err = move_addr_to_user(sock->sk, &address, len, usockaddr, usockaddr_len);
>
> out_put:
> fput_light(sock->file, fput_needed);
> @@ -1767,7 +1934,7 @@ SYSCALL_DEFINE3(getpeername, int, fd, struct sockaddr __user *, usockaddr,
> sock->ops->getname(sock, (struct sockaddr *)&address, &len,
> 1);
> if (!err)
> - err = move_addr_to_user(&address, len, usockaddr,
> + err = move_addr_to_user(sock->sk, &address, len, usockaddr,
> usockaddr_len);
> fput_light(sock->file, fput_needed);
> }
> @@ -1806,7 +1973,7 @@ SYSCALL_DEFINE6(sendto, int, fd, void __user *, buff, size_t, len,
> msg.msg_controllen = 0;
> msg.msg_namelen = 0;
> if (addr) {
> - err = move_addr_to_kernel(addr, addr_len, &address);
> + err = move_addr_to_kernel(sock->sk, addr, &addr_len, &address);
> if (err < 0)
> goto out_put;
> msg.msg_name = (struct sockaddr *)&address;
> @@ -1871,7 +2038,7 @@ SYSCALL_DEFINE6(recvfrom, int, fd, void __user *, ubuf, size_t, size,
> err = sock_recvmsg(sock, &msg, size, flags);
>
> if (err >= 0 && addr != NULL) {
> - err2 = move_addr_to_user(&address,
> + err2 = move_addr_to_user(sock->sk, &address,
> msg.msg_namelen, addr, addr_len);
> if (err2 < 0)
> err = err2;
> @@ -2042,9 +2209,9 @@ static int ___sys_sendmsg(struct socket *sock, struct msghdr __user *msg,
>
> /* This will also move the address data into kernel space */
> if (MSG_CMSG_COMPAT & flags) {
> - err = verify_compat_iovec(msg_sys, iov, &address, VERIFY_READ);
> + err = verify_compat_iovec(sock->sk, msg_sys, iov, &address, VERIFY_READ);
> } else
> - err = verify_iovec(msg_sys, iov, &address, VERIFY_READ);
> + err = verify_iovec(sock->sk, msg_sys, iov, &address, VERIFY_READ);
> if (err < 0)
> goto out_freeiov;
> total_len = err;
> @@ -2258,9 +2425,9 @@ static int ___sys_recvmsg(struct socket *sock, struct msghdr __user *msg,
> uaddr = (__force void __user *)msg_sys->msg_name;
> uaddr_len = COMPAT_NAMELEN(msg);
> if (MSG_CMSG_COMPAT & flags)
> - err = verify_compat_iovec(msg_sys, iov, &addr, VERIFY_WRITE);
> + err = verify_compat_iovec(sock->sk, msg_sys, iov, &addr, VERIFY_WRITE);
> else
> - err = verify_iovec(msg_sys, iov, &addr, VERIFY_WRITE);
> + err = verify_iovec(sock->sk, msg_sys, iov, &addr, VERIFY_WRITE);
> if (err < 0)
> goto out_freeiov;
> total_len = err;
> @@ -2280,7 +2447,7 @@ static int ___sys_recvmsg(struct socket *sock, struct msghdr __user *msg,
> len = err;
>
> if (uaddr != NULL) {
> - err = move_addr_to_user(&addr,
> + err = move_addr_to_user(sock->sk, &addr,
> msg_sys->msg_namelen, uaddr,
> uaddr_len);
> if (err < 0)
> @@ -3159,7 +3326,11 @@ static int routing_ioctl(struct net *net, struct socket *sock,
> u32 rtdev;
> mm_segment_t old_fs = get_fs();
>
> - if (sock && sock->sk && sock->sk->sk_family == AF_INET6) { /* ipv6 */
> + if (sock && sock->sk && sock->sk->sk_family == AF_INET6
> +#if defined(CONFIG_IPV6_IPV4_EMULATE)
> + && !inet6_sk(sock->sk)->ipv4emu
> +#endif
> + ) { /* ipv6 */
> struct in6_rtmsg32 __user *ur6 = argp;
> ret = copy_from_user(&r6.rtmsg_dst, &(ur6->rtmsg_dst),
> 3 * sizeof(struct in6_addr));
> --
> 2.55.0.795.g602f6c329a-goog
>
--
Maciej Żenczykowski, Kernel Networking Developer @ Google
^ permalink raw reply
* Re: [PATCH net v2] ice: propagate ETH56G deskew read errors
From: Simon Horman @ 2026-07-13 9:16 UTC (permalink / raw)
To: pengpeng
Cc: Simon Horman, anthony.l.nguyen, przemyslaw.kitszel,
jedrzej.jagielski, aleksandr.loktionov, andrew+netdev, davem,
edumazet, kuba, pabeni, richardcochran, jacob.e.keller,
arkadiusz.kubalewski, karol.kolacinski, sergey.temerkhanov,
michal.michalik, intel-wired-lan, netdev, linux-kernel
In-Reply-To: <20260706144325.91320-1-pengpeng@iscas.ac.cn>
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
---
ice: propagate ETH56G deskew read errors
ice_ptp_calc_deskew_eth56g() returns a u32 deskew value, but it also
returns the negative read_poll_timeout() error when the DESKEW valid bit
never appears. That converts the negative error into a large unsigned
deskew contribution, which can then be folded into the RX timestamp
offset and programmed into hardware.
Return the deskew value through an output parameter and propagate the
read error from ice_phy_set_offsets_eth56g() instead of using it as
offset data.
> diff --git a/drivers/net/ethernet/intel/ice/ice_ptp_hw.c b/drivers/net/ethernet/intel/ice/ice_ptp_hw.c
> index 8e5f97835954c..cc424518a2a67 100644
> --- a/drivers/net/ethernet/intel/ice/ice_ptp_hw.c
> +++ b/drivers/net/ethernet/intel/ice/ice_ptp_hw.c
[ ... ]
> @@ -1736,17 +1736,21 @@ static u32 ice_ptp_calc_bitslip_eth56g(struct ice_hw *hw, u8 port, u32 bs,
> * @ds: deskew multiplier
> * @rs: RS-FEC enabled
> * @spd: link speed
> + * @deskew: output parameter for the calculated deskew value
> *
> - * Return: calculated deskew value
> + * Return: 0 on success, negative error code otherwise
> */
> -static u32 ice_ptp_calc_deskew_eth56g(struct ice_hw *hw, u8 port, u32 ds,
> - bool rs, enum ice_eth56g_link_spd spd)
> +static int ice_ptp_calc_deskew_eth56g(struct ice_hw *hw, u8 port, u32 ds,
> + bool rs, enum ice_eth56g_link_spd spd,
> + u32 *deskew)
> {
> u32 deskew_i, deskew_f;
> int err;
>
> - if (!ds)
> + if (!ds) {
> + *deskew = 0;
> return 0;
> + }
>
> read_poll_timeout(ice_read_ptp_reg_eth56g, err,
> FIELD_GET(PHY_REG_DESKEW_0_VALID, deskew_i), 500,
> 50 * USEC_PER_MSEC, false, hw, port, PHY_REG_DESKEW_0,
> &deskew_i);
[Severity: High]
This is a pre-existing issue, but is the return value of read_poll_timeout()
supposed to be checked here?
The read_poll_timeout() macro returns -ETIMEDOUT if the condition is never
met, but this return value doesn't appear to be assigned to any variable.
The err variable will only hold the result of the inner function
ice_read_ptp_reg_eth56g(), which might be 0 if the read succeeds but the
VALID bit is not set.
Will this cause the function to proceed as if successful, ignoring the timeout?
[Severity: Medium]
This is a pre-existing issue, but could deskew_i be read uninitialized if
ice_read_ptp_reg_eth56g() fails?
If the read fails, err receives the error code, but deskew_i may remain
uninitialized. The loop condition in read_poll_timeout():
FIELD_GET(PHY_REG_DESKEW_0_VALID, deskew_i)
will evaluate this uninitialized memory.
Additionally, since the loop condition doesn't check if err == 0, won't
this pointlessly spin for 50ms on hard read failures instead of aborting
early?
> if (err)
> return err;
^ permalink raw reply
* RE: [PATCH net] net: txgbe: fix heap overflow when reading module EEPROM
From: Jiawen Wu @ 2026-07-13 9:17 UTC (permalink / raw)
To: 'Chenguang Zhao', mengyuanlou, andrew+netdev, davem,
edumazet, kuba, pabeni
Cc: maxime.chevallier, netdev, 'Chenguang Zhao'
In-Reply-To: <20260713085111.1481884-1-chenguang.zhao@linux.dev>
> From: Chenguang Zhao <zhaochenguang@kylinos.cn>
>
> txgbe_read_eeprom_hostif() always copies round_up(length, 4) bytes
> into the caller buffer, which ethtool allocates with exactly 'length'
> bytes. A non-4-aligned length therefore causes an out-of-bounds write.
> Copy only the remaining bytes on the final dword instead.
>
> Signed-off-by: Chenguang Zhao <zhaochenguang@kylinos.cn>
> ---
> drivers/net/ethernet/wangxun/txgbe/txgbe_aml.c | 6 ++++--
> 1 file changed, 4 insertions(+), 2 deletions(-)
>
> diff --git a/drivers/net/ethernet/wangxun/txgbe/txgbe_aml.c b/drivers/net/ethernet/wangxun/txgbe/txgbe_aml.c
> index affea1a364ef..26d0cfc58ee2 100644
> --- a/drivers/net/ethernet/wangxun/txgbe/txgbe_aml.c
> +++ b/drivers/net/ethernet/wangxun/txgbe/txgbe_aml.c
> @@ -96,11 +96,13 @@ int txgbe_read_eeprom_hostif(struct wx *wx,
> dword_len = round_up(length, 4) >> 2;
>
> for (i = 0; i < dword_len; i++) {
> + u32 copy_len = min_t(u32, 4, length - i * 4);
> +
> value = rd32a(wx, WX_FW2SW_MBOX, i + offset);
> le32_to_cpus(&value);
>
> - memcpy(data, &value, 4);
> - data += 4;
> + memcpy(data, &value, copy_len);
> + data += copy_len;
> }
>
> return 0;
> --
> 2.25.1
Thanks for the fix.
Fixes: 9b97b6b5635b ("net: txgbe: support getting module EEPROM by page")
Reviewed-by: Jiawen Wu <jiawenwu@trustnetic.com>
^ permalink raw reply
* [PATCH net] net: txgbe: fix FDIR filter leak on remove
From: Chenguang Zhao @ 2026-07-13 9:19 UTC (permalink / raw)
To: jiawenwu, mengyuanlou, andrew+netdev, davem, edumazet, kuba,
pabeni
Cc: chenguang.zhao, netdev, Chenguang Zhao
From: Chenguang Zhao <zhaochenguang@kylinos.cn>
Perfect FDIR filters can be added while the interface is down and are
kept on the software list for later restore. unregister_netdev() only
calls ndo_stop when the device is up, so txgbe_fdir_filter_exit() in
txgbe_close() is skipped in that case and the filters are leaked on
driver remove. Free the filter list from txgbe_remove() as well.
Fixes: 4bdb441105dc ("net: txgbe: support Flow Director perfect filters")
Signed-off-by: Chenguang Zhao <zhaochenguang@kylinos.cn>
---
Reproduction:
1. Load the driver (interface remains down after probe).
2. Enable ntuple filters: ethtool -K <dev> ntuple on
3. Add a perfect FDIR rule while the interface is down:
ethtool -N <dev> flow-type ...
4. Keep the interface down (do not bring it up).
5. Unload the driver (rmmod / PCI unbind).
unregister_netdev() skips ndo_stop because the device is not IFF_UP,
and without freeing the software filter list in remove, each rule
leaks sizeof(struct txgbe_fdir_filter).
drivers/net/ethernet/wangxun/txgbe/txgbe_main.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/drivers/net/ethernet/wangxun/txgbe/txgbe_main.c b/drivers/net/ethernet/wangxun/txgbe/txgbe_main.c
index 20c5a295c6c2..c277863baf67 100644
--- a/drivers/net/ethernet/wangxun/txgbe/txgbe_main.c
+++ b/drivers/net/ethernet/wangxun/txgbe/txgbe_main.c
@@ -945,6 +945,7 @@ static void txgbe_remove(struct pci_dev *pdev)
netdev = wx->netdev;
wx_disable_sriov(wx);
unregister_netdev(netdev);
+ txgbe_fdir_filter_exit(wx);
timer_shutdown_sync(&wx->service_timer);
cancel_work_sync(&wx->service_task);
--
2.25.1
^ permalink raw reply related
* [PATCH iproute2-next] ip: display DPLL pin information in link details
From: Ivan Vecera @ 2026-07-13 9:21 UTC (permalink / raw)
To: netdev; +Cc: Jiri Pirko, Petr Oros, David Ahern, Stephen Hemminger
Parse the IFLA_DPLL_PIN nested attribute and display the associated
DPLL pin ID in 'ip link show' detail output. This allows users to see
which DPLL recovered clock pin is associated with a network interface
without having to cross-reference with 'dpll pin show'.
Example output:
$ ip -d link show eth0
... parentbus pci parentdev 0000:51:00.0 dpll-pin 4
Signed-off-by: Ivan Vecera <ivecera@redhat.com>
---
ip/ipaddress.c | 13 +++++++++++++
1 file changed, 13 insertions(+)
diff --git a/ip/ipaddress.c b/ip/ipaddress.c
index 6017bc8370cb..bf470961182e 100644
--- a/ip/ipaddress.c
+++ b/ip/ipaddress.c
@@ -25,6 +25,7 @@
#include <linux/if_infiniband.h>
#include <linux/sockios.h>
#include <linux/net_namespace.h>
+#include <linux/dpll.h>
#include "rt_names.h"
#include "utils.h"
@@ -1294,6 +1295,18 @@ int print_linkinfo(struct nlmsghdr *n, void *arg)
"parentdev %s ",
rta_getattr_str(tb[IFLA_PARENT_DEV_NAME]));
}
+
+ if (tb[IFLA_DPLL_PIN]) {
+ struct rtattr *dp[DPLL_A_PIN_MAX + 1];
+
+ parse_rtattr_nested(dp, DPLL_A_PIN_MAX,
+ tb[IFLA_DPLL_PIN]);
+ if (dp[DPLL_A_PIN_ID])
+ print_uint(PRINT_ANY,
+ "dpll_pin",
+ "dpll-pin %u ",
+ rta_getattr_u32(dp[DPLL_A_PIN_ID]));
+ }
}
if ((do_link || show_details) && tb[IFLA_IFALIAS]) {
--
2.53.0
^ permalink raw reply related
* Re: [PATCH net] selftests: netconsole: only restore MAC when it changed on resume
From: Breno Leitao @ 2026-07-13 9:21 UTC (permalink / raw)
To: Andre Carvalho
Cc: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Shuah Khan, netdev, linux-kselftest, linux-kernel,
matttbe
In-Reply-To: <20260710-netcons-mac-reload-v1-1-3fb1bcc70b4a@gmail.com>
Hello Andre,
Thanks for the fix.
On Fri, Jul 10, 2026 at 11:19:17PM +0100, Andre Carvalho wrote:
> The "mac" bind mode reactivation downs the interface, restores the saved
> MAC and renames it to trigger a target resume. This assumes the recreated
> interface comes back with a different MAC, which is true under
> MACAddressPolicy=none (as on the Netdev CI) but not when MACs are
> persistent. In the persistent case netconsole resumes the target on its
> own, and the down/restore/rename flow instead drops it and fails the test.
>
> Guard the block on the MAC having actually changed so the test passes
> under both policies.
>
> Fixes: 6ecc08329bab ("selftests: netconsole: validate target resume")
> Reported-by: Matthieu Baerts (NGI0) <matttbe@kernel.org>
> Closes: https://lore.kernel.org/netdev/f398373e-2cb4-4649-a491-9763df94d98b@kernel.org/
> Signed-off-by: Andre Carvalho <asantostc@gmail.com>
Reviewed-by: Breno Leitao <leitao@debian.org>
FWIW: netdev CI got yellow for this patch, but, this is not realted to this
test.
https://patchwork.kernel.org/project/netdevbpf/patch/20260710-netcons-mac-reload-v1-1-3fb1bcc70b4a@gmail.com/
^ permalink raw reply
* Re: [PATCH rdma-next 08/13] RDMA/cgroup: Scope rdma cgroup device visibility to the net namespace
From: Jiri Pirko @ 2026-07-13 9:34 UTC (permalink / raw)
To: Michal Koutný
Cc: linux-rdma, cgroups, netdev, linux-s390, linux-kselftest, jgg,
leon, parav, mbloch, cmeiohas, roman.gushchin, bvanassche,
zyjzyj2000, shuah, tj, hannes, alibuda, dust.li, sidraya, wenjia
In-Reply-To: <ak-Z071LrWhnI5lK@localhost.localdomain>
Thu, Jul 09, 2026 at 03:04:23PM +0200, mkoutny@suse.com wrote:
>Hi.
>
>On Thu, Jul 09, 2026 at 11:55:27AM +0200, Jiri Pirko <jiri@resnulli.us> wrote:
>> index 993446ab66d0..4523c1884d67 100644
>> --- a/Documentation/admin-guide/cgroup-v2.rst
>> +++ b/Documentation/admin-guide/cgroup-v2.rst
>> @@ -2752,6 +2752,13 @@ RDMA
>> The "rdma" controller regulates the distribution and accounting of
>> RDMA resources.
>>
>> +When RDMA devices are isolated per network namespace (exclusive mode),
>> +device names are unique only within a network namespace. The device lines
>> +below are therefore scoped to the reading or writing process's network
>> +namespace: only devices accessible from that namespace are listed, and a
>> +limit is applied to the device of that name in that namespace. Configure
>> +limits from the same network namespace as the workloads.
>
>OK.
>
>> --- a/include/linux/cgroup_rdma.h
>> +++ b/include/linux/cgroup_rdma.h
>> @@ -7,6 +7,7 @@
>> #define _CGROUP_RDMA_H
>>
>> #include <linux/cgroup.h>
>> +#include <net/net_namespace.h>
>>
>> enum rdmacg_resource_type {
>> RDMACG_RESOURCE_HCA_HANDLE,
>> @@ -34,6 +35,15 @@ struct rdmacg_device {
>> struct list_head dev_node;
>> struct list_head rpools;
>> char *name;
>> + /*
>> + * Net namespace the device belongs to. @netns_shared mirrors
>> + * ib_devices_shared_netns: when true the device is visible from every
>> + * net namespace (shared mode); otherwise @net is the only namespace
>> + * that may see and configure it. @netns_shared is updated when the
>> + * sharing mode changes, so use {READ,WRITE}_ONCE() to access it.
>> + */
>> + possible_net_t net;
>> + bool netns_shared;
>
>Any reason to store the netns_shared split per device? (IIUC, it's a
>global parameter.)
No reason, changed.
Thanks!
>
>Thanks,
>Michal
^ permalink raw reply
* Re: [PATCH net] igc: remove napi_synchronize() in igc_down()
From: Maciej Fijalkowski @ 2026-07-13 9:42 UTC (permalink / raw)
To: David Carlier
Cc: intel-wired-lan, netdev, anthony.l.nguyen, przemyslaw.kitszel,
aleksandr.loktionov, advoretsky, stable
In-Reply-To: <20260712132242.223254-1-devnexen@gmail.com>
On Sun, Jul 12, 2026 at 02:22:42PM +0100, David Carlier wrote:
> When an AF_XDP zero-copy application is killed abruptly, the XSK pool is
> torn down but NAPI keeps polling. igc_clean_rx_irq_zc() then returns the
> full budget on every poll, so napi_complete_done() never clears
> NAPI_STATE_SCHED.
>
> igc_down() calls napi_synchronize() before napi_disable(), so it spins
> forever waiting for that bit and the interface never goes down. Drop the
> napi_synchronize() and let napi_disable() do the job -- it sets
> NAPI_STATE_DISABLE, which forces the stuck poll to complete. Reorder it
> ahead of igc_set_queue_napi() so the NAPI mapping is cleared only after
> polling has stopped, matching the recent igb fix b1e067240379.
>
> Fixes: fc9df2a0b520 ("igc: Enable RX via AF_XDP zero-copy")
> Suggested-by: Maciej Fijalkowski <maciej.fijalkowski@intel.com>
> Cc: stable@vger.kernel.org
> Signed-off-by: David Carlier <devnexen@gmail.com>
Reviewed-by: Maciej Fijalkowski <maciej.fijalkowski@intel.com>
This is a mirror of what Alex Dvoretsky did on igb, correct? Did you
reproduce the same issue on your side or is it a blind shot at this
driver?
Regardless, I think it's a correct thing to do, but some clarification
would be nice.
> ---
> drivers/net/ethernet/intel/igc/igc_main.c | 3 +--
> 1 file changed, 1 insertion(+), 2 deletions(-)
>
> diff --git a/drivers/net/ethernet/intel/igc/igc_main.c b/drivers/net/ethernet/intel/igc/igc_main.c
> index 2c9e2dfd8499..b3883a5a7d7a 100644
> --- a/drivers/net/ethernet/intel/igc/igc_main.c
> +++ b/drivers/net/ethernet/intel/igc/igc_main.c
> @@ -5352,9 +5352,8 @@ void igc_down(struct igc_adapter *adapter)
>
> for (i = 0; i < adapter->num_q_vectors; i++) {
> if (adapter->q_vector[i]) {
> - napi_synchronize(&adapter->q_vector[i]->napi);
> - igc_set_queue_napi(adapter, i, NULL);
> napi_disable(&adapter->q_vector[i]->napi);
> + igc_set_queue_napi(adapter, i, NULL);
> }
> }
>
> --
> 2.53.0
>
^ permalink raw reply
* Re: [PATCH] net: gre: fix lltx regression for GRE tunnels with SEQ/CSUM
From: Zhou, Yun @ 2026-07-13 9:42 UTC (permalink / raw)
To: Ido Schimmel
Cc: dsahern, davem, edumazet, kuba, pabeni, horms, netdev,
linux-kernel
In-Reply-To: <20260713085325.GA472073@shredder>
On 7/13/26 16:53, Ido Schimmel wrote:
>
> Patch prefix should be [PATCH net]:
>
> https://docs.kernel.org/process/maintainer-netdev.html
>
> On Thu, Jul 09, 2026 at 06:52:43PM +0800, Yun Zhou wrote:
>> Before commit 00d066a4d4ed ("netdev_features: convert NETIF_F_LLTX to
>> dev->lltx"), NETIF_F_LLTX was set unconditionally in __gre_tunnel_init()
>> alongside GRE_FEATURES:
>>
>> dev->features |= GRE_FEATURES | NETIF_F_LLTX;
>>
>> When that commit converted NETIF_F_LLTX to the dev->lltx flag, it
>> placed 'dev->lltx = true' after the SEQ/CSUM early returns instead
>> of before them. This causes GRE/GRETAP tunnels with SEQ or CSUM+encap
>> to lose lockless TX, reintroducing _xmit_lock acquisition around their
>> ndo_start_xmit. Since GRE xmit re-enters the stack via
>> ip_tunnel_xmit(), holding _xmit_lock risks ABBA deadlock with the
>> underlay device.
>>
>> CPU0 CPU1
>> ---- ----
>> lock(&qdisc_xmit_lock_key#6);
>> lock(&qdisc_xmit_lock_key#3);
>> lock(&qdisc_xmit_lock_key#6);
>> lock(&qdisc_xmit_lock_key#3);
>>
>> Fix by moving dev->lltx = true before the early returns, restoring
>> the original unconditional behavior.
>>
>> Fixes: 00d066a4d4ed ("netdev_features: convert NETIF_F_LLTX to dev->lltx")
>
> Both sashiko instances indicate that this commit introduced the same bug
> in ip6gre_tnl_init_features(). Please perform the same fix there and
> fold it into v2.
>
Thanks a lot for your review. IPv6 indeed has the same issue. I will fix
it in v2 as you suggested.
BR,
Yun
^ permalink raw reply
* Re: [PATCH net] ila: reload IPv6 header after pskb_may_pull in checksum adjust
From: Antoine Tenart @ 2026-07-13 9:44 UTC (permalink / raw)
To: Michael Bommarito
Cc: David S . Miller, Jakub Kicinski, Eric Dumazet, Paolo Abeni,
Simon Horman, netdev, linux-kernel, stable
In-Reply-To: <20260711150648.2915106-1-michael.bommarito@gmail.com>
Hi Michael,
On Sat, Jul 11, 2026 at 11:06:48AM -0400, Michael Bommarito wrote:
> ila_csum_adjust_transport() caches ip6h = ipv6_hdr(skb) before calling
> pskb_may_pull(). On a non-linear skb whose transport header sits in a page
> fragment, pskb_may_pull() can call __pskb_pull_tail() / pskb_expand_head()
> and free the old skb head, leaving ip6h dangling; the following
> get_csum_diff(ip6h, p) then reads freed memory. ila_update_ipv6_locator()
> has the same pattern and additionally writes the new locator through the
> stale destination-address pointer.
>
> Impact: a remote IPv6 packet routed through a configured ILA
> csum-adjust-transport route or receive-side mapping triggers a
> slab-use-after-free in ila_update_ipv6_locator() (KASAN). The route or
> mapping requires CAP_NET_ADMIN to configure, but trigger packets are
> unauthenticated once it exists.
>
> Reload ip6h (and the derived iaddr) after each pskb_may_pull() before use,
> matching the transport-header reload the code already performs.
>
> Fixes: 33f11d16142b ("ila: Create net/ipv6/ila directory")
> Cc: stable@vger.kernel.org
> Assisted-by: Claude:claude-opus-4-8
> Signed-off-by: Michael Bommarito <michael.bommarito@gmail.com>
> ---
> net/ipv6/ila/ila_common.c | 6 ++++++
> 1 file changed, 6 insertions(+)
>
> diff --git a/net/ipv6/ila/ila_common.c b/net/ipv6/ila/ila_common.c
> index e71571455c8a0..acedc5a84e4d7 100644
> --- a/net/ipv6/ila/ila_common.c
> +++ b/net/ipv6/ila/ila_common.c
> @@ -85,6 +85,7 @@ static void ila_csum_adjust_transport(struct sk_buff *skb,
> struct tcphdr *th = (struct tcphdr *)
> (skb_network_header(skb) + nhoff);
>
> + ip6h = ipv6_hdr(skb);
> diff = get_csum_diff(ip6h, p);
> inet_proto_csum_replace_by_diff(&th->check, skb,
> diff, true, true);
> @@ -96,6 +97,7 @@ static void ila_csum_adjust_transport(struct sk_buff *skb,
> (skb_network_header(skb) + nhoff);
>
> if (uh->check || skb->ip_summed == CHECKSUM_PARTIAL) {
> + ip6h = ipv6_hdr(skb);
> diff = get_csum_diff(ip6h, p);
> inet_proto_csum_replace_by_diff(&uh->check, skb,
> diff, true, true);
> @@ -110,6 +112,7 @@ static void ila_csum_adjust_transport(struct sk_buff *skb,
> struct icmp6hdr *ih = (struct icmp6hdr *)
> (skb_network_header(skb) + nhoff);
>
> + ip6h = ipv6_hdr(skb);
> diff = get_csum_diff(ip6h, p);
> inet_proto_csum_replace_by_diff(&ih->icmp6_cksum, skb,
> diff, true, true);
> @@ -151,6 +154,9 @@ void ila_update_ipv6_locator(struct sk_buff *skb, struct ila_params *p,
> break;
> }
>
> + ip6h = ipv6_hdr(skb);
> + iaddr = ila_a2i(&ip6h->daddr);
You should be able to reload the pointers only in the
ILA_CSUM_ADJUST_TRANSPORT case.
> +
> /* Now change destination address */
> iaddr->loc = p->locator;
> }
> --
> 2.53.0
>
^ permalink raw reply
* Re: [PATCH v4 3/4] net: xilinx: axienet: Derive RX frame length from residue in dmaengine path
From: Pandey, Radhey Shyam @ 2026-07-13 9:44 UTC (permalink / raw)
To: Srinivas Neeli, Vinod Koul, Radhey Shyam Pandey
Cc: Frank Li, Michal Simek, Andrew Lunn, David S . Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Suraj Gupta,
Marek Vasut, Tomi Valkeinen, Alex Bereza, Folker Schwesinger,
dmaengine, netdev, linux-arm-kernel, linux-kernel, git
In-Reply-To: <20260713072146.45269-4-srinivas.neeli@amd.com>
On 7/13/2026 12:51 PM, Srinivas Neeli wrote:
> The dmaengine RX path derived the received frame length from the descriptor
> APP metadata. That only works when the optional AXI4-Stream status/control
> interface is present, because the hardware populates the APP fields solely
> when that interface is enabled. On designs without it the length read back
> is invalid.
>
> The AXI DMA engine already reports how many bytes it wrote into the buffer
> through the standard dmaengine residue mechanism. Compute the RX frame
> length as the posted buffer length minus result->residue, which is
> independent of the status/control interface and correct across all designs,
> including multi-descriptor frames where the residue is summed over the
> chain.
>
> Drop the descriptor metadata lookup, which was only used for this purpose.
> Detect a failed transfer from dmaengine_result.result instead of the
> metadata pointer return value, and remove the now unused LEN_APP macro.
>
> The transmit path is unaffected. It still passes APP metadata for checksum
> offload and derives its length from the skb.
>
> Signed-off-by: Srinivas Neeli <srinivas.neeli@amd.com>
Reviewed-by: Radhey Shyam Pandey <radhey.shyam.pandey@amd.com>
Thanks!
> ---
> Changes in V4:
> - Renamed subject to "Derive RX frame length from residue in dmaengine
> path".
> - Condensed the commit message.
> - Dropped the Fixes tag.
>
> Changes in V3:
> - New patch in this series.
> - This patch enables axienet to work on designs where the AXI4-Stream
> status/control interface is not present. By using the standard
> dmaengine residue mechanism, the driver no longer depends on APP
> fields being populated by hardware.
> - This approach replaces the V2 xferred_bytes mechanism (V2 patch 5/5),
> making the dt-bindings patch (V2 patch 4/5) for xlnx,include-stscntrl-strm
> also unnecessary. Both V2 patches are dropped in this series.
> ---
> drivers/net/ethernet/xilinx/xilinx_axienet_main.c | 14 +++++---------
> 1 file changed, 5 insertions(+), 9 deletions(-)
>
> diff --git a/drivers/net/ethernet/xilinx/xilinx_axienet_main.c b/drivers/net/ethernet/xilinx/xilinx_axienet_main.c
> index fcf517069d16..67d1b8e91d68 100644
> --- a/drivers/net/ethernet/xilinx/xilinx_axienet_main.c
> +++ b/drivers/net/ethernet/xilinx/xilinx_axienet_main.c
> @@ -53,7 +53,6 @@
> #define TX_BD_NUM_MAX 4096
> #define RX_BD_NUM_MAX 4096
> #define DMA_NUM_APP_WORDS 5
> -#define LEN_APP 4
> #define RX_BUF_NUM_DEFAULT 128
>
> /* Must be shorter than length of ethtool_drvinfo.driver field to fit */
> @@ -1159,29 +1158,26 @@ axienet_start_xmit(struct sk_buff *skb, struct net_device *ndev)
> static void axienet_dma_rx_cb(void *data, const struct dmaengine_result *result)
> {
> struct skbuf_dma_descriptor *skbuf_dma;
> - size_t meta_len, meta_max_len, rx_len;
> struct axienet_local *lp = data;
> struct sk_buff *skb;
> - u32 *app_metadata;
> + size_t rx_len;
> int i;
>
> skbuf_dma = axienet_get_rx_desc(lp, lp->rx_ring_tail++);
> skb = skbuf_dma->skb;
> - app_metadata = dmaengine_desc_get_metadata_ptr(skbuf_dma->desc, &meta_len,
> - &meta_max_len);
> dma_unmap_single(lp->dev, skbuf_dma->dma_address, lp->max_frm_size,
> DMA_FROM_DEVICE);
>
> - if (IS_ERR(app_metadata)) {
> + if (result->result != DMA_TRANS_NOERROR) {
> if (net_ratelimit())
> - netdev_err(lp->ndev, "Failed to get RX metadata pointer\n");
> + netdev_err(lp->ndev, "RX DMA transfer failed\n");
> dev_kfree_skb_any(skb);
> lp->ndev->stats.rx_dropped++;
> goto rx_submit;
> }
>
> - /* TODO: Derive app word index programmatically */
> - rx_len = (app_metadata[LEN_APP] & 0xFFFF);
> + /* Actual length = posted buffer length - residue. */
> + rx_len = lp->max_frm_size - result->residue;
> skb_put(skb, rx_len);
> skb->protocol = eth_type_trans(skb, lp->ndev);
> skb->ip_summed = CHECKSUM_NONE;
^ permalink raw reply
* [PATCH nf v3 0/2] ipvs: fix destination overload state updates
From: Yizhou Zhao @ 2026-07-13 9:48 UTC (permalink / raw)
To: David Ahern, Ido Schimmel, Simon Horman, Julian Anastasov,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Pablo Neira Ayuso, Florian Westphal, Phil Sutter
Cc: Yizhou Zhao, netdev, lvs-devel, linux-kernel, netfilter-devel,
coreteam, stable, Yuxiang Yang, Ao Wang, Xuewei Feng, Qi Li,
Ke Xu
IPVS updates a destination's overload status from connection accounting
and destination configuration paths, while schedulers read it from packet
processing paths.
Patch 1, authored by Julian, updates the overload state whenever a
destination's connection thresholds change. It also limits the upper
threshold to the range that can be compared safely with the connection
counter.
Patch 2 moves overload state out of dest->flags, which it previously
shared with the independent availability state. It uses a separate bitset
and bitops so updates to the two states cannot clobber each other. KCSAN
reports the original race between __ip_vs_update_dest() and
ip_vs_sh_schedule(), as well as between ip_vs_bind_dest() and the SH
scheduler.
The series keeps reader-side synchronization lightweight. test_bit() does
not provide a fresh cross-field snapshot, so schedulers may still observe
stale destination state as they could before this change.
Changes in v3:
- Add Julian's fix to properly refresh OVERLOAD on destination edit and
reject upper thresholds above INT_MAX before comparing them with the signed
connection counter as patch 1/2.
- Keep AVAILABLE in dest->flags and move OVERLOAD to a separate bitset.
- Link to v2: https://lore.kernel.org/netfilter-devel/20260708060454.20534-1-zhaoyz24@mails.tsinghua.edu.cn/
Julian Anastasov (1):
ipvs: properly update the overload flag on dest edit
Yizhou Zhao (1):
ipvs: use bitops for destination overload state
include/net/ip_vs.h | 10 ++++++++
include/uapi/linux/ip_vs.h | 6 -----
net/netfilter/ipvs/ip_vs_conn.c | 44 ++++++++++++++++++++++----------
net/netfilter/ipvs/ip_vs_ctl.c | 26 +++++++++++++------
net/netfilter/ipvs/ip_vs_dh.c | 4 +--
net/netfilter/ipvs/ip_vs_fo.c | 2 +-
net/netfilter/ipvs/ip_vs_lblc.c | 4 +--
net/netfilter/ipvs/ip_vs_lblcr.c | 8 +++---
net/netfilter/ipvs/ip_vs_lc.c | 2 +-
net/netfilter/ipvs/ip_vs_mh.c | 2 +-
net/netfilter/ipvs/ip_vs_nq.c | 2 +-
net/netfilter/ipvs/ip_vs_ovf.c | 2 +-
net/netfilter/ipvs/ip_vs_rr.c | 2 +-
net/netfilter/ipvs/ip_vs_sed.c | 4 +--
net/netfilter/ipvs/ip_vs_sh.c | 2 +-
net/netfilter/ipvs/ip_vs_twos.c | 4 +--
net/netfilter/ipvs/ip_vs_wlc.c | 4 +--
net/netfilter/ipvs/ip_vs_wrr.c | 2 +-
18 files changed, 81 insertions(+), 49 deletions(-)
base-commit: 2c7c88a412aa6d09cd04b414211b4ef8553b5309
--
2.34.1
^ permalink raw reply
* [PATCH nf v3 1/2] ipvs: properly update the overload flag on dest edit
From: Yizhou Zhao @ 2026-07-13 9:48 UTC (permalink / raw)
To: David Ahern, Ido Schimmel, Simon Horman, Julian Anastasov,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Pablo Neira Ayuso, Florian Westphal, Phil Sutter
Cc: netdev, lvs-devel, linux-kernel, netfilter-devel, coreteam,
Yuxiang Yang, Ao Wang, Xuewei Feng, Qi Li, Ke Xu
In-Reply-To: <cover.1783931964.git.zhaoyz24@mails.tsinghua.edu.cn>
From: Julian Anastasov <ja@ssi.bg>
The upper/lower connection thresholds for dest can be changed,
so use ip_vs_dest_update_overload() to properly update the
dest overload flag.
The thresholds were not limited, fit them in the 0 .. INT_MAX
range as already done in ipvsadm.
As the thresholds are also read when connections are created
and expired, use WRITE_ONCE/READ_ONCE to access them.
As the lower threshold is optional, use (u - (u >> 2)) to
calculate the 75% default value based on the upper threshold
by preserving the integer rounding, as suggested by Yizhou Zhao.
Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
Signed-off-by: Julian Anastasov <ja@ssi.bg>
---
include/net/ip_vs.h | 2 ++
net/netfilter/ipvs/ip_vs_conn.c | 43 +++++++++++++++++++++++----------
net/netfilter/ipvs/ip_vs_ctl.c | 26 ++++++++++++++------
3 files changed, 50 insertions(+), 21 deletions(-)
diff --git a/include/net/ip_vs.h b/include/net/ip_vs.h
index 417ff51f62fc..3fc864a320fb 100644
--- a/include/net/ip_vs.h
+++ b/include/net/ip_vs.h
@@ -1907,6 +1907,8 @@ static inline void ip_vs_dest_put_and_free(struct ip_vs_dest *dest)
kfree(dest);
}
+void ip_vs_dest_update_overload(struct ip_vs_dest *dest);
+
/* IPVS sync daemon data and function prototypes
* (from ip_vs_sync.c)
*/
diff --git a/net/netfilter/ipvs/ip_vs_conn.c b/net/netfilter/ipvs/ip_vs_conn.c
index 6ed2622363f0..fa3fbd597f3f 100644
--- a/net/netfilter/ipvs/ip_vs_conn.c
+++ b/net/netfilter/ipvs/ip_vs_conn.c
@@ -991,6 +991,33 @@ static inline int ip_vs_dest_totalconns(struct ip_vs_dest *dest)
+ atomic_read(&dest->inactconns);
}
+/* Update overload flag based on number of dest conns and lower/upper
+ * connection thresholds:
+ * - conns reach u_threshold and exceed it: set the flag
+ * - conns go below l_threshold (or 75% of u_threshold): clear the flag
+ */
+__always_inline void ip_vs_dest_update_overload(struct ip_vs_dest *dest)
+{
+ int conns;
+ u32 l, u;
+
+ u = READ_ONCE(dest->u_threshold);
+ if (!u)
+ goto unset;
+ conns = ip_vs_dest_totalconns(dest);
+ if (conns >= u) {
+ dest->flags |= IP_VS_DEST_F_OVERLOAD;
+ return;
+ }
+ /* Low threshold defaults to 75% of upper threshold */
+ l = READ_ONCE(dest->l_threshold) ? : (u - (u >> 2));
+ if (conns >= l)
+ return;
+
+unset:
+ dest->flags &= ~IP_VS_DEST_F_OVERLOAD;
+}
+
/*
* Bind a connection entry with a virtual service destination
* Called just after a new connection entry is created.
@@ -1053,9 +1080,7 @@ ip_vs_bind_dest(struct ip_vs_conn *cp, struct ip_vs_dest *dest)
atomic_inc(&dest->persistconns);
}
- if (dest->u_threshold != 0 &&
- ip_vs_dest_totalconns(dest) >= dest->u_threshold)
- dest->flags |= IP_VS_DEST_F_OVERLOAD;
+ ip_vs_dest_update_overload(dest);
}
@@ -1149,16 +1174,8 @@ static inline void ip_vs_unbind_dest(struct ip_vs_conn *cp)
atomic_dec(&dest->persistconns);
}
- if (dest->l_threshold != 0) {
- if (ip_vs_dest_totalconns(dest) < dest->l_threshold)
- dest->flags &= ~IP_VS_DEST_F_OVERLOAD;
- } else if (dest->u_threshold != 0) {
- if (ip_vs_dest_totalconns(dest) * 4 < dest->u_threshold * 3)
- dest->flags &= ~IP_VS_DEST_F_OVERLOAD;
- } else {
- if (dest->flags & IP_VS_DEST_F_OVERLOAD)
- dest->flags &= ~IP_VS_DEST_F_OVERLOAD;
- }
+ if (dest->flags & IP_VS_DEST_F_OVERLOAD)
+ ip_vs_dest_update_overload(dest);
ip_vs_dest_put(dest);
}
diff --git a/net/netfilter/ipvs/ip_vs_ctl.c b/net/netfilter/ipvs/ip_vs_ctl.c
index bcf40b8c41cf..62f73d892f97 100644
--- a/net/netfilter/ipvs/ip_vs_ctl.c
+++ b/net/netfilter/ipvs/ip_vs_ctl.c
@@ -1370,10 +1370,12 @@ __ip_vs_update_dest(struct ip_vs_service *svc, struct ip_vs_dest *dest,
/* set the dest status flags */
dest->flags |= IP_VS_DEST_F_AVAILABLE;
- if (udest->u_threshold == 0 || udest->u_threshold > dest->u_threshold)
- dest->flags &= ~IP_VS_DEST_F_OVERLOAD;
- dest->u_threshold = udest->u_threshold;
- dest->l_threshold = udest->l_threshold;
+ if (READ_ONCE(dest->u_threshold) != udest->u_threshold ||
+ READ_ONCE(dest->l_threshold) != udest->l_threshold) {
+ WRITE_ONCE(dest->u_threshold, udest->u_threshold);
+ WRITE_ONCE(dest->l_threshold, udest->l_threshold);
+ ip_vs_dest_update_overload(dest);
+ }
dest->af = udest->af;
@@ -1486,6 +1488,9 @@ ip_vs_add_dest(struct ip_vs_service *svc, struct ip_vs_dest_user_kern *udest)
return -ERANGE;
}
+ if (udest->u_threshold > INT_MAX)
+ return -EINVAL;
+
if (udest->tun_type == IP_VS_CONN_F_TUNNEL_TYPE_GUE) {
if (udest->tun_port == 0) {
pr_err("%s(): tunnel port is zero\n", __func__);
@@ -1559,6 +1564,9 @@ ip_vs_edit_dest(struct ip_vs_service *svc, struct ip_vs_dest_user_kern *udest)
return -ERANGE;
}
+ if (udest->u_threshold > INT_MAX)
+ return -EINVAL;
+
if (udest->tun_type == IP_VS_CONN_F_TUNNEL_TYPE_GUE) {
if (udest->tun_port == 0) {
pr_err("%s(): tunnel port is zero\n", __func__);
@@ -3667,8 +3675,8 @@ __ip_vs_get_dest_entries(struct netns_ipvs *ipvs, const struct ip_vs_get_dests *
entry.port = dest->port;
entry.conn_flags = atomic_read(&dest->conn_flags);
entry.weight = atomic_read(&dest->weight);
- entry.u_threshold = dest->u_threshold;
- entry.l_threshold = dest->l_threshold;
+ entry.u_threshold = READ_ONCE(dest->u_threshold);
+ entry.l_threshold = READ_ONCE(dest->l_threshold);
entry.activeconns = atomic_read(&dest->activeconns);
entry.inactconns = atomic_read(&dest->inactconns);
entry.persistconns = atomic_read(&dest->persistconns);
@@ -4277,8 +4285,10 @@ static int ip_vs_genl_fill_dest(struct sk_buff *skb, struct ip_vs_dest *dest)
dest->tun_port) ||
nla_put_u16(skb, IPVS_DEST_ATTR_TUN_FLAGS,
dest->tun_flags) ||
- nla_put_u32(skb, IPVS_DEST_ATTR_U_THRESH, dest->u_threshold) ||
- nla_put_u32(skb, IPVS_DEST_ATTR_L_THRESH, dest->l_threshold) ||
+ nla_put_u32(skb, IPVS_DEST_ATTR_U_THRESH,
+ READ_ONCE(dest->u_threshold)) ||
+ nla_put_u32(skb, IPVS_DEST_ATTR_L_THRESH,
+ READ_ONCE(dest->l_threshold)) ||
nla_put_u32(skb, IPVS_DEST_ATTR_ACTIVE_CONNS,
atomic_read(&dest->activeconns)) ||
nla_put_u32(skb, IPVS_DEST_ATTR_INACT_CONNS,
--
2.34.1
^ permalink raw reply related
* [PATCH nf v3 2/2] ipvs: use bitops for destination overload state
From: Yizhou Zhao @ 2026-07-13 9:48 UTC (permalink / raw)
To: David Ahern, Ido Schimmel, Simon Horman, Julian Anastasov,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Pablo Neira Ayuso, Florian Westphal, Phil Sutter
Cc: Yizhou Zhao, netdev, lvs-devel, linux-kernel, netfilter-devel,
coreteam, stable, Yuxiang Yang, Ao Wang, Xuewei Feng, Qi Li,
Ke Xu
In-Reply-To: <cover.1783931964.git.zhaoyz24@mails.tsinghua.edu.cn>
IPVS destination schedulers read the overload state from packet processing
paths, while connection accounting and destination updates can change it
concurrently. IP_VS_DEST_F_OVERLOAD currently shares dest->flags with
IP_VS_DEST_F_AVAILABLE, so plain read-modify-write operations on the two
independent states can race and lose either update.
KCSAN reports the race with the SH scheduler and an upper connection
threshold configured:
BUG: KCSAN: data-race in __ip_vs_update_dest / ip_vs_sh_schedule
IP_VS_DEST_F_AVAILABLE is changed under service_mutex. Keep it in the
existing flags word, but move the overload state to a separate unsigned
long and access it with bitops. Use test_bit() in scheduler paths and
set_bit()/clear_bit() in ip_vs_dest_update_overload(). This serializes the
overload bit accesses and prevents updates to the available and overload
states from clobbering each other.
The destination flags are not exposed by the IPVS sockopt or netlink
interfaces, so move their definitions out of the UAPI header. Place the
new overload word next to weight, which keeps the existing flags,
conn_flags and weight offsets unchanged. On x86-64 this grows struct
ip_vs_dest from 472 to 480 bytes.
test_bit() does not add reader-side ordering. Schedulers can still observe
stale destination state, as they could before this change; this does not
provide a fresh cross-field snapshot.
Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
Cc: stable@vger.kernel.org
Reported-by: Yizhou Zhao <zhaoyz24@mails.tsinghua.edu.cn>
Reported-by: Yuxiang Yang <yangyx22@mails.tsinghua.edu.cn>
Reported-by: Ao Wang <wangao@seu.edu.cn>
Reported-by: Xuewei Feng <fengxw06@126.com>
Reported-by: Qi Li <qli01@tsinghua.edu.cn>
Reported-by: Ke Xu <xuke@tsinghua.edu.cn>
Assisted-by: Claude-Code:GLM-5.2
Suggested-by: Julian Anastasov <ja@ssi.bg>
Signed-off-by: Yizhou Zhao <zhaoyz24@mails.tsinghua.edu.cn>
---
include/net/ip_vs.h | 8 ++++++++
include/uapi/linux/ip_vs.h | 6 ------
net/netfilter/ipvs/ip_vs_conn.c | 7 ++++---
net/netfilter/ipvs/ip_vs_dh.c | 4 ++--
net/netfilter/ipvs/ip_vs_fo.c | 2 +-
net/netfilter/ipvs/ip_vs_lblc.c | 4 ++--
net/netfilter/ipvs/ip_vs_lblcr.c | 8 ++++----
net/netfilter/ipvs/ip_vs_lc.c | 2 +-
net/netfilter/ipvs/ip_vs_mh.c | 2 +-
net/netfilter/ipvs/ip_vs_nq.c | 2 +-
net/netfilter/ipvs/ip_vs_ovf.c | 2 +-
net/netfilter/ipvs/ip_vs_rr.c | 2 +-
net/netfilter/ipvs/ip_vs_sed.c | 4 ++--
net/netfilter/ipvs/ip_vs_sh.c | 2 +-
net/netfilter/ipvs/ip_vs_twos.c | 4 ++--
net/netfilter/ipvs/ip_vs_wlc.c | 4 ++--
net/netfilter/ipvs/ip_vs_wrr.c | 2 +-
17 files changed, 34 insertions(+), 31 deletions(-)
diff --git a/include/net/ip_vs.h b/include/net/ip_vs.h
index 3fc864a320fb..5e8e55f82b04 100644
--- a/include/net/ip_vs.h
+++ b/include/net/ip_vs.h
@@ -36,6 +36,13 @@
#define IP_VS_HDR_INVERSE 1
#define IP_VS_HDR_ICMP 2
+/* Destination Server Flags */
+#define IP_VS_DEST_F_AVAILABLE 0x0001 /* server is available */
+
+enum {
+ IP_VS_DEST_FL_OVERLOAD,
+};
+
/* conn_tab limits (as per Kconfig) */
#define IP_VS_CONN_TAB_MIN_BITS 8
#if BITS_PER_LONG > 32
@@ -976,6 +983,7 @@ struct ip_vs_dest {
volatile unsigned int flags; /* dest status flags */
atomic_t conn_flags; /* flags to copy to conn */
atomic_t weight; /* server weight */
+ unsigned long flags2; /* dest status flags */
atomic_t last_weight; /* server latest weight */
__u16 tun_type; /* tunnel type */
__be16 tun_port; /* tunnel port */
diff --git a/include/uapi/linux/ip_vs.h b/include/uapi/linux/ip_vs.h
index 1ed234e7f251..2c37c6ac7525 100644
--- a/include/uapi/linux/ip_vs.h
+++ b/include/uapi/linux/ip_vs.h
@@ -28,12 +28,6 @@
#define IP_VS_SVC_F_SCHED_SH_FALLBACK IP_VS_SVC_F_SCHED1 /* SH fallback */
#define IP_VS_SVC_F_SCHED_SH_PORT IP_VS_SVC_F_SCHED2 /* SH use port */
-/*
- * Destination Server Flags
- */
-#define IP_VS_DEST_F_AVAILABLE 0x0001 /* server is available */
-#define IP_VS_DEST_F_OVERLOAD 0x0002 /* server is overloaded */
-
/*
* IPVS sync daemon states
*/
diff --git a/net/netfilter/ipvs/ip_vs_conn.c b/net/netfilter/ipvs/ip_vs_conn.c
index fa3fbd597f3f..2591f4e143f8 100644
--- a/net/netfilter/ipvs/ip_vs_conn.c
+++ b/net/netfilter/ipvs/ip_vs_conn.c
@@ -1006,7 +1006,7 @@ __always_inline void ip_vs_dest_update_overload(struct ip_vs_dest *dest)
goto unset;
conns = ip_vs_dest_totalconns(dest);
if (conns >= u) {
- dest->flags |= IP_VS_DEST_F_OVERLOAD;
+ set_bit(IP_VS_DEST_FL_OVERLOAD, &dest->flags2);
return;
}
/* Low threshold defaults to 75% of upper threshold */
@@ -1015,7 +1015,8 @@ __always_inline void ip_vs_dest_update_overload(struct ip_vs_dest *dest)
return;
unset:
- dest->flags &= ~IP_VS_DEST_F_OVERLOAD;
+ if (test_bit(IP_VS_DEST_FL_OVERLOAD, &dest->flags2))
+ clear_bit(IP_VS_DEST_FL_OVERLOAD, &dest->flags2);
}
/*
@@ -1174,7 +1175,7 @@ static inline void ip_vs_unbind_dest(struct ip_vs_conn *cp)
atomic_dec(&dest->persistconns);
}
- if (dest->flags & IP_VS_DEST_F_OVERLOAD)
+ if (test_bit(IP_VS_DEST_FL_OVERLOAD, &dest->flags2))
ip_vs_dest_update_overload(dest);
ip_vs_dest_put(dest);
diff --git a/net/netfilter/ipvs/ip_vs_dh.c b/net/netfilter/ipvs/ip_vs_dh.c
index e1f62f6b25e2..364acb6342e2 100644
--- a/net/netfilter/ipvs/ip_vs_dh.c
+++ b/net/netfilter/ipvs/ip_vs_dh.c
@@ -196,12 +196,12 @@ static int ip_vs_dh_dest_changed(struct ip_vs_service *svc,
/*
- * If the dest flags is set with IP_VS_DEST_F_OVERLOAD,
+ * If the dest flags is set with IP_VS_DEST_FL_OVERLOAD,
* consider that the server is overloaded here.
*/
static inline int is_overloaded(struct ip_vs_dest *dest)
{
- return dest->flags & IP_VS_DEST_F_OVERLOAD;
+ return test_bit(IP_VS_DEST_FL_OVERLOAD, &dest->flags2);
}
diff --git a/net/netfilter/ipvs/ip_vs_fo.c b/net/netfilter/ipvs/ip_vs_fo.c
index d657b47c6511..a59af6c1189a 100644
--- a/net/netfilter/ipvs/ip_vs_fo.c
+++ b/net/netfilter/ipvs/ip_vs_fo.c
@@ -29,7 +29,7 @@ ip_vs_fo_schedule(struct ip_vs_service *svc, const struct sk_buff *skb,
* Find virtual server with highest weight and send it traffic
*/
list_for_each_entry_rcu(dest, &svc->destinations, n_list) {
- if (!(dest->flags & IP_VS_DEST_F_OVERLOAD) &&
+ if (!test_bit(IP_VS_DEST_FL_OVERLOAD, &dest->flags2) &&
atomic_read(&dest->weight) > hw) {
hweight = dest;
hw = atomic_read(&dest->weight);
diff --git a/net/netfilter/ipvs/ip_vs_lblc.c b/net/netfilter/ipvs/ip_vs_lblc.c
index 15ccb2b2fa1f..ee26be3eb860 100644
--- a/net/netfilter/ipvs/ip_vs_lblc.c
+++ b/net/netfilter/ipvs/ip_vs_lblc.c
@@ -414,7 +414,7 @@ __ip_vs_lblc_schedule(struct ip_vs_service *svc)
* new connection.
*/
list_for_each_entry_rcu(dest, &svc->destinations, n_list) {
- if (dest->flags & IP_VS_DEST_F_OVERLOAD)
+ if (test_bit(IP_VS_DEST_FL_OVERLOAD, &dest->flags2))
continue;
if (atomic_read(&dest->weight) > 0) {
least = dest;
@@ -429,7 +429,7 @@ __ip_vs_lblc_schedule(struct ip_vs_service *svc)
*/
nextstage:
list_for_each_entry_continue_rcu(dest, &svc->destinations, n_list) {
- if (dest->flags & IP_VS_DEST_F_OVERLOAD)
+ if (test_bit(IP_VS_DEST_FL_OVERLOAD, &dest->flags2))
continue;
doh = ip_vs_dest_conn_overhead(dest);
diff --git a/net/netfilter/ipvs/ip_vs_lblcr.c b/net/netfilter/ipvs/ip_vs_lblcr.c
index c90ea897c3f7..28858e44225a 100644
--- a/net/netfilter/ipvs/ip_vs_lblcr.c
+++ b/net/netfilter/ipvs/ip_vs_lblcr.c
@@ -166,7 +166,7 @@ static inline struct ip_vs_dest *ip_vs_dest_set_min(struct ip_vs_dest_set *set)
/* select the first destination server, whose weight > 0 */
list_for_each_entry_rcu(e, &set->list, list) {
least = e->dest;
- if (least->flags & IP_VS_DEST_F_OVERLOAD)
+ if (test_bit(IP_VS_DEST_FL_OVERLOAD, &least->flags2))
continue;
if ((atomic_read(&least->weight) > 0)
@@ -181,7 +181,7 @@ static inline struct ip_vs_dest *ip_vs_dest_set_min(struct ip_vs_dest_set *set)
nextstage:
list_for_each_entry_continue_rcu(e, &set->list, list) {
dest = e->dest;
- if (dest->flags & IP_VS_DEST_F_OVERLOAD)
+ if (test_bit(IP_VS_DEST_FL_OVERLOAD, &dest->flags2))
continue;
doh = ip_vs_dest_conn_overhead(dest);
@@ -577,7 +577,7 @@ __ip_vs_lblcr_schedule(struct ip_vs_service *svc)
* new connection.
*/
list_for_each_entry_rcu(dest, &svc->destinations, n_list) {
- if (dest->flags & IP_VS_DEST_F_OVERLOAD)
+ if (test_bit(IP_VS_DEST_FL_OVERLOAD, &dest->flags2))
continue;
if (atomic_read(&dest->weight) > 0) {
@@ -593,7 +593,7 @@ __ip_vs_lblcr_schedule(struct ip_vs_service *svc)
*/
nextstage:
list_for_each_entry_continue_rcu(dest, &svc->destinations, n_list) {
- if (dest->flags & IP_VS_DEST_F_OVERLOAD)
+ if (test_bit(IP_VS_DEST_FL_OVERLOAD, &dest->flags2))
continue;
doh = ip_vs_dest_conn_overhead(dest);
diff --git a/net/netfilter/ipvs/ip_vs_lc.c b/net/netfilter/ipvs/ip_vs_lc.c
index 38cc38c5d8bb..c4e4e91e3e6d 100644
--- a/net/netfilter/ipvs/ip_vs_lc.c
+++ b/net/netfilter/ipvs/ip_vs_lc.c
@@ -38,7 +38,7 @@ ip_vs_lc_schedule(struct ip_vs_service *svc, const struct sk_buff *skb,
*/
list_for_each_entry_rcu(dest, &svc->destinations, n_list) {
- if ((dest->flags & IP_VS_DEST_F_OVERLOAD) ||
+ if (test_bit(IP_VS_DEST_FL_OVERLOAD, &dest->flags2) ||
atomic_read(&dest->weight) == 0)
continue;
doh = ip_vs_dest_conn_overhead(dest);
diff --git a/net/netfilter/ipvs/ip_vs_mh.c b/net/netfilter/ipvs/ip_vs_mh.c
index 020863047562..23ffc51ca088 100644
--- a/net/netfilter/ipvs/ip_vs_mh.c
+++ b/net/netfilter/ipvs/ip_vs_mh.c
@@ -80,7 +80,7 @@ static inline void generate_hash_secret(hsiphash_key_t *hash1,
static inline bool is_unavailable(struct ip_vs_dest *dest)
{
return atomic_read(&dest->weight) <= 0 ||
- dest->flags & IP_VS_DEST_F_OVERLOAD;
+ test_bit(IP_VS_DEST_FL_OVERLOAD, &dest->flags2);
}
/* Returns hash value for IPVS MH entry */
diff --git a/net/netfilter/ipvs/ip_vs_nq.c b/net/netfilter/ipvs/ip_vs_nq.c
index ada158c610ce..d6fbb9e50e4b 100644
--- a/net/netfilter/ipvs/ip_vs_nq.c
+++ b/net/netfilter/ipvs/ip_vs_nq.c
@@ -72,7 +72,7 @@ ip_vs_nq_schedule(struct ip_vs_service *svc, const struct sk_buff *skb,
list_for_each_entry_rcu(dest, &svc->destinations, n_list) {
- if (dest->flags & IP_VS_DEST_F_OVERLOAD ||
+ if (test_bit(IP_VS_DEST_FL_OVERLOAD, &dest->flags2) ||
!atomic_read(&dest->weight))
continue;
diff --git a/net/netfilter/ipvs/ip_vs_ovf.c b/net/netfilter/ipvs/ip_vs_ovf.c
index c5c67df80a0b..104de8c24a4f 100644
--- a/net/netfilter/ipvs/ip_vs_ovf.c
+++ b/net/netfilter/ipvs/ip_vs_ovf.c
@@ -33,7 +33,7 @@ ip_vs_ovf_schedule(struct ip_vs_service *svc, const struct sk_buff *skb,
*/
list_for_each_entry_rcu(dest, &svc->destinations, n_list) {
w = atomic_read(&dest->weight);
- if ((dest->flags & IP_VS_DEST_F_OVERLOAD) ||
+ if (test_bit(IP_VS_DEST_FL_OVERLOAD, &dest->flags2) ||
atomic_read(&dest->activeconns) > w ||
w == 0)
continue;
diff --git a/net/netfilter/ipvs/ip_vs_rr.c b/net/netfilter/ipvs/ip_vs_rr.c
index 4125ee561cdc..c38bee987d14 100644
--- a/net/netfilter/ipvs/ip_vs_rr.c
+++ b/net/netfilter/ipvs/ip_vs_rr.c
@@ -66,7 +66,7 @@ ip_vs_rr_schedule(struct ip_vs_service *svc, const struct sk_buff *skb,
list_for_each_entry_continue_rcu(dest,
&svc->destinations,
n_list) {
- if (!(dest->flags & IP_VS_DEST_F_OVERLOAD) &&
+ if (!test_bit(IP_VS_DEST_FL_OVERLOAD, &dest->flags2) &&
atomic_read(&dest->weight) > 0)
/* HIT */
goto out;
diff --git a/net/netfilter/ipvs/ip_vs_sed.c b/net/netfilter/ipvs/ip_vs_sed.c
index 245a323c84cd..0ce425f9748a 100644
--- a/net/netfilter/ipvs/ip_vs_sed.c
+++ b/net/netfilter/ipvs/ip_vs_sed.c
@@ -75,7 +75,7 @@ ip_vs_sed_schedule(struct ip_vs_service *svc, const struct sk_buff *skb,
*/
list_for_each_entry_rcu(dest, &svc->destinations, n_list) {
- if (!(dest->flags & IP_VS_DEST_F_OVERLOAD) &&
+ if (!test_bit(IP_VS_DEST_FL_OVERLOAD, &dest->flags2) &&
atomic_read(&dest->weight) > 0) {
least = dest;
loh = ip_vs_sed_dest_overhead(least);
@@ -90,7 +90,7 @@ ip_vs_sed_schedule(struct ip_vs_service *svc, const struct sk_buff *skb,
*/
nextstage:
list_for_each_entry_continue_rcu(dest, &svc->destinations, n_list) {
- if (dest->flags & IP_VS_DEST_F_OVERLOAD)
+ if (test_bit(IP_VS_DEST_FL_OVERLOAD, &dest->flags2))
continue;
doh = ip_vs_sed_dest_overhead(dest);
if ((__s64)loh * atomic_read(&dest->weight) >
diff --git a/net/netfilter/ipvs/ip_vs_sh.c b/net/netfilter/ipvs/ip_vs_sh.c
index cd67066e3b26..bbdb683b8e86 100644
--- a/net/netfilter/ipvs/ip_vs_sh.c
+++ b/net/netfilter/ipvs/ip_vs_sh.c
@@ -73,7 +73,7 @@ struct ip_vs_sh_state {
static inline bool is_unavailable(struct ip_vs_dest *dest)
{
return atomic_read(&dest->weight) <= 0 ||
- dest->flags & IP_VS_DEST_F_OVERLOAD;
+ test_bit(IP_VS_DEST_FL_OVERLOAD, &dest->flags2);
}
/*
diff --git a/net/netfilter/ipvs/ip_vs_twos.c b/net/netfilter/ipvs/ip_vs_twos.c
index dbb7f5fd4688..ce5618f02e7d 100644
--- a/net/netfilter/ipvs/ip_vs_twos.c
+++ b/net/netfilter/ipvs/ip_vs_twos.c
@@ -52,7 +52,7 @@ static struct ip_vs_dest *ip_vs_twos_schedule(struct ip_vs_service *svc,
/* Generate a random weight between [0,sum of all weights) */
list_for_each_entry_rcu(dest, &svc->destinations, n_list) {
- if (!(dest->flags & IP_VS_DEST_F_OVERLOAD)) {
+ if (!test_bit(IP_VS_DEST_FL_OVERLOAD, &dest->flags2)) {
weight = atomic_read(&dest->weight);
if (weight > 0) {
total_weight += weight;
@@ -75,7 +75,7 @@ static struct ip_vs_dest *ip_vs_twos_schedule(struct ip_vs_service *svc,
/* Pick two weighted servers */
list_for_each_entry_rcu(dest, &svc->destinations, n_list) {
- if (dest->flags & IP_VS_DEST_F_OVERLOAD)
+ if (test_bit(IP_VS_DEST_FL_OVERLOAD, &dest->flags2))
continue;
weight = atomic_read(&dest->weight);
diff --git a/net/netfilter/ipvs/ip_vs_wlc.c b/net/netfilter/ipvs/ip_vs_wlc.c
index 9da445ca09a1..62a4c8149192 100644
--- a/net/netfilter/ipvs/ip_vs_wlc.c
+++ b/net/netfilter/ipvs/ip_vs_wlc.c
@@ -47,7 +47,7 @@ ip_vs_wlc_schedule(struct ip_vs_service *svc, const struct sk_buff *skb,
*/
list_for_each_entry_rcu(dest, &svc->destinations, n_list) {
- if (!(dest->flags & IP_VS_DEST_F_OVERLOAD) &&
+ if (!test_bit(IP_VS_DEST_FL_OVERLOAD, &dest->flags2) &&
atomic_read(&dest->weight) > 0) {
least = dest;
loh = ip_vs_dest_conn_overhead(least);
@@ -62,7 +62,7 @@ ip_vs_wlc_schedule(struct ip_vs_service *svc, const struct sk_buff *skb,
*/
nextstage:
list_for_each_entry_continue_rcu(dest, &svc->destinations, n_list) {
- if (dest->flags & IP_VS_DEST_F_OVERLOAD)
+ if (test_bit(IP_VS_DEST_FL_OVERLOAD, &dest->flags2))
continue;
doh = ip_vs_dest_conn_overhead(dest);
if ((__s64)loh * atomic_read(&dest->weight) >
diff --git a/net/netfilter/ipvs/ip_vs_wrr.c b/net/netfilter/ipvs/ip_vs_wrr.c
index 2dcff1040da5..b99cbc1d1302 100644
--- a/net/netfilter/ipvs/ip_vs_wrr.c
+++ b/net/netfilter/ipvs/ip_vs_wrr.c
@@ -176,7 +176,7 @@ ip_vs_wrr_schedule(struct ip_vs_service *svc, const struct sk_buff *skb,
list_for_each_entry_continue_rcu(dest,
&svc->destinations,
n_list) {
- if (!(dest->flags & IP_VS_DEST_F_OVERLOAD) &&
+ if (!test_bit(IP_VS_DEST_FL_OVERLOAD, &dest->flags2) &&
atomic_read(&dest->weight) >= mark->cw)
goto found;
if (dest == stop)
--
2.34.1
^ permalink raw reply related
* Re: [PATCH v4 4/4] dmaengine: xilinx_dma: Extend metadata handling for AXI DMA and MCDMA
From: Pandey, Radhey Shyam @ 2026-07-13 9:50 UTC (permalink / raw)
To: Srinivas Neeli, Vinod Koul, Radhey Shyam Pandey
Cc: Frank Li, Michal Simek, Andrew Lunn, David S . Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Suraj Gupta,
Marek Vasut, Tomi Valkeinen, Alex Bereza, Folker Schwesinger,
dmaengine, netdev, linux-arm-kernel, linux-kernel, git
In-Reply-To: <20260713072146.45269-5-srinivas.neeli@amd.com>
> From: Suraj Gupta <suraj.gupta2@amd.com>
>
> xilinx_dma_get_metadata_ptr() exposed only the descriptor APP fields.
> Each descriptor also carries a status word, and AXI MCDMA carries an
> AXI4-Stream sideband word holding TID, TDEST and TUSER that clients may
> need. Return a pointer to the status word so clients can read the status,
> the sideband and the APP fields together. The exact index layout is
> documented at the function.
>
> Take the pointer from the End-Of-Frame descriptor, where the hardware
> writes these fields. For AXI DMA the pointer now starts at the status word
> of the EOF descriptor instead of the APP fields of the first descriptor,
> and the payload grows from 20 to 24 bytes. No in-tree consumer is affected,
> since axienet reads the RX frame length from result->residue rather than
> the APP fields.
>
> Read xlnx,axistream-connected for MCDMA as well, and attach metadata_ops
> in xilinx_mcdma_prep_slave_sg() when an AXI Stream interface is present,
> so MCDMA clients use the metadata API the same way as AXI DMA clients.
>
> Signed-off-by: Suraj Gupta <suraj.gupta2@amd.com>
> Co-developed-by: Srinivas Neeli <srinivas.neeli@amd.com>
> Signed-off-by: Srinivas Neeli <srinivas.neeli@amd.com>
Reviewed-by: Radhey Shyam Pandey <radhey.shyam.pandey@amd.com>
Thanks!
> ---
> Changes in V4:
> - Restructured xilinx_dma_get_metadata_ptr(): AXIDMA is now the
> fall-through path instead of a separate branch guarded by
> WARN_ON_ONCE()/ERR_PTR().
> - Rewrote the kernel-doc as an index table covering AXI DMA, MCDMA S2MM
> and MCDMA MM2S, and documented that the pointer and payload length are
> the same for both MCDMA directions.
> - Added an inline comment explaining the union aliasing.
> - Condensed the commit message.
>
> Changes in V3:
> - Renamed subject to include "AXI DMA and MCDMA" (was "AXI MCDMA" only).
> - Complete rewrite of commit message and implementation.
> - Metadata pointer now returns status field at index 0 instead of APP
> fields, exposing status and sideband information to clients.
> - Changed from list_first_entry to list_last_entry to return the EOF
> descriptor where hardware writes status and APP fields.
> - Added explicit handling for both AXIDMA and MCDMA types with proper
> payload length calculation.
> - Added WARN_ON_ONCE for unsupported DMA types.
> - Removed the 'chan' field from struct xilinx_dma_tx_descriptor (was
> added in V2) as it's no longer needed; channel is obtained from
> tx->chan instead.
> - Dropped V2 patches 4/5 (dt-bindings xlnx,include-stscntrl-strm) and
> 5/5 (xferred_bytes support) as the approach changed to use residue.
>
> Changes in V2:
> - Added support for MCDMA metadata handling alongside AXIDMA.
> - Added 'chan' field to struct xilinx_dma_tx_descriptor.
> ---
> drivers/dma/xilinx/xilinx_dma.c | 49 ++++++++++++++++++++++++++++-----
> 1 file changed, 42 insertions(+), 7 deletions(-)
>
> diff --git a/drivers/dma/xilinx/xilinx_dma.c b/drivers/dma/xilinx/xilinx_dma.c
> index 1b5b00f08c5f..2be95f0ba3ea 100644
> --- a/drivers/dma/xilinx/xilinx_dma.c
> +++ b/drivers/dma/xilinx/xilinx_dma.c
> @@ -651,18 +651,49 @@ static inline void xilinx_aximcdma_buf(struct xilinx_dma_chan *chan,
> * @tx: async transaction descriptor
> * @payload_len: metadata payload length
> * @max_len: metadata max length
> - * Return: The app field pointer.
> + *
> + * The hardware writes the status, sideband and APP fields into the last
> + * (End-Of-Frame) descriptor. These words are contiguous, so a client reads
> + * them by index from the returned pointer:
> + *
> + * AXI DMA: [0] status, [1..] app
> + * AXI MCDMA (S2MM): [0] status, [1] sideband (TID/TDEST/TUSER), [2..] app
> + * AXI MCDMA (MM2S): [0] ctrl sideband, [1] status, [2..] app
> + *
> + * For MCDMA the pointer and payload length are the same in both directions
> + * because the union members overlay the same descriptor words.
> + *
> + * Return: Pointer to the first metadata word.
> */
> static void *xilinx_dma_get_metadata_ptr(struct dma_async_tx_descriptor *tx,
> size_t *payload_len, size_t *max_len)
> {
> struct xilinx_dma_tx_descriptor *desc = to_dma_tx_descriptor(tx);
> - struct xilinx_axidma_tx_segment *seg;
> + struct xilinx_dma_chan *chan = to_xilinx_chan(tx->chan);
> +
> + if (chan->xdev->dma_config->dmatype == XDMA_TYPE_AXIMCDMA) {
> + struct xilinx_aximcdma_tx_segment *seg =
> + list_last_entry(&desc->segments,
> + struct xilinx_aximcdma_tx_segment, node);
>
> - *max_len = *payload_len = sizeof(u32) * XILINX_DMA_NUM_APP_WORDS;
> - seg = list_first_entry(&desc->segments,
> - struct xilinx_axidma_tx_segment, node);
> - return seg->hw.app;
> + /*
> + * The union members overlay the same words, so one pointer and
> + * length cover both directions (see the layout above).
> + */
> + *max_len = *payload_len = sizeof(seg->hw.s2mm_status) +
> + sizeof(seg->hw.s2mm_sideband_status) +
> + sizeof(seg->hw.app);
> + return &seg->hw.s2mm_status;
> + }
> +
> + /* Only AXIDMA and MCDMA attach metadata_ops, so this is AXIDMA. */
> + struct xilinx_axidma_tx_segment *seg =
> + list_last_entry(&desc->segments,
> + struct xilinx_axidma_tx_segment, node);
> +
> + *max_len = *payload_len = sizeof(seg->hw.status) +
> + sizeof(seg->hw.app);
> + return &seg->hw.status;
> }
>
> static struct dma_descriptor_metadata_ops xilinx_dma_metadata_ops = {
> @@ -2639,6 +2670,9 @@ xilinx_mcdma_prep_slave_sg(struct dma_chan *dchan, struct scatterlist *sgl,
> segment->hw.control |= XILINX_MCDMA_BD_EOP;
> }
>
> + if (chan->xdev->has_axistream_connected)
> + desc->async_tx.metadata_ops = &xilinx_dma_metadata_ops;
> +
> return &desc->async_tx;
>
> error:
> @@ -3287,7 +3321,8 @@ static int xilinx_dma_probe(struct platform_device *pdev)
>
> dma_set_max_seg_size(xdev->dev, xdev->max_buffer_len);
>
> - if (xdev->dma_config->dmatype == XDMA_TYPE_AXIDMA) {
> + if (xdev->dma_config->dmatype == XDMA_TYPE_AXIDMA ||
> + xdev->dma_config->dmatype == XDMA_TYPE_AXIMCDMA) {
> xdev->has_axistream_connected =
> of_property_read_bool(node, "xlnx,axistream-connected");
> }
^ permalink raw reply
* RE: [Intel-wired-lan] [PATCH net] e1000e: fix IRQ leak when request_irq() fails in e1000_request_msix()
From: Kwapulinski, Piotr @ 2026-07-13 9:53 UTC (permalink / raw)
To: Jiayuan Chen, netdev@vger.kernel.org
Cc: Nguyen, Anthony L, Kitszel, Przemyslaw, Andrew Lunn,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Jeff Garzik, Bruce Allan, intel-wired-lan@lists.osuosl.org,
linux-kernel@vger.kernel.org
In-Reply-To: <20260626083917.49745-1-jiayuan.chen@linux.dev>
>-----Original Message-----
>From: Intel-wired-lan <intel-wired-lan-bounces@osuosl.org> On Behalf Of Jiayuan Chen
>Sent: Friday, June 26, 2026 10:39 AM
>To: netdev@vger.kernel.org
>Cc: Jiayuan Chen <jiayuan.chen@linux.dev>; Nguyen, Anthony L <anthony.l.nguyen@intel.com>; Kitszel, Przemyslaw <przemyslaw.kitszel@intel.com>; Andrew Lunn <andrew+netdev@lunn.ch>; David S. Miller <davem@davemloft.net>; Eric Dumazet <edumazet@google.com>; Jakub Kicinski <kuba@kernel.org>; Paolo Abeni <pabeni@redhat.com>; Jeff Garzik <jgarzik@redhat.com>; Bruce Allan <bruce.w.allan@intel.com>; intel-wired-lan@lists.osuosl.org; linux-kernel@vger.kernel.org
>Subject: [Intel-wired-lan] [PATCH net] e1000e: fix IRQ leak when request_irq() fails in e1000_request_msix()
>
>An internal syzbot instance reported the warning below.
>
>comedi (comedi_parport) lets userspace request_irq() an arbitrary IRQ number and can thus grab one of e1000e's MSI-X vectors. When
>e1000_request_msix() then fails partway through, it returned without freeing the vectors it had already requested; pci_disable_msix() later tears those descriptors down while their irqaction is still attached, leaking the /proc/irq entry.
>
>Free the already requested IRQs on the error path.
>
>genirq: Flags mismatch irq 28. 00200000 (eth1-tx-0) vs. 00200000 (comedi_parport)
>
>remove_proc_entry: removing non-empty directory 'irq/27', leaking at least 'eth1-rx-0'
>WARNING: fs/proc/generic.c:742 at remove_proc_entry+0x436/0x560, CPU#3: ip/445 Modules linked in:
>CPU: 3 UID: 0 PID: 445 Comm: ip Not tainted 7.1.0+ #284 PREEMPT
>RIP: 0010:remove_proc_entry (fs/proc/generic.c:742 (discriminator 4))
>PKRU: 55555554
>Call Trace:
><TASK>
>unregister_irq_proc (kernel/irq/proc.c:406) free_desc (kernel/irq/irqdesc.c:482) irq_free_descs (kernel/irq/irqdesc.c:874 kernel/irq/irqdesc.c:865) irq_domain_free_irqs (kernel/irq/irqdomain.c:1917)
>msi_domain_free_locked.part.0 (kernel/irq/msi.c:1619 kernel/irq/msi.c:1645) msi_domain_free_irqs_all_locked (kernel/irq/msi.c:1632) pci_msi_teardown_msi_irqs (drivers/pci/msi/irqdomain.c:28) pci_free_msi_irqs (drivers/pci/msi/msi.c:925) pci_disable_msix (drivers/pci/msi/api.c:200 drivers/pci/msi/api.c:193) e1000_request_irq (drivers/net/ethernet/intel/e1000e/netdev.c:2028)
>e1000e_open (drivers/net/ethernet/intel/e1000e/netdev.c:4681)
>__dev_open (net/core/dev.c:1702)
>netif_change_flags (net/core/dev.c:9806)
>do_setlink.isra.0 (net/core/rtnetlink.c:3207 (discriminator 1)) rtnetlink_rcv_msg (net/core/rtnetlink.c:7068) netlink_rcv_skb (net/netlink/af_netlink.c:2556)
>
>Fixes: 4662e82b2cb4 ("e1000e: add support for new 82574L part")
>Signed-off-by: Jiayuan Chen <jiayuan.chen@linux.dev>
>Assisted-by: Claude:claude-opus-4-8
>---
> drivers/net/ethernet/intel/e1000e/netdev.c | 13 +++++++++----
> 1 file changed, 9 insertions(+), 4 deletions(-)
>
>diff --git a/drivers/net/ethernet/intel/e1000e/netdev.c b/drivers/net/ethernet/intel/e1000e/netdev.c
>index 808e5cddd6a9..19b9823c5679 100644
>--- a/drivers/net/ethernet/intel/e1000e/netdev.c
>+++ b/drivers/net/ethernet/intel/e1000e/netdev.c
>@@ -2099,7 +2099,7 @@ void e1000e_set_interrupt_capability(struct e1000_adapter *adapter) static int e1000_request_msix(struct e1000_adapter *adapter) {
> struct net_device *netdev = adapter->netdev;
>- int err = 0, vector = 0;
>+ int err = 0, vector = 0, i;
Thank you for the patch.
I'd suggest to declare 'i' right in the loop, besides:
Reviewed-by: Piotr Kwapulinski <piotr.kwapulinski@intel.com>
>
> if (strlen(netdev->name) < (IFNAMSIZ - 5))
> snprintf(adapter->rx_ring->name,
>@@ -2111,7 +2111,7 @@ static int e1000_request_msix(struct e1000_adapter *adapter)
> e1000_intr_msix_rx, 0, adapter->rx_ring->name,
> netdev);
> if (err)
>- return err;
>+ goto err_free;
> adapter->rx_ring->itr_register = adapter->hw.hw_addr +
> E1000_EITR_82574(vector);
> adapter->rx_ring->itr_val = adapter->itr; @@ -2127,7 +2127,7 @@ static int e1000_request_msix(struct e1000_adapter *adapter)
> e1000_intr_msix_tx, 0, adapter->tx_ring->name,
> netdev);
> if (err)
>- return err;
>+ goto err_free;
> adapter->tx_ring->itr_register = adapter->hw.hw_addr +
> E1000_EITR_82574(vector);
> adapter->tx_ring->itr_val = adapter->itr; @@ -2136,11 +2136,16 @@ static int e1000_request_msix(struct e1000_adapter *adapter)
> err = request_irq(adapter->msix_entries[vector].vector,
> e1000_msix_other, 0, netdev->name, netdev);
> if (err)
>- return err;
>+ goto err_free;
>
> e1000_configure_msix(adapter);
>
> return 0;
>+
>+err_free:
>+ for (i = vector - 1; i >= 0; i--)
>+ free_irq(adapter->msix_entries[i].vector, netdev);
>+ return err;
> }
>
> /**
>--
>2.43.0
>
>
^ permalink raw reply
* [PATCH net-next v2] selftests/net: Skip srv6_end_dt46_l3vpn_test::host_vpn_tests if iproute2 too old
From: Alessio Faina @ 2026-07-13 9:57 UTC (permalink / raw)
To: netdev, linux-kselftest, Andrea Mayer
Cc: Alessio Faina, Po-Hsu Lin, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, Shuah Khan
In case iproute2 is older than version 5.14.0, released ~Sept 1, 2021,
the End.DT46 support is not available and the host_vpn_tests test contained
in the srv6_end_dt46_l3vpn_test.sh file is failing in some kernel backports.
This is the result of those tests:
################################################################################
TEST SECTION: SRv6 VPN connectivity test among hosts in the same tenant
################################################################################
TEST: IPv6 Hosts connectivity: hs-t100-1 -> hs-t100-2 (tenant 100) [ FAIL ]
TEST: IPv4 Hosts connectivity: hs-t100-1 -> hs-t100-2 (tenant 100) [ FAIL ]
TEST: IPv6 Hosts connectivity: hs-t100-2 -> hs-t100-1 (tenant 100) [ FAIL ]
TEST: IPv4 Hosts connectivity: hs-t100-2 -> hs-t100-1 (tenant 100) [ FAIL ]
TEST: IPv6 Hosts connectivity: hs-t200-3 -> hs-t200-4 (tenant 200) [ FAIL ]
TEST: IPv4 Hosts connectivity: hs-t200-3 -> hs-t200-4 (tenant 200) [ FAIL ]
TEST: IPv6 Hosts connectivity: hs-t200-4 -> hs-t200-3 (tenant 200) [ FAIL ]
TEST: IPv4 Hosts connectivity: hs-t200-4 -> hs-t200-3 (tenant 200) [ FAIL ]
To amend this, check the current running iproute2 supports the required
feature and, if not, just skip the test to avoid a failure.
Signed-off-by: Alessio Faina <alessio.faina@canonical.com>
---
v2:
- skip entire test suite if iproute2 is too old
v1: https://lore.kernel.org/netdev/20260708152745.2430714-1-alessio.faina@canonical.com/
.../testing/selftests/net/srv6_end_dt46_l3vpn_test.sh | 10 ++++++++++
1 file changed, 10 insertions(+)
diff --git a/tools/testing/selftests/net/srv6_end_dt46_l3vpn_test.sh b/tools/testing/selftests/net/srv6_end_dt46_l3vpn_test.sh
index a5e959a080bb..cd7ac03e3907 100755
--- a/tools/testing/selftests/net/srv6_end_dt46_l3vpn_test.sh
+++ b/tools/testing/selftests/net/srv6_end_dt46_l3vpn_test.sh
@@ -536,6 +536,14 @@ host_vpn_isolation_tests()
done
}
+test_iproute2_supp_or_ksft_skip()
+{
+ if ! ip route add help 2>&1 | grep -qo "End.DT46"; then
+ echo "SKIP: Missing SRv6 End.DT46 support in iproute2"
+ exit "${ksft_skip}"
+ fi
+}
+
if [ "$(id -u)" -ne 0 ];then
echo "SKIP: Need root privileges"
exit $ksft_skip
@@ -546,6 +554,8 @@ if [ ! -x "$(command -v ip)" ]; then
exit $ksft_skip
fi
+test_iproute2_supp_or_ksft_skip
+
modprobe vrf &>/dev/null
if [ ! -e /proc/sys/net/vrf/strict_mode ]; then
echo "SKIP: vrf sysctl does not exist"
--
2.43.0
^ permalink raw reply related
* [PATCH nf] netfilter: ipset: do not update comments from kernel-side hash adds
From: David Lee @ 2026-07-13 9:59 UTC (permalink / raw)
To: Pablo Neira Ayuso
Cc: David Lee, Florian Westphal, Jozsef Kadlecsik, Phil Sutter,
Dominik 'Disconnect3d' Czarnota, netfilter-devel,
coreteam, netdev, linux-kernel, stable
mtype_resize() copies comment pointers with memcpy(), not the comment objects
themselves. During the window after an entry has been copied but before the
table swap and backlog replay, the old table is still published for
packet-side updates while the replacement-table entry already holds the same
ip_set_comment_rcu pointer.
If xt_SET --add-set ... --exist hits that old entry in this window,
mtype_add() calls ip_set_init_comment() even though packet-side adds carry no
comment payload. That call frees the shared comment through the old entry, so
the replacement-table entry now holds a stale pointer. When the queued add is
replayed on the new table, mtype_add() calls ip_set_init_comment() again and
strlen() dereferences the stale pointer.
Fix this in mtype_add() by skipping ip_set_init_comment() when ext->target
marks a packet-side add. Userspace adds still update comments, while
packet-side adds can no longer free comment storage shared with a resize copy.
Fixes: f66ee0410b1c ("netfilter: ipset: Fix "INFO: rcu detected stall in hash_xxx" reports")
Cc: stable@vger.kernel.org
Signed-off-by: David Lee <david.lee@trailofbits.com>
Assisted-by: Codex:gpt-5.5
---
A reproducer triggers a KASAN slab-use-after-free in strlen() from
ip_set_init_comment() during hash_ip4_resize().
Trail of Bits has a privilege escalation PoC for this bug on a
custom kernel, which can be shared further if needed.
net/netfilter/ipset/ip_set_hash_gen.h | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/net/netfilter/ipset/ip_set_hash_gen.h b/net/netfilter/ipset/ip_set_hash_gen.h
index 8231317b0f1f..b2d77973272d 100644
--- a/net/netfilter/ipset/ip_set_hash_gen.h
+++ b/net/netfilter/ipset/ip_set_hash_gen.h
@@ -1005,7 +1005,7 @@ mtype_add(struct ip_set *set, void *value, const struct ip_set_ext *ext,
#endif
if (SET_WITH_COUNTER(set))
ip_set_init_counter(ext_counter(data, set), ext);
- if (SET_WITH_COMMENT(set))
+ if (SET_WITH_COMMENT(set) && !ext->target)
ip_set_init_comment(set, ext_comment(data, set), ext);
if (SET_WITH_SKBINFO(set))
ip_set_init_skbinfo(ext_skbinfo(data, set), ext);
--
2.43.0
^ permalink raw reply related
* Re: [PATCH v3 11/20] pmdomain: imx: use platform_device_set_of_node()
From: Ulf Hansson @ 2026-07-13 10:29 UTC (permalink / raw)
To: Bartosz Golaszewski
Cc: Lee Jones, Mark Brown, Thierry Reding, Sebastian Hesselbarth,
Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Srinivas Kandagatla, Greg Kroah-Hartman, Vinod Koul,
Rafael J. Wysocki, Danilo Krummrich, Rob Herring, Saravana Kannan,
Madhavan Srinivasan, Michael Ellerman, Nicholas Piggin,
Christophe Leroy (CS GROUP), Andi Shyti, Andy Shevchenko,
Joerg Roedel, Will Deacon, Robin Murphy, Doug Berger,
Florian Fainelli, Broadcom internal kernel review list,
Ulf Hansson, Frank Li, Sascha Hauer, Pengutronix Kernel Team,
Fabio Estevam, Matthew Brost, Thomas Hellström, Rodrigo Vivi,
David Airlie, Simona Vetter, Peter Chen, Paul Cercueil, Bin Liu,
Philipp Zabel, Maximilian Luz, Hans de Goede, Ilpo Järvinen,
Krzysztof Kozlowski, Benjamin Herrenschmidt, brgl, linux-kernel,
netdev, linux-arm-msm, linux-sound, driver-core, devicetree,
linuxppc-dev, linux-i2c, iommu, linux-pm, imx, linux-arm-kernel,
intel-xe, dri-devel, linux-usb, linux-mips, platform-driver-x86,
mfd
In-Reply-To: <20260706-pdev-fwnode-ref-v3-11-1ff028e33779@oss.qualcomm.com>
On Mon, Jul 6, 2026 at 2:45 PM Bartosz Golaszewski
<bartosz.golaszewski@oss.qualcomm.com> wrote:
>
> Ahead of reworking the reference counting logic for platform devices,
> encapsulate the assignment of the OF node for dynamically allocated
> platform devices with the provided helper.
>
> Reviewed-by: Frank Li <Frank.Li@nxp.com>
> Reviewed-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
> Signed-off-by: Bartosz Golaszewski <bartosz.golaszewski@oss.qualcomm.com>
If not too late, feel free to add:
Acked-by: Ulf Hansson <ulfh@kernel.org>
Kind regards
Uffe
> ---
> drivers/pmdomain/imx/gpc.c | 3 +--
> 1 file changed, 1 insertion(+), 2 deletions(-)
>
> diff --git a/drivers/pmdomain/imx/gpc.c b/drivers/pmdomain/imx/gpc.c
> index 42e50c9b4fb9ffb96a20a462d4eb5168942a893c..abca5f449a226fbae4213926e1395c413160c950 100644
> --- a/drivers/pmdomain/imx/gpc.c
> +++ b/drivers/pmdomain/imx/gpc.c
> @@ -487,8 +487,7 @@ static int imx_gpc_probe(struct platform_device *pdev)
> domain->ipg_rate_mhz = ipg_rate_mhz;
>
> pd_pdev->dev.parent = &pdev->dev;
> - pd_pdev->dev.of_node = of_node_get(np);
> - pd_pdev->dev.fwnode = of_fwnode_handle(np);
> + platform_device_set_of_node(pd_pdev, np);
>
> ret = platform_device_add(pd_pdev);
> if (ret) {
>
> --
> 2.47.3
>
^ permalink raw reply
* RFC: symmetric SET_MODULE_EEPROM_BY_PAGE with i2c_address for non-SFF pages
From: Ayoub Kaanich @ 2026-07-13 10:29 UTC (permalink / raw)
To: Russell King, Andrew Lunn; +Cc: netdev@vger.kernel.org
Hi,
Raising a gap in the ethtool netlink module-EEPROM API and asking whether a symmetric write-side command would be considered upstream.
Background:
ETHTOOL_MSG_MODULE_EEPROM_GET (net/ethtool/eeprom.c) already accepts an explicit i2c_address attribute alongside page/bank/offset/length, not just the SFF-8472 0x50/0x51 pair.
For SFP cages backed by phy/sfp.c + phylink with a real i2c_adapter, this lets userspace read arbitrary vendor-specific I2C addresses on the module through a standard interface, which is useful well beyond SFF-8472.
There is no equivalent SET. ethtool --set-module-eeprom (both netlink and the legacy ioctl fallback)
Concrete use case:
We (Technica Engineering) manufacture automotive SFP/SFP+ modules for 100/1000BASE-T1 and multi-gig automotive Ethernet (TE-1441, PT-1445). Alongside the standard SFF-8472 EEPROM at 0x50, these modules expose an I2C-to-MDIO gateway at I2C address 0x40 (0x80/0x81 as 8-bit read/write):
Writes to that address are relayed by the module's own logic as MDIO Clause 22/45 transactions to the embedded automotive PHY, giving access to link speed, master/slave mode, and other PHY configuration without needing register-level documentation of the PHY itself.
This isn't a one-off design choice -- at least two other automotive SFP vendor use similar patterns
* Intrepid Control Systems: https://guide.intrepidcs.com/docs/1000BASE-T1-SFP/A-Tour-of-1G-SFP-Hardware.html#i2c-communication
* FL3X: https://flex-product.com/assets/produkte/fl3x_sfp_1000base-t1/instruction_for_use_fl3x_sfp_1000base-t1.pdf
This suggests this is a de facto convention among automotive Ethernet SFP vendors rather than something specific to us. The framing on top of the raw MDIO transaction is vendor-specific, but the kernel doesn't need to understand that -- we just need raw byte-level write access to a non-default I2C address on the module, symmetric to what MODULE_EEPROM_GET already provides for reads.
Today there's no standard kernel path for this write from userspace on phylink/sfp.c-backed ports. Where the SFP's I2C bus happens to be a directly exposed i2c_adapter (board-dependent), i2c-tools works, but that's not guaranteed, and on the phylink-managed path the bus is typically owned/locked by the sfp driver rather than freely available via /dev/i2c-N.
Ask:
Would a SET_MODULE_EEPROM_BY_PAGE netlink command, mirroring the existing GET (same page/bank/offset/length/i2c_address attributes, same 128-byte half-page constraint), be something you'd consider taking?
Wanted to check direction first given Andrew's existing work on the generic SFP GET path before spending time on an RFC series that goes against the intended design.
Thanks.
Ayoub Kaanich
Principal Software Engineer
Engineering Department
Technica Engineering GmbH
Leopoldstr. 236
D - 80807 München/Munich
Telefon:
+49 89 200 07 24-XX
Fax: +49 89 200 07 24-30
E-Mail:
ayoub.kaanich@technica-engineering.de
Website: www.technica-engineering.de
________________________________________
Technica Engineering GmbH is based in Munich | Managing Directors: Pankaj Sathe, Anup Sable, Thomas Königseder | Register Court: Munich Local Court, HRB 172
759 | Ust-ID.: DE-260220745
The information contained in this e-mail is intended solely for the use of the individual or entity to whom it is addressed, and others authorized to receive
it. It may contain confidential or privileged information. If you are not the intended recipient you are notified that any disclosure, copying, distribution or taking any action in reliance on the content of this email is strictly prohibited and may be unlawful.
If you have received this communication in error, please notify us immediately by responding to this email and then delete it. Technica Engineering GmbH is neither liable for the proper and complete transmission of the information obtained or for any delay
in this receipt.
^ permalink raw reply
* [PATCH net-next v2 0/2] dpll: zl3073x: add PTP clock support
From: Ivan Vecera @ 2026-07-13 10:37 UTC (permalink / raw)
To: netdev
Cc: Arkadiusz Kubalewski, David S. Miller, Jakub Kicinski, Jiri Pirko,
Michal Schmidt, Paolo Abeni, Pasi Vaananen, Petr Oros,
Prathosh Satish, Richard Cochran, Simon Horman, Vadim Fedorenko,
linux-kernel
Add PTP hardware clock support to the zl3073x DPLL driver.
Patch 1 adds low-level channel operations for ToD read/write/adjust,
output phase step, delta frequency offset write and TIE write as
building blocks for PTP callbacks.
Patch 2 registers a PTP clock device for each DPLL channel with
gettimex64, settime64, adjtime, adjfine, adjphase, getmaxphase
and perout callbacks. Callback availability adapts to the current
channel state - adjfine requires NCO pin connected, adjphase uses
TIE write when tracking a reference, and adjtime selects the
appropriate mechanism automatically.
Link: https://lore.kernel.org/netdev/20260708170527.916035-1-ivecera@redhat.com/
Changes:
v2:
- Squash low-level ops patches (1+3) into patch 1.
- Squash PTP callback patches (2+4) into patch 2, register PTP
clock regardless of initial channel state.
Ivan Vecera (2):
dpll: zl3073x: add channel ToD, phase step and TIE operations
dpll: zl3073x: add PTP clock support
drivers/dpll/zl3073x/Kconfig | 6 +-
drivers/dpll/zl3073x/chan.c | 307 +++++++++++++++++++
drivers/dpll/zl3073x/chan.h | 48 +++
drivers/dpll/zl3073x/core.c | 8 +
drivers/dpll/zl3073x/core.h | 8 +
drivers/dpll/zl3073x/dpll.c | 575 +++++++++++++++++++++++++++++++++--
drivers/dpll/zl3073x/dpll.h | 6 +
drivers/dpll/zl3073x/regs.h | 56 ++++
8 files changed, 978 insertions(+), 36 deletions(-)
base-commit: 4a13f31a92f35161b499bf29638336885259da78
--
2.53.0
^ permalink raw reply
* [PATCH net-next v2 2/2] dpll: zl3073x: add PTP clock support
From: Ivan Vecera @ 2026-07-13 10:37 UTC (permalink / raw)
To: netdev
Cc: Chris du Quesnay, Arkadiusz Kubalewski, David S. Miller,
Jakub Kicinski, Jiri Pirko, Michal Schmidt, Paolo Abeni,
Pasi Vaananen, Petr Oros, Prathosh Satish, Richard Cochran,
Simon Horman, Vadim Fedorenko, linux-kernel
In-Reply-To: <20260713103739.1553284-1-ivecera@redhat.com>
Add PTP clock support for the ZL3073x DPLL driver. A PTP clock device
is registered for each DPLL channel regardless of the initial channel
state, providing gettimex64, settime64, adjtime, adjfine, adjphase,
getmaxphase and perout callbacks.
Callback availability depends on the current channel state:
- adjfine: when NCO pin is connected (returns -EOPNOTSUPP otherwise)
- adjphase: available when tracking a reference, uses TIE write
- adjtime: always available and uses
* phase step for sub-second deltas when NCO pin is connected
* TIE write when tracking a reference
* plain ToD read-modify-write otherwise
- gettime/settime: always available
The adjtime callback splits multi-second adjustments into a ToD
read-modify-write for the seconds part and a sub-second mechanism
(phase step or TIE write) for the remainder. On partial failure
where seconds were already committed, success is returned to
prevent the PTP servo from retrying and applying seconds again.
Output pins eligible for periodic output (single-ended, marked in
output_step_time_mask, supporting 1 Hz) are registered as PTP perout
channels. Enabling perout sets the output frequency to 1 Hz via the
existing DPLL frequency_set path.
All PTP callbacks are serialized by the existing per-DPLL zldpll->lock
mutex, which is also used by DPLL pin and device callbacks.
Tested-by: Chris du Quesnay <Chris.duQuesnay@microchip.com>
Signed-off-by: Ivan Vecera <ivecera@redhat.com>
---
drivers/dpll/zl3073x/Kconfig | 6 +-
drivers/dpll/zl3073x/dpll.c | 575 +++++++++++++++++++++++++++++++++--
drivers/dpll/zl3073x/dpll.h | 6 +
3 files changed, 551 insertions(+), 36 deletions(-)
diff --git a/drivers/dpll/zl3073x/Kconfig b/drivers/dpll/zl3073x/Kconfig
index 5bbca14005813..d0574ad571d3f 100644
--- a/drivers/dpll/zl3073x/Kconfig
+++ b/drivers/dpll/zl3073x/Kconfig
@@ -2,7 +2,7 @@
config ZL3073X
tristate "Microchip Azurite DPLL/PTP/SyncE devices" if COMPILE_TEST
- depends on NET
+ depends on NET && PTP_1588_CLOCK_OPTIONAL
select DPLL
select NET_DEVLINK
select REGMAP
@@ -16,7 +16,7 @@ config ZL3073X
config ZL3073X_I2C
tristate "I2C bus implementation for Microchip Azurite devices"
- depends on I2C && NET
+ depends on I2C && NET && PTP_1588_CLOCK_OPTIONAL
select REGMAP_I2C
select ZL3073X
help
@@ -28,7 +28,7 @@ config ZL3073X_I2C
config ZL3073X_SPI
tristate "SPI bus implementation for Microchip Azurite devices"
- depends on NET && SPI
+ depends on NET && SPI && PTP_1588_CLOCK_OPTIONAL
select REGMAP_SPI
select ZL3073X
help
diff --git a/drivers/dpll/zl3073x/dpll.c b/drivers/dpll/zl3073x/dpll.c
index d91f52b58eae3..7c11de35821ae 100644
--- a/drivers/dpll/zl3073x/dpll.c
+++ b/drivers/dpll/zl3073x/dpll.c
@@ -2,6 +2,7 @@
#include <linux/bits.h>
#include <linux/bitfield.h>
+#include <linux/cleanup.h>
#include <linux/bug.h>
#include <linux/container_of.h>
#include <linux/dev_printk.h>
@@ -14,6 +15,7 @@
#include <linux/netlink.h>
#include <linux/platform_device.h>
#include <linux/property.h>
+#include <linux/ptp_clock_kernel.h>
#include <linux/slab.h>
#include <linux/sprintf.h>
@@ -42,6 +44,7 @@
* @phase_offset: last saved pin phase offset
* @freq_offset: last saved fractional frequency offset
* @measured_freq: last saved measured frequency
+ * @perout_idx: PTP perout channel index, or -1 if not perout-eligible
*/
struct zl3073x_dpll_pin {
struct list_head list;
@@ -59,6 +62,7 @@ struct zl3073x_dpll_pin {
s64 phase_offset;
s64 freq_offset;
u32 measured_freq;
+ s8 perout_idx;
};
/*
@@ -936,21 +940,17 @@ zl3073x_dpll_output_pin_frequency_get(const struct dpll_pin *dpll_pin,
}
static int
-zl3073x_dpll_output_pin_frequency_set(const struct dpll_pin *dpll_pin,
- void *pin_priv,
- const struct dpll_device *dpll,
- void *dpll_priv, u64 frequency,
- struct netlink_ext_ack *extack)
+__zl3073x_dpll_output_pin_frequency_set(struct zl3073x_dpll *zldpll,
+ struct zl3073x_dpll_pin *pin,
+ u64 frequency)
{
- struct zl3073x_dpll *zldpll = dpll_priv;
struct zl3073x_dev *zldev = zldpll->dev;
- struct zl3073x_dpll_pin *pin = pin_priv;
const struct zl3073x_synth *synth;
u32 new_div, synth_freq;
struct zl3073x_out out;
u8 out_id;
- guard(mutex)(&zldpll->lock);
+ lockdep_assert_held(&zldpll->lock);
out_id = zl3073x_output_pin_out_get(pin->id);
out = *zl3073x_out_state_get(zldev, out_id);
@@ -1011,6 +1011,21 @@ zl3073x_dpll_output_pin_frequency_set(const struct dpll_pin *dpll_pin,
return zl3073x_out_state_set(zldev, out_id, &out);
}
+static int
+zl3073x_dpll_output_pin_frequency_set(const struct dpll_pin *dpll_pin,
+ void *pin_priv,
+ const struct dpll_device *dpll,
+ void *dpll_priv, u64 frequency,
+ struct netlink_ext_ack *extack)
+{
+ struct zl3073x_dpll *zldpll = dpll_priv;
+
+ guard(mutex)(&zldpll->lock);
+
+ return __zl3073x_dpll_output_pin_frequency_set(zldpll, pin_priv,
+ frequency);
+}
+
static int
zl3073x_dpll_output_pin_phase_adjust_get(const struct dpll_pin *dpll_pin,
void *pin_priv,
@@ -1608,6 +1623,7 @@ zl3073x_dpll_pin_alloc(struct zl3073x_dpll *zldpll, enum dpll_pin_direction dir,
pin->dpll = zldpll;
pin->dir = dir;
pin->id = id;
+ pin->perout_idx = -1;
return pin;
}
@@ -1626,6 +1642,44 @@ zl3073x_dpll_pin_free(struct zl3073x_dpll_pin *pin)
kfree(pin);
}
+/**
+ * zl3073x_dpll_pin_is_perout - check if output pin is perout-eligible
+ * @pin: DPLL pin to check
+ * @props: pin properties (already fetched by caller)
+ *
+ * An output pin is eligible for PTP periodic output if it is single-ended
+ * (not differential) and supports 1 Hz in its frequency list.
+ *
+ * Return: true if eligible, false otherwise
+ */
+static bool
+zl3073x_dpll_pin_is_perout(struct zl3073x_dpll_pin *pin,
+ const struct zl3073x_pin_props *props)
+{
+ struct zl3073x_dpll *zldpll = pin->dpll;
+ const struct zl3073x_chan *chan;
+ u8 out_id;
+ int i;
+
+ if (zl3073x_dpll_is_input_pin(pin))
+ return false;
+
+ out_id = zl3073x_output_pin_out_get(pin->id);
+ if (zl3073x_dev_out_is_diff(zldpll->dev, out_id))
+ return false;
+
+ chan = zl3073x_chan_state_get(zldpll->dev, zldpll->id);
+ if (!zl3073x_chan_is_out_stepped(chan, out_id))
+ return false;
+
+ for (i = 0; i < props->dpll_props.freq_supported_num; i++) {
+ if (props->dpll_props.freq_supported[i].min == 1)
+ return true;
+ }
+
+ return false;
+}
+
/**
* zl3073x_dpll_pin_register - register DPLL pin
* @pin: pointer to DPLL pin
@@ -1667,6 +1721,8 @@ zl3073x_dpll_pin_register(struct zl3073x_dpll_pin *pin, u32 index)
if (pin->prio == ZL_DPLL_REF_PRIO_NONE)
/* Clamp prio to max value */
pin->prio = ZL_DPLL_REF_PRIO_MAX;
+ } else if (zl3073x_dpll_pin_is_perout(pin, props)) {
+ pin->perout_idx = zldpll->ptp_info.n_per_out++;
}
/* Create or get existing DPLL pin */
@@ -1897,6 +1953,8 @@ zl3073x_dpll_pins_register(struct zl3073x_dpll *zldpll)
u8 id, index;
int rc;
+ zldpll->ptp_info.n_per_out = 0;
+
/* Process input pins */
for (index = 0; index < ZL3073X_NUM_PINS; index++) {
/* First input pins and then output pins */
@@ -2280,45 +2338,447 @@ zl3073x_dpll_init_fine_phase_adjust(struct zl3073x_dev *zldev)
return rc;
}
+/* Maximum frequency adjustment: +-1% of nominal in ppb */
+#define ZL3073X_DPLL_PTP_MAX_ADJ 10000000
+
/**
- * zl3073x_dpll_alloc - allocate DPLL device
- * @zldev: pointer to zl3073x device
- * @ch: DPLL channel number
+ * zl3073x_dpll_ptp_gettimex64 - read current time from ToD counters
+ * @info: PTP clock info
+ * @ts: timespec to store current time
+ * @sts: optional system timestamp pair for cross-timestamping
*
- * Allocates DPLL device structure for given DPLL channel.
+ * Return: 0 on success, <0 on error
+ */
+static int zl3073x_dpll_ptp_gettimex64(struct ptp_clock_info *info,
+ struct timespec64 *ts,
+ struct ptp_system_timestamp *sts)
+{
+ struct zl3073x_dpll *zldpll = container_of(info, struct zl3073x_dpll,
+ ptp_info);
+
+ guard(mutex)(&zldpll->lock);
+
+ return zl3073x_chan_tod_read(zldpll->dev, zldpll->id, false, ts, sts);
+}
+
+/**
+ * zl3073x_dpll_ptp_settime64 - set ToD counters to given time
+ * @info: PTP clock info
+ * @ts: timespec with time to set
*
- * Return: pointer to DPLL device on success, error pointer on error
+ * Return: 0 on success, <0 on error
*/
-struct zl3073x_dpll *
-zl3073x_dpll_alloc(struct zl3073x_dev *zldev, u8 ch)
+static int zl3073x_dpll_ptp_settime64(struct ptp_clock_info *info,
+ const struct timespec64 *ts)
{
- struct zl3073x_dpll *zldpll;
+ struct zl3073x_dpll *zldpll = container_of(info, struct zl3073x_dpll,
+ ptp_info);
- zldpll = kzalloc_obj(*zldpll);
- if (!zldpll)
- return ERR_PTR(-ENOMEM);
+ guard(mutex)(&zldpll->lock);
- zldpll->dev = zldev;
- zldpll->id = ch;
- mutex_init(&zldpll->lock);
- INIT_LIST_HEAD(&zldpll->pins);
+ return zl3073x_chan_tod_write(zldpll->dev, zldpll->id, *ts);
+}
- return zldpll;
+/**
+ * zl3073x_dpll_ptp_adjtime_phase_step - adjust sub-second time via phase step
+ * @zldpll: DPLL channel
+ * @delta: time adjustment in nanoseconds (must be within (-NSEC_PER_SEC,
+ * NSEC_PER_SEC))
+ *
+ * Uses the output phase step mechanism with tod_step=1 to adjust both
+ * the output clock phase and the ToD counter simultaneously. This keeps
+ * outputs and ToD coherent. Only valid for NCO.
+ *
+ * Outputs are grouped by synthesizer since the phase step value is in
+ * synthesizer clock cycles. The first synth group with enabled outputs
+ * uses tod_step to adjust both outputs and the ToD counter. Remaining
+ * groups step outputs only. If no synth has enabled outputs, the ToD
+ * counter is stepped alone using an empty output mask (the FW uses the
+ * lowest-ID synth's period for the conversion).
+ *
+ * Return: 0 on success, -EOPNOTSUPP if no synths available, <0 on error
+ */
+static int zl3073x_dpll_ptp_adjtime_phase_step(struct zl3073x_dpll *zldpll,
+ s64 delta)
+{
+ u16 synth_mask[ZL3073X_NUM_SYNTHS] = {};
+ struct zl3073x_dev *zldev = zldpll->dev;
+ const struct zl3073x_synth *synth;
+ const struct zl3073x_chan *chan;
+ struct zl3073x_dpll_pin *pin;
+ u32 first_synth_freq = 0;
+ bool tod_stepped = false;
+ s32 step_cycles;
+ u32 synth_freq;
+ int rc;
+ u8 i;
+
+ chan = zl3073x_chan_state_get(zldev, zldpll->id);
+
+ /* Build per-synth output masks from registered output pins */
+ list_for_each_entry(pin, &zldpll->pins, list) {
+ u8 out_id, synth_id;
+
+ if (zl3073x_dpll_is_input_pin(pin))
+ continue;
+
+ out_id = zl3073x_output_pin_out_get(pin->id);
+
+ if (!zl3073x_chan_is_out_stepped(chan, out_id))
+ continue;
+
+ synth_id = zl3073x_dev_out_synth_get(zldev, out_id);
+ if (synth_id >= ZL3073X_NUM_SYNTHS) {
+ dev_warn(zldev->dev, "Unexpected synth id for OUT%u\n",
+ out_id);
+ continue;
+ }
+ synth_mask[synth_id] |= BIT(out_id);
+ }
+
+ /* Process each synth group */
+ for (i = 0; i < ZL3073X_NUM_SYNTHS; i++) {
+ synth = zl3073x_synth_state_get(zldev, i);
+ if (!zl3073x_synth_is_enabled(synth) ||
+ zl3073x_synth_dpll_get(synth) != zldpll->id)
+ continue;
+
+ synth_freq = zl3073x_synth_freq_get(synth);
+
+ /* Remember lowest-ID synth freq for ToD-only fallback */
+ if (!first_synth_freq)
+ first_synth_freq = synth_freq;
+
+ if (!synth_mask[i])
+ continue;
+
+ /* Safe for s32: max synth freq is 750 MHz */
+ step_cycles = div_s64(delta * synth_freq, NSEC_PER_SEC);
+
+ rc = zl3073x_chan_phase_step(zldev, zldpll->id,
+ synth_mask[i], step_cycles,
+ !tod_stepped);
+ if (rc) {
+ if (tod_stepped) {
+ dev_warn(zldev->dev,
+ "Partial phase step failure\n");
+ return 0;
+ }
+ return rc;
+ }
+ tod_stepped = true;
+ }
+
+ if (!first_synth_freq)
+ return -EOPNOTSUPP;
+
+ /* No enabled outputs found; step ToD counter only using the
+ * lowest-ID synth's period (empty output mask).
+ */
+ if (!tod_stepped) {
+ step_cycles = div_s64(delta * first_synth_freq, NSEC_PER_SEC);
+ return zl3073x_chan_phase_step(zldev, zldpll->id, 0,
+ step_cycles, true);
+ }
+
+ return 0;
}
/**
- * zl3073x_dpll_free - free DPLL device
- * @zldpll: pointer to zl3073x_dpll structure
+ * zl3073x_dpll_ptp_adjtime - adjust PTP clock time
+ * @info: PTP clock info
+ * @delta: time adjustment in nanoseconds
*
- * Deallocates given DPLL device previously allocated by @zl3073x_dpll_alloc.
+ * For NCO, large deltas (>= 1 second) are split into a ToD
+ * read-modify-write for the seconds part and an output phase step for
+ * the sub-second remainder. Sub-second deltas use phase step directly,
+ * falling back to ToD read-modify-write if phase step is unavailable.
+ * In AUTO/REFLOCK modes, large deltas are split into ToD
+ * read-modify-write for seconds and TIE write for the sub-second
+ * remainder. Sub-second deltas use TIE write directly.
+ *
+ * Return: 0 on success, <0 on error
*/
-void
-zl3073x_dpll_free(struct zl3073x_dpll *zldpll)
+static int zl3073x_dpll_ptp_adjtime(struct ptp_clock_info *info, s64 delta)
{
- WARN(zldpll->dpll_dev, "DPLL device is still registered\n");
+ struct zl3073x_dpll *zldpll = container_of(info, struct zl3073x_dpll,
+ ptp_info);
+ struct zl3073x_dev *zldev = zldpll->dev;
+ const struct zl3073x_chan *chan;
+ bool sec_adjusted = false;
+ struct timespec64 ts;
+ int rc;
- mutex_destroy(&zldpll->lock);
- kfree(zldpll);
+ if (!delta)
+ return 0;
+
+ guard(mutex)(&zldpll->lock);
+
+ /* Modes without phase step or TIE use plain ToD adjust */
+ chan = zl3073x_chan_state_get(zldev, zldpll->id);
+ if (!zl3073x_chan_mode_is_nco(chan) &&
+ !zl3073x_chan_mode_supports_tie(chan))
+ return zl3073x_chan_tod_adjust(zldev, zldpll->id,
+ ns_to_timespec64(delta));
+
+ /* Split off seconds via ToD read-modify-write so the sub-second
+ * remainder can be applied through the output-coherent mechanism
+ * (phase step or TIE write).
+ */
+ if (abs(delta) >= NSEC_PER_SEC) {
+ s32 remainder;
+
+ ts.tv_sec = div_s64_rem(delta, NSEC_PER_SEC, &remainder);
+ ts.tv_nsec = 0;
+ delta = remainder;
+
+ rc = zl3073x_chan_tod_adjust(zldev, zldpll->id, ts);
+ if (rc)
+ return rc;
+
+ sec_adjusted = true;
+
+ /* No sub-second remainder, done */
+ if (!delta)
+ return 0;
+ }
+
+ /* Apply sub-second delta via phase step (NCO) or TIE write */
+ if (zl3073x_chan_mode_is_nco(chan)) {
+ rc = zl3073x_dpll_ptp_adjtime_phase_step(zldpll, delta);
+ if (!rc)
+ return 0;
+ } else {
+ rc = zl3073x_chan_tie_write(zldev, zldpll->id, delta);
+ if (!rc)
+ return 0;
+ }
+
+ /* Phase step or TIE write failed, fall back to ToD adjust */
+ rc = zl3073x_chan_tod_adjust(zldev, zldpll->id,
+ ns_to_timespec64(delta));
+
+ /* In the unlikely event that both phase step/TIE write and fallback
+ * ToD adjust fail after seconds were already committed, return
+ * success to prevent the PTP servo from retrying the full delta and
+ * applying seconds again. The sub-second residual will self-correct
+ * in the next servo cycle.
+ */
+ if (rc && sec_adjusted) {
+ dev_warn(zldev->dev,
+ "Sub-second adjustment failed after seconds applied\n");
+ return 0;
+ }
+
+ return rc;
+}
+
+/**
+ * zl3073x_dpll_ptp_adjfine - adjust PTP clock frequency
+ * @info: PTP clock info
+ * @scaled_ppm: frequency adjustment in scaled ppm (ppm * 2^16)
+ *
+ * Only supported for NCO. Writes the delta frequency offset register.
+ *
+ * Return: 0 on success, -EOPNOTSUPP if NCO pin is not connected, <0 on error
+ */
+static int
+zl3073x_dpll_ptp_adjfine(struct ptp_clock_info *info, long scaled_ppm)
+{
+ struct zl3073x_dpll *zldpll = container_of(info, struct zl3073x_dpll,
+ ptp_info);
+ const struct zl3073x_chan *chan;
+ s64 offset;
+
+ /* Convert scaled_ppm to df_offset in 2^-48 steps:
+ * df_offset = -(scaled_ppm * 2^32) / 10^6
+ *
+ * Simplify to avoid overflow:
+ * df_offset = -(scaled_ppm * 2^26) / 5^6
+ * df_offset = -(scaled_ppm * 67108864) / 15625
+ */
+ offset = -div_s64((s64)scaled_ppm * 67108864LL, 15625);
+
+ guard(mutex)(&zldpll->lock);
+
+ chan = zl3073x_chan_state_get(zldpll->dev, zldpll->id);
+ if (!zl3073x_chan_mode_is_nco(chan))
+ return scaled_ppm ? -EOPNOTSUPP : 0;
+ if (offset == chan->df_offset)
+ return 0;
+
+ return zl3073x_chan_df_offset_set(zldpll->dev, zldpll->id, offset);
+}
+
+/**
+ * zl3073x_dpll_ptp_adjphase - adjust PTP clock phase
+ * @info: PTP clock info
+ * @delta: phase adjustment in nanoseconds
+ *
+ * Only supported in AUTO and REFLOCK modes. Uses TIE write for
+ * nanosecond resolution phase adjustment.
+ *
+ * Return: 0 on success, -EOPNOTSUPP if mode doesn't support TIE, <0 on error
+ */
+static int zl3073x_dpll_ptp_adjphase(struct ptp_clock_info *info, s32 delta)
+{
+ struct zl3073x_dpll *zldpll = container_of(info, struct zl3073x_dpll,
+ ptp_info);
+ struct zl3073x_dev *zldev = zldpll->dev;
+ const struct zl3073x_chan *chan;
+
+ if (!delta)
+ return 0;
+
+ guard(mutex)(&zldpll->lock);
+
+ chan = zl3073x_chan_state_get(zldev, zldpll->id);
+
+ if (!zl3073x_chan_mode_supports_tie(chan))
+ return -EOPNOTSUPP;
+
+ return zl3073x_chan_tie_write(zldev, zldpll->id, delta);
+}
+
+static s32
+zl3073x_dpll_ptp_getmaxphase(struct ptp_clock_info *info __always_unused)
+{
+ /* HW limits TIE write to +-1 second. Return the constant HW
+ * limit and let adjphase handle mode-specific checks.
+ */
+ return NSEC_PER_SEC - 1;
+}
+
+/**
+ * zl3073x_dpll_ptp_perout_find_pin - find pin by perout channel index
+ * @zldpll: DPLL channel
+ * @idx: perout channel index to find
+ *
+ * Return: pointer to the pin, or NULL if not found
+ */
+static struct zl3073x_dpll_pin *
+zl3073x_dpll_ptp_perout_find_pin(struct zl3073x_dpll *zldpll, int idx)
+{
+ struct zl3073x_dpll_pin *pin;
+
+ list_for_each_entry(pin, &zldpll->pins, list) {
+ if (pin->perout_idx == idx)
+ return pin;
+ }
+
+ return NULL;
+}
+
+/**
+ * zl3073x_dpll_ptp_enable - enable/disable PTP clock functions
+ * @info: PTP clock info
+ * @rq: the requested clock function and parameters
+ * @on: true to enable, false to disable
+ *
+ * Handles PTP_CLK_REQ_PEROUT requests. Only 1PPS (period = 1s) is supported.
+ * On enable, configures the output divider for 1 Hz. On disable, does nothing
+ * as the signal is not disabled.
+ *
+ * Return: 0 on success, <0 on error
+ */
+static int zl3073x_dpll_ptp_enable(struct ptp_clock_info *info,
+ struct ptp_clock_request *rq, int on)
+{
+ struct zl3073x_dpll *zldpll = container_of(info, struct zl3073x_dpll,
+ ptp_info);
+ struct zl3073x_dpll_pin *pin;
+ unsigned int idx;
+ int rc;
+
+ if (rq->type != PTP_CLK_REQ_PEROUT)
+ return -EOPNOTSUPP;
+
+ idx = rq->perout.index;
+ if (idx >= info->n_per_out)
+ return -EINVAL;
+
+ /* Toggling output pins can produce runt pulses, so disable
+ * is a no-op.
+ */
+ if (!on)
+ return 0;
+
+ /* Only accept exactly 1PPS (period.sec == 1, nsec == 0) */
+ if (rq->perout.period.sec != 1 || rq->perout.period.nsec != 0)
+ return -EINVAL;
+
+ mutex_lock(&zldpll->lock);
+
+ pin = zl3073x_dpll_ptp_perout_find_pin(zldpll, idx);
+ if (!pin) {
+ mutex_unlock(&zldpll->lock);
+ return -EINVAL;
+ }
+
+ /* Already at 1 Hz, nothing to do */
+ if (zl3073x_dev_output_pin_freq_get(zldpll->dev, pin->id) == 1) {
+ mutex_unlock(&zldpll->lock);
+ return 0;
+ }
+
+ rc = __zl3073x_dpll_output_pin_frequency_set(zldpll, pin, 1);
+
+ mutex_unlock(&zldpll->lock);
+
+ if (!rc)
+ dpll_pin_change_ntf(pin->dpll_pin);
+
+ return rc;
+}
+
+static const struct ptp_clock_info zl3073x_dpll_ptp_clock_info = {
+ .owner = THIS_MODULE,
+ .max_adj = ZL3073X_DPLL_PTP_MAX_ADJ,
+ .gettimex64 = zl3073x_dpll_ptp_gettimex64,
+ .settime64 = zl3073x_dpll_ptp_settime64,
+ .adjtime = zl3073x_dpll_ptp_adjtime,
+ .adjfine = zl3073x_dpll_ptp_adjfine,
+ .adjphase = zl3073x_dpll_ptp_adjphase,
+ .getmaxphase = zl3073x_dpll_ptp_getmaxphase,
+ .enable = zl3073x_dpll_ptp_enable,
+};
+
+/**
+ * zl3073x_dpll_ptp_register - register PTP clock for a DPLL channel
+ * @zldpll: DPLL channel to register PTP clock for
+ *
+ * Return: 0 on success, <0 on error
+ */
+static int zl3073x_dpll_ptp_register(struct zl3073x_dpll *zldpll)
+{
+ struct zl3073x_dev *zldev = zldpll->dev;
+ struct ptp_clock *ptp_clock;
+
+ snprintf(zldpll->ptp_info.name, sizeof(zldpll->ptp_info.name),
+ "zl3073x-dpll%u", zldpll->id);
+
+ ptp_clock = ptp_clock_register(&zldpll->ptp_info, zldev->dev);
+ if (IS_ERR(ptp_clock)) {
+ dev_err(zldev->dev, "Failed to register PTP clock for DPLL%u\n",
+ zldpll->id);
+ return PTR_ERR(ptp_clock);
+ }
+
+ zldpll->ptp_clock = ptp_clock;
+
+ return 0;
+}
+
+/**
+ * zl3073x_dpll_ptp_unregister - unregister PTP clock for a DPLL channel
+ * @zldpll: DPLL channel to unregister PTP clock for
+ */
+static void zl3073x_dpll_ptp_unregister(struct zl3073x_dpll *zldpll)
+{
+ if (!IS_ERR_OR_NULL(zldpll->ptp_clock)) {
+ ptp_clock_unregister(zldpll->ptp_clock);
+ zldpll->ptp_clock = NULL;
+ }
}
/**
@@ -2403,6 +2863,48 @@ zl3073x_dpll_ref_sync_pairs_register(struct zl3073x_dpll *zldpll)
return 0;
}
+/**
+ * zl3073x_dpll_alloc - allocate DPLL device
+ * @zldev: pointer to zl3073x device
+ * @ch: DPLL channel number
+ *
+ * Allocates DPLL device structure for given DPLL channel.
+ *
+ * Return: pointer to DPLL device on success, error pointer on error
+ */
+struct zl3073x_dpll *
+zl3073x_dpll_alloc(struct zl3073x_dev *zldev, u8 ch)
+{
+ struct zl3073x_dpll *zldpll;
+
+ zldpll = kzalloc_obj(*zldpll);
+ if (!zldpll)
+ return ERR_PTR(-ENOMEM);
+
+ zldpll->dev = zldev;
+ zldpll->id = ch;
+ zldpll->ptp_info = zl3073x_dpll_ptp_clock_info;
+ mutex_init(&zldpll->lock);
+ INIT_LIST_HEAD(&zldpll->pins);
+
+ return zldpll;
+}
+
+/**
+ * zl3073x_dpll_free - free DPLL device
+ * @zldpll: pointer to zl3073x_dpll structure
+ *
+ * Deallocates given DPLL device previously allocated by @zl3073x_dpll_alloc.
+ */
+void
+zl3073x_dpll_free(struct zl3073x_dpll *zldpll)
+{
+ WARN(zldpll->dpll_dev, "DPLL device is still registered\n");
+
+ mutex_destroy(&zldpll->lock);
+ kfree(zldpll);
+}
+
/**
* zl3073x_dpll_register - register DPLL device and all its pins
* @zldpll: pointer to zl3073x_dpll structure
@@ -2433,6 +2935,13 @@ zl3073x_dpll_register(struct zl3073x_dpll *zldpll)
return rc;
}
+ rc = zl3073x_dpll_ptp_register(zldpll);
+ if (rc) {
+ zl3073x_dpll_pins_unregister(zldpll);
+ zl3073x_dpll_device_unregister(zldpll);
+ return rc;
+ }
+
return 0;
}
@@ -2446,7 +2955,7 @@ zl3073x_dpll_register(struct zl3073x_dpll *zldpll)
void
zl3073x_dpll_unregister(struct zl3073x_dpll *zldpll)
{
- /* Unregister all pins and dpll */
+ zl3073x_dpll_ptp_unregister(zldpll);
zl3073x_dpll_pins_unregister(zldpll);
zl3073x_dpll_device_unregister(zldpll);
}
diff --git a/drivers/dpll/zl3073x/dpll.h b/drivers/dpll/zl3073x/dpll.h
index faebc402ba1b7..9b5f72c33849c 100644
--- a/drivers/dpll/zl3073x/dpll.h
+++ b/drivers/dpll/zl3073x/dpll.h
@@ -5,6 +5,8 @@
#include <linux/dpll.h>
#include <linux/list.h>
+#include <linux/mutex.h>
+#include <linux/ptp_clock_kernel.h>
#include "core.h"
@@ -22,6 +24,8 @@
* @type: DPLL type (PPS or EEC)
* @lock_status: last saved DPLL lock status
* @pins: list of pins
+ * @ptp_info: PTP clock info
+ * @ptp_clock: registered PTP clock (or NULL)
*/
struct zl3073x_dpll {
struct list_head list;
@@ -36,6 +40,8 @@ struct zl3073x_dpll {
enum dpll_type type;
enum dpll_lock_status lock_status;
struct list_head pins;
+ struct ptp_clock_info ptp_info;
+ struct ptp_clock *ptp_clock;
};
struct zl3073x_dpll *zl3073x_dpll_alloc(struct zl3073x_dev *zldev, u8 ch);
--
2.53.0
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox