dpll: add a simulated DPLL device driver
Subsystems (5)
v3 cover dpll: add a simulated DPLL device driver
The DPLL subsystem has no in-tree driver that can run without real hardware, so core changes and userspace tooling are hard to exercise in CI, and the pin-on-pin and reference sync paths see almost no coverage outside the vendors' labs. This series adds dpllsim, a simulated DPLL device driver, together with selftests that run against it.
The driver is controlled through configfs, following gpio-sim. Writing a clock ID into a device directory and setting "live" to 1 registers a DPLL device with the core. Pins are directories under pins/ with their type, frequency, direction and priority as attributes, and all of them are registered when the device goes live. The split between creating the configuration and going live keeps the register and unregister paths reachable from userspace, so they can be exercised repeatedly without reloading the module. Nothing in the simulator is a stable ABI.
The patches follow the shape of the zl3073x introduction. The driver starts with the device alone, then gains pins and the static pin operations one at a time (state, priority, frequency, phase adjustment), and every feature patch is followed by the selftest that covers it. The last two patches add concurrency stress tests and reference tracker leak tests for the lifecycle paths.
The simulation loop (lock state machine, phase and frequency measurements, monitoring features), the topology features (MUX pin hierarchies, reference sync, media failure injection), netdevsim integration and device mode setting follow in two further series once this one is in, to stay within the 15 patch limit.
The driver and the selftests were developed with an LLM coding assistant (Claude Code) working under my direction. I chose the configfs design, the split into one feature per patch and the test scenarios, and I reviewed every line. The assistant wrote most of the code and the first drafts of the changelogs, and it found and fixed bugs by running the suite on the lab host. The series then went through several rounds of an LLM based patch reviewer whose findings were fixed, and through a mutation test in which deliberately broken driver and core code was caught by the tests.
The selftests live in tools/testing/selftests/drivers/dpll and run with "make -C tools/testing/selftests TARGETS=drivers/dpll run_tests". By default they create dpllsim devices; with DPLL_DEV_ID=<id> the basic and feature scripts run against an existing device instead, so the same suite exercises real hardware, and DPLL_DRIVER=<module name> picks the first device of that driver, dpllsim for a simulated one. The whole suite was run through the kselftest runner in a KVM guest and on an ice E810 host with CONFIG_KASAN, CONFIG_PROVE_LOCKING and CONFIG_DPLL_REFCNT_TRACKER enabled, against the simulator and, with DPLL_DRIVER=ice, against the ice DPLL, where the tests that need a simulator skip and the pin state and priorities of the hardware are left as they were. All pass with no warnings in dmesg.
---
v3:
- move the selftests from tools/testing/selftests/drivers/net/dpll to tools/testing/selftests/drivers/dpll, with the shared library as a TEST_FILES entry of that directory, so that the tests sit where the drivers do
- let dpllsim refuse pin states that are not valid in the device mode:
connected is refused in automatic mode, which the simulator runs in, as Documentation/driver-api/dpll.rst reserves it for manual mode (Ivan Vecera)
- make the pin state test follow the mode of the device under test and check on the simulator that connected is refused in automatic mode; cycle the stress tests through the states of automatic mode only
- add DPLL_DRIVER to pick the device of a given driver, or the simulator with DPLL_DRIVER=dpllsim, on a system that has hardware DPLL devices
- describe what the leak check waits for instead of a concurrent dump holding references, which nothing does (Ivan Vecera)
- carry the Reviewed-by and the set Acked-by of v2 over to the patches whose code did not change or only moved; patches 2, 5 and 6 changed and ask for a new look
v2:
- sort tools/testing/selftests/drivers/net/dpll/config alphabetically and make the scripts clean under the shellcheck run of the netdev CI (quoted exit and return values, shellcheck directives for the globals shared between the library and the scripts and for the optional argument of dpllsim_setup_standard_device())
v2: https://lore.kernel.org/all/[email protected]/
v1: https://lore.kernel.org/all/[email protected]/
---
Petr Oros (14):
dpll: dpllsim: Add simulated DPLL device driver
selftests: dpll: add test library and device lifecycle tests
dpll: dpllsim: Add configurable pins
selftests: dpll: add pin enumeration and property tests
dpll: dpllsim: Add support to set input pin state
selftests: dpll: add pin state tests
dpll: dpllsim: Add support to get/set priority on input pins
selftests: dpll: add pin priority test
dpll: dpllsim: Add support to set frequency on pins
selftests: dpll: add pin frequency test
dpll: dpllsim: Add support to adjust phase
selftests: dpll: add pin phase adjust test
selftests: dpll: add concurrency stress tests
selftests: dpll: add reference leak tests
Documentation/driver-api/dpllsim.rst | 72 ++ Documentation/driver-api/index.rst | 1 + MAINTAINERS | 8 + drivers/dpll/Kconfig | 1 + drivers/dpll/Makefile | 1 + drivers/dpll/dpllsim/Kconfig | 16 + drivers/dpll/dpllsim/Makefile | 4 + drivers/dpll/dpllsim/core.c | 471 ++++++++++ drivers/dpll/dpllsim/dpll.c | 498 ++++++++++ drivers/dpll/dpllsim/dpllsim.h | 99 ++ tools/testing/selftests/Makefile | 1 + tools/testing/selftests/drivers/dpll/Makefile | 17 + tools/testing/selftests/drivers/dpll/config | 6 + .../selftests/drivers/dpll/dpll_basic.sh | 223 +++++ .../selftests/drivers/dpll/dpll_features.sh | 470 ++++++++++ .../selftests/drivers/dpll/dpll_lib.sh | 872 ++++++++++++++++++ .../selftests/drivers/dpll/dpll_reftrack.sh | 184 ++++ .../selftests/drivers/dpll/dpll_stress.sh | 563 +++++++++++ tools/testing/selftests/drivers/dpll/settings | 1 + 19 files changed, 3508 insertions(+) create mode 100644 Documentation/driver-api/dpllsim.rst create mode 100644 drivers/dpll/dpllsim/Kconfig create mode 100644 drivers/dpll/dpllsim/Makefile create mode 100644 drivers/dpll/dpllsim/core.c create mode 100644 drivers/dpll/dpllsim/dpll.c create mode 100644 drivers/dpll/dpllsim/dpllsim.h create mode 100644 tools/testing/selftests/drivers/dpll/Makefile create mode 100644 tools/testing/selftests/drivers/dpll/config create mode 100755 tools/testing/selftests/drivers/dpll/dpll_basic.sh create mode 100755 tools/testing/selftests/drivers/dpll/dpll_features.sh create mode 100644 tools/testing/selftests/drivers/dpll/dpll_lib.sh create mode 100755 tools/testing/selftests/drivers/dpll/dpll_reftrack.sh create mode 100755 tools/testing/selftests/drivers/dpll/dpll_stress.sh create mode 100644 tools/testing/selftests/drivers/dpll/settings
Signature · 1 line
2.56.0
PATCH v3 1/14 dpll: dpllsim: Add simulated DPLL device driver
Add dpllsim, a driver providing simulated DPLL devices for testing the DPLL subsystem and its userspace tooling without real hardware, in the same spirit as netdevsim for network devices. The DPLL core identifies a device by clock ID, index and module only, so the driver needs no bus and no struct device at all.
Instances are created and configured at runtime through configfs, following the gpio-sim precedent for objects that userspace creates on demand. Creating a directory under /sys/kernel/config/dpllsim/ allocates an instance, its clock_id attribute selects the clock ID, writing 1 to live registers the DPLL device with the core and writing 0 unregisters it again. Removing the directory tears a live instance down implicitly. Keeping registration separate from creation lets a test register and unregister the same device repeatedly, which is where the core bugs the driver is meant to catch tend to live, and it allows the structural attributes to be refused with EBUSY while the device is visible to userspace. Unlike sysfs, configfs does not imply a stable ABI, which suits a driver whose knobs exist only to drive tests.
Reading live reports whether the device is registered with the DPLL subsystem, it turns 1 only once the whole live transition succeeded and 0 again as the first step of the teardown. The configuration is frozen earlier, from the moment the transition obtains the device handle, so that a configuration write racing with the transition is refused rather than lost.
The device registers as a PPS type DPLL with the two mandatory callbacks only. mode_get reports automatic mode and lock_status_get reports unlocked with no error. Pins come with the following patches and the simulation of lock acquisition with a later series.
Document the configfs layout under Documentation/driver-api and add a MAINTAINERS entry.
--- Documentation/driver-api/dpllsim.rst | 41 +++++ Documentation/driver-api/index.rst | 1 + MAINTAINERS | 7 + drivers/dpll/Kconfig | 1 + drivers/dpll/Makefile | 1 + drivers/dpll/dpllsim/Kconfig | 16 ++ drivers/dpll/dpllsim/Makefile | 4 + drivers/dpll/dpllsim/core.c | 221 +++++++++++++++++++++++++++ drivers/dpll/dpllsim/dpll.c | 84 ++++++++++ drivers/dpll/dpllsim/dpllsim.h | 58 +++++++ 10 files changed, 434 insertions(+) create mode 100644 Documentation/driver-api/dpllsim.rst create mode 100644 drivers/dpll/dpllsim/Kconfig create mode 100644 drivers/dpll/dpllsim/Makefile create mode 100644 drivers/dpll/dpllsim/core.c create mode 100644 drivers/dpll/dpllsim/dpll.c create mode 100644 drivers/dpll/dpllsim/dpllsim.h
Documentation/driver-api/dpllsim.rst new file +41 -0
@@ -0,0 +1,41 @@ +.. SPDX-License-Identifier: GPL-2.0 + +========================== +DPLL simulator (dpllsim) +========================== + +The dpllsim driver provides simulated DPLL devices for testing the DPLL +subsystem and its userspace tooling without real hardware, similar to +what netdevsim provides for network devices. + +Configuration interface +======================= + +Devices are described in configfs under ``/sys/kernel/config/dpllsim/``. +A directory created there is a device instance. Attributes are written +while the instance is not live, then ``live`` is set to 1 to register +the device with the DPLL subsystem. Writing 0 unregisters it again. +Structural attributes return ``EBUSY`` while the instance is live. + +Device directory ``/sys/kernel/config/dpllsim/<dev>/``: + +======================= ===================================================== +``clock_id`` Clock ID of the DPLL device, must be nonzero and + unique among live instances +``live`` 1 registers the device, 0 unregisters it +======================= ===================================================== + +Example +======= + +Create a device and make it live:: + + cd /sys/kernel/config/dpllsim + mkdir dev0 && cd dev0 + echo 100 > clock_id + echo 1 > live + + dpll device show + +To remove the device, write 0 to ``live`` and remove the device +directory.
Documentation/driver-api/index.rst +1 -0
@@ -87,6 +87,7 @@ Subsystem-specific APIs crypto/index dmaengine/index dpll + dpllsim edac extcon firmware/index
MAINTAINERS +7 -0
@@ -8070,6 +8070,13 @@ F: Documentation/networking/device_drivers/ethernet/freescale/dpaa2/switch-drive F: drivers/net/ethernet/freescale/dpaa2/dpaa2-switch* F: drivers/net/ethernet/freescale/dpaa2/dpsw* +DPLL SIMULATOR DRIVER (dpllsim) +M: Petr Oros <[email protected]> +L: [email protected] +S: Maintained +F: Documentation/driver-api/dpllsim.rst +F: drivers/dpll/dpllsim/ + DPLL SUBSYSTEM M: Vadim Fedorenko <[email protected]> M: Arkadiusz Kubalewski <[email protected]>
drivers/dpll/Kconfig +1 -0
@@ -24,5 +24,6 @@ config DPLL_REFCNT_TRACKER If unsure, say N. source "drivers/dpll/zl3073x/Kconfig" +source "drivers/dpll/dpllsim/Kconfig" endmenu
drivers/dpll/Makefile +1 -0
@@ -9,3 +9,4 @@ dpll-y += dpll_netlink.o dpll-y += dpll_nl.o obj-$(CONFIG_ZL3073X) += zl3073x/ +obj-$(CONFIG_DPLLSIM) += dpllsim/
drivers/dpll/dpllsim/Kconfig new file +16 -0
@@ -0,0 +1,16 @@ +# SPDX-License-Identifier: GPL-2.0-only + +config DPLLSIM + tristate "DPLL simulator driver" + depends on NET + select DPLL + select CONFIGFS_FS + help + This driver provides simulated DPLL devices for testing and + development of the DPLL subsystem and its userspace tooling + without real hardware. Devices with arbitrary pin topologies + are created and configured at runtime through configfs, + similar to what gpio-sim provides for GPIO. + + To compile this driver as a module, choose M here: the module + will be called dpllsim.
drivers/dpll/dpllsim/Makefile new file +4 -0
@@ -0,0 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0-only + +obj-$(CONFIG_DPLLSIM) += dpllsim.o +dpllsim-y := core.o dpll.o
drivers/dpll/dpllsim/core.c new file +221 -0
221 lines not on this page · raw message
drivers/dpll/dpllsim/dpll.c new file +84 -0
84 lines not on this page · raw message
drivers/dpll/dpllsim/dpllsim.h new file +58 -0
58 lines not on this page · raw message
Signature · 1 line
2.56.0
[email protected]
AI reviewRe: [PATCH net-next v3 01/14] dpll: dpllsim: Add simulated DPLL device driver
Thank you for your contribution! Sashiko AI review found more severe issue(s) in other patches of this series. This mail is only an FYI, in case you want to address the nit picks below when you…
Thank you for your contribution! Sashiko AI review found more severe issue(s) in other patches of this series. This mail is only an FYI, in case you want to address the nit picks below when you respin.
Low priority / nit picks for your consideration:
- [Low] The commit message says configfs was chosen partly because "Unlike sysfs, configfs does not imply a stable ABI, which suits a…
--
dpll: dpllsim: Add simulated DPLL device driver
This commit adds dpllsim, a driver that provides simulated DPLL devices for testing the DPLL subsystem without real hardware. Userspace creates instances through configfs under /sys/kernel/config/dpllsim/, sets a clock_id and writes 1 to live to register the device with the DPLL core. The device registers as a PPS type DPLL with only the mode_get and lock_status_get callbacks.
Quoted 6 lines
Keeping registration separate from creation lets a test register and unregister the same device repeatedly, which is where the core bugs the driver is meant to catch tend to live, and it allows the structural attributes to be refused with EBUSY while the device is visible to userspace. Unlike sysfs, configfs does not imply a stable ABI, which suits a driver whose knobs exist only to drive tests.
[Severity: Low]
Is this statement about configfs accurate?
The kernel has no ABI exemption for configfs. Its attributes are user-visible interfaces and fall under the same no-regression rule as sysfs.
The tree already documents configfs interfaces as ABI in:
Documentation/ABI/testing/configfs-most Documentation/ABI/testing/configfs-thunderbolt_stream Documentation/ABI/testing/configfs-tsm-report
Neither Documentation/filesystems/configfs.rst nor Documentation/ABI/README makes an exception for configfs.
The selftests in this series will use the layout added here, /sys/kernel/config/dpllsim/<dev>/{clock_id,live}, which later patches extend with pins/<pin>/{type,frequency,direction,prio}. External tooling may use it too. After that, renaming or changing these attributes would break userspace.
The layout is currently documented only in Documentation/driver-api/dpllsim.rst. There is no Documentation/ABI entry and no note saying the interface is unstable or for testing only.
Could the claim be dropped from the commit message, or the interface be documented under Documentation/ABI/testing/?
Signature · 1 line
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261109093523.931201-1-poros%40redhat.com
PATCH v3 2/14 selftests: dpll: add test library and device lifecycle tests
In reply to Petr OrosAdd a shell library for DPLL selftests with wrappers around the iproute2 dpll tool and around the dpllsim configfs interface, and the first two test scripts built on it. The library resolves devices by the clock ID chosen at creation time, since netlink ids depend on what else exists on the system, and the dpllsim helpers restrict the lookup to their module, so that a device of another driver with the same clock ID is never picked up. Every query runs the tool first and jq on what it printed, so that a failed query is told apart from a missing attribute. The library mounts configfs when nothing else did, and the exit trap of each script undoes that, removes the devices the script created and runs on INT and TERM as well, so that a timeout imposed by the harness still cleans up.
Two runs of scripts that create dpllsim devices would share the one dpllsim directory and could draw the same clock IDs, so every such script takes a lock before it mounts or creates anything and skips when another run holds it. The lock file lives under /run rather than in /tmp, since the scripts run as root and open the file for writing, and a symlink planted in the world-writable /tmp would redirect that open.
dpll_basic.sh checks that a device reports the mandatory attributes over netlink. It runs against the device named by DPLL_DEV_ID, the first device of the driver named by DPLL_DRIVER, or the first device that is not a dpllsim one, and creates a dpllsim device when there is none or when DPLL_DRIVER asks for dpllsim, so the same script serves the simulator and real drivers and picks one of several drivers on a host. dpll_stress.sh is simulator only and exercises the lifecycle paths of the core by creating and deleting devices in a loop and by cycling one device through repeated register and unregister transitions. The dpllsim MAINTAINERS entry covers the new directory.
---
v3:
- move the selftests to tools/testing/selftests/drivers/dpll, next to where the drivers live, with the library in that directory as a TEST_FILES entry; the TARGETS and MAINTAINERS entries follow
- add DPLL_DRIVER, which picks the first device of the named driver or, as dpllsim, a created simulated device on a system that has hardware DPLL devices
- the Reviewed-by and Acked-by of v2 are not carried over, the device selection changed
v2:
- sort the config file alphabetically (netdev CI check_selftest)
- quote the ksft_skip and ret expansions and add shellcheck directives for the globals read by the scripts, for ksft_skip, which comes from net/lib.sh, and for the optional argument of dpllsim_setup_standard_device() (netdev CI shellcheck)
v2: https://lore.kernel.org/all/[email protected]/
v1: https://lore.kernel.org/all/[email protected]/
--- MAINTAINERS | 1 + tools/testing/selftests/Makefile | 1 + tools/testing/selftests/drivers/dpll/Makefile | 15 + tools/testing/selftests/drivers/dpll/config | 3 + .../selftests/drivers/dpll/dpll_basic.sh | 94 +++++ .../selftests/drivers/dpll/dpll_lib.sh | 348 ++++++++++++++++++ .../selftests/drivers/dpll/dpll_stress.sh | 164 +++++++++ tools/testing/selftests/drivers/dpll/settings | 1 + 8 files changed, 627 insertions(+) create mode 100644 tools/testing/selftests/drivers/dpll/Makefile create mode 100644 tools/testing/selftests/drivers/dpll/config create mode 100755 tools/testing/selftests/drivers/dpll/dpll_basic.sh create mode 100644 tools/testing/selftests/drivers/dpll/dpll_lib.sh create mode 100755 tools/testing/selftests/drivers/dpll/dpll_stress.sh create mode 100644 tools/testing/selftests/drivers/dpll/settings
MAINTAINERS +1 -0
@@ -8076,6 +8076,7 @@ L: [email protected] S: Maintained F: Documentation/driver-api/dpllsim.rst F: drivers/dpll/dpllsim/ +F: tools/testing/selftests/drivers/dpll/ DPLL SUBSYSTEM M: Vadim Fedorenko <[email protected]>
tools/testing/selftests/Makefile +1 -0
@@ -20,6 +20,7 @@ TARGETS += devices/error_logs TARGETS += devices/probe TARGETS += dmabuf-heaps TARGETS += drivers/dma-buf +TARGETS += drivers/dpll TARGETS += drivers/ntsync TARGETS += drivers/s390x/uvdevice TARGETS += drivers/net
tools/testing/selftests/drivers/dpll/Makefile new file +15 -0
@@ -0,0 +1,15 @@ +# SPDX-License-Identifier: GPL-2.0 + +TEST_INCLUDES := \ + $(wildcard ../../net/lib/sh/*.sh) \ + ../../net/lib.sh \ +# end of TEST_INCLUDES + +TEST_FILES := dpll_lib.sh + +TEST_PROGS := \ + dpll_basic.sh \ + dpll_stress.sh \ +# end of TEST_PROGS + +include ../../lib.mk
tools/testing/selftests/drivers/dpll/config new file +3 -0
@@ -0,0 +1,3 @@ +CONFIG_CONFIGFS_FS=y +CONFIG_DPLL=y +CONFIG_DPLLSIM=m
tools/testing/selftests/drivers/dpll/dpll_basic.sh new file +94 -0
94 lines not on this page · raw message
tools/testing/selftests/drivers/dpll/dpll_lib.sh new file +348 -0
348 lines not on this page · raw message
tools/testing/selftests/drivers/dpll/dpll_stress.sh new file +164 -0
164 lines not on this page · raw message
tools/testing/selftests/drivers/dpll/settings new file +1 -0
@@ -0,0 +1 @@ +timeout=600
Signature · 1 line
2.56.0
Re: [PATCH net-next v3 02/14] selftests: dpll: add test library and device lifecycle tests
Quoted 32 lines · Petr Oros
Add a shell library for DPLL selftests with wrappers around the iproute2 dpll tool and around the dpllsim configfs interface, and the first two test scripts built on it. The library resolves devices by the clock ID chosen at creation time, since netlink ids depend on what else exists on the system, and the dpllsim helpers restrict the lookup to their module, so that a device of another driver with the same clock ID is never picked up. Every query runs the tool first and jq on what it printed, so that a failed query is told apart from a missing attribute. The library mounts configfs when nothing else did, and the exit trap of each script undoes that, removes the devices the script created and runs on INT and TERM as well, so that a timeout imposed by the harness still cleans up.
Two runs of scripts that create dpllsim devices would share the one dpllsim directory and could draw the same clock IDs, so every such script takes a lock before it mounts or creates anything and skips when another run holds it. The lock file lives under /run rather than in /tmp, since the scripts run as root and open the file for writing, and a symlink planted in the world-writable /tmp would redirect that open.
dpll_basic.shchecks that a device reports the mandatory attributes over netlink. It runs against the device named byDPLL_DEV_ID, the first device of the driver named byDPLL_DRIVER, or the first device that is not a dpllsim one, and creates a dpllsim device when there is none or whenDPLL_DRIVERasks for dpllsim, so the same script serves the simulator and real drivers and picks one of several drivers on a host.dpll_stress.shis simulator only and exercises the lifecycle paths of the core by creating and deleting devices in a loop and by cycling one device through repeated register and unregister transitions. The dpllsimMAINTAINERSentry covers the new directory.Signed-off-by Petr Oros<[email protected]>
Quoted 10 lines
---
v3:
- move the selftests totools/testing/selftests/drivers/dpll, next to where the drivers live, with the library in that directory as aTEST_FILESentry; the TARGETS andMAINTAINERSentries follow
- addDPLL_DRIVER, which picks the first device of the named driver or, as dpllsim, a created simulated device on a system that has hardware DPLL devices
- the Reviewed-by and Acked-by of v2 are not carried over, the device selection changed
[email protected]
AI reviewRe: [PATCH net-next v3 02/14] selftests: dpll: add test library and device lifecycle tests
In reply to Petr OrosThank you for your contribution! Sashiko AI review found 7 potential issue(s) to consider.
Thank you for your contribution! Sashiko AI review found 7 potential issue(s) to consider.
Critical: 0 · High: 0 · Medium: 2 · Low: 5
If you believe none of the issues are legitimate, please reply to each issue of Medium severity or higher and explain why the AI is wrong. If any of them are legitimate and you plan to respin, please let patchwork know by including "pw-bot: cr" as a separate line at the end of your reply (one such reply per series is enough).
- [Medium] In dpll_lib_init() (tools/testing/selftests/drivers/dpll/dpll_lib.sh), the DPLL_DRIVER…
- [Medium] dpll_basic.sh exits with ksft_skip whenever dpllsim_setup_standard_device() fails (`echo "SKIP: cannot create…
- [Low] Several comments in dpll_lib.sh do not match what the code does.
- [Low] DPLLSIM_CREATED_NAME is never initialized at the top level of dpll_lib.sh or by either script.
- [Low] dpllsim_create_device() sets DPLLSIM_CREATED_NAME=$name before mkdir, so the cleanup handler counts the device as created by this run.
- [Low] dpll_lib_cleanup() unmounts /sys/kernel/config whenever this run did the mount and no dpllsim item is left.
- [Low] In dpllsim_available(), the flag recording that this run mounted configfs is set only after the mount: `mount -t configfs none…
--
selftests: dpll: add test library and device lifecycle tests
This commit adds a shell library for DPLL selftests that wraps the iproute2 dpll tool and the dpllsim configfs interface. It also adds dpll_basic.sh, which checks mandatory device attributes against hardware or a created dpllsim device, and dpll_stress.sh, which cycles dpllsim devices through create/delete and register/unregister. The scripts take a lock under /run and clean up from an exit trap.
Quoted 6 lines
tools/testing/selftests/drivers/dpll/dpll_basic.sh new file +0
-0@@ -0,0 +1,94 @@
[ ... ]
Quoted 5 lines
tools/testing/selftests/drivers/dpll/dpll_basic.sh new file +5
-0+cleanup() +{ + # A second signal must not abort the teardown half way. + trap '' INT TERM +
tools/testing/selftests/drivers/dpll/dpll_basic.sh line 62
Quoted 10 lines
tools/testing/selftests/drivers/dpll/dpll_basic.sh new file +10
-0+ if [ -n "$CREATED_DEVICE" ]; then + dpllsim_delete_device "$CREATED_DEVICE" || + log_info "cannot delete device $CREATED_DEVICE" + fi + # The device whose creation a signal interrupted is recorded by the + # library alone, a double delete of a tracked one is harmless. + if [ -n "$DPLLSIM_CREATED_NAME" ]; then + dpllsim_delete_device "$DPLLSIM_CREATED_NAME" || + log_info "cannot delete device $DPLLSIM_CREATED_NAME" + fi
[Severity: Low]
Is DPLLSIM_CREATED_NAME always empty here when this run never created a device? It is only assigned inside dpllsim_create_device(). Neither dpll_lib.sh nor the scripts initialize it at the top level, unlike DPLLSIM_MOUNTED_CONFIGFS=0 and DPLL_LOCK_FD="" here, and DPLL_CREATED_DEVICE="" and DPLLSIM_PROBE_DEVICE="" later in the series.
Bash imports environment variables as shell variables. If DPLLSIM_CREATED_NAME is set in the inherited environment, this EXIT trap undeploys and removes an existing $DPLLSIM_CFG/<name> device even on paths that never create anything. Examples are a run against hardware, the dpllsim_check_module() skip, or an early signal.
cleanup() in dpll_stress.sh has the same check, and the variable is still uninitialized at the end of the series. Would a top-level DPLLSIM_CREATED_NAME="" in dpll_lib.sh be enough?
Quoted 2 lines
tools/testing/selftests/drivers/dpll/dpll_basic.sh new file +2
-0+ dpll_lib_cleanup +}
[ ... ]
tools/testing/selftests/drivers/dpll/dpll_basic.sh line 84
Quoted 6 lines
tools/testing/selftests/drivers/dpll/dpll_basic.sh new file +6
-0+if [ -z "$DPLL_DEV_ID" ]; then + dpllsim_check_module + if ! dpllsim_setup_standard_device; then + echo "SKIP: cannot create dpllsim device" + exit "$ksft_skip" + fi
[Severity: Medium]
Should every failure of dpllsim_setup_standard_device() be reported as a skip? That function returns 1 in three cases: the configfs mkdir fails, the write of 1 to <dev>/live fails, or dpll_device_wait_by_clock_id() times out.
The live write goes to dpllsim_dev_live_store(), which passes back the DPLL core registration error:
drivers/dpll/dpllsim/core.c:dpllsim_dev_live_store() {
...
err = dpllsim_dev_deploy(sim);
if (err)
return err;
...
}The timeout means the registered device never showed up in the netlink dump. Both are failures of the core and netlink paths this test covers. dpll_stress.sh treats the same condition as a failure in test_deploy_cycle():
check_err 1 "device not on netlink (iteration $i)"
Could only environment problems be skipped here, with deploy and netlink visibility failures reported as failures? This check is unchanged at the end of the series.
Quoted 4 lines
tools/testing/selftests/drivers/dpll/dpll_basic.sh new file +4
-0+ CREATED_DEVICE=$DPLLSIM_CREATED_NAME + DPLL_DEV_ID=$DPLLSIM_DEV_ID + DPLL_MODULE_NAME="dpllsim" +fi
[ ... ]
Quoted 6 lines
tools/testing/selftests/drivers/dpll/dpll_lib.sh new file +0
-0@@ -0,0 +1,348 @@
[ ... ]
Quoted 21 lines
tools/testing/selftests/drivers/dpll/dpll_lib.sh new file +21
-0+dpll_lib_init() +{ + DPLL_MODULE_NAME="" + + if [ -n "${DPLL_DEV_ID:-}" ]; then + if ! dpll device show id "$DPLL_DEV_ID" &> /dev/null; then + echo "SKIP: DPLL device \"$DPLL_DEV_ID\" not found" + exit "$ksft_skip" + fi + elif [ "${DPLL_DRIVER:-}" = "dpllsim" ]; then + DPLL_DEV_ID="" + elif [ -n "${DPLL_DRIVER:-}" ]; then + DPLL_DEV_ID=$(dpll device show -j 2>/dev/null | \ + jq -r --arg mod "$DPLL_DRIVER" '[.device[]? | + select(.["module-name"] == $mod) + ][0].id // empty') + if [ -z "$DPLL_DEV_ID" ]; then + echo "SKIP: no DPLL device of driver \"$DPLL_DRIVER\"" + exit "$ksft_skip" + fi + else
tools/testing/selftests/drivers/dpll/dpll_lib.sh line 123
Quoted 10 lines
tools/testing/selftests/drivers/dpll/dpll_lib.sh new file +10
-0+ DPLL_DEV_ID=$(dpll device show -j 2>/dev/null | \ + jq -r '[.device[]? | + select(.["module-name"] != "dpllsim") + ][0].id // empty') + fi + + if [ -n "$DPLL_DEV_ID" ]; then + DPLL_MODULE_NAME=$(dpll_device_get_module "$DPLL_DEV_ID") + fi +}
[Severity: Medium]
Can a failed dump be told apart from a dump with no matching device here? Both the DPLL_DRIVER branch and the default branch use a pipeline, and nothing sets pipefail, so the command substitution only returns the status of jq.
If dpll device show -j fails, jq reads empty input, prints nothing and exits 0, so DPLL_DEV_ID ends up empty.
With DPLL_DRIVER set to a hardware driver, the run then skips with "no DPLL device of driver". With neither variable set, dpll_basic.sh decides the host has no hardware and tests a dpllsim device instead:
dpll_basic.sh
dpll_lib_init()
dpll device show -j fails, jq prints nothing
DPLL_DEV_ID is empty
if [ -z "$DPLL_DEV_ID" ]; then
dpllsim_check_module
dpllsim_setup_standard_device
The commit message says:
Every query runs the tool first and jq on what it printed, so that a failed query is told apart from a missing attribute.
The other helpers follow that with out=$(dpll ...) || return 1, but the device selection path does not. The DPLL_MODULE_NAME lookup at the end of dpll_lib_init() also ignores a failure of dpll_device_get_module().
This is unchanged at the end of the series. dpll_features.sh, added later in the series, calls the same dpll_lib_init().
[ ... ]
tools/testing/selftests/drivers/dpll/dpll_lib.sh line 232
Quoted 11 lines
tools/testing/selftests/drivers/dpll/dpll_lib.sh new file +11
-0+# dpllsim_available mounts configfs when nothing else did, which is the +# one side effect the library has outside the dpllsim directory. Scripts +# call dpll_lib_cleanup last from their exit trap to undo it. + +DPLLSIM_MOUNTED_CONFIGFS=0 + +dpllsim_available() +{ + [ -d "$DPLLSIM_CFG" ] && return 0 + + modprobe dpllsim &> /dev/null
[Severity: Low]
Is the "one side effect" comment above accurate? dpll_lib_run_dir() and dpll_lib_lock() also create /run/dpll-selftest/ and its lock file, and nothing removes them.
This modprobe is not recorded either, and dpll_lib_cleanup() never unloads the module. A run on a system where dpllsim was not loaded therefore leaves it loaded.
A few other comments also don't match the code. The file header says:
# devices. Sourcing this file has no side effects; call # dpll_lib_check_prereqs and dpll_lib_init explicitly.
dpll_lib_lock is also required, though, and dpll_stress.sh never calls dpll_lib_init.
dpllsim_setup_standard_device() has:
# shellcheck disable=SC2120 # the argument is optional
but the function uses no positional parameter.
The comment on dpllsim_alloc_clock_id() about per-worker id ranges refers to workers that don't exist in this patch. That part is resolved later in the series by "selftests: dpll: add concurrency stress tests".
At the end of the series, the header, the SC2120 directive and the side effect comment are still there. The side effect comment now reads "which are the only side effects the library has outside the dpllsim directory", and it still leaves out the /run files and the module load.
tools/testing/selftests/drivers/dpll/dpll_lib.sh line 235
Quoted 3 lines
tools/testing/selftests/drivers/dpll/dpll_lib.sh new file +3
-0+ if ! mountpoint -q "$CONFIGFS_DIR"; then + mount -t configfs none "$CONFIGFS_DIR" &> /dev/null && + DPLLSIM_MOUNTED_CONFIGFS=1
[Severity: Low]
Can a signal leave configfs mounted while DPLLSIM_MOUNTED_CONFIGFS is still 0? Both scripts install trap cleanup EXIT and trap 'exit 1' INT TERM before dpllsim_check_module().
A SIGTERM or SIGINT that arrives while mount runs is handled after mount returns, before the right-hand side of the && list runs:
mount -t configfs ... succeeds
pending trap: exit 1
cleanup()
dpll_lib_cleanup()
[ "$DPLLSIM_MOUNTED_CONFIGFS" -eq 1 ] || return 0dpllsim_create_device() records its name before mkdir for this reason. The mount flag doesn't get the same ordering, so "a timeout imposed by the harness still cleans up" doesn't hold for the mount. The same pattern is used for debugfs (DPLL_MOUNTED_DEBUGFS) later in the series.
Quoted 12 lines
tools/testing/selftests/drivers/dpll/dpll_lib.sh new file +12
-0+ fi + [ -d "$DPLLSIM_CFG" ] +} + +# Unmount configfs again when dpllsim_available mounted it. Call after +# the devices of the script are gone. A dpllsim device the script could +# not remove keeps the mount, so that what is left can be looked at. +dpll_lib_cleanup() +{ + local dev_path name + + [ "$DPLLSIM_MOUNTED_CONFIGFS" -eq 1 ] || return 0
tools/testing/selftests/drivers/dpll/dpll_lib.sh line 257
Quoted 10 lines
tools/testing/selftests/drivers/dpll/dpll_lib.sh new file +10
-0+ DPLLSIM_MOUNTED_CONFIGFS=0 + + for dev_path in "$DPLLSIM_CFG"/*/; do + [ -d "$dev_path" ] || continue + name=$(basename "$dev_path") + log_info "device $name left behind, $CONFIGFS_DIR kept mounted" + return + done + umount "$CONFIGFS_DIR" 2>/dev/null || + log_info "cannot unmount $CONFIGFS_DIR"
[Severity: Low]
Could this unmount configfs while another test is using it? The loop above only looks at $DPLLSIM_CFG/*/. The flock in dpll_lib_lock() only orders dpll selftests against each other.
Other configfs users, such as the gpio-sim, netconsole or USB gadget tests, use the same check-then-mount pattern without a lock. Say one of them starts while a dpll script holds the mount:
- It sees configfs already mounted and skips its own mount.
- It starts using /sys/kernel/config/<subsys>.
- This umount succeeds as long as that test has no open file or cwd in the mount, and its paths disappear in the middle of the test.
dpll_lib.sh seems to be the only selftest that unmounts configfs. Later in the series, "selftests: dpll: add reference leak tests" applies the same mount and unmount handling to debugfs.
Quoted 1 line
+}
[ ... ]
tools/testing/selftests/drivers/dpll/dpll_lib.sh line 284
Quoted 13 lines
tools/testing/selftests/drivers/dpll/dpll_lib.sh new file +13
-0+dpllsim_create_device() +{ + local name=$1 + local clock_id=$2 + local dev_path="$DPLLSIM_CFG/$name" + + # Record the name before the directory exists, so that a cleanup + # run by a signal between the two still finds it. + DPLLSIM_CREATED_NAME=$name + if ! mkdir "$dev_path" 2>/dev/null; then + DPLLSIM_CREATED_NAME="" + return 1 + fi
[Severity: Low]
What happens when mkdir fails because a directory of that name already exists? Leftover devices can exist: dpll_lib_cleanup() deliberately keeps devices it could not remove, and the probe added later in the series retries on exactly that case.
A SIGTERM or SIGINT that arrives while mkdir runs is handled after mkdir returns, before DPLLSIM_CREATED_NAME is cleared:
DPLLSIM_CREATED_NAME=$name
mkdir "$dev_path" fails with EEXIST pending trap: exit 1
cleanup()
dpllsim_delete_device "$DPLLSIM_CREATED_NAME"
In that case, cleanup() undeploys and removes a device this run did not create.
Quoted 6 lines
tools/testing/selftests/drivers/dpll/dpll_lib.sh new file +6
-0+ if ! echo "$clock_id" 2>/dev/null > "$dev_path/clock_id"; then + rmdir "$dev_path" 2>/dev/null + DPLLSIM_CREATED_NAME="" + return 1 + fi +}
[ ... ]
Signature · 1 line
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261109093523.931201-1-poros%40redhat.com
PATCH v3 3/14 dpll: dpllsim: Add configurable pins
In reply to Petr OrosPins are the unit of most DPLL netlink interaction, so let a device instance carry an arbitrary set of them. Each directory created under the pins/ group of an instance is one pin with type, frequency and direction attributes. The type takes the names of the DPLL pin type enum as iproute2 prints them, the direction is input or output and the frequency must be one of the frequencies the driver advertises as supported for the type. The pin directory name becomes the board label of the pin, which gives tests a stable handle to find a pin by. The attributes are writable only while the instance is not live and return EBUSY otherwise.
At live=1 the pins are ordered as created and each pin records its index in that order, which is the pin index it is registered under. Their frequencies are validated against the per type tables, a MUX pin configured as output is rejected since a MUX pin stands for an input selector, and all pin handles are obtained up front, so that a failure leaves nothing half visible. The device is registered first and the pins after it, so that every pin registration event reaches userspace with a valid device, and a failure unwinds exactly what was registered in reverse order. While the instance is live the pin items are held with configfs_depend_item_unlocked(), which makes rmdir of a pin fail with EBUSY until the instance is taken down.
The pins register with the mandatory callbacks only, direction_get and state_on_dpll_get, plus frequency_get since the frequency is known from the configuration. The input and output ops tables are identical here and diverge when input pins gain their setters in the next patches. The state is always reported as disconnected for now, the settable pin features follow in separate patches.
--- Documentation/driver-api/dpllsim.rst | 40 +++- drivers/dpll/dpllsim/core.c | 225 ++++++++++++++++++++- drivers/dpll/dpllsim/dpll.c | 280 ++++++++++++++++++++++++++- drivers/dpll/dpllsim/dpllsim.h | 32 +++ 4 files changed, 565 insertions(+), 12 deletions(-)
Documentation/driver-api/dpllsim.rst +33 -7
@@ -12,10 +12,12 @@ Configuration interface ======================= Devices are described in configfs under ``/sys/kernel/config/dpllsim/``. -A directory created there is a device instance. Attributes are written -while the instance is not live, then ``live`` is set to 1 to register -the device with the DPLL subsystem. Writing 0 unregisters it again. -Structural attributes return ``EBUSY`` while the instance is live. +A directory created there is a device instance, a directory created in +its ``pins/`` subdirectory is a pin. Attributes are written while the +instance is not live, then ``live`` is set to 1 to register the device +and its pins with the DPLL subsystem. Writing 0 unregisters everything +again. Structural attributes return ``EBUSY`` while the instance is live +and pins of a live instance cannot be removed. Device directory ``/sys/kernel/config/dpllsim/<dev>/``:
@@ -25,17 +27,41 @@ Device directory ``/sys/kernel/config/dpllsim/<dev>/``: ``live`` 1 registers the device, 0 unregisters it ======================= ===================================================== +Pin directory ``/sys/kernel/config/dpllsim/<dev>/pins/<pin>/``: + +============= =============================================================== +``type`` ``gnss``, ``int-oscillator``, ``ext``, ``synce-eth-port`` or + ``mux`` (default ``ext``) +``frequency`` Frequency in Hz, must be supported by the pin type + (default 0, which is rejected) +``direction`` ``input`` or ``output`` (default ``input``), a ``mux`` pin + must be an input +============= =============================================================== + +The pin directory name becomes the board label of the pin. Pins are +registered in the order the directories were created. + Example ======= -Create a device and make it live:: +Create a device with a GNSS input, an external input and an external +output, then make it live:: cd /sys/kernel/config/dpllsim mkdir dev0 && cd dev0 echo 100 > clock_id + mkdir pins/gnss0 + echo gnss > pins/gnss0/type + echo 1 > pins/gnss0/frequency + mkdir pins/ext0 + echo 10000000 > pins/ext0/frequency + mkdir pins/out0 + echo 10000000 > pins/out0/frequency + echo output > pins/out0/direction echo 1 > live dpll device show + dpll pin show -To remove the device, write 0 to ``live`` and remove the device -directory. +To remove the device, write 0 to ``live``, remove the pin directories +and finally the device directory.
drivers/dpll/dpllsim/core.c +222 -3
276 lines not on this page · raw message
drivers/dpll/dpllsim/dpll.c +278 -2
325 lines not on this page · raw message
drivers/dpll/dpllsim/dpllsim.h +32 -0
@@ -9,10 +9,39 @@#include <linux/configfs.h> #include <linux/dpll.h> +#include <linux/list.h> #include <linux/mutex.h> +#define DPLLSIM_MAX_PINS 128 + +struct dpllsim_dev ; + +struct dpllsim_pin { +struct config_item item; +struct list_head list; +struct dpllsim_dev *dev; + +/* Configuration, writable only until dpllsim_dev_frozen() */ +enum dpll_pin_type type; +enum dpll_pin_direction direction; +u64 frequency; + +/* Resolved at live=1 */ +int idx; +struct dpll_pin *dpll_pin; +const struct dpll_pin_ops *ops; +dpll_tracker tracker; +bool registered; + +/* Simulated state, guarded by dev->lock */ +enum dpll_pin_state state; +}; +struct dpllsim_dev {struct config_group group; +struct config_group pins_group; +/* Pin items, guarded by the configfs subsystem mutex */ +struct list_head pin_list;/* * Set from the start of a live transition until the device is
@@ -27,6 +56,9 @@ struct dpllsim_dev {* the live attribute reports, see dpllsim_dev_live(). */ bool registered; +/* Pins in registration order, valid while @dpll is set */ +struct dpllsim_pin **pins; +int num_pins;/* Guarded by the subsystem mutex */ u64 clock_id;
Signature · 1 line
2.56.0
[email protected]
AI reviewRe: [PATCH net-next v3 03/14] dpll: dpllsim: Add configurable pins
Thank you for your contribution! Sashiko AI review found more severe issue(s) in other patches of this series. This mail is only an FYI, in case you want to address the nit picks below when you…
Thank you for your contribution! Sashiko AI review found more severe issue(s) in other patches of this series. This mail is only an FYI, in case you want to address the nit picks below when you respin.
Low priority / nit picks for your consideration:
- [Low] A pin mkdir or rmdir that runs alongside live=1 on the same instance makes the live transition fail with -ENOENT.
- [Low] Two pin-creation limits are visible to users but not documented.
- [Low] dpllsim.rst describes the pin frequency attribute only as 'must be supported by the pin type (default 0, which is rejected)' and never…
- [Low] Concern: dpllsim_pin_types[] maps only 5 of the 6 values of enum dpll_pin_type and leaves out DPLL_PIN_TYPE_INT_NCO, so a simulated pin…
--
dpll: dpllsim: Add configurable pins
This patch lets a dpllsim device instance carry configurable pins. Each pin is a configfs directory under pins/ with type, frequency and direction attributes. At live=1 the pins are validated, ordered, obtained and registered with the DPLL subsystem after the device, and while the instance is live the pin items are held with configfs_depend_item_unlocked().
Quoted 4 lines
drivers/dpll/dpllsim/core.c +0
-0
[ ... ]
drivers/dpll/dpllsim/core.c static struct dpllsim_dev *to_dpllsim_dev(struct config_item *item) · line 171
Quoted 13 lines
drivers/dpll/dpllsim/core.c +13
-0+static struct config_item *dpllsim_pins_make_item (struct config_group *group, +const char *name) +{ + struct dpllsim_dev *sim = container_of(group, struct dpllsim_dev, + pins_group); +struct dpllsim_pin *pin; + +lockdep_assert_held (&dpllsim_subsys.su_mutex); + +if (dpllsim_dev_frozen (sim)) +return ERR_PTR (-EBUSY ); +if (list_count_nodes (&sim->pin_list) >=DPLLSIM_MAX_PINS ) +return ERR_PTR (-ENOSPC );
[Severity: Low]
Should these two mkdir limits be described inDocumentation/driver-api/dpllsim.rst?
Once the instance is frozen, mkdir of a new pin fails with EBUSY. Once the instance has DPLLSIM_MAX_PINS (128) pins, it fails with ENOSPC. The documentation only says:
Structural attributes return EBUSY while the instance is live and pins of a live instance cannot be removed.
It doesn't say that pins can't be added to a live instance, and it doesn't mention the 128 pin cap. The commit message also says an instance can carry "an arbitrary set of them".
Separately, a pin with an invalid frequency or direction makes live=1 fail with a bare -EINVAL from dpllsim_order_pins():
list_for_each_entry(pin, &sim->pin_list, list) {
if (!dpllsim_pin_frequency_valid(pin) ||
!dpllsim_pin_direction_valid(pin))
return -EINVAL;Nothing tells the user which pin was rejected. Could that be reported?
Quoted 2 lines
drivers/dpll/dpllsim/core.c +2
-0+ + pin =kzalloc_obj (*pin);
[ ... ]
Quoted 7 lines
drivers/dpll/dpllsim/core.c +7
-0+/* Pin mkdir and rmdir run under the subsystem mutex like live transitions */ +static void dpllsim_pins_drop_item (struct config_group *group, +struct config_item *item) +{ +list_del (&to_dpllsim_pin (item)->list); +config_item_put (item); +}
[ ... ]
drivers/dpll/dpllsim/core.c static bool dpllsim_clock_id_in_use(struct dpllsim_dev *sim) · line 283
Quoted 8 lines
drivers/dpll/dpllsim/core.c +8
-0+static int dpllsim_dev_depend_pins (struct dpllsim_dev *sim) +{ +struct dpllsim_pin *pin; +int err; + +list_for_each_entry (pin, &sim->pin_list, list) { + err =configfs_depend_item_unlocked (&dpllsim_subsys, + &pin->item);
[Severity: Low]
Can a pin mkdir or rmdir that runs at the same time as "echo 1 > live" on the same instance make this return -ENOENT?
The comments above dpllsim_pins_drop_item() and dpllsim_dev_live_store() say the subsystem mutex excludes pin mkdir and rmdir. However, configfs only holds su_mutex around the make_item() and drop_item() callbacks.
For mkdir, dpllsim_pins_make_item() adds the pin to sim->pin_list under su_mutex. The dirent is only attached and readied after the mutex has been dropped:
fs/configfs/dir.c:configfs_mkdir() {
...
mutex_unlock(&subsys->su_mutex);
...
ret = configfs_attach_item(item, dentry, frag);
...
configfs_dir_set_ready(dentry->d_fsdata);
...
}Suppose dpllsim_dev_live_store() takes su_mutex inside that window. dpllsim_dev_depend_pins() then finds the half-created pin on pin_list. configfs_depend_prep() doesn't find it, either because there is no dirent yet or because it skips CONFIGFS_USET_CREATING entries. So live=1 fails with -ENOENT.
rmdir has the same window. configfs_rmdir() marks the item as dropping in configfs_detach_prep() and detaches the dirent without su_mutex. Only after that does it take su_mutex for client_drop_item()->dpllsim_pins_drop_item(), which does the list_del():
fs/configfs/dir.c:configfs_rmdir() {
...
ret = configfs_detach_prep(sd, &wait);
...
configfs_detach_item(dentry);
mutex_lock(&subsys->su_mutex);
...
client_drop_item(parent_item, item);
mutex_unlock(&subsys->su_mutex);
...
}A live=1 in between still sees the pin on pin_list, and the depend fails with -ENOENT.
Nothing is corrupted and a retry succeeds. Should the comments be corrected, or should a pin that is still being created or removed be handled here?
Quoted 8 lines
drivers/dpll/dpllsim/core.c +8
-0+if (err) { +dpllsim_dev_undepend_pins (sim, pin); +return err; + } + } + +return 0 ; +}
[ ... ]
Quoted 4 lines
drivers/dpll/dpllsim/dpll.c +0
-0
[ ... ]
Quoted 16 lines
drivers/dpll/dpllsim/dpll.c +16
-0+static void dpllsim_pin_props (struct dpllsim_pin *pin, +struct dpll_pin_properties *prop) +{ + prop->type = pin->type; + prop->board_label =config_item_name (&pin->item); + +switch (pin->type) { +case DPLL_PIN_TYPE_GNSS : + prop->freq_supported = dpllsim_freq_1pps; + prop->freq_supported_num =ARRAY_SIZE (dpllsim_freq_1pps); +break ; +case DPLL_PIN_TYPE_INT_OSCILLATOR : + prop->freq_supported = dpllsim_freq_int_osc; + prop->freq_supported_num =ARRAY_SIZE (dpllsim_freq_int_osc); +break ; +case DPLL_PIN_TYPE_MUX :
drivers/dpll/dpllsim/dpll.c static const struct dpll_device_ops dpllsim_dpll_ops = { · line 152
Quoted 10 lines
drivers/dpll/dpllsim/dpll.c +10
-0+case DPLL_PIN_TYPE_SYNCE_ETH_PORT : + prop->freq_supported = dpllsim_freq_synce; + prop->freq_supported_num =ARRAY_SIZE (dpllsim_freq_synce); +break ; +default : + prop->freq_supported = dpllsim_freq_ext; + prop->freq_supported_num =ARRAY_SIZE (dpllsim_freq_ext); +break ; + } +}
[Severity: Low]
Should dpllsim.rst list the frequencies each pin type accepts? The frequency row only says:
Frequency in Hz, must be supported by the pin type (default 0, which is rejected)
dpllsim_pin_frequency_valid() accepts only the fixed tables chosen here:
gnss: 1 Hz
int-oscillator: 25 MHz
synce-eth-port and mux: 1.544, 2.048, 10 and 25 MHz
ext: 1 Hz and 10, 19.44, 25, 77.76, 125, 156.25 MHz
The documentation also doesn't say that mux uses the SyncE table.
freq-supported can only be seen over netlink after live=1 has succeeded. An unsupported frequency makes live=1 fail with a bare -EINVAL that names neither the pin nor the value.
[ ... ]
Signature · 1 line
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261109093523.931201-1-poros%40redhat.com
PATCH v3 4/14 selftests: dpll: add pin enumeration and property tests
In reply to Petr OrosExtend the library with pin queries. Pins of a device are collected from the full pin dump with jq, since older iproute2 lacks the device filter of "pin show", and the fields that live in the parent-device nest are read through a helper that selects the nest of the device under test. The helpers that work on the full dump normalize its shape the same way the id-based ones do, so that a tool printing a flat object instead of the dump wrapper works with all of them. Like the device queries, every pin query runs the tool first and jq on what it printed, so that a query which fails returns the status of the tool instead of the empty output a missing attribute produces too. The prerequisite check now also requires a tool that knows "pin show", so that a tool too old for the pin queries is a skip up front rather than a failed query in every test. The dpllsim helpers gain dpllsim_add_pin, which creates a pin directory and writes its type, frequency and direction, and the standard device grows a GNSS input, an external input and an external output.
dpll_basic.sh checks that the enumeration yields a numeric id for every pin of the device, the ids are taken from the dump as they are and jq prints null for a pin without one, that every pin has an id and a type and carries the direction in the parent-device nest, and that an input pin reports a valid state. The property test does not go on to query pins by an id that is not one, and reports a pin it cannot query at all as one failure rather than as one per attribute. The frequency is not required, a pin without a frequency callback does not report one. The device the script created has the standard pins, so an empty pin list or a missing input pin on it means a failed query or broken tooling rather than a pinless device. The library gains dpll_log_test_skip_or_fail for such conditions, which fails on the device the script created, recorded in DPLL_CREATED_DEVICE next to DPLL_DEV_ID, and skips on a device the user provided. The stress script creates its devices with a random set of pins of every type and both directions, MUX pins always as inputs, and the deploy cycle test runs with the standard pin set.
---
v3:
- follow the move of the selftests to
tools/testing/selftests/drivers/dpll
v2:
- quote the ksft_skip and ret expansions (netdev CI shellcheck)
v2: https://lore.kernel.org/all/[email protected]/
v1: https://lore.kernel.org/all/[email protected]/
--- .../selftests/drivers/dpll/dpll_basic.sh | 149 +++++++++++++- .../selftests/drivers/dpll/dpll_lib.sh | 189 ++++++++++++++++-- .../selftests/drivers/dpll/dpll_stress.sh | 46 ++++- 3 files changed, 359 insertions(+), 25 deletions(-)
tools/testing/selftests/drivers/dpll/dpll_basic.sh +139 -10
@@ -1,11 +1,12 @@ #!/bin/bash # SPDX-License-Identifier: GPL-2.0 # -# Basic DPLL subsystem test. Read-only validation of the device properties -# exposed over netlink. Runs against a hardware DPLL device when one is -# present (or when DPLL_DEV_ID is set), otherwise against a freshly -# created dpllsim device. DPLL_DRIVER names the driver to take the -# device from, dpllsim for a created one although hardware is present. +# Basic DPLL subsystem test. Read-only validation of device and pin +# enumeration and of the properties exposed over netlink. Runs against a +# hardware DPLL device when one is present (or when DPLL_DEV_ID is set), +# otherwise against a freshly created dpllsim device. DPLL_DRIVER names +# the driver to take the device from, dpllsim for a created one although +# hardware is present. # # Copyright (c) 2026 Red Hat, Inc.
@@ -16,8 +17,29 @@ source "$SCRIPTDIR/dpll_lib.sh" ALL_TESTS=" test_device_properties + test_pin_enumeration + test_pin_properties + test_pin_state " +# Fail on every entry of a pin id list that is not a number. The ids are +# taken from the dump as they are, so a pin without one shows up as the +# "null" jq prints for a missing field, not as an empty line. Return 1 +# when any entry is invalid. +check_pin_ids() +{ + local id ret=0 + + for id in "$@"; do + if ! [[ "$id" =~ ^[0-9]+$ ]]; then + check_err 1 "invalid pin id '$id'" + ret=1 + fi + done + + return "$ret" +} + test_device_properties() { local json
@@ -43,6 +65,114 @@ test_device_properties() log_test "DPLL device properties" } +test_pin_enumeration() +{ + local pin_ids + + RET=0 + + mapfile -t pin_ids < <(dpll_pin_get_ids "$DPLL_DEV_ID") + if [ "${#pin_ids[@]}" -eq 0 ]; then + dpll_log_test_skip_or_fail "DPLL pin enumeration" \ + "no pins found" + return + fi + check_pin_ids "${pin_ids[@]}" + + log_test "DPLL pin enumeration (${#pin_ids[@]} pins)" +} + +check_pin_properties() +{ + local pin_id=$1 + local json field + + # A pin that cannot be queried at all would be reported as one + # that misses every attribute, so bail out on that first. + if ! json=$(dpll_pin_json "$pin_id") || [ -z "$json" ]; then + check_err 1 "pin $pin_id query failed" + return + fi + + # Frequency is optional, a pin without a frequency callback such + # as a recovered clock pin does not report it. + for field in '.id' '.type'; do + if ! jq -e "$field" <<< "$json" &> /dev/null; then + check_err 1 \ + "pin $pin_id is missing the $field attribute" + fi + done + + # The parent-device nest is not present for all pin types, but when + # the device under test is in it, that entry must carry the + # direction. A pin can be registered on more than one device, so + # match on parent-id instead of taking the first entry. + if jq -e --argjson dev "$DPLL_DEV_ID" \ + '[.["parent-device"][]? | select(.["parent-id"] == $dev)] | + length > 0' <<< "$json" &> /dev/null; then + if ! jq -e --argjson dev "$DPLL_DEV_ID" \ + '.["parent-device"][]? | + select(.["parent-id"] == $dev) | .direction' \ + <<< "$json" &> /dev/null; then + check_err 1 \ + "pin $pin_id parent-device is missing direction" + fi + fi +} + +test_pin_properties() +{ + local pin_ids pin_id + + RET=0 + + mapfile -t pin_ids < <(dpll_pin_get_ids "$DPLL_DEV_ID") + if [ "${#pin_ids[@]}" -eq 0 ]; then + dpll_log_test_skip_or_fail "DPLL pin properties" "no pins found" + return + fi + if ! check_pin_ids "${pin_ids[@]}"; then + log_test "DPLL pin properties" + return + fi + + for pin_id in "${pin_ids[@]}"; do + check_pin_properties "$pin_id" + done + + log_test "DPLL pin properties (${#pin_ids[@]} pins)" +} + +test_pin_state() +{ + local pin_id state + + RET=0 + + pin_id=$(dpll_pin_find_by_direction "$DPLL_DEV_ID" input | head -1) + if [ -z "$pin_id" ]; then + dpll_log_test_skip_or_fail "DPLL pin state" "no input pin" + return + fi + + # The pin was found through its parent-device nest for this device + # and the state is mandatory there, so an empty value is a failed + # query rather than a state. + state=$(dpll_pin_get_state "$DPLL_DEV_ID" "$pin_id") + case "$state" in + "") + check_err 1 "cannot read the state of pin $pin_id" + ;; + connected|disconnected|selectable) + ;; + *) + check_err 1 "pin $pin_id reports invalid state '$state'" + ;; + esac + + log_test "DPLL pin state" +} + ############################################################################## cleanup()
@@ -50,9 +180,9 @@ cleanup() # A second signal must not abort the teardown half way. trap '' INT TERM - if [ -n "$CREATED_DEVICE" ]; then - dpllsim_delete_device "$CREATED_DEVICE" || - log_info "cannot delete device $CREATED_DEVICE" + if [ -n "$DPLL_CREATED_DEVICE" ]; then + dpllsim_delete_device "$DPLL_CREATED_DEVICE" || + log_info "cannot delete device $DPLL_CREATED_DEVICE" fi # The device whose creation a signal interrupted is recorded by the # library alone, a double delete of a tracked one is harmless.
@@ -69,7 +199,6 @@ dpll_lib_check_prereqs dpll_lib_lock dpll_lib_init -CREATED_DEVICE="" # The module check may mount configfs, so the exit trap that unmounts it # again has to be in place before. INT and TERM exit through it as well, # so that a timeout imposed by the harness still removes the device.
@@ -82,8 +211,8 @@ if [ -z "$DPLL_DEV_ID" ]; then echo "SKIP: cannot create dpllsim device" exit "$ksft_skip" fi - CREATED_DEVICE=$DPLLSIM_CREATED_NAME DPLL_DEV_ID=$DPLLSIM_DEV_ID + DPLL_CREATED_DEVICE=$DPLLSIM_CREATED_NAME DPLL_MODULE_NAME="dpllsim" fi
tools/testing/selftests/drivers/dpll/dpll_lib.sh +177 -12
246 lines not on this page · raw message
tools/testing/selftests/drivers/dpll/dpll_stress.sh +43 -3
71 lines not on this page · raw message
Signature · 1 line
2.56.0
[email protected]
AI reviewRe: [PATCH net-next v3 04/14] selftests: dpll: add pin enumeration and property tests
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider.
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider.
Critical: 0 · High: 0 · Medium: 1 · Low: 1
If you believe none of the issues are legitimate, please reply to each issue of Medium severity or higher and explain why the AI is wrong. If any of them are legitimate and you plan to respin, please let patchwork know by including "pw-bot: cr" as a separate line at the end of your reply (one such reply per series is enough).
- [Medium] In dpll_basic.sh, a failed pin dump is reported as SKIP instead of FAIL on hardware or user-selected DPLL devices.
- [Low] The comment in check_pin_properties (dpll_basic.sh line 106) says 'The parent-device nest is not present for all pin types'.
--
selftests: dpll: add pin enumeration and property tests
This adds pin query helpers and dpllsim_add_pin() to the dpll selftest library and gives the standard dpllsim device three pins. dpll_basic.sh gains pin enumeration, pin property and pin state tests. The stress script now creates its devices with a random set of pins.
Quoted 4 lines
tools/testing/selftests/drivers/dpll/dpll_basic.sh +0
-0
[ ... ]
Quoted 6 lines
tools/testing/selftests/drivers/dpll/dpll_basic.sh +2
-0@@ -43,6 +65,114 @@ test_device_properties() log_test "DPLL device properties" } +test_pin_enumeration() +{
tools/testing/selftests/drivers/dpll/dpll_basic.sh test_device_properties() · line 79
Quoted 10 lines
tools/testing/selftests/drivers/dpll/dpll_basic.sh +10
-0+ local pin_ids + + RET=0 + + mapfile -t pin_ids < <(dpll_pin_get_ids "$DPLL_DEV_ID") + if [ "${#pin_ids[@]}" -eq 0 ]; then + dpll_log_test_skip_or_fail "DPLL pin enumeration" \ + "no pins found" + return + fi
[Severity: Medium]
Can a failed pin dump get reported as a skip here?
dpll_pin_get_ids() returns 1 when the tool fails:
out=$(dpll pin show -j 2>/dev/null) || return 1
The exit status of a process substitution is lost, though, and mapfile itself succeeds. So a failed query leaves pin_ids empty, which looks the same as a device with no pins.
test_pin_properties() uses the same pattern:
mapfile -t pin_ids < <(dpll_pin_get_ids "$DPLL_DEV_ID")
test_pin_state() has the same problem. pipefail is not enabled there, so the pipeline returns the status of head:
pin_id=$(dpll_pin_find_by_direction "$DPLL_DEV_ID" input | head -1)
All three then call dpll_log_test_skip_or_fail(), which calls log_test_skip whenever DPLL_CREATED_DEVICE is empty. That happens for the hardware device dpll_lib_init() picks by default, for a device chosen through DPLL_DRIVER and for one set with DPLL_DEV_ID.
On such a device, would a pin dump that fails because of a driver or netlink regression be logged as a skip with "no pins found" or "no input pin", and not as a failure? On the created dpllsim device the test still fails, but it gives "no pins found" as the reason and does not say that the query failed.
The commit message says:
every pin query runs the tool first and jq on what it printed, so that a query which fails returns the status of the tool instead of the empty output a missing attribute produces too
None of these callers seem to check that status.
Quoted 7 lines
tools/testing/selftests/drivers/dpll/dpll_basic.sh +7
-0+ check_pin_ids "${pin_ids[@]}" + + log_test "DPLL pin enumeration (${#pin_ids[@]} pins)" +} + +check_pin_properties() +{
[ ... ]
tools/testing/selftests/drivers/dpll/dpll_basic.sh test_device_properties() · line 109
Quoted 4 lines
tools/testing/selftests/drivers/dpll/dpll_basic.sh +4
-0+ # The parent-device nest is not present for all pin types, but when + # the device under test is in it, that entry must carry the + # direction. A pin can be registered on more than one device, so + # match on parent-id instead of taking the first entry.
[Severity: Low]
This isn't a bug, but is the first sentence of this comment accurate? In the DPLL core, whether the parent-device nest is present depends on how the pin was registered, not on its type:
drivers/dpll/dpll_netlink.c:dpll_cmd_pin_get_one() {
...
if (xa_empty(&pin->parent_refs))
ret = dpll_msg_add_pin_dplls(msg, pin, extack);
else
ret = dpll_msg_add_pin_parents(msg, pin, ref, extack);
...
}A pin registered directly on a device always gets parent-device nests, MUX pins included. A pin registered on a parent pin gets parent-pin nests instead, whatever its type.
The comment above dpll_pin_get_ids() in dpll_lib.sh gets this right ("pins registered on those pins (MUX children)"). Could this comment say the same thing, that the nest is missing for pins registered on a parent pin? The check itself is not affected.
[ ... ]
Signature · 1 line
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261109093523.931201-1-poros%40redhat.com
PATCH v3 5/14 dpll: dpllsim: Add support to set input pin state
In reply to Petr OrosAdd support for setting the state of input pins on the device. Implement the state_on_dpll_set callback for input pins and advertise the state-can-change capability for them, so that userspace can move a pin between the states valid for the device mode. The core checks the capability only, the states a mode allows are up to the driver: in automatic mode a pin is selectable or disconnected, in manual mode connected or disconnected, as Documentation/driver-api/dpll.rst describes them. The device reports automatic mode, so connected is refused with -EINVAL and an extack message. The check is written for both modes already, so that manual mode has nothing to add to it when it arrives. The driver stores the requested state and reports it back unchanged, it does not act on it yet. Output pins keep reporting their state only.
---
v3:
- refuse pin states that are not valid in the device mode, so that connected is refused in automatic mode, the mode the device reports, with an extack message (Ivan Vecera)
- the Acked-by of v2 is not carried over, the callback changed
v2: https://lore.kernel.org/all/[email protected]/
v1: https://lore.kernel.org/all/[email protected]/
--- drivers/dpll/dpllsim/dpll.c | 42 +++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+)
drivers/dpll/dpllsim/dpll.c +42 -0
@@ -104,10 +104,46 @@ static int dpllsim_pin_state_get(const struct dpll_pin *pin, void *pin_priv,return 0 ; } +/* +* The states a user may request depend on the device mode: connected and +* disconnected select and deselect the reference in manual mode, selectable +* and disconnected offer a pin to or withdraw it from the automatic +* selection. The core checks the state-can-change capability only. +*/ +static bool dpllsim_pin_state_valid (const struct dpllsim_dev *sim, +enum dpll_pin_state state) +{ +if (state ==DPLL_PIN_STATE_DISCONNECTED ) +return true; +if (sim->mode ==DPLL_MODE_MANUAL ) +return state ==DPLL_PIN_STATE_CONNECTED ; +return state ==DPLL_PIN_STATE_SELECTABLE ; +} + +static int dpllsim_pin_state_set (const struct dpll_pin *pin,void *pin_priv, +const struct dpll_device *dpll, +void *dpll_priv,enum dpll_pin_state state, +struct netlink_ext_ack *extack) +{ +struct dpllsim_pin *sim_pin = pin_priv; +struct dpllsim_dev *sim = sim_pin->dev; + +guard (mutex)(&sim->lock); +if (!dpllsim_pin_state_valid (sim, state)) { +NL_SET_ERR_MSG_FMT (extack,"Pin state not valid in %s mode" , + sim->mode ==DPLL_MODE_MANUAL ? +"manual" :"automatic" ); +return -EINVAL ; + } + sim_pin->state = state; +return 0 ; +} +static const struct dpll_pin_ops dpllsim_input_pin_ops = { .frequency_get = dpllsim_pin_frequency_get, .direction_get = dpllsim_pin_direction_get, .state_on_dpll_get = dpllsim_pin_state_get, + .state_on_dpll_set = dpllsim_pin_state_set, };static const struct dpll_pin_ops dpllsim_output_pin_ops = {
@@ -149,6 +185,12 @@ static void dpllsim_pin_props(struct dpllsim_pin *pin, prop->freq_supported_num =ARRAY_SIZE (dpllsim_freq_ext);break ; } + +/* Capabilities follow the ops the pin is served by */ +if (dpllsim_pin_select_ops (pin) == &dpllsim_input_pin_ops) + prop->capabilities =DPLL_PIN_CAPABILITIES_STATE_CAN_CHANGE ; +else + prop->capabilities =0 ; }static bool dpllsim_pin_frequency_valid (struct dpllsim_pin *pin)
Signature · 1 line
2.56.0
Re: [PATCH net-next v3 05/14] dpll: dpllsim: Add support to set input pin state
Quoted 14 lines · Petr Oros
Add support for setting the state of input pins on the device. Implement the
state_on_dpll_setcallback for input pins and advertise the state-can-change capability for them, so that userspace can move a pin between the states valid for the device mode. The core checks the capability only, the states a mode allows are up to the driver: in automatic mode a pin is selectable or disconnected, in manual mode connected or disconnected, asDocumentation/driver-api/dpll.rstdescribes them. The device reports automatic mode, so connected is refused with-EINVALand an extack message. The check is written for both modes already, so that manual mode has nothing to add to it when it arrives. The driver stores the requested state and reports it back unchanged, it does not act on it yet. Output pins keep reporting their state only.
Quoted 7 lines
Signed-off-by Petr Oros<[email protected]>---
v3:
- refuse pin states that are not valid in the device mode, so that connected is refused in automatic mode, the mode the device reports, with an extack message (Ivan Vecera)
- the Acked-by of v2 is not carried over, the callback changed
LGTM...
PATCH v3 6/14 selftests: dpll: add pin state tests
In reply to Petr OrosAdd dpll_features.sh, a script that exercises individual device and pin callbacks with get/set roundtrips over netlink. It creates a dpllsim device with the standard pin layout by default or runs against the device named by DPLL_DEV_ID or the first device of the driver named by DPLL_DRIVER, and picks the pin to reconfigure up front, the first input pin that is not connected on the device, so that no test touches the reference the device is locked to.
The first test moves that pin through the states of the device mode, as Documentation/driver-api/dpll.rst defines them, reads the state back after each step and restores the original state at the end. In automatic mode these are disconnected and selectable, and on the device the script created the test then checks that connected is refused. Hardware drivers are not held to that, ice accepts connected in automatic mode, and a pin with the state-connected-override capability would take over the reference. In manual mode connected and disconnected
are the states, connecting the pin makes it the reference, so the test finds the pin connected before, connects it back at the end and checks that it is the reference again.
A driver may refuse a state the capability promises, for a constraint the interface does not tell. That is a skip on a device provided by the user, with the reason the tool reported, and a failure on the device the script created, whose simulator accepts both states of its mode. The restores are deferred right after the original states are read, and the cleanup handler of every script that defers runs the pending restores before it removes the devices the script created, so that an interrupted run puts the device back as well.
The stress script gains a test that floods one input pin of a fresh device with changes between the states of automatic mode. The library gains the state setter both use, which keeps the extack line of the tool in DPLL_LAST_ERR so that a test can tell a refusal from a failure, the device mode getter, and a check whether the pin set usage of the tool knows a keyword, so that a test skips on a tool too old for it rather than every script requiring a newer tool.
---
v3:
- follow the mode of the device under test: disconnected and selectable in automatic mode, where the simulator is checked to refuse connected, connected and disconnected in manual mode, where the pin connected before is connected back at the end
- cycle the state flooding test through the states of automatic mode
- add the device mode getter to the library
- honor DPLL_DRIVER like dpll_basic.sh does
- follow the move of the selftests to tools/testing/selftests/drivers/dpll
- the Reviewed-by and Acked-by of v2 are not carried over, the state test changed
v2:
- quote the ksft_skip expansion (netdev CI shellcheck)
v2: https://lore.kernel.org/all/[email protected]/
v1: https://lore.kernel.org/all/[email protected]/
--- tools/testing/selftests/drivers/dpll/Makefile | 1 + .../selftests/drivers/dpll/dpll_features.sh | 256 ++++++++++++++++++ .../selftests/drivers/dpll/dpll_lib.sh | 81 +++++- .../selftests/drivers/dpll/dpll_stress.sh | 58 ++++ 4 files changed, 395 insertions(+), 1 deletion(-) create mode 100755 tools/testing/selftests/drivers/dpll/dpll_features.sh
tools/testing/selftests/drivers/dpll/Makefile +1 -0
@@ -9,6 +9,7 @@ TEST_FILES := dpll_lib.sh TEST_PROGS := \ dpll_basic.sh \ + dpll_features.sh \ dpll_stress.sh \ # end of TEST_PROGS
tools/testing/selftests/drivers/dpll/dpll_features.sh new file +256 -0
256 lines not on this page · raw message
tools/testing/selftests/drivers/dpll/dpll_lib.sh +80 -1
105 lines not on this page · raw message
tools/testing/selftests/drivers/dpll/dpll_stress.sh +58 -0
76 lines not on this page · raw message
Signature · 1 line
2.56.0
Re: [PATCH net-next v3 06/14] selftests: dpll: add pin state tests
Quoted 39 lines · Petr Oros
Add
dpll_features.sh, a script that exercises individual device and pin callbacks with get/set roundtrips over netlink. It creates a dpllsim device with the standard pin layout by default or runs against the device named byDPLL_DEV_IDor the first device of the driver named byDPLL_DRIVER, and picks the pin to reconfigure up front, the first input pin that is not connected on the device, so that no test touches the reference the device is locked to.The first test moves that pin through the states of the device mode, as
Documentation/driver-api/dpll.rstdefines them, reads the state back after each step and restores the original state at the end. In automatic mode these are disconnected and selectable, and on the device the script created the test then checks that connected is refused. Hardware drivers are not held to that, ice accepts connected in automatic mode, and a pin with the state-connected-override capability would take over the reference. In manual mode connected and disconnected
are the states, connecting the pin makes it the reference, so the test finds the pin connected before, connects it back at the end and checks that it is the reference again.A driver may refuse a state the capability promises, for a constraint the interface does not tell. That is a skip on a device provided by the user, with the reason the tool reported, and a failure on the device the script created, whose simulator accepts both states of its mode. The restores are deferred right after the original states are read, and the cleanup handler of every script that defers runs the pending restores before it removes the devices the script created, so that an interrupted run puts the device back as well.
The stress script gains a test that floods one input pin of a fresh device with changes between the states of automatic mode. The library gains the state setter both use, which keeps the extack line of the tool in
DPLL_LAST_ERRso that a test can tell a refusal from a failure, the device mode getter, and a check whether the pin set usage of the tool knows a keyword, so that a test skips on a tool too old for it rather than every script requiring a newer tool.Signed-off-by Petr Oros<[email protected]>
Quoted 13 lines
---
v3:
- follow the mode of the device under test: disconnected and selectable in automatic mode, where the simulator is checked to refuse connected, connected and disconnected in manual mode, where the pin connected before is connected back at the end
- cycle the state flooding test through the states of automatic mode
- add the device mode getter to the library
- honorDPLL_DRIVERlikedpll_basic.shdoes
- follow the move of the selftests totools/testing/selftests/drivers/dpll
- the Reviewed-by and Acked-by of v2 are not carried over, the state test changed
[email protected]
AI reviewRe: [PATCH net-next v3 06/14] selftests: dpll: add pin state tests
In reply to Petr OrosThank you for your contribution! Sashiko AI review found 7 potential issue(s) to consider.
Thank you for your contribution! Sashiko AI review found 7 potential issue(s) to consider.
Critical: 0 · High: 0 · Medium: 4 · Low: 3
If you believe none of the issues are legitimate, please reply to each issue of Medium severity or higher and explain why the AI is wrong. If any of them are legitimate and you plan to respin, please let patchwork know by including "pw-bot: cr" as a separate line at the end of your reply (one such reply per series is enough).
- [Medium] In manual mode, test_pin_state_transitions() (tools/testing/selftests/drivers/dpll/dpll_features.sh) leaves the…
- [Medium] In automatic mode, test_pin_state_transitions() runs the pin through states="disconnected selectable".
- [Medium] When a state set inside the loop of test_pin_state_transitions() (lines 116-129) is refused, the test…
- [Medium] dpll_features.sh assumes that a pin whose state is not 'connected' cannot be the reference the device is locked to.
- [Low] The header of dpll_features.sh (lines 6-9) says that when DPLL_DEV_ID or DPLL_DRIVER is set, 'the simulator-only tests then skip…
- [Low] The commit message says the script picks 'the first input pin that is not connected on the device'.
- [Low] In test_pin_state_flooding() (dpll_stress.sh), the only readback check cannot catch a simulator whose state setter does nothing.
--
selftests: dpll: add pin state tests
This adds dpll_features.sh. It moves a spare input pin through the pin states of the device mode and checks each readback, either on a dpllsim device it creates or on a device chosen through DPLL_DEV_ID or DPLL_DRIVER. The stress script gains a pin state flooding test, and dpll_lib.sh gains a state setter, a device mode getter and a check for the pin set keywords the dpll tool supports.
Quoted 6 lines
tools/testing/selftests/drivers/dpll/dpll_features.sh new file +0
-0
tools/testing/selftests/drivers/dpll/dpll_features.sh line 9
Quoted 9 lines
tools/testing/selftests/drivers/dpll/dpll_features.sh new file +9
-0@@ -0,0 +1,256 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# DPLL device and pin feature tests. Exercises individual dpll_device_ops +# and dpll_pin_ops callbacks, verifying get/set roundtrips through the +# netlink interface. By default a dpllsim device with a known pin layout +# is created; set DPLL_DEV_ID or DPLL_DRIVER to run against an existing +# device instead, the simulator-only tests then skip unless that device +# is a dpllsim one.
[Severity: Low]
Is this description accurate? Nothing in the script seems to skip based on whether the device is a dpllsim one.
The only simulator-specific check in test_pin_state_transitions() depends on DPLL_CREATED_DEVICE, not on the module name:
if [ "$mode" = automatic ] && [ -n "$DPLL_CREATED_DEVICE" ]; then
So a dpllsim device passed through DPLL_DEV_ID silently loses that check, and no skip line is logged.
Also, dpll_lib_init() clears DPLL_DEV_ID when DPLL_DRIVER=dpllsim:
elif [ "${DPLL_DRIVER:-}" = "dpllsim" ]; then
DPLL_DEV_ID=""So that setting always creates a new simulator device instead of running against an existing one. The header is still the same at the end of the series.
[ ... ]
Quoted 13 lines
tools/testing/selftests/drivers/dpll/dpll_features.sh new file +13
-0+ before=$(dpll_pin_get_state "$DPLL_DEV_ID" "$PIN_TUNE_ID") + if [ -z "$before" ]; then + dpll_log_test_skip_or_fail "DPLL pin state transitions" \ + "state not reported" + return + fi + # Restore the original state even when the test aborts half way. + defer dpll_pin_set_state "$DPLL_DEV_ID" "$PIN_TUNE_ID" "$before" + + # In manual mode connecting the pin makes it the reference and + # takes that over from the pin connected so far, so that one is + # connected back at the end, after the restore above has + # disconnected the test pin again. In automatic mode the pin is not
tools/testing/selftests/drivers/dpll/dpll_features.sh line 105
Quoted 10 lines
tools/testing/selftests/drivers/dpll/dpll_features.sh new file +10
-0+ # connected, see how PIN_TUNE_ID is picked, and the test never + # connects it, the device keeps the reference it locked to. + ref_pin="" + if [ "$mode" = manual ]; then + ref_pin=$(find_connected_pin) + if [ -n "$ref_pin" ]; then + defer dpll_pin_set_state "$DPLL_DEV_ID" "$ref_pin" \ + connected + fi + fi
[Severity: Medium]
Do these two deferred commands run in the order the comment describes?
defer_scope_pop() in net/lib/sh/defer.sh runs deferred jobs in reverse order, last in first out:
for ((defer_ix=$(__defer__ndefers d); defer_ix-->0; )); do __defer__run d $defer_ix done
tests_run() wraps every test in in_defer_scope(), so both commands run after every test, including one that passed. In manual mode the sequence would be:
test body restores PIN_TUNE_ID to $before and reconnects $ref_pin test body finds $ref_pin connected, log_test reports a pass defer_scope_pop() -> dpll_pin_set_state ... "$ref_pin" connected defer_scope_pop() -> dpll_pin_set_state ... "$PIN_TUNE_ID" disconnected
On zl3073x, ZL_DPLL_MODE_REFSEL_MODE_REFLOCK is reported as manual mode. In that mode, zl3073x_dpll_input_pin_state_on_dpll_set() handles a disconnect request on any input pin by leaving the reference:
case ZL_DPLL_MODE_REFSEL_MODE_REFLOCK:
...
} else if (state == DPLL_PIN_STATE_DISCONNECTED) {
/* Choose new mode based on lock status */
switch (zldpll->lock_status) {
...
mode = ZL_DPLL_MODE_REFSEL_MODE_FREERUN;The dpll core does not skip a set that matches the current state.
Could this last deferred disconnect of the test pin leave the device in holdover or freerun after the test has already reported a pass?
That would also contradict the commit message:
"connects it back at the end and checks that it is the reference again"
An interrupted run has the same ordering problem. The reconnect of $ref_pin runs while PIN_TUNE_ID may still be the selected reference, and PIN_TUNE_ID is disconnected afterwards.
Would registering the $ref_pin defer before the PIN_TUNE_ID one give the intended order? This code is still the same at the end of the series.
[ ... ]
tools/testing/selftests/drivers/dpll/dpll_features.sh line 124
Quoted 9 lines
tools/testing/selftests/drivers/dpll/dpll_features.sh new file +9
-0+ for state in $states; do + if ! dpll_pin_set_state "$DPLL_DEV_ID" "$PIN_TUNE_ID" \ + "$state"; then + reason=${DPLL_LAST_ERR%%$'\n'*} + dpll_log_test_skip_or_fail \ + "DPLL pin state transitions" \ + "cannot set pin state to $state${reason:+, $reason}" + return + fi
[Severity: Medium]
Can this skip hide a failure that an earlier iteration recorded?
Suppose the first state is set but reads back differently, so check_err records a failure. If the second set is then refused on a user-provided device, dpll_log_test_skip_or_fail() calls log_test_skip(), which does:
RET=$ksft_skip retmsg= log_test "$@"
The test then reports a skip and the earlier failure message is lost.
The restore path further down guards against this with
[ "$RET" -eq 0 ], and the comment above the loop says "a skip would hide the earlier one". The loop path has no such check.
tools/testing/selftests/drivers/dpll/dpll_features.sh line 129
Quoted 5 lines
tools/testing/selftests/drivers/dpll/dpll_features.sh new file +5
-0+ after=$(dpll_pin_get_state "$DPLL_DEV_ID" "$PIN_TUNE_ID") + if [ "$after" != "$state" ]; then + check_err 1 "pin not $state, state '$after'" + fi + done
[Severity: Medium]
In automatic mode, can the selectable step change the reference the device is locked to?
When PIN_TUNE_ID starts out disconnected, setting it selectable puts it back into hardware selection with its configured priority. ice enables the input in ice_dpll_input_state_set():
bool enable = state == DPLL_PIN_STATE_SELECTABLE;
zl3073x restores pin->prio in the hardware priority table.
If the pin outranks the current reference and has a valid signal, the DPLL would switch to it. The restore to disconnected would then switch it back.
The commit message says the pin is picked "so that no test touches the reference the device is locked to". However, pick_tune_pin() only looks at the state, not at priority or operstate.
On ice, ice_dpll_pin_state_update() reports the active input as connected:
pin->state[pf->dplls.eec.dpll_idx] = pin->pin == pf->dplls.eec.active_input ? DPLL_PIN_STATE_CONNECTED : DPLL_PIN_STATE_SELECTABLE;
Would this readback then fail with "pin not selectable, state 'connected'", depending on how the board's priorities are set up?
[ ... ]
Quoted 11 lines
tools/testing/selftests/drivers/dpll/dpll_features.sh new file +11
-0+# PIN_TUNE_ID is the input pin the tests reconfigure. It must not be the +# reference the device is locked to, so it is the first input pin that +# is not connected on the device. When the first dumped input pin and +# another one both qualify the other one is taken, so that the first +# pin, the one a simulated device locks to once it is selectable, is +# left alone. Tests that need the pin skip when there is none rather +# than reconfiguring the reference. +pick_tune_pin() +{ + local pin_id state +
tools/testing/selftests/drivers/dpll/dpll_features.sh line 245
Quoted 10 lines
tools/testing/selftests/drivers/dpll/dpll_features.sh new file +10
-0+ PIN_TUNE_ID="" + for pin_id in "${input_pins[@]}"; do + state=$(dpll_pin_get_state "$DPLL_DEV_ID" "$pin_id") + case "$state" in + disconnected|selectable) + ;; + *) + continue + ;; + esac
[Severity: Medium]
On zl3073x, could this pick the reference the device is locked to?
In automatic mode, zl3073x_dpll_input_pin_state_on_dpll_get() never reports connected:
case ZL_DPLL_MODE_REFSEL_MODE_AUTO: if (zl3073x_chan_ref_is_selectable(chan, ref)) *state = DPLL_PIN_STATE_SELECTABLE; else *state = DPLL_PIN_STATE_DISCONNECTED;
So the active reference reads as selectable and passes this filter. pick_tune_pin() prefers a qualifying pin other than input_pins[0], so it would pick the locked reference whenever that reference is not the first dumped pin.
test_pin_state_transitions() then sets the pin disconnected. The ZL_DPLL_MODE_REFSEL_MODE_AUTO branch of zl3073x_dpll_input_pin_state_on_dpll_set() handles that like this:
/* Set pin priority to none in HW */ zl3073x_chan_ref_prio_set(&chan, ref, ZL_DPLL_REF_PRIO_NONE);
That drops the active reference.
Devices that do report the active input as connected have a related gap. The state is checked only once here, before any test runs. test_pin_state_transitions() reads $before again but does not reject connected in automatic mode. A hardware reselection between pick_tune_pin() and the test would therefore go unnoticed.
Would checking the pin operstate (active or standby), and checking again right before the first set, avoid touching the locked reference? This code is still the same at the end of the series.
tools/testing/selftests/drivers/dpll/dpll_features.sh line 249
Quoted 4 lines
tools/testing/selftests/drivers/dpll/dpll_features.sh new file +4
-0+ PIN_TUNE_ID=$pin_id + [ "$pin_id" != "${input_pins[0]}" ] && return + done +}
[Severity: Low]
This isn't a bug, but the commit message and the comment above pick_tune_pin() don't match this code.
The commit message says the script picks:
"the first input pin that is not connected on the device"
This early return, however, prefers any qualifying pin other than input_pins[0].
The comment explains that choice with "the one a simulated device locks to once it is selectable". dpllsim_pin_state_set() only stores the state, though:
sim_pin->state = state; return 0;
Nothing in dpllsim selects a reference or changes the lock status from its initial unlocked value.
Could the comment and the commit message be updated?
[ ... ]
Quoted 4 lines
tools/testing/selftests/drivers/dpll/dpll_stress.sh +0
-0
[ ... ]
Quoted 9 lines
tools/testing/selftests/drivers/dpll/dpll_stress.sh +5
-0@@ -133,6 +135,62 @@ test_rapid_create_delete() log_test "DPLL rapid create/delete ($STRESS_ITERATIONS iterations)" } +test_pin_state_flooding() +{ + local clock_id name dev_id pin_id i last state fails=0 + # The states of automatic mode, the mode the simulator runs in + local states=(disconnected selectable)
[ ... ]
Quoted 4 lines
tools/testing/selftests/drivers/dpll/dpll_stress.sh +4
-0+ for i in $(seq 1 50); do + last=${states[i % 2]} + dpll_pin_set_state "$dev_id" "$pin_id" \ + "$last" ||
tools/testing/selftests/drivers/dpll/dpll_stress.sh test_rapid_create_delete() · line 181
Quoted 10 lines
tools/testing/selftests/drivers/dpll/dpll_stress.sh +10
-0+ fails=$((fails + 1)) + done + if [ "$fails" -gt 0 ]; then + check_err 1 "$fails of 50 state changes failed" + fi + state=$(dpll_pin_get_state "$dev_id" "$pin_id") + if [ "$state" != "$last" ]; then + check_err 1 \ + "state expected '$last', got '$state'" + fi
[Severity: Low]
Can this readback catch a state setter that does nothing? With i running from 1 to 50, the last request is states[50 % 2], which is disconnected.
dpllsim_pin_reset() initialises every pin to that same state:
pin->state = DPLL_PIN_STATE_DISCONNECTED;
On a freshly created device, a state_on_dpll_set that returned 0 without storing anything would still pass. The fails counter only catches non-zero returns. The test is still the same at the end of the series.
Signature · 1 line
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261109093523.931201-1-poros%40redhat.com
PATCH v3 7/14 dpll: dpllsim: Add support to get/set priority on input pins
In reply to Petr OrosAdd support for getting and setting the priority of input pins. Implement the prio_get and prio_set callbacks and advertise the priority-can-change capability for input pins, which includes the MUX type pins since those are always inputs.
The initial priority of a pin is configured through a new prio attribute of the pin directory, which takes a value between 0 and 255 and defaults to 128, so that a test can lay out the reference order before the device goes live. Like the other pin attributes it is writable only while the device is not live. Priority changes made over netlink are stored and reported back, they will drive the automatic reference selection once the simulation loop exists. The setter rejects a value above 255 with EINVAL and an extack message. A priority is meaningful for input pins only, so live fails with EINVAL when an output pin was given a priority other than the default, next to the frequency validation.
--- Documentation/driver-api/dpllsim.rst | 5 ++++ drivers/dpll/dpllsim/core.c | 31 +++++++++++++++++++ drivers/dpll/dpllsim/dpll.c | 45 ++++++++++++++++++++++++++-- drivers/dpll/dpllsim/dpllsim.h | 10 ++++++- 4 files changed, 88 insertions(+), 3 deletions(-)
Documentation/driver-api/dpllsim.rst +5 -0
@@ -36,6 +36,9 @@ Pin directory ``/sys/kernel/config/dpllsim/<dev>/pins/<pin>/``: (default 0, which is rejected) ``direction`` ``input`` or ``output`` (default ``input``), a ``mux`` pin must be an input +``prio`` Priority 0-255 used for automatic reference selection, + input pins only, output pins must keep the default + (default 128) ============= =============================================================== The pin directory name becomes the board label of the pin. Pins are
@@ -53,8 +56,10 @@ output, then make it live:: mkdir pins/gnss0 echo gnss > pins/gnss0/type echo 1 > pins/gnss0/frequency + echo 0 > pins/gnss0/prio mkdir pins/ext0 echo 10000000 > pins/ext0/frequency + echo 1 > pins/ext0/prio mkdir pins/out0 echo 10000000 > pins/out0/frequency echo output > pins/out0/direction
drivers/dpll/dpllsim/core.c +31 -0
@@ -132,10 +132,40 @@ static ssize_t dpllsim_pin_frequency_store(struct config_item *item, }CONFIGFS_ATTR (dpllsim_pin_, frequency); +static ssize_t dpllsim_pin_prio_show (struct config_item *item,char *page) +{ +struct dpllsim_pin *pin =to_dpllsim_pin (item); + +guard (mutex)(&pin->dev->lock); +return sysfs_emit (page,"%u\n" , pin->prio); +} + +static ssize_t dpllsim_pin_prio_store (struct config_item *item, +const char *page,size_t count) +{ +struct dpllsim_pin *pin =to_dpllsim_pin (item); +u32 val; +int err; + + err =kstrtou32 (page,10 , &val); +if (err) +return err; +if (val >DPLLSIM_PIN_PRIO_MAX ) +return -EINVAL ; + +guard (mutex)(&pin->dev->lock); +if (dpllsim_dev_frozen (pin->dev)) +return -EBUSY ; + pin->prio = val; +return count; +} +CONFIGFS_ATTR (dpllsim_pin_, prio); +static struct configfs_attribute *dpllsim_pin_attrs[] = { &dpllsim_pin_attr_type, &dpllsim_pin_attr_direction, &dpllsim_pin_attr_frequency, + &dpllsim_pin_attr_prio,NULL , };
@@ -178,6 +208,7 @@ static struct config_item *dpllsim_pins_make_item(struct config_group *group, pin->dev = sim; pin->type =DPLL_PIN_TYPE_EXT ; pin->direction =DPLL_PIN_DIRECTION_INPUT ; + pin->prio =DPLLSIM_DEFAULT_PIN_PRIO ;list_add_tail (&pin->list, &sim->pin_list);return &pin->item;
drivers/dpll/dpllsim/dpll.c +43 -2
74 lines not on this page · raw message
drivers/dpll/dpllsim/dpllsim.h +9 -1
@@ -12,6 +12,9 @@#include <linux/list.h> #include <linux/mutex.h> +#define DPLLSIM_DEFAULT_PIN_PRIO 128 +#define DPLLSIM_PIN_PRIO_MAX 255 +#define DPLLSIM_MAX_PINS 128struct dpllsim_dev ;
@@ -21,10 +24,15 @@ struct dpllsim_pin {struct list_head list;struct dpllsim_dev *dev; -/* Configuration, writable only until dpllsim_dev_frozen() */ +/* +* Configuration, set through configfs only until +* dpllsim_dev_frozen(). The netlink prio setter changes prio at +* runtime under dev->lock. +*/ enum dpll_pin_type type;enum dpll_pin_direction direction;u64 frequency; +u32 prio;/* Resolved at live=1 */ int idx;
Signature · 1 line
2.56.0
[email protected]
AI reviewRe: [PATCH net-next v3 07/14] dpll: dpllsim: Add support to get/set priority on input pins
Thank you for your contribution! Sashiko AI review found more severe issue(s) in other patches of this series. This mail is only an FYI, in case you want to address the nit picks below when you…
Thank you for your contribution! Sashiko AI review found more severe issue(s) in other patches of this series. This mail is only an FYI, in case you want to address the nit picks below when you respin.
Low priority / nit picks for your consideration:
- [Low] One field, pin->prio, holds both the configfs setting and the live value.
- [Low] Documentation/driver-api/dpllsim.rst (line 39) describes the new prio attribute as 'Priority 0-255 used for automatic reference…
--
dpll: dpllsim: Add support to get/set priority on input pins
This commit adds prio_get and prio_set callbacks for dpllsim input pins and advertises the priority-can-change capability. It also adds a prio configfs attribute that sets the initial priority before the device goes live. Going live is rejected when an output pin has a non-default priority.
Documentation/driver-api/dpllsim.rst Pin directory ``/sys/kernel/config/dpllsim/<dev>/pins/<pin>/``: · line 41
Quoted 11 lines
Documentation/driver-api/dpllsim.rst +3
-0@@ -36,6 +36,9 @@ Pin directory ``/sys/kernel/config/dpllsim/<dev>/pins/<pin>/``: (default 0, which is rejected) ``direction`` ``input`` or ``output`` (default ``input``), a ``mux`` pin must be an input +``prio`` Priority 0-255 used for automatic reference selection, + input pins only, output pins must keep the default + (default 128)
[Severity: Low]
Is "used for automatic reference selection" accurate for dpllsim at this point?
As far as I can tell, only dpllsim_pin_prio_get(), dpllsim_pin_prio_valid() and dpllsim_pin_prio_show() read pin->prio. The device ops have no selection logic:
static const struct dpll_device_ops dpllsim_dpll_ops = {
.mode_get = dpllsim_mode_get, .lock_status_get = dpllsim_lock_status_get,
};
sim->lock_status stays at the DPLL_LOCK_STATUS_UNLOCKED value set in dpllsim_make_group(). Nothing changes a pin's state based on its priority.
The example further down sets gnss0 prio 0 and ext0 prio 1 as if that sets up a reference order. The selftest library comment in dpllsim_setup_standard_device() ("gnss0: ... prio 0 (active reference)") gives the same impression.
The commit message says priorities "will drive the automatic reference selection once the simulation loop exists". I couldn't find a later patch in the series that adds that loop.
Could the documentation say that the priority is currently only stored and reported back?
[ ... ]
Quoted 4 lines
drivers/dpll/dpllsim/dpll.c +0
-0
[ ... ]
Quoted 5 lines
drivers/dpll/dpllsim/dpll.c +5
-0+static int dpllsim_pin_prio_set (const struct dpll_pin *pin,void *pin_priv, +const struct dpll_device *dpll, +void *dpll_priv,u32 prio, +struct netlink_ext_ack *extack) +{
drivers/dpll/dpllsim/dpll.c static int dpllsim_pin_state_set(const struct dpll_pin *pin, void *pin_priv, · line 168
Quoted 10 lines
drivers/dpll/dpllsim/dpll.c +10
-0+struct dpllsim_pin *sim_pin = pin_priv; + +if (prio >DPLLSIM_PIN_PRIO_MAX ) { +NL_SET_ERR_MSG_FMT (extack,"Pin priority must be in range 0-%u" , +DPLLSIM_PIN_PRIO_MAX ); +return -EINVAL ; + } + +guard (mutex)(&sim_pin->dev->lock); + sim_pin->prio = prio;
[Severity: Low]
Should the netlink setter write to the same field that holds the priority configured through configfs?
dpllsim_pins_make_item() and dpllsim_pin_prio_store() set pin->prio, and this function writes it at runtime. On each live=1, dpllsim_order_pins() calls dpllsim_pin_reset(), which resets ops and state but not prio:
static void dpllsim_pin_reset(struct dpllsim_pin *pin)
{
pin->ops = dpllsim_pin_select_ops(pin);
pin->state = DPLL_PIN_STATE_DISCONNECTED;
}dpllsim_dev_undeploy() doesn't restore the configured value either.
Consider this sequence:
echo 1 > pins/ext0/prio
echo 1 > live
dpll pin set ... prio 200
echo 0 > live
echo 1 > live
The second deployment starts with prio 200. Reading pins/ext0/prio through dpllsim_pin_prio_show() also returns 200, which was never written through configfs.
There is a second case. Take an input pin whose prio was changed over netlink to something other than 128. Then write 0 to live, write output to direction, and write 1 to live. The live write fails with EINVAL from dpllsim_order_pins(), because of a priority that was only set at runtime:
static bool dpllsim_pin_prio_valid(const struct dpllsim_pin *pin)
{
return pin->direction == DPLL_PIN_DIRECTION_INPUT ||
pin->prio == DPLLSIM_DEFAULT_PIN_PRIO;
}The commit message describes the attribute as setting the initial priority "so that a test can lay out the reference order before the device goes live". Neither the commit message nor dpllsim.rst says that netlink changes carry into later deployments and replace that layout.
The later commit "dpll: dpllsim: Add support to set frequency on pins" takes the same shared-field approach for frequency. No later patch in the series separates the configured and runtime prio or resets it.
Would it make sense to keep the configured priority separate and copy it into the runtime value in dpllsim_pin_reset()?
[ ... ]
Signature · 1 line
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261109093523.931201-1-poros%40redhat.com
PATCH v3 8/14 selftests: dpll: add pin priority test
In reply to Petr OrosAdd a test that sets a new priority on an input pin, reads it back and restores the original value. The range of priorities a device accepts is not advertised over netlink, so the test tries one below the current value and then the small values every driver accepts, since a large current value may mark an unused pin outside the settable range, as the ice driver does with 255. A driver may still refuse all of them for a constraint the interface does not tell, so that is a skip on a device provided by the user, with the reason the tool reported, and a failure on the device the script created, whose simulator accepts every priority. The test skips when the dpll tool lacks the prio keyword of pin set, and the restore is deferred right after the original value is read so that an interrupted run restores it too. A pin that does not advertise the priority-can-change capability or does not report a priority is a skip on a device provided by the user and a failure on the device the script created, whose pins have both.
The dpllsim_add_pin helper gains an optional priority argument, which is written only when it is given, so that a pin without one keeps the default of the simulator. A priority is accepted for input pins only, so the callers pass one for those alone.
The standard device lays its inputs out as gnss0 with priority 0 and ext0 with priority 1, and the stress devices get random priorities on their input pins.
---
v3:
- follow the move of the selftests to
tools/testing/selftests/drivers/dpll
v2: https://lore.kernel.org/all/[email protected]/
v1: https://lore.kernel.org/all/[email protected]/
--- .../selftests/drivers/dpll/dpll_features.sh | 65 +++++++++++++++++++ .../selftests/drivers/dpll/dpll_lib.sh | 32 +++++++-- .../selftests/drivers/dpll/dpll_stress.sh | 15 +++-- 3 files changed, 100 insertions(+), 12 deletions(-)
tools/testing/selftests/drivers/dpll/dpll_features.sh +65 -0
@@ -17,6 +17,7 @@ source "$SCRIPTDIR/dpll_lib.sh" ALL_TESTS=" test_pin_state_transitions + test_pin_priority_get_set " ##############################################################################
@@ -37,6 +38,70 @@ find_connected_pin() done } +test_pin_priority_get_set() +{ + local before target after reason candidate + + RET=0 + + if [ -z "$PIN_TUNE_ID" ]; then + dpll_log_test_skip_or_fail "DPLL pin priority set/get" \ + "no spare input pin" + return + fi + if ! dpll_pin_set_supports prio; then + log_test_skip "DPLL pin priority set/get" \ + "dpll tool lacks pin set prio" + return + fi + if ! dpll_pin_has_capability "$PIN_TUNE_ID" priority-can-change; then + dpll_log_test_skip_or_fail "DPLL pin priority set/get" \ + "priority cannot change" + return + fi + + before=$(dpll_pin_get_prio "$DPLL_DEV_ID" "$PIN_TUNE_ID") + if [ -z "$before" ]; then + dpll_log_test_skip_or_fail "DPLL pin priority set/get" \ + "prio not reported" + return + fi + defer dpll_pin_set_prio "$DPLL_DEV_ID" "$PIN_TUNE_ID" "$before" + + # The accepted range is not advertised, so try one below the current + # value and then small values, a refusal of all of them is a skip on + # a device the user provided, the simulator accepts every priority. + target="" + for candidate in $((before - 1)) 1 7; do + [ "$candidate" -ge 0 ] && [ "$candidate" != "$before" ] || + continue + if dpll_pin_set_prio "$DPLL_DEV_ID" "$PIN_TUNE_ID" \ + "$candidate"; then + target=$candidate + break + fi + reason=${DPLL_LAST_ERR%%$'\n'*} + done + if [ -z "$target" ]; then + dpll_log_test_skip_or_fail "DPLL pin priority set/get" \ + "cannot set a new prio${reason:+, $reason}" + return + fi + after=$(dpll_pin_get_prio "$DPLL_DEV_ID" "$PIN_TUNE_ID") + if [ "$after" != "$target" ]; then + check_err 1 "priority expected $target, got '$after'" + fi + + dpll_pin_set_prio "$DPLL_DEV_ID" "$PIN_TUNE_ID" "$before" + check_err $? "cannot restore pin priority" + after=$(dpll_pin_get_prio "$DPLL_DEV_ID" "$PIN_TUNE_ID") + if [ "$after" != "$before" ]; then + check_err 1 "priority not restored, got '$after'" + fi + + log_test "DPLL pin priority set/get" +} + test_pin_state_transitions() { local mode before state after reason ref_pin
tools/testing/selftests/drivers/dpll/dpll_lib.sh +26 -6
@@ -395,6 +395,11 @@ dpll_pin_has_capability() <<< "$out" &> /dev/null } +dpll_pin_get_prio() +{ + __dpll_pin_dev_field "$1" "$2" '.prio' +} + ############################################################################## # Pin setters #
@@ -416,6 +421,16 @@ dpll_pin_set_state() state "$state" 2>&1 >/dev/null) } +dpll_pin_set_prio() +{ + local dev_id=$1 + local pin_id=$2 + local prio=$3 + + DPLL_LAST_ERR=$(dpll pin set id "$pin_id" parent-device "$dev_id" \ + prio "$prio" 2>&1 >/dev/null) +} + ############################################################################## # dpllsim configfs interface #
@@ -497,7 +512,9 @@ dpllsim_create_device() # $1: device name, $2: pin name (becomes the board-label), $3: type (gnss, # int-oscillator, ext, synce-eth-port, mux), $4: frequency in Hz, -# $5: direction (input/output) +# $5: direction (input/output), $6: prio (optional, input pins only). +# The priority is written only when one is given, so a pin without one +# keeps the default of the simulator. dpllsim_add_pin() { local dev=$1
@@ -505,13 +522,16 @@ dpllsim_add_pin() local type=$3 local frequency=$4 local direction=$5 + local prio=${6:-} local pin_path="$DPLLSIM_CFG/$dev/pins/$pin" mkdir "$pin_path" 2>/dev/null || return 1 if ! echo "$type" 2>/dev/null > "$pin_path/type" || ! echo "$frequency" 2>/dev/null > "$pin_path/frequency" || - ! echo "$direction" 2>/dev/null > "$pin_path/direction"; then + ! echo "$direction" 2>/dev/null > "$pin_path/direction" || + { [ -n "$prio" ] && + ! echo "$prio" 2>/dev/null > "$pin_path/prio"; }; then rmdir "$pin_path" 2>/dev/null return 1 fi
@@ -559,8 +579,8 @@ dpllsim_alloc_clock_id() # Create a device with the standard 3-pin layout, deploy it and resolve # the netlink device id. Sets DPLLSIM_CREATED_NAME, DPLLSIM_CLOCK_ID and # DPLLSIM_DEV_ID. -# gnss0: GNSS, 1 Hz, input -# ext0: EXT, 10 MHz, input +# gnss0: GNSS, 1 Hz, input, prio 0 (active reference) +# ext0: EXT, 10 MHz, input, prio 1 # out0: EXT, 10 MHz, output # shellcheck disable=SC2120 # the argument is optional dpllsim_setup_standard_device()
@@ -572,8 +592,8 @@ dpllsim_setup_standard_device() name="std$clock_id" dpllsim_create_device "$name" "$clock_id" || return 1 - if ! dpllsim_add_pin "$name" gnss0 gnss 1 input || - ! dpllsim_add_pin "$name" ext0 ext 10000000 input || + if ! dpllsim_add_pin "$name" gnss0 gnss 1 input 0 || + ! dpllsim_add_pin "$name" ext0 ext 10000000 input 1 || ! dpllsim_add_pin "$name" out0 ext 10000000 output; then dpllsim_delete_device "$name" return 1
tools/testing/selftests/drivers/dpll/dpll_stress.sh +9 -6
@@ -77,13 +77,13 @@ create_stress_device() local clock_id=$1 local num_pins=$2 local name="st$clock_id" - local i type freq dir + local i type freq dir prio local dirs=(input output) dpllsim_create_device "$name" "$clock_id" || return 1 # Always have at least one plain input pin, then random ones - if ! dpllsim_add_pin "$name" ext0 ext 10000000 input; then + if ! dpllsim_add_pin "$name" ext0 ext 10000000 input 1; then dpllsim_delete_device "$name" return 1 fi
@@ -94,8 +94,11 @@ create_stress_device() # output one dir=${dirs[RANDOM % 2]} [ "$type" = mux ] && dir=input - if ! dpllsim_add_pin "$name" "pin$i" "$type" "$freq" \ - "$dir"; then + # Only input pins accept a priority + prio="" + [ "$dir" = input ] && prio=$((RANDOM % 128)) + if ! dpllsim_add_pin "$name" "pin$i" "$type" "$freq" "$dir" \ + "$prio"; then dpllsim_delete_device "$name" return 1 fi
@@ -206,8 +209,8 @@ test_deploy_cycle() fi track_device "$name" - if ! dpllsim_add_pin "$name" gnss0 gnss 1 input || - ! dpllsim_add_pin "$name" ext0 ext 10000000 input || + if ! dpllsim_add_pin "$name" gnss0 gnss 1 input 0 || + ! dpllsim_add_pin "$name" ext0 ext 10000000 input 1 || ! dpllsim_add_pin "$name" out0 ext 10000000 output; then check_err 1 "cannot add pins" log_test "DPLL deploy/undeploy cycling"
Signature · 1 line
2.56.0
[email protected]
AI reviewRe: [PATCH net-next v3 08/14] selftests: dpll: add pin priority test
Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider.
Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider.
Critical: 0 · High: 0 · Medium: 2 · Low: 2
If you believe none of the issues are legitimate, please reply to each issue of Medium severity or higher and explain why the AI is wrong. If any of them are legitimate and you plan to respin, please let patchwork know by including "pw-bot: cr" as a separate line at the end of your reply (one such reply per series is enough).
- [Medium] pick_tune_pin() says PIN_TUNE_ID "must not be the reference the device is locked to" and that tests skip "rather than…
- [Medium] The candidate loop in test_pin_priority_get_set() is built for the case the commit message describes: "a large current value may mark…
- [Low] The comment in test_pin_priority_get_set() (tools/testing/selftests/drivers/dpll/dpll_features.sh) and the commit…
- [Low] This patch adds a layout comment above dpllsim_setup_standard_device() in…
--
selftests: dpll: add pin priority test
This adds test_pin_priority_get_set() to dpll_features.sh. The test sets a new priority on a spare input pin, reads it back and restores the original value. dpllsim_add_pin() gains an optional priority argument, and the standard and stress simulator devices now set priorities on their input pins.
Quoted 4 lines
tools/testing/selftests/drivers/dpll/dpll_features.sh +0
-0
[ ... ]
tools/testing/selftests/drivers/dpll/dpll_features.sh find_connected_pin() · line 73
Quoted 11 lines
tools/testing/selftests/drivers/dpll/dpll_features.sh +11
-0+ before=$(dpll_pin_get_prio "$DPLL_DEV_ID" "$PIN_TUNE_ID") + if [ -z "$before" ]; then + dpll_log_test_skip_or_fail "DPLL pin priority set/get" \ + "prio not reported" + return + fi + defer dpll_pin_set_prio "$DPLL_DEV_ID" "$PIN_TUNE_ID" "$before" + + # The accepted range is not advertised, so try one below the current + # value and then small values, a refusal of all of them is a skip on + # a device the user provided, the simulator accepts every priority.
[Severity: Low]
Is "the simulator accepts every priority" accurate? The commit message makes the same claim. However, dpllsim_pin_prio_set() rejects anything above DPLLSIM_PIN_PRIO_MAX, which is 255 in dpllsim.h:
drivers/dpll/dpllsim/dpll.c:dpllsim_pin_prio_set() {
...
if (prio > DPLLSIM_PIN_PRIO_MAX) {
NL_SET_ERR_MSG_FMT(extack, "Pin priority must be in range 0-%u",
DPLLSIM_PIN_PRIO_MAX);
return -EINVAL;
}
...
}The configfs store dpllsim_pin_prio_store() applies the same limit.
The test still works today. Since before is capped at 255, the candidates (before - 1, 1 and 7) are always in range.
This comment is the reason a refusal on the created device is a failure and not a skip. Could it say "every priority in 0-255" or "every value tried here"? The comment is still the same at the end of the series.
tools/testing/selftests/drivers/dpll/dpll_features.sh find_connected_pin() · line 79
Quoted 6 lines
tools/testing/selftests/drivers/dpll/dpll_features.sh +6
-0+ target="" + for candidate in $((before - 1)) 1 7; do + [ "$candidate" -ge 0 ] && [ "$candidate" != "$before" ] || + continue + if dpll_pin_set_prio "$DPLL_DEV_ID" "$PIN_TUNE_ID" \ + "$candidate"; then
[Severity: Medium]
Can this make a user-provided device switch its reference?
The comment above pick_tune_pin() says PIN_TUNE_ID "must not be the reference the device is locked to". It also says tests skip "rather than reconfiguring the reference". But pick_tune_pin() accepts selectable pins:
case "$state" in disconnected|selectable) ;;
In automatic mode, a selectable input takes part in reference selection by priority. This loop gives that pin a higher priority (a smaller value). It never compares the new value with the priority of the current reference.
On a device set through DPLL_DEV_ID or DPLL_DRIVER, the new priority reaches hardware straight away. zl3073x_dpll_input_pin_prio_set() writes REF_PRIO through zl3073x_chan_state_set() when zl3073x_chan_ref_is_selectable() holds. ice_dpll_hw_input_prio_set() sends it to the CGU firmware through ice_aq_set_cgu_ref_prio().
Take a device in automatic, revertive mode with a valid signal on the spare input. If before - 1 ties the locked reference's priority, or if 1 or 7 beats it, could the DPLL switch to the spare pin? Restoring the priority could then switch it back. Each switch can disturb outputs feeding SyncE or PTP.
Would it work to pick a target that does not outrank the connected reference? Other options are to change the priority only on disconnected pins, or to skip in automatic mode on user-provided devices.
Quoted 5 lines
tools/testing/selftests/drivers/dpll/dpll_features.sh +5
-0+ target=$candidate + break + fi + reason=${DPLL_LAST_ERR%%$'\n'*} + done
[ ... ]
tools/testing/selftests/drivers/dpll/dpll_features.sh find_connected_pin() · line 96
Quoted 2 lines
tools/testing/selftests/drivers/dpll/dpll_features.sh +2
-0+ dpll_pin_set_prio "$DPLL_DEV_ID" "$PIN_TUNE_ID" "$before" + check_err $? "cannot restore pin priority"
[Severity: Medium]
What happens here in the case the candidate loop is built for? The commit message says:
since a large current value may mark an unused pin outside the settable range, as the ice driver does with 255.
In that case before itself cannot be set. Suppose before - 1 (254) is refused and then 1 or 7 is accepted. The pin's hardware priority has changed, and this restore to before would be refused for the same reason.
The test would then report a failure, not a skip, on a user-provided device. The deferred restore sends the same value, so it also fails, and without any message. The unused input stays at priority 1 or 7 on the user's hardware, and in automatic mode it could then be picked as a reference.
ice_dpll_input_prio_set() and ice_dpll_hw_input_prio_set() do no range check of their own. They pass (u8)prio to firmware, so only firmware decides whether 255 can be written back.
test_pin_state_transitions() treats a refused restore as a skip when RET is 0, but this test does not. Should it first check that before can be set, for example by writing it back once, before trying a different value? The code is still the same at the end of the series.
Quoted 4 lines
tools/testing/selftests/drivers/dpll/dpll_features.sh +4
-0+ after=$(dpll_pin_get_prio "$DPLL_DEV_ID" "$PIN_TUNE_ID") + if [ "$after" != "$before" ]; then + check_err 1 "priority not restored, got '$after'" + fi
[ ... ]
Quoted 4 lines
tools/testing/selftests/drivers/dpll/dpll_lib.sh +0
-0
[ ... ]
tools/testing/selftests/drivers/dpll/dpll_lib.sh dpllsim_alloc_clock_id() · line 582
Quoted 7 lines
tools/testing/selftests/drivers/dpll/dpll_lib.sh +1
-2@@ -559,8 +579,8 @@ dpllsim_alloc_clock_id() # Create a device with the standard 3-pin layout, deploy it and resolve # the netlink device id. Sets DPLLSIM_CREATED_NAME, DPLLSIM_CLOCK_ID and # DPLLSIM_DEV_ID. -# gnss0: GNSS, 1 Hz, input -# ext0: EXT, 10 MHz, input +# gnss0: GNSS, 1 Hz, input, prio 0 (active reference)
[Severity: Low]
Is "(active reference)" correct for the simulator? It does not seem to ever select or lock to a reference.
dpllsim_pin_reset() starts every pin disconnected:
pin->ops = dpllsim_pin_select_ops(pin); pin->state = DPLL_PIN_STATE_DISCONNECTED;
dpllsim_pin_prio_set() only stores sim_pin->prio, and nothing else reads it. sim->lock_status is set once to DPLL_LOCK_STATUS_UNLOCKED in core.c and never changes.
The device is always in automatic mode. dpllsim_pin_state_set() accepts only disconnected and selectable there, so no pin is ever connected.
So gnss0 never becomes the active reference, and its prio 0 does not affect which pin is used. That includes the tie test_pin_priority_get_set() can create by setting ext0 to 0. The comment is still the same at the end of the series, and no later patch adds selection by priority.
Quoted 2 lines
tools/testing/selftests/drivers/dpll/dpll_lib.sh +1
-0+# ext0: EXT, 10 MHz, input, prio 1 # out0: EXT, 10 MHz, output
[ ... ]
Signature · 1 line
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261109093523.931201-1-poros%40redhat.com
PATCH v3 9/14 dpll: dpllsim: Add support to set frequency on pins
In reply to Petr OrosAdd support to set the frequency of input and output pins. The DPLL core validates the requested value against the supported frequency list the pin advertises, so the frequency_set callback only has to store it. The same per type tables that validate the configured frequency at live time serve as the supported list. The configfs attribute is rejected with EBUSY while the device is live, so only the netlink setter changes the frequency of a registered pin and the two cannot race.
--- drivers/dpll/dpllsim/dpll.c | 15 +++++++++++++++ drivers/dpll/dpllsim/dpllsim.h | 4 ++-- 2 files changed, 17 insertions(+), 2 deletions(-)
drivers/dpll/dpllsim/dpll.c +15 -0
@@ -79,6 +79,19 @@ static int dpllsim_pin_frequency_get(const struct dpll_pin *pin,return 0 ; } +static int dpllsim_pin_frequency_set (const struct dpll_pin *pin, +void *pin_priv, +const struct dpll_device *dpll, +void *dpll_priv,u64 frequency, +struct netlink_ext_ack *extack) +{ +struct dpllsim_pin *sim_pin = pin_priv; + +guard (mutex)(&sim_pin->dev->lock); + sim_pin->frequency = frequency; +return 0 ; +} + static int dpllsim_pin_direction_get(const struct dpll_pin *pin, void *pin_priv, const struct dpll_device *dpll,
@@ -171,6 +184,7 @@ static int dpllsim_pin_prio_set(const struct dpll_pin *pin, void *pin_priv,static const struct dpll_pin_ops dpllsim_input_pin_ops = { .frequency_get = dpllsim_pin_frequency_get, + .frequency_set = dpllsim_pin_frequency_set, .direction_get = dpllsim_pin_direction_get, .state_on_dpll_get = dpllsim_pin_state_get, .state_on_dpll_set = dpllsim_pin_state_set,
@@ -180,6 +194,7 @@ static const struct dpll_pin_ops dpllsim_input_pin_ops = {static const struct dpll_pin_ops dpllsim_output_pin_ops = { .frequency_get = dpllsim_pin_frequency_get, + .frequency_set = dpllsim_pin_frequency_set, .direction_get = dpllsim_pin_direction_get, .state_on_dpll_get = dpllsim_pin_state_get, };
drivers/dpll/dpllsim/dpllsim.h +2 -2
@@ -26,8 +26,8 @@ struct dpllsim_pin {/* * Configuration, set through configfs only until -* dpllsim_dev_frozen(). The netlink prio setter changes prio at -* runtime under dev->lock. +* dpllsim_dev_frozen(). The netlink setters change prio and +* frequency at runtime under dev->lock. */ enum dpll_pin_type type;enum dpll_pin_direction direction;
Signature · 1 line
2.56.0
[email protected]
AI reviewRe: [PATCH net-next v3 09/14] dpll: dpllsim: Add support to set frequency on pins
Thank you for your contribution! Sashiko AI review found more severe issue(s) in other patches of this series. This mail is only an FYI, in case you want to address the nit picks below when you…
Thank you for your contribution! Sashiko AI review found more severe issue(s) in other patches of this series. This mail is only an FYI, in case you want to address the nit picks below when you respin.
Low priority / nit picks for your consideration:
- [Low] This patch adds dpllsim_pin_frequency_set() (drivers/dpll/dpllsim/dpll.c).
--
dpll: dpllsim: Add support to set frequency on pins
This adds a netlink frequency_set callback to the dpllsim input and output pin ops. The callback stores the value in sim_pin->frequency under dev->lock. The DPLL core has already checked it against the pin's supported frequency list.
Quoted 10 lines
drivers/dpll/dpllsim/dpll.c +2
-0@@ -79,6 +79,19 @@ static int dpllsim_pin_frequency_get(const struct dpll_pin *pin,return 0 ; } +static int dpllsim_pin_frequency_set(const struct dpll_pin *pin, + void *pin_priv,
drivers/dpll/dpllsim/dpll.c static int dpllsim_pin_frequency_get(const struct dpll_pin *pin, · line 93
Quoted 10 lines
drivers/dpll/dpllsim/dpll.c +10
-0+const struct dpll_device *dpll, + void *dpll_priv, u64 frequency, + struct netlink_ext_ack *extack) +{ + struct dpllsim_pin *sim_pin = pin_priv; + +guard (mutex)(&sim_pin->dev->lock); + sim_pin->frequency = frequency; +return 0 ; +}
[Severity: Low]
This isn't a bug, but should Documentation/driver-api/dpllsim.rst be updated along with this?
dpllsim_pin_frequency_set() writes sim_pin->frequency. That is the same field the configfs frequency attribute stores and shows through dpllsim_pin_frequency_store() and dpllsim_pin_frequency_show() in core.c. dpllsim_pin_frequency_valid() checks it again on the next live=1.
Nothing on the undeploy path resets it, so a frequency set over netlink:
- shows up when the configfs frequency attribute is read,
- is still there after live=0,
- is used on the next live=1 instead of the value written to configfs.
The rst describes pin attributes only as configuration written before going live:
Attributes are written while the instance is not live, then ``live`` is set to 1 to register the device and its pins with the DPLL subsystem.
``frequency`` Frequency in Hz, must be supported by the pin type
(default 0, which is rejected)
The struct dpllsim_pin comment in dpllsim.h was updated to say "The netlink setters change prio and frequency at runtime under dev->lock", but the rst still doesn't mention runtime changes or that they carry over to the next deploy. Prio, added by the earlier "dpll: dpllsim: Add support to get/set priority on input pins" commit, has the same gap.
Could a sentence be added to dpllsim.rst covering both prio and frequency?
[ ... ]
Signature · 1 line
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261109093523.931201-1-poros%40redhat.com
PATCH v3 10/14 selftests: dpll: add pin frequency test
In reply to Petr OrosAdd a test that picks a supported frequency other than the current one from the frequency-supported list of an input pin, sets it, reads it back and restores the original value. Both ends of every advertised range are candidates, so that a pin advertising a single range wider than one value still has one. It skips when the dpll tool lacks the frequency keyword of pin set, and the restore is deferred right after the original value is read so that an interrupted run restores it too. A pin that reports no frequency or advertises a single frequency only is a skip on a device provided by the user and a failure on the device the script created, whose pins advertise several.
The driver may refuse the candidate. A driver without a frequency setter refuses every value with EOPNOTSUPP, and a driver with constraints of its own refuses values the advertised ranges allow, zl3073x for instance wants the frequencies of an output pair to divide each other. On a device provided by the user a refused set is a skip that names the reason the tool reported, which the setter keeps in DPLL_LAST_ERR, on the created device the simulator accepts every advertised value and the refusal is a failure. Once the set went through, a refused restore is a failure on either.
---
v3:
- follow the move of the selftests to
tools/testing/selftests/drivers/dpll
v2: https://lore.kernel.org/all/[email protected]/
v1: https://lore.kernel.org/all/[email protected]/
--- .../selftests/drivers/dpll/dpll_features.sh | 64 +++++++++++++++++++ .../selftests/drivers/dpll/dpll_lib.sh | 5 ++ 2 files changed, 69 insertions(+)
tools/testing/selftests/drivers/dpll/dpll_features.sh +64 -0
@@ -18,6 +18,7 @@ source "$SCRIPTDIR/dpll_lib.sh" ALL_TESTS=" test_pin_state_transitions test_pin_priority_get_set + test_pin_frequency_set_get " ##############################################################################
@@ -237,6 +238,69 @@ test_pin_state_transitions() log_test "DPLL pin state transitions" } +test_pin_frequency_set_get() +{ + local before target after reason + + RET=0 + + if [ -z "$PIN_TUNE_ID" ]; then + dpll_log_test_skip_or_fail "DPLL pin frequency set/get" \ + "no spare input pin" + return + fi + if ! dpll_pin_set_supports frequency; then + log_test_skip "DPLL pin frequency set/get" \ + "dpll tool lacks pin set frequency" + return + fi + + before=$(dpll_pin_get_frequency "$PIN_TUNE_ID") + if [ -z "$before" ]; then + dpll_log_test_skip_or_fail "DPLL pin frequency set/get" \ + "frequency not reported" + return + fi + defer dpll_pin_set_frequency "$PIN_TUNE_ID" "$before" + + # Pick a supported frequency different from the current one. Both + # ends of every supported range are candidates, so a pin with a + # single range still has an alternative unless it is one value wide. + target=$(dpll_pin_json "$PIN_TUNE_ID" | \ + jq -r --argjson cur "$before" \ + '[.["frequency-supported"][]? | + .["frequency-min"], .["frequency-max"] | + numbers | select(. != $cur)][0] // empty') + if [ -z "$target" ]; then + dpll_log_test_skip_or_fail "DPLL pin frequency set/get" \ + "no alternative supported frequency" + return + fi + + # A driver without a frequency setter or with constraints of its own + # may refuse the candidate, a skip on a device the user provided, the + # simulator accepts every advertised value. + if ! dpll_pin_set_frequency "$PIN_TUNE_ID" "$target"; then + reason=${DPLL_LAST_ERR%%$'\n'*} + dpll_log_test_skip_or_fail "DPLL pin frequency set/get" \ + "cannot set frequency=$target${reason:+, $reason}" + return + fi + after=$(dpll_pin_get_frequency "$PIN_TUNE_ID") + if [ "$after" != "$target" ]; then + check_err 1 "frequency expected $target, got '$after'" + fi + + dpll_pin_set_frequency "$PIN_TUNE_ID" "$before" + check_err $? "cannot restore frequency" + after=$(dpll_pin_get_frequency "$PIN_TUNE_ID") + if [ "$after" != "$before" ]; then + check_err 1 "frequency not restored, got '$after'" + fi + + log_test "DPLL pin frequency set/get" +} + ############################################################################## cleanup()
tools/testing/selftests/drivers/dpll/dpll_lib.sh +5 -0
@@ -431,6 +431,11 @@ dpll_pin_set_prio() prio "$prio" 2>&1 >/dev/null) } +dpll_pin_set_frequency() +{ + DPLL_LAST_ERR=$(dpll pin set id "$1" frequency "$2" 2>&1 >/dev/null) +} + ############################################################################## # dpllsim configfs interface #
Signature · 1 line
2.56.0
[email protected]
AI reviewRe: [PATCH net-next v3 10/14] selftests: dpll: add pin frequency test
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider.
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider.
Critical: 0 · High: 0 · Medium: 2 · Low: 0
If you believe none of the issues are legitimate, please reply to each issue of Medium severity or higher and explain why the AI is wrong. If any of them are legitimate and you plan to respin, please let patchwork know by including "pw-bot: cr" as a separate line at the end of your reply (one such reply per series is enough).
- [Medium] test_pin_frequency_set_get() in tools/testing/selftests/drivers/dpll/dpll_features.sh only checks that…
- [Medium] test_pin_frequency_set_get() reprograms the frequency of PIN_TUNE_ID.
--
selftests: dpll: add pin frequency test
This adds test_pin_frequency_set_get() to dpll_features.sh. The test takes a supported frequency other than the current one from an input pin's frequency-supported list, sets it, reads it back and restores the original value. It also adds a dpll_pin_set_frequency() helper to dpll_lib.sh, which keeps the tool's error output in DPLL_LAST_ERR.
Quoted 4 lines
tools/testing/selftests/drivers/dpll/dpll_features.sh +0
-0
[ ... ]
Quoted 6 lines
tools/testing/selftests/drivers/dpll/dpll_features.sh +2
-0@@ -237,6 +238,69 @@ test_pin_state_transitions() log_test "DPLL pin state transitions" } +test_pin_frequency_set_get() +{
[ ... ]
tools/testing/selftests/drivers/dpll/dpll_features.sh test_pin_state_transitions() · line 264
Quoted 7 lines
tools/testing/selftests/drivers/dpll/dpll_features.sh +7
-0+ before=$(dpll_pin_get_frequency "$PIN_TUNE_ID") + if [ -z "$before" ]; then + dpll_log_test_skip_or_fail "DPLL pin frequency set/get" \ + "frequency not reported" + return + fi + defer dpll_pin_set_frequency "$PIN_TUNE_ID" "$before"
[Severity: Medium]
Can the original frequency always be written back here? The only check on before is that it is not empty. The DPLL core refuses any value outside the pin's frequency-supported ranges, and it does this before it compares the value with the current frequency:
drivers/dpll/dpll_netlink.c:dpll_pin_freq_set() {
...
if (!dpll_pin_is_freq_supported(pin, freq)) {
NL_SET_ERR_MSG_ATTR(extack, a, "frequency is not supported by the device");
return -EINVAL;
}
...
}zl3073x hits this case on purpose. It reads a sub-Hz reference back as 0 Hz and leaves 0 out of freq_supported:
drivers/dpll/zl3073x/prop.c:zl3073x_pin_props_get() {
...
/* Start with current frequency at index 0. A sub-Hz frequency is
* read back as 0 Hz and cannot be set, so it is not offered.
*/
j = 0;
if (curr_freq) {
...
}Here is what happens on such a pin on a device the user provided:
- before=0 passes the -z check.
- The jq filter picks the first advertised frequency as target.
- Setting target succeeds and reprograms the hardware reference.
- The explicit restore and the deferred restore are both refused with EINVAL.
Wouldn't this leave the user's device on a frequency the test picked? The commit message says the test "restores the original value".
The phase adjust test later in the series bails out before it schedules the restore when the current value could not be written back. Would a similar check fit here, one that confirms before lies inside an advertised frequency-supported range before the pin is touched?
Quoted 9 lines
tools/testing/selftests/drivers/dpll/dpll_features.sh +9
-0+ + # Pick a supported frequency different from the current one. Both + # ends of every supported range are candidates, so a pin with a + # single range still has an alternative unless it is one value wide. + target=$(dpll_pin_json "$PIN_TUNE_ID" | \ + jq -r --argjson cur "$before" \ + '[.["frequency-supported"][]? | + .["frequency-min"], .["frequency-max"] | + numbers | select(. != $cur)][0] // empty')
[ ... ]
tools/testing/selftests/drivers/dpll/dpll_features.sh test_pin_state_transitions() · line 283
Quoted 4 lines
tools/testing/selftests/drivers/dpll/dpll_features.sh +4
-0+ # A driver without a frequency setter or with constraints of its own + # may refuse the candidate, a skip on a device the user provided, the + # simulator accepts every advertised value. + if ! dpll_pin_set_frequency "$PIN_TUNE_ID" "$target"; then
[Severity: Medium]
Is it safe to reprogram this pin's frequency based only on its state on DPLL_DEV_ID? pick_tune_pin() counts a pin as spare when it is disconnected or selectable on that one device:
tools/testing/selftests/drivers/dpll/dpll_features.sh:pick_tune_pin() {
...
state=$(dpll_pin_get_state "$DPLL_DEV_ID" "$pin_id")
case "$state" in
disconnected|selectable)
...
}State and priority are per parent device, but frequency belongs to the pin itself. It is shared by every DPLL device the pin is registered with.
On zl3073x, every channel gets the same dpll_pin for an input index through dpll_pin_get(clock_id, index). The setter then writes the chip-wide reference state:
drivers/dpll/zl3073x/dpll.c:zl3073x_dpll_input_pin_frequency_set() {
...
ref_id = zl3073x_input_pin_ref_get(pin->id);
ref = *zl3073x_ref_state_get(zldev, ref_id);
/* Update frequency */
zl3073x_ref_freq_set(&ref, frequency);
/* Commit reference state */
return zl3073x_ref_state_set(zldev, ref_id, &ref);
}Say the chosen input is selectable on the EEC channel but is the locked reference of the PPS channel. Could the PPS channel then lose lock, go into holdover or switch references until the restore runs? ice also registers its inputs with both its EEC and PPS DPLLs. The test never checks that the other channel locks again.
Should the pin be checked against all of its parent devices before this test changes its frequency?
Quoted 5 lines
tools/testing/selftests/drivers/dpll/dpll_features.sh +5
-0+ reason=${DPLL_LAST_ERR%%$'\n'*} + dpll_log_test_skip_or_fail "DPLL pin frequency set/get" \ + "cannot set frequency=$target${reason:+, $reason}" + return + fi
[ ... ]
Signature · 1 line
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261109093523.931201-1-poros%40redhat.com
PATCH v3 11/14 dpll: dpllsim: Add support to adjust phase
In reply to Petr OrosAdd support to get and set the phase adjustment of input and output pins. The pins advertise a granularity of 100 ps and a symmetric range whose ends are the largest multiples of the granularity that fit a 32-bit signed value, so that the advertised ends themselves are settable, the core validates the requested value against both and the driver stores it. The adjustment is reset to zero every time the device goes live, so that repeated deploy cycles start from a known state. It becomes the baseline of the simulated phase offset measurements once those exist.
--- drivers/dpll/dpllsim/dpll.c | 40 ++++++++++++++++++++++++++++++++++ drivers/dpll/dpllsim/dpllsim.h | 1 + 2 files changed, 41 insertions(+)
drivers/dpll/dpllsim/dpll.c +40 -0
@@ -12,6 +12,11 @@#include "dpllsim.h" +#define DPLLSIM_PHASE_ADJUST_GRAN 100/* ps */ +/* Largest multiple of the granularity, so the range ends are settable */ +#define DPLLSIM_PHASE_ADJUST_MAX \ + (S32_MAX - S32_MAX % DPLLSIM_PHASE_ADJUST_GRAN) +static struct dpll_pin_frequency dpllsim_freq_1pps[] = {DPLL_PIN_FREQUENCY_RANGE (1 ,1 ), };
@@ -182,6 +187,33 @@ static int dpllsim_pin_prio_set(const struct dpll_pin *pin, void *pin_priv,return 0 ; } +static int dpllsim_pin_phase_adjust_get (const struct dpll_pin *pin, +void *pin_priv, +const struct dpll_device *dpll, +void *dpll_priv,s32 *phase_adjust, +struct netlink_ext_ack *extack) +{ +struct dpllsim_pin *sim_pin = pin_priv; + +guard (mutex)(&sim_pin->dev->lock); + *phase_adjust = sim_pin->phase_adjust; +return 0 ; +} + +static int dpllsim_pin_phase_adjust_set (const struct dpll_pin *pin, +void *pin_priv, +const struct dpll_device *dpll, +void *dpll_priv, +const s32 phase_adjust, +struct netlink_ext_ack *extack) +{ +struct dpllsim_pin *sim_pin = pin_priv; + +guard (mutex)(&sim_pin->dev->lock); + sim_pin->phase_adjust = phase_adjust; +return 0 ; +} +static const struct dpll_pin_ops dpllsim_input_pin_ops = { .frequency_get = dpllsim_pin_frequency_get, .frequency_set = dpllsim_pin_frequency_set,
@@ -190,6 +222,8 @@ static const struct dpll_pin_ops dpllsim_input_pin_ops = { .state_on_dpll_set = dpllsim_pin_state_set, .prio_get = dpllsim_pin_prio_get, .prio_set = dpllsim_pin_prio_set, + .phase_adjust_get = dpllsim_pin_phase_adjust_get, + .phase_adjust_set = dpllsim_pin_phase_adjust_set, };static const struct dpll_pin_ops dpllsim_output_pin_ops = {
@@ -197,6 +231,8 @@ static const struct dpll_pin_ops dpllsim_output_pin_ops = { .frequency_set = dpllsim_pin_frequency_set, .direction_get = dpllsim_pin_direction_get, .state_on_dpll_get = dpllsim_pin_state_get, + .phase_adjust_get = dpllsim_pin_phase_adjust_get, + .phase_adjust_set = dpllsim_pin_phase_adjust_set, }; static const struct dpll_pin_ops *
@@ -212,6 +248,9 @@ static void dpllsim_pin_props(struct dpllsim_pin *pin, { prop->type = pin->type; prop->board_label =config_item_name (&pin->item); + prop->phase_range.min = -DPLLSIM_PHASE_ADJUST_MAX ; + prop->phase_range.max =DPLLSIM_PHASE_ADJUST_MAX ; + prop->phase_gran =DPLLSIM_PHASE_ADJUST_GRAN ;switch (pin->type) {case DPLL_PIN_TYPE_GNSS :
@@ -273,6 +312,7 @@ static void dpllsim_pin_reset(struct dpllsim_pin *pin) { pin->ops =dpllsim_pin_select_ops (pin); pin->state =DPLL_PIN_STATE_DISCONNECTED ; + pin->phase_adjust =0 ; }/* Build sim->pins in registration order, the order the pins were created */
drivers/dpll/dpllsim/dpllsim.h +1 -0
@@ -43,6 +43,7 @@ struct dpllsim_pin {/* Simulated state, guarded by dev->lock */ enum dpll_pin_state state; +s32 phase_adjust; };struct dpllsim_dev {
Signature · 1 line
2.56.0
PATCH v3 12/14 selftests: dpll: add pin phase adjust test
In reply to Petr OrosAdd a test that reads the phase adjustment range and granularity a pin advertises, picks the largest multiple of the granularity inside the range, or the smallest one when that is already the current value, sets it, reads it back and restores the original value. It skips when the dpll tool lacks the phase-adjust keyword of pin set and when the range is too narrow to pick a different value. A pin that reports no phase adjustment or no range is a skip on a device provided by the user and a failure on the device the script created, whose pins report both.
The core refuses a value that is not a multiple of the granularity a driver reports, the current value included, so a pin that sits at such a value could not be put back. The test bails out on that before it schedules the restore, which is deferred right after so that an interrupted run restores the value too.
The granularity is only reported by drivers that have the core enforce one. Without it the target is computed with a granularity of 1, and a driver may refuse it for a granularity of its own or for any other constraint the interface does not advertise. A refused set is therefore a skip on a device provided by the user, with the reason the tool reported, and a failure on the device the script created, whose simulator accepts every value its range and granularity allow. The test ends there, a value that was not set has nothing to read back.
---
v3:
- follow the move of the selftests to
tools/testing/selftests/drivers/dpll
v2: https://lore.kernel.org/all/[email protected]/
v1: https://lore.kernel.org/all/[email protected]/
--- .../selftests/drivers/dpll/dpll_features.sh | 85 +++++++++++++++++++ .../selftests/drivers/dpll/dpll_lib.sh | 10 +++ 2 files changed, 95 insertions(+)
tools/testing/selftests/drivers/dpll/dpll_features.sh +85 -0
@@ -19,6 +19,7 @@ ALL_TESTS=" test_pin_state_transitions test_pin_priority_get_set test_pin_frequency_set_get + test_pin_phase_adjust_set_get " ##############################################################################
@@ -238,6 +239,90 @@ test_pin_state_transitions() log_test "DPLL pin state transitions" } +test_pin_phase_adjust_set_get() +{ + local before target after json min max gran reason + + RET=0 + + if [ -z "$PIN_TUNE_ID" ]; then + dpll_log_test_skip_or_fail "DPLL pin phase-adjust set/get" \ + "no spare input pin" + return + fi + if ! dpll_pin_set_supports phase-adjust; then + log_test_skip "DPLL pin phase-adjust set/get" \ + "dpll tool lacks pin set phase-adjust" + return + fi + + before=$(dpll_pin_get_phase_adjust "$PIN_TUNE_ID") + if [ -z "$before" ]; then + dpll_log_test_skip_or_fail "DPLL pin phase-adjust set/get" \ + "phase adjust not reported" + return + fi + + # Pick a target inside the advertised range different from the + # current value, a multiple of the granularity, which is taken as 1 + # when the driver reports none. + json=$(dpll_pin_json "$PIN_TUNE_ID") + min=$(jq -r '.["phase-adjust-min"] // empty' <<< "$json") + max=$(jq -r '.["phase-adjust-max"] // empty' <<< "$json") + gran=$(jq -r '.["phase-adjust-gran"] // empty' <<< "$json") + if [ -z "$min" ] || [ -z "$max" ]; then + dpll_log_test_skip_or_fail "DPLL pin phase-adjust set/get" \ + "no adjust range" + return + fi + # A current value that is not a granularity multiple could not be + # written back, so bail out before the restore is scheduled. + if [ -n "$gran" ] && [ $((before % gran)) -ne 0 ]; then + dpll_log_test_skip_or_fail "DPLL pin phase-adjust set/get" \ + "current phase adjust not a granularity multiple" + return + fi + [ -n "$gran" ] || gran=1 + defer dpll_pin_set_phase_adjust "$PIN_TUNE_ID" "$before" + + # Shell division truncates towards zero, so round the bounds towards + # the inside of the range explicitly, which matters for negative ones. + target=$((max / gran * gran)) + [ "$target" -gt "$max" ] && target=$((target - gran)) + if [ "$target" = "$before" ]; then + target=$((min / gran * gran)) + [ "$target" -lt "$min" ] && target=$((target + gran)) + fi + if [ "$target" = "$before" ] || [ "$target" -gt "$max" ] || + [ "$target" -lt "$min" ]; then + log_test_skip "DPLL pin phase-adjust set/get" "range too narrow" + return + fi + + # A driver may enforce a granularity it does not report or another + # constraint, a skip on a device the user provided, the simulator + # accepts every value its range and granularity allow. + if ! dpll_pin_set_phase_adjust "$PIN_TUNE_ID" "$target"; then + reason=${DPLL_LAST_ERR%%$'\n'*} + dpll_log_test_skip_or_fail "DPLL pin phase-adjust set/get" \ + "cannot set phase-adjust=$target${reason:+, $reason}" + return + fi + after=$(dpll_pin_get_phase_adjust "$PIN_TUNE_ID") + if [ "$after" != "$target" ]; then + check_err 1 "phase-adjust expected $target, got '$after'" + fi + + dpll_pin_set_phase_adjust "$PIN_TUNE_ID" "$before" + check_err $? "cannot restore phase-adjust" + after=$(dpll_pin_get_phase_adjust "$PIN_TUNE_ID") + if [ "$after" != "$before" ]; then + check_err 1 "phase-adjust not restored, got '$after'" + fi + + log_test "DPLL pin phase-adjust set/get" +} + test_pin_frequency_set_get() { local before target after reason
tools/testing/selftests/drivers/dpll/dpll_lib.sh +10 -0
@@ -400,6 +400,11 @@ dpll_pin_get_prio() __dpll_pin_dev_field "$1" "$2" '.prio' } +dpll_pin_get_phase_adjust() +{ + __dpll_pin_field "$1" '.["phase-adjust"]' +} + ############################################################################## # Pin setters #
@@ -436,6 +441,11 @@ dpll_pin_set_frequency() DPLL_LAST_ERR=$(dpll pin set id "$1" frequency "$2" 2>&1 >/dev/null) } +dpll_pin_set_phase_adjust() +{ + DPLL_LAST_ERR=$(dpll pin set id "$1" phase-adjust "$2" 2>&1 >/dev/null) +} + ############################################################################## # dpllsim configfs interface #
Signature · 1 line
2.56.0
[email protected]
AI reviewRe: [PATCH net-next v3 12/14] selftests: dpll: add pin phase adjust test
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider.
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider.
Critical: 0 · High: 0 · Medium: 1 · Low: 0
If you believe none of the issues are legitimate, please reply to each issue of Medium severity or higher and explain why the AI is wrong. If any of them are legitimate and you plan to respin, please let patchwork know by including "pw-bot: cr" as a separate line at the end of your reply (one such reply per series is enough).
- [Medium] In tools/testing/selftests/drivers/dpll/dpll_features.sh, test_pin_phase_adjust_set_get() refuses to schedule a restore it…
--
selftests: dpll: add pin phase adjust test
This adds test_pin_phase_adjust_set_get() to the dpll selftests. The test picks a granularity multiple inside the pin's advertised phase adjust range, sets it, reads it back and then restores the original value. It bails out before scheduling the restore when the current value is not a granularity multiple.
Quoted 4 lines
tools/testing/selftests/drivers/dpll/dpll_features.sh +0
-0
[ ... ]
Quoted 8 lines
tools/testing/selftests/drivers/dpll/dpll_features.sh +8
-0+ json=$(dpll_pin_json "$PIN_TUNE_ID") + min=$(jq -r '.["phase-adjust-min"] // empty' <<< "$json") + max=$(jq -r '.["phase-adjust-max"] // empty' <<< "$json") + gran=$(jq -r '.["phase-adjust-gran"] // empty' <<< "$json") + if [ -z "$min" ] || [ -z "$max" ]; then + dpll_log_test_skip_or_fail "DPLL pin phase-adjust set/get" \ + "no adjust range" + return
tools/testing/selftests/drivers/dpll/dpll_features.sh test_pin_state_transitions() · line 286
Quoted 10 lines
tools/testing/selftests/drivers/dpll/dpll_features.sh +10
-0+ fi + # A current value that is not a granularity multiple could not be + # written back, so bail out before the restore is scheduled. + if [ -n "$gran" ] && [ $((before % gran)) -ne 0 ]; then + dpll_log_test_skip_or_fail "DPLL pin phase-adjust set/get" \ + "current phase adjust not a granularity multiple" + return + fi + [ -n "$gran" ] || gran=1 + defer dpll_pin_set_phase_adjust "$PIN_TUNE_ID" "$before"
[Severity: Medium]
Can the restore be refused when before lies outside [min, max]?
The granularity guard is the only check before the defer. The core also checks the range, and it does that first, in dpll_pin_phase_adj_set():
drivers/dpll/dpll_netlink.c:dpll_pin_phase_adj_set() {
...
phase_adj = nla_get_s32(phase_adj_attr);
if (phase_adj > pin->prop.phase_range.max ||
phase_adj < pin->prop.phase_range.min) {
NL_SET_ERR_MSG_ATTR(extack, phase_adj_attr,
"phase adjust value of out range");
return -EINVAL;
}
if (pin->prop.phase_gran && phase_adj % (s32)pin->prop.phase_gran) {
...
}So an out-of-range current value cannot be written back either, the same as a value that is not a granularity multiple.
On such a pin, target is built inside [min, max] and the set succeeds. After that, the explicit restore and the deferred one both fail with -EINVAL. The pin on a user-supplied device then stays at the top (or bottom) of the range, and the test reports a FAIL instead of a skip.
ice may hit this. ice_dpll_phase_range_set() derives the range from the CGU abilities. The current value is read separately from firmware via ice_aq_get_input_pin_cfg() in ice_dpll_pin_state_update(), and nothing checks that the two agree. If the abilities report 0, the range is [0, 0], and any nonzero firmware value is out of range.
Should the guard also bail out when before is less than min or greater than max, before the restore is scheduled?
Quoted 3 lines
tools/testing/selftests/drivers/dpll/dpll_features.sh +3
-0+ + # Shell division truncates towards zero, so round the bounds towards + # the inside of the range explicitly, which matters for negative ones.
[ ... ]
Quoted 2 lines
tools/testing/selftests/drivers/dpll/dpll_features.sh +2
-0+ dpll_pin_set_phase_adjust "$PIN_TUNE_ID" "$before" + check_err $? "cannot restore phase-adjust"
[ ... ]
Signature · 1 line
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261109093523.931201-1-poros%40redhat.com
PATCH v3 13/14 selftests: dpll: add concurrency stress tests
In reply to Petr OrosExtend the stress script with tests that register and unregister devices from several processes at once and that keep a number of devices with many pins live at the same time, checking that every one of them is visible on netlink.
The workers run in subshells, so the parent cannot track the device a worker has in flight. Each worker removes it from its own exit trap instead, and the parent reserves a range of clock IDs for every worker, so that the devices of a worker cannot collide with those of another worker or of the parent and whatever a killed worker left behind can be removed by name, at the end of the test and from the cleanup handler. The handler stops the recorded workers together with the jobs of the shell, gives them a few seconds to run their exit traps before forcing the survivors, and goes on without a worker that does not die, since one stuck in the kernel would hang the teardown. Deleting a device treats a part of it that is gone by the time its step runs as removed, so that a removal running concurrently with the one of a worker does not turn into a spurious failure. The workers inherit the open lock file of the library, so the lock is held until the script and every worker have exited.
---
v3:
- follow the move of the selftests to
tools/testing/selftests/drivers/dpll
v2:
- quote the failed and ret expansions (netdev CI shellcheck)
v2: https://lore.kernel.org/all/[email protected]/
v1: https://lore.kernel.org/all/[email protected]/
--- .../selftests/drivers/dpll/dpll_lib.sh | 30 ++- .../selftests/drivers/dpll/dpll_stress.sh | 219 +++++++++++++++++- 2 files changed, 241 insertions(+), 8 deletions(-)
tools/testing/selftests/drivers/dpll/dpll_lib.sh +23 -7
@@ -54,8 +54,10 @@ dpll_lib_check_prereqs() # The scripts that create dpllsim devices share the one dpllsim directory # and cannot run alongside each other, so each takes a lock on # DPLL_LOCK_FILE before it mounts or creates anything and skips when -# another run holds it. The file lives under /run rather than in the -# world-writable /tmp, where a planted symlink could redirect the open. +# another run holds it. The workers a script forks inherit the open file +# and keep the lock until they exit. The file lives under /run rather +# than in the world-writable /tmp, where a planted symlink could +# redirect the open. DPLL_RUN_DIR="/run/dpll-selftest" DPLL_LOCK_FILE="$DPLL_RUN_DIR/lock" DPLL_LOCK_FD=""
@@ -564,8 +566,12 @@ dpllsim_undeploy() # Take the device offline and remove it together with all its pins. # Every step is attempted even when an earlier one failed, the return -# value is non-zero when any did. A device that does not exist counts as -# deleted. +# value is non-zero when any did. A device that does not exist counts +# as deleted, and so does a part of it that is gone by the time its +# step runs, the live file of the undeploy and the pin or device +# directory of an rmdir, so that a removal running concurrently, or an +# earlier one whose later steps failed, does not turn into a spurious +# failure here. dpllsim_delete_device() { local dev_path="$DPLLSIM_CFG/$1"
@@ -573,12 +579,12 @@ dpllsim_delete_device() [ -d "$dev_path" ] || return 0 - dpllsim_undeploy "$1" || ret=1 + dpllsim_undeploy "$1" || [ ! -e "$dev_path/live" ] || ret=1 for pin_path in "$dev_path"/pins/*/; do [ -d "$pin_path" ] || continue - rmdir "$pin_path" || ret=1 + rmdir "$pin_path" || [ ! -d "$pin_path" ] || ret=1 done - rmdir "$dev_path" || ret=1 + rmdir "$dev_path" || [ ! -d "$dev_path" ] || ret=1 return "$ret" }
@@ -591,6 +597,16 @@ dpllsim_alloc_clock_id() DPLLSIM_CLOCK_ID=$((DPLLSIM_NEXT_CLOCK_ID++)) } +# Set DPLLSIM_CLOCK_ID to the first of $1 consecutive clock-ids unique +# within this test run and move the allocator past them, for a worker +# that derives the clock-ids of its devices from it. A device the parent +# creates afterwards cannot collide with one of the worker. +dpllsim_alloc_clock_id_range() +{ + DPLLSIM_CLOCK_ID=$DPLLSIM_NEXT_CLOCK_ID + DPLLSIM_NEXT_CLOCK_ID=$((DPLLSIM_NEXT_CLOCK_ID + $1)) +} + # Create a device with the standard 3-pin layout, deploy it and resolve # the netlink device id. Sets DPLLSIM_CREATED_NAME, DPLLSIM_CLOCK_ID and # DPLLSIM_DEV_ID.
tools/testing/selftests/drivers/dpll/dpll_stress.sh +218 -1
261 lines not on this page · raw message
Signature · 1 line
2.56.0
[email protected]
AI reviewRe: [PATCH net-next v3 13/14] selftests: dpll: add concurrency stress tests
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider.
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider.
Critical: 0 · High: 0 · Medium: 1 · Low: 2
If you believe none of the issues are legitimate, please reply to each issue of Medium severity or higher and explain why the AI is wrong. If any of them are legitimate and you plan to respin, please let patchwork know by including "pw-bot: cr" as a separate line at the end of your reply (one such reply per series is enough).
- [Medium] In tools/testing/selftests/drivers/dpll/dpll_stress.sh, cleanup() can send SIGTERM and then SIGKILL to PIDs that bash has…
- [Low] test_max_devices() in tools/testing/selftests/drivers/dpll/dpll_stress.sh reports itself as…
- [Low] cleanup() in tools/testing/selftests/drivers/dpll/dpll_stress.sh signals only the PID of each worker subshell, never the external…
--
selftests: dpll: add concurrency stress tests
This adds two tests to dpll_stress.sh. One registers and unregisters dpllsim devices from several background workers at once. The other keeps several devices with many pins live and checks that each one is visible on netlink.
The workers clean up from their own exit trap, and cleanup() stops recorded workers and shell jobs before it removes their devices by name.
Quoted 4 lines
tools/testing/selftests/drivers/dpll/dpll_stress.sh +0
-0
[ ... ]
Quoted 1 line
tools/testing/selftests/drivers/dpll/dpll_stress.sh +0
-0@@ -39,18 +50,103 @@ untrack_device()
[ ... ]
Quoted 5 lines
cleanup()
{tools/testing/selftests/drivers/dpll/dpll_stress.sh +2
-1- local dev + local pid dev base + local pids=() shell_jobs=()
[ ... ]
Quoted 10 lines
tools/testing/selftests/drivers/dpll/dpll_stress.sh +10
-0+ mapfile -t shell_jobs < <(jobs -p) + mapfile -t pids < <(printf '%s\n' "${BACKGROUND_PIDS[@]}" \ + "${shell_jobs[@]}" | grep . | sort -u) + for pid in "${pids[@]}"; do + kill "$pid" 2>/dev/null + done + # Give the workers a few seconds to run their exit traps, then + # force the survivors and give those a few seconds to die. The + # kernel cannot recycle the pid of a child until it is reaped, + # which the wait below does, so the forced kill cannot hit a
tools/testing/selftests/drivers/dpll/dpll_stress.sh untrack_device() · line 132
Quoted 10 lines
tools/testing/selftests/drivers/dpll/dpll_stress.sh +10
-0+ # process that reused one. A worker stuck in the kernel does not + # die of the forced kill before whatever it waits for returns, so + # only the workers that are gone are waited for, which reaps them, + # and the teardown goes on without the rest. The removal of what + # such a worker left behind can then fail or race with it, which + # is logged like any other failed removal. + pids_gone "${pids[@]}" + for pid in "${pids[@]}"; do + kill -KILL "$pid" 2>/dev/null + done
[Severity: Medium]
Does bash really leave the pid unreaped until the wait below? Bash reaps exited background children itself. It does this from its SIGCHLD handler and from waitpid(-1) whenever it waits for a foreground command. It keeps their status in its bgpids table for a later wait.
pids_gone() runs seq, $(sed ...) and sleep 0.1 on every pass. A worker that exits after the SIGTERM can therefore be reaped, and its pid freed, before this loop sends SIGKILL to every entry of pids[]. The loop does not check pid_running() first.
test_concurrent_workers() makes the window wider:
for pid in "${BACKGROUND_PIDS[@]}"; do
wait "$pid" || failed=1
done
BACKGROUND_PIDS=()Workers that finish early are reaped right away, but they stay in BACKGROUND_PIDS until the last wait returns. Suppose a harness timeout or Ctrl-C arrives while the parent is still waiting on a slower worker. cleanup() then sends SIGTERM and later SIGKILL, as root, to those stale pids.
If one of those pids was handed out again after a pid-space wrap, could this kill an unrelated process?
pid_running() relies on kill -0 and /proc/<pid>/stat, so a reused pid also looks like a live worker. In that case pids_gone() would spin for the full five seconds and log a false "worker N did not exit".
Quoted 7 lines
tools/testing/selftests/drivers/dpll/dpll_stress.sh +7
-0+ pids_gone "${pids[@]}" + for pid in "${pids[@]}"; do + if pid_running "$pid"; then + log_info "worker $pid did not exit, giving up" + else + wait "$pid" 2>/dev/null + fi
tools/testing/selftests/drivers/dpll/dpll_stress.sh untrack_device() · line 149
Quoted 10 lines
tools/testing/selftests/drivers/dpll/dpll_stress.sh +5
-0+ done + defer_scopes_cleanup for dev in "${CREATED_DEVICES[@]}"; do dpllsim_delete_device "$dev" || log_info "cannot delete device $dev" done + for base in "${WORKER_BASES[@]}"; do + delete_worker_devices "$base" + done
[Severity: Low]
Does the forced kill above reach the external helpers that a worker runs? In dpll_lib.sh, dpllsim_create_device() and dpllsim_add_pin() create the configfs entries with an external mkdir. dpllsim_delete_device() removes them with an external rmdir.
The commit message describes a worker "stuck in the kernel". Take the case where a worker's mkdir blocks in configfs. Bash holds back the worker's TERM trap until the foreground child returns. kill -KILL then kills only the subshell, and the mkdir child keeps running as an orphan.
The second pids_gone() and pid_running() check only the subshell pid. The worker therefore counts as gone and is reaped, and "worker $pid did not exit, giving up" is never logged.
cleanup() then runs delete_worker_devices() and dpll_lib_cleanup() while the orphan is still busy. If its mkdir completes afterwards, can a dpllsim device or pin directory appear after the last removal pass and be leaked?
The in-flight mkdir would also keep the configfs mount busy, so the unmount in dpll_lib_cleanup() may fail.
This seems to contradict this part of the commit message:
"whatever a killed worker left behind can be removed by name, at the end of the test and from the cleanup handler"
Would it help to start each worker in its own process group and signal the whole group, or to track the helper processes?
[ ... ]
Quoted 6 lines
tools/testing/selftests/drivers/dpll/dpll_stress.sh +2
-0@@ -194,6 +367,50 @@ test_pin_state_flooding() log_test "DPLL pin state flooding" } +test_max_devices() +{
[ ... ]
Quoted 8 lines
tools/testing/selftests/drivers/dpll/dpll_stress.sh +8
-0+ # Every created device must be visible on netlink. + visible=0 + for clock_id in "${clock_ids[@]}"; do + if dpll_device_wait_by_clock_id "$clock_id" dpllsim \ + > /dev/null; then + visible=$((visible + 1)) + fi + done
[ ... ]
tools/testing/selftests/drivers/dpll/dpll_stress.sh test_pin_state_flooding() · line 412
Quoted 2 lines
tools/testing/selftests/drivers/dpll/dpll_stress.sh +2
-0+ log_test "DPLL maximum devices ($STRESS_DEVICES devices)" +}
[Severity: Low]
This isn't a bug, but is "maximum devices" the right name for this test? dpllsim has no limit on the number of devices. Its only per-device limit is DPLLSIM_MAX_PINS:
drivers/dpll/dpllsim/core.c:dpllsim_pins_make_item() {
...
if (list_count_nodes(&sim->pin_list) >= DPLLSIM_MAX_PINS)
return ERR_PTR(-ENOSPC);
...
}By default the test creates 5 devices with 10 pins each. It never reaches that limit, and it never checks that a pin beyond the limit is rejected. The comment above STRESS_PINS_PER_DEVICE mentions the 128 pin limit, but the default is well below it.
The visibility check also only looks up the devices with dpll_device_wait_by_clock_id(). It does not check their pins, so the test would still pass if the pins of these many-pin devices did not appear on netlink.
Could the test be renamed, or extended to cover the pin limit and to check that the pins are visible?
Signature · 1 line
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261109093523.931201-1-poros%40redhat.com
PATCH v3 14/14 selftests: dpll: add reference leak tests
In reply to Petr OrosWith CONFIG_DPLL_REFCNT_TRACKER the DPLL core keeps the stack traces of unreleased device and pin references in debugfs. The entries do not name their owner, so add library helpers that snapshot the entries present before a test creates its devices and fail when an entry that is not in the snapshot still holds references afterwards. An entry in the snapshot belongs to an object of another driver and is ignored even when its content changed, since other drivers take and drop references while the tests run. The teardown of a device releases its references before the write to live returns, so the entries are expected to be gone at the first look; the check still polls for a while before it reports a leak, so that it does not fail on a single read. Attributing
by name means that a leak of an earlier run goes unnoticed and that an object reusing the address of a freed one from the snapshot can hide a leak, which is accepted, the entries carry nothing better to go by. A second run alongside would have its devices attributed to this one, which is one more reason for the lock every script takes.
Whether the tracker works is probed with a throwaway device, which has to show entries while it is live and to drain them once it is deleted, so that a kernel without the tracker is told apart from a broken simulator and from a leak. The probe runs as the first test of the stress script, which gains a final leak check, and of the new dpll_reftrack.sh, which registers a device, removes it and verifies that no reference survived. The snapshot file lives next to the lock file under /run, for the same reason the lock file does not live in /tmp. The library mounts debugfs when nothing else did, like it does for configfs, and the selftest config enables the tracker together with CONFIG_DEBUG_KERNEL, which it depends on, and debugfs.
---
v3:
- say what the leak check waits for instead of a concurrent dump holding references, which nothing does (Ivan Vecera)
- follow the move of the selftests to tools/testing/selftests/drivers/dpll
v2:
- keep the config file sorted and quote the ret expansions (netdev CI shellcheck)
v2: https://lore.kernel.org/all/[email protected]/
v1: https://lore.kernel.org/all/[email protected]/
--- tools/testing/selftests/drivers/dpll/Makefile | 1 + tools/testing/selftests/drivers/dpll/config | 3 + .../selftests/drivers/dpll/dpll_lib.sh | 263 ++++++++++++++++-- .../selftests/drivers/dpll/dpll_reftrack.sh | 184 ++++++++++++ .../selftests/drivers/dpll/dpll_stress.sh | 91 +++++- 5 files changed, 520 insertions(+), 22 deletions(-) create mode 100755 tools/testing/selftests/drivers/dpll/dpll_reftrack.sh
tools/testing/selftests/drivers/dpll/Makefile +1 -0
@@ -10,6 +10,7 @@ TEST_FILES := dpll_lib.sh TEST_PROGS := \ dpll_basic.sh \ dpll_features.sh \ + dpll_reftrack.sh \ dpll_stress.sh \ # end of TEST_PROGS
tools/testing/selftests/drivers/dpll/config +3 -0
@@ -1,3 +1,6 @@ CONFIG_CONFIGFS_FS=y +CONFIG_DEBUG_FS=y +CONFIG_DEBUG_KERNEL=y CONFIG_DPLL=y +CONFIG_DPLL_REFCNT_TRACKER=y CONFIG_DPLLSIM=m
tools/testing/selftests/drivers/dpll/dpll_lib.sh +246 -17
300 lines not on this page · raw message
tools/testing/selftests/drivers/dpll/dpll_reftrack.sh new file +184 -0
184 lines not on this page · raw message
tools/testing/selftests/drivers/dpll/dpll_stress.sh +86 -5
125 lines not on this page · raw message
Signature · 1 line
2.56.0
Re: [PATCH net-next v3 14/14] selftests: dpll: add reference leak tests
Quoted 30 lines · Petr Oros
With
CONFIG_DPLL_REFCNT_TRACKERthe DPLL core keeps the stack traces of unreleased device and pin references in debugfs. The entries do not name their owner, so add library helpers that snapshot the entries present before a test creates its devices and fail when an entry that is not in the snapshot still holds references afterwards. An entry in the snapshot belongs to an object of another driver and is ignored even when its content changed, since other drivers take and drop references while the tests run. The teardown of a device releases its references before the write to live returns, so the entries are expected to be gone at the first look; the check still polls for a while before it reports a leak, so that it does not fail on a single read. Attributing
by name means that a leak of an earlier run goes unnoticed and that an object reusing the address of a freed one from the snapshot can hide a leak, which is accepted, the entries carry nothing better to go by. A second run alongside would have its devices attributed to this one, which is one more reason for the lock every script takes.Whether the tracker works is probed with a throwaway device, which has to show entries while it is live and to drain them once it is deleted, so that a kernel without the tracker is told apart from a broken simulator and from a leak. The probe runs as the first test of the stress script, which gains a final leak check, and of the new
dpll_reftrack.sh, which registers a device, removes it and verifies that no reference survived. The snapshot file lives next to the lock file under /run, for the same reason the lock file does not live in /tmp. The library mounts debugfs when nothing else did, like it does for configfs, and the selftest config enables the tracker together withCONFIG_DEBUG_KERNEL, which it depends on, and debugfs.
Quoted 8 lines
Acked-by Jiri Pirko<[email protected]>Signed-off-by Petr Oros<[email protected]>---
v3:
- say what the leak check waits for instead of a concurrent dump holding references, which nothing does (Ivan Vecera)
- follow the move of the selftests totools/testing/selftests/drivers/dpll
[email protected]
AI reviewRe: [PATCH net-next v3 14/14] selftests: dpll: add reference leak tests
In reply to Petr OrosThank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider.
Thank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider.
Critical: 0 · High: 0 · Medium: 1 · Low: 4
If you believe none of the issues are legitimate, please reply to each issue of Medium severity or higher and explain why the AI is wrong. If any of them are legitimate and you plan to respin, please let patchwork know by including "pw-bot: cr" as a separate line at the end of your reply (one such reply per series is enough).
- [Medium] The snapshot taken right after the probe can record the probe device's own dead ref_tracker entries.
- [Low] The leak check treats any non-empty dpll_device@*/dpll_pin@* entry missing from the snapshot as a leak of this run.
- [Low] The two new final leak checks handle a leftover device differently.
- [Low] dpll_ref_tracker_probe() returns 1 whenever the deployed probe device produces no non-empty new entry.
- [Low] dpll_debugfs_available() mounts debugfs when /sys/kernel/debug is not a mountpoint and sets DPLL_MOUNTED_DEBUGFS=1.
--
selftests: dpll: add reference leak tests
This adds helpers to dpll_lib.sh that snapshot the ref_tracker debugfs entries before a test creates dpllsim devices. A test then fails when an entry that is not in the snapshot still holds references afterwards. A throwaway probe device checks that the tracker works; the probe runs first in dpll_stress.sh, which also gains a final leak check, and in the new dpll_reftrack.sh.
Quoted 4 lines
tools/testing/selftests/drivers/dpll/dpll_lib.sh +0
-0
[ ... ]
Quoted 1 line
tools/testing/selftests/drivers/dpll/dpll_lib.sh +0
-0@@ -641,3 +658,215 @@ dpllsim_setup_standard_device()
[ ... ]
tools/testing/selftests/drivers/dpll/dpll_lib.sh dpllsim_setup_standard_device() · line 719
Quoted 8 lines
tools/testing/selftests/drivers/dpll/dpll_lib.sh +8
-0+dpll_debugfs_available() +{ + if ! mountpoint -q "$DEBUGFS_DIR"; then + mount -t debugfs none "$DEBUGFS_DIR" &> /dev/null && + DPLL_MOUNTED_DEBUGFS=1 + fi + mountpoint -q "$DEBUGFS_DIR" +}
[Severity: Low]
The only sign that this script owns the mount is that debugfs was not mounted when it checked. If no dpllsim device is left behind, dpll_lib_cleanup() unmounts it at exit:
[ "$DPLL_MOUNTED_DEBUGFS" -eq 1 ] && mounted+=("$DEBUGFS_DIR")
...
for dir in "${mounted[@]}"; do
__dpll_lib_umount "$dir"
doneDuring a long dpll_stress.sh run, another process (another selftest, a tracing tool, a user) can find debugfs already mounted and start using it without mounting it itself.
Would the umount at exit pull debugfs out from under that process? A plain umount succeeds unless a file or cwd is held open at that moment, and the other process would not remount it.
The configfs handling already works this way. This patch applies the same pattern to debugfs, which is shared far more widely.
[ ... ]
Quoted 6 lines
tools/testing/selftests/drivers/dpll/dpll_lib.sh +6
-0+__dpll_ref_tracker_new_entries() +{ + local file=$1 + local entry name content + + for entry in "$DPLL_REF_TRACKER_DIR"/dpll_device@* \
tools/testing/selftests/drivers/dpll/dpll_lib.sh dpllsim_setup_standard_device() · line 761
Quoted 10 lines
tools/testing/selftests/drivers/dpll/dpll_lib.sh +10
-0+ "$DPLL_REF_TRACKER_DIR"/dpll_pin@*; do + [ -f "$entry" ] || continue + name=$(basename "$entry") + grep -q "^$name " "$file" && continue + content=$(cat "$entry" 2>/dev/null) + [ -n "$content" ] || continue + echo "Unreleased references in $name:" + echo "$content" + done +}
[Severity: Low]
The only attribution here is snapshot membership plus non-empty content. What happens when another DPLL driver (ice, zl3073x, ptp_ocp, ...) registers a new dpll_device or dpll_pin after the snapshot? This could come from a deferred or async probe, a driver bind, a module reload, or a driver re-allocating its pins during the long dpll_stress.sh run.
A live object of that driver always holds tracked references, so its entry is non-empty and not in the snapshot.
Would test_basic_reftrack(), test_final_leak_check() and test_ref_leaks() then wait BUSYWAIT_TIMEOUT and fail, blaming dpllsim for references it does not own?
dpll_lib_lock() is an flock on /run/dpll-selftest/lock, so it only excludes other dpll selftest scripts. The library comment only covers objects of other drivers that already exist when the snapshot is taken.
[ ... ]
Quoted 2 lines
tools/testing/selftests/drivers/dpll/dpll_lib.sh +2
-0+dpll_ref_tracker_probe() +{
[ ... ]
tools/testing/selftests/drivers/dpll/dpll_lib.sh dpllsim_setup_standard_device() · line 858
Quoted 5 lines
tools/testing/selftests/drivers/dpll/dpll_lib.sh +5
-0+ if dpllsim_add_pin "$name" ext0 ext 10000000 input && + dpllsim_deploy "$name"; then + ret=1 + [ -n "$(__dpll_ref_tracker_new_entries "$file")" ] && ret=0 + fi
[Severity: Low]
Can this tell a kernel without CONFIG_DPLL_REFCNT_TRACKER apart from one where the tracker is enabled but does not record anything?
dpll_device_alloc() and dpll_pin_alloc() call ref_tracker_dir_init() unconditionally:
ref_tracker_dir_init(&dpll->refcnt_tracker, 128, "dpll_device");
So the dpll_device@ and dpll_pin@ files exist whenever CONFIG_REF_TRACKER and CONFIG_DEBUG_FS are set. Only dpll_device_tracker_alloc/free and dpll_pin_tracker_alloc/free are under #ifdef CONFIG_DPLL_REFCNT_TRACKER, and an empty dir->list prints nothing.
Suppose a regression makes the core stop calling ref_tracker_alloc(), or pass a NULL tracker so that only no_tracker is bumped. The probe would then return 1, and both scripts log:
log_test_skip "DPLL ref_tracker probe" \ "CONFIG_DPLL_REFCNT_TRACKER not enabled"
This happens even though the selftest config sets CONFIG_DPLL_REFCNT_TRACKER=y. Would such a regression show up as a SKIP with a misleading reason instead of a FAIL?
Quoted 4 lines
tools/testing/selftests/drivers/dpll/dpll_lib.sh +4
-0+ + if dpllsim_delete_device "$name"; then + DPLLSIM_PROBE_DEVICE="" + else
tools/testing/selftests/drivers/dpll/dpll_lib.sh dpllsim_setup_standard_device() · line 872
Quoted 10 lines
tools/testing/selftests/drivers/dpll/dpll_lib.sh +10
-0+ log_info "cannot delete device $name" + return 4 + fi + if [ "$ret" -eq 0 ] && + ! busywait "$BUSYWAIT_TIMEOUT" __dpll_ref_tracker_settled "$file" \ + > /dev/null; then + ret=3 + fi + return "$ret" +}
[Severity: Medium]
Can the snapshot taken right after the probe record the probe device's own dead ref_tracker entries?
The commit message says:
The teardown of a device releases its references before the write to live returns, so the entries are expected to be gone at the first look
In lib/ref_tracker.c only the references go away synchronously. ref_tracker_dir_exit() calls ref_tracker_debugfs_mark(), which only marks the dir dead and schedules the reaper:
__xa_set_mark(&debugfs_dentries, (unsigned long)dir, REF_TRACKER_DIR_DEAD); ... schedule_work(&debugfs_reap_worker);
The dentry is removed later, in debugfs_reap_work(). Until then the file still exists, and reading it returns -ENODATA from ref_tracker_debugfs_show().
That allows this sequence:
dpllsim_delete_device(probe)
dpll_device_put() -> ref_tracker_dir_exit(), kfree(dpll)
busywait __dpll_ref_tracker_settled
cat of the dead entry fails, content is empty, entry is skippedtest_ref_tracker_probe()
dpll_ref_tracker_snapshot "$REF_SNAPSHOT"
[ -f "$entry" ] is still true, so the probe's names are recorded
The next test (test_basic_reftrack() or test_rapid_create_delete()) then allocates a dpll_device right away. It can land on the probe's freed slab slot, and the %p hash then gives the same entry name.
If the reaper has run by then, the new object's entry is in the snapshot, and __dpll_ref_tracker_new_entries() skips it:
grep -q "^$name " "$file" && continue
If the reaper still has not run, ref_tracker_dir_debugfs() returns early:
/* No-op if already created */ dentry = xa_load(&debugfs_dentries, (unsigned long)dir); if (dentry && !xa_is_err(dentry)) return;
The reaper later removes the old dentry, so the new object never gets a debugfs file at all.
In either case, would a leak in the first device created after the probe go unreported?
Would it help to skip unreadable entries in dpll_ref_tracker_snapshot(), or to wait after deleting the probe until its entries are gone from the ref_tracker dir before taking the snapshot again?
[ ... ]
Quoted 4 lines
tools/testing/selftests/drivers/dpll/dpll_stress.sh +0
-0
[ ... ]
Quoted 7 lines
tools/testing/selftests/drivers/dpll/dpll_stress.sh +3
-0@@ -463,16 +524,36 @@ test_deploy_cycle() log_test "DPLL deploy/undeploy cycling" } +test_ref_leaks() +{ + RET=0
tools/testing/selftests/drivers/dpll/dpll_stress.sh test_deploy_cycle() · line 539
Quoted 10 lines
tools/testing/selftests/drivers/dpll/dpll_stress.sh +10
-0+ + # The probe test found the tracker inactive or could not run. + if [ "$REF_TRACKER" -eq 0 ]; then + log_test_skip "DPLL stress ref_tracker check" \ + "DPLL reference tracker not available" + return + fi + + dpll_ref_tracker_check_leaks "$REF_SNAPSHOT" + check_err $? "leaked DPLL references after stress"
[Severity: Low]
Should this skip when CREATED_DEVICES is not empty, the way test_final_leak_check() in dpll_reftrack.sh does?
# A device an earlier test could not remove still holds its # references, the failure to report is that one.
if [ -n "$CREATED_DEVICE" ]; then
log_test_skip "DPLL final ref_tracker check" \
"device from an earlier test still present"
dpllsim_delete_device() can fail in test_pin_state_flooding(), test_max_devices() or test_deploy_cycle() because the undeploy failed. The device then stays live and stays in CREATED_DEVICES.
It still holds its dpll_device_get(..., &sim->tracker) and dpll_pin_get(..., &pin->tracker) references, so its entries are non-empty and not in the snapshot.
cleanup() only removes those devices at exit. Wouldn't test_ref_leaks() spin for BUSYWAIT_TIMEOUT and then report "leaked DPLL references after stress" for references the device legitimately holds?
Quoted 3 lines
tools/testing/selftests/drivers/dpll/dpll_stress.sh +3
-0+ + log_test "DPLL stress ref_tracker check" +}
[ ... ]
Signature · 1 line
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261109093523.931201-1-poros%40redhat.com