Commit b9b721d1 authored by Sai Prakash Ranjan's avatar Sai Prakash Ranjan Committed by Will Deacon
Browse files

iommu/arm-smmu-qcom: Add debug support for TLB sync timeouts



TLB sync timeouts can be due to various reasons such as TBU power down
or pending TCU/TBU invalidation/sync and so on. Debugging these often
require dumping of some implementation defined registers to know the
status of TBU/TCU operations and some of these registers are not
accessible in non-secure world such as from kernel and requires SMC
calls to read them in the secure world. So, add this debug support
to dump implementation defined registers for TLB sync timeout issues.

Signed-off-by: default avatarSai Prakash Ranjan <quic_saipraka@quicinc.com>
Acked-by: default avatarRobin Murphy <robin.murphy@arm.com>
Link: https://lore.kernel.org/r/20220708094230.4349-1-quic_saipraka@quicinc.com


Signed-off-by: default avatarWill Deacon <will@kernel.org>
parent 3482c0b7
Loading
Loading
Loading
Loading
+10 −0
Original line number Diff line number Diff line
@@ -363,6 +363,16 @@ config ARM_SMMU_QCOM
	  When running on a Qualcomm platform that has the custom variant
	  of the ARM SMMU, this needs to be built into the SMMU driver.

config ARM_SMMU_QCOM_DEBUG
	bool "ARM SMMU QCOM implementation defined debug support"
	depends on ARM_SMMU_QCOM
	help
	  Support for implementation specific debug features in ARM SMMU
	  hardware found in QTI platforms.

	  Say Y here to enable debug for issues such as TLB sync timeouts
	  which requires implementation defined register dumps.

config ARM_SMMU_V3
	tristate "ARM Ltd. System MMU Version 3 (SMMUv3) Support"
	depends on ARM64
+1 −0
Original line number Diff line number Diff line
@@ -3,3 +3,4 @@ obj-$(CONFIG_QCOM_IOMMU) += qcom_iommu.o
obj-$(CONFIG_ARM_SMMU) += arm_smmu.o
arm_smmu-objs += arm-smmu.o arm-smmu-impl.o arm-smmu-nvidia.o
arm_smmu-$(CONFIG_ARM_SMMU_QCOM) += arm-smmu-qcom.o
arm_smmu-$(CONFIG_ARM_SMMU_QCOM_DEBUG) += arm-smmu-qcom-debug.o
+142 −0
Original line number Diff line number Diff line
// SPDX-License-Identifier: GPL-2.0-only
/*
 * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved.
 */

#include <linux/of_device.h>
#include <linux/qcom_scm.h>
#include <linux/ratelimit.h>

#include "arm-smmu.h"
#include "arm-smmu-qcom.h"

enum qcom_smmu_impl_reg_offset {
	QCOM_SMMU_TBU_PWR_STATUS,
	QCOM_SMMU_STATS_SYNC_INV_TBU_ACK,
	QCOM_SMMU_MMU2QSS_AND_SAFE_WAIT_CNTR,
};

struct qcom_smmu_config {
	const u32 *reg_offset;
};

void qcom_smmu_tlb_sync_debug(struct arm_smmu_device *smmu)
{
	int ret;
	u32 tbu_pwr_status, sync_inv_ack, sync_inv_progress;
	struct qcom_smmu *qsmmu = container_of(smmu, struct qcom_smmu, smmu);
	const struct qcom_smmu_config *cfg;
	static DEFINE_RATELIMIT_STATE(rs, DEFAULT_RATELIMIT_INTERVAL,
				      DEFAULT_RATELIMIT_BURST);

	if (__ratelimit(&rs)) {
		dev_err(smmu->dev, "TLB sync timed out -- SMMU may be deadlocked\n");

		cfg = qsmmu->cfg;
		if (!cfg)
			return;

		ret = qcom_scm_io_readl(smmu->ioaddr + cfg->reg_offset[QCOM_SMMU_TBU_PWR_STATUS],
					&tbu_pwr_status);
		if (ret)
			dev_err(smmu->dev,
				"Failed to read TBU power status: %d\n", ret);

		ret = qcom_scm_io_readl(smmu->ioaddr + cfg->reg_offset[QCOM_SMMU_STATS_SYNC_INV_TBU_ACK],
					&sync_inv_ack);
		if (ret)
			dev_err(smmu->dev,
				"Failed to read TBU sync/inv ack status: %d\n", ret);

		ret = qcom_scm_io_readl(smmu->ioaddr + cfg->reg_offset[QCOM_SMMU_MMU2QSS_AND_SAFE_WAIT_CNTR],
					&sync_inv_progress);
		if (ret)
			dev_err(smmu->dev,
				"Failed to read TCU syn/inv progress: %d\n", ret);

		dev_err(smmu->dev,
			"TBU: power_status %#x sync_inv_ack %#x sync_inv_progress %#x\n",
			tbu_pwr_status, sync_inv_ack, sync_inv_progress);
	}
}

/* Implementation Defined Register Space 0 register offsets */
static const u32 qcom_smmu_impl0_reg_offset[] = {
	[QCOM_SMMU_TBU_PWR_STATUS]		= 0x2204,
	[QCOM_SMMU_STATS_SYNC_INV_TBU_ACK]	= 0x25dc,
	[QCOM_SMMU_MMU2QSS_AND_SAFE_WAIT_CNTR]	= 0x2670,
};

static const struct qcom_smmu_config qcm2290_smmu_cfg = {
	.reg_offset = qcom_smmu_impl0_reg_offset,
};

static const struct qcom_smmu_config sc7180_smmu_cfg = {
	.reg_offset = qcom_smmu_impl0_reg_offset,
};

static const struct qcom_smmu_config sc7280_smmu_cfg = {
	.reg_offset = qcom_smmu_impl0_reg_offset,
};

static const struct qcom_smmu_config sc8180x_smmu_cfg = {
	.reg_offset = qcom_smmu_impl0_reg_offset,
};

static const struct qcom_smmu_config sc8280xp_smmu_cfg = {
	.reg_offset = qcom_smmu_impl0_reg_offset,
};

static const struct qcom_smmu_config sm6125_smmu_cfg = {
	.reg_offset = qcom_smmu_impl0_reg_offset,
};

static const struct qcom_smmu_config sm6350_smmu_cfg = {
	.reg_offset = qcom_smmu_impl0_reg_offset,
};

static const struct qcom_smmu_config sm8150_smmu_cfg = {
	.reg_offset = qcom_smmu_impl0_reg_offset,
};

static const struct qcom_smmu_config sm8250_smmu_cfg = {
	.reg_offset = qcom_smmu_impl0_reg_offset,
};

static const struct qcom_smmu_config sm8350_smmu_cfg = {
	.reg_offset = qcom_smmu_impl0_reg_offset,
};

static const struct qcom_smmu_config sm8450_smmu_cfg = {
	.reg_offset = qcom_smmu_impl0_reg_offset,
};

static const struct of_device_id __maybe_unused qcom_smmu_impl_debug_match[] = {
	{ .compatible = "qcom,msm8998-smmu-v2" },
	{ .compatible = "qcom,qcm2290-smmu-500", .data = &qcm2290_smmu_cfg },
	{ .compatible = "qcom,sc7180-smmu-500", .data = &sc7180_smmu_cfg },
	{ .compatible = "qcom,sc7280-smmu-500", .data = &sc7280_smmu_cfg},
	{ .compatible = "qcom,sc8180x-smmu-500", .data = &sc8180x_smmu_cfg },
	{ .compatible = "qcom,sc8280xp-smmu-500", .data = &sc8280xp_smmu_cfg },
	{ .compatible = "qcom,sdm630-smmu-v2" },
	{ .compatible = "qcom,sdm845-smmu-500" },
	{ .compatible = "qcom,sm6125-smmu-500", .data = &sm6125_smmu_cfg},
	{ .compatible = "qcom,sm6350-smmu-500", .data = &sm6350_smmu_cfg},
	{ .compatible = "qcom,sm8150-smmu-500", .data = &sm8150_smmu_cfg },
	{ .compatible = "qcom,sm8250-smmu-500", .data = &sm8250_smmu_cfg },
	{ .compatible = "qcom,sm8350-smmu-500", .data = &sm8350_smmu_cfg },
	{ .compatible = "qcom,sm8450-smmu-500", .data = &sm8450_smmu_cfg },
	{ }
};

const void *qcom_smmu_impl_data(struct arm_smmu_device *smmu)
{
	const struct of_device_id *match;
	const struct device_node *np = smmu->dev->of_node;

	match = of_match_node(qcom_smmu_impl_debug_match, np);
	if (!match)
		return NULL;

	return match->data;
}
+26 −6
Original line number Diff line number Diff line
@@ -5,23 +5,40 @@

#include <linux/acpi.h>
#include <linux/adreno-smmu-priv.h>
#include <linux/delay.h>
#include <linux/of_device.h>
#include <linux/qcom_scm.h>

#include "arm-smmu.h"
#include "arm-smmu-qcom.h"

struct qcom_smmu {
	struct arm_smmu_device smmu;
	bool bypass_quirk;
	u8 bypass_cbndx;
	u32 stall_enabled;
};
#define QCOM_DUMMY_VAL	-1

static struct qcom_smmu *to_qcom_smmu(struct arm_smmu_device *smmu)
{
	return container_of(smmu, struct qcom_smmu, smmu);
}

static void qcom_smmu_tlb_sync(struct arm_smmu_device *smmu, int page,
				int sync, int status)
{
	unsigned int spin_cnt, delay;
	u32 reg;

	arm_smmu_writel(smmu, page, sync, QCOM_DUMMY_VAL);
	for (delay = 1; delay < TLB_LOOP_TIMEOUT; delay *= 2) {
		for (spin_cnt = TLB_SPIN_COUNT; spin_cnt > 0; spin_cnt--) {
			reg = arm_smmu_readl(smmu, page, status);
			if (!(reg & ARM_SMMU_sTLBGSTATUS_GSACTIVE))
				return;
			cpu_relax();
		}
		udelay(delay);
	}

	qcom_smmu_tlb_sync_debug(smmu);
}

static void qcom_adreno_smmu_write_sctlr(struct arm_smmu_device *smmu, int idx,
		u32 reg)
{
@@ -375,6 +392,7 @@ static const struct arm_smmu_impl qcom_smmu_impl = {
	.def_domain_type = qcom_smmu_def_domain_type,
	.reset = qcom_smmu500_reset,
	.write_s2cr = qcom_smmu_write_s2cr,
	.tlb_sync = qcom_smmu_tlb_sync,
};

static const struct arm_smmu_impl qcom_adreno_smmu_impl = {
@@ -383,6 +401,7 @@ static const struct arm_smmu_impl qcom_adreno_smmu_impl = {
	.reset = qcom_smmu500_reset,
	.alloc_context_bank = qcom_adreno_smmu_alloc_context_bank,
	.write_sctlr = qcom_adreno_smmu_write_sctlr,
	.tlb_sync = qcom_smmu_tlb_sync,
};

static struct arm_smmu_device *qcom_smmu_create(struct arm_smmu_device *smmu,
@@ -399,6 +418,7 @@ static struct arm_smmu_device *qcom_smmu_create(struct arm_smmu_device *smmu,
		return ERR_PTR(-ENOMEM);

	qsmmu->smmu.impl = impl;
	qsmmu->cfg = qcom_smmu_impl_data(smmu);

	return &qsmmu->smmu;
}
+28 −0
Original line number Diff line number Diff line
/* SPDX-License-Identifier: GPL-2.0-only */
/*
 * Copyright (c) 2022, Qualcomm Innovation Center, Inc. All rights reserved.
 */

#ifndef _ARM_SMMU_QCOM_H
#define _ARM_SMMU_QCOM_H

struct qcom_smmu {
	struct arm_smmu_device smmu;
	const struct qcom_smmu_config *cfg;
	bool bypass_quirk;
	u8 bypass_cbndx;
	u32 stall_enabled;
};

#ifdef CONFIG_ARM_SMMU_QCOM_DEBUG
void qcom_smmu_tlb_sync_debug(struct arm_smmu_device *smmu);
const void *qcom_smmu_impl_data(struct arm_smmu_device *smmu);
#else
static inline void qcom_smmu_tlb_sync_debug(struct arm_smmu_device *smmu) { }
static inline const void *qcom_smmu_impl_data(struct arm_smmu_device *smmu)
{
	return NULL;
}
#endif

#endif /* _ARM_SMMU_QCOM_H */
Loading