Talk:AMD-Vi IOMMU
QEMU AMD-Vi IOMMU confusion
There are several aspects about the IOMMU that I'm uncertain about, so I won't put them in the article while I query qemu-devel. In the meantime, I will document them here:
INVALIDATE_IOMMU_ALL doesn't reload DTEs
The specification says (emphasis mine):
The INVALIDATE_IOMMU_ALL command instructs the IOMMU to invalidate all cached information for interrupt remapping and address translation for guest and nested translations, including cached portions of the Device Table, the guest CR3 table, page directory entries, page table entries, and interrupt remapping entries.
But QEMU doesn't reload any DTEs. It only reloads if INVALIDATE_DEVTAB_ENTRY is issued. I strongly suspect this is an oversight in QEMU, but I will have to confirm.
fetch_pte() has incorrect page size for large pages (+ ignores level skipping)
As of writing (and excluding the printfs added by me) fetch_pte() uses this loop for walking the page tables:
printf("PT root (level=%d): pte=%016"PRIx64"\n", level, *pte);
do {
level -= 1;
printf("PT walk (level=%d): pte=%016"PRIx64" NextLevel=%llu page_size=0x%"PRIx64"\n", level, *pte, PTE_NEXT_LEVEL(*pte), *page_size);
/* Update the page_size */
*page_size = PTE_LEVEL_PAGE_SIZE(level); // <--- (1)
/* Permission bits are ANDed at every level, including the DTE */
perms &= amdvi_get_perms(*pte);
if (perms == IOMMU_NONE) {
return 0;
}
/* Not Present */
if (!IOMMU_PTE_PRESENT(*pte)) {
return 0;
}
/* Large or Leaf PTE found */
if (PTE_NEXT_LEVEL(*pte) == 7 || PTE_NEXT_LEVEL(*pte) == 0) { // <--- (2)
/* Leaf PTE found */
break;
}
/*
* Index the pgtable using the IOVA bits corresponding to current level
* and walk down to the lower level.
*/
pte_addr = NEXT_PTE_ADDR(*pte, level, address);
*pte = amdvi_get_pte_entry(as->iommu_state, pte_addr, as->devfn);
if (*pte == (uint64_t)-1) {
/*
* A returned PTE of -1 indicates a failure to read the page table
* entry from guest memory.
*/
if (level == mode - 1) {
/* Failure to retrieve the Page Table from Root Pointer */
*page_size = 0;
return -AMDVI_FR_PT_ROOT_INV;
} else {
/* Failure to read PTE. Page walk skips a page_size chunk */
return -AMDVI_FR_PT_ENTRY_INV;
}
}
} while (level > 0);
The first issue is: the page size is too small for large pages (2M becomes 4K, 1G becomes 2M ...). Simply putting (1) below (2) fixes this, but this may not correspond to how real HW works.
The second issue is: this code appears to not implement level skipping.