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

Commit 89aa57d1 authored by Joerg Roedel's avatar Joerg Roedel
Browse files

Merge branch 'iommu/next' of git://linuxtv.org/pinchartl/fbdev into arm/renesas

parents ec6f34e5 78e1f974
Loading
Loading
Loading
Loading
+41 −0
Original line number Diff line number Diff line
* Renesas VMSA-Compatible IOMMU

The IPMMU is an IOMMU implementation compatible with the ARM VMSA page tables.
It provides address translation for bus masters outside of the CPU, each
connected to the IPMMU through a port called micro-TLB.


Required Properties:

  - compatible: Must contain "renesas,ipmmu-vmsa".
  - reg: Base address and size of the IPMMU registers.
  - interrupts: Specifiers for the MMU fault interrupts. For instances that
    support secure mode two interrupts must be specified, for non-secure and
    secure mode, in that order. For instances that don't support secure mode a
    single interrupt must be specified.

  - #iommu-cells: Must be 1.

Each bus master connected to an IPMMU must reference the IPMMU in its device
node with the following property:

  - iommus: A reference to the IPMMU in two cells. The first cell is a phandle
    to the IPMMU and the second cell the number of the micro-TLB that the
    device is connected to.


Example: R8A7791 IPMMU-MX and VSP1-D0 bus master

	ipmmu_mx: mmu@fe951000 {
		compatible = "renasas,ipmmu-vmsa";
		reg = <0 0xfe951000 0 0x1000>;
		interrupts = <0 222 IRQ_TYPE_LEVEL_HIGH>,
			     <0 221 IRQ_TYPE_LEVEL_HIGH>;
		#iommu-cells = <1>;
	};

	vsp1@fe928000 {
		...
		iommus = <&ipmmu_mx 13>;
		...
	};
+113 −38
Original line number Diff line number Diff line
@@ -16,7 +16,7 @@
#include <linux/io.h>
#include <linux/iommu.h>
#include <linux/module.h>
#include <linux/platform_data/ipmmu-vmsa.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/sizes.h>
#include <linux/slab.h>
@@ -29,7 +29,6 @@ struct ipmmu_vmsa_device {
	void __iomem *base;
	struct list_head list;

	const struct ipmmu_vmsa_platform_data *pdata;
	unsigned int num_utlbs;

	struct dma_iommu_mapping *mapping;
@@ -46,7 +45,8 @@ struct ipmmu_vmsa_domain {

struct ipmmu_vmsa_archdata {
	struct ipmmu_vmsa_device *mmu;
	unsigned int utlb;
	unsigned int *utlbs;
	unsigned int num_utlbs;
};

static DEFINE_SPINLOCK(ipmmu_devices_lock);
@@ -58,6 +58,8 @@ static LIST_HEAD(ipmmu_devices);
 * Registers Definition
 */

#define IM_NS_ALIAS_OFFSET		0x800

#define IM_CTX_SIZE			0x40

#define IMCTR				0x0000
@@ -678,30 +680,33 @@ static int ipmmu_create_mapping(struct ipmmu_vmsa_domain *domain,

static void ipmmu_clear_pud(struct ipmmu_vmsa_device *mmu, pud_t *pud)
{
	/* Free the page table. */
	pgtable_t table = pud_pgtable(*pud);
	__free_page(table);

	/* Clear the PUD. */
	*pud = __pud(0);
	ipmmu_flush_pgtable(mmu, pud, sizeof(*pud));

	/* Free the page table. */
	__free_page(table);
}

static void ipmmu_clear_pmd(struct ipmmu_vmsa_device *mmu, pud_t *pud,
			    pmd_t *pmd)
{
	pmd_t pmdval = *pmd;
	unsigned int i;

	/* Free the page table. */
	if (pmd_table(*pmd)) {
		pgtable_t table = pmd_pgtable(*pmd);
		__free_page(table);
	}

	/* Clear the PMD. */
	*pmd = __pmd(0);
	ipmmu_flush_pgtable(mmu, pmd, sizeof(*pmd));

	/* Free the page table. */
	if (pmd_table(pmdval)) {
		pgtable_t table = pmd_pgtable(pmdval);

		__free_page(table);
	}

	/* Check whether the PUD is still needed. */
	pmd = pmd_offset(pud, 0);
	for (i = 0; i < IPMMU_PTRS_PER_PMD; ++i) {
@@ -782,7 +787,6 @@ static int ipmmu_clear_mapping(struct ipmmu_vmsa_domain *domain,
	pud_t *pud;
	pmd_t *pmd;
	pte_t *pte;
	int ret = 0;

	if (!pgd)
		return -EINVAL;
@@ -844,7 +848,6 @@ static int ipmmu_clear_mapping(struct ipmmu_vmsa_domain *domain,
done:
	spin_unlock_irqrestore(&domain->lock, flags);

	if (ret)
	ipmmu_tlb_invalidate(domain);

	return 0;
@@ -896,6 +899,7 @@ static int ipmmu_attach_device(struct iommu_domain *io_domain,
	struct ipmmu_vmsa_device *mmu = archdata->mmu;
	struct ipmmu_vmsa_domain *domain = io_domain->priv;
	unsigned long flags;
	unsigned int i;
	int ret = 0;

	if (!mmu) {
@@ -924,7 +928,8 @@ static int ipmmu_attach_device(struct iommu_domain *io_domain,
	if (ret < 0)
		return ret;

	ipmmu_utlb_enable(domain, archdata->utlb);
	for (i = 0; i < archdata->num_utlbs; ++i)
		ipmmu_utlb_enable(domain, archdata->utlbs[i]);

	return 0;
}
@@ -934,8 +939,10 @@ static void ipmmu_detach_device(struct iommu_domain *io_domain,
{
	struct ipmmu_vmsa_archdata *archdata = dev->archdata.iommu;
	struct ipmmu_vmsa_domain *domain = io_domain->priv;
	unsigned int i;

	ipmmu_utlb_disable(domain, archdata->utlb);
	for (i = 0; i < archdata->num_utlbs; ++i)
		ipmmu_utlb_disable(domain, archdata->utlbs[i]);

	/*
	 * TODO: Optimize by disabling the context when no device is attached.
@@ -999,26 +1006,56 @@ static phys_addr_t ipmmu_iova_to_phys(struct iommu_domain *io_domain,
	return __pfn_to_phys(pte_pfn(pte)) | (iova & ~PAGE_MASK);
}

static int ipmmu_find_utlb(struct ipmmu_vmsa_device *mmu, struct device *dev)
static int ipmmu_find_utlbs(struct ipmmu_vmsa_device *mmu, struct device *dev,
			    unsigned int **_utlbs)
{
	const struct ipmmu_vmsa_master *master = mmu->pdata->masters;
	const char *devname = dev_name(dev);
	unsigned int *utlbs;
	unsigned int i;
	int count;

	count = of_count_phandle_with_args(dev->of_node, "iommus",
					   "#iommu-cells");
	if (count < 0)
		return -EINVAL;

	utlbs = kcalloc(count, sizeof(*utlbs), GFP_KERNEL);
	if (!utlbs)
		return -ENOMEM;

	for (i = 0; i < count; ++i) {
		struct of_phandle_args args;
		int ret;

		ret = of_parse_phandle_with_args(dev->of_node, "iommus",
						 "#iommu-cells", i, &args);
		if (ret < 0)
			goto error;

		of_node_put(args.np);

		if (args.np != mmu->dev->of_node || args.args_count != 1)
			goto error;

	for (i = 0; i < mmu->pdata->num_masters; ++i, ++master) {
		if (strcmp(master->name, devname) == 0)
			return master->utlb;
		utlbs[i] = args.args[0];
	}

	return -1;
	*_utlbs = utlbs;

	return count;

error:
	kfree(utlbs);
	return -EINVAL;
}

static int ipmmu_add_device(struct device *dev)
{
	struct ipmmu_vmsa_archdata *archdata;
	struct ipmmu_vmsa_device *mmu;
	struct iommu_group *group;
	int utlb = -1;
	struct iommu_group *group = NULL;
	unsigned int *utlbs = NULL;
	unsigned int i;
	int num_utlbs = 0;
	int ret;

	if (dev->archdata.iommu) {
@@ -1031,8 +1068,8 @@ static int ipmmu_add_device(struct device *dev)
	spin_lock(&ipmmu_devices_lock);

	list_for_each_entry(mmu, &ipmmu_devices, list) {
		utlb = ipmmu_find_utlb(mmu, dev);
		if (utlb >= 0) {
		num_utlbs = ipmmu_find_utlbs(mmu, dev, &utlbs);
		if (num_utlbs) {
			/*
			 * TODO Take a reference to the MMU to protect
			 * against device removal.
@@ -1043,17 +1080,22 @@ static int ipmmu_add_device(struct device *dev)

	spin_unlock(&ipmmu_devices_lock);

	if (utlb < 0)
	if (num_utlbs <= 0)
		return -ENODEV;

	if (utlb >= mmu->num_utlbs)
		return -EINVAL;
	for (i = 0; i < num_utlbs; ++i) {
		if (utlbs[i] >= mmu->num_utlbs) {
			ret = -EINVAL;
			goto error;
		}
	}

	/* Create a device group and add the device to it. */
	group = iommu_group_alloc();
	if (IS_ERR(group)) {
		dev_err(dev, "Failed to allocate IOMMU group\n");
		return PTR_ERR(group);
		ret = PTR_ERR(group);
		goto error;
	}

	ret = iommu_group_add_device(group, dev);
@@ -1061,7 +1103,8 @@ static int ipmmu_add_device(struct device *dev)

	if (ret < 0) {
		dev_err(dev, "Failed to add device to IPMMU group\n");
		return ret;
		group = NULL;
		goto error;
	}

	archdata = kzalloc(sizeof(*archdata), GFP_KERNEL);
@@ -1071,7 +1114,8 @@ static int ipmmu_add_device(struct device *dev)
	}

	archdata->mmu = mmu;
	archdata->utlb = utlb;
	archdata->utlbs = utlbs;
	archdata->num_utlbs = num_utlbs;
	dev->archdata.iommu = archdata;

	/*
@@ -1090,7 +1134,8 @@ static int ipmmu_add_device(struct device *dev)
						   SZ_1G, SZ_2G);
		if (IS_ERR(mapping)) {
			dev_err(mmu->dev, "failed to create ARM IOMMU mapping\n");
			return PTR_ERR(mapping);
			ret = PTR_ERR(mapping);
			goto error;
		}

		mmu->mapping = mapping;
@@ -1106,17 +1151,29 @@ static int ipmmu_add_device(struct device *dev)
	return 0;

error:
	arm_iommu_release_mapping(mmu->mapping);

	kfree(dev->archdata.iommu);
	kfree(utlbs);

	dev->archdata.iommu = NULL;

	if (!IS_ERR_OR_NULL(group))
		iommu_group_remove_device(dev);

	return ret;
}

static void ipmmu_remove_device(struct device *dev)
{
	struct ipmmu_vmsa_archdata *archdata = dev->archdata.iommu;

	arm_iommu_detach_device(dev);
	iommu_group_remove_device(dev);
	kfree(dev->archdata.iommu);

	kfree(archdata->utlbs);
	kfree(archdata);

	dev->archdata.iommu = NULL;
}

@@ -1154,7 +1211,7 @@ static int ipmmu_probe(struct platform_device *pdev)
	int irq;
	int ret;

	if (!pdev->dev.platform_data) {
	if (!IS_ENABLED(CONFIG_OF) && !pdev->dev.platform_data) {
		dev_err(&pdev->dev, "missing platform data\n");
		return -EINVAL;
	}
@@ -1166,7 +1223,6 @@ static int ipmmu_probe(struct platform_device *pdev)
	}

	mmu->dev = &pdev->dev;
	mmu->pdata = pdev->dev.platform_data;
	mmu->num_utlbs = 32;

	/* Map I/O memory and request IRQ. */
@@ -1175,6 +1231,20 @@ static int ipmmu_probe(struct platform_device *pdev)
	if (IS_ERR(mmu->base))
		return PTR_ERR(mmu->base);

	/*
	 * The IPMMU has two register banks, for secure and non-secure modes.
	 * The bank mapped at the beginning of the IPMMU address space
	 * corresponds to the running mode of the CPU. When running in secure
	 * mode the non-secure register bank is also available at an offset.
	 *
	 * Secure mode operation isn't clearly documented and is thus currently
	 * not implemented in the driver. Furthermore, preliminary tests of
	 * non-secure operation with the main register bank were not successful.
	 * Offset the registers base unconditionally to point to the non-secure
	 * alias space for now.
	 */
	mmu->base += IM_NS_ALIAS_OFFSET;

	irq = platform_get_irq(pdev, 0);
	if (irq < 0) {
		dev_err(&pdev->dev, "no IRQ found\n");
@@ -1220,9 +1290,14 @@ static int ipmmu_remove(struct platform_device *pdev)
	return 0;
}

static const struct of_device_id ipmmu_of_ids[] = {
	{ .compatible = "renesas,ipmmu-vmsa", },
};

static struct platform_driver ipmmu_driver = {
	.driver = {
		.name = "ipmmu-vmsa",
		.of_match_table = of_match_ptr(ipmmu_of_ids),
	},
	.probe = ipmmu_probe,
	.remove	= ipmmu_remove,
+0 −24
Original line number Diff line number Diff line
/*
 * IPMMU VMSA Platform Data
 *
 * Copyright (C) 2014 Renesas Electronics Corporation
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; version 2 of the License.
 */

#ifndef __IPMMU_VMSA_H__
#define __IPMMU_VMSA_H__

struct ipmmu_vmsa_master {
	const char *name;
	unsigned int utlb;
};

struct ipmmu_vmsa_platform_data {
	const struct ipmmu_vmsa_master *masters;
	unsigned int num_masters;
};

#endif /* __IPMMU_VMSA_H__ */