@samitouri / QOSamiQemu / commits / c579197d6b

intel_iommu: Refactor PASID processing to use IOMMU_NO_PASID internally

The PCI subsystem uses PCI_NO_PASID for requests-without-PASID, but VT-d emulation uses IOMMU_NO_PASID internally (ecap.RPS==0). This leads to conversion and checking code between PCI_NO_PASID and IOMMU_NO_PASID throughout the implementation. Refactor to use IOMMU PASID consistently within Intel IOMMU by storing IOMMU PASID value in vtd_as->pasid. After this change, PCI_NO_PASID is only used at three boundary points: 1. PCI_NO_PASID -> IOMMU_NO_PASID: Convert PCI PASID to IOMMU PASID in vtd_find_add_as() and cache in vtd_as->pasid. 2. IOMMU_NO_PASID -> PCI_NO_PASID: Convert when notifying UNMAP events via memory_region_notify_iommu() and returning IOMMUTLBEntry in vtd_iommu_translate(). This eliminates conversion/checks in PASID table lookups, simplifies invalidation logic with consistent PASID values, and improves code readability. The PCI subsystem interface remains unchanged to maintain compatibility with other IOMMU implementations that may not use PASID 0 for requests-without-PASID. Suggested-by: Clement Mathieu--Drif <clement.mathieu--drif@bull.com> Signed-off-by: Zhenzhong Duan <zhenzhong.duan@intel.com> Reviewed-by: Michael S. Tsirkin <mst@redhat.com> Signed-off-by: Michael S. Tsirkin <mst@redhat.com> Message-Id: <20260527054658.1021096-11-zhenzhong.duan@intel.com>

Zhenzhong Duan committed May 27, 2026 at 01:46 UTC c579197d6be77be90607d5d59e8e7f68696dfa26
3 files changed +80 -88
hw/i386/intel_iommu.c
+78 -86
@@ -938,12 +938,8 @@ static int vtd_get_pe_from_pasid_table(IntelIOMMUState *s,
938 static int vtd_ce_get_pasid_entry(IntelIOMMUState *s, VTDContextEntry *ce,
939 VTDPASIDEntry *pe, uint32_t pasid)
940 {
941 - dma_addr_t pasid_dir_base;
941 + dma_addr_t pasid_dir_base = VTD_CE_GET_PASID_DIR_TABLE(ce);
942
943 - if (pasid == PCI_NO_PASID) {
944 - pasid = IOMMU_NO_PASID;
945 - }
946 - pasid_dir_base = VTD_CE_GET_PASID_DIR_TABLE(ce);
943 return vtd_get_pe_from_pasid_table(s, pasid_dir_base, pasid, pe);
944 }
945
@@ -953,15 +949,10 @@ static int vtd_ce_get_pasid_fpd(IntelIOMMUState *s,
949 uint32_t pasid)
950 {
951 int ret;
956 - dma_addr_t pasid_dir_base;
952 + dma_addr_t pasid_dir_base = VTD_CE_GET_PASID_DIR_TABLE(ce);
953 VTDPASIDDirEntry pdire;
954 VTDPASIDEntry pe;
955
960 - if (pasid == PCI_NO_PASID) {
961 - pasid = IOMMU_NO_PASID;
962 - }
963 - pasid_dir_base = VTD_CE_GET_PASID_DIR_TABLE(ce);
964 -
956 /*
957 * No present bit check since fpd is meaningful even
958 * if the present bit is clear.
@@ -1750,7 +1741,7 @@ static bool vtd_switch_address_space(VTDAddressSpace *as)
1741 *
1742 * Need to disable ir for as with PASID.
1743 */
1753 - if (as->pasid != PCI_NO_PASID) {
1744 + if (as->pasid != IOMMU_NO_PASID) {
1745 memory_region_set_enabled(&as->iommu_ir, false);
1746 } else {
1747 memory_region_set_enabled(&as->iommu_ir, true);
@@ -1780,7 +1771,7 @@ static bool vtd_switch_address_space(VTDAddressSpace *as)
1771 * We enable per as memory region (iommu_ir_fault) for catching
1772 * the translation for interrupt range through PASID + PT.
1773 */
1783 - if (pt && as->pasid != PCI_NO_PASID) {
1774 + if (pt && as->pasid != IOMMU_NO_PASID) {
1775 memory_region_set_enabled(&as->iommu_ir_fault, true);
1776 } else {
1777 memory_region_set_enabled(&as->iommu_ir_fault, false);
@@ -1892,7 +1883,7 @@ static VTDAddressSpace *vtd_get_as_by_sid_and_pasid(IntelIOMMUState *s,
1883
1884 VTDAddressSpace *vtd_get_as_by_sid(IntelIOMMUState *s, uint16_t sid)
1885 {
1895 - return vtd_get_as_by_sid_and_pasid(s, sid, PCI_NO_PASID);
1886 + return vtd_get_as_by_sid_and_pasid(s, sid, IOMMU_NO_PASID);
1887 }
1888
1889 static void vtd_pt_enable_fast_path(IntelIOMMUState *s, uint16_t source_id)
@@ -2109,7 +2100,7 @@ static bool vtd_do_iommu_translate(VTDAddressSpace *vtd_as, PCIBus *bus,
2100 bool is_fpd_set = false;
2101 bool reads = true;
2102 bool writes = true;
2112 - bool is_pasid = pasid != PCI_NO_PASID;
2103 + bool is_pasid = pasid != IOMMU_NO_PASID;
2104 uint8_t access_flags, pgtt;
2105 VTDIOTLBEntry *iotlb_entry;
2106 uint64_t xlat, size;
@@ -2122,10 +2113,6 @@ static bool vtd_do_iommu_translate(VTDAddressSpace *vtd_as, PCIBus *bus,
2113
2114 vtd_iommu_lock(s);
2115
2125 - if (pasid == PCI_NO_PASID && s->root_scalable) {
2126 - pasid = IOMMU_NO_PASID;
2127 - }
2128 -
2116 /* Try to fetch pte from IOTLB */
2117 iotlb_entry = vtd_lookup_iotlb(s, source_id, pasid, addr);
2118 if (iotlb_entry) {
@@ -2490,7 +2477,7 @@ static void vtd_iotlb_domain_invalidate(IntelIOMMUState *s, uint16_t domain_id)
2477 /*
2478 * There is no pasid field in iotlb invalidation descriptor, so IOMMU_NO_PASID
2479 * is passed as parameter. Piotlb invalidation supports pasid, pasid in its
2493 - * descriptor is passed which should not be PCI_NO_PASID.
2480 + * descriptor is passed.
2481 */
2482 static void vtd_iotlb_page_invalidate_notify(IntelIOMMUState *s,
2483 uint16_t domain_id, hwaddr addr,
@@ -2504,48 +2491,41 @@ static void vtd_iotlb_page_invalidate_notify(IntelIOMMUState *s,
2491 QLIST_FOREACH(vtd_as, &(s->vtd_as_with_notifiers), next) {
2492 ret = vtd_dev_to_context_entry(s, pci_bus_num(vtd_as->bus),
2493 vtd_as->devfn, &ce);
2507 - if (!ret && domain_id == vtd_get_domain_id(s, &ce, vtd_as->pasid)) {
2494 + if (ret || vtd_as->pasid != pasid ||
2495 + domain_id != vtd_get_domain_id(s, &ce, pasid)) {
2496 + continue;
2497 + }
2498 +
2499 + if (vtd_as_has_map_notifier(vtd_as)) {
2500 /*
2509 - * In legacy mode, vtd_as->pasid == pasid is always true.
2510 - * In scalable mode, for vtd address space backing a PCI
2511 - * device without pasid, needs to compare pasid with
2512 - * IOMMU_NO_PASID of this device.
2501 + * When first stage translation is off, as long as we have MAP
2502 + * notifications registered in any of our IOMMU notifiers,
2503 + * we need to sync the shadow page table. Otherwise VFIO
2504 + * device attaches to nested page table instead of shadow
2505 + * page table, so no need to sync.
2506 */
2514 - if (!(vtd_as->pasid == pasid ||
2515 - (vtd_as->pasid == PCI_NO_PASID && pasid == IOMMU_NO_PASID))) {
2516 - continue;
2517 - }
2518 -
2519 - if (vtd_as_has_map_notifier(vtd_as)) {
2520 - /*
2521 - * When first stage translation is off, as long as we have MAP
2522 - * notifications registered in any of our IOMMU notifiers,
2523 - * we need to sync the shadow page table. Otherwise VFIO
2524 - * device attaches to nested page table instead of shadow
2525 - * page table, so no need to sync.
2526 - */
2527 - if (!s->fsts || !s->root_scalable) {
2528 - vtd_sync_shadow_page_table_range(vtd_as, &ce, addr, size);
2529 - }
2530 - } else {
2531 - /*
2532 - * For UNMAP-only notifiers, we don't need to walk the
2533 - * page tables. We just deliver the PSI down to
2534 - * invalidate caches.
2535 - */
2536 - const IOMMUTLBEvent event = {
2537 - .type = IOMMU_NOTIFIER_UNMAP,
2538 - .entry = {
2539 - .target_as = &address_space_memory,
2540 - .iova = addr,
2541 - .translated_addr = 0,
2542 - .addr_mask = size - 1,
2543 - .perm = IOMMU_NONE,
2544 - .pasid = vtd_as->pasid,
2545 - },
2546 - };
2547 - memory_region_notify_iommu(&vtd_as->iommu, 0, event);
2507 + if (!s->fsts || !s->root_scalable) {
2508 + vtd_sync_shadow_page_table_range(vtd_as, &ce, addr, size);
2509 }
2510 + } else {
2511 + /*
2512 + * For UNMAP-only notifiers, we don't need to walk the
2513 + * page tables. We just deliver the PSI down to
2514 + * invalidate caches.
2515 + */
2516 + const IOMMUTLBEvent event = {
2517 + .type = IOMMU_NOTIFIER_UNMAP,
2518 + .entry = {
2519 + .target_as = &address_space_memory,
2520 + .iova = addr,
2521 + .translated_addr = 0,
2522 + .addr_mask = size - 1,
2523 + .perm = IOMMU_NONE,
2524 + /* Other sub-systems use PCI pasid */
2525 + .pasid = pasid == IOMMU_NO_PASID ? PCI_NO_PASID : pasid,
2526 + },
2527 + };
2528 + memory_region_notify_iommu(&vtd_as->iommu, 0, event);
2529 }
2530 }
2531 }
@@ -3008,6 +2988,7 @@ static void vtd_piotlb_pasid_invalidate(IntelIOMMUState *s,
2988 VTDIOTLBPageInvInfo info;
2989 VTDAddressSpace *vtd_as;
2990 VTDContextEntry ce;
2991 + int ret;
2992
2993 info.domain_id = domain_id;
2994 info.pasid = pasid;
@@ -3020,17 +3001,15 @@ static void vtd_piotlb_pasid_invalidate(IntelIOMMUState *s,
3001 vtd_iommu_unlock(s);
3002
3003 QLIST_FOREACH(vtd_as, &s->vtd_as_with_notifiers, next) {
3023 - if (!vtd_dev_to_context_entry(s, pci_bus_num(vtd_as->bus),
3024 - vtd_as->devfn, &ce) &&
3025 - domain_id == vtd_get_domain_id(s, &ce, vtd_as->pasid)) {
3026 - if ((vtd_as->pasid != PCI_NO_PASID || pasid != IOMMU_NO_PASID) &&
3027 - vtd_as->pasid != pasid) {
3028 - continue;
3029 - }
3004 + ret = vtd_dev_to_context_entry(s, pci_bus_num(vtd_as->bus),
3005 + vtd_as->devfn, &ce);
3006 + if (ret || vtd_as->pasid != pasid ||
3007 + domain_id != vtd_get_domain_id(s, &ce, pasid)) {
3008 + continue;
3009 + }
3010
3031 - if (!s->fsts || !vtd_as_has_map_notifier(vtd_as)) {
3032 - vtd_address_space_sync(vtd_as);
3033 - }
3011 + if (!s->fsts || !vtd_as_has_map_notifier(vtd_as)) {
3012 + vtd_address_space_sync(vtd_as);
3013 }
3014 }
3015 }
@@ -3239,7 +3218,7 @@ static bool vtd_process_pasid_desc(IntelIOMMUState *s,
3218 /* PASID selective implies a DID selective */
3219 trace_vtd_inv_desc_pasid_cache_psi(did, pasid);
3220 pc_info.did = did;
3242 - pc_info.pasid = pasid ?: PCI_NO_PASID;
3221 + pc_info.pasid = pasid;
3222 break;
3223
3224 case VTD_INV_DESC_PASIDC_G_GLOBAL:
@@ -3291,6 +3270,7 @@ static void do_invalidate_device_tlb(VTDAddressSpace *vtd_dev_as,
3270 * ...
3271 */
3272
3273 + uint32_t pasid = vtd_dev_as->pasid;
3274 IOMMUTLBEvent event;
3275 uint64_t sz;
3276
@@ -3307,7 +3287,8 @@ static void do_invalidate_device_tlb(VTDAddressSpace *vtd_dev_as,
3287 event.entry.iova = addr;
3288 event.entry.perm = IOMMU_NONE;
3289 event.entry.translated_addr = 0;
3310 - event.entry.pasid = vtd_dev_as->pasid;
3290 + /* Other sub-systems use PCI pasid */
3291 + event.entry.pasid = pasid == IOMMU_NO_PASID ? PCI_NO_PASID : pasid;
3292 memory_region_notify_iommu(&vtd_dev_as->iommu, 0, event);
3293 }
3294
@@ -3335,7 +3316,7 @@ static bool vtd_process_device_piotlb_desc(IntelIOMMUState *s,
3316 sid = VTD_INV_DESC_PASID_DEVICE_IOTLB_SID(inv_desc->lo);
3317 if (global) {
3318 QLIST_FOREACH(vtd_dev_as, &s->vtd_as_with_notifiers, next) {
3338 - if ((vtd_dev_as->pasid != PCI_NO_PASID) &&
3319 + if ((vtd_dev_as->pasid != IOMMU_NO_PASID) &&
3320 (PCI_BUILD_BDF(pci_bus_num(vtd_dev_as->bus),
3321 vtd_dev_as->devfn) == sid)) {
3322 do_invalidate_device_tlb(vtd_dev_as, size, addr);
@@ -3983,13 +3964,12 @@ static void vtd_mem_write(void *opaque, hwaddr addr,
3964 }
3965
3966 static void vtd_prepare_identity_entry(hwaddr addr, IOMMUAccessFlags perm,
3986 - uint32_t pasid, IOMMUTLBEntry *iotlb)
3967 + IOMMUTLBEntry *iotlb)
3968 {
3969 iotlb->iova = addr & VTD_PAGE_MASK_4K;
3970 iotlb->translated_addr = addr & VTD_PAGE_MASK_4K;
3971 iotlb->addr_mask = ~VTD_PAGE_MASK_4K;
3972 iotlb->perm = perm;
3992 - iotlb->pasid = pasid;
3973 }
3974
3975 static inline void vtd_prepare_error_entry(IOMMUTLBEntry *entry)
@@ -4001,6 +3981,10 @@ static inline void vtd_prepare_error_entry(IOMMUTLBEntry *entry)
3981 entry->pasid = PCI_NO_PASID;
3982 }
3983
3984 +/*
3985 + * This function returns translation result to other sub-system such as PCI,
3986 + * so iommu pasid is converted to PCI pasid and returned in IOMMUTLBEntry.
3987 + */
3988 static IOMMUTLBEntry vtd_iommu_translate(IOMMUMemoryRegion *iommu, hwaddr addr,
3989 IOMMUAccessFlags flag, int iommu_idx)
3990 {
@@ -4009,7 +3993,7 @@ static IOMMUTLBEntry vtd_iommu_translate(IOMMUMemoryRegion *iommu, hwaddr addr,
3993 IOMMUTLBEntry iotlb = {
3994 /* We'll fill in the rest later. */
3995 .target_as = &address_space_memory,
4012 - .pasid = vtd_as->pasid,
3996 + .pasid = vtd_as->pasid == IOMMU_NO_PASID ? PCI_NO_PASID : vtd_as->pasid,
3997 };
3998 bool success;
3999 bool is_write = flag & IOMMU_WO;
@@ -4017,9 +4001,8 @@ static IOMMUTLBEntry vtd_iommu_translate(IOMMUMemoryRegion *iommu, hwaddr addr,
4001 if (likely(s->dmar_enabled)) {
4002 /* Only support translated requests in scalable mode */
4003 if (iommu_idx == VTD_IDX_TRANSLATED && s->root_scalable) {
4020 - if (vtd_as->pasid == PCI_NO_PASID) {
4021 - vtd_prepare_identity_entry(addr, IOMMU_RW, PCI_NO_PASID,
4022 - &iotlb);
4004 + if (vtd_as->pasid == IOMMU_NO_PASID) {
4005 + vtd_prepare_identity_entry(addr, IOMMU_RW, &iotlb);
4006 success = true;
4007 } else {
4008 vtd_prepare_error_entry(&iotlb);
@@ -4034,7 +4017,7 @@ static IOMMUTLBEntry vtd_iommu_translate(IOMMUMemoryRegion *iommu, hwaddr addr,
4017 }
4018 } else {
4019 /* DMAR disabled, passthrough, use 4k-page*/
4037 - vtd_prepare_identity_entry(addr, IOMMU_RW, vtd_as->pasid, &iotlb);
4020 + vtd_prepare_identity_entry(addr, IOMMU_RW, &iotlb);
4021 success = true;
4022 }
4023
@@ -4460,7 +4443,7 @@ static void vtd_report_sid_ir_illegal_access(IntelIOMMUState *s, uint16_t sid,
4443 }
4444
4445 vtd_report_fault(s, VTD_FR_SM_INTERRUPT_ADDR, is_fpd_set, sid, addr,
4463 - is_write, pasid != PCI_NO_PASID, pasid);
4446 + is_write, pasid != IOMMU_NO_PASID, pasid);
4447 }
4448
4449 static void vtd_report_ir_illegal_access(VTDAddressSpace *vtd_as,
@@ -4488,7 +4471,6 @@ static MemTxResult vtd_mem_ir_write(void *opaque, hwaddr addr,
4471 int ret = 0;
4472 MSIMessage from = {}, to = {};
4473 uint16_t sid = X86_IOMMU_SID_INVALID;
4491 - uint32_t pasid;
4474
4475 from.address = (uint64_t) addr + VTD_INTERRUPT_ADDR_FIRST;
4476 from.data = (uint32_t) value;
@@ -4496,11 +4478,11 @@ static MemTxResult vtd_mem_ir_write(void *opaque, hwaddr addr,
4478 if (!attrs.unspecified) {
4479 /* We have explicit Source ID */
4480 sid = attrs.requester_id;
4499 - pasid = attrs.pid != 0 ? attrs.pid : PCI_NO_PASID;
4481
4482 if (attrs.address_type == PCI_AT_TRANSLATED &&
4483 sid != X86_IOMMU_SID_INVALID) {
4503 - vtd_report_sid_ir_illegal_access(s, sid, pasid, from.address, true);
4484 + vtd_report_sid_ir_illegal_access(s, sid, attrs.pid, from.address,
4485 + true);
4486 return MEMTX_ERROR;
4487 }
4488 }
@@ -4562,9 +4544,19 @@ static const MemoryRegionOps vtd_mem_ir_fault_ops = {
4544 },
4545 };
4546
4547 +/*
4548 + * This function is called by many PCIIOMMUOps callbacks to get
4549 + * VTDAddressSpace or create one if non-exist. Those callbacks are
4550 + * used by PCI sub-system and are passed in a PCI pasid value.
4551 + *
4552 + * VTD honors iommu pasid, so the first thing is to convert PCI
4553 + * pasid to iommu pasid.
4554 + */
4555 VTDAddressSpace *vtd_find_add_as(IntelIOMMUState *s, PCIBus *bus,
4556 int devfn, unsigned int pasid)
4557 {
4558 + pasid = pasid == PCI_NO_PASID ? IOMMU_NO_PASID : pasid;
4559 +
4560 /*
4561 * We can't simply use sid here since the bus number might not be
4562 * initialized by the guest.
@@ -4606,7 +4598,7 @@ VTDAddressSpace *vtd_find_add_as(IntelIOMMUState *s, PCIBus *bus,
4598 new_key->devfn = devfn;
4599 new_key->pasid = pasid;
4600
4609 - if (pasid == PCI_NO_PASID) {
4601 + if (pasid == IOMMU_NO_PASID) {
4602 snprintf(name, sizeof(name), "vtd-%02x.%x", PCI_SLOT(devfn),
4603 PCI_FUNC(devfn));
4604 } else {
@@ -5290,7 +5282,7 @@ error_get_fpd_and_report:
5282 vtd_ce_get_pasid_fpd(s, &ce, &is_fpd_set, vtd_as->pasid);
5283 error_report:
5284 vtd_report_fault(s, -ret, is_fpd_set, sid, addr, is_write,
5293 - vtd_as->pasid != PCI_NO_PASID, vtd_as->pasid);
5285 + vtd_as->pasid != IOMMU_NO_PASID, vtd_as->pasid);
5286 return false;
5287 }
5288
@@ -5381,7 +5373,7 @@ static int vtd_pri_request_page(PCIBus *bus, void *opaque, int devfn,
5373 */
5374
5375 /* We do not support PRI without PASID */
5384 - if (vtd_as->pasid == PCI_NO_PASID) {
5376 + if (vtd_as->pasid == IOMMU_NO_PASID) {
5377 return -EPERM;
5378 }
5379 if (exec_req && !is_read) {
hw/i386/intel_iommu_accel.c
+1 -1
@@ -207,7 +207,7 @@ static void vtd_flush_host_piotlb_locked(gpointer key, gpointer value,
207 return;
208 }
209
210 - assert(vtd_as->pasid == PCI_NO_PASID);
210 + assert(vtd_as->pasid == IOMMU_NO_PASID);
211
212 /* Nothing to do if there is no first stage HWPT attached */
213 if (!pc_entry->valid ||
include/system/memory.h
+1 -1
@@ -150,7 +150,7 @@ struct IOMMUTLBEntry {
150 hwaddr translated_addr;
151 hwaddr addr_mask; /* 0xfff = 4k translation */
152 IOMMUAccessFlags perm;
153 - uint32_t pasid;
153 + uint32_t pasid; /* PCI pasid */
154 };
155
156 /*