Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
+51
View File
@@ -0,0 +1,51 @@
# SPDX-License-Identifier: GPL-2.0-only
config IOMMUFD_DRIVER_CORE
bool
default (IOMMUFD_DRIVER || IOMMUFD) if IOMMUFD!=n
config IOMMUFD
tristate "IOMMU Userspace API"
select INTERVAL_TREE
select INTERVAL_TREE_SPAN_ITER
select IOMMU_API
default n
help
Provides /dev/iommu, the user API to control the IOMMU subsystem as
it relates to managing IO page tables that point at user space memory.
If you don't know what to do here, say N.
if IOMMUFD
config IOMMUFD_VFIO_CONTAINER
bool "IOMMUFD provides the VFIO container /dev/vfio/vfio"
depends on VFIO_GROUP && !VFIO_CONTAINER
default VFIO_GROUP && !VFIO_CONTAINER
help
IOMMUFD will provide /dev/vfio/vfio instead of VFIO. This relies on
IOMMUFD providing compatibility emulation to give the same ioctls.
It provides an option to build a kernel with legacy VFIO components
removed.
IOMMUFD VFIO container emulation is known to lack certain features
of the native VFIO container, such as peer-to-peer
DMA mapping, PPC IOMMU support, as well as other potentially
undiscovered gaps. This option is currently intended for the
purpose of testing IOMMUFD with unmodified userspace supporting VFIO
and making use of the Type1 VFIO IOMMU backend. General purpose
enabling of this option is currently discouraged.
Unless testing IOMMUFD, say N here.
config IOMMUFD_TEST
bool "IOMMU Userspace API Test support"
depends on DEBUG_KERNEL
depends on FAULT_INJECTION
depends on RUNTIME_TESTING_MENU
depends on IOMMU_PT_AMDV1=y || IOMMUFD=IOMMU_PT_AMDV1
select DMA_SHARED_BUFFER
select IOMMUFD_DRIVER
default n
help
This is dangerous, do not enable unless running
tools/testing/selftests/iommu
endif
@@ -0,0 +1,19 @@
# SPDX-License-Identifier: GPL-2.0-only
iommufd-y := \
device.o \
eventq.o \
hw_pagetable.o \
io_pagetable.o \
ioas.o \
main.o \
pages.o \
vfio_compat.o \
viommu.o
iommufd-$(CONFIG_IOMMUFD_TEST) += selftest.o
obj-$(CONFIG_IOMMUFD) += iommufd.o
obj-$(CONFIG_IOMMUFD_DRIVER) += iova_bitmap.o
iommufd_driver-y := driver.o
obj-$(CONFIG_IOMMUFD_DRIVER_CORE) += iommufd_driver.o
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,53 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* Copyright (c) 2022, NVIDIA CORPORATION & AFFILIATES.
*/
#ifndef __IOMMUFD_DOUBLE_SPAN_H
#define __IOMMUFD_DOUBLE_SPAN_H
#include <linux/interval_tree.h>
/*
* This is a variation of the general interval_tree_span_iter that computes the
* spans over the union of two different interval trees. Used ranges are broken
* up and reported based on the tree that provides the interval. The first span
* always takes priority. Like interval_tree_span_iter it is greedy and the same
* value of is_used will not repeat on two iteration cycles.
*/
struct interval_tree_double_span_iter {
struct rb_root_cached *itrees[2];
struct interval_tree_span_iter spans[2];
union {
unsigned long start_hole;
unsigned long start_used;
};
union {
unsigned long last_hole;
unsigned long last_used;
};
/* 0 = hole, 1 = used span[0], 2 = used span[1], -1 done iteration */
int is_used;
};
void interval_tree_double_span_iter_update(
struct interval_tree_double_span_iter *iter);
void interval_tree_double_span_iter_first(
struct interval_tree_double_span_iter *iter,
struct rb_root_cached *itree1, struct rb_root_cached *itree2,
unsigned long first_index, unsigned long last_index);
void interval_tree_double_span_iter_next(
struct interval_tree_double_span_iter *iter);
static inline bool
interval_tree_double_span_iter_done(struct interval_tree_double_span_iter *state)
{
return state->is_used == -1;
}
#define interval_tree_for_each_double_span(span, itree1, itree2, first_index, \
last_index) \
for (interval_tree_double_span_iter_first(span, itree1, itree2, \
first_index, last_index); \
!interval_tree_double_span_iter_done(span); \
interval_tree_double_span_iter_next(span))
#endif
+307
View File
@@ -0,0 +1,307 @@
// SPDX-License-Identifier: GPL-2.0-only
/* Copyright (c) 2024, NVIDIA CORPORATION & AFFILIATES
*/
#include "iommufd_private.h"
/* Driver should use a per-structure helper in include/linux/iommufd.h */
int _iommufd_object_depend(struct iommufd_object *obj_dependent,
struct iommufd_object *obj_depended)
{
/* Reject self dependency that dead locks */
if (obj_dependent == obj_depended)
return -EINVAL;
/* Only support dependency between two objects of the same type */
if (obj_dependent->type != obj_depended->type)
return -EINVAL;
refcount_inc(&obj_depended->users);
return 0;
}
EXPORT_SYMBOL_NS_GPL(_iommufd_object_depend, "IOMMUFD");
/* Driver should use a per-structure helper in include/linux/iommufd.h */
void _iommufd_object_undepend(struct iommufd_object *obj_dependent,
struct iommufd_object *obj_depended)
{
if (WARN_ON_ONCE(obj_dependent == obj_depended ||
obj_dependent->type != obj_depended->type))
return;
refcount_dec(&obj_depended->users);
}
EXPORT_SYMBOL_NS_GPL(_iommufd_object_undepend, "IOMMUFD");
/*
* Allocate an @offset to return to user space to use for an mmap() syscall
*
* Driver should use a per-structure helper in include/linux/iommufd.h
*/
int _iommufd_alloc_mmap(struct iommufd_ctx *ictx, struct iommufd_object *owner,
phys_addr_t mmio_addr, size_t length,
unsigned long *offset)
{
struct iommufd_mmap *immap;
unsigned long startp;
int rc;
if (!PAGE_ALIGNED(mmio_addr))
return -EINVAL;
if (!length || !PAGE_ALIGNED(length))
return -EINVAL;
immap = kzalloc(sizeof(*immap), GFP_KERNEL);
if (!immap)
return -ENOMEM;
immap->owner = owner;
immap->length = length;
immap->mmio_addr = mmio_addr;
/* Skip the first page to ease caller identifying the returned offset */
rc = mtree_alloc_range(&ictx->mt_mmap, &startp, immap, immap->length,
PAGE_SIZE, ULONG_MAX, GFP_KERNEL);
if (rc < 0) {
kfree(immap);
return rc;
}
/* mmap() syscall will right-shift the offset in vma->vm_pgoff too */
immap->vm_pgoff = startp >> PAGE_SHIFT;
*offset = startp;
return 0;
}
EXPORT_SYMBOL_NS_GPL(_iommufd_alloc_mmap, "IOMMUFD");
/* Driver should use a per-structure helper in include/linux/iommufd.h */
void _iommufd_destroy_mmap(struct iommufd_ctx *ictx,
struct iommufd_object *owner, unsigned long offset)
{
struct iommufd_mmap *immap;
immap = mtree_erase(&ictx->mt_mmap, offset);
WARN_ON_ONCE(!immap || immap->owner != owner);
kfree(immap);
}
EXPORT_SYMBOL_NS_GPL(_iommufd_destroy_mmap, "IOMMUFD");
struct device *iommufd_vdevice_to_device(struct iommufd_vdevice *vdev)
{
return vdev->idev->dev;
}
EXPORT_SYMBOL_NS_GPL(iommufd_vdevice_to_device, "IOMMUFD");
/* Caller should xa_lock(&viommu->vdevs) to protect the return value */
struct device *iommufd_viommu_find_dev(struct iommufd_viommu *viommu,
unsigned long vdev_id)
{
struct iommufd_vdevice *vdev;
lockdep_assert_held(&viommu->vdevs.xa_lock);
vdev = xa_load(&viommu->vdevs, vdev_id);
return vdev ? iommufd_vdevice_to_device(vdev) : NULL;
}
EXPORT_SYMBOL_NS_GPL(iommufd_viommu_find_dev, "IOMMUFD");
/* Return -ENOENT if device is not associated to the vIOMMU */
int iommufd_viommu_get_vdev_id(struct iommufd_viommu *viommu,
struct device *dev, unsigned long *vdev_id)
{
struct iommufd_vdevice *vdev;
unsigned long index;
int rc = -ENOENT;
if (WARN_ON_ONCE(!vdev_id))
return -EINVAL;
xa_lock(&viommu->vdevs);
xa_for_each(&viommu->vdevs, index, vdev) {
if (iommufd_vdevice_to_device(vdev) == dev) {
*vdev_id = vdev->virt_id;
rc = 0;
break;
}
}
xa_unlock(&viommu->vdevs);
return rc;
}
EXPORT_SYMBOL_NS_GPL(iommufd_viommu_get_vdev_id, "IOMMUFD");
/*
* Typically called in driver's threaded IRQ handler.
* The @type and @event_data must be defined in include/uapi/linux/iommufd.h
*/
int iommufd_viommu_report_event(struct iommufd_viommu *viommu,
enum iommu_veventq_type type, void *event_data,
size_t data_len)
{
struct iommufd_veventq *veventq;
struct iommufd_vevent *vevent;
int rc = 0;
if (WARN_ON_ONCE(!data_len || !event_data))
return -EINVAL;
down_read(&viommu->veventqs_rwsem);
veventq = iommufd_viommu_find_veventq(viommu, type);
if (!veventq) {
rc = -EOPNOTSUPP;
goto out_unlock_veventqs;
}
/* Pre-allocate to avoid GFP_ATOMIC; use GFP_NOWAIT to avoid sleeping */
vevent = kzalloc_flex(*vevent, event_data, data_len, GFP_NOWAIT);
if (!vevent) {
spin_lock(&veventq->common.lock);
vevent = &veventq->lost_events_header;
rc = -ENOMEM;
goto out_set_header;
}
spin_lock(&veventq->common.lock);
if (veventq->num_events == veventq->depth) {
kfree(vevent);
vevent = &veventq->lost_events_header;
goto out_set_header;
}
vevent->data_len = data_len;
memcpy(vevent->event_data, event_data, data_len);
veventq->num_events++;
out_set_header:
iommufd_vevent_handler(veventq, vevent);
spin_unlock(&veventq->common.lock);
out_unlock_veventqs:
up_read(&viommu->veventqs_rwsem);
return rc;
}
EXPORT_SYMBOL_NS_GPL(iommufd_viommu_report_event, "IOMMUFD");
#ifdef CONFIG_IRQ_MSI_IOMMU
/*
* Get a iommufd_sw_msi_map for the msi physical address requested by the irq
* layer. The mapping to IOVA is global to the iommufd file descriptor, every
* domain that is attached to a device using the same MSI parameters will use
* the same IOVA.
*/
static struct iommufd_sw_msi_map *
iommufd_sw_msi_get_map(struct iommufd_ctx *ictx, phys_addr_t msi_addr,
phys_addr_t sw_msi_start)
{
struct iommufd_sw_msi_map *cur;
unsigned int max_pgoff = 0;
lockdep_assert_held(&ictx->sw_msi_lock);
list_for_each_entry(cur, &ictx->sw_msi_list, sw_msi_item) {
if (cur->sw_msi_start != sw_msi_start)
continue;
max_pgoff = max(max_pgoff, cur->pgoff + 1);
if (cur->msi_addr == msi_addr)
return cur;
}
if (ictx->sw_msi_id >=
BITS_PER_BYTE * sizeof_field(struct iommufd_sw_msi_maps, bitmap))
return ERR_PTR(-EOVERFLOW);
cur = kzalloc_obj(*cur);
if (!cur)
return ERR_PTR(-ENOMEM);
cur->sw_msi_start = sw_msi_start;
cur->msi_addr = msi_addr;
cur->pgoff = max_pgoff;
cur->id = ictx->sw_msi_id++;
list_add_tail(&cur->sw_msi_item, &ictx->sw_msi_list);
return cur;
}
int iommufd_sw_msi_install(struct iommufd_ctx *ictx,
struct iommufd_hwpt_paging *hwpt_paging,
struct iommufd_sw_msi_map *msi_map)
{
unsigned long iova;
lockdep_assert_held(&ictx->sw_msi_lock);
iova = msi_map->sw_msi_start + msi_map->pgoff * PAGE_SIZE;
if (!test_bit(msi_map->id, hwpt_paging->present_sw_msi.bitmap)) {
int rc;
rc = iommu_map(hwpt_paging->common.domain, iova,
msi_map->msi_addr, PAGE_SIZE,
IOMMU_WRITE | IOMMU_READ | IOMMU_MMIO,
GFP_KERNEL_ACCOUNT);
if (rc)
return rc;
__set_bit(msi_map->id, hwpt_paging->present_sw_msi.bitmap);
}
return 0;
}
EXPORT_SYMBOL_NS_GPL(iommufd_sw_msi_install, "IOMMUFD_INTERNAL");
/*
* Called by the irq code if the platform translates the MSI address through the
* IOMMU. msi_addr is the physical address of the MSI page. iommufd will
* allocate a fd global iova for the physical page that is the same on all
* domains and devices.
*/
int iommufd_sw_msi(struct iommu_domain *domain, struct msi_desc *desc,
phys_addr_t msi_addr)
{
struct device *dev = msi_desc_to_dev(desc);
struct iommufd_hwpt_paging *hwpt_paging;
struct iommu_attach_handle *raw_handle;
struct iommufd_attach_handle *handle;
struct iommufd_sw_msi_map *msi_map;
struct iommufd_ctx *ictx;
unsigned long iova;
int rc;
/*
* It is safe to call iommu_attach_handle_get() here because the iommu
* core code invokes this under the group mutex which also prevents any
* change of the attach handle for the duration of this function.
*/
iommu_group_mutex_assert(dev);
raw_handle =
iommu_attach_handle_get(dev->iommu_group, IOMMU_NO_PASID, 0);
if (IS_ERR(raw_handle))
return 0;
hwpt_paging = find_hwpt_paging(domain->iommufd_hwpt);
handle = to_iommufd_handle(raw_handle);
/* No IOMMU_RESV_SW_MSI means no change to the msi_msg */
if (handle->idev->igroup->sw_msi_start == PHYS_ADDR_MAX)
return 0;
ictx = handle->idev->ictx;
guard(mutex)(&ictx->sw_msi_lock);
/*
* The input msi_addr is the exact byte offset of the MSI doorbell, we
* assume the caller has checked that it is contained with a MMIO region
* that is secure to map at PAGE_SIZE.
*/
msi_map = iommufd_sw_msi_get_map(handle->idev->ictx,
msi_addr & PAGE_MASK,
handle->idev->igroup->sw_msi_start);
if (IS_ERR(msi_map))
return PTR_ERR(msi_map);
rc = iommufd_sw_msi_install(ictx, hwpt_paging, msi_map);
if (rc)
return rc;
__set_bit(msi_map->id, handle->idev->igroup->required_sw_msi.bitmap);
iova = msi_map->sw_msi_start + msi_map->pgoff * PAGE_SIZE;
msi_desc_set_iommu_msi_iova(desc, iova, PAGE_SHIFT);
return 0;
}
EXPORT_SYMBOL_NS_GPL(iommufd_sw_msi, "IOMMUFD");
#endif
MODULE_DESCRIPTION("iommufd code shared with builtin modules");
MODULE_IMPORT_NS("IOMMUFD_INTERNAL");
MODULE_LICENSE("GPL");
+570
View File
@@ -0,0 +1,570 @@
// SPDX-License-Identifier: GPL-2.0-only
/* Copyright (C) 2024 Intel Corporation
*/
#define pr_fmt(fmt) "iommufd: " fmt
#include <linux/anon_inodes.h>
#include <linux/file.h>
#include <linux/fs.h>
#include <linux/iommufd.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/poll.h>
#include <uapi/linux/iommufd.h>
#include "../iommu-priv.h"
#include "iommufd_private.h"
/* IOMMUFD_OBJ_FAULT Functions */
void iommufd_auto_response_faults(struct iommufd_hw_pagetable *hwpt,
struct iommufd_attach_handle *handle)
{
struct iommufd_fault *fault = hwpt->fault;
struct iopf_group *group, *next;
struct list_head free_list;
unsigned long index;
if (!fault || !handle)
return;
INIT_LIST_HEAD(&free_list);
mutex_lock(&fault->mutex);
spin_lock(&fault->common.lock);
list_for_each_entry_safe(group, next, &fault->common.deliver, node) {
if (group->attach_handle != &handle->handle)
continue;
list_move(&group->node, &free_list);
}
spin_unlock(&fault->common.lock);
list_for_each_entry_safe(group, next, &free_list, node) {
list_del(&group->node);
iopf_group_response(group, IOMMU_PAGE_RESP_INVALID);
iopf_free_group(group);
}
xa_for_each(&fault->response, index, group) {
if (group->attach_handle != &handle->handle)
continue;
xa_erase(&fault->response, index);
iopf_group_response(group, IOMMU_PAGE_RESP_INVALID);
iopf_free_group(group);
}
mutex_unlock(&fault->mutex);
}
void iommufd_fault_destroy(struct iommufd_object *obj)
{
struct iommufd_eventq *eventq =
container_of(obj, struct iommufd_eventq, obj);
struct iommufd_fault *fault = eventq_to_fault(eventq);
struct iopf_group *group, *next;
unsigned long index;
/*
* The iommufd object's reference count is zero at this point.
* We can be confident that no other threads are currently
* accessing this pointer. Therefore, acquiring the mutex here
* is unnecessary.
*/
list_for_each_entry_safe(group, next, &fault->common.deliver, node) {
list_del(&group->node);
iopf_group_response(group, IOMMU_PAGE_RESP_INVALID);
iopf_free_group(group);
}
xa_for_each(&fault->response, index, group) {
xa_erase(&fault->response, index);
iopf_group_response(group, IOMMU_PAGE_RESP_INVALID);
iopf_free_group(group);
}
xa_destroy(&fault->response);
mutex_destroy(&fault->mutex);
}
static void iommufd_compose_fault_message(struct iommu_fault *fault,
struct iommu_hwpt_pgfault *hwpt_fault,
struct iommufd_device *idev,
u32 cookie)
{
hwpt_fault->flags = fault->prm.flags;
hwpt_fault->dev_id = idev->obj.id;
hwpt_fault->pasid = fault->prm.pasid;
hwpt_fault->grpid = fault->prm.grpid;
hwpt_fault->perm = fault->prm.perm;
hwpt_fault->addr = fault->prm.addr;
hwpt_fault->length = 0;
hwpt_fault->cookie = cookie;
}
/* Fetch the first node out of the fault->deliver list */
static struct iopf_group *
iommufd_fault_deliver_fetch(struct iommufd_fault *fault)
{
struct list_head *list = &fault->common.deliver;
struct iopf_group *group = NULL;
spin_lock(&fault->common.lock);
if (!list_empty(list)) {
group = list_first_entry(list, struct iopf_group, node);
list_del(&group->node);
}
spin_unlock(&fault->common.lock);
return group;
}
/* Restore a node back to the head of the fault->deliver list */
static void iommufd_fault_deliver_restore(struct iommufd_fault *fault,
struct iopf_group *group)
{
spin_lock(&fault->common.lock);
list_add(&group->node, &fault->common.deliver);
spin_unlock(&fault->common.lock);
}
static ssize_t iommufd_fault_fops_read(struct file *filep, char __user *buf,
size_t count, loff_t *ppos)
{
size_t fault_size = sizeof(struct iommu_hwpt_pgfault);
struct iommufd_eventq *eventq = filep->private_data;
struct iommufd_fault *fault = eventq_to_fault(eventq);
struct iommu_hwpt_pgfault data = {};
struct iommufd_device *idev;
struct iopf_group *group;
struct iopf_fault *iopf;
size_t done = 0;
int rc = 0;
if (*ppos || count % fault_size)
return -ESPIPE;
mutex_lock(&fault->mutex);
while ((group = iommufd_fault_deliver_fetch(fault))) {
size_t group_done = done;
if (done >= count ||
group->fault_count * fault_size > count - done) {
iommufd_fault_deliver_restore(fault, group);
/* Read count doesn't fit the first fault group */
if (done == 0)
rc = -EINVAL;
break;
}
rc = xa_alloc(&fault->response, &group->cookie, group,
xa_limit_32b, GFP_KERNEL);
if (rc) {
iommufd_fault_deliver_restore(fault, group);
break;
}
idev = to_iommufd_handle(group->attach_handle)->idev;
list_for_each_entry(iopf, &group->faults, list) {
iommufd_compose_fault_message(&iopf->fault,
&data, idev,
group->cookie);
if (copy_to_user(buf + group_done, &data, fault_size)) {
xa_erase(&fault->response, group->cookie);
iommufd_fault_deliver_restore(fault, group);
rc = -EFAULT;
break;
}
group_done += fault_size;
}
if (rc)
break;
done = group_done;
}
mutex_unlock(&fault->mutex);
return done == 0 ? rc : done;
}
static ssize_t iommufd_fault_fops_write(struct file *filep, const char __user *buf,
size_t count, loff_t *ppos)
{
size_t response_size = sizeof(struct iommu_hwpt_page_response);
struct iommufd_eventq *eventq = filep->private_data;
struct iommufd_fault *fault = eventq_to_fault(eventq);
struct iommu_hwpt_page_response response;
struct iopf_group *group;
size_t done = 0;
int rc = 0;
if (*ppos || count % response_size)
return -ESPIPE;
mutex_lock(&fault->mutex);
while (count > done) {
if (copy_from_user(&response, buf + done, response_size)) {
rc = -EFAULT;
break;
}
static_assert((int)IOMMUFD_PAGE_RESP_SUCCESS ==
(int)IOMMU_PAGE_RESP_SUCCESS);
static_assert((int)IOMMUFD_PAGE_RESP_INVALID ==
(int)IOMMU_PAGE_RESP_INVALID);
if (response.code != IOMMUFD_PAGE_RESP_SUCCESS &&
response.code != IOMMUFD_PAGE_RESP_INVALID) {
rc = -EINVAL;
break;
}
group = xa_erase(&fault->response, response.cookie);
if (!group) {
rc = -EINVAL;
break;
}
iopf_group_response(group, response.code);
iopf_free_group(group);
done += response_size;
}
mutex_unlock(&fault->mutex);
return done == 0 ? rc : done;
}
/* IOMMUFD_OBJ_VEVENTQ Functions */
void iommufd_veventq_abort(struct iommufd_object *obj)
{
struct iommufd_eventq *eventq =
container_of(obj, struct iommufd_eventq, obj);
struct iommufd_veventq *veventq = eventq_to_veventq(eventq);
struct iommufd_viommu *viommu = veventq->viommu;
struct iommufd_vevent *cur, *next;
lockdep_assert_held_write(&viommu->veventqs_rwsem);
list_for_each_entry_safe(cur, next, &eventq->deliver, node) {
list_del(&cur->node);
if (cur != &veventq->lost_events_header)
kfree(cur);
}
refcount_dec(&viommu->obj.users);
list_del(&veventq->node);
}
void iommufd_veventq_destroy(struct iommufd_object *obj)
{
struct iommufd_veventq *veventq = eventq_to_veventq(
container_of(obj, struct iommufd_eventq, obj));
down_write(&veventq->viommu->veventqs_rwsem);
iommufd_veventq_abort(obj);
up_write(&veventq->viommu->veventqs_rwsem);
}
static struct iommufd_vevent *
iommufd_veventq_deliver_fetch(struct iommufd_veventq *veventq)
{
struct iommufd_eventq *eventq = &veventq->common;
struct list_head *list = &eventq->deliver;
struct iommufd_vevent *vevent = NULL;
spin_lock(&eventq->lock);
if (!list_empty(list)) {
struct iommufd_vevent *next;
next = list_first_entry(list, struct iommufd_vevent, node);
/* Make a copy of the lost_events_header for copy_to_user */
if (next == &veventq->lost_events_header) {
vevent = kzalloc_obj(*vevent, GFP_ATOMIC);
if (!vevent) {
vevent = ERR_PTR(-ENOMEM);
goto out_unlock;
}
}
list_del(&next->node);
if (vevent)
memcpy(vevent, next, sizeof(*vevent));
else
vevent = next;
}
out_unlock:
spin_unlock(&eventq->lock);
return vevent;
}
static void iommufd_veventq_deliver_restore(struct iommufd_veventq *veventq,
struct iommufd_vevent *vevent)
{
struct iommufd_eventq *eventq = &veventq->common;
struct list_head *list = &eventq->deliver;
spin_lock(&eventq->lock);
if (vevent_for_lost_events_header(vevent)) {
/* Remove the copy of the lost_events_header */
kfree(vevent);
vevent = NULL;
/* An empty list needs the lost_events_header back */
if (list_empty(list))
vevent = &veventq->lost_events_header;
}
if (vevent)
list_add(&vevent->node, list);
spin_unlock(&eventq->lock);
}
static ssize_t iommufd_veventq_fops_read(struct file *filep, char __user *buf,
size_t count, loff_t *ppos)
{
struct iommufd_eventq *eventq = filep->private_data;
struct iommufd_veventq *veventq = eventq_to_veventq(eventq);
struct iommufd_vevent_header *hdr;
struct iommufd_vevent *cur;
size_t done = 0;
int rc = 0;
if (*ppos)
return -ESPIPE;
/* Minimum read count is a vEVENT header */
if (count < sizeof(*hdr))
return -EINVAL;
while ((cur = iommufd_veventq_deliver_fetch(veventq))) {
if (IS_ERR(cur)) {
if (done == 0)
rc = PTR_ERR(cur);
break;
}
/* Validate the remaining bytes against the header size */
if (done >= count || sizeof(*hdr) > count - done) {
iommufd_veventq_deliver_restore(veventq, cur);
break;
}
hdr = &cur->header;
/* If being a normal vEVENT, validate against the full size */
if (!vevent_for_lost_events_header(cur) &&
sizeof(*hdr) + cur->data_len > count - done) {
iommufd_veventq_deliver_restore(veventq, cur);
/* Read count doesn't fit a single normal vEVENT */
if (done == 0)
rc = -EINVAL;
break;
}
if (copy_to_user(buf + done, hdr, sizeof(*hdr))) {
iommufd_veventq_deliver_restore(veventq, cur);
rc = -EFAULT;
break;
}
done += sizeof(*hdr);
if (cur->data_len &&
copy_to_user(buf + done, cur->event_data, cur->data_len)) {
iommufd_veventq_deliver_restore(veventq, cur);
done -= sizeof(*hdr);
rc = -EFAULT;
break;
}
spin_lock(&eventq->lock);
if (!vevent_for_lost_events_header(cur))
veventq->num_events--;
spin_unlock(&eventq->lock);
done += cur->data_len;
kfree(cur);
}
return done == 0 ? rc : done;
}
/* Common Event Queue Functions */
static __poll_t iommufd_eventq_fops_poll(struct file *filep,
struct poll_table_struct *wait)
{
struct iommufd_eventq *eventq = filep->private_data;
__poll_t pollflags = 0;
if (eventq->obj.type == IOMMUFD_OBJ_FAULT)
pollflags |= EPOLLOUT;
poll_wait(filep, &eventq->wait_queue, wait);
spin_lock(&eventq->lock);
if (!list_empty(&eventq->deliver))
pollflags |= EPOLLIN | EPOLLRDNORM;
spin_unlock(&eventq->lock);
return pollflags;
}
static int iommufd_eventq_fops_release(struct inode *inode, struct file *filep)
{
struct iommufd_eventq *eventq = filep->private_data;
refcount_dec(&eventq->obj.users);
iommufd_ctx_put(eventq->ictx);
return 0;
}
#define INIT_EVENTQ_FOPS(read_op, write_op) \
((const struct file_operations){ \
.owner = THIS_MODULE, \
.open = nonseekable_open, \
.read = read_op, \
.write = write_op, \
.poll = iommufd_eventq_fops_poll, \
.release = iommufd_eventq_fops_release, \
})
static int iommufd_eventq_init(struct iommufd_eventq *eventq, char *name,
struct iommufd_ctx *ictx,
const struct file_operations *fops)
{
struct file *filep;
spin_lock_init(&eventq->lock);
INIT_LIST_HEAD(&eventq->deliver);
init_waitqueue_head(&eventq->wait_queue);
/* The filep is fput() by the core code during failure */
filep = anon_inode_getfile(name, fops, eventq, O_RDWR);
if (IS_ERR(filep))
return PTR_ERR(filep);
eventq->ictx = ictx;
iommufd_ctx_get(eventq->ictx);
eventq->filep = filep;
refcount_inc(&eventq->obj.users);
return get_unused_fd_flags(O_CLOEXEC);
}
static const struct file_operations iommufd_fault_fops =
INIT_EVENTQ_FOPS(iommufd_fault_fops_read, iommufd_fault_fops_write);
int iommufd_fault_alloc(struct iommufd_ucmd *ucmd)
{
struct iommu_fault_alloc *cmd = ucmd->cmd;
struct iommufd_fault *fault;
int fdno;
int rc;
if (cmd->flags)
return -EOPNOTSUPP;
fault = __iommufd_object_alloc_ucmd(ucmd, fault, IOMMUFD_OBJ_FAULT,
common.obj);
if (IS_ERR(fault))
return PTR_ERR(fault);
xa_init_flags(&fault->response, XA_FLAGS_ALLOC1);
mutex_init(&fault->mutex);
fdno = iommufd_eventq_init(&fault->common, "[iommufd-pgfault]",
ucmd->ictx, &iommufd_fault_fops);
if (fdno < 0)
return fdno;
cmd->out_fault_id = fault->common.obj.id;
cmd->out_fault_fd = fdno;
rc = iommufd_ucmd_respond(ucmd, sizeof(*cmd));
if (rc)
goto out_put_fdno;
fd_install(fdno, fault->common.filep);
return 0;
out_put_fdno:
put_unused_fd(fdno);
return rc;
}
int iommufd_fault_iopf_handler(struct iopf_group *group)
{
struct iommufd_hw_pagetable *hwpt;
struct iommufd_fault *fault;
hwpt = group->attach_handle->domain->iommufd_hwpt;
fault = hwpt->fault;
iopf_group_dequeue(group);
spin_lock(&fault->common.lock);
list_add_tail(&group->node, &fault->common.deliver);
spin_unlock(&fault->common.lock);
wake_up_interruptible(&fault->common.wait_queue);
return 0;
}
static const struct file_operations iommufd_veventq_fops =
INIT_EVENTQ_FOPS(iommufd_veventq_fops_read, NULL);
/* An arbitrary upper bound for veventq_depth that fits all existing HWs */
#define VEVENTQ_MAX_DEPTH (1U << 19)
int iommufd_veventq_alloc(struct iommufd_ucmd *ucmd)
{
struct iommu_veventq_alloc *cmd = ucmd->cmd;
struct iommufd_veventq *veventq;
struct iommufd_viommu *viommu;
int fdno;
int rc;
if (cmd->flags || cmd->__reserved ||
cmd->type == IOMMU_VEVENTQ_TYPE_DEFAULT)
return -EOPNOTSUPP;
if (!cmd->veventq_depth || cmd->veventq_depth > VEVENTQ_MAX_DEPTH)
return -EINVAL;
viommu = iommufd_get_viommu(ucmd, cmd->viommu_id);
if (IS_ERR(viommu))
return PTR_ERR(viommu);
down_write(&viommu->veventqs_rwsem);
if (iommufd_viommu_find_veventq(viommu, cmd->type)) {
rc = -EEXIST;
goto out_unlock_veventqs;
}
veventq = __iommufd_object_alloc(ucmd->ictx, veventq,
IOMMUFD_OBJ_VEVENTQ, common.obj);
if (IS_ERR(veventq)) {
rc = PTR_ERR(veventq);
goto out_unlock_veventqs;
}
veventq->type = cmd->type;
veventq->viommu = viommu;
refcount_inc(&viommu->obj.users);
veventq->depth = cmd->veventq_depth;
list_add_tail(&veventq->node, &viommu->veventqs);
veventq->lost_events_header.header.flags =
IOMMU_VEVENTQ_FLAG_LOST_EVENTS;
fdno = iommufd_eventq_init(&veventq->common, "[iommufd-viommu-event]",
ucmd->ictx, &iommufd_veventq_fops);
if (fdno < 0) {
rc = fdno;
goto out_abort;
}
cmd->out_veventq_id = veventq->common.obj.id;
cmd->out_veventq_fd = fdno;
rc = iommufd_ucmd_respond(ucmd, sizeof(*cmd));
if (rc)
goto out_put_fdno;
iommufd_object_finalize(ucmd->ictx, &veventq->common.obj);
fd_install(fdno, veventq->common.filep);
goto out_unlock_veventqs;
out_put_fdno:
put_unused_fd(fdno);
out_abort:
iommufd_object_abort_and_destroy(ucmd->ictx, &veventq->common.obj);
out_unlock_veventqs:
up_write(&viommu->veventqs_rwsem);
iommufd_put_object(ucmd->ictx, &viommu->obj);
return rc;
}
@@ -0,0 +1,563 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2021-2022, NVIDIA CORPORATION & AFFILIATES
*/
#include <linux/iommu.h>
#include <uapi/linux/iommufd.h>
#include "../iommu-priv.h"
#include "iommufd_private.h"
static void __iommufd_hwpt_destroy(struct iommufd_hw_pagetable *hwpt)
{
if (hwpt->domain)
iommu_domain_free(hwpt->domain);
if (hwpt->fault)
refcount_dec(&hwpt->fault->common.obj.users);
}
void iommufd_hwpt_paging_destroy(struct iommufd_object *obj)
{
struct iommufd_hwpt_paging *hwpt_paging =
container_of(obj, struct iommufd_hwpt_paging, common.obj);
if (!list_empty(&hwpt_paging->hwpt_item)) {
mutex_lock(&hwpt_paging->ioas->mutex);
list_del(&hwpt_paging->hwpt_item);
mutex_unlock(&hwpt_paging->ioas->mutex);
iopt_table_remove_domain(&hwpt_paging->ioas->iopt,
hwpt_paging->common.domain);
}
__iommufd_hwpt_destroy(&hwpt_paging->common);
refcount_dec(&hwpt_paging->ioas->obj.users);
}
void iommufd_hwpt_paging_abort(struct iommufd_object *obj)
{
struct iommufd_hwpt_paging *hwpt_paging =
container_of(obj, struct iommufd_hwpt_paging, common.obj);
/* The ioas->mutex must be held until finalize is called. */
lockdep_assert_held(&hwpt_paging->ioas->mutex);
if (!list_empty(&hwpt_paging->hwpt_item)) {
list_del_init(&hwpt_paging->hwpt_item);
iopt_table_remove_domain(&hwpt_paging->ioas->iopt,
hwpt_paging->common.domain);
}
iommufd_hwpt_paging_destroy(obj);
}
void iommufd_hwpt_nested_destroy(struct iommufd_object *obj)
{
struct iommufd_hwpt_nested *hwpt_nested =
container_of(obj, struct iommufd_hwpt_nested, common.obj);
__iommufd_hwpt_destroy(&hwpt_nested->common);
if (hwpt_nested->viommu)
refcount_dec(&hwpt_nested->viommu->obj.users);
else
refcount_dec(&hwpt_nested->parent->common.obj.users);
}
void iommufd_hwpt_nested_abort(struct iommufd_object *obj)
{
iommufd_hwpt_nested_destroy(obj);
}
static int
iommufd_hwpt_paging_enforce_cc(struct iommufd_hwpt_paging *hwpt_paging)
{
struct iommu_domain *paging_domain = hwpt_paging->common.domain;
if (hwpt_paging->enforce_cache_coherency)
return 0;
if (paging_domain->ops->enforce_cache_coherency)
hwpt_paging->enforce_cache_coherency =
paging_domain->ops->enforce_cache_coherency(
paging_domain);
if (!hwpt_paging->enforce_cache_coherency)
return -EINVAL;
return 0;
}
/**
* iommufd_hwpt_paging_alloc() - Get a PAGING iommu_domain for a device
* @ictx: iommufd context
* @ioas: IOAS to associate the domain with
* @idev: Device to get an iommu_domain for
* @pasid: PASID to get an iommu_domain for
* @flags: Flags from userspace
* @immediate_attach: True if idev should be attached to the hwpt
* @user_data: The user provided driver specific data describing the domain to
* create
*
* Allocate a new iommu_domain and return it as a hw_pagetable. The HWPT
* will be linked to the given ioas and upon return the underlying iommu_domain
* is fully popoulated.
*
* The caller must hold the ioas->mutex until after
* iommufd_object_abort_and_destroy() or iommufd_object_finalize() is called on
* the returned hwpt.
*/
struct iommufd_hwpt_paging *
iommufd_hwpt_paging_alloc(struct iommufd_ctx *ictx, struct iommufd_ioas *ioas,
struct iommufd_device *idev, ioasid_t pasid,
u32 flags, bool immediate_attach,
const struct iommu_user_data *user_data)
{
const u32 valid_flags = IOMMU_HWPT_ALLOC_NEST_PARENT |
IOMMU_HWPT_ALLOC_DIRTY_TRACKING |
IOMMU_HWPT_FAULT_ID_VALID |
IOMMU_HWPT_ALLOC_PASID;
const struct iommu_ops *ops = dev_iommu_ops(idev->dev);
struct iommufd_hwpt_paging *hwpt_paging;
struct iommufd_hw_pagetable *hwpt;
int rc;
lockdep_assert_held(&ioas->mutex);
if ((flags || user_data) && !ops->domain_alloc_paging_flags)
return ERR_PTR(-EOPNOTSUPP);
if (flags & ~valid_flags)
return ERR_PTR(-EOPNOTSUPP);
if ((flags & IOMMU_HWPT_ALLOC_DIRTY_TRACKING) &&
!device_iommu_capable(idev->dev, IOMMU_CAP_DIRTY_TRACKING))
return ERR_PTR(-EOPNOTSUPP);
if ((flags & IOMMU_HWPT_FAULT_ID_VALID) &&
(flags & IOMMU_HWPT_ALLOC_NEST_PARENT))
return ERR_PTR(-EOPNOTSUPP);
hwpt_paging = __iommufd_object_alloc(
ictx, hwpt_paging, IOMMUFD_OBJ_HWPT_PAGING, common.obj);
if (IS_ERR(hwpt_paging))
return ERR_CAST(hwpt_paging);
hwpt = &hwpt_paging->common;
hwpt->pasid_compat = flags & IOMMU_HWPT_ALLOC_PASID;
INIT_LIST_HEAD(&hwpt_paging->hwpt_item);
/* Pairs with iommufd_hw_pagetable_destroy() */
refcount_inc(&ioas->obj.users);
hwpt_paging->ioas = ioas;
hwpt_paging->nest_parent = flags & IOMMU_HWPT_ALLOC_NEST_PARENT;
if (ops->domain_alloc_paging_flags) {
hwpt->domain = ops->domain_alloc_paging_flags(idev->dev,
flags & ~IOMMU_HWPT_FAULT_ID_VALID, user_data);
if (IS_ERR(hwpt->domain)) {
rc = PTR_ERR(hwpt->domain);
hwpt->domain = NULL;
goto out_abort;
}
hwpt->domain->owner = ops;
} else {
hwpt->domain = iommu_paging_domain_alloc(idev->dev);
if (IS_ERR(hwpt->domain)) {
rc = PTR_ERR(hwpt->domain);
hwpt->domain = NULL;
goto out_abort;
}
}
hwpt->domain->iommufd_hwpt = hwpt;
hwpt->domain->cookie_type = IOMMU_COOKIE_IOMMUFD;
/*
* Set the coherency mode before we do iopt_table_add_domain() as some
* iommus have a per-PTE bit that controls it and need to decide before
* doing any maps. It is an iommu driver bug to report
* IOMMU_CAP_ENFORCE_CACHE_COHERENCY but fail enforce_cache_coherency on
* a new domain.
*
* The cache coherency mode must be configured here and unchanged later.
* Note that a HWPT (non-CC) created for a device (non-CC) can be later
* reused by another device (either non-CC or CC). However, A HWPT (CC)
* created for a device (CC) cannot be reused by another device (non-CC)
* but only devices (CC). Instead user space in this case would need to
* allocate a separate HWPT (non-CC).
*/
if (idev->enforce_cache_coherency) {
rc = iommufd_hwpt_paging_enforce_cc(hwpt_paging);
if (WARN_ON(rc))
goto out_abort;
}
/*
* immediate_attach exists only to accommodate iommu drivers that cannot
* directly allocate a domain. These drivers do not finish creating the
* domain until attach is completed. Thus we must have this call
* sequence. Once those drivers are fixed this should be removed.
*/
if (immediate_attach) {
rc = iommufd_hw_pagetable_attach(hwpt, idev, pasid);
if (rc)
goto out_abort;
}
rc = iopt_table_add_domain(&ioas->iopt, hwpt->domain);
if (rc)
goto out_detach;
list_add_tail(&hwpt_paging->hwpt_item, &ioas->hwpt_list);
return hwpt_paging;
out_detach:
if (immediate_attach)
iommufd_hw_pagetable_detach(idev, pasid);
out_abort:
iommufd_object_abort_and_destroy(ictx, &hwpt->obj);
return ERR_PTR(rc);
}
/**
* iommufd_hwpt_nested_alloc() - Get a NESTED iommu_domain for a device
* @ictx: iommufd context
* @parent: Parent PAGING-type hwpt to associate the domain with
* @idev: Device to get an iommu_domain for
* @flags: Flags from userspace
* @user_data: user_data pointer. Must be valid
*
* Allocate a new iommu_domain (must be IOMMU_DOMAIN_NESTED) and return it as
* a NESTED hw_pagetable. The given parent PAGING-type hwpt must be capable of
* being a parent.
*/
static struct iommufd_hwpt_nested *
iommufd_hwpt_nested_alloc(struct iommufd_ctx *ictx,
struct iommufd_hwpt_paging *parent,
struct iommufd_device *idev, u32 flags,
const struct iommu_user_data *user_data)
{
const struct iommu_ops *ops = dev_iommu_ops(idev->dev);
struct iommufd_hwpt_nested *hwpt_nested;
struct iommufd_hw_pagetable *hwpt;
int rc;
if ((flags & ~(IOMMU_HWPT_FAULT_ID_VALID | IOMMU_HWPT_ALLOC_PASID)) ||
!user_data->len || !ops->domain_alloc_nested)
return ERR_PTR(-EOPNOTSUPP);
if (parent->auto_domain || !parent->nest_parent ||
parent->common.domain->owner != ops)
return ERR_PTR(-EINVAL);
hwpt_nested = __iommufd_object_alloc(
ictx, hwpt_nested, IOMMUFD_OBJ_HWPT_NESTED, common.obj);
if (IS_ERR(hwpt_nested))
return ERR_CAST(hwpt_nested);
hwpt = &hwpt_nested->common;
hwpt->pasid_compat = flags & IOMMU_HWPT_ALLOC_PASID;
refcount_inc(&parent->common.obj.users);
hwpt_nested->parent = parent;
hwpt->domain = ops->domain_alloc_nested(
idev->dev, parent->common.domain,
flags & ~IOMMU_HWPT_FAULT_ID_VALID, user_data);
if (IS_ERR(hwpt->domain)) {
rc = PTR_ERR(hwpt->domain);
hwpt->domain = NULL;
goto out_abort;
}
hwpt->domain->owner = ops;
hwpt->domain->iommufd_hwpt = hwpt;
hwpt->domain->cookie_type = IOMMU_COOKIE_IOMMUFD;
if (WARN_ON_ONCE(hwpt->domain->type != IOMMU_DOMAIN_NESTED)) {
rc = -EOPNOTSUPP;
goto out_abort;
}
return hwpt_nested;
out_abort:
iommufd_object_abort_and_destroy(ictx, &hwpt->obj);
return ERR_PTR(rc);
}
/**
* iommufd_viommu_alloc_hwpt_nested() - Get a hwpt_nested for a vIOMMU
* @viommu: vIOMMU ojbect to associate the hwpt_nested/domain with
* @flags: Flags from userspace
* @user_data: user_data pointer. Must be valid
*
* Allocate a new IOMMU_DOMAIN_NESTED for a vIOMMU and return it as a NESTED
* hw_pagetable.
*/
static struct iommufd_hwpt_nested *
iommufd_viommu_alloc_hwpt_nested(struct iommufd_viommu *viommu, u32 flags,
const struct iommu_user_data *user_data)
{
struct iommufd_hwpt_nested *hwpt_nested;
struct iommufd_hw_pagetable *hwpt;
int rc;
if (flags & ~(IOMMU_HWPT_FAULT_ID_VALID | IOMMU_HWPT_ALLOC_PASID))
return ERR_PTR(-EOPNOTSUPP);
if (!user_data->len)
return ERR_PTR(-EOPNOTSUPP);
if (!viommu->ops || !viommu->ops->alloc_domain_nested)
return ERR_PTR(-EOPNOTSUPP);
hwpt_nested = __iommufd_object_alloc(
viommu->ictx, hwpt_nested, IOMMUFD_OBJ_HWPT_NESTED, common.obj);
if (IS_ERR(hwpt_nested))
return ERR_CAST(hwpt_nested);
hwpt = &hwpt_nested->common;
hwpt->pasid_compat = flags & IOMMU_HWPT_ALLOC_PASID;
hwpt_nested->viommu = viommu;
refcount_inc(&viommu->obj.users);
hwpt_nested->parent = viommu->hwpt;
hwpt->domain = viommu->ops->alloc_domain_nested(
viommu, flags & ~IOMMU_HWPT_FAULT_ID_VALID, user_data);
if (IS_ERR(hwpt->domain)) {
rc = PTR_ERR(hwpt->domain);
hwpt->domain = NULL;
goto out_abort;
}
hwpt->domain->iommufd_hwpt = hwpt;
hwpt->domain->owner = viommu->iommu_dev->ops;
hwpt->domain->cookie_type = IOMMU_COOKIE_IOMMUFD;
if (WARN_ON_ONCE(hwpt->domain->type != IOMMU_DOMAIN_NESTED)) {
rc = -EOPNOTSUPP;
goto out_abort;
}
return hwpt_nested;
out_abort:
iommufd_object_abort_and_destroy(viommu->ictx, &hwpt->obj);
return ERR_PTR(rc);
}
int iommufd_hwpt_alloc(struct iommufd_ucmd *ucmd)
{
struct iommu_hwpt_alloc *cmd = ucmd->cmd;
const struct iommu_user_data user_data = {
.type = cmd->data_type,
.uptr = u64_to_user_ptr(cmd->data_uptr),
.len = cmd->data_len,
};
struct iommufd_hw_pagetable *hwpt;
struct iommufd_ioas *ioas = NULL;
struct iommufd_object *pt_obj;
struct iommufd_device *idev;
int rc;
if (cmd->__reserved)
return -EOPNOTSUPP;
if ((cmd->data_type == IOMMU_HWPT_DATA_NONE && cmd->data_len) ||
(cmd->data_type != IOMMU_HWPT_DATA_NONE && !cmd->data_len))
return -EINVAL;
idev = iommufd_get_device(ucmd, cmd->dev_id);
if (IS_ERR(idev))
return PTR_ERR(idev);
pt_obj = iommufd_get_object(ucmd->ictx, cmd->pt_id, IOMMUFD_OBJ_ANY);
if (IS_ERR(pt_obj)) {
rc = -EINVAL;
goto out_put_idev;
}
if (pt_obj->type == IOMMUFD_OBJ_IOAS) {
struct iommufd_hwpt_paging *hwpt_paging;
ioas = container_of(pt_obj, struct iommufd_ioas, obj);
mutex_lock(&ioas->mutex);
hwpt_paging = iommufd_hwpt_paging_alloc(
ucmd->ictx, ioas, idev, IOMMU_NO_PASID, cmd->flags,
false, user_data.len ? &user_data : NULL);
if (IS_ERR(hwpt_paging)) {
rc = PTR_ERR(hwpt_paging);
goto out_unlock;
}
hwpt = &hwpt_paging->common;
} else if (pt_obj->type == IOMMUFD_OBJ_HWPT_PAGING) {
struct iommufd_hwpt_nested *hwpt_nested;
hwpt_nested = iommufd_hwpt_nested_alloc(
ucmd->ictx,
container_of(pt_obj, struct iommufd_hwpt_paging,
common.obj),
idev, cmd->flags, &user_data);
if (IS_ERR(hwpt_nested)) {
rc = PTR_ERR(hwpt_nested);
goto out_unlock;
}
hwpt = &hwpt_nested->common;
} else if (pt_obj->type == IOMMUFD_OBJ_VIOMMU) {
struct iommufd_hwpt_nested *hwpt_nested;
struct iommufd_viommu *viommu;
viommu = container_of(pt_obj, struct iommufd_viommu, obj);
if (viommu->iommu_dev != __iommu_get_iommu_dev(idev->dev)) {
rc = -EINVAL;
goto out_unlock;
}
hwpt_nested = iommufd_viommu_alloc_hwpt_nested(
viommu, cmd->flags, &user_data);
if (IS_ERR(hwpt_nested)) {
rc = PTR_ERR(hwpt_nested);
goto out_unlock;
}
hwpt = &hwpt_nested->common;
} else {
rc = -EINVAL;
goto out_put_pt;
}
if (cmd->flags & IOMMU_HWPT_FAULT_ID_VALID) {
struct iommufd_fault *fault;
fault = iommufd_get_fault(ucmd, cmd->fault_id);
if (IS_ERR(fault)) {
rc = PTR_ERR(fault);
goto out_hwpt;
}
hwpt->fault = fault;
hwpt->domain->iopf_handler = iommufd_fault_iopf_handler;
refcount_inc(&fault->common.obj.users);
iommufd_put_object(ucmd->ictx, &fault->common.obj);
}
cmd->out_hwpt_id = hwpt->obj.id;
rc = iommufd_ucmd_respond(ucmd, sizeof(*cmd));
if (rc)
goto out_hwpt;
iommufd_object_finalize(ucmd->ictx, &hwpt->obj);
goto out_unlock;
out_hwpt:
iommufd_object_abort_and_destroy(ucmd->ictx, &hwpt->obj);
out_unlock:
if (ioas)
mutex_unlock(&ioas->mutex);
out_put_pt:
iommufd_put_object(ucmd->ictx, pt_obj);
out_put_idev:
iommufd_put_object(ucmd->ictx, &idev->obj);
return rc;
}
int iommufd_hwpt_set_dirty_tracking(struct iommufd_ucmd *ucmd)
{
struct iommu_hwpt_set_dirty_tracking *cmd = ucmd->cmd;
struct iommufd_hwpt_paging *hwpt_paging;
struct iommufd_ioas *ioas;
int rc = -EOPNOTSUPP;
bool enable;
if (cmd->flags & ~IOMMU_HWPT_DIRTY_TRACKING_ENABLE)
return rc;
hwpt_paging = iommufd_get_hwpt_paging(ucmd, cmd->hwpt_id);
if (IS_ERR(hwpt_paging))
return PTR_ERR(hwpt_paging);
ioas = hwpt_paging->ioas;
enable = cmd->flags & IOMMU_HWPT_DIRTY_TRACKING_ENABLE;
rc = iopt_set_dirty_tracking(&ioas->iopt, hwpt_paging->common.domain,
enable);
iommufd_put_object(ucmd->ictx, &hwpt_paging->common.obj);
return rc;
}
int iommufd_hwpt_get_dirty_bitmap(struct iommufd_ucmd *ucmd)
{
struct iommu_hwpt_get_dirty_bitmap *cmd = ucmd->cmd;
struct iommufd_hwpt_paging *hwpt_paging;
struct iommufd_ioas *ioas;
int rc = -EOPNOTSUPP;
if ((cmd->flags & ~(IOMMU_HWPT_GET_DIRTY_BITMAP_NO_CLEAR)) ||
cmd->__reserved)
return -EOPNOTSUPP;
hwpt_paging = iommufd_get_hwpt_paging(ucmd, cmd->hwpt_id);
if (IS_ERR(hwpt_paging))
return PTR_ERR(hwpt_paging);
ioas = hwpt_paging->ioas;
rc = iopt_read_and_clear_dirty_data(
&ioas->iopt, hwpt_paging->common.domain, cmd->flags, cmd);
iommufd_put_object(ucmd->ictx, &hwpt_paging->common.obj);
return rc;
}
/* An arbitrary entry_num cap, far above any realistic invalidation batch */
#define IOMMU_HWPT_INVALIDATE_ENTRY_NUM_MAX (1U << 19)
int iommufd_hwpt_invalidate(struct iommufd_ucmd *ucmd)
{
struct iommu_hwpt_invalidate *cmd = ucmd->cmd;
struct iommu_user_data_array data_array = {
.type = cmd->data_type,
.uptr = u64_to_user_ptr(cmd->data_uptr),
.entry_len = cmd->entry_len,
.entry_num = cmd->entry_num,
};
struct iommufd_object *pt_obj;
u32 done_num = 0;
int rc;
if (cmd->__reserved) {
rc = -EOPNOTSUPP;
goto out;
}
/*
* Bound entry_num and entry_len so a single call cannot pin the CPU;
* entry_len also caps the copy_struct_from_user() trailing-zero scan.
*/
if (cmd->entry_num &&
(!cmd->data_uptr || !cmd->entry_len || cmd->entry_len > PAGE_SIZE ||
cmd->entry_num > IOMMU_HWPT_INVALIDATE_ENTRY_NUM_MAX)) {
rc = -EINVAL;
goto out;
}
pt_obj = iommufd_get_object(ucmd->ictx, cmd->hwpt_id, IOMMUFD_OBJ_ANY);
if (IS_ERR(pt_obj)) {
rc = PTR_ERR(pt_obj);
goto out;
}
if (pt_obj->type == IOMMUFD_OBJ_HWPT_NESTED) {
struct iommufd_hw_pagetable *hwpt =
container_of(pt_obj, struct iommufd_hw_pagetable, obj);
if (!hwpt->domain->ops ||
!hwpt->domain->ops->cache_invalidate_user) {
rc = -EOPNOTSUPP;
goto out_put_pt;
}
rc = hwpt->domain->ops->cache_invalidate_user(hwpt->domain,
&data_array);
} else if (pt_obj->type == IOMMUFD_OBJ_VIOMMU) {
struct iommufd_viommu *viommu =
container_of(pt_obj, struct iommufd_viommu, obj);
if (!viommu->ops || !viommu->ops->cache_invalidate) {
rc = -EOPNOTSUPP;
goto out_put_pt;
}
rc = viommu->ops->cache_invalidate(viommu, &data_array);
} else {
rc = -EINVAL;
goto out_put_pt;
}
done_num = data_array.entry_num;
out_put_pt:
iommufd_put_object(ucmd->ictx, pt_obj);
out:
cmd->entry_num = done_num;
if (iommufd_ucmd_respond(ucmd, sizeof(*cmd)))
return -EFAULT;
return rc;
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,312 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* Copyright (c) 2021-2022, NVIDIA CORPORATION & AFFILIATES.
*
*/
#ifndef __IO_PAGETABLE_H
#define __IO_PAGETABLE_H
#include <linux/dma-buf.h>
#include <linux/interval_tree.h>
#include <linux/kref.h>
#include <linux/mutex.h>
#include <linux/xarray.h>
#include "iommufd_private.h"
struct iommu_domain;
/*
* Each io_pagetable is composed of intervals of areas which cover regions of
* the iova that are backed by something. iova not covered by areas is not
* populated in the page table. Each area is fully populated with pages.
*
* iovas are in byte units, but must be iopt->iova_alignment aligned.
*
* pages can be NULL, this means some other thread is still working on setting
* up or tearing down the area. When observed under the write side of the
* domain_rwsem a NULL pages must mean the area is still being setup and no
* domains are filled.
*
* storage_domain points at an arbitrary iommu_domain that is holding the PFNs
* for this area. It is locked by the pages->mutex. This simplifies the locking
* as the pages code can rely on the storage_domain without having to get the
* iopt->domains_rwsem.
*
* The io_pagetable::iova_rwsem protects node
* The iopt_pages::mutex protects pages_node
* iopt and iommu_prot are immutable
* The pages::mutex protects num_accesses
*/
struct iopt_area {
struct interval_tree_node node;
struct interval_tree_node pages_node;
struct io_pagetable *iopt;
struct iopt_pages *pages;
struct iommu_domain *storage_domain;
/* How many bytes into the first page the area starts */
unsigned int page_offset;
/* IOMMU_READ, IOMMU_WRITE, etc */
int iommu_prot;
bool prevent_access : 1;
unsigned int num_accesses;
unsigned int num_locks;
};
struct iopt_allowed {
struct interval_tree_node node;
};
struct iopt_reserved {
struct interval_tree_node node;
void *owner;
};
int iopt_area_fill_domains(struct iopt_area *area, struct iopt_pages *pages);
void iopt_area_unfill_domains(struct iopt_area *area, struct iopt_pages *pages);
int iopt_area_fill_domain(struct iopt_area *area, struct iommu_domain *domain);
void iopt_area_unfill_domain(struct iopt_area *area, struct iopt_pages *pages,
struct iommu_domain *domain);
void iopt_area_unmap_domain(struct iopt_area *area,
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)
{
return area->pages_node.start;
}
static inline unsigned long iopt_area_last_index(struct iopt_area *area)
{
return area->pages_node.last;
}
static inline unsigned long iopt_area_iova(struct iopt_area *area)
{
return area->node.start;
}
static inline unsigned long iopt_area_last_iova(struct iopt_area *area)
{
return area->node.last;
}
static inline size_t iopt_area_length(struct iopt_area *area)
{
return (area->node.last - area->node.start) + 1;
}
/*
* Number of bytes from the start of the iopt_pages that the iova begins.
* iopt_area_start_byte() / PAGE_SIZE encodes the starting page index
* iopt_area_start_byte() % PAGE_SIZE encodes the offset within that page
*/
static inline unsigned long iopt_area_start_byte(struct iopt_area *area,
unsigned long iova)
{
if (IS_ENABLED(CONFIG_IOMMUFD_TEST))
WARN_ON(iova < iopt_area_iova(area) ||
iova > iopt_area_last_iova(area));
return (iova - iopt_area_iova(area)) + area->page_offset +
iopt_area_index(area) * PAGE_SIZE;
}
static inline unsigned long iopt_area_iova_to_index(struct iopt_area *area,
unsigned long iova)
{
return iopt_area_start_byte(area, iova) / PAGE_SIZE;
}
#define __make_iopt_iter(name) \
static inline struct iopt_##name *iopt_##name##_iter_first( \
struct io_pagetable *iopt, unsigned long start, \
unsigned long last) \
{ \
struct interval_tree_node *node; \
\
lockdep_assert_held(&iopt->iova_rwsem); \
node = interval_tree_iter_first(&iopt->name##_itree, start, \
last); \
if (!node) \
return NULL; \
return container_of(node, struct iopt_##name, node); \
} \
static inline struct iopt_##name *iopt_##name##_iter_next( \
struct iopt_##name *last_node, unsigned long start, \
unsigned long last) \
{ \
struct interval_tree_node *node; \
\
node = interval_tree_iter_next(&last_node->node, start, last); \
if (!node) \
return NULL; \
return container_of(node, struct iopt_##name, node); \
}
__make_iopt_iter(area)
__make_iopt_iter(allowed)
__make_iopt_iter(reserved)
struct iopt_area_contig_iter {
unsigned long cur_iova;
unsigned long last_iova;
struct iopt_area *area;
};
struct iopt_area *iopt_area_contig_init(struct iopt_area_contig_iter *iter,
struct io_pagetable *iopt,
unsigned long iova,
unsigned long last_iova);
struct iopt_area *iopt_area_contig_next(struct iopt_area_contig_iter *iter);
static inline bool iopt_area_contig_done(struct iopt_area_contig_iter *iter)
{
return iter->area && iter->last_iova <= iopt_area_last_iova(iter->area);
}
/*
* Iterate over a contiguous list of areas that span the iova,last_iova range.
* The caller must check iopt_area_contig_done() after the loop to see if
* contiguous areas existed.
*/
#define iopt_for_each_contig_area(iter, area, iopt, iova, last_iova) \
for (area = iopt_area_contig_init(iter, iopt, iova, last_iova); area; \
area = iopt_area_contig_next(iter))
enum {
IOPT_PAGES_ACCOUNT_NONE = 0,
IOPT_PAGES_ACCOUNT_USER = 1,
IOPT_PAGES_ACCOUNT_MM = 2,
IOPT_PAGES_ACCOUNT_MODE_NUM = 3,
};
enum iopt_address_type {
IOPT_ADDRESS_USER = 0,
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 phys_vec phys;
/* Always PAGE_SIZE aligned */
unsigned long start;
struct list_head tracker;
};
/*
* This holds a pinned page list for multiple areas of IO address space. The
* pages always originate from a linear chunk of userspace VA. Multiple
* io_pagetable's, through their iopt_area's, can share a single iopt_pages
* which avoids multi-pinning and double accounting of page consumption.
*
* indexes in this structure are measured in PAGE_SIZE units, are 0 based from
* the start of the uptr and extend to npages. pages are pinned dynamically
* according to the intervals in the access_itree and domains_itree, npinned
* records the current number of pages pinned.
*/
struct iopt_pages {
struct kref kref;
struct mutex mutex;
size_t npages;
size_t npinned;
size_t last_npinned;
struct task_struct *source_task;
struct mm_struct *source_mm;
struct user_struct *source_user;
enum iopt_address_type type;
union {
void __user *uptr; /* IOPT_ADDRESS_USER */
struct { /* IOPT_ADDRESS_FILE */
struct file *file;
unsigned long start;
};
/* IOPT_ADDRESS_DMABUF */
struct iopt_pages_dmabuf dmabuf;
};
bool writable:1;
u8 account_mode;
struct xarray pinned_pfns;
/* Of iopt_pages_access::node */
struct rb_root_cached access_itree;
/* Of iopt_area::pages_node */
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,
unsigned long length, bool writable);
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);
void iopt_release_pages(struct kref *kref);
static inline void iopt_put_pages(struct iopt_pages *pages)
{
kref_put(&pages->kref, iopt_release_pages);
}
void iopt_pages_fill_from_xarray(struct iopt_pages *pages, unsigned long start,
unsigned long last, struct page **out_pages);
int iopt_pages_fill_xarray(struct iopt_pages *pages, unsigned long start,
unsigned long last, struct page **out_pages);
void iopt_pages_unfill_xarray(struct iopt_pages *pages, unsigned long start,
unsigned long last);
int iopt_area_add_access(struct iopt_area *area, unsigned long start,
unsigned long last, struct page **out_pages,
unsigned int flags, bool lock_area);
void iopt_area_remove_access(struct iopt_area *area, unsigned long start,
unsigned long last, bool unlock_area);
int iopt_pages_rw_access(struct iopt_pages *pages, unsigned long start_byte,
void *data, unsigned long length, unsigned int flags);
/*
* Each interval represents an active iopt_access_pages(), it acts as an
* interval lock that keeps the PFNs pinned and stored in the xarray.
*/
struct iopt_pages_access {
struct interval_tree_node node;
unsigned int users;
};
struct pfn_reader_user;
int iopt_pages_update_pinned(struct iopt_pages *pages, unsigned long npages,
bool inc, struct pfn_reader_user *user);
#endif
+667
View File
@@ -0,0 +1,667 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2021-2022, NVIDIA CORPORATION & AFFILIATES
*/
#include <linux/file.h>
#include <linux/interval_tree.h>
#include <linux/iommu.h>
#include <linux/iommufd.h>
#include <uapi/linux/iommufd.h>
#include "io_pagetable.h"
void iommufd_ioas_destroy(struct iommufd_object *obj)
{
struct iommufd_ioas *ioas = container_of(obj, struct iommufd_ioas, obj);
int rc;
rc = iopt_unmap_all(&ioas->iopt, NULL);
WARN_ON(rc && rc != -ENOENT);
iopt_destroy_table(&ioas->iopt);
mutex_destroy(&ioas->mutex);
}
struct iommufd_ioas *iommufd_ioas_alloc(struct iommufd_ctx *ictx)
{
struct iommufd_ioas *ioas;
ioas = iommufd_object_alloc(ictx, ioas, IOMMUFD_OBJ_IOAS);
if (IS_ERR(ioas))
return ioas;
iopt_init_table(&ioas->iopt);
INIT_LIST_HEAD(&ioas->hwpt_list);
mutex_init(&ioas->mutex);
return ioas;
}
int iommufd_ioas_alloc_ioctl(struct iommufd_ucmd *ucmd)
{
struct iommu_ioas_alloc *cmd = ucmd->cmd;
struct iommufd_ioas *ioas;
int rc;
if (cmd->flags)
return -EOPNOTSUPP;
ioas = iommufd_ioas_alloc(ucmd->ictx);
if (IS_ERR(ioas))
return PTR_ERR(ioas);
cmd->out_ioas_id = ioas->obj.id;
rc = iommufd_ucmd_respond(ucmd, sizeof(*cmd));
if (rc)
goto out_table;
down_read(&ucmd->ictx->ioas_creation_lock);
iommufd_object_finalize(ucmd->ictx, &ioas->obj);
up_read(&ucmd->ictx->ioas_creation_lock);
return 0;
out_table:
iommufd_object_abort_and_destroy(ucmd->ictx, &ioas->obj);
return rc;
}
int iommufd_ioas_iova_ranges(struct iommufd_ucmd *ucmd)
{
struct iommu_iova_range __user *ranges;
struct iommu_ioas_iova_ranges *cmd = ucmd->cmd;
struct iommufd_ioas *ioas;
struct interval_tree_span_iter span;
u32 max_iovas;
int rc;
if (cmd->__reserved)
return -EOPNOTSUPP;
ioas = iommufd_get_ioas(ucmd->ictx, cmd->ioas_id);
if (IS_ERR(ioas))
return PTR_ERR(ioas);
down_read(&ioas->iopt.iova_rwsem);
max_iovas = cmd->num_iovas;
ranges = u64_to_user_ptr(cmd->allowed_iovas);
cmd->num_iovas = 0;
cmd->out_iova_alignment = ioas->iopt.iova_alignment;
interval_tree_for_each_span(&span, &ioas->iopt.reserved_itree, 0,
ULONG_MAX) {
if (!span.is_hole)
continue;
if (cmd->num_iovas < max_iovas) {
struct iommu_iova_range elm = {
.start = span.start_hole,
.last = span.last_hole,
};
if (copy_to_user(&ranges[cmd->num_iovas], &elm,
sizeof(elm))) {
rc = -EFAULT;
goto out_put;
}
}
cmd->num_iovas++;
}
rc = iommufd_ucmd_respond(ucmd, sizeof(*cmd));
if (rc)
goto out_put;
if (cmd->num_iovas > max_iovas)
rc = -EMSGSIZE;
out_put:
up_read(&ioas->iopt.iova_rwsem);
iommufd_put_object(ucmd->ictx, &ioas->obj);
return rc;
}
static int iommufd_ioas_load_iovas(struct rb_root_cached *itree,
struct iommu_iova_range __user *ranges,
u32 num)
{
u32 i;
for (i = 0; i != num; i++) {
struct iommu_iova_range range;
struct iopt_allowed *allowed;
if (copy_from_user(&range, ranges + i, sizeof(range)))
return -EFAULT;
if (range.start >= range.last)
return -EINVAL;
if (interval_tree_iter_first(itree, range.start, range.last))
return -EINVAL;
allowed = kzalloc_obj(*allowed, GFP_KERNEL_ACCOUNT);
if (!allowed)
return -ENOMEM;
allowed->node.start = range.start;
allowed->node.last = range.last;
interval_tree_insert(&allowed->node, itree);
}
return 0;
}
int iommufd_ioas_allow_iovas(struct iommufd_ucmd *ucmd)
{
struct iommu_ioas_allow_iovas *cmd = ucmd->cmd;
struct rb_root_cached allowed_iova = RB_ROOT_CACHED;
struct interval_tree_node *node;
struct iommufd_ioas *ioas;
struct io_pagetable *iopt;
int rc = 0;
if (cmd->__reserved)
return -EOPNOTSUPP;
ioas = iommufd_get_ioas(ucmd->ictx, cmd->ioas_id);
if (IS_ERR(ioas))
return PTR_ERR(ioas);
iopt = &ioas->iopt;
rc = iommufd_ioas_load_iovas(&allowed_iova,
u64_to_user_ptr(cmd->allowed_iovas),
cmd->num_iovas);
if (rc)
goto out_free;
/*
* We want the allowed tree update to be atomic, so we have to keep the
* original nodes around, and keep track of the new nodes as we allocate
* memory for them. The simplest solution is to have a new/old tree and
* then swap new for old. On success we free the old tree, on failure we
* free the new tree.
*/
rc = iopt_set_allow_iova(iopt, &allowed_iova);
out_free:
while ((node = interval_tree_iter_first(&allowed_iova, 0, ULONG_MAX))) {
interval_tree_remove(node, &allowed_iova);
kfree(container_of(node, struct iopt_allowed, node));
}
iommufd_put_object(ucmd->ictx, &ioas->obj);
return rc;
}
static int conv_iommu_prot(u32 map_flags)
{
/*
* We provide no manual cache coherency ioctls to userspace and most
* architectures make the CPU ops for cache flushing privileged.
* Therefore we require the underlying IOMMU to support CPU coherent
* operation. Support for IOMMU_CACHE is enforced by the
* IOMMU_CAP_CACHE_COHERENCY test during bind.
*/
int iommu_prot = IOMMU_CACHE;
if (map_flags & IOMMU_IOAS_MAP_WRITEABLE)
iommu_prot |= IOMMU_WRITE;
if (map_flags & IOMMU_IOAS_MAP_READABLE)
iommu_prot |= IOMMU_READ;
return iommu_prot;
}
int iommufd_ioas_map_file(struct iommufd_ucmd *ucmd)
{
struct iommu_ioas_map_file *cmd = ucmd->cmd;
unsigned long iova = cmd->iova;
struct iommufd_ioas *ioas;
unsigned int flags = 0;
int rc;
if (cmd->flags &
~(IOMMU_IOAS_MAP_FIXED_IOVA | IOMMU_IOAS_MAP_WRITEABLE |
IOMMU_IOAS_MAP_READABLE))
return -EOPNOTSUPP;
if (cmd->iova >= ULONG_MAX || cmd->length >= ULONG_MAX)
return -EOVERFLOW;
if (!(cmd->flags &
(IOMMU_IOAS_MAP_WRITEABLE | IOMMU_IOAS_MAP_READABLE)))
return -EINVAL;
ioas = iommufd_get_ioas(ucmd->ictx, cmd->ioas_id);
if (IS_ERR(ioas))
return PTR_ERR(ioas);
if (!(cmd->flags & IOMMU_IOAS_MAP_FIXED_IOVA))
flags = IOPT_ALLOC_IOVA;
rc = iopt_map_file_pages(ucmd->ictx, &ioas->iopt, &iova, cmd->fd,
cmd->start, cmd->length,
conv_iommu_prot(cmd->flags), flags);
if (rc)
goto out_put;
cmd->iova = iova;
rc = iommufd_ucmd_respond(ucmd, sizeof(*cmd));
out_put:
iommufd_put_object(ucmd->ictx, &ioas->obj);
return rc;
}
int iommufd_ioas_map(struct iommufd_ucmd *ucmd)
{
struct iommu_ioas_map *cmd = ucmd->cmd;
unsigned long iova = cmd->iova;
struct iommufd_ioas *ioas;
unsigned int flags = 0;
int rc;
if ((cmd->flags &
~(IOMMU_IOAS_MAP_FIXED_IOVA | IOMMU_IOAS_MAP_WRITEABLE |
IOMMU_IOAS_MAP_READABLE)) ||
cmd->__reserved)
return -EOPNOTSUPP;
if (cmd->iova >= ULONG_MAX || cmd->length >= ULONG_MAX)
return -EOVERFLOW;
if (!(cmd->flags &
(IOMMU_IOAS_MAP_WRITEABLE | IOMMU_IOAS_MAP_READABLE)))
return -EINVAL;
ioas = iommufd_get_ioas(ucmd->ictx, cmd->ioas_id);
if (IS_ERR(ioas))
return PTR_ERR(ioas);
if (!(cmd->flags & IOMMU_IOAS_MAP_FIXED_IOVA))
flags = IOPT_ALLOC_IOVA;
rc = iopt_map_user_pages(ucmd->ictx, &ioas->iopt, &iova,
u64_to_user_ptr(cmd->user_va), cmd->length,
conv_iommu_prot(cmd->flags), flags);
if (rc)
goto out_put;
cmd->iova = iova;
rc = iommufd_ucmd_respond(ucmd, sizeof(*cmd));
out_put:
iommufd_put_object(ucmd->ictx, &ioas->obj);
return rc;
}
int iommufd_ioas_copy(struct iommufd_ucmd *ucmd)
{
struct iommu_ioas_copy *cmd = ucmd->cmd;
struct iommufd_ioas *src_ioas;
struct iommufd_ioas *dst_ioas;
unsigned int flags = 0;
LIST_HEAD(pages_list);
unsigned long iova;
int rc;
iommufd_test_syz_conv_iova_id(ucmd, cmd->src_ioas_id, &cmd->src_iova,
&cmd->flags);
if ((cmd->flags &
~(IOMMU_IOAS_MAP_FIXED_IOVA | IOMMU_IOAS_MAP_WRITEABLE |
IOMMU_IOAS_MAP_READABLE)))
return -EOPNOTSUPP;
if (cmd->length >= ULONG_MAX || cmd->src_iova >= ULONG_MAX ||
cmd->dst_iova >= ULONG_MAX)
return -EOVERFLOW;
if (!(cmd->flags &
(IOMMU_IOAS_MAP_WRITEABLE | IOMMU_IOAS_MAP_READABLE)))
return -EINVAL;
src_ioas = iommufd_get_ioas(ucmd->ictx, cmd->src_ioas_id);
if (IS_ERR(src_ioas))
return PTR_ERR(src_ioas);
rc = iopt_get_pages(&src_ioas->iopt, cmd->src_iova, cmd->length,
&pages_list);
iommufd_put_object(ucmd->ictx, &src_ioas->obj);
if (rc)
return rc;
dst_ioas = iommufd_get_ioas(ucmd->ictx, cmd->dst_ioas_id);
if (IS_ERR(dst_ioas)) {
rc = PTR_ERR(dst_ioas);
goto out_pages;
}
if (!(cmd->flags & IOMMU_IOAS_MAP_FIXED_IOVA))
flags = IOPT_ALLOC_IOVA;
iova = cmd->dst_iova;
rc = iopt_map_pages(&dst_ioas->iopt, &pages_list, cmd->length, &iova,
conv_iommu_prot(cmd->flags), flags);
if (rc)
goto out_put_dst;
cmd->dst_iova = iova;
rc = iommufd_ucmd_respond(ucmd, sizeof(*cmd));
out_put_dst:
iommufd_put_object(ucmd->ictx, &dst_ioas->obj);
out_pages:
iopt_free_pages_list(&pages_list);
return rc;
}
int iommufd_ioas_unmap(struct iommufd_ucmd *ucmd)
{
struct iommu_ioas_unmap *cmd = ucmd->cmd;
struct iommufd_ioas *ioas;
unsigned long unmapped = 0;
int rc;
ioas = iommufd_get_ioas(ucmd->ictx, cmd->ioas_id);
if (IS_ERR(ioas))
return PTR_ERR(ioas);
if (cmd->iova == 0 && cmd->length == U64_MAX) {
rc = iopt_unmap_all(&ioas->iopt, &unmapped);
if (rc)
goto out_put;
} else {
if (cmd->iova >= ULONG_MAX || cmd->length >= ULONG_MAX) {
rc = -EOVERFLOW;
goto out_put;
}
rc = iopt_unmap_iova(&ioas->iopt, cmd->iova, cmd->length,
&unmapped);
if (rc)
goto out_put;
if (!unmapped) {
rc = -ENOENT;
goto out_put;
}
}
cmd->length = unmapped;
rc = iommufd_ucmd_respond(ucmd, sizeof(*cmd));
out_put:
iommufd_put_object(ucmd->ictx, &ioas->obj);
return rc;
}
static void iommufd_release_all_iova_rwsem(struct iommufd_ctx *ictx,
struct xarray *ioas_list)
{
struct iommufd_ioas *ioas;
unsigned long index;
xa_for_each(ioas_list, index, ioas) {
up_write(&ioas->iopt.iova_rwsem);
refcount_dec(&ioas->obj.users);
}
up_write(&ictx->ioas_creation_lock);
xa_destroy(ioas_list);
}
static int iommufd_take_all_iova_rwsem(struct iommufd_ctx *ictx,
struct xarray *ioas_list)
{
struct iommufd_object *obj;
unsigned long index;
int rc;
/*
* This is very ugly, it is done instead of adding a lock around
* pages->source_mm, which is a performance path for mdev, we just
* obtain the write side of all the iova_rwsems which also protects the
* pages->source_*. Due to copies we can't know which IOAS could read
* from the pages, so we just lock everything. This is the only place
* locks are nested and they are uniformly taken in ID order.
*
* ioas_creation_lock prevents new IOAS from being installed in the
* xarray while we do this, and also prevents more than one thread from
* holding nested locks.
*/
down_write(&ictx->ioas_creation_lock);
xa_lock(&ictx->objects);
xa_for_each(&ictx->objects, index, obj) {
struct iommufd_ioas *ioas;
if (!obj || obj->type != IOMMUFD_OBJ_IOAS)
continue;
if (!refcount_inc_not_zero(&obj->users))
continue;
xa_unlock(&ictx->objects);
ioas = container_of(obj, struct iommufd_ioas, obj);
down_write_nest_lock(&ioas->iopt.iova_rwsem,
&ictx->ioas_creation_lock);
rc = xa_err(xa_store(ioas_list, index, ioas, GFP_KERNEL));
if (rc) {
iommufd_release_all_iova_rwsem(ictx, ioas_list);
return rc;
}
xa_lock(&ictx->objects);
}
xa_unlock(&ictx->objects);
return 0;
}
static bool need_charge_update(struct iopt_pages *pages)
{
switch (pages->account_mode) {
case IOPT_PAGES_ACCOUNT_NONE:
return false;
case IOPT_PAGES_ACCOUNT_MM:
return pages->source_mm != current->mm;
case IOPT_PAGES_ACCOUNT_USER:
/*
* Update when mm changes because it also accounts
* in mm->pinned_vm.
*/
return (pages->source_user != current_user()) ||
(pages->source_mm != current->mm);
}
return true;
}
static int charge_current(unsigned long *npinned)
{
struct iopt_pages tmp = {
.source_mm = current->mm,
.source_task = current->group_leader,
.source_user = current_user(),
};
unsigned int account_mode;
int rc;
for (account_mode = 0; account_mode != IOPT_PAGES_ACCOUNT_MODE_NUM;
account_mode++) {
if (!npinned[account_mode])
continue;
tmp.account_mode = account_mode;
rc = iopt_pages_update_pinned(&tmp, npinned[account_mode], true,
NULL);
if (rc)
goto err_undo;
}
return 0;
err_undo:
while (account_mode != 0) {
account_mode--;
if (!npinned[account_mode])
continue;
tmp.account_mode = account_mode;
iopt_pages_update_pinned(&tmp, npinned[account_mode], false,
NULL);
}
return rc;
}
static void change_mm(struct iopt_pages *pages)
{
struct task_struct *old_task = pages->source_task;
struct user_struct *old_user = pages->source_user;
struct mm_struct *old_mm = pages->source_mm;
pages->source_mm = current->mm;
mmgrab(pages->source_mm);
mmdrop(old_mm);
pages->source_task = current->group_leader;
get_task_struct(pages->source_task);
put_task_struct(old_task);
pages->source_user = get_uid(current_user());
free_uid(old_user);
}
#define for_each_ioas_area(_xa, _index, _ioas, _area) \
xa_for_each((_xa), (_index), (_ioas)) \
for (_area = iopt_area_iter_first(&_ioas->iopt, 0, ULONG_MAX); \
_area; \
_area = iopt_area_iter_next(_area, 0, ULONG_MAX))
int iommufd_ioas_change_process(struct iommufd_ucmd *ucmd)
{
struct iommu_ioas_change_process *cmd = ucmd->cmd;
struct iommufd_ctx *ictx = ucmd->ictx;
unsigned long all_npinned[IOPT_PAGES_ACCOUNT_MODE_NUM] = {};
struct iommufd_ioas *ioas;
struct iopt_area *area;
struct iopt_pages *pages;
struct xarray ioas_list;
unsigned long index;
int rc;
if (cmd->__reserved)
return -EOPNOTSUPP;
xa_init(&ioas_list);
rc = iommufd_take_all_iova_rwsem(ictx, &ioas_list);
if (rc)
return rc;
for_each_ioas_area(&ioas_list, index, ioas, area) {
if (!area->pages) {
rc = -EBUSY;
goto out;
}
if (area->pages->type != IOPT_ADDRESS_FILE) {
rc = -EINVAL;
goto out;
}
}
/*
* Count last_pinned pages, then clear it to avoid double counting
* if the same iopt_pages is visited multiple times in this loop.
* Since we are under all the locks, npinned == last_npinned, so we
* can easily restore last_npinned before we return.
*/
for_each_ioas_area(&ioas_list, index, ioas, area) {
pages = area->pages;
if (need_charge_update(pages)) {
all_npinned[pages->account_mode] += pages->last_npinned;
pages->last_npinned = 0;
}
}
rc = charge_current(all_npinned);
if (rc) {
/* Charge failed. Fix last_npinned and bail. */
for_each_ioas_area(&ioas_list, index, ioas, area)
area->pages->last_npinned = area->pages->npinned;
goto out;
}
for_each_ioas_area(&ioas_list, index, ioas, area) {
pages = area->pages;
/* Uncharge the old one (which also restores last_npinned) */
if (need_charge_update(pages)) {
int r = iopt_pages_update_pinned(pages, pages->npinned,
false, NULL);
if (WARN_ON(r))
rc = r;
}
change_mm(pages);
}
out:
iommufd_release_all_iova_rwsem(ictx, &ioas_list);
return rc;
}
int iommufd_option_rlimit_mode(struct iommu_option *cmd,
struct iommufd_ctx *ictx)
{
if (cmd->object_id)
return -EOPNOTSUPP;
if (cmd->op == IOMMU_OPTION_OP_GET) {
cmd->val64 = ictx->account_mode == IOPT_PAGES_ACCOUNT_MM;
return 0;
}
if (cmd->op == IOMMU_OPTION_OP_SET) {
int rc = 0;
if (!capable(CAP_SYS_RESOURCE))
return -EPERM;
xa_lock(&ictx->objects);
if (!xa_empty(&ictx->objects)) {
rc = -EBUSY;
} else {
if (cmd->val64 == 0)
ictx->account_mode = IOPT_PAGES_ACCOUNT_USER;
else if (cmd->val64 == 1)
ictx->account_mode = IOPT_PAGES_ACCOUNT_MM;
else
rc = -EINVAL;
}
xa_unlock(&ictx->objects);
return rc;
}
return -EOPNOTSUPP;
}
static int iommufd_ioas_option_huge_pages(struct iommu_option *cmd,
struct iommufd_ioas *ioas)
{
if (cmd->op == IOMMU_OPTION_OP_GET) {
cmd->val64 = !ioas->iopt.disable_large_pages;
return 0;
}
if (cmd->op == IOMMU_OPTION_OP_SET) {
if (cmd->val64 == 0)
return iopt_disable_large_pages(&ioas->iopt);
if (cmd->val64 == 1) {
iopt_enable_large_pages(&ioas->iopt);
return 0;
}
return -EINVAL;
}
return -EOPNOTSUPP;
}
int iommufd_ioas_option(struct iommufd_ucmd *ucmd)
{
struct iommu_option *cmd = ucmd->cmd;
struct iommufd_ioas *ioas;
int rc = 0;
if (cmd->__reserved)
return -EOPNOTSUPP;
ioas = iommufd_get_ioas(ucmd->ictx, cmd->object_id);
if (IS_ERR(ioas))
return PTR_ERR(ioas);
switch (cmd->option_id) {
case IOMMU_OPTION_HUGE_PAGES:
rc = iommufd_ioas_option_huge_pages(cmd, ioas);
break;
default:
rc = -EOPNOTSUPP;
}
iommufd_put_object(ucmd->ictx, &ioas->obj);
return rc;
}
@@ -0,0 +1,749 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/* Copyright (c) 2021-2022, NVIDIA CORPORATION & AFFILIATES
*/
#ifndef __IOMMUFD_PRIVATE_H
#define __IOMMUFD_PRIVATE_H
#include <linux/iommu.h>
#include <linux/iommufd.h>
#include <linux/iova_bitmap.h>
#include <linux/maple_tree.h>
#include <linux/rwsem.h>
#include <linux/uaccess.h>
#include <linux/xarray.h>
#include <uapi/linux/iommufd.h>
#include "../iommu-priv.h"
struct iommu_domain;
struct iommu_group;
struct iommu_option;
struct iommufd_device;
struct dma_buf_attachment;
struct iommufd_sw_msi_map {
struct list_head sw_msi_item;
phys_addr_t sw_msi_start;
phys_addr_t msi_addr;
unsigned int pgoff;
unsigned int id;
};
/* Bitmap of struct iommufd_sw_msi_map::id */
struct iommufd_sw_msi_maps {
DECLARE_BITMAP(bitmap, 64);
};
#ifdef CONFIG_IRQ_MSI_IOMMU
int iommufd_sw_msi_install(struct iommufd_ctx *ictx,
struct iommufd_hwpt_paging *hwpt_paging,
struct iommufd_sw_msi_map *msi_map);
#endif
struct iommufd_ctx {
struct file *file;
struct xarray objects;
struct xarray groups;
wait_queue_head_t destroy_wait;
struct rw_semaphore ioas_creation_lock;
struct maple_tree mt_mmap;
struct mutex sw_msi_lock;
struct list_head sw_msi_list;
unsigned int sw_msi_id;
u8 account_mode;
/* Compatibility with VFIO no iommu */
u8 no_iommu_mode;
struct iommufd_ioas *vfio_ioas;
};
/* Entry for iommufd_ctx::mt_mmap */
struct iommufd_mmap {
struct iommufd_object *owner;
/* Page-shifted start position in mt_mmap to validate vma->vm_pgoff */
unsigned long vm_pgoff;
/* Physical range for io_remap_pfn_range() */
phys_addr_t mmio_addr;
size_t length;
};
/*
* The IOVA to PFN map. The map automatically copies the PFNs into multiple
* domains and permits sharing of PFNs between io_pagetable instances. This
* supports both a design where IOAS's are 1:1 with a domain (eg because the
* domain is HW customized), or where the IOAS is 1:N with multiple generic
* domains. The io_pagetable holds an interval tree of iopt_areas which point
* to shared iopt_pages which hold the pfns mapped to the page table.
*
* The locking order is domains_rwsem -> iova_rwsem -> pages::mutex
*/
struct io_pagetable {
struct rw_semaphore domains_rwsem;
struct xarray domains;
struct xarray access_list;
unsigned int next_domain_id;
struct rw_semaphore iova_rwsem;
struct rb_root_cached area_itree;
/* IOVA that cannot become reserved, struct iopt_allowed */
struct rb_root_cached allowed_itree;
/* IOVA that cannot be allocated, struct iopt_reserved */
struct rb_root_cached reserved_itree;
u8 disable_large_pages;
unsigned long iova_alignment;
};
void iopt_init_table(struct io_pagetable *iopt);
void iopt_destroy_table(struct io_pagetable *iopt);
int iopt_get_pages(struct io_pagetable *iopt, unsigned long iova,
unsigned long length, struct list_head *pages_list);
void iopt_free_pages_list(struct list_head *pages_list);
enum {
IOPT_ALLOC_IOVA = 1 << 0,
};
int iopt_map_user_pages(struct iommufd_ctx *ictx, struct io_pagetable *iopt,
unsigned long *iova, void __user *uptr,
unsigned long length, int iommu_prot,
unsigned int flags);
int iopt_map_file_pages(struct iommufd_ctx *ictx, struct io_pagetable *iopt,
unsigned long *iova, int fd,
unsigned long start, unsigned long length,
int iommu_prot, unsigned int flags);
int iopt_map_pages(struct io_pagetable *iopt, struct list_head *pages_list,
unsigned long length, unsigned long *dst_iova,
int iommu_prot, unsigned int flags);
int iopt_unmap_iova(struct io_pagetable *iopt, unsigned long iova,
unsigned long length, unsigned long *unmapped);
int iopt_unmap_all(struct io_pagetable *iopt, unsigned long *unmapped);
int iopt_read_and_clear_dirty_data(struct io_pagetable *iopt,
struct iommu_domain *domain,
unsigned long flags,
struct iommu_hwpt_get_dirty_bitmap *bitmap);
int iopt_set_dirty_tracking(struct io_pagetable *iopt,
struct iommu_domain *domain, bool enable);
void iommufd_access_notify_unmap(struct io_pagetable *iopt, unsigned long iova,
unsigned long length);
int iopt_table_add_domain(struct io_pagetable *iopt,
struct iommu_domain *domain);
void iopt_table_remove_domain(struct io_pagetable *iopt,
struct iommu_domain *domain);
int iopt_table_enforce_dev_resv_regions(struct io_pagetable *iopt,
struct device *dev,
phys_addr_t *sw_msi_start);
int iopt_set_allow_iova(struct io_pagetable *iopt,
struct rb_root_cached *allowed_iova);
int iopt_reserve_iova(struct io_pagetable *iopt, unsigned long start,
unsigned long last, void *owner);
void iopt_remove_reserved_iova(struct io_pagetable *iopt, void *owner);
int iopt_cut_iova(struct io_pagetable *iopt, unsigned long *iovas,
size_t num_iovas);
void iopt_enable_large_pages(struct io_pagetable *iopt);
int iopt_disable_large_pages(struct io_pagetable *iopt);
struct iommufd_ucmd {
struct iommufd_ctx *ictx;
void __user *ubuffer;
u32 user_size;
void *cmd;
struct iommufd_object *new_obj;
};
int iommufd_vfio_ioctl(struct iommufd_ctx *ictx, unsigned int cmd,
unsigned long arg);
/* Copy the response in ucmd->cmd back to userspace. */
static inline int iommufd_ucmd_respond(struct iommufd_ucmd *ucmd,
size_t cmd_len)
{
if (copy_to_user(ucmd->ubuffer, ucmd->cmd,
min_t(size_t, ucmd->user_size, cmd_len)))
return -EFAULT;
return 0;
}
static inline bool iommufd_lock_obj(struct iommufd_object *obj)
{
if (!refcount_inc_not_zero(&obj->users))
return false;
if (!refcount_inc_not_zero(&obj->wait_cnt)) {
/*
* If the caller doesn't already have a ref on obj this must be
* called under the xa_lock. Otherwise the caller is holding a
* ref on users. Thus it cannot be one before this decrement.
*/
refcount_dec(&obj->users);
return false;
}
return true;
}
struct iommufd_object *iommufd_get_object(struct iommufd_ctx *ictx, u32 id,
enum iommufd_object_type type);
static inline void iommufd_put_object(struct iommufd_ctx *ictx,
struct iommufd_object *obj)
{
/*
* Users first, then wait_cnt so that REMOVE_WAIT never sees a spurious
* !0 users with a 0 wait_cnt.
*/
refcount_dec(&obj->users);
if (refcount_dec_and_test(&obj->wait_cnt))
wake_up_interruptible_all(&ictx->destroy_wait);
}
void iommufd_object_abort(struct iommufd_ctx *ictx, struct iommufd_object *obj);
void iommufd_object_abort_and_destroy(struct iommufd_ctx *ictx,
struct iommufd_object *obj);
void iommufd_object_finalize(struct iommufd_ctx *ictx,
struct iommufd_object *obj);
enum {
REMOVE_WAIT = BIT(0),
REMOVE_OBJ_TOMBSTONE = BIT(1),
};
int iommufd_object_remove(struct iommufd_ctx *ictx,
struct iommufd_object *to_destroy, u32 id,
unsigned int flags);
/*
* The caller holds a users refcount and wants to destroy the object. At this
* point the caller has no wait_cnt reference and at least the xarray will be
* holding one.
*/
static inline void iommufd_object_destroy_user(struct iommufd_ctx *ictx,
struct iommufd_object *obj)
{
int ret;
ret = iommufd_object_remove(ictx, obj, obj->id, REMOVE_WAIT);
/*
* If there is a bug and we couldn't destroy the object then we did put
* back the caller's users refcount and will eventually try to free it
* again during close.
*/
WARN_ON(ret);
}
/*
* Similar to iommufd_object_destroy_user(), except that the object ID is left
* reserved/tombstoned.
*/
static inline void iommufd_object_tombstone_user(struct iommufd_ctx *ictx,
struct iommufd_object *obj)
{
int ret;
ret = iommufd_object_remove(ictx, obj, obj->id,
REMOVE_WAIT | REMOVE_OBJ_TOMBSTONE);
/*
* If there is a bug and we couldn't destroy the object then we did put
* back the caller's users refcount and will eventually try to free it
* again during close.
*/
WARN_ON(ret);
}
/*
* The HWPT allocated by autodomains is used in possibly many devices and
* is automatically destroyed when its refcount reaches zero.
*
* If userspace uses the HWPT manually, even for a short term, then it will
* disrupt this refcounting and the auto-free in the kernel will not work.
* Userspace that tries to use the automatically allocated HWPT must be careful
* to ensure that it is consistently destroyed, eg by not racing accesses
* and by not attaching an automatic HWPT to a device manually.
*/
static inline void
iommufd_object_put_and_try_destroy(struct iommufd_ctx *ictx,
struct iommufd_object *obj)
{
iommufd_object_remove(ictx, obj, obj->id, 0);
}
/*
* Callers of these normal object allocators must call iommufd_object_finalize()
* to finalize the object, or call iommufd_object_abort_and_destroy() to revert
* the allocation.
*/
struct iommufd_object *_iommufd_object_alloc(struct iommufd_ctx *ictx,
size_t size,
enum iommufd_object_type type);
#define __iommufd_object_alloc(ictx, ptr, type, obj) \
container_of(_iommufd_object_alloc( \
ictx, \
sizeof(*(ptr)) + BUILD_BUG_ON_ZERO( \
offsetof(typeof(*(ptr)), \
obj) != 0), \
type), \
typeof(*(ptr)), obj)
#define iommufd_object_alloc(ictx, ptr, type) \
__iommufd_object_alloc(ictx, ptr, type, obj)
/*
* Callers of these _ucmd allocators should not call iommufd_object_finalize()
* or iommufd_object_abort_and_destroy(), as the core automatically does that.
*/
struct iommufd_object *
_iommufd_object_alloc_ucmd(struct iommufd_ucmd *ucmd, size_t size,
enum iommufd_object_type type);
#define __iommufd_object_alloc_ucmd(ucmd, ptr, type, obj) \
container_of(_iommufd_object_alloc_ucmd( \
ucmd, \
sizeof(*(ptr)) + BUILD_BUG_ON_ZERO( \
offsetof(typeof(*(ptr)), \
obj) != 0), \
type), \
typeof(*(ptr)), obj)
#define iommufd_object_alloc_ucmd(ucmd, ptr, type) \
__iommufd_object_alloc_ucmd(ucmd, ptr, type, obj)
/*
* The IO Address Space (IOAS) pagetable is a virtual page table backed by the
* io_pagetable object. It is a user controlled mapping of IOVA -> PFNs. The
* mapping is copied into all of the associated domains and made available to
* in-kernel users.
*
* Every iommu_domain that is created is wrapped in a iommufd_hw_pagetable
* object. When we go to attach a device to an IOAS we need to get an
* iommu_domain and wrapping iommufd_hw_pagetable for it.
*
* An iommu_domain & iommfd_hw_pagetable will be automatically selected
* for a device based on the hwpt_list. If no suitable iommu_domain
* is found a new iommu_domain will be created.
*/
struct iommufd_ioas {
struct iommufd_object obj;
struct io_pagetable iopt;
struct mutex mutex;
struct list_head hwpt_list;
};
static inline struct iommufd_ioas *iommufd_get_ioas(struct iommufd_ctx *ictx,
u32 id)
{
return container_of(iommufd_get_object(ictx, id, IOMMUFD_OBJ_IOAS),
struct iommufd_ioas, obj);
}
struct iommufd_ioas *iommufd_ioas_alloc(struct iommufd_ctx *ictx);
int iommufd_ioas_alloc_ioctl(struct iommufd_ucmd *ucmd);
void iommufd_ioas_destroy(struct iommufd_object *obj);
int iommufd_ioas_iova_ranges(struct iommufd_ucmd *ucmd);
int iommufd_ioas_allow_iovas(struct iommufd_ucmd *ucmd);
int iommufd_ioas_map(struct iommufd_ucmd *ucmd);
int iommufd_ioas_map_file(struct iommufd_ucmd *ucmd);
int iommufd_ioas_change_process(struct iommufd_ucmd *ucmd);
int iommufd_ioas_copy(struct iommufd_ucmd *ucmd);
int iommufd_ioas_unmap(struct iommufd_ucmd *ucmd);
int iommufd_ioas_option(struct iommufd_ucmd *ucmd);
int iommufd_option_rlimit_mode(struct iommu_option *cmd,
struct iommufd_ctx *ictx);
int iommufd_vfio_ioas(struct iommufd_ucmd *ucmd);
int iommufd_check_iova_range(struct io_pagetable *iopt,
struct iommu_hwpt_get_dirty_bitmap *bitmap);
/*
* A HW pagetable is called an iommu_domain inside the kernel. This user object
* allows directly creating and inspecting the domains. Domains that have kernel
* owned page tables will be associated with an iommufd_ioas that provides the
* IOVA to PFN map.
*/
struct iommufd_hw_pagetable {
struct iommufd_object obj;
struct iommu_domain *domain;
struct iommufd_fault *fault;
bool pasid_compat : 1;
};
struct iommufd_hwpt_paging {
struct iommufd_hw_pagetable common;
struct iommufd_ioas *ioas;
bool auto_domain : 1;
bool enforce_cache_coherency : 1;
bool nest_parent : 1;
/* Head at iommufd_ioas::hwpt_list */
struct list_head hwpt_item;
struct iommufd_sw_msi_maps present_sw_msi;
};
struct iommufd_hwpt_nested {
struct iommufd_hw_pagetable common;
struct iommufd_hwpt_paging *parent;
struct iommufd_viommu *viommu;
};
static inline bool hwpt_is_paging(struct iommufd_hw_pagetable *hwpt)
{
return hwpt->obj.type == IOMMUFD_OBJ_HWPT_PAGING;
}
static inline struct iommufd_hwpt_paging *
to_hwpt_paging(struct iommufd_hw_pagetable *hwpt)
{
return container_of(hwpt, struct iommufd_hwpt_paging, common);
}
static inline struct iommufd_hwpt_nested *
to_hwpt_nested(struct iommufd_hw_pagetable *hwpt)
{
return container_of(hwpt, struct iommufd_hwpt_nested, common);
}
static inline struct iommufd_hwpt_paging *
find_hwpt_paging(struct iommufd_hw_pagetable *hwpt)
{
switch (hwpt->obj.type) {
case IOMMUFD_OBJ_HWPT_PAGING:
return to_hwpt_paging(hwpt);
case IOMMUFD_OBJ_HWPT_NESTED:
return to_hwpt_nested(hwpt)->parent;
default:
return NULL;
}
}
static inline struct iommufd_hwpt_paging *
iommufd_get_hwpt_paging(struct iommufd_ucmd *ucmd, u32 id)
{
return container_of(iommufd_get_object(ucmd->ictx, id,
IOMMUFD_OBJ_HWPT_PAGING),
struct iommufd_hwpt_paging, common.obj);
}
static inline struct iommufd_hw_pagetable *
iommufd_get_hwpt_nested(struct iommufd_ucmd *ucmd, u32 id)
{
return container_of(iommufd_get_object(ucmd->ictx, id,
IOMMUFD_OBJ_HWPT_NESTED),
struct iommufd_hw_pagetable, obj);
}
int iommufd_hwpt_set_dirty_tracking(struct iommufd_ucmd *ucmd);
int iommufd_hwpt_get_dirty_bitmap(struct iommufd_ucmd *ucmd);
struct iommufd_hwpt_paging *
iommufd_hwpt_paging_alloc(struct iommufd_ctx *ictx, struct iommufd_ioas *ioas,
struct iommufd_device *idev, ioasid_t pasid,
u32 flags, bool immediate_attach,
const struct iommu_user_data *user_data);
int iommufd_hw_pagetable_attach(struct iommufd_hw_pagetable *hwpt,
struct iommufd_device *idev, ioasid_t pasid);
struct iommufd_hw_pagetable *
iommufd_hw_pagetable_detach(struct iommufd_device *idev, ioasid_t pasid);
void iommufd_hwpt_paging_destroy(struct iommufd_object *obj);
void iommufd_hwpt_paging_abort(struct iommufd_object *obj);
void iommufd_hwpt_nested_destroy(struct iommufd_object *obj);
void iommufd_hwpt_nested_abort(struct iommufd_object *obj);
int iommufd_hwpt_alloc(struct iommufd_ucmd *ucmd);
int iommufd_hwpt_invalidate(struct iommufd_ucmd *ucmd);
static inline void iommufd_hw_pagetable_put(struct iommufd_ctx *ictx,
struct iommufd_hw_pagetable *hwpt)
{
if (hwpt->obj.type == IOMMUFD_OBJ_HWPT_PAGING) {
struct iommufd_hwpt_paging *hwpt_paging = to_hwpt_paging(hwpt);
if (hwpt_paging->auto_domain) {
lockdep_assert_not_held(&hwpt_paging->ioas->mutex);
iommufd_object_put_and_try_destroy(ictx, &hwpt->obj);
return;
}
}
refcount_dec(&hwpt->obj.users);
}
struct iommufd_attach;
struct iommufd_group {
struct kref ref;
struct mutex lock;
struct iommufd_ctx *ictx;
struct iommu_group *group;
struct xarray pasid_attach;
struct iommufd_sw_msi_maps required_sw_msi;
phys_addr_t sw_msi_start;
};
/*
* A iommufd_device object represents the binding relationship between a
* consuming driver and the iommufd. These objects are created/destroyed by
* external drivers, not by userspace.
*/
struct iommufd_device {
struct iommufd_object obj;
struct iommufd_ctx *ictx;
struct iommufd_group *igroup;
struct list_head group_item;
/* always the physical device */
struct device *dev;
bool enforce_cache_coherency;
struct iommufd_vdevice *vdev;
bool destroying;
};
static inline struct iommufd_device *
iommufd_get_device(struct iommufd_ucmd *ucmd, u32 id)
{
return container_of(iommufd_get_object(ucmd->ictx, id,
IOMMUFD_OBJ_DEVICE),
struct iommufd_device, obj);
}
void iommufd_device_pre_destroy(struct iommufd_object *obj);
void iommufd_device_destroy(struct iommufd_object *obj);
int iommufd_get_hw_info(struct iommufd_ucmd *ucmd);
struct device *iommufd_global_device(void);
struct iommufd_access {
struct iommufd_object obj;
struct iommufd_ctx *ictx;
struct iommufd_ioas *ioas;
struct iommufd_ioas *ioas_unpin;
struct mutex ioas_lock;
const struct iommufd_access_ops *ops;
void *data;
unsigned long iova_alignment;
u32 iopt_access_list_id;
};
int iopt_add_access(struct io_pagetable *iopt, struct iommufd_access *access);
void iopt_remove_access(struct io_pagetable *iopt,
struct iommufd_access *access, u32 iopt_access_list_id);
void iommufd_access_destroy_object(struct iommufd_object *obj);
/* iommufd_access for internal use */
static inline bool iommufd_access_is_internal(struct iommufd_access *access)
{
return !access->ictx;
}
struct iommufd_access *iommufd_access_create_internal(struct iommufd_ctx *ictx);
static inline void
iommufd_access_destroy_internal(struct iommufd_ctx *ictx,
struct iommufd_access *access)
{
iommufd_object_destroy_user(ictx, &access->obj);
}
int iommufd_access_attach_internal(struct iommufd_access *access,
struct iommufd_ioas *ioas);
static inline void iommufd_access_detach_internal(struct iommufd_access *access)
{
iommufd_access_detach(access);
}
struct iommufd_eventq {
struct iommufd_object obj;
struct iommufd_ctx *ictx;
struct file *filep;
spinlock_t lock; /* protects the deliver list */
struct list_head deliver;
struct wait_queue_head wait_queue;
};
struct iommufd_attach_handle {
struct iommu_attach_handle handle;
struct iommufd_device *idev;
};
/* Convert an iommu attach handle to iommufd handle. */
#define to_iommufd_handle(hdl) container_of(hdl, struct iommufd_attach_handle, handle)
/*
* An iommufd_fault object represents an interface to deliver I/O page faults
* to the user space. These objects are created/destroyed by the user space and
* associated with hardware page table objects during page-table allocation.
*/
struct iommufd_fault {
struct iommufd_eventq common;
struct mutex mutex; /* serializes response flows */
struct xarray response;
};
static inline struct iommufd_fault *
eventq_to_fault(struct iommufd_eventq *eventq)
{
return container_of(eventq, struct iommufd_fault, common);
}
static inline struct iommufd_fault *
iommufd_get_fault(struct iommufd_ucmd *ucmd, u32 id)
{
return container_of(iommufd_get_object(ucmd->ictx, id,
IOMMUFD_OBJ_FAULT),
struct iommufd_fault, common.obj);
}
int iommufd_fault_alloc(struct iommufd_ucmd *ucmd);
void iommufd_fault_destroy(struct iommufd_object *obj);
int iommufd_fault_iopf_handler(struct iopf_group *group);
void iommufd_auto_response_faults(struct iommufd_hw_pagetable *hwpt,
struct iommufd_attach_handle *handle);
/* An iommufd_vevent represents a vIOMMU event in an iommufd_veventq */
struct iommufd_vevent {
struct iommufd_vevent_header header;
struct list_head node; /* for iommufd_eventq::deliver */
ssize_t data_len;
u8 event_data[] __counted_by(data_len);
};
#define vevent_for_lost_events_header(vevent) \
(vevent->header.flags & IOMMU_VEVENTQ_FLAG_LOST_EVENTS)
/*
* An iommufd_veventq object represents an interface to deliver vIOMMU events to
* the user space. It is created/destroyed by the user space and associated with
* a vIOMMU object during the allocations.
*/
struct iommufd_veventq {
struct iommufd_eventq common;
struct iommufd_viommu *viommu;
struct list_head node; /* for iommufd_viommu::veventqs */
enum iommu_veventq_type type;
unsigned int depth;
/* Use common.lock for protection */
u32 num_events;
u32 sequence;
/* Must be last as it ends in a flexible-array member. */
struct iommufd_vevent lost_events_header;
};
static inline struct iommufd_veventq *
eventq_to_veventq(struct iommufd_eventq *eventq)
{
return container_of(eventq, struct iommufd_veventq, common);
}
static inline struct iommufd_veventq *
iommufd_get_veventq(struct iommufd_ucmd *ucmd, u32 id)
{
return container_of(iommufd_get_object(ucmd->ictx, id,
IOMMUFD_OBJ_VEVENTQ),
struct iommufd_veventq, common.obj);
}
int iommufd_veventq_alloc(struct iommufd_ucmd *ucmd);
void iommufd_veventq_destroy(struct iommufd_object *obj);
void iommufd_veventq_abort(struct iommufd_object *obj);
static inline void iommufd_vevent_handler(struct iommufd_veventq *veventq,
struct iommufd_vevent *vevent)
{
struct iommufd_eventq *eventq = &veventq->common;
lockdep_assert_held(&eventq->lock);
/*
* Remove the lost_events_header and add the new node at the same time.
* Note the new node can be lost_events_header, for a sequence update.
*/
if (list_is_last(&veventq->lost_events_header.node, &eventq->deliver))
list_del(&veventq->lost_events_header.node);
list_add_tail(&vevent->node, &eventq->deliver);
vevent->header.sequence = veventq->sequence;
veventq->sequence = (veventq->sequence + 1) & INT_MAX;
wake_up_interruptible(&eventq->wait_queue);
}
static inline struct iommufd_viommu *
iommufd_get_viommu(struct iommufd_ucmd *ucmd, u32 id)
{
return container_of(iommufd_get_object(ucmd->ictx, id,
IOMMUFD_OBJ_VIOMMU),
struct iommufd_viommu, obj);
}
static inline struct iommufd_veventq *
iommufd_viommu_find_veventq(struct iommufd_viommu *viommu,
enum iommu_veventq_type type)
{
struct iommufd_veventq *veventq, *next;
lockdep_assert_held(&viommu->veventqs_rwsem);
list_for_each_entry_safe(veventq, next, &viommu->veventqs, node) {
if (veventq->type == type)
return veventq;
}
return NULL;
}
int iommufd_viommu_alloc_ioctl(struct iommufd_ucmd *ucmd);
void iommufd_viommu_destroy(struct iommufd_object *obj);
int iommufd_vdevice_alloc_ioctl(struct iommufd_ucmd *ucmd);
void iommufd_vdevice_destroy(struct iommufd_object *obj);
void iommufd_vdevice_abort(struct iommufd_object *obj);
int iommufd_hw_queue_alloc_ioctl(struct iommufd_ucmd *ucmd);
void iommufd_hw_queue_destroy(struct iommufd_object *obj);
static inline struct iommufd_vdevice *
iommufd_get_vdevice(struct iommufd_ctx *ictx, u32 id)
{
return container_of(iommufd_get_object(ictx, id,
IOMMUFD_OBJ_VDEVICE),
struct iommufd_vdevice, obj);
}
#ifdef CONFIG_IOMMUFD_TEST
int iommufd_test(struct iommufd_ucmd *ucmd);
void iommufd_selftest_destroy(struct iommufd_object *obj);
extern size_t iommufd_test_memory_limit;
void iommufd_test_syz_conv_iova_id(struct iommufd_ucmd *ucmd,
unsigned int ioas_id, u64 *iova, u32 *flags);
bool iommufd_should_fail(void);
int __init iommufd_test_init(void);
void iommufd_test_exit(void);
bool iommufd_selftest_is_mock_dev(struct device *dev);
int iommufd_test_dma_buf_iommufd_map(struct dma_buf_attachment *attachment,
struct phys_vec *phys);
#else
static inline void iommufd_test_syz_conv_iova_id(struct iommufd_ucmd *ucmd,
unsigned int ioas_id,
u64 *iova, u32 *flags)
{
}
static inline bool iommufd_should_fail(void)
{
return false;
}
static inline int __init iommufd_test_init(void)
{
return 0;
}
static inline void iommufd_test_exit(void)
{
}
static inline bool iommufd_selftest_is_mock_dev(struct device *dev)
{
return false;
}
static inline int
iommufd_test_dma_buf_iommufd_map(struct dma_buf_attachment *attachment,
struct phys_vec *phys)
{
return -EOPNOTSUPP;
}
#endif
#endif
@@ -0,0 +1,293 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* Copyright (c) 2021-2022, NVIDIA CORPORATION & AFFILIATES.
*/
#ifndef _UAPI_IOMMUFD_TEST_H
#define _UAPI_IOMMUFD_TEST_H
#include <linux/iommufd.h>
#include <linux/types.h>
enum {
IOMMU_TEST_OP_ADD_RESERVED = 1,
IOMMU_TEST_OP_MOCK_DOMAIN,
IOMMU_TEST_OP_MD_CHECK_MAP,
IOMMU_TEST_OP_MD_CHECK_REFS,
IOMMU_TEST_OP_CREATE_ACCESS,
IOMMU_TEST_OP_DESTROY_ACCESS_PAGES,
IOMMU_TEST_OP_ACCESS_PAGES,
IOMMU_TEST_OP_ACCESS_RW,
IOMMU_TEST_OP_SET_TEMP_MEMORY_LIMIT,
IOMMU_TEST_OP_MOCK_DOMAIN_REPLACE,
IOMMU_TEST_OP_ACCESS_REPLACE_IOAS,
IOMMU_TEST_OP_MOCK_DOMAIN_FLAGS,
IOMMU_TEST_OP_DIRTY,
IOMMU_TEST_OP_MD_CHECK_IOTLB,
IOMMU_TEST_OP_TRIGGER_IOPF,
IOMMU_TEST_OP_DEV_CHECK_CACHE,
IOMMU_TEST_OP_TRIGGER_VEVENT,
IOMMU_TEST_OP_PASID_ATTACH,
IOMMU_TEST_OP_PASID_REPLACE,
IOMMU_TEST_OP_PASID_DETACH,
IOMMU_TEST_OP_PASID_CHECK_HWPT,
IOMMU_TEST_OP_DMABUF_GET,
IOMMU_TEST_OP_DMABUF_REVOKE,
};
enum {
MOCK_IOMMUPT_DEFAULT = 0,
MOCK_IOMMUPT_HUGE,
};
/* These values are true for MOCK_IOMMUPT_DEFAULT */
enum {
MOCK_APERTURE_START = 1UL << 24,
MOCK_APERTURE_LAST = (1UL << 31) - 1,
MOCK_PAGE_SIZE = 2048,
MOCK_HUGE_PAGE_SIZE = 512 * MOCK_PAGE_SIZE,
};
enum {
MOCK_FLAGS_ACCESS_WRITE = 1 << 0,
MOCK_FLAGS_ACCESS_SYZ = 1 << 16,
};
enum {
MOCK_ACCESS_RW_WRITE = 1 << 0,
MOCK_ACCESS_RW_SLOW_PATH = 1 << 2,
};
enum {
MOCK_FLAGS_ACCESS_CREATE_NEEDS_PIN_PAGES = 1 << 0,
};
enum {
MOCK_FLAGS_DEVICE_NO_DIRTY = 1 << 0,
MOCK_FLAGS_DEVICE_PASID = 1 << 2,
};
enum {
MOCK_NESTED_DOMAIN_IOTLB_ID_MAX = 3,
MOCK_NESTED_DOMAIN_IOTLB_NUM = 4,
};
enum {
MOCK_DEV_CACHE_ID_MAX = 3,
MOCK_DEV_CACHE_NUM = 4,
};
/* Reserved for special pasid replace test */
#define IOMMU_TEST_PASID_RESERVED 1024
struct iommu_test_cmd {
__u32 size;
__u32 op;
__u32 id;
__u32 __reserved;
union {
struct {
__aligned_u64 start;
__aligned_u64 length;
} add_reserved;
struct {
__u32 out_stdev_id;
__u32 out_hwpt_id;
/* out_idev_id is the standard iommufd_bind object */
__u32 out_idev_id;
} mock_domain;
struct {
__u32 out_stdev_id;
__u32 out_hwpt_id;
__u32 out_idev_id;
/* Expand mock_domain to set mock device flags */
__u32 dev_flags;
} mock_domain_flags;
struct {
__u32 pt_id;
} mock_domain_replace;
struct {
__aligned_u64 iova;
__aligned_u64 length;
__aligned_u64 uptr;
} check_map;
struct {
__aligned_u64 length;
__aligned_u64 uptr;
__u32 refs;
} check_refs;
struct {
__u32 out_access_fd;
__u32 flags;
} create_access;
struct {
__u32 access_pages_id;
} destroy_access_pages;
struct {
__u32 flags;
__u32 out_access_pages_id;
__aligned_u64 iova;
__aligned_u64 length;
__aligned_u64 uptr;
} access_pages;
struct {
__aligned_u64 iova;
__aligned_u64 length;
__aligned_u64 uptr;
__u32 flags;
} access_rw;
struct {
__u32 limit;
} memory_limit;
struct {
__u32 ioas_id;
} access_replace_ioas;
struct {
__u32 flags;
__aligned_u64 iova;
__aligned_u64 length;
__aligned_u64 page_size;
__aligned_u64 uptr;
__aligned_u64 out_nr_dirty;
} dirty;
struct {
__u32 id;
__u32 iotlb;
} check_iotlb;
struct {
__u32 dev_id;
__u32 pasid;
__u32 grpid;
__u32 perm;
__u64 addr;
} trigger_iopf;
struct {
__u32 id;
__u32 cache;
} check_dev_cache;
struct {
__u32 dev_id;
} trigger_vevent;
struct {
__u32 pasid;
__u32 pt_id;
/* @id is stdev_id */
} pasid_attach;
struct {
__u32 pasid;
__u32 pt_id;
/* @id is stdev_id */
} pasid_replace;
struct {
__u32 pasid;
/* @id is stdev_id */
} pasid_detach;
struct {
__u32 pasid;
__u32 hwpt_id;
/* @id is stdev_id */
} pasid_check;
struct {
__u32 length;
__u32 open_flags;
} dmabuf_get;
struct {
__s32 dmabuf_fd;
__u32 revoked;
} dmabuf_revoke;
};
__u32 last;
};
#define IOMMU_TEST_CMD _IO(IOMMUFD_TYPE, IOMMUFD_CMD_BASE + 32)
/* Mock device/iommu PASID width */
#define MOCK_PASID_WIDTH 20
/* Mock structs for IOMMU_DEVICE_GET_HW_INFO ioctl */
#define IOMMU_HW_INFO_TYPE_SELFTEST 0xfeedbeef
#define IOMMU_HW_INFO_SELFTEST_REGVAL 0xdeadbeef
struct iommu_test_hw_info {
__u32 flags;
__u32 test_reg;
};
/* Should not be equal to any defined value in enum iommu_hwpt_data_type */
#define IOMMU_HWPT_DATA_SELFTEST 0xdead
#define IOMMU_TEST_IOTLB_DEFAULT 0xbadbeef
#define IOMMU_TEST_DEV_CACHE_DEFAULT 0xbaddad
/**
* struct iommu_hwpt_selftest
*
* @iotlb: default mock iotlb value, IOMMU_TEST_IOTLB_DEFAULT
*/
struct iommu_hwpt_selftest {
__u32 iotlb;
__u32 pagetable_type;
};
/* Should not be equal to any defined value in enum iommu_hwpt_invalidate_data_type */
#define IOMMU_HWPT_INVALIDATE_DATA_SELFTEST 0xdeadbeef
#define IOMMU_HWPT_INVALIDATE_DATA_SELFTEST_INVALID 0xdadbeef
/**
* struct iommu_hwpt_invalidate_selftest - Invalidation data for Mock driver
* (IOMMU_HWPT_INVALIDATE_DATA_SELFTEST)
* @flags: Invalidate flags
* @iotlb_id: Invalidate iotlb entry index
*
* If IOMMU_TEST_INVALIDATE_ALL is set in @flags, @iotlb_id will be ignored
*/
struct iommu_hwpt_invalidate_selftest {
#define IOMMU_TEST_INVALIDATE_FLAG_ALL (1 << 0)
__u32 flags;
__u32 iotlb_id;
};
#define IOMMU_VIOMMU_TYPE_SELFTEST 0xdeadbeef
/**
* struct iommu_viommu_selftest - vIOMMU data for Mock driver
* (IOMMU_VIOMMU_TYPE_SELFTEST)
* @in_data: Input random data from user space
* @out_data: Output data (matching @in_data) to user space
* @out_mmap_offset: The offset argument for mmap syscall
* @out_mmap_length: The length argument for mmap syscall
*
* Simply set @out_data=@in_data for a loopback test
*/
struct iommu_viommu_selftest {
__u32 in_data;
__u32 out_data;
__aligned_u64 out_mmap_offset;
__aligned_u64 out_mmap_length;
};
/* Should not be equal to any defined value in enum iommu_viommu_invalidate_data_type */
#define IOMMU_VIOMMU_INVALIDATE_DATA_SELFTEST 0xdeadbeef
#define IOMMU_VIOMMU_INVALIDATE_DATA_SELFTEST_INVALID 0xdadbeef
/**
* struct iommu_viommu_invalidate_selftest - Invalidation data for Mock VIOMMU
* (IOMMU_VIOMMU_INVALIDATE_DATA_SELFTEST)
* @flags: Invalidate flags
* @cache_id: Invalidate cache entry index
*
* If IOMMU_TEST_INVALIDATE_ALL is set in @flags, @cache_id will be ignored
*/
struct iommu_viommu_invalidate_selftest {
#define IOMMU_TEST_INVALIDATE_FLAG_ALL (1 << 0)
__u32 flags;
__u32 vdev_id;
__u32 cache_id;
};
#define IOMMU_VEVENTQ_TYPE_SELFTEST 0xbeefbeef
struct iommu_viommu_event_selftest {
__u32 virt_id;
};
#define IOMMU_HW_QUEUE_TYPE_SELFTEST 0xdeadbeef
#define IOMMU_TEST_HW_QUEUE_MAX 2
#endif
@@ -0,0 +1,446 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2022, Oracle and/or its affiliates.
* Copyright (c) 2022, NVIDIA CORPORATION & AFFILIATES. All rights reserved
*/
#include <linux/highmem.h>
#include <linux/iova_bitmap.h>
#include <linux/mm.h>
#include <linux/slab.h>
#define BITS_PER_PAGE (PAGE_SIZE * BITS_PER_BYTE)
/*
* struct iova_bitmap_map - A bitmap representing an IOVA range
*
* Main data structure for tracking mapped user pages of bitmap data.
*
* For example, for something recording dirty IOVAs, it will be provided a
* struct iova_bitmap structure, as a general structure for iterating the
* total IOVA range. The struct iova_bitmap_map, though, represents the
* subset of said IOVA space that is pinned by its parent structure (struct
* iova_bitmap).
*
* The user does not need to exact location of the bits in the bitmap.
* From user perspective the only API available is iova_bitmap_set() which
* records the IOVA *range* in the bitmap by setting the corresponding
* bits.
*
* The bitmap is an array of u64 whereas each bit represents an IOVA of
* range of (1 << pgshift). Thus formula for the bitmap data to be set is:
*
* data[(iova / page_size) / 64] & (1ULL << (iova % 64))
*/
struct iova_bitmap_map {
/* base IOVA representing bit 0 of the first page */
unsigned long iova;
/* mapped length */
unsigned long length;
/* page size order that each bit granules to */
unsigned long pgshift;
/* page offset of the first user page pinned */
unsigned long pgoff;
/* number of pages pinned */
unsigned long npages;
/* pinned pages representing the bitmap data */
struct page **pages;
};
/*
* struct iova_bitmap - The IOVA bitmap object
*
* Main data structure for iterating over the bitmap data.
*
* Abstracts the pinning work and iterates in IOVA ranges.
* It uses a windowing scheme and pins the bitmap in relatively
* big ranges e.g.
*
* The bitmap object uses one base page to store all the pinned pages
* pointers related to the bitmap. For sizeof(struct page*) == 8 it stores
* 512 struct page pointers which, if the base page size is 4K, it means
* 2M of bitmap data is pinned at a time. If the iova_bitmap page size is
* also 4K then the range window to iterate is 64G.
*
* For example iterating on a total IOVA range of 4G..128G, it will walk
* through this set of ranges:
*
* 4G - 68G-1 (64G)
* 68G - 128G-1 (64G)
*
* An example of the APIs on how to use/iterate over the IOVA bitmap:
*
* bitmap = iova_bitmap_alloc(iova, length, page_size, data);
* if (IS_ERR(bitmap))
* return PTR_ERR(bitmap);
*
* ret = iova_bitmap_for_each(bitmap, arg, dirty_reporter_fn);
*
* iova_bitmap_free(bitmap);
*
* Each iteration of the @dirty_reporter_fn is called with a unique @iova
* and @length argument, indicating the current range available through the
* iova_bitmap. The @dirty_reporter_fn uses iova_bitmap_set() to mark dirty
* areas (@iova_length) within that provided range, as following:
*
* iova_bitmap_set(bitmap, iova, iova_length);
*
* The internals of the object uses an index @mapped_base_index that indexes
* which u64 word of the bitmap is mapped, up to @mapped_total_index.
* Those keep being incremented until @mapped_total_index is reached while
* mapping up to PAGE_SIZE / sizeof(struct page*) maximum of pages.
*
* The IOVA bitmap is usually located on what tracks DMA mapped ranges or
* some form of IOVA range tracking that co-relates to the user passed
* bitmap.
*/
struct iova_bitmap {
/* IOVA range representing the currently mapped bitmap data */
struct iova_bitmap_map mapped;
/* userspace address of the bitmap */
u8 __user *bitmap;
/* u64 index that @mapped points to */
unsigned long mapped_base_index;
/* how many u64 can we walk in total */
unsigned long mapped_total_index;
/* base IOVA of the whole bitmap */
unsigned long iova;
/* length of the IOVA range for the whole bitmap */
size_t length;
};
/*
* Converts a relative IOVA to a bitmap index.
* This function provides the index into the u64 array (bitmap::bitmap)
* for a given IOVA offset.
* Relative IOVA means relative to the bitmap::mapped base IOVA
* (stored in mapped::iova). All computations in this file are done using
* relative IOVAs and thus avoid an extra subtraction against mapped::iova.
* The user API iova_bitmap_set() always uses a regular absolute IOVAs.
*/
static unsigned long iova_bitmap_offset_to_index(struct iova_bitmap *bitmap,
unsigned long iova)
{
return (iova >> bitmap->mapped.pgshift) /
BITS_PER_TYPE(*bitmap->bitmap);
}
/*
* Converts a bitmap index to a *relative* IOVA.
*/
static unsigned long iova_bitmap_index_to_offset(struct iova_bitmap *bitmap,
unsigned long index)
{
unsigned long pgshift = bitmap->mapped.pgshift;
return (index * BITS_PER_TYPE(*bitmap->bitmap)) << pgshift;
}
/*
* Returns the base IOVA of the mapped range.
*/
static unsigned long iova_bitmap_mapped_iova(struct iova_bitmap *bitmap)
{
unsigned long skip = bitmap->mapped_base_index;
return bitmap->iova + iova_bitmap_index_to_offset(bitmap, skip);
}
static unsigned long iova_bitmap_mapped_length(struct iova_bitmap *bitmap);
/*
* Pins the bitmap user pages for the current range window.
* This is internal to IOVA bitmap and called when advancing the
* index (@mapped_base_index) or allocating the bitmap.
*/
static int iova_bitmap_get(struct iova_bitmap *bitmap)
{
struct iova_bitmap_map *mapped = &bitmap->mapped;
unsigned long npages;
u8 __user *addr;
long ret;
/*
* @mapped_base_index is the index of the currently mapped u64 words
* that we have access. Anything before @mapped_base_index is not
* mapped. The range @mapped_base_index .. @mapped_total_index-1 is
* mapped but capped at a maximum number of pages.
*/
npages = DIV_ROUND_UP((bitmap->mapped_total_index -
bitmap->mapped_base_index) *
sizeof(*bitmap->bitmap), PAGE_SIZE);
/*
* Bitmap address to be pinned is calculated via pointer arithmetic
* with bitmap u64 word index.
*/
addr = bitmap->bitmap + bitmap->mapped_base_index;
/*
* We always cap at max number of 'struct page' a base page can fit.
* This is, for example, on x86 means 2M of bitmap data max.
*/
npages = min(npages + !!offset_in_page(addr),
PAGE_SIZE / sizeof(struct page *));
ret = pin_user_pages_fast((unsigned long)addr, npages,
FOLL_WRITE, mapped->pages);
if (ret <= 0)
return -EFAULT;
mapped->npages = (unsigned long)ret;
/* Base IOVA where @pages point to i.e. bit 0 of the first page */
mapped->iova = iova_bitmap_mapped_iova(bitmap);
/*
* offset of the page where pinned pages bit 0 is located.
* This handles the case where the bitmap is not PAGE_SIZE
* aligned.
*/
mapped->pgoff = offset_in_page(addr);
mapped->length = iova_bitmap_mapped_length(bitmap);
return 0;
}
/*
* Unpins the bitmap user pages and clears @npages
* (un)pinning is abstracted from API user and it's done when advancing
* the index or freeing the bitmap.
*/
static void iova_bitmap_put(struct iova_bitmap *bitmap)
{
struct iova_bitmap_map *mapped = &bitmap->mapped;
if (mapped->npages) {
unpin_user_pages(mapped->pages, mapped->npages);
mapped->npages = 0;
}
}
/**
* iova_bitmap_alloc() - Allocates an IOVA bitmap object
* @iova: Start address of the IOVA range
* @length: Length of the IOVA range
* @page_size: Page size of the IOVA bitmap. It defines what each bit
* granularity represents
* @data: Userspace address of the bitmap
*
* Allocates an IOVA object and initializes all its fields including the
* first user pages of @data.
*
* Return: A pointer to a newly allocated struct iova_bitmap
* or ERR_PTR() on error.
*/
struct iova_bitmap *iova_bitmap_alloc(unsigned long iova, size_t length,
unsigned long page_size, u64 __user *data)
{
struct iova_bitmap_map *mapped;
struct iova_bitmap *bitmap;
int rc;
bitmap = kzalloc_obj(*bitmap);
if (!bitmap)
return ERR_PTR(-ENOMEM);
mapped = &bitmap->mapped;
mapped->pgshift = __ffs(page_size);
bitmap->bitmap = (u8 __user *)data;
bitmap->mapped_total_index =
iova_bitmap_offset_to_index(bitmap, length - 1) + 1;
bitmap->iova = iova;
bitmap->length = length;
mapped->iova = iova;
mapped->pages = (struct page **)__get_free_page(GFP_KERNEL);
if (!mapped->pages) {
rc = -ENOMEM;
goto err;
}
return bitmap;
err:
iova_bitmap_free(bitmap);
return ERR_PTR(rc);
}
EXPORT_SYMBOL_NS_GPL(iova_bitmap_alloc, "IOMMUFD");
/**
* iova_bitmap_free() - Frees an IOVA bitmap object
* @bitmap: IOVA bitmap to free
*
* It unpins and releases pages array memory and clears any leftover
* state.
*/
void iova_bitmap_free(struct iova_bitmap *bitmap)
{
struct iova_bitmap_map *mapped = &bitmap->mapped;
iova_bitmap_put(bitmap);
if (mapped->pages) {
free_page((unsigned long)mapped->pages);
mapped->pages = NULL;
}
kfree(bitmap);
}
EXPORT_SYMBOL_NS_GPL(iova_bitmap_free, "IOMMUFD");
/*
* Returns the remaining bitmap indexes from mapped_total_index to process for
* the currently pinned bitmap pages.
*/
static unsigned long iova_bitmap_mapped_remaining(struct iova_bitmap *bitmap)
{
unsigned long remaining, bytes;
bytes = (bitmap->mapped.npages << PAGE_SHIFT) - bitmap->mapped.pgoff;
remaining = bitmap->mapped_total_index - bitmap->mapped_base_index;
remaining = min_t(unsigned long, remaining,
DIV_ROUND_UP(bytes, sizeof(*bitmap->bitmap)));
return remaining;
}
/*
* Returns the length of the mapped IOVA range.
*/
static unsigned long iova_bitmap_mapped_length(struct iova_bitmap *bitmap)
{
unsigned long max_iova = bitmap->iova + bitmap->length - 1;
unsigned long iova = iova_bitmap_mapped_iova(bitmap);
unsigned long remaining;
/*
* iova_bitmap_mapped_remaining() returns a number of indexes which
* when converted to IOVA gives us a max length that the bitmap
* pinned data can cover. Afterwards, that is capped to
* only cover the IOVA range in @bitmap::iova .. @bitmap::length.
*/
remaining = iova_bitmap_index_to_offset(bitmap,
iova_bitmap_mapped_remaining(bitmap));
if (iova + remaining - 1 > max_iova)
remaining -= ((iova + remaining - 1) - max_iova);
return remaining;
}
/*
* Returns true if [@iova..@iova+@length-1] is part of the mapped IOVA range.
*/
static bool iova_bitmap_mapped_range(struct iova_bitmap_map *mapped,
unsigned long iova, size_t length)
{
return mapped->npages &&
(iova >= mapped->iova &&
(iova + length - 1) <= (mapped->iova + mapped->length - 1));
}
/*
* Advances to a selected range, releases the current pinned
* pages and pins the next set of bitmap pages.
* Returns 0 on success or otherwise errno.
*/
static int iova_bitmap_advance_to(struct iova_bitmap *bitmap,
unsigned long iova)
{
unsigned long index;
index = iova_bitmap_offset_to_index(bitmap, iova - bitmap->iova);
if (index >= bitmap->mapped_total_index)
return -EINVAL;
bitmap->mapped_base_index = index;
iova_bitmap_put(bitmap);
/* Pin the next set of bitmap pages */
return iova_bitmap_get(bitmap);
}
/**
* iova_bitmap_for_each() - Iterates over the bitmap
* @bitmap: IOVA bitmap to iterate
* @opaque: Additional argument to pass to the callback
* @fn: Function that gets called for each IOVA range
*
* Helper function to iterate over bitmap data representing a portion of IOVA
* space. It hides the complexity of iterating bitmaps and translating the
* mapped bitmap user pages into IOVA ranges to process.
*
* Return: 0 on success, and an error on failure either upon
* iteration or when the callback returns an error.
*/
int iova_bitmap_for_each(struct iova_bitmap *bitmap, void *opaque,
iova_bitmap_fn_t fn)
{
return fn(bitmap, bitmap->iova, bitmap->length, opaque);
}
EXPORT_SYMBOL_NS_GPL(iova_bitmap_for_each, "IOMMUFD");
/**
* iova_bitmap_set() - Records an IOVA range in bitmap
* @bitmap: IOVA bitmap
* @iova: IOVA to start
* @length: IOVA range length
*
* Set the bits corresponding to the range [iova .. iova+length-1] in
* the user bitmap.
*
*/
void iova_bitmap_set(struct iova_bitmap *bitmap,
unsigned long iova, size_t length)
{
struct iova_bitmap_map *mapped = &bitmap->mapped;
unsigned long cur_bit, last_bit, last_page_idx;
update_indexes:
if (unlikely(!iova_bitmap_mapped_range(mapped, iova, length))) {
/*
* The attempt to advance the base index to @iova
* may fail if it's out of bounds, or pinning the pages
* returns an error.
*/
if (iova_bitmap_advance_to(bitmap, iova))
return;
}
last_page_idx = mapped->npages - 1;
cur_bit = ((iova - mapped->iova) >>
mapped->pgshift) + mapped->pgoff * BITS_PER_BYTE;
last_bit = (((iova + length - 1) - mapped->iova) >>
mapped->pgshift) + mapped->pgoff * BITS_PER_BYTE;
do {
unsigned int page_idx = cur_bit / BITS_PER_PAGE;
unsigned int offset = cur_bit % BITS_PER_PAGE;
unsigned int nbits = min(BITS_PER_PAGE - offset,
last_bit - cur_bit + 1);
void *kaddr;
if (unlikely(page_idx > last_page_idx)) {
unsigned long left =
((last_bit - cur_bit + 1) << mapped->pgshift);
iova += (length - left);
length = left;
goto update_indexes;
}
kaddr = kmap_local_page(mapped->pages[page_idx]);
bitmap_set(kaddr, offset, nbits);
kunmap_local(kaddr);
cur_bit += nbits;
} while (cur_bit <= last_bit);
}
EXPORT_SYMBOL_NS_GPL(iova_bitmap_set, "IOMMUFD");
+808
View File
@@ -0,0 +1,808 @@
// SPDX-License-Identifier: GPL-2.0-only
/* Copyright (C) 2021 Intel Corporation
* Copyright (c) 2021-2022, NVIDIA CORPORATION & AFFILIATES
*
* iommufd provides control over the IOMMU HW objects created by IOMMU kernel
* drivers. IOMMU HW objects revolve around IO page tables that map incoming DMA
* addresses (IOVA) to CPU addresses.
*/
#define pr_fmt(fmt) "iommufd: " fmt
#include <linux/bug.h>
#include <linux/file.h>
#include <linux/fs.h>
#include <linux/iommufd.h>
#include <linux/miscdevice.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/slab.h>
#include <uapi/linux/iommufd.h>
#include "io_pagetable.h"
#include "iommufd_private.h"
#include "iommufd_test.h"
struct iommufd_object_ops {
size_t file_offset;
void (*pre_destroy)(struct iommufd_object *obj);
void (*destroy)(struct iommufd_object *obj);
void (*abort)(struct iommufd_object *obj);
};
static const struct iommufd_object_ops iommufd_object_ops[];
static struct miscdevice vfio_misc_dev;
struct iommufd_object *_iommufd_object_alloc(struct iommufd_ctx *ictx,
size_t size,
enum iommufd_object_type type)
{
struct iommufd_object *obj;
int rc;
obj = kzalloc(size, GFP_KERNEL_ACCOUNT);
if (!obj)
return ERR_PTR(-ENOMEM);
obj->type = type;
/* Starts out bias'd by 1 until it is removed from the xarray */
refcount_set(&obj->wait_cnt, 1);
refcount_set(&obj->users, 1);
/*
* Reserve an ID in the xarray but do not publish the pointer yet since
* the caller hasn't initialized it yet. Once the pointer is published
* in the xarray and visible to other threads we can't reliably destroy
* it anymore, so the caller must complete all errorable operations
* before calling iommufd_object_finalize().
*/
rc = xa_alloc(&ictx->objects, &obj->id, XA_ZERO_ENTRY, xa_limit_31b,
GFP_KERNEL_ACCOUNT);
if (rc)
goto out_free;
return obj;
out_free:
kfree(obj);
return ERR_PTR(rc);
}
struct iommufd_object *_iommufd_object_alloc_ucmd(struct iommufd_ucmd *ucmd,
size_t size,
enum iommufd_object_type type)
{
struct iommufd_object *new_obj;
/* Something is coded wrong if this is hit */
if (WARN_ON(ucmd->new_obj))
return ERR_PTR(-EBUSY);
/*
* An abort op means that its caller needs to invoke it within a lock in
* the caller. So it doesn't work with _iommufd_object_alloc_ucmd() that
* will invoke the abort op in iommufd_object_abort_and_destroy(), which
* must be outside the caller's lock.
*/
if (WARN_ON(iommufd_object_ops[type].abort))
return ERR_PTR(-EOPNOTSUPP);
new_obj = _iommufd_object_alloc(ucmd->ictx, size, type);
if (IS_ERR(new_obj))
return new_obj;
ucmd->new_obj = new_obj;
return new_obj;
}
/*
* Allow concurrent access to the object.
*
* Once another thread can see the object pointer it can prevent object
* destruction. Expect for special kernel-only objects there is no in-kernel way
* to reliably destroy a single object. Thus all APIs that are creating objects
* must use iommufd_object_abort() to handle their errors and only call
* iommufd_object_finalize() once object creation cannot fail.
*/
void iommufd_object_finalize(struct iommufd_ctx *ictx,
struct iommufd_object *obj)
{
XA_STATE(xas, &ictx->objects, obj->id);
void *old;
xa_lock(&ictx->objects);
old = xas_store(&xas, obj);
xa_unlock(&ictx->objects);
/* obj->id was returned from xa_alloc() so the xas_store() cannot fail */
WARN_ON(old != XA_ZERO_ENTRY);
}
/* Undo _iommufd_object_alloc() if iommufd_object_finalize() was not called */
void iommufd_object_abort(struct iommufd_ctx *ictx, struct iommufd_object *obj)
{
XA_STATE(xas, &ictx->objects, obj->id);
void *old;
xa_lock(&ictx->objects);
old = xas_store(&xas, NULL);
xa_unlock(&ictx->objects);
WARN_ON(old != XA_ZERO_ENTRY);
if (WARN_ON(!refcount_dec_and_test(&obj->users)))
return;
kfree(obj);
}
/*
* Abort an object that has been fully initialized and needs destroy, but has
* not been finalized.
*/
void iommufd_object_abort_and_destroy(struct iommufd_ctx *ictx,
struct iommufd_object *obj)
{
const struct iommufd_object_ops *ops = &iommufd_object_ops[obj->type];
if (ops->file_offset) {
struct file **filep = ((void *)obj) + ops->file_offset;
/*
* A file should hold a users refcount while the file is open
* and put it back in its release. The file should hold a
* pointer to obj in their private data. Normal fput() is
* deferred to a workqueue and can get out of order with the
* following kfree(obj). Using the sync version ensures the
* release happens immediately. During abort we require the file
* refcount is one at this point - meaning the object alloc
* function cannot do anything to allow another thread to take a
* refcount prior to a guaranteed success.
*/
if (*filep)
__fput_sync(*filep);
}
if (ops->abort)
ops->abort(obj);
else
ops->destroy(obj);
iommufd_object_abort(ictx, obj);
}
struct iommufd_object *iommufd_get_object(struct iommufd_ctx *ictx, u32 id,
enum iommufd_object_type type)
{
struct iommufd_object *obj;
if (iommufd_should_fail())
return ERR_PTR(-ENOENT);
xa_lock(&ictx->objects);
obj = xa_load(&ictx->objects, id);
if (!obj || (type != IOMMUFD_OBJ_ANY && obj->type != type) ||
!iommufd_lock_obj(obj))
obj = ERR_PTR(-ENOENT);
xa_unlock(&ictx->objects);
return obj;
}
static int iommufd_object_dec_wait(struct iommufd_ctx *ictx,
struct iommufd_object *to_destroy)
{
if (refcount_dec_and_test(&to_destroy->wait_cnt))
return 0;
if (iommufd_object_ops[to_destroy->type].pre_destroy)
iommufd_object_ops[to_destroy->type].pre_destroy(to_destroy);
if (wait_event_timeout(ictx->destroy_wait,
refcount_read(&to_destroy->wait_cnt) == 0,
msecs_to_jiffies(60000)))
return 0;
pr_crit("Time out waiting for iommufd object to become free\n");
refcount_inc(&to_destroy->wait_cnt);
return -EBUSY;
}
/*
* Remove the given object id from the xarray if the only reference to the
* object is held by the xarray.
*/
int iommufd_object_remove(struct iommufd_ctx *ictx,
struct iommufd_object *to_destroy, u32 id,
unsigned int flags)
{
struct iommufd_object *obj;
XA_STATE(xas, &ictx->objects, id);
bool zerod_wait_cnt = false;
int ret;
/*
* The purpose of the wait_cnt is to ensure deterministic destruction
* of objects used by external drivers and destroyed by this function.
* Incrementing this wait_cnt should either be short lived, such as
* during ioctl execution, or be revoked and blocked during
* pre_destroy(), such as vdev holding the idev's refcount.
*/
if (flags & REMOVE_WAIT) {
ret = iommufd_object_dec_wait(ictx, to_destroy);
if (ret) {
/*
* We have a bug. Put back the callers reference and
* defer cleaning this object until close.
*/
refcount_dec(&to_destroy->users);
return ret;
}
zerod_wait_cnt = true;
}
xa_lock(&ictx->objects);
obj = xas_load(&xas);
if (to_destroy) {
/*
* If the caller is holding a ref on obj we put it here under
* the spinlock.
*/
refcount_dec(&obj->users);
if (WARN_ON(obj != to_destroy)) {
ret = -ENOENT;
goto err_xa;
}
} else if (xa_is_zero(obj) || !obj) {
ret = -ENOENT;
goto err_xa;
}
if (!refcount_dec_if_one(&obj->users)) {
ret = -EBUSY;
goto err_xa;
}
xas_store(&xas, (flags & REMOVE_OBJ_TOMBSTONE) ? XA_ZERO_ENTRY : NULL);
if (ictx->vfio_ioas == container_of(obj, struct iommufd_ioas, obj))
ictx->vfio_ioas = NULL;
xa_unlock(&ictx->objects);
/*
* Since users is zero any positive wait_cnt must be racing
* iommufd_put_object(), or we have a bug.
*/
if (!zerod_wait_cnt) {
ret = iommufd_object_dec_wait(ictx, obj);
if (WARN_ON(ret))
return ret;
}
iommufd_object_ops[obj->type].destroy(obj);
kfree(obj);
return 0;
err_xa:
if (zerod_wait_cnt) {
/* Restore the xarray owned reference */
refcount_set(&obj->wait_cnt, 1);
}
xa_unlock(&ictx->objects);
/* The returned object reference count is zero */
return ret;
}
static int iommufd_destroy(struct iommufd_ucmd *ucmd)
{
struct iommu_destroy *cmd = ucmd->cmd;
return iommufd_object_remove(ucmd->ictx, NULL, cmd->id, 0);
}
static int iommufd_fops_open(struct inode *inode, struct file *filp)
{
struct iommufd_ctx *ictx;
ictx = kzalloc_obj(*ictx, GFP_KERNEL_ACCOUNT);
if (!ictx)
return -ENOMEM;
/*
* For compatibility with VFIO when /dev/vfio/vfio is opened we default
* to the same rlimit accounting as vfio uses.
*/
if (IS_ENABLED(CONFIG_IOMMUFD_VFIO_CONTAINER) &&
filp->private_data == &vfio_misc_dev) {
ictx->account_mode = IOPT_PAGES_ACCOUNT_MM;
pr_info_once("IOMMUFD is providing /dev/vfio/vfio, not VFIO.\n");
}
init_rwsem(&ictx->ioas_creation_lock);
xa_init_flags(&ictx->objects, XA_FLAGS_ALLOC1 | XA_FLAGS_ACCOUNT);
xa_init(&ictx->groups);
ictx->file = filp;
mt_init_flags(&ictx->mt_mmap, MT_FLAGS_ALLOC_RANGE);
init_waitqueue_head(&ictx->destroy_wait);
mutex_init(&ictx->sw_msi_lock);
INIT_LIST_HEAD(&ictx->sw_msi_list);
filp->private_data = ictx;
return 0;
}
static int iommufd_fops_release(struct inode *inode, struct file *filp)
{
struct iommufd_ctx *ictx = filp->private_data;
struct iommufd_sw_msi_map *next;
struct iommufd_sw_msi_map *cur;
struct iommufd_object *obj;
/*
* The objects in the xarray form a graph of "users" counts, and we have
* to destroy them in a depth first manner. Leaf objects will reduce the
* users count of interior objects when they are destroyed.
*
* Repeatedly destroying all the "1 users" leaf objects will progress
* until the entire list is destroyed. If this can't progress then there
* is some bug related to object refcounting.
*/
while (!xa_empty(&ictx->objects)) {
unsigned int destroyed = 0;
unsigned long index;
bool empty = true;
/*
* We can't use xa_empty() to end the loop as the tombstones
* are stored as XA_ZERO_ENTRY in the xarray. However
* xa_for_each() automatically converts them to NULL and skips
* them causing xa_empty() to be kept false. Thus once
* xa_for_each() finds no further !NULL entries the loop is
* done.
*/
xa_for_each(&ictx->objects, index, obj) {
empty = false;
if (!refcount_dec_if_one(&obj->users))
continue;
destroyed++;
xa_erase(&ictx->objects, index);
iommufd_object_ops[obj->type].destroy(obj);
kfree(obj);
}
if (empty)
break;
/* Bug related to users refcount */
if (WARN_ON(!destroyed))
break;
}
/*
* There may be some tombstones left over from
* iommufd_object_tombstone_user()
*/
xa_destroy(&ictx->objects);
WARN_ON(!xa_empty(&ictx->groups));
mutex_destroy(&ictx->sw_msi_lock);
list_for_each_entry_safe(cur, next, &ictx->sw_msi_list, sw_msi_item)
kfree(cur);
kfree(ictx);
return 0;
}
static int iommufd_option(struct iommufd_ucmd *ucmd)
{
struct iommu_option *cmd = ucmd->cmd;
int rc;
if (cmd->__reserved)
return -EOPNOTSUPP;
switch (cmd->option_id) {
case IOMMU_OPTION_RLIMIT_MODE:
rc = iommufd_option_rlimit_mode(cmd, ucmd->ictx);
break;
case IOMMU_OPTION_HUGE_PAGES:
rc = iommufd_ioas_option(ucmd);
break;
default:
return -EOPNOTSUPP;
}
if (rc)
return rc;
if (copy_to_user(&((struct iommu_option __user *)ucmd->ubuffer)->val64,
&cmd->val64, sizeof(cmd->val64)))
return -EFAULT;
return 0;
}
union ucmd_buffer {
struct iommu_destroy destroy;
struct iommu_fault_alloc fault;
struct iommu_hw_info info;
struct iommu_hw_queue_alloc hw_queue;
struct iommu_hwpt_alloc hwpt;
struct iommu_hwpt_get_dirty_bitmap get_dirty_bitmap;
struct iommu_hwpt_invalidate cache;
struct iommu_hwpt_set_dirty_tracking set_dirty_tracking;
struct iommu_ioas_alloc alloc;
struct iommu_ioas_allow_iovas allow_iovas;
struct iommu_ioas_copy ioas_copy;
struct iommu_ioas_iova_ranges iova_ranges;
struct iommu_ioas_map map;
struct iommu_ioas_unmap unmap;
struct iommu_option option;
struct iommu_vdevice_alloc vdev;
struct iommu_veventq_alloc veventq;
struct iommu_vfio_ioas vfio_ioas;
struct iommu_viommu_alloc viommu;
#ifdef CONFIG_IOMMUFD_TEST
struct iommu_test_cmd test;
#endif
};
struct iommufd_ioctl_op {
unsigned int size;
unsigned int min_size;
unsigned int ioctl_num;
int (*execute)(struct iommufd_ucmd *ucmd);
};
#define IOCTL_OP(_ioctl, _fn, _struct, _last) \
[_IOC_NR(_ioctl) - IOMMUFD_CMD_BASE] = { \
.size = sizeof(_struct) + \
BUILD_BUG_ON_ZERO(sizeof(union ucmd_buffer) < \
sizeof(_struct)), \
.min_size = offsetofend(_struct, _last), \
.ioctl_num = _ioctl, \
.execute = _fn, \
}
static const struct iommufd_ioctl_op iommufd_ioctl_ops[] = {
IOCTL_OP(IOMMU_DESTROY, iommufd_destroy, struct iommu_destroy, id),
IOCTL_OP(IOMMU_FAULT_QUEUE_ALLOC, iommufd_fault_alloc,
struct iommu_fault_alloc, out_fault_fd),
IOCTL_OP(IOMMU_GET_HW_INFO, iommufd_get_hw_info, struct iommu_hw_info,
__reserved),
IOCTL_OP(IOMMU_HW_QUEUE_ALLOC, iommufd_hw_queue_alloc_ioctl,
struct iommu_hw_queue_alloc, length),
IOCTL_OP(IOMMU_HWPT_ALLOC, iommufd_hwpt_alloc, struct iommu_hwpt_alloc,
__reserved),
IOCTL_OP(IOMMU_HWPT_GET_DIRTY_BITMAP, iommufd_hwpt_get_dirty_bitmap,
struct iommu_hwpt_get_dirty_bitmap, data),
IOCTL_OP(IOMMU_HWPT_INVALIDATE, iommufd_hwpt_invalidate,
struct iommu_hwpt_invalidate, __reserved),
IOCTL_OP(IOMMU_HWPT_SET_DIRTY_TRACKING, iommufd_hwpt_set_dirty_tracking,
struct iommu_hwpt_set_dirty_tracking, __reserved),
IOCTL_OP(IOMMU_IOAS_ALLOC, iommufd_ioas_alloc_ioctl,
struct iommu_ioas_alloc, out_ioas_id),
IOCTL_OP(IOMMU_IOAS_ALLOW_IOVAS, iommufd_ioas_allow_iovas,
struct iommu_ioas_allow_iovas, allowed_iovas),
IOCTL_OP(IOMMU_IOAS_CHANGE_PROCESS, iommufd_ioas_change_process,
struct iommu_ioas_change_process, __reserved),
IOCTL_OP(IOMMU_IOAS_COPY, iommufd_ioas_copy, struct iommu_ioas_copy,
src_iova),
IOCTL_OP(IOMMU_IOAS_IOVA_RANGES, iommufd_ioas_iova_ranges,
struct iommu_ioas_iova_ranges, out_iova_alignment),
IOCTL_OP(IOMMU_IOAS_MAP, iommufd_ioas_map, struct iommu_ioas_map, iova),
IOCTL_OP(IOMMU_IOAS_MAP_FILE, iommufd_ioas_map_file,
struct iommu_ioas_map_file, iova),
IOCTL_OP(IOMMU_IOAS_UNMAP, iommufd_ioas_unmap, struct iommu_ioas_unmap,
length),
IOCTL_OP(IOMMU_OPTION, iommufd_option, struct iommu_option, val64),
IOCTL_OP(IOMMU_VDEVICE_ALLOC, iommufd_vdevice_alloc_ioctl,
struct iommu_vdevice_alloc, virt_id),
IOCTL_OP(IOMMU_VEVENTQ_ALLOC, iommufd_veventq_alloc,
struct iommu_veventq_alloc, out_veventq_fd),
IOCTL_OP(IOMMU_VFIO_IOAS, iommufd_vfio_ioas, struct iommu_vfio_ioas,
__reserved),
IOCTL_OP(IOMMU_VIOMMU_ALLOC, iommufd_viommu_alloc_ioctl,
struct iommu_viommu_alloc, out_viommu_id),
#ifdef CONFIG_IOMMUFD_TEST
IOCTL_OP(IOMMU_TEST_CMD, iommufd_test, struct iommu_test_cmd, last),
#endif
};
static long iommufd_fops_ioctl(struct file *filp, unsigned int cmd,
unsigned long arg)
{
struct iommufd_ctx *ictx = filp->private_data;
const struct iommufd_ioctl_op *op;
struct iommufd_ucmd ucmd = {};
union ucmd_buffer buf;
unsigned int nr;
int ret;
nr = _IOC_NR(cmd);
if (nr < IOMMUFD_CMD_BASE ||
(nr - IOMMUFD_CMD_BASE) >= ARRAY_SIZE(iommufd_ioctl_ops))
return iommufd_vfio_ioctl(ictx, cmd, arg);
ucmd.ictx = ictx;
ucmd.ubuffer = (void __user *)arg;
ret = get_user(ucmd.user_size, (u32 __user *)ucmd.ubuffer);
if (ret)
return ret;
op = &iommufd_ioctl_ops[nr - IOMMUFD_CMD_BASE];
if (op->ioctl_num != cmd)
return -ENOIOCTLCMD;
if (ucmd.user_size < op->min_size)
return -EINVAL;
ucmd.cmd = &buf;
ret = copy_struct_from_user(ucmd.cmd, op->size, ucmd.ubuffer,
ucmd.user_size);
if (ret)
return ret;
ret = op->execute(&ucmd);
if (ucmd.new_obj) {
if (ret)
iommufd_object_abort_and_destroy(ictx, ucmd.new_obj);
else
iommufd_object_finalize(ictx, ucmd.new_obj);
}
return ret;
}
static void iommufd_fops_vma_open(struct vm_area_struct *vma)
{
struct iommufd_mmap *immap = vma->vm_private_data;
refcount_inc(&immap->owner->users);
}
static void iommufd_fops_vma_close(struct vm_area_struct *vma)
{
struct iommufd_mmap *immap = vma->vm_private_data;
refcount_dec(&immap->owner->users);
}
static const struct vm_operations_struct iommufd_vma_ops = {
.open = iommufd_fops_vma_open,
.close = iommufd_fops_vma_close,
};
/* The vm_pgoff must be pre-allocated from mt_mmap, and given to user space */
static int iommufd_fops_mmap(struct file *filp, struct vm_area_struct *vma)
{
struct iommufd_ctx *ictx = filp->private_data;
size_t length = vma->vm_end - vma->vm_start;
struct iommufd_mmap *immap;
int rc;
if (!PAGE_ALIGNED(length))
return -EINVAL;
if (!(vma->vm_flags & VM_SHARED))
return -EINVAL;
if (vma->vm_flags & VM_EXEC)
return -EPERM;
mtree_lock(&ictx->mt_mmap);
/* vma->vm_pgoff carries a page-shifted start position to an immap */
immap = mtree_load(&ictx->mt_mmap, vma->vm_pgoff << PAGE_SHIFT);
if (!immap || !refcount_inc_not_zero(&immap->owner->users)) {
mtree_unlock(&ictx->mt_mmap);
return -ENXIO;
}
mtree_unlock(&ictx->mt_mmap);
/*
* mtree_load() returns the immap for any contained mmio_addr, so only
* allow the exact immap thing to be mapped
*/
if (vma->vm_pgoff != immap->vm_pgoff || length != immap->length) {
rc = -ENXIO;
goto err_refcount;
}
vma->vm_pgoff = 0;
vma->vm_private_data = immap;
vma->vm_ops = &iommufd_vma_ops;
vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
rc = io_remap_pfn_range(vma, vma->vm_start,
immap->mmio_addr >> PAGE_SHIFT, length,
vma->vm_page_prot);
if (rc)
goto err_refcount;
return 0;
err_refcount:
refcount_dec(&immap->owner->users);
return rc;
}
static const struct file_operations iommufd_fops = {
.owner = THIS_MODULE,
.open = iommufd_fops_open,
.release = iommufd_fops_release,
.unlocked_ioctl = iommufd_fops_ioctl,
.mmap = iommufd_fops_mmap,
};
/**
* iommufd_ctx_get - Get a context reference
* @ictx: Context to get
*
* The caller must already hold a valid reference to ictx.
*/
void iommufd_ctx_get(struct iommufd_ctx *ictx)
{
get_file(ictx->file);
}
EXPORT_SYMBOL_NS_GPL(iommufd_ctx_get, "IOMMUFD");
/**
* iommufd_ctx_from_file - Acquires a reference to the iommufd context
* @file: File to obtain the reference from
*
* Returns a pointer to the iommufd_ctx, otherwise ERR_PTR. The struct file
* remains owned by the caller and the caller must still do fput. On success
* the caller is responsible to call iommufd_ctx_put().
*/
struct iommufd_ctx *iommufd_ctx_from_file(struct file *file)
{
struct iommufd_ctx *ictx;
if (file->f_op != &iommufd_fops)
return ERR_PTR(-EBADFD);
ictx = file->private_data;
iommufd_ctx_get(ictx);
return ictx;
}
EXPORT_SYMBOL_NS_GPL(iommufd_ctx_from_file, "IOMMUFD");
/**
* iommufd_ctx_from_fd - Acquires a reference to the iommufd context
* @fd: File descriptor to obtain the reference from
*
* Returns a pointer to the iommufd_ctx, otherwise ERR_PTR. On success
* the caller is responsible to call iommufd_ctx_put().
*/
struct iommufd_ctx *iommufd_ctx_from_fd(int fd)
{
struct file *file;
file = fget(fd);
if (!file)
return ERR_PTR(-EBADF);
if (file->f_op != &iommufd_fops) {
fput(file);
return ERR_PTR(-EBADFD);
}
/* fget is the same as iommufd_ctx_get() */
return file->private_data;
}
EXPORT_SYMBOL_NS_GPL(iommufd_ctx_from_fd, "IOMMUFD");
/**
* iommufd_ctx_put - Put back a reference
* @ictx: Context to put back
*/
void iommufd_ctx_put(struct iommufd_ctx *ictx)
{
fput(ictx->file);
}
EXPORT_SYMBOL_NS_GPL(iommufd_ctx_put, "IOMMUFD");
#define IOMMUFD_FILE_OFFSET(_struct, _filep, _obj) \
.file_offset = (offsetof(_struct, _filep) + \
BUILD_BUG_ON_ZERO(!__same_type( \
struct file *, ((_struct *)NULL)->_filep)) + \
BUILD_BUG_ON_ZERO(offsetof(_struct, _obj)))
static const struct iommufd_object_ops iommufd_object_ops[] = {
[IOMMUFD_OBJ_ACCESS] = {
.destroy = iommufd_access_destroy_object,
},
[IOMMUFD_OBJ_DEVICE] = {
.pre_destroy = iommufd_device_pre_destroy,
.destroy = iommufd_device_destroy,
},
[IOMMUFD_OBJ_FAULT] = {
.destroy = iommufd_fault_destroy,
IOMMUFD_FILE_OFFSET(struct iommufd_fault, common.filep, common.obj),
},
[IOMMUFD_OBJ_HW_QUEUE] = {
.destroy = iommufd_hw_queue_destroy,
},
[IOMMUFD_OBJ_HWPT_PAGING] = {
.destroy = iommufd_hwpt_paging_destroy,
.abort = iommufd_hwpt_paging_abort,
},
[IOMMUFD_OBJ_HWPT_NESTED] = {
.destroy = iommufd_hwpt_nested_destroy,
.abort = iommufd_hwpt_nested_abort,
},
[IOMMUFD_OBJ_IOAS] = {
.destroy = iommufd_ioas_destroy,
},
[IOMMUFD_OBJ_VDEVICE] = {
.destroy = iommufd_vdevice_destroy,
.abort = iommufd_vdevice_abort,
},
[IOMMUFD_OBJ_VEVENTQ] = {
.destroy = iommufd_veventq_destroy,
.abort = iommufd_veventq_abort,
IOMMUFD_FILE_OFFSET(struct iommufd_veventq, common.filep, common.obj),
},
[IOMMUFD_OBJ_VIOMMU] = {
.destroy = iommufd_viommu_destroy,
},
#ifdef CONFIG_IOMMUFD_TEST
[IOMMUFD_OBJ_SELFTEST] = {
.destroy = iommufd_selftest_destroy,
},
#endif
};
static struct miscdevice iommu_misc_dev = {
.minor = MISC_DYNAMIC_MINOR,
.name = "iommu",
.fops = &iommufd_fops,
.nodename = "iommu",
.mode = 0660,
};
static struct miscdevice vfio_misc_dev = {
.minor = VFIO_MINOR,
.name = "vfio",
.fops = &iommufd_fops,
.nodename = "vfio/vfio",
.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)
{
int ret;
ret = misc_register(&iommu_misc_dev);
if (ret)
return ret;
if (IS_ENABLED(CONFIG_IOMMUFD_VFIO_CONTAINER)) {
ret = misc_register(&vfio_misc_dev);
if (ret)
goto err_misc;
}
ret = iommufd_test_init();
if (ret)
goto err_vfio_misc;
return 0;
err_vfio_misc:
if (IS_ENABLED(CONFIG_IOMMUFD_VFIO_CONTAINER))
misc_deregister(&vfio_misc_dev);
err_misc:
misc_deregister(&iommu_misc_dev);
return ret;
}
static void __exit iommufd_exit(void)
{
iommufd_test_exit();
if (IS_ENABLED(CONFIG_IOMMUFD_VFIO_CONTAINER))
misc_deregister(&vfio_misc_dev);
misc_deregister(&iommu_misc_dev);
}
module_init(iommufd_init);
module_exit(iommufd_exit);
#if IS_ENABLED(CONFIG_IOMMUFD_VFIO_CONTAINER)
MODULE_ALIAS_MISCDEV(VFIO_MINOR);
MODULE_ALIAS("devname:vfio/vfio");
#endif
MODULE_IMPORT_NS("IOMMUFD_INTERNAL");
MODULE_IMPORT_NS("IOMMUFD");
MODULE_IMPORT_NS("DMA_BUF");
MODULE_DESCRIPTION("I/O Address Space Management for passthrough devices");
MODULE_LICENSE("GPL");
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,536 @@
// SPDX-License-Identifier: GPL-2.0-only
/* Copyright (c) 2021-2022, NVIDIA CORPORATION & AFFILIATES
*/
#include <linux/file.h>
#include <linux/interval_tree.h>
#include <linux/iommu.h>
#include <linux/iommufd.h>
#include <linux/slab.h>
#include <linux/vfio.h>
#include <uapi/linux/vfio.h>
#include <uapi/linux/iommufd.h>
#include "iommufd_private.h"
static struct iommufd_ioas *get_compat_ioas(struct iommufd_ctx *ictx)
{
struct iommufd_ioas *ioas = ERR_PTR(-ENODEV);
xa_lock(&ictx->objects);
if (!ictx->vfio_ioas || !iommufd_lock_obj(&ictx->vfio_ioas->obj))
goto out_unlock;
ioas = ictx->vfio_ioas;
out_unlock:
xa_unlock(&ictx->objects);
return ioas;
}
/**
* iommufd_vfio_compat_ioas_get_id - Ensure a compat IOAS exists
* @ictx: Context to operate on
* @out_ioas_id: The IOAS ID of the compatibility IOAS
*
* Return the ID of the current compatibility IOAS. The ID can be passed into
* other functions that take an ioas_id.
*/
int iommufd_vfio_compat_ioas_get_id(struct iommufd_ctx *ictx, u32 *out_ioas_id)
{
struct iommufd_ioas *ioas;
ioas = get_compat_ioas(ictx);
if (IS_ERR(ioas))
return PTR_ERR(ioas);
*out_ioas_id = ioas->obj.id;
iommufd_put_object(ictx, &ioas->obj);
return 0;
}
EXPORT_SYMBOL_NS_GPL(iommufd_vfio_compat_ioas_get_id, "IOMMUFD_VFIO");
/**
* iommufd_vfio_compat_set_no_iommu - Called when a no-iommu device is attached
* @ictx: Context to operate on
*
* This allows selecting the VFIO_NOIOMMU_IOMMU and blocks normal types.
*/
int iommufd_vfio_compat_set_no_iommu(struct iommufd_ctx *ictx)
{
int ret;
xa_lock(&ictx->objects);
if (!ictx->vfio_ioas) {
ictx->no_iommu_mode = 1;
ret = 0;
} else {
ret = -EINVAL;
}
xa_unlock(&ictx->objects);
return ret;
}
EXPORT_SYMBOL_NS_GPL(iommufd_vfio_compat_set_no_iommu, "IOMMUFD_VFIO");
/**
* iommufd_vfio_compat_ioas_create - Ensure the compat IOAS is created
* @ictx: Context to operate on
*
* The compatibility IOAS is the IOAS that the vfio compatibility ioctls operate
* on since they do not have an IOAS ID input in their ABI. Only attaching a
* group should cause a default creation of the internal ioas, this does nothing
* if an existing ioas has already been assigned somehow.
*/
int iommufd_vfio_compat_ioas_create(struct iommufd_ctx *ictx)
{
struct iommufd_ioas *ioas = NULL;
int ret;
ioas = iommufd_ioas_alloc(ictx);
if (IS_ERR(ioas))
return PTR_ERR(ioas);
xa_lock(&ictx->objects);
/*
* VFIO won't allow attaching a container to both iommu and no iommu
* operation
*/
if (ictx->no_iommu_mode) {
ret = -EINVAL;
goto out_abort;
}
if (ictx->vfio_ioas && iommufd_lock_obj(&ictx->vfio_ioas->obj)) {
ret = 0;
iommufd_put_object(ictx, &ictx->vfio_ioas->obj);
goto out_abort;
}
ictx->vfio_ioas = ioas;
xa_unlock(&ictx->objects);
/*
* An automatically created compat IOAS is treated as a userspace
* created object. Userspace can learn the ID via IOMMU_VFIO_IOAS_GET,
* and if not manually destroyed it will be destroyed automatically
* at iommufd release.
*/
iommufd_object_finalize(ictx, &ioas->obj);
return 0;
out_abort:
xa_unlock(&ictx->objects);
iommufd_object_abort(ictx, &ioas->obj);
return ret;
}
EXPORT_SYMBOL_NS_GPL(iommufd_vfio_compat_ioas_create, "IOMMUFD_VFIO");
int iommufd_vfio_ioas(struct iommufd_ucmd *ucmd)
{
struct iommu_vfio_ioas *cmd = ucmd->cmd;
struct iommufd_ioas *ioas;
if (cmd->__reserved)
return -EOPNOTSUPP;
switch (cmd->op) {
case IOMMU_VFIO_IOAS_GET:
ioas = get_compat_ioas(ucmd->ictx);
if (IS_ERR(ioas))
return PTR_ERR(ioas);
cmd->ioas_id = ioas->obj.id;
iommufd_put_object(ucmd->ictx, &ioas->obj);
return iommufd_ucmd_respond(ucmd, sizeof(*cmd));
case IOMMU_VFIO_IOAS_SET:
ioas = iommufd_get_ioas(ucmd->ictx, cmd->ioas_id);
if (IS_ERR(ioas))
return PTR_ERR(ioas);
xa_lock(&ucmd->ictx->objects);
ucmd->ictx->vfio_ioas = ioas;
xa_unlock(&ucmd->ictx->objects);
iommufd_put_object(ucmd->ictx, &ioas->obj);
return 0;
case IOMMU_VFIO_IOAS_CLEAR:
xa_lock(&ucmd->ictx->objects);
ucmd->ictx->vfio_ioas = NULL;
xa_unlock(&ucmd->ictx->objects);
return 0;
default:
return -EOPNOTSUPP;
}
}
static int iommufd_vfio_map_dma(struct iommufd_ctx *ictx, unsigned int cmd,
void __user *arg)
{
u32 supported_flags = VFIO_DMA_MAP_FLAG_READ | VFIO_DMA_MAP_FLAG_WRITE;
size_t minsz = offsetofend(struct vfio_iommu_type1_dma_map, size);
struct vfio_iommu_type1_dma_map map;
int iommu_prot = IOMMU_CACHE;
struct iommufd_ioas *ioas;
unsigned long iova;
int rc;
if (copy_from_user(&map, arg, minsz))
return -EFAULT;
if (map.argsz < minsz || map.flags & ~supported_flags)
return -EINVAL;
if (map.flags & VFIO_DMA_MAP_FLAG_READ)
iommu_prot |= IOMMU_READ;
if (map.flags & VFIO_DMA_MAP_FLAG_WRITE)
iommu_prot |= IOMMU_WRITE;
ioas = get_compat_ioas(ictx);
if (IS_ERR(ioas))
return PTR_ERR(ioas);
/*
* Maps created through the legacy interface always use VFIO compatible
* rlimit accounting. If the user wishes to use the faster user based
* rlimit accounting then they must use the new interface.
*/
iova = map.iova;
rc = iopt_map_user_pages(ictx, &ioas->iopt, &iova, u64_to_user_ptr(map.vaddr),
map.size, iommu_prot, 0);
iommufd_put_object(ictx, &ioas->obj);
return rc;
}
static int iommufd_vfio_unmap_dma(struct iommufd_ctx *ictx, unsigned int cmd,
void __user *arg)
{
size_t minsz = offsetofend(struct vfio_iommu_type1_dma_unmap, size);
/*
* VFIO_DMA_UNMAP_FLAG_GET_DIRTY_BITMAP is obsoleted by the new
* dirty tracking direction:
* https://lore.kernel.org/kvm/20220731125503.142683-1-yishaih@nvidia.com/
* https://lore.kernel.org/kvm/20220428210933.3583-1-joao.m.martins@oracle.com/
*/
u32 supported_flags = VFIO_DMA_UNMAP_FLAG_ALL;
struct vfio_iommu_type1_dma_unmap unmap;
unsigned long unmapped = 0;
struct iommufd_ioas *ioas;
int rc;
if (copy_from_user(&unmap, arg, minsz))
return -EFAULT;
if (unmap.argsz < minsz || unmap.flags & ~supported_flags)
return -EINVAL;
ioas = get_compat_ioas(ictx);
if (IS_ERR(ioas))
return PTR_ERR(ioas);
if (unmap.flags & VFIO_DMA_UNMAP_FLAG_ALL) {
if (unmap.iova != 0 || unmap.size != 0) {
rc = -EINVAL;
goto err_put;
}
rc = iopt_unmap_all(&ioas->iopt, &unmapped);
} else {
if (READ_ONCE(ioas->iopt.disable_large_pages)) {
/*
* Create cuts at the start and last of the requested
* range. If the start IOVA is 0 then it doesn't need to
* be cut.
*/
unsigned long iovas[] = { unmap.iova + unmap.size - 1,
unmap.iova - 1 };
rc = iopt_cut_iova(&ioas->iopt, iovas,
unmap.iova ? 2 : 1);
if (rc)
goto err_put;
}
rc = iopt_unmap_iova(&ioas->iopt, unmap.iova, unmap.size,
&unmapped);
}
unmap.size = unmapped;
if (copy_to_user(arg, &unmap, minsz))
rc = -EFAULT;
err_put:
iommufd_put_object(ictx, &ioas->obj);
return rc;
}
static int iommufd_vfio_cc_iommu(struct iommufd_ctx *ictx)
{
struct iommufd_hwpt_paging *hwpt_paging;
struct iommufd_ioas *ioas;
int rc = 1;
ioas = get_compat_ioas(ictx);
if (IS_ERR(ioas))
return PTR_ERR(ioas);
mutex_lock(&ioas->mutex);
list_for_each_entry(hwpt_paging, &ioas->hwpt_list, hwpt_item) {
if (!hwpt_paging->enforce_cache_coherency) {
rc = 0;
break;
}
}
mutex_unlock(&ioas->mutex);
iommufd_put_object(ictx, &ioas->obj);
return rc;
}
static int iommufd_vfio_check_extension(struct iommufd_ctx *ictx,
unsigned long type)
{
switch (type) {
case VFIO_TYPE1_IOMMU:
case VFIO_TYPE1v2_IOMMU:
case VFIO_UNMAP_ALL:
return !ictx->no_iommu_mode;
case VFIO_NOIOMMU_IOMMU:
return IS_ENABLED(CONFIG_VFIO_NOIOMMU);
case VFIO_DMA_CC_IOMMU:
return iommufd_vfio_cc_iommu(ictx);
case __VFIO_RESERVED_TYPE1_NESTING_IOMMU:
return 0;
/*
* VFIO_DMA_MAP_FLAG_VADDR
* https://lore.kernel.org/kvm/1611939252-7240-1-git-send-email-steven.sistare@oracle.com/
* https://lore.kernel.org/all/Yz777bJZjTyLrHEQ@nvidia.com/
*
* It is hard to see how this could be implemented safely.
*/
case VFIO_UPDATE_VADDR:
default:
return 0;
}
}
static int iommufd_vfio_set_iommu(struct iommufd_ctx *ictx, unsigned long type)
{
bool no_iommu_mode = READ_ONCE(ictx->no_iommu_mode);
struct iommufd_ioas *ioas = NULL;
int rc = 0;
/*
* Emulation for NOIOMMU is imperfect in that VFIO blocks almost all
* other ioctls. We let them keep working but they mostly fail since no
* IOAS should exist.
*/
if (IS_ENABLED(CONFIG_VFIO_NOIOMMU) && type == VFIO_NOIOMMU_IOMMU &&
no_iommu_mode) {
if (!capable(CAP_SYS_RAWIO))
return -EPERM;
return 0;
}
if ((type != VFIO_TYPE1_IOMMU && type != VFIO_TYPE1v2_IOMMU) ||
no_iommu_mode)
return -EINVAL;
/* VFIO fails the set_iommu if there is no group */
ioas = get_compat_ioas(ictx);
if (IS_ERR(ioas))
return PTR_ERR(ioas);
/*
* The difference between TYPE1 and TYPE1v2 is the ability to unmap in
* the middle of mapped ranges. This is complicated by huge page support
* which creates single large IOPTEs that cannot be split by the iommu
* driver. TYPE1 is very old at this point and likely nothing uses it,
* however it is simple enough to emulate by simply disabling the
* problematic large IOPTEs. Then we can safely unmap within any range.
*/
if (type == VFIO_TYPE1_IOMMU)
rc = iopt_disable_large_pages(&ioas->iopt);
iommufd_put_object(ictx, &ioas->obj);
return rc;
}
static unsigned long iommufd_get_pagesizes(struct iommufd_ioas *ioas)
{
struct io_pagetable *iopt = &ioas->iopt;
unsigned long pgsize_bitmap = ULONG_MAX;
struct iommu_domain *domain;
unsigned long index;
down_read(&iopt->domains_rwsem);
xa_for_each(&iopt->domains, index, domain)
pgsize_bitmap &= domain->pgsize_bitmap;
/* See vfio_update_pgsize_bitmap() */
if (pgsize_bitmap & ~PAGE_MASK) {
pgsize_bitmap &= PAGE_MASK;
pgsize_bitmap |= PAGE_SIZE;
}
pgsize_bitmap = max(pgsize_bitmap, ioas->iopt.iova_alignment);
up_read(&iopt->domains_rwsem);
return pgsize_bitmap;
}
static int iommufd_fill_cap_iova(struct iommufd_ioas *ioas,
struct vfio_info_cap_header __user *cur,
size_t avail)
{
struct vfio_iommu_type1_info_cap_iova_range __user *ucap_iovas =
container_of(cur,
struct vfio_iommu_type1_info_cap_iova_range __user,
header);
struct vfio_iommu_type1_info_cap_iova_range cap_iovas = {
.header = {
.id = VFIO_IOMMU_TYPE1_INFO_CAP_IOVA_RANGE,
.version = 1,
},
};
struct interval_tree_span_iter span;
interval_tree_for_each_span(&span, &ioas->iopt.reserved_itree, 0,
ULONG_MAX) {
struct vfio_iova_range range;
if (!span.is_hole)
continue;
range.start = span.start_hole;
range.end = span.last_hole;
if (avail >= struct_size(&cap_iovas, iova_ranges,
cap_iovas.nr_iovas + 1) &&
copy_to_user(&ucap_iovas->iova_ranges[cap_iovas.nr_iovas],
&range, sizeof(range)))
return -EFAULT;
cap_iovas.nr_iovas++;
}
if (avail >= struct_size(&cap_iovas, iova_ranges, cap_iovas.nr_iovas) &&
copy_to_user(ucap_iovas, &cap_iovas, sizeof(cap_iovas)))
return -EFAULT;
return struct_size(&cap_iovas, iova_ranges, cap_iovas.nr_iovas);
}
static int iommufd_fill_cap_dma_avail(struct iommufd_ioas *ioas,
struct vfio_info_cap_header __user *cur,
size_t avail)
{
struct vfio_iommu_type1_info_dma_avail cap_dma = {
.header = {
.id = VFIO_IOMMU_TYPE1_INFO_DMA_AVAIL,
.version = 1,
},
/*
* iommufd's limit is based on the cgroup's memory limit.
* Normally vfio would return U16_MAX here, and provide a module
* parameter to adjust it. Since S390 qemu userspace actually
* pays attention and needs a value bigger than U16_MAX return
* U32_MAX.
*/
.avail = U32_MAX,
};
if (avail >= sizeof(cap_dma) &&
copy_to_user(cur, &cap_dma, sizeof(cap_dma)))
return -EFAULT;
return sizeof(cap_dma);
}
static int iommufd_vfio_iommu_get_info(struct iommufd_ctx *ictx,
void __user *arg)
{
typedef int (*fill_cap_fn)(struct iommufd_ioas *ioas,
struct vfio_info_cap_header __user *cur,
size_t avail);
static const fill_cap_fn fill_fns[] = {
iommufd_fill_cap_dma_avail,
iommufd_fill_cap_iova,
};
size_t minsz = offsetofend(struct vfio_iommu_type1_info, iova_pgsizes);
struct vfio_info_cap_header __user *last_cap = NULL;
struct vfio_iommu_type1_info info = {};
struct iommufd_ioas *ioas;
size_t total_cap_size;
int rc;
int i;
if (copy_from_user(&info, arg, minsz))
return -EFAULT;
if (info.argsz < minsz)
return -EINVAL;
minsz = min_t(size_t, info.argsz, sizeof(info));
ioas = get_compat_ioas(ictx);
if (IS_ERR(ioas))
return PTR_ERR(ioas);
info.flags = VFIO_IOMMU_INFO_PGSIZES;
info.iova_pgsizes = iommufd_get_pagesizes(ioas);
info.cap_offset = 0;
down_read(&ioas->iopt.iova_rwsem);
total_cap_size = sizeof(info);
for (i = 0; i != ARRAY_SIZE(fill_fns); i++) {
int cap_size;
if (info.argsz > total_cap_size)
cap_size = fill_fns[i](ioas, arg + total_cap_size,
info.argsz - total_cap_size);
else
cap_size = fill_fns[i](ioas, NULL, 0);
if (cap_size < 0) {
rc = cap_size;
goto out_put;
}
cap_size = ALIGN(cap_size, sizeof(u64));
if (last_cap && info.argsz >= total_cap_size &&
put_user(total_cap_size, &last_cap->next)) {
rc = -EFAULT;
goto out_put;
}
last_cap = arg + total_cap_size;
total_cap_size += cap_size;
}
/*
* If the user did not provide enough space then only some caps are
* returned and the argsz will be updated to the correct amount to get
* all caps.
*/
if (info.argsz >= total_cap_size)
info.cap_offset = sizeof(info);
info.argsz = total_cap_size;
info.flags |= VFIO_IOMMU_INFO_CAPS;
if (copy_to_user(arg, &info, minsz)) {
rc = -EFAULT;
goto out_put;
}
rc = 0;
out_put:
up_read(&ioas->iopt.iova_rwsem);
iommufd_put_object(ictx, &ioas->obj);
return rc;
}
int iommufd_vfio_ioctl(struct iommufd_ctx *ictx, unsigned int cmd,
unsigned long arg)
{
void __user *uarg = (void __user *)arg;
switch (cmd) {
case VFIO_GET_API_VERSION:
return VFIO_API_VERSION;
case VFIO_SET_IOMMU:
return iommufd_vfio_set_iommu(ictx, arg);
case VFIO_CHECK_EXTENSION:
return iommufd_vfio_check_extension(ictx, arg);
case VFIO_IOMMU_GET_INFO:
return iommufd_vfio_iommu_get_info(ictx, uarg);
case VFIO_IOMMU_MAP_DMA:
return iommufd_vfio_map_dma(ictx, cmd, uarg);
case VFIO_IOMMU_UNMAP_DMA:
return iommufd_vfio_unmap_dma(ictx, cmd, uarg);
case VFIO_IOMMU_DIRTY_PAGES:
default:
return -ENOIOCTLCMD;
}
return -ENOIOCTLCMD;
}
+440
View File
@@ -0,0 +1,440 @@
// SPDX-License-Identifier: GPL-2.0-only
/* Copyright (c) 2024, NVIDIA CORPORATION & AFFILIATES
*/
#include "iommufd_private.h"
void iommufd_viommu_destroy(struct iommufd_object *obj)
{
struct iommufd_viommu *viommu =
container_of(obj, struct iommufd_viommu, obj);
if (viommu->ops && viommu->ops->destroy)
viommu->ops->destroy(viommu);
refcount_dec(&viommu->hwpt->common.obj.users);
xa_destroy(&viommu->vdevs);
}
int iommufd_viommu_alloc_ioctl(struct iommufd_ucmd *ucmd)
{
struct iommu_viommu_alloc *cmd = ucmd->cmd;
const struct iommu_user_data user_data = {
.type = cmd->type,
.uptr = u64_to_user_ptr(cmd->data_uptr),
.len = cmd->data_len,
};
struct iommufd_hwpt_paging *hwpt_paging;
struct iommufd_viommu *viommu;
struct iommufd_device *idev;
const struct iommu_ops *ops;
size_t viommu_size;
int rc;
if (cmd->flags || cmd->type == IOMMU_VIOMMU_TYPE_DEFAULT)
return -EOPNOTSUPP;
idev = iommufd_get_device(ucmd, cmd->dev_id);
if (IS_ERR(idev))
return PTR_ERR(idev);
ops = dev_iommu_ops(idev->dev);
if (!ops->get_viommu_size || !ops->viommu_init) {
rc = -EOPNOTSUPP;
goto out_put_idev;
}
viommu_size = ops->get_viommu_size(idev->dev, cmd->type);
if (!viommu_size) {
rc = -EOPNOTSUPP;
goto out_put_idev;
}
/*
* It is a driver bug for providing a viommu_size smaller than the core
* vIOMMU structure size
*/
if (WARN_ON_ONCE(viommu_size < sizeof(*viommu))) {
rc = -EOPNOTSUPP;
goto out_put_idev;
}
hwpt_paging = iommufd_get_hwpt_paging(ucmd, cmd->hwpt_id);
if (IS_ERR(hwpt_paging)) {
rc = PTR_ERR(hwpt_paging);
goto out_put_idev;
}
if (!hwpt_paging->nest_parent) {
rc = -EINVAL;
goto out_put_hwpt;
}
viommu = (struct iommufd_viommu *)_iommufd_object_alloc_ucmd(
ucmd, viommu_size, IOMMUFD_OBJ_VIOMMU);
if (IS_ERR(viommu)) {
rc = PTR_ERR(viommu);
goto out_put_hwpt;
}
xa_init(&viommu->vdevs);
viommu->type = cmd->type;
viommu->ictx = ucmd->ictx;
viommu->hwpt = hwpt_paging;
refcount_inc(&viommu->hwpt->common.obj.users);
INIT_LIST_HEAD(&viommu->veventqs);
init_rwsem(&viommu->veventqs_rwsem);
/*
* It is the most likely case that a physical IOMMU is unpluggable. A
* pluggable IOMMU instance (if exists) is responsible for refcounting
* on its own.
*/
viommu->iommu_dev = __iommu_get_iommu_dev(idev->dev);
rc = ops->viommu_init(viommu, hwpt_paging->common.domain,
user_data.len ? &user_data : NULL);
if (rc)
goto out_put_hwpt;
/* It is a driver bug that viommu->ops isn't filled */
if (WARN_ON_ONCE(!viommu->ops)) {
rc = -EOPNOTSUPP;
goto out_put_hwpt;
}
cmd->out_viommu_id = viommu->obj.id;
rc = iommufd_ucmd_respond(ucmd, sizeof(*cmd));
out_put_hwpt:
iommufd_put_object(ucmd->ictx, &hwpt_paging->common.obj);
out_put_idev:
iommufd_put_object(ucmd->ictx, &idev->obj);
return rc;
}
void iommufd_vdevice_abort(struct iommufd_object *obj)
{
struct iommufd_vdevice *vdev =
container_of(obj, struct iommufd_vdevice, obj);
struct iommufd_viommu *viommu = vdev->viommu;
struct iommufd_device *idev = vdev->idev;
lockdep_assert_held(&idev->igroup->lock);
if (vdev->destroy)
vdev->destroy(vdev);
/* xa_cmpxchg is okay to fail if alloc failed xa_cmpxchg previously */
xa_cmpxchg(&viommu->vdevs, vdev->virt_id, vdev, NULL, GFP_KERNEL);
refcount_dec(&viommu->obj.users);
idev->vdev = NULL;
}
void iommufd_vdevice_destroy(struct iommufd_object *obj)
{
struct iommufd_vdevice *vdev =
container_of(obj, struct iommufd_vdevice, obj);
struct iommufd_device *idev = vdev->idev;
struct iommufd_ctx *ictx = idev->ictx;
mutex_lock(&idev->igroup->lock);
iommufd_vdevice_abort(obj);
mutex_unlock(&idev->igroup->lock);
iommufd_put_object(ictx, &idev->obj);
}
int iommufd_vdevice_alloc_ioctl(struct iommufd_ucmd *ucmd)
{
struct iommu_vdevice_alloc *cmd = ucmd->cmd;
struct iommufd_vdevice *vdev;
size_t vdev_size = sizeof(*vdev);
struct iommufd_viommu *viommu;
struct iommufd_device *idev;
u64 virt_id = cmd->virt_id;
int rc = 0;
/* virt_id indexes an xarray */
if (virt_id > ULONG_MAX)
return -EINVAL;
viommu = iommufd_get_viommu(ucmd, cmd->viommu_id);
if (IS_ERR(viommu))
return PTR_ERR(viommu);
idev = iommufd_get_device(ucmd, cmd->dev_id);
if (IS_ERR(idev)) {
rc = PTR_ERR(idev);
goto out_put_viommu;
}
if (viommu->iommu_dev != __iommu_get_iommu_dev(idev->dev)) {
rc = -EINVAL;
goto out_put_idev;
}
mutex_lock(&idev->igroup->lock);
if (idev->destroying) {
rc = -ENOENT;
goto out_unlock_igroup;
}
if (idev->vdev) {
rc = -EEXIST;
goto out_unlock_igroup;
}
if (viommu->ops && viommu->ops->vdevice_size) {
/*
* It is a driver bug for:
* - ops->vdevice_size smaller than the core structure size
* - not implementing a pairing ops->vdevice_init op
*/
if (WARN_ON_ONCE(viommu->ops->vdevice_size < vdev_size ||
!viommu->ops->vdevice_init)) {
rc = -EOPNOTSUPP;
goto out_unlock_igroup;
}
vdev_size = viommu->ops->vdevice_size;
}
vdev = (struct iommufd_vdevice *)_iommufd_object_alloc(
ucmd->ictx, vdev_size, IOMMUFD_OBJ_VDEVICE);
if (IS_ERR(vdev)) {
rc = PTR_ERR(vdev);
goto out_unlock_igroup;
}
vdev->virt_id = virt_id;
vdev->viommu = viommu;
refcount_inc(&viommu->obj.users);
/*
* A wait_cnt reference is held on the idev so long as we have the
* pointer. iommufd_device_pre_destroy() will revoke it before the
* idev real destruction.
*/
vdev->idev = idev;
/*
* iommufd_device_destroy() delays until idev->vdev is NULL before
* freeing the idev, which only happens once the vdev is finished
* destruction.
*/
idev->vdev = vdev;
/*
* Reserve the slot with a zero entry (reads back as NULL) until the
* vdevice_init() op accepts the vDEVICE. Only the xa_* helpers hide a
* reserved entry, so never use a raw xas_* iterator on this xarray.
*/
rc = xa_insert(&viommu->vdevs, virt_id, NULL, GFP_KERNEL);
if (rc) {
if (rc == -EBUSY)
rc = -EEXIST;
goto out_abort;
}
if (viommu->ops && viommu->ops->vdevice_init) {
rc = viommu->ops->vdevice_init(vdev);
if (rc) {
xa_release(&viommu->vdevs, virt_id);
goto out_abort;
}
}
xa_store(&viommu->vdevs, virt_id, vdev, GFP_KERNEL);
cmd->out_vdevice_id = vdev->obj.id;
rc = iommufd_ucmd_respond(ucmd, sizeof(*cmd));
if (rc)
goto out_abort;
iommufd_object_finalize(ucmd->ictx, &vdev->obj);
goto out_unlock_igroup;
out_abort:
iommufd_object_abort_and_destroy(ucmd->ictx, &vdev->obj);
out_unlock_igroup:
mutex_unlock(&idev->igroup->lock);
out_put_idev:
if (rc)
iommufd_put_object(ucmd->ictx, &idev->obj);
out_put_viommu:
iommufd_put_object(ucmd->ictx, &viommu->obj);
return rc;
}
static void iommufd_hw_queue_destroy_access(struct iommufd_ctx *ictx,
struct iommufd_access *access,
u64 base_iova, size_t length)
{
u64 aligned_iova = PAGE_ALIGN_DOWN(base_iova);
u64 offset = base_iova - aligned_iova;
iommufd_access_unpin_pages(access, aligned_iova,
PAGE_ALIGN(length + offset));
iommufd_access_detach_internal(access);
iommufd_access_destroy_internal(ictx, access);
}
void iommufd_hw_queue_destroy(struct iommufd_object *obj)
{
struct iommufd_hw_queue *hw_queue =
container_of(obj, struct iommufd_hw_queue, obj);
if (hw_queue->destroy)
hw_queue->destroy(hw_queue);
if (hw_queue->access)
iommufd_hw_queue_destroy_access(hw_queue->viommu->ictx,
hw_queue->access,
hw_queue->base_addr,
hw_queue->length);
if (hw_queue->viommu)
refcount_dec(&hw_queue->viommu->obj.users);
}
/*
* When the HW accesses the guest queue via physical addresses, the underlying
* physical pages of the guest queue must be contiguous. Also, for the security
* concern that IOMMUFD_CMD_IOAS_UNMAP could potentially remove the mappings of
* the guest queue from the nesting parent iopt while the HW is still accessing
* the guest queue memory physically, such a HW queue must require an access to
* pin the underlying pages and prevent that from happening.
*/
static struct iommufd_access *
iommufd_hw_queue_alloc_phys(struct iommu_hw_queue_alloc *cmd,
struct iommufd_viommu *viommu, phys_addr_t *base_pa)
{
u64 aligned_iova = PAGE_ALIGN_DOWN(cmd->nesting_parent_iova);
u64 offset = cmd->nesting_parent_iova - aligned_iova;
struct iommufd_access *access;
struct page **pages;
size_t max_npages;
size_t length;
size_t i;
int rc;
/* max_npages = DIV_ROUND_UP(offset + cmd->length, PAGE_SIZE) */
if (check_add_overflow(offset, cmd->length, &length))
return ERR_PTR(-ERANGE);
if (check_add_overflow(length, PAGE_SIZE - 1, &length))
return ERR_PTR(-ERANGE);
max_npages = length / PAGE_SIZE;
/* length needs to be page aligned too */
length = max_npages * PAGE_SIZE;
/*
* Use kvcalloc() to avoid memory fragmentation for a large page array.
* Set __GFP_NOWARN to avoid syzkaller blowups
*/
pages = kvzalloc_objs(*pages, max_npages, GFP_KERNEL | __GFP_NOWARN);
if (!pages)
return ERR_PTR(-ENOMEM);
access = iommufd_access_create_internal(viommu->ictx);
if (IS_ERR(access)) {
rc = PTR_ERR(access);
goto out_free;
}
rc = iommufd_access_attach_internal(access, viommu->hwpt->ioas);
if (rc)
goto out_destroy;
rc = iommufd_access_pin_pages(access, aligned_iova, length, pages, 0);
if (rc)
goto out_detach;
/* Validate if the underlying physical pages are contiguous */
for (i = 1; i < max_npages; i++) {
if (page_to_pfn(pages[i]) == page_to_pfn(pages[i - 1]) + 1)
continue;
rc = -EFAULT;
goto out_unpin;
}
*base_pa = (page_to_pfn(pages[0]) << PAGE_SHIFT) + offset;
kvfree(pages);
return access;
out_unpin:
iommufd_access_unpin_pages(access, aligned_iova, length);
out_detach:
iommufd_access_detach_internal(access);
out_destroy:
iommufd_access_destroy_internal(viommu->ictx, access);
out_free:
kvfree(pages);
return ERR_PTR(rc);
}
int iommufd_hw_queue_alloc_ioctl(struct iommufd_ucmd *ucmd)
{
struct iommu_hw_queue_alloc *cmd = ucmd->cmd;
struct iommufd_hw_queue *hw_queue;
struct iommufd_viommu *viommu;
struct iommufd_access *access;
size_t hw_queue_size;
phys_addr_t base_pa;
u64 last;
int rc;
if (cmd->flags || cmd->type == IOMMU_HW_QUEUE_TYPE_DEFAULT)
return -EOPNOTSUPP;
if (!cmd->length)
return -EINVAL;
if (check_add_overflow(cmd->nesting_parent_iova, cmd->length - 1,
&last))
return -EOVERFLOW;
viommu = iommufd_get_viommu(ucmd, cmd->viommu_id);
if (IS_ERR(viommu))
return PTR_ERR(viommu);
if (!viommu->ops || !viommu->ops->get_hw_queue_size ||
!viommu->ops->hw_queue_init_phys) {
rc = -EOPNOTSUPP;
goto out_put_viommu;
}
hw_queue_size = viommu->ops->get_hw_queue_size(viommu, cmd->type);
if (!hw_queue_size) {
rc = -EOPNOTSUPP;
goto out_put_viommu;
}
/*
* It is a driver bug for providing a hw_queue_size smaller than the
* core HW queue structure size
*/
if (WARN_ON_ONCE(hw_queue_size < sizeof(*hw_queue))) {
rc = -EOPNOTSUPP;
goto out_put_viommu;
}
hw_queue = (struct iommufd_hw_queue *)_iommufd_object_alloc_ucmd(
ucmd, hw_queue_size, IOMMUFD_OBJ_HW_QUEUE);
if (IS_ERR(hw_queue)) {
rc = PTR_ERR(hw_queue);
goto out_put_viommu;
}
access = iommufd_hw_queue_alloc_phys(cmd, viommu, &base_pa);
if (IS_ERR(access)) {
rc = PTR_ERR(access);
goto out_put_viommu;
}
hw_queue->viommu = viommu;
refcount_inc(&viommu->obj.users);
hw_queue->access = access;
hw_queue->type = cmd->type;
hw_queue->length = cmd->length;
hw_queue->base_addr = cmd->nesting_parent_iova;
rc = viommu->ops->hw_queue_init_phys(hw_queue, cmd->index, base_pa);
if (rc)
goto out_put_viommu;
cmd->out_hw_queue_id = hw_queue->obj.id;
rc = iommufd_ucmd_respond(ucmd, sizeof(*cmd));
out_put_viommu:
iommufd_put_object(ucmd->ictx, &viommu->obj);
return rc;
}