@@ -155,6 +155,44 @@ int kvm_pgtable_stage2_map(void *cookie, u64 addr, u64 size, u64 phys,
*/
int kvm_pgtable_stage2_unmap(void *cookie, u64 addr, u64 size);
+/**
+ * kvm_pgtable_stage2_mkyoung() - Set the access flag in a page-table entry.
+ * @cookie: Opaque cookie allocated by kvm_pgtable_stage2_alloc_cookie().
+ * @addr: Intermediate physical address to identify the page-table entry.
+ *
+ * If there is a valid, leaf page-table entry used to translate @addr, then
+ * set the access flag in that entry.
+ *
+ * Return: The old page-table entry prior to setting the flag, 0 on failure.
+ */
+kvm_pte_t kvm_pgtable_stage2_mkyoung(void *cookie, u64 addr);
+
+/**
+ * kvm_pgtable_stage2_mkold() - Clear the access flag in a page-table entry.
+ * @cookie: Opaque cookie allocated by kvm_pgtable_stage2_alloc_cookie().
+ * @addr: Intermediate physical address to identify the page-table entry.
+ *
+ * If there is a valid, leaf page-table entry used to translate @addr, then
+ * clear the access flag in that entry.
+ *
+ * Note that it is the caller's responsibility to invalidate the TLB after
+ * calling this function to ensure that the updated permissions are visible
+ * to the CPUs.
+ *
+ * Return: The old page-table entry prior to clearing the flag, 0 on failure.
+ */
+kvm_pte_t kvm_pgtable_stage2_mkold(void *cookie, u64 addr);
+
+/**
+ * kvm_pgtable_stage2_is_young() - Test whether a page-table entry has the
+ * access flag set.
+ * @cookie: Opaque cookie allocated by kvm_pgtable_stage2_alloc_cookie().
+ * @addr: Intermediate physical address to identify the page-table entry.
+ *
+ * Return: True if the page-table entry has the access flag set, false otherwise.
+ */
+bool kvm_pgtable_stage2_is_young(void *cookie, u64 addr);
+
/**
* kvm_pgtable_get_pgd_phys() - Get physical pgd pointer for a page-table.
* @cookie: Opaque cookie allocated by kvm_pgtable_*_alloc_cookie().
@@ -709,6 +709,91 @@ int kvm_pgtable_stage2_unmap(void *cookie, u64 addr, u64 size)
return kvm_pgtable_walk(cookie, addr, size, &walker);
}
+
+struct stage2_attr_data {
+ kvm_pte_t attr_set;
+ kvm_pte_t attr_clr;
+ kvm_pte_t pte;
+};
+
+static int stage2_attr_walker(u64 addr, u64 end, u32 level, kvm_pte_t *ptep,
+ enum kvm_pgtable_walk_flags flag,
+ void * const arg)
+{
+ kvm_pte_t pte = *ptep;
+ struct stage2_attr_data *data = arg;
+
+ if (!kvm_pte_valid(pte))
+ return 0;
+
+ data->pte = pte;
+ pte &= ~data->attr_clr;
+ pte |= data->attr_set;
+
+ /*
+ * We may race with the CPU trying to set the access flag here,
+ * but worst-case the access flag update gets lost and will be
+ * set on the next access instead.
+ */
+ WRITE_ONCE(*ptep, pte);
+ return 0;
+}
+
+static int stage2_update_leaf_attrs(struct kvm_pgtable *pgt, u64 addr,
+ u64 size, kvm_pte_t attr_set,
+ kvm_pte_t attr_clr, kvm_pte_t *orig_pte)
+{
+ int ret;
+ kvm_pte_t attr_mask = KVM_PTE_LEAF_ATTR_LO | KVM_PTE_LEAF_ATTR_HI;
+ struct stage2_attr_data data = {
+ .attr_set = attr_set & attr_mask,
+ .attr_clr = attr_clr & attr_mask,
+ };
+ struct kvm_pgtable_walker walker = {
+ .cb = stage2_attr_walker,
+ .arg = &data,
+ .flags = KVM_PGTABLE_WALK_LEAF,
+ };
+
+ ret = kvm_pgtable_walk(pgt, addr, size, &walker);
+ if (ret)
+ return ret;
+
+ if (orig_pte)
+ *orig_pte = data.pte;
+ return 0;
+}
+
+kvm_pte_t kvm_pgtable_stage2_mkyoung(void *cookie, u64 addr)
+{
+ kvm_pte_t pte = 0;
+ stage2_update_leaf_attrs(cookie, addr, 1, KVM_PTE_LEAF_ATTR_LO_S2_AF, 0,
+ &pte);
+ dsb(ishst);
+ return pte;
+}
+
+kvm_pte_t kvm_pgtable_stage2_mkold(void *cookie, u64 addr)
+{
+ kvm_pte_t pte = 0;
+ stage2_update_leaf_attrs(cookie, addr, 1, 0, KVM_PTE_LEAF_ATTR_LO_S2_AF,
+ &pte);
+ /*
+ * "But where's the TLBI?!", you scream.
+ * "Over in the core code", I sigh.
+ *
+ * See the '->clear_flush_young()' callback on the KVM mmu notifier.
+ */
+ return pte;
+}
+
+bool kvm_pgtable_stage2_is_young(void *cookie, u64 addr)
+{
+ kvm_pte_t pte = 0;
+ stage2_update_leaf_attrs(cookie, addr, 1, 0, 0, &pte);
+ return !!(pte & KVM_PTE_LEAF_ATTR_LO_S2_AF);
+}
+
void *kvm_pgtable_stage2_alloc_cookie(struct kvm *kvm)
{
size_t pgd_sz;
Add stage-2 mkyoung(), mkold() and is_young() operations to the generic page-table code. Cc: Marc Zyngier <maz@kernel.org> Cc: Quentin Perret <qperret@google.com> Signed-off-by: Will Deacon <will@kernel.org> --- arch/arm64/include/asm/kvm_pgtable.h | 38 +++++++++++++ arch/arm64/kvm/pgtable.c | 85 ++++++++++++++++++++++++++++ 2 files changed, 123 insertions(+)