#!/bin/bash
# SPDX-License-Identifier: GPL-3.0+
# Copyright (C) 2018 Johannes Thumshirn
#
# Test specific to NVMe devices

. common/rc
. common/nvme
. common/multipath-over-rdma

_NVMET_TRTYPES_is_valid() {
	local type

	for type in $NVMET_TRTYPES; do
		case $type in
		loop | rdma | tcp | fc)
			;;
		*)
			SKIP_REASONS+=("Invalid NVMET_TRTYPE value: $type")
			return 1
			;;
		esac
	done
	return 0
}

_set_nvme_trtype() {
	local index=$1
	local -a types

	read -r -a types <<< "$NVMET_TRTYPES"

	if [[ -z $index ]]; then
		echo ${#types[@]}
		return
	fi

	nvme_trtype=${types[index]}
	COND_DESC="tr=${nvme_trtype}"
}

_set_nvmet_blkdev_type() {
	local index=$1
	local -a types

	read -r -a types <<< "$NVMET_BLKDEV_TYPES"

	if [[ -z $index ]]; then
		echo ${#types[@]}
		return
	fi

	nvmet_blkdev_type=${types[index]}
	COND_DESC="bd=${nvmet_blkdev_type}"
}

_nvme_requires() {
	_have_program nvme
	_require_nvme_test_img_size 4m
	case ${nvme_trtype} in
	loop)
		_have_driver nvme-loop
		_have_configfs
		;;
	pci)
		_have_driver nvme
		;;
	tcp)
		_have_driver nvme-tcp
		_have_driver nvmet-tcp
		_have_configfs
		;;
	rdma)
		_have_driver nvme-rdma
		_have_driver nvmet-rdma
		_have_configfs
		_have_program rdma
		if [ -n "$USE_RXE" ]; then
			_have_driver rdma_rxe
		else
			_have_driver siw
		fi
		;;
	fc)
		_have_driver nvme-fc
		_have_driver nvme-fcloop
		_have_configfs
		def_adrfam="fc"
		;;
	esac

	if [[ -n ${nvme_adrfam} ]]; then
		case ${nvme_adrfam} in
		ipv6)
			def_traddr="::1"
			def_adrfam="ipv6"
			;;
		ipv4)
			;; # was already set
		fc)
			def_adrfam="fc"
			;;
		*)
			# ignore for non ip transports
			if [[ "${nvme_trtype}" == "tcp" ||
			      "${nvme_trtype}" == "rdma" ]]; then
				SKIP_REASONS+=("unsupported nvme_adrfam=${nvme_adrfam}")
				return 1
			fi
		esac
	fi

	return 0
}

group_requires() {
	_have_root
	_NVMET_TRTYPES_is_valid
}

group_device_requires() {
	_require_test_dev_is_nvme
}

_require_test_dev_is_nvme() {
	if ! readlink -f "$TEST_DEV_SYSFS/device" | grep -q nvme; then
		SKIP_REASONS+=("$TEST_DEV is not a NVMe device")
		return 1
	fi
	return 0
}

_require_nvme_test_img_size() {
	local require_sz_mb
	local nvme_img_size_mb

	require_sz_mb="$(convert_to_mb "$1")"
	nvme_img_size_mb="$(convert_to_mb "${NVME_IMG_SIZE}")"

	if ((nvme_img_size_mb < require_sz_mb)); then
		SKIP_REASONS+=("NVME_IMG_SIZE must be at least ${require_sz_mb}m")
		return 1
	fi
	return 0
}

_require_nvme_cli_auth() {
	if ! nvme gen-dhchap-key --nqn nvmf-test-subsys > /dev/null 2>&1 ; then
		SKIP_REASONS+=("nvme gen-dhchap-key command missing")
		return 1
	fi
	return 0
}

_require_kernel_nvme_fabrics_feature() {
	local feature="$1"

	_have_driver nvme-fabrics || return 1

	if ! [[ -r /dev/nvme-fabrics ]]; then
		SKIP_REASONS+=("/dev/nvme-fabrics not available")
		return 1;
	fi
	if ! grep -qe "${feature}" /dev/nvme-fabrics; then
		SKIP_REASONS+=("nvme-fabrics does not support ${feature}")
		return 1;
	fi
	return 0
}

_test_dev_nvme_ctrl() {
	echo "/dev/char/$(cat "${TEST_DEV_SYSFS}/device/dev")"
}

_test_dev_nvme_nsid() {
	cat "${TEST_DEV_SYSFS}/nsid"
}

_nvme_calc_rand_io_size() {
	local img_size_mb
	local io_size_kb

	img_size_mb="$(convert_to_mb "$1")"
	io_size_kb="$(((img_size_mb * 1024) / $(nproc)))"

	echo "${io_size_kb}k"
}

_nvme_discover() {
	local trtype="$1"
	local traddr="${2:-$def_traddr}"
	local host_traddr="${3:-$def_host_traddr}"
	local trsvcid="${3:-$def_trsvcid}"

	ARGS=(--transport "${trtype}")
	ARGS+=(--hostnqn="${def_hostnqn}")
	ARGS+=(--hostid="${def_hostid}")
	if [[ "${trtype}" = "fc" ]]; then
		ARGS+=(--traddr "${traddr}" --host-traddr "${host_traddr}")
	elif [[ "${trtype}" != "loop" ]]; then
		ARGS+=(--traddr "${traddr}" --trsvcid "${trsvcid}")
	fi
	nvme discover "${ARGS[@]}"
}

_remove_nvmet_allow_hosts() {
	local nvmet_subsystem="$1"
	local nvmet_hostnqn="$2"
	local cfs_path="${NVMET_CFS}/subsystems/${nvmet_subsystem}"

	rm "${cfs_path}/allowed_hosts/${nvmet_hostnqn}"
}

_create_nvmet_passthru() {
	local nvmet_subsystem="$1"
	local subsys_path="${NVMET_CFS}/subsystems/${nvmet_subsystem}"
	local passthru_path="${subsys_path}/passthru"

	mkdir -p "${subsys_path}"
	echo 0 > "${subsys_path}/attr_allow_any_host"

	_test_dev_nvme_ctrl > "${passthru_path}/device_path"
	echo 1 > "${passthru_path}/enable"
	if [[ -f "${passthru_path}/clear_ids" ]]; then
		echo 1 > "${passthru_path}/clear_ids"
	fi
}

_remove_nvmet_passhtru() {
	local nvmet_subsystem="$1"
	local subsys_path="${NVMET_CFS}/subsystems/${nvmet_subsystem}"
	local passthru_path="${subsys_path}/passthru"

	echo 0 > "${passthru_path}/enable"
	rm -f "${subsys_path}"/allowed_hosts/*
	rmdir "${subsys_path}"
}

_set_nvmet_hostkey() {
	local nvmet_hostnqn="$1"
	local nvmet_hostkey="$2"
	local cfs_path="${NVMET_CFS}/hosts/${nvmet_hostnqn}"

	echo "${nvmet_hostkey}" > \
	     "${cfs_path}/dhchap_key"
}

_set_nvmet_ctrlkey() {
	local nvmet_hostnqn="$1"
	local nvmet_ctrlkey="$2"
	local cfs_path="${NVMET_CFS}/hosts/${nvmet_hostnqn}"

	echo "${nvmet_ctrlkey}" > \
	     "${cfs_path}/dhchap_ctrl_key"
}

_set_nvmet_hash() {
	local nvmet_hostnqn="$1"
	local nvmet_hash="$2"
	local cfs_path="${NVMET_CFS}/hosts/${nvmet_hostnqn}"

	echo "${nvmet_hash}" > \
	     "${cfs_path}/dhchap_hash"
}

_set_nvmet_dhgroup() {
	local nvmet_hostnqn="$1"
	local nvmet_dhgroup="$2"
	local cfs_path="${NVMET_CFS}/hosts/${nvmet_hostnqn}"

	echo "${nvmet_dhgroup}" > \
	     "${cfs_path}/dhchap_dhgroup"
}

_find_nvme_passthru_loop_dev() {
	local subsys=$1
	local nsid
	local dev

	dev=$(_find_nvme_dev "${subsys}")
	nsid=$(_test_dev_nvme_nsid)
	echo "/dev/${dev}n${nsid}"
}

_nvmet_target_setup() {
	local blkdev_type="${nvmet_blkdev_type}"
	local blkdev
	local ctrlkey=""
	local hostkey=""
	local subsysnqn="${def_subsysnqn}"
	local subsys_uuid="${def_subsys_uuid}"
	local port

	while [[ $# -gt 0 ]]; do
		case $1 in
			--blkdev)
				blkdev_type="$2"
				shift 2
				;;
			--ctrlkey)
				ctrlkey="$2"
				shift 2
				;;
			--hostkey)
				hostkey="$2"
				shift 2
				;;
			--subsysnqn)
				subsysnqn="$2"
				shift 2
				;;
			--subsys-uuid)
				subsys_uuid="$2"
				shift 2
				;;
			*)
				echo "WARNING: unknown argument: $1"
				shift
				;;
		esac
	done

	truncate -s "${NVME_IMG_SIZE}" "$(_nvme_def_file_path)"
	if [[ "${blkdev_type}" == "device" ]]; then
		blkdev="$(losetup -f --show "$(_nvme_def_file_path)")"
	else
		blkdev="$(_nvme_def_file_path)"
	fi

	_create_nvmet_subsystem "${subsysnqn}" "${blkdev}" \
				"${subsys_uuid}"
	port="$(_create_nvmet_port "${nvme_trtype}")"
	_add_nvmet_subsys_to_port "${port}" "${subsysnqn}"
	_create_nvmet_host "${subsysnqn}" "${def_hostnqn}" \
			"${hostkey}" "${ctrlkey}"
}

_nvmet_passthru_target_setup() {
	local subsysnqn="$def_subsysnqn"
	local port

	while [[ $# -gt 0 ]]; do
		case $1 in
			--subsysnqn)
				subsysnqn="$2"
				shift 2
				;;
			*)
				echo "WARNING: unknown argument: $1"
				shift
				;;
		esac
	done

	_create_nvmet_passthru "${subsysnqn}"
	port="$(_create_nvmet_port "${nvme_trtype}")"
	_add_nvmet_subsys_to_port "${port}" "${subsysnqn}"
	_create_nvmet_host "${subsysnqn}" "${def_hostnqn}"
}

_nvmet_passthru_target_connect() {
	local subsysnqn="$def_subsysnqn"

	while [[ $# -gt 0 ]]; do
		case $1 in
			--subsysnqn)
				subsysnqn="$2"
				shift 2
				;;
			*)
				echo "WARNING: unknown argument: $1"
				shift
				;;
		esac
	done

	_nvme_connect_subsys --subsysnqn "${subsysnqn}" --no-wait || return
	nsdev=$(_find_nvme_passthru_loop_dev "${subsysnqn}")

	# The following tests can race with the creation
	# of the device so ensure the block device exists
	# before continuing
	while [ ! -b "${nsdev}" ]; do sleep 1; done

	echo "${nsdev}"
}

_nvmet_passthru_target_cleanup() {
	local subsysnqn="$def_subsysnqn"
	local ports
	local port

	while [[ $# -gt 0 ]]; do
		case $1 in
			--subsysnqn)
				subsysnqn="$2"
				shift 2
				;;
			*)
				echo "WARNING: unknown argument: $1"
				shift
				;;
		esac
	done

	_get_nvmet_ports "${subsysnqn}" ports

	for port in "${ports[@]}"; do
		_remove_nvmet_subsystem_from_port "${port}" "${subsysnqn}"
		_remove_nvmet_port "${port}"
	done

	_remove_nvmet_passhtru "${subsysnqn}"
	_remove_nvmet_host "${def_hostnqn}"
}

_discovery_genctr() {
	_nvme_discover "${nvme_trtype}" |
		sed -n -e 's/^.*Generation counter \([0-9]\+\).*$/\1/p'
}

_check_genctr() {
	local last=$1
	local msg=$2
	local genctr

	genctr=$(_discovery_genctr)
	if (( "${genctr}" <= "${last}" )); then
		echo "Generation counter not incremented when ${msg} (${genctr} <= ${last})"
	fi

	echo "${genctr}"
}

_check_uuid() {
	local nvmedev=$1
	local nr_nsid=0

	for ns in "/sys/block/${nvmedev}n"* ; do
		[ -e "${ns}/wwid" ] || continue
		nr_nsid=$((nr_nsid + 1))
		[ -e "${ns}/uuid" ] || continue
		uuid=$(cat "${ns}/uuid")
		wwid=$(cat "${ns}/wwid")
		if [ "${uuid}" != "${wwid#uuid.}" ]; then
			echo "UUID ${uuid} mismatch (wwid ${wwid})"
			return 1
		elif [ "${uuid}" != "${def_subsys_uuid}" ]; then
			echo "UUID ${uuid} mismatch with ${def_subsys_uuid})"
			return 1
		fi
	done
	if [ $nr_nsid -eq 0 ] ; then
		echo "No namespaces found"
		return 1
	fi
}

declare -A NS_DEV_FAULT_INJECT_SAVE
declare -A CTRL_DEV_FAULT_INJECT_SAVE
ns_dev_passthru_logging=off
ctrl_dev_passthru_logging=off

_nvme_passthru_logging_setup()
{
	ctrl_dev_passthru_logging=$(cat /sys/class/nvme/"$2"/passthru_err_log_enabled)
	ns_dev_passthru_logging=$(cat /sys/class/nvme/"$2"/"$1"/passthru_err_log_enabled)

	_nvme_disable_passthru_admin_error_logging "$2"
	_nvme_disable_passthru_io_error_logging "$1" "$2"
}

_nvme_passthru_logging_cleanup()
{
	echo "$ctrl_dev_passthru_logging" > /sys/class/nvme/"$2"/passthru_err_log_enabled
	echo "$ns_dev_passthru_logging" > /sys/class/nvme/"$2"/"$1"/passthru_err_log_enabled
}

_nvme_err_inject_setup()
{
        local a

        for a in /sys/kernel/debug/"$1"/fault_inject/*; do
                NS_DEV_FAULT_INJECT_SAVE[${a}]=$(<"${a}")
        done

        for a in /sys/kernel/debug/"$2"/fault_inject/*; do
                CTRL_DEV_FAULT_INJECT_SAVE[${a}]=$(<"${a}")
        done
}

_nvme_err_inject_cleanup()
{
        local a

        for a in /sys/kernel/debug/"$1"/fault_inject/*; do
                echo "${NS_DEV_FAULT_INJECT_SAVE[${a}]}" > "${a}"
        done

        for a in /sys/kernel/debug/"$2"/fault_inject/*; do
                echo "${CTRL_DEV_FAULT_INJECT_SAVE[${a}]}" > "${a}"
        done
}

_nvme_enable_err_inject()
{
        echo "$2" > /sys/kernel/debug/"$1"/fault_inject/verbose
        echo "$3" > /sys/kernel/debug/"$1"/fault_inject/probability
        echo "$4" > /sys/kernel/debug/"$1"/fault_inject/dont_retry
        echo "$5" > /sys/kernel/debug/"$1"/fault_inject/status
        echo "$6" > /sys/kernel/debug/"$1"/fault_inject/times
}

_nvme_disable_err_inject()
{
        echo 0 > /sys/kernel/debug/"$1"/fault_inject/probability
        echo 0 > /sys/kernel/debug/"$1"/fault_inject/times
}

_nvme_enable_passthru_admin_error_logging()
{
	echo on > /sys/class/nvme/"$1"/passthru_err_log_enabled
}

_nvme_enable_passthru_io_error_logging()
{
	echo on > /sys/class/nvme/"$2"/"$1"/passthru_err_log_enabled
}

_nvme_disable_passthru_admin_error_logging()
{
	echo off > /sys/class/nvme/"$1"/passthru_err_log_enabled
}

_nvme_disable_passthru_io_error_logging()
{
	echo off > /sys/class/nvme/"$2"/"$1"/passthru_err_log_enabled
}

_nvme_reset_ctrl() {
	echo 1 > /sys/class/nvme/"$1"/reset_controller
}

_nvme_delete_ctrl() {
	echo 1 > /sys/class/nvme/"$1"/delete_controller
}
