vfio: powerpc/spapr: Support Dynamic DMA windows

This adds create/remove window ioctls to create and remove DMA windows.
sPAPR defines a Dynamic DMA windows capability which allows
para-virtualized guests to create additional DMA windows on a PCI bus.
The existing linux kernels use this new window to map the entire guest
memory and switch to the direct DMA operations saving time on map/unmap
requests which would normally happen in a big amounts.

This adds 2 ioctl handlers - VFIO_IOMMU_SPAPR_TCE_CREATE and
VFIO_IOMMU_SPAPR_TCE_REMOVE - to create and remove windows.
Up to 2 windows are supported now by the hardware and by this driver.

This changes VFIO_IOMMU_SPAPR_TCE_GET_INFO handler to return additional
information such as a number of supported windows and maximum number
levels of TCE tables.

DDW is added as a capability, not as a SPAPR TCE IOMMU v2 unique feature
as we still want to support v2 on platforms which cannot do DDW for
the sake of TCE acceleration in KVM (coming soon).

Signed-off-by: Alexey Kardashevskiy <aik@ozlabs.ru>
[aw: for the vfio related changes]
Acked-by: Alex Williamson <alex.williamson@redhat.com>
Reviewed-by: David Gibson <david@gibson.dropbear.id.au>
Signed-off-by: Michael Ellerman <mpe@ellerman.id.au>
This commit is contained in:
Alexey Kardashevskiy
2015-06-05 16:35:26 +10:00
committed by Michael Ellerman
parent 2157e7b82f
commit e633bc86a9
4 changed files with 273 additions and 5 deletions

View File

@@ -211,6 +211,18 @@ static long tce_iommu_find_table(struct tce_container *container,
return -1;
}
static int tce_iommu_find_free_table(struct tce_container *container)
{
int i;
for (i = 0; i < IOMMU_TABLE_GROUP_MAX_TABLES; ++i) {
if (!container->tables[i])
return i;
}
return -ENOSPC;
}
static int tce_iommu_enable(struct tce_container *container)
{
int ret = 0;
@@ -593,11 +605,115 @@ static void tce_iommu_free_table(struct iommu_table *tbl)
decrement_locked_vm(pages);
}
static long tce_iommu_create_window(struct tce_container *container,
__u32 page_shift, __u64 window_size, __u32 levels,
__u64 *start_addr)
{
struct tce_iommu_group *tcegrp;
struct iommu_table_group *table_group;
struct iommu_table *tbl = NULL;
long ret, num;
num = tce_iommu_find_free_table(container);
if (num < 0)
return num;
/* Get the first group for ops::create_table */
tcegrp = list_first_entry(&container->group_list,
struct tce_iommu_group, next);
table_group = iommu_group_get_iommudata(tcegrp->grp);
if (!table_group)
return -EFAULT;
if (!(table_group->pgsizes & (1ULL << page_shift)))
return -EINVAL;
if (!table_group->ops->set_window || !table_group->ops->unset_window ||
!table_group->ops->get_table_size ||
!table_group->ops->create_table)
return -EPERM;
/* Create TCE table */
ret = tce_iommu_create_table(container, table_group, num,
page_shift, window_size, levels, &tbl);
if (ret)
return ret;
BUG_ON(!tbl->it_ops->free);
/*
* Program the table to every group.
* Groups have been tested for compatibility at the attach time.
*/
list_for_each_entry(tcegrp, &container->group_list, next) {
table_group = iommu_group_get_iommudata(tcegrp->grp);
ret = table_group->ops->set_window(table_group, num, tbl);
if (ret)
goto unset_exit;
}
container->tables[num] = tbl;
/* Return start address assigned by platform in create_table() */
*start_addr = tbl->it_offset << tbl->it_page_shift;
return 0;
unset_exit:
list_for_each_entry(tcegrp, &container->group_list, next) {
table_group = iommu_group_get_iommudata(tcegrp->grp);
table_group->ops->unset_window(table_group, num);
}
tce_iommu_free_table(tbl);
return ret;
}
static long tce_iommu_remove_window(struct tce_container *container,
__u64 start_addr)
{
struct iommu_table_group *table_group = NULL;
struct iommu_table *tbl;
struct tce_iommu_group *tcegrp;
int num;
num = tce_iommu_find_table(container, start_addr, &tbl);
if (num < 0)
return -EINVAL;
BUG_ON(!tbl->it_size);
/* Detach groups from IOMMUs */
list_for_each_entry(tcegrp, &container->group_list, next) {
table_group = iommu_group_get_iommudata(tcegrp->grp);
/*
* SPAPR TCE IOMMU exposes the default DMA window to
* the guest via dma32_window_start/size of
* VFIO_IOMMU_SPAPR_TCE_GET_INFO. Some platforms allow
* the userspace to remove this window, some do not so
* here we check for the platform capability.
*/
if (!table_group->ops || !table_group->ops->unset_window)
return -EPERM;
table_group->ops->unset_window(table_group, num);
}
/* Free table */
tce_iommu_clear(container, tbl, tbl->it_offset, tbl->it_size);
tce_iommu_free_table(tbl);
container->tables[num] = NULL;
return 0;
}
static long tce_iommu_ioctl(void *iommu_data,
unsigned int cmd, unsigned long arg)
{
struct tce_container *container = iommu_data;
unsigned long minsz;
unsigned long minsz, ddwsz;
long ret;
switch (cmd) {
@@ -641,6 +757,21 @@ static long tce_iommu_ioctl(void *iommu_data,
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;
@@ -834,6 +965,69 @@ static long tce_iommu_ioctl(void *iommu_data,
return ret;
}
case VFIO_IOMMU_SPAPR_TCE_CREATE: {
struct vfio_iommu_spapr_tce_create create;
if (!container->v2)
break;
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_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;
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;
mutex_lock(&container->lock);
ret = tce_iommu_remove_window(container, remove.start_addr);
mutex_unlock(&container->lock);
return ret;
}
}
return -ENOTTY;