# SPDX-License-Identifier: GPL-2.0
# Copyright (C) 2018, FUJITSU LIMITED. All rights reserved.

# err
# $1: line number which error detected
# $2: cleanup function (optional)
#

test_basename=$(basename "$0")

err()
{
	echo test/"$test_basename": failed at line "$1"
	[ -n "$2" ] && "$2"
	exit "$rc"
}

# Global variables

# NDCTL
if [ -z "$NDCTL" ]; then
	if [ -f "../ndctl/ndctl" ] && [ -x "../ndctl/ndctl" ]; then
		export NDCTL=../ndctl/ndctl
	elif [ -f "./ndctl/ndctl" ] && [ -x "./ndctl/ndctl" ]; then
		export NDCTL=./ndctl/ndctl
	else
		echo "Couldn't find an ndctl binary"
		exit 1
	fi
fi

# DAXCTL
if [ -z "$DAXCTL" ]; then
	if [ -f "../daxctl/daxctl" ] && [ -x "../daxctl/daxctl" ]; then
		export DAXCTL=../daxctl/daxctl
	elif [ -f "./daxctl/daxctl" ] && [ -x "./daxctl/daxctl" ]; then
		export DAXCTL=./daxctl/daxctl
	else
		echo "Couldn't find an daxctl binary"
		exit 1
	fi
fi

# CXL
if [ -z "$CXL" ]; then
	if [ -f "../cxl/cxl" ] && [ -x "../cxl/cxl" ]; then
		export CXL=../cxl/cxl
	elif [ -f "./cxl/cxl" ] && [ -x "./cxl/cxl" ]; then
		export CXL=./cxl/cxl
	else
		echo "Couldn't find a cxl binary"
		exit 1
	fi
fi

if [ -z "$TEST_PATH" ]; then
	export TEST_PATH=.
fi

# NFIT_TEST_BUS[01]
#
NFIT_TEST_BUS0="nfit_test.0"
NFIT_TEST_BUS1="nfit_test.1"
CXL_TEST_BUS="cxl_test"
ACPI_BUS="ACPI.NFIT"
E820_BUS="e820"

# Functions

reset()
{
	$NDCTL disable-region -b $NFIT_TEST_BUS0 all
	$NDCTL init-labels -f -b $NFIT_TEST_BUS0 all
	$NDCTL enable-region -b $NFIT_TEST_BUS0 all
}

resetV()
{
	$NDCTL disable-region -b $NFIT_TEST_BUS0 all
	$NDCTL init-labels -f -V 1.2 -b $NFIT_TEST_BUS0 all
	$NDCTL enable-region -b $NFIT_TEST_BUS0 all
}

reset1()
{
	$NDCTL disable-region -b $NFIT_TEST_BUS1 all
	$NDCTL init-labels -f -b $NFIT_TEST_BUS1 all
	$NDCTL enable-region -b $NFIT_TEST_BUS1 all
}


# check_min_kver
# $1: Supported kernel version. format: X.Y
#
check_min_kver()
{
	local ver="$1"
	: "${KVER:=$(uname -r)}"

	[ -n "$ver" ] || return 1
	[[ "$ver" == "$(echo -e "$ver\n$KVER" | sort -V | head -1)" ]]
}

# check_eq_kver
# $1: Kernel version to match. format: X.Y
#
check_eq_kver()
{
        local ver="$1"
        : "${KVER:=$(uname -r)}"

        [ -n "$ver" ] || return 1
        [[ "$KVER" == "$ver"* ]]
}

# do_skip
# $1: Skip message
#
do_skip()
{
	echo kernel "$(uname -r)": "$1"
	exit 77
}

# check_prereq
# $1: command to check
#
check_prereq()
{
	if ! command -v "$1" >/dev/null; then
		do_skip "missing $1, skipping..."
	fi
}

# _cleanup
#
_cleanup()
{
	$NDCTL disable-region -b $NFIT_TEST_BUS0 all
	$NDCTL disable-region -b $NFIT_TEST_BUS1 all
	modprobe -r nfit_test
}

_cxl_cleanup()
{
	$NDCTL disable-region -b $CXL_TEST_BUS all
	modprobe -r cxl_test
}

# json2var
# stdin: json
#
json2var()
{
	sed -e "s/[{}\",]//g; s/\[//g; s/\]//g; s/:/=/g"
}

# check_dmesg
# $1: line number where this is called
check_dmesg()
{
	# validate no WARN or lockdep report during the run
	sleep 1
	log=$(journalctl -r -k --since "-$((SECONDS+1))s")
	grep -q "Call Trace" <<< "$log" && err "$1"
	true
}

# CXL COMMON
CXL_TEST_QOS_CLASS=42

# CXL region replay helpers
#
# replay_regions() snapshots the current region configuration, performs
# a cxl_acpi driver unbind/bind cycle to trigger region replay, and
# verifies that the regions reconstructed during enumeration match the
# original configuration.
#
# This allows tests to create regions through the user interface and
# then replay them as auto-discovered regions during driver
# initialization.
#
# See example usage in test/cxl-region-replay.sh.

CXL_EXPECTED_REGION_SIGS=""

cxl_get_memdev_serial()
{
	local memdev="$1"

	"$CXL" list -m "$memdev" | jq -r '.[0].serial'
}

cxl_emit_region_signature()
{
	local region="$1"
	local region_json size type ways gran state
	local entry pos memdev serial sig

	region_json=$("$CXL" list -R --targets -r "$region")
	[[ -n "$region_json" && "$region_json" != "[]" ]] || return 1

	size=$(jq -r '.[0].size' <<<"$region_json")
	type=$(jq -r '.[0].type' <<<"$region_json")
	ways=$(jq -r '.[0].interleave_ways' <<<"$region_json")
	gran=$(jq -r '.[0].interleave_granularity' <<<"$region_json")
	state=$(jq -r '.[0].decode_state' <<<"$region_json")

	sig="size=$size type=$type ways=$ways gran=$gran"

	while IFS= read -r entry; do
		pos=$(jq -r '.position' <<<"$entry")
		memdev=$(jq -r '.memdev' <<<"$entry")
		serial=$(cxl_get_memdev_serial "$memdev") || return 1
		[[ -n "$serial" && "$serial" != "null" ]] || return 1
		sig="$sig pos=$pos serial=$serial"
	done < <(jq -c '.[0].mappings | sort_by(.position)[]' <<<"$region_json")

	echo "$sig state=$state"
}

cxl_collect_region_signatures()
{
	local region
	local -a regions=()

	mapfile -t regions < <("$CXL" list -Ri | jq -r '.[].region' | sort) || return 1

	for region in "${regions[@]}"; do
		cxl_emit_region_signature "$region" || return 1
	done | sort
}

cxl_capture_expected_region_signatures()
{
	CXL_EXPECTED_REGION_SIGS=$(cxl_collect_region_signatures) || return 1
}

cxl_verify_replayed_regions()
{
	local actual_region_sigs
	local -a expected_lines=()
	local -a actual_lines=()
	local i
	local expected_line actual_line
	local expected_struct actual_struct
	local expected_state actual_state

	actual_region_sigs=$(cxl_collect_region_signatures) || return 1

	mapfile -t expected_lines < <(printf '%s\n' "$CXL_EXPECTED_REGION_SIGS" | sed '/^$/d')
	mapfile -t actual_lines < <(printf '%s\n' "$actual_region_sigs" | sed '/^$/d')

	[ "${#expected_lines[@]}" -eq "${#actual_lines[@]}" ] || return 1

	for ((i = 0; i < ${#expected_lines[@]}; i++)); do
		expected_line=${expected_lines[i]}
		actual_line=${actual_lines[i]}

		expected_struct=${expected_line% state=*}
		expected_state=${expected_line##* state=}
		actual_struct=${actual_line% state=*}
		actual_state=${actual_line##* state=}

		# Require exact structural match for the region config.
		# Only enforce state when the expected state was commit.
		[ "$expected_struct" = "$actual_struct" ] || return 1
		[ "$expected_state" != "commit" ] || [ "$actual_state" = "commit" ] || return 1
	done
}

replay_regions()
{
	echo "replaying existing regions"
	cxl_capture_expected_region_signatures || return 1
	echo 1 > /sys/bus/platform/devices/cxl_acpi.0/decoder_reset_preserve_registry
	echo cxl_acpi.0 > /sys/bus/platform/drivers/cxl_acpi/unbind
	echo cxl_acpi.0 > /sys/bus/platform/drivers/cxl_acpi/bind
	echo 0 > /sys/bus/platform/devices/cxl_acpi.0/decoder_reset_preserve_registry
	cxl_verify_replayed_regions || return 1
}
