@samitouri / QOSamiQemu / commits / 786550e2d3

amd_iommu: Follow root pointer before page walk and use 1-based levels

DTE[Mode] and PTE NextLevel encode page table levels as 1-based values, but fetch_pte() currently uses a 0-based level counter, making the logic harder to follow and requiring conversions between DTE mode and level. Switch the page table walk logic to use 1-based level accounting in fetch_pte() and the relevant macro helpers. To further simplify the page walking loop, split the root page table access from the walk i.e. rework fetch_pte() to follow the DTE Page Table Root Pointer and retrieve the top level pagetable entry before entering the loop, then iterate only over the PDE/PTE entries. The reworked algorithm fixes a page walk bug where the page size was calculated for the next level before checking if the current PTE was already a leaf/hugepage. That caused hugepage mappings to be reported as 4K pages, leading to performance degradation and failures in some setups. Fixes: a74bb3110a5b ("amd_iommu: Add helpers to walk AMD v1 Page Table format") Cc: qemu-stable@nongnu.org Reported-by: David Hoppenbrouwers <qemu@demindiro.com> Reviewed-By: David Hoppenbrouwers <qemu@demindiro.com> Reviewed-by: Sairaj Kodilkar <sarunkod@amd.com> Signed-off-by: Alejandro Jimenez <alejandro.j.jimenez@oracle.com> Reviewed-by: Michael S. Tsirkin <mst@redhat.com> Signed-off-by: Michael S. Tsirkin <mst@redhat.com> Message-Id: <20260330212817.992673-2-alejandro.j.jimenez@oracle.com>

Alejandro Jimenez committed Mar 30, 2026 at 21:28 UTC 786550e2d38a92e90c13eb9d57e3a72d7ab38d51
2 files changed +97 -46
hw/i386/amd_iommu.c
+92 -40
@@ -648,6 +648,52 @@ static uint64_t large_pte_page_size(uint64_t pte)
648 return PTE_LARGE_PAGE_SIZE(pte);
649 }
650
651 +/*
652 + * Validate DTE fields and extract permissions and top level data required to
653 + * initiate the page table walk.
654 + *
655 + * On success, returns 0 and stores:
656 + * - top_level: highest page-table level encoded in DTE[Mode]
657 + * - dte_perms: effective permissions from the DTE
658 + *
659 + * On failure, returns -AMDVI_FR_PT_ROOT_INV. This includes cases where:
660 + * - DTE permissions disallow read AND write
661 + * - DTE[Mode] is invalid for translation
662 + * - IOVA exceeds the address width supported by DTE[Mode]
663 + * In all such cases a page walk must be aborted.
664 + */
665 +static uint64_t amdvi_get_top_pt_level_and_perms(hwaddr address, uint64_t dte,
666 + uint8_t *top_level,
667 + IOMMUAccessFlags *dte_perms)
668 +{
669 + *dte_perms = amdvi_get_perms(dte);
670 + if (*dte_perms == IOMMU_NONE) {
671 + return -AMDVI_FR_PT_ROOT_INV;
672 + }
673 +
674 + /* Verifying a valid mode is encoded in DTE */
675 + *top_level = get_pte_translation_mode(dte);
676 +
677 + /*
678 + * Page Table Root pointer is only valid for GPA->SPA translation on
679 + * supported modes.
680 + */
681 + if (*top_level == 0 || *top_level > 6) {
682 + return -AMDVI_FR_PT_ROOT_INV;
683 + }
684 +
685 + /*
686 + * If IOVA is larger than the max supported by the highest pgtable level,
687 + * there is nothing to do.
688 + */
689 + if (address > PT_LEVEL_MAX_ADDR(*top_level)) {
690 + /* IOVA too large for the current DTE */
691 + return -AMDVI_FR_PT_ROOT_INV;
692 + }
693 +
694 + return 0;
695 +}
696 +
697 /*
698 * Helper function to fetch a PTE using AMD v1 pgtable format.
699 * On successful page walk, returns 0 and pte parameter points to a valid PTE.
@@ -662,40 +708,49 @@ static uint64_t large_pte_page_size(uint64_t pte)
708 static uint64_t fetch_pte(AMDVIAddressSpace *as, hwaddr address, uint64_t dte,
709 uint64_t *pte, hwaddr *page_size)
710 {
665 - IOMMUAccessFlags perms = amdvi_get_perms(dte);
666 -
667 - uint8_t level, mode;
711 uint64_t pte_addr;
712 + uint8_t pt_level, next_pt_level;
713 + IOMMUAccessFlags perms;
714 + int ret;
715
670 - *pte = dte;
716 *page_size = 0;
717
673 - if (perms == IOMMU_NONE) {
674 - return -AMDVI_FR_PT_ROOT_INV;
675 - }
676 -
718 /*
678 - * The Linux kernel driver initializes the default mode to 3, corresponding
679 - * to a 39-bit GPA space, where each entry in the pagetable translates to a
680 - * 1GB (2^30) page size.
719 + * Verify the DTE is properly configured before page walk, and extract
720 + * top pagetable level and permissions.
721 */
682 - level = mode = get_pte_translation_mode(dte);
683 - assert(mode > 0 && mode < 7);
722 + ret = amdvi_get_top_pt_level_and_perms(address, dte, &pt_level, &perms);
723 + if (ret < 0) {
724 + return ret;
725 + }
726
727 /*
686 - * If IOVA is larger than the max supported by the current pgtable level,
687 - * there is nothing to do.
728 + * Retrieve the top pagetable entry by following the DTE Page Table Root
729 + * Pointer and indexing the top level table using the IOVA from the request.
730 */
689 - if (address > PT_LEVEL_MAX_ADDR(mode - 1)) {
690 - /* IOVA too large for the current DTE */
731 + pte_addr = NEXT_PTE_ADDR(dte, pt_level, address);
732 + *pte = amdvi_get_pte_entry(as->iommu_state, pte_addr, as->devfn);
733 +
734 + if (*pte == (uint64_t)-1) {
735 + /*
736 + * A returned PTE of -1 here indicates a failure to read the top level
737 + * page table from guest memory. A page walk is not possible and page
738 + * size must be returned as 0.
739 + */
740 return -AMDVI_FR_PT_ROOT_INV;
741 }
742
694 - do {
695 - level -= 1;
743 + /*
744 + * Calculate page size for the top level page table entry.
745 + * This ensures correct results for a single level Page Table setup.
746 + */
747 + *page_size = PTE_LEVEL_PAGE_SIZE(pt_level);
748
697 - /* Update the page_size */
698 - *page_size = PTE_LEVEL_PAGE_SIZE(level);
749 + /*
750 + * The root page table entry and its level have been determined. Begin the
751 + * page walk.
752 + */
753 + while (pt_level > 0) {
754
755 /* Permission bits are ANDed at every level, including the DTE */
756 perms &= amdvi_get_perms(*pte);
@@ -708,37 +763,34 @@ static uint64_t fetch_pte(AMDVIAddressSpace *as, hwaddr address, uint64_t dte,
763 return 0;
764 }
765
766 + next_pt_level = PTE_NEXT_LEVEL(*pte);
767 +
768 /* Large or Leaf PTE found */
712 - if (PTE_NEXT_LEVEL(*pte) == 7 || PTE_NEXT_LEVEL(*pte) == 0) {
769 + if (next_pt_level == 0 || next_pt_level == 7) {
770 /* Leaf PTE found */
771 break;
772 }
773
774 + pt_level = next_pt_level;
775 +
776 /*
718 - * Index the pgtable using the IOVA bits corresponding to current level
719 - * and walk down to the lower level.
777 + * The current entry is a Page Directory Entry. Descend to the lower
778 + * page table level encoded in current pte, and index the new table
779 + * using the appropriate IOVA bits to retrieve the new entry.
780 */
721 - pte_addr = NEXT_PTE_ADDR(*pte, level, address);
781 + *page_size = PTE_LEVEL_PAGE_SIZE(pt_level);
782 +
783 + pte_addr = NEXT_PTE_ADDR(*pte, pt_level, address);
784 *pte = amdvi_get_pte_entry(as->iommu_state, pte_addr, as->devfn);
785
786 if (*pte == (uint64_t)-1) {
725 - /*
726 - * A returned PTE of -1 indicates a failure to read the page table
727 - * entry from guest memory.
728 - */
729 - if (level == mode - 1) {
730 - /* Failure to retrieve the Page Table from Root Pointer */
731 - *page_size = 0;
732 - return -AMDVI_FR_PT_ROOT_INV;
733 - } else {
734 - /* Failure to read PTE. Page walk skips a page_size chunk */
735 - return -AMDVI_FR_PT_ENTRY_INV;
736 - }
787 + /* Failure to read PTE. Page walk skips a page_size chunk */
788 + return -AMDVI_FR_PT_ENTRY_INV;
789 }
738 - } while (level > 0);
790 + }
791 +
792 + assert(PTE_NEXT_LEVEL(*pte) == 0 || PTE_NEXT_LEVEL(*pte) == 7);
793
740 - assert(PTE_NEXT_LEVEL(*pte) == 0 || PTE_NEXT_LEVEL(*pte) == 7 ||
741 - level == 0);
794 /*
795 * Page walk ends when Next Level field on PTE shows that either a leaf PTE
796 * or a series of large PTEs have been reached. In the latter case, even if
hw/i386/amd_iommu.h
+5 -6
@@ -186,17 +186,16 @@
186
187 #define IOMMU_PTE_PRESENT(pte) ((pte) & AMDVI_PTE_PR)
188
189 -/* Using level=0 for leaf PTE at 4K page size */
190 -#define PT_LEVEL_SHIFT(level) (12 + ((level) * 9))
189 +/* Using level=1 for leaf PTE at 4K page size */
190 +#define PT_LEVEL_SHIFT(level) (12 + (((level) - 1) * 9))
191
192 /* Return IOVA bit group used to index the Page Table at specific level */
193 #define PT_LEVEL_INDEX(level, iova) (((iova) >> PT_LEVEL_SHIFT(level)) & \
194 GENMASK64(8, 0))
195
196 -/* Return the max address for a specified level i.e. max_oaddr */
197 -#define PT_LEVEL_MAX_ADDR(x) (((x) < 5) ? \
198 - ((1ULL << PT_LEVEL_SHIFT((x + 1))) - 1) : \
199 - (~(0ULL)))
196 +/* Return the maximum output address for a specified page table level */
197 +#define PT_LEVEL_MAX_ADDR(level) (((level) > 5) ? (~(0ULL)) : \
198 + ((1ULL << PT_LEVEL_SHIFT((level) + 1)) - 1))
199
200 /* Extract the NextLevel field from PTE/PDE */
201 #define PTE_NEXT_LEVEL(pte) (((pte) & AMDVI_PTE_NEXT_LEVEL_MASK) >> 9)