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 <[email protected]>
---
 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:      [email protected]
 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 <[email protected]>
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


Reply via email to