in vfio_iommu_spapr_tce.c [777:1140]
static long tce_iommu_ioctl(void *iommu_data,
unsigned int cmd, unsigned long arg)
{
struct tce_container *container = iommu_data;
unsigned long minsz, ddwsz;
long ret;
switch (cmd) {
case VFIO_CHECK_EXTENSION:
switch (arg) {
case VFIO_SPAPR_TCE_IOMMU:
case VFIO_SPAPR_TCE_v2_IOMMU:
ret = 1;
break;
default:
ret = vfio_spapr_iommu_eeh_ioctl(NULL, cmd, arg);
break;
}
return (ret < 0) ? 0 : ret;
}
/*
* Sanity check to prevent one userspace from manipulating
* another userspace mm.
*/
BUG_ON(!container);
if (container->mm && container->mm != current->mm)
return -EPERM;
switch (cmd) {
case VFIO_IOMMU_SPAPR_TCE_GET_INFO: {
struct vfio_iommu_spapr_tce_info info;
struct tce_iommu_group *tcegrp;
struct iommu_table_group *table_group;
if (!tce_groups_attached(container))
return -ENXIO;
tcegrp = list_first_entry(&container->group_list,
struct tce_iommu_group, next);
table_group = iommu_group_get_iommudata(tcegrp->grp);
if (!table_group)
return -ENXIO;
minsz = offsetofend(struct vfio_iommu_spapr_tce_info,
dma32_window_size);
if (copy_from_user(&info, (void __user *)arg, minsz))
return -EFAULT;
if (info.argsz < minsz)
return -EINVAL;
info.dma32_window_start = table_group->tce32_start;
info.dma32_window_size = table_group->tce32_size;
info.flags = 0;
memset(&info.ddw, 0, sizeof(info.ddw));
if (table_group->max_dynamic_windows_supported &&
container->v2) {
info.flags |= VFIO_IOMMU_SPAPR_INFO_DDW;
info.ddw.pgsizes = table_group->pgsizes;
info.ddw.max_dynamic_windows_supported =
table_group->max_dynamic_windows_supported;
info.ddw.levels = table_group->max_levels;
}
ddwsz = offsetofend(struct vfio_iommu_spapr_tce_info, ddw);
if (info.argsz >= ddwsz)
minsz = ddwsz;
if (copy_to_user((void __user *)arg, &info, minsz))
return -EFAULT;
return 0;
}
case VFIO_IOMMU_MAP_DMA: {
struct vfio_iommu_type1_dma_map param;
struct iommu_table *tbl = NULL;
long num;
enum dma_data_direction direction;
if (!container->enabled)
return -EPERM;
minsz = offsetofend(struct vfio_iommu_type1_dma_map, size);
if (copy_from_user(¶m, (void __user *)arg, minsz))
return -EFAULT;
if (param.argsz < minsz)
return -EINVAL;
if (param.flags & ~(VFIO_DMA_MAP_FLAG_READ |
VFIO_DMA_MAP_FLAG_WRITE))
return -EINVAL;
ret = tce_iommu_create_default_window(container);
if (ret)
return ret;
num = tce_iommu_find_table(container, param.iova, &tbl);
if (num < 0)
return -ENXIO;
if ((param.size & ~IOMMU_PAGE_MASK(tbl)) ||
(param.vaddr & ~IOMMU_PAGE_MASK(tbl)))
return -EINVAL;
/* iova is checked by the IOMMU API */
if (param.flags & VFIO_DMA_MAP_FLAG_READ) {
if (param.flags & VFIO_DMA_MAP_FLAG_WRITE)
direction = DMA_BIDIRECTIONAL;
else
direction = DMA_TO_DEVICE;
} else {
if (param.flags & VFIO_DMA_MAP_FLAG_WRITE)
direction = DMA_FROM_DEVICE;
else
return -EINVAL;
}
ret = iommu_tce_put_param_check(tbl, param.iova, param.vaddr);
if (ret)
return ret;
if (container->v2)
ret = tce_iommu_build_v2(container, tbl,
param.iova >> tbl->it_page_shift,
param.vaddr,
param.size >> tbl->it_page_shift,
direction);
else
ret = tce_iommu_build(container, tbl,
param.iova >> tbl->it_page_shift,
param.vaddr,
param.size >> tbl->it_page_shift,
direction);
iommu_flush_tce(tbl);
return ret;
}
case VFIO_IOMMU_UNMAP_DMA: {
struct vfio_iommu_type1_dma_unmap param;
struct iommu_table *tbl = NULL;
long num;
if (!container->enabled)
return -EPERM;
minsz = offsetofend(struct vfio_iommu_type1_dma_unmap,
size);
if (copy_from_user(¶m, (void __user *)arg, minsz))
return -EFAULT;
if (param.argsz < minsz)
return -EINVAL;
/* No flag is supported now */
if (param.flags)
return -EINVAL;
ret = tce_iommu_create_default_window(container);
if (ret)
return ret;
num = tce_iommu_find_table(container, param.iova, &tbl);
if (num < 0)
return -ENXIO;
if (param.size & ~IOMMU_PAGE_MASK(tbl))
return -EINVAL;
ret = iommu_tce_clear_param_check(tbl, param.iova, 0,
param.size >> tbl->it_page_shift);
if (ret)
return ret;
ret = tce_iommu_clear(container, tbl,
param.iova >> tbl->it_page_shift,
param.size >> tbl->it_page_shift);
iommu_flush_tce(tbl);
return ret;
}
case VFIO_IOMMU_SPAPR_REGISTER_MEMORY: {
struct vfio_iommu_spapr_register_memory param;
if (!container->v2)
break;
minsz = offsetofend(struct vfio_iommu_spapr_register_memory,
size);
ret = tce_iommu_mm_set(container);
if (ret)
return ret;
if (copy_from_user(¶m, (void __user *)arg, minsz))
return -EFAULT;
if (param.argsz < minsz)
return -EINVAL;
/* No flag is supported now */
if (param.flags)
return -EINVAL;
mutex_lock(&container->lock);
ret = tce_iommu_register_pages(container, param.vaddr,
param.size);
mutex_unlock(&container->lock);
return ret;
}
case VFIO_IOMMU_SPAPR_UNREGISTER_MEMORY: {
struct vfio_iommu_spapr_register_memory param;
if (!container->v2)
break;
if (!container->mm)
return -EPERM;
minsz = offsetofend(struct vfio_iommu_spapr_register_memory,
size);
if (copy_from_user(¶m, (void __user *)arg, minsz))
return -EFAULT;
if (param.argsz < minsz)
return -EINVAL;
/* No flag is supported now */
if (param.flags)
return -EINVAL;
mutex_lock(&container->lock);
ret = tce_iommu_unregister_pages(container, param.vaddr,
param.size);
mutex_unlock(&container->lock);
return ret;
}
case VFIO_IOMMU_ENABLE:
if (container->v2)
break;
mutex_lock(&container->lock);
ret = tce_iommu_enable(container);
mutex_unlock(&container->lock);
return ret;
case VFIO_IOMMU_DISABLE:
if (container->v2)
break;
mutex_lock(&container->lock);
tce_iommu_disable(container);
mutex_unlock(&container->lock);
return 0;
case VFIO_EEH_PE_OP: {
struct tce_iommu_group *tcegrp;
ret = 0;
list_for_each_entry(tcegrp, &container->group_list, next) {
ret = vfio_spapr_iommu_eeh_ioctl(tcegrp->grp,
cmd, arg);
if (ret)
return ret;
}
return ret;
}
case VFIO_IOMMU_SPAPR_TCE_CREATE: {
struct vfio_iommu_spapr_tce_create create;
if (!container->v2)
break;
ret = tce_iommu_mm_set(container);
if (ret)
return ret;
if (!tce_groups_attached(container))
return -ENXIO;
minsz = offsetofend(struct vfio_iommu_spapr_tce_create,
start_addr);
if (copy_from_user(&create, (void __user *)arg, minsz))
return -EFAULT;
if (create.argsz < minsz)
return -EINVAL;
if (create.flags)
return -EINVAL;
mutex_lock(&container->lock);
ret = tce_iommu_create_default_window(container);
if (!ret)
ret = tce_iommu_create_window(container,
create.page_shift,
create.window_size, create.levels,
&create.start_addr);
mutex_unlock(&container->lock);
if (!ret && copy_to_user((void __user *)arg, &create, minsz))
ret = -EFAULT;
return ret;
}
case VFIO_IOMMU_SPAPR_TCE_REMOVE: {
struct vfio_iommu_spapr_tce_remove remove;
if (!container->v2)
break;
ret = tce_iommu_mm_set(container);
if (ret)
return ret;
if (!tce_groups_attached(container))
return -ENXIO;
minsz = offsetofend(struct vfio_iommu_spapr_tce_remove,
start_addr);
if (copy_from_user(&remove, (void __user *)arg, minsz))
return -EFAULT;
if (remove.argsz < minsz)
return -EINVAL;
if (remove.flags)
return -EINVAL;
if (container->def_window_pending && !remove.start_addr) {
container->def_window_pending = false;
return 0;
}
mutex_lock(&container->lock);
ret = tce_iommu_remove_window(container, remove.start_addr);
mutex_unlock(&container->lock);
return ret;
}
}
return -ENOTTY;
}