#!/bin/bash
# SPDX-License-Identifier: GPL-3.0+
# Copyright (C) 2025 Oracle and/or its affiliates
#
# Test SCSI Atomic Writes with MD devices

. tests/scsi/rc
. common/scsi_debug
. common/xfs

DESCRIPTION="test md atomic writes"
QUICK=1

requires() {
	_have_kver 6 14 0
	_have_program mdadm
	_have_driver scsi_debug
	_have_xfs_io_atomic_write
}

test() {
	local scsi_debug_atomic_wr_max_length
	local scsi_debug_atomic_wr_gran
	local scsi_sysfs_atomic_max_bytes
	local scsi_sysfs_atomic_unit_max_bytes
	local scsi_sysfs_atomic_unit_min_bytes
	local md_atomic_max_bytes
	local md_atomic_min_bytes
	local md_sysfs_max_hw_sectors_kb
	local md_max_hw_bytes
	local md_chunk_size
	local md_chunk_size_bytes
	local md_sysfs_logical_block_size
	local md_sysfs_atomic_max_bytes
	local md_sysfs_atomic_unit_max_bytes
	local md_sysfs_atomic_unit_min_bytes
	local bytes_to_write
	local bytes_written
	local test_desc
	local scsi_0
	local scsi_1
	local scsi_2
	local scsi_3
	local scsi_dev_sysfs
	local md_dev
	local md_dev_sysfs
	local scsi_debug_params=(
		delay=0
		atomic_wr=1
		num_tgts=1
		add_host=4
		per_host_store=true
	)

	echo "Running ${TEST_NAME}"

	if ! _configure_scsi_debug "${scsi_debug_params[@]}"; then
		return 1
	fi

	scsi_0="${SCSI_DEBUG_DEVICES[0]}"
	scsi_1="${SCSI_DEBUG_DEVICES[1]}"
	scsi_2="${SCSI_DEBUG_DEVICES[2]}"
	scsi_3="${SCSI_DEBUG_DEVICES[3]}"

	scsi_dev_sysfs="/sys/block/${scsi_0}"
	scsi_sysfs_atomic_max_bytes=$(< "${scsi_dev_sysfs}"/queue/atomic_write_max_bytes)
	scsi_sysfs_atomic_unit_max_bytes=$(< "${scsi_dev_sysfs}"/queue/atomic_write_unit_max_bytes)
	scsi_sysfs_atomic_unit_min_bytes=$(< "${scsi_dev_sysfs}"/queue/atomic_write_unit_min_bytes)
	scsi_debug_atomic_wr_max_length=$(< /sys/module/scsi_debug/parameters/atomic_wr_max_length)
	scsi_debug_atomic_wr_gran=$(< /sys/module/scsi_debug/parameters/atomic_wr_gran)

	for raid_level in 0 1 10; do
		if [ "$raid_level" = 10 ]
		then
			echo y | mdadm --create /dev/md/blktests_md --level=$raid_level \
				--raid-devices=4 --force /dev/"${scsi_0}" /dev/"${scsi_1}" \
				/dev/"${scsi_2}" /dev/"${scsi_3}" 2> /dev/null 1>&2
		else
			echo y | mdadm --create /dev/md/blktests_md --level=$raid_level \
				--raid-devices=2 --force \
				/dev/"${scsi_0}" /dev/"${scsi_1}" 2> /dev/null 1>&2
		fi

		md_dev=$(readlink /dev/md/blktests_md | sed 's|\.\./||')
		md_dev_sysfs="/sys/devices/virtual/block/${md_dev}"

		md_sysfs_logical_block_size=$(< "${md_dev_sysfs}"/queue/logical_block_size)
		md_sysfs_max_hw_sectors_kb=$(< "${md_dev_sysfs}"/queue/max_hw_sectors_kb)
		md_max_hw_bytes=$(( "$md_sysfs_max_hw_sectors_kb" * 1024 ))
		md_sysfs_atomic_max_bytes=$(< "${md_dev_sysfs}"/queue/atomic_write_max_bytes)
		md_sysfs_atomic_unit_max_bytes=$(< "${md_dev_sysfs}"/queue/atomic_write_unit_max_bytes)
		md_sysfs_atomic_unit_min_bytes=$(< "${md_dev_sysfs}"/queue/atomic_write_unit_min_bytes)
		md_atomic_max_bytes=$(( "$scsi_debug_atomic_wr_max_length" * "$md_sysfs_logical_block_size" ))
		md_atomic_min_bytes=$(( "$scsi_debug_atomic_wr_gran" * "$md_sysfs_logical_block_size" ))

		test_desc="TEST 1 RAID $raid_level - Verify md sysfs atomic attributes matches scsi"
		if [ "$md_sysfs_atomic_unit_max_bytes" = "$scsi_sysfs_atomic_unit_max_bytes" ] &&
			[ "$md_sysfs_atomic_unit_min_bytes" = "$scsi_sysfs_atomic_unit_min_bytes" ]
		then
			echo "$test_desc - pass"
		else
			echo "$test_desc - fail $md_sysfs_atomic_unit_max_bytes - $scsi_sysfs_atomic_unit_max_bytes -" \
				"$md_sysfs_atomic_unit_min_bytes - $scsi_sysfs_atomic_unit_min_bytes "
		fi

		test_desc="TEST 2 RAID $raid_level - Verify sysfs atomic attributes"
		if [ "$md_max_hw_bytes" -ge "$md_sysfs_atomic_max_bytes" ] &&
			[ "$md_sysfs_atomic_max_bytes" -ge "$md_sysfs_atomic_unit_max_bytes" ] &&
			[ "$md_sysfs_atomic_unit_max_bytes" -ge "$md_sysfs_atomic_unit_min_bytes" ]
		then
			echo "$test_desc - pass"
		else
			echo "$test_desc - fail $md_max_hw_bytes - $md_sysfs_max_hw_sectors_kb -" \
				"$md_sysfs_atomic_max_bytes - $md_sysfs_atomic_unit_max_bytes -" \
				"$md_sysfs_atomic_unit_min_bytes"
		fi

		test_desc="TEST 3 RAID $raid_level - Verify md sysfs_atomic_max_bytes is less than or equal "
		test_desc+="scsi sysfs_atomic_max_bytes"
		if [ "$md_sysfs_atomic_max_bytes" -le "$scsi_sysfs_atomic_max_bytes" ]
		then
			echo "$test_desc - pass"
		else
			echo "$test_desc - fail $md_sysfs_atomic_max_bytes - $scsi_sysfs_atomic_max_bytes"
		fi

		test_desc="TEST 4 RAID $raid_level - check sysfs atomic_write_unit_max_bytes <= scsi_debug atomic_wr_max_length"
		if (("$md_sysfs_atomic_unit_max_bytes" <= "$md_atomic_max_bytes"))
		then
			echo "$test_desc - pass"
		else
			echo "$test_desc - fail $md_sysfs_atomic_unit_max_bytes - $md_atomic_max_bytes"
		fi

		test_desc="TEST 5 RAID $raid_level - check sysfs atomic_write_unit_min_bytes = scsi_debug atomic_wr_gran"
		if [ "$md_sysfs_atomic_unit_min_bytes" = "$md_atomic_min_bytes" ]
		then
			echo "$test_desc - pass"
		else
			echo "$test_desc - fail $md_sysfs_atomic_unit_min_bytes - $md_atomic_min_bytes"
		fi

		test_desc="TEST 6 RAID $raid_level - check statx stx_atomic_write_unit_min"
		statx_atomic_min=$(run_xfs_io_xstat /dev/"$md_dev" "stat.atomic_write_unit_min")
		if [ "$statx_atomic_min" = "$md_atomic_min_bytes" ]
		then
			echo "$test_desc - pass"
		else
			echo "$test_desc - fail $statx_atomic_min - $md_atomic_min_bytes"
		fi

		test_desc="TEST 7 RAID $raid_level - check statx stx_atomic_write_unit_max"
		statx_atomic_max=$(run_xfs_io_xstat /dev/"$md_dev" "stat.atomic_write_unit_max")
		if [ "$statx_atomic_max" = "$md_sysfs_atomic_unit_max_bytes" ]
		then
			echo "$test_desc - pass"
		else
			echo "$test_desc - fail $statx_atomic_max - $md_sysfs_atomic_unit_max_bytes"
		fi

		test_desc="TEST 8 RAID $raid_level - perform a pwritev2 with size of sysfs_atomic_unit_max_bytes with "
		test_desc+="RWF_ATOMIC flag - pwritev2 should be succesful"
		bytes_written=$(run_xfs_io_pwritev2_atomic /dev/"$md_dev" "$md_sysfs_atomic_unit_max_bytes")
		if [ "$bytes_written" = "$md_sysfs_atomic_unit_max_bytes" ]
		then
			echo "$test_desc - pass"
		else
			echo "$test_desc - fail $bytes_written - $md_sysfs_atomic_unit_max_bytes"
		fi

		test_desc="TEST 9 RAID $raid_level - perform a pwritev2 with size of sysfs_atomic_unit_max_bytes + 512 "
		test_desc+="bytes with RWF_ATOMIC flag - pwritev2 should not be succesful"
		bytes_to_write=$(( "${md_sysfs_atomic_unit_max_bytes}" + 512 ))
		bytes_written=$(run_xfs_io_pwritev2_atomic /dev/"$md_dev" "$bytes_to_write")
		if [ "$bytes_written" = "" ]
		then
			echo "$test_desc - pass"
		else
			echo "$test_desc - fail $bytes_written - $bytes_to_write"
		fi

		test_desc="TEST 10 RAID $raid_level - perform a pwritev2 with size of sysfs_atomic_unit_min_bytes "
		test_desc+="with RWF_ATOMIC flag - pwritev2 should be succesful"
		bytes_written=$(run_xfs_io_pwritev2_atomic /dev/"$md_dev" "$md_sysfs_atomic_unit_min_bytes")
		if [ "$bytes_written" = "$md_sysfs_atomic_unit_min_bytes" ]
		then
			echo "$test_desc - pass"
		else
			echo "$test_desc - fail $bytes_written - $md_atomic_min_bytes"
		fi

		bytes_to_write=$(( "${md_sysfs_atomic_unit_min_bytes}" - "${md_sysfs_logical_block_size}" ))
		test_desc="TEST 11 RAID $raid_level - perform a pwritev2 with a size of sysfs_atomic_unit_min_bytes - 512 "
		test_desc+="bytes with RWF_ATOMIC flag - pwritev2 should fail"
		if [ "$bytes_to_write" = 0 ]
		then
			echo "$test_desc - pass"
		else
			bytes_written=$(run_xfs_io_pwritev2_atomic /dev/"$md_dev" "$bytes_to_write")
			if [ "$bytes_written" = "" ]
			then
				echo "$test_desc - pass"
			else
				echo "$test_desc - fail $bytes_written - $bytes_to_write"
			fi
		fi

		mdadm --stop /dev/md/blktests_md  2> /dev/null 1>&2

		if [ "$raid_level" = 0 ] || [ "$raid_level" = 10 ]
		then
			md_chunk_size=$(( "$scsi_sysfs_atomic_unit_max_bytes" / 2048))

			if [ "$raid_level" = 0 ]
			then
				echo y | mdadm --create /dev/md/blktests_md --level=$raid_level \
					--raid-devices=2 --chunk="${md_chunk_size}"K --force \
					/dev/"${scsi_0}" /dev/"${scsi_1}" 2> /dev/null 1>&2
			else
				echo y | mdadm --create /dev/md/blktests_md --level=$raid_level \
					--raid-devices=4 --chunk="${md_chunk_size}"K --force \
					/dev/"${scsi_0}" /dev/"${scsi_1}" \
					/dev/"${scsi_2}" /dev/"${scsi_3}" 2> /dev/null 1>&2
			fi

			md_dev=$(readlink /dev/md/blktests_md | sed 's|\.\./||')
			md_dev_sysfs="/sys/devices/virtual/block/${md_dev}"
			md_sysfs_atomic_unit_max_bytes=$(< "${md_dev_sysfs}"/queue/atomic_write_unit_max_bytes)
			md_chunk_size_bytes=$(( "$md_chunk_size" * 1024))
			test_desc="TEST 12 RAID $raid_level - Verify chunk size "
			if [ "$md_chunk_size_bytes" -le "$md_sysfs_atomic_unit_max_bytes" ] && \
				(( md_sysfs_atomic_unit_max_bytes % md_chunk_size_bytes == 0 ))
			then
				echo "$test_desc - pass"
			else
				echo "$test_desc - fail $md_chunk_size_bytes - $md_sysfs_atomic_unit_max_bytes"
			fi

			mdadm --quiet --stop /dev/md/blktests_md
		fi
	done

	_exit_scsi_debug

	echo "Test complete"
}
