1#!/bin/bash
2# SPDX-License-Identifier: GPL-2.0
3#
4# Test for cpuset v2 partition root state (PRS)
5#
6# The sched verbose flag can be optionally set so that the console log
7# can be examined for the correct setting of scheduling domain.
8#
9
10skip_test() {
11	echo "$1"
12	echo "Test SKIPPED"
13	exit 4 # ksft_skip
14}
15
16[[ $(id -u) -eq 0 ]] || skip_test "Test must be run as root!"
17
18
19# Get wait_inotify location
20WAIT_INOTIFY=$(cd $(dirname $0); pwd)/wait_inotify
21
22# Find cgroup v2 mount point
23CGROUP2=$(mount -t cgroup2 | head -1 | awk -e '{print $3}')
24[[ -n "$CGROUP2" ]] || skip_test "Cgroup v2 mount point not found!"
25SUBPARTS_CPUS=$CGROUP2/.__DEBUG__.cpuset.cpus.subpartitions
26CPULIST=$(cat $CGROUP2/cpuset.cpus.effective)
27
28NR_CPUS=$(lscpu | grep "^CPU(s):" | sed -e "s/.*:[[:space:]]*//")
29[[ $NR_CPUS -lt 8 ]] && skip_test "Test needs at least 8 cpus available!"
30
31# Set verbose flag and delay factor
32PROG=$1
33VERBOSE=0
34DELAY_FACTOR=1
35SCHED_DEBUG=
36while [[ "$1" = -* ]]
37do
38	case "$1" in
39		-v) ((VERBOSE++))
40		    # Enable sched/verbose can slow thing down
41		    [[ $DELAY_FACTOR -eq 1 ]] &&
42			DELAY_FACTOR=2
43		    ;;
44		-d) DELAY_FACTOR=$2
45		    shift
46		    ;;
47		*)  echo "Usage: $PROG [-v] [-d <delay-factor>"
48		    exit
49		    ;;
50	esac
51	shift
52done
53
54# Set sched verbose flag if available when "-v" option is specified
55if [[ $VERBOSE -gt 0 && -d /sys/kernel/debug/sched ]]
56then
57	# Used to restore the original setting during cleanup
58	SCHED_DEBUG=$(cat /sys/kernel/debug/sched/verbose)
59	echo Y > /sys/kernel/debug/sched/verbose
60fi
61
62cd $CGROUP2
63echo +cpuset > cgroup.subtree_control
64
65#
66# If cpuset has been set up and used in child cgroups, we may not be able to
67# create partition under root cgroup because of the CPU exclusivity rule.
68# So we are going to skip the test if this is the case.
69#
70[[ -d test ]] || mkdir test
71echo 0-6 > test/cpuset.cpus
72echo root > test/cpuset.cpus.partition
73cat test/cpuset.cpus.partition | grep -q invalid
74RESULT=$?
75echo member > test/cpuset.cpus.partition
76echo "" > test/cpuset.cpus
77[[ $RESULT -eq 0 ]] && skip_test "Child cgroups are using cpuset!"
78
79cleanup()
80{
81	online_cpus
82	cd $CGROUP2
83	rmdir A1/A2/A3 A1/A2 A1 B1 > /dev/null 2>&1
84	rmdir test > /dev/null 2>&1
85	[[ -n "$SCHED_DEBUG" ]] &&
86		echo "$SCHED_DEBUG" > /sys/kernel/debug/sched/verbose
87}
88
89# Pause in ms
90pause()
91{
92	DELAY=$1
93	LOOP=0
94	while [[ $LOOP -lt $DELAY_FACTOR ]]
95	do
96		sleep $DELAY
97		((LOOP++))
98	done
99	return 0
100}
101
102console_msg()
103{
104	MSG=$1
105	echo "$MSG"
106	echo "" > /dev/console
107	echo "$MSG" > /dev/console
108	pause 0.01
109}
110
111test_partition()
112{
113	EXPECTED_VAL=$1
114	echo $EXPECTED_VAL > cpuset.cpus.partition
115	[[ $? -eq 0 ]] || exit 1
116	ACTUAL_VAL=$(cat cpuset.cpus.partition)
117	[[ $ACTUAL_VAL != $EXPECTED_VAL ]] && {
118		echo "cpuset.cpus.partition: expect $EXPECTED_VAL, found $ACTUAL_VAL"
119		echo "Test FAILED"
120		exit 1
121	}
122}
123
124test_effective_cpus()
125{
126	EXPECTED_VAL=$1
127	ACTUAL_VAL=$(cat cpuset.cpus.effective)
128	[[ "$ACTUAL_VAL" != "$EXPECTED_VAL" ]] && {
129		echo "cpuset.cpus.effective: expect '$EXPECTED_VAL', found '$ACTUAL_VAL'"
130		echo "Test FAILED"
131		exit 1
132	}
133}
134
135# Adding current process to cgroup.procs as a test
136test_add_proc()
137{
138	OUTSTR="$1"
139	ERRMSG=$((echo $$ > cgroup.procs) |& cat)
140	echo $ERRMSG | grep -q "$OUTSTR"
141	[[ $? -ne 0 ]] && {
142		echo "cgroup.procs: expect '$OUTSTR', got '$ERRMSG'"
143		echo "Test FAILED"
144		exit 1
145	}
146	echo $$ > $CGROUP2/cgroup.procs	# Move out the task
147}
148
149#
150# Cpuset controller state transition test matrix.
151#
152# Cgroup test hierarchy
153#
154# root -- A1 -- A2 -- A3
155#      +- B1
156#
157#  P<v> = set cpus.partition (0:member, 1:root, 2:isolated)
158#  C<l> = add cpu-list to cpuset.cpus
159#  X<l> = add cpu-list to cpuset.cpus.exclusive
160#  S<p> = use prefix in subtree_control
161#  T    = put a task into cgroup
162#  O<c>=<v> = Write <v> to CPU online file of <c>
163#
164SETUP_A123_PARTITIONS="C1-3:P1:S+ C2-3:P1:S+ C3:P1"
165TEST_MATRIX=(
166	#  old-A1 old-A2 old-A3 old-B1 new-A1 new-A2 new-A3 new-B1 fail ECPUs Pstate ISOLCPUS
167	#  ------ ------ ------ ------ ------ ------ ------ ------ ---- ----- ------ --------
168	"   C0-1     .      .    C2-3    S+    C4-5     .      .     0 A2:0-1"
169	"   C0-1     .      .    C2-3    P1      .      .      .     0 "
170	"   C0-1     .      .    C2-3   P1:S+ C0-1:P1   .      .     0 "
171	"   C0-1     .      .    C2-3   P1:S+  C1:P1    .      .     0 "
172	"  C0-1:S+   .      .    C2-3     .      .      .     P1     0 "
173	"  C0-1:P1   .      .    C2-3    S+     C1      .      .     0 "
174	"  C0-1:P1   .      .    C2-3    S+    C1:P1    .      .     0 "
175	"  C0-1:P1   .      .    C2-3    S+    C1:P1    .     P1     0 "
176	"  C0-1:P1   .      .    C2-3   C4-5     .      .      .     0 A1:4-5"
177	"  C0-1:P1   .      .    C2-3  S+:C4-5   .      .      .     0 A1:4-5"
178	"   C0-1     .      .   C2-3:P1   .      .      .     C2     0 "
179	"   C0-1     .      .   C2-3:P1   .      .      .    C4-5    0 B1:4-5"
180	"C0-3:P1:S+ C2-3:P1 .      .      .      .      .      .     0 A1:0-1,A2:2-3"
181	"C0-3:P1:S+ C2-3:P1 .      .     C1-3    .      .      .     0 A1:1,A2:2-3"
182	"C2-3:P1:S+  C3:P1  .      .     C3      .      .      .     0 A1:,A2:3 A1:P1,A2:P1"
183	"C2-3:P1:S+  C3:P1  .      .     C3      P0     .      .     0 A1:3,A2:3 A1:P1,A2:P0"
184	"C2-3:P1:S+  C2:P1  .      .     C2-4    .      .      .     0 A1:3-4,A2:2"
185	"C2-3:P1:S+  C3:P1  .      .     C3      .      .     C0-2   0 A1:,B1:0-2 A1:P1,A2:P1"
186	"$SETUP_A123_PARTITIONS    .     C2-3    .      .      .     0 A1:,A2:2,A3:3 A1:P1,A2:P1,A3:P1"
187
188	# CPU offlining cases:
189	"   C0-1     .      .    C2-3    S+    C4-5     .     O2=0   0 A1:0-1,B1:3"
190	"C0-3:P1:S+ C2-3:P1 .      .     O2=0    .      .      .     0 A1:0-1,A2:3"
191	"C0-3:P1:S+ C2-3:P1 .      .     O2=0   O2=1    .      .     0 A1:0-1,A2:2-3"
192	"C0-3:P1:S+ C2-3:P1 .      .     O1=0    .      .      .     0 A1:0,A2:2-3"
193	"C0-3:P1:S+ C2-3:P1 .      .     O1=0   O1=1    .      .     0 A1:0-1,A2:2-3"
194	"C2-3:P1:S+  C3:P1  .      .     O3=0   O3=1    .      .     0 A1:2,A2:3 A1:P1,A2:P1"
195	"C2-3:P1:S+  C3:P2  .      .     O3=0   O3=1    .      .     0 A1:2,A2:3 A1:P1,A2:P2"
196	"C2-3:P1:S+  C3:P1  .      .     O2=0   O2=1    .      .     0 A1:2,A2:3 A1:P1,A2:P1"
197	"C2-3:P1:S+  C3:P2  .      .     O2=0   O2=1    .      .     0 A1:2,A2:3 A1:P1,A2:P2"
198	"C2-3:P1:S+  C3:P1  .      .     O2=0    .      .      .     0 A1:,A2:3 A1:P1,A2:P1"
199	"C2-3:P1:S+  C3:P1  .      .     O3=0    .      .      .     0 A1:2,A2: A1:P1,A2:P1"
200	"C2-3:P1:S+  C3:P1  .      .    T:O2=0   .      .      .     0 A1:3,A2:3 A1:P1,A2:P-1"
201	"C2-3:P1:S+  C3:P1  .      .      .    T:O3=0   .      .     0 A1:2,A2:2 A1:P1,A2:P-1"
202	"$SETUP_A123_PARTITIONS    .     O1=0    .      .      .     0 A1:,A2:2,A3:3 A1:P1,A2:P1,A3:P1"
203	"$SETUP_A123_PARTITIONS    .     O2=0    .      .      .     0 A1:1,A2:,A3:3 A1:P1,A2:P1,A3:P1"
204	"$SETUP_A123_PARTITIONS    .     O3=0    .      .      .     0 A1:1,A2:2,A3: A1:P1,A2:P1,A3:P1"
205	"$SETUP_A123_PARTITIONS    .    T:O1=0   .      .      .     0 A1:2-3,A2:2-3,A3:3 A1:P1,A2:P-1,A3:P-1"
206	"$SETUP_A123_PARTITIONS    .      .    T:O2=0   .      .     0 A1:1,A2:3,A3:3 A1:P1,A2:P1,A3:P-1"
207	"$SETUP_A123_PARTITIONS    .      .      .    T:O3=0   .     0 A1:1,A2:2,A3:2 A1:P1,A2:P1,A3:P-1"
208	"$SETUP_A123_PARTITIONS    .    T:O1=0  O1=1    .      .     0 A1:1,A2:2,A3:3 A1:P1,A2:P1,A3:P1"
209	"$SETUP_A123_PARTITIONS    .      .    T:O2=0  O2=1    .     0 A1:1,A2:2,A3:3 A1:P1,A2:P1,A3:P1"
210	"$SETUP_A123_PARTITIONS    .      .      .    T:O3=0  O3=1   0 A1:1,A2:2,A3:3 A1:P1,A2:P1,A3:P1"
211	"$SETUP_A123_PARTITIONS    .    T:O1=0  O2=0   O1=1    .     0 A1:1,A2:,A3:3 A1:P1,A2:P1,A3:P1"
212	"$SETUP_A123_PARTITIONS    .    T:O1=0  O2=0   O2=1    .     0 A1:2-3,A2:2-3,A3:3 A1:P1,A2:P-1,A3:P-1"
213
214	#  old-A1 old-A2 old-A3 old-B1 new-A1 new-A2 new-A3 new-B1 fail ECPUs Pstate ISOLCPUS
215	#  ------ ------ ------ ------ ------ ------ ------ ------ ---- ----- ------ --------
216	#
217	# Remote partition and cpuset.cpus.exclusive tests
218	#
219	" C0-3:S+ C1-3:S+ C2-3     .    X2-3     .      .      .     0 A1:0-3,A2:1-3,A3:2-3,XA1:2-3"
220	" C0-3:S+ C1-3:S+ C2-3     .    X2-3  X2-3:P2   .      .     0 A1:0-1,A2:2-3,A3:2-3 A1:P0,A2:P2 2-3"
221	" C0-3:S+ C1-3:S+ C2-3     .    X2-3   X3:P2    .      .     0 A1:0-2,A2:3,A3:3 A1:P0,A2:P2 3"
222	" C0-3:S+ C1-3:S+ C2-3     .    X2-3   X2-3  X2-3:P2   .     0 A1:0-1,A2:1,A3:2-3 A1:P0,A3:P2 2-3"
223	" C0-3:S+ C1-3:S+ C2-3     .    X2-3   X2-3 X2-3:P2:C3 .     0 A1:0-2,A2:1-2,A3:3 A1:P0,A3:P2 3"
224	" C0-3:S+ C1-3:S+ C2-3   C2-3     .      .      .      P2    0 A1:0-3,A2:1-3,A3:2-3,B1:2-3 A1:P0,A3:P0,B1:P-2"
225	" C0-3:S+ C1-3:S+ C2-3   C4-5     .      .      .      P2    0 B1:4-5 B1:P2 4-5"
226	" C0-3:S+ C1-3:S+ C2-3    C4    X2-3   X2-3  X2-3:P2   P2    0 A3:2-3,B1:4 A3:P2,B1:P2 2-4"
227	" C0-3:S+ C1-3:S+ C2-3    C4    X2-3   X2-3 X2-3:P2:C1-3 P2  0 A3:2-3,B1:4 A3:P2,B1:P2 2-4"
228	" C0-3:S+ C1-3:S+ C2-3    C4    X1-3  X1-3:P2   P2     .     0 A2:1,A3:2-3 A2:P2,A3:P2 1-3"
229	" C0-3:S+ C1-3:S+ C2-3    C4    X2-3   X2-3  X2-3:P2 P2:C4-5 0 A3:2-3,B1:4-5 A3:P2,B1:P2 2-5"
230
231	# Nested remote/local partition tests
232	" C0-3:S+ C1-3:S+ C2-3   C4-5   X2-3  X2-3:P1   P2     P1    0 A1:0-1,A2:,A3:2-3,B1:4-5 \
233								       A1:P0,A2:P1,A3:P2,B1:P1 2-3"
234	" C0-3:S+ C1-3:S+ C2-3    C4    X2-3  X2-3:P1   P2     P1    0 A1:0-1,A2:,A3:2-3,B1:4 \
235								       A1:P0,A2:P1,A3:P2,B1:P1 2-4,2-3"
236	" C0-3:S+ C1-3:S+  C3     C4    X2-3  X2-3:P1   P2     P1    0 A1:0-1,A2:2,A3:3,B1:4 \
237								       A1:P0,A2:P1,A3:P2,B1:P1 2-4,3"
238	" C0-4:S+ C1-4:S+ C2-4     .    X2-4  X2-4:P2  X4:P1    .    0 A1:0-1,A2:2-3,A3:4 \
239								       A1:P0,A2:P2,A3:P1 2-4,2-3"
240	" C0-4:X2-4:S+ C1-4:X2-4:S+:P2 C2-4:X4:P1 \
241				   .      .      X5      .      .    0 A1:0-4,A2:1-4,A3:2-4 \
242								       A1:P0,A2:P-2,A3:P-1"
243	" C0-4:X2-4:S+ C1-4:X2-4:S+:P2 C2-4:X4:P1 \
244				   .      .      .      X1      .    0 A1:0-1,A2:2-4,A3:2-4 \
245								       A1:P0,A2:P2,A3:P-1 2-4"
246
247	# Remote partition offline tests
248	" C0-3:S+ C1-3:S+ C2-3     .    X2-3   X2-3 X2-3:P2:O2=0 .   0 A1:0-1,A2:1,A3:3 A1:P0,A3:P2 2-3"
249	" C0-3:S+ C1-3:S+ C2-3     .    X2-3   X2-3 X2-3:P2:O2=0 O2=1 0 A1:0-1,A2:1,A3:2-3 A1:P0,A3:P2 2-3"
250	" C0-3:S+ C1-3:S+  C3      .    X2-3   X2-3    P2:O3=0   .   0 A1:0-2,A2:1-2,A3: A1:P0,A3:P2 3"
251	" C0-3:S+ C1-3:S+  C3      .    X2-3   X2-3   T:P2:O3=0  .   0 A1:0-2,A2:1-2,A3:1-2 A1:P0,A3:P-2 3,"
252
253	# An invalidated remote partition cannot self-recover from hotplug
254	" C0-3:S+ C1-3:S+  C2      .    X2-3   X2-3   T:P2:O2=0 O2=1 0 A1:0-3,A2:1-3,A3:2 A1:P0,A3:P-2"
255
256	# cpus.exclusive.effective clearing test
257	" C0-3:S+ C1-3:S+  C2      .   X2-3:X    .      .      .     0 A1:0-3,A2:1-3,A3:2,XA1:"
258
259	# Invalid to valid remote partition transition test
260	" C0-3:S+   C1-3    .      .      .    X3:P2    .      .     0 A1:0-3,A2:1-3,XA2: A2:P-2"
261	" C0-3:S+ C1-3:X3:P2
262			    .      .    X2-3    P2      .      .     0 A1:0-2,A2:3,XA2:3 A2:P2 3"
263
264	# Invalid to valid local partition direct transition tests
265	" C1-3:S+:P2 C2-3:X1:P2 .  .      .      .      .      .     0 A1:1-3,XA1:1-3,A2:2-3:XA2: A1:P2,A2:P-2 1-3"
266	" C1-3:S+:P2 C2-3:X1:P2 .  .      .    X3:P2    .      .     0 A1:1-2,XA1:1-3,A2:3:XA2:3 A1:P2,A2:P2 1-3"
267	"  C0-3:P2   .      .    C4-6   C0-4     .      .      .     0 A1:0-4,B1:4-6 A1:P-2,B1:P0"
268	"  C0-3:P2   .      .    C4-6 C0-4:C0-3  .      .      .     0 A1:0-3,B1:4-6 A1:P2,B1:P0 0-3"
269	"  C0-3:P2   .      .  C3-5:C4-5  .      .      .      .     0 A1:0-3,B1:4-5 A1:P2,B1:P0 0-3"
270
271	# Local partition invalidation tests
272	" C0-3:X1-3:S+:P2 C1-3:X2-3:S+:P2 C2-3:X3:P2 \
273				   .      .      .      .      .     0 A1:1,A2:2,A3:3 A1:P2,A2:P2,A3:P2 1-3"
274	" C0-3:X1-3:S+:P2 C1-3:X2-3:S+:P2 C2-3:X3:P2 \
275				   .      .     X4      .      .     0 A1:1-3,A2:1-3,A3:2-3,XA2:,XA3: A1:P2,A2:P-2,A3:P-2 1-3"
276	" C0-3:X1-3:S+:P2 C1-3:X2-3:S+:P2 C2-3:X3:P2 \
277				   .      .     C4      .      .     0 A1:1-3,A2:1-3,A3:2-3,XA2:,XA3: A1:P2,A2:P-2,A3:P-2 1-3"
278	# Local partition CPU change tests
279	" C0-5:S+:P2 C4-5:S+:P1 .  .      .    C3-5     .      .     0 A1:0-2,A2:3-5 A1:P2,A2:P1 0-2"
280	" C0-5:S+:P2 C4-5:S+:P1 .  .    C1-5     .      .      .     0 A1:1-3,A2:4-5 A1:P2,A2:P1 1-3"
281
282	# cpus_allowed/exclusive_cpus update tests
283	" C0-3:X2-3:S+ C1-3:X2-3:S+ C2-3:X2-3 \
284				   .     C4      .      P2     .     0 A1:4,A2:4,XA2:,XA3:,A3:4 \
285								       A1:P0,A3:P-2"
286	" C0-3:X2-3:S+ C1-3:X2-3:S+ C2-3:X2-3 \
287				   .     X1      .      P2     .     0 A1:0-3,A2:1-3,XA1:1,XA2:,XA3:,A3:2-3 \
288								       A1:P0,A3:P-2"
289	" C0-3:X2-3:S+ C1-3:X2-3:S+ C2-3:X2-3 \
290				   .      .     C3      P2     .     0 A1:0-2,A2:0-2,XA2:3,XA3:3,A3:3 \
291								       A1:P0,A3:P2 3"
292	" C0-3:X2-3:S+ C1-3:X2-3:S+ C2-3:X2-3 \
293				   .      .     X3      P2     .     0 A1:0-2,A2:1-2,XA2:3,XA3:3,A3:3 \
294								       A1:P0,A3:P2 3"
295	" C0-3:X2-3:S+ C1-3:X2-3:S+ C2-3:X2-3:P2 \
296				   .      .     X3      .      .     0 A1:0-3,A2:1-3,XA2:3,XA3:3,A3:2-3 \
297								       A1:P0,A3:P-2"
298	" C0-3:X2-3:S+ C1-3:X2-3:S+ C2-3:X2-3:P2 \
299				   .      .     C3      .      .     0 A1:0-3,A2:3,XA2:3,XA3:3,A3:3 \
300								       A1:P0,A3:P-2"
301	" C0-3:X2-3:S+ C1-3:X2-3:S+ C2-3:X2-3:P2 \
302				   .     C4      .      .      .     0 A1:4,A2:4,A3:4,XA1:,XA2:,XA3 \
303								       A1:P0,A3:P-2"
304
305	#  old-A1 old-A2 old-A3 old-B1 new-A1 new-A2 new-A3 new-B1 fail ECPUs Pstate ISOLCPUS
306	#  ------ ------ ------ ------ ------ ------ ------ ------ ---- ----- ------ --------
307	#
308	# Incorrect change to cpuset.cpus invalidates partition root
309	#
310	# Adding CPUs to partition root that are not in parent's
311	# cpuset.cpus is allowed, but those extra CPUs are ignored.
312	"C2-3:P1:S+ C3:P1   .      .      .     C2-4    .      .     0 A1:,A2:2-3 A1:P1,A2:P1"
313
314	# Taking away all CPUs from parent or itself if there are tasks
315	# will make the partition invalid.
316	"C2-3:P1:S+  C3:P1  .      .      T     C2-3    .      .     0 A1:2-3,A2:2-3 A1:P1,A2:P-1"
317	" C3:P1:S+    C3    .      .      T      P1     .      .     0 A1:3,A2:3 A1:P1,A2:P-1"
318	"$SETUP_A123_PARTITIONS    .    T:C2-3   .      .      .     0 A1:2-3,A2:2-3,A3:3 A1:P1,A2:P-1,A3:P-1"
319	"$SETUP_A123_PARTITIONS    . T:C2-3:C1-3 .      .      .     0 A1:1,A2:2,A3:3 A1:P1,A2:P1,A3:P1"
320
321	# Changing a partition root to member makes child partitions invalid
322	"C2-3:P1:S+  C3:P1  .      .      P0     .      .      .     0 A1:2-3,A2:3 A1:P0,A2:P-1"
323	"$SETUP_A123_PARTITIONS    .     C2-3    P0     .      .     0 A1:2-3,A2:2-3,A3:3 A1:P1,A2:P0,A3:P-1"
324
325	# cpuset.cpus can contains cpus not in parent's cpuset.cpus as long
326	# as they overlap.
327	"C2-3:P1:S+  .      .      .      .   C3-4:P1   .      .     0 A1:2,A2:3 A1:P1,A2:P1"
328
329	# Deletion of CPUs distributed to child cgroup is allowed.
330	"C0-1:P1:S+ C1      .    C2-3   C4-5     .      .      .     0 A1:4-5,A2:4-5"
331
332	# To become a valid partition root, cpuset.cpus must overlap parent's
333	# cpuset.cpus.
334	"  C0-1:P1   .      .    C2-3    S+   C4-5:P1   .      .     0 A1:0-1,A2:0-1 A1:P1,A2:P-1"
335
336	# Enabling partition with child cpusets is allowed
337	"  C0-1:S+  C1      .    C2-3    P1      .      .      .     0 A1:0-1,A2:1 A1:P1"
338
339	# A partition root with non-partition root parent is invalid, but it
340	# can be made valid if its parent becomes a partition root too.
341	"  C0-1:S+  C1      .    C2-3     .      P2     .      .     0 A1:0-1,A2:1 A1:P0,A2:P-2"
342	"  C0-1:S+ C1:P2    .    C2-3     P1     .      .      .     0 A1:0,A2:1 A1:P1,A2:P2"
343
344	# A non-exclusive cpuset.cpus change will invalidate partition and its siblings
345	"  C0-1:P1   .      .    C2-3   C0-2     .      .      .     0 A1:0-2,B1:2-3 A1:P-1,B1:P0"
346	"  C0-1:P1   .      .  P1:C2-3  C0-2     .      .      .     0 A1:0-2,B1:2-3 A1:P-1,B1:P-1"
347	"   C0-1     .      .  P1:C2-3  C0-2     .      .      .     0 A1:0-2,B1:2-3 A1:P0,B1:P-1"
348
349	#  old-A1 old-A2 old-A3 old-B1 new-A1 new-A2 new-A3 new-B1 fail ECPUs Pstate ISOLCPUS
350	#  ------ ------ ------ ------ ------ ------ ------ ------ ---- ----- ------ --------
351	# Failure cases:
352
353	# A task cannot be added to a partition with no cpu
354	"C2-3:P1:S+  C3:P1  .      .    O2=0:T   .      .      .     1 A1:,A2:3 A1:P1,A2:P1"
355
356	# Changes to cpuset.cpus.exclusive that violate exclusivity rule is rejected
357	"   C0-3     .      .    C4-5   X0-3     .      .     X3-5   1 A1:0-3,B1:4-5"
358)
359
360#
361# Write to the cpu online file
362#  $1 - <c>=<v> where <c> = cpu number, <v> value to be written
363#
364write_cpu_online()
365{
366	CPU=${1%=*}
367	VAL=${1#*=}
368	CPUFILE=//sys/devices/system/cpu/cpu${CPU}/online
369	if [[ $VAL -eq 0 ]]
370	then
371		OFFLINE_CPUS="$OFFLINE_CPUS $CPU"
372	else
373		[[ -n "$OFFLINE_CPUS" ]] && {
374			OFFLINE_CPUS=$(echo $CPU $CPU $OFFLINE_CPUS | fmt -1 |\
375					sort | uniq -u)
376		}
377	fi
378	echo $VAL > $CPUFILE
379	pause 0.05
380}
381
382#
383# Set controller state
384#  $1 - cgroup directory
385#  $2 - state
386#  $3 - showerr
387#
388# The presence of ":" in state means transition from one to the next.
389#
390set_ctrl_state()
391{
392	TMPMSG=/tmp/.msg_$$
393	CGRP=$1
394	STATE=$2
395	SHOWERR=${3}
396	CTRL=${CTRL:=$CONTROLLER}
397	HASERR=0
398	REDIRECT="2> $TMPMSG"
399	[[ -z "$STATE" || "$STATE" = '.' ]] && return 0
400	[[ $VERBOSE -gt 0 ]] && SHOWERR=1
401
402	rm -f $TMPMSG
403	for CMD in $(echo $STATE | sed -e "s/:/ /g")
404	do
405		TFILE=$CGRP/cgroup.procs
406		SFILE=$CGRP/cgroup.subtree_control
407		PFILE=$CGRP/cpuset.cpus.partition
408		CFILE=$CGRP/cpuset.cpus
409		XFILE=$CGRP/cpuset.cpus.exclusive
410		S=$(expr substr $CMD 1 1)
411		if [[ $S = S ]]
412		then
413			PREFIX=${CMD#?}
414			COMM="echo ${PREFIX}${CTRL} > $SFILE"
415			eval $COMM $REDIRECT
416		elif [[ $S = X ]]
417		then
418			CPUS=${CMD#?}
419			COMM="echo $CPUS > $XFILE"
420			eval $COMM $REDIRECT
421		elif [[ $S = C ]]
422		then
423			CPUS=${CMD#?}
424			COMM="echo $CPUS > $CFILE"
425			eval $COMM $REDIRECT
426		elif [[ $S = P ]]
427		then
428			VAL=${CMD#?}
429			case $VAL in
430			0)  VAL=member
431			    ;;
432			1)  VAL=root
433			    ;;
434			2)  VAL=isolated
435			    ;;
436			*)
437			    echo "Invalid partition state - $VAL"
438			    exit 1
439			    ;;
440			esac
441			COMM="echo $VAL > $PFILE"
442			eval $COMM $REDIRECT
443		elif [[ $S = O ]]
444		then
445			VAL=${CMD#?}
446			write_cpu_online $VAL
447		elif [[ $S = T ]]
448		then
449			COMM="echo 0 > $TFILE"
450			eval $COMM $REDIRECT
451		fi
452		RET=$?
453		[[ $RET -ne 0 ]] && {
454			[[ -n "$SHOWERR" ]] && {
455				echo "$COMM"
456				cat $TMPMSG
457			}
458			HASERR=1
459		}
460		pause 0.01
461		rm -f $TMPMSG
462	done
463	return $HASERR
464}
465
466set_ctrl_state_noerr()
467{
468	CGRP=$1
469	STATE=$2
470	[[ -d $CGRP ]] || mkdir $CGRP
471	set_ctrl_state $CGRP $STATE 1
472	[[ $? -ne 0 ]] && {
473		echo "ERROR: Failed to set $2 to cgroup $1!"
474		exit 1
475	}
476}
477
478online_cpus()
479{
480	[[ -n "OFFLINE_CPUS" ]] && {
481		for C in $OFFLINE_CPUS
482		do
483			write_cpu_online ${C}=1
484		done
485	}
486}
487
488#
489# Return 1 if the list of effective cpus isn't the same as the initial list.
490#
491reset_cgroup_states()
492{
493	echo 0 > $CGROUP2/cgroup.procs
494	online_cpus
495	rmdir A1/A2/A3 A1/A2 A1 B1 > /dev/null 2>&1
496	pause 0.02
497	set_ctrl_state . R-
498	pause 0.01
499}
500
501dump_states()
502{
503	for DIR in . A1 A1/A2 A1/A2/A3 B1
504	do
505		CPUS=$DIR/cpuset.cpus
506		ECPUS=$DIR/cpuset.cpus.effective
507		XCPUS=$DIR/cpuset.cpus.exclusive
508		XECPUS=$DIR/cpuset.cpus.exclusive.effective
509		PRS=$DIR/cpuset.cpus.partition
510		PCPUS=$DIR/.__DEBUG__.cpuset.cpus.subpartitions
511		ISCPUS=$DIR/cpuset.cpus.isolated
512		[[ -e $CPUS   ]] && echo "$CPUS: $(cat $CPUS)"
513		[[ -e $XCPUS  ]] && echo "$XCPUS: $(cat $XCPUS)"
514		[[ -e $ECPUS  ]] && echo "$ECPUS: $(cat $ECPUS)"
515		[[ -e $XECPUS ]] && echo "$XECPUS: $(cat $XECPUS)"
516		[[ -e $PRS    ]] && echo "$PRS: $(cat $PRS)"
517		[[ -e $PCPUS  ]] && echo "$PCPUS: $(cat $PCPUS)"
518		[[ -e $ISCPUS ]] && echo "$ISCPUS: $(cat $ISCPUS)"
519	done
520}
521
522#
523# Check effective cpus
524# $1 - check string, format: <cgroup>:<cpu-list>[,<cgroup>:<cpu-list>]*
525#
526check_effective_cpus()
527{
528	CHK_STR=$1
529	for CHK in $(echo $CHK_STR | sed -e "s/,/ /g")
530	do
531		set -- $(echo $CHK | sed -e "s/:/ /g")
532		CGRP=$1
533		CPUS=$2
534		if [[ $CGRP = X* ]]
535		then
536			CGRP=${CGRP#X}
537			FILE=cpuset.cpus.exclusive.effective
538		else
539			FILE=cpuset.cpus.effective
540		fi
541		[[ $CGRP = A2 ]] && CGRP=A1/A2
542		[[ $CGRP = A3 ]] && CGRP=A1/A2/A3
543		[[ -e $CGRP/$FILE ]] || return 1
544		[[ $CPUS = $(cat $CGRP/$FILE) ]] || return 1
545	done
546}
547
548#
549# Check cgroup states
550#  $1 - check string, format: <cgroup>:<state>[,<cgroup>:<state>]*
551#
552check_cgroup_states()
553{
554	CHK_STR=$1
555	for CHK in $(echo $CHK_STR | sed -e "s/,/ /g")
556	do
557		set -- $(echo $CHK | sed -e "s/:/ /g")
558		CGRP=$1
559		STATE=$2
560		FILE=
561		EVAL=$(expr substr $STATE 2 2)
562		[[ $CGRP = A2 ]] && CGRP=A1/A2
563		[[ $CGRP = A3 ]] && CGRP=A1/A2/A3
564
565		case $STATE in
566			P*) FILE=$CGRP/cpuset.cpus.partition
567			    ;;
568			*)  echo "Unknown state: $STATE!"
569			    exit 1
570			    ;;
571		esac
572		VAL=$(cat $FILE)
573
574		case "$VAL" in
575			member) VAL=0
576				;;
577			root)	VAL=1
578				;;
579			isolated)
580				VAL=2
581				;;
582			"root invalid"*)
583				VAL=-1
584				;;
585			"isolated invalid"*)
586				VAL=-2
587				;;
588		esac
589		[[ $EVAL != $VAL ]] && return 1
590	done
591	return 0
592}
593
594#
595# Get isolated (including offline) CPUs by looking at
596# /sys/kernel/debug/sched/domains and cpuset.cpus.isolated control file,
597# if available, and compare that with the expected value.
598#
599# Note that isolated CPUs from the sched/domains context include offline
600# CPUs as well as CPUs in non-isolated 1-CPU partition. Those CPUs may
601# not be included in the cpuset.cpus.isolated control file which contains
602# only CPUs in isolated partitions.
603#
604# $1 - expected isolated cpu list(s) <isolcpus1>{,<isolcpus2>}
605# <isolcpus1> - expected sched/domains value
606# <isolcpus2> - cpuset.cpus.isolated value = <isolcpus1> if not defined
607#
608check_isolcpus()
609{
610	EXPECT_VAL=$1
611	ISOLCPUS=
612	LASTISOLCPU=
613	SCHED_DOMAINS=/sys/kernel/debug/sched/domains
614	ISCPUS=${CGROUP2}/cpuset.cpus.isolated
615	if [[ $EXPECT_VAL = . ]]
616	then
617		EXPECT_VAL=
618		EXPECT_VAL2=
619	elif [[ $(expr $EXPECT_VAL : ".*,.*") > 0 ]]
620	then
621		set -- $(echo $EXPECT_VAL | sed -e "s/,/ /g")
622		EXPECT_VAL=$1
623		EXPECT_VAL2=$2
624	else
625		EXPECT_VAL2=$EXPECT_VAL
626	fi
627
628	#
629	# Check the debug isolated cpumask, if present
630	#
631	[[ -f $ISCPUS ]] && {
632		ISOLCPUS=$(cat $ISCPUS)
633		[[ "$EXPECT_VAL2" != "$ISOLCPUS" ]] && {
634			# Take a 50ms pause and try again
635			pause 0.05
636			ISOLCPUS=$(cat $ISCPUS)
637		}
638		[[ "$EXPECT_VAL2" != "$ISOLCPUS" ]] && return 1
639		ISOLCPUS=
640	}
641
642	#
643	# Use the sched domain in debugfs to check isolated CPUs, if available
644	#
645	[[ -d $SCHED_DOMAINS ]] || return 0
646
647	for ((CPU=0; CPU < $NR_CPUS; CPU++))
648	do
649		[[ -n "$(ls ${SCHED_DOMAINS}/cpu$CPU)" ]] && continue
650
651		if [[ -z "$LASTISOLCPU" ]]
652		then
653			ISOLCPUS=$CPU
654			LASTISOLCPU=$CPU
655		elif [[ "$LASTISOLCPU" -eq $((CPU - 1)) ]]
656		then
657			echo $ISOLCPUS | grep -q "\<$LASTISOLCPU\$"
658			if [[ $? -eq 0 ]]
659			then
660				ISOLCPUS=${ISOLCPUS}-
661			fi
662			LASTISOLCPU=$CPU
663		else
664			if [[ $ISOLCPUS = *- ]]
665			then
666				ISOLCPUS=${ISOLCPUS}$LASTISOLCPU
667			fi
668			ISOLCPUS=${ISOLCPUS},$CPU
669			LASTISOLCPU=$CPU
670		fi
671	done
672	[[ "$ISOLCPUS" = *- ]] && ISOLCPUS=${ISOLCPUS}$LASTISOLCPU
673	[[ "$EXPECT_VAL" = "$ISOLCPUS" ]]
674}
675
676test_fail()
677{
678	TESTNUM=$1
679	TESTTYPE=$2
680	ADDINFO=$3
681	echo "Test $TEST[$TESTNUM] failed $TESTTYPE check!"
682	[[ -n "$ADDINFO" ]] && echo "*** $ADDINFO ***"
683	eval echo \${$TEST[$I]}
684	echo
685	dump_states
686	exit 1
687}
688
689#
690# Check to see if there are unexpected isolated CPUs left
691#
692null_isolcpus_check()
693{
694	[[ $VERBOSE -gt 0 ]] || return 0
695	# Retry a few times before printing error
696	RETRY=0
697	while [[ $RETRY -lt 5 ]]
698	do
699		pause 0.01
700		check_isolcpus "."
701		[[ $? -eq 0 ]] && return 0
702		((RETRY++))
703	done
704	echo "Unexpected isolated CPUs: $ISOLCPUS"
705	dump_states
706	exit 1
707}
708
709#
710# Run cpuset state transition test
711#  $1 - test matrix name
712#
713# This test is somewhat fragile as delays (sleep x) are added in various
714# places to make sure state changes are fully propagated before the next
715# action. These delays may need to be adjusted if running in a slower machine.
716#
717run_state_test()
718{
719	TEST=$1
720	CONTROLLER=cpuset
721	I=0
722	eval CNT="\${#$TEST[@]}"
723
724	reset_cgroup_states
725	console_msg "Running state transition test ..."
726
727	while [[ $I -lt $CNT ]]
728	do
729		echo "Running test $I ..." > /dev/console
730		[[ $VERBOSE -gt 1 ]] && {
731			echo ""
732			eval echo \${$TEST[$I]}
733		}
734		eval set -- "\${$TEST[$I]}"
735		OLD_A1=$1
736		OLD_A2=$2
737		OLD_A3=$3
738		OLD_B1=$4
739		NEW_A1=$5
740		NEW_A2=$6
741		NEW_A3=$7
742		NEW_B1=$8
743		RESULT=$9
744		ECPUS=${10}
745		STATES=${11}
746		ICPUS=${12}
747
748		set_ctrl_state_noerr B1       $OLD_B1
749		set_ctrl_state_noerr A1       $OLD_A1
750		set_ctrl_state_noerr A1/A2    $OLD_A2
751		set_ctrl_state_noerr A1/A2/A3 $OLD_A3
752		RETVAL=0
753		set_ctrl_state A1       $NEW_A1; ((RETVAL += $?))
754		set_ctrl_state A1/A2    $NEW_A2; ((RETVAL += $?))
755		set_ctrl_state A1/A2/A3 $NEW_A3; ((RETVAL += $?))
756		set_ctrl_state B1       $NEW_B1; ((RETVAL += $?))
757
758		[[ $RETVAL -ne $RESULT ]] && test_fail $I result
759
760		[[ -n "$ECPUS" && "$ECPUS" != . ]] && {
761			check_effective_cpus $ECPUS
762			[[ $? -ne 0 ]] && test_fail $I "effective CPU"
763		}
764
765		[[ -n "$STATES" && "$STATES" != . ]] && {
766			check_cgroup_states $STATES
767			[[ $? -ne 0 ]] && test_fail $I states
768		}
769
770		# Compare the expected isolated CPUs with the actual ones,
771		# if available
772		[[ -n "$ICPUS" ]] && {
773			check_isolcpus $ICPUS
774			[[ $? -ne 0 ]] && test_fail $I "isolated CPU" \
775				"Expect $ICPUS, get $ISOLCPUS instead"
776		}
777		reset_cgroup_states
778		#
779		# Check to see if effective cpu list changes
780		#
781		NEWLIST=$(cat cpuset.cpus.effective)
782		RETRY=0
783		while [[ $NEWLIST != $CPULIST && $RETRY -lt 8 ]]
784		do
785			# Wait a bit longer & recheck a few times
786			pause 0.01
787			((RETRY++))
788			NEWLIST=$(cat cpuset.cpus.effective)
789		done
790		[[ $NEWLIST != $CPULIST ]] && {
791			echo "Effective cpus changed to $NEWLIST after test $I!"
792			exit 1
793		}
794		null_isolcpus_check
795		[[ $VERBOSE -gt 0 ]] && echo "Test $I done."
796		((I++))
797	done
798	echo "All $I tests of $TEST PASSED."
799}
800
801#
802# Testing the new "isolated" partition root type
803#
804test_isolated()
805{
806	cd $CGROUP2/test
807	echo 2-3 > cpuset.cpus
808	TYPE=$(cat cpuset.cpus.partition)
809	[[ $TYPE = member ]] || echo member > cpuset.cpus.partition
810
811	console_msg "Change from member to root"
812	test_partition root
813
814	console_msg "Change from root to isolated"
815	test_partition isolated
816
817	console_msg "Change from isolated to member"
818	test_partition member
819
820	console_msg "Change from member to isolated"
821	test_partition isolated
822
823	console_msg "Change from isolated to root"
824	test_partition root
825
826	console_msg "Change from root to member"
827	test_partition member
828
829	#
830	# Testing partition root with no cpu
831	#
832	console_msg "Distribute all cpus to child partition"
833	echo +cpuset > cgroup.subtree_control
834	test_partition root
835
836	mkdir A1
837	cd A1
838	echo 2-3 > cpuset.cpus
839	test_partition root
840	test_effective_cpus 2-3
841	cd ..
842	test_effective_cpus ""
843
844	console_msg "Moving task to partition test"
845	test_add_proc "No space left"
846	cd A1
847	test_add_proc ""
848	cd ..
849
850	console_msg "Shrink and expand child partition"
851	cd A1
852	echo 2 > cpuset.cpus
853	cd ..
854	test_effective_cpus 3
855	cd A1
856	echo 2-3 > cpuset.cpus
857	cd ..
858	test_effective_cpus ""
859
860	# Cleaning up
861	console_msg "Cleaning up"
862	echo $$ > $CGROUP2/cgroup.procs
863	[[ -d A1 ]] && rmdir A1
864	null_isolcpus_check
865}
866
867#
868# Wait for inotify event for the given file and read it
869# $1: cgroup file to wait for
870# $2: file to store the read result
871#
872wait_inotify()
873{
874	CGROUP_FILE=$1
875	OUTPUT_FILE=$2
876
877	$WAIT_INOTIFY $CGROUP_FILE
878	cat $CGROUP_FILE > $OUTPUT_FILE
879}
880
881#
882# Test if inotify events are properly generated when going into and out of
883# invalid partition state.
884#
885test_inotify()
886{
887	ERR=0
888	PRS=/tmp/.prs_$$
889	cd $CGROUP2/test
890	[[ -f $WAIT_INOTIFY ]] || {
891		echo "wait_inotify not found, inotify test SKIPPED."
892		return
893	}
894
895	pause 0.01
896	echo 1 > cpuset.cpus
897	echo 0 > cgroup.procs
898	echo root > cpuset.cpus.partition
899	pause 0.01
900	rm -f $PRS
901	wait_inotify $PWD/cpuset.cpus.partition $PRS &
902	pause 0.01
903	set_ctrl_state . "O1=0"
904	pause 0.01
905	check_cgroup_states ".:P-1"
906	if [[ $? -ne 0 ]]
907	then
908		echo "FAILED: Inotify test - partition not invalid"
909		ERR=1
910	elif [[ ! -f $PRS ]]
911	then
912		echo "FAILED: Inotify test - event not generated"
913		ERR=1
914		kill %1
915	elif [[ $(cat $PRS) != "root invalid"* ]]
916	then
917		echo "FAILED: Inotify test - incorrect state"
918		cat $PRS
919		ERR=1
920	fi
921	online_cpus
922	echo member > cpuset.cpus.partition
923	echo 0 > ../cgroup.procs
924	if [[ $ERR -ne 0 ]]
925	then
926		exit 1
927	else
928		echo "Inotify test PASSED"
929	fi
930}
931
932trap cleanup 0 2 3 6
933run_state_test TEST_MATRIX
934test_isolated
935test_inotify
936echo "All tests PASSED."
937