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

Commit ec271071 authored by Isaac J. Manjarres's avatar Isaac J. Manjarres
Browse files

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: default avatarIsaac J. Manjarres <isaacm@codeaurora.org>
parent 1264c046
Loading
Loading
Loading
Loading
+19 −17
Original line number Diff line number Diff line
@@ -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;
+98 −18
Original line number Diff line number Diff line
@@ -3,6 +3,7 @@
 * Copyright (c) 2020, The Linux Foundation. All rights reserved.
 */

#include <linux/bitfield.h>
#include <linux/module.h>
#include <linux/iommu.h>
#include <linux/slab.h>
@@ -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);
+31 −3
Original line number Diff line number Diff line
@@ -4,18 +4,46 @@
#ifndef __LINUX_QTI_IOMMU_LOGGER_H
#define __LINUX_QTI_IOMMU_LOGGER_H

struct iommu_debug_attachment;
#include <linux/io-pgtable.h>

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;
}