Linux kernel mirror (for testing)
git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel
os
linux
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