#include <linux/iommufd.h>
#include <linux/slab.h>
#include <linux/iommu.h>
#include <uapi/linux/iommufd.h>
#include "../iommu-priv.h"
#include "io_pagetable.h"
#include "iommufd_private.h"
static bool allow_unsafe_interrupts;
module_param(allow_unsafe_interrupts, bool, S_IRUGO | S_IWUSR);
MODULE_PARM_DESC(
allow_unsafe_interrupts,
"Allow IOMMUFD to bind to devices even if the platform cannot isolate "
"the MSI interrupt window. Enabling this is a security weakness.");
static void iommufd_group_release(struct kref *kref)
{
struct iommufd_group *igroup =
container_of(kref, struct iommufd_group, ref);
WARN_ON(igroup->hwpt || !list_empty(&igroup->device_list));
xa_cmpxchg(&igroup->ictx->groups, iommu_group_id(igroup->group), igroup,
NULL, GFP_KERNEL);
iommu_group_put(igroup->group);
mutex_destroy(&igroup->lock);
kfree(igroup);
}
static void iommufd_put_group(struct iommufd_group *group)
{
kref_put(&group->ref, iommufd_group_release);
}
static bool iommufd_group_try_get(struct iommufd_group *igroup,
struct iommu_group *group)
{
if (!igroup)
return false;
if (WARN_ON(igroup->group != group))
return false;
return kref_get_unless_zero(&igroup->ref);
}
static struct iommufd_group *iommufd_get_group(struct iommufd_ctx *ictx,
struct device *dev)
{
struct iommufd_group *new_igroup;
struct iommufd_group *cur_igroup;
struct iommufd_group *igroup;
struct iommu_group *group;
unsigned int id;
group = iommu_group_get(dev);
if (!group)
return ERR_PTR(-ENODEV);
id = iommu_group_id(group);
xa_lock(&ictx->groups);
igroup = xa_load(&ictx->groups, id);
if (iommufd_group_try_get(igroup, group)) {
xa_unlock(&ictx->groups);
iommu_group_put(group);
return igroup;
}
xa_unlock(&ictx->groups);
new_igroup = kzalloc(sizeof(*new_igroup), GFP_KERNEL);
if (!new_igroup) {
iommu_group_put(group);
return ERR_PTR(-ENOMEM);
}
kref_init(&new_igroup->ref);
mutex_init(&new_igroup->lock);
INIT_LIST_HEAD(&new_igroup->device_list);
new_igroup->sw_msi_start = PHYS_ADDR_MAX;
new_igroup->group = group;
new_igroup->ictx = ictx;
cur_igroup = NULL;
xa_lock(&ictx->groups);
while (true) {
igroup = __xa_cmpxchg(&ictx->groups, id, cur_igroup, new_igroup,
GFP_KERNEL);
if (xa_is_err(igroup)) {
xa_unlock(&ictx->groups);
iommufd_put_group(new_igroup);
return ERR_PTR(xa_err(igroup));
}
if (cur_igroup == igroup) {
xa_unlock(&ictx->groups);
return new_igroup;
}
if (iommufd_group_try_get(igroup, group)) {
xa_unlock(&ictx->groups);
iommufd_put_group(new_igroup);
return igroup;
}
cur_igroup = igroup;
}
}
void iommufd_device_destroy(struct iommufd_object *obj)
{
struct iommufd_device *idev =
container_of(obj, struct iommufd_device, obj);
iommu_device_release_dma_owner(idev->dev);
iommufd_put_group(idev->igroup);
if (!iommufd_selftest_is_mock_dev(idev->dev))
iommufd_ctx_put(idev->ictx);
}
struct iommufd_device *iommufd_device_bind(struct iommufd_ctx *ictx,
struct device *dev, u32 *id)
{
struct iommufd_device *idev;
struct iommufd_group *igroup;
int rc;
if (!device_iommu_capable(dev, IOMMU_CAP_CACHE_COHERENCY))
return ERR_PTR(-EINVAL);
igroup = iommufd_get_group(ictx, dev);
if (IS_ERR(igroup))
return ERR_CAST(igroup);
if (!iommufd_selftest_is_mock_dev(dev) &&
!iommu_group_has_isolated_msi(igroup->group)) {
if (!allow_unsafe_interrupts) {
rc = -EPERM;
goto out_group_put;
}
dev_warn(
dev,
"MSI interrupts are not secure, they cannot be isolated by the platform. "
"Check that platform features like interrupt remapping are enabled. "
"Use the \"allow_unsafe_interrupts\" module parameter to override\n");
}
rc = iommu_device_claim_dma_owner(dev, ictx);
if (rc)
goto out_group_put;
idev = iommufd_object_alloc(ictx, idev, IOMMUFD_OBJ_DEVICE);
if (IS_ERR(idev)) {
rc = PTR_ERR(idev);
goto out_release_owner;
}
idev->ictx = ictx;
if (!iommufd_selftest_is_mock_dev(dev))
iommufd_ctx_get(ictx);
idev->dev = dev;
idev->enforce_cache_coherency =
device_iommu_capable(dev, IOMMU_CAP_ENFORCE_CACHE_COHERENCY);
refcount_inc(&idev->obj.users);
idev->igroup = igroup;
iommufd_object_finalize(ictx, &idev->obj);
*id = idev->obj.id;
return idev;
out_release_owner:
iommu_device_release_dma_owner(dev);
out_group_put:
iommufd_put_group(igroup);
return ERR_PTR(rc);
}
EXPORT_SYMBOL_NS_GPL(iommufd_device_bind, IOMMUFD);
bool iommufd_ctx_has_group(struct iommufd_ctx *ictx, struct iommu_group *group)
{
struct iommufd_object *obj;
unsigned long index;
if (!ictx || !group)
return false;
xa_lock(&ictx->objects);
xa_for_each(&ictx->objects, index, obj) {
if (obj->type == IOMMUFD_OBJ_DEVICE &&
container_of(obj, struct iommufd_device, obj)
->igroup->group == group) {
xa_unlock(&ictx->objects);
return true;
}
}
xa_unlock(&ictx->objects);
return false;
}
EXPORT_SYMBOL_NS_GPL(iommufd_ctx_has_group, IOMMUFD);
void iommufd_device_unbind(struct iommufd_device *idev)
{
iommufd_object_destroy_user(idev->ictx, &idev->obj);
}
EXPORT_SYMBOL_NS_GPL(iommufd_device_unbind, IOMMUFD);
struct iommufd_ctx *iommufd_device_to_ictx(struct iommufd_device *idev)
{
return idev->ictx;
}
EXPORT_SYMBOL_NS_GPL(iommufd_device_to_ictx, IOMMUFD);
u32 iommufd_device_to_id(struct iommufd_device *idev)
{
return idev->obj.id;
}
EXPORT_SYMBOL_NS_GPL(iommufd_device_to_id, IOMMUFD);
static int iommufd_group_setup_msi(struct iommufd_group *igroup,
struct iommufd_hw_pagetable *hwpt)
{
phys_addr_t sw_msi_start = igroup->sw_msi_start;
int rc;
if (sw_msi_start != PHYS_ADDR_MAX && !hwpt->msi_cookie) {
rc = iommu_get_msi_cookie(hwpt->domain, sw_msi_start);
if (rc)
return rc;
hwpt->msi_cookie = true;
}
return 0;
}
int iommufd_hw_pagetable_attach(struct iommufd_hw_pagetable *hwpt,
struct iommufd_device *idev)
{
int rc;
mutex_lock(&idev->igroup->lock);
if (idev->igroup->hwpt != NULL && idev->igroup->hwpt != hwpt) {
rc = -EINVAL;
goto err_unlock;
}
if (idev->enforce_cache_coherency) {
rc = iommufd_hw_pagetable_enforce_cc(hwpt);
if (rc)
goto err_unlock;
}
rc = iopt_table_enforce_dev_resv_regions(&hwpt->ioas->iopt, idev->dev,
&idev->igroup->sw_msi_start);
if (rc)
goto err_unlock;
if (list_empty(&idev->igroup->device_list)) {
rc = iommufd_group_setup_msi(idev->igroup, hwpt);
if (rc)
goto err_unresv;
rc = iommu_attach_group(hwpt->domain, idev->igroup->group);
if (rc)
goto err_unresv;
idev->igroup->hwpt = hwpt;
}
refcount_inc(&hwpt->obj.users);
list_add_tail(&idev->group_item, &idev->igroup->device_list);
mutex_unlock(&idev->igroup->lock);
return 0;
err_unresv:
iopt_remove_reserved_iova(&hwpt->ioas->iopt, idev->dev);
err_unlock:
mutex_unlock(&idev->igroup->lock);
return rc;
}
struct iommufd_hw_pagetable *
iommufd_hw_pagetable_detach(struct iommufd_device *idev)
{
struct iommufd_hw_pagetable *hwpt = idev->igroup->hwpt;
mutex_lock(&idev->igroup->lock);
list_del(&idev->group_item);
if (list_empty(&idev->igroup->device_list)) {
iommu_detach_group(hwpt->domain, idev->igroup->group);
idev->igroup->hwpt = NULL;
}
iopt_remove_reserved_iova(&hwpt->ioas->iopt, idev->dev);
mutex_unlock(&idev->igroup->lock);
return hwpt;
}
static struct iommufd_hw_pagetable *
iommufd_device_do_attach(struct iommufd_device *idev,
struct iommufd_hw_pagetable *hwpt)
{
int rc;
rc = iommufd_hw_pagetable_attach(hwpt, idev);
if (rc)
return ERR_PTR(rc);
return NULL;
}
static struct iommufd_hw_pagetable *
iommufd_device_do_replace(struct iommufd_device *idev,
struct iommufd_hw_pagetable *hwpt)
{
struct iommufd_group *igroup = idev->igroup;
struct iommufd_hw_pagetable *old_hwpt;
unsigned int num_devices = 0;
struct iommufd_device *cur;
int rc;
mutex_lock(&idev->igroup->lock);
if (igroup->hwpt == NULL) {
rc = -EINVAL;
goto err_unlock;
}
if (hwpt == igroup->hwpt) {
mutex_unlock(&idev->igroup->lock);
return NULL;
}
list_for_each_entry(cur, &igroup->device_list, group_item) {
num_devices++;
if (cur->enforce_cache_coherency) {
rc = iommufd_hw_pagetable_enforce_cc(hwpt);
if (rc)
goto err_unlock;
}
}
old_hwpt = igroup->hwpt;
if (hwpt->ioas != old_hwpt->ioas) {
list_for_each_entry(cur, &igroup->device_list, group_item) {
rc = iopt_table_enforce_dev_resv_regions(
&hwpt->ioas->iopt, cur->dev, NULL);
if (rc)
goto err_unresv;
}
}
rc = iommufd_group_setup_msi(idev->igroup, hwpt);
if (rc)
goto err_unresv;
rc = iommu_group_replace_domain(igroup->group, hwpt->domain);
if (rc)
goto err_unresv;
if (hwpt->ioas != old_hwpt->ioas) {
list_for_each_entry(cur, &igroup->device_list, group_item)
iopt_remove_reserved_iova(&old_hwpt->ioas->iopt,
cur->dev);
}
igroup->hwpt = hwpt;
refcount_add(num_devices, &hwpt->obj.users);
if (num_devices > 1)
WARN_ON(refcount_sub_and_test(num_devices - 1,
&old_hwpt->obj.users));
mutex_unlock(&idev->igroup->lock);
return old_hwpt;
err_unresv:
list_for_each_entry(cur, &igroup->device_list, group_item)
iopt_remove_reserved_iova(&hwpt->ioas->iopt, cur->dev);
err_unlock:
mutex_unlock(&idev->igroup->lock);
return ERR_PTR(rc);
}
typedef struct iommufd_hw_pagetable *(*attach_fn)(
struct iommufd_device *idev, struct iommufd_hw_pagetable *hwpt);
static struct iommufd_hw_pagetable *
iommufd_device_auto_get_domain(struct iommufd_device *idev,
struct iommufd_ioas *ioas, u32 *pt_id,
attach_fn do_attach)
{
bool immediate_attach = do_attach == iommufd_device_do_attach;
struct iommufd_hw_pagetable *destroy_hwpt;
struct iommufd_hw_pagetable *hwpt;
mutex_lock(&ioas->mutex);
list_for_each_entry(hwpt, &ioas->hwpt_list, hwpt_item) {
if (!hwpt->auto_domain)
continue;
if (!iommufd_lock_obj(&hwpt->obj))
continue;
destroy_hwpt = (*do_attach)(idev, hwpt);
if (IS_ERR(destroy_hwpt)) {
iommufd_put_object(&hwpt->obj);
if (PTR_ERR(destroy_hwpt) == -EINVAL)
continue;
goto out_unlock;
}
*pt_id = hwpt->obj.id;
iommufd_put_object(&hwpt->obj);
goto out_unlock;
}
hwpt = iommufd_hw_pagetable_alloc(idev->ictx, ioas, idev,
immediate_attach);
if (IS_ERR(hwpt)) {
destroy_hwpt = ERR_CAST(hwpt);
goto out_unlock;
}
if (!immediate_attach) {
destroy_hwpt = (*do_attach)(idev, hwpt);
if (IS_ERR(destroy_hwpt))
goto out_abort;
} else {
destroy_hwpt = NULL;
}
hwpt->auto_domain = true;
*pt_id = hwpt->obj.id;
iommufd_object_finalize(idev->ictx, &hwpt->obj);
mutex_unlock(&ioas->mutex);
return destroy_hwpt;
out_abort:
iommufd_object_abort_and_destroy(idev->ictx, &hwpt->obj);
out_unlock:
mutex_unlock(&ioas->mutex);
return destroy_hwpt;
}
static int iommufd_device_change_pt(struct iommufd_device *idev, u32 *pt_id,
attach_fn do_attach)
{
struct iommufd_hw_pagetable *destroy_hwpt;
struct iommufd_object *pt_obj;
pt_obj = iommufd_get_object(idev->ictx, *pt_id, IOMMUFD_OBJ_ANY);
if (IS_ERR(pt_obj))
return PTR_ERR(pt_obj);
switch (pt_obj->type) {
case IOMMUFD_OBJ_HW_PAGETABLE: {
struct iommufd_hw_pagetable *hwpt =
container_of(pt_obj, struct iommufd_hw_pagetable, obj);
destroy_hwpt = (*do_attach)(idev, hwpt);
if (IS_ERR(destroy_hwpt))
goto out_put_pt_obj;
break;
}
case IOMMUFD_OBJ_IOAS: {
struct iommufd_ioas *ioas =
container_of(pt_obj, struct iommufd_ioas, obj);
destroy_hwpt = iommufd_device_auto_get_domain(idev, ioas, pt_id,
do_attach);
if (IS_ERR(destroy_hwpt))
goto out_put_pt_obj;
break;
}
default:
destroy_hwpt = ERR_PTR(-EINVAL);
goto out_put_pt_obj;
}
iommufd_put_object(pt_obj);
if (destroy_hwpt)
iommufd_hw_pagetable_put(idev->ictx, destroy_hwpt);
return 0;
out_put_pt_obj:
iommufd_put_object(pt_obj);
return PTR_ERR(destroy_hwpt);
}
int iommufd_device_attach(struct iommufd_device *idev, u32 *pt_id)
{
int rc;
rc = iommufd_device_change_pt(idev, pt_id, &iommufd_device_do_attach);
if (rc)
return rc;
refcount_inc(&idev->obj.users);
return 0;
}
EXPORT_SYMBOL_NS_GPL(iommufd_device_attach, IOMMUFD);
int iommufd_device_replace(struct iommufd_device *idev, u32 *pt_id)
{
return iommufd_device_change_pt(idev, pt_id,
&iommufd_device_do_replace);
}
EXPORT_SYMBOL_NS_GPL(iommufd_device_replace, IOMMUFD);
void iommufd_device_detach(struct iommufd_device *idev)
{
struct iommufd_hw_pagetable *hwpt;
hwpt = iommufd_hw_pagetable_detach(idev);
iommufd_hw_pagetable_put(idev->ictx, hwpt);
refcount_dec(&idev->obj.users);
}
EXPORT_SYMBOL_NS_GPL(iommufd_device_detach, IOMMUFD);
static int iommufd_access_change_ioas(struct iommufd_access *access,
struct iommufd_ioas *new_ioas)
{
u32 iopt_access_list_id = access->iopt_access_list_id;
struct iommufd_ioas *cur_ioas = access->ioas;
int rc;
lockdep_assert_held(&access->ioas_lock);
if (cur_ioas != access->ioas_unpin)
return -EBUSY;
if (cur_ioas == new_ioas)
return 0;
access->ioas = NULL;
if (new_ioas) {
rc = iopt_add_access(&new_ioas->iopt, access);
if (rc) {
access->ioas = cur_ioas;
return rc;
}
refcount_inc(&new_ioas->obj.users);
}
if (cur_ioas) {
if (access->ops->unmap) {
mutex_unlock(&access->ioas_lock);
access->ops->unmap(access->data, 0, ULONG_MAX);
mutex_lock(&access->ioas_lock);
}
iopt_remove_access(&cur_ioas->iopt, access, iopt_access_list_id);
refcount_dec(&cur_ioas->obj.users);
}
access->ioas = new_ioas;
access->ioas_unpin = new_ioas;
return 0;
}
static int iommufd_access_change_ioas_id(struct iommufd_access *access, u32 id)
{
struct iommufd_ioas *ioas = iommufd_get_ioas(access->ictx, id);
int rc;
if (IS_ERR(ioas))
return PTR_ERR(ioas);
rc = iommufd_access_change_ioas(access, ioas);
iommufd_put_object(&ioas->obj);
return rc;
}
void iommufd_access_destroy_object(struct iommufd_object *obj)
{
struct iommufd_access *access =
container_of(obj, struct iommufd_access, obj);
mutex_lock(&access->ioas_lock);
if (access->ioas)
WARN_ON(iommufd_access_change_ioas(access, NULL));
mutex_unlock(&access->ioas_lock);
iommufd_ctx_put(access->ictx);
}
struct iommufd_access *
iommufd_access_create(struct iommufd_ctx *ictx,
const struct iommufd_access_ops *ops, void *data, u32 *id)
{
struct iommufd_access *access;
access = iommufd_object_alloc(ictx, access, IOMMUFD_OBJ_ACCESS);
if (IS_ERR(access))
return access;
access->data = data;
access->ops = ops;
if (ops->needs_pin_pages)
access->iova_alignment = PAGE_SIZE;
else
access->iova_alignment = 1;
refcount_inc(&access->obj.users);
access->ictx = ictx;
iommufd_ctx_get(ictx);
iommufd_object_finalize(ictx, &access->obj);
*id = access->obj.id;
mutex_init(&access->ioas_lock);
return access;
}
EXPORT_SYMBOL_NS_GPL(iommufd_access_create, IOMMUFD);
void iommufd_access_destroy(struct iommufd_access *access)
{
iommufd_object_destroy_user(access->ictx, &access->obj);
}
EXPORT_SYMBOL_NS_GPL(iommufd_access_destroy, IOMMUFD);
void iommufd_access_detach(struct iommufd_access *access)
{
mutex_lock(&access->ioas_lock);
if (WARN_ON(!access->ioas)) {
mutex_unlock(&access->ioas_lock);
return;
}
WARN_ON(iommufd_access_change_ioas(access, NULL));
mutex_unlock(&access->ioas_lock);
}
EXPORT_SYMBOL_NS_GPL(iommufd_access_detach, IOMMUFD);
int iommufd_access_attach(struct iommufd_access *access, u32 ioas_id)
{
int rc;
mutex_lock(&access->ioas_lock);
if (WARN_ON(access->ioas)) {
mutex_unlock(&access->ioas_lock);
return -EINVAL;
}
rc = iommufd_access_change_ioas_id(access, ioas_id);
mutex_unlock(&access->ioas_lock);
return rc;
}
EXPORT_SYMBOL_NS_GPL(iommufd_access_attach, IOMMUFD);
int iommufd_access_replace(struct iommufd_access *access, u32 ioas_id)
{
int rc;
mutex_lock(&access->ioas_lock);
if (!access->ioas) {
mutex_unlock(&access->ioas_lock);
return -ENOENT;
}
rc = iommufd_access_change_ioas_id(access, ioas_id);
mutex_unlock(&access->ioas_lock);
return rc;
}
EXPORT_SYMBOL_NS_GPL(iommufd_access_replace, IOMMUFD);
void iommufd_access_notify_unmap(struct io_pagetable *iopt, unsigned long iova,
unsigned long length)
{
struct iommufd_ioas *ioas =
container_of(iopt, struct iommufd_ioas, iopt);
struct iommufd_access *access;
unsigned long index;
xa_lock(&ioas->iopt.access_list);
xa_for_each(&ioas->iopt.access_list, index, access) {
if (!iommufd_lock_obj(&access->obj))
continue;
xa_unlock(&ioas->iopt.access_list);
access->ops->unmap(access->data, iova, length);
iommufd_put_object(&access->obj);
xa_lock(&ioas->iopt.access_list);
}
xa_unlock(&ioas->iopt.access_list);
}
void iommufd_access_unpin_pages(struct iommufd_access *access,
unsigned long iova, unsigned long length)
{
struct iopt_area_contig_iter iter;
struct io_pagetable *iopt;
unsigned long last_iova;
struct iopt_area *area;
if (WARN_ON(!length) ||
WARN_ON(check_add_overflow(iova, length - 1, &last_iova)))
return;
mutex_lock(&access->ioas_lock);
if (WARN_ON(!access->ioas_unpin)) {
mutex_unlock(&access->ioas_lock);
return;
}
iopt = &access->ioas_unpin->iopt;
down_read(&iopt->iova_rwsem);
iopt_for_each_contig_area(&iter, area, iopt, iova, last_iova)
iopt_area_remove_access(
area, iopt_area_iova_to_index(area, iter.cur_iova),
iopt_area_iova_to_index(
area,
min(last_iova, iopt_area_last_iova(area))));
WARN_ON(!iopt_area_contig_done(&iter));
up_read(&iopt->iova_rwsem);
mutex_unlock(&access->ioas_lock);
}
EXPORT_SYMBOL_NS_GPL(iommufd_access_unpin_pages, IOMMUFD);
static bool iopt_area_contig_is_aligned(struct iopt_area_contig_iter *iter)
{
if (iopt_area_start_byte(iter->area, iter->cur_iova) % PAGE_SIZE)
return false;
if (!iopt_area_contig_done(iter) &&
(iopt_area_start_byte(iter->area, iopt_area_last_iova(iter->area)) %
PAGE_SIZE) != (PAGE_SIZE - 1))
return false;
return true;
}
static bool check_area_prot(struct iopt_area *area, unsigned int flags)
{
if (flags & IOMMUFD_ACCESS_RW_WRITE)
return area->iommu_prot & IOMMU_WRITE;
return area->iommu_prot & IOMMU_READ;
}
int iommufd_access_pin_pages(struct iommufd_access *access, unsigned long iova,
unsigned long length, struct page **out_pages,
unsigned int flags)
{
struct iopt_area_contig_iter iter;
struct io_pagetable *iopt;
unsigned long last_iova;
struct iopt_area *area;
int rc;
if (IS_ENABLED(CONFIG_IOMMUFD_TEST) &&
WARN_ON(access->iova_alignment != PAGE_SIZE || !access->ops->unmap))
return -EINVAL;
if (!length)
return -EINVAL;
if (check_add_overflow(iova, length - 1, &last_iova))
return -EOVERFLOW;
mutex_lock(&access->ioas_lock);
if (!access->ioas) {
mutex_unlock(&access->ioas_lock);
return -ENOENT;
}
iopt = &access->ioas->iopt;
down_read(&iopt->iova_rwsem);
iopt_for_each_contig_area(&iter, area, iopt, iova, last_iova) {
unsigned long last = min(last_iova, iopt_area_last_iova(area));
unsigned long last_index = iopt_area_iova_to_index(area, last);
unsigned long index =
iopt_area_iova_to_index(area, iter.cur_iova);
if (area->prevent_access ||
!iopt_area_contig_is_aligned(&iter)) {
rc = -EINVAL;
goto err_remove;
}
if (!check_area_prot(area, flags)) {
rc = -EPERM;
goto err_remove;
}
rc = iopt_area_add_access(area, index, last_index, out_pages,
flags);
if (rc)
goto err_remove;
out_pages += last_index - index + 1;
}
if (!iopt_area_contig_done(&iter)) {
rc = -ENOENT;
goto err_remove;
}
up_read(&iopt->iova_rwsem);
mutex_unlock(&access->ioas_lock);
return 0;
err_remove:
if (iova < iter.cur_iova) {
last_iova = iter.cur_iova - 1;
iopt_for_each_contig_area(&iter, area, iopt, iova, last_iova)
iopt_area_remove_access(
area,
iopt_area_iova_to_index(area, iter.cur_iova),
iopt_area_iova_to_index(
area, min(last_iova,
iopt_area_last_iova(area))));
}
up_read(&iopt->iova_rwsem);
mutex_unlock(&access->ioas_lock);
return rc;
}
EXPORT_SYMBOL_NS_GPL(iommufd_access_pin_pages, IOMMUFD);
int iommufd_access_rw(struct iommufd_access *access, unsigned long iova,
void *data, size_t length, unsigned int flags)
{
struct iopt_area_contig_iter iter;
struct io_pagetable *iopt;
struct iopt_area *area;
unsigned long last_iova;
int rc;
if (!length)
return -EINVAL;
if (check_add_overflow(iova, length - 1, &last_iova))
return -EOVERFLOW;
mutex_lock(&access->ioas_lock);
if (!access->ioas) {
mutex_unlock(&access->ioas_lock);
return -ENOENT;
}
iopt = &access->ioas->iopt;
down_read(&iopt->iova_rwsem);
iopt_for_each_contig_area(&iter, area, iopt, iova, last_iova) {
unsigned long last = min(last_iova, iopt_area_last_iova(area));
unsigned long bytes = (last - iter.cur_iova) + 1;
if (area->prevent_access) {
rc = -EINVAL;
goto err_out;
}
if (!check_area_prot(area, flags)) {
rc = -EPERM;
goto err_out;
}
rc = iopt_pages_rw_access(
area->pages, iopt_area_start_byte(area, iter.cur_iova),
data, bytes, flags);
if (rc)
goto err_out;
data += bytes;
}
if (!iopt_area_contig_done(&iter))
rc = -ENOENT;
err_out:
up_read(&iopt->iova_rwsem);
mutex_unlock(&access->ioas_lock);
return rc;
}
EXPORT_SYMBOL_NS_GPL(iommufd_access_rw, IOMMUFD);
int iommufd_get_hw_info(struct iommufd_ucmd *ucmd)
{
struct iommu_hw_info *cmd = ucmd->cmd;
void __user *user_ptr = u64_to_user_ptr(cmd->data_uptr);
const struct iommu_ops *ops;
struct iommufd_device *idev;
unsigned int data_len;
unsigned int copy_len;
void *data;
int rc;
if (cmd->flags || cmd->__reserved)
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->hw_info) {
data = ops->hw_info(idev->dev, &data_len, &cmd->out_data_type);
if (IS_ERR(data)) {
rc = PTR_ERR(data);
goto out_put;
}
if (WARN_ON_ONCE(cmd->out_data_type ==
IOMMU_HW_INFO_TYPE_NONE)) {
rc = -ENODEV;
goto out_free;
}
} else {
cmd->out_data_type = IOMMU_HW_INFO_TYPE_NONE;
data_len = 0;
data = NULL;
}
copy_len = min(cmd->data_len, data_len);
if (copy_to_user(user_ptr, data, copy_len)) {
rc = -EFAULT;
goto out_free;
}
if (copy_len < cmd->data_len) {
if (clear_user(user_ptr + copy_len, cmd->data_len - copy_len)) {
rc = -EFAULT;
goto out_free;
}
}
cmd->data_len = data_len;
rc = iommufd_ucmd_respond(ucmd, sizeof(*cmd));
out_free:
kfree(data);
out_put:
iommufd_put_object(&idev->obj);
return rc;
}