From: Priyank Rathod <rathodpriyank@google.com>
To: Bjorn Helgaas <bhelgaas@google.com>
Cc: "Ilpo Järvinen" <ilpo.jarvinen@linux.intel.com>,
"Lukas Wunner" <lukas@wunner.de>,
"Manivannan Sadhasivam" <mani@kernel.org>,
"Jonathan Corbet" <corbet@lwn.net>,
"Shuah Khan" <skhan@linuxfoundation.org>,
"Shuah Khan" <shuah@kernel.org>,
"Randy Dunlap" <rdunlap@infradead.org>,
linux-pci@vger.kernel.org, linux-doc@vger.kernel.org,
linux-kselftest@vger.kernel.org, linux-kernel@vger.kernel.org,
"Priyank Rathod" <rathodpriyank@google.com>
Subject: [PATCH v6 2/2] selftests/pcie_lmr: Add tests for the Lane Margining debugfs interface
Date: Tue, 06 Oct 2026 20:59:11 +0000 [thread overview]
Message-ID: <20261006-pcie-link-endpoints-v6-2-154d71657d32@google.com> (raw)
In-Reply-To: <20261006-pcie-link-endpoints-v6-0-154d71657d32@google.com>
Add a KTAP test for the Lane Margining at the Receiver debugfs
interface.
By default it only runs checks that do not start a margining session,
on every pcie_lmr_* directory: the files exist, malformed input is
rejected, and commands fail with ENOTCONN without a session.
If PCIE_LMR_DEV names a port, it also starts a session on that link,
checks the receiver and step bounds the receiver reports, and checks
that Link Control and Link Control 2 of both ends are the same after
the session as before it.
Signed-off-by: Priyank Rathod <rathodpriyank@google.com>
---
MAINTAINERS | 1 +
tools/testing/selftests/Makefile | 1 +
tools/testing/selftests/pcie_lmr/Makefile | 3 +
tools/testing/selftests/pcie_lmr/pcie_lmr.sh | 311 +++++++++++++++++++++++++++
4 files changed, 316 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index cc0fdf2ff7e9..fc8c362c5d22 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -21298,6 +21298,7 @@ L: linux-pci@vger.kernel.org
S: Maintained
F: Documentation/PCI/pcie-lmr.rst
F: drivers/pci/pcie/margin.c
+F: tools/testing/selftests/pcie_lmr/
PCMCIA SUBSYSTEM
M: Dominik Brodowski <linux@dominikbrodowski.net>
diff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Makefile
index 2d960626750e..c92465d3d0b5 100644
--- a/tools/testing/selftests/Makefile
+++ b/tools/testing/selftests/Makefile
@@ -93,6 +93,7 @@ TARGETS += net/tcp_ao
TARGETS += nolibc
TARGETS += pci_endpoint
TARGETS += pcie_bwctrl
+TARGETS += pcie_lmr
TARGETS += perf_events
TARGETS += pidfd
TARGETS += pid_namespace
diff --git a/tools/testing/selftests/pcie_lmr/Makefile b/tools/testing/selftests/pcie_lmr/Makefile
new file mode 100644
index 000000000000..101c90e7ee9e
--- /dev/null
+++ b/tools/testing/selftests/pcie_lmr/Makefile
@@ -0,0 +1,3 @@
+# SPDX-License-Identifier: GPL-2.0
+TEST_PROGS = pcie_lmr.sh
+include ../lib.mk
diff --git a/tools/testing/selftests/pcie_lmr/pcie_lmr.sh b/tools/testing/selftests/pcie_lmr/pcie_lmr.sh
new file mode 100755
index 000000000000..9e5d16d098fd
--- /dev/null
+++ b/tools/testing/selftests/pcie_lmr/pcie_lmr.sh
@@ -0,0 +1,311 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+#
+# Tests for the PCIe Lane Margining at the Receiver debugfs interface
+# (Documentation/PCI/pcie-lmr.rst).
+#
+# By default only checks that do not start a margining session are run,
+# on every port that has a pcie_lmr_* directory. To also run a session
+# on one link, name a port with the capability:
+#
+# PCIE_LMR_DEV=0000:00:01.1 ./pcie_lmr.sh
+#
+# The link runs with ASPM off for the duration of that test; do not
+# point it at a link that is in use for something important.
+
+DIR="$(dirname "$(readlink -f "$0")")"
+source "$DIR"/../kselftest/ktap_helpers.sh
+
+DEBUGFS=/sys/kernel/debug
+export LC_ALL=C
+
+# Like ktap_test_result, but passes arguments that contain spaces intact
+check()
+{
+ local desc="$1"
+
+ shift
+ if "$@"; then
+ ktap_test_pass "$desc"
+ else
+ ktap_test_fail "$desc"
+ fi
+}
+
+# write_expect <file> <value> <expected>
+# <expected> is "ok" or the strerror() text of the expected error.
+write_expect()
+{
+ local file="$1" val="$2" want="$3" err
+
+ err=$( { printf '%s' "$val" > "$file"; } 2>&1 )
+ if [ $? -eq 0 ]; then
+ [ "$want" = "ok" ]
+ return
+ fi
+ [ "$want" != "ok" ] && [[ "$err" == *"$want"* ]]
+}
+
+# read_expect <file> <expected>, like write_expect
+read_expect()
+{
+ local file="$1" want="$2" err
+
+ err=$( { cat "$file" > /dev/null; } 2>&1 )
+ if [ $? -eq 0 ]; then
+ [ "$want" = "ok" ]
+ return
+ fi
+ [ "$want" != "ok" ] && [[ "$err" == *"$want"* ]]
+}
+
+# caps_get <port dir> <key>
+caps_get()
+{
+ sed -n "s/^$2: //p" "$1/lane0/caps"
+}
+
+check_layout()
+{
+ local d="$1" f
+
+ for f in enable receiver port lane0/caps lane0/margin_timing \
+ lane0/margin_voltage lane0/status; do
+ [ -e "$d/$f" ] || return 1
+ done
+}
+
+check_port_file()
+{
+ local out
+
+ out=$(cat "$1/port") || return 1
+ [[ "$out" == *"uses_driver_software: "[01]* ]] &&
+ [[ "$out" == *"margining_ready: "[01]* ]] &&
+ [[ "$out" == *"software_ready: "[01]* ]]
+}
+
+PASSIVE_TESTS=9
+
+# Checks that never start a session
+passive_tests()
+{
+ local d="$1" n i
+
+ n=$(basename "$d")
+
+ check "$n: files present" check_layout "$d"
+ check "$n: port file readable" check_port_file "$d"
+
+ if [ "$(cat "$d/enable" 2>/dev/null)" != "0" ]; then
+ for i in $(seq 3 $PASSIVE_TESTS); do
+ ktap_test_skip "$n: session active, test $i skipped"
+ done
+ return
+ fi
+
+ check "$n: enable rejects '2'" \
+ write_expect "$d/enable" 2 "Invalid argument"
+ check "$n: enable rejects 'x'" \
+ write_expect "$d/enable" x "Invalid argument"
+ check "$n: enable=0 without a session is accepted" \
+ write_expect "$d/enable" 0 ok
+ check "$n: margin_timing rejects 'abc'" \
+ write_expect "$d/lane0/margin_timing" abc "Invalid argument"
+ check "$n: margin_timing needs a session" \
+ write_expect "$d/lane0/margin_timing" 1 \
+ "Transport endpoint is not connected"
+ check "$n: caps needs a session" \
+ read_expect "$d/lane0/caps" "Transport endpoint is not connected"
+ check "$n: receiver needs a session" \
+ write_expect "$d/receiver" 1 "Transport endpoint is not connected"
+}
+
+ACTIVE_TESTS=14
+
+# Print the device at the other end of the link of PCI device $1.
+# Receiver 6 is the child device (PCIe Upstream Port) below the Downstream
+# Port; receiver 1 is the Downstream Port (parent bridge).
+partner_of()
+{
+ local sys=/sys/bus/pci/devices/$1 rx c
+
+ rx=$(cat "$DEBUGFS/pcie_lmr_$1/receiver" 2> /dev/null)
+ if [ "$rx" = 6 ]; then
+ basename "$(dirname "$(readlink -f "$sys")")"
+ return
+ fi
+ for c in "$sys"/[0-9a-fA-F]*:??:??.0; do
+ [ -e "$c/config" ] && { basename "$c"; return; }
+ done
+}
+
+# Link Control and Link Control 2 of PCI device $1, or nothing
+link_regs()
+{
+ [ -n "$1" ] && command -v setpci > /dev/null || return
+ setpci -s "$1" CAP_EXP+10.w CAP_EXP+30.w 2> /dev/null | paste -sd ' '
+}
+
+check_bad_rx()
+{
+ write_expect "$1/receiver" 0 "Invalid argument" &&
+ write_expect "$1/receiver" 7 "Invalid argument"
+}
+
+check_step_roundtrip()
+{
+ write_expect "$1/lane0/margin_timing" 1 ok &&
+ [[ "$(cat "$1/lane0/status")" == timing* ]] &&
+ write_expect "$1/lane0/margin_timing" 0 ok &&
+ test "$(cat "$1/lane0/margin_timing")" = 0 &&
+ test "$(cat "$1/lane0/status")" = idle
+}
+
+check_end_session()
+{
+ write_expect "$1/enable" 0 ok &&
+ test "$(cat "$1/enable")" = 0
+}
+
+stop_session()
+{
+ [ -n "$ACTIVE_DIR" ] && echo 0 > "$ACTIVE_DIR/enable" 2> /dev/null
+}
+
+skip_rest()
+{
+ while [ "$KTAP_TESTNO" -le "$KSFT_NUM_TESTS" ]; do
+ ktap_test_skip "$1"
+ done
+}
+
+active_tests()
+{
+ local dev="$1" d="$DEBUGFS/pcie_lmr_$1" partner before after
+ local rx steps child
+
+ partner=$(partner_of "$dev")
+ if [ -z "$partner" ]; then
+ ktap_test_fail "$dev: start a session"
+ skip_rest "$dev: no link partner"
+ return
+ fi
+ before="$(link_regs "$dev") $(link_regs "$partner")"
+
+ ACTIVE_DIR="$d"
+ trap stop_session EXIT INT TERM
+
+ if ! write_expect "$d/enable" 1 ok; then
+ ktap_test_fail "$dev: start a session"
+ skip_rest "$dev: no session"
+ return
+ fi
+ ktap_test_pass "$dev: start a session"
+
+ check "$dev: enable=1 again is accepted" \
+ write_expect "$d/enable" 1 ok
+ check "$dev: enable reads 1" \
+ test "$(cat "$d/enable")" = 1
+
+ if [ -n "$partner" ] && [ -d "$DEBUGFS/pcie_lmr_$partner" ]; then
+ check "$dev: partner port rejects a second session" \
+ write_expect "$DEBUGFS/pcie_lmr_$partner/enable" 1 \
+ "Device or resource busy"
+ else
+ ktap_test_skip "$dev: partner has no pcie_lmr directory"
+ fi
+
+ rx=$(cat "$d/receiver")
+ check "$dev: default receiver is 1 or 6" \
+ test "$rx" = 1 -o "$rx" = 6
+ check "$dev: receiver 0 and 7 are rejected" \
+ check_bad_rx "$d"
+ check "$dev: receiver 256 is rejected" \
+ write_expect "$d/receiver" 256 "Numerical result out of range"
+
+ steps=$(caps_get "$d" timing_steps)
+ check "$dev: caps reports timing_steps" \
+ test -n "$steps"
+ check "$dev: timing step above timing_steps is rejected" \
+ write_expect "$d/lane0/margin_timing" $((steps + 1)) \
+ "Numerical result out of range"
+
+ if [ "${steps:-0}" -ge 1 ]; then
+ check "$dev: timing step 1 then 0" \
+ check_step_roundtrip "$d"
+ else
+ ktap_test_skip "$dev: receiver reports no timing steps"
+ fi
+
+ if [ "$(caps_get "$d" independent_left_right_timing)" = 0 ]; then
+ check "$dev: negative timing step is rejected" \
+ write_expect "$d/lane0/margin_timing" -1 "Invalid argument"
+ else
+ ktap_test_skip "$dev: receiver supports left/right timing"
+ fi
+
+ if [ "$(caps_get "$d" voltage_supported)" = 0 ]; then
+ check "$dev: voltage step is rejected" \
+ write_expect "$d/lane0/margin_voltage" 1 \
+ "Operation not supported"
+ else
+ ktap_test_skip "$dev: receiver supports voltage margining"
+ fi
+
+ if check_end_session "$d"; then
+ ktap_test_pass "$dev: end the session"
+ ACTIVE_DIR=
+ else
+ ktap_test_fail "$dev: end the session"
+ fi
+
+ # In drivers/pci/pcie/aspm.c, aspm_ctrl_attrs_are_visible() uses
+ # pcie_aspm_get_link(pdev) -> pci_upstream_bridge(pdev)->link_state,
+ # so the sysfs 'link/' directory is attached to the child device
+ # below the Downstream Port (the PCIe Upstream Port, receiver 6),
+ # not to the Downstream Port itself.
+ child=$([ "$rx" = 6 ] && echo "$dev" || echo "$partner")
+ after="$(link_regs "$dev") $(link_regs "$partner")"
+ if [ -z "${before// }" ]; then
+ ktap_test_skip "$dev: setpci not found"
+ elif [ -n "$child" ] && [ ! -d "/sys/bus/pci/devices/$child/link" ]; then
+ ktap_test_skip "$dev: no OS ASPM control on link"
+ else
+ check "$dev: LNKCTL and LNKCTL2 restored on both ends" \
+ test "$before" = "$after"
+ fi
+}
+
+ktap_print_header
+
+if [ "$(id -u)" -ne 0 ]; then
+ ktap_skip_all "must be run as root"
+ exit "$KSFT_SKIP"
+fi
+
+mountpoint -q "$DEBUGFS" || mount -t debugfs none "$DEBUGFS" 2> /dev/null
+
+ports=("$DEBUGFS"/pcie_lmr_*)
+if [ ! -d "${ports[0]}" ]; then
+ ktap_skip_all "no pcie_lmr_* directories in debugfs"
+ exit "$KSFT_SKIP"
+fi
+
+if [ -n "$PCIE_LMR_DEV" ] && [ ! -d "$DEBUGFS/pcie_lmr_$PCIE_LMR_DEV" ]; then
+ ktap_exit_fail_msg "no pcie_lmr directory for $PCIE_LMR_DEV"
+fi
+
+plan=$(( ${#ports[@]} * PASSIVE_TESTS ))
+[ -n "$PCIE_LMR_DEV" ] && plan=$((plan + ACTIVE_TESTS))
+ktap_set_plan "$plan"
+
+for d in "${ports[@]}"; do
+ passive_tests "$d"
+done
+
+if [ -n "$PCIE_LMR_DEV" ]; then
+ active_tests "$PCIE_LMR_DEV"
+fi
+
+ktap_finished
--
2.56.0.rc1.315.gc6ed9934b7-goog
next prev parent reply other threads:[~2026-10-06 20:59 UTC|newest]
Thread overview: 4+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-06 20:59 [PATCH v6 0/2] PCI: Add Lane Margining at the Receiver " Priyank Rathod
2026-10-06 20:59 ` [PATCH v6 1/2] PCI/LMR: " Priyank Rathod
2026-10-06 20:59 ` Priyank Rathod [this message]
2026-10-07 21:01 ` [PATCH v6 0/2] PCI: " Bjorn Helgaas
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261006-pcie-link-endpoints-v6-2-154d71657d32@google.com \
--to=rathodpriyank@google.com \
--cc=bhelgaas@google.com \
--cc=corbet@lwn.net \
--cc=ilpo.jarvinen@linux.intel.com \
--cc=linux-doc@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-kselftest@vger.kernel.org \
--cc=linux-pci@vger.kernel.org \
--cc=lukas@wunner.de \
--cc=mani@kernel.org \
--cc=rdunlap@infradead.org \
--cc=shuah@kernel.org \
--cc=skhan@linuxfoundation.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®