mirror of https://github.com/torvalds/linux.git
iommufd 6.19 pull request
- Expand IOMMU_IOAS_MAP_FILE to accept a DMABUF exported from VFIO. This
is the first step to broader DMABUF support in iommufd, right now it
only works with VFIO. This closes the last functional gap with classic
VFIO type 1 to safely support PCI peer to peer DMA by mapping the VFIO
device's MMIO into the IOMMU.
- Relax SMMUv3 restrictions on nesting domains to better support qemu's
sequence to have an identity mapping before the vSID is established.
-----BEGIN PGP SIGNATURE-----
iHUEABYKAB0WIQRRRCHOFoQz/8F5bUaFwuHvBreFYQUCaS8DrgAKCRCFwuHvBreF
YY/kAP0Q1s7LkVb83uQb8kIW3xKzEnFNTlhrSSGV5UBuYLbaDgD+J+y+4VrSkJem
85LMipmzaoZdHqtxMhQWrlYbZMr9TAM=
=BacK
-----END PGP SIGNATURE-----
Merge tag 'for-linus-iommufd' of git://git.kernel.org/pub/scm/linux/kernel/git/jgg/iommufd
Pull iommufd updates from Jason Gunthorpe:
"This is a pretty consequential cycle for iommufd, though this pull is
not too big. It is based on a shared branch with VFIO that introduces
VFIO_DEVICE_FEATURE_DMA_BUF a DMABUF exporter for VFIO device's MMIO
PCI BARs. This was a large multiple series journey over the last year
and a half.
Based on that work IOMMUFD gains support for VFIO DMABUF's in its
existing IOMMU_IOAS_MAP_FILE, which closes the last major gap to
support PCI peer to peer transfers within VMs.
In Joerg's iommu tree we have the "generic page table" work which aims
to consolidate all the duplicated page table code in every iommu
driver into a single algorithm. This will be used by iommufd to
implement unique page table operations to start adding new features
and improve performance.
In here:
- Expand IOMMU_IOAS_MAP_FILE to accept a DMABUF exported from VFIO.
This is the first step to broader DMABUF support in iommufd, right
now it only works with VFIO. This closes the last functional gap
with classic VFIO type 1 to safely support PCI peer to peer DMA by
mapping the VFIO device's MMIO into the IOMMU.
- Relax SMMUv3 restrictions on nesting domains to better support
qemu's sequence to have an identity mapping before the vSID is
established"
* tag 'for-linus-iommufd' of git://git.kernel.org/pub/scm/linux/kernel/git/jgg/iommufd:
iommu/arm-smmu-v3-iommufd: Allow attaching nested domain for GBPA cases
iommufd/selftest: Add some tests for the dmabuf flow
iommufd: Accept a DMABUF through IOMMU_IOAS_MAP_FILE
iommufd: Have iopt_map_file_pages convert the fd to a file
iommufd: Have pfn_reader process DMABUF iopt_pages
iommufd: Allow MMIO pages in a batch
iommufd: Allow a DMABUF to be revoked
iommufd: Do not map/unmap revoked DMABUFs
iommufd: Add DMABUF to iopt_pages
vfio/pci: Add vfio_pci_dma_buf_iommufd_map()
This commit is contained in:
commit
056daec292
|
|
@ -99,6 +99,8 @@ static void arm_smmu_make_nested_domain_ste(
|
||||||
int arm_smmu_attach_prepare_vmaster(struct arm_smmu_attach_state *state,
|
int arm_smmu_attach_prepare_vmaster(struct arm_smmu_attach_state *state,
|
||||||
struct arm_smmu_nested_domain *nested_domain)
|
struct arm_smmu_nested_domain *nested_domain)
|
||||||
{
|
{
|
||||||
|
unsigned int cfg =
|
||||||
|
FIELD_GET(STRTAB_STE_0_CFG, le64_to_cpu(nested_domain->ste[0]));
|
||||||
struct arm_smmu_vmaster *vmaster;
|
struct arm_smmu_vmaster *vmaster;
|
||||||
unsigned long vsid;
|
unsigned long vsid;
|
||||||
int ret;
|
int ret;
|
||||||
|
|
@ -107,8 +109,17 @@ int arm_smmu_attach_prepare_vmaster(struct arm_smmu_attach_state *state,
|
||||||
|
|
||||||
ret = iommufd_viommu_get_vdev_id(&nested_domain->vsmmu->core,
|
ret = iommufd_viommu_get_vdev_id(&nested_domain->vsmmu->core,
|
||||||
state->master->dev, &vsid);
|
state->master->dev, &vsid);
|
||||||
if (ret)
|
/*
|
||||||
|
* Attaching to a translate nested domain must allocate a vDEVICE prior,
|
||||||
|
* as CD/ATS invalidations and vevents require a vSID to work properly.
|
||||||
|
* A abort/bypass domain is allowed to attach w/o vmaster for GBPA case.
|
||||||
|
*/
|
||||||
|
if (ret) {
|
||||||
|
if (cfg == STRTAB_STE_0_CFG_ABORT ||
|
||||||
|
cfg == STRTAB_STE_0_CFG_BYPASS)
|
||||||
|
return 0;
|
||||||
return ret;
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
vmaster = kzalloc(sizeof(*vmaster), GFP_KERNEL);
|
vmaster = kzalloc(sizeof(*vmaster), GFP_KERNEL);
|
||||||
if (!vmaster)
|
if (!vmaster)
|
||||||
|
|
|
||||||
|
|
@ -8,8 +8,10 @@
|
||||||
* The datastructure uses the iopt_pages to optimize the storage of the PFNs
|
* The datastructure uses the iopt_pages to optimize the storage of the PFNs
|
||||||
* between the domains and xarray.
|
* between the domains and xarray.
|
||||||
*/
|
*/
|
||||||
|
#include <linux/dma-buf.h>
|
||||||
#include <linux/err.h>
|
#include <linux/err.h>
|
||||||
#include <linux/errno.h>
|
#include <linux/errno.h>
|
||||||
|
#include <linux/file.h>
|
||||||
#include <linux/iommu.h>
|
#include <linux/iommu.h>
|
||||||
#include <linux/iommufd.h>
|
#include <linux/iommufd.h>
|
||||||
#include <linux/lockdep.h>
|
#include <linux/lockdep.h>
|
||||||
|
|
@ -284,6 +286,9 @@ static int iopt_alloc_area_pages(struct io_pagetable *iopt,
|
||||||
case IOPT_ADDRESS_FILE:
|
case IOPT_ADDRESS_FILE:
|
||||||
start = elm->start_byte + elm->pages->start;
|
start = elm->start_byte + elm->pages->start;
|
||||||
break;
|
break;
|
||||||
|
case IOPT_ADDRESS_DMABUF:
|
||||||
|
start = elm->start_byte + elm->pages->dmabuf.start;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
rc = iopt_alloc_iova(iopt, dst_iova, start, length);
|
rc = iopt_alloc_iova(iopt, dst_iova, start, length);
|
||||||
if (rc)
|
if (rc)
|
||||||
|
|
@ -468,25 +473,53 @@ int iopt_map_user_pages(struct iommufd_ctx *ictx, struct io_pagetable *iopt,
|
||||||
* @iopt: io_pagetable to act on
|
* @iopt: io_pagetable to act on
|
||||||
* @iova: If IOPT_ALLOC_IOVA is set this is unused on input and contains
|
* @iova: If IOPT_ALLOC_IOVA is set this is unused on input and contains
|
||||||
* the chosen iova on output. Otherwise is the iova to map to on input
|
* the chosen iova on output. Otherwise is the iova to map to on input
|
||||||
* @file: file to map
|
* @fd: fdno of a file to map
|
||||||
* @start: map file starting at this byte offset
|
* @start: map file starting at this byte offset
|
||||||
* @length: Number of bytes to map
|
* @length: Number of bytes to map
|
||||||
* @iommu_prot: Combination of IOMMU_READ/WRITE/etc bits for the mapping
|
* @iommu_prot: Combination of IOMMU_READ/WRITE/etc bits for the mapping
|
||||||
* @flags: IOPT_ALLOC_IOVA or zero
|
* @flags: IOPT_ALLOC_IOVA or zero
|
||||||
*/
|
*/
|
||||||
int iopt_map_file_pages(struct iommufd_ctx *ictx, struct io_pagetable *iopt,
|
int iopt_map_file_pages(struct iommufd_ctx *ictx, struct io_pagetable *iopt,
|
||||||
unsigned long *iova, struct file *file,
|
unsigned long *iova, int fd, unsigned long start,
|
||||||
unsigned long start, unsigned long length,
|
unsigned long length, int iommu_prot,
|
||||||
int iommu_prot, unsigned int flags)
|
unsigned int flags)
|
||||||
{
|
{
|
||||||
struct iopt_pages *pages;
|
struct iopt_pages *pages;
|
||||||
|
struct dma_buf *dmabuf;
|
||||||
|
unsigned long start_byte;
|
||||||
|
unsigned long last;
|
||||||
|
|
||||||
pages = iopt_alloc_file_pages(file, start, length,
|
if (!length)
|
||||||
|
return -EINVAL;
|
||||||
|
if (check_add_overflow(start, length - 1, &last))
|
||||||
|
return -EOVERFLOW;
|
||||||
|
|
||||||
|
start_byte = start - ALIGN_DOWN(start, PAGE_SIZE);
|
||||||
|
dmabuf = dma_buf_get(fd);
|
||||||
|
if (!IS_ERR(dmabuf)) {
|
||||||
|
pages = iopt_alloc_dmabuf_pages(ictx, dmabuf, start_byte, start,
|
||||||
|
length,
|
||||||
iommu_prot & IOMMU_WRITE);
|
iommu_prot & IOMMU_WRITE);
|
||||||
|
if (IS_ERR(pages)) {
|
||||||
|
dma_buf_put(dmabuf);
|
||||||
|
return PTR_ERR(pages);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
struct file *file;
|
||||||
|
|
||||||
|
file = fget(fd);
|
||||||
|
if (!file)
|
||||||
|
return -EBADF;
|
||||||
|
|
||||||
|
pages = iopt_alloc_file_pages(file, start_byte, start, length,
|
||||||
|
iommu_prot & IOMMU_WRITE);
|
||||||
|
fput(file);
|
||||||
if (IS_ERR(pages))
|
if (IS_ERR(pages))
|
||||||
return PTR_ERR(pages);
|
return PTR_ERR(pages);
|
||||||
|
}
|
||||||
|
|
||||||
return iopt_map_common(ictx, iopt, pages, iova, length,
|
return iopt_map_common(ictx, iopt, pages, iova, length,
|
||||||
start - pages->start, iommu_prot, flags);
|
start_byte, iommu_prot, flags);
|
||||||
}
|
}
|
||||||
|
|
||||||
struct iova_bitmap_fn_arg {
|
struct iova_bitmap_fn_arg {
|
||||||
|
|
@ -961,8 +994,14 @@ static void iopt_unfill_domain(struct io_pagetable *iopt,
|
||||||
WARN_ON(!area->storage_domain);
|
WARN_ON(!area->storage_domain);
|
||||||
if (area->storage_domain == domain)
|
if (area->storage_domain == domain)
|
||||||
area->storage_domain = storage_domain;
|
area->storage_domain = storage_domain;
|
||||||
|
if (iopt_is_dmabuf(pages)) {
|
||||||
|
if (!iopt_dmabuf_revoked(pages))
|
||||||
|
iopt_area_unmap_domain(area, domain);
|
||||||
|
iopt_dmabuf_untrack_domain(pages, area, domain);
|
||||||
|
}
|
||||||
mutex_unlock(&pages->mutex);
|
mutex_unlock(&pages->mutex);
|
||||||
|
|
||||||
|
if (!iopt_is_dmabuf(pages))
|
||||||
iopt_area_unmap_domain(area, domain);
|
iopt_area_unmap_domain(area, domain);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
|
|
@ -980,6 +1019,8 @@ static void iopt_unfill_domain(struct io_pagetable *iopt,
|
||||||
WARN_ON(area->storage_domain != domain);
|
WARN_ON(area->storage_domain != domain);
|
||||||
area->storage_domain = NULL;
|
area->storage_domain = NULL;
|
||||||
iopt_area_unfill_domain(area, pages, domain);
|
iopt_area_unfill_domain(area, pages, domain);
|
||||||
|
if (iopt_is_dmabuf(pages))
|
||||||
|
iopt_dmabuf_untrack_domain(pages, area, domain);
|
||||||
mutex_unlock(&pages->mutex);
|
mutex_unlock(&pages->mutex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1009,10 +1050,16 @@ static int iopt_fill_domain(struct io_pagetable *iopt,
|
||||||
if (!pages)
|
if (!pages)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
mutex_lock(&pages->mutex);
|
guard(mutex)(&pages->mutex);
|
||||||
|
if (iopt_is_dmabuf(pages)) {
|
||||||
|
rc = iopt_dmabuf_track_domain(pages, area, domain);
|
||||||
|
if (rc)
|
||||||
|
goto out_unfill;
|
||||||
|
}
|
||||||
rc = iopt_area_fill_domain(area, domain);
|
rc = iopt_area_fill_domain(area, domain);
|
||||||
if (rc) {
|
if (rc) {
|
||||||
mutex_unlock(&pages->mutex);
|
if (iopt_is_dmabuf(pages))
|
||||||
|
iopt_dmabuf_untrack_domain(pages, area, domain);
|
||||||
goto out_unfill;
|
goto out_unfill;
|
||||||
}
|
}
|
||||||
if (!area->storage_domain) {
|
if (!area->storage_domain) {
|
||||||
|
|
@ -1021,7 +1068,6 @@ static int iopt_fill_domain(struct io_pagetable *iopt,
|
||||||
interval_tree_insert(&area->pages_node,
|
interval_tree_insert(&area->pages_node,
|
||||||
&pages->domains_itree);
|
&pages->domains_itree);
|
||||||
}
|
}
|
||||||
mutex_unlock(&pages->mutex);
|
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
|
|
@ -1042,6 +1088,8 @@ static int iopt_fill_domain(struct io_pagetable *iopt,
|
||||||
area->storage_domain = NULL;
|
area->storage_domain = NULL;
|
||||||
}
|
}
|
||||||
iopt_area_unfill_domain(area, pages, domain);
|
iopt_area_unfill_domain(area, pages, domain);
|
||||||
|
if (iopt_is_dmabuf(pages))
|
||||||
|
iopt_dmabuf_untrack_domain(pages, area, domain);
|
||||||
mutex_unlock(&pages->mutex);
|
mutex_unlock(&pages->mutex);
|
||||||
}
|
}
|
||||||
return rc;
|
return rc;
|
||||||
|
|
@ -1252,6 +1300,10 @@ static int iopt_area_split(struct iopt_area *area, unsigned long iova)
|
||||||
if (!pages || area->prevent_access)
|
if (!pages || area->prevent_access)
|
||||||
return -EBUSY;
|
return -EBUSY;
|
||||||
|
|
||||||
|
/* Maintaining the domains_itree below is a bit complicated */
|
||||||
|
if (iopt_is_dmabuf(pages))
|
||||||
|
return -EOPNOTSUPP;
|
||||||
|
|
||||||
if (new_start & (alignment - 1) ||
|
if (new_start & (alignment - 1) ||
|
||||||
iopt_area_start_byte(area, new_start) & (alignment - 1))
|
iopt_area_start_byte(area, new_start) & (alignment - 1))
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@
|
||||||
#ifndef __IO_PAGETABLE_H
|
#ifndef __IO_PAGETABLE_H
|
||||||
#define __IO_PAGETABLE_H
|
#define __IO_PAGETABLE_H
|
||||||
|
|
||||||
|
#include <linux/dma-buf.h>
|
||||||
#include <linux/interval_tree.h>
|
#include <linux/interval_tree.h>
|
||||||
#include <linux/kref.h>
|
#include <linux/kref.h>
|
||||||
#include <linux/mutex.h>
|
#include <linux/mutex.h>
|
||||||
|
|
@ -69,6 +70,16 @@ void iopt_area_unfill_domain(struct iopt_area *area, struct iopt_pages *pages,
|
||||||
void iopt_area_unmap_domain(struct iopt_area *area,
|
void iopt_area_unmap_domain(struct iopt_area *area,
|
||||||
struct iommu_domain *domain);
|
struct iommu_domain *domain);
|
||||||
|
|
||||||
|
int iopt_dmabuf_track_domain(struct iopt_pages *pages, struct iopt_area *area,
|
||||||
|
struct iommu_domain *domain);
|
||||||
|
void iopt_dmabuf_untrack_domain(struct iopt_pages *pages,
|
||||||
|
struct iopt_area *area,
|
||||||
|
struct iommu_domain *domain);
|
||||||
|
int iopt_dmabuf_track_all_domains(struct iopt_area *area,
|
||||||
|
struct iopt_pages *pages);
|
||||||
|
void iopt_dmabuf_untrack_all_domains(struct iopt_area *area,
|
||||||
|
struct iopt_pages *pages);
|
||||||
|
|
||||||
static inline unsigned long iopt_area_index(struct iopt_area *area)
|
static inline unsigned long iopt_area_index(struct iopt_area *area)
|
||||||
{
|
{
|
||||||
return area->pages_node.start;
|
return area->pages_node.start;
|
||||||
|
|
@ -179,7 +190,22 @@ enum {
|
||||||
|
|
||||||
enum iopt_address_type {
|
enum iopt_address_type {
|
||||||
IOPT_ADDRESS_USER = 0,
|
IOPT_ADDRESS_USER = 0,
|
||||||
IOPT_ADDRESS_FILE = 1,
|
IOPT_ADDRESS_FILE,
|
||||||
|
IOPT_ADDRESS_DMABUF,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct iopt_pages_dmabuf_track {
|
||||||
|
struct iommu_domain *domain;
|
||||||
|
struct iopt_area *area;
|
||||||
|
struct list_head elm;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct iopt_pages_dmabuf {
|
||||||
|
struct dma_buf_attachment *attach;
|
||||||
|
struct dma_buf_phys_vec phys;
|
||||||
|
/* Always PAGE_SIZE aligned */
|
||||||
|
unsigned long start;
|
||||||
|
struct list_head tracker;
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
@ -209,6 +235,8 @@ struct iopt_pages {
|
||||||
struct file *file;
|
struct file *file;
|
||||||
unsigned long start;
|
unsigned long start;
|
||||||
};
|
};
|
||||||
|
/* IOPT_ADDRESS_DMABUF */
|
||||||
|
struct iopt_pages_dmabuf dmabuf;
|
||||||
};
|
};
|
||||||
bool writable:1;
|
bool writable:1;
|
||||||
u8 account_mode;
|
u8 account_mode;
|
||||||
|
|
@ -220,9 +248,31 @@ struct iopt_pages {
|
||||||
struct rb_root_cached domains_itree;
|
struct rb_root_cached domains_itree;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static inline bool iopt_is_dmabuf(struct iopt_pages *pages)
|
||||||
|
{
|
||||||
|
if (!IS_ENABLED(CONFIG_DMA_SHARED_BUFFER))
|
||||||
|
return false;
|
||||||
|
return pages->type == IOPT_ADDRESS_DMABUF;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bool iopt_dmabuf_revoked(struct iopt_pages *pages)
|
||||||
|
{
|
||||||
|
lockdep_assert_held(&pages->mutex);
|
||||||
|
if (iopt_is_dmabuf(pages))
|
||||||
|
return pages->dmabuf.phys.len == 0;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
struct iopt_pages *iopt_alloc_user_pages(void __user *uptr,
|
struct iopt_pages *iopt_alloc_user_pages(void __user *uptr,
|
||||||
unsigned long length, bool writable);
|
unsigned long length, bool writable);
|
||||||
struct iopt_pages *iopt_alloc_file_pages(struct file *file, unsigned long start,
|
struct iopt_pages *iopt_alloc_file_pages(struct file *file,
|
||||||
|
unsigned long start_byte,
|
||||||
|
unsigned long start,
|
||||||
|
unsigned long length, bool writable);
|
||||||
|
struct iopt_pages *iopt_alloc_dmabuf_pages(struct iommufd_ctx *ictx,
|
||||||
|
struct dma_buf *dmabuf,
|
||||||
|
unsigned long start_byte,
|
||||||
|
unsigned long start,
|
||||||
unsigned long length, bool writable);
|
unsigned long length, bool writable);
|
||||||
void iopt_release_pages(struct kref *kref);
|
void iopt_release_pages(struct kref *kref);
|
||||||
static inline void iopt_put_pages(struct iopt_pages *pages)
|
static inline void iopt_put_pages(struct iopt_pages *pages)
|
||||||
|
|
|
||||||
|
|
@ -207,7 +207,6 @@ int iommufd_ioas_map_file(struct iommufd_ucmd *ucmd)
|
||||||
unsigned long iova = cmd->iova;
|
unsigned long iova = cmd->iova;
|
||||||
struct iommufd_ioas *ioas;
|
struct iommufd_ioas *ioas;
|
||||||
unsigned int flags = 0;
|
unsigned int flags = 0;
|
||||||
struct file *file;
|
|
||||||
int rc;
|
int rc;
|
||||||
|
|
||||||
if (cmd->flags &
|
if (cmd->flags &
|
||||||
|
|
@ -229,11 +228,7 @@ int iommufd_ioas_map_file(struct iommufd_ucmd *ucmd)
|
||||||
if (!(cmd->flags & IOMMU_IOAS_MAP_FIXED_IOVA))
|
if (!(cmd->flags & IOMMU_IOAS_MAP_FIXED_IOVA))
|
||||||
flags = IOPT_ALLOC_IOVA;
|
flags = IOPT_ALLOC_IOVA;
|
||||||
|
|
||||||
file = fget(cmd->fd);
|
rc = iopt_map_file_pages(ucmd->ictx, &ioas->iopt, &iova, cmd->fd,
|
||||||
if (!file)
|
|
||||||
return -EBADF;
|
|
||||||
|
|
||||||
rc = iopt_map_file_pages(ucmd->ictx, &ioas->iopt, &iova, file,
|
|
||||||
cmd->start, cmd->length,
|
cmd->start, cmd->length,
|
||||||
conv_iommu_prot(cmd->flags), flags);
|
conv_iommu_prot(cmd->flags), flags);
|
||||||
if (rc)
|
if (rc)
|
||||||
|
|
@ -243,7 +238,6 @@ int iommufd_ioas_map_file(struct iommufd_ucmd *ucmd)
|
||||||
rc = iommufd_ucmd_respond(ucmd, sizeof(*cmd));
|
rc = iommufd_ucmd_respond(ucmd, sizeof(*cmd));
|
||||||
out_put:
|
out_put:
|
||||||
iommufd_put_object(ucmd->ictx, &ioas->obj);
|
iommufd_put_object(ucmd->ictx, &ioas->obj);
|
||||||
fput(file);
|
|
||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,8 @@ struct iommu_domain;
|
||||||
struct iommu_group;
|
struct iommu_group;
|
||||||
struct iommu_option;
|
struct iommu_option;
|
||||||
struct iommufd_device;
|
struct iommufd_device;
|
||||||
|
struct dma_buf_attachment;
|
||||||
|
struct dma_buf_phys_vec;
|
||||||
|
|
||||||
struct iommufd_sw_msi_map {
|
struct iommufd_sw_msi_map {
|
||||||
struct list_head sw_msi_item;
|
struct list_head sw_msi_item;
|
||||||
|
|
@ -108,7 +110,7 @@ int iopt_map_user_pages(struct iommufd_ctx *ictx, struct io_pagetable *iopt,
|
||||||
unsigned long length, int iommu_prot,
|
unsigned long length, int iommu_prot,
|
||||||
unsigned int flags);
|
unsigned int flags);
|
||||||
int iopt_map_file_pages(struct iommufd_ctx *ictx, struct io_pagetable *iopt,
|
int iopt_map_file_pages(struct iommufd_ctx *ictx, struct io_pagetable *iopt,
|
||||||
unsigned long *iova, struct file *file,
|
unsigned long *iova, int fd,
|
||||||
unsigned long start, unsigned long length,
|
unsigned long start, unsigned long length,
|
||||||
int iommu_prot, unsigned int flags);
|
int iommu_prot, unsigned int flags);
|
||||||
int iopt_map_pages(struct io_pagetable *iopt, struct list_head *pages_list,
|
int iopt_map_pages(struct io_pagetable *iopt, struct list_head *pages_list,
|
||||||
|
|
@ -504,6 +506,8 @@ void iommufd_device_pre_destroy(struct iommufd_object *obj);
|
||||||
void iommufd_device_destroy(struct iommufd_object *obj);
|
void iommufd_device_destroy(struct iommufd_object *obj);
|
||||||
int iommufd_get_hw_info(struct iommufd_ucmd *ucmd);
|
int iommufd_get_hw_info(struct iommufd_ucmd *ucmd);
|
||||||
|
|
||||||
|
struct device *iommufd_global_device(void);
|
||||||
|
|
||||||
struct iommufd_access {
|
struct iommufd_access {
|
||||||
struct iommufd_object obj;
|
struct iommufd_object obj;
|
||||||
struct iommufd_ctx *ictx;
|
struct iommufd_ctx *ictx;
|
||||||
|
|
@ -713,6 +717,8 @@ bool iommufd_should_fail(void);
|
||||||
int __init iommufd_test_init(void);
|
int __init iommufd_test_init(void);
|
||||||
void iommufd_test_exit(void);
|
void iommufd_test_exit(void);
|
||||||
bool iommufd_selftest_is_mock_dev(struct device *dev);
|
bool iommufd_selftest_is_mock_dev(struct device *dev);
|
||||||
|
int iommufd_test_dma_buf_iommufd_map(struct dma_buf_attachment *attachment,
|
||||||
|
struct dma_buf_phys_vec *phys);
|
||||||
#else
|
#else
|
||||||
static inline void iommufd_test_syz_conv_iova_id(struct iommufd_ucmd *ucmd,
|
static inline void iommufd_test_syz_conv_iova_id(struct iommufd_ucmd *ucmd,
|
||||||
unsigned int ioas_id,
|
unsigned int ioas_id,
|
||||||
|
|
@ -734,5 +740,11 @@ static inline bool iommufd_selftest_is_mock_dev(struct device *dev)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
static inline int
|
||||||
|
iommufd_test_dma_buf_iommufd_map(struct dma_buf_attachment *attachment,
|
||||||
|
struct dma_buf_phys_vec *phys)
|
||||||
|
{
|
||||||
|
return -EOPNOTSUPP;
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,8 @@ enum {
|
||||||
IOMMU_TEST_OP_PASID_REPLACE,
|
IOMMU_TEST_OP_PASID_REPLACE,
|
||||||
IOMMU_TEST_OP_PASID_DETACH,
|
IOMMU_TEST_OP_PASID_DETACH,
|
||||||
IOMMU_TEST_OP_PASID_CHECK_HWPT,
|
IOMMU_TEST_OP_PASID_CHECK_HWPT,
|
||||||
|
IOMMU_TEST_OP_DMABUF_GET,
|
||||||
|
IOMMU_TEST_OP_DMABUF_REVOKE,
|
||||||
};
|
};
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
|
|
@ -184,6 +186,14 @@ struct iommu_test_cmd {
|
||||||
__u32 hwpt_id;
|
__u32 hwpt_id;
|
||||||
/* @id is stdev_id */
|
/* @id is stdev_id */
|
||||||
} pasid_check;
|
} pasid_check;
|
||||||
|
struct {
|
||||||
|
__u32 length;
|
||||||
|
__u32 open_flags;
|
||||||
|
} dmabuf_get;
|
||||||
|
struct {
|
||||||
|
__s32 dmabuf_fd;
|
||||||
|
__u32 revoked;
|
||||||
|
} dmabuf_revoke;
|
||||||
};
|
};
|
||||||
__u32 last;
|
__u32 last;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -751,6 +751,15 @@ static struct miscdevice vfio_misc_dev = {
|
||||||
.mode = 0666,
|
.mode = 0666,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Used only by DMABUF, returns a valid struct device to use as a dummy struct
|
||||||
|
* device for attachment.
|
||||||
|
*/
|
||||||
|
struct device *iommufd_global_device(void)
|
||||||
|
{
|
||||||
|
return iommu_misc_dev.this_device;
|
||||||
|
}
|
||||||
|
|
||||||
static int __init iommufd_init(void)
|
static int __init iommufd_init(void)
|
||||||
{
|
{
|
||||||
int ret;
|
int ret;
|
||||||
|
|
@ -794,5 +803,6 @@ MODULE_ALIAS("devname:vfio/vfio");
|
||||||
#endif
|
#endif
|
||||||
MODULE_IMPORT_NS("IOMMUFD_INTERNAL");
|
MODULE_IMPORT_NS("IOMMUFD_INTERNAL");
|
||||||
MODULE_IMPORT_NS("IOMMUFD");
|
MODULE_IMPORT_NS("IOMMUFD");
|
||||||
|
MODULE_IMPORT_NS("DMA_BUF");
|
||||||
MODULE_DESCRIPTION("I/O Address Space Management for passthrough devices");
|
MODULE_DESCRIPTION("I/O Address Space Management for passthrough devices");
|
||||||
MODULE_LICENSE("GPL");
|
MODULE_LICENSE("GPL");
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,8 @@
|
||||||
* last_iova + 1 can overflow. An iopt_pages index will always be much less than
|
* last_iova + 1 can overflow. An iopt_pages index will always be much less than
|
||||||
* ULONG_MAX so last_index + 1 cannot overflow.
|
* ULONG_MAX so last_index + 1 cannot overflow.
|
||||||
*/
|
*/
|
||||||
|
#include <linux/dma-buf.h>
|
||||||
|
#include <linux/dma-resv.h>
|
||||||
#include <linux/file.h>
|
#include <linux/file.h>
|
||||||
#include <linux/highmem.h>
|
#include <linux/highmem.h>
|
||||||
#include <linux/iommu.h>
|
#include <linux/iommu.h>
|
||||||
|
|
@ -53,6 +55,7 @@
|
||||||
#include <linux/overflow.h>
|
#include <linux/overflow.h>
|
||||||
#include <linux/slab.h>
|
#include <linux/slab.h>
|
||||||
#include <linux/sched/mm.h>
|
#include <linux/sched/mm.h>
|
||||||
|
#include <linux/vfio_pci_core.h>
|
||||||
|
|
||||||
#include "double_span.h"
|
#include "double_span.h"
|
||||||
#include "io_pagetable.h"
|
#include "io_pagetable.h"
|
||||||
|
|
@ -258,6 +261,11 @@ static struct iopt_area *iopt_pages_find_domain_area(struct iopt_pages *pages,
|
||||||
return container_of(node, struct iopt_area, pages_node);
|
return container_of(node, struct iopt_area, pages_node);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum batch_kind {
|
||||||
|
BATCH_CPU_MEMORY = 0,
|
||||||
|
BATCH_MMIO,
|
||||||
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* A simple datastructure to hold a vector of PFNs, optimized for contiguous
|
* A simple datastructure to hold a vector of PFNs, optimized for contiguous
|
||||||
* PFNs. This is used as a temporary holding memory for shuttling pfns from one
|
* PFNs. This is used as a temporary holding memory for shuttling pfns from one
|
||||||
|
|
@ -271,7 +279,9 @@ struct pfn_batch {
|
||||||
unsigned int array_size;
|
unsigned int array_size;
|
||||||
unsigned int end;
|
unsigned int end;
|
||||||
unsigned int total_pfns;
|
unsigned int total_pfns;
|
||||||
|
enum batch_kind kind;
|
||||||
};
|
};
|
||||||
|
enum { MAX_NPFNS = type_max(typeof(((struct pfn_batch *)0)->npfns[0])) };
|
||||||
|
|
||||||
static void batch_clear(struct pfn_batch *batch)
|
static void batch_clear(struct pfn_batch *batch)
|
||||||
{
|
{
|
||||||
|
|
@ -348,11 +358,17 @@ static void batch_destroy(struct pfn_batch *batch, void *backup)
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool batch_add_pfn_num(struct pfn_batch *batch, unsigned long pfn,
|
static bool batch_add_pfn_num(struct pfn_batch *batch, unsigned long pfn,
|
||||||
u32 nr)
|
u32 nr, enum batch_kind kind)
|
||||||
{
|
{
|
||||||
const unsigned int MAX_NPFNS = type_max(typeof(*batch->npfns));
|
|
||||||
unsigned int end = batch->end;
|
unsigned int end = batch->end;
|
||||||
|
|
||||||
|
if (batch->kind != kind) {
|
||||||
|
/* One kind per batch */
|
||||||
|
if (batch->end != 0)
|
||||||
|
return false;
|
||||||
|
batch->kind = kind;
|
||||||
|
}
|
||||||
|
|
||||||
if (end && pfn == batch->pfns[end - 1] + batch->npfns[end - 1] &&
|
if (end && pfn == batch->pfns[end - 1] + batch->npfns[end - 1] &&
|
||||||
nr <= MAX_NPFNS - batch->npfns[end - 1]) {
|
nr <= MAX_NPFNS - batch->npfns[end - 1]) {
|
||||||
batch->npfns[end - 1] += nr;
|
batch->npfns[end - 1] += nr;
|
||||||
|
|
@ -379,7 +395,7 @@ static void batch_remove_pfn_num(struct pfn_batch *batch, unsigned long nr)
|
||||||
/* true if the pfn was added, false otherwise */
|
/* true if the pfn was added, false otherwise */
|
||||||
static bool batch_add_pfn(struct pfn_batch *batch, unsigned long pfn)
|
static bool batch_add_pfn(struct pfn_batch *batch, unsigned long pfn)
|
||||||
{
|
{
|
||||||
return batch_add_pfn_num(batch, pfn, 1);
|
return batch_add_pfn_num(batch, pfn, 1, BATCH_CPU_MEMORY);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
@ -492,6 +508,7 @@ static int batch_to_domain(struct pfn_batch *batch, struct iommu_domain *domain,
|
||||||
{
|
{
|
||||||
bool disable_large_pages = area->iopt->disable_large_pages;
|
bool disable_large_pages = area->iopt->disable_large_pages;
|
||||||
unsigned long last_iova = iopt_area_last_iova(area);
|
unsigned long last_iova = iopt_area_last_iova(area);
|
||||||
|
int iommu_prot = area->iommu_prot;
|
||||||
unsigned int page_offset = 0;
|
unsigned int page_offset = 0;
|
||||||
unsigned long start_iova;
|
unsigned long start_iova;
|
||||||
unsigned long next_iova;
|
unsigned long next_iova;
|
||||||
|
|
@ -499,6 +516,11 @@ static int batch_to_domain(struct pfn_batch *batch, struct iommu_domain *domain,
|
||||||
unsigned long iova;
|
unsigned long iova;
|
||||||
int rc;
|
int rc;
|
||||||
|
|
||||||
|
if (batch->kind == BATCH_MMIO) {
|
||||||
|
iommu_prot &= ~IOMMU_CACHE;
|
||||||
|
iommu_prot |= IOMMU_MMIO;
|
||||||
|
}
|
||||||
|
|
||||||
/* The first index might be a partial page */
|
/* The first index might be a partial page */
|
||||||
if (start_index == iopt_area_index(area))
|
if (start_index == iopt_area_index(area))
|
||||||
page_offset = area->page_offset;
|
page_offset = area->page_offset;
|
||||||
|
|
@ -512,11 +534,11 @@ static int batch_to_domain(struct pfn_batch *batch, struct iommu_domain *domain,
|
||||||
rc = batch_iommu_map_small(
|
rc = batch_iommu_map_small(
|
||||||
domain, iova,
|
domain, iova,
|
||||||
PFN_PHYS(batch->pfns[cur]) + page_offset,
|
PFN_PHYS(batch->pfns[cur]) + page_offset,
|
||||||
next_iova - iova, area->iommu_prot);
|
next_iova - iova, iommu_prot);
|
||||||
else
|
else
|
||||||
rc = iommu_map(domain, iova,
|
rc = iommu_map(domain, iova,
|
||||||
PFN_PHYS(batch->pfns[cur]) + page_offset,
|
PFN_PHYS(batch->pfns[cur]) + page_offset,
|
||||||
next_iova - iova, area->iommu_prot,
|
next_iova - iova, iommu_prot,
|
||||||
GFP_KERNEL_ACCOUNT);
|
GFP_KERNEL_ACCOUNT);
|
||||||
if (rc)
|
if (rc)
|
||||||
goto err_unmap;
|
goto err_unmap;
|
||||||
|
|
@ -652,7 +674,7 @@ static int batch_from_folios(struct pfn_batch *batch, struct folio ***folios_p,
|
||||||
nr = min(nr, npages);
|
nr = min(nr, npages);
|
||||||
npages -= nr;
|
npages -= nr;
|
||||||
|
|
||||||
if (!batch_add_pfn_num(batch, pfn, nr))
|
if (!batch_add_pfn_num(batch, pfn, nr, BATCH_CPU_MEMORY))
|
||||||
break;
|
break;
|
||||||
if (nr > 1) {
|
if (nr > 1) {
|
||||||
rc = folio_add_pins(folio, nr - 1);
|
rc = folio_add_pins(folio, nr - 1);
|
||||||
|
|
@ -1054,6 +1076,41 @@ static int pfn_reader_user_update_pinned(struct pfn_reader_user *user,
|
||||||
return iopt_pages_update_pinned(pages, npages, inc, user);
|
return iopt_pages_update_pinned(pages, npages, inc, user);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct pfn_reader_dmabuf {
|
||||||
|
struct dma_buf_phys_vec phys;
|
||||||
|
unsigned long start_offset;
|
||||||
|
};
|
||||||
|
|
||||||
|
static int pfn_reader_dmabuf_init(struct pfn_reader_dmabuf *dmabuf,
|
||||||
|
struct iopt_pages *pages)
|
||||||
|
{
|
||||||
|
/* Callers must not get here if the dmabuf was already revoked */
|
||||||
|
if (WARN_ON(iopt_dmabuf_revoked(pages)))
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
dmabuf->phys = pages->dmabuf.phys;
|
||||||
|
dmabuf->start_offset = pages->dmabuf.start;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int pfn_reader_fill_dmabuf(struct pfn_reader_dmabuf *dmabuf,
|
||||||
|
struct pfn_batch *batch,
|
||||||
|
unsigned long start_index,
|
||||||
|
unsigned long last_index)
|
||||||
|
{
|
||||||
|
unsigned long start = dmabuf->start_offset + start_index * PAGE_SIZE;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* start/last_index and start are all PAGE_SIZE aligned, the batch is
|
||||||
|
* always filled using page size aligned PFNs just like the other types.
|
||||||
|
* If the dmabuf has been sliced on a sub page offset then the common
|
||||||
|
* batch to domain code will adjust it before mapping to the domain.
|
||||||
|
*/
|
||||||
|
batch_add_pfn_num(batch, PHYS_PFN(dmabuf->phys.paddr + start),
|
||||||
|
last_index - start_index + 1, BATCH_MMIO);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* PFNs are stored in three places, in order of preference:
|
* PFNs are stored in three places, in order of preference:
|
||||||
* - The iopt_pages xarray. This is only populated if there is a
|
* - The iopt_pages xarray. This is only populated if there is a
|
||||||
|
|
@ -1072,7 +1129,10 @@ struct pfn_reader {
|
||||||
unsigned long batch_end_index;
|
unsigned long batch_end_index;
|
||||||
unsigned long last_index;
|
unsigned long last_index;
|
||||||
|
|
||||||
|
union {
|
||||||
struct pfn_reader_user user;
|
struct pfn_reader_user user;
|
||||||
|
struct pfn_reader_dmabuf dmabuf;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
static int pfn_reader_update_pinned(struct pfn_reader *pfns)
|
static int pfn_reader_update_pinned(struct pfn_reader *pfns)
|
||||||
|
|
@ -1108,7 +1168,7 @@ static int pfn_reader_fill_span(struct pfn_reader *pfns)
|
||||||
{
|
{
|
||||||
struct interval_tree_double_span_iter *span = &pfns->span;
|
struct interval_tree_double_span_iter *span = &pfns->span;
|
||||||
unsigned long start_index = pfns->batch_end_index;
|
unsigned long start_index = pfns->batch_end_index;
|
||||||
struct pfn_reader_user *user = &pfns->user;
|
struct pfn_reader_user *user;
|
||||||
unsigned long npages;
|
unsigned long npages;
|
||||||
struct iopt_area *area;
|
struct iopt_area *area;
|
||||||
int rc;
|
int rc;
|
||||||
|
|
@ -1140,8 +1200,13 @@ static int pfn_reader_fill_span(struct pfn_reader *pfns)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (start_index >= pfns->user.upages_end) {
|
if (iopt_is_dmabuf(pfns->pages))
|
||||||
rc = pfn_reader_user_pin(&pfns->user, pfns->pages, start_index,
|
return pfn_reader_fill_dmabuf(&pfns->dmabuf, &pfns->batch,
|
||||||
|
start_index, span->last_hole);
|
||||||
|
|
||||||
|
user = &pfns->user;
|
||||||
|
if (start_index >= user->upages_end) {
|
||||||
|
rc = pfn_reader_user_pin(user, pfns->pages, start_index,
|
||||||
span->last_hole);
|
span->last_hole);
|
||||||
if (rc)
|
if (rc)
|
||||||
return rc;
|
return rc;
|
||||||
|
|
@ -1209,6 +1274,9 @@ static int pfn_reader_init(struct pfn_reader *pfns, struct iopt_pages *pages,
|
||||||
pfns->batch_start_index = start_index;
|
pfns->batch_start_index = start_index;
|
||||||
pfns->batch_end_index = start_index;
|
pfns->batch_end_index = start_index;
|
||||||
pfns->last_index = last_index;
|
pfns->last_index = last_index;
|
||||||
|
if (iopt_is_dmabuf(pages))
|
||||||
|
pfn_reader_dmabuf_init(&pfns->dmabuf, pages);
|
||||||
|
else
|
||||||
pfn_reader_user_init(&pfns->user, pages);
|
pfn_reader_user_init(&pfns->user, pages);
|
||||||
rc = batch_init(&pfns->batch, last_index - start_index + 1);
|
rc = batch_init(&pfns->batch, last_index - start_index + 1);
|
||||||
if (rc)
|
if (rc)
|
||||||
|
|
@ -1230,8 +1298,12 @@ static int pfn_reader_init(struct pfn_reader *pfns, struct iopt_pages *pages,
|
||||||
static void pfn_reader_release_pins(struct pfn_reader *pfns)
|
static void pfn_reader_release_pins(struct pfn_reader *pfns)
|
||||||
{
|
{
|
||||||
struct iopt_pages *pages = pfns->pages;
|
struct iopt_pages *pages = pfns->pages;
|
||||||
struct pfn_reader_user *user = &pfns->user;
|
struct pfn_reader_user *user;
|
||||||
|
|
||||||
|
if (iopt_is_dmabuf(pages))
|
||||||
|
return;
|
||||||
|
|
||||||
|
user = &pfns->user;
|
||||||
if (user->upages_end > pfns->batch_end_index) {
|
if (user->upages_end > pfns->batch_end_index) {
|
||||||
/* Any pages not transferred to the batch are just unpinned */
|
/* Any pages not transferred to the batch are just unpinned */
|
||||||
|
|
||||||
|
|
@ -1261,6 +1333,7 @@ static void pfn_reader_destroy(struct pfn_reader *pfns)
|
||||||
struct iopt_pages *pages = pfns->pages;
|
struct iopt_pages *pages = pfns->pages;
|
||||||
|
|
||||||
pfn_reader_release_pins(pfns);
|
pfn_reader_release_pins(pfns);
|
||||||
|
if (!iopt_is_dmabuf(pfns->pages))
|
||||||
pfn_reader_user_destroy(&pfns->user, pfns->pages);
|
pfn_reader_user_destroy(&pfns->user, pfns->pages);
|
||||||
batch_destroy(&pfns->batch, NULL);
|
batch_destroy(&pfns->batch, NULL);
|
||||||
WARN_ON(pages->last_npinned != pages->npinned);
|
WARN_ON(pages->last_npinned != pages->npinned);
|
||||||
|
|
@ -1340,26 +1413,234 @@ struct iopt_pages *iopt_alloc_user_pages(void __user *uptr,
|
||||||
return pages;
|
return pages;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct iopt_pages *iopt_alloc_file_pages(struct file *file, unsigned long start,
|
struct iopt_pages *iopt_alloc_file_pages(struct file *file,
|
||||||
|
unsigned long start_byte,
|
||||||
|
unsigned long start,
|
||||||
unsigned long length, bool writable)
|
unsigned long length, bool writable)
|
||||||
|
|
||||||
{
|
{
|
||||||
struct iopt_pages *pages;
|
struct iopt_pages *pages;
|
||||||
unsigned long start_down = ALIGN_DOWN(start, PAGE_SIZE);
|
|
||||||
unsigned long end;
|
|
||||||
|
|
||||||
if (length && check_add_overflow(start, length - 1, &end))
|
pages = iopt_alloc_pages(start_byte, length, writable);
|
||||||
return ERR_PTR(-EOVERFLOW);
|
|
||||||
|
|
||||||
pages = iopt_alloc_pages(start - start_down, length, writable);
|
|
||||||
if (IS_ERR(pages))
|
if (IS_ERR(pages))
|
||||||
return pages;
|
return pages;
|
||||||
pages->file = get_file(file);
|
pages->file = get_file(file);
|
||||||
pages->start = start_down;
|
pages->start = start - start_byte;
|
||||||
pages->type = IOPT_ADDRESS_FILE;
|
pages->type = IOPT_ADDRESS_FILE;
|
||||||
return pages;
|
return pages;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void iopt_revoke_notify(struct dma_buf_attachment *attach)
|
||||||
|
{
|
||||||
|
struct iopt_pages *pages = attach->importer_priv;
|
||||||
|
struct iopt_pages_dmabuf_track *track;
|
||||||
|
|
||||||
|
guard(mutex)(&pages->mutex);
|
||||||
|
if (iopt_dmabuf_revoked(pages))
|
||||||
|
return;
|
||||||
|
|
||||||
|
list_for_each_entry(track, &pages->dmabuf.tracker, elm) {
|
||||||
|
struct iopt_area *area = track->area;
|
||||||
|
|
||||||
|
iopt_area_unmap_domain_range(area, track->domain,
|
||||||
|
iopt_area_index(area),
|
||||||
|
iopt_area_last_index(area));
|
||||||
|
}
|
||||||
|
pages->dmabuf.phys.len = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct dma_buf_attach_ops iopt_dmabuf_attach_revoke_ops = {
|
||||||
|
.allow_peer2peer = true,
|
||||||
|
.move_notify = iopt_revoke_notify,
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* iommufd and vfio have a circular dependency. Future work for a phys
|
||||||
|
* based private interconnect will remove this.
|
||||||
|
*/
|
||||||
|
static int
|
||||||
|
sym_vfio_pci_dma_buf_iommufd_map(struct dma_buf_attachment *attachment,
|
||||||
|
struct dma_buf_phys_vec *phys)
|
||||||
|
{
|
||||||
|
typeof(&vfio_pci_dma_buf_iommufd_map) fn;
|
||||||
|
int rc;
|
||||||
|
|
||||||
|
rc = iommufd_test_dma_buf_iommufd_map(attachment, phys);
|
||||||
|
if (rc != -EOPNOTSUPP)
|
||||||
|
return rc;
|
||||||
|
|
||||||
|
if (!IS_ENABLED(CONFIG_VFIO_PCI_DMABUF))
|
||||||
|
return -EOPNOTSUPP;
|
||||||
|
|
||||||
|
fn = symbol_get(vfio_pci_dma_buf_iommufd_map);
|
||||||
|
if (!fn)
|
||||||
|
return -EOPNOTSUPP;
|
||||||
|
rc = fn(attachment, phys);
|
||||||
|
symbol_put(vfio_pci_dma_buf_iommufd_map);
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int iopt_map_dmabuf(struct iommufd_ctx *ictx, struct iopt_pages *pages,
|
||||||
|
struct dma_buf *dmabuf)
|
||||||
|
{
|
||||||
|
struct dma_buf_attachment *attach;
|
||||||
|
int rc;
|
||||||
|
|
||||||
|
attach = dma_buf_dynamic_attach(dmabuf, iommufd_global_device(),
|
||||||
|
&iopt_dmabuf_attach_revoke_ops, pages);
|
||||||
|
if (IS_ERR(attach))
|
||||||
|
return PTR_ERR(attach);
|
||||||
|
|
||||||
|
dma_resv_lock(dmabuf->resv, NULL);
|
||||||
|
/*
|
||||||
|
* Lock ordering requires the mutex to be taken inside the reservation,
|
||||||
|
* make sure lockdep sees this.
|
||||||
|
*/
|
||||||
|
if (IS_ENABLED(CONFIG_LOCKDEP)) {
|
||||||
|
mutex_lock(&pages->mutex);
|
||||||
|
mutex_unlock(&pages->mutex);
|
||||||
|
}
|
||||||
|
|
||||||
|
rc = sym_vfio_pci_dma_buf_iommufd_map(attach, &pages->dmabuf.phys);
|
||||||
|
if (rc)
|
||||||
|
goto err_detach;
|
||||||
|
|
||||||
|
dma_resv_unlock(dmabuf->resv);
|
||||||
|
|
||||||
|
/* On success iopt_release_pages() will detach and put the dmabuf. */
|
||||||
|
pages->dmabuf.attach = attach;
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
err_detach:
|
||||||
|
dma_resv_unlock(dmabuf->resv);
|
||||||
|
dma_buf_detach(dmabuf, attach);
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct iopt_pages *iopt_alloc_dmabuf_pages(struct iommufd_ctx *ictx,
|
||||||
|
struct dma_buf *dmabuf,
|
||||||
|
unsigned long start_byte,
|
||||||
|
unsigned long start,
|
||||||
|
unsigned long length, bool writable)
|
||||||
|
{
|
||||||
|
static struct lock_class_key pages_dmabuf_mutex_key;
|
||||||
|
struct iopt_pages *pages;
|
||||||
|
int rc;
|
||||||
|
|
||||||
|
if (!IS_ENABLED(CONFIG_DMA_SHARED_BUFFER))
|
||||||
|
return ERR_PTR(-EOPNOTSUPP);
|
||||||
|
|
||||||
|
if (dmabuf->size <= (start + length - 1) ||
|
||||||
|
length / PAGE_SIZE >= MAX_NPFNS)
|
||||||
|
return ERR_PTR(-EINVAL);
|
||||||
|
|
||||||
|
pages = iopt_alloc_pages(start_byte, length, writable);
|
||||||
|
if (IS_ERR(pages))
|
||||||
|
return pages;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The mmap_lock can be held when obtaining the dmabuf reservation lock
|
||||||
|
* which creates a locking cycle with the pages mutex which is held
|
||||||
|
* while obtaining the mmap_lock. This locking path is not present for
|
||||||
|
* IOPT_ADDRESS_DMABUF so split the lock class.
|
||||||
|
*/
|
||||||
|
lockdep_set_class(&pages->mutex, &pages_dmabuf_mutex_key);
|
||||||
|
|
||||||
|
/* dmabuf does not use pinned page accounting. */
|
||||||
|
pages->account_mode = IOPT_PAGES_ACCOUNT_NONE;
|
||||||
|
pages->type = IOPT_ADDRESS_DMABUF;
|
||||||
|
pages->dmabuf.start = start - start_byte;
|
||||||
|
INIT_LIST_HEAD(&pages->dmabuf.tracker);
|
||||||
|
|
||||||
|
rc = iopt_map_dmabuf(ictx, pages, dmabuf);
|
||||||
|
if (rc) {
|
||||||
|
iopt_put_pages(pages);
|
||||||
|
return ERR_PTR(rc);
|
||||||
|
}
|
||||||
|
|
||||||
|
return pages;
|
||||||
|
}
|
||||||
|
|
||||||
|
int iopt_dmabuf_track_domain(struct iopt_pages *pages, struct iopt_area *area,
|
||||||
|
struct iommu_domain *domain)
|
||||||
|
{
|
||||||
|
struct iopt_pages_dmabuf_track *track;
|
||||||
|
|
||||||
|
lockdep_assert_held(&pages->mutex);
|
||||||
|
if (WARN_ON(!iopt_is_dmabuf(pages)))
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
list_for_each_entry(track, &pages->dmabuf.tracker, elm)
|
||||||
|
if (WARN_ON(track->domain == domain && track->area == area))
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
track = kzalloc(sizeof(*track), GFP_KERNEL);
|
||||||
|
if (!track)
|
||||||
|
return -ENOMEM;
|
||||||
|
track->domain = domain;
|
||||||
|
track->area = area;
|
||||||
|
list_add_tail(&track->elm, &pages->dmabuf.tracker);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void iopt_dmabuf_untrack_domain(struct iopt_pages *pages,
|
||||||
|
struct iopt_area *area,
|
||||||
|
struct iommu_domain *domain)
|
||||||
|
{
|
||||||
|
struct iopt_pages_dmabuf_track *track;
|
||||||
|
|
||||||
|
lockdep_assert_held(&pages->mutex);
|
||||||
|
WARN_ON(!iopt_is_dmabuf(pages));
|
||||||
|
|
||||||
|
list_for_each_entry(track, &pages->dmabuf.tracker, elm) {
|
||||||
|
if (track->domain == domain && track->area == area) {
|
||||||
|
list_del(&track->elm);
|
||||||
|
kfree(track);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
WARN_ON(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
int iopt_dmabuf_track_all_domains(struct iopt_area *area,
|
||||||
|
struct iopt_pages *pages)
|
||||||
|
{
|
||||||
|
struct iopt_pages_dmabuf_track *track;
|
||||||
|
struct iommu_domain *domain;
|
||||||
|
unsigned long index;
|
||||||
|
int rc;
|
||||||
|
|
||||||
|
list_for_each_entry(track, &pages->dmabuf.tracker, elm)
|
||||||
|
if (WARN_ON(track->area == area))
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
xa_for_each(&area->iopt->domains, index, domain) {
|
||||||
|
rc = iopt_dmabuf_track_domain(pages, area, domain);
|
||||||
|
if (rc)
|
||||||
|
goto err_untrack;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
err_untrack:
|
||||||
|
iopt_dmabuf_untrack_all_domains(area, pages);
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
void iopt_dmabuf_untrack_all_domains(struct iopt_area *area,
|
||||||
|
struct iopt_pages *pages)
|
||||||
|
{
|
||||||
|
struct iopt_pages_dmabuf_track *track;
|
||||||
|
struct iopt_pages_dmabuf_track *tmp;
|
||||||
|
|
||||||
|
list_for_each_entry_safe(track, tmp, &pages->dmabuf.tracker,
|
||||||
|
elm) {
|
||||||
|
if (track->area == area) {
|
||||||
|
list_del(&track->elm);
|
||||||
|
kfree(track);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void iopt_release_pages(struct kref *kref)
|
void iopt_release_pages(struct kref *kref)
|
||||||
{
|
{
|
||||||
struct iopt_pages *pages = container_of(kref, struct iopt_pages, kref);
|
struct iopt_pages *pages = container_of(kref, struct iopt_pages, kref);
|
||||||
|
|
@ -1372,8 +1653,15 @@ void iopt_release_pages(struct kref *kref)
|
||||||
mutex_destroy(&pages->mutex);
|
mutex_destroy(&pages->mutex);
|
||||||
put_task_struct(pages->source_task);
|
put_task_struct(pages->source_task);
|
||||||
free_uid(pages->source_user);
|
free_uid(pages->source_user);
|
||||||
if (pages->type == IOPT_ADDRESS_FILE)
|
if (iopt_is_dmabuf(pages) && pages->dmabuf.attach) {
|
||||||
|
struct dma_buf *dmabuf = pages->dmabuf.attach->dmabuf;
|
||||||
|
|
||||||
|
dma_buf_detach(dmabuf, pages->dmabuf.attach);
|
||||||
|
dma_buf_put(dmabuf);
|
||||||
|
WARN_ON(!list_empty(&pages->dmabuf.tracker));
|
||||||
|
} else if (pages->type == IOPT_ADDRESS_FILE) {
|
||||||
fput(pages->file);
|
fput(pages->file);
|
||||||
|
}
|
||||||
kfree(pages);
|
kfree(pages);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1451,6 +1739,14 @@ static void __iopt_area_unfill_domain(struct iopt_area *area,
|
||||||
|
|
||||||
lockdep_assert_held(&pages->mutex);
|
lockdep_assert_held(&pages->mutex);
|
||||||
|
|
||||||
|
if (iopt_is_dmabuf(pages)) {
|
||||||
|
if (WARN_ON(iopt_dmabuf_revoked(pages)))
|
||||||
|
return;
|
||||||
|
iopt_area_unmap_domain_range(area, domain, start_index,
|
||||||
|
last_index);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* For security we must not unpin something that is still DMA mapped,
|
* For security we must not unpin something that is still DMA mapped,
|
||||||
* so this must unmap any IOVA before we go ahead and unpin the pages.
|
* so this must unmap any IOVA before we go ahead and unpin the pages.
|
||||||
|
|
@ -1526,6 +1822,9 @@ void iopt_area_unmap_domain(struct iopt_area *area, struct iommu_domain *domain)
|
||||||
void iopt_area_unfill_domain(struct iopt_area *area, struct iopt_pages *pages,
|
void iopt_area_unfill_domain(struct iopt_area *area, struct iopt_pages *pages,
|
||||||
struct iommu_domain *domain)
|
struct iommu_domain *domain)
|
||||||
{
|
{
|
||||||
|
if (iopt_dmabuf_revoked(pages))
|
||||||
|
return;
|
||||||
|
|
||||||
__iopt_area_unfill_domain(area, pages, domain,
|
__iopt_area_unfill_domain(area, pages, domain,
|
||||||
iopt_area_last_index(area));
|
iopt_area_last_index(area));
|
||||||
}
|
}
|
||||||
|
|
@ -1546,6 +1845,9 @@ int iopt_area_fill_domain(struct iopt_area *area, struct iommu_domain *domain)
|
||||||
|
|
||||||
lockdep_assert_held(&area->pages->mutex);
|
lockdep_assert_held(&area->pages->mutex);
|
||||||
|
|
||||||
|
if (iopt_dmabuf_revoked(area->pages))
|
||||||
|
return 0;
|
||||||
|
|
||||||
rc = pfn_reader_first(&pfns, area->pages, iopt_area_index(area),
|
rc = pfn_reader_first(&pfns, area->pages, iopt_area_index(area),
|
||||||
iopt_area_last_index(area));
|
iopt_area_last_index(area));
|
||||||
if (rc)
|
if (rc)
|
||||||
|
|
@ -1605,10 +1907,17 @@ int iopt_area_fill_domains(struct iopt_area *area, struct iopt_pages *pages)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
mutex_lock(&pages->mutex);
|
mutex_lock(&pages->mutex);
|
||||||
|
if (iopt_is_dmabuf(pages)) {
|
||||||
|
rc = iopt_dmabuf_track_all_domains(area, pages);
|
||||||
|
if (rc)
|
||||||
|
goto out_unlock;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!iopt_dmabuf_revoked(pages)) {
|
||||||
rc = pfn_reader_first(&pfns, pages, iopt_area_index(area),
|
rc = pfn_reader_first(&pfns, pages, iopt_area_index(area),
|
||||||
iopt_area_last_index(area));
|
iopt_area_last_index(area));
|
||||||
if (rc)
|
if (rc)
|
||||||
goto out_unlock;
|
goto out_untrack;
|
||||||
|
|
||||||
while (!pfn_reader_done(&pfns)) {
|
while (!pfn_reader_done(&pfns)) {
|
||||||
done_first_end_index = pfns.batch_end_index;
|
done_first_end_index = pfns.batch_end_index;
|
||||||
|
|
@ -1629,9 +1938,13 @@ int iopt_area_fill_domains(struct iopt_area *area, struct iopt_pages *pages)
|
||||||
if (rc)
|
if (rc)
|
||||||
goto out_unmap;
|
goto out_unmap;
|
||||||
|
|
||||||
|
pfn_reader_destroy(&pfns);
|
||||||
|
}
|
||||||
|
|
||||||
area->storage_domain = xa_load(&area->iopt->domains, 0);
|
area->storage_domain = xa_load(&area->iopt->domains, 0);
|
||||||
interval_tree_insert(&area->pages_node, &pages->domains_itree);
|
interval_tree_insert(&area->pages_node, &pages->domains_itree);
|
||||||
goto out_destroy;
|
mutex_unlock(&pages->mutex);
|
||||||
|
return 0;
|
||||||
|
|
||||||
out_unmap:
|
out_unmap:
|
||||||
pfn_reader_release_pins(&pfns);
|
pfn_reader_release_pins(&pfns);
|
||||||
|
|
@ -1658,8 +1971,10 @@ int iopt_area_fill_domains(struct iopt_area *area, struct iopt_pages *pages)
|
||||||
end_index);
|
end_index);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
out_destroy:
|
|
||||||
pfn_reader_destroy(&pfns);
|
pfn_reader_destroy(&pfns);
|
||||||
|
out_untrack:
|
||||||
|
if (iopt_is_dmabuf(pages))
|
||||||
|
iopt_dmabuf_untrack_all_domains(area, pages);
|
||||||
out_unlock:
|
out_unlock:
|
||||||
mutex_unlock(&pages->mutex);
|
mutex_unlock(&pages->mutex);
|
||||||
return rc;
|
return rc;
|
||||||
|
|
@ -1685,16 +2000,22 @@ void iopt_area_unfill_domains(struct iopt_area *area, struct iopt_pages *pages)
|
||||||
if (!area->storage_domain)
|
if (!area->storage_domain)
|
||||||
goto out_unlock;
|
goto out_unlock;
|
||||||
|
|
||||||
xa_for_each(&iopt->domains, index, domain)
|
xa_for_each(&iopt->domains, index, domain) {
|
||||||
if (domain != area->storage_domain)
|
if (domain == area->storage_domain)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (!iopt_dmabuf_revoked(pages))
|
||||||
iopt_area_unmap_domain_range(
|
iopt_area_unmap_domain_range(
|
||||||
area, domain, iopt_area_index(area),
|
area, domain, iopt_area_index(area),
|
||||||
iopt_area_last_index(area));
|
iopt_area_last_index(area));
|
||||||
|
}
|
||||||
|
|
||||||
if (IS_ENABLED(CONFIG_IOMMUFD_TEST))
|
if (IS_ENABLED(CONFIG_IOMMUFD_TEST))
|
||||||
WARN_ON(RB_EMPTY_NODE(&area->pages_node.rb));
|
WARN_ON(RB_EMPTY_NODE(&area->pages_node.rb));
|
||||||
interval_tree_remove(&area->pages_node, &pages->domains_itree);
|
interval_tree_remove(&area->pages_node, &pages->domains_itree);
|
||||||
iopt_area_unfill_domain(area, pages, area->storage_domain);
|
iopt_area_unfill_domain(area, pages, area->storage_domain);
|
||||||
|
if (iopt_is_dmabuf(pages))
|
||||||
|
iopt_dmabuf_untrack_all_domains(area, pages);
|
||||||
area->storage_domain = NULL;
|
area->storage_domain = NULL;
|
||||||
out_unlock:
|
out_unlock:
|
||||||
mutex_unlock(&pages->mutex);
|
mutex_unlock(&pages->mutex);
|
||||||
|
|
@ -2031,15 +2352,14 @@ int iopt_pages_rw_access(struct iopt_pages *pages, unsigned long start_byte,
|
||||||
if ((flags & IOMMUFD_ACCESS_RW_WRITE) && !pages->writable)
|
if ((flags & IOMMUFD_ACCESS_RW_WRITE) && !pages->writable)
|
||||||
return -EPERM;
|
return -EPERM;
|
||||||
|
|
||||||
if (pages->type == IOPT_ADDRESS_FILE)
|
if (iopt_is_dmabuf(pages))
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
if (pages->type != IOPT_ADDRESS_USER)
|
||||||
return iopt_pages_rw_slow(pages, start_index, last_index,
|
return iopt_pages_rw_slow(pages, start_index, last_index,
|
||||||
start_byte % PAGE_SIZE, data, length,
|
start_byte % PAGE_SIZE, data, length,
|
||||||
flags);
|
flags);
|
||||||
|
|
||||||
if (IS_ENABLED(CONFIG_IOMMUFD_TEST) &&
|
|
||||||
WARN_ON(pages->type != IOPT_ADDRESS_USER))
|
|
||||||
return -EINVAL;
|
|
||||||
|
|
||||||
if (!(flags & IOMMUFD_ACCESS_RW_KTHREAD) && change_mm) {
|
if (!(flags & IOMMUFD_ACCESS_RW_KTHREAD) && change_mm) {
|
||||||
if (start_index == last_index)
|
if (start_index == last_index)
|
||||||
return iopt_pages_rw_page(pages, start_index,
|
return iopt_pages_rw_page(pages, start_index,
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,8 @@
|
||||||
*/
|
*/
|
||||||
#include <linux/anon_inodes.h>
|
#include <linux/anon_inodes.h>
|
||||||
#include <linux/debugfs.h>
|
#include <linux/debugfs.h>
|
||||||
|
#include <linux/dma-buf.h>
|
||||||
|
#include <linux/dma-resv.h>
|
||||||
#include <linux/fault-inject.h>
|
#include <linux/fault-inject.h>
|
||||||
#include <linux/file.h>
|
#include <linux/file.h>
|
||||||
#include <linux/iommu.h>
|
#include <linux/iommu.h>
|
||||||
|
|
@ -1945,6 +1947,140 @@ void iommufd_selftest_destroy(struct iommufd_object *obj)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct iommufd_test_dma_buf {
|
||||||
|
void *memory;
|
||||||
|
size_t length;
|
||||||
|
bool revoked;
|
||||||
|
};
|
||||||
|
|
||||||
|
static int iommufd_test_dma_buf_attach(struct dma_buf *dmabuf,
|
||||||
|
struct dma_buf_attachment *attachment)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void iommufd_test_dma_buf_detach(struct dma_buf *dmabuf,
|
||||||
|
struct dma_buf_attachment *attachment)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct sg_table *
|
||||||
|
iommufd_test_dma_buf_map(struct dma_buf_attachment *attachment,
|
||||||
|
enum dma_data_direction dir)
|
||||||
|
{
|
||||||
|
return ERR_PTR(-EOPNOTSUPP);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void iommufd_test_dma_buf_unmap(struct dma_buf_attachment *attachment,
|
||||||
|
struct sg_table *sgt,
|
||||||
|
enum dma_data_direction dir)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
static void iommufd_test_dma_buf_release(struct dma_buf *dmabuf)
|
||||||
|
{
|
||||||
|
struct iommufd_test_dma_buf *priv = dmabuf->priv;
|
||||||
|
|
||||||
|
kfree(priv->memory);
|
||||||
|
kfree(priv);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct dma_buf_ops iommufd_test_dmabuf_ops = {
|
||||||
|
.attach = iommufd_test_dma_buf_attach,
|
||||||
|
.detach = iommufd_test_dma_buf_detach,
|
||||||
|
.map_dma_buf = iommufd_test_dma_buf_map,
|
||||||
|
.release = iommufd_test_dma_buf_release,
|
||||||
|
.unmap_dma_buf = iommufd_test_dma_buf_unmap,
|
||||||
|
};
|
||||||
|
|
||||||
|
int iommufd_test_dma_buf_iommufd_map(struct dma_buf_attachment *attachment,
|
||||||
|
struct dma_buf_phys_vec *phys)
|
||||||
|
{
|
||||||
|
struct iommufd_test_dma_buf *priv = attachment->dmabuf->priv;
|
||||||
|
|
||||||
|
dma_resv_assert_held(attachment->dmabuf->resv);
|
||||||
|
|
||||||
|
if (attachment->dmabuf->ops != &iommufd_test_dmabuf_ops)
|
||||||
|
return -EOPNOTSUPP;
|
||||||
|
|
||||||
|
if (priv->revoked)
|
||||||
|
return -ENODEV;
|
||||||
|
|
||||||
|
phys->paddr = virt_to_phys(priv->memory);
|
||||||
|
phys->len = priv->length;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int iommufd_test_dmabuf_get(struct iommufd_ucmd *ucmd,
|
||||||
|
unsigned int open_flags,
|
||||||
|
size_t len)
|
||||||
|
{
|
||||||
|
DEFINE_DMA_BUF_EXPORT_INFO(exp_info);
|
||||||
|
struct iommufd_test_dma_buf *priv;
|
||||||
|
struct dma_buf *dmabuf;
|
||||||
|
int rc;
|
||||||
|
|
||||||
|
len = ALIGN(len, PAGE_SIZE);
|
||||||
|
if (len == 0 || len > PAGE_SIZE * 512)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
priv = kzalloc(sizeof(*priv), GFP_KERNEL);
|
||||||
|
if (!priv)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
priv->length = len;
|
||||||
|
priv->memory = kzalloc(len, GFP_KERNEL);
|
||||||
|
if (!priv->memory) {
|
||||||
|
rc = -ENOMEM;
|
||||||
|
goto err_free;
|
||||||
|
}
|
||||||
|
|
||||||
|
exp_info.ops = &iommufd_test_dmabuf_ops;
|
||||||
|
exp_info.size = len;
|
||||||
|
exp_info.flags = open_flags;
|
||||||
|
exp_info.priv = priv;
|
||||||
|
|
||||||
|
dmabuf = dma_buf_export(&exp_info);
|
||||||
|
if (IS_ERR(dmabuf)) {
|
||||||
|
rc = PTR_ERR(dmabuf);
|
||||||
|
goto err_free;
|
||||||
|
}
|
||||||
|
|
||||||
|
return dma_buf_fd(dmabuf, open_flags);
|
||||||
|
|
||||||
|
err_free:
|
||||||
|
kfree(priv->memory);
|
||||||
|
kfree(priv);
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int iommufd_test_dmabuf_revoke(struct iommufd_ucmd *ucmd, int fd,
|
||||||
|
bool revoked)
|
||||||
|
{
|
||||||
|
struct iommufd_test_dma_buf *priv;
|
||||||
|
struct dma_buf *dmabuf;
|
||||||
|
int rc = 0;
|
||||||
|
|
||||||
|
dmabuf = dma_buf_get(fd);
|
||||||
|
if (IS_ERR(dmabuf))
|
||||||
|
return PTR_ERR(dmabuf);
|
||||||
|
|
||||||
|
if (dmabuf->ops != &iommufd_test_dmabuf_ops) {
|
||||||
|
rc = -EOPNOTSUPP;
|
||||||
|
goto err_put;
|
||||||
|
}
|
||||||
|
|
||||||
|
priv = dmabuf->priv;
|
||||||
|
dma_resv_lock(dmabuf->resv, NULL);
|
||||||
|
priv->revoked = revoked;
|
||||||
|
dma_buf_move_notify(dmabuf);
|
||||||
|
dma_resv_unlock(dmabuf->resv);
|
||||||
|
|
||||||
|
err_put:
|
||||||
|
dma_buf_put(dmabuf);
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
int iommufd_test(struct iommufd_ucmd *ucmd)
|
int iommufd_test(struct iommufd_ucmd *ucmd)
|
||||||
{
|
{
|
||||||
struct iommu_test_cmd *cmd = ucmd->cmd;
|
struct iommu_test_cmd *cmd = ucmd->cmd;
|
||||||
|
|
@ -2023,6 +2159,13 @@ int iommufd_test(struct iommufd_ucmd *ucmd)
|
||||||
return iommufd_test_pasid_detach(ucmd, cmd);
|
return iommufd_test_pasid_detach(ucmd, cmd);
|
||||||
case IOMMU_TEST_OP_PASID_CHECK_HWPT:
|
case IOMMU_TEST_OP_PASID_CHECK_HWPT:
|
||||||
return iommufd_test_pasid_check_hwpt(ucmd, cmd);
|
return iommufd_test_pasid_check_hwpt(ucmd, cmd);
|
||||||
|
case IOMMU_TEST_OP_DMABUF_GET:
|
||||||
|
return iommufd_test_dmabuf_get(ucmd, cmd->dmabuf_get.open_flags,
|
||||||
|
cmd->dmabuf_get.length);
|
||||||
|
case IOMMU_TEST_OP_DMABUF_REVOKE:
|
||||||
|
return iommufd_test_dmabuf_revoke(ucmd,
|
||||||
|
cmd->dmabuf_revoke.dmabuf_fd,
|
||||||
|
cmd->dmabuf_revoke.revoked);
|
||||||
default:
|
default:
|
||||||
return -EOPNOTSUPP;
|
return -EOPNOTSUPP;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -82,6 +82,40 @@ static const struct dma_buf_ops vfio_pci_dmabuf_ops = {
|
||||||
.release = vfio_pci_dma_buf_release,
|
.release = vfio_pci_dma_buf_release,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This is a temporary "private interconnect" between VFIO DMABUF and iommufd.
|
||||||
|
* It allows the two co-operating drivers to exchange the physical address of
|
||||||
|
* the BAR. This is to be replaced with a formal DMABUF system for negotiated
|
||||||
|
* interconnect types.
|
||||||
|
*
|
||||||
|
* If this function succeeds the following are true:
|
||||||
|
* - There is one physical range and it is pointing to MMIO
|
||||||
|
* - When move_notify is called it means revoke, not move, vfio_dma_buf_map
|
||||||
|
* will fail if it is currently revoked
|
||||||
|
*/
|
||||||
|
int vfio_pci_dma_buf_iommufd_map(struct dma_buf_attachment *attachment,
|
||||||
|
struct dma_buf_phys_vec *phys)
|
||||||
|
{
|
||||||
|
struct vfio_pci_dma_buf *priv;
|
||||||
|
|
||||||
|
dma_resv_assert_held(attachment->dmabuf->resv);
|
||||||
|
|
||||||
|
if (attachment->dmabuf->ops != &vfio_pci_dmabuf_ops)
|
||||||
|
return -EOPNOTSUPP;
|
||||||
|
|
||||||
|
priv = attachment->dmabuf->priv;
|
||||||
|
if (priv->revoked)
|
||||||
|
return -ENODEV;
|
||||||
|
|
||||||
|
/* More than one range to iommufd will require proper DMABUF support */
|
||||||
|
if (priv->nr_ranges != 1)
|
||||||
|
return -EOPNOTSUPP;
|
||||||
|
|
||||||
|
*phys = priv->phys_vec[0];
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
EXPORT_SYMBOL_FOR_MODULES(vfio_pci_dma_buf_iommufd_map, "iommufd");
|
||||||
|
|
||||||
int vfio_pci_core_fill_phys_vec(struct dma_buf_phys_vec *phys_vec,
|
int vfio_pci_core_fill_phys_vec(struct dma_buf_phys_vec *phys_vec,
|
||||||
struct vfio_region_dma_range *dma_ranges,
|
struct vfio_region_dma_range *dma_ranges,
|
||||||
size_t nr_ranges, phys_addr_t start,
|
size_t nr_ranges, phys_addr_t start,
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@ struct vfio_pci_core_device;
|
||||||
struct vfio_pci_region;
|
struct vfio_pci_region;
|
||||||
struct p2pdma_provider;
|
struct p2pdma_provider;
|
||||||
struct dma_buf_phys_vec;
|
struct dma_buf_phys_vec;
|
||||||
|
struct dma_buf_attachment;
|
||||||
|
|
||||||
struct vfio_pci_eventfd {
|
struct vfio_pci_eventfd {
|
||||||
struct eventfd_ctx *ctx;
|
struct eventfd_ctx *ctx;
|
||||||
|
|
@ -226,4 +227,7 @@ static inline bool is_aligned_for_order(struct vm_area_struct *vma,
|
||||||
!IS_ALIGNED(pfn, 1 << order)));
|
!IS_ALIGNED(pfn, 1 << order)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int vfio_pci_dma_buf_iommufd_map(struct dma_buf_attachment *attachment,
|
||||||
|
struct dma_buf_phys_vec *phys);
|
||||||
|
|
||||||
#endif /* VFIO_PCI_CORE_H */
|
#endif /* VFIO_PCI_CORE_H */
|
||||||
|
|
|
||||||
|
|
@ -450,6 +450,16 @@ struct iommu_hwpt_vtd_s1 {
|
||||||
* nested domain will translate the same as the nesting parent. The S1 will
|
* nested domain will translate the same as the nesting parent. The S1 will
|
||||||
* install a Context Descriptor Table pointing at userspace memory translated
|
* install a Context Descriptor Table pointing at userspace memory translated
|
||||||
* by the nesting parent.
|
* by the nesting parent.
|
||||||
|
*
|
||||||
|
* It's suggested to allocate a vDEVICE object carrying vSID and then re-attach
|
||||||
|
* the nested domain, as soon as the vSID is available in the VMM level:
|
||||||
|
*
|
||||||
|
* - when Cfg=translate, a vDEVICE must be allocated prior to attaching to the
|
||||||
|
* allocated nested domain, as CD/ATS invalidations and vevents need a vSID.
|
||||||
|
* - when Cfg=bypass/abort, a vDEVICE is not enforced during the nested domain
|
||||||
|
* attachment, to support a GBPA case where VM sets CR0.SMMUEN=0. However, if
|
||||||
|
* VM sets CR0.SMMUEN=1 while missing a vDEVICE object, kernel would fail to
|
||||||
|
* report events to the VM. E.g. F_TRANSLATION when guest STE.Cfg=abort.
|
||||||
*/
|
*/
|
||||||
struct iommu_hwpt_arm_smmuv3 {
|
struct iommu_hwpt_arm_smmuv3 {
|
||||||
__aligned_le64 ste[2];
|
__aligned_le64 ste[2];
|
||||||
|
|
|
||||||
|
|
@ -1571,6 +1571,49 @@ TEST_F(iommufd_ioas, copy_sweep)
|
||||||
test_ioctl_destroy(dst_ioas_id);
|
test_ioctl_destroy(dst_ioas_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_F(iommufd_ioas, dmabuf_simple)
|
||||||
|
{
|
||||||
|
size_t buf_size = PAGE_SIZE*4;
|
||||||
|
__u64 iova;
|
||||||
|
int dfd;
|
||||||
|
|
||||||
|
test_cmd_get_dmabuf(buf_size, &dfd);
|
||||||
|
test_err_ioctl_ioas_map_file(EINVAL, dfd, 0, 0, &iova);
|
||||||
|
test_err_ioctl_ioas_map_file(EINVAL, dfd, buf_size, buf_size, &iova);
|
||||||
|
test_err_ioctl_ioas_map_file(EINVAL, dfd, 0, buf_size + 1, &iova);
|
||||||
|
test_ioctl_ioas_map_file(dfd, 0, buf_size, &iova);
|
||||||
|
|
||||||
|
close(dfd);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(iommufd_ioas, dmabuf_revoke)
|
||||||
|
{
|
||||||
|
size_t buf_size = PAGE_SIZE*4;
|
||||||
|
__u32 hwpt_id;
|
||||||
|
__u64 iova;
|
||||||
|
__u64 iova2;
|
||||||
|
int dfd;
|
||||||
|
|
||||||
|
test_cmd_get_dmabuf(buf_size, &dfd);
|
||||||
|
test_ioctl_ioas_map_file(dfd, 0, buf_size, &iova);
|
||||||
|
test_cmd_revoke_dmabuf(dfd, true);
|
||||||
|
|
||||||
|
if (variant->mock_domains)
|
||||||
|
test_cmd_hwpt_alloc(self->device_id, self->ioas_id, 0,
|
||||||
|
&hwpt_id);
|
||||||
|
|
||||||
|
test_err_ioctl_ioas_map_file(ENODEV, dfd, 0, buf_size, &iova2);
|
||||||
|
|
||||||
|
test_cmd_revoke_dmabuf(dfd, false);
|
||||||
|
test_ioctl_ioas_map_file(dfd, 0, buf_size, &iova2);
|
||||||
|
|
||||||
|
/* Restore the iova back */
|
||||||
|
test_ioctl_ioas_unmap(iova, buf_size);
|
||||||
|
test_ioctl_ioas_map_fixed_file(dfd, 0, buf_size, iova);
|
||||||
|
|
||||||
|
close(dfd);
|
||||||
|
}
|
||||||
|
|
||||||
FIXTURE(iommufd_mock_domain)
|
FIXTURE(iommufd_mock_domain)
|
||||||
{
|
{
|
||||||
int fd;
|
int fd;
|
||||||
|
|
|
||||||
|
|
@ -560,6 +560,39 @@ static int _test_cmd_destroy_access_pages(int fd, unsigned int access_id,
|
||||||
EXPECT_ERRNO(_errno, _test_cmd_destroy_access_pages( \
|
EXPECT_ERRNO(_errno, _test_cmd_destroy_access_pages( \
|
||||||
self->fd, access_id, access_pages_id))
|
self->fd, access_id, access_pages_id))
|
||||||
|
|
||||||
|
static int _test_cmd_get_dmabuf(int fd, size_t len, int *out_fd)
|
||||||
|
{
|
||||||
|
struct iommu_test_cmd cmd = {
|
||||||
|
.size = sizeof(cmd),
|
||||||
|
.op = IOMMU_TEST_OP_DMABUF_GET,
|
||||||
|
.dmabuf_get = { .length = len, .open_flags = O_CLOEXEC },
|
||||||
|
};
|
||||||
|
|
||||||
|
*out_fd = ioctl(fd, IOMMU_TEST_CMD, &cmd);
|
||||||
|
if (*out_fd < 0)
|
||||||
|
return -1;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
#define test_cmd_get_dmabuf(len, out_fd) \
|
||||||
|
ASSERT_EQ(0, _test_cmd_get_dmabuf(self->fd, len, out_fd))
|
||||||
|
|
||||||
|
static int _test_cmd_revoke_dmabuf(int fd, int dmabuf_fd, bool revoked)
|
||||||
|
{
|
||||||
|
struct iommu_test_cmd cmd = {
|
||||||
|
.size = sizeof(cmd),
|
||||||
|
.op = IOMMU_TEST_OP_DMABUF_REVOKE,
|
||||||
|
.dmabuf_revoke = { .dmabuf_fd = dmabuf_fd, .revoked = revoked },
|
||||||
|
};
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
ret = ioctl(fd, IOMMU_TEST_CMD, &cmd);
|
||||||
|
if (ret < 0)
|
||||||
|
return -1;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
#define test_cmd_revoke_dmabuf(dmabuf_fd, revoke) \
|
||||||
|
ASSERT_EQ(0, _test_cmd_revoke_dmabuf(self->fd, dmabuf_fd, revoke))
|
||||||
|
|
||||||
static int _test_ioctl_destroy(int fd, unsigned int id)
|
static int _test_ioctl_destroy(int fd, unsigned int id)
|
||||||
{
|
{
|
||||||
struct iommu_destroy cmd = {
|
struct iommu_destroy cmd = {
|
||||||
|
|
@ -730,6 +763,17 @@ static int _test_ioctl_ioas_map_file(int fd, unsigned int ioas_id, int mfd,
|
||||||
self->fd, ioas_id, mfd, start, length, iova_p, \
|
self->fd, ioas_id, mfd, start, length, iova_p, \
|
||||||
IOMMU_IOAS_MAP_WRITEABLE | IOMMU_IOAS_MAP_READABLE))
|
IOMMU_IOAS_MAP_WRITEABLE | IOMMU_IOAS_MAP_READABLE))
|
||||||
|
|
||||||
|
#define test_ioctl_ioas_map_fixed_file(mfd, start, length, iova) \
|
||||||
|
({ \
|
||||||
|
__u64 __iova = iova; \
|
||||||
|
ASSERT_EQ(0, _test_ioctl_ioas_map_file( \
|
||||||
|
self->fd, self->ioas_id, mfd, start, \
|
||||||
|
length, &__iova, \
|
||||||
|
IOMMU_IOAS_MAP_FIXED_IOVA | \
|
||||||
|
IOMMU_IOAS_MAP_WRITEABLE | \
|
||||||
|
IOMMU_IOAS_MAP_READABLE)); \
|
||||||
|
})
|
||||||
|
|
||||||
static int _test_ioctl_set_temp_memory_limit(int fd, unsigned int limit)
|
static int _test_ioctl_set_temp_memory_limit(int fd, unsigned int limit)
|
||||||
{
|
{
|
||||||
struct iommu_test_cmd memlimit_cmd = {
|
struct iommu_test_cmd memlimit_cmd = {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue