1#!/bin/bash 2# SPDX-License-Identifier: GPL-2.0 3 4ALL_TESTS=" \ 5 test_clock_jump_backward \ 6 test_taprio_after_ptp \ 7 test_max_sdu \ 8 test_clock_jump_backward_forward \ 9" 10NUM_NETIFS=4 11source tc_common.sh 12source lib.sh 13source tsn_lib.sh 14 15require_command python3 16 17# The test assumes the usual topology from the README, where h1 is connected to 18# swp1, h2 to swp2, and swp1 and swp2 are together in a bridge. 19# Additional assumption: h1 and h2 use the same PHC, and so do swp1 and swp2. 20# By synchronizing h1 to swp1 via PTP, h2 is also implicitly synchronized to 21# swp1 (and both to CLOCK_REALTIME). 22h1=${NETIFS[p1]} 23swp1=${NETIFS[p2]} 24swp2=${NETIFS[p3]} 25h2=${NETIFS[p4]} 26 27UDS_ADDRESS_H1="/var/run/ptp4l_h1" 28UDS_ADDRESS_SWP1="/var/run/ptp4l_swp1" 29 30H1_IPV4="192.0.2.1" 31H2_IPV4="192.0.2.2" 32H1_IPV6="2001:db8:1::1" 33H2_IPV6="2001:db8:1::2" 34 35# Tunables 36NUM_PKTS=100 37STREAM_VID=10 38STREAM_PRIO_1=6 39STREAM_PRIO_2=5 40STREAM_PRIO_3=4 41# PTP uses TC 0 42ALL_GATES=$((1 << 0 | 1 << STREAM_PRIO_1 | 1 << STREAM_PRIO_2)) 43# Use a conservative cycle of 10 ms to allow the test to still pass when the 44# kernel has some extra overhead like lockdep etc 45CYCLE_TIME_NS=10000000 46# Create two Gate Control List entries, one OPEN and one CLOSE, of equal 47# durations 48GATE_DURATION_NS=$((CYCLE_TIME_NS / 2)) 49# Give 2/3 of the cycle time to user space and 1/3 to the kernel 50FUDGE_FACTOR=$((CYCLE_TIME_NS / 3)) 51# Shift the isochron base time by half the gate time, so that packets are 52# always received by swp1 close to the middle of the time slot, to minimize 53# inaccuracies due to network sync 54SHIFT_TIME_NS=$((GATE_DURATION_NS / 2)) 55 56path_delay= 57 58h1_create() 59{ 60 simple_if_init $h1 $H1_IPV4/24 $H1_IPV6/64 61} 62 63h1_destroy() 64{ 65 simple_if_fini $h1 $H1_IPV4/24 $H1_IPV6/64 66} 67 68h2_create() 69{ 70 simple_if_init $h2 $H2_IPV4/24 $H2_IPV6/64 71} 72 73h2_destroy() 74{ 75 simple_if_fini $h2 $H2_IPV4/24 $H2_IPV6/64 76} 77 78switch_create() 79{ 80 local h2_mac_addr=$(mac_get $h2) 81 82 ip link set $swp1 up 83 ip link set $swp2 up 84 85 ip link add br0 type bridge vlan_filtering 1 86 ip link set $swp1 master br0 87 ip link set $swp2 master br0 88 ip link set br0 up 89 90 bridge vlan add dev $swp2 vid $STREAM_VID 91 bridge vlan add dev $swp1 vid $STREAM_VID 92 bridge fdb add dev $swp2 \ 93 $h2_mac_addr vlan $STREAM_VID static master 94} 95 96switch_destroy() 97{ 98 ip link del br0 99} 100 101ptp_setup() 102{ 103 # Set up swp1 as a master PHC for h1, synchronized to the local 104 # CLOCK_REALTIME. 105 phc2sys_start $UDS_ADDRESS_SWP1 106 ptp4l_start $h1 true $UDS_ADDRESS_H1 107 ptp4l_start $swp1 false $UDS_ADDRESS_SWP1 108} 109 110ptp_cleanup() 111{ 112 ptp4l_stop $swp1 113 ptp4l_stop $h1 114 phc2sys_stop 115} 116 117txtime_setup() 118{ 119 local if_name=$1 120 121 tc qdisc add dev $if_name clsact 122 # Classify PTP on TC 7 and isochron on TC 6 123 tc filter add dev $if_name egress protocol 0x88f7 \ 124 flower action skbedit priority 7 125 tc filter add dev $if_name egress protocol 802.1Q \ 126 flower vlan_ethtype 0xdead action skbedit priority 6 127 tc qdisc add dev $if_name handle 100: parent root mqprio num_tc 8 \ 128 queues 1@0 1@1 1@2 1@3 1@4 1@5 1@6 1@7 \ 129 map 0 1 2 3 4 5 6 7 \ 130 hw 1 131 # Set up TC 5, 6, 7 for SO_TXTIME. tc-mqprio queues count from 1. 132 tc qdisc replace dev $if_name parent 100:$((STREAM_PRIO_1 + 1)) etf \ 133 clockid CLOCK_TAI offload delta $FUDGE_FACTOR 134 tc qdisc replace dev $if_name parent 100:$((STREAM_PRIO_2 + 1)) etf \ 135 clockid CLOCK_TAI offload delta $FUDGE_FACTOR 136 tc qdisc replace dev $if_name parent 100:$((STREAM_PRIO_3 + 1)) etf \ 137 clockid CLOCK_TAI offload delta $FUDGE_FACTOR 138} 139 140txtime_cleanup() 141{ 142 local if_name=$1 143 144 tc qdisc del dev $if_name clsact 145 tc qdisc del dev $if_name root 146} 147 148taprio_replace() 149{ 150 local if_name="$1"; shift 151 local extra_args="$1"; shift 152 153 # STREAM_PRIO_1 always has an open gate. 154 # STREAM_PRIO_2 has a gate open for GATE_DURATION_NS (half the cycle time) 155 # STREAM_PRIO_3 always has a closed gate. 156 tc qdisc replace dev $if_name root stab overhead 24 taprio num_tc 8 \ 157 queues 1@0 1@1 1@2 1@3 1@4 1@5 1@6 1@7 \ 158 map 0 1 2 3 4 5 6 7 \ 159 sched-entry S $(printf "%x" $ALL_GATES) $GATE_DURATION_NS \ 160 sched-entry S $(printf "%x" $((ALL_GATES & ~(1 << STREAM_PRIO_2)))) $GATE_DURATION_NS \ 161 base-time 0 flags 0x2 $extra_args 162 taprio_wait_for_admin $if_name 163} 164 165taprio_cleanup() 166{ 167 local if_name=$1 168 169 tc qdisc del dev $if_name root 170} 171 172probe_path_delay() 173{ 174 local isochron_dat="$(mktemp)" 175 local received 176 177 log_info "Probing path delay" 178 179 isochron_do "$h1" "$h2" "$UDS_ADDRESS_H1" "" 0 \ 180 "$CYCLE_TIME_NS" "" "" "$NUM_PKTS" \ 181 "$STREAM_VID" "$STREAM_PRIO_1" "" "$isochron_dat" 182 183 received=$(isochron_report_num_received "$isochron_dat") 184 if [ "$received" != "$NUM_PKTS" ]; then 185 echo "Cannot establish basic data path between $h1 and $h2" 186 exit $ksft_fail 187 fi 188 189 printf "pdelay = {}\n" > isochron_data.py 190 isochron report --input-file "$isochron_dat" \ 191 --printf-format "pdelay[%u] = %d - %d\n" \ 192 --printf-args "qRT" \ 193 >> isochron_data.py 194 cat <<-'EOF' > isochron_postprocess.py 195 #!/usr/bin/env python3 196 197 from isochron_data import pdelay 198 import numpy as np 199 200 w = np.array(list(pdelay.values())) 201 print("{}".format(np.max(w))) 202 EOF 203 path_delay=$(python3 ./isochron_postprocess.py) 204 205 log_info "Path delay from $h1 to $h2 estimated at $path_delay ns" 206 207 if [ "$path_delay" -gt "$GATE_DURATION_NS" ]; then 208 echo "Path delay larger than gate duration, aborting" 209 exit $ksft_fail 210 fi 211 212 rm -f ./isochron_data.py 2> /dev/null 213 rm -f ./isochron_postprocess.py 2> /dev/null 214 rm -f "$isochron_dat" 2> /dev/null 215} 216 217setup_prepare() 218{ 219 vrf_prepare 220 221 h1_create 222 h2_create 223 switch_create 224 225 txtime_setup $h1 226 227 # Temporarily set up PTP just to probe the end-to-end path delay. 228 ptp_setup 229 probe_path_delay 230 ptp_cleanup 231} 232 233cleanup() 234{ 235 pre_cleanup 236 237 isochron_recv_stop 238 txtime_cleanup $h1 239 240 switch_destroy 241 h2_destroy 242 h1_destroy 243 244 vrf_cleanup 245} 246 247run_test() 248{ 249 local base_time=$1; shift 250 local stream_prio=$1; shift 251 local expected_delay=$1; shift 252 local should_fail=$1; shift 253 local test_name=$1; shift 254 local isochron_dat="$(mktemp)" 255 local received 256 local median_delay 257 258 RET=0 259 260 # Set the shift time equal to the cycle time, which effectively 261 # cancels the default advance time. Packets won't be sent early in 262 # software, which ensures that they won't prematurely enter through 263 # the open gate in __test_out_of_band(). Also, the gate is open for 264 # long enough that this won't cause a problem in __test_in_band(). 265 isochron_do "$h1" "$h2" "$UDS_ADDRESS_H1" "" "$base_time" \ 266 "$CYCLE_TIME_NS" "$SHIFT_TIME_NS" "$GATE_DURATION_NS" \ 267 "$NUM_PKTS" "$STREAM_VID" "$stream_prio" "" "$isochron_dat" 268 269 received=$(isochron_report_num_received "$isochron_dat") 270 [ "$received" = "$NUM_PKTS" ] 271 check_err_fail $should_fail $? "Reception of $NUM_PKTS packets" 272 273 if [ $should_fail = 0 ] && [ "$received" = "$NUM_PKTS" ]; then 274 printf "pdelay = {}\n" > isochron_data.py 275 isochron report --input-file "$isochron_dat" \ 276 --printf-format "pdelay[%u] = %d - %d\n" \ 277 --printf-args "qRT" \ 278 >> isochron_data.py 279 cat <<-'EOF' > isochron_postprocess.py 280 #!/usr/bin/env python3 281 282 from isochron_data import pdelay 283 import numpy as np 284 285 w = np.array(list(pdelay.values())) 286 print("{}".format(int(np.median(w)))) 287 EOF 288 median_delay=$(python3 ./isochron_postprocess.py) 289 290 # If the condition below is true, packets were delayed by a closed gate 291 [ "$median_delay" -gt $((path_delay + expected_delay)) ] 292 check_fail $? "Median delay $median_delay is greater than expected delay $expected_delay plus path delay $path_delay" 293 294 # If the condition below is true, packets were sent expecting them to 295 # hit a closed gate in the switch, but were not delayed 296 [ "$expected_delay" -gt 0 ] && [ "$median_delay" -lt "$expected_delay" ] 297 check_fail $? "Median delay $median_delay is less than expected delay $expected_delay" 298 fi 299 300 log_test "$test_name" 301 302 rm -f ./isochron_data.py 2> /dev/null 303 rm -f ./isochron_postprocess.py 2> /dev/null 304 rm -f "$isochron_dat" 2> /dev/null 305} 306 307__test_always_open() 308{ 309 run_test 0.000000000 $STREAM_PRIO_1 0 0 "Gate always open" 310} 311 312__test_always_closed() 313{ 314 run_test 0.000000000 $STREAM_PRIO_3 0 1 "Gate always closed" 315} 316 317__test_in_band() 318{ 319 # Send packets in-band with the OPEN gate entry 320 run_test 0.000000000 $STREAM_PRIO_2 0 0 "In band with gate" 321} 322 323__test_out_of_band() 324{ 325 # Send packets in-band with the CLOSE gate entry 326 run_test 0.005000000 $STREAM_PRIO_2 \ 327 $((GATE_DURATION_NS - SHIFT_TIME_NS)) 0 \ 328 "Out of band with gate" 329} 330 331run_subtests() 332{ 333 __test_always_open 334 __test_always_closed 335 __test_in_band 336 __test_out_of_band 337} 338 339test_taprio_after_ptp() 340{ 341 log_info "Setting up taprio after PTP" 342 ptp_setup 343 taprio_replace $swp2 344 run_subtests 345 taprio_cleanup $swp2 346 ptp_cleanup 347} 348 349__test_under_max_sdu() 350{ 351 # Limit max-sdu for STREAM_PRIO_1 352 taprio_replace "$swp2" "max-sdu 0 0 0 0 0 0 100 0" 353 run_test 0.000000000 $STREAM_PRIO_1 0 0 "Under maximum SDU" 354} 355 356__test_over_max_sdu() 357{ 358 # Limit max-sdu for STREAM_PRIO_1 359 taprio_replace "$swp2" "max-sdu 0 0 0 0 0 0 20 0" 360 run_test 0.000000000 $STREAM_PRIO_1 0 1 "Over maximum SDU" 361} 362 363test_max_sdu() 364{ 365 ptp_setup 366 __test_under_max_sdu 367 __test_over_max_sdu 368 taprio_cleanup $swp2 369 ptp_cleanup 370} 371 372# Perform a clock jump in the past without synchronization running, so that the 373# time base remains where it was set by phc_ctl. 374test_clock_jump_backward() 375{ 376 # This is a more complex schedule specifically crafted in a way that 377 # has been problematic on NXP LS1028A. Not much to test with it other 378 # than the fact that it passes traffic. 379 tc qdisc replace dev $swp2 root stab overhead 24 taprio num_tc 8 \ 380 queues 1@0 1@1 1@2 1@3 1@4 1@5 1@6 1@7 map 0 1 2 3 4 5 6 7 \ 381 base-time 0 sched-entry S 20 300000 sched-entry S 10 200000 \ 382 sched-entry S 20 300000 sched-entry S 48 200000 \ 383 sched-entry S 20 300000 sched-entry S 83 200000 \ 384 sched-entry S 40 300000 sched-entry S 00 200000 flags 2 385 386 log_info "Forcing a backward clock jump" 387 phc_ctl $swp1 set 0 388 389 ping_test $h1 192.0.2.2 390 taprio_cleanup $swp2 391} 392 393# Test that taprio tolerates clock jumps. 394# Since ptp4l and phc2sys are running, it is expected for the time to 395# eventually recover (through yet another clock jump). Isochron waits 396# until that is the case. 397test_clock_jump_backward_forward() 398{ 399 log_info "Forcing a backward and a forward clock jump" 400 taprio_replace $swp2 401 phc_ctl $swp1 set 0 402 ptp_setup 403 ping_test $h1 192.0.2.2 404 run_subtests 405 ptp_cleanup 406 taprio_cleanup $swp2 407} 408 409tc_offload_check 410if [[ $? -ne 0 ]]; then 411 log_test_skip "Could not test offloaded functionality" 412 exit $EXIT_STATUS 413fi 414 415trap cleanup EXIT 416 417setup_prepare 418setup_wait 419tests_run 420 421exit $EXIT_STATUS 422