158 lines
3.4 KiB
C
158 lines
3.4 KiB
C
// SPDX-License-Identifier: GPL-2.0+
|
|
/*
|
|
* NVIDIA Tegra Video decoder driver
|
|
*
|
|
* Copyright (C) 2016-2019 GRATE-DRIVER project
|
|
*/
|
|
|
|
#include <linux/iommu.h>
|
|
#include <linux/iova.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/platform_device.h>
|
|
|
|
#if IS_ENABLED(CONFIG_ARM_DMA_USE_IOMMU)
|
|
#include <asm/dma-iommu.h>
|
|
#endif
|
|
|
|
#include "vde.h"
|
|
|
|
int tegra_vde_iommu_map(struct tegra_vde *vde,
|
|
struct sg_table *sgt,
|
|
struct iova **iovap,
|
|
size_t size)
|
|
{
|
|
struct iova *iova;
|
|
unsigned long shift;
|
|
unsigned long end;
|
|
dma_addr_t addr;
|
|
|
|
end = vde->domain->geometry.aperture_end;
|
|
size = iova_align(&vde->iova, size);
|
|
shift = iova_shift(&vde->iova);
|
|
|
|
iova = alloc_iova(&vde->iova, size >> shift, end >> shift, true);
|
|
if (!iova)
|
|
return -ENOMEM;
|
|
|
|
addr = iova_dma_addr(&vde->iova, iova);
|
|
|
|
size = iommu_map_sgtable(vde->domain, addr, sgt,
|
|
IOMMU_READ | IOMMU_WRITE);
|
|
if (!size) {
|
|
__free_iova(&vde->iova, iova);
|
|
return -ENXIO;
|
|
}
|
|
|
|
*iovap = iova;
|
|
|
|
return 0;
|
|
}
|
|
|
|
void tegra_vde_iommu_unmap(struct tegra_vde *vde, struct iova *iova)
|
|
{
|
|
unsigned long shift = iova_shift(&vde->iova);
|
|
unsigned long size = iova_size(iova) << shift;
|
|
dma_addr_t addr = iova_dma_addr(&vde->iova, iova);
|
|
|
|
iommu_unmap(vde->domain, addr, size);
|
|
__free_iova(&vde->iova, iova);
|
|
}
|
|
|
|
int tegra_vde_iommu_init(struct tegra_vde *vde)
|
|
{
|
|
struct device *dev = vde->dev;
|
|
struct iova *iova;
|
|
unsigned long order;
|
|
unsigned long shift;
|
|
int err;
|
|
|
|
vde->group = iommu_group_get(dev);
|
|
if (!vde->group)
|
|
return 0;
|
|
|
|
#if IS_ENABLED(CONFIG_ARM_DMA_USE_IOMMU)
|
|
if (dev->archdata.mapping) {
|
|
struct dma_iommu_mapping *mapping = to_dma_iommu_mapping(dev);
|
|
|
|
arm_iommu_detach_device(dev);
|
|
arm_iommu_release_mapping(mapping);
|
|
}
|
|
#endif
|
|
vde->domain = iommu_domain_alloc(&platform_bus_type);
|
|
if (!vde->domain) {
|
|
err = -ENOMEM;
|
|
goto put_group;
|
|
}
|
|
|
|
err = iova_cache_get();
|
|
if (err)
|
|
goto free_domain;
|
|
|
|
order = __ffs(vde->domain->pgsize_bitmap);
|
|
init_iova_domain(&vde->iova, 1UL << order, 0);
|
|
|
|
err = iommu_attach_group(vde->domain, vde->group);
|
|
if (err)
|
|
goto put_iova;
|
|
|
|
/*
|
|
* We're using some static addresses that are not accessible by VDE
|
|
* to trap invalid memory accesses.
|
|
*/
|
|
shift = iova_shift(&vde->iova);
|
|
iova = reserve_iova(&vde->iova, 0x60000000 >> shift,
|
|
0x70000000 >> shift);
|
|
if (!iova) {
|
|
err = -ENOMEM;
|
|
goto detach_group;
|
|
}
|
|
|
|
vde->iova_resv_static_addresses = iova;
|
|
|
|
/*
|
|
* BSEV's end-address wraps around due to integer overflow during
|
|
* of hardware context preparation if IOVA is allocated at the end
|
|
* of address space and VDE can't handle that. Hence simply reserve
|
|
* the last page to avoid the problem.
|
|
*/
|
|
iova = reserve_iova(&vde->iova, 0xffffffff >> shift,
|
|
(0xffffffff >> shift) + 1);
|
|
if (!iova) {
|
|
err = -ENOMEM;
|
|
goto unreserve_iova;
|
|
}
|
|
|
|
vde->iova_resv_last_page = iova;
|
|
|
|
return 0;
|
|
|
|
unreserve_iova:
|
|
__free_iova(&vde->iova, vde->iova_resv_static_addresses);
|
|
detach_group:
|
|
iommu_detach_group(vde->domain, vde->group);
|
|
put_iova:
|
|
put_iova_domain(&vde->iova);
|
|
iova_cache_put();
|
|
free_domain:
|
|
iommu_domain_free(vde->domain);
|
|
put_group:
|
|
iommu_group_put(vde->group);
|
|
|
|
return err;
|
|
}
|
|
|
|
void tegra_vde_iommu_deinit(struct tegra_vde *vde)
|
|
{
|
|
if (vde->domain) {
|
|
__free_iova(&vde->iova, vde->iova_resv_last_page);
|
|
__free_iova(&vde->iova, vde->iova_resv_static_addresses);
|
|
iommu_detach_group(vde->domain, vde->group);
|
|
put_iova_domain(&vde->iova);
|
|
iova_cache_put();
|
|
iommu_domain_free(vde->domain);
|
|
iommu_group_put(vde->group);
|
|
|
|
vde->domain = NULL;
|
|
}
|
|
}
|