Merge tag 'for-linus-iommufd' of git://git.kernel.org/pub/scm/linux/kernel/git/jgg/iommufd

Pull iommufd updates from Jason Gunthorpe:
 "Two significant new items:

   - Allow reporting IOMMU HW events to userspace when the events are
     clearly linked to a device.

     This is linked to the VIOMMU object and is intended to be used by a
     VMM to forward HW events to the virtual machine as part of
     emulating a vIOMMU. ARM SMMUv3 is the first driver to use this
     mechanism. Like the existing fault events the data is delivered
     through a simple FD returning event records on read().

   - PASID support in VFIO.

     The "Process Address Space ID" is a PCI feature that allows the
     device to tag all PCI DMA operations with an ID. The IOMMU will
     then use the ID to select a unique translation for those DMAs. This
     is part of Intel's vIOMMU support as VT-D HW requires the
     hypervisor to manage each PASID entry.

     The support is generic so any VFIO user could attach any
     translation to a PASID, and the support should work on ARM SMMUv3
     as well. AMD requires additional driver work.

  Some minor updates, along with fixes:

   - Prevent using nested parents with fault's, no driver support today

   - Put a single "cookie_type" value in the iommu_domain to indicate
     what owns the various opaque owner fields"

* tag 'for-linus-iommufd' of git://git.kernel.org/pub/scm/linux/kernel/git/jgg/iommufd: (49 commits)
  iommufd: Test attach before detaching pasid
  iommufd: Fix iommu_vevent_header tables markup
  iommu: Convert unreachable() to BUG()
  iommufd: Balance veventq->num_events inc/dec
  iommufd: Initialize the flags of vevent in iommufd_viommu_report_event()
  iommufd/selftest: Add coverage for reporting max_pasid_log2 via IOMMU_HW_INFO
  iommufd: Extend IOMMU_GET_HW_INFO to report PASID capability
  vfio: VFIO_DEVICE_[AT|DE]TACH_IOMMUFD_PT support pasid
  vfio-iommufd: Support pasid [at|de]tach for physical VFIO devices
  ida: Add ida_find_first_range()
  iommufd/selftest: Add coverage for iommufd pasid attach/detach
  iommufd/selftest: Add test ops to test pasid attach/detach
  iommufd/selftest: Add a helper to get test device
  iommufd/selftest: Add set_dev_pasid in mock iommu
  iommufd: Allow allocating PASID-compatible domain
  iommu/vt-d: Add IOMMU_HWPT_ALLOC_PASID support
  iommufd: Enforce PASID-compatible domain for RID
  iommufd: Support pasid attach/replace
  iommufd: Enforce PASID-compatible domain in PASID path
  iommufd/device: Add pasid_attach array to track per-PASID attach
  ...
This commit is contained in:
Linus Torvalds
2025-04-01 18:03:46 -07:00
39 changed files with 3155 additions and 837 deletions

View File

@@ -63,6 +63,13 @@ Following IOMMUFD objects are exposed to userspace:
space usually has mappings from guest-level I/O virtual addresses to guest-
level physical addresses.
- IOMMUFD_FAULT, representing a software queue for an HWPT reporting IO page
faults using the IOMMU HW's PRI (Page Request Interface). This queue object
provides user space an FD to poll the page fault events and also to respond
to those events. A FAULT object must be created first to get a fault_id that
could be then used to allocate a fault-enabled HWPT via the IOMMU_HWPT_ALLOC
command by setting the IOMMU_HWPT_FAULT_ID_VALID bit in its flags field.
- IOMMUFD_OBJ_VIOMMU, representing a slice of the physical IOMMU instance,
passed to or shared with a VM. It may be some HW-accelerated virtualization
features and some SW resources used by the VM. For examples:
@@ -109,6 +116,14 @@ Following IOMMUFD objects are exposed to userspace:
vIOMMU, which is a separate ioctl call from attaching the same device to an
HWPT_PAGING that the vIOMMU holds.
- IOMMUFD_OBJ_VEVENTQ, representing a software queue for a vIOMMU to report its
events such as translation faults occurred to a nested stage-1 (excluding I/O
page faults that should go through IOMMUFD_OBJ_FAULT) and HW-specific events.
This queue object provides user space an FD to poll/read the vIOMMU events. A
vIOMMU object must be created first to get its viommu_id, which could be then
used to allocate a vEVENTQ. Each vIOMMU can support multiple types of vEVENTS,
but is confined to one vEVENTQ per vEVENTQ type.
All user-visible objects are destroyed via the IOMMU_DESTROY uAPI.
The diagrams below show relationships between user-visible objects and kernel
@@ -251,8 +266,10 @@ User visible objects are backed by following datastructures:
- iommufd_device for IOMMUFD_OBJ_DEVICE.
- iommufd_hwpt_paging for IOMMUFD_OBJ_HWPT_PAGING.
- iommufd_hwpt_nested for IOMMUFD_OBJ_HWPT_NESTED.
- iommufd_fault for IOMMUFD_OBJ_FAULT.
- iommufd_viommu for IOMMUFD_OBJ_VIOMMU.
- iommufd_vdevice for IOMMUFD_OBJ_VDEVICE.
- iommufd_veventq for IOMMUFD_OBJ_VEVENTQ.
Several terminologies when looking at these datastructures:

View File

@@ -43,6 +43,8 @@ static void arm_smmu_make_nested_cd_table_ste(
target->data[0] |= nested_domain->ste[0] &
~cpu_to_le64(STRTAB_STE_0_CFG);
target->data[1] |= nested_domain->ste[1];
/* Merge events for DoS mitigations on eventq */
target->data[1] |= cpu_to_le64(STRTAB_STE_1_MEV);
}
/*
@@ -85,6 +87,47 @@ static void arm_smmu_make_nested_domain_ste(
}
}
int arm_smmu_attach_prepare_vmaster(struct arm_smmu_attach_state *state,
struct arm_smmu_nested_domain *nested_domain)
{
struct arm_smmu_vmaster *vmaster;
unsigned long vsid;
int ret;
iommu_group_mutex_assert(state->master->dev);
ret = iommufd_viommu_get_vdev_id(&nested_domain->vsmmu->core,
state->master->dev, &vsid);
if (ret)
return ret;
vmaster = kzalloc(sizeof(*vmaster), GFP_KERNEL);
if (!vmaster)
return -ENOMEM;
vmaster->vsmmu = nested_domain->vsmmu;
vmaster->vsid = vsid;
state->vmaster = vmaster;
return 0;
}
void arm_smmu_attach_commit_vmaster(struct arm_smmu_attach_state *state)
{
struct arm_smmu_master *master = state->master;
mutex_lock(&master->smmu->streams_mutex);
kfree(master->vmaster);
master->vmaster = state->vmaster;
mutex_unlock(&master->smmu->streams_mutex);
}
void arm_smmu_master_clear_vmaster(struct arm_smmu_master *master)
{
struct arm_smmu_attach_state state = { .master = master };
arm_smmu_attach_commit_vmaster(&state);
}
static int arm_smmu_attach_dev_nested(struct iommu_domain *domain,
struct device *dev)
{
@@ -392,4 +435,21 @@ struct iommufd_viommu *arm_vsmmu_alloc(struct device *dev,
return &vsmmu->core;
}
int arm_vmaster_report_event(struct arm_smmu_vmaster *vmaster, u64 *evt)
{
struct iommu_vevent_arm_smmuv3 vevt;
int i;
lockdep_assert_held(&vmaster->vsmmu->smmu->streams_mutex);
vevt.evt[0] = cpu_to_le64((evt[0] & ~EVTQ_0_SID) |
FIELD_PREP(EVTQ_0_SID, vmaster->vsid));
for (i = 1; i < EVTQ_ENT_DWORDS; i++)
vevt.evt[i] = cpu_to_le64(evt[i]);
return iommufd_viommu_report_event(&vmaster->vsmmu->core,
IOMMU_VEVENTQ_TYPE_ARM_SMMUV3, &vevt,
sizeof(vevt));
}
MODULE_IMPORT_NS("IOMMUFD");

View File

@@ -1052,7 +1052,7 @@ void arm_smmu_get_ste_used(const __le64 *ent, __le64 *used_bits)
cpu_to_le64(STRTAB_STE_1_S1DSS | STRTAB_STE_1_S1CIR |
STRTAB_STE_1_S1COR | STRTAB_STE_1_S1CSH |
STRTAB_STE_1_S1STALLD | STRTAB_STE_1_STRW |
STRTAB_STE_1_EATS);
STRTAB_STE_1_EATS | STRTAB_STE_1_MEV);
used_bits[2] |= cpu_to_le64(STRTAB_STE_2_S2VMID);
/*
@@ -1068,7 +1068,7 @@ void arm_smmu_get_ste_used(const __le64 *ent, __le64 *used_bits)
if (cfg & BIT(1)) {
used_bits[1] |=
cpu_to_le64(STRTAB_STE_1_S2FWB | STRTAB_STE_1_EATS |
STRTAB_STE_1_SHCFG);
STRTAB_STE_1_SHCFG | STRTAB_STE_1_MEV);
used_bits[2] |=
cpu_to_le64(STRTAB_STE_2_S2VMID | STRTAB_STE_2_VTCR |
STRTAB_STE_2_S2AA64 | STRTAB_STE_2_S2ENDI |
@@ -1813,8 +1813,8 @@ static void arm_smmu_decode_event(struct arm_smmu_device *smmu, u64 *raw,
mutex_unlock(&smmu->streams_mutex);
}
static int arm_smmu_handle_event(struct arm_smmu_device *smmu,
struct arm_smmu_event *event)
static int arm_smmu_handle_event(struct arm_smmu_device *smmu, u64 *evt,
struct arm_smmu_event *event)
{
int ret = 0;
u32 perm = 0;
@@ -1823,6 +1823,10 @@ static int arm_smmu_handle_event(struct arm_smmu_device *smmu,
struct iommu_fault *flt = &fault_evt.fault;
switch (event->id) {
case EVT_ID_BAD_STE_CONFIG:
case EVT_ID_STREAM_DISABLED_FAULT:
case EVT_ID_BAD_SUBSTREAMID_CONFIG:
case EVT_ID_BAD_CD_CONFIG:
case EVT_ID_TRANSLATION_FAULT:
case EVT_ID_ADDR_SIZE_FAULT:
case EVT_ID_ACCESS_FAULT:
@@ -1832,31 +1836,30 @@ static int arm_smmu_handle_event(struct arm_smmu_device *smmu,
return -EOPNOTSUPP;
}
if (!event->stall)
return -EOPNOTSUPP;
if (event->stall) {
if (event->read)
perm |= IOMMU_FAULT_PERM_READ;
else
perm |= IOMMU_FAULT_PERM_WRITE;
if (event->read)
perm |= IOMMU_FAULT_PERM_READ;
else
perm |= IOMMU_FAULT_PERM_WRITE;
if (event->instruction)
perm |= IOMMU_FAULT_PERM_EXEC;
if (event->instruction)
perm |= IOMMU_FAULT_PERM_EXEC;
if (event->privileged)
perm |= IOMMU_FAULT_PERM_PRIV;
if (event->privileged)
perm |= IOMMU_FAULT_PERM_PRIV;
flt->type = IOMMU_FAULT_PAGE_REQ;
flt->prm = (struct iommu_fault_page_request){
.flags = IOMMU_FAULT_PAGE_REQUEST_LAST_PAGE,
.grpid = event->stag,
.perm = perm,
.addr = event->iova,
};
flt->type = IOMMU_FAULT_PAGE_REQ;
flt->prm = (struct iommu_fault_page_request) {
.flags = IOMMU_FAULT_PAGE_REQUEST_LAST_PAGE,
.grpid = event->stag,
.perm = perm,
.addr = event->iova,
};
if (event->ssv) {
flt->prm.flags |= IOMMU_FAULT_PAGE_REQUEST_PASID_VALID;
flt->prm.pasid = event->ssid;
if (event->ssv) {
flt->prm.flags |= IOMMU_FAULT_PAGE_REQUEST_PASID_VALID;
flt->prm.pasid = event->ssid;
}
}
mutex_lock(&smmu->streams_mutex);
@@ -1866,7 +1869,12 @@ static int arm_smmu_handle_event(struct arm_smmu_device *smmu,
goto out_unlock;
}
ret = iommu_report_device_fault(master->dev, &fault_evt);
if (event->stall)
ret = iommu_report_device_fault(master->dev, &fault_evt);
else if (master->vmaster && !event->s2)
ret = arm_vmaster_report_event(master->vmaster, evt);
else
ret = -EOPNOTSUPP; /* Unhandled events should be pinned */
out_unlock:
mutex_unlock(&smmu->streams_mutex);
return ret;
@@ -1944,7 +1952,7 @@ static irqreturn_t arm_smmu_evtq_thread(int irq, void *dev)
do {
while (!queue_remove_raw(q, evt)) {
arm_smmu_decode_event(smmu, evt, &event);
if (arm_smmu_handle_event(smmu, &event))
if (arm_smmu_handle_event(smmu, evt, &event))
arm_smmu_dump_event(smmu, evt, &event, &rs);
put_device(event.dev);
@@ -2803,6 +2811,7 @@ int arm_smmu_attach_prepare(struct arm_smmu_attach_state *state,
struct arm_smmu_domain *smmu_domain =
to_smmu_domain_devices(new_domain);
unsigned long flags;
int ret;
/*
* arm_smmu_share_asid() must not see two domains pointing to the same
@@ -2832,9 +2841,18 @@ int arm_smmu_attach_prepare(struct arm_smmu_attach_state *state,
}
if (smmu_domain) {
if (new_domain->type == IOMMU_DOMAIN_NESTED) {
ret = arm_smmu_attach_prepare_vmaster(
state, to_smmu_nested_domain(new_domain));
if (ret)
return ret;
}
master_domain = kzalloc(sizeof(*master_domain), GFP_KERNEL);
if (!master_domain)
if (!master_domain) {
kfree(state->vmaster);
return -ENOMEM;
}
master_domain->master = master;
master_domain->ssid = state->ssid;
if (new_domain->type == IOMMU_DOMAIN_NESTED)
@@ -2861,6 +2879,7 @@ int arm_smmu_attach_prepare(struct arm_smmu_attach_state *state,
spin_unlock_irqrestore(&smmu_domain->devices_lock,
flags);
kfree(master_domain);
kfree(state->vmaster);
return -EINVAL;
}
@@ -2893,6 +2912,8 @@ void arm_smmu_attach_commit(struct arm_smmu_attach_state *state)
lockdep_assert_held(&arm_smmu_asid_lock);
arm_smmu_attach_commit_vmaster(state);
if (state->ats_enabled && !master->ats_enabled) {
arm_smmu_enable_ats(master);
} else if (state->ats_enabled && master->ats_enabled) {
@@ -3162,6 +3183,7 @@ static int arm_smmu_attach_dev_identity(struct iommu_domain *domain,
struct arm_smmu_ste ste;
struct arm_smmu_master *master = dev_iommu_priv_get(dev);
arm_smmu_master_clear_vmaster(master);
arm_smmu_make_bypass_ste(master->smmu, &ste);
arm_smmu_attach_dev_ste(domain, dev, &ste, STRTAB_STE_1_S1DSS_BYPASS);
return 0;
@@ -3180,7 +3202,9 @@ static int arm_smmu_attach_dev_blocked(struct iommu_domain *domain,
struct device *dev)
{
struct arm_smmu_ste ste;
struct arm_smmu_master *master = dev_iommu_priv_get(dev);
arm_smmu_master_clear_vmaster(master);
arm_smmu_make_abort_ste(&ste);
arm_smmu_attach_dev_ste(domain, dev, &ste,
STRTAB_STE_1_S1DSS_TERMINATE);

View File

@@ -266,6 +266,7 @@ static inline u32 arm_smmu_strtab_l2_idx(u32 sid)
#define STRTAB_STE_1_S1COR GENMASK_ULL(5, 4)
#define STRTAB_STE_1_S1CSH GENMASK_ULL(7, 6)
#define STRTAB_STE_1_MEV (1UL << 19)
#define STRTAB_STE_1_S2FWB (1UL << 25)
#define STRTAB_STE_1_S1STALLD (1UL << 27)
@@ -799,6 +800,11 @@ struct arm_smmu_stream {
struct rb_node node;
};
struct arm_smmu_vmaster {
struct arm_vsmmu *vsmmu;
unsigned long vsid;
};
struct arm_smmu_event {
u8 stall : 1,
ssv : 1,
@@ -824,6 +830,7 @@ struct arm_smmu_master {
struct arm_smmu_device *smmu;
struct device *dev;
struct arm_smmu_stream *streams;
struct arm_smmu_vmaster *vmaster; /* use smmu->streams_mutex */
/* Locked by the iommu core using the group mutex */
struct arm_smmu_ctx_desc_cfg cd_table;
unsigned int num_streams;
@@ -972,6 +979,7 @@ struct arm_smmu_attach_state {
bool disable_ats;
ioasid_t ssid;
/* Resulting state */
struct arm_smmu_vmaster *vmaster;
bool ats_enabled;
};
@@ -1055,9 +1063,37 @@ struct iommufd_viommu *arm_vsmmu_alloc(struct device *dev,
struct iommu_domain *parent,
struct iommufd_ctx *ictx,
unsigned int viommu_type);
int arm_smmu_attach_prepare_vmaster(struct arm_smmu_attach_state *state,
struct arm_smmu_nested_domain *nested_domain);
void arm_smmu_attach_commit_vmaster(struct arm_smmu_attach_state *state);
void arm_smmu_master_clear_vmaster(struct arm_smmu_master *master);
int arm_vmaster_report_event(struct arm_smmu_vmaster *vmaster, u64 *evt);
#else
#define arm_smmu_hw_info NULL
#define arm_vsmmu_alloc NULL
static inline int
arm_smmu_attach_prepare_vmaster(struct arm_smmu_attach_state *state,
struct arm_smmu_nested_domain *nested_domain)
{
return 0;
}
static inline void
arm_smmu_attach_commit_vmaster(struct arm_smmu_attach_state *state)
{
}
static inline void
arm_smmu_master_clear_vmaster(struct arm_smmu_master *master)
{
}
static inline int arm_vmaster_report_event(struct arm_smmu_vmaster *vmaster,
u64 *evt)
{
return -EOPNOTSUPP;
}
#endif /* CONFIG_ARM_SMMU_V3_IOMMUFD */
#endif /* _ARM_SMMU_V3_H */

View File

@@ -42,11 +42,6 @@ struct iommu_dma_msi_page {
phys_addr_t phys;
};
enum iommu_dma_cookie_type {
IOMMU_DMA_IOVA_COOKIE,
IOMMU_DMA_MSI_COOKIE,
};
enum iommu_dma_queue_type {
IOMMU_DMA_OPTS_PER_CPU_QUEUE,
IOMMU_DMA_OPTS_SINGLE_QUEUE,
@@ -59,34 +54,30 @@ struct iommu_dma_options {
};
struct iommu_dma_cookie {
enum iommu_dma_cookie_type type;
struct iova_domain iovad;
struct list_head msi_page_list;
/* Flush queue */
union {
/* Full allocator for IOMMU_DMA_IOVA_COOKIE */
struct {
struct iova_domain iovad;
/* Flush queue */
union {
struct iova_fq *single_fq;
struct iova_fq __percpu *percpu_fq;
};
/* Number of TLB flushes that have been started */
atomic64_t fq_flush_start_cnt;
/* Number of TLB flushes that have been finished */
atomic64_t fq_flush_finish_cnt;
/* Timer to regularily empty the flush queues */
struct timer_list fq_timer;
/* 1 when timer is active, 0 when not */
atomic_t fq_timer_on;
};
/* Trivial linear page allocator for IOMMU_DMA_MSI_COOKIE */
dma_addr_t msi_iova;
struct iova_fq *single_fq;
struct iova_fq __percpu *percpu_fq;
};
struct list_head msi_page_list;
/* Number of TLB flushes that have been started */
atomic64_t fq_flush_start_cnt;
/* Number of TLB flushes that have been finished */
atomic64_t fq_flush_finish_cnt;
/* Timer to regularily empty the flush queues */
struct timer_list fq_timer;
/* 1 when timer is active, 0 when not */
atomic_t fq_timer_on;
/* Domain for flush queue callback; NULL if flush queue not in use */
struct iommu_domain *fq_domain;
struct iommu_domain *fq_domain;
/* Options for dma-iommu use */
struct iommu_dma_options options;
struct iommu_dma_options options;
};
struct iommu_dma_msi_cookie {
dma_addr_t msi_iova;
struct list_head msi_page_list;
};
static DEFINE_STATIC_KEY_FALSE(iommu_deferred_attach_enabled);
@@ -102,9 +93,6 @@ static int __init iommu_dma_forcedac_setup(char *str)
}
early_param("iommu.forcedac", iommu_dma_forcedac_setup);
static int iommu_dma_sw_msi(struct iommu_domain *domain, struct msi_desc *desc,
phys_addr_t msi_addr);
/* Number of entries per flush queue */
#define IOVA_DEFAULT_FQ_SIZE 256
#define IOVA_SINGLE_FQ_SIZE 32768
@@ -368,39 +356,24 @@ int iommu_dma_init_fq(struct iommu_domain *domain)
return 0;
}
static inline size_t cookie_msi_granule(struct iommu_dma_cookie *cookie)
{
if (cookie->type == IOMMU_DMA_IOVA_COOKIE)
return cookie->iovad.granule;
return PAGE_SIZE;
}
static struct iommu_dma_cookie *cookie_alloc(enum iommu_dma_cookie_type type)
{
struct iommu_dma_cookie *cookie;
cookie = kzalloc(sizeof(*cookie), GFP_KERNEL);
if (cookie) {
INIT_LIST_HEAD(&cookie->msi_page_list);
cookie->type = type;
}
return cookie;
}
/**
* iommu_get_dma_cookie - Acquire DMA-API resources for a domain
* @domain: IOMMU domain to prepare for DMA-API usage
*/
int iommu_get_dma_cookie(struct iommu_domain *domain)
{
if (domain->iova_cookie)
struct iommu_dma_cookie *cookie;
if (domain->cookie_type != IOMMU_COOKIE_NONE)
return -EEXIST;
domain->iova_cookie = cookie_alloc(IOMMU_DMA_IOVA_COOKIE);
if (!domain->iova_cookie)
cookie = kzalloc(sizeof(*cookie), GFP_KERNEL);
if (!cookie)
return -ENOMEM;
iommu_domain_set_sw_msi(domain, iommu_dma_sw_msi);
INIT_LIST_HEAD(&cookie->msi_page_list);
domain->cookie_type = IOMMU_COOKIE_DMA_IOVA;
domain->iova_cookie = cookie;
return 0;
}
@@ -418,54 +391,56 @@ int iommu_get_dma_cookie(struct iommu_domain *domain)
*/
int iommu_get_msi_cookie(struct iommu_domain *domain, dma_addr_t base)
{
struct iommu_dma_cookie *cookie;
struct iommu_dma_msi_cookie *cookie;
if (domain->type != IOMMU_DOMAIN_UNMANAGED)
return -EINVAL;
if (domain->iova_cookie)
if (domain->cookie_type != IOMMU_COOKIE_NONE)
return -EEXIST;
cookie = cookie_alloc(IOMMU_DMA_MSI_COOKIE);
cookie = kzalloc(sizeof(*cookie), GFP_KERNEL);
if (!cookie)
return -ENOMEM;
cookie->msi_iova = base;
domain->iova_cookie = cookie;
iommu_domain_set_sw_msi(domain, iommu_dma_sw_msi);
INIT_LIST_HEAD(&cookie->msi_page_list);
domain->cookie_type = IOMMU_COOKIE_DMA_MSI;
domain->msi_cookie = cookie;
return 0;
}
EXPORT_SYMBOL(iommu_get_msi_cookie);
/**
* iommu_put_dma_cookie - Release a domain's DMA mapping resources
* @domain: IOMMU domain previously prepared by iommu_get_dma_cookie() or
* iommu_get_msi_cookie()
* @domain: IOMMU domain previously prepared by iommu_get_dma_cookie()
*/
void iommu_put_dma_cookie(struct iommu_domain *domain)
{
struct iommu_dma_cookie *cookie = domain->iova_cookie;
struct iommu_dma_msi_page *msi, *tmp;
#if IS_ENABLED(CONFIG_IRQ_MSI_IOMMU)
if (domain->sw_msi != iommu_dma_sw_msi)
return;
#endif
if (!cookie)
return;
if (cookie->type == IOMMU_DMA_IOVA_COOKIE && cookie->iovad.granule) {
if (cookie->iovad.granule) {
iommu_dma_free_fq(cookie);
put_iova_domain(&cookie->iovad);
}
list_for_each_entry_safe(msi, tmp, &cookie->msi_page_list, list) {
list_del(&msi->list);
list_for_each_entry_safe(msi, tmp, &cookie->msi_page_list, list)
kfree(msi);
kfree(cookie);
}
/**
* iommu_put_msi_cookie - Release a domain's MSI mapping resources
* @domain: IOMMU domain previously prepared by iommu_get_msi_cookie()
*/
void iommu_put_msi_cookie(struct iommu_domain *domain)
{
struct iommu_dma_msi_cookie *cookie = domain->msi_cookie;
struct iommu_dma_msi_page *msi, *tmp;
list_for_each_entry_safe(msi, tmp, &cookie->msi_page_list, list)
kfree(msi);
}
kfree(cookie);
domain->iova_cookie = NULL;
}
/**
@@ -685,7 +660,7 @@ static int iommu_dma_init_domain(struct iommu_domain *domain, struct device *dev
struct iova_domain *iovad;
int ret;
if (!cookie || cookie->type != IOMMU_DMA_IOVA_COOKIE)
if (!cookie || domain->cookie_type != IOMMU_COOKIE_DMA_IOVA)
return -EINVAL;
iovad = &cookie->iovad;
@@ -768,9 +743,9 @@ static dma_addr_t iommu_dma_alloc_iova(struct iommu_domain *domain,
struct iova_domain *iovad = &cookie->iovad;
unsigned long shift, iova_len, iova;
if (cookie->type == IOMMU_DMA_MSI_COOKIE) {
cookie->msi_iova += size;
return cookie->msi_iova - size;
if (domain->cookie_type == IOMMU_COOKIE_DMA_MSI) {
domain->msi_cookie->msi_iova += size;
return domain->msi_cookie->msi_iova - size;
}
shift = iova_shift(iovad);
@@ -807,16 +782,16 @@ static dma_addr_t iommu_dma_alloc_iova(struct iommu_domain *domain,
return (dma_addr_t)iova << shift;
}
static void iommu_dma_free_iova(struct iommu_dma_cookie *cookie,
dma_addr_t iova, size_t size, struct iommu_iotlb_gather *gather)
static void iommu_dma_free_iova(struct iommu_domain *domain, dma_addr_t iova,
size_t size, struct iommu_iotlb_gather *gather)
{
struct iova_domain *iovad = &cookie->iovad;
struct iova_domain *iovad = &domain->iova_cookie->iovad;
/* The MSI case is only ever cleaning up its most recent allocation */
if (cookie->type == IOMMU_DMA_MSI_COOKIE)
cookie->msi_iova -= size;
if (domain->cookie_type == IOMMU_COOKIE_DMA_MSI)
domain->msi_cookie->msi_iova -= size;
else if (gather && gather->queued)
queue_iova(cookie, iova_pfn(iovad, iova),
queue_iova(domain->iova_cookie, iova_pfn(iovad, iova),
size >> iova_shift(iovad),
&gather->freelist);
else
@@ -844,7 +819,7 @@ static void __iommu_dma_unmap(struct device *dev, dma_addr_t dma_addr,
if (!iotlb_gather.queued)
iommu_iotlb_sync(domain, &iotlb_gather);
iommu_dma_free_iova(cookie, dma_addr, size, &iotlb_gather);
iommu_dma_free_iova(domain, dma_addr, size, &iotlb_gather);
}
static dma_addr_t __iommu_dma_map(struct device *dev, phys_addr_t phys,
@@ -872,7 +847,7 @@ static dma_addr_t __iommu_dma_map(struct device *dev, phys_addr_t phys,
return DMA_MAPPING_ERROR;
if (iommu_map(domain, iova, phys - iova_off, size, prot, GFP_ATOMIC)) {
iommu_dma_free_iova(cookie, iova, size, NULL);
iommu_dma_free_iova(domain, iova, size, NULL);
return DMA_MAPPING_ERROR;
}
return iova + iova_off;
@@ -1009,7 +984,7 @@ static struct page **__iommu_dma_alloc_noncontiguous(struct device *dev,
out_free_sg:
sg_free_table(sgt);
out_free_iova:
iommu_dma_free_iova(cookie, iova, size, NULL);
iommu_dma_free_iova(domain, iova, size, NULL);
out_free_pages:
__iommu_dma_free_pages(pages, count);
return NULL;
@@ -1486,7 +1461,7 @@ int iommu_dma_map_sg(struct device *dev, struct scatterlist *sg, int nents,
return __finalise_sg(dev, sg, nents, iova);
out_free_iova:
iommu_dma_free_iova(cookie, iova, iova_len, NULL);
iommu_dma_free_iova(domain, iova, iova_len, NULL);
out_restore_sg:
__invalidate_sg(sg, nents);
out:
@@ -1764,17 +1739,47 @@ void iommu_setup_dma_ops(struct device *dev)
dev->dma_iommu = false;
}
static bool has_msi_cookie(const struct iommu_domain *domain)
{
return domain && (domain->cookie_type == IOMMU_COOKIE_DMA_IOVA ||
domain->cookie_type == IOMMU_COOKIE_DMA_MSI);
}
static size_t cookie_msi_granule(const struct iommu_domain *domain)
{
switch (domain->cookie_type) {
case IOMMU_COOKIE_DMA_IOVA:
return domain->iova_cookie->iovad.granule;
case IOMMU_COOKIE_DMA_MSI:
return PAGE_SIZE;
default:
BUG();
};
}
static struct list_head *cookie_msi_pages(const struct iommu_domain *domain)
{
switch (domain->cookie_type) {
case IOMMU_COOKIE_DMA_IOVA:
return &domain->iova_cookie->msi_page_list;
case IOMMU_COOKIE_DMA_MSI:
return &domain->msi_cookie->msi_page_list;
default:
BUG();
};
}
static struct iommu_dma_msi_page *iommu_dma_get_msi_page(struct device *dev,
phys_addr_t msi_addr, struct iommu_domain *domain)
{
struct iommu_dma_cookie *cookie = domain->iova_cookie;
struct list_head *msi_page_list = cookie_msi_pages(domain);
struct iommu_dma_msi_page *msi_page;
dma_addr_t iova;
int prot = IOMMU_WRITE | IOMMU_NOEXEC | IOMMU_MMIO;
size_t size = cookie_msi_granule(cookie);
size_t size = cookie_msi_granule(domain);
msi_addr &= ~(phys_addr_t)(size - 1);
list_for_each_entry(msi_page, &cookie->msi_page_list, list)
list_for_each_entry(msi_page, msi_page_list, list)
if (msi_page->phys == msi_addr)
return msi_page;
@@ -1792,23 +1797,23 @@ static struct iommu_dma_msi_page *iommu_dma_get_msi_page(struct device *dev,
INIT_LIST_HEAD(&msi_page->list);
msi_page->phys = msi_addr;
msi_page->iova = iova;
list_add(&msi_page->list, &cookie->msi_page_list);
list_add(&msi_page->list, msi_page_list);
return msi_page;
out_free_iova:
iommu_dma_free_iova(cookie, iova, size, NULL);
iommu_dma_free_iova(domain, iova, size, NULL);
out_free_page:
kfree(msi_page);
return NULL;
}
static int iommu_dma_sw_msi(struct iommu_domain *domain, struct msi_desc *desc,
phys_addr_t msi_addr)
int iommu_dma_sw_msi(struct iommu_domain *domain, struct msi_desc *desc,
phys_addr_t msi_addr)
{
struct device *dev = msi_desc_to_dev(desc);
const struct iommu_dma_msi_page *msi_page;
if (!domain->iova_cookie) {
if (!has_msi_cookie(domain)) {
msi_desc_set_iommu_msi_iova(desc, 0, 0);
return 0;
}
@@ -1818,9 +1823,8 @@ static int iommu_dma_sw_msi(struct iommu_domain *domain, struct msi_desc *desc,
if (!msi_page)
return -ENOMEM;
msi_desc_set_iommu_msi_iova(
desc, msi_page->iova,
ilog2(cookie_msi_granule(domain->iova_cookie)));
msi_desc_set_iommu_msi_iova(desc, msi_page->iova,
ilog2(cookie_msi_granule(domain)));
return 0;
}

View File

@@ -13,11 +13,15 @@ void iommu_setup_dma_ops(struct device *dev);
int iommu_get_dma_cookie(struct iommu_domain *domain);
void iommu_put_dma_cookie(struct iommu_domain *domain);
void iommu_put_msi_cookie(struct iommu_domain *domain);
int iommu_dma_init_fq(struct iommu_domain *domain);
void iommu_dma_get_resv_regions(struct device *dev, struct list_head *list);
int iommu_dma_sw_msi(struct iommu_domain *domain, struct msi_desc *desc,
phys_addr_t msi_addr);
extern bool iommu_dma_forcedac;
#else /* CONFIG_IOMMU_DMA */
@@ -40,9 +44,19 @@ static inline void iommu_put_dma_cookie(struct iommu_domain *domain)
{
}
static inline void iommu_put_msi_cookie(struct iommu_domain *domain)
{
}
static inline void iommu_dma_get_resv_regions(struct device *dev, struct list_head *list)
{
}
static inline int iommu_dma_sw_msi(struct iommu_domain *domain,
struct msi_desc *desc, phys_addr_t msi_addr)
{
return -ENODEV;
}
#endif /* CONFIG_IOMMU_DMA */
#endif /* __DMA_IOMMU_H */

View File

@@ -3383,7 +3383,8 @@ intel_iommu_domain_alloc_paging_flags(struct device *dev, u32 flags,
bool first_stage;
if (flags &
(~(IOMMU_HWPT_ALLOC_NEST_PARENT | IOMMU_HWPT_ALLOC_DIRTY_TRACKING)))
(~(IOMMU_HWPT_ALLOC_NEST_PARENT | IOMMU_HWPT_ALLOC_DIRTY_TRACKING |
IOMMU_HWPT_ALLOC_PASID)))
return ERR_PTR(-EOPNOTSUPP);
if (nested_parent && !nested_supported(iommu))
return ERR_PTR(-EOPNOTSUPP);

View File

@@ -198,7 +198,7 @@ intel_iommu_domain_alloc_nested(struct device *dev, struct iommu_domain *parent,
struct dmar_domain *domain;
int ret;
if (!nested_supported(iommu) || flags)
if (!nested_supported(iommu) || flags & ~IOMMU_HWPT_ALLOC_PASID)
return ERR_PTR(-EOPNOTSUPP);
/* Must be nested domain */

View File

@@ -5,6 +5,7 @@
#define __LINUX_IOMMU_PRIV_H
#include <linux/iommu.h>
#include <linux/msi.h>
static inline const struct iommu_ops *dev_iommu_ops(struct device *dev)
{
@@ -47,4 +48,19 @@ void iommu_detach_group_handle(struct iommu_domain *domain,
int iommu_replace_group_handle(struct iommu_group *group,
struct iommu_domain *new_domain,
struct iommu_attach_handle *handle);
#if IS_ENABLED(CONFIG_IOMMUFD_DRIVER_CORE) && IS_ENABLED(CONFIG_IRQ_MSI_IOMMU)
int iommufd_sw_msi(struct iommu_domain *domain, struct msi_desc *desc,
phys_addr_t msi_addr);
#else /* !CONFIG_IOMMUFD_DRIVER_CORE || !CONFIG_IRQ_MSI_IOMMU */
static inline int iommufd_sw_msi(struct iommu_domain *domain,
struct msi_desc *desc, phys_addr_t msi_addr)
{
return -EOPNOTSUPP;
}
#endif /* CONFIG_IOMMUFD_DRIVER_CORE && CONFIG_IRQ_MSI_IOMMU */
int iommu_replace_device_pasid(struct iommu_domain *domain,
struct device *dev, ioasid_t pasid,
struct iommu_attach_handle *handle);
#endif /* __LINUX_IOMMU_PRIV_H */

View File

@@ -310,6 +310,7 @@ static struct iommu_domain *iommu_sva_domain_alloc(struct device *dev,
}
domain->type = IOMMU_DOMAIN_SVA;
domain->cookie_type = IOMMU_COOKIE_SVA;
mmgrab(mm);
domain->mm = mm;
domain->owner = ops;

View File

@@ -18,6 +18,7 @@
#include <linux/errno.h>
#include <linux/host1x_context_bus.h>
#include <linux/iommu.h>
#include <linux/iommufd.h>
#include <linux/idr.h>
#include <linux/err.h>
#include <linux/pci.h>
@@ -539,6 +540,13 @@ static void iommu_deinit_device(struct device *dev)
dev_iommu_free(dev);
}
static struct iommu_domain *pasid_array_entry_to_domain(void *entry)
{
if (xa_pointer_tag(entry) == IOMMU_PASID_ARRAY_DOMAIN)
return xa_untag_pointer(entry);
return ((struct iommu_attach_handle *)xa_untag_pointer(entry))->domain;
}
DEFINE_MUTEX(iommu_probe_device_lock);
static int __iommu_probe_device(struct device *dev, struct list_head *group_list)
@@ -1973,8 +1981,10 @@ void iommu_set_fault_handler(struct iommu_domain *domain,
iommu_fault_handler_t handler,
void *token)
{
BUG_ON(!domain);
if (WARN_ON(!domain || domain->cookie_type != IOMMU_COOKIE_NONE))
return;
domain->cookie_type = IOMMU_COOKIE_FAULT_HANDLER;
domain->handler = handler;
domain->handler_token = token;
}
@@ -2044,9 +2054,19 @@ EXPORT_SYMBOL_GPL(iommu_paging_domain_alloc_flags);
void iommu_domain_free(struct iommu_domain *domain)
{
if (domain->type == IOMMU_DOMAIN_SVA)
switch (domain->cookie_type) {
case IOMMU_COOKIE_DMA_IOVA:
iommu_put_dma_cookie(domain);
break;
case IOMMU_COOKIE_DMA_MSI:
iommu_put_msi_cookie(domain);
break;
case IOMMU_COOKIE_SVA:
mmdrop(domain->mm);
iommu_put_dma_cookie(domain);
break;
default:
break;
}
if (domain->ops->free)
domain->ops->free(domain);
}
@@ -3335,14 +3355,15 @@ static void iommu_remove_dev_pasid(struct device *dev, ioasid_t pasid,
}
static int __iommu_set_group_pasid(struct iommu_domain *domain,
struct iommu_group *group, ioasid_t pasid)
struct iommu_group *group, ioasid_t pasid,
struct iommu_domain *old)
{
struct group_device *device, *last_gdev;
int ret;
for_each_group_device(group, device) {
ret = domain->ops->set_dev_pasid(domain, device->dev,
pasid, NULL);
pasid, old);
if (ret)
goto err_revert;
}
@@ -3354,7 +3375,15 @@ static int __iommu_set_group_pasid(struct iommu_domain *domain,
for_each_group_device(group, device) {
if (device == last_gdev)
break;
iommu_remove_dev_pasid(device->dev, pasid, domain);
/*
* If no old domain, undo the succeeded devices/pasid.
* Otherwise, rollback the succeeded devices/pasid to the old
* domain. And it is a driver bug to fail attaching with a
* previously good domain.
*/
if (!old || WARN_ON(old->ops->set_dev_pasid(old, device->dev,
pasid, domain)))
iommu_remove_dev_pasid(device->dev, pasid, domain);
}
return ret;
}
@@ -3376,6 +3405,9 @@ static void __iommu_remove_group_pasid(struct iommu_group *group,
* @pasid: the pasid of the device.
* @handle: the attach handle.
*
* Caller should always provide a new handle to avoid race with the paths
* that have lockless reference to handle if it intends to pass a valid handle.
*
* Return: 0 on success, or an error.
*/
int iommu_attach_device_pasid(struct iommu_domain *domain,
@@ -3420,7 +3452,7 @@ int iommu_attach_device_pasid(struct iommu_domain *domain,
if (ret)
goto out_unlock;
ret = __iommu_set_group_pasid(domain, group, pasid);
ret = __iommu_set_group_pasid(domain, group, pasid, NULL);
if (ret) {
xa_release(&group->pasid_array, pasid);
goto out_unlock;
@@ -3441,6 +3473,97 @@ int iommu_attach_device_pasid(struct iommu_domain *domain,
}
EXPORT_SYMBOL_GPL(iommu_attach_device_pasid);
/**
* iommu_replace_device_pasid - Replace the domain that a specific pasid
* of the device is attached to
* @domain: the new iommu domain
* @dev: the attached device.
* @pasid: the pasid of the device.
* @handle: the attach handle.
*
* This API allows the pasid to switch domains. The @pasid should have been
* attached. Otherwise, this fails. The pasid will keep the old configuration
* if replacement failed.
*
* Caller should always provide a new handle to avoid race with the paths
* that have lockless reference to handle if it intends to pass a valid handle.
*
* Return 0 on success, or an error.
*/
int iommu_replace_device_pasid(struct iommu_domain *domain,
struct device *dev, ioasid_t pasid,
struct iommu_attach_handle *handle)
{
/* Caller must be a probed driver on dev */
struct iommu_group *group = dev->iommu_group;
struct iommu_attach_handle *entry;
struct iommu_domain *curr_domain;
void *curr;
int ret;
if (!group)
return -ENODEV;
if (!domain->ops->set_dev_pasid)
return -EOPNOTSUPP;
if (dev_iommu_ops(dev) != domain->owner ||
pasid == IOMMU_NO_PASID || !handle)
return -EINVAL;
mutex_lock(&group->mutex);
entry = iommu_make_pasid_array_entry(domain, handle);
curr = xa_cmpxchg(&group->pasid_array, pasid, NULL,
XA_ZERO_ENTRY, GFP_KERNEL);
if (xa_is_err(curr)) {
ret = xa_err(curr);
goto out_unlock;
}
/*
* No domain (with or without handle) attached, hence not
* a replace case.
*/
if (!curr) {
xa_release(&group->pasid_array, pasid);
ret = -EINVAL;
goto out_unlock;
}
/*
* Reusing handle is problematic as there are paths that refers
* the handle without lock. To avoid race, reject the callers that
* attempt it.
*/
if (curr == entry) {
WARN_ON(1);
ret = -EINVAL;
goto out_unlock;
}
curr_domain = pasid_array_entry_to_domain(curr);
ret = 0;
if (curr_domain != domain) {
ret = __iommu_set_group_pasid(domain, group,
pasid, curr_domain);
if (ret)
goto out_unlock;
}
/*
* The above xa_cmpxchg() reserved the memory, and the
* group->mutex is held, this cannot fail.
*/
WARN_ON(xa_is_err(xa_store(&group->pasid_array,
pasid, entry, GFP_KERNEL)));
out_unlock:
mutex_unlock(&group->mutex);
return ret;
}
EXPORT_SYMBOL_NS_GPL(iommu_replace_device_pasid, "IOMMUFD_INTERNAL");
/*
* iommu_detach_device_pasid() - Detach the domain from pasid of device
* @domain: the iommu domain.
@@ -3536,6 +3659,9 @@ EXPORT_SYMBOL_NS_GPL(iommu_attach_handle_get, "IOMMUFD_INTERNAL");
* This is a variant of iommu_attach_group(). It allows the caller to provide
* an attach handle and use it when the domain is attached. This is currently
* used by IOMMUFD to deliver the I/O page faults.
*
* Caller should always provide a new handle to avoid race with the paths
* that have lockless reference to handle.
*/
int iommu_attach_group_handle(struct iommu_domain *domain,
struct iommu_group *group,
@@ -3605,6 +3731,9 @@ EXPORT_SYMBOL_NS_GPL(iommu_detach_group_handle, "IOMMUFD_INTERNAL");
*
* If the currently attached domain is a core domain (e.g. a default_domain),
* it will act just like the iommu_attach_group_handle().
*
* Caller should always provide a new handle to avoid race with the paths
* that have lockless reference to handle.
*/
int iommu_replace_group_handle(struct iommu_group *group,
struct iommu_domain *new_domain,
@@ -3662,8 +3791,21 @@ int iommu_dma_prepare_msi(struct msi_desc *desc, phys_addr_t msi_addr)
return 0;
mutex_lock(&group->mutex);
if (group->domain && group->domain->sw_msi)
ret = group->domain->sw_msi(group->domain, desc, msi_addr);
/* An IDENTITY domain must pass through */
if (group->domain && group->domain->type != IOMMU_DOMAIN_IDENTITY) {
switch (group->domain->cookie_type) {
case IOMMU_COOKIE_DMA_MSI:
case IOMMU_COOKIE_DMA_IOVA:
ret = iommu_dma_sw_msi(group->domain, desc, msi_addr);
break;
case IOMMU_COOKIE_IOMMUFD:
ret = iommufd_sw_msi(group->domain, desc, msi_addr);
break;
default:
ret = -EOPNOTSUPP;
break;
}
}
mutex_unlock(&group->mutex);
return ret;
}

View File

@@ -1,6 +1,6 @@
# SPDX-License-Identifier: GPL-2.0-only
config IOMMUFD_DRIVER_CORE
tristate
bool
default (IOMMUFD_DRIVER || IOMMUFD) if IOMMUFD!=n
config IOMMUFD

View File

@@ -1,7 +1,7 @@
# SPDX-License-Identifier: GPL-2.0-only
iommufd-y := \
device.o \
fault.o \
eventq.o \
hw_pagetable.o \
io_pagetable.o \
ioas.o \

View File

@@ -3,9 +3,9 @@
*/
#include <linux/iommu.h>
#include <linux/iommufd.h>
#include <linux/pci-ats.h>
#include <linux/slab.h>
#include <uapi/linux/iommufd.h>
#include <linux/msi.h>
#include "../iommu-priv.h"
#include "io_pagetable.h"
@@ -18,12 +18,17 @@ MODULE_PARM_DESC(
"Allow IOMMUFD to bind to devices even if the platform cannot isolate "
"the MSI interrupt window. Enabling this is a security weakness.");
struct iommufd_attach {
struct iommufd_hw_pagetable *hwpt;
struct xarray device_array;
};
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));
WARN_ON(!xa_empty(&igroup->pasid_attach));
xa_cmpxchg(&igroup->ictx->groups, iommu_group_id(igroup->group), igroup,
NULL, GFP_KERNEL);
@@ -90,7 +95,7 @@ static struct iommufd_group *iommufd_get_group(struct iommufd_ctx *ictx,
kref_init(&new_igroup->ref);
mutex_init(&new_igroup->lock);
INIT_LIST_HEAD(&new_igroup->device_list);
xa_init(&new_igroup->pasid_attach);
new_igroup->sw_msi_start = PHYS_ADDR_MAX;
/* group reference moves into new_igroup */
new_igroup->group = group;
@@ -294,129 +299,24 @@ u32 iommufd_device_to_id(struct iommufd_device *idev)
}
EXPORT_SYMBOL_NS_GPL(iommufd_device_to_id, "IOMMUFD");
/*
* 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 __maybe_unused 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)
static unsigned int iommufd_group_device_num(struct iommufd_group *igroup,
ioasid_t pasid)
{
struct iommufd_sw_msi_map *cur;
unsigned int max_pgoff = 0;
struct iommufd_attach *attach;
struct iommufd_device *idev;
unsigned int count = 0;
unsigned long index;
lockdep_assert_held(&ictx->sw_msi_lock);
lockdep_assert_held(&igroup->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(sizeof(*cur), GFP_KERNEL);
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;
attach = xa_load(&igroup->pasid_attach, pasid);
if (attach)
xa_for_each(&attach->device_array, index, idev)
count++;
return count;
}
static 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;
}
/*
* 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.
*/
#ifdef CONFIG_IRQ_MSI_IOMMU
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;
}
#endif
static int iommufd_group_setup_msi(struct iommufd_group *igroup,
struct iommufd_hwpt_paging *hwpt_paging)
{
@@ -443,23 +343,39 @@ static int iommufd_group_setup_msi(struct iommufd_group *igroup,
}
return 0;
}
#else
static inline int
iommufd_group_setup_msi(struct iommufd_group *igroup,
struct iommufd_hwpt_paging *hwpt_paging)
{
return 0;
}
#endif
static bool
iommufd_group_first_attach(struct iommufd_group *igroup, ioasid_t pasid)
{
lockdep_assert_held(&igroup->lock);
return !xa_load(&igroup->pasid_attach, pasid);
}
static int
iommufd_device_attach_reserved_iova(struct iommufd_device *idev,
struct iommufd_hwpt_paging *hwpt_paging)
{
struct iommufd_group *igroup = idev->igroup;
int rc;
lockdep_assert_held(&idev->igroup->lock);
lockdep_assert_held(&igroup->lock);
rc = iopt_table_enforce_dev_resv_regions(&hwpt_paging->ioas->iopt,
idev->dev,
&idev->igroup->sw_msi_start);
&igroup->sw_msi_start);
if (rc)
return rc;
if (list_empty(&idev->igroup->device_list)) {
rc = iommufd_group_setup_msi(idev->igroup, hwpt_paging);
if (iommufd_group_first_attach(igroup, IOMMU_NO_PASID)) {
rc = iommufd_group_setup_msi(igroup, hwpt_paging);
if (rc) {
iopt_remove_reserved_iova(&hwpt_paging->ioas->iopt,
idev->dev);
@@ -471,13 +387,54 @@ iommufd_device_attach_reserved_iova(struct iommufd_device *idev,
/* The device attach/detach/replace helpers for attach_handle */
static bool iommufd_device_is_attached(struct iommufd_device *idev,
ioasid_t pasid)
{
struct iommufd_attach *attach;
attach = xa_load(&idev->igroup->pasid_attach, pasid);
return xa_load(&attach->device_array, idev->obj.id);
}
static int iommufd_hwpt_pasid_compat(struct iommufd_hw_pagetable *hwpt,
struct iommufd_device *idev,
ioasid_t pasid)
{
struct iommufd_group *igroup = idev->igroup;
lockdep_assert_held(&igroup->lock);
if (pasid == IOMMU_NO_PASID) {
unsigned long start = IOMMU_NO_PASID;
if (!hwpt->pasid_compat &&
xa_find_after(&igroup->pasid_attach,
&start, UINT_MAX, XA_PRESENT))
return -EINVAL;
} else {
struct iommufd_attach *attach;
if (!hwpt->pasid_compat)
return -EINVAL;
attach = xa_load(&igroup->pasid_attach, IOMMU_NO_PASID);
if (attach && attach->hwpt && !attach->hwpt->pasid_compat)
return -EINVAL;
}
return 0;
}
static int iommufd_hwpt_attach_device(struct iommufd_hw_pagetable *hwpt,
struct iommufd_device *idev)
struct iommufd_device *idev,
ioasid_t pasid)
{
struct iommufd_attach_handle *handle;
int rc;
lockdep_assert_held(&idev->igroup->lock);
rc = iommufd_hwpt_pasid_compat(hwpt, idev, pasid);
if (rc)
return rc;
handle = kzalloc(sizeof(*handle), GFP_KERNEL);
if (!handle)
@@ -490,8 +447,12 @@ static int iommufd_hwpt_attach_device(struct iommufd_hw_pagetable *hwpt,
}
handle->idev = idev;
rc = iommu_attach_group_handle(hwpt->domain, idev->igroup->group,
&handle->handle);
if (pasid == IOMMU_NO_PASID)
rc = iommu_attach_group_handle(hwpt->domain, idev->igroup->group,
&handle->handle);
else
rc = iommu_attach_device_pasid(hwpt->domain, idev->dev, pasid,
&handle->handle);
if (rc)
goto out_disable_iopf;
@@ -506,26 +467,31 @@ static int iommufd_hwpt_attach_device(struct iommufd_hw_pagetable *hwpt,
}
static struct iommufd_attach_handle *
iommufd_device_get_attach_handle(struct iommufd_device *idev)
iommufd_device_get_attach_handle(struct iommufd_device *idev, ioasid_t pasid)
{
struct iommu_attach_handle *handle;
lockdep_assert_held(&idev->igroup->lock);
handle =
iommu_attach_handle_get(idev->igroup->group, IOMMU_NO_PASID, 0);
iommu_attach_handle_get(idev->igroup->group, pasid, 0);
if (IS_ERR(handle))
return NULL;
return to_iommufd_handle(handle);
}
static void iommufd_hwpt_detach_device(struct iommufd_hw_pagetable *hwpt,
struct iommufd_device *idev)
struct iommufd_device *idev,
ioasid_t pasid)
{
struct iommufd_attach_handle *handle;
handle = iommufd_device_get_attach_handle(idev);
iommu_detach_group_handle(hwpt->domain, idev->igroup->group);
handle = iommufd_device_get_attach_handle(idev, pasid);
if (pasid == IOMMU_NO_PASID)
iommu_detach_group_handle(hwpt->domain, idev->igroup->group);
else
iommu_detach_device_pasid(hwpt->domain, idev->dev, pasid);
if (hwpt->fault) {
iommufd_auto_response_faults(hwpt, handle);
iommufd_fault_iopf_disable(idev);
@@ -534,13 +500,19 @@ static void iommufd_hwpt_detach_device(struct iommufd_hw_pagetable *hwpt,
}
static int iommufd_hwpt_replace_device(struct iommufd_device *idev,
ioasid_t pasid,
struct iommufd_hw_pagetable *hwpt,
struct iommufd_hw_pagetable *old)
{
struct iommufd_attach_handle *handle, *old_handle =
iommufd_device_get_attach_handle(idev);
struct iommufd_attach_handle *handle, *old_handle;
int rc;
rc = iommufd_hwpt_pasid_compat(hwpt, idev, pasid);
if (rc)
return rc;
old_handle = iommufd_device_get_attach_handle(idev, pasid);
handle = kzalloc(sizeof(*handle), GFP_KERNEL);
if (!handle)
return -ENOMEM;
@@ -552,8 +524,12 @@ static int iommufd_hwpt_replace_device(struct iommufd_device *idev,
}
handle->idev = idev;
rc = iommu_replace_group_handle(idev->igroup->group, hwpt->domain,
&handle->handle);
if (pasid == IOMMU_NO_PASID)
rc = iommu_replace_group_handle(idev->igroup->group,
hwpt->domain, &handle->handle);
else
rc = iommu_replace_device_pasid(hwpt->domain, idev->dev,
pasid, &handle->handle);
if (rc)
goto out_disable_iopf;
@@ -575,22 +551,51 @@ static int iommufd_hwpt_replace_device(struct iommufd_device *idev,
}
int iommufd_hw_pagetable_attach(struct iommufd_hw_pagetable *hwpt,
struct iommufd_device *idev)
struct iommufd_device *idev, ioasid_t pasid)
{
struct iommufd_hwpt_paging *hwpt_paging = find_hwpt_paging(hwpt);
bool attach_resv = hwpt_paging && pasid == IOMMU_NO_PASID;
struct iommufd_group *igroup = idev->igroup;
struct iommufd_hw_pagetable *old_hwpt;
struct iommufd_attach *attach;
int rc;
mutex_lock(&idev->igroup->lock);
mutex_lock(&igroup->lock);
if (idev->igroup->hwpt != NULL && idev->igroup->hwpt != hwpt) {
rc = -EINVAL;
attach = xa_cmpxchg(&igroup->pasid_attach, pasid, NULL,
XA_ZERO_ENTRY, GFP_KERNEL);
if (xa_is_err(attach)) {
rc = xa_err(attach);
goto err_unlock;
}
if (hwpt_paging) {
if (!attach) {
attach = kzalloc(sizeof(*attach), GFP_KERNEL);
if (!attach) {
rc = -ENOMEM;
goto err_release_pasid;
}
xa_init(&attach->device_array);
}
old_hwpt = attach->hwpt;
rc = xa_insert(&attach->device_array, idev->obj.id, XA_ZERO_ENTRY,
GFP_KERNEL);
if (rc) {
WARN_ON(rc == -EBUSY && !old_hwpt);
goto err_free_attach;
}
if (old_hwpt && old_hwpt != hwpt) {
rc = -EINVAL;
goto err_release_devid;
}
if (attach_resv) {
rc = iommufd_device_attach_reserved_iova(idev, hwpt_paging);
if (rc)
goto err_unlock;
goto err_release_devid;
}
/*
@@ -600,51 +605,74 @@ int iommufd_hw_pagetable_attach(struct iommufd_hw_pagetable *hwpt,
* reserved regions are only updated during individual device
* attachment.
*/
if (list_empty(&idev->igroup->device_list)) {
rc = iommufd_hwpt_attach_device(hwpt, idev);
if (iommufd_group_first_attach(igroup, pasid)) {
rc = iommufd_hwpt_attach_device(hwpt, idev, pasid);
if (rc)
goto err_unresv;
idev->igroup->hwpt = hwpt;
attach->hwpt = hwpt;
WARN_ON(xa_is_err(xa_store(&igroup->pasid_attach, pasid, attach,
GFP_KERNEL)));
}
refcount_inc(&hwpt->obj.users);
list_add_tail(&idev->group_item, &idev->igroup->device_list);
mutex_unlock(&idev->igroup->lock);
WARN_ON(xa_is_err(xa_store(&attach->device_array, idev->obj.id,
idev, GFP_KERNEL)));
mutex_unlock(&igroup->lock);
return 0;
err_unresv:
if (hwpt_paging)
if (attach_resv)
iopt_remove_reserved_iova(&hwpt_paging->ioas->iopt, idev->dev);
err_release_devid:
xa_release(&attach->device_array, idev->obj.id);
err_free_attach:
if (iommufd_group_first_attach(igroup, pasid))
kfree(attach);
err_release_pasid:
if (iommufd_group_first_attach(igroup, pasid))
xa_release(&igroup->pasid_attach, pasid);
err_unlock:
mutex_unlock(&idev->igroup->lock);
mutex_unlock(&igroup->lock);
return rc;
}
struct iommufd_hw_pagetable *
iommufd_hw_pagetable_detach(struct iommufd_device *idev)
iommufd_hw_pagetable_detach(struct iommufd_device *idev, ioasid_t pasid)
{
struct iommufd_hw_pagetable *hwpt = idev->igroup->hwpt;
struct iommufd_hwpt_paging *hwpt_paging = find_hwpt_paging(hwpt);
struct iommufd_group *igroup = idev->igroup;
struct iommufd_hwpt_paging *hwpt_paging;
struct iommufd_hw_pagetable *hwpt;
struct iommufd_attach *attach;
mutex_lock(&idev->igroup->lock);
list_del(&idev->group_item);
if (list_empty(&idev->igroup->device_list)) {
iommufd_hwpt_detach_device(hwpt, idev);
idev->igroup->hwpt = NULL;
mutex_lock(&igroup->lock);
attach = xa_load(&igroup->pasid_attach, pasid);
if (!attach) {
mutex_unlock(&igroup->lock);
return NULL;
}
if (hwpt_paging)
hwpt = attach->hwpt;
hwpt_paging = find_hwpt_paging(hwpt);
xa_erase(&attach->device_array, idev->obj.id);
if (xa_empty(&attach->device_array)) {
iommufd_hwpt_detach_device(hwpt, idev, pasid);
xa_erase(&igroup->pasid_attach, pasid);
kfree(attach);
}
if (hwpt_paging && pasid == IOMMU_NO_PASID)
iopt_remove_reserved_iova(&hwpt_paging->ioas->iopt, idev->dev);
mutex_unlock(&idev->igroup->lock);
mutex_unlock(&igroup->lock);
/* Caller must destroy hwpt */
return hwpt;
}
static struct iommufd_hw_pagetable *
iommufd_device_do_attach(struct iommufd_device *idev,
iommufd_device_do_attach(struct iommufd_device *idev, ioasid_t pasid,
struct iommufd_hw_pagetable *hwpt)
{
int rc;
rc = iommufd_hw_pagetable_attach(hwpt, idev);
rc = iommufd_hw_pagetable_attach(hwpt, idev, pasid);
if (rc)
return ERR_PTR(rc);
return NULL;
@@ -654,11 +682,14 @@ static void
iommufd_group_remove_reserved_iova(struct iommufd_group *igroup,
struct iommufd_hwpt_paging *hwpt_paging)
{
struct iommufd_attach *attach;
struct iommufd_device *cur;
unsigned long index;
lockdep_assert_held(&igroup->lock);
list_for_each_entry(cur, &igroup->device_list, group_item)
attach = xa_load(&igroup->pasid_attach, IOMMU_NO_PASID);
xa_for_each(&attach->device_array, index, cur)
iopt_remove_reserved_iova(&hwpt_paging->ioas->iopt, cur->dev);
}
@@ -667,14 +698,17 @@ iommufd_group_do_replace_reserved_iova(struct iommufd_group *igroup,
struct iommufd_hwpt_paging *hwpt_paging)
{
struct iommufd_hwpt_paging *old_hwpt_paging;
struct iommufd_attach *attach;
struct iommufd_device *cur;
unsigned long index;
int rc;
lockdep_assert_held(&igroup->lock);
old_hwpt_paging = find_hwpt_paging(igroup->hwpt);
attach = xa_load(&igroup->pasid_attach, IOMMU_NO_PASID);
old_hwpt_paging = find_hwpt_paging(attach->hwpt);
if (!old_hwpt_paging || hwpt_paging->ioas != old_hwpt_paging->ioas) {
list_for_each_entry(cur, &igroup->device_list, group_item) {
xa_for_each(&attach->device_array, index, cur) {
rc = iopt_table_enforce_dev_resv_regions(
&hwpt_paging->ioas->iopt, cur->dev, NULL);
if (rc)
@@ -693,69 +727,81 @@ iommufd_group_do_replace_reserved_iova(struct iommufd_group *igroup,
}
static struct iommufd_hw_pagetable *
iommufd_device_do_replace(struct iommufd_device *idev,
iommufd_device_do_replace(struct iommufd_device *idev, ioasid_t pasid,
struct iommufd_hw_pagetable *hwpt)
{
struct iommufd_hwpt_paging *hwpt_paging = find_hwpt_paging(hwpt);
bool attach_resv = hwpt_paging && pasid == IOMMU_NO_PASID;
struct iommufd_hwpt_paging *old_hwpt_paging;
struct iommufd_group *igroup = idev->igroup;
struct iommufd_hw_pagetable *old_hwpt;
struct iommufd_attach *attach;
unsigned int num_devices;
int rc;
mutex_lock(&idev->igroup->lock);
mutex_lock(&igroup->lock);
if (igroup->hwpt == NULL) {
attach = xa_load(&igroup->pasid_attach, pasid);
if (!attach) {
rc = -EINVAL;
goto err_unlock;
}
if (hwpt == igroup->hwpt) {
mutex_unlock(&idev->igroup->lock);
old_hwpt = attach->hwpt;
WARN_ON(!old_hwpt || xa_empty(&attach->device_array));
if (!iommufd_device_is_attached(idev, pasid)) {
rc = -EINVAL;
goto err_unlock;
}
if (hwpt == old_hwpt) {
mutex_unlock(&igroup->lock);
return NULL;
}
old_hwpt = igroup->hwpt;
if (hwpt_paging) {
if (attach_resv) {
rc = iommufd_group_do_replace_reserved_iova(igroup, hwpt_paging);
if (rc)
goto err_unlock;
}
rc = iommufd_hwpt_replace_device(idev, hwpt, old_hwpt);
rc = iommufd_hwpt_replace_device(idev, pasid, hwpt, old_hwpt);
if (rc)
goto err_unresv;
old_hwpt_paging = find_hwpt_paging(old_hwpt);
if (old_hwpt_paging &&
if (old_hwpt_paging && pasid == IOMMU_NO_PASID &&
(!hwpt_paging || hwpt_paging->ioas != old_hwpt_paging->ioas))
iommufd_group_remove_reserved_iova(igroup, old_hwpt_paging);
igroup->hwpt = hwpt;
attach->hwpt = hwpt;
num_devices = list_count_nodes(&igroup->device_list);
num_devices = iommufd_group_device_num(igroup, pasid);
/*
* Move the refcounts held by the device_list to the new hwpt. Retain a
* Move the refcounts held by the device_array to the new hwpt. Retain a
* refcount for this thread as the caller will free it.
*/
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);
mutex_unlock(&igroup->lock);
/* Caller must destroy old_hwpt */
return old_hwpt;
err_unresv:
if (hwpt_paging)
if (attach_resv)
iommufd_group_remove_reserved_iova(igroup, hwpt_paging);
err_unlock:
mutex_unlock(&idev->igroup->lock);
mutex_unlock(&igroup->lock);
return ERR_PTR(rc);
}
typedef struct iommufd_hw_pagetable *(*attach_fn)(
struct iommufd_device *idev, struct iommufd_hw_pagetable *hwpt);
struct iommufd_device *idev, ioasid_t pasid,
struct iommufd_hw_pagetable *hwpt);
/*
* When automatically managing the domains we search for a compatible domain in
@@ -763,7 +809,7 @@ typedef struct iommufd_hw_pagetable *(*attach_fn)(
* Automatic domain selection will never pick a manually created domain.
*/
static struct iommufd_hw_pagetable *
iommufd_device_auto_get_domain(struct iommufd_device *idev,
iommufd_device_auto_get_domain(struct iommufd_device *idev, ioasid_t pasid,
struct iommufd_ioas *ioas, u32 *pt_id,
attach_fn do_attach)
{
@@ -792,7 +838,7 @@ iommufd_device_auto_get_domain(struct iommufd_device *idev,
hwpt = &hwpt_paging->common;
if (!iommufd_lock_obj(&hwpt->obj))
continue;
destroy_hwpt = (*do_attach)(idev, hwpt);
destroy_hwpt = (*do_attach)(idev, pasid, hwpt);
if (IS_ERR(destroy_hwpt)) {
iommufd_put_object(idev->ictx, &hwpt->obj);
/*
@@ -810,8 +856,8 @@ iommufd_device_auto_get_domain(struct iommufd_device *idev,
goto out_unlock;
}
hwpt_paging = iommufd_hwpt_paging_alloc(idev->ictx, ioas, idev, 0,
immediate_attach, NULL);
hwpt_paging = iommufd_hwpt_paging_alloc(idev->ictx, ioas, idev, pasid,
0, immediate_attach, NULL);
if (IS_ERR(hwpt_paging)) {
destroy_hwpt = ERR_CAST(hwpt_paging);
goto out_unlock;
@@ -819,7 +865,7 @@ iommufd_device_auto_get_domain(struct iommufd_device *idev,
hwpt = &hwpt_paging->common;
if (!immediate_attach) {
destroy_hwpt = (*do_attach)(idev, hwpt);
destroy_hwpt = (*do_attach)(idev, pasid, hwpt);
if (IS_ERR(destroy_hwpt))
goto out_abort;
} else {
@@ -840,8 +886,9 @@ iommufd_device_auto_get_domain(struct iommufd_device *idev,
return destroy_hwpt;
}
static int iommufd_device_change_pt(struct iommufd_device *idev, u32 *pt_id,
attach_fn do_attach)
static int iommufd_device_change_pt(struct iommufd_device *idev,
ioasid_t pasid,
u32 *pt_id, attach_fn do_attach)
{
struct iommufd_hw_pagetable *destroy_hwpt;
struct iommufd_object *pt_obj;
@@ -856,7 +903,7 @@ static int iommufd_device_change_pt(struct iommufd_device *idev, u32 *pt_id,
struct iommufd_hw_pagetable *hwpt =
container_of(pt_obj, struct iommufd_hw_pagetable, obj);
destroy_hwpt = (*do_attach)(idev, hwpt);
destroy_hwpt = (*do_attach)(idev, pasid, hwpt);
if (IS_ERR(destroy_hwpt))
goto out_put_pt_obj;
break;
@@ -865,8 +912,8 @@ static int iommufd_device_change_pt(struct iommufd_device *idev, u32 *pt_id,
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);
destroy_hwpt = iommufd_device_auto_get_domain(idev, pasid, ioas,
pt_id, do_attach);
if (IS_ERR(destroy_hwpt))
goto out_put_pt_obj;
break;
@@ -888,22 +935,26 @@ static int iommufd_device_change_pt(struct iommufd_device *idev, u32 *pt_id,
}
/**
* iommufd_device_attach - Connect a device to an iommu_domain
* iommufd_device_attach - Connect a device/pasid to an iommu_domain
* @idev: device to attach
* @pasid: pasid to attach
* @pt_id: Input a IOMMUFD_OBJ_IOAS, or IOMMUFD_OBJ_HWPT_PAGING
* Output the IOMMUFD_OBJ_HWPT_PAGING ID
*
* This connects the device to an iommu_domain, either automatically or manually
* selected. Once this completes the device could do DMA.
* This connects the device/pasid to an iommu_domain, either automatically
* or manually selected. Once this completes the device could do DMA with
* @pasid. @pasid is IOMMU_NO_PASID if this attach is for no pasid usage.
*
* The caller should return the resulting pt_id back to userspace.
* This function is undone by calling iommufd_device_detach().
*/
int iommufd_device_attach(struct iommufd_device *idev, u32 *pt_id)
int iommufd_device_attach(struct iommufd_device *idev, ioasid_t pasid,
u32 *pt_id)
{
int rc;
rc = iommufd_device_change_pt(idev, pt_id, &iommufd_device_do_attach);
rc = iommufd_device_change_pt(idev, pasid, pt_id,
&iommufd_device_do_attach);
if (rc)
return rc;
@@ -917,8 +968,9 @@ int iommufd_device_attach(struct iommufd_device *idev, u32 *pt_id)
EXPORT_SYMBOL_NS_GPL(iommufd_device_attach, "IOMMUFD");
/**
* iommufd_device_replace - Change the device's iommu_domain
* iommufd_device_replace - Change the device/pasid's iommu_domain
* @idev: device to change
* @pasid: pasid to change
* @pt_id: Input a IOMMUFD_OBJ_IOAS, or IOMMUFD_OBJ_HWPT_PAGING
* Output the IOMMUFD_OBJ_HWPT_PAGING ID
*
@@ -929,27 +981,33 @@ EXPORT_SYMBOL_NS_GPL(iommufd_device_attach, "IOMMUFD");
*
* If it fails then no change is made to the attachment. The iommu driver may
* implement this so there is no disruption in translation. This can only be
* called if iommufd_device_attach() has already succeeded.
* called if iommufd_device_attach() has already succeeded. @pasid is
* IOMMU_NO_PASID for no pasid usage.
*/
int iommufd_device_replace(struct iommufd_device *idev, u32 *pt_id)
int iommufd_device_replace(struct iommufd_device *idev, ioasid_t pasid,
u32 *pt_id)
{
return iommufd_device_change_pt(idev, pt_id,
return iommufd_device_change_pt(idev, pasid, pt_id,
&iommufd_device_do_replace);
}
EXPORT_SYMBOL_NS_GPL(iommufd_device_replace, "IOMMUFD");
/**
* iommufd_device_detach - Disconnect a device to an iommu_domain
* iommufd_device_detach - Disconnect a device/device to an iommu_domain
* @idev: device to detach
* @pasid: pasid to detach
*
* Undo iommufd_device_attach(). This disconnects the idev from the previously
* attached pt_id. The device returns back to a blocked DMA translation.
* @pasid is IOMMU_NO_PASID for no pasid usage.
*/
void iommufd_device_detach(struct iommufd_device *idev)
void iommufd_device_detach(struct iommufd_device *idev, ioasid_t pasid)
{
struct iommufd_hw_pagetable *hwpt;
hwpt = iommufd_hw_pagetable_detach(idev);
hwpt = iommufd_hw_pagetable_detach(idev, pasid);
if (!hwpt)
return;
iommufd_hw_pagetable_put(idev->ictx, hwpt);
refcount_dec(&idev->obj.users);
}
@@ -1349,7 +1407,7 @@ int iommufd_access_rw(struct iommufd_access *access, unsigned long iova,
struct io_pagetable *iopt;
struct iopt_area *area;
unsigned long last_iova;
int rc;
int rc = -EINVAL;
if (!length)
return -EINVAL;
@@ -1405,7 +1463,8 @@ int iommufd_get_hw_info(struct iommufd_ucmd *ucmd)
void *data;
int rc;
if (cmd->flags || cmd->__reserved)
if (cmd->flags || cmd->__reserved[0] || cmd->__reserved[1] ||
cmd->__reserved[2])
return -EOPNOTSUPP;
idev = iommufd_get_device(ucmd, cmd->dev_id);
@@ -1462,6 +1521,36 @@ int iommufd_get_hw_info(struct iommufd_ucmd *ucmd)
if (device_iommu_capable(idev->dev, IOMMU_CAP_DIRTY_TRACKING))
cmd->out_capabilities |= IOMMU_HW_CAP_DIRTY_TRACKING;
cmd->out_max_pasid_log2 = 0;
/*
* Currently, all iommu drivers enable PASID in the probe_device()
* op if iommu and device supports it. So the max_pasids stored in
* dev->iommu indicates both PASID support and enable status. A
* non-zero dev->iommu->max_pasids means PASID is supported and
* enabled. The iommufd only reports PASID capability to userspace
* if it's enabled.
*/
if (idev->dev->iommu->max_pasids) {
cmd->out_max_pasid_log2 = ilog2(idev->dev->iommu->max_pasids);
if (dev_is_pci(idev->dev)) {
struct pci_dev *pdev = to_pci_dev(idev->dev);
int ctrl;
ctrl = pci_pasid_status(pdev);
WARN_ON_ONCE(ctrl < 0 ||
!(ctrl & PCI_PASID_CTRL_ENABLE));
if (ctrl & PCI_PASID_CTRL_EXEC)
cmd->out_capabilities |=
IOMMU_HW_CAP_PCI_PASID_EXEC;
if (ctrl & PCI_PASID_CTRL_PRIV)
cmd->out_capabilities |=
IOMMU_HW_CAP_PCI_PASID_PRIV;
}
}
rc = iommufd_ucmd_respond(ucmd, sizeof(*cmd));
out_free:
kfree(data);

View File

@@ -49,5 +49,203 @@ struct device *iommufd_viommu_find_dev(struct iommufd_viommu *viommu,
}
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 (vdev->dev == dev) {
*vdev_id = vdev->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;
}
spin_lock(&veventq->common.lock);
if (veventq->num_events == veventq->depth) {
vevent = &veventq->lost_events_header;
goto out_set_header;
}
vevent = kzalloc(struct_size(vevent, event_data, data_len), GFP_ATOMIC);
if (!vevent) {
rc = -ENOMEM;
vevent = &veventq->lost_events_header;
goto out_set_header;
}
memcpy(vevent->event_data, event_data, data_len);
vevent->data_len = 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(sizeof(*cur), GFP_KERNEL);
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");

View File

@@ -0,0 +1,598 @@
// 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/pci.h>
#include <linux/pci-ats.h>
#include <linux/poll.h>
#include <uapi/linux/iommufd.h>
#include "../iommu-priv.h"
#include "iommufd_private.h"
/* IOMMUFD_OBJ_FAULT Functions */
int iommufd_fault_iopf_enable(struct iommufd_device *idev)
{
struct device *dev = idev->dev;
int ret;
/*
* Once we turn on PCI/PRI support for VF, the response failure code
* should not be forwarded to the hardware due to PRI being a shared
* resource between PF and VFs. There is no coordination for this
* shared capability. This waits for a vPRI reset to recover.
*/
if (dev_is_pci(dev)) {
struct pci_dev *pdev = to_pci_dev(dev);
if (pdev->is_virtfn && pci_pri_supported(pdev))
return -EINVAL;
}
mutex_lock(&idev->iopf_lock);
/* Device iopf has already been on. */
if (++idev->iopf_enabled > 1) {
mutex_unlock(&idev->iopf_lock);
return 0;
}
ret = iommu_dev_enable_feature(dev, IOMMU_DEV_FEAT_IOPF);
if (ret)
--idev->iopf_enabled;
mutex_unlock(&idev->iopf_lock);
return ret;
}
void iommufd_fault_iopf_disable(struct iommufd_device *idev)
{
mutex_lock(&idev->iopf_lock);
if (!WARN_ON(idev->iopf_enabled == 0)) {
if (--idev->iopf_enabled == 0)
iommu_dev_disable_feature(idev->dev, IOMMU_DEV_FEAT_IOPF);
}
mutex_unlock(&idev->iopf_lock);
}
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)
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))) {
if (done >= count ||
group->fault_count * fault_size > count - done) {
iommufd_fault_deliver_restore(fault, group);
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 + done, &data, fault_size)) {
xa_erase(&fault->response, group->cookie);
iommufd_fault_deliver_restore(fault, group);
rc = -EFAULT;
break;
}
done += fault_size;
}
}
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) {
rc = copy_from_user(&response, buf + done, response_size);
if (rc)
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(sizeof(*vevent), GFP_ATOMIC);
if (!vevent)
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;
while ((cur = iommufd_veventq_deliver_fetch(veventq))) {
/* 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);
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);
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;
int fdno;
spin_lock_init(&eventq->lock);
INIT_LIST_HEAD(&eventq->deliver);
init_waitqueue_head(&eventq->wait_queue);
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);
fdno = get_unused_fd_flags(O_CLOEXEC);
if (fdno < 0)
fput(filep);
return fdno;
}
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->ictx, 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) {
rc = fdno;
goto out_abort;
}
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;
iommufd_object_finalize(ucmd->ictx, &fault->common.obj);
fd_install(fdno, fault->common.filep);
return 0;
out_put_fdno:
put_unused_fd(fdno);
fput(fault->common.filep);
out_abort:
iommufd_object_abort_and_destroy(ucmd->ictx, &fault->common.obj);
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;
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);
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)
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);
fput(veventq->common.filep);
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;
}

View File

@@ -1,342 +0,0 @@
// 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/pci.h>
#include <linux/pci-ats.h>
#include <linux/poll.h>
#include <uapi/linux/iommufd.h>
#include "../iommu-priv.h"
#include "iommufd_private.h"
int iommufd_fault_iopf_enable(struct iommufd_device *idev)
{
struct device *dev = idev->dev;
int ret;
/*
* Once we turn on PCI/PRI support for VF, the response failure code
* should not be forwarded to the hardware due to PRI being a shared
* resource between PF and VFs. There is no coordination for this
* shared capability. This waits for a vPRI reset to recover.
*/
if (dev_is_pci(dev)) {
struct pci_dev *pdev = to_pci_dev(dev);
if (pdev->is_virtfn && pci_pri_supported(pdev))
return -EINVAL;
}
mutex_lock(&idev->iopf_lock);
/* Device iopf has already been on. */
if (++idev->iopf_enabled > 1) {
mutex_unlock(&idev->iopf_lock);
return 0;
}
ret = iommu_dev_enable_feature(dev, IOMMU_DEV_FEAT_IOPF);
if (ret)
--idev->iopf_enabled;
mutex_unlock(&idev->iopf_lock);
return ret;
}
void iommufd_fault_iopf_disable(struct iommufd_device *idev)
{
mutex_lock(&idev->iopf_lock);
if (!WARN_ON(idev->iopf_enabled == 0)) {
if (--idev->iopf_enabled == 0)
iommu_dev_disable_feature(idev->dev, IOMMU_DEV_FEAT_IOPF);
}
mutex_unlock(&idev->iopf_lock);
}
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)
return;
INIT_LIST_HEAD(&free_list);
mutex_lock(&fault->mutex);
spin_lock(&fault->lock);
list_for_each_entry_safe(group, next, &fault->deliver, node) {
if (group->attach_handle != &handle->handle)
continue;
list_move(&group->node, &free_list);
}
spin_unlock(&fault->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_fault *fault = container_of(obj, struct iommufd_fault, obj);
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->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;
}
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_fault *fault = filep->private_data;
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))) {
if (done >= count ||
group->fault_count * fault_size > count - done) {
iommufd_fault_deliver_restore(fault, group);
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 + done, &data, fault_size)) {
xa_erase(&fault->response, group->cookie);
iommufd_fault_deliver_restore(fault, group);
rc = -EFAULT;
break;
}
done += fault_size;
}
}
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_fault *fault = filep->private_data;
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) {
rc = copy_from_user(&response, buf + done, response_size);
if (rc)
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;
}
static __poll_t iommufd_fault_fops_poll(struct file *filep,
struct poll_table_struct *wait)
{
struct iommufd_fault *fault = filep->private_data;
__poll_t pollflags = EPOLLOUT;
poll_wait(filep, &fault->wait_queue, wait);
spin_lock(&fault->lock);
if (!list_empty(&fault->deliver))
pollflags |= EPOLLIN | EPOLLRDNORM;
spin_unlock(&fault->lock);
return pollflags;
}
static int iommufd_fault_fops_release(struct inode *inode, struct file *filep)
{
struct iommufd_fault *fault = filep->private_data;
refcount_dec(&fault->obj.users);
iommufd_ctx_put(fault->ictx);
return 0;
}
static const struct file_operations iommufd_fault_fops = {
.owner = THIS_MODULE,
.open = nonseekable_open,
.read = iommufd_fault_fops_read,
.write = iommufd_fault_fops_write,
.poll = iommufd_fault_fops_poll,
.release = iommufd_fault_fops_release,
};
int iommufd_fault_alloc(struct iommufd_ucmd *ucmd)
{
struct iommu_fault_alloc *cmd = ucmd->cmd;
struct iommufd_fault *fault;
struct file *filep;
int fdno;
int rc;
if (cmd->flags)
return -EOPNOTSUPP;
fault = iommufd_object_alloc(ucmd->ictx, fault, IOMMUFD_OBJ_FAULT);
if (IS_ERR(fault))
return PTR_ERR(fault);
fault->ictx = ucmd->ictx;
INIT_LIST_HEAD(&fault->deliver);
xa_init_flags(&fault->response, XA_FLAGS_ALLOC1);
mutex_init(&fault->mutex);
spin_lock_init(&fault->lock);
init_waitqueue_head(&fault->wait_queue);
filep = anon_inode_getfile("[iommufd-pgfault]", &iommufd_fault_fops,
fault, O_RDWR);
if (IS_ERR(filep)) {
rc = PTR_ERR(filep);
goto out_abort;
}
refcount_inc(&fault->obj.users);
iommufd_ctx_get(fault->ictx);
fault->filep = filep;
fdno = get_unused_fd_flags(O_CLOEXEC);
if (fdno < 0) {
rc = fdno;
goto out_fput;
}
cmd->out_fault_id = fault->obj.id;
cmd->out_fault_fd = fdno;
rc = iommufd_ucmd_respond(ucmd, sizeof(*cmd));
if (rc)
goto out_put_fdno;
iommufd_object_finalize(ucmd->ictx, &fault->obj);
fd_install(fdno, fault->filep);
return 0;
out_put_fdno:
put_unused_fd(fdno);
out_fput:
fput(filep);
out_abort:
iommufd_object_abort_and_destroy(ucmd->ictx, &fault->obj);
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;
spin_lock(&fault->lock);
list_add_tail(&group->node, &fault->deliver);
spin_unlock(&fault->lock);
wake_up_interruptible(&fault->wait_queue);
return 0;
}

View File

@@ -14,7 +14,7 @@ static void __iommufd_hwpt_destroy(struct iommufd_hw_pagetable *hwpt)
iommu_domain_free(hwpt->domain);
if (hwpt->fault)
refcount_dec(&hwpt->fault->obj.users);
refcount_dec(&hwpt->fault->common.obj.users);
}
void iommufd_hwpt_paging_destroy(struct iommufd_object *obj)
@@ -90,6 +90,7 @@ iommufd_hwpt_paging_enforce_cc(struct iommufd_hwpt_paging *hwpt_paging)
* @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
@@ -105,13 +106,14 @@ iommufd_hwpt_paging_enforce_cc(struct iommufd_hwpt_paging *hwpt_paging)
*/
struct iommufd_hwpt_paging *
iommufd_hwpt_paging_alloc(struct iommufd_ctx *ictx, struct iommufd_ioas *ioas,
struct iommufd_device *idev, u32 flags,
bool immediate_attach,
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_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;
@@ -126,12 +128,16 @@ iommufd_hwpt_paging_alloc(struct iommufd_ctx *ictx, struct iommufd_ioas *ioas,
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() */
@@ -156,7 +162,8 @@ iommufd_hwpt_paging_alloc(struct iommufd_ctx *ictx, struct iommufd_ioas *ioas,
goto out_abort;
}
}
iommu_domain_set_sw_msi(hwpt->domain, iommufd_sw_msi);
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
@@ -185,7 +192,7 @@ iommufd_hwpt_paging_alloc(struct iommufd_ctx *ictx, struct iommufd_ioas *ioas,
* sequence. Once those drivers are fixed this should be removed.
*/
if (immediate_attach) {
rc = iommufd_hw_pagetable_attach(hwpt, idev);
rc = iommufd_hw_pagetable_attach(hwpt, idev, pasid);
if (rc)
goto out_abort;
}
@@ -198,7 +205,7 @@ iommufd_hwpt_paging_alloc(struct iommufd_ctx *ictx, struct iommufd_ioas *ioas,
out_detach:
if (immediate_attach)
iommufd_hw_pagetable_detach(idev);
iommufd_hw_pagetable_detach(idev, pasid);
out_abort:
iommufd_object_abort_and_destroy(ictx, &hwpt->obj);
return ERR_PTR(rc);
@@ -227,7 +234,7 @@ iommufd_hwpt_nested_alloc(struct iommufd_ctx *ictx,
struct iommufd_hw_pagetable *hwpt;
int rc;
if ((flags & ~IOMMU_HWPT_FAULT_ID_VALID) ||
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 ||
@@ -239,6 +246,7 @@ iommufd_hwpt_nested_alloc(struct iommufd_ctx *ictx,
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;
@@ -252,7 +260,8 @@ iommufd_hwpt_nested_alloc(struct iommufd_ctx *ictx,
goto out_abort;
}
hwpt->domain->owner = ops;
iommu_domain_set_sw_msi(hwpt->domain, iommufd_sw_msi);
hwpt->domain->iommufd_hwpt = hwpt;
hwpt->domain->cookie_type = IOMMU_COOKIE_IOMMUFD;
if (WARN_ON_ONCE(hwpt->domain->type != IOMMU_DOMAIN_NESTED)) {
rc = -EINVAL;
@@ -282,7 +291,7 @@ iommufd_viommu_alloc_hwpt_nested(struct iommufd_viommu *viommu, u32 flags,
struct iommufd_hw_pagetable *hwpt;
int rc;
if (flags & ~IOMMU_HWPT_FAULT_ID_VALID)
if (flags & ~(IOMMU_HWPT_FAULT_ID_VALID | IOMMU_HWPT_ALLOC_PASID))
return ERR_PTR(-EOPNOTSUPP);
if (!user_data->len)
return ERR_PTR(-EOPNOTSUPP);
@@ -294,6 +303,7 @@ iommufd_viommu_alloc_hwpt_nested(struct iommufd_viommu *viommu, u32 flags,
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);
@@ -308,8 +318,9 @@ iommufd_viommu_alloc_hwpt_nested(struct iommufd_viommu *viommu, u32 flags,
hwpt->domain = NULL;
goto out_abort;
}
hwpt->domain->iommufd_hwpt = hwpt;
hwpt->domain->owner = viommu->iommu_dev->ops;
iommu_domain_set_sw_msi(hwpt->domain, iommufd_sw_msi);
hwpt->domain->cookie_type = IOMMU_COOKIE_IOMMUFD;
if (WARN_ON_ONCE(hwpt->domain->type != IOMMU_DOMAIN_NESTED)) {
rc = -EINVAL;
@@ -358,8 +369,8 @@ int iommufd_hwpt_alloc(struct iommufd_ucmd *ucmd)
ioas = container_of(pt_obj, struct iommufd_ioas, obj);
mutex_lock(&ioas->mutex);
hwpt_paging = iommufd_hwpt_paging_alloc(
ucmd->ictx, ioas, idev, cmd->flags, false,
user_data.len ? &user_data : NULL);
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;
@@ -409,10 +420,9 @@ int iommufd_hwpt_alloc(struct iommufd_ucmd *ucmd)
}
hwpt->fault = fault;
hwpt->domain->iopf_handler = iommufd_fault_iopf_handler;
refcount_inc(&fault->obj.users);
iommufd_put_object(ucmd->ictx, &fault->obj);
refcount_inc(&fault->common.obj.users);
iommufd_put_object(ucmd->ictx, &fault->common.obj);
}
hwpt->domain->iommufd_hwpt = hwpt;
cmd->out_hwpt_id = hwpt->obj.id;
rc = iommufd_ucmd_respond(ucmd, sizeof(*cmd));

View File

@@ -32,8 +32,11 @@ struct iommufd_sw_msi_maps {
DECLARE_BITMAP(bitmap, 64);
};
int iommufd_sw_msi(struct iommu_domain *domain, struct msi_desc *desc,
phys_addr_t msi_addr);
#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;
@@ -296,6 +299,7 @@ struct iommufd_hw_pagetable {
struct iommufd_object obj;
struct iommu_domain *domain;
struct iommufd_fault *fault;
bool pasid_compat : 1;
};
struct iommufd_hwpt_paging {
@@ -366,13 +370,13 @@ 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, u32 flags,
bool immediate_attach,
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);
struct iommufd_device *idev, ioasid_t pasid);
struct iommufd_hw_pagetable *
iommufd_hw_pagetable_detach(struct iommufd_device *idev);
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);
@@ -396,13 +400,14 @@ static inline void iommufd_hw_pagetable_put(struct iommufd_ctx *ictx,
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 iommufd_hw_pagetable *hwpt;
struct list_head device_list;
struct xarray pasid_attach;
struct iommufd_sw_msi_maps required_sw_msi;
phys_addr_t sw_msi_start;
};
@@ -454,49 +459,17 @@ void iopt_remove_access(struct io_pagetable *iopt,
u32 iopt_access_list_id);
void iommufd_access_destroy_object(struct iommufd_object *obj);
/*
* 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 {
struct iommufd_object obj;
struct iommufd_ctx *ictx;
struct file *filep;
spinlock_t lock; /* protects the deliver list */
struct list_head deliver;
struct mutex mutex; /* serializes response flows */
struct xarray response;
struct wait_queue_head wait_queue;
};
/* Fetch the first node out of the fault->deliver list */
static inline struct iopf_group *
iommufd_fault_deliver_fetch(struct iommufd_fault *fault)
{
struct list_head *list = &fault->deliver;
struct iopf_group *group = NULL;
spin_lock(&fault->lock);
if (!list_empty(list)) {
group = list_first_entry(list, struct iopf_group, node);
list_del(&group->node);
}
spin_unlock(&fault->lock);
return group;
}
/* Restore a node back to the head of the fault->deliver list */
static inline void iommufd_fault_deliver_restore(struct iommufd_fault *fault,
struct iopf_group *group)
{
spin_lock(&fault->lock);
list_add(&group->node, &fault->deliver);
spin_unlock(&fault->lock);
}
struct iommufd_attach_handle {
struct iommu_attach_handle handle;
struct iommufd_device *idev;
@@ -505,12 +478,29 @@ struct iommufd_attach_handle {
/* 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, obj);
struct iommufd_fault, common.obj);
}
int iommufd_fault_alloc(struct iommufd_ucmd *ucmd);
@@ -522,6 +512,74 @@ void iommufd_fault_iopf_disable(struct iommufd_device *idev);
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;
u64 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 */
struct iommufd_vevent lost_events_header;
unsigned int type;
unsigned int depth;
/* Use common.lock for protection */
u32 num_events;
u32 sequence;
};
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)
{
@@ -530,6 +588,20 @@ iommufd_get_viommu(struct iommufd_ucmd *ucmd, u32 id)
struct iommufd_viommu, obj);
}
static inline struct iommufd_veventq *
iommufd_viommu_find_veventq(struct iommufd_viommu *viommu, u32 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);

View File

@@ -24,6 +24,11 @@ enum {
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,
};
enum {
@@ -48,6 +53,7 @@ enum {
enum {
MOCK_FLAGS_DEVICE_NO_DIRTY = 1 << 0,
MOCK_FLAGS_DEVICE_HUGE_IOVA = 1 << 1,
MOCK_FLAGS_DEVICE_PASID = 1 << 2,
};
enum {
@@ -60,6 +66,9 @@ enum {
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;
@@ -145,11 +154,36 @@ struct iommu_test_cmd {
__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;
};
__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
@@ -212,4 +246,10 @@ struct iommu_viommu_invalidate_selftest {
__u32 cache_id;
};
#define IOMMU_VEVENTQ_TYPE_SELFTEST 0xbeefbeef
struct iommu_viommu_event_selftest {
__u32 virt_id;
};
#endif

View File

@@ -317,6 +317,7 @@ union ucmd_buffer {
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
@@ -372,6 +373,8 @@ static const struct iommufd_ioctl_op iommufd_ioctl_ops[] = {
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,
@@ -514,6 +517,10 @@ static const struct iommufd_object_ops iommufd_object_ops[] = {
[IOMMUFD_OBJ_VDEVICE] = {
.destroy = iommufd_vdevice_destroy,
},
[IOMMUFD_OBJ_VEVENTQ] = {
.destroy = iommufd_veventq_destroy,
.abort = iommufd_veventq_abort,
},
[IOMMUFD_OBJ_VIOMMU] = {
.destroy = iommufd_viommu_destroy,
},

View File

@@ -161,9 +161,13 @@ enum selftest_obj_type {
struct mock_dev {
struct device dev;
struct mock_viommu *viommu;
struct rw_semaphore viommu_rwsem;
unsigned long flags;
unsigned long vdev_id;
int id;
u32 cache[MOCK_DEV_CACHE_NUM];
atomic_t pasid_1024_fake_error;
};
static inline struct mock_dev *to_mock_dev(struct device *dev)
@@ -193,15 +197,71 @@ static int mock_domain_nop_attach(struct iommu_domain *domain,
struct device *dev)
{
struct mock_dev *mdev = to_mock_dev(dev);
struct mock_viommu *new_viommu = NULL;
unsigned long vdev_id = 0;
int rc;
if (domain->dirty_ops && (mdev->flags & MOCK_FLAGS_DEVICE_NO_DIRTY))
return -EINVAL;
iommu_group_mutex_assert(dev);
if (domain->type == IOMMU_DOMAIN_NESTED) {
new_viommu = to_mock_nested(domain)->mock_viommu;
if (new_viommu) {
rc = iommufd_viommu_get_vdev_id(&new_viommu->core, dev,
&vdev_id);
if (rc)
return rc;
}
}
if (new_viommu != mdev->viommu) {
down_write(&mdev->viommu_rwsem);
mdev->viommu = new_viommu;
mdev->vdev_id = vdev_id;
up_write(&mdev->viommu_rwsem);
}
return 0;
}
static int mock_domain_set_dev_pasid_nop(struct iommu_domain *domain,
struct device *dev, ioasid_t pasid,
struct iommu_domain *old)
{
struct mock_dev *mdev = to_mock_dev(dev);
/*
* Per the first attach with pasid 1024, set the
* mdev->pasid_1024_fake_error. Hence the second call of this op
* can fake an error to validate the error path of the core. This
* is helpful to test the case in which the iommu core needs to
* rollback to the old domain due to driver failure. e.g. replace.
* User should be careful about the third call of this op, it shall
* succeed since the mdev->pasid_1024_fake_error is cleared in the
* second call.
*/
if (pasid == 1024) {
if (domain->type == IOMMU_DOMAIN_BLOCKED) {
atomic_set(&mdev->pasid_1024_fake_error, 0);
} else if (atomic_read(&mdev->pasid_1024_fake_error)) {
/*
* Clear the flag, and fake an error to fail the
* replacement.
*/
atomic_set(&mdev->pasid_1024_fake_error, 0);
return -ENOMEM;
} else {
/* Set the flag to fake an error in next call */
atomic_set(&mdev->pasid_1024_fake_error, 1);
}
}
return 0;
}
static const struct iommu_domain_ops mock_blocking_ops = {
.attach_dev = mock_domain_nop_attach,
.set_dev_pasid = mock_domain_set_dev_pasid_nop
};
static struct iommu_domain mock_blocking_domain = {
@@ -343,7 +403,7 @@ mock_domain_alloc_nested(struct device *dev, struct iommu_domain *parent,
struct mock_iommu_domain_nested *mock_nested;
struct mock_iommu_domain *mock_parent;
if (flags)
if (flags & ~IOMMU_HWPT_ALLOC_PASID)
return ERR_PTR(-EOPNOTSUPP);
if (!parent || parent->ops != mock_ops.default_domain_ops)
return ERR_PTR(-EINVAL);
@@ -365,7 +425,8 @@ mock_domain_alloc_paging_flags(struct device *dev, u32 flags,
{
bool has_dirty_flag = flags & IOMMU_HWPT_ALLOC_DIRTY_TRACKING;
const u32 PAGING_FLAGS = IOMMU_HWPT_ALLOC_DIRTY_TRACKING |
IOMMU_HWPT_ALLOC_NEST_PARENT;
IOMMU_HWPT_ALLOC_NEST_PARENT |
IOMMU_HWPT_ALLOC_PASID;
struct mock_dev *mdev = to_mock_dev(dev);
bool no_dirty_ops = mdev->flags & MOCK_FLAGS_DEVICE_NO_DIRTY;
struct mock_iommu_domain *mock;
@@ -585,7 +646,7 @@ mock_viommu_alloc_domain_nested(struct iommufd_viommu *viommu, u32 flags,
struct mock_viommu *mock_viommu = to_mock_viommu(viommu);
struct mock_iommu_domain_nested *mock_nested;
if (flags)
if (flags & ~IOMMU_HWPT_ALLOC_PASID)
return ERR_PTR(-EOPNOTSUPP);
mock_nested = __mock_domain_alloc_nested(user_data);
@@ -720,6 +781,7 @@ static const struct iommu_ops mock_ops = {
.map_pages = mock_domain_map_pages,
.unmap_pages = mock_domain_unmap_pages,
.iova_to_phys = mock_domain_iova_to_phys,
.set_dev_pasid = mock_domain_set_dev_pasid_nop,
},
};
@@ -780,6 +842,7 @@ 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,
.set_dev_pasid = mock_domain_set_dev_pasid_nop,
};
static inline struct iommufd_hw_pagetable *
@@ -839,17 +902,24 @@ static void mock_dev_release(struct device *dev)
static struct mock_dev *mock_dev_create(unsigned long dev_flags)
{
struct property_entry prop[] = {
PROPERTY_ENTRY_U32("pasid-num-bits", 0),
{},
};
const u32 valid_flags = MOCK_FLAGS_DEVICE_NO_DIRTY |
MOCK_FLAGS_DEVICE_HUGE_IOVA |
MOCK_FLAGS_DEVICE_PASID;
struct mock_dev *mdev;
int rc, i;
if (dev_flags &
~(MOCK_FLAGS_DEVICE_NO_DIRTY | MOCK_FLAGS_DEVICE_HUGE_IOVA))
if (dev_flags & ~valid_flags)
return ERR_PTR(-EINVAL);
mdev = kzalloc(sizeof(*mdev), GFP_KERNEL);
if (!mdev)
return ERR_PTR(-ENOMEM);
init_rwsem(&mdev->viommu_rwsem);
device_initialize(&mdev->dev);
mdev->flags = dev_flags;
mdev->dev.release = mock_dev_release;
@@ -866,6 +936,15 @@ static struct mock_dev *mock_dev_create(unsigned long dev_flags)
if (rc)
goto err_put;
if (dev_flags & MOCK_FLAGS_DEVICE_PASID)
prop[0] = PROPERTY_ENTRY_U32("pasid-num-bits", MOCK_PASID_WIDTH);
rc = device_create_managed_software_node(&mdev->dev, prop, NULL);
if (rc) {
dev_err(&mdev->dev, "add pasid-num-bits property failed, rc: %d", rc);
goto err_put;
}
rc = device_add(&mdev->dev);
if (rc)
goto err_put;
@@ -921,7 +1000,7 @@ static int iommufd_test_mock_domain(struct iommufd_ucmd *ucmd,
}
sobj->idev.idev = idev;
rc = iommufd_device_attach(idev, &pt_id);
rc = iommufd_device_attach(idev, IOMMU_NO_PASID, &pt_id);
if (rc)
goto out_unbind;
@@ -936,7 +1015,7 @@ static int iommufd_test_mock_domain(struct iommufd_ucmd *ucmd,
return 0;
out_detach:
iommufd_device_detach(idev);
iommufd_device_detach(idev, IOMMU_NO_PASID);
out_unbind:
iommufd_device_unbind(idev);
out_mdev:
@@ -946,39 +1025,49 @@ static int iommufd_test_mock_domain(struct iommufd_ucmd *ucmd,
return rc;
}
/* Replace the mock domain with a manually allocated hw_pagetable */
static int iommufd_test_mock_domain_replace(struct iommufd_ucmd *ucmd,
unsigned int device_id, u32 pt_id,
struct iommu_test_cmd *cmd)
static struct selftest_obj *
iommufd_test_get_selftest_obj(struct iommufd_ctx *ictx, u32 id)
{
struct iommufd_object *dev_obj;
struct selftest_obj *sobj;
int rc;
/*
* Prefer to use the OBJ_SELFTEST because the destroy_rwsem will ensure
* it doesn't race with detach, which is not allowed.
*/
dev_obj =
iommufd_get_object(ucmd->ictx, device_id, IOMMUFD_OBJ_SELFTEST);
dev_obj = iommufd_get_object(ictx, id, IOMMUFD_OBJ_SELFTEST);
if (IS_ERR(dev_obj))
return PTR_ERR(dev_obj);
return ERR_CAST(dev_obj);
sobj = to_selftest_obj(dev_obj);
if (sobj->type != TYPE_IDEV) {
rc = -EINVAL;
goto out_dev_obj;
iommufd_put_object(ictx, dev_obj);
return ERR_PTR(-EINVAL);
}
return sobj;
}
rc = iommufd_device_replace(sobj->idev.idev, &pt_id);
/* Replace the mock domain with a manually allocated hw_pagetable */
static int iommufd_test_mock_domain_replace(struct iommufd_ucmd *ucmd,
unsigned int device_id, u32 pt_id,
struct iommu_test_cmd *cmd)
{
struct selftest_obj *sobj;
int rc;
sobj = iommufd_test_get_selftest_obj(ucmd->ictx, device_id);
if (IS_ERR(sobj))
return PTR_ERR(sobj);
rc = iommufd_device_replace(sobj->idev.idev, IOMMU_NO_PASID, &pt_id);
if (rc)
goto out_dev_obj;
goto out_sobj;
cmd->mock_domain_replace.pt_id = pt_id;
rc = iommufd_ucmd_respond(ucmd, sizeof(*cmd));
out_dev_obj:
iommufd_put_object(ucmd->ictx, dev_obj);
out_sobj:
iommufd_put_object(ucmd->ictx, &sobj->obj);
return rc;
}
@@ -1597,13 +1686,166 @@ static int iommufd_test_trigger_iopf(struct iommufd_ucmd *ucmd,
return 0;
}
static int iommufd_test_trigger_vevent(struct iommufd_ucmd *ucmd,
struct iommu_test_cmd *cmd)
{
struct iommu_viommu_event_selftest test = {};
struct iommufd_device *idev;
struct mock_dev *mdev;
int rc = -ENOENT;
idev = iommufd_get_device(ucmd, cmd->trigger_vevent.dev_id);
if (IS_ERR(idev))
return PTR_ERR(idev);
mdev = to_mock_dev(idev->dev);
down_read(&mdev->viommu_rwsem);
if (!mdev->viommu || !mdev->vdev_id)
goto out_unlock;
test.virt_id = mdev->vdev_id;
rc = iommufd_viommu_report_event(&mdev->viommu->core,
IOMMU_VEVENTQ_TYPE_SELFTEST, &test,
sizeof(test));
out_unlock:
up_read(&mdev->viommu_rwsem);
iommufd_put_object(ucmd->ictx, &idev->obj);
return rc;
}
static inline struct iommufd_hw_pagetable *
iommufd_get_hwpt(struct iommufd_ucmd *ucmd, u32 id)
{
struct iommufd_object *pt_obj;
pt_obj = iommufd_get_object(ucmd->ictx, id, IOMMUFD_OBJ_ANY);
if (IS_ERR(pt_obj))
return ERR_CAST(pt_obj);
if (pt_obj->type != IOMMUFD_OBJ_HWPT_NESTED &&
pt_obj->type != IOMMUFD_OBJ_HWPT_PAGING) {
iommufd_put_object(ucmd->ictx, pt_obj);
return ERR_PTR(-EINVAL);
}
return container_of(pt_obj, struct iommufd_hw_pagetable, obj);
}
static int iommufd_test_pasid_check_hwpt(struct iommufd_ucmd *ucmd,
struct iommu_test_cmd *cmd)
{
u32 hwpt_id = cmd->pasid_check.hwpt_id;
struct iommu_domain *attached_domain;
struct iommu_attach_handle *handle;
struct iommufd_hw_pagetable *hwpt;
struct selftest_obj *sobj;
struct mock_dev *mdev;
int rc = 0;
sobj = iommufd_test_get_selftest_obj(ucmd->ictx, cmd->id);
if (IS_ERR(sobj))
return PTR_ERR(sobj);
mdev = sobj->idev.mock_dev;
handle = iommu_attach_handle_get(mdev->dev.iommu_group,
cmd->pasid_check.pasid, 0);
if (IS_ERR(handle))
attached_domain = NULL;
else
attached_domain = handle->domain;
/* hwpt_id == 0 means to check if pasid is detached */
if (!hwpt_id) {
if (attached_domain)
rc = -EINVAL;
goto out_sobj;
}
hwpt = iommufd_get_hwpt(ucmd, hwpt_id);
if (IS_ERR(hwpt)) {
rc = PTR_ERR(hwpt);
goto out_sobj;
}
if (attached_domain != hwpt->domain)
rc = -EINVAL;
iommufd_put_object(ucmd->ictx, &hwpt->obj);
out_sobj:
iommufd_put_object(ucmd->ictx, &sobj->obj);
return rc;
}
static int iommufd_test_pasid_attach(struct iommufd_ucmd *ucmd,
struct iommu_test_cmd *cmd)
{
struct selftest_obj *sobj;
int rc;
sobj = iommufd_test_get_selftest_obj(ucmd->ictx, cmd->id);
if (IS_ERR(sobj))
return PTR_ERR(sobj);
rc = iommufd_device_attach(sobj->idev.idev, cmd->pasid_attach.pasid,
&cmd->pasid_attach.pt_id);
if (rc)
goto out_sobj;
rc = iommufd_ucmd_respond(ucmd, sizeof(*cmd));
if (rc)
iommufd_device_detach(sobj->idev.idev,
cmd->pasid_attach.pasid);
out_sobj:
iommufd_put_object(ucmd->ictx, &sobj->obj);
return rc;
}
static int iommufd_test_pasid_replace(struct iommufd_ucmd *ucmd,
struct iommu_test_cmd *cmd)
{
struct selftest_obj *sobj;
int rc;
sobj = iommufd_test_get_selftest_obj(ucmd->ictx, cmd->id);
if (IS_ERR(sobj))
return PTR_ERR(sobj);
rc = iommufd_device_replace(sobj->idev.idev, cmd->pasid_attach.pasid,
&cmd->pasid_attach.pt_id);
if (rc)
goto out_sobj;
rc = iommufd_ucmd_respond(ucmd, sizeof(*cmd));
out_sobj:
iommufd_put_object(ucmd->ictx, &sobj->obj);
return rc;
}
static int iommufd_test_pasid_detach(struct iommufd_ucmd *ucmd,
struct iommu_test_cmd *cmd)
{
struct selftest_obj *sobj;
sobj = iommufd_test_get_selftest_obj(ucmd->ictx, cmd->id);
if (IS_ERR(sobj))
return PTR_ERR(sobj);
iommufd_device_detach(sobj->idev.idev, cmd->pasid_detach.pasid);
iommufd_put_object(ucmd->ictx, &sobj->obj);
return 0;
}
void iommufd_selftest_destroy(struct iommufd_object *obj)
{
struct selftest_obj *sobj = to_selftest_obj(obj);
switch (sobj->type) {
case TYPE_IDEV:
iommufd_device_detach(sobj->idev.idev);
iommufd_device_detach(sobj->idev.idev, IOMMU_NO_PASID);
iommufd_device_unbind(sobj->idev.idev);
mock_dev_destroy(sobj->idev.mock_dev);
break;
@@ -1678,6 +1920,16 @@ int iommufd_test(struct iommufd_ucmd *ucmd)
cmd->dirty.flags);
case IOMMU_TEST_OP_TRIGGER_IOPF:
return iommufd_test_trigger_iopf(ucmd, cmd);
case IOMMU_TEST_OP_TRIGGER_VEVENT:
return iommufd_test_trigger_vevent(ucmd, cmd);
case IOMMU_TEST_OP_PASID_ATTACH:
return iommufd_test_pasid_attach(ucmd, cmd);
case IOMMU_TEST_OP_PASID_REPLACE:
return iommufd_test_pasid_replace(ucmd, cmd);
case IOMMU_TEST_OP_PASID_DETACH:
return iommufd_test_pasid_detach(ucmd, cmd);
case IOMMU_TEST_OP_PASID_CHECK_HWPT:
return iommufd_test_pasid_check_hwpt(ucmd, cmd);
default:
return -EOPNOTSUPP;
}
@@ -1724,6 +1976,7 @@ int __init iommufd_test_init(void)
init_completion(&mock_iommu.complete);
mock_iommu_iopf_queue = iopf_queue_alloc("mock-iopfq");
mock_iommu.iommu_dev.max_pasids = (1 << MOCK_PASID_WIDTH);
return 0;

View File

@@ -59,6 +59,8 @@ int iommufd_viommu_alloc_ioctl(struct iommufd_ucmd *ucmd)
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

View File

@@ -538,4 +538,37 @@ int pci_max_pasids(struct pci_dev *pdev)
return (1 << FIELD_GET(PCI_PASID_CAP_WIDTH, supported));
}
EXPORT_SYMBOL_GPL(pci_max_pasids);
/**
* pci_pasid_status - Check the PASID status
* @pdev: PCI device structure
*
* Returns a negative value when no PASID capability is present.
* Otherwise the value of the control register is returned.
* Status reported are:
*
* PCI_PASID_CTRL_ENABLE - PASID enabled
* PCI_PASID_CTRL_EXEC - Execute permission enabled
* PCI_PASID_CTRL_PRIV - Privileged mode enabled
*/
int pci_pasid_status(struct pci_dev *pdev)
{
int pasid;
u16 ctrl;
if (pdev->is_virtfn)
pdev = pci_physfn(pdev);
pasid = pdev->pasid_cap;
if (!pasid)
return -EINVAL;
pci_read_config_word(pdev, pasid + PCI_PASID_CTRL, &ctrl);
ctrl &= PCI_PASID_CTRL_ENABLE | PCI_PASID_CTRL_EXEC |
PCI_PASID_CTRL_PRIV;
return ctrl;
}
EXPORT_SYMBOL_GPL(pci_pasid_status);
#endif /* CONFIG_PCI_PASID */

View File

@@ -162,9 +162,9 @@ void vfio_df_unbind_iommufd(struct vfio_device_file *df)
int vfio_df_ioctl_attach_pt(struct vfio_device_file *df,
struct vfio_device_attach_iommufd_pt __user *arg)
{
struct vfio_device *device = df->device;
struct vfio_device_attach_iommufd_pt attach;
unsigned long minsz;
struct vfio_device *device = df->device;
unsigned long minsz, xend = 0;
int ret;
minsz = offsetofend(struct vfio_device_attach_iommufd_pt, pt_id);
@@ -172,11 +172,34 @@ int vfio_df_ioctl_attach_pt(struct vfio_device_file *df,
if (copy_from_user(&attach, arg, minsz))
return -EFAULT;
if (attach.argsz < minsz || attach.flags)
if (attach.argsz < minsz)
return -EINVAL;
if (attach.flags & ~VFIO_DEVICE_ATTACH_PASID)
return -EINVAL;
if (attach.flags & VFIO_DEVICE_ATTACH_PASID) {
if (!device->ops->pasid_attach_ioas)
return -EOPNOTSUPP;
xend = offsetofend(struct vfio_device_attach_iommufd_pt, pasid);
}
if (xend) {
if (attach.argsz < xend)
return -EINVAL;
if (copy_from_user((void *)&attach + minsz,
(void __user *)arg + minsz, xend - minsz))
return -EFAULT;
}
mutex_lock(&device->dev_set->lock);
ret = device->ops->attach_ioas(device, &attach.pt_id);
if (attach.flags & VFIO_DEVICE_ATTACH_PASID)
ret = device->ops->pasid_attach_ioas(device,
attach.pasid,
&attach.pt_id);
else
ret = device->ops->attach_ioas(device, &attach.pt_id);
if (ret)
goto out_unlock;
@@ -198,20 +221,41 @@ int vfio_df_ioctl_attach_pt(struct vfio_device_file *df,
int vfio_df_ioctl_detach_pt(struct vfio_device_file *df,
struct vfio_device_detach_iommufd_pt __user *arg)
{
struct vfio_device *device = df->device;
struct vfio_device_detach_iommufd_pt detach;
unsigned long minsz;
struct vfio_device *device = df->device;
unsigned long minsz, xend = 0;
minsz = offsetofend(struct vfio_device_detach_iommufd_pt, flags);
if (copy_from_user(&detach, arg, minsz))
return -EFAULT;
if (detach.argsz < minsz || detach.flags)
if (detach.argsz < minsz)
return -EINVAL;
if (detach.flags & ~VFIO_DEVICE_DETACH_PASID)
return -EINVAL;
if (detach.flags & VFIO_DEVICE_DETACH_PASID) {
if (!device->ops->pasid_detach_ioas)
return -EOPNOTSUPP;
xend = offsetofend(struct vfio_device_detach_iommufd_pt, pasid);
}
if (xend) {
if (detach.argsz < xend)
return -EINVAL;
if (copy_from_user((void *)&detach + minsz,
(void __user *)arg + minsz, xend - minsz))
return -EFAULT;
}
mutex_lock(&device->dev_set->lock);
device->ops->detach_ioas(device);
if (detach.flags & VFIO_DEVICE_DETACH_PASID)
device->ops->pasid_detach_ioas(device, detach.pasid);
else
device->ops->detach_ioas(device);
mutex_unlock(&device->dev_set->lock);
return 0;

View File

@@ -119,16 +119,24 @@ int vfio_iommufd_physical_bind(struct vfio_device *vdev,
if (IS_ERR(idev))
return PTR_ERR(idev);
vdev->iommufd_device = idev;
ida_init(&vdev->pasids);
return 0;
}
EXPORT_SYMBOL_GPL(vfio_iommufd_physical_bind);
void vfio_iommufd_physical_unbind(struct vfio_device *vdev)
{
int pasid;
lockdep_assert_held(&vdev->dev_set->lock);
while ((pasid = ida_find_first(&vdev->pasids)) >= 0) {
iommufd_device_detach(vdev->iommufd_device, pasid);
ida_free(&vdev->pasids, pasid);
}
if (vdev->iommufd_attached) {
iommufd_device_detach(vdev->iommufd_device);
iommufd_device_detach(vdev->iommufd_device, IOMMU_NO_PASID);
vdev->iommufd_attached = false;
}
iommufd_device_unbind(vdev->iommufd_device);
@@ -146,9 +154,11 @@ int vfio_iommufd_physical_attach_ioas(struct vfio_device *vdev, u32 *pt_id)
return -EINVAL;
if (vdev->iommufd_attached)
rc = iommufd_device_replace(vdev->iommufd_device, pt_id);
rc = iommufd_device_replace(vdev->iommufd_device,
IOMMU_NO_PASID, pt_id);
else
rc = iommufd_device_attach(vdev->iommufd_device, pt_id);
rc = iommufd_device_attach(vdev->iommufd_device,
IOMMU_NO_PASID, pt_id);
if (rc)
return rc;
vdev->iommufd_attached = true;
@@ -163,11 +173,53 @@ void vfio_iommufd_physical_detach_ioas(struct vfio_device *vdev)
if (WARN_ON(!vdev->iommufd_device) || !vdev->iommufd_attached)
return;
iommufd_device_detach(vdev->iommufd_device);
iommufd_device_detach(vdev->iommufd_device, IOMMU_NO_PASID);
vdev->iommufd_attached = false;
}
EXPORT_SYMBOL_GPL(vfio_iommufd_physical_detach_ioas);
int vfio_iommufd_physical_pasid_attach_ioas(struct vfio_device *vdev,
u32 pasid, u32 *pt_id)
{
int rc;
lockdep_assert_held(&vdev->dev_set->lock);
if (WARN_ON(!vdev->iommufd_device))
return -EINVAL;
if (ida_exists(&vdev->pasids, pasid))
return iommufd_device_replace(vdev->iommufd_device,
pasid, pt_id);
rc = ida_alloc_range(&vdev->pasids, pasid, pasid, GFP_KERNEL);
if (rc < 0)
return rc;
rc = iommufd_device_attach(vdev->iommufd_device, pasid, pt_id);
if (rc)
ida_free(&vdev->pasids, pasid);
return rc;
}
EXPORT_SYMBOL_GPL(vfio_iommufd_physical_pasid_attach_ioas);
void vfio_iommufd_physical_pasid_detach_ioas(struct vfio_device *vdev,
u32 pasid)
{
lockdep_assert_held(&vdev->dev_set->lock);
if (WARN_ON(!vdev->iommufd_device))
return;
if (!ida_exists(&vdev->pasids, pasid))
return;
iommufd_device_detach(vdev->iommufd_device, pasid);
ida_free(&vdev->pasids, pasid);
}
EXPORT_SYMBOL_GPL(vfio_iommufd_physical_pasid_detach_ioas);
/*
* The emulated standard ops mean that vfio_device is going to use the
* "mdev path" and will call vfio_pin_pages()/vfio_dma_rw(). Drivers using this

View File

@@ -144,6 +144,8 @@ static const struct vfio_device_ops vfio_pci_ops = {
.unbind_iommufd = vfio_iommufd_physical_unbind,
.attach_ioas = vfio_iommufd_physical_attach_ioas,
.detach_ioas = vfio_iommufd_physical_detach_ioas,
.pasid_attach_ioas = vfio_iommufd_physical_pasid_attach_ioas,
.pasid_detach_ioas = vfio_iommufd_physical_pasid_detach_ioas,
};
static int vfio_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id)

View File

@@ -274,6 +274,7 @@ struct ida {
int ida_alloc_range(struct ida *, unsigned int min, unsigned int max, gfp_t);
void ida_free(struct ida *, unsigned int id);
void ida_destroy(struct ida *ida);
int ida_find_first_range(struct ida *ida, unsigned int min, unsigned int max);
/**
* ida_alloc() - Allocate an unused ID.
@@ -345,4 +346,14 @@ static inline bool ida_is_empty(const struct ida *ida)
{
return xa_empty(&ida->xa);
}
static inline bool ida_exists(struct ida *ida, unsigned int id)
{
return ida_find_first_range(ida, id, id) == id;
}
static inline int ida_find_first(struct ida *ida)
{
return ida_find_first_range(ida, 0, ~0);
}
#endif /* __IDR_H__ */

View File

@@ -41,6 +41,7 @@ struct iommu_dirty_ops;
struct notifier_block;
struct iommu_sva;
struct iommu_dma_cookie;
struct iommu_dma_msi_cookie;
struct iommu_fault_param;
struct iommufd_ctx;
struct iommufd_viommu;
@@ -165,6 +166,15 @@ struct iommu_domain_geometry {
bool force_aperture; /* DMA only allowed in mappable range? */
};
enum iommu_domain_cookie_type {
IOMMU_COOKIE_NONE,
IOMMU_COOKIE_DMA_IOVA,
IOMMU_COOKIE_DMA_MSI,
IOMMU_COOKIE_FAULT_HANDLER,
IOMMU_COOKIE_SVA,
IOMMU_COOKIE_IOMMUFD,
};
/* Domain feature flags */
#define __IOMMU_DOMAIN_PAGING (1U << 0) /* Support for iommu_map/unmap */
#define __IOMMU_DOMAIN_DMA_API (1U << 1) /* Domain for use in DMA-API
@@ -211,23 +221,18 @@ struct iommu_domain_geometry {
struct iommu_domain {
unsigned type;
enum iommu_domain_cookie_type cookie_type;
const struct iommu_domain_ops *ops;
const struct iommu_dirty_ops *dirty_ops;
const struct iommu_ops *owner; /* Whose domain_alloc we came from */
unsigned long pgsize_bitmap; /* Bitmap of page sizes in use */
struct iommu_domain_geometry geometry;
struct iommu_dma_cookie *iova_cookie;
int (*iopf_handler)(struct iopf_group *group);
#if IS_ENABLED(CONFIG_IRQ_MSI_IOMMU)
int (*sw_msi)(struct iommu_domain *domain, struct msi_desc *desc,
phys_addr_t msi_addr);
#endif
union { /* Pointer usable by owner of the domain */
struct iommufd_hw_pagetable *iommufd_hwpt; /* iommufd */
};
union { /* Fault handler */
union { /* cookie */
struct iommu_dma_cookie *iova_cookie;
struct iommu_dma_msi_cookie *msi_cookie;
struct iommufd_hw_pagetable *iommufd_hwpt;
struct {
iommu_fault_handler_t handler;
void *handler_token;
@@ -244,16 +249,6 @@ struct iommu_domain {
};
};
static inline void iommu_domain_set_sw_msi(
struct iommu_domain *domain,
int (*sw_msi)(struct iommu_domain *domain, struct msi_desc *desc,
phys_addr_t msi_addr))
{
#if IS_ENABLED(CONFIG_IRQ_MSI_IOMMU)
domain->sw_msi = sw_msi;
#endif
}
static inline bool iommu_is_dma_domain(struct iommu_domain *domain)
{
return domain->type & __IOMMU_DOMAIN_DMA_API;

View File

@@ -8,9 +8,11 @@
#include <linux/err.h>
#include <linux/errno.h>
#include <linux/iommu.h>
#include <linux/refcount.h>
#include <linux/types.h>
#include <linux/xarray.h>
#include <uapi/linux/iommufd.h>
struct device;
struct file;
@@ -34,6 +36,7 @@ enum iommufd_object_type {
IOMMUFD_OBJ_FAULT,
IOMMUFD_OBJ_VIOMMU,
IOMMUFD_OBJ_VDEVICE,
IOMMUFD_OBJ_VEVENTQ,
#ifdef CONFIG_IOMMUFD_TEST
IOMMUFD_OBJ_SELFTEST,
#endif
@@ -52,9 +55,11 @@ struct iommufd_device *iommufd_device_bind(struct iommufd_ctx *ictx,
struct device *dev, u32 *id);
void iommufd_device_unbind(struct iommufd_device *idev);
int iommufd_device_attach(struct iommufd_device *idev, u32 *pt_id);
int iommufd_device_replace(struct iommufd_device *idev, u32 *pt_id);
void iommufd_device_detach(struct iommufd_device *idev);
int iommufd_device_attach(struct iommufd_device *idev, ioasid_t pasid,
u32 *pt_id);
int iommufd_device_replace(struct iommufd_device *idev, ioasid_t pasid,
u32 *pt_id);
void iommufd_device_detach(struct iommufd_device *idev, ioasid_t pasid);
struct iommufd_ctx *iommufd_device_to_ictx(struct iommufd_device *idev);
u32 iommufd_device_to_id(struct iommufd_device *idev);
@@ -93,6 +98,8 @@ struct iommufd_viommu {
const struct iommufd_viommu_ops *ops;
struct xarray vdevs;
struct list_head veventqs;
struct rw_semaphore veventqs_rwsem;
unsigned int type;
};
@@ -187,6 +194,11 @@ struct iommufd_object *_iommufd_object_alloc(struct iommufd_ctx *ictx,
enum iommufd_object_type type);
struct device *iommufd_viommu_find_dev(struct iommufd_viommu *viommu,
unsigned long vdev_id);
int iommufd_viommu_get_vdev_id(struct iommufd_viommu *viommu,
struct device *dev, unsigned long *vdev_id);
int iommufd_viommu_report_event(struct iommufd_viommu *viommu,
enum iommu_veventq_type type, void *event_data,
size_t data_len);
#else /* !CONFIG_IOMMUFD_DRIVER_CORE */
static inline struct iommufd_object *
_iommufd_object_alloc(struct iommufd_ctx *ictx, size_t size,
@@ -200,6 +212,20 @@ iommufd_viommu_find_dev(struct iommufd_viommu *viommu, unsigned long vdev_id)
{
return NULL;
}
static inline int iommufd_viommu_get_vdev_id(struct iommufd_viommu *viommu,
struct device *dev,
unsigned long *vdev_id)
{
return -ENOENT;
}
static inline int iommufd_viommu_report_event(struct iommufd_viommu *viommu,
enum iommu_veventq_type type,
void *event_data, size_t data_len)
{
return -EOPNOTSUPP;
}
#endif /* CONFIG_IOMMUFD_DRIVER_CORE */
/*

View File

@@ -42,6 +42,7 @@ int pci_enable_pasid(struct pci_dev *pdev, int features);
void pci_disable_pasid(struct pci_dev *pdev);
int pci_pasid_features(struct pci_dev *pdev);
int pci_max_pasids(struct pci_dev *pdev);
int pci_pasid_status(struct pci_dev *pdev);
#else /* CONFIG_PCI_PASID */
static inline int pci_enable_pasid(struct pci_dev *pdev, int features)
{ return -EINVAL; }
@@ -50,6 +51,8 @@ static inline int pci_pasid_features(struct pci_dev *pdev)
{ return -EINVAL; }
static inline int pci_max_pasids(struct pci_dev *pdev)
{ return -EINVAL; }
static inline int pci_pasid_status(struct pci_dev *pdev)
{ return -EINVAL; }
#endif /* CONFIG_PCI_PASID */
#endif /* LINUX_PCI_ATS_H */

View File

@@ -67,6 +67,7 @@ struct vfio_device {
struct inode *inode;
#if IS_ENABLED(CONFIG_IOMMUFD)
struct iommufd_device *iommufd_device;
struct ida pasids;
u8 iommufd_attached:1;
#endif
u8 cdev_opened:1;
@@ -91,6 +92,8 @@ struct vfio_device {
* bound iommufd. Undo in unbind_iommufd if @detach_ioas is not
* called.
* @detach_ioas: Opposite of attach_ioas
* @pasid_attach_ioas: The pasid variation of attach_ioas
* @pasid_detach_ioas: Opposite of pasid_attach_ioas
* @open_device: Called when the first file descriptor is opened for this device
* @close_device: Opposite of open_device
* @read: Perform read(2) on device file descriptor
@@ -115,6 +118,9 @@ struct vfio_device_ops {
void (*unbind_iommufd)(struct vfio_device *vdev);
int (*attach_ioas)(struct vfio_device *vdev, u32 *pt_id);
void (*detach_ioas)(struct vfio_device *vdev);
int (*pasid_attach_ioas)(struct vfio_device *vdev, u32 pasid,
u32 *pt_id);
void (*pasid_detach_ioas)(struct vfio_device *vdev, u32 pasid);
int (*open_device)(struct vfio_device *vdev);
void (*close_device)(struct vfio_device *vdev);
ssize_t (*read)(struct vfio_device *vdev, char __user *buf,
@@ -139,6 +145,10 @@ int vfio_iommufd_physical_bind(struct vfio_device *vdev,
void vfio_iommufd_physical_unbind(struct vfio_device *vdev);
int vfio_iommufd_physical_attach_ioas(struct vfio_device *vdev, u32 *pt_id);
void vfio_iommufd_physical_detach_ioas(struct vfio_device *vdev);
int vfio_iommufd_physical_pasid_attach_ioas(struct vfio_device *vdev,
u32 pasid, u32 *pt_id);
void vfio_iommufd_physical_pasid_detach_ioas(struct vfio_device *vdev,
u32 pasid);
int vfio_iommufd_emulated_bind(struct vfio_device *vdev,
struct iommufd_ctx *ictx, u32 *out_device_id);
void vfio_iommufd_emulated_unbind(struct vfio_device *vdev);
@@ -166,6 +176,10 @@ vfio_iommufd_get_dev_id(struct vfio_device *vdev, struct iommufd_ctx *ictx)
((int (*)(struct vfio_device *vdev, u32 *pt_id)) NULL)
#define vfio_iommufd_physical_detach_ioas \
((void (*)(struct vfio_device *vdev)) NULL)
#define vfio_iommufd_physical_pasid_attach_ioas \
((int (*)(struct vfio_device *vdev, u32 pasid, u32 *pt_id)) NULL)
#define vfio_iommufd_physical_pasid_detach_ioas \
((void (*)(struct vfio_device *vdev, u32 pasid)) NULL)
#define vfio_iommufd_emulated_bind \
((int (*)(struct vfio_device *vdev, struct iommufd_ctx *ictx, \
u32 *out_device_id)) NULL)

View File

@@ -55,6 +55,7 @@ enum {
IOMMUFD_CMD_VIOMMU_ALLOC = 0x90,
IOMMUFD_CMD_VDEVICE_ALLOC = 0x91,
IOMMUFD_CMD_IOAS_CHANGE_PROCESS = 0x92,
IOMMUFD_CMD_VEVENTQ_ALLOC = 0x93,
};
/**
@@ -392,6 +393,9 @@ struct iommu_vfio_ioas {
* Any domain attached to the non-PASID part of the
* device must also be flagged, otherwise attaching a
* PASID will blocked.
* For the user that wants to attach PASID, ioas is
* not recommended for both the non-PASID part
* and PASID part of the device.
* If IOMMU does not support PASID it will return
* error (-EOPNOTSUPP).
*/
@@ -608,9 +612,17 @@ enum iommu_hw_info_type {
* IOMMU_HWPT_GET_DIRTY_BITMAP
* IOMMU_HWPT_SET_DIRTY_TRACKING
*
* @IOMMU_HW_CAP_PCI_PASID_EXEC: Execute Permission Supported, user ignores it
* when the struct
* iommu_hw_info::out_max_pasid_log2 is zero.
* @IOMMU_HW_CAP_PCI_PASID_PRIV: Privileged Mode Supported, user ignores it
* when the struct
* iommu_hw_info::out_max_pasid_log2 is zero.
*/
enum iommufd_hw_capabilities {
IOMMU_HW_CAP_DIRTY_TRACKING = 1 << 0,
IOMMU_HW_CAP_PCI_PASID_EXEC = 1 << 1,
IOMMU_HW_CAP_PCI_PASID_PRIV = 1 << 2,
};
/**
@@ -626,6 +638,9 @@ enum iommufd_hw_capabilities {
* iommu_hw_info_type.
* @out_capabilities: Output the generic iommu capability info type as defined
* in the enum iommu_hw_capabilities.
* @out_max_pasid_log2: Output the width of PASIDs. 0 means no PASID support.
* PCI devices turn to out_capabilities to check if the
* specific capabilities is supported or not.
* @__reserved: Must be 0
*
* Query an iommu type specific hardware information data from an iommu behind
@@ -649,7 +664,8 @@ struct iommu_hw_info {
__u32 data_len;
__aligned_u64 data_uptr;
__u32 out_data_type;
__u32 __reserved;
__u8 out_max_pasid_log2;
__u8 __reserved[3];
__aligned_u64 out_capabilities;
};
#define IOMMU_GET_HW_INFO _IO(IOMMUFD_TYPE, IOMMUFD_CMD_GET_HW_INFO)
@@ -1014,4 +1030,115 @@ struct iommu_ioas_change_process {
#define IOMMU_IOAS_CHANGE_PROCESS \
_IO(IOMMUFD_TYPE, IOMMUFD_CMD_IOAS_CHANGE_PROCESS)
/**
* enum iommu_veventq_flag - flag for struct iommufd_vevent_header
* @IOMMU_VEVENTQ_FLAG_LOST_EVENTS: vEVENTQ has lost vEVENTs
*/
enum iommu_veventq_flag {
IOMMU_VEVENTQ_FLAG_LOST_EVENTS = (1U << 0),
};
/**
* struct iommufd_vevent_header - Virtual Event Header for a vEVENTQ Status
* @flags: Combination of enum iommu_veventq_flag
* @sequence: The sequence index of a vEVENT in the vEVENTQ, with a range of
* [0, INT_MAX] where the following index of INT_MAX is 0
*
* Each iommufd_vevent_header reports a sequence index of the following vEVENT:
*
* +----------------------+-------+----------------------+-------+---+-------+
* | header0 {sequence=0} | data0 | header1 {sequence=1} | data1 |...| dataN |
* +----------------------+-------+----------------------+-------+---+-------+
*
* And this sequence index is expected to be monotonic to the sequence index of
* the previous vEVENT. If two adjacent sequence indexes has a delta larger than
* 1, it means that delta - 1 number of vEVENTs has lost, e.g. two lost vEVENTs:
*
* +-----+----------------------+-------+----------------------+-------+-----+
* | ... | header3 {sequence=3} | data3 | header6 {sequence=6} | data6 | ... |
* +-----+----------------------+-------+----------------------+-------+-----+
*
* If a vEVENT lost at the tail of the vEVENTQ and there is no following vEVENT
* providing the next sequence index, an IOMMU_VEVENTQ_FLAG_LOST_EVENTS header
* would be added to the tail, and no data would follow this header:
*
* +--+----------------------+-------+-----------------------------------------+
* |..| header3 {sequence=3} | data3 | header4 {flags=LOST_EVENTS, sequence=4} |
* +--+----------------------+-------+-----------------------------------------+
*/
struct iommufd_vevent_header {
__u32 flags;
__u32 sequence;
};
/**
* enum iommu_veventq_type - Virtual Event Queue Type
* @IOMMU_VEVENTQ_TYPE_DEFAULT: Reserved for future use
* @IOMMU_VEVENTQ_TYPE_ARM_SMMUV3: ARM SMMUv3 Virtual Event Queue
*/
enum iommu_veventq_type {
IOMMU_VEVENTQ_TYPE_DEFAULT = 0,
IOMMU_VEVENTQ_TYPE_ARM_SMMUV3 = 1,
};
/**
* struct iommu_vevent_arm_smmuv3 - ARM SMMUv3 Virtual Event
* (IOMMU_VEVENTQ_TYPE_ARM_SMMUV3)
* @evt: 256-bit ARM SMMUv3 Event record, little-endian.
* Reported event records: (Refer to "7.3 Event records" in SMMUv3 HW Spec)
* - 0x04 C_BAD_STE
* - 0x06 F_STREAM_DISABLED
* - 0x08 C_BAD_SUBSTREAMID
* - 0x0a C_BAD_CD
* - 0x10 F_TRANSLATION
* - 0x11 F_ADDR_SIZE
* - 0x12 F_ACCESS
* - 0x13 F_PERMISSION
*
* StreamID field reports a virtual device ID. To receive a virtual event for a
* device, a vDEVICE must be allocated via IOMMU_VDEVICE_ALLOC.
*/
struct iommu_vevent_arm_smmuv3 {
__aligned_le64 evt[4];
};
/**
* struct iommu_veventq_alloc - ioctl(IOMMU_VEVENTQ_ALLOC)
* @size: sizeof(struct iommu_veventq_alloc)
* @flags: Must be 0
* @viommu_id: virtual IOMMU ID to associate the vEVENTQ with
* @type: Type of the vEVENTQ. Must be defined in enum iommu_veventq_type
* @veventq_depth: Maximum number of events in the vEVENTQ
* @out_veventq_id: The ID of the new vEVENTQ
* @out_veventq_fd: The fd of the new vEVENTQ. User space must close the
* successfully returned fd after using it
* @__reserved: Must be 0
*
* Explicitly allocate a virtual event queue interface for a vIOMMU. A vIOMMU
* can have multiple FDs for different types, but is confined to one per @type.
* User space should open the @out_veventq_fd to read vEVENTs out of a vEVENTQ,
* if there are vEVENTs available. A vEVENTQ will lose events due to overflow,
* if the number of the vEVENTs hits @veventq_depth.
*
* Each vEVENT in a vEVENTQ encloses a struct iommufd_vevent_header followed by
* a type-specific data structure, in a normal case:
*
* +-+---------+-------+---------+-------+-----+---------+-------+-+
* | | header0 | data0 | header1 | data1 | ... | headerN | dataN | |
* +-+---------+-------+---------+-------+-----+---------+-------+-+
*
* unless a tailing IOMMU_VEVENTQ_FLAG_LOST_EVENTS header is logged (refer to
* struct iommufd_vevent_header).
*/
struct iommu_veventq_alloc {
__u32 size;
__u32 flags;
__u32 viommu_id;
__u32 type;
__u32 veventq_depth;
__u32 out_veventq_id;
__u32 out_veventq_fd;
__u32 __reserved;
};
#define IOMMU_VEVENTQ_ALLOC _IO(IOMMUFD_TYPE, IOMMUFD_CMD_VEVENTQ_ALLOC)
#endif

View File

@@ -932,29 +932,34 @@ struct vfio_device_bind_iommufd {
* VFIO_DEVICE_ATTACH_IOMMUFD_PT - _IOW(VFIO_TYPE, VFIO_BASE + 19,
* struct vfio_device_attach_iommufd_pt)
* @argsz: User filled size of this data.
* @flags: Must be 0.
* @flags: Flags for attach.
* @pt_id: Input the target id which can represent an ioas or a hwpt
* allocated via iommufd subsystem.
* Output the input ioas id or the attached hwpt id which could
* be the specified hwpt itself or a hwpt automatically created
* for the specified ioas by kernel during the attachment.
* @pasid: The pasid to be attached, only meaningful when
* VFIO_DEVICE_ATTACH_PASID is set in @flags
*
* Associate the device with an address space within the bound iommufd.
* Undo by VFIO_DEVICE_DETACH_IOMMUFD_PT or device fd close. This is only
* allowed on cdev fds.
*
* If a vfio device is currently attached to a valid hw_pagetable, without doing
* a VFIO_DEVICE_DETACH_IOMMUFD_PT, a second VFIO_DEVICE_ATTACH_IOMMUFD_PT ioctl
* passing in another hw_pagetable (hwpt) id is allowed. This action, also known
* as a hw_pagetable replacement, will replace the device's currently attached
* hw_pagetable with a new hw_pagetable corresponding to the given pt_id.
* If a vfio device or a pasid of this device is currently attached to a valid
* hw_pagetable (hwpt), without doing a VFIO_DEVICE_DETACH_IOMMUFD_PT, a second
* VFIO_DEVICE_ATTACH_IOMMUFD_PT ioctl passing in another hwpt id is allowed.
* This action, also known as a hw_pagetable replacement, will replace the
* currently attached hwpt of the device or the pasid of this device with a new
* hwpt corresponding to the given pt_id.
*
* Return: 0 on success, -errno on failure.
*/
struct vfio_device_attach_iommufd_pt {
__u32 argsz;
__u32 flags;
#define VFIO_DEVICE_ATTACH_PASID (1 << 0)
__u32 pt_id;
__u32 pasid;
};
#define VFIO_DEVICE_ATTACH_IOMMUFD_PT _IO(VFIO_TYPE, VFIO_BASE + 19)
@@ -963,17 +968,21 @@ struct vfio_device_attach_iommufd_pt {
* VFIO_DEVICE_DETACH_IOMMUFD_PT - _IOW(VFIO_TYPE, VFIO_BASE + 20,
* struct vfio_device_detach_iommufd_pt)
* @argsz: User filled size of this data.
* @flags: Must be 0.
* @flags: Flags for detach.
* @pasid: The pasid to be detached, only meaningful when
* VFIO_DEVICE_DETACH_PASID is set in @flags
*
* Remove the association of the device and its current associated address
* space. After it, the device should be in a blocking DMA state. This is only
* allowed on cdev fds.
* Remove the association of the device or a pasid of the device and its current
* associated address space. After it, the device or the pasid should be in a
* blocking DMA state. This is only allowed on cdev fds.
*
* Return: 0 on success, -errno on failure.
*/
struct vfio_device_detach_iommufd_pt {
__u32 argsz;
__u32 flags;
#define VFIO_DEVICE_DETACH_PASID (1 << 0)
__u32 pasid;
};
#define VFIO_DEVICE_DETACH_IOMMUFD_PT _IO(VFIO_TYPE, VFIO_BASE + 20)

View File

@@ -476,6 +476,73 @@ int ida_alloc_range(struct ida *ida, unsigned int min, unsigned int max,
}
EXPORT_SYMBOL(ida_alloc_range);
/**
* ida_find_first_range - Get the lowest used ID.
* @ida: IDA handle.
* @min: Lowest ID to get.
* @max: Highest ID to get.
*
* Get the lowest used ID between @min and @max, inclusive. The returned
* ID will not exceed %INT_MAX, even if @max is larger.
*
* Context: Any context. Takes and releases the xa_lock.
* Return: The lowest used ID, or errno if no used ID is found.
*/
int ida_find_first_range(struct ida *ida, unsigned int min, unsigned int max)
{
unsigned long index = min / IDA_BITMAP_BITS;
unsigned int offset = min % IDA_BITMAP_BITS;
unsigned long *addr, size, bit;
unsigned long tmp = 0;
unsigned long flags;
void *entry;
int ret;
if ((int)min < 0)
return -EINVAL;
if ((int)max < 0)
max = INT_MAX;
xa_lock_irqsave(&ida->xa, flags);
entry = xa_find(&ida->xa, &index, max / IDA_BITMAP_BITS, XA_PRESENT);
if (!entry) {
ret = -ENOENT;
goto err_unlock;
}
if (index > min / IDA_BITMAP_BITS)
offset = 0;
if (index * IDA_BITMAP_BITS + offset > max) {
ret = -ENOENT;
goto err_unlock;
}
if (xa_is_value(entry)) {
tmp = xa_to_value(entry);
addr = &tmp;
size = BITS_PER_XA_VALUE;
} else {
addr = ((struct ida_bitmap *)entry)->bitmap;
size = IDA_BITMAP_BITS;
}
bit = find_next_bit(addr, size, offset);
xa_unlock_irqrestore(&ida->xa, flags);
if (bit == size ||
index * IDA_BITMAP_BITS + bit > max)
return -ENOENT;
return index * IDA_BITMAP_BITS + bit;
err_unlock:
xa_unlock_irqrestore(&ida->xa, flags);
return ret;
}
EXPORT_SYMBOL(ida_find_first_range);
/**
* ida_free() - Release an allocated ID.
* @ida: IDA handle.

View File

@@ -189,6 +189,75 @@ static void ida_check_bad_free(struct ida *ida)
IDA_BUG_ON(ida, !ida_is_empty(ida));
}
/*
* Check ida_find_first_range() and varriants.
*/
static void ida_check_find_first(struct ida *ida)
{
/* IDA is empty; all of the below should be not exist */
IDA_BUG_ON(ida, ida_exists(ida, 0));
IDA_BUG_ON(ida, ida_exists(ida, 3));
IDA_BUG_ON(ida, ida_exists(ida, 63));
IDA_BUG_ON(ida, ida_exists(ida, 1023));
IDA_BUG_ON(ida, ida_exists(ida, (1 << 20) - 1));
/* IDA contains a single value entry */
IDA_BUG_ON(ida, ida_alloc_min(ida, 3, GFP_KERNEL) != 3);
IDA_BUG_ON(ida, ida_exists(ida, 0));
IDA_BUG_ON(ida, !ida_exists(ida, 3));
IDA_BUG_ON(ida, ida_exists(ida, 63));
IDA_BUG_ON(ida, ida_exists(ida, 1023));
IDA_BUG_ON(ida, ida_exists(ida, (1 << 20) - 1));
IDA_BUG_ON(ida, ida_alloc_min(ida, 63, GFP_KERNEL) != 63);
IDA_BUG_ON(ida, ida_exists(ida, 0));
IDA_BUG_ON(ida, !ida_exists(ida, 3));
IDA_BUG_ON(ida, !ida_exists(ida, 63));
IDA_BUG_ON(ida, ida_exists(ida, 1023));
IDA_BUG_ON(ida, ida_exists(ida, (1 << 20) - 1));
/* IDA contains a single bitmap */
IDA_BUG_ON(ida, ida_alloc_min(ida, 1023, GFP_KERNEL) != 1023);
IDA_BUG_ON(ida, ida_exists(ida, 0));
IDA_BUG_ON(ida, !ida_exists(ida, 3));
IDA_BUG_ON(ida, !ida_exists(ida, 63));
IDA_BUG_ON(ida, !ida_exists(ida, 1023));
IDA_BUG_ON(ida, ida_exists(ida, (1 << 20) - 1));
/* IDA contains a tree */
IDA_BUG_ON(ida, ida_alloc_min(ida, (1 << 20) - 1, GFP_KERNEL) != (1 << 20) - 1);
IDA_BUG_ON(ida, ida_exists(ida, 0));
IDA_BUG_ON(ida, !ida_exists(ida, 3));
IDA_BUG_ON(ida, !ida_exists(ida, 63));
IDA_BUG_ON(ida, !ida_exists(ida, 1023));
IDA_BUG_ON(ida, !ida_exists(ida, (1 << 20) - 1));
/* Now try to find first */
IDA_BUG_ON(ida, ida_find_first(ida) != 3);
IDA_BUG_ON(ida, ida_find_first_range(ida, -1, 2) != -EINVAL);
IDA_BUG_ON(ida, ida_find_first_range(ida, 0, 2) != -ENOENT); // no used ID
IDA_BUG_ON(ida, ida_find_first_range(ida, 0, 3) != 3);
IDA_BUG_ON(ida, ida_find_first_range(ida, 1, 3) != 3);
IDA_BUG_ON(ida, ida_find_first_range(ida, 3, 3) != 3);
IDA_BUG_ON(ida, ida_find_first_range(ida, 2, 4) != 3);
IDA_BUG_ON(ida, ida_find_first_range(ida, 4, 3) != -ENOENT); // min > max, fail
IDA_BUG_ON(ida, ida_find_first_range(ida, 4, 60) != -ENOENT); // no used ID
IDA_BUG_ON(ida, ida_find_first_range(ida, 4, 64) != 63);
IDA_BUG_ON(ida, ida_find_first_range(ida, 63, 63) != 63);
IDA_BUG_ON(ida, ida_find_first_range(ida, 64, 1026) != 1023);
IDA_BUG_ON(ida, ida_find_first_range(ida, 1023, 1023) != 1023);
IDA_BUG_ON(ida, ida_find_first_range(ida, 1023, (1 << 20) - 1) != 1023);
IDA_BUG_ON(ida, ida_find_first_range(ida, 1024, (1 << 20) - 1) != (1 << 20) - 1);
IDA_BUG_ON(ida, ida_find_first_range(ida, (1 << 20), INT_MAX) != -ENOENT);
ida_free(ida, 3);
ida_free(ida, 63);
ida_free(ida, 1023);
ida_free(ida, (1 << 20) - 1);
IDA_BUG_ON(ida, !ida_is_empty(ida));
}
static DEFINE_IDA(ida);
static int ida_checks(void)
@@ -202,6 +271,7 @@ static int ida_checks(void)
ida_check_max(&ida);
ida_check_conv(&ida);
ida_check_bad_free(&ida);
ida_check_find_first(&ida);
printk("IDA: %u of %u tests passed\n", tests_passed, tests_run);
return (tests_run != tests_passed) ? 0 : -EINVAL;

View File

@@ -342,12 +342,14 @@ FIXTURE(iommufd_ioas)
uint32_t hwpt_id;
uint32_t device_id;
uint64_t base_iova;
uint32_t device_pasid_id;
};
FIXTURE_VARIANT(iommufd_ioas)
{
unsigned int mock_domains;
unsigned int memory_limit;
bool pasid_capable;
};
FIXTURE_SETUP(iommufd_ioas)
@@ -372,6 +374,12 @@ FIXTURE_SETUP(iommufd_ioas)
IOMMU_TEST_DEV_CACHE_DEFAULT);
self->base_iova = MOCK_APERTURE_START;
}
if (variant->pasid_capable)
test_cmd_mock_domain_flags(self->ioas_id,
MOCK_FLAGS_DEVICE_PASID,
NULL, NULL,
&self->device_pasid_id);
}
FIXTURE_TEARDOWN(iommufd_ioas)
@@ -387,6 +395,7 @@ FIXTURE_VARIANT_ADD(iommufd_ioas, no_domain)
FIXTURE_VARIANT_ADD(iommufd_ioas, mock_domain)
{
.mock_domains = 1,
.pasid_capable = true,
};
FIXTURE_VARIANT_ADD(iommufd_ioas, two_mock_domain)
@@ -439,6 +448,10 @@ TEST_F(iommufd_ioas, alloc_hwpt_nested)
&test_hwpt_id);
test_err_hwpt_alloc(EINVAL, self->device_id, self->device_id, 0,
&test_hwpt_id);
test_err_hwpt_alloc(EOPNOTSUPP, self->device_id, self->ioas_id,
IOMMU_HWPT_ALLOC_NEST_PARENT |
IOMMU_HWPT_FAULT_ID_VALID,
&test_hwpt_id);
test_cmd_hwpt_alloc(self->device_id, self->ioas_id,
IOMMU_HWPT_ALLOC_NEST_PARENT,
@@ -748,6 +761,8 @@ TEST_F(iommufd_ioas, get_hw_info)
} buffer_smaller;
if (self->device_id) {
uint8_t max_pasid = 0;
/* Provide a zero-size user_buffer */
test_cmd_get_hw_info(self->device_id, NULL, 0);
/* Provide a user_buffer with exact size */
@@ -762,6 +777,13 @@ TEST_F(iommufd_ioas, get_hw_info)
* the fields within the size range still gets updated.
*/
test_cmd_get_hw_info(self->device_id, &buffer_smaller, sizeof(buffer_smaller));
test_cmd_get_hw_info_pasid(self->device_id, &max_pasid);
ASSERT_EQ(0, max_pasid);
if (variant->pasid_capable) {
test_cmd_get_hw_info_pasid(self->device_pasid_id,
&max_pasid);
ASSERT_EQ(MOCK_PASID_WIDTH, max_pasid);
}
} else {
test_err_get_hw_info(ENOENT, self->device_id,
&buffer_exact, sizeof(buffer_exact));
@@ -2736,6 +2758,7 @@ TEST_F(iommufd_viommu, viommu_alloc_nested_iopf)
uint32_t iopf_hwpt_id;
uint32_t fault_id;
uint32_t fault_fd;
uint32_t vdev_id;
if (self->device_id) {
test_ioctl_fault_alloc(&fault_id, &fault_fd);
@@ -2752,6 +2775,10 @@ TEST_F(iommufd_viommu, viommu_alloc_nested_iopf)
&iopf_hwpt_id, IOMMU_HWPT_DATA_SELFTEST, &data,
sizeof(data));
/* Must allocate vdevice before attaching to a nested hwpt */
test_err_mock_domain_replace(ENOENT, self->stdev_id,
iopf_hwpt_id);
test_cmd_vdevice_alloc(viommu_id, dev_id, 0x99, &vdev_id);
test_cmd_mock_domain_replace(self->stdev_id, iopf_hwpt_id);
EXPECT_ERRNO(EBUSY,
_test_ioctl_destroy(self->fd, iopf_hwpt_id));
@@ -2769,15 +2796,46 @@ TEST_F(iommufd_viommu, vdevice_alloc)
uint32_t viommu_id = self->viommu_id;
uint32_t dev_id = self->device_id;
uint32_t vdev_id = 0;
uint32_t veventq_id;
uint32_t veventq_fd;
int prev_seq = -1;
if (dev_id) {
/* Must allocate vdevice before attaching to a nested hwpt */
test_err_mock_domain_replace(ENOENT, self->stdev_id,
self->nested_hwpt_id);
/* Allocate a vEVENTQ with veventq_depth=2 */
test_cmd_veventq_alloc(viommu_id, IOMMU_VEVENTQ_TYPE_SELFTEST,
&veventq_id, &veventq_fd);
test_err_veventq_alloc(EEXIST, viommu_id,
IOMMU_VEVENTQ_TYPE_SELFTEST, NULL, NULL);
/* Set vdev_id to 0x99, unset it, and set to 0x88 */
test_cmd_vdevice_alloc(viommu_id, dev_id, 0x99, &vdev_id);
test_cmd_mock_domain_replace(self->stdev_id,
self->nested_hwpt_id);
test_cmd_trigger_vevents(dev_id, 1);
test_cmd_read_vevents(veventq_fd, 1, 0x99, &prev_seq);
test_err_vdevice_alloc(EEXIST, viommu_id, dev_id, 0x99,
&vdev_id);
test_cmd_mock_domain_replace(self->stdev_id, self->ioas_id);
test_ioctl_destroy(vdev_id);
/* Try again with 0x88 */
test_cmd_vdevice_alloc(viommu_id, dev_id, 0x88, &vdev_id);
test_cmd_mock_domain_replace(self->stdev_id,
self->nested_hwpt_id);
/* Trigger an overflow with three events */
test_cmd_trigger_vevents(dev_id, 3);
test_err_read_vevents(EOVERFLOW, veventq_fd, 3, 0x88,
&prev_seq);
/* Overflow must be gone after the previous reads */
test_cmd_trigger_vevents(dev_id, 1);
test_cmd_read_vevents(veventq_fd, 1, 0x88, &prev_seq);
close(veventq_fd);
test_cmd_mock_domain_replace(self->stdev_id, self->ioas_id);
test_ioctl_destroy(vdev_id);
test_ioctl_destroy(veventq_id);
} else {
test_err_vdevice_alloc(ENOENT, viommu_id, dev_id, 0x99, NULL);
}
@@ -2956,4 +3014,311 @@ TEST_F(iommufd_viommu, vdevice_cache)
}
}
FIXTURE(iommufd_device_pasid)
{
int fd;
uint32_t ioas_id;
uint32_t hwpt_id;
uint32_t stdev_id;
uint32_t device_id;
uint32_t no_pasid_stdev_id;
uint32_t no_pasid_device_id;
};
FIXTURE_VARIANT(iommufd_device_pasid)
{
bool pasid_capable;
};
FIXTURE_SETUP(iommufd_device_pasid)
{
self->fd = open("/dev/iommu", O_RDWR);
ASSERT_NE(-1, self->fd);
test_ioctl_ioas_alloc(&self->ioas_id);
test_cmd_mock_domain_flags(self->ioas_id,
MOCK_FLAGS_DEVICE_PASID,
&self->stdev_id, &self->hwpt_id,
&self->device_id);
if (!variant->pasid_capable)
test_cmd_mock_domain_flags(self->ioas_id, 0,
&self->no_pasid_stdev_id, NULL,
&self->no_pasid_device_id);
}
FIXTURE_TEARDOWN(iommufd_device_pasid)
{
teardown_iommufd(self->fd, _metadata);
}
FIXTURE_VARIANT_ADD(iommufd_device_pasid, no_pasid)
{
.pasid_capable = false,
};
FIXTURE_VARIANT_ADD(iommufd_device_pasid, has_pasid)
{
.pasid_capable = true,
};
TEST_F(iommufd_device_pasid, pasid_attach)
{
struct iommu_hwpt_selftest data = {
.iotlb = IOMMU_TEST_IOTLB_DEFAULT,
};
uint32_t nested_hwpt_id[3] = {};
uint32_t parent_hwpt_id = 0;
uint32_t fault_id, fault_fd;
uint32_t s2_hwpt_id = 0;
uint32_t iopf_hwpt_id;
uint32_t pasid = 100;
uint32_t viommu_id;
/*
* Negative, detach pasid without attaching, this is not expected.
* But it should not result in failure anyway.
*/
test_cmd_pasid_detach(pasid);
/* Allocate two nested hwpts sharing one common parent hwpt */
test_cmd_hwpt_alloc(self->device_id, self->ioas_id,
IOMMU_HWPT_ALLOC_NEST_PARENT,
&parent_hwpt_id);
test_cmd_hwpt_alloc_nested(self->device_id, parent_hwpt_id,
IOMMU_HWPT_ALLOC_PASID,
&nested_hwpt_id[0],
IOMMU_HWPT_DATA_SELFTEST,
&data, sizeof(data));
test_cmd_hwpt_alloc_nested(self->device_id, parent_hwpt_id,
IOMMU_HWPT_ALLOC_PASID,
&nested_hwpt_id[1],
IOMMU_HWPT_DATA_SELFTEST,
&data, sizeof(data));
/* Fault related preparation */
test_ioctl_fault_alloc(&fault_id, &fault_fd);
test_cmd_hwpt_alloc_iopf(self->device_id, parent_hwpt_id, fault_id,
IOMMU_HWPT_FAULT_ID_VALID | IOMMU_HWPT_ALLOC_PASID,
&iopf_hwpt_id,
IOMMU_HWPT_DATA_SELFTEST, &data,
sizeof(data));
/* Allocate a regular nested hwpt based on viommu */
test_cmd_viommu_alloc(self->device_id, parent_hwpt_id,
IOMMU_VIOMMU_TYPE_SELFTEST,
&viommu_id);
test_cmd_hwpt_alloc_nested(self->device_id, viommu_id,
IOMMU_HWPT_ALLOC_PASID,
&nested_hwpt_id[2],
IOMMU_HWPT_DATA_SELFTEST, &data,
sizeof(data));
test_cmd_hwpt_alloc(self->device_id, self->ioas_id,
IOMMU_HWPT_ALLOC_PASID,
&s2_hwpt_id);
/* Attach RID to non-pasid compat domain, */
test_cmd_mock_domain_replace(self->stdev_id, parent_hwpt_id);
/* then attach to pasid should fail */
test_err_pasid_attach(EINVAL, pasid, s2_hwpt_id);
/* Attach RID to pasid compat domain, */
test_cmd_mock_domain_replace(self->stdev_id, s2_hwpt_id);
/* then attach to pasid should succeed, */
test_cmd_pasid_attach(pasid, nested_hwpt_id[0]);
/* but attach RID to non-pasid compat domain should fail now. */
test_err_mock_domain_replace(EINVAL, self->stdev_id, parent_hwpt_id);
/*
* Detach hwpt from pasid 100, and check if the pasid 100
* has null domain.
*/
test_cmd_pasid_detach(pasid);
ASSERT_EQ(0,
test_cmd_pasid_check_hwpt(self->fd, self->stdev_id,
pasid, 0));
/* RID is attached to pasid-comapt domain, pasid path is not used */
if (!variant->pasid_capable) {
/*
* PASID-compatible domain can be used by non-PASID-capable
* device.
*/
test_cmd_mock_domain_replace(self->no_pasid_stdev_id, nested_hwpt_id[0]);
test_cmd_mock_domain_replace(self->no_pasid_stdev_id, self->ioas_id);
/*
* Attach hwpt to pasid 100 of non-PASID-capable device,
* should fail, no matter domain is pasid-comapt or not.
*/
EXPECT_ERRNO(EINVAL,
_test_cmd_pasid_attach(self->fd, self->no_pasid_stdev_id,
pasid, parent_hwpt_id));
EXPECT_ERRNO(EINVAL,
_test_cmd_pasid_attach(self->fd, self->no_pasid_stdev_id,
pasid, s2_hwpt_id));
}
/*
* Attach non pasid compat hwpt to pasid-capable device, should
* fail, and have null domain.
*/
test_err_pasid_attach(EINVAL, pasid, parent_hwpt_id);
ASSERT_EQ(0,
test_cmd_pasid_check_hwpt(self->fd, self->stdev_id,
pasid, 0));
/*
* Attach ioas to pasid 100, should fail, domain should
* be null.
*/
test_err_pasid_attach(EINVAL, pasid, self->ioas_id);
ASSERT_EQ(0,
test_cmd_pasid_check_hwpt(self->fd, self->stdev_id,
pasid, 0));
/*
* Attach the s2_hwpt to pasid 100, should succeed, domain should
* be valid.
*/
test_cmd_pasid_attach(pasid, s2_hwpt_id);
ASSERT_EQ(0,
test_cmd_pasid_check_hwpt(self->fd, self->stdev_id,
pasid, s2_hwpt_id));
/*
* Try attach pasid 100 with another hwpt, should FAIL
* as attach does not allow overwrite, use REPLACE instead.
*/
test_err_pasid_attach(EBUSY, pasid, nested_hwpt_id[0]);
/*
* Detach hwpt from pasid 100 for next test, should succeed,
* and have null domain.
*/
test_cmd_pasid_detach(pasid);
ASSERT_EQ(0,
test_cmd_pasid_check_hwpt(self->fd, self->stdev_id,
pasid, 0));
/*
* Attach nested hwpt to pasid 100, should succeed, domain
* should be valid.
*/
test_cmd_pasid_attach(pasid, nested_hwpt_id[0]);
ASSERT_EQ(0,
test_cmd_pasid_check_hwpt(self->fd, self->stdev_id,
pasid, nested_hwpt_id[0]));
/* Attach to pasid 100 which has been attached, should fail. */
test_err_pasid_attach(EBUSY, pasid, nested_hwpt_id[0]);
/* cleanup pasid 100 */
test_cmd_pasid_detach(pasid);
/* Replace tests */
pasid = 200;
/*
* Replace pasid 200 without attaching it, should fail
* with -EINVAL.
*/
test_err_pasid_replace(EINVAL, pasid, s2_hwpt_id);
/*
* Attach the s2 hwpt to pasid 200, should succeed, domain should
* be valid.
*/
test_cmd_pasid_attach(pasid, s2_hwpt_id);
ASSERT_EQ(0,
test_cmd_pasid_check_hwpt(self->fd, self->stdev_id,
pasid, s2_hwpt_id));
/*
* Replace pasid 200 with self->ioas_id, should fail
* and domain should be the prior s2 hwpt.
*/
test_err_pasid_replace(EINVAL, pasid, self->ioas_id);
ASSERT_EQ(0,
test_cmd_pasid_check_hwpt(self->fd, self->stdev_id,
pasid, s2_hwpt_id));
/*
* Replace a nested hwpt for pasid 200, should succeed,
* and have valid domain.
*/
test_cmd_pasid_replace(pasid, nested_hwpt_id[0]);
ASSERT_EQ(0,
test_cmd_pasid_check_hwpt(self->fd, self->stdev_id,
pasid, nested_hwpt_id[0]));
/*
* Replace with another nested hwpt for pasid 200, should
* succeed, and have valid domain.
*/
test_cmd_pasid_replace(pasid, nested_hwpt_id[1]);
ASSERT_EQ(0,
test_cmd_pasid_check_hwpt(self->fd, self->stdev_id,
pasid, nested_hwpt_id[1]));
/* cleanup pasid 200 */
test_cmd_pasid_detach(pasid);
/* Negative Tests for pasid replace, use pasid 1024 */
/*
* Attach the s2 hwpt to pasid 1024, should succeed, domain should
* be valid.
*/
pasid = 1024;
test_cmd_pasid_attach(pasid, s2_hwpt_id);
ASSERT_EQ(0,
test_cmd_pasid_check_hwpt(self->fd, self->stdev_id,
pasid, s2_hwpt_id));
/*
* Replace pasid 1024 with nested_hwpt_id[0], should fail,
* but have the old valid domain. This is a designed
* negative case. Normally, this shall succeed.
*/
test_err_pasid_replace(ENOMEM, pasid, nested_hwpt_id[0]);
ASSERT_EQ(0,
test_cmd_pasid_check_hwpt(self->fd, self->stdev_id,
pasid, s2_hwpt_id));
/* cleanup pasid 1024 */
test_cmd_pasid_detach(pasid);
/* Attach to iopf-capable hwpt */
/*
* Attach an iopf hwpt to pasid 2048, should succeed, domain should
* be valid.
*/
pasid = 2048;
test_cmd_pasid_attach(pasid, iopf_hwpt_id);
ASSERT_EQ(0,
test_cmd_pasid_check_hwpt(self->fd, self->stdev_id,
pasid, iopf_hwpt_id));
test_cmd_trigger_iopf_pasid(self->device_id, pasid, fault_fd);
/*
* Replace with s2_hwpt_id for pasid 2048, should
* succeed, and have valid domain.
*/
test_cmd_pasid_replace(pasid, s2_hwpt_id);
ASSERT_EQ(0,
test_cmd_pasid_check_hwpt(self->fd, self->stdev_id,
pasid, s2_hwpt_id));
/* cleanup pasid 2048 */
test_cmd_pasid_detach(pasid);
test_ioctl_destroy(iopf_hwpt_id);
close(fault_fd);
test_ioctl_destroy(fault_id);
/* Detach the s2_hwpt_id from RID */
test_cmd_mock_domain_replace(self->stdev_id, self->ioas_id);
}
TEST_HARNESS_MAIN

View File

@@ -209,12 +209,16 @@ FIXTURE(basic_fail_nth)
{
int fd;
uint32_t access_id;
uint32_t stdev_id;
uint32_t pasid;
};
FIXTURE_SETUP(basic_fail_nth)
{
self->fd = -1;
self->access_id = 0;
self->stdev_id = 0;
self->pasid = 0; //test should use a non-zero value
}
FIXTURE_TEARDOWN(basic_fail_nth)
@@ -226,6 +230,8 @@ FIXTURE_TEARDOWN(basic_fail_nth)
rc = _test_cmd_destroy_access(self->access_id);
assert(rc == 0);
}
if (self->pasid && self->stdev_id)
_test_cmd_pasid_detach(self->fd, self->stdev_id, self->pasid);
teardown_iommufd(self->fd, _metadata);
}
@@ -620,10 +626,11 @@ TEST_FAIL_NTH(basic_fail_nth, device)
};
struct iommu_test_hw_info info;
uint32_t fault_id, fault_fd;
uint32_t veventq_id, veventq_fd;
uint32_t fault_hwpt_id;
uint32_t test_hwpt_id;
uint32_t ioas_id;
uint32_t ioas_id2;
uint32_t stdev_id;
uint32_t idev_id;
uint32_t hwpt_id;
uint32_t viommu_id;
@@ -654,25 +661,30 @@ TEST_FAIL_NTH(basic_fail_nth, device)
fail_nth_enable();
if (_test_cmd_mock_domain(self->fd, ioas_id, &stdev_id, NULL,
&idev_id))
if (_test_cmd_mock_domain_flags(self->fd, ioas_id,
MOCK_FLAGS_DEVICE_PASID,
&self->stdev_id, NULL, &idev_id))
return -1;
if (_test_cmd_get_hw_info(self->fd, idev_id, &info, sizeof(info), NULL))
return -1;
if (_test_cmd_hwpt_alloc(self->fd, idev_id, ioas_id, 0, 0, &hwpt_id,
IOMMU_HWPT_DATA_NONE, 0, 0))
return -1;
if (_test_cmd_mock_domain_replace(self->fd, stdev_id, ioas_id2, NULL))
return -1;
if (_test_cmd_mock_domain_replace(self->fd, stdev_id, hwpt_id, NULL))
if (_test_cmd_get_hw_info(self->fd, idev_id, &info,
sizeof(info), NULL, NULL))
return -1;
if (_test_cmd_hwpt_alloc(self->fd, idev_id, ioas_id, 0,
IOMMU_HWPT_ALLOC_NEST_PARENT, &hwpt_id,
IOMMU_HWPT_ALLOC_PASID, &hwpt_id,
IOMMU_HWPT_DATA_NONE, 0, 0))
return -1;
if (_test_cmd_mock_domain_replace(self->fd, self->stdev_id, ioas_id2, NULL))
return -1;
if (_test_cmd_mock_domain_replace(self->fd, self->stdev_id, hwpt_id, NULL))
return -1;
if (_test_cmd_hwpt_alloc(self->fd, idev_id, ioas_id, 0,
IOMMU_HWPT_ALLOC_NEST_PARENT |
IOMMU_HWPT_ALLOC_PASID,
&hwpt_id,
IOMMU_HWPT_DATA_NONE, 0, 0))
return -1;
@@ -692,6 +704,37 @@ TEST_FAIL_NTH(basic_fail_nth, device)
IOMMU_HWPT_DATA_SELFTEST, &data, sizeof(data)))
return -1;
if (_test_cmd_veventq_alloc(self->fd, viommu_id,
IOMMU_VEVENTQ_TYPE_SELFTEST, &veventq_id,
&veventq_fd))
return -1;
close(veventq_fd);
if (_test_cmd_hwpt_alloc(self->fd, idev_id, ioas_id, 0,
IOMMU_HWPT_ALLOC_PASID,
&test_hwpt_id,
IOMMU_HWPT_DATA_NONE, 0, 0))
return -1;
/* Tests for pasid attach/replace/detach */
self->pasid = 200;
if (_test_cmd_pasid_attach(self->fd, self->stdev_id,
self->pasid, hwpt_id)) {
self->pasid = 0;
return -1;
}
if (_test_cmd_pasid_replace(self->fd, self->stdev_id,
self->pasid, test_hwpt_id))
return -1;
if (_test_cmd_pasid_detach(self->fd, self->stdev_id, self->pasid))
return -1;
self->pasid = 0;
return 0;
}

View File

@@ -9,6 +9,7 @@
#include <sys/ioctl.h>
#include <stdint.h>
#include <assert.h>
#include <poll.h>
#include "../kselftest_harness.h"
#include "../../../../drivers/iommu/iommufd/iommufd_test.h"
@@ -757,7 +758,8 @@ static void teardown_iommufd(int fd, struct __test_metadata *_metadata)
/* @data can be NULL */
static int _test_cmd_get_hw_info(int fd, __u32 device_id, void *data,
size_t data_len, uint32_t *capabilities)
size_t data_len, uint32_t *capabilities,
uint8_t *max_pasid)
{
struct iommu_test_hw_info *info = (struct iommu_test_hw_info *)data;
struct iommu_hw_info cmd = {
@@ -802,6 +804,9 @@ static int _test_cmd_get_hw_info(int fd, __u32 device_id, void *data,
assert(!info->flags);
}
if (max_pasid)
*max_pasid = cmd.out_max_pasid_log2;
if (capabilities)
*capabilities = cmd.out_capabilities;
@@ -810,14 +815,19 @@ static int _test_cmd_get_hw_info(int fd, __u32 device_id, void *data,
#define test_cmd_get_hw_info(device_id, data, data_len) \
ASSERT_EQ(0, _test_cmd_get_hw_info(self->fd, device_id, data, \
data_len, NULL))
data_len, NULL, NULL))
#define test_err_get_hw_info(_errno, device_id, data, data_len) \
EXPECT_ERRNO(_errno, _test_cmd_get_hw_info(self->fd, device_id, data, \
data_len, NULL))
data_len, NULL, NULL))
#define test_cmd_get_hw_capabilities(device_id, caps, mask) \
ASSERT_EQ(0, _test_cmd_get_hw_info(self->fd, device_id, NULL, 0, &caps))
ASSERT_EQ(0, _test_cmd_get_hw_info(self->fd, device_id, NULL, \
0, &caps, NULL))
#define test_cmd_get_hw_info_pasid(device_id, max_pasid) \
ASSERT_EQ(0, _test_cmd_get_hw_info(self->fd, device_id, NULL, \
0, NULL, max_pasid))
static int _test_ioctl_fault_alloc(int fd, __u32 *fault_id, __u32 *fault_fd)
{
@@ -842,14 +852,15 @@ static int _test_ioctl_fault_alloc(int fd, __u32 *fault_id, __u32 *fault_fd)
ASSERT_NE(0, *(fault_fd)); \
})
static int _test_cmd_trigger_iopf(int fd, __u32 device_id, __u32 fault_fd)
static int _test_cmd_trigger_iopf(int fd, __u32 device_id, __u32 pasid,
__u32 fault_fd)
{
struct iommu_test_cmd trigger_iopf_cmd = {
.size = sizeof(trigger_iopf_cmd),
.op = IOMMU_TEST_OP_TRIGGER_IOPF,
.trigger_iopf = {
.dev_id = device_id,
.pasid = 0x1,
.pasid = pasid,
.grpid = 0x2,
.perm = IOMMU_PGFAULT_PERM_READ | IOMMU_PGFAULT_PERM_WRITE,
.addr = 0xdeadbeaf,
@@ -880,7 +891,10 @@ static int _test_cmd_trigger_iopf(int fd, __u32 device_id, __u32 fault_fd)
}
#define test_cmd_trigger_iopf(device_id, fault_fd) \
ASSERT_EQ(0, _test_cmd_trigger_iopf(self->fd, device_id, fault_fd))
ASSERT_EQ(0, _test_cmd_trigger_iopf(self->fd, device_id, 0x1, fault_fd))
#define test_cmd_trigger_iopf_pasid(device_id, pasid, fault_fd) \
ASSERT_EQ(0, _test_cmd_trigger_iopf(self->fd, device_id, \
pasid, fault_fd))
static int _test_cmd_viommu_alloc(int fd, __u32 device_id, __u32 hwpt_id,
__u32 type, __u32 flags, __u32 *viommu_id)
@@ -936,3 +950,204 @@ static int _test_cmd_vdevice_alloc(int fd, __u32 viommu_id, __u32 idev_id,
EXPECT_ERRNO(_errno, \
_test_cmd_vdevice_alloc(self->fd, viommu_id, idev_id, \
virt_id, vdev_id))
static int _test_cmd_veventq_alloc(int fd, __u32 viommu_id, __u32 type,
__u32 *veventq_id, __u32 *veventq_fd)
{
struct iommu_veventq_alloc cmd = {
.size = sizeof(cmd),
.type = type,
.veventq_depth = 2,
.viommu_id = viommu_id,
};
int ret;
ret = ioctl(fd, IOMMU_VEVENTQ_ALLOC, &cmd);
if (ret)
return ret;
if (veventq_id)
*veventq_id = cmd.out_veventq_id;
if (veventq_fd)
*veventq_fd = cmd.out_veventq_fd;
return 0;
}
#define test_cmd_veventq_alloc(viommu_id, type, veventq_id, veventq_fd) \
ASSERT_EQ(0, _test_cmd_veventq_alloc(self->fd, viommu_id, type, \
veventq_id, veventq_fd))
#define test_err_veventq_alloc(_errno, viommu_id, type, veventq_id, \
veventq_fd) \
EXPECT_ERRNO(_errno, \
_test_cmd_veventq_alloc(self->fd, viommu_id, type, \
veventq_id, veventq_fd))
static int _test_cmd_trigger_vevents(int fd, __u32 dev_id, __u32 nvevents)
{
struct iommu_test_cmd trigger_vevent_cmd = {
.size = sizeof(trigger_vevent_cmd),
.op = IOMMU_TEST_OP_TRIGGER_VEVENT,
.trigger_vevent = {
.dev_id = dev_id,
},
};
int ret;
while (nvevents--) {
ret = ioctl(fd, _IOMMU_TEST_CMD(IOMMU_TEST_OP_TRIGGER_VEVENT),
&trigger_vevent_cmd);
if (ret < 0)
return -1;
}
return ret;
}
#define test_cmd_trigger_vevents(dev_id, nvevents) \
ASSERT_EQ(0, _test_cmd_trigger_vevents(self->fd, dev_id, nvevents))
static int _test_cmd_read_vevents(int fd, __u32 event_fd, __u32 nvevents,
__u32 virt_id, int *prev_seq)
{
struct pollfd pollfd = { .fd = event_fd, .events = POLLIN };
struct iommu_viommu_event_selftest *event;
struct iommufd_vevent_header *hdr;
ssize_t bytes;
void *data;
int ret, i;
ret = poll(&pollfd, 1, 1000);
if (ret < 0)
return -1;
data = calloc(nvevents, sizeof(*hdr) + sizeof(*event));
if (!data) {
errno = ENOMEM;
return -1;
}
bytes = read(event_fd, data,
nvevents * (sizeof(*hdr) + sizeof(*event)));
if (bytes <= 0) {
errno = EFAULT;
ret = -1;
goto out_free;
}
for (i = 0; i < nvevents; i++) {
hdr = data + i * (sizeof(*hdr) + sizeof(*event));
if (hdr->flags & IOMMU_VEVENTQ_FLAG_LOST_EVENTS ||
hdr->sequence - *prev_seq > 1) {
*prev_seq = hdr->sequence;
errno = EOVERFLOW;
ret = -1;
goto out_free;
}
*prev_seq = hdr->sequence;
event = data + sizeof(*hdr);
if (event->virt_id != virt_id) {
errno = EINVAL;
ret = -1;
goto out_free;
}
}
ret = 0;
out_free:
free(data);
return ret;
}
#define test_cmd_read_vevents(event_fd, nvevents, virt_id, prev_seq) \
ASSERT_EQ(0, _test_cmd_read_vevents(self->fd, event_fd, nvevents, \
virt_id, prev_seq))
#define test_err_read_vevents(_errno, event_fd, nvevents, virt_id, prev_seq) \
EXPECT_ERRNO(_errno, \
_test_cmd_read_vevents(self->fd, event_fd, nvevents, \
virt_id, prev_seq))
static int _test_cmd_pasid_attach(int fd, __u32 stdev_id, __u32 pasid,
__u32 pt_id)
{
struct iommu_test_cmd test_attach = {
.size = sizeof(test_attach),
.op = IOMMU_TEST_OP_PASID_ATTACH,
.id = stdev_id,
.pasid_attach = {
.pasid = pasid,
.pt_id = pt_id,
},
};
return ioctl(fd, _IOMMU_TEST_CMD(IOMMU_TEST_OP_PASID_ATTACH),
&test_attach);
}
#define test_cmd_pasid_attach(pasid, hwpt_id) \
ASSERT_EQ(0, _test_cmd_pasid_attach(self->fd, self->stdev_id, \
pasid, hwpt_id))
#define test_err_pasid_attach(_errno, pasid, hwpt_id) \
EXPECT_ERRNO(_errno, \
_test_cmd_pasid_attach(self->fd, self->stdev_id, \
pasid, hwpt_id))
static int _test_cmd_pasid_replace(int fd, __u32 stdev_id, __u32 pasid,
__u32 pt_id)
{
struct iommu_test_cmd test_replace = {
.size = sizeof(test_replace),
.op = IOMMU_TEST_OP_PASID_REPLACE,
.id = stdev_id,
.pasid_replace = {
.pasid = pasid,
.pt_id = pt_id,
},
};
return ioctl(fd, _IOMMU_TEST_CMD(IOMMU_TEST_OP_PASID_REPLACE),
&test_replace);
}
#define test_cmd_pasid_replace(pasid, hwpt_id) \
ASSERT_EQ(0, _test_cmd_pasid_replace(self->fd, self->stdev_id, \
pasid, hwpt_id))
#define test_err_pasid_replace(_errno, pasid, hwpt_id) \
EXPECT_ERRNO(_errno, \
_test_cmd_pasid_replace(self->fd, self->stdev_id, \
pasid, hwpt_id))
static int _test_cmd_pasid_detach(int fd, __u32 stdev_id, __u32 pasid)
{
struct iommu_test_cmd test_detach = {
.size = sizeof(test_detach),
.op = IOMMU_TEST_OP_PASID_DETACH,
.id = stdev_id,
.pasid_detach = {
.pasid = pasid,
},
};
return ioctl(fd, _IOMMU_TEST_CMD(IOMMU_TEST_OP_PASID_DETACH),
&test_detach);
}
#define test_cmd_pasid_detach(pasid) \
ASSERT_EQ(0, _test_cmd_pasid_detach(self->fd, self->stdev_id, pasid))
static int test_cmd_pasid_check_hwpt(int fd, __u32 stdev_id, __u32 pasid,
__u32 hwpt_id)
{
struct iommu_test_cmd test_pasid_check = {
.size = sizeof(test_pasid_check),
.op = IOMMU_TEST_OP_PASID_CHECK_HWPT,
.id = stdev_id,
.pasid_check = {
.pasid = pasid,
.hwpt_id = hwpt_id,
},
};
return ioctl(fd, _IOMMU_TEST_CMD(IOMMU_TEST_OP_PASID_CHECK_HWPT),
&test_pasid_check);
}