support rockcihp iommu

This commit is contained in:
xxm
2014-03-17 10:18:13 +08:00
committed by Cody Xie
parent af1d0c8386
commit ca55a92fcb
6 changed files with 1748 additions and 0 deletions

View File

@@ -503,4 +503,65 @@
pinctrl-names = "default";
pinctrl-0 = <&mac_clk &mac_txpins &mac_rxpins &mac_mdpins>;
};
gpu{
compatible = "arm,malit764",
"arm,malit76x",
"arm,malit7xx",
"arm,mali-midgard";
reg = <0xffa40000 0x1000>;
interrupts = <GIC_SPI 38 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 39 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 40 IRQ_TYPE_LEVEL_HIGH>;
interrupt-names = "JOB",
"MMU",
"GPU";
};
iep_mmu{
dbgname = "iep";
compatible = "iommu,iep_mmu";
reg = <0xffa40000 0x10000>;
interrupts = <GIC_SPI 38 IRQ_TYPE_LEVEL_HIGH>;
interrupt-names = "iep_mmu";
};
vip_mmu{
dbgname = "vip";
compatible = "iommu,vip_mmu";
reg = <0xffa40000 0x10000>;
interrupts = <GIC_SPI 38 IRQ_TYPE_LEVEL_HIGH>;
interrupt-names = "vip_mmu";
};
isp0_mmu{
dbgname = "isp0";
compatible = "iommu,isp0_mmu";
reg = <0xffa40000 0x10000>;
interrupts = <GIC_SPI 38 IRQ_TYPE_LEVEL_HIGH>;
interrupt-names = "isp0_mmu";
};
isp1_mmu{
dbgname = "isp1";
compatible = "iommu,isp1_mmu";
reg = <0xffa40000 0x10000>;
interrupts = <GIC_SPI 38 IRQ_TYPE_LEVEL_HIGH>;
interrupt-names = "isp1_mmu";
};
vopb_mmu{
dbgname = "vopb";
compatible = "iommu,vopb_mmu";
reg = <0xffa40000 0x10000>;
interrupts = <GIC_SPI 38 IRQ_TYPE_LEVEL_HIGH>;
interrupt-names = "vopb_mmu";
};
vopl_mmu{
dbgname = "vopl";
compatible = "iommu,vopl_mmu";
reg = <0xffa40000 0x10000>;
interrupts = <GIC_SPI 38 IRQ_TYPE_LEVEL_HIGH>;
interrupt-names = "vopl_mmu";
};
};

1088
drivers/iommu/rockchip-iommu.c Executable file

File diff suppressed because it is too large Load Diff

69
drivers/iommu/rockchip-iommu.h Executable file
View File

@@ -0,0 +1,69 @@
/*
* Data structure definition for Rockchip IOMMU driver
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/kernel.h>
#include <linux/spinlock.h>
#include <linux/list.h>
#include <linux/device.h>
#include <linux/platform_device.h>
#include <linux/genalloc.h>
#include <linux/iommu.h>
#include <linux/rockchip/sysmmu.h>
#ifdef CONFIG_ROCKCHIP_IOVMM
#define IOVA_START 0x10000000
#define IOVM_SIZE (SZ_1G - SZ_4K) /* last 4K is for error values */
struct rk_vm_region {
struct list_head node;
dma_addr_t start;
size_t size;
};
struct rk_iovmm {
struct iommu_domain *domain; /* iommu domain for this iovmm */
struct gen_pool *vmm_pool;
struct list_head regions_list; /* list of rk_vm_region */
spinlock_t lock; /* lock for updating regions_list */
};
#endif
struct sysmmu_drvdata {
struct list_head node; /* entry of rk_iommu_domain.clients */
struct device *sysmmu; /* System MMU's device descriptor */
struct device *dev; /* Owner of system MMU */
int num_res_mem;
int num_res_irq;
const char *dbgname;
void __iomem **res_bases;
int activations;
rwlock_t lock;
struct iommu_domain *domain; /* domain given to iommu_attach_device() */
sysmmu_fault_handler_t fault_handler;
unsigned long pgtable;
#ifdef CONFIG_ROCKCHIP_IOVMM
struct rk_iovmm vmm;
#endif
};
#ifdef CONFIG_ROCKCHIP_IOVMM
static inline struct rk_iovmm *rockchip_get_iovmm(struct device *dev)
{
struct sysmmu_drvdata *data = dev_get_drvdata(dev->archdata.iommu);
BUG_ON(!dev->archdata.iommu || !data);
return &data->vmm;
}
int rockchip_init_iovmm(struct device *sysmmu, struct rk_iovmm *vmm);
#else
#define rockchip_init_iovmm(sysmmu, vmm) 0
#endif

331
drivers/iommu/rockchip-iovmm.c Executable file
View File

@@ -0,0 +1,331 @@
/*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#ifdef CONFIG_ROCKCHIP_IOMMU_DEBUG
#define DEBUG
#endif
#include <linux/kernel.h>
#include <linux/hardirq.h>
#include <linux/slab.h>
#include <linux/scatterlist.h>
#include <linux/err.h>
#include <linux/of.h>
#include <linux/of_platform.h>
#include "rockchip-iommu.h"
static struct rk_vm_region *find_region(struct rk_iovmm *vmm, dma_addr_t iova)
{
struct rk_vm_region *region;
list_for_each_entry(region, &vmm->regions_list, node)
if (region->start == iova)
return region;
return NULL;
}
int iovmm_activate(struct device *dev)
{
struct rk_iovmm *vmm = rockchip_get_iovmm(dev);
return iommu_attach_device(vmm->domain, dev);
}
void iovmm_deactivate(struct device *dev)
{
struct rk_iovmm *vmm = rockchip_get_iovmm(dev);
iommu_detach_device(vmm->domain, dev);
}
dma_addr_t iovmm_map(struct device *dev,struct scatterlist *sg, off_t offset,size_t size)
{
off_t start_off;
dma_addr_t addr, start = 0;
size_t mapped_size = 0;
struct rk_vm_region *region;
struct rk_iovmm *vmm = rockchip_get_iovmm(dev);
int order;
int ret;
for (; sg_dma_len(sg) < offset; sg = sg_next(sg))
offset -= sg_dma_len(sg);
start_off = offset_in_page(sg_phys(sg) + offset);
size = PAGE_ALIGN(size + start_off);
order = __fls(min_t(size_t, size, SZ_1M));
region = kmalloc(sizeof(*region), GFP_KERNEL);
if (!region)
{
ret = -ENOMEM;
goto err_map_nomem;
}
//start = (dma_addr_t)gen_pool_alloc_aligned(vmm->vmm_pool, size, order);
start = (dma_addr_t)gen_pool_alloc(vmm->vmm_pool, size);
if (!start)
{
ret = -ENOMEM;
goto err_map_noiomem;
}
addr = start;
do {
phys_addr_t phys;
size_t len;
phys = sg_phys(sg);
len = sg_dma_len(sg);
/* if back to back sg entries are contiguous consolidate them */
while (sg_next(sg) &&sg_phys(sg) + sg_dma_len(sg) == sg_phys(sg_next(sg)))
{
len += sg_dma_len(sg_next(sg));
sg = sg_next(sg);
}
if (offset > 0)
{
len -= offset;
phys += offset;
offset = 0;
}
if (offset_in_page(phys))
{
len += offset_in_page(phys);
phys = round_down(phys, PAGE_SIZE);
}
len = PAGE_ALIGN(len);
if (len > (size - mapped_size))
len = size - mapped_size;
ret = iommu_map(vmm->domain, addr, phys, len, 0);
if (ret)
break;
addr += len;
mapped_size += len;
} while ((sg = sg_next(sg)) && (mapped_size < size));
BUG_ON(mapped_size > size);
if (mapped_size < size)
goto err_map_map;
region->start = start + start_off;
region->size = size;
INIT_LIST_HEAD(&region->node);
spin_lock(&vmm->lock);
list_add(&region->node, &vmm->regions_list);
spin_unlock(&vmm->lock);
dev_dbg(dev, "IOVMM: Allocated VM region @ %#x/%#X bytes.\n",region->start, region->size);
return region->start;
err_map_map:
iommu_unmap(vmm->domain, start, mapped_size);
gen_pool_free(vmm->vmm_pool, start, size);
err_map_noiomem:
kfree(region);
err_map_nomem:
dev_dbg(dev, "IOVMM: Failed to allocated VM region for %#x bytes.\n",size);
return (dma_addr_t)ret;
}
void iovmm_unmap(struct device *dev, dma_addr_t iova)
{
struct rk_vm_region *region;
struct rk_iovmm *vmm = rockchip_get_iovmm(dev);
size_t unmapped_size;
/* This function must not be called in IRQ handlers */
BUG_ON(in_irq());
spin_lock(&vmm->lock);
region = find_region(vmm, iova);
if (WARN_ON(!region))
{
spin_unlock(&vmm->lock);
return;
}
list_del(&region->node);
spin_unlock(&vmm->lock);
region->start = round_down(region->start, PAGE_SIZE);
unmapped_size = iommu_unmap(vmm->domain, region->start, region->size);
rockchip_sysmmu_tlb_invalidate(dev);
gen_pool_free(vmm->vmm_pool, region->start, region->size);
WARN_ON(unmapped_size != region->size);
dev_dbg(dev, "IOVMM: Unmapped %#x bytes from %#x.\n",unmapped_size, region->start);
kfree(region);
}
int iovmm_map_oto(struct device *dev, phys_addr_t phys, size_t size)
{
struct rk_vm_region *region;
struct rk_iovmm *vmm = rockchip_get_iovmm(dev);
int ret;
if (WARN_ON((phys + size) >= IOVA_START))
{
dev_err(dev,"Unable to create one to one mapping for %#x @ %#x\n",size, phys);
return -EINVAL;
}
region = kmalloc(sizeof(*region), GFP_KERNEL);
if (!region)
return -ENOMEM;
if (WARN_ON(phys & ~PAGE_MASK))
phys = round_down(phys, PAGE_SIZE);
ret = iommu_map(vmm->domain, (dma_addr_t)phys, phys, size, 0);
if (ret < 0)
{
kfree(region);
return ret;
}
region->start = (dma_addr_t)phys;
region->size = size;
INIT_LIST_HEAD(&region->node);
spin_lock(&vmm->lock);
list_add(&region->node, &vmm->regions_list);
spin_unlock(&vmm->lock);
return 0;
}
void iovmm_unmap_oto(struct device *dev, phys_addr_t phys)
{
struct rk_vm_region *region;
struct rk_iovmm *vmm = rockchip_get_iovmm(dev);
size_t unmapped_size;
/* This function must not be called in IRQ handlers */
BUG_ON(in_irq());
if (WARN_ON(phys & ~PAGE_MASK))
phys = round_down(phys, PAGE_SIZE);
spin_lock(&vmm->lock);
region = find_region(vmm, (dma_addr_t)phys);
if (WARN_ON(!region))
{
spin_unlock(&vmm->lock);
return;
}
list_del(&region->node);
spin_unlock(&vmm->lock);
unmapped_size = iommu_unmap(vmm->domain, region->start, region->size);
rockchip_sysmmu_tlb_invalidate(dev);
WARN_ON(unmapped_size != region->size);
dev_dbg(dev, "IOVMM: Unmapped %#x bytes from %#x.\n",unmapped_size, region->start);
kfree(region);
}
int rockchip_init_iovmm(struct device *sysmmu, struct rk_iovmm *vmm)
{
int ret = 0;
vmm->vmm_pool = gen_pool_create(PAGE_SHIFT, -1);
if (!vmm->vmm_pool)
{
ret = -ENOMEM;
goto err_setup_genalloc;
}
/* (1GB - 4KB) addr space from 0x10000000 */
ret = gen_pool_add(vmm->vmm_pool, IOVA_START, IOVM_SIZE, -1);
if (ret)
goto err_setup_domain;
vmm->domain = iommu_domain_alloc(&platform_bus_type);
if (!vmm->domain)
{
ret = -ENOMEM;
goto err_setup_domain;
}
spin_lock_init(&vmm->lock);
INIT_LIST_HEAD(&vmm->regions_list);
pr_info("IOVMM: Created %#x B IOVMM from %#x.\n",IOVM_SIZE, IOVA_START);
dev_dbg(sysmmu, "IOVMM: Created %#x B IOVMM from %#x.\n",IOVM_SIZE, IOVA_START);
return 0;
err_setup_domain:
gen_pool_destroy(vmm->vmm_pool);
err_setup_genalloc:
dev_dbg(sysmmu, "IOVMM: Failed to create IOVMM (%d)\n", ret);
return ret;
}
/****
1,success : pointer to the device inside of platform device
2,fail : NULL
****/
struct device *rockchip_get_sysmmu_device_by_compatible(const char *compt)
{
struct device_node *dn = NULL;
struct platform_device *pd = NULL;
struct device *ret = NULL ;
#if 0
dn = of_find_node_by_name(NULL,name);
#endif
dn = of_find_compatible_node(NULL,NULL,compt);
if(!dn)
{
printk("can't find device node %s \r\n",compt);
return NULL;
}
pd = of_find_device_by_node(dn);
if(!pd)
{
printk("can't find platform device in device node %s \r\n",compt);
return NULL;
}
ret = &pd->dev;
return ret;
}

75
include/linux/rockchip/iovmm.h Executable file
View File

@@ -0,0 +1,75 @@
/*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#ifndef __ASM_PLAT_IOVMM_H
#define __ASM_PLAT_IOVMM_H
#ifdef CONFIG_ROCKCHIP_IOVMM
struct scatterlist;
struct device;
int iovmm_activate(struct device *dev);
void iovmm_deactivate(struct device *dev);
/* iovmm_map() - Maps a list of physical memory chunks
* @dev: the owner of the IO address space where the mapping is created
* @sg: list of physical memory chunks to map
* @offset: length in bytes where the mapping starts
* @size: how much memory to map in bytes. @offset + @size must not exceed
* total size of @sg
*
* This function returns mapped IO address in the address space of @dev.
* Returns minus error number if mapping fails.
* Caller must check its return code with IS_ERROR_VALUE() if the function
* succeeded.
*
* The caller of this function must ensure that iovmm_cleanup() is not called
* while this function is called.
*
*/
dma_addr_t iovmm_map(struct device *dev, struct scatterlist *sg, off_t offset,
size_t size);
/* iovmm_unmap() - unmaps the given IO address
* @dev: the owner of the IO address space where @iova belongs
* @iova: IO address that needs to be unmapped and freed.
*
* The caller of this function must ensure that iovmm_cleanup() is not called
* while this function is called.
*/
void iovmm_unmap(struct device *dev, dma_addr_t iova);
/* iovmm_map_oto - create one to one mapping for the given physical address
* @dev: the owner of the IO address space to map
* @phys: physical address to map
* @size: size of the mapping to create
*
* This function return 0 if mapping is successful. Otherwise, minus error
* value.
*/
int iovmm_map_oto(struct device *dev, phys_addr_t phys, size_t size);
/* iovmm_unmap_oto - remove one to one mapping
* @dev: the owner ofthe IO address space
* @phys: physical address to remove mapping
*/
void iovmm_unmap_oto(struct device *dev, phys_addr_t phys);
struct device *rockchip_get_sysmmu_device_by_compatible(const char *compt);
#else
#define iovmm_activate(dev) (-ENOSYS)
#define iovmm_deactivate(dev) do { } while (0)
#define iovmm_map(dev, sg, offset, size) (-ENOSYS)
#define iovmm_unmap(dev, iova) do { } while (0)
#define iovmm_map_oto(dev, phys, size) (-ENOSYS)
#define iovmm_unmap_oto(dev, phys) do { } while (0)
#define rockchip_get_sysmmu_device_by_compatible(compt) do { } while (0)
#endif /* CONFIG_ROCKCHIP_IOVMM */
#endif /*__ASM_PLAT_IOVMM_H*/

124
include/linux/rockchip/sysmmu.h Executable file
View File

@@ -0,0 +1,124 @@
/*
* Rockchip - System MMU support
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#ifndef _ARM_MACH_RK_SYSMMU_H_
#define _ARM_MACH_RK_SYSMMU_H_
#include <linux/list.h>
#include <linux/atomic.h>
#include <linux/spinlock.h>
#define IEP_SYSMMU_COMPATIBLE_NAME "iommu,iep_mmu"
#define VIP_SYSMMU_COMPATIBLE_NAME "iommu,vip_mmu"
#define ISP0_SYSMMU_COMPATIBLE_NAME "iommu,isp0_mmu"
#define ISP1_SYSMMU_COMPATIBLE_NAME "iommu,isp1_mmu"
#define VOPB_SYSMMU_COMPATIBLE_NAME "iommu,vopb_mmu"
#define VOPL_SYSMMU_COMPATIBLE_NAME "iommu,vopl_mmu"
enum rk_sysmmu_inttype {
SYSMMU_PAGEFAULT,
SYSMMU_BUSERROR,
SYSMMU_FAULT_UNKNOWN,
SYSMMU_FAULTS_NUM
};
struct sysmmu_drvdata;
/*
* @itype: type of fault.
* @pgtable_base: the physical address of page table base. This is 0 if @itype
* is SYSMMU_BUSERROR.
* @fault_addr: the device (virtual) address that the System MMU tried to
* translated. This is 0 if @itype is SYSMMU_BUSERROR.
*/
typedef int (*sysmmu_fault_handler_t)(struct device *dev,
enum rk_sysmmu_inttype itype,
unsigned long pgtable_base,
unsigned long fault_addr,
unsigned int statu
);
#ifdef CONFIG_ROCKCHIP_IOMMU
/**
* rockchip_sysmmu_enable() - enable system mmu
* @owner: The device whose System MMU is about to be enabled.
* @pgd: Base physical address of the 1st level page table
*
* This function enable system mmu to transfer address
* from virtual address to physical address.
* Return non-zero if it fails to enable System MMU.
*/
int rockchip_sysmmu_enable(struct device *owner, unsigned long pgd);
/**
* rockchip_sysmmu_disable() - disable sysmmu mmu of ip
* @owner: The device whose System MMU is about to be disabled.
*
* This function disable system mmu to transfer address
* from virtual address to physical address
*/
bool rockchip_sysmmu_disable(struct device *owner);
/**
* rockchip_sysmmu_tlb_invalidate() - flush all TLB entry in system mmu
* @owner: The device whose System MMU.
*
* This function flush all TLB entry in system mmu
*/
void rockchip_sysmmu_tlb_invalidate(struct device *owner);
/** rockchip_sysmmu_set_fault_handler() - Fault handler for System MMUs
* Called when interrupt occurred by the System MMUs
* The device drivers of peripheral devices that has a System MMU can implement
* a fault handler to resolve address translation fault by System MMU.
* The meanings of return value and parameters are described below.
*
* return value: non-zero if the fault is correctly resolved.
* zero if the fault is not handled.
*/
void rockchip_sysmmu_set_fault_handler(struct device *dev,sysmmu_fault_handler_t handler);
/** rockchip_sysmmu_set_prefbuf() - Initialize prefetch buffers of System MMU v3
* @owner: The device which need to set the prefetch buffers
* @base0: The start virtual address of the area of the @owner device that the
* first prefetch buffer loads translation descriptors
* @size0: The last virtual address of the area of the @owner device that the
* first prefetch buffer loads translation descriptors.
* @base1: The start virtual address of the area of the @owner device that the
* second prefetch buffer loads translation descriptors. This will be
* ignored if @size1 is 0 and this function assigns the 2 prefetch
* buffers with each half of the area specified by @base0 and @size0
* @size1: The last virtual address of the area of the @owner device that the
* prefetch buffer loads translation descriptors. This can be 0. See
* the description of @base1 for more information with @size1 = 0
*/
void rockchip_sysmmu_set_prefbuf(struct device *owner,
unsigned long base0, unsigned long size0,
unsigned long base1, unsigned long size1);
#else /* CONFIG_ROCKCHIP_IOMMU */
#define rockchip_sysmmu_enable(owner, pgd) do { } while (0)
#define rockchip_sysmmu_disable(owner) do { } while (0)
#define rockchip_sysmmu_tlb_invalidate(owner) do { } while (0)
#define rockchip_sysmmu_set_fault_handler(sysmmu, handler) do { } while (0)
#define rockchip_sysmmu_set_prefbuf(owner, b0, s0, b1, s1) do { } while (0)
#endif
#ifdef CONFIG_IOMMU_API
#include <linux/device.h>
static inline void platform_set_sysmmu(struct device *sysmmu, struct device *dev)
{
dev->archdata.iommu = sysmmu;
}
#else
#define platform_set_sysmmu(dev, sysmmu) do { } while (0)
#endif
#endif /* _ARM_MACH_RK_SYSMMU_H_ */