Commit edb7ccda0b for qemu.org
commit edb7ccda0b05f5dda25fe07f8b9a86e6d6a7a41c
Author: Clément MATHIEU--DRIF <clement.mathieu--drif@bull.com>
Date: Thu Sep 3 14:51:15 2026 +0000
intel_iommu: Support concurrent page fault handling with PRI
PRQ slot allocation must be thread safe to avoid posting multiple
commands in the same slot so we take a lock in vtd_pri_request_page.
This allows multiple devices to call the page request interface
of the same iommu instance.
Signed-off-by: Clement Mathieu--Drif <clement.mathieu--drif@bull.com>
Reviewed-by: Michael S. Tsirkin <mst@redhat.com>
Signed-off-by: Michael S. Tsirkin <mst@redhat.com>
Message-ID: <20260903145044.2245609-1-clement.mathieu--drif@bull.com>
diff --git a/hw/i386/intel_iommu.c b/hw/i386/intel_iommu.c
index 82c3c3b2c3..7c442a86ca 100644
--- a/hw/i386/intel_iommu.c
+++ b/hw/i386/intel_iommu.c
@@ -3627,15 +3627,26 @@ static void vtd_handle_iectl_write(IntelIOMMUState *s)
static void vtd_handle_prs_write(IntelIOMMUState *s)
{
- uint32_t prs = vtd_get_long_raw(s, DMAR_PRS_REG);
+ uint32_t prs;
+
+ vtd_iommu_lock(s);
+
+ prs = vtd_get_long_raw(s, DMAR_PRS_REG);
if (!(prs & VTD_PR_STATUS_PPR) && !(prs & VTD_PR_STATUS_PRO)) {
vtd_set_clear_mask_long(s, DMAR_PECTL_REG, VTD_PR_PECTL_IP, 0);
}
+
+ vtd_iommu_unlock(s);
}
static void vtd_handle_pectl_write(IntelIOMMUState *s)
{
- uint32_t pectl = vtd_get_long_raw(s, DMAR_PECTL_REG);
+ uint32_t pectl;
+
+ vtd_iommu_lock(s);
+
+ pectl = vtd_get_long_raw(s, DMAR_PECTL_REG);
+
if ((pectl & VTD_PR_PECTL_IP) && !(pectl & VTD_PR_PECTL_IM)) {
/*
* If IP field was 1 when software clears the IM field,
@@ -3644,6 +3655,8 @@ static void vtd_handle_pectl_write(IntelIOMMUState *s)
vtd_set_clear_mask_long(s, DMAR_PECTL_REG, VTD_PR_PECTL_IP, 0);
vtd_generate_interrupt(s, DMAR_PEADDR_REG, DMAR_PEDATA_REG);
}
+
+ vtd_iommu_unlock(s);
}
static uint64_t vtd_mem_read(void *opaque, hwaddr addr, unsigned size)
@@ -5377,19 +5390,18 @@ static int vtd_pri_request_page(PCIBus *bus, void *opaque, int devfn,
{
IntelIOMMUState *s = opaque;
VTDAddressSpace *vtd_as;
+ uint64_t queue_addr_reg;
+ uint64_t queue_tail_offset_reg;
+ uint64_t new_queue_tail_offset;
+ uint64_t queue_head_offset_reg;
+ hwaddr queue_tail;
+ uint32_t old_pr_status;
+ uint16_t sid;
+ VTDPRDesc desc;
+ int ret = 0;
vtd_as = vtd_find_add_as(s, bus, devfn, pasid);
-
- uint64_t queue_addr_reg = vtd_get_quad(s, DMAR_PQA_REG);
- uint64_t queue_tail_offset_reg = vtd_get_quad(s, DMAR_PQT_REG);
- uint64_t new_queue_tail_offset = (
- (queue_tail_offset_reg + VTD_PQA_ENTRY_SIZE) %
- (vtd_prq_size(s) * VTD_PQA_ENTRY_SIZE));
- uint64_t queue_head_offset_reg = vtd_get_quad(s, DMAR_PQH_REG);
- hwaddr queue_tail = (queue_addr_reg & VTD_PQA_ADDR) + queue_tail_offset_reg;
- uint32_t old_pr_status = vtd_get_long(s, DMAR_PRS_REG);
- uint16_t sid = PCI_BUILD_BDF(pci_bus_num(vtd_as->bus), vtd_as->devfn);
- VTDPRDesc desc;
+ sid = PCI_BUILD_BDF(pci_bus_num(vtd_as->bus), vtd_as->devfn);
if (!(s->ecap & VTD_ECAP_PRS)) {
return -EPERM;
@@ -5413,52 +5425,63 @@ static int vtd_pri_request_page(PCIBus *bus, void *opaque, int devfn,
return -EPERM;
}
+ /* Prepare the descriptor */
+ desc.lo = VTD_PRD_TYPE | VTD_PRD_PP(true) | VTD_PRD_RID(sid) |
+ VTD_PRD_PASID(vtd_as->pasid) | VTD_PRD_PMR(priv_req);
+ desc.hi = VTD_PRD_RDR(is_read) | VTD_PRD_WRR(is_write) |
+ VTD_PRD_LPIG(lpig) | VTD_PRD_PRGI(prgi) | VTD_PRD_ADDR(addr);
+
+ desc.lo = cpu_to_le64(desc.lo);
+ desc.hi = cpu_to_le64(desc.hi);
+
+ if (vtd_pri_perform_implicit_invalidation(vtd_as, addr)) {
+ return -EINVAL;
+ }
+
+ vtd_iommu_lock(s);
+
+ queue_addr_reg = vtd_get_quad(s, DMAR_PQA_REG);
+ queue_tail_offset_reg = vtd_get_quad(s, DMAR_PQT_REG);
+ new_queue_tail_offset = ((queue_tail_offset_reg + VTD_PQA_ENTRY_SIZE) %
+ (vtd_prq_size(s) * VTD_PQA_ENTRY_SIZE));
+ queue_head_offset_reg = vtd_get_quad(s, DMAR_PQH_REG);
+ queue_tail = (queue_addr_reg & VTD_PQA_ADDR) + queue_tail_offset_reg;
+ old_pr_status = vtd_get_long(s, DMAR_PRS_REG);
+
if (old_pr_status & VTD_PR_STATUS_PRO) {
/*
* No action is taken by hardware to report a fault
* or generate an event
*/
- return -ENOSPC;
+ ret = -ENOSPC;
+ goto out;
}
/* Check for overflow */
if (new_queue_tail_offset == queue_head_offset_reg) {
vtd_set_clear_mask_long(s, DMAR_PRS_REG, 0, VTD_PR_STATUS_PRO);
vtd_generate_page_request_event(s, old_pr_status);
- return -ENOSPC;
- }
-
- if (vtd_pri_perform_implicit_invalidation(vtd_as, addr)) {
- return -EINVAL;
+ ret = -ENOSPC;
+ goto out;
}
- desc.lo = VTD_PRD_TYPE | VTD_PRD_PP(true) | VTD_PRD_RID(sid) |
- VTD_PRD_PASID(vtd_as->pasid) | VTD_PRD_PMR(priv_req);
- desc.hi = VTD_PRD_RDR(is_read) | VTD_PRD_WRR(is_write) |
- VTD_PRD_LPIG(lpig) | VTD_PRD_PRGI(prgi) | VTD_PRD_ADDR(addr);
-
- desc.lo = cpu_to_le64(desc.lo);
- desc.hi = cpu_to_le64(desc.hi);
if (dma_memory_write(&address_space_memory, queue_tail, &desc, sizeof(desc),
MEMTXATTRS_UNSPECIFIED)) {
error_report_once("IO error, the PQ tail cannot be updated");
- return -EIO;
+ ret = -EIO;
+ goto out;
}
/* increment the tail register and set the pending request bit */
vtd_set_quad(s, DMAR_PQT_REG, new_queue_tail_offset);
- /*
- * read status again so that the kernel does not miss a request.
- * in some cases, we can trigger an unecessary interrupt but this strategy
- * drastically improves performance as we don't need to take a lock.
- */
- old_pr_status = vtd_get_long(s, DMAR_PRS_REG);
if (!(old_pr_status & VTD_PR_STATUS_PPR)) {
vtd_set_clear_mask_long(s, DMAR_PRS_REG, 0, VTD_PR_STATUS_PPR);
vtd_generate_page_request_event(s, old_pr_status);
}
- return 0;
+out:
+ vtd_iommu_unlock(s);
+ return ret;
}
static void vtd_init_iotlb_notifier(PCIBus *bus, void *opaque, int devfn,