mirror of
https://mirrors.bfsu.edu.cn/git/linux.git
synced 2024-11-23 20:24:12 +08:00
iommufd for 6.8
This brings the first of three planned user IO page table invalidation operations: - IOMMU_HWPT_INVALIDATE allows invalidating the IOTLB integrated into the iommu itself. The Intel implementation will also generate an ATC invalidation to flush the device IOTLB as it unambiguously knows the device, but other HW will not. It goes along with the prior PR to implement userspace IO page tables (aka nested translation for VMs) to allow Intel to have full functionality for simple cases. An Intel implementation of the operation is provided. Fix a small bug in the selftest mock iommu driver probe. -----BEGIN PGP SIGNATURE----- iHQEABYIAB0WIQRRRCHOFoQz/8F5bUaFwuHvBreFYQUCZaFiRQAKCRCFwuHvBreF YbmgAP9Z0+cAUPKxUKaMRls8YR+gmaOCniSkqBlyrxcib+F/WAD2NPLcBPBRk2o7 GfXPIrovx96Btf8M40AFdiTEp7LABw== =9POe -----END PGP SIGNATURE----- Merge tag 'for-linus-iommufd' of git://git.kernel.org/pub/scm/linux/kernel/git/jgg/iommufd Pull iommufd updates from Jason Gunthorpe: "This brings the first of three planned user IO page table invalidation operations: - IOMMU_HWPT_INVALIDATE allows invalidating the IOTLB integrated into the iommu itself. The Intel implementation will also generate an ATC invalidation to flush the device IOTLB as it unambiguously knows the device, but other HW will not. It goes along with the prior PR to implement userspace IO page tables (aka nested translation for VMs) to allow Intel to have full functionality for simple cases. An Intel implementation of the operation is provided. Also fix a small bug in the selftest mock iommu driver probe" * tag 'for-linus-iommufd' of git://git.kernel.org/pub/scm/linux/kernel/git/jgg/iommufd: iommufd/selftest: Check the bus type during probe iommu/vt-d: Add iotlb flush for nested domain iommufd: Add data structure for Intel VT-d stage-1 cache invalidation iommufd/selftest: Add coverage for IOMMU_HWPT_INVALIDATE ioctl iommufd/selftest: Add IOMMU_TEST_OP_MD_CHECK_IOTLB test op iommufd/selftest: Add mock_domain_cache_invalidate_user support iommu: Add iommu_copy_struct_from_user_array helper iommufd: Add IOMMU_HWPT_INVALIDATE iommu: Add cache_invalidate_user op
This commit is contained in:
commit
86c4d58a99
@ -73,9 +73,97 @@ static void intel_nested_domain_free(struct iommu_domain *domain)
|
||||
kfree(to_dmar_domain(domain));
|
||||
}
|
||||
|
||||
static void nested_flush_dev_iotlb(struct dmar_domain *domain, u64 addr,
|
||||
unsigned int mask)
|
||||
{
|
||||
struct device_domain_info *info;
|
||||
unsigned long flags;
|
||||
u16 sid, qdep;
|
||||
|
||||
spin_lock_irqsave(&domain->lock, flags);
|
||||
list_for_each_entry(info, &domain->devices, link) {
|
||||
if (!info->ats_enabled)
|
||||
continue;
|
||||
sid = info->bus << 8 | info->devfn;
|
||||
qdep = info->ats_qdep;
|
||||
qi_flush_dev_iotlb(info->iommu, sid, info->pfsid,
|
||||
qdep, addr, mask);
|
||||
quirk_extra_dev_tlb_flush(info, addr, mask,
|
||||
IOMMU_NO_PASID, qdep);
|
||||
}
|
||||
spin_unlock_irqrestore(&domain->lock, flags);
|
||||
}
|
||||
|
||||
static void intel_nested_flush_cache(struct dmar_domain *domain, u64 addr,
|
||||
unsigned long npages, bool ih)
|
||||
{
|
||||
struct iommu_domain_info *info;
|
||||
unsigned int mask;
|
||||
unsigned long i;
|
||||
|
||||
xa_for_each(&domain->iommu_array, i, info)
|
||||
qi_flush_piotlb(info->iommu,
|
||||
domain_id_iommu(domain, info->iommu),
|
||||
IOMMU_NO_PASID, addr, npages, ih);
|
||||
|
||||
if (!domain->has_iotlb_device)
|
||||
return;
|
||||
|
||||
if (npages == U64_MAX)
|
||||
mask = 64 - VTD_PAGE_SHIFT;
|
||||
else
|
||||
mask = ilog2(__roundup_pow_of_two(npages));
|
||||
|
||||
nested_flush_dev_iotlb(domain, addr, mask);
|
||||
}
|
||||
|
||||
static int intel_nested_cache_invalidate_user(struct iommu_domain *domain,
|
||||
struct iommu_user_data_array *array)
|
||||
{
|
||||
struct dmar_domain *dmar_domain = to_dmar_domain(domain);
|
||||
struct iommu_hwpt_vtd_s1_invalidate inv_entry;
|
||||
u32 index, processed = 0;
|
||||
int ret = 0;
|
||||
|
||||
if (array->type != IOMMU_HWPT_INVALIDATE_DATA_VTD_S1) {
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
for (index = 0; index < array->entry_num; index++) {
|
||||
ret = iommu_copy_struct_from_user_array(&inv_entry, array,
|
||||
IOMMU_HWPT_INVALIDATE_DATA_VTD_S1,
|
||||
index, __reserved);
|
||||
if (ret)
|
||||
break;
|
||||
|
||||
if ((inv_entry.flags & ~IOMMU_VTD_INV_FLAGS_LEAF) ||
|
||||
inv_entry.__reserved) {
|
||||
ret = -EOPNOTSUPP;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!IS_ALIGNED(inv_entry.addr, VTD_PAGE_SIZE) ||
|
||||
((inv_entry.npages == U64_MAX) && inv_entry.addr)) {
|
||||
ret = -EINVAL;
|
||||
break;
|
||||
}
|
||||
|
||||
intel_nested_flush_cache(dmar_domain, inv_entry.addr,
|
||||
inv_entry.npages,
|
||||
inv_entry.flags & IOMMU_VTD_INV_FLAGS_LEAF);
|
||||
processed++;
|
||||
}
|
||||
|
||||
out:
|
||||
array->entry_num = processed;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct iommu_domain_ops intel_nested_domain_ops = {
|
||||
.attach_dev = intel_nested_attach_dev,
|
||||
.free = intel_nested_domain_free,
|
||||
.cache_invalidate_user = intel_nested_cache_invalidate_user,
|
||||
};
|
||||
|
||||
struct iommu_domain *intel_nested_domain_alloc(struct iommu_domain *parent,
|
||||
|
@ -373,3 +373,44 @@ int iommufd_hwpt_get_dirty_bitmap(struct iommufd_ucmd *ucmd)
|
||||
iommufd_put_object(ucmd->ictx, &hwpt_paging->common.obj);
|
||||
return rc;
|
||||
}
|
||||
|
||||
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_hw_pagetable *hwpt;
|
||||
u32 done_num = 0;
|
||||
int rc;
|
||||
|
||||
if (cmd->__reserved) {
|
||||
rc = -EOPNOTSUPP;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (cmd->entry_num && (!cmd->data_uptr || !cmd->entry_len)) {
|
||||
rc = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
hwpt = iommufd_get_hwpt_nested(ucmd, cmd->hwpt_id);
|
||||
if (IS_ERR(hwpt)) {
|
||||
rc = PTR_ERR(hwpt);
|
||||
goto out;
|
||||
}
|
||||
|
||||
rc = hwpt->domain->ops->cache_invalidate_user(hwpt->domain,
|
||||
&data_array);
|
||||
done_num = data_array.entry_num;
|
||||
|
||||
iommufd_put_object(ucmd->ictx, &hwpt->obj);
|
||||
out:
|
||||
cmd->entry_num = done_num;
|
||||
if (iommufd_ucmd_respond(ucmd, sizeof(*cmd)))
|
||||
return -EFAULT;
|
||||
return rc;
|
||||
}
|
||||
|
@ -328,6 +328,15 @@ iommufd_get_hwpt_paging(struct iommufd_ucmd *ucmd, u32 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);
|
||||
|
||||
@ -345,6 +354,7 @@ 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)
|
||||
|
@ -21,6 +21,7 @@ enum {
|
||||
IOMMU_TEST_OP_ACCESS_REPLACE_IOAS,
|
||||
IOMMU_TEST_OP_MOCK_DOMAIN_FLAGS,
|
||||
IOMMU_TEST_OP_DIRTY,
|
||||
IOMMU_TEST_OP_MD_CHECK_IOTLB,
|
||||
};
|
||||
|
||||
enum {
|
||||
@ -121,6 +122,10 @@ struct iommu_test_cmd {
|
||||
__aligned_u64 uptr;
|
||||
__aligned_u64 out_nr_dirty;
|
||||
} dirty;
|
||||
struct {
|
||||
__u32 id;
|
||||
__u32 iotlb;
|
||||
} check_iotlb;
|
||||
};
|
||||
__u32 last;
|
||||
};
|
||||
@ -148,4 +153,22 @@ struct iommu_hwpt_selftest {
|
||||
__u32 iotlb;
|
||||
};
|
||||
|
||||
/* 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;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
@ -322,6 +322,7 @@ union ucmd_buffer {
|
||||
struct iommu_hw_info info;
|
||||
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;
|
||||
@ -360,6 +361,8 @@ static const struct iommufd_ioctl_op iommufd_ioctl_ops[] = {
|
||||
__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,
|
||||
|
@ -25,6 +25,19 @@ static struct iommu_domain_ops domain_nested_ops;
|
||||
|
||||
size_t iommufd_test_memory_limit = 65536;
|
||||
|
||||
struct mock_bus_type {
|
||||
struct bus_type bus;
|
||||
struct notifier_block nb;
|
||||
};
|
||||
|
||||
static struct mock_bus_type iommufd_mock_bus_type = {
|
||||
.bus = {
|
||||
.name = "iommufd_mock",
|
||||
},
|
||||
};
|
||||
|
||||
static atomic_t mock_dev_num;
|
||||
|
||||
enum {
|
||||
MOCK_DIRTY_TRACK = 1,
|
||||
MOCK_IO_PAGE_SIZE = PAGE_SIZE / 2,
|
||||
@ -437,6 +450,8 @@ static struct iommu_device mock_iommu_device = {
|
||||
|
||||
static struct iommu_device *mock_probe_device(struct device *dev)
|
||||
{
|
||||
if (dev->bus != &iommufd_mock_bus_type.bus)
|
||||
return ERR_PTR(-ENODEV);
|
||||
return &mock_iommu_device;
|
||||
}
|
||||
|
||||
@ -473,9 +488,59 @@ static void mock_domain_free_nested(struct iommu_domain *domain)
|
||||
kfree(mock_nested);
|
||||
}
|
||||
|
||||
static int
|
||||
mock_domain_cache_invalidate_user(struct iommu_domain *domain,
|
||||
struct iommu_user_data_array *array)
|
||||
{
|
||||
struct mock_iommu_domain_nested *mock_nested =
|
||||
container_of(domain, struct mock_iommu_domain_nested, domain);
|
||||
struct iommu_hwpt_invalidate_selftest inv;
|
||||
u32 processed = 0;
|
||||
int i = 0, j;
|
||||
int rc = 0;
|
||||
|
||||
if (array->type != IOMMU_HWPT_INVALIDATE_DATA_SELFTEST) {
|
||||
rc = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
for ( ; i < array->entry_num; i++) {
|
||||
rc = iommu_copy_struct_from_user_array(&inv, array,
|
||||
IOMMU_HWPT_INVALIDATE_DATA_SELFTEST,
|
||||
i, iotlb_id);
|
||||
if (rc)
|
||||
break;
|
||||
|
||||
if (inv.flags & ~IOMMU_TEST_INVALIDATE_FLAG_ALL) {
|
||||
rc = -EOPNOTSUPP;
|
||||
break;
|
||||
}
|
||||
|
||||
if (inv.iotlb_id > MOCK_NESTED_DOMAIN_IOTLB_ID_MAX) {
|
||||
rc = -EINVAL;
|
||||
break;
|
||||
}
|
||||
|
||||
if (inv.flags & IOMMU_TEST_INVALIDATE_FLAG_ALL) {
|
||||
/* Invalidate all mock iotlb entries and ignore iotlb_id */
|
||||
for (j = 0; j < MOCK_NESTED_DOMAIN_IOTLB_NUM; j++)
|
||||
mock_nested->iotlb[j] = 0;
|
||||
} else {
|
||||
mock_nested->iotlb[inv.iotlb_id] = 0;
|
||||
}
|
||||
|
||||
processed++;
|
||||
}
|
||||
|
||||
out:
|
||||
array->entry_num = processed;
|
||||
return rc;
|
||||
}
|
||||
|
||||
static struct iommu_domain_ops domain_nested_ops = {
|
||||
.free = mock_domain_free_nested,
|
||||
.attach_dev = mock_domain_nop_attach,
|
||||
.cache_invalidate_user = mock_domain_cache_invalidate_user,
|
||||
};
|
||||
|
||||
static inline struct iommufd_hw_pagetable *
|
||||
@ -526,19 +591,6 @@ get_md_pagetable_nested(struct iommufd_ucmd *ucmd, u32 mockpt_id,
|
||||
return hwpt;
|
||||
}
|
||||
|
||||
struct mock_bus_type {
|
||||
struct bus_type bus;
|
||||
struct notifier_block nb;
|
||||
};
|
||||
|
||||
static struct mock_bus_type iommufd_mock_bus_type = {
|
||||
.bus = {
|
||||
.name = "iommufd_mock",
|
||||
},
|
||||
};
|
||||
|
||||
static atomic_t mock_dev_num;
|
||||
|
||||
static void mock_dev_release(struct device *dev)
|
||||
{
|
||||
struct mock_dev *mdev = container_of(dev, struct mock_dev, dev);
|
||||
@ -793,6 +845,28 @@ static int iommufd_test_md_check_refs(struct iommufd_ucmd *ucmd,
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int iommufd_test_md_check_iotlb(struct iommufd_ucmd *ucmd,
|
||||
u32 mockpt_id, unsigned int iotlb_id,
|
||||
u32 iotlb)
|
||||
{
|
||||
struct mock_iommu_domain_nested *mock_nested;
|
||||
struct iommufd_hw_pagetable *hwpt;
|
||||
int rc = 0;
|
||||
|
||||
hwpt = get_md_pagetable_nested(ucmd, mockpt_id, &mock_nested);
|
||||
if (IS_ERR(hwpt))
|
||||
return PTR_ERR(hwpt);
|
||||
|
||||
mock_nested = container_of(hwpt->domain,
|
||||
struct mock_iommu_domain_nested, domain);
|
||||
|
||||
if (iotlb_id > MOCK_NESTED_DOMAIN_IOTLB_ID_MAX ||
|
||||
mock_nested->iotlb[iotlb_id] != iotlb)
|
||||
rc = -EINVAL;
|
||||
iommufd_put_object(ucmd->ictx, &hwpt->obj);
|
||||
return rc;
|
||||
}
|
||||
|
||||
struct selftest_access {
|
||||
struct iommufd_access *access;
|
||||
struct file *file;
|
||||
@ -1274,6 +1348,10 @@ int iommufd_test(struct iommufd_ucmd *ucmd)
|
||||
return iommufd_test_md_check_refs(
|
||||
ucmd, u64_to_user_ptr(cmd->check_refs.uptr),
|
||||
cmd->check_refs.length, cmd->check_refs.refs);
|
||||
case IOMMU_TEST_OP_MD_CHECK_IOTLB:
|
||||
return iommufd_test_md_check_iotlb(ucmd, cmd->id,
|
||||
cmd->check_iotlb.id,
|
||||
cmd->check_iotlb.iotlb);
|
||||
case IOMMU_TEST_OP_CREATE_ACCESS:
|
||||
return iommufd_test_create_access(ucmd, cmd->id,
|
||||
cmd->create_access.flags);
|
||||
|
@ -289,6 +289,23 @@ struct iommu_user_data {
|
||||
size_t len;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct iommu_user_data_array - iommu driver specific user space data array
|
||||
* @type: The data type of all the entries in the user buffer array
|
||||
* @uptr: Pointer to the user buffer array
|
||||
* @entry_len: The fixed-width length of an entry in the array, in bytes
|
||||
* @entry_num: The number of total entries in the array
|
||||
*
|
||||
* The user buffer includes an array of requests with format defined in
|
||||
* include/uapi/linux/iommufd.h
|
||||
*/
|
||||
struct iommu_user_data_array {
|
||||
unsigned int type;
|
||||
void __user *uptr;
|
||||
size_t entry_len;
|
||||
u32 entry_num;
|
||||
};
|
||||
|
||||
/**
|
||||
* __iommu_copy_struct_from_user - Copy iommu driver specific user space data
|
||||
* @dst_data: Pointer to an iommu driver specific user data that is defined in
|
||||
@ -329,6 +346,57 @@ static inline int __iommu_copy_struct_from_user(
|
||||
sizeof(*kdst), \
|
||||
offsetofend(typeof(*kdst), min_last))
|
||||
|
||||
/**
|
||||
* __iommu_copy_struct_from_user_array - Copy iommu driver specific user space
|
||||
* data from an iommu_user_data_array
|
||||
* @dst_data: Pointer to an iommu driver specific user data that is defined in
|
||||
* include/uapi/linux/iommufd.h
|
||||
* @src_array: Pointer to a struct iommu_user_data_array for a user space array
|
||||
* @data_type: The data type of the @dst_data. Must match with @src_array.type
|
||||
* @index: Index to the location in the array to copy user data from
|
||||
* @data_len: Length of current user data structure, i.e. sizeof(struct _dst)
|
||||
* @min_len: Initial length of user data structure for backward compatibility.
|
||||
* This should be offsetofend using the last member in the user data
|
||||
* struct that was initially added to include/uapi/linux/iommufd.h
|
||||
*/
|
||||
static inline int __iommu_copy_struct_from_user_array(
|
||||
void *dst_data, const struct iommu_user_data_array *src_array,
|
||||
unsigned int data_type, unsigned int index, size_t data_len,
|
||||
size_t min_len)
|
||||
{
|
||||
struct iommu_user_data src_data;
|
||||
|
||||
if (WARN_ON(!src_array || index >= src_array->entry_num))
|
||||
return -EINVAL;
|
||||
if (!src_array->entry_num)
|
||||
return -EINVAL;
|
||||
src_data.uptr = src_array->uptr + src_array->entry_len * index;
|
||||
src_data.len = src_array->entry_len;
|
||||
src_data.type = src_array->type;
|
||||
|
||||
return __iommu_copy_struct_from_user(dst_data, &src_data, data_type,
|
||||
data_len, min_len);
|
||||
}
|
||||
|
||||
/**
|
||||
* iommu_copy_struct_from_user_array - Copy iommu driver specific user space
|
||||
* data from an iommu_user_data_array
|
||||
* @kdst: Pointer to an iommu driver specific user data that is defined in
|
||||
* include/uapi/linux/iommufd.h
|
||||
* @user_array: Pointer to a struct iommu_user_data_array for a user space
|
||||
* array
|
||||
* @data_type: The data type of the @kdst. Must match with @user_array->type
|
||||
* @index: Index to the location in the array to copy user data from
|
||||
* @min_last: The last member of the data structure @kdst points in the
|
||||
* initial version.
|
||||
* Return 0 for success, otherwise -error.
|
||||
*/
|
||||
#define iommu_copy_struct_from_user_array(kdst, user_array, data_type, index, \
|
||||
min_last) \
|
||||
__iommu_copy_struct_from_user_array( \
|
||||
kdst, user_array, data_type, index, sizeof(*(kdst)), \
|
||||
offsetofend(typeof(*(kdst)), min_last))
|
||||
|
||||
/**
|
||||
* struct iommu_ops - iommu ops and capabilities
|
||||
* @capable: check capability
|
||||
@ -445,6 +513,13 @@ struct iommu_ops {
|
||||
* @iotlb_sync_map: Sync mappings created recently using @map to the hardware
|
||||
* @iotlb_sync: Flush all queued ranges from the hardware TLBs and empty flush
|
||||
* queue
|
||||
* @cache_invalidate_user: Flush hardware cache for user space IO page table.
|
||||
* The @domain must be IOMMU_DOMAIN_NESTED. The @array
|
||||
* passes in the cache invalidation requests, in form
|
||||
* of a driver data structure. The driver must update
|
||||
* array->entry_num to report the number of handled
|
||||
* invalidation requests. The driver data structure
|
||||
* must be defined in include/uapi/linux/iommufd.h
|
||||
* @iova_to_phys: translate iova to physical address
|
||||
* @enforce_cache_coherency: Prevent any kind of DMA from bypassing IOMMU_CACHE,
|
||||
* including no-snoop TLPs on PCIe or other platform
|
||||
@ -470,6 +545,8 @@ struct iommu_domain_ops {
|
||||
size_t size);
|
||||
void (*iotlb_sync)(struct iommu_domain *domain,
|
||||
struct iommu_iotlb_gather *iotlb_gather);
|
||||
int (*cache_invalidate_user)(struct iommu_domain *domain,
|
||||
struct iommu_user_data_array *array);
|
||||
|
||||
phys_addr_t (*iova_to_phys)(struct iommu_domain *domain,
|
||||
dma_addr_t iova);
|
||||
|
@ -49,6 +49,7 @@ enum {
|
||||
IOMMUFD_CMD_GET_HW_INFO,
|
||||
IOMMUFD_CMD_HWPT_SET_DIRTY_TRACKING,
|
||||
IOMMUFD_CMD_HWPT_GET_DIRTY_BITMAP,
|
||||
IOMMUFD_CMD_HWPT_INVALIDATE,
|
||||
};
|
||||
|
||||
/**
|
||||
@ -613,4 +614,82 @@ struct iommu_hwpt_get_dirty_bitmap {
|
||||
#define IOMMU_HWPT_GET_DIRTY_BITMAP _IO(IOMMUFD_TYPE, \
|
||||
IOMMUFD_CMD_HWPT_GET_DIRTY_BITMAP)
|
||||
|
||||
/**
|
||||
* enum iommu_hwpt_invalidate_data_type - IOMMU HWPT Cache Invalidation
|
||||
* Data Type
|
||||
* @IOMMU_HWPT_INVALIDATE_DATA_VTD_S1: Invalidation data for VTD_S1
|
||||
*/
|
||||
enum iommu_hwpt_invalidate_data_type {
|
||||
IOMMU_HWPT_INVALIDATE_DATA_VTD_S1,
|
||||
};
|
||||
|
||||
/**
|
||||
* enum iommu_hwpt_vtd_s1_invalidate_flags - Flags for Intel VT-d
|
||||
* stage-1 cache invalidation
|
||||
* @IOMMU_VTD_INV_FLAGS_LEAF: Indicates whether the invalidation applies
|
||||
* to all-levels page structure cache or just
|
||||
* the leaf PTE cache.
|
||||
*/
|
||||
enum iommu_hwpt_vtd_s1_invalidate_flags {
|
||||
IOMMU_VTD_INV_FLAGS_LEAF = 1 << 0,
|
||||
};
|
||||
|
||||
/**
|
||||
* struct iommu_hwpt_vtd_s1_invalidate - Intel VT-d cache invalidation
|
||||
* (IOMMU_HWPT_INVALIDATE_DATA_VTD_S1)
|
||||
* @addr: The start address of the range to be invalidated. It needs to
|
||||
* be 4KB aligned.
|
||||
* @npages: Number of contiguous 4K pages to be invalidated.
|
||||
* @flags: Combination of enum iommu_hwpt_vtd_s1_invalidate_flags
|
||||
* @__reserved: Must be 0
|
||||
*
|
||||
* The Intel VT-d specific invalidation data for user-managed stage-1 cache
|
||||
* invalidation in nested translation. Userspace uses this structure to
|
||||
* tell the impacted cache scope after modifying the stage-1 page table.
|
||||
*
|
||||
* Invalidating all the caches related to the page table by setting @addr
|
||||
* to be 0 and @npages to be U64_MAX.
|
||||
*
|
||||
* The device TLB will be invalidated automatically if ATS is enabled.
|
||||
*/
|
||||
struct iommu_hwpt_vtd_s1_invalidate {
|
||||
__aligned_u64 addr;
|
||||
__aligned_u64 npages;
|
||||
__u32 flags;
|
||||
__u32 __reserved;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct iommu_hwpt_invalidate - ioctl(IOMMU_HWPT_INVALIDATE)
|
||||
* @size: sizeof(struct iommu_hwpt_invalidate)
|
||||
* @hwpt_id: ID of a nested HWPT for cache invalidation
|
||||
* @data_uptr: User pointer to an array of driver-specific cache invalidation
|
||||
* data.
|
||||
* @data_type: One of enum iommu_hwpt_invalidate_data_type, defining the data
|
||||
* type of all the entries in the invalidation request array. It
|
||||
* should be a type supported by the hwpt pointed by @hwpt_id.
|
||||
* @entry_len: Length (in bytes) of a request entry in the request array
|
||||
* @entry_num: Input the number of cache invalidation requests in the array.
|
||||
* Output the number of requests successfully handled by kernel.
|
||||
* @__reserved: Must be 0.
|
||||
*
|
||||
* Invalidate the iommu cache for user-managed page table. Modifications on a
|
||||
* user-managed page table should be followed by this operation to sync cache.
|
||||
* Each ioctl can support one or more cache invalidation requests in the array
|
||||
* that has a total size of @entry_len * @entry_num.
|
||||
*
|
||||
* An empty invalidation request array by setting @entry_num==0 is allowed, and
|
||||
* @entry_len and @data_uptr would be ignored in this case. This can be used to
|
||||
* check if the given @data_type is supported or not by kernel.
|
||||
*/
|
||||
struct iommu_hwpt_invalidate {
|
||||
__u32 size;
|
||||
__u32 hwpt_id;
|
||||
__aligned_u64 data_uptr;
|
||||
__u32 data_type;
|
||||
__u32 entry_len;
|
||||
__u32 entry_num;
|
||||
__u32 __reserved;
|
||||
};
|
||||
#define IOMMU_HWPT_INVALIDATE _IO(IOMMUFD_TYPE, IOMMUFD_CMD_HWPT_INVALIDATE)
|
||||
#endif
|
||||
|
@ -116,6 +116,7 @@ TEST_F(iommufd, cmd_length)
|
||||
TEST_LENGTH(iommu_destroy, IOMMU_DESTROY, id);
|
||||
TEST_LENGTH(iommu_hw_info, IOMMU_GET_HW_INFO, __reserved);
|
||||
TEST_LENGTH(iommu_hwpt_alloc, IOMMU_HWPT_ALLOC, __reserved);
|
||||
TEST_LENGTH(iommu_hwpt_invalidate, IOMMU_HWPT_INVALIDATE, __reserved);
|
||||
TEST_LENGTH(iommu_ioas_alloc, IOMMU_IOAS_ALLOC, out_ioas_id);
|
||||
TEST_LENGTH(iommu_ioas_iova_ranges, IOMMU_IOAS_IOVA_RANGES,
|
||||
out_iova_alignment);
|
||||
@ -271,7 +272,9 @@ TEST_F(iommufd_ioas, alloc_hwpt_nested)
|
||||
struct iommu_hwpt_selftest data = {
|
||||
.iotlb = IOMMU_TEST_IOTLB_DEFAULT,
|
||||
};
|
||||
struct iommu_hwpt_invalidate_selftest inv_reqs[2] = {};
|
||||
uint32_t nested_hwpt_id[2] = {};
|
||||
uint32_t num_inv;
|
||||
uint32_t parent_hwpt_id = 0;
|
||||
uint32_t parent_hwpt_id_not_work = 0;
|
||||
uint32_t test_hwpt_id = 0;
|
||||
@ -330,6 +333,10 @@ TEST_F(iommufd_ioas, alloc_hwpt_nested)
|
||||
&nested_hwpt_id[1],
|
||||
IOMMU_HWPT_DATA_SELFTEST, &data,
|
||||
sizeof(data));
|
||||
test_cmd_hwpt_check_iotlb_all(nested_hwpt_id[0],
|
||||
IOMMU_TEST_IOTLB_DEFAULT);
|
||||
test_cmd_hwpt_check_iotlb_all(nested_hwpt_id[1],
|
||||
IOMMU_TEST_IOTLB_DEFAULT);
|
||||
|
||||
/* Negative test: a nested hwpt on top of a nested hwpt */
|
||||
test_err_hwpt_alloc_nested(EINVAL, self->device_id,
|
||||
@ -340,6 +347,151 @@ TEST_F(iommufd_ioas, alloc_hwpt_nested)
|
||||
EXPECT_ERRNO(EBUSY,
|
||||
_test_ioctl_destroy(self->fd, parent_hwpt_id));
|
||||
|
||||
/* hwpt_invalidate only supports a user-managed hwpt (nested) */
|
||||
num_inv = 1;
|
||||
test_err_hwpt_invalidate(ENOENT, parent_hwpt_id, inv_reqs,
|
||||
IOMMU_HWPT_INVALIDATE_DATA_SELFTEST,
|
||||
sizeof(*inv_reqs), &num_inv);
|
||||
assert(!num_inv);
|
||||
|
||||
/* Check data_type by passing zero-length array */
|
||||
num_inv = 0;
|
||||
test_cmd_hwpt_invalidate(nested_hwpt_id[0], inv_reqs,
|
||||
IOMMU_HWPT_INVALIDATE_DATA_SELFTEST,
|
||||
sizeof(*inv_reqs), &num_inv);
|
||||
assert(!num_inv);
|
||||
|
||||
/* Negative test: Invalid data_type */
|
||||
num_inv = 1;
|
||||
test_err_hwpt_invalidate(EINVAL, nested_hwpt_id[0], inv_reqs,
|
||||
IOMMU_HWPT_INVALIDATE_DATA_SELFTEST_INVALID,
|
||||
sizeof(*inv_reqs), &num_inv);
|
||||
assert(!num_inv);
|
||||
|
||||
/* Negative test: structure size sanity */
|
||||
num_inv = 1;
|
||||
test_err_hwpt_invalidate(EINVAL, nested_hwpt_id[0], inv_reqs,
|
||||
IOMMU_HWPT_INVALIDATE_DATA_SELFTEST,
|
||||
sizeof(*inv_reqs) + 1, &num_inv);
|
||||
assert(!num_inv);
|
||||
|
||||
num_inv = 1;
|
||||
test_err_hwpt_invalidate(EINVAL, nested_hwpt_id[0], inv_reqs,
|
||||
IOMMU_HWPT_INVALIDATE_DATA_SELFTEST,
|
||||
1, &num_inv);
|
||||
assert(!num_inv);
|
||||
|
||||
/* Negative test: invalid flag is passed */
|
||||
num_inv = 1;
|
||||
inv_reqs[0].flags = 0xffffffff;
|
||||
test_err_hwpt_invalidate(EOPNOTSUPP, nested_hwpt_id[0], inv_reqs,
|
||||
IOMMU_HWPT_INVALIDATE_DATA_SELFTEST,
|
||||
sizeof(*inv_reqs), &num_inv);
|
||||
assert(!num_inv);
|
||||
|
||||
/* Negative test: invalid data_uptr when array is not empty */
|
||||
num_inv = 1;
|
||||
inv_reqs[0].flags = 0;
|
||||
test_err_hwpt_invalidate(EINVAL, nested_hwpt_id[0], NULL,
|
||||
IOMMU_HWPT_INVALIDATE_DATA_SELFTEST,
|
||||
sizeof(*inv_reqs), &num_inv);
|
||||
assert(!num_inv);
|
||||
|
||||
/* Negative test: invalid entry_len when array is not empty */
|
||||
num_inv = 1;
|
||||
inv_reqs[0].flags = 0;
|
||||
test_err_hwpt_invalidate(EINVAL, nested_hwpt_id[0], inv_reqs,
|
||||
IOMMU_HWPT_INVALIDATE_DATA_SELFTEST,
|
||||
0, &num_inv);
|
||||
assert(!num_inv);
|
||||
|
||||
/* Negative test: invalid iotlb_id */
|
||||
num_inv = 1;
|
||||
inv_reqs[0].flags = 0;
|
||||
inv_reqs[0].iotlb_id = MOCK_NESTED_DOMAIN_IOTLB_ID_MAX + 1;
|
||||
test_err_hwpt_invalidate(EINVAL, nested_hwpt_id[0], inv_reqs,
|
||||
IOMMU_HWPT_INVALIDATE_DATA_SELFTEST,
|
||||
sizeof(*inv_reqs), &num_inv);
|
||||
assert(!num_inv);
|
||||
|
||||
/*
|
||||
* Invalidate the 1st iotlb entry but fail the 2nd request
|
||||
* due to invalid flags configuration in the 2nd request.
|
||||
*/
|
||||
num_inv = 2;
|
||||
inv_reqs[0].flags = 0;
|
||||
inv_reqs[0].iotlb_id = 0;
|
||||
inv_reqs[1].flags = 0xffffffff;
|
||||
inv_reqs[1].iotlb_id = 1;
|
||||
test_err_hwpt_invalidate(EOPNOTSUPP, nested_hwpt_id[0], inv_reqs,
|
||||
IOMMU_HWPT_INVALIDATE_DATA_SELFTEST,
|
||||
sizeof(*inv_reqs), &num_inv);
|
||||
assert(num_inv == 1);
|
||||
test_cmd_hwpt_check_iotlb(nested_hwpt_id[0], 0, 0);
|
||||
test_cmd_hwpt_check_iotlb(nested_hwpt_id[0], 1,
|
||||
IOMMU_TEST_IOTLB_DEFAULT);
|
||||
test_cmd_hwpt_check_iotlb(nested_hwpt_id[0], 2,
|
||||
IOMMU_TEST_IOTLB_DEFAULT);
|
||||
test_cmd_hwpt_check_iotlb(nested_hwpt_id[0], 3,
|
||||
IOMMU_TEST_IOTLB_DEFAULT);
|
||||
|
||||
/*
|
||||
* Invalidate the 1st iotlb entry but fail the 2nd request
|
||||
* due to invalid iotlb_id configuration in the 2nd request.
|
||||
*/
|
||||
num_inv = 2;
|
||||
inv_reqs[0].flags = 0;
|
||||
inv_reqs[0].iotlb_id = 0;
|
||||
inv_reqs[1].flags = 0;
|
||||
inv_reqs[1].iotlb_id = MOCK_NESTED_DOMAIN_IOTLB_ID_MAX + 1;
|
||||
test_err_hwpt_invalidate(EINVAL, nested_hwpt_id[0], inv_reqs,
|
||||
IOMMU_HWPT_INVALIDATE_DATA_SELFTEST,
|
||||
sizeof(*inv_reqs), &num_inv);
|
||||
assert(num_inv == 1);
|
||||
test_cmd_hwpt_check_iotlb(nested_hwpt_id[0], 0, 0);
|
||||
test_cmd_hwpt_check_iotlb(nested_hwpt_id[0], 1,
|
||||
IOMMU_TEST_IOTLB_DEFAULT);
|
||||
test_cmd_hwpt_check_iotlb(nested_hwpt_id[0], 2,
|
||||
IOMMU_TEST_IOTLB_DEFAULT);
|
||||
test_cmd_hwpt_check_iotlb(nested_hwpt_id[0], 3,
|
||||
IOMMU_TEST_IOTLB_DEFAULT);
|
||||
|
||||
/* Invalidate the 2nd iotlb entry and verify */
|
||||
num_inv = 1;
|
||||
inv_reqs[0].flags = 0;
|
||||
inv_reqs[0].iotlb_id = 1;
|
||||
test_cmd_hwpt_invalidate(nested_hwpt_id[0], inv_reqs,
|
||||
IOMMU_HWPT_INVALIDATE_DATA_SELFTEST,
|
||||
sizeof(*inv_reqs), &num_inv);
|
||||
assert(num_inv == 1);
|
||||
test_cmd_hwpt_check_iotlb(nested_hwpt_id[0], 0, 0);
|
||||
test_cmd_hwpt_check_iotlb(nested_hwpt_id[0], 1, 0);
|
||||
test_cmd_hwpt_check_iotlb(nested_hwpt_id[0], 2,
|
||||
IOMMU_TEST_IOTLB_DEFAULT);
|
||||
test_cmd_hwpt_check_iotlb(nested_hwpt_id[0], 3,
|
||||
IOMMU_TEST_IOTLB_DEFAULT);
|
||||
|
||||
/* Invalidate the 3rd and 4th iotlb entries and verify */
|
||||
num_inv = 2;
|
||||
inv_reqs[0].flags = 0;
|
||||
inv_reqs[0].iotlb_id = 2;
|
||||
inv_reqs[1].flags = 0;
|
||||
inv_reqs[1].iotlb_id = 3;
|
||||
test_cmd_hwpt_invalidate(nested_hwpt_id[0], inv_reqs,
|
||||
IOMMU_HWPT_INVALIDATE_DATA_SELFTEST,
|
||||
sizeof(*inv_reqs), &num_inv);
|
||||
assert(num_inv == 2);
|
||||
test_cmd_hwpt_check_iotlb_all(nested_hwpt_id[0], 0);
|
||||
|
||||
/* Invalidate all iotlb entries for nested_hwpt_id[1] and verify */
|
||||
num_inv = 1;
|
||||
inv_reqs[0].flags = IOMMU_TEST_INVALIDATE_FLAG_ALL;
|
||||
test_cmd_hwpt_invalidate(nested_hwpt_id[1], inv_reqs,
|
||||
IOMMU_HWPT_INVALIDATE_DATA_SELFTEST,
|
||||
sizeof(*inv_reqs), &num_inv);
|
||||
assert(num_inv == 1);
|
||||
test_cmd_hwpt_check_iotlb_all(nested_hwpt_id[1], 0);
|
||||
|
||||
/* Attach device to nested_hwpt_id[0] that then will be busy */
|
||||
test_cmd_mock_domain_replace(self->stdev_id, nested_hwpt_id[0]);
|
||||
EXPECT_ERRNO(EBUSY,
|
||||
|
@ -195,6 +195,61 @@ static int _test_cmd_hwpt_alloc(int fd, __u32 device_id, __u32 pt_id,
|
||||
_test_cmd_hwpt_alloc(self->fd, device_id, pt_id, flags, \
|
||||
hwpt_id, data_type, data, data_len))
|
||||
|
||||
#define test_cmd_hwpt_check_iotlb(hwpt_id, iotlb_id, expected) \
|
||||
({ \
|
||||
struct iommu_test_cmd test_cmd = { \
|
||||
.size = sizeof(test_cmd), \
|
||||
.op = IOMMU_TEST_OP_MD_CHECK_IOTLB, \
|
||||
.id = hwpt_id, \
|
||||
.check_iotlb = { \
|
||||
.id = iotlb_id, \
|
||||
.iotlb = expected, \
|
||||
}, \
|
||||
}; \
|
||||
ASSERT_EQ(0, \
|
||||
ioctl(self->fd, \
|
||||
_IOMMU_TEST_CMD(IOMMU_TEST_OP_MD_CHECK_IOTLB), \
|
||||
&test_cmd)); \
|
||||
})
|
||||
|
||||
#define test_cmd_hwpt_check_iotlb_all(hwpt_id, expected) \
|
||||
({ \
|
||||
int i; \
|
||||
for (i = 0; i < MOCK_NESTED_DOMAIN_IOTLB_NUM; i++) \
|
||||
test_cmd_hwpt_check_iotlb(hwpt_id, i, expected); \
|
||||
})
|
||||
|
||||
static int _test_cmd_hwpt_invalidate(int fd, __u32 hwpt_id, void *reqs,
|
||||
uint32_t data_type, uint32_t lreq,
|
||||
uint32_t *nreqs)
|
||||
{
|
||||
struct iommu_hwpt_invalidate cmd = {
|
||||
.size = sizeof(cmd),
|
||||
.hwpt_id = hwpt_id,
|
||||
.data_type = data_type,
|
||||
.data_uptr = (uint64_t)reqs,
|
||||
.entry_len = lreq,
|
||||
.entry_num = *nreqs,
|
||||
};
|
||||
int rc = ioctl(fd, IOMMU_HWPT_INVALIDATE, &cmd);
|
||||
*nreqs = cmd.entry_num;
|
||||
return rc;
|
||||
}
|
||||
|
||||
#define test_cmd_hwpt_invalidate(hwpt_id, reqs, data_type, lreq, nreqs) \
|
||||
({ \
|
||||
ASSERT_EQ(0, \
|
||||
_test_cmd_hwpt_invalidate(self->fd, hwpt_id, reqs, \
|
||||
data_type, lreq, nreqs)); \
|
||||
})
|
||||
#define test_err_hwpt_invalidate(_errno, hwpt_id, reqs, data_type, lreq, \
|
||||
nreqs) \
|
||||
({ \
|
||||
EXPECT_ERRNO(_errno, _test_cmd_hwpt_invalidate( \
|
||||
self->fd, hwpt_id, reqs, \
|
||||
data_type, lreq, nreqs)); \
|
||||
})
|
||||
|
||||
static int _test_cmd_access_replace_ioas(int fd, __u32 access_id,
|
||||
unsigned int ioas_id)
|
||||
{
|
||||
|
Loading…
Reference in New Issue
Block a user