+
+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 iommu_dma_msi_page *msi_page;
+ struct iova_domain *iovad = &cookie->iovad;
+ struct iova *iova;
+ int prot = IOMMU_WRITE | IOMMU_NOEXEC | IOMMU_MMIO;
+
+ msi_addr &= ~(phys_addr_t)iova_mask(iovad);
+ list_for_each_entry(msi_page, &cookie->msi_page_list, list)
+ if (msi_page->phys == msi_addr)
+ return msi_page;
+
+ msi_page = kzalloc(sizeof(*msi_page), GFP_ATOMIC);
+ if (!msi_page)
+ return NULL;
+
+ iova = __alloc_iova(domain, iovad->granule, dma_get_mask(dev));
+ if (!iova)
+ goto out_free_page;
+
+ msi_page->phys = msi_addr;
+ msi_page->iova = iova_dma_addr(iovad, iova);
+ if (iommu_map(domain, msi_page->iova, msi_addr, iovad->granule, prot))
+ goto out_free_iova;
+
+ INIT_LIST_HEAD(&msi_page->list);
+ list_add(&msi_page->list, &cookie->msi_page_list);
+ return msi_page;
+
+out_free_iova:
+ __free_iova(iovad, iova);
+out_free_page:
+ kfree(msi_page);
+ return NULL;
+}
+
+void iommu_dma_map_msi_msg(int irq, struct msi_msg *msg)
+{
+ struct device *dev = msi_desc_to_dev(irq_get_msi_desc(irq));
+ struct iommu_domain *domain = iommu_get_domain_for_dev(dev);
+ struct iommu_dma_cookie *cookie;
+ struct iommu_dma_msi_page *msi_page;
+ phys_addr_t msi_addr = (u64)msg->address_hi << 32 | msg->address_lo;
+ unsigned long flags;
+
+ if (!domain || !domain->iova_cookie)
+ return;
+
+ cookie = domain->iova_cookie;
+
+ /*
+ * We disable IRQs to rule out a possible inversion against
+ * irq_desc_lock if, say, someone tries to retarget the affinity
+ * of an MSI from within an IPI handler.
+ */
+ spin_lock_irqsave(&cookie->msi_lock, flags);
+ msi_page = iommu_dma_get_msi_page(dev, msi_addr, domain);
+ spin_unlock_irqrestore(&cookie->msi_lock, flags);
+
+ if (WARN_ON(!msi_page)) {
+ /*
+ * We're called from a void callback, so the best we can do is
+ * 'fail' by filling the message with obviously bogus values.
+ * Since we got this far due to an IOMMU being present, it's
+ * not like the existing address would have worked anyway...
+ */
+ msg->address_hi = ~0U;
+ msg->address_lo = ~0U;
+ msg->data = ~0U;
+ } else {
+ msg->address_hi = upper_32_bits(msi_page->iova);
+ msg->address_lo &= iova_mask(&cookie->iovad);
+ msg->address_lo += lower_32_bits(msi_page->iova);
+ }
+}