diff --git a/drivers/iommu/arm-smmu-debug.c b/drivers/iommu/arm-smmu-debug.c index 0220da38d817..9434a8515b60 100644 --- a/drivers/iommu/arm-smmu-debug.c +++ b/drivers/iommu/arm-smmu-debug.c @@ -223,3 +223,66 @@ void arm_smmu_debug_dump_tcu_testbus(struct device *dev, phys_addr_t phys_addr, arm_smmu_debug_tcu_testbus_select(phys_addr, tcu_base, CLK_TESTBUS, READ, 0)); } + +void arm_smmu_debug_set_tnx_tcr_cntl(void __iomem *tbu_base, u64 val) +{ + u64 tcr_cntl_val = readq_relaxed(tbu_base + TNX_TCR_CNTL); + + /* Don't override OT_CAPTURE configuration*/ + if (!(tcr_cntl_val & TNX_TCR_CNTL_TBU_OT_CAPTURE_EN)) + writeq_relaxed(val, tbu_base + TNX_TCR_CNTL); + else + pr_err_ratelimited("OT capture enbl, skip TCR CNTL write\n"); +} + +u64 arm_smmu_debug_get_tnx_tcr_cntl(void __iomem *tbu_base) +{ + return readq_relaxed(tbu_base + TNX_TCR_CNTL); +} + +void arm_smmu_debug_set_mask_and_match(void __iomem *tbu_base, u64 sel, + u64 mask, u64 match) +{ + writeq_relaxed(mask, tbu_base + ARM_SMMU_CAPTURE1_MASK(sel)); + writeq_relaxed(match, tbu_base + ARM_SMMU_CAPTURE1_MATCH(sel)); +} + +void arm_smmu_debug_get_mask_and_match(void __iomem *tbu_base, u64 *mask, + u64 *match) +{ + int i; + + for (i = 0; i < NO_OF_MASK_AND_MATCH; ++i) { + mask[i] = readq_relaxed(tbu_base + + ARM_SMMU_CAPTURE1_MASK(i+1)); + match[i] = readq_relaxed(tbu_base + + ARM_SMMU_CAPTURE1_MATCH(i+1)); + } +} + +void arm_smmu_debug_get_capture_snapshot(void __iomem *tbu_base, + u64 snapshot[NO_OF_CAPTURE_POINTS][REGS_PER_CAPTURE_POINT]) +{ + int i, j; + u64 valid; + + valid = readl_relaxed(tbu_base + TNX_TCR_CNTL_2); + + for (i = 0; i < NO_OF_CAPTURE_POINTS ; ++i) { + if (valid & BIT(i)) + for (j = 0; j < REGS_PER_CAPTURE_POINT; ++j) + snapshot[i][j] = readq_relaxed(tbu_base + + ARM_SMMU_CAPTURE_SNAPSHOT(i, j)); + else + for (j = 0; j < REGS_PER_CAPTURE_POINT; ++j) + snapshot[i][j] = 0xdededede; + } +} + +void arm_smmu_debug_clear_intr_and_validbits(void __iomem *tbu_base) +{ + u64 val = 0; + + val |= INTR_CLR | RESET_VALID; + writeq_relaxed(val, tbu_base + TNX_TCR_CNTL); +} diff --git a/drivers/iommu/arm-smmu-debug.h b/drivers/iommu/arm-smmu-debug.h index a1837bdad9b0..c0f39965cc2f 100644 --- a/drivers/iommu/arm-smmu-debug.h +++ b/drivers/iommu/arm-smmu-debug.h @@ -38,6 +38,28 @@ extern int tbu_testbus_sel; extern int tcu_testbus_sel; +#define TNX_TCR_CNTL 0x130 +#define TNX_TCR_CNTL_TBU_OT_CAPTURE_EN BIT(18) +#define TNX_TCR_CNTL_ALWAYS_CAPTURE BIT(15) +#define TNX_TCR_CNTL_MATCH_MASK_UPD BIT(7) +#define TNX_TCR_CNTL_MATCH_MASK_VALID BIT(6) + +#define CAPTURE1_SNAPSHOT_1 0x138 + +#define TNX_TCR_CNTL_2 0x178 +#define TNX_TCR_CNTL_2_CAP1_VALID BIT(0) + +#define ARM_SMMU_CAPTURE1_MASK(i) (0x100 + (0x8 * (i-1))) +#define ARM_SMMU_CAPTURE1_MATCH(i) (0x118 + (0x8 * (i-1))) +#define ARM_SMMU_CAPTURE_SNAPSHOT(i, j) (CAPTURE1_SNAPSHOT_1 + \ + (0x10 * (i)) + ((j) * 0x8)) + +#define NO_OF_MASK_AND_MATCH 0x3 +#define NO_OF_CAPTURE_POINTS 0x4 +#define REGS_PER_CAPTURE_POINT 0x2 +#define INTR_CLR BIT(0) +#define RESET_VALID BIT(7) + enum tcu_testbus { PTW_AND_CACHE_TESTBUS, CLK_TESTBUS, @@ -66,7 +88,15 @@ 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, phys_addr_t phys_addr, void __iomem *tcu_base, int tcu_testbus_sel); - +void arm_smmu_debug_set_tnx_tcr_cntl(void __iomem *tbu_base, u64 val); +u64 arm_smmu_debug_get_tnx_tcr_cntl(void __iomem *tbu_base); +void arm_smmu_debug_set_mask_and_match(void __iomem *tbu_base, u64 sel, + u64 mask, u64 match); +void arm_smmu_debug_get_mask_and_match(void __iomem *tbu_base, + u64 *mask, u64 *match); +void arm_smmu_debug_get_capture_snapshot(void __iomem *tbu_base, + u64 snapshot[NO_OF_CAPTURE_POINTS][REGS_PER_CAPTURE_POINT]); +void arm_smmu_debug_clear_intr_and_validbits(void __iomem *tbu_base); #else static inline u32 arm_smmu_debug_tbu_testbus_select(void __iomem *tbu_base, bool write, u32 val) @@ -95,5 +125,30 @@ static inline void arm_smmu_debug_dump_tcu_testbus(struct device *dev, int tcu_testbus_sel) { } +static inline void arm_smmu_debug_set_tnx_tcr_cntl(void __iomem *tbu_base, + u64 val) +{ +} +static inline u64 arm_smmu_debug_get_tnx_tcr_cntl(void __iomem *tbu_base) +{ + return 0; +} + +static inline void arm_smmu_debug_set_mask_and_match(void __iomem *tbu_base, u64 + sel, u64 mask, u64 match) +{ +} +static inline void arm_smmu_debug_get_mask_and_match(void __iomem *tbu_base, + u64 *mask, u64 *match) +{ +} +static inline void arm_smmu_debug_get_capture_snapshot(void __iomem *tbu_base, + u64 snapshot[NO_OF_CAPTURE_POINTS][REGS_PER_CAPTURE_POINT]) +{ +} +static inline void arm_smmu_debug_clear_intr_and_validbits(void __iomem + *tbu_base) +{ +} #endif diff --git a/drivers/iommu/arm-smmu-qcom.c b/drivers/iommu/arm-smmu-qcom.c index 3f3c6ef822c7..0b9f6995aeb8 100644 --- a/drivers/iommu/arm-smmu-qcom.c +++ b/drivers/iommu/arm-smmu-qcom.c @@ -4,6 +4,7 @@ */ #include +#include #include #include #include @@ -378,16 +379,6 @@ static const struct arm_smmu_impl qcom_smmu_impl = { #define TBU_DBG_TIMEOUT_US 100 -#define TNX_TCR_CNTL 0x130 -#define TNX_TCR_CNTL_TBU_OT_CAPTURE_EN BIT(18) -#define TNX_TCR_CNTL_ALWAYS_CAPTURE BIT(15) -#define TNX_TCR_CNTL_MATCH_MASK_UPD BIT(7) -#define TNX_TCR_CNTL_MATCH_MASK_VALID BIT(6) - -#define CAPTURE1_SNAPSHOT_1 0x138 - -#define TNX_TCR_CNTL_2 0x178 -#define TNX_TCR_CNTL_2_CAP1_VALID BIT(0) struct actlr_setting { struct arm_smmu_smr smr; @@ -426,6 +417,10 @@ static struct qsmmuv500_tbu_device *qsmmuv500_find_tbu( static DEFINE_MUTEX(capture_reg_lock); static DEFINE_SPINLOCK(testbus_lock); +#ifdef CONFIG_IOMMU_DEBUGFS +static struct dentry *debugfs_capturebus_dir; +#endif + #ifdef CONFIG_ARM_SMMU_TESTBUS_DEBUGFS static struct dentry *debugfs_testbus_dir; @@ -829,6 +824,325 @@ bug: BUG_ON(IS_ENABLED(CONFIG_IOMMU_TLBSYNC_DEBUG)); } +static ssize_t arm_smmu_debug_capturebus_snapshot_read(struct file *file, + char __user *ubuf, size_t count, loff_t *offset) +{ + struct qsmmuv500_tbu_device *tbu = file->private_data; + struct arm_smmu_device *smmu = tbu->smmu; + void __iomem *tbu_base = tbu->base; + u64 snapshot[NO_OF_CAPTURE_POINTS][REGS_PER_CAPTURE_POINT]; + char buf[400]; + ssize_t retval; + size_t buflen; + int buf_len = sizeof(buf); + int i, j; + + if (*offset) + return 0; + + memset(buf, 0, buf_len); + + if (arm_smmu_power_on(smmu->pwr)) + return -EINVAL; + + if (arm_smmu_power_on(tbu->pwr)) { + arm_smmu_power_off(smmu, smmu->pwr); + return -EINVAL; + } + + if (!mutex_trylock(&capture_reg_lock)) { + dev_warn_ratelimited(smmu->dev, + "capture bus regs in use, not dumping it.\n"); + return -EBUSY; + } + + arm_smmu_debug_get_capture_snapshot(tbu_base, snapshot); + + mutex_unlock(&capture_reg_lock); + arm_smmu_power_off(tbu->smmu, tbu->pwr); + arm_smmu_power_off(smmu, smmu->pwr); + + for (i = 0; i < NO_OF_CAPTURE_POINTS ; ++i) { + for (j = 0; j < REGS_PER_CAPTURE_POINT; ++j) { + scnprintf(buf + strlen(buf), buf_len - strlen(buf), + "Capture_%d_Snapshot_%d : 0x%0llx\n", + i+1, j+1, snapshot[i][j]); + } + } + + buflen = min(count, strlen(buf)); + if (copy_to_user(ubuf, buf, buflen)) { + dev_err_ratelimited(smmu->dev, "Couldn't copy_to_user\n"); + retval = -EFAULT; + } else { + *offset = 1; + retval = buflen; + } + + return retval; +} + +static const struct file_operations arm_smmu_debug_capturebus_snapshot_fops = { + .open = simple_open, + .read = arm_smmu_debug_capturebus_snapshot_read, +}; + +static ssize_t arm_smmu_debug_capturebus_config_write(struct file *file, + const char __user *ubuf, size_t count, loff_t *offset) +{ + struct qsmmuv500_tbu_device *tbu = file->private_data; + struct arm_smmu_device *smmu = tbu->smmu; + void __iomem *tbu_base = tbu->base; + char *comma1, *comma2; + char buf[100]; + u64 sel, mask, match, val; + + if (count >= sizeof(buf)) { + dev_err_ratelimited(smmu->dev, "Input too large\n"); + goto invalid_format; + } + + memset(buf, 0, sizeof(buf)); + + if (copy_from_user(buf, ubuf, count)) { + dev_err_ratelimited(smmu->dev, "Couldn't copy from user\n"); + return -EFAULT; + } + + comma1 = strnchr(buf, count, ','); + if (!comma1) + goto invalid_format; + + *comma1 = '\0'; + + if (kstrtou64(buf, 0, &sel)) + goto invalid_format; + + if (sel > 4) { + goto invalid_format; + } else if (sel == 4) { + if (kstrtou64(comma1 + 1, 0, &val)) + goto invalid_format; + goto program_capturebus; + } + + comma2 = strnchr(comma1 + 1, count, ','); + if (!comma2) + goto invalid_format; + + /* split up the words */ + *comma2 = '\0'; + + if (kstrtou64(comma1 + 1, 0, &mask)) + goto invalid_format; + + if (kstrtou64(comma2 + 1, 0, &match)) + goto invalid_format; + +program_capturebus: + if (arm_smmu_power_on(smmu->pwr)) + return -EINVAL; + + if (arm_smmu_power_on(tbu->pwr)) { + arm_smmu_power_off(smmu, smmu->pwr); + return -EINVAL; + } + + if (!mutex_trylock(&capture_reg_lock)) { + dev_warn_ratelimited(smmu->dev, + "capture bus regs in use, not configuring it.\n"); + return -EBUSY; + } + + if (sel == 4) + arm_smmu_debug_set_tnx_tcr_cntl(tbu_base, val); + else + arm_smmu_debug_set_mask_and_match(tbu_base, sel, mask, match); + + mutex_unlock(&capture_reg_lock); + arm_smmu_power_off(tbu->smmu, tbu->pwr); + arm_smmu_power_off(smmu, smmu->pwr); + + return count; + +invalid_format: + dev_err_ratelimited(smmu->dev, "Invalid format\n"); + dev_err_ratelimited(smmu->dev, + "Expected:<1/2/3,Mask,Match> <4,TNX_TCR_CNTL>\n"); + return -EINVAL; +} + +static ssize_t arm_smmu_debug_capturebus_config_read(struct file *file, + char __user *ubuf, size_t count, loff_t *offset) +{ + struct qsmmuv500_tbu_device *tbu = file->private_data; + struct arm_smmu_device *smmu = tbu->smmu; + void __iomem *tbu_base = tbu->base; + u64 val; + u64 mask[NO_OF_MASK_AND_MATCH], match[NO_OF_MASK_AND_MATCH]; + char buf[400]; + ssize_t retval; + size_t buflen; + int buf_len = sizeof(buf); + int i; + + if (*offset) + return 0; + + memset(buf, 0, buf_len); + + if (arm_smmu_power_on(smmu->pwr)) + return -EINVAL; + + if (arm_smmu_power_on(tbu->pwr)) { + arm_smmu_power_off(smmu, smmu->pwr); + return -EINVAL; + } + + if (!mutex_trylock(&capture_reg_lock)) { + dev_warn_ratelimited(smmu->dev, + "capture bus regs in use, not configuring it.\n"); + return -EBUSY; + } + + arm_smmu_debug_get_mask_and_match(tbu_base, + mask, match); + val = arm_smmu_debug_get_tnx_tcr_cntl(tbu_base); + + mutex_unlock(&capture_reg_lock); + arm_smmu_power_off(tbu->smmu, tbu->pwr); + arm_smmu_power_off(smmu, smmu->pwr); + + for (i = 0; i < NO_OF_MASK_AND_MATCH; ++i) { + scnprintf(buf + strlen(buf), buf_len - strlen(buf), + "Mask_%d : 0x%0llx\t", i+1, mask[i]); + scnprintf(buf + strlen(buf), buf_len - strlen(buf), + "Match_%d : 0x%0llx\n", i+1, match[i]); + } + scnprintf(buf + strlen(buf), buf_len - strlen(buf), "0x%0lx\n", val); + + buflen = min(count, strlen(buf)); + if (copy_to_user(ubuf, buf, buflen)) { + dev_err_ratelimited(smmu->dev, "Couldn't copy_to_user\n"); + retval = -EFAULT; + } else { + *offset = 1; + retval = buflen; + } + + return retval; +} + +static const struct file_operations arm_smmu_debug_capturebus_config_fops = { + .open = simple_open, + .write = arm_smmu_debug_capturebus_config_write, + .read = arm_smmu_debug_capturebus_config_read, +}; + +#ifdef CONFIG_IOMMU_DEBUGFS +static int qsmmuv500_capturebus_init(struct qsmmuv500_tbu_device *tbu) +{ + struct dentry *capturebus_dir; + + if (!iommu_debugfs_dir) + return 0; + + if (!debugfs_capturebus_dir) { + debugfs_capturebus_dir = debugfs_create_dir( + "capturebus", iommu_debugfs_dir); + if (IS_ERR(debugfs_capturebus_dir)) { + dev_err_ratelimited(tbu->dev, "Couldn't create iommu/capturebus debugfs directory\n"); + return PTR_ERR(debugfs_capturebus_dir); + } + } + + capturebus_dir = debugfs_create_dir(dev_name(tbu->dev), + debugfs_capturebus_dir); + if (IS_ERR(capturebus_dir)) { + dev_err_ratelimited(tbu->dev, "Couldn't create iommu/capturebus/%s debugfs directory\n", + dev_name(tbu->dev)); + goto err; + } + + if (IS_ERR(debugfs_create_file("config", 0400, capturebus_dir, tbu, + &arm_smmu_debug_capturebus_config_fops))) { + dev_err_ratelimited(tbu->dev, "Couldn't create iommu/capturebus/%s/config debugfs file\n", + dev_name(tbu->dev)); + goto err_rmdir; + } + + if (IS_ERR(debugfs_create_file("snapshot", 0400, capturebus_dir, tbu, + &arm_smmu_debug_capturebus_snapshot_fops))) { + dev_err_ratelimited(tbu->dev, "Couldn't create iommu/capturebus/%s/snapshot debugfs file\n", + dev_name(tbu->dev)); + goto err_rmdir; + } + return 0; +err_rmdir: + debugfs_remove_recursive(capturebus_dir); +err: + return -ENODEV; +} +#else +static int qsmmuv500_capturebus_init(struct qsmmuv500_tbu_device *tbu) +{ + return 0; +} +#endif + +static irqreturn_t arm_smmu_debug_capture_bus_match(int irq, void *dev) +{ + struct qsmmuv500_tbu_device *tbu = dev; + struct arm_smmu_device *smmu = tbu->smmu; + void __iomem *tbu_base = tbu->base; + u64 mask[NO_OF_MASK_AND_MATCH], match[NO_OF_MASK_AND_MATCH]; + u64 snapshot[NO_OF_CAPTURE_POINTS][REGS_PER_CAPTURE_POINT]; + int i, j; + u64 val; + + if (arm_smmu_power_on(smmu->pwr)) + return IRQ_NONE; + + if (arm_smmu_power_on(tbu->pwr)) { + arm_smmu_power_off(smmu, smmu->pwr); + return IRQ_NONE; + } + + if (!mutex_trylock(&capture_reg_lock)) { + dev_warn_ratelimited(smmu->dev, + "capture bus regs in use, not dumping it.\n"); + return IRQ_NONE; + } + + val = arm_smmu_debug_get_tnx_tcr_cntl(tbu_base); + arm_smmu_debug_get_mask_and_match(tbu_base, mask, match); + arm_smmu_debug_get_capture_snapshot(tbu_base, snapshot); + arm_smmu_debug_clear_intr_and_validbits(tbu_base); + + mutex_unlock(&capture_reg_lock); + arm_smmu_power_off(tbu->smmu, tbu->pwr); + arm_smmu_power_off(smmu, smmu->pwr); + + dev_info(tbu->dev, "TNX_TCR_CNTL : 0x%0llx\n", val); + + for (i = 0; i < NO_OF_MASK_AND_MATCH; ++i) { + dev_info(tbu->dev, + "Mask_%d : 0x%0llx\n", i+1, mask[i]); + dev_info(tbu->dev, + "Match_%d : 0x%0llx\n", i+1, match[i]); + } + + for (i = 0; i < NO_OF_CAPTURE_POINTS ; ++i) { + for (j = 0; j < REGS_PER_CAPTURE_POINT; ++j) { + dev_info(tbu->dev, + "Capture_%d_Snapshot_%d : 0x%0llx\n", + i+1, j+1, snapshot[i][j]); + } + } + + return IRQ_HANDLED; +} + static void qsmmuv500_tlb_sync_timeout(struct arm_smmu_device *smmu) { u32 sync_inv_ack, tbu_pwr_status, sync_inv_progress; @@ -1385,8 +1699,11 @@ static void qsmmuv500_init_cb(struct arm_smmu_domain *smmu_domain, static int qsmmuv500_tbu_register(struct device *dev, void *cookie) { + struct resource *res; struct qsmmuv500_tbu_device *tbu; struct qsmmuv500_archdata *data = cookie; + struct platform_device *pdev = to_platform_device(dev); + int i, err, num_irqs = 0; if (!dev->driver) { dev_err(dev, "TBU failed probe, QSMMUV500 cannot continue!\n"); @@ -1399,7 +1716,36 @@ static int qsmmuv500_tbu_register(struct device *dev, void *cookie) tbu->smmu = &data->smmu; list_add(&tbu->list, &data->tbus); + while ((res = platform_get_resource(pdev, IORESOURCE_IRQ, num_irqs))) + num_irqs++; + + tbu->irqs = devm_kzalloc(dev, sizeof(*tbu->irqs) * num_irqs, + GFP_KERNEL); + if (!tbu->irqs) + return -ENOMEM; + + for (i = 0; i < num_irqs; ++i) { + int irq = platform_get_irq(pdev, i); + + if (irq < 0) { + dev_err(dev, "failed to get irq index %d\n", i); + return -ENODEV; + } + tbu->irqs[i] = irq; + + err = devm_request_threaded_irq(tbu->dev, tbu->irqs[i], + NULL, arm_smmu_debug_capture_bus_match, + IRQF_ONESHOT | IRQF_SHARED, + "capture bus", tbu); + if (err) { + dev_err(dev, "failed to request capture bus irq%d (%u)\n", + i, tbu->irqs[i]); + return err; + } + } + qsmmuv500_tbu_testbus_init(tbu); + qsmmuv500_capturebus_init(tbu); return 0; } diff --git a/drivers/iommu/arm-smmu.h b/drivers/iommu/arm-smmu.h index 05f79b0e19a9..74bdf5771724 100644 --- a/drivers/iommu/arm-smmu.h +++ b/drivers/iommu/arm-smmu.h @@ -418,6 +418,7 @@ struct qsmmuv500_tbu_device { /* Protects halt count */ spinlock_t halt_lock; u32 halt_count; + unsigned int *irqs; }; struct arm_smmu_master_cfg {