613 lines
		
	
	
		
			19 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			613 lines
		
	
	
		
			19 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
// SPDX-License-Identifier: GPL-2.0
 | 
						|
/*
 | 
						|
 * Copyright 2024 Google LLC.
 | 
						|
 */
 | 
						|
#include <kunit/test.h>
 | 
						|
#include <linux/io-pgtable.h>
 | 
						|
 | 
						|
#include "arm-smmu-v3.h"
 | 
						|
 | 
						|
struct arm_smmu_test_writer {
 | 
						|
	struct arm_smmu_entry_writer writer;
 | 
						|
	struct kunit *test;
 | 
						|
	const __le64 *init_entry;
 | 
						|
	const __le64 *target_entry;
 | 
						|
	__le64 *entry;
 | 
						|
 | 
						|
	bool invalid_entry_written;
 | 
						|
	unsigned int num_syncs;
 | 
						|
};
 | 
						|
 | 
						|
#define NUM_ENTRY_QWORDS 8
 | 
						|
#define NUM_EXPECTED_SYNCS(x) x
 | 
						|
 | 
						|
static struct arm_smmu_ste bypass_ste;
 | 
						|
static struct arm_smmu_ste abort_ste;
 | 
						|
static struct arm_smmu_device smmu = {
 | 
						|
	.features = ARM_SMMU_FEAT_STALLS | ARM_SMMU_FEAT_ATTR_TYPES_OVR
 | 
						|
};
 | 
						|
static struct mm_struct sva_mm = {
 | 
						|
	.pgd = (void *)0xdaedbeefdeadbeefULL,
 | 
						|
};
 | 
						|
 | 
						|
enum arm_smmu_test_master_feat {
 | 
						|
	ARM_SMMU_MASTER_TEST_ATS = BIT(0),
 | 
						|
	ARM_SMMU_MASTER_TEST_STALL = BIT(1),
 | 
						|
};
 | 
						|
 | 
						|
static bool arm_smmu_entry_differs_in_used_bits(const __le64 *entry,
 | 
						|
						const __le64 *used_bits,
 | 
						|
						const __le64 *target,
 | 
						|
						unsigned int length)
 | 
						|
{
 | 
						|
	bool differs = false;
 | 
						|
	unsigned int i;
 | 
						|
 | 
						|
	for (i = 0; i < length; i++) {
 | 
						|
		if ((entry[i] & used_bits[i]) != target[i])
 | 
						|
			differs = true;
 | 
						|
	}
 | 
						|
	return differs;
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
arm_smmu_test_writer_record_syncs(struct arm_smmu_entry_writer *writer)
 | 
						|
{
 | 
						|
	struct arm_smmu_test_writer *test_writer =
 | 
						|
		container_of(writer, struct arm_smmu_test_writer, writer);
 | 
						|
	__le64 *entry_used_bits;
 | 
						|
 | 
						|
	entry_used_bits = kunit_kzalloc(
 | 
						|
		test_writer->test, sizeof(*entry_used_bits) * NUM_ENTRY_QWORDS,
 | 
						|
		GFP_KERNEL);
 | 
						|
	KUNIT_ASSERT_NOT_NULL(test_writer->test, entry_used_bits);
 | 
						|
 | 
						|
	pr_debug("STE value is now set to: ");
 | 
						|
	print_hex_dump_debug("    ", DUMP_PREFIX_NONE, 16, 8,
 | 
						|
			     test_writer->entry,
 | 
						|
			     NUM_ENTRY_QWORDS * sizeof(*test_writer->entry),
 | 
						|
			     false);
 | 
						|
 | 
						|
	test_writer->num_syncs += 1;
 | 
						|
	if (!test_writer->entry[0]) {
 | 
						|
		test_writer->invalid_entry_written = true;
 | 
						|
	} else {
 | 
						|
		/*
 | 
						|
		 * At any stage in a hitless transition, the entry must be
 | 
						|
		 * equivalent to either the initial entry or the target entry
 | 
						|
		 * when only considering the bits used by the current
 | 
						|
		 * configuration.
 | 
						|
		 */
 | 
						|
		writer->ops->get_used(test_writer->entry, entry_used_bits);
 | 
						|
		KUNIT_EXPECT_FALSE(
 | 
						|
			test_writer->test,
 | 
						|
			arm_smmu_entry_differs_in_used_bits(
 | 
						|
				test_writer->entry, entry_used_bits,
 | 
						|
				test_writer->init_entry, NUM_ENTRY_QWORDS) &&
 | 
						|
				arm_smmu_entry_differs_in_used_bits(
 | 
						|
					test_writer->entry, entry_used_bits,
 | 
						|
					test_writer->target_entry,
 | 
						|
					NUM_ENTRY_QWORDS));
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
arm_smmu_v3_test_debug_print_used_bits(struct arm_smmu_entry_writer *writer,
 | 
						|
				       const __le64 *ste)
 | 
						|
{
 | 
						|
	__le64 used_bits[NUM_ENTRY_QWORDS] = {};
 | 
						|
 | 
						|
	arm_smmu_get_ste_used(ste, used_bits);
 | 
						|
	pr_debug("STE used bits: ");
 | 
						|
	print_hex_dump_debug("    ", DUMP_PREFIX_NONE, 16, 8, used_bits,
 | 
						|
			     sizeof(used_bits), false);
 | 
						|
}
 | 
						|
 | 
						|
static const struct arm_smmu_entry_writer_ops test_ste_ops = {
 | 
						|
	.sync = arm_smmu_test_writer_record_syncs,
 | 
						|
	.get_used = arm_smmu_get_ste_used,
 | 
						|
};
 | 
						|
 | 
						|
static const struct arm_smmu_entry_writer_ops test_cd_ops = {
 | 
						|
	.sync = arm_smmu_test_writer_record_syncs,
 | 
						|
	.get_used = arm_smmu_get_cd_used,
 | 
						|
};
 | 
						|
 | 
						|
static void arm_smmu_v3_test_ste_expect_transition(
 | 
						|
	struct kunit *test, const struct arm_smmu_ste *cur,
 | 
						|
	const struct arm_smmu_ste *target, unsigned int num_syncs_expected,
 | 
						|
	bool hitless)
 | 
						|
{
 | 
						|
	struct arm_smmu_ste cur_copy = *cur;
 | 
						|
	struct arm_smmu_test_writer test_writer = {
 | 
						|
		.writer = {
 | 
						|
			.ops = &test_ste_ops,
 | 
						|
		},
 | 
						|
		.test = test,
 | 
						|
		.init_entry = cur->data,
 | 
						|
		.target_entry = target->data,
 | 
						|
		.entry = cur_copy.data,
 | 
						|
		.num_syncs = 0,
 | 
						|
		.invalid_entry_written = false,
 | 
						|
 | 
						|
	};
 | 
						|
 | 
						|
	pr_debug("STE initial value: ");
 | 
						|
	print_hex_dump_debug("    ", DUMP_PREFIX_NONE, 16, 8, cur_copy.data,
 | 
						|
			     sizeof(cur_copy), false);
 | 
						|
	arm_smmu_v3_test_debug_print_used_bits(&test_writer.writer, cur->data);
 | 
						|
	pr_debug("STE target value: ");
 | 
						|
	print_hex_dump_debug("    ", DUMP_PREFIX_NONE, 16, 8, target->data,
 | 
						|
			     sizeof(cur_copy), false);
 | 
						|
	arm_smmu_v3_test_debug_print_used_bits(&test_writer.writer,
 | 
						|
					       target->data);
 | 
						|
 | 
						|
	arm_smmu_write_entry(&test_writer.writer, cur_copy.data, target->data);
 | 
						|
 | 
						|
	KUNIT_EXPECT_EQ(test, test_writer.invalid_entry_written, !hitless);
 | 
						|
	KUNIT_EXPECT_EQ(test, test_writer.num_syncs, num_syncs_expected);
 | 
						|
	KUNIT_EXPECT_MEMEQ(test, target->data, cur_copy.data, sizeof(cur_copy));
 | 
						|
}
 | 
						|
 | 
						|
static void arm_smmu_v3_test_ste_expect_non_hitless_transition(
 | 
						|
	struct kunit *test, const struct arm_smmu_ste *cur,
 | 
						|
	const struct arm_smmu_ste *target, unsigned int num_syncs_expected)
 | 
						|
{
 | 
						|
	arm_smmu_v3_test_ste_expect_transition(test, cur, target,
 | 
						|
					       num_syncs_expected, false);
 | 
						|
}
 | 
						|
 | 
						|
static void arm_smmu_v3_test_ste_expect_hitless_transition(
 | 
						|
	struct kunit *test, const struct arm_smmu_ste *cur,
 | 
						|
	const struct arm_smmu_ste *target, unsigned int num_syncs_expected)
 | 
						|
{
 | 
						|
	arm_smmu_v3_test_ste_expect_transition(test, cur, target,
 | 
						|
					       num_syncs_expected, true);
 | 
						|
}
 | 
						|
 | 
						|
static const dma_addr_t fake_cdtab_dma_addr = 0xF0F0F0F0F0F0;
 | 
						|
 | 
						|
static void arm_smmu_test_make_cdtable_ste(struct arm_smmu_ste *ste,
 | 
						|
					   unsigned int s1dss,
 | 
						|
					   const dma_addr_t dma_addr,
 | 
						|
					   enum arm_smmu_test_master_feat feat)
 | 
						|
{
 | 
						|
	bool ats_enabled = feat & ARM_SMMU_MASTER_TEST_ATS;
 | 
						|
	bool stall_enabled = feat & ARM_SMMU_MASTER_TEST_STALL;
 | 
						|
 | 
						|
	struct arm_smmu_master master = {
 | 
						|
		.ats_enabled = ats_enabled,
 | 
						|
		.cd_table.cdtab_dma = dma_addr,
 | 
						|
		.cd_table.s1cdmax = 0xFF,
 | 
						|
		.cd_table.s1fmt = STRTAB_STE_0_S1FMT_64K_L2,
 | 
						|
		.smmu = &smmu,
 | 
						|
		.stall_enabled = stall_enabled,
 | 
						|
	};
 | 
						|
 | 
						|
	arm_smmu_make_cdtable_ste(ste, &master, ats_enabled, s1dss);
 | 
						|
}
 | 
						|
 | 
						|
static void arm_smmu_v3_write_ste_test_bypass_to_abort(struct kunit *test)
 | 
						|
{
 | 
						|
	/*
 | 
						|
	 * Bypass STEs has used bits in the first two Qwords, while abort STEs
 | 
						|
	 * only have used bits in the first QWord. Transitioning from bypass to
 | 
						|
	 * abort requires two syncs: the first to set the first qword and make
 | 
						|
	 * the STE into an abort, the second to clean up the second qword.
 | 
						|
	 */
 | 
						|
	arm_smmu_v3_test_ste_expect_hitless_transition(
 | 
						|
		test, &bypass_ste, &abort_ste, NUM_EXPECTED_SYNCS(2));
 | 
						|
}
 | 
						|
 | 
						|
static void arm_smmu_v3_write_ste_test_abort_to_bypass(struct kunit *test)
 | 
						|
{
 | 
						|
	/*
 | 
						|
	 * Transitioning from abort to bypass also requires two syncs: the first
 | 
						|
	 * to set the second qword data required by the bypass STE, and the
 | 
						|
	 * second to set the first qword and switch to bypass.
 | 
						|
	 */
 | 
						|
	arm_smmu_v3_test_ste_expect_hitless_transition(
 | 
						|
		test, &abort_ste, &bypass_ste, NUM_EXPECTED_SYNCS(2));
 | 
						|
}
 | 
						|
 | 
						|
static void arm_smmu_v3_write_ste_test_cdtable_to_abort(struct kunit *test)
 | 
						|
{
 | 
						|
	struct arm_smmu_ste ste;
 | 
						|
 | 
						|
	arm_smmu_test_make_cdtable_ste(&ste, STRTAB_STE_1_S1DSS_SSID0,
 | 
						|
				       fake_cdtab_dma_addr, ARM_SMMU_MASTER_TEST_ATS);
 | 
						|
	arm_smmu_v3_test_ste_expect_hitless_transition(test, &ste, &abort_ste,
 | 
						|
						       NUM_EXPECTED_SYNCS(2));
 | 
						|
}
 | 
						|
 | 
						|
static void arm_smmu_v3_write_ste_test_abort_to_cdtable(struct kunit *test)
 | 
						|
{
 | 
						|
	struct arm_smmu_ste ste;
 | 
						|
 | 
						|
	arm_smmu_test_make_cdtable_ste(&ste, STRTAB_STE_1_S1DSS_SSID0,
 | 
						|
				       fake_cdtab_dma_addr, ARM_SMMU_MASTER_TEST_ATS);
 | 
						|
	arm_smmu_v3_test_ste_expect_hitless_transition(test, &abort_ste, &ste,
 | 
						|
						       NUM_EXPECTED_SYNCS(2));
 | 
						|
}
 | 
						|
 | 
						|
static void arm_smmu_v3_write_ste_test_cdtable_to_bypass(struct kunit *test)
 | 
						|
{
 | 
						|
	struct arm_smmu_ste ste;
 | 
						|
 | 
						|
	arm_smmu_test_make_cdtable_ste(&ste, STRTAB_STE_1_S1DSS_SSID0,
 | 
						|
				       fake_cdtab_dma_addr, ARM_SMMU_MASTER_TEST_ATS);
 | 
						|
	arm_smmu_v3_test_ste_expect_hitless_transition(test, &ste, &bypass_ste,
 | 
						|
						       NUM_EXPECTED_SYNCS(3));
 | 
						|
}
 | 
						|
 | 
						|
static void arm_smmu_v3_write_ste_test_bypass_to_cdtable(struct kunit *test)
 | 
						|
{
 | 
						|
	struct arm_smmu_ste ste;
 | 
						|
 | 
						|
	arm_smmu_test_make_cdtable_ste(&ste, STRTAB_STE_1_S1DSS_SSID0,
 | 
						|
				       fake_cdtab_dma_addr, ARM_SMMU_MASTER_TEST_ATS);
 | 
						|
	arm_smmu_v3_test_ste_expect_hitless_transition(test, &bypass_ste, &ste,
 | 
						|
						       NUM_EXPECTED_SYNCS(3));
 | 
						|
}
 | 
						|
 | 
						|
static void arm_smmu_v3_write_ste_test_cdtable_s1dss_change(struct kunit *test)
 | 
						|
{
 | 
						|
	struct arm_smmu_ste ste;
 | 
						|
	struct arm_smmu_ste s1dss_bypass;
 | 
						|
 | 
						|
	arm_smmu_test_make_cdtable_ste(&ste, STRTAB_STE_1_S1DSS_SSID0,
 | 
						|
				       fake_cdtab_dma_addr, ARM_SMMU_MASTER_TEST_ATS);
 | 
						|
	arm_smmu_test_make_cdtable_ste(&s1dss_bypass, STRTAB_STE_1_S1DSS_BYPASS,
 | 
						|
				       fake_cdtab_dma_addr, ARM_SMMU_MASTER_TEST_ATS);
 | 
						|
 | 
						|
	/*
 | 
						|
	 * Flipping s1dss on a CD table STE only involves changes to the second
 | 
						|
	 * qword of an STE and can be done in a single write.
 | 
						|
	 */
 | 
						|
	arm_smmu_v3_test_ste_expect_hitless_transition(
 | 
						|
		test, &ste, &s1dss_bypass, NUM_EXPECTED_SYNCS(1));
 | 
						|
	arm_smmu_v3_test_ste_expect_hitless_transition(
 | 
						|
		test, &s1dss_bypass, &ste, NUM_EXPECTED_SYNCS(1));
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
arm_smmu_v3_write_ste_test_s1dssbypass_to_stebypass(struct kunit *test)
 | 
						|
{
 | 
						|
	struct arm_smmu_ste s1dss_bypass;
 | 
						|
 | 
						|
	arm_smmu_test_make_cdtable_ste(&s1dss_bypass, STRTAB_STE_1_S1DSS_BYPASS,
 | 
						|
				       fake_cdtab_dma_addr, ARM_SMMU_MASTER_TEST_ATS);
 | 
						|
	arm_smmu_v3_test_ste_expect_hitless_transition(
 | 
						|
		test, &s1dss_bypass, &bypass_ste, NUM_EXPECTED_SYNCS(2));
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
arm_smmu_v3_write_ste_test_stebypass_to_s1dssbypass(struct kunit *test)
 | 
						|
{
 | 
						|
	struct arm_smmu_ste s1dss_bypass;
 | 
						|
 | 
						|
	arm_smmu_test_make_cdtable_ste(&s1dss_bypass, STRTAB_STE_1_S1DSS_BYPASS,
 | 
						|
				       fake_cdtab_dma_addr, ARM_SMMU_MASTER_TEST_ATS);
 | 
						|
	arm_smmu_v3_test_ste_expect_hitless_transition(
 | 
						|
		test, &bypass_ste, &s1dss_bypass, NUM_EXPECTED_SYNCS(2));
 | 
						|
}
 | 
						|
 | 
						|
static void arm_smmu_test_make_s2_ste(struct arm_smmu_ste *ste,
 | 
						|
				      enum arm_smmu_test_master_feat feat)
 | 
						|
{
 | 
						|
	bool ats_enabled = feat & ARM_SMMU_MASTER_TEST_ATS;
 | 
						|
	bool stall_enabled = feat & ARM_SMMU_MASTER_TEST_STALL;
 | 
						|
	struct arm_smmu_master master = {
 | 
						|
		.ats_enabled = ats_enabled,
 | 
						|
		.smmu = &smmu,
 | 
						|
		.stall_enabled = stall_enabled,
 | 
						|
	};
 | 
						|
	struct io_pgtable io_pgtable = {};
 | 
						|
	struct arm_smmu_domain smmu_domain = {
 | 
						|
		.pgtbl_ops = &io_pgtable.ops,
 | 
						|
	};
 | 
						|
 | 
						|
	io_pgtable.cfg.arm_lpae_s2_cfg.vttbr = 0xdaedbeefdeadbeefULL;
 | 
						|
	io_pgtable.cfg.arm_lpae_s2_cfg.vtcr.ps = 1;
 | 
						|
	io_pgtable.cfg.arm_lpae_s2_cfg.vtcr.tg = 2;
 | 
						|
	io_pgtable.cfg.arm_lpae_s2_cfg.vtcr.sh = 3;
 | 
						|
	io_pgtable.cfg.arm_lpae_s2_cfg.vtcr.orgn = 1;
 | 
						|
	io_pgtable.cfg.arm_lpae_s2_cfg.vtcr.irgn = 2;
 | 
						|
	io_pgtable.cfg.arm_lpae_s2_cfg.vtcr.sl = 3;
 | 
						|
	io_pgtable.cfg.arm_lpae_s2_cfg.vtcr.tsz = 4;
 | 
						|
 | 
						|
	arm_smmu_make_s2_domain_ste(ste, &master, &smmu_domain, ats_enabled);
 | 
						|
}
 | 
						|
 | 
						|
static void arm_smmu_v3_write_ste_test_s2_to_abort(struct kunit *test)
 | 
						|
{
 | 
						|
	struct arm_smmu_ste ste;
 | 
						|
 | 
						|
	arm_smmu_test_make_s2_ste(&ste, ARM_SMMU_MASTER_TEST_ATS);
 | 
						|
	arm_smmu_v3_test_ste_expect_hitless_transition(test, &ste, &abort_ste,
 | 
						|
						       NUM_EXPECTED_SYNCS(2));
 | 
						|
}
 | 
						|
 | 
						|
static void arm_smmu_v3_write_ste_test_abort_to_s2(struct kunit *test)
 | 
						|
{
 | 
						|
	struct arm_smmu_ste ste;
 | 
						|
 | 
						|
	arm_smmu_test_make_s2_ste(&ste, ARM_SMMU_MASTER_TEST_ATS);
 | 
						|
	arm_smmu_v3_test_ste_expect_hitless_transition(test, &abort_ste, &ste,
 | 
						|
						       NUM_EXPECTED_SYNCS(2));
 | 
						|
}
 | 
						|
 | 
						|
static void arm_smmu_v3_write_ste_test_s2_to_bypass(struct kunit *test)
 | 
						|
{
 | 
						|
	struct arm_smmu_ste ste;
 | 
						|
 | 
						|
	arm_smmu_test_make_s2_ste(&ste, ARM_SMMU_MASTER_TEST_ATS);
 | 
						|
	arm_smmu_v3_test_ste_expect_hitless_transition(test, &ste, &bypass_ste,
 | 
						|
						       NUM_EXPECTED_SYNCS(2));
 | 
						|
}
 | 
						|
 | 
						|
static void arm_smmu_v3_write_ste_test_bypass_to_s2(struct kunit *test)
 | 
						|
{
 | 
						|
	struct arm_smmu_ste ste;
 | 
						|
 | 
						|
	arm_smmu_test_make_s2_ste(&ste, ARM_SMMU_MASTER_TEST_ATS);
 | 
						|
	arm_smmu_v3_test_ste_expect_hitless_transition(test, &bypass_ste, &ste,
 | 
						|
						       NUM_EXPECTED_SYNCS(2));
 | 
						|
}
 | 
						|
 | 
						|
static void arm_smmu_v3_write_ste_test_s1_to_s2(struct kunit *test)
 | 
						|
{
 | 
						|
	struct arm_smmu_ste s1_ste;
 | 
						|
	struct arm_smmu_ste s2_ste;
 | 
						|
 | 
						|
	arm_smmu_test_make_cdtable_ste(&s1_ste, STRTAB_STE_1_S1DSS_SSID0,
 | 
						|
				       fake_cdtab_dma_addr, ARM_SMMU_MASTER_TEST_ATS);
 | 
						|
	arm_smmu_test_make_s2_ste(&s2_ste, ARM_SMMU_MASTER_TEST_ATS);
 | 
						|
	arm_smmu_v3_test_ste_expect_hitless_transition(test, &s1_ste, &s2_ste,
 | 
						|
						       NUM_EXPECTED_SYNCS(3));
 | 
						|
}
 | 
						|
 | 
						|
static void arm_smmu_v3_write_ste_test_s2_to_s1(struct kunit *test)
 | 
						|
{
 | 
						|
	struct arm_smmu_ste s1_ste;
 | 
						|
	struct arm_smmu_ste s2_ste;
 | 
						|
 | 
						|
	arm_smmu_test_make_cdtable_ste(&s1_ste, STRTAB_STE_1_S1DSS_SSID0,
 | 
						|
				       fake_cdtab_dma_addr, ARM_SMMU_MASTER_TEST_ATS);
 | 
						|
	arm_smmu_test_make_s2_ste(&s2_ste, ARM_SMMU_MASTER_TEST_ATS);
 | 
						|
	arm_smmu_v3_test_ste_expect_hitless_transition(test, &s2_ste, &s1_ste,
 | 
						|
						       NUM_EXPECTED_SYNCS(3));
 | 
						|
}
 | 
						|
 | 
						|
static void arm_smmu_v3_write_ste_test_non_hitless(struct kunit *test)
 | 
						|
{
 | 
						|
	struct arm_smmu_ste ste;
 | 
						|
	struct arm_smmu_ste ste_2;
 | 
						|
 | 
						|
	/*
 | 
						|
	 * Although no flow resembles this in practice, one way to force an STE
 | 
						|
	 * update to be non-hitless is to change its CD table pointer as well as
 | 
						|
	 * s1 dss field in the same update.
 | 
						|
	 */
 | 
						|
	arm_smmu_test_make_cdtable_ste(&ste, STRTAB_STE_1_S1DSS_SSID0,
 | 
						|
				       fake_cdtab_dma_addr, ARM_SMMU_MASTER_TEST_ATS);
 | 
						|
	arm_smmu_test_make_cdtable_ste(&ste_2, STRTAB_STE_1_S1DSS_BYPASS,
 | 
						|
				       0x4B4B4b4B4B, ARM_SMMU_MASTER_TEST_ATS);
 | 
						|
	arm_smmu_v3_test_ste_expect_non_hitless_transition(
 | 
						|
		test, &ste, &ste_2, NUM_EXPECTED_SYNCS(3));
 | 
						|
}
 | 
						|
 | 
						|
static void arm_smmu_v3_test_cd_expect_transition(
 | 
						|
	struct kunit *test, const struct arm_smmu_cd *cur,
 | 
						|
	const struct arm_smmu_cd *target, unsigned int num_syncs_expected,
 | 
						|
	bool hitless)
 | 
						|
{
 | 
						|
	struct arm_smmu_cd cur_copy = *cur;
 | 
						|
	struct arm_smmu_test_writer test_writer = {
 | 
						|
		.writer = {
 | 
						|
			.ops = &test_cd_ops,
 | 
						|
		},
 | 
						|
		.test = test,
 | 
						|
		.init_entry = cur->data,
 | 
						|
		.target_entry = target->data,
 | 
						|
		.entry = cur_copy.data,
 | 
						|
		.num_syncs = 0,
 | 
						|
		.invalid_entry_written = false,
 | 
						|
 | 
						|
	};
 | 
						|
 | 
						|
	pr_debug("CD initial value: ");
 | 
						|
	print_hex_dump_debug("    ", DUMP_PREFIX_NONE, 16, 8, cur_copy.data,
 | 
						|
			     sizeof(cur_copy), false);
 | 
						|
	arm_smmu_v3_test_debug_print_used_bits(&test_writer.writer, cur->data);
 | 
						|
	pr_debug("CD target value: ");
 | 
						|
	print_hex_dump_debug("    ", DUMP_PREFIX_NONE, 16, 8, target->data,
 | 
						|
			     sizeof(cur_copy), false);
 | 
						|
	arm_smmu_v3_test_debug_print_used_bits(&test_writer.writer,
 | 
						|
					       target->data);
 | 
						|
 | 
						|
	arm_smmu_write_entry(&test_writer.writer, cur_copy.data, target->data);
 | 
						|
 | 
						|
	KUNIT_EXPECT_EQ(test, test_writer.invalid_entry_written, !hitless);
 | 
						|
	KUNIT_EXPECT_EQ(test, test_writer.num_syncs, num_syncs_expected);
 | 
						|
	KUNIT_EXPECT_MEMEQ(test, target->data, cur_copy.data, sizeof(cur_copy));
 | 
						|
}
 | 
						|
 | 
						|
static void arm_smmu_v3_test_cd_expect_non_hitless_transition(
 | 
						|
	struct kunit *test, const struct arm_smmu_cd *cur,
 | 
						|
	const struct arm_smmu_cd *target, unsigned int num_syncs_expected)
 | 
						|
{
 | 
						|
	arm_smmu_v3_test_cd_expect_transition(test, cur, target,
 | 
						|
					      num_syncs_expected, false);
 | 
						|
}
 | 
						|
 | 
						|
static void arm_smmu_v3_test_cd_expect_hitless_transition(
 | 
						|
	struct kunit *test, const struct arm_smmu_cd *cur,
 | 
						|
	const struct arm_smmu_cd *target, unsigned int num_syncs_expected)
 | 
						|
{
 | 
						|
	arm_smmu_v3_test_cd_expect_transition(test, cur, target,
 | 
						|
					      num_syncs_expected, true);
 | 
						|
}
 | 
						|
 | 
						|
static void arm_smmu_test_make_s1_cd(struct arm_smmu_cd *cd, unsigned int asid)
 | 
						|
{
 | 
						|
	struct arm_smmu_master master = {
 | 
						|
		.smmu = &smmu,
 | 
						|
	};
 | 
						|
	struct io_pgtable io_pgtable = {};
 | 
						|
	struct arm_smmu_domain smmu_domain = {
 | 
						|
		.pgtbl_ops = &io_pgtable.ops,
 | 
						|
		.cd = {
 | 
						|
			.asid = asid,
 | 
						|
		},
 | 
						|
	};
 | 
						|
 | 
						|
	io_pgtable.cfg.arm_lpae_s1_cfg.ttbr = 0xdaedbeefdeadbeefULL;
 | 
						|
	io_pgtable.cfg.arm_lpae_s1_cfg.tcr.ips = 1;
 | 
						|
	io_pgtable.cfg.arm_lpae_s1_cfg.tcr.tg = 2;
 | 
						|
	io_pgtable.cfg.arm_lpae_s1_cfg.tcr.sh = 3;
 | 
						|
	io_pgtable.cfg.arm_lpae_s1_cfg.tcr.orgn = 1;
 | 
						|
	io_pgtable.cfg.arm_lpae_s1_cfg.tcr.irgn = 2;
 | 
						|
	io_pgtable.cfg.arm_lpae_s1_cfg.tcr.tsz = 4;
 | 
						|
	io_pgtable.cfg.arm_lpae_s1_cfg.mair = 0xabcdef012345678ULL;
 | 
						|
 | 
						|
	arm_smmu_make_s1_cd(cd, &master, &smmu_domain);
 | 
						|
}
 | 
						|
 | 
						|
static void arm_smmu_v3_write_cd_test_s1_clear(struct kunit *test)
 | 
						|
{
 | 
						|
	struct arm_smmu_cd cd = {};
 | 
						|
	struct arm_smmu_cd cd_2;
 | 
						|
 | 
						|
	arm_smmu_test_make_s1_cd(&cd_2, 1997);
 | 
						|
	arm_smmu_v3_test_cd_expect_non_hitless_transition(
 | 
						|
		test, &cd, &cd_2, NUM_EXPECTED_SYNCS(2));
 | 
						|
	arm_smmu_v3_test_cd_expect_non_hitless_transition(
 | 
						|
		test, &cd_2, &cd, NUM_EXPECTED_SYNCS(2));
 | 
						|
}
 | 
						|
 | 
						|
static void arm_smmu_v3_write_cd_test_s1_change_asid(struct kunit *test)
 | 
						|
{
 | 
						|
	struct arm_smmu_cd cd = {};
 | 
						|
	struct arm_smmu_cd cd_2;
 | 
						|
 | 
						|
	arm_smmu_test_make_s1_cd(&cd, 778);
 | 
						|
	arm_smmu_test_make_s1_cd(&cd_2, 1997);
 | 
						|
	arm_smmu_v3_test_cd_expect_hitless_transition(test, &cd, &cd_2,
 | 
						|
						      NUM_EXPECTED_SYNCS(1));
 | 
						|
	arm_smmu_v3_test_cd_expect_hitless_transition(test, &cd_2, &cd,
 | 
						|
						      NUM_EXPECTED_SYNCS(1));
 | 
						|
}
 | 
						|
 | 
						|
static void arm_smmu_test_make_sva_cd(struct arm_smmu_cd *cd, unsigned int asid)
 | 
						|
{
 | 
						|
	struct arm_smmu_master master = {
 | 
						|
		.smmu = &smmu,
 | 
						|
	};
 | 
						|
 | 
						|
	arm_smmu_make_sva_cd(cd, &master, &sva_mm, asid);
 | 
						|
}
 | 
						|
 | 
						|
static void arm_smmu_test_make_sva_release_cd(struct arm_smmu_cd *cd,
 | 
						|
					      unsigned int asid)
 | 
						|
{
 | 
						|
	struct arm_smmu_master master = {
 | 
						|
		.smmu = &smmu,
 | 
						|
	};
 | 
						|
 | 
						|
	arm_smmu_make_sva_cd(cd, &master, NULL, asid);
 | 
						|
}
 | 
						|
 | 
						|
static void arm_smmu_v3_write_ste_test_s1_to_s2_stall(struct kunit *test)
 | 
						|
{
 | 
						|
	struct arm_smmu_ste s1_ste;
 | 
						|
	struct arm_smmu_ste s2_ste;
 | 
						|
 | 
						|
	arm_smmu_test_make_cdtable_ste(&s1_ste, STRTAB_STE_1_S1DSS_SSID0,
 | 
						|
				       fake_cdtab_dma_addr, ARM_SMMU_MASTER_TEST_STALL);
 | 
						|
	arm_smmu_test_make_s2_ste(&s2_ste, ARM_SMMU_MASTER_TEST_STALL);
 | 
						|
	arm_smmu_v3_test_ste_expect_hitless_transition(test, &s1_ste, &s2_ste,
 | 
						|
						       NUM_EXPECTED_SYNCS(3));
 | 
						|
}
 | 
						|
 | 
						|
static void arm_smmu_v3_write_ste_test_s2_to_s1_stall(struct kunit *test)
 | 
						|
{
 | 
						|
	struct arm_smmu_ste s1_ste;
 | 
						|
	struct arm_smmu_ste s2_ste;
 | 
						|
 | 
						|
	arm_smmu_test_make_cdtable_ste(&s1_ste, STRTAB_STE_1_S1DSS_SSID0,
 | 
						|
				       fake_cdtab_dma_addr, ARM_SMMU_MASTER_TEST_STALL);
 | 
						|
	arm_smmu_test_make_s2_ste(&s2_ste, ARM_SMMU_MASTER_TEST_STALL);
 | 
						|
	arm_smmu_v3_test_ste_expect_hitless_transition(test, &s2_ste, &s1_ste,
 | 
						|
						       NUM_EXPECTED_SYNCS(3));
 | 
						|
}
 | 
						|
 | 
						|
static void arm_smmu_v3_write_cd_test_sva_clear(struct kunit *test)
 | 
						|
{
 | 
						|
	struct arm_smmu_cd cd = {};
 | 
						|
	struct arm_smmu_cd cd_2;
 | 
						|
 | 
						|
	arm_smmu_test_make_sva_cd(&cd_2, 1997);
 | 
						|
	arm_smmu_v3_test_cd_expect_non_hitless_transition(
 | 
						|
		test, &cd, &cd_2, NUM_EXPECTED_SYNCS(2));
 | 
						|
	arm_smmu_v3_test_cd_expect_non_hitless_transition(
 | 
						|
		test, &cd_2, &cd, NUM_EXPECTED_SYNCS(2));
 | 
						|
}
 | 
						|
 | 
						|
static void arm_smmu_v3_write_cd_test_sva_release(struct kunit *test)
 | 
						|
{
 | 
						|
	struct arm_smmu_cd cd;
 | 
						|
	struct arm_smmu_cd cd_2;
 | 
						|
 | 
						|
	arm_smmu_test_make_sva_cd(&cd, 1997);
 | 
						|
	arm_smmu_test_make_sva_release_cd(&cd_2, 1997);
 | 
						|
	arm_smmu_v3_test_cd_expect_hitless_transition(test, &cd, &cd_2,
 | 
						|
						      NUM_EXPECTED_SYNCS(2));
 | 
						|
	arm_smmu_v3_test_cd_expect_hitless_transition(test, &cd_2, &cd,
 | 
						|
						      NUM_EXPECTED_SYNCS(2));
 | 
						|
}
 | 
						|
 | 
						|
static struct kunit_case arm_smmu_v3_test_cases[] = {
 | 
						|
	KUNIT_CASE(arm_smmu_v3_write_ste_test_bypass_to_abort),
 | 
						|
	KUNIT_CASE(arm_smmu_v3_write_ste_test_abort_to_bypass),
 | 
						|
	KUNIT_CASE(arm_smmu_v3_write_ste_test_cdtable_to_abort),
 | 
						|
	KUNIT_CASE(arm_smmu_v3_write_ste_test_abort_to_cdtable),
 | 
						|
	KUNIT_CASE(arm_smmu_v3_write_ste_test_cdtable_to_bypass),
 | 
						|
	KUNIT_CASE(arm_smmu_v3_write_ste_test_bypass_to_cdtable),
 | 
						|
	KUNIT_CASE(arm_smmu_v3_write_ste_test_cdtable_s1dss_change),
 | 
						|
	KUNIT_CASE(arm_smmu_v3_write_ste_test_s1dssbypass_to_stebypass),
 | 
						|
	KUNIT_CASE(arm_smmu_v3_write_ste_test_stebypass_to_s1dssbypass),
 | 
						|
	KUNIT_CASE(arm_smmu_v3_write_ste_test_s2_to_abort),
 | 
						|
	KUNIT_CASE(arm_smmu_v3_write_ste_test_abort_to_s2),
 | 
						|
	KUNIT_CASE(arm_smmu_v3_write_ste_test_s2_to_bypass),
 | 
						|
	KUNIT_CASE(arm_smmu_v3_write_ste_test_bypass_to_s2),
 | 
						|
	KUNIT_CASE(arm_smmu_v3_write_ste_test_s1_to_s2),
 | 
						|
	KUNIT_CASE(arm_smmu_v3_write_ste_test_s2_to_s1),
 | 
						|
	KUNIT_CASE(arm_smmu_v3_write_ste_test_non_hitless),
 | 
						|
	KUNIT_CASE(arm_smmu_v3_write_cd_test_s1_clear),
 | 
						|
	KUNIT_CASE(arm_smmu_v3_write_cd_test_s1_change_asid),
 | 
						|
	KUNIT_CASE(arm_smmu_v3_write_ste_test_s1_to_s2_stall),
 | 
						|
	KUNIT_CASE(arm_smmu_v3_write_ste_test_s2_to_s1_stall),
 | 
						|
	KUNIT_CASE(arm_smmu_v3_write_cd_test_sva_clear),
 | 
						|
	KUNIT_CASE(arm_smmu_v3_write_cd_test_sva_release),
 | 
						|
	{},
 | 
						|
};
 | 
						|
 | 
						|
static int arm_smmu_v3_test_suite_init(struct kunit_suite *test)
 | 
						|
{
 | 
						|
	arm_smmu_make_bypass_ste(&smmu, &bypass_ste);
 | 
						|
	arm_smmu_make_abort_ste(&abort_ste);
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
static struct kunit_suite arm_smmu_v3_test_module = {
 | 
						|
	.name = "arm-smmu-v3-kunit-test",
 | 
						|
	.suite_init = arm_smmu_v3_test_suite_init,
 | 
						|
	.test_cases = arm_smmu_v3_test_cases,
 | 
						|
};
 | 
						|
kunit_test_suites(&arm_smmu_v3_test_module);
 | 
						|
 | 
						|
MODULE_IMPORT_NS(EXPORTED_FOR_KUNIT_TESTING);
 | 
						|
MODULE_DESCRIPTION("KUnit tests for arm-smmu-v3 driver");
 | 
						|
MODULE_LICENSE("GPL v2");
 |