Loading Documentation/devicetree/bindings/iommu/arm,smmu.txt +7 −0 Original line number Diff line number Diff line Loading @@ -146,6 +146,13 @@ conditions. we can choose to have a single ASID associated with all domains for a context bank. - qcom,testbus-version: Testbus implementation is different in some hardware for eg some doesn't have a separate register for programming tbu testbuses so, they share the same register to program both tcu and tbu testbuses. on such hardware this option can be used to specify the testbus version to support testbus interface. Type is <u32>. - clocks : List of clocks to be used during SMMU register access. See Documentation/devicetree/bindings/clock/clock-bindings.txt for information about the format. For each clock specified Loading arch/arm64/configs/vendor/trinket_defconfig +1 −0 Original line number Diff line number Diff line Loading @@ -570,6 +570,7 @@ CONFIG_MSM_QMP=y CONFIG_IOMMU_IO_PGTABLE_FAST=y CONFIG_ARM_SMMU=y CONFIG_IOMMU_TLBSYNC_DEBUG=y CONFIG_ARM_SMMU_TESTBUS_DUMP=y CONFIG_QCOM_LAZY_MAPPING=y CONFIG_IOMMU_DEBUG=y CONFIG_IOMMU_DEBUG_TRACKING=y Loading drivers/iommu/Kconfig +10 −0 Original line number Diff line number Diff line Loading @@ -381,6 +381,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 bool "Reference counted iommu-mapping support" depends on ION Loading drivers/iommu/Makefile +1 −1 Original line number Diff line number Diff line Loading @@ -15,7 +15,7 @@ obj-$(CONFIG_IOMMU_DEBUG) += iommu-debug.o obj-$(CONFIG_MSM_IOMMU) += msm_iommu.o obj-$(CONFIG_AMD_IOMMU) += amd_iommu.o amd_iommu_init.o obj-$(CONFIG_AMD_IOMMU_V2) += amd_iommu_v2.o obj-$(CONFIG_ARM_SMMU) += arm-smmu.o arm-smmu-errata.o obj-$(CONFIG_ARM_SMMU) += arm-smmu.o arm-smmu-errata.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 Loading drivers/iommu/arm-smmu-debug.c 0 → 100644 +233 −0 Original line number Diff line number Diff line /* * Copyright (c) 2019, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and * only version 2 as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * */ #include <linux/kernel.h> #include <linux/io.h> #include <linux/device.h> #include "arm-smmu-regs.h" #include "arm-smmu-debug.h" u32 arm_smmu_debug_tbu_testbus_select(void __iomem *tbu_base, void __iomem *tcu_base, u32 testbus_version, bool write, u32 val) { void __iomem *base; int offset; if (testbus_version == 1) { base = tcu_base; offset = ARM_SMMU_TESTBUS_SEL_HLOS1_NS; } else { base = tbu_base; offset = DEBUG_TESTBUS_SEL_TBU; } if (write) { writel_relaxed(val, base + offset); /* Make sure tbu select register is written to */ wmb(); } else { return readl_relaxed(base + offset); } return 0; } u32 arm_smmu_debug_tbu_testbus_output(void __iomem *tbu_base, u32 testbus_version) { int offset = (testbus_version == 1) ? CLIENT_DEBUG_SR_HALT_ACK : DEBUG_TESTBUS_TBU; return readl_relaxed(tbu_base + offset); } 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, void __iomem *tcu_base, u32 testbus_version) { int i; u32 reg; for (i = 0 ; i < TBU_QNS4_BRIDGE_SIZE; ++i) { reg = arm_smmu_debug_tbu_testbus_select(tbu_base, tcu_base, testbus_version, READ, 0); reg = (reg & ~GENMASK(4, 0)) | i << 0; arm_smmu_debug_tbu_testbus_select(tbu_base, tcu_base, testbus_version, WRITE, reg); dev_info(dev, "testbus_sel: 0x%lx Index: %d val: 0x%llx\n", arm_smmu_debug_tbu_testbus_select(tbu_base, tcu_base, testbus_version, READ, 0), i, arm_smmu_debug_tbu_testbus_output(tbu_base, testbus_version)); } } static void arm_smmu_debug_program_tbu_testbus(void __iomem *tbu_base, void __iomem *tcu_base, u32 testbus_version, int tbu_testbus) { u32 reg; reg = arm_smmu_debug_tbu_testbus_select(tbu_base, tcu_base, testbus_version, READ, 0); if (testbus_version == 1) reg = (reg & ~GENMASK(9, 0)); else reg = (reg & ~GENMASK(7, 0)); reg |= tbu_testbus; arm_smmu_debug_tbu_testbus_select(tbu_base, tcu_base, testbus_version, WRITE, reg); } void arm_smmu_debug_dump_tbu_testbus(struct device *dev, void __iomem *tbu_base, void __iomem *tcu_base, int tbu_testbus_sel, u32 testbus_version) { if (tbu_testbus_sel & TBU_CLK_GATE_CONTROLLER_TESTBUS_SEL) { dev_info(dev, "Dumping TBU clk gate controller:"); arm_smmu_debug_program_tbu_testbus(tbu_base, tcu_base, testbus_version, TBU_CLK_GATE_CONTROLLER_TESTBUS); dev_info(dev, "testbus_sel: 0x%lx val: 0x%llx\n", arm_smmu_debug_tbu_testbus_select(tbu_base, tcu_base, testbus_version, READ, 0), arm_smmu_debug_tbu_testbus_output(tbu_base, testbus_version)); } if (tbu_testbus_sel & TBU_QNS4_A2Q_TESTBUS_SEL) { dev_info(dev, "Dumping TBU qns4 a2q test bus"); arm_smmu_debug_program_tbu_testbus(tbu_base, tcu_base, testbus_version, TBU_QNS4_A2Q_TESTBUS); arm_smmu_debug_dump_tbu_qns4_testbus(dev, tbu_base, tcu_base, testbus_version); } if (tbu_testbus_sel & TBU_QNS4_Q2A_TESTBUS_SEL) { dev_info(dev, "Dumping qns4 q2a test bus"); arm_smmu_debug_program_tbu_testbus(tbu_base, tcu_base, testbus_version, TBU_QNS4_Q2A_TESTBUS); arm_smmu_debug_dump_tbu_qns4_testbus(dev, tbu_base, tcu_base, testbus_version); } if (tbu_testbus_sel & TBU_MULTIMASTER_QCHANNEL_TESTBUS_SEL) { dev_info(dev, "Dumping multi master qchannel:"); arm_smmu_debug_program_tbu_testbus(tbu_base, tcu_base, testbus_version, TBU_MULTIMASTER_QCHANNEL_TESTBUS); dev_info(dev, "testbus_sel: 0x%lx val: 0x%llx\n", arm_smmu_debug_tbu_testbus_select(tbu_base, tcu_base, testbus_version, READ, 0), arm_smmu_debug_tbu_testbus_output(tbu_base, testbus_version)); } } 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, ~GENMASK(7, 0), 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, ~GENMASK(7, 2), 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, ~GENMASK(7, 0), i, i + 1, 2, true); arm_smmu_debug_program_tcu_testbus(dev, base, tcu_base, ~GENMASK(1, 0), 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)); } Loading
Documentation/devicetree/bindings/iommu/arm,smmu.txt +7 −0 Original line number Diff line number Diff line Loading @@ -146,6 +146,13 @@ conditions. we can choose to have a single ASID associated with all domains for a context bank. - qcom,testbus-version: Testbus implementation is different in some hardware for eg some doesn't have a separate register for programming tbu testbuses so, they share the same register to program both tcu and tbu testbuses. on such hardware this option can be used to specify the testbus version to support testbus interface. Type is <u32>. - clocks : List of clocks to be used during SMMU register access. See Documentation/devicetree/bindings/clock/clock-bindings.txt for information about the format. For each clock specified Loading
arch/arm64/configs/vendor/trinket_defconfig +1 −0 Original line number Diff line number Diff line Loading @@ -570,6 +570,7 @@ CONFIG_MSM_QMP=y CONFIG_IOMMU_IO_PGTABLE_FAST=y CONFIG_ARM_SMMU=y CONFIG_IOMMU_TLBSYNC_DEBUG=y CONFIG_ARM_SMMU_TESTBUS_DUMP=y CONFIG_QCOM_LAZY_MAPPING=y CONFIG_IOMMU_DEBUG=y CONFIG_IOMMU_DEBUG_TRACKING=y Loading
drivers/iommu/Kconfig +10 −0 Original line number Diff line number Diff line Loading @@ -381,6 +381,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 bool "Reference counted iommu-mapping support" depends on ION Loading
drivers/iommu/Makefile +1 −1 Original line number Diff line number Diff line Loading @@ -15,7 +15,7 @@ obj-$(CONFIG_IOMMU_DEBUG) += iommu-debug.o obj-$(CONFIG_MSM_IOMMU) += msm_iommu.o obj-$(CONFIG_AMD_IOMMU) += amd_iommu.o amd_iommu_init.o obj-$(CONFIG_AMD_IOMMU_V2) += amd_iommu_v2.o obj-$(CONFIG_ARM_SMMU) += arm-smmu.o arm-smmu-errata.o obj-$(CONFIG_ARM_SMMU) += arm-smmu.o arm-smmu-errata.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 Loading
drivers/iommu/arm-smmu-debug.c 0 → 100644 +233 −0 Original line number Diff line number Diff line /* * Copyright (c) 2019, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and * only version 2 as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * */ #include <linux/kernel.h> #include <linux/io.h> #include <linux/device.h> #include "arm-smmu-regs.h" #include "arm-smmu-debug.h" u32 arm_smmu_debug_tbu_testbus_select(void __iomem *tbu_base, void __iomem *tcu_base, u32 testbus_version, bool write, u32 val) { void __iomem *base; int offset; if (testbus_version == 1) { base = tcu_base; offset = ARM_SMMU_TESTBUS_SEL_HLOS1_NS; } else { base = tbu_base; offset = DEBUG_TESTBUS_SEL_TBU; } if (write) { writel_relaxed(val, base + offset); /* Make sure tbu select register is written to */ wmb(); } else { return readl_relaxed(base + offset); } return 0; } u32 arm_smmu_debug_tbu_testbus_output(void __iomem *tbu_base, u32 testbus_version) { int offset = (testbus_version == 1) ? CLIENT_DEBUG_SR_HALT_ACK : DEBUG_TESTBUS_TBU; return readl_relaxed(tbu_base + offset); } 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, void __iomem *tcu_base, u32 testbus_version) { int i; u32 reg; for (i = 0 ; i < TBU_QNS4_BRIDGE_SIZE; ++i) { reg = arm_smmu_debug_tbu_testbus_select(tbu_base, tcu_base, testbus_version, READ, 0); reg = (reg & ~GENMASK(4, 0)) | i << 0; arm_smmu_debug_tbu_testbus_select(tbu_base, tcu_base, testbus_version, WRITE, reg); dev_info(dev, "testbus_sel: 0x%lx Index: %d val: 0x%llx\n", arm_smmu_debug_tbu_testbus_select(tbu_base, tcu_base, testbus_version, READ, 0), i, arm_smmu_debug_tbu_testbus_output(tbu_base, testbus_version)); } } static void arm_smmu_debug_program_tbu_testbus(void __iomem *tbu_base, void __iomem *tcu_base, u32 testbus_version, int tbu_testbus) { u32 reg; reg = arm_smmu_debug_tbu_testbus_select(tbu_base, tcu_base, testbus_version, READ, 0); if (testbus_version == 1) reg = (reg & ~GENMASK(9, 0)); else reg = (reg & ~GENMASK(7, 0)); reg |= tbu_testbus; arm_smmu_debug_tbu_testbus_select(tbu_base, tcu_base, testbus_version, WRITE, reg); } void arm_smmu_debug_dump_tbu_testbus(struct device *dev, void __iomem *tbu_base, void __iomem *tcu_base, int tbu_testbus_sel, u32 testbus_version) { if (tbu_testbus_sel & TBU_CLK_GATE_CONTROLLER_TESTBUS_SEL) { dev_info(dev, "Dumping TBU clk gate controller:"); arm_smmu_debug_program_tbu_testbus(tbu_base, tcu_base, testbus_version, TBU_CLK_GATE_CONTROLLER_TESTBUS); dev_info(dev, "testbus_sel: 0x%lx val: 0x%llx\n", arm_smmu_debug_tbu_testbus_select(tbu_base, tcu_base, testbus_version, READ, 0), arm_smmu_debug_tbu_testbus_output(tbu_base, testbus_version)); } if (tbu_testbus_sel & TBU_QNS4_A2Q_TESTBUS_SEL) { dev_info(dev, "Dumping TBU qns4 a2q test bus"); arm_smmu_debug_program_tbu_testbus(tbu_base, tcu_base, testbus_version, TBU_QNS4_A2Q_TESTBUS); arm_smmu_debug_dump_tbu_qns4_testbus(dev, tbu_base, tcu_base, testbus_version); } if (tbu_testbus_sel & TBU_QNS4_Q2A_TESTBUS_SEL) { dev_info(dev, "Dumping qns4 q2a test bus"); arm_smmu_debug_program_tbu_testbus(tbu_base, tcu_base, testbus_version, TBU_QNS4_Q2A_TESTBUS); arm_smmu_debug_dump_tbu_qns4_testbus(dev, tbu_base, tcu_base, testbus_version); } if (tbu_testbus_sel & TBU_MULTIMASTER_QCHANNEL_TESTBUS_SEL) { dev_info(dev, "Dumping multi master qchannel:"); arm_smmu_debug_program_tbu_testbus(tbu_base, tcu_base, testbus_version, TBU_MULTIMASTER_QCHANNEL_TESTBUS); dev_info(dev, "testbus_sel: 0x%lx val: 0x%llx\n", arm_smmu_debug_tbu_testbus_select(tbu_base, tcu_base, testbus_version, READ, 0), arm_smmu_debug_tbu_testbus_output(tbu_base, testbus_version)); } } 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, ~GENMASK(7, 0), 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, ~GENMASK(7, 2), 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, ~GENMASK(7, 0), i, i + 1, 2, true); arm_smmu_debug_program_tcu_testbus(dev, base, tcu_base, ~GENMASK(1, 0), 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)); }