[RFC PATCH v1 7/8] iommufd: Prepare software MSI installation for address lists
From: Andrew Jones
Date: Wed Aug 26 2026 - 10:57:20 EST
Teach the software MSI path to install every mapping in an address list
before publishing newly allocated maps. Mark the complete list as
required by the group and roll back mappings installed by a failed
operation.
Keep iommufd_sw_msi() as a one-address wrapper so existing callers
retain their current behavior.
Signed-off-by: Andrew Jones <andrew.jones@xxxxxxxxxxxxxxxx>
---
drivers/iommu/iommufd/driver.c | 101 +++++++++++++++++++++++----------
1 file changed, 72 insertions(+), 29 deletions(-)
diff --git a/drivers/iommu/iommufd/driver.c b/drivers/iommu/iommufd/driver.c
index a43a25078f87..45278c11c688 100644
--- a/drivers/iommu/iommufd/driver.c
+++ b/drivers/iommu/iommufd/driver.c
@@ -340,14 +340,14 @@ static int iommufd_sw_msi_install_one(struct iommufd_ctx *ictx,
return 0;
}
-int iommufd_sw_msi_install(struct iommufd_ctx *ictx,
- struct iommufd_hwpt_paging *hwpt_paging,
- struct iommufd_sw_msi_map *base_map)
+static int __iommufd_sw_msi_install_range(struct iommufd_ctx *ictx,
+ struct iommufd_hwpt_paging *hwpt_paging,
+ struct list_head *msi_maps,
+ const struct iommufd_sw_msi_map *base_map,
+ unsigned int nr_addrs)
{
struct iommufd_sw_msi_map *msi_map;
- struct list_head *msi_maps = &ictx->sw_msi_list;
unsigned long *newly_mapped;
- unsigned int nr_addrs = base_map->range_size ? base_map->range_size / PAGE_SIZE : 1;
unsigned int nr_found = 0;
unsigned int index;
int rc = 0;
@@ -392,16 +392,36 @@ int iommufd_sw_msi_install(struct iommufd_ctx *ictx,
bitmap_free(newly_mapped);
return rc;
}
+
+int iommufd_sw_msi_install(struct iommufd_ctx *ictx,
+ struct iommufd_hwpt_paging *hwpt_paging,
+ struct iommufd_sw_msi_map *base_map)
+{
+ unsigned int nr_addrs = base_map->range_size ? base_map->range_size / PAGE_SIZE : 1;
+
+ return __iommufd_sw_msi_install_range(ictx, hwpt_paging, &ictx->sw_msi_list, base_map,
+ nr_addrs);
+}
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)
+static void iommufd_sw_msi_set_required(struct iommufd_group *igroup,
+ struct list_head *msi_maps,
+ const struct iommufd_sw_msi_map *base_map,
+ unsigned int nr_addrs)
+{
+ struct iommufd_sw_msi_map *msi_map;
+ unsigned int index;
+
+ list_for_each_entry(msi_map, msi_maps, sw_msi_item) {
+ if (!iommufd_sw_msi_range_index(msi_map, base_map, nr_addrs, &index))
+ continue;
+ __set_bit(msi_map->id, igroup->required_sw_msi.bitmap);
+ }
+}
+
+static int iommufd_sw_msi_list(struct iommu_domain *domain, struct msi_desc *desc,
+ const phys_addr_t *phys_addrs, unsigned int nr_addrs,
+ size_t granule)
{
struct device *dev = msi_desc_to_dev(desc);
struct iommufd_hwpt_paging *hwpt_paging;
@@ -410,10 +430,19 @@ int iommufd_sw_msi(struct iommu_domain *domain, struct msi_desc *desc,
struct iommufd_sw_msi_map *msi_map;
struct iommufd_ctx *ictx;
LIST_HEAD(new_msi_maps);
+ struct list_head *msi_maps;
unsigned long iova;
- phys_addr_t phys_addr;
+ unsigned int i;
int rc;
+ if (granule != PAGE_SIZE)
+ return -EOPNOTSUPP;
+ if (!nr_addrs || nr_addrs > SIZE_MAX / PAGE_SIZE)
+ return -EINVAL;
+ for (i = 0; i < nr_addrs; i++)
+ if (!IS_ALIGNED(phys_addrs[i], PAGE_SIZE))
+ return -EINVAL;
+
/*
* 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
@@ -434,33 +463,33 @@ int iommufd_sw_msi(struct iommu_domain *domain, struct msi_desc *desc,
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.
- */
- phys_addr = msi_addr & PAGE_MASK;
- msi_map = iommufd_sw_msi_get_map(ictx, &phys_addr, 1, &handle->idev->igroup->sw_msi_range);
- if (!msi_map)
- msi_map = iommufd_sw_msi_alloc_map(ictx, &phys_addr, 1,
+ msi_map = iommufd_sw_msi_get_map(ictx, phys_addrs, nr_addrs,
+ &handle->idev->igroup->sw_msi_range);
+ if (msi_map) {
+ msi_maps = &ictx->sw_msi_list;
+ } else {
+ msi_map = iommufd_sw_msi_alloc_map(ictx, phys_addrs, nr_addrs,
&handle->idev->igroup->sw_msi_range,
&new_msi_maps);
- if (IS_ERR(msi_map))
- return PTR_ERR(msi_map);
+ if (IS_ERR(msi_map))
+ return PTR_ERR(msi_map);
+ msi_maps = &new_msi_maps;
+ }
rc = iommufd_sw_msi_maps_ensure(&handle->idev->igroup->required_sw_msi,
- msi_map->id);
+ msi_map->id + nr_addrs - 1);
if (rc)
goto err_free;
- rc = iommufd_sw_msi_install(ictx, hwpt_paging, msi_map);
+ rc = __iommufd_sw_msi_install_range(ictx, hwpt_paging, msi_maps, msi_map, nr_addrs);
if (rc)
goto err_free;
- __set_bit(msi_map->id, handle->idev->igroup->required_sw_msi.bitmap);
+
+ iommufd_sw_msi_set_required(handle->idev->igroup, msi_maps, msi_map, nr_addrs);
if (!list_empty(&new_msi_maps)) {
list_splice_tail_init(&new_msi_maps, &ictx->sw_msi_list);
- ictx->sw_msi_id++;
+ ictx->sw_msi_id += nr_addrs;
}
iova = msi_map->sw_msi_start + msi_map->pgoff * PAGE_SIZE;
@@ -475,6 +504,20 @@ int iommufd_sw_msi(struct iommu_domain *domain, struct msi_desc *desc,
}
return rc;
}
+
+/*
+ * 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)
+{
+ phys_addr_t phys_addr = msi_addr & PAGE_MASK;
+
+ return iommufd_sw_msi_list(domain, desc, &phys_addr, 1, PAGE_SIZE);
+}
EXPORT_SYMBOL_NS_GPL(iommufd_sw_msi, "IOMMUFD");
#endif
--
2.43.0