linux_kselftest-next-6.11-rc1
This kselftest next update for Linux 6.11-rc1 consists of: -- changes to resctrl test to cleanup resctrl_val() and generalize it by removing test name specific handling from the function. -- several clang build failure fixes to framework and tests -- adds tests to verify IFS (In Field Scan) driver functionality -- cleanups to remove unused variables and document changes -----BEGIN PGP SIGNATURE----- iQIzBAABCgAdFiEEPZKym/RZuOCGeA/kCwJExA0NQxwFAmaWzrUACgkQCwJExA0N Qxx4UQ//VCkYyI/R6CUDSWU0+mUk4SxoMqwjw1cpqC/QkW3u03AyD7sjNyUtZT57 JaOHlbC1drgGDUOtLLiVNaZRXNBhJEmr0+917BtyxIO3iRaMR3FOw8mQ1w8BiMHq 46L2+/wk7xZrSFhj1/qjzwelKVTC+I+CwG8xkmBqcBSs41DyqldHkeddEf47/ijQ qYt6RyNTTKZMQrTN/KhjtdyMk3KBigE3UwrVUuYEFT6Nlvc0fKX+2XfAlS8CdZu1 EDyq7HBPMgr/UhZt+Gvo62+T/9HBiBckVw3EYdM/WbAK45rDmTPXrL32lYEvia0q NXjWpFLuIc1CjTEMdP1dLS1yuZlngKKco2odbPOTE9EUVpS9y9IvmzdNxMTX59mZ AkpSkEx5PKrHHuTrN+GRxfvEYnrzYbjLvgXO2StSFuuR/huZaC7juPVcBQwXFwvM ekciAMxt8TG0UMeEQ3+3U4HysFz6Ra7qgLv3aBfe6tbw3IMFP6b1kHoMLlbuFdwl /A+z6Ty5rePqc/8WSCCQgwvloUeif2jzDwUXCOXWoQuQpGGnNHcumMktKIKrVzav Zi5s32qGSqwUdvZQaSLUGL/AXjC6EzgEaMBdR6Ve+7jL+DB0ug5mkCZe8ukGuEgG rr+sHZ5y4Eu5u0KInGKSS1b9ou37GErDVfmCUgNxrisDCr6F2hQ= =3vp1 -----END PGP SIGNATURE----- Merge tag 'linux_kselftest-next-6.11-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/shuah/linux-kselftest Pull kselftest updates from Shuah Khan: - change resctrl test to cleanup resctrl_val() and generalize it by removing test name specific handling from the function. - several clang build failure fixes to framework and tests - add tests to verify IFS (In Field Scan) driver functionality - cleanups to remove unused variables and document changes * tag 'linux_kselftest-next-6.11-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/shuah/linux-kselftest: (33 commits) selftests: ifs: verify IFS ARRAY BIST functionality selftests: ifs: verify IFS scan test functionality selftests: ifs: verify test image loading functionality selftests: ifs: verify test interfaces are created by the driver selftests/dma:remove unused variable selftests/breakpoints:Remove unused variable selftests/x86: fix printk warnings reported by clang selftests/x86: remove (or use) unused variables and functions selftests/x86: avoid -no-pie warnings from clang during compilation selftests/x86: build sysret_rip.c with clang selftests/x86: build fsgsbase_restore.c with clang selftests: x86: test_FISTTP: use fisttps instead of ambiguous fisttp selftests/x86: fix Makefile dependencies to work with clang selftests/timers: remove unused irqcount variable selftests: Add information about TAP conformance in tests selftests/resctrl: Remove test name comparing from write_bm_pid_to_resctrl() selftests/resctrl: Remove mongrp from CMT test selftests/resctrl: Remove mongrp from MBA test selftests/resctrl: Convert ctrlgrp & mongrp to pointers selftests/resctrl: Make some strings passed to resctrlfs functions const ...
This commit is contained in:
commit
0434dbe320
30 changed files with 901 additions and 358 deletions
|
@ -228,6 +228,13 @@ In general, the rules for selftests are
|
|||
* Don't cause the top-level "make run_tests" to fail if your feature is
|
||||
unconfigured.
|
||||
|
||||
* The output of tests must conform to the TAP standard to ensure high
|
||||
testing quality and to capture failures/errors with specific details.
|
||||
The kselftest.h and kselftest_harness.h headers provide wrappers for
|
||||
outputting test results. These wrappers should be used for pass,
|
||||
fail, exit, and skip messages. CI systems can easily parse TAP output
|
||||
messages to detect test results.
|
||||
|
||||
Contributing new tests (details)
|
||||
================================
|
||||
|
||||
|
|
|
@ -11216,6 +11216,7 @@ R: Tony Luck <tony.luck@intel.com>
|
|||
S: Maintained
|
||||
F: drivers/platform/x86/intel/ifs
|
||||
F: include/trace/events/intel_ifs.h
|
||||
F: tools/testing/selftests/drivers/platform/x86/intel/ifs/
|
||||
|
||||
INTEL INTEGRATED SENSOR HUB DRIVER
|
||||
M: Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com>
|
||||
|
|
|
@ -21,6 +21,7 @@ TARGETS += drivers/net
|
|||
TARGETS += drivers/net/bonding
|
||||
TARGETS += drivers/net/team
|
||||
TARGETS += drivers/net/virtio_net
|
||||
TARGETS += drivers/platform/x86/intel/ifs
|
||||
TARGETS += dt
|
||||
TARGETS += efivarfs
|
||||
TARGETS += exec
|
||||
|
|
|
@ -130,7 +130,6 @@ int run_test(int cpu)
|
|||
void suspend(void)
|
||||
{
|
||||
int power_state_fd;
|
||||
struct sigevent event = {};
|
||||
int timerfd;
|
||||
int err;
|
||||
struct itimerspec spec = {};
|
||||
|
|
|
@ -33,7 +33,6 @@ int main(int argc, char **argv)
|
|||
int granule = 1;
|
||||
|
||||
int cmd = DMA_MAP_BENCHMARK;
|
||||
char *p;
|
||||
|
||||
while ((opt = getopt(argc, argv, "t:s:n:b:d:x:g:")) != -1) {
|
||||
switch (opt) {
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
# SPDX-License-Identifier: GPL-2.0
|
||||
# Makefile for ifs(In Field Scan) selftests
|
||||
|
||||
TEST_PROGS := test_ifs.sh
|
||||
|
||||
include ../../../../../lib.mk
|
494
tools/testing/selftests/drivers/platform/x86/intel/ifs/test_ifs.sh
Executable file
494
tools/testing/selftests/drivers/platform/x86/intel/ifs/test_ifs.sh
Executable file
|
@ -0,0 +1,494 @@
|
|||
#!/bin/bash
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
#
|
||||
# Test the functionality of the Intel IFS(In Field Scan) driver.
|
||||
#
|
||||
|
||||
# Matched with kselftest framework: tools/testing/selftests/kselftest.h
|
||||
readonly KSFT_PASS=0
|
||||
readonly KSFT_FAIL=1
|
||||
readonly KSFT_XFAIL=2
|
||||
readonly KSFT_SKIP=4
|
||||
|
||||
readonly CPU_SYSFS="/sys/devices/system/cpu"
|
||||
readonly CPU_OFFLINE_SYSFS="${CPU_SYSFS}/offline"
|
||||
readonly IMG_PATH="/lib/firmware/intel/ifs_0"
|
||||
readonly IFS_SCAN_MODE="0"
|
||||
readonly IFS_ARRAY_BIST_SCAN_MODE="1"
|
||||
readonly IFS_PATH="/sys/devices/virtual/misc/intel_ifs"
|
||||
readonly IFS_SCAN_SYSFS_PATH="${IFS_PATH}_${IFS_SCAN_MODE}"
|
||||
readonly IFS_ARRAY_BIST_SYSFS_PATH="${IFS_PATH}_${IFS_ARRAY_BIST_SCAN_MODE}"
|
||||
readonly RUN_TEST="run_test"
|
||||
readonly STATUS="status"
|
||||
readonly DETAILS="details"
|
||||
readonly STATUS_PASS="pass"
|
||||
readonly PASS="PASS"
|
||||
readonly FAIL="FAIL"
|
||||
readonly INFO="INFO"
|
||||
readonly XFAIL="XFAIL"
|
||||
readonly SKIP="SKIP"
|
||||
readonly IFS_NAME="intel_ifs"
|
||||
readonly ALL="all"
|
||||
readonly SIBLINGS="siblings"
|
||||
|
||||
# Matches arch/x86/include/asm/intel-family.h and
|
||||
# drivers/platform/x86/intel/ifs/core.c requirement as follows
|
||||
readonly SAPPHIRERAPIDS_X="8f"
|
||||
readonly EMERALDRAPIDS_X="cf"
|
||||
|
||||
readonly INTEL_FAM6="06"
|
||||
|
||||
LOOP_TIMES=3
|
||||
FML=""
|
||||
MODEL=""
|
||||
STEPPING=""
|
||||
CPU_FMS=""
|
||||
TRUE="true"
|
||||
FALSE="false"
|
||||
RESULT=$KSFT_PASS
|
||||
IMAGE_NAME=""
|
||||
INTERVAL_TIME=1
|
||||
OFFLINE_CPUS=""
|
||||
# For IFS cleanup tags
|
||||
ORIGIN_IFS_LOADED=""
|
||||
IFS_IMAGE_NEED_RESTORE=$FALSE
|
||||
IFS_LOG="/tmp/ifs_logs.$$"
|
||||
RANDOM_CPU=""
|
||||
DEFAULT_IMG_ID=""
|
||||
|
||||
append_log()
|
||||
{
|
||||
echo -e "$1" | tee -a "$IFS_LOG"
|
||||
}
|
||||
|
||||
online_offline_cpu_list()
|
||||
{
|
||||
local on_off=$1
|
||||
local target_cpus=$2
|
||||
local cpu=""
|
||||
local cpu_start=""
|
||||
local cpu_end=""
|
||||
local i=""
|
||||
|
||||
if [[ -n "$target_cpus" ]]; then
|
||||
for cpu in $(echo "$target_cpus" | tr ',' ' '); do
|
||||
if [[ "$cpu" == *"-"* ]]; then
|
||||
cpu_start=""
|
||||
cpu_end=""
|
||||
i=""
|
||||
cpu_start=$(echo "$cpu" | cut -d "-" -f 1)
|
||||
cpu_end=$(echo "$cpu" | cut -d "-" -f 2)
|
||||
for((i=cpu_start;i<=cpu_end;i++)); do
|
||||
append_log "[$INFO] echo $on_off > \
|
||||
${CPU_SYSFS}/cpu${i}/online"
|
||||
echo "$on_off" > "$CPU_SYSFS"/cpu"$i"/online
|
||||
done
|
||||
else
|
||||
set_target_cpu "$on_off" "$cpu"
|
||||
fi
|
||||
done
|
||||
fi
|
||||
}
|
||||
|
||||
ifs_scan_result_summary()
|
||||
{
|
||||
local failed_info pass_num skip_num fail_num
|
||||
|
||||
if [[ -e "$IFS_LOG" ]]; then
|
||||
failed_info=$(grep ^"\[${FAIL}\]" "$IFS_LOG")
|
||||
fail_num=$(grep -c ^"\[${FAIL}\]" "$IFS_LOG")
|
||||
skip_num=$(grep -c ^"\[${SKIP}\]" "$IFS_LOG")
|
||||
pass_num=$(grep -c ^"\[${PASS}\]" "$IFS_LOG")
|
||||
|
||||
if [[ "$fail_num" -ne 0 ]]; then
|
||||
RESULT=$KSFT_FAIL
|
||||
echo "[$INFO] IFS test failure summary:"
|
||||
echo "$failed_info"
|
||||
elif [[ "$skip_num" -ne 0 ]]; then
|
||||
RESULT=$KSFT_SKIP
|
||||
fi
|
||||
echo "[$INFO] IFS test pass:$pass_num, skip:$skip_num, fail:$fail_num"
|
||||
else
|
||||
echo "[$INFO] No file $IFS_LOG for IFS scan summary"
|
||||
fi
|
||||
}
|
||||
|
||||
ifs_cleanup()
|
||||
{
|
||||
echo "[$INFO] Restore environment after IFS test"
|
||||
|
||||
# Restore ifs origin image if origin image backup step is needed
|
||||
[[ "$IFS_IMAGE_NEED_RESTORE" == "$TRUE" ]] && {
|
||||
mv -f "$IMG_PATH"/"$IMAGE_NAME"_origin "$IMG_PATH"/"$IMAGE_NAME"
|
||||
}
|
||||
|
||||
# Restore the CPUs to the state before testing
|
||||
[[ -z "$OFFLINE_CPUS" ]] || online_offline_cpu_list "0" "$OFFLINE_CPUS"
|
||||
|
||||
lsmod | grep -q "$IFS_NAME" && [[ "$ORIGIN_IFS_LOADED" == "$FALSE" ]] && {
|
||||
echo "[$INFO] modprobe -r $IFS_NAME"
|
||||
modprobe -r "$IFS_NAME"
|
||||
}
|
||||
|
||||
ifs_scan_result_summary
|
||||
[[ -e "$IFS_LOG" ]] && rm -rf "$IFS_LOG"
|
||||
|
||||
echo "[RESULT] IFS test exit with $RESULT"
|
||||
exit "$RESULT"
|
||||
}
|
||||
|
||||
do_cmd()
|
||||
{
|
||||
local cmd=$*
|
||||
local ret=""
|
||||
|
||||
append_log "[$INFO] $cmd"
|
||||
eval "$cmd"
|
||||
ret=$?
|
||||
if [[ $ret -ne 0 ]]; then
|
||||
append_log "[$FAIL] $cmd failed. Return code is $ret"
|
||||
RESULT=$KSFT_XFAIL
|
||||
ifs_cleanup
|
||||
fi
|
||||
}
|
||||
|
||||
test_exit()
|
||||
{
|
||||
local info=$1
|
||||
RESULT=$2
|
||||
|
||||
declare -A EXIT_MAP
|
||||
EXIT_MAP[$KSFT_PASS]=$PASS
|
||||
EXIT_MAP[$KSFT_FAIL]=$FAIL
|
||||
EXIT_MAP[$KSFT_XFAIL]=$XFAIL
|
||||
EXIT_MAP[$KSFT_SKIP]=$SKIP
|
||||
|
||||
append_log "[${EXIT_MAP[$RESULT]}] $info"
|
||||
ifs_cleanup
|
||||
}
|
||||
|
||||
online_all_cpus()
|
||||
{
|
||||
local off_cpus=""
|
||||
|
||||
OFFLINE_CPUS=$(cat "$CPU_OFFLINE_SYSFS")
|
||||
online_offline_cpu_list "1" "$OFFLINE_CPUS"
|
||||
|
||||
off_cpus=$(cat "$CPU_OFFLINE_SYSFS")
|
||||
if [[ -z "$off_cpus" ]]; then
|
||||
append_log "[$INFO] All CPUs are online."
|
||||
else
|
||||
append_log "[$XFAIL] There is offline cpu:$off_cpus after online all cpu!"
|
||||
RESULT=$KSFT_XFAIL
|
||||
ifs_cleanup
|
||||
fi
|
||||
}
|
||||
|
||||
get_cpu_fms()
|
||||
{
|
||||
FML=$(grep -m 1 "family" /proc/cpuinfo | awk -F ":" '{printf "%02x",$2;}')
|
||||
MODEL=$(grep -m 1 "model" /proc/cpuinfo | awk -F ":" '{printf "%02x",$2;}')
|
||||
STEPPING=$(grep -m 1 "stepping" /proc/cpuinfo | awk -F ":" '{printf "%02x",$2;}')
|
||||
CPU_FMS="${FML}-${MODEL}-${STEPPING}"
|
||||
}
|
||||
|
||||
check_cpu_ifs_support_interval_time()
|
||||
{
|
||||
get_cpu_fms
|
||||
|
||||
if [[ "$FML" != "$INTEL_FAM6" ]]; then
|
||||
test_exit "CPU family:$FML does not support IFS" "$KSFT_SKIP"
|
||||
fi
|
||||
|
||||
# Ucode has time interval requirement for IFS scan on same CPU as follows:
|
||||
case $MODEL in
|
||||
"$SAPPHIRERAPIDS_X")
|
||||
INTERVAL_TIME=180;
|
||||
;;
|
||||
"$EMERALDRAPIDS_X")
|
||||
INTERVAL_TIME=30;
|
||||
;;
|
||||
*)
|
||||
# Set default interval time for other platforms
|
||||
INTERVAL_TIME=1;
|
||||
append_log "[$INFO] CPU FML:$FML model:0x$MODEL, default: 1s interval time"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
check_ifs_loaded()
|
||||
{
|
||||
local ifs_info=""
|
||||
|
||||
ifs_info=$(lsmod | grep "$IFS_NAME")
|
||||
if [[ -z "$ifs_info" ]]; then
|
||||
append_log "[$INFO] modprobe $IFS_NAME"
|
||||
modprobe "$IFS_NAME" || {
|
||||
test_exit "Check if CONFIG_INTEL_IFS is set to m or \
|
||||
platform doesn't support ifs" "$KSFT_SKIP"
|
||||
}
|
||||
ifs_info=$(lsmod | grep "$IFS_NAME")
|
||||
[[ -n "$ifs_info" ]] || test_exit "No ifs module listed by lsmod" "$KSFT_FAIL"
|
||||
fi
|
||||
}
|
||||
|
||||
test_ifs_scan_entry()
|
||||
{
|
||||
local ifs_info=""
|
||||
|
||||
ifs_info=$(lsmod | grep "$IFS_NAME")
|
||||
|
||||
if [[ -z "$ifs_info" ]]; then
|
||||
ORIGIN_IFS_LOADED="$FALSE"
|
||||
check_ifs_loaded
|
||||
else
|
||||
ORIGIN_IFS_LOADED="$TRUE"
|
||||
append_log "[$INFO] Module $IFS_NAME is already loaded"
|
||||
fi
|
||||
|
||||
if [[ -d "$IFS_SCAN_SYSFS_PATH" ]]; then
|
||||
append_log "[$PASS] IFS sysfs $IFS_SCAN_SYSFS_PATH entry is created\n"
|
||||
else
|
||||
test_exit "No sysfs entry in $IFS_SCAN_SYSFS_PATH" "$KSFT_FAIL"
|
||||
fi
|
||||
}
|
||||
|
||||
load_image()
|
||||
{
|
||||
local image_id=$1
|
||||
local image_info=""
|
||||
local ret=""
|
||||
|
||||
check_ifs_loaded
|
||||
if [[ -e "${IMG_PATH}/${IMAGE_NAME}" ]]; then
|
||||
append_log "[$INFO] echo 0x$image_id > ${IFS_SCAN_SYSFS_PATH}/current_batch"
|
||||
echo "0x$image_id" > "$IFS_SCAN_SYSFS_PATH"/current_batch 2>/dev/null
|
||||
ret=$?
|
||||
[[ "$ret" -eq 0 ]] || {
|
||||
append_log "[$FAIL] Load ifs image $image_id failed with ret:$ret\n"
|
||||
return "$ret"
|
||||
}
|
||||
image_info=$(cat ${IFS_SCAN_SYSFS_PATH}/current_batch)
|
||||
if [[ "$image_info" == 0x"$image_id" ]]; then
|
||||
append_log "[$PASS] load IFS current_batch:$image_info"
|
||||
else
|
||||
append_log "[$FAIL] current_batch:$image_info is not expected:$image_id"
|
||||
return "$KSFT_FAIL"
|
||||
fi
|
||||
else
|
||||
append_log "[$FAIL] No IFS image file ${IMG_PATH}/${IMAGE_NAME}"\
|
||||
return "$KSFT_FAIL"
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
test_load_origin_ifs_image()
|
||||
{
|
||||
local image_id=$1
|
||||
|
||||
IMAGE_NAME="${CPU_FMS}-${image_id}.scan"
|
||||
|
||||
load_image "$image_id" || return $?
|
||||
return 0
|
||||
}
|
||||
|
||||
test_load_bad_ifs_image()
|
||||
{
|
||||
local image_id=$1
|
||||
|
||||
IMAGE_NAME="${CPU_FMS}-${image_id}.scan"
|
||||
|
||||
do_cmd "mv -f ${IMG_PATH}/${IMAGE_NAME} ${IMG_PATH}/${IMAGE_NAME}_origin"
|
||||
|
||||
# Set IFS_IMAGE_NEED_RESTORE to true before corrupt the origin ifs image file
|
||||
IFS_IMAGE_NEED_RESTORE=$TRUE
|
||||
do_cmd "dd if=/dev/urandom of=${IMG_PATH}/${IMAGE_NAME} bs=1K count=6 2>/dev/null"
|
||||
|
||||
# Use the specified judgment for negative testing
|
||||
append_log "[$INFO] echo 0x$image_id > ${IFS_SCAN_SYSFS_PATH}/current_batch"
|
||||
echo "0x$image_id" > "$IFS_SCAN_SYSFS_PATH"/current_batch 2>/dev/null
|
||||
ret=$?
|
||||
if [[ "$ret" -ne 0 ]]; then
|
||||
append_log "[$PASS] Load invalid ifs image failed with ret:$ret not 0 as expected"
|
||||
else
|
||||
append_log "[$FAIL] Load invalid ifs image ret:$ret unexpectedly"
|
||||
fi
|
||||
|
||||
do_cmd "mv -f ${IMG_PATH}/${IMAGE_NAME}_origin ${IMG_PATH}/${IMAGE_NAME}"
|
||||
IFS_IMAGE_NEED_RESTORE=$FALSE
|
||||
}
|
||||
|
||||
test_bad_and_origin_ifs_image()
|
||||
{
|
||||
local image_id=$1
|
||||
|
||||
append_log "[$INFO] Test loading bad and then loading original IFS image:"
|
||||
test_load_origin_ifs_image "$image_id" || return $?
|
||||
test_load_bad_ifs_image "$image_id"
|
||||
# Load origin image again and make sure it's worked
|
||||
test_load_origin_ifs_image "$image_id" || return $?
|
||||
append_log "[$INFO] Loading invalid IFS image and then loading initial image passed.\n"
|
||||
}
|
||||
|
||||
ifs_test_cpu()
|
||||
{
|
||||
local ifs_mode=$1
|
||||
local cpu_num=$2
|
||||
local image_id status details ret result result_info
|
||||
|
||||
echo "$cpu_num" > "$IFS_PATH"_"$ifs_mode"/"$RUN_TEST"
|
||||
ret=$?
|
||||
|
||||
status=$(cat "${IFS_PATH}_${ifs_mode}/${STATUS}")
|
||||
details=$(cat "${IFS_PATH}_${ifs_mode}/${DETAILS}")
|
||||
|
||||
if [[ "$ret" -eq 0 && "$status" == "$STATUS_PASS" ]]; then
|
||||
result="$PASS"
|
||||
else
|
||||
result="$FAIL"
|
||||
fi
|
||||
|
||||
cpu_num=$(cat "${CPU_SYSFS}/cpu${cpu_num}/topology/thread_siblings_list")
|
||||
|
||||
# There is no image file for IFS ARRAY BIST scan
|
||||
if [[ -e "${IFS_PATH}_${ifs_mode}/current_batch" ]]; then
|
||||
image_id=$(cat "${IFS_PATH}_${ifs_mode}/current_batch")
|
||||
result_info=$(printf "[%s] ifs_%1d cpu(s):%s, current_batch:0x%02x, \
|
||||
ret:%2d, status:%s, details:0x%016x" \
|
||||
"$result" "$ifs_mode" "$cpu_num" "$image_id" "$ret" \
|
||||
"$status" "$details")
|
||||
else
|
||||
result_info=$(printf "[%s] ifs_%1d cpu(s):%s, ret:%2d, status:%s, details:0x%016x" \
|
||||
"$result" "$ifs_mode" "$cpu_num" "$ret" "$status" "$details")
|
||||
fi
|
||||
|
||||
append_log "$result_info"
|
||||
}
|
||||
|
||||
ifs_test_cpus()
|
||||
{
|
||||
local cpus_type=$1
|
||||
local ifs_mode=$2
|
||||
local image_id=$3
|
||||
local cpu_max_num=""
|
||||
local cpu_num=""
|
||||
|
||||
case "$cpus_type" in
|
||||
"$ALL")
|
||||
cpu_max_num=$(($(nproc) - 1))
|
||||
cpus=$(seq 0 $cpu_max_num)
|
||||
;;
|
||||
"$SIBLINGS")
|
||||
cpus=$(cat ${CPU_SYSFS}/cpu*/topology/thread_siblings_list \
|
||||
| sed -e 's/,.*//' \
|
||||
| sed -e 's/-.*//' \
|
||||
| sort -n \
|
||||
| uniq)
|
||||
;;
|
||||
*)
|
||||
test_exit "Invalid cpus_type:$cpus_type" "$KSFT_XFAIL"
|
||||
;;
|
||||
esac
|
||||
|
||||
for cpu_num in $cpus; do
|
||||
ifs_test_cpu "$ifs_mode" "$cpu_num"
|
||||
done
|
||||
|
||||
if [[ -z "$image_id" ]]; then
|
||||
append_log "[$INFO] ifs_$ifs_mode test $cpus_type cpus completed\n"
|
||||
else
|
||||
append_log "[$INFO] ifs_$ifs_mode $cpus_type cpus with $CPU_FMS-$image_id.scan \
|
||||
completed\n"
|
||||
fi
|
||||
}
|
||||
|
||||
test_ifs_same_cpu_loop()
|
||||
{
|
||||
local ifs_mode=$1
|
||||
local cpu_num=$2
|
||||
local loop_times=$3
|
||||
|
||||
append_log "[$INFO] Test ifs mode $ifs_mode on CPU:$cpu_num for $loop_times rounds:"
|
||||
[[ "$ifs_mode" == "$IFS_SCAN_MODE" ]] && {
|
||||
load_image "$DEFAULT_IMG_ID" || return $?
|
||||
}
|
||||
for (( i=1; i<=loop_times; i++ )); do
|
||||
append_log "[$INFO] Loop iteration: $i in total of $loop_times"
|
||||
# Only IFS scan needs the interval time
|
||||
if [[ "$ifs_mode" == "$IFS_SCAN_MODE" ]]; then
|
||||
do_cmd "sleep $INTERVAL_TIME"
|
||||
elif [[ "$ifs_mode" == "$IFS_ARRAY_BIST_SCAN_MODE" ]]; then
|
||||
true
|
||||
else
|
||||
test_exit "Invalid ifs_mode:$ifs_mode" "$KSFT_XFAIL"
|
||||
fi
|
||||
|
||||
ifs_test_cpu "$ifs_mode" "$cpu_num"
|
||||
done
|
||||
append_log "[$INFO] $loop_times rounds of ifs_$ifs_mode test on CPU:$cpu_num completed.\n"
|
||||
}
|
||||
|
||||
test_ifs_scan_available_imgs()
|
||||
{
|
||||
local image_ids=""
|
||||
local image_id=""
|
||||
|
||||
append_log "[$INFO] Test ifs scan with available images:"
|
||||
image_ids=$(find "$IMG_PATH" -maxdepth 1 -name "${CPU_FMS}-[0-9a-fA-F][0-9a-fA-F].scan" \
|
||||
2>/dev/null \
|
||||
| sort \
|
||||
| awk -F "-" '{print $NF}' \
|
||||
| cut -d "." -f 1)
|
||||
|
||||
for image_id in $image_ids; do
|
||||
load_image "$image_id" || return $?
|
||||
|
||||
ifs_test_cpus "$SIBLINGS" "$IFS_SCAN_MODE" "$image_id"
|
||||
# IFS scan requires time interval for the scan on the same CPU
|
||||
do_cmd "sleep $INTERVAL_TIME"
|
||||
done
|
||||
}
|
||||
|
||||
prepare_ifs_test_env()
|
||||
{
|
||||
local max_cpu=""
|
||||
|
||||
check_cpu_ifs_support_interval_time
|
||||
|
||||
online_all_cpus
|
||||
max_cpu=$(($(nproc) - 1))
|
||||
RANDOM_CPU=$(shuf -i 0-$max_cpu -n 1)
|
||||
|
||||
DEFAULT_IMG_ID=$(find $IMG_PATH -maxdepth 1 -name "${CPU_FMS}-[0-9a-fA-F][0-9a-fA-F].scan" \
|
||||
2>/dev/null \
|
||||
| sort \
|
||||
| head -n 1 \
|
||||
| awk -F "-" '{print $NF}' \
|
||||
| cut -d "." -f 1)
|
||||
}
|
||||
|
||||
test_ifs()
|
||||
{
|
||||
prepare_ifs_test_env
|
||||
|
||||
test_ifs_scan_entry
|
||||
|
||||
if [[ -z "$DEFAULT_IMG_ID" ]]; then
|
||||
append_log "[$SKIP] No proper ${IMG_PATH}/${CPU_FMS}-*.scan, skip ifs_0 scan"
|
||||
else
|
||||
test_bad_and_origin_ifs_image "$DEFAULT_IMG_ID"
|
||||
test_ifs_scan_available_imgs
|
||||
test_ifs_same_cpu_loop "$IFS_SCAN_MODE" "$RANDOM_CPU" "$LOOP_TIMES"
|
||||
fi
|
||||
|
||||
if [[ -d "$IFS_ARRAY_BIST_SYSFS_PATH" ]]; then
|
||||
ifs_test_cpus "$SIBLINGS" "$IFS_ARRAY_BIST_SCAN_MODE"
|
||||
test_ifs_same_cpu_loop "$IFS_ARRAY_BIST_SCAN_MODE" "$RANDOM_CPU" "$LOOP_TIMES"
|
||||
else
|
||||
append_log "[$SKIP] No $IFS_ARRAY_BIST_SYSFS_PATH, skip IFS ARRAY BIST scan"
|
||||
fi
|
||||
}
|
||||
|
||||
trap ifs_cleanup SIGTERM SIGINT
|
||||
test_ifs
|
||||
ifs_cleanup
|
|
@ -38,6 +38,14 @@ else
|
|||
CLANG_FLAGS += --target=$(notdir $(CROSS_COMPILE:%-=%))
|
||||
endif # CROSS_COMPILE
|
||||
|
||||
# gcc defaults to silence (off) for the following warnings, but clang defaults
|
||||
# to the opposite. The warnings are not useful for the kernel itself, which is
|
||||
# why they have remained disabled in gcc for the main kernel build. And it is
|
||||
# only due to including kernel data structures in the selftests, that we get the
|
||||
# warnings from clang. Therefore, disable the warnings for clang builds.
|
||||
CFLAGS += -Wno-address-of-packed-member
|
||||
CFLAGS += -Wno-gnu-variable-sized-type-not-at-end
|
||||
|
||||
CC := $(CLANG) $(CLANG_FLAGS) -fintegrated-as
|
||||
else
|
||||
CC := $(CROSS_COMPILE)gcc
|
||||
|
|
|
@ -101,12 +101,12 @@ static int get_llc_occu_resctrl(unsigned long *llc_occupancy)
|
|||
*
|
||||
* Return: 0 on success, < 0 on error.
|
||||
*/
|
||||
static int print_results_cache(const char *filename, int bm_pid, __u64 llc_value)
|
||||
static int print_results_cache(const char *filename, pid_t bm_pid, __u64 llc_value)
|
||||
{
|
||||
FILE *fp;
|
||||
|
||||
if (strcmp(filename, "stdio") == 0 || strcmp(filename, "stderr") == 0) {
|
||||
printf("Pid: %d \t LLC_value: %llu\n", bm_pid, llc_value);
|
||||
printf("Pid: %d \t LLC_value: %llu\n", (int)bm_pid, llc_value);
|
||||
} else {
|
||||
fp = fopen(filename, "a");
|
||||
if (!fp) {
|
||||
|
@ -114,7 +114,7 @@ static int print_results_cache(const char *filename, int bm_pid, __u64 llc_value
|
|||
|
||||
return -1;
|
||||
}
|
||||
fprintf(fp, "Pid: %d \t llc_value: %llu\n", bm_pid, llc_value);
|
||||
fprintf(fp, "Pid: %d \t llc_value: %llu\n", (int)bm_pid, llc_value);
|
||||
fclose(fp);
|
||||
}
|
||||
|
||||
|
@ -133,7 +133,7 @@ static int print_results_cache(const char *filename, int bm_pid, __u64 llc_value
|
|||
* Return: =0 on success. <0 on failure.
|
||||
*/
|
||||
int perf_event_measure(int pe_fd, struct perf_event_read *pe_read,
|
||||
const char *filename, int bm_pid)
|
||||
const char *filename, pid_t bm_pid)
|
||||
{
|
||||
int ret;
|
||||
|
||||
|
@ -161,7 +161,7 @@ int perf_event_measure(int pe_fd, struct perf_event_read *pe_read,
|
|||
*
|
||||
* Return: =0 on success. <0 on failure.
|
||||
*/
|
||||
int measure_llc_resctrl(const char *filename, int bm_pid)
|
||||
int measure_llc_resctrl(const char *filename, pid_t bm_pid)
|
||||
{
|
||||
unsigned long llc_occu_resc = 0;
|
||||
int ret;
|
||||
|
|
|
@ -158,7 +158,6 @@ static int cat_test(const struct resctrl_test *test,
|
|||
struct resctrl_val_param *param,
|
||||
size_t span, unsigned long current_mask)
|
||||
{
|
||||
char *resctrl_val = param->resctrl_val;
|
||||
struct perf_event_read pe_read;
|
||||
struct perf_event_attr pea;
|
||||
cpu_set_t old_affinity;
|
||||
|
@ -178,8 +177,7 @@ static int cat_test(const struct resctrl_test *test,
|
|||
return ret;
|
||||
|
||||
/* Write benchmark to specified con_mon grp, mon_grp in resctrl FS*/
|
||||
ret = write_bm_pid_to_resctrl(bm_pid, param->ctrlgrp, param->mongrp,
|
||||
resctrl_val);
|
||||
ret = write_bm_pid_to_resctrl(bm_pid, param->ctrlgrp, param->mongrp);
|
||||
if (ret)
|
||||
goto reset_affinity;
|
||||
|
||||
|
@ -272,7 +270,6 @@ static int cat_run_test(const struct resctrl_test *test, const struct user_param
|
|||
start_mask = create_bit_mask(start, n);
|
||||
|
||||
struct resctrl_val_param param = {
|
||||
.resctrl_val = CAT_STR,
|
||||
.ctrlgrp = "c1",
|
||||
.filename = RESULT_FILE_NAME,
|
||||
.num_of_runs = 0,
|
||||
|
|
|
@ -16,6 +16,17 @@
|
|||
#define MAX_DIFF 2000000
|
||||
#define MAX_DIFF_PERCENT 15
|
||||
|
||||
#define CON_MON_LCC_OCCUP_PATH \
|
||||
"%s/%s/mon_data/mon_L3_%02d/llc_occupancy"
|
||||
|
||||
static int cmt_init(const struct resctrl_val_param *param, int domain_id)
|
||||
{
|
||||
sprintf(llc_occup_path, CON_MON_LCC_OCCUP_PATH, RESCTRL_PATH,
|
||||
param->ctrlgrp, domain_id);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cmt_setup(const struct resctrl_test *test,
|
||||
const struct user_params *uparams,
|
||||
struct resctrl_val_param *p)
|
||||
|
@ -29,6 +40,13 @@ static int cmt_setup(const struct resctrl_test *test,
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int cmt_measure(const struct user_params *uparams,
|
||||
struct resctrl_val_param *param, pid_t bm_pid)
|
||||
{
|
||||
sleep(1);
|
||||
return measure_llc_resctrl(param->filename, bm_pid);
|
||||
}
|
||||
|
||||
static int show_results_info(unsigned long sum_llc_val, int no_of_bits,
|
||||
unsigned long cache_span, unsigned long max_diff,
|
||||
unsigned long max_diff_percent, unsigned long num_of_runs,
|
||||
|
@ -126,13 +144,13 @@ static int cmt_run_test(const struct resctrl_test *test, const struct user_param
|
|||
}
|
||||
|
||||
struct resctrl_val_param param = {
|
||||
.resctrl_val = CMT_STR,
|
||||
.ctrlgrp = "c1",
|
||||
.mongrp = "m1",
|
||||
.filename = RESULT_FILE_NAME,
|
||||
.mask = ~(long_mask << n) & long_mask,
|
||||
.num_of_runs = 0,
|
||||
.init = cmt_init,
|
||||
.setup = cmt_setup,
|
||||
.measure = cmt_measure,
|
||||
};
|
||||
|
||||
span = cache_portion_size(cache_total_size, param.mask, long_mask);
|
||||
|
|
|
@ -17,6 +17,19 @@
|
|||
#define ALLOCATION_MIN 10
|
||||
#define ALLOCATION_STEP 10
|
||||
|
||||
static int mba_init(const struct resctrl_val_param *param, int domain_id)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = initialize_mem_bw_imc();
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
initialize_mem_bw_resctrl(param, domain_id);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Change schemata percentage from 100 to 10%. Write schemata to specified
|
||||
* con_mon grp, mon_grp in resctrl FS.
|
||||
|
@ -51,6 +64,12 @@ static int mba_setup(const struct resctrl_test *test,
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int mba_measure(const struct user_params *uparams,
|
||||
struct resctrl_val_param *param, pid_t bm_pid)
|
||||
{
|
||||
return measure_mem_bw(uparams, param, bm_pid, "reads");
|
||||
}
|
||||
|
||||
static bool show_mba_info(unsigned long *bw_imc, unsigned long *bw_resc)
|
||||
{
|
||||
int allocation, runs;
|
||||
|
@ -145,12 +164,11 @@ static void mba_test_cleanup(void)
|
|||
static int mba_run_test(const struct resctrl_test *test, const struct user_params *uparams)
|
||||
{
|
||||
struct resctrl_val_param param = {
|
||||
.resctrl_val = MBA_STR,
|
||||
.ctrlgrp = "c1",
|
||||
.mongrp = "m1",
|
||||
.filename = RESULT_FILE_NAME,
|
||||
.bw_report = "reads",
|
||||
.setup = mba_setup
|
||||
.init = mba_init,
|
||||
.setup = mba_setup,
|
||||
.measure = mba_measure,
|
||||
};
|
||||
int ret;
|
||||
|
||||
|
|
|
@ -86,6 +86,19 @@ static int check_results(size_t span)
|
|||
return ret;
|
||||
}
|
||||
|
||||
static int mbm_init(const struct resctrl_val_param *param, int domain_id)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = initialize_mem_bw_imc();
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
initialize_mem_bw_resctrl(param, domain_id);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mbm_setup(const struct resctrl_test *test,
|
||||
const struct user_params *uparams,
|
||||
struct resctrl_val_param *p)
|
||||
|
@ -105,6 +118,12 @@ static int mbm_setup(const struct resctrl_test *test,
|
|||
return ret;
|
||||
}
|
||||
|
||||
static int mbm_measure(const struct user_params *uparams,
|
||||
struct resctrl_val_param *param, pid_t bm_pid)
|
||||
{
|
||||
return measure_mem_bw(uparams, param, bm_pid, "reads");
|
||||
}
|
||||
|
||||
static void mbm_test_cleanup(void)
|
||||
{
|
||||
remove(RESULT_FILE_NAME);
|
||||
|
@ -113,12 +132,11 @@ static void mbm_test_cleanup(void)
|
|||
static int mbm_run_test(const struct resctrl_test *test, const struct user_params *uparams)
|
||||
{
|
||||
struct resctrl_val_param param = {
|
||||
.resctrl_val = MBM_STR,
|
||||
.ctrlgrp = "c1",
|
||||
.mongrp = "m1",
|
||||
.filename = RESULT_FILE_NAME,
|
||||
.bw_report = "reads",
|
||||
.setup = mbm_setup
|
||||
.init = mbm_init,
|
||||
.setup = mbm_setup,
|
||||
.measure = mbm_measure,
|
||||
};
|
||||
int ret;
|
||||
|
||||
|
|
|
@ -43,13 +43,6 @@
|
|||
|
||||
#define DEFAULT_SPAN (250 * MB)
|
||||
|
||||
#define PARENT_EXIT() \
|
||||
do { \
|
||||
kill(ppid, SIGKILL); \
|
||||
umount_resctrlfs(); \
|
||||
exit(EXIT_FAILURE); \
|
||||
} while (0)
|
||||
|
||||
/*
|
||||
* user_params: User supplied parameters
|
||||
* @cpu: CPU number to which the benchmark will be bound to
|
||||
|
@ -88,24 +81,27 @@ struct resctrl_test {
|
|||
|
||||
/*
|
||||
* resctrl_val_param: resctrl test parameters
|
||||
* @resctrl_val: Resctrl feature (Eg: mbm, mba.. etc)
|
||||
* @ctrlgrp: Name of the control monitor group (con_mon grp)
|
||||
* @mongrp: Name of the monitor group (mon grp)
|
||||
* @filename: Name of file to which the o/p should be written
|
||||
* @bw_report: Bandwidth report type (reads vs writes)
|
||||
* @setup: Call back function to setup test environment
|
||||
* @init: Callback function to initialize test environment
|
||||
* @setup: Callback function to setup per test run environment
|
||||
* @measure: Callback that performs the measurement (a single test)
|
||||
*/
|
||||
struct resctrl_val_param {
|
||||
char *resctrl_val;
|
||||
char ctrlgrp[64];
|
||||
char mongrp[64];
|
||||
const char *ctrlgrp;
|
||||
const char *mongrp;
|
||||
char filename[64];
|
||||
char *bw_report;
|
||||
unsigned long mask;
|
||||
int num_of_runs;
|
||||
int (*init)(const struct resctrl_val_param *param,
|
||||
int domain_id);
|
||||
int (*setup)(const struct resctrl_test *test,
|
||||
const struct user_params *uparams,
|
||||
struct resctrl_val_param *param);
|
||||
int (*measure)(const struct user_params *uparams,
|
||||
struct resctrl_val_param *param,
|
||||
pid_t bm_pid);
|
||||
};
|
||||
|
||||
struct perf_event_read {
|
||||
|
@ -115,11 +111,6 @@ struct perf_event_read {
|
|||
} values[2];
|
||||
};
|
||||
|
||||
#define MBM_STR "mbm"
|
||||
#define MBA_STR "mba"
|
||||
#define CMT_STR "cmt"
|
||||
#define CAT_STR "cat"
|
||||
|
||||
/*
|
||||
* Memory location that consumes values compiler must not optimize away.
|
||||
* Volatile ensures writes to this location cannot be optimized away by
|
||||
|
@ -127,8 +118,6 @@ struct perf_event_read {
|
|||
*/
|
||||
extern volatile int *value_sink;
|
||||
|
||||
extern pid_t bm_pid, ppid;
|
||||
|
||||
extern char llc_occup_path[1024];
|
||||
|
||||
int get_vendor(void);
|
||||
|
@ -137,7 +126,7 @@ int filter_dmesg(void);
|
|||
int get_domain_id(const char *resource, int cpu_no, int *domain_id);
|
||||
int mount_resctrlfs(void);
|
||||
int umount_resctrlfs(void);
|
||||
int validate_bw_report_request(char *bw_report);
|
||||
const char *get_bw_report_type(const char *bw_report);
|
||||
bool resctrl_resource_exists(const char *resource);
|
||||
bool resctrl_mon_feature_exists(const char *resource, const char *feature);
|
||||
bool resource_info_file_exists(const char *resource, const char *file);
|
||||
|
@ -145,15 +134,21 @@ bool test_resource_feature_check(const struct resctrl_test *test);
|
|||
char *fgrep(FILE *inf, const char *str);
|
||||
int taskset_benchmark(pid_t bm_pid, int cpu_no, cpu_set_t *old_affinity);
|
||||
int taskset_restore(pid_t bm_pid, cpu_set_t *old_affinity);
|
||||
int write_schemata(char *ctrlgrp, char *schemata, int cpu_no, const char *resource);
|
||||
int write_bm_pid_to_resctrl(pid_t bm_pid, char *ctrlgrp, char *mongrp,
|
||||
char *resctrl_val);
|
||||
int write_schemata(const char *ctrlgrp, char *schemata, int cpu_no,
|
||||
const char *resource);
|
||||
int write_bm_pid_to_resctrl(pid_t bm_pid, const char *ctrlgrp, const char *mongrp);
|
||||
int perf_event_open(struct perf_event_attr *hw_event, pid_t pid, int cpu,
|
||||
int group_fd, unsigned long flags);
|
||||
unsigned char *alloc_buffer(size_t buf_size, int memflush);
|
||||
void mem_flush(unsigned char *buf, size_t buf_size);
|
||||
void fill_cache_read(unsigned char *buf, size_t buf_size, bool once);
|
||||
int run_fill_buf(size_t buf_size, int memflush, int op, bool once);
|
||||
int initialize_mem_bw_imc(void);
|
||||
int measure_mem_bw(const struct user_params *uparams,
|
||||
struct resctrl_val_param *param, pid_t bm_pid,
|
||||
const char *bw_report);
|
||||
void initialize_mem_bw_resctrl(const struct resctrl_val_param *param,
|
||||
int domain_id);
|
||||
int resctrl_val(const struct resctrl_test *test,
|
||||
const struct user_params *uparams,
|
||||
const char * const *benchmark_cmd,
|
||||
|
@ -174,8 +169,8 @@ void perf_event_initialize_read_format(struct perf_event_read *pe_read);
|
|||
int perf_open(struct perf_event_attr *pea, pid_t pid, int cpu_no);
|
||||
int perf_event_reset_enable(int pe_fd);
|
||||
int perf_event_measure(int pe_fd, struct perf_event_read *pe_read,
|
||||
const char *filename, int bm_pid);
|
||||
int measure_llc_resctrl(const char *filename, int bm_pid);
|
||||
const char *filename, pid_t bm_pid);
|
||||
int measure_llc_resctrl(const char *filename, pid_t bm_pid);
|
||||
void show_cache_info(int no_of_bits, __u64 avg_llc_val, size_t cache_span, bool lines);
|
||||
|
||||
/*
|
||||
|
|
|
@ -19,30 +19,10 @@
|
|||
#define MAX_TOKENS 5
|
||||
#define READ 0
|
||||
#define WRITE 1
|
||||
#define CON_MON_MBM_LOCAL_BYTES_PATH \
|
||||
"%s/%s/mon_groups/%s/mon_data/mon_L3_%02d/mbm_local_bytes"
|
||||
|
||||
#define CON_MBM_LOCAL_BYTES_PATH \
|
||||
"%s/%s/mon_data/mon_L3_%02d/mbm_local_bytes"
|
||||
|
||||
#define MON_MBM_LOCAL_BYTES_PATH \
|
||||
"%s/mon_groups/%s/mon_data/mon_L3_%02d/mbm_local_bytes"
|
||||
|
||||
#define MBM_LOCAL_BYTES_PATH \
|
||||
"%s/mon_data/mon_L3_%02d/mbm_local_bytes"
|
||||
|
||||
#define CON_MON_LCC_OCCUP_PATH \
|
||||
"%s/%s/mon_groups/%s/mon_data/mon_L3_%02d/llc_occupancy"
|
||||
|
||||
#define CON_LCC_OCCUP_PATH \
|
||||
"%s/%s/mon_data/mon_L3_%02d/llc_occupancy"
|
||||
|
||||
#define MON_LCC_OCCUP_PATH \
|
||||
"%s/mon_groups/%s/mon_data/mon_L3_%02d/llc_occupancy"
|
||||
|
||||
#define LCC_OCCUP_PATH \
|
||||
"%s/mon_data/mon_L3_%02d/llc_occupancy"
|
||||
|
||||
struct membw_read_format {
|
||||
__u64 value; /* The value of the event */
|
||||
__u64 time_enabled; /* if PERF_FORMAT_TOTAL_TIME_ENABLED */
|
||||
|
@ -276,7 +256,7 @@ static int num_of_imcs(void)
|
|||
return count;
|
||||
}
|
||||
|
||||
static int initialize_mem_bw_imc(void)
|
||||
int initialize_mem_bw_imc(void)
|
||||
{
|
||||
int imc, j;
|
||||
|
||||
|
@ -293,44 +273,93 @@ static int initialize_mem_bw_imc(void)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static void perf_close_imc_mem_bw(void)
|
||||
{
|
||||
int mc;
|
||||
|
||||
for (mc = 0; mc < imcs; mc++) {
|
||||
if (imc_counters_config[mc][READ].fd != -1)
|
||||
close(imc_counters_config[mc][READ].fd);
|
||||
if (imc_counters_config[mc][WRITE].fd != -1)
|
||||
close(imc_counters_config[mc][WRITE].fd);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* get_mem_bw_imc: Memory band width as reported by iMC counters
|
||||
* @cpu_no: CPU number that the benchmark PID is binded to
|
||||
* @bw_report: Bandwidth report type (reads, writes)
|
||||
*
|
||||
* Memory B/W utilized by a process on a socket can be calculated using
|
||||
* iMC counters. Perf events are used to read these counters.
|
||||
* perf_open_imc_mem_bw - Open perf fds for IMCs
|
||||
* @cpu_no: CPU number that the benchmark PID is bound to
|
||||
*
|
||||
* Return: = 0 on success. < 0 on failure.
|
||||
*/
|
||||
static int get_mem_bw_imc(int cpu_no, char *bw_report, float *bw_imc)
|
||||
static int perf_open_imc_mem_bw(int cpu_no)
|
||||
{
|
||||
float reads, writes, of_mul_read, of_mul_write;
|
||||
int imc, j, ret;
|
||||
int imc, ret;
|
||||
|
||||
/* Start all iMC counters to log values (both read and write) */
|
||||
reads = 0, writes = 0, of_mul_read = 1, of_mul_write = 1;
|
||||
for (imc = 0; imc < imcs; imc++) {
|
||||
for (j = 0; j < 2; j++) {
|
||||
ret = open_perf_event(imc, cpu_no, j);
|
||||
if (ret)
|
||||
return -1;
|
||||
}
|
||||
for (j = 0; j < 2; j++)
|
||||
membw_ioctl_perf_event_ioc_reset_enable(imc, j);
|
||||
imc_counters_config[imc][READ].fd = -1;
|
||||
imc_counters_config[imc][WRITE].fd = -1;
|
||||
}
|
||||
|
||||
for (imc = 0; imc < imcs; imc++) {
|
||||
ret = open_perf_event(imc, cpu_no, READ);
|
||||
if (ret)
|
||||
goto close_fds;
|
||||
ret = open_perf_event(imc, cpu_no, WRITE);
|
||||
if (ret)
|
||||
goto close_fds;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
close_fds:
|
||||
perf_close_imc_mem_bw();
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*
|
||||
* do_mem_bw_test - Perform memory bandwidth test
|
||||
*
|
||||
* Runs memory bandwidth test over one second period. Also, handles starting
|
||||
* and stopping of the IMC perf counters around the test.
|
||||
*/
|
||||
static void do_imc_mem_bw_test(void)
|
||||
{
|
||||
int imc;
|
||||
|
||||
for (imc = 0; imc < imcs; imc++) {
|
||||
membw_ioctl_perf_event_ioc_reset_enable(imc, READ);
|
||||
membw_ioctl_perf_event_ioc_reset_enable(imc, WRITE);
|
||||
}
|
||||
|
||||
sleep(1);
|
||||
|
||||
/* Stop counters after a second to get results (both read and write) */
|
||||
for (imc = 0; imc < imcs; imc++) {
|
||||
for (j = 0; j < 2; j++)
|
||||
membw_ioctl_perf_event_ioc_disable(imc, j);
|
||||
membw_ioctl_perf_event_ioc_disable(imc, READ);
|
||||
membw_ioctl_perf_event_ioc_disable(imc, WRITE);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* get_mem_bw_imc - Memory bandwidth as reported by iMC counters
|
||||
* @bw_report: Bandwidth report type (reads, writes)
|
||||
*
|
||||
* Memory bandwidth utilized by a process on a socket can be calculated
|
||||
* using iMC counters. Perf events are used to read these counters.
|
||||
*
|
||||
* Return: = 0 on success. < 0 on failure.
|
||||
*/
|
||||
static int get_mem_bw_imc(const char *bw_report, float *bw_imc)
|
||||
{
|
||||
float reads, writes, of_mul_read, of_mul_write;
|
||||
int imc;
|
||||
|
||||
/* Start all iMC counters to log values (both read and write) */
|
||||
reads = 0, writes = 0, of_mul_read = 1, of_mul_write = 1;
|
||||
|
||||
/*
|
||||
* Get results which are stored in struct type imc_counter_config
|
||||
* Take over flow into consideration before calculating total b/w
|
||||
* Take overflow into consideration before calculating total bandwidth.
|
||||
*/
|
||||
for (imc = 0; imc < imcs; imc++) {
|
||||
struct imc_counter_config *r =
|
||||
|
@ -340,15 +369,13 @@ static int get_mem_bw_imc(int cpu_no, char *bw_report, float *bw_imc)
|
|||
|
||||
if (read(r->fd, &r->return_value,
|
||||
sizeof(struct membw_read_format)) == -1) {
|
||||
ksft_perror("Couldn't get read b/w through iMC");
|
||||
|
||||
ksft_perror("Couldn't get read bandwidth through iMC");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (read(w->fd, &w->return_value,
|
||||
sizeof(struct membw_read_format)) == -1) {
|
||||
ksft_perror("Couldn't get write bw through iMC");
|
||||
|
||||
ksft_perror("Couldn't get write bandwidth through iMC");
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
@ -369,11 +396,6 @@ static int get_mem_bw_imc(int cpu_no, char *bw_report, float *bw_imc)
|
|||
writes += w->return_value.value * of_mul_write * SCALE;
|
||||
}
|
||||
|
||||
for (imc = 0; imc < imcs; imc++) {
|
||||
close(imc_counters_config[imc][READ].fd);
|
||||
close(imc_counters_config[imc][WRITE].fd);
|
||||
}
|
||||
|
||||
if (strcmp(bw_report, "reads") == 0) {
|
||||
*bw_imc = reads;
|
||||
return 0;
|
||||
|
@ -388,84 +410,45 @@ static int get_mem_bw_imc(int cpu_no, char *bw_report, float *bw_imc)
|
|||
return 0;
|
||||
}
|
||||
|
||||
void set_mbm_path(const char *ctrlgrp, const char *mongrp, int domain_id)
|
||||
/*
|
||||
* initialize_mem_bw_resctrl: Appropriately populate "mbm_total_path"
|
||||
* @param: Parameters passed to resctrl_val()
|
||||
* @domain_id: Domain ID (cache ID; for MB, L3 cache ID)
|
||||
*/
|
||||
void initialize_mem_bw_resctrl(const struct resctrl_val_param *param,
|
||||
int domain_id)
|
||||
{
|
||||
if (ctrlgrp && mongrp)
|
||||
sprintf(mbm_total_path, CON_MON_MBM_LOCAL_BYTES_PATH,
|
||||
RESCTRL_PATH, ctrlgrp, mongrp, domain_id);
|
||||
else if (!ctrlgrp && mongrp)
|
||||
sprintf(mbm_total_path, MON_MBM_LOCAL_BYTES_PATH, RESCTRL_PATH,
|
||||
mongrp, domain_id);
|
||||
else if (ctrlgrp && !mongrp)
|
||||
sprintf(mbm_total_path, CON_MBM_LOCAL_BYTES_PATH, RESCTRL_PATH,
|
||||
ctrlgrp, domain_id);
|
||||
else if (!ctrlgrp && !mongrp)
|
||||
sprintf(mbm_total_path, MBM_LOCAL_BYTES_PATH, RESCTRL_PATH,
|
||||
domain_id);
|
||||
sprintf(mbm_total_path, CON_MBM_LOCAL_BYTES_PATH, RESCTRL_PATH,
|
||||
param->ctrlgrp, domain_id);
|
||||
}
|
||||
|
||||
/*
|
||||
* initialize_mem_bw_resctrl: Appropriately populate "mbm_total_path"
|
||||
* @ctrlgrp: Name of the control monitor group (con_mon grp)
|
||||
* @mongrp: Name of the monitor group (mon grp)
|
||||
* @cpu_no: CPU number that the benchmark PID is binded to
|
||||
* @resctrl_val: Resctrl feature (Eg: mbm, mba.. etc)
|
||||
* Open file to read MBM local bytes from resctrl FS
|
||||
*/
|
||||
static void initialize_mem_bw_resctrl(const char *ctrlgrp, const char *mongrp,
|
||||
int cpu_no, char *resctrl_val)
|
||||
static FILE *open_mem_bw_resctrl(const char *mbm_bw_file)
|
||||
{
|
||||
int domain_id;
|
||||
FILE *fp;
|
||||
|
||||
if (get_domain_id("MB", cpu_no, &domain_id) < 0) {
|
||||
ksft_print_msg("Could not get domain ID\n");
|
||||
return;
|
||||
}
|
||||
fp = fopen(mbm_bw_file, "r");
|
||||
if (!fp)
|
||||
ksft_perror("Failed to open total memory bandwidth file");
|
||||
|
||||
if (!strncmp(resctrl_val, MBM_STR, sizeof(MBM_STR)))
|
||||
set_mbm_path(ctrlgrp, mongrp, domain_id);
|
||||
|
||||
if (!strncmp(resctrl_val, MBA_STR, sizeof(MBA_STR))) {
|
||||
if (ctrlgrp)
|
||||
sprintf(mbm_total_path, CON_MBM_LOCAL_BYTES_PATH,
|
||||
RESCTRL_PATH, ctrlgrp, domain_id);
|
||||
else
|
||||
sprintf(mbm_total_path, MBM_LOCAL_BYTES_PATH,
|
||||
RESCTRL_PATH, domain_id);
|
||||
}
|
||||
return fp;
|
||||
}
|
||||
|
||||
/*
|
||||
* Get MBM Local bytes as reported by resctrl FS
|
||||
* For MBM,
|
||||
* 1. If con_mon grp and mon grp are given, then read from con_mon grp's mon grp
|
||||
* 2. If only con_mon grp is given, then read from con_mon grp
|
||||
* 3. If both are not given, then read from root con_mon grp
|
||||
* For MBA,
|
||||
* 1. If con_mon grp is given, then read from it
|
||||
* 2. If con_mon grp is not given, then read from root con_mon grp
|
||||
*/
|
||||
static int get_mem_bw_resctrl(unsigned long *mbm_total)
|
||||
static int get_mem_bw_resctrl(FILE *fp, unsigned long *mbm_total)
|
||||
{
|
||||
FILE *fp;
|
||||
|
||||
fp = fopen(mbm_total_path, "r");
|
||||
if (!fp) {
|
||||
ksft_perror("Failed to open total bw file");
|
||||
|
||||
if (fscanf(fp, "%lu\n", mbm_total) <= 0) {
|
||||
ksft_perror("Could not get MBM local bytes");
|
||||
return -1;
|
||||
}
|
||||
if (fscanf(fp, "%lu", mbm_total) <= 0) {
|
||||
ksft_perror("Could not get mbm local bytes");
|
||||
fclose(fp);
|
||||
|
||||
return -1;
|
||||
}
|
||||
fclose(fp);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
pid_t bm_pid, ppid;
|
||||
static pid_t bm_pid, ppid;
|
||||
|
||||
void ctrlc_handler(int signum, siginfo_t *info, void *ptr)
|
||||
{
|
||||
|
@ -523,6 +506,13 @@ void signal_handler_unregister(void)
|
|||
}
|
||||
}
|
||||
|
||||
static void parent_exit(pid_t ppid)
|
||||
{
|
||||
kill(ppid, SIGKILL);
|
||||
umount_resctrlfs();
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
/*
|
||||
* print_results_bw: the memory bandwidth results are stored in a file
|
||||
* @filename: file that stores the results
|
||||
|
@ -532,14 +522,14 @@ void signal_handler_unregister(void)
|
|||
*
|
||||
* Return: 0 on success, < 0 on error.
|
||||
*/
|
||||
static int print_results_bw(char *filename, int bm_pid, float bw_imc,
|
||||
static int print_results_bw(char *filename, pid_t bm_pid, float bw_imc,
|
||||
unsigned long bw_resc)
|
||||
{
|
||||
unsigned long diff = fabs(bw_imc - bw_resc);
|
||||
FILE *fp;
|
||||
|
||||
if (strcmp(filename, "stdio") == 0 || strcmp(filename, "stderr") == 0) {
|
||||
printf("Pid: %d \t Mem_BW_iMC: %f \t ", bm_pid, bw_imc);
|
||||
printf("Pid: %d \t Mem_BW_iMC: %f \t ", (int)bm_pid, bw_imc);
|
||||
printf("Mem_BW_resc: %lu \t Difference: %lu\n", bw_resc, diff);
|
||||
} else {
|
||||
fp = fopen(filename, "a");
|
||||
|
@ -549,7 +539,7 @@ static int print_results_bw(char *filename, int bm_pid, float bw_imc,
|
|||
return -1;
|
||||
}
|
||||
if (fprintf(fp, "Pid: %d \t Mem_BW_iMC: %f \t Mem_BW_resc: %lu \t Difference: %lu\n",
|
||||
bm_pid, bw_imc, bw_resc, diff) <= 0) {
|
||||
(int)bm_pid, bw_imc, bw_resc, diff) <= 0) {
|
||||
ksft_print_msg("Could not log results\n");
|
||||
fclose(fp);
|
||||
|
||||
|
@ -561,73 +551,67 @@ static int print_results_bw(char *filename, int bm_pid, float bw_imc,
|
|||
return 0;
|
||||
}
|
||||
|
||||
static void set_cmt_path(const char *ctrlgrp, const char *mongrp, char sock_num)
|
||||
{
|
||||
if (strlen(ctrlgrp) && strlen(mongrp))
|
||||
sprintf(llc_occup_path, CON_MON_LCC_OCCUP_PATH, RESCTRL_PATH,
|
||||
ctrlgrp, mongrp, sock_num);
|
||||
else if (!strlen(ctrlgrp) && strlen(mongrp))
|
||||
sprintf(llc_occup_path, MON_LCC_OCCUP_PATH, RESCTRL_PATH,
|
||||
mongrp, sock_num);
|
||||
else if (strlen(ctrlgrp) && !strlen(mongrp))
|
||||
sprintf(llc_occup_path, CON_LCC_OCCUP_PATH, RESCTRL_PATH,
|
||||
ctrlgrp, sock_num);
|
||||
else if (!strlen(ctrlgrp) && !strlen(mongrp))
|
||||
sprintf(llc_occup_path, LCC_OCCUP_PATH, RESCTRL_PATH, sock_num);
|
||||
}
|
||||
|
||||
/*
|
||||
* initialize_llc_occu_resctrl: Appropriately populate "llc_occup_path"
|
||||
* @ctrlgrp: Name of the control monitor group (con_mon grp)
|
||||
* @mongrp: Name of the monitor group (mon grp)
|
||||
* @cpu_no: CPU number that the benchmark PID is binded to
|
||||
* @resctrl_val: Resctrl feature (Eg: cat, cmt.. etc)
|
||||
* measure_mem_bw - Measures memory bandwidth numbers while benchmark runs
|
||||
* @uparams: User supplied parameters
|
||||
* @param: Parameters passed to resctrl_val()
|
||||
* @bm_pid: PID that runs the benchmark
|
||||
* @bw_report: Bandwidth report type (reads, writes)
|
||||
*
|
||||
* Measure memory bandwidth from resctrl and from another source which is
|
||||
* perf imc value or could be something else if perf imc event is not
|
||||
* available. Compare the two values to validate resctrl value. It takes
|
||||
* 1 sec to measure the data.
|
||||
*/
|
||||
static void initialize_llc_occu_resctrl(const char *ctrlgrp, const char *mongrp,
|
||||
int cpu_no, char *resctrl_val)
|
||||
int measure_mem_bw(const struct user_params *uparams,
|
||||
struct resctrl_val_param *param, pid_t bm_pid,
|
||||
const char *bw_report)
|
||||
{
|
||||
int domain_id;
|
||||
|
||||
if (get_domain_id("L3", cpu_no, &domain_id) < 0) {
|
||||
ksft_print_msg("Could not get domain ID\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!strncmp(resctrl_val, CMT_STR, sizeof(CMT_STR)))
|
||||
set_cmt_path(ctrlgrp, mongrp, domain_id);
|
||||
}
|
||||
|
||||
static int measure_vals(const struct user_params *uparams,
|
||||
struct resctrl_val_param *param,
|
||||
unsigned long *bw_resc_start)
|
||||
{
|
||||
unsigned long bw_resc, bw_resc_end;
|
||||
unsigned long bw_resc, bw_resc_start, bw_resc_end;
|
||||
FILE *mem_bw_fp;
|
||||
float bw_imc;
|
||||
int ret;
|
||||
|
||||
/*
|
||||
* Measure memory bandwidth from resctrl and from
|
||||
* another source which is perf imc value or could
|
||||
* be something else if perf imc event is not available.
|
||||
* Compare the two values to validate resctrl value.
|
||||
* It takes 1sec to measure the data.
|
||||
*/
|
||||
ret = get_mem_bw_imc(uparams->cpu, param->bw_report, &bw_imc);
|
||||
bw_report = get_bw_report_type(bw_report);
|
||||
if (!bw_report)
|
||||
return -1;
|
||||
|
||||
mem_bw_fp = open_mem_bw_resctrl(mbm_total_path);
|
||||
if (!mem_bw_fp)
|
||||
return -1;
|
||||
|
||||
ret = perf_open_imc_mem_bw(uparams->cpu);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
goto close_fp;
|
||||
|
||||
ret = get_mem_bw_resctrl(&bw_resc_end);
|
||||
ret = get_mem_bw_resctrl(mem_bw_fp, &bw_resc_start);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
goto close_imc;
|
||||
|
||||
bw_resc = (bw_resc_end - *bw_resc_start) / MB;
|
||||
ret = print_results_bw(param->filename, bm_pid, bw_imc, bw_resc);
|
||||
if (ret)
|
||||
return ret;
|
||||
rewind(mem_bw_fp);
|
||||
|
||||
*bw_resc_start = bw_resc_end;
|
||||
do_imc_mem_bw_test();
|
||||
|
||||
return 0;
|
||||
ret = get_mem_bw_resctrl(mem_bw_fp, &bw_resc_end);
|
||||
if (ret < 0)
|
||||
goto close_imc;
|
||||
|
||||
ret = get_mem_bw_imc(bw_report, &bw_imc);
|
||||
if (ret < 0)
|
||||
goto close_imc;
|
||||
|
||||
perf_close_imc_mem_bw();
|
||||
fclose(mem_bw_fp);
|
||||
|
||||
bw_resc = (bw_resc_end - bw_resc_start) / MB;
|
||||
|
||||
return print_results_bw(param->filename, bm_pid, bw_imc, bw_resc);
|
||||
|
||||
close_imc:
|
||||
perf_close_imc_mem_bw();
|
||||
close_fp:
|
||||
fclose(mem_bw_fp);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -654,7 +638,7 @@ static void run_benchmark(int signum, siginfo_t *info, void *ucontext)
|
|||
fp = freopen("/dev/null", "w", stdout);
|
||||
if (!fp) {
|
||||
ksft_perror("Unable to direct benchmark status to /dev/null");
|
||||
PARENT_EXIT();
|
||||
parent_exit(ppid);
|
||||
}
|
||||
|
||||
if (strcmp(benchmark_cmd[0], "fill_buf") == 0) {
|
||||
|
@ -668,7 +652,7 @@ static void run_benchmark(int signum, siginfo_t *info, void *ucontext)
|
|||
once = false;
|
||||
} else {
|
||||
ksft_print_msg("Invalid once parameter\n");
|
||||
PARENT_EXIT();
|
||||
parent_exit(ppid);
|
||||
}
|
||||
|
||||
if (run_fill_buf(span, memflush, operation, once))
|
||||
|
@ -682,7 +666,7 @@ static void run_benchmark(int signum, siginfo_t *info, void *ucontext)
|
|||
|
||||
fclose(stdout);
|
||||
ksft_print_msg("Unable to run specified benchmark\n");
|
||||
PARENT_EXIT();
|
||||
parent_exit(ppid);
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -700,21 +684,19 @@ int resctrl_val(const struct resctrl_test *test,
|
|||
const char * const *benchmark_cmd,
|
||||
struct resctrl_val_param *param)
|
||||
{
|
||||
char *resctrl_val = param->resctrl_val;
|
||||
unsigned long bw_resc_start = 0;
|
||||
struct sigaction sigact;
|
||||
int ret = 0, pipefd[2];
|
||||
char pipe_message = 0;
|
||||
union sigval value;
|
||||
int domain_id;
|
||||
|
||||
if (strcmp(param->filename, "") == 0)
|
||||
sprintf(param->filename, "stdio");
|
||||
|
||||
if (!strncmp(resctrl_val, MBA_STR, sizeof(MBA_STR)) ||
|
||||
!strncmp(resctrl_val, MBM_STR, sizeof(MBM_STR))) {
|
||||
ret = validate_bw_report_request(param->bw_report);
|
||||
if (ret)
|
||||
return ret;
|
||||
ret = get_domain_id(test->resource, uparams->cpu, &domain_id);
|
||||
if (ret < 0) {
|
||||
ksft_print_msg("Could not get domain ID\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -755,7 +737,7 @@ int resctrl_val(const struct resctrl_test *test,
|
|||
/* Register for "SIGUSR1" signal from parent */
|
||||
if (sigaction(SIGUSR1, &sigact, NULL)) {
|
||||
ksft_perror("Can't register child for signal");
|
||||
PARENT_EXIT();
|
||||
parent_exit(ppid);
|
||||
}
|
||||
|
||||
/* Tell parent that child is ready */
|
||||
|
@ -773,10 +755,10 @@ int resctrl_val(const struct resctrl_test *test,
|
|||
sigsuspend(&sigact.sa_mask);
|
||||
|
||||
ksft_perror("Child is done");
|
||||
PARENT_EXIT();
|
||||
parent_exit(ppid);
|
||||
}
|
||||
|
||||
ksft_print_msg("Benchmark PID: %d\n", bm_pid);
|
||||
ksft_print_msg("Benchmark PID: %d\n", (int)bm_pid);
|
||||
|
||||
/*
|
||||
* The cast removes constness but nothing mutates benchmark_cmd within
|
||||
|
@ -792,22 +774,15 @@ int resctrl_val(const struct resctrl_test *test,
|
|||
goto out;
|
||||
|
||||
/* Write benchmark to specified control&monitoring grp in resctrl FS */
|
||||
ret = write_bm_pid_to_resctrl(bm_pid, param->ctrlgrp, param->mongrp,
|
||||
resctrl_val);
|
||||
ret = write_bm_pid_to_resctrl(bm_pid, param->ctrlgrp, param->mongrp);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
if (!strncmp(resctrl_val, MBM_STR, sizeof(MBM_STR)) ||
|
||||
!strncmp(resctrl_val, MBA_STR, sizeof(MBA_STR))) {
|
||||
ret = initialize_mem_bw_imc();
|
||||
if (param->init) {
|
||||
ret = param->init(param, domain_id);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
initialize_mem_bw_resctrl(param->ctrlgrp, param->mongrp,
|
||||
uparams->cpu, resctrl_val);
|
||||
} else if (!strncmp(resctrl_val, CMT_STR, sizeof(CMT_STR)))
|
||||
initialize_llc_occu_resctrl(param->ctrlgrp, param->mongrp,
|
||||
uparams->cpu, resctrl_val);
|
||||
}
|
||||
|
||||
/* Parent waits for child to be ready. */
|
||||
close(pipefd[1]);
|
||||
|
@ -841,17 +816,9 @@ int resctrl_val(const struct resctrl_test *test,
|
|||
if (ret < 0)
|
||||
break;
|
||||
|
||||
if (!strncmp(resctrl_val, MBM_STR, sizeof(MBM_STR)) ||
|
||||
!strncmp(resctrl_val, MBA_STR, sizeof(MBA_STR))) {
|
||||
ret = measure_vals(uparams, param, &bw_resc_start);
|
||||
if (ret)
|
||||
break;
|
||||
} else if (!strncmp(resctrl_val, CMT_STR, sizeof(CMT_STR))) {
|
||||
sleep(1);
|
||||
ret = measure_llc_resctrl(param->filename, bm_pid);
|
||||
if (ret)
|
||||
break;
|
||||
}
|
||||
ret = param->measure(uparams, param, bm_pid);
|
||||
if (ret)
|
||||
break;
|
||||
}
|
||||
|
||||
out:
|
||||
|
|
|
@ -456,6 +456,9 @@ int taskset_restore(pid_t bm_pid, cpu_set_t *old_affinity)
|
|||
* @grp: Full path and name of the group
|
||||
* @parent_grp: Full path and name of the parent group
|
||||
*
|
||||
* Creates a group @grp_name if it does not exist yet. If @grp_name is NULL,
|
||||
* it is interpreted as the root group which always results in success.
|
||||
*
|
||||
* Return: 0 on success, < 0 on error.
|
||||
*/
|
||||
static int create_grp(const char *grp_name, char *grp, const char *parent_grp)
|
||||
|
@ -464,12 +467,7 @@ static int create_grp(const char *grp_name, char *grp, const char *parent_grp)
|
|||
struct dirent *ep;
|
||||
DIR *dp;
|
||||
|
||||
/*
|
||||
* At this point, we are guaranteed to have resctrl FS mounted and if
|
||||
* length of grp_name == 0, it means, user wants to use root con_mon
|
||||
* grp, so do nothing
|
||||
*/
|
||||
if (strlen(grp_name) == 0)
|
||||
if (!grp_name)
|
||||
return 0;
|
||||
|
||||
/* Check if requested grp exists or not */
|
||||
|
@ -508,7 +506,7 @@ static int write_pid_to_tasks(char *tasks, pid_t pid)
|
|||
|
||||
return -1;
|
||||
}
|
||||
if (fprintf(fp, "%d\n", pid) < 0) {
|
||||
if (fprintf(fp, "%d\n", (int)pid) < 0) {
|
||||
ksft_print_msg("Failed to write pid to tasks file\n");
|
||||
fclose(fp);
|
||||
|
||||
|
@ -524,7 +522,6 @@ static int write_pid_to_tasks(char *tasks, pid_t pid)
|
|||
* @bm_pid: PID that should be written
|
||||
* @ctrlgrp: Name of the control monitor group (con_mon grp)
|
||||
* @mongrp: Name of the monitor group (mon grp)
|
||||
* @resctrl_val: Resctrl feature (Eg: mbm, mba.. etc)
|
||||
*
|
||||
* If a con_mon grp is requested, create it and write pid to it, otherwise
|
||||
* write pid to root con_mon grp.
|
||||
|
@ -534,14 +531,13 @@ static int write_pid_to_tasks(char *tasks, pid_t pid)
|
|||
*
|
||||
* Return: 0 on success, < 0 on error.
|
||||
*/
|
||||
int write_bm_pid_to_resctrl(pid_t bm_pid, char *ctrlgrp, char *mongrp,
|
||||
char *resctrl_val)
|
||||
int write_bm_pid_to_resctrl(pid_t bm_pid, const char *ctrlgrp, const char *mongrp)
|
||||
{
|
||||
char controlgroup[128], monitorgroup[512], monitorgroup_p[256];
|
||||
char tasks[1024];
|
||||
int ret = 0;
|
||||
|
||||
if (strlen(ctrlgrp))
|
||||
if (ctrlgrp)
|
||||
sprintf(controlgroup, "%s/%s", RESCTRL_PATH, ctrlgrp);
|
||||
else
|
||||
sprintf(controlgroup, "%s", RESCTRL_PATH);
|
||||
|
@ -555,22 +551,19 @@ int write_bm_pid_to_resctrl(pid_t bm_pid, char *ctrlgrp, char *mongrp,
|
|||
if (ret)
|
||||
goto out;
|
||||
|
||||
/* Create mon grp and write pid into it for "mbm" and "cmt" test */
|
||||
if (!strncmp(resctrl_val, CMT_STR, sizeof(CMT_STR)) ||
|
||||
!strncmp(resctrl_val, MBM_STR, sizeof(MBM_STR))) {
|
||||
if (strlen(mongrp)) {
|
||||
sprintf(monitorgroup_p, "%s/mon_groups", controlgroup);
|
||||
sprintf(monitorgroup, "%s/%s", monitorgroup_p, mongrp);
|
||||
ret = create_grp(mongrp, monitorgroup, monitorgroup_p);
|
||||
if (ret)
|
||||
goto out;
|
||||
/* Create monitor group and write pid into if it is used */
|
||||
if (mongrp) {
|
||||
sprintf(monitorgroup_p, "%s/mon_groups", controlgroup);
|
||||
sprintf(monitorgroup, "%s/%s", monitorgroup_p, mongrp);
|
||||
ret = create_grp(mongrp, monitorgroup, monitorgroup_p);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
sprintf(tasks, "%s/mon_groups/%s/tasks",
|
||||
controlgroup, mongrp);
|
||||
ret = write_pid_to_tasks(tasks, bm_pid);
|
||||
if (ret)
|
||||
goto out;
|
||||
}
|
||||
sprintf(tasks, "%s/mon_groups/%s/tasks",
|
||||
controlgroup, mongrp);
|
||||
ret = write_pid_to_tasks(tasks, bm_pid);
|
||||
if (ret)
|
||||
goto out;
|
||||
}
|
||||
|
||||
out:
|
||||
|
@ -593,7 +586,8 @@ int write_bm_pid_to_resctrl(pid_t bm_pid, char *ctrlgrp, char *mongrp,
|
|||
*
|
||||
* Return: 0 on success, < 0 on error.
|
||||
*/
|
||||
int write_schemata(char *ctrlgrp, char *schemata, int cpu_no, const char *resource)
|
||||
int write_schemata(const char *ctrlgrp, char *schemata, int cpu_no,
|
||||
const char *resource)
|
||||
{
|
||||
char controlgroup[1024], reason[128], schema[1024] = {};
|
||||
int domain_id, fd, schema_len, ret = 0;
|
||||
|
@ -611,7 +605,7 @@ int write_schemata(char *ctrlgrp, char *schemata, int cpu_no, const char *resour
|
|||
goto out;
|
||||
}
|
||||
|
||||
if (strlen(ctrlgrp) != 0)
|
||||
if (ctrlgrp)
|
||||
sprintf(controlgroup, "%s/%s/schemata", RESCTRL_PATH, ctrlgrp);
|
||||
else
|
||||
sprintf(controlgroup, "%s/schemata", RESCTRL_PATH);
|
||||
|
@ -837,22 +831,21 @@ int filter_dmesg(void)
|
|||
return 0;
|
||||
}
|
||||
|
||||
int validate_bw_report_request(char *bw_report)
|
||||
const char *get_bw_report_type(const char *bw_report)
|
||||
{
|
||||
if (strcmp(bw_report, "reads") == 0)
|
||||
return 0;
|
||||
return bw_report;
|
||||
if (strcmp(bw_report, "writes") == 0)
|
||||
return 0;
|
||||
return bw_report;
|
||||
if (strcmp(bw_report, "nt-writes") == 0) {
|
||||
strcpy(bw_report, "writes");
|
||||
return 0;
|
||||
return "writes";
|
||||
}
|
||||
if (strcmp(bw_report, "total") == 0)
|
||||
return 0;
|
||||
return bw_report;
|
||||
|
||||
fprintf(stderr, "Requested iMC B/W report type unavailable\n");
|
||||
fprintf(stderr, "Requested iMC bandwidth report type unavailable\n");
|
||||
|
||||
return -1;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int perf_event_open(struct perf_event_attr *hw_event, pid_t pid, int cpu,
|
||||
|
|
|
@ -42,11 +42,11 @@ static pid_t gettid(void)
|
|||
|
||||
#ifndef PR_SCHED_CORE
|
||||
#define PR_SCHED_CORE 62
|
||||
# define PR_SCHED_CORE_GET 0
|
||||
# define PR_SCHED_CORE_CREATE 1 /* create unique core_sched cookie */
|
||||
# define PR_SCHED_CORE_SHARE_TO 2 /* push core_sched cookie to pid */
|
||||
# define PR_SCHED_CORE_SHARE_FROM 3 /* pull core_sched cookie to pid */
|
||||
# define PR_SCHED_CORE_MAX 4
|
||||
#define PR_SCHED_CORE_GET 0
|
||||
#define PR_SCHED_CORE_CREATE 1 /* create unique core_sched cookie */
|
||||
#define PR_SCHED_CORE_SHARE_TO 2 /* push core_sched cookie to pid */
|
||||
#define PR_SCHED_CORE_SHARE_FROM 3 /* pull core_sched cookie to pid */
|
||||
#define PR_SCHED_CORE_MAX 4
|
||||
#endif
|
||||
|
||||
#define MAX_PROCESSES 128
|
||||
|
|
|
@ -29,7 +29,7 @@ static const char default_rtc[] = "/dev/rtc0";
|
|||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
int i, fd, retval, irqcount = 0;
|
||||
int i, fd, retval;
|
||||
unsigned long tmp, data, old_pie_rate;
|
||||
const char *rtc = default_rtc;
|
||||
struct timeval start, end, diff;
|
||||
|
@ -120,7 +120,6 @@ int main(int argc, char **argv)
|
|||
|
||||
fprintf(stderr, " %d",i);
|
||||
fflush(stderr);
|
||||
irqcount++;
|
||||
}
|
||||
|
||||
/* Disable periodic interrupts */
|
||||
|
|
|
@ -40,6 +40,13 @@ CFLAGS := -O2 -g -std=gnu99 -pthread -Wall $(KHDR_INCLUDES)
|
|||
# call32_from_64 in thunks.S uses absolute addresses.
|
||||
ifeq ($(CAN_BUILD_WITH_NOPIE),1)
|
||||
CFLAGS += -no-pie
|
||||
|
||||
ifneq ($(LLVM),)
|
||||
# clang only wants to see -no-pie during linking. Here, we don't have a separate
|
||||
# linking stage, so a compiler warning is unavoidable without (wastefully)
|
||||
# restructuring the Makefile. Avoid this by simply disabling that warning.
|
||||
CFLAGS += -Wno-unused-command-line-argument
|
||||
endif
|
||||
endif
|
||||
|
||||
define gen-target-rule-32
|
||||
|
@ -73,10 +80,10 @@ all_64: $(BINARIES_64)
|
|||
EXTRA_CLEAN := $(BINARIES_32) $(BINARIES_64)
|
||||
|
||||
$(BINARIES_32): $(OUTPUT)/%_32: %.c helpers.h
|
||||
$(CC) -m32 -o $@ $(CFLAGS) $(EXTRA_CFLAGS) $^ -lrt -ldl -lm
|
||||
$(CC) -m32 -o $@ $(CFLAGS) $(EXTRA_CFLAGS) $< $(EXTRA_FILES) -lrt -ldl -lm
|
||||
|
||||
$(BINARIES_64): $(OUTPUT)/%_64: %.c helpers.h
|
||||
$(CC) -m64 -o $@ $(CFLAGS) $(EXTRA_CFLAGS) $^ -lrt -ldl
|
||||
$(CC) -m64 -o $@ $(CFLAGS) $(EXTRA_CFLAGS) $< $(EXTRA_FILES) -lrt -ldl
|
||||
|
||||
# x86_64 users should be encouraged to install 32-bit libraries
|
||||
ifeq ($(CAN_BUILD_I386)$(CAN_BUILD_X86_64),01)
|
||||
|
@ -100,10 +107,22 @@ warn_32bit_failure:
|
|||
exit 0;
|
||||
endif
|
||||
|
||||
# Some tests have additional dependencies.
|
||||
$(OUTPUT)/sysret_ss_attrs_64: thunks.S
|
||||
$(OUTPUT)/ptrace_syscall_32: raw_syscall_helper_32.S
|
||||
$(OUTPUT)/test_syscall_vdso_32: thunks_32.S
|
||||
# Add an additional file to the source file list for a given target, and also
|
||||
# add a Makefile dependency on that same file. However, do these separately, so
|
||||
# that the compiler invocation ("$(CC) file1.c file2.S") is not combined with
|
||||
# the dependencies ("header3.h"), because clang, unlike gcc, will not accept
|
||||
# header files as an input to the compiler invocation.
|
||||
define extra-files
|
||||
$(OUTPUT)/$(1): EXTRA_FILES := $(2)
|
||||
$(OUTPUT)/$(1): $(2)
|
||||
endef
|
||||
|
||||
$(eval $(call extra-files,sysret_ss_attrs_64,thunks.S))
|
||||
$(eval $(call extra-files,ptrace_syscall_32,raw_syscall_helper_32.S))
|
||||
$(eval $(call extra-files,test_syscall_vdso_32,thunks_32.S))
|
||||
$(eval $(call extra-files,fsgsbase_restore_64,clang_helpers_64.S))
|
||||
$(eval $(call extra-files,fsgsbase_restore_32,clang_helpers_32.S))
|
||||
$(eval $(call extra-files,sysret_rip_64,clang_helpers_64.S))
|
||||
|
||||
# check_initial_reg_state is special: it needs a custom entry, and it
|
||||
# needs to be static so that its interpreter doesn't destroy its initial
|
||||
|
|
|
@ -39,16 +39,6 @@ struct xsave_buffer {
|
|||
};
|
||||
};
|
||||
|
||||
static inline uint64_t xgetbv(uint32_t index)
|
||||
{
|
||||
uint32_t eax, edx;
|
||||
|
||||
asm volatile("xgetbv;"
|
||||
: "=a" (eax), "=d" (edx)
|
||||
: "c" (index));
|
||||
return eax + ((uint64_t)edx << 32);
|
||||
}
|
||||
|
||||
static inline void xsave(struct xsave_buffer *xbuf, uint64_t rfbm)
|
||||
{
|
||||
uint32_t rfbm_lo = rfbm;
|
||||
|
@ -164,12 +154,6 @@ static inline void clear_xstate_header(struct xsave_buffer *buffer)
|
|||
memset(&buffer->header, 0, sizeof(buffer->header));
|
||||
}
|
||||
|
||||
static inline uint64_t get_xstatebv(struct xsave_buffer *buffer)
|
||||
{
|
||||
/* XSTATE_BV is at the beginning of the header: */
|
||||
return *(uint64_t *)&buffer->header;
|
||||
}
|
||||
|
||||
static inline void set_xstatebv(struct xsave_buffer *buffer, uint64_t bv)
|
||||
{
|
||||
/* XSTATE_BV is at the beginning of the header: */
|
||||
|
|
11
tools/testing/selftests/x86/clang_helpers_32.S
Normal file
11
tools/testing/selftests/x86/clang_helpers_32.S
Normal file
|
@ -0,0 +1,11 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
/*
|
||||
* 32-bit assembly helpers for asm operations that lack support in both gcc and
|
||||
* clang. For example, clang asm does not support segment prefixes.
|
||||
*/
|
||||
.global dereference_seg_base
|
||||
dereference_seg_base:
|
||||
mov %fs:(0), %eax
|
||||
ret
|
||||
|
||||
.section .note.GNU-stack,"",%progbits
|
28
tools/testing/selftests/x86/clang_helpers_64.S
Normal file
28
tools/testing/selftests/x86/clang_helpers_64.S
Normal file
|
@ -0,0 +1,28 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
/*
|
||||
* 64-bit assembly helpers for asm operations that lack support in both gcc and
|
||||
* clang. For example, clang asm does not support segment prefixes.
|
||||
*/
|
||||
.global dereference_seg_base
|
||||
|
||||
dereference_seg_base:
|
||||
mov %gs:(0), %rax
|
||||
ret
|
||||
|
||||
.global test_page
|
||||
.global test_syscall_insn
|
||||
|
||||
.pushsection ".text", "ax"
|
||||
.balign 4096
|
||||
test_page: .globl test_page
|
||||
.fill 4094,1,0xcc
|
||||
|
||||
test_syscall_insn:
|
||||
syscall
|
||||
|
||||
.ifne . - test_page - 4096
|
||||
.error "test page is not one page long"
|
||||
.endif
|
||||
.popsection
|
||||
|
||||
.section .note.GNU-stack,"",%progbits
|
|
@ -109,11 +109,6 @@ static inline void wrgsbase(unsigned long gsbase)
|
|||
asm volatile("wrgsbase %0" :: "r" (gsbase) : "memory");
|
||||
}
|
||||
|
||||
static inline void wrfsbase(unsigned long fsbase)
|
||||
{
|
||||
asm volatile("wrfsbase %0" :: "r" (fsbase) : "memory");
|
||||
}
|
||||
|
||||
enum which_base { FS, GS };
|
||||
|
||||
static unsigned long read_base(enum which_base which)
|
||||
|
@ -212,7 +207,6 @@ static void mov_0_gs(unsigned long initial_base, bool schedule)
|
|||
}
|
||||
|
||||
static volatile unsigned long remote_base;
|
||||
static volatile bool remote_hard_zero;
|
||||
static volatile unsigned int ftx;
|
||||
|
||||
/*
|
||||
|
|
|
@ -39,12 +39,11 @@
|
|||
# define SEG "%fs"
|
||||
#endif
|
||||
|
||||
static unsigned int dereference_seg_base(void)
|
||||
{
|
||||
int ret;
|
||||
asm volatile ("mov %" SEG ":(0), %0" : "=rm" (ret));
|
||||
return ret;
|
||||
}
|
||||
/*
|
||||
* Defined in clang_helpers_[32|64].S, because unlike gcc, clang inline asm does
|
||||
* not support segmentation prefixes.
|
||||
*/
|
||||
unsigned int dereference_seg_base(void);
|
||||
|
||||
static void init_seg(void)
|
||||
{
|
||||
|
|
|
@ -487,7 +487,7 @@ static void sigtrap(int sig, siginfo_t *info, void *ctx_void)
|
|||
greg_t asm_ss = ctx->uc_mcontext.gregs[REG_CX];
|
||||
if (asm_ss != sig_ss && sig == SIGTRAP) {
|
||||
/* Sanity check failure. */
|
||||
printf("[FAIL]\tSIGTRAP: ss = %hx, frame ss = %hx, ax = %llx\n",
|
||||
printf("[FAIL]\tSIGTRAP: ss = %hx, frame ss = %x, ax = %llx\n",
|
||||
ss, *ssptr(ctx), (unsigned long long)asm_ss);
|
||||
nerrs++;
|
||||
}
|
||||
|
|
|
@ -29,7 +29,6 @@ static void sethandler(int sig, void (*handler)(int, siginfo_t *, void *),
|
|||
err(1, "sigaction");
|
||||
}
|
||||
|
||||
static volatile sig_atomic_t sig_traps;
|
||||
static sigjmp_buf jmpbuf;
|
||||
|
||||
static volatile sig_atomic_t n_errs;
|
||||
|
|
|
@ -22,21 +22,13 @@
|
|||
#include <sys/mman.h>
|
||||
#include <assert.h>
|
||||
|
||||
|
||||
asm (
|
||||
".pushsection \".text\", \"ax\"\n\t"
|
||||
".balign 4096\n\t"
|
||||
"test_page: .globl test_page\n\t"
|
||||
".fill 4094,1,0xcc\n\t"
|
||||
"test_syscall_insn:\n\t"
|
||||
"syscall\n\t"
|
||||
".ifne . - test_page - 4096\n\t"
|
||||
".error \"test page is not one page long\"\n\t"
|
||||
".endif\n\t"
|
||||
".popsection"
|
||||
);
|
||||
|
||||
/*
|
||||
* These items are in clang_helpers_64.S, in order to avoid clang inline asm
|
||||
* limitations:
|
||||
*/
|
||||
void test_syscall_ins(void);
|
||||
extern const char test_page[];
|
||||
|
||||
static void const *current_test_page_addr = test_page;
|
||||
|
||||
static void sethandler(int sig, void (*handler)(int, siginfo_t *, void *),
|
||||
|
|
|
@ -25,7 +25,7 @@ int test(void)
|
|||
feclearexcept(FE_DIVBYZERO|FE_INEXACT|FE_INVALID|FE_OVERFLOW|FE_UNDERFLOW);
|
||||
asm volatile ("\n"
|
||||
" fld1""\n"
|
||||
" fisttp res16""\n"
|
||||
" fisttps res16""\n"
|
||||
" fld1""\n"
|
||||
" fisttpl res32""\n"
|
||||
" fld1""\n"
|
||||
|
@ -45,7 +45,7 @@ int test(void)
|
|||
feclearexcept(FE_DIVBYZERO|FE_INEXACT|FE_INVALID|FE_OVERFLOW|FE_UNDERFLOW);
|
||||
asm volatile ("\n"
|
||||
" fldpi""\n"
|
||||
" fisttp res16""\n"
|
||||
" fisttps res16""\n"
|
||||
" fldpi""\n"
|
||||
" fisttpl res32""\n"
|
||||
" fldpi""\n"
|
||||
|
@ -66,7 +66,7 @@ int test(void)
|
|||
asm volatile ("\n"
|
||||
" fldpi""\n"
|
||||
" fchs""\n"
|
||||
" fisttp res16""\n"
|
||||
" fisttps res16""\n"
|
||||
" fldpi""\n"
|
||||
" fchs""\n"
|
||||
" fisttpl res32""\n"
|
||||
|
@ -88,7 +88,7 @@ int test(void)
|
|||
feclearexcept(FE_DIVBYZERO|FE_INEXACT|FE_INVALID|FE_OVERFLOW|FE_UNDERFLOW);
|
||||
asm volatile ("\n"
|
||||
" fldln2""\n"
|
||||
" fisttp res16""\n"
|
||||
" fisttps res16""\n"
|
||||
" fldln2""\n"
|
||||
" fisttpl res32""\n"
|
||||
" fldln2""\n"
|
||||
|
|
|
@ -97,11 +97,6 @@ static inline long sys_gtod(struct timeval *tv, struct timezone *tz)
|
|||
return syscall(SYS_gettimeofday, tv, tz);
|
||||
}
|
||||
|
||||
static inline int sys_clock_gettime(clockid_t id, struct timespec *ts)
|
||||
{
|
||||
return syscall(SYS_clock_gettime, id, ts);
|
||||
}
|
||||
|
||||
static inline long sys_time(time_t *t)
|
||||
{
|
||||
return syscall(SYS_time, t);
|
||||
|
@ -252,7 +247,7 @@ static void test_getcpu(int cpu)
|
|||
|
||||
if (ret_sys == 0) {
|
||||
if (cpu_sys != cpu)
|
||||
ksft_print_msg("syscall reported CPU %hu but should be %d\n",
|
||||
ksft_print_msg("syscall reported CPU %u but should be %d\n",
|
||||
cpu_sys, cpu);
|
||||
|
||||
have_node = true;
|
||||
|
@ -270,10 +265,10 @@ static void test_getcpu(int cpu)
|
|||
|
||||
if (cpu_vdso != cpu || node_vdso != node) {
|
||||
if (cpu_vdso != cpu)
|
||||
ksft_print_msg("vDSO reported CPU %hu but should be %d\n",
|
||||
ksft_print_msg("vDSO reported CPU %u but should be %d\n",
|
||||
cpu_vdso, cpu);
|
||||
if (node_vdso != node)
|
||||
ksft_print_msg("vDSO reported node %hu but should be %hu\n",
|
||||
ksft_print_msg("vDSO reported node %u but should be %u\n",
|
||||
node_vdso, node);
|
||||
ksft_test_result_fail("Wrong values\n");
|
||||
} else {
|
||||
|
@ -295,10 +290,10 @@ static void test_getcpu(int cpu)
|
|||
|
||||
if (cpu_vsys != cpu || node_vsys != node) {
|
||||
if (cpu_vsys != cpu)
|
||||
ksft_print_msg("vsyscall reported CPU %hu but should be %d\n",
|
||||
ksft_print_msg("vsyscall reported CPU %u but should be %d\n",
|
||||
cpu_vsys, cpu);
|
||||
if (node_vsys != node)
|
||||
ksft_print_msg("vsyscall reported node %hu but should be %hu\n",
|
||||
ksft_print_msg("vsyscall reported node %u but should be %u\n",
|
||||
node_vsys, node);
|
||||
ksft_test_result_fail("Wrong values\n");
|
||||
} else {
|
||||
|
|
|
@ -92,4 +92,6 @@ int main()
|
|||
printf("[FAIL]\t!SA_SIGINFO handler was not called\n");
|
||||
nerrs++;
|
||||
}
|
||||
|
||||
return nerrs;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue