Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 9d67a36c authored by Vijayanand Jitta's avatar Vijayanand Jitta Committed by Prakash Gupta
Browse files

iommu: arm-smmu: add testbus support for smmuv500



add a debugfs interface to support testbus also add an interface for
dumping testbus information, module parameters tcu_testbus_sel and
tbu_testbus_sel can be used for selecting different testbuses to dump
on tcu and tbu respectively.

Change-Id: I1bf420db053458b2ce1f43dc544c0776a3f4d176
Signed-off-by: default avatarVijayanand Jitta <vjitta@codeaurora.org>
[isaacm@codeaurora.org: introduced CONFIG_ARM_SMMU_TESTBUS_DEBUGFS]
Signed-off-by: default avatarIsaac J. Manjarres <isaacm@codeaurora.org>
[guptap@codeaurora.org: resolved merge conflict]
Signed-off-by: default avatarPrakash Gupta <guptap@codeaurora.org>
parent 65d212e2
Loading
Loading
Loading
Loading
+19 −0
Original line number Diff line number Diff line
@@ -529,6 +529,15 @@ config ARM_SMMU_DISABLE_BYPASS_BY_DEFAULT
	  'arm-smmu.disable_bypass' will continue to override this
	  config.

config ARM_SMMU_TESTBUS_DEBUGFS
	bool "Expose testbus control debugfs nodes"
	depends on ARM_SMMU && IOMMU_DEBUGFS
	help
	  Support for exposing debugfs nodes to set testbus select values
	  for selecting a testbus to inspect for a particular TCU/TBU
	  on an SMMU. This also exposes debugfs nodes to read testbus output
	  after the output has been selected.

config ARM_SMMU_V3
	tristate "ARM Ltd. System MMU Version 3 (SMMUv3) Support"
	depends on ARM64
@@ -563,6 +572,16 @@ config IOMMU_TLBSYNC_DEBUG

	  If unsure, say N here.

config ARM_SMMU_TESTBUS_DUMP
	bool "ARM SMMU testbus dump"
	depends on ARM_SMMU
	help
	  Enables testbus dump collection on arm smmu right after TLB
	  sync timeout failure.
	  Note to use this only on debug builds.

	  If unsure, say N here.

config QCOM_LAZY_MAPPING
	tristate "Reference counted iommu-mapping support"
	depends on ION
+1 −1
Original line number Diff line number Diff line
@@ -17,7 +17,7 @@ obj-$(CONFIG_AMD_IOMMU) += amd_iommu.o amd_iommu_init.o amd_iommu_quirks.o
obj-$(CONFIG_AMD_IOMMU_DEBUGFS) += amd_iommu_debugfs.o
obj-$(CONFIG_AMD_IOMMU_V2) += amd_iommu_v2.o
obj-$(CONFIG_ARM_SMMU) += qcom-arm-smmu-mod.o
qcom-arm-smmu-mod-objs += arm-smmu.o arm-smmu-impl.o arm-smmu-qcom.o
qcom-arm-smmu-mod-objs += arm-smmu.o arm-smmu-impl.o arm-smmu-qcom.o arm-smmu-debug.o
obj-$(CONFIG_ARM_SMMU_V3) += arm-smmu-v3.o
obj-$(CONFIG_DMAR_TABLE) += dmar.o
obj-$(CONFIG_INTEL_IOMMU) += intel-iommu.o intel-pasid.o
+193 −0
Original line number Diff line number Diff line
// SPDX-License-Identifier: GPL-2.0-only
/*
 * Copyright (c) 2019, The Linux Foundation. All rights reserved.
 */

#include <linux/kernel.h>
#include <linux/io.h>
#include <linux/device.h>
#include "arm-smmu.h"
#include "arm-smmu-debug.h"


u32 arm_smmu_debug_tbu_testbus_select(void __iomem *tbu_base,
				bool write, u32 val)
{
	if (write) {
		writel_relaxed(val, tbu_base + DEBUG_TESTBUS_SEL_TBU);
		/* Make sure tbu select register is written to */
		wmb();
	} else {
		return readl_relaxed(tbu_base + DEBUG_TESTBUS_SEL_TBU);
	}
	return 0;
}

u32 arm_smmu_debug_tbu_testbus_output(void __iomem *tbu_base)
{
	return readl_relaxed(tbu_base + DEBUG_TESTBUS_TBU);
}

u32 arm_smmu_debug_tcu_testbus_select(void __iomem *base,
		void __iomem *tcu_base, enum tcu_testbus testbus,
		bool write, u32 val)
{
	int offset;

	if (testbus == CLK_TESTBUS) {
		base = tcu_base;
		offset = ARM_SMMU_TESTBUS_SEL_HLOS1_NS;
	} else {
		offset = ARM_SMMU_TESTBUS_SEL;
	}

	if (write) {
		writel_relaxed(val, base + offset);
		/* Make sure tcu select register is written to */
		wmb();
	} else {
		return readl_relaxed(base + offset);
	}

	return 0;
}

u32 arm_smmu_debug_tcu_testbus_output(void __iomem *base)
{
	return readl_relaxed(base + ARM_SMMU_TESTBUS);
}

static void arm_smmu_debug_dump_tbu_qns4_testbus(struct device *dev,
					void __iomem *tbu_base)
{
	int i;
	u32 reg;

	for (i = 0 ; i < TBU_QNS4_BRIDGE_SIZE; ++i) {
		reg = arm_smmu_debug_tbu_testbus_select(tbu_base, READ, 0);
		reg = (reg & ~TBU_QNS4_BRIDGE_MASK) | i << 0;
		arm_smmu_debug_tbu_testbus_select(tbu_base, WRITE, reg);
		dev_info(dev, "testbus_sel: 0x%lx Index: %d val: 0x%llx\n",
			arm_smmu_debug_tbu_testbus_select(tbu_base,
						READ, 0), i,
			arm_smmu_debug_tbu_testbus_output(tbu_base));
	}
}

static void arm_smmu_debug_program_tbu_testbus(void __iomem *tbu_base,
					int tbu_testbus)
{
	u32 reg;

	reg = arm_smmu_debug_tbu_testbus_select(tbu_base, READ, 0);
	reg = (reg & ~TCU_PTW_QUEUE_MASK) | tbu_testbus;
	arm_smmu_debug_tbu_testbus_select(tbu_base, WRITE, reg);
}

void arm_smmu_debug_dump_tbu_testbus(struct device *dev, void __iomem *tbu_base,
			int tbu_testbus_sel)
{
	if (tbu_testbus_sel & TBU_CLK_GATE_CONTROLLER_TESTBUS_SEL) {
		dev_info(dev, "Dumping TBU clk gate controller:\n");
		arm_smmu_debug_program_tbu_testbus(tbu_base,
				TBU_CLK_GATE_CONTROLLER_TESTBUS);
		dev_info(dev, "testbus_sel: 0x%lx val: 0x%llx\n",
			arm_smmu_debug_tbu_testbus_select(tbu_base,
						READ, 0),
			arm_smmu_debug_tbu_testbus_output(tbu_base));
	}

	if (tbu_testbus_sel & TBU_QNS4_A2Q_TESTBUS_SEL) {
		dev_info(dev, "Dumping TBU qns4 a2q test bus:\n");
		arm_smmu_debug_program_tbu_testbus(tbu_base,
				TBU_QNS4_A2Q_TESTBUS);
		arm_smmu_debug_dump_tbu_qns4_testbus(dev, tbu_base);
	}

	if (tbu_testbus_sel & TBU_QNS4_Q2A_TESTBUS_SEL) {
		dev_info(dev, "Dumping qns4 q2a test bus:\n");
		arm_smmu_debug_program_tbu_testbus(tbu_base,
				TBU_QNS4_Q2A_TESTBUS);
		arm_smmu_debug_dump_tbu_qns4_testbus(dev, tbu_base);
	}

	if (tbu_testbus_sel & TBU_MULTIMASTER_QCHANNEL_TESTBUS_SEL) {
		dev_info(dev, "Dumping multi master qchannel:\n");
		arm_smmu_debug_program_tbu_testbus(tbu_base,
				TBU_MULTIMASTER_QCHANNEL_TESTBUS);
		dev_info(dev, "testbus_sel: 0x%lx val: 0x%llx\n",
			arm_smmu_debug_tbu_testbus_select(tbu_base,
						READ, 0),
			arm_smmu_debug_tbu_testbus_output(tbu_base));
	}
}

static void arm_smmu_debug_program_tcu_testbus(struct device *dev,
		void __iomem *base, void __iomem *tcu_base,
		unsigned long mask, int start, int end, int shift,
		bool print)
{
	u32 reg;
	int i;

	for (i = start; i < end; i++) {
		reg = arm_smmu_debug_tcu_testbus_select(base, tcu_base,
				PTW_AND_CACHE_TESTBUS, READ, 0);
		reg &= mask;
		reg |= i << shift;
		arm_smmu_debug_tcu_testbus_select(base, tcu_base,
				PTW_AND_CACHE_TESTBUS, WRITE, reg);
		if (print)
			dev_info(dev, "testbus_sel: 0x%lx Index: %d val: 0x%lx\n",
				 arm_smmu_debug_tcu_testbus_select(base,
				 tcu_base, PTW_AND_CACHE_TESTBUS, READ, 0),
				 i, arm_smmu_debug_tcu_testbus_output(base));
	}
}

void arm_smmu_debug_dump_tcu_testbus(struct device *dev, void __iomem *base,
			void __iomem *tcu_base, int tcu_testbus_sel)
{
	int i;

	if (tcu_testbus_sel & TCU_CACHE_TESTBUS_SEL) {
		dev_info(dev, "Dumping TCU cache testbus:\n");
		arm_smmu_debug_program_tcu_testbus(dev, base, tcu_base,
				TCU_CACHE_TESTBUS, 0, 1, 0, false);
		arm_smmu_debug_program_tcu_testbus(dev, base, tcu_base,
						   ~TCU_PTW_QUEUE_MASK, 0,
						   TCU_CACHE_LOOKUP_QUEUE_SIZE,
						   2, true);
	}

	if (tcu_testbus_sel & TCU_PTW_TESTBUS_SEL) {
		dev_info(dev, "Dumping TCU PTW test bus:\n");
		arm_smmu_debug_program_tcu_testbus(dev, base, tcu_base, 1,
				TCU_PTW_TESTBUS, TCU_PTW_TESTBUS + 1, 0, false);

		arm_smmu_debug_program_tcu_testbus(dev, base, tcu_base,
						~TCU_PTW_INTERNAL_STATES_MASK,
						   0, TCU_PTW_INTERNAL_STATES,
						   2, true);

		for (i = TCU_PTW_QUEUE_START;
			i < TCU_PTW_QUEUE_START + TCU_PTW_QUEUE_SIZE; ++i) {
			arm_smmu_debug_program_tcu_testbus(dev, base, tcu_base,
					~TCU_PTW_QUEUE_MASK, i, i + 1, 2, true);
			arm_smmu_debug_program_tcu_testbus(dev, base, tcu_base,
					~TCU_PTW_TESTBUS_SEL2_MASK, TCU_PTW_TESTBUS_SEL2,
					TCU_PTW_TESTBUS_SEL2 + 1, 0, false);
			dev_info(dev, "testbus_sel: 0x%lx Index: %d val: 0x%lx\n",
				 arm_smmu_debug_tcu_testbus_select(base,
				 tcu_base, PTW_AND_CACHE_TESTBUS, READ, 0),
				 i, arm_smmu_debug_tcu_testbus_output(base));
		}
	}

	/* program ARM_SMMU_TESTBUS_SEL_HLOS1_NS to select TCU clk testbus*/
	arm_smmu_debug_tcu_testbus_select(base, tcu_base,
			CLK_TESTBUS, WRITE, TCU_CLK_TESTBUS_SEL);
	dev_info(dev, "Programming Tcu clk gate controller: testbus_sel: 0x%lx\n",
		arm_smmu_debug_tcu_testbus_select(base, tcu_base,
						CLK_TESTBUS, READ, 0));
}
+99 −0
Original line number Diff line number Diff line
/* SPDX-License-Identifier: GPL-2.0-only */
/*
 * Copyright (c) 2019, The Linux Foundation. All rights reserved.
 */

#define ARM_SMMU_TESTBUS_SEL			0x25E4
#define ARM_SMMU_TESTBUS			0x25E8
#define ARM_SMMU_TESTBUS_SEL_HLOS1_NS		0x8
#define DEBUG_TESTBUS_SEL_TBU			0x50
#define DEBUG_TESTBUS_TBU			0x58

#define TCU_PTW_TESTBUS				BIT(8)
#define TCU_CACHE_TESTBUS			~TCU_PTW_TESTBUS
#define TCU_PTW_TESTBUS_SEL			BIT(1)
#define TCU_PTW_INTERNAL_STATES			3
#define TCU_PTW_INTERNAL_STATES_MASK		GENMASK(7, 2)
#define TCU_PTW_TESTBUS_SEL2			3
#define TCU_PTW_TESTBUS_SEL2_MASK		GENMASK(1, 0)
#define TCU_PTW_QUEUE_START			32
#define TCU_PTW_QUEUE_SIZE			32
#define TCU_PTW_QUEUE_MASK			GENMASK(7, 0)
#define TCU_CACHE_TESTBUS_SEL			0x1
#define TCU_CACHE_LOOKUP_QUEUE_SIZE		32
#define TCU_CLK_TESTBUS_SEL			0x200


#define TBU_CLK_GATE_CONTROLLER_TESTBUS_SEL	0x1
#define TBU_QNS4_A2Q_TESTBUS_SEL		BIT(1)
#define TBU_QNS4_Q2A_TESTBUS_SEL		BIT(2)
#define TBU_MULTIMASTER_QCHANNEL_TESTBUS_SEL	BIT(3)
#define TBU_CLK_GATE_CONTROLLER_TESTBUS		BIT(6)
#define TBU_QNS4_A2Q_TESTBUS			BIT(7)
#define TBU_QNS4_Q2A_TESTBUS			(BIT(5) | BIT(7))
#define TBU_MULTIMASTER_QCHANNEL_TESTBUS	GENMASK(7, 6)
#define TBU_QNS4_BRIDGE_SIZE			32
#define TBU_QNS4_BRIDGE_MASK			GENMASK(4, 0)

extern int tbu_testbus_sel;
extern int tcu_testbus_sel;

enum tcu_testbus {
	PTW_AND_CACHE_TESTBUS,
	CLK_TESTBUS,
};

enum testbus_sel {
	SEL_TCU,
	SEL_TBU,
};

enum testbus_ops {
	TESTBUS_SELECT,
	TESTBUS_OUTPUT,
};

#if IS_ENABLED(CONFIG_ARM_SMMU)

u32 arm_smmu_debug_tbu_testbus_select(void __iomem *tbu_base,
					bool write, u32 val);
u32 arm_smmu_debug_tbu_testbus_output(void __iomem *tbu_base);
u32 arm_smmu_debug_tcu_testbus_select(void __iomem *base,
		void __iomem *tcu_base, enum tcu_testbus testbus,
		bool write, u32 val);
u32 arm_smmu_debug_tcu_testbus_output(void __iomem *base);
void arm_smmu_debug_dump_tbu_testbus(struct device *dev, void __iomem *tbu_base,
			int tbu_testbus_sel);
void arm_smmu_debug_dump_tcu_testbus(struct device *dev, void __iomem *base,
			void __iomem *tcu_base, int tcu_testbus_sel);

#else
static inline u32 arm_smmu_debug_tbu_testbus_select(void __iomem *tbu_base,
				bool write, u32 val)
{
	return 0;
}
static inline u32 arm_smmu_debug_tbu_testbus_output(void __iomem *tbu_base)
{
	return 0;
}
u32 arm_smmu_debug_tcu_testbus_select(void __iomem *base,
		void __iomem *tcu_base, enum tcu_testbus testbus,
		bool write, u32 val)
{
}
static inline u32 arm_smmu_debug_tcu_testbus_output(void __iomem *base)
{
	return 0;
}
static inline void arm_smmu_debug_dump_tbu_testbus(struct device *dev,
			void __iomem *tbu_base, int tbu_testbus_sel)
{
}
static inline void arm_smmu_debug_dump_tcu_testbus(struct device *dev,
			void __iomem *base, void __iomem *tcu_base,
			int tcu_testbus_sel)
{
}
#endif
+338 −0
Original line number Diff line number Diff line
@@ -14,6 +14,9 @@
#include <linux/workqueue.h>

#include "arm-smmu.h"
#include "arm-smmu-debug.h"
#include <linux/debugfs.h>
#include <linux/uaccess.h>

#define ARM_SMMU_IMPL_DEF1	6

@@ -421,6 +424,331 @@ static struct qsmmuv500_tbu_device *qsmmuv500_find_tbu(
 * snapshot capture feature.
 */
static DEFINE_MUTEX(capture_reg_lock);
static DEFINE_SPINLOCK(testbus_lock);

#ifdef CONFIG_ARM_SMMU_TESTBUS_DEBUGFS
static struct dentry *debugfs_testbus_dir;

static ssize_t arm_smmu_debug_testbus_read(struct file *file,
		char __user *ubuf, size_t count, loff_t *offset,
		enum testbus_sel tbu, enum testbus_ops ops)

{
	char buf[100];
	ssize_t retval;
	size_t buflen;
	int buf_len = sizeof(buf);

	if (*offset)
		return 0;

	memset(buf, 0, buf_len);

	if (tbu == SEL_TBU) {
		struct qsmmuv500_tbu_device *tbu = file->private_data;
		void __iomem *tbu_base = tbu->base;
		long val;

		arm_smmu_power_on(tbu->pwr);
		if (ops == TESTBUS_SELECT)
			val = arm_smmu_debug_tbu_testbus_select(tbu_base,
							READ, 0);
		else
			val = arm_smmu_debug_tbu_testbus_output(tbu_base);
		arm_smmu_power_off(tbu->smmu, tbu->pwr);

		scnprintf(buf, buf_len, "0x%0x\n", val);
	} else {

		struct arm_smmu_device *smmu = file->private_data;
		struct qsmmuv500_archdata *data = to_qsmmuv500_archdata(smmu);
		void __iomem *base = arm_smmu_page(smmu, ARM_SMMU_GR0);
		void __iomem *tcu_base = data->tcu_base;

		arm_smmu_power_on(smmu->pwr);

		if (ops == TESTBUS_SELECT) {
			scnprintf(buf, buf_len, "TCU clk testbus sel: 0x%0x\n",
				  arm_smmu_debug_tcu_testbus_select(base,
					tcu_base, CLK_TESTBUS, READ, 0));
			scnprintf(buf + strlen(buf), buf_len - strlen(buf),
				  "TCU testbus sel : 0x%0x\n",
				  arm_smmu_debug_tcu_testbus_select(base,
					 tcu_base, PTW_AND_CACHE_TESTBUS,
					 READ, 0));
		} else {
			scnprintf(buf, buf_len, "0x%0x\n",
				  arm_smmu_debug_tcu_testbus_output(base));
		}

		arm_smmu_power_off(smmu, smmu->pwr);
	}
	buflen = min(count, strlen(buf));
	if (copy_to_user(ubuf, buf, buflen)) {
		pr_err_ratelimited("Couldn't copy_to_user\n");
		retval = -EFAULT;
	} else {
		*offset = 1;
		retval = buflen;
	}

	return retval;
}

static ssize_t arm_smmu_debug_tcu_testbus_sel_write(struct file *file,
		const char __user *ubuf, size_t count, loff_t *offset)
{
	struct arm_smmu_device *smmu = file->private_data;
	struct qsmmuv500_archdata *data = to_qsmmuv500_archdata(smmu);
	void __iomem *tcu_base = data->tcu_base;
	void __iomem *base = arm_smmu_page(smmu, ARM_SMMU_GR0);
	char *comma;
	char buf[100];
	u64 sel, val;

	if (count >= 100) {
		pr_err_ratelimited("Value too large\n");
		return -EINVAL;
	}

	memset(buf, 0, 100);

	if (copy_from_user(buf, ubuf, count)) {
		pr_err_ratelimited("Couldn't copy from user\n");
		return -EFAULT;
	}

	comma = strnchr(buf, count, ',');
	if (!comma)
		goto invalid_format;

	/* split up the words */
	*comma = '\0';

	if (kstrtou64(buf, 0, &sel))
		goto invalid_format;

	if (sel != 1 && sel != 2)
		goto invalid_format;

	if (kstrtou64(comma + 1, 0, &val))
		goto invalid_format;

	arm_smmu_power_on(smmu->pwr);

	if (sel == 1)
		arm_smmu_debug_tcu_testbus_select(base,
				tcu_base, CLK_TESTBUS, WRITE, val);
	else if (sel == 2)
		arm_smmu_debug_tcu_testbus_select(base,
				tcu_base, PTW_AND_CACHE_TESTBUS, WRITE, val);

	arm_smmu_power_off(smmu, smmu->pwr);

	return count;

invalid_format:
	pr_err_ratelimited("Invalid format. Expected: <1, testbus select> for tcu CLK testbus (or) <2, testbus select> for tcu PTW/CACHE testbuses\n");
	return -EINVAL;
}

static ssize_t arm_smmu_debug_tcu_testbus_sel_read(struct file *file,
		char __user *ubuf, size_t count, loff_t *offset)
{
	return arm_smmu_debug_testbus_read(file, ubuf,
			count, offset, SEL_TCU, TESTBUS_SELECT);
}

static const struct file_operations arm_smmu_debug_tcu_testbus_sel_fops = {
	.open	= simple_open,
	.write	= arm_smmu_debug_tcu_testbus_sel_write,
	.read	= arm_smmu_debug_tcu_testbus_sel_read,
};

static ssize_t arm_smmu_debug_tcu_testbus_read(struct file *file,
		char __user *ubuf, size_t count, loff_t *offset)
{
	return arm_smmu_debug_testbus_read(file, ubuf,
			count, offset, SEL_TCU, TESTBUS_OUTPUT);
}

static const struct file_operations arm_smmu_debug_tcu_testbus_fops = {
	.open	= simple_open,
	.read	= arm_smmu_debug_tcu_testbus_read,
};

static int qsmmuv500_tcu_testbus_init(struct arm_smmu_device *smmu)
{
	struct dentry *testbus_dir;

	if (!iommu_debugfs_dir)
		return 0;

	if (!debugfs_testbus_dir) {
		debugfs_testbus_dir = debugfs_create_dir("testbus",
						       iommu_debugfs_dir);
		if (IS_ERR(debugfs_testbus_dir)) {
			pr_err_ratelimited("Couldn't create iommu/testbus debugfs directory\n");
			return -ENODEV;
		}
	}

	testbus_dir = debugfs_create_dir(dev_name(smmu->dev),
				debugfs_testbus_dir);

	if (IS_ERR(testbus_dir)) {
		pr_err_ratelimited("Couldn't create iommu/testbus/%s debugfs directory\n",
		       dev_name(smmu->dev));
		goto err;
	}

	if (IS_ERR(debugfs_create_file("tcu_testbus_sel", 0400,
					testbus_dir, smmu,
					&arm_smmu_debug_tcu_testbus_sel_fops))) {
		pr_err_ratelimited("Couldn't create iommu/testbus/%s/tcu_testbus_sel debugfs file\n",
		       dev_name(smmu->dev));
		goto err_rmdir;
	}

	if (IS_ERR(debugfs_create_file("tcu_testbus_output", 0400,
					testbus_dir, smmu,
					&arm_smmu_debug_tcu_testbus_fops))) {
		pr_err_ratelimited("Couldn't create iommu/testbus/%s/tcu_testbus_output debugfs file\n",
				   dev_name(smmu->dev));
		goto err_rmdir;
	}

	return 0;
err_rmdir:
	debugfs_remove_recursive(testbus_dir);
err:
	return 0;
}

static ssize_t arm_smmu_debug_tbu_testbus_sel_write(struct file *file,
		const char __user *ubuf, size_t count, loff_t *offset)
{
	struct qsmmuv500_tbu_device *tbu = file->private_data;
	void __iomem *tbu_base = tbu->base;
	u64 val;

	if (kstrtoull_from_user(ubuf, count, 0, &val)) {
		pr_err_ratelimited("Invalid format for tbu testbus select\n");
		return -EINVAL;
	}

	arm_smmu_power_on(tbu->pwr);
	arm_smmu_debug_tbu_testbus_select(tbu_base, WRITE, val);
	arm_smmu_power_off(tbu->smmu, tbu->pwr);

	return count;
}

static ssize_t arm_smmu_debug_tbu_testbus_sel_read(struct file *file,
		char __user *ubuf, size_t count, loff_t *offset)
{
	return arm_smmu_debug_testbus_read(file, ubuf,
			count, offset, SEL_TBU, TESTBUS_SELECT);
}

static const struct file_operations arm_smmu_debug_tbu_testbus_sel_fops = {
	.open	= simple_open,
	.write	= arm_smmu_debug_tbu_testbus_sel_write,
	.read	= arm_smmu_debug_tbu_testbus_sel_read,
};

static ssize_t arm_smmu_debug_tbu_testbus_read(struct file *file,
		char __user *ubuf, size_t count, loff_t *offset)
{
	return arm_smmu_debug_testbus_read(file, ubuf,
			count, offset, SEL_TBU, TESTBUS_OUTPUT);
}

static const struct file_operations arm_smmu_debug_tbu_testbus_fops = {
	.open	= simple_open,
	.read	= arm_smmu_debug_tbu_testbus_read,
};

static int qsmmuv500_tbu_testbus_init(struct qsmmuv500_tbu_device *tbu)
{
	struct dentry *testbus_dir;

	if (!iommu_debugfs_dir)
		return 0;

	if (!debugfs_testbus_dir) {
		debugfs_testbus_dir = debugfs_create_dir("testbus",
						       iommu_debugfs_dir);
		if (IS_ERR(debugfs_testbus_dir)) {
			pr_err_ratelimited("Couldn't create iommu/testbus debugfs directory\n");
			return -ENODEV;
		}
	}

	testbus_dir = debugfs_create_dir(dev_name(tbu->dev),
				debugfs_testbus_dir);

	if (IS_ERR(testbus_dir)) {
		pr_err_ratelimited("Couldn't create iommu/testbus/%s debugfs directory\n",
		       dev_name(tbu->dev));
		goto err;
	}

	if (IS_ERR(debugfs_create_file("tbu_testbus_sel", 0400,
					testbus_dir, tbu,
					&arm_smmu_debug_tbu_testbus_sel_fops)))	{
		pr_err_ratelimited("Couldn't create iommu/testbus/%s/tbu_testbus_sel debugfs file\n",
		       dev_name(tbu->dev));
		goto err_rmdir;
	}

	if (IS_ERR(debugfs_create_file("tbu_testbus_output", 0400,
					testbus_dir, tbu,
					&arm_smmu_debug_tbu_testbus_fops))) {
		pr_err_ratelimited("Couldn't create iommu/testbus/%s/tbu_testbus_output debugfs file\n",
		       dev_name(tbu->dev));
		goto err_rmdir;
	}

	return 0;
err_rmdir:
	debugfs_remove_recursive(testbus_dir);
err:
	return 0;
}
#else
static int qsmmuv500_tcu_testbus_init(struct arm_smmu_device *smmu)
{
	return 0;
}

static int qsmmuv500_tbu_testbus_init(struct qsmmuv500_tbu_device *tbu)
{
	return 0;
}
#endif

static void arm_smmu_testbus_dump(struct arm_smmu_device *smmu, u16 sid)
{
	if (smmu->model == QCOM_SMMUV500 &&
	    IS_ENABLED(CONFIG_ARM_SMMU_TESTBUS_DUMP)) {
		struct qsmmuv500_archdata *data = to_qsmmuv500_archdata(smmu);
		struct qsmmuv500_tbu_device *tbu;

		tbu = qsmmuv500_find_tbu(smmu, sid);
		spin_lock(&testbus_lock);
		if (tbu)
			arm_smmu_debug_dump_tbu_testbus(tbu->dev,
							tbu->base,
							tbu_testbus_sel);
		else
			arm_smmu_debug_dump_tcu_testbus(smmu->dev,
							smmu->base,
							data->tcu_base,
							tcu_testbus_sel);
		spin_unlock(&testbus_lock);
	}
}

static void qsmmuv500_log_outstanding_transactions(struct work_struct *work)
{
@@ -583,8 +911,14 @@ static void qsmmuv500_tlb_sync_timeout(struct arm_smmu_device *smmu)
					tbu_sync_pending ?
					"check pending transactions on TBU"
					: "check for TBU power status");
				arm_smmu_testbus_dump(smmu,
						(u16)(tbu_id << TBUID_SHIFT));
			}
		}

		/*dump TCU testbus*/
		arm_smmu_testbus_dump(smmu, U16_MAX);

	}

	if (tcu_sync_pending) {
@@ -1064,6 +1398,8 @@ static int qsmmuv500_tbu_register(struct device *dev, void *cookie)
	INIT_LIST_HEAD(&tbu->list);
	tbu->smmu = &data->smmu;
	list_add(&tbu->list, &data->tbus);

	qsmmuv500_tbu_testbus_init(tbu);
	return 0;
}

@@ -1156,6 +1492,8 @@ struct arm_smmu_device *qsmmuv500_impl_init(struct arm_smmu_device *smmu)
	data->smmu = *smmu;
	data->smmu.impl = &qsmmuv500_impl;

	qsmmuv500_tcu_testbus_init(&data->smmu);

	ret = qsmmuv500_read_actlr_tbl(data);
	if (ret)
		return ERR_PTR(ret);
Loading