From ec271071bd0cbcc74ac31f12f35366ce753d4578 Mon Sep 17 00:00:00 2001 From: "Isaac J. Manjarres" Date: Tue, 10 Mar 2020 15:24:16 -0700 Subject: [PATCH] iommu/arm-smmu: Record page table configuration in debug structures Log relevant information about an IOMMU domain's page table (e.g. the page table format, levels, etc) as part of the debug attachments structure for a domain. This is helpful in determining how to parse the IOMMU page tables during post-mortem analysis. Change-Id: I2ce6cb8843f1f353b53e5474e00da7e8aedc64c8 Signed-off-by: Isaac J. Manjarres --- drivers/iommu/arm-smmu.c | 36 ++++++----- drivers/iommu/iommu-logger.c | 116 +++++++++++++++++++++++++++++------ drivers/iommu/iommu-logger.h | 34 +++++++++- 3 files changed, 148 insertions(+), 38 deletions(-) diff --git a/drivers/iommu/arm-smmu.c b/drivers/iommu/arm-smmu.c index 4dffea14e0e7..0f1c998fb135 100644 --- a/drivers/iommu/arm-smmu.c +++ b/drivers/iommu/arm-smmu.c @@ -1817,23 +1817,18 @@ static int arm_smmu_init_domain_context(struct iommu_domain *domain, struct arm_smmu_cfg *cfg = &smmu_domain->cfg; unsigned long quirks = 0; struct iommu_group *group; + struct io_pgtable *iop; mutex_lock(&smmu_domain->init_mutex); if (smmu_domain->smmu) goto out_unlock; - group = iommu_group_get(dev); - ret = iommu_logger_register(&smmu_domain->logger, domain, group); - iommu_group_put(group); - if (ret) - goto out_unlock; - if (domain->type == IOMMU_DOMAIN_DMA) { ret = arm_smmu_setup_default_domain(dev, domain); if (ret) { dev_err(dev, "%s: default domain setup failed\n", __func__); - goto out_logger; + goto out_unlock; } } @@ -1890,7 +1885,7 @@ static int arm_smmu_init_domain_context(struct iommu_domain *domain, if (cfg->fmt == ARM_SMMU_CTX_FMT_NONE) { ret = -EINVAL; - goto out_logger; + goto out_unlock; } switch (smmu_domain->stage) { @@ -1938,7 +1933,7 @@ static int arm_smmu_init_domain_context(struct iommu_domain *domain, break; default: ret = -EINVAL; - goto out_logger; + goto out_unlock; } #ifdef CONFIG_IOMMU_IO_PGTABLE_FAST @@ -1952,7 +1947,7 @@ static int arm_smmu_init_domain_context(struct iommu_domain *domain, ret = arm_smmu_alloc_cb(domain, smmu, dev); if (ret < 0) - goto out_logger; + goto out_unlock; cfg->cbndx = ret; @@ -1982,6 +1977,13 @@ static int arm_smmu_init_domain_context(struct iommu_domain *domain, goto out_clear_smmu; } + group = iommu_group_get_for_dev(smmu_domain->dev); + iop = container_of(smmu_domain->pgtbl_ops, struct io_pgtable, ops); + ret = iommu_logger_register(&smmu_domain->logger, domain, group, iop); + iommu_group_put(group); + if (ret) + goto out_clear_smmu; + /* * assign any page table memory that might have been allocated * during alloc_io_pgtable_ops @@ -1995,23 +1997,23 @@ static int arm_smmu_init_domain_context(struct iommu_domain *domain, domain->geometry.aperture_end = (1UL << ias) - 1; ret = arm_smmu_adjust_domain_geometry(dev, domain); if (ret) - goto out_clear_smmu; + goto out_logger; domain->geometry.force_aperture = true; if (domain->type == IOMMU_DOMAIN_DMA) { ret = arm_smmu_get_dma_cookie(dev, smmu_domain); if (ret) - goto out_clear_smmu; + goto out_logger; } /* Assign an asid */ ret = arm_smmu_init_asid(domain, smmu); if (ret) - goto out_clear_smmu; + goto out_logger; ret = arm_smmu_setup_context_bank(smmu_domain, smmu, dev); if (ret) - goto out_clear_smmu; + goto out_logger; strlcpy(smmu_domain->domain.name, dev_name(dev), sizeof(smmu_domain->domain.name)); @@ -2019,12 +2021,12 @@ static int arm_smmu_init_domain_context(struct iommu_domain *domain, return 0; -out_clear_smmu: - arm_smmu_destroy_domain_context(domain); - smmu_domain->smmu = NULL; out_logger: iommu_logger_unregister(smmu_domain->logger); smmu_domain->logger = NULL; +out_clear_smmu: + arm_smmu_destroy_domain_context(domain); + smmu_domain->smmu = NULL; out_unlock: mutex_unlock(&smmu_domain->init_mutex); return ret; diff --git a/drivers/iommu/iommu-logger.c b/drivers/iommu/iommu-logger.c index 483c9c4be897..190b2604f48f 100644 --- a/drivers/iommu/iommu-logger.c +++ b/drivers/iommu/iommu-logger.c @@ -3,6 +3,7 @@ * Copyright (c) 2020, The Linux Foundation. All rights reserved. */ +#include #include #include #include @@ -12,47 +13,126 @@ static DEFINE_MUTEX(iommu_debug_attachments_lock); static LIST_HEAD(iommu_debug_attachments); -/* - * Each group may have more than one domain; but each domain may - * only have one group. - * Used by debug tools to display the name of the device(s) associated - * with a particular domain. - */ -struct iommu_debug_attachment { - struct iommu_domain *domain; - struct iommu_group *group; - struct list_head list; -}; +static unsigned int iommu_logger_pgtable_levels(struct iommu_domain *domain, + struct io_pgtable *iop) +{ + unsigned int va_bits, pte_size, bits_per_level, pg_shift; + unsigned long ias = iop->cfg.ias; + + switch (iop->fmt) { + case ARM_32_LPAE_S1: + case ARM_64_LPAE_S1: +#ifdef CONFIG_IOMMU_IO_PGTABLE_FAST + case ARM_V8L_FAST: +#endif + pte_size = sizeof(u64); + break; + default: + return 0; + } + + pg_shift = __ffs(domain->pgsize_bitmap); + bits_per_level = pg_shift - ilog2(pte_size); + va_bits = ias - pg_shift; + return DIV_ROUND_UP(va_bits, bits_per_level); +} + +static enum iommu_logger_pgtable_fmt iommu_logger_pgtable_fmt_lut( + enum io_pgtable_fmt fmt) +{ + switch (fmt) { + case ARM_32_LPAE_S1: + return IOMMU_LOGGER_ARM_32_LPAE_S1; + case ARM_64_LPAE_S1: +#ifdef CONFIG_IOMMU_IO_PGTABLE_FAST + case ARM_V8L_FAST: +#endif + return IOMMU_LOGGER_ARM_64_LPAE_S1; + default: + return IOMMU_LOGGER_MAX_PGTABLE_FMTS; + } +} + +static int iommu_logger_domain_ttbrs(struct io_pgtable *iop, void **ttbr0_ptr, + void **ttbr1_ptr) +{ + int ret; + u64 ttbr0, ttbr1; + + switch (iop->fmt) { + case ARM_32_LPAE_S1: + case ARM_64_LPAE_S1: + ttbr0 = iop->cfg.arm_lpae_s1_cfg.ttbr[0]; + ttbr1 = iop->cfg.arm_lpae_s1_cfg.ttbr[1]; + ret = 0; + break; +#ifdef CONFIG_IOMMU_IO_PGTABLE_FAST + case ARM_V8L_FAST: + ttbr0 = iop->cfg.av8l_fast_cfg.ttbr[0]; + ttbr1 = iop->cfg.av8l_fast_cfg.ttbr[1]; + ret = 0; + break; +#endif + default: + ret = -EINVAL; + } + + if (!ret) { + *ttbr0_ptr = phys_to_virt(ttbr0); + *ttbr1_ptr = ttbr1 ? phys_to_virt(ttbr1) : NULL; + } + + return ret; +} static struct iommu_debug_attachment *iommu_logger_init( struct iommu_domain *domain, - struct iommu_group *group) + struct iommu_group *group, + struct io_pgtable *iop) { struct iommu_debug_attachment *logger; + unsigned int levels = iommu_logger_pgtable_levels(domain, iop); + enum iommu_logger_pgtable_fmt fmt = iommu_logger_pgtable_fmt_lut( + iop->fmt); + void *ttbr0, *ttbr1; + int ret; + + if (!levels || fmt == IOMMU_LOGGER_MAX_PGTABLE_FMTS) + return ERR_PTR(-EINVAL); + + ret = iommu_logger_domain_ttbrs(iop, &ttbr0, &ttbr1); + if (ret) + return ERR_PTR(ret); logger = kzalloc(sizeof(*logger), GFP_KERNEL); if (!logger) - return NULL; + return ERR_PTR(-ENOMEM); INIT_LIST_HEAD(&logger->list); logger->domain = domain; logger->group = group; + logger->fmt = fmt; + logger->levels = levels; + logger->ttbr0 = ttbr0; + logger->ttbr1 = ttbr1; return logger; } int iommu_logger_register(struct iommu_debug_attachment **logger_out, struct iommu_domain *domain, - struct iommu_group *group) + struct iommu_group *group, + struct io_pgtable *iop) { struct iommu_debug_attachment *logger; - if (!logger_out) + if (!logger_out || !domain || !group || !iop || + iop->fmt >= IO_PGTABLE_NUM_FMTS) return -EINVAL; - logger = iommu_logger_init(domain, group); - if (!logger) - return -ENOMEM; + logger = iommu_logger_init(domain, group, iop); + if (IS_ERR(logger)) + return PTR_ERR(logger); mutex_lock(&iommu_debug_attachments_lock); list_add(&logger->list, &iommu_debug_attachments); diff --git a/drivers/iommu/iommu-logger.h b/drivers/iommu/iommu-logger.h index 3afe1872a201..5e3d9dd6f194 100644 --- a/drivers/iommu/iommu-logger.h +++ b/drivers/iommu/iommu-logger.h @@ -4,18 +4,46 @@ #ifndef __LINUX_QTI_IOMMU_LOGGER_H #define __LINUX_QTI_IOMMU_LOGGER_H -struct iommu_debug_attachment; +#include + +enum iommu_logger_pgtable_fmt { + IOMMU_LOGGER_ARM_32_LPAE_S1, + IOMMU_LOGGER_ARM_64_LPAE_S1, + IOMMU_LOGGER_MAX_PGTABLE_FMTS, +}; + +/* + * Each group may have more than one domain; but each domain may + * only have one group. + * Used by debug tools to display the name of the device(s) associated + * with a particular domain. + */ +struct iommu_debug_attachment { + struct iommu_domain *domain; + struct iommu_group *group; + enum iommu_logger_pgtable_fmt fmt; + unsigned int levels; + /* + * Virtual addresses of the top-level page tables are stored here, + * as they are more useful for debug tools than physical addresses. + */ + void *ttbr0; + void *ttbr1; + struct list_head list; +}; #if IS_ENABLED(CONFIG_QTI_IOMMU_SUPPORT) int iommu_logger_register(struct iommu_debug_attachment **a, struct iommu_domain *domain, - struct iommu_group *group); + struct iommu_group *group, + struct io_pgtable *iop); void iommu_logger_unregister(struct iommu_debug_attachment *a); #else static inline int iommu_logger_register(struct iommu_debug_attachment **a, struct iommu_domain *domain, - struct iommu_group *group) + struct iommu_group *group, + struct io_pgtable *iop) { return 0; }