Merge branch 'misc.namei' of git://git.kernel.org/pub/scm/linux/kernel/git/viro/vfs
[linux-2.6-microblaze.git] / arch / arm64 / kvm / hyp / pgtable.c
index 05321f4..f8ceebe 100644 (file)
 #include <asm/kvm_pgtable.h>
 #include <asm/stage2_pgtable.h>
 
-#define KVM_PTE_VALID                  BIT(0)
 
 #define KVM_PTE_TYPE                   BIT(1)
 #define KVM_PTE_TYPE_BLOCK             0
 #define KVM_PTE_TYPE_PAGE              1
 #define KVM_PTE_TYPE_TABLE             1
 
-#define KVM_PTE_ADDR_MASK              GENMASK(47, PAGE_SHIFT)
-#define KVM_PTE_ADDR_51_48             GENMASK(15, 12)
-
 #define KVM_PTE_LEAF_ATTR_LO           GENMASK(11, 2)
 
 #define KVM_PTE_LEAF_ATTR_LO_S1_ATTRIDX        GENMASK(4, 2)
@@ -40,6 +36,8 @@
 
 #define KVM_PTE_LEAF_ATTR_HI           GENMASK(63, 51)
 
+#define KVM_PTE_LEAF_ATTR_HI_SW                GENMASK(58, 55)
+
 #define KVM_PTE_LEAF_ATTR_HI_S1_XN     BIT(54)
 
 #define KVM_PTE_LEAF_ATTR_HI_S2_XN     BIT(54)
@@ -48,9 +46,7 @@
                                         KVM_PTE_LEAF_ATTR_LO_S2_S2AP_W | \
                                         KVM_PTE_LEAF_ATTR_HI_S2_XN)
 
-#define KVM_PTE_LEAF_ATTR_S2_IGNORED   GENMASK(58, 55)
-
-#define KVM_INVALID_PTE_OWNER_MASK     GENMASK(63, 56)
+#define KVM_INVALID_PTE_OWNER_MASK     GENMASK(9, 2)
 #define KVM_MAX_OWNER_ID               1
 
 struct kvm_pgtable_walk_data {
@@ -61,17 +57,6 @@ struct kvm_pgtable_walk_data {
        u64                             end;
 };
 
-static u64 kvm_granule_shift(u32 level)
-{
-       /* Assumes KVM_PGTABLE_MAX_LEVELS is 4 */
-       return ARM64_HW_PGTABLE_LEVEL_SHIFT(level);
-}
-
-static u64 kvm_granule_size(u32 level)
-{
-       return BIT(kvm_granule_shift(level));
-}
-
 #define KVM_PHYS_INVALID (-1ULL)
 
 static bool kvm_phys_is_valid(u64 phys)
@@ -79,15 +64,6 @@ static bool kvm_phys_is_valid(u64 phys)
        return phys < BIT(id_aa64mmfr0_parange_to_phys_shift(ID_AA64MMFR0_PARANGE_MAX));
 }
 
-static bool kvm_level_supports_block_mapping(u32 level)
-{
-       /*
-        * Reject invalid block mappings and don't bother with 4TB mappings for
-        * 52-bit PAs.
-        */
-       return !(level == 0 || (PAGE_SIZE != SZ_4K && level == 1));
-}
-
 static bool kvm_block_mapping_supported(u64 addr, u64 end, u64 phys, u32 level)
 {
        u64 granule = kvm_granule_size(level);
@@ -135,11 +111,6 @@ static u32 kvm_pgd_pages(u32 ia_bits, u32 start_level)
        return __kvm_pgd_page_idx(&pgt, -1ULL) + 1;
 }
 
-static bool kvm_pte_valid(kvm_pte_t pte)
-{
-       return pte & KVM_PTE_VALID;
-}
-
 static bool kvm_pte_table(kvm_pte_t pte, u32 level)
 {
        if (level == KVM_PGTABLE_MAX_LEVELS - 1)
@@ -151,16 +122,6 @@ static bool kvm_pte_table(kvm_pte_t pte, u32 level)
        return FIELD_GET(KVM_PTE_TYPE, pte) == KVM_PTE_TYPE_TABLE;
 }
 
-static u64 kvm_pte_to_phys(kvm_pte_t pte)
-{
-       u64 pa = pte & KVM_PTE_ADDR_MASK;
-
-       if (PAGE_SHIFT == 16)
-               pa |= FIELD_GET(KVM_PTE_ADDR_51_48, pte) << 48;
-
-       return pa;
-}
-
 static kvm_pte_t kvm_phys_to_pte(u64 pa)
 {
        kvm_pte_t pte = pa & KVM_PTE_ADDR_MASK;
@@ -326,6 +287,45 @@ int kvm_pgtable_walk(struct kvm_pgtable *pgt, u64 addr, u64 size,
        return _kvm_pgtable_walk(&walk_data);
 }
 
+struct leaf_walk_data {
+       kvm_pte_t       pte;
+       u32             level;
+};
+
+static int leaf_walker(u64 addr, u64 end, u32 level, kvm_pte_t *ptep,
+                      enum kvm_pgtable_walk_flags flag, void * const arg)
+{
+       struct leaf_walk_data *data = arg;
+
+       data->pte   = *ptep;
+       data->level = level;
+
+       return 0;
+}
+
+int kvm_pgtable_get_leaf(struct kvm_pgtable *pgt, u64 addr,
+                        kvm_pte_t *ptep, u32 *level)
+{
+       struct leaf_walk_data data;
+       struct kvm_pgtable_walker walker = {
+               .cb     = leaf_walker,
+               .flags  = KVM_PGTABLE_WALK_LEAF,
+               .arg    = &data,
+       };
+       int ret;
+
+       ret = kvm_pgtable_walk(pgt, ALIGN_DOWN(addr, PAGE_SIZE),
+                              PAGE_SIZE, &walker);
+       if (!ret) {
+               if (ptep)
+                       *ptep  = data.pte;
+               if (level)
+                       *level = data.level;
+       }
+
+       return ret;
+}
+
 struct hyp_map_data {
        u64                             phys;
        kvm_pte_t                       attr;
@@ -357,11 +357,47 @@ static int hyp_set_prot_attr(enum kvm_pgtable_prot prot, kvm_pte_t *ptep)
        attr |= FIELD_PREP(KVM_PTE_LEAF_ATTR_LO_S1_AP, ap);
        attr |= FIELD_PREP(KVM_PTE_LEAF_ATTR_LO_S1_SH, sh);
        attr |= KVM_PTE_LEAF_ATTR_LO_S1_AF;
+       attr |= prot & KVM_PTE_LEAF_ATTR_HI_SW;
        *ptep = attr;
 
        return 0;
 }
 
+enum kvm_pgtable_prot kvm_pgtable_hyp_pte_prot(kvm_pte_t pte)
+{
+       enum kvm_pgtable_prot prot = pte & KVM_PTE_LEAF_ATTR_HI_SW;
+       u32 ap;
+
+       if (!kvm_pte_valid(pte))
+               return prot;
+
+       if (!(pte & KVM_PTE_LEAF_ATTR_HI_S1_XN))
+               prot |= KVM_PGTABLE_PROT_X;
+
+       ap = FIELD_GET(KVM_PTE_LEAF_ATTR_LO_S1_AP, pte);
+       if (ap == KVM_PTE_LEAF_ATTR_LO_S1_AP_RO)
+               prot |= KVM_PGTABLE_PROT_R;
+       else if (ap == KVM_PTE_LEAF_ATTR_LO_S1_AP_RW)
+               prot |= KVM_PGTABLE_PROT_RW;
+
+       return prot;
+}
+
+static bool hyp_pte_needs_update(kvm_pte_t old, kvm_pte_t new)
+{
+       /*
+        * Tolerate KVM recreating the exact same mapping, or changing software
+        * bits if the existing mapping was valid.
+        */
+       if (old == new)
+               return false;
+
+       if (!kvm_pte_valid(old))
+               return true;
+
+       return !WARN_ON((old ^ new) & ~KVM_PTE_LEAF_ATTR_HI_SW);
+}
+
 static bool hyp_map_walker_try_leaf(u64 addr, u64 end, u32 level,
                                    kvm_pte_t *ptep, struct hyp_map_data *data)
 {
@@ -371,9 +407,8 @@ static bool hyp_map_walker_try_leaf(u64 addr, u64 end, u32 level,
        if (!kvm_block_mapping_supported(addr, end, phys, level))
                return false;
 
-       /* Tolerate KVM recreating the exact same mapping */
        new = kvm_init_valid_leaf_pte(phys, data->attr, level);
-       if (old != new && !WARN_ON(kvm_pte_valid(old)))
+       if (hyp_pte_needs_update(old, new))
                smp_store_release(ptep, new);
 
        data->phys += granule;
@@ -438,6 +473,8 @@ int kvm_pgtable_hyp_init(struct kvm_pgtable *pgt, u32 va_bits,
        pgt->start_level        = KVM_PGTABLE_MAX_LEVELS - levels;
        pgt->mm_ops             = mm_ops;
        pgt->mmu                = NULL;
+       pgt->force_pte_cb       = NULL;
+
        return 0;
 }
 
@@ -475,6 +512,9 @@ struct stage2_map_data {
        void                            *memcache;
 
        struct kvm_pgtable_mm_ops       *mm_ops;
+
+       /* Force mappings to page granularity */
+       bool                            force_pte;
 };
 
 u64 kvm_get_vtcr(u64 mmfr0, u64 mmfr1, u32 phys_shift)
@@ -539,11 +579,29 @@ static int stage2_set_prot_attr(struct kvm_pgtable *pgt, enum kvm_pgtable_prot p
 
        attr |= FIELD_PREP(KVM_PTE_LEAF_ATTR_LO_S2_SH, sh);
        attr |= KVM_PTE_LEAF_ATTR_LO_S2_AF;
+       attr |= prot & KVM_PTE_LEAF_ATTR_HI_SW;
        *ptep = attr;
 
        return 0;
 }
 
+enum kvm_pgtable_prot kvm_pgtable_stage2_pte_prot(kvm_pte_t pte)
+{
+       enum kvm_pgtable_prot prot = pte & KVM_PTE_LEAF_ATTR_HI_SW;
+
+       if (!kvm_pte_valid(pte))
+               return prot;
+
+       if (pte & KVM_PTE_LEAF_ATTR_LO_S2_S2AP_R)
+               prot |= KVM_PGTABLE_PROT_R;
+       if (pte & KVM_PTE_LEAF_ATTR_LO_S2_S2AP_W)
+               prot |= KVM_PGTABLE_PROT_W;
+       if (!(pte & KVM_PTE_LEAF_ATTR_HI_S2_XN))
+               prot |= KVM_PGTABLE_PROT_X;
+
+       return prot;
+}
+
 static bool stage2_pte_needs_update(kvm_pte_t old, kvm_pte_t new)
 {
        if (!kvm_pte_valid(old) || !kvm_pte_valid(new))
@@ -588,6 +646,15 @@ static bool stage2_pte_executable(kvm_pte_t pte)
        return !(pte & KVM_PTE_LEAF_ATTR_HI_S2_XN);
 }
 
+static bool stage2_leaf_mapping_allowed(u64 addr, u64 end, u32 level,
+                                       struct stage2_map_data *data)
+{
+       if (data->force_pte && (level < (KVM_PGTABLE_MAX_LEVELS - 1)))
+               return false;
+
+       return kvm_block_mapping_supported(addr, end, data->phys, level);
+}
+
 static int stage2_map_walker_try_leaf(u64 addr, u64 end, u32 level,
                                      kvm_pte_t *ptep,
                                      struct stage2_map_data *data)
@@ -597,7 +664,7 @@ static int stage2_map_walker_try_leaf(u64 addr, u64 end, u32 level,
        struct kvm_pgtable *pgt = data->mmu->pgt;
        struct kvm_pgtable_mm_ops *mm_ops = data->mm_ops;
 
-       if (!kvm_block_mapping_supported(addr, end, phys, level))
+       if (!stage2_leaf_mapping_allowed(addr, end, level, data))
                return -E2BIG;
 
        if (kvm_phys_is_valid(phys))
@@ -641,7 +708,7 @@ static int stage2_map_walk_table_pre(u64 addr, u64 end, u32 level,
        if (data->anchor)
                return 0;
 
-       if (!kvm_block_mapping_supported(addr, end, data->phys, level))
+       if (!stage2_leaf_mapping_allowed(addr, end, level, data))
                return 0;
 
        data->childp = kvm_pte_follow(*ptep, data->mm_ops);
@@ -771,6 +838,7 @@ int kvm_pgtable_stage2_map(struct kvm_pgtable *pgt, u64 addr, u64 size,
                .mmu            = pgt->mmu,
                .memcache       = mc,
                .mm_ops         = pgt->mm_ops,
+               .force_pte      = pgt->force_pte_cb && pgt->force_pte_cb(addr, addr + size, prot),
        };
        struct kvm_pgtable_walker walker = {
                .cb             = stage2_map_walker,
@@ -802,6 +870,7 @@ int kvm_pgtable_stage2_set_owner(struct kvm_pgtable *pgt, u64 addr, u64 size,
                .memcache       = mc,
                .mm_ops         = pgt->mm_ops,
                .owner_id       = owner_id,
+               .force_pte      = true,
        };
        struct kvm_pgtable_walker walker = {
                .cb             = stage2_map_walker,
@@ -995,6 +1064,9 @@ int kvm_pgtable_stage2_relax_perms(struct kvm_pgtable *pgt, u64 addr,
        u32 level;
        kvm_pte_t set = 0, clr = 0;
 
+       if (prot & KVM_PTE_LEAF_ATTR_HI_SW)
+               return -EINVAL;
+
        if (prot & KVM_PGTABLE_PROT_R)
                set |= KVM_PTE_LEAF_ATTR_LO_S2_S2AP_R;
 
@@ -1043,9 +1115,11 @@ int kvm_pgtable_stage2_flush(struct kvm_pgtable *pgt, u64 addr, u64 size)
        return kvm_pgtable_walk(pgt, addr, size, &walker);
 }
 
-int kvm_pgtable_stage2_init_flags(struct kvm_pgtable *pgt, struct kvm_arch *arch,
-                                 struct kvm_pgtable_mm_ops *mm_ops,
-                                 enum kvm_pgtable_stage2_flags flags)
+
+int __kvm_pgtable_stage2_init(struct kvm_pgtable *pgt, struct kvm_arch *arch,
+                             struct kvm_pgtable_mm_ops *mm_ops,
+                             enum kvm_pgtable_stage2_flags flags,
+                             kvm_pgtable_force_pte_cb_t force_pte_cb)
 {
        size_t pgd_sz;
        u64 vtcr = arch->vtcr;
@@ -1063,6 +1137,7 @@ int kvm_pgtable_stage2_init_flags(struct kvm_pgtable *pgt, struct kvm_arch *arch
        pgt->mm_ops             = mm_ops;
        pgt->mmu                = &arch->mmu;
        pgt->flags              = flags;
+       pgt->force_pte_cb       = force_pte_cb;
 
        /* Ensure zeroed PGD pages are visible to the hardware walker */
        dsb(ishst);
@@ -1102,77 +1177,3 @@ void kvm_pgtable_stage2_destroy(struct kvm_pgtable *pgt)
        pgt->mm_ops->free_pages_exact(pgt->pgd, pgd_sz);
        pgt->pgd = NULL;
 }
-
-#define KVM_PTE_LEAF_S2_COMPAT_MASK    (KVM_PTE_LEAF_ATTR_S2_PERMS | \
-                                        KVM_PTE_LEAF_ATTR_LO_S2_MEMATTR | \
-                                        KVM_PTE_LEAF_ATTR_S2_IGNORED)
-
-static int stage2_check_permission_walker(u64 addr, u64 end, u32 level,
-                                         kvm_pte_t *ptep,
-                                         enum kvm_pgtable_walk_flags flag,
-                                         void * const arg)
-{
-       kvm_pte_t old_attr, pte = *ptep, *new_attr = arg;
-
-       /*
-        * Compatible mappings are either invalid and owned by the page-table
-        * owner (whose id is 0), or valid with matching permission attributes.
-        */
-       if (kvm_pte_valid(pte)) {
-               old_attr = pte & KVM_PTE_LEAF_S2_COMPAT_MASK;
-               if (old_attr != *new_attr)
-                       return -EEXIST;
-       } else if (pte) {
-               return -EEXIST;
-       }
-
-       return 0;
-}
-
-int kvm_pgtable_stage2_find_range(struct kvm_pgtable *pgt, u64 addr,
-                                 enum kvm_pgtable_prot prot,
-                                 struct kvm_mem_range *range)
-{
-       kvm_pte_t attr;
-       struct kvm_pgtable_walker check_perm_walker = {
-               .cb             = stage2_check_permission_walker,
-               .flags          = KVM_PGTABLE_WALK_LEAF,
-               .arg            = &attr,
-       };
-       u64 granule, start, end;
-       u32 level;
-       int ret;
-
-       ret = stage2_set_prot_attr(pgt, prot, &attr);
-       if (ret)
-               return ret;
-       attr &= KVM_PTE_LEAF_S2_COMPAT_MASK;
-
-       for (level = pgt->start_level; level < KVM_PGTABLE_MAX_LEVELS; level++) {
-               granule = kvm_granule_size(level);
-               start = ALIGN_DOWN(addr, granule);
-               end = start + granule;
-
-               if (!kvm_level_supports_block_mapping(level))
-                       continue;
-
-               if (start < range->start || range->end < end)
-                       continue;
-
-               /*
-                * Check the presence of existing mappings with incompatible
-                * permissions within the current block range, and try one level
-                * deeper if one is found.
-                */
-               ret = kvm_pgtable_walk(pgt, start, granule, &check_perm_walker);
-               if (ret != -EEXIST)
-                       break;
-       }
-
-       if (!ret) {
-               range->start = start;
-               range->end = end;
-       }
-
-       return ret;
-}