mirror of
https://github.com/torvalds/linux.git
synced 2026-04-26 18:42:25 -04:00
Introduce a new IOMMUFD_OBJ_VDEVICE to represent a physical device (struct device) against a vIOMMU (struct iommufd_viommu) object in a VM. This vDEVICE object (and its structure) holds all the infos and attributes in the VM, regarding the device related to the vIOMMU. As an initial patch, add a per-vIOMMU virtual ID. This can be: - Virtual StreamID on a nested ARM SMMUv3, an index to a Stream Table - Virtual DeviceID on a nested AMD IOMMU, an index to a Device Table - Virtual RID on a nested Intel VT-D IOMMU, an index to a Context Table Potentially, this vDEVICE structure would hold some vData for Confidential Compute Architecture (CCA). Use this virtual ID to index an "vdevs" xarray that belongs to a vIOMMU object. Add a new ioctl for vDEVICE allocations. Since a vDEVICE is a connection of a device object and an iommufd_viommu object, take two refcounts in the ioctl handler. Link: https://patch.msgid.link/r/cda8fd2263166e61b8191a3b3207e0d2b08545bf.1730836308.git.nicolinc@nvidia.com Signed-off-by: Nicolin Chen <nicolinc@nvidia.com> Signed-off-by: Jason Gunthorpe <jgg@nvidia.com>
158 lines
3.9 KiB
C
158 lines
3.9 KiB
C
// 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;
|
|
struct iommufd_hwpt_paging *hwpt_paging;
|
|
struct iommufd_viommu *viommu;
|
|
struct iommufd_device *idev;
|
|
const struct iommu_ops *ops;
|
|
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->viommu_alloc) {
|
|
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 = ops->viommu_alloc(idev->dev, hwpt_paging->common.domain,
|
|
ucmd->ictx, cmd->type);
|
|
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);
|
|
/*
|
|
* 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);
|
|
|
|
cmd->out_viommu_id = viommu->obj.id;
|
|
rc = iommufd_ucmd_respond(ucmd, sizeof(*cmd));
|
|
if (rc)
|
|
goto out_abort;
|
|
iommufd_object_finalize(ucmd->ictx, &viommu->obj);
|
|
goto out_put_hwpt;
|
|
|
|
out_abort:
|
|
iommufd_object_abort_and_destroy(ucmd->ictx, &viommu->obj);
|
|
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_destroy(struct iommufd_object *obj)
|
|
{
|
|
struct iommufd_vdevice *vdev =
|
|
container_of(obj, struct iommufd_vdevice, obj);
|
|
struct iommufd_viommu *viommu = vdev->viommu;
|
|
|
|
/* xa_cmpxchg is okay to fail if alloc failed xa_cmpxchg previously */
|
|
xa_cmpxchg(&viommu->vdevs, vdev->id, vdev, NULL, GFP_KERNEL);
|
|
refcount_dec(&viommu->obj.users);
|
|
put_device(vdev->dev);
|
|
}
|
|
|
|
int iommufd_vdevice_alloc_ioctl(struct iommufd_ucmd *ucmd)
|
|
{
|
|
struct iommu_vdevice_alloc *cmd = ucmd->cmd;
|
|
struct iommufd_vdevice *vdev, *curr;
|
|
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;
|
|
}
|
|
|
|
vdev = iommufd_object_alloc(ucmd->ictx, vdev, IOMMUFD_OBJ_VDEVICE);
|
|
if (IS_ERR(vdev)) {
|
|
rc = PTR_ERR(vdev);
|
|
goto out_put_idev;
|
|
}
|
|
|
|
vdev->id = virt_id;
|
|
vdev->dev = idev->dev;
|
|
get_device(idev->dev);
|
|
vdev->viommu = viommu;
|
|
refcount_inc(&viommu->obj.users);
|
|
|
|
curr = xa_cmpxchg(&viommu->vdevs, virt_id, NULL, vdev, GFP_KERNEL);
|
|
if (curr) {
|
|
rc = xa_err(curr) ?: -EEXIST;
|
|
goto out_abort;
|
|
}
|
|
|
|
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_put_idev;
|
|
|
|
out_abort:
|
|
iommufd_object_abort_and_destroy(ucmd->ictx, &vdev->obj);
|
|
out_put_idev:
|
|
iommufd_put_object(ucmd->ictx, &idev->obj);
|
|
out_put_viommu:
|
|
iommufd_put_object(ucmd->ictx, &viommu->obj);
|
|
return rc;
|
|
}
|