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

Commit 23301488 authored by Patrick Daly's avatar Patrick Daly
Browse files

iommu: Support MMU500 Deep Prefetch errata workaround



If deep prefetch is enabled, but not all page table entries in the
prefetch window are valid, improper permissions may be cached for
valid entries in this window. Ensure the start and end of all mapped
buffers are aligned to 16kB to prevent this issue.

Change-Id: I53bc18529b77c775338c477487333b404b225c58
Signed-off-by: default avatarPatrick Daly <pdaly@codeaurora.org>
parent 03330cc8
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -23,6 +23,8 @@ struct dma_iommu_mapping {
	void			*bitmap;
	size_t			bits;
	dma_addr_t		base;
	u32			min_iova_align;
	struct page		*guard_page;

	struct dma_fast_smmu_mapping *fast;
};
+58 −6
Original line number Diff line number Diff line
@@ -37,7 +37,8 @@
#include <asm/dma-iommu.h>
#include <linux/dma-mapping-fast.h>
#include <linux/msm_dma_iommu_mapping.h>

#include <linux/arm-smmu-errata.h>
#include <soc/qcom/secure_buffer.h>


static int swiotlb __ro_after_init;
@@ -1165,15 +1166,24 @@ static void __dma_clear_buffer(struct page *page, size_t size,
static inline dma_addr_t __alloc_iova(struct dma_iommu_mapping *mapping,
				      size_t size)
{
	unsigned int order = get_order(size);
	unsigned int order;
	unsigned int align = 0;
	unsigned int count, start;
	unsigned long flags;
	dma_addr_t iova;
	size_t guard_len;

	size = PAGE_ALIGN(size);
	if (mapping->min_iova_align)
		guard_len = ALIGN(size, mapping->min_iova_align) - size;
	else
		guard_len = 0;

	order = get_order(size + guard_len);
	if (order > CONFIG_ARM64_DMA_IOMMU_ALIGNMENT)
		order = CONFIG_ARM64_DMA_IOMMU_ALIGNMENT;

	count = PAGE_ALIGN(size) >> PAGE_SHIFT;
	count = PAGE_ALIGN(size + guard_len) >> PAGE_SHIFT;
	align = (1 << order) - 1;

	spin_lock_irqsave(&mapping->lock, flags);
@@ -1187,16 +1197,41 @@ static inline dma_addr_t __alloc_iova(struct dma_iommu_mapping *mapping,
	bitmap_set(mapping->bitmap, start, count);
	spin_unlock_irqrestore(&mapping->lock, flags);

	return mapping->base + (start << PAGE_SHIFT);
	iova = mapping->base + (start << PAGE_SHIFT);

	if (guard_len &&
		iommu_map(mapping->domain, iova + size,
			page_to_phys(mapping->guard_page),
			guard_len, ARM_SMMU_GUARD_PROT)) {

		spin_lock_irqsave(&mapping->lock, flags);
		bitmap_clear(mapping->bitmap, start, count);
		spin_unlock_irqrestore(&mapping->lock, flags);
		return DMA_ERROR_CODE;
	}

	return iova;
}

static inline void __free_iova(struct dma_iommu_mapping *mapping,
			       dma_addr_t addr, size_t size)
{
	unsigned int start = (addr - mapping->base) >> PAGE_SHIFT;
	unsigned int count = size >> PAGE_SHIFT;
	unsigned int start;
	unsigned int count;
	unsigned long flags;
	size_t guard_len;

	addr = addr & PAGE_MASK;
	size = PAGE_ALIGN(size);
	if (mapping->min_iova_align)
		guard_len = ALIGN(size, mapping->min_iova_align) - size;
	else
		guard_len = 0;

	iommu_unmap(mapping->domain, addr + size, guard_len);

	start = (addr - mapping->base) >> PAGE_SHIFT;
	count = (size + guard_len) >> PAGE_SHIFT;
	spin_lock_irqsave(&mapping->lock, flags);
	bitmap_clear(mapping->bitmap, start, count);
	spin_unlock_irqrestore(&mapping->lock, flags);
@@ -1942,6 +1977,23 @@ static int
bitmap_iommu_init_mapping(struct device *dev, struct dma_iommu_mapping *mapping)
{
	unsigned int bitmap_size = BITS_TO_LONGS(mapping->bits) * sizeof(long);
	int vmid = VMID_HLOS;
	bool min_iova_align = 0;

	iommu_domain_get_attr(mapping->domain,
			DOMAIN_ATTR_QCOM_MMU500_ERRATA_MIN_ALIGN,
			&min_iova_align);
	iommu_domain_get_attr(mapping->domain,
			DOMAIN_ATTR_SECURE_VMID, &vmid);
	if (vmid >= VMID_LAST || vmid < 0)
		vmid = VMID_HLOS;

	if (min_iova_align) {
		mapping->min_iova_align = ARM_SMMU_MIN_IOVA_ALIGN;
		mapping->guard_page = arm_smmu_errata_get_guard_page(vmid);
		if (!mapping->guard_page)
			return -ENOMEM;
	}

	mapping->bitmap = kzalloc(bitmap_size, GFP_KERNEL | __GFP_NOWARN |
							__GFP_NORETRY);
+1 −1
Original line number Diff line number Diff line
@@ -13,7 +13,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
obj-$(CONFIG_ARM_SMMU) += arm-smmu.o arm-smmu-errata.o
obj-$(CONFIG_ARM_SMMU_V3) += arm-smmu-v3.o
obj-$(CONFIG_DMAR_TABLE) += dmar.o
obj-$(CONFIG_INTEL_IOMMU) += intel-iommu.o
+53 −0
Original line number Diff line number Diff line
/*
 * Copyright (c) 2017, 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 <soc/qcom/secure_buffer.h>
#include <linux/arm-smmu-errata.h>

static struct page *guard_pages[VMID_LAST];
static DEFINE_MUTEX(guard_page_lock);

struct page *arm_smmu_errata_get_guard_page(int vmid)
{
	struct page *page;
	int ret;
	int source_vm = VMID_HLOS;
	int dest_vm = vmid;
	int dest_perm = PERM_READ | PERM_WRITE | PERM_EXEC;
	size_t size = ARM_SMMU_MIN_IOVA_ALIGN;

	mutex_lock(&guard_page_lock);
	page = guard_pages[vmid];
	if (page)
		goto out;

	page = alloc_pages(GFP_KERNEL, get_order(size));
	if (!page)
		goto out;

	if (vmid != VMID_HLOS) {
		ret = hyp_assign_phys(page_to_phys(page), PAGE_ALIGN(size),
				&source_vm, 1,
				&dest_vm, &dest_perm, 1);
		if (ret) {
			__free_pages(page, get_order(size));
			page = NULL;
		}
	}
	guard_pages[vmid] = page;
out:
	mutex_unlock(&guard_page_lock);
	return page;
}
+16 −0
Original line number Diff line number Diff line
@@ -536,6 +536,7 @@ struct arm_smmu_domain {

	bool				qsmmuv500_errata1_init;
	bool				qsmmuv500_errata1_client;
	bool				qsmmuv500_errata2_min_align;
};

static DEFINE_SPINLOCK(arm_smmu_devices_lock);
@@ -2898,6 +2899,10 @@ static int arm_smmu_domain_get_attr(struct iommu_domain *domain,
			& (1 << DOMAIN_ATTR_CB_STALL_DISABLE));
		ret = 0;
		break;
	case DOMAIN_ATTR_QCOM_MMU500_ERRATA_MIN_ALIGN:
		*((int *)data) = smmu_domain->qsmmuv500_errata2_min_align;
		ret = 0;
		break;
	default:
		ret = -ENODEV;
		break;
@@ -4395,6 +4400,9 @@ IOMMU_OF_DECLARE(cavium_smmuv2, "cavium,smmu-v2", arm_smmu_of_init);

#define TBU_DBG_TIMEOUT_US		30000

#define QSMMUV500_ACTLR_DEEP_PREFETCH_MASK	0x3
#define QSMMUV500_ACTLR_DEEP_PREFETCH_SHIFT	0x8


struct actlr_setting {
	struct arm_smmu_smr smr;
@@ -4872,6 +4880,14 @@ static void qsmmuv500_init_cb(struct arm_smmu_domain *smmu_domain,

	writel_relaxed(iommudata->actlr, cb_base + ARM_SMMU_CB_ACTLR);

	/*
	 * Prefetch only works properly if the start and end of all
	 * buffers in the page table are aligned to 16 Kb.
	 */
	if ((iommudata->actlr >> QSMMUV500_ACTLR_DEEP_PREFETCH_SHIFT) &&
			QSMMUV500_ACTLR_DEEP_PREFETCH_MASK)
		smmu_domain->qsmmuv500_errata2_min_align = true;

	/*
	 * Flush the context bank after modifying ACTLR to ensure there
	 * are no cache entries with stale state
Loading