Oh, yep, thanks for the effort.
Do you mind if we go this route for the next iteration?
Maybe we could add a comment on vtd_pasid_to_pci_pasid to explain why and when
this translation is required so that future developers can find the information
without digging into the mailing list or git history.
On Mon, 2026-04-27 at 06:56 +0000, Duan, Zhenzhong wrote:
> Hi Clement,
>
> I see your pains. How about below cleanup?
> It introduces a converting function vtd_pasid_to_pci_pasid(),
> then the check always is on PCI pasid with vtd_as->pasid
>
> --- a/hw/i386/intel_iommu.c
> +++ b/hw/i386/intel_iommu.c
> @@ -98,6 +98,14 @@ static void vtd_pasid_cache_reset_locked(IntelIOMMUState
> *s)
> }
> }
>
> +static uint32_t vtd_pasid_to_pci_pasid(uint32_t pasid)
> +{
> + if (pasid == PASID_0) {
> + pasid = PCI_NO_PASID;
> + }
> + return pasid;
> +}
> +
> static void vtd_define_quad(IntelIOMMUState *s, hwaddr addr, uint64_t val,
> uint64_t wmask, uint64_t w1cmask)
> {
> @@ -2506,9 +2514,10 @@ static void
> vtd_iotlb_domain_invalidate(IntelIOMMUState *s, uint16_t domain_id)
> }
>
> /*
> - * There is no pasid field in iotlb invalidation descriptor, so PCI_NO_PASID
>
> + * There is no pasid field in iotlb invalidation descriptor, so PASID_0
> * is passed as parameter. Piotlb invalidation supports pasid, pasid in its
> - * descriptor is passed which should not be PCI_NO_PASID.
> + * descriptor is passed. In both cases, pasid is converted to PCI pasid
> + * before checking with vtd_as->pasid.
> */
> static void vtd_iotlb_page_invalidate_notify(IntelIOMMUState *s,
> uint16_t domain_id, hwaddr
> addr,
> @@ -2519,51 +2528,45 @@ static void
> vtd_iotlb_page_invalidate_notify(IntelIOMMUState *s,
> int ret;
> hwaddr size = (1 << am) * VTD_PAGE_SIZE;
>
> + pasid = vtd_pasid_to_pci_pasid(pasid);
> +
> QLIST_FOREACH(vtd_as, &(s->vtd_as_with_notifiers), next) {
> ret = vtd_dev_to_context_entry(s, pci_bus_num(vtd_as->bus),
> vtd_as->devfn, &ce);
> - if (!ret && domain_id == vtd_get_domain_id(s, &ce, vtd_as->pasid)) {
>
> + if (ret || vtd_as->pasid != pasid ||
> + domain_id != vtd_get_domain_id(s, &ce, vtd_as->pasid)) {
> + continue;
> + }
> +
> + if (vtd_as_has_map_notifier(vtd_as)) {
> /*
> - * In legacy mode, vtd_as->pasid == pasid is always true.
> - * In scalable mode, for vtd address space backing a PCI
> - * device without pasid, needs to compare pasid with
> - * PASID_0 of this device.
> + * When first stage translation is off, as long as we have MAP
> + * notifications registered in any of our IOMMU notifiers,
> + * we need to sync the shadow page table. Otherwise VFIO
> + * device attaches to nested page table instead of shadow
> + * page table, so no need to sync.
> */
> - if (!(vtd_as->pasid == pasid ||
> - (vtd_as->pasid == PCI_NO_PASID && pasid == PASID_0))) {
> - continue;
> - }
> -
> - if (vtd_as_has_map_notifier(vtd_as)) {
> - /*
> - * When first stage translation is off, as long as we have
> MAP
> - * notifications registered in any of our IOMMU notifiers,
> - * we need to sync the shadow page table. Otherwise VFIO
> - * device attaches to nested page table instead of shadow
> - * page table, so no need to sync.
> - */
> - if (!s->fsts || !s->root_scalable) {
> - vtd_sync_shadow_page_table_range(vtd_as, &ce, addr,
> size);
> - }
> - } else {
> - /*
> - * For UNMAP-only notifiers, we don't need to walk the
> - * page tables. We just deliver the PSI down to
> - * invalidate caches.
> - */
> - const IOMMUTLBEvent event = {
> - .type = IOMMU_NOTIFIER_UNMAP,
> - .entry = {
> - .target_as = &address_space_memory,
> - .iova = addr,
> - .translated_addr = 0,
> - .addr_mask = size - 1,
> - .perm = IOMMU_NONE,
> - .pasid = vtd_as->pasid,
> - },
> - };
> - memory_region_notify_iommu(&vtd_as->iommu, 0, event);
> + if (!s->fsts || !s->root_scalable) {
> + vtd_sync_shadow_page_table_range(vtd_as, &ce, addr, size);
> }
> + } else {
> + /*
> + * For UNMAP-only notifiers, we don't need to walk the
> + * page tables. We just deliver the PSI down to
> + * invalidate caches.
> + */
> + const IOMMUTLBEvent event = {
> + .type = IOMMU_NOTIFIER_UNMAP,
> + .entry = {
> + .target_as = &address_space_memory,
> + .iova = addr,
> + .translated_addr = 0,
> + .addr_mask = size - 1,
> + .perm = IOMMU_NONE,
> + .pasid = vtd_as->pasid,
> + },
> + };
> + memory_region_notify_iommu(&vtd_as->iommu, 0, event);
> }
> }
> }
> @@ -2582,7 +2585,7 @@ static void vtd_iotlb_page_invalidate(IntelIOMMUState
> *s, uint16_t domain_id,
> vtd_iommu_lock(s);
> g_hash_table_foreach_remove(s->iotlb, vtd_hash_remove_by_page, &info);
> vtd_iommu_unlock(s);
> - vtd_iotlb_page_invalidate_notify(s, domain_id, addr, am, PCI_NO_PASID);
> + vtd_iotlb_page_invalidate_notify(s, domain_id, addr, am, PASID_0);
> }
>
> /* Flush IOTLB
> @@ -3020,12 +3023,17 @@ static gboolean vtd_hash_remove_by_pasid(gpointer
> key, gpointer value,
> (entry->pasid == info->pasid));
> }
>
> +/*
> + * Piotlb invalidation supports pasid, pasid in its descriptor is passed and
>
> + * is converted to PCI pasid before checking with vtd_as->pasid.
> + */
> static void vtd_piotlb_pasid_invalidate(IntelIOMMUState *s,
> uint16_t domain_id, uint32_t pasid)
> {
> VTDIOTLBPageInvInfo info;
> VTDAddressSpace *vtd_as;
> VTDContextEntry ce;
> + int ret;
>
> info.domain_id = domain_id;
> info.pasid = pasid;
> @@ -3037,18 +3045,18 @@ static void
> vtd_piotlb_pasid_invalidate(IntelIOMMUState *s,
> false);
> vtd_iommu_unlock(s);
>
> + pasid = vtd_pasid_to_pci_pasid(pasid);
> +
> QLIST_FOREACH(vtd_as, &s->vtd_as_with_notifiers, next) {
> - if (!vtd_dev_to_context_entry(s, pci_bus_num(vtd_as->bus),
> - vtd_as->devfn, &ce) &&
> - domain_id == vtd_get_domain_id(s, &ce, vtd_as->pasid)) {
> - if ((vtd_as->pasid != PCI_NO_PASID || pasid != PASID_0) &&
> - vtd_as->pasid != pasid) {
> - continue;
> - }
> + ret = vtd_dev_to_context_entry(s, pci_bus_num(vtd_as->bus),
> + vtd_as->devfn, &ce);
> + if (ret || vtd_as->pasid != pasid ||
> + domain_id != vtd_get_domain_id(s, &ce, vtd_as->pasid)) {
> + continue;
> + }
>
> - if (!s->fsts || !vtd_as_has_map_notifier(vtd_as)) {
> - vtd_address_space_sync(vtd_as);
> - }
> + if (!s->fsts || !vtd_as_has_map_notifier(vtd_as)) {
> + vtd_address_space_sync(vtd_as);
> }
> }
> }
>
> Thanks
> Zhenzhong
>
>
>
> > -----Original Message-----
> > From: Clément MATHIEU--DRIF
> > <[[email protected]](mailto:[email protected])>
> > Subject: Re: [PATCH v3 07/14] intel_iommu: Use IOMMU_NO_PASID and delete
> > PASID_0
> >
> > Hi Zhenzhong, sorry for the delay, a bit busy these days o.O
> >
> > Thanks for sharing the rationale.
> >
> > I see the purpose but I'm still a bit reluctant to the idea of mixing
> > worlds too much.
> > I think might be error prone and/or difficult to understand for newcomers.
> >
> > For instance, the following statement has a lot of implicit complexity:
> >
> > ```c
> > if (!(vtd_as->pasid == pasid ||
> > (vtd_as->pasid == PCI_NO_PASID && pasid ==
> > IOMMU_NO_PASID))) {
> > continue;
> > ```
> >
> > Couldn't we find a design that makes the code a bit more homogeneous?
> >
> > Thanks
> >
> >
> > On Mon, 2026-04-13 at 07:44 +0000, Duan, Zhenzhong wrote:
> >
> > > Oh, sorry, you asked PCI_NO_PASID and I read it as IOMMU_NO_PASID.
> > >
> > > My understanding is:
> > > PCI_NO_PASID(-1) is a definition in PCI sub-system, IOMMU_NO_PASID is used
> >
> > in
> >
> > > vIOMMU. We should not cross using them in different sub-systems.
> > > So need to translate PCI_NO_PASID to IOMMU_NO_PASID in cross sub-system
> > > communication.
> > >
> > > Thanks
> > > Zhenzhong
> > >
> > >
> > >
> > > > -----Original Message-----
> > > > From: Duan, Zhenzhong
> > > > Subject: RE: [PATCH v3 07/14] intel_iommu: Use IOMMU_NO_PASID and
> > >
> >
> > delete
> >
> > >
> > > > PASID_0
> > > >
> > > > Hi Clement,
> > > >
> > > > In kernel we defined it as:
> > > >
> > > > #define IOMMU_NO_PASID (0U) /* Reserved for DMA w/o PASID */
> > > >
> > > > In intel_pasid_get_entry(), IOMMU_NO_PASID is used to index the pasid
> > > > table
> > > > directly.
> > > >
> > > > In qemu, PASID_0 is used for DMA w/o PASID, so we think it's duplicated
> > > > with
> > > > IOMMU_NO_PASID.
> > > >
> > > > Thanks
> > > > Zhenzhong
> > > >
> > > >
> > > >
> > > > > -----Original Message-----
> > > > > From: Clément MATHIEU--DRIF <[clement.mathieu--
> > > >
> > >
> >
> > [[email protected]](mailto:[email protected])](mailto:[[email protected]](mailto:[email protected]))>
> >
> > >
> > > >
> > > > > Subject: Re: [PATCH v3 07/14] intel_iommu: Use IOMMU_NO_PASID and
> > > >
> > >
> >
> > delete
> >
> > >
> > > >
> > > > > PASID_0
> > > > >
> > > > > Hi Zhenzhong,
> > > > >
> > > > > Could you just clarify how this interracts with PCI_NO_PASID?
> > > > >
> > > > > No sure I understand properly :/
> > > > >
> > > > > On Thu, 2026-04-02 at 23:55 -0400, Zhenzhong Duan wrote:
> > > > >
> > > > >
> > > > > > In previous patch we introduced a global macro IOMMU_NO_PASID(0) for
> > > > > > Requests-without-PASID, this makes the local macro PASID_0
> > > > > > redundant.
> > > > > > Delete it and use IOMMU_NO_PASID instead.
> > > > > >
> > > > > > Suggested-by: Yi Liu
> > > > >
> > > >
> > >
> >
> > <[[[[email protected]](mailto:[email protected])](mailto:[[email protected]](mailto:[email protected]))](mailto:[[[email protected]](mailto:[email protected])](mailto:
> > [[email protected]](mailto:[email protected])))>
> >
> > >
> > > >
> > > > >
> > > > > > Signed-off-by: Zhenzhong Duan
> > > > >
> > > > >
> > > > >
> > > >
> > >
> >
> > <[[[[email protected]](mailto:[email protected])](mailto:[[email protected]](mailto:[email protected]))](mailto:[zhen
> > [[email protected]](mailto:[email protected])](mailto:[[email protected]](mailto:[email protected])))>
> >
> > >
> > > >
> > > > >
> > > > >
> > > > > > Tested-by: Xudong Hao
> > > > >
> > > > >
> > > > >
> > > >
> > >
> >
> > <[[[[email protected]](mailto:[email protected])](mailto:[[email protected]](mailto:[email protected]))](mailto:[xudong.hao@i
> > ntel.com](mailto:[[email protected]](mailto:[email protected])))>
> >
> > >
> > > >
> > > > >
> > > > >
> > > > > > ---
> > > > > > hw/i386/intel_iommu_internal.h | 1 -
> > > > > > hw/i386/intel_iommu.c | 18 +++++++++---------
> > > > > > hw/i386/intel_iommu_accel.c | 2 +-
> > > > > > 3 files changed, 10 insertions(+), 11 deletions(-)
> > > > > >
> > > > > > diff --git a/hw/i386/intel_iommu_internal.h
> > > > >
> > > >
> > >
> >
> > b/hw/i386/intel_iommu_internal.h
> >
> > >
> > > >
> > > > >
> > > > > > index c7e107fe87..0141316f83 100644
> > > > > > --- a/hw/i386/intel_iommu_internal.h
> > > > > > +++ b/hw/i386/intel_iommu_internal.h
> > > > > > @@ -615,7 +615,6 @@ typedef struct VTDRootEntry VTDRootEntry;
> > > > > > #define VTD_CTX_ENTRY_LEGACY_SIZE 16
> > > > > > #define VTD_CTX_ENTRY_SCALABLE_SIZE 32
> > > > > >
> > > > > > -#define PASID_0 0
> > > > > > #define VTD_SM_CONTEXT_ENTRY_RSVD_VAL0(aw) (0x1e0ULL |
> > > > >
> > > > >
> > > > > ~VTD_HAW_MASK(aw))
> > > > >
> > > > >
> > > > > > #define VTD_SM_CONTEXT_ENTRY_RSVD_VAL1 0xffffffffffe00000ULL
> > > > > > #define VTD_SM_CONTEXT_ENTRY_PRE 0x10ULL
> > > > > > diff --git a/hw/i386/intel_iommu.c b/hw/i386/intel_iommu.c
> > > > > > index aa67c89e73..3b8d3a96d2 100644
> > > > > > --- a/hw/i386/intel_iommu.c
> > > > > > +++ b/hw/i386/intel_iommu.c
> > > > > > @@ -941,7 +941,7 @@ int vtd_ce_get_pasid_entry(IntelIOMMUState *s,
> > > > >
> > > > >
> > > > > VTDContextEntry *ce,
> > > > >
> > > > >
> > > > > > dma_addr_t pasid_dir_base;
> > > > > >
> > > > > > if (pasid == PCI_NO_PASID) {
> > > > > > - pasid = PASID_0;
> > > > > > + pasid = IOMMU_NO_PASID;
> > > > > > }
> > > > > > pasid_dir_base = VTD_CE_GET_PASID_DIR_TABLE(ce);
> > > > > > return vtd_get_pe_from_pasid_table(s, pasid_dir_base, pasid,
> > > > > > pe);
> > > > > > @@ -958,7 +958,7 @@ static int vtd_ce_get_pasid_fpd(IntelIOMMUState
> > > > >
> > > >
> > >
> >
> > *s,
> >
> > >
> > > >
> > > > >
> > > > > > VTDPASIDEntry pe;
> > > > > >
> > > > > > if (pasid == PCI_NO_PASID) {
> > > > > > - pasid = PASID_0;
> > > > > > + pasid = IOMMU_NO_PASID;
> > > > > > }
> > > > > > pasid_dir_base = VTD_CE_GET_PASID_DIR_TABLE(ce);
> > > > > >
> > > > > > @@ -1501,9 +1501,9 @@ static int
> > > > >
> > > >
> > >
> >
> > vtd_ce_pasid_0_check(IntelIOMMUState
> >
> > >
> > > >
> > > > >
> > > >
> > > >
> > > > *s,
> > > >
> > > >
> > > > > VTDContextEntry *ce)
> > > > >
> > > > >
> > > > > >
> > > > > > /*
> > > > > > * Make sure in Scalable Mode, a present context entry
> > > > > > - * has valid pasid entry setting at PASID_0.
> > > > > > + * has valid pasid entry setting at IOMMU_NO_PASID.
> > > > > > */
> > > > > > - return vtd_ce_get_pasid_entry(s, ce, &pe, PASID_0);
> > > > > > + return vtd_ce_get_pasid_entry(s, ce, &pe, IOMMU_NO_PASID);
> > > > > > }
> > > > > >
> > > > > > /* Map a device to its corresponding domain (context-entry) */
> > > > > > @@ -1564,7 +1564,7 @@ int vtd_dev_to_context_entry(IntelIOMMUState
> > > > >
> > > >
> > >
> >
> > *s,
> >
> > >
> > > >
> > > > >
> > > > > uint8_t bus_num,
> > > > >
> > > > >
> > > > > > }
> > > > > > } else {
> > > > > > /*
> > > > > > - * Check if the programming of pasid setting of PASID_0
> > > > > > + * Check if the programming of pasid setting of
> > > > > > IOMMU_NO_PASID
> > > > > > * is valid, and thus avoids to check pasid entry fetching
> > > > > > * result in future helper function calling.
> > > > > > */
> > > > > > @@ -2122,7 +2122,7 @@ static bool
> > > > >
> > > > >
> > > > > vtd_do_iommu_translate(VTDAddressSpace *vtd_as, PCIBus *bus,
> > > > >
> > > > >
> > > > > > vtd_iommu_lock(s);
> > > > > >
> > > > > > if (pasid == PCI_NO_PASID && s->root_scalable) {
> > > > > > - pasid = PASID_0;
> > > > > > + pasid = IOMMU_NO_PASID;
> > > > > > }
> > > > > >
> > > > > > /* Try to fetch pte from IOTLB */
> > > > > > @@ -2508,10 +2508,10 @@ static void
> > > > >
> > > > >
> > > > > vtd_iotlb_page_invalidate_notify(IntelIOMMUState *s,
> > > > >
> > > > >
> > > > > > * In legacy mode, vtd_as->pasid == pasid is always
> > > > > > true.
> > > > > > * In scalable mode, for vtd address space backing a
> > > > > > PCI
> > > > > > * device without pasid, needs to compare pasid with
> > > > > > - * PASID_0 of this device.
> > > > > > + * IOMMU_NO_PASID of this device.
> > > > > > */
> > > > > > if (!(vtd_as->pasid == pasid ||
> > > > > > - (vtd_as->pasid == PCI_NO_PASID && pasid ==
> > > > > > PASID_0))) {
> > > > > > + (vtd_as->pasid == PCI_NO_PASID && pasid ==
> > > > >
> > > > >
> > > >
> > > >
> > > > IOMMU_NO_PASID)))
> > > >
> > > >
> > > > > {
> > > > >
> > > > >
> > > > > > continue;
> > > > > > }
> > > > > >
> > > > > > @@ -3022,7 +3022,7 @@ static void
> > > > >
> > > > >
> > > > > vtd_piotlb_pasid_invalidate(IntelIOMMUState *s,
> > > > >
> > > > >
> > > > > > if (!vtd_dev_to_context_entry(s, pci_bus_num(vtd_as->bus),
> > > > > > vtd_as->devfn, &ce) &&
> > > > > > domain_id == vtd_get_domain_id(s, &ce, vtd_as->pasid))
> > > > > > {
> > > > > > - if ((vtd_as->pasid != PCI_NO_PASID || pasid !=
> > > > > > PASID_0) &&
> > > > > > + if ((vtd_as->pasid != PCI_NO_PASID || pasid !=
> > > > >
> > > >
> > >
> >
> > IOMMU_NO_PASID) &&
> >
> > >
> > > >
> > > > >
> > > > > > vtd_as->pasid != pasid) {
> > > > > > continue;
> > > > > > }
> > > > > > diff --git a/hw/i386/intel_iommu_accel.c
> > > > > > b/hw/i386/intel_iommu_accel.c
> > > > > > index bd1236c070..8940d240a1 100644
> > > > > > --- a/hw/i386/intel_iommu_accel.c
> > > > > > +++ b/hw/i386/intel_iommu_accel.c
> > > > > > @@ -217,7 +217,7 @@ static void
> > > > > > vtd_flush_host_piotlb_locked(gpointer
> > > > >
> > > >
> > >
> >
> > key,
> >
> > >
> > > >
> > > > >
> > > > > gpointer value,
> > > > >
> > > > >
> > > > > >
> > > > > > did = VTD_SM_PASID_ENTRY_DID(&pc_entry->pasid_entry);
> > > > > >
> > > > > > - if (piotlb_info->domain_id == did && piotlb_info->pasid ==
> > > > > > PASID_0) {
> > > > > > + if (piotlb_info->domain_id == did && piotlb_info->pasid ==
> > > > >
> > > > >
> > > > > IOMMU_NO_PASID) {
> > > > >
> > > > >
> > > > > > HostIOMMUDeviceIOMMUFD *hiodi =
> > > > > > HOST_IOMMU_DEVICE_IOMMUFD(vtd_hiod->hiod);
> > > > > > uint32_t entry_num = 1; /* Only implement one request for
> > > > > > simplicity
> > > > >
> > > >
> > >
> >
> > */
> >
> > >
> > > >
> > > > >
> > > >
> > > >
> > >
> > >
> >
>