iommu/vt-d: Don't free parent pagetable of the PTE we're adding
When adding a large scatterlist entry that covers more than the L3 superpage size (1GB) but has an alignment such that we must use L2 superpages (2MB) , we give dma_pte_free_level() a range that causes it to free the L3 pagetable we're about to populate. We fix this by telling dma_pte_free_pagetable() about the pagetable level we're about to populate to prevent freeing it. For example, mapping a scatterlist with entry lengths 854MB and 1194MB at IOVA 0xffff80000000 would, when processing the 2MB-aligned second entry, cause pfn_to_dma_pte() to create a L3 directory to hold L2 superpages for the mapping at IOVA 0xffffc0000000. We would previously call dma_pte_free_pagetable(domain, 0xffffc0000, 0xfffffffff), which would free the L3 directory pfn_to_dma_pte() just created for IO PFN 0xffffc0000. Telling dma_pte_free_pagetable() to retain the L3 directories while using L2 superpages avoids the erroneous free. Signed-off-by: David Dillow <dillow@google.com> Signed-off-by: Joerg Roedel <jroedel@suse.de>
This commit is contained in:
parent
01e1932a17
commit
bc24c57159
|
@ -1137,8 +1137,9 @@ static void dma_pte_clear_range(struct dmar_domain *domain,
|
||||||
}
|
}
|
||||||
|
|
||||||
static void dma_pte_free_level(struct dmar_domain *domain, int level,
|
static void dma_pte_free_level(struct dmar_domain *domain, int level,
|
||||||
struct dma_pte *pte, unsigned long pfn,
|
int retain_level, struct dma_pte *pte,
|
||||||
unsigned long start_pfn, unsigned long last_pfn)
|
unsigned long pfn, unsigned long start_pfn,
|
||||||
|
unsigned long last_pfn)
|
||||||
{
|
{
|
||||||
pfn = max(start_pfn, pfn);
|
pfn = max(start_pfn, pfn);
|
||||||
pte = &pte[pfn_level_offset(pfn, level)];
|
pte = &pte[pfn_level_offset(pfn, level)];
|
||||||
|
@ -1153,12 +1154,17 @@ static void dma_pte_free_level(struct dmar_domain *domain, int level,
|
||||||
level_pfn = pfn & level_mask(level);
|
level_pfn = pfn & level_mask(level);
|
||||||
level_pte = phys_to_virt(dma_pte_addr(pte));
|
level_pte = phys_to_virt(dma_pte_addr(pte));
|
||||||
|
|
||||||
if (level > 2)
|
if (level > 2) {
|
||||||
dma_pte_free_level(domain, level - 1, level_pte,
|
dma_pte_free_level(domain, level - 1, retain_level,
|
||||||
level_pfn, start_pfn, last_pfn);
|
level_pte, level_pfn, start_pfn,
|
||||||
|
last_pfn);
|
||||||
|
}
|
||||||
|
|
||||||
/* If range covers entire pagetable, free it */
|
/*
|
||||||
if (!(start_pfn > level_pfn ||
|
* Free the page table if we're below the level we want to
|
||||||
|
* retain and the range covers the entire table.
|
||||||
|
*/
|
||||||
|
if (level < retain_level && !(start_pfn > level_pfn ||
|
||||||
last_pfn < level_pfn + level_size(level) - 1)) {
|
last_pfn < level_pfn + level_size(level) - 1)) {
|
||||||
dma_clear_pte(pte);
|
dma_clear_pte(pte);
|
||||||
domain_flush_cache(domain, pte, sizeof(*pte));
|
domain_flush_cache(domain, pte, sizeof(*pte));
|
||||||
|
@ -1169,10 +1175,14 @@ next:
|
||||||
} while (!first_pte_in_page(++pte) && pfn <= last_pfn);
|
} while (!first_pte_in_page(++pte) && pfn <= last_pfn);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* clear last level (leaf) ptes and free page table pages. */
|
/*
|
||||||
|
* clear last level (leaf) ptes and free page table pages below the
|
||||||
|
* level we wish to keep intact.
|
||||||
|
*/
|
||||||
static void dma_pte_free_pagetable(struct dmar_domain *domain,
|
static void dma_pte_free_pagetable(struct dmar_domain *domain,
|
||||||
unsigned long start_pfn,
|
unsigned long start_pfn,
|
||||||
unsigned long last_pfn)
|
unsigned long last_pfn,
|
||||||
|
int retain_level)
|
||||||
{
|
{
|
||||||
BUG_ON(!domain_pfn_supported(domain, start_pfn));
|
BUG_ON(!domain_pfn_supported(domain, start_pfn));
|
||||||
BUG_ON(!domain_pfn_supported(domain, last_pfn));
|
BUG_ON(!domain_pfn_supported(domain, last_pfn));
|
||||||
|
@ -1181,7 +1191,7 @@ static void dma_pte_free_pagetable(struct dmar_domain *domain,
|
||||||
dma_pte_clear_range(domain, start_pfn, last_pfn);
|
dma_pte_clear_range(domain, start_pfn, last_pfn);
|
||||||
|
|
||||||
/* We don't need lock here; nobody else touches the iova range */
|
/* We don't need lock here; nobody else touches the iova range */
|
||||||
dma_pte_free_level(domain, agaw_to_level(domain->agaw),
|
dma_pte_free_level(domain, agaw_to_level(domain->agaw), retain_level,
|
||||||
domain->pgd, 0, start_pfn, last_pfn);
|
domain->pgd, 0, start_pfn, last_pfn);
|
||||||
|
|
||||||
/* free pgd */
|
/* free pgd */
|
||||||
|
@ -2274,8 +2284,11 @@ static int __domain_mapping(struct dmar_domain *domain, unsigned long iov_pfn,
|
||||||
/*
|
/*
|
||||||
* Ensure that old small page tables are
|
* Ensure that old small page tables are
|
||||||
* removed to make room for superpage(s).
|
* removed to make room for superpage(s).
|
||||||
|
* We're adding new large pages, so make sure
|
||||||
|
* we don't remove their parent tables.
|
||||||
*/
|
*/
|
||||||
dma_pte_free_pagetable(domain, iov_pfn, end_pfn);
|
dma_pte_free_pagetable(domain, iov_pfn, end_pfn,
|
||||||
|
largepage_lvl + 1);
|
||||||
} else {
|
} else {
|
||||||
pteval &= ~(uint64_t)DMA_PTE_LARGE_PAGE;
|
pteval &= ~(uint64_t)DMA_PTE_LARGE_PAGE;
|
||||||
}
|
}
|
||||||
|
@ -3935,7 +3948,8 @@ static int intel_map_sg(struct device *dev, struct scatterlist *sglist, int nele
|
||||||
ret = domain_sg_mapping(domain, start_vpfn, sglist, size, prot);
|
ret = domain_sg_mapping(domain, start_vpfn, sglist, size, prot);
|
||||||
if (unlikely(ret)) {
|
if (unlikely(ret)) {
|
||||||
dma_pte_free_pagetable(domain, start_vpfn,
|
dma_pte_free_pagetable(domain, start_vpfn,
|
||||||
start_vpfn + size - 1);
|
start_vpfn + size - 1,
|
||||||
|
agaw_to_level(domain->agaw) + 1);
|
||||||
free_iova_fast(&domain->iovad, iova_pfn, dma_to_mm_pfn(size));
|
free_iova_fast(&domain->iovad, iova_pfn, dma_to_mm_pfn(size));
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue