1#!/bin/bash
2# SPDX-License-Identifier: GPL-2.0
3#
4# Test the functionality of the Intel IFS(In Field Scan) driver.
5#
6
7# Matched with kselftest framework: tools/testing/selftests/kselftest.h
8readonly KSFT_PASS=0
9readonly KSFT_FAIL=1
10readonly KSFT_XFAIL=2
11readonly KSFT_SKIP=4
12
13readonly CPU_SYSFS="/sys/devices/system/cpu"
14readonly CPU_OFFLINE_SYSFS="${CPU_SYSFS}/offline"
15readonly IMG_PATH="/lib/firmware/intel/ifs_0"
16readonly IFS_SCAN_MODE="0"
17readonly IFS_ARRAY_BIST_SCAN_MODE="1"
18readonly IFS_PATH="/sys/devices/virtual/misc/intel_ifs"
19readonly IFS_SCAN_SYSFS_PATH="${IFS_PATH}_${IFS_SCAN_MODE}"
20readonly IFS_ARRAY_BIST_SYSFS_PATH="${IFS_PATH}_${IFS_ARRAY_BIST_SCAN_MODE}"
21readonly RUN_TEST="run_test"
22readonly STATUS="status"
23readonly DETAILS="details"
24readonly STATUS_PASS="pass"
25readonly PASS="PASS"
26readonly FAIL="FAIL"
27readonly INFO="INFO"
28readonly XFAIL="XFAIL"
29readonly SKIP="SKIP"
30readonly IFS_NAME="intel_ifs"
31readonly ALL="all"
32readonly SIBLINGS="siblings"
33
34# Matches arch/x86/include/asm/intel-family.h and
35# drivers/platform/x86/intel/ifs/core.c requirement as follows
36readonly SAPPHIRERAPIDS_X="8f"
37readonly EMERALDRAPIDS_X="cf"
38
39readonly INTEL_FAM6="06"
40
41LOOP_TIMES=3
42FML=""
43MODEL=""
44STEPPING=""
45CPU_FMS=""
46TRUE="true"
47FALSE="false"
48RESULT=$KSFT_PASS
49IMAGE_NAME=""
50INTERVAL_TIME=1
51OFFLINE_CPUS=""
52# For IFS cleanup tags
53ORIGIN_IFS_LOADED=""
54IFS_IMAGE_NEED_RESTORE=$FALSE
55IFS_LOG="/tmp/ifs_logs.$$"
56RANDOM_CPU=""
57DEFAULT_IMG_ID=""
58
59append_log()
60{
61	echo -e "$1" | tee -a "$IFS_LOG"
62}
63
64online_offline_cpu_list()
65{
66	local on_off=$1
67	local target_cpus=$2
68	local cpu=""
69	local cpu_start=""
70	local cpu_end=""
71	local i=""
72
73	if [[ -n "$target_cpus" ]]; then
74		for cpu in $(echo "$target_cpus" | tr ',' ' '); do
75			if [[ "$cpu" == *"-"* ]]; then
76				cpu_start=""
77				cpu_end=""
78				i=""
79				cpu_start=$(echo "$cpu" | cut -d "-" -f 1)
80				cpu_end=$(echo "$cpu" | cut -d "-" -f 2)
81				for((i=cpu_start;i<=cpu_end;i++)); do
82					append_log "[$INFO] echo $on_off > \
83${CPU_SYSFS}/cpu${i}/online"
84					echo "$on_off" > "$CPU_SYSFS"/cpu"$i"/online
85				done
86			else
87				set_target_cpu "$on_off" "$cpu"
88			fi
89		done
90	fi
91}
92
93ifs_scan_result_summary()
94{
95	local failed_info pass_num skip_num fail_num
96
97	if [[ -e "$IFS_LOG" ]]; then
98		failed_info=$(grep ^"\[${FAIL}\]" "$IFS_LOG")
99		fail_num=$(grep -c ^"\[${FAIL}\]" "$IFS_LOG")
100		skip_num=$(grep -c ^"\[${SKIP}\]" "$IFS_LOG")
101		pass_num=$(grep -c ^"\[${PASS}\]" "$IFS_LOG")
102
103		if [[ "$fail_num" -ne 0 ]]; then
104			RESULT=$KSFT_FAIL
105			echo "[$INFO] IFS test failure summary:"
106			echo "$failed_info"
107		elif [[ "$skip_num" -ne 0 ]]; then
108			RESULT=$KSFT_SKIP
109		fi
110			echo "[$INFO] IFS test pass:$pass_num, skip:$skip_num, fail:$fail_num"
111	else
112		echo "[$INFO] No file $IFS_LOG for IFS scan summary"
113	fi
114}
115
116ifs_cleanup()
117{
118	echo "[$INFO] Restore environment after IFS test"
119
120	# Restore ifs origin image if origin image backup step is needed
121	[[ "$IFS_IMAGE_NEED_RESTORE" == "$TRUE" ]] && {
122		mv -f "$IMG_PATH"/"$IMAGE_NAME"_origin "$IMG_PATH"/"$IMAGE_NAME"
123	}
124
125	# Restore the CPUs to the state before testing
126	[[ -z "$OFFLINE_CPUS" ]] || online_offline_cpu_list "0" "$OFFLINE_CPUS"
127
128	lsmod | grep -q "$IFS_NAME" && [[ "$ORIGIN_IFS_LOADED" == "$FALSE" ]] && {
129		echo "[$INFO] modprobe -r $IFS_NAME"
130		modprobe -r "$IFS_NAME"
131	}
132
133	ifs_scan_result_summary
134	[[ -e "$IFS_LOG" ]] && rm -rf "$IFS_LOG"
135
136	echo "[RESULT] IFS test exit with $RESULT"
137	exit "$RESULT"
138}
139
140do_cmd()
141{
142	local cmd=$*
143	local ret=""
144
145	append_log "[$INFO] $cmd"
146	eval "$cmd"
147	ret=$?
148	if [[ $ret -ne 0 ]]; then
149		append_log "[$FAIL] $cmd failed. Return code is $ret"
150		RESULT=$KSFT_XFAIL
151		ifs_cleanup
152	fi
153}
154
155test_exit()
156{
157	local info=$1
158	RESULT=$2
159
160	declare -A EXIT_MAP
161	EXIT_MAP[$KSFT_PASS]=$PASS
162	EXIT_MAP[$KSFT_FAIL]=$FAIL
163	EXIT_MAP[$KSFT_XFAIL]=$XFAIL
164	EXIT_MAP[$KSFT_SKIP]=$SKIP
165
166	append_log "[${EXIT_MAP[$RESULT]}] $info"
167	ifs_cleanup
168}
169
170online_all_cpus()
171{
172	local off_cpus=""
173
174	OFFLINE_CPUS=$(cat "$CPU_OFFLINE_SYSFS")
175	online_offline_cpu_list "1" "$OFFLINE_CPUS"
176
177	off_cpus=$(cat "$CPU_OFFLINE_SYSFS")
178	if [[ -z "$off_cpus" ]]; then
179		append_log "[$INFO] All CPUs are online."
180	else
181		append_log "[$XFAIL] There is offline cpu:$off_cpus after online all cpu!"
182		RESULT=$KSFT_XFAIL
183		ifs_cleanup
184	fi
185}
186
187get_cpu_fms()
188{
189	FML=$(grep -m 1 "family" /proc/cpuinfo | awk -F ":" '{printf "%02x",$2;}')
190	MODEL=$(grep -m 1 "model" /proc/cpuinfo | awk -F ":" '{printf "%02x",$2;}')
191	STEPPING=$(grep -m 1 "stepping" /proc/cpuinfo | awk -F ":" '{printf "%02x",$2;}')
192	CPU_FMS="${FML}-${MODEL}-${STEPPING}"
193}
194
195check_cpu_ifs_support_interval_time()
196{
197	get_cpu_fms
198
199	if [[ "$FML" != "$INTEL_FAM6" ]]; then
200		test_exit "CPU family:$FML does not support IFS" "$KSFT_SKIP"
201	fi
202
203	# Ucode has time interval requirement for IFS scan on same CPU as follows:
204	case $MODEL in
205		"$SAPPHIRERAPIDS_X")
206			INTERVAL_TIME=180;
207			;;
208		"$EMERALDRAPIDS_X")
209			INTERVAL_TIME=30;
210			;;
211		*)
212			# Set default interval time for other platforms
213			INTERVAL_TIME=1;
214			append_log "[$INFO] CPU FML:$FML model:0x$MODEL, default: 1s interval time"
215			;;
216	esac
217}
218
219check_ifs_loaded()
220{
221	local ifs_info=""
222
223	ifs_info=$(lsmod | grep "$IFS_NAME")
224	if [[ -z "$ifs_info" ]]; then
225		append_log "[$INFO] modprobe $IFS_NAME"
226		modprobe "$IFS_NAME" || {
227			test_exit "Check if CONFIG_INTEL_IFS is set to m or \
228platform doesn't support ifs" "$KSFT_SKIP"
229		}
230		ifs_info=$(lsmod | grep "$IFS_NAME")
231		[[ -n "$ifs_info" ]] || test_exit "No ifs module listed by lsmod" "$KSFT_FAIL"
232	fi
233}
234
235test_ifs_scan_entry()
236{
237	local ifs_info=""
238
239	ifs_info=$(lsmod | grep "$IFS_NAME")
240
241	if [[ -z "$ifs_info" ]]; then
242		ORIGIN_IFS_LOADED="$FALSE"
243		check_ifs_loaded
244	else
245		ORIGIN_IFS_LOADED="$TRUE"
246		append_log "[$INFO] Module $IFS_NAME is already loaded"
247	fi
248
249	if [[ -d "$IFS_SCAN_SYSFS_PATH" ]]; then
250		append_log "[$PASS] IFS sysfs $IFS_SCAN_SYSFS_PATH entry is created\n"
251	else
252		test_exit "No sysfs entry in $IFS_SCAN_SYSFS_PATH" "$KSFT_FAIL"
253	fi
254}
255
256load_image()
257{
258	local image_id=$1
259	local image_info=""
260	local ret=""
261
262	check_ifs_loaded
263	if [[ -e "${IMG_PATH}/${IMAGE_NAME}" ]]; then
264		append_log "[$INFO] echo 0x$image_id > ${IFS_SCAN_SYSFS_PATH}/current_batch"
265		echo "0x$image_id" > "$IFS_SCAN_SYSFS_PATH"/current_batch 2>/dev/null
266		ret=$?
267		[[ "$ret" -eq 0 ]] || {
268			append_log "[$FAIL] Load ifs image $image_id failed with ret:$ret\n"
269			return "$ret"
270		}
271		image_info=$(cat ${IFS_SCAN_SYSFS_PATH}/current_batch)
272		if [[ "$image_info" == 0x"$image_id" ]]; then
273			append_log "[$PASS] load IFS current_batch:$image_info"
274		else
275			append_log "[$FAIL] current_batch:$image_info is not expected:$image_id"
276			return "$KSFT_FAIL"
277		fi
278	else
279		append_log "[$FAIL] No IFS image file ${IMG_PATH}/${IMAGE_NAME}"\
280		return "$KSFT_FAIL"
281	fi
282	return 0
283}
284
285test_load_origin_ifs_image()
286{
287	local image_id=$1
288
289	IMAGE_NAME="${CPU_FMS}-${image_id}.scan"
290
291	load_image "$image_id" || return $?
292	return 0
293}
294
295test_load_bad_ifs_image()
296{
297	local image_id=$1
298
299	IMAGE_NAME="${CPU_FMS}-${image_id}.scan"
300
301	do_cmd "mv -f ${IMG_PATH}/${IMAGE_NAME} ${IMG_PATH}/${IMAGE_NAME}_origin"
302
303	# Set IFS_IMAGE_NEED_RESTORE to true before corrupt the origin ifs image file
304	IFS_IMAGE_NEED_RESTORE=$TRUE
305	do_cmd "dd if=/dev/urandom of=${IMG_PATH}/${IMAGE_NAME} bs=1K count=6 2>/dev/null"
306
307	# Use the specified judgment for negative testing
308	append_log "[$INFO] echo 0x$image_id > ${IFS_SCAN_SYSFS_PATH}/current_batch"
309	echo "0x$image_id" > "$IFS_SCAN_SYSFS_PATH"/current_batch 2>/dev/null
310	ret=$?
311	if [[ "$ret" -ne 0 ]]; then
312		append_log "[$PASS] Load invalid ifs image failed with ret:$ret not 0 as expected"
313	else
314		append_log "[$FAIL] Load invalid ifs image ret:$ret unexpectedly"
315	fi
316
317	do_cmd "mv -f ${IMG_PATH}/${IMAGE_NAME}_origin ${IMG_PATH}/${IMAGE_NAME}"
318	IFS_IMAGE_NEED_RESTORE=$FALSE
319}
320
321test_bad_and_origin_ifs_image()
322{
323	local image_id=$1
324
325	append_log "[$INFO] Test loading bad and then loading original IFS image:"
326	test_load_origin_ifs_image "$image_id" || return $?
327	test_load_bad_ifs_image "$image_id"
328	# Load origin image again and make sure it's worked
329	test_load_origin_ifs_image "$image_id" || return $?
330	append_log "[$INFO] Loading invalid IFS image and then loading initial image passed.\n"
331}
332
333ifs_test_cpu()
334{
335	local ifs_mode=$1
336	local cpu_num=$2
337	local image_id status details ret result result_info
338
339	echo "$cpu_num" > "$IFS_PATH"_"$ifs_mode"/"$RUN_TEST"
340	ret=$?
341
342	status=$(cat "${IFS_PATH}_${ifs_mode}/${STATUS}")
343	details=$(cat "${IFS_PATH}_${ifs_mode}/${DETAILS}")
344
345	if [[ "$ret" -eq 0 && "$status" == "$STATUS_PASS" ]]; then
346		result="$PASS"
347	else
348		result="$FAIL"
349	fi
350
351	cpu_num=$(cat "${CPU_SYSFS}/cpu${cpu_num}/topology/thread_siblings_list")
352
353	# There is no image file for IFS ARRAY BIST scan
354	if [[ -e "${IFS_PATH}_${ifs_mode}/current_batch" ]]; then
355		image_id=$(cat "${IFS_PATH}_${ifs_mode}/current_batch")
356		result_info=$(printf "[%s] ifs_%1d cpu(s):%s, current_batch:0x%02x, \
357ret:%2d, status:%s, details:0x%016x" \
358			     "$result" "$ifs_mode" "$cpu_num" "$image_id" "$ret" \
359			     "$status" "$details")
360	else
361		result_info=$(printf "[%s] ifs_%1d cpu(s):%s, ret:%2d, status:%s, details:0x%016x" \
362			     "$result" "$ifs_mode" "$cpu_num" "$ret" "$status" "$details")
363	fi
364
365	append_log "$result_info"
366}
367
368ifs_test_cpus()
369{
370	local cpus_type=$1
371	local ifs_mode=$2
372	local image_id=$3
373	local cpu_max_num=""
374	local cpu_num=""
375
376	case "$cpus_type" in
377		"$ALL")
378			cpu_max_num=$(($(nproc) - 1))
379			cpus=$(seq 0 $cpu_max_num)
380			;;
381		"$SIBLINGS")
382			cpus=$(cat ${CPU_SYSFS}/cpu*/topology/thread_siblings_list \
383				| sed -e 's/,.*//' \
384				| sed -e 's/-.*//' \
385				| sort -n \
386				| uniq)
387			;;
388		*)
389			test_exit "Invalid cpus_type:$cpus_type" "$KSFT_XFAIL"
390			;;
391	esac
392
393	for cpu_num in $cpus; do
394		ifs_test_cpu "$ifs_mode" "$cpu_num"
395	done
396
397	if [[ -z "$image_id" ]]; then
398		append_log "[$INFO] ifs_$ifs_mode test $cpus_type cpus completed\n"
399	else
400		append_log "[$INFO] ifs_$ifs_mode $cpus_type cpus with $CPU_FMS-$image_id.scan \
401completed\n"
402	fi
403}
404
405test_ifs_same_cpu_loop()
406{
407	local ifs_mode=$1
408	local cpu_num=$2
409	local loop_times=$3
410
411	append_log "[$INFO] Test ifs mode $ifs_mode on CPU:$cpu_num for $loop_times rounds:"
412	[[ "$ifs_mode" == "$IFS_SCAN_MODE" ]] && {
413		load_image "$DEFAULT_IMG_ID" ||	return $?
414	}
415	for (( i=1; i<=loop_times; i++ )); do
416		append_log "[$INFO] Loop iteration: $i in total of $loop_times"
417		# Only IFS scan needs the interval time
418		if [[ "$ifs_mode" == "$IFS_SCAN_MODE" ]]; then
419			do_cmd "sleep $INTERVAL_TIME"
420		elif [[ "$ifs_mode" == "$IFS_ARRAY_BIST_SCAN_MODE" ]]; then
421			true
422		else
423			test_exit "Invalid ifs_mode:$ifs_mode" "$KSFT_XFAIL"
424		fi
425
426		ifs_test_cpu "$ifs_mode" "$cpu_num"
427	done
428	append_log "[$INFO] $loop_times rounds of ifs_$ifs_mode test on CPU:$cpu_num completed.\n"
429}
430
431test_ifs_scan_available_imgs()
432{
433	local image_ids=""
434	local image_id=""
435
436	append_log "[$INFO] Test ifs scan with available images:"
437	image_ids=$(find "$IMG_PATH" -maxdepth 1 -name "${CPU_FMS}-[0-9a-fA-F][0-9a-fA-F].scan" \
438		    2>/dev/null \
439		    | sort \
440		    | awk -F "-" '{print $NF}' \
441		    | cut -d "." -f 1)
442
443	for image_id in $image_ids; do
444		load_image "$image_id" || return $?
445
446		ifs_test_cpus "$SIBLINGS" "$IFS_SCAN_MODE" "$image_id"
447		# IFS scan requires time interval for the scan on the same CPU
448		do_cmd "sleep $INTERVAL_TIME"
449	done
450}
451
452prepare_ifs_test_env()
453{
454	local max_cpu=""
455
456	check_cpu_ifs_support_interval_time
457
458	online_all_cpus
459	max_cpu=$(($(nproc) - 1))
460	RANDOM_CPU=$(shuf -i 0-$max_cpu -n 1)
461
462	DEFAULT_IMG_ID=$(find $IMG_PATH -maxdepth 1 -name "${CPU_FMS}-[0-9a-fA-F][0-9a-fA-F].scan" \
463			 2>/dev/null \
464			 | sort \
465			 | head -n 1 \
466			 | awk -F "-" '{print $NF}' \
467			 | cut -d "." -f 1)
468}
469
470test_ifs()
471{
472	prepare_ifs_test_env
473
474	test_ifs_scan_entry
475
476	if [[ -z "$DEFAULT_IMG_ID" ]]; then
477		append_log "[$SKIP] No proper ${IMG_PATH}/${CPU_FMS}-*.scan, skip ifs_0 scan"
478	else
479		test_bad_and_origin_ifs_image "$DEFAULT_IMG_ID"
480		test_ifs_scan_available_imgs
481		test_ifs_same_cpu_loop "$IFS_SCAN_MODE" "$RANDOM_CPU" "$LOOP_TIMES"
482	fi
483
484	if [[ -d "$IFS_ARRAY_BIST_SYSFS_PATH" ]]; then
485		ifs_test_cpus "$SIBLINGS" "$IFS_ARRAY_BIST_SCAN_MODE"
486		test_ifs_same_cpu_loop "$IFS_ARRAY_BIST_SCAN_MODE" "$RANDOM_CPU" "$LOOP_TIMES"
487	else
488		append_log "[$SKIP] No $IFS_ARRAY_BIST_SYSFS_PATH, skip IFS ARRAY BIST scan"
489	fi
490}
491
492trap ifs_cleanup SIGTERM SIGINT
493test_ifs
494ifs_cleanup
495