diff options
Diffstat (limited to 'drivers/iommu/amd_iommu.c')
-rw-r--r-- | drivers/iommu/amd_iommu.c | 130 |
1 files changed, 130 insertions, 0 deletions
diff --git a/drivers/iommu/amd_iommu.c b/drivers/iommu/amd_iommu.c index b0861739a27d..65a118ce626e 100644 --- a/drivers/iommu/amd_iommu.c +++ b/drivers/iommu/amd_iommu.c | |||
@@ -1637,8 +1637,45 @@ static void free_pagetable(struct protection_domain *domain) | |||
1637 | domain->pt_root = NULL; | 1637 | domain->pt_root = NULL; |
1638 | } | 1638 | } |
1639 | 1639 | ||
1640 | static void free_gcr3_tbl_level1(u64 *tbl) | ||
1641 | { | ||
1642 | u64 *ptr; | ||
1643 | int i; | ||
1644 | |||
1645 | for (i = 0; i < 512; ++i) { | ||
1646 | if (!(tbl[i] & GCR3_VALID)) | ||
1647 | continue; | ||
1648 | |||
1649 | ptr = __va(tbl[i] & PAGE_MASK); | ||
1650 | |||
1651 | free_page((unsigned long)ptr); | ||
1652 | } | ||
1653 | } | ||
1654 | |||
1655 | static void free_gcr3_tbl_level2(u64 *tbl) | ||
1656 | { | ||
1657 | u64 *ptr; | ||
1658 | int i; | ||
1659 | |||
1660 | for (i = 0; i < 512; ++i) { | ||
1661 | if (!(tbl[i] & GCR3_VALID)) | ||
1662 | continue; | ||
1663 | |||
1664 | ptr = __va(tbl[i] & PAGE_MASK); | ||
1665 | |||
1666 | free_gcr3_tbl_level1(ptr); | ||
1667 | } | ||
1668 | } | ||
1669 | |||
1640 | static void free_gcr3_table(struct protection_domain *domain) | 1670 | static void free_gcr3_table(struct protection_domain *domain) |
1641 | { | 1671 | { |
1672 | if (domain->glx == 2) | ||
1673 | free_gcr3_tbl_level2(domain->gcr3_tbl); | ||
1674 | else if (domain->glx == 1) | ||
1675 | free_gcr3_tbl_level1(domain->gcr3_tbl); | ||
1676 | else if (domain->glx != 0) | ||
1677 | BUG(); | ||
1678 | |||
1642 | free_page((unsigned long)domain->gcr3_tbl); | 1679 | free_page((unsigned long)domain->gcr3_tbl); |
1643 | } | 1680 | } |
1644 | 1681 | ||
@@ -3282,3 +3319,96 @@ int amd_iommu_flush_tlb(struct iommu_domain *dom, int pasid) | |||
3282 | } | 3319 | } |
3283 | EXPORT_SYMBOL(amd_iommu_flush_tlb); | 3320 | EXPORT_SYMBOL(amd_iommu_flush_tlb); |
3284 | 3321 | ||
3322 | static u64 *__get_gcr3_pte(u64 *root, int level, int pasid, bool alloc) | ||
3323 | { | ||
3324 | int index; | ||
3325 | u64 *pte; | ||
3326 | |||
3327 | while (true) { | ||
3328 | |||
3329 | index = (pasid >> (9 * level)) & 0x1ff; | ||
3330 | pte = &root[index]; | ||
3331 | |||
3332 | if (level == 0) | ||
3333 | break; | ||
3334 | |||
3335 | if (!(*pte & GCR3_VALID)) { | ||
3336 | if (!alloc) | ||
3337 | return NULL; | ||
3338 | |||
3339 | root = (void *)get_zeroed_page(GFP_ATOMIC); | ||
3340 | if (root == NULL) | ||
3341 | return NULL; | ||
3342 | |||
3343 | *pte = __pa(root) | GCR3_VALID; | ||
3344 | } | ||
3345 | |||
3346 | root = __va(*pte & PAGE_MASK); | ||
3347 | |||
3348 | level -= 1; | ||
3349 | } | ||
3350 | |||
3351 | return pte; | ||
3352 | } | ||
3353 | |||
3354 | static int __set_gcr3(struct protection_domain *domain, int pasid, | ||
3355 | unsigned long cr3) | ||
3356 | { | ||
3357 | u64 *pte; | ||
3358 | |||
3359 | if (domain->mode != PAGE_MODE_NONE) | ||
3360 | return -EINVAL; | ||
3361 | |||
3362 | pte = __get_gcr3_pte(domain->gcr3_tbl, domain->glx, pasid, true); | ||
3363 | if (pte == NULL) | ||
3364 | return -ENOMEM; | ||
3365 | |||
3366 | *pte = (cr3 & PAGE_MASK) | GCR3_VALID; | ||
3367 | |||
3368 | return __amd_iommu_flush_tlb(domain, pasid); | ||
3369 | } | ||
3370 | |||
3371 | static int __clear_gcr3(struct protection_domain *domain, int pasid) | ||
3372 | { | ||
3373 | u64 *pte; | ||
3374 | |||
3375 | if (domain->mode != PAGE_MODE_NONE) | ||
3376 | return -EINVAL; | ||
3377 | |||
3378 | pte = __get_gcr3_pte(domain->gcr3_tbl, domain->glx, pasid, false); | ||
3379 | if (pte == NULL) | ||
3380 | return 0; | ||
3381 | |||
3382 | *pte = 0; | ||
3383 | |||
3384 | return __amd_iommu_flush_tlb(domain, pasid); | ||
3385 | } | ||
3386 | |||
3387 | int amd_iommu_domain_set_gcr3(struct iommu_domain *dom, int pasid, | ||
3388 | unsigned long cr3) | ||
3389 | { | ||
3390 | struct protection_domain *domain = dom->priv; | ||
3391 | unsigned long flags; | ||
3392 | int ret; | ||
3393 | |||
3394 | spin_lock_irqsave(&domain->lock, flags); | ||
3395 | ret = __set_gcr3(domain, pasid, cr3); | ||
3396 | spin_unlock_irqrestore(&domain->lock, flags); | ||
3397 | |||
3398 | return ret; | ||
3399 | } | ||
3400 | EXPORT_SYMBOL(amd_iommu_domain_set_gcr3); | ||
3401 | |||
3402 | int amd_iommu_domain_clear_gcr3(struct iommu_domain *dom, int pasid) | ||
3403 | { | ||
3404 | struct protection_domain *domain = dom->priv; | ||
3405 | unsigned long flags; | ||
3406 | int ret; | ||
3407 | |||
3408 | spin_lock_irqsave(&domain->lock, flags); | ||
3409 | ret = __clear_gcr3(domain, pasid); | ||
3410 | spin_unlock_irqrestore(&domain->lock, flags); | ||
3411 | |||
3412 | return ret; | ||
3413 | } | ||
3414 | EXPORT_SYMBOL(amd_iommu_domain_clear_gcr3); | ||