Re: [PATCH] hw/riscv/riscv-iommu: preserve requested perm in spa_fetch()

Alistair <[email protected]>
Newsgroups org.nongnu.qemu-riscv,org.nongnu.qemu-devel
Message-ID <[email protected]>
On Fri, 2026-07-17 at 16:45 +0200, Andrew Jones wrote:
> b18e3f0e2d0f fixed spa_fetch() faults whose TTYP used the leaf PTE
> permission instead of the original request permission.  However, it
> kept
> that request-narrowed value in iotlb->perm after a successful walk,
> and
> riscv_iommu_translate() caches iotlb->perm for later accesses to the
> same
> IOVA.
> 
> That means a write to an RW mapping can cache the entry as write-
> only.
> A later read then hits the cache and faults even though the mapping
> allows
> it, which showed up in NVMe testing as bogus completions and
> controller
> timeouts.
> 
> Keep the requested permission in a separate req_perm and use it for
> all
> permission checks and fault-type decisions.  Accumulate the leaf
> permissions separately and copy them to iotlb->perm only after the
> full
> walk succeeds, so cached entries describe the mapping rather than the
> current request.  Since faults leave iotlb->perm as the original
> request,
> the S-stage and G-stage TTYP fixes remain intact.
> 
> Fixes: b18e3f0e2d0f ("hw/riscv/riscv-iommu.c: fix fault type for
> spa_fetch() faults")
> Signed-off-by: Andrew Jones <[email protected]>

Thanks!

Applied to riscv-to-apply.next

Alistair

> ---
>  hw/riscv/riscv-iommu.c | 50 ++++++++++++++++++++++++----------------
> --
>  1 file changed, 29 insertions(+), 21 deletions(-)
> 
> diff --git a/hw/riscv/riscv-iommu.c b/hw/riscv/riscv-iommu.c
> index ed9fb09f8bc7..323a041b4a55 100644
> --- a/hw/riscv/riscv-iommu.c
> +++ b/hw/riscv/riscv-iommu.c
> @@ -281,7 +281,7 @@ static hwaddr riscv_iommu_napot_page_mask(hwaddr
> ppn, hwaddr addr, hwaddr *out)
>  static int riscv_iommu_spa_fetch(RISCVIOMMUState *s,
> RISCVIOMMUContext *ctx,
>      IOMMUTLBEntry *iotlb)
>  {
> -    IOMMUAccessFlags pte_perm;
> +    IOMMUAccessFlags trans_perm = IOMMU_NONE;
>      dma_addr_t addr, base;
>      uint64_t satp, gatp, pte;
>      bool en_s, en_g;
> @@ -298,6 +298,14 @@ static int riscv_iommu_spa_fetch(RISCVIOMMUState
> *s, RISCVIOMMUContext *ctx,
>      } pass;
>      MemTxResult ret;
>      bool pv = !!ctx->process_id;
> +    /*
> +     * Keep the request permission separate from iotlb->perm.  G-
> stage
> +     * walks translate S-stage PTE addresses before the real leaf is
> +     * reached, but permission checks and fault types must still use
> the
> +     * original request.  A successful walk leaves iotlb->perm with
> the
> +     * effective leaf permission for the translation cache.
> +     */
> +    const IOMMUAccessFlags req_perm = iotlb->perm;
>  
>      satp = get_field(ctx->satp, RISCV_IOMMU_ATP_MODE_FIELD);
>      gatp = get_field(ctx->gatp, RISCV_IOMMU_ATP_MODE_FIELD);
> @@ -316,7 +324,7 @@ static int riscv_iommu_spa_fetch(RISCVIOMMUState
> *s, RISCVIOMMUContext *ctx,
>       * means we can't do an early MSI check unless we have
>       * strictly !en_s.
>       */
> -    if (!en_s && (iotlb->perm & IOMMU_WO) &&
> +    if (!en_s && (req_perm & IOMMU_WO) &&
>          riscv_iommu_msi_check(s, ctx, iotlb->iova)) {
>          iotlb->target_as = &s->trap_as;
>          iotlb->translated_addr = iotlb->iova;
> @@ -434,13 +442,13 @@ static int
> riscv_iommu_spa_fetch(RISCVIOMMUState *s, RISCVIOMMUContext *ctx,
>                  masked_msbs = (addr >> (va_len - 1)) & mask;
>  
>                  if (masked_msbs != 0 && masked_msbs != mask) {
> -                    return (iotlb->perm & IOMMU_WO) ?
> +                    return (req_perm & IOMMU_WO) ?
>                                  RISCV_IOMMU_FQ_CAUSE_WR_FAULT_S :
>                                  RISCV_IOMMU_FQ_CAUSE_RD_FAULT_S;
>                  }
>              } else {
>                  if ((addr & va_mask) != addr) {
> -                    return (iotlb->perm & IOMMU_WO) ?
> +                    return (req_perm & IOMMU_WO) ?
>                                  RISCV_IOMMU_FQ_CAUSE_WR_FAULT_VS :
>                                  RISCV_IOMMU_FQ_CAUSE_RD_FAULT_VS;
>                  }
> @@ -465,8 +473,8 @@ static int riscv_iommu_spa_fetch(RISCVIOMMUState
> *s, RISCVIOMMUContext *ctx,
>                               MEMTXATTRS_UNSPECIFIED);
>          }
>          if (ret != MEMTX_OK) {
> -            return (iotlb->perm & IOMMU_WO) ?
> RISCV_IOMMU_FQ_CAUSE_WR_FAULT
> -                                            :
> RISCV_IOMMU_FQ_CAUSE_RD_FAULT;
> +            return (req_perm & IOMMU_WO) ?
> RISCV_IOMMU_FQ_CAUSE_WR_FAULT
> +                                         :
> RISCV_IOMMU_FQ_CAUSE_RD_FAULT;
>          }
>  
>          sc[pass].step++;
> @@ -491,13 +499,13 @@ static int
> riscv_iommu_spa_fetch(RISCVIOMMUState *s, RISCVIOMMUContext *ctx,
>               * non-user mode leaf PTE and !pv we need to fault.
>               */
>              break;
> -        } else if ((iotlb->perm & IOMMU_RO) && !(pte & PTE_R)) {
> +        } else if ((req_perm & IOMMU_RO) && !(pte & PTE_R)) {
>              break;                /* Read access check failed */
> -        } else if ((iotlb->perm & IOMMU_WO) && !(pte & PTE_W)) {
> +        } else if ((req_perm & IOMMU_WO) && !(pte & PTE_W)) {
>              break;                /* Write access check failed */
>          } else if (!ade && !(pte & PTE_A)) {
>              break;                /* Access bit not set */
> -        } else if ((iotlb->perm & IOMMU_WO) && !ade && !(pte &
> PTE_D)) {
> +        } else if ((req_perm & IOMMU_WO) && !ade && !(pte & PTE_D))
> {
>              break;                /* Dirty bit not set */
>          } else if (pass == G_STAGE && !(pte & PTE_U)) {
>              /*
> @@ -532,21 +540,20 @@ static int
> riscv_iommu_spa_fetch(RISCVIOMMUState *s, RISCVIOMMUContext *ctx,
>                  addr = iotlb->iova;
>                  continue;
>              }
> +
> +            /* Cache the effective permission, not this request's
> subset. */
> +            IOMMUAccessFlags leaf_perm = (pte & PTE_W) ?
> +                                         ((pte & PTE_R) ? IOMMU_RW :
> IOMMU_WO) :
> +                                         IOMMU_RO;
> +
> +            trans_perm = trans_perm == IOMMU_NONE ?
> +                         leaf_perm : trans_perm & leaf_perm;
> +
>              /* Translation phase completed (GPA or SPA) */
>              iotlb->translated_addr = base;
>  
> -            /*
> -             * Do a bit_and between the PTE bits and the original
> -             * request flags to determine the exact permission we
> -             * need, i.e. if the original request is RO and the
> -             * PTE has RW flags the actual perm is RO.
> -             */
> -            pte_perm = (pte & PTE_W) ? ((pte & PTE_R) ? IOMMU_RW :
> IOMMU_WO)
> -                                     : IOMMU_RO;
> -            iotlb->perm &= pte_perm;
> -
>              /* Check MSI GPA address match */
> -            if (pass == S_STAGE && (iotlb->perm & IOMMU_WO) &&
> +            if (pass == S_STAGE && (req_perm & IOMMU_WO) &&
>                  riscv_iommu_msi_check(s, ctx, base)) {
>                  /* Trap MSI writes and return GPA address. */
>                  iotlb->target_as = &s->trap_as;
> @@ -563,6 +570,7 @@ static int riscv_iommu_spa_fetch(RISCVIOMMUState
> *s, RISCVIOMMUContext *ctx,
>                  continue;
>              }
>  
> +            iotlb->perm = trans_perm;
>              return 0;
>          }
>  
> @@ -587,7 +595,7 @@ static int riscv_iommu_spa_fetch(RISCVIOMMUState
> *s, RISCVIOMMUContext *ctx,
>       */
>      iotlb->translated_addr = addr;
>  
> -    return (iotlb->perm & IOMMU_WO) ?
> +    return (req_perm & IOMMU_WO) ?
>                  (pass ? RISCV_IOMMU_FQ_CAUSE_WR_FAULT_VS :
>                          RISCV_IOMMU_FQ_CAUSE_WR_FAULT_S) :
>                  (pass ? RISCV_IOMMU_FQ_CAUSE_RD_FAULT_VS :
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.