]> rtime.felk.cvut.cz Git - jailhouse.git/commitdiff
x86: Unify AMD page tables for CPU and IOMMU
authorJan Kiszka <jan.kiszka@siemens.com>
Tue, 1 Mar 2016 22:31:31 +0000 (23:31 +0100)
committerJan Kiszka <jan.kiszka@siemens.com>
Wed, 16 Mar 2016 08:23:53 +0000 (09:23 +0100)
This exploits AMD's architecture feature that you can reuse the nested
page tables also for the IOMMU.

Both tables have the same depth (4), share the same address fields, the
valid bit - but all other bits are separate. Therefore, we need to
enhance the NPT paging handlers so that they fold both bit sets into an
entry.

The rewards are saving of several lines of code as well as a bunch of
hypervisor pages (typically some dozen).

Signed-off-by: Jan Kiszka <jan.kiszka@siemens.com>
hypervisor/arch/x86/amd_iommu.c
hypervisor/arch/x86/include/asm/amd_iommu.h
hypervisor/arch/x86/include/asm/cell.h
hypervisor/arch/x86/include/asm/svm.h
hypervisor/arch/x86/svm.c

index f2e2f9e70181efd4c9a662cd202b4cbea498816a..87d6293da1ed5df7d8549221de811c9ee90e9c1c 100644 (file)
@@ -148,7 +148,6 @@ static struct amd_iommu {
                                   iommu++)
 
 static unsigned int iommu_units_count;
-static struct paging amd_iommu_paging[AMD_IOMMU_MAX_PAGE_TABLE_LEVELS];
 
 /*
  * Interrupt remapping is not emulated on AMD,
@@ -313,38 +312,6 @@ static void amd_iommu_enable_command_processing(struct amd_iommu *iommu)
        mmio_write64(iommu->mmio_base + AMD_CONTROL_REG, ctrl_reg);
 }
 
-static void amd_iommu_set_next_pt_l4(pt_entry_t pte, unsigned long next_pt)
-{
-       *pte = (next_pt & BIT_MASK(51, 12)) | AMD_IOMMU_PTE_PG_MODE(3) |
-               AMD_IOMMU_PTE_IR | AMD_IOMMU_PTE_IW | AMD_IOMMU_PTE_P;
-}
-
-static void amd_iommu_set_next_pt_l3(pt_entry_t pte, unsigned long next_pt)
-{
-       *pte = (next_pt & BIT_MASK(51, 12)) | AMD_IOMMU_PTE_PG_MODE(2) |
-               AMD_IOMMU_PTE_IR | AMD_IOMMU_PTE_IW | AMD_IOMMU_PTE_P;
-}
-
-static void amd_iommu_set_next_pt_l2(pt_entry_t pte, unsigned long next_pt)
-{
-       *pte = (next_pt & BIT_MASK(51, 12)) | AMD_IOMMU_PTE_PG_MODE(1) |
-               AMD_IOMMU_PTE_IR | AMD_IOMMU_PTE_IW | AMD_IOMMU_PTE_P;
-}
-
-static unsigned long amd_iommu_get_phys_l3(pt_entry_t pte, unsigned long virt)
-{
-       if (*pte & AMD_IOMMU_PTE_PG_MODE_MASK)
-               return INVALID_PHYS_ADDR;
-       return (*pte & BIT_MASK(51, 30)) | (virt & BIT_MASK(29, 0));
-}
-
-static unsigned long amd_iommu_get_phys_l2(pt_entry_t pte, unsigned long virt)
-{
-       if (*pte & AMD_IOMMU_PTE_PG_MODE_MASK)
-               return INVALID_PHYS_ADDR;
-       return (*pte & BIT_MASK(51, 21)) | (virt & BIT_MASK(20, 0));
-}
-
 int iommu_init(void)
 {
        struct jailhouse_iommu *iommu;
@@ -385,17 +352,6 @@ int iommu_init(void)
                iommu_units_count++;
        }
 
-       /*
-        * Derive amd_iommu_paging from very similar x86_64_paging,
-        * replicating all 4 levels.
-        */
-       memcpy(amd_iommu_paging, x86_64_paging, sizeof(amd_iommu_paging));
-       amd_iommu_paging[0].set_next_pt = amd_iommu_set_next_pt_l4;
-       amd_iommu_paging[1].set_next_pt = amd_iommu_set_next_pt_l3;
-       amd_iommu_paging[2].set_next_pt = amd_iommu_set_next_pt_l2;
-       amd_iommu_paging[1].get_phys = amd_iommu_get_phys_l3;
-       amd_iommu_paging[2].get_phys = amd_iommu_get_phys_l2;
-
        return iommu_cell_init(&root_cell);
 }
 
@@ -408,11 +364,6 @@ int iommu_cell_init(struct cell *cell)
        if (cell->id > 0xffff)
                return trace_error(-ERANGE);
 
-       cell->arch.amd_iommu.pg_structs.root_paging = amd_iommu_paging;
-       cell->arch.amd_iommu.pg_structs.root_table = page_alloc(&mem_pool, 1);
-       if (!cell->arch.amd_iommu.pg_structs.root_table)
-               return trace_error(-ENOMEM);
-
        return 0;
 }
 
@@ -443,22 +394,10 @@ static void amd_iommu_submit_command(struct amd_iommu *iommu,
                (iommu->cmd_tail_ptr + sizeof(*cmd)) % CMD_BUF_SIZE;
 }
 
-int iommu_map_memory_region(struct cell *cell,
-                           const struct jailhouse_memory *mem)
+u64 amd_iommu_get_memory_region_flags(const struct jailhouse_memory *mem)
 {
        unsigned long flags = AMD_IOMMU_PTE_P;
 
-       // HACK for QEMU
-       if (iommu_units_count == 0)
-               return 0;
-
-       /*
-        * Check that the address is not outside scope of current page
-        * tables. With 4 levels, we only support 48 address bits.
-        */
-       if (mem->virt_start & BIT_MASK(63, 48))
-               return trace_error(-E2BIG);
-
        if (!(mem->flags & JAILHOUSE_MEM_DMA))
                return 0;
 
@@ -467,27 +406,28 @@ int iommu_map_memory_region(struct cell *cell,
        if (mem->flags & JAILHOUSE_MEM_WRITE)
                flags |= AMD_IOMMU_PTE_IW;
 
-       return paging_create(&cell->arch.amd_iommu.pg_structs, mem->phys_start,
-                       mem->size, mem->virt_start, flags, PAGING_COHERENT);
+       return flags;
 }
 
-int iommu_unmap_memory_region(struct cell *cell,
-                             const struct jailhouse_memory *mem)
+int iommu_map_memory_region(struct cell *cell,
+                           const struct jailhouse_memory *mem)
 {
        /*
-         * TODO: This is almost a complete copy of vtd.c counterpart
-        * (sans QEMU hack). Think of unification.
+        * Check that the address is not outside the scope of the page tables.
+        * With 4 levels, we only support 48 address bits.
         */
+       if (mem->virt_start & BIT_MASK(63, 48))
+               return trace_error(-E2BIG);
 
-       // HACK for QEMU
-       if (iommu_units_count == 0)
-               return 0;
-
-       if (!(mem->flags & JAILHOUSE_MEM_DMA))
-               return 0;
+       /* vcpu_map_memory_region already did the actual work. */
+       return 0;
+}
 
-       return paging_destroy(&cell->arch.amd_iommu.pg_structs, mem->virt_start,
-                       mem->size, PAGING_COHERENT);
+int iommu_unmap_memory_region(struct cell *cell,
+                             const struct jailhouse_memory *mem)
+{
+       /* vcpu_map_memory_region already did the actual work. */
+       return 0;
 }
 
 static void amd_iommu_inv_dte(struct amd_iommu *iommu, u16 device_id)
@@ -585,7 +525,7 @@ int iommu_add_pci_device(struct cell *cell, struct pci_device *device)
 
        /* Translation information */
        dte->raw64[0] = DTE_IR | DTE_IW |
-               paging_hvirt2phys(cell->arch.amd_iommu.pg_structs.root_table) |
+               paging_hvirt2phys(cell->arch.svm.npt_iommu_structs.root_table) |
                DTE_PAGING_MODE_4_LEVEL | DTE_TRANSLATION_VALID | DTE_VALID;
 
        /* TODO: Interrupt remapping. For now, just forward them unmapped. */
@@ -632,12 +572,6 @@ void iommu_remove_pci_device(struct pci_device *device)
 
 void iommu_cell_exit(struct cell *cell)
 {
-       /* TODO: Again, this a copy of vtd.c:iommu_cell_exit */
-       // HACK for QEMU
-       if (iommu_units_count == 0)
-               return;
-
-       page_free(&mem_pool, cell->arch.amd_iommu.pg_structs.root_table, 1);
 }
 
 static void wait_for_zero(volatile u64 *sem, unsigned long mask)
index 0d06035dbb93459eec0f44f11fa66fe1a6f9e5c1..dc069b2697eb9df669ae05cb06841939e3c500e8 100644 (file)
@@ -27,4 +27,6 @@
 #define AMD_IOMMU_PAGE_DEFAULT_FLAGS   (AMD_IOMMU_PTE_IW | AMD_IOMMU_PTE_IR | \
                                         AMD_IOMMU_PTE_P)
 
+u64 amd_iommu_get_memory_region_flags(const struct jailhouse_memory *mem);
+
 #endif
index a07072970c36b0db12cba2d257d6be0ff718e323..2223532a39c93f13cd7f771e646ea3da276b742e 100644 (file)
@@ -36,8 +36,8 @@ struct arch_cell {
                struct {
                        /** I/O Permissions Map. */
                        u8 *iopm;
-                       /** Paging structures used for cell CPUs. */
-                       struct paging_structures npt_structs;
+                       /** Paging structures used for cell CPUs and IOMMU. */
+                       struct paging_structures npt_iommu_structs;
                } svm; /**< AMD SVM-specific fields. */
        };
 
@@ -49,10 +49,6 @@ struct arch_cell {
                         * cell. */
                        bool ir_emulation;
                } vtd; /**< Intel VT-d specific fields. */
-               struct {
-                       /** Paging structures used for DMA requests. */
-                       struct paging_structures pg_structs;
-               } amd_iommu; /**< AMD IOMMU specific fields. */
        };
 
        /** Shadow value of PCI config space address port register. */
index d5f2e1e40ec9f01ca574b081bc52bb4941616620..6be28041d2de0bb1ed0ed87362c5ac4920767c0b 100644 (file)
@@ -38,8 +38,6 @@
 #define SVM_TLB_FLUSH_ALL      0x01
 #define SVM_TLB_FLUSH_GUEST    0x03
 
-#define NPT_PAGE_DIR_LEVELS    4
-
 #define SVM_EVENTINJ_EXCEPTION (3UL << 8)
 #define SVM_EVENTINJ_ERR_VALID (1UL << 11)
 #define SVM_EVENTINJ_VALID     (1UL << 31)
index 000370f1d74ccf5c3c238f3de950d15f32d385e2..5364617bf5629dc0cc22f48465c45b9e4a6a9f44 100644 (file)
@@ -23,6 +23,7 @@
 #include <jailhouse/processor.h>
 #include <jailhouse/string.h>
 #include <jailhouse/utils.h>
+#include <asm/amd_iommu.h>
 #include <asm/apic.h>
 #include <asm/control.h>
 #include <asm/iommu.h>
  * combinations of NW and CD bits are prohibited by SVM (see APMv2,
  * Sect. 15.5). To handle this, we always keep the NW bit off.
  */
-#define SVM_CR0_ALLOWED_BITS   (~X86_CR0_NW)
+#define SVM_CR0_ALLOWED_BITS           (~X86_CR0_NW)
 
 /* IOPM size: two 4-K pages + 3 bits */
-#define IOPM_PAGES             3
+#define IOPM_PAGES                     3
+
+#define NPT_IOMMU_PAGE_DIR_LEVELS      4
 
 static bool has_avic, has_assists, has_flush_by_asid;
 
 static const struct segment invalid_seg;
 
-static struct paging npt_paging[NPT_PAGE_DIR_LEVELS];
+static struct paging npt_iommu_paging[NPT_IOMMU_PAGE_DIR_LEVELS];
 
 /* bit cleared: direct access allowed */
 // TODO: convert to whitelist
@@ -150,7 +153,8 @@ static void set_svm_segment_from_segment(struct svm_segment *svm_segment,
 static void svm_set_cell_config(struct cell *cell, struct vmcb *vmcb)
 {
        vmcb->iopm_base_pa = paging_hvirt2phys(cell->arch.svm.iopm);
-       vmcb->n_cr3 = paging_hvirt2phys(cell->arch.svm.npt_structs.root_table);
+       vmcb->n_cr3 =
+               paging_hvirt2phys(cell->arch.svm.npt_iommu_structs.root_table);
 }
 
 static void vmcb_setup(struct per_cpu *cpu_data)
@@ -236,21 +240,54 @@ unsigned long arch_paging_gphys2phys(struct per_cpu *cpu_data,
                                     unsigned long gphys,
                                     unsigned long flags)
 {
-       return paging_virt2phys(&cpu_data->cell->arch.svm.npt_structs,
-                       gphys, flags);
+       return paging_virt2phys(&cpu_data->cell->arch.svm.npt_iommu_structs,
+                               gphys, flags);
+}
+
+static void npt_iommu_set_next_pt_l4(pt_entry_t pte, unsigned long next_pt)
+{
+       /*
+        * Merge IOMMU and NPT flags. We need to mark the NTP entries as user
+        * accessible, see APMv2, Section 15.25.5.
+        */
+       *pte = (next_pt & BIT_MASK(51, 12)) | AMD_IOMMU_PTE_PG_MODE(3) |
+               AMD_IOMMU_PTE_IR | AMD_IOMMU_PTE_IW | AMD_IOMMU_PTE_P |
+               PAGE_DEFAULT_FLAGS | PAGE_FLAG_US;
+}
+
+static void npt_iommu_set_next_pt_l3(pt_entry_t pte, unsigned long next_pt)
+{
+       *pte = (next_pt & BIT_MASK(51, 12)) | AMD_IOMMU_PTE_PG_MODE(2) |
+               AMD_IOMMU_PTE_IR | AMD_IOMMU_PTE_IW | AMD_IOMMU_PTE_P |
+               PAGE_DEFAULT_FLAGS | PAGE_FLAG_US;
+}
+
+static void npt_iommu_set_next_pt_l2(pt_entry_t pte, unsigned long next_pt)
+{
+       *pte = (next_pt & BIT_MASK(51, 12)) | AMD_IOMMU_PTE_PG_MODE(1) |
+               AMD_IOMMU_PTE_IR | AMD_IOMMU_PTE_IW | AMD_IOMMU_PTE_P |
+               PAGE_DEFAULT_FLAGS | PAGE_FLAG_US;
+}
+
+static unsigned long npt_iommu_get_phys_l3(pt_entry_t pte, unsigned long virt)
+{
+       if (*pte & AMD_IOMMU_PTE_PG_MODE_MASK)
+               return INVALID_PHYS_ADDR;
+       return (*pte & BIT_MASK(51, 30)) | (virt & BIT_MASK(29, 0));
 }
 
-static void npt_set_next_pt(pt_entry_t pte, unsigned long next_pt)
+static unsigned long npt_iommu_get_phys_l2(pt_entry_t pte, unsigned long virt)
 {
-       /* See APMv2, Section 15.25.5 */
-       *pte = (next_pt & BIT_MASK(51, 12)) | PAGE_DEFAULT_FLAGS | PAGE_FLAG_US;
+       if (*pte & AMD_IOMMU_PTE_PG_MODE_MASK)
+               return INVALID_PHYS_ADDR;
+       return (*pte & BIT_MASK(51, 21)) | (virt & BIT_MASK(20, 0));
 }
 
 int vcpu_vendor_init(void)
 {
        struct paging_structures parking_pt;
        unsigned long vm_cr;
-       int err, n;
+       int err;
 
        err = svm_check_features();
        if (err)
@@ -261,13 +298,20 @@ int vcpu_vendor_init(void)
                /* SVM disabled in BIOS */
                return trace_error(-EPERM);
 
-       /* Nested paging is the same as the native one */
-       memcpy(npt_paging, x86_64_paging, sizeof(npt_paging));
-       for (n = 0; n < NPT_PAGE_DIR_LEVELS; n++)
-               npt_paging[n].set_next_pt = npt_set_next_pt;
+       /*
+        * Nested paging is almost the same as the native one. However, we
+        * need to override some handlers in order to reuse the page table for
+        * the IOMMU as well.
+        */
+       memcpy(npt_iommu_paging, x86_64_paging, sizeof(npt_iommu_paging));
+       npt_iommu_paging[0].set_next_pt = npt_iommu_set_next_pt_l4;
+       npt_iommu_paging[1].set_next_pt = npt_iommu_set_next_pt_l3;
+       npt_iommu_paging[2].set_next_pt = npt_iommu_set_next_pt_l2;
+       npt_iommu_paging[1].get_phys = npt_iommu_get_phys_l3;
+       npt_iommu_paging[2].get_phys = npt_iommu_get_phys_l2;
 
        /* Map guest parking code (shared between cells and CPUs) */
-       parking_pt.root_paging = npt_paging;
+       parking_pt.root_paging = npt_iommu_paging;
        parking_pt.root_table = parked_mode_npt = page_alloc(&mem_pool, 1);
        if (!parked_mode_npt)
                return -ENOMEM;
@@ -307,8 +351,8 @@ int vcpu_vendor_cell_init(struct cell *cell)
                return err;
 
        /* build root NPT of cell */
-       cell->arch.svm.npt_structs.root_paging = npt_paging;
-       cell->arch.svm.npt_structs.root_table =
+       cell->arch.svm.npt_iommu_structs.root_paging = npt_iommu_paging;
+       cell->arch.svm.npt_iommu_structs.root_table =
                (page_table_t)cell->arch.root_table_page;
 
        if (!has_avic) {
@@ -316,17 +360,15 @@ int vcpu_vendor_cell_init(struct cell *cell)
                 * Map xAPIC as is; reads are passed, writes are trapped.
                 */
                flags = PAGE_READONLY_FLAGS | PAGE_FLAG_US | PAGE_FLAG_DEVICE;
-               err = paging_create(&cell->arch.svm.npt_structs, XAPIC_BASE,
-                                   PAGE_SIZE, XAPIC_BASE,
-                                   flags,
-                                   PAGING_NON_COHERENT);
+               err = paging_create(&cell->arch.svm.npt_iommu_structs,
+                                   XAPIC_BASE, PAGE_SIZE, XAPIC_BASE,
+                                   flags, PAGING_NON_COHERENT);
        } else {
                flags = PAGE_DEFAULT_FLAGS | PAGE_FLAG_DEVICE;
-               err = paging_create(&cell->arch.svm.npt_structs,
+               err = paging_create(&cell->arch.svm.npt_iommu_structs,
                                    paging_hvirt2phys(avic_page),
                                    PAGE_SIZE, XAPIC_BASE,
-                                   flags,
-                                   PAGING_NON_COHERENT);
+                                   flags, PAGING_NON_COHERENT);
        }
        if (err)
                goto err_free_iopm;
@@ -354,21 +396,28 @@ int vcpu_map_memory_region(struct cell *cell,
        if (mem->flags & JAILHOUSE_MEM_COMM_REGION)
                phys_start = paging_hvirt2phys(&cell->comm_page);
 
-       return paging_create(&cell->arch.svm.npt_structs, phys_start, mem->size,
-                            mem->virt_start, flags, PAGING_NON_COHERENT);
+       flags |= amd_iommu_get_memory_region_flags(mem);
+
+       /*
+        * As we also manipulate the IOMMU page table, changes need to be
+        * coherent.
+        */
+       return paging_create(&cell->arch.svm.npt_iommu_structs, phys_start,
+                            mem->size, mem->virt_start, flags,
+                            PAGING_COHERENT);
 }
 
 int vcpu_unmap_memory_region(struct cell *cell,
                             const struct jailhouse_memory *mem)
 {
-       return paging_destroy(&cell->arch.svm.npt_structs, mem->virt_start,
-                             mem->size, PAGING_NON_COHERENT);
+       return paging_destroy(&cell->arch.svm.npt_iommu_structs,
+                             mem->virt_start, mem->size, PAGING_COHERENT);
 }
 
 void vcpu_vendor_cell_exit(struct cell *cell)
 {
-       paging_destroy(&cell->arch.svm.npt_structs, XAPIC_BASE, PAGE_SIZE,
-                      PAGING_NON_COHERENT);
+       paging_destroy(&cell->arch.svm.npt_iommu_structs, XAPIC_BASE,
+                      PAGE_SIZE, PAGING_NON_COHERENT);
        page_free(&mem_pool, cell->arch.svm.iopm, 3);
 }