blob: adf8dc7ee25c6d6a5c220b09b564f51aca8b0bbe [file] [log] [blame]
// 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->miscdev.parent;
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;
}
}